# The Legend of Zelda: Tears of the Kingdom (0100F2C0115B6000): the rewriter's patches of this game's own builds. dm/tools/patch_nso.py loads every profile's patches.py and applies those of a module's NSO build ID; # CUTS, OBS and FRAME_STEP as described there ("Registered patches"). Patches of Nintendo SDK module builds are in dm/tools/sdk_patches.py import struct from nso_asm import ADRP, B, NOP, STP_PRE # Tears of the Kingdom picks a present interval each frame (main+0x2db174: 2 = 30 fps, 3 under load) and steps its game time by 60 / fps (int fps at timing +0x50, read at main+0x2daa54 # and main+0x3e562c). The stub replaces that logic: int fps = 60 / step, and the float fps (timing +0x24, unused, per masagrator's FPSLocker config for this build, which found the # sites) the same, which the consumers read. Until the host has decided a step (the first frames) the step is the measured frame time (timing frame +0xd8, 19.2 MHz # ticks), clamped to [1 / cap, 1 / 15 s]; game time that ran ahead after a short frame (the "lead", at most 0.25 s; totk-mobile) is paid back by later frames. Prerendered movies set a 30 cap (flag set and cleared around main+0x2b36100 / # main+0x2b33918), the D-pad menu fade starts at full rate (FPSLocker's fix at main+0x1fbc80) and the swapchain argument at main+0xbe14a8 becomes 1 as in FPSLocker def frame_step(ctx): here, emit, fs_site, fs_put, FS_STATE_OFF = ctx.here, ctx.emit, ctx.site, ctx.put, ctx.state # frame step (replaces ldrb w9,[x0,#0x58], the first instruction after the prologue) fs_site(0x2db18c, '09604139') start = here(); emit(ADRP(16, here(), FS_STATE_OFF)) for w in (0xbd400200, # ldr s0,[x16] target fps 0x1e202008, # fcmp s0,#0.0 0x54000640, # b.eq off (+50) target 0: original path 0xb9402202, # ldr w2,[x16,#0x20] step in vsyncs 0x340001a2, # cbz w2,elapsed (+13) not decided yet: by the measured frame time 0x1e230041, # ucvtf s1,w2 0x1e27d003, # fmov s3,#30.0 0x1e211872, # fdiv s18,s3,s1 0x1e322a52, # fadd s18,s18,s18 fps = 60 / step 0xbd002412, # str s18,[x0,#0x24] float fps 0x52800785, # mov w5,#60 0x1ac208a5, # udiv w5,w5,w2 0xb9005005, # str w5,[x0,#0x50] integer fps 0xf9401a04, # ldr x4,[x16,#0x30] 0x8b224084, # add x4,x4,w2,uxtw 0xf9001a04): # str x4,[x16,#0x30] vsyncs stepped emit(w) emit(B(here(), 0x2db284)) # the original epilogue for w in (0xf9406c22, # elapsed: ldr x2,[x1,#0xd8] last frame time in 19.2 MHz ticks 0x9e630041, # ucvtf d1,x2 0x529f0003, # mov w3,#0xf800 0x72a02483, # movk w3,#0x124,lsl #16 (19,200,000) 0x1e630062, # ucvtf d2,w3 0x1e621821, # fdiv d1,d1,d2 elapsed seconds 0xb9400604, # ldr w4,[x16,#4] movie flag 0x1e27d003, # fmov s3,#30.0 0x7100009f, # cmp w4,#0 0x1e201c60, # fcsel s0,s3,s0,ne cap = movie ? 30 : target 0x1e22c000, # fcvt d0,s0 0x1e6e1003, # fmov d3,#1.0 0x1e601864, # fdiv d4,d3,d0 shortest step 1 / cap 0x1e65d005, # fmov d5,#15.0 0x1e651865, # fdiv d5,d3,d5 longest step 1 / 15 0xfd400606, # ldr d6,[x16,#8] lead 0x1e663827, # fsub d7,d1,d6 0x1e6448e7, # fmax d7,d7,d4 0x1e6558e7, # fmin d7,d7,d5 step = clamp(elapsed - lead) 0x1e6728c6, # fadd d6,d6,d7 0x1e6138c6, # fsub d6,d6,d1 lead += step - elapsed 0x2f00e410, # movi d16,#0 0x1e7048c6, # fmax d6,d6,d16 0x1e6a1011, # fmov d17,#0.25 0x1e7158c6, # fmin d6,d6,d17 lead in [0, 0.25] 0xfd000606, # str d6,[x16,#8] 0x1e671872, # fdiv d18,d3,d7 fps = 1 / step 0x1e624252, # fcvt s18,d18 0xbd002412, # str s18,[x0,#0x24] float fps 0x1e380245, # fcvtzs w5,s18 0xb9005005, # str w5,[x0,#0x50] integer fps 0xfd400e13, # ldr d19,[x16,#0x18] 0x1e672a73, # fadd d19,d19,d7 0xfd000e13): # str d19,[x16,#0x18] game seconds stepped emit(w) emit(B(here(), 0x2db284)) # the original epilogue emit(0x39416009); emit(B(here(), 0x2db190)) # off: ldrb w9,[x0,#0x58]; back fs_put(0x2db18c, [B(0x2db18c, start)], 'frame step from the host in whole vsyncs, by elapsed time until it has decided one (state page target fps; 0 = original interval logic)') # the two readers of the integer fps: float fps when on; the scvtf a few instructions later moves into the stub (s0 is not touched in between) for site, cvt, r in ((0x2daa54, 0x2daa6c, 19), (0x3e562c, 0x3e5638, 22)): fs_site(site, struct.pack('