@rhyster/wow-casc-dbc 2.4.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +69 -30
- package/dist/index.d.cts +1 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +69 -30
- package/package.json +8 -7
package/dist/index.cjs
CHANGED
|
@@ -485,19 +485,22 @@ class BLTEReader {
|
|
|
485
485
|
offset += ivLength;
|
|
486
486
|
const encryptType = buffer.readUInt8(offset);
|
|
487
487
|
offset += 1;
|
|
488
|
-
assert__default(encryptType === ENC_TYPE_SALSA20, `[BLTE]: Invalid encrypt type: ${encryptType.toString(16).padStart(2, "0")}`);
|
|
488
|
+
assert__default(encryptType === ENC_TYPE_SALSA20, `[BLTE]: Invalid encrypt type: ${encryptType.toString(16).padStart(2, "0")} at block ${index.toString()}`);
|
|
489
489
|
const keyName = [...keyNameBE.matchAll(/.{2}/g)].map((v) => v[0]).reverse().join("").toLowerCase();
|
|
490
490
|
const key = this.keys.get(keyName);
|
|
491
491
|
if (!key) {
|
|
492
492
|
if (allowMissingKey) {
|
|
493
493
|
return keyName;
|
|
494
494
|
}
|
|
495
|
-
throw new Error(`[BLTE]: Missing key: ${keyName}`);
|
|
495
|
+
throw new Error(`[BLTE]: Missing key: ${keyName} at block ${index.toString()}`);
|
|
496
496
|
}
|
|
497
497
|
const iv = new Uint8Array(8);
|
|
498
498
|
for (let i = 0; i < 8; i += 1) {
|
|
499
|
-
|
|
500
|
-
|
|
499
|
+
if (i < ivLength) {
|
|
500
|
+
iv[i] = ivBuffer.readUInt8(i) ^ index >>> 8 * i & 255;
|
|
501
|
+
} else {
|
|
502
|
+
iv[i] = 0;
|
|
503
|
+
}
|
|
501
504
|
}
|
|
502
505
|
const handler = new Salsa20(key, iv);
|
|
503
506
|
const decrypted = handler.process(buffer.subarray(offset));
|
|
@@ -507,13 +510,13 @@ class BLTEReader {
|
|
|
507
510
|
return this.processBlock(Buffer.from(decrypted.buffer), index, false);
|
|
508
511
|
}
|
|
509
512
|
case 70:
|
|
510
|
-
throw new Error(
|
|
513
|
+
throw new Error(`[BLTE]: Frame (Recursive) block not supported at block ${index.toString()}`);
|
|
511
514
|
case 78:
|
|
512
515
|
return buffer.subarray(1);
|
|
513
516
|
case 90:
|
|
514
517
|
return zlib__default.inflateSync(buffer.subarray(1));
|
|
515
518
|
default:
|
|
516
|
-
throw new Error(`[BLTE]: Invalid block flag: ${flag.toString(16).padStart(2, "0")}`);
|
|
519
|
+
throw new Error(`[BLTE]: Invalid block flag: ${flag.toString(16).padStart(2, "0")} at block ${index.toString()}`);
|
|
517
520
|
}
|
|
518
521
|
}
|
|
519
522
|
processBytes(allowMissingKey = false, size = Infinity) {
|
|
@@ -912,14 +915,10 @@ const readBitpackedValue = (buffer, fieldOffsetBits, fieldSizeBits, signed = fal
|
|
|
912
915
|
signed ? BigInt.asIntN(fieldSizeBits, BigInt(rawValue >>> bitOffset)) : BigInt.asUintN(fieldSizeBits, BigInt(rawValue >>> bitOffset))
|
|
913
916
|
);
|
|
914
917
|
}
|
|
915
|
-
let remain = sizeBytes;
|
|
916
918
|
let value = 0n;
|
|
917
|
-
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
const rawValue = buffer.readUIntLE(offset, byteLength);
|
|
921
|
-
value = value << BigInt(byteLength * 8) | BigInt(rawValue);
|
|
922
|
-
remain -= byteLength;
|
|
919
|
+
for (let i = sizeBytes - 1; i >= 0; i -= 1) {
|
|
920
|
+
const byte = buffer.readUInt8(offsetBytes + i);
|
|
921
|
+
value = value << 8n | BigInt(byte);
|
|
923
922
|
}
|
|
924
923
|
return signed ? BigInt.asIntN(fieldSizeBits, value >> BigInt(bitOffset)) : BigInt.asUintN(fieldSizeBits, value >> BigInt(bitOffset));
|
|
925
924
|
};
|
|
@@ -1678,9 +1677,12 @@ class CASCClient {
|
|
|
1678
1677
|
const reader = new BLTEReader(blte, eKey, this.keys);
|
|
1679
1678
|
if (!allowMissingKey) {
|
|
1680
1679
|
reader.processBytes(allowMissingKey);
|
|
1680
|
+
const hash = crypto__default.createHash("md5").update(reader.buffer).digest("hex");
|
|
1681
|
+
assert__default(hash === cKey, `Invalid hash: expected ${cKey}, got ${hash}`);
|
|
1681
1682
|
return {
|
|
1682
1683
|
type: "full",
|
|
1683
|
-
buffer: reader.buffer
|
|
1684
|
+
buffer: reader.buffer,
|
|
1685
|
+
blocks: void 0
|
|
1684
1686
|
};
|
|
1685
1687
|
}
|
|
1686
1688
|
const blocks = reader.processBytes(allowMissingKey);
|
|
@@ -1689,7 +1691,8 @@ class CASCClient {
|
|
|
1689
1691
|
assert__default(hash === cKey, `Invalid hash: expected ${cKey}, got ${hash}`);
|
|
1690
1692
|
return {
|
|
1691
1693
|
type: "full",
|
|
1692
|
-
buffer: reader.buffer
|
|
1694
|
+
buffer: reader.buffer,
|
|
1695
|
+
blocks: void 0
|
|
1693
1696
|
};
|
|
1694
1697
|
}
|
|
1695
1698
|
return {
|
|
@@ -1740,6 +1743,15 @@ const castBigInt64 = (value, srcSigned, dstSigned) => {
|
|
|
1740
1743
|
}
|
|
1741
1744
|
return dstSigned ? castBuffer.readBigInt64LE(0) : castBuffer.readBigUInt64LE(0);
|
|
1742
1745
|
};
|
|
1746
|
+
const getCastBuffer = (value, srcSize, dstSize) => {
|
|
1747
|
+
const castBuffer = Buffer.alloc(dstSize);
|
|
1748
|
+
let remain = value;
|
|
1749
|
+
for (let i = 0; i < srcSize && remain > 0n; i += 1, remain >>= 8n) {
|
|
1750
|
+
const byte = Number(BigInt.asUintN(8, remain));
|
|
1751
|
+
castBuffer.writeUInt8(byte, i);
|
|
1752
|
+
}
|
|
1753
|
+
return castBuffer;
|
|
1754
|
+
};
|
|
1743
1755
|
class DBDParser {
|
|
1744
1756
|
constructor(wdc) {
|
|
1745
1757
|
__publicField$1(this, "wdc");
|
|
@@ -1867,9 +1879,12 @@ class DBDParser {
|
|
|
1867
1879
|
}
|
|
1868
1880
|
} else if (column.type === "float") {
|
|
1869
1881
|
if (column.arraySize) {
|
|
1870
|
-
|
|
1882
|
+
const castBuffer = getCastBuffer(
|
|
1883
|
+
typeof cell.data === "number" ? BigInt(cell.data) : cell.data,
|
|
1884
|
+
srcSize,
|
|
1885
|
+
4 * column.arraySize
|
|
1886
|
+
);
|
|
1871
1887
|
const values = [];
|
|
1872
|
-
const castBuffer = Buffer.from(cell.data.toString(16).padStart(8 * column.arraySize, "0"), "hex");
|
|
1873
1888
|
for (let i = 0; i < column.arraySize; i += 1) {
|
|
1874
1889
|
const value = castBuffer.readFloatLE(i * 4);
|
|
1875
1890
|
values.push(Math.round(value * 100) / 100);
|
|
@@ -1879,25 +1894,49 @@ class DBDParser {
|
|
|
1879
1894
|
assert__default(typeof cell.data === "number", `Invalid data type for float column ${column.name}`);
|
|
1880
1895
|
data[column.name] = castFloat(cell.data, srcSize, srcSigned);
|
|
1881
1896
|
}
|
|
1882
|
-
} else if (
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1897
|
+
} else if (column.type === "int") {
|
|
1898
|
+
if (column.arraySize) {
|
|
1899
|
+
assert__default(dstSize, `Missing size for int array column ${column.name}`);
|
|
1900
|
+
const castBuffer = getCastBuffer(
|
|
1901
|
+
typeof cell.data === "number" ? BigInt(cell.data) : cell.data,
|
|
1902
|
+
srcSize,
|
|
1903
|
+
4 * column.arraySize
|
|
1904
|
+
);
|
|
1905
|
+
const values = [];
|
|
1906
|
+
if (column.isSigned) {
|
|
1907
|
+
for (let i = 0; i < column.arraySize; i += 1) {
|
|
1908
|
+
const value = castBuffer.readIntLE(i * dstSize, dstSize);
|
|
1909
|
+
values.push(value);
|
|
1910
|
+
}
|
|
1911
|
+
} else {
|
|
1912
|
+
for (let i = 0; i < column.arraySize; i += 1) {
|
|
1913
|
+
const value = castBuffer.readUIntLE(i * dstSize, dstSize);
|
|
1914
|
+
values.push(value);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
data[column.name] = values;
|
|
1918
|
+
} else if (typeof cell.data === "number") {
|
|
1919
|
+
data[column.name] = castIntegerBySize(
|
|
1894
1920
|
cell.data,
|
|
1921
|
+
srcSize,
|
|
1895
1922
|
srcSigned,
|
|
1923
|
+
dstSize ?? srcSize,
|
|
1896
1924
|
column.isSigned
|
|
1897
1925
|
);
|
|
1898
1926
|
} else {
|
|
1899
|
-
|
|
1927
|
+
assert__default(!column.size || column.size === 64, `Unexpected size ${column.size?.toString() ?? ""} for column ${column.name}`);
|
|
1928
|
+
if (srcSigned !== column.isSigned) {
|
|
1929
|
+
data[column.name] = castBigInt64(
|
|
1930
|
+
cell.data,
|
|
1931
|
+
srcSigned,
|
|
1932
|
+
column.isSigned
|
|
1933
|
+
);
|
|
1934
|
+
} else {
|
|
1935
|
+
data[column.name] = cell.data;
|
|
1936
|
+
}
|
|
1900
1937
|
}
|
|
1938
|
+
} else {
|
|
1939
|
+
throw new Error(`Unsupported column type ${column.type} for column ${column.name}`);
|
|
1901
1940
|
}
|
|
1902
1941
|
fieldIndex += 1;
|
|
1903
1942
|
} else if (column.isRelation) {
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -473,19 +473,22 @@ class BLTEReader {
|
|
|
473
473
|
offset += ivLength;
|
|
474
474
|
const encryptType = buffer.readUInt8(offset);
|
|
475
475
|
offset += 1;
|
|
476
|
-
assert(encryptType === ENC_TYPE_SALSA20, `[BLTE]: Invalid encrypt type: ${encryptType.toString(16).padStart(2, "0")}`);
|
|
476
|
+
assert(encryptType === ENC_TYPE_SALSA20, `[BLTE]: Invalid encrypt type: ${encryptType.toString(16).padStart(2, "0")} at block ${index.toString()}`);
|
|
477
477
|
const keyName = [...keyNameBE.matchAll(/.{2}/g)].map((v) => v[0]).reverse().join("").toLowerCase();
|
|
478
478
|
const key = this.keys.get(keyName);
|
|
479
479
|
if (!key) {
|
|
480
480
|
if (allowMissingKey) {
|
|
481
481
|
return keyName;
|
|
482
482
|
}
|
|
483
|
-
throw new Error(`[BLTE]: Missing key: ${keyName}`);
|
|
483
|
+
throw new Error(`[BLTE]: Missing key: ${keyName} at block ${index.toString()}`);
|
|
484
484
|
}
|
|
485
485
|
const iv = new Uint8Array(8);
|
|
486
486
|
for (let i = 0; i < 8; i += 1) {
|
|
487
|
-
|
|
488
|
-
|
|
487
|
+
if (i < ivLength) {
|
|
488
|
+
iv[i] = ivBuffer.readUInt8(i) ^ index >>> 8 * i & 255;
|
|
489
|
+
} else {
|
|
490
|
+
iv[i] = 0;
|
|
491
|
+
}
|
|
489
492
|
}
|
|
490
493
|
const handler = new Salsa20(key, iv);
|
|
491
494
|
const decrypted = handler.process(buffer.subarray(offset));
|
|
@@ -495,13 +498,13 @@ class BLTEReader {
|
|
|
495
498
|
return this.processBlock(Buffer.from(decrypted.buffer), index, false);
|
|
496
499
|
}
|
|
497
500
|
case 70:
|
|
498
|
-
throw new Error(
|
|
501
|
+
throw new Error(`[BLTE]: Frame (Recursive) block not supported at block ${index.toString()}`);
|
|
499
502
|
case 78:
|
|
500
503
|
return buffer.subarray(1);
|
|
501
504
|
case 90:
|
|
502
505
|
return zlib.inflateSync(buffer.subarray(1));
|
|
503
506
|
default:
|
|
504
|
-
throw new Error(`[BLTE]: Invalid block flag: ${flag.toString(16).padStart(2, "0")}`);
|
|
507
|
+
throw new Error(`[BLTE]: Invalid block flag: ${flag.toString(16).padStart(2, "0")} at block ${index.toString()}`);
|
|
505
508
|
}
|
|
506
509
|
}
|
|
507
510
|
processBytes(allowMissingKey = false, size = Infinity) {
|
|
@@ -900,14 +903,10 @@ const readBitpackedValue = (buffer, fieldOffsetBits, fieldSizeBits, signed = fal
|
|
|
900
903
|
signed ? BigInt.asIntN(fieldSizeBits, BigInt(rawValue >>> bitOffset)) : BigInt.asUintN(fieldSizeBits, BigInt(rawValue >>> bitOffset))
|
|
901
904
|
);
|
|
902
905
|
}
|
|
903
|
-
let remain = sizeBytes;
|
|
904
906
|
let value = 0n;
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
const rawValue = buffer.readUIntLE(offset, byteLength);
|
|
909
|
-
value = value << BigInt(byteLength * 8) | BigInt(rawValue);
|
|
910
|
-
remain -= byteLength;
|
|
907
|
+
for (let i = sizeBytes - 1; i >= 0; i -= 1) {
|
|
908
|
+
const byte = buffer.readUInt8(offsetBytes + i);
|
|
909
|
+
value = value << 8n | BigInt(byte);
|
|
911
910
|
}
|
|
912
911
|
return signed ? BigInt.asIntN(fieldSizeBits, value >> BigInt(bitOffset)) : BigInt.asUintN(fieldSizeBits, value >> BigInt(bitOffset));
|
|
913
912
|
};
|
|
@@ -1666,9 +1665,12 @@ class CASCClient {
|
|
|
1666
1665
|
const reader = new BLTEReader(blte, eKey, this.keys);
|
|
1667
1666
|
if (!allowMissingKey) {
|
|
1668
1667
|
reader.processBytes(allowMissingKey);
|
|
1668
|
+
const hash = crypto.createHash("md5").update(reader.buffer).digest("hex");
|
|
1669
|
+
assert(hash === cKey, `Invalid hash: expected ${cKey}, got ${hash}`);
|
|
1669
1670
|
return {
|
|
1670
1671
|
type: "full",
|
|
1671
|
-
buffer: reader.buffer
|
|
1672
|
+
buffer: reader.buffer,
|
|
1673
|
+
blocks: void 0
|
|
1672
1674
|
};
|
|
1673
1675
|
}
|
|
1674
1676
|
const blocks = reader.processBytes(allowMissingKey);
|
|
@@ -1677,7 +1679,8 @@ class CASCClient {
|
|
|
1677
1679
|
assert(hash === cKey, `Invalid hash: expected ${cKey}, got ${hash}`);
|
|
1678
1680
|
return {
|
|
1679
1681
|
type: "full",
|
|
1680
|
-
buffer: reader.buffer
|
|
1682
|
+
buffer: reader.buffer,
|
|
1683
|
+
blocks: void 0
|
|
1681
1684
|
};
|
|
1682
1685
|
}
|
|
1683
1686
|
return {
|
|
@@ -1728,6 +1731,15 @@ const castBigInt64 = (value, srcSigned, dstSigned) => {
|
|
|
1728
1731
|
}
|
|
1729
1732
|
return dstSigned ? castBuffer.readBigInt64LE(0) : castBuffer.readBigUInt64LE(0);
|
|
1730
1733
|
};
|
|
1734
|
+
const getCastBuffer = (value, srcSize, dstSize) => {
|
|
1735
|
+
const castBuffer = Buffer.alloc(dstSize);
|
|
1736
|
+
let remain = value;
|
|
1737
|
+
for (let i = 0; i < srcSize && remain > 0n; i += 1, remain >>= 8n) {
|
|
1738
|
+
const byte = Number(BigInt.asUintN(8, remain));
|
|
1739
|
+
castBuffer.writeUInt8(byte, i);
|
|
1740
|
+
}
|
|
1741
|
+
return castBuffer;
|
|
1742
|
+
};
|
|
1731
1743
|
class DBDParser {
|
|
1732
1744
|
constructor(wdc) {
|
|
1733
1745
|
__publicField$1(this, "wdc");
|
|
@@ -1855,9 +1867,12 @@ class DBDParser {
|
|
|
1855
1867
|
}
|
|
1856
1868
|
} else if (column.type === "float") {
|
|
1857
1869
|
if (column.arraySize) {
|
|
1858
|
-
|
|
1870
|
+
const castBuffer = getCastBuffer(
|
|
1871
|
+
typeof cell.data === "number" ? BigInt(cell.data) : cell.data,
|
|
1872
|
+
srcSize,
|
|
1873
|
+
4 * column.arraySize
|
|
1874
|
+
);
|
|
1859
1875
|
const values = [];
|
|
1860
|
-
const castBuffer = Buffer.from(cell.data.toString(16).padStart(8 * column.arraySize, "0"), "hex");
|
|
1861
1876
|
for (let i = 0; i < column.arraySize; i += 1) {
|
|
1862
1877
|
const value = castBuffer.readFloatLE(i * 4);
|
|
1863
1878
|
values.push(Math.round(value * 100) / 100);
|
|
@@ -1867,25 +1882,49 @@ class DBDParser {
|
|
|
1867
1882
|
assert(typeof cell.data === "number", `Invalid data type for float column ${column.name}`);
|
|
1868
1883
|
data[column.name] = castFloat(cell.data, srcSize, srcSigned);
|
|
1869
1884
|
}
|
|
1870
|
-
} else if (
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1885
|
+
} else if (column.type === "int") {
|
|
1886
|
+
if (column.arraySize) {
|
|
1887
|
+
assert(dstSize, `Missing size for int array column ${column.name}`);
|
|
1888
|
+
const castBuffer = getCastBuffer(
|
|
1889
|
+
typeof cell.data === "number" ? BigInt(cell.data) : cell.data,
|
|
1890
|
+
srcSize,
|
|
1891
|
+
4 * column.arraySize
|
|
1892
|
+
);
|
|
1893
|
+
const values = [];
|
|
1894
|
+
if (column.isSigned) {
|
|
1895
|
+
for (let i = 0; i < column.arraySize; i += 1) {
|
|
1896
|
+
const value = castBuffer.readIntLE(i * dstSize, dstSize);
|
|
1897
|
+
values.push(value);
|
|
1898
|
+
}
|
|
1899
|
+
} else {
|
|
1900
|
+
for (let i = 0; i < column.arraySize; i += 1) {
|
|
1901
|
+
const value = castBuffer.readUIntLE(i * dstSize, dstSize);
|
|
1902
|
+
values.push(value);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
data[column.name] = values;
|
|
1906
|
+
} else if (typeof cell.data === "number") {
|
|
1907
|
+
data[column.name] = castIntegerBySize(
|
|
1882
1908
|
cell.data,
|
|
1909
|
+
srcSize,
|
|
1883
1910
|
srcSigned,
|
|
1911
|
+
dstSize ?? srcSize,
|
|
1884
1912
|
column.isSigned
|
|
1885
1913
|
);
|
|
1886
1914
|
} else {
|
|
1887
|
-
|
|
1915
|
+
assert(!column.size || column.size === 64, `Unexpected size ${column.size?.toString() ?? ""} for column ${column.name}`);
|
|
1916
|
+
if (srcSigned !== column.isSigned) {
|
|
1917
|
+
data[column.name] = castBigInt64(
|
|
1918
|
+
cell.data,
|
|
1919
|
+
srcSigned,
|
|
1920
|
+
column.isSigned
|
|
1921
|
+
);
|
|
1922
|
+
} else {
|
|
1923
|
+
data[column.name] = cell.data;
|
|
1924
|
+
}
|
|
1888
1925
|
}
|
|
1926
|
+
} else {
|
|
1927
|
+
throw new Error(`Unsupported column type ${column.type} for column ${column.name}`);
|
|
1889
1928
|
}
|
|
1890
1929
|
fieldIndex += 1;
|
|
1891
1930
|
} else if (column.isRelation) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhyster/wow-casc-dbc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
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.
|
|
44
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
45
|
-
"@typescript-eslint/parser": "^7.
|
|
43
|
+
"@types/node": "^20.14.2",
|
|
44
|
+
"@typescript-eslint/eslint-plugin": "^7.12.0",
|
|
45
|
+
"@typescript-eslint/parser": "^7.12.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.
|
|
51
|
-
"tsx": "^4.
|
|
50
|
+
"ts-jest": "^29.1.4",
|
|
51
|
+
"tsx": "^4.11.2",
|
|
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.2.0+sha512.98a80fd11c2e7096747762304106432b3ddc67dcf54b5a8c01c93f68a2cd5e05e6821849522a06fb76284d41a2660d5e334f2ee3bbf29183bf2e739b1dafa771"
|
|
59
60
|
}
|