diff --git a/src/processor/memory/rom.rs b/src/processor/memory/rom.rs index 9df6ad4..f487a36 100644 --- a/src/processor/memory/rom.rs +++ b/src/processor/memory/rom.rs @@ -32,13 +32,19 @@ impl Rom { println!("MBC1 w/battery - battery not implemented!"); Box::new(Mbc1::init(data, rom_size, ram_size, None)) } - 0x0F => panic!("MBC3 + RTC"), - 0x1F => panic!("MBC3 + RTC + RAM"), - 0x11 => Box::new(Mbc3::init(data, rom_size, 0)), - 0x12 => Box::new(Mbc3::init(data, rom_size, ram_size)), + 0x0F => { + println!("MBC3 w/timer + battery - battery not implemented!"); + Box::new(Mbc3::init(data, rom_size, 0, true)) + } + 0x10 => { + println!("MBC3 w/timer + battery - battery not implemented!"); + Box::new(Mbc3::init(data, rom_size, ram_size, true)) + } + 0x11 => Box::new(Mbc3::init(data, rom_size, 0, false)), + 0x12 => Box::new(Mbc3::init(data, rom_size, ram_size, false)), 0x13 => { println!("MBC3 w/battery - battery not implemented!"); - Box::new(Mbc3::init(data, rom_size, 0)) + Box::new(Mbc3::init(data, rom_size, 0, false)) } 0x19 => Box::new(Mbc5::init(data, rom_size, 0, false)), 0x1A => Box::new(Mbc5::init(data, rom_size, ram_size, false)), @@ -46,16 +52,10 @@ impl Rom { println!("MBC5 w/battery - battery not implemented!"); Box::new(Mbc5::init(data, rom_size, ram_size, false)) } - 0x1C => { - println!("MBC5 w/rumble - rumble not implemented!"); - Box::new(Mbc5::init(data, rom_size, 0, true)) - } - 0x1D => { - println!("MBC5 w/rumble - rumble not implemented!"); - Box::new(Mbc5::init(data, rom_size, ram_size, true)) - } + 0x1C => Box::new(Mbc5::init(data, rom_size, 0, true)), + 0x1D => Box::new(Mbc5::init(data, rom_size, ram_size, true)), 0x1E => { - println!("MBC5 w/rumble + battery - rumble + battery not implemented!"); + println!("MBC5 w/rumble + battery - battery not implemented!"); Box::new(Mbc5::init(data, rom_size, ram_size, true)) } _ => panic!("unimplemented mbc: {:#X}", data[0x147]), diff --git a/src/processor/memory/rom/mbcs/mbc3.rs b/src/processor/memory/rom/mbcs/mbc3.rs index d0a2536..6af73da 100644 --- a/src/processor/memory/rom/mbcs/mbc3.rs +++ b/src/processor/memory/rom/mbcs/mbc3.rs @@ -1,28 +1,130 @@ -use crate::processor::memory::Address; - use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE}; +use crate::{processor::memory::Address, util::set_or_clear_bit}; +use std::time::{Duration, Instant}; + +#[derive(Debug)] +enum RtcRegister { + Seconds, + Minutes, + Hours, + DayCounterLsb, + Misc, +} + +enum RamBank { + Ram(u8), + Rtc(RtcRegister), +} + +trait AsRtcRegister { + fn get_rtc_register(&self, register: &RtcRegister, is_halted: bool) -> u8; + fn set_rtc_register(&mut self, register: &RtcRegister, data: u8); +} + +// impl AsRtcRegister for DateTime { +// fn get_rtc_register(&self, register: &RtcRegister, is_halted: bool) -> u8 { +// match register { +// RtcRegister::Seconds => self.second() as u8, +// RtcRegister::Minutes => self.minute() as u8, +// RtcRegister::Hours => self.hour() as u8, +// RtcRegister::DayCounterLsb => 0, +// RtcRegister::Misc => set_or_clear_bit(0, 6, is_halted), +// } +// } + +// fn set_rtc_register(&mut self, register: &RtcRegister, data: u8) { +// let maybe = match register { +// RtcRegister::Seconds => self.with_second(data as u32), +// RtcRegister::Minutes => self.with_minute(data as u32), +// RtcRegister::Hours => self.with_hour(data as u32), +// RtcRegister::DayCounterLsb => None, +// RtcRegister::Misc => None, +// }; +// if let Some(is) = maybe { +// self.clone_from(&is); +// } +// } +// } + +struct Rtc { + base_time: Instant, + latched_time: Option, + latch_prepared: bool, + is_halted: bool, +} + +impl Rtc { + fn get_register(&self, rtc_register: &RtcRegister) -> u8 { + let time = self.latched_time.unwrap_or(Instant::now()) - self.base_time; + match rtc_register { + RtcRegister::Seconds => (time.as_secs() % 60) as u8, + RtcRegister::Minutes => ((time.as_secs() / 60) % 60) as u8, + RtcRegister::Hours => ((time.as_secs() / (60 * 60)) % 24) as u8, + RtcRegister::DayCounterLsb => ((time.as_secs() / (60 * 60 * 24)) % 0xFF) as u8, + RtcRegister::Misc => { + // we need to do bit 0 and 7 + set_or_clear_bit(0, 6, self.is_halted) + } + } + } + + fn set_register(&mut self, rtc_register: &RtcRegister, data: u8) { + let seconds_offset = match rtc_register { + RtcRegister::Seconds => data as i64 - self.get_register(&RtcRegister::Seconds) as i64, + RtcRegister::Minutes => { + (data as i64 - self.get_register(&RtcRegister::Minutes) as i64) * 60 + } + RtcRegister::Hours => { + (data as i64 - self.get_register(&RtcRegister::Hours) as i64) * 60 * 60 + } + RtcRegister::DayCounterLsb => { + (data as i64 - self.get_register(&RtcRegister::DayCounterLsb) as i64) * 60 * 60 * 24 + } + RtcRegister::Misc => 0, + }; + let duration_offset = Duration::from_secs(seconds_offset.unsigned_abs()); + if seconds_offset.is_positive() { + self.base_time -= duration_offset; + } else { + self.base_time += duration_offset; + } + } +} + +impl Default for Rtc { + fn default() -> Self { + Self { + base_time: Instant::now(), + latched_time: None, + latch_prepared: false, + is_halted: false, + } + } +} pub struct Mbc3 { data: Vec, rom_bank: u8, rom_size: usize, ram: Option>, - ram_bank: u8, + ram_bank: RamBank, ram_size: usize, ram_enabled: bool, + rtc: Option, } impl Mbc3 { - pub fn init(data: Vec, rom_size: u8, ram_size: u8) -> Self { + pub fn init(data: Vec, rom_size: u8, ram_size: u8, rtc: bool) -> Self { let ram = ram_size_kb(ram_size).map(|s| vec![0; s * KB]); Self { data, rom_bank: 1, rom_size: rom_banks(rom_size) * ROM_BANK_SIZE, ram, - ram_bank: 0, + ram_bank: RamBank::Ram(0), ram_size: ram_size_kb(ram_size).map_or(1, |s| s * KB), ram_enabled: false, + rtc: if rtc { Some(Rtc::default()) } else { None }, } } @@ -37,8 +139,8 @@ impl Mbc3 { } % self.rom_size) } - fn get_ram_addr(&self, address: Address) -> usize { - ((address as usize - 0xA000) + (RAM_BANK_SIZE * self.ram_bank as usize)) % self.ram_size + fn get_ram_addr(&self, address: Address, ram_bank: usize) -> usize { + ((address as usize - 0xA000) + (RAM_BANK_SIZE * ram_bank)) % self.ram_size } } @@ -48,11 +150,21 @@ impl Mbc for Mbc3 { } fn get_ram(&self, address: Address) -> u8 { - if self.ram_enabled && let Some(ram) = &self.ram { - ram[self.get_ram_addr(address)] - } else { - 0xFF + if self.ram_enabled { + match &self.ram_bank { + RamBank::Ram(ram_bank) => { + if let Some(ram) = &self.ram { + return ram[self.get_ram_addr(address, *ram_bank as usize)]; + } + } + RamBank::Rtc(rtc_register) => { + if let Some(rtc) = &self.rtc { + return rtc.get_register(rtc_register); + } + } + } } + 0xFF } fn set(&mut self, address: Address, data: u8) { @@ -68,13 +180,30 @@ impl Mbc for Mbc3 { self.rom_bank = 1; } } - 0x4000..0x6000 => match data { - 0x0..=0x03 => self.ram_bank = data, - 0x08..=0x0C => panic!("rtc bank map: {data:#X}"), - _ => panic!("ram/rtc bank error: tried to map {data:#X}"), - }, + 0x4000..0x6000 => { + self.ram_bank = match data { + 0x0..=0x03 => RamBank::Ram(data), + 0x08 => RamBank::Rtc(RtcRegister::Seconds), + 0x09 => RamBank::Rtc(RtcRegister::Minutes), + 0x0A => RamBank::Rtc(RtcRegister::Hours), + 0x0B => RamBank::Rtc(RtcRegister::DayCounterLsb), + 0x0C => RamBank::Rtc(RtcRegister::Misc), + _ => panic!("ram/rtc bank error: tried to map {data:#X}"), + } + } 0x6000..0x8000 => { - // RTC + // latch RTC time + if let Some(ref mut rtc) = self.rtc { + if data == 0x01 && rtc.latch_prepared { + rtc.latched_time = Some(Instant::now()); + } + + if data == 0x00 { + rtc.latch_prepared = true; + } else { + rtc.latch_prepared = false; + } + } } _ => panic!("unsupported addr"), @@ -82,9 +211,20 @@ impl Mbc for Mbc3 { } fn set_ram(&mut self, address: Address, data: u8) { - let real_addr = self.get_ram_addr(address); - if self.ram_enabled && let Some(ram) = &mut self.ram { - ram[real_addr] = data; + if self.ram_enabled { + match &self.ram_bank { + RamBank::Ram(ram_bank) => { + let real_addr = self.get_ram_addr(address, *ram_bank as usize); + if let Some(ram) = &mut self.ram { + ram[real_addr] = data; + } + } + RamBank::Rtc(rtc_register) => { + if let Some(ref mut rtc) = self.rtc { + rtc.set_register(rtc_register, data); + } + } + } } }