rkg_utils/input_data/dpad_input.rs
1/// Errors that can occur while parsing a [`DPadButton`].
2#[derive(thiserror::Error, Debug)]
3pub enum DPadButtonError {
4 /// The extracted button value did not correspond to any known D-pad direction.
5 #[error("Non Existent DPad Button")]
6 NonExistentDPadButton,
7}
8
9/// A D-pad button state for a single input entry in a Mario Kart Wii ghost file.
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub enum DPadButton {
12 /// No D-pad button is pressed.
13 None,
14 Up,
15 Down,
16 Left,
17 Right,
18}
19
20/// Extracts a [`DPadButton`] from the upper nibble of a packed input byte.
21///
22/// The button ID is stored in bits 4–6 of the byte (mask `0x70`).
23///
24/// # Errors
25///
26/// Returns [`DPadButtonError::NonExistentDPadButton`] if the extracted 3-bit
27/// value does not map to a known D-pad direction (valid range is 0–4).
28pub fn parse_dpad_button(value: u8) -> Result<DPadButton, DPadButtonError> {
29 let button = (value & 0x70) >> 4;
30
31 match button {
32 0 => Ok(DPadButton::None),
33 1 => Ok(DPadButton::Up),
34 2 => Ok(DPadButton::Down),
35 3 => Ok(DPadButton::Left),
36 4 => Ok(DPadButton::Right),
37 _ => Err(DPadButtonError::NonExistentDPadButton),
38 }
39}
40
41/// Errors that can occur while parsing a [`DPadInput`].
42#[derive(thiserror::Error, Debug)]
43pub enum DPadInputError {
44 /// The raw bytes did not form a valid D-pad input entry.
45 #[error("Invalid DPad Input")]
46 InvalidDpadInput,
47 /// The D-pad button value could not be parsed.
48 #[error("Invalid DPad Button: {0}")]
49 InvalidButton(#[from] DPadButtonError),
50}
51
52/// A single run-length encoded D-pad input entry from a Mario Kart Wii ghost file.
53///
54/// Each entry records which D-pad button was held and for how many consecutive
55/// frames it was held. Frame durations exceeding 255 frames are encoded across
56/// two fields: the lower nibble of the first byte stores the number of
57/// additional 256-frame intervals, and the second byte stores the remainder.
58#[derive(Debug)]
59pub struct DPadInput {
60 /// The D-pad button that was held during this input entry.
61 button: DPadButton,
62 /// The number of frames this D-pad state was held.
63 frame_duration: u32,
64}
65
66impl DPadInput {
67 /// Returns the D-pad button held during this input entry.
68 pub fn button(&self) -> DPadButton {
69 self.button
70 }
71
72 /// Returns the number of frames this D-pad state was held.
73 pub fn frame_duration(&self) -> u32 {
74 self.frame_duration
75 }
76}
77
78/// Two [`DPadInput`] values are equal if they share the same button state,
79/// regardless of frame duration.
80impl PartialEq for DPadInput {
81 fn eq(&self, other: &DPadInput) -> bool {
82 self.button == other.button
83 }
84}
85
86/// Parses a [`DPadInput`] from a 2-byte slice.
87///
88/// # Errors
89///
90/// Returns [`DPadInputError::InvalidButton`] if the button bits do not map to
91/// a known D-pad direction.
92impl TryFrom<&[u8]> for DPadInput {
93 type Error = DPadInputError;
94
95 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
96 let button = parse_dpad_button(value[0])?;
97 // 0x0F bit mask used if this input state was held longer than 255 frames, gives amount of 256-frame intervals to add to frame duration
98 let previous_full_byte_presses: u32 = (value[0] & 0x0F).into();
99 let frame_duration = value[1] as u32 + (previous_full_byte_presses * 256);
100
101 Ok(Self {
102 button,
103 frame_duration,
104 })
105 }
106}