Skip to content

Mii support from the firmware (2026-10-26, branch claude/mii-firmware-2026-10-26)

User priority: real Mii data, services and the Mii Maker applet from the user's own firmware (22.1.0, 238 NCAs; never committed). Decisions were taken without asking (the user was away): they are marked Decision.

1. What the firmware holds for Miis (napple build --list-firmware <zip> -k <prod.keys> [--filter mii] [--id <hex>], new)

Title id Name (NPDM / content) What it is Handling
0100000000000039 sdb (program) hosts mii:u, mii:e, miiimg (plus avm, pdm, pl:s). Its rodata holds the 6 default Miis (+0xa8a24, 54 u32 each) and the 3DS-to-common colour tables (+0xa8756) defaults and tables read from it into mii_core.c (verified against the data)
0100000000000802 MiiModel (data, 5.7 MB: NXTexture{Low,Mid}{Linear,SRGB}.dat, Shape{Mid,High}.dat) the heads' shapes and textures games render themselves already written by the build to sysdata/0100000000000802.bin; fs.c serves it to OpenDataStorageByDataId. MK8D mounts it as MiiResource: (verified, log below)
0100000000001009 miiEdit (program: exefs main, rtld, sdk; romfs 12.8 MB: Gfx/Layout/Mii/Ptcl/Shader/Yaml archives, IconAll.bntx, MiiEditN messages) the Mii Maker library applet (applet id 0x12). Uses appletAE, hid, hid:sys, audren/audout, nvdrv:a, vi:s, fsp-srv, mii:e, miiimg, acc:u1 ... napple build --firmware-program 0100000000001009 (new) builds it; boots (see 4)
0100000000001000 qlaunch, 1002 cabinet, 1007 playerSelect, 1012 starter programs that use mii:e / mii:u also contain icon/SubstituteUserIcon.jpg (256x256), the default profile picture picture extracted at build time to system/icon/ and served by acc
010000000000081c.. none Mii related

2. Phase A: data and services

  • mii_core.c/.h rewritten (portable C): the six real default Miis (BuildDefault 0..5: favourite colours 4,5,0,2,6,7; hair 68,55,33,24,14,12; genders M,M,M,F,F,F), colour tables, CRC-16/XMODEM, CharInfo <-> bit-packed CoreData (7 words + 10-char nickname) <-> StoreData (0x44, data CRC + device CRC), a persisted database (up to 100 Miis, file <save root>/system/mii/MiiDatabase.dat, header NFDB + CRC, atomic rename, damaged file rebuilt and reported once by IsBrokenDatabaseWithClearFlag), BuildRandom with full-range valid parts and fresh UUID v4 ids.
  • mii.c: mii:u (IsUpdated per session generation, IsFullDatabase, GetCount, Get/Get1/Get2/Get3, UpdateLatest/UpdateLatest1, BuildRandom/BuildDefault, FindIndex, GetIndex, SetInterfaceVersion, IsBrokenDatabaseWithClearFlag, Convert CoreData<->CharInfo) and mii:e (all of those plus Move, AddOrReplace, Delete, DestroyFile, DeleteFile, Format, Import, Export, Append). Same database for both.
  • acc: IProfile::GetImageSize/LoadImage serve the firmware's default avatar (build writes system/icon/SubstituteUserIcon.jpg; napple build --dump-fonts writes it next to the fonts).
  • Decision: the exact bit order inside CoreData words 5-6 and the device CRC recipe are reconstructed from memory (not verifiable without the console); they round-trip and are range-safe, but a title that unpacks CoreData itself could disagree. Flag for the first title that does. The StoreData CRC recipe and the file container are host-side.
  • Decision: UpdateLatest of a Mii found nowhere returns it unchanged (the old behaviour games ran with) rather than NotFound.
  • Decision: BuildDefault keeps fixed create ids per default (a saved choice finds the same Mii); BuildRandom is fresh.
  • Not done: BuildRandom's per-gender/age/race tables (not extracted), miiimg (Mii image generation service), Ver3StoreData Convert (cmd 23), profile pictures rendered from a Mii.
  • Tests: runtime/tests/test_svc_mii.c grew from 40 to 271 checks (defaults, round trips, CRC vector, persistence across a simulated restart, full database, damaged file, mii:u/mii:e IPC); portable (no Linux-only calls).

Evidence (macOS, this branch): MK8D 3.0.5 + DLC (--data-only --update-romfs in-place), headless 45 s: exit 0, fs: OpenDataStorageByDataId: data id 0100000000000802 ... sysdata/0100000000000802.bin (5773460 bytes), opened service mii:u, MiiDatabase.dat (84 bytes) written, 2213 frames, no FAIL/CRASH. Scripted 500:L+R:1.0,800:A...: title -> character preview -> a race with Mii-less AI racers (screenshots in the session; Mii faces on screen not confirmed). ACNH base (v0) 45 s: exit 0, 1187 frames, no Mii FAIL; ACNH 3.0.1 crashes in gfx_shader_register (gfx, not Mii; not investigated).

2b. One Mii database shared by all games

