From 27c01fc0893d05aaaca464292321affbc880c6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freitas?= Date: Thu, 25 Jul 2024 21:47:33 +0100 Subject: [PATCH 1/6] Memory restructure Many things: * split memory.rs into multiple files: memory.rs -> lib.rs, map.rs. * Introduce Cartridge to handle multiple cartridge types * mbc_builder to create cartridges according to mbc id * Implement the single mbc_none used in Tetris --- fpt/src/lr35902.rs | 2 +- fpt/src/memory.rs | 436 ---------------------------------- fpt/src/memory/cartridge.rs | 67 ++++++ fpt/src/memory/lib.rs | 262 ++++++++++++++++++++ fpt/src/memory/map.rs | 214 +++++++++++++++++ fpt/src/memory/mbc_builder.rs | 19 ++ fpt/src/memory/mbc_none.rs | 28 +++ fpt/src/memory/mod.rs | 10 + 8 files changed, 601 insertions(+), 437 deletions(-) delete mode 100644 fpt/src/memory.rs create mode 100644 fpt/src/memory/cartridge.rs create mode 100644 fpt/src/memory/lib.rs create mode 100644 fpt/src/memory/map.rs create mode 100644 fpt/src/memory/mbc_builder.rs create mode 100644 fpt/src/memory/mbc_none.rs create mode 100644 fpt/src/memory/mod.rs diff --git a/fpt/src/lr35902.rs b/fpt/src/lr35902.rs index 86dbab9..e1e5260 100644 --- a/fpt/src/lr35902.rs +++ b/fpt/src/lr35902.rs @@ -277,7 +277,7 @@ impl LR35902 { self.bus.write(index as usize, value); // TODO: watchpoint trigger write // Write triggers (TODO: better solution) - if index == memory::map::BANK as u16 && value != 0 { + if (index == memory::map::BANK as u16) && (value != 0) { self.bus.unload_bootrom(); } if index == memory::map::LCDC as u16 diff --git a/fpt/src/memory.rs b/fpt/src/memory.rs deleted file mode 100644 index 43da8a6..0000000 --- a/fpt/src/memory.rs +++ /dev/null @@ -1,436 +0,0 @@ -use std::cell::{Ref, RefCell, RefMut}; -use std::ops::Range; -use std::rc::Rc; - -use crate::bw; - -pub type Address = usize; -pub type MemoryRange = Range
; - -/// You can access these consts like this: -/// ``` -/// assert_eq!(fpt::memory::map::ROM_DATA.start, 0x0100); -/// ``` -pub mod map { - use super::{Address, MemoryRange}; - - //------------------------------------------------------------------------- - // Memory map - //------------------------------------------------------------------------- - - /// This is where the bootrom lives - pub const BOOTROM: MemoryRange = 0x0000..0x0100; - - /// The Cartridge Header - pub const ROM_DATA: MemoryRange = 0x0100..0x0150; - - /// User Program Area (32 KB) - /// 0x0000..0x4000 From cartridge, usually a fixed bank - /// 0x4000..0x8000 From cartridge, switchable bank via mapper (if any) pub const USER_PROGRAM: MemoryRange = 0x0000..0x8000; - - /// Video RAM (8 KB) - In CGB mode, switchable bank 0/1 - pub const VRAM: MemoryRange = 0x8000..0xA000; - - /// External Expansion Working RAM (8 KB) - From cartridge, switchable bank if any - pub const EXT_WRAM: MemoryRange = 0xA000..0xC000; - - /// Unit Working RAM (8 KB) - pub const WRAM: MemoryRange = 0xC000..0xE000; - - /// Not usable (Mirror of C000~DDFF (ECHO RAM)) https://gbdev.io/pandocs/Memory_Map.html#echo-ram - pub const NOT_USABLE1: MemoryRange = 0xE000..0xFE00; - - /// Object Attribute Memory (40 OBJs, 40 x 32 bits) - pub const OAM: MemoryRange = 0xFE00..0xFEA0; - - /// Not usable https://gbdev.io/pandocs/Memory_Map.html#fea0-feff-range - pub const NOT_USABLE2: MemoryRange = 0xFEA0..0xFF00; - - //------------------------------------------------------------------------- - // I/O Registers - //------------------------------------------------------------------------- - - /// Joypad - pub const JOYP: Address = 0xFF00; - /// Serial transfer data - pub const SB: Address = 0xFF01; - /// Serial transfer control - pub const SC: Address = 0xFF02; - /// Divider register - pub const DIV: Address = 0xFF04; - /// Timer counter - pub const TIMA: Address = 0xFF05; - /// Timer modulo - pub const TMA: Address = 0xFF06; - /// Timer control - pub const TAC: Address = 0xFF07; - - //------------------------------------------------------------------------- - // I/O: Sound - //------------------------------------------------------------------------- - - /// Sound channel 1 sweep - pub const NR10: Address = 0xFF10; - /// Sound channel 1 length timer & duty cycle - pub const NR11: Address = 0xFF11; - /// Sound channel 1 volume & envelope - pub const NR12: Address = 0xFF12; - /// Sound channel 1 period low - pub const NR13: Address = 0xFF13; - /// Sound channel 1 period high & control - pub const NR14: Address = 0xFF14; - /// Sound channel 2 length timer & duty cycle - pub const NR21: Address = 0xFF16; - /// Sound channel 2 volume & envelope - pub const NR22: Address = 0xFF17; - /// Sound channel 2 period low - pub const NR23: Address = 0xFF18; - /// Sound channel 2 period high & control - pub const NR24: Address = 0xFF19; - /// Sound channel 3 DAC enable - pub const NR30: Address = 0xFF1A; - /// Sound channel 3 length timer - pub const NR31: Address = 0xFF1B; - /// Sound channel 3 output level - pub const NR32: Address = 0xFF1C; - /// Sound channel 3 period low - pub const NR33: Address = 0xFF1D; - /// Sound channel 3 period high & control - pub const NR34: Address = 0xFF1E; - /// Sound channel 4 length timer - pub const NR41: Address = 0xFF20; - /// Sound channel 4 volume & envelope - pub const NR42: Address = 0xFF21; - /// Sound channel 4 frequency & randomness - pub const NR43: Address = 0xFF22; - /// Sound channel 4 control - pub const NR44: Address = 0xFF23; - /// Master volume & VIN panning - pub const NR50: Address = 0xFF24; - /// Sound panning - pub const NR51: Address = 0xFF25; - /// Sound on/off - pub const NR52: Address = 0xFF26; - /// Wave RAM - pub const WAVE_RAM: MemoryRange = 0xFF30..0xFF40; - - //------------------------------------------------------------------------- - // IO: PPU - //------------------------------------------------------------------------- - - /// LCD control - pub const LCDC: Address = 0xFF40; - /// LCD status - pub const STAT: Address = 0xFF41; - /// Viewport Y position - pub const SCY: Address = 0xFF42; - /// Viewport X position - pub const SCX: Address = 0xFF43; - /// LCD Y coordinate - pub const LY: Address = 0xFF44; - /// LY compare - pub const LYC: Address = 0xFF45; - /// OAM DMA source address & start - pub const DMA: Address = 0xFF46; - /// BG palette data (DMG) - pub const BGP: Address = 0xFF47; - /// OBJ palette 0 data (DMG) - pub const OBP0: Address = 0xFF48; - /// OBJ palette 1 data (DMG) - pub const OBP1: Address = 0xFF49; - /// Window Y position - pub const WY: Address = 0xFF4A; - /// Window X position plus 7 - pub const WX: Address = 0xFF4B; - - /// BANK register: Set to non-zero to disable boot ROM - pub const BANK: Address = 0xFF50; - - //------------------------------------------------------------------------- - // CGB extra - // https://gbdev.io/pandocs/CGB_Registers.html - //------------------------------------------------------------------------- - - /// Prepare speed switch (CGB) - pub const KEY1: Address = 0xFF4C; - /// VRAM bank (CGB) - pub const VBK: Address = 0xFF4F; - /// VRAM DMA source high (CGB) - pub const HDMA1: Address = 0xFF51; - /// VRAM DMA source low (CGB) - pub const HDMA2: Address = 0xFF52; - /// VRAM DMA destination high (CGB) - pub const HDMA3: Address = 0xFF53; - /// VRAM DMA destination low (CGB) - pub const HDMA4: Address = 0xFF54; - /// VRAM DMA length/mode/start (CGB) - pub const HDMA5: Address = 0xFF55; - /// Infrared communications port (GGB) - pub const RP: Address = 0xFF56; - /// Background color palette specification / Background palette index (CGB) - pub const BCPS: Address = 0xFF68; - /// Background color palette data / Background palette data (CGB) - pub const BCPD: Address = 0xFF69; - /// OBJ color palette specification / OBJ palette index (CGB) - pub const OCPS: Address = 0xFF6A; - /// OBJ color palette data / OBJ palette data (CGB) - pub const OCPD: Address = 0xFF6B; - /// Object priority mode (CGB) - pub const OPRI: Address = 0xFF6C; - /// WRAM bank (CGB) pub const SVBK: Address = 0xFF70; - /// Audio digital outputs 1 & 2 (CGB) - pub const PCM12: Address = 0xFF76; - /// Audio digital outputs 3 & 4 (CGB) - pub const PCM34: Address = 0xFF77; - - //------------------------------------------------------------------------- - // High RAM - //------------------------------------------------------------------------- - - /// Working & Stack RAM (127 bytes) - pub const HRAM: MemoryRange = 0xFF80..0xFFFF; - - //------------------------------------------------------------------------- - // Interrupts - //------------------------------------------------------------------------- - - /// Interrupt enable - pub const IE: Address = 0xFFFF; - /// Interrupt flag - pub const IF: Address = 0xFF0F; -} - -#[derive(Clone)] -pub struct Memory { - mem: Vec, - bootrom: &'static [u8; 256], - rom_first256bytes: Vec, - code_listing: Vec>, - pub buttons: Buttons, -} - -#[derive(Clone, Copy, Default, Debug)] -pub struct Buttons { - pub a: bool, - pub b: bool, - pub start: bool, - pub select: bool, - pub up: bool, - pub right: bool, - pub down: bool, - pub left: bool, -} - -impl PartialEq for Memory { - fn eq(&self, other: &Self) -> bool { - self.slice(map::WRAM) == other.slice(map::WRAM) - } -} - -impl Default for Memory { - fn default() -> Self { - Self::new() - } -} - -impl Memory { - pub fn new() -> Self { - const ARRAY_REPEAT_VALUE: Option = None; - Self { - mem: vec![0; 65536], - bootrom: include_bytes!("../dmg.bin"), - rom_first256bytes: vec![0; 256], - code_listing: vec![ARRAY_REPEAT_VALUE; 0xffff + 1], - buttons: Buttons::default(), - } - } - - fn array_ref(&self, from: Address) -> &[u8; N] { - self.mem[from..from + N].try_into().unwrap() // guaranteed to have size N - } - - pub fn slice(&self, range: MemoryRange) -> &[u8] { - &self.mem[range] - } - - pub fn slice_mut(&mut self, range: MemoryRange) -> &mut [u8] { - &mut self.mem[range] - } - - pub fn code_listing(&self) -> &[Option] { - &self.code_listing - } - - pub fn set_code_listing_at(&mut self, pc: u16, v: String) { - self.code_listing[pc as usize] = Some(v); - } -} - -#[derive(Clone, PartialEq)] -pub struct Bus(Rc>); - -impl Bus { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Bus(Rc::new(RefCell::new(Memory::new()))) - } - - pub fn memory(&self) -> Ref { - self.0.borrow() - } - - pub fn memory_mut(&self) -> RefMut { - self.0.borrow_mut() - } - - pub fn load_bootrom(&mut self) { - self.memory_mut().rom_first256bytes = self.copy_range(0x0000..0x0100); - let bootrom = self.memory().bootrom; - self.clone_from_slice(map::BOOTROM, bootrom); - self.memory_mut().code_listing[map::BOOTROM].fill(None); - } - - pub fn unload_bootrom(&mut self) { - let backup = self.memory_mut().rom_first256bytes.clone(); - self.clone_from_slice(map::BOOTROM, &backup); - self.memory_mut().code_listing[map::BOOTROM].fill(None); - } - - pub fn load_cartridge(&mut self, cartridge: &[u8]) { - let l = cartridge.len(); - self.clone_from_slice(0x0000..l, &cartridge[0x0000..l]); - } - - pub fn read(&self, address: Address) -> u8 { - if address == map::JOYP { - self.joyp() - } else { - self.memory().mem[address] - } - } - - pub fn write(&mut self, address: Address, value: u8) { - if address == 0x2000 { - return; - } - self.memory_mut().mem[address] = value; - } - - pub fn clone_from_slice(&mut self, range: MemoryRange, slice: &[u8]) { - self.memory_mut().mem[range.start..range.end].clone_from_slice(slice); - } - - pub fn copy_range(&self, range: MemoryRange) -> Vec { - self.memory_mut().mem[range.start..range.end].to_vec() - } - - pub fn with_slice(&self, range: MemoryRange, reader: impl FnOnce(&[u8]) -> T) -> T { - reader(&self.memory().mem[range]) - } - - /// Runs closure `reader` with access to a fixed-size slice of `N` bytes. - pub fn with_span( - &self, - start: Address, - reader: impl FnOnce(&[u8; N]) -> T, - ) -> T { - reader(self.memory().array_ref(start)) - } - - // registers - pub fn lcdc(&self) -> u8 { - self.read(map::LCDC) - } - - pub fn set_lcdc(&mut self, value: u8) { - self.write(map::LCDC, value); - } - - pub fn stat(&self) -> u8 { - self.read(map::STAT) - } - - pub fn set_stat(&mut self, value: u8) { - self.write(map::STAT, value); - } - - pub fn scy(&self) -> u8 { - self.read(map::SCY) - } - - pub fn set_scy(&mut self, value: u8) { - self.write(map::SCY, value); - } - - pub fn scx(&self) -> u8 { - self.read(map::SCX) - } - - pub fn set_scx(&mut self, value: u8) { - self.write(map::SCX, value); - } - - pub fn ly(&self) -> u8 { - self.read(map::LY) - } - - pub fn set_ly(&mut self, value: u8) { - self.write(map::LY, value); - } - - pub fn lyc(&self) -> u8 { - self.read(map::LYC) - } - - pub fn set_lyc(&mut self, value: u8) { - self.write(map::LYC, value) - } - - pub fn with_vram(&self, reader: impl FnOnce(&[u8]) -> R) -> R { - reader(&self.memory().mem[map::VRAM]) - } - - fn joyp(&self) -> u8 { - let buttons = self.buttons(); - let joyp = self.memory().mem[map::JOYP]; - let sel_buttons = !bw::test_bit8::<5>(joyp); - let sel_dpad = !bw::test_bit8::<4>(joyp); - let b = if sel_dpad && sel_buttons { - 0 - } else if sel_dpad { - ((buttons.down as u8) << 3) - + ((buttons.up as u8) << 2) - + ((buttons.left as u8) << 1) - + (buttons.right as u8) - } else if sel_buttons { - ((buttons.start as u8) << 3) - + ((buttons.select as u8) << 2) - + ((buttons.b as u8) << 1) - + (buttons.a as u8) - } else { - 0 - }; - // Setting higher 2 bits (which are ignored) to 1 just because SameBoy does it too - ((joyp & 0xf0) + (!b & 0x0f)) | 0b1100_0000 - } - - pub fn buttons(&self) -> Buttons { - self.memory().buttons - } - - pub fn set_buttons(&mut self, buttons: &Buttons) { - self.memory_mut().buttons = *buttons; - } - - pub fn ie(&self) -> u8 { - self.read(map::IE) - } - - pub fn iflag(&self) -> u8 { - self.read(map::IF) - } - - pub fn set_iflag(&mut self, value: u8) { - self.write(map::IF, value) - } -} diff --git a/fpt/src/memory/cartridge.rs b/fpt/src/memory/cartridge.rs new file mode 100644 index 0000000..3544065 --- /dev/null +++ b/fpt/src/memory/cartridge.rs @@ -0,0 +1,67 @@ +use crate::memory::map; +use crate::memory::{Address, MemoryRange}; + +pub fn get_cartridge_type(tape: &[u8]) -> u8 { + tape[map::CARTRIDGE_TYPE] +} + +pub trait Cartridge { + fn read(&self, address: Address) -> u8; + fn write(&mut self, address: Address, value: u8); + + fn read_range(&self, memory_range: MemoryRange) -> Vec; + + fn get_title(&self) -> String { + String::from_utf8(self.read_range(map::TITLE)).unwrap() + } + + fn get_manufacturer_code(&self) -> String { + String::from_utf8(self.read_range(map::MANUFACTURER_CODE)).unwrap() + } + + fn get_new_licensee_code(&self) -> String { + String::from_utf8(self.read_range(map::NEW_LICENSEE_CODE)).unwrap() + } + + fn get_sgb_flag(&self) -> u8 { + self.read(map::SGB_FLAG) + } + + fn get_cartridge_type(&self) -> u8 { + self.read(map::CARTRIDGE_TYPE) + } + + fn get_rom_size(&self) -> u8 { + self.read(map::ROM_SIZE) + } + + fn get_ram_size(&self) -> u8 { + self.read(map::RAM_SIZE) + } + + fn get_old_licensee_code(&self) -> u8 { + self.read(map::OLD_LICENSEE_CODE) + } + + fn get_version_number(&self) -> u8 { + self.read(map::VERSION_NUMBER) + } +} + +pub struct EmptyCartridge {} + +impl EmptyCartridge { + pub fn new() -> EmptyCartridge { + EmptyCartridge {} + } +} +impl Cartridge for EmptyCartridge { + fn read(&self, _address: Address) -> u8 { + 0xFF + } + fn write(&mut self, _address: Address, _value: u8) {} + + fn read_range(&self, _memory_range: MemoryRange) -> Vec { + Vec::new() + } +} diff --git a/fpt/src/memory/lib.rs b/fpt/src/memory/lib.rs new file mode 100644 index 0000000..c54cfe9 --- /dev/null +++ b/fpt/src/memory/lib.rs @@ -0,0 +1,262 @@ +use std::cell::{Ref, RefCell, RefMut}; +use std::ops::Range; +use std::rc::Rc; + +use crate::bw; +use crate::memory::map; +use crate::memory::{create_empty_mbc, create_mbc, Cartridge}; + +pub type Address = usize; +pub type MemoryRange = Range
; + +#[derive(Clone)] +pub struct Memory { + mem: Vec, + pub bootrom_loaded: bool, + pub cartridge: Rc>, + bootrom: &'static [u8; 256], + code_listing: Vec>, + pub buttons: Buttons, +} + +#[derive(Clone, Copy, Default, Debug)] +pub struct Buttons { + pub a: bool, + pub b: bool, + pub start: bool, + pub select: bool, + pub up: bool, + pub right: bool, + pub down: bool, + pub left: bool, +} + +impl PartialEq for Memory { + fn eq(&self, other: &Self) -> bool { + self.slice(map::WRAM) == other.slice(map::WRAM) + } +} + +impl Default for Memory { + fn default() -> Self { + Self::new() + } +} + +impl Memory { + pub fn new() -> Self { + const ARRAY_REPEAT_VALUE: Option = None; + Self { + mem: vec![0; 65536], + bootrom_loaded: false, + cartridge: create_empty_mbc(), + bootrom: include_bytes!("../../dmg.bin"), + code_listing: vec![ARRAY_REPEAT_VALUE; 0xffff + 1], + buttons: Buttons::default(), + } + } + + fn array_ref(&self, from: Address) -> &[u8; N] { + self.mem[from..from + N].try_into().unwrap() // guaranteed to have size N + } + + pub fn slice(&self, range: MemoryRange) -> &[u8] { + &self.mem[range] + } + + pub fn slice_mut(&mut self, range: MemoryRange) -> &mut [u8] { + &mut self.mem[range] + } + + pub fn code_listing(&self) -> &[Option] { + &self.code_listing + } + + pub fn set_code_listing_at(&mut self, pc: u16, v: String) { + self.code_listing[pc as usize] = Some(v); + } +} + +#[derive(Clone, PartialEq)] +pub struct Bus(Rc>); + +impl Bus { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Bus(Rc::new(RefCell::new(Memory::new()))) + } + + pub fn memory(&self) -> Ref { + self.0.borrow() + } + + pub fn memory_mut(&self) -> RefMut { + self.0.borrow_mut() + } + + pub fn load_bootrom(&mut self) { + self.memory_mut().bootrom_loaded = true; + //self.memory_mut().rom_first256bytes = self.copy_range(0x0000..0x0100); + //let bootrom = self.memory().bootrom; + //self.clone_from_slice(map::BOOTROM, bootrom); + //self.memory_mut().code_listing[map::BOOTROM].fill(None); + } + + pub fn unload_bootrom(&mut self) { + self.memory_mut().bootrom_loaded = false; + //let backup = self.memory_mut().rom_first256bytes.clone(); + //self.clone_from_slice(map::BOOTROM, &backup); + //self.memory_mut().code_listing[map::BOOTROM].fill(None); + } + + pub fn load_cartridge(&mut self, cartridge: &[u8]) { + self.memory_mut().cartridge = create_mbc(cartridge); + } + + pub fn read(&self, address: Address) -> u8 { + if map::BOOTROM.contains(&address) && self.memory().bootrom_loaded { + self.memory().bootrom[address] + } else if map::ROM_BANK0.contains(&address) + || map::ROM_BANK1.contains(&address) + || map::EXT_WRAM.contains(&address) + { + self.memory().cartridge.borrow().read(address) + } else if address == map::JOYP { + self.joyp() + } else { + self.memory().mem[address as Address] + } + } + + pub fn write(&mut self, address: Address, value: u8) { + if map::ROM_BANK0.contains(&address) + || map::ROM_BANK1.contains(&address) + || map::EXT_WRAM.contains(&address) + { + self.memory_mut() + .cartridge + .borrow_mut() + .write(address, value); + } else { + self.memory_mut().mem[address as Address] = value; + } + } + + pub fn clone_from_slice(&mut self, range: MemoryRange, slice: &[u8]) { + self.memory_mut().mem[range.start..range.end].clone_from_slice(slice); + } + + pub fn copy_range(&self, range: MemoryRange) -> Vec { + self.memory_mut().mem[range.start..range.end].to_vec() + } + + pub fn with_slice(&self, range: MemoryRange, reader: impl FnOnce(&[u8]) -> T) -> T { + reader(&self.memory().mem[range]) + } + + /// Runs closure `reader` with access to a fixed-size slice of `N` bytes. + pub fn with_span( + &self, + start: Address, + reader: impl FnOnce(&[u8; N]) -> T, + ) -> T { + reader(self.memory().array_ref(start)) + } + + // registers + pub fn lcdc(&self) -> u8 { + self.read(map::LCDC) + } + + pub fn set_lcdc(&mut self, value: u8) { + self.write(map::LCDC, value); + } + + pub fn stat(&self) -> u8 { + self.read(map::STAT) + } + + pub fn set_stat(&mut self, value: u8) { + self.write(map::STAT, value); + } + + pub fn scy(&self) -> u8 { + self.read(map::SCY) + } + + pub fn set_scy(&mut self, value: u8) { + self.write(map::SCY, value); + } + + pub fn scx(&self) -> u8 { + self.read(map::SCX) + } + + pub fn set_scx(&mut self, value: u8) { + self.write(map::SCX, value); + } + + pub fn ly(&self) -> u8 { + self.read(map::LY) + } + + pub fn set_ly(&mut self, value: u8) { + self.write(map::LY, value); + } + + pub fn lyc(&self) -> u8 { + self.read(map::LYC) + } + + pub fn set_lyc(&mut self, value: u8) { + self.write(map::LYC, value) + } + + pub fn with_vram(&self, reader: impl FnOnce(&[u8]) -> R) -> R { + reader(&self.memory().mem[map::VRAM]) + } + + fn joyp(&self) -> u8 { + let buttons = self.buttons(); + let joyp = self.memory().mem[map::JOYP]; + let sel_buttons = !bw::test_bit8::<5>(joyp); + let sel_dpad = !bw::test_bit8::<4>(joyp); + let b = if sel_dpad && sel_buttons { + 0 + } else if sel_dpad { + ((buttons.down as u8) << 3) + + ((buttons.up as u8) << 2) + + ((buttons.left as u8) << 1) + + (buttons.right as u8) + } else if sel_buttons { + ((buttons.start as u8) << 3) + + ((buttons.select as u8) << 2) + + ((buttons.b as u8) << 1) + + (buttons.a as u8) + } else { + 0 + }; + // Setting higher 2 bits (which are ignored) to 1 just because SameBoy does it too + ((joyp & 0xf0) + (!b & 0x0f)) | 0b1100_0000 + } + + pub fn buttons(&self) -> Buttons { + self.memory().buttons + } + + pub fn set_buttons(&mut self, buttons: &Buttons) { + self.memory_mut().buttons = *buttons; + } + + pub fn ie(&self) -> u8 { + self.read(map::IE) + } + + pub fn iflag(&self) -> u8 { + self.read(map::IF) + } + + pub fn set_iflag(&mut self, value: u8) { + self.write(map::IF, value) + } +} diff --git a/fpt/src/memory/map.rs b/fpt/src/memory/map.rs new file mode 100644 index 0000000..6db6deb --- /dev/null +++ b/fpt/src/memory/map.rs @@ -0,0 +1,214 @@ +/// You can access these consts like this: +/// ``` +/// assert_eq!(fpt::memory::map::ROM_DATA.start, 0x0100); +/// ``` +use super::{Address, MemoryRange}; + +//------------------------------------------------------------------------- +// Memory map +//------------------------------------------------------------------------- + +/// This is where the bootrom lives +pub const BOOTROM: MemoryRange = 0x0000..0x0100; + +/// The Cartridge Header +pub const ROM_DATA: MemoryRange = 0x0100..0x0150; + +/// User Program Area (32 KB) +/// From cartridge, usually a fixed bank +pub const ROM_BANK0: MemoryRange = 0x0000..0x4000; +/// From cartridge, switchable bank via mapper (if any) pub const USER_PROGRAM: MemoryRange = 0x0000..0x8000; +pub const ROM_BANK1: MemoryRange = 0x4000..0x8000; + +/// Video RAM (8 KB) - In CGB mode, switchable bank 0/1 +pub const VRAM: MemoryRange = 0x8000..0xA000; + +/// External Expansion Working RAM (8 KB) - From cartridge, switchable bank if any +pub const EXT_WRAM: MemoryRange = 0xA000..0xC000; + +/// Unit Working RAM (8 KB) +pub const WRAM: MemoryRange = 0xC000..0xE000; + +/// Not usable (Mirror of C000~DDFF (ECHO RAM)) https://gbdev.io/pandocs/Memory_Map.html#echo-ram +pub const NOT_USABLE1: MemoryRange = 0xE000..0xFE00; + +/// Object Attribute Memory (40 OBJs, 40 x 32 bits) +pub const OAM: MemoryRange = 0xFE00..0xFEA0; + +/// Not usable https://gbdev.io/pandocs/Memory_Map.html#fea0-feff-range +pub const NOT_USABLE2: MemoryRange = 0xFEA0..0xFF00; + +//------------------------------------------------------------------------- +// I/O Registers +//------------------------------------------------------------------------- + +/// Joypad +pub const JOYP: Address = 0xFF00; +/// Serial transfer data +pub const SB: Address = 0xFF01; +/// Serial transfer control +pub const SC: Address = 0xFF02; +/// Divider register +pub const DIV: Address = 0xFF04; +/// Timer counter +pub const TIMA: Address = 0xFF05; +/// Timer modulo +pub const TMA: Address = 0xFF06; +/// Timer control +pub const TAC: Address = 0xFF07; + +//------------------------------------------------------------------------- +// I/O: Sound +//------------------------------------------------------------------------- + +/// Sound channel 1 sweep +pub const NR10: Address = 0xFF10; +/// Sound channel 1 length timer & duty cycle +pub const NR11: Address = 0xFF11; +/// Sound channel 1 volume & envelope +pub const NR12: Address = 0xFF12; +/// Sound channel 1 period low +pub const NR13: Address = 0xFF13; +/// Sound channel 1 period high & control +pub const NR14: Address = 0xFF14; +/// Sound channel 2 length timer & duty cycle +pub const NR21: Address = 0xFF16; +/// Sound channel 2 volume & envelope +pub const NR22: Address = 0xFF17; +/// Sound channel 2 period low +pub const NR23: Address = 0xFF18; +/// Sound channel 2 period high & control +pub const NR24: Address = 0xFF19; +/// Sound channel 3 DAC enable +pub const NR30: Address = 0xFF1A; +/// Sound channel 3 length timer +pub const NR31: Address = 0xFF1B; +/// Sound channel 3 output level +pub const NR32: Address = 0xFF1C; +/// Sound channel 3 period low +pub const NR33: Address = 0xFF1D; +/// Sound channel 3 period high & control +pub const NR34: Address = 0xFF1E; +/// Sound channel 4 length timer +pub const NR41: Address = 0xFF20; +/// Sound channel 4 volume & envelope +pub const NR42: Address = 0xFF21; +/// Sound channel 4 frequency & randomness +pub const NR43: Address = 0xFF22; +/// Sound channel 4 control +pub const NR44: Address = 0xFF23; +/// Master volume & VIN panning +pub const NR50: Address = 0xFF24; +/// Sound panning +pub const NR51: Address = 0xFF25; +/// Sound on/off +pub const NR52: Address = 0xFF26; +/// Wave RAM +pub const WAVE_RAM: MemoryRange = 0xFF30..0xFF40; + +//------------------------------------------------------------------------- +// IO: PPU +//------------------------------------------------------------------------- + +/// LCD control +pub const LCDC: Address = 0xFF40; +/// LCD status +pub const STAT: Address = 0xFF41; +/// Viewport Y position +pub const SCY: Address = 0xFF42; +/// Viewport X position +pub const SCX: Address = 0xFF43; +/// LCD Y coordinate +pub const LY: Address = 0xFF44; +/// LY compare +pub const LYC: Address = 0xFF45; +/// OAM DMA source address & start +pub const DMA: Address = 0xFF46; +/// BG palette data (DMG) +pub const BGP: Address = 0xFF47; +/// OBJ palette 0 data (DMG) +pub const OBP0: Address = 0xFF48; +/// OBJ palette 1 data (DMG) +pub const OBP1: Address = 0xFF49; +/// Window Y position +pub const WY: Address = 0xFF4A; +/// Window X position plus 7 +pub const WX: Address = 0xFF4B; + +/// BANK register: Set to non-zero to disable boot ROM +pub const BANK: Address = 0xFF50; + +//------------------------------------------------------------------------- +// CGB extra +// https://gbdev.io/pandocs/CGB_Registers.html +//------------------------------------------------------------------------- + +/// Prepare speed switch (CGB) +pub const KEY1: Address = 0xFF4C; +/// VRAM bank (CGB) +pub const VBK: Address = 0xFF4F; +/// VRAM DMA source high (CGB) +pub const HDMA1: Address = 0xFF51; +/// VRAM DMA source low (CGB) +pub const HDMA2: Address = 0xFF52; +/// VRAM DMA destination high (CGB) +pub const HDMA3: Address = 0xFF53; +/// VRAM DMA destination low (CGB) +pub const HDMA4: Address = 0xFF54; +/// VRAM DMA length/mode/start (CGB) +pub const HDMA5: Address = 0xFF55; +/// Infrared communications port (GGB) +pub const RP: Address = 0xFF56; +/// Background color palette specification / Background palette index (CGB) +pub const BCPS: Address = 0xFF68; +/// Background color palette data / Background palette data (CGB) +pub const BCPD: Address = 0xFF69; +/// OBJ color palette specification / OBJ palette index (CGB) +pub const OCPS: Address = 0xFF6A; +/// OBJ color palette data / OBJ palette data (CGB) +pub const OCPD: Address = 0xFF6B; +/// Object priority mode (CGB) +pub const OPRI: Address = 0xFF6C; +/// WRAM bank (CGB) pub const SVBK: Address = 0xFF70; +/// Audio digital outputs 1 & 2 (CGB) +pub const PCM12: Address = 0xFF76; +/// Audio digital outputs 3 & 4 (CGB) +pub const PCM34: Address = 0xFF77; + +//------------------------------------------------------------------------- +// High RAM +//------------------------------------------------------------------------- + +/// Working & Stack RAM (127 bytes) +pub const HRAM: MemoryRange = 0xFF80..0xFFFF; + +//------------------------------------------------------------------------- +// Interrupts +//------------------------------------------------------------------------- + +/// Interrupt enable +pub const IE: Address = 0xFFFF; +/// Interrupt flag +pub const IF: Address = 0xFF0F; + +/// Cartridge sections +/// Game title - upper case ascii - 16 bytes +pub const TITLE: MemoryRange = 0x134..0x144; +/// Unknown manufacturer code - 4 bytes +pub const MANUFACTURER_CODE: MemoryRange = 0x13F..0x143; +/// Color game boy flag +pub const CGB_FLAG: Address = 0x143; +/// Valid when old licensee is 0x33 - 2 bytes +pub const NEW_LICENSEE_CODE: MemoryRange = 0x144..0x146; +/// Super game boy flag +pub const SGB_FLAG: Address = 0x146; +/// Cartridge type including memory mapper +pub const CARTRIDGE_TYPE: Address = 0x147; +/// Number of rom banks = 2^(ROM_SIZE+1) +pub const ROM_SIZE: Address = 0x148; +/// Number of ram banks +pub const RAM_SIZE: Address = 0x149; +/// Old licensee code +pub const OLD_LICENSEE_CODE: Address = 0x14b; +/// Game version +pub const VERSION_NUMBER: Address = 0x14c; diff --git a/fpt/src/memory/mbc_builder.rs b/fpt/src/memory/mbc_builder.rs new file mode 100644 index 0000000..3ff6087 --- /dev/null +++ b/fpt/src/memory/mbc_builder.rs @@ -0,0 +1,19 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use super::cartridge::{get_cartridge_type, EmptyCartridge}; +use super::mbc_none::NoMbcCartridge; +use super::Cartridge; + +pub fn create_mbc(cartridge_data: &[u8]) -> Rc> { + let cartridge_type = get_cartridge_type(cartridge_data); + + match dbg!(cartridge_type) { + 0x00 => Rc::new(RefCell::new(NoMbcCartridge::new(cartridge_data))), + _ => panic!(), + } +} + +pub fn create_empty_mbc() -> Rc> { + Rc::new(RefCell::new(EmptyCartridge::new())) +} diff --git a/fpt/src/memory/mbc_none.rs b/fpt/src/memory/mbc_none.rs new file mode 100644 index 0000000..1745081 --- /dev/null +++ b/fpt/src/memory/mbc_none.rs @@ -0,0 +1,28 @@ +use super::cartridge::Cartridge; +use super::{Address, MemoryRange}; + +pub struct NoMbcCartridge { + memory: Vec, +} + +impl NoMbcCartridge { + pub fn new(cartridge: &[u8]) -> NoMbcCartridge { + let mut cartridge = cartridge.to_vec(); + let mut padding = vec![0; (0x10000 - cartridge.len()).max(0)]; + cartridge.append(&mut padding); + NoMbcCartridge { memory: cartridge } + } +} + +impl Cartridge for NoMbcCartridge { + fn read(&self, address: Address) -> u8 { + self.memory[address] + } + fn write(&mut self, address: Address, value: u8) { + self.memory[address] = value; + } + + fn read_range(&self, memory_range: MemoryRange) -> Vec { + self.memory[memory_range].to_vec() + } +} diff --git a/fpt/src/memory/mod.rs b/fpt/src/memory/mod.rs new file mode 100644 index 0000000..bd531f3 --- /dev/null +++ b/fpt/src/memory/mod.rs @@ -0,0 +1,10 @@ +mod cartridge; +mod lib; +pub mod map; +mod mbc_builder; +mod mbc_none; + +pub use cartridge::Cartridge; +pub use lib::{Address, Bus, Buttons, MemoryRange}; +pub use mbc_builder::{create_empty_mbc, create_mbc}; +pub use mbc_none::NoMbcCartridge; From 3d7f579b625e62d8f4fdf38f2f45c701d3ca0532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freitas?= Date: Thu, 25 Jul 2024 21:56:32 +0100 Subject: [PATCH 2/6] Implement read range over the read method --- fpt/src/memory/cartridge.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fpt/src/memory/cartridge.rs b/fpt/src/memory/cartridge.rs index 3544065..b2a18a4 100644 --- a/fpt/src/memory/cartridge.rs +++ b/fpt/src/memory/cartridge.rs @@ -9,7 +9,9 @@ pub trait Cartridge { fn read(&self, address: Address) -> u8; fn write(&mut self, address: Address, value: u8); - fn read_range(&self, memory_range: MemoryRange) -> Vec; + fn read_range(&self, memory_range: MemoryRange) -> Vec { + memory_range.map(|address| self.read(address)).collect() + } fn get_title(&self) -> String { String::from_utf8(self.read_range(map::TITLE)).unwrap() @@ -55,13 +57,10 @@ impl EmptyCartridge { EmptyCartridge {} } } + impl Cartridge for EmptyCartridge { fn read(&self, _address: Address) -> u8 { 0xFF } fn write(&mut self, _address: Address, _value: u8) {} - - fn read_range(&self, _memory_range: MemoryRange) -> Vec { - Vec::new() - } } From 9f65eead4804935397109b994a2c90a5686237ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freitas?= Date: Thu, 25 Jul 2024 22:36:19 +0100 Subject: [PATCH 3/6] Whitelist more memory regions --- fpt/src/memory/lib.rs | 41 +++++++++++++++++++++++++++++++++++------ fpt/src/memory/map.rs | 2 ++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/fpt/src/memory/lib.rs b/fpt/src/memory/lib.rs index c54cfe9..13b20e4 100644 --- a/fpt/src/memory/lib.rs +++ b/fpt/src/memory/lib.rs @@ -123,8 +123,22 @@ impl Bus { self.memory().cartridge.borrow().read(address) } else if address == map::JOYP { self.joyp() - } else { + } else if map::IO_REGISTERS.contains(&address) + || map::VRAM.contains(&address) + || map::HRAM.contains(&address) + || map::WRAM.contains(&address) + || map::NOT_USABLE2.contains(&address) + || map::OAM.contains(&address) + { self.memory().mem[address as Address] + } else if map::NOT_USABLE1.contains(&address) { + self.memory().mem[(address - 0x2000) as Address] + } else if address == map::IE { + self.memory().mem[address as Address] + } else { + //self.memory().mem[address as Address] + dbg!(address); + panic!(); } } @@ -133,12 +147,27 @@ impl Bus { || map::ROM_BANK1.contains(&address) || map::EXT_WRAM.contains(&address) { - self.memory_mut() - .cartridge - .borrow_mut() - .write(address, value); - } else { + //self.memory_mut() + // .cartridge + // .borrow_mut() + // .write(address, value); + } else if map::IO_REGISTERS.contains(&address) + || map::VRAM.contains(&address) + || map::HRAM.contains(&address) + || map::WRAM.contains(&address) + || map::NOT_USABLE2.contains(&address) + || map::OAM.contains(&address) + { self.memory_mut().mem[address as Address] = value; + } else if map::NOT_USABLE1.contains(&address) { + self.memory_mut().mem[address - 0x2000 as Address] = value; + } else if address == map::IE { + self.memory_mut().mem[address as Address] = value; + } else { + //self.memory_mut().mem[address as Address] = value; + dbg!(address); + dbg!(value); + panic!(); } } diff --git a/fpt/src/memory/map.rs b/fpt/src/memory/map.rs index 6db6deb..3f3debc 100644 --- a/fpt/src/memory/map.rs +++ b/fpt/src/memory/map.rs @@ -38,6 +38,8 @@ pub const OAM: MemoryRange = 0xFE00..0xFEA0; /// Not usable https://gbdev.io/pandocs/Memory_Map.html#fea0-feff-range pub const NOT_USABLE2: MemoryRange = 0xFEA0..0xFF00; +pub const IO_REGISTERS: MemoryRange = 0xFF00..0xFF80; + //------------------------------------------------------------------------- // I/O Registers //------------------------------------------------------------------------- From 80faed7db98797880ab0d6649361802aeca91914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freitas?= Date: Thu, 25 Jul 2024 23:32:04 +0100 Subject: [PATCH 4/6] Cleanup memory restructure --- fpt/src/lib.rs | 2 +- fpt/src/memory/lib.rs | 26 ++++++-------------------- fpt/src/memory/map.rs | 15 ++++++--------- fpt/src/memory/mbc_builder.rs | 2 +- fpt/src/memory/mbc_none.rs | 30 ++++++++++++++++++++---------- 5 files changed, 34 insertions(+), 41 deletions(-) diff --git a/fpt/src/lib.rs b/fpt/src/lib.rs index 24a54ce..de4ebe4 100644 --- a/fpt/src/lib.rs +++ b/fpt/src/lib.rs @@ -44,7 +44,7 @@ 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 boot_fake(&mut self) { // CPU registers self.cpu.set_af(0x0100); diff --git a/fpt/src/memory/lib.rs b/fpt/src/memory/lib.rs index 13b20e4..fa69fe5 100644 --- a/fpt/src/memory/lib.rs +++ b/fpt/src/memory/lib.rs @@ -96,17 +96,10 @@ impl Bus { pub fn load_bootrom(&mut self) { self.memory_mut().bootrom_loaded = true; - //self.memory_mut().rom_first256bytes = self.copy_range(0x0000..0x0100); - //let bootrom = self.memory().bootrom; - //self.clone_from_slice(map::BOOTROM, bootrom); - //self.memory_mut().code_listing[map::BOOTROM].fill(None); } pub fn unload_bootrom(&mut self) { self.memory_mut().bootrom_loaded = false; - //let backup = self.memory_mut().rom_first256bytes.clone(); - //self.clone_from_slice(map::BOOTROM, &backup); - //self.memory_mut().code_listing[map::BOOTROM].fill(None); } pub fn load_cartridge(&mut self, cartridge: &[u8]) { @@ -129,15 +122,12 @@ impl Bus { || map::WRAM.contains(&address) || map::NOT_USABLE2.contains(&address) || map::OAM.contains(&address) + || address == map::IE { self.memory().mem[address as Address] } else if map::NOT_USABLE1.contains(&address) { self.memory().mem[(address - 0x2000) as Address] - } else if address == map::IE { - self.memory().mem[address as Address] } else { - //self.memory().mem[address as Address] - dbg!(address); panic!(); } } @@ -147,26 +137,22 @@ impl Bus { || map::ROM_BANK1.contains(&address) || map::EXT_WRAM.contains(&address) { - //self.memory_mut() - // .cartridge - // .borrow_mut() - // .write(address, value); + self.memory_mut() + .cartridge + .borrow_mut() + .write(address, value); } else if map::IO_REGISTERS.contains(&address) || map::VRAM.contains(&address) || map::HRAM.contains(&address) || map::WRAM.contains(&address) || map::NOT_USABLE2.contains(&address) || map::OAM.contains(&address) + || address == map::IE { self.memory_mut().mem[address as Address] = value; } else if map::NOT_USABLE1.contains(&address) { self.memory_mut().mem[address - 0x2000 as Address] = value; - } else if address == map::IE { - self.memory_mut().mem[address as Address] = value; } else { - //self.memory_mut().mem[address as Address] = value; - dbg!(address); - dbg!(value); panic!(); } } diff --git a/fpt/src/memory/map.rs b/fpt/src/memory/map.rs index 3f3debc..c5bc332 100644 --- a/fpt/src/memory/map.rs +++ b/fpt/src/memory/map.rs @@ -29,17 +29,21 @@ pub const EXT_WRAM: MemoryRange = 0xA000..0xC000; /// Unit Working RAM (8 KB) pub const WRAM: MemoryRange = 0xC000..0xE000; -/// Not usable (Mirror of C000~DDFF (ECHO RAM)) https://gbdev.io/pandocs/Memory_Map.html#echo-ram +/// Not usable (Mirror of C000~DDFF (ECHO RAM)) pub const NOT_USABLE1: MemoryRange = 0xE000..0xFE00; /// Object Attribute Memory (40 OBJs, 40 x 32 bits) pub const OAM: MemoryRange = 0xFE00..0xFEA0; -/// Not usable https://gbdev.io/pandocs/Memory_Map.html#fea0-feff-range +/// Not usable pub const NOT_USABLE2: MemoryRange = 0xFEA0..0xFF00; +/// Hardware registers section pub const IO_REGISTERS: MemoryRange = 0xFF00..0xFF80; +/// Working & Stack RAM (127 bytes) +pub const HRAM: MemoryRange = 0xFF80..0xFFFF; + //------------------------------------------------------------------------- // I/O Registers //------------------------------------------------------------------------- @@ -177,13 +181,6 @@ pub const PCM12: Address = 0xFF76; /// Audio digital outputs 3 & 4 (CGB) pub const PCM34: Address = 0xFF77; -//------------------------------------------------------------------------- -// High RAM -//------------------------------------------------------------------------- - -/// Working & Stack RAM (127 bytes) -pub const HRAM: MemoryRange = 0xFF80..0xFFFF; - //------------------------------------------------------------------------- // Interrupts //------------------------------------------------------------------------- diff --git a/fpt/src/memory/mbc_builder.rs b/fpt/src/memory/mbc_builder.rs index 3ff6087..8ffc49c 100644 --- a/fpt/src/memory/mbc_builder.rs +++ b/fpt/src/memory/mbc_builder.rs @@ -8,7 +8,7 @@ use super::Cartridge; pub fn create_mbc(cartridge_data: &[u8]) -> Rc> { let cartridge_type = get_cartridge_type(cartridge_data); - match dbg!(cartridge_type) { + match cartridge_type { 0x00 => Rc::new(RefCell::new(NoMbcCartridge::new(cartridge_data))), _ => panic!(), } diff --git a/fpt/src/memory/mbc_none.rs b/fpt/src/memory/mbc_none.rs index 1745081..a85be87 100644 --- a/fpt/src/memory/mbc_none.rs +++ b/fpt/src/memory/mbc_none.rs @@ -1,28 +1,38 @@ use super::cartridge::Cartridge; -use super::{Address, MemoryRange}; +use super::{map, Address}; +/// Cartridge with no banking and no external ram +/// +/// pub struct NoMbcCartridge { memory: Vec, } impl NoMbcCartridge { pub fn new(cartridge: &[u8]) -> NoMbcCartridge { - let mut cartridge = cartridge.to_vec(); - let mut padding = vec![0; (0x10000 - cartridge.len()).max(0)]; - cartridge.append(&mut padding); - NoMbcCartridge { memory: cartridge } + assert!( + cartridge.len() == 0x8000, + "Expected cartridge size of 0x8000" + ); + NoMbcCartridge { + memory: cartridge.to_vec(), + } } } impl Cartridge for NoMbcCartridge { fn read(&self, address: Address) -> u8 { + assert!( + map::ROM_BANK0.contains(&address) || map::ROM_BANK1.contains(&address), + "Cartridge must only be accessed for cartridge specific memory segments" + ); self.memory[address] } - fn write(&mut self, address: Address, value: u8) { - self.memory[address] = value; - } - fn read_range(&self, memory_range: MemoryRange) -> Vec { - self.memory[memory_range].to_vec() + fn write(&mut self, address: Address, _value: u8) { + assert!( + map::ROM_BANK0.contains(&address) || map::ROM_BANK1.contains(&address), + "Cartridge must only be accessed for cartridge specific memory segments" + ); } } From 5416bee91a45e824880f9665bb479f6696d6b833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freitas?= Date: Thu, 25 Jul 2024 23:35:50 +0100 Subject: [PATCH 5/6] Document mbc builder --- fpt/src/memory/mbc_builder.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fpt/src/memory/mbc_builder.rs b/fpt/src/memory/mbc_builder.rs index 8ffc49c..6abd491 100644 --- a/fpt/src/memory/mbc_builder.rs +++ b/fpt/src/memory/mbc_builder.rs @@ -8,8 +8,9 @@ use super::Cartridge; pub fn create_mbc(cartridge_data: &[u8]) -> Rc> { let cartridge_type = get_cartridge_type(cartridge_data); + // https://gbdev.io/pandocs/The_Cartridge_Header.html#0147--cartridge-type match cartridge_type { - 0x00 => Rc::new(RefCell::new(NoMbcCartridge::new(cartridge_data))), + 0x00 => Rc::new(RefCell::new(NoMbcCartridge::new(cartridge_data))), // rom only _ => panic!(), } } From b6b687e906483078f5505f4d808a244d0156fc3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Freitas?= Date: Thu, 25 Jul 2024 23:42:44 +0100 Subject: [PATCH 6/6] Propragate errors from create_mbc --- fpt/src/lib.rs | 2 +- fpt/src/memory/cartridge.rs | 5 +++-- fpt/src/memory/lib.rs | 5 +++-- fpt/src/memory/mbc_builder.rs | 10 ++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/fpt/src/lib.rs b/fpt/src/lib.rs index de4ebe4..6a956cd 100644 --- a/fpt/src/lib.rs +++ b/fpt/src/lib.rs @@ -99,7 +99,7 @@ impl Gameboy { } pub fn load_rom(&mut self, rom: &[u8]) { - self.bus.load_cartridge(rom); + self.bus.load_rom(rom); } pub fn bus(&self) -> &Bus { diff --git a/fpt/src/memory/cartridge.rs b/fpt/src/memory/cartridge.rs index b2a18a4..6604237 100644 --- a/fpt/src/memory/cartridge.rs +++ b/fpt/src/memory/cartridge.rs @@ -1,12 +1,13 @@ use crate::memory::map; use crate::memory::{Address, MemoryRange}; -pub fn get_cartridge_type(tape: &[u8]) -> u8 { - tape[map::CARTRIDGE_TYPE] +pub fn get_cartridge_type(cartridge_rom_data: &[u8]) -> u8 { + cartridge_rom_data[map::CARTRIDGE_TYPE] } pub trait Cartridge { fn read(&self, address: Address) -> u8; + fn write(&mut self, address: Address, value: u8); fn read_range(&self, memory_range: MemoryRange) -> Vec { diff --git a/fpt/src/memory/lib.rs b/fpt/src/memory/lib.rs index fa69fe5..48e2512 100644 --- a/fpt/src/memory/lib.rs +++ b/fpt/src/memory/lib.rs @@ -102,8 +102,9 @@ impl Bus { self.memory_mut().bootrom_loaded = false; } - pub fn load_cartridge(&mut self, cartridge: &[u8]) { - self.memory_mut().cartridge = create_mbc(cartridge); + pub fn load_rom(&mut self, rom: &[u8]) { + self.memory_mut().cartridge = + create_mbc(rom).expect("Given rom cannot be interpreted as a valid cartridge type"); } pub fn read(&self, address: Address) -> u8 { diff --git a/fpt/src/memory/mbc_builder.rs b/fpt/src/memory/mbc_builder.rs index 6abd491..333c529 100644 --- a/fpt/src/memory/mbc_builder.rs +++ b/fpt/src/memory/mbc_builder.rs @@ -5,13 +5,11 @@ use super::cartridge::{get_cartridge_type, EmptyCartridge}; use super::mbc_none::NoMbcCartridge; use super::Cartridge; -pub fn create_mbc(cartridge_data: &[u8]) -> Rc> { - let cartridge_type = get_cartridge_type(cartridge_data); - +pub fn create_mbc(cartridge_data: &[u8]) -> Option>> { // https://gbdev.io/pandocs/The_Cartridge_Header.html#0147--cartridge-type - match cartridge_type { - 0x00 => Rc::new(RefCell::new(NoMbcCartridge::new(cartridge_data))), // rom only - _ => panic!(), + match get_cartridge_type(cartridge_data) { + 0x00 => Some(Rc::new(RefCell::new(NoMbcCartridge::new(cartridge_data)))), // rom only + _ => None, } }