@rhyster/wow-casc-dbc 2.3.0 → 2.5.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/README.md CHANGED
@@ -4,7 +4,11 @@ Node.js tool to fetch World of Warcraft data files from CASC and parse DBC/DB2 f
4
4
 
5
5
  ## Snippet
6
6
 
7
+ ### Basic Usage
8
+
7
9
  ```javascript
10
+ import { CASCClient, WDCReader, DBDParser } from '@rhyster/wow-casc-dbc';
11
+
8
12
  // Get version
9
13
  const region = 'us';
10
14
  const product = 'wow';
@@ -14,10 +18,10 @@ const version = await CASCClient.getProductVersion(region, product);
14
18
  const client = new CASCClient(region, product, version);
15
19
  await client.init();
16
20
  await client.loadRemoteTACTKeys();
17
- await client.loadRemoteListFile();
21
+ await client.loadRemoteListFile(); // pretty slow, recommend to provide fileDataID directly
18
22
 
19
23
  // Fetch file
20
- const fileDataID = client.getFileDataIDByName('dbfilesclient/questxp.db2');
24
+ const fileDataID = client.getFileDataIDByName('dbfilesclient/questxp.db2'); // see previous line
21
25
  const cKeys = client.getContentKeysByFileDataID(fileDataID);
22
26
  const cKey = cKeys.find((data) => !!(data.localeFlags & CASCClient.LocaleFlags.enUS));
23
27
  const { buffer } = await client.getFileByContentKey(cKey.cKey);
@@ -27,8 +31,47 @@ const reader = new WDCReader(buffer);
27
31
  const parser = await DBDParser.parse(reader);
28
32
 
29
33
  // Access DB2 file
30
- // reader.getRowData
31
- // parser.getRowData
34
+ reader.getAllIDs().forEach((id) => {
35
+ const rowFields = reader.getRowData(id);
36
+ });
37
+ parser.getAllIDs().forEach((id) => {
38
+ const rowObject = parser.getRowData(id);
39
+ });
40
+ ```
41
+
42
+ ### Partial Decrypt
43
+
44
+ Some file is encrypted and no key released yet. For DB2 files, you can ignore the encrypted part and parse the others.
45
+
46
+ ```javascript
47
+ // ...
48
+
49
+ const result = await client.getFileByContentKey(cKey.cKey, true);
50
+ const reader = new WDCReader(result.buffer, result.type === 'partial' ? result.blocks : []);
51
+ const parser = await DBDParser.parse(reader);
52
+
53
+ // ...
54
+ ```
55
+
56
+ ### Hotfix
57
+
58
+ Applying hotfix requires `DBCache.bin` file from the client, and it seems the only way to get this is from the client. So, you need to search for `DBCache.bin` yourself, like `<WoWPath>/_retail_/Cache/ADB/enUS/DBCache.bin` or download it somewhere.
59
+
60
+ It's also important to compare build, region and locale with the db2 file that to be patched to avoid broken data.
61
+
62
+ ```javascript
63
+ // ...
64
+
65
+ const dbcache = await fs.readFile('path/to/DBCache.bin');
66
+ const buffer = await fs.readFile('path/to/name.db2');
67
+
68
+ const adb = new ADBReader(dbcache);
69
+ assert(adb.build === parseInt(version.BuildId, 10));
70
+
71
+ const reader = new WDCReader(buffer, [], adb);
72
+ const parser = await DBDParser.parse(reader);
73
+
74
+ // ...
32
75
  ```
33
76
 
34
77
  ## License
package/dist/index.cjs CHANGED
@@ -19,17 +19,17 @@ const path__default = /*#__PURE__*/_interopDefaultCompat(path);
19
19
  const http__default = /*#__PURE__*/_interopDefaultCompat(http);
20
20
  const zlib__default = /*#__PURE__*/_interopDefaultCompat(zlib);
21
21
 
