diff --git a/frontends/sdl/src/main.rs b/frontends/sdl/src/main.rs index c8227f12f20924c219e6744a59d9e5b06f8727df..139b8a22f4281a197ccc731c0a2fa101224780b6 100644 --- a/frontends/sdl/src/main.rs +++ b/frontends/sdl/src/main.rs @@ -10,6 +10,7 @@ use boytacean::{ gb::{AudioProvider, GameBoy, GameBoyMode}, pad::PadKey, ppu::{PaletteInfo, PpuMode, DISPLAY_HEIGHT, DISPLAY_WIDTH}, + rom::Cartridge, serial::{NullDevice, SerialDevice}, }; use chrono::Utc; @@ -49,6 +50,7 @@ impl Default for Benchmark { pub struct Emulator { system: GameBoy, + auto_mode: bool, graphics: Option<Graphics>, audio: Option<Audio>, title: &'static str, @@ -63,9 +65,10 @@ pub struct Emulator { } impl Emulator { - pub fn new(system: GameBoy) -> Self { + pub fn new(system: GameBoy, auto_mode: bool) -> Self { Self { system, + auto_mode, graphics: None, audio: None, title: TITLE, @@ -183,7 +186,7 @@ impl Emulator { pub fn load_rom(&mut self, path: Option<&str>) { let path_res = path.unwrap_or(&self.rom_path); - let rom = self.system.load_rom_file(path_res); + let rom: &boytacean::rom::Cartridge = self.system.load_rom_file(path_res); println!( "========= Cartridge =========\n{}\n=============================", rom @@ -329,6 +332,10 @@ impl Emulator { } } Event::DropFile { filename, .. } => { + if self.auto_mode { + let mode = Cartridge::from_file(&filename).gb_mode(); + self.system.set_mode(mode); + } self.system.reset(); self.system.load(true); self.load_rom(Some(&filename)); @@ -461,7 +468,7 @@ impl Emulator { #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { - #[arg(short, long, default_value_t = String::from("cgb"))] + #[arg(short, long, default_value_t = String::from("auto"))] mode: String, #[arg(short, long, default_value_t = String::from("printer"))] @@ -487,7 +494,12 @@ fn main() { // parses the provided command line arguments and uses them to // obtain structured values let args = Args::parse(); - let mode: GameBoyMode = GameBoyMode::from_string(&args.mode); + let mode: GameBoyMode = if args.mode == "auto" { + GameBoyMode::Dmg + } else { + GameBoyMode::from_string(&args.mode) + }; + let auto_mode = args.mode == "auto"; // creates a new Game Boy instance and loads both the boot ROM // and the initial game ROM to "start the engine" @@ -506,7 +518,7 @@ fn main() { // creates a new generic emulator structure then starts // both the video and audio sub-systems, loads default // ROM file and starts running it - let mut emulator = Emulator::new(game_boy); + let mut emulator = Emulator::new(game_boy, auto_mode); emulator.start(SCREEN_SCALE); emulator.load_rom(Some(&args.rom_path)); emulator.toggle_palette(); diff --git a/frontends/web/react/components/serial-section/serial-section.tsx b/frontends/web/react/components/serial-section/serial-section.tsx index 03d2ffb5416b66cd8eb8758666fa5527f3ca704f..2b2f8406ba08d01ed646cf8c6f428a77d90c2cdc 100644 --- a/frontends/web/react/components/serial-section/serial-section.tsx +++ b/frontends/web/react/components/serial-section/serial-section.tsx @@ -58,7 +58,7 @@ export const SerialSection: FC<SerialSectionProps> = ({ }; }, []); - const onEngineChange = (option: string) => { + const onDeviceChange = (option: string) => { emulator.loadSerialDevice(option as SerialDevice); const optionIcon = DEVICE_ICON[option] ?? ""; emulator.handlers.showToast?.( @@ -79,7 +79,7 @@ export const SerialSection: FC<SerialSectionProps> = ({ uppercase={true} size={"large"} style={["simple"]} - onChange={onEngineChange} + onChange={onDeviceChange} /> } /> diff --git a/frontends/web/ts/gb.ts b/frontends/web/ts/gb.ts index 2e5af350f548a56ede50af2d2e08761c0c718adb..20a58b9a188403a27b750291a41375e70097d03c 100644 --- a/frontends/web/ts/gb.ts +++ b/frontends/web/ts/gb.ts @@ -119,9 +119,18 @@ export class GameboyEmulator extends EmulatorBase implements Emulator { */ private _engine: string | null = null; - private logicFrequency: number = LOGIC_HZ; - private visualFrequency: number = VISUAL_HZ; - private idleFrequency: number = IDLE_HZ; + /** + * If the GB running mode should be automatically inferred + * from the GBC flag in the cartridge. Meaning that if the + * cartridge is a GBC compatible or GBC only the GBC emulation + * mode is going to be used, otherwise the DMG mode is used + * instead. This should provide an optimal usage experience. + */ + private autoMode = false; + + private logicFrequency = LOGIC_HZ; + private visualFrequency = VISUAL_HZ; + private idleFrequency = IDLE_HZ; private paused = false; private nextTickTime = 0; @@ -361,7 +370,7 @@ export class GameboyEmulator extends EmulatorBase implements Emulator { * the emulator engine to use. */ async boot({ - engine = "cgb", + engine = "auto", restore = true, loadRom = false, romPath = ROM_PATH, @@ -394,11 +403,17 @@ export class GameboyEmulator extends EmulatorBase implements Emulator { // selects the proper engine for execution // and builds a new instance of it switch (engine) { + case "auto": + this.gameBoy = new GameBoy(GameBoyMode.Dmg); + this.autoMode = true; + break; case "cgb": this.gameBoy = new GameBoy(GameBoyMode.Cgb); + this.autoMode = false; break; case "dmg": this.gameBoy = new GameBoy(GameBoyMode.Dmg); + this.autoMode = false; break; default: if (!this.gameBoy) { @@ -412,6 +427,13 @@ export class GameboyEmulator extends EmulatorBase implements Emulator { // selected one this.updatePalette(); + // in case the auto emulation mode is enabled runs the + // inference logic to try to infer the best mode from the + // GBC header in the cartridge data + if (this.autoMode) { + this.gameBoy.infer_mode_ws(romData); + } + // resets the Game Boy engine to restore it into // a valid state ready to be used this.gameBoy.reset(); @@ -541,11 +563,11 @@ export class GameboyEmulator extends EmulatorBase implements Emulator { } get engines(): string[] { - return ["cgb", "dmg"]; + return ["auto", "cgb", "dmg"]; } get engine(): string { - return this._engine || "cgb"; + return this._engine || "auto"; } get romExts(): string[] { diff --git a/src/gb.rs b/src/gb.rs index 67244e46509261f0fb9694fb8250bba3c1673fb7..6f09ab21473d350fb48bea533bd826c821566bf9 100644 --- a/src/gb.rs +++ b/src/gb.rs @@ -936,6 +936,11 @@ impl GameBoy { })); } + pub fn infer_mode_ws(&mut self, data: &[u8]) { + let mode = Cartridge::from_data(data).gb_mode(); + self.set_mode(mode); + } + pub fn load_rom_ws(&mut self, data: &[u8]) -> Cartridge { self.load_rom(data).clone() } diff --git a/src/rom.rs b/src/rom.rs index ca26e57a9228fb3a40394b0fd4635e82437459c2..1ab7975ffc3cadfbb0c7d313269646fb718e01eb 100644 --- a/src/rom.rs +++ b/src/rom.rs @@ -4,7 +4,7 @@ use std::{ fmt::{Display, Formatter}, }; -use crate::{debugln, warnln}; +use crate::{debugln, gb::GameBoyMode, util::read_file, warnln}; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; @@ -276,6 +276,11 @@ impl Cartridge { cartridge } + pub fn from_file(path: &str) -> Self { + let data = read_file(path); + Self::from_data(&data) + } + pub fn read(&self, addr: u16) -> u8 { match addr & 0xf000 { 0x0000 | 0x1000 | 0x2000 | 0x3000 | 0x4000 | 0x5000 | 0x6000 | 0x7000 => { @@ -404,6 +409,13 @@ impl Cartridge { } } + pub fn gb_mode(&self) -> GameBoyMode { + match self.cgb_flag() { + CgbMode::CgbCompatible | CgbMode::CgbOnly => GameBoyMode::Cgb, + _ => GameBoyMode::Dmg, + } + } + /// A cartridge is considered legacy if it does /// not have a CGB flag bit (bit 7 of 0x0143) set. /// These are the monochromatic only Cartridges built