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\n
Hello 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 264d0ef..f461822 100644
--- a/ctru-rs/src/lib.rs
+++ b/ctru-rs/src/lib.rs
@@ -76,6 +76,7 @@ pub mod applets;
pub mod console;
pub mod error;
pub mod gfx;
+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.