Add support for native minecraft bedrock protocol, as some bedrock servers apparently don't respond to gamespy3. Fixes #211 (2.0.26)

This commit is contained in:
Michael Morrison 2021-02-12 11:07:50 -06:00
parent e4c29f9cbc
commit 4ecce4eff8
4 changed files with 104 additions and 9 deletions

View file

@ -1,3 +1,7 @@
### 2.0.26
* Added support for the native minecraft bedrock protocol, since some
bedrock servers apparently do not respond to the gamespy3 protocol.
### 2.0.25
* Support challenges in A2S_INFO (upcoming change to valve protocol)

View file

@ -24,7 +24,7 @@
],
"main": "lib/index.js",
"author": "GameDig Contributors",
"version": "2.0.25",
"version": "2.0.26",
"repository": {
"type": "git",
"url": "https://github.com/gamedig/node-gamedig.git"

View file

@ -1,7 +1,16 @@
const Core = require('./core'),
MinecraftVanilla = require('./minecraftvanilla'),
MinecraftBedrock = require('./minecraftbedrock'),
Gamespy3 = require('./gamespy3');
/*
Vanilla servers respond to minecraftvanilla only
Some modded vanilla servers respond to minecraftvanilla and gamespy3, or gamespy3 only
Some bedrock servers respond to gamespy3 only
Some bedrock servers respond to minecraftbedrock only
Unsure if any bedrock servers respond to gamespy3 and minecraftbedrock
*/
class Minecraft extends Core {
constructor() {
super();
@ -17,25 +26,40 @@ class Minecraft extends Core {
try { return await vanillaResolver.runOnceSafe(); } catch(e) {}
})());
const bedrockResolver = new Gamespy3();
bedrockResolver.options = {
const gamespyResolver = new Gamespy3();
gamespyResolver.options = {
...this.options,
encoding: 'utf8',
};
gamespyResolver.udpSocket = this.udpSocket;
promises.push((async () => {
try { return await gamespyResolver.runOnceSafe(); } catch(e) {}
})());
const bedrockResolver = new MinecraftBedrock();
bedrockResolver.options = this.options;
bedrockResolver.udpSocket = this.udpSocket;
promises.push((async () => {
try { return await bedrockResolver.runOnceSafe(); } catch(e) {}
})());
const [ vanillaState, bedrockState ] = await Promise.all(promises);
const [ vanillaState, gamespyState, bedrockState ] = await Promise.all(promises);
state.raw.vanilla = vanillaState;
state.raw.gamespy = gamespyState;
state.raw.bedrock = bedrockState;
if (!vanillaState && !bedrockState) {
if (!vanillaState && !gamespyState && !bedrockState) {
throw new Error('No protocols succeeded');
}
// Ordered from least worth to most worth (player names / etc)
if (bedrockState) {
if (bedrockState.name) state.name = bedrockState.name;
if (bedrockState.maxplayers) state.maxplayers = bedrockState.maxplayers;
if (bedrockState.players) state.players = bedrockState.players;
if (bedrockState.map) state.map = bedrockState.map;
}
if (vanillaState) {
try {
let name = '';
@ -54,10 +78,10 @@ class Minecraft extends Core {
if (vanillaState.maxplayers) state.maxplayers = vanillaState.maxplayers;
if (vanillaState.players) state.players = vanillaState.players;
}
if (bedrockState) {
if (bedrockState.name) state.name = bedrockState.name;
if (bedrockState.maxplayers) state.maxplayers = bedrockState.maxplayers;
if (bedrockState.players) state.players = bedrockState.players;
if (gamespyState) {
if (gamespyState.name) state.name = gamespyState.name;
if (gamespyState.maxplayers) state.maxplayers = gamespyState.maxplayers;
if (gamespyState.players) state.players = gamespyState.players;
}
// remove dupe spaces from name
state.name = state.name.replace(/\s+/g, ' ');

View file

@ -0,0 +1,67 @@
const Core = require('./core');
class MinecraftBedrock extends Core {
constructor() {
super();
this.byteorder = 'be';
}
async run(state) {
const bufs = [
Buffer.from([0x01]), // Message ID, ID_UNCONNECTED_PING
Buffer.from('0000000000000000', 'hex'), // Nonce / timestamp
Buffer.from('00ffff00fefefefefdfdfdfd12345678', 'hex'), // Magic
Buffer.from('0000000000000000', 'hex') // Cliend GUID
];
return await this.udpSend(Buffer.concat(bufs), buffer => {
const reader = this.reader(buffer);
const messageId = reader.uint(1);
if (messageId !== 0x1c) {
throw new Error('Invalid message id');
}
const nonce = reader.part(8).toString('hex'); // should match the nonce we sent
this.logger.debug('Nonce: ' + nonce);
state.raw.guid = reader.part(8).toString('hex');
const magic = reader.part(16).toString('hex');
this.logger.debug('Magic value: ' + magic);
const statusLen = reader.uint(2);
if (reader.remaining() !== statusLen) {
throw new Error('Invalid status length: ' + reader.remaining() + ' vs ' + statusLen);
}
const statusStr = reader.rest().toString('utf8');
this.logger.debug('Raw status str: ' + statusStr);
const split = statusStr.split(';');
if (split.length < 12) {
throw new Error('Missing enough chunks in status str');
}
let i = 0;
state.raw.edition = split[i++];
state.name = split[i++];
state.raw.protocolVersion = split[i++];
state.raw.mcVersion = split[i++];
state.players = parseInt(split[i++]);
state.maxplayers = parseInt(split[i++]);
state.raw.serverId = split[i++];
state.map = split[i++];
state.raw.gameMode = split[i++];
state.raw.nintendoOnly = !!parseInt(split[i++]);
state.raw.ipv4Port = split[i++];
state.raw.ipv6Port = split[i++];
return true;
});
}
}
module.exports = MinecraftBedrock;