# Code patches for XC3 2.2.1 main (recomp/patches.py).
#
# scene (XC3_RENDER_SCALE, resolution.c): the 3D render scale. The renderer's frame setup (main+0xed49dc, the object at
# main+0x1ba3970) has a scaled-scene path: each frame it resets the requested scale of view 0 (renderer +0x4a58, a
# width and a height factor) to 1.0 (main+0xed5730), takes the request of any other view that asks for one (flags at
# view +0x48, renderer +0x7d8 + 0x50 i, & 0x13 == 3), and sets bit 2 of view 0's flags (renderer +0x7d0) only when one
# did (main+0xed58cc). With bit 2 the view loop (main+0xed5ae4) applies the request: the render size (view +0x18) is the
# frame size times the factors. No view asks in play, so the path is unused.
# The first site reads the flags and says a view asked (w9 = 1), the second loads both factors from the patch page's
# +0x58 (two floats) in place of 1.0. x8 is rewritten at main+0xed573c.
# The view loop applies the factors when view 0's flag bit 3 is clear or they differ from the ones it last applied
# (renderer +0x4a60), and it multiplies the render size it has (view +0x18, main+0xed5b54), not the frame size (view
# +0x28, which it keeps equal to the resolution table at main+0xed5aa0). Each new application shrank the scene again:
# at render_scale 67 it was 67 % of 67 % after a second one and 770x430 of a 3826x2138 frame after four, drawn in the
# top left of the screen under a whole interface; players saw it after cutscenes (issues 29 and 49). Clearing bit 3
# by hand reproduces it. The third site multiplies the frame size instead, so applying the factors again changes nothing.
set scene XC3_SCENE_SCALE 0
site scene main ed58cc b947d268 b947d268 52800029
site scene main ed5730 b2091be8 @page:x8 f9402d08
site scene main ed5b54 2d430500 2d450500

# realtime (XC3_REALTIME, frame_rate.c): the frame step follows the time each frame is shown. The game's frame-step
# update (main+0xf3c0) takes 60 / its vsync count per frame (main+0x1249b6c) as the frame rate (+0x210, an int), its
# reciprocal as the step (+0x214), and the frame's measured time (main+0x12800c4) clamped to [step, 2 step] as the time
# stepped (+0x218, ratio to 30 fps at +0x21c). The first site stores the object to the patch page's +0x70 (frame_rate.c
# cutscene reads its fixed-step flag) and the sites read the frame rate from the page's +8 (an int), the step
# and the measured time from +4 (a float). The main loop's limiter (main+0x124fa34) waits until the vsync count times
# the vsync period has passed since the last frame; it already returns at once when its object's +0xa0 bit 0 or +0x194
# is set, and the fourth site makes that test always pass, so the host pacer alone holds frames to the limit.
set realtime XC3_REALTIME 1
site realtime main f3dc 1ac00d08 @page:x8 f9003913 b9400908
site realtime main f3e8 1e201821 @page:x9 bd400521
site realtime main f400 9449c331 @page:x8 bd400500
site realtime main 124fa70 2a090108 32000108

# menus (XC3_MENU_TIME, frame_rate.c): the interface's animation follows the time each frame is shown. Every layout
# (menus, the HUD, their fades) advances each frame by its own speed (+0x290) times the layout manager's rate (+0x80 of
# the object at *main+0x1b64fc0, the 1/30 s step at +0x7c times a rate at +0x94, set in main+0x10107b4) and stores the
# product at +0x294, so at 60 fps it ran twice the console's speed. Each site loads the rate, then multiplies the speed
# by it and loads the patch page's +0 (the frame's step in 30 fps frames, frame_rate.c step_store) in its place, so
# the game's own multiply that follows applies both. x8 (the manager) and the reloaded float are dead after each site.
# The last site (main+0x1031e88) scales a frame time by the rate the same way, with s2. Sites from Lucky7
# (games/xc3/patches.py, the L7 layout step), which scales the same loads in its runtime.
set menus XC3_MENU_TIME 1
site menus main 1015c84 bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 1015ff0 bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 10162dc bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 1016444 bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 1021f48 bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 102202c bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 1023ef8 bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 1024218 bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 1029fbc bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 102a098 bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 102d73c bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 102d8fc bd408101 bd408101 @page:x8 1e210800 bd400101
site menus main 1031e88 bd408102 bd408102 @page:x8 1e220800 bd400102

