diff --git a/ctru-rs/src/applets/mii_selector.rs b/ctru-rs/src/applets/mii_selector.rs new file mode 100644 index 0000000..a54729a --- /dev/null +++ b/ctru-rs/src/applets/mii_selector.rs @@ -0,0 +1,166 @@ +use std::ffi::{CString}; +use bitflags::bitflags; + +#[derive(Debug, Clone)] +pub enum MiiConfigIndex { + Index(u32), + All, +} + +#[derive(Debug, Clone)] +pub enum MiiType { + Guest { + index: u32, + name: String, + }, + User, +} + +bitflags! { + pub struct Options: u32 { + const MII_SELECTOR_CANCEL = ctru_sys::MIISELECTOR_CANCEL; + const MII_SELECTOR_GUESTS = ctru_sys::MIISELECTOR_GUESTS; + const MII_SELECTOR_TOP = ctru_sys::MIISELECTOR_TOP; + const MII_SELECTOR_GUEST_START = ctru_sys::MIISELECTOR_GUESTSTART; + } +} + +pub struct MiiSelector { + config: Box, +} + +pub struct MiiSelectorReturn { + raw_return: Box, + pub is_mii_selected: bool, + pub mii_type: MiiType, + pub checksum: u16, +} + +impl MiiSelector { + pub fn init() -> Self { + let mut config = Box::::default(); + unsafe { ctru_sys::miiSelectorInit(config.as_mut()); } + Self { config } + } + + 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("Cstring::new failed"); + unsafe { ctru_sys::miiSelectorSetTitle(self.config.as_mut(), c_text.as_ptr()); } + } + + pub fn set_options(&mut self, options: Options) { + unsafe { ctru_sys::miiSelectorSetOptions(self.config.as_mut(), options.bits) } + } + + pub fn whitelist_guest_mii(&mut self, mii_index: MiiConfigIndex) { + match mii_index { + MiiConfigIndex::Index(i) => { + unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), i) } + } + MiiConfigIndex::All => { + unsafe { ctru_sys::miiSelectorWhitelistGuestMii(self.config.as_mut(), ctru_sys::MIISELECTOR_GUESTMII_SLOTS) } + } + } + } + + pub fn blacklist_guest_mii(&mut self, mii_index: MiiConfigIndex) { + match mii_index { + MiiConfigIndex::Index(i) => { + unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), i) } + } + MiiConfigIndex::All => { + unsafe { ctru_sys::miiSelectorBlacklistGuestMii(self.config.as_mut(), ctru_sys::MIISELECTOR_GUESTMII_SLOTS) } + } + } + } + + pub fn whitelist_user_mii(&mut self, mii_index: MiiConfigIndex) { + match mii_index { + MiiConfigIndex::Index(i) => { + unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), i) } + } + MiiConfigIndex::All => { + unsafe { ctru_sys::miiSelectorWhitelistUserMii(self.config.as_mut(), ctru_sys::MIISELECTOR_USERMII_SLOTS) } + } + } + } + + pub fn blacklist_user_mii(&mut self, mii_index: MiiConfigIndex) { + match mii_index { + MiiConfigIndex::Index(i) => { + unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), i) } + } + MiiConfigIndex::All => { + unsafe { ctru_sys::miiSelectorBlacklistUserMii(self.config.as_mut(), ctru_sys::MIISELECTOR_USERMII_SLOTS) } + } + } + } + + // This function is static inline in libctru + // https://github.com/devkitPro/libctru/blob/af5321c78ee5c72a55b526fd2ed0d95ca1c05af9/libctru/include/3ds/applets/miiselector.h#L155 + pub fn set_initial_index(&mut self, index: u32) { + self.config.initial_index = index + } + + pub fn launch(&mut self) -> MiiSelectorReturn { + let mut return_val = Box::::default(); + unsafe { + ctru_sys::miiSelectorLaunch(self.config.as_mut(), return_val.as_mut()) + } + + return_val.into() + } +} + +impl MiiSelectorReturn { + pub fn name(&self) -> String { + let mut tmp = [0u8; 36]; + unsafe { ctru_sys::miiSelectorReturnGetName(self.raw_return.as_ref(), tmp.as_mut_ptr(), 36) } + + let len = unsafe { libc::strlen(tmp.as_ptr()) }; + let utf8 = unsafe { std::str::from_utf8_unchecked(&tmp[..len]) }; + + utf8.to_owned() + } + + pub fn author(&self) -> String { + let mut tmp = [0u8; 30]; + unsafe { ctru_sys::miiSelectorReturnGetName(self.raw_return.as_ref(), tmp.as_mut_ptr(), 30) } + let utf8 = unsafe { std::str::from_utf8_unchecked(&tmp) }; + + utf8.to_owned() + } + + pub fn valid_checksum(&self) -> bool { + unsafe { ctru_sys::miiSelectorChecksumIsValid(self.raw_return.as_ref()) } + } +} + +impl From> for MiiSelectorReturn { + fn from(ret: Box) -> Self { + let checksum = ret.checksum; + 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, + is_mii_selected: no_mii_selected == 0, + mii_type: if guest_mii_index_clone != 0xFFFFFFFF { + MiiType::Guest { + index: guest_mii_index_clone, + name: { + let utf16_be = &mut guest_mii_name; + utf16_be.reverse(); + String::from_utf16(utf16_be.as_slice()).unwrap() + }, + } + } else { + MiiType::User + }, + checksum, + } + } +} \ No newline at end of file diff --git a/ctru-rs/src/applets/mod.rs b/ctru-rs/src/applets/mod.rs index 73469a3..47eec73 100644 --- a/ctru-rs/src/applets/mod.rs +++ b/ctru-rs/src/applets/mod.rs @@ -1 +1,2 @@ pub mod swkbd; +pub mod mii_selector; \ No newline at end of file