From 17d69fcf0a5abc1a89a2e50479105d5710d46ce2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Mon, 7 Aug 2023 22:41:41 +0100
Subject: [PATCH] chore: MBC handling in state

---
 src/rom.rs   |  54 +++++++++++++++++++++++++-
 src/state.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 154 insertions(+), 4 deletions(-)

diff --git a/src/rom.rs b/src/rom.rs
index a781cf1d..c1de9209 100644
--- a/src/rom.rs
+++ b/src/rom.rs
@@ -12,6 +12,18 @@ use wasm_bindgen::prelude::*;
 pub const ROM_BANK_SIZE: usize = 16384;
 pub const RAM_BANK_SIZE: usize = 8192;
 
+#[cfg_attr(feature = "wasm", wasm_bindgen)]
+pub enum MbcType {
+    NoMbc = 0x00,
+    Mbc1 = 0x01,
+    Mbc2 = 0x02,
+    Mbc3 = 0x03,
+    Mbc5 = 0x04,
+    Mbc6 = 0x05,
+    Mbc7 = 0x06,
+    Unknown = 0x07,
+}
+
 #[cfg_attr(feature = "wasm", wasm_bindgen)]
 pub enum RomType {
     RomOnly = 0x00,
@@ -79,6 +91,28 @@ impl RomType {
             RomType::Unknown => "Unknown",
         }
     }
+
+    pub fn mbc_type(&self) -> MbcType {
+        match self {
+            RomType::RomOnly => MbcType::NoMbc,
+            RomType::Mbc1 | RomType::Mbc1Ram | RomType::Mbc1RamBattery => MbcType::Mbc1,
+            RomType::Mbc2 | RomType::Mbc2Battery => MbcType::Mbc2,
+            RomType::Mbc3
+            | RomType::Mbc3Ram
+            | RomType::Mbc3RamBattery
+            | RomType::Mbc3TimerBattery
+            | RomType::Mbc3TimerRamBattery => MbcType::Mbc3,
+            RomType::Mbc5
+            | RomType::Mbc5Ram
+            | RomType::Mbc5RamBattery
+            | RomType::Mbc5Rumble
+            | RomType::Mbc5RumbleRam
+            | RomType::Mbc5RumbleRamBattery => MbcType::Mbc5,
+            RomType::Mbc6 => MbcType::Mbc6,
+            RomType::Mbc7SensorRumbleRamBattery => MbcType::Mbc7,
+            _ => MbcType::Unknown,
+        }
+    }
 }
 
 impl Display for RomType {
@@ -380,14 +414,30 @@ impl Cartridge {
         )
     }
 
-    pub fn set_rom_bank(&mut self, rom_bank: u8) {
-        self.rom_offset = rom_bank as usize * ROM_BANK_SIZE;
+    pub fn ram_enabled(&self) -> bool {
+        self.ram_enabled
+    }
+
+    pub fn set_ram_enabled(&mut self, ram_enabled: bool) {
+        self.ram_enabled = ram_enabled
+    }
+
+    pub fn ram_bank(&self) -> u8 {
+        (self.ram_offset / RAM_BANK_SIZE) as u8
     }
 
     pub fn set_ram_bank(&mut self, ram_bank: u8) {
         self.ram_offset = ram_bank as usize * RAM_BANK_SIZE;
     }
 
+    pub fn rom_bank(&self) -> u8 {
+        (self.rom_offset / ROM_BANK_SIZE) as u8
+    }
+
+    pub fn set_rom_bank(&mut self, rom_bank: u8) {
+        self.rom_offset = rom_bank as usize * ROM_BANK_SIZE;
+    }
+
     pub fn set_rumble_cb(&mut self, rumble_cb: fn(active: bool)) {
         self.rumble_cb = rumble_cb;
     }
diff --git a/src/state.rs b/src/state.rs
index b1c5b3b6..d86abbc7 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -24,6 +24,7 @@ pub struct BeesState {
     name: BeesName,
     info: BeesInfo,
     core: BeesCore,
+    mbc: BeesMbc,
     end: BeesBlock,
 }
 
@@ -85,6 +86,7 @@ impl Serialize for BeesState {
         self.name.save(buffer);
         self.info.save(buffer);
         self.core.save(buffer);
+        self.mbc.save(buffer);
         self.end.save(buffer);
         self.footer.save(buffer);
     }
@@ -103,20 +105,23 @@ impl Serialize for BeesState {
             // reads the block information and then moves the cursor
             // back to the original position to be able to re-read the
             // block data
-            // @TODO: may read only the Block header that should ufice
+            // @TODO: may read only the Block header that should suffice
             // fo the verification we're doing and should save us some cycles
             let block = BeesBlock::from_data(data);
             let offset = -((block.header.size as usize + size_of::<u32>() * 2) as i64);
             data.seek(SeekFrom::Current(offset)).unwrap();
 
+            println!("{}", block.magic().as_str());
+
             match block.magic().as_str() {
                 "NAME" => self.name = BeesName::from_data(data),
                 "INFO" => self.info = BeesInfo::from_data(data),
                 "CORE" => self.core = BeesCore::from_data(data),
+                "MBC " => self.mbc = BeesMbc::from_data(data),
                 "END " => self.end = BeesBlock::from_data(data),
                 _ => (),
             }
-            println!("{}", block.magic().as_str());
+
             if block.is_end() {
                 break;
             }
@@ -132,6 +137,7 @@ impl State for BeesState {
             info: BeesInfo::from_gb(gb),
             core: BeesCore::from_gb(gb),
             end: BeesBlock::from_magic(String::from("END ")),
+            mbc: BeesMbc::from_gb(gb),
         }
     }
 
@@ -140,6 +146,7 @@ impl State for BeesState {
         self.name.to_gb(gb)?;
         self.info.to_gb(gb)?;
         self.core.to_gb(gb)?;
+        self.mbc.to_gb(gb)?;
         Ok(())
     }
 }
@@ -750,6 +757,99 @@ impl Default for BeesCore {
     }
 }
 
+pub struct BeesMbrRegister {
+    address: u16,
+    value: u8,
+}
+
+impl BeesMbrRegister {
+    pub fn new(address: u16, value: u8) -> Self {
+        Self { address, value }
+    }
+}
+
+pub struct BeesMbc {
+    header: BeesBlockHeader,
+    registers: Vec<BeesMbrRegister>,
+}
+
+impl BeesMbc {
+    pub fn new(registers: Vec<BeesMbrRegister>) -> Self {
+        Self {
+            header: BeesBlockHeader::new(
+                String::from("MBC "),
+                ((size_of::<u8>() + size_of::<u16>()) * registers.len()) as u32,
+            ),
+            registers,
+        }
+    }
+
+    pub fn from_data(data: &mut Cursor<Vec<u8>>) -> Self {
+        let mut instance = Self::default();
+        instance.load(data);
+        instance
+    }
+}
+
+impl Serialize for BeesMbc {
+    fn save(&mut self, buffer: &mut Vec<u8>) {
+        self.header.save(buffer);
+        for register in self.registers.iter() {
+            buffer.write_all(&register.address.to_le_bytes()).unwrap();
+            buffer.write_all(&register.value.to_le_bytes()).unwrap();
+        }
+    }
+
+    fn load(&mut self, data: &mut Cursor<Vec<u8>>) {
+        self.header.load(data);
+        for _ in 0..(self.header.size / 3) {
+            let mut buffer = [0x00; 2];
+            data.read_exact(&mut buffer).unwrap();
+            let address = u16::from_le_bytes(buffer);
+            let mut buffer = [0x00; 1];
+            data.read_exact(&mut buffer).unwrap();
+            let value = u8::from_le_bytes(buffer);
+            self.registers.push(BeesMbrRegister::new(address, value));
+        }
+    }
+}
+
+impl State for BeesMbc {
+    fn from_gb(gb: &mut GameBoy) -> Self {
+        let mut registers = vec![];
+        match gb.cartridge().rom_type().mbc_type() {
+            crate::rom::MbcType::NoMbc => todo!(),
+            crate::rom::MbcType::Mbc1 => {
+                registers.push(BeesMbrRegister::new(0x0000, if gb.rom().ram_enabled() { 0x0a_u8 } else { 0x00_u8 }));
+                registers.push(BeesMbrRegister::new(0x2000, gb.rom().rom_bank()));
+                registers.push(BeesMbrRegister::new(0x4000, gb.rom().ram_bank()));
+                registers.push(BeesMbrRegister::new(0x6000, 0x00_u8));
+            }
+            crate::rom::MbcType::Mbc2 => todo!(),
+            crate::rom::MbcType::Mbc3 => todo!(),
+            crate::rom::MbcType::Mbc5 => todo!(),
+            crate::rom::MbcType::Mbc6 => todo!(),
+            crate::rom::MbcType::Mbc7 => todo!(),
+            crate::rom::MbcType::Unknown => todo!(),
+        }
+
+        Self::new(registers)
+    }
+
+    fn to_gb(&self, gb: &mut GameBoy) -> Result<(), String> {
+        for register in self.registers.iter() {
+            gb.mmu().write(register.address, register.value);
+        }
+        Ok(())
+    }
+}
+
+impl Default for BeesMbc {
+    fn default() -> Self {
+        Self::new(vec![])
+    }
+}
+
 pub fn save_state_file(file_path: &str, gb: &mut GameBoy) {
     let mut file = File::create(file_path).unwrap();
     let data = save_state(gb);
-- 
GitLab