85 KiB
85 KiB
Services and SDK calls
SDK calls and system services the host answers (dm/host/hle_*.c). Each entry: symptom, cause, fix, the games it was found on. The symptom index lists every symptom.
Service connections
SDK calls that open a system service through sm
- Symptom:
sm::Initializeaborts right afterConnectToNamedPort(SVC 0x1F) andSendSyncRequest(0x21); abort insidenn::sm::Initialize, the log showingSVC 0x1f(ConnectToNamedPort, port namesm:) andSVC 0x21answered by placeholders, then annn::diagabort; abort with no message at start-up in a service call the host has no hook for (Breakfromsdk+…with annn::diagbacktrace). - Cause: an SDK path reached a system service the host doesn't replace: the SDK's IPC to the service fails and it calls
AbortImpl. Often it is an SDK call the host does not hook, for example a newer overload of a hooked call. - Fix: hook the public SDK entry point named in the backtrace (
HLE_FORCEin the relevanthle_*.c). Symbolize the abort backtrace against the SDK's exports to find the caller (resolve the frames withsdis.py --symon the decompressed module) and hook that name. List the game's imports of that service the host does not hook (Listing a game's unhooked imports before they are met) and add the ones that talk to a service; library helpers that only fill the game's own buffers need nothing. Look at how the game uses the result before choosing it (abcatmount failure is the game's own "nothing delivered" path). - Found on: Super Mario Galaxy (SDK 18):
nn::hid::InitializeTouchScreen(int, int), the SDK 18 overload of the hookedInitializeTouchScreen(); Super Mario Odyssey (SDK 7):account::OpenPreselectedUser, thenbcat::Initialize, thenaudio::AcquireAudioDeviceSwitchNotification, and a null read inaccount::GetLastOpenedUser. - Note: the same abort from other services:
ec::InitializeForShopServiceAccessors(Shop service accessors),pctl::IsFreeCommunicationAvailable(Parental controls and free communication),oe::FinishStartupLogo(The startup logo),hid::VibrationNode::Update(Vibration values flushed by the SDK's vibration graph),audio::GetAudioDeviceOutputVolume(Audio device output volume and names),capsrv::InitializeForApplication(Album calls fail with no album),settings::LanguageCode(Language codes), and the rest of this file.
SDK 3.6.0 answered as SDK 4
- The oldest SDK so far booted to its frame loop with the provisional profile as generated and no host change: the services, the NVN API version (53, as for every SDK up to 10), the audio output and the heap all answered as for SDK 4.
- Found on: Bayonetta 2 (PlatinumGames' engine in its Wii U form): what it needed was graphics (Clears of attachments larger than the render pass,
Downsampleof a single-sample texture) and a reader for its CRI CPK archives (BNSH files in PlatinumGames DAT archives inside CRI CPK archives); Bayonetta, on the samesdkandsubsdk0builds, then needed no change at all (its profile is Bayonetta 2's with its own build row and tables, and the route from the boot to the first fight logged no red line).
Listing a game's unhooked imports before they are met
Most service calls can be found before they crash, before the first boot or the next one: the calls that would abort in sm::Initialize (or read a null service object) are in the import list.
- Procedure: list
main'snn::imports (its undefined imports) that no host hook names, by namespace: the survey's import table (nso_syms.py), thenmain's undefinednn::symbols that no hook (HLE/HLE_FORCE) names (02), and read each one's SDK code. This gives the calls a game will make before they are met. The import list itself says where to look: diff the undefined symbols against the closest title the host already runs (and every other game's imports), drop what the host hooks, and the rest are the candidates. To find whichsdkfunction aborted, map the backtrace offsets to the nearest exported symbol (The Legend of Zelda: Tears of the Kingdom 1.4.3). - What needs nothing: most
nn::os/nn::memimports run natively insdk. Calls that are pure computations need nothing, nor do socket calls behind aSocket()that already fails offline, nor library helpers that only fill the game's own structures. - Hook names: hooks are
HLE_FORCE,SSF,SKF,IKB_IGNOREacrossdm/hostanddm/gfx.stringsmisses names the symbol table stores as the tail of a longer one, so list them withnso_syms.dynsyms. - The survey table's blind spots: it counts only names given to
HLE/HLE_FORCEdirectly: hooks made through macros (SKFandSSFinhle_net.c,TOUCH(N)inhle_hid.c) show as missing. The touch-screen states (GetTouchScreenState<16>) were already hooked by such a macro, which the import table does not see. It once listed every vi call as unhooked:nso_syms.py host_hooksread onlydm/host, and the vi hooks are indm/gfx(it readsdm/gfxanddm/gfx/nvnnow; found on Absolum). - What it found (the calls themselves are in the entries of their service below):
- Pokémon Sword (SDK 7, one boot each): the import list against the hooks and every other game's imports named all of them beforehand: 33
nn::imports no host hook names and no other game imports. - 13 Sentinels: Aegis Rim (SDK 11).
- Monster Hunter Rise (SDK 11, one boot each): the import list against the hooks and the other SDK 11 games found most of them before the first boot.
- DRAGON QUEST XI S.
- Octopath Traveler 0: the pure computations were the swkbd presets, the web argument constructors,
time::GetDayOfWeekand the audio resampler. - DRAGON QUEST VII Reimagined (SDK 19.3.1): the import list against the hooks gave the rest before they were met; the rest fill the game's own structures or sit behind a socket that fails offline.
- Pokémon Legends: Z-A (19.3.11): the import list against the hooks (the survey's
nso_syms.pytable, thenmain's undefinednn::symbols that noHLE_FORCEnames) left 86 candidates; most only fill the game's own structures (error and web argument builders,preporeport buffers,swkbdconfiguration, the audio resampler,nn::async). The ones that reach a service were hooked before they were met where the answer is clear:time::ToPosixTimewith an explicit rule,album::SaveCurrentScreenshot,err::ShowApplicationErrorand theShowShopAddOnContentList(UserHandle)overload. - STEINS;GATE RE:BOOT (SDK 20.5.17):
nn::audio::SetAudioOutVolume(AudioOut volume kept per output) was amongmain's imports that no host hook names; the others there fill the game's own structures or run insdk(the CPU audio renderer, the swkbd and hid argument builders). - Paper Mario: The Thousand-Year Door (SDK 15.3.2):
nn::settings::GetAvailableLanguageCodeswas the one import ofmainthat no host hook names and that reaches a service;fs::MountRamDiskand the swkbd and hid helpers it also imports run insdkor were never called. - Catherine: Full Body (SDK 8.2.1): besides
GetAvailableLanguageCodeCount, the other imports no hook names run insdk(theLanguageCodecomparison,ClockSnapshot's constructor and getters, fibers,NumberLineAllocator). - Animal Crossing: New Horizons (SDK 20.5.4): the clock, region and album calls under their own entries below were found in
main's imports before the first boot. - The Great Ace Attorney Chronicles (SDK 10.4.1): listed after its first boot crashed in
oe::DisableAlbumImageTakenNotification; the calls found are in the entries under## Display, window and operation mode,## File system and save dataand## Accounts, network, shop and parental controls. - Big Brain Academy: Brain vs. Brain (SDK 11.4.3).
- Pokémon Sword (SDK 7, one boot each): the import list against the hooks and every other game's imports named all of them beforehand: 33
A game update imports SDK calls the base game did not
- Symptom: a game update aborts at boot where the base game ran.
- Cause: the update imports SDK functions the base game didn't. Mario 1.2.1 added 28, among them
nn::nfp(amiibo, initialized at boot),pl::IsRunningOnOunce(is this a Switch 2),ec::ShowShopApplicationInformationForUpgrade,account::PopOpenUsersandaoc::CountAddOnContent. - Fix: diff the undefined dynamic symbols of the two
mainmodules before the first run, and read the call sites before choosing answers: Mario only reads the count fromPopOpenUsersand falls back toTryOpenPreselectedUseron 0, and only counts add-on content when running on a Switch 2. The host answers as a Switch 1 with no amiibo and no add-on content (hle_sdk16.c,hle_account.c,hle_misc.c). - Found on: Super Mario Bros. Wonder (update 1.2.1).
Game-linked HIPC clients of system services
- Symptom: abort in
sm::Initializeundersf::hipc::InitializeHipcServiceResolution, right after the first frames (frommain+0x5b8198). - Cause:
mainlinks its own clients ofolsc:u(cloud save data backup settings) andnpns(push notifications), which talk HIPC throughsmandsvcSendSyncRequest. For each user the game asks whether backup is on and switches it off where it is (main+0x2809c8c); a failed connect aborts as well. - Fix:
hle_sf.ctakes over the SDK's HIPC client entry points (InitializeHipcServiceResolution,ConnectToHipcService,SendSyncRequest,CloseClientSessionHandleand the control requestsQueryPointerBufferSize,ConvertSessionToDomain,DuplicateSession(Ex)): a service it serves (olsc:u) gets a host session, and every request on it succeeds with zeroed output, which reads as backup off for every user. Any other name is refused with a red line, assmrefuses a service nobody registered (npnswas never connected).DM_SF_TRACE=1dumps each request. - Found on: Animal Crossing: New Horizons (SDK 20.5.4).
SDK names that changed between versions
Older SDK names for the same calls
- Symptom: a call the host hooks under its newer name reaches the SDK's own code on an older game.
- Cause: older SDKs export other names for the same calls.
- Fix: hook both generations. SDK 12 names:
ListAudioDeviceName/GetActiveAudioDeviceName, singularhid::GetNpadState,OpenDefaultAudioOut. SDK 11 names:fs::CommitSaveData(mount)is the older name offs::Commit. - Found on: Metroid Dread (SDK 11 names).
- Note: overloads change too: SDK 18's
nn::hid::InitializeTouchScreen(int, int)(Super Mario Galaxy, SDK calls that open a system service through sm), SDK 19'sLoadModulewith one more bool (nn::roand its port), andvi::CreateLayerwith the layer's size (Absolum,vi::CreateLayerwith the layer's size).
SDK 4's friends::GetBlockedUserList without a Uid
- Symptom: abort in
sm::Initializeundernn::friends::GetBlockedUserListafter the opening movie. - Cause: SDK 4's overload without the
Uid(EPiPNS_7account23NetworkServiceAccountIdEii), which the host did not take over. - Fix: it reports an empty list with success: the game turns a failure into an error for the error applet (
main+0x8ff9f0). The overload with aUidkeeps reporting a failure, which DRAGON QUEST XI S takes as it is (Account, friends, play data and Nintendo Account calls). - Found on: DARK SOULS: REMASTERED (SDK 4.5.0, the oldest so far; one boot).
SDK 6's account::OpenPreselectedUser returns a Result
- Symptom: the first loading screen never ends;
DM_FS=1showssave:/system"not found" and nofs mount save. - Cause: in SDK 6
account::OpenPreselectedUserreturns a Result, and the game skips its save mount when it is not 0 (main+0x53b7e0). The host's hook returned void and left the handle pointer in w0. - Fix: the hook returns 0 now; SDK 7 and later callers ignore it.
- Found on: Fire Emblem: Three Houses (SDK 6.5.0).
File system and save data
The SDK's own fs::IsMounted reads an empty mount table
- Symptom: IL2CPP "Could not open …/global-metadata.dat" (then a crash in its logger).
- Cause: IL2CPP checks
nn::fs::IsMounted("rom")before opening; the SDK's ownIsMountedreads a mount table the host never fills. - Fix: answer from the host's mount table (
hle_fs.c). - Note: the C library's path checks read the same empty table through
IsPathMounted(C library path checks ask the file system's limits).
C library path checks ask the file system's limits
- Symptom: Addressables never start (no
StreamingAssets/aa/settings.jsonin theDM_FS=1output at all), then a null asset crash (main+0x31100ec). - Cause: Unity 2022's C# file API reaches the SDK's C library, not
nn::fs:File.Exists→SystemNative_Stat→stat=open+fstat+close(sdk+0x948b0). nnmusl's path check (sdk+0x904e0, then the convertersdk+0x8ef50) asksnn::fs::IsPathMounted,GetPathLengthMax,GetUtf16PathLengthMax,GetEntryNameLengthMaxandGetUtf16EntryNameLengthMaxbefore it callsnn::fs::GetEntryType/OpenFile, and the SDK's own versions read the empty mount table (IsPathMountedfalse; the next one aborted with Result 0x35f202). - Fix: the host answers them from its mount table, with Horizon's 0x300-byte limits and no UTF-16 limit (
hle_fs.c). The path .NET builds is/rom:/…; nnmusl's converter turns it back intorom:/…. - Found on: Ace Attorney Investigations Collection (1.0.0.2, SDK 17.5.3, Unity 2022.3 IL2CPP).
- Note: armyja's fork (GitHub issue #18) patched the managed stat wrappers by address instead; these hooks need no per-build table.
Paths with . and .. components
- Symptom: an Atk task aborts in a
switchdefault (UnexpectedDefaultImpl, thenAbortImpl) while a stream sound loads. - Cause: the stream path was
rom:/system/snd/../../sounds/streams/…, and the host answered "not found" for any..component. Horizon's path normalizer resolves.and..lexically and fails only above the mount root. - Fix:
host_path_exnow does the same (norm_restinhle_fs.c). - Found on: Metroid Dread (
main+0x1225a8c).
RomFS lookups on large directories
- Symptom: a long black screen at boot with nothing in the log (Engage: about 17 s on the Mac, 32 s on an iPhone); sampling the process (
sample <pid> 3) showsgetxattrunderpath_componentat the top of stack. - Cause: the game is walking RomFS, and every path component scanned its whole parent directory. That scan maps the game's case-sensitive names onto APFS, so it calls
getxattron each entry, and Engage has directories of 38,000 files. The rest of the time was the listing itself:ReadDirectorycalledstat,getxattrand a failingfopen("<file>.holes")for every entry. - Fix: read-only mounts now use a per-directory name index built at the first lookup (
dir_index_getinhle_fs.c), which also speeds up every file Wwise opens during play. In listings, now only.dm-case-*names (the only ones that carry the xattr) read it, directories take their type fromd_type, and files usefstatat/openatrelative to the directory. - Found on: Fire Emblem Engage: with the name index the first frame came at 5.7 s on the Mac; with the listing fix too it comes at 1.8 s on the Mac (9.9 s → expected about 3 s on an iPhone), and the opening movie starts at 7 s instead of 49 s.
ReadFile into write-protected texture pool pages
- Symptom:
[RED] fs ReadFile into … failed at +0 (EFAULT): VM region … protection 1, now and then. - Cause: the destination was texture pool memory, which the dirty-page tracker write-protects after each upload (
nvn_dirty.c).ReadFilelifts the protection first (dm_guest_write_prepare), but the graphics layer can protect the pages again beforepreadwrites them, and a system call gets EFAULT where a plain write would fault into the tracker's handler; at 60 fps uploads come twice as often. - Fix: on EFAULT the host now prepares the rest of the buffer again (unprotect, mark dirty, so the texture is uploaded again) and reads on, up to eight times (
h_fs_ReadFile; the first case is logged,[fs] ReadFile … prepared again). - Found on: Monster Train 2 at 60 fps: two runs in three as the route map loads, never at 30 fps.
- Check: four runs at 60 fps: one retry, no failed read.
File-not-found noise from optional paths
- Symptom:
fs … not foundlines withDM_FS=1. - Cause: games probe many optional paths (debug parameters, other-language voice files).
- Note: only a missing file followed by a failure matters.
- Found on: every game (general).
Directory, file data cache and save-thumbnail calls
- Symptom: a missing
fs::DeleteDirectory; undefinednn::fs::/nn::account::imports with no hook, found from the import list (Listing a game's unhooked imports before they are met) before they were met. - Fix: hooked:
fs::DeleteDirectory(an empty directory),fs::CleanDirectoryRecursively,fs::RenameDirectory(done asRenameFile);fs::EnableGlobalFileDataCache,fs::DisableGlobalFileDataCache,fs::EnableIndividualFileDataCache/DisableIndividualFileDataCache;account::StoreSaveDataThumbnailImage/DeleteSaveDataThumbnailImage(nothing kept) (hle_fs.c,hle_account.c). - Found on: Metroid Dread (SDK 11:
fs::DeleteDirectorywas missing); 13 Sentinels: Aegis Rim (SDK 11:CleanDirectoryRecursively,EnableGlobalFileDataCache); DRAGON QUEST XI S (SDK 7.3.2:DisableGlobalFileDataCache); The Great Ace Attorney Chronicles (SDK 10.4.1:RenameDirectory, the thumbnail calls), Big Brain Academy: Brain vs. Brain (SDK 11.4.3: the individual file data cache).
fs::Commit with a list of mounts
- Symptom: the SDK's own code opened fsp-srv and aborted, leaving the save writes uncommitted.
- Cause:
fs::Commit(const char* const*, int), the mount-list overload, had no hook. - Fix: the host commits the list as one transaction when every name is the same host save; cache and temporary storage in the list are skipped (their writes are already on disk, as for the single-name
Commit), and a list of two separate saves is refused without committing either. - Found on: Animal Crossing: New Horizons (PR #25).
- Note: SDK 11's
fs::CommitSaveData(mount)is the older name offs::Commit(Older SDK names for the same calls);fs::Commitwith aCommitOptionis under Device save data on the account save's directory.
Save data size queries
- Symptom: SIGSEGV at address 0 in
nn::fs::GetSaveDataSizeMax, about 13 minutes into a new game. - Cause: Unreal's save system asks before its first save; the SDK's version reads the fsp-srv object the host never made.
- Fix: it reports 256 MB of data and journal, as
GetSaveDataSizedoes (hle_fs.c).fs::GetCacheStorageSizeandfs::GetFreeSpaceSizegive the same answers. Also hooked:fs::GetSaveDataSize/ExtendSaveDataand the one-argumentfs::MountCacheStorage. - Found on: Octopath Traveler 0 (
GetSaveDataSizeMax,GetCacheStorageSize,GetFreeSpaceSize, found by the import list); Monster Hunter Rise (SDK 11:GetSaveDataSize,ExtendSaveData,MountCacheStorage).
Cache storage and other titles' save data
- Symptom: each aborted in
sm::Initializeor read a null applet proxy, one per boot. - Cause: SDK 20 file system calls that reach a service.
- Fix: cache storage:
OpenCacheStorageList/ReadCacheStorageListreport none,CreateCacheStorage/MountCacheStorageuse a host folder beside the temporary storage.fs::IsSaveDataExisting(ApplicationId, Uid)for another title (it asks for Breath of the Wild's save: none) andMountSaveDataReadOnly. - Found on: The Legend of Zelda: Tears of the Kingdom (1.4.3, the same
sdkbuild as Breath of the Wild 1.9.0).
Device save data on the account save's directory
- Symptom: the island lives in device save data, which the host did not mount.
- Cause: 3.0.3's
mainmounts no account save data; the island is in device save data (MountDeviceSaveData,GetDeviceSaveDataSize,ExtendDeviceSaveData). - Fix: the host gives device save data the account save's host directory and transaction (one player). A second mount name over a save already mounted now shares that mount's transaction (
mount_ex); it used to clone the committed directory again, over the first name's pending writes. Alsofs::Commitwith aCommitOption(its one flag clears the restore flag) andGetSaveDataRestoreFlag(never restored from a backup). - Found on: Animal Crossing: New Horizons (3.0.3, SDK 20.5.4).
- Note: a game whose
mainimportsMountSaveDatatoo gets its device save as a save of its own (Device and account save data as two saves).
Device and account save data as two saves
- Symptom: every boot asks to pick a Mii again (
DM_FS=1:UserSave:/userdata.datnot found although the host save has it). - Cause: the game mounts
CommonSavewithMountDeviceSaveDataandUserSavewithMountSaveData(uid)(main+0x8b0c84), two saves on Horizon; the host made device save data the account save (for Animal Crossing: New Horizons, which mounts only device save data), so both names shared one directory. At boot the save manager deletesCommonSave:/userdata.dat(main+0x8b1910, through its file device'sDeleteFile,main+0x633afc; a probe there with@*x1+8shows the name) and commits, and in the shared directory that was the account save's own file. - Fix: a game whose
mainimportsMountSaveDatatoo gets its device save as<DM_SAVE>_device, a transaction of its own (hle_fs.c); of the games on hand only this one imports both, so Animal Crossing's islands stay where they are. - Found on: Mario Kart 8 Deluxe (4.0.0, SDK 4.4.0; one boot).
System data stand-in for the MiiModel archive
- Symptom: abort in
fs::detail::GetFileSystemProxyServiceObjectunderfs::QueryMountSystemDataCacheSize(main+0x8f23bc). - Cause: the game mounts system data 0100000000000802, MiiModel, as
MiiResource:and readsNXTexture{Low,Mid}{Linear,SRGB}.datandShape{Mid,High}.datwhole; the SDK's Mii rendering is linked intomain(main+0x8f1400–+0x8fc000). - Fix: firmware content is not the host's to ship, so the host writes a stand-in of the same layout with every part empty (
dm_mii_standininhle_mii.c, mounted byhle_fs.cMountSystemData; any other system data answers "target not found"). Heads build and draw nothing; bodies and favourite colours show. - Found on: Mario Kart 8 Deluxe (4.0.0, SDK 4.4.0; one boot).
- Note: the layout as the code reads it: texture file +0xc the largest decompressed size of each of 11 part kinds, +0x38 their alignments, from +0x64 one 0x1c-byte entry per part (offset, size, stored size, 4 zlib parameters, then width, height, format, mips); shape file +0xc / +0x3c the same for 12 kinds, from +0x6c one 0x58-byte entry per part (offset, size, stored size, parameters, five vertex attribute offsets and sizes, index offset and byte size, bounding box), then hair (132 × 0x48, +0x12704) and faceline (12 × 0x24, +0x14c24) transforms. A texture of size 0 is skipped (
main+0x8faaf4); shapes cannot all be absent, because the faceline is drawn without a check (a null buffer read atmain+0xa690d0undermain+0x8286c4), so every shape entry points at one shared mesh of one vertex and the triangle (0, 0, 0), 32 bytes of zeros in a stored zlib block. The largest sizes must not be 0: they size the model's memory pool (main+0x8f2e6c), whose alignment is the first one. The Mii database itself: Mii database answered by the host.
Add-on content
Add-on content from DM_AOC_DIR
- Symptom: DLC the player owns is not unlocked.
- Fix: the host reports add-on content from
DM_AOC_DIR, one numbered folder per DLC index (title ID − base − 0x1000) holding that DLC's RomFS.build_ipa_from_nsp.pyextracts the DLC NSPs in the folder withnx_extract --aoc,build_ipa.shcopies them to<App>/aoc/, and the app setsDM_AOC_DIR.aoc::CountAddOnContent/ListAddOnContentreport those indices (hle_misc.c), andfs::MountAddOnContentmounts the folder read-only (hle_fs.c). D2R keeps its fixed{1}withoutDM_AOC_DIR;DM_AOC_OWNED=0reports none. - Found on: Fire Emblem Engage: its two DLC packages each hold one empty marker file (
e0,h0), and the content is already in update 2.0.0.DLCManager.RefreshHasListmarks a content owned when its mount succeeded (probemain+0x29f3c88).
Add-on content mount cache sized as RomFS working memory
- Symptom: SIGSEGV in
crypto::detail::CbcModeAes128Helper::DecryptBlockswith a null destination (main+0x8737e8, right afteraoc:is mounted). - Cause: the add-on content (0100152000023001:
contents.datandversion.txt, the courses are in the update) is decrypted inAocHeap, a 0x3130-byte heap that also holds the mount cache. The host answeredQueryMountAddOnContentCacheSizewith 4 KB, and the 0x1400-byte decryption buffer then did not fit. - Fix: on Horizon the cache is the RomFS's file table as
RomFsFileSystem::GetRequiredWorkingMemorySizesizes it: both hash tables (4 bytes a bucket, the builder's bucket count) and all directory and file entries (0x18 / 0x20 bytes plus the name, padded to 4), 0x88 bytes for these two files. The host computes that from the folder it mounts (romfs_work_sizeinhle_fs.c);romkeeps its minimum answer, which is too small rather than too large. - Found on: Mario Kart 8 Deluxe (4.0.0, SDK 4.4.0; one boot).
Add-on content queries on a small guest stack
- Symptom: SIGBUS in
__vfprintfunderaoc_load, fromh_aoc_Count(a store to0x…67ff0, right below a page of the guest thread's stack). - Cause: the first add-on content query lists
DM_AOC_DIRand logs, a few KB of stack, and the game asks from a thread with little left. - Fix:
aoc::CountAddOnContentandListAddOnContentrun on the big host stack (HLE_FORCE_BIG,hle_misc.c). - Found on: Digimon Story Cyber Sleuth: Complete Edition (SDK 6.4.0; one boot).
Input
History reads fill every requested entry
- Symptom: buttons and menus work, the sticks do nothing in play (Super Mario Odyssey: its first hint, "Look Around", waited forever); an
nn::diagabort with Result 0x2aca in the first frames ofnnMain(Hollow Knight: Silksong, Unity 6000.0.50f1:bt: sdk+0x514580 main+0x1296c64 …). - Cause: games read whole histories and ignore the returned count. Odyssey reads all 16 entries of
GetNpadStates; the host filled only the newest. It polls every controller slot each frame, which moves the shared sampling number on by about 16, so the stick samples it combined were mostly empty, while buttons and the stick's direction bits (which menus use) still came through. Silksong'smain+0x1296c60callshid::GetDebugPadStates(states, 16)every frame, and the SDK's own reads a debug pad resource nothing initialized (module 202, hid); the host had only the single-stateGetDebugPadState. - Fix: the host now fills every requested
GetNpadStatesentry with the current state under consecutive sampling numbers; theGetDebugPadStateshistory form answers every requested entry as not connected, newest first with consecutive sampling numbers (hle_hid.c).DM_KEYStakesRLeft/RUp/RRight/RDownfor the right stick. - Found on: Super Mario Odyssey; Hollow Knight: Silksong.
- Note: Odyssey's six-axis history is read the same way (Six-axis sensors).
Npad style set update event
- Symptom: the game never reads the controller (the title's "Press L+R" never sees a press; no
GetNpadStatecall at all). - Cause: it waits for the event bound by
hid::BindNpadStyleSetUpdateEventbefore reading. - Fix: the host now signals it once when it is bound for a connected controller, and again when a player's controller connects or goes away.
- Found on: The Legend of Zelda: Link's Awakening (SDK 8.2.0).
Six-axis sensors
- Symptom: motion moves never happen; shaking does nothing (Super Mario Odyssey: cap throws by shaking).
- Cause: the host handed out six-axis handles but reported no sensor.
- Fix: it now reports one per connected controller: two for handheld or dual Joy-Cons, one otherwise, with the style, npad id and device in the handle. At rest the sensor lies flat: acceleration (0, 0, −1) G, no rotation, identity direction. Samples are 5 ms apart on the guest clock and numbered by time, so a history read returns the samples at those times.
DM_BTN_SHAKE(dm_input.h: the iPhone shaken, Space on the Mac,DM_KEYSShake) plays a side-to-side shake in whole cycles. - Found on: Super Mario Odyssey: it reads all 16 history entries per sensor and ignores the count, like its npad history (History reads fill every requested entry).
- Note: find a game's swing detector by probing the accelerometer value it reads (
DM_PROBE=main:626248@x8+10in Odyssey 1.4.1).
SDK 20 six-axis sensor and amiibo (nfp) calls
- Symptom: SDK 20 calls that reach a service;
nfp'sAttachActivateEvent/AttachDeactivateEventcrashed in the SDK atsdk+0x3f2a78. - Fix: the console six-axis sensor (
hid::*SevenSixAxisSensor*) reports zero states; morenfpcalls are hooked:AttachActivateEvent/AttachDeactivateEvent, the two-argumentMount,Unmount,GetCommonInfo,GetRegisterInfo,GetState,Finalize. The host answers as a console with no amiibo. - Found on: The Legend of Zelda: Breath of the Wild (1.9.0, SDK 20.5.22). Super Mario Bros. Wonder 1.2.1 already initialized
nn::nfpat boot (A game update imports SDK calls the base game did not).
Vibration values flushed by the SDK's vibration graph
- Symptom: abort in
nn::sm::Initializeundernn::hid::VibrationNode::Update. - Cause: the SDK's vibration graph flushes queued values with
hid::detail::SendVibrationValues, which opens the hid service. The game sets handles on itsVibrationTargets even when the host reports no vibration device. - Fix: the flush calls it through the PLT, so an
HLE_FORCEon that symbol catches it and returns success (hle_hid.c). - Found on: Metroid Dread.
Touch gestures recognized from the touch points
- Symptom: taps in handheld mode do nothing, though the game reads the touch screen (the title's PRESS ANY BUTTON, the menus' cards).
- Cause: the game's touch handler (
main+0x42f1ac) usesGetTouchScreenStateonly to track the finger and takes its taps, pans and swipes fromhid::GetGestureStates(it acts on types 5–9), which the host answered with no entries. - Fix: the host now recognizes gestures from the same touch points, by yuzu's rules plus an 8 px slop before a touch counts as moving (
hle_touch.cgesture_sample, unit testt_gesture; only in handheld mode, as touches): Touch, Press after 0.5 s, Pan, Pinch / Rotate with two fingers, Cancel on a change in the finger count, then Tap (IsDoubleTap within 0.35 s), Swipe above 400 px/s or Complete, and Idle. For every game. - Check: every menu, the cards, the map's drag and flick worked by touch with it.
DM_TOUCH_LOG=1logs each gesture entry, and aDM_CTLlinedrag <x0> <y0> <x1> <y1> [seconds]moves a finger for testing pans and swipes on the Mac. - Found on: Taiko Risshiden V DX (SDK 12.3.3, Koei Tecmo's KTGL).
Mouse, keyboard and key code maps
- Symptom: the first boot aborted in
hid::GetMouseState(the SDK's mouse resource was never initialized). - Fix: hooked:
hid::GetMouseState,hid::GetKeyboardStates, andkpr::GetKeyCodeMap, which asks the settings service (settings::detail::GetKeyCodeMap) for the keyboard layout's map; the host leaves the map zero. - Found on: 13 Sentinels: Aegis Rim (SDK 11,
GetMouseState); Monster Hunter Rise (SDK 11,GetKeyboardStates); DRAGON QUEST VII Reimagined (kpr::GetKeyCodeMap: the game zeroes a 0x1000-byte map and fetches it only once a keyboard reports keys,main+0x1f0eb4).
Poké Ball Plus (Palma)
- Symptom: the first boot crashed in
hid::detail::SetPalmaBoostMode, a null read atsdk+0x238eb0(the hid service never opened). - Fix: the Poké Ball Plus calls (
hid::*Palma*, 26 functions) answer that none is connected: no npad has the Palma style, so the game never gets a connection handle, and every call on a handle fails (hle_hid.c). - Found on: Pokémon Sword (SDK 7).
IR camera reported unconnected
- Symptom: undefined
irsensorimports found from the import list (Listing a game's unhooked imports before they are met). - Cause: Bezel Engine's input layer makes one (an IR camera) per controller and asks its status (
main+0x497f84). - Fix: the IR camera (
irsensor) answers every camera unconnected, and processors cannot run. - Found on: Big Brain Academy: Brain vs. Brain (SDK 11.4.3, NintendoWare Bezel Engine).
Software keyboard and applets
System applets opened by Show… calls
- Symptom: null read in
sdkundernn::applet::CreateLibraryApplet(Engage: title menu → DLC →nn::ec::ShowShopAddOnContentList);Show…overloads found in the imports before they were met. - Cause: every
Show…call that opens a system applet (eShop, friend lists, web pages, error screens) goes through the applet proxy thatoe::Initializewould have set up, and the host has none. - Fix: hook each
Show…overload the game imports: listmain's undefined symbols containingShow/Appletand grep the host sources for each mangled name; one overload missing is enough to crash. The shop calls returnvoidand act as if the player closed the shop (hle_misc.c); the friends and web ones return failure (hle_friends.c,hle_offline.c).web::ShowOfflineHtmlPageis cut, asShowWebPage.err::ShowNetworkServiceErroranderr::ShowApplicationErrorare logged. Also hooked, found in the imports before they were met: theShowShopAddOnContentList(UserHandle)overload, and theec::ShowShop…overloads without anApplicationId(they show the calling application's own pages).ec::ShowShopSubscriptionDetailsis under Shop service accessors. - Found on: Fire Emblem Engage (the shop calls); Octopath Traveler 0 (
web::ShowOfflineHtmlPage); Pokémon Sword (SDK 7,err::ShowNetworkServiceError); Pokémon Legends: Z-A (19.3.11:ShowShopAddOnContentList(UserHandle),err::ShowApplicationError); The Great Ace Attorney Chronicles (SDK 10.4.1: the overloads without anApplicationId).
The software keyboard blocks only its caller
- Symptom: the sound stutters and the input box lags while the player types a name (Octopath Traveler 0 on an iPhone: one 4.7 s frame, 5,035 wakeups a second instead of about 1,800, and no audio underruns).
- Cause: the host froze the guest clock while
swkbd::ShowKeyboardwas up (d2r-mobile's choice, so that monsters did not attack while the player typed). Unreal calls it off its game thread, so the game kept running, and every thread that waits on the clock spun while its audio mixing starved. On the Switch the applet blocks only the caller while the clock runs. - Fix: the host now does the same (
hle_swkbd.c). The iOS input box froze the clock a second time around its own wait (front_ios.m), so the first fix changed nothing on the phone (4,977 ms and 2,577 ms frames, 5,181 wakeups a second): look for every caller ofguest_clock_freeze_for, the front ends included. The inline keyboard keeps the freeze: its game (Monster Hunter Rise) does not block on hardware at all, and the host's input box blocks it. - Found on: Octopath Traveler 0.
- Check: on the Mac
DM_KBD=<text> DM_TEST_SWKBD_HOLD_MS=8000holds the keyboard up: frozen, 8.8 fps and 7,936 wakeups a second in that window; running, 30 fps and 1,788.
The inline keyboard as a resident library applet
- Symptom: null read in
applet::CreateLibraryAppletunderswkbd::InlineKeyboard::Initialize(3D World, entering the save select after the character select). - Cause: the inline keyboard is a resident library applet whose results arrive through
Calc. - Fix:
hle_swkbd.ctakes over every entry that touches the applet:Appeararms a request, and the nextCalcasks for the text the wayShowKeyboarddoes (DM_KBD, else the front end's input box) and hands it to the game's changed-string and enter callbacks, or calls its cancel callback;GetImagereports no picture.GetImageMemoryRequirementis not a pure computation: it asks vi, hence thevi::GetIndirectImageMemoryRequirementhook (vi layer and display calls without vi service objects). - Found on: Super Mario 3D World + Bowser's Fury.
Inline keyboard window size, setters and callbacks
- Symptom: texture creation returns null right after the inline keyboard's
Initialize, then a null read atmain+0x7713044. - Cause: the game makes three textures of
swkbd::InlineKeyboard::GetWindowSize, and the host answered 0 × 0 (its creation function returns null for a size of 0,main+0x77112b8). - Fix: it is 1280 × 720 as on hardware now. SDK 11 adds keyboard setters (window mode, key top alpha / scale / position, touch, hardware keyboard, user words, dictionaries; ignored) and two callbacks the applet makes from
Calc: finished-initialize afterInitializeand released-user-words afterUnsetUserWordInfo. The host makes them on the nextCalc. - Found on: Monster Hunter Rise.
Inline keyboard text callbacks and Calc states
- Symptom: the hunter's name box stays empty, then stays open with the name in it (Monster Hunter Rise, character creation).
- Cause: the inline keyboard used to count every
Appearas a cancel. The game also finishes the entry only whenCalcreturns 1 (ready / hidden) after a non-zero value (main+0x6fb3e6c); the hook returned nothing. - Fix: the text arrives through the enter callback, whose argument is the text inline (
char16_t[502], the length at +0x3EC; the changed-string argument adds three cursor positions at +0x3F0..+0x3F8:ikb_str_arg, read off the game's callbacks atmain+0x6fb3910/main+0x6fb3afc).Calcnow returns 0 until initialization finishes, 3 while shown and 1 otherwise.SetFinishedKeyboardCallbackis called on theCalcafterDisappear. AppearArg'stextMaxLength(+0x20) limits the text. - Found on: Monster Hunter Rise.
nn::ngc::ProfanityFilter vtable and by-name overloads
- Symptom:
CRASH sig=10at a heap address, called frommain+0x6bca160right after the name was entered (Monster Hunter Rise); crash inlmem::AllocateFromExpHeapunderngc::ProfanityFilter::MaskProfanityWordsInTextright after the character name is entered (DARK SOULS: REMASTERED); filter methods among the undefined imports with no hook, found from the import list before they were met (The Great Ace Attorney Chronicles). - Cause:
nn::ngc::ProfanityFilteris polymorphic and Monster Hunter Rise masks the name through its vtable (slot 8,MaskProfanityWordsInText(int *, char16_t *, uint32_t)). The constructor hook did nothing, so the object kept what the heap held. Other games call the methods by name: DARK SOULS: REMASTERED calls the UTF-16 overload (EPiPtj) by name, and the host had taken it over only in the filter's vtable, so the SDK's version ran on a filter whose word list the host never loaded; The Great Ace Attorney Chronicles callsngc::ProfanityFilter::MaskProfanityWordsInText(int *, char *, uint32_t),SetMaskModeandFinalizeby name rather than through the vtable. - Fix: the filter now gets a host vtable (
ngc_vtableinhle_misc.c): each slot of the SDK's_ZTVN2nn3ngc15ProfanityFilterEis matched by address with the class's methods, and slots the loader already bound to forced take-overs keep them. Theunsigned shortandchar16_toverloads are taken over by name too, as are thecharoverload,SetMaskModeandFinalize(hle_misc.c). Nothing is masked or reported. - Found on: Monster Hunter Rise (the vtable); DARK SOULS: REMASTERED (SDK 4.5.0; one boot: the UTF-16 overload by name); The Great Ace Attorney Chronicles (SDK 10.4.1: the
charoverload,SetMaskMode,Finalize).
Display, window and operation mode
vi::CreateLayer with the layer's size
- Symptom: SIGSEGV in
vi::CreateLayer(Layer**, Display*, int, int)(sdk+0x3f9bc4, from SDL3's Switch video driver atmain+0x239304). - Cause: the overload with the layer's size; the host had only the two-argument one.
- Fix: it returns the static layer (
hle_vi.c). - Found on: Absolum (SDK 18.3.2, FNA on .NET NativeAOT with SDL3; one boot each).
vi layer and display calls without vi service objects
- Symptom: SIGSEGV in
vi::SetLayerCrop. - Cause: the SDK's version dereferences vi service objects the host never made.
- Fix:
SetLayerCrop,DestroyLayer,CloseDisplayandvi::Finalizeare host no-ops. Also hooked:vi::GetIndirectImageMemoryRequirement(asked by the inline keyboard'sGetImageMemoryRequirement, The inline keyboard as a resident library applet);vi::ListDisplays(one display, "Default"; the game lists 10 into a stack buffer and does not read them,main+0x808f08), found from the import list before it was met. - Found on: The Legend of Zelda: Link's Awakening (SDK 8.2.0); Super Mario 3D World + Bowser's Fury (SDK 10:
vi::GetIndirectImageMemoryRequirement); The Great Ace Attorney Chronicles (SDK 10.4.1:vi::ListDisplays).
Layer frame numbers
- Symptom: SIGSEGV in
android::Surface::getQueuedFrameNumberundernn::vi::GetLatestFrameNumber. - Cause: Unreal paces frames by the layer's frame number (
main+0x10140e8); the SDK reads the vi service's surface, which the host never made. - Fix:
hle_vi.canswers with the frames presented so far;ListFrameHistoryreturns no entries. - Found on: Octopath Traveler 0.
Default display panel identifier
- Symptom: SIGSEGV in
vi::GetDefaultDisplayPanelIdentifier(main+0xa9a4, before the window). - Cause: the SDK's version calls through the vi service object the host never made. The game reads a u32 kind and, from kind 2, 0x14 more bytes; anything else makes it use zeros.
- Fix: the host answers an all-zero identifier (
hle_vi.c). - Note: a passed TV latency calibration in this game is saved per TV panel identity, the host's all-zero one included (TV latency calibration's late, beat-timed listening window).
- Found on: Rhythm Heaven Groove (SDK 21.4.0, one boot).
oe calls answered without an applet proxy
- Symptom: the first boot crashed in
oe::DisableAlbumImageTakenNotification: a null read atsdk+0x338070, the first call innnMain(main+0x6b188c); otheroecalls that reach am, found in the imports before they were met. - Cause: the SDK asks am through the applet proxy the host never made.
- Fix: hooked (
hle_oe.c):oe::DisableAlbumImageTakenNotification;oe::IsInStoreDemoModeEnabled(no);oe::GetDefaultDisplayResolutionand its change event;oe::SendServerMaintenanceOverlayNotification;oe::SetUserInactivityDetectionTimeExtendedUnsafe;oe::GetPerformanceConfiguration;oe::SetAlbumImageOrientation;oe::Begin/EndAutoSleepDisabledSectionandIsAutoSleepDisabled(counted);oe::ExecuteProgram(logged red: the host runs one program). The ones with a symptom of their own follow: The startup logo, Copyright frame buffer overlay, Applet resource user ID, launch parameters and VR mode, CPU overclock calls without an apm session, Focus handling mode decides whether focus changes are notified. - Found on: Monster Hunter Rise (SDK 11:
GetDefaultDisplayResolution,SendServerMaintenanceOverlayNotification); 13 Sentinels: Aegis Rim (SDK 11:SetUserInactivityDetectionTimeExtendedUnsafe); Super Mario 3D World + Bowser's Fury (SDK 10:GetPerformanceConfiguration,SetAlbumImageOrientation); DRAGON QUEST XI S (SDK 7.3.2: the auto-sleep calls); Octopath Traveler 0 (oe::ExecuteProgram); The Great Ace Attorney Chronicles (SDK 10.4.1, Capcom's MT Framework:DisableAlbumImageTakenNotification, thenIsInStoreDemoModeEnabledfrom the import list). After all of its fixes The Great Ace Attorney Chronicles runs its frame loop at 30 fps; the black screen it showed was its textures, never uploaded (CPU-written textures, the profile'stex_flush_uploadandtex_dirty_upload: Mapped-range flushes taken as uploads (tex_flush_upload), CPU writes with no flush, caught by page protection (tex_dirty_upload)).
The startup logo
- Symptom: abort in
nn::sm::Initializeundernn::oe::FinishStartupLogo, right after the first frames. - Cause: it reaches the applet proxy through
am::GetApplicationFunctions. - Fix: the host has no startup logo: a no-op hook (
hle_oe.c). - Found on: Metroid Dread.
Copyright frame buffer overlay
- Symptom: null read in
oe::InitializeCopyrightFrameBuffer, right after the window setup. - Cause: the copyright frame buffer is an overlay the system draws only into screenshots and recordings; the SDK hands it to am as transfer memory.
- Fix:
InitializeCopyrightFrameBuffer,SetCopyrightImageandSetCopyrightVisibilityare host no-ops (hle_oe.c). - Found on: Hyrule Warriors: Age of Calamity (SDK 9.6).
Applet resource user ID, launch parameters and VR mode
- Symptom: SDK 20 calls that reach a service, each one boot; SDK 20's
OpenAudioRendereraborts on an ARUID of 0. - Cause: the SDK's own
applet::GetAppletResourceUserIdreturns 0 without am, and SDK 20'sOpenAudioRendereraborts on that. - Fix:
applet::GetAppletResourceUserIdreports 0x80; the VR-mode calls (pl::IsVrMode/BeginVrMode/EndVrMode);pctl::CheckStereoVisionPermission(bool, permitted);oe::TryPopLaunchParameter(none) (The Legend of Zelda: Breath of the Wild 1.9.0, SDK 20.5.22).oe::GetPreviousProgramIndexreports −1: launched from the home menu (The Legend of Zelda: Tears of the Kingdom 1.4.3). - Found on: The Legend of Zelda: Breath of the Wild, The Legend of Zelda: Tears of the Kingdom.
CPU overclock calls without an apm session
- Symptom: a null read in
sdk+0x35258(apm's session) inoe::IsCpuOverclockEnabled, the first call innnMain; null read atsdk+0x362b8(apm::SetCpuOverclockEnabled) in the first calls ofnnMain(main+0x2e1e4c). - Cause:
oe::IsCpuOverclockEnabledandoe::SetCpuOverclockEnabled(bool)go through the apm session the host never opened. - Fix:
oe::IsCpuOverclockEnabledis hooked; forSetCpuOverclockEnabledthe host keeps the flag and the query reports it (hle_oe.c); it does not scale clocks. - Found on: Monster Hunter Rise (SDK 11:
IsCpuOverclockEnabled); Taiko Risshiden V DX (SDK 12.3.3:SetCpuOverclockEnabled).
Focus handling mode decides whether focus changes are notified
- Symptom: the game freezes after switching apps and back, picture still, sound on; the main loop waits in its pause handshake (
main+0x5d10d4/main+0x5cea68). Mac reproDM_OE_SCRIPT=bg=1@20,bg=0@25. - Cause: the game's own pause deadlocks. The game set focus handling mode 3. The SDK (
oe::SetFocusHandlingMode, SDK 18sdk+0x3c5770) turns modes 0 and 3 into "no focus notifications, the system suspends the game out of focus"; only 1 and 2 notify. The host sent message 15 anyway, and Galaxy 2's Wii layer ran a pause the console never triggers. - Fix:
hle_oe.cnow queues 15 only under modes 1 and 2 (or when no mode was set). - Found on: Super Mario Galaxy 2 (one of two causes of the freeze; the other is Guest clock reads wait while the app is in the background).
- Note: modes seen: 1 Monster Hunter Rise; 2 Wonder, Breath of the Wild, Tears of the Kingdom, Link's Awakening, Cuphead; 3 Galaxy, Galaxy 2, 3D World, Odyssey.
Album calls fail with no album
- Symptom: abort in
sm::Initializeundercapsrv::InitializeForApplication(album::Initialize); calls that reach the album service, found in the imports before they were met. - Fix: with no album they fail:
album::Initialize(saving to the album now fails,hle_offline.c); SDK 7'salbum::SaveScreenshotwithout an orientation (fails); SDK 20'sSaveAndShareScreenshotwith a share ID andGetSharePostedLastUrl;album::SaveCurrentScreenshot. - Found on: Monster Hunter Rise (SDK 11:
album::Initialize); DRAGON QUEST XI S (SDK 7.3.2:album::SaveScreenshot); Animal Crossing: New Horizons (SDK 20.5.4: the sharing calls); Pokémon Legends: Z-A (19.3.11:SaveCurrentScreenshot).
Accounts, network, shop and parental controls
Account, friends, play data and Nintendo Account calls
- Symptom: a null read at
sdk+0x336cccinpdm::QueryApplicationPlayStatistics(Pokémon Sword, the second boot); undefinednn::account::/nn::friends::imports with no hook, found from the import list (Listing a game's unhooked imports before they are met) before they were met. - Fix: hooked (
hle_account.c,hle_friends.c,hle_offline.c):account::ListAllUsers;account::PushOpenUsers(nothing to hand over);account::LoadCachedNintendoAccountInfo(fails: no Nintendo Account; Big Brain Academy then skips the birthday and country getters);friends::GetBlockedUserList,DeclareOpen/DeclareCloseOnlinePlaySession(unavailable);friends::StartFacedFriendRequest/StartSendingFriendRequest(fail); the Nintendo Account request'sGetAuthorizationCodeandGetState(both fail, asGetIdToken);pdm::QueryApplicationPlayStatistics, which asks how often this user launched two other games and reads only the launch count, so each is never played. - Found on: Super Mario 3D World + Bowser's Fury (SDK 10,
ListAllUsers); Octopath Traveler 0 (PushOpenUsers); DRAGON QUEST XI S (the friends calls); DRAGON QUEST VII Reimagined (the Nintendo Account request); Pokémon Sword (SDK 7,pdm); The Great Ace Attorney Chronicles (SDK 10.4.1: the friend requests); Big Brain Academy: Brain vs. Brain (SDK 11.4.3:LoadCachedNintendoAccountInfo). - Note: Super Mario Odyssey's
account::OpenPreselectedUserandaccount::GetLastOpenedUserare in SDK calls that open a system service through sm; SDK 6'sOpenPreselectedUserin SDK 6'saccount::OpenPreselectedUserreturns a Result; SDK 4'sGetBlockedUserListwithout aUidin SDK 4'sfriends::GetBlockedUserListwithout aUid; the save thumbnail calls in Directory, file data cache and save-thumbnail calls.
Mii database answered by the host
- Symptom:
[RED] sf: the game connects to service mii:e … mii:u itself, then an abort. - Cause:
nn::mii::Databasekeeps anIDatabaseServiceof the mii service at +0 (Database::Initialize,sdk+0x1c5664), andAppendMii/ShowMiiEditstart the MiiEdit applet. - Fix:
hle_mii.ctakes over the public methods: a console with no Mii of its own (an empty database: no index, nothing to update) that still has the six default Miis every console has. A CharInfo the host builds is the default Mii's parameters with Switch common colours, a create ID with version 4 and variant bits (fixed per default Mii, so a saved choice finds the same Mii; fresh forBuildRandom, which the title's demo race calls for 12 racers) and the nickname "no name"; every field is withinCharInfoRaw::Verify's ranges (sdk+0x1b68c8). The Mii editor is cancelled (0xE7E, whichShowMiiEdititself treats as success). - Found on: Mario Kart 8 Deluxe (4.0.0, SDK 4.4.0; one boot). The game asks for the default Miis with
Get(…, 6, SourceFlag 2)and never reads the count (main+0x81e954): its boot-time list of Mii icons (main+0x419dc8) builds a head model for each of the six, and zeroed entries failed the model'sCharInfoAccessor::IsValid(CharModel::Initializereturned 0xd67e,main+0x8f15b0) and crashed atmain+0x8f152c. - Note: the heads' model data is a stand-in with every part empty (System data stand-in for the MiiModel archive).
Shop service accessors
- Symptom: the abort with no message (
SVC 0x15/0x1f/0x21placeholders, thenBreakwithsm::FinalizeunderShopServiceAccessServer::Proxy::Initialize) fromec::InitializeForShopServiceAccessors, called from Unreal'sFOnlineSubsystemSwitch::Initat the start ofnnMain. - Cause: the shop service accessor library reaches the shop service.
- Fix: the library init succeeds with nothing to set up, an accessor never initializes, and its async response is complete at once with no data (
hle_misc.c); the accessor and response are stubbed whole because the SDK's destructor aborts unlessFinalizesucceeds. Also hooked:ec::FinalizeForShopServiceAccessors, the shop response'sCancelandGetErrorCode, andec::ShowShopSubscriptionDetails. - Found on: DRAGON QUEST XI S (SDK 7.3.2: the game then runs its opening movie at 30 fps); DRAGON QUEST VII Reimagined (SDK 19.3.1:
FinalizeForShopServiceAccessors,Cancel,GetErrorCode,ShowShopSubscriptionDetails, found by the import list). - Note: the shop's
Show…pages are under System applets opened byShow…calls.
Parental controls and free communication
- Symptom: abort in
nn::sm::Initializeundernn::pctl::IsFreeCommunicationAvailable(Engage, entering its first battle map). - Cause: the SDK connects to the parental-control service through
sm. - Fix: the host answers it like the other free-communication calls: not available (
hle_misc.c).pctl::CheckFreeCommunicationPermission: never. - Found on: Fire Emblem Engage; DRAGON QUEST XI S (
CheckFreeCommunicationPermission). - Note:
pctl::CheckStereoVisionPermissionis under Applet resource user ID, launch parameters and VR mode.
Network interface, LDN, socket and play report calls
- Symptom: play report calls met entering the save select (3D World); undefined
nn::ldn::/nn::socket::imports with no hook, found from the import list before they were met (Big Brain Academy). - Fix: hooked (
hle_net.c,hle_offline.c): thenifmIP queries with SDK 10'ssocket::InAddr/in_addrmangling;prepo::AddSessionId/AddInternetConnectionStatus;ldn::GetNetworkConfig; SDK 11's socket overloads with plainsockaddr/intarguments (Connect,Accept,GetSockName,Get/SetSockOpt,Fcntl,Recv,Send). - Found on: Super Mario 3D World + Bowser's Fury (SDK 10:
nifm,prepo); Big Brain Academy: Brain vs. Brain (SDK 11.4.3: LDN, sockets).
Network requests stay on hold before they settle
- Symptom: the boot sits at a black loading screen for good, CPU nearly idle, every thread waiting, about 1 boot in 6 (3 in 4 with four Mac hosts at once); the title movie is never opened, and the save files are never probed.
- Cause: a race the host's instant offline answers lost. The game's boot state machine (
main+0x1f2f0e0, state atthis+0xd4, found by probing the save probe's call stack and dumping the state each tick) stayed in state 7, which polls the status word (+0x14) of the game's BCAT news runnable ("OlimarMemo") for 0, 4, 5, 6 or 7; it read 1. The runnable's constructor (main+0x203f3b0) clears the status, startsFBCATThreadand only then stores 1; the thread'sRun(main+0x203f510) stores 2, callsnn::nifm::SubmitNetworkRequest, sleeps whileIsNetworkRequestOnHold, and stores 6 whenIsNetworkAvailableis false. On a Switch the request stays on hold while the nifm service evaluates it, so the constructor's 1 always comes first; the host answered "not on hold" at once, and a thread that ran before the constructor's store finished at 6, which the 1 then overwrote. - Fix: the host now keeps a submitted request on hold for 50 ms, then settles it as unavailable, as Horizon's asynchronous request does (
hle_net.c). - Found on: Pikmin 4 (Unreal Engine 4.26.2).
- Check: 12 of 12 boots with four hosts at once reached the title.
- Note: how it was found: the hung boots' logs diverged from good ones at the save probe (
DM_FS=1), the thread dumps showed nothing but idle waits, andDM_PROBEon the state machine, thenpeeklines in a hung run'sDM_CTLfile, followed the pointers to the status word (a probe on the hot path slowed the boot enough to hide the race: 8 of 8 good).
Socket Poll and Select with nothing to wait on
- Symptom: one thread at 100% CPU from boot, with one SVC in 99 s (
NexusRevolution Socket,main+0x92c7c4, the game's network thread). - Cause: it calls
nn::socket::Poll(fds, 0, 33): no descriptors and a 33 ms timeout, which on hardware is a 33 ms sleep. The host failed every socket call at once, so the thread spun 11,000 times a second (88,000 hits of a probe in 8 s). - Fix:
PollandSelectwith nothing to wait on (no descriptors, or only negative ones, which POSIX ignores) now wait out their timeout and return 0, in every mangling the host takes over, including SDK 4'sSelect(int, fd_set*, …, timeval*); an endless timeout returns 0 after 100 ms, since a thread inside a host hook pauses for IL2CPP's collector only once the hook returns (hle_net.c,dm_guest_sleep_nsinhle_thread.c). - Found on: DARK SOULS: REMASTERED (SDK 4.5.0; one boot). Guest CPU fell from 1,030 to 60 ms per second.
Settings, languages and fonts
Language codes
- Symptom: the first comparison in
nnMainaborted insm. - Fix: hooked:
settings::LanguageCode::Makeand the reversedoperator==(LanguageCode, Language). - Found on: Super Mario 3D World + Bowser's Fury (SDK 10).
Available language codes and their count
- Symptom: abort in
sm::Initializeundernn::settings::GetAvailableLanguageCodes(early innnMain,main+0x327888); abort insm::Initializeundernn::settings::GetAvailableLanguageCodeCount()(main+0x117590). - Cause: Paper Mario asks for up to 32 codes and takes the index of
oe::GetDesiredLanguage's code in the list as its language. Catherine comparesLanguageCode::Make(0 … count − 1)withGetDesiredLanguage's code. - Fix: the host lists the system's codes in
nn::settings::Languageorder, the tableLanguageCode::Makeuses, and reports the count of that table (hle_sdk16.c). - Found on: Paper Mario: The Thousand-Year Door (SDK 15.3.2, the codes), Catherine: Full Body (SDK 8.2.1, the count; one boot).
Region code from the language
- Fix:
oe::GetRegionCodeanswers from the language: Japan, the Americas, Europe or Hong Kong / Taiwan / Korea. - Found on: Animal Crossing: New Horizons (SDK 20.5.4; found in
main's imports before the first boot).
Shared font address: BFTTF header or bare TTF
- Symptom: the font engine runs on garbage; crash after the shared fonts load, looking up the "?" glyph (
main+0x6c73c0, a font cache lookup returning null), with SDK 10'sScalableFontEngine::LoadFontfailing with error 403 andSetFontwith 301 on both stand-in fonts; or Japanese, Korean or Chinese text draws nothing in a Unity game while Latin text is fine (Patrick's Parabox's language menu showed three blank lines). - Cause: consumers read the shared font differently.
- The SDK's font engine calls
ScalableFontEngineHelper::Decode(ptr, size), which XOR-decodes in place unless the first word is the "already decoded" marker (LE0x18029a7f). So for a game that decodes it the system shared font must look like BFTTF: servemarker + bswap(size ^ 0x49621806) + TTF(hle_sdk16.c). Mario decodes the pointer and needs the 8-byte BFTTF header in front. - 3D World records shared fonts as type 0 (
main+0x975658), calls noScalableFontEngineHelper::Decodeand hands theGetSharedFontAddresspointer straight toLoadFont, so the pointer must be the plain TTF. SDK 10 and older games all take the plain TTF. - Unity's Switch player hands the shared fonts straight to FreeType. In Patrick's Parabox (SDK 13) the game's one font (Jost, a Unity dynamic font) falls back to the OS fonts for those glyphs, and the player loads them from the shared fonts:
main+0x2390b0maps the namesSwitch_Font_STD,Switch_Font_CN_Simplified,Switch_Font_CN_Extended,Switch_Font_CN_Traditional,Switch_Font_KRandSwitch_Font_NIN_Extendedto types 0–5 and handsGetSharedFontAddress/GetSharedFontSizestraight to FreeType'sFT_New_Memory_Face(main+0x23b2e0), as libnx homebrew does with the same pl offsets. The host gave every SDK 11+ game the 8-byte BFTTF header in front, so every face failed to open. - The SDK's
GetSharedFontAddressis the shared memory base plus the pl service's offset, with no adjustment, and that offset points past the header (yuzu's pl service: "font offset and size do not account for the header"); Captain Toad (SDK 8) records its shared fonts as plain TTFs and callsDecodeonly for its BFTTF formats. - A game on a newer SDK can import
Decode(and so get the header) yet hand the address straight toLoadFontthe same way (The Legend of Zelda: Echoes of Wisdom 1.1.0, SDK 20.5.1:main+0x8b37ccrashed at address 0x38).
- The SDK's font engine calls
- Fix: the host first decided by the SDK version alone (SDK 10 and older the TTF, SDK 11+ the header). Now the address is the header (marker form above) only for a game on SDK 11 or later whose
mainimportsnn::fontll::ScalableFontEngineHelper::Decode(Wonder, Breath of the Wild, Tears of the Kingdom, Pokémon Legends: Z-A); every other game gets the TTF itself (font_skipinhle_sdk16.c,dm_main_importsinloader.c; the profile keyshared_fontoverrides). The host'sLoadFonttakeover passes the TTF past the header whenever it is given the host's own BFTTF base, so the address formfont_skippicks only matters toDecodeand other readers (hle_sdk16.ch_font_LoadFont). - Check: 3D World's case was found by probing the engine's error code after each call (
DM_PROBEon the instruction afterGetError). - Note: of the games on hand, the ones that call
GetSharedFontAddresswithout importingDecodeare all Unity players (Engage, Ace Attorney Investigations Collection, Brain Memory Training Academy, Patrick's Parabox). To see which fonts a Unity game asks for, probe its name mapper's entry (DM_PROBE=main:2390b0on Patrick's Parabox 1.0.1; the function is the onemaincaller ofGetSharedFontLoadState). - Found on: Super Mario 3D World + Bowser's Fury (SDK 10, the plain TTF); Super Mario Bros. Wonder, The Legend of Zelda: Breath of the Wild, The Legend of Zelda: Tears of the Kingdom, Pokémon Legends: Z-A (the
Decodeimporters); Patrick's Parabox (the blank CJK text, 1.0.1); Captain Toad: Treasure Tracker (SDK 8, plain TTFs); Fire Emblem Engage, Ace Attorney Investigations Collection, Brain Memory Training Academy (the other Unity players); The Legend of Zelda: Echoes of Wisdom 1.1.0 (no profile).
Shared fonts requested all at once
- Symptom: abort in
nn::sm::Initializeundernn::pl::sharedresource::GetPlatformSharedResourceManager(first thing innnMain). - Cause: the game asks for every shared font at once with
pl::GetSharedFont(int *, SharedFontData *, int, LanguageCode)(sdk+0x40f270), not by type.SharedFontDatais 0x18 bytes {u32 type, address, size}; the SDK adds the pl service's offsets, which point past each font's 8-byte BFTTF header, to the shared memory base, and the game hands each address straight toScalableFontEngine::LoadFont(main+0x501ff0). - Fix: the hook returns the six types in order with the stand-in TTFs past the header, whatever the profile's
shared_font(hle_sdk16.c). - Found on: Rhythm Heaven Groove (SDK 21.4.0, one boot).
Shared font load request
- Fix:
pl::RequestSharedFontLoadis hooked (an SDK 20 call that reaches a service). - Found on: The Legend of Zelda: Breath of the Wild (1.9.0, SDK 20.5.22).
Nintendo-extension glyphs for button icons
- Symptom: controller-button icons in text show as
?. - Cause: those glyphs come from the system shared font's Nintendo-extension set (private-use code points; Mario's
GameMsg/Key.msbtuses U+E0E0–E0F0 for A/B/X/Y/L/R/ZL/ZR/SL/SR/D-pad/+/−, U+E0AB–E0AE for D-pad directions, U+E090–E097 for stick arrows, U+E100/E101 for sticks). - Fix:
dm/tools/make_ext_font.pygenerates a stand-innintendo_ext.ttfwith those icons; the host serves it for shared-font type 5 (DM_FONT_DIRorDM_SHARED_FONT_EXT) and a desktop font for the other types. - Note: to find which code points a title uses, scan the
TXT2sections of its MSBT files for U+E000–F8FF. - Found on: Super Mario Bros. Wonder.
Shared fonts on iOS
- Symptom: on a phone,
GetSharedFontLoadStatewould report "not loaded". - Cause: the macOS stand-in path (
/System/Library/Fonts/Supplemental/Arial Unicode.ttf) does not exist on a phone. - Fix: the iOS app ships
dm/font/standard.ttfanddm/font/nintendo_ext.ttf(copied / generated bybuild_ipa.sh) and setsDM_FONT_DIR. - Found on: every game on iOS (general).
C library, time and random numbers
Time zone rules, calendar conversion and the network clock
- Fix: hooked:
time::ToPosixTime(hostmktime), andtime::ToPosixTimewith an explicit rule (the host's zone, as forToCalendarTime);time::LoadTimeZoneRuleand theToCalendarTimeoverload with a rule (the host's zone is used);time::StandardNetworkSystemClock::IsAccuracySufficient(offline: no).time::GetDayOfWeekis a pure computation and needs nothing. - Found on: 13 Sentinels: Aegis Rim (SDK 11,
ToPosixTime); Monster Hunter Rise (SDK 11, the rest); Octopath Traveler 0 (GetDayOfWeek); Pokémon Legends: Z-A (19.3.11,ToPosixTimewith an explicit rule, found in the imports before it was met).
Steady clock, clock snapshots and user clocks
- Symptom: SDK calls that reach the time service, each aborting in
sm::Initialize(or found in the imports before they were met). - Fix:
time::StandardSteadyClock::GetCurrentTimePoint: host monotonic seconds, fixed source ID;GetSpanBetweenruns insdk(The Legend of Zelda: Breath of the Wild 1.9.0, SDK 20.5.22).time::ClockSnapshot::CreateWithStandardSystemClock: 0xD0 bytes, filled on the host from the steady clock, the user clock and the calendar; the network fields stay 0 (The Legend of Zelda: Tears of the Kingdom 1.4.3).- The adjustable user clock (
time::AdjustableUserSystemClock::GetCurrentTime, which the game's days follow) reads the time service's shared memory in the SDK (MonotonicSystemClock::GetCurrentTime,sdk+0x4421cc), which the host never maps, so it is the host's clock, as the user clock;time::GetStandardSteadyClockCurrentTimePoint, the free-function form of the steady clock (Animal Crossing: New Horizons SDK 20.5.4, found inmain's imports before the first boot).
- Found on: The Legend of Zelda: Breath of the Wild, The Legend of Zelda: Tears of the Kingdom, Animal Crossing: New Horizons.
clock_gettime(CLOCK_REALTIME) through the time source pointer
- Symptom:
std::terminate(Breakfromsdk+0x5bfa48) at the first real-time read (right after the first-boot dialog,main+0x3f3f68→system_clock::now()). - Cause:
clock_gettime(CLOCK_REALTIME)calls through a source pointer (sdk+0xb05010) thatnntimeStartupsets oncetime::Initializesucceeds: 1 =nn::time::GetStandardUserSystemClockCurrentTimespec, 2 = the adjustable clock's (sdk+0x8e414). Both asked the time service. - Fix: they are host take-overs now (
hle_time.c), andsystem_clock::now()works on any build. - Found on: Ace Attorney Investigations Collection (1.0.0.2, SDK 17.5.3).
Guest clock reads wait while the app is in the background
- Symptom: after switching apps and back, one guest thread at 100% in a tight loop (
main+0x9abd0,x10 = 0); the game freezes, picture still, sound on. Mac reproDM_OE_SCRIPT=bg=1@20,bg=0@25. - Cause: the frame pacing measured a frame period of 0 (
main+0x5926a8: the clock difference between two frames). The host freezes the guest clock in the background but its threads went on, so two frames read the same time. - Fix: guest reads of the system counter (the
cntpctgate,svc GetSystemTick) now wait while the app is in the background (hle_os.cguest_clock_read_wait); host work and the keyboard freeze (The software keyboard blocks only its caller) keep the frozen clock. - Found on: Super Mario Galaxy 2 (one of two causes; the other is Focus handling mode decides whether focus changes are notified).
Cryptographically random bytes
- Symptom: abort in
sm::Initializeundernn::crypto::GenerateCryptographicallyRandomBytes(Unicorn Overlord:std::random_device→ thesdk's owngetentropy→InitializeCsrng; Z-A: the first call innnMain,main+0x1734b4). - Cause: the bytes come from the csrng service. The
sdkdefinesgetentropy, so the host'sgetentropyhook never sees the call, but the call intonn::cryptogoes through thesdk's PLT. - Fix: an
HLE_FORCEon it fills from the host's entropy stream, the same stream asgetentropy(hle_os.c). - Found on: Unicorn Overlord (SDK 16.2.4; one boot), Pokémon Legends: Z-A (19.3.11; one boot).
Modules and symbols
Global symbol resolution in load order
- Symptom: a library's
freerejects a pointer (CentralHeap::Free→NotNMallocPtrError). - Cause: the game's
maindefines its ownoperator new/delete(Unity's memory manager) andsdkhas a copy forwarding tomalloc/free. - Fix: symbols are resolved globally in load order (the first module's definition wins, also for a module's references to its own exports); only local / non-default-visibility symbols bind to the module itself (
loader.c). The loader logs how many references bind to an earlier module.
Undefined weak imports bind to 0
- Symptom: a weakly imported function the game calls although nothing defines it: an
UNIMPLline, then an abort. - Cause: the loader bound every undefined import to a named stub, so the address test the compiler emits for a weak symbol saw a function.
- Fix: an undefined weak symbol that no module defines and the host does not implement is now 0, as Horizon's rtld binds it (
loader.c, logged asundefined weak … bound to 0). - Found on: Octopath Traveler 0 (the
thread_localinit function_ZTHN13LowLevelTasks5FTask10ActiveTaskE).
nn::ro and its port
- Symptom: crash in
nn::ro::InitializeWithPortName. - Cause:
nn::roconnects toldr:ro. - Fix: take over
Initializeand answerLookupSymbolfrom the loaded modules (hle_ro.c). Check the mangled names against the new title's imports: SDK 19'sLoadModuletakes one more bool (_ZN2nn2ro10LoadModuleEPNS0_6ModuleEPKvPvmib), so the hook for the older form did not catch it and the SDK's own code would have run. - Found on: Hades II (SDK 19's
LoadModule).
ro::ProtectRelro runs natively
nn::ro::ProtectRelro(called frommain's DSO init), a new import of SDK 20, needs nothing: it runs natively on the host's no-opSetMemoryPermission/SetMemoryAttribute. Found on The Legend of Zelda: Breath of the Wild (1.9.0, SDK 20.5.22).
Loading NROs with ro::LoadModule
- Symptom: the frame loop runs, but the game waits for code it loads itself (
ro::RegisterModuleInfo/ro::GetBufferSizered lines;mainimports 1,045lua2cpp::create_agent_*functions, global, that no ExeFS module defines, bound to stubs at start). - Cause: the fighters' code is 98 NROs inside
data.arc, registered by.nrr/cross2_*.nrr. The SDK's ownLoadModule(SDK 8.2.1LoadModuleInternal,sdk+0x347460) uses the service only to map the image, then zeroes bss, relocates through rtld's module lists, calls the module's init and binds the variables of the modules loaded before; the host replaces rtld, so it takes over the whole call. - Fix: the NROs are collected at pack time by the SHA-256s the NRRs list (
dm/tools/nro_collect.py: loose files, the current files of an indexed archive, or a scan for stored and zstd-framed NROs; Smash's NRRs of older releases also register 666 superseded copies, whichdata.arcstill holds and which are left out), rewritten like any module (patch_nso.pyreads NROs: the flat image, segments at +0x20, build ID at +0x40) and listed in<patched dir>/nro.tsv. The host maps them all at start and keeps them out of symbol lookups;LoadModulefinds one by the image's SHA-256, restores its data and bss (after the first load), relocates it against the modules loaded then in load order, binds the earlier modules' symbol relocations it defines (main'slua2cppstubs), fillsnn::ro::Moduleas the SDK does (+0 module, +8 state 1, +0x10 image, +0x18 buffer) and calls itsDT_INIT;UnloadModulecallsDT_FINIand binds every relocation that pointed into it again (loader.c"NRO modules",hle_ro.c).GetBufferSizereturns the bss size from header +0x38 as the SDK does. The startup init pass must skip NROs (their init ran unrelocated and crashed). Only data is written at a load, so it works with code embedded in the iOS executable:gen_embed.pypacks the NROs into three sections (__dm_nro_x0,__DM_NRO_W,__dm_nro_x1) and a table of their pieces (__dm_nro_tab), since a Mach-O file has at most 255 sections and three per NRO would pass it, andmap_imagemaps an NRO from there (loader.cnro_pieces). - Found on: Super Smash Bros. Ultimate (13.0.5): it loads six NROs in its first 30 s and calls none of the stubs. Played on an iPhone.
Online SDKs in NROs are never loaded
- Symptom:
[EOS] Failed to create platform(the game plays offline);refused …: an online SDK (Epic Online Services), not loaded;refused an NRR that registers only online SDKs (1 hashes). - Cause: Epic Online Services in an NRO (
rom:/nro/EOSSDK-Switch-Shipping.nro, loaded withro::LoadModule;mainimports theEOS_*functions). Before the host loaded NROs, the imports stayedUNIMPLstubs andEOS_Platform_Createhanded back a garbage handle; the game checks it and plays offline. - Fix: the host now loads NROs (Loading NROs with
ro::LoadModule), but never an online SDK, whatever the game: an NRO that exportsEOS_Platform_Createis left out of the pack (dm/tools/nro_collect.pyONLINE_SDKS, arefusedrow innro.tsv) andro::LoadModulerefuses it with one log line (refused …: an online SDK (Epic Online Services), not loaded), also by the export when an oldernro.tsvhas it. An NRR that registers only such modules failsRegisterModuleInfo, as every registration did before the host loaded NROs, so the game takes the path it took then. So both games behave as before: offline, local saves. - Found on: Absolum (SDK 18.3.2, one boot each); Hades: it never asks for the module (checked on 1.0.38233:
refused an NRR that registers only online SDKs (1 hashes), noLoadModule, its frames as before). Hades'mainimports only EOS Auth, Connect and PlayerDataStorage, Epic-account cross-save the host cannot serve; the real SDK would at best fail its login and at worst wait on time-outs or contact Epic's servers, with an unknown risk to the player's account. - Note: open question for the maintainer: always load, refuse online SDKs (as now), or decide per profile.
GPU driver modules that are never loaded
- Symptom:
mainimports EGL (SDL3's GL support) andvkGetInstanceProcAddr(its Vulkan support), but the game renders with NVN (FNA3D_Driver_NVN.cpp). - Cause: NVIDIA's OpenGL and Vulkan drivers (
subsdk1,subsdk0) program the GPU through NvRm, which the host does not have. - Fix: they are never loaded (
profile_guess.pyGPU_DRIVERS); the EGL imports stay unbound stubs that nothing calls. - Found on: Absolum (FNA3D).
IL2CPP common string table intact
- Not needed in the end: the tolerant common string table of pull request #15. Ace Attorney Investigations Collection 1.0.0.2's
mainhas the table intact atmain+0x46169ac, 109 entries ending atHash128.
Code inherited from another title at fixed addresses
- Symptom: harmless on the build it was written for, silent memory corruption on any other.
- Cause: code that calls fixed module addresses. d2r-mobile's 3D render scale read a renderer pointer at
main+0x31e0848and calledmain+0x8582f0; in Mario v0 that is the middle of a static initializer and only did nothing because the pointer happened to read 0. - Fix: such code checks
module_t.build_idnow (written intofixups.tsvby the rewriter) and stays off on other builds. - Found on: Super Mario Bros. Wonder (v0, code inherited from d2r-mobile).