1use bincode::{Decode, Encode};
2use jgenesis_common::num::{GetBit, U16Ext, U24Ext};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
5pub enum DirectRomStep {
6    #[default]
7    One,
8    Step,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
12pub enum DirectRomStepTarget {
13    #[default]
14    Base,
15    Offset,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
19pub enum DirectRomSpecialAction {
20    #[default]
21    None,
22    Add8Bit,
23    Add16BitAfterWrite,
24    Add16BitAfterRead,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
28pub struct DirectDataRomMode {
29    step: DirectRomStep,
30    step_target: DirectRomStepTarget,
31    offset_enabled: bool,
32    sign_extend_step: bool,
33    sign_extend_offset: bool,
34    special_action: DirectRomSpecialAction,
35}
36
37impl From<u8> for DirectDataRomMode {
38    fn from(value: u8) -> Self {
39        Self {
40            step: if value.bit(0) { DirectRomStep::Step } else { DirectRomStep::One },
41            step_target: if value.bit(4) {
42                DirectRomStepTarget::Offset
43            } else {
44                DirectRomStepTarget::Base
45            },
46            offset_enabled: value.bit(1),
47            sign_extend_step: value.bit(2),
48            sign_extend_offset: value.bit(3),
49            special_action: match (value >> 5) & 0x03 {
50                0x00 => DirectRomSpecialAction::None,
51                0x01 => DirectRomSpecialAction::Add8Bit,
52                0x02 => DirectRomSpecialAction::Add16BitAfterWrite,
53                0x03 => DirectRomSpecialAction::Add16BitAfterRead,
54                _ => unreachable!("value & 0x03 is always <= 0x03"),
55            },
56        }
57    }
58}
59
60#[derive(Debug, Clone, Default, Encode, Decode)]
61pub struct MathRegisters {
62    pub dividend: u32,
63    pub multiplier: u16,
64    pub divisor: u16,
65    pub result: u32,
66    pub remainder: u16,
67    pub signed: bool,
68}
69
70impl MathRegisters {
71    fn execute_multiplication(&mut self) {
72        let product = if self.signed {
73            (self.dividend as i16 as u32).wrapping_mul(self.multiplier as i16 as u32)
74        } else {
75            u32::from(self.dividend as u16) * u32::from(self.multiplier)
76        };
77        self.result = product;
78    }
79
80    fn execute_division(&mut self) {
81        if self.divisor == 0 {
82            // Divide by zero
83            self.result = 0;
84            self.remainder = self.dividend as u16;
85            return;
86        }
87
88        let (quotient, remainder) = if self.signed {
89            let dividend = self.dividend as i32;
90            let divisor: i32 = (self.divisor as i16).into();
91
92            ((dividend / divisor) as u32, (dividend % divisor) as u16)
93        } else {
94            let divisor: u32 = self.divisor.into();
95            (self.dividend / divisor, (self.dividend % divisor) as u16)
96        };
97        self.result = quotient;
98        self.remainder = remainder;
99    }
100}
101
102#[derive(Debug, Clone, Encode, Decode)]
103pub struct Registers {
104    pub sram_enabled: bool,
105    pub rom_bank_d: u8,
106    pub rom_bank_e: u8,
107    pub rom_bank_f: u8,
108    pub direct_data_rom_initialized: bool,
109    pub direct_data_rom_base: u32,
110    pub direct_data_rom_offset: u16,
111    pub direct_data_rom_step: u16,
112    pub direct_data_rom_mode: DirectDataRomMode,
113    pub direct_data_rom_mode_byte: u8,
114    pub r4814_written: bool,
115    pub r4815_written: bool,
116    pub math: MathRegisters,
117    // Functionality unknown; treat as read/write register
118    pub sram_bank: u8,
119}
120
121impl Registers {
122    pub fn new() -> Self {
123        Self {
124            sram_enabled: false,
125            rom_bank_d: 0x00,
126            rom_bank_e: 0x01,
127            rom_bank_f: 0x02,
128            direct_data_rom_initialized: false,
129            direct_data_rom_base: 0,
130            direct_data_rom_offset: 0,
131            direct_data_rom_step: 0,
132            direct_data_rom_mode: DirectDataRomMode::default(),
133            direct_data_rom_mode_byte: 0,
134            r4814_written: false,
135            r4815_written: false,
136            math: MathRegisters::default(),
137            sram_bank: 0,
138        }
139    }
140
141    pub fn read_sram_enabled(&self) -> u8 {
142        u8::from(self.sram_enabled) << 7
143    }
144
145    pub fn write_sram_enabled(&mut self, value: u8) {
146        self.sram_enabled = value.bit(7);
147    }
148
149    pub fn read_direct_data_rom_r4810(&mut self, data_rom: &[u8]) -> u8 {
150        if !self.direct_data_rom_initialized {
151            return 0;
152        }
153
154        // Register $4810: Read from either ROM[Base] or ROM[Base+Offset] depending on mode, and then
155        // increment either Base or Offset depending on mode
156        let mode = self.direct_data_rom_mode;
157        let rom_addr = if mode.offset_enabled {
158            let offset = extend_u16(self.direct_data_rom_offset, mode.sign_extend_offset);
159            self.direct_data_rom_base.wrapping_add(offset) & 0xFFFFFF
160        } else {
161            self.direct_data_rom_base
162        };
163        let byte = data_rom.get(rom_addr as usize).copied().unwrap_or(0);
164
165        let step = match mode.step {
166            DirectRomStep::One => 1,
167            DirectRomStep::Step => extend_u16(self.direct_data_rom_step, mode.sign_extend_step),
168        };
169
170        match mode.step_target {
171            DirectRomStepTarget::Base => {
172                self.direct_data_rom_base = self.direct_data_rom_base.wrapping_add(step) & 0xFFFFFF;
173            }
174            DirectRomStepTarget::Offset => {
175                self.direct_data_rom_offset = self.direct_data_rom_offset.wrapping_add(step as u16);
176            }
177        }
178
179        byte
180    }
181
182    pub fn read_direct_data_rom_r481a(&mut self, data_rom: &[u8]) -> u8 {
183        if !self.direct_data_rom_initialized {
184            return 0;
185        }
186
187        // Register $481A: Read from ROM[Base+Offset], and potentially set Base = Base+Offset depending
188        // on mode
189        let offset =
190            extend_u16(self.direct_data_rom_offset, self.direct_data_rom_mode.sign_extend_offset);
191        let rom_addr = self.direct_data_rom_base.wrapping_add(offset) & 0xFFFFFF;
192        let byte = data_rom.get(rom_addr as usize).copied().unwrap_or(0);
193
194        if self.direct_data_rom_mode.special_action == DirectRomSpecialAction::Add16BitAfterRead {
195            self.direct_data_rom_base = rom_addr;
196        }
197
198        byte
199    }
200
201    pub fn write_direct_data_rom_base_low(&mut self, value: u8) {
202        self.direct_data_rom_base.set_low_byte(value);
203    }
204
205    pub fn write_direct_data_rom_base_mid(&mut self, value: u8) {
206        self.direct_data_rom_base.set_mid_byte(value);
207    }
208
209    pub fn write_direct_data_rom_base_high(&mut self, value: u8) {
210        self.direct_data_rom_base.set_high_byte(value);
211
212        // Direct data ROM reads are "initialized" after the first write to this register
213        // Pre-initialization reads from $4810 and $481A always return 0
214        self.direct_data_rom_initialized = true;
215    }
216
217    pub fn write_direct_data_rom_offset_low(&mut self, value: u8) {
218        self.direct_data_rom_offset.set_lsb(value);
219        self.r4814_written = true;
220
221        if self.r4814_written && self.r4815_written {
222            self.apply_mode_write();
223        }
224    }
225
226    pub fn write_direct_data_rom_offset_high(&mut self, value: u8) {
227        self.direct_data_rom_offset.set_msb(value);
228        self.r4815_written = true;
229
230        if self.r4814_written && self.r4815_written {
231            self.apply_mode_write();
232        }
233    }
234
235    fn apply_mode_write(&mut self) {
236        let mode = self.direct_data_rom_mode_byte.into();
237        self.direct_data_rom_mode = mode;
238        self.r4814_written = false;
239        self.r4815_written = false;
240
241        // 2 of the 3 special actions apply after $4814 and $4815 are written
242        match mode.special_action {
243            DirectRomSpecialAction::Add8Bit => {
244                let offset = extend_u8(self.direct_data_rom_offset.lsb(), mode.sign_extend_offset);
245                self.direct_data_rom_base =
246                    self.direct_data_rom_base.wrapping_add(offset) & 0xFFFFFF;
247            }
248            DirectRomSpecialAction::Add16BitAfterWrite => {
249                let offset = extend_u16(self.direct_data_rom_offset, mode.sign_extend_offset);
250                self.direct_data_rom_base =
251                    self.direct_data_rom_base.wrapping_add(offset) & 0xFFFFFF;
252            }
253            DirectRomSpecialAction::None | DirectRomSpecialAction::Add16BitAfterRead => {}
254        }
255    }
256
257    pub fn write_direct_data_rom_step_low(&mut self, value: u8) {
258        self.direct_data_rom_step.set_lsb(value);
259    }
260
261    pub fn write_direct_data_rom_step_high(&mut self, value: u8) {
262        self.direct_data_rom_step.set_msb(value);
263    }
264
265    pub fn write_direct_data_rom_mode(&mut self, value: u8) {
266        // Direct data ROM mode changes are not applied immediately, only after $4814 and $4815 are
267        // written (direct data ROM offset)
268        self.direct_data_rom_mode_byte = value;
269        self.r4814_written = false;
270        self.r4815_written = false;
271
272        // Writing to this register seems to reset offset? Momotarou Dentetsu Happy has glitchy
273        // audio without doing this
274        self.direct_data_rom_offset = 0;
275    }
276
277    pub fn read_dividend(&self, address: u32) -> u8 {
278        // Dividend is at $4820-$4823
279        let shift = 8 * (address & 0x3);
280        (self.math.dividend >> shift) as u8
281    }
282
283    pub fn write_dividend(&mut self, address: u32, value: u8) {
284        // Divdend is at $4820-$4823
285        let shift = 8 * (address & 0x3);
286        self.math.dividend = (self.math.dividend & !(0xFF << shift)) | (u32::from(value) << shift);
287    }
288
289    pub fn write_multiplier_low(&mut self, value: u8) {
290        self.math.multiplier.set_lsb(value);
291    }
292
293    pub fn write_multiplier_high(&mut self, value: u8) {
294        self.math.multiplier.set_msb(value);
295
296        // Writing multiplier MSB initiates multiplication
297        self.math.execute_multiplication();
298    }
299
300    pub fn write_divisor_low(&mut self, value: u8) {
301        self.math.divisor.set_lsb(value);
302    }
303
304    pub fn write_divisor_high(&mut self, value: u8) {
305        self.math.divisor.set_msb(value);
306
307        // Writing divisor MSB initiates division
308        self.math.execute_division();
309    }
310
311    pub fn read_math_result(&self, address: u32) -> u8 {
312        // Result is at $4828-$482B
313        let shift = 8 * (address & 0x3);
314        (self.math.result >> shift) as u8
315    }
316
317    pub fn write_math_mode(&mut self, value: u8) {
318        self.math.signed = value.bit(0);
319    }
320}
321
322fn extend_u8(value: u8, sign_extend: bool) -> u32 {
323    if sign_extend { (value as i8 as u32) & 0xFFFFFF } else { value.into() }
324}
325
326fn extend_u16(value: u16, sign_extend: bool) -> u32 {
327    if sign_extend { (value as i16 as u32) & 0xFFFFFF } else { value.into() }
328}