use super::cartridge::GbCartridge;
pub struct Huc1 {
rom: Vec<u8>,
ram: Vec<u8>,
rom_bank: u8,
secondary_bank: u8,
mode: bool,
ram_enabled: bool,
}
impl Huc1 {
pub fn new(rom: Vec<u8>, ram: Vec<u8>) -> Self {
Self {
rom,
ram,
rom_bank: 0,
secondary_bank: 0,
mode: false,
ram_enabled: false,
}
}
fn rom_bank_count(&self) -> usize {
(self.rom.len() / 0x4000).max(1)
}
fn ram_bank_count(&self) -> usize {
(self.ram.len() / 0x2000).max(1)
}
fn effective_rom_bank(&self) -> usize {
let raw = (self.rom_bank & 0x7F) as usize;
if raw == 0 { 1 } else { raw }
}
fn effective_rom_bank_high(&self) -> usize {
let mut bank = self.effective_rom_bank();
if self.mode {
bank = (bank & 0x1F) | ((self.secondary_bank as usize & 0x03) << 5);
}
bank % self.rom_bank_count()
}
fn effective_secondary_bank(&self) -> usize {
(self.secondary_bank as usize & 0x03) % self.ram_bank_count()
}
fn read_rom(&self, addr: u16) -> u8 {
let (bank, offset) = if addr < 0x4000 {
(0, addr as usize)
} else {
(self.effective_rom_bank_high(), addr as usize - 0x4000)
};
let idx = bank * 0x4000 + offset;
self.rom.get(idx).copied().unwrap_or(0xFF)
}
fn read_ram(&self, addr: u16) -> u8 {
if !self.ram_enabled || self.ram.is_empty() {
return 0xFF;
}
let offset = addr as usize - 0xA000;
let bank = if self.mode {
self.effective_secondary_bank()
} else {
0
};
let idx = bank * 0x2000 + offset;
self.ram.get(idx).copied().unwrap_or(0xFF)
}
fn write_registers(&mut self, addr: u16, val: u8) {
match addr {
0x0000..=0x1FFF => {
self.ram_enabled = (val & 0x0F) == 0x0A;
}
0x2000..=0x3FFF => {
self.rom_bank = val & 0x7F;
}
0x4000..=0x5FFF => {
self.secondary_bank = val & 0x03;
}
0x6000..=0x7FFF => {
self.mode = (val & 0x01) != 0;
}
_ => {}
}
}
fn write_ram(&mut self, addr: u16, val: u8) {
if !self.ram_enabled || self.ram.is_empty() {
return;
}
let offset = addr as usize - 0xA000;
let bank = if self.mode {
self.effective_secondary_bank()
} else {
0
};
let idx = bank * 0x2000 + offset;
if let Some(slot) = self.ram.get_mut(idx) {
*slot = val;
}
}
}
impl GbCartridge for Huc1 {
fn read(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x7FFF => self.read_rom(addr),
0xA000..=0xBFFF => self.read_ram(addr),
_ => 0xFF,
}
}
fn write(&mut self, addr: u16, val: u8) {
match addr {
0x0000..=0x7FFF => self.write_registers(addr, val),
0xA000..=0xBFFF => self.write_ram(addr, val),
_ => {}
}
}
fn ram_snapshot(&self) -> Vec<u8> {
self.ram.clone()
}
fn restore_ram(&mut self, data: &[u8]) {
if data.len() <= self.ram.len() {
self.ram[..data.len()].copy_from_slice(data);
}
}
fn mbc_state_snapshot(&self) -> Vec<u8> {
vec![
self.rom_bank,
self.secondary_bank,
self.mode as u8,
self.ram_enabled as u8,
]
}
fn restore_mbc_state(&mut self, data: &[u8]) {
if data.len() >= 4 {
self.rom_bank = data[0];
self.secondary_bank = data[1];
self.mode = data[2] != 0;
self.ram_enabled = data[3] != 0;
}
}
fn has_battery(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_rom(size_bytes: usize) -> Vec<u8> {
vec![0u8; size_bytes]
}
fn make_ram(size_bytes: usize) -> Vec<u8> {
vec![0u8; size_bytes]
}
#[test]
fn test_rom_bank_selection() {
let rom = make_rom(64 * 0x4000); let ram = make_ram(4 * 0x2000); let mut cart = Huc1::new(rom, ram);
assert_eq!(cart.effective_rom_bank(), 1);
cart.write_registers(0x2000, 0x01);
assert_eq!(cart.effective_rom_bank(), 1);
cart.write_registers(0x2000, 0x02);
assert_eq!(cart.effective_rom_bank(), 2);
cart.write_registers(0x2000, 0x00);
assert_eq!(cart.effective_rom_bank(), 1);
}
#[test]
fn test_ram_enable_disable() {
let rom = make_rom(2 * 0x4000);
let ram = make_ram(0x2000);
let mut cart = Huc1::new(rom, ram);
assert!(!cart.ram_enabled);
assert_eq!(cart.read_ram(0xA000), 0xFF);
cart.write_registers(0x0000, 0x0A);
assert!(cart.ram_enabled);
cart.write_ram(0xA000, 0x42);
assert_eq!(cart.read_ram(0xA000), 0x42);
cart.write_registers(0x0000, 0x00);
assert!(!cart.ram_enabled);
assert_eq!(cart.read_ram(0xA000), 0xFF);
}
#[test]
fn test_mode_0_banking() {
let rom = make_rom(64 * 0x4000);
let ram = make_ram(4 * 0x2000);
let mut cart = Huc1::new(rom, ram);
cart.write_registers(0x6000, 0x00); cart.write_registers(0x2000, 0x01); cart.write_registers(0x4000, 0x00);
cart.write_registers(0x0000, 0x0A); cart.write_ram(0xA000, 0x11);
assert_eq!(cart.read_ram(0xA000), 0x11);
cart.write_registers(0x4000, 0x01);
assert_eq!(cart.read_ram(0xA000), 0x11);
}
#[test]
fn test_mode_1_banking() {
let rom = make_rom(64 * 0x4000);
let ram = make_ram(4 * 0x2000);
let mut cart = Huc1::new(rom, ram);
cart.write_registers(0x0000, 0x0A); cart.write_registers(0x6000, 0x01);
cart.write_registers(0x4000, 0x00); cart.write_ram(0xA000, 0x11);
cart.write_registers(0x4000, 0x01); cart.write_ram(0xA000, 0x22);
cart.write_registers(0x4000, 0x00);
assert_eq!(cart.read_ram(0xA000), 0x11);
cart.write_registers(0x4000, 0x01);
assert_eq!(cart.read_ram(0xA000), 0x22);
}
#[test]
fn test_state_snapshot_restore() {
let rom = make_rom(4 * 0x4000);
let ram = make_ram(2 * 0x2000);
let mut cart = Huc1::new(rom.clone(), ram.clone());
cart.write_registers(0x2000, 0x03);
cart.write_registers(0x4000, 0x02);
cart.write_registers(0x6000, 0x01);
cart.write_registers(0x0000, 0x0A);
let ram_snapshot = cart.ram_snapshot();
let mbc_snapshot = cart.mbc_state_snapshot();
let mut cart2 = Huc1::new(rom, ram);
cart2.restore_mbc_state(&mbc_snapshot);
cart2.restore_ram(&ram_snapshot);
assert_eq!(cart2.rom_bank, 0x03);
assert_eq!(cart2.secondary_bank, 0x02);
assert!(cart2.mode);
assert!(cart2.ram_enabled);
}
#[test]
fn test_rom_read_boundary() {
let mut rom_data = make_rom(2 * 0x4000);
rom_data[0x0100] = 0xAA;
rom_data[0x4100] = 0xBB;
let ram = make_ram(0x2000);
let mut cart = Huc1::new(rom_data, ram);
assert_eq!(cart.read(0x0100), 0xAA);
cart.write_registers(0x2000, 0x01);
assert_eq!(cart.read(0x4100), 0xBB);
}
#[test]
fn test_out_of_range_reads() {
let rom = make_rom(2 * 0x4000);
let ram = make_ram(0x2000);
let cart = Huc1::new(rom, ram);
assert_eq!(cart.read(0x8000), 0xFF);
assert_eq!(cart.read(0x9000), 0xFF);
assert_eq!(cart.read(0xC000), 0xFF);
assert_eq!(cart.read(0xE000), 0xFF);
}
}