diff --git a/commands/test.js b/commands/test.js deleted file mode 100644 index 73b2f40..0000000 --- a/commands/test.js +++ /dev/null @@ -1,23 +0,0 @@ -const { literal } = require('brigadier-commands') -const { generateString } = require('/var/tmp/nbt-overflow.js') -const fs = require('fs') - -module.exports = { - register (dispatcher) { - const node = dispatcher.register( - literal('test') - .executes(this.testCommand) - ) - - node.description = 'Tests something' - node.permissionLevel = 1 - }, - - testCommand (context) { - const source = context.source - const bot = source.bot - - const payload = fs.readFileSync('/var/tmp/test_payload.bin') - bot.core.run(`data modify storage test '${generateString(payload).replace(/\'/g, m => '\\' + m)}' set value a`) - } -} diff --git a/index.js b/index.js index 885b77a..c298335 100644 --- a/index.js +++ b/index.js @@ -54,6 +54,15 @@ async function main () { bot.on('error', error => bot.console.error(error)) } + + process.on('uncaughtException', error => { + if (error.stack.includes('protodef')) { + console.error('Uncaught protodef exception!\n' + error.stack) + return // Ignore protodef-related errors (packet-related) + } + + throw error + }) } main() diff --git a/plugins/core.js b/plugins/core.js index c155629..b5ba248 100644 --- a/plugins/core.js +++ b/plugins/core.js @@ -11,7 +11,14 @@ function inject (bot) { block: { x: null, y: null, z: null }, refill () { - const refillCommand = `/fill ${this.start.x} ${this.start.y} ${this.start.z} ${this.end.x} ${this.end.y} ${this.end.z} repeating_command_block{CustomName:'""'}` + const refillCommand = `fill ${this.start.x} ${this.start.y} ${this.start.z} ${this.end.x} ${this.end.y} ${this.end.z} repeating_command_block{CustomName:'""'}` + + if (bot.proxy?.client) { + // Use chat commands when a player is logged into the proxy + bot.chat.command(refillCommand) + return + } + const location = { x: Math.floor(bot.position.x), y: Math.floor(bot.position.y) - 1, z: Math.floor(bot.position.z) } const commandBlockId = bot.registry?.itemsByName.command_block.id diff --git a/plugins/proxy.js b/plugins/proxy.js new file mode 100644 index 0000000..d1dbba6 --- /dev/null +++ b/plugins/proxy.js @@ -0,0 +1,123 @@ +const mc = require('minecraft-protocol') + +const LOGIN_PACKET_NAMES = [ + 'login', + 'difficulty', + 'abilities', + 'held_item_slot', + 'declare_recipes', + 'entity_status', + 'declare_commands', + 'unlock_recipes', + 'position', + // 'server_data', + // 'player_info', + 'initialize_world_border', + 'update_time', + 'spawn_position', + 'game_state_change', + 'set_ticking_state', + 'step_tick', + 'update_view_position' +] + +function createLoginPacketMap () { + return new Map(LOGIN_PACKET_NAMES.map(name => [name, null])) +} + +function inject (bot, options) { + if (!options.proxy?.enabled) return + + options.proxy.version ??= bot._client.version + options.proxy.enforceSecureProfile ??= false + + bot.proxy = { + server: mc.createServer(options.proxy), + options: options.proxy, + client: null, + loginPackets: createLoginPacketMap(), + _players: {} + } + + bot.on('end', () => { + bot.proxy.loginPackets = createLoginPacketMap() + bot.proxy._players = {} + }) + + bot.on('packet.registry_data', packet => { + bot.proxy.options.registryCodec = packet.codec // * nmp gets the registry codec from options + }) + + bot.on('packet', (data, meta) => { + if (bot.proxy.client && bot.proxy.client.state === mc.states.PLAY && meta.state === mc.states.PLAY) { + // Forward packets + bot.proxy.client.write(meta.name, data) + } + + // Store login-related packets + if (!bot.proxy.loginPackets.has(meta.name)) return + if (meta.name === 'entity_status' && (data.entityStatus < 24 || data.entityStatus > 28)) return // Only forward permission level entity statuses on login + if (meta.name === 'game_state_change' && data.reason !== 13) return // Only forward "Start waiting for level chunks" + + bot.proxy.loginPackets.set(meta.name, data) + }) + + bot.proxy.server.on('playerJoin', client => { + if (bot.proxy.client) bot.proxy.client.end('Logged in from another client!') + + bot.proxy.client = client + + client.on('packet', (data, meta) => { + if (bot._client.state !== mc.states.PLAY || meta.state !== mc.states.PLAY) return + bot._client.write(meta.name, data) + }) + + client.on('position', packet => { + bot.position.x = packet.x + bot.position.y = packet.y + bot.position.z = packet.z + }) + + + client.on('look', packet => { + bot.position.yaw = packet.yaw + bot.position.pitch = packet.pitch + }) + + client.on('position_look', packet => { + bot.position.x = packet.x + bot.position.y = packet.y + bot.position.z = packet.z + bot.position.yaw = packet.yaw + bot.position.pitch = packet.pitch + }) + + client.on('vehicle_move', packet => { + bot.position.x = packet.x + bot.position.y = packet.y + bot.position.z = packet.z + bot.position.yaw = packet.yaw + bot.position.pitch = packet.pitch + }) + + for (const [name, data] of bot.proxy.loginPackets) { + if (!data) continue + client.write(name, data) + } + + // send player info + for (const player of Object.values(bot.proxy._players)) {console.log('writing data for player %s (%s)', player.player?.username, player.uuid) + client.write('player_info', { action: player.flags, data: [{ ...player, flags: undefined }] }) + } + }) + + bot.on('packet.player_info', packet => { + for (const player of packet.data) { + const old = bot.proxy._players[player.uuid] ?? { flags: 0 } + const filteredPlayer = Object.fromEntries(Object.entries(player).filter(([k, v]) => v != null)) + bot.proxy._players[player.uuid] = { ...old, ...filteredPlayer, flags: old.flags | packet.action } + } + }) +} + +module.exports = inject