pornofon
This commit is contained in:
parent
16b6f2f2d6
commit
8b31dfa980
174
ByteStream/ByteStream.js
Normal file
174
ByteStream/ByteStream.js
Normal file
@ -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
|
217
Client/BrawlClient.js
Normal file
217
Client/BrawlClient.js
Normal file
@ -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
|
40
Config/config.js
Normal file
40
Config/config.js
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
}
|
98
Protocol.js
Normal file
98
Protocol.js
Normal file
@ -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
|
69
Protocol/MessageFactory.js
Normal file
69
Protocol/MessageFactory.js
Normal file
@ -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
|
61
Protocol/Messages/ClientHelloMessage.js
Normal file
61
Protocol/Messages/ClientHelloMessage.js
Normal file
@ -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
|
49
Protocol/Messages/HeartbeatMessage.js
Normal file
49
Protocol/Messages/HeartbeatMessage.js
Normal file
@ -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
|
44
Protocol/Messages/KeepAliveMessage.js
Normal file
44
Protocol/Messages/KeepAliveMessage.js
Normal file
@ -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
|
71
Protocol/Messages/LoginMessage.js
Normal file
71
Protocol/Messages/LoginMessage.js
Normal file
@ -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
|
101
Utils/Crypto.js
Normal file
101
Utils/Crypto.js
Normal file
@ -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 }
|
160
Utils/Logger.js
Normal file
160
Utils/Logger.js
Normal file
@ -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
|
86
index.js
Normal file
86
index.js
Normal file
@ -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)
|
23
package.json
Normal file
23
package.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user