teeworlds 2.0.0 → 2.0.3
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 +2 -2
- package/lib/client.js +71 -25
- package/lib/client.ts +251 -198
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ Library to connect a bot to a Teeworlds server.
|
|
|
3
3
|
|
|
4
4
|
# Usage
|
|
5
5
|
Example file:
|
|
6
|
-
```const test = require('
|
|
6
|
+
```const test = require('teeworlds');
|
|
7
7
|
let client = new test.Client("127.0.0.1", 8303, "test");
|
|
8
8
|
|
|
9
9
|
client.connect();
|
|
@@ -75,7 +75,7 @@ client.on("kill", info => {
|
|
|
75
75
|
})
|
|
76
76
|
|
|
77
77
|
process.on("SIGINT", () => {
|
|
78
|
-
client.
|
|
78
|
+
client.Disconnect(); // disconnect on ctrl + c
|
|
79
79
|
})
|
|
80
80
|
process.stdin.on("data", data => {
|
|
81
81
|
client.Say(data.toString()); // write input in chat
|
package/lib/client.js
CHANGED
|
@@ -51,6 +51,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
51
51
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
52
52
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
53
53
|
};
|
|
54
|
+
var crypto_1 = require("crypto");
|
|
54
55
|
var dgram_1 = __importDefault(require("dgram"));
|
|
55
56
|
var stream_1 = require("stream");
|
|
56
57
|
var child_process_1 = require("child_process");
|
|
@@ -241,11 +242,13 @@ var Client = /** @class */ (function (_super) {
|
|
|
241
242
|
var _this = this;
|
|
242
243
|
if (ExtraMsg === void 0) { ExtraMsg = ""; }
|
|
243
244
|
return new Promise(function (resolve, reject) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
if (_this.socket) {
|
|
246
|
+
var latestBuf = Buffer.from([0x10 + (((16 << 4) & 0xf0) | ((_this.ack >> 8) & 0xf)), _this.ack & 0xff, 0x00, msg]);
|
|
247
|
+
latestBuf = Buffer.concat([latestBuf, Buffer.from(ExtraMsg), _this.TKEN]); // move header (latestBuf), optional extraMsg & TKEN into 1 buffer
|
|
248
|
+
_this.socket.send(latestBuf, 0, latestBuf.length, _this.port, _this.host, function (err, bytes) {
|
|
249
|
+
resolve(bytes);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
249
252
|
setTimeout(function () { resolve("failed, rip"); }, 2000);
|
|
250
253
|
/* after 2 seconds it was probably not able to send,
|
|
251
254
|
so when sending a quit message the user doesnt
|
|
@@ -256,6 +259,8 @@ var Client = /** @class */ (function (_super) {
|
|
|
256
259
|
Client.prototype.SendMsgEx = function (Msg, Flags) {
|
|
257
260
|
if (this.State == -1)
|
|
258
261
|
throw new Error("Client is not connected");
|
|
262
|
+
if (!this.socket)
|
|
263
|
+
return;
|
|
259
264
|
var header = [];
|
|
260
265
|
header[0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
|
|
261
266
|
header[1] = (Msg.size & 0xf);
|
|
@@ -266,15 +271,45 @@ var Client = /** @class */ (function (_super) {
|
|
|
266
271
|
}
|
|
267
272
|
var latestBuf = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x1, header[0], header[1], this.clientAck]);
|
|
268
273
|
var latestBuf = Buffer.concat([latestBuf, Msg.buffer, this.TKEN]);
|
|
269
|
-
this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host
|
|
274
|
+
this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host);
|
|
275
|
+
};
|
|
276
|
+
Client.prototype.SendMsgExWithChunks = function (Msgs, Flags) {
|
|
277
|
+
var _this = this;
|
|
278
|
+
if (this.State == -1)
|
|
279
|
+
throw new Error("Client is not connected");
|
|
280
|
+
if (!this.socket)
|
|
281
|
+
return;
|
|
282
|
+
var header = [];
|
|
283
|
+
Msgs.forEach(function (Msg, index) {
|
|
284
|
+
header[index] = new Array(2);
|
|
285
|
+
header[index][0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
|
|
286
|
+
header[index][1] = (Msg.size & 0xf);
|
|
287
|
+
if (Flags & 1) {
|
|
288
|
+
_this.clientAck = (_this.clientAck + 1) % (1 << 10);
|
|
289
|
+
header[index][1] |= (_this.clientAck >> 2) & 0xf0;
|
|
290
|
+
header[index][2] = _this.clientAck & 0xff;
|
|
291
|
+
}
|
|
270
292
|
});
|
|
293
|
+
var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, Msgs.length]);
|
|
294
|
+
var chunks = Buffer.from([]);
|
|
295
|
+
Msgs.forEach(function (Msg, index) {
|
|
296
|
+
chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
|
|
297
|
+
});
|
|
298
|
+
var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
|
|
299
|
+
this.socket.send(packet, 0, packet.length, this.port, this.host);
|
|
271
300
|
};
|
|
272
301
|
Client.prototype.connect = function () {
|
|
273
302
|
var _this = this;
|
|
274
303
|
this.SendControlMsg(1, "TKEN");
|
|
304
|
+
var connectInterval = setInterval(function () {
|
|
305
|
+
if (_this.State == 0)
|
|
306
|
+
_this.SendControlMsg(1, "TKEN");
|
|
307
|
+
else
|
|
308
|
+
clearInterval(connectInterval);
|
|
309
|
+
}, 500);
|
|
275
310
|
this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
|
|
276
311
|
this.socket.on("message", function (a) { return __awaiter(_this, void 0, void 0, function () {
|
|
277
|
-
var unpacked, chunkMessages, chat, chat,
|
|
312
|
+
var unpacked, chunkMessages, chat, chat, info, client_version, randomUuid, reason, Msg, Msg, info, info, chunks, part_1, num_parts_1;
|
|
278
313
|
var _this = this;
|
|
279
314
|
return __generator(this, function (_a) {
|
|
280
315
|
switch (_a.label) {
|
|
@@ -330,13 +365,21 @@ var Client = /** @class */ (function (_super) {
|
|
|
330
365
|
}
|
|
331
366
|
if (a.toJSON().data[0] == 0x10) {
|
|
332
367
|
if (a.toString().includes("TKEN") || arrStartsWith(a.toJSON().data, [0x10, 0x0, 0x0, 0x0])) {
|
|
368
|
+
clearInterval(connectInterval);
|
|
333
369
|
this.TKEN = Buffer.from(a.toJSON().data.slice(a.toJSON().data.length - 4, a.toJSON().data.length));
|
|
334
370
|
this.SendControlMsg(3);
|
|
335
371
|
this.State = 2; // loading state
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
372
|
+
info = new MsgPacker_1.default(1, true);
|
|
373
|
+
info.AddString("0.6 626fce9a778df4d4");
|
|
374
|
+
info.AddString(""); // password
|
|
375
|
+
client_version = new MsgPacker_1.default(0, true);
|
|
376
|
+
client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
|
|
377
|
+
randomUuid = new Uint8Array(16);
|
|
378
|
+
crypto_1.randomBytes(16).copy(randomUuid);
|
|
379
|
+
client_version.AddBuffer(Buffer.from(randomUuid));
|
|
380
|
+
client_version.AddInt(15091);
|
|
381
|
+
client_version.AddString("DDNet 15.9.1");
|
|
382
|
+
this.SendMsgExWithChunks([client_version, info], 1);
|
|
340
383
|
}
|
|
341
384
|
else if (a.toJSON().data[3] == 0x4) {
|
|
342
385
|
// disconnected
|
|
@@ -355,15 +398,15 @@ var Client = /** @class */ (function (_super) {
|
|
|
355
398
|
this.SendMsgEx(Msg, 1);
|
|
356
399
|
}
|
|
357
400
|
else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY") || unpacked.chunks[0] && chunkMessages.includes("SV_MOTD"))) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
this.SendMsgEx(
|
|
401
|
+
info = new MsgPacker_1.default(20, false);
|
|
402
|
+
info.AddString(this.name); /* name */
|
|
403
|
+
info.AddString(""); /* clan */
|
|
404
|
+
info.AddInt(-1); /* country */
|
|
405
|
+
info.AddString("greyfox"); /* skin */
|
|
406
|
+
info.AddInt(1); /* use custom color */
|
|
407
|
+
info.AddInt(10346103); /* color body */
|
|
408
|
+
info.AddInt(65535); /* color feet */
|
|
409
|
+
this.SendMsgEx(info, 1);
|
|
367
410
|
}
|
|
368
411
|
else if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
369
412
|
if (this.State != 3) {
|
|
@@ -372,8 +415,8 @@ var Client = /** @class */ (function (_super) {
|
|
|
372
415
|
this.State = 3;
|
|
373
416
|
}
|
|
374
417
|
else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
|
|
375
|
-
|
|
376
|
-
this.SendMsgEx(
|
|
418
|
+
info = new MsgPacker_1.default(23, true);
|
|
419
|
+
this.SendMsgEx(info, 1);
|
|
377
420
|
}
|
|
378
421
|
if (chunkMessages.includes("SNAP") || chunkMessages.includes("SNAP_EMPTY") || chunkMessages.includes("SNAP_SINGLE")) {
|
|
379
422
|
this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
|
|
@@ -439,9 +482,12 @@ var Client = /** @class */ (function (_super) {
|
|
|
439
482
|
};
|
|
440
483
|
Client.prototype.Disconnect = function () {
|
|
441
484
|
var _this = this;
|
|
442
|
-
|
|
443
|
-
_this.
|
|
444
|
-
|
|
485
|
+
return new Promise(function (resolve) {
|
|
486
|
+
_this.SendControlMsg(4).then(function () {
|
|
487
|
+
resolve(true);
|
|
488
|
+
_this.socket.close();
|
|
489
|
+
_this.State = -1;
|
|
490
|
+
});
|
|
445
491
|
});
|
|
446
492
|
};
|
|
447
493
|
Client.prototype.Say = function (message, team) {
|
package/lib/client.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
2
|
+
|
|
1
3
|
import net from 'dgram';
|
|
2
4
|
import fs from 'fs';
|
|
3
5
|
import { EventEmitter } from 'stream';
|
|
@@ -69,7 +71,7 @@ enum items {
|
|
|
69
71
|
}
|
|
70
72
|
interface chunk {
|
|
71
73
|
bytes: number,
|
|
72
|
-
flags: number,
|
|
74
|
+
flags: number,
|
|
73
75
|
sequence?: number,
|
|
74
76
|
seq?: number,
|
|
75
77
|
type: 'sys' | 'game',
|
|
@@ -96,7 +98,7 @@ async function decompress(buff: Buffer): Promise<Buffer> {
|
|
|
96
98
|
|
|
97
99
|
resolve(Buffer.from([]));
|
|
98
100
|
}, 750)
|
|
99
|
-
|
|
101
|
+
|
|
100
102
|
})
|
|
101
103
|
}
|
|
102
104
|
interface _packet {
|
|
@@ -105,7 +107,7 @@ interface _packet {
|
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
var messageTypes = [
|
|
108
|
-
["none, starts at 1", "SV_MOTD", "SV_BROADCAST", "SV_CHAT", "SV_KILL_MSG", "SV_SOUND_GLOBAL", "SV_TUNE_PARAMS", "SV_EXTRA_PROJECTILE", "SV_READY_TO_ENTER", "SV_WEAPON_PICKUP", "SV_EMOTICON", "SV_VOTE_CLEAR_OPTIONS", "SV_VOTE_OPTION_LIST_ADD", "SV_VOTE_OPTION_ADD", "SV_VOTE_OPTION_REMOVE", "SV_VOTE_SET", "SV_VOTE_STATUS", "CL_SAY", "CL_SET_TEAM", "CL_SET_SPECTATOR_MODE", "CL_START_INFO", "CL_CHANGE_INFO", "CL_KILL", "CL_EMOTICON", "CL_VOTE", "CL_CALL_VOTE", "CL_IS_DDNET", "SV_DDRACE_TIME", "SV_RECORD", "UNUSED", "SV_TEAMS_STATE", "CL_SHOW_OTHERS_LEGACY"],
|
|
110
|
+
["none, starts at 1", "SV_MOTD", "SV_BROADCAST", "SV_CHAT", "SV_KILL_MSG", "SV_SOUND_GLOBAL", "SV_TUNE_PARAMS", "SV_EXTRA_PROJECTILE", "SV_READY_TO_ENTER", "SV_WEAPON_PICKUP", "SV_EMOTICON", "SV_VOTE_CLEAR_OPTIONS", "SV_VOTE_OPTION_LIST_ADD", "SV_VOTE_OPTION_ADD", "SV_VOTE_OPTION_REMOVE", "SV_VOTE_SET", "SV_VOTE_STATUS", "CL_SAY", "CL_SET_TEAM", "CL_SET_SPECTATOR_MODE", "CL_START_INFO", "CL_CHANGE_INFO", "CL_KILL", "CL_EMOTICON", "CL_VOTE", "CL_CALL_VOTE", "CL_IS_DDNET", "SV_DDRACE_TIME", "SV_RECORD", "UNUSED", "SV_TEAMS_STATE", "CL_SHOW_OTHERS_LEGACY"],
|
|
109
111
|
["none, starts at 1", "INFO", "MAP_CHANGE", "MAP_DATA", "CON_READY", "SNAP", "SNAP_EMPTY", "SNAP_SINGLE", "INPUT_TIMING", "RCON_AUTH_STATUS", "RCON_LINE", "READY", "ENTER_GAME", "INPUT", "RCON_CMD", "RCON_AUTH", "REQUEST_MAP_DATA", "PING", "PING_REPLY", "RCON_CMD_ADD", "RCON_CMD_REMOVE"]
|
|
110
112
|
]
|
|
111
113
|
|
|
@@ -119,28 +121,28 @@ var messageUUIDs = {
|
|
|
119
121
|
"CAPABILITIES": Buffer.from([0xf6, 0x21, 0xa5, 0xa1, 0xf5, 0x85, 0x37, 0x75, 0x8e, 0x73, 0x41, 0xbe, 0xee, 0x79, 0xf2, 0xb2]),
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
function arrStartsWith(arr: number[], arrStart: number[], start=0) {
|
|
124
|
+
function arrStartsWith(arr: number[], arrStart: number[], start = 0) {
|
|
123
125
|
arr.splice(0, start)
|
|
124
|
-
|
|
126
|
+
for (let i = 0; i < arrStart.length; i++) {
|
|
125
127
|
if (arr[i] == arrStart[i])
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
continue;
|
|
129
|
+
else return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
130
132
|
}
|
|
131
133
|
interface iMessage {
|
|
132
|
-
team: number,
|
|
134
|
+
team: number,
|
|
133
135
|
client_id: number,
|
|
134
|
-
author?: {ClientInfo: ClientInfo, PlayerInfo: PlayerInfo},
|
|
136
|
+
author?: { ClientInfo: ClientInfo, PlayerInfo: PlayerInfo },
|
|
135
137
|
message: string
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
interface iKillMsg {
|
|
139
|
-
killer_id: number,
|
|
140
|
-
killer?: {ClientInfo: ClientInfo, PlayerInfo: PlayerInfo},
|
|
141
|
-
victim_id: number,
|
|
142
|
-
victim?: {ClientInfo: ClientInfo, PlayerInfo: PlayerInfo},
|
|
143
|
-
weapon: number,
|
|
141
|
+
killer_id: number,
|
|
142
|
+
killer?: { ClientInfo: ClientInfo, PlayerInfo: PlayerInfo },
|
|
143
|
+
victim_id: number,
|
|
144
|
+
victim?: { ClientInfo: ClientInfo, PlayerInfo: PlayerInfo },
|
|
145
|
+
weapon: number,
|
|
144
146
|
special_mode: number
|
|
145
147
|
}
|
|
146
148
|
|
|
@@ -162,12 +164,12 @@ declare interface Client {
|
|
|
162
164
|
client_infos: ClientInfo[];
|
|
163
165
|
player_infos: PlayerInfo[];
|
|
164
166
|
|
|
165
|
-
|
|
167
|
+
|
|
166
168
|
on(event: 'connected'): this;
|
|
167
169
|
on(event: 'disconnect', reason: string): this;
|
|
168
170
|
|
|
169
|
-
on(event: 'message', message: iMessage
|
|
170
|
-
on(event: 'kill', kill:
|
|
171
|
+
on(event: 'message', message: iMessage): this;
|
|
172
|
+
on(event: 'kill', kill: iKillMsg): this;
|
|
171
173
|
|
|
172
174
|
}
|
|
173
175
|
class Client extends EventEmitter {
|
|
@@ -189,23 +191,23 @@ class Client extends EventEmitter {
|
|
|
189
191
|
this.clientAck = 0; // ack of messages the client has sent
|
|
190
192
|
this.receivedSnaps = 0; /* wait for 2 snaps before seeing self as connected */
|
|
191
193
|
this.lastMsg = "";
|
|
192
|
-
this._port = Math.floor(Math.random()*65535)
|
|
194
|
+
this._port = Math.floor(Math.random() * 65535)
|
|
193
195
|
this.socket = net.createSocket("udp4")
|
|
194
196
|
this.socket.bind();
|
|
195
197
|
|
|
196
198
|
this.TKEN = Buffer.from([255, 255, 255, 255])
|
|
197
|
-
this.time = new Date().getTime()+2000; // time (used for keepalives, start to send keepalives after 2 seconds)
|
|
199
|
+
this.time = new Date().getTime() + 2000; // time (used for keepalives, start to send keepalives after 2 seconds)
|
|
198
200
|
this.State = 0;
|
|
199
201
|
}
|
|
200
202
|
async Unpack(packet: Buffer): Promise<_packet> {
|
|
201
|
-
var unpacked: _packet = {twprotocol: {flags: packet[0], ack: packet[1], chunkAmount: packet[2], size: packet.byteLength-3}, chunks: []}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (packet.indexOf(Buffer.from([0xff,0xff,0xff,0xff])) == 0 && !(unpacked.twprotocol.flags & 8) || unpacked.twprotocol.flags == 255) // flags == 255 is connectionless (used for sending usernames)
|
|
203
|
+
var unpacked: _packet = { twprotocol: { flags: packet[0], ack: packet[1], chunkAmount: packet[2], size: packet.byteLength - 3 }, chunks: [] }
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
if (packet.indexOf(Buffer.from([0xff, 0xff, 0xff, 0xff])) == 0 && !(unpacked.twprotocol.flags & 8) || unpacked.twprotocol.flags == 255) // flags == 255 is connectionless (used for sending usernames)
|
|
205
207
|
return unpacked;
|
|
206
208
|
packet = packet.slice(3)
|
|
207
209
|
if (unpacked.twprotocol.flags & 128) {
|
|
208
|
-
packet = await decompress(packet)
|
|
210
|
+
packet = await decompress(packet)
|
|
209
211
|
if (packet.length == 1 && packet[0] == -1)
|
|
210
212
|
return unpacked
|
|
211
213
|
}
|
|
@@ -215,15 +217,15 @@ class Client extends EventEmitter {
|
|
|
215
217
|
chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1)); // idk what this shit is but it works
|
|
216
218
|
chunk.flags = (packet[0] >> 6) & 3;
|
|
217
219
|
chunk.sequence = -1;
|
|
218
|
-
|
|
220
|
+
|
|
219
221
|
if (chunk.flags & 1) {
|
|
220
|
-
chunk.seq = ((packet[1]&0xf0)<<2) | packet[2];
|
|
222
|
+
chunk.seq = ((packet[1] & 0xf0) << 2) | packet[2];
|
|
221
223
|
packet = packet.slice(3) // remove flags & size
|
|
222
224
|
} else
|
|
223
225
|
packet = packet.slice(2)
|
|
224
226
|
chunk.type = packet[0] & 1 ? "sys" : "game"; // & 1 = binary, ****_***1. e.g 0001_0111 sys, 0001_0110 game
|
|
225
|
-
chunk.msgid = (packet[0]-(packet[0]&1))/2;
|
|
226
|
-
chunk.msg = messageTypes[packet[0]&1][chunk.msgid];
|
|
227
|
+
chunk.msgid = (packet[0] - (packet[0] & 1)) / 2;
|
|
228
|
+
chunk.msg = messageTypes[packet[0] & 1][chunk.msgid];
|
|
227
229
|
chunk.raw = packet.slice(1, chunk.bytes)
|
|
228
230
|
Object.values(messageUUIDs).forEach((a, i) => {
|
|
229
231
|
if (a.compare(packet.slice(0, 16)) == 0) {
|
|
@@ -232,209 +234,260 @@ class Client extends EventEmitter {
|
|
|
232
234
|
chunk.msg = Object.keys(messageUUIDs)[i];
|
|
233
235
|
}
|
|
234
236
|
})
|
|
235
|
-
|
|
237
|
+
|
|
236
238
|
packet = packet.slice(chunk.bytes) // +1 cuz it adds an extra \x00 for easier parsing i guess
|
|
237
239
|
unpacked.chunks.push(chunk)
|
|
238
240
|
}
|
|
239
241
|
return unpacked
|
|
240
|
-
|
|
242
|
+
}
|
|
241
243
|
SendControlMsg(msg: number, ExtraMsg: string = "") {
|
|
242
244
|
return new Promise((resolve, reject) => {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
if (this.socket) {
|
|
246
|
+
var latestBuf = Buffer.from([0x10 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x00, msg])
|
|
247
|
+
latestBuf = Buffer.concat([latestBuf, Buffer.from(ExtraMsg), this.TKEN]) // move header (latestBuf), optional extraMsg & TKEN into 1 buffer
|
|
246
248
|
this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host, (err, bytes) => {
|
|
247
249
|
resolve(bytes)
|
|
248
250
|
})
|
|
249
|
-
|
|
251
|
+
|
|
252
|
+
}
|
|
253
|
+
setTimeout(() => { resolve("failed, rip") }, 2000)
|
|
250
254
|
/* after 2 seconds it was probably not able to send,
|
|
251
255
|
so when sending a quit message the user doesnt
|
|
252
256
|
stay stuck not being able to ctrl + c
|
|
253
257
|
*/
|
|
254
|
-
|
|
258
|
+
})
|
|
255
259
|
}
|
|
256
260
|
SendMsgEx(Msg: MsgPacker, Flags: number) {
|
|
257
261
|
if (this.State == -1)
|
|
258
262
|
throw new Error("Client is not connected");
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
header
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
263
|
+
if (!this.socket)
|
|
264
|
+
return;
|
|
265
|
+
var header = []
|
|
266
|
+
header[0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
|
|
267
|
+
header[1] = (Msg.size & 0xf);
|
|
268
|
+
if (Flags & 1) {
|
|
269
|
+
this.clientAck = (this.clientAck + 1) % (1 << 10);
|
|
270
|
+
header[1] |= (this.clientAck >> 2) & 0xf0;
|
|
271
|
+
header[2] = this.clientAck & 0xff;
|
|
272
|
+
}
|
|
273
|
+
var latestBuf = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x1, header[0], header[1], this.clientAck]);
|
|
268
274
|
var latestBuf = Buffer.concat([latestBuf, Msg.buffer, this.TKEN]);
|
|
269
|
-
|
|
270
|
-
|
|
275
|
+
|
|
276
|
+
this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host)
|
|
277
|
+
}
|
|
278
|
+
SendMsgExWithChunks(Msgs: MsgPacker[], Flags: number) {
|
|
279
|
+
if (this.State == -1)
|
|
280
|
+
throw new Error("Client is not connected");
|
|
281
|
+
if (!this.socket)
|
|
282
|
+
return;
|
|
283
|
+
var header: any[][] = [];
|
|
284
|
+
|
|
285
|
+
Msgs.forEach((Msg: MsgPacker, index) => {
|
|
286
|
+
header[index] = new Array(2);
|
|
287
|
+
header[index][0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
|
|
288
|
+
header[index][1] = (Msg.size & 0xf);
|
|
289
|
+
if (Flags & 1) {
|
|
290
|
+
this.clientAck = (this.clientAck + 1) % (1 << 10);
|
|
291
|
+
header[index][1] |= (this.clientAck >> 2) & 0xf0;
|
|
292
|
+
header[index][2] = this.clientAck & 0xff;
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, Msgs.length]);
|
|
296
|
+
var chunks = Buffer.from([]);
|
|
297
|
+
Msgs.forEach((Msg: MsgPacker, index) => {
|
|
298
|
+
chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
|
|
299
|
+
})
|
|
300
|
+
var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
|
|
301
|
+
|
|
302
|
+
this.socket.send(packet, 0, packet.length, this.port, this.host)
|
|
271
303
|
}
|
|
272
304
|
connect() {
|
|
273
305
|
this.SendControlMsg(1, "TKEN")
|
|
306
|
+
let connectInterval = setInterval(() => {
|
|
307
|
+
if (this.State == 0)
|
|
308
|
+
this.SendControlMsg(1, "TKEN")
|
|
309
|
+
else
|
|
310
|
+
clearInterval(connectInterval)
|
|
311
|
+
}, 500)
|
|
274
312
|
this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
313
|
+
this.socket.on("message", async (a) => {
|
|
314
|
+
var unpacked: _packet = await this.Unpack(a)
|
|
315
|
+
if (unpacked.twprotocol.flags != 128 && unpacked.twprotocol.ack) {
|
|
316
|
+
unpacked.chunks.forEach(a => {
|
|
317
|
+
if (a.msg && !a.msg.startsWith("SNAP")) {
|
|
318
|
+
if (a.seq != undefined && a.seq != -1)
|
|
319
|
+
this.ack = a.seq
|
|
320
|
+
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
var chunkMessages = unpacked.chunks.map(a => a.msg)
|
|
325
|
+
if (chunkMessages.includes("SV_CHAT")) {
|
|
326
|
+
var chat = unpacked.chunks.filter(a => a.msg == "SV_CHAT");
|
|
327
|
+
chat.forEach(a => {
|
|
328
|
+
if (a.msg == "SV_CHAT") {
|
|
329
|
+
var unpacked: iMessage = {} as iMessage;
|
|
330
|
+
unpacked.team = MsgUnpacker.unpackInt(a.raw.toJSON().data).result;
|
|
331
|
+
var remaining: number[] = MsgUnpacker.unpackInt(a.raw.toJSON().data).remaining;
|
|
332
|
+
unpacked.client_id = MsgUnpacker.unpackInt(remaining).result;
|
|
333
|
+
remaining = MsgUnpacker.unpackInt(remaining).remaining;
|
|
334
|
+
unpacked.message = MsgUnpacker.unpackString(remaining).result;
|
|
335
|
+
if (unpacked.client_id != -1)
|
|
336
|
+
unpacked.author = { ClientInfo: this.client_infos[unpacked.client_id], PlayerInfo: this.player_infos[unpacked.client_id] }
|
|
337
|
+
// console.log(unpacked)
|
|
338
|
+
this.emit("message", unpacked)
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
if (chunkMessages.includes("SV_KILL_MSG")) {
|
|
343
|
+
var chat = unpacked.chunks.filter(a => a.msg == "SV_KILL_MSG");
|
|
344
|
+
chat.forEach(a => {
|
|
345
|
+
if (a.msg == "SV_KILL_MSG") {
|
|
346
|
+
var unpacked: iKillMsg = {} as iKillMsg;
|
|
347
|
+
unpacked.killer_id = MsgUnpacker.unpackInt(a.raw.toJSON().data).result;
|
|
348
|
+
var remaining: number[] = MsgUnpacker.unpackInt(a.raw.toJSON().data).remaining;
|
|
349
|
+
unpacked.victim_id = MsgUnpacker.unpackInt(remaining).result;
|
|
350
|
+
remaining = MsgUnpacker.unpackInt(remaining).remaining;
|
|
351
|
+
unpacked.weapon = MsgUnpacker.unpackInt(remaining).result;
|
|
352
|
+
remaining = MsgUnpacker.unpackInt(remaining).remaining;
|
|
353
|
+
unpacked.special_mode = MsgUnpacker.unpackInt(remaining).result;
|
|
354
|
+
if (unpacked.victim_id != -1)
|
|
355
|
+
unpacked.victim = { ClientInfo: this.client_infos[unpacked.victim_id], PlayerInfo: this.player_infos[unpacked.victim_id] }
|
|
356
|
+
if (unpacked.killer_id != -1)
|
|
357
|
+
unpacked.killer = { ClientInfo: this.client_infos[unpacked.killer_id], PlayerInfo: this.player_infos[unpacked.killer_id] }
|
|
358
|
+
// console.log(unpacked)
|
|
359
|
+
this.emit("kill", unpacked)
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
if (a.toJSON().data[0] == 0x10) {
|
|
364
|
+
if (a.toString().includes("TKEN") || arrStartsWith(a.toJSON().data, [0x10, 0x0, 0x0, 0x0])) {
|
|
365
|
+
clearInterval(connectInterval);
|
|
366
|
+
this.TKEN = Buffer.from(a.toJSON().data.slice(a.toJSON().data.length - 4, a.toJSON().data.length))
|
|
367
|
+
this.SendControlMsg(3);
|
|
368
|
+
this.State = 2; // loading state
|
|
369
|
+
var info = new MsgPacker(1, true);
|
|
370
|
+
info.AddString("0.6 626fce9a778df4d4");
|
|
371
|
+
info.AddString(""); // password
|
|
372
|
+
|
|
373
|
+
var client_version = new MsgPacker(0, true);
|
|
374
|
+
client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
|
|
375
|
+
let randomUuid = new Uint8Array(16);
|
|
376
|
+
|
|
377
|
+
randomBytes(16).copy(randomUuid);
|
|
378
|
+
|
|
379
|
+
client_version.AddBuffer(Buffer.from(randomUuid));
|
|
380
|
+
client_version.AddInt(15091);
|
|
381
|
+
client_version.AddString("DDNet 15.9.1");
|
|
382
|
+
|
|
383
|
+
this.SendMsgExWithChunks([client_version, info], 1)
|
|
384
|
+
} else if (a.toJSON().data[3] == 0x4) {
|
|
385
|
+
// disconnected
|
|
386
|
+
this.State = 0;
|
|
387
|
+
let reason: string = (MsgUnpacker.unpackString(a.toJSON().data.slice(4)).result);
|
|
388
|
+
this.State = -1;
|
|
389
|
+
this.emit("disconnect", reason);
|
|
322
390
|
}
|
|
323
|
-
})
|
|
324
|
-
}
|
|
325
|
-
if (a.toJSON().data[0] == 0x10) {
|
|
326
|
-
if (a.toString().includes("TKEN") || arrStartsWith(a.toJSON().data, [0x10, 0x0, 0x0, 0x0])) {
|
|
327
|
-
this.TKEN = Buffer.from(a.toJSON().data.slice(a.toJSON().data.length-4, a.toJSON().data.length))
|
|
328
|
-
this.SendControlMsg(3);
|
|
329
|
-
this.State = 2; // loading state
|
|
330
|
-
var packer = new MsgPacker(1, true);
|
|
331
|
-
packer.AddString("0.6 626fce9a778df4d4");
|
|
332
|
-
packer.AddString(""); // password
|
|
333
|
-
this.SendMsgEx(packer, 1)
|
|
334
|
-
} else if (a.toJSON().data[3] == 0x4) {
|
|
335
|
-
// disconnected
|
|
336
|
-
this.State = 0;
|
|
337
|
-
let reason: string = (MsgUnpacker.unpackString(a.toJSON().data.slice(4)).result);
|
|
338
|
-
this.State = -1;
|
|
339
|
-
this.emit("disconnect", reason);
|
|
340
|
-
}
|
|
341
391
|
|
|
342
|
-
}
|
|
343
|
-
if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
344
|
-
var Msg = new MsgPacker(15, true); /* entergame */
|
|
345
|
-
this.SendMsgEx(Msg, 1);
|
|
346
|
-
} else if ((unpacked.chunks[0] && chunkMessages.includes("CAPABILITIES") || unpacked.chunks[0] && chunkMessages.includes("MAP_CHANGE"))) {
|
|
347
|
-
// send ready
|
|
348
|
-
var Msg = new MsgPacker(14, true); /* ready */
|
|
349
|
-
this.SendMsgEx(Msg, 1);
|
|
350
|
-
} else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY") || unpacked.chunks[0] && chunkMessages.includes("SV_MOTD"))) {
|
|
351
|
-
var packer = new MsgPacker(20, false);
|
|
352
|
-
packer.AddString(this.name); /* name */
|
|
353
|
-
packer.AddString(""); /* clan */
|
|
354
|
-
packer.AddInt(-1); /* country */
|
|
355
|
-
packer.AddString("greyfox"); /* skin */
|
|
356
|
-
packer.AddInt(1); /* use custom color */
|
|
357
|
-
packer.AddInt(10346103); /* color body */
|
|
358
|
-
packer.AddInt(65535); /* color feet */
|
|
359
|
-
this.SendMsgEx(packer, 1);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
} else if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
363
|
-
|
|
364
|
-
if (this.State != 3) {
|
|
365
|
-
this.emit('connected');
|
|
366
392
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (
|
|
375
|
-
|
|
376
|
-
|
|
393
|
+
if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
394
|
+
var Msg = new MsgPacker(15, true); /* entergame */
|
|
395
|
+
this.SendMsgEx(Msg, 1);
|
|
396
|
+
} else if ((unpacked.chunks[0] && chunkMessages.includes("CAPABILITIES") || unpacked.chunks[0] && chunkMessages.includes("MAP_CHANGE"))) {
|
|
397
|
+
// send ready
|
|
398
|
+
var Msg = new MsgPacker(14, true); /* ready */
|
|
399
|
+
this.SendMsgEx(Msg, 1);
|
|
400
|
+
} else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY") || unpacked.chunks[0] && chunkMessages.includes("SV_MOTD"))) {
|
|
401
|
+
var info = new MsgPacker(20, false);
|
|
402
|
+
info.AddString(this.name); /* name */
|
|
403
|
+
info.AddString(""); /* clan */
|
|
404
|
+
info.AddInt(-1); /* country */
|
|
405
|
+
info.AddString("greyfox"); /* skin */
|
|
406
|
+
info.AddInt(1); /* use custom color */
|
|
407
|
+
info.AddInt(10346103); /* color body */
|
|
408
|
+
info.AddInt(65535); /* color feet */
|
|
409
|
+
this.SendMsgEx(info, 1);
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
} else if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
413
|
+
|
|
414
|
+
if (this.State != 3) {
|
|
415
|
+
this.emit('connected');
|
|
416
|
+
}
|
|
377
417
|
this.State = 3
|
|
418
|
+
} else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
|
|
419
|
+
var info = new MsgPacker(23, true);
|
|
420
|
+
this.SendMsgEx(info, 1)
|
|
378
421
|
}
|
|
422
|
+
if (chunkMessages.includes("SNAP") || chunkMessages.includes("SNAP_EMPTY") || chunkMessages.includes("SNAP_SINGLE")) {
|
|
423
|
+
this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
|
|
424
|
+
if (this.receivedSnaps >= 2) {
|
|
425
|
+
if (this.State != 3)
|
|
426
|
+
this.emit('connected')
|
|
427
|
+
this.State = 3
|
|
428
|
+
}
|
|
379
429
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
430
|
+
var chunks = unpacked.chunks.filter(a => a.msg == "SNAP" || a.msg == "SNAP_SINGLE" || a.msg == "SNAP_EMPTY");
|
|
431
|
+
if (chunks.length > 0) {
|
|
432
|
+
let part = 0;
|
|
433
|
+
let num_parts = 1;
|
|
434
|
+
chunks.forEach(chunk => {
|
|
435
|
+
let AckGameTick = (MsgUnpacker.unpackInt(chunk.raw.toJSON().data).result);
|
|
436
|
+
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining);
|
|
437
|
+
let DeltaTick = MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).result
|
|
438
|
+
if (chunk.msg == "SNAP") {
|
|
439
|
+
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // delta tick
|
|
440
|
+
num_parts = (MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).result)
|
|
441
|
+
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // num parts
|
|
442
|
+
part = (MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).result)
|
|
443
|
+
}
|
|
444
|
+
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // part
|
|
445
|
+
if (chunk.msg != "SNAP_EMPTY")
|
|
446
|
+
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // crc
|
|
396
447
|
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // crc
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
448
|
+
if (part == 0 || this.snaps.length > 30) {
|
|
449
|
+
this.snaps = [];
|
|
450
|
+
}
|
|
451
|
+
this.snaps.push(chunk.raw)
|
|
452
|
+
|
|
453
|
+
if ((num_parts - 1) == part && this.snaps.length == num_parts) {
|
|
454
|
+
let mergedSnaps = Buffer.concat(this.snaps);
|
|
455
|
+
let snapUnpacked = SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, 1)
|
|
456
|
+
|
|
457
|
+
snapUnpacked.items.forEach((a, i) => {
|
|
458
|
+
if (a.type_id == items.OBJ_CLIENT_INFO) {
|
|
459
|
+
this.client_infos[a.id] = a.parsed as ClientInfo;
|
|
460
|
+
// console.log(a.parsed, i)
|
|
461
|
+
// console.log(this.client_infos[a.id])
|
|
462
|
+
} else if (a.type_id == items.OBJ_PLAYER_INFO) {
|
|
463
|
+
this.player_infos[i] = a.parsed as PlayerInfo;
|
|
464
|
+
} else if (a.type_id == items.OBJ_EX || a.type_id > 0x4000) {
|
|
465
|
+
if (a.data.length == 5 && ((a.parsed as DdnetCharacter).freeze_end > 0 || (a.parsed as DdnetCharacter).freeze_end == -1)) {
|
|
466
|
+
// var packer = new MsgPacker(22, false)
|
|
467
|
+
// this.SendMsgEx(packer, 1)
|
|
468
|
+
}
|
|
418
469
|
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
470
|
+
})
|
|
471
|
+
}
|
|
422
472
|
|
|
423
|
-
|
|
424
|
-
|
|
473
|
+
})
|
|
474
|
+
}
|
|
425
475
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
476
|
+
}
|
|
477
|
+
if (new Date().getTime() - this.time >= 1000) {
|
|
478
|
+
this.time = new Date().getTime();
|
|
479
|
+
this.SendControlMsg(0);
|
|
480
|
+
}
|
|
481
|
+
})
|
|
432
482
|
}
|
|
433
483
|
|
|
434
484
|
Disconnect() {
|
|
435
|
-
|
|
436
|
-
this.
|
|
437
|
-
|
|
485
|
+
return new Promise((resolve) => {
|
|
486
|
+
this.SendControlMsg(4).then(() => {
|
|
487
|
+
resolve(true);
|
|
488
|
+
this.socket.close();
|
|
489
|
+
this.State = -1;
|
|
490
|
+
})
|
|
438
491
|
})
|
|
439
492
|
}
|
|
440
493
|
|