//! Main Game Boy emulation entrypoint functions and structures. use std::{ collections::VecDeque, fmt::{self, Display, Formatter}, sync::{Arc, Mutex}, }; use crate::{ apu::Apu, cheats::{ genie::{GameGenie, GameGenieCode}, shark::{GameShark, GameSharkCode}, }, cpu::Cpu, data::{BootRom, CGB_BOOT, CGB_BOYTACEAN, DMG_BOOT, DMG_BOOTIX, MGB_BOOTIX, SGB_BOOT}, devices::{printer::PrinterDevice, stdout::StdoutDevice}, dma::Dma, error::Error, info::Info, mmu::Mmu, pad::{Pad, PadKey}, ppu::{ Ppu, PpuMode, Tile, DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_RGB1555_SIZE, FRAME_BUFFER_RGB565_SIZE, FRAME_BUFFER_SIZE, FRAME_BUFFER_XRGB8888_SIZE, }, rom::{Cartridge, RamSize}, serial::{NullDevice, Serial, SerialDevice}, timer::Timer, util::{read_file, SharedThread}, }; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; #[cfg(feature = "wasm")] use crate::{color::Pixel, gen::dependencies_map, ppu::Palette}; #[cfg(feature = "wasm")] use std::{ convert::TryInto, panic::{set_hook, take_hook, PanicInfo}, }; /// Enumeration that describes the multiple running // modes of the Game Boy emulator. // DMG = Original Game Boy // CGB = Game Boy Color // SGB = Super Game Boy #[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive(Clone, Copy, PartialEq, Eq)] pub enum GameBoyMode { Dmg = 1, Cgb = 2, Sgb = 3, } impl GameBoyMode { pub fn description(&self) -> &'static str { match self { GameBoyMode::Dmg => "Game Boy (DMG)", GameBoyMode::Cgb => "Game Boy Color (CGB)", GameBoyMode::Sgb => "Super Game Boy (SGB)", } } pub fn from_u8(value: u8) -> Self { match value { 1 => GameBoyMode::Dmg, 2 => GameBoyMode::Cgb, 3 => GameBoyMode::Sgb, _ => panic!("Invalid mode value: {}", value), } } pub fn from_string(value: &str) -> Self { match value { "dmg" | "DMG" => GameBoyMode::Dmg, "cgb" | "CGB" => GameBoyMode::Cgb, "sgb" | "SGB" => GameBoyMode::Sgb, _ => panic!("Invalid mode value: {}", value), } } pub fn to_string(&self, uppercase: Option<bool>) -> String { let uppercase = uppercase.unwrap_or(false); match self { GameBoyMode::Dmg => (if uppercase { "DMG" } else { "dmg" }).to_string(), GameBoyMode::Cgb => (if uppercase { "CGB" } else { "cgb" }).to_string(), GameBoyMode::Sgb => (if uppercase { "SGB" } else { "sgb" }).to_string(), } } pub fn is_dmg(&self) -> bool { *self == GameBoyMode::Dmg } pub fn is_cgb(&self) -> bool { *self == GameBoyMode::Cgb } pub fn is_sgb(&self) -> bool { *self == GameBoyMode::Sgb } } impl Display for GameBoyMode { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.description()) } } #[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive(Clone, Copy, PartialEq, Eq)] pub enum GameBoySpeed { Normal = 0, Double = 1, } impl GameBoySpeed { pub fn description(&self) -> &'static str { match self { GameBoySpeed::Normal => "Normal Speed", GameBoySpeed::Double => "Double Speed", } } pub fn switch(&self) -> Self { match self { GameBoySpeed::Normal => GameBoySpeed::Double, GameBoySpeed::Double => GameBoySpeed::Normal, } } pub fn multiplier(&self) -> u8 { match self { GameBoySpeed::Normal => 1, GameBoySpeed::Double => 2, } } pub fn from_u8(value: u8) -> Self { match value { 0 => GameBoySpeed::Normal, 1 => GameBoySpeed::Double, _ => panic!("Invalid speed value: {}", value), } } } impl Display for GameBoySpeed { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.description()) } } #[cfg_attr(feature = "wasm", wasm_bindgen)] #[derive(Clone, Copy, PartialEq, Eq)] pub struct GameBoyConfig { /// The current running mode of the emulator, this /// may affect many aspects of the emulation, like /// CPU frequency, PPU frequency, Boot rome size, etc. mode: GameBoyMode, /// If the PPU is enabled, it will be clocked. ppu_enabled: bool, /// If the APU is enabled, it will be clocked. apu_enabled: bool, /// if the DMA is enabled, it will be clocked. dma_enabled: bool, /// If the timer is enabled, it will be clocked. timer_enabled: bool, /// If the serial is enabled, it will be clocked. serial_enabled: bool, /// The current frequency at which the Game Boy /// emulator is being handled. This is a "hint" that /// may help components to adjust their internal /// logic to match the current frequency. For example /// the APU will adjust its internal clock to match /// this hint. clock_freq: u32, } #[cfg_attr(feature = "wasm", wasm_bindgen)] impl GameBoyConfig { pub fn is_dmg(&self) -> bool { self.mode == GameBoyMode::Dmg } pub fn is_cgb(&self) -> bool { self.mode == GameBoyMode::Cgb } pub fn is_sgb(&self) -> bool { self.mode == GameBoyMode::Sgb } pub fn mode(&self) -> GameBoyMode { self.mode } pub fn set_mode(&mut self, value: GameBoyMode) { self.mode = value; } pub fn ppu_enabled(&self) -> bool { self.ppu_enabled } pub fn set_ppu_enabled(&mut self, value: bool) { self.ppu_enabled = value; } pub fn apu_enabled(&self) -> bool { self.apu_enabled } pub fn set_apu_enabled(&mut self, value: bool) { self.apu_enabled = value; } pub fn dma_enabled(&self) -> bool { self.dma_enabled } pub fn set_dma_enabled(&mut self, value: bool) { self.dma_enabled = value; } pub fn timer_enabled(&self) -> bool { self.timer_enabled } pub fn set_timer_enabled(&mut self, value: bool) { self.timer_enabled = value; } pub fn serial_enabled(&self) -> bool { self.serial_enabled } pub fn set_serial_enabled(&mut self, value: bool) { self.serial_enabled = value; } pub fn clock_freq(&self) -> u32 { self.clock_freq } pub fn set_clock_freq(&mut self, value: u32) { self.clock_freq = value; } } impl Default for GameBoyConfig { fn default() -> Self { Self { mode: GameBoyMode::Dmg, ppu_enabled: true, apu_enabled: true, dma_enabled: true, timer_enabled: true, serial_enabled: true, clock_freq: GameBoy::CPU_FREQ, } } } /// Aggregation structure allowing the bundling of /// all the components of a GameBoy into a single /// element for easy access. pub struct Components { pub ppu: Ppu, pub apu: Apu, pub dma: Dma, pub pad: Pad, pub timer: Timer, pub serial: Serial, } #[cfg_attr(feature = "wasm", wasm_bindgen)] pub struct Registers { pub pc: u16, pub sp: u16, pub a: u8, pub b: u8, pub c: u8, pub d: u8, pub e: u8, pub h: u8, pub l: u8, pub scy: u8, pub scx: u8, pub wy: u8, pub wx: u8, pub ly: u8, pub lyc: u8, } pub trait AudioProvider { fn audio_output(&self) -> u8; fn audio_buffer(&self) -> &VecDeque<u8>; fn clear_audio_buffer(&mut self); } #[cfg_attr(feature = "wasm", wasm_bindgen)] pub struct ClockFrame { pub cycles: u64, pub frames: u16, frame_buffer: Option<Vec<u8>>, } #[cfg_attr(feature = "wasm", wasm_bindgen)] impl ClockFrame { pub fn frame_buffer_eager(&mut self) -> Option<Vec<u8>> { self.frame_buffer.take() } } /// Top level structure that abstracts the usage of the /// Game Boy system under the Boytacean emulator. /// /// Should serve as the main entry-point API. #[cfg_attr(feature = "wasm", wasm_bindgen)] pub struct GameBoy { /// The current running mode of the emulator, this /// may affect many aspects of the emulation, like /// CPU frequency, PPU frequency, Boot rome size, etc. /// /// This is a clone of the configuration value /// kept for performance reasons. mode: GameBoyMode, /// If the PPU is enabled, it will be clocked. /// /// This is a clone of the configuration value /// kept for performance reasons. ppu_enabled: bool, /// If the APU is enabled, it will be clocked. /// /// This is a clone of the configuration value /// kept for performance reasons. apu_enabled: bool, /// If the DMA is enabled, it will be clocked. /// /// This is a clone of the configuration value /// kept for performance reasons. dma_enabled: bool, /// If the timer is enabled, it will be clocked. /// /// This is a clone of the configuration value /// kept for performance reasons. timer_enabled: bool, /// If the serial is enabled, it will be clocked. /// /// This is a clone of the configuration value /// kept for performance reasons. serial_enabled: bool, /// The current frequency at which the Game Boy /// emulator is being handled. This is a "hint" that /// may help components to adjust their internal /// logic to match the current frequency. For example /// the APU will adjust its internal clock to match /// this hint. /// /// This is a clone of the configuration value /// kept for performance reasons. clock_freq: u32, /// The boot ROM that will (or was) used to boot the /// current Game Boy system. /// /// This should be explicitly set by the developed when /// set the boot ROM in the system's memory. /// /// The loading process used to load the boot ROM is not /// taken in consideration for this value. boot_rom: BootRom, /// Reference to the Game Boy CPU component to be /// used as the main element of the system, when /// clocked, the amount of ticks from it will be /// used as reference or the rest of the components. cpu: Cpu, /// The reference counted and mutable reference to /// Game Boy configuration structure that can be /// used by the GB components to access global /// configuration values on the current emulator. /// /// If performance is required (may value access) /// the values should be cloned and stored locally. gbc: SharedThread<GameBoyConfig>, } #[cfg_attr(feature = "wasm", wasm_bindgen)] impl GameBoy { #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))] pub fn new(mode: Option<GameBoyMode>) -> Self { let mode = mode.unwrap_or(GameBoyMode::Dmg); let gbc = Arc::new(Mutex::new(GameBoyConfig { mode, ppu_enabled: true, apu_enabled: true, dma_enabled: true, timer_enabled: true, serial_enabled: true, clock_freq: GameBoy::CPU_FREQ, })); let components = Components { ppu: Ppu::new(mode, gbc.clone()), apu: Apu::default(), dma: Dma::default(), pad: Pad::default(), timer: Timer::default(), serial: Serial::default(), }; let mmu = Mmu::new(components, mode, gbc.clone()); let cpu = Cpu::new(mmu, gbc.clone()); Self { mode, boot_rom: BootRom::None, ppu_enabled: true, apu_enabled: true, dma_enabled: true, timer_enabled: true, serial_enabled: true, clock_freq: GameBoy::CPU_FREQ, cpu, gbc, } } pub fn verify_rom(data: &[u8]) -> bool { Cartridge::from_data(data).is_ok() } pub fn reset(&mut self) { self.ppu().reset(); self.apu().reset(); self.timer().reset(); self.serial().reset(); self.mmu().reset(); self.cpu.reset(); self.reset_cheats(); } pub fn reload(&mut self) { let rom = self.rom().clone(); self.reset(); self.load(true).unwrap(); self.load_cartridge(rom).unwrap(); } /// Advance the clock of the system by one tick, this will /// usually imply executing one CPU instruction and advancing /// all the other components of the system by the required /// amount of cycles. /// /// This method takes into account the current speed of the /// system (single or double) and will execute the required /// amount of cycles in the other components of the system /// accordingly. /// /// The amount of cycles executed by the CPU is returned. pub fn clock(&mut self) -> u16 { let cycles = self.cpu_clock() as u16; let cycles_n = cycles / self.multiplier() as u16; self.clock_devices(cycles, cycles_n); cycles } /// Risky function that will clock the CPU multiple times /// allowing an undefined number of cycles to be executed /// in the other Game Boy components. /// /// This can cause unwanted behaviour in components like /// the PPU where only one mode switch operation is expected /// per each clock call. /// /// At the end of this execution major synchronization issues /// may arise, so use with caution. pub fn clock_many(&mut self, count: usize) -> u16 { let mut cycles = 0u16; for _ in 0..count { cycles += self.cpu_clock() as u16; } let cycles_n = cycles / self.multiplier() as u16; self.clock_devices(cycles, cycles_n); cycles } /// Function equivalent to `clock()` but that allows pre-emptive /// breaking of the clock cycle loop if the PC (Program Counter) /// reaches the provided address. pub fn clock_step(&mut self, addr: u16) -> u16 { let cycles = self.cpu_clock() as u16; if self.cpu_i().pc() == addr { return cycles; } let cycles_n = cycles / self.multiplier() as u16; self.clock_devices(cycles, cycles_n); cycles } /// Equivalent to `clock()` but allows the execution of multiple /// clock operations in a single call. pub fn clocks(&mut self, count: usize) -> u64 { let mut cycles = 0_u64; for _ in 0..count { cycles += self.clock() as u64; } cycles } /// Clocks the emulator until the limit of cycles that has been /// provided and returns the amount of cycles that have been /// clocked. pub fn clocks_cycles(&mut self, limit: usize) -> u64 { let mut cycles = 0_u64; while cycles < limit as u64 { cycles += self.clock() as u64; } cycles } /// Clocks the emulator until the limit of cycles that has been /// provided and returns the amount of cycles that have been /// clocked together with the frame buffer of the PPU. /// /// Allows a caller to clock the emulator and at the same time /// retrieve the frame buffer of the PPU at the proper timing /// (on V-Blank). /// /// This method allows for complex foreign call optimizations /// by preventing the need to call the emulator clock multiple /// times to obtain the right frame buffer retrieval timing. pub fn clocks_frame_buffer(&mut self, limit: usize) -> ClockFrame { let mut cycles = 0_u64; let mut frames = 0_u16; let mut frame_buffer: Option<Vec<u8>> = None; let mut last_frame = self.ppu_frame(); while cycles < limit as u64 { cycles += self.clock() as u64; if self.ppu_frame() != last_frame { frame_buffer = Some(self.frame_buffer().to_vec()); last_frame = self.ppu_frame(); frames += 1; } } ClockFrame { cycles, frames, frame_buffer, } } pub fn next_frame(&mut self) -> u32 { let mut cycles = 0u32; let current_frame = self.ppu_frame(); while self.ppu_frame() == current_frame { cycles += self.clock() as u32; } cycles } pub fn step_to(&mut self, addr: u16) -> u32 { let mut cycles = 0u32; while self.cpu_i().pc() != addr { cycles += self.clock_step(addr) as u32; } cycles } #[inline(always)] fn clock_devices(&mut self, cycles: u16, cycles_n: u16) { if self.ppu_enabled { self.ppu_clock(cycles_n); } if self.apu_enabled { self.apu_clock(cycles_n); } if self.dma_enabled { self.dma_clock(cycles); } if self.timer_enabled { self.timer_clock(cycles); } if self.serial_enabled { self.serial_clock(cycles); } } pub fn key_press(&mut self, key: PadKey) { self.pad().key_press(key); } pub fn key_lift(&mut self, key: PadKey) { self.pad().key_lift(key); } pub fn cpu_clock(&mut self) -> u8 { self.cpu.clock() } pub fn ppu_clock(&mut self, cycles: u16) { self.ppu().clock(cycles) } pub fn apu_clock(&mut self, cycles: u16) { self.apu().clock(cycles) } pub fn dma_clock(&mut self, cycles: u16) { self.mmu().clock_dma(cycles); } pub fn timer_clock(&mut self, cycles: u16) { self.timer().clock(cycles) } pub fn serial_clock(&mut self, cycles: u16) { self.serial().clock(cycles) } pub fn ppu_ly(&mut self) -> u8 { self.ppu().ly() } pub fn ppu_mode(&mut self) -> PpuMode { self.ppu().mode() } pub fn ppu_frame(&mut self) -> u16 { self.ppu().frame_index() } /// Direct boot method that immediately jumps the machine /// to the post boot state, this will effectively skip the /// boot sequence and jump to the cartridge execution. pub fn boot(&mut self) { self.load_boot_state(); } /// Unsafe load strategy that will panic the current system /// in case there are boot ROM loading issues. pub fn load_unsafe(&mut self, boot: bool) { self.load(boot).unwrap(); } /// Loads the machine directly to after the boot execution state, /// setting the state of the system accordingly and updating the /// Program Counter (PC) to the post boot address (0x0100). /// /// Should allow the machine to jump to the cartridge (ROM) execution /// directly, skipping the boot sequence. /// /// Currently supports only DMG machines. pub fn load_boot_state(&mut self) { self.cpu.boot(); } pub fn vram_eager(&mut self) -> Vec<u8> { self.ppu().vram().to_vec() } pub fn hram_eager(&mut self) -> Vec<u8> { self.ppu().vram().to_vec() } pub fn frame_buffer_eager(&mut self) -> Vec<u8> { self.frame_buffer().to_vec() } pub fn frame_buffer_raw_eager(&mut self) -> Vec<u8> { self.frame_buffer_raw().to_vec() } pub fn audio_buffer_eager(&mut self, clear: bool) -> Vec<u8> { let buffer = Vec::from(self.audio_buffer().clone()); if clear { self.clear_audio_buffer(); } buffer } pub fn audio_output(&self) -> u8 { self.apu_i().output() } pub fn audio_all_output(&self) -> Vec<u8> { vec![ self.audio_output(), self.audio_ch1_output(), self.audio_ch2_output(), self.audio_ch3_output(), self.audio_ch4_output(), ] } pub fn audio_ch1_output(&self) -> u8 { self.apu_i().ch1_output() } pub fn audio_ch2_output(&self) -> u8 { self.apu_i().ch2_output() } pub fn audio_ch3_output(&self) -> u8 { self.apu_i().ch3_output() } pub fn audio_ch4_output(&self) -> u8 { self.apu_i().ch4_output() } pub fn audio_ch1_enabled(&self) -> bool { self.apu_i().ch2_out_enabled() } pub fn set_audio_ch1_enabled(&mut self, enabled: bool) { self.apu().set_ch1_out_enabled(enabled) } pub fn audio_ch2_enabled(&self) -> bool { self.apu_i().ch2_out_enabled() } pub fn set_audio_ch2_enabled(&mut self, enabled: bool) { self.apu().set_ch2_out_enabled(enabled) } pub fn audio_ch3_enabled(&self) -> bool { self.apu_i().ch3_out_enabled() } pub fn set_audio_ch3_enabled(&mut self, enabled: bool) { self.apu().set_ch3_out_enabled(enabled) } pub fn audio_ch4_enabled(&self) -> bool { self.apu_i().ch4_out_enabled() } pub fn set_audio_ch4_enabled(&mut self, enabled: bool) { self.apu().set_ch4_out_enabled(enabled) } pub fn audio_sampling_rate(&self) -> u16 { self.apu_i().sampling_rate() } pub fn audio_channels(&self) -> u8 { self.apu_i().channels() } pub fn cartridge_eager(&mut self) -> Cartridge { self.mmu().rom().clone() } pub fn ram_data_eager(&mut self) -> Vec<u8> { self.mmu().rom().ram_data_eager() } pub fn set_ram_data(&mut self, ram_data: Vec<u8>) { self.mmu().rom().set_ram_data(&ram_data) } pub fn registers(&mut self) -> Registers { let ppu_registers = self.ppu().registers(); Registers { pc: self.cpu.pc, sp: self.cpu.sp, a: self.cpu.a, b: self.cpu.b, c: self.cpu.c, d: self.cpu.d, e: self.cpu.e, h: self.cpu.h, l: self.cpu.l, scy: ppu_registers.scy, scx: ppu_registers.scx, wy: ppu_registers.wy, wx: ppu_registers.wx, ly: ppu_registers.ly, lyc: ppu_registers.lyc, } } /// Obtains the tile structure for the tile at the /// given index, no conversion in the pixel buffer /// is done so that the color reference is the GB one. pub fn get_tile(&mut self, index: usize) -> Tile { self.ppu().tiles()[index] } /// Obtains the pixel buffer for the tile at the /// provided index, converting the color buffer /// using the currently loaded (background) palette. pub fn get_tile_buffer(&mut self, index: usize) -> Vec<u8> { let tile = self.get_tile(index); tile.palette_buffer(self.ppu().palette_bg()) } pub fn is_dmg(&self) -> bool { self.mode == GameBoyMode::Dmg } pub fn is_cgb(&self) -> bool { self.mode == GameBoyMode::Cgb } pub fn is_sgb(&self) -> bool { self.mode == GameBoyMode::Sgb } pub fn speed(&self) -> GameBoySpeed { self.mmu_i().speed() } pub fn multiplier(&self) -> u8 { self.mmu_i().speed().multiplier() } pub fn mode(&self) -> GameBoyMode { self.mode } pub fn set_mode(&mut self, value: GameBoyMode) { self.mode = value; (*self.gbc).lock().unwrap().set_mode(value); self.mmu().set_mode(value); self.ppu().set_gb_mode(value); } pub fn ppu_enabled(&self) -> bool { self.ppu_enabled } pub fn set_ppu_enabled(&mut self, value: bool) { self.ppu_enabled = value; (*self.gbc).lock().unwrap().set_ppu_enabled(value); } pub fn apu_enabled(&self) -> bool { self.apu_enabled } pub fn set_apu_enabled(&mut self, value: bool) { self.apu_enabled = value; (*self.gbc).lock().unwrap().set_apu_enabled(value); } pub fn dma_enabled(&self) -> bool { self.dma_enabled } pub fn set_dma_enabled(&mut self, value: bool) { self.dma_enabled = value; (*self.gbc).lock().unwrap().set_dma_enabled(value); } pub fn timer_enabled(&self) -> bool { self.timer_enabled } pub fn set_timer_enabled(&mut self, value: bool) { self.timer_enabled = value; (*self.gbc).lock().unwrap().set_timer_enabled(value); } pub fn serial_enabled(&self) -> bool { self.serial_enabled } pub fn set_serial_enabled(&mut self, value: bool) { self.serial_enabled = value; (*self.gbc).lock().unwrap().set_serial_enabled(value); } pub fn set_all_enabled(&mut self, value: bool) { self.set_ppu_enabled(value); self.set_apu_enabled(value); self.set_dma_enabled(value); self.set_timer_enabled(value); self.set_serial_enabled(value); } pub fn clock_freq(&self) -> u32 { self.clock_freq } pub fn set_clock_freq(&mut self, value: u32) { self.clock_freq = value; (*self.gbc).lock().unwrap().set_clock_freq(value); self.apu().set_clock_freq(value); } pub fn clock_freq_s(&self) -> String { format!("{:.02} Mhz", self.clock_freq() as f32 / 1000.0 / 1000.0) } pub fn boot_rom(&self) -> BootRom { self.boot_rom } pub fn set_boot_rom(&mut self, value: BootRom) { self.boot_rom = value; } pub fn boot_rom_s(&self) -> String { String::from(self.boot_rom().description()) } pub fn attach_null_serial(&mut self) { self.attach_serial(Box::<NullDevice>::default()); } pub fn attach_stdout_serial(&mut self) { self.attach_serial(Box::<StdoutDevice>::default()); } pub fn attach_printer_serial(&mut self) { self.attach_serial(Box::<PrinterDevice>::default()); } pub fn display_width(&self) -> usize { DISPLAY_WIDTH } pub fn display_height(&self) -> usize { DISPLAY_HEIGHT } pub fn ram_size(&self) -> RamSize { match self.mode { GameBoyMode::Dmg => RamSize::Size8K, GameBoyMode::Cgb => RamSize::Size32K, GameBoyMode::Sgb => RamSize::Size8K, } } pub fn vram_size(&self) -> RamSize { match self.mode { GameBoyMode::Dmg => RamSize::Size8K, GameBoyMode::Cgb => RamSize::Size16K, GameBoyMode::Sgb => RamSize::Size8K, } } pub fn description(&self, column_length: usize) -> String { let version_l = format!("{:width$}", "Version", width = column_length); let mode_l = format!("{:width$}", "Mode", width = column_length); let boot_rom_l = format!("{:width$}", "Boot ROM", width = column_length); let clock_l = format!("{:width$}", "Clock", width = column_length); let ram_size_l = format!("{:width$}", "RAM Size", width = column_length); let vram_size_l = format!("{:width$}", "VRAM Size", width = column_length); let serial_l = format!("{:width$}", "Serial", width = column_length); format!( "{} {}\n{} {}\n{} {}\n{} {}\n{} {}\n{} {}\n{} {}", version_l, Info::version(), mode_l, self.mode(), boot_rom_l, self.boot_rom(), clock_l, self.clock_freq_s(), ram_size_l, self.ram_size(), vram_size_l, self.vram_size(), serial_l, self.serial_i().device().description(), ) } } /// Gameboy implementations that are meant with performance /// in mind and that do not support WASM interface of copy. impl GameBoy { /// The logic frequency of the Game Boy /// CPU in hz. pub const CPU_FREQ: u32 = 4194304; /// The visual frequency (refresh rate) /// of the Game Boy, close to 60 hz. pub const VISUAL_FREQ: f32 = 59.7275; /// The cycles taken to run a complete frame /// loop in the Game Boy's PPU (in CPU cycles). pub const LCD_CYCLES: u32 = 70224; pub fn cpu(&mut self) -> &mut Cpu { &mut self.cpu } pub fn cpu_i(&self) -> &Cpu { &self.cpu } pub fn mmu(&mut self) -> &mut Mmu { self.cpu.mmu() } pub fn mmu_i(&self) -> &Mmu { self.cpu.mmu_i() } pub fn ppu(&mut self) -> &mut Ppu { self.cpu.ppu() } pub fn ppu_i(&self) -> &Ppu { self.cpu.ppu_i() } pub fn apu(&mut self) -> &mut Apu { self.cpu.apu() } pub fn apu_i(&self) -> &Apu { self.cpu.apu_i() } pub fn dma(&mut self) -> &mut Dma { self.cpu.dma() } pub fn dma_i(&self) -> &Dma { self.cpu.dma_i() } pub fn pad(&mut self) -> &mut Pad { self.cpu.pad() } pub fn pad_i(&self) -> &Pad { self.cpu.pad_i() } pub fn timer(&mut self) -> &mut Timer { self.cpu.timer() } pub fn timer_i(&self) -> &Timer { self.cpu.timer_i() } pub fn serial(&mut self) -> &mut Serial { self.cpu.serial() } pub fn serial_i(&self) -> &Serial { self.cpu.serial_i() } pub fn rom(&mut self) -> &mut Cartridge { self.mmu().rom() } pub fn rom_i(&self) -> &Cartridge { self.mmu_i().rom_i() } pub fn frame_buffer(&mut self) -> &[u8; FRAME_BUFFER_SIZE] { self.ppu().frame_buffer() } pub fn frame_buffer_xrgb8888(&mut self) -> [u8; FRAME_BUFFER_XRGB8888_SIZE] { self.ppu().frame_buffer_xrgb8888() } pub fn frame_buffer_xrgb8888_u32(&mut self) -> [u32; FRAME_BUFFER_SIZE] { self.ppu().frame_buffer_xrgb8888_u32() } pub fn frame_buffer_rgb1555(&mut self) -> [u8; FRAME_BUFFER_RGB1555_SIZE] { self.ppu().frame_buffer_rgb1555() } pub fn frame_buffer_rgb1555_u16(&mut self) -> [u16; FRAME_BUFFER_SIZE] { self.ppu().frame_buffer_rgb1555_u16() } pub fn frame_buffer_rgb565(&mut self) -> [u8; FRAME_BUFFER_RGB565_SIZE] { self.ppu().frame_buffer_rgb565() } pub fn frame_buffer_rgb565_u16(&mut self) -> [u16; FRAME_BUFFER_SIZE] { self.ppu().frame_buffer_rgb565_u16() } pub fn frame_buffer_raw(&mut self) -> [u8; FRAME_BUFFER_SIZE] { self.ppu().frame_buffer_raw() } pub fn audio_buffer(&mut self) -> &VecDeque<u8> { self.apu().audio_buffer() } pub fn cartridge(&mut self) -> &mut Cartridge { self.mmu().rom() } pub fn cartridge_i(&self) -> &Cartridge { self.mmu_i().rom_i() } pub fn load(&mut self, boot: bool) -> Result<(), Error> { let boot_rom = self.boot_rom().reusable(self.mode()); match self.mode() { GameBoyMode::Dmg => self.load_dmg(boot, boot_rom)?, GameBoyMode::Cgb => self.load_cgb(boot, boot_rom)?, GameBoyMode::Sgb => unimplemented!("SGB is not supported"), } Ok(()) } pub fn load_dmg(&mut self, boot: bool, boot_rom: Option<BootRom>) -> Result<(), Error> { self.mmu().allocate_dmg(); if boot { self.load_boot_dmg(boot_rom)?; } Ok(()) } pub fn load_cgb(&mut self, boot: bool, boot_rom: Option<BootRom>) -> Result<(), Error> { self.mmu().allocate_cgb(); if boot { self.load_boot_cgb(boot_rom)?; } Ok(()) } pub fn load_boot(&mut self, data: &[u8]) { self.cpu.mmu().write_boot(0x0000, data); } pub fn load_boot_path(&mut self, path: &str) -> Result<(), Error> { let data = read_file(path)?; self.load_boot(&data); self.boot_rom = BootRom::Other; Ok(()) } pub fn load_boot_static(&mut self, boot_rom: BootRom) { match boot_rom { BootRom::Dmg => self.load_boot(&DMG_BOOT), BootRom::Sgb => self.load_boot(&SGB_BOOT), BootRom::DmgBootix => self.load_boot(&DMG_BOOTIX), BootRom::MgbBootix => self.load_boot(&MGB_BOOTIX), BootRom::Cgb => self.load_boot(&CGB_BOOT), BootRom::CgbBoytacean => self.load_boot(&CGB_BOYTACEAN), BootRom::Other | BootRom::None => (), } self.boot_rom = boot_rom; } pub fn load_boot_file(&mut self, boot_rom: BootRom) -> Result<(), Error> { match boot_rom { BootRom::Dmg => self.load_boot_path("./res/boot/dmg_boot.bin")?, BootRom::Sgb => self.load_boot_path("./res/boot/sgb_boot.bin")?, BootRom::DmgBootix => self.load_boot_path("./res/boot/dmg_bootix.bin")?, BootRom::MgbBootix => self.load_boot_path("./res/boot/mgb_bootix.bin")?, BootRom::Cgb => self.load_boot_path("./res/boot/cgb_boot.bin")?, BootRom::CgbBoytacean => self.load_boot_path("./res/boot/cgb_boytacean.bin")?, BootRom::Other | BootRom::None => (), } self.boot_rom = boot_rom; Ok(()) } pub fn load_boot_default(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> { self.load_boot_dmg(boot_rom) } pub fn load_boot_smart(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> { match self.mode() { GameBoyMode::Dmg => self.load_boot_dmg(boot_rom)?, GameBoyMode::Cgb => self.load_boot_cgb(boot_rom)?, GameBoyMode::Sgb => unimplemented!("SGB is not supported"), } Ok(()) } pub fn load_boot_dmg(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> { let boot_rom = boot_rom.unwrap_or(BootRom::DmgBootix); if !boot_rom.is_dmg_compat() { return Err(Error::IncompatibleBootRom); } self.load_boot_static(boot_rom); Ok(()) } pub fn load_boot_cgb(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> { let boot_rom = boot_rom.unwrap_or(BootRom::CgbBoytacean); if !boot_rom.is_cgb_compat() { return Err(Error::IncompatibleBootRom); } self.load_boot_static(boot_rom); Ok(()) } pub fn load_boot_default_f(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> { self.load_boot_dmg_f(boot_rom)?; Ok(()) } pub fn load_boot_smart_f(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> { match self.mode() { GameBoyMode::Dmg => self.load_boot_dmg_f(boot_rom)?, GameBoyMode::Cgb => self.load_boot_cgb_f(boot_rom)?, GameBoyMode::Sgb => unimplemented!("SGB is not supported"), } Ok(()) } pub fn load_boot_dmg_f(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> { let boot_rom = boot_rom.unwrap_or(BootRom::DmgBootix); if !boot_rom.is_dmg_compat() { return Err(Error::IncompatibleBootRom); } self.load_boot_file(boot_rom)?; Ok(()) } pub fn load_boot_cgb_f(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> { let boot_rom = boot_rom.unwrap_or(BootRom::Cgb); if !boot_rom.is_cgb_compat() { return Err(Error::IncompatibleBootRom); } self.load_boot_file(boot_rom)?; Ok(()) } pub fn load_cartridge(&mut self, rom: Cartridge) -> Result<&mut Cartridge, Error> { self.mmu().set_rom(rom); Ok(self.mmu().rom()) } pub fn load_rom( &mut self, data: &[u8], ram_data: Option<&[u8]>, ) -> Result<&mut Cartridge, Error> { let mut rom = Cartridge::from_data(data)?; if let Some(ram_data) = ram_data { rom.set_ram_data(ram_data) } self.load_cartridge(rom) } pub fn load_rom_file( &mut self, path: &str, ram_path: Option<&str>, ) -> Result<&mut Cartridge, Error> { let data = read_file(path)?; match ram_path { Some(ram_path) => { let ram_data = read_file(ram_path)?; self.load_rom(&data, Some(&ram_data)) } None => self.load_rom(&data, None), } } pub fn attach_serial(&mut self, device: Box<dyn SerialDevice>) { self.serial().set_device(device); } pub fn read_memory(&mut self, addr: u16) -> u8 { self.mmu().read(addr) } pub fn write_memory(&mut self, addr: u16, value: u8) { self.mmu().write(addr, value); } pub fn set_speed_callback(&mut self, callback: fn(speed: GameBoySpeed)) { self.mmu().set_speed_callback(callback); } pub fn reset_cheats(&mut self) { self.reset_game_genie(); self.reset_game_shark(); } pub fn add_cheat_code(&mut self, code: &str) -> Result<bool, Error> { if GameGenie::is_code(code) { return self.add_game_genie_code(code).map(|_| true); } if GameShark::is_code(code) { return self.add_game_shark_code(code).map(|_| true); } Err(Error::CustomError(String::from("Not a valid cheat code"))) } pub fn add_game_genie_code(&mut self, code: &str) -> Result<&GameGenieCode, Error> { let rom = self.mmu().rom(); if rom.game_genie().is_none() { let game_genie = GameGenie::default(); rom.attach_genie(game_genie); } let game_genie = rom.game_genie_mut().as_mut().unwrap(); game_genie.add_code(code) } pub fn add_game_shark_code(&mut self, code: &str) -> Result<&GameSharkCode, Error> { let rom = self.rom(); if rom.game_shark().is_none() { let game_shark = GameShark::default(); rom.attach_shark(game_shark); } let game_shark = rom.game_shark_mut().as_mut().unwrap(); game_shark.add_code(code) } pub fn reset_game_genie(&mut self) { let rom = self.rom(); if rom.game_genie().is_some() { rom.game_genie_mut().as_mut().unwrap().reset(); } } pub fn reset_game_shark(&mut self) { let rom = self.mmu().rom(); if rom.game_shark().is_some() { rom.game_shark_mut().as_mut().unwrap().reset(); } } } #[cfg(feature = "wasm")] #[cfg_attr(feature = "wasm", wasm_bindgen)] impl GameBoy { pub fn set_panic_hook_wa() { let prev = take_hook(); set_hook(Box::new(move |info| { hook_impl(info); prev(info); })); } pub fn load_rom_wa(&mut self, data: &[u8]) -> Result<Cartridge, String> { let rom = self.load_rom(data, None).map_err(|e| e.to_string())?; rom.set_rumble_cb(|active| { rumble_callback(active); }); Ok(rom.clone()) } pub fn load_callbacks_wa(&mut self) { self.set_speed_callback(|speed| { speed_callback(speed); }); } pub fn load_null_wa(&mut self) { let null = Box::<NullDevice>::default(); self.attach_serial(null); } pub fn load_logger_wa(&mut self) { let mut logger = Box::<StdoutDevice>::default(); logger.set_callback(|data| { logger_callback(data.to_vec()); }); self.attach_serial(logger); } pub fn load_printer_wa(&mut self) { let mut printer = Box::<PrinterDevice>::default(); printer.set_callback(|image_buffer| { printer_callback(image_buffer.to_vec()); }); self.attach_serial(printer); } /// Updates the emulation mode using the cartridge info /// for the provided data to obtain the CGB flag value /// and set the mode accordingly. /// /// This can be an expensive operation as it will require /// cartridge data parsing to obtain the CGB flag. /// It will also have to clone the data buffer. pub fn infer_mode_wa(&mut self, data: &[u8]) -> Result<(), String> { let mode = Cartridge::from_data(data) .map_err(|e| e.to_string())? .gb_mode(); self.set_mode(mode); Ok(()) } pub fn set_palette_colors_wa(&mut self, value: Vec<JsValue>) { let palette: Palette = value .into_iter() .map(|v| Self::js_to_pixel(&v)) .collect::<Vec<Pixel>>() .try_into() .unwrap(); self.ppu().set_palette_colors(&palette); } pub fn wasm_engine_wa(&self) -> Option<String> { let dependencies = dependencies_map(); if !dependencies.contains_key("wasm-bindgen") { return None; } Some(String::from(format!( "wasm-bindgen/{}", *dependencies.get("wasm-bindgen").unwrap() ))) } fn js_to_pixel(value: &JsValue) -> Pixel { value .as_string() .unwrap() .chars() .collect::<Vec<char>>() .chunks(2) .map(|s| s.iter().collect::<String>()) .map(|s| u8::from_str_radix(&s, 16).unwrap()) .collect::<Vec<u8>>() .try_into() .unwrap() } } #[cfg(feature = "wasm")] #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = window)] fn panic(message: &str); #[wasm_bindgen(js_namespace = window, js_name = speedCallback)] fn speed_callback(speed: GameBoySpeed); #[wasm_bindgen(js_namespace = window, js_name = loggerCallback)] fn logger_callback(data: Vec<u8>); #[wasm_bindgen(js_namespace = window, js_name = printerCallback)] fn printer_callback(image_buffer: Vec<u8>); #[wasm_bindgen(js_namespace = window, js_name = rumbleCallback)] fn rumble_callback(active: bool); } #[cfg(feature = "wasm")] pub fn hook_impl(info: &PanicInfo) { let message = info.to_string(); panic(message.as_str()); } impl AudioProvider for GameBoy { fn audio_output(&self) -> u8 { self.apu_i().output() } fn audio_buffer(&self) -> &VecDeque<u8> { self.apu_i().audio_buffer() } fn clear_audio_buffer(&mut self) { self.apu().clear_audio_buffer() } } impl Default for GameBoy { fn default() -> Self { Self::new(None) } } impl Display for GameBoy { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.description(9)) } }