@rhyster/wow-casc-dbc 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +107 -76
- package/dist/index.mjs +106 -76
- package/package.json +9 -3
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const assert = require('node:assert');
|
|
4
4
|
const crypto = require('node:crypto');
|
|
5
|
+
const async = require('async');
|
|
6
|
+
const cliProgress = require('cli-progress');
|
|
5
7
|
const fs = require('node:fs/promises');
|
|
6
8
|
const path = require('node:path');
|
|
7
9
|
const http = require('node:http');
|
|
@@ -11,6 +13,7 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
|
|
|
11
13
|
|
|
12
14
|
const assert__default = /*#__PURE__*/_interopDefaultCompat(assert);
|
|
13
15
|
const crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto);
|
|
16
|
+
const cliProgress__default = /*#__PURE__*/_interopDefaultCompat(cliProgress);
|
|
14
17
|
const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
15
18
|
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
16
19
|
const http__default = /*#__PURE__*/_interopDefaultCompat(http);
|
|
@@ -60,7 +63,11 @@ const CACHE_DIRS = {
|
|
|
60
63
|
const CACHE_INTEGRITY_FILE = path__default.resolve(CACHE_ROOT, "integrity.json");
|
|
61
64
|
const cacheIntegrity = new Store(CACHE_INTEGRITY_FILE);
|
|
62
65
|
const formatCDNKey = (key) => `${key.substring(0, 2)}/${key.substring(2, 4)}/${key}`;
|
|
63
|
-
const requestData = async (url,
|
|
66
|
+
const requestData = async (url, {
|
|
67
|
+
partialOffset,
|
|
68
|
+
partialLength,
|
|
69
|
+
showProgress
|
|
70
|
+
} = {}) => new Promise((resolve, reject) => {
|
|
64
71
|
const options = {
|
|
65
72
|
headers: {
|
|
66
73
|
"User-Agent": USER_AGENT,
|
|
@@ -70,7 +77,9 @@ const requestData = async (url, partialOffset = void 0, partialLength = void 0)
|
|
|
70
77
|
http__default.get(url, options, (res) => {
|
|
71
78
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
72
79
|
if (res.headers.location) {
|
|
73
|
-
requestData(res.headers.location, partialOffset, partialLength).then(resolve).catch(
|
|
80
|
+
requestData(res.headers.location, { partialOffset, partialLength, showProgress }).then(resolve).catch((err) => {
|
|
81
|
+
throw err;
|
|
82
|
+
});
|
|
74
83
|
} else {
|
|
75
84
|
reject(new Error(`Failed to request ${url}, Status Code: ${res.statusCode.toString()}`));
|
|
76
85
|
}
|
|
@@ -80,17 +89,39 @@ const requestData = async (url, partialOffset = void 0, partialLength = void 0)
|
|
|
80
89
|
reject(new Error(`Failed to request ${url}, Status Code: ${res.statusCode?.toString() ?? "undefined"}`));
|
|
81
90
|
return;
|
|
82
91
|
}
|
|
92
|
+
const lengthText = res.headers["content-length"];
|
|
93
|
+
const length = lengthText ? parseInt(lengthText, 10) : 0;
|
|
94
|
+
const bar = showProgress && !Number.isNaN(length) && length >= 10485760 ? new cliProgress__default.SingleBar({ etaBuffer: 10240 }, cliProgress__default.Presets.shades_classic) : void 0;
|
|
95
|
+
bar?.start(length, 0);
|
|
83
96
|
const chunks = [];
|
|
84
|
-
res.on("data", (chunk) =>
|
|
97
|
+
res.on("data", (chunk) => {
|
|
98
|
+
bar?.increment(chunk.length);
|
|
99
|
+
chunks.push(chunk);
|
|
100
|
+
});
|
|
85
101
|
res.on("end", () => {
|
|
102
|
+
bar?.stop();
|
|
86
103
|
resolve(Buffer.concat(chunks));
|
|
87
104
|
});
|
|
105
|
+
res.on("error", (err) => {
|
|
106
|
+
bar?.stop();
|
|
107
|
+
reject(err);
|
|
108
|
+
});
|
|
88
109
|
}).on("error", reject).end();
|
|
89
110
|
});
|
|
90
|
-
const downloadFile = (prefixes, type, key,
|
|
111
|
+
const downloadFile = (prefixes, type, key, {
|
|
112
|
+
partialOffset,
|
|
113
|
+
partialLength,
|
|
114
|
+
showProgress,
|
|
115
|
+
showAttemptFail
|
|
116
|
+
} = {}) => {
|
|
91
117
|
const urls = prefixes.map((prefix) => `${prefix}/${type}/${formatCDNKey(key)}`);
|
|
92
118
|
return urls.reduce(
|
|
93
|
-
(prev, url) => prev.catch(() =>
|
|
119
|
+
(prev, url, index) => prev.catch((err) => {
|
|
120
|
+
if (showAttemptFail && index > 0 && err instanceof Error) {
|
|
121
|
+
console.warn(`${( new Date()).toISOString()} [WARN]:`, err.message);
|
|
122
|
+
}
|
|
123
|
+
return requestData(url, { partialOffset, partialLength, showProgress });
|
|
124
|
+
}),
|
|
94
125
|
Promise.reject(new Error(""))
|
|
95
126
|
);
|
|
96
127
|
};
|
|
@@ -108,7 +139,13 @@ const getFileCache = async (file) => {
|
|
|
108
139
|
}
|
|
109
140
|
return void 0;
|
|
110
141
|
};
|
|
111
|
-
const getDataFile = async (prefixes, key, type, buildCKey,
|
|
142
|
+
const getDataFile = async (prefixes, key, type, buildCKey, {
|
|
143
|
+
name,
|
|
144
|
+
partialOffset,
|
|
145
|
+
partialLength,
|
|
146
|
+
showProgress,
|
|
147
|
+
showAttemptFail
|
|
148
|
+
} = {}) => {
|
|
112
149
|
const dir = type === "build" ? path__default.join(CACHE_DIRS[type], buildCKey) : CACHE_DIRS[type];
|
|
113
150
|
const file = name ? path__default.join(dir, name) : path__default.join(dir, key);
|
|
114
151
|
const cacheBuffer = await getFileCache(file);
|
|
@@ -118,7 +155,12 @@ const getDataFile = async (prefixes, key, type, buildCKey, name, partialOffset =
|
|
|
118
155
|
}
|
|
119
156
|
return cacheBuffer;
|
|
120
157
|
}
|
|
121
|
-
const downloadBuffer = await downloadFile(prefixes, "data", key,
|
|
158
|
+
const downloadBuffer = await downloadFile(prefixes, "data", key, {
|
|
159
|
+
partialOffset,
|
|
160
|
+
partialLength,
|
|
161
|
+
showProgress,
|
|
162
|
+
showAttemptFail
|
|
163
|
+
});
|
|
122
164
|
if (partialOffset === void 0 && partialLength === void 0 || name) {
|
|
123
165
|
await fs__default.mkdir(path__default.resolve(CACHE_ROOT, dir), { recursive: true });
|
|
124
166
|
await fs__default.writeFile(path__default.resolve(CACHE_ROOT, file), downloadBuffer);
|
|
@@ -127,8 +169,11 @@ const getDataFile = async (prefixes, key, type, buildCKey, name, partialOffset =
|
|
|
127
169
|
}
|
|
128
170
|
return downloadBuffer;
|
|
129
171
|
};
|
|
130
|
-
const getConfigFile = async (prefixes, key
|
|
131
|
-
|
|
172
|
+
const getConfigFile = async (prefixes, key, {
|
|
173
|
+
showProgress,
|
|
174
|
+
showAttemptFail
|
|
175
|
+
} = {}) => {
|
|
176
|
+
const downloadBuffer = await downloadFile(prefixes, "config", key, { showProgress, showAttemptFail });
|
|
132
177
|
return downloadBuffer.toString("utf-8");
|
|
133
178
|
};
|
|
134
179
|
const getProductVersions = async (region, product) => {
|
|
@@ -1329,56 +1374,6 @@ const resolveCDNHost = async (hosts, path) => {
|
|
|
1329
1374
|
const resolved = latencies.filter((result) => result.status === "fulfilled").map((result) => result.value).sort((a, b) => a.latency - b.latency);
|
|
1330
1375
|
return resolved.map((result) => `http://${result.host}/${path}`);
|
|
1331
1376
|
};
|
|
1332
|
-
const startProcessBar = (total, screenWidth = 80) => {
|
|
1333
|
-
const totalText = total.toString();
|
|
1334
|
-
const barLength = screenWidth - totalText.length * 2 - 4;
|
|
1335
|
-
const bar = " ".repeat(barLength);
|
|
1336
|
-
process.stdout.write(`[${bar}] ${"0".padStart(totalText.length, " ")}/${totalText}`);
|
|
1337
|
-
process.stdout.cursorTo(0);
|
|
1338
|
-
};
|
|
1339
|
-
const updateProcessBar = (current, total, screenWidth = 80) => {
|
|
1340
|
-
const totalText = total.toString();
|
|
1341
|
-
const barLength = screenWidth - totalText.length * 2 - 4;
|
|
1342
|
-
const bar = "=".repeat(Math.floor(current / total * barLength));
|
|
1343
|
-
process.stdout.write(`[${bar.padEnd(barLength, " ")}] ${current.toString().padStart(totalText.length, " ")}/${totalText}`);
|
|
1344
|
-
process.stdout.cursorTo(0);
|
|
1345
|
-
};
|
|
1346
|
-
const endProcessBar = () => {
|
|
1347
|
-
process.stdout.write("\n");
|
|
1348
|
-
};
|
|
1349
|
-
const asyncQueue = (items, handler, limit) => {
|
|
1350
|
-
if (items.length === 0) {
|
|
1351
|
-
return Promise.resolve([]);
|
|
1352
|
-
}
|
|
1353
|
-
return new Promise((resolve, reject) => {
|
|
1354
|
-
const results = [];
|
|
1355
|
-
let current = 0;
|
|
1356
|
-
let pending = 0;
|
|
1357
|
-
let completed = 0;
|
|
1358
|
-
const next = () => {
|
|
1359
|
-
if (current < items.length) {
|
|
1360
|
-
const index = current;
|
|
1361
|
-
current += 1;
|
|
1362
|
-
pending += 1;
|
|
1363
|
-
handler(items[index]).then((result) => {
|
|
1364
|
-
results[index] = result;
|
|
1365
|
-
}).catch(reject).finally(() => {
|
|
1366
|
-
pending -= 1;
|
|
1367
|
-
completed += 1;
|
|
1368
|
-
updateProcessBar(completed, items.length);
|
|
1369
|
-
next();
|
|
1370
|
-
});
|
|
1371
|
-
} else if (pending === 0) {
|
|
1372
|
-
endProcessBar();
|
|
1373
|
-
resolve(results);
|
|
1374
|
-
}
|
|
1375
|
-
};
|
|
1376
|
-
startProcessBar(items.length);
|
|
1377
|
-
for (let i = 0; i < limit; i += 1) {
|
|
1378
|
-
next();
|
|
1379
|
-
}
|
|
1380
|
-
});
|
|
1381
|
-
};
|
|
1382
1377
|
const JEDEC = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
1383
1378
|
const formatFileSize = (input) => {
|
|
1384
1379
|
if (Number.isNaN(input))
|
|
@@ -1445,7 +1440,7 @@ class CASCClient {
|
|
|
1445
1440
|
}
|
|
1446
1441
|
log(level, message) {
|
|
1447
1442
|
if (level <= this.logLevel) {
|
|
1448
|
-
if (level
|
|
1443
|
+
if (level <= 0 /* error */) {
|
|
1449
1444
|
console.error(`${( new Date()).toISOString()} [${textLogLevel[level]}]:`, message);
|
|
1450
1445
|
} else {
|
|
1451
1446
|
console.log(`${( new Date()).toISOString()} [${textLogLevel[level]}]:`, message);
|
|
@@ -1471,32 +1466,55 @@ class CASCClient {
|
|
|
1471
1466
|
this.log(2 /* info */, prefix);
|
|
1472
1467
|
});
|
|
1473
1468
|
this.log(2 /* info */, "Fetching build configurations...");
|
|
1474
|
-
const cdnConfigText = await getConfigFile(prefixes, this.version.CDNConfig
|
|
1469
|
+
const cdnConfigText = await getConfigFile(prefixes, this.version.CDNConfig, {
|
|
1470
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1471
|
+
});
|
|
1475
1472
|
const cdnConfig = parseCDNConfig(cdnConfigText);
|
|
1476
|
-
const buildConfigText = await getConfigFile(prefixes, this.version.BuildConfig
|
|
1473
|
+
const buildConfigText = await getConfigFile(prefixes, this.version.BuildConfig, {
|
|
1474
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1475
|
+
});
|
|
1477
1476
|
const buildConfig = parseBuildConfig(buildConfigText);
|
|
1478
1477
|
this.log(2 /* info */, "Loading archives...");
|
|
1479
1478
|
const archiveKeys = cdnConfig.archives.split(" ");
|
|
1480
1479
|
const archiveCount = archiveKeys.length;
|
|
1481
1480
|
const archiveTotalSize = cdnConfig.archivesIndexSize.split(" ").reduce((a, b) => a + parseInt(b, 10), 0);
|
|
1482
|
-
const
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1481
|
+
const archiveBar = this.logLevel >= 2 /* info */ ? new cliProgress__default.SingleBar({ etaBuffer: 100 }, cliProgress__default.Presets.shades_classic) : void 0;
|
|
1482
|
+
archiveBar?.start(archiveCount, 0);
|
|
1483
|
+
const archivesMapArray = await async.mapLimit(
|
|
1484
|
+
archiveKeys,
|
|
1485
|
+
50,
|
|
1486
|
+
async (key) => {
|
|
1487
|
+
const fileName = `${key}.index`;
|
|
1488
|
+
const buffer = await async.retry({
|
|
1489
|
+
times: 5,
|
|
1490
|
+
interval: 3e3
|
|
1491
|
+
}, async () => getDataFile(prefixes, fileName, "indexes", this.version.BuildConfig, {
|
|
1492
|
+
showProgress: this.logLevel >= 2 /* info */,
|
|
1493
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1494
|
+
}));
|
|
1495
|
+
const map = parseArchiveIndex(buffer, key);
|
|
1496
|
+
archiveBar?.increment();
|
|
1497
|
+
return map;
|
|
1498
|
+
}
|
|
1499
|
+
).then((result) => {
|
|
1500
|
+
archiveBar?.stop();
|
|
1501
|
+
return result.flatMap((e) => [...e]);
|
|
1502
|
+
}).catch((error) => {
|
|
1503
|
+
archiveBar?.stop();
|
|
1504
|
+
throw error;
|
|
1505
|
+
});
|
|
1506
|
+
const archives = new Map(archivesMapArray);
|
|
1493
1507
|
this.log(
|
|
1494
1508
|
2 /* info */,
|
|
1495
1509
|
`Loaded ${archiveCount.toString()} archives (${archives.size.toString()} entries, ${formatFileSize(archiveTotalSize)})`
|
|
1496
1510
|
);
|
|
1497
1511
|
this.log(2 /* info */, "Loading encoding table...");
|
|
1498
1512
|
const [encodingCKey, encodingEKey] = buildConfig.encoding.split(" ");
|
|
1499
|
-
const encodingBuffer = await getDataFile(prefixes, encodingEKey, "build", this.version.BuildConfig,
|
|
1513
|
+
const encodingBuffer = await getDataFile(prefixes, encodingEKey, "build", this.version.BuildConfig, {
|
|
1514
|
+
name: "encoding",
|
|
1515
|
+
showProgress: this.logLevel >= 2 /* info */,
|
|
1516
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1517
|
+
});
|
|
1500
1518
|
this.log(2 /* info */, `Loaded encoding table (${formatFileSize(encodingBuffer.byteLength)})`);
|
|
1501
1519
|
this.log(2 /* info */, "Parsing encoding table...");
|
|
1502
1520
|
const encoding = parseEncodingFile(encodingBuffer, encodingEKey, encodingCKey);
|
|
@@ -1506,7 +1524,11 @@ class CASCClient {
|
|
|
1506
1524
|
const rootEKeys = encoding.cKey2EKey.get(rootCKey);
|
|
1507
1525
|
assert__default(rootEKeys, "Failing to find EKey for root table.");
|
|
1508
1526
|
const rootEKey = typeof rootEKeys === "string" ? rootEKeys : rootEKeys[0];
|
|
1509
|
-
const rootBuffer = await getDataFile(prefixes, rootEKey, "build", this.version.BuildConfig,
|
|
1527
|
+
const rootBuffer = await getDataFile(prefixes, rootEKey, "build", this.version.BuildConfig, {
|
|
1528
|
+
name: "root",
|
|
1529
|
+
showProgress: this.logLevel >= 2 /* info */,
|
|
1530
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1531
|
+
});
|
|
1510
1532
|
this.log(2 /* info */, `Loaded root table (${formatFileSize(rootBuffer.byteLength)})`);
|
|
1511
1533
|
this.log(2 /* info */, "Parsing root file...");
|
|
1512
1534
|
const rootFile = parseRootFile(rootBuffer, rootEKey, rootCKey);
|
|
@@ -1585,7 +1607,16 @@ class CASCClient {
|
|
|
1585
1607
|
assert__default(eKeys, `Failing to find encoding key for ${cKey}`);
|
|
1586
1608
|
const eKey = typeof eKeys === "string" ? eKeys : eKeys[0];
|
|
1587
1609
|
const archive = archives.get(eKey);
|
|
1588
|
-
const blte = archive ? await getDataFile(prefixes, archive.key, "data", this.version.BuildConfig,
|
|
1610
|
+
const blte = archive ? await getDataFile(prefixes, archive.key, "data", this.version.BuildConfig, {
|
|
1611
|
+
name: eKey,
|
|
1612
|
+
partialOffset: archive.offset,
|
|
1613
|
+
partialLength: archive.size,
|
|
1614
|
+
showProgress: this.logLevel >= 2 /* info */,
|
|
1615
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1616
|
+
}) : await getDataFile(prefixes, eKey, "data", this.version.BuildConfig, {
|
|
1617
|
+
showProgress: this.logLevel >= 2 /* info */,
|
|
1618
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1619
|
+
});
|
|
1589
1620
|
const reader = new BLTEReader(blte, eKey, this.keys);
|
|
1590
1621
|
if (!allowMissingKey) {
|
|
1591
1622
|
reader.processBytes(allowMissingKey);
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
|
+
import { mapLimit, retry } from 'async';
|
|
4
|
+
import cliProgress from 'cli-progress';
|
|
3
5
|
import fs from 'node:fs/promises';
|
|
4
6
|
import path from 'node:path';
|
|
5
7
|
import http from 'node:http';
|
|
@@ -49,7 +51,11 @@ const CACHE_DIRS = {
|
|
|
49
51
|
const CACHE_INTEGRITY_FILE = path.resolve(CACHE_ROOT, "integrity.json");
|
|
50
52
|
const cacheIntegrity = new Store(CACHE_INTEGRITY_FILE);
|
|
51
53
|
const formatCDNKey = (key) => `${key.substring(0, 2)}/${key.substring(2, 4)}/${key}`;
|
|
52
|
-
const requestData = async (url,
|
|
54
|
+
const requestData = async (url, {
|
|
55
|
+
partialOffset,
|
|
56
|
+
partialLength,
|
|
57
|
+
showProgress
|
|
58
|
+
} = {}) => new Promise((resolve, reject) => {
|
|
53
59
|
const options = {
|
|
54
60
|
headers: {
|
|
55
61
|
"User-Agent": USER_AGENT,
|
|
@@ -59,7 +65,9 @@ const requestData = async (url, partialOffset = void 0, partialLength = void 0)
|
|
|
59
65
|
http.get(url, options, (res) => {
|
|
60
66
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
61
67
|
if (res.headers.location) {
|
|
62
|
-
requestData(res.headers.location, partialOffset, partialLength).then(resolve).catch(
|
|
68
|
+
requestData(res.headers.location, { partialOffset, partialLength, showProgress }).then(resolve).catch((err) => {
|
|
69
|
+
throw err;
|
|
70
|
+
});
|
|
63
71
|
} else {
|
|
64
72
|
reject(new Error(`Failed to request ${url}, Status Code: ${res.statusCode.toString()}`));
|
|
65
73
|
}
|
|
@@ -69,17 +77,39 @@ const requestData = async (url, partialOffset = void 0, partialLength = void 0)
|
|
|
69
77
|
reject(new Error(`Failed to request ${url}, Status Code: ${res.statusCode?.toString() ?? "undefined"}`));
|
|
70
78
|
return;
|
|
71
79
|
}
|
|
80
|
+
const lengthText = res.headers["content-length"];
|
|
81
|
+
const length = lengthText ? parseInt(lengthText, 10) : 0;
|
|
82
|
+
const bar = showProgress && !Number.isNaN(length) && length >= 10485760 ? new cliProgress.SingleBar({ etaBuffer: 10240 }, cliProgress.Presets.shades_classic) : void 0;
|
|
83
|
+
bar?.start(length, 0);
|
|
72
84
|
const chunks = [];
|
|
73
|
-
res.on("data", (chunk) =>
|
|
85
|
+
res.on("data", (chunk) => {
|
|
86
|
+
bar?.increment(chunk.length);
|
|
87
|
+
chunks.push(chunk);
|
|
88
|
+
});
|
|
74
89
|
res.on("end", () => {
|
|
90
|
+
bar?.stop();
|
|
75
91
|
resolve(Buffer.concat(chunks));
|
|
76
92
|
});
|
|
93
|
+
res.on("error", (err) => {
|
|
94
|
+
bar?.stop();
|
|
95
|
+
reject(err);
|
|
96
|
+
});
|
|
77
97
|
}).on("error", reject).end();
|
|
78
98
|
});
|
|
79
|
-
const downloadFile = (prefixes, type, key,
|
|
99
|
+
const downloadFile = (prefixes, type, key, {
|
|
100
|
+
partialOffset,
|
|
101
|
+
partialLength,
|
|
102
|
+
showProgress,
|
|
103
|
+
showAttemptFail
|
|
104
|
+
} = {}) => {
|
|
80
105
|
const urls = prefixes.map((prefix) => `${prefix}/${type}/${formatCDNKey(key)}`);
|
|
81
106
|
return urls.reduce(
|
|
82
|
-
(prev, url) => prev.catch(() =>
|
|
107
|
+
(prev, url, index) => prev.catch((err) => {
|
|
108
|
+
if (showAttemptFail && index > 0 && err instanceof Error) {
|
|
109
|
+
console.warn(`${( new Date()).toISOString()} [WARN]:`, err.message);
|
|
110
|
+
}
|
|
111
|
+
return requestData(url, { partialOffset, partialLength, showProgress });
|
|
112
|
+
}),
|
|
83
113
|
Promise.reject(new Error(""))
|
|
84
114
|
);
|
|
85
115
|
};
|
|
@@ -97,7 +127,13 @@ const getFileCache = async (file) => {
|
|
|
97
127
|
}
|
|
98
128
|
return void 0;
|
|
99
129
|
};
|
|
100
|
-
const getDataFile = async (prefixes, key, type, buildCKey,
|
|
130
|
+
const getDataFile = async (prefixes, key, type, buildCKey, {
|
|
131
|
+
name,
|
|
132
|
+
partialOffset,
|
|
133
|
+
partialLength,
|
|
134
|
+
showProgress,
|
|
135
|
+
showAttemptFail
|
|
136
|
+
} = {}) => {
|
|
101
137
|
const dir = type === "build" ? path.join(CACHE_DIRS[type], buildCKey) : CACHE_DIRS[type];
|
|
102
138
|
const file = name ? path.join(dir, name) : path.join(dir, key);
|
|
103
139
|
const cacheBuffer = await getFileCache(file);
|
|
@@ -107,7 +143,12 @@ const getDataFile = async (prefixes, key, type, buildCKey, name, partialOffset =
|
|
|
107
143
|
}
|
|
108
144
|
return cacheBuffer;
|
|
109
145
|
}
|
|
110
|
-
const downloadBuffer = await downloadFile(prefixes, "data", key,
|
|
146
|
+
const downloadBuffer = await downloadFile(prefixes, "data", key, {
|
|
147
|
+
partialOffset,
|
|
148
|
+
partialLength,
|
|
149
|
+
showProgress,
|
|
150
|
+
showAttemptFail
|
|
151
|
+
});
|
|
111
152
|
if (partialOffset === void 0 && partialLength === void 0 || name) {
|
|
112
153
|
await fs.mkdir(path.resolve(CACHE_ROOT, dir), { recursive: true });
|
|
113
154
|
await fs.writeFile(path.resolve(CACHE_ROOT, file), downloadBuffer);
|
|
@@ -116,8 +157,11 @@ const getDataFile = async (prefixes, key, type, buildCKey, name, partialOffset =
|
|
|
116
157
|
}
|
|
117
158
|
return downloadBuffer;
|
|
118
159
|
};
|
|
119
|
-
const getConfigFile = async (prefixes, key
|
|
120
|
-
|
|
160
|
+
const getConfigFile = async (prefixes, key, {
|
|
161
|
+
showProgress,
|
|
162
|
+
showAttemptFail
|
|
163
|
+
} = {}) => {
|
|
164
|
+
const downloadBuffer = await downloadFile(prefixes, "config", key, { showProgress, showAttemptFail });
|
|
121
165
|
return downloadBuffer.toString("utf-8");
|
|
122
166
|
};
|
|
123
167
|
const getProductVersions = async (region, product) => {
|
|
@@ -1318,56 +1362,6 @@ const resolveCDNHost = async (hosts, path) => {
|
|
|
1318
1362
|
const resolved = latencies.filter((result) => result.status === "fulfilled").map((result) => result.value).sort((a, b) => a.latency - b.latency);
|
|
1319
1363
|
return resolved.map((result) => `http://${result.host}/${path}`);
|
|
1320
1364
|
};
|
|
1321
|
-
const startProcessBar = (total, screenWidth = 80) => {
|
|
1322
|
-
const totalText = total.toString();
|
|
1323
|
-
const barLength = screenWidth - totalText.length * 2 - 4;
|
|
1324
|
-
const bar = " ".repeat(barLength);
|
|
1325
|
-
process.stdout.write(`[${bar}] ${"0".padStart(totalText.length, " ")}/${totalText}`);
|
|
1326
|
-
process.stdout.cursorTo(0);
|
|
1327
|
-
};
|
|
1328
|
-
const updateProcessBar = (current, total, screenWidth = 80) => {
|
|
1329
|
-
const totalText = total.toString();
|
|
1330
|
-
const barLength = screenWidth - totalText.length * 2 - 4;
|
|
1331
|
-
const bar = "=".repeat(Math.floor(current / total * barLength));
|
|
1332
|
-
process.stdout.write(`[${bar.padEnd(barLength, " ")}] ${current.toString().padStart(totalText.length, " ")}/${totalText}`);
|
|
1333
|
-
process.stdout.cursorTo(0);
|
|
1334
|
-
};
|
|
1335
|
-
const endProcessBar = () => {
|
|
1336
|
-
process.stdout.write("\n");
|
|
1337
|
-
};
|
|
1338
|
-
const asyncQueue = (items, handler, limit) => {
|
|
1339
|
-
if (items.length === 0) {
|
|
1340
|
-
return Promise.resolve([]);
|
|
1341
|
-
}
|
|
1342
|
-
return new Promise((resolve, reject) => {
|
|
1343
|
-
const results = [];
|
|
1344
|
-
let current = 0;
|
|
1345
|
-
let pending = 0;
|
|
1346
|
-
let completed = 0;
|
|
1347
|
-
const next = () => {
|
|
1348
|
-
if (current < items.length) {
|
|
1349
|
-
const index = current;
|
|
1350
|
-
current += 1;
|
|
1351
|
-
pending += 1;
|
|
1352
|
-
handler(items[index]).then((result) => {
|
|
1353
|
-
results[index] = result;
|
|
1354
|
-
}).catch(reject).finally(() => {
|
|
1355
|
-
pending -= 1;
|
|
1356
|
-
completed += 1;
|
|
1357
|
-
updateProcessBar(completed, items.length);
|
|
1358
|
-
next();
|
|
1359
|
-
});
|
|
1360
|
-
} else if (pending === 0) {
|
|
1361
|
-
endProcessBar();
|
|
1362
|
-
resolve(results);
|
|
1363
|
-
}
|
|
1364
|
-
};
|
|
1365
|
-
startProcessBar(items.length);
|
|
1366
|
-
for (let i = 0; i < limit; i += 1) {
|
|
1367
|
-
next();
|
|
1368
|
-
}
|
|
1369
|
-
});
|
|
1370
|
-
};
|
|
1371
1365
|
const JEDEC = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
1372
1366
|
const formatFileSize = (input) => {
|
|
1373
1367
|
if (Number.isNaN(input))
|
|
@@ -1434,7 +1428,7 @@ class CASCClient {
|
|
|
1434
1428
|
}
|
|
1435
1429
|
log(level, message) {
|
|
1436
1430
|
if (level <= this.logLevel) {
|
|
1437
|
-
if (level
|
|
1431
|
+
if (level <= 0 /* error */) {
|
|
1438
1432
|
console.error(`${( new Date()).toISOString()} [${textLogLevel[level]}]:`, message);
|
|
1439
1433
|
} else {
|
|
1440
1434
|
console.log(`${( new Date()).toISOString()} [${textLogLevel[level]}]:`, message);
|
|
@@ -1460,32 +1454,55 @@ class CASCClient {
|
|
|
1460
1454
|
this.log(2 /* info */, prefix);
|
|
1461
1455
|
});
|
|
1462
1456
|
this.log(2 /* info */, "Fetching build configurations...");
|
|
1463
|
-
const cdnConfigText = await getConfigFile(prefixes, this.version.CDNConfig
|
|
1457
|
+
const cdnConfigText = await getConfigFile(prefixes, this.version.CDNConfig, {
|
|
1458
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1459
|
+
});
|
|
1464
1460
|
const cdnConfig = parseCDNConfig(cdnConfigText);
|
|
1465
|
-
const buildConfigText = await getConfigFile(prefixes, this.version.BuildConfig
|
|
1461
|
+
const buildConfigText = await getConfigFile(prefixes, this.version.BuildConfig, {
|
|
1462
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1463
|
+
});
|
|
1466
1464
|
const buildConfig = parseBuildConfig(buildConfigText);
|
|
1467
1465
|
this.log(2 /* info */, "Loading archives...");
|
|
1468
1466
|
const archiveKeys = cdnConfig.archives.split(" ");
|
|
1469
1467
|
const archiveCount = archiveKeys.length;
|
|
1470
1468
|
const archiveTotalSize = cdnConfig.archivesIndexSize.split(" ").reduce((a, b) => a + parseInt(b, 10), 0);
|
|
1471
|
-
const
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1469
|
+
const archiveBar = this.logLevel >= 2 /* info */ ? new cliProgress.SingleBar({ etaBuffer: 100 }, cliProgress.Presets.shades_classic) : void 0;
|
|
1470
|
+
archiveBar?.start(archiveCount, 0);
|
|
1471
|
+
const archivesMapArray = await mapLimit(
|
|
1472
|
+
archiveKeys,
|
|
1473
|
+
50,
|
|
1474
|
+
async (key) => {
|
|
1475
|
+
const fileName = `${key}.index`;
|
|
1476
|
+
const buffer = await retry({
|
|
1477
|
+
times: 5,
|
|
1478
|
+
interval: 3e3
|
|
1479
|
+
}, async () => getDataFile(prefixes, fileName, "indexes", this.version.BuildConfig, {
|
|
1480
|
+
showProgress: this.logLevel >= 2 /* info */,
|
|
1481
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1482
|
+
}));
|
|
1483
|
+
const map = parseArchiveIndex(buffer, key);
|
|
1484
|
+
archiveBar?.increment();
|
|
1485
|
+
return map;
|
|
1486
|
+
}
|
|
1487
|
+
).then((result) => {
|
|
1488
|
+
archiveBar?.stop();
|
|
1489
|
+
return result.flatMap((e) => [...e]);
|
|
1490
|
+
}).catch((error) => {
|
|
1491
|
+
archiveBar?.stop();
|
|
1492
|
+
throw error;
|
|
1493
|
+
});
|
|
1494
|
+
const archives = new Map(archivesMapArray);
|
|
1482
1495
|
this.log(
|
|
1483
1496
|
2 /* info */,
|
|
1484
1497
|
`Loaded ${archiveCount.toString()} archives (${archives.size.toString()} entries, ${formatFileSize(archiveTotalSize)})`
|
|
1485
1498
|
);
|
|
1486
1499
|
this.log(2 /* info */, "Loading encoding table...");
|
|
1487
1500
|
const [encodingCKey, encodingEKey] = buildConfig.encoding.split(" ");
|
|
1488
|
-
const encodingBuffer = await getDataFile(prefixes, encodingEKey, "build", this.version.BuildConfig,
|
|
1501
|
+
const encodingBuffer = await getDataFile(prefixes, encodingEKey, "build", this.version.BuildConfig, {
|
|
1502
|
+
name: "encoding",
|
|
1503
|
+
showProgress: this.logLevel >= 2 /* info */,
|
|
1504
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1505
|
+
});
|
|
1489
1506
|
this.log(2 /* info */, `Loaded encoding table (${formatFileSize(encodingBuffer.byteLength)})`);
|
|
1490
1507
|
this.log(2 /* info */, "Parsing encoding table...");
|
|
1491
1508
|
const encoding = parseEncodingFile(encodingBuffer, encodingEKey, encodingCKey);
|
|
@@ -1495,7 +1512,11 @@ class CASCClient {
|
|
|
1495
1512
|
const rootEKeys = encoding.cKey2EKey.get(rootCKey);
|
|
1496
1513
|
assert(rootEKeys, "Failing to find EKey for root table.");
|
|
1497
1514
|
const rootEKey = typeof rootEKeys === "string" ? rootEKeys : rootEKeys[0];
|
|
1498
|
-
const rootBuffer = await getDataFile(prefixes, rootEKey, "build", this.version.BuildConfig,
|
|
1515
|
+
const rootBuffer = await getDataFile(prefixes, rootEKey, "build", this.version.BuildConfig, {
|
|
1516
|
+
name: "root",
|
|
1517
|
+
showProgress: this.logLevel >= 2 /* info */,
|
|
1518
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1519
|
+
});
|
|
1499
1520
|
this.log(2 /* info */, `Loaded root table (${formatFileSize(rootBuffer.byteLength)})`);
|
|
1500
1521
|
this.log(2 /* info */, "Parsing root file...");
|
|
1501
1522
|
const rootFile = parseRootFile(rootBuffer, rootEKey, rootCKey);
|
|
@@ -1574,7 +1595,16 @@ class CASCClient {
|
|
|
1574
1595
|
assert(eKeys, `Failing to find encoding key for ${cKey}`);
|
|
1575
1596
|
const eKey = typeof eKeys === "string" ? eKeys : eKeys[0];
|
|
1576
1597
|
const archive = archives.get(eKey);
|
|
1577
|
-
const blte = archive ? await getDataFile(prefixes, archive.key, "data", this.version.BuildConfig,
|
|
1598
|
+
const blte = archive ? await getDataFile(prefixes, archive.key, "data", this.version.BuildConfig, {
|
|
1599
|
+
name: eKey,
|
|
1600
|
+
partialOffset: archive.offset,
|
|
1601
|
+
partialLength: archive.size,
|
|
1602
|
+
showProgress: this.logLevel >= 2 /* info */,
|
|
1603
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1604
|
+
}) : await getDataFile(prefixes, eKey, "data", this.version.BuildConfig, {
|
|
1605
|
+
showProgress: this.logLevel >= 2 /* info */,
|
|
1606
|
+
showAttemptFail: this.logLevel >= 1 /* warn */
|
|
1607
|
+
});
|
|
1578
1608
|
const reader = new BLTEReader(blte, eKey, this.keys);
|
|
1579
1609
|
if (!allowMissingKey) {
|
|
1580
1610
|
reader.processBytes(allowMissingKey);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhyster/wow-casc-dbc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Fetch World of Warcraft data files from CASC and parse DBC/DB2 files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -37,10 +37,12 @@
|
|
|
37
37
|
"author": "Rhythm",
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"devDependencies": {
|
|
40
|
+
"@types/async": "^3.2.24",
|
|
41
|
+
"@types/cli-progress": "^3.11.5",
|
|
40
42
|
"@types/jest": "^29.5.12",
|
|
41
43
|
"@types/node": "^20.11.30",
|
|
42
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
43
|
-
"@typescript-eslint/parser": "^7.
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
|
45
|
+
"@typescript-eslint/parser": "^7.4.0",
|
|
44
46
|
"eslint": "^8.57.0",
|
|
45
47
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
46
48
|
"eslint-config-airbnb-typescript": "^18.0.0",
|
|
@@ -49,5 +51,9 @@
|
|
|
49
51
|
"ts-node": "^10.9.2",
|
|
50
52
|
"typescript": "^5.4.3",
|
|
51
53
|
"unbuild": "^2.0.0"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"async": "^3.2.5",
|
|
57
|
+
"cli-progress": "^3.12.0"
|
|
52
58
|
}
|
|
53
59
|
}
|