stormlib-js 0.1.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/README.md +254 -0
- package/dist/index.d.mts +420 -0
- package/dist/index.d.ts +420 -0
- package/dist/index.js +2979 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2920 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +44 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2920 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __esm = (fn, res) => function __init() {
|
|
6
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
|
+
};
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/compression/pkware.ts
|
|
23
|
+
var pkware_exports = {};
|
|
24
|
+
__export(pkware_exports, {
|
|
25
|
+
decompressPkware: () => decompressPkware
|
|
26
|
+
});
|
|
27
|
+
function decodeLength(reader) {
|
|
28
|
+
const bits8 = reader.peekBits(8);
|
|
29
|
+
let lengthCode = -1;
|
|
30
|
+
for (let i = 0; i < LENGTH_CODES.length; i++) {
|
|
31
|
+
const codeLen = LENGTH_BITS[i];
|
|
32
|
+
const mask = (1 << codeLen) - 1;
|
|
33
|
+
if ((bits8 & mask) === LENGTH_CODES[i]) {
|
|
34
|
+
lengthCode = i;
|
|
35
|
+
reader.skipBits(codeLen);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (lengthCode === -1) {
|
|
40
|
+
for (let i = 0; i < LENGTH_CODES.length; i++) {
|
|
41
|
+
const codeLen = LENGTH_BITS[i];
|
|
42
|
+
const peek = reader.peekBits(codeLen);
|
|
43
|
+
if (peek === LENGTH_CODES[i]) {
|
|
44
|
+
lengthCode = i;
|
|
45
|
+
reader.skipBits(codeLen);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (lengthCode === -1) return -1;
|
|
51
|
+
if (lengthCode === 15) {
|
|
52
|
+
return 15 + 256 + reader.readBits(8);
|
|
53
|
+
}
|
|
54
|
+
return lengthCode + 2;
|
|
55
|
+
}
|
|
56
|
+
function decompressPkware(inBuffer, outSize) {
|
|
57
|
+
if (inBuffer.length < 4) {
|
|
58
|
+
return Buffer.from(inBuffer);
|
|
59
|
+
}
|
|
60
|
+
const output = Buffer.alloc(outSize);
|
|
61
|
+
let outPos = 0;
|
|
62
|
+
const compressionType = inBuffer[0];
|
|
63
|
+
const dictSizeBits = inBuffer[1];
|
|
64
|
+
const dictSize = 1 << dictSizeBits;
|
|
65
|
+
const reader = new PkBitReader(inBuffer.subarray(2));
|
|
66
|
+
while (outPos < outSize && !reader.isEof) {
|
|
67
|
+
const flag = reader.readBits(1);
|
|
68
|
+
if (flag === 0) {
|
|
69
|
+
if (compressionType === 0) {
|
|
70
|
+
output[outPos++] = reader.readBits(8);
|
|
71
|
+
} else {
|
|
72
|
+
let value = 0;
|
|
73
|
+
const bits = reader.peekBits(8);
|
|
74
|
+
let found = false;
|
|
75
|
+
for (let i = 0; i < 256; i++) {
|
|
76
|
+
const numBits = CH_BITS_ASC[i];
|
|
77
|
+
const peek = reader.peekBits(numBits);
|
|
78
|
+
if (false) {
|
|
79
|
+
value = i;
|
|
80
|
+
reader.skipBits(numBits);
|
|
81
|
+
found = true;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!found) {
|
|
86
|
+
output[outPos++] = reader.readBits(8);
|
|
87
|
+
} else {
|
|
88
|
+
output[outPos++] = value;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
const distBits8 = reader.peekBits(8);
|
|
93
|
+
let distCode = -1;
|
|
94
|
+
for (let i = 0; i < DIST_CODES.length; i++) {
|
|
95
|
+
if (DIST_CODES[i] === (distBits8 & (1 << DIST_BITS[i]) - 1)) {
|
|
96
|
+
distCode = i;
|
|
97
|
+
reader.skipBits(DIST_BITS[i]);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (distCode === -1) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
const distance = distCode << dictSizeBits | reader.readBits(dictSizeBits);
|
|
105
|
+
const length = decodeLength(reader);
|
|
106
|
+
if (length < 0) break;
|
|
107
|
+
const srcPos = outPos - distance - 1;
|
|
108
|
+
if (srcPos < 0) break;
|
|
109
|
+
for (let i = 0; i < length && outPos < outSize; i++) {
|
|
110
|
+
output[outPos] = output[srcPos + i];
|
|
111
|
+
outPos++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return output.subarray(0, outPos);
|
|
116
|
+
}
|
|
117
|
+
var DIST_CODES, DIST_BITS, LENGTH_CODES, LENGTH_BITS, CH_BITS_ASC, PkBitReader;
|
|
118
|
+
var init_pkware = __esm({
|
|
119
|
+
"src/compression/pkware.ts"() {
|
|
120
|
+
"use strict";
|
|
121
|
+
DIST_CODES = [
|
|
122
|
+
3,
|
|
123
|
+
13,
|
|
124
|
+
5,
|
|
125
|
+
25,
|
|
126
|
+
9,
|
|
127
|
+
17,
|
|
128
|
+
1,
|
|
129
|
+
62,
|
|
130
|
+
30,
|
|
131
|
+
46,
|
|
132
|
+
14,
|
|
133
|
+
54,
|
|
134
|
+
22,
|
|
135
|
+
38,
|
|
136
|
+
6,
|
|
137
|
+
58,
|
|
138
|
+
26,
|
|
139
|
+
42,
|
|
140
|
+
10,
|
|
141
|
+
50,
|
|
142
|
+
18,
|
|
143
|
+
34,
|
|
144
|
+
66,
|
|
145
|
+
2,
|
|
146
|
+
124,
|
|
147
|
+
60,
|
|
148
|
+
92,
|
|
149
|
+
28,
|
|
150
|
+
108,
|
|
151
|
+
44,
|
|
152
|
+
76,
|
|
153
|
+
12,
|
|
154
|
+
116,
|
|
155
|
+
52,
|
|
156
|
+
84,
|
|
157
|
+
20,
|
|
158
|
+
100,
|
|
159
|
+
36,
|
|
160
|
+
68,
|
|
161
|
+
4,
|
|
162
|
+
120,
|
|
163
|
+
56,
|
|
164
|
+
88,
|
|
165
|
+
24,
|
|
166
|
+
104,
|
|
167
|
+
40,
|
|
168
|
+
72,
|
|
169
|
+
8,
|
|
170
|
+
240,
|
|
171
|
+
112,
|
|
172
|
+
176,
|
|
173
|
+
48,
|
|
174
|
+
208,
|
|
175
|
+
80,
|
|
176
|
+
144,
|
|
177
|
+
16,
|
|
178
|
+
224,
|
|
179
|
+
96,
|
|
180
|
+
160,
|
|
181
|
+
32,
|
|
182
|
+
192,
|
|
183
|
+
64,
|
|
184
|
+
128,
|
|
185
|
+
0
|
|
186
|
+
];
|
|
187
|
+
DIST_BITS = [
|
|
188
|
+
2,
|
|
189
|
+
4,
|
|
190
|
+
4,
|
|
191
|
+
5,
|
|
192
|
+
5,
|
|
193
|
+
5,
|
|
194
|
+
5,
|
|
195
|
+
6,
|
|
196
|
+
6,
|
|
197
|
+
6,
|
|
198
|
+
6,
|
|
199
|
+
6,
|
|
200
|
+
6,
|
|
201
|
+
6,
|
|
202
|
+
6,
|
|
203
|
+
6,
|
|
204
|
+
6,
|
|
205
|
+
6,
|
|
206
|
+
6,
|
|
207
|
+
6,
|
|
208
|
+
6,
|
|
209
|
+
6,
|
|
210
|
+
7,
|
|
211
|
+
7,
|
|
212
|
+
7,
|
|
213
|
+
7,
|
|
214
|
+
7,
|
|
215
|
+
7,
|
|
216
|
+
7,
|
|
217
|
+
7,
|
|
218
|
+
7,
|
|
219
|
+
7,
|
|
220
|
+
7,
|
|
221
|
+
7,
|
|
222
|
+
7,
|
|
223
|
+
7,
|
|
224
|
+
7,
|
|
225
|
+
7,
|
|
226
|
+
7,
|
|
227
|
+
7,
|
|
228
|
+
7,
|
|
229
|
+
7,
|
|
230
|
+
7,
|
|
231
|
+
7,
|
|
232
|
+
7,
|
|
233
|
+
7,
|
|
234
|
+
7,
|
|
235
|
+
7,
|
|
236
|
+
8,
|
|
237
|
+
8,
|
|
238
|
+
8,
|
|
239
|
+
8,
|
|
240
|
+
8,
|
|
241
|
+
8,
|
|
242
|
+
8,
|
|
243
|
+
8,
|
|
244
|
+
8,
|
|
245
|
+
8,
|
|
246
|
+
8,
|
|
247
|
+
8,
|
|
248
|
+
8,
|
|
249
|
+
8,
|
|
250
|
+
8,
|
|
251
|
+
8
|
|
252
|
+
];
|
|
253
|
+
LENGTH_CODES = [
|
|
254
|
+
5,
|
|
255
|
+
3,
|
|
256
|
+
1,
|
|
257
|
+
6,
|
|
258
|
+
10,
|
|
259
|
+
2,
|
|
260
|
+
12,
|
|
261
|
+
20,
|
|
262
|
+
4,
|
|
263
|
+
24,
|
|
264
|
+
8,
|
|
265
|
+
48,
|
|
266
|
+
16,
|
|
267
|
+
32,
|
|
268
|
+
64,
|
|
269
|
+
0
|
|
270
|
+
];
|
|
271
|
+
LENGTH_BITS = [
|
|
272
|
+
3,
|
|
273
|
+
2,
|
|
274
|
+
3,
|
|
275
|
+
3,
|
|
276
|
+
4,
|
|
277
|
+
4,
|
|
278
|
+
4,
|
|
279
|
+
5,
|
|
280
|
+
5,
|
|
281
|
+
5,
|
|
282
|
+
5,
|
|
283
|
+
6,
|
|
284
|
+
6,
|
|
285
|
+
6,
|
|
286
|
+
7,
|
|
287
|
+
7
|
|
288
|
+
];
|
|
289
|
+
CH_BITS_ASC = [
|
|
290
|
+
11,
|
|
291
|
+
12,
|
|
292
|
+
12,
|
|
293
|
+
12,
|
|
294
|
+
12,
|
|
295
|
+
12,
|
|
296
|
+
12,
|
|
297
|
+
12,
|
|
298
|
+
12,
|
|
299
|
+
8,
|
|
300
|
+
7,
|
|
301
|
+
12,
|
|
302
|
+
12,
|
|
303
|
+
7,
|
|
304
|
+
12,
|
|
305
|
+
12,
|
|
306
|
+
12,
|
|
307
|
+
12,
|
|
308
|
+
12,
|
|
309
|
+
12,
|
|
310
|
+
12,
|
|
311
|
+
12,
|
|
312
|
+
12,
|
|
313
|
+
12,
|
|
314
|
+
12,
|
|
315
|
+
12,
|
|
316
|
+
13,
|
|
317
|
+
12,
|
|
318
|
+
12,
|
|
319
|
+
12,
|
|
320
|
+
12,
|
|
321
|
+
12,
|
|
322
|
+
4,
|
|
323
|
+
10,
|
|
324
|
+
8,
|
|
325
|
+
12,
|
|
326
|
+
10,
|
|
327
|
+
12,
|
|
328
|
+
10,
|
|
329
|
+
8,
|
|
330
|
+
7,
|
|
331
|
+
7,
|
|
332
|
+
8,
|
|
333
|
+
9,
|
|
334
|
+
7,
|
|
335
|
+
6,
|
|
336
|
+
7,
|
|
337
|
+
8,
|
|
338
|
+
7,
|
|
339
|
+
6,
|
|
340
|
+
7,
|
|
341
|
+
7,
|
|
342
|
+
7,
|
|
343
|
+
7,
|
|
344
|
+
8,
|
|
345
|
+
7,
|
|
346
|
+
7,
|
|
347
|
+
8,
|
|
348
|
+
8,
|
|
349
|
+
12,
|
|
350
|
+
11,
|
|
351
|
+
7,
|
|
352
|
+
9,
|
|
353
|
+
11,
|
|
354
|
+
12,
|
|
355
|
+
6,
|
|
356
|
+
7,
|
|
357
|
+
6,
|
|
358
|
+
6,
|
|
359
|
+
5,
|
|
360
|
+
7,
|
|
361
|
+
8,
|
|
362
|
+
8,
|
|
363
|
+
6,
|
|
364
|
+
11,
|
|
365
|
+
9,
|
|
366
|
+
6,
|
|
367
|
+
7,
|
|
368
|
+
6,
|
|
369
|
+
6,
|
|
370
|
+
7,
|
|
371
|
+
11,
|
|
372
|
+
6,
|
|
373
|
+
6,
|
|
374
|
+
6,
|
|
375
|
+
7,
|
|
376
|
+
9,
|
|
377
|
+
8,
|
|
378
|
+
9,
|
|
379
|
+
9,
|
|
380
|
+
11,
|
|
381
|
+
8,
|
|
382
|
+
11,
|
|
383
|
+
9,
|
|
384
|
+
12,
|
|
385
|
+
8,
|
|
386
|
+
12,
|
|
387
|
+
5,
|
|
388
|
+
6,
|
|
389
|
+
6,
|
|
390
|
+
6,
|
|
391
|
+
5,
|
|
392
|
+
6,
|
|
393
|
+
6,
|
|
394
|
+
6,
|
|
395
|
+
5,
|
|
396
|
+
11,
|
|
397
|
+
7,
|
|
398
|
+
5,
|
|
399
|
+
6,
|
|
400
|
+
5,
|
|
401
|
+
5,
|
|
402
|
+
6,
|
|
403
|
+
10,
|
|
404
|
+
5,
|
|
405
|
+
5,
|
|
406
|
+
5,
|
|
407
|
+
5,
|
|
408
|
+
8,
|
|
409
|
+
7,
|
|
410
|
+
8,
|
|
411
|
+
8,
|
|
412
|
+
10,
|
|
413
|
+
11,
|
|
414
|
+
11,
|
|
415
|
+
12,
|
|
416
|
+
12,
|
|
417
|
+
12,
|
|
418
|
+
13,
|
|
419
|
+
13,
|
|
420
|
+
13,
|
|
421
|
+
13,
|
|
422
|
+
13,
|
|
423
|
+
13,
|
|
424
|
+
13,
|
|
425
|
+
13,
|
|
426
|
+
13,
|
|
427
|
+
13,
|
|
428
|
+
13,
|
|
429
|
+
13,
|
|
430
|
+
13,
|
|
431
|
+
13,
|
|
432
|
+
13,
|
|
433
|
+
13,
|
|
434
|
+
13,
|
|
435
|
+
13,
|
|
436
|
+
13,
|
|
437
|
+
13,
|
|
438
|
+
13,
|
|
439
|
+
13,
|
|
440
|
+
13,
|
|
441
|
+
13,
|
|
442
|
+
13,
|
|
443
|
+
13,
|
|
444
|
+
13,
|
|
445
|
+
13,
|
|
446
|
+
13,
|
|
447
|
+
13,
|
|
448
|
+
13,
|
|
449
|
+
13,
|
|
450
|
+
13,
|
|
451
|
+
13,
|
|
452
|
+
13,
|
|
453
|
+
13,
|
|
454
|
+
13,
|
|
455
|
+
13,
|
|
456
|
+
13,
|
|
457
|
+
13,
|
|
458
|
+
13,
|
|
459
|
+
13,
|
|
460
|
+
13,
|
|
461
|
+
13,
|
|
462
|
+
13,
|
|
463
|
+
13,
|
|
464
|
+
13,
|
|
465
|
+
13,
|
|
466
|
+
12,
|
|
467
|
+
12,
|
|
468
|
+
12,
|
|
469
|
+
12,
|
|
470
|
+
12,
|
|
471
|
+
12,
|
|
472
|
+
12,
|
|
473
|
+
12,
|
|
474
|
+
12,
|
|
475
|
+
12,
|
|
476
|
+
12,
|
|
477
|
+
12,
|
|
478
|
+
12,
|
|
479
|
+
12,
|
|
480
|
+
12,
|
|
481
|
+
12,
|
|
482
|
+
12,
|
|
483
|
+
12,
|
|
484
|
+
12,
|
|
485
|
+
12,
|
|
486
|
+
12,
|
|
487
|
+
12,
|
|
488
|
+
12,
|
|
489
|
+
12,
|
|
490
|
+
12,
|
|
491
|
+
12,
|
|
492
|
+
12,
|
|
493
|
+
12,
|
|
494
|
+
12,
|
|
495
|
+
12,
|
|
496
|
+
12,
|
|
497
|
+
12,
|
|
498
|
+
12,
|
|
499
|
+
12,
|
|
500
|
+
12,
|
|
501
|
+
12,
|
|
502
|
+
12,
|
|
503
|
+
12,
|
|
504
|
+
12,
|
|
505
|
+
12,
|
|
506
|
+
12,
|
|
507
|
+
12,
|
|
508
|
+
12,
|
|
509
|
+
12,
|
|
510
|
+
12,
|
|
511
|
+
12,
|
|
512
|
+
12,
|
|
513
|
+
12,
|
|
514
|
+
13,
|
|
515
|
+
12,
|
|
516
|
+
13,
|
|
517
|
+
13,
|
|
518
|
+
13,
|
|
519
|
+
12,
|
|
520
|
+
13,
|
|
521
|
+
13,
|
|
522
|
+
13,
|
|
523
|
+
12,
|
|
524
|
+
13,
|
|
525
|
+
13,
|
|
526
|
+
13,
|
|
527
|
+
13,
|
|
528
|
+
12,
|
|
529
|
+
13,
|
|
530
|
+
13,
|
|
531
|
+
13,
|
|
532
|
+
12,
|
|
533
|
+
12,
|
|
534
|
+
12,
|
|
535
|
+
13,
|
|
536
|
+
13,
|
|
537
|
+
13,
|
|
538
|
+
13,
|
|
539
|
+
13,
|
|
540
|
+
13,
|
|
541
|
+
13,
|
|
542
|
+
13,
|
|
543
|
+
13,
|
|
544
|
+
13,
|
|
545
|
+
13
|
|
546
|
+
];
|
|
547
|
+
PkBitReader = class {
|
|
548
|
+
constructor(data) {
|
|
549
|
+
this.bytePos = 0;
|
|
550
|
+
this.bitBuf = 0;
|
|
551
|
+
this.bitsAvail = 0;
|
|
552
|
+
this.data = data;
|
|
553
|
+
}
|
|
554
|
+
fillBuffer() {
|
|
555
|
+
while (this.bitsAvail <= 24 && this.bytePos < this.data.length) {
|
|
556
|
+
this.bitBuf |= this.data[this.bytePos++] << this.bitsAvail;
|
|
557
|
+
this.bitsAvail += 8;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
peekBits(n) {
|
|
561
|
+
this.fillBuffer();
|
|
562
|
+
return this.bitBuf & (1 << n) - 1;
|
|
563
|
+
}
|
|
564
|
+
skipBits(n) {
|
|
565
|
+
this.fillBuffer();
|
|
566
|
+
this.bitBuf >>>= n;
|
|
567
|
+
this.bitsAvail -= n;
|
|
568
|
+
}
|
|
569
|
+
readBits(n) {
|
|
570
|
+
const val = this.peekBits(n);
|
|
571
|
+
this.skipBits(n);
|
|
572
|
+
return val;
|
|
573
|
+
}
|
|
574
|
+
get isEof() {
|
|
575
|
+
return this.bytePos >= this.data.length && this.bitsAvail === 0;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// src/constants.ts
|
|
582
|
+
var ID_MPQ = 441536589;
|
|
583
|
+
var ID_MPQ_USERDATA = 458313805;
|
|
584
|
+
var MPQ_FORMAT_VERSION_1 = 0;
|
|
585
|
+
var MPQ_FORMAT_VERSION_2 = 1;
|
|
586
|
+
var MPQ_FORMAT_VERSION_3 = 2;
|
|
587
|
+
var MPQ_FORMAT_VERSION_4 = 3;
|
|
588
|
+
var MPQ_HEADER_SIZE_V1 = 32;
|
|
589
|
+
var MPQ_HEADER_SIZE_V2 = 44;
|
|
590
|
+
var MPQ_HEADER_SIZE_V3 = 68;
|
|
591
|
+
var MPQ_HEADER_SIZE_V4 = 208;
|
|
592
|
+
var HASH_ENTRY_FREE = 4294967295;
|
|
593
|
+
var HET_ENTRY_FREE = 0;
|
|
594
|
+
var MPQ_HASH_TABLE_INDEX = 0;
|
|
595
|
+
var MPQ_HASH_NAME_A = 256;
|
|
596
|
+
var MPQ_HASH_NAME_B = 512;
|
|
597
|
+
var MPQ_HASH_FILE_KEY = 768;
|
|
598
|
+
var MPQ_HASH_KEY2_MIX = 1024;
|
|
599
|
+
var STORM_BUFFER_SIZE = 1280;
|
|
600
|
+
var MPQ_KEY_HASH_TABLE = 3283040112;
|
|
601
|
+
var MPQ_KEY_BLOCK_TABLE = 3968054179;
|
|
602
|
+
var MPQ_FILE_IMPLODE = 256;
|
|
603
|
+
var MPQ_FILE_COMPRESS = 512;
|
|
604
|
+
var MPQ_FILE_ENCRYPTED = 65536;
|
|
605
|
+
var MPQ_FILE_KEY_V2 = 131072;
|
|
606
|
+
var MPQ_FILE_SINGLE_UNIT = 16777216;
|
|
607
|
+
var MPQ_FILE_SECTOR_CRC = 67108864;
|
|
608
|
+
var MPQ_FILE_EXISTS = 2147483648;
|
|
609
|
+
var MPQ_COMPRESSION_HUFFMANN = 1;
|
|
610
|
+
var MPQ_COMPRESSION_ZLIB = 2;
|
|
611
|
+
var MPQ_COMPRESSION_PKWARE = 8;
|
|
612
|
+
var MPQ_COMPRESSION_BZIP2 = 16;
|
|
613
|
+
var MPQ_COMPRESSION_LZMA = 18;
|
|
614
|
+
var MPQ_COMPRESSION_SPARSE = 32;
|
|
615
|
+
var MPQ_COMPRESSION_ADPCM_MONO = 64;
|
|
616
|
+
var MPQ_COMPRESSION_ADPCM_STEREO = 128;
|
|
617
|
+
var HET_TABLE_SIGNATURE = 441730376;
|
|
618
|
+
var BET_TABLE_SIGNATURE = 441730370;
|
|
619
|
+
var BLOCK_INDEX_MASK = 268435455;
|
|
620
|
+
var LISTFILE_NAME = "(listfile)";
|
|
621
|
+
var SIGNATURE_NAME = "(signature)";
|
|
622
|
+
var ATTRIBUTES_NAME = "(attributes)";
|
|
623
|
+
var MPQ_HEADER_SEARCH_ALIGNMENT = 512;
|
|
624
|
+
|
|
625
|
+
// src/errors.ts
|
|
626
|
+
var MpqError = class extends Error {
|
|
627
|
+
constructor(message) {
|
|
628
|
+
super(message);
|
|
629
|
+
this.name = "MpqError";
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
var MpqNotFoundError = class extends MpqError {
|
|
633
|
+
constructor(message = "File not found in archive") {
|
|
634
|
+
super(message);
|
|
635
|
+
this.name = "MpqNotFoundError";
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
var MpqCorruptError = class extends MpqError {
|
|
639
|
+
constructor(message = "Archive data is corrupt") {
|
|
640
|
+
super(message);
|
|
641
|
+
this.name = "MpqCorruptError";
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
var MpqUnsupportedError = class extends MpqError {
|
|
645
|
+
constructor(message = "Unsupported feature") {
|
|
646
|
+
super(message);
|
|
647
|
+
this.name = "MpqUnsupportedError";
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
var MpqEncryptionError = class extends MpqError {
|
|
651
|
+
constructor(message = "Unable to decrypt data") {
|
|
652
|
+
super(message);
|
|
653
|
+
this.name = "MpqEncryptionError";
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
var MpqCompressionError = class extends MpqError {
|
|
657
|
+
constructor(message = "Decompression failed") {
|
|
658
|
+
super(message);
|
|
659
|
+
this.name = "MpqCompressionError";
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
// src/stream/file-stream.ts
|
|
664
|
+
import * as fs from "fs";
|
|
665
|
+
var FileStream = class _FileStream {
|
|
666
|
+
constructor(fd, fileSize, filePath) {
|
|
667
|
+
this.fd = fd;
|
|
668
|
+
this.fileSize = fileSize;
|
|
669
|
+
this.filePath = filePath;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Open a file for reading.
|
|
673
|
+
*/
|
|
674
|
+
static open(path) {
|
|
675
|
+
const fd = fs.openSync(path, "r");
|
|
676
|
+
const stat = fs.fstatSync(fd);
|
|
677
|
+
return new _FileStream(fd, BigInt(stat.size), path);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Read bytes at a given offset.
|
|
681
|
+
*
|
|
682
|
+
* @param offset - Byte offset from the beginning of the file
|
|
683
|
+
* @param length - Number of bytes to read
|
|
684
|
+
* @returns Buffer containing the read data
|
|
685
|
+
*/
|
|
686
|
+
read(offset, length) {
|
|
687
|
+
const buffer = Buffer.alloc(length);
|
|
688
|
+
const bytesRead = fs.readSync(this.fd, buffer, 0, length, Number(offset));
|
|
689
|
+
if (bytesRead < length) {
|
|
690
|
+
return buffer.subarray(0, bytesRead);
|
|
691
|
+
}
|
|
692
|
+
return buffer;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Read bytes into an existing buffer.
|
|
696
|
+
*/
|
|
697
|
+
readInto(offset, buffer, bufOffset, length) {
|
|
698
|
+
return fs.readSync(this.fd, buffer, bufOffset, length, Number(offset));
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Get the total file size.
|
|
702
|
+
*/
|
|
703
|
+
getSize() {
|
|
704
|
+
return this.fileSize;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Get the file path.
|
|
708
|
+
*/
|
|
709
|
+
getPath() {
|
|
710
|
+
return this.filePath;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Close the file stream.
|
|
714
|
+
*/
|
|
715
|
+
close() {
|
|
716
|
+
if (this.fd >= 0) {
|
|
717
|
+
fs.closeSync(this.fd);
|
|
718
|
+
this.fd = -1;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
// src/archive/header.ts
|
|
724
|
+
function createDefaultHeader() {
|
|
725
|
+
return {
|
|
726
|
+
dwID: 0,
|
|
727
|
+
dwHeaderSize: 0,
|
|
728
|
+
dwArchiveSize: 0,
|
|
729
|
+
wFormatVersion: 0,
|
|
730
|
+
wSectorSize: 0,
|
|
731
|
+
dwHashTablePos: 0,
|
|
732
|
+
dwBlockTablePos: 0,
|
|
733
|
+
dwHashTableSize: 0,
|
|
734
|
+
dwBlockTableSize: 0,
|
|
735
|
+
hiBlockTablePos64: 0n,
|
|
736
|
+
wHashTablePosHi: 0,
|
|
737
|
+
wBlockTablePosHi: 0,
|
|
738
|
+
archiveSize64: 0n,
|
|
739
|
+
betTablePos64: 0n,
|
|
740
|
+
hetTablePos64: 0n,
|
|
741
|
+
hashTableSize64: 0n,
|
|
742
|
+
blockTableSize64: 0n,
|
|
743
|
+
hiBlockTableSize64: 0n,
|
|
744
|
+
hetTableSize64: 0n,
|
|
745
|
+
betTableSize64: 0n,
|
|
746
|
+
dwRawChunkSize: 0,
|
|
747
|
+
md5BlockTable: new Uint8Array(16),
|
|
748
|
+
md5HashTable: new Uint8Array(16),
|
|
749
|
+
md5HiBlockTable: new Uint8Array(16),
|
|
750
|
+
md5BetTable: new Uint8Array(16),
|
|
751
|
+
md5HetTable: new Uint8Array(16),
|
|
752
|
+
md5MpqHeader: new Uint8Array(16)
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function parseHeaderV1(data, header) {
|
|
756
|
+
header.dwID = data.readUInt32LE(0);
|
|
757
|
+
header.dwHeaderSize = data.readUInt32LE(4);
|
|
758
|
+
header.dwArchiveSize = data.readUInt32LE(8);
|
|
759
|
+
header.wFormatVersion = data.readUInt16LE(12);
|
|
760
|
+
header.wSectorSize = data.readUInt16LE(14);
|
|
761
|
+
header.dwHashTablePos = data.readUInt32LE(16);
|
|
762
|
+
header.dwBlockTablePos = data.readUInt32LE(20);
|
|
763
|
+
header.dwHashTableSize = data.readUInt32LE(24);
|
|
764
|
+
header.dwBlockTableSize = data.readUInt32LE(28);
|
|
765
|
+
header.archiveSize64 = BigInt(header.dwArchiveSize);
|
|
766
|
+
header.hashTableSize64 = BigInt(header.dwHashTableSize * 16);
|
|
767
|
+
header.blockTableSize64 = BigInt(header.dwBlockTableSize * 16);
|
|
768
|
+
}
|
|
769
|
+
function parseHeaderV2(data, header) {
|
|
770
|
+
if (data.length < MPQ_HEADER_SIZE_V2) return;
|
|
771
|
+
header.hiBlockTablePos64 = data.readBigUInt64LE(32);
|
|
772
|
+
header.wHashTablePosHi = data.readUInt16LE(40);
|
|
773
|
+
header.wBlockTablePosHi = data.readUInt16LE(42);
|
|
774
|
+
}
|
|
775
|
+
function parseHeaderV3(data, header) {
|
|
776
|
+
if (data.length < MPQ_HEADER_SIZE_V3) return;
|
|
777
|
+
header.archiveSize64 = data.readBigUInt64LE(44);
|
|
778
|
+
header.betTablePos64 = data.readBigUInt64LE(52);
|
|
779
|
+
header.hetTablePos64 = data.readBigUInt64LE(60);
|
|
780
|
+
}
|
|
781
|
+
function parseHeaderV4(data, header) {
|
|
782
|
+
if (data.length < MPQ_HEADER_SIZE_V4) return;
|
|
783
|
+
header.hashTableSize64 = data.readBigUInt64LE(68);
|
|
784
|
+
header.blockTableSize64 = data.readBigUInt64LE(76);
|
|
785
|
+
header.hiBlockTableSize64 = data.readBigUInt64LE(84);
|
|
786
|
+
header.hetTableSize64 = data.readBigUInt64LE(92);
|
|
787
|
+
header.betTableSize64 = data.readBigUInt64LE(100);
|
|
788
|
+
header.dwRawChunkSize = data.readUInt32LE(108);
|
|
789
|
+
header.md5BlockTable = new Uint8Array(data.subarray(112, 128));
|
|
790
|
+
header.md5HashTable = new Uint8Array(data.subarray(128, 144));
|
|
791
|
+
header.md5HiBlockTable = new Uint8Array(data.subarray(144, 160));
|
|
792
|
+
header.md5BetTable = new Uint8Array(data.subarray(160, 176));
|
|
793
|
+
header.md5HetTable = new Uint8Array(data.subarray(176, 192));
|
|
794
|
+
header.md5MpqHeader = new Uint8Array(data.subarray(192, 208));
|
|
795
|
+
}
|
|
796
|
+
function parseHeader(data) {
|
|
797
|
+
if (data.length < MPQ_HEADER_SIZE_V1) {
|
|
798
|
+
throw new MpqCorruptError("Header data too small");
|
|
799
|
+
}
|
|
800
|
+
const header = createDefaultHeader();
|
|
801
|
+
parseHeaderV1(data, header);
|
|
802
|
+
if (header.dwID !== ID_MPQ) {
|
|
803
|
+
throw new MpqCorruptError(`Invalid MPQ signature: 0x${header.dwID.toString(16)}`);
|
|
804
|
+
}
|
|
805
|
+
if (header.wFormatVersion >= MPQ_FORMAT_VERSION_2) {
|
|
806
|
+
parseHeaderV2(data, header);
|
|
807
|
+
}
|
|
808
|
+
if (header.wFormatVersion >= MPQ_FORMAT_VERSION_3) {
|
|
809
|
+
parseHeaderV3(data, header);
|
|
810
|
+
}
|
|
811
|
+
if (header.wFormatVersion >= MPQ_FORMAT_VERSION_4) {
|
|
812
|
+
parseHeaderV4(data, header);
|
|
813
|
+
}
|
|
814
|
+
return header;
|
|
815
|
+
}
|
|
816
|
+
function parseUserData(data) {
|
|
817
|
+
return {
|
|
818
|
+
dwID: data.readUInt32LE(0),
|
|
819
|
+
cbUserDataSize: data.readUInt32LE(4),
|
|
820
|
+
dwHeaderOffs: data.readUInt32LE(8),
|
|
821
|
+
cbUserDataHeader: data.readUInt32LE(12)
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function findHeader(stream, noHeaderSearch = false) {
|
|
825
|
+
const fileSize = stream.getSize();
|
|
826
|
+
const searchLimit = noHeaderSearch ? 0n : fileSize;
|
|
827
|
+
let userData;
|
|
828
|
+
let userDataOffset;
|
|
829
|
+
for (let offset = 0n; offset <= searchLimit; offset += BigInt(MPQ_HEADER_SEARCH_ALIGNMENT)) {
|
|
830
|
+
const sigBuf = stream.read(offset, 4);
|
|
831
|
+
if (sigBuf.length < 4) break;
|
|
832
|
+
const signature = sigBuf.readUInt32LE(0);
|
|
833
|
+
if (signature === ID_MPQ_USERDATA && !noHeaderSearch) {
|
|
834
|
+
const udBuf = stream.read(offset, 16);
|
|
835
|
+
if (udBuf.length >= 16) {
|
|
836
|
+
userData = parseUserData(udBuf);
|
|
837
|
+
userDataOffset = offset;
|
|
838
|
+
const mpqOffset = offset + BigInt(userData.dwHeaderOffs);
|
|
839
|
+
const headerBuf = stream.read(mpqOffset, MPQ_HEADER_SIZE_V4);
|
|
840
|
+
if (headerBuf.length >= MPQ_HEADER_SIZE_V1) {
|
|
841
|
+
const headerSig = headerBuf.readUInt32LE(0);
|
|
842
|
+
if (headerSig === ID_MPQ) {
|
|
843
|
+
const header = parseHeader(headerBuf);
|
|
844
|
+
return { header, headerOffset: mpqOffset, userData, userDataOffset };
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
} else if (signature === ID_MPQ) {
|
|
849
|
+
const headerBuf = stream.read(offset, MPQ_HEADER_SIZE_V4);
|
|
850
|
+
if (headerBuf.length >= MPQ_HEADER_SIZE_V1) {
|
|
851
|
+
const header = parseHeader(headerBuf);
|
|
852
|
+
return { header, headerOffset: offset, userData, userDataOffset };
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (noHeaderSearch) break;
|
|
856
|
+
}
|
|
857
|
+
throw new MpqNotFoundError("MPQ header not found in file");
|
|
858
|
+
}
|
|
859
|
+
function getHashTableOffset(header, archiveOffset) {
|
|
860
|
+
let offset = BigInt(header.dwHashTablePos);
|
|
861
|
+
if (header.wFormatVersion >= MPQ_FORMAT_VERSION_2) {
|
|
862
|
+
offset |= BigInt(header.wHashTablePosHi) << 32n;
|
|
863
|
+
}
|
|
864
|
+
return archiveOffset + offset;
|
|
865
|
+
}
|
|
866
|
+
function getBlockTableOffset(header, archiveOffset) {
|
|
867
|
+
let offset = BigInt(header.dwBlockTablePos);
|
|
868
|
+
if (header.wFormatVersion >= MPQ_FORMAT_VERSION_2) {
|
|
869
|
+
offset |= BigInt(header.wBlockTablePosHi) << 32n;
|
|
870
|
+
}
|
|
871
|
+
return archiveOffset + offset;
|
|
872
|
+
}
|
|
873
|
+
function getHiBlockTableOffset(header, archiveOffset) {
|
|
874
|
+
if (header.wFormatVersion >= MPQ_FORMAT_VERSION_2 && header.hiBlockTablePos64 !== 0n) {
|
|
875
|
+
return archiveOffset + header.hiBlockTablePos64;
|
|
876
|
+
}
|
|
877
|
+
return 0n;
|
|
878
|
+
}
|
|
879
|
+
function getHetTableOffset(header, archiveOffset) {
|
|
880
|
+
if (header.wFormatVersion >= MPQ_FORMAT_VERSION_3 && header.hetTablePos64 !== 0n) {
|
|
881
|
+
return archiveOffset + header.hetTablePos64;
|
|
882
|
+
}
|
|
883
|
+
return 0n;
|
|
884
|
+
}
|
|
885
|
+
function getBetTableOffset(header, archiveOffset) {
|
|
886
|
+
if (header.wFormatVersion >= MPQ_FORMAT_VERSION_3 && header.betTablePos64 !== 0n) {
|
|
887
|
+
return archiveOffset + header.betTablePos64;
|
|
888
|
+
}
|
|
889
|
+
return 0n;
|
|
890
|
+
}
|
|
891
|
+
function getSectorSize(header) {
|
|
892
|
+
return 512 << header.wSectorSize;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/crypto/storm-buffer.ts
|
|
896
|
+
var stormBuffer = new Uint32Array(STORM_BUFFER_SIZE);
|
|
897
|
+
var initialized = false;
|
|
898
|
+
function initializeStormBuffer() {
|
|
899
|
+
let seed = 1048577;
|
|
900
|
+
for (let index1 = 0; index1 < 256; index1++) {
|
|
901
|
+
let index2 = index1;
|
|
902
|
+
for (let i = 0; i < 5; i++, index2 += 256) {
|
|
903
|
+
seed = (seed * 125 + 3) % 2796203;
|
|
904
|
+
const temp1 = (seed & 65535) << 16;
|
|
905
|
+
seed = (seed * 125 + 3) % 2796203;
|
|
906
|
+
const temp2 = seed & 65535;
|
|
907
|
+
stormBuffer[index2] = (temp1 | temp2) >>> 0;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
initialized = true;
|
|
911
|
+
}
|
|
912
|
+
function getStormBuffer() {
|
|
913
|
+
if (!initialized) {
|
|
914
|
+
initializeStormBuffer();
|
|
915
|
+
}
|
|
916
|
+
return stormBuffer;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// src/crypto/hash.ts
|
|
920
|
+
var AsciiToUpperTable = new Uint8Array([
|
|
921
|
+
0,
|
|
922
|
+
1,
|
|
923
|
+
2,
|
|
924
|
+
3,
|
|
925
|
+
4,
|
|
926
|
+
5,
|
|
927
|
+
6,
|
|
928
|
+
7,
|
|
929
|
+
8,
|
|
930
|
+
9,
|
|
931
|
+
10,
|
|
932
|
+
11,
|
|
933
|
+
12,
|
|
934
|
+
13,
|
|
935
|
+
14,
|
|
936
|
+
15,
|
|
937
|
+
16,
|
|
938
|
+
17,
|
|
939
|
+
18,
|
|
940
|
+
19,
|
|
941
|
+
20,
|
|
942
|
+
21,
|
|
943
|
+
22,
|
|
944
|
+
23,
|
|
945
|
+
24,
|
|
946
|
+
25,
|
|
947
|
+
26,
|
|
948
|
+
27,
|
|
949
|
+
28,
|
|
950
|
+
29,
|
|
951
|
+
30,
|
|
952
|
+
31,
|
|
953
|
+
32,
|
|
954
|
+
33,
|
|
955
|
+
34,
|
|
956
|
+
35,
|
|
957
|
+
36,
|
|
958
|
+
37,
|
|
959
|
+
38,
|
|
960
|
+
39,
|
|
961
|
+
40,
|
|
962
|
+
41,
|
|
963
|
+
42,
|
|
964
|
+
43,
|
|
965
|
+
44,
|
|
966
|
+
45,
|
|
967
|
+
46,
|
|
968
|
+
92,
|
|
969
|
+
// '/' -> '\'
|
|
970
|
+
48,
|
|
971
|
+
49,
|
|
972
|
+
50,
|
|
973
|
+
51,
|
|
974
|
+
52,
|
|
975
|
+
53,
|
|
976
|
+
54,
|
|
977
|
+
55,
|
|
978
|
+
56,
|
|
979
|
+
57,
|
|
980
|
+
58,
|
|
981
|
+
59,
|
|
982
|
+
60,
|
|
983
|
+
61,
|
|
984
|
+
62,
|
|
985
|
+
63,
|
|
986
|
+
64,
|
|
987
|
+
65,
|
|
988
|
+
66,
|
|
989
|
+
67,
|
|
990
|
+
68,
|
|
991
|
+
69,
|
|
992
|
+
70,
|
|
993
|
+
71,
|
|
994
|
+
72,
|
|
995
|
+
73,
|
|
996
|
+
74,
|
|
997
|
+
75,
|
|
998
|
+
76,
|
|
999
|
+
77,
|
|
1000
|
+
78,
|
|
1001
|
+
79,
|
|
1002
|
+
80,
|
|
1003
|
+
81,
|
|
1004
|
+
82,
|
|
1005
|
+
83,
|
|
1006
|
+
84,
|
|
1007
|
+
85,
|
|
1008
|
+
86,
|
|
1009
|
+
87,
|
|
1010
|
+
88,
|
|
1011
|
+
89,
|
|
1012
|
+
90,
|
|
1013
|
+
91,
|
|
1014
|
+
92,
|
|
1015
|
+
93,
|
|
1016
|
+
94,
|
|
1017
|
+
95,
|
|
1018
|
+
96,
|
|
1019
|
+
65,
|
|
1020
|
+
66,
|
|
1021
|
+
67,
|
|
1022
|
+
68,
|
|
1023
|
+
69,
|
|
1024
|
+
70,
|
|
1025
|
+
71,
|
|
1026
|
+
72,
|
|
1027
|
+
73,
|
|
1028
|
+
74,
|
|
1029
|
+
75,
|
|
1030
|
+
76,
|
|
1031
|
+
77,
|
|
1032
|
+
78,
|
|
1033
|
+
79,
|
|
1034
|
+
80,
|
|
1035
|
+
81,
|
|
1036
|
+
82,
|
|
1037
|
+
83,
|
|
1038
|
+
84,
|
|
1039
|
+
85,
|
|
1040
|
+
86,
|
|
1041
|
+
87,
|
|
1042
|
+
88,
|
|
1043
|
+
89,
|
|
1044
|
+
90,
|
|
1045
|
+
123,
|
|
1046
|
+
124,
|
|
1047
|
+
125,
|
|
1048
|
+
126,
|
|
1049
|
+
127,
|
|
1050
|
+
128,
|
|
1051
|
+
129,
|
|
1052
|
+
130,
|
|
1053
|
+
131,
|
|
1054
|
+
132,
|
|
1055
|
+
133,
|
|
1056
|
+
134,
|
|
1057
|
+
135,
|
|
1058
|
+
136,
|
|
1059
|
+
137,
|
|
1060
|
+
138,
|
|
1061
|
+
139,
|
|
1062
|
+
140,
|
|
1063
|
+
141,
|
|
1064
|
+
142,
|
|
1065
|
+
143,
|
|
1066
|
+
144,
|
|
1067
|
+
145,
|
|
1068
|
+
146,
|
|
1069
|
+
147,
|
|
1070
|
+
148,
|
|
1071
|
+
149,
|
|
1072
|
+
150,
|
|
1073
|
+
151,
|
|
1074
|
+
152,
|
|
1075
|
+
153,
|
|
1076
|
+
154,
|
|
1077
|
+
155,
|
|
1078
|
+
156,
|
|
1079
|
+
157,
|
|
1080
|
+
158,
|
|
1081
|
+
159,
|
|
1082
|
+
160,
|
|
1083
|
+
161,
|
|
1084
|
+
162,
|
|
1085
|
+
163,
|
|
1086
|
+
164,
|
|
1087
|
+
165,
|
|
1088
|
+
166,
|
|
1089
|
+
167,
|
|
1090
|
+
168,
|
|
1091
|
+
169,
|
|
1092
|
+
170,
|
|
1093
|
+
171,
|
|
1094
|
+
172,
|
|
1095
|
+
173,
|
|
1096
|
+
174,
|
|
1097
|
+
175,
|
|
1098
|
+
176,
|
|
1099
|
+
177,
|
|
1100
|
+
178,
|
|
1101
|
+
179,
|
|
1102
|
+
180,
|
|
1103
|
+
181,
|
|
1104
|
+
182,
|
|
1105
|
+
183,
|
|
1106
|
+
184,
|
|
1107
|
+
185,
|
|
1108
|
+
186,
|
|
1109
|
+
187,
|
|
1110
|
+
188,
|
|
1111
|
+
189,
|
|
1112
|
+
190,
|
|
1113
|
+
191,
|
|
1114
|
+
192,
|
|
1115
|
+
193,
|
|
1116
|
+
194,
|
|
1117
|
+
195,
|
|
1118
|
+
196,
|
|
1119
|
+
197,
|
|
1120
|
+
198,
|
|
1121
|
+
199,
|
|
1122
|
+
200,
|
|
1123
|
+
201,
|
|
1124
|
+
202,
|
|
1125
|
+
203,
|
|
1126
|
+
204,
|
|
1127
|
+
205,
|
|
1128
|
+
206,
|
|
1129
|
+
207,
|
|
1130
|
+
208,
|
|
1131
|
+
209,
|
|
1132
|
+
210,
|
|
1133
|
+
211,
|
|
1134
|
+
212,
|
|
1135
|
+
213,
|
|
1136
|
+
214,
|
|
1137
|
+
215,
|
|
1138
|
+
216,
|
|
1139
|
+
217,
|
|
1140
|
+
218,
|
|
1141
|
+
219,
|
|
1142
|
+
220,
|
|
1143
|
+
221,
|
|
1144
|
+
222,
|
|
1145
|
+
223,
|
|
1146
|
+
224,
|
|
1147
|
+
225,
|
|
1148
|
+
226,
|
|
1149
|
+
227,
|
|
1150
|
+
228,
|
|
1151
|
+
229,
|
|
1152
|
+
230,
|
|
1153
|
+
231,
|
|
1154
|
+
232,
|
|
1155
|
+
233,
|
|
1156
|
+
234,
|
|
1157
|
+
235,
|
|
1158
|
+
236,
|
|
1159
|
+
237,
|
|
1160
|
+
238,
|
|
1161
|
+
239,
|
|
1162
|
+
240,
|
|
1163
|
+
241,
|
|
1164
|
+
242,
|
|
1165
|
+
243,
|
|
1166
|
+
244,
|
|
1167
|
+
245,
|
|
1168
|
+
246,
|
|
1169
|
+
247,
|
|
1170
|
+
248,
|
|
1171
|
+
249,
|
|
1172
|
+
250,
|
|
1173
|
+
251,
|
|
1174
|
+
252,
|
|
1175
|
+
253,
|
|
1176
|
+
254,
|
|
1177
|
+
255
|
|
1178
|
+
]);
|
|
1179
|
+
function hashString(str, hashType) {
|
|
1180
|
+
const buffer = getStormBuffer();
|
|
1181
|
+
let seed1 = 2146271213;
|
|
1182
|
+
let seed2 = 4008636142;
|
|
1183
|
+
for (let i = 0; i < str.length; i++) {
|
|
1184
|
+
const ch = AsciiToUpperTable[str.charCodeAt(i) & 255];
|
|
1185
|
+
seed1 = (buffer[hashType + ch] ^ seed1 + seed2 >>> 0) >>> 0;
|
|
1186
|
+
seed2 = (ch + seed1 + seed2 + (seed2 << 5) + 3 & 4294967295) >>> 0;
|
|
1187
|
+
}
|
|
1188
|
+
return seed1 >>> 0;
|
|
1189
|
+
}
|
|
1190
|
+
function hashTableIndex(fileName) {
|
|
1191
|
+
return hashString(fileName, MPQ_HASH_TABLE_INDEX);
|
|
1192
|
+
}
|
|
1193
|
+
function hashNameA(fileName) {
|
|
1194
|
+
return hashString(fileName, MPQ_HASH_NAME_A);
|
|
1195
|
+
}
|
|
1196
|
+
function hashNameB(fileName) {
|
|
1197
|
+
return hashString(fileName, MPQ_HASH_NAME_B);
|
|
1198
|
+
}
|
|
1199
|
+
function hashFileKey(fileName) {
|
|
1200
|
+
return hashString(fileName, MPQ_HASH_FILE_KEY);
|
|
1201
|
+
}
|
|
1202
|
+
function jenkinsHash(str) {
|
|
1203
|
+
let a = 3735928559 + str.length;
|
|
1204
|
+
let b = a;
|
|
1205
|
+
let c = a;
|
|
1206
|
+
let i = 0;
|
|
1207
|
+
while (i + 12 <= str.length) {
|
|
1208
|
+
a = a + str.charCodeAt(i) >>> 0;
|
|
1209
|
+
a = a + (str.charCodeAt(i + 1) << 8) >>> 0;
|
|
1210
|
+
a = a + (str.charCodeAt(i + 2) << 16) >>> 0;
|
|
1211
|
+
a = a + (str.charCodeAt(i + 3) << 24 >>> 0) >>> 0;
|
|
1212
|
+
b = b + str.charCodeAt(i + 4) >>> 0;
|
|
1213
|
+
b = b + (str.charCodeAt(i + 5) << 8) >>> 0;
|
|
1214
|
+
b = b + (str.charCodeAt(i + 6) << 16) >>> 0;
|
|
1215
|
+
b = b + (str.charCodeAt(i + 7) << 24 >>> 0) >>> 0;
|
|
1216
|
+
c = c + str.charCodeAt(i + 8) >>> 0;
|
|
1217
|
+
c = c + (str.charCodeAt(i + 9) << 8) >>> 0;
|
|
1218
|
+
c = c + (str.charCodeAt(i + 10) << 16) >>> 0;
|
|
1219
|
+
c = c + (str.charCodeAt(i + 11) << 24 >>> 0) >>> 0;
|
|
1220
|
+
a = a - c >>> 0;
|
|
1221
|
+
a = (a ^ (c << 4 | c >>> 28)) >>> 0;
|
|
1222
|
+
c = c + b >>> 0;
|
|
1223
|
+
b = b - a >>> 0;
|
|
1224
|
+
b = (b ^ (a << 6 | a >>> 26)) >>> 0;
|
|
1225
|
+
a = a + c >>> 0;
|
|
1226
|
+
c = c - b >>> 0;
|
|
1227
|
+
c = (c ^ (b << 8 | b >>> 24)) >>> 0;
|
|
1228
|
+
b = b + a >>> 0;
|
|
1229
|
+
a = a - c >>> 0;
|
|
1230
|
+
a = (a ^ (c << 16 | c >>> 16)) >>> 0;
|
|
1231
|
+
c = c + b >>> 0;
|
|
1232
|
+
b = b - a >>> 0;
|
|
1233
|
+
b = (b ^ (a << 19 | a >>> 13)) >>> 0;
|
|
1234
|
+
a = a + c >>> 0;
|
|
1235
|
+
c = c - b >>> 0;
|
|
1236
|
+
c = (c ^ (b << 4 | b >>> 28)) >>> 0;
|
|
1237
|
+
b = b + a >>> 0;
|
|
1238
|
+
i += 12;
|
|
1239
|
+
}
|
|
1240
|
+
const remaining = str.length - i;
|
|
1241
|
+
switch (remaining) {
|
|
1242
|
+
case 12:
|
|
1243
|
+
c = c + (str.charCodeAt(i + 11) << 24 >>> 0) >>> 0;
|
|
1244
|
+
// fallthrough
|
|
1245
|
+
case 11:
|
|
1246
|
+
c = c + (str.charCodeAt(i + 10) << 16) >>> 0;
|
|
1247
|
+
case 10:
|
|
1248
|
+
c = c + (str.charCodeAt(i + 9) << 8) >>> 0;
|
|
1249
|
+
case 9:
|
|
1250
|
+
c = c + str.charCodeAt(i + 8) >>> 0;
|
|
1251
|
+
case 8:
|
|
1252
|
+
b = b + (str.charCodeAt(i + 7) << 24 >>> 0) >>> 0;
|
|
1253
|
+
case 7:
|
|
1254
|
+
b = b + (str.charCodeAt(i + 6) << 16) >>> 0;
|
|
1255
|
+
case 6:
|
|
1256
|
+
b = b + (str.charCodeAt(i + 5) << 8) >>> 0;
|
|
1257
|
+
case 5:
|
|
1258
|
+
b = b + str.charCodeAt(i + 4) >>> 0;
|
|
1259
|
+
case 4:
|
|
1260
|
+
a = a + (str.charCodeAt(i + 3) << 24 >>> 0) >>> 0;
|
|
1261
|
+
case 3:
|
|
1262
|
+
a = a + (str.charCodeAt(i + 2) << 16) >>> 0;
|
|
1263
|
+
case 2:
|
|
1264
|
+
a = a + (str.charCodeAt(i + 1) << 8) >>> 0;
|
|
1265
|
+
case 1:
|
|
1266
|
+
a = a + str.charCodeAt(i) >>> 0;
|
|
1267
|
+
break;
|
|
1268
|
+
case 0:
|
|
1269
|
+
return BigInt(c >>> 0) << 32n | BigInt(b >>> 0);
|
|
1270
|
+
}
|
|
1271
|
+
c = (c ^ b) >>> 0;
|
|
1272
|
+
c = c - (b << 14 | b >>> 18) >>> 0;
|
|
1273
|
+
a = (a ^ c) >>> 0;
|
|
1274
|
+
a = a - (c << 11 | c >>> 21) >>> 0;
|
|
1275
|
+
b = (b ^ a) >>> 0;
|
|
1276
|
+
b = b - (a << 25 | a >>> 7) >>> 0;
|
|
1277
|
+
c = (c ^ b) >>> 0;
|
|
1278
|
+
c = c - (b << 16 | b >>> 16) >>> 0;
|
|
1279
|
+
a = (a ^ c) >>> 0;
|
|
1280
|
+
a = a - (c << 4 | c >>> 28) >>> 0;
|
|
1281
|
+
b = (b ^ a) >>> 0;
|
|
1282
|
+
b = b - (a << 14 | a >>> 18) >>> 0;
|
|
1283
|
+
c = (c ^ b) >>> 0;
|
|
1284
|
+
c = c - (b << 24 | b >>> 8) >>> 0;
|
|
1285
|
+
return BigInt(c >>> 0) << 32n | BigInt(b >>> 0);
|
|
1286
|
+
}
|
|
1287
|
+
function getPlainFileName(filePath) {
|
|
1288
|
+
let plainStart = 0;
|
|
1289
|
+
for (let i = 0; i < filePath.length; i++) {
|
|
1290
|
+
const ch = filePath.charCodeAt(i);
|
|
1291
|
+
if (ch === 92 || ch === 47) {
|
|
1292
|
+
plainStart = i + 1;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
return filePath.substring(plainStart);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// src/crypto/cipher.ts
|
|
1299
|
+
function decryptBlock(data, key) {
|
|
1300
|
+
const buffer = getStormBuffer();
|
|
1301
|
+
const dwCount = data.length >>> 2;
|
|
1302
|
+
let dwKey1 = key >>> 0;
|
|
1303
|
+
let dwKey2 = 4008636142;
|
|
1304
|
+
for (let i = 0; i < dwCount; i++) {
|
|
1305
|
+
dwKey2 = (dwKey2 + buffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 255)] & 4294967295) >>> 0;
|
|
1306
|
+
const encrypted = data.readUInt32LE(i * 4);
|
|
1307
|
+
const decrypted = (encrypted ^ dwKey1 + dwKey2 >>> 0) >>> 0;
|
|
1308
|
+
data.writeUInt32LE(decrypted, i * 4);
|
|
1309
|
+
dwKey1 = ((~dwKey1 << 21) + 286331153 | dwKey1 >>> 11) >>> 0;
|
|
1310
|
+
dwKey2 = (decrypted + dwKey2 + (dwKey2 << 5) + 3 & 4294967295) >>> 0;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
function encryptBlock(data, key) {
|
|
1314
|
+
const buffer = getStormBuffer();
|
|
1315
|
+
const dwCount = data.length >>> 2;
|
|
1316
|
+
let dwKey1 = key >>> 0;
|
|
1317
|
+
let dwKey2 = 4008636142;
|
|
1318
|
+
for (let i = 0; i < dwCount; i++) {
|
|
1319
|
+
dwKey2 = (dwKey2 + buffer[MPQ_HASH_KEY2_MIX + (dwKey1 & 255)] & 4294967295) >>> 0;
|
|
1320
|
+
const plaintext = data.readUInt32LE(i * 4);
|
|
1321
|
+
const encrypted = (plaintext ^ dwKey1 + dwKey2 >>> 0) >>> 0;
|
|
1322
|
+
data.writeUInt32LE(encrypted, i * 4);
|
|
1323
|
+
dwKey1 = ((~dwKey1 << 21) + 286331153 | dwKey1 >>> 11) >>> 0;
|
|
1324
|
+
dwKey2 = (plaintext + dwKey2 + (dwKey2 << 5) + 3 & 4294967295) >>> 0;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
function decryptFileKey(fileName, byteOffset, fileSize, flags) {
|
|
1328
|
+
const plainName = getPlainFileName(fileName);
|
|
1329
|
+
let key = hashString(plainName, MPQ_HASH_FILE_KEY);
|
|
1330
|
+
if (flags & MPQ_FILE_KEY_V2) {
|
|
1331
|
+
key = (key + Number(byteOffset & 0xFFFFFFFFn) ^ fileSize) >>> 0;
|
|
1332
|
+
}
|
|
1333
|
+
return key >>> 0;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// src/tables/hash-table.ts
|
|
1337
|
+
function loadHashTable(stream, offset, count) {
|
|
1338
|
+
const dataSize = count * 16;
|
|
1339
|
+
const data = stream.read(offset, dataSize);
|
|
1340
|
+
if (data.length < dataSize) {
|
|
1341
|
+
count = Math.floor(data.length / 16);
|
|
1342
|
+
}
|
|
1343
|
+
decryptBlock(data, MPQ_KEY_HASH_TABLE);
|
|
1344
|
+
const entries = new Array(count);
|
|
1345
|
+
for (let i = 0; i < count; i++) {
|
|
1346
|
+
const off = i * 16;
|
|
1347
|
+
entries[i] = {
|
|
1348
|
+
dwName1: data.readUInt32LE(off + 0),
|
|
1349
|
+
dwName2: data.readUInt32LE(off + 4),
|
|
1350
|
+
lcLocale: data.readUInt16LE(off + 8),
|
|
1351
|
+
platform: data[off + 10],
|
|
1352
|
+
dwBlockIndex: data.readUInt32LE(off + 12)
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
return entries;
|
|
1356
|
+
}
|
|
1357
|
+
function findHashEntry(hashTable, fileName, locale, maxBlockIndex) {
|
|
1358
|
+
if (hashTable.length === 0) return null;
|
|
1359
|
+
const tableSize = hashTable.length;
|
|
1360
|
+
const mask = tableSize - 1;
|
|
1361
|
+
const startIndex = hashString(fileName, MPQ_HASH_TABLE_INDEX) & mask;
|
|
1362
|
+
const hashCheck1 = hashString(fileName, MPQ_HASH_NAME_A);
|
|
1363
|
+
const hashCheck2 = hashString(fileName, MPQ_HASH_NAME_B);
|
|
1364
|
+
let index = startIndex;
|
|
1365
|
+
for (; ; ) {
|
|
1366
|
+
const entry = hashTable[index];
|
|
1367
|
+
if (entry.dwName1 === hashCheck1 && entry.dwName2 === hashCheck2 && (entry.dwBlockIndex & BLOCK_INDEX_MASK) < maxBlockIndex) {
|
|
1368
|
+
if (locale === 0 || entry.lcLocale === locale || entry.lcLocale === 0) {
|
|
1369
|
+
return entry;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
if (entry.dwBlockIndex === HASH_ENTRY_FREE) {
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
index = index + 1 & mask;
|
|
1376
|
+
if (index === startIndex) {
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// src/tables/block-table.ts
|
|
1383
|
+
function loadBlockTable(stream, offset, count) {
|
|
1384
|
+
const dataSize = count * 16;
|
|
1385
|
+
const data = stream.read(offset, dataSize);
|
|
1386
|
+
if (data.length < dataSize) {
|
|
1387
|
+
count = Math.floor(data.length / 16);
|
|
1388
|
+
}
|
|
1389
|
+
decryptBlock(data, MPQ_KEY_BLOCK_TABLE);
|
|
1390
|
+
const entries = new Array(count);
|
|
1391
|
+
for (let i = 0; i < count; i++) {
|
|
1392
|
+
const off = i * 16;
|
|
1393
|
+
entries[i] = {
|
|
1394
|
+
dwFilePos: data.readUInt32LE(off + 0),
|
|
1395
|
+
dwCSize: data.readUInt32LE(off + 4),
|
|
1396
|
+
dwFSize: data.readUInt32LE(off + 8),
|
|
1397
|
+
dwFlags: data.readUInt32LE(off + 12)
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
return entries;
|
|
1401
|
+
}
|
|
1402
|
+
function loadHiBlockTable(stream, offset, count) {
|
|
1403
|
+
const dataSize = count * 2;
|
|
1404
|
+
const data = stream.read(offset, dataSize);
|
|
1405
|
+
const hiOffsets = new Uint16Array(count);
|
|
1406
|
+
const actualCount = Math.min(count, Math.floor(data.length / 2));
|
|
1407
|
+
for (let i = 0; i < actualCount; i++) {
|
|
1408
|
+
hiOffsets[i] = data.readUInt16LE(i * 2);
|
|
1409
|
+
}
|
|
1410
|
+
return hiOffsets;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// src/tables/het-table.ts
|
|
1414
|
+
function readBits(data, bitOffset, numBits) {
|
|
1415
|
+
let result = 0;
|
|
1416
|
+
for (let i = 0; i < numBits; i++) {
|
|
1417
|
+
const byteIdx = bitOffset + i >>> 3;
|
|
1418
|
+
const bitIdx = bitOffset + i & 7;
|
|
1419
|
+
if (byteIdx < data.length && data[byteIdx] & 1 << bitIdx) {
|
|
1420
|
+
result |= 1 << i;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
return result;
|
|
1424
|
+
}
|
|
1425
|
+
function loadHetTable(stream, offset, compressedSize) {
|
|
1426
|
+
if (offset === 0n) return null;
|
|
1427
|
+
const extHeaderBuf = stream.read(offset, 12);
|
|
1428
|
+
if (extHeaderBuf.length < 12) return null;
|
|
1429
|
+
const signature = extHeaderBuf.readUInt32LE(0);
|
|
1430
|
+
if (signature !== HET_TABLE_SIGNATURE) return null;
|
|
1431
|
+
const version = extHeaderBuf.readUInt32LE(4);
|
|
1432
|
+
const dataSize = extHeaderBuf.readUInt32LE(8);
|
|
1433
|
+
const dataBuf = stream.read(offset + 12n, dataSize);
|
|
1434
|
+
if (dataBuf.length < dataSize) return null;
|
|
1435
|
+
let pos = 0;
|
|
1436
|
+
const hashTableSize = dataBuf.readUInt32LE(pos);
|
|
1437
|
+
pos += 4;
|
|
1438
|
+
const maxFileCount = dataBuf.readUInt32LE(pos);
|
|
1439
|
+
pos += 4;
|
|
1440
|
+
const hetHashTableSize = dataBuf.readUInt32LE(pos);
|
|
1441
|
+
pos += 4;
|
|
1442
|
+
const hashEntrySize = dataBuf.readUInt32LE(pos);
|
|
1443
|
+
pos += 4;
|
|
1444
|
+
const totalIndexSize = dataBuf.readUInt32LE(pos);
|
|
1445
|
+
pos += 4;
|
|
1446
|
+
const indexSizeExtra = dataBuf.readUInt32LE(pos);
|
|
1447
|
+
pos += 4;
|
|
1448
|
+
const indexSize = dataBuf.readUInt32LE(pos);
|
|
1449
|
+
pos += 4;
|
|
1450
|
+
const blockTableSize = dataBuf.readUInt32LE(pos);
|
|
1451
|
+
pos += 4;
|
|
1452
|
+
const hetHashes = new Uint8Array(dataBuf.subarray(pos, pos + hetHashTableSize));
|
|
1453
|
+
pos += hetHashTableSize;
|
|
1454
|
+
const betIndexBits = new Uint8Array(dataBuf.subarray(pos));
|
|
1455
|
+
const betIndices = new Array(hetHashTableSize);
|
|
1456
|
+
for (let i = 0; i < hetHashTableSize; i++) {
|
|
1457
|
+
betIndices[i] = readBits(betIndexBits, i * totalIndexSize, totalIndexSize);
|
|
1458
|
+
}
|
|
1459
|
+
return {
|
|
1460
|
+
hashTableSize: hetHashTableSize,
|
|
1461
|
+
maxFileCount,
|
|
1462
|
+
hashEntrySize,
|
|
1463
|
+
totalIndexSize,
|
|
1464
|
+
indexSizeExtra,
|
|
1465
|
+
indexSize,
|
|
1466
|
+
hetHashes,
|
|
1467
|
+
betIndices
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
function findInHetTable(het, fileName) {
|
|
1471
|
+
if (!het || het.hashTableSize === 0) return -1;
|
|
1472
|
+
const fullHash = jenkinsHash(fileName);
|
|
1473
|
+
const hashByte = Number(fullHash >> 56n & 0xFFn) | 128;
|
|
1474
|
+
const startIndex = Number(fullHash % BigInt(het.hashTableSize));
|
|
1475
|
+
let index = startIndex;
|
|
1476
|
+
for (; ; ) {
|
|
1477
|
+
const hetHash = het.hetHashes[index];
|
|
1478
|
+
if (hetHash === HET_ENTRY_FREE) {
|
|
1479
|
+
return -1;
|
|
1480
|
+
}
|
|
1481
|
+
if (hetHash === hashByte) {
|
|
1482
|
+
return het.betIndices[index];
|
|
1483
|
+
}
|
|
1484
|
+
index = (index + 1) % het.hashTableSize;
|
|
1485
|
+
if (index === startIndex) return -1;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// src/tables/bet-table.ts
|
|
1490
|
+
function readBits2(data, bitOffset, numBits) {
|
|
1491
|
+
let result = 0;
|
|
1492
|
+
for (let i = 0; i < numBits; i++) {
|
|
1493
|
+
const byteIdx = bitOffset + i >>> 3;
|
|
1494
|
+
const bitIdx = bitOffset + i & 7;
|
|
1495
|
+
if (byteIdx < data.length && data[byteIdx] & 1 << bitIdx) {
|
|
1496
|
+
result |= 1 << i;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return result;
|
|
1500
|
+
}
|
|
1501
|
+
function readBits64(data, bitOffset, numBits) {
|
|
1502
|
+
let result = 0n;
|
|
1503
|
+
for (let i = 0; i < numBits; i++) {
|
|
1504
|
+
const byteIdx = bitOffset + i >>> 3;
|
|
1505
|
+
const bitIdx = bitOffset + i & 7;
|
|
1506
|
+
if (byteIdx < data.length && data[byteIdx] & 1 << bitIdx) {
|
|
1507
|
+
result |= 1n << BigInt(i);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
return result;
|
|
1511
|
+
}
|
|
1512
|
+
function loadBetTable(stream, offset, compressedSize) {
|
|
1513
|
+
if (offset === 0n) return null;
|
|
1514
|
+
const extHeaderBuf = stream.read(offset, 12);
|
|
1515
|
+
if (extHeaderBuf.length < 12) return null;
|
|
1516
|
+
const signature = extHeaderBuf.readUInt32LE(0);
|
|
1517
|
+
if (signature !== BET_TABLE_SIGNATURE) return null;
|
|
1518
|
+
const version = extHeaderBuf.readUInt32LE(4);
|
|
1519
|
+
const dataSize = extHeaderBuf.readUInt32LE(8);
|
|
1520
|
+
const dataBuf = stream.read(offset + 12n, dataSize);
|
|
1521
|
+
if (dataBuf.length < dataSize) return null;
|
|
1522
|
+
let pos = 0;
|
|
1523
|
+
const tableSize = dataBuf.readUInt32LE(pos);
|
|
1524
|
+
pos += 4;
|
|
1525
|
+
const maxFileCount = dataBuf.readUInt32LE(pos);
|
|
1526
|
+
pos += 4;
|
|
1527
|
+
const flagCount = dataBuf.readUInt32LE(pos);
|
|
1528
|
+
pos += 4;
|
|
1529
|
+
const filePosBits = dataBuf.readUInt32LE(pos);
|
|
1530
|
+
pos += 4;
|
|
1531
|
+
const fileSizeBits = dataBuf.readUInt32LE(pos);
|
|
1532
|
+
pos += 4;
|
|
1533
|
+
const cmpSizeBits = dataBuf.readUInt32LE(pos);
|
|
1534
|
+
pos += 4;
|
|
1535
|
+
const flagIndexBits = dataBuf.readUInt32LE(pos);
|
|
1536
|
+
pos += 4;
|
|
1537
|
+
const unknownBits = dataBuf.readUInt32LE(pos);
|
|
1538
|
+
pos += 4;
|
|
1539
|
+
const hashSizeBits = dataBuf.readUInt32LE(pos);
|
|
1540
|
+
pos += 4;
|
|
1541
|
+
const hashSizeTotal = dataBuf.readUInt32LE(pos);
|
|
1542
|
+
pos += 4;
|
|
1543
|
+
const hashSizeExtra = dataBuf.readUInt32LE(pos);
|
|
1544
|
+
pos += 4;
|
|
1545
|
+
const hashArraySize = dataBuf.readUInt32LE(pos);
|
|
1546
|
+
pos += 4;
|
|
1547
|
+
const flags = [];
|
|
1548
|
+
for (let i = 0; i < flagCount; i++) {
|
|
1549
|
+
flags.push(dataBuf.readUInt32LE(pos));
|
|
1550
|
+
pos += 4;
|
|
1551
|
+
}
|
|
1552
|
+
const bitsPerEntry = filePosBits + fileSizeBits + cmpSizeBits + flagIndexBits + unknownBits;
|
|
1553
|
+
const entryData = new Uint8Array(dataBuf.subarray(pos));
|
|
1554
|
+
const entries = [];
|
|
1555
|
+
for (let i = 0; i < maxFileCount; i++) {
|
|
1556
|
+
let bitOff = i * bitsPerEntry;
|
|
1557
|
+
const byteOffset = readBits64(entryData, bitOff, filePosBits);
|
|
1558
|
+
bitOff += filePosBits;
|
|
1559
|
+
const fileSize = readBits2(entryData, bitOff, fileSizeBits);
|
|
1560
|
+
bitOff += fileSizeBits;
|
|
1561
|
+
const cmpSize = readBits2(entryData, bitOff, cmpSizeBits);
|
|
1562
|
+
bitOff += cmpSizeBits;
|
|
1563
|
+
const flagIndex = readBits2(entryData, bitOff, flagIndexBits);
|
|
1564
|
+
bitOff += flagIndexBits;
|
|
1565
|
+
const hashBitOffset = maxFileCount * bitsPerEntry + i * hashSizeTotal;
|
|
1566
|
+
const fileNameHash = readBits64(entryData, hashBitOffset, hashSizeTotal);
|
|
1567
|
+
entries.push({
|
|
1568
|
+
fileNameHash,
|
|
1569
|
+
byteOffset,
|
|
1570
|
+
fileSize,
|
|
1571
|
+
cmpSize,
|
|
1572
|
+
flagIndex
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
return { maxFileCount, flagCount, flags, entries };
|
|
1576
|
+
}
|
|
1577
|
+
function getBetEntry(bet, index) {
|
|
1578
|
+
if (index < 0 || index >= bet.entries.length) return null;
|
|
1579
|
+
const entry = bet.entries[index];
|
|
1580
|
+
const flags = entry.flagIndex < bet.flags.length ? bet.flags[entry.flagIndex] : 0;
|
|
1581
|
+
return {
|
|
1582
|
+
fileNameHash: entry.fileNameHash,
|
|
1583
|
+
byteOffset: entry.byteOffset,
|
|
1584
|
+
fileTime: 0n,
|
|
1585
|
+
fileSize: entry.fileSize,
|
|
1586
|
+
cmpSize: entry.cmpSize,
|
|
1587
|
+
flags,
|
|
1588
|
+
crc32: 0,
|
|
1589
|
+
md5: new Uint8Array(16),
|
|
1590
|
+
fileName: ""
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// src/tables/file-table.ts
|
|
1595
|
+
function buildFileTable(hashTable, blockTable, hiBlockTable) {
|
|
1596
|
+
const fileCount = blockTable.length;
|
|
1597
|
+
const entries = new Array(fileCount);
|
|
1598
|
+
for (let i = 0; i < fileCount; i++) {
|
|
1599
|
+
const block = blockTable[i];
|
|
1600
|
+
let byteOffset = BigInt(block.dwFilePos);
|
|
1601
|
+
if (hiBlockTable && i < hiBlockTable.length) {
|
|
1602
|
+
byteOffset |= BigInt(hiBlockTable[i]) << 32n;
|
|
1603
|
+
}
|
|
1604
|
+
entries[i] = {
|
|
1605
|
+
fileNameHash: 0n,
|
|
1606
|
+
byteOffset,
|
|
1607
|
+
fileTime: 0n,
|
|
1608
|
+
fileSize: block.dwFSize,
|
|
1609
|
+
cmpSize: block.dwCSize,
|
|
1610
|
+
flags: block.dwFlags,
|
|
1611
|
+
crc32: 0,
|
|
1612
|
+
md5: new Uint8Array(16),
|
|
1613
|
+
fileName: ""
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
return entries;
|
|
1617
|
+
}
|
|
1618
|
+
function buildFileTableFromHetBet(het, bet) {
|
|
1619
|
+
const entries = [];
|
|
1620
|
+
for (let i = 0; i < bet.entries.length; i++) {
|
|
1621
|
+
const entry = getBetEntry(bet, i);
|
|
1622
|
+
if (entry) {
|
|
1623
|
+
entries.push(entry);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return entries;
|
|
1627
|
+
}
|
|
1628
|
+
function applyFileNames(fileEntries, hashTable, names) {
|
|
1629
|
+
const tableSize = hashTable.length;
|
|
1630
|
+
if (tableSize === 0) return;
|
|
1631
|
+
const mask = tableSize - 1;
|
|
1632
|
+
for (const name of names) {
|
|
1633
|
+
if (!name) continue;
|
|
1634
|
+
const startIndex = hashString(name, MPQ_HASH_TABLE_INDEX) & mask;
|
|
1635
|
+
const check1 = hashString(name, MPQ_HASH_NAME_A);
|
|
1636
|
+
const check2 = hashString(name, MPQ_HASH_NAME_B);
|
|
1637
|
+
let index = startIndex;
|
|
1638
|
+
for (; ; ) {
|
|
1639
|
+
const entry = hashTable[index];
|
|
1640
|
+
if (entry.dwName1 === check1 && entry.dwName2 === check2 && (entry.dwBlockIndex & BLOCK_INDEX_MASK) < fileEntries.length) {
|
|
1641
|
+
const blockIndex = entry.dwBlockIndex & BLOCK_INDEX_MASK;
|
|
1642
|
+
if (!fileEntries[blockIndex].fileName) {
|
|
1643
|
+
fileEntries[blockIndex].fileName = name;
|
|
1644
|
+
}
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
if (entry.dwBlockIndex === HASH_ENTRY_FREE) break;
|
|
1648
|
+
index = index + 1 & mask;
|
|
1649
|
+
if (index === startIndex) break;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// src/archive/listfile.ts
|
|
1655
|
+
function parseListfile(data) {
|
|
1656
|
+
const text = data.toString("utf8");
|
|
1657
|
+
const names = [];
|
|
1658
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1659
|
+
const trimmed = line.trim();
|
|
1660
|
+
if (trimmed.length > 0) {
|
|
1661
|
+
names.push(trimmed);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return names;
|
|
1665
|
+
}
|
|
1666
|
+
function parseAttributes(data, fileCount) {
|
|
1667
|
+
if (data.length < 8) return null;
|
|
1668
|
+
const version = data.readUInt32LE(0);
|
|
1669
|
+
const flags = data.readUInt32LE(4);
|
|
1670
|
+
if (version !== 100) return null;
|
|
1671
|
+
const result = {
|
|
1672
|
+
crc32s: [],
|
|
1673
|
+
fileTimes: [],
|
|
1674
|
+
md5s: []
|
|
1675
|
+
};
|
|
1676
|
+
let offset = 8;
|
|
1677
|
+
if (flags & 1) {
|
|
1678
|
+
for (let i = 0; i < fileCount && offset + 4 <= data.length; i++) {
|
|
1679
|
+
result.crc32s.push(data.readUInt32LE(offset));
|
|
1680
|
+
offset += 4;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
if (flags & 2) {
|
|
1684
|
+
for (let i = 0; i < fileCount && offset + 8 <= data.length; i++) {
|
|
1685
|
+
result.fileTimes.push(data.readBigUInt64LE(offset));
|
|
1686
|
+
offset += 8;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
if (flags & 4) {
|
|
1690
|
+
for (let i = 0; i < fileCount && offset + 16 <= data.length; i++) {
|
|
1691
|
+
result.md5s.push(new Uint8Array(data.subarray(offset, offset + 16)));
|
|
1692
|
+
offset += 16;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
return result;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// src/compression/huffman.ts
|
|
1699
|
+
var WEIGHT_TABLES = {
|
|
1700
|
+
// Generic text compression (type 0)
|
|
1701
|
+
0: [
|
|
1702
|
+
2571,
|
|
1703
|
+
2568,
|
|
1704
|
+
2567,
|
|
1705
|
+
2566,
|
|
1706
|
+
2570,
|
|
1707
|
+
2569,
|
|
1708
|
+
2565,
|
|
1709
|
+
2564,
|
|
1710
|
+
2563,
|
|
1711
|
+
2562,
|
|
1712
|
+
2561,
|
|
1713
|
+
2560,
|
|
1714
|
+
2319,
|
|
1715
|
+
2318,
|
|
1716
|
+
2317,
|
|
1717
|
+
2316,
|
|
1718
|
+
2315,
|
|
1719
|
+
2314,
|
|
1720
|
+
2313,
|
|
1721
|
+
2312,
|
|
1722
|
+
2311,
|
|
1723
|
+
2310,
|
|
1724
|
+
2309,
|
|
1725
|
+
2308,
|
|
1726
|
+
2307,
|
|
1727
|
+
2306,
|
|
1728
|
+
2305,
|
|
1729
|
+
2304,
|
|
1730
|
+
2063,
|
|
1731
|
+
2062,
|
|
1732
|
+
2061,
|
|
1733
|
+
2060,
|
|
1734
|
+
2059,
|
|
1735
|
+
2058,
|
|
1736
|
+
2057,
|
|
1737
|
+
2056,
|
|
1738
|
+
2055,
|
|
1739
|
+
2054,
|
|
1740
|
+
2053,
|
|
1741
|
+
2052,
|
|
1742
|
+
2051,
|
|
1743
|
+
2050,
|
|
1744
|
+
2049,
|
|
1745
|
+
2048,
|
|
1746
|
+
1807,
|
|
1747
|
+
1806,
|
|
1748
|
+
1805,
|
|
1749
|
+
1804,
|
|
1750
|
+
1803,
|
|
1751
|
+
1802,
|
|
1752
|
+
1801,
|
|
1753
|
+
1800,
|
|
1754
|
+
1799,
|
|
1755
|
+
1798,
|
|
1756
|
+
1797,
|
|
1757
|
+
1796,
|
|
1758
|
+
1795,
|
|
1759
|
+
1794,
|
|
1760
|
+
1793,
|
|
1761
|
+
1792,
|
|
1762
|
+
1551,
|
|
1763
|
+
1550,
|
|
1764
|
+
1549,
|
|
1765
|
+
1548,
|
|
1766
|
+
1547,
|
|
1767
|
+
1546,
|
|
1768
|
+
1545,
|
|
1769
|
+
1544,
|
|
1770
|
+
1543,
|
|
1771
|
+
1542,
|
|
1772
|
+
1541,
|
|
1773
|
+
1540,
|
|
1774
|
+
1539,
|
|
1775
|
+
1538,
|
|
1776
|
+
1537,
|
|
1777
|
+
1536,
|
|
1778
|
+
1295,
|
|
1779
|
+
1294,
|
|
1780
|
+
1293,
|
|
1781
|
+
1292,
|
|
1782
|
+
1291,
|
|
1783
|
+
1290,
|
|
1784
|
+
1289,
|
|
1785
|
+
1288,
|
|
1786
|
+
1287,
|
|
1787
|
+
1286,
|
|
1788
|
+
1285,
|
|
1789
|
+
1284,
|
|
1790
|
+
1283,
|
|
1791
|
+
1282,
|
|
1792
|
+
1281,
|
|
1793
|
+
1280,
|
|
1794
|
+
1039,
|
|
1795
|
+
1038,
|
|
1796
|
+
1037,
|
|
1797
|
+
1036,
|
|
1798
|
+
1035,
|
|
1799
|
+
1034,
|
|
1800
|
+
1033,
|
|
1801
|
+
1032,
|
|
1802
|
+
1031,
|
|
1803
|
+
1030,
|
|
1804
|
+
1029,
|
|
1805
|
+
1028,
|
|
1806
|
+
1027,
|
|
1807
|
+
1026,
|
|
1808
|
+
1025,
|
|
1809
|
+
1024,
|
|
1810
|
+
783,
|
|
1811
|
+
782,
|
|
1812
|
+
781,
|
|
1813
|
+
780,
|
|
1814
|
+
779,
|
|
1815
|
+
778,
|
|
1816
|
+
777,
|
|
1817
|
+
776,
|
|
1818
|
+
775,
|
|
1819
|
+
774,
|
|
1820
|
+
773,
|
|
1821
|
+
772,
|
|
1822
|
+
771,
|
|
1823
|
+
770,
|
|
1824
|
+
769,
|
|
1825
|
+
768,
|
|
1826
|
+
527,
|
|
1827
|
+
526,
|
|
1828
|
+
525,
|
|
1829
|
+
524,
|
|
1830
|
+
523,
|
|
1831
|
+
522,
|
|
1832
|
+
521,
|
|
1833
|
+
520,
|
|
1834
|
+
519,
|
|
1835
|
+
518,
|
|
1836
|
+
517,
|
|
1837
|
+
516,
|
|
1838
|
+
515,
|
|
1839
|
+
514,
|
|
1840
|
+
513,
|
|
1841
|
+
512,
|
|
1842
|
+
271,
|
|
1843
|
+
270,
|
|
1844
|
+
269,
|
|
1845
|
+
268,
|
|
1846
|
+
267,
|
|
1847
|
+
266,
|
|
1848
|
+
265,
|
|
1849
|
+
264,
|
|
1850
|
+
263,
|
|
1851
|
+
262,
|
|
1852
|
+
261,
|
|
1853
|
+
260,
|
|
1854
|
+
259,
|
|
1855
|
+
258,
|
|
1856
|
+
257,
|
|
1857
|
+
256,
|
|
1858
|
+
15,
|
|
1859
|
+
14,
|
|
1860
|
+
13,
|
|
1861
|
+
12,
|
|
1862
|
+
11,
|
|
1863
|
+
10,
|
|
1864
|
+
9,
|
|
1865
|
+
8,
|
|
1866
|
+
7,
|
|
1867
|
+
6,
|
|
1868
|
+
5,
|
|
1869
|
+
4,
|
|
1870
|
+
3,
|
|
1871
|
+
2,
|
|
1872
|
+
1,
|
|
1873
|
+
0,
|
|
1874
|
+
0,
|
|
1875
|
+
0,
|
|
1876
|
+
0,
|
|
1877
|
+
0,
|
|
1878
|
+
0,
|
|
1879
|
+
0,
|
|
1880
|
+
0,
|
|
1881
|
+
0,
|
|
1882
|
+
0,
|
|
1883
|
+
0,
|
|
1884
|
+
0,
|
|
1885
|
+
0,
|
|
1886
|
+
0,
|
|
1887
|
+
0,
|
|
1888
|
+
0,
|
|
1889
|
+
0,
|
|
1890
|
+
0,
|
|
1891
|
+
0,
|
|
1892
|
+
0,
|
|
1893
|
+
0,
|
|
1894
|
+
0,
|
|
1895
|
+
0,
|
|
1896
|
+
0,
|
|
1897
|
+
0,
|
|
1898
|
+
0,
|
|
1899
|
+
0,
|
|
1900
|
+
0,
|
|
1901
|
+
0,
|
|
1902
|
+
0,
|
|
1903
|
+
0,
|
|
1904
|
+
0,
|
|
1905
|
+
0,
|
|
1906
|
+
0,
|
|
1907
|
+
0,
|
|
1908
|
+
0,
|
|
1909
|
+
0,
|
|
1910
|
+
0,
|
|
1911
|
+
0,
|
|
1912
|
+
0,
|
|
1913
|
+
0,
|
|
1914
|
+
0,
|
|
1915
|
+
0,
|
|
1916
|
+
0,
|
|
1917
|
+
0,
|
|
1918
|
+
0,
|
|
1919
|
+
0,
|
|
1920
|
+
0,
|
|
1921
|
+
0,
|
|
1922
|
+
0,
|
|
1923
|
+
0,
|
|
1924
|
+
0,
|
|
1925
|
+
0,
|
|
1926
|
+
0,
|
|
1927
|
+
0,
|
|
1928
|
+
0,
|
|
1929
|
+
0,
|
|
1930
|
+
0,
|
|
1931
|
+
0,
|
|
1932
|
+
0,
|
|
1933
|
+
0,
|
|
1934
|
+
0,
|
|
1935
|
+
0,
|
|
1936
|
+
0,
|
|
1937
|
+
0,
|
|
1938
|
+
0,
|
|
1939
|
+
0,
|
|
1940
|
+
0,
|
|
1941
|
+
0,
|
|
1942
|
+
0,
|
|
1943
|
+
0,
|
|
1944
|
+
0,
|
|
1945
|
+
0,
|
|
1946
|
+
0,
|
|
1947
|
+
0,
|
|
1948
|
+
0,
|
|
1949
|
+
0,
|
|
1950
|
+
0,
|
|
1951
|
+
0,
|
|
1952
|
+
0,
|
|
1953
|
+
0,
|
|
1954
|
+
0,
|
|
1955
|
+
0,
|
|
1956
|
+
0,
|
|
1957
|
+
2
|
|
1958
|
+
]
|
|
1959
|
+
};
|
|
1960
|
+
var BitReader = class {
|
|
1961
|
+
constructor(data) {
|
|
1962
|
+
this.bitPos = 0;
|
|
1963
|
+
this.data = data;
|
|
1964
|
+
}
|
|
1965
|
+
readBits(numBits) {
|
|
1966
|
+
let result = 0;
|
|
1967
|
+
for (let i = 0; i < numBits; i++) {
|
|
1968
|
+
const byteIndex = this.bitPos >>> 3;
|
|
1969
|
+
const bitIndex = this.bitPos & 7;
|
|
1970
|
+
if (byteIndex >= this.data.length) return result;
|
|
1971
|
+
if (this.data[byteIndex] & 1 << bitIndex) {
|
|
1972
|
+
result |= 1 << i;
|
|
1973
|
+
}
|
|
1974
|
+
this.bitPos++;
|
|
1975
|
+
}
|
|
1976
|
+
return result;
|
|
1977
|
+
}
|
|
1978
|
+
readBit() {
|
|
1979
|
+
const byteIndex = this.bitPos >>> 3;
|
|
1980
|
+
const bitIndex = this.bitPos & 7;
|
|
1981
|
+
if (byteIndex >= this.data.length) return 0;
|
|
1982
|
+
const bit = this.data[byteIndex] >>> bitIndex & 1;
|
|
1983
|
+
this.bitPos++;
|
|
1984
|
+
return bit;
|
|
1985
|
+
}
|
|
1986
|
+
get isEof() {
|
|
1987
|
+
return this.bitPos >>> 3 >= this.data.length;
|
|
1988
|
+
}
|
|
1989
|
+
};
|
|
1990
|
+
var HuffmanTree = class {
|
|
1991
|
+
constructor(compressionType) {
|
|
1992
|
+
this.nodes = [];
|
|
1993
|
+
this.root = -1;
|
|
1994
|
+
this.items256 = new Array(256).fill(-1);
|
|
1995
|
+
this.buildTree(compressionType);
|
|
1996
|
+
}
|
|
1997
|
+
buildTree(compressionType) {
|
|
1998
|
+
const weights = WEIGHT_TABLES[compressionType] || WEIGHT_TABLES[0];
|
|
1999
|
+
for (let i = 0; i < 257; i++) {
|
|
2000
|
+
this.nodes.push({
|
|
2001
|
+
weight: i < 256 ? weights ? weights[i] || 0 : 0 : 0,
|
|
2002
|
+
child0: -1,
|
|
2003
|
+
child1: -1,
|
|
2004
|
+
parent: -1,
|
|
2005
|
+
value: i
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
const leafIndices = Array.from({ length: 257 }, (_, i) => i).filter((i) => this.nodes[i].weight > 0 || i === 256);
|
|
2009
|
+
leafIndices.sort((a, b) => this.nodes[a].weight - this.nodes[b].weight);
|
|
2010
|
+
const active = [...leafIndices];
|
|
2011
|
+
while (active.length > 1) {
|
|
2012
|
+
const idx0 = active.shift();
|
|
2013
|
+
const idx1 = active.shift();
|
|
2014
|
+
const parentIdx = this.nodes.length;
|
|
2015
|
+
this.nodes.push({
|
|
2016
|
+
weight: this.nodes[idx0].weight + this.nodes[idx1].weight,
|
|
2017
|
+
child0: idx0,
|
|
2018
|
+
child1: idx1,
|
|
2019
|
+
parent: -1,
|
|
2020
|
+
value: -1
|
|
2021
|
+
});
|
|
2022
|
+
this.nodes[idx0].parent = parentIdx;
|
|
2023
|
+
this.nodes[idx1].parent = parentIdx;
|
|
2024
|
+
let insertAt = active.length;
|
|
2025
|
+
for (let j = 0; j < active.length; j++) {
|
|
2026
|
+
if (this.nodes[parentIdx].weight <= this.nodes[active[j]].weight) {
|
|
2027
|
+
insertAt = j;
|
|
2028
|
+
break;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
active.splice(insertAt, 0, parentIdx);
|
|
2032
|
+
}
|
|
2033
|
+
if (active.length > 0) {
|
|
2034
|
+
this.root = active[0];
|
|
2035
|
+
}
|
|
2036
|
+
for (let i = 0; i < 256; i++) {
|
|
2037
|
+
if (this.nodes[i].weight > 0) {
|
|
2038
|
+
this.items256[i] = i;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
decodeOne(reader) {
|
|
2043
|
+
if (this.root === -1) return -1;
|
|
2044
|
+
let current = this.root;
|
|
2045
|
+
while (this.nodes[current].value === -1) {
|
|
2046
|
+
const bit = reader.readBit();
|
|
2047
|
+
if (bit === 0) {
|
|
2048
|
+
current = this.nodes[current].child0;
|
|
2049
|
+
} else {
|
|
2050
|
+
current = this.nodes[current].child1;
|
|
2051
|
+
}
|
|
2052
|
+
if (current === -1) return -1;
|
|
2053
|
+
}
|
|
2054
|
+
return this.nodes[current].value;
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
function decompressHuffman(inBuffer, outSize) {
|
|
2058
|
+
if (inBuffer.length === 0) {
|
|
2059
|
+
return Buffer.alloc(outSize);
|
|
2060
|
+
}
|
|
2061
|
+
const compressionType = inBuffer[0];
|
|
2062
|
+
const reader = new BitReader(inBuffer.subarray(1));
|
|
2063
|
+
const tree = new HuffmanTree(compressionType);
|
|
2064
|
+
const output = Buffer.alloc(outSize);
|
|
2065
|
+
let outPos = 0;
|
|
2066
|
+
while (outPos < outSize && !reader.isEof) {
|
|
2067
|
+
const value = tree.decodeOne(reader);
|
|
2068
|
+
if (value < 0 || value === 256) break;
|
|
2069
|
+
output[outPos++] = value & 255;
|
|
2070
|
+
}
|
|
2071
|
+
return output;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
// src/compression/index.ts
|
|
2075
|
+
init_pkware();
|
|
2076
|
+
|
|
2077
|
+
// src/compression/adpcm.ts
|
|
2078
|
+
var STEP_TABLE = [
|
|
2079
|
+
7,
|
|
2080
|
+
8,
|
|
2081
|
+
9,
|
|
2082
|
+
10,
|
|
2083
|
+
11,
|
|
2084
|
+
12,
|
|
2085
|
+
13,
|
|
2086
|
+
14,
|
|
2087
|
+
16,
|
|
2088
|
+
17,
|
|
2089
|
+
19,
|
|
2090
|
+
21,
|
|
2091
|
+
23,
|
|
2092
|
+
25,
|
|
2093
|
+
28,
|
|
2094
|
+
31,
|
|
2095
|
+
34,
|
|
2096
|
+
37,
|
|
2097
|
+
41,
|
|
2098
|
+
45,
|
|
2099
|
+
50,
|
|
2100
|
+
55,
|
|
2101
|
+
60,
|
|
2102
|
+
66,
|
|
2103
|
+
73,
|
|
2104
|
+
80,
|
|
2105
|
+
88,
|
|
2106
|
+
97,
|
|
2107
|
+
107,
|
|
2108
|
+
118,
|
|
2109
|
+
130,
|
|
2110
|
+
143,
|
|
2111
|
+
157,
|
|
2112
|
+
173,
|
|
2113
|
+
190,
|
|
2114
|
+
209,
|
|
2115
|
+
230,
|
|
2116
|
+
253,
|
|
2117
|
+
279,
|
|
2118
|
+
307,
|
|
2119
|
+
337,
|
|
2120
|
+
371,
|
|
2121
|
+
408,
|
|
2122
|
+
449,
|
|
2123
|
+
494,
|
|
2124
|
+
544,
|
|
2125
|
+
598,
|
|
2126
|
+
658,
|
|
2127
|
+
724,
|
|
2128
|
+
796,
|
|
2129
|
+
876,
|
|
2130
|
+
963,
|
|
2131
|
+
1060,
|
|
2132
|
+
1166,
|
|
2133
|
+
1282,
|
|
2134
|
+
1411,
|
|
2135
|
+
1552,
|
|
2136
|
+
1707,
|
|
2137
|
+
1878,
|
|
2138
|
+
2066,
|
|
2139
|
+
2272,
|
|
2140
|
+
2499,
|
|
2141
|
+
2749,
|
|
2142
|
+
3024,
|
|
2143
|
+
3327,
|
|
2144
|
+
3660,
|
|
2145
|
+
4026,
|
|
2146
|
+
4428,
|
|
2147
|
+
4871,
|
|
2148
|
+
5358,
|
|
2149
|
+
5894,
|
|
2150
|
+
6484,
|
|
2151
|
+
7132,
|
|
2152
|
+
7845,
|
|
2153
|
+
8630,
|
|
2154
|
+
9493,
|
|
2155
|
+
10442,
|
|
2156
|
+
11487,
|
|
2157
|
+
12635,
|
|
2158
|
+
13899,
|
|
2159
|
+
15289,
|
|
2160
|
+
16818,
|
|
2161
|
+
18500,
|
|
2162
|
+
20350,
|
|
2163
|
+
22385,
|
|
2164
|
+
24623,
|
|
2165
|
+
27086,
|
|
2166
|
+
29794,
|
|
2167
|
+
32767
|
|
2168
|
+
];
|
|
2169
|
+
var INDEX_TABLE = [
|
|
2170
|
+
-1,
|
|
2171
|
+
0,
|
|
2172
|
+
-1,
|
|
2173
|
+
4,
|
|
2174
|
+
-1,
|
|
2175
|
+
2,
|
|
2176
|
+
-1,
|
|
2177
|
+
6,
|
|
2178
|
+
-1,
|
|
2179
|
+
1,
|
|
2180
|
+
-1,
|
|
2181
|
+
5,
|
|
2182
|
+
-1,
|
|
2183
|
+
3,
|
|
2184
|
+
-1,
|
|
2185
|
+
7,
|
|
2186
|
+
-1,
|
|
2187
|
+
1,
|
|
2188
|
+
-1,
|
|
2189
|
+
5,
|
|
2190
|
+
-1,
|
|
2191
|
+
3,
|
|
2192
|
+
-1,
|
|
2193
|
+
7,
|
|
2194
|
+
-1,
|
|
2195
|
+
2,
|
|
2196
|
+
-1,
|
|
2197
|
+
4,
|
|
2198
|
+
-1,
|
|
2199
|
+
6,
|
|
2200
|
+
-1,
|
|
2201
|
+
8
|
|
2202
|
+
];
|
|
2203
|
+
function clampStepIndex(index) {
|
|
2204
|
+
if (index < 0) return 0;
|
|
2205
|
+
if (index > 88) return 88;
|
|
2206
|
+
return index;
|
|
2207
|
+
}
|
|
2208
|
+
function clampSample(sample) {
|
|
2209
|
+
if (sample > 32767) return 32767;
|
|
2210
|
+
if (sample < -32768) return -32768;
|
|
2211
|
+
return sample;
|
|
2212
|
+
}
|
|
2213
|
+
function decodeSample(state, code) {
|
|
2214
|
+
const step = STEP_TABLE[state.stepIndex];
|
|
2215
|
+
let diff = step >>> 3;
|
|
2216
|
+
if (code & 4) diff += step;
|
|
2217
|
+
if (code & 2) diff += step >>> 1;
|
|
2218
|
+
if (code & 1) diff += step >>> 2;
|
|
2219
|
+
if (code & 8) {
|
|
2220
|
+
state.predSample = clampSample(state.predSample - diff);
|
|
2221
|
+
} else {
|
|
2222
|
+
state.predSample = clampSample(state.predSample + diff);
|
|
2223
|
+
}
|
|
2224
|
+
state.stepIndex = clampStepIndex(state.stepIndex + INDEX_TABLE[code & 31]);
|
|
2225
|
+
return state.predSample;
|
|
2226
|
+
}
|
|
2227
|
+
function decompressAdpcmMono(inBuffer, outSize) {
|
|
2228
|
+
if (inBuffer.length < 4) {
|
|
2229
|
+
return Buffer.alloc(outSize);
|
|
2230
|
+
}
|
|
2231
|
+
const output = Buffer.alloc(outSize);
|
|
2232
|
+
let inPos = 0;
|
|
2233
|
+
let outPos = 0;
|
|
2234
|
+
const shift = inBuffer[inPos++];
|
|
2235
|
+
const initialSample = inBuffer.readInt16LE(inPos);
|
|
2236
|
+
inPos += 2;
|
|
2237
|
+
const state = {
|
|
2238
|
+
predSample: initialSample,
|
|
2239
|
+
stepIndex: STEP_TABLE.indexOf(Math.abs(initialSample)) || 0
|
|
2240
|
+
};
|
|
2241
|
+
if (outPos + 2 <= outSize) {
|
|
2242
|
+
output.writeInt16LE(state.predSample, outPos);
|
|
2243
|
+
outPos += 2;
|
|
2244
|
+
}
|
|
2245
|
+
while (inPos < inBuffer.length && outPos + 2 <= outSize) {
|
|
2246
|
+
const byte = inBuffer[inPos++];
|
|
2247
|
+
const sample1 = decodeSample(state, byte & 15);
|
|
2248
|
+
if (outPos + 2 <= outSize) {
|
|
2249
|
+
output.writeInt16LE(sample1, outPos);
|
|
2250
|
+
outPos += 2;
|
|
2251
|
+
}
|
|
2252
|
+
if (outPos + 2 <= outSize) {
|
|
2253
|
+
const sample2 = decodeSample(state, byte >>> 4 & 15);
|
|
2254
|
+
output.writeInt16LE(sample2, outPos);
|
|
2255
|
+
outPos += 2;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
return output;
|
|
2259
|
+
}
|
|
2260
|
+
function decompressAdpcmStereo(inBuffer, outSize) {
|
|
2261
|
+
if (inBuffer.length < 8) {
|
|
2262
|
+
return Buffer.alloc(outSize);
|
|
2263
|
+
}
|
|
2264
|
+
const output = Buffer.alloc(outSize);
|
|
2265
|
+
let inPos = 0;
|
|
2266
|
+
let outPos = 0;
|
|
2267
|
+
const shift = inBuffer[inPos++];
|
|
2268
|
+
const initialLeft = inBuffer.readInt16LE(inPos);
|
|
2269
|
+
inPos += 2;
|
|
2270
|
+
const initialRight = inBuffer.readInt16LE(inPos);
|
|
2271
|
+
inPos += 2;
|
|
2272
|
+
const stateL = {
|
|
2273
|
+
predSample: initialLeft,
|
|
2274
|
+
stepIndex: 0
|
|
2275
|
+
};
|
|
2276
|
+
const stateR = {
|
|
2277
|
+
predSample: initialRight,
|
|
2278
|
+
stepIndex: 0
|
|
2279
|
+
};
|
|
2280
|
+
if (outPos + 4 <= outSize) {
|
|
2281
|
+
output.writeInt16LE(stateL.predSample, outPos);
|
|
2282
|
+
outPos += 2;
|
|
2283
|
+
output.writeInt16LE(stateR.predSample, outPos);
|
|
2284
|
+
outPos += 2;
|
|
2285
|
+
}
|
|
2286
|
+
let channel = 0;
|
|
2287
|
+
while (inPos < inBuffer.length && outPos + 2 <= outSize) {
|
|
2288
|
+
const byte = inBuffer[inPos++];
|
|
2289
|
+
const state = channel === 0 ? stateL : stateR;
|
|
2290
|
+
const sample1 = decodeSample(state, byte & 15);
|
|
2291
|
+
if (outPos + 2 <= outSize) {
|
|
2292
|
+
output.writeInt16LE(sample1, outPos);
|
|
2293
|
+
outPos += 2;
|
|
2294
|
+
}
|
|
2295
|
+
channel ^= 1;
|
|
2296
|
+
if (outPos + 2 <= outSize) {
|
|
2297
|
+
const state2 = channel === 0 ? stateL : stateR;
|
|
2298
|
+
const sample2 = decodeSample(state2, byte >>> 4 & 15);
|
|
2299
|
+
output.writeInt16LE(sample2, outPos);
|
|
2300
|
+
outPos += 2;
|
|
2301
|
+
}
|
|
2302
|
+
channel ^= 1;
|
|
2303
|
+
}
|
|
2304
|
+
return output;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
// src/compression/sparse.ts
|
|
2308
|
+
function decompressSparse(inBuffer, outSize) {
|
|
2309
|
+
const output = Buffer.alloc(outSize);
|
|
2310
|
+
let inPos = 0;
|
|
2311
|
+
let outPos = 0;
|
|
2312
|
+
while (inPos < inBuffer.length && outPos < outSize) {
|
|
2313
|
+
const byte = inBuffer[inPos++];
|
|
2314
|
+
if (byte !== 0) {
|
|
2315
|
+
output[outPos++] = byte;
|
|
2316
|
+
} else {
|
|
2317
|
+
if (inPos >= inBuffer.length) break;
|
|
2318
|
+
const count = inBuffer[inPos++];
|
|
2319
|
+
if (count === 0) {
|
|
2320
|
+
output[outPos++] = 0;
|
|
2321
|
+
} else {
|
|
2322
|
+
const end = Math.min(outPos + count, outSize);
|
|
2323
|
+
outPos = end;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
return output;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// src/compression/index.ts
|
|
2331
|
+
import pako from "pako";
|
|
2332
|
+
function decompressZlib(inBuffer, outSize) {
|
|
2333
|
+
const result = pako.inflate(inBuffer);
|
|
2334
|
+
return Buffer.from(result.buffer, result.byteOffset, result.byteLength);
|
|
2335
|
+
}
|
|
2336
|
+
function decompressBzip2(_inBuffer, _outSize) {
|
|
2337
|
+
throw new MpqUnsupportedError("BZIP2 decompression is not supported. Install a bzip2 package if needed.");
|
|
2338
|
+
}
|
|
2339
|
+
function decompressLzma(_inBuffer, _outSize) {
|
|
2340
|
+
throw new MpqUnsupportedError("LZMA decompression is not supported. Install an LZMA package if needed.");
|
|
2341
|
+
}
|
|
2342
|
+
var DECOMPRESS_TABLE = [
|
|
2343
|
+
{ mask: MPQ_COMPRESSION_BZIP2, decompress: decompressBzip2 },
|
|
2344
|
+
{ mask: MPQ_COMPRESSION_PKWARE, decompress: decompressPkware },
|
|
2345
|
+
{ mask: MPQ_COMPRESSION_ZLIB, decompress: decompressZlib },
|
|
2346
|
+
{ mask: MPQ_COMPRESSION_HUFFMANN, decompress: decompressHuffman },
|
|
2347
|
+
{ mask: MPQ_COMPRESSION_ADPCM_STEREO, decompress: decompressAdpcmStereo },
|
|
2348
|
+
{ mask: MPQ_COMPRESSION_ADPCM_MONO, decompress: decompressAdpcmMono },
|
|
2349
|
+
{ mask: MPQ_COMPRESSION_SPARSE, decompress: decompressSparse }
|
|
2350
|
+
];
|
|
2351
|
+
function decompress(inBuffer, outSize) {
|
|
2352
|
+
if (inBuffer.length === outSize) {
|
|
2353
|
+
return Buffer.from(inBuffer);
|
|
2354
|
+
}
|
|
2355
|
+
if (inBuffer.length === 0) {
|
|
2356
|
+
throw new MpqCompressionError("Empty input buffer");
|
|
2357
|
+
}
|
|
2358
|
+
const compressionMask = inBuffer[0];
|
|
2359
|
+
let data = inBuffer.subarray(1);
|
|
2360
|
+
if (compressionMask === MPQ_COMPRESSION_LZMA) {
|
|
2361
|
+
return decompressLzma(data, outSize);
|
|
2362
|
+
}
|
|
2363
|
+
const stages = [];
|
|
2364
|
+
let remainingMask = compressionMask;
|
|
2365
|
+
for (const entry of DECOMPRESS_TABLE) {
|
|
2366
|
+
if (remainingMask & entry.mask) {
|
|
2367
|
+
stages.push(entry);
|
|
2368
|
+
remainingMask &= ~entry.mask;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
if (stages.length === 0 || remainingMask !== 0) {
|
|
2372
|
+
throw new MpqCompressionError(
|
|
2373
|
+
`Unsupported compression mask: 0x${compressionMask.toString(16).padStart(2, "0")}`
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
let currentData = data;
|
|
2377
|
+
let currentSize = outSize;
|
|
2378
|
+
for (let i = 0; i < stages.length; i++) {
|
|
2379
|
+
const isLast = i === stages.length - 1;
|
|
2380
|
+
const targetSize = isLast ? outSize : currentData.length * 4;
|
|
2381
|
+
try {
|
|
2382
|
+
currentData = stages[i].decompress(
|
|
2383
|
+
Buffer.isBuffer(currentData) ? currentData : Buffer.from(currentData),
|
|
2384
|
+
isLast ? outSize : targetSize
|
|
2385
|
+
);
|
|
2386
|
+
} catch (err) {
|
|
2387
|
+
if (err instanceof MpqUnsupportedError) throw err;
|
|
2388
|
+
throw new MpqCompressionError(
|
|
2389
|
+
`Decompression stage 0x${stages[i].mask.toString(16)} failed: ${err}`
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
return Buffer.isBuffer(currentData) ? currentData : Buffer.from(currentData);
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
// src/file/sector-reader.ts
|
|
2397
|
+
init_pkware();
|
|
2398
|
+
function readSectorOffsets(stream, fileOffset, fileEntry, sectorSize, fileKey) {
|
|
2399
|
+
const sectorCount = Math.ceil(fileEntry.fileSize / sectorSize);
|
|
2400
|
+
let offsetCount = sectorCount + 1;
|
|
2401
|
+
if (fileEntry.flags & MPQ_FILE_SECTOR_CRC) {
|
|
2402
|
+
offsetCount++;
|
|
2403
|
+
}
|
|
2404
|
+
const tableSize = offsetCount * 4;
|
|
2405
|
+
const tableData = stream.read(fileOffset, tableSize);
|
|
2406
|
+
if (tableData.length < tableSize) {
|
|
2407
|
+
throw new MpqCorruptError("Sector offset table is truncated");
|
|
2408
|
+
}
|
|
2409
|
+
if (fileEntry.flags & MPQ_FILE_ENCRYPTED) {
|
|
2410
|
+
decryptBlock(tableData, fileKey - 1 >>> 0);
|
|
2411
|
+
}
|
|
2412
|
+
const offsets = new Uint32Array(offsetCount);
|
|
2413
|
+
for (let i = 0; i < offsetCount; i++) {
|
|
2414
|
+
offsets[i] = tableData.readUInt32LE(i * 4);
|
|
2415
|
+
}
|
|
2416
|
+
return offsets;
|
|
2417
|
+
}
|
|
2418
|
+
function readSector(stream, fileOffset, sectorOffsets, sectorIndex, fileEntry, sectorSize, fileKey) {
|
|
2419
|
+
const rawOffset = sectorOffsets[sectorIndex];
|
|
2420
|
+
const rawEnd = sectorOffsets[sectorIndex + 1];
|
|
2421
|
+
const rawSize = rawEnd - rawOffset;
|
|
2422
|
+
const sectorStart = sectorIndex * sectorSize;
|
|
2423
|
+
const expectedSize = Math.min(sectorSize, fileEntry.fileSize - sectorStart);
|
|
2424
|
+
let sectorData = stream.read(fileOffset + BigInt(rawOffset), rawSize);
|
|
2425
|
+
if (sectorData.length < rawSize) {
|
|
2426
|
+
throw new MpqCorruptError(`Sector ${sectorIndex} data is truncated`);
|
|
2427
|
+
}
|
|
2428
|
+
if (fileEntry.flags & MPQ_FILE_ENCRYPTED) {
|
|
2429
|
+
sectorData = Buffer.from(sectorData);
|
|
2430
|
+
decryptBlock(sectorData, fileKey + sectorIndex >>> 0);
|
|
2431
|
+
}
|
|
2432
|
+
if (rawSize < expectedSize) {
|
|
2433
|
+
if (fileEntry.flags & MPQ_FILE_COMPRESS) {
|
|
2434
|
+
sectorData = decompress(sectorData, expectedSize);
|
|
2435
|
+
} else if (fileEntry.flags & MPQ_FILE_IMPLODE) {
|
|
2436
|
+
sectorData = decompressPkware(sectorData, expectedSize);
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
return sectorData;
|
|
2440
|
+
}
|
|
2441
|
+
function readSectorFile(stream, archiveOffset, fileEntry, sectorSize, fileKey) {
|
|
2442
|
+
const fileOffset = archiveOffset + fileEntry.byteOffset;
|
|
2443
|
+
const sectorOffsets = readSectorOffsets(stream, fileOffset, fileEntry, sectorSize, fileKey);
|
|
2444
|
+
const sectorCount = Math.ceil(fileEntry.fileSize / sectorSize);
|
|
2445
|
+
const output = Buffer.alloc(fileEntry.fileSize);
|
|
2446
|
+
let outPos = 0;
|
|
2447
|
+
for (let i = 0; i < sectorCount; i++) {
|
|
2448
|
+
const sectorData = readSector(
|
|
2449
|
+
stream,
|
|
2450
|
+
fileOffset,
|
|
2451
|
+
sectorOffsets,
|
|
2452
|
+
i,
|
|
2453
|
+
fileEntry,
|
|
2454
|
+
sectorSize,
|
|
2455
|
+
fileKey
|
|
2456
|
+
);
|
|
2457
|
+
const copyLen = Math.min(sectorData.length, fileEntry.fileSize - outPos);
|
|
2458
|
+
sectorData.copy(output, outPos, 0, copyLen);
|
|
2459
|
+
outPos += copyLen;
|
|
2460
|
+
}
|
|
2461
|
+
return output;
|
|
2462
|
+
}
|
|
2463
|
+
function readSingleUnitFile(stream, archiveOffset, fileEntry, fileKey) {
|
|
2464
|
+
const fileOffset = archiveOffset + fileEntry.byteOffset;
|
|
2465
|
+
let data = stream.read(fileOffset, fileEntry.cmpSize);
|
|
2466
|
+
if (data.length < fileEntry.cmpSize) {
|
|
2467
|
+
throw new MpqCorruptError("Single-unit file data is truncated");
|
|
2468
|
+
}
|
|
2469
|
+
if (fileEntry.flags & MPQ_FILE_ENCRYPTED) {
|
|
2470
|
+
data = Buffer.from(data);
|
|
2471
|
+
decryptBlock(data, fileKey);
|
|
2472
|
+
}
|
|
2473
|
+
if (fileEntry.cmpSize < fileEntry.fileSize) {
|
|
2474
|
+
if (fileEntry.flags & MPQ_FILE_COMPRESS) {
|
|
2475
|
+
data = decompress(data, fileEntry.fileSize);
|
|
2476
|
+
} else if (fileEntry.flags & MPQ_FILE_IMPLODE) {
|
|
2477
|
+
const { decompressPkware: decompressPkware2 } = (init_pkware(), __toCommonJS(pkware_exports));
|
|
2478
|
+
data = decompressPkware2(data, fileEntry.fileSize);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
return data;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
// src/file/mpq-file.ts
|
|
2485
|
+
var MpqFile = class {
|
|
2486
|
+
constructor(stream, archiveOffset, entry, sectorSize) {
|
|
2487
|
+
this.closed = false;
|
|
2488
|
+
this.stream = stream;
|
|
2489
|
+
this.archiveOffset = archiveOffset;
|
|
2490
|
+
this.entry = entry;
|
|
2491
|
+
this.sectorSize = sectorSize;
|
|
2492
|
+
if (entry.flags & MPQ_FILE_ENCRYPTED) {
|
|
2493
|
+
this.fileKey = decryptFileKey(
|
|
2494
|
+
entry.fileName,
|
|
2495
|
+
entry.byteOffset,
|
|
2496
|
+
entry.fileSize,
|
|
2497
|
+
entry.flags
|
|
2498
|
+
);
|
|
2499
|
+
} else {
|
|
2500
|
+
this.fileKey = 0;
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
/** Uncompressed file size */
|
|
2504
|
+
get size() {
|
|
2505
|
+
return this.entry.fileSize;
|
|
2506
|
+
}
|
|
2507
|
+
/** Compressed file size */
|
|
2508
|
+
get compressedSize() {
|
|
2509
|
+
return this.entry.cmpSize;
|
|
2510
|
+
}
|
|
2511
|
+
/** File flags */
|
|
2512
|
+
get flags() {
|
|
2513
|
+
return this.entry.flags;
|
|
2514
|
+
}
|
|
2515
|
+
/** File name */
|
|
2516
|
+
get name() {
|
|
2517
|
+
return this.entry.fileName;
|
|
2518
|
+
}
|
|
2519
|
+
/** The underlying file entry */
|
|
2520
|
+
get fileEntry() {
|
|
2521
|
+
return this.entry;
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Read the entire file contents.
|
|
2525
|
+
*
|
|
2526
|
+
* @returns Buffer containing the decompressed file data
|
|
2527
|
+
*/
|
|
2528
|
+
read() {
|
|
2529
|
+
if (this.closed) {
|
|
2530
|
+
throw new Error("File handle is closed");
|
|
2531
|
+
}
|
|
2532
|
+
if (this.entry.fileSize === 0) {
|
|
2533
|
+
return Buffer.alloc(0);
|
|
2534
|
+
}
|
|
2535
|
+
if (this.entry.flags & MPQ_FILE_SINGLE_UNIT) {
|
|
2536
|
+
return readSingleUnitFile(
|
|
2537
|
+
this.stream,
|
|
2538
|
+
this.archiveOffset,
|
|
2539
|
+
this.entry,
|
|
2540
|
+
this.fileKey
|
|
2541
|
+
);
|
|
2542
|
+
}
|
|
2543
|
+
return readSectorFile(
|
|
2544
|
+
this.stream,
|
|
2545
|
+
this.archiveOffset,
|
|
2546
|
+
this.entry,
|
|
2547
|
+
this.sectorSize,
|
|
2548
|
+
this.fileKey
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Close the file handle.
|
|
2553
|
+
*/
|
|
2554
|
+
close() {
|
|
2555
|
+
this.closed = true;
|
|
2556
|
+
}
|
|
2557
|
+
};
|
|
2558
|
+
|
|
2559
|
+
// src/archive/mpq-archive.ts
|
|
2560
|
+
var MpqArchive = class _MpqArchive {
|
|
2561
|
+
constructor(stream, header, archiveOffset) {
|
|
2562
|
+
this.hashTable = [];
|
|
2563
|
+
this.blockTable = [];
|
|
2564
|
+
this.hiBlockTable = null;
|
|
2565
|
+
this.hetTable = null;
|
|
2566
|
+
this.betTable = null;
|
|
2567
|
+
this.fileEntries = [];
|
|
2568
|
+
this.attributes = null;
|
|
2569
|
+
this.closed = false;
|
|
2570
|
+
this.stream = stream;
|
|
2571
|
+
this.header = header;
|
|
2572
|
+
this.archiveOffset = archiveOffset;
|
|
2573
|
+
this.sectorSize = getSectorSize(header);
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* Open an MPQ archive from a file path.
|
|
2577
|
+
*
|
|
2578
|
+
* @param path - Path to the MPQ file
|
|
2579
|
+
* @param options - Open options
|
|
2580
|
+
* @returns MpqArchive instance
|
|
2581
|
+
*/
|
|
2582
|
+
static open(path, options) {
|
|
2583
|
+
const stream = FileStream.open(path);
|
|
2584
|
+
try {
|
|
2585
|
+
const noHeaderSearch = options?.noHeaderSearch ?? false;
|
|
2586
|
+
const { header, headerOffset } = findHeader(stream, noHeaderSearch);
|
|
2587
|
+
if (options?.forceMpqV1) {
|
|
2588
|
+
header.wFormatVersion = MPQ_FORMAT_VERSION_1;
|
|
2589
|
+
}
|
|
2590
|
+
const archive = new _MpqArchive(stream, header, headerOffset);
|
|
2591
|
+
archive.loadTables();
|
|
2592
|
+
if (!options?.noListfile) {
|
|
2593
|
+
archive.loadListfile();
|
|
2594
|
+
}
|
|
2595
|
+
if (!options?.noAttributes) {
|
|
2596
|
+
archive.loadAttributes();
|
|
2597
|
+
}
|
|
2598
|
+
return archive;
|
|
2599
|
+
} catch (err) {
|
|
2600
|
+
stream.close();
|
|
2601
|
+
throw err;
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Load all archive tables (hash, block, HET, BET).
|
|
2606
|
+
*/
|
|
2607
|
+
loadTables() {
|
|
2608
|
+
const h = this.header;
|
|
2609
|
+
if (h.dwHashTableSize > 0) {
|
|
2610
|
+
const hashOffset = getHashTableOffset(h, this.archiveOffset);
|
|
2611
|
+
this.hashTable = loadHashTable(this.stream, hashOffset, h.dwHashTableSize);
|
|
2612
|
+
}
|
|
2613
|
+
if (h.dwBlockTableSize > 0) {
|
|
2614
|
+
const blockOffset = getBlockTableOffset(h, this.archiveOffset);
|
|
2615
|
+
this.blockTable = loadBlockTable(this.stream, blockOffset, h.dwBlockTableSize);
|
|
2616
|
+
}
|
|
2617
|
+
const hiBlockOffset = getHiBlockTableOffset(h, this.archiveOffset);
|
|
2618
|
+
if (hiBlockOffset !== 0n && this.blockTable.length > 0) {
|
|
2619
|
+
this.hiBlockTable = loadHiBlockTable(this.stream, hiBlockOffset, this.blockTable.length);
|
|
2620
|
+
}
|
|
2621
|
+
if (h.wFormatVersion >= MPQ_FORMAT_VERSION_3) {
|
|
2622
|
+
const hetOffset = getHetTableOffset(h, this.archiveOffset);
|
|
2623
|
+
if (hetOffset !== 0n) {
|
|
2624
|
+
this.hetTable = loadHetTable(this.stream, hetOffset, h.hetTableSize64);
|
|
2625
|
+
}
|
|
2626
|
+
const betOffset = getBetTableOffset(h, this.archiveOffset);
|
|
2627
|
+
if (betOffset !== 0n) {
|
|
2628
|
+
this.betTable = loadBetTable(this.stream, betOffset, h.betTableSize64);
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
if (this.hetTable && this.betTable) {
|
|
2632
|
+
this.fileEntries = buildFileTableFromHetBet(this.hetTable, this.betTable);
|
|
2633
|
+
} else {
|
|
2634
|
+
this.fileEntries = buildFileTable(this.hashTable, this.blockTable, this.hiBlockTable);
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
/**
|
|
2638
|
+
* Load and parse the (listfile) to populate file names.
|
|
2639
|
+
*/
|
|
2640
|
+
loadListfile() {
|
|
2641
|
+
try {
|
|
2642
|
+
const data = this.extractFileByName(LISTFILE_NAME);
|
|
2643
|
+
if (data) {
|
|
2644
|
+
const names = parseListfile(data);
|
|
2645
|
+
applyFileNames(this.fileEntries, this.hashTable, names);
|
|
2646
|
+
}
|
|
2647
|
+
} catch {
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
/**
|
|
2651
|
+
* Load and parse the (attributes) file.
|
|
2652
|
+
*/
|
|
2653
|
+
loadAttributes() {
|
|
2654
|
+
try {
|
|
2655
|
+
const data = this.extractFileByName(ATTRIBUTES_NAME);
|
|
2656
|
+
if (data) {
|
|
2657
|
+
this.attributes = parseAttributes(data, this.fileEntries.length);
|
|
2658
|
+
if (this.attributes) {
|
|
2659
|
+
for (let i = 0; i < this.fileEntries.length; i++) {
|
|
2660
|
+
if (i < this.attributes.crc32s.length) {
|
|
2661
|
+
this.fileEntries[i].crc32 = this.attributes.crc32s[i];
|
|
2662
|
+
}
|
|
2663
|
+
if (i < this.attributes.fileTimes.length) {
|
|
2664
|
+
this.fileEntries[i].fileTime = this.attributes.fileTimes[i];
|
|
2665
|
+
}
|
|
2666
|
+
if (i < this.attributes.md5s.length) {
|
|
2667
|
+
this.fileEntries[i].md5 = this.attributes.md5s[i];
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
} catch {
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
/**
|
|
2676
|
+
* Internal: extract a file by name without relying on the listfile.
|
|
2677
|
+
* Used for bootstrapping (listfile) and (attributes) loading.
|
|
2678
|
+
*/
|
|
2679
|
+
extractFileByName(fileName) {
|
|
2680
|
+
const hashEntry = findHashEntry(
|
|
2681
|
+
this.hashTable,
|
|
2682
|
+
fileName,
|
|
2683
|
+
0,
|
|
2684
|
+
this.fileEntries.length
|
|
2685
|
+
);
|
|
2686
|
+
if (!hashEntry) return null;
|
|
2687
|
+
const blockIndex = hashEntry.dwBlockIndex & BLOCK_INDEX_MASK;
|
|
2688
|
+
if (blockIndex >= this.fileEntries.length) return null;
|
|
2689
|
+
const entry = this.fileEntries[blockIndex];
|
|
2690
|
+
if (!(entry.flags & MPQ_FILE_EXISTS)) return null;
|
|
2691
|
+
const savedName = entry.fileName;
|
|
2692
|
+
entry.fileName = fileName;
|
|
2693
|
+
try {
|
|
2694
|
+
const file = new MpqFile(this.stream, this.archiveOffset, entry, this.sectorSize);
|
|
2695
|
+
const data = file.read();
|
|
2696
|
+
file.close();
|
|
2697
|
+
return data;
|
|
2698
|
+
} finally {
|
|
2699
|
+
entry.fileName = savedName;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
/**
|
|
2703
|
+
* Check if a file exists in the archive.
|
|
2704
|
+
*/
|
|
2705
|
+
hasFile(name) {
|
|
2706
|
+
this.ensureOpen();
|
|
2707
|
+
if (this.hetTable && this.betTable) {
|
|
2708
|
+
const betIndex = findInHetTable(this.hetTable, name);
|
|
2709
|
+
if (betIndex >= 0 && betIndex < this.fileEntries.length) {
|
|
2710
|
+
return !!(this.fileEntries[betIndex].flags & MPQ_FILE_EXISTS);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
const hashEntry = findHashEntry(this.hashTable, name, 0, this.fileEntries.length);
|
|
2714
|
+
if (!hashEntry) return false;
|
|
2715
|
+
const blockIndex = hashEntry.dwBlockIndex & BLOCK_INDEX_MASK;
|
|
2716
|
+
return blockIndex < this.fileEntries.length && !!(this.fileEntries[blockIndex].flags & MPQ_FILE_EXISTS);
|
|
2717
|
+
}
|
|
2718
|
+
/**
|
|
2719
|
+
* Open a file within the archive for reading.
|
|
2720
|
+
*
|
|
2721
|
+
* @param name - File path within the archive
|
|
2722
|
+
* @returns MpqFile handle
|
|
2723
|
+
*/
|
|
2724
|
+
openFile(name) {
|
|
2725
|
+
this.ensureOpen();
|
|
2726
|
+
const entry = this.resolveFile(name);
|
|
2727
|
+
if (!entry) {
|
|
2728
|
+
throw new MpqNotFoundError(`File not found: ${name}`);
|
|
2729
|
+
}
|
|
2730
|
+
return new MpqFile(this.stream, this.archiveOffset, entry, this.sectorSize);
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2733
|
+
* Extract a file's contents directly.
|
|
2734
|
+
*
|
|
2735
|
+
* @param name - File path within the archive
|
|
2736
|
+
* @returns Buffer with the file contents
|
|
2737
|
+
*/
|
|
2738
|
+
extractFile(name) {
|
|
2739
|
+
const file = this.openFile(name);
|
|
2740
|
+
try {
|
|
2741
|
+
return file.read();
|
|
2742
|
+
} finally {
|
|
2743
|
+
file.close();
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
/**
|
|
2747
|
+
* Get the list of all known file names (from listfile).
|
|
2748
|
+
*/
|
|
2749
|
+
getFileList() {
|
|
2750
|
+
this.ensureOpen();
|
|
2751
|
+
return this.fileEntries.filter((e) => e.fileName && e.flags & MPQ_FILE_EXISTS).map((e) => e.fileName);
|
|
2752
|
+
}
|
|
2753
|
+
/**
|
|
2754
|
+
* Find files matching a wildcard pattern.
|
|
2755
|
+
*
|
|
2756
|
+
* @param mask - Wildcard pattern (supports '*' and '?')
|
|
2757
|
+
* @returns Array of matching file entries
|
|
2758
|
+
*/
|
|
2759
|
+
findFiles(mask = "*") {
|
|
2760
|
+
this.ensureOpen();
|
|
2761
|
+
const regex = wildcardToRegex(mask);
|
|
2762
|
+
const results = [];
|
|
2763
|
+
for (let i = 0; i < this.fileEntries.length; i++) {
|
|
2764
|
+
const entry = this.fileEntries[i];
|
|
2765
|
+
if (!(entry.flags & MPQ_FILE_EXISTS)) continue;
|
|
2766
|
+
if (!entry.fileName) continue;
|
|
2767
|
+
if (regex.test(entry.fileName)) {
|
|
2768
|
+
const plainName = entry.fileName.replace(/^.*[\\/]/, "");
|
|
2769
|
+
results.push({
|
|
2770
|
+
fileName: entry.fileName,
|
|
2771
|
+
plainName,
|
|
2772
|
+
hashIndex: 0,
|
|
2773
|
+
blockIndex: i,
|
|
2774
|
+
fileSize: entry.fileSize,
|
|
2775
|
+
compSize: entry.cmpSize,
|
|
2776
|
+
fileFlags: entry.flags,
|
|
2777
|
+
locale: 0
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
return results;
|
|
2782
|
+
}
|
|
2783
|
+
/**
|
|
2784
|
+
* Apply an external list of file names to resolve unnamed entries.
|
|
2785
|
+
* Equivalent to providing an external listfile to SFileFindFirstFile.
|
|
2786
|
+
*
|
|
2787
|
+
* @param names - Array of file names to try
|
|
2788
|
+
* @returns Number of newly resolved names
|
|
2789
|
+
*/
|
|
2790
|
+
addListfile(names) {
|
|
2791
|
+
this.ensureOpen();
|
|
2792
|
+
const before = this.fileEntries.filter((e) => e.fileName).length;
|
|
2793
|
+
applyFileNames(this.fileEntries, this.hashTable, names);
|
|
2794
|
+
return this.fileEntries.filter((e) => e.fileName).length - before;
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Enumerate all existing file entries, including those without names.
|
|
2798
|
+
* Unnamed entries get a synthetic name like "File00001234.xxx".
|
|
2799
|
+
*/
|
|
2800
|
+
enumerateFiles() {
|
|
2801
|
+
this.ensureOpen();
|
|
2802
|
+
const results = [];
|
|
2803
|
+
for (let i = 0; i < this.fileEntries.length; i++) {
|
|
2804
|
+
const entry = this.fileEntries[i];
|
|
2805
|
+
if (!(entry.flags & MPQ_FILE_EXISTS)) continue;
|
|
2806
|
+
const fileName = entry.fileName || `File${String(i).padStart(8, "0")}.xxx`;
|
|
2807
|
+
const plainName = fileName.replace(/^.*[\\/]/, "");
|
|
2808
|
+
results.push({
|
|
2809
|
+
fileName,
|
|
2810
|
+
plainName,
|
|
2811
|
+
hashIndex: 0,
|
|
2812
|
+
blockIndex: i,
|
|
2813
|
+
fileSize: entry.fileSize,
|
|
2814
|
+
compSize: entry.cmpSize,
|
|
2815
|
+
fileFlags: entry.flags,
|
|
2816
|
+
locale: 0
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
return results;
|
|
2820
|
+
}
|
|
2821
|
+
/** Get the archive header */
|
|
2822
|
+
getHeader() {
|
|
2823
|
+
return this.header;
|
|
2824
|
+
}
|
|
2825
|
+
/** Get the number of file entries */
|
|
2826
|
+
get fileCount() {
|
|
2827
|
+
return this.fileEntries.length;
|
|
2828
|
+
}
|
|
2829
|
+
/** Get the sector size */
|
|
2830
|
+
getSectorSize() {
|
|
2831
|
+
return this.sectorSize;
|
|
2832
|
+
}
|
|
2833
|
+
/**
|
|
2834
|
+
* Close the archive and release resources.
|
|
2835
|
+
*/
|
|
2836
|
+
close() {
|
|
2837
|
+
if (!this.closed) {
|
|
2838
|
+
this.stream.close();
|
|
2839
|
+
this.closed = true;
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
/**
|
|
2843
|
+
* Resolve a file name to its FileEntry.
|
|
2844
|
+
*/
|
|
2845
|
+
resolveFile(name) {
|
|
2846
|
+
if (this.hetTable && this.betTable) {
|
|
2847
|
+
const betIndex = findInHetTable(this.hetTable, name);
|
|
2848
|
+
if (betIndex >= 0 && betIndex < this.fileEntries.length) {
|
|
2849
|
+
const entry2 = this.fileEntries[betIndex];
|
|
2850
|
+
if (entry2.flags & MPQ_FILE_EXISTS) {
|
|
2851
|
+
if (!entry2.fileName) entry2.fileName = name;
|
|
2852
|
+
return entry2;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
const hashEntry = findHashEntry(this.hashTable, name, 0, this.fileEntries.length);
|
|
2857
|
+
if (!hashEntry) return null;
|
|
2858
|
+
const blockIndex = hashEntry.dwBlockIndex & BLOCK_INDEX_MASK;
|
|
2859
|
+
if (blockIndex >= this.fileEntries.length) return null;
|
|
2860
|
+
const entry = this.fileEntries[blockIndex];
|
|
2861
|
+
if (!(entry.flags & MPQ_FILE_EXISTS)) return null;
|
|
2862
|
+
if (!entry.fileName) {
|
|
2863
|
+
entry.fileName = name;
|
|
2864
|
+
}
|
|
2865
|
+
return entry;
|
|
2866
|
+
}
|
|
2867
|
+
ensureOpen() {
|
|
2868
|
+
if (this.closed) {
|
|
2869
|
+
throw new MpqError("Archive is closed");
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
};
|
|
2873
|
+
function wildcardToRegex(pattern) {
|
|
2874
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
2875
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
2876
|
+
}
|
|
2877
|
+
export {
|
|
2878
|
+
ATTRIBUTES_NAME,
|
|
2879
|
+
ID_MPQ,
|
|
2880
|
+
ID_MPQ_USERDATA,
|
|
2881
|
+
LISTFILE_NAME,
|
|
2882
|
+
MPQ_COMPRESSION_ADPCM_MONO,
|
|
2883
|
+
MPQ_COMPRESSION_ADPCM_STEREO,
|
|
2884
|
+
MPQ_COMPRESSION_BZIP2,
|
|
2885
|
+
MPQ_COMPRESSION_HUFFMANN,
|
|
2886
|
+
MPQ_COMPRESSION_LZMA,
|
|
2887
|
+
MPQ_COMPRESSION_PKWARE,
|
|
2888
|
+
MPQ_COMPRESSION_SPARSE,
|
|
2889
|
+
MPQ_COMPRESSION_ZLIB,
|
|
2890
|
+
MPQ_FILE_COMPRESS,
|
|
2891
|
+
MPQ_FILE_ENCRYPTED,
|
|
2892
|
+
MPQ_FILE_EXISTS,
|
|
2893
|
+
MPQ_FILE_IMPLODE,
|
|
2894
|
+
MPQ_FILE_KEY_V2,
|
|
2895
|
+
MPQ_FILE_SINGLE_UNIT,
|
|
2896
|
+
MPQ_FORMAT_VERSION_1,
|
|
2897
|
+
MPQ_FORMAT_VERSION_2,
|
|
2898
|
+
MPQ_FORMAT_VERSION_3,
|
|
2899
|
+
MPQ_FORMAT_VERSION_4,
|
|
2900
|
+
MpqArchive,
|
|
2901
|
+
MpqCompressionError,
|
|
2902
|
+
MpqCorruptError,
|
|
2903
|
+
MpqEncryptionError,
|
|
2904
|
+
MpqError,
|
|
2905
|
+
MpqFile,
|
|
2906
|
+
MpqNotFoundError,
|
|
2907
|
+
MpqUnsupportedError,
|
|
2908
|
+
SIGNATURE_NAME,
|
|
2909
|
+
decryptBlock,
|
|
2910
|
+
decryptFileKey,
|
|
2911
|
+
encryptBlock,
|
|
2912
|
+
getStormBuffer,
|
|
2913
|
+
hashFileKey,
|
|
2914
|
+
hashNameA,
|
|
2915
|
+
hashNameB,
|
|
2916
|
+
hashString,
|
|
2917
|
+
hashTableIndex,
|
|
2918
|
+
jenkinsHash
|
|
2919
|
+
};
|
|
2920
|
+
//# sourceMappingURL=index.mjs.map
|