diff --git a/HISTORY.md b/HISTORY.md index 799d0a9..3bd4a54 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,32 +1,3 @@ -## 3.49.0 -* [1.21.111 (#649)](https://github.com/PrismarineJS/bedrock-protocol/commit/b48518a6e79e72101fe7136433cbd6277339fc5c) (thanks @Slauh) -* [Skin Data Changes (#647)](https://github.com/PrismarineJS/bedrock-protocol/commit/407756b93880cdda4fdbff194fc4163ceedf4e82) (thanks @thejfkvis) - -## 3.48.1 -* [Update login client skinData (#635)](https://github.com/PrismarineJS/bedrock-protocol/commit/6b1474d2c6f93b47dee9d4816de59579f82ed5a9) (thanks @TSL534) - -## 3.48.0 -* [1.21.100 (#632)](https://github.com/PrismarineJS/bedrock-protocol/commit/06fb3de3a0023d03201dbcee7e9178c269462766) (thanks @extremeheat) - -## 3.47.0 -* [1.21.93 support (#623)](https://github.com/PrismarineJS/bedrock-protocol/commit/14daa2d95aac90ffcc7b42d625e270020ec2f162) (thanks @CreeperG16) - -## 3.46.0 -* [1.21.90 support (#617)](https://github.com/PrismarineJS/bedrock-protocol/commit/c66cdd3d62d2fa9c581693d8c70d7b41f355b63e) (thanks @CreeperG16) - -## 3.45.0 -* [1.21.80 (#602)](https://github.com/PrismarineJS/bedrock-protocol/commit/e71fd513ddbd432983f221980080b61e11576965) (thanks @extremeheat) - -## 3.44.0 -* [1.21.70 (#594)](https://github.com/PrismarineJS/bedrock-protocol/commit/065f41db8cfc8cbd8106bd9e376c899ec25f3f77) (thanks @CreeperG16) - -## 3.43.1 -* [Fix server not correctly removing clients (#588)](https://github.com/PrismarineJS/bedrock-protocol/commit/47f342ca958ba87a7719783bd5c855cebdd4aa65) (thanks @EntifiedOptics) - -## 3.43.0 -* [1.21.60 support (#570)](https://github.com/PrismarineJS/bedrock-protocol/commit/eeb5e47e35f31cc571a9a8a491f5a89b27e637f1) (thanks @CreeperG16) -* [Fix version feature handling (#572)](https://github.com/PrismarineJS/bedrock-protocol/commit/0ed8e32be85f05926cd97d5f0317ed004ae5eefa) (thanks @ItsMax123) - ## 3.42.3 * [Fix Server `maxPlayers` option (#565)](https://github.com/PrismarineJS/bedrock-protocol/commit/38dc5a256105a44786d5455570d5a130e64ef561) (thanks @extremeheat) diff --git a/README.md b/README.md index 5e3add2..25971a1 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Minecraft Bedrock Edition (aka MCPE) protocol library, supporting authentication ## Features - - Supports Minecraft Bedrock version 1.16.201, 1.16.210, 1.16.220, 1.17.0, 1.17.10, 1.17.30, 1.17.40, 1.18.0, 1.18.11, 1.18.30, 1.19.1, 1.19.10, 1.19.20, 1.19.21, 1.19.30, 1.19.40, 1.19.41, 1.19.50, 1.19.60, 1.19.62, 1.19.63, 1.19.70, 1.19.80, 1.20.0, 1.20.10, 1.20.30, 1.20.40, 1.20.50, 1.20.61, 1.20.71, 1.20.80, 1.21.0, 1.21.2, 1.21.21, 1.21.30, 1.21.42, 1.21.50, 1.21.60, 1.21.70, 1.21.80, 1.21.90, 1.21.93, 1.21.100, 1.21.111 + - Supports Minecraft Bedrock version 1.16.201, 1.16.210, 1.16.220, 1.17.0, 1.17.10, 1.17.30, 1.17.40, 1.18.0, 1.18.11, 1.18.30, 1.19.1, 1.19.10, 1.19.20, 1.19.21, 1.19.30, 1.19.40, 1.19.41, 1.19.50, 1.19.60, 1.19.62, 1.19.63, 1.19.70, 1.19.80, 1.20.0, 1.20.10, 1.20.30, 1.20.40, 1.20.50, 1.20.61, 1.20.71, 1.20.80, 1.21.0, 1.21.2, 1.21.21, 1.21.30, 1.21.42, 1.21.50 - Parse and serialize packets as JavaScript objects - Automatically respond to keep-alive packets - [Proxy and mitm connections](docs/API.md#proxy-docs) diff --git a/docs/API.md b/docs/API.md index d69c385..b7b5722 100644 --- a/docs/API.md +++ b/docs/API.md @@ -142,7 +142,7 @@ client.on('text', (packet) => { // names and as explained in the "Protocol doc" section below, fields are all case sensitive! client.on('add_player', (packet) => { client.queue('text', { - type: 'chat', needs_translation: false, source_name: client.username, xuid: '', platform_chat_id: '', filtered_message: '', + type: 'chat', needs_translation: false, source_name: client.username, xuid: '', platform_chat_id: '', message: `Hey, ${packet.username} just joined!` }) }) diff --git a/index.d.ts b/index.d.ts index 3f4a571..cda2551 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,7 +3,7 @@ import { Realm } from 'prismarine-realms' import { ServerDeviceCodeResponse } from 'prismarine-auth' declare module 'bedrock-protocol' { - type Version = '1.21.93' | '1.21.90' | '1.21.80' | '1.21.70' | '1.21.60' | '1.21.50' | '1.21.42' | '1.21.30' | '1.21.2' | '1.21.0' | '1.20.80' | '1.20.71' | '1.20.61' | '1.20.50' | '1.20.40' | '1.20.30' | '1.20.10' | '1.20.0' | '1.19.80' | '1.19.70' | '1.19.63' | '1.19.62' | '1.19.60' | '1.19.51' | '1.19.50' | '1.19.41' | '1.19.40' | '1.19.31' | '1.19.30' | '1.19.22' | '1.19.21' | '1.19.20' | '1.19.11' | '1.19.10' | '1.19.2' | '1.19.1' | '1.18.31' | '1.18.30' | '1.18.12' | '1.18.11' | '1.18.10' | '1.18.2' | '1.18.1' | '1.18.0' | '1.17.41' | '1.17.40' | '1.17.34' | '1.17.30' | '1.17.11' | '1.17.10' | '1.17.0' | '1.16.220' | '1.16.210' | '1.16.201' + type Version = '1.21.42' | '1.21.30' | '1.21.2' | '1.21.0' | '1.20.80' | '1.20.71' | '1.20.61' | '1.20.50' | '1.20.40' | '1.20.30' | '1.20.10' | '1.20.0' | '1.19.80' | '1.19.70' | '1.19.63' | '1.19.62' | '1.19.60' | '1.19.51' | '1.19.50' | '1.19.41' | '1.19.40' | '1.19.31' | '1.19.30' | '1.19.22' | '1.19.21' | '1.19.20' | '1.19.11' | '1.19.10' | '1.19.2' | '1.19.1' | '1.18.31' | '1.18.30' | '1.18.12' | '1.18.11' | '1.18.10' | '1.18.2' | '1.18.1' | '1.18.0' | '1.17.41' | '1.17.40' | '1.17.34' | '1.17.30' | '1.17.11' | '1.17.10' | '1.17.0' | '1.16.220' | '1.16.210' | '1.16.201' export interface Options { // The string version to start the client or server as diff --git a/package.json b/package.json index 6301be0..424b2d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bedrock-protocol", - "version": "3.49.0", + "version": "3.42.3", "description": "Minecraft Bedrock Edition protocol library", "main": "index.js", "types": "index.d.ts", diff --git a/src/client.js b/src/client.js index e3af1d0..2d798e6 100644 --- a/src/client.js +++ b/src/client.js @@ -61,9 +61,7 @@ class Client extends Connection { try { const mcData = require('minecraft-data')('bedrock_' + this.options.version) this.features = { - compressorInHeader: mcData.supportFeature('compressorInPacketHeader'), - itemRegistryPacket: mcData.supportFeature('itemRegistryPacket'), - newLoginIdentityFields: mcData.supportFeature('newLoginIdentityFields') + compressorInHeader: mcData.supportFeature('compressorInPacketHeader') } } catch (e) { throw new Error(`Unsupported version: '${this.options.version}', no data available`) @@ -147,18 +145,9 @@ class Client extends Connection { ...this.accessToken // Mojang + Xbox JWT from auth ] - let encodedChain - if (this.features.newLoginIdentityFields) { // 1.21.90+ - encodedChain = JSON.stringify({ - Certificate: JSON.stringify({ chain }), - // 0 = normal, 1 = ss, 2 = offline - AuthenticationType: this.options.offline ? 2 : 0, - Token: '' - }) - } else { - encodedChain = JSON.stringify({ chain }) - } - debug('Auth chain', encodedChain) + const encodedChain = JSON.stringify({ chain }) + + debug('Auth chain', chain) this.write('login', { protocol_version: this.options.protocolVersion, @@ -252,9 +241,7 @@ class Client extends Connection { break case 'start_game': this.startGameData = pakData.params - // fallsthrough - case 'item_registry': // 1.21.60+ send itemstates in item_registry packet - pakData.params.itemstates?.forEach(state => { + 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) diff --git a/src/connection.js b/src/connection.js index f1e9051..eb01f7f 100644 --- a/src/connection.js +++ b/src/connection.js @@ -28,22 +28,18 @@ class Connection extends EventEmitter { } versionLessThan (version) { - if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version) return this.options.protocolVersion < (typeof version === 'string' ? Versions[version] : version) } versionGreaterThan (version) { - if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version) return this.options.protocolVersion > (typeof version === 'string' ? Versions[version] : version) } versionGreaterThanOrEqualTo (version) { - if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version) return this.options.protocolVersion >= (typeof version === 'string' ? Versions[version] : version) } versionLessThanOrEqualTo (version) { - if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version) return this.options.protocolVersion <= (typeof version === 'string' ? Versions[version] : version) } @@ -70,17 +66,9 @@ class Connection extends EventEmitter { } } - _processOutbound (name, params) { - if (name === 'item_registry') { - this.updateItemPalette(params.itemstates) - } else if (name === 'start_game' && params.itemstates) { - this.updateItemPalette(params.itemstates) - } - } - write (name, params) { this.outLog?.(name, params) - this._processOutbound(name, params) + if (name === 'start_game') this.updateItemPalette(params.itemstates) const batch = new Framer(this) const packet = this.serializer.createPacketBuffer({ name, params }) batch.addEncodedPacket(packet) @@ -94,7 +82,7 @@ class Connection extends EventEmitter { queue (name, params) { this.outLog?.('Q <- ', name, params) - this._processOutbound(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 diff --git a/src/handshake/login.js b/src/handshake/login.js index 8a2830d..e25b15f 100644 --- a/src/handshake/login.js +++ b/src/handshake/login.js @@ -36,6 +36,7 @@ module.exports = (client, server, options) => { client.createClientUserChain = (privateKey) => { let payload = { ...skinData, + SkinGeometryDataEngineVersion: client.versionGreaterThanOrEqualTo('1.17.30') ? '' : undefined, ClientRandomId: Date.now(), CurrentInputMode: 1, @@ -46,20 +47,19 @@ module.exports = (client, server, options) => { GameVersion: options.version || '1.16.201', GuiScale: -1, LanguageCode: 'en_GB', // TODO locale - GraphicsMode: 1, // 1:simple, 2:fancy, 3:advanced, 4:ray_traced 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.The playfab ID is always lowercased. - PlayFabId: nextUUID().replace(/-/g, '').slice(0, 16).toLowerCase(), // 1.16.210 + // inside of PlayFab. + PlayFabId: nextUUID().replace(/-/g, '').slice(0, 16), // 1.16.210 SelfSignedId: nextUUID(), ServerAddress: `${options.host}:${options.port}`, - ThirdPartyName: client.profile.name, // Gamertag - ThirdPartyNameOnly: client.versionGreaterThanOrEqualTo('1.21.90') ? undefined : false, + ThirdPartyName: client.profile.name, + ThirdPartyNameOnly: false, UIProfile: 0, IsEditorMode: false, @@ -67,9 +67,9 @@ module.exports = (client, server, options) => { OverrideSkin: client.versionGreaterThanOrEqualTo('1.19.62') ? false : undefined, CompatibleWithClientSideChunkGen: client.versionGreaterThanOrEqualTo('1.19.80') ? false : undefined, - MaxViewDistance: client.versionGreaterThanOrEqualTo('1.21.42') ? 0 : undefined, - MemoryTier: client.versionGreaterThanOrEqualTo('1.21.42') ? 0 : undefined, - PlatformType: client.versionGreaterThanOrEqualTo('1.21.42') ? 0 : undefined + MaxViewDistance: client.versionGreaterThanOrEqualTo('1.21.40') ? 0 : undefined, + MemoryTier: client.versionGreaterThanOrEqualTo('1.21.40') ? 0 : undefined, + PlatformType: client.versionGreaterThanOrEqualTo('1.21.40') ? 0 : undefined } const customPayload = options.skinData || {} payload = { ...payload, ...customPayload } diff --git a/src/options.js b/src/options.js index 6e27dbe..dd42cdf 100644 --- a/src/options.js +++ b/src/options.js @@ -3,12 +3,12 @@ const mcData = require('minecraft-data') // Minimum supported version (< will be kicked) const MIN_VERSION = '1.16.201' // Currently supported verson. Note, clients with newer versions can still connect as long as data is in minecraft-data -const CURRENT_VERSION = '1.21.111' +const CURRENT_VERSION = '1.21.50' const Versions = Object.fromEntries(mcData.versions.bedrock.filter(e => e.releaseType === 'release').map(e => [e.minecraftVersion, e.version])) // Skip some low priority versions (middle major) on Github Actions to allow faster CI -const skippedVersionsOnGithubCI = ['1.16.210', '1.17.10', '1.17.30', '1.18.11', '1.19.10', '1.19.20', '1.19.30', '1.19.40', '1.19.50', '1.19.60', '1.19.63', '1.19.70', '1.20.10', '1.20.15', '1.20.30', '1.20.40', '1.20.50', '1.20.61', '1.20.71', '1.21.2', '1.21.20', '1.21.30', '1.21.42', '1.21.50', '1.21.60', '1.21.70', '1.21.80', '1.21.90'] +const skippedVersionsOnGithubCI = ['1.16.210', '1.17.10', '1.17.30', '1.18.11', '1.19.10', '1.19.20', '1.19.30', '1.19.40', '1.19.50', '1.19.60', '1.19.63', '1.19.70', '1.20.10', '1.20.15', '1.20.30', '1.20.40', '1.20.50', '1.20.61', '1.20.71'] const testedVersions = process.env.CI ? Object.keys(Versions).filter(v => !skippedVersionsOnGithubCI.includes(v)) : Object.keys(Versions) const defaultOptions = { diff --git a/src/server.js b/src/server.js index 0b43fd2..4dc9852 100644 --- a/src/server.js +++ b/src/server.js @@ -33,8 +33,7 @@ class Server extends EventEmitter { try { const mcData = require('minecraft-data')('bedrock_' + version) this.features = { - compressorInHeader: mcData.supportFeature('compressorInPacketHeader'), - newLoginIdentityFields: mcData.supportFeature('newLoginIdentityFields') + compressorInHeader: mcData.supportFeature('compressorInPacketHeader') } } catch (e) { throw new Error(`Unsupported version: '${version}', no data available`) @@ -90,10 +89,12 @@ class Server extends EventEmitter { this.emit('connect', player) } - onCloseConnection = (conn, reason) => { - this.conLog('Connection closed: ', conn.address, reason) - this.clients[conn.address]?.close() - delete this.clients[conn.address] + onCloseConnection = (inetAddr, reason) => { + this.conLog('Connection closed: ', inetAddr?.address, reason) + + delete this.clients[inetAddr]?.connection // Prevent close loop + this.clients[inetAddr?.address ?? inetAddr]?.close() + delete this.clients[inetAddr] this.clientCount-- } diff --git a/src/serverPlayer.js b/src/serverPlayer.js index 2e8ea77..b7ef02d 100644 --- a/src/serverPlayer.js +++ b/src/serverPlayer.js @@ -78,18 +78,11 @@ class Player extends Connection { // Parse login data const tokens = body.params.tokens + const authChain = JSON.parse(tokens.identity) + const skinChain = tokens.client + try { - const skinChain = tokens.client - const authChain = JSON.parse(tokens.identity) - let chain - if (authChain.Certificate) { // 1.21.90+ - chain = JSON.parse(authChain.Certificate).chain - } else if (authChain.chain) { - chain = authChain.chain - } else { - throw new Error('Invalid login packet: missing chain or Certificate') - } - var { key, userData, skinData } = this.decodeLoginJWT(chain, skinChain) // eslint-disable-line + var { key, userData, skinData } = this.decodeLoginJWT(authChain.chain, skinChain) // eslint-disable-line } catch (e) { debug(this.address, e) this.disconnect('Server authentication error') diff --git a/test/internal.js b/test/internal.js index ffb403e..1003a87 100644 --- a/test/internal.js +++ b/test/internal.js @@ -63,11 +63,7 @@ async function startTest (version = CURRENT_VERSION, ok) { // 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')) - if (client.versionLessThan('1.21.60')) { - client.queue('item_component', { entries: [] }) - } else { - client.queue('item_registry', get('packets/item_registry.json')) - } + 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 }) @@ -101,11 +97,11 @@ async function startTest (version = CURRENT_VERSION, ok) { loop = setInterval(() => { client.write('network_chunk_publisher_update', { coordinates: { x: 646, y: 130, z: 77 }, radius: 64 }) - }, 6500) + }, 9500) setTimeout(() => { client.write('play_status', { status: 'player_spawn' }) - }, 3000) + }, 6000) // Respond to tick synchronization packets client.on('tick_sync', (packet) => { diff --git a/test/internal.test.js b/test/internal.test.js index 271a7c5..1b6250c 100644 --- a/test/internal.test.js +++ b/test/internal.test.js @@ -3,7 +3,6 @@ const { timedTest } = require('./internal') const { testedVersions } = require('../src/options') const { sleep } = require('../src/datatypes/util') -require('events').captureRejections = true describe('internal client/server test', function () { const vcount = testedVersions.length diff --git a/test/proxy.js b/test/proxy.js index af443d7..345e89f 100644 --- a/test/proxy.js +++ b/test/proxy.js @@ -22,12 +22,12 @@ function proxyTest (version, raknetBackend = 'raknet-native', timeout = 1000 * 4 console.debug('Client has authenticated') setTimeout(() => { client.disconnect('Hello world !') - }, 500) // allow some time for client to connect + }, 1000) // allow some time for client to connect }) }) console.debug('Server started', server.options.version) - await new Promise(resolve => setTimeout(resolve, 500)) + await new Promise(resolve => setTimeout(resolve, 1000)) const relay = new Relay({ version, @@ -46,7 +46,7 @@ function proxyTest (version, raknetBackend = 'raknet-native', timeout = 1000 * 4 await relay.listen() console.debug('Proxy started', server.options.version) - await new Promise(resolve => setTimeout(resolve, 500)) + await new Promise(resolve => setTimeout(resolve, 1000)) const client = createClient({ host: '127.0.0.1', port: CLIENT_PORT, version, username: 'Boat', offline: true, raknetBackend, skipPing: true }) console.debug('Client started') @@ -58,7 +58,7 @@ function proxyTest (version, raknetBackend = 'raknet-native', timeout = 1000 * 4 server.close() relay.close() console.log('✔ OK') - sleep(200).then(res) + sleep(500).then(res) }) }, timeout, () => { throw Error('timed out') }) } diff --git a/test/proxy.test.js b/test/proxy.test.js index 27761ae..70ea5f1 100644 --- a/test/proxy.test.js +++ b/test/proxy.test.js @@ -11,7 +11,7 @@ describe('proxies client/server', function () { it('proxies ' + version, async () => { console.debug(version) await proxyTest(version) - await sleep(100) + await sleep(1000) console.debug('Done', version) }) } diff --git a/tools/compileProtocol.js b/tools/compileProtocol.js index a7c3aa0..dcf6545 100644 --- a/tools/compileProtocol.js +++ b/tools/compileProtocol.js @@ -15,6 +15,7 @@ function createProtocol (version) { const compiler = new ProtoDefCompiler() const protocol = mcData('bedrock_' + version).protocol.types compiler.addTypes(require('../src/datatypes/compiler-minecraft')) + compiler.addTypes(require('prismarine-nbt/zigzag').compiler) compiler.addTypesToCompile(protocol) fs.writeFileSync('./read.js', 'module.exports = ' + compiler.readCompiler.generate().replace('() =>', 'native =>')) @@ -38,7 +39,7 @@ require('minecraft-data/bin/generate_data') // If no argument, build everything if (!process.argv[2]) { - convert('bedrock', 'latest') + convert('latest') for (const version of versions) { main(version) }