teeworlds 2.1.3 → 2.1.8
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/index.js +16 -9
- package/index.ts +5 -9
- package/lib/MsgPacker.js +4 -3
- package/lib/MsgPacker.ts +2 -6
- package/lib/MsgUnpacker.js +0 -3
- package/lib/MsgUnpacker.ts +1 -6
- package/lib/client.js +359 -280
- package/lib/client.ts +381 -200
- package/lib/movement.js +54 -0
- package/lib/movement.ts +63 -0
- package/lib/snapshot.js +6 -14
- package/lib/snapshot.ts +9 -15
- package/package.json +1 -1
package/lib/client.ts
CHANGED
|
@@ -1,20 +1,42 @@
|
|
|
1
1
|
import { randomBytes } from "crypto";
|
|
2
2
|
|
|
3
3
|
import net from 'dgram';
|
|
4
|
-
import fs from 'fs';
|
|
5
4
|
import { EventEmitter } from 'stream';
|
|
6
|
-
import { spawn } from 'child_process';
|
|
7
5
|
|
|
8
|
-
import {
|
|
6
|
+
import { unpackString, MsgUnpacker } from "./MsgUnpacker";
|
|
9
7
|
|
|
8
|
+
import Movement from './movement';
|
|
10
9
|
|
|
11
|
-
import MsgPacker from './MsgPacker';
|
|
10
|
+
import { MsgPacker } from './MsgPacker';
|
|
12
11
|
import { Snapshot } from './snapshot';
|
|
13
12
|
import Huffman from "./huffman";
|
|
14
13
|
|
|
15
14
|
const huff = new Huffman();
|
|
16
15
|
const SnapUnpacker = new Snapshot();
|
|
17
16
|
|
|
17
|
+
enum States {
|
|
18
|
+
STATE_OFFLINE = 0,
|
|
19
|
+
STATE_CONNECTING,
|
|
20
|
+
STATE_LOADING,
|
|
21
|
+
STATE_ONLINE,
|
|
22
|
+
STATE_DEMOPLAYBACK,
|
|
23
|
+
STATE_QUITTING,
|
|
24
|
+
STATE_RESTARTING
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface NetObj_PlayerInput {
|
|
28
|
+
m_Direction: number,
|
|
29
|
+
m_TargetX: number,
|
|
30
|
+
m_TargetY: number,
|
|
31
|
+
m_Jump: number,
|
|
32
|
+
m_Fire: number,
|
|
33
|
+
m_Hook: number,
|
|
34
|
+
m_PlayerFlags: number,
|
|
35
|
+
m_WantedWeapon: number,
|
|
36
|
+
m_NextWeapon: number,
|
|
37
|
+
m_PrevWeapon: number
|
|
38
|
+
};
|
|
39
|
+
|
|
18
40
|
enum NETMSGTYPE {
|
|
19
41
|
EX,
|
|
20
42
|
SV_MOTD,
|
|
@@ -118,21 +140,21 @@ function arrStartsWith(arr: number[], arrStart: number[], start = 0) {
|
|
|
118
140
|
return true;
|
|
119
141
|
}
|
|
120
142
|
declare interface PlayerInfo {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
143
|
+
local: number,
|
|
144
|
+
client_id: number,
|
|
145
|
+
team: number,
|
|
146
|
+
score: number,
|
|
147
|
+
latency: number,
|
|
126
148
|
}
|
|
127
149
|
|
|
128
150
|
declare interface ClientInfo {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
151
|
+
name: string,
|
|
152
|
+
clan: string,
|
|
153
|
+
country: number,
|
|
154
|
+
skin: string,
|
|
155
|
+
use_custom_color: number,
|
|
156
|
+
color_body: number,
|
|
157
|
+
color_feet: number,
|
|
136
158
|
}
|
|
137
159
|
declare interface iMessage {
|
|
138
160
|
team: number,
|
|
@@ -150,7 +172,13 @@ declare interface iKillMsg {
|
|
|
150
172
|
special_mode: number
|
|
151
173
|
}
|
|
152
174
|
|
|
153
|
-
declare interface
|
|
175
|
+
declare interface iOptions {
|
|
176
|
+
identity?: ClientInfo,
|
|
177
|
+
password?: string,
|
|
178
|
+
ddnet_version?: {version: number, release_version: string},
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export declare interface Client {
|
|
154
182
|
host: string;
|
|
155
183
|
port: number;
|
|
156
184
|
name: string;
|
|
@@ -159,15 +187,25 @@ declare interface Client {
|
|
|
159
187
|
clientAck: number;
|
|
160
188
|
receivedSnaps: number; /* wait for 2 ss before seeing self as connected */
|
|
161
189
|
lastMsg: string;
|
|
162
|
-
_port: number;
|
|
163
190
|
socket: net.Socket | undefined;
|
|
164
191
|
TKEN: Buffer;
|
|
165
192
|
time: number;
|
|
166
193
|
|
|
194
|
+
timer: number;
|
|
195
|
+
PredGameTick: number;
|
|
196
|
+
AckGameTick: number;
|
|
197
|
+
|
|
198
|
+
movement: Movement;
|
|
199
|
+
|
|
167
200
|
snaps: Buffer[];
|
|
168
201
|
client_infos: ClientInfo[];
|
|
169
202
|
player_infos: PlayerInfo[];
|
|
170
203
|
|
|
204
|
+
sentChunkQueue: Buffer[];
|
|
205
|
+
|
|
206
|
+
lastSendTime: number;
|
|
207
|
+
|
|
208
|
+
options?: iOptions;
|
|
171
209
|
|
|
172
210
|
on(event: 'connected', listener: () => void): this;
|
|
173
211
|
on(event: 'disconnect', listener: (reason: string) => void): this;
|
|
@@ -176,34 +214,49 @@ declare interface Client {
|
|
|
176
214
|
on(event: 'kill', listener: (kill: iKillMsg) => void): this;
|
|
177
215
|
|
|
178
216
|
}
|
|
179
|
-
class Client extends EventEmitter {
|
|
180
217
|
|
|
181
218
|
|
|
182
219
|
|
|
183
|
-
|
|
220
|
+
export class Client extends EventEmitter {
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
constructor(ip: string, port: number, nickname: string, options?: iOptions) {
|
|
184
225
|
super();
|
|
185
226
|
this.host = ip;
|
|
186
227
|
this.port = port;
|
|
187
228
|
this.name = nickname;
|
|
229
|
+
this.AckGameTick = 0;
|
|
230
|
+
this.PredGameTick = 0;
|
|
231
|
+
|
|
232
|
+
if (options)
|
|
233
|
+
this.options = options;
|
|
234
|
+
|
|
235
|
+
this.timer = 0;
|
|
236
|
+
|
|
237
|
+
this.movement = new Movement();
|
|
188
238
|
|
|
189
239
|
this.snaps = [];
|
|
190
240
|
this.client_infos = [];
|
|
191
241
|
this.player_infos = [];
|
|
192
242
|
|
|
193
|
-
this.
|
|
243
|
+
this.sentChunkQueue = [];
|
|
244
|
+
|
|
245
|
+
this.State = States.STATE_OFFLINE; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
|
|
194
246
|
this.ack = 0; // ack of messages the client has received
|
|
195
247
|
this.clientAck = 0; // ack of messages the client has sent
|
|
196
248
|
this.receivedSnaps = 0; /* wait for 2 snaps before seeing self as connected */
|
|
197
249
|
this.lastMsg = "";
|
|
198
|
-
this._port = Math.floor(Math.random() * 65535)
|
|
199
250
|
this.socket = net.createSocket("udp4")
|
|
200
251
|
this.socket.bind();
|
|
201
252
|
|
|
202
253
|
this.TKEN = Buffer.from([255, 255, 255, 255])
|
|
203
254
|
this.time = new Date().getTime() + 2000; // time (used for keepalives, start to send keepalives after 2 seconds)
|
|
204
|
-
|
|
255
|
+
|
|
256
|
+
this.lastSendTime = new Date().getTime();
|
|
257
|
+
|
|
205
258
|
}
|
|
206
|
-
|
|
259
|
+
Unpack(packet: Buffer): _packet {
|
|
207
260
|
var unpacked: _packet = { twprotocol: { flags: packet[0], ack: packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] }
|
|
208
261
|
|
|
209
262
|
|
|
@@ -215,10 +268,10 @@ class Client extends EventEmitter {
|
|
|
215
268
|
if (packet.length == 1 && packet[0] == -1)
|
|
216
269
|
return unpacked
|
|
217
270
|
}
|
|
218
|
-
|
|
271
|
+
|
|
219
272
|
for (let i = 0; i < unpacked.twprotocol.chunkAmount; i++) {
|
|
220
273
|
var chunk: chunk = {} as chunk;
|
|
221
|
-
chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1));
|
|
274
|
+
chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1));
|
|
222
275
|
chunk.flags = (packet[0] >> 6) & 3;
|
|
223
276
|
chunk.sequence = -1;
|
|
224
277
|
|
|
@@ -234,17 +287,17 @@ class Client extends EventEmitter {
|
|
|
234
287
|
Object.values(messageUUIDs).forEach((a, i) => {
|
|
235
288
|
if (a.compare(packet.slice(0, 16)) == 0) {
|
|
236
289
|
chunk.extended_msgid = a;
|
|
237
|
-
// chunk.type = 'sys';
|
|
238
290
|
chunk.msg = Object.keys(messageUUIDs)[i];
|
|
239
291
|
}
|
|
240
292
|
})
|
|
241
293
|
|
|
242
|
-
packet = packet.slice(chunk.bytes)
|
|
294
|
+
packet = packet.slice(chunk.bytes)
|
|
243
295
|
unpacked.chunks.push(chunk)
|
|
244
296
|
}
|
|
245
297
|
return unpacked
|
|
246
298
|
}
|
|
247
299
|
SendControlMsg(msg: number, ExtraMsg: string = "") {
|
|
300
|
+
this.lastSendTime = new Date().getTime();
|
|
248
301
|
return new Promise((resolve, reject) => {
|
|
249
302
|
if (this.socket) {
|
|
250
303
|
var latestBuf = Buffer.from([0x10 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x00, msg])
|
|
@@ -261,227 +314,356 @@ class Client extends EventEmitter {
|
|
|
261
314
|
*/
|
|
262
315
|
})
|
|
263
316
|
}
|
|
264
|
-
SendMsgEx(Msg: MsgPacker, Flags: number) {
|
|
265
|
-
if (this.State == -1)
|
|
266
|
-
throw new Error("Client is not connected");
|
|
267
|
-
if (!this.socket)
|
|
268
|
-
return;
|
|
269
|
-
var header = []
|
|
270
|
-
header[0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
|
|
271
|
-
header[1] = (Msg.size & 0xf);
|
|
272
|
-
if (Flags & 1) {
|
|
273
|
-
this.clientAck = (this.clientAck + 1) % (1 << 10);
|
|
274
|
-
header[1] |= (this.clientAck >> 2) & 0xf0;
|
|
275
|
-
header[2] = this.clientAck & 0xff;
|
|
276
|
-
}
|
|
277
|
-
var latestBuf = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x1, header[0], header[1], this.clientAck]);
|
|
278
|
-
var latestBuf = Buffer.concat([latestBuf, Msg.buffer, this.TKEN]);
|
|
279
317
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (this.State == -1)
|
|
284
|
-
throw new Error("Client is not connected");
|
|
318
|
+
SendMsgEx(Msgs: MsgPacker[] | MsgPacker, Flags: number) {
|
|
319
|
+
if (this.State == States.STATE_OFFLINE)
|
|
320
|
+
return;
|
|
285
321
|
if (!this.socket)
|
|
286
322
|
return;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
323
|
+
let _Msgs: MsgPacker[];
|
|
324
|
+
if (Msgs instanceof Array)
|
|
325
|
+
_Msgs = Msgs;
|
|
326
|
+
else
|
|
327
|
+
_Msgs = [Msgs];
|
|
328
|
+
this.lastSendTime = new Date().getTime();
|
|
329
|
+
var header: Buffer[] = [];
|
|
330
|
+
_Msgs.forEach((Msg: MsgPacker, index) => {
|
|
331
|
+
header[index] = Buffer.alloc((Flags & 1 ? 3 : 2));
|
|
291
332
|
header[index][0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
|
|
292
333
|
header[index][1] = (Msg.size & 0xf);
|
|
293
334
|
if (Flags & 1) {
|
|
294
335
|
this.clientAck = (this.clientAck + 1) % (1 << 10);
|
|
295
336
|
header[index][1] |= (this.clientAck >> 2) & 0xf0;
|
|
296
337
|
header[index][2] = this.clientAck & 0xff;
|
|
338
|
+
header[index][0] = (((Flags | 2)&3)<<6)|((Msg.size>>4)&0x3f); // 2 is resend flag (ugly hack for queue)
|
|
339
|
+
|
|
340
|
+
this.sentChunkQueue.push(Buffer.concat([header[index], Msg.buffer]));
|
|
341
|
+
header[index][0] = (((Flags)&3)<<6)|((Msg.size>>4)&0x3f);
|
|
297
342
|
}
|
|
298
343
|
})
|
|
299
|
-
var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff,
|
|
344
|
+
var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, _Msgs.length]);
|
|
300
345
|
var chunks = Buffer.from([]);
|
|
301
|
-
|
|
346
|
+
_Msgs.forEach((Msg: MsgPacker, index) => {
|
|
302
347
|
chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
|
|
303
348
|
})
|
|
304
349
|
var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
|
|
305
350
|
|
|
306
351
|
this.socket.send(packet, 0, packet.length, this.port, this.host)
|
|
307
352
|
}
|
|
353
|
+
SendMsgRaw(chunks: Buffer[]) {
|
|
354
|
+
if (this.State == States.STATE_OFFLINE)
|
|
355
|
+
return;
|
|
356
|
+
if (!this.socket)
|
|
357
|
+
return;
|
|
358
|
+
|
|
359
|
+
this.lastSendTime = new Date().getTime();
|
|
360
|
+
|
|
361
|
+
var packetHeader = Buffer.from([0x0+(((16<<4)&0xf0)|((this.ack>>8)&0xf)), this.ack&0xff, chunks.length]);
|
|
362
|
+
|
|
363
|
+
var packet = Buffer.concat([(packetHeader), Buffer.concat(chunks), this.TKEN]);
|
|
364
|
+
|
|
365
|
+
this.socket.send(packet, 0, packet.length, this.port, this.host)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
MsgToChunk(packet: Buffer) {
|
|
369
|
+
var chunk: chunk = {} as chunk;
|
|
370
|
+
chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1));
|
|
371
|
+
chunk.flags = (packet[0] >> 6) & 3;
|
|
372
|
+
chunk.sequence = -1;
|
|
373
|
+
|
|
374
|
+
if (chunk.flags & 1) {
|
|
375
|
+
chunk.seq = ((packet[1]&0xf0)<<2) | packet[2];
|
|
376
|
+
packet = packet.slice(3) // remove flags & size
|
|
377
|
+
} else
|
|
378
|
+
packet = packet.slice(2)
|
|
379
|
+
chunk.type = packet[0] & 1 ? "sys" : "game"; // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
380
|
+
chunk.msgid = (packet[0]-(packet[0]&1))/2;
|
|
381
|
+
chunk.msg = messageTypes[packet[0]&1][chunk.msgid];
|
|
382
|
+
chunk.raw = packet.slice(1, chunk.bytes)
|
|
383
|
+
Object.values(messageUUIDs).forEach((a, i) => {
|
|
384
|
+
if (a.compare(packet.slice(0, 16)) === 0) {
|
|
385
|
+
chunk.extended_msgid = a;
|
|
386
|
+
chunk.msg = Object.keys(messageUUIDs)[i];
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
return chunk;
|
|
390
|
+
}
|
|
391
|
+
|
|
308
392
|
connect() {
|
|
393
|
+
|
|
394
|
+
this.State = States.STATE_CONNECTING;
|
|
395
|
+
|
|
396
|
+
let predTimer = setInterval(() => {
|
|
397
|
+
if (this.State == States.STATE_ONLINE) {
|
|
398
|
+
if (this.AckGameTick > 0)
|
|
399
|
+
this.PredGameTick++;
|
|
400
|
+
} else if (this.State == States.STATE_OFFLINE)
|
|
401
|
+
clearInterval(predTimer);
|
|
402
|
+
|
|
403
|
+
}, 20);
|
|
404
|
+
|
|
309
405
|
this.SendControlMsg(1, "TKEN")
|
|
310
406
|
let connectInterval = setInterval(() => {
|
|
311
|
-
if (this.State ==
|
|
407
|
+
if (this.State == States.STATE_CONNECTING)
|
|
312
408
|
this.SendControlMsg(1, "TKEN")
|
|
313
409
|
else
|
|
314
410
|
clearInterval(connectInterval)
|
|
411
|
+
}, 500);
|
|
412
|
+
|
|
413
|
+
let inputInterval = setInterval(() => {
|
|
414
|
+
if (this.State == States.STATE_OFFLINE)
|
|
415
|
+
clearInterval(inputInterval)
|
|
416
|
+
if (this.State != States.STATE_ONLINE)
|
|
417
|
+
return;
|
|
418
|
+
this.time = new Date().getTime();
|
|
419
|
+
this.sendInput();
|
|
315
420
|
}, 500)
|
|
421
|
+
|
|
422
|
+
let resendTimeout = setInterval(() => {
|
|
423
|
+
if (this.State != States.STATE_OFFLINE) {
|
|
424
|
+
if (((new Date().getTime()) - this.lastSendTime) > 900 && this.sentChunkQueue.length > 0) {
|
|
425
|
+
this.SendMsgRaw([this.sentChunkQueue[0]]);
|
|
426
|
+
}
|
|
427
|
+
} else
|
|
428
|
+
clearInterval(resendTimeout)
|
|
429
|
+
}, 1000)
|
|
430
|
+
|
|
431
|
+
|
|
316
432
|
this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
|
|
433
|
+
|
|
317
434
|
if (this.socket)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
435
|
+
this.socket.on("message", (a, rinfo) => {
|
|
436
|
+
if (this.State == 0 || rinfo.address != this.host || rinfo.port != this.port)
|
|
437
|
+
return;
|
|
438
|
+
clearInterval(connectInterval)
|
|
439
|
+
if (a.toJSON().data[0] == 0x10) {
|
|
440
|
+
if (a.toString().includes("TKEN") || a.toJSON().data[3] == 0x2) {
|
|
441
|
+
clearInterval(connectInterval);
|
|
442
|
+
this.TKEN = Buffer.from(a.toJSON().data.slice(a.toJSON().data.length - 4, a.toJSON().data.length))
|
|
443
|
+
this.SendControlMsg(3);
|
|
444
|
+
this.State = States.STATE_LOADING; // loading state
|
|
445
|
+
this.receivedSnaps = 0;
|
|
446
|
+
|
|
447
|
+
var info = new MsgPacker(1, true);
|
|
448
|
+
info.AddString("0.6 626fce9a778df4d4");
|
|
449
|
+
info.AddString(this.options?.password === undefined ? "" : this.options?.password); // password
|
|
450
|
+
|
|
451
|
+
var client_version = new MsgPacker(0, true);
|
|
452
|
+
client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
|
|
453
|
+
let randomUuid = Buffer.alloc(16);
|
|
454
|
+
|
|
455
|
+
randomBytes(16).copy(randomUuid);
|
|
456
|
+
|
|
457
|
+
client_version.AddBuffer(randomUuid);
|
|
458
|
+
if (this.options?.ddnet_version !== undefined) {
|
|
459
|
+
client_version.AddInt(this.options?.ddnet_version.version);
|
|
460
|
+
client_version.AddString("DDNet " + this.options?.ddnet_version.release_version);
|
|
461
|
+
} else {
|
|
462
|
+
client_version.AddInt(16003);
|
|
463
|
+
client_version.AddString("DDNet 16.0.3");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
this.SendMsgEx([client_version, info], 1)
|
|
467
|
+
} else if (a.toJSON().data[3] == 0x4) {
|
|
468
|
+
// disconnected
|
|
469
|
+
this.State = States.STATE_OFFLINE;
|
|
470
|
+
let reason: string = (unpackString(a.toJSON().data.slice(4)).result);
|
|
471
|
+
this.emit("disconnect", reason);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
var unpacked: _packet = this.Unpack(a)
|
|
321
477
|
unpacked.chunks.forEach(a => {
|
|
322
|
-
if (a.
|
|
478
|
+
if (a.flags & 1) { // vital
|
|
323
479
|
if (a.seq != undefined && a.seq != -1)
|
|
324
480
|
this.ack = a.seq
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
})
|
|
328
|
-
}
|
|
329
|
-
var chunkMessages = unpacked.chunks.map(a => a.msg)
|
|
330
|
-
if (chunkMessages.includes("SV_CHAT")) {
|
|
331
|
-
var chat = unpacked.chunks.filter(a => a.msg == "SV_CHAT");
|
|
332
|
-
chat.forEach(a => {
|
|
333
|
-
if (a.msg == "SV_CHAT") {
|
|
334
|
-
var unpacked: iMessage = {} as iMessage;
|
|
335
|
-
unpacked.team = unpackInt(a.raw.toJSON().data).result;
|
|
336
|
-
var remaining: number[] = unpackInt(a.raw.toJSON().data).remaining;
|
|
337
|
-
unpacked.client_id = unpackInt(remaining).result;
|
|
338
|
-
remaining = unpackInt(remaining).remaining;
|
|
339
|
-
unpacked.message = unpackString(remaining).result;
|
|
340
|
-
if (unpacked.client_id != -1)
|
|
341
|
-
unpacked.author = { ClientInfo: this.client_infos[unpacked.client_id], PlayerInfo: this.player_infos[unpacked.client_id] }
|
|
342
|
-
// console.log(unpacked)
|
|
343
|
-
this.emit("message", unpacked)
|
|
344
481
|
}
|
|
345
482
|
})
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
unpacked.killer_id = unpacker.unpackInt();
|
|
354
|
-
unpacked.victim_id = unpacker.unpackInt();
|
|
355
|
-
unpacked.weapon = unpacker.unpackInt();
|
|
356
|
-
unpacked.special_mode = unpacker.unpackInt();
|
|
357
|
-
if (unpacked.victim_id != -1)
|
|
358
|
-
unpacked.victim = { ClientInfo: this.client_infos[unpacked.victim_id], PlayerInfo: this.player_infos[unpacked.victim_id] }
|
|
359
|
-
if (unpacked.killer_id != -1)
|
|
360
|
-
unpacked.killer = { ClientInfo: this.client_infos[unpacked.killer_id], PlayerInfo: this.player_infos[unpacked.killer_id] }
|
|
361
|
-
// console.log(unpacked)
|
|
362
|
-
this.emit("kill", unpacked)
|
|
363
|
-
}
|
|
483
|
+
this.sentChunkQueue.forEach((buff, i) => {
|
|
484
|
+
let chunk = this.MsgToChunk(buff);
|
|
485
|
+
if (chunk.flags & 1) {
|
|
486
|
+
if (chunk.seq && chunk.seq < this.ack) {
|
|
487
|
+
this.sentChunkQueue.splice(i, 1);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
364
490
|
})
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (a.toString().includes("TKEN") || arrStartsWith(a.toJSON().data, [0x10, 0x0, 0x0, 0x0])) {
|
|
368
|
-
clearInterval(connectInterval);
|
|
369
|
-
this.TKEN = Buffer.from(a.toJSON().data.slice(a.toJSON().data.length - 4, a.toJSON().data.length))
|
|
370
|
-
this.SendControlMsg(3);
|
|
371
|
-
this.State = 2; // loading state
|
|
372
|
-
var info = new MsgPacker(1, true);
|
|
373
|
-
info.AddString("0.6 626fce9a778df4d4");
|
|
374
|
-
info.AddString(""); // password
|
|
375
|
-
|
|
376
|
-
var client_version = new MsgPacker(0, true);
|
|
377
|
-
client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
|
|
378
|
-
let randomUuid = new Uint8Array(16);
|
|
379
|
-
|
|
380
|
-
randomBytes(16).copy(randomUuid);
|
|
381
|
-
|
|
382
|
-
client_version.AddBuffer(Buffer.from(randomUuid));
|
|
383
|
-
client_version.AddInt(15091);
|
|
384
|
-
client_version.AddString("DDNet 15.9.1");
|
|
385
|
-
|
|
386
|
-
this.SendMsgExWithChunks([client_version, info], 1)
|
|
387
|
-
} else if (a.toJSON().data[3] == 0x4) {
|
|
388
|
-
// disconnected
|
|
389
|
-
this.State = 0;
|
|
390
|
-
let reason: string = (unpackString(a.toJSON().data.slice(4)).result);
|
|
391
|
-
this.State = -1;
|
|
392
|
-
this.emit("disconnect", reason);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
397
|
-
var Msg = new MsgPacker(15, true); /* entergame */
|
|
398
|
-
this.SendMsgEx(Msg, 1);
|
|
399
|
-
} else if ((unpacked.chunks[0] && chunkMessages.includes("CAPABILITIES") || unpacked.chunks[0] && chunkMessages.includes("MAP_CHANGE"))) {
|
|
400
|
-
// send ready
|
|
401
|
-
var Msg = new MsgPacker(14, true); /* ready */
|
|
402
|
-
this.SendMsgEx(Msg, 1);
|
|
403
|
-
} else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY") || unpacked.chunks[0] && chunkMessages.includes("SV_MOTD"))) {
|
|
404
|
-
var info = new MsgPacker(20, false);
|
|
405
|
-
info.AddString(this.name); /* name */
|
|
406
|
-
info.AddString(""); /* clan */
|
|
407
|
-
info.AddInt(-1); /* country */
|
|
408
|
-
info.AddString("greyfox"); /* skin */
|
|
409
|
-
info.AddInt(1); /* use custom color */
|
|
410
|
-
info.AddInt(10346103); /* color body */
|
|
411
|
-
info.AddInt(65535); /* color feet */
|
|
412
|
-
this.SendMsgEx(info, 1);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
} else if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
416
|
-
|
|
417
|
-
if (this.State != 3) {
|
|
418
|
-
this.emit('connected');
|
|
419
|
-
}
|
|
420
|
-
this.State = 3
|
|
421
|
-
} else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
|
|
422
|
-
var info = new MsgPacker(23, true);
|
|
423
|
-
this.SendMsgEx(info, 1)
|
|
424
|
-
}
|
|
425
|
-
if (chunkMessages.includes("SNAP") || chunkMessages.includes("SNAP_EMPTY") || chunkMessages.includes("SNAP_SINGLE")) {
|
|
426
|
-
this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
|
|
427
|
-
if (this.receivedSnaps >= 2) {
|
|
428
|
-
if (this.State != 3)
|
|
429
|
-
this.emit('connected')
|
|
430
|
-
this.State = 3
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
var chunks = unpacked.chunks.filter(a => a.msg == "SNAP" || a.msg == "SNAP_SINGLE" || a.msg == "SNAP_EMPTY");
|
|
434
|
-
if (chunks.length > 0) {
|
|
491
|
+
var snapChunks = unpacked.chunks.filter(a => a.msg === "SNAP" || a.msg === "SNAP_SINGLE" || a.msg === "SNAP_EMPTY");
|
|
492
|
+
if (snapChunks.length > 0) {
|
|
435
493
|
let part = 0;
|
|
436
494
|
let num_parts = 1;
|
|
437
|
-
|
|
438
|
-
let
|
|
439
|
-
|
|
440
|
-
let
|
|
441
|
-
if (
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
495
|
+
snapChunks.forEach(chunk => {
|
|
496
|
+
let unpacker = new MsgUnpacker(chunk.raw.toJSON().data);
|
|
497
|
+
|
|
498
|
+
let AckGameTick = unpacker.unpackInt();
|
|
499
|
+
if (AckGameTick > this.AckGameTick) {
|
|
500
|
+
this.AckGameTick = AckGameTick;
|
|
501
|
+
if (Math.abs(this.PredGameTick - this.AckGameTick) > 10)
|
|
502
|
+
this.PredGameTick = AckGameTick + 1;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining);
|
|
506
|
+
let DeltaTick = AckGameTick - unpacker.unpackInt();
|
|
507
|
+
let num_parts = 1;
|
|
508
|
+
let part = 0;
|
|
509
|
+
|
|
510
|
+
if (chunk.msg === "SNAP") {
|
|
511
|
+
// chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // delta tick
|
|
512
|
+
num_parts = unpacker.unpackInt();
|
|
513
|
+
// chunk.raw = Buffer.from(unpackInt(chunk?.raw?.toJSON().data).remaining); // num parts
|
|
514
|
+
part = unpacker.unpackInt();
|
|
446
515
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
chunk.
|
|
451
|
-
|
|
516
|
+
|
|
517
|
+
let crc = 0;
|
|
518
|
+
let part_size = 0;
|
|
519
|
+
if (chunk.msg != "SNAP_EMPTY") {
|
|
520
|
+
crc = unpacker.unpackInt(); // crc
|
|
521
|
+
part_size = unpacker.unpackInt();
|
|
522
|
+
}
|
|
523
|
+
if (part === 0 || this.snaps.length > 30) {
|
|
452
524
|
this.snaps = [];
|
|
453
525
|
}
|
|
526
|
+
chunk.raw = Buffer.from(unpacker.remaining);
|
|
454
527
|
this.snaps.push(chunk.raw)
|
|
455
528
|
|
|
456
|
-
if ((num_parts - 1)
|
|
529
|
+
if ((num_parts - 1) === part && this.snaps.length === num_parts) {
|
|
457
530
|
let mergedSnaps = Buffer.concat(this.snaps);
|
|
531
|
+
// mergedSnaps = Buffer.from(unpackInt(mergedSnaps.toJSON().data).remaining);
|
|
458
532
|
let snapUnpacked = SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, 1)
|
|
459
|
-
|
|
533
|
+
// console.log(snapUnpacked.items, toHexStream(mergedSnaps));
|
|
460
534
|
snapUnpacked.items.forEach((a, i) => {
|
|
461
|
-
if (a.type_id
|
|
535
|
+
if (a.type_id === items.OBJ_CLIENT_INFO) {
|
|
462
536
|
this.client_infos[a.id] = a.parsed as ClientInfo;
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
} else if (a.type_id == items.OBJ_PLAYER_INFO) {
|
|
466
|
-
this.player_infos[i] = a.parsed as PlayerInfo;
|
|
467
|
-
} else if (a.type_id == items.OBJ_EX || a.type_id > 0x4000) {
|
|
468
|
-
if (a.data.length == 5 && ((a.parsed as DdnetCharacter).freeze_end > 0 || (a.parsed as DdnetCharacter).freeze_end == -1)) {
|
|
469
|
-
// var packer = new MsgPacker(22, false)
|
|
470
|
-
// this.SendMsgEx(packer, 1)
|
|
537
|
+
if (this.client_infos[a.id].name.includes("�") || this.client_infos[a.id].clan.includes("�")) {
|
|
538
|
+
console.log("bad name", this.client_infos[a.id], toHexStream(mergedSnaps), chunk, AckGameTick, DeltaTick, crc, part_size);
|
|
471
539
|
}
|
|
540
|
+
console.log(this.client_infos[a.id])
|
|
472
541
|
}
|
|
473
542
|
})
|
|
474
543
|
}
|
|
475
544
|
|
|
545
|
+
|
|
546
|
+
})
|
|
547
|
+
}
|
|
548
|
+
var chunkMessages = unpacked.chunks.map(a => a.msg)
|
|
549
|
+
if (chunkMessages.includes("SV_CHAT")) {
|
|
550
|
+
var chat = unpacked.chunks.filter(a => a.msg == "SV_CHAT");
|
|
551
|
+
chat.forEach(a => {
|
|
552
|
+
if (a.msg == "SV_CHAT") {
|
|
553
|
+
let unpacker = new MsgUnpacker(a.raw.toJSON().data);
|
|
554
|
+
var unpacked: iMessage = {
|
|
555
|
+
team: unpacker.unpackInt(),
|
|
556
|
+
client_id: unpacker.unpackInt(),
|
|
557
|
+
message: unpacker.unpackString()
|
|
558
|
+
} as iMessage;
|
|
559
|
+
|
|
560
|
+
if (unpacked.client_id != -1)
|
|
561
|
+
unpacked.author = {
|
|
562
|
+
ClientInfo: this.client_infos[unpacked.client_id],
|
|
563
|
+
PlayerInfo: this.player_infos[unpacked.client_id]
|
|
564
|
+
}
|
|
565
|
+
this.emit("message", unpacked)
|
|
566
|
+
}
|
|
567
|
+
})
|
|
568
|
+
}
|
|
569
|
+
if (chunkMessages.includes("SV_KILL_MSG")) {
|
|
570
|
+
var chat = unpacked.chunks.filter(a => a.msg == "SV_KILL_MSG");
|
|
571
|
+
chat.forEach(a => {
|
|
572
|
+
if (a.msg == "SV_KILL_MSG") {
|
|
573
|
+
var unpacked: iKillMsg = {} as iKillMsg;
|
|
574
|
+
let unpacker = new MsgUnpacker(a.raw.toJSON().data);
|
|
575
|
+
unpacked.killer_id = unpacker.unpackInt();
|
|
576
|
+
unpacked.victim_id = unpacker.unpackInt();
|
|
577
|
+
unpacked.weapon = unpacker.unpackInt();
|
|
578
|
+
unpacked.special_mode = unpacker.unpackInt();
|
|
579
|
+
if (unpacked.victim_id != -1)
|
|
580
|
+
unpacked.victim = { ClientInfo: this.client_infos[unpacked.victim_id], PlayerInfo: this.player_infos[unpacked.victim_id] }
|
|
581
|
+
if (unpacked.killer_id != -1)
|
|
582
|
+
unpacked.killer = { ClientInfo: this.client_infos[unpacked.killer_id], PlayerInfo: this.player_infos[unpacked.killer_id] }
|
|
583
|
+
this.emit("kill", unpacked)
|
|
584
|
+
}
|
|
476
585
|
})
|
|
477
586
|
}
|
|
478
587
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
588
|
+
if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
589
|
+
var Msg = new MsgPacker(15, true); /* entergame */
|
|
590
|
+
this.SendMsgEx(Msg, 1);
|
|
591
|
+
} else if ((unpacked.chunks[0] && chunkMessages.includes("CAPABILITIES") || unpacked.chunks[0] && chunkMessages.includes("MAP_CHANGE"))) {
|
|
592
|
+
// send ready
|
|
593
|
+
var Msg = new MsgPacker(14, true); /* ready */
|
|
594
|
+
this.SendMsgEx(Msg, 1);
|
|
595
|
+
} else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY") || unpacked.chunks[0] && chunkMessages.includes("SV_MOTD"))) {
|
|
596
|
+
var info = new MsgPacker(20, false);
|
|
597
|
+
if (this.options?.identity) {
|
|
598
|
+
info.AddString(this.options.identity.name);
|
|
599
|
+
info.AddString(this.options.identity.clan);
|
|
600
|
+
info.AddInt(this.options.identity.country);
|
|
601
|
+
info.AddString(this.options.identity.skin);
|
|
602
|
+
info.AddInt(this.options.identity.use_custom_color);
|
|
603
|
+
info.AddInt(this.options.identity.color_body);
|
|
604
|
+
info.AddInt(this.options.identity.color_feet);
|
|
605
|
+
} else {
|
|
606
|
+
info.AddString(this.name); /* name */
|
|
607
|
+
info.AddString(""); /* clan */
|
|
608
|
+
info.AddInt(-1); /* country */
|
|
609
|
+
info.AddString("greyfox"); /* skin */
|
|
610
|
+
info.AddInt(1); /* use custom color */
|
|
611
|
+
info.AddInt(10346103); /* color body */
|
|
612
|
+
info.AddInt(65535); /* color feet */
|
|
613
|
+
|
|
614
|
+
}
|
|
615
|
+
this.SendMsgEx(info, 1);
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
} else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
|
|
619
|
+
var info = new MsgPacker(23, true);
|
|
620
|
+
this.SendMsgEx(info, 1)
|
|
621
|
+
}
|
|
622
|
+
if (chunkMessages.includes("SNAP") || chunkMessages.includes("SNAP_EMPTY") || chunkMessages.includes("SNAP_SINGLE")) {
|
|
623
|
+
this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
|
|
624
|
+
if (this.receivedSnaps == 2) {
|
|
625
|
+
if (this.State != States.STATE_ONLINE)
|
|
626
|
+
this.emit('connected')
|
|
627
|
+
this.State = States.STATE_ONLINE;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
}
|
|
631
|
+
if (new Date().getTime() - this.time >= 1000 && this.State == States.STATE_ONLINE) {
|
|
632
|
+
this.time = new Date().getTime();
|
|
633
|
+
this.SendControlMsg(0);
|
|
634
|
+
}
|
|
635
|
+
})
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
sendInput(input = this.movement.input) {
|
|
639
|
+
if (this.State != States.STATE_ONLINE)
|
|
640
|
+
return;
|
|
641
|
+
|
|
642
|
+
let inputMsg = new MsgPacker(16, true);
|
|
643
|
+
inputMsg.AddInt(this.AckGameTick);
|
|
644
|
+
inputMsg.AddInt(this.PredGameTick);
|
|
645
|
+
inputMsg.AddInt(40);
|
|
646
|
+
|
|
647
|
+
let input_data = [
|
|
648
|
+
|
|
649
|
+
input.m_Direction,
|
|
650
|
+
input.m_TargetX,
|
|
651
|
+
input.m_TargetY,
|
|
652
|
+
input.m_Jump,
|
|
653
|
+
input.m_Fire,
|
|
654
|
+
input.m_Hook,
|
|
655
|
+
input.m_PlayerFlags,
|
|
656
|
+
input.m_WantedWeapon,
|
|
657
|
+
input.m_NextWeapon,
|
|
658
|
+
input.m_PrevWeapon
|
|
659
|
+
]
|
|
660
|
+
input_data.forEach(a => {
|
|
661
|
+
inputMsg.AddInt(a);
|
|
662
|
+
});
|
|
663
|
+
this.SendMsgEx(inputMsg, 0);
|
|
664
|
+
}
|
|
665
|
+
get input() {
|
|
666
|
+
return this.movement.input;
|
|
485
667
|
}
|
|
486
668
|
|
|
487
669
|
Disconnect() {
|
|
@@ -491,7 +673,7 @@ class Client extends EventEmitter {
|
|
|
491
673
|
if (this.socket)
|
|
492
674
|
this.socket.close();
|
|
493
675
|
this.socket = undefined
|
|
494
|
-
this.State =
|
|
676
|
+
this.State = States.STATE_OFFLINE;
|
|
495
677
|
})
|
|
496
678
|
})
|
|
497
679
|
}
|
|
@@ -533,6 +715,5 @@ class Client extends EventEmitter {
|
|
|
533
715
|
this.SendMsgEx(packer, 1);
|
|
534
716
|
}
|
|
535
717
|
|
|
718
|
+
|
|
536
719
|
}
|
|
537
|
-
export = Client;
|
|
538
|
-
// module.exports = Client;
|