obc1.rsannotatedobc1.rssource144 lines · 4.7 KB · raw
1//! OBC1, an OBJ controller chip that is designed to enable updating the OAM sprite table more efficiently
2//!
3//! Used by 1 game, Metal Combat: Falcon's Revenge
4
5use crate::common;
6use crate::common::{Rom, impl_take_set_rom};
7use bincode::{Decode, Encode};
8use jgenesis_common::num::GetBit;
9use jgenesis_proc_macros::PartialClone;
10
11#[derive(Debug, Clone, Encode, Decode, PartialClone)]
12pub struct Obc1 {
13    #[partial_clone(default)]
14    rom: Rom,
15    sram: Box<[u8]>,
16    oam_base: u16,
17    oam_index: u16,
18    register_7: u8,
19}
20
21const OAM_BASE_0: u16 = 0x1C00;
22const OAM_BASE_1: u16 = 0x1800;
23
24impl Obc1 {
25    #[must_use]
26    pub fn new(rom: Box<[u8]>, sram: Box<[u8]>) -> Self {
27        Self { rom: Rom(rom), sram, oam_base: OAM_BASE_0, oam_index: 0, register_7: 0 }
28    }
29
30    #[inline]
31    #[must_use]
32    pub fn read(&self, address: u32) -> Option<u8> {
33        let bank = (address >> 16) & 0xFF;
34        let offset = address & 0xFFFF;
35        match (bank, offset) {
36            (0x00..=0x3F | 0x80..=0xBF, 0x7FF0..=0x7FF7) => {
37                // OBC1 internal ports
38                Some(self.read_obc1_port(address))
39            }
40            (0x00..=0x3F | 0x80..=0xBF, 0x8000..=0xFFFF) | (0x40..=0x6F | 0xC0..=0xFF, _) => {
41                // ROM
42                let rom_addr = common::lorom_map_rom_address(address, self.rom.len() as u32);
43                Some(self.rom[rom_addr as usize])
44            }
45            (0x00..=0x3F | 0x80..=0xBF, 0x6000..=0x7FEF | 0x7FF8..=0x7FFF)
46            | (0x70..=0x7D, 0x6000..=0x7FFF) => {
47                // SRAM (8KB)
48                let sram_addr = address & 0x1FFF;
49                Some(self.sram[sram_addr as usize])
50            }
51            _ => None,
52        }
53    }
54
55    fn read_obc1_port(&self, address: u32) -> u8 {
56        log::trace!("OBC1 register read: {}", address & 7);
57
58        match address & 7 {
59            port @ 0..=3 => {
60                // OAM lower bytes
61                let sram_addr = self.oam_base + (self.oam_index << 2) + port as u16;
62                self.sram[sram_addr as usize]
63            }
64            4 => {
65                // OAM upper bits
66                let sram_addr = self.oam_base + 0x200 + (self.oam_index >> 2);
67                self.sram[sram_addr as usize]
68            }
69            5 => {
70                // OAM base in SRAM (bit 0; 0=$7C00, 1=$7800)
71                u8::from(self.oam_base == OAM_BASE_1)
72            }
73            6 => {
74                // OAM index
75                self.oam_index as u8
76            }
77            7 => {
78                // Unknown (SRAM vs. I/O ports?)
79                self.register_7
80            }
81            _ => unreachable!("value & 7 is always <= 7"),
82        }
83    }
84
85    #[inline]
86    pub fn write(&mut self, address: u32, value: u8) {
87        let bank = (address >> 16) & 0xFF;
88        let offset = address & 0xFFFF;
89        match (bank, offset) {
90            (0x00..=0x3F | 0x80..=0xBF, 0x7FF0..=0x7FF7) => {
91                // OBC1 internal ports
92                self.write_obc1_port(address, value);
93            }
94            (0x00..=0x3F | 0x80..=0xBF, 0x6000..=0x7FEF | 0x7FF8..=0x7FFF)
95            | (0x70..=0x7D, 0x6000..=0x7FFF) => {
96                // SRAM (8KB)
97                let sram_addr = address & 0x1FFF;
98                self.sram[sram_addr as usize] = value;
99            }
100            _ => {}
101        }
102    }
103
104    fn write_obc1_port(&mut self, address: u32, value: u8) {
105        log::trace!("OBC1 register write: {} {value:02X}", address & 7);
106
107        match address & 7 {
108            port @ 0..=3 => {
109                // OAM lower bytes
110                let sram_addr = self.oam_base + (self.oam_index << 2) + port as u16;
111                self.sram[sram_addr as usize] = value;
112            }
113            4 => {
114                // OAM upper bits
115                // Only set the 2 bits for the specified OAM index
116                let sram_addr = (self.oam_base + 0x200 + (self.oam_index >> 2)) as usize;
117                let shift = 2 * (self.oam_index & 0x03);
118                self.sram[sram_addr] =
119                    (self.sram[sram_addr] & !(0x03 << shift)) | ((value & 0x03) << shift);
120            }
121            5 => {
122                // OAM base in SRAM (bit 0; 0=$7C00, 1=$7800)
123                self.oam_base = if value.bit(0) { OAM_BASE_1 } else { OAM_BASE_0 };
124            }
125            6 => {
126                // OAM index (0-127)
127                self.oam_index = u16::from(value & 0x7F);
128            }
129            7 => {
130                // Unknown (SRAM vs. I/O ports?)
131                self.register_7 = value;
132            }
133            _ => unreachable!("value & 7 is always <= 7"),
134        }
135    }
136
137    #[inline]
138    #[must_use]
139    pub fn sram(&self) -> &[u8] {
140        self.sram.as_ref()
141    }
142
143    impl_take_set_rom!(rom);
144}