|
|
|
@ -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 = (timestamp) => {
|
|
|
|
|
const buffer = Buffer.alloc(33);
|
|
|
|
|
buffer.writeUInt8(0x01, 0);
|
|
|
|
|
buffer.writeBigInt64LE(BigInt(pingId), 1);
|
|
|
|
|
Buffer.from("00ffff00fefefefefdfdfdfd12345678", "hex").copy(buffer, 9);
|
|
|
|
|
buffer.writeBigInt64LE(BigInt(0), 25);
|
|
|
|
|
buffer.writeUInt8(0x01, 0); // Packet ID
|
|
|
|
|
buffer.writeBigInt64LE(BigInt(timestamp), 1); // Timestamp
|
|
|
|
|
Buffer.from(MAGIC, "hex").copy(buffer, 9); // OFFLINE_MESSAGE_DATA_ID (Magic)
|
|
|
|
|
buffer.writeBigInt64LE(BigInt(0), 25); // Client GUID
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|