Compare commits
5 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1bbef36a7 |
||
|
|
8460ac3042 |
||
|
|
47f7fb9d9d |
||
|
|
4beeaff099 |
||
|
|
7b8d1e8a53 |
27 changed files with 353 additions and 184 deletions
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
node-version: [22.x]
|
node-version: [18.x]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 14
|
timeout-minutes: 14
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -26,11 +26,5 @@ jobs:
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
# Old versions of bedrock use old libssl that Ubuntu no longer ships with; need manual install
|
|
||||||
- name: (Linux) Install libssl 1.1
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb
|
|
||||||
sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb
|
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm test
|
- run: npm test
|
||||||
|
|
|
||||||
2
.github/workflows/update-helper.yml
vendored
2
.github/workflows/update-helper.yml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@master
|
uses: actions/setup-node@master
|
||||||
with:
|
with:
|
||||||
node-version: 22.0.0
|
node-version: 18.0.0
|
||||||
- name: Install Github Actions helper
|
- name: Install Github Actions helper
|
||||||
run: npm i gh-helpers
|
run: npm i gh-helpers
|
||||||
# The env vars contain the relevant trigger information, so we don't need to pass it
|
# The env vars contain the relevant trigger information, so we don't need to pass it
|
||||||
|
|
|
||||||
50
HISTORY.md
50
HISTORY.md
|
|
@ -1,53 +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
|
|
||||||
|
|
||||||
## 3.42.1
|
|
||||||
* [Add 1.21.40 login fields (#553)](https://github.com/PrismarineJS/bedrock-protocol/commit/24d3200181c060162b04fb233fef6e0d6d1a93aa) (thanks @extremeheat)
|
|
||||||
* [Remove protodef varint types (#552)](https://github.com/PrismarineJS/bedrock-protocol/commit/347e303ce422bdb6f6dfd4cba57d7d3937214707) (thanks @extremeheat)
|
|
||||||
|
|
||||||
## 3.42.0
|
|
||||||
* [1.21.50 support](https://github.com/PrismarineJS/bedrock-protocol/commit/1c0836bff03d50cb12a3e45763eac6c9f605e00c) (thanks @extremeheat)
|
|
||||||
* [Dynamic compression & batch header (#544)](https://github.com/PrismarineJS/bedrock-protocol/commit/911e0e890febc00102cd1e5406731e66f7bad0ef) (thanks @LucienHH)
|
|
||||||
|
|
||||||
## 3.41.0
|
|
||||||
* [1.21.42 support](https://github.com/PrismarineJS/bedrock-protocol/commit/dd5c4de4f2624c3654af66e9a40a65eb13de0850) (thanks @CreeperG16)
|
|
||||||
|
|
||||||
## 3.40.0
|
|
||||||
* [1.21.30 support (#527)](https://github.com/PrismarineJS/bedrock-protocol/commit/fc30c96135ec20dca1257f702152cba61d4a59be) (thanks @pokecosimo)
|
|
||||||
* [Update tests (#528)](https://github.com/PrismarineJS/bedrock-protocol/commit/cb530c8b45bf505f75e0e39241d88085c5564ae8) (thanks @extremeheat)
|
|
||||||
|
|
||||||
## 3.39.0
|
## 3.39.0
|
||||||
* [1.21.20](https://github.com/PrismarineJS/bedrock-protocol/commit/3be55777fab4949179d3a7108ee29bbd8fada5a7) (thanks @extremeheat)
|
* [1.21.20](https://github.com/PrismarineJS/bedrock-protocol/commit/3be55777fab4949179d3a7108ee29bbd8fada5a7) (thanks @extremeheat)
|
||||||
* [update disconnect packet](https://github.com/PrismarineJS/bedrock-protocol/commit/4c3f62567e0f6ce20b70ea23238fce8606011e95) (thanks @extremeheat)
|
* [update disconnect packet](https://github.com/PrismarineJS/bedrock-protocol/commit/4c3f62567e0f6ce20b70ea23238fce8606011e95) (thanks @extremeheat)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Minecraft Bedrock Edition (aka MCPE) protocol library, supporting authentication
|
||||||
|
|
||||||
## Features
|
## 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
|
||||||
- Parse and serialize packets as JavaScript objects
|
- Parse and serialize packets as JavaScript objects
|
||||||
- Automatically respond to keep-alive packets
|
- Automatically respond to keep-alive packets
|
||||||
- [Proxy and mitm connections](docs/API.md#proxy-docs)
|
- [Proxy and mitm connections](docs/API.md#proxy-docs)
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ client.on('text', (packet) => {
|
||||||
// names and as explained in the "Protocol doc" section below, fields are all case sensitive!
|
// names and as explained in the "Protocol doc" section below, fields are all case sensitive!
|
||||||
client.on('add_player', (packet) => {
|
client.on('add_player', (packet) => {
|
||||||
client.queue('text', {
|
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!`
|
message: `Hey, ${packet.username} just joined!`
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
4
index.d.ts
vendored
4
index.d.ts
vendored
|
|
@ -3,7 +3,7 @@ import { Realm } from 'prismarine-realms'
|
||||||
import { ServerDeviceCodeResponse } from 'prismarine-auth'
|
import { ServerDeviceCodeResponse } from 'prismarine-auth'
|
||||||
|
|
||||||
declare module 'bedrock-protocol' {
|
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.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 {
|
export interface Options {
|
||||||
// The string version to start the client or server as
|
// The string version to start the client or server as
|
||||||
|
|
@ -162,7 +162,7 @@ declare module 'bedrock-protocol' {
|
||||||
|
|
||||||
constructor(options: Options)
|
constructor(options: Options)
|
||||||
|
|
||||||
listen(): Promise<void>
|
listen(host?: string, port?: number): Promise<void>
|
||||||
close(disconnectReason?: string): Promise<void>
|
close(disconnectReason?: string): Promise<void>
|
||||||
|
|
||||||
on(event: 'connect', cb: (client: Player) => void): any
|
on(event: 'connect', cb: (client: Player) => void): any
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "bedrock-protocol",
|
"name": "bedrock-protocol",
|
||||||
"version": "3.49.0",
|
"version": "3.39.0",
|
||||||
"description": "Minecraft Bedrock Edition protocol library",
|
"description": "Minecraft Bedrock Edition protocol library",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "cd tools && node compileProtocol.js",
|
"build": "cd tools && node compileProtocol.js",
|
||||||
"test": "mocha --retries 2 --bail --exit",
|
"test": "mocha --bail --exit",
|
||||||
"pretest": "npm run lint",
|
"pretest": "npm run lint",
|
||||||
"lint": "standard",
|
"lint": "standard",
|
||||||
"vanillaServer": "minecraft-bedrock-server --root tools --version",
|
"vanillaServer": "node tools/startVanillaServer.js",
|
||||||
"dumpPackets": "node tools/genPacketDumps.js",
|
"dumpPackets": "node tools/genPacketDumps.js",
|
||||||
"fix": "standard --fix"
|
"fix": "standard --fix"
|
||||||
},
|
},
|
||||||
|
|
@ -40,8 +40,7 @@
|
||||||
"bedrock-protocol": "file:.",
|
"bedrock-protocol": "file:.",
|
||||||
"bedrock-provider": "^2.0.0",
|
"bedrock-provider": "^2.0.0",
|
||||||
"leveldb-zlib": "^1.0.1",
|
"leveldb-zlib": "^1.0.1",
|
||||||
"minecraft-bedrock-server": "^1.4.2",
|
"mocha": "^10.0.0",
|
||||||
"mocha": "^11.0.1",
|
|
||||||
"protodef-yaml": "^1.1.0",
|
"protodef-yaml": "^1.1.0",
|
||||||
"standard": "^17.0.0-2"
|
"standard": "^17.0.0-2"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ class Client extends Connection {
|
||||||
this.compressionAlgorithm = this.versionGreaterThanOrEqualTo('1.19.30') ? 'none' : 'deflate'
|
this.compressionAlgorithm = this.versionGreaterThanOrEqualTo('1.19.30') ? 'none' : 'deflate'
|
||||||
this.compressionThreshold = 512
|
this.compressionThreshold = 512
|
||||||
this.compressionLevel = this.options.compressionLevel
|
this.compressionLevel = this.options.compressionLevel
|
||||||
this.batchHeader = 0xfe
|
|
||||||
|
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
this.inLog = (...args) => debug('C ->', ...args)
|
this.inLog = (...args) => debug('C ->', ...args)
|
||||||
|
|
@ -61,9 +60,7 @@ class Client extends Connection {
|
||||||
try {
|
try {
|
||||||
const mcData = require('minecraft-data')('bedrock_' + this.options.version)
|
const mcData = require('minecraft-data')('bedrock_' + this.options.version)
|
||||||
this.features = {
|
this.features = {
|
||||||
compressorInHeader: mcData.supportFeature('compressorInPacketHeader'),
|
compressorInHeader: mcData.supportFeature('compressorInPacketHeader')
|
||||||
itemRegistryPacket: mcData.supportFeature('itemRegistryPacket'),
|
|
||||||
newLoginIdentityFields: mcData.supportFeature('newLoginIdentityFields')
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Unsupported version: '${this.options.version}', no data available`)
|
throw new Error(`Unsupported version: '${this.options.version}', no data available`)
|
||||||
|
|
@ -147,18 +144,9 @@ class Client extends Connection {
|
||||||
...this.accessToken // Mojang + Xbox JWT from auth
|
...this.accessToken // Mojang + Xbox JWT from auth
|
||||||
]
|
]
|
||||||
|
|
||||||
let encodedChain
|
const encodedChain = JSON.stringify({ chain })
|
||||||
if (this.features.newLoginIdentityFields) { // 1.21.90+
|
|
||||||
encodedChain = JSON.stringify({
|
debug('Auth chain', chain)
|
||||||
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)
|
|
||||||
|
|
||||||
this.write('login', {
|
this.write('login', {
|
||||||
protocol_version: this.options.protocolVersion,
|
protocol_version: this.options.protocolVersion,
|
||||||
|
|
@ -252,9 +240,7 @@ class Client extends Connection {
|
||||||
break
|
break
|
||||||
case 'start_game':
|
case 'start_game':
|
||||||
this.startGameData = pakData.params
|
this.startGameData = pakData.params
|
||||||
// fallsthrough
|
this.startGameData.itemstates.forEach(state => {
|
||||||
case 'item_registry': // 1.21.60+ send itemstates in item_registry packet
|
|
||||||
pakData.params.itemstates?.forEach(state => {
|
|
||||||
if (state.name === 'minecraft:shield') {
|
if (state.name === 'minecraft:shield') {
|
||||||
this.serializer.proto.setVariable('ShieldItemID', state.runtime_id)
|
this.serializer.proto.setVariable('ShieldItemID', state.runtime_id)
|
||||||
this.deserializer.proto.setVariable('ShieldItemID', state.runtime_id)
|
this.deserializer.proto.setVariable('ShieldItemID', state.runtime_id)
|
||||||
|
|
|
||||||
|
|
@ -28,22 +28,18 @@ class Connection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
versionLessThan (version) {
|
versionLessThan (version) {
|
||||||
if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version)
|
|
||||||
return this.options.protocolVersion < (typeof version === 'string' ? Versions[version] : version)
|
return this.options.protocolVersion < (typeof version === 'string' ? Versions[version] : version)
|
||||||
}
|
}
|
||||||
|
|
||||||
versionGreaterThan (version) {
|
versionGreaterThan (version) {
|
||||||
if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version)
|
|
||||||
return this.options.protocolVersion > (typeof version === 'string' ? Versions[version] : version)
|
return this.options.protocolVersion > (typeof version === 'string' ? Versions[version] : version)
|
||||||
}
|
}
|
||||||
|
|
||||||
versionGreaterThanOrEqualTo (version) {
|
versionGreaterThanOrEqualTo (version) {
|
||||||
if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version)
|
|
||||||
return this.options.protocolVersion >= (typeof version === 'string' ? Versions[version] : version)
|
return this.options.protocolVersion >= (typeof version === 'string' ? Versions[version] : version)
|
||||||
}
|
}
|
||||||
|
|
||||||
versionLessThanOrEqualTo (version) {
|
versionLessThanOrEqualTo (version) {
|
||||||
if (typeof version === 'string' && !Versions[version]) throw Error('Unknown version: ' + version)
|
|
||||||
return this.options.protocolVersion <= (typeof version === 'string' ? Versions[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) {
|
write (name, params) {
|
||||||
this.outLog?.(name, params)
|
this.outLog?.(name, params)
|
||||||
this._processOutbound(name, params)
|
if (name === 'start_game') this.updateItemPalette(params.itemstates)
|
||||||
const batch = new Framer(this)
|
const batch = new Framer(this)
|
||||||
const packet = this.serializer.createPacketBuffer({ name, params })
|
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||||
batch.addEncodedPacket(packet)
|
batch.addEncodedPacket(packet)
|
||||||
|
|
@ -94,7 +82,7 @@ class Connection extends EventEmitter {
|
||||||
|
|
||||||
queue (name, params) {
|
queue (name, params) {
|
||||||
this.outLog?.('Q <- ', 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 })
|
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||||
if (name === 'level_chunk') {
|
if (name === 'level_chunk') {
|
||||||
// Skip queue, send ASAP
|
// Skip queue, send ASAP
|
||||||
|
|
@ -165,7 +153,7 @@ class Connection extends EventEmitter {
|
||||||
|
|
||||||
// These are callbacks called from encryption.js
|
// These are callbacks called from encryption.js
|
||||||
onEncryptedPacket = (buf) => {
|
onEncryptedPacket = (buf) => {
|
||||||
const packet = this.batchHeader ? Buffer.concat([Buffer.from([this.batchHeader]), buf]) : buf
|
const packet = Buffer.concat([Buffer.from([0xfe]), buf]) // add header
|
||||||
this.sendMCPE(packet)
|
this.sendMCPE(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,7 +165,7 @@ class Connection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
handle (buffer) { // handle encapsulated
|
handle (buffer) { // handle encapsulated
|
||||||
if (!this.batchHeader || buffer[0] === this.batchHeader) { // wrapper
|
if (buffer[0] === 0xfe) { // wrapper
|
||||||
if (this.encryptionEnabled) {
|
if (this.encryptionEnabled) {
|
||||||
this.decrypt(buffer.slice(1))
|
this.decrypt(buffer.slice(1))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
const UUID = require('uuid-1345')
|
const UUID = require('uuid-1345')
|
||||||
const minecraft = require('./minecraft')
|
const minecraft = require('./minecraft')
|
||||||
const [Read, Write, SizeOf] = [{}, {}, {}]
|
const { Read, Write, SizeOf } = require('./varlong')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UUIDs
|
* UUIDs
|
||||||
|
|
@ -116,6 +116,74 @@ Read.lnbt = ['native', minecraft.lnbt[0]]
|
||||||
Write.lnbt = ['native', minecraft.lnbt[1]]
|
Write.lnbt = ['native', minecraft.lnbt[1]]
|
||||||
SizeOf.lnbt = ['native', minecraft.lnbt[2]]
|
SizeOf.lnbt = ['native', minecraft.lnbt[2]]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bits
|
||||||
|
*/
|
||||||
|
|
||||||
|
Read.bitflags = ['parametrizable', (compiler, { type, flags, shift, big }) => {
|
||||||
|
let fstr = JSON.stringify(flags)
|
||||||
|
if (Array.isArray(flags)) {
|
||||||
|
fstr = '{'
|
||||||
|
flags.map((v, k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
|
||||||
|
fstr += '}'
|
||||||
|
} else if (shift) {
|
||||||
|
fstr = '{'
|
||||||
|
for (const key in flags) fstr += `"${key}": ${1 << flags[key]},`;
|
||||||
|
fstr += '}'
|
||||||
|
}
|
||||||
|
return compiler.wrapCode(`
|
||||||
|
const { value: _value, size } = ${compiler.callType(type, 'offset')}
|
||||||
|
const value = { _value }
|
||||||
|
const flags = ${fstr}
|
||||||
|
for (const key in flags) {
|
||||||
|
value[key] = (_value & flags[key]) == flags[key]
|
||||||
|
}
|
||||||
|
return { value, size }
|
||||||
|
`.trim())
|
||||||
|
}]
|
||||||
|
|
||||||
|
Write.bitflags = ['parametrizable', (compiler, { type, flags, shift, big }) => {
|
||||||
|
let fstr = JSON.stringify(flags)
|
||||||
|
if (Array.isArray(flags)) {
|
||||||
|
fstr = '{'
|
||||||
|
flags.map((v, k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
|
||||||
|
fstr += '}'
|
||||||
|
} else if (shift) {
|
||||||
|
fstr = '{'
|
||||||
|
for (const key in flags) fstr += `"${key}": ${1 << flags[key]},`;
|
||||||
|
fstr += '}'
|
||||||
|
}
|
||||||
|
return compiler.wrapCode(`
|
||||||
|
const flags = ${fstr}
|
||||||
|
let val = value._value ${big ? '|| 0n' : ''}
|
||||||
|
for (const key in flags) {
|
||||||
|
if (value[key]) val |= flags[key]
|
||||||
|
}
|
||||||
|
return (ctx.${type})(val, buffer, offset)
|
||||||
|
`.trim())
|
||||||
|
}]
|
||||||
|
|
||||||
|
SizeOf.bitflags = ['parametrizable', (compiler, { type, flags, shift, big }) => {
|
||||||
|
let fstr = JSON.stringify(flags)
|
||||||
|
if (Array.isArray(flags)) {
|
||||||
|
fstr = '{'
|
||||||
|
flags.map((v, k) => fstr += `"${v}": ${big ? 1n << BigInt(k) : 1 << k}` + (big ? 'n,' : ','))
|
||||||
|
fstr += '}'
|
||||||
|
} else if (shift) {
|
||||||
|
fstr = '{'
|
||||||
|
for (const key in flags) fstr += `"${key}": ${1 << flags[key]},`;
|
||||||
|
fstr += '}'
|
||||||
|
}
|
||||||
|
return compiler.wrapCode(`
|
||||||
|
const flags = ${fstr}
|
||||||
|
let val = value._value ${big ? '|| 0n' : ''}
|
||||||
|
for (const key in flags) {
|
||||||
|
if (value[key]) val |= flags[key]
|
||||||
|
}
|
||||||
|
return (ctx.${type})(val)
|
||||||
|
`.trim())
|
||||||
|
}]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command Packet
|
* Command Packet
|
||||||
* - used for determining the size of the following enum
|
* - used for determining the size of the following enum
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
|
/* eslint-disable */
|
||||||
const nbt = require('prismarine-nbt')
|
const nbt = require('prismarine-nbt')
|
||||||
const UUID = require('uuid-1345')
|
const UUID = require('uuid-1345')
|
||||||
|
|
||||||
const protoLE = nbt.protos.little
|
const protoLE = nbt.protos.little
|
||||||
const protoLEV = nbt.protos.littleVarint
|
const protoLEV = nbt.protos.littleVarint
|
||||||
|
// TODO: deal with this:
|
||||||
|
const zigzag = require('prismarine-nbt/zigzag')
|
||||||
|
|
||||||
function readUUID (buffer, offset) {
|
function readUUID (buffer, offset) {
|
||||||
if (offset + 16 > buffer.length) { throw new Error('Reached end of buffer') }
|
if (offset + 16 > buffer.length) { throw new PartialReadError() }
|
||||||
return {
|
return {
|
||||||
value: UUID.stringify(buffer.slice(offset, 16 + offset)),
|
value: UUID.stringify(buffer.slice(offset, 16 + offset)),
|
||||||
size: 16
|
size: 16
|
||||||
|
|
@ -62,7 +65,7 @@ function readEntityMetadata (buffer, offset, _ref) {
|
||||||
const metadata = []
|
const metadata = []
|
||||||
let item
|
let item
|
||||||
while (true) {
|
while (true) {
|
||||||
if (offset + 1 > buffer.length) throw new Error('Reached end of buffer')
|
if (offset + 1 > buffer.length) throw new PartialReadError()
|
||||||
item = buffer.readUInt8(cursor)
|
item = buffer.readUInt8(cursor)
|
||||||
if (item === endVal) {
|
if (item === endVal) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -156,5 +159,7 @@ module.exports = {
|
||||||
lnbt: [readNbtLE, writeNbtLE, sizeOfNbtLE],
|
lnbt: [readNbtLE, writeNbtLE, sizeOfNbtLE],
|
||||||
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
|
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
|
||||||
ipAddress: [readIpAddress, writeIpAddress, 4],
|
ipAddress: [readIpAddress, writeIpAddress, 4],
|
||||||
endOfArray: [readEndOfArray, writeEndOfArray, sizeOfEndOfArray]
|
endOfArray: [readEndOfArray, writeEndOfArray, sizeOfEndOfArray],
|
||||||
|
zigzag32: zigzag.interpret.zigzag32,
|
||||||
|
zigzag64: zigzag.interpret.zigzag64
|
||||||
}
|
}
|
||||||
|
|
|
||||||
63
src/datatypes/varlong.js
Normal file
63
src/datatypes/varlong.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
function sizeOfVarLong (value) {
|
||||||
|
if (typeof value.valueOf() === 'object') {
|
||||||
|
value = (BigInt(value[0]) << 32n) | BigInt(value[1])
|
||||||
|
} else if (typeof value !== 'bigint') value = BigInt(value)
|
||||||
|
|
||||||
|
let cursor = 0
|
||||||
|
while (value > 127n) {
|
||||||
|
value >>= 7n
|
||||||
|
cursor++
|
||||||
|
}
|
||||||
|
return cursor + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a 64-bit VarInt as a BigInt
|
||||||
|
*/
|
||||||
|
function readVarLong (buffer, offset) {
|
||||||
|
let result = BigInt(0)
|
||||||
|
let shift = 0n
|
||||||
|
let cursor = offset
|
||||||
|
let size = 0
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (cursor + 1 > buffer.length) { throw new Error('unexpected buffer end') }
|
||||||
|
const b = buffer.readUInt8(cursor)
|
||||||
|
result |= (BigInt(b) & 0x7fn) << shift // Add the bits to our number, except MSB
|
||||||
|
cursor++
|
||||||
|
if (!(b & 0x80)) { // If the MSB is not set, we return the number
|
||||||
|
size = cursor - offset
|
||||||
|
break
|
||||||
|
}
|
||||||
|
shift += 7n // we only have 7 bits, MSB being the return-trigger
|
||||||
|
if (shift > 63n) throw new Error(`varint is too big: ${shift}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { value: result, size }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a zigzag encoded 64-bit VarInt as a BigInt
|
||||||
|
*/
|
||||||
|
function writeVarLong (value, buffer, offset) {
|
||||||
|
// if an array, turn it into a BigInt
|
||||||
|
if (typeof value.valueOf() === 'object') {
|
||||||
|
value = BigInt.asIntN(64, (BigInt(value[0]) << 32n)) | BigInt(value[1])
|
||||||
|
} else if (typeof value !== 'bigint') value = BigInt(value)
|
||||||
|
|
||||||
|
let cursor = 0
|
||||||
|
while (value > 127n) { // keep writing in 7 bit slices
|
||||||
|
const num = Number(value & 0xFFn)
|
||||||
|
buffer.writeUInt8(num | 0x80, offset + cursor)
|
||||||
|
cursor++
|
||||||
|
value >>= 7n
|
||||||
|
}
|
||||||
|
buffer.writeUInt8(Number(value), offset + cursor)
|
||||||
|
return offset + cursor + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Read: { varint64: ['native', readVarLong] },
|
||||||
|
Write: { varint64: ['native', writeVarLong] },
|
||||||
|
SizeOf: { varint64: ['native', sizeOfVarLong] }
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,7 @@ module.exports = (client, server, options) => {
|
||||||
client.createClientUserChain = (privateKey) => {
|
client.createClientUserChain = (privateKey) => {
|
||||||
let payload = {
|
let payload = {
|
||||||
...skinData,
|
...skinData,
|
||||||
|
SkinGeometryDataEngineVersion: client.versionGreaterThanOrEqualTo('1.17.30') ? '' : undefined,
|
||||||
|
|
||||||
ClientRandomId: Date.now(),
|
ClientRandomId: Date.now(),
|
||||||
CurrentInputMode: 1,
|
CurrentInputMode: 1,
|
||||||
|
|
@ -46,30 +47,25 @@ module.exports = (client, server, options) => {
|
||||||
GameVersion: options.version || '1.16.201',
|
GameVersion: options.version || '1.16.201',
|
||||||
GuiScale: -1,
|
GuiScale: -1,
|
||||||
LanguageCode: 'en_GB', // TODO locale
|
LanguageCode: 'en_GB', // TODO locale
|
||||||
GraphicsMode: 1, // 1:simple, 2:fancy, 3:advanced, 4:ray_traced
|
|
||||||
|
|
||||||
PlatformOfflineId: '',
|
PlatformOfflineId: '',
|
||||||
PlatformOnlineId: '', // chat
|
PlatformOnlineId: '', // chat
|
||||||
// PlayFabID is the PlayFab ID produced for the skin. PlayFab is the company that hosts the Marketplace,
|
// 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
|
// 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.
|
// inside of PlayFab.
|
||||||
PlayFabId: nextUUID().replace(/-/g, '').slice(0, 16).toLowerCase(), // 1.16.210
|
PlayFabId: nextUUID().replace(/-/g, '').slice(0, 16), // 1.16.210
|
||||||
|
|
||||||
SelfSignedId: nextUUID(),
|
SelfSignedId: nextUUID(),
|
||||||
ServerAddress: `${options.host}:${options.port}`,
|
ServerAddress: `${options.host}:${options.port}`,
|
||||||
|
|
||||||
ThirdPartyName: client.profile.name, // Gamertag
|
ThirdPartyName: client.profile.name,
|
||||||
ThirdPartyNameOnly: client.versionGreaterThanOrEqualTo('1.21.90') ? undefined : false,
|
ThirdPartyNameOnly: false,
|
||||||
UIProfile: 0,
|
UIProfile: 0,
|
||||||
|
|
||||||
IsEditorMode: false,
|
IsEditorMode: false,
|
||||||
TrustedSkin: client.versionGreaterThanOrEqualTo('1.19.20') ? false : undefined,
|
TrustedSkin: client.versionGreaterThanOrEqualTo('1.19.20') ? false : undefined,
|
||||||
OverrideSkin: client.versionGreaterThanOrEqualTo('1.19.62') ? false : undefined,
|
OverrideSkin: client.versionGreaterThanOrEqualTo('1.19.62') ? false : undefined,
|
||||||
CompatibleWithClientSideChunkGen: client.versionGreaterThanOrEqualTo('1.19.80') ? 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
|
|
||||||
}
|
}
|
||||||
const customPayload = options.skinData || {}
|
const customPayload = options.skinData || {}
|
||||||
payload = { ...payload, ...customPayload }
|
payload = { ...payload, ...customPayload }
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ const mcData = require('minecraft-data')
|
||||||
// Minimum supported version (< will be kicked)
|
// Minimum supported version (< will be kicked)
|
||||||
const MIN_VERSION = '1.16.201'
|
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
|
// 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.20'
|
||||||
|
|
||||||
const Versions = Object.fromEntries(mcData.versions.bedrock.filter(e => e.releaseType === 'release').map(e => [e.minecraftVersion, e.version]))
|
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
|
// 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']
|
||||||
const testedVersions = process.env.CI ? Object.keys(Versions).filter(v => !skippedVersionsOnGithubCI.includes(v)) : Object.keys(Versions)
|
const testedVersions = process.env.CI ? Object.keys(Versions).filter(v => !skippedVersionsOnGithubCI.includes(v)) : Object.keys(Versions)
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ class Server extends EventEmitter {
|
||||||
this.clients = {}
|
this.clients = {}
|
||||||
this.clientCount = 0
|
this.clientCount = 0
|
||||||
this.conLog = debug
|
this.conLog = debug
|
||||||
this.batchHeader = 0xfe
|
|
||||||
|
|
||||||
this.setCompressor(this.options.compressionAlgorithm, this.options.compressionLevel, this.options.compressionThreshold)
|
this.setCompressor(this.options.compressionAlgorithm, this.options.compressionLevel, this.options.compressionThreshold)
|
||||||
}
|
}
|
||||||
|
|
@ -33,8 +32,7 @@ class Server extends EventEmitter {
|
||||||
try {
|
try {
|
||||||
const mcData = require('minecraft-data')('bedrock_' + version)
|
const mcData = require('minecraft-data')('bedrock_' + version)
|
||||||
this.features = {
|
this.features = {
|
||||||
compressorInHeader: mcData.supportFeature('compressorInPacketHeader'),
|
compressorInHeader: mcData.supportFeature('compressorInPacketHeader')
|
||||||
newLoginIdentityFields: mcData.supportFeature('newLoginIdentityFields')
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Unsupported version: '${version}', no data available`)
|
throw new Error(`Unsupported version: '${version}', no data available`)
|
||||||
|
|
@ -46,19 +44,16 @@ class Server extends EventEmitter {
|
||||||
case 'none':
|
case 'none':
|
||||||
this.compressionAlgorithm = 'none'
|
this.compressionAlgorithm = 'none'
|
||||||
this.compressionLevel = 0
|
this.compressionLevel = 0
|
||||||
this.compressionHeader = 255
|
|
||||||
break
|
break
|
||||||
case 'deflate':
|
case 'deflate':
|
||||||
this.compressionAlgorithm = 'deflate'
|
this.compressionAlgorithm = 'deflate'
|
||||||
this.compressionLevel = level
|
this.compressionLevel = level
|
||||||
this.compressionThreshold = threshold
|
this.compressionThreshold = threshold
|
||||||
this.compressionHeader = 0
|
|
||||||
break
|
break
|
||||||
case 'snappy':
|
case 'snappy':
|
||||||
this.compressionAlgorithm = 'snappy'
|
this.compressionAlgorithm = 'snappy'
|
||||||
this.compressionLevel = level
|
this.compressionLevel = level
|
||||||
this.compressionThreshold = threshold
|
this.compressionThreshold = threshold
|
||||||
this.compressionHeader = 1
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown compression algorithm: ${algorithm}`)
|
throw new Error(`Unknown compression algorithm: ${algorithm}`)
|
||||||
|
|
@ -90,10 +85,12 @@ class Server extends EventEmitter {
|
||||||
this.emit('connect', player)
|
this.emit('connect', player)
|
||||||
}
|
}
|
||||||
|
|
||||||
onCloseConnection = (conn, reason) => {
|
onCloseConnection = (inetAddr, reason) => {
|
||||||
this.conLog('Connection closed: ', conn.address, reason)
|
this.conLog('Connection closed: ', inetAddr?.address, reason)
|
||||||
this.clients[conn.address]?.close()
|
|
||||||
delete this.clients[conn.address]
|
delete this.clients[inetAddr]?.connection // Prevent close loop
|
||||||
|
this.clients[inetAddr?.address ?? inetAddr]?.close()
|
||||||
|
delete this.clients[inetAddr]
|
||||||
this.clientCount--
|
this.clientCount--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,9 +114,8 @@ class Server extends EventEmitter {
|
||||||
return this.advertisement
|
return this.advertisement
|
||||||
}
|
}
|
||||||
|
|
||||||
async listen () {
|
async listen (host = this.options.host, port = this.options.port) {
|
||||||
const { host, port, maxPlayers } = this.options
|
this.raknet = new this.RakServer({ host, port }, this)
|
||||||
this.raknet = new this.RakServer({ host, port, maxPlayers }, this)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.raknet.listen()
|
await this.raknet.listen()
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,10 @@ class Player extends Connection {
|
||||||
this.outLog = (...args) => debug('<- S', ...args)
|
this.outLog = (...args) => debug('<- S', ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.batchHeader = this.server.batchHeader
|
|
||||||
// Compression is server-wide
|
// Compression is server-wide
|
||||||
this.compressionAlgorithm = this.server.compressionAlgorithm
|
this.compressionAlgorithm = this.server.compressionAlgorithm
|
||||||
this.compressionLevel = this.server.compressionLevel
|
this.compressionLevel = this.server.compressionLevel
|
||||||
this.compressionThreshold = this.server.compressionThreshold
|
this.compressionThreshold = this.server.compressionThreshold
|
||||||
this.compressionHeader = this.server.compressionHeader
|
|
||||||
|
|
||||||
this._sentNetworkSettings = false // 1.19.30+
|
this._sentNetworkSettings = false // 1.19.30+
|
||||||
}
|
}
|
||||||
|
|
@ -78,18 +76,11 @@ class Player extends Connection {
|
||||||
|
|
||||||
// Parse login data
|
// Parse login data
|
||||||
const tokens = body.params.tokens
|
const tokens = body.params.tokens
|
||||||
try {
|
|
||||||
const skinChain = tokens.client
|
|
||||||
const authChain = JSON.parse(tokens.identity)
|
const authChain = JSON.parse(tokens.identity)
|
||||||
let chain
|
const skinChain = tokens.client
|
||||||
if (authChain.Certificate) { // 1.21.90+
|
|
||||||
chain = JSON.parse(authChain.Certificate).chain
|
try {
|
||||||
} else if (authChain.chain) {
|
var { key, userData, skinData } = this.decodeLoginJWT(authChain.chain, skinChain) // eslint-disable-line
|
||||||
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
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debug(this.address, e)
|
debug(this.address, e)
|
||||||
this.disconnect('Server authentication error')
|
this.disconnect('Server authentication error')
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,9 @@ class Framer {
|
||||||
constructor (client) {
|
constructor (client) {
|
||||||
// Encoding
|
// Encoding
|
||||||
this.packets = []
|
this.packets = []
|
||||||
this.batchHeader = client.batchHeader
|
|
||||||
this.compressor = client.compressionAlgorithm || 'none'
|
this.compressor = client.compressionAlgorithm || 'none'
|
||||||
this.compressionLevel = client.compressionLevel
|
this.compressionLevel = client.compressionLevel
|
||||||
this.compressionThreshold = client.compressionThreshold
|
this.compressionThreshold = client.compressionThreshold
|
||||||
this.compressionHeader = client.compressionHeader || 0
|
|
||||||
this.writeCompressor = client.features.compressorInHeader && client.compressionReady
|
this.writeCompressor = client.features.compressorInHeader && client.compressionReady
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,7 +38,7 @@ class Framer {
|
||||||
|
|
||||||
static decode (client, buf) {
|
static decode (client, buf) {
|
||||||
// Read header
|
// Read header
|
||||||
if (this.batchHeader && buf[0] !== this.batchHeader) throw Error(`bad batch packet header, received: ${buf[0]}, expected: ${this.batchHeader}`)
|
if (buf[0] !== 0xfe) throw Error('bad batch packet header ' + buf[0])
|
||||||
const buffer = buf.slice(1)
|
const buffer = buf.slice(1)
|
||||||
// Decompress
|
// Decompress
|
||||||
let decompressed
|
let decompressed
|
||||||
|
|
@ -60,10 +58,9 @@ class Framer {
|
||||||
|
|
||||||
encode () {
|
encode () {
|
||||||
const buf = Buffer.concat(this.packets)
|
const buf = Buffer.concat(this.packets)
|
||||||
const shouldCompress = buf.length > this.compressionThreshold
|
const compressed = (buf.length > this.compressionThreshold) ? this.compress(buf) : buf
|
||||||
const header = this.batchHeader ? [this.batchHeader] : []
|
const header = this.writeCompressor ? [0xfe, 0] : [0xfe]
|
||||||
if (this.writeCompressor) header.push(shouldCompress ? this.compressionHeader : 255)
|
return Buffer.concat([Buffer.from(header), compressed])
|
||||||
return Buffer.concat([Buffer.from(header), shouldCompress ? this.compress(buf) : buf])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addEncodedPacket (chunk) {
|
addEncodedPacket (chunk) {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ function createProtocol (version) {
|
||||||
const compiler = new ProtoDefCompiler()
|
const compiler = new ProtoDefCompiler()
|
||||||
compiler.addTypesToCompile(protocol.types)
|
compiler.addTypesToCompile(protocol.types)
|
||||||
compiler.addTypes(require('../datatypes/compiler-minecraft'))
|
compiler.addTypes(require('../datatypes/compiler-minecraft'))
|
||||||
|
compiler.addTypes(require('prismarine-nbt/zigzag').compiler)
|
||||||
|
|
||||||
const compiledProto = compiler.compileProtoDefSync()
|
const compiledProto = compiler.compileProtoDefSync()
|
||||||
return compiledProto
|
return compiledProto
|
||||||
|
|
@ -46,6 +47,7 @@ function createProtocol (version) {
|
||||||
function getProtocol (version) {
|
function getProtocol (version) {
|
||||||
const compiler = new ProtoDefCompiler()
|
const compiler = new ProtoDefCompiler()
|
||||||
compiler.addTypes(require(join(__dirname, '../datatypes/compiler-minecraft')))
|
compiler.addTypes(require(join(__dirname, '../datatypes/compiler-minecraft')))
|
||||||
|
compiler.addTypes(require('prismarine-nbt/zigzag').compiler)
|
||||||
|
|
||||||
global.PartialReadError = require('protodef/src/utils').PartialReadError
|
global.PartialReadError = require('protodef/src/utils').PartialReadError
|
||||||
const compile = (compiler, file) => require(file)(compiler.native)
|
const compile = (compiler, file) => require(file)(compiler.native)
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ async function startTest (version = CURRENT_VERSION, ok) {
|
||||||
must_accept: false,
|
must_accept: false,
|
||||||
has_scripts: false,
|
has_scripts: false,
|
||||||
behaviour_packs: [],
|
behaviour_packs: [],
|
||||||
world_template: { uuid: '550e8400-e29b-41d4-a716-446655440000', version: '' }, // 1.21.50
|
|
||||||
texture_packs: [],
|
texture_packs: [],
|
||||||
resource_pack_links: []
|
resource_pack_links: []
|
||||||
})
|
})
|
||||||
|
|
@ -63,11 +62,7 @@ async function startTest (version = CURRENT_VERSION, ok) {
|
||||||
// client.queue('inventory_transaction', get('packets/inventory_transaction.json'))
|
// client.queue('inventory_transaction', get('packets/inventory_transaction.json'))
|
||||||
client.queue('player_list', get('packets/player_list.json'))
|
client.queue('player_list', get('packets/player_list.json'))
|
||||||
client.queue('start_game', get('packets/start_game.json'))
|
client.queue('start_game', get('packets/start_game.json'))
|
||||||
if (client.versionLessThan('1.21.60')) {
|
|
||||||
client.queue('item_component', { entries: [] })
|
client.queue('item_component', { entries: [] })
|
||||||
} else {
|
|
||||||
client.queue('item_registry', get('packets/item_registry.json'))
|
|
||||||
}
|
|
||||||
client.queue('set_spawn_position', get('packets/set_spawn_position.json'))
|
client.queue('set_spawn_position', get('packets/set_spawn_position.json'))
|
||||||
client.queue('set_time', { time: 5433771 })
|
client.queue('set_time', { time: 5433771 })
|
||||||
client.queue('set_difficulty', { difficulty: 1 })
|
client.queue('set_difficulty', { difficulty: 1 })
|
||||||
|
|
@ -101,11 +96,11 @@ async function startTest (version = CURRENT_VERSION, ok) {
|
||||||
|
|
||||||
loop = setInterval(() => {
|
loop = setInterval(() => {
|
||||||
client.write('network_chunk_publisher_update', { coordinates: { x: 646, y: 130, z: 77 }, radius: 64 })
|
client.write('network_chunk_publisher_update', { coordinates: { x: 646, y: 130, z: 77 }, radius: 64 })
|
||||||
}, 6500)
|
}, 9500)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
client.write('play_status', { status: 'player_spawn' })
|
client.write('play_status', { status: 'player_spawn' })
|
||||||
}, 3000)
|
}, 6000)
|
||||||
|
|
||||||
// Respond to tick synchronization packets
|
// Respond to tick synchronization packets
|
||||||
client.on('tick_sync', (packet) => {
|
client.on('tick_sync', (packet) => {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
const { timedTest } = require('./internal')
|
const { timedTest } = require('./internal')
|
||||||
const { testedVersions } = require('../src/options')
|
const { testedVersions } = require('../src/options')
|
||||||
const { sleep } = require('../src/datatypes/util')
|
const { sleep } = require('../src/datatypes/util')
|
||||||
require('events').captureRejections = true
|
|
||||||
|
|
||||||
describe('internal client/server test', function () {
|
describe('internal client/server test', function () {
|
||||||
const vcount = testedVersions.length
|
const vcount = testedVersions.length
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,12 @@ function proxyTest (version, raknetBackend = 'raknet-native', timeout = 1000 * 4
|
||||||
console.debug('Client has authenticated')
|
console.debug('Client has authenticated')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
client.disconnect('Hello world !')
|
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)
|
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({
|
const relay = new Relay({
|
||||||
version,
|
version,
|
||||||
|
|
@ -46,7 +46,7 @@ function proxyTest (version, raknetBackend = 'raknet-native', timeout = 1000 * 4
|
||||||
await relay.listen()
|
await relay.listen()
|
||||||
|
|
||||||
console.debug('Proxy started', server.options.version)
|
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 })
|
const client = createClient({ host: '127.0.0.1', port: CLIENT_PORT, version, username: 'Boat', offline: true, raknetBackend, skipPing: true })
|
||||||
console.debug('Client started')
|
console.debug('Client started')
|
||||||
|
|
@ -58,7 +58,7 @@ function proxyTest (version, raknetBackend = 'raknet-native', timeout = 1000 * 4
|
||||||
server.close()
|
server.close()
|
||||||
relay.close()
|
relay.close()
|
||||||
console.log('✔ OK')
|
console.log('✔ OK')
|
||||||
sleep(200).then(res)
|
sleep(500).then(res)
|
||||||
})
|
})
|
||||||
}, timeout, () => { throw Error('timed out') })
|
}, timeout, () => { throw Error('timed out') })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ describe('proxies client/server', function () {
|
||||||
it('proxies ' + version, async () => {
|
it('proxies ' + version, async () => {
|
||||||
console.debug(version)
|
console.debug(version)
|
||||||
await proxyTest(version)
|
await proxyTest(version)
|
||||||
await sleep(100)
|
await sleep(1000)
|
||||||
console.debug('Done', version)
|
console.debug('Done', version)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,7 @@ const getPort = () => new Promise(resolve => {
|
||||||
server.listen(0, '127.0.0.1')
|
server.listen(0, '127.0.0.1')
|
||||||
server.on('listening', () => {
|
server.on('listening', () => {
|
||||||
const { port } = server.address()
|
const { port } = server.address()
|
||||||
server.close(() => {
|
server.close(() => resolve(port))
|
||||||
// Wait a bit for port to free as we try to bind right after freeing it
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve(port)
|
|
||||||
}, 200)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,8 @@ async function test (version) {
|
||||||
// const ChunkColumn = require('bedrock-provider').chunk('bedrock_' + (version.includes('1.19') ? '1.18.30' : version)) // TODO: Fix prismarine-chunk
|
// const ChunkColumn = require('bedrock-provider').chunk('bedrock_' + (version.includes('1.19') ? '1.18.30' : version)) // TODO: Fix prismarine-chunk
|
||||||
|
|
||||||
// Start the server, wait for it to accept clients, throws on timeout
|
// Start the server, wait for it to accept clients, throws on timeout
|
||||||
const [port, v6] = [await getPort(), await getPort()]
|
const port = await getPort()
|
||||||
console.log('Starting vanilla server', version, 'on port', port, v6)
|
const handle = await vanillaServer.startServerAndWait2(version, 1000 * 220, { 'server-port': port })
|
||||||
const handle = await vanillaServer.startServerAndWait2(version, 1000 * 220, { 'server-port': port, 'server-portv6': v6 })
|
|
||||||
console.log('Started server')
|
console.log('Started server')
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ function createProtocol (version) {
|
||||||
const compiler = new ProtoDefCompiler()
|
const compiler = new ProtoDefCompiler()
|
||||||
const protocol = mcData('bedrock_' + version).protocol.types
|
const protocol = mcData('bedrock_' + version).protocol.types
|
||||||
compiler.addTypes(require('../src/datatypes/compiler-minecraft'))
|
compiler.addTypes(require('../src/datatypes/compiler-minecraft'))
|
||||||
|
compiler.addTypes(require('prismarine-nbt/zigzag').compiler)
|
||||||
compiler.addTypesToCompile(protocol)
|
compiler.addTypesToCompile(protocol)
|
||||||
|
|
||||||
fs.writeFileSync('./read.js', 'module.exports = ' + compiler.readCompiler.generate().replace('() =>', 'native =>'))
|
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 no argument, build everything
|
||||||
if (!process.argv[2]) {
|
if (!process.argv[2]) {
|
||||||
convert('bedrock', 'latest')
|
convert('latest')
|
||||||
for (const version of versions) {
|
for (const version of versions) {
|
||||||
main(version)
|
main(version)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ async function dump (version, force = true) {
|
||||||
const random = (Math.random() * 1000) | 0
|
const random = (Math.random() * 1000) | 0
|
||||||
const [port, v6] = [await getPort(), await getPort()]
|
const [port, v6] = [await getPort(), await getPort()]
|
||||||
|
|
||||||
console.log('Starting dump server', version, 'on port', port, v6)
|
console.log('Starting dump server', version)
|
||||||
const handle = await vanillaServer.startServerAndWait2(version || CURRENT_VERSION, 1000 * 120, { 'server-port': port, 'server-portv6': v6 })
|
const handle = await vanillaServer.startServerAndWait2(version || CURRENT_VERSION, 1000 * 120, { 'server-port': port, 'server-portv6': v6 })
|
||||||
|
|
||||||
console.log('Started dump server', version)
|
console.log('Started dump server', version)
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,156 @@
|
||||||
const bedrockServer = require('minecraft-bedrock-server')
|
const http = require('https')
|
||||||
|
const fs = require('fs')
|
||||||
|
const cp = require('child_process')
|
||||||
|
const debug = process.env.CI ? console.debug : require('debug')('minecraft-protocol')
|
||||||
|
const https = require('https')
|
||||||
|
const { getFiles, waitFor } = require('../src/datatypes/util')
|
||||||
|
|
||||||
module.exports = {
|
function head (url) {
|
||||||
...bedrockServer,
|
return new Promise((resolve, reject) => {
|
||||||
startServerAndWait (version, withTimeout, options) {
|
const req = http.request(url, { method: 'HEAD', timeout: 1000 }, resolve)
|
||||||
return bedrockServer.startServerAndWait(version, withTimeout, { ...options, root: __dirname })
|
req.on('error', reject)
|
||||||
},
|
req.on('timeout', () => { req.destroy(); debug('HEAD request timeout'); reject(new Error('timeout')) })
|
||||||
startServerAndWait2 (version, withTimeout, options) {
|
req.end()
|
||||||
return bedrockServer.startServerAndWait2(version, withTimeout, { ...options, root: __dirname })
|
})
|
||||||
|
}
|
||||||
|
function get (url, outPath) {
|
||||||
|
const file = fs.createWriteStream(outPath)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
https.get(url, { timeout: 1000 * 20 }, response => {
|
||||||
|
if (response.statusCode !== 200) return reject(new Error('Server returned code ' + response.statusCode))
|
||||||
|
response.pipe(file)
|
||||||
|
file.on('finish', () => {
|
||||||
|
file.close()
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the latest versions
|
||||||
|
// TODO: once we support multi-versions
|
||||||
|
function fetchLatestStable () {
|
||||||
|
get('https://raw.githubusercontent.com/minecraft-linux/mcpelauncher-versiondb/master/versions.json', 'versions.json')
|
||||||
|
const versions = JSON.parse(fs.readFileSync('./versions.json'))
|
||||||
|
const latest = versions[0]
|
||||||
|
return latest.version_name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download + extract vanilla server and enter the directory
|
||||||
|
async function download (os, version, path = 'bds-') {
|
||||||
|
debug('Downloading server', os, version, 'into', path)
|
||||||
|
process.chdir(__dirname)
|
||||||
|
const verStr = version.split('.').slice(0, 3).join('.')
|
||||||
|
const dir = path + version
|
||||||
|
|
||||||
|
if (fs.existsSync(dir) && getFiles(dir).length) {
|
||||||
|
process.chdir(path + version) // Enter server folder
|
||||||
|
return verStr
|
||||||
|
}
|
||||||
|
try { fs.mkdirSync(dir) } catch { }
|
||||||
|
|
||||||
|
process.chdir(path + version) // Enter server folder
|
||||||
|
const url = (os, version) => `https://minecraft.azureedge.net/bin-${os}/bedrock-server-${version}.zip`
|
||||||
|
|
||||||
|
let found = false
|
||||||
|
|
||||||
|
for (let i = 0; i < 8; i++) { // Check for the latest server build for version (major.minor.patch.BUILD)
|
||||||
|
const u = url(os, `${verStr}.${String(i).padStart(2, '0')}`)
|
||||||
|
debug('Opening', u, Date.now())
|
||||||
|
let ret
|
||||||
|
try { ret = await head(u) } catch (e) { continue }
|
||||||
|
if (ret.statusCode === 200) {
|
||||||
|
found = u
|
||||||
|
debug('Found server', ret.statusCode)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) throw Error('did not find server bin for ' + os + ' ' + version)
|
||||||
|
console.info('🔻 Downloading', found)
|
||||||
|
await get(found, 'bds.zip')
|
||||||
|
console.info('⚡ Unzipping')
|
||||||
|
// Unzip server
|
||||||
|
if (process.platform === 'linux') cp.execSync('unzip -u bds.zip && chmod +777 ./bedrock_server')
|
||||||
|
else cp.execSync('tar -xf bds.zip')
|
||||||
|
return verStr
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
'level-generator': '2',
|
||||||
|
'server-port': '19130',
|
||||||
|
'online-mode': 'false'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the server
|
||||||
|
function configure (options = {}) {
|
||||||
|
const opts = { ...defaultOptions, ...options }
|
||||||
|
let config = fs.readFileSync('./server.properties', 'utf-8')
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
function run (inheritStdout = true) {
|
||||||
|
const exe = process.platform === 'win32' ? 'bedrock_server.exe' : './bedrock_server'
|
||||||
|
return cp.spawn(exe, inheritStdout ? { stdio: 'inherit' } : {})
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastHandle
|
||||||
|
|
||||||
|
// Run the server
|
||||||
|
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, options.path)
|
||||||
|
configure(options)
|
||||||
|
const handle = lastHandle = run(!onStart)
|
||||||
|
handle.on('error', (...a) => {
|
||||||
|
console.warn('*** THE MINECRAFT PROCESS CRASHED ***', a)
|
||||||
|
handle.kill('SIGKILL')
|
||||||
|
})
|
||||||
|
if (onStart) {
|
||||||
|
let stdout = ''
|
||||||
|
handle.stdout.on('data', data => {
|
||||||
|
stdout += data
|
||||||
|
if (stdout.includes('Server started')) onStart()
|
||||||
|
})
|
||||||
|
handle.stdout.pipe(process.stdout)
|
||||||
|
handle.stderr.pipe(process.stdout)
|
||||||
|
}
|
||||||
|
return handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the server and wait for it to be ready, with a timeout
|
||||||
|
async function startServerAndWait (version, withTimeout, options) {
|
||||||
|
let handle
|
||||||
|
await waitFor(async res => {
|
||||||
|
handle = await startServer(version, res, options)
|
||||||
|
}, withTimeout, () => {
|
||||||
|
handle?.kill()
|
||||||
|
throw new Error(`Server did not start on time (${withTimeout}ms, now ${Date.now()})`)
|
||||||
|
})
|
||||||
|
return handle
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startServerAndWait2 (version, withTimeout, options) {
|
||||||
|
try {
|
||||||
|
return await startServerAndWait(version, 1000 * 60, options)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
console.log('^ Tring once more to start server in 10 seconds...')
|
||||||
|
lastHandle?.kill()
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 10000))
|
||||||
|
process.chdir(__dirname)
|
||||||
|
fs.rmSync('bds-' + version, { recursive: true })
|
||||||
|
return await startServerAndWait(version, withTimeout, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!module.parent) {
|
||||||
|
// if (process.argv.length < 3) throw Error('Missing version argument')
|
||||||
|
startServer(process.argv[2] || '1.17.10', null, process.argv[3] ? { 'server-port': process.argv[3], 'online-mode': !!process.argv[4] } : undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { fetchLatestStable, startServer, startServerAndWait, startServerAndWait2 }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue