diff --git a/bus.jam b/bus.jam index 58f24f4..b317420 100644 --- a/bus.jam +++ b/bus.jam @@ -19,7 +19,8 @@ const { Vec } = import("std/collections"); const { Timer } = import("timer"); const { gteAlloc } = import("gte"); const { Cdrom } = import("cdrom"); -const { discAlloc, discFree } = import("disc"); +const { Disc, discAlloc, discClose } = import("disc"); +const { Box } = import("std/box"); const { padRead32, padRead16, padRead8, padWrite32, padWrite16, padWrite8, padAlloc } = import("pad"); @@ -30,17 +31,6 @@ const { Mdec, mdecInputAlloc, mdecOutputAlloc } = import("mdec"); const { Sio1 } = import("sio1"); const { Mcd, mcdRamAlloc } = import("mcd"); -// Disc shape — kept in sync with disc.jam so callers can reference the -// stored pointer via `bus.disc[0]`. -const Disc = struct { - file: *mut[] u8, - loaded: u8, - isCue: u8, - pad1: u8, - pad2: u8, - cuePtr: *mut[] u8, -}; - pub extern fn fflush(stream: u64) i32; // The Bus struct holds every piece of state we route through the @@ -60,7 +50,7 @@ pub const Bus = struct { timer: Timer, gte: Vec(u32), cdrom: Cdrom, - disc: *mut[] Disc, + disc: Box(Disc), pad: Vec(u8), spu: Vec(u8), spuram: Vec(u8), @@ -231,7 +221,9 @@ pub fn freeBus(b: Bus) { // b.timer is by-value; no free needed. // b.gte is a Vec(u32) — auto-dropped when `b` goes out of scope. // b.cdrom is by-value; no free needed. - discFree(b.disc); + // Close any open file/cue resources the Disc was holding; the Box + // itself auto-drops the backing memory when `b` goes out of scope. + discClose(b.disc.ptrMut()); // b.pad is a Vec(u8) — auto-dropped when `b` goes out of scope. // b.spu and b.spuram are Vec(u8) — auto-dropped when `b` goes out of scope. // b.mdec is by-value; no free needed. diff --git a/cdrom.jam b/cdrom.jam index 1029e9c..3418f4a 100644 --- a/cdrom.jam +++ b/cdrom.jam @@ -27,7 +27,7 @@ // then. const { irqRaise, IC_CDROM } = import("irq"); -const { discRead, discLoaded, +const { Disc, discRead, discLoaded, discTrackCount, discTrackLba, discTrackNumber, discQuery } = import("disc"); const { xaSectorIsAudio, xaSectorMatchesFilter, xaSectorIsStereo, @@ -36,16 +36,6 @@ const { const { spuPushCdSample } = import("spu"); -// Disc struct redeclaration (same shape as disc.jam). -const Disc = struct { - file: *mut[] u8, - loaded: u8, - isCue: u8, - pad1: u8, - pad2: u8, - cuePtr: *mut[] u8, -}; - const SECTOR_BYTES: u32 = 2352; // State machine states (psxe enum: IDLE/TX_RESP1/TX_RESP2/READ/PLAY). diff --git a/cpu.jam b/cpu.jam index 07d3ca1..52feca3 100644 --- a/cpu.jam +++ b/cpu.jam @@ -38,21 +38,13 @@ const { Cdrom } = import("cdrom"); const { Mcd } = import("mcd"); const { Sio1 } = import("sio1"); const { Mdec } = import("mdec"); +const { Disc } = import("disc"); pub extern fn putchar(c: i32) i32; const { Vec } = import("std/collections"); pub extern fn fflush(stream: u64) i32; -const Disc = struct { - file: *mut[] u8, - loaded: u8, - isCue: u8, - pad1: u8, - pad2: u8, - cuePtr: *mut[] u8, -}; - const Cpu = struct { pc: u32, nextPc: u32, diff --git a/cue.jam b/cue.jam index ad189ec..1f591f2 100644 --- a/cue.jam +++ b/cue.jam @@ -18,9 +18,15 @@ // than 99 tracks anyway. Each track stores absolute LBA start/end so the // disc-read path can look up by LBA. -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; +// Internal to cue.jam — the Cue struct is heap-allocated with a custom +// layout that doesn't fit the Box(T) / Vec(T) machinery cleanly (its +// inner Vec fields would still need explicit cleanup because they live +// inside heap bytes, not a stack-tracked container). Keeping the libc +// allocator extern-private here means no other jamstation module sees +// these symbols — defining-module resolution scopes them locally. +extern fn malloc(size: u64) *mut[] u8; +extern fn free(ptr: *mut[] u8); +extern fn memset(dst: *mut[] u8, c: i32, n: u64) *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"); @@ -334,6 +340,17 @@ pub fn cueFindTrack(raw: *mut[] u8, lba: u32) u32 { return c[0].trackCount; } +// Pregap sector layout: byte 0 = 0x00, bytes 1..10 = 0xFF (sync pattern), +// byte 11 = 0x00, the rest zero. Matches psxe cue.c:535-536. Local +// duplicate of disc.fillPregapSector — keeps cue.jam acyclic (disc +// imports cue, not the other way around). +fn fillPregap(buf: *mut[] u8) { + var i: u32 = 0; + while (i < SECTOR) { buf[i] = 0; i = i + 1; } + i = 1; + while (i < 11) { buf[i] = 0xFF; i = i + 1; } +} + // Read a 2352-byte sector at the given absolute LBA. Returns 1 on // success, 0 on out-of-range or read failure. pub fn cueRead(raw: *mut[] u8, lba: u32, buf: *mut[] u8) i32 { @@ -345,17 +362,13 @@ pub fn cueRead(raw: *mut[] u8, lba: u32, buf: *mut[] u8) i32 { return 0; } // Pregap: psxe cue.c:535-536 fills zero + sync at bytes [1..10]. - memset(buf, 0, SECTOR as u64); - var i: u32 = 1; - while (i < 11) { buf[i] = 0xFF; i = i + 1; } + fillPregap(buf); return 1; } const fi: u32 = c[0].tracks[ti].fileIdx; const off: i64 = ((lba - c[0].files[fi].startLba) as i64) * (SECTOR as i64); if (off < 0) { - memset(buf, 0, SECTOR as u64); - var j: u32 = 1; - while (j < 11) { buf[j] = 0xFF; j = j + 1; } + fillPregap(buf); return 1; } var tf: File = c[0].files[fi].file; diff --git a/disc.jam b/disc.jam index 8211e62..576247f 100644 --- a/disc.jam +++ b/disc.jam @@ -21,9 +21,7 @@ const { cueAlloc, cueFree, cueParse, cueRead, cueQuery, cueGetTrackCount, cueGetTrackLba, cueGetTrackNumber } = import("cue"); -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; +const { Box } = import("std/box"); // File I/O exclusively via std.fs — no raw libc file ABI in this module. const { File, exists } = import("std/fs"); const { print } = import("std/fmt"); @@ -31,7 +29,7 @@ const { print } = import("std/fmt"); const SECTOR_SIZE: u32 = 2352; const LEAD_IN_SECTORS: u32 = 150; -const Disc = struct { +pub const Disc = struct { 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) @@ -40,21 +38,34 @@ const Disc = struct { cuePtr: *mut[] u8, // opaque *mut[] Cue stored as bytes for struct-mirror compat }; -pub fn discAlloc() *mut[] Disc { - // Disc struct: file ptr (16) + 4*u8 + cuePtr (16) = ~40 with align. - // Bump to 48 to be safe. - var raw: *mut[] u8 = malloc(48); - memset(raw, 0, 48); - return raw as *mut[] Disc; +// Owning heap-allocated Disc. Bus stores it as a `Box(Disc)` and the +// auto-drop fires when the Bus goes out of scope. Internal helpers +// (discOpen / discRead / ...) still take a raw `*mut[] Disc` so the +// callsites can keep the `d[0].field` idiom; callers extract the raw +// pointer with `box.ptrMut()`. +pub fn discAlloc() Box(Disc) { + return Box(Disc).init(Disc { + file: File { handle: 0 as *mut[] u8 }, + loaded: 0, + isCue: 0, + pad1: 0, + pad2: 0, + cuePtr: 0 as *mut[] u8, + }); } -pub fn discFree(d: *mut[] Disc) { +// File-handle / cue-buffer cleanup that the auto-drop can't do on its own. +// Box's drop frees the Disc's backing memory; this hook closes any open +// resources the Disc was holding. +pub fn discClose(d: *mut[] Disc) { if (d[0].isCue != 0) { cueFree(d[0].cuePtr); + d[0].cuePtr = 0 as *mut[] u8; + d[0].isCue = 0; } else if (d[0].loaded != 0) { d[0].file.close(); } - free(d as *mut[] u8); + d[0].loaded = 0; } // Detect .cue extension on a path. Case-insensitive on the suffix bytes. @@ -111,8 +122,9 @@ pub fn discOpen(d: *mut[] Disc, path: []u8) i32 { // zero. Matches psxe cue.c:535-536. Some games inspect the sync pattern // when reading pregap sectors and would otherwise see all-zero data. pub fn fillPregapSector(buf: *mut[] u8) { - memset(buf, 0, SECTOR_SIZE as u64); - var i: u32 = 1; + var i: u32 = 0; + while (i < SECTOR_SIZE) { buf[i] = 0; i = i + 1; } + i = 1; while (i < 11) { buf[i] = 0xFF; i = i + 1; } } diff --git a/dma.jam b/dma.jam index 8c76489..f392053 100644 --- a/dma.jam +++ b/dma.jam @@ -18,23 +18,11 @@ const { Bus } = import("bus"); const { Cdrom } = import("cdrom"); const { Mdec } = import("mdec"); const { spuWrite16, spuRead16 } = import("spu"); +const { Disc } = import("disc"); const SPU_TFIFO_OFF: u32 = 0x1A8; -// Disc shape — keep in sync with disc.jam / bus.jam so the structural -// match holds at the call site. Members aren't accessed here; only the -// pointer is moved around. -const Disc = struct { - file: *mut[] u8, - loaded: u8, - isCue: u8, - pad1: u8, - pad2: u8, - cuePtr: *mut[] u8, -}; - - // d[0..20] = 7 channels × 3 regs (MADR/BCR/CHCR) // d[21] = DPCR // d[22] = DICR diff --git a/main.jam b/main.jam index c7d1109..5965596 100644 --- a/main.jam +++ b/main.jam @@ -20,7 +20,7 @@ const { const { Gpu } = import("gpu"); const { gteAlloc } = import("gte"); const { Cdrom } = import("cdrom"); -const { discAlloc, discOpen } = import("disc"); +const { Disc, discAlloc, discOpen } = import("disc"); const { dmaAlloc, dmaTickSpu, dmaUpdate } = import("dma"); const { padAlloc, padUpdate, padSetButtons } = import("pad"); const { spuAlloc, spuRamAlloc, psone_audio_cb, setU64, spuStepAdsrCpu, @@ -50,9 +50,6 @@ const { File, exists } = import("std/fs"); const { Args, args, next, exit } = import("std").process; const { print } = import("std/fmt"); -extern fn malloc(size: u64) *mut[] u8; -extern fn free(ptr: *mut[] u8); -extern fn memset(dst: *mut[] u8, c: i32, n: u64) *mut[] u8; extern fn write(fd: i32, buf: *const[] u8, count: u64) i64; extern fn putchar(c: i32) i32; // File I/O (fopen/fread/fwrite/fclose/fseek/access) comes from std/fs @@ -97,17 +94,7 @@ fn fileExists(path: []u8) bool { return exists(path); } -// Locally-redeclared shapes matching bus.jam / cpu.jam / sdl.jam. -const Disc = struct { - file: File, - loaded: u8, - isCue: u8, - pad1: u8, - pad2: u8, - cuePtr: *mut[] u8, -}; - - +// Locally-redeclared shapes matching cpu.jam / sdl.jam. const Cpu = struct { pc: u32, nextPc: u32, @@ -315,7 +302,7 @@ fn runOneFrame(c: mut Cpu, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { // psxe fires the GPU hblank/vblank events BEFORE the sysclk timer +2.) // 1. CDROM (psx.c:95) - bus.cdrom.update(bus.disc, bus.spu.ptr, bus.irq.ptr, cop0, delta); + bus.cdrom.update(bus.disc.ptrMut(), bus.spu.ptr, bus.irq.ptr, cop0, delta); // 2. GPU (psx.c:96) — psx_gpu_update: scanline window [0,3413], HBlank // [2560,3413]. The HBlank event (line++, GPUSTAT bit-31 odd/even, the @@ -382,12 +369,6 @@ fn runOneFrame(c: mut Cpu, bus: Bus, regs: *mut[] u32, cop0: *mut[] u32) { gpuBuf[35] = spuAcc; } -// Pre-fill VRAM with a soft gradient so the user sees the SDL pipe is -// alive even before the GPU emits a single pixel. -// (the prime helper is gone; main() inlines a memset for now since the -// gradient version is too slow in unoptimized builds — see issue -// notes in the README.) - // Dump the full 1024x512 VRAM as a binary PPM (P6 / 24-bit RGB) at // `path`. BGR555 → RGB888 expansion is done inline so the output is // viewable with any image viewer. Used as an end-of-run diagnostic so @@ -537,20 +518,20 @@ fn main() { pendingGameExe = true; var msg: []u8 = "EXE arg — booting BIOS first, will side-load at PC=80030000\n"; write(STDOUT, msg.ptr, msg.len); - } else if (discOpen(bus.disc, gameArg) != 0) { - bus.cdrom.attachDisc(bus.disc); + } else if (discOpen(bus.disc.ptrMut(), gameArg) != 0) { + bus.cdrom.attachDisc(bus.disc.ptrMut()); } else { var msg: []u8 = "Could not open game image from arg — running BIOS only\n"; write(STDOUT, msg.ptr, msg.len); } } else { if (fileExists(cuePath)) { - if (discOpen(bus.disc, cuePath) != 0) { - bus.cdrom.attachDisc(bus.disc); + if (discOpen(bus.disc.ptrMut(), cuePath) != 0) { + bus.cdrom.attachDisc(bus.disc.ptrMut()); } } else if (fileExists(binPath)) { - if (discOpen(bus.disc, binPath) != 0) { - bus.cdrom.attachDisc(bus.disc); + if (discOpen(bus.disc.ptrMut(), binPath) != 0) { + bus.cdrom.attachDisc(bus.disc.ptrMut()); } } if (fileExists(demoPath)) { @@ -572,7 +553,7 @@ fn main() { // is a debug pattern. var preMsg: []u8 = "Initializing SDL2...\n"; write(STDOUT, preMsg.ptr, preMsg.len); - var title: []u8 = "psone-jam"; + var title: []u8 = "jamstation"; // Set the JAM_PSONE_FULL_VRAM env var (any non-empty value) to // see the whole 1024×512 VRAM in the window instead of the // BIOS's 320×240 display rectangle. Useful for spotting where the @@ -601,27 +582,28 @@ fn main() { // services that pull by running the SPU one sample per output // frame. The audio thread needs handles to spu / spuram / irq / // cop0 — pack them into a 32-byte AudioCtx so we can pass a - // single pointer through SDL_AudioSpec.userdata. - var audioCtx: *mut[] u8 = malloc(32); - setU64(audioCtx, 0, bus.spu.ptr as u64); - setU64(audioCtx, 8, bus.spuram.ptr as u64); - setU64(audioCtx, 16, bus.irq.ptr as u64); - setU64(audioCtx, 24, cop0.ptr as u64); + // single pointer through SDL_AudioSpec.userdata. Lives on main()'s + // stack: it has to outlive the SDL audio thread, but main() runs + // until the user quits, so this is safe and avoids a heap alloc. + var audioCtx: [32]u8 = [0; 32]; + var actx: *mut[] u8 = audioCtx.asMutPtr(); + setU64(actx, 0, bus.spu.ptr as u64); + setU64(actx, 8, bus.spuram.ptr as u64); + setU64(actx, 16, bus.irq.ptr as u64); + setU64(actx, 24, cop0.ptr as u64); const cbAddr: u64 = psone_audio_cb as u64; - var audioDev: u32 = sdlAudioOpen(1024, cbAddr, audioCtx as u64); + var audioDev: u32 = sdlAudioOpen(1024, cbAddr, actx as u64); if (audioDev == 0) { var afmt: []u8 = "WARN: SDL audio device failed to open\n"; write(STDOUT, afmt.ptr, afmt.len); } else { print("Audio device opened (id={audioDev}, 44.1kHz S16 stereo, callback mode)\n"); } - var postMsg: []u8 = "SDL2 ready. Priming VRAM...\n"; + var postMsg: []u8 = "SDL2 ready.\n"; write(STDOUT, postMsg.ptr, postMsg.len); - // Skip the gradient test pattern for now — the nested 1024x512 loop - // is slow in debug builds. Memset the VRAM to a solid dark gray so - // the window is visibly populated. - var vp: *mut[] u8 = busVramPtr(bus); - memset(vp, 0x18, 0x100000); // 0x1818 → muted blue-grey in BGR555 + // VRAM was already zero-filled when the Bus was allocated; the BIOS + // renders into it within the first frame, so the brief black flash + // before its first GP0 commit is fine. var msg: []u8 = "Running — Esc or close to quit\n"; write(STDOUT, msg.ptr, msg.len); @@ -629,9 +611,10 @@ fn main() { // Active-low button mask the SDL pump updates from keyboard events. // 0xFFFF = no buttons pressed; sdlPump clears bits on KEYDOWN and // sets them on KEYUP. padUpdate copies it into the SDA pad state - // each frame so the next controller poll sees fresh input. - var buttons: *mut[] u32 = malloc(4) as *mut[] u32; - buttons[0] = 0xFFFF; + // each frame so the next controller poll sees fresh input. Stack- + // local — sdlPump just borrows the pointer per call. + var buttons: [1]u32 = [0xFFFF; 1]; + var btnPtr: *mut[] u32 = buttons.asMutPtr(); var frames: u32 = 0; var quit: u32 = 0; @@ -705,7 +688,7 @@ fn main() { sdlBlit(sdl, vram, dispX, dispY, w, h, is24bpp, displayOff); } - quit = sdlPump(buttons); + quit = sdlPump(btnPtr); // Push the latest keyboard state into the SDA pad. Cheap (2-byte // write); doing it once per frame is enough since controller // polling runs at VBlank cadence in the BIOS. @@ -726,8 +709,8 @@ fn main() { mcdRamSave(bus.mcdRam.ptr, mcdPath); sdlAudioClose(audioDev); - free(audioCtx); - free(buttons as *mut[] u8); + // audioCtx and buttons are stack arrays — they vanish with main()'s + // frame; no manual free needed. sdlQuit(sdl); freeBus(bus); } diff --git a/sdl.jam b/sdl.jam index 0e589c6..026ee8c 100644 --- a/sdl.jam +++ b/sdl.jam @@ -9,10 +9,6 @@ // macOS desktop only — set up via Homebrew's `sdl2` formula. Build // with the SDL2 linker flags via `make run`. -extern fn malloc(size: u64) *mut[] u8; -extern fn free(ptr: *mut[] u8); -extern fn memset(dst: *mut[] u8, c: i32, n: u64) *mut[] u8; - const { Vec } = import("std").collections; // SDL_Init flags. We need video + events (and game-controller would @@ -166,31 +162,31 @@ pub fn setU64Le(buf: *mut[] u8, off: u32, v: u64) { } pub fn sdlAudioOpen(samplesPerBuffer: u32, cbAddr: u64, userdata: u64) u32 { - var desired: *mut[] u8 = malloc(32); - var obtained: *mut[] u8 = malloc(32); - memset(desired, 0, 32); - memset(obtained, 0, 32); - // freq @ 0 (i32) - desired[0] = 0x44; desired[1] = 0xAC; desired[2] = 0; desired[3] = 0; // 44100 + // SDL_AudioSpec is a 32-byte C struct; we hand-pack it and let SDL + // overwrite the obtained side. Stack arrays beat malloc for these + // single-call scratch buffers — they auto-vanish on function exit. + var desired: [32]u8 = [0; 32]; + var obtained: [32]u8 = [0; 32]; + var dp: *mut[] u8 = desired.asMutPtr(); + // freq @ 0 (i32) = 44100 + dp[0] = 0x44; dp[1] = 0xAC; dp[2] = 0; dp[3] = 0; // format @ 4 (u16) — AUDIO_S16SYS - desired[4] = (SDL_AUDIO_S16SYS & 0xFF) as u8; - desired[5] = ((SDL_AUDIO_S16SYS >> 8) & 0xFF) as u8; + dp[4] = (SDL_AUDIO_S16SYS & 0xFF) as u8; + dp[5] = ((SDL_AUDIO_S16SYS >> 8) & 0xFF) as u8; // channels @ 6 (u8) = 2 - desired[6] = 2; + dp[6] = 2; // silence @ 7 (u8) — SDL fills // samples @ 8 (u16) — buffer size in stereo samples - desired[8] = (samplesPerBuffer & 0xFF) as u8; - desired[9] = ((samplesPerBuffer >> 8) & 0xFF) as u8; + dp[8] = (samplesPerBuffer & 0xFF) as u8; + dp[9] = ((samplesPerBuffer >> 8) & 0xFF) as u8; // padding, size — SDL fills // callback @ 16 (8-byte fn pointer) - setU64Le(desired, 16, cbAddr); + setU64Le(dp, 16, cbAddr); // userdata @ 24 (8-byte ptr) — handed back to the callback as-is - setU64Le(desired, 24, userdata); + setU64Le(dp, 24, userdata); // First arg = NULL (default playback device). const dev: u32 = SDL_OpenAudioDevice(0 as u64, 0, - desired, obtained, 0); - free(desired); - free(obtained); + dp, obtained.asMutPtr(), 0); if (dev != 0) { SDL_PauseAudioDevice(dev, 0); // unpause } @@ -248,19 +244,23 @@ pub fn packRect(buf: *mut[] u8, x: i32, y: i32, w: i32, h: i32) { pub fn sdlBlit(s: Sdl, pixels: *mut[] u8, dx: i32, dy: i32, dw: i32, dh: i32, is24bpp: bool, displayOff: bool) { - var fullRect: *mut[] u8 = malloc(16); - packRect(fullRect, 0, 0, s.width, s.height); - var dstOnTex: *mut[] u8 = malloc(16); - packRect(dstOnTex, 0, 0, dw, dh); + // SDL_Rect = 4 × i32 = 16 bytes; both are filled in-place by packRect. + var fullRect: [16]u8 = [0; 16]; + packRect(fullRect.asMutPtr(), 0, 0, s.width, s.height); + var dstOnTex: [16]u8 = [0; 16]; + packRect(dstOnTex.asMutPtr(), 0, 0, dw, dh); - // Destination buffer is always ARGB8888 = 4 bytes/pixel. + // Destination buffer is always ARGB8888 = 4 bytes/pixel. `displayOff` + // wants every byte zero; the active path overwrites every byte in the + // conversion loop. `filled(0, N)` covers both — the zero-fill is one + // pass through the same cache lines we touch immediately after. const dwU: u32 = dw as u32; const dhU: u32 = dh as u32; const cropBytes: u64 = (dwU * dhU * 4) as u64; - var cropped: Vec(u8) = Vec(u8).withCapacity(cropBytes as u32); + var cropped: Vec(u8) = Vec(u8).filled(0, cropBytes as u32); if (displayOff) { - memset(cropped.ptr, 0, cropBytes); + // already zero-filled } else { var row: u32 = 0; while (row < dhU) { @@ -312,14 +312,12 @@ pub fn sdlBlit(s: Sdl, pixels: *mut[] u8, } } - SDL_UpdateTexture(s.texture, dstOnTex, cropped.ptr, dw * 4); + SDL_UpdateTexture(s.texture, dstOnTex.asMutPtr(), cropped.ptr, dw * 4); SDL_RenderClear(s.renderer); - SDL_RenderCopy(s.renderer, s.texture, dstOnTex, fullRect); + SDL_RenderCopy(s.renderer, s.texture, dstOnTex.asMutPtr(), fullRect.asMutPtr()); SDL_RenderPresent(s.renderer); - // cropped auto-drops on function-scope exit. dstOnTex / fullRect - // stay raw — they're C-ABI SDL_Rect structs we hand to SDL2. - free(dstOnTex); - free(fullRect); + // cropped auto-drops on function-scope exit. dstOnTex / fullRect are + // stack arrays that vanish with the frame; no manual cleanup. } // SDL scancodes (USB HID page 7) — see SDL2 SDL_scancode.h. @@ -383,14 +381,16 @@ pub fn scancodeToBtn(scan: u32) u32 { // KEYDOWN clears the bit, KEYUP sets it; updates take effect on the // next controller poll. pub fn sdlPump(buttons: *mut[] u32) u32 { - var ev: *mut[] u8 = malloc(SDL_EVENT_SIZE as u64); - memset(ev, 0, SDL_EVENT_SIZE as u64); + // SDL_Event is a 56-byte union in SDL 2.x; pad to 64 bytes on the + // stack so newer SDL releases that grow the union don't trample. + var ev: [64]u8 = [0; 64]; + var evP: *mut[] u8 = ev.asMutPtr(); var quit: u32 = 0; - while (SDL_PollEvent(ev) != 0) { - const t: u32 = (ev[0] as u32) - | ((ev[1] as u32) << 8) - | ((ev[2] as u32) << 16) - | ((ev[3] as u32) << 24); + while (SDL_PollEvent(evP) != 0) { + const t: u32 = (evP[0] as u32) + | ((evP[1] as u32) << 8) + | ((evP[2] as u32) << 16) + | ((evP[3] as u32) << 24); if (t == SDL_QUIT) { quit = 1; } if (t == SDL_KEYDOWN || t == SDL_KEYUP) { // SDL_KeyboardEvent layout: type(4) + timestamp(4) + @@ -398,10 +398,10 @@ pub fn sdlPump(buttons: *mut[] u32) u32 { // padding3(1) = 16 bytes before SDL_Keysym, whose first // field is the i32 scancode. So scancode bytes live at // offsets 16..19 of the event blob. - const scan: u32 = (ev[16] as u32) - | ((ev[17] as u32) << 8) - | ((ev[18] as u32) << 16) - | ((ev[19] as u32) << 24); + const scan: u32 = (evP[16] as u32) + | ((evP[17] as u32) << 8) + | ((evP[18] as u32) << 16) + | ((evP[19] as u32) << 24); if (scan == SC_ESC && t == SDL_KEYDOWN) { quit = 1; } const bit: u32 = scancodeToBtn(scan); if (bit != 0) { @@ -413,7 +413,6 @@ pub fn sdlPump(buttons: *mut[] u32) u32 { } } } - free(ev); return quit; } diff --git a/tests.jam b/tests.jam index f9d48f4..0fa1321 100644 --- a/tests.jam +++ b/tests.jam @@ -29,7 +29,7 @@ const { gteAlloc, gteExec, gteDivide, gteDataRead, gteDataWrite, gteCtrlWrite, gteCtrlRead, gteOpCycles } = import("gte"); const { Cdrom } = import("cdrom"); -const { discAlloc, discOpen } = import("disc"); +const { Disc, discAlloc, discOpen } = import("disc"); const { dmaAlloc, dmaUpdate } = import("dma"); const { padAlloc, padUpdate } = import("pad"); const { spuAlloc, spuRamAlloc, spuUpdate } = import("spu"); @@ -47,18 +47,8 @@ const { File, exists } = import("std/fs"); const C0_SR: u32 = 12; 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: File, - loaded: u8, - isCue: u8, - pad1: u8, - pad2: u8, - cuePtr: *mut[] u8, -}; - - +// Re-declare the same shapes as cpu.jam — structural equality lets us +// pass values across module boundaries. const Cpu = struct { pc: u32, nextPc: u32, @@ -540,7 +530,7 @@ fn emuRunOneFrame(c: mut Cpu, bus: Bus, regs: Vec(u32), cop0: Vec(u32)) { const delta: u32 = (c.cycles - before) as u32; bus.timer.updateCyc(bus.irq.ptr, cop0.ptr, delta); dmaUpdate(bus.dma.ptr, bus.irq.ptr, cop0.ptr); - bus.cdrom.update(bus.disc, bus.irq.ptr, cop0.ptr, delta); + bus.cdrom.update(bus.disc.ptrMut(), bus.irq.ptr, cop0.ptr, delta); padUpdate(bus.pad.ptr, bus.irq.ptr, cop0.ptr, delta); bus.sio1.update(bus.irq.ptr, cop0.ptr, delta); spuUpdate(bus.spu.ptr, bus.spuram.ptr, bus.irq.ptr, cop0.ptr, delta); @@ -610,7 +600,7 @@ tfn emuDiscBootsExec() { if (emuBoot(c, bus, regs.ptr, cop0.ptr) == 0) { freeBus(bus); return; } - discOpen(bus.disc, diskPath); + discOpen(bus.disc.ptrMut(), diskPath); emuRunFrames(c, bus, regs.ptr, cop0.ptr, 120); // Bare minimum: BIOS handed control out of ROM (0xBFC*) into RAM // or scratchpad. Stricter "PC inside game text segment" check is