From 296294ca9601f70a5a3a840f16e84f5d6304e7cc Mon Sep 17 00:00:00 2001 From: xzeldon Date: Sun, 31 Mar 2024 01:56:03 +0300 Subject: [PATCH] refactor!: changes in bedrock protocol code BREAKING CHANGE: new bedrock ping response format --- lib/bedrock.js | 124 ++++++++++++----------------------------- types/lib/bedrock.d.ts | 18 +++--- 2 files changed, 47 insertions(+), 95 deletions(-) diff --git a/lib/bedrock.js b/lib/bedrock.js index c1426d6..212b199 100644 --- a/lib/bedrock.js +++ b/lib/bedrock.js @@ -1,96 +1,60 @@ /** * Implementation of the RakNet ping/pong protocol. - * @see https://wiki.vg/Raknet_Protocol#Unconnected_Ping - * - * Data types: - * @see https://wiki.vg/Raknet_Protocol#Data_types + * @see https://wiki.vg/Raknet_Protocol */ 'use strict'; import dgram from 'dgram'; +const MAGIC = "00ffff00fefefefefdfdfdfd12345678"; const START_TIME = new Date().getTime(); /** - * Reads a BigInt value from the buffer at the specified offset using big-endian byte order. - * @param {Buffer} buffer - The buffer to read from. - * @param {number} offset - The offset in the buffer to read the value. - * @returns {BigInt} - The read BigInt value. - */ -const readBigInt64BE = (buffer, offset) => { - return buffer.readBigInt64BE(offset); -}; - -/** - * Reads a string from the buffer at the specified offset. - * @param {Buffer} buffer - The buffer to read from. - * @param {number} offset - The offset in the buffer to read the string. - * @returns {string} - The read string. - */ -const readStringFromBuffer = (buffer, offset) => { - const length = buffer.readUInt16BE(offset); - return buffer.toString('utf8', offset + 2, offset + 2 + length); -}; - -/** - * Parses the advertise string into an object with properties. - * @param {string} advertiseStr - The advertise string to parse. - * @returns {Object} - The parsed object with properties. - */ -const parseAdvertiseString = (advertiseStr) => { - const parts = advertiseStr.split(';'); - const parsedParts = { - gameId: parts[0], - description: parts[1], - protocolVersion: parts[2], - gameVersion: parts[3], - currentPlayers: parts[4], - maxPlayers: parts[5], - name: parts[7], - mode: parts[8] - }; - - return parsedParts; -}; - -/** - * Creates an Unconnected Ping buffer. - * @param {number} pingId - The ping ID. - * @returns {Buffer} - The Unconnected Ping buffer. + * Creates an Unconnected Ping packet. + * @param {number} pingId + * @returns {Buffer} * @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Ping} */ -const UNCONNECTED_PING = (pingId) => { +const createUnconnectedPingFrame = (pingId) => { const buffer = Buffer.alloc(33); buffer.writeUInt8(0x01, 0); buffer.writeBigInt64LE(BigInt(pingId), 1); - Buffer.from("00ffff00fefefefefdfdfdfd12345678", "hex").copy(buffer, 9); + Buffer.from(MAGIC, "hex").copy(buffer, 9); buffer.writeBigInt64LE(BigInt(0), 25); return buffer; }; /** - * Decodes an Unconnected Pong buffer and returns the parsed data. - * @param {Buffer} buffer - The Unconnected Pong buffer. - * @returns {Object} - The parsed Unconnected Pong data. + * Extract Modt from Unconnected Pong Packet and convert to an object + * @param {Buffer} unconnectedPongPacket + * @returns {Object} * @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Pong} */ -const UNCONNECTED_PONG = (buffer) => { - const pingId = readBigInt64BE(buffer, 1); - const serverId = readBigInt64BE(buffer, 9); - let offset = 25; - let advertiseStr; +const extractModt = (unconnectedPongPacket) => { + // Skip everything to Modt + const offset = 33; + const length = unconnectedPongPacket.readUInt16BE(offset); + let modt = unconnectedPongPacket.toString("utf-8", offset + 2, offset + 2 + length); - try { - advertiseStr = readStringFromBuffer(buffer, offset); - } catch (err) { - const length = parseInt(err.message.substr(err.message.indexOf(',') + 2, 3)); - advertiseStr = buffer.toString('utf8', offset, offset + length); - } + const components = modt.split(';'); + const parsedComponents = { + edition: components[0], + name: components[1], + version: { + protocolVersion: Number(components[2]), + minecraftVersion: components[3], + }, + players: { + online: Number(components[4]), + max: Number(components[5]) + }, + serverId: components[6], + mapName: components[7], + gameMode: components[8] + }; - const parsedAdvertiseStr = parseAdvertiseString(advertiseStr); - - return { pingId, advertiseStr, serverId, offset, ...parsedAdvertiseStr }; + return parsedComponents; }; /** @@ -133,34 +97,20 @@ const ping = (host, port = 19132, cb, timeout = 5000) => { }; try { - const ping = UNCONNECTED_PING(new Date().getTime() - START_TIME); + const ping = createUnconnectedPingFrame(new Date().getTime() - START_TIME); socket.send(ping, 0, ping.length, port, host); } catch (err) { handleError(err); } - socket.on('message', (msg) => { - const id = msg[0]; + socket.on('message', (pongPacket) => { + const id = pongPacket[0]; switch (id) { case 0x1c: { - const pong = UNCONNECTED_PONG(msg); - const clientData = { - name: pong.name, - version: { - gameVersion: pong.gameVersion, - protocol: pong.protocolVersion - }, - players: { - max: pong.maxPlayers, - online: pong.currentPlayers - }, - description: pong.description.replace(/\xA7[0-9A-FK-OR]/ig, ''), - gamemode: pong.mode - }; - + const modtObject = extractModt(pongPacket); closeSocket(); - cb(clientData, null); + cb(modtObject, null); break; } diff --git a/types/lib/bedrock.d.ts b/types/lib/bedrock.d.ts index 127e446..ac58424 100644 --- a/types/lib/bedrock.d.ts +++ b/types/lib/bedrock.d.ts @@ -3,22 +3,24 @@ * @param timeout The read/write socket timeout. */ export type BedrockPingOptions = { - port?: number | undefined, - timeout?: number | undefined; + port?: number; + timeout?: number; }; export type BedrockPingResponse = { + edition: string; name: string; version: { - gameVersion: string; - protocol: string; + protocolVersion: number; + minecraftVersion: string; }; players: { - max: string; - online: string; + online: number; + max: number; }; - description: string; - gamemode: string; + serverId: string; + mapName: string; + gameMode: string; }; /**