@vaitosoi/minejs 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -8
- package/assets/minecraft/26.2/packets.json +27 -10
- package/dist/base/buffer.d.ts +2 -0
- package/dist/base/buffer.d.ts.map +1 -0
- package/dist/base/buffer.js +12 -0
- package/dist/base/buffer.js.map +1 -0
- package/dist/base/error.d.ts +33 -6
- package/dist/base/error.d.ts.map +1 -1
- package/dist/base/error.js +74 -11
- package/dist/base/error.js.map +1 -1
- package/dist/base/http.d.ts +2 -0
- package/dist/base/http.d.ts.map +1 -0
- package/dist/base/http.js +14 -0
- package/dist/base/http.js.map +1 -0
- package/dist/base/math.d.ts +22 -1
- package/dist/base/math.d.ts.map +1 -1
- package/dist/base/math.js +49 -2
- package/dist/base/math.js.map +1 -1
- package/dist/client/auth.d.ts +41 -7
- package/dist/client/auth.d.ts.map +1 -1
- package/dist/client/auth.js +96 -39
- package/dist/client/auth.js.map +1 -1
- package/dist/client/client.d.ts +50 -5
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +47 -18
- package/dist/client/client.js.map +1 -1
- package/dist/client/tcp.d.ts +1 -5
- package/dist/client/tcp.d.ts.map +1 -1
- package/dist/client/tcp.js +8 -21
- package/dist/client/tcp.js.map +1 -1
- package/dist/message/link.d.ts +10 -0
- package/dist/message/link.d.ts.map +1 -0
- package/dist/message/link.js +22 -0
- package/dist/message/link.js.map +1 -0
- package/dist/message/sigCache.d.ts +13 -0
- package/dist/message/sigCache.d.ts.map +1 -0
- package/dist/message/sigCache.js +26 -0
- package/dist/message/sigCache.js.map +1 -0
- package/dist/packet/decoder.d.ts +13 -0
- package/dist/packet/decoder.d.ts.map +1 -1
- package/dist/packet/decoder.js +21 -0
- package/dist/packet/decoder.js.map +1 -1
- package/dist/packet/dispatcher.d.ts +8 -1
- package/dist/packet/dispatcher.d.ts.map +1 -1
- package/dist/packet/dispatcher.js +142 -30
- package/dist/packet/dispatcher.js.map +1 -1
- package/dist/physics/player.d.ts +17 -23
- package/dist/physics/player.d.ts.map +1 -1
- package/dist/physics/player.js +59 -61
- package/dist/physics/player.js.map +1 -1
- package/dist/version/codec.d.ts.map +1 -1
- package/dist/version/codec.js +28 -7
- package/dist/version/codec.js.map +1 -1
- package/dist/version/registry.d.ts +1 -1
- package/dist/version/registry.d.ts.map +1 -1
- package/dist/version/registry.js +2 -7
- package/dist/version/registry.js.map +1 -1
- package/dist/world/state.d.ts +26 -2
- package/dist/world/state.d.ts.map +1 -1
- package/dist/world/state.js +58 -3
- package/dist/world/state.js.map +1 -1
- package/package.json +1 -1
- package/src/base/buffer.ts +12 -0
- package/src/base/error.ts +63 -10
- package/src/base/http.ts +10 -0
- package/src/base/math.ts +55 -3
- package/src/client/auth.ts +130 -52
- package/src/client/client.ts +96 -21
- package/src/client/tcp.ts +10 -27
- package/src/message/link.ts +17 -0
- package/src/message/sigCache.ts +25 -0
- package/src/packet/decoder.ts +21 -0
- package/src/packet/dispatcher.ts +169 -35
- package/src/physics/player.ts +83 -58
- package/src/version/codec.ts +32 -11
- package/src/version/registry.ts +2 -7
- package/src/world/state.ts +72 -4
- package/test/client/index.ts +27 -20
package/src/version/codec.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import z
|
|
2
|
-
import { MissingField, MissingPacketField, UnexpectedValue } from "../base/error";
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { MissingField, MissingPacketField, NotImplemented, UnexpectedValue } from "../base/error";
|
|
3
3
|
import { BinaryDecoder } from "../packet/decoder";
|
|
4
4
|
import { ConnectionState, SharedState } from "../world/state";
|
|
5
5
|
import { FieldNode, PacketObject, PacketRegistry, VersionDefinitions } from "./registry";
|
|
6
|
-
import { Dispatcher } from "../packet/dispatcher";
|
|
7
6
|
import { BinaryEncoder } from "../packet/encoder";
|
|
8
7
|
|
|
9
8
|
const TCPStateMapping: Record<ConnectionState, keyof z.infer<typeof VersionDefinitions> | null> = {
|
|
@@ -22,7 +21,7 @@ export interface EncodeResult {
|
|
|
22
21
|
export class VersionCodec {
|
|
23
22
|
constructor(private readonly state: SharedState) { }
|
|
24
23
|
|
|
25
|
-
public consumePacket: (packetId: string, data: object) => void = (
|
|
24
|
+
public consumePacket: (packetId: string, data: object) => void = () => { throw new NotImplemented(); };
|
|
26
25
|
|
|
27
26
|
public handlePacket(packetId: number, decoder: BinaryDecoder) {
|
|
28
27
|
const mappedState = TCPStateMapping[this.state.state];
|
|
@@ -31,13 +30,25 @@ export class VersionCodec {
|
|
|
31
30
|
if (!packet) return; // console.log("no packet");
|
|
32
31
|
|
|
33
32
|
const { name, structure, skipForNow } = packet;
|
|
33
|
+
|
|
34
34
|
if (skipForNow) return;
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
const resolvedObject: Record<string, any> = {};
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
if (Object.keys(structure).length > 0)
|
|
38
|
+
for (const [name, field] of Object.entries(structure))
|
|
39
|
+
try {
|
|
40
|
+
resolvedObject[name] = this.readField(packetId, resolvedObject, name, field, decoder);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
if (err instanceof NotImplemented) return;
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
if (this.state.clientOptions.debug?.packetLogger)
|
|
46
|
+
console.dir({
|
|
47
|
+
dir: "to client",
|
|
48
|
+
state: mappedState,
|
|
49
|
+
name,
|
|
50
|
+
data: resolvedObject,
|
|
51
|
+
});
|
|
41
52
|
|
|
42
53
|
this.consumePacket(`${TCPStateMapping[this.state.state]}:${name}`, resolvedObject);
|
|
43
54
|
}
|
|
@@ -48,6 +59,14 @@ export class VersionCodec {
|
|
|
48
59
|
const packet = PacketRegistry.getPacket(mappedState, "serverbound", packetId);
|
|
49
60
|
if (!packet) throw new UnexpectedValue("valid serverbound packet id", packetId.toString());
|
|
50
61
|
|
|
62
|
+
if (this.state.clientOptions.debug?.packetLogger)
|
|
63
|
+
console.dir({
|
|
64
|
+
dir: "to server",
|
|
65
|
+
state: mappedState,
|
|
66
|
+
id: packetId,
|
|
67
|
+
data,
|
|
68
|
+
});
|
|
69
|
+
|
|
51
70
|
const { name: packetName, structure } = packet;
|
|
52
71
|
let encoder = new BinaryEncoder();
|
|
53
72
|
for (const [name, field] of Object.entries(structure)) {
|
|
@@ -98,7 +117,7 @@ export class VersionCodec {
|
|
|
98
117
|
let length;
|
|
99
118
|
if (!isNaN(Number(field.length)))
|
|
100
119
|
length = Number(field.length);
|
|
101
|
-
else
|
|
120
|
+
else
|
|
102
121
|
length = readObject[field.length];
|
|
103
122
|
if (length === undefined) throw new MissingPacketField(packetId, fieldName, field.length.toString());
|
|
104
123
|
return decoder.readArray(length, (decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
@@ -114,6 +133,7 @@ export class VersionCodec {
|
|
|
114
133
|
}
|
|
115
134
|
case "angle": return decoder.readAngle();
|
|
116
135
|
case "id_or_x": return decoder.readIdOrX((decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
136
|
+
case "id_set": return decoder.readIdSet();
|
|
117
137
|
case "chat_type_decoration": return decoder.readChatTypeDecoration();
|
|
118
138
|
case "chat_type": return decoder.readChatType();
|
|
119
139
|
case "enum": return this.readField(packetId, readObject, fieldName, field.subType, decoder);
|
|
@@ -144,6 +164,7 @@ export class VersionCodec {
|
|
|
144
164
|
case "bitset": return decoder.readPrefixedArray((decoder) => decoder.readLong());
|
|
145
165
|
case "teleport_flags": return decoder.readTeleportFlag();
|
|
146
166
|
case "null": return null;
|
|
167
|
+
case "not_implemented": throw new NotImplemented();
|
|
147
168
|
case "switch": {
|
|
148
169
|
if (!(field.dependsOn in readObject)) throw new MissingPacketField(packetId, fieldName, field.dependsOn);
|
|
149
170
|
const dependencyValue = readObject[field.dependsOn];
|
|
@@ -185,7 +206,7 @@ export class VersionCodec {
|
|
|
185
206
|
// return encoder.writeArray(length, (decoder) => this.readField(packetId, readObject, fieldName, field.subType, decoder));
|
|
186
207
|
// }
|
|
187
208
|
case "prefixed_array": {
|
|
188
|
-
const length = metadata["length"];
|
|
209
|
+
const length = metadata["length"] || field.length;
|
|
189
210
|
if (!length) throw new MissingField("length", "prefixed array metadata object");
|
|
190
211
|
if (!Array.isArray(value))
|
|
191
212
|
throw new UnexpectedValue("an array for prefixed array", typeof value);
|
package/src/version/registry.ts
CHANGED
|
@@ -125,9 +125,9 @@ export const SupportTypes = z.enum([
|
|
|
125
125
|
"fixed_point",
|
|
126
126
|
"angle",
|
|
127
127
|
"id_or_x",
|
|
128
|
+
"id_set",
|
|
128
129
|
"chat_type_decoration",
|
|
129
130
|
"chat_type",
|
|
130
|
-
"chunk_data",
|
|
131
131
|
"enum",
|
|
132
132
|
"json_text",
|
|
133
133
|
"not_implemented",
|
|
@@ -301,11 +301,6 @@ export class PacketRegistry {
|
|
|
301
301
|
this.nameToId[stateKey]![typeKey] = {};
|
|
302
302
|
// eslint-disable-next-line prefer-const
|
|
303
303
|
for (let [key, value] of Object.entries(type) as [string | number, any]) {
|
|
304
|
-
if (typeof key === "string" && !isNaN(parseInt(key))) {
|
|
305
|
-
type[parseInt(key)] = value;
|
|
306
|
-
delete type[key];
|
|
307
|
-
key = parseInt(key);
|
|
308
|
-
} // transform all string key into numberic key
|
|
309
304
|
this.nameToId[stateKey]![typeKey]![value.name] = key;
|
|
310
305
|
}
|
|
311
306
|
}
|
|
@@ -328,6 +323,6 @@ export class PacketRegistry {
|
|
|
328
323
|
if (isNaN(numbericId))
|
|
329
324
|
throw new UnexpectedValue("valid packet id", id.toString(), "name or number or hex");
|
|
330
325
|
else
|
|
331
|
-
return this.definition[state][type][numbericId.toString()];
|
|
326
|
+
return this.definition[state][type]["0x" + numbericId.toString(16).toUpperCase()];
|
|
332
327
|
}
|
|
333
328
|
}
|
package/src/world/state.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Cipheriv, Decipheriv } from "node:crypto";
|
|
2
2
|
import { If } from "../base/typing";
|
|
3
3
|
import { Angle, BaseVec3 } from "../physics/direction";
|
|
4
|
-
import { ClientNotReady } from "../base/error";
|
|
4
|
+
import { AuthRelatedNotFound, ClientNotReady } from "../base/error";
|
|
5
5
|
import { ClientEvents, ClientOption } from "../client/client";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { AuthClient } from "../client/auth";
|
|
7
|
+
import { MessageLink } from "../message/link";
|
|
8
|
+
import { SignatureCache } from "../message/sigCache";
|
|
8
9
|
|
|
9
10
|
export interface Server {
|
|
10
11
|
knownPacks?: ServerKnownPack
|
|
@@ -146,18 +147,36 @@ export class SharedState<IsReady extends boolean = boolean> {
|
|
|
146
147
|
public status: ClientStatus = ClientStatus.Disconnected;
|
|
147
148
|
public compressionThreshold: number = 0;
|
|
148
149
|
|
|
150
|
+
// Auth stuffs
|
|
151
|
+
public authClient: AuthClient | undefined = undefined;
|
|
152
|
+
public playerUUID: string | undefined = undefined;
|
|
153
|
+
public publicKey: Buffer | undefined = undefined;
|
|
154
|
+
public signature: Buffer | undefined = undefined;
|
|
155
|
+
public signatureExpireTime: number | undefined = undefined;
|
|
156
|
+
// eslint-disable-next-line no-undef
|
|
157
|
+
private signatureTimeoutInstance: NodeJS.Timeout | undefined = undefined;
|
|
158
|
+
|
|
149
159
|
public sharedSecret: Buffer | undefined = undefined;
|
|
150
160
|
public cipher: Cipheriv | undefined = undefined;
|
|
151
161
|
public decipher: Decipheriv | undefined = undefined;
|
|
162
|
+
public useEncryption: boolean = false;
|
|
163
|
+
public sessionID: Buffer | undefined = undefined;
|
|
152
164
|
|
|
153
165
|
public server: Server | undefined = undefined;
|
|
154
166
|
public registry: Record<string, ServerRegistryEntry[]> | undefined = undefined;
|
|
155
167
|
public world: If<IsReady, ServerWorld> = null as any;
|
|
156
168
|
public player: If<IsReady, ClientPlayer> = null as any;
|
|
157
169
|
|
|
170
|
+
/**
|
|
171
|
+
* For maintaining message order.
|
|
172
|
+
*
|
|
173
|
+
* A UUID map to a MessageLink.
|
|
174
|
+
*/
|
|
175
|
+
public messageLinks: Record<string, MessageLink> | undefined = undefined;
|
|
176
|
+
public messageSignatureCache: SignatureCache | undefined = undefined;
|
|
177
|
+
|
|
158
178
|
constructor(
|
|
159
179
|
public clientOptions: ClientOption,
|
|
160
|
-
public getUuid: () => Buffer,
|
|
161
180
|
) { }
|
|
162
181
|
|
|
163
182
|
public isReady(): this is SharedState<true> {
|
|
@@ -173,15 +192,64 @@ export class SharedState<IsReady extends boolean = boolean> {
|
|
|
173
192
|
this.mutationQueue = [];
|
|
174
193
|
this.eventQueue = [];
|
|
175
194
|
this.sendQueue = [];
|
|
195
|
+
|
|
176
196
|
this.state = ConnectionState.Disconnected;
|
|
197
|
+
this.status = ClientStatus.Disconnected;
|
|
177
198
|
this.compressionThreshold = 0;
|
|
199
|
+
|
|
200
|
+
this.authClient = undefined;
|
|
201
|
+
this.playerUUID = undefined;
|
|
202
|
+
this.publicKey = undefined;
|
|
203
|
+
this.signature = undefined;
|
|
204
|
+
|
|
178
205
|
this.sharedSecret = undefined;
|
|
179
206
|
this.cipher = undefined;
|
|
180
207
|
this.decipher = undefined;
|
|
208
|
+
this.useEncryption = false;
|
|
209
|
+
this.sessionID = undefined;
|
|
210
|
+
this.signatureTimeoutInstance = undefined;
|
|
211
|
+
|
|
181
212
|
this.server = undefined;
|
|
182
213
|
this.registry = undefined;
|
|
183
214
|
this.world = undefined as any;
|
|
184
215
|
this.player = undefined as any;
|
|
216
|
+
|
|
217
|
+
if (this.clientOptions.shouldVerifyMessageSignature)
|
|
218
|
+
this.messageSignatureCache = SignatureCache.default();
|
|
219
|
+
else
|
|
220
|
+
this.messageSignatureCache = undefined;
|
|
221
|
+
if (this.clientOptions.shouldVerifyMessageOrder)
|
|
222
|
+
this.messageLinks = {};
|
|
223
|
+
else
|
|
224
|
+
this.messageLinks = undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
public getSignature() {
|
|
228
|
+
if (this.publicKey && this.signature)
|
|
229
|
+
return {
|
|
230
|
+
publicKey: this.publicKey,
|
|
231
|
+
signature: this.signature
|
|
232
|
+
};
|
|
233
|
+
throw new AuthRelatedNotFound("public key and signature");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private async fetchSignature() {
|
|
237
|
+
if (!this.authClient)
|
|
238
|
+
throw new AuthRelatedNotFound("instance of class AuthClient");
|
|
239
|
+
const { publicKey, signature, expiresAt } = await this.authClient.getKeyPair();
|
|
240
|
+
this.publicKey = Buffer.from(publicKey);
|
|
241
|
+
this.signature = Buffer.from(signature);
|
|
242
|
+
return expiresAt;
|
|
243
|
+
}
|
|
244
|
+
public async startSignatureLoop() {
|
|
245
|
+
const expiresAt = await this.fetchSignature();
|
|
246
|
+
this.signatureTimeoutInstance = setTimeout(
|
|
247
|
+
async () => await this.startSignatureLoop(),
|
|
248
|
+
expiresAt - Date.now() - 10_000
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
public stopSignatureLoop() {
|
|
252
|
+
clearTimeout(this.signatureTimeoutInstance);
|
|
185
253
|
}
|
|
186
254
|
|
|
187
255
|
public enqueueMutation(mutation: MutateState) { this.mutationQueue.push(mutation); }
|
package/test/client/index.ts
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Input, Client } from "../../src/index";
|
|
2
2
|
|
|
3
3
|
const client = new Client({
|
|
4
4
|
playerName: "bot",
|
|
5
5
|
host: "localhost",
|
|
6
6
|
port: 25565,
|
|
7
7
|
version: "26.2",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
isOffline: true,
|
|
8
|
+
debug: {
|
|
9
|
+
packetLogger: true
|
|
10
|
+
},
|
|
12
11
|
loadRegistry: false,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
auth: {
|
|
13
|
+
client_id: "d86254d8-edf7-4640-90eb-643c99af188e",
|
|
14
|
+
method: "loopback",
|
|
15
|
+
openBrowser: true,
|
|
16
|
+
port: 12345
|
|
17
|
+
},
|
|
18
|
+
shouldVerifyMessageOrder: true,
|
|
19
|
+
shouldVerifyMessageSignature: true
|
|
18
20
|
});
|
|
19
21
|
client.connect();
|
|
22
|
+
client.hold(Input.Jump);
|
|
20
23
|
client.on("disconnect", (...args) => console.dir({ name: "disconnect", args }, { depth: null }));
|
|
21
24
|
client.on("disconnectRaw", (...args) => console.dir({ name: "disconnectRaw", args }, { depth: null }));
|
|
22
25
|
// client.on("loadChunk", (...args) => console.dir({ name: "loadChunk", args }, { depth: null }));
|
|
@@ -34,18 +37,22 @@ client.on("message", (message) => {
|
|
|
34
37
|
if (!message.content.startsWith("_")) return;
|
|
35
38
|
const args = message.content.slice(1).split(" ");
|
|
36
39
|
switch (args[0]) {
|
|
37
|
-
case "w": client.hold(
|
|
38
|
-
case "sw": client.release(
|
|
40
|
+
case "w": client.hold(Input.Forward); break;
|
|
41
|
+
case "sw": client.release(Input.Forward); break;
|
|
39
42
|
case "ws":
|
|
40
|
-
client.hold(
|
|
43
|
+
client.hold(Input.Forward);
|
|
41
44
|
setTimeout(() => client.disconnect(), 2000);
|
|
42
45
|
break;
|
|
43
|
-
case "s": client.hold(
|
|
44
|
-
case "ss": client.release(
|
|
45
|
-
case "a": client.hold(
|
|
46
|
-
case "sa": client.release(
|
|
47
|
-
case "d": client.hold(
|
|
48
|
-
case "sd": client.release(
|
|
46
|
+
case "s": client.hold(Input.Backward); break;
|
|
47
|
+
case "ss": client.release(Input.Backward); break;
|
|
48
|
+
case "a": client.hold(Input.Left); break;
|
|
49
|
+
case "sa": client.release(Input.Left); break;
|
|
50
|
+
case "d": client.hold(Input.Right); break;
|
|
51
|
+
case "sd": client.release(Input.Right); break;
|
|
52
|
+
case "j": client.hold(Input.Jump); break;
|
|
53
|
+
case "sj": client.release(Input.Jump); break;
|
|
54
|
+
case "run": client.hold(Input.Sprint); break;
|
|
55
|
+
case "srun": client.release(Input.Sprint); break;
|
|
49
56
|
case "stop": client.stopMoving(); break;
|
|
50
57
|
|
|
51
58
|
case "at": {
|
|
@@ -56,7 +63,7 @@ client.on("message", (message) => {
|
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
case "disconnect":
|
|
59
|
-
case "exit":
|
|
66
|
+
case "exit":
|
|
60
67
|
client.disconnect();
|
|
61
68
|
break;
|
|
62
69
|
}
|