@rhyster/wow-casc-dbc 2.6.19 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/adb.d.ts +17 -0
  2. package/dist/adb.d.ts.map +1 -0
  3. package/dist/blte.d.ts +25 -0
  4. package/dist/blte.d.ts.map +1 -0
  5. package/dist/client.d.ts +84 -0
  6. package/dist/client.d.ts.map +1 -0
  7. package/dist/dbd.d.ts +26 -0
  8. package/dist/dbd.d.ts.map +1 -0
  9. package/dist/fetcher.d.ts +21 -0
  10. package/dist/fetcher.d.ts.map +1 -0
  11. package/dist/index.cjs +1 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.ts +9 -261
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.mjs +1 -0
  16. package/dist/index.mjs.map +1 -0
  17. package/dist/jenkins96.d.ts +3 -0
  18. package/dist/jenkins96.d.ts.map +1 -0
  19. package/dist/parsers/archiveIndex.d.ts +9 -0
  20. package/dist/parsers/archiveIndex.d.ts.map +1 -0
  21. package/dist/parsers/config.d.ts +40 -0
  22. package/dist/parsers/config.d.ts.map +1 -0
  23. package/dist/parsers/encodingFile.d.ts +11 -0
  24. package/dist/parsers/encodingFile.d.ts.map +1 -0
  25. package/dist/parsers/productConfig.d.ts +21 -0
  26. package/dist/parsers/productConfig.d.ts.map +1 -0
  27. package/dist/parsers/rootFile.d.ts +45 -0
  28. package/dist/parsers/rootFile.d.ts.map +1 -0
  29. package/dist/salsa20.d.ts +14 -0
  30. package/dist/salsa20.d.ts.map +1 -0
  31. package/dist/store.d.ts +9 -0
  32. package/dist/store.d.ts.map +1 -0
  33. package/dist/test/salsa20.test.d.ts +2 -0
  34. package/dist/test/salsa20.test.d.ts.map +1 -0
  35. package/dist/utils.d.ts +3 -0
  36. package/dist/utils.d.ts.map +1 -0
  37. package/dist/wdc.d.ts +104 -0
  38. package/dist/wdc.d.ts.map +1 -0
  39. package/package.json +5 -4
  40. package/src/adb.ts +70 -0
  41. package/src/blte.ts +220 -0
  42. package/src/client.ts +411 -0
  43. package/src/dbd.ts +427 -0
  44. package/src/fetcher.ts +223 -0
  45. package/src/index.ts +44 -0
  46. package/src/jenkins96.ts +75 -0
  47. package/src/parsers/archiveIndex.ts +119 -0
  48. package/src/parsers/config.ts +75 -0
  49. package/src/parsers/encodingFile.ts +159 -0
  50. package/src/parsers/productConfig.ts +57 -0
  51. package/src/parsers/rootFile.ts +172 -0
  52. package/src/salsa20.ts +143 -0
  53. package/src/store.ts +37 -0
  54. package/src/test/salsa20.test.ts +522 -0
  55. package/src/utils.ts +77 -0
  56. package/src/wdc.ts +788 -0