# tour (XC3_TOUR, tour.c): a test tool, off unless XC3_TOUR names a list. The world tick (main+0xf3c0, once a frame on
# the main thread) calls tour.c tour_tick with its object in x0 before it reads the vsync count; x0 to x18 are dead at
# the site (a bl follows). Hook site from Lucky7 (games/xc3/patches.py, the L8 tour).
set tour XC3_TOUR 0
site tour main f3d0 aa0003f3 aa0003f3 @call:f00

# canvas (XC3_UI_CANVAS, resolution.c): the interface's canvas. The layout manager's setup (main+0x1037790, the object
# at *main+0x1b64fc0 +0x48) makes its canvas (+0x7a0 width, +0x7ac height) the frame size it is given, at most the
# resolution table's second size: shipped 1280x720, the size every layout is made for. resolution.c writes the output
# size into that entry, so the canvas grew to it while the layouts kept drawing in 1280x720 units, and what the game
# places at a world position through the canvas moved away from it: at 1920x1080 a field event's speech bubble, the
# one that waits for A, sat 1.5 times too far from the top left, at 3840x2160 3 times, off the screen. Each site loads
# the entry's address from its GOT entry; the replacement points it at the patch page's +0x60 (width) and +0x64
# (height), where resolution.c keeps 1280 and 720. x8 is reloaded before its next use.
set canvas XC3_UI_CANVAS 1
site canvas main 10377b4 f9449d08 @page:x8 91018108
site canvas main 10377e4 f944a108 @page:x8 91019108

# uifit (XC3_UI_FIT, resolution.c): on a frame wider than 16:9 the runtime narrows the interface about the screen centre
# (nvn_api_cmd.c ui_fit), which would pull anything placed at a 3D position, such as talk bubbles, quest markers and
# enemy labels, towards the centre. The game places those on the canvas as (1 - x) times half the canvas width, x the
# projected position, in two ways:
# - main+0x126ff90 projects a world position for a screen size its one caller (main+0x243468, 12 callers: talk
#   bubbles, field icons) passes as 1280x720. The site (main+0x1270144) is the (1 - x); x9 and s5 are dead there.
# - Three functions call the canvas width getter main+0x100dcf8 for it (main+0x587f48, +0x799904, +0x83f5f4: quest
#   markers); x8 and s10 are dead at each site (the getter clobbers x8, s10 is set after it).
# Each site multiplies x by the patch page's +0x68 (1 / the fit, resolution.c ui_fit_init) first, so the position lands
# where the wider 3D view shows it once narrowed. The same site in XCX: xcx-recomp patches-upd.txt, uifit at a17580.
set uifit XC3_UI_FIT 1
site uifit main 1270144 1e213801 @page:x9 bd406925 1e250821 1e213801
site uifit main 587f48 1e203909 @page:x8 bd40690a 1e2a0800 1e203909
site uifit main 799904 1e203909 @page:x8 bd40690a 1e2a0800 1e203909
site uifit main 83f5f4 1e203909 @page:x8 bd40690a 1e2a0800 1e203909

# orbs (XC3_ETHER_ORBS, ether_orbs.c): a cap on the ether orbs of an Ether Channel. Extracting ether adds an orb to
# a list of 8 inline slots (main+0x493bd0: slots of 0x30 bytes at +0x10, the count at +0x190) without a check, and the
# update (main+0x493098) walks count slots and writes through each slot's first word, the orb's effect handle. A 9th
# orb is moved over the count at +0x190, which becomes that handle plus 1: the update then walks memory past the list
# and writes through whatever it finds there (issue 30: an access violation writing address 0x28 in main+0x493098
# after collecting ether). The first site loads the count and calls ether_orbs.c orbs_push, which logs it and makes
# x8 the slot, min(count, 7); x30 is dead there (the bl at main+0x493d18 sets it). The second releases the handle the
# slot holds (main+0x32e118, the release the update gives an orb that ended; it does nothing on an empty slot) before
# main+0x32e0fc moves the new handle in without releasing. The count is at most 8, so count - (count >> 3) is
# min(count, 7): the third site stores that plus 1 as the count. So a full list ends its newest orb for the new one
# and the count stays 8. x8 and x9 are reloaded after the call; no site reads or sets the flags.
set orbs XC3_ETHER_ORBS 1
site orbs main 493d00 f940ca68 f940ca68 @call:f10
site orbs main 493d0c 910042b4 910042b4 aa1403e0 97fa6903
site orbs main 493d50 91000508 cb480d08 91000508
