mbc3.rsannotatedmbc3.rssource151 lines · 4.5 KB · raw
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}