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 [] []"); process.exit(1); } var client = mc.createClient({ forge: true, // Client/server mods installed on the client // TODO: send from ServerListPing packet forgeMods: [ {modid:'IronChest', version:'6.0.121.768'} ], host: process.argv[2], port: parseInt(process.argv[3]), username: process.argv[4] ? process.argv[4] : "echo", password: process.argv[5] }); client.on('connect', function() { console.info('connected'); }); client.on('disconnect', function(packet) { console.log('disconnected: '+ packet.reason); }); client.on('end', function(err) { console.log('Connection lost'); }); client.on('chat', function(packet) { var jsonMsg = JSON.parse(packet.message); if(jsonMsg.translate == 'chat.type.announcement' || jsonMsg.translate == 'chat.type.text') { var username = jsonMsg.with[0].text; var msg = jsonMsg.with[1]; if(username === client.username) return; client.write('chat', {message: msg}); } }); 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, 2); // 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, 3); // 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, 4); // PENDINGCOMPLETE client.fmlHandshakeState = FMLHandshakeClientState.COMPLETE break; } case FMLHandshakeClientState.COMPLETE: { assert.ok(parsed.data.phase === 3, `expected HandshakeAck phase COMPLETE, got ${parsed.data.phase}`); writeAck(client, 5); // COMPLETE console.log('HandshakeAck Complete!'); break; } default: console.error(`unexpected FML state ${fmlHandshakeState}`); } } 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); } });