inputs.rsannotatedinputs.rssource368 lines · 12.4 KB · raw
1use crate::api::SnesEmulatorConfig;
2use crate::input::{
3    SnesController, SnesInputs, SnesJoypadStateExt, SnesMouseState, SuperScopeState,
4};
5use bincode::{Decode, Encode};
6use jgenesis_common::num::GetBit;
7use snes_config::SnesJoypadState;
8
9const AUTO_JOYPAD_DURATION_MCLK: u64 = 4224;
10
11#[derive(Debug, Clone, Copy, Encode, Decode)]
12struct MouseRegister {
13    sensitivity: u8,
14    last_x: f64,
15    last_y: f64,
16    x_direction: bool,
17    x_magnitude: u8,
18    y_direction: bool,
19    y_magnitude: u8,
20    left: bool,
21    right: bool,
22}
23
24impl Default for MouseRegister {
25    fn default() -> Self {
26        Self {
27            sensitivity: 0,
28            last_x: 0.0,
29            last_y: 0.0,
30            x_direction: false,
31            x_magnitude: 0,
32            y_direction: false,
33            y_magnitude: 0,
34            left: false,
35            right: false,
36        }
37    }
38}
39
40impl MouseRegister {
41    fn update(&mut self, mouse: SnesMouseState, config_sensitivity: f64) {
42        // According to the manual, 0 is "slow", 1 is "normal", 2 is "fast"
43        // Normal and fast speeds have an exponential curve where the reported movement values
44        // increase more rapidly at higher speeds, but presumably any half-decent PC mouse already
45        // works that way, so instead of trying to emulate that just apply a fixed multiplier to dx/dy values
46        const SENSITIVITY_MULTIPLIERS: [f64; 3] = [0.75, 1.0, 1.5];
47
48        fn mouse_axis_to_magnitude(value: f64) -> u8 {
49            // Mouse magnitude values are unsigned 7-bit
50            // Multiplying by 0.2 is arbitrary, mouse feels way too sensitive without that
51            (0.2 * value).abs().round().clamp(0.0, 127.0) as u8
52        }
53
54        let sensitivity = config_sensitivity * SENSITIVITY_MULTIPLIERS[self.sensitivity as usize];
55
56        let dx = sensitivity * (mouse.x_position.get() - self.last_x);
57        let dy = sensitivity * (mouse.y_position.get() - self.last_y);
58        self.last_x = mouse.x_position.get();
59        self.last_y = mouse.y_position.get();
60
61        self.left = mouse.left;
62        self.right = mouse.right;
63
64        self.x_magnitude = mouse_axis_to_magnitude(dx);
65        self.y_magnitude = mouse_axis_to_magnitude(dy);
66
67        // Supposedly the direction bits are sticky and only change when magnitude is non-zero
68        if self.x_magnitude != 0 {
69            self.x_direction = dx < 0.0; // 0 = right, 1 = left
70        }
71        if self.y_magnitude != 0 {
72            self.y_direction = dy < 0.0; // 0 = down, 1 = up
73        }
74    }
75
76    fn increment_sensitivity(&mut self) {
77        // Sensitivity is always 0-2
78        self.sensitivity = (self.sensitivity + 1) % 3;
79        log::debug!("SNES mouse sensitivity set to {}", self.sensitivity);
80    }
81
82    fn to_register_bits(self) -> u32 {
83        fn reverse_bits_u2(value: u8) -> u8 {
84            ((value & 1) << 1) | ((value & 2) >> 1)
85        }
86
87        fn reverse_bits_u7(value: u8) -> u8 {
88            value.reverse_bits() >> 1
89        }
90
91        // First 8 bits are always 0
92        (u32::from(self.right) << 8)
93        | (u32::from(self.left) << 9)
94        | (u32::from(reverse_bits_u2(self.sensitivity)) << 10)
95        | (1 << 15) // Bits 12-15 are always mouse ID (0, 0, 0, 1)
96        | (u32::from(self.y_direction) << 16)
97        | (u32::from(reverse_bits_u7(self.y_magnitude)) << 17)
98        | (u32::from(self.x_direction) << 24)
99        | (u32::from(reverse_bits_u7(self.x_magnitude)) << 25)
100    }
101}
102
103#[derive(Debug, Clone, Copy, Encode, Decode)]
104struct SuperScopeRegister {
105    fire: bool,
106    cursor: bool,
107    pause: bool,
108    turbo: bool,
109    offscreen: bool,
110    position: Option<(u16, u16)>,
111}
112
113impl Default for SuperScopeRegister {
114    fn default() -> Self {
115        Self {
116            fire: false,
117            cursor: false,
118            pause: false,
119            turbo: false,
120            offscreen: true,
121            position: None,
122        }
123    }
124}
125
126impl SuperScopeRegister {
127    fn update(&mut self, current_state: SuperScopeState, last_strobe_state: SuperScopeState) {
128        if current_state.fire && !last_strobe_state.fire {
129            // Turbo bit updates only when fire is pressed
130            self.turbo = current_state.turbo;
131        }
132
133        // If turbo is off, fire bit is only set for one strobe after the button is pressed
134        self.fire = if current_state.turbo {
135            current_state.fire
136        } else {
137            current_state.fire && !last_strobe_state.fire
138        };
139
140        // Cursor bit is always set to button press state
141        self.cursor = current_state.cursor;
142
143        // Pause is only set for one strobe after the button is pressed
144        self.pause = current_state.pause && !last_strobe_state.pause;
145
146        // Offscreen bit is only updated when fire or cursor bit is set
147        if self.fire || self.cursor {
148            self.offscreen = current_state.position.is_none();
149        }
150
151        // Position is only used for latching, which only occurs when fire or cursor bit is set
152        self.position = current_state.position;
153    }
154
155    fn to_register_word(self) -> u16 {
156        u16::from(self.fire)
157            | (u16::from(self.cursor) << 1)
158            | (u16::from(self.turbo) << 2)
159            | (u16::from(self.pause) << 3)
160            | (u16::from(self.offscreen) << 6)
161            | 0xFF00 // ID bits, always 1
162    }
163}
164
165fn register_word_to_u32(word: u16) -> u32 {
166    // Controllers with 16 bits always read out constant 1s afterwards
167    u32::from(word) | (0xFFFF << 16)
168}
169
170#[derive(Debug, Clone, Encode, Decode)]
171struct ControllerPort {
172    auto_joypad_inputs: u16,
173    manual_joypad_inputs: u32,
174    mouse_register: MouseRegister,
175    mouse_sensitivity: f64,
176    super_scope_register: SuperScopeRegister,
177    last_strobe_inputs: SnesController,
178}
179
180impl ControllerPort {
181    fn new(config: &SnesEmulatorConfig) -> Self {
182        Self {
183            auto_joypad_inputs: 0,
184            manual_joypad_inputs: 0,
185            mouse_register: MouseRegister::default(),
186            mouse_sensitivity: config.mouse_sensitivity,
187            super_scope_register: SuperScopeRegister::default(),
188            last_strobe_inputs: SnesController::None,
189        }
190    }
191
192    fn update_strobe(&mut self, strobe: bool, current_inputs: SnesController) {
193        if !matches!(current_inputs, SnesController::Mouse(_)) {
194            self.mouse_register = MouseRegister::default();
195        }
196
197        if !matches!(current_inputs, SnesController::SuperScope(_)) {
198            self.super_scope_register = SuperScopeRegister::default();
199        }
200
201        match current_inputs {
202            SnesController::Gamepad(joypad) => {
203                // Gamepads only reset on strobe 1->0 transitions
204                if !strobe {
205                    self.manual_joypad_inputs = register_word_to_u32(joypad.to_register_word());
206                }
207            }
208            SnesController::Mouse(mouse) => {
209                // Not sure the mouse actually works this way, but only update mouse state
210                // on strobe 1->0 transitions because of how mouse movement is tracked and latched
211                if !strobe {
212                    self.mouse_register.update(mouse, self.mouse_sensitivity);
213                    self.manual_joypad_inputs = self.mouse_register.to_register_bits();
214                }
215            }
216            SnesController::SuperScope(super_scope) => {
217                self.manual_joypad_inputs =
218                    register_word_to_u32(self.super_scope_register.to_register_word());
219
220                let last_strobe_state = match self.last_strobe_inputs {
221                    SnesController::SuperScope(state) => state,
222                    _ => SuperScopeState::default(),
223                };
224
225                self.super_scope_register.update(super_scope, last_strobe_state);
226            }
227            SnesController::None => {
228                // All bits read 0 when no controller is connected
229                // Some games use this for controller detection (e.g. Donkey Kong Country)
230                self.manual_joypad_inputs = 0;
231            }
232        }
233
234        self.last_strobe_inputs = current_inputs;
235    }
236
237    fn next_manual_bit(&mut self, strobe: bool) -> bool {
238        if strobe && matches!(self.last_strobe_inputs, SnesController::Mouse(_)) {
239            // Reading from the mouse while strobe=1 increments sensitivity
240            self.mouse_register.increment_sensitivity();
241            return false;
242        }
243
244        let next_bit = self.manual_joypad_inputs.bit(0);
245        self.manual_joypad_inputs =
246            (self.manual_joypad_inputs >> 1) | (u32::from(self.end_of_input_bit()) << 31);
247        next_bit
248    }
249
250    fn end_of_input_bit(&self) -> bool {
251        // Reading past end of inputs produces 1s if a controller is connected, 0s if not
252        !matches!(self.last_strobe_inputs, SnesController::None)
253    }
254
255    fn do_auto_read(&mut self) -> u16 {
256        // Auto joypad read always reads out 16 bits serially, earliest in most significant bits
257        let mut auto_read_inputs = 0;
258        for _ in 0..16 {
259            auto_read_inputs = (auto_read_inputs << 1) | u16::from(self.next_manual_bit(false));
260        }
261        auto_read_inputs
262    }
263}
264
265#[derive(Debug, Clone, Encode, Decode)]
266pub struct InputState {
267    auto_read_cycles_remaining: u64,
268    auto_joypad_p1_inputs: u16,
269    auto_joypad_p2_inputs: u16,
270    strobe: bool,
271    p1: ControllerPort,
272    p2: ControllerPort,
273    inputs: SnesInputs,
274    allow_opposing_directions: bool,
275}
276
277impl InputState {
278    pub fn new(config: &SnesEmulatorConfig) -> Self {
279        Self {
280            auto_read_cycles_remaining: 0,
281            auto_joypad_p1_inputs: SnesJoypadState::default().to_register_word(),
282            auto_joypad_p2_inputs: SnesJoypadState::default().to_register_word(),
283            strobe: false,
284            p1: ControllerPort::new(config),
285            p2: ControllerPort::new(config),
286            inputs: SnesInputs::default(),
287            allow_opposing_directions: config.allow_opposing_joypad_directions,
288        }
289    }
290
291    pub fn set_strobe(&mut self, strobe: bool) {
292        if strobe != self.strobe {
293            self.p1.update_strobe(
294                strobe,
295                self.inputs.p1.with_allow_opposing_directions(self.allow_opposing_directions),
296            );
297            self.p2.update_strobe(
298                strobe,
299                self.inputs.p2.with_allow_opposing_directions(self.allow_opposing_directions),
300            );
301        }
302
303        self.strobe = strobe;
304    }
305
306    pub fn auto_joypad_read_in_progress(&self) -> bool {
307        self.auto_read_cycles_remaining != 0
308    }
309
310    pub fn auto_joypad_p1_inputs(&self) -> u16 {
311        self.auto_joypad_p1_inputs
312    }
313
314    pub fn auto_joypad_p2_inputs(&self) -> u16 {
315        self.auto_joypad_p2_inputs
316    }
317
318    pub fn next_manual_p1_bit(&mut self) -> bool {
319        self.p1.next_manual_bit(self.strobe)
320    }
321
322    pub fn next_manual_p2_bit(&mut self) -> bool {
323        self.p2.next_manual_bit(self.strobe)
324    }
325
326    pub fn start_auto_joypad_read(&mut self) {
327        self.auto_read_cycles_remaining = AUTO_JOYPAD_DURATION_MCLK;
328    }
329
330    pub fn tick(&mut self, master_cycles_elapsed: u64, inputs: SnesInputs) {
331        self.inputs = inputs;
332
333        if self.auto_read_cycles_remaining != 0 {
334            self.progress_auto_joypad_read(master_cycles_elapsed);
335        }
336    }
337
338    fn progress_auto_joypad_read(&mut self, master_cycles_elapsed: u64) {
339        self.auto_read_cycles_remaining =
340            self.auto_read_cycles_remaining.saturating_sub(master_cycles_elapsed);
341
342        if self.auto_read_cycles_remaining == 0 {
343            // Auto joypad read strobes the joypad while reading inputs; this populates the manual
344            // joypad read registers
345            self.set_strobe(true);
346            self.set_strobe(false);
347
348            self.auto_joypad_p1_inputs = self.p1.do_auto_read();
349            self.auto_joypad_p2_inputs = self.p2.do_auto_read();
350        }
351    }
352
353    pub fn hv_latch(&self) -> Option<(u16, u16)> {
354        // Super Scope can only trigger HV latching behavior when plugged into port 2
355        let super_scope_register = self.p2.super_scope_register;
356
357        // Super Scope latches the PPU at H=X+40, V=Y+1 when Fire or Cursor is set
358        (super_scope_register.fire || super_scope_register.cursor)
359            .then(|| super_scope_register.position.map(|(x, y)| (x + 40, y + 1)))
360            .flatten()
361    }
362
363    pub fn reload_config(&mut self, config: &SnesEmulatorConfig) {
364        self.allow_opposing_directions = config.allow_opposing_joypad_directions;
365        self.p1.mouse_sensitivity = config.mouse_sensitivity;
366        self.p2.mouse_sensitivity = config.mouse_sensitivity;
367    }
368}