1use bincode::{Decode, Encode}; 2use jgenesis_common::num::{GetBit, U16Ext}; 3use jgenesis_common::timeutils; 4 5#[derive(Debug, Clone, Copy, Encode, Decode)] 6struct RtcTime { 7 nanos: u32, 8 seconds: u8, 9 minutes: u8, 10 hours: u8, 11 days: u16, 12 day_overflow: bool, 13} 14 15impl Default for RtcTime { 16 fn default() -> Self { 17 Self { 18 nanos: 0, 19 seconds: 0, 20 minutes: 0, 21 hours: 0, 22 days: 0, 23 // Default day overflow flag to true so that games will prompt to reset the time if 24 // no existing RTC state is loaded 25 day_overflow: true, 26 } 27 } 28} 29 30#[derive(Debug, Clone, Encode, Decode)] 31pub struct Mbc3Rtc { 32 current_time: RtcTime, 33 latched_time: RtcTime, 34 last_update_nanos: u128, 35 last_latch_write: u8, 36 halted: bool, 37} 38 39impl Mbc3Rtc { 40 pub fn new() -> Self { 41 Self::new_from_current_time(RtcTime::default(), timeutils::current_time_nanos()) 42 } 43 44 fn new_from_current_time(current_time: RtcTime, last_update_nanos: u128) -> Self { 45 Self { 46 current_time, 47 latched_time: current_time, 48 last_update_nanos, 49 last_latch_write: 0xFF, 50 halted: false, 51 } 52 } 53 54 pub fn read_register(&self, register: u8) -> u8 { 55 match register { 56 0x08 => self.latched_time.seconds, 57 0x09 => self.latched_time.minutes, 58 0x0A => self.latched_time.hours, 59 0x0B => self.latched_time.days.lsb(), 60 0x0C => { 61 (self.latched_time.days.msb() & 0x01) 62 | (u8::from(self.halted) << 6) 63 | (u8::from(self.latched_time.day_overflow) << 7) 64 } 65 _ => 0xFF, 66 } 67 } 68 69 pub fn write_register(&mut self, register: u8, value: u8) { 70 match register { 71 0x08 => { 72 self.current_time.seconds = value % 60; 73 } 74 0x09 => { 75 self.current_time.minutes = value % 60; 76 } 77 0x0A => { 78 self.current_time.hours = value % 24; 79 } 80 0x0B => { 81 self.current_time.days.set_lsb(value); 82 } 83 0x0C => { 84 self.current_time.days.set_msb(value & 0x01); 85 self.halted = value.bit(6); 86 self.current_time.day_overflow = value.bit(7); 87 } 88 _ => {} 89 } 90 } 91 92 pub fn write_latch(&mut self, value: u8) { 93 // Writing $00 then $01 updates the RTC latch 94 if self.last_latch_write == 0x00 && value == 0x01 { 95 self.latched_time = self.current_time; 96 log::trace!("RTC latched to {:?}", self.latched_time); 97 } 98 99 self.last_latch_write = value; 100 } 101 102 pub fn update_time(&mut self) { 103 let current_time_nanos = timeutils::current_time_nanos(); 104 if current_time_nanos < self.last_update_nanos { 105 log::error!( 106 "Time has gone backwards; last update was at {} ns, current time is {current_time_nanos} ns", 107 self.last_update_nanos 108 ); 109 self.last_update_nanos = current_time_nanos; 110 return; 111 } 112 113 if self.halted { 114 self.last_update_nanos = current_time_nanos; 115 return; 116 } 117 118 let elapsed_nanos = current_time_nanos - self.last_update_nanos; 119 self.last_update_nanos = current_time_nanos; 120 121 let new_nanos = u128::from(self.current_time.nanos) + elapsed_nanos; 122 self.current_time.nanos = (new_nanos % 1_000_000_000) as u32; 123 if new_nanos < 1_000_000_000 { 124 return; 125 } 126 127 let new_seconds = u64::from(self.current_time.seconds) + (new_nanos / 1_000_000_000) as u64; 128 self.current_time.seconds = (new_seconds % 60) as u8; 129 if new_seconds < 60 { 130 return; 131 } 132 133 let new_minutes = u64::from(self.current_time.minutes) + new_seconds / 60; 134 self.current_time.minutes = (new_minutes % 60) as u8; 135 if new_minutes < 60 { 136 return; 137 } 138 139 let new_hours = u64::from(self.current_time.hours) + new_minutes / 60; 140 self.current_time.hours = (new_hours % 24) as u8; 141 if new_hours < 24 { 142 return; 143 } 144 145 let new_days = u64::from(self.current_time.days) + new_hours / 24; 146 self.current_time.days = (new_days % 512) as u16; 147 if new_days >= 512 { 148 self.current_time.day_overflow = true; 149 } 150 } 151}