@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.
- package/dist/adb.d.ts +17 -0
- package/dist/adb.d.ts.map +1 -0
- package/dist/blte.d.ts +25 -0
- package/dist/blte.d.ts.map +1 -0
- package/dist/client.d.ts +84 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/dbd.d.ts +26 -0
- package/dist/dbd.d.ts.map +1 -0
- package/dist/fetcher.d.ts +21 -0
- package/dist/fetcher.d.ts.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +9 -261
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -0
- package/dist/jenkins96.d.ts +3 -0
- package/dist/jenkins96.d.ts.map +1 -0
- package/dist/parsers/archiveIndex.d.ts +9 -0
- package/dist/parsers/archiveIndex.d.ts.map +1 -0
- package/dist/parsers/config.d.ts +40 -0
- package/dist/parsers/config.d.ts.map +1 -0
- package/dist/parsers/encodingFile.d.ts +11 -0
- package/dist/parsers/encodingFile.d.ts.map +1 -0
- package/dist/parsers/productConfig.d.ts +21 -0
- package/dist/parsers/productConfig.d.ts.map +1 -0
- package/dist/parsers/rootFile.d.ts +45 -0
- package/dist/parsers/rootFile.d.ts.map +1 -0
- package/dist/salsa20.d.ts +14 -0
- package/dist/salsa20.d.ts.map +1 -0
- package/dist/store.d.ts +9 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/test/salsa20.test.d.ts +2 -0
- package/dist/test/salsa20.test.d.ts.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/wdc.d.ts +104 -0
- package/dist/wdc.d.ts.map +1 -0
- package/package.json +5 -4
- package/src/adb.ts +70 -0
- package/src/blte.ts +220 -0
- package/src/client.ts +411 -0
- package/src/dbd.ts +427 -0
- package/src/fetcher.ts +223 -0
- package/src/index.ts +44 -0
- package/src/jenkins96.ts +75 -0
- package/src/parsers/archiveIndex.ts +119 -0
- package/src/parsers/config.ts +75 -0
- package/src/parsers/encodingFile.ts +159 -0
- package/src/parsers/productConfig.ts +57 -0
- package/src/parsers/rootFile.ts +172 -0
- package/src/salsa20.ts +143 -0
- package/src/store.ts +37 -0
- package/src/test/salsa20.test.ts +522 -0
- package/src/utils.ts +77 -0
- 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
|
+
};
|