diff --git a/AGENTS.md b/AGENTS.md index e884bf3..c57ba77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,21 @@ # Notes for agents -- This repository runs Nintendo Switch games natively on Apple platforms: the game's own code runs, rewritten only where Apple's kernel requires it. Sixty games are playable on the iPhone: Super Mario Bros. Wonder (update 1.2.1, profile `010015100B514000`), Fire Emblem Engage (update 2.0.0, `0100A6301214E000`), Metroid Dread (update 2.1.0, `010093801237C000`), 13 Sentinels: Aegis Rim (update 1.10, `01003FC01670C000`), Super Mario 3D World + Bowser's Fury (update 1.2.2, `010028600EBDA000`), The Legend of Zelda: Breath of the Wild (update 1.9.0, SDK 20, `01007EF00011E000`), The Legend of Zelda: Link's Awakening (update 1.1.0, SDK 8, `01006BB00C6F0000`) Super Mario Odyssey (update 1.4.1, SDK 7, `0100000000010000`), Hades (update 1.0.38233, SDK 9, `0100535012974000`), Hyrule Warriors: Age of Calamity (update 1.3.0, SDK 9.6, `01002B00111A2000`), Fire Emblem: Three Houses (update 1.2.0, SDK 6.5, `010055D009F78000`), Super Mario Galaxy (update 1.3.1, SDK 18, `010099C022B96000`) The Legend of Zelda: Tears of the Kingdom (update 1.4.3, SDK 20, `0100F2C0115B6000`) Monster Hunter Rise (update 16.0.2, SDK 11.4.3, `0100B04011742000`), Ace Attorney Investigations Collection (update 1.0.0.2, SDK 17.5.3, `010005501E68C000`), Hades II (base game, SDK 19.3.9, `0100A00019DE0000`), Unicorn Overlord (update 1.05, SDK 16.2.4, `010069401ADB8000`), Rhythm Heaven Groove (base game 1.0.0, SDK 21.4.0, `0100D9F01D474000`), Octopath Traveler 0 (update 1.0.5, SDK 19.3.13, `01005270232F2000`), Pokémon Legends: Z-A (update 2.0.2, SDK 19.3.11, `0100F43008C44000`), Cuphead (update 1.3.7, SDK 5.5.0, `0100A5C00D162000`), Super Mario Galaxy 2 (update 1.4.0, SDK 18.3.5, `0100FD8022DAA000`), FINAL FANTASY TACTICS - The Ivalice Chronicles (update 1.4.0, SDK 19.3.9, `010038B015560000`), Absolum (update 1.1.1, SDK 18.3.2, `0100A83022A22000`), Captain Toad: Treasure Tracker (update 1.4.2, SDK 8.2.0, `01009BF0072D4000`) and Brain Memory Training Academy (base game 1.0.0, SDK 16.2.4, `0100F5D01DA3A000`), The Great Ace Attorney Chronicles (base game 1.0.0, SDK 10.4.1, `010036E00FB20000`), STEINS;GATE RE:BOOT (update 1.0.3, SDK 20.5.17, `01008BC024FDE000`), Big Brain Academy: Brain vs. Brain (update 1.2.0, SDK 11.4.3, `0100620012D6E000`), Patrick's Parabox (update 1.0.1, SDK 13.3.7, `0100E44016C22000`), Celeste (update 1.4.1.1, SDK 15.3.2, `01002B30028F6000`), DRAGON QUEST XI S: Echoes of an Elusive Age – Definitive Edition (update 1.0.4, SDK 7.3.2, `01006C300E9F0000`), Monster Train 2 (update 1.1.1, SDK 18.3.5, `010051701FB46000`), Raging Loop (base game 1.0.0, SDK 7.3.2, `010020400E1C2000`), DARK SOULS: REMASTERED (update 1.0.3, SDK 4.5.0, `01004AB00A260000`), Paper Mario: The Thousand-Year Door (update 1.0.1, SDK 15.3.2, `0100ECD018EBE000`), Hollow Knight: Silksong (update 1.0.30000, SDK 20.5.8, `010013C00E930000`), Catherine: Full Body (update 1.0.1a, SDK 8.2.1, `0100BF00112C0000`), Digimon Story Cyber Sleuth: Complete Edition (update 1.0.3, SDK 6.4.0, `010014E00DB56000`), Animal Crossing: New Horizons (update 3.0.3, SDK 20.5.4, `01006F8002326000`), DRAGON QUEST VII Reimagined (update 1.2.0, SDK 19.3.1, `0100A9D01C446000`), FINAL FANTASY RESONANCE (update 1.0.1, SDK 20.5.17, `0100606026F12000`), Luigi's Mansion 3 (update 1.4.0, SDK 7.3.2, `0100DCA0064A6000`), AI: THE SOMNIUM FILES (update 1.0.1, SDK 6.4.2, `010089B00D09C000`), ANIMAL WELL (update 1.0.10, SDK 20.5.7, `010020D01AD24000`), AI: THE SOMNIUM FILES - nirvanA Initiative (update 1.0.2, SDK 13.3.5, `0100713016CCC000`), Mario Kart 8 Deluxe (update 4.0.0, SDK 4.4.0, `0100152000022000`), Balatro (update 1.1.3, SDK 18.3.2, `0100CD801CE5E000`), ROMANCE OF THE THREE KINGDOMS 8 REMAKE (update 1.1.3, SDK 17.5.3, `0100C2A01A730000`), Taiko Risshiden V DX (update 1.1.1, SDK 12.3.3, `0100346017304000`), Super Smash Bros. Ultimate (update 13.0.5, SDK 8.2.1, `01006A800016E000`), Bayonetta 2 (update 1.2, SDK 3.6.0, `01007960049A0000`), Bayonetta 3 (update 1.2.0, SDK 13.3.2, `01004A4010FEA000`), Advance Wars 1+2: Re-Boot Camp (base game 1.0.0, SDK 10.4.1, `0100300012F2A000`) Pokémon Sword (update 1.3.2, SDK 7.3.2, `0100ABF008968000`), Pikmin 4 (update 1.1.1, SDK 10.4.8, `0100B7C00933A000`), Stardew Valley (update 1.6.15.13, SDK 20.5.27, `0100E65002BB8000`), BALL x PIT (update 1.293, SDK 20.5.19, `010086A022444000`), Mario vs. Donkey Kong (update 1.0.1, SDK 15.3.0, `0100B99019412000`) and Bayonetta (update 1.1, SDK 3.6.0, `010076F0049A2000`). Hades renders with Vulkan, which the host implements on Metal (`dm/gfx/vk/hle_vk.m`; `main` imports Vulkan, not NVN). Age of Calamity is Koei Tecmo's KTGL engine: files in RDB archives, shaders as GLSLC outputs (`shader_split.py` `idrk_shaders`). Three Houses is KTGL too; 9 of its shaders exist only as compiles of the game's own GLSLC at run time, captured once on the Mac (its `main` holds GLSL sources and imports `glslcCompile`). Breath of the Wild and Tears of the Kingdom run at 60 fps through a frame step the host decides in whole vsyncs (`FRAME_STEP` in their profiles' `patches.py`, `nvn_fstep.h`, profile keys `fps_target` and `frame_step`); Tears of the Kingdom has a 5 GB guest layout, and its `sdk` build (Breath of the Wild's) has the SDK's address-region node area cut (`sdk_patches.py`): without it the resource heaps ran out of alias region at the first descent from the Great Sky Island and the game played its memory-shortage Blood Moon, which put Link back at the start (its log shows `MSF`). Monster Hunter Rise is Capcom's RE Engine: files in KPKA PAK archives (`shader_split.py` `kpka_jobs`, shaders in SDF files), the base game an XCI, its GPU heap a 64 GB sparse virtual pool (`hle_nvn.c` `vpool_*`, a 4 KB page table the replay reads textures through); it runs at 60 fps (30 on the Switch). Super Mario Galaxy is the Wii game on NERD's Hagi layer (PowerPC code recompiled into `main`), renders with Vulkan, and generates its shaders at run time, which the NSP script captures on the Mac (no SPIR-V file in its RomFS); its pointer is the touch screen in handheld mode (profile key `op_mode`). Ace Attorney Investigations Collection is Unity 2022 IL2CPP: its shaders keep their parameters as blob entries of their own (`shader_split.py` `unity_code_blobs`), and its C# file API goes through the SDK's C library, whose path checks the host answers (`hle_fs.c` `IsPathMounted` and the length limits). Hades II, unlike Hades, renders with NVN; its shaders bind textures at fixed slots up to 34 (slot 34's handle lies in the driver's image table, `dmsh_build.py`), and the two long lines through Melinoë while the staff's Omega attack charges are the game's own aim lines, not a rendering bug. Unicorn Overlord is Vanillaware's engine like 13 Sentinels (shaders compiled into `main`); both need the profile key `alloc_big_align`, which aligns large allocations to 64 KB so the ring heaps the engine cuts from a memory pool fit. Rhythm Heaven Groove is Nintendo's own 2D engine on SDK 21, the newest SDK so far (its DSP command list has no magic, `hle_audren.c`); it plays handheld with Boost (profile keys `op_mode handheld`, `perf_mode boost`), which skips its TV latency calibration and keeps its 1920x1080 draw size. Octopath Traveler 0 is Unreal Engine 5: everything in one pak with an AES-encrypted index whose key is found in `main` (`dm/gfx/tools/uepak.py`), its shaders Oodle-compressed in the pak's shader code archives and decoded once on the Mac by the game's own Oodle (any game with Unreal paks, `DM_OODLE_JOB`), its allocator replaced by the host's because Unreal's reserves 37 GB of address space (a cut in its `patches.py`, `hle_uemalloc.c`), its address region capped by the game's own argument (profile key `game_args`), and the particle shaders that pick a texture slot at run time bound by slot (profile key `tex_dyn_slots`); it runs at 60 fps (30 on the Switch) because the host writes Unreal's `t.MaxFPS` and `rhi.SyncInterval` in game memory (`hle_fpslock.c`, profile key `fps_target`). Pokémon Legends: Z-A is Game Freak's Trinity engine: everything in the packs of `arc/data.trpfs` (FlatBuffers, every file Oodle-compressed), which the shader split decodes with the game's own decoder in the Mac host's tool mode (`shader_split.py` `trinity_shaders`, `hle_oodle.c` `DM_OODLE_SERVE`); its 12 GB StandardAllocator is cut to 4 GB to fit a 5 GB guest layout, and the host refuses maps past the pool as Horizon does (profile key `mem_map_limit`); its characters are skinned with transform feedback (`dmsh_build.py` `vs_tf`, `nvn_mtl.m` `tf_draw`), and its occlusion queries, which decide how characters are animated, need the draw's base instance in the driver segment (c0 + 0x644, `encode_args`). Cuphead is Unity 2017.4 IL2CPP on SDK 5.5.0: Unity 2017's serialized and Shader formats are read by `unityfs.py` / `shader_split.py`, its rewritten modules (80 MB) need the profile key `mem_used` above the default 64 MB, or its `SetHeapSize` fails, and its NVN pool allocator breaks on a zero-size block, so the host never reports a ZCull storage size of 0 (`hle_nvn.c` `zcull_size`). Super Mario Galaxy 2 is Galaxy's Hagi layer with Galaxy's `sdk` build; its movie player reserves 384 MB of virtual address memory, which fits only because a rewriter cut on that `sdk` build shrinks the SDK's address-region node area to 62 MB (`sdk_patches.py`; the profile's alias region is 512 MB). FINAL FANTASY TACTICS - The Ivalice Chronicles is Square Enix's engine on NVN: its shaders are GLSLC outputs in loose technique files, which `shader_split.py` reads in any RomFS file no other reader takes; its UI textures are written by the CPU into uncached pools (profile keys `tex_flush_upload`, `tex_dirty_upload`); and two job-pool workers share one auto-clear event, so a build cut makes a worker woken in another's place pass the wake on (its `patches.py`). Absolum is FNA (XNA 4 in C#) on .NET NativeAOT with SDL3 and FNA3D's NVN driver: its `main` starts its text with 1.7 MB of data the rewriter leaves alone (`nso_code.py`), its shaders are GLSLC outputs in `shaders.nx.bin`, its picture is flipped by a viewport swizzle the host applies, and everything lives in virtual address memory: malloc's 40 GB StandardAllocator is cut to 2.5 GB and the SDK's node area and large-region alignment are cut (its `patches.py` and `sdk_patches.py`), and the .NET GC's region range is 1.5 GB (profile key `game_env`), to fit a 5 GB guest layout. Captain Toad: Treasure Tracker is Nintendo EPD Tokyo's engine (the `al` library over sead / agl, as 3D World and Odyssey) and needed no host change: it plays handheld because its pointer is the touch screen there (the Joy-Con gyro docked; profile key `op_mode`), and agl's post-effect program that samples its own attachment reads a snapshot (profile key `feedback_snap`). Brain Memory Training Academy is Unity 2022.3 IL2CPP with no bundles: its serialized files carry no type trees and nothing lends it a `Shader` tree, so `unityfs.py` has Unity 2022.3's `Shader` layout written out (`KNOWN_TREES`), and like Ace Attorney Investigations Collection it needs the SDK's address-region node area cut on its `sdk` build (`sdk_patches.py`). The Great Ace Attorney Chronicles is Capcom's MT Framework: files in ARC archives of zlib entries, its 460 shaders in one shader package inside one of them (`shader_split.py` `mtspk_shaders`); it copies its textures into a CPU-cached pool after creating them and never flushes (its `main` has no cache maintenance at all), so it needs `tex_flush_upload` and `tex_dirty_upload` like FINAL FANTASY TACTICS. STEINS;GATE RE:BOOT is M2's ADV engine (E-mote characters, PSB archives, H.264 movies through the SDK's movie library): it needed `nn::audio::SetAudioOutVolume` (`hle_audio.c`), and because it decodes every picture into one reused region of a pool that lives for good, the storage-sharing texture table now drops an entry whose storage another texture takes over (`nvn_mtl.m` `alias_take_over`); it draws 1280x720 in both modes by its own choice. Big Brain Academy: Brain vs. Brain is NintendoWare Bezel Engine 1.56: nearly all of RomFS is in Bezel Engine archives of zstd entries, its shaders in their `.bfsha` entries (`shader_split.py` `bea_members`); its `nninitStartup` sets a heap or a 60 GB virtual address region by `IsVirtualAddressMemoryEnabled`, so the host reports the NPDM's system resource (profile key `sys_resource`, 0 here); its activity previews loop the movie decoder with Flush and Start from four threads (`hle_movie.c`); and it plays handheld with Boost (profile keys `op_mode handheld`, `perf_mode boost`), because only handheld does it read the touch screen. Patrick's Parabox is Unity 2020.3 IL2CPP with no bundles: `unityfs.py` has Unity 2020.3's `Shader` layout written out (`KNOWN_TREES`), it needs the SDK's address-region node area cut on its `sdk` build like Brain Memory Training Academy (`sdk_patches.py`), and Unity's player hands the shared fonts straight to FreeType, so a game that imports no `ScalableFontEngineHelper::Decode` gets the TTF itself (`hle_sdk16.c` `font_skip`). Celeste is MonoGame for the Switch, its C# compiled to C++ by Sickhead's BRUTE: its shaders are MonoGame effects (MGFX) in its XNB files and in `main` (`shader_split.py` `mgfx_effect`), and it needed no host change. DRAGON QUEST XI S is Unreal Engine 4.18: legacy paks with LZ5 blocks, decoded on the Mac by the game's own LZ5 (`uepak.py`, `hle_oodle.c`), its shaders inline in the cooked packages (`shader_split.py` `ue_legacy_jobs`), and the slice-writing geometry shader of its colour-grading LUT folded into the vertex shader (`dmsh_build.py` `gs_slice`, `nvn_mtl.m` `prog_fold_gs`); Square Enix's sound driver never waits on the renderer event, so audio catch-up frames wait for queue space (`hle_audren.c`); its movie player plays only once every track holds a decoded output, so audio movie decoders keep 32 (`hle_movie.c`); and its Unreal region is capped at 4064 MB (profile key `game_args`, MB up to 4095) with the node area and large-region alignment cut on its SDK 7.3.2 build (`sdk_patches.py`) for a 5 GB guest layout; it runs at 60 fps (30 on the Switch) because the host writes Unreal's `t.MaxFPS` and `nvn.SyncInterval` in game memory (`hle_fpslock.c`, profile key `fps_target`). Monster Train 2 is Unity 2022.3 IL2CPP on Super Mario Galaxy's SDK 18.3.5 `sdk` build: Unity creates a thread only when `nn::os::GetFreeThreadCount` (`svcGetInfo` type 24, `hle_svc.c`) is at least 1; its MP4 intro needs a movie audio decoder with more output slots than a video one, because Unity's player holds about half a second of audio before it plays (`hle_movie.c` `MD_NOUT`); and its post-processing compute shaders (the colour grading LUT among them) are Unity `ComputeShader` objects, which `shader_split.py` reads (`unity_compute`) with Unity 2022.3's layout written out in `unityfs.py`. It reads no touch, so it stays docked with the 3D pad layout, and it runs at 60 fps (30 on the Switch) with the profile key `present_interval display`, as its animations follow the time that passed. Raging Loop is Unity 2018.3 IL2CPP with no bundles on DRAGON QUEST XI S's `sdk` and `subsdk0` builds: `unityfs.py` has Unity 2018.3's `Shader` layout written out (`KNOWN_TREES`, an older form than the 2020 and 2022 layouts, with the parameters in each subprogram), and it needed no host change; it reads no touch, so it stays docked with the 2D pad layout. DARK SOULS: REMASTERED is FromSoftware's engine on SDK 4.5.0, the oldest SDK so far: its shaders are GLSLC outputs in BND3 archives inside the dvdbnd archives (`shader_split.py` `bdt_shaders`), and SDK 4's GLSLC outputs are read through their section headers (`glslc_section`); it keeps 12 sampler pools with different samplers under the same IDs, so handles a shader reads from a uniform buffer take the base of the pool bound at replay (`nvn_mtl.m` `ubo_tex_id` / `ubo_samp_id`); and it runs at 60 fps (30 on the Switch) with two rewriter cuts, its frame limiter's 30 and its frame update's fixed 1/30 s step (its `patches.py`). Paper Mario: The Thousand-Year Door is Intelligent Systems' engine on NintendoWare, on Celeste's SDK 15.3.2 `sdk` build: nearly every RomFS file is zstd (`.zst`, which `shader_split.py` decompresses by magic); its AngelScript call bridges leave sp misaligned, which a general rewriter rule rounds up (`site_sp_align`); it runs at 60 fps (30 on the Switch) by the host's frame step (`FRAME_STEP`, with the floor of its vsync step cut from 2 to 1); and its three job workers poll the job list with no SVC, which held it at 24 fps on an iPhone until the general spin wait rule (`patch_nso.py` "Spin waits", `hle_thread.c` `dm_spin_wait`). Hollow Knight: Silksong is Unity 6 (6000.0.50f1) IL2CPP, the newest Unity so far: its bundles carry their own type trees, so `unityfs.py` needed no layout; Unity 6 polls `nn::hid::GetDebugPadStates` every frame (`hle_hid.c`), and it takes all its memory itself through `nn::os::AllocateAddressRegion` / `AllocateMemoryPages` with no heap, so it runs in the 5 GB guest layout that `profile_guess.py` now gives every game with an NPDM system resource, with the SDK's address-region node area cut on its `sdk` build (`sdk_patches.py`). Catherine: Full Body is Atlus's GFD engine with CRI middleware (CPK archives, ADX2 sound, Sofdec2 movies through `movie::Decoder`, whose player reads `nv12-uv-stride`, `hle_movie.c`): its 3,128 shaders are GLSLC outputs in `data/shaders_binary`, `main` and two compiles of its own GLSLC at boot, which the host lets run by reporting at least that GLSLC's GPU code version (`hle_glslc.c`); that GLSLC writes every vertex shader as a VertexA part (shader type 0, the position) and a VertexB part, which `dmsh_build.py` merges as Ryujinx does (`vertex_ab`; the screen was black without it), and because the game reuses a copy's staging memory a frame after recording it, the host commits a segment at the first pass boundary after a buffer → texture copy (`nvn_mtl.m` `g_cut_copy`). Digimon Story Cyber Sleuth: Complete Edition is Media.Vision's MVGL engine and renders with NVIDIA's desktop OpenGL 4.5 through EGL, not NVN, which the host implements on Metal (`dm/gfx/gl/hle_gl.m`: the 13 EGL imports and the 79 GL functions its code calls of the 2,956 its glad loader fetches, counted with `dm/tools/loader_census.py`; `profile_guess.py` gives `graphics gl` when `main` imports `eglCreateWindowSurface` and names `glShaderSource`); its shaders are GLSL ES 3.20 source in its MVGL archives (`dm/gfx/tools/mvgl.py`: MDB1 with doboz entries) and in `main`, which `gl_shaders.py` translates offline with glslang and SPIRV-Cross (packing needs `brew install glslang`) and the host finds by the hash of its text. Animal Crossing: New Horizons is Nintendo EPD's engine (sead / agl / gsys) on SDK 20.5.4: its `main` links its own HIPC client for `olsc:u` (cloud backup settings), which the host answers with sessions of its own (`hle_sf.c`), its island lives in device save data (`MountDeviceSaveData`, `hle_fs.c`), it skips clears with conditional rendering (profile key `render_cond`), its 63 MB `main` needed halfword jump tables padded only to their largest entry (`patch_nso.py` `half_count`), and its `SaveProc` builds a new island in grid walks that yield once per cell, so a yield whose loop moved its callee-saved registers since the last one now returns at once instead of backing off, for every game (`hle_thread.c` `thr_yield`; the island showed after 77 s of black screen, now about 10 s). DRAGON QUEST VII Reimagined is Unreal Engine 4.27: IoStore containers beside one pak, its shaders LZ4 blocks in the pak's shader code archives, which `uepak.py` decodes itself (`lz4_shaders`), and its allocator replaced as Octopath Traveler 0's (a cut and an observation gate in its `patches.py`, `hle_uemalloc.c` layout `UE4_27`) in a 2 GB engine region (profile key `game_args`); its audio renderer runs at 32 kHz, resampled by the host on the CPU path (`hle_audren.c`); and Unreal's volumetric lightmap merge sizes its indirection texture with a query on the image it writes, so a texture slot that shares its c2 word with an image slot reads the bound image (`nvn_mtl.m` `tex_slot_h`) and the translator types such a query from the image (`dmsh_xlate` `ImageTypesOf`): without them every character in the opening cutscene glowed white; it runs at 60 fps (30 on the Switch) because the host writes Unreal's `t.MaxFPS` and `rhi.SyncInterval` in game memory (`hle_fpslock.c`, profile key `fps_target`), the same two limits a 60 fps mod for the base game lifts with code patches (`UEngine::SetMaxFPS` returning at once, the name `r.VSync` broken). FINAL FANTASY RESONANCE is Unreal Engine 5.6.1, the newest Unreal so far: its shader libraries are IoStore chunks in its container, not in its pak, two layers of Oodle that `uepak.py` reads through the game's own decoder in the Mac host with no boot (`Toc`, `io_archive`, `OodleHost`); its 94 MB `main` is 95% without unwind entries, so the rewriter analyses long functions in pieces (`patch_nso.py` `pieces`; one 82 MB function took 100 GB of memory) and treats code before the first unwind entry as a function; its allocator is replaced as Octopath Traveler 0's (UE 5.6's 32-slot FMalloc, `hle_uemalloc.c` layout `UE5_6`), and its engine region is 4064 MB (profile key `game_args`), because the engine's own page allocator never gives address space back (2 GB aborted in the first battle); 2,077 particle shaders need `tex_dyn_slots`; it runs at 60 fps (30 on the Switch) by the host's `rhi.SyncInterval` write (`hle_fpslock.c`, profile key `fps_target`). Luigi's Mansion 3 is Next Level Games' own engine on NVN: its RomFS files are in the engine's `.dict` / `.data` archives, and update 1.4.0 ships `.patch` files beside the base ones, which `shader_split.py` applies as the game does (`nlg_shaders`); the game patches texture handles into its recorded command buffers in place, at the command start + 0x10 it took from `GetCommandMemoryUsed`, so single texture binds are recorded in the hardware's layout (`OP_BIND_TEX1`, every game; without it the title showed a stretched icon and a new game a white screen); its G-buffer is two integer targets, which its pixel shaders write through a second, integer-output entry point (`dmsh_build.py` `fs_uint`, every game); and it runs at 60 fps (30 on the Switch) because the host writes its frame limiter's 33.333 ms, a constant in read-only data, as 1000 / `fps_target` less half a millisecond (`hle_fpslock.c`, with `present_interval display`). Its tessellated draws are skipped (Metal 4 has no tessellation stage) and its geometry shaders do not build; neither was drawn in play so far. AI: THE SOMNIUM FILES is Unity 2017.4 IL2CPP (Cuphead's Unity layout) with FMOD: its 168 movies are H.264 MP4s that Unity's player copies into NV12 texture pairs, and its Spike Chunsoft logo movie held the game before the title until a released output slot also went on with the H.264 decoder's drain at the end of a stream (`hle_movie.c`: Unity holds all 16 video outputs by then); it binds its vertex and fragment programs apart, and the precompile prediction reused the ring slot of the combined program the replay had saved, so a draw ran with another pair's shaders until each restore of the replay state took the combination again from the per-stage bindings (`nvn_mtl.m` `prog_resolve`, every game); it plays docked with the 3D pad layout (the investigation's view is on the right stick). ANIMAL WELL embeds all its assets and 46 BNSH shaders in `main`; its base and update Program NCAs have no data section, which `nx_extract` represents as an empty RomFS directory. It needed no host change; its profile uses the 2D pad layout. AI: THE SOMNIUM FILES - nirvanA Initiative is Unity 2020.3 IL2CPP (Patrick's Parabox's Unity) with Addressables bundles and FMOD, and needed no host change: Unity 2020.3 keys a compute shader kernel's variants by keyword set (`shader_split.py` `unity_compute`, `variantMap`); its `nninitStartup` hands pool − used to StandardAllocator as virtual address memory and Unity reserves its own regions beside it, so it has the SDK's address-region node area cut on its `sdk` build (`sdk_patches.py`) and a 1 GB pool, which the NSP script's heap check now sets for such a game (`profile_guess.py` `heap_signals`: the alias region nearly full within seconds and no heap); it plays docked with the 3D pad layout (the investigation's view is on the right stick). Mario Kart 8 Deluxe is Nintendo's sead / agl / gsys engine on SDK 4.4.0, the oldest SDK so far: it lists the six default Miis at boot and builds Mii heads from the console's MiiModel system data, so the host answers the Mii database itself (`hle_mii.c`: the six default Miis, none of the console's own) and mounts a stand-in of that archive with every part empty (`dm_mii_standin`, `hle_fs.c` `MountSystemData`; the firmware is not the host's to ship, so Mii heads draw nothing); its DLC licence is decrypted in a 0x3130-byte heap that also holds the mount cache, now sized as Horizon's RomFS working memory (`romfs_work_size`); it mounts device and account save data, which are now two host saves for a game whose `main` imports both (at boot it deletes `CommonSave:/userdata.dat`); SDK 4's DeviceSink is command 0xf of 0x130 bytes, and the audio renderer's update lock is read off each SDK's own `AudioRenderSystem::Update` (`hle_audren.c` `update_lock`: it was SDK 16's for every SDK before 20); it evaluates its conditional rendering (profile key `render_cond`) and runs at 60 fps. Balatro is LÖVE 11.4 (love-nx: Lua source in the RomFS, LuaJIT's interpreter, SDL) on NVIDIA's desktop OpenGL through EGL, like Digimon: `main` imports `vkGetInstanceProcAddr` only for SDL's Vulkan loader, which `profile_guess.py` leaves out of its Vulkan count; LÖVE takes its core-profile paths when the GL layer says core (`GL_CONTEXT_PROFILE_MASK`): persistently mapped vertex streams fenced per frame, attribute bindings and current values, uniform queries, texture storage, 3D and array textures, swizzles and mipmaps, about 150 GL functions in all (`hle_gl.m`, counted with `loader_census.py`, which now follows LÖVE's glad through registers; `DM_GL_STUBS=1` names a missing one at its first call); its 35 shaders are GLSL that LÖVE puts together at run time, captured by the NSP script on the Mac (profile `gl_capture.tsv`, `capture_shaders`), with `bool` uniforms keeping their type (`gl_shaders.py`); it opens the audio renderer without an event, which started no render thread before (`hle_audren.c` `ar_open`: it was silent); and it plays by touch in handheld mode (profile keys `op_mode handheld`, `pad_layout touch`: the iPhone gamepad's Tap layout sends every touch to the game). Any Lua error makes it quit, and love-nx then crashes in its video worker's teardown (`main+0x339c00`): look for the Lua error first. ROMANCE OF THE THREE KINGDOMS 8 REMAKE is Koei Tecmo's KTGL on SDK 17.5.3, with a newer RDB than Age of Calamity's: `.fdata` files (a `PDRK` header, then `IDRK` entries) whose entries are LZ4 or zlib in 16 KB chunks of a new form (`shader_split.py` `idrk_data`: flag 0x400000, LZ4 with 0x100000 too), 59,363 shaders in all; its update is a multi-program patch that carries a whole second program (`…0001`, started with `nn::oe::ExecuteProgram(1)`) beside the game's, so `nx_extract` takes program index 0's NCAs (it took the second program and failed); SDK 17.5.3's `OpenAudioRenderer` with an event calls the form without one through the PLT, which the host's hook now passes straight to the SDK (`hle_audren.c` `t_in_open`); it plays at 30 fps with a controller (it reads a USB mouse, not the touch screen). Taiko Risshiden V DX is KTGL on SDK 12.3.3, mostly 2D, in Japanese and Chinese only (the NSP script packs Japanese): its 212 shaders are GLSLC outputs in `main` and one G2S container (`eff/KTGLEffect.g1s`), read as before; it needed `nn::oe::SetCpuOverclockEnabled` (`hle_oe.c`: its first boot crashed in apm); it plays by touch in handheld mode (profile keys `op_mode handheld`, `pad_layout touch`), taking its taps, drags and flicks only from `nn::hid::GetGestureStates`, which the host now recognizes from the touch points for every game (`hle_touch.c` `gesture_sample`, yuzu's rules plus an 8 px slop; unit test `t_gesture`; the `DM_CTL` line `drag` tests pans on the Mac); and its frame end slept in 2 ms rounds until 1/60 s had passed, which ended at 18.2 ms (55 fps), so a build cut skips that wait and the presents hold 60 (its `patches.py`). Super Smash Bros. Ultimate is Bandai Namco / Sora's engine on Catherine: Full Body's SDK 8.2.1 `sdk` build: everything is in one `data.arc` of Hash40-named zstd files (`dm/gfx/tools/smash_arc.py`; the split reads its current files, 35,012 shaders); its fighters' code is 98 NROs in that archive, loaded and unloaded per battle with `nn::ro::LoadModule`, which the NSP script collects by the SHA-256s its NRRs list and rewrites (`dm/tools/nro_collect.py`), the host loads by the image's hash and binds `main`'s `lua2cpp` imports to (`hle_ro.c`, `loader.c` "NRO modules"), and the iOS app carries packed into three sections of the executable (`gen_embed.py`, `loader.c` `nro_pieces`); its engine reserves 62 GB of address space, cut to 4 GB in its `patches.py` with the SDK's node area and 1 GB alignment cut on that `sdk` build (`sdk_patches.py`) for the 5 GB guest layout; and it fetches `nvnSetExternalDebuggerEnabledNVX`, a no-op. Bayonetta 3 is PlatinumGames' engine: everything in PKZ archives of DAT archives whose entries are Oodle Kraken, which the shader split decodes with the game's own streaming decoder (`main` has no `OodleLZ_Decompress`, so `hle_oodle.c` drives `OodleLZDecoder_*`), its shaders the engine's own shader objects (`shader_split.py` `pg_shader`); the engine refuses shader code newer than the driver reports, so the NVN layer's newest GPU code version follows the SDK (`hle_nvn.c`, 1.16 from SDK 13). Advance Wars 1+2: Re-Boot Camp is Unity 2020.2 IL2CPP on The Great Ace Attorney Chronicles' SDK 10.4.1 `sdk` build, which takes the node-area and 1 GB alignment cuts (`sdk_patches.py`) for its virtual address memory, with a 2 GB pool (the heap check's 1 GB ran its malloc dry at the first loading screen, and Unity's GPU pool allocation then spun for good); its Bink 2 movies draw with 32 shaders whose control sections and code areas lie loose in `main`, paired by data records or by the code that builds their addresses (`shader_split.py` `raw_shaders`; every movie drew white without them). Pokémon Sword is Game Freak's engine before Trinity, on Super Mario Odyssey's SDK 7.3.2 `sdk` and movie builds: its shaders are BNSH entries of GFPAK archives (LZ4, `shader_split.py` `gfpak_members`) and Silicon Studio's YEBIS post effects, a `.tfxo` file in a zlib stream in `main` (`tfxo_streams`); its G-buffer writes float and integer targets at once, which takes a pixel shader entry point that returns only the integer outputs as integers (`dmsh_build.py` `fs_uint`, `main0_m`; `nvn_mtl.m` `pso_mixed_uint`), its cube views of fewer than 6 layers count layers in cubes (`tex_by_id`), and it writes beside its packaged textures in host pages they share, which no longer re-uploads them when the texture's own bytes are unchanged (`nvn_mtl.m` `pkg_edges_clean`; frames went from 33 to 75 ms without it); it asks for a Poké Ball Plus (Palma, `hle_hid.c`) and the Let's Go play statistics (`hle_offline.c`), both answered as absent. Pikmin 4 is Unreal Engine 4.26.2 with IoStore (its shaders LZ4 in the pak's shader code archives, as DRAGON QUEST VII Reimagined's) and Wwise: its allocator is replaced as DRAGON QUEST VII Reimagined's (`hle_uemalloc.c` layout `UE4_27`), but this build reserves its engine region only when `-SwitchVirtualAddressSpaceSize=` is absent, so a cut in its `patches.py` lowers the 62 GB default to 4064 MB instead of `game_args`; the rewriter now re-encodes an `adrp` inside an exclusive window (`window_adrp`) and accepts a gate before a `ret` (`scratch_dead`); and a network request now stays on hold for 50 ms, as Horizon's asynchronous one (`hle_net.c`): answered at once, its BCAT thread could finish before its creator stored "started", and about one boot in six waited at the black loading screen for good. Stardew Valley is MonoGame on Sickhead's BRUTE like Celeste (C# compiled to C++, a Boehm collector) with XACT sound, on SDK 20.5.27: 3,535 of its 3,549 XNB files are compressed (LZ4, 8 LZX), which `shader_split.py` decompresses (`xnb_decompress`) to reach its one content effect beside the 64 shaders in `main`; it draws nearly everything with SpriteBatch (a prewarm list of 2 shaders and 4 pipelines), reads only the pad, and its character creation names the farmer and the farm through the software keyboard, whose Mac text box releases the buttons that opened it (`front_mac.m`, found on this game); its profile uses the 3D pad layout (the D-pad in menus, the right stick for the cursor), and it needed no other host change. BALL x PIT is Unity 6 (6000.0.62f1) IL2CPP with FMOD and Rewired, as Hollow Knight: Silksong: it needed no host change, only the SDK's address-region node area cut on its SDK 20.5.19 `sdk` build (`sdk_patches.py`); its loose serialized files have no type trees and borrow the `Shader` tree its bundles carry, and it draws 1280x720 into 1920x1080 window textures with a window crop (a Mac shot shows the crop only with `DM_NVN_SHOT_OUT`); it plays docked with the 3D pad layout (the right stick aims). Mario vs. Donkey Kong is Nintendo's sead / agl / gsys engine on SDK 15.3.0, its base game a cartridge dump (.xci), its cutscenes MP4 files (H.264 and AAC) through the SDK's movie library and an AudioOut of its own: its movie player keeps the outputs it holds in a ring of 32 per track with no bounds check, so AAC movie decoders offer 32 outputs (Vorbis keeps 64), and it sizes its sound buffers by `GetOutputBufferSize` and appends each whole, so an AAC decoder reports one frame (`hle_movie.c`: with 64 outputs the opening movie overran the heap, with the 128 KB answer it froze); it skips clears under render conditions (profile key `render_cond`), sets the advanced blend mode SRC_OVER, which the replay now blends with the Porter-Duff factors it amounts to (`nvn_mtl.m` `adv_blend`), and plays docked with the 2D pad layout. Bayonetta 2 is PlatinumGames' engine in its Wii U form on SDK 3.6.0, the oldest SDK so far, which needed no host change of its own: its files are DAT archives in CRI CPK archives (CRILAYLA-compressed entries, `dm/gfx/tools/cpk.py`, which also reads the masked @UTF tables of Unicorn Overlord's CPKs), its 1,856 shaders BNSH files of one stage each in the shader DATs, which the split decodes from every CPK entry (`shader_split.py` `cpk_jobs`); a cleared attachment larger than the render pass is now cleared in a pass of its own, as NVN clears the whole target (`nvn_mtl.m` `rp_begin_ex_`; the opening narration drew in a corner of a black screen), and `Downsample` of a single-sample texture is a filtered scaling copy that may convert the format (its bloom chain, `copy_t2t`); it plays docked with the 3D pad layout (the camera is on the right stick). Bayonetta is the same engine on Bayonetta 2's `sdk` and `subsdk0` builds and needed no host change: nine CPK archives, its 458 shaders in `shader.dat` of `data00.cpk`, CRI ADX2 sound, the 3D pad layout; its prologue tutorial waits on a Witch Time page until a dodge lands right before the strike (the work folder's `run/witch.py`). [README.md](README.md) is the player-facing page (games, packing, playing); [docs/development.md](docs/development.md) has the internals, the script's options, building by hand and prewarm lists; each game's notes are in `dm/games//README.md` (the [games table](dm/games/README.md#games)). +- This repository runs Nintendo Switch games natively on Apple platforms: the game's own code runs, rewritten only where Apple's kernel requires it (`dm/tools/patch_nso.py`), on a host that answers Horizon's SVCs and the SDK's services (`dm/host`) and implements NVN, Vulkan and NVIDIA's desktop OpenGL on Metal (`dm/gfx/nvn`, `dm/gfx/vk`, `dm/gfx/gl`). Sixty games are playable on the iPhone; the [games table](dm/games/README.md#games) lists each with its build, SDK, engine and graphics API, and links its note, `dm/games//README.md`: what its profile needs and why, what was found on it, how to drive it on the Mac, what is open. [README.md](README.md) is the player-facing page (games, packing, playing); [docs/development.md](docs/development.md) has the internals, the script's options, building by hand, prewarm lists and power. +- Where to start: + + | Task | Read first | + |---|---| + | A crash, abort, hang, `[RED]` line or wrong picture | [docs/agents/symptoms.md](docs/agents/symptoms.md): search it for the text in the log or what you see; it leads to the entry in [docs/reference](docs/reference/README.md) and the games it was found on. Recipes (crash reports, lldb, probes, dumps) are in [05-debugging.md](docs/agents/05-debugging.md) | + | A game that does not reach its frame loop | [03-bringup.md](docs/agents/03-bringup.md) (the run loop, reading the log), then the symptom index | + | Rendering or shaders | [04-graphics.md](docs/agents/04-graphics.md), then [rendering](docs/reference/rendering.md), [shaders](docs/reference/shaders.md) and [NVN objects](docs/reference/nvn-objects.md) | + | Work on one game, or a change to its profile | its note, `dm/games//README.md`, then [dm/games/README.md](dm/games/README.md) for the keys | + | Another Switch title | the NSP script (below), then [docs/agents](docs/agents/README.md) | + | Packing, building, prewarm lists, power | [docs/development.md](docs/development.md) | +- Where a new finding goes, once: a fact about one game (its passes, shader keys, measurements, why its profile needs a key or a cut, its status) in that game's note, in the section it belongs to and marked current or resolved; a general mechanism (a host or tool change that holds for other games) as an entry in its `docs/reference/.md` section (Symptom, Cause, Fix, Found on with the game's note linked), then `python3 dm/tools/docs_symptoms.py` to update the symptom index, and a line under the game note's "Found on this game". Link to a finding instead of repeating it. Adding a game takes its note, its row in the games table and its line in README's games list; this file changes only when a rule does (and the count above). - Each game's constants and shader tables form a game profile in `dm/games//`, where `` is the base game's title ID: `game.tsv` plus the tables (and `patches.py` when the rewriter has patches for its builds). Mario's profile is `010015100B514000`. Tools pick the profile with `DM_GAME`; `make -C dm/host` and `build_ipa.sh` pick it with `GAME`. There is no default game: set it every time (the tools stop and list the profiles otherwise). Put per-game values in the profile, not in code ([dm/games/README.md](dm/games/README.md)). - Prefer general optimizations and fixes to game-specific rules. Performance work (frame pacing, power, caches, load times) counts here as much as correctness. First look for a change that holds for every game: Horizon / NVN / Vulkan semantics, or what the binary, its SDK version, its imports or its files show. Use a game-specific rule (a profile key, a rewriter cut, a build-ID gate, a per-game branch in the host) only when it is absolutely necessary, that is, when no general rule can do it. - Per-build knowledge is keyed by the NSO build ID: rewriter cuts, observation gates and frame-step stubs in the profile's `patches.py` (a game's own modules) or `dm/tools/sdk_patches.py` (Nintendo's `sdk` modules, which several games ship), which `dm/tools/patch_nso.py` (the general rewriter) loads, the profile's `build` rows (the builds the game was brought up on, and their display versions; the NSP script packs any other version from a derived profile, without those builds' fixes), and host behaviour that uses fixed module addresses checks `module_t.build_id` (from `fixups.tsv`; `dm_main_build()`). When a new game build appears, carry addresses over with `dm/tools/nso_fnmap.py` and check them by disassembly; never let fixed addresses run on a build they were not found in. -- To analyse or port **another** Switch title, first run `scripts/build_ipa_from_nsp.py` on its NSP folder. With no matching profile, it writes a survey to `/_survey/survey.md`, then a provisional profile guessed from the game files (`dm/games/profile_guess.py`; `<NSP folder>/<title ID>_build/profile/<title ID>/`, found by the tools through `DM_GAMES_DIR`), with a short heap check on the Mac when the game has virtual address memory, and a best-effort IPA that may not boot. Then follow [docs/agents](docs/agents/README.md) (extraction, binary survey, host bring-up failure catalogue, graphics specifics, debugging recipes) and add a profile. +- To analyse or port **another** Switch title, first run `scripts/build_ipa_from_nsp.py` on its NSP folder. With no matching profile, it writes a survey to `<NSP folder>/<title ID>_survey/survey.md`, then a provisional profile guessed from the game files (`dm/games/profile_guess.py`; `<NSP folder>/<title ID>_build/profile/<title ID>/`, found by the tools through `DM_GAMES_DIR`), with a short heap check on the Mac when the game has virtual address memory, and a best-effort IPA that may not boot. Then follow [docs/agents](docs/agents/README.md) (extraction, binary survey, host bring-up, graphics specifics, debugging recipes; the [symptom index](docs/agents/symptoms.md) for each failure) and add a profile with its note. - Scripts the user runs live in `scripts/` at the repository root. Each one runs from a terminal (non-zero exit on error). `build_ipa_from_nsp.py` also runs in Spyder (F5, configured by the settings block at its top; command-line options override those settings); the others need not. Tools that only agents or other scripts call stay under `dm/`. - Game files, keys and anything extracted from them live in the game's NSP folder, never in this repository and never in the home folder (it is nearly full): the work folder `<NSP folder>/<id>-work` for extraction, runs and analysis, beside the NSP script's `<id>_build/`. Tool venvs and other large scratch data go on that drive too. Do not print key values. - Language: everything is in English: `README.md`, docs, code comments, log messages, script output and other files. Red log lines (something unimplemented or wrong) start with `[RED] `. Text that must match a game's own Chinese data is written as escapes (`"\xE8\xB5\xB7"`), never as characters. diff --git a/dm/games/0100CD801CE5E000/README.md b/dm/games/0100CD801CE5E000/README.md index e8ad848..785ffeb 100644 --- a/dm/games/0100CD801CE5E000/README.md +++ b/dm/games/0100CD801CE5E000/README.md @@ -58,7 +58,7 @@ General host changes first needed here; each applies to every game: - **The GL layer's core-profile paths** (`GL_CONTEXT_PROFILE_MASK`, persistently mapped buffers and fences, attribute bindings and current values, uniform queries, texture storage, 3D and array textures, swizzles, mipmaps, base-vertex and instanced draws, blits) in `hle_gl.m`, and **`DM_GL_STUBS=1`**: [The GL core profile and persistently mapped buffers](../../../docs/reference/rendering.md#the-gl-core-profile-and-persistently-mapped-buffers), [GL uniforms, pipelines and attributes](../../../docs/reference/rendering.md#gl-uniforms-pipelines-and-attributes), [GL textures, swizzles and uploads](../../../docs/reference/rendering.md#gl-textures-swizzles-and-uploads), [GL draws and framebuffers](../../../docs/reference/rendering.md#gl-draws-and-framebuffers), [Which GL functions a game calls](../../../docs/reference/rendering.md#which-gl-functions-a-game-calls). - **`loader_census.py` following registers** (LÖVE's glad), and `vkGetInstanceProcAddr` left out of `profile_guess.py`'s Vulkan count: same section. -- **GL shaders captured at run time** (`DM_GL_CAPTURE`, `gl_capture.tsv`): [development.md, The packing script in detail](../../../docs/development.md#the-packing-script-in-detail). +- **GL shaders captured at run time** (`DM_GL_CAPTURE`, `gl_capture.tsv`, the NSP script's `capture_shaders`): [development.md, The packing script in detail](../../../docs/development.md#the-packing-script-in-detail). - **A renderer opened without an event** starts the render thread (`ar_open`): [A renderer opened without an event](../../../docs/reference/audio.md#a-renderer-opened-without-an-event). - **The Tap pad layout** (`pad_layout touch`, `DmGamepad.m`): [dm/games/README.md](../README.md). diff --git a/dm/tools/docs_symptoms.py b/dm/tools/docs_symptoms.py new file mode 100644 index 0000000..07a6ce0 --- /dev/null +++ b/dm/tools/docs_symptoms.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# Writes docs/agents/symptoms.md, the symptom index, from the subsystem references in docs/reference: one line per entry that has a "- **Symptom:**" bullet (the text of that +# bullet, links reduced to their text) with a link to the entry's heading, grouped by reference file in the order docs/reference/README.md lists them. Run it after adding or +# changing an entry; --check only reports whether the index is current (dm/tools/tests/test_docs.py runs it). +# Usage: docs_symptoms.py [--check] +import re, sys, pathlib, unicodedata + +REPO = pathlib.Path(__file__).resolve().parents[2] +REF = REPO / 'docs' / 'reference' +OUT = REPO / 'docs' / 'agents' / 'symptoms.md' + +def slug(h): + # GitHub's heading anchor: lower case, punctuation dropped except - and _, spaces to - + h = re.sub(r'<[^>]+>', '', h).strip().lower() + h = ''.join(c for c in h if c in ' -_' or unicodedata.category(c)[0] in 'LN') + return h.replace(' ', '-') + +def ref_files(): + # the reference files in the order of the table in docs/reference/README.md + return [REF / m for m in re.findall(r'^\| \[[^\]]+\]\(([a-z-]+\.md)\)', (REF / 'README.md').read_text(), re.M)] + +def entries(path): + # (heading, anchor, symptom) of each ### entry with a Symptom bullet; anchors numbered as GitHub numbers repeated headings + seen, head, fence, out = {}, None, False, [] + for line in path.read_text().split('\n'): + if line.startswith('```'): fence = not fence + if fence: continue + m = re.match(r'(#{1,6}) (.*?)\s*#*$', line) + if m: + s = slug(m.group(2)); n = seen.get(s, 0); seen[s] = n + 1 + head = (m.group(2), s if n == 0 else f'{s}-{n}') if len(m.group(1)) == 3 else None + continue + m = re.match(r'- \*\*Symptom:\*\* (.*)', line) + if m and head: + out.append((head[0], head[1], re.sub(r'\[([^\]]+)\]\([^)]*\)', r'\1', m.group(1).strip()))) + return out + +def index(): + lines = ['# Symptom index', '', + 'One line per symptom in the [subsystem references](../reference/): what you see (an assertion, a `[RED]` line, an abort or crash site, a visible result) and the ' + 'entry that explains it. Search this page for the text in your log first (`grep -i` the function name, the `[RED]` text or the address form), then read the entry and ' + 'the game notes it names. Generated by `dm/tools/docs_symptoms.py` from the entries\' Symptom bullets: change the entry, then run it.', ''] + for p in ref_files(): + e = entries(p) + if not e: continue + title = re.match(r'# (.*)', p.read_text()).group(1) + lines += [f'## {title}', ''] + [f'- {sym} → [{h}](../reference/{p.name}#{a})' for h, a, sym in e] + [''] + return '\n'.join(lines).rstrip() + '\n' + +if __name__ == '__main__': + text = index() + if '--check' in sys.argv[1:]: + if OUT.read_text() != text: sys.exit(f'{OUT.relative_to(REPO)} is out of date: run dm/tools/docs_symptoms.py') + print(f'{OUT.relative_to(REPO)} is current') + else: + OUT.write_text(text) + print(f'{OUT.relative_to(REPO)}: {text.count(" → [")} symptoms') diff --git a/docs/agents/symptoms.md b/docs/agents/symptoms.md index 4929c30..e7c0555 100644 --- a/docs/agents/symptoms.md +++ b/docs/agents/symptoms.md @@ -1,6 +1,6 @@ # Symptom index -One line per symptom in the [subsystem references](../reference/): what you see (an assertion, a `[RED]` line, an abort or crash site, a visible result) and the entry that explains it. Search this page for the text in your log first (`grep -i` the function name, the `[RED]` text or the address form), then read the entry and the game notes it names. +One line per symptom in the [subsystem references](../reference/): what you see (an assertion, a `[RED]` line, an abort or crash site, a visible result) and the entry that explains it. Search this page for the text in your log first (`grep -i` the function name, the `[RED]` text or the address form), then read the entry and the game notes it names. Generated by `dm/tools/docs_symptoms.py` from the entries' Symptom bullets: change the entry, then run it. ## Rewriter diff --git a/scripts/build_ipa_from_nsp.py b/scripts/build_ipa_from_nsp.py index e4d430f..f370441 100755 --- a/scripts/build_ipa_from_nsp.py +++ b/scripts/build_ipa_from_nsp.py @@ -597,7 +597,7 @@ def survey(nsp_dir, app, fam, keys, repo, py, env): notes.append('no BNSH / BAHS at the top level: the shaders are inside compressed or other containers or in main, and must be located first') major = {v.split('_')[0] for v in F['sdkver']} if not major: notes.append('no SDK version string found') - elif major != {'16'}: notes.append(f'SDK {"/".join(sorted(major))}, not Mario\'s 16: NVN object sizes, API version and service layouts differ by SDK (the host was converted from D2R\'s SDK 12 to SDK 16 sizes); check them one by one per docs/agents/03') + elif major != {'16'}: notes.append(f'SDK {"/".join(sorted(major))}, not Mario\'s 16: NVN object sizes, API version and service layouts differ by SDK (the host was converted from D2R\'s SDK 12 to SDK 16 sizes); check them one by one per docs/reference/nvn-objects.md') if any(l.split()[:1] == ['nn::ro'] for l in R): nro, nrr = rom['exts']['.nro'], rom['exts']['.nrr'] # NRR files register NROs by hash; with none loose the NROs are inside an archive (Super Smash Bros. Ultimate: 14 NRRs, 98 NROs in data.arc, whose exports main imports) @@ -607,7 +607,7 @@ def survey(nsp_dir, app, fam, keys, repo, py, env): R += ['', '## What it means for a port', ''] + [f'- {n}' for n in notes or ['—']] R += ['', '## Provisional profile keys (dm/games/profile_guess.py)', '', 'What the game files decide; mem_used follows the rewrite, and a game with virtual address memory that sets a heap gets a heap check on the Mac (scripts/build_ipa_from_nsp.py heap_check).', '', '```'] R += [f'{k}\t{v}' + (f'\t# {why}' if why else '') for k, (v, why) in G.items()] + ['```'] - R += ['', 'Next steps: docs/agents (02 survey, 03 bring-up).'] + R += ['', 'Next steps: docs/agents (02 survey, 03 bring-up, symptoms.md for each failure).'] (work / 'survey.md').write_text('\n'.join(R) + '\n') for n in notes: say(n) say(f'survey report: {work / "survey.md"}')