mirror of
https://github.com/d07RiV/diabloweb.git
synced 2026-06-03 21:41:38 +00:00
230 lines
6.2 KiB
JavaScript
230 lines
6.2 KiB
JavaScript
import { explode } from './explode';
|
|
|
|
function pkzip_decompress(data, out_size) {
|
|
if (data.length === out_size) {
|
|
return data;
|
|
}
|
|
|
|
const output = new Uint8Array(out_size);
|
|
let in_pos = 0;
|
|
let out_pos = 0;
|
|
function read_buf(dst) {
|
|
const count = Math.min(data.length - in_pos, dst.length);
|
|
dst.set(data.subarray(in_pos, count));
|
|
in_pos += count;
|
|
return count;
|
|
}
|
|
function write_buf(src) {
|
|
if (out_pos + src.length > out_size) {
|
|
throw Error('decompress buffer overflow');
|
|
}
|
|
output.set(src, out_pos);
|
|
out_pos += src.length;
|
|
}
|
|
|
|
if (explode(read_buf, write_buf) || out_pos !== out_size) {
|
|
return null;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
const hashtable = (function() {
|
|
const hashtable = new Uint32Array(1280);
|
|
let seed = 0x00100001;
|
|
for (let i = 0; i < 256; i++) {
|
|
for (let j = i; j < 1280; j += 256) {
|
|
seed = (seed * 125 + 3) % 0x2AAAAB;
|
|
const a = (seed & 0xFFFF) << 16;
|
|
seed = (seed * 125 + 3) % 0x2AAAAB;
|
|
const b = (seed & 0xFFFF);
|
|
hashtable[j] = a | b;
|
|
}
|
|
}
|
|
return hashtable;
|
|
})();
|
|
function decrypt(u32, key) {
|
|
let seed = 0xEEEEEEEE;
|
|
for (let i = 0; i < u32.length; ++i) {
|
|
seed += hashtable[0x400 + (key & 0xFF)];
|
|
u32[i] ^= seed + key;
|
|
seed = (u32[i] + seed * 33 + 3) | 0;
|
|
key = ((~key << 0x15) + 0x11111111) | (key >>> 0x0B);
|
|
}
|
|
}
|
|
function decrypt8(u8, key) {
|
|
decrypt(new Uint32Array(u8.buffer, u8.byteOffset, u8.length >> 2), key);
|
|
}
|
|
function hash(name, type) {
|
|
let seed1 = 0x7FED7FED;
|
|
let seed2 = 0xEEEEEEEE;
|
|
for (let i = 0; i < name.length; ++i) {
|
|
let ch = name.charCodeAt(i);
|
|
if (ch >= 0x61 && ch <= 0x7A) {
|
|
ch -= 0x20;
|
|
}
|
|
if (ch === 0x2F) {
|
|
ch = 0x5C;
|
|
}
|
|
seed1 = hashtable[type * 256 + ch] ^ (seed1 + seed2);
|
|
seed2 = (ch + seed1 + seed2 * 33 + 3) | 0;
|
|
}
|
|
return seed1 >>> 0;
|
|
}
|
|
|
|
function path_name(name) {
|
|
const pos = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\'));
|
|
return name.substring(pos + 1);
|
|
}
|
|
|
|
const Flags = {
|
|
CompressPkWare: 0x00000100,
|
|
CompressMulti: 0x00000200,
|
|
Compressed: 0x0000FF00,
|
|
Encrypted: 0x00010000,
|
|
FixSeed: 0x00020000,
|
|
PatchFile: 0x00100000,
|
|
SingleUnit: 0x01000000,
|
|
DummyFile: 0x02000000,
|
|
SectorCrc: 0x04000000,
|
|
Exists: 0x80000000,
|
|
};
|
|
|
|
class MpqReader {
|
|
constructor(buffer) {
|
|
this.buffer = buffer;
|
|
this.u8 = new Uint8Array(buffer);
|
|
this.u32 = new Uint32Array(buffer);
|
|
|
|
this.readHeader();
|
|
}
|
|
|
|
readHeader() {
|
|
const {u8, u32} = this;
|
|
if (u32[0] !== 0x1A51504D) {
|
|
throw Error('invalid MPQ header');
|
|
}
|
|
const sizeId = u8[14] + (u8[15] << 8);
|
|
const hashOffset = u32[4];
|
|
const blockOffset = u32[5];
|
|
const hashCount = u32[6];
|
|
const blockCount = u32[7];
|
|
this.hashTable = this.readTable(hashOffset, hashCount, "(hash table)");
|
|
this.blockTable = this.readTable(blockOffset, blockCount, "(block table)");
|
|
this.blockSize = 1 << (9 + sizeId);
|
|
}
|
|
|
|
readTable(offset, count, key) {
|
|
const buffer = new Uint32Array(this.buffer.slice(offset, offset + count * 16));
|
|
decrypt(buffer, hash(key, 3));
|
|
return buffer;
|
|
}
|
|
|
|
fileIndex(name) {
|
|
const {hashTable} = this;
|
|
const length = hashTable.length >> 2;
|
|
const index = hash(name, 0) % length;
|
|
const keyA = hash(name, 1), keyB = hash(name, 2);
|
|
for (let i = index, count = 0; hashTable[i * 4 + 3] !== 0xFFFFFFFF && count < length; i = (i + 1) % length, ++count) {
|
|
if (hashTable[i * 4] === keyA && hashTable[i * 4 + 1] === keyB && hashTable[i * 4 + 3] !== 0xFFFFFFFE) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
read(name) {
|
|
const index = this.fileIndex(name);
|
|
if (index == null) {
|
|
return;
|
|
}
|
|
const block = this.hashTable[index * 4 + 3];
|
|
const filePos = this.blockTable[block * 4];
|
|
let cmpSize = this.blockTable[block * 4 + 1];
|
|
const fileSize = this.blockTable[block * 4 + 2];
|
|
const flags = this.blockTable[block * 4 + 3];
|
|
|
|
if (flags & Flags.PatchFile) {
|
|
return;
|
|
}
|
|
if (!(flags & Flags.Compressed)) {
|
|
cmpSize = fileSize;
|
|
}
|
|
|
|
let key = hash(path_name(name), 3);
|
|
if (flags & Flags.FixSeed) {
|
|
key = (key + filePos) ^ fileSize;
|
|
}
|
|
|
|
if (flags & Flags.SingleUnit) {
|
|
const raw = new Uint8Array(this.buffer, filePos, cmpSize);
|
|
if (raw.length !== cmpSize) {
|
|
return;
|
|
}
|
|
if (flags & Flags.Encrypted) {
|
|
decrypt8(raw, key);
|
|
}
|
|
if (flags & Flags.CompressMulti) {
|
|
return;
|
|
} else if (flags & Flags.CompressPkWare) {
|
|
return pkzip_decompress(raw, fileSize);
|
|
}
|
|
return raw;
|
|
} else if (!(flags & Flags.Compressed)) {
|
|
const raw = Uint8Array(this.buffer, filePos, fileSize);
|
|
if (raw.length !== fileSize) {
|
|
return;
|
|
}
|
|
if (flags & Flags.Encrypted) {
|
|
for (let i = 0; i < fileSize; i += this.blockSize) {
|
|
decrypt8(raw.subarray(i, Math.min(fileSize, i + this.blockSize)), key + i / this.blockSize);
|
|
}
|
|
}
|
|
return raw;
|
|
} else {
|
|
const numBlocks = Math.floor((fileSize + this.blockSize - 1) / this.blockSize);
|
|
const tableSize = numBlocks + 1 + ((flags & Flags.SectorCrc) ? 1 : 0);
|
|
const blocks = new Uint32Array(this.buffer, filePos, tableSize);
|
|
if (blocks.length !== tableSize) {
|
|
return;
|
|
}
|
|
if (flags & Flags.Encrypted) {
|
|
decrypt(blocks, key - 1);
|
|
}
|
|
const output = new Uint8Array(fileSize);
|
|
for (let i = 0; i < numBlocks; ++i) {
|
|
const oPos = i * this.blockSize;
|
|
const cSize = blocks[i + 1] - blocks[i];
|
|
const uSize = Math.min(this.blockSize, fileSize - oPos);
|
|
let tmp = new Uint8Array(this.buffer, filePos + blocks[i], cSize);
|
|
if (tmp.length !== cSize) {
|
|
return;
|
|
}
|
|
if (flags & Flags.Encrypted) {
|
|
decrypt8(tmp, key + i);
|
|
}
|
|
if (flags & Flags.CompressMulti) {
|
|
return;
|
|
} else if (flags & Flags.CompressPkWare) {
|
|
tmp = pkzip_decompress(tmp, uSize);
|
|
}
|
|
if (!tmp || tmp.length !== uSize) {
|
|
return;
|
|
}
|
|
output.set(tmp, oPos);
|
|
}
|
|
return output;
|
|
}
|
|
}
|
|
}
|
|
|
|
export default function getPlayerName(data) {
|
|
debugger;
|
|
try {
|
|
const reader = new MpqReader(data);
|
|
const hero = reader.read("hero");
|
|
return '';
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|