diff --git a/ctru-rs/build.rs b/ctru-rs/build.rs index ecdef33..f65d194 100644 --- a/ctru-rs/build.rs +++ b/ctru-rs/build.rs @@ -28,5 +28,5 @@ fn main() { println!("cargo:rustc-cfg=romfs_exists"); } - println!("cargo:rerun-if-changed={}", manifest_dir); + println!("cargo:rerun-if-changed={manifest_dir}"); } diff --git a/ctru-rs/examples/file-explorer.rs b/ctru-rs/examples/file-explorer.rs index ff6296f..1cff988 100644 --- a/ctru-rs/examples/file-explorer.rs +++ b/ctru-rs/examples/file-explorer.rs @@ -96,7 +96,7 @@ impl<'a> FileExplorer<'a> { } } Err(e) => { - println!("Failed to read {}: {}", self.path.display(), e) + println!("Failed to read {}: {e}", self.path.display()) } }; @@ -110,7 +110,7 @@ impl<'a> FileExplorer<'a> { for (i, entry) in dir_listing.enumerate() { match entry { Ok(entry) => { - println!("{:2} - {}", i, entry.file_name().to_string_lossy()); + println!("{i:2} - {}", entry.file_name().to_string_lossy()); self.entries.push(entry); if (i + 1) % 20 == 0 { @@ -118,7 +118,7 @@ impl<'a> FileExplorer<'a> { } } Err(e) => { - println!("{} - Error: {}", i, e); + println!("{i} - Error: {e}"); } } } @@ -132,7 +132,7 @@ impl<'a> FileExplorer<'a> { println!("{0:->80}", ""); } Err(err) => { - println!("Error reading file: {}", err); + println!("Error reading file: {err}"); } } } @@ -175,7 +175,7 @@ impl<'a> FileExplorer<'a> { unreachable!() } Err(e) => { - panic!("Error: {:?}", e) + panic!("Error: {e:?}") } } } @@ -184,7 +184,7 @@ impl<'a> FileExplorer<'a> { let next_path_index: usize = match next_path_index.parse() { Ok(index) => index, Err(e) => { - println!("Number parsing error: {}", e); + println!("Number parsing error: {e}"); return; } }; @@ -205,7 +205,7 @@ impl<'a> FileExplorer<'a> { fn set_exact_path(&mut self, new_path_str: String) { let new_path = Path::new(&new_path_str); if !new_path.is_dir() { - println!("Not a directory: {}", new_path_str); + println!("Not a directory: {new_path_str}"); return; } diff --git a/ctru-rs/examples/hashmaps.rs b/ctru-rs/examples/hashmaps.rs index 3218dc5..694752c 100644 --- a/ctru-rs/examples/hashmaps.rs +++ b/ctru-rs/examples/hashmaps.rs @@ -17,7 +17,7 @@ fn main() { map.insert("Another key?", 543); map.remove("A Key!"); - println!("{:#?}", map); + println!("{map:#?}"); while apt.main_loop() { gfx.flush_buffers(); diff --git a/ctru-rs/examples/mii-selector.rs b/ctru-rs/examples/mii-selector.rs new file mode 100644 index 0000000..975fda0 --- /dev/null +++ b/ctru-rs/examples/mii-selector.rs @@ -0,0 +1,43 @@ +use ctru::applets::mii_selector::MiiSelector; +use ctru::prelude::*; + +fn main() { + ctru::init(); + + let gfx = Gfx::init().expect("Couldn't obtain GFX controller"); + let hid = Hid::init().expect("Couldn't obtain HID controller"); + let apt = Apt::init().expect("Couldn't obtain APT controller"); + let _console = Console::init(gfx.top_screen.borrow_mut()); + + let mut mii_selector = MiiSelector::init(); + mii_selector.set_initial_index(3); + mii_selector.blacklist_user_mii(0.into()); + mii_selector.set_title("Great Mii Selector!"); + + let result = mii_selector.launch().unwrap(); + + println!("Is Mii selected?: {:?}", result.is_mii_selected); + println!("Mii type: {:?}", result.mii_type); + println!("Name: {:?}", result.mii_data.name); + println!("Author: {:?}", result.mii_data.author_name); + println!( + "Does the Mii have moles?: {:?}", + result.mii_data.mole_details.is_enabled + ); + + // Main loop + while apt.main_loop() { + //Scan all the inputs. This should be done once for each frame + hid.scan_input(); + + if hid.keys_down().contains(KeyPad::KEY_START) { + break; + } + // Flush and swap framebuffers + gfx.flush_buffers(); + gfx.swap_buffers(); + + //Wait for VBlank + gfx.wait_for_vblank(); + } +} diff --git a/ctru-rs/examples/network-sockets.rs b/ctru-rs/examples/network-sockets.rs index a0b64d0..89a1212 100644 --- a/ctru-rs/examples/network-sockets.rs +++ b/ctru-rs/examples/network-sockets.rs @@ -25,19 +25,19 @@ fn main() { match server.accept() { Ok((mut stream, socket_addr)) => { - println!("Got connection from {}", socket_addr); + println!("Got connection from {socket_addr}"); let mut buf = [0u8; 4096]; match stream.read(&mut buf) { Ok(_) => { let req_str = String::from_utf8_lossy(&buf); - println!("{}", req_str); + println!("{req_str}"); } Err(e) => { if e.kind() == io::ErrorKind::WouldBlock { println!("Note: Reading the connection returned ErrorKind::WouldBlock.") } else { - println!("Unable to read stream: {}", e) + println!("Unable to read stream: {e}") } } } @@ -45,7 +45,7 @@ fn main() { let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\nHello world\r\n"; if let Err(e) = stream.write(response) { - println!("Error writing http response: {}", e); + println!("Error writing http response: {e}"); } stream.shutdown(Shutdown::Both).unwrap(); @@ -53,7 +53,7 @@ fn main() { Err(e) => match e.kind() { std::io::ErrorKind::WouldBlock => {} _ => { - println!("Error accepting connection: {}", e); + println!("Error accepting connection: {e}"); std::thread::sleep(Duration::from_secs(2)); } }, diff --git a/ctru-rs/examples/software-keyboard.rs b/ctru-rs/examples/software-keyboard.rs index b830811..888a157 100644 --- a/ctru-rs/examples/software-keyboard.rs +++ b/ctru-rs/examples/software-keyboard.rs @@ -29,7 +29,7 @@ fn main() { // Raise the software keyboard. You can perform different actions depending on which // software button the user pressed match keyboard.get_utf8(&mut text) { - Ok(Button::Right) => println!("You entered: {}", text), + Ok(Button::Right) => println!("You entered: {text}"), Ok(Button::Left) => println!("Cancelled"), Ok(Button::Middle) => println!("How did you even press this?"), Err(_) => println!("Oh noes, an error happened!"), diff --git a/ctru-rs/examples/time-rtc.rs b/ctru-rs/examples/time-rtc.rs index 1554a21..219abda 100644 --- a/ctru-rs/examples/time-rtc.rs +++ b/ctru-rs/examples/time-rtc.rs @@ -33,8 +33,8 @@ fn main() { let day = cur_time.day(); let year = cur_time.year(); - println!("\x1b[1;1H{:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds); - println!("{} {} {} {}", weekday, month, day, year); + println!("\x1b[1;1H{hours:0>2}:{minutes:0>2}:{seconds:0>2}"); + println!("{weekday} {month} {day} {year}"); // Flush and swap framebuffers gfx.flush_buffers(); diff --git a/ctru-rs/src/applets/mii_selector.rs b/ctru-rs/src/applets/mii_selector.rs new file mode 100644 index 0000000..d324055 --- /dev/null +++ b/ctru-rs/src/applets/mii_selector.rs @@ -0,0 +1,186 @@ +//! Mii Selector applet +//! +//! This module contains the methods to launch the Mii Selector. + +use crate::mii::MiiData; +use bitflags::bitflags; +use std::ffi::CString; + +/// Index of a Mii used to configure some parameters of the Mii Selector +/// Can be either a single index, or _all_ Miis +#[derive(Debug, Clone)] +pub enum MiiConfigIndex { + Index(u32), + All, +} + +/// The type of a Mii with their respective data +#[derive(Debug, Clone)] +pub enum MiiType { + Guest { index: u32, name: String }, + User, +} + +bitflags! { + /// Options for the Mii Selector + pub struct Options: u32 { + /// Show the cancel button + const MII_SELECTOR_CANCEL = ctru_sys::MIISELECTOR_CANCEL; + /// Make guest Miis selectable + const MII_SELECTOR_GUESTS = ctru_sys::MIISELECTOR_GUESTS; + /// Show on the top screen + const MII_SELECTOR_TOP = ctru_sys::MIISELECTOR_TOP; + /// Start on the guest's page + const MII_SELECTOR_GUEST_START = ctru_sys::MIISELECTOR_GUESTSTART; + } +} + +/// An instance of the Mii Selector +/// +/// # Example +/// ``` +/// use ctru::applets::mii_selector::MiiSelector; +/// +/// let mut mii_selector = MiiSelector::init(); +/// mii_selector.set_title("Example Mii selector"); +/// +/// let result = mii_selector.launch().unwrap(); +/// ``` +#[derive(Clone, Debug)] +pub struct MiiSelector { + config: Box, +} + +/// Return value from a MiiSelector's launch +#[non_exhaustive] +#[derive(Clone, Debug)] +pub struct MiiSelectorReturn { + pub mii_data: MiiData, + pub is_mii_selected: bool, + pub mii_type: MiiType, +} + +/// Error type for the Mii selector +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MiiLaunchError { + InvalidChecksum, +} + +impl MiiSelector { + /// Initializes a Mii Selector + pub fn init() -> Self { + let mut config = Box::::default(); + unsafe { + ctru_sys::miiSelectorInit(config.as_mut()); + } + Self { config } + } + + /// Set the title of the Mii Selector. + /// + /// This function would panic if the given ``&str`` contains NUL bytes. + pub fn set_title(&mut self, text: &str) { + // This can only fail if the text contains NUL bytes in the string... which seems + // unlikely and is documented + let c_text = CString::new(text).expect("Failed to convert the title text into a CString"); + unsafe { + ctru_sys::miiSelectorSetTitle(self.config.as_mut(), c_text.as_ptr()); + } + } + + /// Set the options of the Mii Selector + pub fn set_options(&mut self, options: Options) { + unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits) } + } + + /// Whitelist a guest Mii + pub fn whitelist_guest_mii(&mut self, mii_index: MiiConfigIndex) { + let index = match mii_index { + MiiConfigIndex::Index(i) => i, + MiiConfigIndex::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS, + }; + + unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), index) } + } + + /// Blacklist a guest Mii + pub fn blacklist_guest_mii(&mut self, mii_index: MiiConfigIndex) { + let index = match mii_index { + MiiConfigIndex::Index(i) => i, + MiiConfigIndex::All => ctru_sys::MIISELECTOR_GUESTMII_SLOTS, + }; + + unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), index) } + } + + /// Whitelist a user Mii + pub fn whitelist_user_mii(&mut self, mii_index: MiiConfigIndex) { + let index = match mii_index { + MiiConfigIndex::Index(i) => i, + MiiConfigIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS, + }; + + unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), index) } + } + + /// Blacklist a user Mii + pub fn blacklist_user_mii(&mut self, mii_index: MiiConfigIndex) { + let index = match mii_index { + MiiConfigIndex::Index(i) => i, + MiiConfigIndex::All => ctru_sys::MIISELECTOR_USERMII_SLOTS, + }; + + unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), index) } + } + + /// Set where the cursor will be. + /// If there's no Mii at that index, the cursor will start at the Mii with the index 0 + pub fn set_initial_index(&mut self, index: u32) { + // This function is static inline in libctru + // https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155 + self.config.initial_index = index + } + + /// Launch the Mii Selector. + /// Returns an error when the checksum of the Mii is invalid. + pub fn launch(&mut self) -> Result { + let mut return_val = Box::::default(); + unsafe { ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) } + + if unsafe { ctru_sys::miiSelectorChecksumIsValid(return_val.as_mut()) } { + Ok((*return_val).into()) + } else { + Err(MiiLaunchError::InvalidChecksum) + } + } +} + +impl From for MiiSelectorReturn { + fn from(ret: ctru_sys::MiiSelectorReturn) -> Self { + let raw_mii_data = ret.mii; + let mut guest_mii_name = ret.guest_mii_name; + + MiiSelectorReturn { + mii_data: raw_mii_data.into(), + is_mii_selected: ret.no_mii_selected == 0, + mii_type: if ret.guest_mii_index != 0xFFFFFFFF { + MiiType::Guest { + index: ret.guest_mii_index, + name: { + let utf16_be = &mut guest_mii_name; + utf16_be.reverse(); + String::from_utf16(utf16_be.as_slice()).unwrap() + }, + } + } else { + MiiType::User + }, + } + } +} + +impl From for MiiConfigIndex { + fn from(v: u32) -> Self { + Self::Index(v) + } +} diff --git a/ctru-rs/src/applets/mod.rs b/ctru-rs/src/applets/mod.rs index 73469a3..f5813ad 100644 --- a/ctru-rs/src/applets/mod.rs +++ b/ctru-rs/src/applets/mod.rs @@ -1 +1,2 @@ +pub mod mii_selector; pub mod swkbd; diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 650ab7a..5054ada 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -87,7 +87,7 @@ impl fmt::Debug for Error { match self { &Self::Os(err) => f .debug_struct("Error") - .field("raw", &format_args!("{:#08X}", err)) + .field("raw", &format_args!("{err:#08X}")) .field("description", &R_DESCRIPTION(err)) .field("module", &R_MODULE(err)) .field("summary", &R_SUMMARY(err)) @@ -106,8 +106,8 @@ impl fmt::Debug for Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &Self::Os(err) => write!(f, "libctru result code: 0x{:08X}", err), - Self::Libc(err) => write!(f, "{}", err), + &Self::Os(err) => write!(f, "libctru result code: 0x{err:08X}"), + Self::Libc(err) => write!(f, "{err}"), Self::ServiceAlreadyActive => write!(f, "Service already active"), Self::OutputAlreadyRedirected => { write!(f, "output streams are already redirected to 3dslink") diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 35fdc0c..8439531 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -78,6 +78,7 @@ pub mod console; pub mod error; pub mod gfx; pub mod linear; +pub mod mii; pub mod prelude; pub mod services; diff --git a/ctru-rs/src/mii.rs b/ctru-rs/src/mii.rs new file mode 100644 index 0000000..17cb9bf --- /dev/null +++ b/ctru-rs/src/mii.rs @@ -0,0 +1,466 @@ +//! Mii Data +//! +//! This module contains the structs that represent all the data of a Mii. +//! This data is given by the [``MiiSelector``](crate::applets::mii_selector::MiiSelector) + +/// Represents the region lock of the console +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum RegionLock { + None, + Japan, + USA, + Europe, +} + +/// Represent the charset of the console +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Charset { + JapanUSAEurope, + China, + Korea, + Taiwan, +} + +/// Represents the options of the Mii +#[derive(Copy, Clone, Debug)] +pub struct MiiDataOptions { + pub is_copying_allowed: bool, + pub is_profanity_flag_enabled: bool, + pub region_lock: RegionLock, + pub charset: Charset, +} + +/// Represents the position that the Mii has on the selector +#[derive(Copy, Clone, Debug)] +pub struct SelectorPosition { + pub page_index: u8, + pub slot_index: u8, +} + +/// Represents the kind of origin console +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum OriginConsole { + Wii, + DSi, + /// Both New 3DS and Old 3DS + N3DS, + WiiUSwitch, +} + +/// Represents the identity of the origin console +#[derive(Copy, Clone, Debug)] +pub struct ConsoleIdentity { + pub origin_console: OriginConsole, +} + +/// Represents the sex of the Mii +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MiiSex { + Male, + Female, +} + +/// Represents the details of the Mii +#[derive(Copy, Clone, Debug)] +pub struct Details { + pub sex: MiiSex, + pub birthday_month: u8, + pub birthday_day: u8, + pub shirt_color: u8, + pub is_favorite: bool, +} + +/// Represents the face style of the Mii +#[derive(Copy, Clone, Debug)] +pub struct FaceStyle { + pub is_sharing_enabled: bool, + pub shape: u8, + pub skin_color: u8, +} + +/// Represents the face details of the Mii +#[derive(Copy, Clone, Debug)] +pub struct FaceDetails { + pub style: FaceStyle, + pub wrinkles: u8, + pub makeup: u8, +} + +/// Represents the hair details of the Mii +#[derive(Copy, Clone, Debug)] +pub struct HairDetails { + pub style: u8, + pub color: u8, + pub is_flipped: bool, +} + +/// Represents the eye details of the Mii +#[derive(Copy, Clone, Debug)] +pub struct EyeDetails { + pub style: u8, + pub color: u8, + pub scale: u8, + pub y_scale: u8, + pub rotation: u8, + /// Spacing between the eyes + pub x_spacing: u8, + pub y_position: u8, +} + +/// Represents the eyebrow details of the Mii +#[derive(Copy, Clone, Debug)] +pub struct EyebrowDetails { + pub style: u8, + pub color: u8, + pub scale: u8, + pub y_scale: u8, + pub rotation: u8, + /// Spacing between the eyebrows + pub x_spacing: u8, + pub y_position: u8, +} + +/// Represents the details of the nose +#[derive(Copy, Clone, Debug)] +pub struct NoseDetails { + pub style: u8, + pub scale: u8, + pub y_position: u8, +} + +/// Represents the details of the mouth +#[derive(Copy, Clone, Debug)] +pub struct MouthDetails { + pub style: u8, + pub color: u8, + pub scale: u8, + pub y_scale: u8, +} + +/// Represents the details of the mustache +#[derive(Copy, Clone, Debug)] +pub struct MustacheDetails { + pub mouth_y_position: u8, + pub mustache_style: u8, +} + +/// Represents the details of the beard +#[derive(Copy, Clone, Debug)] +pub struct BeardDetails { + pub style: u8, + pub color: u8, + pub scale: u8, + pub y_position: u8, +} + +/// Represents the details of the glass +#[derive(Copy, Clone, Debug)] +pub struct GlassDetails { + pub style: u8, + pub color: u8, + pub scale: u8, + pub y_position: u8, +} + +/// Represents the details of the mole +#[derive(Copy, Clone, Debug)] +pub struct MoleDetails { + pub is_enabled: bool, + pub scale: u8, + pub x_position: u8, + pub y_position: u8, +} + +/// Represents all the data of a Mii +/// +/// Some values are not ordered _like_ the Mii Editor UI. The mapped values can be seen here: +/// +/// +/// This struct is returned by the [``MiiSelector``](crate::applets::mii_selector::MiiSelector) +#[derive(Clone, Debug)] +pub struct MiiData { + pub options: MiiDataOptions, + pub selector_position: SelectorPosition, + pub console_identity: ConsoleIdentity, + + /// Unique system ID, not dependant on the MAC address + pub system_id: [u8; 8], + pub mac_address: [u8; 6], + + pub details: Details, + pub name: String, + + pub height: u8, + pub width: u8, + + pub face_details: FaceDetails, + 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 for MiiData { + fn from(mii_data: ctru_sys::MiiData) -> Self { + let raw_mii_data = mii_data._bindgen_opaque_blob; + // 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 mac_address = [ + 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, &[0x18, 0x19]) + .try_into() + .unwrap(); + let raw_utf16_name = &raw_mii_data[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 raw_hair_details = vec_bit(raw_mii_data[0x33]); + let raw_eye_details: [bool; 32] = + get_and_concat_vec_bit(&raw_mii_data, &[0x34, 0x35, 0x36, 0x37]) + .try_into() + .unwrap(); + let raw_eyebrow_details: [bool; 32] = + get_and_concat_vec_bit(&raw_mii_data, &[0x38, 0x39, 0x3A, 0x3B]) + .try_into() + .unwrap(); + let raw_nose_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x3C, 0x3D]) + .try_into() + .unwrap(); + let raw_mouth_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x3E, 0x3F]) + .try_into() + .unwrap(); + let raw_mustache_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x40, 0x41]) + .try_into() + .unwrap(); + let raw_beard_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x42, 0x42]) + .try_into() + .unwrap(); + let raw_glass_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x44, 0x45]) + .try_into() + .unwrap(); + let raw_mole_details: [bool; 16] = get_and_concat_vec_bit(&raw_mii_data, &[0x46, 0x47]) + .try_into() + .unwrap(); + let raw_utf16_author = &raw_mii_data[0x48..0x5C]; + + let 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[0], + is_profanity_flag_enabled: raw_options[1], + region_lock: { + match (raw_options[3], raw_options[2]) { + (false, false) => RegionLock::None, + (false, true) => RegionLock::Japan, + (true, false) => RegionLock::USA, + (true, true) => RegionLock::Europe, + } + }, + charset: { + match (raw_options[5], raw_options[4]) { + (false, false) => Charset::JapanUSAEurope, + (false, true) => Charset::China, + (true, false) => Charset::Korea, + (true, true) => Charset::Taiwan, + } + }, + }; + + let selector_position = SelectorPosition { + page_index: partial_u8_bits_to_u8(&raw_position[0..=3]), + slot_index: partial_u8_bits_to_u8(&raw_position[4..=7]), + }; + + let console_identity = ConsoleIdentity { + origin_console: { + match (raw_device[6], raw_device[5], raw_device[4]) { + (false, false, true) => OriginConsole::Wii, + (false, true, false) => OriginConsole::DSi, + (false, true, true) => OriginConsole::N3DS, + _ => OriginConsole::WiiUSwitch, + } + }, + }; + + let details = Details { + sex: { + match raw_details[0] { + true => MiiSex::Female, + false => MiiSex::Male, + } + }, + birthday_month: partial_u8_bits_to_u8(&raw_details[1..=4]), + birthday_day: partial_u8_bits_to_u8(&raw_details[5..=9]), + shirt_color: partial_u8_bits_to_u8(&raw_details[10..=13]), + is_favorite: raw_details[14], + }; + + let face_details = FaceDetails { + style: FaceStyle { + is_sharing_enabled: !raw_face_style[0], + shape: partial_u8_bits_to_u8(&raw_face_style[1..=4]), + skin_color: partial_u8_bits_to_u8(&raw_face_style[5..=7]), + }, + wrinkles: partial_u8_bits_to_u8(&raw_face_details[0..=3]), + makeup: partial_u8_bits_to_u8(&raw_face_details[4..=7]), + }; + + let hair_details = HairDetails { + style: raw_mii_data[0x32], + color: partial_u8_bits_to_u8(&raw_hair_details[0..=2]), + is_flipped: raw_hair_details[3], + }; + + let eye_details = EyeDetails { + style: partial_u8_bits_to_u8(&raw_eye_details[0..=5]), + color: partial_u8_bits_to_u8(&raw_eye_details[6..=8]), + scale: partial_u8_bits_to_u8(&raw_eye_details[9..=12]), + y_scale: partial_u8_bits_to_u8(&raw_eye_details[13..=15]), + rotation: partial_u8_bits_to_u8(&raw_eye_details[16..=20]), + x_spacing: partial_u8_bits_to_u8(&raw_eye_details[21..=24]), + y_position: partial_u8_bits_to_u8(&raw_eye_details[25..=29]), + }; + + let eyebrow_details = EyebrowDetails { + style: partial_u8_bits_to_u8(&raw_eyebrow_details[0..=4]), + color: partial_u8_bits_to_u8(&raw_eyebrow_details[5..=7]), + scale: partial_u8_bits_to_u8(&raw_eyebrow_details[8..=11]), + // Bits are skipped here, following the 3dbrew wiki: + // https://www.3dbrew.org/wiki/Mii#Mii_format offset 0x38 + y_scale: partial_u8_bits_to_u8(&raw_eyebrow_details[12..=14]), + rotation: partial_u8_bits_to_u8(&raw_eyebrow_details[16..=19]), + x_spacing: partial_u8_bits_to_u8(&raw_eyebrow_details[21..=24]), + y_position: partial_u8_bits_to_u8(&raw_eyebrow_details[25..=29]), + }; + + let nose_details = NoseDetails { + style: partial_u8_bits_to_u8(&raw_nose_details[0..=4]), + scale: partial_u8_bits_to_u8(&raw_nose_details[5..=8]), + y_position: partial_u8_bits_to_u8(&raw_nose_details[9..=13]), + }; + + let mouth_details = MouthDetails { + style: partial_u8_bits_to_u8(&raw_mouth_details[0..=5]), + color: partial_u8_bits_to_u8(&raw_mouth_details[6..=8]), + scale: partial_u8_bits_to_u8(&raw_mouth_details[9..=12]), + y_scale: partial_u8_bits_to_u8(&raw_mouth_details[13..=15]), + }; + + let mustache_details = MustacheDetails { + mouth_y_position: partial_u8_bits_to_u8(&raw_mustache_details[0..=4]), + mustache_style: partial_u8_bits_to_u8(&raw_mustache_details[5..=7]), + }; + + let beard_details = BeardDetails { + style: partial_u8_bits_to_u8(&raw_beard_details[0..=2]), + color: partial_u8_bits_to_u8(&raw_beard_details[3..=5]), + scale: partial_u8_bits_to_u8(&raw_beard_details[6..=9]), + y_position: partial_u8_bits_to_u8(&raw_beard_details[10..=14]), + }; + + let glass_details = GlassDetails { + style: partial_u8_bits_to_u8(&raw_glass_details[0..=3]), + color: partial_u8_bits_to_u8(&raw_glass_details[4..=6]), + scale: partial_u8_bits_to_u8(&raw_glass_details[7..=10]), + y_position: partial_u8_bits_to_u8(&raw_glass_details[11..=15]), + }; + + let mole_details = MoleDetails { + is_enabled: raw_mole_details[0], + scale: partial_u8_bits_to_u8(&raw_mole_details[1..=4]), + x_position: partial_u8_bits_to_u8(&raw_mole_details[5..=9]), + y_position: partial_u8_bits_to_u8(&raw_mole_details[10..=14]), + }; + + MiiData { + options, + selector_position, + console_identity, + system_id, + mac_address, + details, + name, + height, + width, + face_details, + 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 { + data.into_iter() + .fold(0, |result, bit| (result << 1) ^ u8::from(bit)) +} + +/// Given a series of LE bits, they are filled until a full LE u8 is reached +fn partial_u8_bits_to_u8(data: &[bool]) -> u8 { + let leading_zeroes_to_add = 8 - data.len(); + let leading_zeroes = vec![false; leading_zeroes_to_add]; + + vec_bit_to_u8([data, &leading_zeroes].concat().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_exact(2) + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .collect::>(); + + String::from_utf16_lossy(raw_utf16_composed.as_slice()).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().flat_map(|v| vec_bit(data[*v])).collect() +} diff --git a/ctru-rs/src/services/fs.rs b/ctru-rs/src/services/fs.rs index d86cb55..64da9c3 100644 --- a/ctru-rs/src/services/fs.rs +++ b/ctru-rs/src/services/fs.rs @@ -669,7 +669,7 @@ impl<'a> DirEntry<'a> { /// The full path is created by joining the original path to `read_dir` /// with the filename of this entry. pub fn path(&self) -> PathBuf { - self.root.join(&self.file_name()) + self.root.join(self.file_name()) } /// Return the metadata for the file that this entry points at.