FINAL FANTASY RESONANCE plays on the Mac at 60 fps: profile 0100606026F12000 (update 1.0.1, SDK 20.5.17)

- Engine: Unreal Engine 5.6.1 (Square Enix, the desktop renderer), the newest Unreal so far; everything in one IoStore container beside one pak; the sdk and subsdk0 builds are STEINS;GATE RE:BOOT's
- Rewriter, for every module: code before the first unwind entry is one function (each site there was a function of one instruction, so a byte jump table at main+0x219234 went unseen), and a function longer than 256 KB is analysed in pieces cut after a ret / br / b that no conditional branch or b within 64 KB crosses, with far branches and jump table targets checked against windows and retry blocks afterwards (this main's 82 MB without unwind entries took 100 GB of memory as one function; now 2.7 GB resident). Checked: DRAGON QUEST VII Reimagined's main byte-identical to its rewrite by the previous patch_nso.py; FINAL FANTASY RESONANCE's, Octopath Traveler 0's and DRAGON QUEST XI S's byte-identical between two different cut placements; only DQ VII's main among the 120 modules of 33 games on hand has other sites before its first unwind entry (none in a window or jump table)
- Shaders: no archive in the pak; the two libraries are IoStore shader library chunks whose tables and shader groups are Oodle-compressed, read through the game's own Oodle in the Mac host's tool mode with no boot (uepak.py Toc / io_archive / OodleHost; library names from CityHash64 of the manifest's archive names) and written as decoded version 2 archives; the NSP script uses them for any game with IoStore shader libraries. The pak's single-block files store block size 0 (Pak.entry). 73,496 unique shaders; 11 do not build (3 geometry, 8 compute with 3D image atomics), none met so far. 2,077 particle vertex shaders pick their texture slot at run time (tex_dyn_slots); CopyStencilToLightingChannelsPS writes integers to an R8Uint target (shader_facts_manual.tsv; without it the sprites were dark silhouettes)
- Host, for every game: a shader with run-time texture handles in a game without tex_dyn_slots freed its reflection twice and aborted the host (nvn_mtl.m); oe::MuteRecording / UnmuteRecording (no-ops)
- Memory: the FMallocBinned3 constructor call is cut and the host's allocator runs Unreal's heap (hle_uemalloc.c layout UE5_6: UE 5.5+ FMalloc has 32 slots, with MallocZeroed and two more TLS cache calls); the SDK's node area is cut to 62 MB. The engine's own page allocator only grows its use of the engine region (2.03 GB of address space over seven minutes, about 1 GB mapped), and a 2 GB region aborted in the first battle, so the region is 4064 MB in Engage's 5 GB block (this sdk build aligns regions to 2 MB, no alignment cut needed)
- Frame rate: 60 fps (30 on the Switch): the host writes rhi.SyncInterval 1 (hle_fpslock.c rows for this build; profile fps_target 60); the game steps by real time (DM_NVN_REGLUM traces at 30 and 60 fps match from the load through a walk)
- Mac: first-boot settings, the title, a new game, the opening cutscene, the first field and its autosave, Continue, and the first two battles at 59.7 fps, 2.3-2.6 GB resident, flat over seven minutes, no red line apart from a few texture type mismatches at one moment of the new game's load in some runs. Docs: docs/agents 01, 03, 04 and development.md
This commit is contained in:
seedds authored and MacMini committed 2026-10-23 07:22:09 +08:00
1 parent 0b3c7b1200
commit 2000f9274a
23 files changed
+159450 -18

No files matched your search

+1
View File
@@ -0,0 +1 @@
# generated
1 # generated
+26
View File
@@ -0,0 +1,26 @@
# chain b-650bb0cacdba 2026-10-23T06:37:35+08:00
114ba379d35f98ac
12041d2a0d717938
18ccc110f0abf48e
1c69a5865b200713
1d0e296df7df9d2e
1e31184b70862647
327581a0d46e0817
45cae1f83852724f
61308412eb911f79
83210ea2c1575566
853b5fb1bbaebd54
88ba8b2a90149c97
894f7cbca8f53c2b
922273c0c61517ce
9390e020cb6333e4
9907e4343ceb38cc
b28cebe353a212a8
b9907381cf1fbff5
ce7c8c914188744f
cf75f8c9fb510b28
ef9ee1bbe3495128
f3a188919ebbd946
f4db3f54ae52c90a
f741f3837e362c3e
fc0dc911a2850953
1 # chain b-650bb0cacdba 2026-10-23T06:37:35+08:00
2 114ba379d35f98ac
3 12041d2a0d717938
4 18ccc110f0abf48e
5 1c69a5865b200713
6 1d0e296df7df9d2e
7 1e31184b70862647
8 327581a0d46e0817
9 45cae1f83852724f
10 61308412eb911f79
11 83210ea2c1575566
12 853b5fb1bbaebd54
13 88ba8b2a90149c97
14 894f7cbca8f53c2b
15 922273c0c61517ce
16 9390e020cb6333e4
17 9907e4343ceb38cc
18 b28cebe353a212a8
19 b9907381cf1fbff5
20 ce7c8c914188744f
21 cf75f8c9fb510b28
22 ef9ee1bbe3495128
23 f3a188919ebbd946
24 f4db3f54ae52c90a
25 f741f3837e362c3e
26 fc0dc911a2850953
File diff suppressed because it is too large. Load diff
+2
View File
@@ -0,0 +1,2 @@
// Generated file (dm/gfx/tools/dmsh_chain.py --header, game 0100606026F12000): the B shader library translation chain version this source tree expects (default options); the host loads only <library dir>/b-<V>/
#define DMSH_CHAIN "650bb0cacdba"
+65
View File
@@ -0,0 +1,65 @@
# FINAL FANTASY RESONANCE: Square Enix, Unreal Engine 5.6.1 (SDK MW+EpicGames+UnrealEngine-5_6_1 in main; the desktop renderer, SF_GLSL_SWITCH shaders), SDK 20.5.17, NVN, CRIWARE sound;
# update 1.0.1 (v65536). Everything is in one IoStore container beside one pak; the shader libraries are IoStore chunks (uepak.py).
# Started from the NSP script's provisional profile (dm/games/profile_guess.py) and brought up on the Mac (docs/agents)
# Format: see dm/games/README.md
# ---- Identity ----
title_id 0100606026F12000
program_id 0100606026F12000
# ---- App ----
# Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
name FINAL FANTASY RESONANCE
ipa FinalFantasyResonance.ipa
app_category public.app-category.games
# ---- Versions ----
build v65536 1.0.1 main=84e182f72ee1da4f477804bd05b54c15 subsdk0=ab62fe8edd07a6a293f885d54359e559 sdk=0023a1b06130cd795f7bc4613772af0a
package v65536
shader_versions v65536
# ---- Files ----
# load order as on Horizon: main, subsdk*, sdk (subsdk0 = NintendoSDK_movie, which main imports from; the sdk is STEINS;GATE RE:BOOT's build)
modules main subsdk0 sdk
romfs_marker FFRS/Content/Paks/FFRS-Switch.ucas
zip_store .pak:.ttf:.txt:.ucas:.utoc:.metallib
# ---- Graphics ----
# 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, and the game sets no t.MaxFPS.
# The host writes rhi.SyncInterval 1 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_REGLUM traces 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.txt on the iPhone) keeps the game's 30 and saves power
fps_target 60
# ---- Memory ----
# Unreal Engine 5.6.1: nninitStartup (main+0x13370) reserves one 62 GB region for the engine (-SwitchVirtualAddressSpaceSize= in MB up to 4095, game_args below), and its FMallocBinned3
# (38 GB of address space) is constructed in place in the bss; the constructor call is cut and the host runs Unreal's heap on a malloc zone (patch_nso.py, hle_uemalloc.c layout UE5_6),
# and the SDK's node area is cut to 62 MB. The engine hands the region out with a page allocator of its own (main+0x13ef0): a bump pointer, and freed blocks kept in free lists by size
# class, so its address use only grows, and past the region's end OnOutOfMemory (main+0x13c74) aborts. Probed at main+0x13fcc from the save in the first field, through the first battle:
# a 1 GB block at boot (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; a 2 GB region (DRAGON QUEST VII Reimagined's) aborted in that
# battle. 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: a 4064 MB region
# in Engage's 5 GB block (256 MB in front + 4.375 GB alias region + 256 MB stack region + 128 MB heap region), as DRAGON QUEST XI S's. On the Mac in 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
game_args -SwitchVirtualAddressSpaceSize=4064
mem_reserve 0x140000000
mem_alias 0x118000000
mem_stack 0x10000000
app_pool 0xCD500000
# the NPDM declares 20 MB of system resource
sys_resource 0x1400000
# ---- Input ----
# A 3D field whose camera the right stick tilts and turns: the 3D pad layout. It plays docked (1920x1080)
pad_layout full
# ---- NVN ----
# 2,077 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. The translator cannot trace them to one slot: slot indices in c2, the bound slots in its arrays (without the key the first one aborted the host in a double free, now fixed)
tex_dyn_slots on
# ---- Shader split ----
# The two shader libraries of the IoStore container FFRS-Switch.utoc / .ucas (no shader code archive in the pak): the engine's global shaders and the game's material shaders. Their tables and
# shader groups are Oodle-compressed, which the game's own decoder undoes in the Mac host's tool mode (uepak.py Toc / io_archive / OodleHost, no boot). Unreal combines vertex and pixel
# shaders per draw from its pipeline state, so every vertex shader is made to link with any pixel shader (shader_split.py separable_pairs)
shader_groups ShaderArchive-Global-=global ShaderArchive-=game
shader_note FINAL FANTASY RESONANCE shaders
1 # FINAL FANTASY RESONANCE: Square Enix, Unreal Engine 5.6.1 (SDK MW+EpicGames+UnrealEngine-5_6_1 in main; the desktop renderer, SF_GLSL_SWITCH shaders), SDK 20.5.17, NVN, CRIWARE sound;
2 # update 1.0.1 (v65536). Everything is in one IoStore container beside one pak; the shader libraries are IoStore chunks (uepak.py).
3 # Started from the NSP script's provisional profile (dm/games/profile_guess.py) and brought up on the Mac (docs/agents)
4 # Format: see dm/games/README.md
5 # ---- Identity ----
6 title_id 0100606026F12000
7 program_id 0100606026F12000
8 # ---- App ----
9 # Game name: only a label for profile listings and the Mac window title; the iOS app name is the game name in the base NSP, and the bundle ID comes from the base game title ID (build_ipa.sh / NSP script)
10 name FINAL FANTASY RESONANCE
11 ipa FinalFantasyResonance.ipa
12 app_category public.app-category.games
13 # ---- Versions ----
14 build v65536 1.0.1 main=84e182f72ee1da4f477804bd05b54c15 subsdk0=ab62fe8edd07a6a293f885d54359e559 sdk=0023a1b06130cd795f7bc4613772af0a
15 package v65536
16 shader_versions v65536
17 # ---- Files ----
18 # load order as on Horizon: main, subsdk*, sdk (subsdk0 = NintendoSDK_movie, which main imports from; the sdk is STEINS;GATE RE:BOOT's build)
19 modules main subsdk0 sdk
20 romfs_marker FFRS/Content/Paks/FFRS-Switch.ucas
21 zip_store .pak:.ttf:.txt:.ucas:.utoc:.metallib
22 # ---- Graphics ----
23 # 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, and the game sets no t.MaxFPS.
24 # The host writes rhi.SyncInterval 1 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
25 # the game by the time that really passed: DM_NVN_REGLUM traces 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
26 # through a 3 s walk (thumbnail difference ≤ 0.006). DM_FPS_TARGET=30 (Documents/env.txt on the iPhone) keeps the game's 30 and saves power
27 fps_target 60
28 # ---- Memory ----
29 # Unreal Engine 5.6.1: nninitStartup (main+0x13370) reserves one 62 GB region for the engine (-SwitchVirtualAddressSpaceSize= in MB up to 4095, game_args below), and its FMallocBinned3
30 # (38 GB of address space) is constructed in place in the bss; the constructor call is cut and the host runs Unreal's heap on a malloc zone (patch_nso.py, hle_uemalloc.c layout UE5_6),
31 # and the SDK's node area is cut to 62 MB. The engine hands the region out with a page allocator of its own (main+0x13ef0): a bump pointer, and freed blocks kept in free lists by size
32 # class, so its address use only grows, and past the region's end OnOutOfMemory (main+0x13c74) aborts. Probed at main+0x13fcc from the save in the first field, through the first battle:
33 # a 1 GB block at boot (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; a 2 GB region (DRAGON QUEST VII Reimagined's) aborted in that
34 # battle. 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: a 4064 MB region
35 # in Engage's 5 GB block (256 MB in front + 4.375 GB alias region + 256 MB stack region + 128 MB heap region), as DRAGON QUEST XI S's. On the Mac in the field and the first battle:
36 # 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
37 game_args -SwitchVirtualAddressSpaceSize=4064
38 mem_reserve 0x140000000
39 mem_alias 0x118000000
40 mem_stack 0x10000000
41 app_pool 0xCD500000
42 # the NPDM declares 20 MB of system resource
43 sys_resource 0x1400000
44 # ---- Input ----
45 # A 3D field whose camera the right stick tilts and turns: the 3D pad layout. It plays docked (1920x1080)
46 pad_layout full
47 # ---- NVN ----
48 # 2,077 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
49 # word of c2. The translator cannot trace them to one slot: slot indices in c2, the bound slots in its arrays (without the key the first one aborted the host in a double free, now fixed)
50 tex_dyn_slots on
51 # ---- Shader split ----
52 # The two shader libraries of the IoStore container FFRS-Switch.utoc / .ucas (no shader code archive in the pak): the engine's global shaders and the game's material shaders. Their tables and
53 # shader groups are Oodle-compressed, which the game's own decoder undoes in the Mac host's tool mode (uepak.py Toc / io_archive / OodleHost, no boot). Unreal combines vertex and pixel
54 # shaders per draw from its pipeline state, so every vertex shader is made to link with any pixel shader (shader_split.py separable_pairs)
55 shader_groups ShaderArchive-Global-=global ShaderArchive-=game
56 shader_note FINAL FANTASY RESONANCE shaders
@@ -0,0 +1,2 @@
# Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
# Provisional profile: empty until recorded on the Mac (DM_PSO_MANIFEST=off DM_PSO_RECORD).
1 # Prewarm seed list (shipped in the app, next to shader_map.tsv). Rows: S<TAB>shader key<TAB>stage / R<TAB>render pipeline key (pso_skey hex) / C<TAB>compute shader key.
2 # Provisional profile: empty until recorded on the Mac (DM_PSO_MANIFEST=off DM_PSO_RECORD).
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,6 @@
# Hand-written translation facts, merged over the generated rows of shader_facts.tsv by dm/gfx/tools/shader_split.py: key <TAB> facts JSON (same format)
# Unreal's GLSL_SWITCH shaders carry no fragment output types, so the translator declares every output float; Metal refuses a pipeline whose float output meets an integer attachment
# (the host's red line then prints the row to add here).
# c254eaa40e0c4f1e (ShaderArchive-Global-SF_GLSL_SWITCH-SP_GLSL_SWITCH shader 1627, pixel: DownsampleDepthPixelShader.usf CopyStencilToLightingChannelsPS): writes the stencil's lighting
# channel bits to an R8Uint target (seen in the first scene after the opening cutscene)
c254eaa40e0c4f1e {"fout":{"0":"uint"}}
Can't render this file because it contains an unexpected character in line 6 and column 19.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,2 @@
# kind: evidence control-section key conflict rulings: empty
dm_ctl_sha16 chosen_pc class candidates records
1 # kind: evidence control-section key conflict rulings: empty
2 dm_ctl_sha16 chosen_pc class candidates records
File diff suppressed because it is too large. Load diff