@rhyster/wow-casc-dbc 2.6.18 → 2.7.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.
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 +72 -107
  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 +72 -107
  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/dbd.ts ADDED
@@ -0,0 +1,427 @@
1
+ import assert from 'node:assert';
2
+
3
+ import type WDCReader from './wdc.ts';
4
+
5
+ interface Manifest {
6
+ tableHash: string,
7
+ tableName?: string,
8
+ db2FileDataID?: number,
9
+ dbcFileDataID?: number,
10
+ }
11
+
12
+ interface Column {
13
+ name: string,
14
+ type: string,
15
+ isID: boolean,
16
+ isInline: boolean,
17
+ isRelation: boolean,
18
+ isSigned: boolean,
19
+ size?: number,
20
+ arraySize?: number,
21
+ }
22
+
23
+ type BasicColumnData = number | bigint | string | undefined;
24
+
25
+ type ColumnData = BasicColumnData | BasicColumnData[];
26
+
27
+ const PATTERN_COLUMN = /^(int|float|locstring|string)(<[^:]+::[^>]+>)?\s([^\s]+)/;
28
+ const PATTERN_LAYOUT = /^LAYOUT\s(.*)/;
29
+ const PATTERN_FIELD = /^(\$([^$]+)\$)?([^<[]+)(<(u|)(\d+)>)?(\[(\d+)\])?$/;
30
+
31
+ const castIntegerBySize = (
32
+ value: number,
33
+ src: number,
34
+ srcSigned: boolean,
35
+ dst: number,
36
+ dstSigned: boolean,
37
+ ): number => {
38
+ const castBuffer = Buffer.alloc(6);
39
+
40
+ if (srcSigned) {
41
+ castBuffer.writeIntLE(value, 0, src);
42
+ } else {
43
+ castBuffer.writeUIntLE(value, 0, src);
44
+ }
45
+
46
+ return dstSigned ? castBuffer.readIntLE(0, dst) : castBuffer.readUIntLE(0, dst);
47
+ };
48
+
49
+ const castFloat = (value: number, src: number, srcSigned: boolean): number => {
50
+ const castBuffer = Buffer.alloc(4);
51
+
52
+ if (srcSigned) {
53
+ castBuffer.writeIntLE(value, 0, src);
54
+ } else {
55
+ castBuffer.writeUIntLE(value, 0, src);
56
+ }
57
+
58
+ const result = castBuffer.readFloatLE(0);
59
+ return Math.round(result * 100) / 100;
60
+ };
61
+
62
+ const castBigInt64 = (value: bigint, srcSigned: boolean, dstSigned: boolean): bigint => {
63
+ const castBuffer = Buffer.alloc(8);
64
+
65
+ if (srcSigned) {
66
+ castBuffer.writeBigInt64LE(value, 0);
67
+ } else {
68
+ castBuffer.writeBigUInt64LE(value, 0);
69
+ }
70
+
71
+ return dstSigned ? castBuffer.readBigInt64LE(0) : castBuffer.readBigUInt64LE(0);
72
+ };
73
+
74
+ const getCastBuffer = (value: bigint, srcSize: number, dstSize: number): Buffer => {
75
+ const castBuffer = Buffer.alloc(dstSize);
76
+ let remain = value;
77
+
78
+ // eslint-disable-next-line no-bitwise
79
+ for (let i = 0; i < srcSize && remain > 0n; i += 1, remain >>= 8n) {
80
+ const byte = Number(BigInt.asUintN(8, remain));
81
+ castBuffer.writeUInt8(byte, i);
82
+ }
83
+
84
+ return castBuffer;
85
+ };
86
+
87
+ export default class DBDParser {
88
+ public readonly wdc: WDCReader;
89
+
90
+ public readonly definitions = new Map<string, string>();
91
+
92
+ public columns: Column[] = [];
93
+
94
+ private cache = new Map<number, Record<string, ColumnData>>();
95
+
96
+ private constructor(wdc: WDCReader) {
97
+ this.wdc = wdc;
98
+ }
99
+
100
+ private async init(): Promise<void> {
101
+ const manifestsURL = 'https://raw.githubusercontent.com/wowdev/WoWDBDefs/master/manifest.json';
102
+ const manifests = await (await fetch(manifestsURL)).json() as Manifest[];
103
+
104
+ const tableHashHex = this.wdc.tableHash.toString(16).padStart(8, '0').toLowerCase();
105
+ const manifest = manifests.find((v) => v.tableHash.toLowerCase() === tableHashHex);
106
+
107
+ assert(manifest?.tableName !== undefined, `No manifest found for table hash ${tableHashHex}`);
108
+
109
+ const url = `https://raw.githubusercontent.com/wowdev/WoWDBDefs/master/definitions/${manifest.tableName}.dbd`;
110
+ const text = await (await fetch(url)).text();
111
+ const lines = text.split('\n').map((v) => v.trim());
112
+
113
+ const chunks = lines.reduce<string[][]>((acc, line) => {
114
+ if (line.length > 0) {
115
+ acc[acc.length - 1].push(line);
116
+ } else {
117
+ acc.push([]);
118
+ }
119
+ return acc;
120
+ }, [[]]).filter((chunk) => chunk.length > 0);
121
+
122
+ const columnsChunk = chunks.shift();
123
+ assert(columnsChunk?.[0] === 'COLUMNS', 'No column definitions found');
124
+
125
+ columnsChunk.shift();
126
+ columnsChunk.forEach((line) => {
127
+ const match = PATTERN_COLUMN.exec(line);
128
+ if (match) {
129
+ const [, type, , name] = match;
130
+ this.definitions.set(name.replace('?', ''), type);
131
+ }
132
+ });
133
+
134
+ const layoutHashHex = this.wdc.layoutHash.toString(16).padStart(8, '0').toLowerCase();
135
+ const versionChunk = chunks.find((chunk) => chunk.find((line) => {
136
+ const layoutsMatch = PATTERN_LAYOUT.exec(line);
137
+ const layouts = layoutsMatch?.[1].split(',').map((v) => v.trim().toLowerCase());
138
+ return layouts?.includes(layoutHashHex);
139
+ }));
140
+
141
+ assert(versionChunk, `No version definition found for layout hash ${layoutHashHex}`);
142
+
143
+ versionChunk.forEach((line) => {
144
+ if (line.startsWith('LAYOUT') || line.startsWith('BUILD') || line.startsWith('COMMENT')) {
145
+ return;
146
+ }
147
+
148
+ const match = PATTERN_FIELD.exec(line);
149
+ if (match) {
150
+ const [
151
+ , ,
152
+ annotationsText,
153
+ name, ,
154
+ unsigned,
155
+ sizeText, ,
156
+ arraySizeText,
157
+ ] = match;
158
+ const type = this.definitions.get(name);
159
+
160
+ assert(type !== undefined, `No type found for column ${name}`);
161
+
162
+ const annotations = annotationsText ? annotationsText.split(',').map((v) => v.trim()) : [];
163
+ const size = sizeText ? parseInt(sizeText, 10) : undefined;
164
+ const arraySize = arraySizeText ? parseInt(arraySizeText, 10) : undefined;
165
+
166
+ const isID = !!annotations.includes('id');
167
+ const isInline = !annotations.includes('noninline');
168
+ const isRelation = !!annotations.includes('relation');
169
+ const isSigned = !unsigned;
170
+
171
+ this.columns.push({
172
+ name,
173
+ type,
174
+ isID,
175
+ isInline,
176
+ isRelation,
177
+ isSigned,
178
+ size,
179
+ arraySize,
180
+ });
181
+ }
182
+ });
183
+ }
184
+
185
+ static async parse(wdc: WDCReader): Promise<DBDParser> {
186
+ const parser = new DBDParser(wdc);
187
+
188
+ await parser.init();
189
+
190
+ return parser;
191
+ }
192
+
193
+ getAllIDs(): number[] {
194
+ return this.wdc.getAllIDs();
195
+ }
196
+
197
+ getRowData(id: number): Record<string, ColumnData> | undefined {
198
+ if (this.cache.has(id)) {
199
+ return structuredClone(this.cache.get(id));
200
+ }
201
+
202
+ const row = this.wdc.getRowData(id);
203
+ if (!row) {
204
+ return undefined;
205
+ }
206
+
207
+ const data: Record<string, ColumnData> = {};
208
+ if (Array.isArray(row)) {
209
+ let fieldIndex = 0;
210
+ this.columns.forEach((column) => {
211
+ if (column.isID) {
212
+ data[column.name] = id;
213
+
214
+ if (column.isInline) {
215
+ fieldIndex += 1;
216
+ }
217
+ } else if (column.isInline) {
218
+ assert(row.length > fieldIndex, `No value found for column ${column.name}`);
219
+
220
+ const cell = row[fieldIndex];
221
+ const fieldInfo = this.wdc.fieldsInfo[fieldIndex];
222
+ const srcSigned = fieldInfo.storageType === 'bitpackedSigned';
223
+ const srcSize = (
224
+ fieldInfo.storageType === 'none'
225
+ || fieldInfo.storageType === 'bitpacked'
226
+ || fieldInfo.storageType === 'bitpackedSigned'
227
+ )
228
+ ? Math.ceil(fieldInfo.fieldSizeBits / 8)
229
+ : 4;
230
+ const dstSize = column.size !== undefined
231
+ ? Math.ceil(column.size / 8)
232
+ : undefined;
233
+
234
+ if (cell.type === 'bitpackedArray') {
235
+ data[column.name] = cell.data.map((v) => {
236
+ if (column.type === 'float') {
237
+ return castFloat(v, srcSize, srcSigned);
238
+ }
239
+ if (dstSize !== undefined) {
240
+ return castIntegerBySize(
241
+ v,
242
+ srcSize,
243
+ srcSigned,
244
+ dstSize,
245
+ column.isSigned,
246
+ );
247
+ }
248
+ return v;
249
+ });
250
+ } else if (column.type === 'string' || column.type === 'locstring') {
251
+ if (cell.data > 0) {
252
+ assert(cell.type === 'none', `Invalid data type for string column ${column.name}`);
253
+ assert(typeof cell.string === 'string', `Missing string for string column ${column.name}`);
254
+
255
+ data[column.name] = cell.string;
256
+ }
257
+ } else if (column.type === 'float') {
258
+ if (column.arraySize !== undefined) {
259
+ const castBuffer = getCastBuffer(
260
+ typeof cell.data === 'number' ? BigInt(cell.data) : cell.data,
261
+ srcSize,
262
+ 4 * column.arraySize,
263
+ );
264
+
265
+ const values: number[] = [];
266
+ for (let i = 0; i < column.arraySize; i += 1) {
267
+ const value = castBuffer.readFloatLE(i * 4);
268
+ values.push(Math.round(value * 100) / 100);
269
+ }
270
+
271
+ data[column.name] = values;
272
+ } else {
273
+ assert(typeof cell.data === 'number', `Invalid data type for float column ${column.name}`);
274
+
275
+ data[column.name] = castFloat(cell.data, srcSize, srcSigned);
276
+ }
277
+ } else if (column.type === 'int') {
278
+ if (column.arraySize !== undefined) {
279
+ assert(dstSize !== undefined, `Missing size for int array column ${column.name}`);
280
+
281
+ const castBuffer = getCastBuffer(
282
+ typeof cell.data === 'number' ? BigInt(cell.data) : cell.data,
283
+ srcSize,
284
+ 4 * column.arraySize,
285
+ );
286
+
287
+ const values: number[] = [];
288
+ if (column.isSigned) {
289
+ for (let i = 0; i < column.arraySize; i += 1) {
290
+ const value = castBuffer.readIntLE(i * dstSize, dstSize);
291
+ values.push(value);
292
+ }
293
+ } else {
294
+ for (let i = 0; i < column.arraySize; i += 1) {
295
+ const value = castBuffer.readUIntLE(i * dstSize, dstSize);
296
+ values.push(value);
297
+ }
298
+ }
299
+
300
+ data[column.name] = values;
301
+ } else if (typeof cell.data === 'number') {
302
+ data[column.name] = castIntegerBySize(
303
+ cell.data,
304
+ srcSize,
305
+ srcSigned,
306
+ dstSize ?? srcSize,
307
+ column.isSigned,
308
+ );
309
+ } else {
310
+ assert(column.size === undefined || column.size === 64, `Unexpected size ${column.size?.toString() ?? ''} for column ${column.name}`);
311
+
312
+ if (srcSigned !== column.isSigned) {
313
+ data[column.name] = castBigInt64(
314
+ cell.data,
315
+ srcSigned,
316
+ column.isSigned,
317
+ );
318
+ } else {
319
+ data[column.name] = cell.data;
320
+ }
321
+ }
322
+ } else {
323
+ throw new Error(`Unsupported column type ${column.type} for column ${column.name}`);
324
+ }
325
+
326
+ fieldIndex += 1;
327
+ } else if (column.isRelation) {
328
+ const relation = this.wdc.getRowRelationship(id);
329
+ data[column.name] = relation ?? 0;
330
+ }
331
+ });
332
+ } else {
333
+ const buffer = row.data;
334
+ let offset = 0;
335
+ let fieldIndex = 0;
336
+ this.columns.forEach((column) => {
337
+ if (column.isID) {
338
+ data[column.name] = id;
339
+
340
+ if (column.isInline) {
341
+ const currField = this.wdc.fields[fieldIndex];
342
+ const size = Math.ceil((column.size ?? (32 - currField.size)) / 8);
343
+ offset += size;
344
+
345
+ fieldIndex += 1;
346
+ }
347
+ } else if (column.isInline) {
348
+ const values = [];
349
+
350
+ if (column.type === 'string' || column.type === 'locstring') {
351
+ const count = column.arraySize ?? 1;
352
+
353
+ for (let i = 0; i < count; i += 1) {
354
+ const startOffset = offset;
355
+ while (buffer[offset] !== 0x00) {
356
+ offset += 1;
357
+ }
358
+
359
+ values.push(buffer.toString('utf-8', startOffset, offset));
360
+ offset += 1;
361
+ }
362
+
363
+ data[column.name] = count > 1 ? values : values[0];
364
+ } else {
365
+ // note: layout hash won't change when array size changes
366
+ // so we try to determine array size based on field structure
367
+ const currField = this.wdc.fields[fieldIndex];
368
+ const nextField = this.wdc.fields[fieldIndex + 1];
369
+ const size = Math.ceil((column.size ?? (32 - currField.size)) / 8);
370
+
371
+ let count;
372
+ if (fieldIndex + 1 < this.wdc.fields.length) {
373
+ // reading hotfix for normal db2 may have fields with same position
374
+ count = Math.max((nextField.position - currField.position) / size, 1);
375
+ } else {
376
+ // nextPos = byteLength - offset + currPos
377
+ count = column.arraySize !== undefined
378
+ ? ((buffer.byteLength - offset) / size)
379
+ : 1;
380
+ }
381
+
382
+ for (let i = 0; i < count; i += 1) {
383
+ if (column.type === 'float') {
384
+ const value = buffer.readFloatLE(offset);
385
+ values.push(Math.round(value * 100) / 100);
386
+ offset += 4;
387
+ } else if (size > 6) {
388
+ assert(size === 8, `Unexpected size ${size.toString()} for column ${column.name}`);
389
+
390
+ const value = column.isSigned
391
+ ? buffer.readBigInt64LE(offset)
392
+ : buffer.readBigUInt64LE(offset);
393
+
394
+ values.push(value);
395
+ offset += size;
396
+ } else {
397
+ const value = column.isSigned
398
+ ? buffer.readIntLE(offset, size)
399
+ : buffer.readUIntLE(offset, size);
400
+
401
+ values.push(value);
402
+ offset += size;
403
+ }
404
+ }
405
+
406
+ data[column.name] = count > 1 ? values : values[0];
407
+ }
408
+
409
+ fieldIndex += 1;
410
+ } else if (column.isRelation) {
411
+ const relation = this.wdc.getRowRelationship(id);
412
+ data[column.name] = relation ?? 0;
413
+ }
414
+ });
415
+ }
416
+
417
+ this.cache.set(id, data);
418
+
419
+ return structuredClone(data);
420
+ }
421
+ }
422
+
423
+ export type {
424
+ Column,
425
+ ColumnData,
426
+ BasicColumnData,
427
+ };
package/src/fetcher.ts ADDED
@@ -0,0 +1,223 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import http from 'node:http';
4
+ import path from 'node:path';
5
+
6
+ import cliProgress from 'cli-progress';
7
+
8
+ import Store from './store.ts';
9
+
10
+ const USER_AGENT = 'node-wow-casc-dbc';
11
+
12
+ const CACHE_ROOT = path.resolve('cache');
13
+ const CACHE_DIRS = {
14
+ build: 'builds',
15
+ indexes: 'indices',
16
+ data: 'data',
17
+ dbd: 'dbd',
18
+ };
19
+
20
+ const CACHE_INTEGRITY_FILE = path.resolve(CACHE_ROOT, 'integrity.json');
21
+
22
+ const cacheIntegrity = new Store<string, string>(CACHE_INTEGRITY_FILE);
23
+
24
+ const formatCDNKey = (key: string): string => `${key.substring(0, 2)}/${key.substring(2, 4)}/${key}`;
25
+
26
+ const requestData = async (
27
+ url: string,
28
+ {
29
+ partialOffset,
30
+ partialLength,
31
+ showProgress,
32
+ }: {
33
+ partialOffset?: number,
34
+ partialLength?: number,
35
+ showProgress?: boolean,
36
+ } = {},
37
+ ): Promise<Buffer> => new Promise((resolve, reject) => {
38
+ const options = {
39
+ headers: {
40
+ // eslint-disable-next-line @typescript-eslint/naming-convention
41
+ 'User-Agent': USER_AGENT,
42
+ // eslint-disable-next-line @typescript-eslint/naming-convention
43
+ Range: partialOffset !== undefined && partialLength !== undefined
44
+ ? `bytes=${partialOffset.toString()}-${(partialOffset + partialLength - 1).toString()}`
45
+ : 'bytes=0-',
46
+ },
47
+ };
48
+
49
+ http.get(url, options, (res) => {
50
+ if (res.statusCode === 301 || res.statusCode === 302) {
51
+ if (res.headers.location !== undefined) {
52
+ requestData(res.headers.location, { partialOffset, partialLength, showProgress })
53
+ .then(resolve)
54
+ .catch((err: unknown) => {
55
+ throw err;
56
+ });
57
+ } else {
58
+ reject(new Error(`Failed to request ${url}, Status Code: ${res.statusCode.toString()}`));
59
+ }
60
+ return;
61
+ }
62
+
63
+ if (res.statusCode === undefined || res.statusCode < 200 || res.statusCode > 302) {
64
+ reject(new Error(`Failed to request ${url}, Status Code: ${res.statusCode?.toString() ?? 'undefined'}`));
65
+ return;
66
+ }
67
+
68
+ const lengthText = res.headers['content-length'];
69
+ const length = lengthText !== undefined ? parseInt(lengthText, 10) : 0;
70
+ const bar = showProgress === true && !Number.isNaN(length) && length >= 10485760
71
+ ? new cliProgress.SingleBar({ etaBuffer: 10240 }, cliProgress.Presets.shades_classic)
72
+ : undefined;
73
+ bar?.start(length, 0);
74
+
75
+ const chunks: Buffer[] = [];
76
+ res.on('data', (chunk: Buffer) => {
77
+ bar?.increment(chunk.length);
78
+ chunks.push(chunk);
79
+ });
80
+ res.on('end', () => {
81
+ bar?.stop();
82
+ resolve(Buffer.concat(chunks));
83
+ });
84
+ res.on('error', (err) => {
85
+ bar?.stop();
86
+ reject(err);
87
+ });
88
+ })
89
+ .on('error', reject)
90
+ .end();
91
+ });
92
+
93
+ const downloadFile = (
94
+ prefixes: string[],
95
+ type: 'data' | 'config',
96
+ key: string,
97
+ {
98
+ partialOffset,
99
+ partialLength,
100
+ showProgress,
101
+ showAttemptFail,
102
+ }: {
103
+ partialOffset?: number,
104
+ partialLength?: number,
105
+ showProgress?: boolean,
106
+ showAttemptFail?: boolean,
107
+ } = {},
108
+ ): Promise<Buffer> => {
109
+ const urls = prefixes.map((prefix) => `${prefix}/${type}/${formatCDNKey(key)}`);
110
+
111
+ return urls
112
+ .reduce(
113
+ (prev, url, index) => prev
114
+ .catch((err: unknown) => {
115
+ if (showAttemptFail === true && index > 0 && err instanceof Error) {
116
+ console.warn(`${new Date().toISOString()} [WARN]:`, err.message);
117
+ }
118
+ return requestData(url, { partialOffset, partialLength, showProgress });
119
+ }),
120
+ Promise.reject<Buffer>(new Error('')),
121
+ );
122
+ };
123
+
124
+ const getFileCache = async (file: string): Promise<Buffer | undefined> => {
125
+ const integrity = await cacheIntegrity.get(file);
126
+ if (integrity !== undefined) {
127
+ try {
128
+ const buffer = await fs.readFile(path.resolve(CACHE_ROOT, file));
129
+ const hash = crypto.createHash('sha256').update(buffer).digest('hex');
130
+ if (hash === integrity) {
131
+ return buffer;
132
+ }
133
+ } catch {
134
+ // ignore
135
+ }
136
+ }
137
+ return undefined;
138
+ };
139
+
140
+ export const getDataFile = async (
141
+ prefixes: string[],
142
+ key: string,
143
+ type: keyof typeof CACHE_DIRS,
144
+ buildCKey: string,
145
+ {
146
+ name,
147
+ partialOffset,
148
+ partialLength,
149
+ showProgress,
150
+ showAttemptFail,
151
+ }: {
152
+ name?: string,
153
+ partialOffset?: number,
154
+ partialLength?: number,
155
+ showProgress?: boolean,
156
+ showAttemptFail?: boolean,
157
+ } = {},
158
+ ): Promise<Buffer> => {
159
+ const dir = type === 'build'
160
+ ? path.join(CACHE_DIRS[type], buildCKey)
161
+ : CACHE_DIRS[type];
162
+ const file = name !== undefined ? path.join(dir, name) : path.join(dir, key);
163
+ const cacheBuffer = await getFileCache(file);
164
+
165
+ if (cacheBuffer) {
166
+ if (name === undefined && partialOffset !== undefined && partialLength !== undefined) {
167
+ return cacheBuffer.subarray(partialOffset, partialOffset + partialLength);
168
+ }
169
+ return cacheBuffer;
170
+ }
171
+
172
+ const downloadBuffer = await downloadFile(prefixes, 'data', key, {
173
+ partialOffset, partialLength, showProgress, showAttemptFail,
174
+ });
175
+ if ((partialOffset === undefined && partialLength === undefined) || name !== undefined) {
176
+ await fs.mkdir(path.resolve(CACHE_ROOT, dir), { recursive: true });
177
+ await fs.writeFile(path.resolve(CACHE_ROOT, file), downloadBuffer);
178
+
179
+ const hash = crypto.createHash('sha256').update(downloadBuffer).digest('hex');
180
+ await cacheIntegrity.set(file, hash);
181
+ }
182
+
183
+ return downloadBuffer;
184
+ };
185
+
186
+ export const getConfigFile = async (
187
+ prefixes: string[],
188
+ key: string,
189
+ {
190
+ showProgress, showAttemptFail,
191
+ }: {
192
+ showProgress?: boolean, showAttemptFail?: boolean,
193
+ } = {},
194
+ ): Promise<string> => {
195
+ const downloadBuffer = await downloadFile(prefixes, 'config', key, { showProgress, showAttemptFail });
196
+ return downloadBuffer.toString('utf-8');
197
+ };
198
+
199
+ export const getProductVersions = async (
200
+ region: string,
201
+ product: string,
202
+ ): Promise<string> => {
203
+ const url = `http://${region}.patch.battle.net:1119/${product}/versions`;
204
+ const headers = new Headers();
205
+ headers.set('User-Agent', USER_AGENT);
206
+
207
+ const res = await fetch(url, { headers });
208
+
209
+ return res.text();
210
+ };
211
+
212
+ export const getProductCDNs = async (
213
+ region: string,
214
+ product: string,
215
+ ): Promise<string> => {
216
+ const url = `http://${region}.patch.battle.net:1119/${product}/cdns`;
217
+ const headers = new Headers();
218
+ headers.set('User-Agent', USER_AGENT);
219
+
220
+ const res = await fetch(url, { headers });
221
+
222
+ return res.text();
223
+ };
package/src/index.ts ADDED
@@ -0,0 +1,44 @@
1
+ /* eslint-disable import-x/no-unused-modules */
2
+
3
+ export { default as ADBReader } from './adb.ts';
4
+ export { default as CASCClient } from './client.ts';
5
+ export { default as DBDParser } from './dbd.ts';
6
+ export { default as WDCReader } from './wdc.ts';
7
+
8
+ export type { HotfixEntry } from './adb.ts';
9
+ export type {
10
+ Version,
11
+ ClientPreloadData,
12
+ ArchiveIndex,
13
+ EncodingData,
14
+ RootData,
15
+ FileInfo,
16
+ FileFetchResultFull,
17
+ FileFetchResultPartial,
18
+ FileFetchResult,
19
+ MissingKeyBlock,
20
+ } from './client.ts';
21
+ export type {
22
+ Column,
23
+ ColumnData,
24
+ BasicColumnData,
25
+ } from './dbd.ts';
26
+ export type {
27
+ FieldStructure,
28
+ FieldStorageInfo,
29
+ FieldStorageInfoCompressionNone,
30
+ FieldStorageInfoCompressionBitpacked,
31
+ FieldStorageInfoCompressionCommonData,
32
+ FieldStorageInfoCompressionBitpackedIndexed,
33
+ FieldStorageInfoCompressionBitpackedIndexedArray,
34
+ FieldStorageInfoCompressionBitpackedSigned,
35
+ ParsedField,
36
+ ParsedFieldNone,
37
+ ParsedFieldCommonData,
38
+ ParsedFieldBitpacked,
39
+ ParsedFieldBitpackedArray,
40
+ SparseRow,
41
+ Hotfix,
42
+ HotfixModify,
43
+ HotfixDelete,
44
+ } from './wdc.ts';