@vaitosoi/minejs 0.2.2 → 0.3.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/assets/minecraft/26.2/packets.json +4741 -0
- package/dist/base/error.d.ts +29 -0
- package/dist/base/error.d.ts.map +1 -1
- package/dist/base/error.js +54 -1
- package/dist/base/error.js.map +1 -1
- package/dist/base/typing.d.ts +7 -1
- package/dist/base/typing.d.ts.map +1 -1
- package/dist/client/client.d.ts +25 -14
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +73 -35
- package/dist/client/client.js.map +1 -1
- package/dist/client/tcp.d.ts +16 -201
- package/dist/client/tcp.d.ts.map +1 -1
- package/dist/client/tcp.js +41 -706
- package/dist/client/tcp.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/{translator → packet}/decoder.d.ts +1 -1
- package/dist/packet/decoder.d.ts.map +1 -0
- package/dist/{translator → packet}/decoder.js +3 -3
- package/dist/packet/decoder.js.map +1 -0
- package/dist/packet/dispatcher.d.ts +46 -0
- package/dist/packet/dispatcher.d.ts.map +1 -0
- package/dist/packet/dispatcher.js +626 -0
- package/dist/packet/dispatcher.js.map +1 -0
- package/dist/{translator → packet}/encoder.d.ts +4 -3
- package/dist/packet/encoder.d.ts.map +1 -0
- package/dist/{translator → packet}/encoder.js +18 -7
- package/dist/packet/encoder.js.map +1 -0
- package/dist/packet/static.d.ts.map +1 -0
- package/dist/packet/static.js.map +1 -0
- package/dist/{base → physics}/aabb.d.ts +2 -2
- package/dist/physics/aabb.d.ts.map +1 -0
- package/dist/{base → physics}/aabb.js +5 -5
- package/dist/physics/aabb.js.map +1 -0
- package/dist/physics/direction.d.ts.map +1 -0
- package/dist/physics/direction.js.map +1 -0
- package/dist/{base → physics}/player.d.ts +8 -5
- package/dist/physics/player.d.ts.map +1 -0
- package/dist/{base → physics}/player.js +40 -25
- package/dist/physics/player.js.map +1 -0
- package/dist/version/codec.d.ts +18 -0
- package/dist/version/codec.d.ts.map +1 -0
- package/dist/version/codec.js +214 -0
- package/dist/version/codec.js.map +1 -0
- package/dist/version/registry.d.ts +236 -0
- package/dist/version/registry.d.ts.map +1 -0
- package/dist/version/registry.js +289 -0
- package/dist/version/registry.js.map +1 -0
- package/dist/{base → world}/block.d.ts +5 -5
- package/dist/world/block.d.ts.map +1 -0
- package/dist/{base → world}/block.js +13 -13
- package/dist/world/block.js.map +1 -0
- package/dist/{base → world}/entity.d.ts +4 -4
- package/dist/world/entity.d.ts.map +1 -0
- package/dist/{base → world}/entity.js +6 -6
- package/dist/world/entity.js.map +1 -0
- package/dist/world/state.d.ts +151 -0
- package/dist/world/state.d.ts.map +1 -0
- package/dist/world/state.js +108 -0
- package/dist/world/state.js.map +1 -0
- package/dist/world/tick.d.ts.map +1 -0
- package/dist/world/tick.js.map +1 -0
- package/package.json +7 -2
- package/src/base/error.ts +36 -0
- package/src/base/typing.ts +9 -1
- package/src/client/client.ts +99 -42
- package/src/client/tcp.ts +49 -954
- package/src/index.ts +12 -12
- package/src/{translator → packet}/decoder.ts +3 -3
- package/src/packet/dispatcher.ts +785 -0
- package/src/{translator → packet}/encoder.ts +20 -9
- package/src/{base → physics}/aabb.ts +5 -5
- package/src/{base → physics}/player.ts +41 -25
- package/src/version/codec.ts +227 -0
- package/src/version/registry.ts +333 -0
- package/src/{base → world}/block.ts +12 -11
- package/src/{base → world}/entity.ts +5 -5
- package/src/world/state.ts +232 -0
- package/test/adapter/index.ts +14 -0
- package/test/client/index.ts +1 -1
- package/test/json/index.ts +3 -0
- package/dist/base/aabb.d.ts.map +0 -1
- package/dist/base/aabb.js.map +0 -1
- package/dist/base/block.d.ts.map +0 -1
- package/dist/base/block.js.map +0 -1
- package/dist/base/direction.d.ts.map +0 -1
- package/dist/base/direction.js.map +0 -1
- package/dist/base/entity.d.ts.map +0 -1
- package/dist/base/entity.js.map +0 -1
- package/dist/base/player.d.ts.map +0 -1
- package/dist/base/player.js.map +0 -1
- package/dist/base/registry.d.ts +0 -50
- package/dist/base/registry.d.ts.map +0 -1
- package/dist/base/registry.js +0 -89
- package/dist/base/registry.js.map +0 -1
- package/dist/base/tick.d.ts.map +0 -1
- package/dist/base/tick.js.map +0 -1
- package/dist/translator/decoder.d.ts.map +0 -1
- package/dist/translator/decoder.js.map +0 -1
- package/dist/translator/encoder.d.ts.map +0 -1
- package/dist/translator/encoder.js.map +0 -1
- package/dist/translator/static.d.ts.map +0 -1
- package/dist/translator/static.js.map +0 -1
- package/src/base/registry.ts +0 -87
- /package/assets/minecraft/{blocks.json → 26.2/blocks.json} +0 -0
- /package/assets/minecraft/{entities.json → 26.2/entities.json} +0 -0
- /package/dist/{translator → packet}/static.d.ts +0 -0
- /package/dist/{translator → packet}/static.js +0 -0
- /package/dist/{base → physics}/direction.d.ts +0 -0
- /package/dist/{base → physics}/direction.js +0 -0
- /package/dist/{base → world}/tick.d.ts +0 -0
- /package/dist/{base → world}/tick.js +0 -0
- /package/src/{translator → packet}/static.ts +0 -0
- /package/src/{base → physics}/direction.ts +0 -0
- /package/src/{base → world}/tick.ts +0 -0
package/src/client/tcp.ts
CHANGED
|
@@ -1,142 +1,19 @@
|
|
|
1
1
|
import { Socket, SocketConstructorOpts } from "node:net";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
-
import {
|
|
4
|
-
constants as crypto_constants,
|
|
5
|
-
createPublicKey,
|
|
6
|
-
publicEncrypt,
|
|
7
|
-
randomBytes,
|
|
8
|
-
createDecipheriv,
|
|
9
|
-
createCipheriv,
|
|
10
|
-
Cipheriv,
|
|
11
|
-
Decipheriv
|
|
12
|
-
} from "node:crypto";
|
|
13
3
|
import { deflateSync, inflateSync } from "node:zlib";
|
|
14
4
|
|
|
15
5
|
import { TypedEmmiter } from "../base/event";
|
|
16
|
-
import { BinaryDecoder
|
|
17
|
-
import { BinaryEncoder } from "../
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { AuthClient, AuthOption } from "./auth";
|
|
6
|
+
import { BinaryDecoder } from "../packet/decoder";
|
|
7
|
+
import { BinaryEncoder } from "../packet/encoder";
|
|
8
|
+
import { AuthOption } from "./auth";
|
|
9
|
+
import { ClientStatus, ConnectionState, SharedState } from "../world/state";
|
|
10
|
+
import { EncodeResult } from "../version/codec";
|
|
11
|
+
import { TextComponent } from "../base/typing";
|
|
12
|
+
import { MissingAuthOption, SockerIsNotWritable } from "../base/error";
|
|
24
13
|
|
|
25
14
|
// Minecraft related typing
|
|
26
|
-
/**
|
|
27
|
-
* Representing a TextComponent
|
|
28
|
-
*
|
|
29
|
-
* @see https://minecraft.wiki/w/Text_component_format
|
|
30
|
-
*/
|
|
31
|
-
export type TextComponent = Record<string, any>;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
export interface Server {
|
|
35
|
-
knownPacks?: ServerKnownPack
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface ServerKnownPack {
|
|
39
|
-
namespace: string,
|
|
40
|
-
id: string,
|
|
41
|
-
version: string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Represent world information
|
|
46
|
-
*/
|
|
47
|
-
export interface ServerWorld {
|
|
48
|
-
/** Is hardcore enabled */
|
|
49
|
-
hardcore: boolean,
|
|
50
|
-
/** All dimensions */
|
|
51
|
-
dimensions: string[],
|
|
52
|
-
/** Max players allowed */
|
|
53
|
-
maxPlayers: number,
|
|
54
|
-
/** View distances */
|
|
55
|
-
viewDistance: number,
|
|
56
|
-
/** Simulation distance */
|
|
57
|
-
simulationDistance: number,
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* The record key is a chunk section position packed as `Player Dimension:Chunk X:Chunk Z[Section Y]`
|
|
61
|
-
*
|
|
62
|
-
* For `Section Y`, a chunk is splitted into several cubic 16x16x16 sections, or in other word, a chunk is a bund of 16x16x16 sections stacked on each other.
|
|
63
|
-
*/
|
|
64
|
-
chunks: Record<string, ChunkColumn>,
|
|
65
|
-
/**
|
|
66
|
-
* The record key is entity ID
|
|
67
|
-
*/
|
|
68
|
-
entities: Record<number, Entity>,
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Represent a chunk collumns, which may contains multiple chunks sections
|
|
73
|
-
*/
|
|
74
|
-
export interface ChunkColumn {
|
|
75
|
-
sections: Record<number, ChunkSection>,
|
|
76
|
-
blockEntities: Record<number, BlockEntity>
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface ChunkSection {
|
|
80
|
-
block: PaletteContainer,
|
|
81
|
-
biome: PaletteContainer,
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Used for indirect mapping
|
|
86
|
-
*
|
|
87
|
-
* @see https://minecraft.wiki/w/Java_Edition_protocol/Chunk_format#Paletted_Container_structure
|
|
88
|
-
*/
|
|
89
|
-
export interface PaletteContainer {
|
|
90
|
-
bpe: number, // Bit per entry
|
|
91
|
-
palette: number[],
|
|
92
|
-
data: BigInt64Array | null, // null if the whole section contains one 1 type of block
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** Represent a block entity */
|
|
96
|
-
export interface BlockEntity {
|
|
97
|
-
type: number,
|
|
98
|
-
data: Record<string, any>
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Represent an entity */
|
|
102
|
-
export interface Entity {
|
|
103
|
-
id: number,
|
|
104
|
-
type: number,
|
|
105
|
-
position: BaseVec3,
|
|
106
|
-
velocity: BaseVec3,
|
|
107
|
-
angle: Angle,
|
|
108
|
-
data: number
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export enum GameMode {
|
|
112
|
-
Survival = 0,
|
|
113
|
-
Creative = 1,
|
|
114
|
-
Adventure = 2,
|
|
115
|
-
Spectator = 3
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export interface ClientPlayer {
|
|
119
|
-
/** Player UUID */
|
|
120
|
-
uuid: string,
|
|
121
|
-
/** Player username */
|
|
122
|
-
username: string,
|
|
123
|
-
/** Player entity ID */
|
|
124
|
-
entityId: number,
|
|
125
|
-
/** Current game mode */
|
|
126
|
-
gameMode: GameMode,
|
|
127
|
-
|
|
128
|
-
position: BaseVec3,
|
|
129
|
-
velocity: BaseVec3,
|
|
130
|
-
angle: Angle,
|
|
131
15
|
|
|
132
|
-
/** Current dimension */
|
|
133
|
-
dimension: string
|
|
134
|
-
}
|
|
135
16
|
|
|
136
|
-
export interface ServerRegistryEntry {
|
|
137
|
-
id: string,
|
|
138
|
-
data: object
|
|
139
|
-
}
|
|
140
17
|
|
|
141
18
|
// TCP related typing
|
|
142
19
|
export interface TCPClientOption {
|
|
@@ -175,71 +52,21 @@ export interface TCPClientOption {
|
|
|
175
52
|
|
|
176
53
|
export interface TCPClientEvents {
|
|
177
54
|
connect: [],
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
raw: [buf: Buffer],
|
|
181
|
-
|
|
182
|
-
/** Whenever client is ready to use */
|
|
183
|
-
ready: [readyClient: TCPClient<true>],
|
|
184
|
-
|
|
185
|
-
// Position
|
|
186
|
-
/**
|
|
187
|
-
* Sync player position.
|
|
188
|
-
*
|
|
189
|
-
* Noted that this fired when recived packet from server, not from the internal movement, meaning that calling `input` function would not fire this event.
|
|
190
|
-
*/
|
|
191
|
-
playerPosition: [position: BaseVec3],
|
|
192
|
-
|
|
193
|
-
/** Load chunk section */
|
|
194
|
-
loadChunk: [chunkX: number, sectionY: number, chunkZ: number],
|
|
195
|
-
/** Unload chunk column */
|
|
196
|
-
unloadChunk: [chunkX: number, chunkZ: number],
|
|
197
|
-
|
|
198
|
-
/** Spawn an entity */
|
|
199
|
-
spawnEntity: [entity: Entity],
|
|
200
|
-
/** Update spawned entity */
|
|
201
|
-
updateEntity: [entity: Entity],
|
|
202
|
-
/** Remove entity */
|
|
203
|
-
removeEntity: [entityId: number],
|
|
204
|
-
|
|
205
|
-
// Chat
|
|
206
|
-
/** Message, usually sent from player */
|
|
207
|
-
message: [message: Message],
|
|
208
|
-
/** Parsed system message, usually sent from the console, plugins, etc... */
|
|
209
|
-
systemMessage: [message: string],
|
|
210
|
-
/** Raw system messgae */
|
|
211
|
-
systemMessageRaw: [textComponent: TextComponent],
|
|
212
|
-
/** Parsed action bar message, the message appeared above your hot bar */
|
|
213
|
-
actionBar: [message: string],
|
|
214
|
-
/** Raw action bar */
|
|
215
|
-
actionBarRaw: [textComponent: TextComponent],
|
|
55
|
+
raw: [buffer: Buffer]
|
|
56
|
+
destroy: [],
|
|
216
57
|
}
|
|
217
58
|
|
|
218
59
|
export interface Message {
|
|
219
60
|
sender: string,
|
|
220
|
-
target
|
|
61
|
+
target: string | undefined,
|
|
221
62
|
content: string,
|
|
222
63
|
raw: {
|
|
223
64
|
sender: TextComponent,
|
|
224
|
-
target
|
|
225
|
-
content
|
|
65
|
+
target: TextComponent | undefined,
|
|
66
|
+
content: TextComponent | undefined
|
|
226
67
|
}
|
|
227
68
|
}
|
|
228
69
|
|
|
229
|
-
export enum ClientStatus {
|
|
230
|
-
Disconnected,
|
|
231
|
-
Connecting,
|
|
232
|
-
Logining,
|
|
233
|
-
Ready,
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export enum ClientState {
|
|
237
|
-
Disconnected,
|
|
238
|
-
Handshake,
|
|
239
|
-
Login,
|
|
240
|
-
Configure,
|
|
241
|
-
Play
|
|
242
|
-
}
|
|
243
70
|
|
|
244
71
|
// export enum TCPServerIntent {
|
|
245
72
|
// Status = 1,
|
|
@@ -252,11 +79,9 @@ export enum ClientState {
|
|
|
252
79
|
*
|
|
253
80
|
* @see https://minecraft.wiki/w/Java_Edition_protocol/Packets?oldid=3445844
|
|
254
81
|
*/
|
|
255
|
-
export class TCPClient
|
|
82
|
+
export class TCPClient extends (EventEmitter as new () => TypedEmmiter<TCPClientEvents>) {
|
|
256
83
|
public readonly socket: Socket;
|
|
257
84
|
|
|
258
|
-
public server: Server | undefined = undefined;
|
|
259
|
-
|
|
260
85
|
// Internal var
|
|
261
86
|
/**
|
|
262
87
|
* | Value | Meaning |
|
|
@@ -265,56 +90,29 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
265
90
|
* | -1 | dont compress |
|
|
266
91
|
* | > 0 | compression threshold |
|
|
267
92
|
*/
|
|
268
|
-
private compressionThreshold: number = 0;
|
|
269
93
|
private bufferPool: Buffer = Buffer.alloc(0);
|
|
270
|
-
public status: ClientStatus = ClientStatus.Disconnected;
|
|
271
|
-
private state: ClientState = ClientState.Disconnected;
|
|
272
|
-
|
|
273
|
-
// Encryption
|
|
274
|
-
private sharedSecret: Buffer | undefined = undefined;
|
|
275
|
-
private cipher: Cipheriv | undefined = undefined;
|
|
276
|
-
private decipher: Decipheriv | undefined = undefined;
|
|
277
|
-
|
|
278
|
-
// Play data
|
|
279
|
-
public world?: If<IsReady, ServerWorld>;
|
|
280
|
-
public player?: If<IsReady, ClientPlayer>;
|
|
281
|
-
public registry?: If<IsReady, Record<string, ServerRegistryEntry[]>>;
|
|
282
|
-
|
|
283
|
-
// Initial data
|
|
284
|
-
private playerUUID?: Buffer;
|
|
285
94
|
|
|
286
95
|
constructor(
|
|
96
|
+
private state: SharedState,
|
|
287
97
|
public readonly option: TCPClientOption,
|
|
288
|
-
socketOption?: SocketConstructorOpts
|
|
98
|
+
socketOption?: SocketConstructorOpts,
|
|
289
99
|
) {
|
|
290
100
|
super();
|
|
291
101
|
this.socket = new Socket(socketOption);
|
|
292
|
-
this.wipePlayData();
|
|
293
102
|
|
|
294
103
|
if (!option.isOffline && !option.auth)
|
|
295
104
|
throw new MissingAuthOption();
|
|
296
105
|
}
|
|
297
106
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
this.registry = null as any;
|
|
302
|
-
}
|
|
107
|
+
public sendInitPacket: () => void = () => { throw new Error("method not implemented"); };
|
|
108
|
+
public forwardPacket: (packetId: number, decoder: BinaryDecoder) => void = (...args) => { throw new Error("method not implemented"); };
|
|
109
|
+
public parsePacket: (packetId: string | number, data: object) => EncodeResult = (...args) => { throw new Error("method not implemented"); };
|
|
303
110
|
|
|
304
111
|
/**
|
|
305
112
|
* Connect to server
|
|
306
113
|
*/
|
|
307
114
|
public async connect() {
|
|
308
|
-
|
|
309
|
-
if (this.option.auth) {
|
|
310
|
-
const authClient = new AuthClient(this.option.auth);
|
|
311
|
-
const { uuid } = await authClient.auth();
|
|
312
|
-
this.playerUUID = Buffer.from(uuid);
|
|
313
|
-
} else
|
|
314
|
-
this.playerUUID = computeUUID(this.option.playerName);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
this.status = ClientStatus.Connecting;
|
|
115
|
+
this.state.status = ClientStatus.Connecting;
|
|
318
116
|
const connection = this.socket.connect({
|
|
319
117
|
host: this.option.host,
|
|
320
118
|
port: this.option.port
|
|
@@ -322,30 +120,30 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
322
120
|
|
|
323
121
|
connection.on("connect", () => {
|
|
324
122
|
this.emit("connect");
|
|
325
|
-
this.
|
|
326
|
-
this.sendHandshake();
|
|
327
|
-
this.sendLoginStart();
|
|
123
|
+
this.sendInitPacket();
|
|
328
124
|
});
|
|
329
125
|
connection.on("data", (data) => {
|
|
330
126
|
let buf = Buffer.from(data);
|
|
331
|
-
if (this.decipher) {
|
|
332
|
-
buf = this.decipher.update(buf);
|
|
127
|
+
if (this.state.decipher) {
|
|
128
|
+
buf = this.state.decipher.update(buf);
|
|
333
129
|
}
|
|
334
130
|
this.emit("raw", Buffer.from(buf));
|
|
335
131
|
this.bufferPool = Buffer.concat([this.bufferPool, buf]);
|
|
336
132
|
this.handlePacket();
|
|
337
133
|
});
|
|
338
134
|
connection.once("end", () => {
|
|
339
|
-
|
|
340
|
-
this.status
|
|
341
|
-
this.state =
|
|
342
|
-
this.
|
|
135
|
+
this.emit("destroy");
|
|
136
|
+
if (this.state.status === ClientStatus.Disconnected) return;
|
|
137
|
+
this.state.status = ClientStatus.Disconnected;
|
|
138
|
+
this.state.state = ConnectionState.Disconnected;
|
|
139
|
+
// this.emit("disconnect", "socket close");
|
|
343
140
|
});
|
|
344
141
|
connection.once("close", () => {
|
|
345
|
-
|
|
346
|
-
this.status
|
|
347
|
-
this.state =
|
|
348
|
-
this.
|
|
142
|
+
this.emit("destroy");
|
|
143
|
+
if (this.state.status === ClientStatus.Disconnected) return;
|
|
144
|
+
this.state.status = ClientStatus.Disconnected;
|
|
145
|
+
this.state.state = ConnectionState.Disconnected;
|
|
146
|
+
// this.emit("disconnect", "socket close");
|
|
349
147
|
});
|
|
350
148
|
}
|
|
351
149
|
|
|
@@ -354,16 +152,6 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
354
152
|
this.socket.destroy();
|
|
355
153
|
}
|
|
356
154
|
|
|
357
|
-
public isReady(): this is TCPClient<true> {
|
|
358
|
-
return this.status === ClientStatus.Ready;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
public checkReady() {
|
|
362
|
-
if (!this.isReady())
|
|
363
|
-
throw new ClientNotReady();
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
155
|
/*
|
|
368
156
|
* Read packet
|
|
369
157
|
*/
|
|
@@ -384,9 +172,9 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
384
172
|
const expectedPacketEnd = decoder.offset + packetLength;
|
|
385
173
|
|
|
386
174
|
let packetID: number;
|
|
387
|
-
if (this.compressionThreshold === 0)
|
|
175
|
+
if (this.state.compressionThreshold === 0)
|
|
388
176
|
packetID = decoder.readVarInt();
|
|
389
|
-
else if (this.compressionThreshold === -1) {
|
|
177
|
+
else if (this.state.compressionThreshold === -1) {
|
|
390
178
|
decoder.readVarInt();
|
|
391
179
|
packetID = decoder.readVarInt();
|
|
392
180
|
} else {
|
|
@@ -400,600 +188,38 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
400
188
|
packetID = decoder.readVarInt();
|
|
401
189
|
}
|
|
402
190
|
}
|
|
403
|
-
|
|
404
|
-
// Debug
|
|
405
|
-
if (this.option.debug?.packetLogger)
|
|
406
|
-
console.dir({
|
|
407
|
-
state: ClientState[this.state],
|
|
408
|
-
packetID: "0x" + packetID.toString(16),
|
|
409
|
-
length: decoder.buffer.length
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
switch (packetID) {
|
|
413
|
-
case 0x00:
|
|
414
|
-
if (this.state === ClientState.Login) this.handleLoginDisconnect(decoder);
|
|
415
|
-
break;
|
|
416
|
-
case 0x01:
|
|
417
|
-
if (this.state === ClientState.Login) this.handleEncryption(decoder);
|
|
418
|
-
if (this.state === ClientState.Play) this.handleSpawnEntity(decoder);
|
|
419
|
-
break;
|
|
420
|
-
case 0x02:
|
|
421
|
-
if (this.state === ClientState.Configure) this.handleConfiguarionPlayDisconnect(decoder);
|
|
422
|
-
if (this.state === ClientState.Login) this.handleLoginSucess(decoder);
|
|
423
|
-
break;
|
|
424
|
-
case 0x03:
|
|
425
|
-
if (this.state === ClientState.Login) this.handleSetCompression(decoder);
|
|
426
|
-
if (this.state === ClientState.Configure) this.sendConfigureAck();
|
|
427
|
-
break;
|
|
428
|
-
case 0x06:
|
|
429
|
-
if (this.state === ClientState.Play) this.handleBlockEntityData(decoder);
|
|
430
|
-
break;
|
|
431
|
-
case 0x07:
|
|
432
|
-
if (this.state === ClientState.Configure) this.handleRegistryData(decoder);
|
|
433
|
-
break;
|
|
434
|
-
case 0x0A:
|
|
435
|
-
if (this.state === ClientState.Play) this.handleChangeGameMode(decoder);
|
|
436
|
-
break;
|
|
437
|
-
case 0x0E:
|
|
438
|
-
if (this.state === ClientState.Configure) this.handleKnownPack(decoder);
|
|
439
|
-
break;
|
|
440
|
-
case 0x20:
|
|
441
|
-
if (this.state === ClientState.Play) this.handleConfiguarionPlayDisconnect(decoder);
|
|
442
|
-
break;
|
|
443
|
-
case 0x21:
|
|
444
|
-
if (this.state === ClientState.Play) this.handleDisguisedChatMessage(decoder);
|
|
445
|
-
break;
|
|
446
|
-
case 0x23:
|
|
447
|
-
if (this.state === ClientState.Play) this.handleTeleportEntity(decoder);
|
|
448
|
-
break;
|
|
449
|
-
case 0x25:
|
|
450
|
-
if (this.state === ClientState.Play) this.handleUnloadChunk(decoder);
|
|
451
|
-
break;
|
|
452
|
-
case 0x2D:
|
|
453
|
-
if (this.state === ClientState.Play) this.handleChunkData(decoder);
|
|
454
|
-
break;
|
|
455
|
-
case 0x2C:
|
|
456
|
-
if (this.state === ClientState.Play) this.handleKeepAlive(decoder);
|
|
457
|
-
break;
|
|
458
|
-
case 0x31:
|
|
459
|
-
if (this.state === ClientState.Play) this.handlePlayLogin(decoder);
|
|
460
|
-
break;
|
|
461
|
-
case 0x35:
|
|
462
|
-
if (this.state === ClientState.Play) this.handleUpdateEntityPosition(decoder);
|
|
463
|
-
break;
|
|
464
|
-
case 0x36:
|
|
465
|
-
if (this.state === ClientState.Play) this.handleUpdateEntityPositionRotation(decoder);
|
|
466
|
-
break;
|
|
467
|
-
case 0x41:
|
|
468
|
-
if (this.state === ClientState.Play) this.handlePlayerChat(decoder);
|
|
469
|
-
break;
|
|
470
|
-
case 0x48:
|
|
471
|
-
if (this.state === ClientState.Play) this.handleSynchronizePlayerPosition(decoder);
|
|
472
|
-
break;
|
|
473
|
-
case 0x4D:
|
|
474
|
-
if (this.state === ClientState.Play) this.handleRemoveEntity(decoder);
|
|
475
|
-
break;
|
|
476
|
-
case 0x57:
|
|
477
|
-
if (this.state === ClientState.Play) this.handleSetActionBar(decoder);
|
|
478
|
-
break;
|
|
479
|
-
case 0x65:
|
|
480
|
-
if (this.state === ClientState.Play) this.handleSetEntityVelocity(decoder);
|
|
481
|
-
break;
|
|
482
|
-
case 0x79:
|
|
483
|
-
if (this.state === ClientState.Play) this.handleSystemMessage(decoder);
|
|
484
|
-
break;
|
|
485
|
-
}
|
|
191
|
+
this.forwardPacket(packetID, decoder);
|
|
486
192
|
|
|
487
193
|
this.bufferPool = this.bufferPool.subarray(expectedPacketEnd);
|
|
488
194
|
}
|
|
489
195
|
}
|
|
490
196
|
|
|
491
|
-
// Login
|
|
492
|
-
|
|
493
|
-
private handleLoginDisconnect(decoder: BinaryDecoder) {
|
|
494
|
-
const reason = JSON.parse(decoder.readString());
|
|
495
|
-
const text = getTextFromTextComponent(reason);
|
|
496
|
-
|
|
497
|
-
this.status = ClientStatus.Disconnected;
|
|
498
|
-
this.state = ClientState.Disconnected;
|
|
499
|
-
this.wipePlayData();
|
|
500
|
-
|
|
501
|
-
this.emit("disconnect", text.toString());
|
|
502
|
-
this.emit("disconnectRaw", reason);
|
|
503
|
-
this.disconnect();
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
private handleEncryption(decoder: BinaryDecoder) {
|
|
507
|
-
const serverId = decoder.readString(); // Should be ignored in 1.7+ server
|
|
508
|
-
const publicKey = decoder.readRawPrefixedArray(8);
|
|
509
|
-
const verifyToken = decoder.readRawPrefixedArray(8);
|
|
510
|
-
const shoudlCallMojang = decoder.readBoolean(); // I'm too lazy to do this D:
|
|
511
|
-
|
|
512
|
-
const sharedSecret = randomBytes(16);
|
|
513
|
-
this.sharedSecret = sharedSecret;
|
|
514
|
-
this.cipher = createCipheriv("aes-128-cfb8", this.sharedSecret, this.sharedSecret);
|
|
515
|
-
this.decipher = createDecipheriv("aes-128-cfb8", this.sharedSecret, this.sharedSecret);
|
|
516
|
-
|
|
517
|
-
this.sendEncryptionResponse(publicKey, verifyToken);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
private handleSetCompression(decoder: BinaryDecoder) {
|
|
521
|
-
const threshold = decoder.readVarInt();
|
|
522
|
-
if (threshold < 0) this.compressionThreshold = -1;
|
|
523
|
-
else this.compressionThreshold = threshold;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
private handleLoginSucess(decoder: BinaryDecoder) {
|
|
527
|
-
const uuid = decoder.readUUID();
|
|
528
|
-
const username = decoder.readString();
|
|
529
|
-
this.player = {
|
|
530
|
-
uuid,
|
|
531
|
-
username,
|
|
532
|
-
entityId: 0,
|
|
533
|
-
dimension: "",
|
|
534
|
-
gameMode: GameMode.Survival,
|
|
535
|
-
|
|
536
|
-
position: {
|
|
537
|
-
x: 0,
|
|
538
|
-
y: 0,
|
|
539
|
-
z: 0,
|
|
540
|
-
},
|
|
541
|
-
velocity: {
|
|
542
|
-
x: 0,
|
|
543
|
-
y: 0,
|
|
544
|
-
z: 0,
|
|
545
|
-
},
|
|
546
|
-
angle: {
|
|
547
|
-
yaw: 0,
|
|
548
|
-
pitch: 0,
|
|
549
|
-
}
|
|
550
|
-
} satisfies ClientPlayer as any;
|
|
551
|
-
this.sendLoginAck();
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// Configure
|
|
555
|
-
|
|
556
|
-
private handleKnownPack(decoder: BinaryDecoder) {
|
|
557
|
-
const packs = decoder.readPrefixedArray((decoder) => ({
|
|
558
|
-
namespace: decoder.readString(),
|
|
559
|
-
id: decoder.readString(),
|
|
560
|
-
version: decoder.readString()
|
|
561
|
-
}));
|
|
562
|
-
this.server!.knownPacks = packs satisfies ServerKnownPack[] as any;
|
|
563
|
-
// this.sendKnownPack(this.option.loadRegistry === true ? [] : packs);
|
|
564
|
-
this.sendKnownPack([]);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
private handleRegistryData(decoder: BinaryDecoder) {
|
|
568
|
-
const id = decoder.readString();
|
|
569
|
-
const entries = decoder.readPrefixedArray((decoder) => ({
|
|
570
|
-
id: decoder.readString(),
|
|
571
|
-
data: decoder.readPrefixedOptional((decoder) => decoder.readNBT())
|
|
572
|
-
}));
|
|
573
|
-
|
|
574
|
-
if (!this.registry) this.registry = {} as any;
|
|
575
|
-
const registry = this.registry as Record<string, any[]>;
|
|
576
|
-
if (!(id in registry)) registry[id] = [];
|
|
577
|
-
registry[id]!.push(...entries);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
private handleConfiguarionPlayDisconnect(decoder: BinaryDecoder) {
|
|
581
|
-
const reason = decoder.readNBT();
|
|
582
|
-
const text = getTextFromTextComponent(reason);
|
|
583
|
-
|
|
584
|
-
this.status = ClientStatus.Disconnected;
|
|
585
|
-
this.state = ClientState.Disconnected;
|
|
586
|
-
this.wipePlayData();
|
|
587
|
-
|
|
588
|
-
this.emit("disconnect", text.toString());
|
|
589
|
-
this.emit("disconnectRaw", reason);
|
|
590
|
-
this.disconnect();
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// Play
|
|
594
|
-
|
|
595
|
-
private handlePlayLogin(decoder: BinaryDecoder) {
|
|
596
|
-
const entityId = decoder.readInt(),
|
|
597
|
-
isHardcore = decoder.readBoolean(),
|
|
598
|
-
dimensions = decoder.readPrefixedArray((decoder) => decoder.readString()),
|
|
599
|
-
maxPlayers = decoder.readVarInt(),
|
|
600
|
-
viewDistance = decoder.readVarInt(),
|
|
601
|
-
simulationDistance = decoder.readVarInt();
|
|
602
|
-
decoder.readBoolean(); // Reduced Debug Info
|
|
603
|
-
decoder.readBoolean(); // Enable respawn screen
|
|
604
|
-
decoder.readBoolean(); // Do limited crafting
|
|
605
|
-
decoder.readVarInt(); // Dimension Type
|
|
606
|
-
const dimensionName = decoder.readString();
|
|
607
|
-
decoder.readLong(); // Hashed seed
|
|
608
|
-
const gameMode = decoder.readUByte();
|
|
609
|
-
|
|
610
|
-
this.world = {
|
|
611
|
-
hardcore: isHardcore,
|
|
612
|
-
dimensions,
|
|
613
|
-
maxPlayers,
|
|
614
|
-
viewDistance,
|
|
615
|
-
simulationDistance,
|
|
616
|
-
chunks: {},
|
|
617
|
-
entities: {}
|
|
618
|
-
} satisfies ServerWorld as any; // To avoid type conflict
|
|
619
|
-
this.player!.dimension = dimensionName;
|
|
620
|
-
this.player!.entityId = entityId;
|
|
621
|
-
this.player!.gameMode = gameMode;
|
|
622
|
-
|
|
623
|
-
this.status = ClientStatus.Ready;
|
|
624
|
-
this.emit("ready", this as TCPClient<true>);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
private handleSynchronizePlayerPosition(decoder: BinaryDecoder) {
|
|
628
|
-
const teleportId = decoder.readVarInt();
|
|
629
|
-
const x = decoder.readDouble(),
|
|
630
|
-
y = decoder.readDouble(),
|
|
631
|
-
z = decoder.readDouble(),
|
|
632
|
-
velX = decoder.readDouble(),
|
|
633
|
-
velY = decoder.readDouble(),
|
|
634
|
-
velZ = decoder.readDouble(),
|
|
635
|
-
yaw = decoder.readFloat() % 360,
|
|
636
|
-
pitch = decoder.readFloat() % 360;
|
|
637
|
-
const flag = decoder.readTeleportFlag();
|
|
638
|
-
|
|
639
|
-
const { position, velocity, angle } = this.player!;
|
|
640
|
-
let { x: newPosX, y: newPosY, z: newPosZ } = position;
|
|
641
|
-
let { x: newVelX, y: newVelY, z: newVelZ } = velocity;
|
|
642
|
-
let { yaw: newYaw, pitch: newPitch } = angle;
|
|
643
|
-
if (flag.x) newPosX += x; else newPosX = x;
|
|
644
|
-
if (flag.y) newPosY += y; else newPosY = y;
|
|
645
|
-
if (flag.z) newPosZ += z; else newPosZ = z;
|
|
646
|
-
if (flag.velX) newVelX += velX; else newVelX = velX;
|
|
647
|
-
if (flag.velY) newVelY += velY; else newVelY = velY;
|
|
648
|
-
if (flag.velZ) newVelZ += velZ; else newVelZ = velZ;
|
|
649
|
-
if (flag.yaw) newYaw += yaw; else newYaw = yaw;
|
|
650
|
-
if (flag.pitch) newPitch += pitch; else newPitch = pitch;
|
|
651
|
-
const newPosition = { x: newPosX, y: newPosY, z: newPosZ },
|
|
652
|
-
newVelocity = { x: newVelX, y: newVelY, z: newVelZ },
|
|
653
|
-
newAngle = { yaw: newYaw, pitch: newPitch };
|
|
654
|
-
this.player = {
|
|
655
|
-
...this.player!,
|
|
656
|
-
position: newPosition,
|
|
657
|
-
velocity: newVelocity,
|
|
658
|
-
angle: newAngle,
|
|
659
|
-
} satisfies ClientPlayer as any;
|
|
660
|
-
this.emit("playerPosition", { x: newPosX, y: newPosY, z: newPosZ });
|
|
661
|
-
this.sendConfirmTeleportation(teleportId);
|
|
662
|
-
this.sendPlayerPosRot(newPosition, newAngle, true, false);
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
private handleKeepAlive(decoder: BinaryDecoder) {
|
|
666
|
-
const aliveID = decoder.readLong();
|
|
667
|
-
this.sendKeepAlive(aliveID);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
private handleChangeGameMode(decoder: BinaryDecoder) {
|
|
671
|
-
const gameMode = decoder.readUByte();
|
|
672
|
-
this.player!.gameMode = gameMode;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// Chunk
|
|
676
|
-
private handleChunkData(decoder: BinaryDecoder) {
|
|
677
|
-
const chunkX = decoder.readInt(),
|
|
678
|
-
chunkZ = decoder.readInt(),
|
|
679
|
-
heightMaps = decoder.readPrefixedArray(decoder => ({
|
|
680
|
-
type: decoder.readVarInt(),
|
|
681
|
-
data: decoder.readPrefixedArray(decoder => decoder.readLong())
|
|
682
|
-
})), // Not used yet
|
|
683
|
-
chunkData = decoder.readPrefixedArray(decoder => decoder.readByte()),
|
|
684
|
-
blockEntities = decoder.readPrefixedArray(decoder => {
|
|
685
|
-
const packedXZ = decoder.readByte(),
|
|
686
|
-
y = decoder.readShort(),
|
|
687
|
-
type = decoder.readVarInt(),
|
|
688
|
-
nbt = decoder.readNBT();
|
|
689
|
-
const x = (packedXZ >> 4) & 15, z = packedXZ & 15;
|
|
690
|
-
const packedPosition = packBlockPos(x, y, z);
|
|
691
|
-
return { packedPosition, type, nbt };
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
// this.world!.chunks[`${this.player!.dimension}:${chunkX}:${chunkZ}`] = [];
|
|
695
|
-
const chunkSections: Record<number, ChunkSection> = {};
|
|
696
|
-
|
|
697
|
-
const chunkDataDecoder = new BinaryDecoder(Buffer.from(chunkData));
|
|
698
|
-
for (let height = 0; height < (SectionsPerChunk[this.player!.dimension] || 0); height++) {
|
|
699
|
-
const blockCount = chunkDataDecoder.readShort();
|
|
700
|
-
const fluidCount = chunkDataDecoder.readShort();
|
|
701
|
-
|
|
702
|
-
// Blocks
|
|
703
|
-
let blockStateBPE = chunkDataDecoder.readUByte();
|
|
704
|
-
let blockStatePalettes: number[] = [];
|
|
705
|
-
|
|
706
|
-
if (blockStateBPE === 0) {
|
|
707
|
-
blockStatePalettes = [chunkDataDecoder.readVarInt()];
|
|
708
|
-
} else if (blockStateBPE <= 8) {
|
|
709
|
-
// Indirect palette
|
|
710
|
-
if (blockStateBPE < 4) blockStateBPE = 4; // BPE smaller than 4 will be rounđe up to 4
|
|
711
|
-
blockStatePalettes = chunkDataDecoder.readPrefixedArray(decoder => decoder.readVarInt());
|
|
712
|
-
} else {
|
|
713
|
-
// Direct palette (no palette array)
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
const blockEntriesPerSection = 4096;
|
|
717
|
-
const blockEntriesPerLong = Math.floor(64 / blockStateBPE);
|
|
718
|
-
let blockDataArray: BigInt64Array | null = null;
|
|
719
|
-
if (blockStateBPE !== 0) {
|
|
720
|
-
const blockDataArrayLength = Math.floor((blockEntriesPerSection + blockEntriesPerLong - 1) / blockEntriesPerLong);
|
|
721
|
-
blockDataArray = new BigInt64Array(chunkDataDecoder.readArray(blockDataArrayLength, (decoder) => decoder.readLong()));
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
// Biomes - Not used yet, but need to be read to advance decoder
|
|
725
|
-
const biomeBPE = chunkDataDecoder.readUByte();
|
|
726
|
-
let biomePalettes: number[] = [];
|
|
727
|
-
if (biomeBPE === 0) {
|
|
728
|
-
biomePalettes = [chunkDataDecoder.readVarInt()]; // Palette value
|
|
729
|
-
} else if (biomeBPE <= 3) {
|
|
730
|
-
// Indirect palette
|
|
731
|
-
biomePalettes = chunkDataDecoder.readPrefixedArray(decoder => decoder.readVarInt());
|
|
732
|
-
} else {
|
|
733
|
-
// Direct palette (no palette array)
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
const biomeEntriesPerSection = 64;
|
|
737
|
-
const biomeEntriesPerLong = Math.floor(64 / biomeBPE);
|
|
738
|
-
let biomeDataArray: BigInt64Array | null = null;
|
|
739
|
-
if (biomeBPE !== 0) {
|
|
740
|
-
const biomeDataArrayLength = Math.floor((biomeEntriesPerSection + biomeEntriesPerLong - 1) / biomeEntriesPerLong);
|
|
741
|
-
biomeDataArray = new BigInt64Array(chunkDataDecoder.readArray(biomeDataArrayLength, (decoder) => decoder.readLong()));
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
chunkSections[height] = {
|
|
745
|
-
block: {
|
|
746
|
-
bpe: blockStateBPE,
|
|
747
|
-
palette: blockStatePalettes,
|
|
748
|
-
data: blockDataArray
|
|
749
|
-
},
|
|
750
|
-
biome: {
|
|
751
|
-
bpe: biomeBPE,
|
|
752
|
-
palette: biomePalettes,
|
|
753
|
-
data: biomeDataArray
|
|
754
|
-
}
|
|
755
|
-
};
|
|
756
|
-
this.emit("loadChunk", chunkX, height, chunkZ);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
const blockEntitiesObj: Record<number, BlockEntity> = Object.fromEntries(
|
|
760
|
-
blockEntities.map(val => [val.packedPosition, { type: val.type, data: val.nbt }])
|
|
761
|
-
);
|
|
762
|
-
|
|
763
|
-
this.world!.chunks[`${this.player!.dimension}:${chunkX}:${chunkZ}`] = {
|
|
764
|
-
sections: chunkSections,
|
|
765
|
-
blockEntities: blockEntitiesObj
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
private handleUnloadChunk(decoder: BinaryDecoder) {
|
|
770
|
-
const chunkZ = decoder.readInt(),
|
|
771
|
-
chunkX = decoder.readInt();
|
|
772
|
-
delete this.world!.chunks[`${this.player!.dimension}:${chunkX}:${chunkZ}`];
|
|
773
|
-
this.emit("unloadChunk", chunkX, chunkZ);
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
private handleBlockEntityData(decoder: BinaryDecoder) {
|
|
777
|
-
const location = decoder.readPosition(),
|
|
778
|
-
type = decoder.readVarInt(),
|
|
779
|
-
data = decoder.readNBT();
|
|
780
|
-
|
|
781
|
-
const chunkX = Math.floor(location.x / 16),
|
|
782
|
-
chunkZ = Math.floor(location.z / 16);
|
|
783
|
-
const xWitinChunk = location.x % 16,
|
|
784
|
-
zWitinChunk = location.z % 16;
|
|
785
|
-
const packedPosition = packBlockPos(xWitinChunk, location.y, zWitinChunk);
|
|
786
|
-
if (`${this.player!.dimension}:${chunkX}:${chunkZ}` in this.world!.chunks)
|
|
787
|
-
this.world!.chunks[`${this.player!.dimension}:${chunkX}:${chunkZ}`]!.blockEntities[packedPosition] = {
|
|
788
|
-
type,
|
|
789
|
-
data
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// Entity
|
|
794
|
-
private handleSpawnEntity(decoder: BinaryDecoder) {
|
|
795
|
-
const id = decoder.readVarInt(),
|
|
796
|
-
UUID = decoder.readUUID(),
|
|
797
|
-
type = decoder.readVarInt(),
|
|
798
|
-
x = decoder.readDouble(),
|
|
799
|
-
y = decoder.readDouble(),
|
|
800
|
-
z = decoder.readDouble(),
|
|
801
|
-
velocity = decoder.readLpVec3(),
|
|
802
|
-
pitch = decoder.readAngle(),
|
|
803
|
-
yaw = decoder.readAngle(),
|
|
804
|
-
headYaw = decoder.readAngle(),
|
|
805
|
-
data = decoder.readVarInt();
|
|
806
|
-
|
|
807
|
-
this.world!.entities[id] = {
|
|
808
|
-
id,
|
|
809
|
-
type,
|
|
810
|
-
position: { x, y, z },
|
|
811
|
-
velocity,
|
|
812
|
-
angle: {
|
|
813
|
-
pitch,
|
|
814
|
-
yaw
|
|
815
|
-
},
|
|
816
|
-
data
|
|
817
|
-
};
|
|
818
|
-
this.emit("spawnEntity", this.world!.entities[id]!);
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
private handleTeleportEntity(decoder: BinaryDecoder) {
|
|
822
|
-
const id = decoder.readVarInt(),
|
|
823
|
-
x = decoder.readDouble(),
|
|
824
|
-
y = decoder.readDouble(),
|
|
825
|
-
z = decoder.readDouble(),
|
|
826
|
-
velX = decoder.readDouble(),
|
|
827
|
-
velY = decoder.readDouble(),
|
|
828
|
-
velZ = decoder.readDouble(),
|
|
829
|
-
pitch = decoder.readAngle(),
|
|
830
|
-
yaw = decoder.readAngle();
|
|
831
|
-
|
|
832
|
-
if (id in this.world!.entities) {
|
|
833
|
-
this.world!.entities[id]!.position = { x, y, z };
|
|
834
|
-
this.world!.entities[id]!.velocity = { x: velX, y: velY, z: velZ };
|
|
835
|
-
this.world!.entities[id]!.angle = { pitch, yaw };
|
|
836
|
-
this.emit("updateEntity", this.world!.entities[id]!);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
private handleUpdateEntityPosition(decoder: BinaryDecoder) {
|
|
841
|
-
const id = decoder.readVarInt(),
|
|
842
|
-
delX = decoder.readFixedPoint(decoder.readShort(), 12),
|
|
843
|
-
delY = decoder.readFixedPoint(decoder.readShort(), 12),
|
|
844
|
-
delZ = decoder.readFixedPoint(decoder.readShort(), 12);
|
|
845
|
-
|
|
846
|
-
if (id in this.world!.entities) {
|
|
847
|
-
let { x, y, z } = this.world!.entities[id]!.position;
|
|
848
|
-
x += delX;
|
|
849
|
-
y += delY;
|
|
850
|
-
z += delZ;
|
|
851
|
-
this.world!.entities[id]!.position = { x, y, z };
|
|
852
|
-
this.emit("updateEntity", this.world!.entities[id]!);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
private handleUpdateEntityPositionRotation(decoder: BinaryDecoder) {
|
|
857
|
-
const id = decoder.readVarInt(),
|
|
858
|
-
delX = decoder.readFixedPoint(decoder.readShort(), 12),
|
|
859
|
-
delY = decoder.readFixedPoint(decoder.readShort(), 12),
|
|
860
|
-
delZ = decoder.readFixedPoint(decoder.readShort(), 12),
|
|
861
|
-
pitch = decoder.readAngle(),
|
|
862
|
-
yaw = decoder.readAngle();
|
|
863
|
-
|
|
864
|
-
if (id in this.world!.entities) {
|
|
865
|
-
let { x, y, z } = this.world!.entities[id]!.position;
|
|
866
|
-
x += delX;
|
|
867
|
-
y += delY;
|
|
868
|
-
z += delZ;
|
|
869
|
-
this.world!.entities[id]!.position = { x, y, z };
|
|
870
|
-
this.world!.entities[id]!.angle = { yaw, pitch };
|
|
871
|
-
this.emit("updateEntity", this.world!.entities[id]!);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
private handleSetEntityVelocity(decoder: BinaryDecoder) {
|
|
876
|
-
const entityId = decoder.readVarInt(),
|
|
877
|
-
velocity = decoder.readLpVec3();
|
|
878
|
-
|
|
879
|
-
if (entityId in this.world!.entities)
|
|
880
|
-
this.world!.entities[entityId]!.velocity = velocity;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
private handleRemoveEntity(decoder: BinaryDecoder) {
|
|
884
|
-
const entities = decoder.readPrefixedArray((decoder) => decoder.readVarInt());
|
|
885
|
-
|
|
886
|
-
for (const entityId of entities) {
|
|
887
|
-
delete this.world!.entities[entityId];
|
|
888
|
-
this.emit("removeEntity", entityId);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
// Chat
|
|
893
|
-
private handlePlayerChat(decoder: BinaryDecoder) {
|
|
894
|
-
const globalIndex = decoder.readVarInt(),
|
|
895
|
-
senderUUID = decoder.readUUID(),
|
|
896
|
-
index = decoder.readVarInt(),
|
|
897
|
-
messageSignature = decoder.readPrefixedOptional(decoder => decoder.readArray(256, decoder => decoder.readByte())),
|
|
898
|
-
message = decoder.readString(),
|
|
899
|
-
timestamp = decoder.readLong(),
|
|
900
|
-
salt = decoder.readLong(),
|
|
901
|
-
previousMessages = decoder.readPrefixedArray(decoder => {
|
|
902
|
-
const id = decoder.readVarInt();
|
|
903
|
-
let signature;
|
|
904
|
-
if (id === 0)
|
|
905
|
-
signature = decoder.readArray(256, decoder => decoder.readByte());
|
|
906
|
-
return { id, signature };
|
|
907
|
-
}),
|
|
908
|
-
unsignedContent = decoder.readPrefixedOptional(decoder => decoder.readNBT()), // Text component
|
|
909
|
-
filterType = decoder.readVarInt(),
|
|
910
|
-
filterTypeBits = filterType === 1 ? decoder.readPrefixedArray(decoder => decoder.readLong()) : undefined,
|
|
911
|
-
chatType = decoder.readIdOrX(decoder => decoder.readChatType()),
|
|
912
|
-
senderName = decoder.readNBT(),
|
|
913
|
-
targetName = decoder.readPrefixedOptional(decoder => decoder.readNBT());
|
|
914
|
-
|
|
915
|
-
const senderNameText = getTextFromTextComponent(senderName).toString(),
|
|
916
|
-
targetNameText = targetName ?? getTextFromTextComponent(targetName).toString();
|
|
917
|
-
|
|
918
|
-
this.emit("message", {
|
|
919
|
-
sender: senderNameText,
|
|
920
|
-
target: targetNameText,
|
|
921
|
-
content: message,
|
|
922
|
-
raw: {
|
|
923
|
-
sender: senderName,
|
|
924
|
-
target: targetName,
|
|
925
|
-
content: unsignedContent,
|
|
926
|
-
}
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
private handleSystemMessage(decoder: BinaryDecoder) {
|
|
931
|
-
const content = decoder.readNBT(),
|
|
932
|
-
isActionbar = decoder.readBoolean();
|
|
933
|
-
const text = getTextFromTextComponent(content);
|
|
934
|
-
|
|
935
|
-
if (isActionbar) {
|
|
936
|
-
this.emit("actionBar", text.toString());
|
|
937
|
-
this.emit("actionBarRaw", content);
|
|
938
|
-
} else {
|
|
939
|
-
this.emit("systemMessage", text.toString());
|
|
940
|
-
this.emit("systemMessageRaw", content);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
private handleSetActionBar(decoder: BinaryDecoder) {
|
|
945
|
-
const content = decoder.readNBT();
|
|
946
|
-
const text = getTextFromTextComponent(content);
|
|
947
|
-
this.emit("actionBar", text.toString());
|
|
948
|
-
this.emit("actionBarRaw", content);
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
private handleDisguisedChatMessage(decoder: BinaryDecoder) {
|
|
952
|
-
const message = decoder.readNBT(),
|
|
953
|
-
chatType = decoder.readChatType(),
|
|
954
|
-
senderName = decoder.readNBT(),
|
|
955
|
-
targetName = decoder.readPrefixedOptional(decoder => decoder.readNBT());
|
|
956
|
-
const messageText = getTextFromTextComponent(message).toString(),
|
|
957
|
-
senderNameText = getTextFromTextComponent(senderName).toString(),
|
|
958
|
-
targetNameText = targetName ?? getTextFromTextComponent(targetName).toString();
|
|
959
|
-
this.emit("message", {
|
|
960
|
-
sender: senderNameText,
|
|
961
|
-
target: targetNameText,
|
|
962
|
-
content: messageText,
|
|
963
|
-
raw: {
|
|
964
|
-
sender: senderName,
|
|
965
|
-
target: targetName,
|
|
966
|
-
content: message,
|
|
967
|
-
}
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
|
|
971
197
|
/*
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
198
|
+
* Send packet
|
|
199
|
+
*/
|
|
975
200
|
public write(buf: Buffer) {
|
|
976
|
-
if (
|
|
201
|
+
if (!this.socket.writable)
|
|
977
202
|
throw new SockerIsNotWritable();
|
|
978
203
|
|
|
979
204
|
let sendBuffer = buf;
|
|
980
|
-
if (this.cipher)
|
|
981
|
-
sendBuffer = this.cipher.update(sendBuffer);
|
|
205
|
+
if (this.state.cipher)
|
|
206
|
+
sendBuffer = this.state.cipher.update(sendBuffer);
|
|
982
207
|
|
|
983
208
|
this.socket.write(sendBuffer);
|
|
984
209
|
}
|
|
985
210
|
|
|
986
|
-
public sendPacket(packetId: number,
|
|
211
|
+
public sendPacket(packetId: number | string, data: Record<string, any>) {
|
|
212
|
+
const { buffer: content, packet } = this.parsePacket(packetId, data);
|
|
987
213
|
const encodePacketId = new BinaryEncoder();
|
|
988
|
-
encodePacketId.writeVarInt(
|
|
214
|
+
encodePacketId.writeVarInt(packet.id);
|
|
989
215
|
const sendData = Buffer.concat([encodePacketId.getBuffer(), content]);
|
|
990
216
|
|
|
991
|
-
let
|
|
992
|
-
if (this.compressionThreshold !== 0) {
|
|
217
|
+
let sendingPacket: Buffer;
|
|
218
|
+
if (this.state.compressionThreshold !== 0) {
|
|
993
219
|
let data: Buffer;
|
|
994
220
|
let uncompressedLength: number;
|
|
995
221
|
|
|
996
|
-
if (this.compressionThreshold > 0 && sendData.length > this.compressionThreshold) {
|
|
222
|
+
if (this.state.compressionThreshold > 0 && sendData.length > this.state.compressionThreshold) {
|
|
997
223
|
uncompressedLength = sendData.length;
|
|
998
224
|
data = deflateSync(sendData);
|
|
999
225
|
} else {
|
|
@@ -1007,145 +233,14 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
1007
233
|
|
|
1008
234
|
const packetLengthEncoder = new BinaryEncoder();
|
|
1009
235
|
packetLengthEncoder.writeVarInt(dataLengthBuf.length + data.length);
|
|
1010
|
-
|
|
236
|
+
sendingPacket = Buffer.concat([packetLengthEncoder.getBuffer(), dataLengthBuf, data]);
|
|
1011
237
|
} else {
|
|
1012
238
|
const packetLengthEncoder = new BinaryEncoder();
|
|
1013
239
|
packetLengthEncoder.writeVarInt(sendData.length);
|
|
1014
|
-
|
|
240
|
+
sendingPacket = Buffer.concat([packetLengthEncoder.getBuffer(), sendData]);
|
|
1015
241
|
}
|
|
1016
242
|
|
|
1017
|
-
this.write(
|
|
243
|
+
this.write(sendingPacket);
|
|
1018
244
|
}
|
|
1019
245
|
|
|
1020
|
-
// Login
|
|
1021
|
-
|
|
1022
|
-
private sendHandshake() {
|
|
1023
|
-
this.state = ClientState.Handshake;
|
|
1024
|
-
const encoder = new BinaryEncoder();
|
|
1025
|
-
encoder.writeVarInt(this.option.protocolVersion);
|
|
1026
|
-
encoder.writeString(this.option.host);
|
|
1027
|
-
encoder.writeUShort(this.option.port);
|
|
1028
|
-
encoder.writeVarInt(2);
|
|
1029
|
-
this.sendPacket(0x00, encoder.getBuffer());
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
private sendLoginStart() {
|
|
1033
|
-
this.state = ClientState.Login;
|
|
1034
|
-
this.status = ClientStatus.Logining;
|
|
1035
|
-
const encoder = new BinaryEncoder();
|
|
1036
|
-
const playerName = this.option.playerName;
|
|
1037
|
-
const playerUUID = this.playerUUID!;
|
|
1038
|
-
encoder.writeString(playerName);
|
|
1039
|
-
encoder.concat(playerUUID);
|
|
1040
|
-
this.sendPacket(0x00, encoder.getBuffer());
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
private sendEncryptionResponse(publicKey: Buffer, verifyToken: Buffer) {
|
|
1044
|
-
const encryptedSecret = publicEncrypt({
|
|
1045
|
-
key: createPublicKey({ key: publicKey, format: 'der', type: 'spki' }),
|
|
1046
|
-
padding: crypto_constants.RSA_PKCS1_PADDING
|
|
1047
|
-
}, this.sharedSecret!);
|
|
1048
|
-
const encryptedVerifyToken = publicEncrypt({
|
|
1049
|
-
key: createPublicKey({ key: publicKey, format: 'der', type: 'spki' }),
|
|
1050
|
-
padding: crypto_constants.RSA_PKCS1_PADDING
|
|
1051
|
-
}, verifyToken);
|
|
1052
|
-
const encoder = new BinaryEncoder();
|
|
1053
|
-
encoder.writeRawPrefixedArray(encryptedSecret, 8);
|
|
1054
|
-
encoder.writeRawPrefixedArray(encryptedVerifyToken, 8);
|
|
1055
|
-
this.sendPacket(0x01, encoder.getBuffer());
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
private sendLoginAck() {
|
|
1059
|
-
this.state = ClientState.Configure;
|
|
1060
|
-
this.server = {};
|
|
1061
|
-
this.sendPacket(0x03, Buffer.alloc(0));
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
// Configuring
|
|
1065
|
-
|
|
1066
|
-
private sendKnownPack(knownPacks: ServerKnownPack[]) {
|
|
1067
|
-
const encoder = new BinaryEncoder();
|
|
1068
|
-
encoder.writePrefixedArray(knownPacks.length, (encoder, ind) => {
|
|
1069
|
-
const pack = knownPacks[ind]!;
|
|
1070
|
-
return encoder
|
|
1071
|
-
.writeString(pack.namespace)
|
|
1072
|
-
.writeString(pack.id)
|
|
1073
|
-
.writeString(pack.version);
|
|
1074
|
-
});
|
|
1075
|
-
this.sendPacket(0x07, encoder.getBuffer());
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
private sendConfigureAck() {
|
|
1079
|
-
this.state = ClientState.Play;
|
|
1080
|
-
this.sendPacket(0x3, Buffer.alloc(0));
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
// Play
|
|
1084
|
-
|
|
1085
|
-
private sendConfirmTeleportation(teleportId: number) {
|
|
1086
|
-
const encoder = new BinaryEncoder();
|
|
1087
|
-
encoder.writeVarInt(teleportId);
|
|
1088
|
-
this.sendPacket(0, encoder.getBuffer());
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
private sendKeepAlive(id: bigint) {
|
|
1092
|
-
const encoder = new BinaryEncoder();
|
|
1093
|
-
encoder.writeLong(id);
|
|
1094
|
-
this.sendPacket(0x1C, encoder.getBuffer());
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
public sendPlayerPosRot(pos: BaseVec3, rot: Angle, onGround: boolean, pushingWall: boolean) {
|
|
1098
|
-
let flag = 0;
|
|
1099
|
-
if (onGround)
|
|
1100
|
-
flag |= 1;
|
|
1101
|
-
if (pushingWall)
|
|
1102
|
-
flag |= 1 << 2;
|
|
1103
|
-
// // console.log({ pos, rot, flag });
|
|
1104
|
-
const encoder = new BinaryEncoder();
|
|
1105
|
-
encoder.writeDouble(pos.x);
|
|
1106
|
-
encoder.writeDouble(pos.y);
|
|
1107
|
-
encoder.writeDouble(pos.z);
|
|
1108
|
-
encoder.writeFloat(rot.yaw);
|
|
1109
|
-
encoder.writeFloat(rot.pitch);
|
|
1110
|
-
encoder.writeByte(flag);
|
|
1111
|
-
this.sendPacket(0x1F, encoder.getBuffer());
|
|
1112
|
-
}
|
|
1113
|
-
public sendPlayerPos(pos: BaseVec3, onGround: boolean, pushingWall: boolean) {
|
|
1114
|
-
let flag = 0;
|
|
1115
|
-
if (onGround)
|
|
1116
|
-
flag &= 1;
|
|
1117
|
-
if (pushingWall)
|
|
1118
|
-
flag |= 1 << 2;
|
|
1119
|
-
// // console.log({ pos, flag });
|
|
1120
|
-
const encoder = new BinaryEncoder();
|
|
1121
|
-
encoder.writeDouble(pos.x);
|
|
1122
|
-
encoder.writeDouble(pos.y);
|
|
1123
|
-
encoder.writeDouble(pos.z);
|
|
1124
|
-
encoder.writeByte(flag);
|
|
1125
|
-
this.sendPacket(0x1E, encoder.getBuffer());
|
|
1126
|
-
}
|
|
1127
|
-
public sendPlayerRot(rot: Angle, onGround: boolean, pushingWall: boolean) {
|
|
1128
|
-
let flag = 0;
|
|
1129
|
-
if (onGround)
|
|
1130
|
-
flag &= 1;
|
|
1131
|
-
if (pushingWall)
|
|
1132
|
-
flag |= 1 << 2;
|
|
1133
|
-
// // console.log({ rot, flag });
|
|
1134
|
-
const encoder = new BinaryEncoder();
|
|
1135
|
-
encoder.writeFloat(rot.yaw);
|
|
1136
|
-
encoder.writeFloat(rot.pitch);
|
|
1137
|
-
encoder.writeByte(flag);
|
|
1138
|
-
this.sendPacket(0x20, encoder.getBuffer());
|
|
1139
|
-
}
|
|
1140
|
-
public sendPlayerStatus(onGround: boolean, pushingWall: boolean) {
|
|
1141
|
-
let flag = 0;
|
|
1142
|
-
if (onGround)
|
|
1143
|
-
flag &= 1;
|
|
1144
|
-
if (pushingWall)
|
|
1145
|
-
flag |= 1 << 2;
|
|
1146
|
-
// // console.log({ flag });
|
|
1147
|
-
const encoder = new BinaryEncoder();
|
|
1148
|
-
encoder.writeByte(flag);
|
|
1149
|
-
this.sendPacket(0x21, encoder.getBuffer());
|
|
1150
|
-
}
|
|
1151
246
|
}
|