refactor!: changes in bedrock protocol code

BREAKING CHANGE: new bedrock ping response format
This commit is contained in:
Timofey Gelazoniya 2024-03-31 01:56:03 +03:00
parent 9d25aaf4ea
commit 296294ca96
Signed by: zeldon
GPG Key ID: 047886915281DD2A
2 changed files with 47 additions and 95 deletions

View File

@ -1,96 +1,60 @@
/** /**
* Implementation of the RakNet ping/pong protocol. * Implementation of the RakNet ping/pong protocol.
* @see https://wiki.vg/Raknet_Protocol#Unconnected_Ping * @see https://wiki.vg/Raknet_Protocol
*
* Data types:
* @see https://wiki.vg/Raknet_Protocol#Data_types
*/ */
'use strict'; 'use strict';
import dgram from 'dgram'; import dgram from 'dgram';
const MAGIC = "00ffff00fefefefefdfdfdfd12345678";
const START_TIME = new Date().getTime(); const START_TIME = new Date().getTime();
/** /**
* Reads a BigInt value from the buffer at the specified offset using big-endian byte order. * Creates an Unconnected Ping packet.
* @param {Buffer} buffer - The buffer to read from. * @param {number} pingId
* @param {number} offset - The offset in the buffer to read the value. * @returns {Buffer}
* @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.
* @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Ping} * @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Ping}
*/ */
const UNCONNECTED_PING = (pingId) => { const createUnconnectedPingFrame = (pingId) => {
const buffer = Buffer.alloc(33); const buffer = Buffer.alloc(33);
buffer.writeUInt8(0x01, 0); buffer.writeUInt8(0x01, 0);
buffer.writeBigInt64LE(BigInt(pingId), 1); buffer.writeBigInt64LE(BigInt(pingId), 1);
Buffer.from("00ffff00fefefefefdfdfdfd12345678", "hex").copy(buffer, 9); Buffer.from(MAGIC, "hex").copy(buffer, 9);
buffer.writeBigInt64LE(BigInt(0), 25); buffer.writeBigInt64LE(BigInt(0), 25);
return buffer; return buffer;
}; };
/** /**
* Decodes an Unconnected Pong buffer and returns the parsed data. * Extract Modt from Unconnected Pong Packet and convert to an object
* @param {Buffer} buffer - The Unconnected Pong buffer. * @param {Buffer} unconnectedPongPacket
* @returns {Object} - The parsed Unconnected Pong data. * @returns {Object}
* @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Pong} * @see {@link https://wiki.vg/Raknet_Protocol#Unconnected_Pong}
*/ */
const UNCONNECTED_PONG = (buffer) => { const extractModt = (unconnectedPongPacket) => {
const pingId = readBigInt64BE(buffer, 1); // Skip everything to Modt
const serverId = readBigInt64BE(buffer, 9); const offset = 33;
let offset = 25; const length = unconnectedPongPacket.readUInt16BE(offset);
let advertiseStr; let modt = unconnectedPongPacket.toString("utf-8", offset + 2, offset + 2 + length);
try { const components = modt.split(';');
advertiseStr = readStringFromBuffer(buffer, offset); const parsedComponents = {
} catch (err) { edition: components[0],
const length = parseInt(err.message.substr(err.message.indexOf(',') + 2, 3)); name: components[1],
advertiseStr = buffer.toString('utf8', offset, offset + length); 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 parsedComponents;
return { pingId, advertiseStr, serverId, offset, ...parsedAdvertiseStr };
}; };
/** /**
@ -133,34 +97,20 @@ const ping = (host, port = 19132, cb, timeout = 5000) => {
}; };
try { 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); socket.send(ping, 0, ping.length, port, host);
} catch (err) { } catch (err) {
handleError(err); handleError(err);
} }
socket.on('message', (msg) => { socket.on('message', (pongPacket) => {
const id = msg[0]; const id = pongPacket[0];
switch (id) { switch (id) {
case 0x1c: { case 0x1c: {
const pong = UNCONNECTED_PONG(msg); const modtObject = extractModt(pongPacket);
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
};
closeSocket(); closeSocket();
cb(clientData, null); cb(modtObject, null);
break; break;
} }

View File

@ -3,22 +3,24 @@
* @param timeout The read/write socket timeout. * @param timeout The read/write socket timeout.
*/ */
export type BedrockPingOptions = { export type BedrockPingOptions = {
port?: number | undefined, port?: number;
timeout?: number | undefined; timeout?: number;
}; };
export type BedrockPingResponse = { export type BedrockPingResponse = {
edition: string;
name: string; name: string;
version: { version: {
gameVersion: string; protocolVersion: number;
protocol: string; minecraftVersion: string;
}; };
players: { players: {
max: string; online: number;
online: string; max: number;
}; };
description: string; serverId: string;
gamemode: string; mapName: string;
gameMode: string;
}; };
/** /**