@rhyster/wow-casc-dbc 2.7.6 → 2.8.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/dist/client.d.ts +4 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/index.cjs +99 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.mts +19 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +99 -9
- package/dist/index.mjs.map +1 -1
- package/dist/parsers/installFile.d.ts +19 -0
- package/dist/parsers/installFile.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/client.ts +43 -4
- package/src/index.ts +2 -0
- package/src/parsers/installFile.ts +113 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface InstallTag {
|
|
2
|
+
name: string;
|
|
3
|
+
type: number;
|
|
4
|
+
files: boolean[];
|
|
5
|
+
}
|
|
6
|
+
interface InstallFile {
|
|
7
|
+
name: string;
|
|
8
|
+
hash: string;
|
|
9
|
+
size: number;
|
|
10
|
+
tags: InstallTag[];
|
|
11
|
+
}
|
|
12
|
+
interface InstallData {
|
|
13
|
+
tags: InstallTag[];
|
|
14
|
+
files: InstallFile[];
|
|
15
|
+
}
|
|
16
|
+
declare const parseInstallFile: (inputBuffer: Buffer, eKey: string, cKey: string) => InstallData;
|
|
17
|
+
export default parseInstallFile;
|
|
18
|
+
export type { InstallFile, InstallData };
|
|
19
|
+
//# sourceMappingURL=installFile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installFile.d.ts","sourceRoot":"","sources":["../../src/parsers/installFile.ts"],"names":[],"mappings":"AAcA,UAAU,UAAU;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,EAAE,CAAC;CACpB;AAED,UAAU,WAAW;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,UAAU,WAAW;IACjB,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,KAAK,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,QAAA,MAAM,gBAAgB,gBAAiB,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAG,WA4E3E,CAAC;AAEF,eAAe,gBAAgB,CAAC;AAEhC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC"}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -15,6 +15,7 @@ import getNameHash from './jenkins96.ts';
|
|
|
15
15
|
import parseArchiveIndex from './parsers/archiveIndex.ts';
|
|
16
16
|
import { parseCDNConfig, parseBuildConfig } from './parsers/config.ts';
|
|
17
17
|
import parseEncodingFile from './parsers/encodingFile.ts';
|
|
18
|
+
import parseInstallFile from './parsers/installFile.ts';
|
|
18
19
|
import { parseProductVersions, parseProductCDNs } from './parsers/productConfig.ts';
|
|
19
20
|
import parseRootFile, { LocaleFlags, ContentFlags } from './parsers/rootFile.ts';
|
|
20
21
|
import { resolveCDNHost, formatFileSize } from './utils.ts';
|
|
@@ -24,6 +25,7 @@ import type ADBReader from './adb.ts';
|
|
|
24
25
|
import type { MissingKeyBlock } from './blte.ts';
|
|
25
26
|
import type { ArchiveIndex } from './parsers/archiveIndex.ts';
|
|
26
27
|
import type { EncodingData } from './parsers/encodingFile.ts';
|
|
28
|
+
import type { InstallFile, InstallData } from './parsers/installFile.ts';
|
|
27
29
|
import type { Version } from './parsers/productConfig.ts';
|
|
28
30
|
import type { FileInfo, RootData } from './parsers/rootFile.ts';
|
|
29
31
|
|
|
@@ -32,6 +34,7 @@ interface ClientPreloadData {
|
|
|
32
34
|
archives: Map<string, ArchiveIndex>,
|
|
33
35
|
encoding: EncodingData,
|
|
34
36
|
rootFile: RootData,
|
|
37
|
+
install: InstallData,
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
interface FileFetchResultFull {
|
|
@@ -196,11 +199,23 @@ export default class CASCClient {
|
|
|
196
199
|
const encoding = parseEncodingFile(encodingBuffer, encodingEKey, encodingCKey);
|
|
197
200
|
this.log(LogLevel.info, `Parsed encoding table (${encoding.cKey2EKey.size.toString()} entries)`);
|
|
198
201
|
|
|
202
|
+
const getBuildConfigKeys = (configText: string): [string, string] => {
|
|
203
|
+
if (configText.includes(' ')) {
|
|
204
|
+
const [cKey, eKey] = configText.split(' ');
|
|
205
|
+
return [cKey, eKey];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const cKey = configText;
|
|
209
|
+
const eKeys = encoding.cKey2EKey.get(cKey);
|
|
210
|
+
assert(eKeys !== undefined, `Failing to find encoding key for ${cKey}`);
|
|
211
|
+
|
|
212
|
+
const eKey = typeof eKeys === 'string' ? eKeys : eKeys[0];
|
|
213
|
+
|
|
214
|
+
return [cKey, eKey];
|
|
215
|
+
};
|
|
216
|
+
|
|
199
217
|
this.log(LogLevel.info, 'Loading root table...');
|
|
200
|
-
const rootCKey = buildConfig.root;
|
|
201
|
-
const rootEKeys = encoding.cKey2EKey.get(rootCKey);
|
|
202
|
-
assert(rootEKeys !== undefined, 'Failing to find EKey for root table.');
|
|
203
|
-
const rootEKey = typeof rootEKeys === 'string' ? rootEKeys : rootEKeys[0];
|
|
218
|
+
const [rootCKey, rootEKey] = getBuildConfigKeys(buildConfig.root);
|
|
204
219
|
const rootBuffer = await getDataFile(prefixes, rootEKey, 'build', this.version.BuildConfig, {
|
|
205
220
|
name: 'root',
|
|
206
221
|
showProgress: this.logLevel >= LogLevel.info,
|
|
@@ -212,11 +227,25 @@ export default class CASCClient {
|
|
|
212
227
|
const rootFile = parseRootFile(rootBuffer, rootEKey, rootCKey);
|
|
213
228
|
this.log(LogLevel.info, `Parsed root file (${rootFile.fileDataID2CKey.size.toString()} entries, ${rootFile.nameHash2FileDataID.size.toString()} hashes)`);
|
|
214
229
|
|
|
230
|
+
this.log(LogLevel.info, 'Loading install manifest...');
|
|
231
|
+
const [installCKey, installEKey] = getBuildConfigKeys(buildConfig.install);
|
|
232
|
+
const installBuffer = await getDataFile(prefixes, installEKey, 'build', this.version.BuildConfig, {
|
|
233
|
+
name: 'install',
|
|
234
|
+
showProgress: this.logLevel >= LogLevel.info,
|
|
235
|
+
showAttemptFail: this.logLevel >= LogLevel.warn,
|
|
236
|
+
});
|
|
237
|
+
this.log(LogLevel.info, `Loaded install manifest (${formatFileSize(installBuffer.byteLength)})`);
|
|
238
|
+
|
|
239
|
+
this.log(LogLevel.info, 'Parsing install manifest...');
|
|
240
|
+
const install = parseInstallFile(installBuffer, installEKey, installCKey);
|
|
241
|
+
this.log(LogLevel.info, `Parsed install manifest (${install.tags.length.toString()} tags, ${install.files.length.toString()} files)`);
|
|
242
|
+
|
|
215
243
|
this.preload = {
|
|
216
244
|
prefixes,
|
|
217
245
|
archives,
|
|
218
246
|
encoding,
|
|
219
247
|
rootFile,
|
|
248
|
+
install,
|
|
220
249
|
};
|
|
221
250
|
}
|
|
222
251
|
|
|
@@ -337,6 +366,14 @@ export default class CASCClient {
|
|
|
337
366
|
return rootFile.fileDataID2CKey.get(fileDataID);
|
|
338
367
|
}
|
|
339
368
|
|
|
369
|
+
getContentKeysFromInstall(name: string): InstallFile[] | undefined {
|
|
370
|
+
assert(this.preload, 'Client not initialized');
|
|
371
|
+
|
|
372
|
+
const { install } = this.preload;
|
|
373
|
+
|
|
374
|
+
return install.files.filter((file) => file.name === name);
|
|
375
|
+
}
|
|
376
|
+
|
|
340
377
|
async getFileByContentKey(cKey: string, allowMissingKey?: false): Promise<FileFetchResultFull>;
|
|
341
378
|
async getFileByContentKey(cKey: string, allowMissingKey: true): Promise<FileFetchResult>;
|
|
342
379
|
async getFileByContentKey(cKey: string, allowMissingKey = false): Promise<FileFetchResult> {
|
|
@@ -402,6 +439,8 @@ export type {
|
|
|
402
439
|
ClientPreloadData,
|
|
403
440
|
ArchiveIndex,
|
|
404
441
|
EncodingData,
|
|
442
|
+
InstallFile,
|
|
443
|
+
InstallData,
|
|
405
444
|
RootData,
|
|
406
445
|
FileInfo,
|
|
407
446
|
FileFetchResultFull,
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
import BLTEReader from '../blte.ts';
|
|
5
|
+
|
|
6
|
+
const INSTALL_MAGIC = 0x494e;
|
|
7
|
+
|
|
8
|
+
const MAGIC_OFFSET = 0;
|
|
9
|
+
const VERSION_OFFSET = 2;
|
|
10
|
+
const HASH_SIZE_OFFSET = 3;
|
|
11
|
+
const NUM_TAGS_OFFSET = 4;
|
|
12
|
+
const NUM_ENTRIES_OFFSET = 6;
|
|
13
|
+
const TAGS_OFFSET = 10;
|
|
14
|
+
|
|
15
|
+
interface InstallTag {
|
|
16
|
+
name: string,
|
|
17
|
+
type: number,
|
|
18
|
+
files: boolean[],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface InstallFile {
|
|
22
|
+
name: string,
|
|
23
|
+
hash: string,
|
|
24
|
+
size: number,
|
|
25
|
+
tags: InstallTag[],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface InstallData {
|
|
29
|
+
tags: InstallTag[],
|
|
30
|
+
files: InstallFile[],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const parseInstallFile = (inputBuffer: Buffer, eKey: string, cKey: string): InstallData => {
|
|
34
|
+
const reader = new BLTEReader(inputBuffer, eKey);
|
|
35
|
+
reader.processBytes();
|
|
36
|
+
|
|
37
|
+
const { buffer } = reader;
|
|
38
|
+
|
|
39
|
+
const installHash = crypto.createHash('md5').update(buffer).digest('hex');
|
|
40
|
+
assert(installHash === cKey, `Invalid root hash: expected ${cKey}, got ${installHash}`);
|
|
41
|
+
|
|
42
|
+
const magic = buffer.readUInt16BE(MAGIC_OFFSET);
|
|
43
|
+
assert(magic === INSTALL_MAGIC, `Invalid install magic: ${magic.toString(16).padStart(4, '0')}`);
|
|
44
|
+
|
|
45
|
+
const version = buffer.readUInt8(VERSION_OFFSET);
|
|
46
|
+
const hashSize = buffer.readUInt8(HASH_SIZE_OFFSET);
|
|
47
|
+
const numTags = buffer.readUInt16BE(NUM_TAGS_OFFSET);
|
|
48
|
+
const numEntries = buffer.readUInt32BE(NUM_ENTRIES_OFFSET);
|
|
49
|
+
|
|
50
|
+
assert(version === 1, `Invalid install version: ${version.toString()}`);
|
|
51
|
+
|
|
52
|
+
let pointer = TAGS_OFFSET;
|
|
53
|
+
|
|
54
|
+
const tags: InstallTag[] = [];
|
|
55
|
+
for (let i = 0; i < numTags; i += 1) {
|
|
56
|
+
const startOffset = pointer;
|
|
57
|
+
while (buffer[pointer] !== 0x00) {
|
|
58
|
+
pointer += 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const name = buffer.toString('utf-8', startOffset, pointer);
|
|
62
|
+
pointer += 1;
|
|
63
|
+
|
|
64
|
+
const type = buffer.readUInt16BE(pointer);
|
|
65
|
+
pointer += 2;
|
|
66
|
+
|
|
67
|
+
const files = [];
|
|
68
|
+
const finalOffset = pointer + Math.ceil(numEntries / 8);
|
|
69
|
+
while (pointer < finalOffset) {
|
|
70
|
+
const byte = buffer.readUInt8(pointer);
|
|
71
|
+
pointer += 1;
|
|
72
|
+
|
|
73
|
+
for (let j = 7; j >= 0; j -= 1) {
|
|
74
|
+
// eslint-disable-next-line no-bitwise
|
|
75
|
+
files.push((byte & (1 << j)) > 0);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
tags.push({ name, type, files });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const files: InstallFile[] = [];
|
|
83
|
+
for (let i = 0; i < numEntries; i += 1) {
|
|
84
|
+
const startOffset = pointer;
|
|
85
|
+
while (buffer[pointer] !== 0x00) {
|
|
86
|
+
pointer += 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const name = buffer.toString('utf-8', startOffset, pointer);
|
|
90
|
+
pointer += 1;
|
|
91
|
+
|
|
92
|
+
const hash = buffer.toString('hex', pointer, pointer + hashSize);
|
|
93
|
+
pointer += hashSize;
|
|
94
|
+
|
|
95
|
+
const size = buffer.readUInt32BE(pointer);
|
|
96
|
+
pointer += 4;
|
|
97
|
+
|
|
98
|
+
const fileTags = tags.filter((tag) => tag.files[i]);
|
|
99
|
+
|
|
100
|
+
files.push({
|
|
101
|
+
name,
|
|
102
|
+
hash,
|
|
103
|
+
size,
|
|
104
|
+
tags: fileTags,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { tags, files };
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default parseInstallFile;
|
|
112
|
+
|
|
113
|
+
export type { InstallFile, InstallData };
|