What is shared: the Mii database only (the console's system save data 8000000000000030 equivalent): up to 100 StoreData. Per-title saves stay per title. The MiiModel data and fonts are read-only system data. Where (macOS): ~/Library/Application Support/Napple/system/mii/MiiDatabase.dat (+ .bak, .lock), i.e. under the app-support root, not a per-title save root; NAPPLE_MII_DIR=<folder> overrides it (a Studio setting can set that env later). Interface: MiiStorage (mii_core.h) = read(file | previous copy) / atomic write / lock / unlock / stamp (observe) / destroy. The file provider is mii_db_set_path; other platforms call mii_db_set_storage. Safety: every operation is lock (flock on <file>.lock, plus an in-process mutex) -> refresh when the stamp (mtime, size, inode) changed -> mutate -> write temp (fsync) + rename -> unlock; the previous copy is kept as .bak; header has magic, version 1, count, CRC, generation. A missing or damaged file falls back to .bak, then to the seed (the player's Mii); the six defaults are code, so a game never sees an error. Tests (test_svc_mii, portable C, uses fork + flock, no Linux-only calls): two processes adding 20 Miis each lose none (41 total), a damaged file recovers from the backup, file and backup both damaged -> seed, reported once by IsBrokenDatabaseWithClearFlag. Sandbox check: the generated macOS apps are NOT sandboxed (studio/packaging/entitlements.plist has only hardened-runtime JIT/library-validation exceptions, no com.apple.security.app-sandbox), so they can read and write ~/Library/Application Support/Napple and an iCloud Drive folder. If an app were sandboxed it would need an App Group (com.apple.security.application-groups) or com.apple.security.files.user-selected.read-write plus a security-scoped bookmark. iCloud Drive on macOS (design, not wired): point NAPPLE_MII_DIR at ~/Library/Mobile Documents/com~apple~CloudDocs/Napple/mii; the file is small and replaced atomically, iCloud syncs it to the iPhone. Caveats: macOS may ask for iCloud Drive file access once; sync is whole-file last-writer-wins, so a conflicting copy (MiiDatabase 2.dat) can appear, so a merge-on-open by create id (newest wins) is the next step. iOS (design, not implemented): apps are sandboxed, so options are (1) an App Group container shared by all the generated apps (needs one signing team and the group in each profile; simplest, a provider that opens the group folder), (2) a user-selected shared folder (Files / iCloud Drive) kept through a security-scoped bookmark, read and written with NSFileCoordinator (the provider's lock = coordinated access, stamp = file version), (3) import/export of the NFDB file or single StoreData/CharInfo files through the share sheet. All three plug into MiiStorage.

3. Build additions

  • napple build --list-firmware, napple build --firmware-program <title id hex> --firmware <zip> -k <keys> -o <dir> (extracts exefs + NPDM + romfs of a firmware program, AOT rewrites it like a title, writes sysdata/fonts; Firmware::program_romfs, nca_src).

4. Phase B: the real Mii Maker (status: works on macOS)

napple build --firmware-program 0100000000001009 (or any napple build --firmware of a game, which now also writes it to <title>/applets/0100000000001009/<build>/, iOS builds included) gives a data directory the runtime boots like a title. Verified (macOS, headless scripted input, screenshots): Mii menu with the count from the shared database, Create New Mii, Start from Scratch (gender), the full part editor (face shape, hair, eyebrows, eyes, nose, mouth, ... with the live 3D Mii rendered from MiiModel 0802), the name prompt (swkbd answered by the host text input), Done: the new Mii (UTF-16 name, valid StoreData) is appended to the shared MiiDatabase.dat through mii:e, and a game (MK8D) reading mii:u afterwards lists both Miis (mii: list ... wrote 2). What it took, in order: 1. ChangeIntoForeground (applet message 1) activates the SDK's UI; applet_ae.c queues it after the first PopInData. 2. The applet's own statically linked NVN driver: napple build finds nvnBootstrapLoader by signature (fwprog.rs::find_nvn_bootstrap), the loader hooks it to the host's (manifest hooks), the proc-address getter is wrapped (NVX stubs, NvRm syncs signalled). 3. Services: appletAE (applet_ae.c), fatal:u, catch-all stubs (nfc:sys, nfp:sys, ldn, lp2p, ngct), hid:sys (ApplyNpadSystemCommonPolicy 303 / GetLastActiveNpad 306), miiimg (empty image DB), vi system/manager display, set/set:sys (key maps, GetMiiAuthorId), acc:u1, nvdrv Ioctl3 + ctrl-gpu characteristics, ICommonStateGetter 300, IDisplayController capture stubs, ISelfController 41/42. 4. The face-render crash and the "worker" patch were one bug: invalid StoreData. CharModel init failed with 0xd67e because the stored CRCs were wrong. The firmware's CRC is stored big-endian, the device CRC is seeded with the console's Mii author id (set:sys 90). With firmware-exact CRCs the worker crash vanished and the known-build patch table is gone (manifest patches is empty; the mechanism stays). 5. Input: MiiEdit reads the npad state from the SystemExt lifo, after hid:sys ApplyNpadSystemCommonPolicy; napple_hid_push_npad fills it too. 6. The swkbd answer for a firmware applet is UTF-16 (games keep UTF-8); the host's LibAppletArgs (common arguments) storage is skipped because MiiEdit's SDK pops only its own 0x100-byte argument ({u32 version <= 4, u32 mode < 6, ...}). Not fully verified: CoreData bit positions of words 5-6 (faces look right, eyes/brows slightly off in one build), MiiEdit's output storage format (only out0 is returned; the result code), and a real game triggering the editor (no available title requests library applet 0x12; the host path is tested with a stand-in child and the child contract was run by hand with the real caller storage layout: common args + 0x100 argument). Firmware applet host (parent side), applet.c libapplet_run_firmware: for applet id 0x12 the game's Start spawns napple-runtime <title dir>/applets/0100000000001009/<build>/ (its sysdata/system are links to the game's; NAPPLE_NO_FIRMWARE_APPLETS=1 skips building it), pushed storages become in<N>.bin, out0.bin becomes the applet's output, timeout NAPPLE_APPLET_TIMEOUT (600 s), any failure answers "cancelled". On by default (NAPPLE_FIRMWARE_APPLETS=0 disables). The child is a separate process (the runtime is one guest per process: module list, handle table, service singletons, one gfx device), with its own window. In-process host (iOS, and macOS with NAPPLE_APPLET_INPROC=1), done on macOS, verified headless and with a window. iOS cannot spawn a process and the Mii creator must stay inside the game app, so the runtime is built a second time as a library: make -C runtime applet-lib -> runtime/build-lib/libnapple-applet.dylib (-DNAPPLE_LIB, entry napple_applet_main(argc, argv), every global its own copy because it is a separate image; tools/build_ios.sh builds the iOS one beside the iOS runtime and napple build --target ios copies it into Frameworks/). applet.c libapplet_run_firmware dlopens it (NAPPLE_APPLET_LIB, else beside the executable, Frameworks/, ../Frameworks/), calls napple_applet_attach(layer, input) and runs napple_applet_main on a thread of the game's process, with the same in<N>.bin/out0.bin file protocol as the child process. Library mode (src/libmode.c): the guest ending (ExitProcess, runtime_fail, a crash report, frontend_quit, ExitProcessAndReturn) goes through GUEST_EXIT -> lib_guest_end, which ends only the calling thread and wakes napple_applet_main; frontend_run waits for that instead of pause()/[NSApp run]; the crash handlers of the second image chain to the host's for faults outside its own guest code; with a window the applet draws into the host's CAMetalLayer (frontend_metal_layer returns the attached one) and its own HID shared memory is fed from the host's napple_input by a mirror thread (napple_input_virtual_pad at 250 Hz); while the applet is on screen the game is told FocusState OutOfFocus (+ message 15, back to InFocus after) and its frames are completed but not shown (napple_gfx_set_hidden, a small addition in gfx/, so the two images do not alternate frames on the one layer). iOS packaging: ios::dylibize also packs game/applets/<id>/<build>/modules as Frameworks/applets.<id>.<build>.<module>.dylib and ios_dylib_path derives that name (host-tested). Verified on macOS: MK8D with NAPPLE_DEBUG_LAUNCH_APPLET=15 NAPPLE_APPLET_INPROC=1 (a debug aid that launches the Mii Maker from inside any title as the game would) brings the Mii Maker up inside the MK8D window, driven by the game's input, and returns its output storage to the host when it exits; headless the same with --headless. Not verified: on an iOS device (no device here; the iOS runtime and library build with tools/build_ios.sh, see the integrator file), the output-storage format a game expects beyond "32 bytes, 0 = cancelled". The database side: on iOS mii.c uses <App Group container>/Napple/mii (Info.plist key NappleAppGroup, entitlement com.apple.security.application-groups; ios_platform.m ios_shared_mii_dir), so all Napple game apps of one developer share one database; without a group it stays in the app's save root; iCloud sync/import/export as in section 2b. Debug aids: NAPPLE_DEBUG_TRAP, NAPPLE_DEBUG_LOG, NAPPLE_DEBUG_PATCH, NAPPLE_NO_MANIFEST_PATCHES, NAPPLE_CRASH_DUMP, NAPPLE_NVN_TRACE, NAPPLE_NVN_FAILTRACE, NAPPLE_DEBUG_THREAD_DELAY_MS, NAPPLE_NVN_SYNC_TIMEOUT_KEEP, NAPPLE_APPLET_MSGS, NAPPLE_HID_DUMP, NAPPLE_SWKBD_ANSWER. tools/dev/miiedit-activate-patch.* is obsolete. Note: a test run needs the shared fonts in <save-root>/system/fonts or MiiEdit draws no text.

5. Files

New: runtime/src/services/applet_ae.c, crates/napple-build/src/fwlist.rs, fwprog.rs. Changed: mii.c, mii_core.c/.h, account.c, applet.c (shared accessors, kind), ipc.c (catch-all stub, error trace), module.c (skew, debug trap), napple.h (skew), nvdrv.c, vi.c, services.c, sysdata.rs, firmware.rs, pipeline.rs (3 visibility edits + icon), lib.rs, tests/test_svc_mii.c.