diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7803afb8..0a1ac075 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,8 @@ jobs: strategy: matrix: node-version: [14.x] - + env: + FORCE_BUILD: true steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} diff --git a/HISTORY.md b/HISTORY.md index 71c0f33f..1127ef56 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,7 @@ +## 3.5.0 +* Add 1.17.10 support [#109](https://github.com/PrismarineJS/bedrock-protocol/pull/109) +* You can switch to the JS implementation of raknet by setting `useNativeRaknet: false` in options. + ## 3.4.0 * Initial 1.17 support [#99](https://github.com/PrismarineJS/bedrock-protocol/pull/99) * update connect version based on ping response & fix typings (u9g) [#101](https://github.com/PrismarineJS/bedrock-protocol/pull/101) diff --git a/data/1.17.10/protocol.json b/data/1.17.10/protocol.json index 1fcbc862..c9949421 100644 --- a/data/1.17.10/protocol.json +++ b/data/1.17.10/protocol.json @@ -3780,6 +3780,10 @@ "name": "has_scripts", "type": "bool" }, + { + "name": "force_server_packs", + "type": "bool" + }, { "name": "behaviour_packs", "type": "BehaviourPackInfos" @@ -3787,10 +3791,6 @@ { "name": "texture_packs", "type": "TexturePackInfos" - }, - { - "name": "force_server_packs", - "type": "bool" } ] ], diff --git a/data/latest/proto.yml b/data/latest/proto.yml index 43b79bf9..a2146767 100644 --- a/data/latest/proto.yml +++ b/data/latest/proto.yml @@ -120,14 +120,14 @@ packet_resource_packs_info: must_accept: bool # If scripting is enabled. has_scripts: bool + # ForcingServerPacks is currently an unclear field. + force_server_packs: bool # A list of behaviour packs that the client needs to download before joining the server. # All of these behaviour packs will be applied together. behaviour_packs: BehaviourPackInfos # A list of resource packs that the client needs to download before joining the server. # The order of these resource packs is not relevant in this packet. It is however important in the Resource Pack Stack packet. texture_packs: TexturePackInfos - # ForcingServerPacks is currently an unclear field. - force_server_packs: bool packet_resource_pack_stack: !id: 0x07 diff --git a/docs/API.md b/docs/API.md index 051ea095..35db115d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -19,7 +19,7 @@ Returns a `Client` instance and connects to the server. | profilesFolder | *optional* | Where to store cached authentication tokens. Defaults to .minecraft, or the node_modules folder if not found. | | autoInitPlayer | *optional* | default to true, If we should send SetPlayerInitialized to the server after getting play_status spawn. | | skipPing | *optional* | Whether pinging the server to check its version should be skipped. | - +| useNativeRaknet | *optional* | Whether to use the C++ version of RakNet. Set to false to use JS. | ## be.createServer(options) : Server diff --git a/index.d.ts b/index.d.ts index d59c8fd2..3e9d8014 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,7 +1,7 @@ import EventEmitter from "events" declare module "bedrock-protocol" { - type Version = '1.17.0' | '1.16.220' | '1.16.210' | '1.16.201' + type Version = '1.17.10' | '1.17.0' | '1.16.220' | '1.16.210' | '1.16.201' enum title { MinecraftNintendoSwitch, MinecraftJava } @@ -15,7 +15,12 @@ declare module "bedrock-protocol" { port?: number // For the client, if we should login with Microsoft/Xbox Live. // For the server, if we should verify client's authentication with Xbox Live. - offline?: boolean + offline?: boolean, + + // Whether or not to use C++ version of RakNet + useNativeRaknet?: boolean, + // If using JS implementation of RakNet, should we use workers? (This only affects the client) + useRaknetWorker?: boolean } export interface ClientOptions extends Options { diff --git a/package.json b/package.json index 50e85f41..825cde1f 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "debug": "^4.3.1", "jose-node-cjs-runtime": "^3.12.1", "jsonwebtoken": "^8.5.1", - "jsp-raknet": "^2.1.0", + "jsp-raknet": "^2.1.3", "minecraft-folder-path": "^1.1.0", "node-fetch": "^2.6.1", "prismarine-nbt": "^1.5.0", diff --git a/src/client.js b/src/client.js index 068dce56..2796546a 100644 --- a/src/client.js +++ b/src/client.js @@ -1,6 +1,5 @@ const { ClientStatus, Connection } = require('./connection') const { createDeserializer, createSerializer } = require('./transforms/serializer') -const { RakClient } = require('./rak') const { serialize, isDebug } = require('./datatypes/util') const debug = require('debug')('minecraft-protocol') const Options = require('./options') @@ -21,6 +20,9 @@ class Client extends Connection { super() this.options = { ...Options.defaultOptions, ...options } this.validateOptions() + + const { RakClient } = require('./rak')(this.options.useNativeRaknet) + this.serializer = createSerializer(this.options.version) this.deserializer = createDeserializer(this.options.version) @@ -30,7 +32,7 @@ class Client extends Connection { const host = this.options.host const port = this.options.port - this.connection = new RakClient({ useWorkers: true, host, port }) + this.connection = new RakClient({ useWorkers: this.options.useRaknetWorkers, host, port }) this.startGameData = {} this.clientRuntimeId = null diff --git a/src/createClient.js b/src/createClient.js index f8e205a6..e79b9a70 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -1,5 +1,5 @@ const { Client } = require('./client') -const { RakClient } = require('./rak') +const { RakClient } = require('./rak')(true) const assert = require('assert') const advertisement = require('./server/advertisement') const { sleep } = require('./datatypes/util') diff --git a/src/options.js b/src/options.js index 21472527..32d7d93a 100644 --- a/src/options.js +++ b/src/options.js @@ -21,7 +21,11 @@ const defaultOptions = { // If true, do not authenticate with Xbox Live offline: false, // Milliseconds to wait before aborting connection attempt - connectTimeout: 9000 + connectTimeout: 9000, + // Whether or not to use C++ version of RakNet + useNativeRaknet: true, + // If using JS implementation of RakNet, should we use workers? (This only affects the client) + useRaknetWorkers: true } module.exports = { defaultOptions, MIN_VERSION, CURRENT_VERSION, Versions } diff --git a/src/rak.js b/src/rak.js index d8e99ee2..32a3b31d 100644 --- a/src/rak.js +++ b/src/rak.js @@ -1,12 +1,23 @@ const { EventEmitter } = require('events') const ConnWorker = require('./rakWorker') const { waitFor } = require('./datatypes/util') -// TODO: better way to switch, via an option -try { - var { Client, Server, PacketPriority, PacketReliability } = require('raknet-native') // eslint-disable-line -} catch (e) { - var { Client, Server, EncapsulatedPacket, Reliability } = require('jsp-raknet') // eslint-disable-line - console.debug('[raknet] native not found, using js', e) + +let Client, Server, PacketPriority, EncapsulatedPacket, PacketReliability, Reliability + +module.exports = nativeRaknet => { + if (nativeRaknet) { + try { + ({ Client, Server, PacketPriority, PacketReliability } = require('raknet-native')) + return { RakServer: RakNativeServer, RakClient: RakNativeClient } + } catch (e) { + ({ Client, Server, EncapsulatedPacket, Reliability } = require('jsp-raknet')) + console.debug('[raknet] native not found, using js', e) + console.debug('You can suppress the error above by disabling `useNativeRaknet` in your options') + } + } else { + ({ Client, Server, EncapsulatedPacket, Reliability } = require('jsp-raknet')) + } + return { RakServer: RakJsServer, RakClient: RakJsClient } } class RakNativeClient extends EventEmitter { @@ -118,9 +129,10 @@ class RakJsClient extends EventEmitter { this.sendReliable = this.workerSendReliable } else { this.connect = this.plainConnect - this.close = this.plainClose + this.close = reason => this.raknet.close(reason) this.sendReliable = this.plainSendReliable } + this.pongCb = null } workerConnect (host = this.options.host, port = this.options.port) { @@ -137,6 +149,8 @@ class RakJsClient extends EventEmitter { this.onEncapsulated(ecapsulated, address.hash) break } + case 'pong': + this.pongCb?.(evt.args) } }) } @@ -165,12 +179,21 @@ class RakJsClient extends EventEmitter { if (immediate) this.connection.sendQueue() } - plainClose (reason) { - this.raknet.close(reason) - } - - ping () { - // TODO + async ping (timeout = 1000) { + if (this.worker) { + this.worker.postMessage({ type: 'ping' }) + return waitFor(res => { + this.pongCb = data => res(data) + }, timeout, () => { throw new Error('Ping timed out') }) + } else { + if (!this.raknet) this.raknet = new Client(this.options.host, this.options.port) + return waitFor(res => { + this.raknet.ping(data => { + this.raknet.close() + res(data) + }) + }, timeout, () => { throw new Error('Ping timed out') }) + } } } @@ -207,9 +230,11 @@ class RakJsServer extends EventEmitter { this.raknet.on('closeConnection', this.onCloseConnection) this.raknet.on('encapsulated', this.onEncapsulated) } -} -module.exports = { - RakClient: PacketPriority ? RakNativeClient : RakJsClient, - RakServer: PacketPriority ? RakNativeServer : RakJsServer + close () { + // Allow some time for the final packets to come in/out + setTimeout(() => { + this.raknet.close() + }, 40) + } } diff --git a/src/rakWorker.js b/src/rakWorker.js index f6e40465..06cd736f 100644 --- a/src/rakWorker.js +++ b/src/rakWorker.js @@ -53,6 +53,11 @@ function main () { } } else if (evt.type === 'close') { raknet.close() + process.exit(0) + } else if (evt.type === 'ping') { + raknet.ping((args) => { + parentPort.postMessage({ type: 'pong', args }) + }) } }) } diff --git a/src/server.js b/src/server.js index eb40a4e8..e13ca921 100644 --- a/src/server.js +++ b/src/server.js @@ -1,7 +1,6 @@ const { EventEmitter } = require('events') const { createDeserializer, createSerializer } = require('./transforms/serializer') const { Player } = require('./serverPlayer') -const { RakServer } = require('./rak') const { sleep } = require('./datatypes/util') const { ServerAdvertisement } = require('./server/advertisement') const Options = require('./options') @@ -12,6 +11,9 @@ class Server extends EventEmitter { super() this.options = { ...Options.defaultOptions, ...options } this.validateOptions() + + this.RakServer = require('./rak')(this.options.useNativeRaknet).RakServer + this.serializer = createSerializer(this.options.version) this.deserializer = createDeserializer(this.options.version) this.advertisement = new ServerAdvertisement(this.options.motd) @@ -66,7 +68,7 @@ class Server extends EventEmitter { } async listen (host = this.options.host, port = this.options.port) { - this.raknet = new RakServer({ host, port }, this) + this.raknet = new this.RakServer({ host, port }, this) try { await this.raknet.listen() } catch (e) { diff --git a/test/internal.js b/test/internal.js index ddfd0252..fd318c46 100644 --- a/test/internal.js +++ b/test/internal.js @@ -196,7 +196,7 @@ async function requestChunks (x, z, radius) { return chunks } -async function timedTest (version, timeout = 1000 * 120) { +async function timedTest (version, timeout = 1000 * 220) { await waitFor((res) => { startTest(version, res) }, timeout, () => { diff --git a/test/internal.test.js b/test/internal.test.js index b2a901b7..32207e30 100644 --- a/test/internal.test.js +++ b/test/internal.test.js @@ -5,7 +5,8 @@ const { proxyTest } = require('./proxy') const { Versions } = require('../src/options') describe('internal client/server test', function () { - this.timeout(240 * 1000) + const vcount = Object.keys(Versions).length + this.timeout(vcount * 80 * 1000) for (const version in Versions) { it('connects ' + version, async () => { diff --git a/test/vanilla.test.js b/test/vanilla.test.js index 2670223b..9cea3598 100644 --- a/test/vanilla.test.js +++ b/test/vanilla.test.js @@ -4,7 +4,8 @@ const { clientTest } = require('./vanilla') const { Versions } = require('../src/options') describe('vanilla server test', function () { - this.timeout(220 * 1000) + const vcount = Object.keys(Versions).length + this.timeout(vcount * 80 * 1000) for (const version in Versions) { it('client spawns ' + version, async () => { diff --git a/tools/startVanillaServer.js b/tools/startVanillaServer.js index 32d0e0ab..8e347944 100644 --- a/tools/startVanillaServer.js +++ b/tools/startVanillaServer.js @@ -82,6 +82,10 @@ async function startServer (version, onStart, options = {}) { await download(os, version, options.path) configure(options) const handle = 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 => {