Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dm/games/0400486024DEC000/game.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ shader_note Ocarina of Time shaders
# The game steps its time by a frame ratio, its base rate / its frame rate, whatever the frame took: frames longer than a vsync played in slow motion. The ratio takes the host's
# frame step instead (MATCH_FRAME_STEP in patches.py, found by its function on any build that keeps it): 1 vsync at 60 fps, 2 when 60 does not hold, so game time = real time
fps_target 60
present_snap on

# ---- Graphics ----
# agl wraps its clears in render conditions on Donkey Kong Bananza and Splatoon Raiders; the same library here
Expand Down
15 changes: 6 additions & 9 deletions dm/games/0400486024DEC000/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

# The game's frame ratio. Once a frame the function at main+0x6e6be0 (v131072) divides its base rate (60.0, or 30.0 while bit 0 of [x21+0x24f] is set) by the int frame rate at
# [x21+0x2c8] (fdiv s8,s1,s0 at +0xe4) and hands the result to the time object (main+0x3b4e300: ratio at +0x10, 1 / ratio at +0x14, round(60 / ratio) at +0x1c), which the game
# steps by; the frame's own time never enters it, so a frame that takes two vsyncs at rate 60 plays at half speed. The stub gives the ratio the host's step instead: step × base / 60
# (the division's value when the host keeps frames 60 / rate vsyncs), and counts the vsyncs stepped. Until the host has decided a step, and at fps_target 0, the division runs.
# steps by; the frame's own time never enters it, so a frame that takes two vsyncs at rate 60 plays at half speed. The stub gives the ratio the host's step instead: (float)step
# (the ratio when the host keeps frames 60 / rate vsyncs), and counts the vsyncs stepped. Until the host has decided a step, and at fps_target 0, the division runs.
# s0 and x16 / x17 are free there (s0 is written again on every path before it is read; the function uses neither x16 nor x17); s1 keeps the base rate.
# Lichii (a GPL emulator; only the fact was taken) found the division (its notes: main+0x6E6CC4, 60 / [x21+0x2C8])
def frame_step(ctx):
Expand All @@ -14,23 +14,20 @@ def frame_step(ctx):
fs_site(site, '2818201e') # fdiv s8,s1,s0
start = here(); emit(ADRP(16, here(), FS_STATE_OFF))
for w in (0xb9400211, # ldr w17,[x16] target fps (0 = off)
0x340001b1, # cbz w17,orig (+13)
0x34000151, # cbz w17,orig (+10)
0xb9402211, # ldr w17,[x16,#0x20] step in vsyncs
0x34000171, # cbz w17,orig (+11) not decided yet
0x34000111, # cbz w17,orig (+8) not decided yet
0x1e230220, # ucvtf s0,w17
0xf9401a10, # ldr x16,[x16,#0x30]
0x8b110210): # add x16,x16,x17
emit(w)
emit(ADRP(17, here(), FS_STATE_OFF))
for w in (0xf9001a30, # str x16,[x17,#0x30] vsyncs stepped
0x1e210808, # fmul s8,s0,s1 step × base rate
0x52a84e10, # mov w16,#0x42700000 60.0
0x1e270200, # fmov s0,w16
0x1e201908): # fdiv s8,s8,s0 / 60
0x1e204008): # fmov s8,s0 step as ratio
emit(w)
emit(B(here(), site + 4))
emit(0x1e201828); emit(B(here(), site + 4)) # orig: fdiv s8,s1,s0; back
fs_put(site, [B(site, start)], 'frame ratio: the host\'s step × base rate / 60 instead of base rate / the game\'s frame rate (state page target fps; 0 = the game\'s ratio)')
fs_put(site, [B(site, start)], 'frame ratio: the host\'s step instead of base rate / the game\'s frame rate (state page target fps; 0 = the game\'s ratio)')
dynamic_resolution(ctx)

# The game's dynamic resolution (game__gfx__DynamicResolutionParam: LoadThreshold, ReduceCounterMax, IncreaseCounterMax and a list of levels, each a ResolutionReduceScale and a
Expand Down
Loading