From 1c582acdb50720d8b48ad4e9614c1e227c00cccf Mon Sep 17 00:00:00 2001 From: extremeheat Date: Sat, 13 Mar 2021 13:38:31 -0500 Subject: [PATCH] Standard (#44) * standard * remove old examples --- .eslintignore | 1 + babel.config.js | 3 + data/new/compile.js | 98 ++++++++-------- data/provider.js | 17 +-- examples/clientTest.js | 9 +- examples/createRelay.js | 6 +- examples/old/chunk | Bin 83200 -> 0 bytes examples/old/client.js | 25 ----- examples/old/deserialize.js | 16 --- examples/old/server.js | 108 ------------------ examples/old/server_simple.js | 31 ------ examples/serverTest.js | 117 ++++++++++---------- index.js | 3 - package.json | 18 ++- src/auth/chains.js | 128 ++++++++++----------- src/auth/constants.js | 2 +- src/auth/encryption.js | 76 ++++++------- src/client.js | 21 ++-- src/client/auth.js | 4 +- src/client/authConstants.js | 4 +- src/client/authFlow.js | 9 +- src/client/tokens.js | 47 ++++---- src/connection.js | 35 +++--- src/datatypes/BatchPacket.js | 20 ++-- src/datatypes/compiler-minecraft.js | 21 +--- src/datatypes/minecraft.js | 166 ++++++++++++++-------------- src/datatypes/util.js | 52 ++++----- src/datatypes/varlong.js | 8 +- src/options.js | 2 +- src/rak.js | 44 ++++---- src/rakWorker.js | 18 +-- src/relay.js | 35 +++--- src/server.js | 8 +- src/serverPlayer.js | 31 +++--- src/transforms/encryption.js | 71 ++++++------ src/transforms/serializer.js | 20 ++-- 36 files changed, 541 insertions(+), 733 deletions(-) create mode 100644 .eslintignore create mode 100644 babel.config.js delete mode 100644 examples/old/chunk delete mode 100644 examples/old/client.js delete mode 100644 examples/old/deserialize.js delete mode 100644 examples/old/server.js delete mode 100644 examples/old/server_simple.js delete mode 100644 index.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..b59f7e3 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +test/ \ No newline at end of file diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..7db9b6f --- /dev/null +++ b/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['@babel/preset-env'] +} diff --git a/data/new/compile.js b/data/new/compile.js index a4ebce5..5c67af1 100644 --- a/data/new/compile.js +++ b/data/new/compile.js @@ -1,71 +1,71 @@ /** * This is a utility script that converts the YAML here into ProtoDef schema code and (soon) docs/typescript definitions. * It also pre-compiles JS code from the schema for easier development. - * + * * You can run this with `npm run build` - * + * */ const fs = require('fs') const { ProtoDefCompiler } = require('protodef').Compiler // Parse the YML files and turn to JSON -function genProtoSchema() { - const { parse, compile } = require('protodef-yaml/compiler') - let version +function genProtoSchema () { + const { parse, compile } = require('protodef-yaml/compiler') - // Create the packet_map.yml from proto.yml - const parsed = parse('./proto.yml') - version = parsed['!version'] - const packets = [] - for (const key in parsed) { - if (key.startsWith('%container')) { - const [, name] = key.split(',') - if (name.startsWith('packet_')) { - const children = parsed[key] - const packetName = name.replace('packet_', '') - const packetID = children['!id'] - packets.push([packetID, packetName, name]) - } - } + // Create the packet_map.yml from proto.yml + const parsed = parse('./proto.yml') + const version = parsed['!version'] + const packets = [] + for (const key in parsed) { + if (key.startsWith('%container')) { + const [, name] = key.split(',') + if (name.startsWith('packet_')) { + const children = parsed[key] + const packetName = name.replace('packet_', '') + const packetID = children['!id'] + packets.push([packetID, packetName, name]) + } } - let l1 = l2 = '' - for (const [id,name,fname] of packets) { - l1 += ` 0x${id.toString(16).padStart(2, '0')}: ${name}\n` - l2 += ` if ${name}: ${fname}\n` - } - // TODO: skip creating packet_map.yml and just generate the ProtoDef map JSON directly - const t = `#Auto-generated from proto.yml, do not modify\n!import: types.yaml\nmcpe_packet:\n name: varint =>\n${l1}\n params: name ?\n${l2}` - fs.writeFileSync('./packet_map.yml', t) + } + let l1 = '' + let l2 = '' + for (const [id, name, fname] of packets) { + l1 += ` 0x${id.toString(16).padStart(2, '0')}: ${name}\n` + l2 += ` if ${name}: ${fname}\n` + } + // TODO: skip creating packet_map.yml and just generate the ProtoDef map JSON directly + const t = `#Auto-generated from proto.yml, do not modify\n!import: types.yaml\nmcpe_packet:\n name: varint =>\n${l1}\n params: name ?\n${l2}` + fs.writeFileSync('./packet_map.yml', t) - compile('./proto.yml', 'protocol.json') - return version + compile('./proto.yml', 'protocol.json') + return version } // Compile the ProtoDef JSON into JS -function createProtocol(version) { - const compiler = new ProtoDefCompiler() - const protocol = require(`../${version}/protocol.json`).types - compiler.addTypes(require('../../src/datatypes/compiler-minecraft')) - compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) - compiler.addTypesToCompile(protocol) +function createProtocol (version) { + const compiler = new ProtoDefCompiler() + const protocol = require(`../${version}/protocol.json`).types + compiler.addTypes(require('../../src/datatypes/compiler-minecraft')) + compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) + compiler.addTypesToCompile(protocol) - fs.writeFileSync(`../${version}/read.js`, 'module.exports = ' + compiler.readCompiler.generate()) - fs.writeFileSync(`../${version}/write.js`, 'module.exports = ' + compiler.writeCompiler.generate()) - fs.writeFileSync(`../${version}/size.js`, 'module.exports = ' + compiler.sizeOfCompiler.generate()) + fs.writeFileSync(`../${version}/read.js`, 'module.exports = ' + compiler.readCompiler.generate()) + fs.writeFileSync(`../${version}/write.js`, 'module.exports = ' + compiler.writeCompiler.generate()) + fs.writeFileSync(`../${version}/size.js`, 'module.exports = ' + compiler.sizeOfCompiler.generate()) - const compiledProto = compiler.compileProtoDefSync() - return compiledProto + const compiledProto = compiler.compileProtoDefSync() + return compiledProto } -function main() { - const version = genProtoSchema() +function main () { + const version = genProtoSchema() - fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify({ types: require('./protocol.json') }, null, 2)) - fs.unlinkSync('./protocol.json') //remove temp file - fs.unlinkSync('./packet_map.yml') //remove temp file - - console.log('Generating JS...') - createProtocol(version) + fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify({ types: require('./protocol.json') }, null, 2)) + fs.unlinkSync('./protocol.json') // remove temp file + fs.unlinkSync('./packet_map.yml') // remove temp file + + console.log('Generating JS...') + createProtocol(version) } -main() \ No newline at end of file +main() diff --git a/data/provider.js b/data/provider.js index 3475db3..437d53a 100644 --- a/data/provider.js +++ b/data/provider.js @@ -1,19 +1,20 @@ const { Versions } = require('../src/options') const { getFiles } = require('../src/datatypes/util') +const { join } = require('path') const fileMap = {} // Walks all the directories for each of the supported versions in options.js // then builds a file map for each version // { 'protocol.json': { '1.16.200': '1.16.200/protocol.json', '1.16.210': '1.16.210/...' } } -function loadVersions() { +function loadVersions () { for (const version in Versions) { let files = [] - try { - files = getFiles(__dirname + '/' + version) + try { + files = getFiles(join(__dirname, '/', version)) } catch {} - for (let file of files) { - const rfile = file.replace(__dirname + '/' + version + '/', '') + for (const file of files) { + const rfile = file.replace(join(__dirname, '/', version), '') fileMap[rfile] ??= [] fileMap[rfile].push([Versions[version], file]) fileMap[rfile].sort().reverse() @@ -25,11 +26,11 @@ module.exports = (protocolVersion) => { return { // Returns the most recent file based on the specified protocolVersion // e.g. if `version` is 1.16 and a file for 1.16 doesn't exist, load from 1.15 file - getPath(file) { + getPath (file) { if (!fileMap[file]) { throw Error('Unknown file ' + file) } - for (const [ pver, path ] of fileMap[file]) { + for (const [pver, path] of fileMap[file]) { if (pver <= protocolVersion) { // console.debug('for', file, 'returining', path) return path @@ -42,4 +43,4 @@ module.exports = (protocolVersion) => { loadVersions() // console.log('file map', fileMap) -// module.exports(Versions['1.16.210']).open('creativeitems.json') \ No newline at end of file +// module.exports(Versions['1.16.210']).open('creativeitems.json') diff --git a/examples/clientTest.js b/examples/clientTest.js index 11b9b3a..a3d4b2b 100644 --- a/examples/clientTest.js +++ b/examples/clientTest.js @@ -1,9 +1,7 @@ process.env.DEBUG = 'minecraft-protocol raknet' const { Client } = require('../src/client') -const fs = require('fs') -// console.log = () => -async function test() { +async function test () { const client = new Client({ hostname: '127.0.0.1', port: 19132 @@ -32,11 +30,8 @@ async function test() { client.queue('client_cache_status', { enabled: false }) client.queue('request_chunk_radius', { chunk_radius: 1 }) client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n }) - }) - - // var read = 0; // client.on('level_chunk', (packet) => { // read++ @@ -44,4 +39,4 @@ async function test() { // }) } -test() \ No newline at end of file +test() diff --git a/examples/createRelay.js b/examples/createRelay.js index 008027b..31d8553 100644 --- a/examples/createRelay.js +++ b/examples/createRelay.js @@ -1,6 +1,6 @@ const { Relay } = require('../src/relay') -function createRelay() { +function createRelay () { console.log('Creating relay') /** * Example to create a non-transparent proxy (or 'Relay') connection to destination server @@ -27,7 +27,7 @@ function createRelay() { /* Where to send upstream packets to */ destination: { hostname: '127.0.0.1', - port: 19132, + port: 19132 // encryption: true } }) @@ -35,4 +35,4 @@ function createRelay() { relay.create() } -createRelay() \ No newline at end of file +createRelay() diff --git a/examples/old/chunk b/examples/old/chunk deleted file mode 100644 index 4152a449e50dace389356c21650ac8bea1744cea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83200 zcmeI3OK#j&5JibJj2Hpi0wT(i^>Mr;O?n^607EsEy<+xoKpbl=6C^*)7d>`FV?{ky&fD$T!+$)Q5DP-dP%bXUh~)0 zpY8ppHaLHM!)JLEZ@8|u??aJZ{r)HLwyBl1_GZeS@{%7A3*JP?I4E@VxS%8LzX!3~ zzwcSjejn0i`5@=lvQ%q750lN&twuemSZTBwrF{7MLdbT&m9&?q-JBo5G7jajERim< z4|xB!yE&yg@{1Dnv@&ng-R-)e1=QUu{K=mvPDQl)g7vD-bfw0x}#HQ`;IErq!e+m-rsS?gvuI?YcU z;43D*|7;WWo?gmF+Ve@8&Qf(Dp-U)(dCeLxew)T+Z?;^ z^uBYkv{3Y+4K=!v)ABUM{O-m}kDG0|FsgYp)s1wTxeLu_srf=emr?*UveuL8I=#BP zDo1bPrY#l+o6c=i+KbaUciEheQnOOdyzn~ROjNX~brW6hZqm|hnMd!UeT0-UoeL{H zU!3;(VfE&$4nKeH#3n1f+smf2#Q3?ep0}CE=TFsDu~}A-kx#{*FSQ(B$*Hc=ljF;AK(#gR4`k@c}2rXT(OUQO>I@>Kkn`4wXFvD&soRHqzzpTSO31`z&H&_dmA4jbjEs9hf zQ6w}ZB0>>$S+qwBqg9QgOWh-XdjH_*-ddBQufSh>E;#6DY3`NzwfN)a|2q7&OL?FE zqJ8eT2OsnP7v?vo{2t8MUq|o$DO$F-O?K;atKL5;Zto#(DE360zJELF3-j+|ejjvk z{@rfZ?cR-}#9fLquS<=XAKIf-tBwdMd6g1X_AYyqV{h5`{#X2WZ~hp^9|i1d+1hn> zQg^-E$?_=j4NiPwQqEUd#9xsf^$?9Jdz>jxuYV`8^6y?({#~cO(vL@^aW4=M)zG?y zM%NOx3Q=Wm)nmgwz81AVYB9fDKb)_xY$hU&yc#Urw`8EjGIvDXB-<@!|3BU6=dGOx zjXlf$cH+dpF$mv?O}aDPb$^L31pE=9v8V8dT%CK1B6)CzJZWy-EZXm1q&?RDs9pEh z^GE-FjaxSee~j!(TcTe=}Yje0Z zC0L}l)Q;so<=^Pa`}maBm6zkF)+k7T1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCL^pfg1;~w|L|6 zlVPF1p?)m<<9#-_pkBoTp!$j!S~DafB(T=!Ltwh{gK?w_aFR%LdX*@CzlD4Icj6oV5gUIs z^eX8q{M|PO{SW;0DCy^Re>2icBOeoJ_xBf_maW_P-oR zDs|RJBPYvNEv)UaxnZoj=_7n2s!!t|JX-&SwkH13$DBWkvEKOW8M2IaByTq_`J))i z8rJ7mUn$lodo}6-<^Uc35zW9Htx8^l`9@6s)<=3RA#tocN9LDe(?`?Kt^RSc^)d0U zmec#UE>{2Qw={F``PEVOLduBE%o0RUHq{U!1@E!IQ^Q zdwy~C@}<;b_b{BEUtWe5IXu6-x{`&h?1ntOxV%DvXwAM*eR;L*Eqfu!OL;VTCTICG z3@2yer0PPRlasR-FG8Q64`(8?9-fNI`unGwLJ!B=jcM6%ar)iUr<={D`{lQv_>T;D J2Oj4g_!n1^QO5uP diff --git a/examples/old/client.js b/examples/old/client.js deleted file mode 100644 index 65b703f..0000000 --- a/examples/old/client.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; -var pmp = require('../'); - -if(process.argv.length !=5) { - console.log("Usage: node client.js "); - process.exit(1); -} - -var client = pmp.createClient({ - host: process.argv[2], - port: parseInt(process.argv[3]), - username:process.argv[4] -}); - -client.on('mcpe', packet => console.log(packet)); - -client.on('set_spawn_position', () => { - client.writeMCPE('request_chunk_radius', { - chunkRadius:8 - }); -}); - -client.on('error',function(err){ - console.log(err); -}); diff --git a/examples/old/deserialize.js b/examples/old/deserialize.js deleted file mode 100644 index bc9132f..0000000 --- a/examples/old/deserialize.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; -var mcpe = require('../'); -var Parser = require('protodef').Parser; - -var parser = new Parser(mcpe.createProtocol(),'mcpe_packet'); -var serializer = mcpe.createSerializer(); - -parser.write(new Buffer('9F000000010000007E000000804800B0', 'hex')); - -parser.on('error', function(err) { - console.log(err.stack); -}) - -parser.on('data', function(chunk) { - console.log(JSON.stringify(chunk, null, 2)); -}); diff --git a/examples/old/server.js b/examples/old/server.js deleted file mode 100644 index 7116391..0000000 --- a/examples/old/server.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; - -var pmp = require('../'); -var fs = require("fs"); - -if(process.argv.length !=4) { - console.log("Usage: node server.js "); - process.exit(1); -} - -var server = pmp.createServer({ - host: process.argv[2], - port: parseInt(process.argv[3]), - name: 'MCPE;Minecraft: PE Server;81 81;0.15.0;0;20' -}); - -server.on('connection', function(client) { - - - client.on("mcpe",packet => console.log(packet)); - - client.on("login_mcpe",packet => { - client.writeMCPE("player_status",{ - status:0 - }); - - client.writeMCPE('move_player', { - entityId: [0,0], - x: 1, - y: 64 + 1.62, - z: 1, - yaw: 0, - headYaw: 0, - pitch: 0, - mode: 0, - onGround: 1 - }); - - client.writeMCPE("start_game",{ - seed:-1, - dimension:0, - generator:1, - gamemode:1, - entityId:[0,0], - spawnX:1, - spawnY:1, - spawnZ:1, - x:0, - y:1+1.62, - z:0, - isLoadedInCreative:0, - dayCycleStopTime:0, - eduMode:0, - worldName:"" - }); - - client.writeMCPE('set_spawn_position', { - x: 1, - y: 64, - z: 1 - }); - client.writeMCPE("set_time",{ - time:0, - started:1 - }); - - client.writeMCPE('respawn', { - x: 1, - y: 64, - z: 1 - }); - }); - - client.on("chunk_radius_update",() => { - client.writeMCPE('chunk_radius_update',{ - chunk_radius:1 - }); - - for (let x = -1; x <=1; x++) { - for (let z = -1; z <=1; z++) { - client.writeBatch([{name:"full_chunk_data",params:{ - chunkX: x, - chunkZ: z, - order: 1, - chunkData:fs.readFileSync(__dirname+"/chunk") - }}]); - } - } - - client.writeMCPE('player_status', { - status: 3 - }); - - client.writeMCPE('set_time', { - time: 0, - started: 1 - }); - - }); - - client.on('error', function(err) { - console.log(err.stack); - }); - - client.on('end',function() { - console.log("client left"); - }) -}); diff --git a/examples/old/server_simple.js b/examples/old/server_simple.js deleted file mode 100644 index 846666e..0000000 --- a/examples/old/server_simple.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -var pmp = require('../'); -var fs = require("fs"); - -if(process.argv.length !=4) { - console.log("Usage: node server.js "); - process.exit(1); -} - -var server = pmp.createServer({ - host: process.argv[2], - port: parseInt(process.argv[3]), - name: 'MCPE;Minecraft: PE Server;81 81;0.15.0;0;20' -}); - -server.on('connection', function(client) { - client.on("mcpe", packet => console.log(packet)); - - client.on("login_mcpe", data => { - console.log(client.displayName + '(' + client.XUID + ') ' + ' joined the game'); - }); - - client.on('error', err => { - console.error(err); - }); - - client.on('end', () => { - console.log("client left"); - }) -}); diff --git a/examples/serverTest.js b/examples/serverTest.js index b03b27c..5006736 100644 --- a/examples/serverTest.js +++ b/examples/serverTest.js @@ -1,25 +1,23 @@ +const fs = require('fs') process.env.DEBUG = 'minecraft-protocol raknet' const { Server } = require('../src/server') // const CreativeItems = require('../data/creativeitems.json') -const NBT = require('prismarine-nbt') -const fs = require('fs') const DataProvider = require('../data/provider') - -let server = new Server({ +const server = new Server({ }) server.create('0.0.0.0', 19132) -function getPath(packetPath) { +function getPath (packetPath) { return DataProvider(server.options.protocolVersion).getPath(packetPath) } -function get(packetPath) { +function get (packetPath) { return require(getPath('sample/' + packetPath)) } -let ran = false +// const ran = false server.on('connect', ({ client }) => { /** @type {Player} */ @@ -29,26 +27,26 @@ server.on('connect', ({ client }) => { // ResourcePacksInfo is sent by the server to inform the client on what resource packs the server has. It // sends a list of the resource packs it has and basic information on them like the version and description. client.write('resource_packs_info', { - 'must_accept': false, - 'has_scripts': false, - 'behaviour_packs': [], - 'texture_packs': [] + must_accept: false, + has_scripts: false, + behaviour_packs: [], + texture_packs: [] }) client.once('resource_pack_client_response', async (packet) => { // ResourcePackStack is sent by the server to send the order in which resource packs and behaviour packs // should be applied (and downloaded) by the client. client.write('resource_pack_stack', { - 'must_accept': false, - 'behavior_packs': [], - 'resource_packs': [], - 'game_version': '', - 'experiments': [], - 'experiments_previously_used': false + must_accept: false, + behavior_packs: [], + resource_packs: [], + game_version: '', + experiments: [], + experiments_previously_used: false }) client.once('resource_pack_client_response', async (packet) => { - + }) client.write('network_settings', { @@ -56,35 +54,35 @@ server.on('connect', ({ client }) => { }) for (let i = 0; i < 3; i++) { - client.queue('inventory_slot', {"inventory_id":120,"slot":i,"uniqueid":0,"item":{"network_id":0}}) + client.queue('inventory_slot', { inventory_id: 120, slot: i, uniqueid: 0, item: { network_id: 0 } }) } client.queue('inventory_transaction', get('packets/inventory_transaction.json')) client.queue('player_list', get('packets/player_list.json')) client.queue('start_game', get('packets/start_game.json')) - client.queue('item_component', {"entries":[]}) + client.queue('item_component', { entries: [] }) client.queue('set_spawn_position', get('packets/set_spawn_position.json')) client.queue('set_time', { time: 5433771 }) client.queue('set_difficulty', { difficulty: 1 }) client.queue('set_commands_enabled', { enabled: true }) client.queue('adventure_settings', get('packets/adventure_settings.json')) - + client.queue('biome_definition_list', get('packets/biome_definition_list.json')) client.queue('available_entity_identifiers', get('packets/available_entity_identifiers.json')) client.queue('update_attributes', get('packets/update_attributes.json')) client.queue('creative_content', get('packets/creative_content.json')) client.queue('inventory_content', get('packets/inventory_content.json')) - client.queue('player_hotbar', {"selected_slot":3,"window_id":0,"select_slot":true}) + client.queue('player_hotbar', { selected_slot: 3, window_id: 0, select_slot: true }) client.queue('crafting_data', get('packets/crafting_data.json')) client.queue('available_commands', get('packets/available_commands.json')) - client.queue('chunk_radius_update', {"chunk_radius":5}) + client.queue('chunk_radius_update', { chunk_radius: 5 }) client.queue('set_entity_data', get('packets/set_entity_data.json')) client.queue('game_rules_changed', get('packets/game_rules_changed.json')) - client.queue('respawn', {"x":646.9405517578125,"y":65.62001037597656,"z":77.86255645751953,"state":0,"runtime_entity_id":0}) + client.queue('respawn', { x: 646.9405517578125, y: 65.62001037597656, z: 77.86255645751953, state: 0, runtime_entity_id: 0 }) for (const file of fs.readdirSync(`../data/${server.options.version}/sample/chunks`)) { const buffer = Buffer.from(fs.readFileSync(`../data/${server.options.version}/sample/chunks/` + file, 'utf8'), 'hex') @@ -97,18 +95,17 @@ server.on('connect', ({ client }) => { // } setInterval(() => { - client.write('network_chunk_publisher_update', {"coordinates":{"x":646,"y":130,"z":77},"radius":64}) + client.write('network_chunk_publisher_update', { coordinates: { x: 646, y: 130, z: 77 }, radius: 64 }) }, 9500) - setTimeout(() => { client.write('play_status', { status: 'player_spawn' }) }, 8000) // Respond to tick synchronization packets - client.on('tick_sync', ({ request_time }) => { + client.on('tick_sync', (packet) => { client.queue('tick_sync', { - request_time, + request_time: packet.request_time, response_time: BigInt(Date.now()) }) }) @@ -116,43 +113,41 @@ server.on('connect', ({ client }) => { }) }) -async function sleep(ms) { - return new Promise(res => { - setTimeout(() => { res() }, ms) - }) -} - // CHUNKS // const { ChunkColumn, Version } = require('bedrock-provider') -const mcData = require('minecraft-data')('1.16') -var chunks = [] -async function buildChunks() { - // "x": 40, - // "z": 4, - - const stone = mcData.blocksByName.stone +// const mcData = require('minecraft-data')('1.16') +// const chunks = [] +// async function buildChunks () { +// // "x": 40, +// // "z": 4, - for (var cx = 35; cx < 45; cx++) { - for (var cz = 0; cz < 8; cz++) { - const column = new ChunkColumn(Version.v1_2_0_bis, x, z) - for (var x = 0; x < 16; x++) { - for (var y = 0; y < 60; y++) { - for (var z = 0; z < 16; z++) { - column.setBlock(x,y,z,stone) - } - } - } +// const stone = mcData.blocksByName.stone - const ser = await column.networkEncodeNoCache() +// for (let cx = 35; cx < 45; cx++) { +// for (let cz = 0; cz < 8; cz++) { +// const column = new ChunkColumn(Version.v1_2_0_bis, x, z) +// for (let x = 0; x < 16; x++) { +// for (let y = 0; y < 60; y++) { +// for (let z = 0; z < 16; z++) { +// column.setBlock(x, y, z, stone) +// } +// } +// } - chunks.push({ - x:cx, z:cz, sub_chunk_count: column.sectionsLen, cache_enabled: false, - blobs: [], payload: ser - }) - } - } +// const ser = await column.networkEncodeNoCache() - // console.log('Chunks',chunks) -} +// chunks.push({ +// x: cx, +// z: cz, +// sub_chunk_count: column.sectionsLen, +// cache_enabled: false, +// blobs: [], +// payload: ser +// }) +// } +// } -// buildChunks() \ No newline at end of file +// // console.log('Chunks',chunks) +// } + +// // buildChunks() diff --git a/index.js b/index.js deleted file mode 100644 index b3da4d2..0000000 --- a/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require('./dist/index.js'); diff --git a/package.json b/package.json index 043dc14..0797ea3 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,11 @@ "main": "index.js", "scripts": { "build": "cd data/new && node compile.js", - "test": "echo \"Error: no test specified\" && exit 1" + "prepare": "npm run build", + "test": "mocha", + "pretest": "npm run lint", + "lint": "standard", + "fix": "standard --fix" }, "keywords": [ "minecraft", @@ -19,19 +23,25 @@ "@xboxreplay/xboxlive-auth": "^3.3.3", "aes-js": "^3.1.2", "asn1": "^0.2.4", - "bedrock-provider": "github:extremeheat/bedrock-provider", + "bedrock-provider": "^0.1.1", "debug": "^4.3.1", "ec-pem": "^0.18.0", "jsonwebtoken": "^8.5.1", "jsp-raknet": "github:extremeheat/raknet#client", "minecraft-folder-path": "^1.1.0", "prismarine-nbt": "^1.5.0", - "protodef": "github:extremeheat/node-protodef#compiler", + "protodef": "github:extremeheat/node-protodef#compiler2", "raknet-native": "^0.1.0", "uuid-1345": "^0.99.7" }, "devDependencies": { - "mocha": "^2.5.3" + "@babel/eslint-parser": "^7.13.10", + "babel-eslint": "^10.1.0", + "mocha": "^2.5.3", + "standard": "^16.0.3" + }, + "standard": { + "parser": "babel-eslint" }, "repository": { "type": "git", diff --git a/src/auth/chains.js b/src/auth/chains.js index 20ded36..2c350d8 100644 --- a/src/auth/chains.js +++ b/src/auth/chains.js @@ -4,81 +4,81 @@ const constants = require('./constants') // Refer to the docs: // https://web.archive.org/web/20180917171505if_/https://confluence.yawk.at/display/PEPROTOCOL/Game+Packets#GamePackets-Login -function mcPubKeyToPem(mcPubKeyBuffer) { - console.log(mcPubKeyBuffer) - if (mcPubKeyBuffer[0] == '-') return mcPubKeyBuffer - let pem = '-----BEGIN PUBLIC KEY-----\n' - let base64PubKey = mcPubKeyBuffer.toString('base64') - const maxLineLength = 65 - while (base64PubKey.length > 0) { - pem += base64PubKey.substring(0, maxLineLength) + '\n' - base64PubKey = base64PubKey.substring(maxLineLength) - } - pem += '-----END PUBLIC KEY-----\n' - return pem +function mcPubKeyToPem (mcPubKeyBuffer) { + console.log(mcPubKeyBuffer) + if (mcPubKeyBuffer[0] === '-') return mcPubKeyBuffer + let pem = '-----BEGIN PUBLIC KEY-----\n' + let base64PubKey = mcPubKeyBuffer.toString('base64') + const maxLineLength = 65 + while (base64PubKey.length > 0) { + pem += base64PubKey.substring(0, maxLineLength) + '\n' + base64PubKey = base64PubKey.substring(maxLineLength) + } + pem += '-----END PUBLIC KEY-----\n' + return pem } -function getX5U(token) { - const [header] = token.split('.') - const hdec = Buffer.from(header, 'base64').toString('utf-8') - const hjson = JSON.parse(hdec) - return hjson.x5u +function getX5U (token) { + const [header] = token.split('.') + const hdec = Buffer.from(header, 'base64').toString('utf-8') + const hjson = JSON.parse(hdec) + return hjson.x5u } -function verifyAuth(chain) { - let data = {} +function verifyAuth (chain) { + let data = {} - // There are three JWT tokens sent to us, one signed by the client - // one signed by Mojang with the Mojang token we have and another one - // from Xbox with addition user profile data - // We verify that at least one of the tokens in the chain has been properly - // signed by Mojang by checking the x509 public key in the JWT headers - let didVerify = false - - let pubKey = mcPubKeyToPem(getX5U(chain[0])) // the first one is client signed, allow it - let finalKey = null - console.log(pubKey) - for (var token of chain) { - // const decoded = jwt.decode(token, pubKey, 'ES384') - // console.log('Decoding...', token) - const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' }) - // console.log('Decoded...') - console.log('Decoded', decoded) - - // Check if signed by Mojang key - const x5u = getX5U(token) - if (x5u == constants.PUBLIC_KEY && !data.extraData?.XUID) { - didVerify = true - console.log('verified with mojang key!', x5u) - } - - // TODO: Handle `didVerify` = false - - pubKey = decoded.identityPublicKey ? mcPubKeyToPem(decoded.identityPublicKey) : x5u - finalKey = decoded.identityPublicKey || finalKey // non pem - data = { ...data, ...decoded } - } - // console.log('Result', data) - - return { key: finalKey, data } -} - -function verifySkin(publicKey, token) { - // console.log('token', token) - const pubKey = mcPubKeyToPem(publicKey) + // There are three JWT tokens sent to us, one signed by the client + // one signed by Mojang with the Mojang token we have and another one + // from Xbox with addition user profile data + // We verify that at least one of the tokens in the chain has been properly + // signed by Mojang by checking the x509 public key in the JWT headers + // let didVerify = false + let pubKey = mcPubKeyToPem(getX5U(chain[0])) // the first one is client signed, allow it + let finalKey = null + console.log(pubKey) + for (const token of chain) { + // const decoded = jwt.decode(token, pubKey, 'ES384') + // console.log('Decoding...', token) const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' }) + // console.log('Decoded...') + console.log('Decoded', decoded) - return decoded + // Check if signed by Mojang key + const x5u = getX5U(token) + if (x5u === constants.PUBLIC_KEY && !data.extraData?.XUID) { + // didVerify = true + console.log('verified with mojang key!', x5u) + } + + // TODO: Handle `didVerify` = false + + pubKey = decoded.identityPublicKey ? mcPubKeyToPem(decoded.identityPublicKey) : x5u + finalKey = decoded.identityPublicKey || finalKey // non pem + data = { ...data, ...decoded } + } + // console.log('Result', data) + + return { key: finalKey, data } } -function decodeLoginJWT(authTokens, skinTokens) { - const { key, data } = verifyAuth(authTokens) - const skinData = verifySkin(key, skinTokens) - return { key, userData: data, skinData } +function verifySkin (publicKey, token) { + // console.log('token', token) + const pubKey = mcPubKeyToPem(publicKey) + + const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' }) + + return decoded } -function encodeLoginJWT(localChain, mojangChain) { +function decodeLoginJWT (authTokens, skinTokens) { + const { key, data } = verifyAuth(authTokens) + const skinData = verifySkin(key, skinTokens) + return { key, userData: data, skinData } +} + +function encodeLoginJWT (localChain, mojangChain) { const chains = [] chains.push(localChain) for (const chain of mojangChain) { @@ -107,4 +107,4 @@ module.exports = { encodeLoginJWT, decodeLoginJWT } // // console.log(loginPacket) // } -// // testServer() \ No newline at end of file +// // testServer() diff --git a/src/auth/constants.js b/src/auth/constants.js index 2ffbe1d..3e7ef06 100644 --- a/src/auth/constants.js +++ b/src/auth/constants.js @@ -1,3 +1,3 @@ module.exports = { - PUBLIC_KEY: 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V' + PUBLIC_KEY: 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V' } diff --git a/src/auth/encryption.js b/src/auth/encryption.js index 21ebbdb..56afa1d 100644 --- a/src/auth/encryption.js +++ b/src/auth/encryption.js @@ -1,26 +1,26 @@ const JWT = require('jsonwebtoken') const crypto = require('crypto') const { Ber } = require('asn1') -const ec_pem = require('ec-pem') +const ecPem = require('ec-pem') const fs = require('fs') const DataProvider = require('../../data/provider') const SALT = '🧂' const curve = 'secp384r1' -function Encrypt(client, server, options) { +function Encrypt (client, server, options) { const skinGeom = fs.readFileSync(DataProvider(options.protocolVersion).getPath('skin_geom.txt'), 'utf-8') client.ecdhKeyPair = crypto.createECDH(curve) client.ecdhKeyPair.generateKeys() client.clientX509 = writeX509PublicKey(client.ecdhKeyPair.getPublicKey()) - function startClientboundEncryption(publicKey) { + function startClientboundEncryption (publicKey) { console.warn('[encrypt] Pub key base64: ', publicKey) const pubKeyBuf = readX509PublicKey(publicKey.key) const alice = client.ecdhKeyPair - const alicePEM = ec_pem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125 + const alicePEM = ecPem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125 const alicePEMPrivate = alicePEM.encodePrivateKey() // Shared secret from bob's public key + our private key client.sharedSecret = alice.computeSecret(pubKeyBuf) @@ -28,7 +28,7 @@ function Encrypt(client, server, options) { // Secret hash we use for packet encryption: // From the public key of the remote and the private key // of the local, a shared secret is generated using ECDH. - // The secret key bytes are then computed as + // The secret key bytes are then computed as // sha256(server_token + shared_secret). These secret key // bytes are 32 bytes long. const secretHash = crypto.createHash('sha256') @@ -49,13 +49,13 @@ function Encrypt(client, server, options) { }) // The encryption scheme is AES/CFB8/NoPadding with the - // secret key being the result of the sha256 above and + // secret key being the result of the sha256 above and // the IV being the first 16 bytes of this secret key. const initial = client.secretKeyBytes.slice(0, 16) client.startEncryption(initial) } - function startServerboundEncryption(token) { + function startServerboundEncryption (token) { console.warn('[encrypt] Starting serverbound encryption', token) const jwt = token?.token if (!jwt) { @@ -64,7 +64,7 @@ function Encrypt(client, server, options) { } // TODO: Should we do some JWT signature validation here? Seems pointless const alice = client.ecdhKeyPair - const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64')) + const [header, payload] = jwt.split('.').map(k => Buffer.from(k, 'base64')) const head = JSON.parse(String(header)) const body = JSON.parse(String(payload)) const serverPublicKey = readX509PublicKey(head.x5u) @@ -93,7 +93,7 @@ function Encrypt(client, server, options) { client.createClientChain = (mojangKey) => { mojangKey = mojangKey || require('./constants').PUBLIC_KEY const alice = client.ecdhKeyPair - const alicePEM = ec_pem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125 + const alicePEM = ecPem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125 const alicePEMPrivate = alicePEM.encodePrivateKey() const token = JWT.sign({ @@ -122,16 +122,16 @@ function Encrypt(client, server, options) { SkinData: 'AAAAAA==', SkinResourcePatch: 'ewogICAiZ2VvbWV0cnkiIDogewogICAgICAiYW5pbWF0ZWRfMTI4eDEyOCIgOiAiZ2VvbWV0cnkuYW5pbWF0ZWRfMTI4eDEyOF9wZXJzb25hLWUxOTk2NzJhOGMxYTg3ZTAtMCIsCiAgICAgICJhbmltYXRlZF9mYWNlIiA6ICJnZW9tZXRyeS5hbmltYXRlZF9mYWNlX3BlcnNvbmEtZTE5OTY3MmE4YzFhODdlMC0wIiwKICAgICAgImRlZmF1bHQiIDogImdlb21ldHJ5LnBlcnNvbmFfZTE5OTY3MmE4YzFhODdlMC0wIgogICB9Cn0K', SkinGeometryData: skinGeom, - "SkinImageHeight": 1, - "SkinImageWidth": 1, - "ArmSize": "wide", - "CapeData": "", - "CapeId": "", - "CapeImageHeight": 0, - "CapeImageWidth": 0, - "CapeOnClassicSkin": false, + SkinImageHeight: 1, + SkinImageWidth: 1, + ArmSize: 'wide', + CapeData: '', + CapeId: '', + CapeImageHeight: 0, + CapeImageWidth: 0, + CapeOnClassicSkin: false, PlatformOfflineId: '', - PlatformOnlineId: '', //chat + PlatformOnlineId: '', // chat // a bunch of meaningless junk CurrentInputMode: 1, DefaultInputMode: 1, @@ -144,7 +144,7 @@ function Encrypt(client, server, options) { PieceTintColors: [], SkinAnimationData: '', ThirdPartyNameOnly: false, - "SkinColor": "#ffffcd96", + SkinColor: '#ffffcd96' } payload = require('./logPack.json') const customPayload = options.userData || {} @@ -155,33 +155,33 @@ function Encrypt(client, server, options) { } } -function toBase64(string) { +function toBase64 (string) { return Buffer.from(string).toString('base64') } -function readX509PublicKey(key) { - var reader = new Ber.Reader(Buffer.from(key, "base64")); - reader.readSequence(); - reader.readSequence(); - reader.readOID(); // Hey, I'm an elliptic curve - reader.readOID(); // This contains the curve type, could be useful - return Buffer.from(reader.readString(Ber.BitString, true)).slice(1); +function readX509PublicKey (key) { + const reader = new Ber.Reader(Buffer.from(key, 'base64')) + reader.readSequence() + reader.readSequence() + reader.readOID() // Hey, I'm an elliptic curve + reader.readOID() // This contains the curve type, could be useful + return Buffer.from(reader.readString(Ber.BitString, true)).slice(1) } -function writeX509PublicKey(key) { - var writer = new Ber.Writer(); - writer.startSequence(); - writer.startSequence(); - writer.writeOID("1.2.840.10045.2.1"); - writer.writeOID("1.3.132.0.34"); - writer.endSequence(); - writer.writeBuffer(Buffer.concat([Buffer.from([0x00]), key]), Ber.BitString); - writer.endSequence(); - return writer.buffer.toString("base64"); +function writeX509PublicKey (key) { + const writer = new Ber.Writer() + writer.startSequence() + writer.startSequence() + writer.writeOID('1.2.840.10045.2.1') + writer.writeOID('1.3.132.0.34') + writer.endSequence() + writer.writeBuffer(Buffer.concat([Buffer.from([0x00]), key]), Ber.BitString) + writer.endSequence() + return writer.buffer.toString('base64') } module.exports = { readX509PublicKey, writeX509PublicKey, Encrypt -} \ No newline at end of file +} diff --git a/src/client.js b/src/client.js index 1bbd167..1f859e2 100644 --- a/src/client.js +++ b/src/client.js @@ -12,7 +12,7 @@ const debugging = false class Client extends Connection { /** @param {{ version: number, hostname: string, port: number }} options */ - constructor(options) { + constructor (options) { super() this.options = { ...Options.defaultOptions, ...options } this.validateOptions() @@ -33,7 +33,7 @@ class Client extends Connection { this.outLog = (...args) => console.info('C <-', ...args) } - validateOptions() { + validateOptions () { if (!this.options.hostname || this.options.port == null) throw Error('Invalid hostname/port') if (!Options.Versions[this.options.version]) { @@ -61,7 +61,7 @@ class Client extends Connection { this.connection.connect() } - sendLogin() { + sendLogin () { this.createClientChain() const chain = [ @@ -84,23 +84,22 @@ class Client extends Connection { this.emit('loggingIn') } - onDisconnectRequest(packet) { + onDisconnectRequest (packet) { // We're talking over UDP, so there is no connection to close, instead // we stop communicating with the server console.warn(`Server requested ${packet.hide_disconnect_reason ? 'silent disconnect' : 'disconnect'}: ${packet.message}`) process.exit(1) // TODO: handle } - close() { + close () { console.warn('Close not implemented!!') } - tryRencode(name, params, actual) { + tryRencode (name, params, actual) { const packet = this.serializer.createPacketBuffer({ name, params }) - console.assert(packet.toString('hex') == actual.toString('hex')) + console.assert(packet.toString('hex') === actual.toString('hex')) if (packet.toString('hex') !== actual.toString('hex')) { - const ours = packet.toString('hex').match(/.{1,16}/g).join('\n') const theirs = actual.toString('hex').match(/.{1,16}/g).join('\n') @@ -113,10 +112,10 @@ class Client extends Connection { } } - readPacket(packet) { + readPacket (packet) { const des = this.deserializer.parsePacketBuffer(packet) const pakData = { name: des.data.name, params: des.data.params } - this.inLog('-> C', pakData.name/*, serialize(pakData.params).slice(0, 100)*/) + this.inLog('-> C', pakData.name/*, serialize(pakData.params).slice(0, 100) */) if (debugging) { // Packet verifying (decode + re-encode + match test) @@ -161,4 +160,4 @@ class Client extends Connection { } } -module.exports = { Client } \ No newline at end of file +module.exports = { Client } diff --git a/src/client/auth.js b/src/client/auth.js index 20775d2..f4d139f 100644 --- a/src/client/auth.js +++ b/src/client/auth.js @@ -9,7 +9,7 @@ const { MsAuthFlow } = require('./authFlow.js') async function postAuthenticate (client, options, chains) { // First chain is Mojang stuff, second is Xbox profile data used by mc const jwt = chains[1] - const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64')) + const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64')) // eslint-disable-line const xboxProfile = JSON.parse(String(payload)) // This profile / session here could be simplified down to where it just passes the uuid of the player to encrypt.js @@ -17,7 +17,7 @@ async function postAuthenticate (client, options, chains) { // - Kashalls const profile = { name: xboxProfile?.extraData?.displayName || 'Player', - uuid: xboxProfile?.extraData?.identity || 'adfcf5ca-206c-404a-aec4-f59fff264c9b', //random + uuid: xboxProfile?.extraData?.identity || 'adfcf5ca-206c-404a-aec4-f59fff264c9b', // random xuid: xboxProfile?.extraData?.XUID || 0 } diff --git a/src/client/authConstants.js b/src/client/authConstants.js index 26e052d..65c2575 100644 --- a/src/client/authConstants.js +++ b/src/client/authConstants.js @@ -1,4 +1,4 @@ module.exports = { - XSTSRelyingParty: 'https://multiplayer.minecraft.net/', - MinecraftAuth: 'https://multiplayer.minecraft.net/authentication' + XSTSRelyingParty: 'https://multiplayer.minecraft.net/', + MinecraftAuth: 'https://multiplayer.minecraft.net/authentication' } diff --git a/src/client/authFlow.js b/src/client/authFlow.js index 1c0325f..7e496ba 100644 --- a/src/client/authFlow.js +++ b/src/client/authFlow.js @@ -13,15 +13,16 @@ const msalConfig = { // the minecraft client: // clientId: "000000004C12AE6F", clientId: '389b1b32-b5d5-43b2-bddc-84ce938d6737', // token from https://github.com/microsoft/Office365APIEditor - authority: 'https://login.microsoftonline.com/consumers', + authority: 'https://login.microsoftonline.com/consumers' } } -async function retry (methodFn, beforeRety, times) { +async function retry (methodFn, beforeRetry, times) { while (times--) { if (times !== 0) { try { return await methodFn() } catch (e) { debug(e) } - await beforeRety() + await new Promise(resolve => setTimeout(resolve, 2000)) + await beforeRetry() } else { return await methodFn() } @@ -111,7 +112,7 @@ class MsAuthFlow { async getMinecraftToken (publicKey) { // TODO: Fix cache, in order to do cache we also need to cache the ECDH keys so disable it // is this even a good idea to cache? - if (await this.mca.verifyTokens() && false) { + if (await this.mca.verifyTokens() && false) { // eslint-disable-line debug('[mc] Using existing tokens') return this.mca.getCachedAccessToken().chain } else { diff --git a/src/client/tokens.js b/src/client/tokens.js index 1959211..9ed3a18 100644 --- a/src/client/tokens.js +++ b/src/client/tokens.js @@ -8,7 +8,7 @@ const authConstants = require('./authConstants') // Manages Microsoft account tokens class MsaTokenManager { - constructor(msalConfig, scopes, cacheLocation) { + constructor (msalConfig, scopes, cacheLocation) { this.msaClientId = msalConfig.auth.clientId this.scopes = scopes this.cacheLocation = cacheLocation || path.join(__dirname, './msa-cache.json') @@ -42,7 +42,7 @@ class MsaTokenManager { this.msalConfig = msalConfig } - getUsers() { + getUsers () { const accounts = this.msaCache.Account const users = [] if (!accounts) return users @@ -52,7 +52,7 @@ class MsaTokenManager { return users } - getAccessToken() { + getAccessToken () { const tokens = this.msaCache.AccessToken if (!tokens) return const account = Object.values(tokens).filter(t => t.client_id === this.msaClientId)[0] @@ -65,7 +65,7 @@ class MsaTokenManager { return { valid, until: until, token: account.secret } } - getRefreshToken() { + getRefreshToken () { const tokens = this.msaCache.RefreshToken if (!tokens) return const account = Object.values(tokens).filter(t => t.client_id === this.msaClientId)[0] @@ -76,7 +76,7 @@ class MsaTokenManager { return { token: account.secret } } - async refreshTokens() { + async refreshTokens () { const rtoken = this.getRefreshToken() if (!rtoken) { throw new Error('Cannot refresh without refresh token') @@ -97,7 +97,7 @@ class MsaTokenManager { }) } - async verifyTokens() { + async verifyTokens () { const at = this.getAccessToken() const rt = this.getRefreshToken() if (!at || !rt || this.forceRefresh) { @@ -111,13 +111,14 @@ class MsaTokenManager { await this.refreshTokens() return true } catch (e) { + console.warn('Error refreshing token', e) // TODO: looks like an error happens here return false } } } // Authenticate with device_code flow - async authDeviceCode(dataCallback) { + async authDeviceCode (dataCallback) { const deviceCodeRequest = { deviceCodeCallback: (resp) => { debug('[msa] device_code response: ', resp) @@ -142,7 +143,7 @@ class MsaTokenManager { // Manages Xbox Live tokens for xboxlive.com class XboxTokenManager { - constructor(relyingParty, cacheLocation) { + constructor (relyingParty, cacheLocation) { this.relyingParty = relyingParty this.cacheLocation = cacheLocation || path.join(__dirname, './xbl-cache.json') try { @@ -152,7 +153,7 @@ class XboxTokenManager { } } - getCachedUserToken() { + getCachedUserToken () { const token = this.cache.userToken if (!token) return const until = new Date(token.NotAfter) @@ -162,7 +163,7 @@ class XboxTokenManager { return { valid, token: token.Token, data: token } } - getCachedXstsToken() { + getCachedXstsToken () { const token = this.cache.xstsToken if (!token) return const until = new Date(token.expiresOn) @@ -172,17 +173,17 @@ class XboxTokenManager { return { valid, token: token.XSTSToken, data: token } } - setCachedUserToken(data) { + setCachedUserToken (data) { this.cache.userToken = data fs.writeFileSync(this.cacheLocation, JSON.stringify(this.cache)) } - setCachedXstsToken(data) { + setCachedXstsToken (data) { this.cache.xstsToken = data fs.writeFileSync(this.cacheLocation, JSON.stringify(this.cache)) } - async verifyTokens() { + async verifyTokens () { const ut = this.getCachedUserToken() const xt = this.getCachedXstsToken() if (!ut || !xt || this.forceRefresh) { @@ -202,7 +203,7 @@ class XboxTokenManager { return false } - async getUserToken(msaAccessToken) { + async getUserToken (msaAccessToken) { debug('[xbl] obtaining xbox token with ms token', msaAccessToken) if (!msaAccessToken.startsWith('d=')) { msaAccessToken = 'd=' + msaAccessToken } const xblUserToken = await XboxLiveAuth.exchangeRpsTicketForUserToken(msaAccessToken) @@ -211,7 +212,7 @@ class XboxTokenManager { return xblUserToken } - async getXSTSToken(xblUserToken) { + async getXSTSToken (xblUserToken) { debug('[xbl] obtaining xsts token with xbox user token', xblUserToken.Token) const xsts = await XboxLiveAuth.exchangeUserTokenForXSTSIdentity( xblUserToken.Token, { XSTSRelyingParty: this.relyingParty, raw: false } @@ -224,7 +225,7 @@ class XboxTokenManager { // Manages Minecraft tokens for sessionserver.mojang.com class MinecraftTokenManager { - constructor(clientPublicKey, cacheLocation) { + constructor (clientPublicKey, cacheLocation) { this.clientPublicKey = clientPublicKey this.cacheLocation = cacheLocation || path.join(__dirname, './bed-cache.json') try { @@ -234,13 +235,13 @@ class MinecraftTokenManager { } } - getCachedAccessToken() { + getCachedAccessToken () { const token = this.cache.mca debug('[mc] token cache', this.cache) if (!token) return console.log('TOKEN', token) const jwt = token.chain[0] - const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64')) + const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64')) // eslint-disable-line const body = JSON.parse(String(payload)) const expires = new Date(body.exp * 1000) @@ -249,13 +250,13 @@ class MinecraftTokenManager { return { valid, until: expires, chain: token.chain } } - setCachedAccessToken(data) { + setCachedAccessToken (data) { data.obtainedOn = Date.now() this.cache.mca = data fs.writeFileSync(this.cacheLocation, JSON.stringify(this.cache)) } - async verifyTokens() { + async verifyTokens () { const at = this.getCachedAccessToken() if (!at || this.forceRefresh) { return false @@ -267,13 +268,13 @@ class MinecraftTokenManager { return false } - async getAccessToken(clientPublicKey, xsts) { + async getAccessToken (clientPublicKey, xsts) { debug('[mc] authing to minecraft', clientPublicKey, xsts) const getFetchOptions = { headers: { 'Content-Type': 'application/json', 'User-Agent': 'node-minecraft-protocol', - 'Authorization': `XBL3.0 x=${xsts.userHash};${xsts.XSTSToken}` + Authorization: `XBL3.0 x=${xsts.userHash};${xsts.XSTSToken}` } } const MineServicesResponse = await fetch(authConstants.MinecraftAuth, { @@ -288,7 +289,7 @@ class MinecraftTokenManager { } } -function checkStatus(res) { +function checkStatus (res) { if (res.ok) { // res.status >= 200 && res.status < 300 return res.json() } else { diff --git a/src/connection.js b/src/connection.js index eda0ca9..ba03549 100644 --- a/src/connection.js +++ b/src/connection.js @@ -5,8 +5,10 @@ const { EventEmitter } = require('events') const Versions = require('./options') const debug = require('debug')('minecraft-protocol') +const SKIP_BATCH = ['level_chunk', 'client_cache_blob_status', 'client_cache_miss_response'] + class Connection extends EventEmitter { - versionLessThan(version) { + versionLessThan (version) { if (typeof version === 'string') { return Versions[version] < this.options.version } else { @@ -14,7 +16,7 @@ class Connection extends EventEmitter { } } - versionGreaterThan(version) { + versionGreaterThan (version) { if (typeof version === 'string') { return Versions[version] > this.options.version } else { @@ -22,7 +24,7 @@ class Connection extends EventEmitter { } } - startEncryption(iv) { + startEncryption (iv) { this.encryptionEnabled = true this.inLog('Started encryption', this.sharedSecret, iv) this.decrypt = cipher.createDecryptor(this, iv) @@ -30,7 +32,7 @@ class Connection extends EventEmitter { this.q2 = [] } - write(name, params) { + write (name, params) { this.outLog('sending', name, params) const batch = new BatchPacket() const packet = this.serializer.createPacketBuffer({ name, params }) @@ -43,10 +45,10 @@ class Connection extends EventEmitter { } } - queue(name, params) { + queue (name, params) { this.outLog('Q <- ', name, params) const packet = this.serializer.createPacketBuffer({ name, params }) - if (name == 'level_chunk' || name=='client_cache_blob_status' || name == 'client_cache_miss_response') { + if (SKIP_BATCH.includes(name)) { // Skip queue, send ASAP this.sendBuffer(packet) return @@ -55,11 +57,11 @@ class Connection extends EventEmitter { this.q2.push(name) } - startQueue() { + startQueue () { this.q = [] this.loop = setInterval(() => { if (this.q.length) { - //TODO: can we just build Batch before the queue loop? + // TODO: can we just build Batch before the queue loop? const batch = new BatchPacket() this.outLog('<- BATCH', this.q2) const sending = [] @@ -78,11 +80,10 @@ class Connection extends EventEmitter { }, 20) } - /** * Sends a MCPE packet buffer */ - sendBuffer(buffer, immediate = false) { + sendBuffer (buffer, immediate = false) { if (immediate) { const batch = new BatchPacket() batch.addEncodedPacket(buffer) @@ -97,20 +98,20 @@ class Connection extends EventEmitter { } } - sendDecryptedBatch(batch) { + sendDecryptedBatch (batch) { const buf = batch.encode() // send to raknet this.sendMCPE(buf, true) } - sendEncryptedBatch(batch) { + sendEncryptedBatch (batch) { const buf = batch.stream.getBuffer() debug('Sending encrypted batch', batch) this.encrypt(buf) } // TODO: Rename this to sendEncapsulated - sendMCPE(buffer, immediate) { + sendMCPE (buffer, immediate) { this.connection.sendReliable(buffer, immediate) } @@ -132,8 +133,8 @@ class Connection extends EventEmitter { } } - handle(buffer) { // handle encapsulated - if (buffer[0] == 0xfe) { // wrapper + handle (buffer) { // handle encapsulated + if (buffer[0] === 0xfe) { // wrapper if (this.encryptionEnabled) { this.decrypt(buffer.slice(1)) } else { @@ -142,7 +143,7 @@ class Connection extends EventEmitter { batch.decode() const packets = batch.getPackets() this.inLog('Reading ', packets.length, 'packets') - for (var packet of packets) { + for (const packet of packets) { this.readPacket(packet) } } @@ -150,4 +151,4 @@ class Connection extends EventEmitter { } } -module.exports = { Connection } \ No newline at end of file +module.exports = { Connection } diff --git a/src/datatypes/BatchPacket.js b/src/datatypes/BatchPacket.js index 52f4320..cdfb292 100644 --- a/src/datatypes/BatchPacket.js +++ b/src/datatypes/BatchPacket.js @@ -1,11 +1,11 @@ const BinaryStream = require('@jsprismarine/jsbinaryutils').default -const Zlib = require('zlib'); +const Zlib = require('zlib') const NETWORK_ID = 0xfe // This is not a real MCPE packet, it's a wrapper that contains compressed/encrypted batched packets class BatchPacket { - constructor(stream) { + constructor (stream) { // Shared this.payload = Buffer.alloc(0) this.stream = stream || new BinaryStream() @@ -18,9 +18,9 @@ class BatchPacket { this.count = 0 } - decode() { + decode () { // Read header - const pid = this.stream.readByte(); + const pid = this.stream.readByte() if (!pid === NETWORK_ID) { throw new Error(`Batch ID mismatch: is ${BatchPacket.NETWORK_ID}, got ${pid}`) // this is not a BatchPacket } @@ -29,14 +29,14 @@ class BatchPacket { try { this.payload = Zlib.inflateRawSync(this.stream.readRemaining(), { chunkSize: 1024 * 1024 * 2 - }); + }) } catch (e) { console.error(e) console.debug(`[bp] Error decompressing packet ${pid}`) } } - encode() { + encode () { const buf = this.stream.getBuffer() console.log('Encoding payload', buf) const def = Zlib.deflateRawSync(buf, { level: this.compressionLevel }) @@ -45,13 +45,13 @@ class BatchPacket { return ret } - addEncodedPacket(packet) { + addEncodedPacket (packet) { this.stream.writeUnsignedVarInt(packet.byteLength) this.stream.append(packet) this.count++ } - getPackets() { + getPackets () { const stream = new BinaryStream() stream.buffer = this.payload const packets = [] @@ -64,7 +64,7 @@ class BatchPacket { return packets } - static getPackets(stream) { + static getPackets (stream) { const packets = [] while (!stream.feof()) { const length = stream.readUnsignedVarInt() @@ -76,4 +76,4 @@ class BatchPacket { } } -module.exports = BatchPacket \ No newline at end of file +module.exports = BatchPacket diff --git a/src/datatypes/compiler-minecraft.js b/src/datatypes/compiler-minecraft.js index a4ab558..b5fda57 100644 --- a/src/datatypes/compiler-minecraft.js +++ b/src/datatypes/compiler-minecraft.js @@ -1,3 +1,4 @@ +/* eslint-disable */ const UUID = require('uuid-1345') const minecraft = require('./minecraft') const { Read, Write, SizeOf } = require('./varlong') @@ -78,19 +79,6 @@ SizeOf.nbt = ['native', minecraft.nbt[2]] /** * Bits */ -// nvm, -// Read.bitflags = ['parametrizable', (compiler, { type, flags }) => { -// return compiler.wrapCode(` -// const { value, size } = ${compiler.callType('buffer, offset', type)} -// const val = {} -// for (let i = 0; i < size; i++) { -// const hi = (value >> i) & 1 -// if () -// const v = value & -// if (flags[i]) -// } -// ` -// }] Read.bitflags = ['parametrizable', (compiler, { type, flags }) => { return compiler.wrapCode(` @@ -104,7 +92,6 @@ Read.bitflags = ['parametrizable', (compiler, { type, flags }) => { `.trim()) }] - Write.bitflags = ['parametrizable', (compiler, { type, flags }) => { return compiler.wrapCode(` const flags = ${JSON.stringify(flags)} @@ -155,12 +142,12 @@ SizeOf.enum_size_based_on_values_len = ['parametrizable', (compiler) => { }) }] -function js(fn) { +function js (fn) { return fn.toString().split('\n').slice(1, -1).join('\n').trim() } -function str(fn) { +function str (fn) { return fn.toString() + ')();(()=>{}' } -module.exports = { Read, Write, SizeOf } \ No newline at end of file +module.exports = { Read, Write, SizeOf } diff --git a/src/datatypes/minecraft.js b/src/datatypes/minecraft.js index f16b16c..9ca4920 100644 --- a/src/datatypes/minecraft.js +++ b/src/datatypes/minecraft.js @@ -1,139 +1,139 @@ -var nbt = require('prismarine-nbt') +/* eslint-disable */ +const nbt = require('prismarine-nbt') const UUID = require('uuid-1345') const proto = nbt.protos.littleVarint // TODO: deal with this: -var zigzag = require('prismarine-nbt/compiler-zigzag') +const zigzag = require('prismarine-nbt/compiler-zigzag') -function readUUID(buffer, offset) { - if (offset + 16 > buffer.length) - throw new PartialReadError(); +function readUUID (buffer, offset) { + if (offset + 16 > buffer.length) { throw new PartialReadError() } return { value: UUID.stringify(buffer.slice(offset, 16 + offset)), size: 16 - }; + } } -function writeUUID(value, buffer, offset) { - const buf = UUID.parse(value); - buf.copy(buffer, offset); - return offset + 16; +function writeUUID (value, buffer, offset) { + const buf = UUID.parse(value) + buf.copy(buffer, offset) + return offset + 16 } -function readNbt(buffer, offset) { - return proto.read(buffer, offset, "nbt") +function readNbt (buffer, offset) { + return proto.read(buffer, offset, 'nbt') } -function writeNbt(value, buffer, offset) { - return proto.write(value, buffer, offset, "nbt") +function writeNbt (value, buffer, offset) { + return proto.write(value, buffer, offset, 'nbt') } -function sizeOfNbt(value) { - return proto.sizeOf(value, "nbt") +function sizeOfNbt (value) { + return proto.sizeOf(value, 'nbt') } -function readEntityMetadata(buffer, offset, _ref) { - var type = _ref.type; - var endVal = _ref.endVal; +function readEntityMetadata (buffer, offset, _ref) { + const type = _ref.type + const endVal = _ref.endVal - var cursor = offset; - var metadata = []; - var item = undefined; + let cursor = offset + const metadata = [] + let item while (true) { - if (offset + 1 > buffer.length) throw new PartialReadError(); - item = buffer.readUInt8(cursor); + if (offset + 1 > buffer.length) throw new PartialReadError() + item = buffer.readUInt8(cursor) if (item === endVal) { return { value: metadata, size: cursor + 1 - offset - }; + } } - var results = this.read(buffer, cursor, type, {}); - metadata.push(results.value); - cursor += results.size; + const results = this.read(buffer, cursor, type, {}) + metadata.push(results.value) + cursor += results.size } } -function writeEntityMetadata(value, buffer, offset, _ref2) { - var type = _ref2.type; - var endVal = _ref2.endVal; +function writeEntityMetadata (value, buffer, offset, _ref2) { + const type = _ref2.type + const endVal = _ref2.endVal - var self = this; + const self = this value.forEach(function (item) { - offset = self.write(item, buffer, offset, type, {}); - }); - buffer.writeUInt8(endVal, offset); - return offset + 1; + offset = self.write(item, buffer, offset, type, {}) + }) + buffer.writeUInt8(endVal, offset) + return offset + 1 } -function sizeOfEntityMetadata(value, _ref3) { - var type = _ref3.type; +function sizeOfEntityMetadata (value, _ref3) { + const type = _ref3.type - var size = 1; - for (var i = 0; i < value.length; ++i) { - size += this.sizeOf(value[i], type, {}); + let size = 1 + for (let i = 0; i < value.length; ++i) { + size += this.sizeOf(value[i], type, {}) } - return size; + return size } -function readIpAddress(buffer, offset) { - var address = buffer[offset] + '.' + buffer[offset + 1] + '.' + buffer[offset + 2] + '.' + buffer[offset + 3]; +function readIpAddress (buffer, offset) { + const address = buffer[offset] + '.' + buffer[offset + 1] + '.' + buffer[offset + 2] + '.' + buffer[offset + 3] return { size: 4, value: address } } -function writeIpAddress(value, buffer, offset) { - var address = value.split('.'); +function writeIpAddress (value, buffer, offset) { + const address = value.split('.') address.forEach(function (b) { - buffer[offset] = parseInt(b); - offset++; - }); + buffer[offset] = parseInt(b) + offset++ + }) - return offset; + return offset } -function readEndOfArray(buffer, offset, typeArgs) { - var type = typeArgs.type; - var cursor = offset; - var elements = []; +function readEndOfArray (buffer, offset, typeArgs) { + const type = typeArgs.type + let cursor = offset + const elements = [] while (cursor < buffer.length) { - var results = this.read(buffer, cursor, type, {}); - elements.push(results.value); - cursor += results.size; + const results = this.read(buffer, cursor, type, {}) + elements.push(results.value) + cursor += results.size } return { value: elements, size: cursor - offset - }; -} - -function writeEndOfArray(value, buffer, offset, typeArgs) { - var type = typeArgs.type; - var self = this; - value.forEach(function (item) { - offset = self.write(item, buffer, offset, type, {}); - }); - return offset; -} - -function sizeOfEndOfArray(value, typeArgs) { - var type = typeArgs.type; - var size = 0; - for (var i = 0; i < value.length; ++i) { - size += this.sizeOf(value[i], type, {}); } - return size; +} + +function writeEndOfArray (value, buffer, offset, typeArgs) { + const type = typeArgs.type + const self = this + value.forEach(function (item) { + offset = self.write(item, buffer, offset, type, {}) + }) + return offset +} + +function sizeOfEndOfArray (value, typeArgs) { + const type = typeArgs.type + let size = 0 + for (let i = 0; i < value.length; ++i) { + size += this.sizeOf(value[i], type, {}) + } + return size } module.exports = { - 'uuid': [readUUID, writeUUID, 16], - 'nbt': [readNbt, writeNbt, sizeOfNbt], - 'entityMetadataLoop': [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata], - 'ipAddress': [readIpAddress, writeIpAddress, 4], - 'endOfArray': [readEndOfArray, writeEndOfArray, sizeOfEndOfArray], - 'zigzag32': zigzag.zigzag32, - 'zigzag64': zigzag.zigzag64 -} \ No newline at end of file + uuid: [readUUID, writeUUID, 16], + nbt: [readNbt, writeNbt, sizeOfNbt], + entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata], + ipAddress: [readIpAddress, writeIpAddress, 4], + endOfArray: [readEndOfArray, writeEndOfArray, sizeOfEndOfArray], + zigzag32: zigzag.zigzag32, + zigzag64: zigzag.zigzag64 +} diff --git a/src/datatypes/util.js b/src/datatypes/util.js index 1c8c3d8..029df2b 100644 --- a/src/datatypes/util.js +++ b/src/datatypes/util.js @@ -1,37 +1,33 @@ -const fs = require('fs'); +const fs = require('fs') -function getFiles(dir) { - var results = []; - var list = fs.readdirSync(dir); - list.forEach(function (file) { - file = dir + '/' + file; - var stat = fs.statSync(file); +function getFiles (dir) { + let results = [] + const list = fs.readdirSync(dir) + list.forEach((file) => { + file = dir + '/' + file + const stat = fs.statSync(file) if (stat && stat.isDirectory()) { - /* Recurse into a subdirectory */ - results = results.concat(getFiles(file)); + results = results.concat(getFiles(file)) } else { - /* Is a file */ - results.push(file); + results.push(file) } - }); - return results; + }) + return results } -module.exports = { - sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)) - }, +function sleep (ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} - waitFor(cb, withTimeout) { - return Promise.race([ - new Promise((res, rej) => cb(res)), - sleep(withTimeout) - ]) - }, +function waitFor (cb, withTimeout) { + return Promise.race([ + new Promise((resolve) => cb(resolve)), + sleep(withTimeout) + ]) +} - serialize(obj = {}, fmt) { - return JSON.stringify(obj, (k, v) => typeof v == 'bigint' ? v.toString() : v, fmt) - }, +function serialize (obj = {}, fmt) { + return JSON.stringify(obj, (k, v) => typeof v === 'bigint' ? v.toString() : v, fmt) +} - getFiles -} \ No newline at end of file +module.exports = { getFiles, sleep, waitFor, serialize } diff --git a/src/datatypes/varlong.js b/src/datatypes/varlong.js index fa1059f..5e57ad7 100644 --- a/src/datatypes/varlong.js +++ b/src/datatypes/varlong.js @@ -1,4 +1,4 @@ -function sizeOfVarLong(value) { +function sizeOfVarLong (value) { if (typeof value.valueOf() === 'object') { value = (BigInt(value[0]) << 32n) | BigInt(value[1]) } else if (typeof value !== 'bigint') value = BigInt(value) @@ -14,7 +14,7 @@ function sizeOfVarLong(value) { /** * Reads a 64-bit VarInt as a BigInt */ -function readVarLong(buffer, offset) { +function readVarLong (buffer, offset) { let result = BigInt(0) let shift = 0n let cursor = offset @@ -39,7 +39,7 @@ function readVarLong(buffer, offset) { /** * Writes a zigzag encoded 64-bit VarInt as a BigInt */ -function writeVarLong(value, buffer, offset) { +function writeVarLong (value, buffer, offset) { // if an array, turn it into a BigInt if (typeof value.valueOf() === 'object') { value = BigInt.asIntN(64, (BigInt(value[0]) << 32n)) | BigInt(value[1]) @@ -60,4 +60,4 @@ module.exports = { Read: { varint64: ['native', readVarLong] }, Write: { varint64: ['native', writeVarLong] }, SizeOf: { varint64: ['native', sizeOfVarLong] } -} \ No newline at end of file +} diff --git a/src/options.js b/src/options.js index e6fb930..9d204e1 100644 --- a/src/options.js +++ b/src/options.js @@ -13,4 +13,4 @@ const Versions = { '1.16.201': 422 } -module.exports = { defaultOptions, MIN_VERSION, CURRENT_VERSION, Versions } \ No newline at end of file +module.exports = { defaultOptions, MIN_VERSION, CURRENT_VERSION, Versions } diff --git a/src/rak.js b/src/rak.js index 09510ad..c08fe83 100644 --- a/src/rak.js +++ b/src/rak.js @@ -1,26 +1,25 @@ const { EventEmitter } = require('events') const Listener = require('jsp-raknet/listener') const EncapsulatedPacket = require('jsp-raknet/protocol/encapsulated_packet') +const Reliability = require('jsp-raknet/protocol/reliability') const RakClient = require('jsp-raknet/client') const ConnWorker = require('./rakWorker') const { waitFor } = require('./datatypes/util') try { - var { Client, Server, PacketPriority, PacketReliability, McPingMessage } = require('raknet-native') + var { Client, Server, PacketPriority, PacketReliability, McPingMessage } = require('raknet-native') // eslint-disable-line } catch (e) { console.debug('[raknet] native not found, using js', e) } class RakNativeClient extends EventEmitter { - constructor(options) { + constructor (options) { super() this.onConnected = () => { } this.onCloseConnection = () => { } this.onEncapsulated = () => { } this.raknet = new Client(options.hostname, options.port, 'minecraft') - this.raknet.on('encapsulated', thingy => { - // console.log('Encap',thingy) - const { buffer, address, guid } = thingy + this.raknet.on('encapsulated', ({ buffer, address }) => { this.onEncapsulated(buffer, address) }) this.raknet.on('connected', () => { @@ -28,7 +27,7 @@ class RakNativeClient extends EventEmitter { }) } - async ping() { + async ping () { this.raknet.ping() return waitFor((done) => { this.raknet.on('pong', (ret) => { @@ -39,18 +38,18 @@ class RakNativeClient extends EventEmitter { }, 1000) } - connect() { + connect () { this.raknet.connect() } - sendReliable(buffer, immediate) { + sendReliable (buffer, immediate) { const priority = immediate ? PacketPriority.IMMEDIATE_PRIORITY : PacketPriority.MEDIUM_PRIORITY return this.raknet.send(buffer, priority, PacketReliability.RELIABLE_ORDERED, 0) } } class RakNativeServer extends EventEmitter { - constructor(options = {}) { + constructor (options = {}) { super() this.onOpenConnection = () => { } this.onCloseConnection = () => { } @@ -75,20 +74,19 @@ class RakNativeServer extends EventEmitter { this.onCloseConnection(client) }) - this.raknet.on('encapsulated', (thingy) => { - const { buffer, address, guid } = thingy + this.raknet.on('encapsulated', ({ buffer, address }) => { // console.log('ENCAP',thingy) this.onEncapsulated(buffer, address) }) } - listen() { + listen () { this.raknet.listen() } } class RakJsClient extends EventEmitter { - constructor(options = {}) { + constructor (options = {}) { super() this.onConnected = () => { } this.onEncapsulated = () => { } @@ -101,23 +99,25 @@ class RakJsClient extends EventEmitter { } } - workerConnect(hostname = this.options.hostname, port = this.options.port) { + workerConnect (hostname = this.options.hostname, port = this.options.port) { this.worker = ConnWorker.connect(hostname, port) this.worker.on('message', (evt) => { switch (evt.type) { - case 'connected': + case 'connected': { this.onConnected() break - case 'encapsulated': + } + case 'encapsulated': { const [ecapsulated, address] = evt.args this.onEncapsulated(ecapsulated.buffer, address.hash) break + } } }) } - async plainConnect(hostname = this.options.hostname, port = this.options.port) { + async plainConnect (hostname = this.options.hostname, port = this.options.port) { this.raknet = new RakClient(hostname, port) await this.raknet.connect() @@ -129,11 +129,11 @@ class RakJsClient extends EventEmitter { this.raknet.on('encapsulated', (encapsulated, addr) => this.onEncapsulated(encapsulated.buffer, addr.hash)) } - workerSendReliable(buffer, immediate) { + workerSendReliable (buffer, immediate) { this.worker.postMessage({ type: 'queueEncapsulated', packet: buffer, immediate }) } - plainSendReliable(buffer, immediate) { + plainSendReliable (buffer, immediate) { const sendPacket = new EncapsulatedPacket() sendPacket.reliability = Reliability.ReliableOrdered sendPacket.buffer = buffer @@ -143,7 +143,7 @@ class RakJsClient extends EventEmitter { } class RakJsServer extends EventEmitter { - constructor(options = {}) { + constructor (options = {}) { super() this.options = options this.onOpenConnection = () => { } @@ -157,7 +157,7 @@ class RakJsServer extends EventEmitter { } } - async plainListen() { + async plainListen () { this.raknet = new Listener() await this.raknet.listen(this.options.hostname, this.options.port) this.raknet.on('openConnection', (conn) => { @@ -178,4 +178,4 @@ class RakJsServer extends EventEmitter { module.exports = { RakClient: Client ? RakNativeClient : RakJsClient, RakServer: Server ? RakNativeServer : RakJsServer -} \ No newline at end of file +} diff --git a/src/rakWorker.js b/src/rakWorker.js index 3dc837a..d9a4bc5 100644 --- a/src/rakWorker.js +++ b/src/rakWorker.js @@ -3,7 +3,7 @@ const { Worker, isMainThread, parentPort } = require('worker_threads') const EncapsulatedPacket = require('jsp-raknet/protocol/encapsulated_packet') const Reliability = require('jsp-raknet/protocol/reliability') -function connect(hostname, port) { +function connect (hostname, port) { if (isMainThread) { const worker = new Worker(__filename) worker.postMessage({ type: 'connect', hostname, port }) @@ -11,12 +11,12 @@ function connect(hostname, port) { } } -var raknet +let raknet -function main() { +function main () { parentPort.on('message', (evt) => { - if (evt.type == 'connect') { - const { hostname, port } =evt + if (evt.type === 'connect') { + const { hostname, port } = evt raknet = new RakClient(hostname, port) raknet.connect().then(() => { @@ -30,7 +30,7 @@ function main() { }) raknet.once('connected', (connection) => { - console.log(`[worker] connected!`) + console.log('[worker] connected!') globalThis.raknetConnection = connection parentPort.postMessage({ type: 'connected' }) }) @@ -45,8 +45,8 @@ function main() { raknet.on('raw', (buffer, inetAddr) => { console.log('Raw packet', buffer, inetAddr) }) - } else if (evt.type == 'queueEncapsulated') { - console.log('SEND' , globalThis.raknetConnection, evt.packet) + } else if (evt.type === 'queueEncapsulated') { + // console.log('SEND', globalThis.raknetConnection, evt.packet) const sendPacket = new EncapsulatedPacket() sendPacket.reliability = Reliability.ReliableOrdered @@ -61,4 +61,4 @@ function main() { } if (!isMainThread) main() -module.exports = { connect } \ No newline at end of file +module.exports = { connect } diff --git a/src/relay.js b/src/relay.js index 08314d1..dbb15c5 100644 --- a/src/relay.js +++ b/src/relay.js @@ -1,8 +1,7 @@ // process.env.DEBUG = 'minecraft-protocol raknet' -const fs = require('fs') -const { Client } = require("./client") -const { Server } = require("./server") -const { Player } = require("./serverPlayer") +const { Client } = require('./client') +const { Server } = require('./server') +const { Player } = require('./serverPlayer') const debug = require('debug')('minecraft-protocol relay') const { serialize } = require('./datatypes/util') @@ -11,7 +10,7 @@ const { serialize } = require('./datatypes/util') const debugging = true // Do re-encoding tests class RelayPlayer extends Player { - constructor(server, conn) { + constructor (server, conn) { super(server, conn) this.server = server this.conn = conn @@ -47,7 +46,7 @@ class RelayPlayer extends Player { } // Called when we get a packet from backend server (Backend -> PROXY -> Client) - readUpstream(packet) { + readUpstream (packet) { if (!this.startRelaying) { console.warn('The downstream client is not ready yet !!') this.downQ.push(packet) @@ -59,7 +58,7 @@ class RelayPlayer extends Player { const params = des.data.params this.upInLog('~ Bounce B->C', name, serialize(params).slice(0, 100)) // this.upInLog('~ ', des.buffer) - if (name == 'play_status' && params.status == 'login_success') return // We already sent this, this needs to be sent ASAP or client will disconnect + if (name === 'play_status' && params.status === 'login_success') return // We already sent this, this needs to be sent ASAP or client will disconnect if (debugging) { // some packet encode/decode testing stuff const rpacket = this.server.serializer.createPacketBuffer({ name, params }) @@ -76,7 +75,7 @@ class RelayPlayer extends Player { } // Send queued packets to the connected client - flushDownQueue() { + flushDownQueue () { for (const packet of this.downQ) { const des = this.server.deserializer.parsePacketBuffer(packet) this.write(des.data.name, des.data.params) @@ -85,10 +84,10 @@ class RelayPlayer extends Player { } // Send queued packets to the backend upstream server from the client - flushUpQueue() { - for (var e of this.upQ) { // Send the queue + flushUpQueue () { + for (const e of this.upQ) { // Send the queue const des = this.server.deserializer.parsePacketBuffer(e) - if (des.data.name == 'client_cache_status') { // Currently broken, force off the chunk cache + if (des.data.name === 'client_cache_status') { // Currently broken, force off the chunk cache this.upstream.write('client_cache_status', { enabled: false }) } else { this.upstream.write(des.data.name, des.data.params) @@ -98,8 +97,8 @@ class RelayPlayer extends Player { } // Called when the server gets a packet from the downstream player (Client -> PROXY -> Backend) - readPacket(packet) { - if (this.startRelaying) { // The downstream client conn is established & we got a packet to send to upstream server + readPacket (packet) { + if (this.startRelaying) { // The downstream client conn is established & we got a packet to send to upstream server if (!this.upstream) { // Upstream is still connecting/handshaking this.downInLog('Got downstream connected packet but upstream is not connected yet, added to q', this.upQ.length) this.upQ.push(packet) // Put into a queue @@ -138,16 +137,16 @@ class RelayPlayer extends Player { class Relay extends Server { /** * Creates a new non-transparent proxy connection to a destination server - * @param {Options} options + * @param {Options} options */ - constructor(options) { + constructor (options) { super(options) this.RelayPlayer = options.relayPlayer || RelayPlayer this.forceSingle = true this.upstreams = new Map() } - openUpstreamConnection(ds, clientAddr) { + openUpstreamConnection (ds, clientAddr) { const client = new Client({ hostname: this.options.destination.hostname, port: this.options.destination.port, @@ -165,7 +164,7 @@ class Relay extends Server { this.upstreams.set(clientAddr.hash, client) } - closeUpstreamConnection(clientAddr) { + closeUpstreamConnection (clientAddr) { const up = this.upstreams.get(clientAddr.hash) if (!up) throw Error(`unable to close non-open connection ${clientAddr.hash}`) up.close() @@ -189,4 +188,4 @@ class Relay extends Server { } // Too many things called 'Proxy' ;) -module.exports = { Relay } \ No newline at end of file +module.exports = { Relay } diff --git a/src/server.js b/src/server.js index 1dff4e7..94010d0 100644 --- a/src/server.js +++ b/src/server.js @@ -6,7 +6,7 @@ const Options = require('./options') const debug = require('debug')('minecraft-protocol') class Server extends EventEmitter { - constructor(options) { + constructor (options) { super() this.options = { ...Options.defaultOptions, ...options } this.validateOptions() @@ -18,7 +18,7 @@ class Server extends EventEmitter { this.outLog = (...args) => console.debug('S -> C', ...args) } - validateOptions() { + validateOptions () { if (!Options.Versions[this.options.version]) { console.warn('Supported versions: ', Options.Versions) throw Error(`Unsupported version ${this.options.version}`) @@ -52,7 +52,7 @@ class Server extends EventEmitter { client.handle(buffer) } - async create(hostname = this.options.hostname, port = this.options.port) { + async create (hostname = this.options.hostname, port = this.options.port) { this.raknet = new RakServer({ hostname, port }) await this.raknet.listen() console.debug('Listening on', hostname, port) @@ -62,4 +62,4 @@ class Server extends EventEmitter { } } -module.exports = { Server } \ No newline at end of file +module.exports = { Server } diff --git a/src/serverPlayer.js b/src/serverPlayer.js index 3f41817..4a33aed 100644 --- a/src/serverPlayer.js +++ b/src/serverPlayer.js @@ -2,7 +2,8 @@ const { Encrypt } = require('./auth/encryption') const { decodeLoginJWT } = require('./auth/chains') const { Connection } = require('./connection') const fs = require('fs') -const debug = require('debug')('minecraft-protocol') +// const debug = require('debug')('minecraft-protocol') +const { MIN_VERSION } = require('./options') const ClientStatus = { Authenticating: 0, @@ -11,7 +12,7 @@ const ClientStatus = { } class Player extends Connection { - constructor(server, connection) { + constructor (server, connection) { super() this.server = server this.serializer = server.serializer @@ -26,12 +27,12 @@ class Player extends Connection { this.outLog = (...args) => console.info('C -> S', ...args) } - getData() { + getData () { return this.userData } - onLogin(packet) { - let body = packet.data + onLogin (packet) { + const body = packet.data // debug('Login body', body) this.emit('loggingIn', body) @@ -51,7 +52,7 @@ class Player extends Connection { const skinChain = body.params.client_data try { - var { key, userData, chain } = decodeLoginJWT(authChain.chain, skinChain) + var { key, userData, chain } = decodeLoginJWT(authChain.chain, skinChain) // eslint-disable-line } catch (e) { console.error(e) // TODO: disconnect user @@ -68,17 +69,17 @@ class Player extends Connection { /** * Disconnects a client before it has joined - * @param {string} play_status + * @param {string} playStatus */ - sendDisconnectStatus(play_status) { - this.write('play_status', { status: play_status }) + sendDisconnectStatus (playStatus) { + this.write('play_status', { status: playStatus }) this.connection.close() } /** * Disconnects a client after it has joined */ - disconnect(reason, hide = false) { + disconnect (reason, hide = false) { this.write('disconnect', { hide_disconnect_screen: hide, message: reason @@ -88,7 +89,7 @@ class Player extends Connection { // After sending Server to Client Handshake, this handles the client's // Client to Server handshake response. This indicates successful encryption - onHandshake() { + onHandshake () { // this.outLog('Sending login success!', this.status) // https://wiki.vg/Bedrock_Protocol#Play_Status this.write('play_status', { status: 'login_success' }) @@ -96,10 +97,10 @@ class Player extends Connection { this.emit('join') } - readPacket(packet) { + readPacket (packet) { // console.log('packet', packet) try { - var des = this.server.deserializer.parsePacketBuffer(packet) + var des = this.server.deserializer.parsePacketBuffer(packet) // eslint-disable-line } catch (e) { this.disconnect('Server error') console.warn('Packet parsing failed! Writing dump to ./packetdump.bin') @@ -117,10 +118,12 @@ class Player extends Connection { case 'client_to_server_handshake': // Emit the 'join' event this.onHandshake() + break case 'set_local_player_as_initialized': this.state = ClientStatus.Initialized // Emit the 'spawn' event this.emit('spawn') + break default: console.log('ignoring, unhandled') } @@ -128,4 +131,4 @@ class Player extends Connection { } } -module.exports = { Player, ClientStatus } \ No newline at end of file +module.exports = { Player, ClientStatus } diff --git a/src/transforms/encryption.js b/src/transforms/encryption.js index d2c1794..1822238 100644 --- a/src/transforms/encryption.js +++ b/src/transforms/encryption.js @@ -5,14 +5,14 @@ const Zlib = require('zlib') const CIPHER_ALG = 'aes-256-cfb8' -function createCipher(secret, initialValue) { +function createCipher (secret, initialValue) { if (crypto.getCiphers().includes(CIPHER_ALG)) { return crypto.createCipheriv(CIPHER_ALG, secret, initialValue) } return new Cipher(secret, initialValue) } -function createDecipher(secret, initialValue) { +function createDecipher (secret, initialValue) { if (crypto.getCiphers().includes(CIPHER_ALG)) { return crypto.createDecipheriv(CIPHER_ALG, secret, initialValue) } @@ -20,12 +20,12 @@ function createDecipher(secret, initialValue) { } class Cipher extends Transform { - constructor(secret, iv) { + constructor (secret, iv) { super() this.aes = new aesjs.ModeOfOperation.cfb(secret, iv, 1) // eslint-disable-line new-cap } - _transform(chunk, enc, cb) { + _transform (chunk, enc, cb) { try { const res = this.aes.encrypt(chunk) cb(null, res) @@ -36,12 +36,12 @@ class Cipher extends Transform { } class Decipher extends Transform { - constructor(secret, iv) { + constructor (secret, iv) { super() this.aes = new aesjs.ModeOfOperation.cfb(secret, iv, 1) // eslint-disable-line new-cap } - _transform(chunk, enc, cb) { + _transform (chunk, enc, cb) { try { const res = this.aes.decrypt(chunk) cb(null, res) @@ -51,26 +51,26 @@ class Decipher extends Transform { } } -function computeCheckSum(packetPlaintext, sendCounter, secretKeyBytes) { - let digest = crypto.createHash('sha256'); - let counter = Buffer.alloc(8) +function computeCheckSum (packetPlaintext, sendCounter, secretKeyBytes) { + const digest = crypto.createHash('sha256') + const counter = Buffer.alloc(8) counter.writeBigInt64LE(sendCounter, 0) - digest.update(counter); - digest.update(packetPlaintext); - digest.update(secretKeyBytes); - let hash = digest.digest(); - return hash.slice(0, 8); + digest.update(counter) + digest.update(packetPlaintext) + digest.update(secretKeyBytes) + const hash = digest.digest() + return hash.slice(0, 8) } -function createEncryptor(client, iv) { +function createEncryptor (client, iv) { client.cipher = createCipher(client.secretKeyBytes, iv) client.sendCounter = client.sendCounter || 0n // A packet is encrypted via AES256(plaintext + SHA256(send_counter + plaintext + secret_key)[0:8]). // The send counter is represented as a little-endian 64-bit long and incremented after each packet. - function process(chunk) { - const buffer = Zlib.deflateRawSync(chunk, { level: 7 }) + function process (chunk) { + const buffer = Zlib.deflateRawSync(chunk, { level: 7 }) const packet = Buffer.concat([buffer, computeCheckSum(buffer, client.sendCounter, client.secretKeyBytes)]) client.sendCounter++ client.cipher.write(packet) @@ -83,12 +83,11 @@ function createEncryptor(client, iv) { } } - -function createDecryptor(client, iv) { +function createDecryptor (client, iv) { client.decipher = createDecipher(client.secretKeyBytes, iv) client.receiveCounter = client.receiveCounter || 0n - function verify(chunk) { + function verify (chunk) { // TODO: remove the extra logic here, probably fixed with new raknet impl // console.log('Decryptor: checking checksum', client.receiveCounter, chunk) @@ -102,7 +101,7 @@ function createDecryptor(client, iv) { // Holds how much bytes we read, also where the checksum (should) start const inflatedLen = engine.bytesRead // It appears that mc sends extra bytes past the checksum. I don't think this is a raknet - // issue (as we are able to decipher properly, zlib works and should also have a checksum) so + // issue (as we are able to decipher properly, zlib works and should also have a checksum) so // there needs to be more investigation done. If you know what's wrong here, please make an issue :) const extraneousLen = chunk.length - inflatedLen - 8 if (extraneousLen > 0) { // Extra bytes @@ -115,16 +114,16 @@ function createDecryptor(client, iv) { throw new Error('Decrypted packet is missing checksum') } - const packet = chunk.slice(0, inflatedLen); - const checksum = chunk.slice(inflatedLen, inflatedLen + 8); + const packet = chunk.slice(0, inflatedLen) + const checksum = chunk.slice(inflatedLen, inflatedLen + 8) const computedCheckSum = computeCheckSum(packet, client.receiveCounter, client.secretKeyBytes) client.receiveCounter++ - if (checksum.toString("hex") == computedCheckSum.toString("hex")) { + if (checksum.toString('hex') === computedCheckSum.toString('hex')) { client.onDecryptedPacket(buffer) } else { console.log('Inflated', inflatedLen, chunk.length, extraneousLen, chunk.toString('hex')) - throw Error(`Checksum mismatch ${checksum.toString("hex")} != ${computedCheckSum.toString("hex")}`) + throw Error(`Checksum mismatch ${checksum.toString('hex')} != ${computedCheckSum.toString('hex')}`) } } @@ -139,16 +138,16 @@ module.exports = { createCipher, createDecipher, createEncryptor, createDecryptor } -function testDecrypt() { - const client = { - secretKeyBytes: Buffer.from('ZOBpyzki/M8UZv5tiBih048eYOBVPkQE3r5Fl0gmUP4=', 'base64'), - onDecryptedPacket: (...data) => console.log('Decrypted', data) - } - const iv = Buffer.from('ZOBpyzki/M8UZv5tiBih0w==', 'base64') +// function testDecrypt () { +// const client = { +// secretKeyBytes: Buffer.from('ZOBpyzki/M8UZv5tiBih048eYOBVPkQE3r5Fl0gmUP4=', 'base64'), +// onDecryptedPacket: (...data) => console.log('Decrypted', data) +// } +// const iv = Buffer.from('ZOBpyzki/M8UZv5tiBih0w==', 'base64') - const decrypt = createDecryptor(client, iv) - console.log('Dec', decrypt(Buffer.from('4B4FCA0C2A4114155D67F8092154AAA5EF', 'hex'))) - console.log('Dec 2', decrypt(Buffer.from('DF53B9764DB48252FA1AE3AEE4', 'hex'))) -} +// const decrypt = createDecryptor(client, iv) +// console.log('Dec', decrypt(Buffer.from('4B4FCA0C2A4114155D67F8092154AAA5EF', 'hex'))) +// console.log('Dec 2', decrypt(Buffer.from('DF53B9764DB48252FA1AE3AEE4', 'hex'))) +// } -// testDecrypt() \ No newline at end of file +// testDecrypt() diff --git a/src/transforms/serializer.js b/src/transforms/serializer.js index c7a70ed..bbaac1a 100644 --- a/src/transforms/serializer.js +++ b/src/transforms/serializer.js @@ -2,9 +2,9 @@ const { ProtoDefCompiler, CompiledProtodef } = require('protodef').Compiler const { FullPacketParser, Serializer } = require('protodef') // Compiles the ProtoDef schema at runtime -function createProtocol(version) { +function createProtocol (version) { const protocol = require(`../../data/${version}/protocol.json`).types - var compiler = new ProtoDefCompiler() + const compiler = new ProtoDefCompiler() compiler.addTypesToCompile(protocol) compiler.addTypes(require('../datatypes/compiler-minecraft')) compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) @@ -14,7 +14,7 @@ function createProtocol(version) { } // Loads already generated read/write/sizeof code -function getProtocol(version) { +function getProtocol (version) { const compiler = new ProtoDefCompiler() compiler.addTypes(require('../datatypes/compiler-minecraft')) compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) @@ -32,18 +32,18 @@ function getProtocol(version) { ) } -function createSerializer(version) { - var proto = getProtocol(version) - return new Serializer(proto, 'mcpe_packet'); +function createSerializer (version) { + const proto = getProtocol(version) + return new Serializer(proto, 'mcpe_packet') } -function createDeserializer(version) { - var proto = getProtocol(version) - return new FullPacketParser(proto, 'mcpe_packet'); +function createDeserializer (version) { + const proto = getProtocol(version) + return new FullPacketParser(proto, 'mcpe_packet') } module.exports = { createDeserializer: createDeserializer, createSerializer: createSerializer, createProtocol: createProtocol -} \ No newline at end of file +}