# Patch file for Pokemon Scarlet 4.0.0 main (recomp/patches.py).

# 60 fps, as the game runs on a Switch 2. The engine's startup config holds dynamicResolution.baseFrameRate, an enum
# FPS60, FPS30, FPS20, FPS15, FPS12 (the reflection names at main+0x44e7bf0); the shipping data says FPS30.
# main+0x16df68c turns it into frames per second through the table {60, 30, 20, 15, 12} at main+0x3c63498 (60 for any
# other value), and its caller (main+0x16df4a0) hands 1 / that rate to the game's systems as their fixed step per
# frame. The window init (main+0xbcbec8) presents every enum + 1 vsyncs (main+0xbcc01c). The set takes the default
# branch to 60 and presents every vsync, so the game steps 1/60 s per frame at 60 frames per second.
set fps60 TOTK_FPS60 1
# b.hi 0x16df6cc (enum above 4: 60) -> b 0x16df6cc
site fps60 main 16df6b8 540000a8 14000005
# add w1, w8, #1 (present interval = enum + 1) -> mov w1, #1
site fps60 main bcc01c 11000501 52800021
# The per-frame rate controller (function main+0x2b79f60) buckets the last frame time (16.7, 33.3, 50, 66.4 ms), then
# sets the present interval to its base level + 1, the base level being the enum again as an int8 at [state+0x20]
# (main+0x2b7a858), and keeps the lower of base and bucket. ldrsb w20, [x10, #0x20] -> mov w20, #0 (FPS60)
site fps60 main 2b7a858 39c08154 52800014

# Game resolution (runtime/resolution.c sv_res_init, TOTK_OUTPUT_RES): the game draws at 1920x1080, a size written into
# its code in several places, and set svres makes each of them read the patch page instead: +128 the width and +132
# the height (u32), +136 {1280, width} and +144 {720, height} (the handheld and docked sizes, two u32 each), +152 the
# width and +156 the height as floats, +160 the GPU memory for render targets in bytes (u32), +164 half the width and
# +168 half the height (u32), +172 the render scale (float, at most 1), +176 half
# the sun shadow map's side (u32, set svshadow).
set svres TERA_SVRES 0
set svshadow TERA_SVSHADOW 0
# The window (described by main+0xbc51b0..0xbc528c, which also sizes the swapchain and the output and UI targets): its
# size comes from the engine config's screenWidth / screenHeight (config +0x10 / +0x18), read at main+0xbc525c and
# main+0xbc5264. ldr x9, [x8, #0x10] / [x8, #0x18] -> the page's width / height.
site svres main bc525c f9400909 @page:x9 b9408129
site svres main bc5264 f9400d09 @page:x9 b9408529
# The render resolution controller (constructed at main+0xbd6fe0, run each frame by main+0xdfa800, which scales its base
# for the dynamic resolution and publishes the render size): its base at +0x430 {handheld, docked} widths and +0x438
# heights. movk x8, #0x780, lsl #32 / #0x438 -> {1280, width} / {720, height} from the page.
site svres main bd7018 f2c0f008 @page:x8 f9404508
site svres main bd7024 f2c08708 @page:x8 f9404908
# The render scale below 100%: the controller's per-frame size (main+0xdfa800) is its base times the dynamic
# resolution's scale (each at most 1), height at main+0xdfabf8 and width at main+0xdfac04. Both scales are multiplied
# by the page's render scale first, so the scene is drawn smaller inside its targets and the game's own composite
# enlarges it, as it does for its dynamic resolution. x10 and s3 are free there.
# fmul s8, s0, s1 -> adrp x10, page; ldr s3, [x10, #0xac]; fmul s1, s1, s3; fmul s8, s0, s1
site svres main dfabf8 1e210808 @page:x10 bd40ad43 1e230821 1e210808
# fmul s9, s2, s0 -> fmul s0, s0, s3; fmul s9, s2, s0
site svres main dfac04 1e200849 1e230800 1e200849
# The scene's share of its render target, the render size divided by a fixed 1920.0 and 1080.0 (main+0xd42930,
# main+0x187be70). mov w8, #0x44f00000 / #0x44870000 -> the page's floats.
site svres main d42930 52a89e08 @page:x8 b9409908
site svres main d42940 52a890e8 @page:x8 b9409d08
site svres main 187be70 52a89e08 @page:x8 b9409908
# The scene render targets with a fixed 1920x1080 in their description (passed to main+0x107e220, which creates them).
site svres main 2635370 5280f00a @page:x10 b940814a
site svres main 2635374 5280870b @page:x11 b940856b
site svres main 1664100 5280f009 @page:x9 b9408129
site svres main 16640f8 5280870a @page:x10 b940854a
site svres main 14b0020 5280f008 @page:x8 b9408108
site svres main 14b0024 5280870a @page:x10 b940854a
# The half-size scene targets, a fixed 960x540 in their description (also created through main+0x107e220): at a larger
# size they stayed 960x540 and the screen-space shadows read them at the wrong places (the shadows turned with the
# camera). mov wN, #0x3c0 / #0x21c -> half the page's width / height.
site svres main 1664240 52807808 @page:x8 b940a508
site svres main 1664248 52804389 @page:x9 b940a929
site svres main 2635c90 5280780a @page:x10 b940a54a
site svres main 2635c94 5280438b @page:x11 b940a96b
site svres main 2636298 5280780a @page:x10 b940a54a
site svres main 263629c 5280438b @page:x11 b940a96b
site svres main 116f498 5280780a @page:x10 b940a54a
site svres main 116f49c 5280438b @page:x11 b940a96b
site svres main 135d1e8 52807808 @page:x8 b940a508
site svres main 135d1f0 52804388 @page:x8 b940a908
site svres main 17d9e64 52807808 @page:x8 b940a508
site svres main 17d9e68 5280438a @page:x10 b940a94a
site svres main e939c4 5280780a @page:x10 b940a54a
site svres main e939c8 5280438b @page:x11 b940a96b
# The sun shadow map (TOTK_SHADOW_RES): main+0x16dc200 creates the shadow renderer's depth array (a layer per cascade)
# from the field at +0x3d4, which only this reads and which is half the array's side (1024 for the game's 2048x2048).
# ldrsw x13, [x19, #0x3d4] -> adrp x13, page; ldr w13, [x13, #0xb0] (the page holds half the side).
site svshadow main 16dc200 b983d66d @page:x13 b940b1ad
# The GPU memory the render targets come from (graphics config memorySize_CpuInvisible_GpuCached, which main+0x146e770
# sets to 268 MiB when the data says 320 MiB): without more of it, a larger size runs out when the map opens.
site svres main 146e770 52a21809 @page:x9 b940a129

