mbc3 rtc
This commit is contained in:
parent
66b9e43a54
commit
6d2cab2d87
|
@ -32,13 +32,19 @@ impl Rom {
|
||||||
println!("MBC1 w/battery - battery not implemented!");
|
println!("MBC1 w/battery - battery not implemented!");
|
||||||
Box::new(Mbc1::init(data, rom_size, ram_size, None))
|
Box::new(Mbc1::init(data, rom_size, ram_size, None))
|
||||||
}
|
}
|
||||||
0x0F => panic!("MBC3 + RTC"),
|
0x0F => {
|
||||||
0x1F => panic!("MBC3 + RTC + RAM"),
|
println!("MBC3 w/timer + battery - battery not implemented!");
|
||||||
0x11 => Box::new(Mbc3::init(data, rom_size, 0)),
|
Box::new(Mbc3::init(data, rom_size, 0, true))
|
||||||
0x12 => Box::new(Mbc3::init(data, rom_size, ram_size)),
|
}
|
||||||
|
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 => {
|
0x13 => {
|
||||||
println!("MBC3 w/battery - battery not implemented!");
|
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)),
|
0x19 => Box::new(Mbc5::init(data, rom_size, 0, false)),
|
||||||
0x1A => Box::new(Mbc5::init(data, rom_size, ram_size, 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!");
|
println!("MBC5 w/battery - battery not implemented!");
|
||||||
Box::new(Mbc5::init(data, rom_size, ram_size, false))
|
Box::new(Mbc5::init(data, rom_size, ram_size, false))
|
||||||
}
|
}
|
||||||
0x1C => {
|
0x1C => Box::new(Mbc5::init(data, rom_size, 0, true)),
|
||||||
println!("MBC5 w/rumble - rumble not implemented!");
|
0x1D => Box::new(Mbc5::init(data, rom_size, ram_size, true)),
|
||||||
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))
|
|
||||||
}
|
|
||||||
0x1E => {
|
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))
|
Box::new(Mbc5::init(data, rom_size, ram_size, true))
|
||||||
}
|
}
|
||||||
_ => panic!("unimplemented mbc: {:#X}", data[0x147]),
|
_ => panic!("unimplemented mbc: {:#X}", data[0x147]),
|
||||||
|
|
|
@ -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 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<Local> {
|
||||||
|
// 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<Instant>,
|
||||||
|
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 {
|
pub struct Mbc3 {
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
rom_bank: u8,
|
rom_bank: u8,
|
||||||
rom_size: usize,
|
rom_size: usize,
|
||||||
ram: Option<Vec<u8>>,
|
ram: Option<Vec<u8>>,
|
||||||
ram_bank: u8,
|
ram_bank: RamBank,
|
||||||
ram_size: usize,
|
ram_size: usize,
|
||||||
ram_enabled: bool,
|
ram_enabled: bool,
|
||||||
|
rtc: Option<Rtc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mbc3 {
|
impl Mbc3 {
|
||||||
pub fn init(data: Vec<u8>, rom_size: u8, ram_size: u8) -> Self {
|
pub fn init(data: Vec<u8>, rom_size: u8, ram_size: u8, rtc: bool) -> Self {
|
||||||
let ram = ram_size_kb(ram_size).map(|s| vec![0; s * KB]);
|
let ram = ram_size_kb(ram_size).map(|s| vec![0; s * KB]);
|
||||||
Self {
|
Self {
|
||||||
data,
|
data,
|
||||||
rom_bank: 1,
|
rom_bank: 1,
|
||||||
rom_size: rom_banks(rom_size) * ROM_BANK_SIZE,
|
rom_size: rom_banks(rom_size) * ROM_BANK_SIZE,
|
||||||
ram,
|
ram,
|
||||||
ram_bank: 0,
|
ram_bank: RamBank::Ram(0),
|
||||||
ram_size: ram_size_kb(ram_size).map_or(1, |s| s * KB),
|
ram_size: ram_size_kb(ram_size).map_or(1, |s| s * KB),
|
||||||
ram_enabled: false,
|
ram_enabled: false,
|
||||||
|
rtc: if rtc { Some(Rtc::default()) } else { None },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +139,8 @@ impl Mbc3 {
|
||||||
} % self.rom_size)
|
} % self.rom_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ram_addr(&self, address: Address) -> usize {
|
fn get_ram_addr(&self, address: Address, ram_bank: usize) -> usize {
|
||||||
((address as usize - 0xA000) + (RAM_BANK_SIZE * self.ram_bank as usize)) % self.ram_size
|
((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 {
|
fn get_ram(&self, address: Address) -> u8 {
|
||||||
if self.ram_enabled && let Some(ram) = &self.ram {
|
if self.ram_enabled {
|
||||||
ram[self.get_ram_addr(address)]
|
match &self.ram_bank {
|
||||||
} else {
|
RamBank::Ram(ram_bank) => {
|
||||||
0xFF
|
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) {
|
fn set(&mut self, address: Address, data: u8) {
|
||||||
|
@ -68,13 +180,30 @@ impl Mbc for Mbc3 {
|
||||||
self.rom_bank = 1;
|
self.rom_bank = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0x4000..0x6000 => match data {
|
0x4000..0x6000 => {
|
||||||
0x0..=0x03 => self.ram_bank = data,
|
self.ram_bank = match data {
|
||||||
0x08..=0x0C => panic!("rtc bank map: {data:#X}"),
|
0x0..=0x03 => RamBank::Ram(data),
|
||||||
_ => panic!("ram/rtc bank error: tried to map {data:#X}"),
|
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 => {
|
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"),
|
_ => panic!("unsupported addr"),
|
||||||
|
@ -82,9 +211,20 @@ impl Mbc for Mbc3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_ram(&mut self, address: Address, data: u8) {
|
fn set_ram(&mut self, address: Address, data: u8) {
|
||||||
let real_addr = self.get_ram_addr(address);
|
if self.ram_enabled {
|
||||||
if self.ram_enabled && let Some(ram) = &mut self.ram {
|
match &self.ram_bank {
|
||||||
ram[real_addr] = data;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue