mpq compress

This commit is contained in:
d07riv
2019-08-25 18:45:53 +03:00
parent 41f912ef50
commit 9f9a63fa00
11 changed files with 1200 additions and 1344 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "diabloweb",
"version": "1.0.36",
"version": "1.0.37",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "diabloweb",
"version": "1.0.36",
"version": "1.0.37",
"private": true,
"dependencies": {
"@babel/core": "7.4.3",

View File

@@ -11,6 +11,7 @@ import { mapStackTrace } from 'sourcemapped-stacktrace';
import create_fs from './fs';
import load_game from './api/loader';
import { SpawnSizes } from './api/load_spawn';
import CompressMpq from './mpqcmp';
import Peer from 'peerjs';
@@ -130,7 +131,9 @@ class App extends React.Component {
if (spawn && SpawnSizes.includes(spawn.byteLength)) {
this.setState({has_spawn: true});
}
this.updateSaves();
if ([...fs.files.keys()].filter(name => name.match(/\.sv$/i)).length) {
this.setState({save_names: true});
}
});
}
@@ -138,7 +141,11 @@ class App extends React.Component {
const file = getDropFile(e);
if (file) {
e.preventDefault();
this.start(file);
if (this.compressMpq) {
this.compressMpq.start(file);
} else {
this.start(file);
}
}
this.setState({dropping: 0});
}
@@ -223,12 +230,18 @@ class App extends React.Component {
this.saveName = name;
}
showSaves = () => {
if (this.state.save_names === true) {
this.updateSaves().then(() => this.setState({show_saves: !this.state.show_saves}));
} else {
this.setState({show_saves: !this.state.show_saves});
}
}
updateSaves() {
this.fs.then(fs => {
const saves = [];
return this.fs.then(fs => {
const saves = {};
[...fs.files.keys()].filter(name => name.match(/\.sv$/i)).forEach(name => {
saves.push(name);
console.log(name, getPlayerName(fs.files.get(name).buffer));
saves[name] = getPlayerName(fs.files.get(name).buffer, name);
});
this.setState({save_names: saves});
});
@@ -302,7 +315,7 @@ class App extends React.Component {
document.removeEventListener("dragleave", this.onDragLeave, true);
this.setState({dropping: 0});
const retail = !!(file && file.name.match(/^diabdat\.mpq$/i));
const retail = !!(file && !file.name.match(/^spawn\.mpq$/i));
if (process.env.NODE_ENV === 'production') {
ReactGA.event({
category: 'Game',
@@ -656,30 +669,78 @@ class App extends React.Component {
}
}
render() {
const {started, loading, error, progress, dropping, has_spawn, save_names, show_saves} = this.state;
if (show_saves && save_names) {
renderUi() {
const {started, loading, error, progress, has_spawn, save_names, show_saves, compress} = this.state;
if (show_saves && typeof save_names === "object") {
const plrClass = ["Warrior", "Rogue", "Sorcerer"];
return (
<div className={classNames("App", {touch: this.touchControls, started, dropping, keyboard: !!this.showKeyboard})} ref={this.setElement}>
<div className="BodyV">
<div className="start">
<ul className="saveList">
{save_names.map(name => <li key={name}>
{name}
<FontAwesomeIcon className="btnDownload" icon={faDownload} onClick={() => this.downloadSave(name)}/>
<FontAwesomeIcon className="btnRemove" icon={faTimes} onClick={() => this.removeSave(name)}/>
</li>)}
</ul>
<form>
<label htmlFor="loadFile" className="startButton">Upload Save</label>
<input accept=".sv" type="file" id="loadFile" style={{display: "none"}} onChange={this.parseSave}/>
</form>
<span className="startButton" onClick={() => this.setState({show_saves: false})}>Back</span>
</div>
</div>
<div className="start">
<ul className="saveList">
{Object.entries(save_names).map(([name, info]) => <li key={name}>
{name}{info ? <span className="info">{info.name} (lv. {info.level} {plrClass[info.cls]})</span> : ""}
<FontAwesomeIcon className="btnDownload" icon={faDownload} onClick={() => this.downloadSave(name)}/>
<FontAwesomeIcon className="btnRemove" icon={faTimes} onClick={() => this.removeSave(name)}/>
</li>)}
</ul>
<form>
<label htmlFor="loadFile" className="startButton">Upload Save</label>
<input accept=".sv" type="file" id="loadFile" style={{display: "none"}} onChange={this.parseSave}/>
</form>
<div className="startButton" onClick={() => this.setState({show_saves: false})}>Back</div>
</div>
);
} else if (compress) {
return (
<CompressMpq api={this} ref={e => this.compressMpq = e}/>
);
} else if (error) {
return (
<Link className="error" href={reportLink(error, this.state.retail)}>
<p className="header">The following error has occurred:</p>
<p className="body">{error.message}</p>
<p className="footer">Click to create an issue on GitHub</p>
{error.save != null && <a href={error.save} download={this.saveName}>Download save file</a>}
</Link>
);
} else if (loading && !started) {
return (
<div className="loading">
{(progress && progress.text) || 'Loading...'}
{progress != null && !!progress.total && (
<span className="progressBar"><span><span style={{width: `${Math.round(100 * progress.loaded / progress.total)}%`}}/></span></span>
)}
</div>
);
} else if (!started) {
return (
<div className="start">
<p>
This is a web port of the original Diablo game, based on source code reconstructed by
GalaXyHaXz and devilution team. The project page with information and links can be found over here <Link href="https://github.com/d07RiV/diabloweb">https://github.com/d07RiV/diabloweb</Link>
</p>
<p>
If you own the original game, you can drop the original DIABDAT.MPQ onto this page or click the button below to start playing.
The game can be purchased from <Link href="https://www.gog.com/game/diablo">GoG</Link>.
{" "}<span className="link" onClick={() => this.setState({compress: true})}>Click here to compress the MPQ, greatly reducing its size.</span>
</p>
{!has_spawn && (
<p>
Or you can play the shareware version for free (50MB download).
</p>
)}
<form>
<label htmlFor="loadFile" className="startButton">Select MPQ</label>
<input accept=".mpq" type="file" id="loadFile" style={{display: "none"}} onChange={this.parseFile}/>
</form>
<div className="startButton" onClick={() => this.start()}>Play Shareware</div>
{!!save_names && <div className="startButton" onClick={this.showSaves}>Manage Saves</div>}
</div>
);
}
}
render() {
const {started, error, dropping} = this.state;
return (
<div className={classNames("App", {touch: this.touchControls, started, dropping, keyboard: !!this.showKeyboard})} ref={this.setElement}>
<div className="touch-ui touch-mods">
@@ -699,45 +760,7 @@ class App extends React.Component {
</div>
</div>
<div className="BodyV">
{!!error && (
<Link className="error" href={reportLink(error, this.state.retail)}>
<p className="header">The following error has occurred:</p>
<p className="body">{error.message}</p>
<p className="footer">Click to create an issue on GitHub</p>
{error.save != null && <a href={error.save} download={this.saveName}>Download save file</a>}
</Link>
)}
{!!loading && !started && !error && (
<div className="loading">
{(progress && progress.text) || 'Loading...'}
{progress != null && !!progress.total && (
<span className="progressBar"><span><span style={{width: `${Math.round(100 * progress.loaded / progress.total)}%`}}/></span></span>
)}
</div>
)}
{!started && !loading && !error && (
<div className="start">
<p>
This is a web port of the original Diablo game, based on source code reconstructed by
GalaXyHaXz and devilution team. The project page with information and links can be found over here <Link href="https://github.com/d07RiV/diabloweb">https://github.com/d07RiV/diabloweb</Link>
</p>
<p>
If you own the original game, you can drop the original DIABDAT.MPQ onto this page or click the button below to start playing.
The game can be purchased from <Link href="https://www.gog.com/game/diablo">GoG</Link>.
</p>
{!has_spawn && (
<p>
Or you can play the shareware version for free (50MB download).
</p>
)}
<form>
<label htmlFor="loadFile" className="startButton">Select MPQ</label>
<input accept=".mpq" type="file" id="loadFile" style={{display: "none"}} onChange={this.parseFile}/>
</form>
<span className="startButton" onClick={() => this.start()}>Play Shareware</span>
{!!(save_names && save_names.length) && <span className="startButton" onClick={() => this.setState({show_saves: true})}>Manage Saves</span>}
</div>
)}
{this.renderUi()}
</div>
</div>
);

View File

@@ -110,7 +110,9 @@ body, #root, .App {
margin: 10px 0;
}
.startButton {
display: inline-block;
display: block;
margin-left: auto;
margin-right: auto;
border: 1px solid #fff;
background: #000;
font-size: 2em;
@@ -127,14 +129,24 @@ body, #root, .App {
text-align: left;
li {
padding: 0 6px;
.info {
color: #888;
margin-left: 6px;
}
.btnRemove {
color: #f88;
color: #800;
&:hover {
color: #f00;
}
float: right;
cursor: pointer;
margin: 0 4px;
}
.btnDownload {
color: #fff;
color: #888;
&:hover {
color: #fff;
}
float: right;
cursor: pointer;
margin: 0 4px;

View File

@@ -1,25 +1,28 @@
const W = new Uint32Array(80);
const SHA1CircularShift = (shift, value) => ((value << shift) | (value >>> (32 - shift)));
const SHA1CircularShift = (shift, value) => ((value << shift) | (value >> (32 - shift)));
class SHA1 {
state = new Uint32Array(5);
digest = new Uint32Array(5);
count = 0;
input(u8) {
input8(u8) {
const u32 = new Uint32Array(u8.buffer, u8.byteOffset, 16);
context.count += data.length * 32;
this.input(u32);
}
input(u32) {
this.count += u32.length * 32;
for (let i = 0; i < 16; ++i) {
W[i] = u32[i];
}
for (let i = 16; i < 80; ++i) {
W[i] = W[i - 16] ^ W[i - 14] ^ W[i - 8] ^ W[i - 3];
}
let A = this.state[0];
let B = this.state[1];
let C = this.state[2];
let D = this.state[3];
let E = this.state[4];
let A = this.digest[0];
let B = this.digest[1];
let C = this.digest[2];
let D = this.digest[3];
let E = this.digest[4];
for (let i = 0; i < 20; i++) {
const temp = SHA1CircularShift(5, A) + ((B & C) | ((~B) & D)) + E + W[i] + 0x5A827999;
@@ -57,21 +60,21 @@ class SHA1 {
A = temp | 0;
}
this.state[0] += A;
this.state[1] += B;
this.state[2] += C;
this.state[3] += D;
this.state[4] += E;
this.digest[0] += A;
this.digest[1] += B;
this.digest[2] += C;
this.digest[3] += D;
this.digest[4] += E;
}
constructor() {
this.state[0] = 0x67452301;
this.state[1] = 0xEFCDAB89;
this.state[2] = 0x98BADCFE;
this.state[3] = 0x10325476;
this.state[4] = 0xC3D2E1F0;
this.digest[0] = 0x67452301;
this.digest[1] = 0xEFCDAB89;
this.digest[2] = 0x98BADCFE;
this.digest[3] = 0x10325476;
this.digest[4] = 0xC3D2E1F0;
this.result = new Uint8Array(this.state.buffer);
this.digest8 = new Uint8Array(this.digest.buffer);
}
}
@@ -85,16 +88,10 @@ class Random {
}
}
struct CodecSignature {
DWORD checksum;
BYTE error;
BYTE last_chunk_size;
WORD unused;
};
function codec_init_key(password) {
const rand = new Random(0x7058);
const key = new Uint8Array(136);
const k32 = new Uint32Array(key.buffer);
for (let i = 0; i < 136; ++i) {
key[i] = rand.next();
}
@@ -103,62 +100,50 @@ function codec_init_key(password) {
pw[i] = password.charCodeAt(i % password.length);
}
const sha = new SHA1();
sha.input(pw);
let sha = new SHA1();
sha.input8(pw);
for (let i = 0; i < 136; ++i) {
key[i] ^= sha.result[i % sha.result.length];
for (let i = 0; i < 34; ++i) {
k32[i] ^= sha.digest[i % sha.digest.length];
}
sha = new SHA1();
sha.input(key.subarray(72));
sha.input(k32.subarray(18));
return sha;
}
function codec_decode(data, password) {
const sha = codec_init_key(password);
export default function codec_decode(data, password) {
if (data.length <= 8) {
return;
}
const size = data.length - 8;
if ()
char buf[128];
char dst[SHA1HashSize];
int i;
CodecSignature *sig;
if (size % 64) {
return;
}
codec_init_key(0, pszPassword);
if (size <= 8)
return 0;
size = size - 8;
if (size % 64 != 0)
return 0;
for (i = size; i != 0; pbSrcDst += 64, i -= 64) {
memcpy(buf, pbSrcDst, 64);
SHA1Result(0, dst);
for (int j = 0; j < 64; j++) {
buf[j] ^= dst[j % SHA1HashSize];
}
SHA1Calculate(0, buf, NULL);
memset(dst, 0, sizeof(dst));
memcpy(pbSrcDst, buf, 64);
}
if (data[size + 4]) {
return;
}
memset(buf, 0, sizeof(buf));
sig = (CodecSignature *)pbSrcDst;
if (sig->error > 0) {
size = 0;
SHA1Clear();
} else {
SHA1Result(0, dst);
if (sig->checksum != *(DWORD *)dst) {
memset(dst, 0, sizeof(dst));
size = 0;
SHA1Clear();
} else {
size += sig->last_chunk_size - 64;
SHA1Clear();
}
}
return size;
const last_size = data[size + 5];
const result_size = size + last_size - 64;
const result = new Uint8Array(result_size);
const sha = codec_init_key(password);
const size32 = size >> 2;
const data32 = new Uint32Array(data.buffer, data.byteOffset, size32 + 1);
const buf32 = new Uint32Array(16);
const buf = new Uint8Array(buf32.buffer);
for (let i = 0; i < size32; i += 16) {
for (let j = 0; j < 16; ++j) {
buf32[j] = data32[i + j] ^ sha.digest[j % sha.digest.length];
}
sha.input(buf32);
result.set(i === size32 - 16 ? buf.subarray(0, last_size) : buf, i * 4);
}
if (data32[size32] !== sha.digest[0]) {
return;
}
return result;
}

View File

@@ -1,4 +1,5 @@
import { explode } from './explode';
import codec_decode from './codec';
function pkzip_decompress(data, out_size) {
if (data.length === out_size) {
@@ -43,7 +44,7 @@ const hashtable = (function() {
}
return hashtable;
})();
function decrypt(u32, key) {
export function decrypt(u32, key) {
let seed = 0xEEEEEEEE;
for (let i = 0; i < u32.length; ++i) {
seed += hashtable[0x400 + (key & 0xFF)];
@@ -52,10 +53,23 @@ function decrypt(u32, key) {
key = ((~key << 0x15) + 0x11111111) | (key >>> 0x0B);
}
}
function decrypt8(u8, key) {
export function decrypt8(u8, key) {
decrypt(new Uint32Array(u8.buffer, u8.byteOffset, u8.length >> 2), key);
}
function hash(name, type) {
export function encrypt(u32, key) {
let seed = 0xEEEEEEEE;
for (let i = 0; i < u32.length; ++i) {
seed += hashtable[0x400 + (key & 0xFF)];
const orig = u32[i];
u32[i] ^= seed + key;
seed = (orig + seed * 33 + 3) | 0;
key = ((~key << 0x15) + 0x11111111) | (key >>> 0x0B);
}
}
export function encrypt8(u8, key) {
encrypt(new Uint32Array(u8.buffer, u8.byteOffset, u8.length >> 2), key);
}
export function hash(name, type) {
let seed1 = 0x7FED7FED;
let seed2 = 0xEEEEEEEE;
for (let i = 0; i < name.length; ++i) {
@@ -72,7 +86,7 @@ function hash(name, type) {
return seed1 >>> 0;
}
function path_name(name) {
export function path_name(name) {
const pos = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\'));
return name.substring(pos + 1);
}
@@ -90,11 +104,11 @@ const Flags = {
Exists: 0x80000000,
};
class MpqReader {
export class MpqReader {
constructor(buffer) {
this.buffer = buffer;
this.u8 = new Uint8Array(buffer);
this.u32 = new Uint32Array(buffer);
this.u32 = new Uint32Array(buffer, 0, buffer.byteLength >> 2);
this.readHeader();
}
@@ -132,79 +146,81 @@ class MpqReader {
}
}
read(name) {
readRaw(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) {
const info = {
filePos: this.blockTable[block * 4],
cmpSize: this.blockTable[block * 4 + 1],
fileSize: this.blockTable[block * 4 + 2],
flags: this.blockTable[block * 4 + 3],
key: hash(path_name(name), 3),
};
if ((info.flags & Flags.PatchFile) || info.filePos + info.cmpSize > this.buffer.byteLength) {
return;
}
if (!(flags & Flags.Compressed)) {
cmpSize = fileSize;
if (!(info.flags & Flags.Compressed)) {
info.cmpSize = info.fileSize;
}
let key = hash(path_name(name), 3);
if (flags & Flags.FixSeed) {
key = (key + filePos) ^ fileSize;
if (info.flags & Flags.FixSeed) {
info.key = (info.key + info.filePos) ^ info.fileSize;
}
return {info, data: new Uint8Array(this.buffer, info.filePos, info.cmpSize)};
}
if (flags & Flags.SingleUnit) {
const raw = new Uint8Array(this.buffer, filePos, cmpSize);
if (raw.length !== cmpSize) {
read(name) {
const raw = this.readRaw(name);
if (!raw) {
return;
}
let {info, data} = raw;
data = data.slice();
if (info.flags & Flags.SingleUnit) {
if (info.flags & Flags.Encrypted) {
decrypt8(data, info.key);
}
if (info.flags & Flags.CompressMulti) {
return;
} else if (info.flags & Flags.CompressPkWare) {
return pkzip_decompress(data, info.fileSize);
}
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 data;
} else if (!(info.flags & Flags.Compressed)) {
if (info.flags & Flags.Encrypted) {
for (let i = 0; i < info.fileSize; i += this.blockSize) {
decrypt8(data.subarray(i, Math.min(info.fileSize, i + this.blockSize)), info.key + i / this.blockSize);
}
}
return raw;
return data;
} 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) {
const numBlocks = Math.floor((info.fileSize + this.blockSize - 1) / this.blockSize);
const tableSize = numBlocks + 1;
if (data.length < tableSize * 4) {
return;
}
if (flags & Flags.Encrypted) {
decrypt(blocks, key - 1);
const blocks = new Uint32Array(data.buffer, 0, tableSize);
if (info.flags & Flags.Encrypted) {
decrypt(blocks, info.key - 1);
}
const output = new Uint8Array(fileSize);
const output = new Uint8Array(info.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) {
const uSize = Math.min(this.blockSize, info.fileSize - oPos);
if (blocks[i + 1] > data.length) {
return;
}
if (flags & Flags.Encrypted) {
decrypt8(tmp, key + i);
let tmp = data.subarray(blocks[i], blocks[i + 1]);
if (info.flags & Flags.Encrypted) {
// this is not safe, but our files are small enough
decrypt8(tmp, info.key + i);
}
if (flags & Flags.CompressMulti) {
if (info.flags & Flags.CompressMulti) {
return;
} else if (flags & Flags.CompressPkWare) {
} else if (info.flags & Flags.CompressPkWare) {
tmp = pkzip_decompress(tmp, uSize);
}
if (!tmp || tmp.length !== uSize) {
@@ -217,12 +233,28 @@ class MpqReader {
}
}
export default function getPlayerName(data) {
debugger;
function getPassword(name) {
if (name.match(/spawn\d+\.sv/i)) {
return 'lshbkfg1'; // single, spawn
} else if (name.match(/share_\d+\.sv/i)) {
return 'lshbkfg1'; // multi, spawn
} else if (name.match(/multi_\d+\.sv/i)) {
return 'szqnlsk1'; // multi, retail
} else {
return 'xrgyrkj1'; // single, retail
}
}
export default function getPlayerName(data, name) {
try {
const reader = new MpqReader(data);
const hero = reader.read("hero");
return '';
const hero = codec_decode(reader.read("hero"), getPassword(name));
const nameEnd = hero.indexOf(0, 16);
const result = {};
result.name = String.fromCharCode(...hero.subarray(16, nameEnd));
result.cls = hero[48];
result.level = hero[53];
return result;
} catch (e) {
return null;
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

27
src/mpqcmp/compress.js Normal file
View File

@@ -0,0 +1,27 @@
import Worker from './mpqcmp.worker.js';
export default function compress(mpq, progress) {
progress("Loading...");
return new Promise((resolve, reject) => {
try {
const worker = new Worker();
worker.addEventListener("message", ({data}) => {
switch (data.action) {
case "result":
resolve(data.result);
break;
case "error":
reject({message: data.error, stack: data.stack});
break;
case "progress":
progress(data.text, data.loaded, data.total);
break;
default:
}
});
worker.postMessage({action: "run", mpq});
} catch (e) {
reject(e);
}
});
}

84
src/mpqcmp/index.js Normal file
View File

@@ -0,0 +1,84 @@
import React from 'react';
import compress from './compress';
export default class CompressMpq extends React.Component {
state = {};
parseFile = e => {
const files = e.target.files;
if (files.length > 0) {
this.start(files[0]);
}
}
onProgress(progress) {
this.setState({progress});
}
onDone = result => {
const blob = new Blob([result], {type: 'binary/octet-stream'});
const url = URL.createObjectURL(blob);
this.setState({url});
const lnk = document.createElement('a');
lnk.setAttribute('href', url);
lnk.setAttribute('download', 'DIABDAT.MPQ');
document.body.appendChild(lnk);
lnk.click();
document.body.removeChild(lnk);
}
onError(message, stack) {
const { api } = this.props;
api.setState({compress: false});
api.onError(message, stack);
}
onClose = () => {
if (this.state.url) {
URL.revokeObjectURL(this.state.url);
}
this.props.api.setState({compress: false});
}
start(file) {
this.setState({started: true});
compress(file, (text, loaded, total) => this.onProgress({text, loaded, total}))
.then(this.onDone, e => this.onError(e.message, e.stack));
}
render() {
const { url, started, progress } = this.state;
if (url) {
return (
<div className="start">
<p>
<a href={url} download="DIABDAT.MPQ">Click here if download doesn't start.</a>
</p>
<div className="startButton" onClick={this.onClose}>Back</div>
</div>
);
}
if (started) {
return (
<div className="loading">
{(progress && progress.text) || 'Processing...'}
{progress != null && !!progress.total && (
<span className="progressBar"><span><span style={{width: `${Math.round(100 * progress.loaded / progress.total)}%`}}/></span></span>
)}
</div>
);
}
return (
<div className="start">
<p>
You can use this tool to reduce the original MPQ to about half its size. It encodes sounds in MP3 format and uses better compression for regular files.
To begin, click the button below or drop the MPQ onto the page.
</p>
<form>
<label htmlFor="loadFile" className="startButton">Select MPQ</label>
<input accept=".mpq" type="file" id="loadFile" style={{display: "none"}} onChange={this.parseFile}/>
</form>
<div className="startButton" onClick={this.onClose}>Back</div>
</div>
);
}
}

View File

@@ -7,14 +7,6 @@ const MpqSize = 356747;
/* eslint-disable-next-line no-restricted-globals */
const worker = self;
function onError(err, action="error") {
if (err instanceof Error) {
worker.postMessage({action, error: err.toString(), stack: err.stack});
} else {
worker.postMessage({action, error: err.toString()});
}
}
let input_file = null;
let output_file = null;
let last_progress = 0;
@@ -31,6 +23,7 @@ const DApi = {
array.set(input_file.subarray(offset, offset + array.byteLength));
},
put_file_size(size) {
debugger;
output_file = new Uint8Array(size);
},
put_file_contents(array, offset) {
@@ -65,25 +58,27 @@ const readFile = (file, progress) => new Promise((resolve, reject) => {
reader.readAsArrayBuffer(file);
});
async function initWasm(spawn, progress) {
async function initWasm(progress) {
const binary = await axios.request({
url: spawn ? SpawnBinary : DiabloBinary,
url: MpqBinary,
responseType: 'arraybuffer',
onDownloadProgress: progress,
});
const result = await (spawn ? SpawnModule : DiabloModule)({wasmBinary: binary.data}).ready;
progress({loaded: 2000000});
const result = await MpqModule({
wasmBinary: binary.data,
}).ready;
progress({loaded: MpqSize});
return result;
}
async function run(mpq) {
progress("Loading...");
let mpqLoaded = 0, mpqTotal = (mpq ? mpq.size : 0), wasmLoaded = 0, wasmTotal = (spawn ? SpawnSize : DiabloSize);
let mpqLoaded = 0, mpqTotal = (mpq ? mpq.size : 0), wasmLoaded = 0, wasmTotal = MpqSize;
const wasmWeight = 5;
function updateProgress() {
progress("Loading...", mpqLoaded + wasmLoaded * wasmWeight, mpqTotal + wasmTotal * wasmWeight);
}
const loadWasm = initWasm(spawn, e => {
const loadWasm = initWasm(e => {
wasmLoaded = Math.min(e.loaded, wasmTotal);
updateProgress();
});
@@ -95,7 +90,7 @@ async function run(mpq) {
input_file = new Uint8Array(mpq);
progress("Initializing...");
progress("Processing...");
wasm._DApi_MpqCmp(input_file.length);
@@ -105,10 +100,10 @@ async function run(mpq) {
worker.addEventListener("message", ({data}) => {
switch (data.action) {
case "run":
init_game(data.mpq).then(
res => worker.postMessage({action: "result", data: res}, [res]),
e => onError(e, "failed"));
run(data.mpq).then(
result => worker.postMessage({action: "result", result}, [result]),
err => worker.postMessage({action: "error", error: err.toString(), stack: err.stack}));
break;
default:
}
});
});