From 8b31dfa980a2185bb999e2fde4f5cf85f45ecacb Mon Sep 17 00:00:00 2001 From: acerdiv Date: Sat, 21 Jun 2025 00:41:45 +0300 Subject: [PATCH] pornofon --- ByteStream/ByteStream.js | 174 +++++++++++++++++++ Client/BrawlClient.js | 217 ++++++++++++++++++++++++ Config/config.js | 40 +++++ Protocol.js | 98 +++++++++++ Protocol/MessageFactory.js | 69 ++++++++ Protocol/Messages/ClientHelloMessage.js | 61 +++++++ Protocol/Messages/HeartbeatMessage.js | 49 ++++++ Protocol/Messages/KeepAliveMessage.js | 44 +++++ Protocol/Messages/LoginMessage.js | 71 ++++++++ Utils/Crypto.js | 101 +++++++++++ Utils/Logger.js | 160 +++++++++++++++++ index.js | 86 ++++++++++ package.json | 23 +++ readme.md | 0 14 files changed, 1193 insertions(+) create mode 100644 ByteStream/ByteStream.js create mode 100644 Client/BrawlClient.js create mode 100644 Config/config.js create mode 100644 Protocol.js create mode 100644 Protocol/MessageFactory.js create mode 100644 Protocol/Messages/ClientHelloMessage.js create mode 100644 Protocol/Messages/HeartbeatMessage.js create mode 100644 Protocol/Messages/KeepAliveMessage.js create mode 100644 Protocol/Messages/LoginMessage.js create mode 100644 Utils/Crypto.js create mode 100644 Utils/Logger.js create mode 100644 index.js create mode 100644 package.json create mode 100644 readme.md diff --git a/ByteStream/ByteStream.js b/ByteStream/ByteStream.js new file mode 100644 index 0000000..c9aed8f --- /dev/null +++ b/ByteStream/ByteStream.js @@ -0,0 +1,174 @@ +class ByteStream { + constructor(buffer = null) { + this.buffer = buffer || Buffer.alloc(0) + this.offset = 0 + } + + readUInt8() { + if (this.offset >= this.buffer.length) { + throw new Error("ByteStream: Cannot read beyond buffer length") + } + const value = this.buffer.readUInt8(this.offset) + this.offset += 1 + return value + } + + readUInt16() { + if (this.offset + 2 > this.buffer.length) { + throw new Error("ByteStream: Cannot read beyond buffer length") + } + const value = this.buffer.readUInt16BE(this.offset) + this.offset += 2 + return value + } + + readUInt24() { + if (this.offset + 3 > this.buffer.length) { + throw new Error("ByteStream: Cannot read beyond buffer length") + } + const value = + (this.buffer.readUInt8(this.offset) << 16) | + (this.buffer.readUInt8(this.offset + 1) << 8) | + this.buffer.readUInt8(this.offset + 2) + this.offset += 3 + return value + } + + readUInt32() { + if (this.offset + 4 > this.buffer.length) { + throw new Error("ByteStream: Cannot read beyond buffer length") + } + const value = this.buffer.readUInt32BE(this.offset) + this.offset += 4 + return value + } + + readInt32() { + if (this.offset + 4 > this.buffer.length) { + throw new Error("ByteStream: Cannot read beyond buffer length") + } + const value = this.buffer.readInt32BE(this.offset) + this.offset += 4 + return value + } + + readString() { + const length = this.readUInt32() + if (length === 0) return "" + + if (this.offset + length > this.buffer.length) { + throw new Error("ByteStream: Cannot read beyond buffer length") + } + + const value = this.buffer.toString("utf8", this.offset, this.offset + length) + this.offset += length + return value + } + + readBytes(length) { + if (this.offset + length > this.buffer.length) { + throw new Error("ByteStream: Cannot read beyond buffer length") + } + const value = this.buffer.slice(this.offset, this.offset + length) + this.offset += length + return value + } + + writeUInt8(value) { + const buf = Buffer.alloc(1) + buf.writeUInt8(value, 0) + this.buffer = Buffer.concat([this.buffer, buf]) + return this + } + + writeUInt16(value) { + const buf = Buffer.alloc(2) + buf.writeUInt16BE(value, 0) + this.buffer = Buffer.concat([this.buffer, buf]) + return this + } + + writeUInt24(value) { + const buf = Buffer.alloc(3) + buf.writeUInt8((value >> 16) & 0xff, 0) + buf.writeUInt8((value >> 8) & 0xff, 1) + buf.writeUInt8(value & 0xff, 2) + this.buffer = Buffer.concat([this.buffer, buf]) + return this + } + + writeUInt32(value) { + const buf = Buffer.alloc(4) + buf.writeUInt32BE(value, 0) + this.buffer = Buffer.concat([this.buffer, buf]) + return this + } + + writeInt32(value) { + const buf = Buffer.alloc(4) + buf.writeInt32BE(value, 0) + this.buffer = Buffer.concat([this.buffer, buf]) + return this + } + + writeString(value) { + this.writeUInt32(value.length) + if (value.length > 0) { + const buf = Buffer.from(value, "utf8") + this.buffer = Buffer.concat([this.buffer, buf]) + } + return this + } + + writeBytes(bytes) { + this.buffer = Buffer.concat([this.buffer, bytes]) + return this + } + + // Utility methods + getBuffer() { + return this.buffer + } + + getLength() { + return this.buffer.length + } + + getRemainingLength() { + return this.buffer.length - this.offset + } + + getOffset() { + return this.offset + } + + setOffset(offset) { + if (offset < 0 || offset > this.buffer.length) { + throw new Error("ByteStream: Invalid offset") + } + this.offset = offset + return this + } + + reset() { + this.buffer = Buffer.alloc(0) + this.offset = 0 + return this + } + + clone() { + const cloned = new ByteStream(Buffer.from(this.buffer)) + cloned.offset = this.offset + return cloned + } + + toHex() { + return this.buffer.toString("hex") + } + + static fromHex(hexString) { + return new ByteStream(Buffer.from(hexString, "hex")) + } +} + +module.exports = ByteStream diff --git a/Client/BrawlClient.js b/Client/BrawlClient.js new file mode 100644 index 0000000..3674f4d --- /dev/null +++ b/Client/BrawlClient.js @@ -0,0 +1,217 @@ +const net = require("net") +const MessageFactory = require("../Protocol/MessageFactory") +const { Crypto } = require("../Utils/Crypto") + +class BrawlClient { + constructor(config, logger) { + this.config = config + this.logger = logger + this.socket = null + this.connected = false + + this.crypto = new Crypto(config.crypto) + this.messageFactory = new MessageFactory(config, this.crypto, logger) + + this.packetTypes = [10100, 10101, 20108, 0] + this.currentPacketIndex = 0 + this.sequenceNumber = 0 + this.keepAliveCounter = 0 + + this.reconnectAttempts = 0 + this.keepAliveInterval = null + } + + connect() { + this.logger.info(`🌐 Connecting to ${this.config.server.host}:${this.config.server.port}...`) + + this.socket = new net.Socket() + this.socket.setTimeout(this.config.network.timeout) + + this.socket.connect(this.config.server.port, this.config.server.host, () => { + this.logger.connection("connected", { + host: this.config.server.host, + port: this.config.server.port, + }) + + this.connected = true + this.reconnectAttempts = 0 + this.startProtocolSequence() + }) + + this.socket.on("data", (data) => { + this.handleServerData(data) + }) + + this.socket.on("close", () => { + this.logger.connection("disconnected") + this.connected = false + this.stopKeepAlive() + this.attemptReconnect() + }) + + this.socket.on("error", (err) => { + this.logger.error("Connection error:", err.message) + this.connected = false + this.stopKeepAlive() + }) + + this.socket.on("timeout", () => { + this.logger.warn("Connection timeout") + this.socket.destroy() + }) + } + + handleServerData(data) { + try { + let decryptedData = data + if (this.config.crypto.enabled) { + decryptedData = this.crypto.decrypt(data) + this.logger.debug("šŸ”“ Packet decrypted") + } + + const message = this.messageFactory.parseMessage(decryptedData) + + switch (message.type) { + case 20100: + this.logger.success("šŸ” Server Hello received") + break + case 20103: + this.logger.success("āœ… Login OK received") + this.startKeepAlive() + break + case 20104: + this.logger.error("āŒ Login Failed received") + break + case 20108: + this.logger.debug("šŸ’“ Keep Alive response received") + break + default: + this.logger.info(`šŸ“¦ Unknown packet type: ${message.type}`) + } + } catch (error) { + this.logger.error("Error parsing server data:", error.message) + } + } + + sendMessage(type, data = null) { + if (!this.connected) { + this.logger.warn("āŒ Not connected to server") + return false + } + + try { + const message = this.messageFactory.createMessage(type, data) + const payload = message.encode() + const packet = this.messageFactory.createPacketHeader(type, payload) + + let finalPacket = packet + if (this.config.crypto.enabled) { + finalPacket = this.crypto.encrypt(packet) + this.logger.debug("šŸ” Packet encrypted") + } else { + this.logger.debug("šŸ“¤ Packet sent without encryption") + } + + this.socket.write(finalPacket) + + const typeName = this.getMessageTypeName(type) + this.logger.success(`āœ… ${typeName} sent successfully`) + + return true + } catch (error) { + this.logger.error(`šŸ’„ Error sending message ${type}:`, error.message) + return false + } + } + + getMessageTypeName(type) { + const names = { + 10100: "ClientHelloMessage", + 10101: "LoginMessage", + 20108: "KeepAliveMessage", + 0: "HeartbeatMessage", + } + return names[type] || `Unknown(${type})` + } + + startProtocolSequence() { + this.logger.info("šŸŽÆ Starting protocol sequence...") + + setTimeout(() => this.sendMessage(10100), 100) + setTimeout(() => this.sendMessage(10101), 1000) + setTimeout(() => this.sendMessage(0, { sequence: this.sequenceNumber++ }), 2000) + setTimeout(() => { + this.startPeriodicSending() + }, 5000) + } + + startPeriodicSending() { + setInterval(() => { + if (this.connected) { + const packetType = this.packetTypes[this.currentPacketIndex] + let data = null + + if (packetType === 0) { + data = { sequence: this.sequenceNumber++ } + } else if (packetType === 20108) { + data = { counter: this.keepAliveCounter++ } + } + + this.sendMessage(packetType, data) + this.currentPacketIndex = (this.currentPacketIndex + 1) % this.packetTypes.length + } + }, this.config.network.keepAliveInterval) + } + + startKeepAlive() { + this.keepAliveInterval = setInterval(() => { + if (this.connected) { + this.sendMessage(20108, { counter: this.keepAliveCounter++ }) + } + }, this.config.network.keepAliveInterval) + } + + stopKeepAlive() { + if (this.keepAliveInterval) { + clearInterval(this.keepAliveInterval) + this.keepAliveInterval = null + } + } + + attemptReconnect() { + if (this.reconnectAttempts < this.config.network.reconnectAttempts) { + this.reconnectAttempts++ + this.logger.info( + `šŸ”„ Attempting reconnection ${this.reconnectAttempts}/${this.config.network.reconnectAttempts}...`, + ) + + setTimeout(() => { + this.connect() + }, this.config.network.reconnectDelay) + } else { + this.logger.error("šŸ’€ Max reconnection attempts reached") + } + } + + disconnect() { + this.logger.info("šŸ”Œ Disconnecting from server...") + this.connected = false + this.stopKeepAlive() + + if (this.socket) { + this.socket.destroy() + this.socket = null + } + } + + getStatus() { + return { + connected: this.connected, + sequenceNumber: this.sequenceNumber, + keepAliveCounter: this.keepAliveCounter, + reconnectAttempts: this.reconnectAttempts, + } + } +} + +module.exports = BrawlClient diff --git a/Config/config.js b/Config/config.js new file mode 100644 index 0000000..3c7439a --- /dev/null +++ b/Config/config.js @@ -0,0 +1,40 @@ +module.exports = { + server: { + host: "127.0.0.1", + port: 9339, + }, + + auth: { + token: "", + }, + + crypto: { + rc4Key: "opahelpme", + nonce: "DSFDSDECFJDJDKSK", + enabled: false, + }, + + client: { + version: { + major: 52, + minor: 0, + build: 258, + }, + device: "XIAOMI", + language: "RU", + }, + + network: { + timeout: 30000, + keepAliveInterval: 5000, + reconnectAttempts: 3, + reconnectDelay: 2000, + }, + + logging: { + level: "debug", + saveToFile: true, + maxFileSize: "10MB", + maxFiles: 5, + }, +} diff --git a/Protocol.js b/Protocol.js new file mode 100644 index 0000000..05f087f --- /dev/null +++ b/Protocol.js @@ -0,0 +1,98 @@ +const ByteStream = require("./ByteStream") + +class Protocol { + constructor() { + this.version = "1.0.0" + this.clientVersion = 52 + } + + createPacketHeader(messageType, payload) { + const stream = new ByteStream() + stream.writeUInt16(messageType) + stream.writeUInt24(payload.length) + return Buffer.concat([stream.getBuffer(), payload]) + } + + createClientHello() { + const stream = new ByteStream() + + // Protocol version + stream.writeUInt32(1) + + // Key version + stream.writeUInt32(this.clientVersion) + + // Major version + stream.writeUInt32(52) + + // Minor version + stream.writeUInt32(0) + + // Build version + stream.writeUInt32(258) + + // Hash + stream.writeString("") + + return this.createPacketHeader(10100, stream.getBuffer()) + } + + createLoginMessage() { + const stream = new ByteStream() + + // High ID + stream.writeUInt32(0) + + // Low ID + stream.writeUInt32(Math.floor(Math.random() * 1000000)) + + // Token + stream.writeString("") + + // Major version + stream.writeUInt32(52) + + // Minor version + stream.writeUInt32(0) + + // Build version + stream.writeUInt32(258) + + // Hash + stream.writeString("") + + // Device + stream.writeString("OpenBrawlProject") + + // Language + stream.writeString("EN") + + return this.createPacketHeader(10101, stream.getBuffer()) + } + + createKeepAlive() { + const stream = new ByteStream() + // Keep alive doesn't need payload + return this.createPacketHeader(20108, stream.getBuffer()) + } + + createHeartbeat() { + const stream = new ByteStream() + // Heartbeat with timestamp + stream.writeUInt32(Date.now()) + return this.createPacketHeader(0, stream.getBuffer()) + } + + createCustomPacket() { + const stream = new ByteStream() + + // Custom data + stream.writeString("OpenBrawlProject") + stream.writeUInt32(Date.now()) + stream.writeUInt8(1) + + return this.createPacketHeader(6076, stream.getBuffer()) + } +} + +module.exports = Protocol diff --git a/Protocol/MessageFactory.js b/Protocol/MessageFactory.js new file mode 100644 index 0000000..b146185 --- /dev/null +++ b/Protocol/MessageFactory.js @@ -0,0 +1,69 @@ +const ClientHelloMessage = require("./Messages/ClientHelloMessage") +const LoginMessage = require("./Messages/LoginMessage") +const KeepAliveMessage = require("./Messages/KeepAliveMessage") +const HeartbeatMessage = require("./Messages/HeartbeatMessage") + +class MessageFactory { + constructor(config, crypto, logger) { + this.config = config + this.crypto = crypto + this.logger = logger + + this.messageTypes = { + 10100: ClientHelloMessage, + 10101: LoginMessage, + 20108: KeepAliveMessage, + 0: HeartbeatMessage, + // 6076: CustomMessage, + } + } + + createMessage(type, data = null) { + const MessageClass = this.messageTypes[type] + + if (!MessageClass) { + throw new Error(`Unknown message type: ${type}`) + } + + return new MessageClass(this.config, this.crypto, this.logger, data) + } + + parseMessage(buffer) { + try { + const ByteStream = require("../ByteStream/ByteStream") + const stream = new ByteStream(buffer) + + const messageType = stream.readUInt16() + const length = stream.readUInt24() + const payload = stream.readBytes(length) + + this.logger.packet("in", messageType, length, { payload: payload.toString("hex") }) + + return { + type: messageType, + length: length, + payload: payload, + } + } catch (error) { + this.logger.error("Failed to parse message:", error.message) + throw error + } + } + + createPacketHeader(messageType, payload) { + const ByteStream = require("../ByteStream/ByteStream") + const stream = new ByteStream() + + stream.writeUInt16(messageType) + stream.writeUInt24(payload.length) + + const header = stream.getBuffer() + const packet = Buffer.concat([header, payload]) + + this.logger.packet("out", messageType, packet.length, { payload: payload.toString("hex") }) + + return packet + } +} + +module.exports = MessageFactory diff --git a/Protocol/Messages/ClientHelloMessage.js b/Protocol/Messages/ClientHelloMessage.js new file mode 100644 index 0000000..390ea8a --- /dev/null +++ b/Protocol/Messages/ClientHelloMessage.js @@ -0,0 +1,61 @@ +const ByteStream = require("../../ByteStream/ByteStream") + +class ClientHelloMessage { + constructor(config, crypto, logger, data = null) { + this.config = config + this.crypto = crypto + this.logger = logger + this.type = 10100 + this.data = data + } + + encode() { + const stream = new ByteStream() + + // Protocol version + stream.writeUInt32(1) + + stream.writeUInt32(this.config.client.version.major) + + stream.writeUInt32(this.config.client.version.major) + + + stream.writeUInt32(this.config.client.version.minor) + + stream.writeUInt32(this.config.client.version.build) + + stream.writeString("") + + const nonce = this.crypto.generateNonce() + stream.writeBytes(nonce) + + this.logger.debug(`Encoding ClientHelloMessage`, { + version: this.config.client.version, + nonceLength: nonce.length, + }) + + return stream.getBuffer() + } + + decode(buffer) { + const stream = new ByteStream(buffer) + + const data = { + protocolVersion: stream.readUInt32(), + keyVersion: stream.readUInt32(), + majorVersion: stream.readUInt32(), + minorVersion: stream.readUInt32(), + buildVersion: stream.readUInt32(), + hash: stream.readString(), + } + + this.logger.debug(`Decoded ClientHelloMessage`, data) + return data + } + + getType() { + return this.type + } +} + +module.exports = ClientHelloMessage diff --git a/Protocol/Messages/HeartbeatMessage.js b/Protocol/Messages/HeartbeatMessage.js new file mode 100644 index 0000000..1091016 --- /dev/null +++ b/Protocol/Messages/HeartbeatMessage.js @@ -0,0 +1,49 @@ +const ByteStream = require("../../ByteStream/ByteStream") + +class HeartbeatMessage { + constructor(config, crypto, logger, data = null) { + this.config = config + this.crypto = crypto + this.logger = logger + this.type = 0 + this.data = data + } + + encode() { + const stream = new ByteStream() + + const timestamp = Math.floor(Date.now() / 1000) + stream.writeUInt32(timestamp) + + // Client status + stream.writeUInt8(1) // 1 = alive + + stream.writeUInt32(this.data?.sequence || 0) + + this.logger.debug(`Encoding HeartbeatMessage`, { + timestamp: timestamp, + sequence: this.data?.sequence || 0, + }) + + return stream.getBuffer() + } + + decode(buffer) { + const stream = new ByteStream(buffer) + + const data = { + timestamp: stream.readUInt32(), + status: stream.readUInt8(), + sequence: stream.readUInt32(), + } + + this.logger.debug(`Decoded HeartbeatMessage`, data) + return data + } + + getType() { + return this.type + } +} + +module.exports = HeartbeatMessage diff --git a/Protocol/Messages/KeepAliveMessage.js b/Protocol/Messages/KeepAliveMessage.js new file mode 100644 index 0000000..40111c7 --- /dev/null +++ b/Protocol/Messages/KeepAliveMessage.js @@ -0,0 +1,44 @@ +const ByteStream = require("../../ByteStream/ByteStream") + +class KeepAliveMessage { + constructor(config, crypto, logger, data = null) { + this.config = config + this.crypto = crypto + this.logger = logger + this.type = 20108 + this.data = data + } + + encode() { + const stream = new ByteStream() + + stream.writeUInt32(Math.floor(Date.now() / 1000)) + + stream.writeUInt32(this.data?.counter || 0) + + this.logger.debug(`Encoding KeepAliveMessage`, { + timestamp: Math.floor(Date.now() / 1000), + counter: this.data?.counter || 0, + }) + + return stream.getBuffer() + } + + decode(buffer) { + const stream = new ByteStream(buffer) + + const data = { + timestamp: stream.readUInt32(), + counter: stream.readUInt32(), + } + + this.logger.debug(`Decoded KeepAliveMessage`, data) + return data + } + + getType() { + return this.type + } +} + +module.exports = KeepAliveMessage diff --git a/Protocol/Messages/LoginMessage.js b/Protocol/Messages/LoginMessage.js new file mode 100644 index 0000000..d5f67c9 --- /dev/null +++ b/Protocol/Messages/LoginMessage.js @@ -0,0 +1,71 @@ +const ByteStream = require("../../ByteStream/ByteStream") + +class LoginMessage { + constructor(config, crypto, logger, data = null) { + this.config = config + this.crypto = crypto + this.logger = logger + this.type = 10101 + this.data = data + } + + encode() { + const stream = new ByteStream() + + stream.writeUInt32(0) + + + const playerId = Math.floor(Math.random() * 1000000) + stream.writeUInt32(playerId) + + const token = this.config.auth.token || "" + stream.writeString(token) + + stream.writeUInt32(this.config.client.version.major) + + stream.writeUInt32(this.config.client.version.minor) + + stream.writeUInt32(this.config.client.version.build) + + stream.writeString("") + + stream.writeString(this.config.client.device) + + stream.writeString(this.config.client.language) + + this.logger.debug(`Encoding LoginMessage`, { + playerId: playerId, + device: this.config.client.device, + language: this.config.client.language, + hasToken: token.length > 0, + cryptoEnabled: this.config.crypto.enabled, + }) + + return stream.getBuffer() + } + + decode(buffer) { + const stream = new ByteStream(buffer) + + const data = { + highId: stream.readUInt32(), + lowId: stream.readUInt32(), + token: stream.readString(), + majorVersion: stream.readUInt32(), + minorVersion: stream.readUInt32(), + buildVersion: stream.readUInt32(), + hash: stream.readString(), + device: stream.readString(), + language: stream.readString(), + } + + this.logger.debug(`Decoded LoginMessage`, data) + return data + } + + getType() { + return this.type + } +} + +module.exports = LoginMessage diff --git a/Utils/Crypto.js b/Utils/Crypto.js new file mode 100644 index 0000000..85a89cf --- /dev/null +++ b/Utils/Crypto.js @@ -0,0 +1,101 @@ +class RC4 { + constructor(key) { + this.key = Buffer.isBuffer(key) ? key : Buffer.from(key, "utf8") + this.s = new Array(256) + this.init() + } + + init() { + for (let i = 0; i < 256; i++) { + this.s[i] = i + } + + let j = 0 + for (let i = 0; i < 256; i++) { + j = (j + this.s[i] + this.key[i % this.key.length]) % 256 + this.swap(i, j) + } + } + + swap(i, j) { + const temp = this.s[i] + this.s[i] = this.s[j] + this.s[j] = temp + } + + encrypt(data) { + const input = Buffer.isBuffer(data) ? data : Buffer.from(data, "utf8") + const output = Buffer.alloc(input.length) + + let i = 0, + j = 0 + + for (let k = 0; k < input.length; k++) { + i = (i + 1) % 256 + j = (j + this.s[i]) % 256 + this.swap(i, j) + + const keystream = this.s[(this.s[i] + this.s[j]) % 256] + output[k] = input[k] ^ keystream + } + + return output + } + + decrypt(data) { + return this.encrypt(data) + } +} + +class Crypto { + constructor(config) { + this.config = config + this.rc4 = new RC4(config.rc4Key) + this.nonce = Buffer.from(config.nonce, "utf8") + } + + encrypt(data) { + if (!this.config.enabled) return data + + const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data) + return this.rc4.encrypt(buffer) + } + + decrypt(data) { + if (!this.config.enabled) return data + + const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data) + return this.rc4.decrypt(buffer) + } + + generateNonce() { + const timestamp = Date.now() + const random = Math.floor(Math.random() * 0xffffffff) + + const nonceBuffer = Buffer.alloc(16) + nonceBuffer.writeUInt32BE(timestamp >> 32, 0) + nonceBuffer.writeUInt32BE(timestamp & 0xffffffff, 4) + nonceBuffer.writeUInt32BE(random, 8) + nonceBuffer.writeUInt32BE(random ^ timestamp, 12) + + return nonceBuffer + } + + hash(data) { + const crypto = require("crypto") + return crypto.createHash("sha256").update(data).digest() + } + + xorWithNonce(data) { + const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data) + const result = Buffer.alloc(buffer.length) + + for (let i = 0; i < buffer.length; i++) { + result[i] = buffer[i] ^ this.nonce[i % this.nonce.length] + } + + return result + } +} + +module.exports = { RC4, Crypto } diff --git a/Utils/Logger.js b/Utils/Logger.js new file mode 100644 index 0000000..dbfca01 --- /dev/null +++ b/Utils/Logger.js @@ -0,0 +1,160 @@ +const fs = require("fs") +const path = require("path") + +class Logger { + constructor(config = {}) { + this.config = { + level: config.level || "info", + saveToFile: config.saveToFile || true, + maxFileSize: config.maxFileSize || "10MB", + maxFiles: config.maxFiles || 5, + logDir: config.logDir || "./logs", + } + + this.levels = { + debug: 0, + info: 1, + warn: 2, + error: 3, + success: 1, + } + + this.colors = { + debug: "\x1b[36m", // Cyan + info: "\x1b[34m", // Blue + warn: "\x1b[33m", // Yellow + error: "\x1b[31m", // Red + success: "\x1b[32m", // Green + reset: "\x1b[0m", + } + + this.initLogDirectory() + } + + initLogDirectory() { + if (this.config.saveToFile && !fs.existsSync(this.config.logDir)) { + fs.mkdirSync(this.config.logDir, { recursive: true }) + } + } + + formatMessage(level, message, data = null) { + const timestamp = new Date().toISOString() + const upperLevel = level.toUpperCase().padEnd(7) + + let logMessage = `[${timestamp}] [${upperLevel}] ${message}` + + if (data) { + logMessage += ` ${JSON.stringify(data, null, 2)}` + } + + return logMessage + } + + shouldLog(level) { + return this.levels[level] >= this.levels[this.config.level] + } + + writeToFile(message) { + if (!this.config.saveToFile) return + + const logFile = path.join(this.config.logDir, `openbrawl-${new Date().toISOString().split("T")[0]}.log`) + + try { + fs.appendFileSync(logFile, message + "\n") + this.rotateLogsIfNeeded(logFile) + } catch (error) { + console.error("Failed to write to log file:", error.message) + } + } + + rotateLogsIfNeeded(logFile) { + try { + const stats = fs.statSync(logFile) + const maxSize = this.parseSize(this.config.maxFileSize) + + if (stats.size > maxSize) { + const timestamp = new Date().toISOString().replace(/[:.]/g, "-") + const rotatedFile = logFile.replace(".log", `-${timestamp}.log`) + fs.renameSync(logFile, rotatedFile) + + this.cleanOldLogs() + } + } catch (error) { + console.error("Failed to rotate logs:", error.message) + } + } + + cleanOldLogs() { + try { + const files = fs + .readdirSync(this.config.logDir) + .filter((file) => file.startsWith("openbrawl-") && file.endsWith(".log")) + .map((file) => ({ + name: file, + path: path.join(this.config.logDir, file), + time: fs.statSync(path.join(this.config.logDir, file)).mtime, + })) + .sort((a, b) => b.time - a.time) + + if (files.length > this.config.maxFiles) { + files.slice(this.config.maxFiles).forEach((file) => { + fs.unlinkSync(file.path) + }) + } + } catch (error) { + console.error("Failed to clean old logs:", error.message) + } + } + + parseSize(sizeStr) { + const units = { B: 1, KB: 1024, MB: 1024 * 1024, GB: 1024 * 1024 * 1024 } + const match = sizeStr.match(/^(\d+)(B|KB|MB|GB)$/i) + + if (!match) return 10 * 1024 * 1024 // Default 10MB + + return Number.parseInt(match[1]) * units[match[2].toUpperCase()] + } + + log(level, message, data = null) { + if (!this.shouldLog(level)) return + + const formattedMessage = this.formatMessage(level, message, data) + const coloredMessage = `${this.colors[level]}${formattedMessage}${this.colors.reset}` + + console.log(coloredMessage) + this.writeToFile(formattedMessage) + } + + debug(message, data = null) { + this.log("debug", message, data) + } + + info(message, data = null) { + this.log("info", message, data) + } + + warn(message, data = null) { + this.log("warn", message, data) + } + + error(message, data = null) { + this.log("error", message, data) + } + + success(message, data = null) { + this.log("success", message, data) + } + + packet(direction, type, size, data = null) { + const arrow = direction === "in" ? "šŸ“Ø" : "šŸ“¤" + const message = `${arrow} ${direction.toUpperCase()} | Type: ${type} | Size: ${size} bytes` + this.debug(message, data) + } + + connection(status, details = null) { + const emoji = status === "connected" ? "āœ…" : status === "disconnected" ? "āŒ" : "šŸ”„" + this.info(`${emoji} Connection ${status}`, details) + } +} + +module.exports = Logger diff --git a/index.js b/index.js new file mode 100644 index 0000000..bd71cf8 --- /dev/null +++ b/index.js @@ -0,0 +1,86 @@ +const BrawlClient = require("./Client/BrawlClient") +const Logger = require("./Utils/Logger") +const config = require("./Config/config") + +const asciiArt = ` +╔═══════════════════════════════════════════════════════════════╗ +ā•‘ ā•‘ +ā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā•‘ +ā•‘ ā–ˆā–ˆā•”ā•ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā•ā•ā•ā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•— ā•‘ +ā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•”ā–ˆā–ˆā•— ā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•‘ ā•‘ +ā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā•ā• ā–ˆā–ˆā•”ā•ā•ā• ā–ˆā–ˆā•‘ā•šā–ˆā–ˆā•—ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•‘ ā•‘ +ā•‘ ā•šā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā•‘ ā•šā–ˆā–ˆā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā•‘ +ā•‘ ā•šā•ā•ā•ā•ā•ā• ā•šā•ā• ā•šā•ā•ā•ā•ā•ā•ā•ā•šā•ā• ā•šā•ā•ā•ā•ā•šā•ā•ā•ā•ā•ā• ā•šā•ā• ā•šā•ā•ā•šā•ā• ā•šā•ā• ā•‘ +ā•‘ ā•‘ +ā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•— ā–ˆā–ˆā•—ā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā•‘ +ā•‘ ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•— ā•‘ +ā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā•— ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā• ā•‘ +ā•‘ ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā•”ā•ā•ā•ā• ā•‘ +ā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā•šā–ˆā–ˆā–ˆā•”ā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•‘ ā•‘ +ā•‘ ā•šā•ā•ā•ā•ā•ā• ā•šā•ā• ā•šā•ā•ā•šā•ā• ā•šā•ā• ā•šā•ā•ā•ā•šā•ā•ā• ā•šā•ā•ā•ā•ā•ā•ā• ā•šā•ā• ā•‘ +ā•‘ ā•‘ +ā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā•‘ +ā•‘ ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•ā•ā•ā–ˆā–ˆā•— ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā•ā•ā•ā–ˆā–ˆā•”ā•ā•ā•ā•ā•ā•šā•ā•ā–ˆā–ˆā•”ā•ā•ā• ā•‘ +ā•‘ ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā•‘ +ā•‘ ā–ˆā–ˆā•”ā•ā•ā•ā• ā–ˆā–ˆā•”ā•ā•ā–ˆā–ˆā•—ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā–ˆā–ˆ ā–ˆā–ˆā•‘ā–ˆā–ˆā•”ā•ā•ā• ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā•‘ +ā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ ā–ˆā–ˆā•‘ā•šā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā•šā–ˆā–ˆā–ˆā–ˆā–ˆā•”ā•ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā•šā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•— ā–ˆā–ˆā•‘ ā•‘ +ā•‘ ā•šā•ā• ā•šā•ā• ā•šā•ā• ā•šā•ā•ā•ā•ā•ā• ā•šā•ā•ā•ā•ā• ā•šā•ā•ā•ā•ā•ā•ā• ā•šā•ā•ā•ā•ā•ā• ā•šā•ā• ā•‘ +ā•‘ ā•‘ +ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā• +` + +async function main() { + console.log(asciiArt) + + const logger = new Logger() + + logger.info("šŸš€ Starting OpenBrawl Client...") + await sleep(500) + + logger.info("šŸ“‹ Loading configuration...") + await sleep(300) + + logger.info("šŸ” Initializing encryption...") + await sleep(400) + + logger.info("🌐 Preparing network connection...") + await sleep(300) + + logger.info("šŸ“¦ Loading protocol handlers...") + await sleep(400) + + logger.success("āœ… All systems ready!") + await sleep(200) + + const readline = require("readline") + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }) + + rl.question("šŸ”‘ Enter password: ", (password) => { + if (password === "openproject") { + logger.success("šŸ”“ Password correct! Starting client...") + rl.close() + + const client = new BrawlClient(config, logger) + client.connect() + + process.on("SIGINT", () => { + logger.info("\nšŸ‘‹ Shutting down client...") + client.disconnect() + process.exit(0) + }) + } else { + logger.error("āŒ Invalid password!") + rl.close() + process.exit(1) + } + }) +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +main().catch(console.error) diff --git a/package.json b/package.json new file mode 100644 index 0000000..5d59a5a --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "openbrawl-client", + "version": "1.0.0", + "description": "Advanced Brawl Stars client for OpenBrawlProject with encryption and logging", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "node index.js", + "test": "node test.js" + }, + "keywords": ["brawl-stars", "client", "openbrawlproject", "nodejs", "rc4", "encryption", "logging"], + "author": "OpenBrawlProject", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, + "dependencies": {}, + "devDependencies": {}, + "repository": { + "type": "git", + "url": "https://github.com/OpenBrawlProject/openbrawl-client" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e69de29