@vaitosoi/minejs 0.2.2 → 0.4.0

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.
Files changed (149) hide show
  1. package/README.md +1 -1
  2. package/assets/minecraft/26.2/packets.json +4758 -0
  3. package/dist/base/buffer.d.ts +2 -0
  4. package/dist/base/buffer.d.ts.map +1 -0
  5. package/dist/base/buffer.js +12 -0
  6. package/dist/base/buffer.js.map +1 -0
  7. package/dist/base/error.d.ts +59 -3
  8. package/dist/base/error.d.ts.map +1 -1
  9. package/dist/base/error.js +123 -7
  10. package/dist/base/error.js.map +1 -1
  11. package/dist/base/http.d.ts +2 -0
  12. package/dist/base/http.d.ts.map +1 -0
  13. package/dist/base/http.js +14 -0
  14. package/dist/base/http.js.map +1 -0
  15. package/dist/base/math.d.ts +22 -1
  16. package/dist/base/math.d.ts.map +1 -1
  17. package/dist/base/math.js +49 -2
  18. package/dist/base/math.js.map +1 -1
  19. package/dist/base/typing.d.ts +7 -1
  20. package/dist/base/typing.d.ts.map +1 -1
  21. package/dist/client/auth.d.ts +41 -7
  22. package/dist/client/auth.d.ts.map +1 -1
  23. package/dist/client/auth.js +96 -39
  24. package/dist/client/auth.js.map +1 -1
  25. package/dist/client/client.d.ts +73 -17
  26. package/dist/client/client.d.ts.map +1 -1
  27. package/dist/client/client.js +111 -44
  28. package/dist/client/client.js.map +1 -1
  29. package/dist/client/tcp.d.ts +17 -206
  30. package/dist/client/tcp.d.ts.map +1 -1
  31. package/dist/client/tcp.js +37 -715
  32. package/dist/client/tcp.js.map +1 -1
  33. package/dist/index.d.ts +12 -12
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +12 -12
  36. package/dist/index.js.map +1 -1
  37. package/dist/message/link.d.ts +10 -0
  38. package/dist/message/link.d.ts.map +1 -0
  39. package/dist/message/link.js +22 -0
  40. package/dist/message/link.js.map +1 -0
  41. package/dist/message/sigCache.d.ts +13 -0
  42. package/dist/message/sigCache.d.ts.map +1 -0
  43. package/dist/message/sigCache.js +26 -0
  44. package/dist/message/sigCache.js.map +1 -0
  45. package/dist/{translator → packet}/decoder.d.ts +14 -1
  46. package/dist/packet/decoder.d.ts.map +1 -0
  47. package/dist/{translator → packet}/decoder.js +24 -3
  48. package/dist/packet/decoder.js.map +1 -0
  49. package/dist/packet/dispatcher.d.ts +53 -0
  50. package/dist/packet/dispatcher.d.ts.map +1 -0
  51. package/dist/packet/dispatcher.js +738 -0
  52. package/dist/packet/dispatcher.js.map +1 -0
  53. package/dist/{translator → packet}/encoder.d.ts +4 -3
  54. package/dist/packet/encoder.d.ts.map +1 -0
  55. package/dist/{translator → packet}/encoder.js +18 -7
  56. package/dist/packet/encoder.js.map +1 -0
  57. package/dist/packet/static.d.ts.map +1 -0
  58. package/dist/packet/static.js.map +1 -0
  59. package/dist/{base → physics}/aabb.d.ts +2 -2
  60. package/dist/physics/aabb.d.ts.map +1 -0
  61. package/dist/{base → physics}/aabb.js +5 -5
  62. package/dist/physics/aabb.js.map +1 -0
  63. package/dist/physics/direction.d.ts.map +1 -0
  64. package/dist/physics/direction.js.map +1 -0
  65. package/dist/{base → physics}/player.d.ts +25 -28
  66. package/dist/physics/player.d.ts.map +1 -0
  67. package/dist/{base → physics}/player.js +81 -68
  68. package/dist/physics/player.js.map +1 -0
  69. package/dist/version/codec.d.ts +18 -0
  70. package/dist/version/codec.d.ts.map +1 -0
  71. package/dist/version/codec.js +235 -0
  72. package/dist/version/codec.js.map +1 -0
  73. package/dist/version/registry.d.ts +236 -0
  74. package/dist/version/registry.d.ts.map +1 -0
  75. package/dist/version/registry.js +284 -0
  76. package/dist/version/registry.js.map +1 -0
  77. package/dist/{base → world}/block.d.ts +5 -5
  78. package/dist/world/block.d.ts.map +1 -0
  79. package/dist/{base → world}/block.js +13 -13
  80. package/dist/world/block.js.map +1 -0
  81. package/dist/{base → world}/entity.d.ts +4 -4
  82. package/dist/world/entity.d.ts.map +1 -0
  83. package/dist/{base → world}/entity.js +6 -6
  84. package/dist/world/entity.js.map +1 -0
  85. package/dist/world/state.d.ts +175 -0
  86. package/dist/world/state.d.ts.map +1 -0
  87. package/dist/world/state.js +163 -0
  88. package/dist/world/state.js.map +1 -0
  89. package/dist/world/tick.d.ts.map +1 -0
  90. package/dist/world/tick.js.map +1 -0
  91. package/package.json +7 -2
  92. package/src/base/buffer.ts +12 -0
  93. package/src/base/error.ts +93 -4
  94. package/src/base/http.ts +10 -0
  95. package/src/base/math.ts +55 -3
  96. package/src/base/typing.ts +9 -1
  97. package/src/client/auth.ts +130 -52
  98. package/src/client/client.ts +182 -50
  99. package/src/client/tcp.ts +45 -967
  100. package/src/index.ts +12 -12
  101. package/src/message/link.ts +17 -0
  102. package/src/message/sigCache.ts +25 -0
  103. package/src/{translator → packet}/decoder.ts +24 -3
  104. package/src/packet/dispatcher.ts +919 -0
  105. package/src/{translator → packet}/encoder.ts +20 -9
  106. package/src/{base → physics}/aabb.ts +5 -5
  107. package/src/{base → physics}/player.ts +106 -65
  108. package/src/version/codec.ts +248 -0
  109. package/src/version/registry.ts +328 -0
  110. package/src/{base → world}/block.ts +12 -11
  111. package/src/{base → world}/entity.ts +5 -5
  112. package/src/world/state.ts +300 -0
  113. package/test/adapter/index.ts +14 -0
  114. package/test/client/index.ts +28 -21
  115. package/test/json/index.ts +3 -0
  116. package/dist/base/aabb.d.ts.map +0 -1
  117. package/dist/base/aabb.js.map +0 -1
  118. package/dist/base/block.d.ts.map +0 -1
  119. package/dist/base/block.js.map +0 -1
  120. package/dist/base/direction.d.ts.map +0 -1
  121. package/dist/base/direction.js.map +0 -1
  122. package/dist/base/entity.d.ts.map +0 -1
  123. package/dist/base/entity.js.map +0 -1
  124. package/dist/base/player.d.ts.map +0 -1
  125. package/dist/base/player.js.map +0 -1
  126. package/dist/base/registry.d.ts +0 -50
  127. package/dist/base/registry.d.ts.map +0 -1
  128. package/dist/base/registry.js +0 -89
  129. package/dist/base/registry.js.map +0 -1
  130. package/dist/base/tick.d.ts.map +0 -1
  131. package/dist/base/tick.js.map +0 -1
  132. package/dist/translator/decoder.d.ts.map +0 -1
  133. package/dist/translator/decoder.js.map +0 -1
  134. package/dist/translator/encoder.d.ts.map +0 -1
  135. package/dist/translator/encoder.js.map +0 -1
  136. package/dist/translator/static.d.ts.map +0 -1
  137. package/dist/translator/static.js.map +0 -1
  138. package/src/base/registry.ts +0 -87
  139. /package/assets/minecraft/{blocks.json → 26.2/blocks.json} +0 -0
  140. /package/assets/minecraft/{entities.json → 26.2/entities.json} +0 -0
  141. /package/dist/{translator → packet}/static.d.ts +0 -0
  142. /package/dist/{translator → packet}/static.js +0 -0
  143. /package/dist/{base → physics}/direction.d.ts +0 -0
  144. /package/dist/{base → physics}/direction.js +0 -0
  145. /package/dist/{base → world}/tick.d.ts +0 -0
  146. /package/dist/{base → world}/tick.js +0 -0
  147. /package/src/{translator → packet}/static.ts +0 -0
  148. /package/src/{base → physics}/direction.ts +0 -0
  149. /package/src/{base → world}/tick.ts +0 -0
