From 1ec78a1b3478e0c6ce6f20806d26d281477542c5 Mon Sep 17 00:00:00 2001 From: diogotito Date: Mon, 10 Jun 2024 14:01:58 +0100 Subject: [PATCH 1/2] Initialize CPU/HW registers to post-DMG0 values --- Cargo.lock | 1 + fpt-cli/src/main.rs | 37 +++++++++++++++++++++++++----- fpt/src/lib.rs | 55 +++++++++++++++++++++++++++++++++++++++++++++ fpt/src/lr35902.rs | 12 +++++----- 4 files changed, 94 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdd653d..b15c485 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -735,6 +735,7 @@ dependencies = [ name = "fpt" version = "0.1.0" dependencies = [ + "regex", "rstest", ] diff --git a/fpt-cli/src/main.rs b/fpt-cli/src/main.rs index 5f69154..cd380f4 100644 --- a/fpt-cli/src/main.rs +++ b/fpt-cli/src/main.rs @@ -3,7 +3,7 @@ use std::fs; -use clap::{Args, Parser, Subcommand}; +use clap::{Args, Parser, Subcommand, ValueEnum}; use debugger::DebuggerTextInterface; use fpt::Gameboy; use rustyline::error::ReadlineError; @@ -14,10 +14,34 @@ pub mod debugger; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Cli { + #[command(flatten)] + gameboy_config: GameboyConfig, #[command(subcommand)] command: Commands, } +#[derive(Debug, Args)] +struct GameboyConfig { + #[arg(short, long)] + fake_bootrom: Option, +} + +impl GameboyConfig { + /// Build a Gameboy following this configuration. Consumes self. + pub fn build_gameboy(self: Self) -> Gameboy { + let mut gameboy = Gameboy::new(); + if let Some(BootromToFake::DMG0) = self.fake_bootrom { + gameboy.simulate_dmg0_bootrom_handoff_state(); + } + gameboy + } +} + +#[derive(ValueEnum, Debug, Clone, PartialEq)] +enum BootromToFake { + DMG0, +} + #[derive(Subcommand, Debug)] enum Commands { Debug {}, @@ -93,9 +117,10 @@ fn dump(args: Dump) -> Result<()> { } } -fn run(args: Run) -> Result<()> { - let mut gameboy = Gameboy::new(); - let rom = fs::read(args.rom).unwrap(); +fn run(gb_config: GameboyConfig, args: Run) -> Result<()> { + let mut gameboy = gb_config.build_gameboy(); + + let rom = fs::read(args.rom)?; gameboy.load_rom(&rom); loop { if args.debug.unwrap_or(false) { @@ -107,9 +132,11 @@ fn run(args: Run) -> Result<()> { fn main() -> Result<()> { let args = Cli::parse(); + let gb_config = args.gameboy_config; + match args.command { Commands::Debug {} => debug(), Commands::Dump(args) => dump(args), - Commands::Run(args) => run(args), + Commands::Run(args) => run(gb_config, args), } } diff --git a/fpt/src/lib.rs b/fpt/src/lib.rs index a53544c..1cedba0 100644 --- a/fpt/src/lib.rs +++ b/fpt/src/lib.rs @@ -31,6 +31,61 @@ impl Gameboy { } } + /// Sets CPU and hardware registers to the values found in the DMG0 column in the tables at + /// https://gbdev.io/pandocs/Power_Up_Sequence.html#console-state-after-boot-rom-hand-off + pub fn simulate_dmg0_bootrom_handoff_state(&mut self) { + // CPU registers + self.cpu.set_af(0x0100); + self.cpu.set_bc(0xff13); + self.cpu.set_de(0x00c1); + self.cpu.set_hl(0x8403); + self.cpu.set_sp(0xfffe); + self.cpu.set_pc(0x100); // This effectively skips the bootrom + + // HW registers + self.bus.write(0xFF00, 0xCF); // P1 + self.bus.write(0xFF01, 0x00); // SB + self.bus.write(0xFF02, 0x7E); // SC + self.bus.write(0xFF04, 0x18); // DIV + self.bus.write(0xFF05, 0x00); // TIMA + self.bus.write(0xFF06, 0x00); // TMA + self.bus.write(0xFF07, 0xF8); // TAC + self.bus.write(0xFF0F, 0xE1); // IF + self.bus.write(0xFF10, 0x80); // NR10 + self.bus.write(0xFF11, 0xBF); // NR11 + self.bus.write(0xFF12, 0xF3); // NR12 + self.bus.write(0xFF13, 0xFF); // NR13 + self.bus.write(0xFF14, 0xBF); // NR14 + self.bus.write(0xFF16, 0x3F); // NR21 + self.bus.write(0xFF17, 0x00); // NR22 + self.bus.write(0xFF18, 0xFF); // NR23 + self.bus.write(0xFF19, 0xBF); // NR24 + self.bus.write(0xFF1A, 0x7F); // NR30 + self.bus.write(0xFF1B, 0xFF); // NR31 + self.bus.write(0xFF1C, 0x9F); // NR32 + self.bus.write(0xFF1D, 0xFF); // NR33 + self.bus.write(0xFF1E, 0xBF); // NR34 + self.bus.write(0xFF20, 0xFF); // NR41 + self.bus.write(0xFF21, 0x00); // NR42 + self.bus.write(0xFF22, 0x00); // NR43 + self.bus.write(0xFF23, 0xBF); // NR44 + self.bus.write(0xFF24, 0x77); // NR50 + self.bus.write(0xFF25, 0xF3); // NR51 + self.bus.write(0xFF26, 0xF1); // NR52 + self.bus.write(0xFF40, 0x91); // LCDC + self.bus.write(0xFF41, 0x81); // STAT + self.bus.write(0xFF42, 0x00); // SCY + self.bus.write(0xFF43, 0x00); // SCX + self.bus.write(0xFF44, 0x91); // LY + self.bus.write(0xFF45, 0x00); // LYC + self.bus.write(0xFF46, 0xFF); // DMA + self.bus.write(0xFF47, 0xFC); // BGP + self.bus.write(0xFF48, 0x00); // OBP0 + self.bus.write(0xFF49, 0x00); // OBP1 + self.bus.write(0xFF4A, 0x00); // WY + self.bus.write(0xFF4B, 0x00); // WX + } + pub fn load_rom(&mut self, rom: &[u8]) { self.bus.load_cartridge(rom); } diff --git a/fpt/src/lr35902.rs b/fpt/src/lr35902.rs index 1140359..07a8d0d 100644 --- a/fpt/src/lr35902.rs +++ b/fpt/src/lr35902.rs @@ -54,12 +54,12 @@ impl DebugInterface for LR35902 { impl LR35902 { pub fn new(memory: Bus) -> Self { Self { - af: 0x0100, - bc: 0xff13, - de: 0x00c1, - hl: 0x8403, - sp: 0xfffe, - pc: 0x100, + af: 0, + bc: 0, + de: 0, + hl: 0, + sp: 0, + pc: 0, ime: false, imenc: false, prefix_cb: false, From 70c7edbcfb358c5b580bae1c3dde914d772454f3 Mon Sep 17 00:00:00 2001 From: diogotito Date: Mon, 10 Jun 2024 19:19:21 +0100 Subject: [PATCH 2/2] Copy bootrom skipping logic to fpt-egui --- fpt-cli/src/main.rs | 13 +++++++++---- fpt-egui/src/main.rs | 29 ++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/fpt-cli/src/main.rs b/fpt-cli/src/main.rs index cd380f4..377301b 100644 --- a/fpt-cli/src/main.rs +++ b/fpt-cli/src/main.rs @@ -20,18 +20,23 @@ struct Cli { command: Commands, } -#[derive(Debug, Args)] +#[derive(Clone, Debug, Args)] struct GameboyConfig { + /// Apply known CPU and hardware register values of a well-known bootrom when it + /// hands off the execution to the cartridge's code. This skips emulating a bootrom. #[arg(short, long)] fake_bootrom: Option, } impl GameboyConfig { - /// Build a Gameboy following this configuration. Consumes self. + /// Build a `Gameboy` following this configuration. Consumes self. pub fn build_gameboy(self: Self) -> Gameboy { let mut gameboy = Gameboy::new(); - if let Some(BootromToFake::DMG0) = self.fake_bootrom { - gameboy.simulate_dmg0_bootrom_handoff_state(); + match self.fake_bootrom { + Some(BootromToFake::DMG0) => { + gameboy.simulate_dmg0_bootrom_handoff_state(); + } + None => {} } gameboy } diff --git a/fpt-egui/src/main.rs b/fpt-egui/src/main.rs index 39d945d..8e2689b 100644 --- a/fpt-egui/src/main.rs +++ b/fpt-egui/src/main.rs @@ -3,7 +3,7 @@ use std::time::Duration; -use clap::Parser; +use clap::{Parser, ValueEnum}; use eframe::Frame; use egui::{ menu, CentralPanel, Color32, ColorImage, Context, Grid, Key, RichText, ScrollArea, SidePanel, @@ -125,7 +125,11 @@ impl Default for FPT { impl FPT { /// Called once before the first frame. - pub fn new(_cc: &eframe::CreationContext, rom_path: &str) -> Self { + pub fn new( + _cc: &eframe::CreationContext, + fake_bootrom: Option, + rom_path: &str, + ) -> Self { let mut app = FPT::default(); #[cfg(not(target_arch = "wasm32"))] if std::env::var("CI").is_err() { @@ -135,6 +139,13 @@ impl FPT { panic!("Unable to open {}", rom_path); } } + // XXX duplicated logic from fpt-cli main.rs + match fake_bootrom { + Some(BootromToFake::DMG0) => { + app.gb.simulate_dmg0_bootrom_handoff_state(); + } + None => {} + } app } @@ -586,10 +597,21 @@ impl eframe::App for FPT { #[derive(Parser)] #[command(version, about, long_about = None)] struct Cli { - /// rom + /// Apply known CPU and hardware register values of a well-known bootrom when it + /// hands off the execution to the cartridge's code. This skips emulating a bootrom. + #[arg(short, long)] + fake_bootrom: Option, + /// ROM path rom: Option, } +// XXX duplicated struct from fpt-cli's main.rs +#[derive(ValueEnum, Debug, Clone, PartialEq)] +enum BootromToFake { + DMG0, +} + +/// Desktop entry point #[cfg(not(target_arch = "wasm32"))] fn main() -> eframe::Result<()> { let cli = Cli::parse(); @@ -609,6 +631,7 @@ fn main() -> eframe::Result<()> { Box::new(|cc| { Box::new(FPT::new( cc, + cli.fake_bootrom, &cli.rom.unwrap_or("roms/Tetris_World_Rev_1.gb".to_string()), )) }),