mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
165 lines (133 loc) • 3.99 kB
JavaScript
import {HASH_FILE_KEY, FILE_OFFSET_ADJUSTED_KEY} from './constants';
// Global variables for this module.
let bytes = new Uint8Array(4);
let long = new Uint32Array(bytes.buffer);
/**
* MPQ crypto.
*/
export default class MpqCrypto {
/**
*
*/
constructor() {
let cryptTable = new Uint32Array(0x500);
let seed = 0x00100001;
let temp1;
let temp2;
for (let index1 = 0; index1 < 0x100; index1++) {
for (let index2 = index1, i = 0; i < 5; i += 1, index2 += 0x100) {
seed = (seed * 125 + 3) % 0x2AAAAB;
temp1 = (seed & 0xFFFF) << 0x10;
seed = (seed * 125 + 3) % 0x2AAAAB;
temp2 = (seed & 0xFFFF);
cryptTable[index2] = temp1 | temp2;
}
}
/** @member {Uint32Array} */
this.cryptTable = cryptTable;
}
/**
* @param {string} name
* @param {number} key
* @return {number}
*/
hash(name, key) {
let cryptTable = this.cryptTable;
let seed1 = 0x7FED7FED;
let seed2 = 0xEEEEEEEE;
name = name.toUpperCase();
for (let i = 0; i < name.length; i++) {
let ch = name.charCodeAt(i);
seed1 = cryptTable[(key << 8) + ch] ^ (seed1 + seed2);
seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3;
}
// Convert the seed to an unsigned integer
return seed1 >>> 0;
}
/**
* @param {ArrayBuffer|TypedArray} data
* @param {number} key
* @return {ArrayBuffer|TypedArray}
*/
decryptBlock(data, key) {
let cryptTable = this.cryptTable;
let seed = 0xEEEEEEEE;
let view;
if (data instanceof ArrayBuffer) {
view = new Uint8Array(data);
} else {
view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
}
for (let i = 0, l = data.byteLength >>> 2; i < l; i++) {
// Update the seed.
seed += cryptTable[0x400 + (key & 0xFF)];
// Get 4 encrypted bytes.
bytes[0] = view[i * 4];
bytes[1] = view[i * 4 + 1];
bytes[2] = view[i * 4 + 2];
bytes[3] = view[i * 4 + 3];
// Decrypted 32bit integer.
long[0] ^= (key + seed);
// Update the seed.
key = ((~key << 0x15) + 0x11111111) | (key >>> 0x0B);
seed = long[0] + seed + (seed << 5) + 3;
// Set 4 decryped bytes.
view[i * 4] = bytes[0];
view[i * 4 + 1] = bytes[1];
view[i * 4 + 2] = bytes[2];
view[i * 4 + 3] = bytes[3];
}
return data;
}
/**
* @param {ArrayBuffer|TypedArray} data
* @param {number} key
* @return {ArrayBuffer|TypedArray}
*/
encryptBlock(data, key) {
let cryptTable = this.cryptTable;
let seed = 0xEEEEEEEE;
let view;
if (data instanceof ArrayBuffer) {
view = new Uint8Array(data);
} else {
view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
}
for (let i = 0, l = data.byteLength >>> 2; i < l; i++) {
// Update the seed.
seed += cryptTable[0x400 + (key & 0xFF)];
// Get 4 decrypted bytes.
bytes[0] = view[i * 4];
bytes[1] = view[i * 4 + 1];
bytes[2] = view[i * 4 + 2];
bytes[3] = view[i * 4 + 3];
// Decrypted 32bit integer.
let decrypted = long[0];
// Encrypted 32bit integer.
long[0] ^= (key + seed);
// Update the seed.
key = ((~key << 0x15) + 0x11111111) | (key >>> 0x0B);
seed = decrypted + seed + (seed << 5) + 3;
// Set 4 encrypted bytes.
view[i * 4] = bytes[0];
view[i * 4 + 1] = bytes[1];
view[i * 4 + 2] = bytes[2];
view[i * 4 + 3] = bytes[3];
}
return data;
}
/**
* @param {string} name
* @param {MpqBlock} block
* @return {number}
*/
computeFileKey(name, block) {
let sepIndex = name.lastIndexOf('\\');
let pathlessName = name.substring(sepIndex + 1);
let encryptionKey = this.hash(pathlessName, HASH_FILE_KEY);
if (block.flags & FILE_OFFSET_ADJUSTED_KEY) {
encryptionKey = (encryptionKey + block.offset) ^ block.normalSize;
}
return encryptionKey;
}
}