feat(cartridge): add explicit Flash identification and banked reads

This commit is contained in:
0xrsydn 2026-10-07 06:13:39 +07:00
commit 8b3b7ae62d
23 changed files with 1042 additions and 52 deletions

View file

@ -14,14 +14,14 @@ use std::{
use gba_core::{
bios,
cartridge::{CartridgeHardware, RtcError},
cartridge::{CartridgeHardware, RtcError, SaveDevice},
machine::{Machine, MachineError, StepKind},
memory::ROM_CAPACITY,
};
const MAX_STEPS: u64 = 100_000_000;
const MAX_FRAMES: u64 = 100_000;
const USAGE: &str = "usage: gameboy-rust --rom PATH [--rtc] (--steps COUNT | --window [--frames COUNT]); steps: 1..=100000000, frames: 1..=100000; do not combine with demo or help options";
const USAGE: &str = "usage: gameboy-rust --rom PATH [--rtc] [--save-type flash64|flash128] (--steps COUNT | --window [--frames COUNT]); steps: 1..=100000000, frames: 1..=100000; do not combine with demo or help options";
#[derive(Debug, PartialEq, Eq)]
enum Mode {
@ -34,6 +34,7 @@ pub struct Options {
path: PathBuf,
mode: Mode,
hardware: CartridgeHardware,
save_device: SaveDevice,
}
pub fn parse_args(args: &[OsString]) -> io::Result<Options> {
@ -43,6 +44,7 @@ pub fn parse_args(args: &[OsString]) -> io::Result<Options> {
let mut frames = None;
let mut window = false;
let mut hardware = CartridgeHardware::None;
let mut save_device = SaveDevice::None;
let mut args = args.iter();
while let Some(arg) = args.next() {
if arg == "--rom" && path.is_none() {
@ -53,6 +55,12 @@ pub fn parse_args(args: &[OsString]) -> io::Result<Options> {
path = Some(PathBuf::from(value));
} else if arg == "--rtc" && hardware == CartridgeHardware::None {
hardware = CartridgeHardware::Rtc;
} else if arg == "--save-type" && save_device == SaveDevice::None {
save_device = match args.next().and_then(|value| value.to_str()) {
Some("flash64") => SaveDevice::Flash64,
Some("flash128") => SaveDevice::Flash128,
_ => return Err(invalid()),
};
} else if arg == "--window" && !window {
window = true;
} else if (arg == "--steps" && steps.is_none()) || (arg == "--frames" && frames.is_none()) {
@ -90,6 +98,7 @@ pub fn parse_args(args: &[OsString]) -> io::Result<Options> {
path: path.ok_or_else(invalid)?,
mode,
hardware,
save_device,
})
}
@ -264,10 +273,19 @@ pub fn execute(options: Options, writer: &mut impl Write) -> Result<(), Box<dyn
if options.hardware == CartridgeHardware::Rtc {
writeln!(writer, "RTC selected: UTC at startup, then host elapsed time; no persistence or RTC interrupts.")?;
}
if options.save_device != SaveDevice::None {
writeln!(writer, "Flash selected: {} KiB Macronix, erased array; identification/bank reads only. No programming, erase, or save files.", options.save_device.capacity() / 1024)?;
}
let steps = match options.mode {
Mode::Terminal { steps } => steps,
Mode::Window { frames } => {
return crate::desktop::run_rom(bytes, frames, options.hardware, writer)
return crate::desktop::run_rom(
bytes,
frames,
options.hardware,
options.save_device,
writer,
)
}
};
writeln!(writer, "Machine-step limit: {steps} (includes boot)")?;
@ -275,6 +293,7 @@ pub fn execute(options: Options, writer: &mut impl Write) -> Result<(), Box<dyn
machine
.memory_mut()
.set_cartridge_hardware(options.hardware);
machine.memory_mut().set_save_device(options.save_device);
let mut clock = if options.hardware == CartridgeHardware::Rtc {
Some(rtc_clock::RtcHostClock::new(machine.memory_mut())?)
} else {

View file

@ -27,7 +27,8 @@ fn options_require_both_flags_in_either_order() {
Options {
path: "original program.gba".into(),
mode: Mode::Terminal { steps: count },
hardware: CartridgeHardware::None
hardware: CartridgeHardware::None,
save_device: SaveDevice::None
}
);
}

View file

@ -9,10 +9,12 @@ pub fn run(
bytes: Vec<u8>,
frame_limit: Option<u64>,
hardware: gba_core::cartridge::CartridgeHardware,
save_device: gba_core::cartridge::SaveDevice,
writer: &mut impl Write,
) -> Result<(), Box<dyn Error>> {
let mut session = Session::new(bytes)?;
session.set_cartridge_hardware(hardware);
session.memory_mut().set_save_device(save_device);
let mut rtc_clock = if hardware == gba_core::cartridge::CartridgeHardware::Rtc {
Some(crate::cartridge::rtc_clock::RtcHostClock::new(
session.memory_mut(),

View file

@ -47,9 +47,16 @@ fn parse_args(args: &[OsString]) -> Result<RunMode, io::Error> {
return cartridge::suite::parse_args(args).map(RunMode::TestSuite);
}
if args.iter().any(|arg| {
["--rom", "--steps", "--window", "--frames", "--rtc"]
.iter()
.any(|flag| arg == flag)
[
"--rom",
"--steps",
"--window",
"--frames",
"--rtc",
"--save-type",
]
.iter()
.any(|flag| arg == flag)
}) {
return cartridge::parse_args(args).map(RunMode::Rom);
}
@ -79,7 +86,7 @@ fn parse_args(args: &[OsString]) -> Result<RunMode, io::Error> {
[arg] if arg == "--help" || arg == "-h" => Ok(RunMode::Help),
_ => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"usage: gameboy-rust [--cpu-demo | --timer-demo | --graphics-demo | --smoke-test | --graphics-smoke-test | --raster-demo | --raster-smoke-test | --mosaic-demo | --mosaic-smoke-test | --effects-demo | --effects-smoke-test | --tile-demo | --tile-smoke-test | --affine-demo | --affine-smoke-test | --affine-raster-demo | --affine-raster-smoke-test | --bitmap4-demo | --bitmap5-demo | --bitmap4-smoke-test | --bitmap5-smoke-test | --help]; or gameboy-rust --rom PATH [--rtc] (--steps COUNT | --window [--frames COUNT]); or gameboy-rust --test-suite PATH.json",
"usage: gameboy-rust [--cpu-demo | --timer-demo | --graphics-demo | --smoke-test | --graphics-smoke-test | --raster-demo | --raster-smoke-test | --mosaic-demo | --mosaic-smoke-test | --effects-demo | --effects-smoke-test | --tile-demo | --tile-smoke-test | --affine-demo | --affine-smoke-test | --affine-raster-demo | --affine-raster-smoke-test | --bitmap4-demo | --bitmap5-demo | --bitmap4-smoke-test | --bitmap5-smoke-test | --help]; or gameboy-rust --rom PATH [--rtc] [--save-type flash64|flash128] (--steps COUNT | --window [--frames COUNT]); or gameboy-rust --test-suite PATH.json",
)),
}
}
@ -126,6 +133,7 @@ fn run() -> Result<(), Box<dyn Error>> {
--rom PATH --window Open a raw ROM with keyboard input\n\
--frames COUNT Exit ROM window after 1..=100000 captured frames\n\
--rtc Select cartridge RTC (UTC startup, host elapsed time)\n\
--save-type flash64|flash128 Select Flash identification/read subset (no saving yet)\n\
--test-suite PATH.json Run bounded ROM assertions; write a JSON report\n\
--cpu-demo Run the terminal-only ARM/Thumb instruction demo\n\
--timer-demo Run the timer IRQ demo with nominal cycle costs\n\