From 18be5665403c8aba6627c2c3462892ace227cdb2 Mon Sep 17 00:00:00 2001 From: mmorrison Date: Tue, 15 Oct 2019 23:56:33 -0500 Subject: [PATCH] Improve minecraft protocol compatibility --- README.md | 2 +- games.txt | 4 +- protocols/minecraft.js | 108 +++++++++++++++------------------- protocols/minecraftvanilla.js | 77 ++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 64 deletions(-) create mode 100644 protocols/minecraftvanilla.js diff --git a/README.md b/README.md index 07860b9..0d7bb60 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ Games List | `mohpa` | Medal of Honor: Pacific Assault (2004) | `mohwf` | Medal of Honor: Warfighter (2012) | `medievalengineers` | Medieval Engineers (2015) -| `minecraft`
`minecraftping` | Minecraft (2009) | [Notes](#minecraft) +| `minecraft`
`minecraftping` | Minecraft (2009) | `minecraftpe`
`minecraftbe` | Minecraft: Bedrock Edition (2011) | `mnc` | Monday Night Combat (2011) | `mumble` | Mumble - GTmurmur Plugin (2005) | [Notes](#mumble) diff --git a/games.txt b/games.txt index 37e1188..2bbf25b 100644 --- a/games.txt +++ b/games.txt @@ -152,9 +152,9 @@ mohab|Medal of Honor: Airborne (2007)|gamespy1|port=12203,port_query_offset=97 moh2010|Medal of Honor (2010)|battlefield|port=7673,port_query=48888 mohwf|Medal of Honor: Warfighter (2012)|battlefield|port=25200,port_query_offset=22000 -minecraft,minecraftping|Minecraft (2009)|minecraft|port=25565|doc_notes=minecraft +minecraft,minecraftping|Minecraft (2009)|minecraft|port=25565 +minecraftpe,minecraftbe|Minecraft: Bedrock Edition (2011)|minecraft|port=19132 -minecraftpe,minecraftbe|Minecraft: Bedrock Edition (2011)|gamespy3|port=19132,maxAttempts=2 mnc|Monday Night Combat (2011)|valve|port=7777,port_query=27016 mtavc|Grand Theft Auto: Vice City - Multi Theft Auto (2002)|ase|port=22003,port_query_offset=123 mtasa|Grand Theft Auto: San Andreas - Multi Theft Auto (2004)|ase|port=22003,port_query_offset=123 diff --git a/protocols/minecraft.js b/protocols/minecraft.js index 536b87b..dc46c8f 100644 --- a/protocols/minecraft.js +++ b/protocols/minecraft.js @@ -1,5 +1,6 @@ const Core = require('./core'), - Varint = require('varint'); + MinecraftVanilla = require('./minecraftvanilla'), + Gamespy3 = require('./gamespy3'); class Minecraft extends Core { constructor() { @@ -7,72 +8,57 @@ class Minecraft extends Core { this.srvRecord = "_minecraft._tcp"; } async run(state) { - const portBuf = Buffer.alloc(2); - portBuf.writeUInt16BE(this.options.port,0); + const promises = []; - const addressBuf = Buffer.from(this.options.host,'utf8'); + const vanillaResolver = new MinecraftVanilla(); + vanillaResolver.options = this.options; + vanillaResolver.udpSocket = this.udpSocket; + promises.push((async () => { + try { return await vanillaResolver.runOnceSafe(); } catch(e) {} + })()); - const bufs = [ - this.varIntBuffer(4), - this.varIntBuffer(addressBuf.length), - addressBuf, - portBuf, - this.varIntBuffer(1) - ]; + const bedrockResolver = new Gamespy3(); + bedrockResolver.options = { + ...this.options, + encoding: 'utf8', + }; + bedrockResolver.udpSocket = this.udpSocket; + promises.push((async () => { + try { return await bedrockResolver.runOnceSafe(); } catch(e) {} + })()); - const outBuffer = Buffer.concat([ - this.buildPacket(0,Buffer.concat(bufs)), - this.buildPacket(0) - ]); + const [ vanillaState, bedrockState ] = await Promise.all(promises); - const data = await this.withTcp(async socket => { - return await this.tcpSend(socket, outBuffer, data => { - if(data.length < 10) return; - const reader = this.reader(data); - const length = reader.varint(); - if(data.length < length) return; - return reader.rest(); - }); - }); + state.raw.vanilla = vanillaState; + state.raw.bedrock = bedrockState; - const reader = this.reader(data); - - const packetId = reader.varint(); - this.debugLog("Packet ID: "+packetId); - - const strLen = reader.varint(); - this.debugLog("String Length: "+strLen); - - const str = reader.rest().toString('utf8'); - this.debugLog(str); - - const json = JSON.parse(str); - delete json.favicon; - - state.raw = json; - state.maxplayers = json.players.max; - if(json.players.sample) { - for(const player of json.players.sample) { - state.players.push({ - id: player.id, - name: player.name - }); - } + if (vanillaState) { + try { + let name = ''; + const description = vanillaState.raw.description; + if (typeof description === 'string') { + name = description; + } + if (!name && typeof description === 'object' && description.text) { + name = description.text; + } + if (!name && typeof description === 'object' && description.extra) { + name = description.extra.map(part => part.text).join(''); + } + state.name = name; + } catch(e) {} + if (vanillaState.maxplayers) state.maxplayers = vanillaState.maxplayers; + if (vanillaState.players) state.players = vanillaState.players; } - state.players = json.players.online; - } - - varIntBuffer(num) { - return Buffer.from(Varint.encode(num)); - } - buildPacket(id,data) { - if(!data) data = Buffer.from([]); - const idBuffer = this.varIntBuffer(id); - return Buffer.concat([ - this.varIntBuffer(data.length+idBuffer.length), - idBuffer, - data - ]); + if (bedrockState) { + if (bedrockState.name) state.name = bedrockState.name; + if (bedrockState.maxplayers) state.maxplayers = bedrockState.maxplayers; + if (bedrockState.players) state.players = bedrockState.players; + } + // remove dupe spaces from name + state.name = state.name.replace(/\s+/g, ' '); + // remove color codes from name + state.name = state.name.replace(/\u00A7./g, ''); } } diff --git a/protocols/minecraftvanilla.js b/protocols/minecraftvanilla.js new file mode 100644 index 0000000..c55294e --- /dev/null +++ b/protocols/minecraftvanilla.js @@ -0,0 +1,77 @@ +const Core = require('./core'), + Varint = require('varint'); + +class MinecraftVanilla extends Core { + async run(state) { + const portBuf = Buffer.alloc(2); + portBuf.writeUInt16BE(this.options.port,0); + + const addressBuf = Buffer.from(this.options.host,'utf8'); + + const bufs = [ + this.varIntBuffer(47), + this.varIntBuffer(addressBuf.length), + addressBuf, + portBuf, + this.varIntBuffer(1) + ]; + + const outBuffer = Buffer.concat([ + this.buildPacket(0,Buffer.concat(bufs)), + this.buildPacket(0) + ]); + + const data = await this.withTcp(async socket => { + return await this.tcpSend(socket, outBuffer, data => { + if(data.length < 10) return; + const reader = this.reader(data); + const length = reader.varint(); + if(data.length < length) return; + return reader.rest(); + }); + }); + + const reader = this.reader(data); + + const packetId = reader.varint(); + this.debugLog("Packet ID: "+packetId); + + const strLen = reader.varint(); + this.debugLog("String Length: "+strLen); + + const str = reader.rest().toString('utf8'); + this.debugLog(str); + + const json = JSON.parse(str); + delete json.favicon; + + state.raw = json; + state.maxplayers = json.players.max; + if(json.players.sample) { + for(const player of json.players.sample) { + state.players.push({ + id: player.id, + name: player.name + }); + } + } + for (let i = 0; i < Math.min(json.players.online, 10000); i++) { + state.players.push({}); + } + } + + varIntBuffer(num) { + return Buffer.from(Varint.encode(num)); + } + buildPacket(id,data) { + if(!data) data = Buffer.from([]); + const idBuffer = this.varIntBuffer(id); + return Buffer.concat([ + this.varIntBuffer(data.length+idBuffer.length), + idBuffer, + data + ]); + } +} + +module.exports = MinecraftVanilla;