Skip to content

Instantly share code, notes, and snippets.

@Kattoor
Created July 3, 2026 20:36
Show Gist options
  • Select an option

  • Save Kattoor/e8ff7f83c3cf631cb4e20b11e0152e48 to your computer and use it in GitHub Desktop.

Select an option

Save Kattoor/e8ff7f83c3cf631cb4e20b11e0152e48 to your computer and use it in GitHub Desktop.
Arcanists v9.0.1 rate reader
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