# Debugging trace points (runtime/tracepoint.c). Remove before a release build.
# Boot: nnMain's init chain (main+0x11bcc84) calls the object's virtual main+0x14fd2d8, which locks a field of the
# null result of main+0x14ff4dc: that object's +0x58 is empty. main+0x11e1a60 (early in the same chain) was checked
# and is not it: its flag reads 1 and it fills +0x58 of another object.
trace main 14ff4dc 14ff5cc 14fe7e8
# The failing object (vtable main+0x4500fc0, multiple inheritance) is freshly constructed: +0x38..+0x78 all zero.
# The outer object is 0x...874000, vtable main+0x443ffd0: the same Trinity file/archive manager that mounted the RomFS
# and read data.trpfd (main+0x11e1a60). Its virtual main+0x14fd2d8 (1348 instructions) obtains the inner object.
# The inner object is the Wwise setup's global singleton (GOT slot main+0x46d8288). On Ryujinx (tools/gdb/inspect.py)
# its +0x38 is 1 and +0x58 holds a root node of class vtable main+0x4501468; on the port both are 0. main+0x14ff4dc
# is find-or-create under that root (children made by the constructor main+0x14ff98c) and returns null without a root.
# The root's constructor is reached through main+0x14fd2d8 -> (0x14fdd90) 0x1505338 -> (0x15054f4) 0x1509880 ->
# (0x1509aac) 0x1509d74 -> (0x1509d84) 0x1509e38 -> (0x1509f04) 0x14ff98c; 0x1505338 returns 0 on the port.
# Entry and the return of each call along that chain:
trace main 1505338 15054f8 1509880 1509ab0 1509d74 1509d88 1509e38 1509f08 14ff98c
# Run 1 of that chain: only main+0x1505338's entry fires, so it returns early through one of its two checks:
# main+0x1507028([singleton+0x200], name) at 0x15053ac, or main+0x1508a4c(singleton, x21) at 0x15054e4.
trace main 1507028 15053b0 15054e8
# Run 2: main+0x1507028 returns 1, main+0x1508a4c returns 0. main+0x1508a4c(singleton, holder): holder->+0x38/+0x40 is
# a directory path that must end in '/', then main+0x15096ec(singleton, path) decides; more checks follow. Its decisions:
trace main 1508a74 1508a94 1508ac8 1508ad4 1508b2c 1508b84 1508bb8 1508be4 1508c60 1508c88 1508ca0 1508dcc 1508df0

# Run 3 (after the trailing-slash fix, the hid calls and the shared fonts): main+0x2151b80 reads +0x38 of a null glyph
# from main+0x788704, whose nn::fontll::ScalableFontEngine::CheckGlyphExist says no. What the font engine gets:
# LoadFont(name, data, size, index), SetFont(name), CheckGlyphExist(code), and the null result.
trace sdk 18de08 18e60c 18e664
trace main 2151bc0
