Spaces:
No application file
No application file
| (function (global) { | |
| /** | |
| * Emulator constructor | |
| * @param {object} options | |
| */ | |
| function Halfix(options) { | |
| this.options = options || {}; | |
| this.canvas = this.options["canvas"] || null; | |
| this.ctx = this.canvas ? this.canvas.getContext("2d") : null; | |
| this.total_memory = 256; | |
| this.fast = options["fast"] || false; | |
| this.winnt_hack = options["winnt_hack"] || false; | |
| this.reportSpeed = options["reportSpeed"] || function (n) { }; | |
| console.log(options.reportSpeed); | |
| this.paused = false; | |
| /** @type {ImageData} */ | |
| this.image_data = null; | |
| this.config = this.buildConfiguration(); | |
| this.onprogress = options["onprogress"] || function (a, b, c) { }; | |
| } | |
| var _cache = []; | |
| /** | |
| * @param {string} name | |
| * @returns {string|null} Value of the parameter or null | |
| */ | |
| Halfix.prototype.getParameterByName = function (name) { | |
| var opt = this.options[name]; | |
| if (!opt) return null; | |
| // Check if we have a special kind of object here | |
| var index = _cache.length; | |
| if (opt instanceof ArrayBuffer) { | |
| _cache.push(opt); | |
| // Return that we have this in the cache | |
| return "ab!" + index; | |
| } else if (opt instanceof File) { | |
| _cache.push(opt); | |
| return "file!" + index; | |
| } | |
| return opt; | |
| }; | |
| /** | |
| * @param {string} name | |
| * @param {boolean} defaultValue | |
| * @returns {boolean} Value of parameter as a boolean | |
| */ | |
| Halfix.prototype.getBooleanByName = function (name, defaultValue) { | |
| var res = this.getParameterByName(name); | |
| if (res === null) return defaultValue | 0; | |
| else return !!res | 0; | |
| }; | |
| /** | |
| * @param {string} name | |
| * @param {number} defaultValue | |
| * @returns {number} Value of parameter as an integer | |
| */ | |
| Halfix.prototype.getIntegerByName = function (name, defaultValue) { | |
| var res = this.getParameterByName(name); | |
| if (res === null) return defaultValue; | |
| else return parseInt(res) | 0; | |
| }; | |
| /** | |
| * @param {string} name | |
| * @param {number} defaultValue | |
| * @returns {number} Value of parameter as a double | |
| */ | |
| Halfix.prototype.getDoubleByName = function (name, defaultValue) { | |
| var res = this.getParameterByName(name); | |
| if (res === null) return defaultValue; | |
| else return +parseFloat(res); | |
| }; | |
| /** | |
| * Build drive configuration | |
| * @param {array} config | |
| * @param {string} drvid A drive ID (a/b/c/d) | |
| * @param {number} primary Primary? (0=yes, 1=no) | |
| * @param {number} master Master? (0=yes, 1=no) | |
| */ | |
| Halfix.prototype.buildDrive = function (config, drvid, primary, master) { | |
| var hd = this.getParameterByName("hd" + drvid), | |
| cd = this.getParameterByName("cd" + drvid); | |
| if (!hd && !cd) | |
| return; | |
| config.push("[ata" + primary + "-" + master + "]"); | |
| if (hd) { | |
| if (hd !== "none") { | |
| config.push("file=" + hd); | |
| config.push("inserted=1"); | |
| } | |
| config.push("type=hd"); | |
| } else { | |
| if (cd !== "none") { | |
| config.push("file=" + cd); | |
| config.push("inserted=1"); | |
| } | |
| config.push("type=cd"); | |
| } | |
| }; | |
| /** | |
| * @param {string} f name of file | |
| * @param {number} a current position | |
| * @param {number} b end | |
| */ | |
| Halfix.prototype.updateNetworkProgress = function (f, a, b) { | |
| this.onprogress(f, a, b); | |
| }; | |
| /** | |
| * @param {number} total Total bytes loaded | |
| */ | |
| Halfix.prototype.updateTotalBytes = function (total) { | |
| }; | |
| Halfix.prototype.handleSavestate = function () { | |
| // TODO | |
| }; | |
| /** | |
| * Build floppy configuration | |
| */ | |
| Halfix.prototype.buildFloppy = function (config, drvid) { | |
| var fd = this.getParameterByName("fd" + drvid); | |
| if (!fd) | |
| return; | |
| config.push("[fd" + drvid + "]"); | |
| config.push("file=" + fd); | |
| config.push("inserted=1"); | |
| } | |
| /** | |
| * From the given URL parameters, we build a .conf file for Halfix to consume. | |
| */ | |
| Halfix.prototype.buildConfiguration = function () { | |
| var config = []; | |
| /* | |
| bios_path: getParameterByName("bios") || "bios.bin", | |
| bios: null, | |
| vgabios_path: getParameterByName("vgabios") || "vgabios.bin", | |
| vgabios: null, | |
| hd: [getParameterByName("hda"), getParameterByName("hdb"), getParameterByName("hdc"), getParameterByName("hdd")], | |
| cd: [getParameterByName("cda"), getParameterByName("cdb"), getParameterByName("cdc"), getParameterByName("cdd")], | |
| pci: getBooleanByName("pcienabled"), | |
| apic: getBooleanByName("apicenabled"), | |
| acpi: getBooleanByName("apicenabled"), | |
| now: getParameterByName("now") ? parseFloat(getParameterByName("now")) : 1563602400, | |
| mem: getParameterByName("mem") ? parseInt(getParameterByName("mem")) : 32, | |
| vgamem: getParameterByName("vgamem") ? parseInt(getParameterByName("vgamem")) : 32, | |
| fd: [getParameterByName("fda"), getParameterByName("fdb")], | |
| boot: getParameterByName("boot") || "chf" // HDA, FDC, CDROM | |
| */ | |
| config.push("bios=" + (this.getParameterByName("bios") || "bios.bin")); | |
| config.push("vgabios=" + (this.getParameterByName("vgabios") || "vgabios.bin")); | |
| config.push("pci=" + this.getBooleanByName("pcienabled", true)); | |
| config.push("apic=" + this.getBooleanByName("apicenabled", true)); | |
| config.push("acpi=" + this.getBooleanByName("acpienabled", true)); | |
| config.push("pcivga=" + this.getBooleanByName("pcivga", false)); | |
| config.push("now=" + this.getDoubleByName("now", new Date().getTime())); | |
| var floppyRequired = (!!this.getParameterByName("fda") || !!this.getParameterByName("fdb")) | 0; | |
| console.log(floppyRequired); | |
| config.push("floppy=" + floppyRequired); | |
| var mem = this.getIntegerByName("mem", 32), | |
| vgamem = this.getIntegerByName("vgamem", 4); | |
| function roundUp(v) { | |
| v--; | |
| v |= v >> 1; | |
| v |= v >> 2; | |
| v |= v >> 4; | |
| v |= v >> 8; | |
| v |= v >> 16; | |
| v++; | |
| return v; | |
| } | |
| this.total_memory = roundUp(mem + 32 + vgamem) * 1024 * 1024 | 0; | |
| //Module["TOTAL_MEMORY"] = roundUp(mem + 32 + vgamem) * 1024 * 1024 | 0; | |
| config.push("memory=" + mem + "M"); | |
| config.push("vgamemory=" + vgamem + "M"); | |
| this.buildDrive(config, "a", 0, "master"); | |
| this.buildDrive(config, "b", 0, "slave"); | |
| this.buildDrive(config, "c", 1, "master"); | |
| this.buildDrive(config, "d", 1, "slave"); | |
| this.buildFloppy(config, "a"); | |
| this.buildFloppy(config, "b"); | |
| config.push("[boot]"); | |
| var bootOrder = this.getParameterByName("boot") || "hcf"; | |
| config.push("a=" + bootOrder[0] + "d"); | |
| config.push("b=" + bootOrder[1] + "d"); | |
| config.push("c=" + bootOrder[2] + "d"); | |
| config.push("[cpu]"); | |
| config.push("cpuid_limit_winnt=" + (this.winnt_hack ? "1" : "0")); | |
| config.push(""); // Trailing empty line | |
| return config.join("\n"); | |
| } | |
| Halfix.prototype["send_ctrlaltdel"] = function () { | |
| send_ctrlaltdel(1); | |
| send_ctrlaltdel(0); | |
| }; | |
| function loadFiles(paths, cb, gz) { | |
| var resultCounter = paths.length | 0, | |
| results = []; | |
| inLoading = true; | |
| for (var i = 0; i < paths.length; i = i + 1 | 0) { | |
| (function () { | |
| // Save some state information inside the closure. | |
| var xhr = new XMLHttpRequest(), | |
| idx = i, | |
| lastProgress = 0; | |
| var path = paths[i] + (gz ? ".gz" : ""); | |
| xhr.open("GET", paths[i] + (gz ? ".gz" : "")); | |
| xhr.onprogress = function (e) { | |
| if (e.lengthComputable) { | |
| _halfix.updateNetworkProgress(path, e.loaded, e.total); | |
| lastProgress = now; | |
| } | |
| }; | |
| xhr.responseType = "arraybuffer"; | |
| xhr.onload = function () { | |
| if (!gz) | |
| results[idx] = new Uint8Array(xhr.response); | |
| else | |
| results[idx] = pako.inflate(new Uint8Array(xhr.response)); | |
| resultCounter = resultCounter - 1 | 0; | |
| _halfix.updateTotalBytes(xhr.response.byteLength | 0); | |
| if (resultCounter === 0) { | |
| cb(null, results); | |
| inLoading = false; | |
| // If we have requested a savestate, then create it now. | |
| _halfix.handleSavestate(); | |
| } | |
| }; | |
| xhr.onerror = function (e) { | |
| alert("Unable to load file"); | |
| cb(e, null); | |
| }; | |
| xhr.send(); | |
| })(); | |
| } | |
| } | |
| Halfix.prototype.updateScreen = function () { | |
| if (this.ctx) | |
| this.ctx.putImageData(this.image_data, 0, 0); | |
| }; | |
| var _loaded = false; | |
| // ======================================================================== | |
| // Public API | |
| // ======================================================================== | |
| /** | |
| * Get configuration file | |
| * @returns {string} Configuration text | |
| */ | |
| Halfix.prototype["getConfiguration"] = function () { | |
| return this.config; | |
| }; | |
| /** @type {Halfix} */ | |
| var _halfix = null; | |
| var init_cb = null; | |
| /** | |
| * Load and initialize emulator instance | |
| * @param {function(Error, object)} cb Callback | |
| */ | |
| Halfix.prototype["init"] = function (cb) { | |
| // Only one instance can be loaded at a time, unfortunately | |
| if (_loaded) cb(new Error("Already initialized"), null); | |
| _loaded = true; | |
| // Save our Halfix instance for later | |
| _halfix = this; | |
| // Set up our module instance | |
| global["Module"]["canvas"] = this.canvas; | |
| global["Module"]["TOTAL_MEMORY"] = this.total_memory; | |
| init_cb = cb; | |
| // Load emulator | |
| var script = document.createElement("script"); | |
| script.src = this.getParameterByName("emulator") || "VirtualXP.js"; | |
| document.head.appendChild(script); | |
| }; | |
| var savestate_files = {}; | |
| function u8tostr(u) { | |
| var str = ""; | |
| for (var i = 0; i < u.length; i = i + 1 | 0)str += String.fromCharCode(u[i]); | |
| return str; | |
| } | |
| /** | |
| * Load savestate from directory | |
| * @param {string} statepath | |
| * @param {function} cb | |
| */ | |
| Halfix.prototype["loadStateXHR"] = function (statepath, cb) { | |
| loadFiles([ | |
| statepath + "/state.bin", | |
| statepath + "/ram", | |
| statepath + "/vram", | |
| statepath + "/diskinfo.json"], function (err, data) { | |
| if (err) throw err; | |
| savestate_files["/state.bin"] = data[0]; | |
| savestate_files["/ram"] = data[1]; | |
| savestate_files["/vram"] = data[2]; | |
| savestate_files["/diskinfo.json"] = JSON.parse(u8tostr(data[3])); | |
| wrap("emscripten_load_state")(); | |
| delete data[3]; // try to get this gc'ed | |
| cb(); | |
| }, true); | |
| }; | |
| /** | |
| * Pause the emulator | |
| * @param {boolean} paused | |
| */ | |
| Halfix.prototype["pause"] = function (paused) { | |
| this.paused = paused; | |
| }; | |
| /** | |
| * Send a fullscreen request to the brower. | |
| */ | |
| Halfix.prototype["fullscreen"] = function () { | |
| Module["requestFullscreen"](); | |
| }; | |
| var cyclebase = 0; | |
| function run_again(me, x) { | |
| if (requests_in_progress !== 0) { | |
| setTimeout(function () { run_again(me, x); }, x) | |
| } else | |
| me["run"](); | |
| } | |
| Halfix.prototype["run"] = function () { | |
| if (this.paused) return; | |
| try { | |
| var x = run(); | |
| var temp; | |
| var elapsed = (temp = new Date().getTime()) - now; | |
| if (elapsed >= 1000) { | |
| var curcycles = cycles(); | |
| this.reportSpeed(((curcycles - cyclebase) / (elapsed) / (1000)).toFixed(2)); | |
| //console.log(((curcycles - cyclebase) / (elapsed) / (1000)).toFixed(2)); | |
| //$("speed").innerHTML = ((curcycles - cyclebase) / (elapsed) / (1000)).toFixed(2); | |
| cyclebase = curcycles; | |
| now = temp; | |
| } | |
| var me = this; | |
| setTimeout(function () { | |
| run_again(me, x); | |
| }, x); | |
| } catch (e) { | |
| $("error").innerHTML = "Exception thrown -- see JavaScript console"; | |
| $("messages").innerHTML = e.toString() + "<br />" + e.stack; | |
| failed = true; | |
| console.log(e); | |
| throw e; | |
| } | |
| }; | |
| // ======================================================================== | |
| // Emscripten support code | |
| // ======================================================================== | |
| var dynCall_vii, send_ctrlaltdel; | |
| var cycles, now; | |
| function run_wrapper2() { | |
| wrap("emscripten_init")(); | |
| now = new Date().getTime(); | |
| cycles = wrap("emscripten_get_cycles"); | |
| get_now = wrap("emscripten_get_now"); | |
| dynCall_vii = wrap("emscripten_dyncall_vii"); | |
| run = wrap("emscripten_run"); | |
| send_ctrlaltdel = wrap("display_send_ctrl_alt_del"); | |
| wrap("emscripten_set_fast")(_halfix.fast); | |
| init_cb(); | |
| } | |
| // Initialize module instance | |
| global["Module"] = {}; | |
| // Set up some stuff that we might find helpful | |
| const SAVE_LOGS = true; | |
| var arr = []; | |
| Module["printErr"] = function (ln) { if (SAVE_LOGS) arr.push(ln); }; | |
| function save(filename, data) { | |
| var blob = new Blob([data], { type: 'text/csv' }); | |
| if (window.navigator.msSaveOrOpenBlob) { | |
| window.navigator.msSaveBlob(blob, filename); | |
| } | |
| else { | |
| var elem = window.document.createElement('a'); | |
| elem.href = window.URL.createObjectURL(blob); | |
| elem.download = filename; | |
| document.body.appendChild(elem); | |
| elem.click(); | |
| document.body.removeChild(elem); | |
| } | |
| } | |
| function saveLog() { | |
| save("test.txt", arr.join("\n")); | |
| } | |
| window["saveLog"] = saveLog; | |
| Module["print"] = function (ln) { console.log(ln); }; | |
| global["update_screen"] = function () { | |
| _halfix.updateScreen(); | |
| }; | |
| var requests_in_progress = 0; | |
| /** | |
| * @param {number} lenptr Pointer to length (type: int*) | |
| * @param {number} dataptr Pointer to allocated data (type: void**) | |
| * @param {number} Pointer to path (type: char*) | |
| */ | |
| global["load_file_xhr"] = function (lenptr, dataptr, path) { | |
| var pathstr = readstr(path); | |
| var cb = function (err, data) { | |
| if (err) throw err; | |
| var destination = Module["_emscripten_alloc"](data.length, 4096); | |
| memcpy(destination, data); | |
| i32[lenptr >> 2] = data.length; | |
| i32[dataptr >> 2] = destination; | |
| requests_in_progress = requests_in_progress - 1 | 0; | |
| if (requests_in_progress === 0) run_wrapper2(); | |
| }; | |
| // Increment requests in progress by one | |
| requests_in_progress = requests_in_progress + 1 | 0; | |
| if (pathstr.indexOf("!") !== -1) { | |
| // If the user specified an arraybuffer or a file | |
| var pathparts = pathstr.split("!"); | |
| var data = _cache[parseInt(pathparts[1])]; | |
| /** @type {WholeFileLoader} */ | |
| var driver = new xhr_replacements[pathparts[0]](data); | |
| driver.load(cb); | |
| } else loadFiles([pathstr], function (err, datas) { | |
| cb(err, datas[0]); | |
| }, false); | |
| }; | |
| /** | |
| * @param {number} fbptr Pointer to framebuffer information | |
| * @param {number} x The width of the window | |
| * @param {number} y The height of the window | |
| */ | |
| global["update_size"] = function (fbptr, x, y) { | |
| if (x == 0 || y == 0) return; // Don't do anything if x or y is zero (VGA resizing sometimes gives weird sizes) | |
| Module["canvas"].width = x; | |
| Module["canvas"].height = y; | |
| _halfix.image_data = new ImageData(new Uint8ClampedArray(Module["HEAPU8"].buffer, fbptr, (x * y) << 2), x, y); | |
| }; | |
| Module["onRuntimeInitialized"] = function () { | |
| // Initialize runtime | |
| u8 = Module["HEAPU8"]; | |
| u16 = Module["HEAPU16"]; | |
| i32 = Module["HEAP32"]; | |
| // Get pointer to configuration | |
| var pc = Module["_emscripten_get_pc_config"](); | |
| var cfg = _halfix.getConfiguration(); | |
| var area = alloc(cfg.length + 1); | |
| strcpy(area, cfg); | |
| Module["_parse_cfg"](pc, area); | |
| var fast = _halfix.getBooleanByName("fast", false); | |
| Module["_emscripten_set_fast"](fast); | |
| // The story continues in global["load_file_xhr"], which loads the BIOS/VGA BIOS files | |
| // Run some primitive garbage collection | |
| gc(); | |
| }; | |
| global["drives"] = []; | |
| global["drive_init"] = function (info_ptr, path, id) { | |
| var p = readstr(path), image; | |
| if (p.indexOf("!") !== -1) { | |
| var chunks = p.split("!"); | |
| image = new image_backends[chunks[0]](_cache[parseInt(chunks[1]) | 0]); | |
| } else | |
| image = new XHRImage(); | |
| requests_in_progress = requests_in_progress + 1 | 0; | |
| image.init(p, function (err, data) { | |
| if (err) throw err; | |
| var dataptr = alloc(data.length), strptr = alloc(p.length + 1); | |
| memcpy(dataptr, data); | |
| strcpy(strptr, p); | |
| wrap("drive_emscripten_init")(info_ptr, strptr, dataptr, id); | |
| gc(); | |
| global["drives"][id] = image; | |
| requests_in_progress = requests_in_progress - 1 | 0; | |
| if (requests_in_progress === 0) run_wrapper2(); | |
| }); | |
| }; | |
| // ======================================================================== | |
| // Data reading functions | |
| // ======================================================================== | |
| /** | |
| * @constructor | |
| */ | |
| function WholeFileLoader() { } | |
| /** | |
| * Load a file | |
| * @param {function} cb Callback | |
| */ | |
| WholeFileLoader.prototype.load = function (cb) { | |
| throw new Error("requires implementation"); | |
| }; | |
| /** | |
| * @extends WholeFileLoader | |
| * @constructor | |
| * @param {ArrayBuffer} data | |
| */ | |
| function ArrayBufferLoader(data) { | |
| this.data = data; | |
| } | |
| ArrayBufferLoader.prototype = new WholeFileLoader(); | |
| ArrayBufferLoader.prototype.load = function (cb) { | |
| cb(null, new Uint8Array(this.data)); | |
| }; | |
| /** | |
| * @extends WholeFileLoader | |
| * @constructor | |
| * @param {File} file | |
| */ | |
| function FileReaderLoader(file) { | |
| this.file = file; | |
| } | |
| FileReaderLoader.prototype = new WholeFileLoader(); | |
| FileReaderLoader.prototype.load = function (cb) { | |
| var fr = new FileReader(); | |
| fr.onload = function () { | |
| cb(null, new Uint8Array(fr.result)); | |
| }; | |
| fr.onerror = function (e) { | |
| cb(e, null); | |
| }; | |
| fr.onabort = function () { | |
| cb(new Error("filereader aborted"), null); | |
| }; | |
| fr.readAsArrayBuffer(this.file); | |
| }; | |
| // see load_file_xhr | |
| var xhr_replacements = { | |
| "file": FileReaderLoader, | |
| "ab": ArrayBufferLoader | |
| }; | |
| /** | |
| * Generic hard drive image. All you have to do is fill in | |
| * @constructor | |
| */ | |
| function HardDriveImage() { | |
| /** @type {Uint8Array[]} */ | |
| this.blocks = []; | |
| /** @type {string[]} */ | |
| this.request_queue = []; | |
| /** @type {number[]} */ | |
| this.request_queue_ids = []; | |
| /** @type {number} */ | |
| this.cb = 0; | |
| /** @type {number} */ | |
| this.arg1 = 0; | |
| } | |
| /** | |
| * Adds a block to the cache. Called on IDE writes, typically | |
| * | |
| * @param {number} id | |
| * @param {number} offset Offset in memory to read from | |
| * @param {number} length | |
| */ | |
| HardDriveImage.prototype["addCache"] = function (id, offset, length) { | |
| this.blocks[id] = u8.slice(offset, length + offset | 0); | |
| }; | |
| /** | |
| * Reads a section of a block from the cache | |
| * | |
| * See src/drive.c: drive_read_block_internal | |
| * | |
| * @param {number} id Block ID | |
| * @param {number} buffer Position in the buffer | |
| * @param {number} offset Offset in the block to read from | |
| * @param {number} length Number of bytes to read | |
| */ | |
| HardDriveImage.prototype["readCache"] = function (id, buffer, offset, length) { | |
| id = id | 0; | |
| if (!this.blocks[id]) { | |
| //printElt.value += "[JSError] readCache(id=0x" + id.toString(16) + ", buffer=0x" + buffer.toString(16) + ", length=0x" + buffer.toString(16) + ")\n"; | |
| return 1; // No block here with that data. | |
| } | |
| var buf = this.blocks[id].subarray(offset, length + offset | 0); | |
| if (buf.length > length) throw new Error("Block too long"); | |
| u8.set(this.blocks[id].subarray(offset, length + offset | 0), buffer); | |
| return 0; | |
| }; | |
| /** | |
| * Writes a section of a block with some data | |
| * | |
| * See src/drive.c: drive_write_block_internal | |
| * | |
| * @param {number} id Block ID | |
| * @param {number} buffer Position in the buffer | |
| * @param {number} offset Offset in the block to read from | |
| * @param {number} length Number of bytes to read | |
| */ | |
| HardDriveImage.prototype["writeCache"] = function (id, buffer, offset, length) { | |
| id = id | 0; | |
| if (!this.blocks[id]) { | |
| //printElt.value += "[JSError] writeCache(id=0x" + id.toString(16) + ", buffer=0x" + buffer.toString(16) + ", length=0x" + buffer.toString(16) + ")\n"; | |
| return 1; // No block here with that data. | |
| } | |
| var buf = u8.subarray(buffer, length + buffer | 0); | |
| if (buf.length > length) throw new Error("Block too long"); | |
| this.blocks[id].set(buf, offset); | |
| return 0; | |
| }; | |
| /** | |
| * Queues a memory read. Useful for multi-block reads | |
| * | |
| * @param {number} str Pointer to URL | |
| * @param {number} id Block ID to store it in | |
| */ | |
| HardDriveImage.prototype["readQueue"] = function (str, id) { | |
| this.request_queue.push(readstr(str)); | |
| this.request_queue_ids.push(id); | |
| }; | |
| /** | |
| * Runs all requests simultaneously. | |
| * | |
| * @param {number} cb Callback pointer | |
| * @param {number} arg1 Callback argument | |
| */ | |
| HardDriveImage.prototype["flushReadQueue"] = function (cb, arg1) { | |
| /** @type {HardDriveImage} */ | |
| var me = this; | |
| this.cb = cb; | |
| this.arg1 = arg1; | |
| this.load(this.request_queue, function (err, data) { | |
| if (err) throw err; | |
| var rql = me.request_queue.length; | |
| for (var i = 0; i < rql; i = i + 1 | 0) | |
| me.blocks[me.request_queue_ids[i]] = data[i]; | |
| // Empty request queue | |
| me.request_queue = []; | |
| me.request_queue_ids = []; | |
| me.callback(0); | |
| }, true); | |
| }; | |
| /** | |
| * Call an Emscripten callback | |
| * @type {number} res | |
| */ | |
| HardDriveImage.prototype.callback = function (res) { | |
| fptr_vii(this.cb | 0, this.arg1 | 0, res | 0); | |
| }; | |
| /** | |
| * @param {string[]} paths | |
| * @param {function(object,Uint8Array[])} cb | |
| * | |
| * Note that all Uint8Arrays passed back to cb MUST be BLOCK_SIZE bytes long | |
| */ | |
| HardDriveImage.prototype.load = function (reqs, cb) { | |
| throw new Error("implement me"); | |
| }; | |
| /** | |
| * Initialize hard drive image | |
| * @param {string} arg | |
| * @param {function(Uint8Array)} cb | |
| */ | |
| HardDriveImage.prototype.init = function (arg, cb) { | |
| throw new Error("implement me"); | |
| }; | |
| /** | |
| * Convert a URL (i.e. os2/blk0000005a.bin) into a number (i.e. 0x5a) | |
| * @param {string} str | |
| * @return {number} | |
| */ | |
| function _url_to_blkid(str) { | |
| var parts = str.match(/blk([0-9a-f]{8})\.bin/); | |
| return parseInt(parts[1], 16) >>> 0; | |
| } | |
| /** | |
| * Create an "info.dat" file | |
| * @param {number} size | |
| * @param {number} blksize | |
| * @returns {Uint8Array} The data that would have been contained in info.dat | |
| */ | |
| function _construct_info(size, blksize) { | |
| var i32 = new Int32Array(2); | |
| i32[0] = size; | |
| i32[1] = blksize; | |
| return new Uint8Array(i32.buffer); | |
| } | |
| /** | |
| * ArrayBuffer-backed image | |
| * @param {ArrayBuffer} ab | |
| * @constructor | |
| * @extends HardDriveImage | |
| */ | |
| function ArrayBufferImage(ab) { | |
| this.data = new Uint8Array(ab); | |
| } | |
| ArrayBufferImage.prototype = new HardDriveImage(); | |
| /** | |
| * @param {string[]} paths | |
| * @param {function(object,Uint8Array[])} cb | |
| */ | |
| ArrayBufferImage.prototype.load = function (reqs, cb) { | |
| var data = []; | |
| for (var i = 0; i < reqs.length; i = i + 1 | 0) { | |
| // note to self: Math.log(256*1024)/Math.log(2) === 18 | |
| var blockoffs = (_url_to_blkid(i) << 18) >>> 0; | |
| data[i] = this.data.slice(blockoffs, (blockoffs + (256 << 10)) >>> 0); | |
| } | |
| setTimeout(function () { | |
| cb(null, data); | |
| }, 0); | |
| }; | |
| ArrayBufferImage.prototype.init = function (arg, cb) { | |
| var data = _construct_info(this.data.byteLength, 256 << 10); | |
| setTimeout(function () { | |
| cb(null, data); | |
| }, 0); | |
| }; | |
| /** | |
| * File API-backed image | |
| * @param {File} f | |
| * @constructor | |
| * @extends HardDriveImage | |
| */ | |
| function FileImage(f) { | |
| this.file = finalResponse; | |
| } | |
| FileImage.prototype = new HardDriveImage(); | |
| FileImage.prototype.load = function (reqs, cb) { | |
| console.log(reqs); | |
| // Ensure that loads are in order and consecutive, which speeds things up | |
| var blockBase = _url_to_blkid(reqs[0]); | |
| for (var i = 1; i < reqs.length; i = i + 1 | 0) | |
| if ((_url_to_blkid(reqs[i]) - i | 0) !== blockBase) throw new Error("non-consecutive reads"); | |
| var blocks = reqs.length; | |
| /** @type {File} */ | |
| var fileslice = this.file.slice((blockBase << 18) >>> 0, ((blockBase + blocks) << 18) >>> 0); | |
| var fr = new FileReader(); | |
| fr.onload = function () { | |
| var arr = []; | |
| for (var i = 0; i < reqs.length; i = i + 1 | 0) { | |
| // Slice a 256 KB chunk of the file | |
| arr.push(new Uint8Array(fr.result.slice(i << 18, (i + 1) << 18))); | |
| } | |
| cb(null, arr); | |
| }; | |
| fr.onerror = function (e) { | |
| cb(e, null); | |
| }; | |
| fr.onabort = function () { | |
| cb(new Error("filereader aborted"), null); | |
| }; | |
| fr.readAsArrayBuffer(fileslice); | |
| }; | |
| FileImage.prototype.init = function (arg, cb) { | |
| var data = _construct_info(this.file.size, 256 << 10); | |
| setTimeout(function () { | |
| cb(null, data); | |
| }, 0); | |
| }; | |
| /** | |
| * XHR-backed image | |
| * @constructor | |
| * @extends HardDriveImage | |
| */ | |
| function XHRImage() { | |
| } | |
| XHRImage.prototype = new HardDriveImage(); | |
| XHRImage.prototype.load = function (reqs, cb) { | |
| loadFiles(reqs, cb, true); | |
| }; | |
| XHRImage.prototype.init = function (arg, cb) { | |
| loadFiles([join_path(arg, "info.dat")], function (err, data) { | |
| if (err) throw err; | |
| cb(null, data[0]); | |
| }); | |
| }; | |
| var image_backends = { | |
| "file": FileImage, | |
| "ab": ArrayBufferImage | |
| }; | |
| // ======================================================================== | |
| // Useful functions | |
| // ======================================================================== | |
| var _allocs = []; | |
| /** | |
| * Allocates a patch of memory in Emscripten. Returns the address pointer | |
| * @param {number} size | |
| * @return {number} Address | |
| */ | |
| function alloc(size) { | |
| var n = Module["_malloc"](size); | |
| _allocs.push(n); | |
| return n; | |
| } | |
| var u8 = null, u16 = null, i32 = null; | |
| /** | |
| * Copy a string into memory | |
| * @param {number} dest | |
| * @param {string} src | |
| */ | |
| function strcpy(dest, src) { | |
| var srclen = src.length | 0; | |
| for (var i = 0; i < srclen; i = i + 1 | 0) | |
| u8[i + dest | 0] = src.charCodeAt(i); | |
| u8[dest + srclen | 0] = 0; // End with NULL terminator | |
| } | |
| /** | |
| * Copy a Uint8Array into memory | |
| * @param {number} dest | |
| * @param {Uint8Array} src | |
| */ | |
| function memcpy(dest, src) { | |
| var srclen = src.length | 0; | |
| for (var i = 0; i < srclen; i = i + 1 | 0) | |
| u8[i + dest | 0] = src[i | 0]; | |
| } | |
| /** | |
| * Frees every single patch of memory we have reserved with alloc() | |
| */ | |
| function gc() { | |
| var free = Module["_free"]; | |
| for (var i = 0; i < _allocs.length; i = i + 1 | 0) free(_allocs[i]); | |
| _allocs = []; | |
| } | |
| /** | |
| * Call an Emscripten function pointer with the signature void func(int, int); | |
| * @param {number} cb The function pointer itself | |
| * @param {number} cb_ptr The first argument | |
| * @param {number} arg2 The second argument | |
| */ | |
| function fptr_vii(cb, cb_ptr, arg2) { | |
| dynCall_vii(cb, cb_ptr, arg2); | |
| } | |
| /** | |
| * Copy a string into JavaScript | |
| * @param {number} src | |
| */ | |
| function readstr(src) { | |
| var str = ""; | |
| while (u8[src] !== 0) { | |
| str += String.fromCharCode(u8[src]); | |
| src = src + 1 | 0; | |
| } | |
| return str; | |
| } | |
| // Returns a pointer to an Emscripten-compiled function | |
| function wrap(nm) { | |
| return Module["_" + nm]; | |
| } | |
| /** | |
| * Join two fragments of a path together | |
| * @param {string} a The first part of the path | |
| * @param {string} b The second part of the path | |
| */ | |
| function join_path(a, b) { | |
| if (b.charAt(0) !== "/") | |
| b = "/" + b; | |
| if (a.charAt(a.length - 1 | 0) === "/") | |
| a = a.substring(0, a.length - 1 | 0); | |
| return a + b; //normalize_path(a + b); | |
| } | |
| // Some more savestate-related functions | |
| /** | |
| * Load file from file cache | |
| * @param {number} pathstr Pointer to path string | |
| * @param {number} addr Address to load the data | |
| */ | |
| window["loadFile"] = function (pathstr, addr) { | |
| var path = readstr(pathstr); | |
| var data = savestate_files[path]; | |
| if (!data) throw new Error("ENOENT: " + path); | |
| memcpy(addr, data); | |
| return addr; | |
| }; | |
| /** | |
| * Load file from file cache and allocate a buffer to store it in. | |
| * It is the responsibility of the caller to free the memory. | |
| * @param {number} pathstr Pointer to path string | |
| * @param {number} addr Address to load the data | |
| */ | |
| window["loadFile2"] = function (pathstr, addr) { | |
| var path = readstr(pathstr); | |
| var data = savestate_files[path]; | |
| if (!data) throw new Error("ENOENT: " + path); | |
| var len = data.length; | |
| var addr = alloc(len); | |
| _allocs.pop(); | |
| memcpy(addr, data); | |
| console.log(path, data, addr); | |
| return addr; | |
| }; | |
| if (typeof module !== "undefined" && module["exports"]) | |
| module["exports"] = Halfix; | |
| else | |
| global["Halfix"] = Halfix; | |
| })(typeof window !== "undefined" ? window : global); |