diff --git a/README.md b/README.md index 0baeb7e..3fa5607 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,18 @@ let response = await client.pingLegacyPost14('localhost', 25565, {signal: AbortS let response = await client.pingLegacyPre14('localhost', 25565, {signal: AbortSignal.timeout(5000)}); ``` -Both legacy ping versions will return a [`LegacyStatus`](src/JavaPing/Status/LegacyStatus.js) object. +#### Ping a server that supports either of the two legacy ping versions +Some custom server software seems to not respond to pre 1.4 pings, but will instead only respond to 1.4+ pings. +The only currently known instance of this is Better Than Adventure. If you are trying to ping a server that may or +may not support pre 1.4 pings, you can use the following code: +```js +let response = await client.pingLegacyUniversal('localhost', 25565, {signal: AbortSignal.timeout(5000)}); +``` + +Note that this weird hack involves sending the first half of a packet, then waiting for up to 500ms if the servers +responds, and if it does not, sending the second half of the packet. It may therefore run into problems if the timing is off. + +All legacy ping versions will return a [`LegacyStatus`](src/JavaPing/Status/LegacyStatus.js) object. Note that for pre 1.4 pings, this object will not include the server version name and protocol version, as this information was not included in the response packets before Minecraft 1.4. diff --git a/package.json b/package.json index 97b77bf..91e338c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "craftping", "type": "module", - "version": "1.0.3", + "version": "1.1.0", "main": "index.js", "repository": "github:aternosorg/craftping", "scripts": { diff --git a/src/JavaPing/JavaPing.js b/src/JavaPing/JavaPing.js index 3c8f4a9..1493563 100644 --- a/src/JavaPing/JavaPing.js +++ b/src/JavaPing/JavaPing.js @@ -69,6 +69,41 @@ export default class JavaPing extends TCPSocket { return new LegacyStatus().fromPre14String(response.getMessage()); } + /** + * @param {PingOptions} options + * @return {Promise} + */ + async pingLegacyUniversal(options = {}) { + await this.write(Buffer.from([0xfe])); + this.signal?.throwIfAborted(); + + let usePost14 = false; + try { + await this.waitForData(1, AbortSignal.timeout(500)); + } catch (e) { + usePost14 = true; + } + this.signal?.throwIfAborted(); + + if (usePost14) { + await this.write(Buffer.from([0x01])); + this.signal?.throwIfAborted(); + await this.write(new LegacyPingHostPluginMessage() + .setProtocolVersion(options.protocolVersion ?? null) + .setHostname(options.hostname ?? this.address) + .setPort(options.port ?? this.port).write()); + this.signal?.throwIfAborted(); + } + + let response = await this.readPacket(LegacyKick); + let message = response.getMessage(); + if (message.startsWith("ยง1")) { + return new LegacyStatus().fromPost14String(response.getMessage()); + } else { + return new LegacyStatus().fromPre14String(response.getMessage()); + } + } + /** * Read the next packet from the socket * diff --git a/src/JavaPing/JavaPingClient.js b/src/JavaPing/JavaPingClient.js index d57827e..8534e3a 100644 --- a/src/JavaPing/JavaPingClient.js +++ b/src/JavaPing/JavaPingClient.js @@ -123,4 +123,30 @@ export default class JavaPingClient { await ping.close(); return response; } + + /** + * Send a weird hybrid ping that can be understood by both pre 1.4 servers, and custom servers that + * somehow only support the 1.4 - 1.6 protocol + * + * Note that, in some cases, this may result in pre 1.4 servers logging error messages. + * + * @param {string} address + * @param {number} port + * @param {PingOptions} options + * @return {Promise} + */ + async pingLegacyUniversal(address, port, options = {}) { + [address, port] = await this.resolveSrv(address, port, options); + let ping = new JavaPing(address, port, options.signal); + await ping.connect(); + let response; + try { + response = await ping.pingLegacyUniversal(options); + } catch (e) { + await ping.destroy(); + throw e; + } + await ping.close(); + return response; + } } diff --git a/src/TCPSocket/TCPSocket.js b/src/TCPSocket/TCPSocket.js index e9d5ffa..97f5961 100644 --- a/src/TCPSocket/TCPSocket.js +++ b/src/TCPSocket/TCPSocket.js @@ -149,9 +149,10 @@ export default class TCPSocket extends EventEmitter { /** * @param {number} length + * @param {?AbortSignal} signal * @return {Promise} */ - waitForData(length) { + waitForData(length, signal = null) { if (this.waitForDataPromise !== null) { throw new NetworkError("Already waiting for data"); } @@ -163,6 +164,14 @@ export default class TCPSocket extends EventEmitter { this.resolveDataPromise(); return promise.getPromise(); } + + signal?.addEventListener("abort", () => { + if (this.waitForDataPromise === promise) { + this.waitForDataPromise = null; + promise.reject(new NetworkError("Operation was aborted")); + } + }); + this.socket.resume(); return promise.getPromise(); }