package/src/wdc.ts ADDED
@@ -0,0 +1,788 @@
1
+ import assert from 'node:assert';
2
+
3
+ import type ADBReader from './adb.ts';
4
+ import type { MissingKeyBlock } from './blte.ts';
5
+
6
+ const WDC5_MAGIC = 0x57444335;
7
+
8
+ interface SectionHeader {
9
+ tactKeyHash: bigint,
10
+ fileOffset: number,
11
+ recordCount: number,
12
+ stringTableSize: number,
13
+ offsetRecordsEnd: number,
14
+ idListSize: number,
15
+ relationshipDataSize: number,
16
+ offsetMapIDCount: number,
17
+ copyTableCount: number,
18
+ }
19
+
20
+ interface FieldStructure {
21
+ size: number,
22
+ position: number,
23
+ }
24
+
25
+ interface FieldStorageInfoCompressionNone {
26
+ fieldOffsetBits: number,
27
+ fieldSizeBits: number,
28
+ additionalDataSize: number,
29
+ storageType: 'none',
30
+ }
31
+
32
+ interface FieldStorageInfoCompressionBitpacked {
33
+ fieldOffsetBits: number,
34
+ fieldSizeBits: number,
35
+ additionalDataSize: number,
36
+ storageType: 'bitpacked',
37
+ bitpackingOffsetBits: number,
38
+ bitpackingSizeBits: number,
39
+ flags: number,
40
+ }
41
+
42
+ interface FieldStorageInfoCompressionCommonData {
43
+ fieldOffsetBits: number,
44
+ fieldSizeBits: number,
45
+ additionalDataSize: number,
46
+ storageType: 'commonData',
47
+ defaultValue: number,
48
+ }
49
+
50
+ interface FieldStorageInfoCompressionBitpackedIndexed {
51
+ fieldOffsetBits: number,
52
+ fieldSizeBits: number,
53
+ additionalDataSize: number,
54
+ storageType: 'bitpackedIndexed',
55
+ bitpackingOffsetBits: number,
56
+ bitpackingSizeBits: number,
57
+ }
58
+
59
+ interface FieldStorageInfoCompressionBitpackedIndexedArray {
60
+ fieldOffsetBits: number,
61
+ fieldSizeBits: number,
62
+ additionalDataSize: number,
63
+ storageType: 'bitpackedIndexedArray',
64
+ bitpackingOffsetBits: number,
65
+ bitpackingSizeBits: number,
66
+ arrayCount: number,
67
+ }
68
+
69
+ interface FieldStorageInfoCompressionBitpackedSigned {
70
+ fieldOffsetBits: number,
71
+ fieldSizeBits: number,
72
+ additionalDataSize: number,
73
+ storageType: 'bitpackedSigned',
74
+ bitpackingOffsetBits: number,
75
+ bitpackingSizeBits: number,
76
+ flags: number,
77
+ }
78
+
79
+ type FieldStorageInfo = FieldStorageInfoCompressionNone
80
+ | FieldStorageInfoCompressionBitpacked
81
+ | FieldStorageInfoCompressionCommonData
82
+ | FieldStorageInfoCompressionBitpackedIndexed
83
+ | FieldStorageInfoCompressionBitpackedIndexedArray
84
+ | FieldStorageInfoCompressionBitpackedSigned;
85
+
86
+ interface OffsetMapEntry {
87
+ offset: number,
88
+ size: number,
89
+ data: Buffer,
90
+ }
91
+
92
+ interface ParsedFieldNone {
93
+ type: 'none',
94
+ data: number | bigint,
95
+ string?: string,
96
+ }
97
+
98
+ interface ParsedFieldCommonData {
99
+ type: 'commonData',
100
+ data: number,
101
+ }
102
+
103
+ interface ParsedFieldBitpacked {
104
+ type: 'bitpacked',
105
+ data: number,
106
+ }
107
+
108
+ interface ParsedFieldBitpackedArray {
109
+ type: 'bitpackedArray',
110
+ data: number[],
111
+ }
112
+
113
+ type ParsedField = ParsedFieldNone
114
+ | ParsedFieldCommonData
115
+ | ParsedFieldBitpacked
116
+ | ParsedFieldBitpackedArray;
117
+
118
+ interface SparseRow {
119
+ type: 'sparse',
120
+ data: Buffer,
121
+ }
122
+
123
+ interface Section {
124
+ header: SectionHeader,
125
+ isZeroed: boolean,
126
+ recordDataSize: number,
127
+ records: Buffer[],
128
+ idList: number[],
129
+ offsetMap: OffsetMapEntry[],
130
+ relationshipMap: Map<number, number>,
131
+ }
132
+
133
+ interface HotfixModify {
134
+ type: 'modify',
135
+ data: Buffer,
136
+ }
137
+
138
+ interface HotfixDelete {
139
+ type: 'delete',
140
+ }
141
+
142
+ type Hotfix = HotfixModify | HotfixDelete;
143
+
144
+ /* eslint-disable no-bitwise */
145
+ const readBitpackedValue = (
146
+ buffer: Buffer,
147
+ fieldOffsetBits: number,
148
+ fieldSizeBits: number,
149
+ signed = false,
150
+ ) => {
151
+ const offsetBytes = fieldOffsetBits >>> 3;
152
+ const bitOffset = fieldOffsetBits & 0x7;
153
+ const sizeBytes = Math.ceil((fieldSizeBits + bitOffset) / 8);
154
+
155
+ if (sizeBytes <= 6) {
156
+ // safe to be number
157
+ const rawValue = buffer.readUIntLE(offsetBytes, sizeBytes);
158
+ return Number(
159
+ signed
160
+ ? BigInt.asIntN(fieldSizeBits, BigInt(rawValue >>> bitOffset))
161
+ : BigInt.asUintN(fieldSizeBits, BigInt(rawValue >>> bitOffset)),
162
+ );
163
+ }
164
+
165
+ // need to be bigint
166
+ let value = 0n;
167
+
168
+ for (let i = sizeBytes - 1; i >= 0; i -= 1) {
169
+ const byte = buffer.readUInt8(offsetBytes + i);
170
+ value = (value << 8n) | BigInt(byte);
171
+ }
172
+
173
+ return signed
174
+ ? BigInt.asIntN(fieldSizeBits, value >> BigInt(bitOffset))
175
+ : BigInt.asUintN(fieldSizeBits, value >> BigInt(bitOffset));
176
+ };
177
+ /* eslint-enable no-bitwise */
178
+
179
+ export default class WDCReader {
180
+ public readonly tableHash: number;
181
+
182
+ public readonly layoutHash: number;
183
+
184
+ public readonly locale: number;
185
+
186
+ public readonly isNormal: boolean;
187
+
188
+ public readonly hasRelationshipData: boolean;
189
+
190
+ public readonly fields: FieldStructure[];
191
+
192
+ public readonly fieldsInfo: FieldStorageInfo[];
193
+
194
+ public readonly rows = new Map<number, ParsedField[] | SparseRow>();
195
+
196
+ public readonly relationships = new Map<number, number>();
197
+
198
+ public readonly copyTable = new Map<number, number>();
199
+
200
+ public readonly hotfixes = new Map<number, Hotfix>();
201
+
202
+ constructor(buffer: Buffer, blocks: MissingKeyBlock[] = [], adb?: ADBReader) {
203
+ const magic = buffer.readUInt32BE(0);
204
+ // const version = buffer.readUInt32LE(4);
205
+ // const schema = buffer.toString('ascii', 8, 136);
206
+ // const recordCount = buffer.readUInt32LE(136);
207
+ const fieldCount = buffer.readUInt32LE(140);
208
+ const recordSize = buffer.readUInt32LE(144);
209
+ // const stringTableSize = buffer.readUInt32LE(148);
210
+ const tableHash = buffer.readUInt32LE(152);
211
+ const layoutHash = buffer.readUInt32LE(156);
212
+ // const minID = buffer.readUInt32LE(160);
213
+ // const maxID = buffer.readUInt32LE(164);
214
+ const locale = buffer.readUInt32LE(168);
215
+ const flags = buffer.readUInt16LE(172);
216
+ const idIndex = buffer.readUInt16LE(174);
217
+ // const totalFieldCount = buffer.readUInt32LE(176);
218
+ // const bitpackedDataOffset = buffer.readUInt32LE(180);
219
+ // const lookupColumnCount = buffer.readUInt32LE(184);
220
+ const fieldStorageInfoSize = buffer.readUInt32LE(188);
221
+ const commonDataSize = buffer.readUInt32LE(192);
222
+ const palletDataSize = buffer.readUInt32LE(196);
223
+ const sectionCount = buffer.readUInt32LE(200);
224
+
225
+ assert(magic === WDC5_MAGIC, `Invalid magic: ${magic.toString(16).padStart(8, '0')}`);
226
+
227
+ this.tableHash = tableHash;
228
+ this.layoutHash = layoutHash;
229
+ this.locale = locale;
230
+
231
+ // eslint-disable-next-line no-bitwise
232
+ const isNormal = !(flags & 0x1);
233
+ // eslint-disable-next-line no-bitwise
234
+ const hasRelationshipData = !!(flags & 0x2);
235
+
236
+ this.isNormal = isNormal;
237
+ this.hasRelationshipData = hasRelationshipData;
238
+
239
+ const sectionHeaders: SectionHeader[] = [];
240
+ const sectionHeadersOffset = 204;
241
+ for (let i = 0; i < sectionCount; i += 1) {
242
+ const sectionHeaderOffset = sectionHeadersOffset + i * 40;
243
+
244
+ sectionHeaders.push({
245
+ tactKeyHash: buffer.readBigUInt64LE(sectionHeaderOffset),
246
+ fileOffset: buffer.readUInt32LE(sectionHeaderOffset + 8),
247
+ recordCount: buffer.readUInt32LE(sectionHeaderOffset + 12),
248
+ stringTableSize: buffer.readUInt32LE(sectionHeaderOffset + 16),
249
+ offsetRecordsEnd: buffer.readUInt32LE(sectionHeaderOffset + 20),
250
+ idListSize: buffer.readUInt32LE(sectionHeaderOffset + 24),
251
+ relationshipDataSize: buffer.readUInt32LE(sectionHeaderOffset + 28),
252
+ offsetMapIDCount: buffer.readUInt32LE(sectionHeaderOffset + 32),
253
+ copyTableCount: buffer.readUInt32LE(sectionHeaderOffset + 36),
254
+ });
255
+ }
256
+
257
+ const fields: FieldStructure[] = [];
258
+ const fieldsOffset = 204 + sectionCount * 40;
259
+ for (let i = 0; i < fieldCount; i += 1) {
260
+ const fieldOffset = fieldsOffset + i * 4;
261
+ fields.push({
262
+ size: buffer.readInt16LE(fieldOffset),
263
+ position: buffer.readUInt16LE(fieldOffset + 2),
264
+ });
265
+ }
266
+ this.fields = fields;
267
+
268
+ const fieldsInfo: FieldStorageInfo[] = [];
269
+ const fieldsInfoOffset = fieldsOffset + fieldCount * 4;
270
+ for (let i = 0; i < fieldStorageInfoSize / 24; i += 1) {
271
+ const fieldInfoOffset = fieldsInfoOffset + i * 24;
272
+
273
+ const fieldOffsetBits = buffer.readUInt16LE(fieldInfoOffset);
274
+ const fieldSizeBits = buffer.readUInt16LE(fieldInfoOffset + 2);
275
+ const additionalDataSize = buffer.readUInt32LE(fieldInfoOffset + 4);
276
+ const storageType = buffer.readUInt32LE(fieldInfoOffset + 8);
277
+ const arg1 = buffer.readUInt32LE(fieldInfoOffset + 12);
278
+ const arg2 = buffer.readUInt32LE(fieldInfoOffset + 16);
279
+ const arg3 = buffer.readUInt32LE(fieldInfoOffset + 20);
280
+
281
+ switch (storageType) {
282
+ case 0:
283
+ fieldsInfo.push({
284
+ fieldOffsetBits,
285
+ fieldSizeBits,
286
+ additionalDataSize,
287
+ storageType: 'none',
288
+ });
289
+ break;
290
+ case 1:
291
+ fieldsInfo.push({
292
+ fieldOffsetBits,
293
+ fieldSizeBits,
294
+ additionalDataSize,
295
+ storageType: 'bitpacked',
296
+ bitpackingOffsetBits: arg1,
297
+ bitpackingSizeBits: arg2,
298
+ flags: arg3,
299
+ });
300
+ break;
301
+ case 2:
302
+ fieldsInfo.push({
303
+ fieldOffsetBits,
304
+ fieldSizeBits,
305
+ additionalDataSize,
306
+ storageType: 'commonData',
307
+ defaultValue: arg1,
308
+ });
309
+ break;
310
+ case 3:
311
+ fieldsInfo.push({
312
+ fieldOffsetBits,
313
+ fieldSizeBits,
314
+ additionalDataSize,
315
+ storageType: 'bitpackedIndexed',
316
+ bitpackingOffsetBits: arg1,
317
+ bitpackingSizeBits: arg2,
318
+ });
319
+ break;
320
+ case 4:
321
+ fieldsInfo.push({
322
+ fieldOffsetBits,
323
+ fieldSizeBits,
324
+ additionalDataSize,
325
+ storageType: 'bitpackedIndexedArray',
326
+ bitpackingOffsetBits: arg1,
327
+ bitpackingSizeBits: arg2,
328
+ arrayCount: arg3,
329
+ });
330
+ break;
331
+ case 5:
332
+ fieldsInfo.push({
333
+ fieldOffsetBits,
334
+ fieldSizeBits,
335
+ additionalDataSize,
336
+ storageType: 'bitpackedSigned',
337
+ bitpackingOffsetBits: arg1,
338
+ bitpackingSizeBits: arg2,
339
+ flags: arg3,
340
+ });
341
+ break;
342
+ default:
343
+ throw new Error(`Unknown storage type: ${storageType.toString(16).padStart(8, '0')}`);
344
+ }
345
+ }
346
+ this.fieldsInfo = fieldsInfo;
347
+
348
+ const palletData = new Map<number, number[]>();
349
+ const palletDataOffset = fieldsInfoOffset + fieldStorageInfoSize;
350
+ let palletDataPointer = palletDataOffset;
351
+ for (let i = 0; i < fieldsInfo.length; i += 1) {
352
+ const fieldInfo = fieldsInfo[i];
353
+ if (fieldInfo.storageType === 'bitpackedIndexed' || fieldInfo.storageType === 'bitpackedIndexedArray') {
354
+ const data: number[] = [];
355
+ for (let j = 0; j < fieldInfo.additionalDataSize / 4; j += 1) {
356
+ data.push(buffer.readUInt32LE(palletDataPointer));
357
+ palletDataPointer += 4;
358
+ }
359
+ palletData.set(i, data);
360
+ }
361
+ }
362
+
363
+ assert(
364
+ palletDataPointer === palletDataOffset + palletDataSize,
365
+ `Invalid pallet data size: ${(palletDataPointer - palletDataOffset).toString()} != ${palletDataSize.toString()}`,
366
+ );
367
+
368
+ const commonData = new Map<number, Map<number, number>>();
369
+ const commonDataOffset = palletDataPointer;
370
+ let commonDataPointer = commonDataOffset;
371
+ for (let i = 0; i < fieldsInfo.length; i += 1) {
372
+ const fieldInfo = fieldsInfo[i];
373
+ if (fieldInfo.storageType === 'commonData') {
374
+ const map = new Map<number, number>();
375
+ for (let j = 0; j < fieldInfo.additionalDataSize / 8; j += 1) {
376
+ map.set(
377
+ buffer.readUInt32LE(commonDataPointer),
378
+ buffer.readUInt32LE(commonDataPointer + 4),
379
+ );
380
+ commonDataPointer += 8;
381
+ }
382
+ commonData.set(i, map);
383
+ }
384
+ }
385
+
386
+ assert(
387
+ commonDataPointer === commonDataOffset + commonDataSize,
388
+ `Invalid common data size: ${(commonDataPointer - commonDataOffset).toString()} != ${commonDataSize.toString()}`,
389
+ );
390
+
391
+ const encryptedIDs = new Map<number, number[]>();
392
+ const encryptedRecordsOffset = commonDataPointer;
393
+ let encryptedRecordsPointer = encryptedRecordsOffset;
394
+ for (let i = 0; i < sectionHeaders.length; i += 1) {
395
+ const sectionHeader = sectionHeaders[i];
396
+ if (sectionHeader.tactKeyHash !== 0n) {
397
+ const count = buffer.readUInt32LE(encryptedRecordsPointer);
398
+ encryptedRecordsPointer += 4;
399
+
400
+ const data: number[] = [];
401
+ for (let j = 0; j < count; j += 1) {
402
+ data.push(buffer.readUInt32LE(encryptedRecordsPointer));
403
+ encryptedRecordsPointer += 4;
404
+ }
405
+ encryptedIDs.set(i, data);
406
+ }
407
+ }
408
+
409
+ const stringTable = new Map<number, string>();
410
+ let stringTableDelta = 0;
411
+
412
+ const sectionsOffset = encryptedRecordsPointer;
413
+ let sectionPointer = sectionsOffset;
414
+ const sections = sectionHeaders.map((sectionHeader): Section => {
415
+ assert(
416
+ sectionPointer === sectionHeader.fileOffset,
417
+ `Invalid section offset: ${sectionPointer.toString()} != ${sectionHeader.fileOffset.toString()}`,
418
+ );
419
+
420
+ const sectionSize = (
421
+ isNormal
422
+ ? (sectionHeader.recordCount * recordSize + sectionHeader.stringTableSize)
423
+ : (sectionHeader.offsetRecordsEnd - sectionPointer)
424
+ )
425
+ + sectionHeader.idListSize
426
+ + sectionHeader.copyTableCount * 8
427
+ + sectionHeader.offsetMapIDCount * 10
428
+ + sectionHeader.relationshipDataSize;
429
+
430
+ const recordDataSize = isNormal
431
+ ? recordSize * sectionHeader.recordCount
432
+ : sectionHeader.offsetRecordsEnd - sectionHeader.fileOffset;
433
+
434
+ const isZeroed = blocks.some((block) => {
435
+ const sectionStart = sectionHeader.fileOffset;
436
+ const sectionEnd = sectionStart + sectionSize;
437
+ const blockStart = block.offset;
438
+ const blockEnd = blockStart + block.size;
439
+
440
+ return sectionStart >= blockStart && sectionEnd <= blockEnd;
441
+ });
442
+
443
+ if (isZeroed) {
444
+ sectionPointer += sectionSize;
445
+ if (isNormal) {
446
+ stringTableDelta += sectionHeader.stringTableSize;
447
+ }
448
+ return {
449
+ header: sectionHeader,
450
+ isZeroed,
451
+ recordDataSize,
452
+ records: [],
453
+ idList: [],
454
+ offsetMap: [],
455
+ relationshipMap: new Map(),
456
+ };
457
+ }
458
+
459
+ const records: Buffer[] = [];
460
+ if (isNormal) {
461
+ for (let j = 0; j < sectionHeader.recordCount; j += 1) {
462
+ records.push(buffer.subarray(sectionPointer, sectionPointer + recordSize));
463
+ sectionPointer += recordSize;
464
+ }
465
+
466
+ const stringTableOffset = sectionPointer;
467
+ let stringStartPointer = stringTableOffset;
468
+ while (sectionPointer < stringTableOffset + sectionHeader.stringTableSize) {
469
+ if (buffer[sectionPointer] === 0x00) {
470
+ if (sectionPointer - stringStartPointer > 0) {
471
+ const string = buffer.toString('utf-8', stringStartPointer, sectionPointer);
472
+ stringTable.set(
473
+ stringStartPointer - stringTableOffset + stringTableDelta,
474
+ string,
475
+ );
476
+ }
477
+
478
+ stringStartPointer = sectionPointer + 1;
479
+ }
480
+ sectionPointer += 1;
481
+ }
482
+ stringTableDelta += sectionHeader.stringTableSize;
483
+ } else {
484
+ sectionPointer = sectionHeader.offsetRecordsEnd;
485
+ }
486
+
487
+ const idList: number[] = [];
488
+ for (let j = 0; j < sectionHeader.idListSize / 4; j += 1) {
489
+ idList.push(buffer.readUInt32LE(sectionPointer));
490
+ sectionPointer += 4;
491
+ }
492
+
493
+ for (let j = 0; j < sectionHeader.copyTableCount; j += 1) {
494
+ const dst = buffer.readUInt32LE(sectionPointer);
495
+ const src = buffer.readUInt32LE(sectionPointer + 4);
496
+ this.copyTable.set(dst, src);
497
+
498
+ sectionPointer += 8;
499
+ }
500
+
501
+ const offsetMap: OffsetMapEntry[] = [];
502
+ for (let j = 0; j < sectionHeader.offsetMapIDCount; j += 1) {
503
+ const offset = buffer.readUInt32LE(sectionPointer);
504
+ const size = buffer.readUInt16LE(sectionPointer + 4);
505
+ const data = buffer.subarray(offset, offset + size);
506
+
507
+ sectionPointer += 6;
508
+
509
+ offsetMap.push({
510
+ offset,
511
+ size,
512
+ data,
513
+ });
514
+ }
515
+
516
+ const offsetMapIDList: number[] = [];
517
+
518
+ if (hasRelationshipData) {
519
+ // Note, if flag 0x02 is set,
520
+ // offset_map_id_list will appear before relationship_map instead
521
+ for (let j = 0; j < sectionHeader.offsetMapIDCount; j += 1) {
522
+ offsetMapIDList.push(buffer.readUInt32LE(sectionPointer));
523
+ sectionPointer += 4;
524
+ }
525
+ }
526
+
527
+ const relationshipMap = new Map<number, number>();
528
+ if (sectionHeader.relationshipDataSize > 0) {
529
+ const numEntries = buffer.readUInt32LE(sectionPointer);
530
+ // const relationshipMinID = buffer.readUInt32LE(sectionPointer + 4);
531
+ // const relationshipMaxID = buffer.readUInt32LE(sectionPointer + 8);
532
+
533
+ sectionPointer += 12;
534
+
535
+ for (let j = 0; j < numEntries; j += 1) {
536
+ const foreignID = buffer.readUInt32LE(sectionPointer);
537
+ const recordIndex = buffer.readUInt32LE(sectionPointer + 4);
538
+ sectionPointer += 8;
539
+
540
+ relationshipMap.set(recordIndex, foreignID);
541
+ }
542
+ }
543
+
544
+ if (!hasRelationshipData) {
545
+ // see if (hasRelationshipData)
546
+ for (let j = 0; j < sectionHeader.offsetMapIDCount; j += 1) {
547
+ offsetMapIDList.push(buffer.readUInt32LE(sectionPointer));
548
+ sectionPointer += 4;
549
+ }
550
+ }
551
+
552
+ return {
553
+ header: sectionHeader,
554
+ isZeroed,
555
+ recordDataSize,
556
+ records,
557
+ idList,
558
+ offsetMap,
559
+ relationshipMap,
560
+ };
561
+ });
562
+
563
+ const totalRecordDataSize = sections
564
+ .reduce((acc, section) => acc + section.recordDataSize, 0);
565
+ sections.forEach((section) => {
566
+ const {
567
+ header, isZeroed, records, idList, offsetMap, relationshipMap,
568
+ } = section;
569
+
570
+ const prevRecordDataSize = sections
571
+ .filter((s) => s.header.fileOffset < header.fileOffset)
572
+ .reduce((acc, s) => acc + s.recordDataSize, 0);
573
+
574
+ if (isZeroed) {
575
+ return;
576
+ }
577
+
578
+ for (let recordIndex = 0; recordIndex < header.recordCount; recordIndex += 1) {
579
+ let recordID = idList.length > 0 ? idList[recordIndex] : undefined;
580
+ const recordBuffer = isNormal
581
+ ? records[recordIndex]
582
+ : offsetMap[recordIndex].data;
583
+
584
+ if (isNormal) {
585
+ const recordData = fieldsInfo.map((fieldInfo, fieldIndex): ParsedField => {
586
+ switch (fieldInfo.storageType) {
587
+ case 'none': {
588
+ const value = readBitpackedValue(
589
+ recordBuffer,
590
+ fieldInfo.fieldOffsetBits,
591
+ fieldInfo.fieldSizeBits,
592
+ );
593
+
594
+ if (typeof value === 'bigint') {
595
+ return {
596
+ type: 'none',
597
+ data: value,
598
+ };
599
+ }
600
+
601
+ if (recordID === undefined && fieldIndex === idIndex) {
602
+ recordID = value;
603
+ }
604
+
605
+ // eslint-disable-next-line no-bitwise
606
+ const fieldOffset = fieldInfo.fieldOffsetBits >>> 3;
607
+ const offset = prevRecordDataSize - totalRecordDataSize
608
+ + (recordSize * recordIndex) + fieldOffset + value;
609
+
610
+ return {
611
+ type: 'none',
612
+ data: value,
613
+ string: stringTable.get(offset),
614
+ };
615
+ }
616
+ case 'commonData': {
617
+ const value = (
618
+ recordID !== undefined
619
+ ? commonData.get(fieldIndex)?.get(recordID)
620
+ : undefined
621
+ )
622
+ ?? fieldInfo.defaultValue;
623
+ return {
624
+ type: 'commonData',
625
+ data: value,
626
+ };
627
+ }
628
+ case 'bitpacked':
629
+ case 'bitpackedSigned':
630
+ case 'bitpackedIndexed':
631
+ case 'bitpackedIndexedArray': {
632
+ let value = readBitpackedValue(
633
+ recordBuffer,
634
+ fieldInfo.fieldOffsetBits,
635
+ fieldInfo.fieldSizeBits,
636
+ fieldInfo.storageType === 'bitpackedSigned',
637
+ );
638
+
639
+ assert(typeof value === 'number', 'Bitpacked value must be a number');
640
+
641
+ if (fieldInfo.storageType === 'bitpackedIndexedArray') {
642
+ const fieldPalletData = palletData.get(fieldIndex);
643
+
644
+ assert(fieldPalletData, `No pallet data for field ${fieldIndex.toString()}`);
645
+
646
+ const data: number[] = [];
647
+ const palletStart = value * fieldInfo.arrayCount;
648
+
649
+ for (let j = 0; j < fieldInfo.arrayCount; j += 1) {
650
+ data.push(fieldPalletData[palletStart + j]);
651
+ }
652
+
653
+ return {
654
+ type: 'bitpackedArray',
655
+ data,
656
+ };
657
+ }
658
+
659
+ if (fieldInfo.storageType === 'bitpackedIndexed') {
660
+ const fieldPalletData = palletData.get(fieldIndex);
661
+
662
+ assert(fieldPalletData, `No pallet data for field ${fieldIndex.toString()}`);
663
+
664
+ value = fieldPalletData[value];
665
+ }
666
+
667
+ if (recordID === undefined && fieldIndex === idIndex) {
668
+ recordID = value;
669
+ }
670
+
671
+ return {
672
+ type: 'bitpacked',
673
+ data: value,
674
+ };
675
+ }
676
+ default:
677
+ fieldInfo satisfies never;
678
+ throw new Error('Unreachable');
679
+ }
680
+ });
681
+
682
+ assert(recordID !== undefined, 'No record ID found');
683
+
684
+ this.rows.set(recordID, recordData);
685
+
686
+ const foreignID = relationshipMap.get(recordIndex);
687
+ if (foreignID !== undefined) {
688
+ this.relationships.set(recordID, foreignID);
689
+ }
690
+ } else {
691
+ const recordData = {
692
+ type: 'sparse',
693
+ data: recordBuffer,
694
+ } satisfies SparseRow;
695
+
696
+ // for now (10.2.5), every sparse table has idList
697
+ // so we can safely assume recordID is not undefined
698
+ assert(recordID !== undefined, 'No record ID found');
699
+
700
+ this.rows.set(recordID, recordData);
701
+
702
+ const foreignID = relationshipMap.get(recordIndex);
703
+ if (foreignID !== undefined) {
704
+ this.relationships.set(recordID, foreignID);
705
+ }
706
+ }
707
+ }
708
+ });
709
+
710
+ const entries = adb?.tableEntries.get(tableHash);
711
+ entries
712
+ ?.filter((entry) => entry.pushID !== -1)
713
+ .sort((a, b) => a.pushID - b.pushID)
714
+ .forEach((entry) => {
715
+ switch (entry.recordState) {
716
+ case 1: // Valid
717
+ this.hotfixes.set(entry.recordID, { type: 'modify', data: entry.data });
718
+ break;
719
+ case 2: // Delete
720
+ this.hotfixes.set(entry.recordID, { type: 'delete' });
721
+ break;
722
+ case 3: // Invalid
723
+ this.hotfixes.delete(entry.recordID);
724
+ break;
725
+ case 4: // NotPublic
726
+ break;
727
+ default:
728
+ throw new Error(`Unknown record state: ${entry.recordState.toString()}`);
729
+ }
730
+ });
731
+ }
732
+
733
+ getAllIDs(): number[] {
734
+ return [...this.rows.keys(), ...this.copyTable.keys()];
735
+ }
736
+
737
+ getRowData(id: number): ParsedField[] | SparseRow | undefined {
738
+ const hotfix = this.hotfixes.get(id);
739
+ if (hotfix) {
740
+ switch (hotfix.type) {
741
+ case 'modify':
742
+ return {
743
+ type: 'sparse',
744
+ data: hotfix.data,
745
+ };
746
+ case 'delete':
747
+ return undefined;
748
+ default:
749
+ hotfix satisfies never;
750
+ throw new Error('Unreachable');
751
+ }
752
+ }
753
+
754
+ const dst = this.copyTable.get(id);
755
+ if (dst !== undefined) {
756
+ return this.rows.get(dst);
757
+ }
758
+ return this.rows.get(id);
759
+ }
760
+
761
+ getRowRelationship(id: number): number | undefined {
762
+ const dst = this.copyTable.get(id);
763
+ if (dst !== undefined) {
764
+ return this.relationships.get(dst);
765
+ }
766
+ return this.relationships.get(id);
767
+ }
768
+ }
769
+
770
+ export type {
771
+ FieldStructure,
772
+ FieldStorageInfo,
773
+ FieldStorageInfoCompressionNone,
774
+ FieldStorageInfoCompressionBitpacked,
775
+ FieldStorageInfoCompressionCommonData,
776
+ FieldStorageInfoCompressionBitpackedIndexed,
777
+ FieldStorageInfoCompressionBitpackedIndexedArray,
778
+ FieldStorageInfoCompressionBitpackedSigned,
779
+ ParsedField,
780
+ ParsedFieldNone,
781
+ ParsedFieldCommonData,
782
+ ParsedFieldBitpacked,
783
+ ParsedFieldBitpackedArray,
784
+ SparseRow,
785
+ Hotfix,
786
+ HotfixModify,
787
+ HotfixDelete,
788
+ };