Skip to content

Commit

Permalink
Add support for native minecraft bedrock protocol, as some bedrock se…
Browse files Browse the repository at this point in the history
…rvers apparently don't respond to gamespy3. Fixes gamedig#211 (2.0.26)
  • Loading branch information
mmorrisontx committed Feb 12, 2021
1 parent e4c29f9 commit 4ecce4e
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
40 changes: 32 additions & 8 deletions protocols/minecraft.js
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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 = '';
Expand All @@ -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, ' ');
Expand Down
67 changes: 67 additions & 0 deletions protocols/minecraftbedrock.js
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit 4ecce4e

Please sign in to comment.