Compare commits

..

3 commits

Author SHA1 Message Date
extremeheat
2ea5c27893
Update index.d.ts 2025-01-17 05:37:26 -05:00
extremeheat
b4f4202faa
Update server.js 2025-01-17 05:36:27 -05:00
extremeheat
6b5aa08dba
Fix Server maxPlayers option 2025-01-17 05:35:26 -05:00
18 changed files with 45 additions and 112 deletions

View file

@ -17,7 +17,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [22.x]
node-version: [18.x]
runs-on: ${{ matrix.os }}
timeout-minutes: 14
steps:

View file

@ -14,7 +14,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@master
with:
node-version: 22.0.0
node-version: 18.0.0
- name: Install Github Actions helper
run: npm i gh-helpers
# The env vars contain the relevant trigger information, so we don't need to pass it

View file

@ -1,35 +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)
## 3.42.2
* Fix missing type serialization error

View file

@ -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)

View file

@ -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!`
})
})

2
index.d.ts vendored
View file

@ -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

View file

@ -1,6 +1,6 @@
{
"name": "bedrock-protocol",
"version": "3.49.0",
"version": "3.42.2",
"description": "Minecraft Bedrock Edition protocol library",
"main": "index.js",
"types": "index.d.ts",
@ -41,7 +41,7 @@
"bedrock-provider": "^2.0.0",
"leveldb-zlib": "^1.0.1",
"minecraft-bedrock-server": "^1.4.2",
"mocha": "^11.0.1",
"mocha": "^10.0.0",
"protodef-yaml": "^1.1.0",
"standard": "^17.0.0-2"
},

View file

@ -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)

View file

@ -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

View file

@ -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 }

View file

@ -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 = {

View file

@ -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--
}

View file

@ -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')

View file

@ -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) => {

View file

@ -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

View file

@ -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') })
}

View file

@ -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)
})
}

View file

@ -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)
}