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}