teeworlds 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/client.js +60 -20
- package/lib/client.ts +247 -200
- 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");
|
|
@@ -266,15 +267,43 @@ var Client = /** @class */ (function (_super) {
|
|
|
266
267
|
}
|
|
267
268
|
var latestBuf = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x1, header[0], header[1], this.clientAck]);
|
|
268
269
|
var latestBuf = Buffer.concat([latestBuf, Msg.buffer, this.TKEN]);
|
|
269
|
-
this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host
|
|
270
|
+
this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host);
|
|
271
|
+
};
|
|
272
|
+
Client.prototype.SendMsgExWithChunks = function (Msgs, Flags) {
|
|
273
|
+
var _this = this;
|
|
274
|
+
if (this.State == -1)
|
|
275
|
+
throw new Error("Client is not connected");
|
|
276
|
+
var header = [];
|
|
277
|
+
Msgs.forEach(function (Msg, index) {
|
|
278
|
+
header[index] = new Array(2);
|
|
279
|
+
header[index][0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
|
|
280
|
+
header[index][1] = (Msg.size & 0xf);
|
|
281
|
+
if (Flags & 1) {
|
|
282
|
+
_this.clientAck = (_this.clientAck + 1) % (1 << 10);
|
|
283
|
+
header[index][1] |= (_this.clientAck >> 2) & 0xf0;
|
|
284
|
+
header[index][2] = _this.clientAck & 0xff;
|
|
285
|
+
}
|
|
270
286
|
});
|
|
287
|
+
var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, Msgs.length]);
|
|
288
|
+
var chunks = Buffer.from([]);
|
|
289
|
+
Msgs.forEach(function (Msg, index) {
|
|
290
|
+
chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
|
|
291
|
+
});
|
|
292
|
+
var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
|
|
293
|
+
this.socket.send(packet, 0, packet.length, this.port, this.host);
|
|
271
294
|
};
|
|
272
295
|
Client.prototype.connect = function () {
|
|
273
296
|
var _this = this;
|
|
274
297
|
this.SendControlMsg(1, "TKEN");
|
|
298
|
+
var connectInterval = setInterval(function () {
|
|
299
|
+
if (_this.State == 0)
|
|
300
|
+
_this.SendControlMsg(1, "TKEN");
|
|
301
|
+
else
|
|
302
|
+
clearInterval(connectInterval);
|
|
303
|
+
}, 500);
|
|
275
304
|
this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
|
|
276
305
|
this.socket.on("message", function (a) { return __awaiter(_this, void 0, void 0, function () {
|
|
277
|
-
var unpacked, chunkMessages, chat, chat,
|
|
306
|
+
var unpacked, chunkMessages, chat, chat, info, client_version, randomUuid, reason, Msg, Msg, info, info, chunks, part_1, num_parts_1;
|
|
278
307
|
var _this = this;
|
|
279
308
|
return __generator(this, function (_a) {
|
|
280
309
|
switch (_a.label) {
|
|
@@ -330,13 +359,21 @@ var Client = /** @class */ (function (_super) {
|
|
|
330
359
|
}
|
|
331
360
|
if (a.toJSON().data[0] == 0x10) {
|
|
332
361
|
if (a.toString().includes("TKEN") || arrStartsWith(a.toJSON().data, [0x10, 0x0, 0x0, 0x0])) {
|
|
362
|
+
clearInterval(connectInterval);
|
|
333
363
|
this.TKEN = Buffer.from(a.toJSON().data.slice(a.toJSON().data.length - 4, a.toJSON().data.length));
|
|
334
364
|
this.SendControlMsg(3);
|
|
335
365
|
this.State = 2; // loading state
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
366
|
+
info = new MsgPacker_1.default(1, true);
|
|
367
|
+
info.AddString("0.6 626fce9a778df4d4");
|
|
368
|
+
info.AddString(""); // password
|
|
369
|
+
client_version = new MsgPacker_1.default(0, true);
|
|
370
|
+
client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
|
|
371
|
+
randomUuid = new Uint8Array(16);
|
|
372
|
+
crypto_1.randomBytes(16).copy(randomUuid);
|
|
373
|
+
client_version.AddBuffer(Buffer.from(randomUuid));
|
|
374
|
+
client_version.AddInt(15091);
|
|
375
|
+
client_version.AddString("DDNet 15.9.1");
|
|
376
|
+
this.SendMsgExWithChunks([client_version, info], 1);
|
|
340
377
|
}
|
|
341
378
|
else if (a.toJSON().data[3] == 0x4) {
|
|
342
379
|
// disconnected
|
|
@@ -355,15 +392,15 @@ var Client = /** @class */ (function (_super) {
|
|
|
355
392
|
this.SendMsgEx(Msg, 1);
|
|
356
393
|
}
|
|
357
394
|
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(
|
|
395
|
+
info = new MsgPacker_1.default(20, false);
|
|
396
|
+
info.AddString(this.name); /* name */
|
|
397
|
+
info.AddString(""); /* clan */
|
|
398
|
+
info.AddInt(-1); /* country */
|
|
399
|
+
info.AddString("greyfox"); /* skin */
|
|
400
|
+
info.AddInt(1); /* use custom color */
|
|
401
|
+
info.AddInt(10346103); /* color body */
|
|
402
|
+
info.AddInt(65535); /* color feet */
|
|
403
|
+
this.SendMsgEx(info, 1);
|
|
367
404
|
}
|
|
368
405
|
else if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
369
406
|
if (this.State != 3) {
|
|
@@ -372,8 +409,8 @@ var Client = /** @class */ (function (_super) {
|
|
|
372
409
|
this.State = 3;
|
|
373
410
|
}
|
|
374
411
|
else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
|
|
375
|
-
|
|
376
|
-
this.SendMsgEx(
|
|
412
|
+
info = new MsgPacker_1.default(23, true);
|
|
413
|
+
this.SendMsgEx(info, 1);
|
|
377
414
|
}
|
|
378
415
|
if (chunkMessages.includes("SNAP") || chunkMessages.includes("SNAP_EMPTY") || chunkMessages.includes("SNAP_SINGLE")) {
|
|
379
416
|
this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
|
|
@@ -439,9 +476,12 @@ var Client = /** @class */ (function (_super) {
|
|
|
439
476
|
};
|
|
440
477
|
Client.prototype.Disconnect = function () {
|
|
441
478
|
var _this = this;
|
|
442
|
-
|
|
443
|
-
_this.
|
|
444
|
-
|
|
479
|
+
return new Promise(function (resolve) {
|
|
480
|
+
_this.SendControlMsg(4).then(function () {
|
|
481
|
+
resolve(true);
|
|
482
|
+
_this.socket.close();
|
|
483
|
+
_this.State = -1;
|
|
484
|
+
});
|
|
445
485
|
});
|
|
446
486
|
};
|
|
447
487
|
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,254 @@ 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
|
-
var latestBuf = Buffer.from([0x10+(((16<<4)&0xf0)|((this.ack>>8)&0xf)), this.ack&0xff, 0x00, msg])
|
|
245
|
+
|
|
246
|
+
var latestBuf = Buffer.from([0x10 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x00, msg])
|
|
245
247
|
latestBuf = Buffer.concat([latestBuf, Buffer.from(ExtraMsg), this.TKEN]) // move header (latestBuf), optional extraMsg & TKEN into 1 buffer
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
setTimeout(() => { resolve("failed, rip") }, 2000)
|
|
248
|
+
this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host, (err, bytes) => {
|
|
249
|
+
resolve(bytes)
|
|
250
|
+
})
|
|
251
|
+
setTimeout(() => { resolve("failed, rip") }, 2000)
|
|
250
252
|
/* after 2 seconds it was probably not able to send,
|
|
251
253
|
so when sending a quit message the user doesnt
|
|
252
254
|
stay stuck not being able to ctrl + c
|
|
253
255
|
*/
|
|
254
|
-
|
|
256
|
+
})
|
|
255
257
|
}
|
|
256
258
|
SendMsgEx(Msg: MsgPacker, Flags: number) {
|
|
257
259
|
if (this.State == -1)
|
|
258
260
|
throw new Error("Client is not connected");
|
|
259
|
-
var header = []
|
|
260
|
-
header[0] = ((Flags&3)<<6)|((Msg.size>>4)&0x3f);
|
|
261
|
-
header[1] = (Msg.size&0xf);
|
|
262
|
-
if(Flags&1) {
|
|
263
|
-
this.clientAck = (this.clientAck+1)%(1<<10);
|
|
264
|
-
header[1] |= (this.clientAck>>2)&0xf0;
|
|
265
|
-
header[2] = this.clientAck&0xff;
|
|
266
|
-
}
|
|
267
|
-
var latestBuf = Buffer.from([0x0+(((16<<4)&0xf0)|((this.ack>>8)&0xf)), this.ack&0xff, 0x1, header[0], header[1], this.clientAck]);
|
|
261
|
+
var header = []
|
|
262
|
+
header[0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
|
|
263
|
+
header[1] = (Msg.size & 0xf);
|
|
264
|
+
if (Flags & 1) {
|
|
265
|
+
this.clientAck = (this.clientAck + 1) % (1 << 10);
|
|
266
|
+
header[1] |= (this.clientAck >> 2) & 0xf0;
|
|
267
|
+
header[2] = this.clientAck & 0xff;
|
|
268
|
+
}
|
|
269
|
+
var latestBuf = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, 0x1, header[0], header[1], this.clientAck]);
|
|
268
270
|
var latestBuf = Buffer.concat([latestBuf, Msg.buffer, this.TKEN]);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
+
|
|
272
|
+
this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host)
|
|
273
|
+
}
|
|
274
|
+
SendMsgExWithChunks(Msgs: MsgPacker[], Flags: number) {
|
|
275
|
+
if (this.State == -1)
|
|
276
|
+
throw new Error("Client is not connected");
|
|
277
|
+
var header: any[][] = [];
|
|
278
|
+
|
|
279
|
+
Msgs.forEach((Msg: MsgPacker, index) => {
|
|
280
|
+
header[index] = new Array(2);
|
|
281
|
+
header[index][0] = ((Flags & 3) << 6) | ((Msg.size >> 4) & 0x3f);
|
|
282
|
+
header[index][1] = (Msg.size & 0xf);
|
|
283
|
+
if (Flags & 1) {
|
|
284
|
+
this.clientAck = (this.clientAck + 1) % (1 << 10);
|
|
285
|
+
header[index][1] |= (this.clientAck >> 2) & 0xf0;
|
|
286
|
+
header[index][2] = this.clientAck & 0xff;
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
var packetHeader = Buffer.from([0x0 + (((16 << 4) & 0xf0) | ((this.ack >> 8) & 0xf)), this.ack & 0xff, Msgs.length]);
|
|
290
|
+
var chunks = Buffer.from([]);
|
|
291
|
+
Msgs.forEach((Msg: MsgPacker, index) => {
|
|
292
|
+
chunks = Buffer.concat([chunks, Buffer.from(header[index]), Msg.buffer]);
|
|
293
|
+
})
|
|
294
|
+
var packet = Buffer.concat([(packetHeader), chunks, this.TKEN]);
|
|
295
|
+
|
|
296
|
+
this.socket.send(packet, 0, packet.length, this.port, this.host)
|
|
271
297
|
}
|
|
272
298
|
connect() {
|
|
273
299
|
this.SendControlMsg(1, "TKEN")
|
|
300
|
+
let connectInterval = setInterval(() => {
|
|
301
|
+
if (this.State == 0)
|
|
302
|
+
this.SendControlMsg(1, "TKEN")
|
|
303
|
+
else
|
|
304
|
+
clearInterval(connectInterval)
|
|
305
|
+
}, 500)
|
|
274
306
|
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
|
-
|
|
307
|
+
this.socket.on("message", async (a) => {
|
|
308
|
+
var unpacked: _packet = await this.Unpack(a)
|
|
309
|
+
if (unpacked.twprotocol.flags != 128 && unpacked.twprotocol.ack) {
|
|
310
|
+
unpacked.chunks.forEach(a => {
|
|
311
|
+
if (a.msg && !a.msg.startsWith("SNAP")) {
|
|
312
|
+
if (a.seq != undefined && a.seq != -1)
|
|
313
|
+
this.ack = a.seq
|
|
314
|
+
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
var chunkMessages = unpacked.chunks.map(a => a.msg)
|
|
319
|
+
if (chunkMessages.includes("SV_CHAT")) {
|
|
320
|
+
var chat = unpacked.chunks.filter(a => a.msg == "SV_CHAT");
|
|
321
|
+
chat.forEach(a => {
|
|
322
|
+
if (a.msg == "SV_CHAT") {
|
|
323
|
+
var unpacked: iMessage = {} as iMessage;
|
|
324
|
+
unpacked.team = MsgUnpacker.unpackInt(a.raw.toJSON().data).result;
|
|
325
|
+
var remaining: number[] = MsgUnpacker.unpackInt(a.raw.toJSON().data).remaining;
|
|
326
|
+
unpacked.client_id = MsgUnpacker.unpackInt(remaining).result;
|
|
327
|
+
remaining = MsgUnpacker.unpackInt(remaining).remaining;
|
|
328
|
+
unpacked.message = MsgUnpacker.unpackString(remaining).result;
|
|
329
|
+
if (unpacked.client_id != -1)
|
|
330
|
+
unpacked.author = { ClientInfo: this.client_infos[unpacked.client_id], PlayerInfo: this.player_infos[unpacked.client_id] }
|
|
331
|
+
// console.log(unpacked)
|
|
332
|
+
this.emit("message", unpacked)
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
if (chunkMessages.includes("SV_KILL_MSG")) {
|
|
337
|
+
var chat = unpacked.chunks.filter(a => a.msg == "SV_KILL_MSG");
|
|
338
|
+
chat.forEach(a => {
|
|
339
|
+
if (a.msg == "SV_KILL_MSG") {
|
|
340
|
+
var unpacked: iKillMsg = {} as iKillMsg;
|
|
341
|
+
unpacked.killer_id = MsgUnpacker.unpackInt(a.raw.toJSON().data).result;
|
|
342
|
+
var remaining: number[] = MsgUnpacker.unpackInt(a.raw.toJSON().data).remaining;
|
|
343
|
+
unpacked.victim_id = MsgUnpacker.unpackInt(remaining).result;
|
|
344
|
+
remaining = MsgUnpacker.unpackInt(remaining).remaining;
|
|
345
|
+
unpacked.weapon = MsgUnpacker.unpackInt(remaining).result;
|
|
346
|
+
remaining = MsgUnpacker.unpackInt(remaining).remaining;
|
|
347
|
+
unpacked.special_mode = MsgUnpacker.unpackInt(remaining).result;
|
|
348
|
+
if (unpacked.victim_id != -1)
|
|
349
|
+
unpacked.victim = { ClientInfo: this.client_infos[unpacked.victim_id], PlayerInfo: this.player_infos[unpacked.victim_id] }
|
|
350
|
+
if (unpacked.killer_id != -1)
|
|
351
|
+
unpacked.killer = { ClientInfo: this.client_infos[unpacked.killer_id], PlayerInfo: this.player_infos[unpacked.killer_id] }
|
|
352
|
+
// console.log(unpacked)
|
|
353
|
+
this.emit("kill", unpacked)
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
if (a.toJSON().data[0] == 0x10) {
|
|
358
|
+
if (a.toString().includes("TKEN") || arrStartsWith(a.toJSON().data, [0x10, 0x0, 0x0, 0x0])) {
|
|
359
|
+
clearInterval(connectInterval);
|
|
360
|
+
this.TKEN = Buffer.from(a.toJSON().data.slice(a.toJSON().data.length - 4, a.toJSON().data.length))
|
|
361
|
+
this.SendControlMsg(3);
|
|
362
|
+
this.State = 2; // loading state
|
|
363
|
+
var info = new MsgPacker(1, true);
|
|
364
|
+
info.AddString("0.6 626fce9a778df4d4");
|
|
365
|
+
info.AddString(""); // password
|
|
366
|
+
|
|
367
|
+
var client_version = new MsgPacker(0, true);
|
|
368
|
+
client_version.AddBuffer(Buffer.from("8c00130484613e478787f672b3835bd4", 'hex'));
|
|
369
|
+
let randomUuid = new Uint8Array(16);
|
|
370
|
+
|
|
371
|
+
randomBytes(16).copy(randomUuid);
|
|
372
|
+
|
|
373
|
+
client_version.AddBuffer(Buffer.from(randomUuid));
|
|
374
|
+
client_version.AddInt(15091);
|
|
375
|
+
client_version.AddString("DDNet 15.9.1");
|
|
376
|
+
|
|
377
|
+
this.SendMsgExWithChunks([client_version, info], 1)
|
|
378
|
+
} else if (a.toJSON().data[3] == 0x4) {
|
|
379
|
+
// disconnected
|
|
380
|
+
this.State = 0;
|
|
381
|
+
let reason: string = (MsgUnpacker.unpackString(a.toJSON().data.slice(4)).result);
|
|
382
|
+
this.State = -1;
|
|
383
|
+
this.emit("disconnect", reason);
|
|
322
384
|
}
|
|
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
385
|
|
|
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
386
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (
|
|
375
|
-
|
|
376
|
-
|
|
387
|
+
if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
388
|
+
var Msg = new MsgPacker(15, true); /* entergame */
|
|
389
|
+
this.SendMsgEx(Msg, 1);
|
|
390
|
+
} else if ((unpacked.chunks[0] && chunkMessages.includes("CAPABILITIES") || unpacked.chunks[0] && chunkMessages.includes("MAP_CHANGE"))) {
|
|
391
|
+
// send ready
|
|
392
|
+
var Msg = new MsgPacker(14, true); /* ready */
|
|
393
|
+
this.SendMsgEx(Msg, 1);
|
|
394
|
+
} else if ((unpacked.chunks[0] && chunkMessages.includes("CON_READY") || unpacked.chunks[0] && chunkMessages.includes("SV_MOTD"))) {
|
|
395
|
+
var info = new MsgPacker(20, false);
|
|
396
|
+
info.AddString(this.name); /* name */
|
|
397
|
+
info.AddString(""); /* clan */
|
|
398
|
+
info.AddInt(-1); /* country */
|
|
399
|
+
info.AddString("greyfox"); /* skin */
|
|
400
|
+
info.AddInt(1); /* use custom color */
|
|
401
|
+
info.AddInt(10346103); /* color body */
|
|
402
|
+
info.AddInt(65535); /* color feet */
|
|
403
|
+
this.SendMsgEx(info, 1);
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
} else if (unpacked.chunks[0] && chunkMessages.includes("SV_READY_TO_ENTER")) {
|
|
407
|
+
|
|
408
|
+
if (this.State != 3) {
|
|
409
|
+
this.emit('connected');
|
|
410
|
+
}
|
|
377
411
|
this.State = 3
|
|
412
|
+
} else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
|
|
413
|
+
var info = new MsgPacker(23, true);
|
|
414
|
+
this.SendMsgEx(info, 1)
|
|
378
415
|
}
|
|
416
|
+
if (chunkMessages.includes("SNAP") || chunkMessages.includes("SNAP_EMPTY") || chunkMessages.includes("SNAP_SINGLE")) {
|
|
417
|
+
this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
|
|
418
|
+
if (this.receivedSnaps >= 2) {
|
|
419
|
+
if (this.State != 3)
|
|
420
|
+
this.emit('connected')
|
|
421
|
+
this.State = 3
|
|
422
|
+
}
|
|
379
423
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
424
|
+
var chunks = unpacked.chunks.filter(a => a.msg == "SNAP" || a.msg == "SNAP_SINGLE" || a.msg == "SNAP_EMPTY");
|
|
425
|
+
if (chunks.length > 0) {
|
|
426
|
+
let part = 0;
|
|
427
|
+
let num_parts = 1;
|
|
428
|
+
chunks.forEach(chunk => {
|
|
429
|
+
let AckGameTick = (MsgUnpacker.unpackInt(chunk.raw.toJSON().data).result);
|
|
430
|
+
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining);
|
|
431
|
+
let DeltaTick = MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).result
|
|
432
|
+
if (chunk.msg == "SNAP") {
|
|
433
|
+
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // delta tick
|
|
434
|
+
num_parts = (MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).result)
|
|
435
|
+
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // num parts
|
|
436
|
+
part = (MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).result)
|
|
437
|
+
}
|
|
438
|
+
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // part
|
|
439
|
+
if (chunk.msg != "SNAP_EMPTY")
|
|
440
|
+
chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // crc
|
|
396
441
|
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
|
-
|
|
442
|
+
if (part == 0 || this.snaps.length > 30) {
|
|
443
|
+
this.snaps = [];
|
|
444
|
+
}
|
|
445
|
+
this.snaps.push(chunk.raw)
|
|
446
|
+
|
|
447
|
+
if ((num_parts - 1) == part && this.snaps.length == num_parts) {
|
|
448
|
+
let mergedSnaps = Buffer.concat(this.snaps);
|
|
449
|
+
let snapUnpacked = SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, 1)
|
|
450
|
+
|
|
451
|
+
snapUnpacked.items.forEach((a, i) => {
|
|
452
|
+
if (a.type_id == items.OBJ_CLIENT_INFO) {
|
|
453
|
+
this.client_infos[a.id] = a.parsed as ClientInfo;
|
|
454
|
+
// console.log(a.parsed, i)
|
|
455
|
+
// console.log(this.client_infos[a.id])
|
|
456
|
+
} else if (a.type_id == items.OBJ_PLAYER_INFO) {
|
|
457
|
+
this.player_infos[i] = a.parsed as PlayerInfo;
|
|
458
|
+
} else if (a.type_id == items.OBJ_EX || a.type_id > 0x4000) {
|
|
459
|
+
if (a.data.length == 5 && ((a.parsed as DdnetCharacter).freeze_end > 0 || (a.parsed as DdnetCharacter).freeze_end == -1)) {
|
|
460
|
+
// var packer = new MsgPacker(22, false)
|
|
461
|
+
// this.SendMsgEx(packer, 1)
|
|
462
|
+
}
|
|
418
463
|
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
464
|
+
})
|
|
465
|
+
}
|
|
422
466
|
|
|
423
|
-
|
|
424
|
-
|
|
467
|
+
})
|
|
468
|
+
}
|
|
425
469
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
470
|
+
}
|
|
471
|
+
if (new Date().getTime() - this.time >= 1000) {
|
|
472
|
+
this.time = new Date().getTime();
|
|
473
|
+
this.SendControlMsg(0);
|
|
474
|
+
}
|
|
475
|
+
})
|
|
432
476
|
}
|
|
433
477
|
|
|
434
478
|
Disconnect() {
|
|
435
|
-
|
|
436
|
-
this.
|
|
437
|
-
|
|
479
|
+
return new Promise((resolve) => {
|
|
480
|
+
this.SendControlMsg(4).then(() => {
|
|
481
|
+
resolve(true);
|
|
482
|
+
this.socket.close();
|
|
483
|
+
this.State = -1;
|
|
484
|
+
})
|
|
438
485
|
})
|
|
439
486
|
}
|
|
440
487
|
|