From 3c9bd8f2febb0d94141a1e9db236a01538a25470 Mon Sep 17 00:00:00 2001 From: TechiePi Date: Fri, 21 Oct 2022 19:59:41 +0200 Subject: [PATCH] Implement MiiData parsing --- ctru-rs/src/applets/mii_selector.rs | 4 + ctru-rs/src/lib.rs | 1 + ctru-rs/src/mii.rs | 435 ++++++++++++++++++++++++++++ 3 files changed, 440 insertions(+) create mode 100644 ctru-rs/src/mii.rs diff --git a/ctru-rs/src/applets/mii_selector.rs b/ctru-rs/src/applets/mii_selector.rs index 634cd88..dc88bde 100644 --- a/ctru-rs/src/applets/mii_selector.rs +++ b/ctru-rs/src/applets/mii_selector.rs @@ -1,5 +1,6 @@ use bitflags::bitflags; use std::ffi::CString; +use crate::mii::MiiData; #[derive(Debug, Clone)] pub enum MiiConfigIndex { @@ -28,6 +29,7 @@ pub struct MiiSelector { pub struct MiiSelectorReturn { raw_return: Box, + pub mii_data: MiiData, pub is_mii_selected: bool, pub mii_type: MiiType, pub checksum: u16, @@ -158,12 +160,14 @@ impl MiiSelectorReturn { impl From> for MiiSelectorReturn { fn from(ret: Box) -> Self { let checksum = ret.checksum; + let raw_mii_data = ret.mii._bindgen_opaque_blob.clone(); let no_mii_selected = ret.no_mii_selected; let guest_mii_index_clone = ret.guest_mii_index; let mut guest_mii_name = ret.guest_mii_name; MiiSelectorReturn { raw_return: ret, + mii_data: raw_mii_data.into(), is_mii_selected: no_mii_selected == 0, mii_type: if guest_mii_index_clone != 0xFFFFFFFF { MiiType::Guest { diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 264d0ef..20282c6 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -78,6 +78,7 @@ pub mod error; pub mod gfx; pub mod prelude; pub mod services; +pub mod mii; cfg_if::cfg_if! { if #[cfg(all(feature = "romfs", romfs_exists))] { diff --git a/ctru-rs/src/mii.rs b/ctru-rs/src/mii.rs new file mode 100644 index 0000000..b211d76 --- /dev/null +++ b/ctru-rs/src/mii.rs @@ -0,0 +1,435 @@ +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum RegionLock { + NoLock, + Japan, + USA, + Europe, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Charset { + JapanUSAEurope, + China, + Korea, + Taiwan, +} + +#[derive(Clone, Debug)] +pub struct MiiDataOptions { + pub is_copying_allowed: bool, + pub is_profanity_flag_enabled: bool, + pub region_lock: RegionLock, + pub charset: Charset, +} + +#[derive(Clone, Debug)] +pub struct SelectorPosition { + pub page_index: u8, + pub slot_index: u8, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum OriginConsole { + ConsoleWii, + ConsoleDSi, + Console3DS, + ConsoleWiiUSwitch +} + +#[derive(Clone, Debug)] +pub struct ConsoleIdentity { + pub origin_console: OriginConsole, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum MiiSex { + Male, + Female, +} + +#[derive(Clone, Debug)] +pub struct Details { + pub sex: MiiSex, + pub birthday_month: u8, + pub birthday_day: u8, + pub shirt_color: u8, + pub is_favorite: bool, +} + +#[derive(Clone, Debug)] +pub struct FaceStyle { + pub is_sharing_enabled: bool, + pub shape: u8, + pub skin_color: u8, +} + +#[derive(Clone, Debug)] +pub struct FaceDetails { + pub wrinkles: u8, + pub makeup: u8, +} + +#[derive(Clone, Debug)] +pub struct HairDetails { + pub color: u8, + pub is_flipped: bool, +} + +#[derive(Clone, Debug)] +pub struct EyeDetails { + pub style: u8, + pub color: u8, + pub scale: u8, + pub y_scale: u8, + pub rotation: u8, + pub x_spacing: u8, + pub y_position: u8, +} + +#[derive(Clone, Debug)] +pub struct EyebrowDetails { + pub style: u8, + pub color: u8, + pub scale: u8, + pub y_scale: u8, + pub rotation: u8, + pub x_spacing: u8, + pub y_position: u8, +} + +#[derive(Clone, Debug)] +pub struct NoseDetails { + pub style: u8, + pub scale: u8, + pub y_position: u8, +} + +#[derive(Clone, Debug)] +pub struct MouthDetails { + pub style: u8, + pub color: u8, + pub scale: u8, + pub y_scale: u8, +} + +#[derive(Clone, Debug)] +pub struct MustacheDetails { + pub mouth_y_position: u8, + pub mustache_style: u8, +} + +#[derive(Clone, Debug)] +pub struct BeardDetails { + pub style: u8, + pub color: u8, + pub scale: u8, + pub y_position: u8, +} + +#[derive(Clone, Debug)] +pub struct GlassDetails { + pub style: u8, + pub color: u8, + pub scale: u8, + pub y_position: u8, +} + +#[derive(Clone, Debug)] +pub struct MoleDetails { + pub is_enabled: bool, + pub scale: u8, + pub x_position: u8, + pub y_position: u8, +} + +#[derive(Clone, Debug)] +pub struct MiiData { + pub mii_options: MiiDataOptions, + pub mii_selector_position: SelectorPosition, + pub mii_console_identity: ConsoleIdentity, + + pub system_id: [u8; 8], + pub mac_address: [u8; 6], + + pub mii_details: Details, + pub mii_name: String, + + pub height: u8, + pub width: u8, + + pub face_style: FaceStyle, + pub face_details: FaceDetails, + + pub hair_style: u8, + + pub hair_details: HairDetails, + pub eye_details: EyeDetails, + pub eyebrow_details: EyebrowDetails, + pub nose_details: NoseDetails, + pub mouth_details: MouthDetails, + pub mustache_details: MustacheDetails, + pub beard_details: BeardDetails, + pub glass_details: GlassDetails, + pub mole_details: MoleDetails, + + pub author_name: String, +} + +impl From<[u8; 92]> for MiiData { + fn from(raw_mii_data: [u8; 92]) -> Self { + // Source for the representation and what each thing means: https://www.3dbrew.org/wiki/Mii + let raw_options = vec_bit(raw_mii_data[0x1]); + let raw_position = vec_bit(raw_mii_data[0x2]); + let raw_device = vec_bit(raw_mii_data[0x3]); + let system_id = [raw_mii_data[0x4], raw_mii_data[0x5], raw_mii_data[0x6], raw_mii_data[0x7], raw_mii_data[0x8], raw_mii_data[0x9], raw_mii_data[0xA], raw_mii_data[0xB]]; + let creator_mac = [raw_mii_data[0x10], raw_mii_data[0x11], raw_mii_data[0x12], raw_mii_data[0x13], raw_mii_data[0x14], raw_mii_data[0x15]]; + let raw_details: [bool; 16] = get_and_concat_vec_bit(raw_mii_data.as_slice(), &[0x18, 0x19]).try_into().unwrap(); + let raw_utf16_name = &raw_mii_data.as_slice()[0x1A..0x2D]; + let height = raw_mii_data[0x2E]; + let width = raw_mii_data[0x2F]; + let raw_face_style = vec_bit(raw_mii_data[0x30]); + let raw_face_details = vec_bit(raw_mii_data[0x31]); + let hair_style = raw_mii_data[0x32]; + let raw_hair_details = vec_bit(raw_mii_data[0x33]); + let raw_eye_details: [bool; 32] = get_and_concat_vec_bit(raw_mii_data.as_slice(), &[0x34, 0x35, 0x36, 0x37]).try_into().unwrap(); + let raw_eyebrow_details: [bool; 32] = get_and_concat_vec_bit(raw_mii_data.as_slice(), &[0x38, 0x39, 0x3A, 0x3B]).try_into().unwrap(); + let raw_nose_details: [bool; 16] = get_and_concat_vec_bit(raw_mii_data.as_slice(), &[0x3C, 0x3D]).try_into().unwrap(); + let raw_mouth_details: [bool; 16] = get_and_concat_vec_bit(raw_mii_data.as_slice(), &[0x3E, 0x3F]).try_into().unwrap(); + let raw_mustache_details: [bool; 16] = get_and_concat_vec_bit(raw_mii_data.as_slice(), &[0x40, 0x41]).try_into().unwrap(); + let raw_beard_details: [bool; 16] = get_and_concat_vec_bit(raw_mii_data.as_slice(), &[0x42, 0x42]).try_into().unwrap(); + let raw_glass_details: [bool; 16] = get_and_concat_vec_bit(raw_mii_data.as_slice(), &[0x44, 0x45]).try_into().unwrap(); + let raw_mole_details: [bool; 16] = get_and_concat_vec_bit(raw_mii_data.as_slice(), &[0x46, 0x47]).try_into().unwrap(); + let raw_utf16_author = &raw_mii_data.as_slice()[0x48..0x5C]; + + let mii_name = utf16_byte_pairs_to_string(raw_utf16_name); + let author_name = utf16_byte_pairs_to_string(raw_utf16_author); + + let options = MiiDataOptions { + is_copying_allowed: *raw_options.first().unwrap(), + is_profanity_flag_enabled: *raw_options.get(1).unwrap(), + region_lock: { + let first_bit = raw_options[3]; + let second_bit = raw_options[2]; + if !first_bit && !second_bit { // 0b00 + RegionLock::NoLock + } else if !first_bit && second_bit { // 0b01 + RegionLock::Japan + } else if first_bit && !second_bit { // 0b10 + RegionLock::USA + } else { + RegionLock::Europe + } + }, + charset: { + let first_bit = raw_options[5]; + let second_bit = raw_options[4]; + if !first_bit && !second_bit { // 0b00 + Charset::JapanUSAEurope + } else if !first_bit && second_bit { // 0b01 + Charset::China + } else if first_bit && !second_bit { // 0b10 + Charset::Korea + } else { + Charset::Taiwan + } + }, + }; + + let position = SelectorPosition { + page_index: partial_vec_to_u8_with_reverse(&raw_position[0..3]), + //page_index: vec_bit_to_u8([false, false, false, false, raw_position[3], raw_position[2], raw_position[1], raw_position[0]]), + slot_index: partial_vec_to_u8_with_reverse(&raw_position[4..7]) + }; + + let device = ConsoleIdentity { + origin_console: { + let first_bit = raw_device[6]; + let second_bit = raw_device[5]; + let third_bit = raw_device[4]; + + if !first_bit && !second_bit && third_bit { + OriginConsole::ConsoleWii + } else if !first_bit && second_bit && !third_bit { + OriginConsole::ConsoleDSi + } else if !first_bit && second_bit && third_bit { + OriginConsole::Console3DS + } else { + OriginConsole::ConsoleWiiUSwitch + } + } + }; + + let details = Details { + sex: { + let first_bit = raw_details[0]; + if first_bit { + MiiSex::Female + } else { + MiiSex::Male + } + }, + birthday_month: partial_vec_to_u8_with_reverse(&raw_details[1..4]), + birthday_day: partial_vec_to_u8_with_reverse(&raw_details[5..9]), + shirt_color: partial_vec_to_u8_with_reverse(&raw_details[10..13]), + is_favorite: raw_details[14], + }; + + let face_style = FaceStyle { + is_sharing_enabled: !raw_face_style[1], + shape: partial_vec_to_u8_with_reverse(&raw_face_style[1..4]), + skin_color: partial_vec_to_u8_with_reverse(&raw_face_style[5..7]), + }; + + let face_details = FaceDetails { + wrinkles: partial_vec_to_u8_with_reverse(&raw_face_details[0..3]), + makeup: partial_vec_to_u8_with_reverse(&raw_face_details[4..7]), + }; + + let hair_details = HairDetails { + color: partial_vec_to_u8_with_reverse(&raw_hair_details[0..2]), + is_flipped: raw_hair_details[3], + }; + + let eye_details = EyeDetails { + style: partial_vec_to_u8_with_reverse(&raw_eye_details[0..5]), + color: partial_vec_to_u8_with_reverse(&raw_eye_details[6..8]), + scale: partial_vec_to_u8_with_reverse(&raw_eye_details[9..12]), + y_scale: partial_vec_to_u8_with_reverse(&raw_eye_details[13..15]), + rotation: partial_vec_to_u8_with_reverse(&raw_eye_details[16..20]), + x_spacing: partial_vec_to_u8_with_reverse(&raw_eye_details[21..24]), + y_position: partial_vec_to_u8_with_reverse(&raw_eye_details[25..29]), + }; + + let eyebrow_details = EyebrowDetails { + style: partial_vec_to_u8_with_reverse(&raw_eyebrow_details[0..4]), + color: partial_vec_to_u8_with_reverse(&raw_eyebrow_details[5..7]), + scale: partial_vec_to_u8_with_reverse(&raw_eyebrow_details[8..11]), + y_scale: partial_vec_to_u8_with_reverse(&raw_eyebrow_details[12..14]), + rotation: partial_vec_to_u8_with_reverse(&raw_eyebrow_details[16..19]), + x_spacing: partial_vec_to_u8_with_reverse(&raw_eyebrow_details[21..24]), + y_position: partial_vec_to_u8_with_reverse(&raw_eyebrow_details[25..29]), + }; + + let nose_details = NoseDetails { + style: partial_vec_to_u8_with_reverse(&raw_nose_details[0..4]), + scale: partial_vec_to_u8_with_reverse(&raw_nose_details[5..8]), + y_position: partial_vec_to_u8_with_reverse(&raw_nose_details[9..13]), + }; + + let mouth_details = MouthDetails { + style: partial_vec_to_u8_with_reverse(&raw_mouth_details[0..5]), + color: partial_vec_to_u8_with_reverse(&raw_mouth_details[6..8]), + scale: partial_vec_to_u8_with_reverse(&raw_mouth_details[9..12]), + y_scale: partial_vec_to_u8_with_reverse(&raw_mouth_details[13..15]), + }; + + let mustache_details = MustacheDetails { + mouth_y_position: partial_vec_to_u8_with_reverse(&raw_mustache_details[0..4]), + mustache_style: partial_vec_to_u8_with_reverse(&raw_mustache_details[5..7]), + }; + + let beard_details = BeardDetails { + style: partial_vec_to_u8_with_reverse(&raw_beard_details[0..2]), + color: partial_vec_to_u8_with_reverse(&raw_beard_details[3..5]), + scale: partial_vec_to_u8_with_reverse(&raw_beard_details[6..9]), + y_position: partial_vec_to_u8_with_reverse(&raw_beard_details[10..14]), + }; + + let glass_details = GlassDetails { + style: partial_vec_to_u8_with_reverse(&raw_glass_details[0..3]), + color: partial_vec_to_u8_with_reverse(&raw_glass_details[4..6]), + scale: partial_vec_to_u8_with_reverse(&raw_glass_details[7..10]), + y_position: partial_vec_to_u8_with_reverse(&raw_glass_details[11..15]), + }; + + let mole_details = MoleDetails { + is_enabled: raw_mole_details[0], + scale: partial_vec_to_u8_with_reverse(&raw_mole_details[1..4]), + x_position: partial_vec_to_u8_with_reverse(&raw_mole_details[5..9]), + y_position: partial_vec_to_u8_with_reverse(&raw_mole_details[10..14]), + }; + + MiiData { + mii_options: options, + mii_selector_position: position, + mii_console_identity: device, + system_id, + mac_address: creator_mac, + mii_details: details, + mii_name, + height, + width, + face_style, + face_details, + hair_style, + hair_details, + eye_details, + eyebrow_details, + nose_details, + mouth_details, + mustache_details, + beard_details, + glass_details, + mole_details, + author_name, + } + } +} + +// Methods to handle "_bits_", ``bitvec`` cannot compile to 32-bit targets, so I had to create a few +// helper methods + +/// Transforms a u8 into a [bool; 8] +fn vec_bit(data: u8) -> [bool; 8] { + (0..8) + .map(|i| (data & (1 << i)) != 0) + .collect::>() + .try_into() + .unwrap() +} + +/// Transforms a [bool; 8] into an u8 +fn vec_bit_to_u8(data: [bool; 8]) -> u8 { + let mut result: u8 = 0; + data + .map(|v| if v { 1_u8 } else { 0_u8 }) + .iter() + .for_each(|&bit| { + result <<= 1; + result ^= bit; + }); + result +} + +/// The reverse allows to write things on a more _humane_ way, but return a LE u8 +fn partial_vec_to_u8_with_reverse(data: &[bool]) -> u8 { + let leading_zeroes_to_add = 8 - data.len(); + let leading_zeroes = vec![false; leading_zeroes_to_add]; + let mut val = [data, leading_zeroes.as_slice()].concat(); + val.reverse(); + vec_bit_to_u8(val.try_into().unwrap()) +} + +/// UTF-16 Strings are give in pairs of bytes (u8), this converts them into an _actual_ string +fn utf16_byte_pairs_to_string(data: &[u8]) -> String { + let raw_utf16_composed = data + .chunks(2) + .collect::>() + .iter() + .map(|v| { + u16::from_le_bytes([*v.get(0).unwrap_or(&0), *v.get(1).unwrap_or(&0)]) + }) + .collect::>(); + + String::from_utf16(raw_utf16_composed.as_slice()).unwrap().replace("\0", "") +} + +/// Gets the values from the slice and concatenates them +fn get_and_concat_vec_bit(data: &[u8], get_values: &[usize]) -> Vec { + get_values.iter() + .map(|v| { + vec_bit(data[*v]) + }) + .collect::>() + .concat() +} \ No newline at end of file