-
Notifications
You must be signed in to change notification settings - Fork 0
/
mod.ts
117 lines (108 loc) · 3.24 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import {
BufferedReadableStream,
BufferedWritableStream,
readFull,
readFullSync,
readVarUint32LE,
readVarUint32LESync,
Uint8ArrayReader,
Uint8ArrayWriter,
unexpectedEof,
writeBigInt64BESync,
writeInt16BESync,
writeVarUint32LE,
writeVarUint32LESync,
} from "@ud2/binio";
import { abortable } from "./abortable.ts";
import { deadline } from "./deadline.ts";
const encoder = new TextEncoder();
const decoder = new TextDecoder();
function readTextSync(r: Uint8ArrayReader): string | null {
const len = readVarUint32LESync(r);
if (len === null) {
return null;
}
const bytes = readFullSync(r, new Uint8Array(len)) ?? unexpectedEof();
return decoder.decode(bytes);
}
async function readPacket(
r: ReadableStreamBYOBReader,
): Promise<Uint8ArrayReader> {
const len = await readVarUint32LE(r) ?? unexpectedEof();
const bytes = await readFull(r, new Uint8Array(len)) ?? unexpectedEof();
return new Uint8ArrayReader(bytes);
}
function writeTextSync(w: Uint8ArrayWriter, str: string): undefined {
const bytes = encoder.encode(str);
writeVarUint32LESync(w, bytes.length);
w.write(bytes);
}
async function writePacket(
w: WritableStreamDefaultWriter<Uint8Array>,
fn: (p: Uint8ArrayWriter) => unknown,
): Promise<undefined> {
const packet = new Uint8ArrayWriter();
await fn(packet);
const bytes = packet.bytes;
await writeVarUint32LE(w, bytes.length);
await w.write(bytes);
}
export const defaultPort = 25565;
export interface ServerListPingOptions {
hostname: string;
port?: number | undefined;
protocol?: number | undefined;
signal?: AbortSignal | undefined;
}
/** @tags allow-net */
export async function serverListPing(
options: ServerListPingOptions,
): Promise<string> {
let { hostname, port = defaultPort, protocol = -1, signal } = options;
if (port === defaultPort) {
try {
const [record] = await Deno.resolveDns(
`_minecraft._tcp.${hostname}`,
"SRV",
signal && { signal },
);
if (record) {
hostname = record.target;
port = record.port;
}
} catch {
// ignored
}
}
return await deadline(signal, async () => {
using conn = await Deno.connect({ hostname, port });
const bufferedReadable = new BufferedReadableStream(conn.readable);
const bufferedWritable = new BufferedWritableStream(conn.writable);
return await abortable(signal, () => conn.close(), async () => {
const r = bufferedReadable.getReader({ mode: "byob" });
const w = bufferedWritable.getWriter();
await writePacket(w, (p) => {
writeVarUint32LESync(p, 0);
writeVarUint32LESync(p, protocol);
writeTextSync(p, hostname);
writeInt16BESync(p, port);
writeVarUint32LESync(p, 1);
});
await writePacket(w, (p) => {
writeVarUint32LESync(p, 0);
});
await writePacket(w, (p) => {
writeVarUint32LESync(p, 1);
writeBigInt64BESync(p, 0n);
});
await w.write({ type: "flush" });
const rp = await readPacket(r);
if (readVarUint32LESync(rp) ?? unexpectedEof() !== 0) {
throw new TypeError("Expected to receive a Response packet");
}
const json = readTextSync(rp) ?? unexpectedEof();
JSON.parse(json);
return json;
});
});
}