teeworlds 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.ts ADDED
@@ -0,0 +1,480 @@
1
+ import net from 'dgram';
2
+ import fs from 'fs';
3
+ import { EventEmitter } from 'stream';
4
+ import { spawn } from 'child_process';
5
+ import MsgUnpacker from './MsgUnpacker';
6
+ import MsgPacker from './MsgPacker';
7
+ import { Snapshot } from './snapshot';
8
+
9
+ const SnapUnpacker = new Snapshot();
10
+
11
+ enum NETMSGTYPE {
12
+ EX,
13
+ SV_MOTD,
14
+ SV_BROADCAST,
15
+ SV_CHAT,
16
+ SV_KILLMSG,
17
+ SV_SOUNDGLOBAL,
18
+ SV_TUNEPARAMS,
19
+ SV_EXTRAPROJECTILE,
20
+ SV_READYTOENTER,
21
+ SV_WEAPONPICKUP,
22
+ SV_EMOTICON,
23
+ SV_VOTECLEAROPTIONS,
24
+ SV_VOTEOPTIONLISTADD,
25
+ SV_VOTEOPTIONADD,
26
+ SV_VOTEOPTIONREMOVE,
27
+ SV_VOTESET,
28
+ SV_VOTESTATUS,
29
+ CL_SAY,
30
+ CL_SETTEAM,
31
+ CL_SETSPECTATORMODE,
32
+ CL_STARTINFO,
33
+ CL_CHANGEINFO,
34
+ CL_KILL,
35
+ CL_EMOTICON,
36
+ CL_VOTE,
37
+ CL_CALLVOTE,
38
+ CL_ISDDNETLEGACY,
39
+ SV_DDRACETIMELEGACY,
40
+ SV_RECORDLEGACY,
41
+ UNUSED,
42
+ SV_TEAMSSTATELEGACY,
43
+ CL_SHOWOTHERSLEGACY,
44
+ NUM
45
+ }
46
+
47
+ enum items {
48
+ OBJ_EX,
49
+ OBJ_PLAYER_INPUT,
50
+ OBJ_PROJECTILE,
51
+ OBJ_LASER,
52
+ OBJ_PICKUP,
53
+ OBJ_FLAG,
54
+ OBJ_GAME_INFO,
55
+ OBJ_GAME_DATA,
56
+ OBJ_CHARACTER_CORE,
57
+ OBJ_CHARACTER,
58
+ OBJ_PLAYER_INFO,
59
+ OBJ_CLIENT_INFO,
60
+ OBJ_SPECTATOR_INFO,
61
+ EVENT_COMMON,
62
+ EVENT_EXPLOSION,
63
+ EVENT_SPAWN,
64
+ EVENT_HAMMERHIT,
65
+ EVENT_DEATH,
66
+ EVENT_SOUND_GLOBAL,
67
+ EVENT_SOUND_WORLD,
68
+ EVENT_DAMAGE_INDICATOR
69
+ }
70
+ interface chunk {
71
+ bytes: number,
72
+ flags: number,
73
+ sequence?: number,
74
+ seq?: number,
75
+ type: 'sys' | 'game',
76
+ msgid: number,
77
+ msg: string,
78
+ raw: Buffer,
79
+ extended_msgid?: Buffer;
80
+ }
81
+ function toHexStream(buff: Buffer): string {
82
+ return buff.toJSON().data.map(a => ('0' + (a & 0xff).toString(16)).slice(-2)).join("");
83
+ }
84
+ async function decompress(buff: Buffer): Promise<Buffer> {
85
+ return new Promise((resolve) => {
86
+ // get hex stream
87
+ var hexStream = toHexStream(buff)
88
+ var ls = spawn('python', [__dirname + '\\huffman.py', hexStream, "-decompress"])
89
+ ls.stdout.on('data', (data) => {
90
+ resolve(Buffer.from(eval(data.toString()))); // convert stdout array to actual array, then convert the array to Buffer & return it
91
+ });
92
+ setTimeout(() => {
93
+ ls.stdin.end();
94
+ ls.stdout.destroy();
95
+ ls.stderr.destroy();
96
+
97
+ resolve(Buffer.from([]));
98
+ }, 750)
99
+
100
+ })
101
+ }
102
+ interface _packet {
103
+ twprotocol: { flags: number, ack: number, chunkAmount: number, size: number },
104
+ chunks: chunk[]
105
+ }
106
+
107
+ 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"],
109
+ ["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
+ ]
111
+
112
+ var messageUUIDs = {
113
+ "WHAT_IS": Buffer.from([0x24, 0x5e, 0x50, 0x97, 0x9f, 0xe0, 0x39, 0xd6, 0xbf, 0x7d, 0x9a, 0x29, 0xe1, 0x69, 0x1e, 0x4c]),
114
+ "IT_IS": Buffer.from([0x69, 0x54, 0x84, 0x7e, 0x2e, 0x87, 0x36, 0x03, 0xb5, 0x62, 0x36, 0xda, 0x29, 0xed, 0x1a, 0xca]),
115
+ "I_DONT_KNOW": Buffer.from([0x41, 0x69, 0x11, 0xb5, 0x79, 0x73, 0x33, 0xbf, 0x8d, 0x52, 0x7b, 0xf0, 0x1e, 0x51, 0x9c, 0xf0]),
116
+ "RCON_TYPE": Buffer.from([0x12, 0x81, 0x0e, 0x1f, 0xa1, 0xdb, 0x33, 0x78, 0xb4, 0xfb, 0x16, 0x4e, 0xd6, 0x50, 0x59, 0x26]),
117
+ "MAP_DETAILS": Buffer.from([0xf9, 0x11, 0x7b, 0x3c, 0x80, 0x39, 0x34, 0x16, 0x9f, 0xc0, 0xae, 0xf2, 0xbc, 0xb7, 0x5c, 0x03]),
118
+ "CLIENT_VERSION": Buffer.from([0x8c, 0x00, 0x13, 0x04, 0x84, 0x61, 0x3e, 0x47, 0x87, 0x87, 0xf6, 0x72, 0xb3, 0x83, 0x5b, 0xd4]),
119
+ "CAPABILITIES": Buffer.from([0xf6, 0x21, 0xa5, 0xa1, 0xf5, 0x85, 0x37, 0x75, 0x8e, 0x73, 0x41, 0xbe, 0xee, 0x79, 0xf2, 0xb2]),
120
+ }
121
+
122
+ function arrStartsWith(arr: number[], arrStart: number[], start=0) {
123
+ arr.splice(0, start)
124
+ for (let i = 0; i < arrStart.length; i++) {
125
+ if (arr[i] == arrStart[i])
126
+ continue;
127
+ else return false;
128
+ }
129
+ return true;
130
+ }
131
+ interface iMessage {
132
+ team: number,
133
+ client_id: number,
134
+ author?: {ClientInfo: ClientInfo, PlayerInfo: PlayerInfo},
135
+ message: string
136
+ }
137
+
138
+ 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,
144
+ special_mode: number
145
+ }
146
+
147
+ declare interface Client {
148
+ host: string;
149
+ port: number;
150
+ name: string;
151
+ State: number; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
152
+ ack: number;
153
+ clientAck: number;
154
+ receivedSnaps: number; /* wait for 2 ss before seeing self as connected */
155
+ lastMsg: string;
156
+ _port: number;
157
+ socket: net.Socket;
158
+ TKEN: Buffer;
159
+ time: number;
160
+
161
+ snaps: Buffer[];
162
+ client_infos: ClientInfo[];
163
+ player_infos: PlayerInfo[];
164
+
165
+
166
+ on(event: 'connected'): this;
167
+ on(event: 'disconnect', reason: string): this;
168
+
169
+ on(event: 'message', message: iMessage, client_info: ClientInfo, player_info: PlayerInfo): this;
170
+ on(event: 'kill', kill: {killer_id: number, victim_id: number, weapon: number, special_mode: number}, client_info: ClientInfo, player_info: PlayerInfo): this;
171
+
172
+ }
173
+ class Client extends EventEmitter {
174
+
175
+
176
+
177
+ constructor(ip: string, port: number, nickname: string) {
178
+ super();
179
+ this.host = ip;
180
+ this.port = port;
181
+ this.name = nickname;
182
+
183
+ this.snaps = [];
184
+ this.client_infos = [];
185
+ this.player_infos = [];
186
+
187
+ this.State = 0; // 0 = offline; 1 = STATE_CONNECTING = 1, STATE_LOADING = 2, STATE_ONLINE = 3
188
+ this.ack = 0; // ack of messages the client has received
189
+ this.clientAck = 0; // ack of messages the client has sent
190
+ this.receivedSnaps = 0; /* wait for 2 snaps before seeing self as connected */
191
+ this.lastMsg = "";
192
+ this._port = Math.floor(Math.random()*65535)
193
+ this.socket = net.createSocket("udp4")
194
+ this.socket.bind();
195
+
196
+ 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)
198
+ this.State = 0;
199
+ }
200
+ 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)
205
+ return unpacked;
206
+ packet = packet.slice(3)
207
+ if (unpacked.twprotocol.flags & 128) {
208
+ packet = await decompress(packet)
209
+ if (packet.length == 1 && packet[0] == -1)
210
+ return unpacked
211
+ }
212
+ // return unpacked;
213
+ for (let i = 0; i < unpacked.twprotocol.chunkAmount; i++) {
214
+ var chunk: chunk = {} as chunk;
215
+ chunk.bytes = ((packet[0] & 0x3f) << 4) | (packet[1] & ((1 << 4) - 1)); // idk what this shit is but it works
216
+ chunk.flags = (packet[0] >> 6) & 3;
217
+ chunk.sequence = -1;
218
+
219
+ if (chunk.flags & 1) {
220
+ chunk.seq = ((packet[1]&0xf0)<<2) | packet[2];
221
+ packet = packet.slice(3) // remove flags & size
222
+ } else
223
+ packet = packet.slice(2)
224
+ 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.raw = packet.slice(1, chunk.bytes)
228
+ Object.values(messageUUIDs).forEach((a, i) => {
229
+ if (a.compare(packet.slice(0, 16)) == 0) {
230
+ chunk.extended_msgid = a;
231
+ // chunk.type = 'sys';
232
+ chunk.msg = Object.keys(messageUUIDs)[i];
233
+ }
234
+ })
235
+
236
+ packet = packet.slice(chunk.bytes) // +1 cuz it adds an extra \x00 for easier parsing i guess
237
+ unpacked.chunks.push(chunk)
238
+ }
239
+ return unpacked
240
+ }
241
+ SendControlMsg(msg: number, ExtraMsg: string = "") {
242
+ 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
+ latestBuf = Buffer.concat([latestBuf, Buffer.from(ExtraMsg), this.TKEN]) // move header (latestBuf), optional extraMsg & TKEN into 1 buffer
246
+ this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host, (err, bytes) => {
247
+ resolve(bytes)
248
+ })
249
+ setTimeout(() => { resolve("failed, rip") }, 2000)
250
+ /* after 2 seconds it was probably not able to send,
251
+ so when sending a quit message the user doesnt
252
+ stay stuck not being able to ctrl + c
253
+ */
254
+ })
255
+ }
256
+ SendMsgEx(Msg: MsgPacker, Flags: number) {
257
+ if (this.State == -1)
258
+ 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]);
268
+ var latestBuf = Buffer.concat([latestBuf, Msg.buffer, this.TKEN]);
269
+ this.socket.send(latestBuf, 0, latestBuf.length, this.port, this.host, (err, bytes) => {
270
+ })
271
+ }
272
+ connect() {
273
+ this.SendControlMsg(1, "TKEN")
274
+ this.time = new Date().getTime() + 2000; // start sending keepalives after 2s
275
+ this.socket.on("message", async (a) => {
276
+ var unpacked: _packet = await this.Unpack(a)
277
+ if (unpacked.twprotocol.flags != 128 && unpacked.twprotocol.ack) {
278
+ unpacked.chunks.forEach(a => {
279
+ if (a.msg && !a.msg.startsWith("SNAP")) {
280
+ if (a.seq != undefined && a.seq != -1)
281
+ this.ack = a.seq
282
+
283
+ }
284
+ })
285
+ }
286
+ var chunkMessages = unpacked.chunks.map(a => a.msg)
287
+ if (chunkMessages.includes("SV_CHAT")) {
288
+ var chat = unpacked.chunks.filter(a => a.msg == "SV_CHAT");
289
+ chat.forEach(a => {
290
+ if (a.msg == "SV_CHAT") {
291
+ var unpacked: iMessage = {} as iMessage;
292
+ unpacked.team = MsgUnpacker.unpackInt(a.raw.toJSON().data).result;
293
+ var remaining: number[] = MsgUnpacker.unpackInt(a.raw.toJSON().data).remaining;
294
+ unpacked.client_id = MsgUnpacker.unpackInt(remaining).result;
295
+ remaining = MsgUnpacker.unpackInt(remaining).remaining;
296
+ unpacked.message = MsgUnpacker.unpackString(remaining).result;
297
+ if (unpacked.client_id != -1)
298
+ unpacked.author = {ClientInfo: this.client_infos[unpacked.client_id], PlayerInfo: this.player_infos[unpacked.client_id]}
299
+ // console.log(unpacked)
300
+ this.emit("message", unpacked)
301
+ }
302
+ })
303
+ }
304
+ if (chunkMessages.includes("SV_KILL_MSG")) {
305
+ var chat = unpacked.chunks.filter(a => a.msg == "SV_KILL_MSG");
306
+ chat.forEach(a => {
307
+ if (a.msg == "SV_KILL_MSG") {
308
+ var unpacked: iKillMsg = {} as iKillMsg;
309
+ unpacked.killer_id = MsgUnpacker.unpackInt(a.raw.toJSON().data).result;
310
+ var remaining: number[] = MsgUnpacker.unpackInt(a.raw.toJSON().data).remaining;
311
+ unpacked.victim_id = MsgUnpacker.unpackInt(remaining).result;
312
+ remaining = MsgUnpacker.unpackInt(remaining).remaining;
313
+ unpacked.weapon = MsgUnpacker.unpackInt(remaining).result;
314
+ remaining = MsgUnpacker.unpackInt(remaining).remaining;
315
+ unpacked.special_mode = MsgUnpacker.unpackInt(remaining).result;
316
+ if (unpacked.victim_id != -1)
317
+ unpacked.victim = {ClientInfo: this.client_infos[unpacked.victim_id], PlayerInfo: this.player_infos[unpacked.victim_id]}
318
+ if (unpacked.killer_id != -1)
319
+ unpacked.killer = {ClientInfo: this.client_infos[unpacked.killer_id], PlayerInfo: this.player_infos[unpacked.killer_id]}
320
+ // console.log(unpacked)
321
+ this.emit("kill", unpacked)
322
+ }
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
+
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
+ }
367
+ this.State = 3
368
+ } else if (unpacked.chunks[0] && chunkMessages.includes("PING")) {
369
+ var packer = new MsgPacker(23, true);
370
+ this.SendMsgEx(packer, 1)
371
+ }
372
+ if (chunkMessages.includes("SNAP") || chunkMessages.includes("SNAP_EMPTY") || chunkMessages.includes("SNAP_SINGLE")) {
373
+ this.receivedSnaps++; /* wait for 2 ss before seeing self as connected */
374
+ if (this.receivedSnaps >= 2) {
375
+ if (this.State != 3)
376
+ this.emit('connected')
377
+ this.State = 3
378
+ }
379
+
380
+ var chunks = unpacked.chunks.filter(a => a.msg == "SNAP" || a.msg == "SNAP_SINGLE" || a.msg == "SNAP_EMPTY");
381
+ if (chunks.length > 0) {
382
+ let part = 0;
383
+ let num_parts = 1;
384
+ chunks.forEach(chunk => {
385
+ let AckGameTick = (MsgUnpacker.unpackInt(chunk.raw.toJSON().data).result);
386
+ chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining);
387
+ let DeltaTick = MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).result
388
+ if (chunk.msg == "SNAP") {
389
+ chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // delta tick
390
+ num_parts = (MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).result)
391
+ chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // num parts
392
+ part = (MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).result)
393
+ }
394
+ chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // part
395
+ if (chunk.msg != "SNAP_EMPTY")
396
+ chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // crc
397
+ chunk.raw = Buffer.from(MsgUnpacker.unpackInt(chunk?.raw?.toJSON().data).remaining); // crc
398
+ if (part == 0 || this.snaps.length > 30) {
399
+ this.snaps = [];
400
+ }
401
+ this.snaps.push(chunk.raw)
402
+
403
+ if ((num_parts-1) == part && this.snaps.length == num_parts) {
404
+ let mergedSnaps = Buffer.concat(this.snaps);
405
+ let snapUnpacked = SnapUnpacker.unpackSnapshot(mergedSnaps.toJSON().data, 1)
406
+
407
+ snapUnpacked.items.forEach((a, i) => {
408
+ if (a.type_id == items.OBJ_CLIENT_INFO) {
409
+ this.client_infos[a.id] = a.parsed as ClientInfo;
410
+ // console.log(a.parsed, i)
411
+ // console.log(this.client_infos[a.id])
412
+ } else if (a.type_id == items.OBJ_PLAYER_INFO) {
413
+ this.player_infos[i] = a.parsed as PlayerInfo;
414
+ } else if (a.type_id == items.OBJ_EX || a.type_id > 0x4000) {
415
+ if (a.data.length == 5 && ((a.parsed as DdnetCharacter).freeze_end > 0 || (a.parsed as DdnetCharacter).freeze_end == -1)) {
416
+ // var packer = new MsgPacker(22, false)
417
+ // this.SendMsgEx(packer, 1)
418
+ }
419
+ }
420
+ })
421
+ }
422
+
423
+ })
424
+ }
425
+
426
+ }
427
+ if (new Date().getTime() - this.time >= 1000) {
428
+ this.time = new Date().getTime();
429
+ this.SendControlMsg(0);
430
+ }
431
+ })
432
+ }
433
+
434
+ Disconnect() {
435
+ this.SendControlMsg(4).then(() => {
436
+ this.State = -1;
437
+ this.socket.disconnect();
438
+ })
439
+ }
440
+
441
+ Say(message: string, team = false) {
442
+ var packer = new MsgPacker(NETMSGTYPE.CL_SAY, false);
443
+ packer.AddInt(team ? 1 : 0); // team
444
+ packer.AddString(message);
445
+ this.SendMsgEx(packer, 1);
446
+ }
447
+ Vote(vote: boolean) {
448
+ var packer = new MsgPacker(NETMSGTYPE.CL_VOTE, false);
449
+ packer.AddInt(vote ? 1 : 0);
450
+ this.SendMsgEx(packer, 1);
451
+ }
452
+ ChangePlayerInfo(playerInfo: ClientInfo) {
453
+ var packer = new MsgPacker(NETMSGTYPE.CL_CHANGEINFO, false);
454
+ packer.AddString(playerInfo.name); //m_pName);
455
+ packer.AddString(playerInfo.clan); //m_pClan);
456
+ packer.AddInt(playerInfo.country); //m_Country);
457
+ packer.AddString(playerInfo.skin); //m_pSkin);
458
+ packer.AddInt(playerInfo.use_custom_color ? 1 : 0); //m_UseCustomColor);
459
+ packer.AddInt(playerInfo.color_body); //m_ColorBody);
460
+ packer.AddInt(playerInfo.color_feet); //m_ColorFeet);
461
+ this.SendMsgEx(packer, 1);
462
+ }
463
+ Kill() {
464
+ var packer = new MsgPacker(NETMSGTYPE.CL_KILL, false);
465
+ this.SendMsgEx(packer, 1);
466
+ }
467
+ ChangeTeam(team: number) {
468
+ var packer = new MsgPacker(NETMSGTYPE.CL_SETTEAM, false);
469
+ packer.AddInt(team);
470
+ this.SendMsgEx(packer, 1);
471
+ }
472
+ Emote(emote: number) {
473
+ var packer = new MsgPacker(NETMSGTYPE.CL_EMOTICON, false);
474
+ packer.AddInt(emote);
475
+ this.SendMsgEx(packer, 1);
476
+ }
477
+
478
+ }
479
+ export = Client;
480
+ // module.exports = Client;
package/lib/huffman.py ADDED
@@ -0,0 +1,224 @@
1
+ # from Ryozuki, found on ddnet discord ( 11.10.2018 )
2
+ from typing import List
3
+ from sys import argv
4
+ FREQ_TABLE = [
5
+ 1 << 30, 4545, 2657, 431, 1950, 919, 444, 482, 2244, 617, 838, 542, 715, 1814, 304, 240, 754, 212, 647, 186,
6
+ 283, 131, 146, 166, 543, 164, 167, 136, 179, 859, 363, 113, 157, 154, 204, 108, 137, 180, 202, 176,
7
+ 872, 404, 168, 134, 151, 111, 113, 109, 120, 126, 129, 100, 41, 20, 16, 22, 18, 18, 17, 19,
8
+ 16, 37, 13, 21, 362, 166, 99, 78, 95, 88, 81, 70, 83, 284, 91, 187, 77, 68, 52, 68,
9
+ 59, 66, 61, 638, 71, 157, 50, 46, 69, 43, 11, 24, 13, 19, 10, 12, 12, 20, 14, 9,
10
+ 20, 20, 10, 10, 15, 15, 12, 12, 7, 19, 15, 14, 13, 18, 35, 19, 17, 14, 8, 5,
11
+ 15, 17, 9, 15, 14, 18, 8, 10, 2173, 134, 157, 68, 188, 60, 170, 60, 194, 62, 175, 71,
12
+ 148, 67, 167, 78, 211, 67, 156, 69, 1674, 90, 174, 53, 147, 89, 181, 51, 174, 63, 163, 80,
13
+ 167, 94, 128, 122, 223, 153, 218, 77, 200, 110, 190, 73, 174, 69, 145, 66, 277, 143, 141, 60,
14
+ 136, 53, 180, 57, 142, 57, 158, 61, 166, 112, 152, 92, 26, 22, 21, 28, 20, 26, 30, 21,
15
+ 32, 27, 20, 17, 23, 21, 30, 22, 22, 21, 27, 25, 17, 27, 23, 18, 39, 26, 15, 21,
16
+ 12, 18, 18, 27, 20, 18, 15, 19, 11, 17, 33, 12, 18, 15, 19, 18, 16, 26, 17, 18,
17
+ 9, 10, 25, 22, 22, 17, 20, 16, 6, 16, 15, 20, 14, 18, 24, 335, 1517]
18
+
19
+ HUFFMAN_EOF_SYMBOL = 256
20
+ HUFFMAN_MAX_SYMBOLS = HUFFMAN_EOF_SYMBOL + 1
21
+ HUFFMAN_MAX_NODES = HUFFMAN_MAX_SYMBOLS * 2 - 1
22
+ HUFFMAN_LUTBITS = 10
23
+ HUFFMAN_LUTSIZE = 1 << HUFFMAN_LUTBITS
24
+ HUFFMAN_LUTMASK = HUFFMAN_LUTSIZE - 1
25
+
26
+
27
+ class Node:
28
+ def __init__(self):
29
+ self.bits: int = None
30
+ self.numbits: int = None
31
+ # Leafs
32
+ self.left: int = None
33
+ self.right: int = None
34
+
35
+ self.symbol: int = None
36
+
37
+ def __eq__(self, other):
38
+ return self.symbol == other.symbol
39
+
40
+
41
+ class HuffmanConstructNode:
42
+ def __init__(self):
43
+ self.node_id: int = None
44
+ self.frequency: int = None
45
+
46
+
47
+ class Huffman:
48
+ def __init__(self, frequencies: List[int]):
49
+ self.nodes: List[Node] = [Node() for _ in range(HUFFMAN_MAX_NODES)]
50
+ # list of index of nodes
51
+ self.decode_lut: List[int] = [None for _ in range(HUFFMAN_LUTSIZE)]
52
+ self.num_nodes: int = None
53
+ self.start_node_index: int = None
54
+
55
+ self.construct_tree(frequencies)
56
+ # print(self.num_nodes, "numnodes")
57
+ # print(self.start_node_index, "nodeindex")
58
+ # print(len(self.decode_lut), "dcodelut")
59
+ xx = 0
60
+ for i in range(HUFFMAN_LUTSIZE):
61
+ bits = i
62
+ broke = False
63
+ index = self.start_node_index
64
+ for x in range(HUFFMAN_LUTBITS):
65
+ if bits & 1:
66
+ index = self.nodes[index].right
67
+ else:
68
+ index = self.nodes[index].left
69
+ # print(index)
70
+ bits >>= 1
71
+
72
+ if self.nodes[index].numbits:
73
+ # print(i, bits)
74
+ self.decode_lut[i] = index
75
+ xx += 1
76
+ broke = True
77
+ break
78
+ if not broke:
79
+ self.decode_lut[i] = index
80
+ # print(xx, HUFFMAN_LUTSIZE, HUFFMAN_LUTBITS)
81
+ def set_bits_r(self, node_index: int, bits: int, depth: int):
82
+ if self.nodes[node_index].right != 0xffff:
83
+ self.set_bits_r(self.nodes[node_index].right, bits | (1 << depth), depth + 1)
84
+ if self.nodes[node_index].left != 0xffff:
85
+ self.set_bits_r(self.nodes[node_index].left, bits, depth + 1)
86
+
87
+ if self.nodes[node_index].numbits:
88
+ self.nodes[node_index].bits = bits
89
+ self.nodes[node_index].numbits = depth
90
+
91
+ @staticmethod
92
+ def bubble_sort(index_list: List[int], node_list: List[HuffmanConstructNode], size: int):
93
+ changed = True
94
+ while changed:
95
+ changed = False
96
+ for i in range(size - 1):
97
+ if node_list[index_list[i]].frequency < node_list[index_list[i + 1]].frequency:
98
+ index_list[i], index_list[i + 1] = index_list[i + 1], index_list[i]
99
+ changed = True
100
+ size -= 1
101
+ return index_list
102
+
103
+ def construct_tree(self, frequencies: List[int]):
104
+ nodes_left_storage: List[HuffmanConstructNode] = [HuffmanConstructNode() for _ in range(HUFFMAN_MAX_SYMBOLS)]
105
+ nodes_left: List[int] = [None for _ in range(HUFFMAN_MAX_SYMBOLS)]
106
+ num_nodes_left = HUFFMAN_MAX_SYMBOLS
107
+
108
+ for i in range(HUFFMAN_MAX_SYMBOLS):
109
+ self.nodes[i].numbits = 0xFFFFFFFF
110
+ self.nodes[i].symbol = i
111
+ self.nodes[i].left = 0xffff
112
+ self.nodes[i].right = 0xffff
113
+
114
+ if i == HUFFMAN_EOF_SYMBOL:
115
+ nodes_left_storage[i].frequency = 1
116
+ else:
117
+ nodes_left_storage[i].frequency = frequencies[i]
118
+ nodes_left_storage[i].node_id = i
119
+ nodes_left[i] = i
120
+
121
+ self.num_nodes = HUFFMAN_MAX_SYMBOLS
122
+
123
+ while num_nodes_left > 1:
124
+ nodes_left = Huffman.bubble_sort(nodes_left, nodes_left_storage, num_nodes_left)
125
+
126
+ self.nodes[self.num_nodes].numbits = 0
127
+ self.nodes[self.num_nodes].left = nodes_left_storage[nodes_left[num_nodes_left - 1]].node_id
128
+ self.nodes[self.num_nodes].right = nodes_left_storage[nodes_left[num_nodes_left - 2]].node_id
129
+ nodes_left_storage[nodes_left[num_nodes_left - 2]].node_id = self.num_nodes
130
+ nodes_left_storage[nodes_left[num_nodes_left - 2]].frequency = \
131
+ nodes_left_storage[nodes_left[num_nodes_left - 1]].frequency \
132
+ + nodes_left_storage[nodes_left[num_nodes_left - 2]].frequency
133
+ self.num_nodes += 1
134
+ num_nodes_left -= 1
135
+ self.start_node_index = self.num_nodes - 1
136
+ self.set_bits_r(self.start_node_index, 0, 0)
137
+
138
+ def compress(self, inp_buffer: bytearray, start_index: int = 0, size: int = None):
139
+ output = bytearray()
140
+ bits = 0
141
+ bitcount = 0
142
+
143
+ if size is None:
144
+ size = len(inp_buffer)
145
+
146
+ for x in inp_buffer[start_index:size:]:
147
+ # print(x)
148
+ bits |= self.nodes[x].bits << bitcount
149
+ bitcount += self.nodes[x].numbits
150
+
151
+ while bitcount >= 8:
152
+ output.append(bits & 0xff)
153
+ bits >>= 8
154
+ bitcount -= 8
155
+
156
+ bits |= self.nodes[HUFFMAN_EOF_SYMBOL].bits << bitcount
157
+ bitcount += self.nodes[HUFFMAN_EOF_SYMBOL].numbits
158
+
159
+ while bitcount >= 8:
160
+ output.append(bits & 0xff)
161
+ bits >>= 8
162
+ bitcount -= 8
163
+
164
+ # write out last bits
165
+ output.append(bits)
166
+ return output
167
+
168
+ def decompress(self, inp_buffer: bytearray, size: int = None):
169
+ bits = 0
170
+ bitcount = 0
171
+ eof = self.nodes[HUFFMAN_EOF_SYMBOL]
172
+ output = bytearray()
173
+
174
+ if size is None:
175
+ size = len(inp_buffer)
176
+
177
+ src_index = 0
178
+ while True:
179
+ node_i = None
180
+ if bitcount >= HUFFMAN_LUTBITS:
181
+ node_i = self.decode_lut[bits & HUFFMAN_LUTMASK]
182
+ # print("yes im here!", output, node_i, bits & HUFFMAN_LUTMASK)
183
+
184
+ while bitcount < 24 and src_index != size:
185
+ bits |= inp_buffer[src_index] << bitcount
186
+ src_index += 1
187
+ bitcount += 8
188
+
189
+ # print(self.decode_lut)
190
+ if node_i is None:
191
+ node_i = self.decode_lut[bits & HUFFMAN_LUTMASK]
192
+ if self.nodes[node_i].numbits:
193
+ bits >>= self.nodes[node_i].numbits
194
+ bitcount -= self.nodes[node_i].numbits
195
+ else:
196
+ bits >>= HUFFMAN_LUTBITS
197
+ bitcount -= HUFFMAN_LUTBITS
198
+ # print("yes im here!", output)
199
+
200
+ while True:
201
+ if bits & 1:
202
+ node_i = self.nodes[node_i].right
203
+ else:
204
+ node_i = self.nodes[node_i].left
205
+
206
+ bitcount -= 1
207
+ bits >>= 1
208
+
209
+ if self.nodes[node_i].numbits:
210
+ break
211
+
212
+ if bitcount == 0:
213
+ raise ValueError("No more bits, decoding error")
214
+
215
+ if self.nodes[node_i] == eof:
216
+ break
217
+ output.append(self.nodes[node_i].symbol)
218
+
219
+ return output
220
+ huff = Huffman(frequencies=FREQ_TABLE)
221
+ if (argv[1] and argv[2] == "-decompress"):
222
+ print(list(huff.decompress(bytearray.fromhex(argv[1]))))
223
+ elif (argv[1] and argv[2] == "-compress"):
224
+ print(list(huff.compress(bytearray.fromhex(argv[1]))))