Skip to main content

rkg_utils/header/mii/
hair.rs

1use std::convert::Infallible;
2
3use crate::byte_handler::{ByteHandlerError, FromByteHandler};
4
5/// Represents the hair customization options of a Mii,
6/// including hair style, color, and whether the style is horizontally flipped.
7#[derive(Clone, Copy)]
8pub struct Hair {
9    /// Hair style.
10    hair_type: HairType,
11    /// Hair color.
12    hair_color: HairColor,
13    /// Whether the hair style is horizontally mirrored.
14    is_flipped: bool,
15}
16
17impl Hair {
18    /// Creates a new [`Hair`] from its individual components.
19    ///
20    /// # Arguments
21    ///
22    /// * `hair_type` - Hair style.
23    /// * `hair_color` - Hair color.
24    /// * `is_flipped` - Whether the hair style is horizontally mirrored.
25    pub fn new(hair_type: HairType, hair_color: HairColor, is_flipped: bool) -> Self {
26        Self {
27            hair_type,
28            hair_color,
29            is_flipped,
30        }
31    }
32
33    /// Returns the hair style.
34    pub fn hair_type(&self) -> HairType {
35        self.hair_type
36    }
37
38    /// Returns the hair color.
39    pub fn hair_color(&self) -> HairColor {
40        self.hair_color
41    }
42
43    /// Returns whether the hair style is horizontally mirrored.
44    pub fn is_flipped(&self) -> bool {
45        self.is_flipped
46    }
47
48    /// Sets the hair style.
49    pub fn set_hair_type(&mut self, hair_type: HairType) {
50        self.hair_type = hair_type;
51    }
52
53    /// Sets the hair color.
54    pub fn set_hair_color(&mut self, hair_color: HairColor) {
55        self.hair_color = hair_color;
56    }
57
58    /// Sets whether the hair style is horizontally mirrored.
59    pub fn set_is_flipped(&mut self, is_flipped: bool) {
60        self.is_flipped = is_flipped;
61    }
62}
63
64/// Errors that can occur while deserializing [`Hair`].
65#[derive(thiserror::Error, Debug)]
66pub enum HairError {
67    /// The hair type byte did not map to a known [`HairType`] variant.
68    #[error("Type is invalid")]
69    TypeInvalid,
70    /// The hair color byte did not map to a known [`HairColor`] variant.
71    #[error("Color is invalid")]
72    ColorInvalid,
73    /// A `ByteHandler` operation failed.
74    #[error("ByteHandler Error: {0}")]
75    ByteHandlerError(#[from] ByteHandlerError),
76    /// Infallible conversion error; cannot occur at runtime.
77    #[error("")]
78    Infallible(#[from] Infallible),
79}
80
81/// Deserializes [`Hair`] from a `ByteHandler`.
82///
83/// The handler is shifted right by 1 bit before extracting the flip flag,
84/// hair type, and hair color from the packed Mii binary format.
85impl FromByteHandler for Hair {
86    type Err = HairError;
87    fn from_byte_handler<T>(handler: T) -> Result<Self, Self::Err>
88    where
89        T: TryInto<crate::byte_handler::ByteHandler>,
90        Self::Err: From<T::Error>,
91    {
92        let mut handler = handler.try_into()?;
93        handler.shift_right(1);
94        Ok(Self {
95            is_flipped: handler.read_bool(12),
96            hair_type: HairType::try_from(handler.copy_byte(0))
97                .map_err(|_| HairError::TypeInvalid)?,
98            hair_color: HairColor::try_from(handler.copy_byte(1) >> 5)
99                .map_err(|_| HairError::ColorInvalid)?,
100        })
101    }
102}
103
104/// Hair color options available in the Mii editor.
105///
106/// This palette is also shared by eyebrows and facial hair.
107#[derive(Clone, Copy, PartialEq, Debug)]
108pub enum HairColor {
109    Black,
110    Chocolate,
111    PhilippineBrown,
112    Walnut,
113    Gray,
114    Pineapple,
115    Grizzly,
116    Blond,
117}
118
119/// Converts a raw byte value from the Mii data format into a [`HairColor`].
120///
121/// Returns `Err(())` if the byte does not correspond to any known hair color.
122impl TryFrom<u8> for HairColor {
123    type Error = ();
124    fn try_from(value: u8) -> Result<Self, Self::Error> {
125        match value {
126            0x00 => Ok(Self::Black),
127            0x01 => Ok(Self::Chocolate),
128            0x02 => Ok(Self::PhilippineBrown),
129            0x03 => Ok(Self::Walnut),
130            0x04 => Ok(Self::Gray),
131            0x05 => Ok(Self::Pineapple),
132            0x06 => Ok(Self::Grizzly),
133            0x07 => Ok(Self::Blond),
134            _ => Err(()),
135        }
136    }
137}
138
139/// Converts a [`HairColor`] into its raw byte representation for the Mii data format.
140impl From<HairColor> for u8 {
141    fn from(value: HairColor) -> Self {
142        match value {
143            HairColor::Black => 0x00,
144            HairColor::Chocolate => 0x01,
145            HairColor::PhilippineBrown => 0x02,
146            HairColor::Walnut => 0x03,
147            HairColor::Gray => 0x04,
148            HairColor::Pineapple => 0x05,
149            HairColor::Grizzly => 0x06,
150            HairColor::Blond => 0x07,
151        }
152    }
153}
154
155/// Hair styles available in the Mii editor.
156///
157/// Variants prefixed with `LongUnknown` are long hair styles whose exact
158/// appearance has not yet been identified and are named by their raw byte value.
159/// Similarly, `ShortUnknown` variants are unidentified short styles.
160#[derive(Clone, Copy, PartialEq, Debug)]
161pub enum HairType {
162    NormalLong,
163    NormalMedium,
164    FrontLock,
165    PartingExtraLong,
166    MilitaryParting,
167    PartingExtraLongCurved,
168    ShortUnknown3,
169    PeaksSquared,
170    ShortUnknown5,
171    Peaks,
172    PeaksRounded,
173    PeaksLongBottom,
174    NormalLongBottom,
175    NormalShort,
176    NormalExtraLong,
177    PartingLong,
178    PartingMiddleLong,
179    PartingSquared,
180    LongRounded,
181    PartingLongBottom,
182    PartingShort,
183    PartingFrontPeaks,
184    NormalUnknown1,
185    PeaksSide,
186    PartingPeaks,
187    PeaksTop,
188    DreadLocks,
189    Short,
190    ShortUnknown4,
191    Afro,
192    Military,
193    NoneTop,
194    ShortUnknown6,
195    None,
196    Caps,
197    Beanie,
198    LongUnknown1,
199    LongUnknown40,
200    LongUnknown38,
201    LongUnknown60,
202    LongUnknown16,
203    LongUnknown36,
204    LongUnknown56,
205    PartingFrontTwoLongBackPonyTails,
206    LongUnknown31,
207    LongUnknown20,
208    LongUnknown15,
209    LongUnknown52,
210    LongUnknown7,
211    LongUnknown23,
212    PartingExtraLongRounded,
213    LongUnknown3,
214    LongUnknown11,
215    LongUnknown12,
216    LongUnknown29,
217    LongUnknown27,
218    LongUnknown17,
219    LongUnknown39,
220    LongUnknown24,
221    LongUnknown25,
222    LongUnknown61,
223    LongUnknown2,
224    StrandsTwoShortSidedPonyTails,
225    TwoFrontStrandsLongBackPonyTail,
226    LongUnknown65,
227    LongUnknown63,
228    ShortFrontTwoBackPonyTails,
229    LongUnknown43,
230    LongUnknown47,
231    LongUnknown44,
232    LongUnknown53,
233    LongUnknown51,
234}
235
236/// Converts a raw byte value from the Mii data format into a [`HairType`].
237///
238/// Returns `Err(())` if the byte does not correspond to any known hair type.
239impl TryFrom<u8> for HairType {
240    type Error = ();
241    fn try_from(value: u8) -> Result<Self, Self::Error> {
242        match value {
243            0x21 => Ok(Self::NormalLong),
244            0x28 => Ok(Self::NormalMedium),
245            0x33 => Ok(Self::FrontLock),
246            0x2c => Ok(Self::PartingExtraLong),
247            0x27 => Ok(Self::MilitaryParting),
248            0x46 => Ok(Self::PartingExtraLongCurved),
249            0x2d => Ok(Self::ShortUnknown3),
250            0x31 => Ok(Self::PeaksSquared),
251            0x3b => Ok(Self::ShortUnknown5),
252            0x38 => Ok(Self::Peaks),
253            0x44 => Ok(Self::PeaksRounded),
254            0x1f => Ok(Self::PeaksLongBottom),
255            0x20 => Ok(Self::NormalLongBottom),
256            0x2f => Ok(Self::NormalShort),
257            0x25 => Ok(Self::NormalExtraLong),
258            0x30 => Ok(Self::PartingLong),
259            0x42 => Ok(Self::PartingMiddleLong),
260            0x34 => Ok(Self::PartingSquared),
261            0x3a => Ok(Self::LongRounded),
262            0x32 => Ok(Self::PartingLongBottom),
263            0x37 => Ok(Self::PartingShort),
264            0x40 => Ok(Self::PartingFrontPeaks),
265            0x3c => Ok(Self::NormalUnknown1),
266            0x3e => Ok(Self::PeaksSide),
267            0x2b => Ok(Self::PartingPeaks),
268            0x26 => Ok(Self::PeaksTop),
269            0x2a => Ok(Self::DreadLocks),
270            0x17 => Ok(Self::Short),
271            0x43 => Ok(Self::ShortUnknown4),
272            0x36 => Ok(Self::Afro),
273            0x24 => Ok(Self::Military),
274            0x29 => Ok(Self::NoneTop),
275            0x41 => Ok(Self::ShortUnknown6),
276            0x1e => Ok(Self::None),
277            0x39 => Ok(Self::Caps),
278            0x22 => Ok(Self::Beanie),
279            0x0c => Ok(Self::LongUnknown1),
280            0x0d => Ok(Self::LongUnknown40),
281            0x45 => Ok(Self::LongUnknown38),
282            0x1a => Ok(Self::LongUnknown60),
283            0x04 => Ok(Self::LongUnknown16),
284            0x19 => Ok(Self::LongUnknown36),
285            0x01 => Ok(Self::LongUnknown56),
286            0x13 => Ok(Self::PartingFrontTwoLongBackPonyTails),
287            0x05 => Ok(Self::LongUnknown31),
288            0x08 => Ok(Self::LongUnknown20),
289            0x1b => Ok(Self::LongUnknown15),
290            0x07 => Ok(Self::LongUnknown52),
291            0x0e => Ok(Self::LongUnknown7),
292            0x03 => Ok(Self::LongUnknown23),
293            0x16 => Ok(Self::PartingExtraLongRounded),
294            0x0a => Ok(Self::LongUnknown3),
295            0x06 => Ok(Self::LongUnknown11),
296            0x14 => Ok(Self::LongUnknown12),
297            0x0b => Ok(Self::LongUnknown29),
298            0x3f => Ok(Self::LongUnknown27),
299            0x11 => Ok(Self::LongUnknown17),
300            0x23 => Ok(Self::LongUnknown39),
301            0x15 => Ok(Self::LongUnknown24),
302            0x00 => Ok(Self::LongUnknown25),
303            0x3d => Ok(Self::LongUnknown61),
304            0x10 => Ok(Self::LongUnknown2),
305            0x2e => Ok(Self::StrandsTwoShortSidedPonyTails),
306            0x09 => Ok(Self::TwoFrontStrandsLongBackPonyTail),
307            0x12 => Ok(Self::LongUnknown65),
308            0x02 => Ok(Self::LongUnknown63),
309            0x1c => Ok(Self::ShortFrontTwoBackPonyTails),
310            0x35 => Ok(Self::LongUnknown43),
311            0x47 => Ok(Self::LongUnknown47),
312            0x18 => Ok(Self::LongUnknown44),
313            0x0f => Ok(Self::LongUnknown53),
314            0x1d => Ok(Self::LongUnknown51),
315            _ => Err(()),
316        }
317    }
318}
319
320/// Converts a [`HairType`] into its raw byte representation for the Mii data format.
321impl From<HairType> for u8 {
322    fn from(value: HairType) -> Self {
323        match value {
324            HairType::NormalLong => 0x21,
325            HairType::NormalMedium => 0x28,
326            HairType::FrontLock => 0x33,
327            HairType::PartingExtraLong => 0x2c,
328            HairType::MilitaryParting => 0x27,
329            HairType::PartingExtraLongCurved => 0x46,
330            HairType::ShortUnknown3 => 0x2d,
331            HairType::PeaksSquared => 0x31,
332            HairType::ShortUnknown5 => 0x3b,
333            HairType::Peaks => 0x38,
334            HairType::PeaksRounded => 0x44,
335            HairType::PeaksLongBottom => 0x1f,
336            HairType::NormalLongBottom => 0x20,
337            HairType::NormalShort => 0x2f,
338            HairType::NormalExtraLong => 0x25,
339            HairType::PartingLong => 0x30,
340            HairType::PartingMiddleLong => 0x42,
341            HairType::PartingSquared => 0x34,
342            HairType::LongRounded => 0x3a,
343            HairType::PartingLongBottom => 0x32,
344            HairType::PartingShort => 0x37,
345            HairType::PartingFrontPeaks => 0x40,
346            HairType::NormalUnknown1 => 0x3c,
347            HairType::PeaksSide => 0x3e,
348            HairType::PartingPeaks => 0x2b,
349            HairType::PeaksTop => 0x26,
350            HairType::DreadLocks => 0x2a,
351            HairType::Short => 0x17,
352            HairType::ShortUnknown4 => 0x43,
353            HairType::Afro => 0x36,
354            HairType::Military => 0x24,
355            HairType::NoneTop => 0x29,
356            HairType::ShortUnknown6 => 0x41,
357            HairType::None => 0x1e,
358            HairType::Caps => 0x39,
359            HairType::Beanie => 0x22,
360            HairType::LongUnknown1 => 0x0c,
361            HairType::LongUnknown40 => 0x0d,
362            HairType::LongUnknown38 => 0x45,
363            HairType::LongUnknown60 => 0x1a,
364            HairType::LongUnknown16 => 0x04,
365            HairType::LongUnknown36 => 0x19,
366            HairType::LongUnknown56 => 0x01,
367            HairType::PartingFrontTwoLongBackPonyTails => 0x13,
368            HairType::LongUnknown31 => 0x05,
369            HairType::LongUnknown20 => 0x08,
370            HairType::LongUnknown15 => 0x1b,
371            HairType::LongUnknown52 => 0x07,
372            HairType::LongUnknown7 => 0x0e,
373            HairType::LongUnknown23 => 0x03,
374            HairType::PartingExtraLongRounded => 0x16,
375            HairType::LongUnknown3 => 0x0a,
376            HairType::LongUnknown11 => 0x06,
377            HairType::LongUnknown12 => 0x14,
378            HairType::LongUnknown29 => 0x0b,
379            HairType::LongUnknown27 => 0x3f,
380            HairType::LongUnknown17 => 0x11,
381            HairType::LongUnknown39 => 0x23,
382            HairType::LongUnknown24 => 0x15,
383            HairType::LongUnknown25 => 0x00,
384            HairType::LongUnknown61 => 0x3d,
385            HairType::LongUnknown2 => 0x10,
386            HairType::StrandsTwoShortSidedPonyTails => 0x2e,
387            HairType::TwoFrontStrandsLongBackPonyTail => 0x09,
388            HairType::LongUnknown65 => 0x12,
389            HairType::LongUnknown63 => 0x02,
390            HairType::ShortFrontTwoBackPonyTails => 0x1c,
391            HairType::LongUnknown43 => 0x35,
392            HairType::LongUnknown47 => 0x47,
393            HairType::LongUnknown44 => 0x18,
394            HairType::LongUnknown53 => 0x0f,
395            HairType::LongUnknown51 => 0x1d,
396        }
397    }
398}