Files

10 KiB
Raw Permalink Blame History

OCTOPATH TRAVELER II (0100A3501946E000)

Update 1.1.1 (v131072) over the base game, SDK 12.3.2 (its own sdk build). Square Enix / Acquire, Unreal Engine 4.27 (++UE4+Release-4.27): everything in one pak with the shaders inline in the cooked packages, NVN, PhysX, CRIWARE (ADX2 sound through a 6-channel AudioOut, USM movies). Modules main, subsdk0 (the movie module) and sdk. 60 fps (30 on the Switch) by the host's writes of Unreal's frame limits.

Status: brought up on the Mac: the boot, the initial settings, the title, a new game, the traveler selection (the stick's quill cursor and L / R), Hikari's opening into his first battle (the battle tutorials, a won battle and the scene after it) and Castti's opening (the flashback, the ship, free play on the deck: talking, the Path Action tutorial), at 60 fps (frame time p50 16.7 ms, p95 about 17 ms) with CRIWARE sound; about 2.8 GB resident with 1.4–1.9 GB of Metal allocations in the battle. Plays on the iPhone (IPA 5.9 GB, user, 2026-10-25), also with the geometry split and the Unreal-heap thread stacks below (16f4889).

What it needs

Setting Where Why
fps_target 60 game.tsv, hle_fpslock.c the host writes 60 into t.MaxFPS (main+0x76fef58) and 1 into rhi.SyncInterval (main+0x75ad0d0); see Frame rate
FMallocBinned3 constructor cut: main+0xbecc08 bl 0xc4d778 → nop patches.py, main 0d964901… its pools are cut out of the engine region; see Memory
Observation gate OBS_UE_MALLOC at main+0xbecc0c (orig mov x0, x19) patches.py puts the host allocator's vtable into the new object in x19 (hle_uemalloc.c layout UE4_27)
game_args -SwitchVirtualAddressSpaceSize=2048 game.tsv a 2 GB engine region instead of 62 GB
Node area cut 1 GB → 62 MB (sdk+0x346190 / +0x34622c) dm/tools/sdk_patches.py, sdk build 5bcf525f… the AddressRegionManager constructor at sdk+0x346180; the engine region then starts 62 MB into the alias region
3.75 GB guest block: mem_reserve 0xF0000000, mem_alias 0xC8000000, mem_stack 0x10000000, app_pool 0xCD500000 game.tsv as DRAGON QUEST VII Reimagined: alias region = node area + up to 1 GB of alignment + the 2 GB region
sys_resource 0x5000000 game.tsv the NPDM declares 80 MB of system resource
pad_layout full game.tsv the battle menu picks a weapon with the D-pad. It reads the controller only, so it stays docked (1920x1080)
shader_groups GlobalShaderCache=global #Engine/=engine #Majesty/=game game.tsv the global shader cache, the engine's materials and the game's

Frame rate

  • Current: 60 fps (30 on the Switch). The game's Switch device profile (Majesty/Config/Switch/SwitchDeviceProfiles.ini) sets t.MaxFPS=30 and rhi.syncinterval=2 (its Windows, PS4 and PS5 profiles set t.MaxFPS=60; there is no fixed frame rate). As for Octopath Traveler 0 and DRAGON QUEST VII Reimagined, the host writes 60 and 1 into the two console variables whenever the game sets them back (registered at main+0x2e50444 and main+0x134d380; the TAutoConsoleVariable holds at +0x10 the pointer to its value, the game thread's copy then the render thread's).
  • The same scripted route into Hikari's battle at 30 and at 60 fps (frame time p50 33.4 → 16.7 ms, run/ab.sh) reached the same scenes at the same times: Unreal steps the game by the time that really passed. DM_FPS_TARGET=30 keeps the game's own 30.

Memory

  • nninitStartup (main+0xbebd44) needs virtual address memory, sets a 6 MB heap for the C library's malloc (SetMemoryHeapSize) and reserves the engine region with nn::os::AllocateAddressRegion (main+0xbec3b4): 62 GB unless -SwitchVirtualAddressSpaceSize= (MB) set it.
  • Current: the allocator is the host's. GCreateMalloc (main+0xbecbf0) mallocs 0x1c38 bytes on the first allocation and constructs them (main+0xbecc08 → main+0xc4d778, which stores the vtable main+0x64710b0). The constructor call is cut and the gate after it hands Unreal the host's malloc zone. The vtable has 20 slots, the 4.27 layout without OnMallocInitialized: TryMalloc / TryRealloc call +0x18 / +0x28, slots 17 and 18 return 1, 19 the name; the 21st word the host copies is the next vtable's offset to top, which nothing calls.
  • Current: a 2 GB region with the node-area cut on this SDK 12.3.2 build, in a 3.75 GB guest block. On the Mac: the region mapped up to +1.76 GB of the 3.12 GB alias region, the Unreal heap up to 1.25 GB.

Rendering

  • Resolved: the traveler selection's map black, the travelers on their pedestals around it: its pixel shader binds 66 resources, over the host's old limit of 64: More than 64 bindings in one shader.
  • Resolved: the picture bright green and white from Castti's opening flashback and the battlefield of Hikari's opening on, with pipelines built on demand: the precompile look-ahead lost the scissor: The precompile look-ahead restores the scissor.
  • Resolved: a point light's shadow geometry skipped ([RED] geometry draw needs 261 MiB of scratch): one 129,032-triangle mesh drawn through Unreal's one-pass point light shadow at the start of the first battle and in the scene after it, now drawn in runs: Geometry draws above the capture scratch bound. Its first frame takes about 0.4 s on the Mac (two new pipelines and the scratch allocation).

Files and shaders

  • Everything is in Majesty/Content/Paks/Majesty-Switch.pak (version 11, the 0xcc footer, 3.3 GB, 376,601 files, Zlib blocks, index not encrypted; the profile's romfs_marker), beside the CRIWARE banks (CueSheet_BGM.awb and others), the two 720p title background movies (TitleBackgroundMovie/*.usm) and vibration data. The update is a delta over the base RomFS, so the merged extraction holds the whole game. The DLC NSP holds a licence only.
  • Shaders: 13,980 unique (1,910 vertex, 11,346 pixel, 694 compute, 30 geometry), inline in the cooked packages as LZ4 blocks: Unreal Engine 4.27 shaders inline in cooked packages (LZ4). The game has no PC version in this repository, so the facts table holds only compute group sizes; every vertex shader is made to link with any pixel shader (shader_split.py separable_pairs).
  • Prewarm list (pso_manifest.tsv, 843 lines): recorded on the Mac with prewarm off over the boot, the title, the traveler selection, Hikari's opening and first battle and Castti's opening on the ship.

Input

  • On the traveler selection a traveler's card opens while the quill cursor rests on them; L / R step from traveler to traveler and open each. A scripted press with the stick fully tilted swept the cursor past every traveler (a tilt of 0.3 is inside the game's dead zone), so scripted input takes partly tilted sticks, L<x>/<y> and R<x>/<y> (hle_hid.c; L-0.6/-0.4~0.5 moves the cursor one traveler at a time).

Mac routes

  • The work folder's run/ctlrun.sh <run> starts a run with DM_CTL, run/see.sh <shot> <wait> <presses…> presses and takes a screenshot, run/mash.sh presses through dialog. From run/save_ref (the initial settings only): run/to_ship.sh <run> goes through the title, New Game and Castti (R L L L L on the traveler selection) to her free play on the ship's deck, run/to_hikari.sh to Hikari's battlefield (R L L L), run/to_battle.sh on through his first battle and the scene after it, and run/ab.sh <prefix> "<env>"… drives several runs at once into Hikari's battlefield with shots along the way (how the scissor fix was found): 05, Scripted input.

Found on this game

General changes first needed here; each applies to every game:

Open

  • A rare crash at the title on a task graph thread (main+0x1d8054, 2 of 87 Mac boots). The game's CRI cue sheet loader runs one task per cue sheet (main+0x1e356c, each under its own manager's mutex at manager+0xe0; the GameThread takes it too) and copied record 2 of a cue sheet's 73 (manager+0x70, 0x70 bytes each, copy assignment main+0x1d7ddc) while the record's TArray at +0x60 held count 1 and no data, the state of a TArray between CopyToEmpty's count and its allocation; both crashes stopped at the same record. Probes showed two such tasks at once on two task graph threads, each with its own manager, and no thread holding another's lock; eight boots with MallocScribble=1 did not crash, which argues against reading freed memory. Which thread writes the record is not found yet.