Created
July 3, 2026 20:36
-
-
Save Kattoor/e8ff7f83c3cf631cb4e20b11e0152e48 to your computer and use it in GitHub Desktop.
Arcanists v9.0.1 rate reader
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const crypto = require('node:crypto'); | |
| const net = require('node:net'); | |
| const [username, password] = process.argv.slice(2); | |
| if (!username || !password) { | |
| console.error('Usage: node self-rating.js <username> <password>'); | |
| process.exit(1); | |
| } | |
| const aesKey = crypto.randomBytes(32); | |
| const clientId = 'Kattoor-rate-reader'; | |
| const serverKey = crypto.createPublicKey({ | |
| key: { | |
| kty: 'RSA', | |
| n: Buffer.from( | |
| 'mJbQP4q78dSvBUsCKXJXMnPQVVBtwEoC6IZH/n+jVWwT4P/AUw3pIGY69LzcLSjVErJmx2cTFHOk61mlqyQp0+51fUOkHL7Z9Xz7A486O9Sb85AtcUBfpyRuTdS9mgOVuXUeLzxwzuxiTqRRhLv7zOWPBze5nZG97luHkIWQ3AkjIdlurCIqeS2KHlwtdPgqzrAev/Qt38z/dug1bYJkxq3zVUBJR67JHR/WjBP6g8ZvU8dNTNNROU/gaaACnBOgx4IQOtJTAF4x9AWt3TG13opXlo/Pr5HD2FtWIcmPYf+cKLqtGd6tbN+GZvTgDsEic2w+HH9c5bRTHlBVcCvr3w==', | |
| 'base64', | |
| ).toString('base64url'), | |
| e: 'AQAB', | |
| }, | |
| format: 'jwk', | |
| }); | |
| class PacketReader { | |
| constructor(packet) { | |
| this.packet = packet; | |
| this.offset = 0; | |
| } | |
| readByte() { | |
| return this.packet[this.offset++]; | |
| } | |
| readInt16() { | |
| const value = this.packet.readInt16LE(this.offset); | |
| this.offset += 2; | |
| return value; | |
| } | |
| readInt32() { | |
| const value = this.packet.readInt32LE(this.offset); | |
| this.offset += 4; | |
| return value; | |
| } | |
| readString() { | |
| let length = 0; | |
| let shift = 0; | |
| let byte; | |
| do { | |
| byte = this.readByte(); | |
| length |= (byte & 127) << shift; | |
| shift += 7; | |
| } while (byte & 128); | |
| const value = this.packet.toString('utf8', this.offset, this.offset + length); | |
| this.offset += length; | |
| return value; | |
| } | |
| readBytes() { | |
| const length = this.readInt32(); | |
| const value = this.packet.subarray(this.offset, this.offset + length); | |
| this.offset += length; | |
| return value; | |
| } | |
| } | |
| function writeString(value) { | |
| const text = Buffer.from(value); | |
| const length = []; | |
| let remaining = text.length; | |
| do { | |
| length.push((remaining & 127) | (remaining > 127 ? 128 : 0)); | |
| remaining >>>= 7; | |
| } while (remaining); | |
| return Buffer.concat([Buffer.from(length), text]); | |
| } | |
| function writeBytes(value) { | |
| const length = Buffer.allocUnsafe(4); | |
| length.writeInt32LE(value.length); | |
| return Buffer.concat([length, value]); | |
| } | |
| function encrypt(packet) { | |
| const iv = crypto.randomBytes(16); | |
| const cipher = crypto.createCipheriv('aes-256-cbc', aesKey, iv); | |
| const encrypted = Buffer.concat([cipher.update(packet), cipher.final()]); | |
| return Buffer.concat([writeBytes(iv), writeBytes(encrypted)]); | |
| } | |
| function decrypt(packet) { | |
| const reader = new PacketReader(packet); | |
| const iv = reader.readBytes(); | |
| const encrypted = reader.readBytes(); | |
| const decipher = crypto.createDecipheriv('aes-256-cbc', aesKey, iv); | |
| return Buffer.concat([decipher.update(encrypted), decipher.final()]); | |
| } | |
| function makeLogin(serverRandom) { | |
| const login = Buffer.concat([ | |
| Buffer.from([1]), | |
| writeBytes(crypto.randomBytes(10)), | |
| writeString(clientId), | |
| writeString(clientId), | |
| writeString(username), | |
| writeString(password), | |
| writeBytes(serverRandom), | |
| Buffer.from([1]), | |
| writeBytes(aesKey), | |
| ]); | |
| return crypto.publicEncrypt( | |
| { | |
| key: serverKey, | |
| padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, | |
| oaepHash: 'sha1', | |
| }, | |
| login, | |
| ); | |
| } | |
| function readRatings(packet) { | |
| const reader = new PacketReader(packet); | |
| reader.readByte(); | |
| reader.readByte(); | |
| const entries = []; | |
| while (reader.offset < packet.length) { | |
| const count = reader.readInt32(); | |
| let entry = null; | |
| for (let i = 0; i < count; i += 1) { | |
| const name = reader.readString(); | |
| const rating = reader.readInt16(); | |
| reader.offset += 56; | |
| entry ??= { name, rating }; | |
| } | |
| entries.push(entry); | |
| } | |
| const modes = entries.length === 5 | |
| ? ['lts', 'hts', 'rnd', 'ele', 'teams'] | |
| : ['lts', 'hts', 'party', 'teams', 'casino', 'ele', 'rnd']; | |
| return { | |
| name: entries.find(Boolean)?.name ?? username, | |
| ratings: Object.fromEntries( | |
| modes.map((mode, i) => [mode, entries[i]?.rating ?? null]), | |
| ), | |
| }; | |
| } | |
| const socket = net.createConnection({ | |
| host: '18.117.245.0', | |
| port: 43594, | |
| }); | |
| let received = Buffer.alloc(0); | |
| let loggedIn = false; | |
| let requestedRatings = false; | |
| function send(packet) { | |
| const length = Buffer.allocUnsafe(4); | |
| length.writeUInt32BE(packet.length); | |
| socket.write(Buffer.concat([length, packet])); | |
| } | |
| socket.on('connect', () => { | |
| send(Buffer.from([0])); | |
| }); | |
| socket.on('data', (chunk) => { | |
| received = Buffer.concat([received, chunk]); | |
| while (received.length >= 4) { | |
| const length = received.readUInt32BE(); | |
| if (received.length < length + 4) return; | |
| let packet = received.subarray(4, length + 4); | |
| received = received.subarray(length + 4); | |
| if (loggedIn && packet.length > 1) { | |
| packet = decrypt(packet); | |
| } | |
| if (!loggedIn && packet[0] === 1) { | |
| const greeting = new PacketReader(packet); | |
| greeting.readByte(); | |
| greeting.readString(); | |
| send(makeLogin(greeting.readBytes())); | |
| loggedIn = true; | |
| } else if (packet[0] === 20 && !requestedRatings) { | |
| send(encrypt(Buffer.from([68, 1]))); | |
| requestedRatings = true; | |
| } else if (packet[0] === 68 && packet[1] === 1) { | |
| console.log(JSON.stringify(readRatings(packet))); | |
| socket.end(); | |
| return; | |
| } | |
| } | |
| }); | |
| socket.on('error', (error) => { | |
| console.error(error.message); | |
| process.exitCode = 1; | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment