From 6b35a0b0981a873db62c6a9fc32cbd15a433ded9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Mon, 11 Jul 2022 00:26:03 +0100
Subject: [PATCH] feat: support for MBC5

---
 CHANGELOG.md             |  1 +
 examples/sdl/src/main.rs |  3 +-
 src/ppu.rs               | 16 +++++++++
 src/rom.rs               | 74 +++++++++++++++++++++++++++++++++++++---
 4 files changed, 88 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d2425423..f991c882 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 * Support for 8x16 sprites
+* Support for MBC5, think Pokemon Yellow
 
 ### Changed
 
diff --git a/examples/sdl/src/main.rs b/examples/sdl/src/main.rs
index b2f65017..a3411a55 100644
--- a/examples/sdl/src/main.rs
+++ b/examples/sdl/src/main.rs
@@ -162,7 +162,8 @@ fn main() {
     //let rom = game_boy.load_rom_file("../../res/roms.prop/super_mario.gb");
     //let rom = game_boy.load_rom_file("../../res/roms.prop/super_mario_2.gb");
     //let rom = game_boy.load_rom_file("../../res/roms.prop/pokemon_red.gb");
-    let rom = game_boy.load_rom_file("../../res/roms.prop/zelda.gb");
+    let rom = game_boy.load_rom_file("../../res/roms.prop/pokemon_yellow.gb");
+    //let rom = game_boy.load_rom_file("../../res/roms.prop/zelda.gb");
 
     //let rom = game_boy.load_rom_file("../../res/roms/firstwhite.gb");
     //let rom = game_boy.load_rom_file("../../res/roms/opus5.gb");
diff --git a/src/ppu.rs b/src/ppu.rs
index 14dcca66..efbb3a87 100644
--- a/src/ppu.rs
+++ b/src/ppu.rs
@@ -172,6 +172,10 @@ pub struct Ppu {
     /// The palette that is going to be used for sprites/objects #1.
     palette_obj_1: Palette,
 
+    /// The complete set of palettes in binary data so that they can
+    /// bre read if required by the system.
+    palettes: [u8; 3],
+
     /// The scroll Y register that controls the Y offset
     /// of the background.
     scy: u8,
@@ -282,6 +286,7 @@ impl Ppu {
             palette: [[0u8; RGB_SIZE]; PALETTE_SIZE],
             palette_obj_0: [[0u8; RGB_SIZE]; PALETTE_SIZE],
             palette_obj_1: [[0u8; RGB_SIZE]; PALETTE_SIZE],
+            palettes: [0u8; 3],
             scy: 0x0,
             scx: 0x0,
             wy: 0x0,
@@ -317,6 +322,7 @@ impl Ppu {
         self.palette = [[0u8; RGB_SIZE]; PALETTE_SIZE];
         self.palette_obj_0 = [[0u8; RGB_SIZE]; PALETTE_SIZE];
         self.palette_obj_1 = [[0u8; RGB_SIZE]; PALETTE_SIZE];
+        self.palettes = [0u8; 3];
         self.scy = 0x0;
         self.scx = 0x0;
         self.ly = 0x0;
@@ -432,8 +438,13 @@ impl Ppu {
             0x0043 => self.scx,
             0x0044 => self.ly,
             0x0045 => self.lyc,
+            0x0047 => self.palettes[0],
+            0x0048 => self.palettes[1],
+            0x0049 => self.palettes[2],
             0x004a => self.wy,
             0x004b => self.wx,
+            // VBK - CGB Mode Only
+            0x004f => 0xff,
             _ => {
                 warnln!("Reading from unknown PPU location 0x{:04x}", addr);
                 0xff
@@ -485,6 +496,7 @@ impl Ppu {
                         color_index => panic!("Invalid palette color index {:04x}", color_index),
                     }
                 }
+                self.palettes[0] = value;
             }
             0x0048 => {
                 for index in 0..PALETTE_SIZE {
@@ -496,6 +508,7 @@ impl Ppu {
                         color_index => panic!("Invalid palette color index {:04x}", color_index),
                     }
                 }
+                self.palettes[1] = value;
             }
             0x0049 => {
                 for index in 0..PALETTE_SIZE {
@@ -507,9 +520,12 @@ impl Ppu {
                         color_index => panic!("Invalid palette color index {:04x}", color_index),
                     }
                 }
+                self.palettes[2] = value;
             }
             0x004a => self.wy = value,
             0x004b => self.wx = value,
+            // VBK - CGB Mode Only
+            0x004f => (),
             0x007f => (),
             _ => warnln!("Writing in unknown PPU location 0x{:04x}", addr),
         }
diff --git a/src/rom.rs b/src/rom.rs
index e05b6386..d83821ec 100644
--- a/src/rom.rs
+++ b/src/rom.rs
@@ -4,7 +4,7 @@ use std::{
     fmt::{Display, Formatter},
 };
 
-use crate::debugln;
+use crate::{debugln, warnln};
 
 #[cfg(feature = "wasm")]
 use wasm_bindgen::prelude::*;
@@ -267,6 +267,12 @@ impl Cartridge {
             RomType::Mbc3 => &MBC3,
             RomType::Mbc3Ram => &MBC3,
             RomType::Mbc3RamBattery => &MBC3,
+            RomType::Mbc5 => &MBC5,
+            RomType::Mbc5Ram => &MBC5,
+            RomType::Mbc5RamBattery => &MBC5,
+            RomType::Mbc5Rumble => &MBC5,
+            RomType::Mbc5RumbleRam => &MBC5,
+            RomType::Mbc5RumbleRamBattery => &MBC5,
             rom_type => panic!("No MBC controller available for {}", rom_type),
         }
     }
@@ -418,7 +424,10 @@ pub static MBC1: Mbc = Mbc {
             0x4000 | 0x5000 | 0x6000 | 0x7000 => {
                 rom.rom_data[rom.rom_offset + (addr - 0x4000) as usize]
             }
-            _ => panic!("Reading from unknown Cartridge ROM location 0x{:04x}", addr),
+            _ => {
+                warnln!("Reading from unknown Cartridge ROM location 0x{:04x}", addr);
+                0xff
+            }
         }
     },
     write_rom: |rom: &mut Cartridge, addr: u16, value: u8| {
@@ -447,7 +456,7 @@ pub static MBC1: Mbc = Mbc {
                     unimplemented!("Advanced ROM banking mode for MBC1 is not implemented");
                 }
             }
-            _ => panic!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
+            _ => warnln!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
         }
     },
     read_ram: |rom: &Cartridge, addr: u16| -> u8 {
@@ -473,7 +482,10 @@ pub static MBC3: Mbc = Mbc {
             0x4000 | 0x5000 | 0x6000 | 0x7000 => {
                 rom.rom_data[rom.rom_offset + (addr - 0x4000) as usize]
             }
-            _ => panic!("Reading from unknown Cartridge ROM location 0x{:04x}", addr),
+            _ => {
+                warnln!("Reading from unknown Cartridge ROM location 0x{:04x}", addr);
+                0xff
+            }
         }
     },
     write_rom: |rom: &mut Cartridge, addr: u16, value: u8| {
@@ -496,7 +508,59 @@ pub static MBC3: Mbc = Mbc {
                 let ram_bank = value & 0x03;
                 rom.set_ram_bank(ram_bank);
             }
-            _ => panic!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
+            _ => warnln!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
+        }
+    },
+    read_ram: |rom: &Cartridge, addr: u16| -> u8 {
+        if !rom.ram_enabled {
+            return 0xff;
+        }
+        rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize]
+    },
+    write_ram: |rom: &mut Cartridge, addr: u16, value: u8| {
+        if !rom.ram_enabled {
+            debugln!("Attempt to write to ERAM while write protect is active");
+            return;
+        }
+        rom.ram_data[rom.ram_offset + (addr - 0xa000) as usize] = value;
+    },
+};
+
+pub static MBC5: Mbc = Mbc {
+    name: "MBC5",
+    read_rom: |rom: &Cartridge, addr: u16| -> u8 {
+        match addr & 0xf000 {
+            0x0000 | 0x1000 | 0x2000 | 0x3000 => rom.rom_data[addr as usize],
+            0x4000 | 0x5000 | 0x6000 | 0x7000 => {
+                rom.rom_data[rom.rom_offset + (addr - 0x4000) as usize]
+            }
+            _ => {
+                warnln!("Reading from unknown Cartridge ROM location 0x{:04x}", addr);
+                0xff
+            }
+        }
+    },
+    write_rom: |rom: &mut Cartridge, addr: u16, value: u8| {
+        match addr & 0xf000 {
+            // RAM enabled flag
+            0x0000 | 0x1000 => {
+                rom.ram_enabled = (value & 0x0f) == 0x0a;
+            }
+            // ROM bank selection
+            0x2000 => {
+                let mut rom_bank = value & 0xff;
+                rom_bank = rom_bank & (rom.rom_bank_count * 2 - 1) as u8;
+                if rom_bank == 0 {
+                    rom_bank = 1;
+                }
+                rom.set_rom_bank(rom_bank);
+            }
+            // RAM bank selection
+            0x4000 | 0x5000 => {
+                let ram_bank = value & 0x0f;
+                rom.set_ram_bank(ram_bank);
+            }
+            _ => warnln!("Writing to unknown Cartridge ROM location 0x{:04x}", addr),
         }
     },
     read_ram: |rom: &Cartridge, addr: u16| -> u8 {
-- 
GitLab