input.rsannotatedinput.rssource173 lines · 5.5 KB · raw
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}