From f6ad1d8061cbc2e3f126543ecf68806ac97e0803 Mon Sep 17 00:00:00 2001 From: deathcap Date: Sat, 23 Jan 2016 20:47:29 -0800 Subject: [PATCH] Move FML|HS to fml.js --- examples/client_forge/client_forge.js | 280 +------------------------ src/createClient.js | 8 + src/fml.js | 285 ++++++++++++++++++++++++++ 3 files changed, 294 insertions(+), 279 deletions(-) create mode 100644 src/fml.js diff --git a/examples/client_forge/client_forge.js b/examples/client_forge/client_forge.js index cd8d9ed..fa19c10 100644 --- a/examples/client_forge/client_forge.js +++ b/examples/client_forge/client_forge.js @@ -1,6 +1,4 @@ var mc = require('minecraft-protocol'); -var ProtoDef = require('protodef').ProtoDef; -var assert = require('assert'); if(process.argv.length < 4 || process.argv.length > 6) { console.log("Usage : node echo.js [] []"); @@ -12,267 +10,6 @@ var port = parseInt(process.argv[3]); var username = process.argv[4] ? process.argv[4] : "echo"; var password = process.argv[5]; -var proto = new ProtoDef(); -// copied from ../../dist/transforms/serializer.js TODO: refactor -proto.addType("string", ["pstring", { - countType: "varint" - }]); - - -// http://wiki.vg/Minecraft_Forge_Handshake -proto.addType('fml|hsMapper', - [ - "mapper", - { - "type": "byte", - "mappings": { - "0": "ServerHello", - "1": "ClientHello", - "2": "ModList", - "3": "RegistryData", - "-1": "HandshakeAck", - "-2": "HandshakeReset" - }, - } - ] -); - -proto.addType('FML|HS', - [ - "container", - [ - { - "name": "discriminator", - "type": "fml|hsMapper" - }, - - { - "anon": true, - "type": - [ - "switch", - { - "compareTo": "discriminator", - "fields": - { - "ServerHello": - [ - "container", - [ - { - "name": "fmlProtocolVersion", - "type": "byte" - }, - { - "name": "overrideDimension", - "type": - [ - "switch", - { - // "Only sent if protocol version is greater than 1." - "compareTo": "fmlProtocolVersion", - "fields": - { - "0": "void", - "1": "void" - }, - "default": "int" - } - ] - }, - ], - ], - - "ClientHello": - [ - "container", - [ - { - "name": "fmlProtocolVersion", - "type": "byte" - } - ] - ], - - "ModList": - [ - "container", - [ - { - "name": "mods", - "type": - [ - "array", - { - "countType": "varint", - "type": - [ - "container", - [ - { - "name": "modid", - "type": "string" - }, - { - "name": "version", - "type": "string" - } - ] - ], - }, - ] - } - ], - ], - - "RegistryData": - [ - "container", - [ - { - "name": "hasMore", - "type": "bool" - }, - - /* TODO: support all fields - { - "name": "registryName", - "type": "string" - }, - */ - ], - ], - - "HandshakeAck": - [ - "container", - [ - { - "name": "phase", - "type": "byte" - }, - ], - ], - - }, - } - ] - } - ] - ] -); - -function writeAck(client, phase) { - var ackData = proto.createPacketBuffer('FML|HS', { - discriminator: 'HandshakeAck', // HandshakeAck, - phase: phase - }); - client.write('custom_payload', { - channel: 'FML|HS', - data: ackData - }); -} - -var FMLHandshakeClientState = { - START: 1, - WAITINGSERVERDATA: 2, - WAITINGSERVERCOMPLETE: 3, - PENDINGCOMPLETE: 4, - COMPLETE: 5, -}; - -function fmlHandshakeStep(client, data) -{ - var parsed = proto.parsePacketBuffer('FML|HS', data); - console.log('FML|HS',parsed); - - var fmlHandshakeState = client.fmlHandshakeState || FMLHandshakeClientState.START; - - switch(fmlHandshakeState) { - case FMLHandshakeClientState.START: - { - assert.ok(parsed.data.discriminator === 'ServerHello', `expected ServerHello in START state, got ${parsed.data.discriminator}`); - if (parsed.data.fmlProtocolVersion > 2) { - // TODO: support higher protocols, if they change - } - - client.write('custom_payload', { - channel: 'REGISTER', - data: new Buffer(['FML|HS', 'FML', 'FML|MP', 'FML', 'FORGE'].join('\0')) - }); - - var clientHello = proto.createPacketBuffer('FML|HS', { - discriminator: 'ClientHello', - fmlProtocolVersion: parsed.data.fmlProtocolVersion - }); - - client.write('custom_payload', { - channel: 'FML|HS', - data: clientHello - }); - - console.log('Sending client modlist'); - var modList = proto.createPacketBuffer('FML|HS', { - discriminator: 'ModList', - mods: client.forgeMods || [] - }); - client.write('custom_payload', { - channel: 'FML|HS', - data: modList - }); - writeAck(client, FMLHandshakeClientState.WAITINGSERVERDATA); - client.fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA; - break; - } - - case FMLHandshakeClientState.WAITINGSERVERDATA: - { - assert.ok(parsed.data.discriminator === 'ModList', `expected ModList in WAITINGSERVERDATA state, got ${parsed.data.discriminator}`); - console.log('Server ModList:',parsed.data.mods); - // TODO: client/server check if mods compatible - client.fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERCOMPLETE; - break; - } - - case FMLHandshakeClientState.WAITINGSERVERCOMPLETE: - { - assert.ok(parsed.data.discriminator === 'RegistryData', `expected RegistryData in WAITINGSERVERCOMPLETE, got ${parsed.data.discriminator}`); - console.log('RegistryData',parsed.data); - // TODO: support <=1.7.10 single registry, https://github.com/ORelio/Minecraft-Console-Client/pull/100/files#diff-65b97c02a9736311374109e22d30ca9cR297 - if (parsed.data.hasMore === false) { - console.log('LAST RegistryData'); - - writeAck(client, FMLHandshakeClientState.WAITINGSERVERCOMPLETE); - client.fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE; - } - break; - } - - case FMLHandshakeClientState.PENDINGCOMPLETE: - { - assert.ok(parsed.data.discriminator === 'HandshakeAck', `expected HandshakeAck in PENDINGCOMPLETE, got ${parsed.data.discrimnator}`); - assert.ok(parsed.data.phase === 2, `expected HandshakeAck phase WAITINGACK, got ${parsed.data.phase}`); - writeAck(client, FMLHandshakeClientState.PENDINGCOMPLETE4); - client.fmlHandshakeState = FMLHandshakeClientState.COMPLETE - break; - } - - case FMLHandshakeClientState.COMPLETE: - { - assert.ok(parsed.data.phase === 3, `expected HandshakeAck phase COMPLETE, got ${parsed.data.phase}`); - - writeAck(client, FMLHandshakeClientState.COMPLETE); - console.log('HandshakeAck Complete!'); - break; - } - - default: - console.error(`unexpected FML state ${fmlHandshakeState}`); - } -} - -//var forgeMods; // = [ {modid:'IronChest', version:'6.0.121.768'} ]; - mc.ping({host, port}, function(err, response) { if (err) throw err; console.log('ping response',response); @@ -280,6 +17,7 @@ mc.ping({host, port}, function(err, response) { throw new Error('not an FML server, aborting connection'); // TODO: gracefully connect non-FML // TODO: could also use ping pre-connect to save description, type, negotiate protocol etc. + // ^ see https://github.com/PrismarineJS/node-minecraft-protocol/issues/327 } // Use the list of Forge mods from the server ping, so client will match server var forgeMods = response.modinfo.modList; @@ -317,20 +55,4 @@ mc.ping({host, port}, function(err, response) { client.write('chat', {message: msg}); } }); - - client.on('custom_payload', function(packet) { - var channel = packet.channel; - var data = packet.data; - - if (channel === 'REGISTER') { - var channels = data.toString().split('\0'); - console.log('Server-side registered channels:',channels); - // TODO: do something? - // expect: [ 'FML|HS', 'FML', 'FML|MP', 'FML', 'FORGE' ] - } else if (channel === 'FML|HS') { - fmlHandshakeStep(client, data); - } - }); }); - - diff --git a/src/createClient.js b/src/createClient.js index 74ea962..3d8ceef 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -9,6 +9,7 @@ var yggserver = require('yggdrasil').server({}); var states = require("./states"); var debug = require("./debug"); var UUID = require('uuid-1345'); +var fml = require('./fml'); module.exports=createClient; @@ -53,6 +54,13 @@ function createClient(options) { client.once('success', onLogin); client.once("compress", onCompressionRequest); client.on("set_compression", onCompressionRequest); + if(client.forge) { + client.on('custom_payload', function(packet) { + if (packet.channel === 'FML|HS') { + fml.fmlHandshakeStep(client, packet.data); + } + }); + } if(haveCredentials) { // make a request to get the case-correct username before connecting. var cb = function(err, session) { diff --git a/src/fml.js b/src/fml.js new file mode 100644 index 0000000..418b3de --- /dev/null +++ b/src/fml.js @@ -0,0 +1,285 @@ +var ProtoDef = require('protodef').ProtoDef; +var assert = require('assert'); + +var proto = new ProtoDef(); +// copied from ../../dist/transforms/serializer.js TODO: refactor +proto.addType("string", ["pstring", { + countType: "varint" + }]); + + +// http://wiki.vg/Minecraft_Forge_Handshake +// TODO: move to https://github.com/PrismarineJS/minecraft-data +proto.addType('fml|hsMapper', + [ + "mapper", + { + "type": "byte", + "mappings": { + "0": "ServerHello", + "1": "ClientHello", + "2": "ModList", + "3": "RegistryData", + "-1": "HandshakeAck", + "-2": "HandshakeReset" + }, + } + ] +); + +proto.addType('FML|HS', + [ + "container", + [ + { + "name": "discriminator", + "type": "fml|hsMapper" + }, + + { + "anon": true, + "type": + [ + "switch", + { + "compareTo": "discriminator", + "fields": + { + "ServerHello": + [ + "container", + [ + { + "name": "fmlProtocolVersion", + "type": "byte" + }, + { + "name": "overrideDimension", + "type": + [ + "switch", + { + // "Only sent if protocol version is greater than 1." + "compareTo": "fmlProtocolVersion", + "fields": + { + "0": "void", + "1": "void" + }, + "default": "int" + } + ] + }, + ], + ], + + "ClientHello": + [ + "container", + [ + { + "name": "fmlProtocolVersion", + "type": "byte" + } + ] + ], + + "ModList": + [ + "container", + [ + { + "name": "mods", + "type": + [ + "array", + { + "countType": "varint", + "type": + [ + "container", + [ + { + "name": "modid", + "type": "string" + }, + { + "name": "version", + "type": "string" + } + ] + ], + }, + ] + } + ], + ], + + "RegistryData": + [ + "container", + [ + { + "name": "hasMore", + "type": "bool" + }, + + /* TODO: support all fields + { + "name": "registryName", + "type": "string" + }, + */ + ], + ], + + "HandshakeAck": + [ + "container", + [ + { + "name": "phase", + "type": "byte" + }, + ], + ], + + }, + } + ] + } + ] + ] +); + +function writeAck(client, phase) { + var ackData = proto.createPacketBuffer('FML|HS', { + discriminator: 'HandshakeAck', // HandshakeAck, + phase: phase + }); + client.write('custom_payload', { + channel: 'FML|HS', + data: ackData + }); +} + +var FMLHandshakeClientState = { + START: 1, + WAITINGSERVERDATA: 2, + WAITINGSERVERCOMPLETE: 3, + PENDINGCOMPLETE: 4, + COMPLETE: 5, +}; + +function fmlHandshakeStep(client, data) +{ + var parsed = proto.parsePacketBuffer('FML|HS', data); + console.log('FML|HS',parsed); + + var fmlHandshakeState = client.fmlHandshakeState || FMLHandshakeClientState.START; + + switch(fmlHandshakeState) { + case FMLHandshakeClientState.START: + { + assert.ok(parsed.data.discriminator === 'ServerHello', `expected ServerHello in START state, got ${parsed.data.discriminator}`); + if (parsed.data.fmlProtocolVersion > 2) { + // TODO: support higher protocols, if they change + } + + client.write('custom_payload', { + channel: 'REGISTER', + data: new Buffer(['FML|HS', 'FML', 'FML|MP', 'FML', 'FORGE'].join('\0')) + }); + + var clientHello = proto.createPacketBuffer('FML|HS', { + discriminator: 'ClientHello', + fmlProtocolVersion: parsed.data.fmlProtocolVersion + }); + + client.write('custom_payload', { + channel: 'FML|HS', + data: clientHello + }); + + console.log('Sending client modlist'); + var modList = proto.createPacketBuffer('FML|HS', { + discriminator: 'ModList', + mods: client.forgeMods || [] + }); + client.write('custom_payload', { + channel: 'FML|HS', + data: modList + }); + writeAck(client, FMLHandshakeClientState.WAITINGSERVERDATA); + client.fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA; + break; + } + + case FMLHandshakeClientState.WAITINGSERVERDATA: + { + assert.ok(parsed.data.discriminator === 'ModList', `expected ModList in WAITINGSERVERDATA state, got ${parsed.data.discriminator}`); + console.log('Server ModList:',parsed.data.mods); + // TODO: client/server check if mods compatible + client.fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERCOMPLETE; + break; + } + + case FMLHandshakeClientState.WAITINGSERVERCOMPLETE: + { + assert.ok(parsed.data.discriminator === 'RegistryData', `expected RegistryData in WAITINGSERVERCOMPLETE, got ${parsed.data.discriminator}`); + console.log('RegistryData',parsed.data); + // TODO: support <=1.7.10 single registry, https://github.com/ORelio/Minecraft-Console-Client/pull/100/files#diff-65b97c02a9736311374109e22d30ca9cR297 + if (parsed.data.hasMore === false) { + console.log('LAST RegistryData'); + + writeAck(client, FMLHandshakeClientState.WAITINGSERVERCOMPLETE); + client.fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE; + } + break; + } + + case FMLHandshakeClientState.PENDINGCOMPLETE: + { + assert.ok(parsed.data.discriminator === 'HandshakeAck', `expected HandshakeAck in PENDINGCOMPLETE, got ${parsed.data.discrimnator}`); + assert.ok(parsed.data.phase === 2, `expected HandshakeAck phase WAITINGACK, got ${parsed.data.phase}`); + writeAck(client, FMLHandshakeClientState.PENDINGCOMPLETE4); + client.fmlHandshakeState = FMLHandshakeClientState.COMPLETE + break; + } + + case FMLHandshakeClientState.COMPLETE: + { + assert.ok(parsed.data.phase === 3, `expected HandshakeAck phase COMPLETE, got ${parsed.data.phase}`); + + writeAck(client, FMLHandshakeClientState.COMPLETE); + console.log('HandshakeAck Complete!'); + break; + } + + default: + console.error(`unexpected FML state ${fmlHandshakeState}`); + } +} + +//var forgeMods; // = [ {modid:'IronChest', version:'6.0.121.768'} ]; +/* +function injectForge(client) { + client.on('custom_payload', function(packet) { + var channel = packet.channel; + var data = packet.data; + + if (channel === 'REGISTER') { + var channels = data.toString().split('\0'); + console.log('Server-side registered channels:',channels); + // TODO: do something? + // expect: [ 'FML|HS', 'FML', 'FML|MP', 'FML', 'FORGE' ] + } else if (channel === 'FML|HS') { + fmlHandshakeStep(client, data); + } + }); +} +*/ + +module.exports = { + fmlHandshakeStep +};