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