From f0fbf4f85979ac47a723ceda3df3afb40ca543d9 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Mon, 24 May 2021 10:17:09 -0400 Subject: [PATCH] Send skin data, protocol updates (#88) * Add skin data * Serialization updates * Dynamic shield item id * NBT reading/writing on void type uses 0 length, fix some third party servers * Fix proxy empty chunk issue * Fix scoreboards compiler needs ../ * fix indentation * Fix set_score packet * Fix readme title auth doc * Implement new compiler vars --- README.md | 4 +- data/1.16.201/biome_definitions.nbt | Bin 37626 -> 0 bytes data/1.16.201/creativeitems.json | 4898 ------------------------- data/1.16.201/skin_geom.txt | 1 - data/1.16.201/steve.json | 120 + data/1.16.201/steveGeometry.json | 5147 +++++++++++++++++++++++++++ data/1.16.201/steveSkin.bin | Bin 0 -> 262144 bytes data/1.16.220/protocol.json | 292 +- data/latest/proto.yml | 43 +- data/latest/types.yaml | 36 +- package.json | 6 +- src/client.js | 28 +- src/connection.js | 24 +- src/createClient.js | 2 +- src/datatypes/minecraft.js | 6 +- src/handshake/login.js | 40 +- src/relay.js | 49 +- src/transforms/serializer.js | 19 +- tools/compileProtocol.js | 6 +- 19 files changed, 5557 insertions(+), 5164 deletions(-) delete mode 100644 data/1.16.201/biome_definitions.nbt delete mode 100644 data/1.16.201/creativeitems.json delete mode 100644 data/1.16.201/skin_geom.txt create mode 100644 data/1.16.201/steve.json create mode 100644 data/1.16.201/steveGeometry.json create mode 100644 data/1.16.201/steveSkin.bin diff --git a/README.md b/README.md index 1c87815..124f1b2 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ const client = bedrock.createClient({ port: 19132, // optional, default 19132 username: 'Notch', // the username you want to join as, optional if online mode offline: true // optional, default false. if true, do not login with Xbox Live. You will not be asked to sign-in if set to true. + // Optional for some servers which verify the title ID: + // authTitle: bedrock.title.MinecraftNintendoSwitch }) client.on('text', (packet) => { // Listen for chat messages and echo them back. @@ -66,8 +68,6 @@ const server = new bedrock.createServer({ host: '0.0.0.0', // optional. host to bind as. port: 19132, // optional version: '1.16.220', // optional. The server version, latest if not specified. - // Optional for some servers which verify the title ID: - // authTitle: bedrock.title.MinecraftNintendoSwitch }) server.on('connect', client => { diff --git a/data/1.16.201/biome_definitions.nbt b/data/1.16.201/biome_definitions.nbt deleted file mode 100644 index 68705c5d4f97c43f712894fcf5f25cb07bcf5892..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37626 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 TAOQ(TKmrnwfCMCPIRgI&V?knt literal 0 HcmV?d00001 diff --git a/data/1.16.220/protocol.json b/data/1.16.220/protocol.json index 115676d..ed967de 100644 --- a/data/1.16.220/protocol.json +++ b/data/1.16.220/protocol.json @@ -422,7 +422,7 @@ { "compareTo": "network_id", "fields": { - "355": [ + "/ShieldItemID": [ "encapsulated", { "lengthType": "varint", @@ -502,7 +502,7 @@ { "compareTo": "network_id", "fields": { - "355": [ + "/ShieldItemID": [ "encapsulated", { "lengthType": "varint", @@ -1933,157 +1933,6 @@ } ] ], - "ScoreEntries": [ - "container", - [ - { - "name": "type", - "type": [ - "mapper", - { - "type": "u8", - "mappings": { - "0": "change", - "1": "remove" - } - } - ] - }, - { - "name": "entries", - "type": [ - "array", - { - "countType": "varint", - "type": [ - "container", - [ - { - "name": "scoreboard_id", - "type": "zigzag64" - }, - { - "name": "objective_name", - "type": "string" - }, - { - "name": "score", - "type": "li32" - }, - { - "anon": true, - "type": [ - "switch", - { - "compareTo": "type", - "fields": { - "remove": [ - "container", - [ - { - "name": "entry_type", - "type": [ - "mapper", - { - "type": "i8", - "mappings": { - "1": "player", - "2": "entity", - "3": "fake_player" - } - } - ] - }, - { - "name": "entity_unique_id", - "type": [ - "switch", - { - "compareTo": "entry_type", - "fields": { - "player": "zigzag64", - "entity": "zigzag64" - }, - "default": "void" - } - ] - }, - { - "name": "custom_name", - "type": [ - "switch", - { - "compareTo": "entry_type", - "fields": { - "fake_player": "string" - }, - "default": "void" - } - ] - } - ] - ] - }, - "default": "void" - } - ] - } - ] - ] - } - ] - } - ] - ], - "ScoreboardIdentityEntries": [ - "container", - [ - { - "name": "type", - "type": [ - "mapper", - { - "type": "i8", - "mappings": { - "0": "TYPE_REGISTER_IDENTITY", - "1": "TYPE_CLEAR_IDENTITY" - } - } - ] - }, - { - "name": "entries", - "type": [ - "array", - { - "countType": "varint", - "type": [ - "container", - [ - { - "name": "scoreboard_id", - "type": "zigzag64" - }, - { - "name": "entity_unique_id", - "type": [ - "switch", - { - "compareTo": "type", - "fields": { - "TYPE_REGISTER_IDENTITY": "zigzag64" - }, - "default": "void" - } - ] - } - ] - ] - } - ] - } - ] - ], "Enchant": [ "container", [ @@ -6982,9 +6831,102 @@ "packet_set_score": [ "container", [ + { + "name": "action", + "type": [ + "mapper", + { + "type": "u8", + "mappings": { + "0": "change", + "1": "remove" + } + } + ] + }, { "name": "entries", - "type": "ScoreEntries" + "type": [ + "array", + { + "countType": "varint", + "type": [ + "container", + [ + { + "name": "scoreboard_id", + "type": "zigzag64" + }, + { + "name": "objective_name", + "type": "string" + }, + { + "name": "score", + "type": "li32" + }, + { + "anon": true, + "type": [ + "switch", + { + "compareTo": "../action", + "fields": { + "change": [ + "container", + [ + { + "name": "entry_type", + "type": [ + "mapper", + { + "type": "i8", + "mappings": { + "1": "player", + "2": "entity", + "3": "fake_player" + } + } + ] + }, + { + "name": "entity_unique_id", + "type": [ + "switch", + { + "compareTo": "entry_type", + "fields": { + "player": "zigzag64", + "entity": "zigzag64" + }, + "default": "void" + } + ] + }, + { + "name": "custom_name", + "type": [ + "switch", + { + "compareTo": "entry_type", + "fields": { + "fake_player": "string" + }, + "default": "void" + } + ] + } + ] + ] + }, + "default": "void" + } + ] + } + ] + ] + } + ] } ] ], @@ -7146,9 +7088,49 @@ "packet_set_scoreboard_identity": [ "container", [ + { + "name": "action", + "type": [ + "mapper", + { + "type": "i8", + "mappings": { + "0": "register_identity", + "1": "clear_identity" + } + } + ] + }, { "name": "entries", - "type": "ScoreboardIdentityEntries" + "type": [ + "array", + { + "countType": "varint", + "type": [ + "container", + [ + { + "name": "scoreboard_id", + "type": "zigzag64" + }, + { + "name": "entity_unique_id", + "type": [ + "switch", + { + "compareTo": "../action", + "fields": { + "register_identity": "zigzag64" + }, + "default": "void" + } + ] + } + ] + ] + } + ] } ] ], diff --git a/data/latest/proto.yml b/data/latest/proto.yml index 57bbd76..e9ad376 100644 --- a/data/latest/proto.yml +++ b/data/latest/proto.yml @@ -1854,10 +1854,32 @@ packet_set_display_objective: criteria_name: string sort_order: zigzag32 +# SetScore is sent by the server to send the contents of a scoreboard to the player. It may be used to either +# add, remove or edit entries on the scoreboard. packet_set_score: !id: 0x6c !bound: client - entries: ScoreEntries + # ActionType is the type of the action to execute upon the scoreboard with the entries that the packet + # has. If ActionType is ScoreboardActionModify, all entries will be added to the scoreboard if not yet + # present, or modified if already present. If set to ScoreboardActionRemove, all scoreboard entries set + # will be removed from the scoreboard. + action: u8 => + 0: change + 1: remove + entries: []varint + scoreboard_id: zigzag64 + objective_name: string + score: li32 + _: ../action ? + if change: + entry_type: i8 => + 1: player + 2: entity + 3: fake_player + entity_unique_id: entry_type ? + if player or entity: zigzag64 + custom_name: entry_type ? + if fake_player: string packet_lab_table: !id: 0x6d @@ -1942,10 +1964,27 @@ DeltaMoveFlags: [ "bitflags", } ] +# SetScoreboardIdentity is sent by the server to change the identity type of one of the entries on a +# scoreboard. This is used to change, for example, an entry pointing to a player, to a fake player when it +# leaves the server, and to change it back to a real player when it joins again. +# In non-vanilla situations, the packet is quite useless. packet_set_scoreboard_identity: !id: 0x70 !bound: client - entries: ScoreboardIdentityEntries + # ActionType is the type of the action to execute. The action is either ScoreboardIdentityActionRegister + # to associate an identity with the entry, or ScoreboardIdentityActionClear to remove associations with + # an entity. + action: i8 => + 0: register_identity + 1: clear_identity + # Entries is a list of all entries in the packet. Each of these entries points to one of the entries on + # a scoreboard. Depending on ActionType, their identity will either be registered or cleared. + entries: []varint + scoreboard_id: zigzag64 + entity_unique_id: ../action ? + if register_identity: zigzag64 + default: void + # SetLocalPlayerAsInitialised is sent by the client in response to a PlayStatus packet with the status set # to spawn. The packet marks the moment at which the client is fully initialised and can receive any packet diff --git a/data/latest/types.yaml b/data/latest/types.yaml index 8174c0f..9410467 100644 --- a/data/latest/types.yaml +++ b/data/latest/types.yaml @@ -112,7 +112,8 @@ ItemLegacy: metadata: varint block_runtime_id: zigzag32 extra: network_id ? - if 355: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithBlockingTick" }]' + # The Shield Item ID is sent in the StartGame packet. It is usually 355 in vanilla. + if /ShieldItemID: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithBlockingTick" }]' default: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithoutBlockingTick" }]' # An "ItemStack" here represents an Item instance. You can think about it like a pointer @@ -137,7 +138,9 @@ Item: default: zigzag32 block_runtime_id: zigzag32 extra: network_id ? - if 355: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithBlockingTick" }]' + # The Shield Item ID is sent in the StartGame packet. It is usually 355 in vanilla. + ## Really bad compiler hack to allow us to use a global variable + if /ShieldItemID: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithBlockingTick" }]' default: '["encapsulated", { "lengthType": "varint", "type": "ItemExtraDataWithoutBlockingTick" }]' vec3i: @@ -708,35 +711,6 @@ PlayerRecords: verified: type ? if add: bool[]$records_count -ScoreEntries: - type: u8 => - 0: change - 1: remove - entries: []varint - scoreboard_id: zigzag64 - objective_name: string - score: li32 - _: type? - if remove: - entry_type: i8 => - 1: player - 2: entity - 3: fake_player - entity_unique_id: entry_type? - if player or entity: zigzag64 - custom_name: entry_type? - if fake_player: string - -ScoreboardIdentityEntries: - type: i8 => - 0: TYPE_REGISTER_IDENTITY - 1: TYPE_CLEAR_IDENTITY - entries: []varint - scoreboard_id: zigzag64 - entity_unique_id: type ? - if TYPE_REGISTER_IDENTITY: zigzag64 - default: void - Enchant: id: u8 level: u8 diff --git a/package.json b/package.json index ba7d7d6..dc2463b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "minecraft-folder-path": "^1.1.0", "node-fetch": "^2.6.1", "prismarine-nbt": "^1.5.0", - "protodef": "extremeheat/node-protodef#patch-1", + "protodef": "github:extremeheat/node-protodef#vars", "smart-buffer": "^4.1.0", "uuid-1345": "^1.0.2" }, @@ -42,9 +42,9 @@ "bedrock-provider": "^1.0.0", "babel-eslint": "^10.1.0", "mocha": "^8.3.2", - "protodef-yaml": "^1.0.3", + "protodef-yaml": "^1.1.0", "standard": "^16.0.3", - "leveldb-zlib": "0.0.26", + "leveldb-zlib": "^0.0.26", "bedrock-protocol": "file:." }, "standard": { diff --git a/src/client.js b/src/client.js index 68c8886..0f82802 100644 --- a/src/client.js +++ b/src/client.js @@ -2,7 +2,6 @@ const { ClientStatus, Connection } = require('./connection') const { createDeserializer, createSerializer } = require('./transforms/serializer') const { RakClient } = require('./rak') const { serialize } = require('./datatypes/util') -const fs = require('fs') const debug = require('debug')('minecraft-protocol') const Options = require('./options') const auth = require('./client/auth') @@ -152,33 +151,16 @@ class Client extends Connection { this.status = ClientStatus.Disconnected } - tryRencode (name, params, actual) { - const packet = this.serializer.createPacketBuffer({ name, params }) - - console.assert(packet.equals(actual)) - if (!packet.equals(actual)) { - const ours = packet.toString('hex').match(/.{1,16}/g).join('\n') - const theirs = actual.toString('hex').match(/.{1,16}/g).join('\n') - - fs.writeFileSync('ours.txt', ours) - fs.writeFileSync('theirs.txt', theirs) - fs.writeFileSync('ours.json', serialize(params)) - fs.writeFileSync('theirs.json', serialize(this.deserializer.parsePacketBuffer(packet).data.params)) - - throw new Error(name + ' Packet comparison failed!') - } - } - 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, this.options.loggging ? serialize(pakData.params) : '') this.emit('packet', des) if (debugging) { // Packet verifying (decode + re-encode + match test) if (pakData.name) { - this.tryRencode(pakData.name, pakData.params, packet) + this.deserializer.verify(packet, this.serializer) } } @@ -193,6 +175,12 @@ class Client extends Connection { break case 'start_game': this.startGameData = pakData.params + this.startGameData.itemstates.forEach(state => { + if (state.name === 'minecraft:shield') { + this.serializer.proto.setVariable('ShieldItemID', state.runtime_id) + this.deserializer.proto.setVariable('ShieldItemID', state.runtime_id) + } + }) break case 'play_status': if (this.status === ClientStatus.Authenticating) { diff --git a/src/connection.js b/src/connection.js index 247dc0d..bf08245 100644 --- a/src/connection.js +++ b/src/connection.js @@ -40,8 +40,25 @@ class Connection extends EventEmitter { this.encrypt = cipher.createEncryptor(this, iv) } + updateItemPalette (palette) { + // In the future, we can send down the whole item palette if we need + // but since it's only one item, we can just make a single variable. + let shieldItemID + for (const state of palette) { + if (state.name === 'minecraft:shield') { + shieldItemID = state.runtime_id + break + } + } + if (shieldItemID) { + this.serializer.proto.setVariable('ShieldItemID', shieldItemID) + this.deserializer.proto.setVariable('ShieldItemID', shieldItemID) + } + } + write (name, params) { this.outLog('sending', name, params) + if (name === 'start_game') this.updateItemPalette(params.itemstates) const batch = new Framer() const packet = this.serializer.createPacketBuffer({ name, params }) batch.addEncodedPacket(packet) @@ -55,6 +72,7 @@ class Connection extends EventEmitter { queue (name, params) { this.outLog('Q <- ', name, params) + if (name === 'start_game') this.updateItemPalette(params.itemstates) const packet = this.serializer.createPacketBuffer({ name, params }) if (name === 'level_chunk') { // Skip queue, send ASAP @@ -113,7 +131,11 @@ class Connection extends EventEmitter { sendMCPE (buffer, immediate) { if (this.connection.connected === false || this.status === ClientStatus.Disconnected) return - this.connection.sendReliable(buffer, immediate) + try { + this.connection.sendReliable(buffer, immediate) + } catch (e) { + debug('while sending to', this.connection, e) + } } // These are callbacks called from encryption.js diff --git a/src/createClient.js b/src/createClient.js index 85856f9..77b2fc9 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -37,10 +37,10 @@ function connect (client) { response_status: 'completed', resourcepackids: [] }) + client.queue('request_chunk_radius', { chunk_radius: client.renderDistance || 10 }) }) client.queue('client_cache_status', { enabled: false }) - client.queue('request_chunk_radius', { chunk_radius: client.renderDistance || 1 }) client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n }) }) diff --git a/src/datatypes/minecraft.js b/src/datatypes/minecraft.js index ae5d324..a52d9c8 100644 --- a/src/datatypes/minecraft.js +++ b/src/datatypes/minecraft.js @@ -38,14 +38,18 @@ function sizeOfNbt (value) { // Little Endian function readNbtLE (buffer, offset) { - return protoLE.read(buffer, offset, 'nbt') + const r = protoLE.read(buffer, offset, 'nbt') + if (r.value.type === 'end') return { value: r.value, size: 0 } + return r } function writeNbtLE (value, buffer, offset) { + if (value.type === 'end') return offset return protoLE.write(value, buffer, offset, 'nbt') } function sizeOfNbtLE (value) { + if (value.type === 'end') return 0 return protoLE.sizeOf(value, 'nbt') } diff --git a/src/handshake/login.js b/src/handshake/login.js index c55665c..e4a9be8 100644 --- a/src/handshake/login.js +++ b/src/handshake/login.js @@ -6,7 +6,10 @@ const { PUBLIC_KEY } = require('./constants') const algorithm = 'ES384' module.exports = (client, server, options) => { - const skinGeom = fs.readFileSync(DataProvider(options.protocolVersion).getPath('skin_geom.txt'), 'utf-8') + const dp = DataProvider(options.protocolVersion) + const skinTex = fs.readFileSync(dp.getPath('steveSkin.bin')).toString('base64') + const skinGeom = fs.readFileSync(dp.getPath('steveGeometry.json')).toString('base64') + const skinData = JSON.parse(fs.readFileSync(dp.getPath('steve.json'), 'utf-8')) client.createClientChain = (mojangKey, offline) => { const privateKey = client.ecdhKeyPair.privateKey @@ -22,12 +25,12 @@ module.exports = (client, server, options) => { certificateAuthority: true, identityPublicKey: client.clientX509 } - token = JWT.sign(payload, privateKey, { algorithm, notBefore: 0, issuer: 'self', expiresIn: 60 * 60, header: { x5u: client.clientX509 } }) + token = JWT.sign(payload, privateKey, { algorithm, notBefore: 0, issuer: 'self', expiresIn: 60 * 60, header: { x5u: client.clientX509, typ: undefined } }) } else { token = JWT.sign({ identityPublicKey: mojangKey || PUBLIC_KEY, certificateAuthority: true - }, privateKey, { algorithm, header: { x5u: client.clientX509 } }) + }, privateKey, { algorithm, header: { x5u: client.clientX509, typ: undefined } }) } client.clientIdentityChain = token @@ -36,49 +39,38 @@ module.exports = (client, server, options) => { client.createClientUserChain = (privateKey) => { let payload = { - AnimatedImageData: [], - ArmSize: 'wide', - CapeData: '', - CapeId: '', - CapeImageHeight: 0, - CapeImageWidth: 0, - CapeOnClassicSkin: false, + ...skinData, + ClientRandomId: Date.now(), CurrentInputMode: 1, DefaultInputMode: 1, DeviceId: nextUUID(), - DeviceModel: '', + DeviceModel: 'PrismarineJS', DeviceOS: client.session?.deviceOS || 7, GameVersion: options.version || '1.16.201', GuiScale: -1, LanguageCode: 'en_GB', // TODO locale - PersonaPieces: [], - PersonaSkin: true, - PieceTintColors: [], + PlatformOfflineId: '', PlatformOnlineId: '', // chat // PlayFabID is the PlayFab ID produced for the skin. PlayFab is the company that hosts the Marketplace, // skins and other related features from the game. This ID is the ID of the skin used to store the skin // inside of PlayFab. - PlayFabId: '5eb65f73-af11-448e-82aa-1b7b165316ad.persona-e199672a8c1a87e0-0', // 1.16.210 - PremiumSkin: false, + PlayFabId: nextUUID().replace(/-/g, '').slice(0, 16), // 1.16.210 + SelfSignedId: nextUUID(), ServerAddress: `${options.host}:${options.port}`, - SkinAnimationData: '', - SkinColor: '#ffffcd96', - SkinData: 'AAAAAA==', + SkinData: skinTex, SkinGeometryData: skinGeom, - SkinId: '5eb65f73-af11-448e-82aa-1b7b165316ad.persona-e199672a8c1a87e0-0', - SkinImageHeight: 1, - SkinImageWidth: 1, - SkinResourcePatch: '', + ThirdPartyName: client.profile.name, ThirdPartyNameOnly: false, UIProfile: 0 } const customPayload = options.skinData || {} payload = { ...payload, ...customPayload } + payload.ServerAddress = `${options.host}:${options.port}` - client.clientUserChain = JWT.sign(payload, privateKey, { algorithm, header: { x5u: client.clientX509 } }) + client.clientUserChain = JWT.sign(payload, privateKey, { algorithm, header: { x5u: client.clientX509, typ: undefined }, noTimestamp: true /* pocketmine.. */ }) } } diff --git a/src/relay.js b/src/relay.js index 9e7992a..26a44e2 100644 --- a/src/relay.js +++ b/src/relay.js @@ -30,6 +30,7 @@ class RelayPlayer extends Player { this.outLog = this.downOutLog this.inLog = this.downInLog + this.chunkSendCache = [] } // Called when we get a packet from backend server (Backend -> PROXY -> Client) @@ -45,16 +46,23 @@ class RelayPlayer extends Player { 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 }) - if (!rpacket.equals(packet)) { - console.warn('New', rpacket.toString('hex')) - console.warn('Old', packet.toString('hex')) - console.log('Failed to re-encode', name, params) - process.exit(1) - } + this.server.deserializer.verify(des, this.server.serializer) } this.emit('clientbound', des.data) + + // If we're sending a chunk, but player isn't yet initialized, wait until it is. + // This is wrong and should not be an issue to send chunks before the client + // is in the world; need to investigate further, but for now it's fine. + if (name === 'level_chunk' && this.status !== 3) { + this.chunkSendCache.push([name, params]) + return + } else if (this.status === 3 && this.chunkSendCache.length) { + for (const chunk of this.chunkSendCache) { + this.queue(...chunk) + } + this.chunkSendCache = [] + } this.queue(name, params) } @@ -82,33 +90,36 @@ 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 - if (!this.upstream) { // Upstream is still connecting/handshaking + // The downstream client conn is established & we got a packet to send to upstream server + if (this.startRelaying) { + // Upstream is still connecting/handshaking + if (!this.upstream) { 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 return } - this.flushUpQueue() // Send queued packets + + // Send queued packets + this.flushUpQueue() this.downInLog('recv', packet) + // TODO: If we fail to parse a packet, proxy it raw and log an error const des = this.server.deserializer.parsePacketBuffer(packet) if (debugging) { // some packet encode/decode testing stuff - const rpacket = this.server.serializer.createPacketBuffer(des.data) - if (!rpacket.equals(packet)) { - console.warn('New', rpacket.toString('hex')) - console.warn('Old', packet.toString('hex')) - console.log('Failed to re-encode', des.data) - process.exit(1) - } + this.server.deserializer.verify(des, this.server.serializer) } this.emit('serverbound', des.data) switch (des.data.name) { case 'client_cache_status': + // Force the chunk cache off. this.upstream.queue('client_cache_status', { enabled: false }) break + case 'set_local_player_as_initialized': + this.status = 3 + break default: // Emit the packet as-is back to the upstream server this.downInLog('Relaying', des.data) @@ -149,6 +160,10 @@ class Relay extends Server { client.outLog = ds.upOutLog client.inLog = ds.upInLog client.once('join', () => { // Intercept once handshaking done + // Tell the server to disable chunk cache for this connection as a client. + // Wait a bit for the server to ack and process, the continue with proxying + // otherwise the player can get stuck in an empty world. + client.write('client_cache_status', { enabled: false }) ds.upstream = client ds.flushUpQueue() this.conLog('Connected to upstream server') diff --git a/src/transforms/serializer.js b/src/transforms/serializer.js index f78e4c8..4a6102f 100644 --- a/src/transforms/serializer.js +++ b/src/transforms/serializer.js @@ -11,6 +11,18 @@ class Parser extends FullPacketParser { throw e } } + + verify (deserialized, serializer) { + const { name, params } = deserialized.data + const oldBuffer = deserialized.fullBuffer + const newBuffer = serializer.createPacketBuffer({ name, params }) + if (!newBuffer.equals(oldBuffer)) { + console.warn('New', newBuffer.toString('hex')) + console.warn('Old', oldBuffer.toString('hex')) + console.log('Failed to re-encode', name, params) + process.exit(1) + } + } } // Compiles the ProtoDef schema at runtime @@ -31,11 +43,8 @@ function getProtocol (version) { compiler.addTypes(require(join(__dirname, '../datatypes/compiler-minecraft'))) compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) - const compile = (compiler, file) => { - global.native = compiler.native // eslint-disable-line - const { PartialReadError } = require('protodef/src/utils') // eslint-disable-line - return require(file)() // eslint-disable-line - } + global.PartialReadError = require('protodef/src/utils').PartialReadError + const compile = (compiler, file) => require(file)(compiler.native) return new CompiledProtodef( compile(compiler.sizeOfCompiler, join(__dirname, `../../data/${version}/size.js`)), diff --git a/tools/compileProtocol.js b/tools/compileProtocol.js index 4163094..bed7b2c 100644 --- a/tools/compileProtocol.js +++ b/tools/compileProtocol.js @@ -55,9 +55,9 @@ function createProtocol () { compiler.addTypes(require('prismarine-nbt/compiler-zigzag')) compiler.addTypesToCompile(protocol) - fs.writeFileSync('./read.js', 'module.exports = ' + compiler.readCompiler.generate()) - fs.writeFileSync('./write.js', 'module.exports = ' + compiler.writeCompiler.generate()) - fs.writeFileSync('./size.js', 'module.exports = ' + compiler.sizeOfCompiler.generate()) + fs.writeFileSync('./read.js', 'module.exports = ' + compiler.readCompiler.generate().replace('() =>', 'native =>')) + fs.writeFileSync('./write.js', 'module.exports = ' + compiler.writeCompiler.generate().replace('() =>', 'native =>')) + fs.writeFileSync('./size.js', 'module.exports = ' + compiler.sizeOfCompiler.generate().replace('() =>', 'native =>')) const compiledProto = compiler.compileProtoDefSync() return compiledProto