1use crate::api::PceEmulatorConfig; 2use bincode::{Decode, Encode}; 3use jgenesis_common::num::GetBit; 4use pce_config::{PceInputDevice, PceInputs, PceJoypadState, PceRegion}; 5 6const TURBO_TAP_GAMEPADS: u8 = pce_config::TURBO_TAP_GAMEPADS; 7 8trait PceJoypadStateExt { 9 fn with_simultaneous_run_select(self, allow_simultaneous_run_select: bool) -> Self; 10} 11 12impl PceJoypadStateExt for PceJoypadState { 13 fn with_simultaneous_run_select(mut self, allow_simultaneous_run_select: bool) -> Self { 14 if allow_simultaneous_run_select { 15 return self; 16 } 17 18 if self.run && self.select { 19 self.run = false; 20 self.select = false; 21 } 22 23 self 24 } 25} 26 27#[derive(Debug, Clone, Encode, Decode)] 28pub struct InputState { 29 region: PceRegion, 30 input_device: PceInputDevice, 31 turbo_tap_connected: [bool; TURBO_TAP_GAMEPADS as usize], 32 inputs: PceInputs, 33 latched_inputs: PceInputs, 34 select_pin: bool, 35 clear_pin: bool, 36 turbo_tap_gamepad_idx: u8, 37 allow_opposing_directions: bool, 38 allow_simultaneous_run_select: bool, 39} 40 41impl InputState { 42 pub fn new(config: PceEmulatorConfig) -> Self { 43 Self { 44 region: config.region, 45 input_device: config.input_device, 46 turbo_tap_connected: config.turbo_tap_connected, 47 inputs: PceInputs::default(), 48 latched_inputs: PceInputs::default(), 49 select_pin: false, 50 clear_pin: false, 51 turbo_tap_gamepad_idx: TURBO_TAP_GAMEPADS, 52 allow_opposing_directions: config.allow_opposing_joypad_directions, 53 allow_simultaneous_run_select: config.allow_simultaneous_run_select, 54 } 55 } 56 57 pub fn reload_config(&mut self, config: PceEmulatorConfig) { 58 if self.input_device != config.input_device { 59 self.turbo_tap_gamepad_idx = TURBO_TAP_GAMEPADS; 60 } 61 62 self.region = config.region; 63 self.input_device = config.input_device; 64 self.turbo_tap_connected = config.turbo_tap_connected; 65 self.allow_opposing_directions = config.allow_opposing_joypad_directions; 66 self.allow_simultaneous_run_select = config.allow_simultaneous_run_select; 67 } 68 69 pub fn update_inputs(&mut self, inputs: PceInputs) { 70 self.inputs = inputs; 71 } 72 73 pub fn read_port(&mut self) -> u8 { 74 let controller_data = match self.input_device { 75 PceInputDevice::TwoButtonGamepad => self.read_gamepad(), 76 PceInputDevice::TurboTap => self.read_turbo_tap(), 77 }; 78 79 // Bit 7: CD-ROM present (1 = not attached) 80 // Bits 5 and 4 always read 1 81 let region_bit = u8::from(self.region == PceRegion::PcEngine); 82 let value = (1 << 7) | (region_bit << 6) | (1 << 5) | (1 << 4) | controller_data; 83 84 log::trace!( 85 "I/O port read; SEL={} CLR={}, value={value:02X}", 86 u8::from(self.select_pin), 87 u8::from(self.clear_pin) 88 ); 89 90 value 91 } 92 93 fn read_gamepad(&self) -> u8 { 94 if self.clear_pin { 95 return 0x00; 96 } 97 98 self.read_joypad(self.latched_inputs.p1) 99 } 100 101 fn read_turbo_tap(&self) -> u8 { 102 if self.turbo_tap_gamepad_idx >= TURBO_TAP_GAMEPADS { 103 return 0x00; 104 } 105 106 if !self.turbo_tap_connected[self.turbo_tap_gamepad_idx as usize] { 107 return 0x0F; 108 } 109 110 let joypad = [ 111 self.latched_inputs.p1, 112 self.latched_inputs.p2, 113 self.latched_inputs.p3, 114 self.latched_inputs.p4, 115 self.latched_inputs.p5, 116 ][self.turbo_tap_gamepad_idx as usize]; 117 self.read_joypad(joypad) 118 } 119 120 fn read_joypad(&self, mut joypad: PceJoypadState) -> u8 { 121 joypad = joypad 122 .with_allow_opposing_directions(self.allow_opposing_directions) 123 .with_simultaneous_run_select(self.allow_simultaneous_run_select); 124 125 let data = if self.select_pin { 126 [joypad.up, joypad.right, joypad.down, joypad.left] 127 } else { 128 [joypad.button1, joypad.button2, joypad.select, joypad.run] 129 }; 130 131 (u8::from(!data[3]) << 3) 132 | (u8::from(!data[2]) << 2) 133 | (u8::from(!data[1]) << 1) 134 | u8::from(!data[0]) 135 } 136 137 pub fn write_port(&mut self, value: u8) { 138 let prev_select = self.select_pin; 139 let prev_clear = self.clear_pin; 140 141 self.clear_pin = value.bit(1); 142 self.select_pin = value.bit(0); 143 144 match self.input_device { 145 PceInputDevice::TwoButtonGamepad => { 146 // Latching inputs on CLR 1->0 transitions fixes Order of the Griffon sometimes 147 // double-reading button presses 148 if prev_clear && !self.clear_pin { 149 self.latched_inputs = self.inputs; 150 } 151 } 152 PceInputDevice::TurboTap => { 153 // Turbo Tap resets on CLR 0->1 transition while SEL=1 154 if prev_select && self.select_pin && !prev_clear && self.clear_pin { 155 self.turbo_tap_gamepad_idx = 0; 156 self.latched_inputs = self.inputs; 157 } 158 159 // Turbo Tap counter increments on SEL 0->1 transitions 160 if !prev_select && self.select_pin { 161 self.turbo_tap_gamepad_idx = 162 (self.turbo_tap_gamepad_idx + 1).min(TURBO_TAP_GAMEPADS); 163 } 164 } 165 } 166 167 log::trace!( 168 "I/O port write: {value:02X} (SEL={} CLR={})", 169 u8::from(self.select_pin), 170 u8::from(self.clear_pin) 171 ); 172 } 173}