From 869d0ce44b6265531957f9b3601b7ebfde2a7e85 Mon Sep 17 00:00:00 2001 From: Philipp Joram Date: Mon, 7 Aug 2017 21:08:54 +0200 Subject: [PATCH] Add Mii selector applet (appletEd) --- libctru/include/3ds.h | 1 + libctru/include/3ds/applets/miiselector.h | 111 ++++++++++++++++++++++ libctru/source/applets/miiselector.c | 42 ++++++++ 3 files changed, 154 insertions(+) create mode 100644 libctru/include/3ds/applets/miiselector.h create mode 100644 libctru/source/applets/miiselector.c diff --git a/libctru/include/3ds.h b/libctru/include/3ds.h index eaa360e..055677e 100644 --- a/libctru/include/3ds.h +++ b/libctru/include/3ds.h @@ -76,6 +76,7 @@ extern "C" { #include <3ds/applets/swkbd.h> #include <3ds/applets/error.h> +#include <3ds/applets/miiselector.h> #include <3ds/sdmc.h> #include <3ds/romfs.h> diff --git a/libctru/include/3ds/applets/miiselector.h b/libctru/include/3ds/applets/miiselector.h new file mode 100644 index 0000000..e52c64f --- /dev/null +++ b/libctru/include/3ds/applets/miiselector.h @@ -0,0 +1,111 @@ +/** + * @file miiselector.h + * @brief Mii Selector Applet (appletEd). + */ + +#pragma once + +/// @cond INTERNAL +#define _assert_struct_size(s, size) \ + _Static_assert(sizeof(s) == (size), #s " is not of size " #size "!") +/// @endcond + +/// Magic value needed to launch the applet. +#define MIISELECTOR_MAGIC 0x13DE28CF + +/// Maximum length of title to be displayed at the top of the Mii selector applet +#define MIISELECTOR_TITLE_LEN 64 + +/// Number of Guest Miis available for selection +#define MIISELECTOR_GUESTMII_SLOTS 6 + +/// Maximum number of user Miis available for selection +#define MIISELECTOR_USERMII_SLOTS 100 + +/// Parameter structure passed to AppletEd +typedef struct +{ + char enable_cancel_button; ///< Enables canceling of selection if nonzero. + char enable_selecting_guests; ///< Makes Guets Miis selectable if nonzero. + char show_on_top_screen; ///< Shows applet on top screen if nonzero, + ///< otherwise show it on the bottom screen. + char _unk0x3[5]; ///< @private + u16 title[MIISELECTOR_TITLE_LEN]; ///< UTF16-LE string displayed at the top of the applet. If + ///< set to the empty string, a default title is displayed. + char _unk0x88[4]; ///< @private + char show_guest_page; ///< If nonzero, the applet shows a page with Guest + ///< Miis on launch. + char _unk0x8D[3]; ///< @private + u32 initial_index; ///< Index of the initially selected Mii. If + ///< @ref MiiSelectorConf.show_guest_page is + ///< set, this is the index of a Guest Mii, + ///< otherwise that of a user Mii. + char mii_guest_whitelist[MIISELECTOR_GUESTMII_SLOTS]; ///< Each byte set to a nonzero value + ///< enables its corresponding Guest + ///< Mii to be enabled for selection. + char mii_whitelist[MIISELECTOR_USERMII_SLOTS]; ///< Each byte set to a nonzero value enables + ///< its corresponding user Mii to be enabled + ///< for selection. + u16 _unk0xFE; ///< @private + u32 magic; ///< Will be set to @ref MIISELECTOR_MAGIC before launching the + ///< applet. +} MiiSelectorConf; + +/// @private +_assert_struct_size(MiiSelectorConf, 0x104); + +/// Size of the data describing a single Mii +#define MIISELECTOR_MIIDATA_SIZE 0x5c + +/// Maximum length of the localized name of a Guest Mii +#define MIISELECTOR_GUESTMII_NAME_LEN 12 + +/// Structure written by AppletEd +typedef struct +{ + u32 no_mii_selected; ///< 0 if a Mii was selected, 1 if the selection was + ///< canceled. + u32 guest_mii_was_selected; ///< 1 if a Guest Mii was selected, 0 otherwise. + u32 guest_mii_index; ///< Index of the selected Guest Mii, + ///< 0xFFFFFFFF if no guest was selected. + char mii[MIISELECTOR_MIIDATA_SIZE]; ///< Data of selected Mii. + u16 _pad0x68; ///< @private + u16 checksum; ///< Checksum of the returned Mii data. + ///< Stored as a big-endian value; use + ///< @ref miiSelectorChecksumIsValid to + ///< verify. + u16 guest_mii_name[MIISELECTOR_GUESTMII_NAME_LEN]; ///< Localized name of a Guest Mii, + ///< if one was selected (UTF16-LE + ///< string). Zeroed otherwise. +} MiiSelectorReturn; + +/// @private +_assert_struct_size(MiiSelectorReturn, 0x84); + +#undef _assert_struct_size + +/** + * @brief Mii selector context + * + * Configure how to launch the applet in @ref MiiSelectorContext.config. After + * returning, the applet has written data to @ref MiiSelectorContext.ret. + */ +typedef union { + MiiSelectorConf config; ///< Configuration passed to applet + MiiSelectorReturn ret; ///< Data written by applet after return +} MiiSelectorContext; + +/** + * @brief Launch the Mii selector library applet + */ +Result miiSelectorLaunch(MiiSelectorContext *ctx); + +/** + * @brief Verifies that the Mii data returned from the applet matches its + * checksum + * + * @param returnbuffer Buffer filled by Mii selector applet + * + * @return `true` if `returnbuf->checksum` is the same as the one computed from `returnbuf` + */ +bool miiSelectorChecksumIsValid(MiiSelectorReturn *returnbuf); diff --git a/libctru/source/applets/miiselector.c b/libctru/source/applets/miiselector.c new file mode 100644 index 0000000..a1f56a4 --- /dev/null +++ b/libctru/source/applets/miiselector.c @@ -0,0 +1,42 @@ +#include <3ds/types.h> +#include <3ds/services/apt.h> + +#include <3ds/applets/miiselector.h> + +Result miiSelectorLaunch(MiiSelectorContext *ctx) +{ + ctx->config.magic = MIISELECTOR_MAGIC; + return aptLaunchLibraryApplet(APPID_APPLETED, &ctx->config, sizeof(MiiSelectorConf), 0); +} + +static u16 crc16_ccitt(void const *buf, size_t len, uint32_t starting_val) +{ + if(buf == NULL) { + return -1; + } + + u8 const *cbuf = buf; + u32 crc = starting_val; + + static const u16 POLY = 0x1021; + + for(size_t i = 0; i < len; i++) { + for(int bit = 7; bit >= 0; bit--) { + crc = ((crc << 1) | ((cbuf[i] >> bit) & 0x1)) ^ (crc & 0x8000 ? POLY : 0); + } + } + + for(int _ = 0; _ < 16; _++) { + crc = (crc << 1) ^ (crc & 0x8000 ? POLY : 0); + } + + return (u16)(crc & 0xffff); +} + +bool miiSelectorChecksumIsValid(MiiSelectorReturn *returnbuf) +{ + u16 computed = + crc16_ccitt(&returnbuf->mii, sizeof(returnbuf->mii) + sizeof(returnbuf->_pad0x68), 0x0000); + u16 chk_little_endian = __builtin_bswap16(returnbuf->checksum); + return computed == chk_little_endian; +}