Ian Chamberlain
2 years ago
32 changed files with 12618 additions and 3562 deletions
@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
|
||||
[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 |
||||
ctru-sys = { path = "ctru-sys" } |
||||
|
@ -0,0 +1,46 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1 +1,2 @@
|
||||
pub mod mii_selector; |
||||
pub mod swkbd; |
||||
|
@ -0,0 +1,42 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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