
- Title ID
- 0100606026F12000
- Tested build
- 1.0.1
- Nintendo SDK
- 20.5.17
- Engine
- Unreal Engine 5.6.1
- Graphics API
- NVN
Build this game
Keep [0100606026F12000] in your file names, put the base game, update and DLC in one folder and run the script. Follow the build guide, then the sideloading guide. Needs an iPhone 15 Pro or newer, or an M1 iPad or later.
Technical notes
The project's own record of bringing this game up: what its profile needs and why, what was found and what is still open.
Update 1.0.1 (v65536) over the base game, SDK 20.5.17. Square Enix, Unreal Engine 5.6.1 (SDK MW+EpicGames+UnrealEngine-5_6_1 in main; the desktop renderer, SF_GLSL_SWITCH shaders), NVN, CRIWARE sound. Modules main, subsdk0 (the movie module, which main imports from) and sdk; sdk and subsdk0 are the builds STEINS;GATE RE:BOOT runs (01008BC024FDE000). Plays on the iPhone; 60 fps (30 on the Switch) by the host's write of rhi.SyncInterval.
Status: brought up on the Mac: the first-boot brightness screen, the title, a new game (Normal difficulty), the opening cutscene, the first field (Battlefield) with its autosave, Continue from that save, and the first two battles, at 60 fps with no red line apart from one moment of the new game's load (see Open). Playable on an iPhone.
What it needs
| Setting | Where | Why |
|---|---|---|
fps_target 60 |
game.tsv, hle_fpslock.c |
the host writes 1 into rhi.SyncInterval; see Frame rate |
FMallocBinned3 constructor call cut, main+0x137f0 bl 0xae770 → nop, with gate OBS_UE_MALLOC on it |
the rewriter's engine-wide rule (dm/tools/ue_malloc.py), no build ID |
the constructor reserves 38 GB of address space for its pools; the host runs Unreal's heap (hle_uemalloc.c, the UE 5.6 layout); see Memory. This build's own cut until 2026-10-30 |
Engine region given what the alias region has room for, 4.06 GB: gate OBS_UE_REGION on the store of its size, main+0x137cc |
the rewriter's engine-wide rule (dm/tools/ue_region.py), no build ID; the host's room (hle_svc.c) |
Unreal's nninitStartup reserves 62 GB by default. Until 2026-10-31 game_args -SwitchVirtualAddressSpaceSize=4064. The engine's page allocator never gives address space back: a 2 GB region aborted in the first battle |
Node area cut 1 GB → 62 MB (sdk+0x145cbc / +0x145d28) |
dm/tools/sdk_patches.py SDK-wide rules, sdk build 0023a1b0… |
as on the other Unreal games' SDKs; this build aligns regions of 2 MB and more to 2 MB only, so it needs no alignment cut |
Engage's 5 GB block: mem_reserve 0x140000000, mem_alias 0x118000000, mem_stack 0x10000000, app_pool 0xCD500000 |
game.tsv |
256 MB in front + 4.375 GB alias region + 256 MB stack region + 128 MB heap region, as DRAGON QUEST XI S's |
sys_resource 0x1400000 |
game.tsv |
the NPDM declares 20 MB of system resource |
tex_dyn_slots on |
game.tsv |
2,077 particle vertex shaders pick their texture slot at run time; see Shaders |
Integer output row c254eaa40e0c4f1e (fout 0 uint) |
shader_facts_manual.tsv |
CopyStencilToLightingChannelsPS (DownsampleDepthPixelShader.usf, global shader 1627) writes the stencil's lighting channel bits to an R8Uint target: without it the lighting channels stayed empty and the sprites showed as dark silhouettes (seen in the first scene after the opening cutscene) |
pad_layout full |
game.tsv |
a 3D field whose camera the right stick tilts and turns. It plays docked (1920x1080) |
Frame rate
- Current: 60 fps (30 on the Switch). The engine's Switch device profile sets
rhi.syncinterval=2(BaseSwitchDeviceProfiles.ini), so the game asks for present interval 2; the game sets not.MaxFPS. The host writesrhi.SyncInterval1 into its console variable (hle_fpslock.c) whenever the game sets it back (at level loads), so the game asks for present interval 1 itself. - Unreal steps the game by the time that really passed:
DM_NVN_REGLUMtraces of the same keys from the same save at 30 and 60 fps (59.8 fps, frame time p99 18 ms on the Mac) match from the load through a 3 s walk (thumbnail difference ≤ 0.006). DM_FPS_TARGET=30(Documents/env.txton the iPhone) keeps the game's 30 and saves power.
Memory
- Current: the allocator is the host's.
nninitStartup(main+0x13370) reserves one 62 GB region for the engine (main+0x137d0; the rewriter's gate on the size's store gives it what the alias region has room for, Unreal's engine address region and its options), then constructs its FMallocBinned3 in place in the bss, as Octopath Traveler 0's (01005270232F2000): the call atmain+0x137f0→main+0xae770onmain+0x77c6210, whose constructor would reserve 38 GB for its pools (main+0xae860). The call is cut by the rewriter's engine-wide rule (dm/tools/ue_malloc.py), and at the gate on it the host puts its allocator's vtable in the object (hle_uemalloc.c; until 2026-10-30 before any guest code ran).FPlatformMemory::BaseAllocator(main+0x13c64) returns the object and the thread-local storage allocator (main+0x13830/main+0x13870) calls its Malloc / Free slots; its pool base (main+0x7c148a0) and page size (main+0x7c14800) are read only by the allocator's own code (main+0xae024tomain+0xb4568). - UE 5.6's FMalloc has 32 slots (layout
UE5_6: Malloc 6 … Free 10, QuantizeSize 13, GetAllocationSize 14). UE 5.5 and later addMallocZeroed/TryMallocZeroedafterFree, two TLS cache calls (MarkTLSCachesAsUsed/Unused) and two cached-memory queries. The base vtable shows it: its three pure slots are 0, and its slots 11 and 12 are one function that calls slot 6, thenmemset. - Resolved: a 2 GB region (DRAGON QUEST VII Reimagined's) aborted in the first battle (
nn::diagabort frommain+0x11bc, no message). The engine hands its region out with a page allocator of its own (main+0x13ef0): a bump pointer plus free lists per size class, so the address space it has used only grows, and past the region's end it callsOnOutOfMemory(main+0x13c74), with about 1 GB mapped. Probed where each new block comes back (DM_PROBE=main:13fcc: x0 the block, x21 its size, x8 the region's end) from the save in the first field through the first battle: a 1 GB block at boot from a static constructor, the high-water mark at +1.50 GB 15 s in, +1.77 GB at 56 s, +2.02 GB at 166 s, level at +2.03 GB over seven minutes (2.03 GB of address space, about 1 GB of it mapped). - Current: a 4.06 GB region (4064 MB from
game_argsuntil 2026-10-31). This SDK build aligns regions of 2 MB and more to 2 MB only (VammManager::AllocateAddressRegion,sdk+0x145e00), so the region starts right after the node area, in Engage's 5 GB block, as DRAGON QUEST XI S's (01006C300E9F0000). - On the Mac in the field and the first battles: about 2.3–2.6 GB resident and 1.2–1.6 GB of Metal allocations, flat over seven minutes (development.md);
game.tsvgives, for the field and the first battle, about 1 GB of the alias region mapped, an Unreal heap of 1 GB, 2.2–2.4 GB resident and 1.2–1.5 GB of Metal allocations.
Shaders
- 73,496 unique shaders (9,253 vertex, 61,305 pixel, 2,934 compute, 4 geometry) in the container's two IoStore shader libraries (3,542 + 70,702 shaders), grouped as
globalandgame. They hold the sameLSLGrecords as the other Unreal games. Each shader entry packs its frequency (bits 0–3), group (4–33) and offset in the group's decoded bytes (from bit 34), and a shader runs to the next one's offset;uepak.pywrites the libraries out as version 2 archives, so the split reads them as any other. Every vertex shader is made to link with any pixel shader (shader_split.pyseparable_pairs). - Current: 2,077 particle vertex shaders (Unreal's particle vertex factories) pick one of several c2 table slots at run time: texture table words 0x168-0x1c0 and sampler table words 0x568-0x590, and no other word of c2, so
tex_dyn_slotsis right here. Without the key the first one aborted the host in a double free (now fixed). - Resolved: texture reads removed by the translator (GitHub issue #168). 58,122 reads, 57,861 of them texture-buffer reads in Unreal's Niagara particle vertex factories, reached their handle through a phi that also carried non-handle values, and bindless elimination replaced them with 0: particle scale 0, so fire, smoke and muzzle-flash meshes collapsed to a point. 58,096 of them in 3,197 shaders are now resolved; 26 in 14 shaders, whose handle comes from no c2 word, are still removed. The SPIR-V of the 69,449 other shaders is unchanged. See Bindless reads whose handle phi also carries other values.
- Current: particle data in buffer textures at offsets 4, 8 and 12 past a 16-byte boundary (pool offset
0x2fd208, for one), which an Apple GPU reads from the boundary below. They are read through an aligned copy (Buffer textures at offsets below 16 bytes). - Unbuilt: 11 shaders, none met so far: 3 geometry shaders and 8 compute shaders with 3D image atomics. Image atomics are native texture atomics since chain
0ce4b5e8aefb, which build 3D ones on Octopath Traveler 0 and Ghost Trick (Image atomics); this game's library has not been rebuilt on it yet.
Files
- Everything is in one IoStore container,
FFRS/Content/Paks/FFRS-Switch.utoc/.ucas(6 GB, 63,910 chunks, Oodle in 64 KB blocks, not encrypted;.ucasis the profile'sromfs_marker), besideFFRS-Switch.pak(2.9 GB, 2,363 files: configs, fonts, sound banks; version 11, index not encrypted, Oodle), aglobal.utocholding only the script objects, and the RomFS'sManifest_UFSFiles_Switch.txt, which lists every file the engine sees, the shader archives among them. - The pak has no shader code archive: the cooker converted both libraries into the container (EIoChunkType 8
ShaderCodeLibrary, one chunk each, the tables; type 9ShaderCode, one chunk per shader group). A TOC (version 8) is a 144-byte header, the chunk IDs (12 bytes: hash, index, type), offsets and lengths (5 + 5 bytes big-endian), perfect hash seeds, the compression blocks (5-byte offset, 3-byte stored and raw sizes, method) and the method names; a library chunk's ID is CityHash64 of<library>-<format>lowercased in UTF-16 (global-sf_glsl_switch-sp_glsl_switch). - The tables and each group are Oodle-compressed, two layers, so
uepak.py(Toc,io_archive) reads them through the game's own decoder in the Mac host's tool mode (OodleHost,DM_OODLE_SERVE; served before the SDK starts, no boot) and writes each library as a version 2 archive with every shader decoded: 707 MB in about 40 s. - Checked 2026-10-28: the IoStore patch-container resolver preserves this game's single-container extraction on update v65536. Both decoded archives match the original reader byte for byte: 3,542 + 70,702 shader entries, 73,496 distinct control-and-code keys, 707,636,257 archive bytes; both readers together took 39.4 s through the existing Mac host's Oodle service. This checks extraction only; gameplay was not rerun for this change. The full
regress.py checkwas not completed (cold shader rebuild). - The pak stores block size 0 for a file of one block (
Pak.entry).
Rewriter
main has 94 MB of text, 95% of it without unwind entries: 82 MB in one stretch (main+0x6c4f88–+0x59220c0) and the 6.65 MB before the first FDE (none before main+0x6a75d4).
- Resolved: a
jump table <addr>assertion on an ordinary byte table near the start of the text (main+0x219234,adr x18; ldrb w0,[x17,x16]; add x18,x18,x0,lsl #2; br x18).func_ofmade every site before the first FDE a function of one instruction, so no form saw theadrin front of thebr. The text before the first FDE is now one function, from the end of the data block before it (or the text's start), as a gap between two FDEs already was. - Resolved: the rewrite never finished, the Mac swapped, the rewriter's footprint reached 100 GB. A stretch without FDEs is one function to
func_of, and its detailed disassembly costs about 880 bytes an instruction once built (2.6 KB while capstone builds it): 21.6 million instructions. A function longer than 256 KB is now analysed in pieces (piecesinpatch_nso.py), cut only right after aret/br/bwhere no conditional branch and nobwithin 64 KB crosses. Countingbup to 1 MB left only 21,385 cut points in the 82 MB (tail calls between neighbours) and pieces of up to 6.5 MB; with 64 KB there are 185,228, and no piece is over 256 KB (the two stretches: 28 and 344 pieces). The rewrite then takes 13 minutes at about 2.7 GB resident on a loaded Mac. It came out the same as withbcounted up to 1 MB (3 and 136 pieces), so the analysis does not depend on where the cuts fall.
Mac routes
- The first boot shows the brightness screen. From a fresh save,
DM_KEYS="f600:A,f1500:A,f1700:A,f1850:Up,f1950:A,f2400:A*36/150,f7950:B,f8100:B,f8250:B,f8400:B"takes New Game, Normal, "Yes", the opening and the tutorials into the first field (about frame 9,600 at 30 fps; B closes a tutorial on its last page, the stick turns its pages). - From a save made there,
DM_KEYS="52:A,58:A,62:A"continues into the field;70:LUp~4walks north to the first battle. The work folder'srun/save_battlefieldholds such a save.
Found on this game
General host changes first needed here; each applies to every game:
- UE 5.6's 32-slot FMalloc (
hle_uemalloc.clayoutUE5_6): Unreal's FMallocBinned3 replaced by the host allocator. - IoStore shader libraries read through the game's own Oodle with no boot (
uepak.pyToc,io_archive,OodleHost,DM_OODLE_SERVE): 01, Compressed RomFS content, Unreal Engine 5.6 IoStore shader libraries. - Rewriter: text before the first FDE as one function (
func_of) and long functions analysed in pieces (pieces): Code before the first unwind entry is one function, Long functions analysed in pieces. - Bindless reads through a phi that also carries non-handle values (the vendored translator's
TryTableHandle): Bindless reads whose handle phi also carries other values. - Buffer texture views below the 16-byte alignment read through an aligned copy (
tbuf_shadow): Buffer textures at offsets below 16 bytes.
Open
- The engine region's gate (4.06 GB instead of the 4064 MB
game_argsgave) has not been run on this game yet: the[mem] Unreal's engine regionline in a boot's log shows it. - Known: once in two runs, the frames that begin the new game's load bind 2D textures where a base pass shader declares 3D ones (red
texture type mismatchlines, under a black screen). The host now binds the zero 3D texture there (A 2D texture in a 3D slot binds the zero 3D texture); not run on this game since. - 11 shaders do not build (3 geometry, 8 compute with 3D image atomics); none met so far.
- Resolved: the image-atomic guards of
c3a0880(2026-10-25) broke the opening. 842 more compute shaders did not translate (MSL 2.3 image atomics require IGN with a discarded result;41b20b5f09269ad8is dispatched in the opening), and a new game aborted the host at the opening cutscene (FATAL [RED] image atomics: unsupported or overflowing IMG extent, compute shaderf3c5ced35cb619d0, image slot 2, texture ID 2142). Since2e655b7translates image atomics whose result is used, chain39e4d9585b1dbuilds all but the 8 listed above, and a new game from an empty save plays the opening cutscene: both shaders run their atomics directly on their buffer textures (target 10, 8 KB). Checked on the Mac on 2026-10-26.
These notes are republished from the nxnative repository under its MIT license (license and attribution). The original, which may be newer, is the game's notes on GitHub. This site hosts no game files.