From cf6471f6eb0db16f097aa1751faba1916f1e9421 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Tue, 23 Mar 2021 03:35:38 -0400 Subject: [PATCH] Add packet dumper, configuable vanilla server, client events --- src/client.js | 18 ++------ tools/genPacketDumps.js | 83 +++++++++++++++++++++++++++++++++++++ tools/startVanillaServer.js | 30 +++++++++----- 3 files changed, 105 insertions(+), 26 deletions(-) create mode 100644 tools/genPacketDumps.js diff --git a/src/client.js b/src/client.js index 47c5a60..b270c2c 100644 --- a/src/client.js +++ b/src/client.js @@ -108,10 +108,8 @@ class Client extends Connection { } 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 + this.emit('kick', packet) } onPlayStatus (statusPacket) { @@ -125,6 +123,7 @@ class Client extends Connection { } close () { + this.emit('close') clearInterval(this.loop) clearTimeout(this.connectTimeout) this.q = [] @@ -155,22 +154,13 @@ class Client extends Connection { 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.emit('packet', pakData) if (debugging) { // Packet verifying (decode + re-encode + match test) if (pakData.name) { this.tryRencode(pakData.name, pakData.params, packet) } - - // console.info('->', JSON.stringify(pakData, (k,v) => typeof v == 'bigint' ? v.toString() : v)) - // Packet dumping - try { - const root = __dirname + `../data/${this.options.version}/sample/` - if (!fs.existsSync(root + `packets/${pakData.name}.json`)) { - fs.writeFileSync(root + `packets/${pakData.name}.json`, serialize(pakData.params, 2)) - fs.writeFileSync(root + `packets/${pakData.name}.txt`, packet.toString('hex')) - } - } catch { } } // Abstract some boilerplate before sending to listeners @@ -187,8 +177,6 @@ class Client extends Connection { case 'play_status': this.onPlayStatus(pakData.params) break - default: - // console.log('Sending to listeners') } // Emit packet diff --git a/tools/genPacketDumps.js b/tools/genPacketDumps.js new file mode 100644 index 0000000..dd74150 --- /dev/null +++ b/tools/genPacketDumps.js @@ -0,0 +1,83 @@ +/* eslint-disable */ +// Collect sample packets needed for `serverTest.js` +// process.env.DEBUG = 'minecraft-protocol' +const fs = require('fs') +const vanillaServer = require('../tools/startVanillaServer') +const { Client } = require('../src/client') +const { serialize, waitFor } = require('../src/datatypes/util') +const { CURRENT_VERSION } = require('../src/options') +const { join } = require('path') + +let loop + +async function main() { + const random = ((Math.random() * 100) | 0) + const port = 19130 + random + + const handle = await vanillaServer.startServerAndWait(CURRENT_VERSION, 1000 * 120, { 'server-port': port, path: 'bds_' }) + + console.log('Started server') + const client = new Client({ + hostname: '127.0.0.1', + port, + username: 'Boat' + random, + offline: true + }) + + return waitFor(async res => { + const root = join(__dirname, `../data/${client.options.version}/sample/packets/`) + if (!fs.existsSync(root)) { + fs.mkdirSync(root, { recursive: true }) + } + + client.once('resource_packs_info', (packet) => { + client.write('resource_pack_client_response', { + response_status: 'completed', + resourcepackids: [] + }) + + client.once('resource_pack_stack', (stack) => { + client.write('resource_pack_client_response', { + response_status: 'completed', + resourcepackids: [] + }) + }) + + + 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 }) + + clearInterval(loop) + loop = setInterval(() => { + client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: BigInt(Date.now()) }) + }, 200) + }) + + client.on('packet', pakData => { // Packet dumping + if (pakData.name == 'level_chunk') return + try { + if (!fs.existsSync(root + `${pakData.name}.json`)) { + fs.promises.writeFile(root + `${pakData.name}.json`, serialize(pakData.params, 2)) + } + } catch (e) { console.log(e) } + }) + + console.log('Awaiting join...') + + client.on('spawn', () => { + console.log('Spawned!') + clearInterval(loop) + client.close() + handle.kill() + res() + }) + }, 1000 * 60, () => { + clearInterval(loop) + throw Error('timed out') + }) +} + +main().then(() => { + console.log('Successfully dumped packets') +}) \ No newline at end of file diff --git a/tools/startVanillaServer.js b/tools/startVanillaServer.js index 83856f4..07587ae 100644 --- a/tools/startVanillaServer.js +++ b/tools/startVanillaServer.js @@ -17,18 +17,18 @@ function fetchLatestStable () { } // Download + extract vanilla server and enter the directory -async function download (os, version) { +async function download (os, version, path = 'bds-') { process.chdir(__dirname) const verStr = version.split('.').slice(0, 3).join('.') - const dir = 'bds-' + version + const dir = path + version if (fs.existsSync(dir) && getFiles(dir).length) { - process.chdir('bds-' + version) // Enter server folder + process.chdir(path + version) // Enter server folder return verStr } try { fs.mkdirSync(dir) } catch { } - process.chdir('bds-' + version) // Enter server folder + process.chdir(path + version) // Enter server folder const url = (os, version) => `https://minecraft.azureedge.net/bin-${os}/bedrock-server-${version}.zip` let found = false @@ -53,10 +53,18 @@ async function download (os, version) { return verStr } +const defaultOptions = { + 'level-generator': '2', + 'server-port': '19130', + 'online-mode': 'false' +} + // Setup the server -function configure () { +function configure (options = {}) { + const opts = { ...defaultOptions, ...options } let config = fs.readFileSync('./server.properties', 'utf-8') - config += '\nlevel-generator=2\nserver-port=19130\nplayer-idle-timeout=1\nallow-cheats=true\ndefault-player-permission-level=operator\nonline-mode=false' + config += '\nplayer-idle-timeout=1\nallow-cheats=true\ndefault-player-permission-level=operator' + for (const o in opts) config += `\n${o}=${opts[o]}` fs.writeFileSync('./server.properties', config) } @@ -66,13 +74,13 @@ function run (inheritStdout = true) { } // Run the server -async function startServer (version, onStart) { +async function startServer (version, onStart, options = {}) { const os = process.platform === 'win32' ? 'win' : process.platform if (os !== 'win' && os !== 'linux') { throw Error('unsupported os ' + os) } - await download(os, version) - configure() + await download(os, version, options.path) + configure(options) const handle = run(!onStart) if (onStart) { handle.stdout.on('data', data => data.includes('Server started.') ? onStart() : null) @@ -83,10 +91,10 @@ async function startServer (version, onStart) { } // Start the server and wait for it to be ready, with a timeout -async function startServerAndWait (version, withTimeout) { +async function startServerAndWait (version, withTimeout, options) { let handle await waitFor(async res => { - handle = await startServer(version, res) + handle = await startServer(version, res, options) }, withTimeout, () => { handle?.kill() throw new Error('Server did not start on time ' + withTimeout)