Repository navigation
Conversation
added 30 commits
October 12, 2026 22:04
Generic host, gfx and tools taken from d2r-mobile (MIT), with the D2R gameplay, diagnostics and Blizzard/Granny/movie layers removed. - hle_hid.c: generic standard-controller layer (all Npad styles) - hle_sdk16.c: newer-SDK oe/time/pl/vsync/diag hooks - hle_offline.c: offline stubs for prepo/ldn/socket/ssl/nifm/nsd/friends/ngc/err - hle_fs.c, hle_account.c: SDK 16 overloads - loader.c: unreferenced forced hooks only logged (DM_FORCE_STRICT=1 aborts) - NVN: API 55.15, 13 newer names, ~45 state-only functions, tex_t packed to 0x80 (Mario allocates NVNtexture as 0x80; 0x88 corrupted the heap) Frontier: null read at main+0x64763c inside the frame loop.
- hle_svc.c: guest AS 6 GB with a 1.25 GB alias region (newer SDK's AddressRegionManager always takes ~1 GB of it as its node area), MapPhysicalMemory rounded to 16 KB host pages, GetInfo(UserExceptionContext) backed by a real buffer, program id now SMBW (DM_PROGRAM_ID overrides) - hle_audren.c (new): silent AudioRenderer, event fired every 5 ms - hle_fs.c: SetPriorityOnCurrentThread / IsEnabledAccessLog (fsp-srv) - hle_sdk16.c: settings language comparison; diag aborts report the call site and backtrace - NVN: nvnProgramSetShadersExt (0x18-byte stage entries) The game now runs ~40 threads; the main thread waits on job events while render commands (CallCommands, BindTexture, BindVertexBuffer, DrawElements, WriteTexels) are still unimplemented.
- BindTexture (combined handles go straight into the texture slot; the replay already splits texture / sampler ids), BindImage, BindTexturesDeferred, BindVertexBuffer, CallCommands - DrawElements / BaseVertex / ArraysInstanced; viewport, scissors and depth ranges in single / multi forms - ReportValue via the event-write record; new OP_CLEAR_BUF record with in-order replay (fillBuffer, or a pattern buffer for non-uniform values) - TextureWriteTexels: deswizzle level, patch region, swizzle back, after lifting write protection - Command memory: when the game's callback cannot supply enough, continue in a host segment instead of aborting - Remaining state the replay lacks (conditional render, depth clamp, rasterizer discard, ...) is logged once and drawing continues The game now runs its frame without errors from these; the main thread waits in the job system's fork-join (executors keep polling).
- patch_nso.py: fix exclusive-window exit order. The fall-through stub must come right after the copied window; it was emitted last, so a successful stxr in 'ldxr; cmp; b.ne out; stxr; cbnz' fell into the branch-out stub (main 0x81c40: job-system enqueue retried forever, tail filled to capacity with no task stored). Bug inherited from d2r-mobile, latent there (no branch-out windows). - patch_nso.py: rewrite 'mrs xN, ctr_el0' (trapped on macOS) to a constant with 64-byte data cache lines. - hle_thread.c: SetThreadCoreMask moves the ideal core into the new mask (Horizon behavior), so pinned threads report the right core. Frames 2 and 3 now record, submit and present (~1,900 NVN calls). Next: Mario's shaders (bfsha) are not in the translation map yet.
Shaders - shader_split.py (new): scan RomFS and main for every BNSH and agl BAHS container (bfsha, particle effects, layouts, agl/gsys packs, main): 12,811 shaders, map + compute facts from the control section - key = sha256(control + code): many shaders share a control section - NVN 55 binding layout: bound textures at 0x20 + 8*slot, images 0x120 - bindless textures read from uniform buffers (tex_ubo / samp_ubo) - GL depth mode: translator remaps z' = z/2 + w/2 in vertex shaders - compute code areas carry a 0x30 header (magic 0x12345679) Replay - 8-bit index buffers widened to 16-bit - texture swizzle through Metal swizzled views - non-packaged textures shared by storage (fixes a ~13 MB/frame leak from transient render targets rebuilt every frame) - view_t packed to 16 bytes (SDK 16 NVNtextureView; fixes a stack smash) - TBDR optimizations off by default until they know bindless readers Host - alias region 2.875 GB for the 1.75 GB VirtualAddressMemory reservation - ideal core -2 resolves to the process default core - shared font served with a BFTTF "decoded" header - crash reports scan the stack and dump the popped frame Docs: docs/agents (guides for porting other titles), AGENTS.md
- DM_KEYS scripted controller input ("seconds:button[+button][~hold]")
- NVN virtual memory pools aliased onto their physical pool when every
MapVirtual request shares one base (Mario's 128 MB pool); previously
MapVirtual failed and the game treated the pool as unusable
- DM_PROBE ported from d2r-mobile (host/probe.c): brk probes with
register / backtrace output, plus DM_PROBE_X2 object dumps; used
because lldb launches now wait on a macOS authorization prompt
- diagnostics: DM_NVN_VLOG (pools, MapVirtual), DM_NVN_CBLOG (command
memory grants and callbacks), DM_NVN_ALLOCPROF
- docs: new failure patterns and the probe recipe
Open: starting a new game crashes while loading the opening course
(model resource heap overflow for NpcCommonerA.bfres).
…PU-written text - patch_nso.py: Mario main cut gives every resource heap +16 KB; Model/NpcCommonerA.bfres came up 0x70 bytes short of its size-table budget while loading Course900 - dmsh_build.py: compute instructions start at code area +0x100, not +0x30; the misaligned decode turned 12 particle / post-effect compute shaders into endless loops (GPU hang) - Linear textures in CPU-visible pools are built on the pool buffer, so the CPU-rasterized glyph cache reaches the GPU (menu and dialogue text) - Host command-memory segments grow on demand; RGB10A2SN vertex format; instanced DrawArrays - DM_PROBE: thread IDs, register-relative dumps, hit filters, ExpHeap walker - dm/tools/ghidra/Decompile.java; agent guides and README updated
…y colour ramps - dmsh_build.py: vertex shaders whose inputs are only used as integer bit casts are re-translated with uint inputs (529 shaders); bone indices were converted to floats and every character was stretched off screen - Host: Metal vertex formats follow the shader input type (signedness swap, 32-bit float reinterpretation), mismatches reported - LINEAR_RENDER_TARGET textures in CPU-visible pools are zero-copy too (sky colour ramps) - Pass dump lists all vertex attributes; docs and README updated
- dm/ios: lean app shell (main.m boot/env/log, front_ios.m front.h + 16:9 Metal view, DmGamepad.m touch gamepad in Nintendo positions + GameController), xcodegen project, tools/build_ipa.sh (embedded game code, iOS shader libraries, RomFS in the bundle) - Guest address space back to 5 GB (heap 1.625 GB; the game asks for 1.47 GB) - README / agent guide: iOS build steps and address-space sizing
…nt, ordered copies - Audio: run the sdk's own audio renderer on the CPU in manual mode (hle_audren.c), feed its DeviceSink output to the AudioOut sink; catch-up render on back-to-back updates fixes the ApplyLightLimiterEffect crash at the title screen - Button icons: make_ext_font.py generates a stand-in Nintendo extension font (A/B/X/Y, L/R/ZL/ZR/SL/SR, +/-, D-pad, stick arrows); served as shared font type 5; the iOS bundle now carries both shared fonts - Fountain panels: Metal 4 does not order copies inside one encoder; add barriers when ranges overlap, so the GPU wave simulation no longer reads a stale uniform - Zero-fill new colour textures; scissored zero clears; uniform-update snapshots - Docs: graphics ordering, audio, diagnostics, intermittent-glitch method
Ranges are rounded to 16 KB host pages but NVN pools are only 4 KB aligned, so adjacent pools share their first / last page and one address can fall in several registered ranges. Every lookup took the first range containing the address: - textures whose pool's first page is shared with a smaller preceding pool were judged untracked and re-uploaded on every use (two ASTC 6x5 textures in the opening course: 12 re-uploads, about 6 MB, per frame); - a fault or host pre-write on a shared page released only one range, leaving the other marked protected; - destroying a pool could unregister its neighbour (same rounded base). Tracked / protect / since now use the range that holds the whole span; faults and host pre-writes release every range covering the page; pool removal matches the exact pool start. Opening course: 3600 re-uploads per 300 frames -> 0; DM_NVN_DIRTYCHECK=1 over 3900 frames: 0 misses. t_dirty_slots covers the shared-page case (3 checks fail on the old code).
A sideloaded app cannot be given environment variables from outside. main.m now reads KEY=VALUE lines from Documents/env.txt and from dm/env.txt in the bundle (Documents wins, real environment wins over both) and logs each applied line as "boot: 环境 ...". build_ipa.sh copies DM_ENV_FILE=<file> into the bundle as dm/env.txt.
dmsh_chain.py --update-lock on a fresh Mac (Xcode 27.0, metal 32023.921 unchanged): the translator DLLs built here and Homebrew's spirv-cross have different fingerprints. Chain b1a4876b9511; dmsh_build gives the same result as documented (12809 / 12811 compile, the same 2 agl compute shaders make SPIRV-Cross throw) and c1_full.tsv lists the same 11 shaders. Another machine with other tool builds needs --update-lock + --header.
Opening course on an iPhone 18 Pro Max (A20 Pro GPU): black diagonal band, black-edged white glow at the horizon, grey striped blocks, alternating frame by frame. The Mac (M4 Pro) never shows it, not even with the iOS metallibs, display pacing, -Os, DM_3D_SCALE=0.75 or the no-BC path. On-device colour-target stats (DM_DBG_TEXSTAT, diagnostic commit that follows): even frames' main 1920x1080 RG11B10 target has 54 NaN pixels (Mac: 52); odd frames 727k NaN, and the 960x540 / 1100x280 / 275x70 targets downsampled from it carry it on. The NaN is already in the main target before the first downsample (pass 47 of 80), so it comes from the scene passes, not the post-effect chain. The translated shaders reproduce Maxwell's NaN rules with select(x, 0, isnan(x)) (8337 / 12809 shaders). Default fast math records air.compile.fast_math_enable, which lets each GPU's backend compiler assume no NaN / Inf; relaxed keeps the reassociation / contraction but preserves NaN / Inf. Hypothesis only: chain 1b46c9a2e920 (MSL byte-identical to b1a4876b9511; c1_full.tsv lists the same 11 shaders) still has to be run on the iPhone. If the NaN persists, the next step is finding the producing draw in passes 12-45.
Temporary instrumentation for the A20 Pro NaN investigation; every line is tagged [DEBUG-flk1] so it can be removed with one grep. - DM_DBG_TEXSTAT=N with DM_NVN_TEXDUMP=<frame>:<path>: for N consecutive frames, log one line per colour target instead of writing PNGs: per-channel mean, NaN / Inf counts, largest finite value, black share. - Inside the DM_NVN_PASSDUMP window: each submission's commit->completion time and the counter values written back (timestamps / samples passed). - DM_NVN_SHOT accepts up to 4096 frames (was 16). Device capture used so far (unsigned IPA, DM_ENV_FILE for build_ipa.sh): DM_NVN_TEXDUMP=3600:td_%d.png DM_DBG_TEXSTAT=2 DM_NVN_PASSDUMP=3600-3601 DM_NVN_SHOT=3600,3601,3602,3603:shot_%d.png DM_NVN_SHOT_OUT=960x540
Fixes the flicker / black band in the opening course and the crash after the first scene on the iPhone (A20 Pro), confirmed on the device. - nvn_obj.h: cmd_t leaves byte +0x10 of NVNcommandBuffer to the game. Mario's inlined NVN code writes 1 there (main+0x4da5c, main+0xb8dec). It used to hit the low byte of `end`: the game's 512-byte material setup blocks shrank and overflowed into host segments, GetCommandMemoryUsed (cur - begin) returned 0, the game marked the setups empty and skipped calling them, and draws ran with the previous vertex buffers (out-of-range vertices, NaN). - hle_nvn.c: GetCommandMemoryUsed counts bytes across host segments plus 16 for the jump. Host segments are released only when the game re-grants memory overlapping the recording's guest bytes (the game keeps calling recorded setup blocks for many frames); the 8-frame recycling is gone. - nvn_mtl.m: failed pipelines are cached and reported once with their vertex / fragment keys instead of being rebuilt on every draw. - front_ios.m / main.m: the audio session asks for a 5 ms I/O buffer at boot and on return to the foreground. The default (~21 ms, 1024 frames per callback) outran the 4 x 5 ms the renderer keeps queued: every callback underran (crackling). Device: 240-frame callbacks, 0 underruns. Still contains [DEBUG-flk1] instrumentation (tagged, mostly behind DM_DBG_* switches; audio statistics every 2000 callbacks in hle_audio.c). To be removed with one grep.
Metal builds a pipeline only when every pixel shader input is a vertex shader output; Maxwell gives an unwritten attribute a default. With shaders translated one at a time, 331 of v0's 9,043 vertex / pixel pairs failed to link and later levels lost those draws (about 11,500 failed pipeline builds in one device session). - shader_split.py writes shader_pairs.tsv (pairs of every BNSH variation) and takes comma-separated RomFS / main lists for several game versions, writing their union (keys are content hashes; v0 rows keep their package indices). - dmsh_build.py vs_pad(): vertex shaders get the outputs their partners read but they don't write, float4 (0, 0, 0, 1); vertex shaders with partners are finished last and their partner list is part of the cache rule. shader_pairs.tsv is part of the chain version. - Tables now cover v0 and update 1.2.1: 16,268 shaders (1.2.1 recompiled 3,451 SecredUber pixel shaders and added 6 effect shaders), 14,603 pairs. Chain 5f1ec82dfcf0: 16,266 compile (the 2 known SPIRV-Cross failures); an offline link test of all pairs gives 14,603 / 0 (v0 before the padding: 8,712 / 331). c1_full.tsv regenerated (same 11 keys). The v0 IPA with this padding played 11 minutes on the iPhone with no pipeline failures.
…calls - dm/tools/nx_extract (C#, LibHac 0.19): hactool stops with "Invalid BKTR layout!" on the update's patch NCA. nx_extract reads the NCAs straight from both NSPs, imports their common tickets, merges the update's RomFS over the base game, prints the display version and module build IDs, and writes files identical to a base RomFS as APFS clones (the merged 1.2.1 RomFS adds about 114 MB). - dm/tools/nso_fnmap.py: carries offsets between builds by matching eh_frame functions with address bits masked; globals through their adrp references. - patch_nso.py: resource heap +16 KB cut for 1.2.1 main ff773e90... (main+0x1cf9b0, mapped from v0 0x1e62fc; the function is identical after masking). fixups.tsv now records the module build ID; the loader keeps it in module_t.build_id. - hle_oe.c: the 3D render scale inherited from d2r-mobile writes and calls fixed main addresses (0x31e0848, 0x8582f0). In Mario v0 0x8582f0 is the middle of a static initializer; it only did nothing because the pointer read 0. Now enabled only on D2R's build. - hle_misc.c: owned add-on content defaults to none except on D2R's build (DM_AOC_OWNED overrides). - New imports in 1.2.1 answered as a Switch 1 without amiibo: nn::nfp (one NFC device on the connected controller, detection never finds a tag; nfp::Initialize at boot used to abort in nn::sm), pl::IsRunningOnOunce (false), ec::ShowShopApplicationInformation- ForUpgrade (no-op), account::PopOpenUsers (none: the game falls back to TryOpenPreselectedUser as in v0). oe::GetDisplayVersion by build (1.0.0 / 1.2.1). Mac: 1.2.1 and v0 both run the scripted opening for 120 s with only the known SetRenderEnableConditional red line. iPhone: 1.2.1 played 8.5 minutes with no crash.
- README: status for v0 / 1.2.1 on the iPhone, update 1.2.1 build steps, diagnostic env files, finishing the IPA by hand when Xcode uses a custom build location, app data folders after a reinstall, prewarm lists (pso_user.tsv / pso_manifest.tsv). - AGENTS.md: per-build knowledge keyed by build ID, shader_pairs.tsv on the chain, unsigned IPAs only. - docs/agents: update extraction and offset porting (01), the command-buffer byte, command memory, iOS audio, update imports and build-gated D2R code (03), vertex output padding and iOS math mode (04), on-device diagnostics and the offline link check (05). - dm/gfx README / nvn_metal_mapping (en + zh-CN): pairs table, several game versions, prewarm lists, DM_PSO_USER, command buffer notes; corrected shader key, build script and 8-bit index lines. - dm/host/README(.zh-CN).md removed: an index of d2r-mobile's D2R features; most files it listed no longer exist.
- DmGamepad.m: layout after a PS remote-play pad. ZL above L and ZR above R (teardrops) in the top corners; -/+ in the bottom corners with the D-pad (four arrow pads) and the A/B/X/Y diamond just above them; left and right sticks next to those along the bottom edge. Everything hugs the edges to leave the picture room. Layout constants are at the top of the file. - Sticks are fixed rings (tilt from the ring centre, 0.12 R dead zone) instead of the floating left stick; the new right stick merges with a physical pad's right stick the same way as the left one. - One CAShapeLayer per control instead of a full-screen drawRect: a press swaps the fill, a stick move only moves its knob. Colours carry low alpha (fill 0.14, outline 0.38, labels 0.6) rather than a view alpha, which would add an off-screen pass. - front_ios.m header and the README app-shell line follow.
- dm/ios/tools/build_ipa_from_nsp.py (Spyder or python3): picks the base game and update 1.2.1 by title ID (DLC is skipped), extracts them with nx_extract, checks the main / sdk build IDs, rewrites the modules, splits the shaders and requires the tables to match the repo's byte for byte, builds the iOS shader libraries, then runs build_ipa.sh. It uses only the repo, the NSP folder and prod.keys: intermediates go to <NSP folder>/smbw_build/ (full step logs in logs/), and the extracted game files are deleted after success. Tested end to end: first run 33 min, re-run 10 min. - dm/gfx/tools/dmsh_xlate_bin: the translator build that matches dmsh_chain.lock. A fresh dotnet publish gives different dmsh_xlate and Ryujinx dll fingerprints, which dmsh_build refuses. - build_ipa.sh: when Xcode's Locations setting is a custom location, take the app path from the build log instead of stopping at "no built app". - README: the script; the custom-location note replaces the by-hand finish.
- nx_extract --manifest: write ExeFS and romfs_manifest.tsv (path, size, first 32 bytes of every file) instead of the RomFS, so a large title can be surveyed without writing its game files. - nx_extract prints the build ID of every NSO module, the display version and the game name, and saves the raw control.nacp next to the output for naming the app. - Game name: newer SDKs compress the NACP title area (u16 size + raw deflate, inflating to 32 languages x 0x300; Mario 1.2.1 does this). LibHac 0.19 does not decode it, so nx_extract does. - nso_syms.py: '-' for the reference game compares one game's imports with the host hooks only.
- dm/games/<id>/game.tsv holds what is specific to one game: title and program ID, known builds (module build IDs, display versions), package and shader versions, code modules, RomFS marker, zip store list, memory split, NVN driver constants, shader split groups, app name / bundle ID / IPA name. Mario's profile is smbw, the default. - The shader tables, c1_full.tsv, pso_manifest.tsv, bf_want.tsv and dmsh_chain.h move from dm/gfx/nvn to dm/games/smbw. - dm/games/game.py loads profiles (DM_GAME, by title ID), answers shell queries and generates the host's game_profile.h. - Shader tools (dmsh_chain, dmsh_build, dmsh_facts, shader_split, tbdr_budget) take tables and constants from the profile. dmsh_build stops if the profile's texture / sampler bases differ from the translator's built-in ones. dmsh_chain hashes the profile's nvn_* / shader_* keys; the chain version is now 02ed42db694f. All 16,266 translated shaders are byte-identical to 5f1ec82dfcf0's, and c1_full.tsv differs only in its header. - Host: GAME ?= smbw in the Makefile; memory split, display versions, NVN bases, default modules, window title and the shader map path come from the profile. D2R-only code (Settings.json edits, the main+0x3509a0 read) runs only on D2R's build, which also stops a stray Settings.json in Mario's iOS save. - iOS: build_ipa.sh, gen_build.py, project.yml and main.m take the app name, bundle ID, category, modules, RomFS marker and tables from the profile (GAME=<id>, workspace ~/<id>-work). - dm/games/README(.zh-CN).md: the keys and how to add a game.
- The game comes from the NSPs' title ID and its profile in dm/games/; the versions to extract, the accepted build IDs, the modules to rewrite and the tables to compare all come from the profile. TITLE / BUILDS are gone from the script. Work folder: <NSP folder>/<id>_build/. - A title with no profile is surveyed instead: <NSP folder>/<title ID>_survey/survey.md lists the NSPs, modules and build IDs, SDK version and components, Unity version, engine / API markers, RomFS folders, magics and extensions, insn_census counts, imports vs host hooks, and notes. Fire Emblem Engage was surveyed this way. - APP_NAME setting; empty uses the game name from the base NSP's NACP (compressed title areas decoded), then the profile's app_name. - PACKAGE and OUT_IPA default to the profile's package and ipa. - Tested on Mario end to end from fresh extraction: the IPA is named "Super Mario Bros. Wonder" and carries the current chain's tables.
- README: presented as Switch games running natively on Apple platforms, with Mario as the one supported game. A games table (Mario playable, Fire Emblem Engage surveyed), component status, game profiles and how to add a game, the NSP script, generic step-by-step build commands (profile, workspace and version as variables), and a Mario section for the per-game notes (1.2.1). - AGENTS.md: profiles, DM_GAME / GAME, workspaces, build-ID gating, the chain rule per profile. - docs/agents: one repository with one profile per title; survey mode in 02-survey, including the compressed NACP title area. - dm/gfx README (+ zh-CN): table paths in the profile.
- hle_nvn.c: drop the command memory callback / grant / caller logging, the overflow details, the +0x10 write log, the one-off NVN table dump, and the DM_DBG_GRANTS, DM_DBG_CALLSITES, DM_DBG_CMDMEM_SHRINK and DM_DBG_OLD_USED switches. - nvn_mtl.m: drop the per-draw NaN probe (DM_DBG_NANPROBE), the colour target statistics (DM_DBG_TEXSTAT) and the PASSDUMP completion timing. - Kept as regular diagnostics: the audio callback line every 2000 callbacks (frames per callback, queued blocks, underruns) and DM_DBG_AU_FRAMES (docs/agents/03-bringup.md, crackling on iOS); DM_NVN_SHOT up to 4096 frames (the 05-debugging burst recipe). - Mac 1.2.1 scripted run reaches the opening course with the same single red line as before.
- attach(): a render-target view that changes the format is attached as a Metal view of that format (kept until the submission completes), so pipeline formats follow it. Mario's last pass writes the RGBA8 swapchain texture through an RGBA8_SRGB view; with the base texture attached, linear values were stored unencoded and the whole picture was darker: on the Mac and the iPhone every channel equalled the Switch value decoded from sRGB once more (0.55 -> 0.26). After the fix the Mac title frame matches a Switch screenshot (mean RGB 0.556 / 0.621 / 0.633 vs 0.557 / 0.621 / 0.633). - present_raw_src(): an sRGB-typed swapchain texture is sampled through a non-sRGB view, so the stored bytes reach the screen as on the Switch's scan-out (no change for Mario, whose swapchain is RGBA8). t_present_crop gets an sRGB case (34/34; fails without the fix). - docs/agents/04-graphics.md and the mapping docs: the rule and how it was measured.
- patch_nso.py: three more x18 forms from IL2CPP code: an offset jump table (targets read at rewrite time), blr x18 (original return address kept for C++ unwinding) and an sp write from x18 (alloca). Mario's rewrite output stays byte-identical. - loader.c: symbols resolve globally in load order, also for a module's references to its own exports (local / non-default visibility excepted). Engage's main overrides operator new / delete; sdk bound its own copies and freed Unity's blocks into the SDK heap. - hle_svc.c: QueryMemory reports each module's Horizon layout (code, read-only, data, patch area) and other host memory as free, which the SDK's C++ unwinder (nn::ro::detail::GetExceptionInfo) needs. - New takeovers: nn::fs::IsMounted, nn::ro (hle_ro.c), prepo Initialize, SDK 12 audio device names and OpenDefaultAudioOut, an SDK 12 hid block (single GetNpadState, modes, touch, gestures, six-axis details, colors, applets), movie::Decoder with no impl (hle_movie.c) and failing nvdrv opens (hle_nvdrv.c), and NVN getters / no-ops Engage calls. nvn.h: TextureGetMemoryPool returns a pointer. - Makefile: a game stamp, so changing GAME rebuilds the host. - docs/agents/03-bringup.md: catalogue entries for all of the above. - Mario 1.2.1 scripted run still reaches the opening course with the same single red line.
- dm/games/fe_engage: identity and the 2.0.0 build IDs (main, subsdk0, sdk), modules in Horizon load order, RomFS marker, empty shader tables and their chain header. Memory: an 8 GB reservation with a 5.5 GB alias region for its virtual address memory (Mac only for now: iOS allows at most 6 GB without the extended-addressing entitlement); NVN constants are Mario's until measured. - On the Mac the host boots Engage through Unity's startup and runs to the opening movie with nothing on screen yet: its shaders are in UnityFS bundles that shader_split.py cannot read, and movies are not decoded. - README: the games table row.
- New dm/gfx/tools/unityfs.py: UnityFS bundles (LZ4 / LZMA blocks), serialized files (formats 19..22), objects read by their type trees (Unity's common string table comes from the game's own main), and a Shader object's per-platform compressed subprograms. The player's own files have no type trees and borrow the bundles' Shader tree of the same type hash. - shader_split.py reads Unity Shader objects in a second pass: each Switch subprogram is one NVN program (0x60-byte header of per-stage offset / control size / code size, then control + code per stage); a program's vertex / pixel stages form a pair. Engage: 9,269 shaders, 4,634 pairs. Mario's tables and package are byte-identical. - Engage profile: shader groups engine / assets, measured NVN layout (texture handles 0x168 + 8 r, 128 slots; separate samplers 0x568), regenerated tables, chain a295a8035b7b. 9,268 of 9,269 compile (one geometry shader; Metal has none). - dmsh_build.py: the translator's built-in bases only matter with per-slot facts, so the guard checks that; c2 reads count the sampler table and 16-byte rows overlapping a table as fillable. - game.py: DM_GAME_NVN_SAMP_BASE. NSP script survey note updated. - Mario chain is now 271056163ea2 (Mac and iOS libraries rebuilt, 16,266 of 16,268 as before; c1_full regenerated).
- The idle timer is off while the app is active: only touches reset it, so with a controller or in a cutscene the screen dimmed and locked. - Open file limit raised from 256 to 4096 at host start; Documents/romfs and temp are kept out of device backups. - Audio: observers for audio session interruptions (Siri, alarms, calls) and media services resets (dm_audio_reset recreates the AudioUnits); becoming active ends an interruption whose "ended" never came. - A watchdog switches a started output whose device has not called back for 200 ms to the silent sink and retries the AudioUnit after 1, 2, 4, 8 s; a start that fails falls back the same way instead of aborting. t_audio scenario 3 stops a playing AudioUnit behind the host's back. - GetCurrentProcessorNumber: a thread with no ideal core reports the lowest core of its mask, not core 0.
- patch_nso.py FRAME_STEP_BUILD (main 277178b7, 1.4.3): stubs step the game by the measured frame time instead of its own interval (2 = 30 fps), capped at the target (30 while a prerendered movie plays), with game time that ran ahead paid back by the next frames. Also the two readers of the fps, the D-pad menu fade and the swapchain argument. Sites from masagrator's FPSLocker config for this build; the pay-back from OJZen/totk-mobile; stubs written and assembled here. - State page after the key page (fixups key FPS_STATE_OFF): the loader writes the target (profile key fps_target, DM_FPS_TARGET; 0 = every stub runs the original instructions) and the render scale (DM_RENDER_SCALE 0.5-1.0; any factor but 1 fixes the size and drops the game's dynamic resolution). - hle_nvn.c paces presents by the cap instead of the game's interval and logs game time against real time every 1,800 presents. - TOTK profile: fps_target 60. Mac (M4 Pro): 60 presents a second in the opening cave, game time 1.000 of real time; at 30 and 0 as before.
…ad spun a core on iOS) - MessageSuperviseThread (main+0x6bc8fc4) waits on oe::GetNotificationMessageEvent with WaitAny, which does not clear it, then pops until the queue is empty. The host only ever signalled the event, so after the focus change at iOS launch it stayed set and the thread looped: 995 ms/s, 5-6 W on an iPhone. - hle_oe.c mq_ev_sync: the event is set while a notification is queued and cleared when the last one is popped (as the am service does), decided under the queue lock; hle_thread.c hle_clear_system_event. - DM_OE_SCRIPT also starts at the game's first GetNotificationMessageEvent, for games that pop only once the event fires. Mac with focus=2@5,focus=1@6: before 995 ms/s, CPU 109%, 6.97 W; after the thread is idle, 0.05 W. Hades still pops its notifications. - t_oe: stubs for the system event calls (it no longer linked) and 6 checks of the event state; 53/53.
- TOTK moves from "being ported" into the games table (60 fps on the iPhone, 30 on the Switch); DM_FPS_TARGET=30 in env.txt keeps 30 for battery. - Monster Hunter Rise: runs on an iPhone at 30 fps from character creation into the opening. - AGENTS.md: thirteen playable games.
…ost) - hle_fpslock.c: on every present the host writes the profile's fps_target into RE Engine's frame limit at [main+0x1295b3f0]+0x434 (FPSLocker's address for build c9a3dd77; the game sets 30 after boot). Its frame loop (main+0x6b106c8) waits until [+0x430]/[+0x434] sixtieths of a second have passed and steps by the time that really passed. - The game also asks for present interval 2: the profile's present_interval display ignores it. Both are needed; either alone stays at 30. - Profile: fps_target 60, present_interval display; DM_FPS_TARGET=30 gives back 30. Mac: frame time p50 16.67 ms.
…n whole vsyncs decided by the host The host decides each frame's step (1 to 3 vsyncs) from its own measurements (nvn_fstep.h, hle_nvn.c fstep_tick), keeps the frame on screen that long and pays back real time lost to late frames one vsync per frame: even motion, game time = real time, and 30 fps (not 40) when 60 does not hold. Tears of the Kingdom's frame-step stub writes 60 / step; Breath of the Wild gets a stub at the framework's setWaitVsyncInterval, so its VFR and every reader of the framework's interval take the host's step, and its VFR override is published so the host holds it. DM_FRAME_STEP=legacy (profile key frame_step) keeps each game's earlier method for the iPhone A/B test. Mac: both games at 1.000 of real time at 60, and at 30 with 12 ms added to every frame. The docs record a pre-existing intermittent Breath of the Wild crash at the world load (legacy too).
A stub sets the scale where the game's dynamic resolution applies its own (main+0x1266880), so the scene buffers, the shader's resolution ratio and the LOD check all follow it; a second stub takes that apply block every frame when the factor is not 1.0 (the game only runs it after recomputing its scale). 1.0, the default, keeps the game's dynamic resolution. Mac: at 0.5 every scene pass halves while the shadow maps, the 1920x1080 output and the HUD keep their size; handheld works too (960x540 at 0.75). The Mac's GPU time does not change (about 650 passes a frame, fixed cost first); the iPhone test decides whether it helps there.
…nd draws with no target (from PR seedds#11) Tears of the Kingdom's three remaining red lines, with the fixes PR seedds#11 found: - A BC4 texture written through an RGBA16UI view (BC4 encoded on the GPU; TOTK a 32x32x32 3D texture one depth plane per pass, BotW a 32x32 one) could not be attached, so its draws were lost. Such a pass now draws into an uncompressed shadow of blocks x blocks; the blocks are copied in through a buffer before the pass and back after it (nvn_mtl.m bcrt_attach / bcrt_flush). Shadows stay resident and are reused, and they are kept out of the load-skip / store-elision bookkeeping. - An agl fragment shader that only stores to a buffer draws into a 1x1 viewport with no target set: the pass is now attachment-less at the viewport size (bare_dims) instead of skipped. The opening cave gets the ambient light it lacked. - nvnDeviceRegisterFastClearDepth returns success; nvnCommandBufferSetAlphaRef is logged once. Neither game has a PC version, so the translator declared every fragment output float and Metal refused the encoders' integer targets. The profiles' shader_facts_manual.tsv declare them (TOTK 0544282c162305db and the Ultrahand mask e3fb0bb58a691e10, BotW 8e2cc2671cae6020), which changes only those two chains (743f7a102a72, f5259a6c6fb6; Mac and iOS libraries and c1_full.tsv rebuilt). A pipeline red line now prints the row to add when a float output meets an integer attachment. Mac: the 3D BC4 texture was all zeros before and holds encoded blocks in all 32 planes after (raw dump, frame 1500); TOTK's three red lines are gone; BotW (two runs) and Mario Wonder show no new red lines. Co-authored-by: digitalhivehub <141404180+digitalhivehub@users.noreply.github.com>
…scale line only on request
With the frame step at 2 vsyncs every frame stays on screen 33.3 ms, and the on-screen summary counted all of
them as dropped against the 60 fps period ("300 frames, dropped 300" in an iPhone BotW log). Each present's hold now
goes into a ring by present number, and the interval read 8 presents later is judged against its own presents'
holds: dropped = on screen longer than 1.5 x max(target period, hold x 16.7 ms). The [slowframe] diagnostic uses
the same limit. Checked with a scratch test built like t_nvn: step 2 at 33.3 ms 0 dropped, step changes 1 -> 3 -> 2
0, five doubled frames at step 1 5, discarded presents still counted.
The D2R 3D render-scale line ("main build ... has no known addresses") printed for every other game, where it read
like a failure (BotW and TOTK scale through DM_RENDER_SCALE); it now prints only when DM_3D_SCALE is set.
seedds#14) nn::time::StandardUserSystemClock::GetSystemClockContext reads the time service's shared memory, which the host never opens, so it returned 0x74 and wrote nothing. Unity's DateTime.UtcNow (Fire Emblem Engage main+0x4ad40c; Ace Attorney Investigations 1&2 per PR seedds#14) then never started its clock and read 0001-01-01. The hook answers with the Horizon layout: offset = user time minus the steady time point, the steady time point and source ID from the host steady clock (as the clock snapshot fills it), DM_FIXED_TIME honoured. Unity only compares the context to notice the clock being set, so it stays the same until the host clock moves against the guest ticks by more than a second. Engage on the Mac: Unity's clock now starts and DateTime.UtcNow reads the right UTC time, monotonic; without the hook it read 0 ticks. Wonder boot unchanged. Co-authored-by: armyja <546323405@qq.com>
…teger-as-float attribute Two red lines from an iPhone log of the PR seedds#11 build: - agl's BC4 block encoder has a twin for a 2D source (93aa5e22a8202409, agl_common BAHS@0x44918; 8e2cc2671cae6020 reads a 3D one). Now that the BC4 target is attached, its pipeline was refused (float4 output, RGBA16Uint attachment) and its draws were lost. The profile's shader_facts_manual.tsv declares its output uint: chain d3a07e79011d (Mac and iOS libraries and c1_full.tsv rebuilt). The Mac route does not reach it; a prewarm row for the pair fails on the old chain and builds on the new one. - NVN's integer-read-as-float vertex formats (*_UI2F / *_I2F, 0x51-0x68) had no Metal format, so the attribute read 0. They now go through the replay's CPU conversion (vconv_i2f in nvn_mtl.m): the values as floats for a float input, their float32 bits for an integer one. BotW's grass (uking_grass variants 3 and 7) reads attribute 2 as RG8_UI2F; with it the grass parts around Link's feet again (Mac A/B from the same outdoor save). No other game's Mac logs show these formats; Mario Wonder runs as before.
… the vertices each draw reads 60 fps (30 on the Switch), two host-only cuts on 1.3.0 main (patch_nso.py): - nnMain sets KTGL's device up with present interval 2 (main+0x8d94b0), and the device's frame rate, 59.94 / interval, is what its event scripts step by (main+0x7802c4). The config interval becomes 1, the engine's own 60 fps mode. - A KIDS task waits until 1 / fps has passed since the last frame (fps 30 from engine data) and then steps the game by the time that really passed. Its wait becomes 1/60 s (main+0x166db80). On the Mac the opening scenes at frame 2N match the original's frame N (same subtitle, same poses) and hold 60. Battles ran at 25.8 fps at a 30 or a 60 fps target: the replay converted the soldiers' Half2 / Short2 attributes (fed to integer shader inputs) on the CPU over whole bound mesh pools, 4.4 million vertices per submission at about twelve submissions a frame, 24 of the 32 ms the replay took. In nvn_mtl.m: - Profile key vconv_range (Age of Calamity on): only the vertices each draw reads (first / count, or the indices' smallest and largest, kept while the index pages stay unwritten and dropped once a GPU write is queued on them), bound as many vertices before the conversion as the range starts at. 219,000 vertices per submission; DM_NVN_VCONV_CHECK=1 found no stale index range in 983,000 reuses. Keeping the conversions across frames did not pay: the soldiers' attributes change every frame. - vconv_fill: loops of their own for the 8 / 16-bit integer and the half formats (byte-identical to vconv_elem for every format in an offline test; affects every game that converts). - The visibility buffer ring is as deep as the command allocator pool (8, was 4): the submit thread waited for the previous frame's GPU work. First battle on the Mac (M4 Pro): 58.6 fps, frame p50 16.9 ms, replay CPU 10 ms, GPU 11 ms. On an iPhone the GPU will likely decide in battle; the game steps by measured time, so it keeps normal speed below 60. IPA HyruleWarriorsAgeOfCalamity-2026-10-20-60fps.ipa built from HEAD + these changes; awaits an iPhone test.
…deadline Tears of the Kingdom on an iPhone ran 60 fps for 217 s, then stepped to hold 2 after three late frames and stayed at 23-30 fps: the CPU fell from 3.4 to 1.5 GHz and from 65% to 5% on performance cores (thermal 0, GPU time unchanged). The acquire thread's time-constraint declaration followed the hold (33.3 ms) and every change reset its computation to 2 ms, so the performance controller parked the game on the efficiency cores, where every trial back to 60 failed. - hle_nvn.c: rt_period_ns declares the cap's period while the frame step holds longer for lateness (a 30 cap and a step the game fixes itself are declared as they are); a period change keeps the computation declared so far instead of 2 ms. - front_ios.m: Low Power Mode at start and on each change, beside the thermal state. Mac, 20 ms present lag: TOTK and BotW step to hold 2 and keep period 16.667 ms at a steady 33.4 ms; without lag both stay at 60, game time 1.000 of real time. t_fstep 20/20, t_nvn only its known failure.
…haders, the C library's file checks and clock New profile 010005501E68C000 (update 1.0.0.2, Unity 2022.3.17f1 IL2CPP, SDK 17.5.3; the base game an XCI): - Rewriter: two IL2CPP thunks end in an indirect tail call, ldr x18, [x0, #0x18]; ...; br x18 (main+0x1d271bc, +0x204ebec), which asserted as an unknown jump table. When neither table form is found, the last write of x18 is a plain load and the br ends the function, it becomes x16 = guest x18; br x16 (tail_call / site_br_call). Tried only where the rewriter used to stop, so modules it accepted before come out byte-identical. - sdk cut: the AddressRegionManager node area 1 GB -> 62 MB on this build (sdk+0x139468 / +0x1394fc), as for Engage; StandardAllocator::Initialize aborted in nninitStartup without it. - Shaders (unityfs.py, shader_split.py): Unity 2022 keeps each program's parameters as blob entries of their own (m_ParameterBlobIndices; the programs are m_PlayerSubPrograms[tier][].m_BlobIndex), and subprogram version 202012090 (from 2021.2) has no local keyword list. The player's files borrow the unitybuiltinshaders bundle's Shader type tree. 4,951 shaders, 4,950 build (one geometry shader); Engage's layout of driver segments decodes every binding. UnityPy's type trees for 2020.3.18 and 2021.3 have neither field, so Engage reads every entry as before. - hle_fs.c: nnmusl (open / stat / opendir) checks every path with nn::fs::IsPathMounted, GetPathLengthMax, GetUtf16PathLengthMax, GetEntryNameLengthMax and GetUtf16EntryNameLengthMax, which the SDK answered from its empty mount table: File.Exists failed with no nn::fs call, Addressables never found settings.json and the game crashed on a null asset (main+0x31100ec). Answered from the host's mount table with Horizon's 0x300-byte limits. - hle_time.c: clock_gettime(CLOCK_REALTIME) reads the source nntimeStartup selects (sdk+0x8e414), nn::time::GetStandardUserSystemClockCurrentTimespec, which asked the time service; system_clock::now() then called std::terminate after the first dialog. It and the adjustable clock's getter are the host clock now. - Memory: Engage's 5 GB block with a 1 GB application pool. StandardAllocator commits 0.19 GB; with a 2.5 GB pool its reservation and Unity's own pools reached the end of the 4.375 GB alias region after 140 s (probes on VammManager::AllocateAddressRegion / FreeAddressRegion). 25 minutes now peak at +2.81 GB. On the Mac: first-boot settings, title, Investigations 1's Episode 1 into the first investigation, 60 fps, FMOD sound, 25 minutes with no red line, 1.55 GB resident and 1.25 GB of Metal allocations, flat. IPA AceAttorneyInvestigations.ipa (7.0 GB) built from HEAD + these changes; awaits an iPhone test. Also: UNIMPL stubs log once (the seen counter was a byte and wrapped every 256 calls); build_ipa.sh quotes FONT_PY (a Python path with spaces broke the font step); profile_guess.py marks IL2CPP by global-metadata.dat (Unity 2022's main has no il2cpp_ names). Not needed in the end: pull requests seedds#15 (this main's string table is intact) and seedds#16 (nninitStartup sizes no heap with virtual address memory). Much of the bring-up was found first by armyja (GitHub issues seedds#17, seedds#18, seedds#19, pull requests seedds#15, seedds#16). Co-authored-by: armyja <546323405@qq.com>
…table (on an iPhone) The IPA built from b1cd440 plays on an iPhone. README lists it (~7 GB) with a screenshot from the Mac (Edgeworth's office, the first investigation); AGENTS.md counts fifteen games and says where its Unity 2022 shader and file-API handling live; the game notes and the profile header drop "not tested on an iPhone".
…9's nn::ro::LoadModule New profile 0100A00019DE0000 (base game v0, display version 0.0.126424, SDK 19.3.9), from the NSP script's provisional profile. Unlike Hades it renders with NVN: 88 BNSH shaders in Shaders/NVN/bin; the ExeFS holds only main and sdk. - Shaders bind textures at fixed, sparse slots (0, 1, 3, …, 31 and 34): SceneFade.frag reads c2 0x130 = 0x20 + 8 x 34, past Mario's 32 slots, so dmsh_build left that binding undecoded. nvn_tex_slots 35; the game has no image bindings. - hle_ro.c: SDK 19's LoadModule takes one more bool (…mib), which the older hook did not catch; it and ro::Finalize are hooked. The Epic Online Services NRO stays offline as in Hades (RegisterModuleInfo fails). - Memory: no system resource; the 3.10 GB heap fits the 4 GB layout; main's 82 MB bss gives mem_used 108 MB. - nvn_mtl.m: DM_NVN_HILITE set its keys and library up lazily on whichever thread built a pipeline first; with several keys two threads raced and one used a library the other had replaced (objc_msgSend crash in pso_desc). Set up once. On the Mac: title, a new save, the opening and Erebus with combat at 60 fps, resident memory flat at about 3.4 GB, one red line (the NRR). The two long lines through Melinoë while the staff's Omega attack charges are the game's own aim lines (WeaponStaffSwing5: AimLineCountOverride 2, AimLineAngleOffsetOverride 180), traced with vertex dumps and probes; docs/development.md says so. IPA HadesII.ipa (4.0 GB) built; awaits an iPhone test.
The IPA built from the Hades II profile plays on an iPhone. README lists it (~4 GB) with a screenshot from the Mac (Erebus, the first room of a run); AGENTS.md counts sixteen games and notes its texture slots up to 34 and the staff's Omega aim lines that look like a rendering bug; the game notes drop "not tested on an iPhone".
…Opus C interface, CSRNG, alloc_big_align Unicorn Overlord 1.05 (SDK 16.2.4, Vanillaware's engine as in 13 Sentinels) plays from boot through the title screen into the opening story scene. Three boots, three fixes: - the hardware multi-stream Opus decoder's C interface (CRI's Opus streams; the size query takes the SDK 12+ option argument) goes to the sdk's software OpusMultiStreamDecoder, as the single-stream one does (hle_codec.c); - nn::crypto::GenerateCryptographicallyRandomBytes, which the sdk's own getentropy calls under std::random_device, fills from the host's entropy stream instead of opening csrng (hle_os.c); - 13 Sentinels' 64 KB alignment of large StandardAllocator blocks becomes the profile key alloc_big_align. The engine cuts ring heaps out of a memory pool at 64 KB GPU alignment; with 4 KB aligned pool storage one kept base 0, and the game wrote to 0x268 right after the title screen. 13 Sentinels sets the key (its profile lists only the 1.10 build the old check named), so its behaviour is unchanged. The profile (dm/games/010069401ADB8000) holds 1,725 shaders from 2,188 GLSLC outputs in main, Mario's binding layout with every binding decoded, translation chain b-239c170b7616.
The IPA built from the Unicorn Overlord profile plays on an iPhone (A20 Pro: 60 fps, sound, a 2.88 GB footprint). README lists it (~4 GB) with a screenshot from the Mac (the opening scene in the castle garden); AGENTS.md counts seventeen games and notes the engine it shares with 13 Sentinels and their alloc_big_align key.
…ode, the host's allocator for Unreal Octopath Traveler 0 1.0.5 (Unreal Engine 4.26 / 4.27 mobile renderer, SDK 19.3.13) plays from boot through character creation into the story on the Mac (15 minutes with the iPhone memory layout, no crash); not yet run on an iPhone. - Shaders: the 10,324 are in the two shader code archives of the game's pak, each one Oodle-compressed. uepak.py reads the version 11 pak (AES-256 index; the key is the window of main that decrypts it, never printed or written; AES through CommonCrypto) and the archives; the Mac host decodes them with the game's own OodleLZ_Decompress (DM_OODLE_JOB, hle_oodle.c, build-gated) and shader_split.py reads the decoded LSLG records (profile key shader_ue_decode; the NSP script's decode_ue_shaders stage). Bindings measured: Engage's separate texture layout. - Memory: Unreal's nninitStartup reserves a 62 GB address region and its FMallocBinned3 37 GB of it. A cut skips the allocator's constructor and the host puts its own FMalloc vtable on a malloc zone in the object (hle_uemalloc.c); the region is capped at 2 GB with the game's own -SwitchVirtualAddressSpaceSize (new profile key game_args, also for the iOS app) and SDK 19's node area is cut to 62 MB as Engage's, for a 3.75 GB guest block. - Host: game arguments are set before nninitStartup, as on hardware; an undefined weak import nothing defines binds to 0, as rtld binds it (Tears of the Kingdom boots the same with the old and new hosts: its weak glslc* imports now resolve to 0); nvnCommandBufferFenceSync (OP_FENCE_SYNC), the texture / sampler pool getters, vi::GetLatestFrameNumber / ListFrameHistory; DM_ALIAS_WINDOWS for sizing fixed-window allocators. - Rewriter: a byte jump table whose add sits 17 instructions before its br (jump_bytes_far, tried last). - build_ipa.sh quotes FONT_PY (a python path with spaces failed the font step). Open: 61 vertex shaders pick a texture slot at run time (dynamic handle), 16 compute shaders with 3D image atomics do not build. The profile (dm/games/01005270232F2000) holds the shader tables, translation chain b-ba76ba138e78.
The native mount-list overload opened filesystem IPC even though the host owns save writes, aborting before those writes were committed. Share the existing transaction engine and validate the whole list before publishing one backing tree. Reject independent transactions rather than exposing a partial commit. Add synthetic regression coverage through the registered big-stack ABI, including rollback, remount, clone failure and unsupported multi-save requests. The regression fails on the unchanged base when the overload is missing.
Owner
|
I am sorry that I accidentally closed the PR. I will look into it. |
seedds
added a commit
that referenced
this pull request
Oct 21, 2026
nn::fs::Commit(const char* const*, int) had no takeover, so the SDK's own code opened fsp-srv and aborted with the save writes left uncommitted (Animal Crossing: New Horizons). Both overloads now share one transaction engine. A list commits once when every name is the same host save; cache and temporary storage are skipped as the single-name Commit does, and a list of two separate saves is refused without committing either. t_fs_commit_list covers rollback, remount, aliases, clone failure, cache storage beside the save and the refused multi-save list. Co-authored-by: BeeeeeMo <14192706+BeeeeeMo@users.noreply.github.com>
Owner
|
Thanks for this fix. It's now on main as fa6ce1a, and you're credited as co-author. This branch is built on the repository's history from before the rewrite and shares no commits with current main, so it couldn't be merged. I ported it by hand instead:
Closing, since the change is on main. |
seedds
added a commit
that referenced
this pull request
Oct 21, 2026
nn::fs::Commit(const char* const*, int) had no takeover, so the SDK's own code opened fsp-srv and aborted with the save writes left uncommitted (Animal Crossing: New Horizons). Both overloads now share one transaction engine. A list commits once when every name is the same host save; cache and temporary storage are skipped as the single-name Commit does, and a list of two separate saves is refused without committing either. t_fs_commit_list covers rollback, remount, aliases, clone failure, cache storage beside the save and the refused multi-save list. Co-authored-by: BeeeeeMo <14192706+BeeeeeMo@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The host takes over
nn::fs::Commit(const char*), but notCommit(const char* const*, int). A game using the mount-list overload enters native filesystem IPC instead of committing the host transaction. With no native filesystem/service-manager session, the SDK aborts and leaves the writes in the uncommitted work directory.Observed on Animal Crossing: New Horizons (base v0, SDK 9), using the native Mac host on Apple M2.
Change and limits
Both overloads use the existing clone-and-atomic-swap transaction engine. The list overload validates every mount before committing once when all names refer to the same host backing transaction. Duplicate names and existing aliases do not cause repeated commits. Write-failure rollback, commit errors and callbacks are preserved.
Lists spanning independent backing transactions are explicitly refused before either is published. This does not implement Horizon's full multi-save atomic transaction support. The existing single-name behavior is unchanged.
Verification
Isolated branch based on
06d9375, using the checked-in Super Mario Bros. Wonder profile for build headers:hle_fs.cand exits 1 because the exact SDK symbol is missing. With the fix, 30 checks pass through the registered big-stack trampoline.t_fs: 68 checks pass. Full Mac host builds; arm64 iOS source syntax/type check passes. No iOS runtime test or full host-suite claim.git diff --checkpasses. The bundled native-write scan passes its self-test, but its repository scan fails on both base and patch because it references the absentdm_vault_ui.c; that unrelated scanner was not changed.The earlier local game test (
c628df1plus local bring-up hooks) loaded a recovered save, saved back to the title screen, and loaded it again in a fresh process. That reproduction also used local bring-up hooks and a separate clock-difference fix; those are not included here. This PR contains no game profile, save recovery tool, clock/audio change, or claim of full game support.