Ian Chamberlain
2 years ago
32 changed files with 12618 additions and 3562 deletions
@ -1,6 +1,6 @@ |
|||||||
[workspace] |
[workspace] |
||||||
members = ["ctru-rs", "ctru-sys"] |
members = ["ctru-rs", "ctru-sys", "ctru-sys/docstring-to-rustdoc"] |
||||||
|
|
||||||
[patch.'https://github.com/Meziu/ctru-rs'] |
[patch.'https://github.com/rust3ds/ctru-rs'] |
||||||
# Make sure all dependencies use the local ctru-sys package |
# Make sure all dependencies use the local ctru-sys package |
||||||
ctru-sys = { path = "ctru-sys" } |
ctru-sys = { path = "ctru-sys" } |
||||||
|
@ -0,0 +1,46 @@ |
|||||||
|
#![feature(allocator_api)] |
||||||
|
|
||||||
|
use ctru::linear::LinearAllocator; |
||||||
|
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 linear_space_before = LinearAllocator::free_space(); |
||||||
|
|
||||||
|
// Normal `Box` on the heap
|
||||||
|
let heap_box = Box::new(1492); |
||||||
|
// `Box` living on the linear memory sector
|
||||||
|
let linear_box: Box<i32, LinearAllocator> = Box::new_in(2022, LinearAllocator); |
||||||
|
|
||||||
|
println!("This value is from the heap: {heap_box}"); |
||||||
|
println!("This value is from the LINEAR memory: {linear_box}"); |
||||||
|
|
||||||
|
println!("\nLINEAR space free before allocation: {linear_space_before}"); |
||||||
|
println!( |
||||||
|
"LINEAR space free after allocation: {}", |
||||||
|
LinearAllocator::free_space() |
||||||
|
); |
||||||
|
|
||||||
|
println!("\x1b[29;16HPress Start to exit"); |
||||||
|
|
||||||
|
// 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(); |
||||||
|
} |
||||||
|
} |
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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<ctru_sys::MiiSelectorConf>, |
||||||
|
} |
||||||
|
|
||||||
|
/// 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::<ctru_sys::MiiSelectorConf>::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<MiiSelectorReturn, MiiLaunchError> { |
||||||
|
let mut return_val = Box::<ctru_sys::MiiSelectorReturn>::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<ctru_sys::MiiSelectorReturn> 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<u32> for MiiConfigIndex { |
||||||
|
fn from(v: u32) -> Self { |
||||||
|
Self::Index(v) |
||||||
|
} |
||||||
|
} |
@ -1 +1,2 @@ |
|||||||
|
pub mod mii_selector; |
||||||
pub mod swkbd; |
pub mod swkbd; |
||||||
|
@ -0,0 +1,42 @@ |
|||||||
|
//! Linear memory allocator
|
||||||
|
//!
|
||||||
|
//! Linear memory is a sector of the 3DS' RAM that binds virtual addresses exactly to the physical address.
|
||||||
|
//! As such, it is used for fast and safe memory sharing between services (and is especially needed for GPU and DSP).
|
||||||
|
//!
|
||||||
|
//! Resources:<br>
|
||||||
|
//! <https://github.com/devkitPro/libctru/blob/master/libctru/source/allocator/linear.cpp><br>
|
||||||
|
//! <https://www.3dbrew.org/wiki/Memory_layout>
|
||||||
|
|
||||||
|
use std::alloc::{AllocError, Allocator, Layout}; |
||||||
|
use std::ptr::NonNull; |
||||||
|
|
||||||
|
// Implementing an `std::alloc::Allocator` type is the best way to handle this case, since it gives
|
||||||
|
// us full control over the normal `std` implementations (like `Box`). The only issue is that this is another unstable feature to add.
|
||||||
|
// Sadly the linear memory allocator included in `libctru` doesn't implement `linearRealloc` at the time of these additions,
|
||||||
|
// but the default fallback of the `std` will take care of that for us.
|
||||||
|
|
||||||
|
/// [`std::alloc::Allocator`] struct for LINEAR memory
|
||||||
|
/// To use this struct the main crate must activate the `allocator_api` unstable feature.
|
||||||
|
pub struct LinearAllocator; |
||||||
|
|
||||||
|
impl LinearAllocator { |
||||||
|
/// Returns the amount of free space left in the LINEAR sector
|
||||||
|
pub fn free_space() -> u32 { |
||||||
|
unsafe { ctru_sys::linearSpaceFree() } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
unsafe impl Allocator for LinearAllocator { |
||||||
|
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> { |
||||||
|
let pointer = |
||||||
|
unsafe { ctru_sys::linearMemAlign(layout.size() as u32, layout.align() as u32) }; |
||||||
|
|
||||||
|
NonNull::new(pointer.cast()) |
||||||
|
.map(|ptr| NonNull::slice_from_raw_parts(ptr, layout.size())) |
||||||
|
.ok_or(AllocError) |
||||||
|
} |
||||||
|
|
||||||
|
unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) { |
||||||
|
ctru_sys::linearFree(ptr.as_ptr().cast()); |
||||||
|
} |
||||||
|
} |
@ -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:
|
||||||
|
/// <https://www.3dbrew.org/wiki/Mii#Mapped_Editor_.3C-.3E_Hex_values>
|
||||||
|
///
|
||||||
|
/// 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<ctru_sys::MiiData> 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::<Vec<bool>>() |
||||||
|
.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::<Vec<u16>>(); |
||||||
|
|
||||||
|
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<bool> { |
||||||
|
get_values.iter().flat_map(|v| vec_bit(data[*v])).collect() |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
[package] |
||||||
|
name = "docstring-to-rustdoc" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
doxygen-rs = { git = "https://github.com/Techie-Pi/doxygen-rs.git", version = "0.2.2", rev = "573f483ad63ab4662650961998d3ed093a703983" } |
@ -0,0 +1,31 @@ |
|||||||
|
//! This script transforms _some_ Boxygen comments to Rustdoc format
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//!
|
||||||
|
//! `cargo run --package docstring-to-rustdoc -- [location of the bindings.rs]`
|
||||||
|
//! Example: `cargo run --package docstring-to-rustdoc -- src/bindings.rs`
|
||||||
|
//!
|
||||||
|
//! # Transformations
|
||||||
|
//!
|
||||||
|
//! Check [doxygen-rs docs](https://techie-pi.github.io/doxygen-rs/doxygen_rs/)
|
||||||
|
|
||||||
|
use std::path::Path; |
||||||
|
use std::{env, fs, io}; |
||||||
|
|
||||||
|
fn main() -> io::Result<()> { |
||||||
|
let args: Vec<String> = env::args().collect(); |
||||||
|
|
||||||
|
let bindings_path = Path::new(args.get(1).expect("bindings.rs not provided in the args")); |
||||||
|
let bindings = fs::read_to_string(bindings_path)?; |
||||||
|
|
||||||
|
let parsed = doxygen_rs::transform_bindgen(bindings.as_str()); |
||||||
|
|
||||||
|
let old_bindings_path = bindings_path.to_str().unwrap().to_owned() + ".old"; |
||||||
|
|
||||||
|
// If something fails, the original bindings are available at ``bindings.rs.old``
|
||||||
|
fs::rename(bindings_path, &old_bindings_path)?; |
||||||
|
fs::write(bindings_path, parsed)?; |
||||||
|
fs::remove_file(&old_bindings_path)?; |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
@ -1,67 +0,0 @@ |
|||||||
//! This script transforms _some_ Boxygen comments to Rustdoc format
|
|
||||||
//!
|
|
||||||
//! # Usage
|
|
||||||
//!
|
|
||||||
//! `cargo run --bin docstring-to-rustdoc -- [location of the bindings.rs]`
|
|
||||||
//! Example: `cargo run --bin docstring-to-rustdoc -- src/bindings.rs`
|
|
||||||
//!
|
|
||||||
//! # Transformations
|
|
||||||
//!
|
|
||||||
//! The following are _completely_ removed, but _its contents are kept_:
|
|
||||||
//! * `@brief`
|
|
||||||
//! * `@ref`
|
|
||||||
//! * `@note`
|
|
||||||
//! * `@return`
|
|
||||||
//! * `@sa`
|
|
||||||
//! * `<`
|
|
||||||
//! * `[out]` and `[in]`
|
|
||||||
//!
|
|
||||||
//! The followings are _partially_ transformed to Rustdoc format:
|
|
||||||
//! * `@param`
|
|
||||||
|
|
||||||
use std::path::Path; |
|
||||||
use std::{env, fs, io}; |
|
||||||
|
|
||||||
fn main() -> io::Result<()> { |
|
||||||
let args: Vec<String> = env::args().collect(); |
|
||||||
|
|
||||||
let bindings_path = Path::new(args.get(1).expect("bindings.rs not provided in the args")); |
|
||||||
let bindings_string: String = fs::read_to_string(bindings_path)?; |
|
||||||
|
|
||||||
let parsed = bindings_string |
|
||||||
.lines() |
|
||||||
.map(|v| { |
|
||||||
// Only modify lines with the following structure: `` #[doc ... ] ``
|
|
||||||
if v.trim_start().starts_with("#[doc") && v.trim_end().ends_with(']') { |
|
||||||
v.replace("@brief", "") |
|
||||||
// Example: ``@param offset Offset of the RomFS...`` -> ``- offset Offset of the RomFS...``
|
|
||||||
// Will improve in the future
|
|
||||||
.replace("@param", "* ") |
|
||||||
.replace("@ref", "") |
|
||||||
.replace("@note", "") |
|
||||||
.replace("@return", "") |
|
||||||
.replace("@sa", "") |
|
||||||
.replace("< ", "") |
|
||||||
// Remove things like ``@param[out]``
|
|
||||||
.replace("[out]", "") |
|
||||||
.replace("[in]", "") |
|
||||||
// Trim start of the Rustdoc: ``...= " ...`` -> ``...= "...``
|
|
||||||
.replace("= \" ", "= \"") |
|
||||||
// Double pass because _most_ annotations are at the start
|
|
||||||
.replace("= \" ", "= \"") |
|
||||||
} else { |
|
||||||
String::from(v) |
|
||||||
} |
|
||||||
}) |
|
||||||
.map(|v| v + "\n") |
|
||||||
.collect::<String>(); |
|
||||||
|
|
||||||
let old_bindings_path = bindings_path.to_str().unwrap().to_owned() + ".old"; |
|
||||||
|
|
||||||
// If something fails, the original bindings are available at ``bindings.rs.old``
|
|
||||||
fs::rename(bindings_path, &old_bindings_path)?; |
|
||||||
fs::write(bindings_path, parsed)?; |
|
||||||
fs::remove_file(&old_bindings_path)?; |
|
||||||
|
|
||||||
Ok(()) |
|
||||||
} |
|
Loading…
Reference in new issue