diff --git a/cue.jam b/cue.jam index 83f6101..ad189ec 100644 --- a/cue.jam +++ b/cue.jam @@ -21,15 +21,8 @@ pub extern fn malloc(size: u64) *mut[] u8; pub extern fn free(ptr: *mut[] u8); pub extern fn memset(dst: *mut[] u8, c: i32, n: u64) *mut[] u8; -pub extern fn fopen(path: *const[] u8, mode: *const[] u8) *mut[] u8; -pub extern fn fclose(file: *mut[] u8) i32; -pub extern fn fread(buf: *mut[] u8, size: u64, n: u64, file: *mut[] u8) u64; -pub extern fn fseek(file: *mut[] u8, off: i64, whence: i32) i32; -pub extern fn ftell(file: *mut[] u8) i64; -pub extern fn fgetc(file: *mut[] u8) i32; -pub extern fn feof(file: *mut[] u8) i32; -pub extern fn access(path: *const[] u8, mode: i32) i32; -pub extern fn realpath(path: *const[] u8, resolved: *mut[] u8) *mut[] u8; +// All file I/O goes through std.fs — no raw libc file ABI in this module. +const { File, exists, canonicalize } = import("std/fs"); // Track-mode enum (psxe cue.h subset we actually use). pub const TRACK_MODE_UNKNOWN: u32 = 0; @@ -49,7 +42,7 @@ pub const SECTOR: u32 = 2352; // Internal types — not exported (other modules treat Cue as an opaque // `*mut[] u8` blob and use the cueXxx functions to interact). pub const CueFile = struct { - file: *mut[] u8, // libc FILE* (opaque) + file: File, // std.fs.File handle sectors: u32, // file_size / SECTOR startLba: u32, // LBA where this file's sector 0 lands pathLo: u32, // path stored separately, see Cue.pathBuf @@ -76,8 +69,8 @@ pub const Cue = struct { pathBuf: *mut[] u8, pathBufLen: u32, pathBufCap: u32, - // Parser scratch state (psxe-style fgetc loop). - file: *mut[] u8, + // Parser scratch state (psxe-style readByte loop). + file: File, cur: u32, // current character (sentinel-extended) rootPath: *mut[] u8, // directory of the .cue, used to resolve BIN paths rootLen: u32, @@ -115,10 +108,10 @@ pub fn parseMsf(raw: *mut[] u8) u32 { if (!isDigit(c[0].cur)) { return 0; } const m: u32 = parseNumber(raw); if (c[0].cur != 0x3A) { return 0; } // ':' - c[0].cur = fgetc(c[0].file) as u32 & 0xFF; + c[0].cur = c[0].file.readByte() as u32 & 0xFF; const s: u32 = parseNumber(raw); if (c[0].cur != 0x3A) { return 0; } - c[0].cur = fgetc(c[0].file) as u32 & 0xFF; + c[0].cur = c[0].file.readByte() as u32 & 0xFF; const f: u32 = parseNumber(raw); return f + (s * 75) + (m * 4500); } @@ -128,15 +121,15 @@ pub fn parseNumber(raw: *mut[] u8) u32 { var n: u32 = 0; while (isDigit(c[0].cur)) { n = n * 10 + (c[0].cur - 0x30); - c[0].cur = fgetc(c[0].file) as u32 & 0xFF; + c[0].cur = c[0].file.readByte() as u32 & 0xFF; } return n; } pub fn skipWs(raw: *mut[] u8) { var c: *mut[] Cue = raw as *mut[] Cue; - while (isSpace(c[0].cur) && feof(c[0].file) == 0) { - c[0].cur = fgetc(c[0].file) as u32 & 0xFF; + while (isSpace(c[0].cur) && !c[0].file.eof()) { + c[0].cur = c[0].file.readByte() as u32 & 0xFF; } } @@ -148,7 +141,7 @@ pub fn parseKeyword(raw: *mut[] u8, buf: *mut[] u8) u32 { && n < 15) { buf[n] = c[0].cur as u8; n = n + 1; - c[0].cur = fgetc(c[0].file) as u32 & 0xFF; + c[0].cur = c[0].file.readByte() as u32 & 0xFF; } buf[n] = 0; return n; @@ -212,7 +205,7 @@ pub fn cueFree(raw: *mut[] u8) { var i: u32 = 0; while (i < c[0].fileCount) { if (c[0].files[i].sectors != 0) { - fclose(c[0].files[i].file); + c[0].files[i].file.close(); } i = i + 1; } @@ -365,8 +358,9 @@ pub fn cueRead(raw: *mut[] u8, lba: u32, buf: *mut[] u8) i32 { while (j < 11) { buf[j] = 0xFF; j = j + 1; } return 1; } - fseek(c[0].files[fi].file, off, 0); - const got: u64 = fread(buf, 1, SECTOR as u64, c[0].files[fi].file); + var tf: File = c[0].files[fi].file; + tf.seek(off, 0); + const got: u64 = tf.read(buf[0..SECTOR]); if (got != SECTOR as u64) { return 0; } return 1; } @@ -390,13 +384,13 @@ pub fn cueQuery(raw: *mut[] u8, lba: u32) u32 { // Stream the .cue text char-by-char (psxe's fgetc model). Each keyword // dispatches to its handler; FILE pushes onto files[], TRACK pushes // onto tracks[] with the most recent file, INDEX records its MSF. -pub fn cueParse(raw: *mut[] u8, path: *const[] u8) i32 { +pub fn cueParse(raw: *mut[] u8, path: []u8) i32 { var c: *mut[] Cue = raw as *mut[] Cue; - var rb: []u8 = "rb"; - c[0].file = fopen(path, rb.ptr); - var afmt: []u8 = "rb"; - if (access(path, 4) != 0) { return 1; } - c[0].cur = fgetc(c[0].file) as u32 & 0xFF; + match (File.open(path)) { + Some(opened) { c[0].file = opened; } + None { return 1; } + } + c[0].cur = c[0].file.readByte() as u32 & 0xFF; // Resolve any symlinks in the .cue path so the rootPath we extract is // the directory of the actual file. Without this, a symlinked // `./game.cue` whose target lives elsewhere produces rootPath="./", @@ -416,7 +410,7 @@ pub fn cueParse(raw: *mut[] u8, path: *const[] u8) i32 { resolved[pi] = path[pi]; pi = pi + 1; } - realpath(path, resolved.asMutPtr()); + canonicalize(path, resolved.asMutPtr()); // Compute root path = resolved path up to last '/' or '\\'. var i: u32 = 0; var lastSlash: u32 = 0; @@ -437,7 +431,7 @@ pub fn cueParse(raw: *mut[] u8, path: *const[] u8) i32 { // plus a little slack; parseKeyword bounds itself to this size. var kwbuf: [16]u8 = [0; 16]; - while (feof(c[0].file) == 0) { + while (!c[0].file.eof()) { const kn: u32 = parseKeyword(raw, kwbuf.asMutPtr()); if (kn == 0) { break; } const kw: u32 = kwId(kwbuf.asMutPtr()); @@ -450,14 +444,14 @@ pub fn cueParse(raw: *mut[] u8, path: *const[] u8) i32 { } else { // REM / PREGAP / POSTGAP / FLAGS — skip to end of line. while (c[0].cur != 0x0A && c[0].cur != 0x0D - && feof(c[0].file) == 0) { - c[0].cur = fgetc(c[0].file) as u32 & 0xFF; + && !c[0].file.eof()) { + c[0].cur = c[0].file.readByte() as u32 & 0xFF; } } skipWs(raw); } // kwbuf / resolved auto-drop at function-scope exit. - fclose(c[0].file); + c[0].file.close(); cueComputeRanges(raw); return 0; } @@ -467,7 +461,7 @@ pub fn cueParseFile(raw: *mut[] u8) { var c: *mut[] Cue = raw as *mut[] Cue; skipWs(raw); if (c[0].cur != 0x22) { return; } // expect '"' - c[0].cur = fgetc(c[0].file) as u32 & 0xFF; + c[0].cur = c[0].file.readByte() as u32 & 0xFF; // 512 bytes covers any realistic path made of rootPath + name. // Loop below bounds itself to 510 to leave room for NUL. @@ -478,30 +472,33 @@ pub fn cueParseFile(raw: *mut[] u8) { nameBuf[ni] = c[0].rootPath[ni]; ni = ni + 1; } - while (c[0].cur != 0x22 && feof(c[0].file) == 0 && ni < 510) { + while (c[0].cur != 0x22 && !c[0].file.eof() && ni < 510) { nameBuf[ni] = c[0].cur as u8; ni = ni + 1; - c[0].cur = fgetc(c[0].file) as u32 & 0xFF; + c[0].cur = c[0].file.readByte() as u32 & 0xFF; } nameBuf[ni] = 0; - c[0].cur = fgetc(c[0].file) as u32 & 0xFF; // consume closing '"' + c[0].cur = c[0].file.readByte() as u32 & 0xFF; // consume closing '"' // Skip type keyword (BINARY / WAVE / etc.) — we only treat as binary. - while (isSpace(c[0].cur)) { c[0].cur = fgetc(c[0].file) as u32 & 0xFF; } - while (isAlpha(c[0].cur)) { c[0].cur = fgetc(c[0].file) as u32 & 0xFF; } + while (isSpace(c[0].cur)) { c[0].cur = c[0].file.readByte() as u32 & 0xFF; } + while (isAlpha(c[0].cur)) { c[0].cur = c[0].file.readByte() as u32 & 0xFF; } if (c[0].fileCount >= MAX_FILES) { return; } - var rb: []u8 = "rb"; - var f: *mut[] u8 = fopen(nameBuf.asPtr(), rb.ptr); - if (access(nameBuf.asPtr(), 4) != 0) { return; } - // Size by seeking to end. - fseek(f, 0, 2); - const sz: i64 = ftell(f); - fseek(f, 0, 0); - const fi: u32 = c[0].fileCount; - c[0].files[fi].file = f; - c[0].files[fi].sectors = (sz as u32) / SECTOR; - c[0].fileCount = fi + 1; + // nameBuf is a NUL-terminated path of length `ni`; view it as []u8. + var namePtr: *const[] u8 = nameBuf.asPtr(); + var nameSlice: []u8 = namePtr[0..ni]; + if (!exists(nameSlice)) { return; } + match (File.open(nameSlice)) { + Some(f) { + const sz: u64 = f.size(); + const fi: u32 = c[0].fileCount; + c[0].files[fi].file = f; + c[0].files[fi].sectors = (sz as u32) / SECTOR; + c[0].fileCount = fi + 1; + } + None { return; } + } // nameBuf auto-drops on function-scope exit. } diff --git a/disc.jam b/disc.jam index 1360fb9..a33d360 100644 --- a/disc.jam +++ b/disc.jam @@ -25,18 +25,15 @@ const { print } = import("std").fmt; pub extern fn malloc(size: u64) *mut[] u8; pub extern fn free(ptr: *mut[] u8); pub extern fn memset(dst: *mut[] u8, c: i32, n: u64) *mut[] u8; -pub extern fn fopen(path: *const[] u8, mode: *const[] u8) *mut[] u8; -pub extern fn fclose(file: *mut[] u8) i32; -pub extern fn fread(buf: *mut[] u8, size: u64, n: u64, file: *mut[] u8) u64; -pub extern fn fseek(file: *mut[] u8, off: i64, whence: i32) i32; pub extern fn printf(format: *const[] u8, ...) i32; -pub extern fn access(path: *const[] u8, mode: i32) i32; +// File I/O exclusively via std.fs — no raw libc file ABI in this module. +const { File, exists } = import("std/fs"); const SECTOR_SIZE: u32 = 2352; const LEAD_IN_SECTORS: u32 = 150; const Disc = struct { - file: *mut[] u8, // libc FILE* (opaque) — set when raw .bin loaded + file: File, // std.fs.File — valid when a raw .bin is loaded loaded: u8, // 1 once discOpen succeeds isCue: u8, // 1 if cue-loaded (cuePtr is valid) pad1: u8, @@ -56,7 +53,7 @@ pub fn discFree(d: *mut[] Disc) { if (d[0].isCue != 0) { cueFree(d[0].cuePtr); } else if (d[0].loaded != 0) { - fclose(d[0].file); + d[0].file.close(); } free(d as *mut[] u8); } @@ -83,10 +80,9 @@ pub fn pathEndsWithCue(p: []u8) bool { // opens its own .bin files); everything else is treated as a raw // single-track Mode2/2352 .bin. pub fn discOpen(d: *mut[] Disc, path: []u8) i32 { - if (access(path.ptr, 4) != 0) { return 0; } if (pathEndsWithCue(path)) { var c: *mut[] u8 = cueAlloc(); - if (cueParse(c, path.ptr) != 0) { + if (cueParse(c, path) != 0) { cueFree(c); return 0; } @@ -100,13 +96,17 @@ pub fn discOpen(d: *mut[] Disc, path: []u8) i32 { print("{fmtAddress} {pathAddress} {trackCount}\n"); return 1; } - var mode: []u8 = "rb"; - var f: *mut[] u8 = fopen(path.ptr, mode.ptr); - d[0].file = f; - d[0].loaded = 1; - var fmt: []u8 = "Opened disc image: %s\n"; - printf(fmt.ptr, path.ptr); - return 1; + match (File.open(path)) { + Some(f) { + d[0].file = f; + d[0].loaded = 1; + var fmt: []u8 = "Opened disc image: %.*s\n"; + printf(fmt.ptr, path.len as i32, path.ptr); + return 1; + } + None { return 0; } + } + return 0; } // Read one 2352-byte sector at `lba` (absolute, including lead-in) into @@ -131,8 +131,9 @@ pub fn discRead(d: *mut[] Disc, lba: u32, buf: *mut[] u8) i32 { return 1; } const off: i64 = ((lba - LEAD_IN_SECTORS) as i64) * (SECTOR_SIZE as i64); - fseek(d[0].file, off, 0); - const got: u64 = fread(buf, 1, SECTOR_SIZE as u64, d[0].file); + var f: File = d[0].file; + f.seek(off, 0); + const got: u64 = f.read(buf[0..SECTOR_SIZE]); if (got != SECTOR_SIZE as u64) { return 0; } return 1; } diff --git a/main.jam b/main.jam index 554e374..1f4a285 100644 --- a/main.jam +++ b/main.jam @@ -44,7 +44,7 @@ const { sdlAudioOpen, sdlAudioClose } = import("sdl"); const { Vec } = import("std").collections; -const { File } = import("std/fs"); +const { File, exists } = import("std/fs"); const { Args, args, next } = import("std").process; extern fn malloc(size: u64) *mut[] u8; @@ -53,10 +53,10 @@ extern fn memset(dst: *mut[] u8, c: i32, n: u64) *mut[] u8; extern fn printf(format: *const[] u8, ...) i32; extern fn write(fd: i32, buf: *const[] u8, count: u64) i64; extern fn putchar(c: i32) i32; -// fopen / fclose / fread / fseek now come from std/fs (imported above). -extern fn access(path: *const[] u8, mode: i32) i32; +// File I/O (fopen/fread/fwrite/fclose/fseek/access) comes from std/fs +// (imported above) — loadExe/dumpVramPpm/fileExists resolve those there. +// fflush stays: it flushes stdout buffers, not a file. extern fn fflush(stream: u64) i32; -extern fn fwrite(buf: *const[] u8, size: u64, n: u64, file: *mut[] u8) u64; // SDL2 externs need to be re-declared here so the codegen for sdl.jam's // pub wrappers (which lives in this module's compilation context) can @@ -92,12 +92,12 @@ extern fn SDL_GetQueuedAudioSize(dev: u32) u32; const STDOUT: i32 = 1; fn fileExists(path: []u8) bool { - return access(path.ptr, 0) == 0; + return exists(path); } // Locally-redeclared shapes matching bus.jam / cpu.jam / sdl.jam. const Disc = struct { - file: *mut[] u8, + file: File, loaded: u8, isCue: u8, pad1: u8, @@ -216,52 +216,58 @@ fn readU32At(buf: *mut[] u8, off: u32) u32 { } fn loadExe(path: []u8, bus: Bus, regs: *mut[] u32, c: mut Cpu) { - var modeRb: []u8 = "rb"; - var f: *mut[] u8 = fopen(path.ptr, modeRb.ptr); - // 0x800 = 2 KB PSX-EXE header (fixed by the platform). Array - // literal [0; N] already zero-fills; no memset needed. - var hdr: [2048]u8 = [0; 2048]; - const hdrBytes: u64 = fread(hdr.asMutPtr(), 1, 0x800, f); - if (hdrBytes < 0x800) { - fclose(f); - var msg: []u8 = "loadExe: header read failed\n"; - write(STDOUT, msg.ptr, msg.len); - return; - } + match (File.open(path)) { + Some(f) { + // 0x800 = 2 KB PSX-EXE header (fixed by the platform). The + // [0; N] array literal already zero-fills; no memset needed. + var hdr: [2048]u8 = [0; 2048]; + var hp: *mut[] u8 = hdr.asMutPtr(); + const hdrBytes: u64 = f.read(hp[0..0x800]); + if (hdrBytes < 0x800) { + f.close(); + var msg: []u8 = "loadExe: header read failed\n"; + write(STDOUT, msg.ptr, msg.len); + return; + } - const initPc: u32 = readU32At(hdr.asMutPtr(), 0x10); - const initGp: u32 = readU32At(hdr.asMutPtr(), 0x14); - const ramDest: u32 = readU32At(hdr.asMutPtr(), 0x18); - const fileSz: u32 = readU32At(hdr.asMutPtr(), 0x1C); - const spBase: u32 = readU32At(hdr.asMutPtr(), 0x30); - const spOff: u32 = readU32At(hdr.asMutPtr(), 0x34); - - // Body is runtime-sized (length from the header), so stays Vec. - // Copy byte-by-byte into RAM: Jam doesn't have pointer arithmetic - // on many-item pointers, so we stream through a temp buffer. - var body: Vec(u8) = Vec(u8).withCapacity(fileSz); - fread(body.ptr, 1, fileSz as u64, f); - const dst: u32 = ramDest & 0x001FFFFF; - var ram: *mut[] u8 = busRamPtr(bus); - var i: u32 = 0; - while (i < fileSz) { - ram[dst + i] = body[i]; - i = i + 1; - } - fclose(f); - // body auto-drops at scope exit; hdr is stack-allocated. - c.pc = initPc; - c.nextPc = c.pc + 4; - gprWrite(regs, 28, initGp); - if (spBase != 0) { - const sp: u32 = spBase + spOff; - gprWrite(regs, 29, sp); - gprWrite(regs, 30, sp); - } + const initPc: u32 = readU32At(hdr.asMutPtr(), 0x10); + const initGp: u32 = readU32At(hdr.asMutPtr(), 0x14); + const ramDest: u32 = readU32At(hdr.asMutPtr(), 0x18); + const fileSz: u32 = readU32At(hdr.asMutPtr(), 0x1C); + const spBase: u32 = readU32At(hdr.asMutPtr(), 0x30); + const spOff: u32 = readU32At(hdr.asMutPtr(), 0x34); + + // Body is runtime-sized (length from the header). Read into a + // Vec(u8), then stream byte-by-byte into RAM. + var body: Vec(u8) = Vec(u8).withCapacity(fileSz); + var bp: *mut[] u8 = body.ptr; + f.read(bp[0..fileSz]); + const dst: u32 = ramDest & 0x001FFFFF; + var ram: *mut[] u8 = busRamPtr(bus); + var i: u32 = 0; + while (i < fileSz) { + ram[dst + i] = body[i]; + i = i + 1; + } + f.close(); + c.pc = initPc; + c.nextPc = c.pc + 4; + gprWrite(regs, 28, initGp); + if (spBase != 0) { + const sp: u32 = spBase + spOff; + gprWrite(regs, 29, sp); + gprWrite(regs, 30, sp); + } - var fmt: []u8 = "Loaded EXE: PC=%08X SP=%08X GP=%08X size=%u dest=%08X\n"; - printf(fmt.ptr, c.pc, gprRead(regs, 29), gprRead(regs, 28), - fileSz, ramDest); + var fmt: []u8 = "Loaded EXE: PC=%08X SP=%08X GP=%08X size=%u dest=%08X\n"; + printf(fmt.ptr, c.pc, gprRead(regs, 29), gprRead(regs, 28), + fileSz, ramDest); + } + None { + var msg: []u8 = "loadExe: cannot open file\n"; + write(STDOUT, msg.ptr, msg.len); + } + } } // Load the 512 KB BIOS image into bus BIOS memory via std.fs. The @@ -390,35 +396,39 @@ fn runOneFrame(c: mut Cpu, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { // we can confirm what the BIOS/game actually rendered without staring // at the SDL window. fn dumpVramPpm(path: []u8, vram: *mut[] u8) { - var modeWb: []u8 = "wb"; - var f: *mut[] u8 = fopen(path.ptr, modeWb.ptr); - var hdr: []u8 = "P6\n1024 512\n255\n"; - fwrite(hdr.ptr, 1, hdr.len, f); - // 3072 = 1024 px × 3 bytes (RGB24). VRAM width is fixed. - var row: [3072]u8 = [0; 3072]; - var y: u32 = 0; - while (y < 512) { - var x: u32 = 0; - while (x < 1024) { - const off: u32 = (y * 1024 + x) * 2; - const lo: u32 = vram[off] as u32; - const hi: u32 = vram[off + 1] as u32; - const px: u32 = lo | (hi << 8); - const r: u32 = ((px ) & 0x1F) << 3; - const g: u32 = ((px >> 5 ) & 0x1F) << 3; - const b: u32 = ((px >> 10) & 0x1F) << 3; - const di: u32 = x * 3; - row[di] = r as u8; - row[di + 1] = g as u8; - row[di + 2] = b as u8; - x = x + 1; + match (File.create(path)) { + Some(f) { + var hdr: []u8 = "P6\n1024 512\n255\n"; + f.write(hdr); + // 3072 = 1024 px × 3 bytes (RGB24). VRAM width is fixed. + var row: [3072]u8 = [0; 3072]; + var y: u32 = 0; + while (y < 512) { + var x: u32 = 0; + while (x < 1024) { + const off: u32 = (y * 1024 + x) * 2; + const lo: u32 = vram[off] as u32; + const hi: u32 = vram[off + 1] as u32; + const px: u32 = lo | (hi << 8); + const r: u32 = ((px ) & 0x1F) << 3; + const g: u32 = ((px >> 5 ) & 0x1F) << 3; + const b: u32 = ((px >> 10) & 0x1F) << 3; + const di: u32 = x * 3; + row[di] = r as u8; + row[di + 1] = g as u8; + row[di + 2] = b as u8; + x = x + 1; + } + var rp: *const[] u8 = row.asPtr(); + f.write(rp[0..3072]); + y = y + 1; + } + f.close(); + var msg: []u8 = "Wrote VRAM dump: %.*s\n"; + printf(msg.ptr, path.len as i32, path.ptr); } - fwrite(row.asPtr(), 1, 1024 * 3, f); - y = y + 1; + None { } } - fclose(f); - var msg: []u8 = "Wrote VRAM dump: %s\n"; - printf(msg.ptr, path.ptr); } // ---------- main -------------------------------------------------------- @@ -440,7 +450,7 @@ fn main() { // back to the same file so saves survive across runs. Mirrors // psxe's psx_mcd_init / psx_mcd_destroy lifecycle. var mcdPath: []u8 = "./slot1.mcd"; - if (mcdRamLoad(bus.mcdRam, mcdPath) != 0) { + if (mcdRamLoad(bus.mcdRam, mcdPath)) { var loadMsg: []u8 = "Loaded memcard: ./slot1.mcd\n"; write(STDOUT, loadMsg.ptr, loadMsg.len); } diff --git a/mcd.jam b/mcd.jam index 0ce9d60..72aa7cc 100644 --- a/mcd.jam +++ b/mcd.jam @@ -24,11 +24,8 @@ pub extern fn malloc(size: u64) *mut[] u8; pub extern fn free(ptr: *mut[] u8); pub extern fn memset(dst: *mut[] u8, c: i32, n: u64) *mut[] u8; -pub extern fn fopen(path: *const[] u8, mode: *const[] u8) *mut[] u8; -pub extern fn fclose(file: *mut[] u8) i32; -pub extern fn fread(buf: *mut[] u8, size: u64, n: u64, file: *mut[] u8) u64; -pub extern fn fwrite(buf: *const[] u8, size: u64, n: u64, file: *mut[] u8) u64; -pub extern fn access(path: *const[] u8, mode: i32) i32; +// File I/O via std.fs (its libc externs are the single source). +const { File } = import("std/fs"); const MCD_MEMORY_SIZE: u64 = 0x20000; // 128 KB @@ -248,23 +245,28 @@ pub fn mcdRamFree(r: *mut[] u8) { free(r); } // Read a 128 KB memory-card image from `path` into `ram`. Returns 1 on // success, 0 if the file is absent or unreadable. We use access(R_OK=4) // first because Jam v0.1.0 can't null-check fopen's return. -pub fn mcdRamLoad(ram: *mut[] u8, path: []u8) i32 { - if (access(path.ptr, 4) != 0) { return 0; } - var modeRb: []u8 = "rb"; - var f: *mut[] u8 = fopen(path.ptr, modeRb.ptr); - const got: u64 = fread(ram, 1, MCD_MEMORY_SIZE, f); - fclose(f); - if (got != MCD_MEMORY_SIZE) { return 0; } - return 1; +pub fn mcdRamLoad(ram: *mut[] u8, path: []u8) bool { + match (File.open(path)) { + Some(f) { + const got: u64 = f.read(ram[0..MCD_MEMORY_SIZE]); + f.close(); + return got == MCD_MEMORY_SIZE; + } + None { return false; } + } + return false; } // Write the 128 KB memory-card image at `ram` to `path`. Used at // shutdown so player progress isn't lost across emulator runs. -pub fn mcdRamSave(ram: *mut[] u8, path: []u8) i32 { - var modeWb: []u8 = "wb"; - var f: *mut[] u8 = fopen(path.ptr, modeWb.ptr); - const put: u64 = fwrite(ram, 1, MCD_MEMORY_SIZE, f); - fclose(f); - if (put != MCD_MEMORY_SIZE) { return 0; } - return 1; +pub fn mcdRamSave(ram: *mut[] u8, path: []u8) bool { + match (File.create(path)) { + Some(f) { + const put: u64 = f.write(ram[0..MCD_MEMORY_SIZE]); + f.close(); + return put == MCD_MEMORY_SIZE; + } + None { return false; } + } + return false; } diff --git a/tests.jam b/tests.jam index e148b75..a335249 100644 --- a/tests.jam +++ b/tests.jam @@ -37,11 +37,8 @@ const { Mcd } = import("mcd"); const { Mdec } = import("mdec"); const { busBiosPtr } = import("bus"); -// libc for the headless boot harness. -pub extern fn fopen(path: *const[] u8, mode: *const[] u8) *mut[] u8; -pub extern fn fclose(file: *mut[] u8) i32; -pub extern fn fread(buf: *mut[] u8, size: u64, n: u64, file: *mut[] u8) u64; -pub extern fn access(path: *const[] u8, mode: i32) i32; +// File I/O via std.fs — no raw libc file ABI in the boot harness. +const { File, exists } = import("std/fs"); const C0_SR: u32 = 12; const SR_BEV: u32 = 0x00400000; @@ -49,7 +46,7 @@ const SR_BEV: u32 = 0x00400000; // Re-declare the same shapes as bus.jam / cpu.jam — structural equality // lets us pass values across module boundaries. const Disc = struct { - file: *mut[] u8, + file: File, loaded: u8, isCue: u8, pad1: u8, @@ -405,19 +402,22 @@ tfn psxLwlLwr() { assert(tLwlLwr(), 0xBBCCDD11); } const C0_PRID: u32 = 15; fn fileExists(path: []u8) bool { - return access(path.ptr, 0) == 0; + return exists(path); } fn emuLoadBios(bus: Bus) i32 { var path: []u8 = "SCPH1001.BIN"; - if (!fileExists(path)) { return 0; } - var modeRb: []u8 = "rb"; - var f: *mut[] u8 = fopen(path.ptr, modeRb.ptr); - var biosBuf: *mut[] u8 = busBiosPtr(bus); - const got: u64 = fread(biosBuf, 1, 0x80000, f); - fclose(f); - if (got != 0x80000) { return 0; } - return 1; + match (File.open(path)) { + Some(f) { + var biosPtr: *mut[] u8 = busBiosPtr(bus); + const got: u64 = f.read(biosPtr[0..0x80000]); + f.close(); + if (got != 0x80000) { return 0; } + return 1; + } + None { return 0; } + } + return 0; } const CYC_PER_SCANLINE: u32 = 2172;