Skip to content
Snippets Groups Projects
gb.rs 29.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • use std::{
        cell::RefCell,
        collections::VecDeque,
        fmt::{self, Display, Formatter},
        rc::Rc,
    };
    
    
    use crate::{
    
        data::{BootRom, CGB_BOOT, DMG_BOOT, DMG_BOOTIX, MGB_BOOTIX, SGB_BOOT},
    
        devices::{printer::PrinterDevice, stdout::StdoutDevice},
    
        gen::{COMPILATION_DATE, COMPILATION_TIME, COMPILER, COMPILER_VERSION, VERSION},
    
        pad::{Pad, PadKey},
    
    João Magalhães's avatar
    João Magalhães committed
        ppu::{Ppu, PpuMode, Tile, DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_SIZE},
    
        rom::{Cartridge, RamSize},
    
        serial::{NullDevice, Serial, SerialDevice},
    
        timer::Timer,
        util::read_file,
    
    #[cfg(feature = "wasm")]
    use wasm_bindgen::prelude::*;
    
    
    #[cfg(feature = "wasm")]
    
    use crate::{
        gen::dependencies_map,
        ppu::{Palette, Pixel},
    };
    
    João Magalhães's avatar
    João Magalhães committed
    
    #[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 {
    
    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" => GameBoyMode::Dmg,
                "cgb" => GameBoyMode::Cgb,
                "sgb" => GameBoyMode::Sgb,
                _ => panic!("Invalid mode value: {}", value),
            }
        }
    
    
        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 {
    
        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,
    
                timer_enabled: true,
                serial_enabled: true,
                clock_freq: GameBoy::CPU_FREQ,
            }
        }
    }
    
    
    /// Aggregation structure tha allows the bundling of
    /// all the components of a gameboy into a single a
    /// single element for easy access.
    
    João Magalhães's avatar
    João Magalhães committed
    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);
    }
    
    
    /// 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,
    
    
        /// 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: Rc<RefCell<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 = Rc::new(RefCell::new(GameBoyConfig {
    
                ppu_enabled: true,
    
                apu_enabled: true,
    
                timer_enabled: true,
    
                serial_enabled: true,
    
                clock_freq: GameBoy::CPU_FREQ,
    
    João Magalhães's avatar
    João Magalhães committed
            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,
                ppu_enabled: true,
                apu_enabled: true,
    
                timer_enabled: true,
                serial_enabled: true,
                clock_freq: GameBoy::CPU_FREQ,
                cpu,
                gbc,
            }
    
        pub fn reset(&mut self) {
            self.ppu().reset();
    
            self.apu().reset();
    
            self.timer().reset();
            self.serial().reset();
    
            self.mmu().reset();
            self.cpu.reset();
        }
    
    
        pub fn clock(&mut self) -> u16 {
            let cycles = self.cpu_clock() as u16;
            let cycles_n = cycles / self.multiplier() as 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);
            }
            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.
    
        pub fn clock_m(&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;
    
            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 {
    
        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()
    
        pub fn boot(&mut self) {
            self.cpu.boot();
    
        pub fn load(&mut self, boot: bool) {
    
            match self.mode() {
    
                GameBoyMode::Dmg => self.load_dmg(boot),
                GameBoyMode::Cgb => self.load_cgb(boot),
                GameBoyMode::Sgb => todo!(),
    
            }
        }
    
        pub fn load_dmg(&mut self, boot: bool) {
            self.mmu().allocate_dmg();
            if boot {
                self.load_boot_dmg();
            }
        }
    
        pub fn load_cgb(&mut self, boot: bool) {
            self.mmu().allocate_cgb();
            if boot {
                self.load_boot_cgb();
            }
        }
    
    
        pub fn load_boot(&mut self, data: &[u8]) {
            self.cpu.mmu().write_boot(0x0000, data);
    
        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::None => (),
    
    João Magalhães's avatar
    João Magalhães committed
        pub fn load_boot_default(&mut self) {
    
            self.load_boot_dmg();
        }
    
        pub fn load_boot_dmg(&mut self) {
    
            self.load_boot_static(BootRom::DmgBootix);
    
        pub fn load_boot_cgb(&mut self) {
            self.load_boot_static(BootRom::Cgb);
        }
    
    
        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 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(&mut self) -> bool {
            self.apu().ch2_enabled()
        }
    
        pub fn set_audio_ch1_enabled(&mut self, enabled: bool) {
    
            self.apu().set_ch1_enabled(enabled)
        }
    
    
        pub fn audio_ch2_enabled(&mut self) -> bool {
            self.apu().ch2_enabled()
        }
    
        pub fn set_audio_ch2_enabled(&mut self, enabled: bool) {
    
            self.apu().set_ch2_enabled(enabled)
        }
    
    
        pub fn audio_ch3_enabled(&mut self) -> bool {
            self.apu().ch3_enabled()
        }
    
        pub fn set_audio_ch3_enabled(&mut self, enabled: bool) {
    
            self.apu().set_ch3_enabled(enabled)
        }
    
    
        pub fn audio_ch4_enabled(&mut self) -> bool {
            self.apu().ch4_enabled()
        }
    
        pub fn set_audio_ch4_enabled(&mut self, enabled: bool) {
    
            self.apu().set_ch4_enabled(enabled)
        }
    
    
        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())
    
    
        /// Obtains the name of the compiler that has been
        /// used in the compilation of the base Boytacean
        /// library. Can be used for diagnostics.
    
        pub fn compiler(&self) -> String {
    
        pub fn compiler_version(&self) -> String {
    
            String::from(COMPILER_VERSION)
        }
    
    
        pub fn compilation_date(&self) -> String {
    
            String::from(COMPILATION_DATE)
        }
    
    
        pub fn compilation_time(&self) -> String {
    
            String::from(COMPILATION_TIME)
        }
    
        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 {
    
        pub fn multiplier(&self) -> u8 {
    
            self.mmu_i().speed().multiplier()
    
        pub fn mode(&self) -> GameBoyMode {
    
        pub fn set_mode(&mut self, value: GameBoyMode) {
    
            self.mode = value;
    
            (*self.gbc).borrow_mut().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).borrow_mut().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).borrow_mut().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).borrow_mut().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).borrow_mut().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).borrow_mut().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).borrow_mut().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)
        }
    
    
    João Magalhães's avatar
    João Magalhães committed
        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 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);
    
                "{}  {}\n{}  {}\n{}  {}\n{}  {}\n{}  {}\n{}  {}",
    
                self.clock_freq_s(),
    
                ram_size_l,
    
                vram_size_l,
    
                self.vram_size(),
    
                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 mmu(&mut self) -> &mut 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 frame_buffer(&mut self) -> &[u8; FRAME_BUFFER_SIZE] {
    
            &(self.ppu().frame_buffer)
        }
    
        pub fn audio_buffer(&mut self) -> &VecDeque<u8> {
    
            self.apu().audio_buffer()
        }
    
    
        pub fn load_boot_path(&mut self, path: &str) {
            let data = read_file(path);
            self.load_boot(&data);
        }
    
        pub fn load_boot_file(&mut self, boot_rom: BootRom) {
            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::None => (),
    
            }
        }
    
        pub fn load_boot_default_f(&mut self) {
    
    João Magalhães's avatar
    João Magalhães committed
            self.load_boot_dmg_f();
    
        }
    
        pub fn load_boot_dmg_f(&mut self) {
    
            self.load_boot_file(BootRom::DmgBootix);
        }
    
    
        pub fn load_boot_cgb_f(&mut self) {
            self.load_boot_file(BootRom::Cgb);
        }
    
    
        pub fn load_rom(&mut self, data: &[u8]) -> &mut Cartridge {
    
            let rom = Cartridge::from_data(data);
            self.mmu().set_rom(rom);
            self.mmu().rom()
        }
    
    
        pub fn load_rom_file(&mut self, path: &str) -> &mut Cartridge {
    
            let data = read_file(path);
            self.load_rom(&data)
        }
    
    
        pub fn attach_serial(&mut self, device: Box<dyn SerialDevice>) {
            self.serial().set_device(device);
        }
    
    
        pub fn set_speed_callback(&mut self, callback: fn(speed: GameBoySpeed)) {
            self.mmu().set_speed_callback(callback);
        }
    
    #[cfg(feature = "wasm")]
    
    #[cfg_attr(feature = "wasm", wasm_bindgen)]
    impl GameBoy {
    
        pub fn set_panic_hook_ws() {