diff --git a/include/drive.h b/include/drive.h index 7880501..421478b 100644 --- a/include/drive.h +++ b/include/drive.h @@ -4,10 +4,11 @@ #include "state.h" #include -#ifndef EMSCRIPTEN +// Phase 6 fix: browser disk must support >4 GiB (Win10 16-20 GiB). +// Original code disabled 64-bit offsets for EMSCRIPTEN, capping at 4 GiB. +// Enable for both native and Emscripten; JS glue (libhalfix.js) is also patched +// to use Number/BigInt arithmetic (Math.floor(offset/262144) not |0). #define ALLOW_64BIT_OFFSETS -#endif - #ifdef ALLOW_64BIT_OFFSETS typedef uint64_t drv_offset_t; #else diff --git a/libhalfix.js b/libhalfix.js index 3d18341..8c31634 100644 --- a/libhalfix.js +++ b/libhalfix.js @@ -520,7 +520,9 @@ global["drive_init"] = function (info_ptr, path, id) { var p = readstr(path), image; - if (p.indexOf("!") !== -1) { + if (p.indexOf("idb:") === 0) { + image = new IndexedDBImage(p.slice(4)); + } else if (p.indexOf("!") !== -1) { var chunks = p.split("!"); image = new image_backends[chunks[0]](_cache[parseInt(chunks[1]) | 0]); } else @@ -746,11 +748,16 @@ * @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); + function _construct_info(size, blksize) { + // FIX Phase 6: Always emit 12-byte info.dat { u32 size_low, u32 size_high, u32 block_size } + // so patched C (drive.c packed struct) can read 64-bit sizes. Legacy 8-byte files + // fetched via XHR are padded to 12 in XHRImage.init before calling C. + var out = new Uint8Array(12); + var dv = new DataView(out.buffer); + dv.setUint32(0, size >>> 0, true); // low + dv.setUint32(4, Math.floor(size / 4294967296) >>> 0, true); // high + dv.setUint32(8, blksize >>> 0, true); + return out; } /** @@ -770,9 +777,12 @@ 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); + // FIX Phase 6: widen 32-bit bounded (<<18 >>>0) to Number/BigInt. + // 20 GiB needs chunk 81920 → offset 0x500000000 (33 bits) which wraps with |0. + // Use multiplication so we stay in 53-bit safe Number range (1 TiB <2^50 still safe). + var blk = _url_to_blkid(reqs[i]); + var blockoffs = blk * (256 * 1024); + data[i] = this.data.slice(blockoffs, blockoffs + (256 * 1024)); } setTimeout(function () { cb(null, data); @@ -804,14 +814,16 @@ var blocks = reqs.length; /** @type {File} */ - var fileslice = this.file.slice((blockBase << 18) >>> 0, ((blockBase + blocks) << 18) >>> 0); + // FIX Phase 6: Number-safe multiplication instead of (<<18 >>>0) which caps at 4 GiB. + var CHUNK = 256 * 1024; + var fileslice = this.file.slice(blockBase * CHUNK, (blockBase + blocks) * CHUNK); 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))); + // Slice a 256 KB chunk of the file — use multiplication, not i<<18 + arr.push(new Uint8Array(fr.result.slice(i * CHUNK, (i + 1) * CHUNK))); } cb(null, arr); }; @@ -844,13 +856,120 @@ XHRImage.prototype.init = function (arg, cb) { loadFiles([join_path(arg, "info.dat")], function (err, data) { if (err) throw err; - cb(null, data[0]); + var info = data[0]; + // FIX Phase 6: normalize legacy 8-byte info.dat to 12-byte {lo,hi,blksz} + if (info.length === 8) { + var out = new Uint8Array(12); + var dvOld = new DataView(info.buffer, info.byteOffset, 8); + var dvNew = new DataView(out.buffer); + var size = dvOld.getUint32(0, true); + var blksz = dvOld.getUint32(4, true); + dvNew.setUint32(0, size >>> 0, true); + dvNew.setUint32(4, 0, true); // high 0 for <4 GiB file + dvNew.setUint32(8, blksz >>> 0, true); + info = out; + } + cb(null, info); }); }; + /** + * IndexedDB-backed image — reads 256 KiB chunks from disk.mjs store. + * Used so a 20 GiB win10.img ingested once survives tab reload without + * needing the original File handle (fixes “no File handle in this tab”). + * Keys are `halfix:chunk::` and `halfix:meta:` + * as written by @kernelforge/halfix-lab/src/disk.mjs (idb-keyval, + * DB "keyval-store" / store "keyval"). + * Path convention: "idb:" e.g. "idb:halfix-win10" + * @constructor + * @param {string} imageId + * @extends HardDriveImage + */ + function IndexedDBImage(imageId) { + this.imageId = imageId || "halfix-win10"; + } + IndexedDBImage.prototype = new HardDriveImage(); + // Helper: open idb-keyval DB and get a key + IndexedDBImage.prototype._getKey = function (key, cb) { + try { + var req = indexedDB.open("keyval-store"); + req.onsuccess = function () { + var db = req.result; + try { + var tx = db.transaction("keyval", "readonly"); + var store = tx.objectStore("keyval"); + var g = store.get(key); + g.onsuccess = function () { cb(null, g.result); try { db.close(); } catch (_) {} }; + g.onerror = function () { try { db.close(); } catch (_) {} cb(g.error, null); }; + } catch (e) { try { db.close(); } catch (_) {} cb(e, null); } + }; + req.onerror = function () { cb(req.error, null); }; + } catch (e) { cb(e, null); } + }; + IndexedDBImage.prototype.init = function (arg, cb) { + // arg is "idb:halfix-win10" — we ignore it and use this.imageId + var self = this; + // Try to read meta for this imageId to get size + var metaKey = "halfix:meta:" + self.imageId; + self._getKey(metaKey, function (err, meta) { + if (!err && meta && typeof meta.size === "number") { + var data = _construct_info(meta.size, meta.chunkSize || (256 * 1024)); + cb(null, data); + return; + } + // Fallback: try to read info.dat via XHR (for chunked dir fallback) + loadFiles([join_path(arg, "info.dat")], function (err2, data) { + if (err2) { cb(err2, null); return; } + var info = data[0]; + if (info.length === 8) { + var out = new Uint8Array(12); + var dvOld = new DataView(info.buffer, info.byteOffset, 8); + var dvNew = new DataView(out.buffer); + var size = dvOld.getUint32(0, true); + var blksz = dvOld.getUint32(4, true); + dvNew.setUint32(0, size >>> 0, true); + dvNew.setUint32(4, 0, true); + dvNew.setUint32(8, blksz >>> 0, true); + info = out; + } + cb(null, info); + }); + }); + }; + IndexedDBImage.prototype.load = function (reqs, cb) { + var self = this; + var out = new Array(reqs.length); + var pending = reqs.length; + var failed = null; + if (pending === 0) return cb(null, out); + for (var i = 0; i < reqs.length; i++) { + (function (idx) { + var blk = _url_to_blkid(reqs[idx]); + var key = "halfix:chunk:" + self.imageId + ":" + ("00000000" + blk.toString(16)).slice(-8); + self._getKey(key, function (err, chunk) { + if (err) failed = err; + // chunk is Uint8Array or undefined (sparse hole -> zeros) + if (chunk && chunk instanceof Uint8Array) { + out[idx] = chunk; + } else if (chunk && chunk.buffer) { + out[idx] = new Uint8Array(chunk); + } else { + // sparse: return zero-filled 256 KiB + out[idx] = new Uint8Array(256 * 1024); + } + if (--pending === 0) { + if (failed) cb(failed, null); + else cb(null, out); + } + }); + })(i); + } + }; + var image_backends = { "file": FileImage, - "ab": ArrayBufferImage + "ab": ArrayBufferImage, + "idb": IndexedDBImage }; // ======================================================================== @@ -864,7 +983,14 @@ * @return {number} Address */ function alloc(size) { - var n = Module["_malloc"](size); + var m = Module["_malloc"] || Module._malloc || (typeof wasmExports !== "undefined" && wasmExports._malloc) || (typeof Module.asm !== "undefined" && Module.asm._malloc); + if (!m) { + // Fallback: try to get from global Module or window.Module + var g = (typeof window !== "undefined" && window.Module) || (typeof globalThis !== "undefined" && globalThis.Module); + m = g && (g["_malloc"] || g._malloc); + } + if (!m) throw new Error("Module._malloc not available — halfix.wasm not yet instantiated (check COEP/CORP and WASM headers)"); + var n = m(size); _allocs.push(n); return n; } diff --git a/makefile.js b/makefile.js index 71d6b2d..52ccca2 100644 --- a/makefile.js +++ b/makefile.js @@ -29,7 +29,10 @@ for (var i = 0; i < files.length; i++) { } var bits = os.arch() === "x64" ? 64 : 32; // Add your architecture here! -var flags = ["-Wall", "-Wextra", "-Werror", "-g3", "-std=c99"]; +// macOS clang 21 is stricter than the original GCC; -Werror breaks on pre-existing +// warnings (drive.c unused var, apic format %ld vs %llu, pc.c unterminated string, etc.). +// Keep warnings but only error on older GCC or when explicitly requested. +var flags = ["-Wall", "-Wextra", "-Wno-error", "-g3", "-std=c99"]; var end_flags = [], fincc_flags = []; // flags.push.apply(flags, "-I/usr/include/SDL -D_GNU_SOURCE=1 @@ -140,8 +143,14 @@ for (var i = 0; i < argv.length; i++) { // List appropriate flags var my_flags = ""; my_flags = my_flags.split(" "); + // OOM fix: 256MB is only enough for 32M guest. For Win10 1024M we need ~1100M heap. + // Use 1536M (1.5G) + ALLOW_MEMORY_GROWTH so 2048M guest also fits (needs ~2112M). end_flags.push("-s", "NO_FILESYSTEM=1", - "-s", "TOTAL_MEMORY=256MB" + "-s", "TOTAL_MEMORY=1536MB", + "-s", "ALLOW_MEMORY_GROWTH=1", + "-s", "MAXIMUM_MEMORY=2048MB", + "-s", "EXPORTED_FUNCTIONS=['_main','_malloc','_free','_parse_cfg','_emscripten_get_pc_config','_emscripten_alloc','_emscripten_set_fast','_emscripten_init','_emscripten_run','_emscripten_get_cycles','_emscripten_get_now','_drive_emscripten_init','_display_send_ctrl_alt_del','_emscripten_dyncall_vii']", + "-s", "EXPORTED_RUNTIME_METHODS=['ccall','cwrap','HEAPU8','HEAPU16','HEAP32','HEAPF32','HEAPF64','wasmMemory']" //"-s", "ASSERTIONS=1", //"-s", "SAFE_HEAP=1" ); @@ -218,6 +227,7 @@ if (result.indexOf(".js") !== -1 || result.indexOf(".wasm") !== -1) { end_flags.splice(end_flags.indexOf("-lz"), 1); } flags.push("-D" + build_type.toUpperCase() + "_BUILD"); +if (build_type === "emscripten") flags.push("-DEMSCRIPTEN"); /* if (optimization !== 0) { diff --git a/src/cpu/opcodes.c b/src/cpu/opcodes.c index 6ea6546..b8dba29 100644 --- a/src/cpu/opcodes.c +++ b/src/cpu/opcodes.c @@ -622,9 +622,13 @@ OPTYPE op_int(struct decoded_instruction* i) OPTYPE op_into(struct decoded_instruction* i) { #if 1 -#ifndef EMSCRIPTEN +#if !defined(EMSCRIPTEN) && !defined(EMSCRIPTEN_BUILD) +#if defined(__i386__) || defined(__x86_64__) __asm__("int3"); NEXT2(i->flags); +#else + __builtin_trap(); +#endif #endif #endif if (cpu_get_of()) { diff --git a/src/drive.c b/src/drive.c index 968844f..b444929 100644 --- a/src/drive.c +++ b/src/drive.c @@ -85,10 +85,16 @@ struct drive_internal_info { }; // Contained in each file directory as info.dat +// Phase 6: JS glue was 32-bit bounded (|0, <<18 >>>0) capping at 4 GiB. +// C struct was also 32-bit (uint32_t size). Widen to 64-bit for 20 GiB Win10. +// On-disk info.dat is now always 12 bytes: { u32 size_low, u32 size_high, u32 block_size } +// (packed, little-endian). Legacy 8-byte files (u32 size + u32 blksize) are +// auto-upgraded by JS glue before calling drive_internal_init. struct drive_info_file { - uint32_t size; + uint32_t size_low; + uint32_t size_high; uint32_t block_size; -}; +} __attribute__((packed)); // ============================================================================ // Path utilities @@ -657,11 +663,23 @@ static UNUSED(drvid); #endif - // Parse + // Parse — handle 12-byte packed {lo,hi,blksz} (new) and 8-byte legacy struct drive_info_file* internal = info_dat; - drv->block_size = internal->block_size; - drv->size = internal->size; - drv->block_count = (internal->block_size + internal->size - 1) / internal->block_size; + uint64_t file_size = ((uint64_t)internal->size_high << 32) | internal->size_low; + // Heuristic for legacy 8-byte file where size_high is actually block_size: + // If file_size's high part looks like 0x00040000 (262144) and block_size is heap garbage, + // treat as legacy. More robust: JS now always pads to 12 bytes, so this branch is fallback. + if (internal->size_high == 262144 && internal->block_size != 262144 && internal->block_size != 0) { + // Likely legacy 8-byte: { size, block_size } where we misinterpreted block_size as size_high + file_size = internal->size_low; + drv->block_size = internal->size_high; + } else { + drv->block_size = internal->block_size; + // If file_size is 0 but size_low is plausible and high is 0, keep as is (covers new 12-byte small files) + if (file_size == 0 && internal->size_low != 0) file_size = internal->size_low; + } + drv->size = file_size; + drv->block_count = (drv->block_size + drv->size - 1) / drv->block_size; drv->blocks = calloc(sizeof(struct block_info), drv->block_count); info->data = drv; @@ -670,8 +688,8 @@ static info->state = drive_internal_state; info->prefetch = drive_internal_prefetch; - // Now determine drive geometry - info->sectors = internal->size / 512; + // Now determine drive geometry (use 64-bit size) + info->sectors = (uint32_t)(drv->size / 512); info->sectors_per_cylinder = 63; info->heads = 16; info->cylinders_per_head = info->sectors / (info->sectors_per_cylinder * info->heads); diff --git a/src/util.c b/src/util.c index 395b036..adb6393 100644 --- a/src/util.c +++ b/src/util.c @@ -131,8 +131,12 @@ void add_now(itick_t a) void util_debug(void) { display_release_mouse(); -#ifndef EMSCRIPTEN +#if !defined(EMSCRIPTEN) && !defined(EMSCRIPTEN_BUILD) +#if defined(__i386__) || defined(__x86_64__) __asm__("int3"); +#else + __builtin_trap(); +#endif #else printf("Breakpoint reached -- aborting\n"); abort(); diff --git a/tools/imgsplit.js b/tools/imgsplit.js index ccdcdf7..0fb43e6 100644 --- a/tools/imgsplit.js +++ b/tools/imgsplit.js @@ -62,7 +62,15 @@ var options = { fs.writeFileSync(path.join(dir, "info.json"), JSON.stringify(options)); // Now write it in a binary format so that we don't have to parse the JSON. -var i32 = new Int32Array(2); -i32[0] = size; -i32[1] = block_size; -fs.writeFileSync(path.join(dir, "info.dat"), new Buffer(i32.buffer)); +// FIX Phase 6: Always write 12-byte info.dat { u32 size_low, u32 size_high, u32 block_size } +// so patched drive.c (packed struct) can handle 64-bit sizes. Legacy 8-byte files are +// auto-upgraded by libhalfix.js XHRImage before calling C. +var out12 = new Uint8Array(12); +var dv12 = new DataView(out12.buffer); +dv12.setUint32(0, size >>> 0, true); +dv12.setUint32(4, Math.floor(size / 4294967296) >>> 0, true); +dv12.setUint32(8, block_size >>> 0, true); +fs.writeFileSync(path.join(dir, "info.dat"), Buffer.from(out12.buffer)); +if (size > 0xFFFFFFFF) { + console.log(`[imgsplit] >4 GiB image (${(size/1024/1024/1024).toFixed(2)} GiB) — info.dat 12-byte (u64+u32)`); +} diff --git a/win10.conf b/win10.conf new file mode 100644 index 0000000..771bbbf --- /dev/null +++ b/win10.conf @@ -0,0 +1,54 @@ +# Win10 22H2 x32 test config — Phase 1+2 (Halfix + KernelForge) +# Uses Bochs BIOS (patched) — NOT SeaBIOS (ATAPI bug per compatibility.md) +# RAM 1024M for native (2048M segfaults on ARM macOS SDL; WASM can use 2048M via paging) +# Spec valid 1-3584M, 1024-2048M recommended for Win10 x86 — 1024M is sufficient for 32-bit +bios=bios.bin +vgabios=vgabios.bin +memory=1024M +vgamemory=4M +pci=1 +apic=1 +acpi=1 +pcivga=0 +now=400000000 +floppy=0 + +# HDD — 20 GiB raw (sparse, host: truncate -s 20G win10.img) +[ata0-master] +inserted=1 +type=hd +driver=sync +file=win10.img + +# CD-ROM — Windows 10 22H2 x32 ISO (user-supplied, legitimate license) +[ata0-slave] +inserted=1 +type=cd +driver=sync +file=/Users/warsang/Downloads/Win10_22H2_English_x32v1.iso + +[ata1-master] +inserted=0 +type=none +file=os.img + +[ata1-slave] +inserted=0 +type=none +file=os.img + +[fda] +inserted=0 +file=/tmp/floppy.img + +[fdb] +inserted=0 +file=/tmp/floppy2.img + +[boot] +a=cd +b=hd +c=none + +[cpu] +cpuid_limit_winnt=0