diff --git a/lib/src/constants.rs b/lib/src/constants.rs index 3c4d28d..c7d16e5 100644 --- a/lib/src/constants.rs +++ b/lib/src/constants.rs @@ -1,10 +1,2 @@ -use once_cell::sync::OnceCell; - // Hz pub const CLOCK_SPEED: usize = 4194304; - -pub(crate) static STATIC_IS_CGB: OnceCell = OnceCell::new(); - -pub(crate) fn is_cgb() -> bool { - STATIC_IS_CGB.get().copied().unwrap_or(false) -} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index fa01c19..979c02d 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -8,9 +8,8 @@ use connect::{ AudioOutput, CameraWrapper, CameraWrapperRef, EmulatorMessage, EmulatorOptions, NoCamera, PocketCamera, Renderer, RomFile, SerialTarget, }; -use constants::{is_cgb, STATIC_IS_CGB}; use processor::{ - memory::{mmio::gpu::Colour, rom::CgbRomType, Rom}, + memory::{mmio::gpu::Colour, rom::CgbRomType, OutputTargets, Rom}, Cpu, CpuSaveState, }; use std::{ @@ -77,14 +76,14 @@ where RomFile::Raw(data) => Rom::load(data, None, camera.clone()), }; - let _ = STATIC_IS_CGB.set(rom.rom_type == CgbRomType::CgbOnly || options.cgb_mode); + let cgb = rom.rom_type == CgbRomType::CgbOnly || options.cgb_mode; options.window.prepare(WIDTH, HEIGHT); options.window.set_title(format!( "{} on {} on {}", rom.get_title(), rom.mbc_type(), - if is_cgb() { "CGB" } else { "DMG" } + if cgb { "CGB" } else { "DMG" } )); let bootrom_enabled = options.bootrom.is_some(); @@ -103,13 +102,16 @@ where receiver, Cpu::new( Memory::init( + cgb, bootrom, rom, - options.window, - options.output, - options.serial_target, - options.tile_window, - camera, + OutputTargets::new( + options.window, + options.output, + options.serial_target, + options.tile_window, + camera, + ), ), bootrom_enabled, ), diff --git a/lib/src/processor/memory.rs b/lib/src/processor/memory.rs index 365d9c7..96fbe09 100644 --- a/lib/src/processor/memory.rs +++ b/lib/src/processor/memory.rs @@ -1,6 +1,8 @@ +use std::marker::PhantomData; + pub use self::rom::Rom; use self::{ - addresses::{Address, AddressMarker, IoAddress}, + addresses::{Address, AddressMarker, CgbIoAddress, IoAddress}, mmio::{ apu::ApuSaveState, gpu::{Colour, GpuSaveState}, @@ -11,7 +13,6 @@ use self::{ }; use crate::{ connect::{AudioOutput, CameraWrapperRef, JoypadState, PocketCamera, Renderer, SerialTarget}, - constants::is_cgb, Cpu, }; @@ -22,6 +23,63 @@ pub(crate) mod addresses; pub mod mmio; pub(crate) mod rom; +#[serde_with::serde_as] +#[derive(Serialize, Deserialize, Clone)] +pub struct Wram { + #[serde_as(as = "[_; 4096]")] + bank_0: [u8; 4096], + banks: WramBanks, +} + +#[serde_with::serde_as] +#[derive(Serialize, Deserialize, Clone)] +enum WramBanks { + Dmg { + #[serde_as(as = "Box<[_; 4096]>")] + bank: Box<[u8; 4096]>, + }, + Cgb { + #[serde_as(as = "Box<[[_; 4096]; 7]>")] + banks: Box<[[u8; 4096]; 7]>, + selected: usize, + }, +} + +impl Wram { + fn new(cgb: bool) -> Self { + Self { + bank_0: [0; 4096], + banks: if cgb { + WramBanks::Cgb { + banks: Box::new([[0; 4096]; 7]), + selected: 0, + } + } else { + WramBanks::Dmg { + bank: Box::new([0; 4096]), + } + }, + } + } + + fn get_banked(&self, address: usize) -> u8 { + match &self.banks { + WramBanks::Dmg { bank } => bank[address], + WramBanks::Cgb { banks, selected } => banks[*selected][address], + } + } + + fn set_banked(&mut self, address: usize, data: u8) { + match self.banks { + WramBanks::Dmg { ref mut bank } => bank[address] = data, + WramBanks::Cgb { + ref mut banks, + selected, + } => banks[selected][address] = data, + } + } +} + pub struct Memory where ColourFormat: From + Clone, @@ -30,7 +88,7 @@ where { bootrom: Option>, rom: Rom, - ram: [u8; 8192], + ram: Wram, cpu_ram: [u8; 128], pub(super) interrupts: Interrupts, pub(super) ime: bool, @@ -53,8 +111,7 @@ where R: Renderer, { rom: RomSaveState, - #[serde_as(as = "[_; 8192]")] - ram: [u8; 8192], + ram: Wram, #[serde_as(as = "[_; 128]")] cpu_ram: [u8; 128], pub(super) interrupts: Interrupts, @@ -78,7 +135,7 @@ where pub fn create(memory: &Memory) -> Self { Self { rom: RomSaveState::create(&memory.rom), - ram: memory.ram, + ram: memory.ram.clone(), cpu_ram: memory.cpu_ram, interrupts: memory.interrupts, ime: memory.ime, @@ -94,6 +151,44 @@ where } } +pub(crate) struct OutputTargets +where + ColourFormat: From + Clone, + R: Renderer, + C: PocketCamera + Send + 'static, +{ + window: R, + audio: AudioOutput, + serial_target: SerialTarget, + tile_window: Option, + camera: CameraWrapperRef, + _phantom: PhantomData, +} + +impl OutputTargets +where + ColourFormat: From + Clone, + R: Renderer, + C: PocketCamera + Send + 'static, +{ + pub(crate) fn new( + window: R, + audio: AudioOutput, + serial_target: SerialTarget, + tile_window: Option, + camera: CameraWrapperRef, + ) -> Self { + Self { + window, + audio, + serial_target, + tile_window, + camera, + _phantom: PhantomData, + } + } +} + impl Memory where ColourFormat: From + Clone, @@ -101,18 +196,15 @@ where C: PocketCamera + Send + 'static, { pub(crate) fn init( + cgb: bool, bootrom: Option>, rom: Rom, - window: R, - output: AudioOutput, - serial_target: SerialTarget, - tile_window: Option, - camera: CameraWrapperRef, + output: OutputTargets, ) -> Self { Self { bootrom, rom, - ram: [0x0; 8192], + ram: Wram::new(cgb), cpu_ram: [0x0; 128], interrupts: Interrupts::default(), ime: false, @@ -120,11 +212,11 @@ where user_mode: false, dma: OamDma::default(), joypad: Joypad::default(), - gpu: Gpu::new(window, tile_window), - apu: Apu::new(output), - serial: Serial::new(serial_target), + gpu: Gpu::new(output.window, output.tile_window), + apu: Apu::new(output.audio), + serial: Serial::new(output.serial_target), timers: Timer::init(), - camera, + camera: output.camera, } } @@ -156,9 +248,9 @@ where } Address::Vram(address) => self.gpu.get_vram(address), Address::CartRam(address) => self.rom.get_ram(address), - Address::WorkRam(address) => self.ram[address.get_local() as usize], - Address::BankedWorkRam(address) => self.ram[(address.get_local() + 0x1000) as usize], - Address::MirroredRam(address) => self.ram[address.get_local() as usize], + Address::WorkRam(address) => self.ram.bank_0[address.get_local() as usize], + Address::BankedWorkRam(address) => self.ram.get_banked((address.get_local()) as usize), + Address::MirroredRam(address) => self.ram.bank_0[address.get_local() as usize], Address::Oam(address) => self.gpu.get_oam(address), Address::Prohibited(_) => 0xFF, Address::Io(address) => self.get_io(address), @@ -193,11 +285,11 @@ where } Address::Vram(address) => self.gpu.set_vram(address, data), Address::CartRam(address) => self.rom.set_ram(address, data), - Address::WorkRam(address) => self.ram[address.get_local() as usize] = data, + Address::WorkRam(address) => self.ram.bank_0[address.get_local() as usize] = data, Address::BankedWorkRam(address) => { - self.ram[(address.get_local() + 0x1000) as usize] = data + self.ram.set_banked(address.get_local() as usize, data) } - Address::MirroredRam(address) => self.ram[address.get_local() as usize] = data, + Address::MirroredRam(address) => self.ram.bank_0[address.get_local() as usize] = data, Address::Oam(address) => self.gpu.set_oam(address, data), Address::Prohibited(_) => {} Address::Io(address) => { @@ -244,9 +336,15 @@ where 0xFF4B => self.gpu.get_wx(), 0x0..0xFF40 | 0xFF4C..=0xFFFF => unreachable!(), }, - IoAddress::Cgb(_) => { - if is_cgb() { - todo!(); + IoAddress::Cgb(address) => { + if let WramBanks::Cgb { banks: _, selected } = &self.ram.banks { + match address { + CgbIoAddress::WramBank => ((*selected) & 0b111) as u8, + CgbIoAddress::Unused(v) => { + println!("attempt to get 0x{v:0>4X}"); + todo!() + } + } } else { 0xFF } @@ -290,9 +388,15 @@ where 0xFF4B => self.gpu.update_wx(data), 0x0..0xFF40 | 0xFF4C..=0xFFFF => unreachable!(), }, - IoAddress::Cgb(_) => { - if is_cgb() { - todo!() + IoAddress::Cgb(address) => { + if let WramBanks::Cgb { banks: _, selected } = &mut self.ram.banks { + match address { + CgbIoAddress::WramBank => *selected = (data & 0b111).max(1) as usize, + CgbIoAddress::Unused(v) => { + println!("attempt to set 0x{v:0>4X} to 0x{data:0>2X}"); + todo!() + } + } } } IoAddress::Unused(_) => {} diff --git a/lib/src/processor/memory/addresses.rs b/lib/src/processor/memory/addresses.rs index 3a932ff..73f3daf 100644 --- a/lib/src/processor/memory/addresses.rs +++ b/lib/src/processor/memory/addresses.rs @@ -40,7 +40,10 @@ pub(crate) enum IoAddress { Unused(u16), } -pub(crate) enum CgbIoAddress {} +pub(crate) enum CgbIoAddress { + WramBank, + Unused(u16), +} pub(crate) enum Address { Rom(RomAddress), @@ -105,8 +108,7 @@ impl TryInto for u16 { 0xFF30..0xFF40 => Ok(IoAddress::WaveRam(self.try_into().unwrap())), 0xFF40..0xFF4C => Ok(IoAddress::Video(self.try_into().unwrap())), 0xFF4D..0xFF78 => Ok(IoAddress::Cgb(self.try_into().unwrap())), - 0x0..0xFF00 => Err(AddressError::OutOfBounds), - 0xFFFF => Err(AddressError::OutOfBounds), + 0x0..0xFF00 | 0xFFFF => Err(AddressError::OutOfBounds), _ => Ok(IoAddress::Unused(self)), } } @@ -116,7 +118,11 @@ impl TryInto for u16 { type Error = AddressError; fn try_into(self) -> Result { - todo!() + match self { + 0xFF70 => Ok(CgbIoAddress::WramBank), + 0x0..0xFF4D | 0xFF78..=0xFFFF => Err(AddressError::OutOfBounds), + _ => Ok(CgbIoAddress::Unused(self)), + } } } @@ -147,7 +153,10 @@ impl AddressMarker for IoAddress { impl AddressMarker for CgbIoAddress { fn inner(&self) -> u16 { - todo!() + match self { + CgbIoAddress::Unused(v) => *v, + CgbIoAddress::WramBank => 0xFF70, + } } }