@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.
- package/README.md +1 -1
- package/assets/minecraft/26.2/packets.json +4758 -0
- 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 +59 -3
- package/dist/base/error.d.ts.map +1 -1
- package/dist/base/error.js +123 -7
- 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/base/typing.d.ts +7 -1
- package/dist/base/typing.d.ts.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 +73 -17
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +111 -44
- package/dist/client/client.js.map +1 -1
- package/dist/client/tcp.d.ts +17 -206
- package/dist/client/tcp.d.ts.map +1 -1
- package/dist/client/tcp.js +37 -715
- 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/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/{translator → packet}/decoder.d.ts +14 -1
- package/dist/packet/decoder.d.ts.map +1 -0
- package/dist/{translator → packet}/decoder.js +24 -3
- package/dist/packet/decoder.js.map +1 -0
- package/dist/packet/dispatcher.d.ts +53 -0
- package/dist/packet/dispatcher.d.ts.map +1 -0
- package/dist/packet/dispatcher.js +738 -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 +25 -28
- package/dist/physics/player.d.ts.map +1 -0
- package/dist/{base → physics}/player.js +81 -68
- 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 +235 -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 +284 -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 +175 -0
- package/dist/world/state.d.ts.map +1 -0
- package/dist/world/state.js +163 -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/buffer.ts +12 -0
- package/src/base/error.ts +93 -4
- package/src/base/http.ts +10 -0
- package/src/base/math.ts +55 -3
- package/src/base/typing.ts +9 -1
- package/src/client/auth.ts +130 -52
- package/src/client/client.ts +182 -50
- package/src/client/tcp.ts +45 -967
- package/src/index.ts +12 -12
- package/src/message/link.ts +17 -0
- package/src/message/sigCache.ts +25 -0
- package/src/{translator → packet}/decoder.ts +24 -3
- package/src/packet/dispatcher.ts +919 -0
- package/src/{translator → packet}/encoder.ts +20 -9
- package/src/{base → physics}/aabb.ts +5 -5
- package/src/{base → physics}/player.ts +106 -65
- package/src/version/codec.ts +248 -0
- package/src/version/registry.ts +328 -0
- package/src/{base → world}/block.ts +12 -11
- package/src/{base → world}/entity.ts +5 -5
- package/src/world/state.ts +300 -0
- package/test/adapter/index.ts +14 -0
- package/test/client/index.ts +28 -21
- 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, SharedState } from "../world/state";
|
|
10
|
+
import { EncodeResult } from "../version/codec";
|
|
11
|
+
import { TextComponent } from "../base/typing";
|
|
12
|
+
import { NotImplemented, SockerIsNotWritable, UnexpectedValue } 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
15
|
|
|
33
16
|
|
|
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
|
-
|
|
132
|
-
/** Current dimension */
|
|
133
|
-
dimension: string
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export interface ServerRegistryEntry {
|
|
137
|
-
id: string,
|
|
138
|
-
data: object
|
|
139
|
-
}
|
|
140
17
|
|
|
141
18
|
// TCP related typing
|
|
142
19
|
export interface TCPClientOption {
|
|
@@ -154,11 +31,6 @@ export interface TCPClientOption {
|
|
|
154
31
|
protocolVersion: number,
|
|
155
32
|
/** Player name */
|
|
156
33
|
playerName: string,
|
|
157
|
-
/**
|
|
158
|
-
* Set to true only if you are playing in offline server or crack server
|
|
159
|
-
*/
|
|
160
|
-
isOffline?: boolean,
|
|
161
|
-
|
|
162
34
|
/**
|
|
163
35
|
* Send empty `Known Packet` to let server send all Registry data, which may consume a lot of bandwith
|
|
164
36
|
*/
|
|
@@ -175,71 +47,21 @@ export interface TCPClientOption {
|
|
|
175
47
|
|
|
176
48
|
export interface TCPClientEvents {
|
|
177
49
|
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],
|
|
50
|
+
raw: [buffer: Buffer]
|
|
51
|
+
destroy: [],
|
|
216
52
|
}
|
|
217
53
|
|
|
218
54
|
export interface Message {
|
|
219
55
|
sender: string,
|
|
220
|
-
target
|
|
56
|
+
target: string | undefined,
|
|
221
57
|
content: string,
|
|
222
58
|
raw: {
|
|
223
59
|
sender: TextComponent,
|
|
224
|
-
target
|
|
225
|
-
content
|
|
60
|
+
target: TextComponent | undefined,
|
|
61
|
+
content: TextComponent | undefined
|
|
226
62
|
}
|
|
227
63
|
}
|
|
228
64
|
|
|
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
65
|
|
|
244
66
|
// export enum TCPServerIntent {
|
|
245
67
|
// Status = 1,
|
|
@@ -252,11 +74,9 @@ export enum ClientState {
|
|
|
252
74
|
*
|
|
253
75
|
* @see https://minecraft.wiki/w/Java_Edition_protocol/Packets?oldid=3445844
|
|
254
76
|
*/
|
|
255
|
-
export class TCPClient
|
|
77
|
+
export class TCPClient extends (EventEmitter as new () => TypedEmmiter<TCPClientEvents>) {
|
|
256
78
|
public readonly socket: Socket;
|
|
257
79
|
|
|
258
|
-
public server: Server | undefined = undefined;
|
|
259
|
-
|
|
260
80
|
// Internal var
|
|
261
81
|
/**
|
|
262
82
|
* | Value | Meaning |
|
|
@@ -265,56 +85,26 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
265
85
|
* | -1 | dont compress |
|
|
266
86
|
* | > 0 | compression threshold |
|
|
267
87
|
*/
|
|
268
|
-
private compressionThreshold: number = 0;
|
|
269
88
|
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
89
|
|
|
286
90
|
constructor(
|
|
91
|
+
private state: SharedState,
|
|
287
92
|
public readonly option: TCPClientOption,
|
|
288
|
-
socketOption?: SocketConstructorOpts
|
|
93
|
+
socketOption?: SocketConstructorOpts,
|
|
289
94
|
) {
|
|
290
95
|
super();
|
|
291
96
|
this.socket = new Socket(socketOption);
|
|
292
|
-
this.wipePlayData();
|
|
293
|
-
|
|
294
|
-
if (!option.isOffline && !option.auth)
|
|
295
|
-
throw new MissingAuthOption();
|
|
296
97
|
}
|
|
297
98
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
this.registry = null as any;
|
|
302
|
-
}
|
|
99
|
+
public sendInitPacket: () => void = () => { throw new NotImplemented(); };
|
|
100
|
+
public forwardPacket: (packetId: number, decoder: BinaryDecoder) => void = () => { throw new NotImplemented(); };
|
|
101
|
+
public parsePacket: (packetId: string | number, data: object) => EncodeResult = () => { throw new NotImplemented(); };
|
|
303
102
|
|
|
304
103
|
/**
|
|
305
104
|
* Connect to server
|
|
306
105
|
*/
|
|
307
|
-
public
|
|
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;
|
|
106
|
+
public connect() {
|
|
107
|
+
this.state.status = ClientStatus.Connecting;
|
|
318
108
|
const connection = this.socket.connect({
|
|
319
109
|
host: this.option.host,
|
|
320
110
|
port: this.option.port
|
|
@@ -322,30 +112,21 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
322
112
|
|
|
323
113
|
connection.on("connect", () => {
|
|
324
114
|
this.emit("connect");
|
|
325
|
-
this.
|
|
326
|
-
this.sendHandshake();
|
|
327
|
-
this.sendLoginStart();
|
|
115
|
+
this.sendInitPacket();
|
|
328
116
|
});
|
|
329
117
|
connection.on("data", (data) => {
|
|
330
118
|
let buf = Buffer.from(data);
|
|
331
|
-
if (this.decipher)
|
|
332
|
-
buf = this.decipher.update(buf);
|
|
333
|
-
}
|
|
119
|
+
if (this.state.decipher)
|
|
120
|
+
buf = this.state.decipher.update(buf);
|
|
334
121
|
this.emit("raw", Buffer.from(buf));
|
|
335
122
|
this.bufferPool = Buffer.concat([this.bufferPool, buf]);
|
|
336
123
|
this.handlePacket();
|
|
337
124
|
});
|
|
338
125
|
connection.once("end", () => {
|
|
339
|
-
|
|
340
|
-
this.status = ClientStatus.Disconnected;
|
|
341
|
-
this.state = ClientState.Disconnected;
|
|
342
|
-
this.emit("disconnect", "socket close");
|
|
126
|
+
this.emit("destroy");
|
|
343
127
|
});
|
|
344
128
|
connection.once("close", () => {
|
|
345
|
-
|
|
346
|
-
this.status = ClientStatus.Disconnected;
|
|
347
|
-
this.state = ClientState.Disconnected;
|
|
348
|
-
this.emit("disconnect", "socket close");
|
|
129
|
+
this.emit("destroy");
|
|
349
130
|
});
|
|
350
131
|
}
|
|
351
132
|
|
|
@@ -354,24 +135,12 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
354
135
|
this.socket.destroy();
|
|
355
136
|
}
|
|
356
137
|
|
|
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
138
|
/*
|
|
368
139
|
* Read packet
|
|
369
140
|
*/
|
|
370
141
|
|
|
371
142
|
private handlePacket() {
|
|
372
|
-
while (
|
|
373
|
-
if (this.bufferPool.length === 0) break;
|
|
374
|
-
|
|
143
|
+
while (this.bufferPool.length > 0) {
|
|
375
144
|
let decoder = new BinaryDecoder(this.bufferPool);
|
|
376
145
|
let packetLength: number;
|
|
377
146
|
try {
|
|
@@ -384,9 +153,9 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
384
153
|
const expectedPacketEnd = decoder.offset + packetLength;
|
|
385
154
|
|
|
386
155
|
let packetID: number;
|
|
387
|
-
if (this.compressionThreshold === 0)
|
|
156
|
+
if (this.state.compressionThreshold === 0)
|
|
388
157
|
packetID = decoder.readVarInt();
|
|
389
|
-
else if (this.compressionThreshold === -1) {
|
|
158
|
+
else if (this.state.compressionThreshold === -1) {
|
|
390
159
|
decoder.readVarInt();
|
|
391
160
|
packetID = decoder.readVarInt();
|
|
392
161
|
} else {
|
|
@@ -396,604 +165,44 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
396
165
|
} else {
|
|
397
166
|
const dataBuffer = this.bufferPool.subarray(decoder.offset, expectedPacketEnd);
|
|
398
167
|
const decompressed = inflateSync(dataBuffer);
|
|
168
|
+
if (decompressed.length !== dataLength)
|
|
169
|
+
throw new UnexpectedValue("decompressed packet data", decompressed.length.toString(), dataLength.toString());
|
|
399
170
|
decoder = new BinaryDecoder(decompressed);
|
|
400
171
|
packetID = decoder.readVarInt();
|
|
401
172
|
}
|
|
402
173
|
}
|
|
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
|
-
}
|
|
174
|
+
this.forwardPacket(packetID, decoder);
|
|
486
175
|
|
|
487
176
|
this.bufferPool = this.bufferPool.subarray(expectedPacketEnd);
|
|
488
177
|
}
|
|
489
178
|
}
|
|
490
179
|
|
|
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
180
|
/*
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
181
|
+
* Send packet
|
|
182
|
+
*/
|
|
975
183
|
public write(buf: Buffer) {
|
|
976
|
-
if (
|
|
184
|
+
if (!this.socket.writable)
|
|
977
185
|
throw new SockerIsNotWritable();
|
|
978
186
|
|
|
979
187
|
let sendBuffer = buf;
|
|
980
|
-
if (this.cipher)
|
|
981
|
-
sendBuffer = this.cipher.update(sendBuffer);
|
|
188
|
+
if (this.state.cipher)
|
|
189
|
+
sendBuffer = this.state.cipher.update(sendBuffer);
|
|
982
190
|
|
|
983
191
|
this.socket.write(sendBuffer);
|
|
984
192
|
}
|
|
985
193
|
|
|
986
|
-
public sendPacket(packetId: number,
|
|
194
|
+
public sendPacket(packetId: number | string, data: Record<string, any>) {
|
|
195
|
+
const { buffer: content, packet } = this.parsePacket(packetId, data);
|
|
987
196
|
const encodePacketId = new BinaryEncoder();
|
|
988
|
-
encodePacketId.writeVarInt(
|
|
197
|
+
encodePacketId.writeVarInt(packet.id);
|
|
989
198
|
const sendData = Buffer.concat([encodePacketId.getBuffer(), content]);
|
|
990
199
|
|
|
991
|
-
let
|
|
992
|
-
if (this.compressionThreshold !== 0) {
|
|
200
|
+
let sendingPacket: Buffer;
|
|
201
|
+
if (this.state.compressionThreshold !== 0) {
|
|
993
202
|
let data: Buffer;
|
|
994
203
|
let uncompressedLength: number;
|
|
995
204
|
|
|
996
|
-
if (this.compressionThreshold > 0 && sendData.length > this.compressionThreshold) {
|
|
205
|
+
if (this.state.compressionThreshold > 0 && sendData.length > this.state.compressionThreshold) {
|
|
997
206
|
uncompressedLength = sendData.length;
|
|
998
207
|
data = deflateSync(sendData);
|
|
999
208
|
} else {
|
|
@@ -1007,145 +216,14 @@ export class TCPClient<IsReady extends boolean = boolean> extends (EventEmitter
|
|
|
1007
216
|
|
|
1008
217
|
const packetLengthEncoder = new BinaryEncoder();
|
|
1009
218
|
packetLengthEncoder.writeVarInt(dataLengthBuf.length + data.length);
|
|
1010
|
-
|
|
219
|
+
sendingPacket = Buffer.concat([packetLengthEncoder.getBuffer(), dataLengthBuf, data]);
|
|
1011
220
|
} else {
|
|
1012
221
|
const packetLengthEncoder = new BinaryEncoder();
|
|
1013
222
|
packetLengthEncoder.writeVarInt(sendData.length);
|
|
1014
|
-
|
|
223
|
+
sendingPacket = Buffer.concat([packetLengthEncoder.getBuffer(), sendData]);
|
|
1015
224
|
}
|
|
1016
225
|
|
|
1017
|
-
this.write(
|
|
226
|
+
this.write(sendingPacket);
|
|
1018
227
|
}
|
|
1019
228
|
|
|
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
229
|
}
|