Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions include/drive.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
#include "state.h"
#include <stdint.h>

#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
Expand Down
156 changes: 141 additions & 15 deletions libhalfix.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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);
Expand Down Expand Up @@ -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);
};
Expand Down Expand Up @@ -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:<imageId>:<hex>` and `halfix:meta:<imageId>`
* as written by @kernelforge/halfix-lab/src/disk.mjs (idb-keyval,
* DB "keyval-store" / store "keyval").
* Path convention: "idb:<imageId>" 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
};

// ========================================================================
Expand All @@ -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;
}
Expand Down
14 changes: 12 additions & 2 deletions makefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
);
Expand Down Expand Up @@ -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) {
Expand Down
6 changes: 5 additions & 1 deletion src/cpu/opcodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
34 changes: 26 additions & 8 deletions src/drive.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down
6 changes: 5 additions & 1 deletion src/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
16 changes: 12 additions & 4 deletions tools/imgsplit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)`);
}
Loading