Code for handling Sega Master System / Game Gear controller input I/O registers
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 10enum PinDirection { 11 Input, 12 Output(bool), 13} 14 15impl PinDirection { 16 fn bit(self, joypad_value: bool) -> bool { 17 match self { 18 Self::Input => joypad_value, 19 Self::Output(output_value) => output_value, 20 } 21 } 22} 23 24#[derive(Debug, Clone, Encode, Decode)] 25pub struct InputState { 26 inputs: SmsGgInputs, 27 port_a_tr: PinDirection, 28 port_a_th: PinDirection, 29 port_b_tr: PinDirection, 30 port_b_th: PinDirection, 31 region: SmsGgRegion, 32 reset: bool, 33 allow_opposing_directions: bool, 34} 35 36impl InputState { 37 pub fn new(region: SmsGgRegion, config: &SmsGgEmulatorConfig) -> Self { 38 Self { 39 inputs: SmsGgInputs::default(), 40 port_a_tr: PinDirection::Input, 41 port_a_th: PinDirection::Input, 42 port_b_tr: PinDirection::Input, 43 port_b_th: PinDirection::Input, 44 region, 45 reset: false, 46 allow_opposing_directions: config.allow_opposing_joypad_directions, 47 } 48 } 49 50 pub fn pause_pressed(&self) -> bool { 51 self.inputs.pause 52 } 53 54 pub fn set_inputs(&mut self, inputs: SmsGgInputs) { 55 self.inputs = inputs; 56 } 57 58 pub fn region(&self) -> SmsGgRegion { 59 self.region 60 } 61 62 pub fn reload_config(&mut self, region: SmsGgRegion, config: &SmsGgEmulatorConfig) { 63 self.region = region; 64 self.allow_opposing_directions = config.allow_opposing_joypad_directions; 65 } 66 67 pub fn set_reset(&mut self, reset: bool) { 68 self.reset = reset; 69 } 70 71 pub fn write_control(&mut self, value: u8, vdp: &mut Vdp) { 72 log::debug!("I/O control write {value:02X}"); 73 74 let prev_a_th = self.port_a_th != PinDirection::Output(false); 75 let prev_b_th = self.port_b_th != PinDirection::Output(false); 76 77 self.port_b_th = 78 if value.bit(3) { PinDirection::Input } else { PinDirection::Output(value.bit(7)) }; 79 self.port_b_tr = 80 if value.bit(2) { PinDirection::Input } else { PinDirection::Output(value.bit(6)) }; 81 self.port_a_th = 82 if value.bit(1) { PinDirection::Input } else { PinDirection::Output(value.bit(5)) }; 83 self.port_a_tr = 84 if value.bit(0) { PinDirection::Input } else { PinDirection::Output(value.bit(4)) }; 85 86 if (!prev_a_th && self.port_a_th != PinDirection::Output(false)) 87 || (!prev_b_th && self.port_b_th != PinDirection::Output(false)) 88 { 89 vdp.latch_h_counter_on_th_change(); 90 } 91 } 92 93 pub fn port_dc(&self) -> u8 { 94 let p1_inputs = 95 self.inputs.p1.with_allow_opposing_directions(self.allow_opposing_directions); 96 let p2_inputs = 97 self.inputs.p2.with_allow_opposing_directions(self.allow_opposing_directions); 98 99 let port_a_tr_bit = u8::from(self.port_a_tr.bit(!p1_inputs.button2)) << 5; 100 101 (u8::from(!p2_inputs.down) << 7) 102 | (u8::from(!p2_inputs.up) << 6) 103 | port_a_tr_bit 104 | (u8::from(!p1_inputs.button1) << 4) 105 | (u8::from(!p1_inputs.right) << 3) 106 | (u8::from(!p1_inputs.left) << 2) 107 | (u8::from(!p1_inputs.down) << 1) 108 | u8::from(!p1_inputs.up) 109 } 110 111 pub fn port_dd(&self) -> u8 { 112 let p2_inputs = 113 self.inputs.p2.with_allow_opposing_directions(self.allow_opposing_directions); 114 115 let port_b_th_bit = 116 u8::from(self.region == SmsGgRegion::International && self.port_b_th.bit(true)) << 7; 117 let port_a_th_bit = 118 u8::from(self.region == SmsGgRegion::International && self.port_a_th.bit(true)) << 6; 119 let port_b_tr_bit = u8::from(self.port_b_tr.bit(!p2_inputs.button2)) << 3; 120 121 port_b_th_bit 122 | port_a_th_bit 123 | (1 << 5) 124 | (u8::from(!self.reset) << 4) 125 | port_b_tr_bit 126 | (u8::from(!p2_inputs.button1) << 2) 127 | (u8::from(!p2_inputs.right) << 1) 128 | u8::from(!p2_inputs.left) 129 } 130}