@@ -0,0 +1,919 @@
1
+ import zod from "zod";
2
+ import { createCipheriv, createDecipheriv, createPublicKey, publicEncrypt, randomBytes, constants as crypto_constants, createVerify } from "node:crypto";
3
+ import { BlockEntity, ChunkSection, ClientPlayer, ClientStatus, ConnectionState, Entity, GameMode, PaletteContainer, ServerKnownPack, ServerWorld, SharedState } from "../world/state";
4
+ import { BinaryDecoder, getTextFromTextComponent } from "./decoder";
5
+ import { packBlockPos, SectionsPerChunk } from "../client/static";
6
+ import { Angle, BaseVec3, Vec3 } from "../physics/direction";
7
+ import { ProtocolVersionMapping } from "../version/registry";
8
+ import { TypedEmmiter } from "../base/event";
9
+ import { ClientEvents } from "../client/client";
10
+ import { makeMovementFlag } from "./encoder";
11
+ import { TextComponent } from "../base/typing";
12
+ import { AuthRelatedNotFound, HaveSignatureButNotIndex, MessageLinkNotFound, MissingAuthOption, NotImplemented, UnexpectedValue } from "../base/error";
13
+ import { randomUUIDBytes, uuidToBuffer } from "../base/math";
14
+ import { MessageLink } from "../message/link";
15
+ import { sliceBuffer } from "../base/buffer";
16
+
17
+ function zodParse<Type extends zod.ZodType>(data: object, zod: Type): zod.infer<Type> {
18
+ return zod.parse(data);
19
+ }
20
+
21
+ export class Dispatcher {
22
+ private readonly mapping: Record<string, (data: object) => void>;
23
+
24
+ constructor(
25
+ private state: SharedState,
26
+ private emit: TypedEmmiter<ClientEvents>["emit"]
27
+ ) {
28
+ this.mapping = {
29
+ "login:login_disconnect": this.handleLoginDisconnect,
30
+ "login:hello": this.handleEncryption,
31
+ "login:login_finished": this.handleLoginSucess,
32
+ "login:login_compression": this.handleSetCompression,
33
+ "configuration:disconnect": this.handleConfiguarionPlayDisconnect,
34
+ "configuration:keep_alive": this.handleKeepAlive,
35
+ "configuration:registry_data": this.handleRegistryData,
36
+ "configuration:select_known_packs": this.handleKnownPack,
37
+ "configuration:finish_configuration": this.handleFinishConfiguration,
38
+ "play:add_entity": this.handleSpawnEntity,
39
+ "play:disconnect": this.handleConfiguarionPlayDisconnect,
40
+ "play:disguised_chat": this.handleDisguisedChatMessage,
41
+ "play:entity_position_sync": this.handleTeleportEntity,
42
+ "play:forget_level_chunk": this.handleUnloadChunk,
43
+ "play:keep_alive": this.handleKeepAlive,
44
+ "play:level_chunk_with_light": this.handleChunkData,
45
+ "play:login": this.handlePlayLogin,
46
+ "play:move_entity_pos": this.handleUpdateEntityPosition,
47
+ "play:move_entity_pos_rot": this.handleUpdateEntityPositionRotation,
48
+ "play:player_chat": this.handlePlayerChat,
49
+ "play:player_position": this.handleSynchronizePlayerPosition,
50
+ "play:remove_entities": this.handleRemoveEntity,
51
+ // "play:rotate_head":
52
+ "play:set_action_bar_text": this.handleSetActionBar,
53
+ "play:set_entity_motion": this.handleSetEntityVelocity,
54
+ "play:system_chat": this.handleSystemMessage,
55
+ };
56
+ }
57
+
58
+ public sendPacket: (packetId: string, data: object) => void = () => { throw new NotImplemented(); };
59
+ public disconnect: (reason: string, reasonRaw?: TextComponent) => void = () => { throw new NotImplemented(); };
60
+
61
+
62
+ public handlePacket(packetId: string, data: object) {
63
+ if (packetId in this.mapping)
64
+ this.mapping[packetId]!.bind(this)(data);
65
+ }
66
+
67
+ // Login
68
+
69
+ private handleLoginDisconnect(data: object) {
70
+ const { reason } = zodParse(data, zod.object({ reason: zod.record(zod.string(), zod.any()) }));
71
+ const text = getTextFromTextComponent(reason);
72
+
73
+ this.disconnect(text, reason);
74
+ }
75
+
76
+ private handleEncryption(data: object) {
77
+ const {
78
+ server_id,
79
+ public_key,
80
+ verify_token,
81
+ should_authenticate
82
+ } = zodParse(data, zod.object({
83
+ server_id: zod.string(),
84
+ public_key: zod.array(zod.number()),
85
+ verify_token: zod.array(zod.number()),
86
+ should_authenticate: zod.boolean()
87
+ }));
88
+
89
+ const sharedSecret = randomBytes(16);
90
+ this.state.sharedSecret = sharedSecret;
91
+ this.state.cipher = createCipheriv("aes-128-cfb8", sharedSecret, sharedSecret);
92
+ this.state.decipher = createDecipheriv("aes-128-cfb8", sharedSecret, sharedSecret);
93
+ this.state.useEncryption = true;
94
+
95
+ const publicKey = Buffer.from(public_key),
96
+ verifyToken = Buffer.from(verify_token);
97
+ if (should_authenticate)
98
+ if (!this.state.authClient)
99
+ throw new AuthRelatedNotFound("auth client");
100
+ else
101
+ this.state.authClient.sendAuth(server_id, sharedSecret, publicKey)
102
+ .then(() => this.sendEncryptionResponse(publicKey, verifyToken));
103
+ else
104
+ this.sendEncryptionResponse(publicKey, verifyToken);
105
+ }
106
+
107
+ private handleSetCompression(data: object) {
108
+ const { threshold } = zodParse(data, zod.object({ threshold: zod.number() }));
109
+ if (threshold < 0) this.state.compressionThreshold = -1;
110
+ else this.state.compressionThreshold = threshold;
111
+ }
112
+
113
+ private handleLoginSucess(data: object) {
114
+ const { profile: { uuid, username } } = zodParse(data,
115
+ zod.object({
116
+ profile: zod.object({ uuid: zod.string(), username: zod.string() }),
117
+ session_id: zod.string()
118
+ }));
119
+ this.state.player = {
120
+ uuid,
121
+ username,
122
+ entityId: 0,
123
+ dimension: "",
124
+ gameMode: GameMode.Survival,
125
+
126
+ position: {
127
+ x: 0,
128
+ y: 0,
129
+ z: 0,
130
+ },
131
+ velocity: {
132
+ x: 0,
133
+ y: 0,
134
+ z: 0,
135
+ },
136
+ angle: {
137
+ yaw: 0,
138
+ pitch: 0,
139
+ }
140
+ } satisfies ClientPlayer as any;
141
+ this.sendLoginAck();
142
+ }
143
+
144
+ // Configure
145
+
146
+ private handleKnownPack(data: object) {
147
+ const { known_packs } = zodParse(data, zod.object({
148
+ known_packs: zod.array(zod.object({
149
+ namespace: zod.string(),
150
+ id: zod.string(),
151
+ version: zod.string(),
152
+ }))
153
+ }));
154
+ this.state.server!.knownPacks = known_packs satisfies ServerKnownPack[] as any;
155
+ this.sendKnownPack(this.state.clientOptions.loadRegistry === true ? [] : known_packs);
156
+ // this.sendKnownPack([]);
157
+ }
158
+
159
+ private handleRegistryData(data: object) {
160
+ const { registry_id, entries } = zodParse(data,
161
+ zod.object({
162
+ registry_id: zod.string(),
163
+ entries: zod.array(zod.object({
164
+ entry_id: zod.string(),
165
+ data: zod.record(zod.string(), zod.any()).nullable()
166
+ }))
167
+ })
168
+ );
169
+
170
+ if (!this.state.registry) this.state.registry = {} as any;
171
+ const registry = this.state.registry as Record<string, any[]>;
172
+ if (!(registry_id in registry)) registry[registry_id] = [];
173
+ registry[registry_id]!.push(...entries);
174
+ }
175
+
176
+ private handleFinishConfiguration(data: object) {
177
+ this.sendConfigureAck();
178
+ }
179
+
180
+ private handleConfiguarionPlayDisconnect(data: object) {
181
+ const { reason } = zodParse(data, zod.object({ reason: zod.record(zod.string(), zod.any()) }));
182
+ const text = getTextFromTextComponent(reason);
183
+
184
+ this.disconnect(text, reason);
185
+ }
186
+
187
+ // Play
188
+
189
+ private handlePlayLogin(data: object) {
190
+ const {
191
+ entity_id,
192
+ is_hardcore,
193
+ dimension_names,
194
+ max_players,
195
+ view_distance,
196
+ simulation_distance,
197
+ dimension_name,
198
+ game_mode
199
+ } = zodParse(data,
200
+ zod.object({
201
+ entity_id: zod.int(),
202
+ is_hardcore: zod.boolean(),
203
+ dimension_names: zod.array(zod.string()),
204
+ max_players: zod.int(),
205
+ view_distance: zod.int(),
206
+ simulation_distance: zod.int(),
207
+ dimension_name: zod.string(),
208
+ game_mode: zod.int()
209
+ })
210
+ );
211
+
212
+
213
+ this.state.world = {
214
+ hardcore: is_hardcore,
215
+ dimensions: dimension_names,
216
+ maxPlayers: max_players,
217
+ viewDistance: view_distance,
218
+ simulationDistance: simulation_distance,
219
+ chunks: {},
220
+ entities: {}
221
+ } satisfies ServerWorld as any; // To avoid type conflict
222
+ this.state.player!.dimension = dimension_name;
223
+ this.state.player!.entityId = entity_id;
224
+ this.state.player!.gameMode = game_mode;
225
+ this.state.status = ClientStatus.Ready;
226
+
227
+ if (this.state.useEncryption)
228
+ this.sendPlayerSession();
229
+
230
+ this.emit("ready");
231
+ }
232
+
233
+ private handleSynchronizePlayerPosition(data: object) {
234
+ const {
235
+ teleport_id,
236
+ x,
237
+ y,
238
+ z,
239
+ velocity_x,
240
+ velocity_y,
241
+ velocity_z,
242
+ yaw,
243
+ pitch,
244
+ flags
245
+ } = zodParse(data,
246
+ zod.object({
247
+ teleport_id: zod.int(),
248
+ x: zod.number(),
249
+ y: zod.number(),
250
+ z: zod.number(),
251
+ velocity_x: zod.number(),
252
+ velocity_y: zod.number(),
253
+ velocity_z: zod.number(),
254
+ yaw: zod.number(),
255
+ pitch: zod.number(),
256
+ flags: zod.object({
257
+ x: zod.boolean(),
258
+ y: zod.boolean(),
259
+ z: zod.boolean(),
260
+ yaw: zod.boolean(),
261
+ pitch: zod.boolean(),
262
+ velX: zod.boolean(),
263
+ velY: zod.boolean(),
264
+ velZ: zod.boolean(),
265
+ rotateVelocity: zod.boolean(),
266
+ }),
267
+ })
268
+ );
269
+
270
+ const { position, velocity, angle } = this.state.player!;
271
+ let { x: newPosX, y: newPosY, z: newPosZ } = position;
272
+ let { x: newVelX, y: newVelY, z: newVelZ } = velocity;
273
+ let { yaw: newYaw, pitch: newPitch } = angle;
274
+ if (flags.x) newPosX += x; else newPosX = x;
275
+ if (flags.y) newPosY += y; else newPosY = y;
276
+ if (flags.z) newPosZ += z; else newPosZ = z;
277
+ if (flags.velX) newVelX += velocity_x; else newVelX = velocity_x;
278
+ if (flags.velY) newVelY += velocity_y; else newVelY = velocity_y;
279
+ if (flags.velZ) newVelZ += velocity_z; else newVelZ = velocity_z;
280
+ if (flags.yaw) newYaw += yaw; else newYaw = yaw;
281
+ if (flags.pitch) newPitch += pitch; else newPitch = pitch;
282
+ const newPosition = { x: newPosX, y: newPosY, z: newPosZ },
283
+ newVelocity = { x: newVelX, y: newVelY, z: newVelZ },
284
+ newAngle = { yaw: newYaw, pitch: newPitch };
285
+ this.state.enqueueMutation(state =>
286
+ state.player = {
287
+ ...state.player!,
288
+ position: newPosition,
289
+ velocity: newVelocity,
290
+ angle: newAngle,
291
+ } satisfies ClientPlayer as any
292
+ );
293
+ this.state.enqueueEvent("playerPosition", { x: newPosX, y: newPosY, z: newPosZ });
294
+ this.sendConfirmTeleportation(teleport_id);
295
+ this.sendPacket("move_player_pos_rot", {
296
+ x: newPosition.x,
297
+ feet_y: newPosition.y,
298
+ z: newPosition.z,
299
+ yaw: newAngle.yaw,
300
+ pitch: newAngle.pitch,
301
+ flags: makeMovementFlag(true, false)
302
+ });
303
+ }
304
+
305
+ private handleKeepAlive(data: object) {
306
+ const { keep_alive_id } = zodParse(data,
307
+ zod.object({
308
+ keep_alive_id: zod.bigint().or(zod.number())
309
+ })
310
+ );
311
+ this.sendKeepAlive(BigInt(keep_alive_id));
312
+ }
313
+
314
+ private handleChangeGameMode(data: object) {
315
+ const { gameMode } = zodParse(data, zod.object({ gameMode: zod.number() }));
316
+ this.state.enqueueMutation(state => state.player!.gameMode = gameMode);
317
+ }
318
+
319
+ // Chunk
320
+ private handleChunkData(data_: object) {
321
+ const {
322
+ chunk_x,
323
+ chunk_z,
324
+ heightmaps,
325
+ data,
326
+ block_entities,
327
+ light,
328
+ } = zodParse(data_,
329
+ zod.object({
330
+ chunk_x: zod.int(),
331
+ chunk_z: zod.int(),
332
+ heightmaps: zod.array(zod.any()),
333
+ data: zod.array(zod.int()),
334
+ block_entities: zod.array(zod.object({
335
+ packed_xz: zod.int(),
336
+ y: zod.int(),
337
+ type: zod.int(),
338
+ data: zod.record(zod.string(), zod.any())
339
+ })),
340
+ light: zod.any(),
341
+ })
342
+ );
343
+
344
+ const chunkSections: Record<number, ChunkSection> = {};
345
+
346
+ const chunkDataDecoder = new BinaryDecoder(Buffer.from(data));
347
+ for (let height = 0; height < (SectionsPerChunk[this.state.player!.dimension] || 0); height++) {
348
+ const blockCount = chunkDataDecoder.readShort();
349
+ const fluidCount = chunkDataDecoder.readShort();
350
+
351
+ // Blocks
352
+ let blockStateBPE = chunkDataDecoder.readUByte();
353
+ let blockStatePalettes: number[] = [];
354
+
355
+ if (blockStateBPE === 0) {
356
+ blockStatePalettes = [chunkDataDecoder.readVarInt()];
357
+ } else if (blockStateBPE <= 8) {
358
+ // Indirect palette
359
+ if (blockStateBPE < 4) blockStateBPE = 4; // BPE smaller than 4 will be rounđe up to 4
360
+ blockStatePalettes = chunkDataDecoder.readPrefixedArray(decoder => decoder.readVarInt());
361
+ } else {
362
+ // Direct palette (no palette array)
363
+ }
364
+
365
+ const blockEntriesPerSection = 4096;
366
+ const blockEntriesPerLong = Math.floor(64 / blockStateBPE);
367
+ let blockDataArray: BigInt64Array | null = null;
368
+ if (blockStateBPE !== 0) {
369
+ const blockDataArrayLength = Math.floor((blockEntriesPerSection + blockEntriesPerLong - 1) / blockEntriesPerLong);
370
+ blockDataArray = new BigInt64Array(chunkDataDecoder.readArray(blockDataArrayLength, (decoder) => decoder.readLong()));
371
+ }
372
+
373
+ // Biomes - Not used yet, but need to be read to advance decoder
374
+ const biomeBPE = chunkDataDecoder.readUByte();
375
+ let biomePalettes: number[] = [];
376
+ if (biomeBPE === 0) {
377
+ biomePalettes = [chunkDataDecoder.readVarInt()]; // Palette value
378
+ } else if (biomeBPE <= 3) {
379
+ // Indirect palette
380
+ biomePalettes = chunkDataDecoder.readPrefixedArray(decoder => decoder.readVarInt());
381
+ } else {
382
+ // Direct palette (no palette array)
383
+ }
384
+
385
+ const biomeEntriesPerSection = 64;
386
+ const biomeEntriesPerLong = Math.floor(64 / biomeBPE);
387
+ let biomeDataArray: BigInt64Array | null = null;
388
+ if (biomeBPE !== 0) {
389
+ const biomeDataArrayLength = Math.floor((biomeEntriesPerSection + biomeEntriesPerLong - 1) / biomeEntriesPerLong);
390
+ biomeDataArray = new BigInt64Array(chunkDataDecoder.readArray(biomeDataArrayLength, (decoder) => decoder.readLong()));
391
+ }
392
+
393
+ chunkSections[height] = {
394
+ block: {
395
+ bpe: blockStateBPE,
396
+ palette: blockStatePalettes,
397
+ data: blockDataArray
398
+ },
399
+ biome: {
400
+ bpe: biomeBPE,
401
+ palette: biomePalettes,
402
+ data: biomeDataArray
403
+ }
404
+ };
405
+
406
+ this.state.enqueueEvent("loadChunk", chunk_x, height, chunk_z);
407
+ }
408
+
409
+ const blockEntitiesObj: Record<number, BlockEntity> = Object.fromEntries(
410
+ block_entities.map(val => [val.packed_xz, { type: val.type, data: val.data }])
411
+ );
412
+
413
+
414
+ this.state.enqueueMutation(state =>
415
+ state.world!.chunks[`${state.player!.dimension}:${chunk_x}:${chunk_z}`] = {
416
+ sections: chunkSections,
417
+ blockEntities: blockEntitiesObj
418
+ });
419
+ }
420
+
421
+ private handleUnloadChunk(data: object) {
422
+ const {
423
+ chunk_x,
424
+ chunk_z
425
+ } = zodParse(data, zod.object({
426
+ chunk_x: zod.number(),
427
+ chunk_z: zod.number()
428
+ }));
429
+ this.state.enqueueMutation(state => delete state.world!.chunks[`${state.player!.dimension}:${chunk_x}:${chunk_z}`]);
430
+ this.state.enqueueEvent("unloadChunk", chunk_x, chunk_z);
431
+ }
432
+
433
+ private handleBlockEntityData(data_: object) {
434
+ const {
435
+ location,
436
+ type,
437
+ data
438
+ } = zodParse(data_, zod.object({
439
+ location: zod.object({ x: zod.number(), y: zod.number(), z: zod.number() }),
440
+ type: zod.int(),
441
+ data: zod.record(zod.string(), zod.any())
442
+ }));
443
+
444
+ const chunkX = Math.floor(location.x / 16),
445
+ chunkZ = Math.floor(location.z / 16);
446
+ const xWitinChunk = location.x % 16,
447
+ zWitinChunk = location.z % 16;
448
+ const packedPosition = packBlockPos(xWitinChunk, location.y, zWitinChunk);
449
+ if (`${this.state.player!.dimension}:${chunkX}:${chunkZ}` in this.state.world!.chunks)
450
+ this.state.enqueueMutation(state =>
451
+ state.world!.chunks[`${state.player!.dimension}:${chunkX}:${chunkZ}`]!.blockEntities[packedPosition] = {
452
+ type,
453
+ data
454
+ }
455
+ );
456
+ }
457
+
458
+ // Entity
459
+ private handleSpawnEntity(data_: object) {
460
+ const {
461
+ entity_id,
462
+ entity_uuid,
463
+ type,
464
+ x,
465
+ y,
466
+ z,
467
+ velocity,
468
+ pitch,
469
+ yaw,
470
+ head_yaw,
471
+ data
472
+ } = zodParse(data_, zod.object({
473
+ entity_id: zod.int(),
474
+ entity_uuid: zod.string(),
475
+ type: zod.int(),
476
+ x: zod.number(),
477
+ y: zod.number(),
478
+ z: zod.number(),
479
+ velocity: zod.instanceof(Vec3),
480
+ pitch: zod.number(),
481
+ yaw: zod.number(),
482
+ head_yaw: zod.number(),
483
+ data: zod.int(),
484
+ }));
485
+
486
+ const entity: Entity = {
487
+ id: entity_id,
488
+ type,
489
+ position: { x, y, z },
490
+ velocity,
491
+ angle: {
492
+ pitch,
493
+ yaw
494
+ },
495
+ data
496
+ };
497
+ this.state.enqueueMutation(state => state.world!.entities[entity_id] = entity);
498
+ this.state.enqueueEvent("spawnEntity", entity);
499
+ }
500
+
501
+ private handleTeleportEntity(data: object) {
502
+ const {
503
+ entity_id,
504
+ x,
505
+ y,
506
+ z,
507
+ velocity_x,
508
+ velocity_y,
509
+ velocity_z,
510
+ pitch,
511
+ yaw
512
+ } = zodParse(data, zod.object({
513
+ entity_id: zod.int(),
514
+ x: zod.number(),
515
+ y: zod.number(),
516
+ z: zod.number(),
517
+ velocity_x: zod.number(),
518
+ velocity_y: zod.number(),
519
+ velocity_z: zod.number(),
520
+ pitch: zod.number(),
521
+ yaw: zod.number(),
522
+ }));
523
+
524
+ if (entity_id in this.state.world!.entities) this.state.enqueueMutation(state => {
525
+ state.world!.entities[entity_id]!.position = { x, y, z };
526
+ state.world!.entities[entity_id]!.velocity = { x: velocity_x, y: velocity_y, z: velocity_z };
527
+ state.world!.entities[entity_id]!.angle = { pitch, yaw };
528
+ });
529
+ this.state.enqueueEvent("updateEntity", entity_id);
530
+ }
531
+
532
+ private handleUpdateEntityPosition(data: object) {
533
+ const {
534
+ entity_id,
535
+ delta_x,
536
+ delta_y,
537
+ delta_z
538
+ } = zodParse(data, zod.object({
539
+ entity_id: zod.number(),
540
+ delta_x: zod.number(),
541
+ delta_y: zod.number(),
542
+ delta_z: zod.number(),
543
+ }));
544
+
545
+ if (entity_id in this.state.world!.entities) this.state.enqueueMutation(state => {
546
+ let { x, y, z } = state.world!.entities[entity_id]!.position;
547
+ x += delta_x;
548
+ y += delta_y;
549
+ z += delta_z;
550
+ state.world!.entities[entity_id]!.position = { x, y, z };
551
+ });
552
+ this.state.enqueueEvent("updateEntity", entity_id);
553
+ }
554
+
555
+ private handleUpdateEntityPositionRotation(data: object) {
556
+ const {
557
+ entity_id,
558
+ delta_x,
559
+ delta_y,
560
+ delta_z,
561
+ pitch,
562
+ yaw
563
+ } = zodParse(data, zod.object({
564
+ entity_id: zod.number(),
565
+ delta_x: zod.number(),
566
+ delta_y: zod.number(),
567
+ delta_z: zod.number(),
568
+ pitch: zod.number(),
569
+ yaw: zod.number(),
570
+ }));
571
+ if (entity_id in this.state.world!.entities) {
572
+ this.state.enqueueMutation(state => {
573
+ let { x, y, z } = state.world!.entities[entity_id]!.position;
574
+ x += delta_x;
575
+ y += delta_y;
576
+ z += delta_z;
577
+ state.world!.entities[entity_id]!.position = { x, y, z };
578
+ state.world!.entities[entity_id]!.angle = { yaw, pitch };
579
+ });
580
+ this.state.enqueueEvent("updateEntity", entity_id);
581
+ }
582
+ }
583
+
584
+ private handleSetEntityVelocity(data: object) {
585
+ const { entity_id, velocity } = zodParse(data, zod.object({
586
+ entity_id: zod.number(),
587
+ velocity: zod.instanceof(Vec3)
588
+ }));
589
+
590
+ if (entity_id in this.state.world!.entities)
591
+ this.state.enqueueMutation(state => state.world!.entities[entity_id]!.velocity = velocity);
592
+ }
593
+
594
+ private handleRemoveEntity(data: object) {
595
+ const { entity_ids } = zodParse(data, zod.object({ entity_ids: zod.array(zod.number()) }));
596
+
597
+ this.state.enqueueMutation(state => {
598
+ for (const entityId of entity_ids) {
599
+ delete state.world!.entities[entityId];
600
+ this.state.enqueueEvent("removeEntity", entityId);
601
+ }
602
+ });
603
+ }
604
+
605
+ // Chat
606
+ private handlePlayerChat(data: object) {
607
+ const {
608
+ global_index,
609
+ sender,
610
+ index,
611
+ message_signature_bytes,
612
+ message,
613
+ timestamp,
614
+ salt,
615
+ previous_messages,
616
+ unsigned_content,
617
+ filter_type,
618
+ filter_type_bits,
619
+ chat_type,
620
+ sender_name,
621
+ target_name
622
+ } = zodParse(data, zod.object({
623
+ global_index: zod.int(),
624
+ sender: zod.string(),
625
+ index: zod.int(),
626
+ message_signature_bytes: zod.array(zod.number()).nullable(),
627
+ message: zod.string(),
628
+ timestamp: zod.bigint(),
629
+ salt: zod.bigint(),
630
+ previous_messages: zod.array(zod.object({
631
+ message_id: zod.number(),
632
+ signature: zod.array(zod.number()).optional()
633
+ })),
634
+ unsigned_content: zod.record(zod.string(), zod.any()).nullable(),
635
+ filter_type: zod.int(),
636
+ filter_type_bits: zod.array(zod.bigint()).nullable(),
637
+ chat_type: zod.int().or(zod.record(zod.string(), zod.any())),
638
+ sender_name: zod.record(zod.string(), zod.any()),
639
+ target_name: zod.record(zod.string(), zod.any()).nullable()
640
+ }));
641
+
642
+ const senderNameText = getTextFromTextComponent(sender_name).toString(),
643
+ targetNameText = target_name ? getTextFromTextComponent(target_name).toString() : undefined;
644
+
645
+ const emitObject = {
646
+ sender: senderNameText,
647
+ target: targetNameText,
648
+ content: getTextFromTextComponent(unsigned_content),
649
+ raw: {
650
+ sender: sender_name,
651
+ target: target_name || undefined,
652
+ content: unsigned_content || undefined,
653
+ }
654
+ };
655
+
656
+ let link: MessageLink | undefined = undefined;
657
+ if (this.state.clientOptions.shouldVerifyMessageOrder && this.state.messageLinks) {
658
+ if (index === 0) {
659
+ if (this.state.sessionID)
660
+ link = MessageLink.root(sender, this.state.sessionID.toString("hex"));
661
+ else
662
+ link = MessageLink.unsinged(sender);
663
+ this.state.messageLinks[sender] = link;
664
+ } else {
665
+ const oldLink = this.state.messageLinks[sender];
666
+ if (!oldLink)
667
+ throw new MessageLinkNotFound(sender);
668
+ let newLink;
669
+ if (this.state.sessionID)
670
+ newLink = new MessageLink(global_index, sender, this.state.sessionID.toString("hex"));
671
+ else
672
+ newLink = MessageLink.unsinged(sender);
673
+ if (!newLink.isDescendantOf(oldLink))
674
+ throw new UnexpectedValue("message order", newLink.index.toString(), (oldLink.index + 1).toString());
675
+ this.state.messageLinks[sender] = newLink;
676
+ link = newLink;
677
+ }
678
+ }
679
+
680
+ if (this.state.clientOptions.shouldVerifyMessageSignature === true && message_signature_bytes) {
681
+ if (!this.state.sessionID)
682
+ throw new AuthRelatedNotFound("session uuid");
683
+ if (!this.state.messageSignatureCache)
684
+ throw new AuthRelatedNotFound("signature cache");
685
+ if (!link)
686
+ throw new HaveSignatureButNotIndex();
687
+ const { publicKey } = this.state.getSignature();
688
+
689
+ const formatVersion = Buffer.alloc(4); formatVersion.writeInt32BE(1);
690
+ const senderUUIDBuf = uuidToBuffer(sender);
691
+ const sessionUUIDBuf = this.state.sessionID;
692
+ const indexBuf = Buffer.alloc(4); indexBuf.writeInt32BE(link.index);
693
+ const saltBuf = Buffer.alloc(8); saltBuf.writeBigInt64BE(salt);
694
+ const timestampBuf = Buffer.alloc(8); timestampBuf.writeBigInt64BE(timestamp);
695
+ const _messageBuf = Buffer.from(message, "utf-8");
696
+ const lengthBuf = Buffer.alloc(4); lengthBuf.writeInt32BE(_messageBuf.length);
697
+ const messageBuf = _messageBuf;
698
+ const previousMessagesLenghtBuf = Buffer.alloc(4); previousMessagesLenghtBuf.writeInt32BE(previous_messages.length);
699
+ const previousMessagesBuf = Buffer.concat(this.state.messageSignatureCache.unpack(previous_messages));
700
+ const signaturePayload = Buffer.concat([
701
+ formatVersion,
702
+ senderUUIDBuf,
703
+ sessionUUIDBuf,
704
+ indexBuf,
705
+ saltBuf,
706
+ timestampBuf,
707
+ lengthBuf,
708
+ messageBuf,
709
+ previousMessagesLenghtBuf,
710
+ previousMessagesBuf
711
+ ]);
712
+
713
+ const verify = createVerify("RSA-SHA256");
714
+ verify.update(signaturePayload);
715
+ verify.end();
716
+ const signature = Buffer.from(message_signature_bytes);
717
+
718
+ if (!verify.verify(publicKey!, signature))
719
+ this.emit("failedMessage", emitObject);
720
+ }
721
+
722
+ this.emit("message", emitObject);
723
+ }
724
+
725
+ private handleSystemMessage(data: object) {
726
+ const {
727
+ content,
728
+ overlay: isActionbar
729
+ } = zodParse(data, zod.object({
730
+ content: zod.record(zod.string(), zod.any()),
731
+ overlay: zod.boolean()
732
+ }));
733
+ const text = getTextFromTextComponent(content);
734
+
735
+ if (isActionbar) {
736
+ this.emit("actionBar", text.toString());
737
+ this.emit("actionBarRaw", content);
738
+ } else {
739
+ this.emit("systemMessage", text.toString());
740
+ this.emit("systemMessageRaw", content);
741
+ }
742
+ }
743
+
744
+ private handleSetActionBar(data: object) {
745
+ const { action_bar_text: content } = zodParse(data, zod.object({ action_bar_text: zod.record(zod.string(), zod.any()) }));
746
+ const text = getTextFromTextComponent(content);
747
+ this.emit("actionBar", text.toString());
748
+ this.emit("actionBarRaw", content);
749
+ }
750
+
751
+ private handleDisguisedChatMessage(data: object) {
752
+ const {
753
+ message,
754
+ chatType,
755
+ senderName,
756
+ targetName
757
+ } = zodParse(data, zod.object({
758
+ message: zod.record(zod.string(), zod.any()),
759
+ chatType: zod.string().or(zod.record(zod.string(), zod.any())),
760
+ senderName: zod.record(zod.string(), zod.any()),
761
+ targetName: zod.record(zod.string(), zod.any()).nullable(),
762
+ }));
763
+ const messageText = getTextFromTextComponent(message).toString(),
764
+ senderNameText = getTextFromTextComponent(senderName).toString(),
765
+ targetNameText = targetName ? getTextFromTextComponent(targetName).toString() : undefined;
766
+ this.emit("message", {
767
+ sender: senderNameText,
768
+ target: targetNameText,
769
+ content: messageText,
770
+ raw: {
771
+ sender: senderName,
772
+ target: targetName || undefined,
773
+ content: message,
774
+ }
775
+ });
776
+ }
777
+
778
+ /*
779
+ * Send packet
780
+ */
781
+
782
+ // Login
783
+
784
+ public sendHandshake() {
785
+ this.state.state = ConnectionState.Handshake;
786
+ this.sendPacket("intention",
787
+ {
788
+ protocol_version: ProtocolVersionMapping[this.state.clientOptions.version]!,
789
+ server_address: this.state.clientOptions.host,
790
+ server_port: this.state.clientOptions.port,
791
+ intent: 2
792
+ }
793
+ );
794
+ }
795
+
796
+ public sendLoginStart() {
797
+ this.state.state = ConnectionState.Login;
798
+ this.state.status = ClientStatus.Logining;
799
+ this.sendPacket("hello", {
800
+ name: this.state.clientOptions.playerName,
801
+ player_uuid: Buffer.from(this.state.playerUUID!, "hex")
802
+ });
803
+ }
804
+
805
+ private sendEncryptionResponse(publicKey: Buffer, verifyToken: Buffer) {
806
+ const encryptedSecret = publicEncrypt({
807
+ key: createPublicKey({ key: publicKey, format: 'der', type: 'spki' }),
808
+ padding: crypto_constants.RSA_PKCS1_PADDING
809
+ }, this.state.sharedSecret!);
810
+ const encryptedVerifyToken = publicEncrypt({
811
+ key: createPublicKey({ key: publicKey, format: 'der', type: 'spki' }),
812
+ padding: crypto_constants.RSA_PKCS1_PADDING
813
+ }, verifyToken);
814
+ const secretBuf = sliceBuffer(encryptedSecret, 1, (buf) => buf.readInt8());
815
+ const token = sliceBuffer(encryptedVerifyToken, 1, (buf) => buf.readInt8());
816
+ this.sendPacket("key", {
817
+ shared_secret: { value: secretBuf, length: secretBuf.length },
818
+ verify_token: { value: token, length: token.length }
819
+ });
820
+ }
821
+
822
+ private sendLoginAck() {
823
+ this.sendPacket("login_acknowledged", {});
824
+ this.state.state = ConnectionState.Configure;
825
+ this.state.server = {};
826
+ }
827
+
828
+ // Configuring
829
+
830
+ private sendKnownPack(knownPacks: ServerKnownPack[]) {
831
+ this.sendPacket("select_known_packs", {
832
+ known_packs: {
833
+ value: knownPacks,
834
+ length: knownPacks.length
835
+ }
836
+ });
837
+ }
838
+
839
+ private sendConfigureAck() {
840
+ this.sendPacket("finish_configuration", {});
841
+ this.state.state = ConnectionState.Play;
842
+ }
843
+
844
+ // Play
845
+
846
+ private sendPlayerSession() {
847
+ const sessionUUID = randomUUIDBytes();
848
+ const expireTime = 15 * 60 * 1000; // 15min in ms
849
+ const expiredAt = Date.now() + expireTime;
850
+ const { publicKey, signature } = this.state.getSignature();
851
+ this.state.sessionID = sessionUUID;
852
+ this.sendPacket("chat_session_update", {
853
+ session_id: sessionUUID,
854
+ expire_at: expiredAt,
855
+ public_key: publicKey,
856
+ signature
857
+ });
858
+ }
859
+
860
+ private sendConfirmTeleportation(teleportId: number) {
861
+ this.sendPacket("accept_teleportation", {
862
+ teleport_id: teleportId
863
+ });
864
+ }
865
+
866
+ private sendKeepAlive(id: bigint) {
867
+ this.sendPacket("keep_alive", {
868
+ keep_alive_id: id
869
+ });
870
+ }
871
+
872
+ public sendPlayerPos(
873
+ position: BaseVec3,
874
+ onGround: boolean,
875
+ horizontalCollision: boolean
876
+ ) {
877
+ const { x, y, z } = position;
878
+ this.state.enqueuePacket("move_player_pos", {
879
+ x, feet_y: y, z,
880
+ flags: makeMovementFlag(onGround, horizontalCollision)
881
+ });
882
+ }
883
+
884
+ public sendPlayerPosRot(
885
+ position: BaseVec3,
886
+ angle: Angle,
887
+ onGround: boolean,
888
+ horizontalCollision: boolean
889
+ ) {
890
+ const { x, y, z } = position,
891
+ { yaw, pitch } = angle;
892
+ this.state.enqueuePacket("move_player_pos_rot", {
893
+ x, feet_y: y, z,
894
+ yaw, pitch,
895
+ flags: makeMovementFlag(onGround, horizontalCollision)
896
+ });
897
+ }
898
+
899
+ public sendPlayerRot(
900
+ angle: Angle,
901
+ onGround: boolean,
902
+ horizontalCollision: boolean
903
+ ) {
904
+ const { yaw, pitch } = angle;
905
+ this.state.enqueuePacket("move_player_rot", {
906
+ yaw, pitch,
907
+ flags: makeMovementFlag(onGround, horizontalCollision)
908
+ });
909
+ }
910
+
911
+ public sendPlayerStatus(
912
+ onGround: boolean,
913
+ horizontalCollision: boolean
914
+ ) {
915
+ this.state.enqueuePacket("move_player_status_only", {
916
+ flags: makeMovementFlag(onGround, horizontalCollision)
917
+ });
918
+ }
919
+ }