diff --git a/package-lock.json b/package-lock.json index fcdd2cd..abd13be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "diabloweb", - "version": "1.0.18", + "version": "1.0.19", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 839ca50..1b6ab08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "diabloweb", - "version": "1.0.18", + "version": "1.0.19", "private": true, "dependencies": { "@babel/core": "7.4.3", diff --git a/src/App.js b/src/App.js index d8fe341..dd9f26e 100644 --- a/src/App.js +++ b/src/App.js @@ -237,7 +237,7 @@ class App extends React.Component { this.setState({loading: true, retail}); - load_game(this, file).then(game => { + load_game(this, file, !retail).then(game => { this.game = game; document.addEventListener('mousemove', this.onMouseMove, true); diff --git a/src/api/Diablo.wasm b/src/api/Diablo.wasm index c39e870..3abe2d0 100644 Binary files a/src/api/Diablo.wasm and b/src/api/Diablo.wasm differ diff --git a/src/api/DiabloSpawn.wasm b/src/api/DiabloSpawn.wasm index cc9aeaf..ea9c758 100644 Binary files a/src/api/DiabloSpawn.wasm and b/src/api/DiabloSpawn.wasm differ diff --git a/src/api/game.worker.js b/src/api/game.worker.js index 7f05000..c221e66 100644 --- a/src/api/game.worker.js +++ b/src/api/game.worker.js @@ -7,6 +7,9 @@ import axios from 'axios'; const DiabloSize = 1316452; const SpawnSize = 1196648; +const SpawnMpqSize = 50274091; +const RetailMpqSize = 517501282; + /* eslint-disable-next-line no-restricted-globals */ const worker = self; @@ -17,6 +20,52 @@ let renderBatch = null; let drawBelt = null; let is_spawn = false; +const ChunkSize = 1 << 20; +class RemoteFile { + constructor(url, size) { + this.url = url; + this.byteLength = size; + + this.buffer = new Uint8Array(size); + this.chunks = new Uint8Array(((size + ChunkSize - 1) >> 20) | 0); + } + + subarray(start, end) { + let chunk0 = (start / ChunkSize) | 0; + let chunk1 = ((end + ChunkSize - 1) / ChunkSize) | 0; + let missing0 = chunk1, missing1 = chunk0; + for (let i = chunk0; i < chunk1; ++i) { + if (!this.chunks[i]) { + missing0 = Math.min(missing0, i); + missing1 = Math.max(missing1, i); + } + } + if (missing0 <= missing1) { + const request = new XMLHttpRequest(); + request.open('GET', this.url, false); + request.setRequestHeader('Range', `bytes=${missing0 * ChunkSize}-${Math.min(missing1 * ChunkSize + ChunkSize - 1, this.byteLength - 1)}`); + request.responseType = 'arraybuffer'; + request.send(); + if (request.status < 200 || request.status > 206) { + worker.postMessage({action: "error", error: `Failed to load remote file`}); + } else { + const header = request.getResponseHeader('Content-Range'); + let m, start = 0; + if (header && (m = header.match(/bytes (\d+)-(\d+)\/(\d+)/))) { + start = parseInt(m[1]); + } + this.buffer.set(new Uint8Array(request.response), start); + chunk0 = ((start + ChunkSize - 1) / ChunkSize) | 0; + chunk1 = ((start + request.response.byteLength + ChunkSize - 1) / ChunkSize) | 0; + for (let i = chunk0; i < chunk1; ++i) { + this.chunks[i] = 1; + } + } + } + return this.buffer.subarray(start, end); + } +} + const DApi = { exit_error(error) { worker.postMessage({action: "error", error}); @@ -36,7 +85,7 @@ const DApi = { get_file_contents(path, array, offset) { const data = files.get(path.toLowerCase()); if (data) { - array.set(data.subarray(offset, offset + array.length)); + array.set(data.subarray(offset, offset + array.byteLength)); } }, put_file_contents(path, array) { @@ -240,6 +289,14 @@ async function init_game(mpq, spawn, offscreen) { Object.assign(DApi, DApi_renderLegacy); } + if (!mpq) { + const name = (spawn ? 'spawn.mpq' : 'diabdat.mpq'); + if (!files.has(name)) { + // This should never happen, but we do support remote loading + files.set(name, new RemoteFile(`${process.env.PUBLIC_URL}/${name}`, spawn ? SpawnMpqSize : RetailMpqSize)); + } + } + progress("Loading..."); let mpqLoaded = 0, mpqTotal = (mpq ? mpq.size : 0), wasmLoaded = 0, wasmTotal = (spawn ? SpawnSize : DiabloSize); const wasmWeight = 5; diff --git a/src/api/loader.js b/src/api/loader.js index 81ff001..a355b8c 100644 --- a/src/api/loader.js +++ b/src/api/loader.js @@ -46,15 +46,9 @@ function testOffscreen() { }*/ } -async function do_load_game(api, audio, mpq) { +async function do_load_game(api, audio, mpq, spawn) { const fs = await api.fs; - let spawn = true; - if (mpq) { - if (!mpq.name.match(/^spawn\.mpq$/i)) { - spawn = false; - fs.files.delete('spawn.mpq'); - } - } else { + if (spawn && !mpq) { await load_spawn(api, fs); } @@ -124,7 +118,7 @@ async function do_load_game(api, audio, mpq) { }); } -export default function load_game(api, mpq) { +export default function load_game(api, mpq, spawn) { const audio = init_sound(); - return do_load_game(api, audio, mpq); + return do_load_game(api, audio, mpq, spawn); }