1use crate::input::Pins; 2use bincode::{Decode, Encode}; 3use genesis_config::MegaMouseJoypadState; 4use std::cmp; 5 6const MAX_COUNTER: u8 = 9; 7 8const COUNTER_INCREMENT_WAIT_CYCLES: u32 = 23; // Roughly 3 μs, some games depend on counter changing well before TL changes 9const TL_CHANGE_WAIT_CYCLES: u32 = 76; // Roughly 10 μs 10 11#[derive(Debug, Clone, Encode, Decode)] 12pub struct MegaMouseState { 13 pub joypad: MegaMouseJoypadState, 14 latched: MegaMouseJoypadState, 15 latched_x: i16, 16 latched_y: i16, 17 sensitivity: f64, 18 counter: u8, 19 tl: bool, 20 clock_frozen: bool, 21 counter_increment_wait_cycles: u32, 22 tl_change_wait_cycles: u32, 23 prev_th: bool, 24 prev_tr: bool, 25} 26 27impl MegaMouseState { 28 pub fn new(joypad: MegaMouseJoypadState, sensitivity: f64) -> Self { 29 Self { 30 joypad, 31 latched: joypad, 32 latched_x: 0, 33 latched_y: 0, 34 sensitivity, 35 counter: 0, 36 tl: true, 37 clock_frozen: false, 38 counter_increment_wait_cycles: 0, 39 tl_change_wait_cycles: 0, 40 prev_th: true, 41 prev_tr: true, 42 } 43 } 44 45 pub fn update_pins(&mut self, pins: &mut Pins) { 46 let th = pins.th(); 47 let tr = pins.tr(); 48 49 let th_changed = th != self.prev_th; 50 let tr_changed = tr != self.prev_tr; 51 52 if tr_changed { 53 if tr != self.tl && !self.clock_frozen { 54 // TL changes to reflect TR, but only after a delay (which several games depend on) 55 self.tl_change_wait_cycles = TL_CHANGE_WAIT_CYCLES; 56 57 if !th { 58 // TR changes while TH=0 clock the internal counter 59 self.counter_increment_wait_cycles = COUNTER_INCREMENT_WAIT_CYCLES; 60 } 61 } else if tr == self.tl { 62 self.counter_increment_wait_cycles = 0; 63 self.tl_change_wait_cycles = 0; 64 } 65 } 66 67 if th { 68 // While TH=1, mouse is deselected and outputs constants 0s 69 self.counter = 0; 70 self.counter_increment_wait_cycles = 0; 71 72 // TH=1 + TR=1 resets the mouse 73 self.clock_frozen &= !tr; 74 75 // There appears to be additional delay after a reset, but not always, and it's not 76 // clear to me why that delay happens so I'm not emulating it 77 } else if th_changed { 78 // TH 1->0 transition begins a transfer 79 self.latch_inputs(); 80 self.counter = 1; 81 82 // If previous state was TH=1 TR=0, the clock freezes until the mouse is reset (based on test ROM) 83 // TODO does this only happen if there was also a TR 0->1 transition? 84 self.clock_frozen = !self.prev_tr; 85 } 86 87 self.prev_th = th; 88 self.prev_tr = tr; 89 90 log::debug!( 91 "Mouse update, TH={} TR={}; TL={} counter={}", 92 u8::from(th), 93 u8::from(tr), 94 u8::from(self.tl), 95 self.counter 96 ); 97 98 pins.input_tl(self.tl); 99 100 match self.counter { 101 0 => { 102 // No transfer in progress, output constant 0s 103 pins.input_data_nibble(0b0000); 104 } 105 1..=3 => { 106 // Mouse ID (3 nibble sequence) 107 pins.input_data_nibble([0xB, 0xF, 0xF][(self.counter - 1) as usize]); 108 } 109 4 => { 110 // Axis sign and overflow bits (overflow not emulated) 111 pins.input_d3(false); // Y overflow 112 pins.input_d2(false); // X overflow 113 pins.input_d1(self.latched_y < 0); 114 pins.input_d0(self.latched_x < 0); 115 } 116 5 => { 117 // Buttons 118 pins.input_d3(self.latched.start); 119 pins.input_d2(self.latched.middle); 120 pins.input_d1(self.latched.right); 121 pins.input_d0(self.latched.left); 122 } 123 6 => { 124 // X axis, high nibble 125 pins.input_data_nibble((self.latched_x as u8) >> 4); 126 } 127 7 => { 128 // X axis, low nibble 129 pins.input_data_nibble((self.latched_x as u8) & 0xF); 130 } 131 8 => { 132 // Y axis, high nibble 133 pins.input_data_nibble((self.latched_y as u8) >> 4); 134 } 135 9 => { 136 // Y axis, low nibble 137 pins.input_data_nibble((self.latched_y as u8) & 0xF); 138 } 139 _ => panic!( 140 "invalid Mega Mouse counter value {}, should be <= {MAX_COUNTER}", 141 self.counter 142 ), 143 } 144 } 145 146 fn latch_inputs(&mut self) { 147 fn f64_to_i9(value: f64) -> i16 { 148 // Mega Mouse axis values are signed 9-bit 149 // Arbitrarily multiply by 0.3 because using full pixel values is way too sensitive 150 (0.3 * value).round().clamp(-256.0, 255.0) as i16 151 } 152 153 let dx = self.sensitivity * (self.joypad.x_position.get() - self.latched.x_position.get()); 154 let dy = self.sensitivity * (self.joypad.y_position.get() - self.latched.y_position.get()); 155 self.latched = self.joypad; 156 157 self.latched_x = f64_to_i9(dx); 158 self.latched_y = f64_to_i9(-dy); // Mega Mouse Y axis is inverted (+ is up, - is down) 159 } 160 161 pub fn tick(&mut self, m68k_cycles: u32, pins: &mut Pins) { 162 if self.counter_increment_wait_cycles == 0 && self.tl_change_wait_cycles == 0 { 163 return; 164 } 165 166 if self.counter_increment_wait_cycles != 0 { 167 self.counter_increment_wait_cycles = 168 self.counter_increment_wait_cycles.saturating_sub(m68k_cycles); 169 if self.counter_increment_wait_cycles == 0 { 170 self.counter = cmp::min(MAX_COUNTER, self.counter + 1); 171 } 172 } 173 174 if self.tl_change_wait_cycles != 0 { 175 self.tl_change_wait_cycles = self.tl_change_wait_cycles.saturating_sub(m68k_cycles); 176 if self.tl_change_wait_cycles == 0 { 177 self.tl = self.prev_tr; 178 } 179 } 180 181 self.update_pins(pins); 182 } 183 184 pub fn set_sensitivity(&mut self, sensitivity: f64) { 185 self.sensitivity = sensitivity; 186 } 187}