22
- var __defProp$5 = Object.defineProperty;
23
- var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
24
- var __publicField$5 = (obj, key, value) => {
25
- __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
22
+ var __defProp$6 = Object.defineProperty;
23
+ var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
24
+ var __publicField$6 = (obj, key, value) => {
25
+ __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
26
26
  return value;
27
27
  };
28
28
  class Store {
29
29
  constructor(dataFile) {
30
- __publicField$5(this, "data");
31
- __publicField$5(this, "dataFile");
32
- __publicField$5(this, "promise");
30
+ __publicField$6(this, "data");
31
+ __publicField$6(this, "dataFile");
32
+ __publicField$6(this, "promise");
33
33
  this.dataFile = dataFile;
34
34
  this.data = {};
35
35
  this.promise = new Promise((resolve) => {
@@ -294,21 +294,21 @@ const parseArchiveIndex = (buffer, cKey) => {
294
294
  return result;
295
295
  };
296
296
 
297
- var __defProp$4 = Object.defineProperty;
298
- var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
299
- var __publicField$4 = (obj, key, value) => {
300
- __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
297
+ var __defProp$5 = Object.defineProperty;
298
+ var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
299
+ var __publicField$5 = (obj, key, value) => {
300
+ __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
301
301
  return value;
302
302
  };
303
303
  class Salsa20 {
304
304
  constructor(key, nonce) {
305
- __publicField$4(this, "fixed");
306
- __publicField$4(this, "key");
307
- __publicField$4(this, "nonce");
308
- __publicField$4(this, "counter", new Uint32Array([0, 0]));
309
- __publicField$4(this, "state", new Uint32Array(16));
310
- __publicField$4(this, "block", new Uint8Array(64));
311
- __publicField$4(this, "position", 0);
305
+ __publicField$5(this, "fixed");
306
+ __publicField$5(this, "key");
307
+ __publicField$5(this, "nonce");
308
+ __publicField$5(this, "counter", new Uint32Array([0, 0]));
309
+ __publicField$5(this, "state", new Uint32Array(16));
310
+ __publicField$5(this, "block", new Uint8Array(64));
311
+ __publicField$5(this, "position", 0);
312
312
  assert__default(key.length === 32 || key.length === 16, "Salsa20 requires 128-bit or 256-bit key");
313
313
  assert__default(nonce.length === 8, "Salsa20 requires 64-bit nonce");
314
314
  this.key = new Uint32Array(8);
@@ -411,10 +411,10 @@ class Salsa20 {
411
411
  }
412
412
  }
413
413
 
414
- var __defProp$3 = Object.defineProperty;
415
- var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
416
- var __publicField$3 = (obj, key, value) => {
417
- __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
414
+ var __defProp$4 = Object.defineProperty;
415
+ var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
416
+ var __publicField$4 = (obj, key, value) => {
417
+ __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
418
418
  return value;
419
419
  };
420
420
  const BLTE_MAGIC = 1112298565;
@@ -422,12 +422,12 @@ const ENC_TYPE_SALSA20 = 83;
422
422
  const EMPTY_HASH = "00000000000000000000000000000000";
423
423
  class BLTEReader {
424
424
  constructor(buffer, eKey, keys = /* @__PURE__ */ new Map()) {
425
- __publicField$3(this, "buffer");
426
- __publicField$3(this, "blte");
427
- __publicField$3(this, "blocks", []);
428
- __publicField$3(this, "keys");
429
- __publicField$3(this, "processedBlock", 0);
430
- __publicField$3(this, "processedOffset", 0);
425
+ __publicField$4(this, "buffer");
426
+ __publicField$4(this, "blte");
427
+ __publicField$4(this, "blocks", []);
428
+ __publicField$4(this, "keys");
429
+ __publicField$4(this, "processedBlock", 0);
430
+ __publicField$4(this, "processedOffset", 0);
431
431
  this.blte = buffer;
432
432
  this.buffer = Buffer.alloc(0);
433
433
  this.keys = keys;
@@ -895,10 +895,10 @@ const getNameHash = (name) => {
895
895
  return `${pc.toString(16).padStart(8, "0")}${pb.toString(16).padStart(8, "0")}`;
896
896
  };
897
897
 
898
- var __defProp$2 = Object.defineProperty;
899
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
900
- var __publicField$2 = (obj, key, value) => {
901
- __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
898
+ var __defProp$3 = Object.defineProperty;
899
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
900
+ var __publicField$3 = (obj, key, value) => {
901
+ __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
902
902
  return value;
903
903
  };
904
904
  const WDC5_MAGIC = 1464091445;
@@ -912,29 +912,26 @@ const readBitpackedValue = (buffer, fieldOffsetBits, fieldSizeBits, signed = fal
912
912
  signed ? BigInt.asIntN(fieldSizeBits, BigInt(rawValue >>> bitOffset)) : BigInt.asUintN(fieldSizeBits, BigInt(rawValue >>> bitOffset))
913
913
  );
914
914
  }
915
- let remain = sizeBytes;
916
915
  let value = 0n;
917
- while (remain > 0) {
918
- const byteLength = Math.min(remain, 6);
919
- const offset = offsetBytes + remain - byteLength;
920
- const rawValue = buffer.readUIntLE(offset, byteLength);
921
- value = value << BigInt(byteLength * 8) | BigInt(rawValue);
922
- remain -= byteLength;
916
+ for (let i = sizeBytes - 1; i >= 0; i -= 1) {
917
+ const byte = buffer.readUInt8(offsetBytes + i);
918
+ value = value << 8n | BigInt(byte);
923
919
  }
924
920
  return signed ? BigInt.asIntN(fieldSizeBits, value >> BigInt(bitOffset)) : BigInt.asUintN(fieldSizeBits, value >> BigInt(bitOffset));
925
921
  };
926
922
  class WDCReader {
927
- constructor(buffer, blocks = []) {
928
- __publicField$2(this, "tableHash");
929
- __publicField$2(this, "layoutHash");
930
- __publicField$2(this, "locale");
931
- __publicField$2(this, "isNormal");
932
- __publicField$2(this, "hasRelationshipData");
933
- __publicField$2(this, "fields");
934
- __publicField$2(this, "fieldsInfo");
935
- __publicField$2(this, "rows", /* @__PURE__ */ new Map());
936
- __publicField$2(this, "relationships", /* @__PURE__ */ new Map());
937
- __publicField$2(this, "copyTable", /* @__PURE__ */ new Map());
923
+ constructor(buffer, blocks = [], adb) {
924
+ __publicField$3(this, "tableHash");
925
+ __publicField$3(this, "layoutHash");
926
+ __publicField$3(this, "locale");
927
+ __publicField$3(this, "isNormal");
928
+ __publicField$3(this, "hasRelationshipData");
929
+ __publicField$3(this, "fields");
930
+ __publicField$3(this, "fieldsInfo");
931
+ __publicField$3(this, "rows", /* @__PURE__ */ new Map());
932
+ __publicField$3(this, "relationships", /* @__PURE__ */ new Map());
933
+ __publicField$3(this, "copyTable", /* @__PURE__ */ new Map());
934
+ __publicField$3(this, "hotfixes", /* @__PURE__ */ new Map());
938
935
  const magic = buffer.readUInt32BE(0);
939
936
  const fieldCount = buffer.readUInt32LE(140);
940
937
  const recordSize = buffer.readUInt32LE(144);
@@ -1339,11 +1336,43 @@ class WDCReader {
1339
1336
  }
1340
1337
  }
1341
1338
  });
1339
+ const entries = adb?.tableEntries.get(tableHash);
1340
+ entries?.filter((entry) => entry.pushID !== -1).sort((a, b) => a.pushID - b.pushID).forEach((entry) => {
1341
+ switch (entry.recordState) {
1342
+ case 1:
1343
+ this.hotfixes.set(entry.recordID, { type: "modify", data: entry.data });
1344
+ break;
1345
+ case 2:
1346
+ this.hotfixes.set(entry.recordID, { type: "delete" });
1347
+ break;
1348
+ case 3:
1349
+ this.hotfixes.delete(entry.recordID);
1350
+ break;
1351
+ case 4:
1352
+ break;
1353
+ default:
1354
+ throw new Error(`Unknown record state: ${entry.recordState.toString()}`);
1355
+ }
1356
+ });
1342
1357
  }
1343
1358
  getAllIDs() {
1344
1359
  return [...this.rows.keys(), ...this.copyTable.keys()];
1345
1360
  }
1346
1361
  getRowData(id) {
1362
+ const hotfix = this.hotfixes.get(id);
1363
+ if (hotfix) {
1364
+ switch (hotfix.type) {
1365
+ case "modify":
1366
+ return {
1367
+ type: "sparse",
1368
+ data: hotfix.data
1369
+ };
1370
+ case "delete":
1371
+ return void 0;
1372
+ default:
1373
+ throw new Error("Unreachable");
1374
+ }
1375
+ }
1347
1376
  const dst = this.copyTable.get(id);
1348
1377
  if (dst) {
1349
1378
  return this.rows.get(dst);
@@ -1405,10 +1434,10 @@ const formatFileSize = (input) => {
1405
1434
  return result.join(" ");
1406
1435
  };
1407
1436
 
1408
- var __defProp$1 = Object.defineProperty;
1409
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1410
- var __publicField$1 = (obj, key, value) => {
1411
- __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1437
+ var __defProp$2 = Object.defineProperty;
1438
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1439
+ var __publicField$2 = (obj, key, value) => {
1440
+ __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
1412
1441
  return value;
1413
1442
  };
1414
1443
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
@@ -1421,13 +1450,13 @@ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
1421
1450
  const textLogLevel = ["ERROR", "WARN", "INFO", "DEBUG"];
1422
1451
  class CASCClient {
1423
1452
  constructor(region, product, version, logLevel = 2 /* info */) {
1424
- __publicField$1(this, "region");
1425
- __publicField$1(this, "product");
1426
- __publicField$1(this, "version");
1427
- __publicField$1(this, "name2FileDataID", /* @__PURE__ */ new Map());
1428
- __publicField$1(this, "keys", /* @__PURE__ */ new Map());
1429
- __publicField$1(this, "preload");
1430
- __publicField$1(this, "logLevel");
1453
+ __publicField$2(this, "region");
1454
+ __publicField$2(this, "product");
1455
+ __publicField$2(this, "version");
1456
+ __publicField$2(this, "name2FileDataID", /* @__PURE__ */ new Map());
1457
+ __publicField$2(this, "keys", /* @__PURE__ */ new Map());
1458
+ __publicField$2(this, "preload");
1459
+ __publicField$2(this, "logLevel");
1431
1460
  this.region = region;
1432
1461
  this.product = product;
1433
1462
  this.version = version;
@@ -1572,7 +1601,7 @@ class CASCClient {
1572
1601
  ]);
1573
1602
  const keysReader = new WDCReader(keysResult.buffer);
1574
1603
  const lookupReader = new WDCReader(lookupResult.buffer);
1575
- [...lookupReader.rows.keys()].forEach((keyID) => {
1604
+ lookupReader.getAllIDs().forEach((keyID) => {
1576
1605
  const lookupRow = lookupReader.rows.get(keyID);
1577
1606
  const keyRow = keysReader.rows.get(keyID);
1578
1607
  if (keyRow) {
@@ -1588,6 +1617,31 @@ class CASCClient {
1588
1617
  }
1589
1618
  });
1590
1619
  }
1620
+ loadBroadcastTACTKeys(adb) {
1621
+ adb.tableEntries.get(35137211)?.forEach(({ data }) => {
1622
+ if (data.byteLength > 0) {
1623
+ let pointer = 0;
1624
+ while (data[pointer] !== 0) {
1625
+ pointer += 1;
1626
+ }
1627
+ pointer += 1;
1628
+ while (data[pointer] !== 0) {
1629
+ pointer += 1;
1630
+ }
1631
+ pointer += 1 + 43;
1632
+ if (pointer < data.byteLength) {
1633
+ const extraTableHash = data.readUInt32LE(pointer);
1634
+ if (extraTableHash === 3744420815) {
1635
+ const keyName = data.readBigUInt64LE(pointer + 4).toString(16).padStart(16, "0");
1636
+ const key = Uint8Array.from(data.subarray(pointer + 12));
1637
+ if (!this.keys.has(keyName)) {
1638
+ this.keys.set(keyName, key);
1639
+ }
1640
+ }
1641
+ }
1642
+ }
1643
+ });
1644
+ }
1591
1645
  getFileDataIDByName(name) {
1592
1646
  assert__default(this.preload, "Client not initialized");
1593
1647
  const { rootFile } = this.preload;
@@ -1641,14 +1695,14 @@ class CASCClient {
1641
1695
  };
1642
1696
  }
1643
1697
  }
1644
- __publicField$1(CASCClient, "LocaleFlags", LocaleFlags);
1645
- __publicField$1(CASCClient, "ContentFlags", ContentFlags);
1646
- __publicField$1(CASCClient, "LogLevel", LogLevel);
1698
+ __publicField$2(CASCClient, "LocaleFlags", LocaleFlags);
1699
+ __publicField$2(CASCClient, "ContentFlags", ContentFlags);
1700
+ __publicField$2(CASCClient, "LogLevel", LogLevel);
1647
1701
 
1648
- var __defProp = Object.defineProperty;
1649
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1650
- var __publicField = (obj, key, value) => {
1651
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1702
+ var __defProp$1 = Object.defineProperty;
1703
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1704
+ var __publicField$1 = (obj, key, value) => {
1705
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1652
1706
  return value;
1653
1707
  };
1654
1708
  const PATTERN_COLUMN = /^(int|float|locstring|string)(<[^:]+::[^>]+>)?\s([^\s]+)/;
@@ -1682,12 +1736,21 @@ const castBigInt64 = (value, srcSigned, dstSigned) => {
1682
1736
  }
1683
1737
  return dstSigned ? castBuffer.readBigInt64LE(0) : castBuffer.readBigUInt64LE(0);
1684
1738
  };
1739
+ const getCastBuffer = (value, srcSize, dstSize) => {
1740
+ const castBuffer = Buffer.alloc(dstSize);
1741
+ let remain = value;
1742
+ for (let i = 0; i < srcSize && remain > 0n; i += 1, remain >>= 8n) {
1743
+ const byte = Number(BigInt.asUintN(8, remain));
1744
+ castBuffer.writeUInt8(byte, i);
1745
+ }
1746
+ return castBuffer;
1747
+ };
1685
1748
  class DBDParser {
1686
1749
  constructor(wdc) {
1687
- __publicField(this, "wdc");
1688
- __publicField(this, "definitions", /* @__PURE__ */ new Map());
1689
- __publicField(this, "columns", []);
1690
- __publicField(this, "cache", /* @__PURE__ */ new Map());
1750
+ __publicField$1(this, "wdc");
1751
+ __publicField$1(this, "definitions", /* @__PURE__ */ new Map());
1752
+ __publicField$1(this, "columns", []);
1753
+ __publicField$1(this, "cache", /* @__PURE__ */ new Map());
1691
1754
  this.wdc = wdc;
1692
1755
  }
1693
1756
  async init() {
@@ -1809,9 +1872,12 @@ class DBDParser {
1809
1872
  }
1810
1873
  } else if (column.type === "float") {
1811
1874
  if (column.arraySize) {
1812
- assert__default(typeof cell.data === "bigint", `Invalid data type for float array column ${column.name}`);
1875
+ const castBuffer = getCastBuffer(
1876
+ typeof cell.data === "number" ? BigInt(cell.data) : cell.data,
1877
+ srcSize,
1878
+ 4 * column.arraySize
1879
+ );
1813
1880
  const values = [];
1814
- const castBuffer = Buffer.from(cell.data.toString(16).padStart(8 * column.arraySize, "0"), "hex");
1815
1881
  for (let i = 0; i < column.arraySize; i += 1) {
1816
1882
  const value = castBuffer.readFloatLE(i * 4);
1817
1883
  values.push(Math.round(value * 100) / 100);
@@ -1821,25 +1887,49 @@ class DBDParser {
1821
1887
  assert__default(typeof cell.data === "number", `Invalid data type for float column ${column.name}`);
1822
1888
  data[column.name] = castFloat(cell.data, srcSize, srcSigned);
1823
1889
  }
1824
- } else if (typeof cell.data === "number") {
1825
- data[column.name] = castIntegerBySize(
1826
- cell.data,
1827
- srcSize,
1828
- srcSigned,
1829
- dstSize ?? srcSize,
1830
- column.isSigned
1831
- );
1832
- } else {
1833
- assert__default(!column.size || column.size === 64, `Unexpected size ${column.size?.toString() ?? ""} for column ${column.name}`);
1834
- if (srcSigned !== column.isSigned) {
1835
- data[column.name] = castBigInt64(
1890
+ } else if (column.type === "int") {
1891
+ if (column.arraySize) {
1892
+ assert__default(dstSize, `Missing size for int array column ${column.name}`);
1893
+ const castBuffer = getCastBuffer(
1894
+ typeof cell.data === "number" ? BigInt(cell.data) : cell.data,
1895
+ srcSize,
1896
+ 4 * column.arraySize
1897
+ );
1898
+ const values = [];
1899
+ if (column.isSigned) {
1900
+ for (let i = 0; i < column.arraySize; i += 1) {
1901
+ const value = castBuffer.readIntLE(i * dstSize, dstSize);
1902
+ values.push(value);
1903
+ }
1904
+ } else {
1905
+ for (let i = 0; i < column.arraySize; i += 1) {
1906
+ const value = castBuffer.readUIntLE(i * dstSize, dstSize);
1907
+ values.push(value);
1908
+ }
1909
+ }
1910
+ data[column.name] = values;
1911
+ } else if (typeof cell.data === "number") {
1912
+ data[column.name] = castIntegerBySize(
1836
1913
  cell.data,
1914
+ srcSize,
1837
1915
  srcSigned,
1916
+ dstSize ?? srcSize,
1838
1917
  column.isSigned
1839
1918
  );
1840
1919
  } else {
1841
- data[column.name] = cell.data;
1920
+ assert__default(!column.size || column.size === 64, `Unexpected size ${column.size?.toString() ?? ""} for column ${column.name}`);
1921
+ if (srcSigned !== column.isSigned) {
1922
+ data[column.name] = castBigInt64(
1923
+ cell.data,
1924
+ srcSigned,
1925
+ column.isSigned
1926
+ );
1927
+ } else {
1928
+ data[column.name] = cell.data;
1929
+ }
1842
1930
  }
1931
+ } else {
1932
+ throw new Error(`Unsupported column type ${column.type} for column ${column.name}`);
1843
1933
  }
1844
1934
  fieldIndex += 1;
1845
1935
  } else if (column.isRelation) {
@@ -1855,6 +1945,9 @@ class DBDParser {
1855
1945
  if (column.isID) {
1856
1946
  data[column.name] = id;
1857
1947
  if (column.isInline) {
1948
+ const currField = this.wdc.fields[fieldIndex];
1949
+ const size = Math.ceil((column.size ?? 32 - currField.size) / 8);
1950
+ offset += size;
1858
1951
  fieldIndex += 1;
1859
1952
  }
1860
1953
  } else if (column.isInline) {
@@ -1876,7 +1969,7 @@ class DBDParser {
1876
1969
  const size = Math.ceil((column.size ?? 32 - currField.size) / 8);
1877
1970
  let count;
1878
1971
  if (fieldIndex + 1 < this.wdc.fields.length) {
1879
- count = (nextField.position - currField.position) / size;
1972
+ count = Math.max((nextField.position - currField.position) / size, 1);
1880
1973
  } else {
1881
1974
  count = column.arraySize ? (buffer.byteLength - offset) / size : 1;
1882
1975
  }
@@ -1910,6 +2003,58 @@ class DBDParser {
1910
2003
  }
1911
2004
  }
1912
2005
 
2006
+ var __defProp = Object.defineProperty;
2007
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
2008
+ var __publicField = (obj, key, value) => {
2009
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
2010
+ return value;
2011
+ };
2012
+ const ADB_MAGIC = 1481004104;
2013
+ class ADBReader {
2014
+ constructor(buffer) {
2015
+ __publicField(this, "build");
2016
+ __publicField(this, "entries", []);
2017
+ __publicField(this, "tableEntries", /* @__PURE__ */ new Map());
2018
+ const magic = buffer.readUInt32BE(0);
2019
+ assert__default(magic === ADB_MAGIC, `[ADB]: Invalid magic: ${magic.toString(16).padStart(8, "0")}`);
2020
+ const version = buffer.readUInt32LE(4);
2021
+ assert__default(version === 9, `[ADB]: Invalid version: ${version.toString()}`);
2022
+ const build = buffer.readUInt32LE(8);
2023
+ this.build = build;
2024
+ let pointer = 44;
2025
+ while (pointer < buffer.byteLength) {
2026
+ const offset = pointer;
2027
+ const entryMagic = buffer.readUInt32BE(offset);
2028
+ assert__default(entryMagic === ADB_MAGIC, `[ADB]: Invalid entry magic: ${magic.toString(16).padStart(8, "0")}`);
2029
+ const regionID = buffer.readInt32LE(offset + 4);
2030
+ const pushID = buffer.readInt32LE(offset + 8);
2031
+ const uniqueID = buffer.readUInt32LE(offset + 12);
2032
+ const tableHash = buffer.readUInt32LE(offset + 16);
2033
+ const recordID = buffer.readUInt32LE(offset + 20);
2034
+ const dataSize = buffer.readUInt32LE(offset + 24);
2035
+ const recordState = buffer.readUInt32LE(offset + 28);
2036
+ const data = buffer.subarray(offset + 32, offset + 32 + dataSize);
2037
+ const entry = {
2038
+ regionID,
2039
+ pushID,
2040
+ uniqueID,
2041
+ tableHash,
2042
+ recordID,
2043
+ dataSize,
2044
+ recordState,
2045
+ data
2046
+ };
2047
+ this.entries.push(entry);
2048
+ if (!this.tableEntries.has(tableHash)) {
2049
+ this.tableEntries.set(tableHash, []);
2050
+ }
2051
+ this.tableEntries.get(tableHash)?.push(entry);
2052
+ pointer += 32 + dataSize;
2053
+ }
2054
+ }
2055
+ }
2056
+
2057
+ exports.ADBReader = ADBReader;
1913
2058
  exports.CASCClient = CASCClient;
1914
2059
  exports.DBDParser = DBDParser;
1915
2060
  exports.WDCReader = WDCReader;
package/dist/index.d.cts CHANGED
@@ -42,6 +42,23 @@ interface MissingKeyBlock {
42
42
  keyName: string;
43
43
  }
44
44
 
45
+ interface HotfixEntry {
46
+ regionID: number;
47
+ pushID: number;
48
+ uniqueID: number;
49
+ tableHash: number;
50
+ recordID: number;
51
+ dataSize: number;
52
+ recordState: number;
53
+ data: Buffer;
54
+ }
55
+ declare class ADBReader {
56
+ build: number;
57
+ entries: HotfixEntry[];
58
+ tableEntries: Map<number, HotfixEntry[]>;
59
+ constructor(buffer: Buffer);
60
+ }
61
+
45
62
  interface ClientPreloadData {
46
63
  prefixes: string[];
47
64
  archives: ReturnType<typeof parseArchiveIndex>;
@@ -111,6 +128,7 @@ declare class CASCClient {
111
128
  loadRemoteListFile(): Promise<void>;
112
129
  loadRemoteTACTKeys(): Promise<void>;
113
130
  loadTACTKeys(): Promise<void>;
131
+ loadBroadcastTACTKeys(adb: ADBReader): void;
114
132
  getFileDataIDByName(name: string): number | undefined;
115
133
  getContentKeysByFileDataID(fileDataID: number): FileInfo[] | undefined;
116
134
  getFileByContentKey(cKey: string, allowMissingKey?: false): Promise<FileFetchResultFull>;
@@ -192,6 +210,14 @@ interface SparseRow {
192
210
  type: 'sparse';
193
211
  data: Buffer;
194
212
  }
213
+ interface HotfixModify {
214
+ type: 'modify';
215
+ data: Buffer;
216
+ }
217
+ interface HotfixDelete {
218
+ type: 'delete';
219
+ }
220
+ type Hotfix = HotfixModify | HotfixDelete;
195
221
  declare class WDCReader {
196
222
  readonly tableHash: number;
197
223
  readonly layoutHash: number;
@@ -203,7 +229,8 @@ declare class WDCReader {
203
229
  readonly rows: Map<number, SparseRow | ParsedField[]>;
204
230
  readonly relationships: Map<number, number>;
205
231
  readonly copyTable: Map<number, number>;
206
- constructor(buffer: Buffer, blocks?: MissingKeyBlock[]);
232
+ readonly hotfixes: Map<number, Hotfix>;
233
+ constructor(buffer: Buffer, blocks?: MissingKeyBlock[], adb?: ADBReader);
207
234
  getAllIDs(): number[];
208
235
  getRowData(id: number): ParsedField[] | SparseRow | undefined;
209
236
  getRowRelationship(id: number): number | undefined;
@@ -232,4 +259,4 @@ declare class DBDParser {
232
259
  getRowData(id: number): Record<string, ColumnData | ColumnData[]> | undefined;
233
260
  }
234
261
 
235
- export { CASCClient, DBDParser, WDCReader };
262
+ export { ADBReader, CASCClient, DBDParser, WDCReader };
package/dist/index.d.mts CHANGED
@@ -42,6 +42,23 @@ interface MissingKeyBlock {
42
42
  keyName: string;
43
43
  }
44
44
 
45
+ interface HotfixEntry {
46
+ regionID: number;
47
+ pushID: number;
48
+ uniqueID: number;
49
+ tableHash: number;
50
+ recordID: number;
51
+ dataSize: number;
52
+ recordState: number;
53
+ data: Buffer;
54
+ }
55
+ declare class ADBReader {
56
+ build: number;
57
+ entries: HotfixEntry[];
58
+ tableEntries: Map<number, HotfixEntry[]>;
59
+ constructor(buffer: Buffer);
60
+ }
61
+
45
62
  interface ClientPreloadData {
46
63
  prefixes: string[];
47
64
  archives: ReturnType<typeof parseArchiveIndex>;
@@ -111,6 +128,7 @@ declare class CASCClient {
111
128
  loadRemoteListFile(): Promise<void>;
112
129
  loadRemoteTACTKeys(): Promise<void>;
113
130
  loadTACTKeys(): Promise<void>;
131
+ loadBroadcastTACTKeys(adb: ADBReader): void;
114
132
  getFileDataIDByName(name: string): number | undefined;
115
133
  getContentKeysByFileDataID(fileDataID: number): FileInfo[] | undefined;
116
134
  getFileByContentKey(cKey: string, allowMissingKey?: false): Promise<FileFetchResultFull>;
@@ -192,6 +210,14 @@ interface SparseRow {
192
210
  type: 'sparse';
193
211
  data: Buffer;
194
212
  }
213
+ interface HotfixModify {
214
+ type: 'modify';
215
+ data: Buffer;
216
+ }
217
+ interface HotfixDelete {
218
+ type: 'delete';
219
+ }
220
+ type Hotfix = HotfixModify | HotfixDelete;
195
221
  declare class WDCReader {
196
222
  readonly tableHash: number;
197
223
  readonly layoutHash: number;
@@ -203,7 +229,8 @@ declare class WDCReader {
203
229
  readonly rows: Map<number, SparseRow | ParsedField[]>;
204
230
  readonly relationships: Map<number, number>;
205
231
  readonly copyTable: Map<number, number>;
206
- constructor(buffer: Buffer, blocks?: MissingKeyBlock[]);
232
+ readonly hotfixes: Map<number, Hotfix>;
233
+ constructor(buffer: Buffer, blocks?: MissingKeyBlock[], adb?: ADBReader);
207
234
  getAllIDs(): number[];
208
235
  getRowData(id: number): ParsedField[] | SparseRow | undefined;
209
236
  getRowRelationship(id: number): number | undefined;
@@ -232,4 +259,4 @@ declare class DBDParser {
232
259
  getRowData(id: number): Record<string, ColumnData | ColumnData[]> | undefined;
233
260
  }
234
261
 
235
- export { CASCClient, DBDParser, WDCReader };
262
+ export { ADBReader, CASCClient, DBDParser, WDCReader };
package/dist/index.d.ts CHANGED
@@ -42,6 +42,23 @@ interface MissingKeyBlock {
42
42
  keyName: string;
43
43
  }
44
44
 
45
+ interface HotfixEntry {
46
+ regionID: number;
47
+ pushID: number;
48
+ uniqueID: number;
49
+ tableHash: number;
50
+ recordID: number;
51
+ dataSize: number;
52
+ recordState: number;
53
+ data: Buffer;
54
+ }
55
+ declare class ADBReader {
56
+ build: number;
57
+ entries: HotfixEntry[];
58
+ tableEntries: Map<number, HotfixEntry[]>;
59
+ constructor(buffer: Buffer);
60
+ }
61
+
45
62
  interface ClientPreloadData {
46
63
  prefixes: string[];
47
64
  archives: ReturnType<typeof parseArchiveIndex>;
@@ -111,6 +128,7 @@ declare class CASCClient {
111
128
  loadRemoteListFile(): Promise<void>;
112
129
  loadRemoteTACTKeys(): Promise<void>;
113
130
  loadTACTKeys(): Promise<void>;
131
+ loadBroadcastTACTKeys(adb: ADBReader): void;
114
132
  getFileDataIDByName(name: string): number | undefined;
115
133
  getContentKeysByFileDataID(fileDataID: number): FileInfo[] | undefined;
116
134
  getFileByContentKey(cKey: string, allowMissingKey?: false): Promise<FileFetchResultFull>;
@@ -192,6 +210,14 @@ interface SparseRow {
192
210
  type: 'sparse';
193
211
  data: Buffer;
194
212
  }
213
+ interface HotfixModify {
214
+ type: 'modify';
215
+ data: Buffer;
216
+ }
217
+ interface HotfixDelete {
218
+ type: 'delete';
219
+ }
220
+ type Hotfix = HotfixModify | HotfixDelete;
195
221
  declare class WDCReader {
196
222
  readonly tableHash: number;
197
223
  readonly layoutHash: number;
@@ -203,7 +229,8 @@ declare class WDCReader {
203
229
  readonly rows: Map<number, SparseRow | ParsedField[]>;
204
230
  readonly relationships: Map<number, number>;
205
231
  readonly copyTable: Map<number, number>;
206
- constructor(buffer: Buffer, blocks?: MissingKeyBlock[]);
232
+ readonly hotfixes: Map<number, Hotfix>;
233
+ constructor(buffer: Buffer, blocks?: MissingKeyBlock[], adb?: ADBReader);
207
234
  getAllIDs(): number[];
208
235
  getRowData(id: number): ParsedField[] | SparseRow | undefined;
209
236
  getRowRelationship(id: number): number | undefined;
@@ -232,4 +259,4 @@ declare class DBDParser {
232
259
  getRowData(id: number): Record<string, ColumnData | ColumnData[]> | undefined;
233
260
  }
234
261
 
235
- export { CASCClient, DBDParser, WDCReader };
262
+ export { ADBReader, CASCClient, DBDParser, WDCReader };
package/dist/index.mjs CHANGED
@@ -7,17 +7,17 @@ import path from 'node:path';
7
7
  import http from 'node:http';
8
8
  import zlib from 'node:zlib';
9
9
 
10
- var __defProp$5 = Object.defineProperty;
11
- var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
- var __publicField$5 = (obj, key, value) => {
13
- __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
10
+ var __defProp$6 = Object.defineProperty;
11
+ var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
12
+ var __publicField$6 = (obj, key, value) => {
13
+ __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
14
14
  return value;
15
15
  };
16
16
  class Store {
17
17
  constructor(dataFile) {
18
- __publicField$5(this, "data");
19
- __publicField$5(this, "dataFile");
20
- __publicField$5(this, "promise");
18
+ __publicField$6(this, "data");
19
+ __publicField$6(this, "dataFile");
20
+ __publicField$6(this, "promise");
21
21
  this.dataFile = dataFile;
22
22
  this.data = {};
23
23
  this.promise = new Promise((resolve) => {
@@ -282,21 +282,21 @@ const parseArchiveIndex = (buffer, cKey) => {
282
282
  return result;
283
283
  };
284
284
 
285
- var __defProp$4 = Object.defineProperty;
286
- var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
287
- var __publicField$4 = (obj, key, value) => {
288
- __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
285
+ var __defProp$5 = Object.defineProperty;
286
+ var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
287
+ var __publicField$5 = (obj, key, value) => {
288
+ __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
289
289
  return value;
290
290
  };
291
291
  class Salsa20 {
292
292
  constructor(key, nonce) {
293
- __publicField$4(this, "fixed");
294
- __publicField$4(this, "key");
295
- __publicField$4(this, "nonce");
296
- __publicField$4(this, "counter", new Uint32Array([0, 0]));
297
- __publicField$4(this, "state", new Uint32Array(16));
298
- __publicField$4(this, "block", new Uint8Array(64));
299
- __publicField$4(this, "position", 0);
293
+ __publicField$5(this, "fixed");
294
+ __publicField$5(this, "key");
295
+ __publicField$5(this, "nonce");
296
+ __publicField$5(this, "counter", new Uint32Array([0, 0]));
297
+ __publicField$5(this, "state", new Uint32Array(16));
298
+ __publicField$5(this, "block", new Uint8Array(64));
299
+ __publicField$5(this, "position", 0);
300
300
  assert(key.length === 32 || key.length === 16, "Salsa20 requires 128-bit or 256-bit key");
301
301
  assert(nonce.length === 8, "Salsa20 requires 64-bit nonce");
302
302
  this.key = new Uint32Array(8);
@@ -399,10 +399,10 @@ class Salsa20 {
399
399
  }
400
400
  }
401
401
 
402
- var __defProp$3 = Object.defineProperty;
403
- var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
404
- var __publicField$3 = (obj, key, value) => {
405
- __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
402
+ var __defProp$4 = Object.defineProperty;
403
+ var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
404
+ var __publicField$4 = (obj, key, value) => {
405
+ __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
406
406
  return value;
407
407
  };
408
408
  const BLTE_MAGIC = 1112298565;
@@ -410,12 +410,12 @@ const ENC_TYPE_SALSA20 = 83;
410
410
  const EMPTY_HASH = "00000000000000000000000000000000";
411
411
  class BLTEReader {
412
412
  constructor(buffer, eKey, keys = /* @__PURE__ */ new Map()) {
413
- __publicField$3(this, "buffer");
414
- __publicField$3(this, "blte");
415
- __publicField$3(this, "blocks", []);
416
- __publicField$3(this, "keys");
417
- __publicField$3(this, "processedBlock", 0);
418
- __publicField$3(this, "processedOffset", 0);
413
+ __publicField$4(this, "buffer");
414
+ __publicField$4(this, "blte");
415
+ __publicField$4(this, "blocks", []);
416
+ __publicField$4(this, "keys");
417
+ __publicField$4(this, "processedBlock", 0);
418
+ __publicField$4(this, "processedOffset", 0);
419
419
  this.blte = buffer;
420
420
  this.buffer = Buffer.alloc(0);
421
421
  this.keys = keys;
@@ -883,10 +883,10 @@ const getNameHash = (name) => {
883
883
  return `${pc.toString(16).padStart(8, "0")}${pb.toString(16).padStart(8, "0")}`;
884
884
  };
885
885
 
886
- var __defProp$2 = Object.defineProperty;
887
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
888
- var __publicField$2 = (obj, key, value) => {
889
- __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
886
+ var __defProp$3 = Object.defineProperty;
887
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
888
+ var __publicField$3 = (obj, key, value) => {
889
+ __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
890
890
  return value;
891
891
  };
892
892
  const WDC5_MAGIC = 1464091445;
@@ -900,29 +900,26 @@ const readBitpackedValue = (buffer, fieldOffsetBits, fieldSizeBits, signed = fal
900
900
  signed ? BigInt.asIntN(fieldSizeBits, BigInt(rawValue >>> bitOffset)) : BigInt.asUintN(fieldSizeBits, BigInt(rawValue >>> bitOffset))
901
901
  );
902
902
  }
903
- let remain = sizeBytes;
904
903
  let value = 0n;
905
- while (remain > 0) {
906
- const byteLength = Math.min(remain, 6);
907
- const offset = offsetBytes + remain - byteLength;
908
- const rawValue = buffer.readUIntLE(offset, byteLength);
909
- value = value << BigInt(byteLength * 8) | BigInt(rawValue);
910
- remain -= byteLength;
904
+ for (let i = sizeBytes - 1; i >= 0; i -= 1) {
905
+ const byte = buffer.readUInt8(offsetBytes + i);
906
+ value = value << 8n | BigInt(byte);
911
907
  }
912
908
  return signed ? BigInt.asIntN(fieldSizeBits, value >> BigInt(bitOffset)) : BigInt.asUintN(fieldSizeBits, value >> BigInt(bitOffset));
913
909
  };
914
910
  class WDCReader {
915
- constructor(buffer, blocks = []) {
916
- __publicField$2(this, "tableHash");
917
- __publicField$2(this, "layoutHash");
918
- __publicField$2(this, "locale");
919
- __publicField$2(this, "isNormal");
920
- __publicField$2(this, "hasRelationshipData");
921
- __publicField$2(this, "fields");
922
- __publicField$2(this, "fieldsInfo");
923
- __publicField$2(this, "rows", /* @__PURE__ */ new Map());
924
- __publicField$2(this, "relationships", /* @__PURE__ */ new Map());
925
- __publicField$2(this, "copyTable", /* @__PURE__ */ new Map());
911
+ constructor(buffer, blocks = [], adb) {
912
+ __publicField$3(this, "tableHash");
913
+ __publicField$3(this, "layoutHash");
914
+ __publicField$3(this, "locale");
915
+ __publicField$3(this, "isNormal");
916
+ __publicField$3(this, "hasRelationshipData");
917
+ __publicField$3(this, "fields");
918
+ __publicField$3(this, "fieldsInfo");
919
+ __publicField$3(this, "rows", /* @__PURE__ */ new Map());
920
+ __publicField$3(this, "relationships", /* @__PURE__ */ new Map());
921
+ __publicField$3(this, "copyTable", /* @__PURE__ */ new Map());
922
+ __publicField$3(this, "hotfixes", /* @__PURE__ */ new Map());
926
923
  const magic = buffer.readUInt32BE(0);
927
924
  const fieldCount = buffer.readUInt32LE(140);
928
925
  const recordSize = buffer.readUInt32LE(144);
@@ -1327,11 +1324,43 @@ class WDCReader {
1327
1324
  }
1328
1325
  }
1329
1326
  });
1327
+ const entries = adb?.tableEntries.get(tableHash);
1328
+ entries?.filter((entry) => entry.pushID !== -1).sort((a, b) => a.pushID - b.pushID).forEach((entry) => {
1329
+ switch (entry.recordState) {
1330
+ case 1:
1331
+ this.hotfixes.set(entry.recordID, { type: "modify", data: entry.data });
1332
+ break;
1333
+ case 2:
1334
+ this.hotfixes.set(entry.recordID, { type: "delete" });
1335
+ break;
1336
+ case 3:
1337
+ this.hotfixes.delete(entry.recordID);
1338
+ break;
1339
+ case 4:
1340
+ break;
1341
+ default:
1342
+ throw new Error(`Unknown record state: ${entry.recordState.toString()}`);
1343
+ }
1344
+ });
1330
1345
  }
1331
1346
  getAllIDs() {
1332
1347
  return [...this.rows.keys(), ...this.copyTable.keys()];
1333
1348
  }
1334
1349
  getRowData(id) {
1350
+ const hotfix = this.hotfixes.get(id);
1351
+ if (hotfix) {
1352
+ switch (hotfix.type) {
1353
+ case "modify":
1354
+ return {
1355
+ type: "sparse",
1356
+ data: hotfix.data
1357
+ };
1358
+ case "delete":
1359
+ return void 0;
1360
+ default:
1361
+ throw new Error("Unreachable");
1362
+ }
1363
+ }
1335
1364
  const dst = this.copyTable.get(id);
1336
1365
  if (dst) {
1337
1366
  return this.rows.get(dst);
@@ -1393,10 +1422,10 @@ const formatFileSize = (input) => {
1393
1422
  return result.join(" ");
1394
1423
  };
1395
1424
 
1396
- var __defProp$1 = Object.defineProperty;
1397
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1398
- var __publicField$1 = (obj, key, value) => {
1399
- __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1425
+ var __defProp$2 = Object.defineProperty;
1426
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1427
+ var __publicField$2 = (obj, key, value) => {
1428
+ __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
1400
1429
  return value;
1401
1430
  };
1402
1431
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
@@ -1409,13 +1438,13 @@ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
1409
1438
  const textLogLevel = ["ERROR", "WARN", "INFO", "DEBUG"];
1410
1439
  class CASCClient {
1411
1440
  constructor(region, product, version, logLevel = 2 /* info */) {
1412
- __publicField$1(this, "region");
1413
- __publicField$1(this, "product");
1414
- __publicField$1(this, "version");
1415
- __publicField$1(this, "name2FileDataID", /* @__PURE__ */ new Map());
1416
- __publicField$1(this, "keys", /* @__PURE__ */ new Map());
1417
- __publicField$1(this, "preload");
1418
- __publicField$1(this, "logLevel");
1441
+ __publicField$2(this, "region");
1442
+ __publicField$2(this, "product");
1443
+ __publicField$2(this, "version");
1444
+ __publicField$2(this, "name2FileDataID", /* @__PURE__ */ new Map());
1445
+ __publicField$2(this, "keys", /* @__PURE__ */ new Map());
1446
+ __publicField$2(this, "preload");
1447
+ __publicField$2(this, "logLevel");
1419
1448
  this.region = region;
1420
1449
  this.product = product;
1421
1450
  this.version = version;
@@ -1560,7 +1589,7 @@ class CASCClient {
1560
1589
  ]);
1561
1590
  const keysReader = new WDCReader(keysResult.buffer);
1562
1591
  const lookupReader = new WDCReader(lookupResult.buffer);
1563
- [...lookupReader.rows.keys()].forEach((keyID) => {
1592
+ lookupReader.getAllIDs().forEach((keyID) => {
1564
1593
  const lookupRow = lookupReader.rows.get(keyID);
1565
1594
  const keyRow = keysReader.rows.get(keyID);
1566
1595
  if (keyRow) {
@@ -1576,6 +1605,31 @@ class CASCClient {
1576
1605
  }
1577
1606
  });
1578
1607
  }
1608
+ loadBroadcastTACTKeys(adb) {
1609
+ adb.tableEntries.get(35137211)?.forEach(({ data }) => {
1610
+ if (data.byteLength > 0) {
1611
+ let pointer = 0;
1612
+ while (data[pointer] !== 0) {
1613
+ pointer += 1;
1614
+ }
1615
+ pointer += 1;
1616
+ while (data[pointer] !== 0) {
1617
+ pointer += 1;
1618
+ }
1619
+ pointer += 1 + 43;
1620
+ if (pointer < data.byteLength) {
1621
+ const extraTableHash = data.readUInt32LE(pointer);
1622
+ if (extraTableHash === 3744420815) {
1623
+ const keyName = data.readBigUInt64LE(pointer + 4).toString(16).padStart(16, "0");
1624
+ const key = Uint8Array.from(data.subarray(pointer + 12));
1625
+ if (!this.keys.has(keyName)) {
1626
+ this.keys.set(keyName, key);
1627
+ }
1628
+ }
1629
+ }
1630
+ }
1631
+ });
1632
+ }
1579
1633
  getFileDataIDByName(name) {
1580
1634
  assert(this.preload, "Client not initialized");
1581
1635
  const { rootFile } = this.preload;
@@ -1629,14 +1683,14 @@ class CASCClient {
1629
1683
  };
1630
1684
  }
1631
1685
  }
1632
- __publicField$1(CASCClient, "LocaleFlags", LocaleFlags);
1633
- __publicField$1(CASCClient, "ContentFlags", ContentFlags);
1634
- __publicField$1(CASCClient, "LogLevel", LogLevel);
1686
+ __publicField$2(CASCClient, "LocaleFlags", LocaleFlags);
1687
+ __publicField$2(CASCClient, "ContentFlags", ContentFlags);
1688
+ __publicField$2(CASCClient, "LogLevel", LogLevel);
1635
1689
 
1636
- var __defProp = Object.defineProperty;
1637
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1638
- var __publicField = (obj, key, value) => {
1639
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1690
+ var __defProp$1 = Object.defineProperty;
1691
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1692
+ var __publicField$1 = (obj, key, value) => {
1693
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
1640
1694
  return value;
1641
1695
  };
1642
1696
  const PATTERN_COLUMN = /^(int|float|locstring|string)(<[^:]+::[^>]+>)?\s([^\s]+)/;
@@ -1670,12 +1724,21 @@ const castBigInt64 = (value, srcSigned, dstSigned) => {
1670
1724
  }
1671
1725
  return dstSigned ? castBuffer.readBigInt64LE(0) : castBuffer.readBigUInt64LE(0);
1672
1726
  };
1727
+ const getCastBuffer = (value, srcSize, dstSize) => {
1728
+ const castBuffer = Buffer.alloc(dstSize);
1729
+ let remain = value;
1730
+ for (let i = 0; i < srcSize && remain > 0n; i += 1, remain >>= 8n) {
1731
+ const byte = Number(BigInt.asUintN(8, remain));
1732
+ castBuffer.writeUInt8(byte, i);
1733
+ }
1734
+ return castBuffer;
1735
+ };
1673
1736
  class DBDParser {
1674
1737
  constructor(wdc) {
1675
- __publicField(this, "wdc");
1676
- __publicField(this, "definitions", /* @__PURE__ */ new Map());
1677
- __publicField(this, "columns", []);
1678
- __publicField(this, "cache", /* @__PURE__ */ new Map());
1738
+ __publicField$1(this, "wdc");
1739
+ __publicField$1(this, "definitions", /* @__PURE__ */ new Map());
1740
+ __publicField$1(this, "columns", []);
1741
+ __publicField$1(this, "cache", /* @__PURE__ */ new Map());
1679
1742
  this.wdc = wdc;
1680
1743
  }
1681
1744
  async init() {
@@ -1797,9 +1860,12 @@ class DBDParser {
1797
1860
  }
1798
1861
  } else if (column.type === "float") {
1799
1862
  if (column.arraySize) {
1800
- assert(typeof cell.data === "bigint", `Invalid data type for float array column ${column.name}`);
1863
+ const castBuffer = getCastBuffer(
1864
+ typeof cell.data === "number" ? BigInt(cell.data) : cell.data,
1865
+ srcSize,
1866
+ 4 * column.arraySize
1867
+ );
1801
1868
  const values = [];
1802
- const castBuffer = Buffer.from(cell.data.toString(16).padStart(8 * column.arraySize, "0"), "hex");
1803
1869
  for (let i = 0; i < column.arraySize; i += 1) {
1804
1870
  const value = castBuffer.readFloatLE(i * 4);
1805
1871
  values.push(Math.round(value * 100) / 100);
@@ -1809,25 +1875,49 @@ class DBDParser {
1809
1875
  assert(typeof cell.data === "number", `Invalid data type for float column ${column.name}`);
1810
1876
  data[column.name] = castFloat(cell.data, srcSize, srcSigned);
1811
1877
  }
1812
- } else if (typeof cell.data === "number") {
1813
- data[column.name] = castIntegerBySize(
1814
- cell.data,
1815
- srcSize,
1816
- srcSigned,
1817
- dstSize ?? srcSize,
1818
- column.isSigned
1819
- );
1820
- } else {
1821
- assert(!column.size || column.size === 64, `Unexpected size ${column.size?.toString() ?? ""} for column ${column.name}`);
1822
- if (srcSigned !== column.isSigned) {
1823
- data[column.name] = castBigInt64(
1878
+ } else if (column.type === "int") {
1879
+ if (column.arraySize) {
1880
+ assert(dstSize, `Missing size for int array column ${column.name}`);
1881
+ const castBuffer = getCastBuffer(
1882
+ typeof cell.data === "number" ? BigInt(cell.data) : cell.data,
1883
+ srcSize,
1884
+ 4 * column.arraySize
1885
+ );
1886
+ const values = [];
1887
+ if (column.isSigned) {
1888
+ for (let i = 0; i < column.arraySize; i += 1) {
1889
+ const value = castBuffer.readIntLE(i * dstSize, dstSize);
1890
+ values.push(value);
1891
+ }
1892
+ } else {
1893
+ for (let i = 0; i < column.arraySize; i += 1) {
1894
+ const value = castBuffer.readUIntLE(i * dstSize, dstSize);
1895
+ values.push(value);
1896
+ }
1897
+ }
1898
+ data[column.name] = values;
1899
+ } else if (typeof cell.data === "number") {
1900
+ data[column.name] = castIntegerBySize(
1824
1901
  cell.data,
1902
+ srcSize,
1825
1903
  srcSigned,
1904
+ dstSize ?? srcSize,
1826
1905
  column.isSigned
1827
1906
  );
1828
1907
  } else {
1829
- data[column.name] = cell.data;
1908
+ assert(!column.size || column.size === 64, `Unexpected size ${column.size?.toString() ?? ""} for column ${column.name}`);
1909
+ if (srcSigned !== column.isSigned) {
1910
+ data[column.name] = castBigInt64(
1911
+ cell.data,
1912
+ srcSigned,
1913
+ column.isSigned
1914
+ );
1915
+ } else {
1916
+ data[column.name] = cell.data;
1917
+ }
1830
1918
  }
1919
+ } else {
1920
+ throw new Error(`Unsupported column type ${column.type} for column ${column.name}`);
1831
1921
  }
1832
1922
  fieldIndex += 1;
1833
1923
  } else if (column.isRelation) {
@@ -1843,6 +1933,9 @@ class DBDParser {
1843
1933
  if (column.isID) {
1844
1934
  data[column.name] = id;
1845
1935
  if (column.isInline) {
1936
+ const currField = this.wdc.fields[fieldIndex];
1937
+ const size = Math.ceil((column.size ?? 32 - currField.size) / 8);
1938
+ offset += size;
1846
1939
  fieldIndex += 1;
1847
1940
  }
1848
1941
  } else if (column.isInline) {
@@ -1864,7 +1957,7 @@ class DBDParser {
1864
1957
  const size = Math.ceil((column.size ?? 32 - currField.size) / 8);
1865
1958
  let count;
1866
1959
  if (fieldIndex + 1 < this.wdc.fields.length) {
1867
- count = (nextField.position - currField.position) / size;
1960
+ count = Math.max((nextField.position - currField.position) / size, 1);
1868
1961
  } else {
1869
1962
  count = column.arraySize ? (buffer.byteLength - offset) / size : 1;
1870
1963
  }
@@ -1898,4 +1991,55 @@ class DBDParser {
1898
1991
  }
1899
1992
  }
1900
1993
 
1901
- export { CASCClient, DBDParser, WDCReader };
1994
+ var __defProp = Object.defineProperty;
1995
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1996
+ var __publicField = (obj, key, value) => {
1997
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1998
+ return value;
1999
+ };
2000
+ const ADB_MAGIC = 1481004104;
2001
+ class ADBReader {
2002
+ constructor(buffer) {
2003
+ __publicField(this, "build");
2004
+ __publicField(this, "entries", []);
2005
+ __publicField(this, "tableEntries", /* @__PURE__ */ new Map());
2006
+ const magic = buffer.readUInt32BE(0);
2007
+ assert(magic === ADB_MAGIC, `[ADB]: Invalid magic: ${magic.toString(16).padStart(8, "0")}`);
2008
+ const version = buffer.readUInt32LE(4);
2009
+ assert(version === 9, `[ADB]: Invalid version: ${version.toString()}`);
2010
+ const build = buffer.readUInt32LE(8);
2011
+ this.build = build;
2012
+ let pointer = 44;
2013
+ while (pointer < buffer.byteLength) {
2014
+ const offset = pointer;
2015
+ const entryMagic = buffer.readUInt32BE(offset);
2016
+ assert(entryMagic === ADB_MAGIC, `[ADB]: Invalid entry magic: ${magic.toString(16).padStart(8, "0")}`);
2017
+ const regionID = buffer.readInt32LE(offset + 4);
2018
+ const pushID = buffer.readInt32LE(offset + 8);
2019
+ const uniqueID = buffer.readUInt32LE(offset + 12);
2020
+ const tableHash = buffer.readUInt32LE(offset + 16);
2021
+ const recordID = buffer.readUInt32LE(offset + 20);
2022
+ const dataSize = buffer.readUInt32LE(offset + 24);
2023
+ const recordState = buffer.readUInt32LE(offset + 28);
2024
+ const data = buffer.subarray(offset + 32, offset + 32 + dataSize);
2025
+ const entry = {
2026
+ regionID,
2027
+ pushID,
2028
+ uniqueID,
2029
+ tableHash,
2030
+ recordID,
2031
+ dataSize,
2032
+ recordState,
2033
+ data
2034
+ };
2035
+ this.entries.push(entry);
2036
+ if (!this.tableEntries.has(tableHash)) {
2037
+ this.tableEntries.set(tableHash, []);
2038
+ }
2039
+ this.tableEntries.get(tableHash)?.push(entry);
2040
+ pointer += 32 + dataSize;
2041
+ }
2042
+ }
2043
+ }
2044
+
2045
+ export { ADBReader, CASCClient, DBDParser, WDCReader };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhyster/wow-casc-dbc",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "Fetch World of Warcraft data files from CASC and parse DBC/DB2 files.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -40,20 +40,21 @@
40
40
  "@types/async": "^3.2.24",
41
41
  "@types/cli-progress": "^3.11.5",
42
42
  "@types/jest": "^29.5.12",
43
- "@types/node": "^20.12.7",
44
- "@typescript-eslint/eslint-plugin": "^7.7.1",
45
- "@typescript-eslint/parser": "^7.7.1",
43
+ "@types/node": "^20.13.0",
44
+ "@typescript-eslint/eslint-plugin": "^7.11.0",
45
+ "@typescript-eslint/parser": "^7.11.0",
46
46
  "eslint": "^8.57.0",
47
47
  "eslint-config-airbnb-base": "^15.0.0",
48
48
  "eslint-config-airbnb-typescript": "^18.0.0",
49
49
  "jest": "^29.7.0",
50
- "ts-jest": "^29.1.2",
51
- "ts-node": "^10.9.2",
50
+ "ts-jest": "^29.1.4",
51
+ "tsx": "^4.11.0",
52
52
  "typescript": "^5.4.5",
53
53
  "unbuild": "^2.0.0"
54
54
  },
55
55
  "dependencies": {
56
56
  "async": "^3.2.5",
57
57
  "cli-progress": "^3.12.0"
58
- }
58
+ },
59
+ "packageManager": "pnpm@9.1.4+sha512.9df9cf27c91715646c7d675d1c9c8e41f6fce88246f1318c1aa6a1ed1aeb3c4f032fcdf4ba63cc69c4fe6d634279176b5358727d8f2cc1e65b65f43ce2f8bfb0"
59
60
  }