mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
Compare commits
No files matched your search
@@ -64,6 +64,8 @@ These tools run on the developer's or player's machine during the build. They ar
|
||||
|
||||
| Component | Source | License | Where in the release |
|
||||
|---|---|---|---|
|
||||
| Tcl/Tk 9.0.4, rebuilt with Xft for the Linux installer | https://github.com/tcltk/tcl and https://github.com/tcltk/tk, tag `core-9-0-4`; archives pinned by SHA-256 in `tools/make_release_linux.py` | Tcl/Tk licence (BSD-style) | `python/lib/`; licence texts in `licenses/tcl-license.terms` and `licenses/tk-license.terms` |
|
||||
| libXft 2 and libXrender 1, the sniper SDK sysroot's shared libraries, bundled beside the Xft Tk of the Linux installer; their Debian `copyright` files go into `licenses/` | https://repo.steampowered.com/steamrt-images-sniper/snapshots/3.0.20261021.266025/ | HPND (MIT-style) | `python/lib/`; `licenses/libXft-copyright.txt`, `licenses/libXrender-copyright.txt` |
|
||||
| CPython 3.14.9 embeddable package, with `tkinter` and Tcl/Tk 9.0.4 taken from the python.org 3.14.9 installer's component MSI (unpacked, never run); includes `vcruntime140*.dll` (Microsoft, redistributable with Python), OpenSSL, libffi, SQLite, zlib, libtommath | https://www.python.org/downloads/release/python-3149/ | PSF-2.0 (Tcl/Tk: Tcl/Tk licence, BSD-style) | `python/` |
|
||||
| numpy 2.5.4 (with its bundled OpenBLAS), lz4 4.4.5, cryptography 50.0.2 (with its bundled OpenSSL), cffi 2.1.1, pycparser 3.11 | PyPI binary wheels, pins in `tools/requirements-installer.txt` | BSD-3-Clause; lz4 BSD-3-Clause; cryptography Apache-2.0 OR BSD-3-Clause; cffi MIT; pycparser BSD-3-Clause | `python/Lib/site-packages/` |
|
||||
| llvm-mingw 20261020 (LLVM/Clang/LLD 23, mingw-w64, libc++, libunwind, compiler-rt), x86_64 subset | https://github.com/mstorsjo/llvm-mingw/releases/tag/20261020 | Apache-2.0 with LLVM exception; mingw-w64 ZPL-2.1/BSD/public domain; libc++ and libunwind Apache-2.0 with LLVM exception and MIT/UIUC; winpthreads MIT | `llvm/` |
|
||||
|
||||
+2
-1
@@ -52,7 +52,8 @@ hactool reads `-k <prod.keys>`; MSYS2 shells set `HOME`, which hides its default
|
||||
## Build
|
||||
|
||||
From the repository root, in PowerShell. Every step that depends on the game version takes it as `<ver>`: `v143` for
|
||||
1.4.3 or `v121` for 1.2.1. The tools default to `v121` when `--version` is left out. Saves load on either version.
|
||||
1.4.3, `v121` for 1.2.1 or `v111` for 1.1.1. The tools default to `v121` when `--version` is left out. Saves load on any
|
||||
of them.
|
||||
|
||||
1. Python environments (once). `.venv` runs the lifter and the build; `.venv-py314` runs the extraction scripts, which
|
||||
need the stdlib `compression.zstd` of Python 3.14:
|
||||
|
||||
+3
-2
@@ -34,7 +34,7 @@ The player runs a newer installer on the same folder. It reads `totk-install.jso
|
||||
`profile/` and `totk.ini` after checking their hashes, rebuilds `totk.exe` and re-prepares the shaders. A newer
|
||||
release that changes nothing game-derived skips the rebuild.
|
||||
|
||||
Given another update file than the recorded one (1.2.1, then 1.4.3), the installer extracts and builds again from it and
|
||||
Given an update file other than the recorded one (1.2.1, then 1.4.3), the installer extracts and builds again from it and
|
||||
keeps `profile/`. Saves carry over both ways: 1.4.3 converts a 1.2.1 save on load, and 1.2.1 loads the saves 1.4.3 wrote.
|
||||
|
||||
## Setup steps
|
||||
@@ -113,7 +113,8 @@ totk.ini.example, VERSION the default ini; the release string (the repository's
|
||||
|
||||
`tools/make_release_linux.py` builds the same setup for Linux and packs it as `TotK-Setup-x86_64.AppImage` (the static
|
||||
type-2 runtime: no libfuse2 needed on the host). The player makes it executable and starts it; on a Steam Deck, from
|
||||
Desktop Mode. The setup builds the game into the folder they pick, as on Windows, and the game is `totk` there with
|
||||
Desktop Mode. Building the release also needs `make` and `pkg-config`. The bundled Tcl/Tk 9.0.4 is rebuilt with
|
||||
Xft against the sniper sysroot so Russian text uses system TrueType fonts. The setup builds the game into the folder they pick, as on Windows, and the game is `totk` there with
|
||||
`libSDL3.so.0` beside it. The folder inside the AppImage:
|
||||
|
||||
```
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ def cancel(signum, frame):
|
||||
def main(argv=None):
|
||||
ap = Parser(prog='python -m installer', description=__doc__.splitlines()[0])
|
||||
ap.add_argument('--base', type=Path, help='base game, XCI or NSP')
|
||||
ap.add_argument('--update', type=Path, help='1.2.1 or 1.4.3 update NSP')
|
||||
ap.add_argument('--update', type=Path, help='the update NSP: 1.1.1, 1.2.1 or 1.4.3')
|
||||
ap.add_argument('--keys', type=Path, help='prod.keys')
|
||||
ap.add_argument('--lang', help='game language, a Switch language code (en-US, en-GB, ja, de, ru, ...)')
|
||||
ap.add_argument('--dest', type=Path, required=True, help='install folder')
|
||||
|
||||
+6
-4
@@ -66,8 +66,10 @@ class App:
|
||||
self.icon = tk.PhotoImage(file=str(png))
|
||||
root.iconphoto(True, self.icon)
|
||||
root.protocol('WM_DELETE_WINDOW', self.on_close)
|
||||
self.ui_font = 'Segoe UI' if os.name == 'nt' else 'DejaVu Sans'
|
||||
self.mono_font = 'Consolas' if os.name == 'nt' else 'DejaVu Sans Mono'
|
||||
style = ttk.Style(root)
|
||||
style.configure('Title.TLabel', font=('Segoe UI', 15, 'bold'))
|
||||
style.configure('Title.TLabel', font=(self.ui_font, 15, 'bold'))
|
||||
style.configure('Notice.TLabel', background='#fff3cd', foreground='#664d03', padding=8)
|
||||
style.configure('Err.TLabel', foreground='#c01c28')
|
||||
style.configure('Version.TLabel', foreground='#808080')
|
||||
@@ -219,7 +221,7 @@ class App:
|
||||
self.cancel_button.pack(side='right')
|
||||
self.log_toggle = self.button(bar, 'ui.show_log', self.toggle_log)
|
||||
self.log_toggle.pack(side='left')
|
||||
self.run_header = ttk.Label(f, font=('Segoe UI', 11, 'bold'))
|
||||
self.run_header = ttk.Label(f, font=(self.ui_font, 11, 'bold'))
|
||||
self.run_header.pack(anchor='w')
|
||||
self.step_rows = {}
|
||||
self.steps_box = ttk.Frame(f)
|
||||
@@ -233,7 +235,7 @@ class App:
|
||||
self.warn_label = ttk.Label(f, wraplength=620, justify='left', foreground='#9a6700')
|
||||
self.warn_label.pack(anchor='w')
|
||||
self.log_frame = ttk.Frame(f)
|
||||
self.log = tk.Text(self.log_frame, height=10, wrap='word', state='disabled', font=('Consolas', 9))
|
||||
self.log = tk.Text(self.log_frame, height=10, wrap='word', state='disabled', font=(self.mono_font, 9))
|
||||
scroll = ttk.Scrollbar(self.log_frame, command=self.log.yview)
|
||||
self.log.configure(yscrollcommand=scroll.set)
|
||||
scroll.pack(side='right', fill='y')
|
||||
@@ -378,7 +380,7 @@ class App:
|
||||
glyph.configure(text=GLYPH[state], foreground=COLOR[state])
|
||||
name.configure(text=self.cat('step.' + s) + (f' ({self.cat("ui.state_" + state)})'
|
||||
if state in (SKIPPED, FAILED) else ''),
|
||||
font=('Segoe UI', 10, 'bold') if state == RUNNING else ('Segoe UI', 10))
|
||||
font=(self.ui_font, 10, 'bold') if state == RUNNING else (self.ui_font, 10))
|
||||
mode = m.mode or 'install'
|
||||
self.run_header.configure(text=self.cat('ui.running_' + mode if mode in ('install', 'resume', 'update')
|
||||
else 'ui.running_install'))
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ from pathlib import Path
|
||||
WIN = os.name == 'nt'
|
||||
EXE = '.exe' if WIN else ''
|
||||
UCRT = Path(r'C:\msys64\ucrt64\bin')
|
||||
SUPPORTED = ('1.2.1', '1.4.3') # updates the installer builds; the check identifies the player's by hash
|
||||
SUPPORTED = ('1.2.1', '1.4.3', '1.1.1') # updates the installer builds; the check identifies the player's by hash
|
||||
|
||||
|
||||
def version_tag(version):
|
||||
|
||||
+3
-3
@@ -168,9 +168,9 @@ def raise_if_damaged(error, name, path):
|
||||
|
||||
|
||||
def game_version(ctx):
|
||||
"""The update this install is built from: what the check identified, else the recorded one (an install made before
|
||||
1.4.3 support records none: 1.2.1)."""
|
||||
return ctx.state.get('version', SUPPORTED[0])
|
||||
"""The update this install is built from: what the check identified, else the recorded one. An install made before
|
||||
1.4.3 support records no version, and that install is 1.2.1: name it here rather than leaning on SUPPORTED's order."""
|
||||
return ctx.state.get('version', '1.2.1')
|
||||
|
||||
|
||||
def expected_update(ctx):
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"check.vulkan_missing": "No Vulkan driver is installed. Install the latest driver for your graphics card.",
|
||||
"check.vulkan_no_device": "Vulkan finds no graphics card. Install the latest driver for your graphics card.",
|
||||
"check.vulkan_too_old": "The game needs a graphics card with Vulkan 1.3; found: {devices}. Update the graphics driver.",
|
||||
"check.inputs_needed": "The game files are needed: choose the base game, the 1.2.1 or 1.4.3 update and prod.keys.",
|
||||
"check.inputs_needed": "The game files are needed: choose the base game, the 1.1.1 or 1.2.1 or 1.4.3 update and prod.keys.",
|
||||
"check.input_missing": "File not found ({input}): {path}",
|
||||
"check.keys_unreadable": "The keys file cannot be read. Choose a prod.keys file.",
|
||||
"check.keys_no_header_key": "The keys file has no header_key. Dump prod.keys from your console again.",
|
||||
@@ -60,7 +60,7 @@
|
||||
"check.ticket_missing": "The {input} file lacks the ticket its content needs.",
|
||||
"check.base_not_totk": "The base game file is not The Legend of Zelda: Tears of the Kingdom ({detail}).",
|
||||
"check.update_not_nsp": "The update must be an NSP file.",
|
||||
"check.update_is_base": "The update file holds a base game. Choose the 1.2.1 or 1.4.3 update NSP.",
|
||||
"check.update_is_base": "The update file holds a base game. Choose the 1.1.1 or 1.2.1 or 1.4.3 update NSP.",
|
||||
"check.update_not_totk": "The update file is not a Tears of the Kingdom update ({detail}).",
|
||||
"check.exefs_failed": "The game code of the {input} file failed its checks; see {log}.",
|
||||
"check.input_damaged": "The {input} file is damaged or incomplete: {path} ({detail}). Copy that file again from its source, or dump it again, then run the setup again.",
|
||||
@@ -92,13 +92,13 @@
|
||||
"ui.notice": "The folder this setup builds contains the game. It is for your own use only; do not share or upload it.",
|
||||
"ui.keys_private": "The keys file is read in place. Its contents are never shown, copied or stored.",
|
||||
"ui.base": "Base game (NSP or XCI)",
|
||||
"ui.update": "Update 1.2.1 or 1.4.3 (NSP)",
|
||||
"ui.update": "Update 1.1.1 or 1.2.1 or 1.4.3 (NSP)",
|
||||
"ui.keys": "prod.keys",
|
||||
"ui.game_language": "Game language",
|
||||
"ui.dest": "Install folder",
|
||||
"ui.browse": "Browse...",
|
||||
"ui.pick_game": "Choose the base game file",
|
||||
"ui.pick_update": "Choose the 1.2.1 or 1.4.3 update file",
|
||||
"ui.pick_update": "Choose the 1.1.1 or 1.2.1 or 1.4.3 update file",
|
||||
"ui.pick_keys": "Choose prod.keys",
|
||||
"ui.pick_dest": "Choose the install folder",
|
||||
"ui.type_game": "Game files (NSP, XCI)",
|
||||
@@ -116,7 +116,7 @@
|
||||
"ui.update_mode": "This folder holds an earlier installation. The setup continues or updates it; the game files are asked for only if they are needed again.",
|
||||
"ui.need_dest": "Choose the install folder.",
|
||||
"ui.need_base": "Choose the base game file.",
|
||||
"ui.need_update": "Choose the 1.2.1 or 1.4.3 update file.",
|
||||
"ui.need_update": "Choose the 1.1.1 or 1.2.1 or 1.4.3 update file.",
|
||||
"ui.need_keys": "Choose the prod.keys file.",
|
||||
"ui.need_lang": "Choose the game language.",
|
||||
"ui.missing_base": "The base game file does not exist.",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"check.vulkan_missing": "Драйвер Vulkan не установлен. Установите свежий драйвер видеокарты.",
|
||||
"check.vulkan_no_device": "Vulkan не находит видеокарту. Установите свежий драйвер видеокарты.",
|
||||
"check.vulkan_too_old": "Игре нужна видеокарта с Vulkan 1.3; найдено: {devices}. Обновите драйвер видеокарты.",
|
||||
"check.inputs_needed": "Нужны игровые файлы: выберите базовую игру, обновление 1.2.1 или 1.4.3 и prod.keys.",
|
||||
"check.inputs_needed": "Нужны игровые файлы: выберите базовую игру, обновление 1.1.1 или 1.2.1 или 1.4.3 и prod.keys.",
|
||||
"check.input_missing": "Файл не найден ({input}): {path}",
|
||||
"check.keys_unreadable": "Не удаётся прочитать файл ключей. Выберите файл prod.keys.",
|
||||
"check.keys_no_header_key": "В файле ключей нет header_key. Снова получите prod.keys со своей консоли.",
|
||||
@@ -60,7 +60,7 @@
|
||||
"check.ticket_missing": "В файле «{input}» нет тикета, который нужен его содержимому.",
|
||||
"check.base_not_totk": "Файл базовой игры — не The Legend of Zelda: Tears of the Kingdom ({detail}).",
|
||||
"check.update_not_nsp": "Обновление должно быть файлом NSP.",
|
||||
"check.update_is_base": "Файл обновления содержит базовую игру. Выберите NSP с обновлением 1.2.1 или 1.4.3.",
|
||||
"check.update_is_base": "Файл обновления содержит базовую игру. Выберите NSP с обновлением 1.1.1 или 1.2.1 или 1.4.3.",
|
||||
"check.update_not_totk": "Файл обновления — не обновление Tears of the Kingdom ({detail}).",
|
||||
"check.exefs_failed": "Код игры из файла «{input}» не прошёл проверку; подробности в {log}.",
|
||||
"check.input_damaged": "Файл «{input}» повреждён или скопирован не полностью: {path} ({detail}). Скопируйте этот файл заново из источника или сделайте дамп ещё раз, затем снова запустите установку.",
|
||||
@@ -92,13 +92,13 @@
|
||||
"ui.notice": "Папка, которую создаёт установка, содержит игру. Она предназначена только для личного использования: не передавайте и не выкладывайте её.",
|
||||
"ui.keys_private": "Файл ключей читается на месте. Его содержимое не показывается, не копируется и не сохраняется.",
|
||||
"ui.base": "Базовая игра (NSP или XCI)",
|
||||
"ui.update": "Обновление 1.2.1 или 1.4.3 (NSP)",
|
||||
"ui.update": "Обновление 1.1.1 или 1.2.1 или 1.4.3 (NSP)",
|
||||
"ui.keys": "prod.keys",
|
||||
"ui.game_language": "Язык игры",
|
||||
"ui.dest": "Папка установки",
|
||||
"ui.browse": "Обзор...",
|
||||
"ui.pick_game": "Выберите файл базовой игры",
|
||||
"ui.pick_update": "Выберите файл обновления 1.2.1 или 1.4.3",
|
||||
"ui.pick_update": "Выберите файл обновления 1.1.1 или 1.2.1 или 1.4.3",
|
||||
"ui.pick_keys": "Выберите prod.keys",
|
||||
"ui.pick_dest": "Выберите папку установки",
|
||||
"ui.type_game": "Файлы игры (NSP, XCI)",
|
||||
@@ -116,7 +116,7 @@
|
||||
"ui.update_mode": "В этой папке уже есть установка. Программа продолжит или обновит её; файлы игры понадобятся, только если их снова придётся читать.",
|
||||
"ui.need_dest": "Выберите папку установки.",
|
||||
"ui.need_base": "Выберите файл базовой игры.",
|
||||
"ui.need_update": "Выберите файл обновления 1.2.1 или 1.4.3.",
|
||||
"ui.need_update": "Выберите файл обновления 1.1.1 или 1.2.1 или 1.4.3.",
|
||||
"ui.need_keys": "Выберите файл prod.keys.",
|
||||
"ui.need_lang": "Выберите язык игры.",
|
||||
"ui.missing_base": "Файл базовой игры не найден.",
|
||||
|
||||
@@ -0,0 +1,957 @@
|
||||
# Code patches for TotK 1.1.1 main (recomp/patches.py). The sites come from MacNativeGames
|
||||
# games/totk/runtime/game_patches.c (MIT, Copyright (c) 2026 MacNativeGames contributors) at upstream ebe621d, through
|
||||
# its 1.2.1 addresses in game_version.h (2db612b); each one was located again in 1.1.1 by instruction-window matching
|
||||
# and its original words checked against this build's main. The patch page holds two floats the runtime writes at every
|
||||
# present (frame_rate.c): +0 the 30 Hz ratio (seconds * 30), +4 the frame step in seconds.
|
||||
|
||||
set fps60 TOTK_FPS60 1
|
||||
set realtime TOTK_REALTIME 1
|
||||
set cloth TOTK_CLOTH_REALTIME 1
|
||||
set bounce TOTK_HIGHFPS_FIX 1
|
||||
set output TOTK_PATCH_OUTPUT 0
|
||||
set scene TOTK_PATCH_SCENE 0
|
||||
|
||||
# 60 fps: the adaptive present-interval controller (main+0x8f26c8, 1.0.0 0x8f6bec) gets the minimum interval 1.
|
||||
site fps60 main 90e978 6b08015f 7100055f # clamp: cmp w10, #1 (was cmp w10, w8 = minimum)
|
||||
site fps60 main 90e97c 1a89b10a 1a9fa52a # clamp: csinc w10, w9, wzr, ge (desired = max(desired, 1))
|
||||
site fps60 main 90e9a0 b9401a8a d503201f # lower trigger: nop the stored-candidate load
|
||||
site fps60 main 90ea30 6b08015f 7100055f # raise path: cmp w10, #1
|
||||
site fps60 main 90ea40 1a88c1ac 1a9fc5ac # raise path: csinc w12, w13, wzr, gt (next lower candidate floors at 1)
|
||||
site fps60 main 90ea94 b9400288 52800028 # lower path: mov w8, #1 (minimum interval 1)
|
||||
site fps60 main 90eab0 2a0a03f5 1a9fa555 # lower path: csinc w21, w10, wzr, ge (result never below 1)
|
||||
|
||||
# Real-time step: the VFR step (main+0x7ede6c, 1.0.0 0x7a83e4) and the global ratio after the controller call
|
||||
# (main+0x8f2394, 1.0.0 0x8f68b8) read ratio and seconds from the patch page instead of interval * 0.5 and interval / 60.
|
||||
site realtime main 833e4c 1e220020 @page:x9 # scvtf s0, w1 -> adrp x9, page
|
||||
site realtime main 833e50 52a84e08 bd400120 # mov w8, #60.0 -> ldr s0, [x9] (ratio)
|
||||
site realtime main 833e54 1e270101 bd400521 # fmov s1, w8 -> ldr s1, [x9, #4] (seconds)
|
||||
site realtime main 833e58 1e2c1002 d503201f # fmov s2, #0.5 -> nop
|
||||
site realtime main 833e64 1e211801 d503201f # fdiv s1, s0, s1 -> nop
|
||||
site realtime main 833e68 1e220800 d503201f # fmul s0, s0, s2 -> nop
|
||||
site realtime main 90e5d0 bd425e60 @page:x9 # ldr s0, [x19, #0x25c] -> adrp x9, page
|
||||
site realtime main 90e5d4 1e2c1001 bd400120 # fmov s1, #0.5 -> ldr s0, [x9] (global ratio)
|
||||
site realtime main 90e5e4 5e21d800 d503201f # scvtf s0, s0 -> nop
|
||||
site realtime main 90e5e8 1e210800 d503201f # fmul s0, s0, s1 -> nop
|
||||
|
||||
# Hair and cloth (upstream 290ec9f, 8f8022f): the actor animation update builds a second clock {seconds = factor / 30,
|
||||
# ratio = factor} per rendered frame. Three inlined places: at the fmul, seconds = measured seconds * factor; at the
|
||||
# store of the factor into the ratio slot, measured ratio * factor. The call of the shared update (1.1.1
|
||||
# main+0x8ecb40, 1.2.1 main+0x89822c) scales s2 (seconds) and s3 (ratio) by the measured ratio. Upstream's
|
||||
# veneer bodies, inlined at the site.
|
||||
site cloth main 9efc20 1e200900 f81f0fe9 @page:x9 bd400520 1e200900 f84107e9 # fmul s0, s8, s0
|
||||
site cloth main 9efc1c bd0117e8 d10083ff f90003e9 3d8007ff @page:x9 bd40013f 1e3f091f bd0137ff 3dc007ff f94003e9 910083ff # str s8, [sp, #0x114]
|
||||
site cloth main 9f0bcc 1e200900 f81f0fe9 @page:x9 bd400520 1e200900 f84107e9 # fmul s0, s8, s0
|
||||
site cloth main 9f0ba8 bd0117e8 d10083ff f90003e9 3d8007ff @page:x9 bd40013f 1e3f091f bd0137ff 3dc007ff f94003e9 910083ff # str s8, [sp, #0x114]
|
||||
site cloth main a05a04 1e210801 f81f0fe9 @page:x9 bd400521 1e210801 f84107e9 # fmul s1, s0, s1
|
||||
site cloth main a05a08 bd0137e0 d10083ff f90003e9 3d8007ff @page:x9 bd40013f 1e3f081f bd0157ff 3dc007ff f94003e9 910083ff # str s0, [sp, #0x134]
|
||||
site cloth main 9f044c 97fbf1bd @page:x8 bd400104 1e240842 1e240863 97fbf1bd # bl 0x8ecb40
|
||||
|
||||
# Trampolines (upstream 27c3b43): the slow-motion check compares time object +0x28 (30 / (ratio * time scale)) with
|
||||
# 100; compare +0x28 * +0x38 (30 / time scale) instead, the same value at 30 Hz and independent of the frame rate.
|
||||
site bounce main bee7e0 bd402900 bd402900 bd403901 1e210800 # ldr s0, [x8, #0x28]; ldr s1, [x8, #0x38]; fmul s0, s0, s1
|
||||
|
||||
# Output size (upstream games/totk/runtime/game_patches.c patch_output_res; runtime/resolution.c switches the set on).
|
||||
# The swapchain size nnMain writes before NvInitializeGraphics comes from page +48 ({w, h} as one 64-bit value), and
|
||||
# the output size the renderer lays out its canvas, crop and UI in (main+0xcc3f00, read from the graphics parameters
|
||||
# through main+0xcc4160) from page +32; the renderer itself goes to page +40.
|
||||
site output main eb8cfc d280f008 @page:x8 # mov x8, #0x780 -> adrp x8, page
|
||||
site output main eb8d00 f2c08708 f9401908 # movk x8, #0x438, lsl #32 -> ldr x8, [x8, #48]
|
||||
site output main cea5c8 aa1403e0 @page:x21 # mov x0, x20 -> adrp x21, page
|
||||
site output main cea5cc 94000081 f90016b3 # bl 0xcc4160 -> str x19, [x21, #40]
|
||||
site output main cea5d0 f9400015 f94012b5 # ldr x21, [x0] -> ldr x21, [x21, #32]
|
||||
|
||||
# Scene render size (upstream patch_render_res): the renderer's +0xcc4 / +0xcc8, which size every scene render target,
|
||||
# take two floats from page +16 instead of the graphics parameters' docked 1600x900.
|
||||
site scene main cea5e0 5e21d900 @page:x9 # scvtf s0, s8 -> adrp x9, page
|
||||
site scene main cea5e8 bd0cc660 2d420520 # str s0, [x19, #0xcc4] -> ldp s0, s1, [x9, #16]
|
||||
site scene main cea5ec 5e21d921 bd0cc660 # scvtf s1, s9 -> str s0, [x19, #0xcc4]
|
||||
|
||||
# Spin waits, from tools/spin_waits.py --exefs build/extracted/v111/exefs --patches recomp/patches-v111.txt: loops that only
|
||||
# another thread can end; their back-edges pass rt_spin_gate (runtime/kernel.c), TOTK_SPIN=0 keeps the core.
|
||||
|
||||
# spin waits found by tools/spin_waits.py on the 1.1.x modules
|
||||
# main: 869 spin waits
|
||||
spin main ac600 ac620 ac630 # 13 instructions; loads
|
||||
spin main ac680 ac6a8 ac6b8 # 15 instructions; loads
|
||||
spin main ac6f0 ac718 ac728 # 15 instructions; loads
|
||||
spin main e8544 e854c # 3 instructions; loads
|
||||
spin main 2457f0 245804 # 6 instructions; loads
|
||||
spin main 644bbc 644bd0 # 6 instructions; loads
|
||||
spin main 6456ac 6456c0 # 6 instructions; loads
|
||||
spin main 6459d4 6459e8 # 6 instructions; loads
|
||||
spin main 645d58 645d6c # 6 instructions; loads
|
||||
spin main 6460c4 6460d8 # 6 instructions; loads
|
||||
spin main 64649c 6464b0 # 6 instructions; loads
|
||||
spin main 646740 646754 # 6 instructions; loads
|
||||
spin main 6467d4 6467e8 # 6 instructions; loads
|
||||
spin main 646e6c 646e80 # 6 instructions; loads
|
||||
spin main 647080 647094 # 6 instructions; loads
|
||||
spin main 6484f4 648510 # 8 instructions; loads
|
||||
spin main 6485a4 6485c0 # 8 instructions; loads
|
||||
spin main 649fb0 649fc4 # 6 instructions; loads
|
||||
spin main 64a064 64a078 # 6 instructions; loads
|
||||
spin main 64a3d0 64a3e4 # 6 instructions; loads
|
||||
spin main 64afac 64afc8 # 8 instructions; loads
|
||||
spin main 64b404 64b418 # 6 instructions; loads
|
||||
spin main 64b980 64b994 # 6 instructions; loads
|
||||
spin main 64c68c 64c6a0 # 6 instructions; loads
|
||||
spin main 64c978 64c98c # 6 instructions; loads
|
||||
spin main 64ca28 64ca3c # 6 instructions; loads
|
||||
spin main 64d378 64d38c # 6 instructions; loads
|
||||
spin main 64d8d8 64d8ec # 6 instructions; loads
|
||||
spin main 64db90 64dba4 # 6 instructions; loads
|
||||
spin main 64de18 64de2c # 6 instructions; loads
|
||||
spin main 64de40 64de54 # 6 instructions; loads
|
||||
spin main 64de68 64de7c # 6 instructions; loads
|
||||
spin main 64e6c8 64e6dc # 6 instructions; loads
|
||||
spin main 64e6f0 64e704 # 6 instructions; loads
|
||||
spin main 64e8ac 64e8c0 # 6 instructions; loads
|
||||
spin main 64e9a4 64e9b8 # 6 instructions; loads
|
||||
spin main 64fb50 64fb64 # 6 instructions; loads
|
||||
spin main 652eac 652ec0 # 6 instructions; loads
|
||||
spin main 652ed4 652ee8 # 6 instructions; loads
|
||||
spin main 652efc 652f10 # 6 instructions; loads
|
||||
spin main 654590 6545a4 # 6 instructions; loads
|
||||
spin main 654c5c 654c70 # 6 instructions; loads
|
||||
spin main 654cfc 654d10 # 6 instructions; loads
|
||||
spin main 655124 655138 # 6 instructions; loads
|
||||
spin main 655c14 655c28 # 6 instructions; loads
|
||||
spin main 656f80 656f94 # 6 instructions; loads
|
||||
spin main 658e60 658e74 # 6 instructions; loads
|
||||
spin main 658ebc 658ed0 # 6 instructions; loads
|
||||
spin main 659b28 659b3c # 6 instructions; loads
|
||||
spin main 659b50 659b64 # 6 instructions; loads
|
||||
spin main 65c9e0 65c9f4 # 6 instructions; loads
|
||||
spin main 65ca08 65ca1c # 6 instructions; loads
|
||||
spin main 65ca30 65ca44 # 6 instructions; loads
|
||||
spin main 65ca58 65ca6c # 6 instructions; loads
|
||||
spin main 65ca80 65ca94 # 6 instructions; loads
|
||||
spin main 65caa8 65cabc # 6 instructions; loads
|
||||
spin main 65cad0 65cae4 # 6 instructions; loads
|
||||
spin main 65caf8 65cb0c # 6 instructions; loads
|
||||
spin main 65cb20 65cb34 # 6 instructions; loads
|
||||
spin main 65cb48 65cb5c # 6 instructions; loads
|
||||
spin main 65cb70 65cb84 # 6 instructions; loads
|
||||
spin main 65cb98 65cbac # 6 instructions; loads
|
||||
spin main 65cbc0 65cbd4 # 6 instructions; loads
|
||||
spin main 65d4b0 65d4c4 # 6 instructions; loads
|
||||
spin main 65d4d8 65d4ec # 6 instructions; loads
|
||||
spin main 65d500 65d514 # 6 instructions; loads
|
||||
spin main 65d528 65d53c # 6 instructions; loads
|
||||
spin main 65da54 65da68 # 6 instructions; loads
|
||||
spin main 65de00 65de14 # 6 instructions; loads
|
||||
spin main 65e200 65e214 # 6 instructions; loads
|
||||
spin main 65e6ec 65e700 # 6 instructions; loads
|
||||
spin main 65ecbc 65ecd0 # 6 instructions; loads
|
||||
spin main 65ece4 65ecf8 # 6 instructions; loads
|
||||
spin main 663588 66359c # 6 instructions; loads
|
||||
spin main 663b0c 663b20 # 6 instructions; loads
|
||||
spin main 663da0 663db4 # 6 instructions; loads
|
||||
spin main 663dec 663e00 # 6 instructions; loads
|
||||
spin main 6645a4 6645b8 # 6 instructions; loads
|
||||
spin main 6645cc 6645e0 # 6 instructions; loads
|
||||
spin main 666530 666544 # 6 instructions; loads
|
||||
spin main 666570 666584 # 6 instructions; loads
|
||||
spin main 6671d4 6671e8 # 6 instructions; loads
|
||||
spin main 6671fc 667210 # 6 instructions; loads
|
||||
spin main 6676c0 6676d4 # 6 instructions; loads
|
||||
spin main 667cfc 667d10 # 6 instructions; loads
|
||||
spin main 66f34c 66f360 # 6 instructions; loads
|
||||
spin main 6708f8 67090c # 6 instructions; loads
|
||||
spin main 670920 670934 # 6 instructions; loads
|
||||
spin main 670948 67095c # 6 instructions; loads
|
||||
spin main 673f34 673f48 # 6 instructions; loads
|
||||
spin main 67556c 675580 # 6 instructions; loads
|
||||
spin main 676954 676968 # 6 instructions; loads
|
||||
spin main 676994 6769a8 # 6 instructions; loads
|
||||
spin main 676e6c 676e80 # 6 instructions; loads
|
||||
spin main 677148 67715c # 6 instructions; loads
|
||||
spin main 6771c8 6771dc # 6 instructions; loads
|
||||
spin main 677484 677498 # 6 instructions; loads
|
||||
spin main 677df0 677e04 # 6 instructions; loads
|
||||
spin main 6792a8 6792bc # 6 instructions; loads
|
||||
spin main 67a984 67a998 # 6 instructions; loads
|
||||
spin main 698ef8 698f0c # 6 instructions; loads
|
||||
spin main 6b6d8c 6b6da0 # 6 instructions; loads
|
||||
spin main 6b922c 6b9240 # 6 instructions; loads
|
||||
spin main 6c5408 6c541c # 6 instructions; loads
|
||||
spin main 6c5614 6c5628 # 6 instructions; loads
|
||||
spin main 6ccce0 6cccf4 # 6 instructions; loads
|
||||
spin main 6ccd98 6ccdac # 6 instructions; loads
|
||||
spin main 6d3c9c 6d3cb0 # 6 instructions; loads
|
||||
spin main 6d3ff8 6d400c # 6 instructions; loads
|
||||
spin main 6d61cc 6d61e0 # 6 instructions; loads
|
||||
spin main 6d6210 6d6224 # 6 instructions; loads
|
||||
spin main 6d6254 6d6268 # 6 instructions; loads
|
||||
spin main 6d6298 6d62ac # 6 instructions; loads
|
||||
spin main 6d75c4 6d75d8 # 6 instructions; loads
|
||||
spin main 6d79dc 6d79f0 # 6 instructions; loads
|
||||
spin main 6d9398 6d93ac # 6 instructions; loads
|
||||
spin main 6dd8d0 6dd8e4 # 6 instructions; loads
|
||||
spin main 6de150 6de164 # 6 instructions; loads
|
||||
spin main 6e073c 6e0750 # 6 instructions; loads
|
||||
spin main 6e0a94 6e0aa8 # 6 instructions; loads
|
||||
spin main 6e4eb8 6e4ecc # 6 instructions; loads
|
||||
spin main 6eb590 6eb5a4 # 6 instructions; loads
|
||||
spin main 6eccac 6eccc0 # 6 instructions; loads
|
||||
spin main 6ed9b4 6ed9c8 # 6 instructions; loads
|
||||
spin main 6ee69c 6ee6b0 # 6 instructions; loads
|
||||
spin main 6f94c0 6f94d4 # 6 instructions; loads
|
||||
spin main 6f9738 6f974c # 6 instructions; loads
|
||||
spin main 6fa324 6fa338 # 6 instructions; loads
|
||||
spin main 70d844 70d858 # 6 instructions; loads
|
||||
spin main 70dc74 70dc88 # 6 instructions; loads
|
||||
spin main 710a70 710a84 # 6 instructions; loads
|
||||
spin main 711848 71185c # 6 instructions; loads
|
||||
spin main 7125a4 7125b8 # 6 instructions; loads
|
||||
spin main 716474 716488 # 6 instructions; loads
|
||||
spin main 719a30 719a44 # 6 instructions; loads
|
||||
spin main 72cd78 72cd8c # 6 instructions; loads
|
||||
spin main 72f66c 72f680 # 6 instructions; loads
|
||||
spin main 737684 737698 # 6 instructions; loads
|
||||
spin main 73e0dc 73e0f0 # 6 instructions; loads
|
||||
spin main 73ed14 73ed28 # 6 instructions; loads
|
||||
spin main 783520 783534 # 6 instructions; loads
|
||||
spin main 797924 797938 # 6 instructions; loads
|
||||
spin main 79794c 797960 # 6 instructions; loads
|
||||
spin main 797c08 797c1c # 6 instructions; loads
|
||||
spin main 797c64 797c78 # 6 instructions; loads
|
||||
spin main 798ff4 799008 # 6 instructions; loads
|
||||
spin main 79cbec 79cc00 # 6 instructions; loads
|
||||
spin main 7ae134 7ae148 # 6 instructions; loads
|
||||
spin main 7ae388 7ae39c # 6 instructions; loads
|
||||
spin main 7c292c 7c2940 # 6 instructions; loads
|
||||
spin main 7d618c 7d61a0 # 6 instructions; loads
|
||||
spin main 7d904c 7d9060 # 6 instructions; loads
|
||||
spin main 7e11c0 7e11d4 # 6 instructions; loads
|
||||
spin main 7e1c9c 7e1cb0 # 6 instructions; loads
|
||||
spin main 7e24a0 7e24b4 # 6 instructions; loads
|
||||
spin main 7ed89c 7ed8b0 # 6 instructions; loads
|
||||
spin main 7eef28 7eef3c # 6 instructions; loads
|
||||
spin main 7f515c 7f5170 # 6 instructions; loads
|
||||
spin main 801468 80147c # 6 instructions; loads
|
||||
spin main 801490 8014a4 # 6 instructions; loads
|
||||
spin main 8014b8 8014cc # 6 instructions; loads
|
||||
spin main 80a0a4 80a0b8 # 6 instructions; loads
|
||||
spin main 811500 811514 # 6 instructions; loads
|
||||
spin main 816c40 816c54 # 6 instructions; loads
|
||||
spin main 819450 819464 # 6 instructions; loads
|
||||
spin main 85df6c 85e068 # 4 instructions; loads
|
||||
spin main 863220 863234 # 6 instructions; loads
|
||||
spin main 86352c 863540 # 6 instructions; loads
|
||||
spin main 863a80 863a94 # 6 instructions; loads
|
||||
spin main 863b94 863ba8 # 6 instructions; loads
|
||||
spin main 86543c 865450 # 6 instructions; loads
|
||||
spin main 865464 865478 # 6 instructions; loads
|
||||
spin main 87e564 87e578 # 6 instructions; loads
|
||||
spin main 87e58c 87e5a0 # 6 instructions; loads
|
||||
spin main 880d44 880d58 # 6 instructions; loads
|
||||
spin main 8810f4 881108 # 6 instructions; loads
|
||||
spin main 8814c8 8814dc # 6 instructions; loads
|
||||
spin main 88189c 8818b0 # 6 instructions; loads
|
||||
spin main 887ce8 887cfc # 6 instructions; loads
|
||||
spin main 887f50 887f64 # 6 instructions; loads
|
||||
spin main 8884ec 888500 # 6 instructions; loads
|
||||
spin main 888618 88862c # 6 instructions; loads
|
||||
spin main 888748 88875c # 6 instructions; loads
|
||||
spin main 8888e0 8888f4 # 6 instructions; loads
|
||||
spin main 89183c 891850 # 6 instructions; loads
|
||||
spin main 892ccc 892cec # 9 instructions; loads
|
||||
spin main 89540c 89541c # 5 instructions; loads
|
||||
spin main 895668 895694 # 12 instructions; loads
|
||||
spin main 8956a8 8956b8 # 5 instructions; loads
|
||||
spin main 8da7dc 8da7e4 # 3 instructions; loads
|
||||
spin main 8da7ec 8da7f0 # 2 instructions; loads
|
||||
spin main 8da858 8da860 # 3 instructions; loads
|
||||
spin main 8fb190 8fb1a4 # 6 instructions; loads
|
||||
spin main 9045bc 9045d0 # 6 instructions; loads
|
||||
spin main 9045e4 9045f8 # 6 instructions; loads
|
||||
spin main 90460c 904620 # 6 instructions; loads
|
||||
spin main 904634 904648 # 6 instructions; loads
|
||||
spin main 905b20 905b34 # 6 instructions; loads
|
||||
spin main 9062f0 906304 # 6 instructions; loads
|
||||
spin main 91019c 9101b0 # 6 instructions; loads
|
||||
spin main 910720 910734 # 6 instructions; loads
|
||||
spin main 9363cc 9363e0 # 6 instructions; loads
|
||||
spin main 93655c 936570 # 6 instructions; loads
|
||||
spin main 93819c 9381b0 # 6 instructions; loads
|
||||
spin main 940060 94007c # 8 instructions; loads
|
||||
spin main 94012c 940148 # 8 instructions; loads
|
||||
spin main 97864c 978660 # 6 instructions; loads
|
||||
spin main 99aa8c 99aabc # 13 instructions; loads
|
||||
spin main 99aad0 99aae0 # 5 instructions; loads
|
||||
spin main 9a5fd4 9a6004 # 13 instructions; loads
|
||||
spin main 9a6018 9a6028 # 5 instructions; loads
|
||||
spin main 9b2700 9b2710 # 5 instructions; loads
|
||||
spin main 9f395c 9f3970 # 6 instructions; loads
|
||||
spin main 9f39e0 9f39f4 # 6 instructions; loads
|
||||
spin main 9f3b2c 9f3b40 # 6 instructions; loads
|
||||
spin main 9f3bf8 9f3c0c # 6 instructions; loads
|
||||
spin main 9f6f5c 9f6f70 # 6 instructions; loads
|
||||
spin main a11908 a1191c # 6 instructions; loads
|
||||
spin main a1257c a12590 # 6 instructions; loads
|
||||
spin main a14d6c a14d80 # 6 instructions; loads
|
||||
spin main a1b0a4 a1b0b8 # 6 instructions; loads
|
||||
spin main a1b0ec a1b100 # 6 instructions; loads
|
||||
spin main a1c3dc a1c3f0 # 6 instructions; loads
|
||||
spin main a2b268 a2b27c # 6 instructions; loads
|
||||
spin main a2cab8 a2cacc # 6 instructions; loads
|
||||
spin main a2f228 a2f23c # 6 instructions; loads
|
||||
spin main a8e920 a8e934 # 6 instructions; loads
|
||||
spin main aa2c20 aa2c34 # 6 instructions; loads
|
||||
spin main aa5108 aa511c # 6 instructions; loads
|
||||
spin main aa9f94 aa9fa8 # 6 instructions; loads
|
||||
spin main aaa674 aaa688 # 6 instructions; loads
|
||||
spin main aad564 aad578 # 6 instructions; loads
|
||||
spin main ab245c ab24b4 # 7 instructions; loads
|
||||
spin main ac8874 ac88fc # 35 instructions; loads
|
||||
spin main af4040 af4054 # 6 instructions; loads
|
||||
spin main af4068 af407c # 6 instructions; loads
|
||||
spin main af4090 af40a4 # 6 instructions; loads
|
||||
spin main af40cc af40e0 # 6 instructions; loads
|
||||
spin main af40f4 af4108 # 6 instructions; loads
|
||||
spin main af411c af4130 # 6 instructions; loads
|
||||
spin main b47a74 b47a88 # 6 instructions; loads
|
||||
spin main b4e080 b4e094 # 6 instructions; loads
|
||||
spin main b529f4 b52a08 # 6 instructions; loads
|
||||
spin main b52a7c b52a90 # 6 instructions; loads
|
||||
spin main b52cfc b52d10 # 6 instructions; loads
|
||||
spin main b52dd0 b52de4 # 6 instructions; loads
|
||||
spin main b53458 b5346c # 6 instructions; loads
|
||||
spin main b535a8 b535bc # 6 instructions; loads
|
||||
spin main b54320 b54334 # 6 instructions; loads
|
||||
spin main b545fc b54610 # 6 instructions; loads
|
||||
spin main b5662c b56640 # 6 instructions; loads
|
||||
spin main b56654 b56668 # 6 instructions; loads
|
||||
spin main b71800 b71814 # 6 instructions; loads
|
||||
spin main b71828 b7183c # 6 instructions; loads
|
||||
spin main b7b82c b7b840 # 6 instructions; loads
|
||||
spin main b80970 b80984 # 6 instructions; loads
|
||||
spin main b86fe8 b86ffc # 6 instructions; loads
|
||||
spin main b8702c b87040 # 6 instructions; loads
|
||||
spin main b87070 b87084 # 6 instructions; loads
|
||||
spin main b8775c b87770 # 6 instructions; loads
|
||||
spin main b97d6c b97d80 # 6 instructions; loads
|
||||
spin main b97ed4 b97ee8 # 6 instructions; loads
|
||||
spin main b9c628 b9c63c # 6 instructions; loads
|
||||
spin main b9c6c8 b9c6dc # 6 instructions; loads
|
||||
spin main b9c6f0 b9c704 # 6 instructions; loads
|
||||
spin main b9c7b8 b9c7cc # 6 instructions; loads
|
||||
spin main ba1db4 ba1dc8 # 6 instructions; loads
|
||||
spin main ba72e8 ba72fc # 6 instructions; loads
|
||||
spin main bb1070 bb1084 # 6 instructions; loads
|
||||
spin main bb1098 bb10ac # 6 instructions; loads
|
||||
spin main bb6c38 bb6c4c # 6 instructions; loads
|
||||
spin main bb7fa0 bb7fb4 # 6 instructions; loads
|
||||
spin main bd27c8 bd27d8 # 5 instructions; loads
|
||||
spin main bdd678 bdd68c # 6 instructions; loads
|
||||
spin main bf2ccc bf2ce0 # 6 instructions; loads
|
||||
spin main bf2e24 bf2e38 # 6 instructions; loads
|
||||
spin main bf32a0 bf32b4 # 6 instructions; loads
|
||||
spin main bf3730 bf3744 # 6 instructions; loads
|
||||
spin main c0a554 c0a568 # 6 instructions; loads
|
||||
spin main c0a6b4 c0a6c8 # 6 instructions; loads
|
||||
spin main c0a850 c0a864 # 6 instructions; loads
|
||||
spin main c0d758 c0d76c # 6 instructions; loads
|
||||
spin main c0d8b0 c0d8c4 # 6 instructions; loads
|
||||
spin main c0dd3c c0dd50 # 6 instructions; loads
|
||||
spin main c15ffc c16010 # 6 instructions; loads
|
||||
spin main c1665c c16670 # 6 instructions; loads
|
||||
spin main c2315c c23170 # 6 instructions; loads
|
||||
spin main c2374c c23760 # 6 instructions; loads
|
||||
spin main c23998 c239ac # 6 instructions; loads
|
||||
spin main c2d128 c2d13c # 6 instructions; loads
|
||||
spin main c3a9cc c3a9e0 # 6 instructions; loads
|
||||
spin main c5ab4c c5ab60 # 6 instructions; loads
|
||||
spin main c70388 c7039c # 6 instructions; loads
|
||||
spin main c703b0 c703c4 # 6 instructions; loads
|
||||
spin main c94f70 c94f84 # 6 instructions; loads
|
||||
spin main c95a00 c95a14 # 6 instructions; loads
|
||||
spin main c96764 c96778 # 6 instructions; loads
|
||||
spin main c96948 c9695c # 6 instructions; loads
|
||||
spin main c96ce4 c96cf8 # 6 instructions; loads
|
||||
spin main c96d54 c96d68 # 6 instructions; loads
|
||||
spin main c97400 c97414 # 6 instructions; loads
|
||||
spin main c97ca0 c97cb4 # 6 instructions; loads
|
||||
spin main c99ba0 c99bb4 # 6 instructions; loads
|
||||
spin main c99c90 c99ca4 # 6 instructions; loads
|
||||
spin main c9abac c9abc0 # 6 instructions; loads
|
||||
spin main c9ac8c c9aca0 # 6 instructions; loads
|
||||
spin main c9b2e0 c9b2f4 # 6 instructions; loads
|
||||
spin main c9d9f4 c9da08 # 6 instructions; loads
|
||||
spin main c9db3c c9db50 # 6 instructions; loads
|
||||
spin main c9df14 c9df30 # 8 instructions; loads
|
||||
spin main ccaaa8 ccaabc # 6 instructions; loads
|
||||
spin main cd38a4 cd38b8 # 6 instructions; loads
|
||||
spin main cda7c4 cda7d8 # 6 instructions; loads
|
||||
spin main cdb388 cdb39c # 6 instructions; loads
|
||||
spin main cdb3cc cdb3e0 # 6 instructions; loads
|
||||
spin main cdbab4 cdbac8 # 6 instructions; loads
|
||||
spin main cdbaf8 cdbb0c # 6 instructions; loads
|
||||
spin main cdbce4 cdbcf8 # 6 instructions; loads
|
||||
spin main cdce0c cdce20 # 6 instructions; loads
|
||||
spin main cecedc cecef0 # 6 instructions; loads
|
||||
spin main d0fbb4 d0fbc8 # 6 instructions; loads
|
||||
spin main d1acd0 d1ace4 # 6 instructions; loads
|
||||
spin main d1dc54 d1dc60 d1dcac d1dcc4 # 29 instructions; loads
|
||||
spin main d2273c d22750 # 6 instructions; loads
|
||||
spin main d231bc d231d0 # 6 instructions; loads
|
||||
spin main d23398 d233ac # 6 instructions; loads
|
||||
spin main d2e810 d2e824 # 6 instructions; loads
|
||||
spin main d3d8dc d3d8f0 # 6 instructions; loads
|
||||
spin main d4c3a8 d4c3bc # 6 instructions; loads
|
||||
spin main d4ffc4 d4ffd8 # 6 instructions; loads
|
||||
spin main d50a78 d50a8c # 6 instructions; loads
|
||||
spin main d50b04 d50b18 # 6 instructions; loads
|
||||
spin main d50bfc d50c10 # 6 instructions; loads
|
||||
spin main d50e60 d50e74 # 6 instructions; loads
|
||||
spin main d50e88 d50e9c # 6 instructions; loads
|
||||
spin main d50ebc d50ed0 # 6 instructions; loads
|
||||
spin main d510b8 d510cc # 6 instructions; loads
|
||||
spin main d51afc d51b18 # 8 instructions; loads
|
||||
spin main d52478 d5248c # 6 instructions; loads
|
||||
spin main d527f4 d52808 # 6 instructions; loads
|
||||
spin main d5283c d52850 # 6 instructions; loads
|
||||
spin main d52864 d52878 # 6 instructions; loads
|
||||
spin main d5288c d528a0 # 6 instructions; loads
|
||||
spin main d528f8 d5290c # 6 instructions; loads
|
||||
spin main d52920 d52934 # 6 instructions; loads
|
||||
spin main d529a8 d529bc # 6 instructions; loads
|
||||
spin main d52ab0 d52ac4 # 6 instructions; loads
|
||||
spin main d91344 d91358 # 6 instructions; loads
|
||||
spin main d91970 d91984 # 6 instructions; loads
|
||||
spin main d91cc0 d91cd4 # 6 instructions; loads
|
||||
spin main d91dc0 d91dd4 # 6 instructions; loads
|
||||
spin main d92280 d92294 # 6 instructions; loads
|
||||
spin main d923cc d923e0 # 6 instructions; loads
|
||||
spin main d93600 d93614 # 6 instructions; loads
|
||||
spin main d94100 d94114 # 6 instructions; loads
|
||||
spin main d9412c d94140 # 6 instructions; loads
|
||||
spin main d99098 d990ac # 6 instructions; loads
|
||||
spin main d9c260 d9c298 # 8 instructions; loads
|
||||
spin main d9c2c8 d9c300 # 8 instructions; loads
|
||||
spin main d9fe34 d9fe48 # 6 instructions; loads
|
||||
spin main da1eb4 da1ec8 # 6 instructions; loads
|
||||
spin main da1edc da1ef0 # 6 instructions; loads
|
||||
spin main da7010 da7024 # 6 instructions; loads
|
||||
spin main da70f8 da710c # 6 instructions; loads
|
||||
spin main dade1c dade30 # 6 instructions; loads
|
||||
spin main db58dc db58f0 # 6 instructions; loads
|
||||
spin main dbb0e4 dbb0f8 # 6 instructions; loads
|
||||
spin main dbca30 dbca4c # 8 instructions; loads
|
||||
spin main dbfc20 dbfc34 # 6 instructions; loads
|
||||
spin main dbff00 dbff14 # 6 instructions; loads
|
||||
spin main dc78fc dc7910 # 6 instructions; loads
|
||||
spin main dcaa88 dcaa9c # 6 instructions; loads
|
||||
spin main dcb2ac dcb2c0 # 6 instructions; loads
|
||||
spin main dcb9c8 dcb9dc # 6 instructions; loads
|
||||
spin main dd4a28 dd4a3c # 6 instructions; loads
|
||||
spin main dd6414 dd6428 # 6 instructions; loads
|
||||
spin main dd7498 dd74ac # 6 instructions; loads
|
||||
spin main dd78b4 dd78c8 # 6 instructions; loads
|
||||
spin main ddf338 ddf34c # 6 instructions; loads
|
||||
spin main de7324 de7338 # 6 instructions; loads
|
||||
spin main deb218 deb22c # 6 instructions; loads
|
||||
spin main df249c df24b0 # 6 instructions; loads
|
||||
spin main df2930 df2944 # 6 instructions; loads
|
||||
spin main dfab0c dfab20 # 6 instructions; loads
|
||||
spin main e1a248 e1a25c # 6 instructions; loads
|
||||
spin main e27b10 e27b24 # 6 instructions; loads
|
||||
spin main e28428 e2843c # 6 instructions; loads
|
||||
spin main e38cac e38cc0 # 6 instructions; loads
|
||||
spin main e38f90 e38fa4 # 6 instructions; loads
|
||||
spin main e39274 e39288 # 6 instructions; loads
|
||||
spin main e39558 e3956c # 6 instructions; loads
|
||||
spin main e40b1c e40b30 # 6 instructions; loads
|
||||
spin main e41478 e4148c # 6 instructions; loads
|
||||
spin main e450bc e450d0 # 6 instructions; loads
|
||||
spin main e45400 e45414 # 6 instructions; loads
|
||||
spin main e51aa0 e51ab4 # 6 instructions; loads
|
||||
spin main e51c08 e51c1c # 6 instructions; loads
|
||||
spin main e51cf4 e51d08 # 6 instructions; loads
|
||||
spin main e8e42c e8e440 # 6 instructions; loads
|
||||
spin main e90628 e9063c # 6 instructions; loads
|
||||
spin main e930d8 e930ec # 6 instructions; loads
|
||||
spin main e94c64 e94c78 # 6 instructions; loads
|
||||
spin main e9557c e95590 # 6 instructions; loads
|
||||
spin main e966e4 e966f8 # 6 instructions; loads
|
||||
spin main e9711c e97130 # 6 instructions; loads
|
||||
spin main e97164 e97178 # 6 instructions; loads
|
||||
spin main e9c134 e9c148 # 6 instructions; loads
|
||||
spin main ea4c28 ea4c3c # 6 instructions; loads
|
||||
spin main ea4c78 ea4c8c # 6 instructions; loads
|
||||
spin main ea4cc4 ea4cd8 # 6 instructions; loads
|
||||
spin main ea4d10 ea4d24 # 6 instructions; loads
|
||||
spin main eb07cc eb07d4 # 3 instructions; loads
|
||||
spin main eb07dc eb07e0 # 2 instructions; loads
|
||||
spin main eb0808 eb0810 # 3 instructions; loads
|
||||
spin main eb1548 eb155c # 6 instructions; loads
|
||||
spin main eb4240 eb4254 # 6 instructions; loads
|
||||
spin main eb5aa8 eb5abc # 6 instructions; loads
|
||||
spin main eb637c eb6390 # 6 instructions; loads
|
||||
spin main eb7954 eb7968 # 6 instructions; loads
|
||||
spin main ebf63c ebf650 # 6 instructions; loads
|
||||
spin main ebfb5c ebfb70 # 6 instructions; loads
|
||||
spin main ebfb98 ebfbac # 6 instructions; loads
|
||||
spin main ebfd50 ebfd64 # 6 instructions; loads
|
||||
spin main ebffb4 ebffc8 # 6 instructions; loads
|
||||
spin main eca534 eca548 # 6 instructions; loads
|
||||
spin main eca6f8 eca70c # 6 instructions; loads
|
||||
spin main ecb088 ecb09c # 6 instructions; loads
|
||||
spin main ed26e8 ed26fc # 6 instructions; loads
|
||||
spin main ed293c ed2950 # 6 instructions; loads
|
||||
spin main ed2ab0 ed2ac4 # 6 instructions; loads
|
||||
spin main ed2c24 ed2c38 # 6 instructions; loads
|
||||
spin main ed2ed0 ed2ee4 # 6 instructions; loads
|
||||
spin main ed3348 ed335c # 6 instructions; loads
|
||||
spin main ed351c ed3530 # 6 instructions; loads
|
||||
spin main ed3cf4 ed3d08 # 6 instructions; loads
|
||||
spin main ed42e0 ed42f4 # 6 instructions; loads
|
||||
spin main ed430c ed4320 # 6 instructions; loads
|
||||
spin main ed5294 ed52a8 # 6 instructions; loads
|
||||
spin main ed5658 ed566c # 6 instructions; loads
|
||||
spin main ed5680 ed5694 # 6 instructions; loads
|
||||
spin main ed79e8 ed79fc # 6 instructions; loads
|
||||
spin main ed7a28 ed7a3c # 6 instructions; loads
|
||||
spin main ed7a50 ed7a64 # 6 instructions; loads
|
||||
spin main ed7a78 ed7a8c # 6 instructions; loads
|
||||
spin main ed80ac ed80c0 # 6 instructions; loads
|
||||
spin main ed80d4 ed80e8 # 6 instructions; loads
|
||||
spin main ed934c ed9360 # 6 instructions; loads
|
||||
spin main ed94a0 ed94b4 # 6 instructions; loads
|
||||
spin main ed9a50 ed9a64 # 6 instructions; loads
|
||||
spin main ede7fc ede810 # 6 instructions; loads
|
||||
spin main ee0ce4 ee0cf8 # 6 instructions; loads
|
||||
spin main eebd28 eebd3c # 6 instructions; loads
|
||||
spin main ef9ed4 ef9ee8 # 6 instructions; loads
|
||||
spin main efa7a4 efa7b8 # 6 instructions; loads
|
||||
spin main efa7d4 efa7e8 # 6 instructions; loads
|
||||
spin main efad94 efada8 # 6 instructions; loads
|
||||
spin main efadc4 efadd8 # 6 instructions; loads
|
||||
spin main efdac8 efdadc # 6 instructions; loads
|
||||
spin main f01fa4 f01fb8 # 6 instructions; loads
|
||||
spin main f022e4 f022f8 # 6 instructions; loads
|
||||
spin main f02394 f023a8 # 6 instructions; loads
|
||||
spin main f02434 f02448 # 6 instructions; loads
|
||||
spin main f024e4 f024f8 # 6 instructions; loads
|
||||
spin main f3c87c f3c890 # 6 instructions; loads
|
||||
spin main f3d54c f3d560 # 6 instructions; loads
|
||||
spin main f3dba8 f3dbbc # 6 instructions; loads
|
||||
spin main f3dbd4 f3dbe8 # 6 instructions; loads
|
||||
spin main f547a4 f547b8 # 6 instructions; loads
|
||||
spin main f54d60 f54d74 # 6 instructions; loads
|
||||
spin main f57494 f574a8 # 6 instructions; loads
|
||||
spin main f66ef8 f66f0c # 6 instructions; loads
|
||||
spin main f66f20 f66f34 # 6 instructions; loads
|
||||
spin main f67170 f67184 # 6 instructions; loads
|
||||
spin main f671fc f67210 # 6 instructions; loads
|
||||
spin main f67224 f67238 # 6 instructions; loads
|
||||
spin main f67254 f67268 # 6 instructions; loads
|
||||
spin main f684f0 f68504 # 6 instructions; loads
|
||||
spin main f6a1ec f6a200 # 6 instructions; loads
|
||||
spin main f6a3f8 f6a40c # 6 instructions; loads
|
||||
spin main f6afc4 f6afd8 # 6 instructions; loads
|
||||
spin main f712a4 f712b8 # 6 instructions; loads
|
||||
spin main f71400 f71414 # 6 instructions; loads
|
||||
spin main f7155c f71570 # 6 instructions; loads
|
||||
spin main f716b8 f716cc # 6 instructions; loads
|
||||
spin main f729dc f729f0 # 6 instructions; loads
|
||||
spin main f73084 f73098 # 6 instructions; loads
|
||||
spin main f73148 f7315c # 6 instructions; loads
|
||||
spin main f76574 f76588 # 6 instructions; loads
|
||||
spin main f7c8ec f7c900 # 6 instructions; loads
|
||||
spin main f83bd0 f83be4 # 6 instructions; loads
|
||||
spin main f870f0 f87104 # 6 instructions; loads
|
||||
spin main f94eac f94ec0 # 6 instructions; loads
|
||||
spin main fa6580 fa6594 # 6 instructions; loads
|
||||
spin main fa65a8 fa65bc # 6 instructions; loads
|
||||
spin main fabb50 fabb64 # 6 instructions; loads
|
||||
spin main fabe74 fabe88 # 6 instructions; loads
|
||||
spin main fac8a0 fac8b4 # 6 instructions; loads
|
||||
spin main fac8c4 fac8d8 # 6 instructions; loads
|
||||
spin main face58 face6c # 6 instructions; loads
|
||||
spin main fc0a28 fc0a3c # 6 instructions; loads
|
||||
spin main fc0c68 fc0c7c # 6 instructions; loads
|
||||
spin main fc0e8c fc0ea0 # 6 instructions; loads
|
||||
spin main fc1054 fc1068 # 6 instructions; loads
|
||||
spin main fc1178 fc118c # 6 instructions; loads
|
||||
spin main fcb18c fcb1a8 # 8 instructions; loads
|
||||
spin main fccfe8 fccffc # 6 instructions; loads
|
||||
spin main fcd554 fcd640 # 4 instructions; loads
|
||||
spin main fcdc5c fcdce4 # 4 instructions; loads
|
||||
spin main fd2d30 fd2d44 # 6 instructions; loads
|
||||
spin main fd6250 fd6264 # 6 instructions; loads
|
||||
spin main fdf818 fdf82c # 6 instructions; loads
|
||||
spin main fdfb30 fdfb44 # 6 instructions; loads
|
||||
spin main fe0fa8 fe0fbc # 6 instructions; loads
|
||||
spin main fe1ad8 fe1aec # 6 instructions; loads
|
||||
spin main fea3a0 fea3b4 # 6 instructions; loads
|
||||
spin main fead08 fead1c # 6 instructions; loads
|
||||
spin main feb168 feb17c # 6 instructions; loads
|
||||
spin main feb1d0 feb1e4 # 6 instructions; loads
|
||||
spin main ff2af8 ff2b0c # 6 instructions; loads
|
||||
spin main ff5680 ff5694 # 6 instructions; loads
|
||||
spin main ff58e0 ff58f4 # 6 instructions; loads
|
||||
spin main ff9660 ff9674 # 6 instructions; loads
|
||||
spin main ffacec ffad00 # 6 instructions; loads
|
||||
spin main 1001500 1001514 # 6 instructions; loads
|
||||
spin main 1002c28 1002c3c # 6 instructions; loads
|
||||
spin main 1010b3c 1010b50 # 6 instructions; loads
|
||||
spin main 1010ef4 1010f08 # 6 instructions; loads
|
||||
spin main 1011244 1011258 # 6 instructions; loads
|
||||
spin main 1011544 1011558 # 6 instructions; loads
|
||||
spin main 1011a9c 1011ab0 # 6 instructions; loads
|
||||
spin main 1011e34 1011e48 # 6 instructions; loads
|
||||
spin main 10121b4 10121c8 # 6 instructions; loads
|
||||
spin main 1012228 101223c # 6 instructions; loads
|
||||
spin main 1013150 1013164 # 6 instructions; loads
|
||||
spin main 101349c 10134b0 # 6 instructions; loads
|
||||
spin main 1013920 1013934 # 6 instructions; loads
|
||||
spin main 1013b6c 1013b80 # 6 instructions; loads
|
||||
spin main 1014950 1014964 # 6 instructions; loads
|
||||
spin main 10149e8 10149fc # 6 instructions; loads
|
||||
spin main 1015bac 1015bc0 # 6 instructions; loads
|
||||
spin main 10160c0 10160d4 # 6 instructions; loads
|
||||
spin main 10166f8 101670c # 6 instructions; loads
|
||||
spin main 10167b4 10167c8 # 6 instructions; loads
|
||||
spin main 1016870 1016884 # 6 instructions; loads
|
||||
spin main 101692c 1016940 # 6 instructions; loads
|
||||
spin main 10170f0 1017104 # 6 instructions; loads
|
||||
spin main 10191dc 10191f0 # 6 instructions; loads
|
||||
spin main 101948c 10194a0 # 6 instructions; loads
|
||||
spin main 1019778 101978c # 6 instructions; loads
|
||||
spin main 1019a2c 1019a40 # 6 instructions; loads
|
||||
spin main 1019cf0 1019d04 # 6 instructions; loads
|
||||
spin main 1019ff4 101a008 # 6 instructions; loads
|
||||
spin main 101a054 101a068 # 6 instructions; loads
|
||||
spin main 101b594 101b5b0 # 8 instructions; loads
|
||||
spin main 101c7b4 101c7c8 # 6 instructions; loads
|
||||
spin main 101c7fc 101c810 # 6 instructions; loads
|
||||
spin main 101c844 101c858 # 6 instructions; loads
|
||||
spin main 101c88c 101c8a0 # 6 instructions; loads
|
||||
spin main 101c8d4 101c8e8 # 6 instructions; loads
|
||||
spin main 1023190 10231a4 # 6 instructions; loads
|
||||
spin main 10461ac 10461c0 # 6 instructions; loads
|
||||
spin main 1047278 104728c # 6 instructions; loads
|
||||
spin main 1050a8c 1050aa0 # 6 instructions; loads
|
||||
spin main 1055074 10550bc # 3 instructions; loads
|
||||
spin main 1056bd4 1056be8 # 6 instructions; loads
|
||||
spin main 106037c 1060390 # 6 instructions; loads
|
||||
spin main 1066bf4 1066c08 # 6 instructions; loads
|
||||
spin main 106bc38 106bc4c # 6 instructions; loads
|
||||
spin main 106be80 106be94 # 6 instructions; loads
|
||||
spin main 1070220 1070234 # 6 instructions; loads
|
||||
spin main 1071264 1071278 # 6 instructions; loads
|
||||
spin main 1071788 107179c # 6 instructions; loads
|
||||
spin main 1073910 1073924 # 6 instructions; loads
|
||||
spin main 10815e0 10815f4 # 6 instructions; loads
|
||||
spin main 108cb28 108cb3c # 6 instructions; loads
|
||||
spin main 1095678 109568c # 6 instructions; loads
|
||||
spin main 10956c0 10956d4 # 6 instructions; loads
|
||||
spin main 109f134 109f148 # 6 instructions; loads
|
||||
spin main 10bc820 10bc834 # 6 instructions; loads
|
||||
spin main 10be4c8 10be4dc # 6 instructions; loads
|
||||
spin main 10be4f0 10be504 # 6 instructions; loads
|
||||
spin main 10c2d4c 10c2d60 # 6 instructions; loads
|
||||
spin main 10c3748 10c375c # 6 instructions; loads
|
||||
spin main 10d5ef8 10d5f0c # 6 instructions; loads
|
||||
spin main 10da208 10da21c # 6 instructions; loads
|
||||
spin main 10e3edc 10e3ef0 # 6 instructions; loads
|
||||
spin main 10e5ee4 10e5ef8 # 6 instructions; loads
|
||||
spin main 10ec860 10ec874 # 6 instructions; loads
|
||||
spin main 10ec8dc 10ec8f0 # 6 instructions; loads
|
||||
spin main 10f1db4 10f1dc8 # 6 instructions; loads
|
||||
spin main 10ff3b4 10ff3c8 # 6 instructions; loads
|
||||
spin main 10ff788 10ff79c # 6 instructions; loads
|
||||
spin main 10ffef0 10fff04 # 6 instructions; loads
|
||||
spin main 1100500 1100514 # 6 instructions; loads
|
||||
spin main 1103e80 1103e94 # 6 instructions; loads
|
||||
spin main 110ca34 110cb68 # 4 instructions; loads
|
||||
spin main 110cab8 110cb4c # 4 instructions; loads
|
||||
spin main 1117420 1117434 # 6 instructions; loads
|
||||
spin main 1117448 111745c # 6 instructions; loads
|
||||
spin main 111bf5c 111bf70 # 6 instructions; loads
|
||||
spin main 111c1bc 111c1d0 # 6 instructions; loads
|
||||
spin main 111c918 111c92c # 6 instructions; loads
|
||||
spin main 112aef0 112af04 # 6 instructions; loads
|
||||
spin main 1143e28 1143e3c # 6 instructions; loads
|
||||
spin main 1145110 1145124 # 6 instructions; loads
|
||||
spin main 1145138 114514c # 6 instructions; loads
|
||||
spin main 1149244 1149258 # 6 instructions; loads
|
||||
spin main 114b868 114b87c # 6 instructions; loads
|
||||
spin main 114f850 114f864 # 6 instructions; loads
|
||||
spin main 114fcdc 114fcf0 # 6 instructions; loads
|
||||
spin main 1150cbc 1150cd0 # 6 instructions; loads
|
||||
spin main 115aa38 115aa4c # 6 instructions; loads
|
||||
spin main 117d7c8 117d7dc # 6 instructions; loads
|
||||
spin main 118a000 118a014 # 6 instructions; loads
|
||||
spin main 118a3d8 118a3ec # 6 instructions; loads
|
||||
spin main 118f65c 118f670 # 6 instructions; loads
|
||||
spin main 1193d30 1193d44 # 6 instructions; loads
|
||||
spin main 1198130 1198144 # 6 instructions; loads
|
||||
spin main 1198158 119816c # 6 instructions; loads
|
||||
spin main 1198e48 1198e5c # 6 instructions; loads
|
||||
spin main 119c414 119c428 # 6 instructions; loads
|
||||
spin main 11a0778 11a078c # 6 instructions; loads
|
||||
spin main 11a0fcc 11a0fe0 # 6 instructions; loads
|
||||
spin main 11a5884 11a5898 # 6 instructions; loads
|
||||
spin main 11bbd80 11bbd94 # 6 instructions; loads
|
||||
spin main 11bd154 11bd168 # 6 instructions; loads
|
||||
spin main 11bd17c 11bd190 # 6 instructions; loads
|
||||
spin main 11e5fc8 11e5fdc # 6 instructions; loads
|
||||
spin main 11e5ff0 11e6004 # 6 instructions; loads
|
||||
spin main 11ea5b8 11ea5cc # 6 instructions; loads
|
||||
spin main 11eccf8 11ecd0c # 6 instructions; loads
|
||||
spin main 11f50d8 11f50ec # 6 instructions; loads
|
||||
spin main 11f5624 11f5638 # 6 instructions; loads
|
||||
spin main 12010e8 12010fc # 6 instructions; loads
|
||||
spin main 120ecc0 120ecd4 # 6 instructions; loads
|
||||
spin main 12171d8 12171ec # 6 instructions; loads
|
||||
spin main 1217cd8 1217cec # 6 instructions; loads
|
||||
spin main 1219124 1219138 # 6 instructions; loads
|
||||
spin main 1231ca8 1231cbc # 6 instructions; loads
|
||||
spin main 1231e58 1231e6c # 6 instructions; loads
|
||||
spin main 1234784 1234798 # 6 instructions; loads
|
||||
spin main 1234d14 1234d28 # 6 instructions; loads
|
||||
spin main 1236974 1236988 # 6 instructions; loads
|
||||
spin main 1236b1c 1236b30 # 6 instructions; loads
|
||||
spin main 12479cc 12479e0 # 6 instructions; loads
|
||||
spin main 1261af8 1261b0c # 6 instructions; loads
|
||||
spin main 1261f20 1261f34 # 6 instructions; loads
|
||||
spin main 1262200 1262214 # 6 instructions; loads
|
||||
spin main 1268448 126845c # 6 instructions; loads
|
||||
spin main 1283a80 1283a94 # 6 instructions; loads
|
||||
spin main 128ba28 128ba3c # 6 instructions; loads
|
||||
spin main 128ba90 128baa4 # 6 instructions; loads
|
||||
spin main 128bc40 128bc54 # 6 instructions; loads
|
||||
spin main 128bc6c 128bc80 # 6 instructions; loads
|
||||
spin main 128bc98 128bcac # 6 instructions; loads
|
||||
spin main 128bcc4 128bcd8 # 6 instructions; loads
|
||||
spin main 128c468 128c47c # 6 instructions; loads
|
||||
spin main 128c490 128c4a4 # 6 instructions; loads
|
||||
spin main 12924ec 1292500 # 6 instructions; loads
|
||||
spin main 1292e4c 1292e60 # 6 instructions; loads
|
||||
spin main 1292eb4 1292ec8 # 6 instructions; loads
|
||||
spin main 129458c 12945a0 # 6 instructions; loads
|
||||
spin main 12966e0 12966f4 # 6 instructions; loads
|
||||
spin main 129e62c 129e640 # 6 instructions; loads
|
||||
spin main 129f954 129f968 # 6 instructions; loads
|
||||
spin main 12a61b0 12a61c4 # 6 instructions; loads
|
||||
spin main 12ab4cc 12ab4e0 # 6 instructions; loads
|
||||
spin main 12ab4f4 12ab508 # 6 instructions; loads
|
||||
spin main 12c1228 12c123c # 6 instructions; loads
|
||||
spin main 12c1250 12c1264 # 6 instructions; loads
|
||||
spin main 12ca068 12ca07c # 6 instructions; loads
|
||||
spin main 12d040c 12d0420 # 6 instructions; loads
|
||||
spin main 12e3820 12e3834 # 6 instructions; loads
|
||||
spin main 12e8e80 12e8e94 # 6 instructions; loads
|
||||
spin main 12e9640 12e9654 # 6 instructions; loads
|
||||
spin main 1309bc8 1309bdc # 6 instructions; loads
|
||||
spin main 1314ed8 1314eec # 6 instructions; loads
|
||||
spin main 131553c 1315550 # 6 instructions; loads
|
||||
spin main 1322ccc 1322dac # 11 instructions; loads
|
||||
spin main 13277f4 1327808 # 6 instructions; loads
|
||||
spin main 1327ab8 1327acc # 6 instructions; loads
|
||||
spin main 132a1e0 132a1f4 # 6 instructions; loads
|
||||
spin main 132b0a8 132b0bc # 6 instructions; loads
|
||||
spin main 132b87c 132b890 # 6 instructions; loads
|
||||
spin main 1332fc0 1332fd4 # 6 instructions; loads
|
||||
spin main 1363450 1363464 # 6 instructions; loads
|
||||
spin main 13feca8 13fecbc # 6 instructions; loads
|
||||
spin main 13fee40 13fee54 # 6 instructions; loads
|
||||
spin main 157c12c 157c140 # 6 instructions; loads
|
||||
spin main 157dfe0 157dff4 # 6 instructions; loads
|
||||
spin main 1585a20 1585a34 # 6 instructions; loads
|
||||
spin main 1585ee0 1585ef4 # 6 instructions; loads
|
||||
spin main 158c4c8 158c4dc # 6 instructions; loads
|
||||
spin main 1599a14 1599a28 # 6 instructions; loads
|
||||
spin main 159aed0 159aee4 # 6 instructions; loads
|
||||
spin main 159aef8 159af0c # 6 instructions; loads
|
||||
spin main 15a2a20 15a2a34 # 6 instructions; loads
|
||||
spin main 15a3228 15a323c # 6 instructions; loads
|
||||
spin main 15a34e8 15a34fc # 6 instructions; loads
|
||||
spin main 15a3510 15a3524 # 6 instructions; loads
|
||||
spin main 15a3f44 15a3f58 # 6 instructions; loads
|
||||
spin main 15ae678 15ae68c # 6 instructions; loads
|
||||
spin main 15b8d78 15b8d8c # 6 instructions; loads
|
||||
spin main 15b9218 15b922c # 6 instructions; loads
|
||||
spin main 15b9320 15b9334 # 6 instructions; loads
|
||||
spin main 15b9638 15b964c # 6 instructions; loads
|
||||
spin main 15c6c9c 15c6cb0 # 6 instructions; loads
|
||||
spin main 15d5380 15d5394 # 6 instructions; loads
|
||||
spin main 15e8a0c 15e8a20 # 6 instructions; loads
|
||||
spin main 15e9ad4 15e9ae8 # 6 instructions; loads
|
||||
spin main 15e9b7c 15e9b90 # 6 instructions; loads
|
||||
spin main 15ef1c4 15ef1d8 # 6 instructions; loads
|
||||
spin main 15ef304 15ef318 # 6 instructions; loads
|
||||
spin main 15ef32c 15ef340 # 6 instructions; loads
|
||||
spin main 1607238 160724c # 6 instructions; loads
|
||||
spin main 160779c 16077b0 # 6 instructions; loads
|
||||
spin main 16173e0 16173f4 # 6 instructions; loads
|
||||
spin main 1623748 162375c # 6 instructions; loads
|
||||
spin main 1627bdc 1627bf0 # 6 instructions; loads
|
||||
spin main 1627c2c 1627c40 # 6 instructions; loads
|
||||
spin main 1628eb8 1628ecc # 6 instructions; loads
|
||||
spin main 16298a8 16298bc # 6 instructions; loads
|
||||
spin main 16340f8 163410c # 6 instructions; loads
|
||||
spin main 1636748 163675c # 6 instructions; loads
|
||||
spin main 1636ed4 1636ee8 # 6 instructions; loads
|
||||
spin main 163b7f0 163b804 # 6 instructions; loads
|
||||
spin main 1648354 1648368 # 6 instructions; loads
|
||||
spin main 1651478 165148c # 6 instructions; loads
|
||||
spin main 1653730 1653744 # 6 instructions; loads
|
||||
spin main 1656c5c 1656c70 # 6 instructions; loads
|
||||
spin main 16596b0 16596b8 # 3 instructions; loads
|
||||
spin main 16596c0 16596c4 # 2 instructions; loads
|
||||
spin main 16596f8 1659700 # 3 instructions; loads
|
||||
spin main 165cb68 165cb7c # 6 instructions; loads
|
||||
spin main 1677368 167737c # 6 instructions; loads
|
||||
spin main 16dc224 16dc238 # 6 instructions; loads
|
||||
spin main 173d61c 173d630 # 6 instructions; loads
|
||||
spin main 1747354 1747368 # 6 instructions; loads
|
||||
spin main 1747918 174792c # 6 instructions; loads
|
||||
spin main 17a0a20 17a0a34 # 6 instructions; loads
|
||||
spin main 1932774 1932788 # 6 instructions; loads
|
||||
spin main 1933ea8 1933ebc # 6 instructions; loads
|
||||
spin main 1962e88 1962e90 # 3 instructions; loads
|
||||
spin main 1962e98 1962e9c # 2 instructions; loads
|
||||
spin main 1962f20 1962f28 # 3 instructions; loads
|
||||
spin main 196300c 1963014 # 3 instructions; loads
|
||||
spin main 196301c 1963020 # 2 instructions; loads
|
||||
spin main 19630c8 19630d0 # 3 instructions; loads
|
||||
spin main 1975850 1975858 # 3 instructions; loads
|
||||
spin main 1975860 1975864 # 2 instructions; loads
|
||||
spin main 1975888 1975890 # 3 instructions; loads
|
||||
spin main 19758c0 19758c8 # 3 instructions; loads
|
||||
spin main 19758d0 19758d4 # 2 instructions; loads
|
||||
spin main 19758fc 1975904 # 3 instructions; loads
|
||||
spin main 19e5048 19e505c # 6 instructions; loads
|
||||
spin main 1a8b8b4 1a8b8c4 # 5 instructions; loads
|
||||
spin main 1b65f9c 1b65fb0 # 6 instructions; loads
|
||||
spin main 1b66058 1b6606c # 6 instructions; loads
|
||||
spin main 1b66114 1b66128 # 6 instructions; loads
|
||||
spin main 1b66714 1b66728 # 6 instructions; loads
|
||||
spin main 1b667d0 1b667e4 # 6 instructions; loads
|
||||
spin main 1b6688c 1b668a0 # 6 instructions; loads
|
||||
spin main 1dff370 1dff37c # 4 instructions; loads
|
||||
spin main 21e6508 21e6530 # 4 instructions; loads
|
||||
spin main 222a3e0 222a3fc # 8 instructions; loads
|
||||
spin main 22322d0 22322d8 # 3 instructions; loads
|
||||
spin main 2232358 2232360 # 3 instructions; loads
|
||||
spin main 22323e8 2232410 # 3 instructions; loads
|
||||
spin main 223931c 223932c # 5 instructions; loads
|
||||
spin main 225dc10 225dc18 # 3 instructions; loads
|
||||
spin main 225dc20 225dc24 # 2 instructions; loads
|
||||
spin main 225dcac 225dcb4 # 3 instructions; loads
|
||||
spin main 225dd38 225dd40 # 3 instructions; loads
|
||||
spin main 225dd48 225dd4c # 2 instructions; loads
|
||||
spin main 225dd64 225dd6c # 3 instructions; loads
|
||||
spin main 22b72dc 22b72f0 # 6 instructions; loads
|
||||
spin main 22b7440 22b7454 # 6 instructions; loads
|
||||
spin main 22c60ac 22c60d0 # 4 instructions; loads
|
||||
spin main 22c619c 22c61a8 # 4 instructions; loads
|
||||
spin main 233ce40 233ce54 # 6 instructions; loads
|
||||
spin main 240ccb0 240ccb8 # 3 instructions; loads
|
||||
spin main 240ccc0 240ccc4 # 2 instructions; loads
|
||||
spin main 240cd40 240cd48 # 3 instructions; loads
|
||||
spin main 240d57c 240d584 # 3 instructions; loads
|
||||
spin main 240d58c 240d590 # 2 instructions; loads
|
||||
spin main 240d5fc 240d604 # 3 instructions; loads
|
||||
spin main 240da9c 240daa4 # 3 instructions; loads
|
||||
spin main 240daac 240dab0 # 2 instructions; loads
|
||||
spin main 240db3c 240db44 # 3 instructions; loads
|
||||
spin main 240dc80 240dc88 # 3 instructions; loads
|
||||
spin main 240dc90 240dc94 # 2 instructions; loads
|
||||
spin main 240dce8 240dcf0 # 3 instructions; loads
|
||||
spin main 240dd34 240dd3c # 3 instructions; loads
|
||||
spin main 240dd44 240dd48 # 2 instructions; loads
|
||||
spin main 240ddfc 240de04 # 3 instructions; loads
|
||||
spin main 240de0c 240de10 # 2 instructions; loads
|
||||
spin main 240de50 240de58 # 3 instructions; loads
|
||||
spin main 240de80 240de88 # 3 instructions; loads
|
||||
spin main 240df74 240df7c # 3 instructions; loads
|
||||
spin main 240df84 240df88 # 2 instructions; loads
|
||||
spin main 240e068 240e070 # 3 instructions; loads
|
||||
spin main 240e0fc 240e104 # 3 instructions; loads
|
||||
spin main 240e10c 240e110 # 2 instructions; loads
|
||||
spin main 240e1c4 240e1cc # 3 instructions; loads
|
||||
spin main 240e1e4 240e1e8 # 2 instructions; loads
|
||||
spin main 240e288 240e290 # 3 instructions; loads
|
||||
spin main 240e2b8 240e2c0 # 3 instructions; loads
|
||||
spin main 240e300 240e308 # 3 instructions; loads
|
||||
spin main 240e310 240e314 # 2 instructions; loads
|
||||
spin main 240e338 240e340 # 3 instructions; loads
|
||||
spin main 240e384 240e38c # 3 instructions; loads
|
||||
spin main 240e394 240e398 # 2 instructions; loads
|
||||
spin main 240e3c0 240e3c8 # 3 instructions; loads
|
||||
spin main 240e91c 240e924 # 3 instructions; loads
|
||||
spin main 240e92c 240e930 # 2 instructions; loads
|
||||
spin main 240e954 240e95c # 3 instructions; loads
|
||||
spin main 240f088 240f090 # 3 instructions; loads
|
||||
spin main 240f098 240f09c # 2 instructions; loads
|
||||
spin main 240f0c0 240f0c8 # 3 instructions; loads
|
||||
spin main 2410984 241098c # 3 instructions; loads
|
||||
spin main 2410994 2410998 # 2 instructions; loads
|
||||
spin main 2410a18 2410a20 # 3 instructions; loads
|
||||
spin main 2410a40 2410a48 # 3 instructions; loads
|
||||
spin main 2410a50 2410a54 # 2 instructions; loads
|
||||
spin main 2410ad4 2410adc # 3 instructions; loads
|
||||
spin main 2410e70 2410e78 # 3 instructions; loads
|
||||
spin main 2410e80 2410e84 # 2 instructions; loads
|
||||
spin main 2410ed4 2410edc # 3 instructions; loads
|
||||
spin main 2410f1c 2410f24 # 3 instructions; loads
|
||||
spin main 2410f2c 2410f30 # 2 instructions; loads
|
||||
spin main 2410fcc 2410fd4 # 3 instructions; loads
|
||||
spin main 241102c 2411034 # 3 instructions; loads
|
||||
spin main 241103c 2411040 # 2 instructions; loads
|
||||
spin main 24110c4 24110cc # 3 instructions; loads
|
||||
spin main 2413d28 2413d30 # 3 instructions; loads
|
||||
spin main 2413d38 2413d3c # 2 instructions; loads
|
||||
spin main 2413dc8 2413dd0 # 3 instructions; loads
|
||||
spin main 2413e04 2413e0c # 3 instructions; loads
|
||||
spin main 2413e14 2413e18 # 2 instructions; loads
|
||||
spin main 2413e84 2413e8c # 3 instructions; loads
|
||||
spin main 2416358 2416360 # 3 instructions; loads
|
||||
spin main 2416368 241636c # 2 instructions; loads
|
||||
spin main 2416388 2416390 # 3 instructions; loads
|
||||
spin main 2462470 2462484 # 6 instructions; loads
|
||||
spin main 2462498 24624ac # 6 instructions; loads
|
||||
spin main 24651f4 2465208 # 6 instructions; loads
|
||||
spin main 2465688 246569c # 6 instructions; loads
|
||||
spin main 2474d20 2474d34 # 6 instructions; loads
|
||||
spin main 2475154 2475168 # 6 instructions; loads
|
||||
spin main 2479134 2479148 # 6 instructions; loads
|
||||
spin main 2479b7c 2479b90 # 6 instructions; loads
|
||||
spin main 247a378 247a38c # 6 instructions; loads
|
||||
spin main 2493050 2493064 # 6 instructions; loads
|
||||
spin main 2886500 2886514 # 6 instructions; loads
|
||||
spin main 28865bc 28865d0 # 6 instructions; loads
|
||||
spin main 2886678 288668c # 6 instructions; loads
|
||||
spin main 2887484 2887498 # 6 instructions; loads
|
||||
spin main 2887540 2887554 # 6 instructions; loads
|
||||
spin main 28875fc 2887610 # 6 instructions; loads
|
||||
spin main 28876b8 28876cc # 6 instructions; loads
|
||||
spin main 2887774 2887788 # 6 instructions; loads
|
||||
spin main 2887830 2887844 # 6 instructions; loads
|
||||
spin main 28878ec 2887900 # 6 instructions; loads
|
||||
spin main 28879a8 28879bc # 6 instructions; loads
|
||||
spin main 2887e70 2887e84 # 6 instructions; loads
|
||||
spin main 2887f2c 2887f40 # 6 instructions; loads
|
||||
spin main 2a3e540 2a3e548 # 3 instructions; loads
|
||||
spin main 2a3e550 2a3e554 # 2 instructions; loads
|
||||
spin main 2a3e58c 2a3e594 # 3 instructions; loads
|
||||
# subsdk0: 8 spin waits
|
||||
spin subsdk0 2090 20a4 # 6 instructions; loads
|
||||
spin subsdk0 278b4 278c8 # 6 instructions; loads
|
||||
spin subsdk0 bcf28 bcfd0 bcfd8 # 14 instructions; loads
|
||||
spin subsdk0 bd0d0 bd178 bd180 # 14 instructions; loads
|
||||
spin subsdk0 c03a0 c03b4 # 6 instructions; loads
|
||||
spin subsdk0 1bbefc 1bbf08 # 4 instructions; loads
|
||||
spin subsdk0 1bd838 1bd84c # 6 instructions; loads
|
||||
spin subsdk0 1c0c68 1c0c74 # 4 instructions; loads
|
||||
# sdk: 5 spin waits
|
||||
spin sdk f5218 f5350 # 14 instructions; loads
|
||||
spin sdk 23a590 23a5d8 # 19 instructions; loads
|
||||
spin sdk 23a658 23a6a0 # 19 instructions; loads
|
||||
spin sdk 23a7fc 23a918 # 19 instructions; loads
|
||||
spin sdk 3d3250 3d3270 # 9 instructions; loads
|
||||
@@ -32,6 +32,12 @@ site realtime main 8f2398 1e2c1001 bd400120 # fmov s1, #0.5 -> ldr s0, [x9] (g
|
||||
site realtime main 8f23a8 5e21d800 d503201f # scvtf s0, s0 -> nop
|
||||
site realtime main 8f23ac 1e210800 d503201f # fmul s0, s0, s1 -> nop
|
||||
|
||||
# Equipment-switch cooldown (issue #18, found by JieML1100 on 1.4.3): the player update (main+0x852d24) steps the
|
||||
# countdown at player state +0x109c through the timer step (main+0xec98cc) only while it is above FLT_EPSILON, but the
|
||||
# equipment gate (main+0xcae6f8) blocks weapon, shield and bow changes while it is above 0. A measured ratio can leave
|
||||
# a remainder in (0, FLT_EPSILON] that is never stepped again; stepping while above 0 takes it below 0.
|
||||
site realtime main 852d28 1e270101 1e2703e1 # fmov s1, w8 (FLT_EPSILON) -> fmov s1, wzr
|
||||
|
||||
# Hair and cloth (upstream 290ec9f, 8f8022f): the actor animation update builds a second clock {seconds = factor / 30,
|
||||
# ratio = factor} per rendered frame. Three inlined places: at the fmul, seconds = measured seconds * factor; at the
|
||||
# store of the factor into the ratio slot, measured ratio * factor. The call of the shared update (main+0x89822c)
|
||||
|
||||
@@ -32,6 +32,13 @@ site realtime main 2daa70 1e201820 d503201f # fdiv s0, s1, s0 -> nop
|
||||
site realtime main 2daa74 1e2c1001 d503201f # fmov s1, #0.5 -> nop
|
||||
site realtime main 2daa78 1e210800 d503201f # fmul s0, s0, s1 -> nop
|
||||
|
||||
# Equipment-switch cooldown (issue #18, found by JieML1100): the player update (main+0x2b0368) steps the countdown at
|
||||
# player state +0x109c through the timer step (main+0x1b0834, value += ratio * rate) only while it is above FLT_EPSILON,
|
||||
# but the equipment gate (main+0x31cc7c) blocks weapon, shield and bow changes while it is above 0. Whole 30 Hz steps
|
||||
# reach exactly 0; a measured ratio leaves a rounding remainder, and one in (0, FLT_EPSILON] is never stepped again and
|
||||
# keeps equipment locked until a load. Stepping while above 0 takes the remainder below 0 like any other last step.
|
||||
site realtime main 2b036c 0f016681 0f000401 # movi v1.2s, #0x34, lsl #24 (FLT_EPSILON) -> movi v1.2s, #0
|
||||
|
||||
# Hair and cloth: the actor animation update builds a second clock {seconds = factor / 30, ratio = factor} per
|
||||
# rendered frame. 1.4.3 inlines it in two actor paths, where the factor was just stored to the ratio slot: the
|
||||
# replacement stores measured ratio * factor there and computes measured seconds * factor. Two other callers share the
|
||||
@@ -85,6 +92,15 @@ site aspect main b43460 bd406ac0 f81f0fea @page:x10 bd403940 f84107ea # ldr s0
|
||||
site aspect main 1ff04f4 1e211800 f81f0fea @page:x10 bd403940 f84107ea # fdiv s0, s0, s1 (viewport)
|
||||
site aspect main 253fbb0 1e211800 f81f0fea @page:x10 bd403940 f84107ea # fdiv s0, s0, s1 (viewport)
|
||||
|
||||
# Field of view (runtime/resolution.c res_fov_live writes TOTK_FOV / 100 to page +60 and switches the set). The same two
|
||||
# camera routines as the aspect set load the camera's vertical view angle (radians) a few words before the ratio and
|
||||
# hand it to sead's perspective projection (+0xa0, then sin / cos / tan of half of it at +0xa4..+0xac). The replacement
|
||||
# loads the angle as before and multiplies it by the factor on the page; s8 holds the factor because the next
|
||||
# instruction (fmul s8, s0, s1) overwrites it. Zooms the game makes itself (the bow, the scope) scale with it.
|
||||
set fov TOTK_FOV_PATCH 0
|
||||
site fov main 628c3c bd413e60 f81f0fea @page:x10 bd413e60 bd403d48 1e280800 f84107ea # ldr s0, [x19, #0x13c] (camera update)
|
||||
site fov main b4342c bd4066c0 f81f0fea @page:x10 bd4066c0 bd403d48 1e280800 f84107ea # ldr s0, [x22, #0x64] (camera setup)
|
||||
|
||||
# The game's zstd decoder (BSD zstd, statically linked into main) through the host's libzstd (runtime/ov_zstd.c):
|
||||
# every call of ZSTD_decompressMultiFrame (main+0x3a1f0: dctx, dst, capacity, src, size, dict, dict size, ddict) and of
|
||||
# its wrapper ZSTD_decompress_usingDDict (main+0x3a1e0, which tail-branches to it) goes to a host function on the patch
|
||||
|
||||
+2
-1
@@ -13,7 +13,8 @@ the generator), each <replacement> an instruction word (hex) or `@page:x<n>`, wh
|
||||
encoded for the site's address. The patch page is the first 4 KiB page after main's image (layout.py leaves the rest
|
||||
of the 2 MiB-aligned module slot free); the runtime commits it and owns its contents. Words in use, by byte offset:
|
||||
+0..15 the frame-rate step (runtime/frame_rate.c), +16..55 the output, swapchain and scene sizes
|
||||
(runtime/resolution.c res_init), +56..59 the camera's aspect ratio (runtime/resolution.c res_init, set aspect). A new
|
||||
(runtime/resolution.c res_init), +56..59 the camera's aspect ratio (runtime/resolution.c res_init, set aspect), +60..63
|
||||
the field-of-view factor (runtime/resolution.c res_fov_live, set fov). A new
|
||||
set takes words outside these.
|
||||
|
||||
The replacement words are lifted at the site's address in place of the original instruction, and the generated C reads
|
||||
|
||||
+14
-12
@@ -3,11 +3,12 @@
|
||||
// da35b83 (MIT, Copyright (c) 2026 Peiikai).
|
||||
// The boot driver: the guest main thread runs the game's own nn::init::Start
|
||||
// with three host callbacks, the second of which runs DT_INIT of main, subsdk0 and sdk; Start then calls nnMain.
|
||||
// --boot checks two checkpoints on the way and ends at the first nvnBootstrapLoader call:
|
||||
// SetFocusHandlingMode called from main+0xe7a954 (1.4.3: +0xbdb48c) in nnMain, every module's constructor flag
|
||||
// reads 2
|
||||
// nvnBootstrapLoader called from main+0xe7eb2c (1.4.3: +0xbe0360) in the graphics bring-up; logs the run
|
||||
// statistics, exits 0
|
||||
// --boot checks two checkpoints on the way and ends at the first nvnBootstrapLoader call, at the return address of
|
||||
// each in main (1.1.1, 1.2.1, 1.4.3):
|
||||
// SetFocusHandlingMode main+0xeb8c14 (1.2.1: +0xe7a954, 1.4.3: +0xbdb48c) in nnMain, every module's constructor
|
||||
// flag reads 2
|
||||
// nvnBootstrapLoader main+0xebcdec (1.2.1: +0xe7eb2c, 1.4.3: +0xbe0360) in the graphics bring-up; logs the
|
||||
// run statistics, exits 0
|
||||
// With --seconds S the run continues past that call; rt_frame_presented ends it once the world clock passed S.
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -22,18 +23,19 @@
|
||||
#include "platform/platform.h"
|
||||
|
||||
#define SYM_INIT_START "_ZN2nn4init5StartEmmPFvvES2_S2_"
|
||||
// main: return addresses of nnMain's SetFocusHandlingMode call (1.2.1 from this port's analysis of main; 1.4.3 from
|
||||
// MacNativeGames' scripts/verify_boot.py) and of the first nvnBootstrapLoader call, per game version.
|
||||
#define CHECKPOINT_RET (RT_V143 ? 0xbdb48cull : 0xe7a954ull)
|
||||
#define NVN_RET (RT_V143 ? 0xbe0360ull : 0xe7eb2cull)
|
||||
// main: return addresses of nnMain's SetFocusHandlingMode call (1.1.1 and 1.2.1 from this port's analysis of main;
|
||||
// 1.4.3 from MacNativeGames' scripts/verify_boot.py) and of the first nvnBootstrapLoader call, per game version.
|
||||
#define CHECKPOINT_RET (RT_V143 ? 0xbdb48cull : RT_V111 ? 0xeb8c14ull : 0xe7a954ull)
|
||||
#define NVN_RET (RT_V143 ? 0xbe0360ull : RT_V111 ? 0xebcdecull : 0xe7eb2cull)
|
||||
#define CTOR_DONE 2 // a module's constructor flag after its DT_INIT ran the init_array
|
||||
|
||||
// The init_array sizes (1.2.1: counted in the modules; 1.4.3: DT_INIT_ARRAYSZ); MacNativeGames' boot check expects
|
||||
// the same.
|
||||
// The init_array sizes (1.1.1 and 1.2.1: counted in the modules; 1.4.3: DT_INIT_ARRAYSZ); MacNativeGames' boot check
|
||||
// expects the same. The three tables carry the same rows, so cb_module_inits walks them by the size of one.
|
||||
typedef struct { const char *name; uint64_t entries; } InitCount;
|
||||
static const InitCount inits_111[] = {{"main", 10668}, {"subsdk0", 45}, {"sdk", 116}};
|
||||
static const InitCount inits_121[] = {{"main", 10673}, {"subsdk0", 45}, {"sdk", 116}};
|
||||
static const InitCount inits_143[] = {{"main", 10717}, {"subsdk0", 45}, {"sdk", 83}};
|
||||
#define expected_inits (RT_V143 ? inits_143 : inits_121)
|
||||
#define expected_inits (RT_V143 ? inits_143 : RT_V111 ? inits_111 : inits_121)
|
||||
|
||||
static volatile int32_t inits_done, checkpoint_seen;
|
||||
uint64_t rt_guest_start_ns;
|
||||
|
||||
+13
-7
@@ -35,9 +35,9 @@ static const char *const known[] = {
|
||||
"BIND_LSTICK_RIGHT", "BIND_LSTICK_UP", "BIND_MINUS",
|
||||
"BIND_PLUS", "BIND_R", "BIND_RCLICK", "BIND_RIGHT", "BIND_RSTICK_DOWN", "BIND_RSTICK_LEFT", "BIND_RSTICK_RIGHT", "BIND_RSTICK_UP",
|
||||
"BIND_SKIP", "BIND_STATS", "BIND_UP", "BIND_X", "BIND_Y",
|
||||
"BIND_ZL", "BIND_ZR", "BLOCK_VIEWS_3D", "CLOTH_REALTIME", "CORE_DIAG", "CRIT", "CRIT_OUT", "DESC_TEMPLATE", "DEVICE_PROFILE", "DUMP", "DUMP_BUF", "DUMP_DIR", "DUMP_LOG",
|
||||
"BIND_ZL", "BIND_ZR", "BLOCK_VIEWS_3D", "CLOTH_REALTIME", "COLOR_BRIGHTNESS", "COLOR_CONTRAST", "COLOR_GAMMA", "COLOR_PRESET", "COLOR_SATURATION", "CORE_DIAG", "CRIT", "CRIT_OUT", "DESC_TEMPLATE", "DEVICE_PROFILE", "DUMP", "DUMP_BUF", "DUMP_DIR", "DUMP_LOG",
|
||||
"EXEFS", "FEEDBACK", "FIX_C0", "FIX_ZERO", "FLIGHT", "FLIGHT_AT", "FLIGHT_DIR", "FLIGHT_FRAMES", "FLIGHT_SPIKE_MS",
|
||||
"FLIGHT_THUMBS", "FPS60", "FPS_LIMIT", "FREEZE_IDLE", "FREE_CORES", "FULLSCREEN", "GPL", "GPL_DRAW_BUILD", "GPU",
|
||||
"FLIGHT_THUMBS", "FOV", "FOV_PATCH", "FPS60", "FPS_LIMIT", "FREEZE_IDLE", "FREE_CORES", "FULLSCREEN", "GPL", "GPL_DRAW_BUILD", "GPU",
|
||||
"GPU_INDEX", "GPU_NAME", "HIGHFPS_FIX", "INPUT", "INPUT_RECORD", "INPUT_SCRIPT", "LANG", "LIB_PREBUILD",
|
||||
"LIB_PREBUILD_KEEP", "LIB_PREBUILD_THREADS", "LOCALE", "LOD_GUARD", "MEMORY", "MENU_LANG", "MENU_TEST",
|
||||
"MENU_TEST_DIR", "MENU_TEST_SET", "MINIMAL_LAYOUT", "MIRROR_GAP", "MOTION", "MOUSE_SENS", "MOVIE_HOST", "MOVIE_PACE",
|
||||
@@ -50,7 +50,7 @@ static const char *const known[] = {
|
||||
"SHADER_MEMO", "SHARED_FONTS", "SHARPNESS", "SHOTS", "SHOTS_DIR", "SHOTS_FULL", "SPEED_PROBE", "SPIN", "SPIN_HANDOFF_US", "SPLIT_DRAWS", "SPLIT_US", "STICK_DEADZONE", "SWKBD_ANSWER",
|
||||
"SWKBD_TEST", "TEST_PAD", "TEST_PREPARE_SHADERS", "TEX_ALIAS", "TEX_MEMO", "TEX_VRAM_PRESSURE", "TIMER_1MS", "TRACE", "UBO_SNAPSHOT", "UPSCALER", "VK_DYN_A2C", "VK_DYN_VERTEX", "VK_NO_FLOAT64",
|
||||
"VK_PRESENT", "VK_RECORDER", "VK_SSBO_ALIGN", "VK_SUBGROUP", "VK_SUBGROUP_PIN", "VK_VALIDATION", "VOLUME", "VRAM_HEADROOM", "VS_OUTS",
|
||||
"WB_FENCE_LIMIT", "WINDOW_SIZE", "XLATE", "XLATE_ID", "XLATE_PROCS", "ZSTD_CHECK", "ZSTD_NATIVE",
|
||||
"WB_EXACT", "WB_FENCE_LIMIT", "WINDOW_SIZE", "XLATE", "XLATE_ID", "XLATE_PROCS", "ZSTD_CHECK", "ZSTD_NATIVE",
|
||||
};
|
||||
|
||||
// The totk.ini section a key cfg_set may write goes to when the file lacks it (totk.ini.example's sections).
|
||||
@@ -59,7 +59,9 @@ static const struct { const char *key, *section; } sections[] = {
|
||||
{"REALTIME", "frame rate"}, {"CLOTH_REALTIME", "frame rate"}, {"HIGHFPS_FIX", "frame rate"},
|
||||
{"FULLSCREEN", "display"}, {"WINDOW_SIZE", "display"}, {"PERF_OVERLAY", "display"}, {"VK_PRESENT", "display"},
|
||||
{"GPU_INDEX", "display"}, {"OUTPUT_RES", "display"}, {"RENDER_SCALE", "display"}, {"UPSCALER", "display"},
|
||||
{"SHARPNESS", "display"}, {"ASPECT", "display"},
|
||||
{"SHARPNESS", "display"}, {"ASPECT", "display"}, {"FOV", "display"}, {"COLOR_PRESET", "display"},
|
||||
{"COLOR_BRIGHTNESS", "display"}, {"COLOR_CONTRAST", "display"}, {"COLOR_SATURATION", "display"},
|
||||
{"COLOR_GAMMA", "display"},
|
||||
{"DEVICE_PROFILE", "display"}, {"ASSUME_FOCUS", "display"},
|
||||
{"MENU_LANG", "language"}, {"LANG", "language"}, {"OPERATION_MODE", "language"},
|
||||
{"VOLUME", "audio"},
|
||||
@@ -80,7 +82,7 @@ static const struct { const char *key, *section; } sections[] = {
|
||||
{"PUSH_SKIP", "performance"}, {"SPLIT_DRAWS", "performance"}, {"SPLIT_US", "performance"}, {"VK_VALIDATION", "performance"},
|
||||
{"VK_DYN_VERTEX", "performance"}, {"VK_DYN_A2C", "performance"}, {"VK_SUBGROUP_PIN", "performance"},
|
||||
{"TRACE", "diagnostics"}, {"FLIGHT", "diagnostics"}, {"FLIGHT_SPIKE_MS", "diagnostics"},
|
||||
{"WB_FENCE_LIMIT", "diagnostics"},
|
||||
{"WB_FENCE_LIMIT", "diagnostics"}, {"WB_EXACT", "diagnostics"},
|
||||
};
|
||||
|
||||
// The settings the menu shows: the value in effect when the key is unset, and what the key accepts. The only place a
|
||||
@@ -95,7 +97,11 @@ static const CfgMeta metas[] = {
|
||||
{"PERF_OVERLAY", "off", "off|fps|full", LIVE},
|
||||
{"OUTPUT_RES", "1920x1080", NULL, RESTART}, {"RENDER_SCALE", "game", "game|50|67|75|100|125|150|200", RESTART},
|
||||
{"UPSCALER", "fsr", "fsr|bilinear", LIVE}, {"SHARPNESS", "80", "0..100", LIVE},
|
||||
{"ASPECT", "fit", "fit|stretch", LIVE},
|
||||
{"ASPECT", "fit", "fit|stretch", LIVE}, {"FOV", "100", "70..140", LIVE},
|
||||
// colour adjustment (vk_color.c); the preset is the menu's, the four values are what the present uses
|
||||
{"COLOR_PRESET", "default", "default|vivid|clarity|custom", LIVE}, {"COLOR_BRIGHTNESS", "0", "-30..30", LIVE},
|
||||
{"COLOR_CONTRAST", "100", "70..150", LIVE}, {"COLOR_SATURATION", "100", "50..200", LIVE},
|
||||
{"COLOR_GAMMA", "1", "0.8..1.2", LIVE},
|
||||
{"DEVICE_PROFILE", "auto", "auto|desktop|deck", RESTART},
|
||||
// Game
|
||||
{"FPS_LIMIT", "60", "30|40|45|60|90|120|144|display", LIVE}, {"FPS60", "1", "0|1", LIVE},
|
||||
@@ -131,7 +137,7 @@ static const CfgMeta metas[] = {
|
||||
{"VK_DYN_A2C", "", "0|1", RESTART}, {"VK_SUBGROUP_PIN", "1", "0|1", RESTART},
|
||||
// diagnostics
|
||||
{"TRACE", "", NULL, RESTART}, {"FLIGHT", "0", "0|1", RESTART}, {"FLIGHT_SPIKE_MS", "100", "0..60000", RESTART},
|
||||
{"WB_FENCE_LIMIT", "65536", "0..1073741824", RESTART},
|
||||
{"WB_FENCE_LIMIT", "65536", "0..1073741824", RESTART}, {"WB_EXACT", "1", "0|1", RESTART},
|
||||
{"INPUT_RECORD", "", NULL, RESTART},
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// Generated by tools/build_shaders.py from runtime/shaders/color_grade.comp; do not edit.
|
||||
// source sha256 c93d6ac75667bdee5442d7e94fbf0bd6e7fe7250caa98974424aead10f554106
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
static const uint32_t color_grade_spv[] = {
|
||||
0x07230203, 0x00010300, 0x000d000b, 0x00000073, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
|
||||
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
|
||||
0x0006000f, 0x00000005, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00060010, 0x00000004,
|
||||
0x00000011, 0x00000008, 0x00000008, 0x00000001, 0x00040047, 0x0000000d, 0x0000000b, 0x0000001c,
|
||||
0x00030047, 0x00000015, 0x00000002, 0x00050048, 0x00000015, 0x00000000, 0x00000023, 0x00000000,
|
||||
0x00050048, 0x00000015, 0x00000001, 0x00000023, 0x00000008, 0x00050048, 0x00000015, 0x00000002,
|
||||
0x00000023, 0x00000010, 0x00040047, 0x00000029, 0x00000021, 0x00000000, 0x00040047, 0x00000029,
|
||||
0x00000022, 0x00000000, 0x00030047, 0x0000005d, 0x00000019, 0x00040047, 0x0000005d, 0x00000021,
|
||||
0x00000001, 0x00040047, 0x0000005d, 0x00000022, 0x00000000, 0x00040047, 0x0000006a, 0x0000000b,
|
||||
0x00000019, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00040015, 0x00000006,
|
||||
0x00000020, 0x00000001, 0x00040017, 0x00000007, 0x00000006, 0x00000002, 0x00040015, 0x0000000a,
|
||||
0x00000020, 0x00000000, 0x00040017, 0x0000000b, 0x0000000a, 0x00000003, 0x00040020, 0x0000000c,
|
||||
0x00000001, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000001, 0x00040017, 0x0000000e,
|
||||
0x0000000a, 0x00000002, 0x00030016, 0x00000013, 0x00000020, 0x00040017, 0x00000014, 0x00000013,
|
||||
0x00000004, 0x0005001e, 0x00000015, 0x00000007, 0x00000007, 0x00000014, 0x00040020, 0x00000016,
|
||||
0x00000009, 0x00000015, 0x0004003b, 0x00000016, 0x00000017, 0x00000009, 0x0004002b, 0x00000006,
|
||||
0x00000018, 0x00000001, 0x00040020, 0x00000019, 0x00000009, 0x00000007, 0x00020014, 0x0000001c,
|
||||
0x00040017, 0x0000001d, 0x0000001c, 0x00000002, 0x00040017, 0x00000023, 0x00000013, 0x00000003,
|
||||
0x00090019, 0x00000026, 0x00000013, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001,
|
||||
0x00000000, 0x0003001b, 0x00000027, 0x00000026, 0x00040020, 0x00000028, 0x00000000, 0x00000027,
|
||||
0x0004003b, 0x00000028, 0x00000029, 0x00000000, 0x0004002b, 0x00000006, 0x0000002c, 0x00000000,
|
||||
0x0004002b, 0x00000006, 0x00000033, 0x00000002, 0x0004002b, 0x0000000a, 0x00000034, 0x00000000,
|
||||
0x00040020, 0x00000035, 0x00000009, 0x00000013, 0x0004002b, 0x00000013, 0x0000003c, 0x3f000000,
|
||||
0x0004002b, 0x0000000a, 0x0000003f, 0x00000001, 0x0004002b, 0x00000013, 0x00000046, 0x3e59b3d0,
|
||||
0x0004002b, 0x00000013, 0x00000047, 0x3f371759, 0x0004002b, 0x00000013, 0x00000048, 0x3d93dd98,
|
||||
0x0006002c, 0x00000023, 0x00000049, 0x00000046, 0x00000047, 0x00000048, 0x0004002b, 0x0000000a,
|
||||
0x0000004d, 0x00000002, 0x0004002b, 0x00000013, 0x00000053, 0x00000000, 0x0006002c, 0x00000023,
|
||||
0x00000054, 0x00000053, 0x00000053, 0x00000053, 0x0004002b, 0x0000000a, 0x00000056, 0x00000003,
|
||||
0x00090019, 0x0000005b, 0x00000013, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000002,
|
||||
0x00000002, 0x00040020, 0x0000005c, 0x00000000, 0x0000005b, 0x0004003b, 0x0000005c, 0x0000005d,
|
||||
0x00000000, 0x0004002b, 0x00000013, 0x00000061, 0x3f800000, 0x0004002b, 0x0000000a, 0x00000069,
|
||||
0x00000008, 0x0006002c, 0x0000000b, 0x0000006a, 0x00000069, 0x00000069, 0x0000003f, 0x0006002c,
|
||||
0x00000023, 0x00000071, 0x0000003c, 0x0000003c, 0x0000003c, 0x0006002c, 0x00000023, 0x00000072,
|
||||
0x00000061, 0x00000061, 0x00000061, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003,
|
||||
0x000200f8, 0x00000005, 0x000300f7, 0x0000006b, 0x00000000, 0x000300fb, 0x00000034, 0x0000006c,
|
||||
0x000200f8, 0x0000006c, 0x0004003d, 0x0000000b, 0x0000000f, 0x0000000d, 0x0007004f, 0x0000000e,
|
||||
0x00000010, 0x0000000f, 0x0000000f, 0x00000000, 0x00000001, 0x0004007c, 0x00000007, 0x00000011,
|
||||
0x00000010, 0x00050041, 0x00000019, 0x0000001a, 0x00000017, 0x00000018, 0x0004003d, 0x00000007,
|
||||
0x0000001b, 0x0000001a, 0x000500af, 0x0000001d, 0x0000001e, 0x00000011, 0x0000001b, 0x0004009a,
|
||||
0x0000001c, 0x0000001f, 0x0000001e, 0x000300f7, 0x00000021, 0x00000000, 0x000400fa, 0x0000001f,
|
||||
0x00000020, 0x00000021, 0x000200f8, 0x00000020, 0x000200f9, 0x0000006b, 0x000200f8, 0x00000021,
|
||||
0x0004003d, 0x00000027, 0x0000002a, 0x00000029, 0x00050041, 0x00000019, 0x0000002d, 0x00000017,
|
||||
0x0000002c, 0x0004003d, 0x00000007, 0x0000002e, 0x0000002d, 0x00050080, 0x00000007, 0x0000002f,
|
||||
0x00000011, 0x0000002e, 0x00040064, 0x00000026, 0x00000030, 0x0000002a, 0x0007005f, 0x00000014,
|
||||
0x00000031, 0x00000030, 0x0000002f, 0x00000002, 0x0000002c, 0x0008004f, 0x00000023, 0x00000032,
|
||||
0x00000031, 0x00000031, 0x00000000, 0x00000001, 0x00000002, 0x00060041, 0x00000035, 0x00000036,
|
||||
0x00000017, 0x00000033, 0x00000034, 0x0004003d, 0x00000013, 0x00000037, 0x00000036, 0x00060050,
|
||||
0x00000023, 0x00000039, 0x00000037, 0x00000037, 0x00000037, 0x00050081, 0x00000023, 0x0000003a,
|
||||
0x00000032, 0x00000039, 0x00050083, 0x00000023, 0x0000003e, 0x0000003a, 0x00000071, 0x00060041,
|
||||
0x00000035, 0x00000040, 0x00000017, 0x00000033, 0x0000003f, 0x0004003d, 0x00000013, 0x00000041,
|
||||
0x00000040, 0x0005008e, 0x00000023, 0x00000042, 0x0000003e, 0x00000041, 0x00050081, 0x00000023,
|
||||
0x00000044, 0x00000042, 0x00000071, 0x00050094, 0x00000013, 0x0000004a, 0x00000044, 0x00000049,
|
||||
0x00060050, 0x00000023, 0x0000004b, 0x0000004a, 0x0000004a, 0x0000004a, 0x00060041, 0x00000035,
|
||||
0x0000004e, 0x00000017, 0x00000033, 0x0000004d, 0x0004003d, 0x00000013, 0x0000004f, 0x0000004e,
|
||||
0x00060050, 0x00000023, 0x00000050, 0x0000004f, 0x0000004f, 0x0000004f, 0x0008000c, 0x00000023,
|
||||
0x00000051, 0x00000001, 0x0000002e, 0x0000004b, 0x00000044, 0x00000050, 0x0007000c, 0x00000023,
|
||||
0x00000055, 0x00000001, 0x00000028, 0x00000051, 0x00000054, 0x00060041, 0x00000035, 0x00000057,
|
||||
0x00000017, 0x00000033, 0x00000056, 0x0004003d, 0x00000013, 0x00000058, 0x00000057, 0x00060050,
|
||||
0x00000023, 0x00000059, 0x00000058, 0x00000058, 0x00000058, 0x0007000c, 0x00000023, 0x0000005a,
|
||||
0x00000001, 0x0000001a, 0x00000055, 0x00000059, 0x0004003d, 0x0000005b, 0x0000005e, 0x0000005d,
|
||||
0x0008000c, 0x00000023, 0x00000064, 0x00000001, 0x0000002b, 0x0000005a, 0x00000054, 0x00000072,
|
||||
0x00050051, 0x00000013, 0x00000065, 0x00000064, 0x00000000, 0x00050051, 0x00000013, 0x00000066,
|
||||
0x00000064, 0x00000001, 0x00050051, 0x00000013, 0x00000067, 0x00000064, 0x00000002, 0x00070050,
|
||||
0x00000014, 0x00000068, 0x00000065, 0x00000066, 0x00000067, 0x00000061, 0x00040063, 0x0000005e,
|
||||
0x00000011, 0x00000068, 0x000200f9, 0x0000006b, 0x000200f8, 0x0000006b, 0x000100fd, 0x00010038,
|
||||
};
|
||||
@@ -161,10 +161,14 @@ static uint64_t read_ptr(uint64_t a) {
|
||||
uint64_t p = 0;
|
||||
return read_guest(a, &p, 8) ? p : 0;
|
||||
}
|
||||
// 1.2.1: [main+0x4728538] (1.4.3: [main+0x3add238]) +0x1e8 scene, its component 13 lists the resident actors; the one
|
||||
// named "Player" has its position at +0x2b4 (MacNativeGames world_survey.c, game_version.h TOTK_GAME_V121/V143).
|
||||
// 1.2.1: [main+0x4728538] (1.1.1: [main+0x4630a68], 1.4.3: [main+0x3add238]) +0x1e8 scene, its component 13 lists the
|
||||
// resident actors; the one named "Player" has its position at +0x2b4 (MacNativeGames world_survey.c, game_version.h
|
||||
// TOTK_GAME_V121/V143). The 1.1.1 slot was measured from its own dereference chain: 181 scene accesses in that build
|
||||
// read it, and one of them reproduces this walk (the 14-component check, component 13, the 0x70 stride and the name
|
||||
// compare) instruction for instruction.
|
||||
static int player_pos(float xyz[3]) {
|
||||
uint64_t module = read_ptr(MOD_MAIN_BASE + (RT_V143 ? 0x3add238 : 0x4728538)), scene = module ? read_ptr(module + 0x1e8) : 0;
|
||||
uint64_t module = read_ptr(MOD_MAIN_BASE + (RT_V143 ? 0x3add238 : RT_V111 ? 0x4630a68 : 0x4728538)),
|
||||
scene = module ? read_ptr(module + 0x1e8) : 0;
|
||||
int32_t count = 0;
|
||||
if (!scene || !read_guest(scene + 0x50, &count, 4) || count < 14 || count > 256) return 0;
|
||||
uint64_t comps = read_ptr(scene + 0x58), resident = comps ? read_ptr(comps + 13 * 8) : 0;
|
||||
|
||||
+185
-46
@@ -146,31 +146,36 @@ static int remove_tree(const char *path) {
|
||||
}
|
||||
|
||||
// ---- save transactions
|
||||
// The save mount's root is a working copy, made from the committed save when the first save mount appears. Commit
|
||||
// copies it to a staging directory and swaps that in for the committed save:
|
||||
// The save mount's root is a working copy, made from the newest save when the first save mount appears. Commit copies
|
||||
// it to a staging directory, which a rename makes a finished commit, and swaps that in for the committed save:
|
||||
//
|
||||
// <profile>/save/<uid>/ committed save: what the next start loads
|
||||
// <profile>/save/<uid>/ committed save: what the next start loads when no finished commit waits
|
||||
// <profile>/save-tx/work/ working copy, the save mount's root; deleted at the last Unmount and at start
|
||||
// <profile>/save-tx/new/ Commit's staging copy of the working copy
|
||||
// <profile>/save-tx/ready-<n>/ a finished commit not swapped in yet: the newest save
|
||||
// <profile>/save-tx/old/ the committed save a Commit replaced, on its way to save-backup
|
||||
//
|
||||
// Commit: (1) copy work to new and flush every file (FlushFileBuffers) and directory, a file equal to the committed
|
||||
// one as a hard link to it; (2) rename save/<uid> to old;
|
||||
// (3) rename new to save/<uid>, the commit point; (4) move old to save-backup/<UTC time> and keep the newest three.
|
||||
// Both renames are durable (plat_fs_rename). The game's Commit returns 0 once steps (1) to (4) are
|
||||
// queued; a thread runs them (95-135 ms on the game thread before, in the 1.4.3 fight save) while every change to the
|
||||
// working copy waits in save_touched, and Unmount and the next Commit wait too. A crash in that time loses this Commit
|
||||
// only, and a failed one is logged instead of returned. save/<uid> is renamed away only after new is complete
|
||||
// on disk, so the recovery rule at start (save_recover) is:
|
||||
// save/<uid> present it is the committed save (the old one if the crash came before (3), the new one after);
|
||||
// new, if present, is an unfinished copy and is deleted (rollback);
|
||||
// save/<uid> absent the crash came between (2) and (3) and new is complete: rename it to save/<uid> (roll
|
||||
// forward); with no new either, old goes back to save/<uid>.
|
||||
// Commit: (1) copy work to new and flush every file (FlushFileBuffers) and directory, a file equal to the newest
|
||||
// save's as a hard link to it; (2) rename new to ready-<n>, the commit point, and delete the ready-<n> it supersedes;
|
||||
// (3) rename save/<uid> to old; (4) rename ready-<n> to save/<uid>; (5) move old to save-backup/<UTC time> and keep
|
||||
// the newest three. Every rename is durable (plat_fs_rename). Windows refuses (3) while any file under save/<uid> is
|
||||
// open, in this or another process (a virus scanner, Explorer's preview, a sync tool), and a folder an elevated run
|
||||
// made can refuse it too. ready-<n> then stays the newest save: later working copies are made from it, and the next
|
||||
// Commit and the next start try (3) to (5) again. The game's Commit returns 0 once the steps are queued; a thread runs
|
||||
// them (95-135 ms on the game thread before, in the 1.4.3 fight save) while every change to the working copy waits in
|
||||
// save_touched, and Unmount and the next Commit wait too. A crash in that time loses this Commit only, and a failed
|
||||
// one is logged instead of returned. The recovery rule at start (save_recover):
|
||||
// new present an unfinished copy: deleted (rollback). An older build renamed save/<uid> away before its
|
||||
// finished copy: with save/<uid> and ready-<n> absent, new is that copy and becomes save/<uid>;
|
||||
// ready-<n> present the highest n is the newest save and (3) to (5) run (roll forward); the others are deleted;
|
||||
// save/<uid> absent with no ready-<n> either, old goes back to save/<uid>.
|
||||
// Then old, if present, goes to save-backup, and work (changes no Commit took) is deleted.
|
||||
#define SAVE_BACKUPS 3
|
||||
#define COPY_CHUNK (1u << 20)
|
||||
|
||||
static char tx_root[1100], backup_root[1100], new_root[1100], old_root[1100];
|
||||
static char ready_root[1100]; // the newest finished commit not swapped in, empty when none
|
||||
static unsigned ready_seq; // the highest ready-<n> this run made or found
|
||||
static PlatMutex tx_lock = PLAT_MUTEX_INIT; // taken before lock, never inside it
|
||||
static int save_mounts; // M_SAVE mounts: the working copy exists while this is above 0
|
||||
|
||||
@@ -183,10 +188,11 @@ static int crashed(void) {
|
||||
|
||||
static int exists(const char *p) { return plat_fs_kind(p) != PLAT_FS_MISSING; }
|
||||
|
||||
// Rename a directory, durably; retried for up to 2 s while another process (a virus scanner, the indexer) holds a
|
||||
// file in it. 0 on success.
|
||||
static int move_dir(const char *from, const char *to) {
|
||||
for (int i = 0; i < 40; i++) {
|
||||
// Rename a directory, durably; retried every 50 ms, tries times in all, while another process (a virus scanner, the
|
||||
// indexer) holds a file in it. 0 on success.
|
||||
#define MOVE_TRIES 40 // 2 s
|
||||
static int move_dir(const char *from, const char *to, int tries) {
|
||||
for (int i = 0; i < tries; i++) {
|
||||
int e = plat_fs_rename(from, to, 1);
|
||||
if (!e) return 0;
|
||||
if (e != PLAT_FS_BUSY) return -1;
|
||||
@@ -274,6 +280,44 @@ static int copy_tree(const char *src, const char *dst, const char *base, int dur
|
||||
return bad;
|
||||
}
|
||||
|
||||
static int listed_cmp(const void *a, const void *b) {
|
||||
return strcmp(((const Listed *)a)->name, ((const Listed *)b)->name);
|
||||
}
|
||||
|
||||
// Whether paths a and b are one file (hard links of it).
|
||||
static int same_inode(const char *a, const char *b) {
|
||||
PlatFile *h[2] = {plat_fs_open(a, 0, NULL), plat_fs_open(b, 0, NULL)};
|
||||
PlatFileId ia, ib;
|
||||
int same = h[0] && h[1] && plat_file_id(h[0], &ia) && plat_file_id(h[1], &ib) && ia.volume == ib.volume &&
|
||||
ia.file_id == ib.file_id;
|
||||
for (int i = 0; i < 2; i++)
|
||||
if (h[i]) plat_file_close(h[i]);
|
||||
return same;
|
||||
}
|
||||
|
||||
// Whether tree a holds the names and kinds of tree b and each file of it as a hard link: what copy_tree makes from a
|
||||
// working copy whose every file equals base's.
|
||||
static int same_tree(const char *a, const char *b) {
|
||||
Listing la, lb;
|
||||
int ea = list_dir(a, &la), eb = list_dir(b, &lb);
|
||||
int same = !ea && !eb && la.n == lb.n;
|
||||
if (same) {
|
||||
qsort(la.e, (size_t)la.n, sizeof *la.e, listed_cmp);
|
||||
qsort(lb.e, (size_t)lb.n, sizeof *lb.e, listed_cmp);
|
||||
}
|
||||
for (int i = 0; same && i < la.n; i++) {
|
||||
same = !strcmp(la.e[i].name, lb.e[i].name) && la.e[i].kind == lb.e[i].kind && la.e[i].kind != PLAT_FS_LINK;
|
||||
if (!same) break;
|
||||
char *pa = join(a, la.e[i].name), *pb = join(b, lb.e[i].name);
|
||||
same = la.e[i].kind == PLAT_FS_DIR ? same_tree(pa, pb) : same_inode(pa, pb);
|
||||
free(pa);
|
||||
free(pb);
|
||||
}
|
||||
listing_free(la.e, la.n);
|
||||
listing_free(lb.e, lb.n);
|
||||
return same;
|
||||
}
|
||||
|
||||
static int name_cmp(const void *a, const void *b) { return strcmp(a, b); }
|
||||
|
||||
// The backups' folder names (digits first), sorted: oldest first, as rotate_backup names them in creation order.
|
||||
@@ -312,7 +356,7 @@ static void rotate_backup(void) {
|
||||
while (strcmp(name, newest) <= 0 && k < 100000);
|
||||
}
|
||||
char *dst = join(backup_root, name);
|
||||
if (move_dir(old_root, dst)) {
|
||||
if (move_dir(old_root, dst, MOVE_TRIES)) {
|
||||
rt_log("fs: cannot move the replaced save to save-backup; deleted");
|
||||
remove_tree(old_root);
|
||||
}
|
||||
@@ -325,22 +369,82 @@ static void rotate_backup(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// The newest save: the finished commit waiting to be swapped in, else the committed save.
|
||||
static const char *newest_save(void) { return *ready_root ? ready_root : save_root; }
|
||||
|
||||
// Steps (3) to (5): swap the finished commit ready_root in for the committed save. 0 on success; on failure
|
||||
// ready_root stays the newest save. (3) is tried for 200 ms only: the commit is safe in ready_root, the next Commit
|
||||
// or start tries again, and the game's next change to its save data waits for this one.
|
||||
#define SWAP_TRIES 4
|
||||
static int swap_in(void) {
|
||||
if (!*ready_root) return 0;
|
||||
rotate_backup(); // an old left by a failed (5): (3) needs its name
|
||||
if (exists(save_root) && move_dir(save_root, old_root, SWAP_TRIES)) {
|
||||
char why[512];
|
||||
plat_fs_busy_reason(save_root, why, sizeof why);
|
||||
rt_log("fs: save commit: the committed save cannot be replaced (%s); the commit waits in %s and the next "
|
||||
"Commit or start retries", *why ? why : "a file in it is in use", ready_root);
|
||||
return -1;
|
||||
}
|
||||
if (crashed()) return -1;
|
||||
if (move_dir(ready_root, save_root, MOVE_TRIES)) {
|
||||
rt_log("fs: save commit: cannot move the finished commit to the committed save; it waits in %s", ready_root);
|
||||
move_dir(old_root, save_root, MOVE_TRIES); // else save/<uid> stays absent and the next start rolls forward
|
||||
return -1;
|
||||
}
|
||||
*ready_root = 0;
|
||||
if (crashed()) return -1;
|
||||
rotate_backup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The number n of a "ready-<n>" name, 0 for any other name.
|
||||
static unsigned ready_number(const char *name) {
|
||||
char *end;
|
||||
if (strncmp(name, "ready-", 6) || name[6] < '0' || name[6] > '9') return 0;
|
||||
unsigned long n = strtoul(name + 6, &end, 10);
|
||||
return *end || n > 0xffffffffu ? 0 : (unsigned)n;
|
||||
}
|
||||
|
||||
// Startup: the newest finished commit into ready_root; the older ones, which it supersedes, are deleted.
|
||||
static void ready_scan(void) {
|
||||
Listing l;
|
||||
list_dir(tx_root, &l);
|
||||
*ready_root = 0, ready_seq = 0;
|
||||
for (int i = 0; i < l.n; i++)
|
||||
if (l.e[i].kind == PLAT_FS_DIR && ready_number(l.e[i].name) > ready_seq) ready_seq = ready_number(l.e[i].name);
|
||||
for (int i = 0; i < l.n; i++) {
|
||||
unsigned n = l.e[i].kind == PLAT_FS_DIR ? ready_number(l.e[i].name) : 0;
|
||||
char *p = n ? join(tx_root, l.e[i].name) : NULL;
|
||||
if (n == ready_seq && p) snprintf(ready_root, sizeof ready_root, "%s", p);
|
||||
else if (p) remove_tree(p);
|
||||
free(p);
|
||||
}
|
||||
listing_free(l.e, l.n);
|
||||
}
|
||||
|
||||
// Startup: finish or roll back an interrupted Commit and drop uncommitted changes (the rule above).
|
||||
static void save_recover(void) {
|
||||
const char *did = "nothing to recover";
|
||||
ready_scan();
|
||||
int has_save = exists(save_root);
|
||||
if (!has_save && exists(new_root)) {
|
||||
if (move_dir(new_root, save_root)) rt_fatal("fs: cannot complete an interrupted save commit");
|
||||
if (exists(new_root) && !has_save && !*ready_root) {
|
||||
if (move_dir(new_root, save_root, MOVE_TRIES)) rt_fatal("fs: cannot complete an interrupted save commit");
|
||||
did = "completed an interrupted commit";
|
||||
} else if (!has_save && exists(old_root)) {
|
||||
if (move_dir(old_root, save_root)) rt_fatal("fs: cannot roll back an interrupted save commit");
|
||||
did = "rolled back an interrupted commit";
|
||||
has_save = 1;
|
||||
} else if (exists(new_root)) {
|
||||
if (remove_tree(new_root)) rt_fatal("fs: cannot delete an unfinished save commit");
|
||||
did = "rolled back an unfinished commit";
|
||||
}
|
||||
if (!has_save && !*ready_root && exists(old_root)) {
|
||||
if (move_dir(old_root, save_root, MOVE_TRIES)) rt_fatal("fs: cannot roll back an interrupted save commit");
|
||||
did = "rolled back an interrupted commit";
|
||||
} else if (exists(old_root))
|
||||
did = "moved the save an interrupted commit replaced to save-backup";
|
||||
rotate_backup();
|
||||
if (*ready_root)
|
||||
did = swap_in() ? "a finished commit still waits in save-tx; the game loads it from there"
|
||||
: "completed a finished commit";
|
||||
if (exists(work_root)) {
|
||||
if (remove_tree(work_root)) rt_fatal("fs: cannot delete the old save working copy");
|
||||
if (!strcmp(did, "nothing to recover"))
|
||||
@@ -349,11 +453,11 @@ static void save_recover(void) {
|
||||
rt_log("host save recovery: %s", did);
|
||||
}
|
||||
|
||||
// The first save mount: a fresh working copy of the committed save.
|
||||
// The first save mount: a fresh working copy of the newest save.
|
||||
static void work_create(void) {
|
||||
int n = 0, linked = 0;
|
||||
uint64_t bytes = 0;
|
||||
if (remove_tree(work_root) || copy_tree(save_root, work_root, NULL, 0, &n, &linked, &bytes))
|
||||
if (remove_tree(work_root) || copy_tree(newest_save(), work_root, NULL, 0, &n, &linked, &bytes))
|
||||
rt_fatal("fs: cannot make the save working copy");
|
||||
rt_log("host save working copy: %d files, %.1f MiB", n, bytes / 1048576.0);
|
||||
}
|
||||
@@ -364,38 +468,45 @@ static void work_discard(void) {
|
||||
if (remove_tree(work_root)) rt_log("fs: save working copy not fully deleted (a file still open?)");
|
||||
}
|
||||
|
||||
// Commit of the save mount, tx_lock held. Steps (1) to (4) above; every failure before (3) leaves the committed save.
|
||||
// Commit of the save mount, tx_lock held. Steps (1) to (5) above; a failure before (2) leaves the newest save as it
|
||||
// was, one after it leaves the commit waiting in ready_root.
|
||||
static uint32_t save_commit_locked(void) {
|
||||
uint64_t t0 = plat_now_ns(), bytes = 0;
|
||||
int nfiles = 0, linked = 0;
|
||||
tx_step = tx_dead = 0;
|
||||
rotate_backup(); // an old left by a failed (4)
|
||||
const char *base = exists(save_root) ? save_root : NULL;
|
||||
const char *base = exists(newest_save()) ? newest_save() : NULL;
|
||||
if (remove_tree(new_root) || copy_tree(work_root, new_root, base, 1, &nfiles, &linked, &bytes)) {
|
||||
if (tx_dead) return FS_R_INVALID_ARGUMENT;
|
||||
rt_log("fs: save commit: copying the working copy failed; committed save unchanged");
|
||||
rt_log("fs: save commit: copying the working copy failed; the newest save is unchanged");
|
||||
remove_tree(new_root);
|
||||
return FS_R_INVALID_ARGUMENT;
|
||||
}
|
||||
if (crashed()) return FS_R_INVALID_ARGUMENT;
|
||||
if (exists(save_root) && move_dir(save_root, old_root)) {
|
||||
rt_log("fs: save commit: cannot move the committed save; committed save unchanged");
|
||||
if (base && linked == nfiles && same_tree(new_root, base)) { // the game rewrote equal bytes: no save, no backup
|
||||
remove_tree(new_root);
|
||||
plat_xchg32(&save_dirty, 0);
|
||||
int waits = swap_in();
|
||||
if (tx_dead) return FS_R_INVALID_ARGUMENT;
|
||||
rt_log("fs commit save: %d files, none changed, in %.0f ms%s", nfiles, (plat_now_ns() - t0) / 1e6,
|
||||
waits ? "; the newest save waits in save-tx" : "");
|
||||
return 0;
|
||||
}
|
||||
char ready[1100];
|
||||
snprintf(ready, sizeof ready, "%s/ready-%010u", tx_root, ready_seq + 1);
|
||||
if (move_dir(new_root, ready, MOVE_TRIES)) {
|
||||
rt_log("fs: save commit: cannot finish the staging copy; the newest save is unchanged");
|
||||
remove_tree(new_root);
|
||||
return FS_R_INVALID_ARGUMENT;
|
||||
}
|
||||
if (crashed()) return FS_R_INVALID_ARGUMENT;
|
||||
if (move_dir(new_root, save_root)) {
|
||||
if (move_dir(old_root, save_root))
|
||||
rt_fatal("fs: save commit: cannot swap in the new save; the next start completes it");
|
||||
rt_log("fs: save commit: cannot swap in the new save; committed save unchanged");
|
||||
remove_tree(new_root);
|
||||
return FS_R_INVALID_ARGUMENT;
|
||||
}
|
||||
if (crashed()) return FS_R_INVALID_ARGUMENT;
|
||||
rotate_backup();
|
||||
ready_seq++;
|
||||
if (*ready_root) remove_tree(ready_root); // superseded: the working copy it was made from held all of it
|
||||
snprintf(ready_root, sizeof ready_root, "%s", ready);
|
||||
plat_xchg32(&save_dirty, 0);
|
||||
rt_log("fs commit save: %d files, %d changed (%.1f MiB written), %d unchanged linked, in %.0f ms", nfiles,
|
||||
nfiles - linked, bytes / 1048576.0, linked, (plat_now_ns() - t0) / 1e6);
|
||||
if (crashed()) return FS_R_INVALID_ARGUMENT;
|
||||
int waits = swap_in();
|
||||
if (tx_dead) return FS_R_INVALID_ARGUMENT;
|
||||
rt_log("fs commit save: %d files, %d changed (%.1f MiB written), %d unchanged linked, in %.0f ms%s", nfiles,
|
||||
nfiles - linked, bytes / 1048576.0, linked, (plat_now_ns() - t0) / 1e6, waits ? "; waits in save-tx" : "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1401,7 +1512,8 @@ static void probe_save_tx(const uint64_t *uid) {
|
||||
else mixed++;
|
||||
CHECK(ok && (va == vb) && (va == committed), "crash at commit step %d: tx_a %d, tx_b %d (old %d, new %d)", k,
|
||||
va, vb, committed, next);
|
||||
CHECK(!exists(new_root) && !exists(old_root), "crash at commit step %d: staging left after recovery", k);
|
||||
CHECK(!exists(new_root) && !exists(old_root) && !*ready_root, "crash at commit step %d: staging left after "
|
||||
"recovery", k);
|
||||
}
|
||||
CHECK(steps >= 4 && olds && news && !mixed, "crash points: %d (%d old, %d new, %d mixed)", steps, olds, news,
|
||||
mixed);
|
||||
@@ -1416,6 +1528,13 @@ static void probe_save_tx(const uint64_t *uid) {
|
||||
tx_links_committed("tx_a.sav"), tx_links_committed("tx_b.sav"));
|
||||
committed = gen;
|
||||
|
||||
// a Commit that changed no byte (the game rewrites its files at start) makes no new save and no backup
|
||||
static char backups_before[64][48], backups_after[64][48];
|
||||
int nbefore = backup_names(backups_before);
|
||||
CHECK(tx_write(a, committed) && tx_commit() == 0 && tx_read_committed("tx_a.sav") == committed &&
|
||||
backup_names(backups_after) == nbefore && !memcmp(backups_before, backups_after, sizeof backups_before),
|
||||
"a Commit of unchanged bytes made a backup");
|
||||
|
||||
// the background commit: a write right after Commit waits for it and stays out of it; an Unmount right after
|
||||
// Commit waits for it and keeps it
|
||||
int queued = ++gen;
|
||||
@@ -1428,6 +1547,26 @@ static void probe_save_tx(const uint64_t *uid) {
|
||||
"an Unmount right after Commit lost it");
|
||||
committed = gen;
|
||||
|
||||
// a file of the committed save held open by another program (a virus scanner, Explorer's preview, a sync tool):
|
||||
// Windows refuses to rename a directory with an open file under it, so the committed save cannot be swapped out.
|
||||
// The game and a restart still see every Commit, and the first start after the file is closed swaps them in.
|
||||
char *held_path = join(save_root, "tx_b.sav");
|
||||
PlatFile *held = plat_fs_open(held_path, 0, NULL);
|
||||
free(held_path);
|
||||
int pending = ++gen;
|
||||
CHECK(held && tx_write(a, pending) && tx_commit() == 0, "Commit with a committed file held open");
|
||||
probe_restart(uid);
|
||||
CHECK(tx_read(a) == pending, "a Commit made while a committed file was held open, after a restart: tx_a %d, "
|
||||
"expected %d", tx_read(a), pending);
|
||||
pending = ++gen;
|
||||
CHECK(tx_write(a, pending) && tx_commit() == 0 && tx_read(a) == pending, "a second Commit while held open");
|
||||
if (held) plat_file_close(held);
|
||||
probe_restart(uid);
|
||||
CHECK(tx_read(a) == pending && tx_read_committed("tx_a.sav") == pending && tx_read_committed("tx_b.sav") == kept,
|
||||
"the held-open Commits after the file was closed and a restart: tx_a %d, on disk %d, expected %d",
|
||||
tx_read(a), tx_read_committed("tx_a.sav"), pending);
|
||||
committed = pending;
|
||||
|
||||
// the read-only mount shows the committed save and refuses changes
|
||||
CHECK(tx_write(a, 99) && RES(S_MOUNT_SAVE_RO, U("ro"), TOTK_APPLICATION_ID, U(uid)) == 0 &&
|
||||
tx_read("ro:/tx_a.sav") == committed && RES(S_OPEN_FILE, U(&h), U("ro:/tx_a.sav"), FS_OPEN_WRITE) ==
|
||||
|
||||
+14
-5
@@ -214,6 +214,7 @@ static void window_settings_live(void) {
|
||||
unsigned v = cfg_version();
|
||||
if (started && v == seen) return;
|
||||
seen = v;
|
||||
res_fov_live();
|
||||
const char *e = cfg_value("TOTK_VK_PRESENT");
|
||||
if (!started || strncmp(e, present, sizeof present - 1)) {
|
||||
snprintf(present, sizeof present, "%s", e);
|
||||
@@ -595,20 +596,27 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
|
||||
VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
}
|
||||
// Colour adjustment (TOTK_COLOR_*, vk_color.c): an adjusted copy of the crop takes the game image's place below
|
||||
VkImage graded = general ? vk_color_record(cb, slot, src, fmt, rect) : VK_NULL_HANDLE;
|
||||
const int grect[4] = {0, 0, cw, ch};
|
||||
VkImage psrc = graded ? graded : src;
|
||||
VkFormat pfmt = graded ? VK_FORMAT_R16G16B16A16_SFLOAT : fmt;
|
||||
const int *prect = graded ? grect : rect;
|
||||
// FSR (TOTK_UPSCALER) when the picture's rectangle is larger than the crop; else the bilinear blit
|
||||
int fsr = general && vk_fsr_record(cb, slot, src, fmt, w, h, rect, dst, to);
|
||||
int fsr = general && vk_fsr_record(cb, slot, psrc, pfmt, graded ? cw : w, graded ? ch : h, prect, dst, to);
|
||||
g_scaler = fsr ? 2 : cw != to[2] || ch != to[3];
|
||||
if (!fsr) {
|
||||
if (general) // a GENERAL source (vk_texture.c images): earlier submissions' writes become visible to the blit here
|
||||
sync_barrier(cb, src, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
sync_barrier(cb, psrc, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_WRITE_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
VkImageBlit region = {
|
||||
.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .srcOffsets = {{cx, cy, 0}, {cx + cw, cy + ch, 1}},
|
||||
.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||
.srcOffsets = {{prect[0], prect[1], 0}, {prect[0] + cw, prect[1] + ch, 1}},
|
||||
.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstOffsets = {{to[0], to[1], 0}, {to[0] + to[2], to[1] + to[3], 1}}};
|
||||
vkCmdBlitImage(cb, src, general ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst,
|
||||
vkCmdBlitImage(cb, psrc, general ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, VK_FILTER_LINEAR);
|
||||
if (general) // later submissions that write the source wait for the blit's read
|
||||
sync_barrier(cb, src, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_TRANSFER_BIT, 0,
|
||||
sync_barrier(cb, psrc, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_TRANSFER_BIT, 0,
|
||||
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, 0);
|
||||
}
|
||||
// last: the image's layout, and the stage and access of its last write, as each step leaves it
|
||||
@@ -719,6 +727,7 @@ void gfx_window_release_vk(void) {
|
||||
g_flip_on = 0; // its blit is done (device idle); the swapchain goes away unpresented
|
||||
menu_vk_release();
|
||||
vk_fsr_release();
|
||||
vk_color_release();
|
||||
free_capture();
|
||||
free_thumbs();
|
||||
for (int i = 0; i < FRAMES; i++) {
|
||||
|
||||
@@ -50,11 +50,13 @@ static const char *str_field(const char *from, const char *to, const char *name,
|
||||
static int hexval(char c) { return c <= '9' ? c - '0' : (c | 32) - 'a' + 10; }
|
||||
|
||||
// The shader census the probes translate (tools/shader_census.py --version <ver>): build/<ver>/shaders in the nearest
|
||||
// parent of totk.exe that has one with a jobs.json, the running game version's first, then the other's (any census
|
||||
// parent of totk.exe that has one with a jobs.json, the running game version's first, then the others' (any census
|
||||
// serves a probe). 1 with that folder in dir.
|
||||
int probe_census_dir(char *dir, size_t n) {
|
||||
const char *const versions[2] = {RT_V143 ? "v143" : "v121", RT_V143 ? "v121" : "v143"};
|
||||
for (int v = 0; v < 2; v++) {
|
||||
const char *const versions[3] = {RT_V111 ? "v111" : RT_V143 ? "v143" : "v121",
|
||||
RT_V111 ? "v121" : RT_V143 ? "v121" : "v143",
|
||||
RT_V111 ? "v143" : RT_V143 ? "v111" : "v111"};
|
||||
for (int v = 0; v < 3; v++) {
|
||||
char exe[PLAT_PATH_MAX * 2];
|
||||
int len = plat_exe_path(exe, sizeof exe);
|
||||
if (!len || len >= sizeof exe) return 0;
|
||||
|
||||
@@ -131,6 +131,7 @@ static int present_and_check(Source *s, int first, int i, int *w_out, int *h_out
|
||||
}
|
||||
|
||||
int probe_window(void) {
|
||||
if (!menu_font_check()) return fail("the menu's font check takes a damaged font or refuses a good one");
|
||||
gfx_window_start();
|
||||
VkCtx *c = vk_ctx();
|
||||
if (!c) return fail("no Vulkan device");
|
||||
|
||||
+3
-1
@@ -184,8 +184,9 @@ typedef struct { const char *file, *sha256; } RtFileHash;
|
||||
extern const RtFileHash rt_exefs_sha256[]; // tools/data/expected-<version>.json "exefs_sha256"
|
||||
extern const int rt_exefs_sha256_count;
|
||||
extern const char rt_default_exefs[], rt_default_romfs[], rt_default_profile[];
|
||||
extern const int rt_game_version; // the game build: 121 (1.2.1) or 143 (1.4.3)
|
||||
extern const int rt_game_version; // the game build: 111 (1.1.1), 121 (1.2.1) or 143 (1.4.3)
|
||||
extern const char rt_port_version[]; // VERSION at the build's root: 0.1.0, 0.1.0+973c626 in a release
|
||||
#define RT_V111 (rt_game_version == 111) // 1.1.1: its own boot constants and data slots
|
||||
#define RT_V143 (rt_game_version >= 143)
|
||||
|
||||
// ---- kernel (kernel.c)
|
||||
@@ -253,6 +254,7 @@ void res_sizes(unsigned out[2], unsigned scene[2]); // the output and scene
|
||||
int res_is_16x9(unsigned w, unsigned h); // within 1% of 16:9 (the test res_init and romfs.c share)
|
||||
int res_wants_wide_overlay(const unsigned out[2]); // not 16:9 on a game version with the camera aspect patch
|
||||
void res_wanted(unsigned out[2]); // the output size TOTK_OUTPUT_RES asks for
|
||||
void res_fov_live(void); // TOTK_FOV to the camera (set fov); on a settings change
|
||||
// ---- code patches (patches.c) and the frame step (frame_rate.c)
|
||||
void rt_patches_init(void); // after ldr_load: switches from the environment, the patch
|
||||
// page committed; logs the sets
|
||||
|
||||
@@ -306,6 +306,9 @@ static void ap_init(void);
|
||||
static int cores_enabled(void) {
|
||||
if (g_cores_on < 0) {
|
||||
g_cores_on = !cfg_bool("TOTK_FREE_CORES"); // "0" keeps the gate, like unset
|
||||
if (!g_cores_on)
|
||||
rt_log("kernel: virtual core gate off (TOTK_FREE_CORES): guest threads run without the limit of one per "
|
||||
"core that the game's per-core data needs; for experiments only, the game can fail");
|
||||
diag_init();
|
||||
ap_init();
|
||||
}
|
||||
|
||||
+8
-3
@@ -552,8 +552,8 @@ uint64_t ldr_plt_stub(const char *module, const char *name) {
|
||||
}
|
||||
|
||||
// ---- --probe-loader
|
||||
// Expected values, measured on the 1.2.1 and 1.4.3 modules; the 1.4.3 relocation totals match the ones
|
||||
// MacNativeGames' port notes give for that version.
|
||||
// Expected values, measured on the 1.1.1, 1.2.1 and 1.4.3 modules; the 1.4.3 relocation totals match the ones
|
||||
// MacNativeGames' port notes give for that version. 1.1.1 shares subsdk0 and sdk with 1.2.1 down to every count.
|
||||
typedef struct {
|
||||
const char *name;
|
||||
uint64_t kinds[K_COUNT], total, sym_import, sym_own;
|
||||
@@ -564,12 +564,17 @@ static const Expect expect_121[] = {
|
||||
{"subsdk0", {1623, 12820, 1030, 3924}, 19397, 6576, 11198, 667, {9, 0, 654}, 4, 0x30, 45},
|
||||
{"sdk", {10917, 13976, 1207, 8932}, 35032, 16, 24099, 13, {8, 0, 0}, 5, 0x30, 116},
|
||||
};
|
||||
static const Expect expect_111[] = {
|
||||
{"main", {508604, 1412, 32, 639}, 510687, 2083, 0, 673, {0, 16, 645}, 12, 0x30, 10668},
|
||||
{"subsdk0", {1623, 12820, 1030, 3924}, 19397, 6576, 11198, 667, {9, 0, 654}, 4, 0x30, 45},
|
||||
{"sdk", {10917, 13976, 1207, 8932}, 35032, 16, 24099, 13, {8, 0, 0}, 5, 0x30, 116},
|
||||
};
|
||||
static const Expect expect_143[] = {
|
||||
{"main", {499078, 1393, 28, 650}, 501149, 2071, 0, 680, {0, 16, 656}, 8, 0x10b4f10, 10717},
|
||||
{"subsdk0", {3424, 10927, 905, 3817}, 19073, 6557, 9092, 663, {9, 0, 654}, 0, 0x20, 45},
|
||||
{"sdk", {15013, 10135, 1079, 7832}, 34059, 10, 19036, 8, {8, 0, 0}, 0, 0x400, 83},
|
||||
};
|
||||
#define expect (RT_V143 ? expect_143 : expect_121)
|
||||
#define expect (RT_V143 ? expect_143 : RT_V111 ? expect_111 : expect_121)
|
||||
|
||||
static int failures;
|
||||
#define CHECK(cond, ...) do { if (!(cond)) { failures++; printf(" FAIL: " __VA_ARGS__); putchar('\n'); } } while (0)
|
||||
|
||||
+11
-1
@@ -183,7 +183,8 @@ static int by_host(const void *a, const void *b) {
|
||||
return x < y ? -1 : x > y;
|
||||
}
|
||||
|
||||
// The lifted function that contains a host code address, by the nearest function start below it.
|
||||
// The lifted function that contains a host code address, by the nearest function start below it; 0 outside the lifted
|
||||
// code.
|
||||
static uint64_t lifted_function(uintptr_t rip) {
|
||||
if (!fn_rev) {
|
||||
static RtModule mods[MAX_MODULES];
|
||||
@@ -197,6 +198,9 @@ static uint64_t lifted_function(uintptr_t rip) {
|
||||
fn_rev[fn_rev_n++] = (FnRev){(uintptr_t)mods[i].fns[k], mods[i].addrs[k]};
|
||||
qsort(fn_rev, fn_rev_n, sizeof *fn_rev, by_host);
|
||||
}
|
||||
// The lifted code is one range with the host code outside it (kernel.c ap_init checks that); an address above the
|
||||
// last function start is taken for host code, which costs only the last function's body.
|
||||
if (!fn_rev_n || rip > fn_rev[fn_rev_n - 1].host) return 0;
|
||||
uint64_t lo = 0, hi = fn_rev_n;
|
||||
while (lo < hi) {
|
||||
uint64_t mid = (lo + hi) / 2;
|
||||
@@ -220,6 +224,12 @@ static void crash_handler(const PlatCrash *x) {
|
||||
rt_log_raw("\n", 1);
|
||||
uint64_t f = lifted_function(rip);
|
||||
if (f) rt_logf(" in lifted function f_%llx = %s\n", (unsigned long long)f, ldr_describe(f, d, sizeof d));
|
||||
for (int i = 0; i < x->nframes; i++) { // abort and invalid parameter: where the host code called it
|
||||
uint64_t g = lifted_function(x->frames[i]);
|
||||
rt_logf(" host frame %2d: totk+%#llx", i, (unsigned long long)(x->frames[i] - (rip - x->offset)));
|
||||
if (g) rt_logf(" in lifted function f_%llx", (unsigned long long)g);
|
||||
rt_log_raw("\n", 1);
|
||||
}
|
||||
Cpu *c = kern_cpu();
|
||||
if (c) {
|
||||
for (int i = 0; i < 31; i++)
|
||||
|
||||
+12
-1
@@ -82,7 +82,12 @@ static void cmd_reserve(cmd_t *o, size_t need) {
|
||||
if (!cap) cap = 64 << 10;
|
||||
while (used + need + OP_RESERVE > cap) cap *= 2;
|
||||
uint8_t *b = realloc(o->seg, cap);
|
||||
if (!b) rt_fatal("nvn: command recording buffer of %zu bytes", cap);
|
||||
// A block this size fails only without commit (the fatal report states it) or when seg is no block of ours: a
|
||||
// command buffer object the game overwrote or copied shows a wrong magic or more used than its old size.
|
||||
if (!b)
|
||||
rt_fatal("nvn: command recording buffer of %zu bytes (command buffer %p, magic %#x, buffer %p of %zu bytes, %zu "
|
||||
"used, %zu asked)", cap, (void *)o, o->magic, (void *)o->seg, o->seg ? (size_t)(o->end - o->seg) : 0,
|
||||
used, need);
|
||||
o->seg = b;
|
||||
o->cur = b + used;
|
||||
o->end = b + cap;
|
||||
@@ -312,6 +317,12 @@ static void release_copies(uintptr_t lo, uintptr_t hi) {
|
||||
// at ~0 / 0 so NVN 55 inline code never finds room and calls the API (nvn_obj.h cmd_t)
|
||||
NVN_API(nvnCommandBufferInitialize) {
|
||||
cmd_t *o = cmd_of(c);
|
||||
// Initialize over an object that was never finalized drops its recording buffer (seg lives in host memory). Logged
|
||||
// only: the old bytes may be a copy whose buffer another object owns, so freeing it here is not safe.
|
||||
static uint32_t reinits;
|
||||
if (o->magic == CMD_MAGIC && o->seg && __atomic_fetch_add(&reinits, 1, __ATOMIC_RELAXED) < 8)
|
||||
rt_log("nvn: Initialize over the command buffer %p, which was not finalized; its %zu-byte recording buffer %p "
|
||||
"is dropped", (void *)o, (size_t)(o->end - o->seg), (void *)o->seg);
|
||||
memset(o, 0, sizeof *o);
|
||||
o->magic = CMD_MAGIC;
|
||||
o->dev = arg_p(c, 1);
|
||||
|
||||
@@ -1094,6 +1094,12 @@ void plat_fs_sync_dir(const char *path) {
|
||||
}
|
||||
}
|
||||
|
||||
// Open files never stop a rename here.
|
||||
void plat_fs_busy_reason(const char *dir, char *out, size_t cap) {
|
||||
(void)dir;
|
||||
if (cap) *out = 0;
|
||||
}
|
||||
|
||||
int plat_fs_list(const char *dir, int (*cb)(const char *name, int kind, uint64_t size, void *ctx), void *ctx) {
|
||||
DIR *d = opendir(dir);
|
||||
if (!d) return fs_err(errno);
|
||||
@@ -1172,7 +1178,14 @@ size_t plat_getenv(const char *name, char *out, size_t cap) {
|
||||
return n;
|
||||
}
|
||||
|
||||
uint32_t plat_thread_id(void) { return (uint32_t)syscall(SYS_gettid); }
|
||||
// Cached per thread: plat_rmutex_lock asks on every acquire, hundreds of thousands of times a second in play, and
|
||||
// gettid is a full syscall on Linux (Windows reads GetCurrentThreadId from the TEB). A thread's id never changes, and
|
||||
// the runtime starts child processes with posix_spawn only, so no forked copy inherits a stale id.
|
||||
uint32_t plat_thread_id(void) {
|
||||
static __thread uint32_t tid;
|
||||
if (!tid) tid = (uint32_t)syscall(SYS_gettid);
|
||||
return tid;
|
||||
}
|
||||
|
||||
// The first set variable of names ("ru_RU.UTF-8") as a BCP-47 tag ("ru-RU") into tag; "" for C and POSIX.
|
||||
static void locale_tag(const char *const *names, int n, char *tag, size_t cap) {
|
||||
|
||||
@@ -203,13 +203,17 @@ _Noreturn void plat_reexec(void);
|
||||
|
||||
// A crash in this executable's own code (an access violation, an illegal instruction, a division by zero, a stack
|
||||
// overflow) goes to fn, which reports it and must end the process. Faults in other modules (drivers) are left to the
|
||||
// OS. The write-fault hook above runs first.
|
||||
// OS. The write-fault hook above runs first. On Windows abort() (a failed assert, std::terminate) and an invalid
|
||||
// parameter to a C runtime function go there too, with the host frames in this executable: the C runtime ends the
|
||||
// process for both with 0xc0000409 and no exception.
|
||||
typedef struct {
|
||||
const char *what; // "access violation", "illegal instruction", ...
|
||||
uint32_t code; // the Windows exception code or the signal number
|
||||
uintptr_t rip, offset; // the faulting instruction, and its offset in the executable
|
||||
int access; // the data access that faulted: 0 read, 1 write, 8 execute; -1 when not an access
|
||||
uint64_t addr; // its address when access >= 0
|
||||
int nframes; // abort and invalid parameter: return addresses in this executable, innermost first
|
||||
uintptr_t frames[16]; // (rip is frames[0]); 0 for a fault, whose rip is exact
|
||||
} PlatCrash;
|
||||
void plat_crash_hook(void (*fn)(const PlatCrash *c));
|
||||
int plat_mem_read(uint64_t addr, void *out, size_t n); // copies n bytes, 0 instead of faulting on unmapped memory
|
||||
@@ -303,6 +307,10 @@ int plat_fs_rename(const char *from, const char *to, int durable);
|
||||
int plat_fs_link(const char *existing, const char *path); // a hard link
|
||||
int plat_fs_links(const char *path); // the file's hard link count, 0 when unknown
|
||||
void plat_fs_sync_dir(const char *path); // a directory's entries to the disk, as far as the FS allows
|
||||
// Why dir could not be renamed, for the log: into out "no permission to rename it" when this process may not delete
|
||||
// it, else the programs holding a file under it ("explorer.exe 1234, ..."), else an empty string. Windows refuses to
|
||||
// rename a directory with any file open under it; elsewhere out is always empty.
|
||||
void plat_fs_busy_reason(const char *dir, char *out, size_t cap);
|
||||
// cb(name, kind, size, ctx) for each entry of dir but "." and ".."; cb returns 0 to stop. The listing's error.
|
||||
int plat_fs_list(const char *dir, int (*cb)(const char *name, int kind, uint64_t size, void *ctx), void *ctx);
|
||||
// An existing file for reading and, with write, writing; *err (may be NULL) gets the reason on failure.
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
#include <pdh.h>
|
||||
#include <pdhmsg.h>
|
||||
#include <psapi.h>
|
||||
#include <restartmanager.h>
|
||||
#include <shellapi.h>
|
||||
#include <timeapi.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
@@ -582,9 +584,37 @@ static LONG CALLBACK crash_veh(EXCEPTION_POINTERS *ep) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// abort() and an invalid parameter to a C runtime function raise no exception: the C runtime ends the process with
|
||||
// 0xc0000409 after calling the SIGABRT or invalid-parameter handler. The report names the host frames in this
|
||||
// executable; the innermost is the caller of abort (assert) or of the C runtime function. Skipped frames: this function
|
||||
// and the handler that called it.
|
||||
__attribute__((noinline)) static void crash_from_runtime(const char *what, uint32_t code) {
|
||||
PlatCrash c = {what, code, 0, 0, -1, 0};
|
||||
void *f[62];
|
||||
USHORT n = RtlCaptureStackBackTrace(2, 62, f, NULL);
|
||||
MODULEINFO mi;
|
||||
if (GetModuleInformation(GetCurrentProcess(), GetModuleHandleW(NULL), &mi, sizeof mi)) {
|
||||
uintptr_t base = (uintptr_t)mi.lpBaseOfDll;
|
||||
for (USHORT i = 0; i < n && c.nframes < (int)(sizeof c.frames / sizeof *c.frames); i++)
|
||||
if ((uintptr_t)f[i] - base < mi.SizeOfImage) c.frames[c.nframes++] = (uintptr_t)f[i];
|
||||
if (c.nframes) c.rip = c.frames[0], c.offset = c.rip - base;
|
||||
}
|
||||
crash_fn(&c);
|
||||
}
|
||||
|
||||
static void abort_signal(int sig) { crash_from_runtime("abort", (uint32_t)sig); }
|
||||
|
||||
static void invalid_parameter(const wchar_t *expr, const wchar_t *func, const wchar_t *file, unsigned line,
|
||||
uintptr_t reserved) {
|
||||
(void)expr, (void)func, (void)file, (void)line, (void)reserved; // NULL and 0 in the release C runtime
|
||||
crash_from_runtime("invalid parameter to a C runtime function", 0xc0000417u); // STATUS_INVALID_CRUNTIME_PARAMETER
|
||||
}
|
||||
|
||||
void plat_crash_hook(void (*fn)(const PlatCrash *c)) {
|
||||
crash_fn = fn;
|
||||
AddVectoredExceptionHandler(0, crash_veh);
|
||||
signal(SIGABRT, abort_signal);
|
||||
_set_invalid_parameter_handler(invalid_parameter);
|
||||
}
|
||||
|
||||
// A fresh process gets a fresh layout: Windows places DLLs, heaps and thread stacks at random addresses. The parent
|
||||
@@ -846,6 +876,94 @@ void plat_fs_sync_dir(const char *path) {
|
||||
}
|
||||
}
|
||||
|
||||
// Restart Manager (rstrtmgr.dll, loaded on first use) names the processes that hold a file open.
|
||||
typedef DWORD(WINAPI *RmStartSession_t)(DWORD *, DWORD, WCHAR *);
|
||||
typedef DWORD(WINAPI *RmRegisterResources_t)(DWORD, UINT, LPCWSTR *, UINT, RM_UNIQUE_PROCESS *, UINT, LPCWSTR *);
|
||||
typedef DWORD(WINAPI *RmGetList_t)(DWORD, UINT *, UINT *, RM_PROCESS_INFO *, LPDWORD);
|
||||
typedef DWORD(WINAPI *RmEndSession_t)(DWORD);
|
||||
static struct {
|
||||
INIT_ONCE once;
|
||||
RmStartSession_t start;
|
||||
RmRegisterResources_t add;
|
||||
RmGetList_t list;
|
||||
RmEndSession_t end;
|
||||
} rm = {INIT_ONCE_STATIC_INIT};
|
||||
|
||||
static BOOL CALLBACK rm_load(INIT_ONCE *o, void *p, void **c) {
|
||||
(void)o, (void)p, (void)c;
|
||||
HMODULE l = plat_lib_open("rstrtmgr.dll");
|
||||
if (!l) return TRUE;
|
||||
rm.add = (RmRegisterResources_t)(void *)GetProcAddress(l, "RmRegisterResources");
|
||||
rm.list = (RmGetList_t)(void *)GetProcAddress(l, "RmGetList");
|
||||
rm.end = (RmEndSession_t)(void *)GetProcAddress(l, "RmEndSession");
|
||||
RmStartSession_t start = (RmStartSession_t)(void *)GetProcAddress(l, "RmStartSession");
|
||||
if (rm.add && rm.list && rm.end) rm.start = start; // start set: all of them are there
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// The files under dir (links not followed), at most BUSY_FILES of them, each a malloc'd path.
|
||||
#define BUSY_FILES 256
|
||||
typedef struct { LPCWSTR p[BUSY_FILES]; UINT n; } BusyFiles;
|
||||
|
||||
static void busy_files(const wchar_t *dir, BusyFiles *f) {
|
||||
wchar_t pat[WPATH + 4];
|
||||
swprintf(pat, WPATH + 4, L"%ls\\*", dir);
|
||||
WIN32_FIND_DATAW fd;
|
||||
HANDLE h = FindFirstFileW(pat, &fd);
|
||||
if (h == INVALID_HANDLE_VALUE) return;
|
||||
do {
|
||||
DWORD a = fd.dwFileAttributes;
|
||||
if (!wcscmp(fd.cFileName, L".") || !wcscmp(fd.cFileName, L"..") || (a & FILE_ATTRIBUTE_REPARSE_POINT)) continue;
|
||||
size_t n = wcslen(dir) + wcslen(fd.cFileName) + 2;
|
||||
wchar_t *p = n <= WPATH ? malloc(n * sizeof *p) : NULL;
|
||||
if (!p) continue;
|
||||
swprintf(p, n, L"%ls\\%ls", dir, fd.cFileName);
|
||||
if (a & FILE_ATTRIBUTE_DIRECTORY) busy_files(p, f), free(p);
|
||||
else f->p[f->n++] = p;
|
||||
} while (f->n < BUSY_FILES && FindNextFileW(h, &fd));
|
||||
FindClose(h);
|
||||
}
|
||||
|
||||
void plat_fs_busy_reason(const char *dir, char *out, size_t cap) {
|
||||
if (!cap) return;
|
||||
*out = 0;
|
||||
wchar_t w[WPATH];
|
||||
if (!widen(dir, w, WPATH)) return;
|
||||
for (wchar_t *q = w; *q; q++)
|
||||
if (*q == L'/') *q = L'\\';
|
||||
// a rename needs DELETE access to the directory: a folder made by an elevated run can refuse it
|
||||
HANDLE h = CreateFileW(w, DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
if (GetLastError() == ERROR_ACCESS_DENIED) snprintf(out, cap, "no permission to rename it");
|
||||
return;
|
||||
}
|
||||
CloseHandle(h);
|
||||
InitOnceExecuteOnce(&rm.once, rm_load, NULL, NULL);
|
||||
DWORD session;
|
||||
WCHAR key[CCH_RM_SESSION_KEY + 1] = {0};
|
||||
if (!rm.start || rm.start(&session, 0, key) != ERROR_SUCCESS) return;
|
||||
BusyFiles f = {.n = 0};
|
||||
busy_files(w, &f);
|
||||
RM_PROCESS_INFO info[8];
|
||||
UINT need = 0, n = 8;
|
||||
DWORD reasons = 0, e = f.n ? rm.add(session, f.n, f.p, 0, NULL, 0, NULL) : ERROR_SUCCESS;
|
||||
if (f.n && e == ERROR_SUCCESS) e = rm.list(session, &need, &n, info, &reasons);
|
||||
else n = 0;
|
||||
rm.end(session);
|
||||
for (UINT i = 0; i < f.n; i++) free((void *)f.p[i]);
|
||||
if (e == ERROR_MORE_DATA) snprintf(out, cap, "%u programs hold files in it", need); // info is not filled then
|
||||
if (e != ERROR_SUCCESS) return;
|
||||
size_t at = 0;
|
||||
for (UINT i = 0; i < n && at < cap; i++) { // the program's display name ("Windows Explorer") and process id
|
||||
char name[CCH_RM_MAX_APP_NAME + 1];
|
||||
if (!WideCharToMultiByte(CP_UTF8, 0, info[i].strAppName, -1, name, sizeof name, NULL, NULL)) strcpy(name, "?");
|
||||
int k = snprintf(out + at, cap - at, "%s%s %lu", i ? ", " : "", name, info[i].Process.dwProcessId);
|
||||
if (k < 0) break;
|
||||
at += (size_t)k;
|
||||
}
|
||||
}
|
||||
|
||||
int plat_fs_list(const char *dir, int (*cb)(const char *name, int kind, uint64_t size, void *ctx), void *ctx) {
|
||||
wchar_t wd[WPATH], pat[WPATH + 4];
|
||||
if (!widen(dir, wd, WPATH)) return PLAT_FS_ERROR;
|
||||
|
||||
+25
-1
@@ -12,7 +12,8 @@
|
||||
//
|
||||
// Patch page: +16 the scene size {w, h} as floats, +32 the output size and +48 the swapchain size as {w, h} in one
|
||||
// 64-bit word, +40 the renderer (1.2.1) or display configuration (1.4.3) the patched code stores there.
|
||||
// +56 the camera's aspect ratio as a float when the output is not 16:9 (set aspect).
|
||||
// +56 the camera's aspect ratio as a float when the output is not 16:9 (set aspect), +60 the field-of-view factor
|
||||
// (set fov, res_fov_live).
|
||||
// 1.4.3 scales the scene it reads by output width / 1920 (a 2560x1440 output renders its 1600x900 scene at 2128x1200;
|
||||
// MacNativeGames measured the same), so its page holds the wanted size divided by that factor and the game's 5/6 needs
|
||||
// no scene patch. 1.2.1 renders the size it reads (1600x900 on a 2560x1440 output): its page holds the wanted size, and
|
||||
@@ -253,6 +254,29 @@ int res_wants_wide_overlay(const unsigned out[2]) {
|
||||
return !res_is_16x9(out[0], out[1]) && rt_patch_set("aspect") >= 0;
|
||||
}
|
||||
|
||||
// Field of view (TOTK_FOV, percent of the game's own, 70..140, live): the camera's vertical view angle times the
|
||||
// factor on page +60 (recomp/patches-v143.txt, set fov). The horizontal angle follows through the aspect ratio. 100
|
||||
// switches the set off, so the camera code is the game's own. Runs on a settings change (gfx_present.c
|
||||
// window_settings_live), the first time at the first present.
|
||||
#define FOV_MIN 70
|
||||
#define FOV_MAX 140
|
||||
void res_fov_live(void) {
|
||||
static int set = -2, shown = 100;
|
||||
if (set == -2) set = rt_patch_set("fov");
|
||||
int pct = cfg_int("TOTK_FOV");
|
||||
pct = pct < FOV_MIN ? FOV_MIN : pct > FOV_MAX ? FOV_MAX : pct;
|
||||
if (pct == shown) return;
|
||||
shown = pct;
|
||||
if (set < 0) {
|
||||
rt_log("resolution: this game version has no field-of-view patch; TOTK_FOV=%d does nothing", pct);
|
||||
return;
|
||||
}
|
||||
float k = pct / 100.0f;
|
||||
memcpy((uint8_t *)rt_patch_page() + 60, &k, sizeof k); // before the switch: a site that sees it on reads k
|
||||
rt_patch_switch(set, pct != 100);
|
||||
rt_log("resolution: field of view %d%% of the game's", pct);
|
||||
}
|
||||
|
||||
void res_wanted(unsigned out[2]) {
|
||||
decide();
|
||||
out[0] = R.ow, out[1] = R.oh;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#version 450
|
||||
// Colour adjustment on the present (runtime/vk_color.c): the game's picture, crop rectangle at offset, into an RGBA16F
|
||||
// image the FSR 1 pass or the blit then scales to the window. Brightness, contrast around mid-grey, saturation against
|
||||
// Rec. 709 luma, then a power curve: the order and ranges of STS78721's colour profiles (issue #136).
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
layout(binding = 0) uniform sampler2D src;
|
||||
layout(binding = 1, rgba16f) uniform writeonly image2D dst;
|
||||
layout(push_constant) uniform P {
|
||||
ivec2 offset; // the crop rectangle's corner in src
|
||||
ivec2 size; // its size, the size of dst
|
||||
vec4 adj; // brightness (added), contrast, saturation, gamma (exponent)
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
ivec2 p = ivec2(gl_GlobalInvocationID.xy);
|
||||
if (any(greaterThanEqual(p, pc.size))) return;
|
||||
vec3 c = texelFetch(src, p + pc.offset, 0).rgb;
|
||||
c += pc.adj.x;
|
||||
c = (c - 0.5) * pc.adj.y + 0.5;
|
||||
c = mix(vec3(dot(c, vec3(0.2126, 0.7152, 0.0722))), c, pc.adj.z);
|
||||
c = pow(max(c, vec3(0.0)), vec3(pc.adj.w));
|
||||
imageStore(dst, p, vec4(clamp(c, 0.0, 1.0), 1.0));
|
||||
}
|
||||
+86
-1
@@ -11,6 +11,7 @@
|
||||
// 575ad88e02f9a89a9a885d90039d48af33c982f4, MIT, Copyright (c) 2026 Revivedx and the Savage Wind Recomp contributors,
|
||||
// Copyright (c) 2026 Peiikai); the pages are rows of option_rows.cpp.
|
||||
#include <atomic>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -19,6 +20,16 @@
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h" // SetFocusID, NavCursorVisible
|
||||
#include "backends/imgui_impl_vulkan.h"
|
||||
// A private copy of stb_truetype, the parser Dear ImGui builds its font atlas with, to check a system font before the
|
||||
// atlas gets it (font_usable). Its asserts only mark the font bad here.
|
||||
static int g_font_bad;
|
||||
#define STBTT_STATIC
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#define STBTT_assert(x) ((x) ? (void)0 : (void)(g_font_bad = 1))
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-function"
|
||||
#include "imstb_truetype.h"
|
||||
#pragma clang diagnostic pop
|
||||
#include "ui/binds_page.h"
|
||||
#include "ui/option_rows.h"
|
||||
|
||||
@@ -262,6 +273,43 @@ static const struct { const char *lang; UiTextId name; } kCjk[] = {
|
||||
{"zh-Hant", UI_MENU_LANGNAME_ZH_HANT},
|
||||
};
|
||||
|
||||
static uint32_t be32(const uint8_t *p) { return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | p[3]; }
|
||||
|
||||
// 1 when Dear ImGui can take this font file: more than the 100 bytes AddFontFromMemoryTTF asserts on, every table of its
|
||||
// directory inside the file (stb_truetype reads tables without bounds checks, so a cut-off file faults when the glyphs
|
||||
// are made), and stb_truetype parses it. ImGui asserts on a font it cannot parse, and an assert ends the process with
|
||||
// 0xc0000409 and no line in the log.
|
||||
static int font_usable(const uint8_t *d, uint64_t n) {
|
||||
if (!d || n <= 100 || n > INT_MAX) return 0;
|
||||
int off = stbtt_GetFontOffsetForIndex(d, 0);
|
||||
if (off < 0 || (uint64_t)off + 12 > n) return 0;
|
||||
uint32_t tables = (uint32_t)d[off + 4] << 8 | d[off + 5];
|
||||
if ((uint64_t)off + 12 + 16ull * tables > n) return 0;
|
||||
for (uint32_t i = 0; i < tables; i++) {
|
||||
const uint8_t *r = d + off + 12 + 16 * i;
|
||||
if ((uint64_t)be32(r + 8) + be32(r + 12) > n) return 0;
|
||||
}
|
||||
stbtt_fontinfo fi;
|
||||
g_font_bad = 0;
|
||||
return stbtt_InitFont(&fi, d, off) && !g_font_bad;
|
||||
}
|
||||
|
||||
extern "C" int menu_font_check(void) {
|
||||
uint64_t n = sizeof roboto_ttf;
|
||||
uint8_t *bad = (uint8_t *)malloc(n);
|
||||
if (!bad) return 0;
|
||||
memcpy(bad, roboto_ttf, n);
|
||||
uint32_t tables = (uint32_t)bad[4] << 8 | bad[5];
|
||||
for (uint32_t i = 0; i < tables; i++)
|
||||
if (!memcmp(bad + 12 + 16 * i, "cmap", 4)) memset(bad + 12 + 16 * i, 0, 4); // no character map
|
||||
int ok = font_usable(roboto_ttf, n) && !font_usable(roboto_ttf, 4096) && !font_usable(roboto_ttf, 100) &&
|
||||
!font_usable(bad, n);
|
||||
memset(bad, 0, 4); // an unknown font tag
|
||||
ok = ok && !font_usable(bad, n);
|
||||
free(bad);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int fonts_up(float scale) {
|
||||
enum { N = sizeof kCjk / sizeof *kCjk };
|
||||
ImFontAtlas *fa = ImGui::GetIO().Fonts;
|
||||
@@ -276,6 +324,13 @@ static int fonts_up(float scale) {
|
||||
char path[512];
|
||||
uint64_t size = 0;
|
||||
if (!plat_font_path(kCjk[i].lang, path, sizeof path) || !(data[i] = plat_file_read(path, &size))) continue;
|
||||
if (!font_usable(data[i], size)) {
|
||||
rt_log("menu: system font %s left out: the font parser cannot read it (%llu bytes)", path,
|
||||
(unsigned long long)size);
|
||||
free(data[i]);
|
||||
data[i] = NULL;
|
||||
continue;
|
||||
}
|
||||
merged++;
|
||||
ImFontGlyphRangesBuilder b;
|
||||
b.AddText(ui_text_in("en", kCjk[i].name));
|
||||
@@ -527,6 +582,35 @@ static const char *present_note(void) {
|
||||
}
|
||||
|
||||
// ---- the tabs
|
||||
// Colour adjustment (vk_color.c): a profile writes the four values (STS78721's presets, issue #136); moving one of them
|
||||
// makes the profile Custom.
|
||||
static void rows_color(void) {
|
||||
static const struct { const char *name, *b, *c, *s, *g; } presets[] = {
|
||||
{"default", "0", "100", "100", "1"}, {"vivid", "0", "115", "135", "0.95"}, {"clarity", "2", "110", "115", "1"}};
|
||||
static const UiTextId names[] = {UI_MENU_COLOR_DEFAULT, UI_MENU_COLOR_VIVID, UI_MENU_COLOR_CLARITY,
|
||||
UI_MENU_COLOR_CUSTOM};
|
||||
opt_group(UI_MENU_COLOR);
|
||||
if (opt_choice("TOTK_COLOR_PRESET", UI_MENU_COLOR_PRESET, names, 4, UI_MENU_COLOR_PRESET_DESC)) {
|
||||
const char *p = cfg_value("TOTK_COLOR_PRESET");
|
||||
for (const auto &e : presets)
|
||||
if (!strcmp(p, e.name)) {
|
||||
cfg_set("TOTK_COLOR_BRIGHTNESS", e.b), cfg_set("TOTK_COLOR_CONTRAST", e.c);
|
||||
cfg_set("TOTK_COLOR_SATURATION", e.s), cfg_set("TOTK_COLOR_GAMMA", e.g);
|
||||
cfg_save();
|
||||
}
|
||||
}
|
||||
bool moved = opt_slider("TOTK_COLOR_BRIGHTNESS", UI_MENU_COLOR_BRIGHTNESS, -30, 30, UI_MENU_COLOR_BRIGHTNESS_DESC, 1,
|
||||
"%+.0f %%");
|
||||
moved |= opt_slider("TOTK_COLOR_CONTRAST", UI_MENU_COLOR_CONTRAST, 70, 150, UI_MENU_COLOR_CONTRAST_DESC, 5, "%.0f %%");
|
||||
moved |= opt_slider("TOTK_COLOR_SATURATION", UI_MENU_COLOR_SATURATION, 50, 200, UI_MENU_COLOR_SATURATION_DESC, 5,
|
||||
"%.0f %%");
|
||||
moved |= opt_slider("TOTK_COLOR_GAMMA", UI_MENU_COLOR_GAMMA, 0.8f, 1.2f, UI_MENU_COLOR_GAMMA_DESC, 0.02f, "%.2f");
|
||||
if (moved && strcmp(cfg_value("TOTK_COLOR_PRESET"), "custom")) {
|
||||
cfg_set("TOTK_COLOR_PRESET", "custom");
|
||||
cfg_save();
|
||||
}
|
||||
}
|
||||
|
||||
static void tab_screen(float scale) {
|
||||
opt_begin("##screen", scale);
|
||||
opt_toggle("TOTK_FULLSCREEN", UI_MENU_FULLSCREEN, UI_MENU_FULLSCREEN_DESC);
|
||||
@@ -544,6 +628,8 @@ static void tab_screen(float scale) {
|
||||
opt_slider("TOTK_SHARPNESS", UI_MENU_SHARPNESS, 0, 100, UI_MENU_SHARPNESS_DESC, 5, "%.0f %%");
|
||||
static const UiTextId aspect[] = {UI_MENU_ASPECT_FIT, UI_MENU_ASPECT_STRETCH};
|
||||
opt_choice("TOTK_ASPECT", UI_MENU_ASPECT, aspect, 2, UI_MENU_ASPECT_DESC);
|
||||
opt_slider("TOTK_FOV", UI_MENU_FOV, 70, 140, UI_MENU_FOV_DESC, 5, "%.0f %%");
|
||||
rows_color();
|
||||
opt_end();
|
||||
}
|
||||
|
||||
@@ -667,7 +753,6 @@ static void tab_advanced(float scale) {
|
||||
static const UiTextId preempt[] = {UI_MENU_PREEMPT_OFF, UI_MENU_PREEMPT_ON, UI_MENU_PREEMPT_INHERIT}; // 0|1|2
|
||||
opt_choice("TOTK_ASYNC_PREEMPT", UI_MENU_ASYNC_PREEMPT, preempt, 3, UI_MENU_ASYNC_PREEMPT_DESC);
|
||||
opt_toggle("TOTK_REPLAY_ASYNC", UI_MENU_REPLAY_ASYNC, UI_MENU_REPLAY_ASYNC_DESC);
|
||||
opt_toggle("TOTK_FREE_CORES", UI_MENU_FREE_CORES, UI_MENU_FREE_CORES_DESC);
|
||||
opt_toggle("TOTK_TIMER_1MS", UI_MENU_TIMER_1MS, UI_MENU_TIMER_1MS_DESC);
|
||||
|
||||
opt_group(UI_MENU_GROUP_SHADERS);
|
||||
|
||||
@@ -53,6 +53,9 @@ int menu_overlay_frame(uint64_t frame, const MenuTarget *t, const char **shot);
|
||||
// Records the frame menu_overlay_frame laid out into cb, which is inside dynamic rendering on the image.
|
||||
void menu_overlay_draw(VkCommandBuffer cb);
|
||||
void menu_vk_release(void); // before the device goes away (gfx_window_release_vk)
|
||||
// The check the font atlas runs on a system font, on the embedded font and damaged copies of it: 1 when it takes the
|
||||
// whole font and refuses a cut-off one, one without a character map and one with an unknown tag (--probe-window).
|
||||
int menu_font_check(void);
|
||||
|
||||
// ---- what the menu asks of the platform side (gfx_window_win.c, gfx_window_sdl.c)
|
||||
void gfx_window_apply_settings(void); // TOTK_FULLSCREEN or TOTK_WINDOW_SIZE changed: the window follows
|
||||
|
||||
+35
-4
@@ -67,8 +67,25 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_ASPECT_DESC] = "When the window's shape differs from the game resolution's (a 16:9 picture on a 21:9 "
|
||||
"screen): keep the picture's shape with black bars, or stretch it over the window.",
|
||||
[UI_MENU_ASPECT_FIT] = "Black bars", [UI_MENU_ASPECT_STRETCH] = "Stretch",
|
||||
[UI_MENU_FOV] = "Field of view",
|
||||
[UI_MENU_FOV_DESC] = "How much of the world the camera shows, in percent of the game's own. Game version 1.4.3 only.",
|
||||
[UI_MENU_SHARPNESS] = "FSR sharpness",
|
||||
[UI_MENU_SHARPNESS_DESC] = "Sharpening after FSR scaling; 0 turns it off.",
|
||||
[UI_MENU_COLOR] = "Colour",
|
||||
[UI_MENU_COLOR_PRESET] = "Colour profile",
|
||||
[UI_MENU_COLOR_PRESET_DESC] = "Sets the four values below. Vivid: more contrast and saturation, shadows opened a "
|
||||
"little (made for OLED screens). Clarity: a little more contrast, saturation and "
|
||||
"brightness. Moving a value makes the profile Custom. The HUD changes with the picture; "
|
||||
"this menu does not.",
|
||||
[UI_MENU_COLOR_DEFAULT] = "Game's own", [UI_MENU_COLOR_VIVID] = "Vivid (OLED)", [UI_MENU_COLOR_CLARITY] = "Clarity",
|
||||
[UI_MENU_COLOR_CUSTOM] = "Custom",
|
||||
[UI_MENU_COLOR_BRIGHTNESS] = "Brightness", [UI_MENU_COLOR_BRIGHTNESS_DESC] = "Added to every colour; 0 leaves it.",
|
||||
[UI_MENU_COLOR_CONTRAST] = "Contrast",
|
||||
[UI_MENU_COLOR_CONTRAST_DESC] = "Spreads colours away from mid-grey (above 100 %) or towards it; 100 % leaves them.",
|
||||
[UI_MENU_COLOR_SATURATION] = "Saturation",
|
||||
[UI_MENU_COLOR_SATURATION_DESC] = "Colour strength against grey; 100 % leaves it.",
|
||||
[UI_MENU_COLOR_GAMMA] = "Gamma",
|
||||
[UI_MENU_COLOR_GAMMA_DESC] = "Below 1 brightens dark and middle tones, above 1 darkens them; 1 leaves them.",
|
||||
|
||||
[UI_MENU_FPS_LIMIT] = "Frame rate limit",
|
||||
[UI_MENU_FPS_LIMIT_DESC] = "The most frames per second the game draws. Above 60 is experimental and needs Game speed "
|
||||
@@ -178,8 +195,6 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_PREEMPT_OFF] = "Off", [UI_MENU_PREEMPT_ON] = "On", [UI_MENU_PREEMPT_INHERIT] = "Inheritance only",
|
||||
[UI_MENU_REPLAY_ASYNC] = "GPU frame prep on its own thread",
|
||||
[UI_MENU_REPLAY_ASYNC_DESC] = "Prepares frames for the GPU on a separate thread instead of the game thread. Adds up to one frame of latency.",
|
||||
[UI_MENU_FREE_CORES] = "Virtual core gate",
|
||||
[UI_MENU_FREE_CORES_DESC] = "Lets only one thread run per virtual core, as on the console.",
|
||||
[UI_MENU_TIMER_1MS] = "1 ms Windows timer",
|
||||
[UI_MENU_TIMER_1MS_DESC] = "Raises the system timer resolution to 1 ms for steadier frame pacing.",
|
||||
[UI_MENU_PIPE_WAIT] = "Waiting draw handling",
|
||||
@@ -312,8 +327,26 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_ASPECT_DESC] = "Когда форма окна отличается от разрешения игры (картинка 16:9 на экране 21:9): сохранить "
|
||||
"форму картинки с чёрными полосами или растянуть её на всё окно.",
|
||||
[UI_MENU_ASPECT_FIT] = "Чёрные полосы", [UI_MENU_ASPECT_STRETCH] = "Растянуть",
|
||||
[UI_MENU_FOV] = "Поле зрения",
|
||||
[UI_MENU_FOV_DESC] = "Сколько мира показывает камера, в процентах от игрового. Только для версии игры 1.4.3.",
|
||||
[UI_MENU_SHARPNESS] = "Резкость FSR",
|
||||
[UI_MENU_SHARPNESS_DESC] = "Повышение резкости после масштабирования FSR; 0 выключает его.",
|
||||
[UI_MENU_COLOR] = "Цвет",
|
||||
[UI_MENU_COLOR_PRESET] = "Цветовой профиль",
|
||||
[UI_MENU_COLOR_PRESET_DESC] = "Задаёт четыре значения ниже. «Яркий»: больше контраста и насыщенности, тени немного "
|
||||
"светлее (для OLED-экранов). «Чёткость»: немного больше контраста, насыщенности и "
|
||||
"яркости. Изменение любого значения делает профиль «Своим». Интерфейс игры меняется "
|
||||
"вместе с картинкой, это меню — нет.",
|
||||
[UI_MENU_COLOR_DEFAULT] = "Как в игре", [UI_MENU_COLOR_VIVID] = "Яркий (OLED)", [UI_MENU_COLOR_CLARITY] = "Чёткость",
|
||||
[UI_MENU_COLOR_CUSTOM] = "Свой",
|
||||
[UI_MENU_COLOR_BRIGHTNESS] = "Яркость", [UI_MENU_COLOR_BRIGHTNESS_DESC] = "Прибавляется к каждому цвету; 0 — без изменений.",
|
||||
[UI_MENU_COLOR_CONTRAST] = "Контраст",
|
||||
[UI_MENU_COLOR_CONTRAST_DESC] = "Отдаляет цвета от среднего серого (выше 100 %) или приближает к нему; 100 % — без "
|
||||
"изменений.",
|
||||
[UI_MENU_COLOR_SATURATION] = "Насыщенность",
|
||||
[UI_MENU_COLOR_SATURATION_DESC] = "Сила цвета относительно серого; 100 % — без изменений.",
|
||||
[UI_MENU_COLOR_GAMMA] = "Гамма",
|
||||
[UI_MENU_COLOR_GAMMA_DESC] = "Ниже 1 осветляет тёмные и средние тона, выше 1 затемняет их; 1 — без изменений.",
|
||||
|
||||
[UI_MENU_FPS_LIMIT] = "Ограничение частоты кадров",
|
||||
[UI_MENU_FPS_LIMIT_DESC] = "Наибольшее число кадров в секунду. Выше 60 — экспериментальный режим, нужна «Скорость "
|
||||
@@ -427,8 +460,6 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_PREEMPT_OFF] = "Выкл.", [UI_MENU_PREEMPT_ON] = "Вкл.", [UI_MENU_PREEMPT_INHERIT] = "Только наследование",
|
||||
[UI_MENU_REPLAY_ASYNC] = "Подготовка кадра для GPU в отдельном потоке",
|
||||
[UI_MENU_REPLAY_ASYNC_DESC] = "Кадр для GPU готовится в отдельном потоке, а не в потоке игры. Добавляет до одного кадра задержки.",
|
||||
[UI_MENU_FREE_CORES] = "Ограничение виртуальных ядер",
|
||||
[UI_MENU_FREE_CORES_DESC] = "На каждом виртуальном ядре работает один поток, как на консоли.",
|
||||
[UI_MENU_TIMER_1MS] = "Таймер Windows 1 мс",
|
||||
[UI_MENU_TIMER_1MS_DESC] = "Повышает точность системного таймера до 1 мс для ровного темпа кадров.",
|
||||
[UI_MENU_PIPE_WAIT] = "Обработка ожидающих отрисовок",
|
||||
|
||||
+6
-1
@@ -36,7 +36,13 @@ typedef enum {
|
||||
UI_MENU_RENDER_SCALE, UI_MENU_RENDER_SCALE_DESC, UI_MENU_RENDER_SCALE_GAME,
|
||||
UI_MENU_UPSCALER, UI_MENU_UPSCALER_DESC, UI_MENU_UPSCALER_FSR, UI_MENU_UPSCALER_BILINEAR,
|
||||
UI_MENU_ASPECT, UI_MENU_ASPECT_DESC, UI_MENU_ASPECT_FIT, UI_MENU_ASPECT_STRETCH,
|
||||
UI_MENU_FOV, UI_MENU_FOV_DESC,
|
||||
UI_MENU_SHARPNESS, UI_MENU_SHARPNESS_DESC,
|
||||
UI_MENU_COLOR, // group heading over the colour rows
|
||||
UI_MENU_COLOR_PRESET, UI_MENU_COLOR_PRESET_DESC, UI_MENU_COLOR_DEFAULT, UI_MENU_COLOR_VIVID, UI_MENU_COLOR_CLARITY,
|
||||
UI_MENU_COLOR_CUSTOM,
|
||||
UI_MENU_COLOR_BRIGHTNESS, UI_MENU_COLOR_BRIGHTNESS_DESC, UI_MENU_COLOR_CONTRAST, UI_MENU_COLOR_CONTRAST_DESC,
|
||||
UI_MENU_COLOR_SATURATION, UI_MENU_COLOR_SATURATION_DESC, UI_MENU_COLOR_GAMMA, UI_MENU_COLOR_GAMMA_DESC,
|
||||
|
||||
// Game
|
||||
UI_MENU_FPS_LIMIT, UI_MENU_FPS_LIMIT_DESC, UI_MENU_FPS_30, UI_MENU_FPS_40, UI_MENU_FPS_45, UI_MENU_FPS_60, UI_MENU_FPS_90,
|
||||
@@ -108,7 +114,6 @@ typedef enum {
|
||||
UI_MENU_ASYNC_PREEMPT, UI_MENU_ASYNC_PREEMPT_DESC, UI_MENU_PREEMPT_OFF, UI_MENU_PREEMPT_ON,
|
||||
UI_MENU_PREEMPT_INHERIT,
|
||||
UI_MENU_REPLAY_ASYNC, UI_MENU_REPLAY_ASYNC_DESC,
|
||||
UI_MENU_FREE_CORES, UI_MENU_FREE_CORES_DESC,
|
||||
UI_MENU_TIMER_1MS, UI_MENU_TIMER_1MS_DESC,
|
||||
UI_MENU_PIPE_WAIT, UI_MENU_PIPE_WAIT_DESC, UI_MENU_PIPE_WAIT_DEFAULT, UI_MENU_PIPE_WAIT_DRAW,
|
||||
UI_MENU_PIPE_SKIP, UI_MENU_PIPE_SKIP_DESC, UI_MENU_PIPE_SKIP_STRICT, UI_MENU_PIPE_SKIP_ANY,
|
||||
|
||||
@@ -102,6 +102,11 @@ void vk_present_blit(VkImage src, VkImageLayout layout, VkFormat fmt, int w, int
|
||||
int vk_fsr_record(VkCommandBuffer cb, unsigned slot, VkImage src, VkFormat fmt, int sw, int sh, const int crop[4],
|
||||
VkImage dst, const int out[4]);
|
||||
void vk_fsr_release(void); // before the device goes away
|
||||
// Colour adjustment on the present (vk_color.c): with TOTK_COLOR_* away from their defaults, records a pass that writes
|
||||
// src's crop rectangle {x, y, w, h}, adjusted, into an RGBA16F image of that size in GENERAL and returns it (the caller
|
||||
// scales that one instead, crop {0, 0, w, h}); VK_NULL_HANDLE = nothing recorded. src is in GENERAL, fmt its format.
|
||||
VkImage vk_color_record(VkCommandBuffer cb, unsigned slot, VkImage src, VkFormat fmt, const int crop[4]);
|
||||
void vk_color_release(void); // before the device goes away
|
||||
void vk_present_flip(void);
|
||||
// Every submission to ctx->gfx_queue (and xfer_queue when shared) takes this lock; vk_present_image and the swapchain
|
||||
// rebuild do. Recursive.
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// Colour adjustment on the present (vk_color.h): brightness, contrast, saturation and gamma of the game's picture, after
|
||||
// the game and before the FSR 1 pass or the blit scale it to the window. Settings TOTK_COLOR_BRIGHTNESS (-30..30, %
|
||||
// added), TOTK_COLOR_CONTRAST and TOTK_COLOR_SATURATION (%, 100 = unchanged) and TOTK_COLOR_GAMMA (exponent, 1 =
|
||||
// unchanged), read when the settings change; the menu's colour profiles only write these four. At the default values
|
||||
// nothing is recorded and the present is what it was. Design and preset values: STS78721's colour profiles (issue #136).
|
||||
//
|
||||
// One compute pass (shaders/color_grade.comp) reads the crop rectangle of the game's image and writes an RGBA16F image
|
||||
// of its size, which present_image then hands to FSR or the blit in place of the game's image. The HUD is part of that
|
||||
// picture and is adjusted with it; the in-game menu is drawn later, straight into the swapchain image, and is not.
|
||||
// The source view of slot s is destroyed when slot s records again (present_image has waited for that slot's fence);
|
||||
// the output image is shared by all slots, and each frame's first barrier orders it after the previous frame's reads.
|
||||
#include "vk.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "cfg.h"
|
||||
#include "color_grade_spv.h"
|
||||
#include "host.h"
|
||||
|
||||
#define SLOTS 8
|
||||
|
||||
static struct {
|
||||
int tried, ok;
|
||||
PFN_vkCmdPushDescriptorSetKHR push;
|
||||
VkDescriptorSetLayout dsl;
|
||||
VkPipelineLayout layout;
|
||||
VkPipeline pipe;
|
||||
VkSampler sampler;
|
||||
VkImage img; // the adjusted picture, RGBA16F at the crop size
|
||||
VmaAllocation alloc;
|
||||
VkImageView view;
|
||||
uint32_t w, h;
|
||||
VkImageView src_view[SLOTS]; // the source view each slot's last frame read
|
||||
unsigned seen; // cfg_version the values below were read at
|
||||
float adj[4]; // brightness (added), contrast, saturation, gamma
|
||||
} C = {.seen = ~0u};
|
||||
|
||||
static int init(VkCtx *c) {
|
||||
C.tried = 1;
|
||||
C.push = (PFN_vkCmdPushDescriptorSetKHR)vkGetDeviceProcAddr(c->device, "vkCmdPushDescriptorSetKHR");
|
||||
VkDescriptorSetLayoutBinding b[2] = {
|
||||
{.binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT},
|
||||
{.binding = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 1,
|
||||
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT}};
|
||||
VkDescriptorSetLayoutCreateInfo dci = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR,
|
||||
.bindingCount = 2, .pBindings = b};
|
||||
VkPushConstantRange pr = {VK_SHADER_STAGE_COMPUTE_BIT, 0, 32};
|
||||
VkPipelineLayoutCreateInfo li = {.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1,
|
||||
.pSetLayouts = &C.dsl, .pushConstantRangeCount = 1, .pPushConstantRanges = &pr};
|
||||
VkSamplerCreateInfo si = {.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_NEAREST,
|
||||
.minFilter = VK_FILTER_NEAREST, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST,
|
||||
.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
||||
.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .maxLod = 0};
|
||||
VkShaderModuleCreateInfo mi = {.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = sizeof color_grade_spv,
|
||||
.pCode = color_grade_spv};
|
||||
VkShaderModule m = VK_NULL_HANDLE;
|
||||
if (!C.push || vkCreateDescriptorSetLayout(c->device, &dci, NULL, &C.dsl) != VK_SUCCESS ||
|
||||
vkCreatePipelineLayout(c->device, &li, NULL, &C.layout) != VK_SUCCESS ||
|
||||
vkCreateSampler(c->device, &si, NULL, &C.sampler) != VK_SUCCESS ||
|
||||
vkCreateShaderModule(c->device, &mi, NULL, &m) != VK_SUCCESS) {
|
||||
rt_log("vk: colour adjustment off: its pipeline could not be created");
|
||||
return 0;
|
||||
}
|
||||
VkComputePipelineCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
|
||||
.stage = {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.stage = VK_SHADER_STAGE_COMPUTE_BIT, .module = m, .pName = "main"},
|
||||
.layout = C.layout};
|
||||
VkResult r = vkCreateComputePipelines(c->device, VK_NULL_HANDLE, 1, &ci, NULL, &C.pipe);
|
||||
vkDestroyShaderModule(c->device, m, NULL);
|
||||
if (r != VK_SUCCESS) {
|
||||
rt_log("vk: colour adjustment off: its pipeline could not be created");
|
||||
return 0;
|
||||
}
|
||||
return C.ok = 1;
|
||||
}
|
||||
|
||||
static void free_image(VkCtx *c) {
|
||||
if (C.view) vkDestroyImageView(c->device, C.view, NULL);
|
||||
if (C.img) vmaDestroyImage(c->vma, C.img, C.alloc);
|
||||
C.view = VK_NULL_HANDLE, C.img = VK_NULL_HANDLE, C.w = C.h = 0;
|
||||
}
|
||||
|
||||
static int ensure_image(VkCtx *c, uint32_t w, uint32_t h) {
|
||||
if (C.img && C.w == w && C.h == h) return 1;
|
||||
// another crop size (a resolution change): earlier presents may still read the old image
|
||||
vk_queue_lock();
|
||||
vkDeviceWaitIdle(c->device);
|
||||
vk_queue_unlock();
|
||||
free_image(c);
|
||||
VkImageCreateInfo ii = {.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = VK_FORMAT_R16G16B16A16_SFLOAT, .extent = {w, h, 1}, .mipLevels = 1, .arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT};
|
||||
VmaAllocationCreateInfo ai = {.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE};
|
||||
if (vmaCreateImage(c->vma, &ii, &ai, &C.img, &C.alloc, NULL) != VK_SUCCESS) {
|
||||
C.img = VK_NULL_HANDLE;
|
||||
return 0;
|
||||
}
|
||||
VkImageViewCreateInfo vi = {.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = C.img,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D, .format = VK_FORMAT_R16G16B16A16_SFLOAT,
|
||||
.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
||||
if (vkCreateImageView(c->device, &vi, NULL, &C.view) != VK_SUCCESS) {
|
||||
C.view = VK_NULL_HANDLE;
|
||||
free_image(c);
|
||||
return 0;
|
||||
}
|
||||
C.w = w, C.h = h;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The four values from the settings, re-read when they change. 1 when any differs from "unchanged".
|
||||
static int adjusting(void) {
|
||||
unsigned v = cfg_version();
|
||||
if (v != C.seen) {
|
||||
C.seen = v;
|
||||
C.adj[0] = (float)cfg_double("TOTK_COLOR_BRIGHTNESS") / 100.0f;
|
||||
C.adj[1] = (float)cfg_double("TOTK_COLOR_CONTRAST") / 100.0f;
|
||||
C.adj[2] = (float)cfg_double("TOTK_COLOR_SATURATION") / 100.0f;
|
||||
C.adj[3] = (float)cfg_double("TOTK_COLOR_GAMMA");
|
||||
}
|
||||
return C.adj[0] != 0.0f || C.adj[1] != 1.0f || C.adj[2] != 1.0f || C.adj[3] != 1.0f;
|
||||
}
|
||||
|
||||
static void barrier(VkCommandBuffer cb, VkImage img, VkImageLayout from, VkPipelineStageFlags2 ss, VkAccessFlags2 sa,
|
||||
VkPipelineStageFlags2 ds, VkAccessFlags2 da) {
|
||||
VkImageMemoryBarrier2 b = {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, .srcStageMask = ss, .srcAccessMask = sa,
|
||||
.dstStageMask = ds, .dstAccessMask = da, .oldLayout = from,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = img,
|
||||
.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
||||
VkDependencyInfo d = {.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .imageMemoryBarrierCount = 1,
|
||||
.pImageMemoryBarriers = &b};
|
||||
vkCmdPipelineBarrier2(cb, &d);
|
||||
}
|
||||
|
||||
VkImage vk_color_record(VkCommandBuffer cb, unsigned slot, VkImage src, VkFormat fmt, const int crop[4]) {
|
||||
if (!adjusting()) return VK_NULL_HANDLE;
|
||||
VkCtx *c = vk_ctx();
|
||||
if (!C.tried) init(c);
|
||||
if (!C.ok || !ensure_image(c, (uint32_t)crop[2], (uint32_t)crop[3])) return VK_NULL_HANDLE;
|
||||
slot %= SLOTS;
|
||||
if (C.src_view[slot]) vkDestroyImageView(c->device, C.src_view[slot], NULL);
|
||||
VkImageViewCreateInfo vi = {.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = src,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D, .format = fmt,
|
||||
.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
||||
if (vkCreateImageView(c->device, &vi, NULL, &C.src_view[slot]) != VK_SUCCESS) {
|
||||
C.src_view[slot] = VK_NULL_HANDLE;
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
const VkPipelineStageFlags2 CS = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, ALL = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
|
||||
// the game's writes to src are visible; last frame's reads of the output are done (UNDEFINED: its old content goes)
|
||||
barrier(cb, src, VK_IMAGE_LAYOUT_GENERAL, ALL, VK_ACCESS_2_MEMORY_WRITE_BIT, CS, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
|
||||
barrier(cb, C.img, VK_IMAGE_LAYOUT_UNDEFINED, ALL, 0, CS, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT);
|
||||
VkDescriptorImageInfo ii = {C.sampler, C.src_view[slot], VK_IMAGE_LAYOUT_GENERAL},
|
||||
oi = {VK_NULL_HANDLE, C.view, VK_IMAGE_LAYOUT_GENERAL};
|
||||
VkWriteDescriptorSet w[2] = {
|
||||
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 0, .descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &ii},
|
||||
{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 1, .descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .pImageInfo = &oi}};
|
||||
vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, C.pipe);
|
||||
rec_push(C.push, cb, VK_PIPELINE_BIND_POINT_COMPUTE, C.layout, 0, 2, w);
|
||||
struct { int32_t offset[2], size[2]; float adj[4]; } pc = {{crop[0], crop[1]}, {crop[2], crop[3]},
|
||||
{C.adj[0], C.adj[1], C.adj[2], C.adj[3]}};
|
||||
vkCmdPushConstants(cb, C.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof pc, &pc);
|
||||
vkCmdDispatch(cb, ((uint32_t)crop[2] + 7) / 8, ((uint32_t)crop[3] + 7) / 8, 1);
|
||||
// later submissions that write src wait for this read; FSR and the blit read the output
|
||||
barrier(cb, src, VK_IMAGE_LAYOUT_GENERAL, CS, 0, ALL, 0);
|
||||
barrier(cb, C.img, VK_IMAGE_LAYOUT_GENERAL, CS, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT, ALL,
|
||||
VK_ACCESS_2_MEMORY_READ_BIT);
|
||||
return C.img;
|
||||
}
|
||||
|
||||
void vk_color_release(void) {
|
||||
VkCtx *c = vk_ctx();
|
||||
for (int i = 0; i < SLOTS; i++)
|
||||
if (C.src_view[i]) vkDestroyImageView(c->device, C.src_view[i], NULL), C.src_view[i] = VK_NULL_HANDLE;
|
||||
free_image(c);
|
||||
if (C.pipe) vkDestroyPipeline(c->device, C.pipe, NULL);
|
||||
if (C.sampler) vkDestroySampler(c->device, C.sampler, NULL);
|
||||
if (C.layout) vkDestroyPipelineLayout(c->device, C.layout, NULL);
|
||||
if (C.dsl) vkDestroyDescriptorSetLayout(c->device, C.dsl, NULL);
|
||||
C.pipe = VK_NULL_HANDLE, C.sampler = VK_NULL_HANDLE, C.layout = VK_NULL_HANDLE, C.dsl = VK_NULL_HANDLE;
|
||||
C.tried = C.ok = 0;
|
||||
}
|
||||
+24
-9
@@ -2568,7 +2568,8 @@ static struct {
|
||||
PlatThread *th[LB_MAX_THREADS];
|
||||
uint8_t clamp, poly, minus_w, samples; // library state (GKey fields)
|
||||
double t0, ms[3]; // ms: build time per kind (vertex, fragment, compute)
|
||||
uint64_t built[3], have[3], fail[3], warmed[3], noshader, done, cs_draw; // warmed: driver cache only
|
||||
// warmed: driver cache only; nolib: vertex and fragment variants skipped with the graphics pipeline library off
|
||||
uint64_t built[3], have[3], fail[3], warmed[3], noshader, nolib, done, cs_draw;
|
||||
double warm_ms[3];
|
||||
volatile int32_t kept; // libraries entered in libs[] so far
|
||||
int32_t keep_n;
|
||||
@@ -2734,14 +2735,21 @@ static void lb_library(const GfxShader *sh, int vertex, int keep) {
|
||||
lb_count(stat, !pipe ? 2 : have || got != pipe ? 1 : 0, ms);
|
||||
}
|
||||
|
||||
// A vertex or fragment variant with the graphics pipeline library off: no stage library to build ahead.
|
||||
static void lb_skip_lib(void) {
|
||||
plat_mutex_lock(&LB.stat);
|
||||
LB.nolib++;
|
||||
plat_mutex_unlock(&LB.stat);
|
||||
}
|
||||
|
||||
static void lb_report(const char *how) {
|
||||
float mem_used, mem_total, mem_proc;
|
||||
plat_mem_info(&mem_used, &mem_total, &mem_proc);
|
||||
plat_mutex_lock_shared(&LB.stat);
|
||||
rt_log("vk_pipeline: stage library prebuild %s: %llu of %zu variants in %.0f ms; kept: vertex %llu built (avg %.1f ms), "
|
||||
"%llu there; fragment %llu built (avg %.1f ms), %llu there; compute %llu built (avg %.1f ms), %llu there; driver "
|
||||
"cache only: vertex %llu (avg %.1f ms), fragment %llu (avg %.1f ms), compute %llu (avg %.1f ms); %llu failed, %llu not in the shader cache; "
|
||||
"process working set %.0f MB", how, (unsigned long long)LB.done, LB.n,
|
||||
"cache only: vertex %llu (avg %.1f ms), fragment %llu (avg %.1f ms), compute %llu (avg %.1f ms); %llu failed, %llu not in the shader cache, "
|
||||
"%llu vertex/fragment skipped (graphics pipeline library off); process working set %.0f MB", how, (unsigned long long)LB.done, LB.n,
|
||||
now_ms() - LB.t0, (unsigned long long)LB.built[0], LB.built[0] ? LB.ms[0] / LB.built[0] : 0.0,
|
||||
(unsigned long long)LB.have[0], (unsigned long long)LB.built[1], LB.built[1] ? LB.ms[1] / LB.built[1] : 0.0,
|
||||
(unsigned long long)LB.have[1], (unsigned long long)LB.built[2], LB.built[2] ? LB.ms[2] / LB.built[2] : 0.0,
|
||||
@@ -2749,7 +2757,7 @@ static void lb_report(const char *how) {
|
||||
(unsigned long long)LB.warmed[1], LB.warmed[1] ? LB.warm_ms[1] / LB.warmed[1] : 0.0,
|
||||
(unsigned long long)LB.warmed[2], LB.warmed[2] ? LB.warm_ms[2] / LB.warmed[2] : 0.0,
|
||||
(unsigned long long)(LB.fail[0] + LB.fail[1] + LB.fail[2]),
|
||||
(unsigned long long)LB.noshader, (double)mem_proc);
|
||||
(unsigned long long)LB.noshader, (unsigned long long)LB.nolib, (double)mem_proc);
|
||||
plat_mutex_unlock_shared(&LB.stat);
|
||||
}
|
||||
|
||||
@@ -2769,8 +2777,9 @@ static void lb_worker(void *arg) {
|
||||
plat_mutex_unlock(&LB.stat);
|
||||
} else if (gfx_shader_stage(sh) == GFX_STAGE_CS) {
|
||||
lb_compute(sh);
|
||||
} else if (P.gpl && (gfx_shader_stage(sh) == GFX_STAGE_VS || gfx_shader_stage(sh) == GFX_STAGE_FS)) {
|
||||
lb_library(sh, gfx_shader_stage(sh) == GFX_STAGE_VS, 1);
|
||||
} else if (gfx_shader_stage(sh) == GFX_STAGE_VS || gfx_shader_stage(sh) == GFX_STAGE_FS) {
|
||||
if (P.gpl) lb_library(sh, gfx_shader_stage(sh) == GFX_STAGE_VS, 1);
|
||||
else lb_skip_lib();
|
||||
}
|
||||
plat_mutex_lock(&LB.stat);
|
||||
LB.done++;
|
||||
@@ -2861,8 +2870,9 @@ static void lb_warm_worker(void *arg) {
|
||||
VkPipeline pipe = l ? compute_create(sh, l) : VK_NULL_HANDLE;
|
||||
if (pipe) vkDestroyPipeline(P.device, pipe, NULL);
|
||||
lb_count(2, pipe ? 3 : 2, now_ms() - t0);
|
||||
} else if (P.gpl && (st == GFX_STAGE_VS || st == GFX_STAGE_FS)) {
|
||||
lb_library(sh, st == GFX_STAGE_VS, 0);
|
||||
} else if (st == GFX_STAGE_VS || st == GFX_STAGE_FS) {
|
||||
if (P.gpl) lb_library(sh, st == GFX_STAGE_VS, 0);
|
||||
else lb_skip_lib();
|
||||
}
|
||||
if (owned) gfx_shader_release_private(sh);
|
||||
plat_mutex_lock(&LB.stat);
|
||||
@@ -2894,7 +2904,12 @@ static void lb_warm_prepared(const char *dir) {
|
||||
if (!k) lb_warm_worker(NULL);
|
||||
for (int i = 0; i < k; i++) plat_thread_join(t[i]);
|
||||
lb_report("preparation done");
|
||||
printf("prepare-shaders: %zu variants built into the driver cache on %d threads in %.0f ms\n", n, k, now_ms() - LB.t0);
|
||||
// with gpl off only compute variants are built: say so, rather than count the skipped ones as built
|
||||
if (LB.nolib)
|
||||
printf("prepare-shaders: %zu variants on %d threads in %.0f ms, %llu vertex/fragment variants skipped (graphics "
|
||||
"pipeline library off, they build at their first draw)\n", n, k, now_ms() - LB.t0, (unsigned long long)LB.nolib);
|
||||
else
|
||||
printf("prepare-shaders: %zu variants built into the driver cache on %d threads in %.0f ms\n", n, k, now_ms() - LB.t0);
|
||||
free(LB.e), LB.e = NULL, LB.n = 0, LB.next = 0, LB.done = 0;
|
||||
}
|
||||
|
||||
|
||||
+4
-8
@@ -127,14 +127,10 @@ void rec_drain(void) {
|
||||
void rec_begin(VkCommandBuffer cb) {
|
||||
static int mode = -1;
|
||||
if (mode < 0) {
|
||||
// Default off on AMD GPUs: with the recorder their drivers lost the device within the first frames or at menu
|
||||
// opens, 1 boot in 3 on an RX 9070 XT, and TOTK_VK_RECORDER=0 ended it for three users (issue #5); under RADV
|
||||
// the kernel saw an illegal opcode at the end of a command buffer (issue #40). Letting the recorder end its own
|
||||
// parts (rec_end_cb) fixed the white scene on an RX 6700 XT; the default stays off until more AMD cards run it.
|
||||
const char *set = cfg_get("TOTK_VK_RECORDER");
|
||||
mode = rt_knob("VK_RECORDER", vk_caps.amd ? "0" : "1");
|
||||
if (!mode && vk_caps.amd && !(set && *set))
|
||||
rt_log("vk_rec: no recorder thread on AMD GPUs (TOTK_VK_RECORDER=1 turns it on)");
|
||||
// Default on for every GPU. AMD drivers once lost the device with the recorder (issues #5 and #40); after
|
||||
// vk_mem_note_recording kept objects alive until their submission completed and the recorder began ending
|
||||
// its own parts (rec_end_cb), RDNA 1 and 2 cards ran with it on and off without a loss or a white scene.
|
||||
mode = rt_knob("VK_RECORDER", "1");
|
||||
if (mode) {
|
||||
plat_event_init(&wake_ev, 0, 0);
|
||||
rec_prio = plat_thread_priority_get();
|
||||
|
||||
+29
-2
@@ -55,6 +55,7 @@ typedef struct Tex {
|
||||
uint64_t wr_fence; // any entry: newest submission recorded to write its image (render, copy, clear,
|
||||
// storage binding); ReadTexels waits for it instead of draining the replay queue
|
||||
uint8_t listed; // in owned[]
|
||||
uint8_t *snap; // a texture that fills no page: its storage bytes when the GPU write was recorded
|
||||
uint32_t wt_n; // WriteTexels into the storage since the last upload
|
||||
struct Tex *rel_prev, *rel_next; // while released: the list of released entries, oldest first
|
||||
} Tex;
|
||||
@@ -507,6 +508,7 @@ static void destroy_tex(Tex *e) {
|
||||
if (e->released) nreleased--, rel_unlink(e);
|
||||
vk_exec_image_gone(e->pub.image);
|
||||
vk_defer_image(e->pub.image, e->alloc);
|
||||
free(e->snap);
|
||||
free(e);
|
||||
}
|
||||
|
||||
@@ -727,12 +729,33 @@ static void upload(Tex *e) {
|
||||
perf_end(PERF_TEX, p0);
|
||||
}
|
||||
|
||||
// Whether e's storage fills no 4 KiB page of its own, so the write tracking can only see writes to pages it shares.
|
||||
static int fills_no_page(const Tex *e) {
|
||||
return ((e->pub.addr + e->t.L.size) & ~0xfffull) <= ((e->pub.addr + 0xfff) & ~0xfffull);
|
||||
}
|
||||
|
||||
// A texture that fills no page keeps a copy of its storage bytes from when a GPU write to it was recorded
|
||||
// (vk_texture_gpu_written), up to SNAP_MAX bytes; wb_exact (TOTK_WB_EXACT, default on) enables it.
|
||||
enum { SNAP_MAX = 4096 };
|
||||
static int wb_exact = -1;
|
||||
static volatile int32_t n_kept;
|
||||
|
||||
// vk_mem_changed over the pages wholly inside e's storage (all of its pages when it fills none): a neighbour that shares
|
||||
// the first or last page does not count as a write to e.
|
||||
// the first or last page does not count as a write to e. A texture that fills no page shares all of its pages, so
|
||||
// there a page write counts only when e's own bytes differ from the copy taken at the GPU write. TotK's Purah Pad
|
||||
// camera renders the ID, screen rect and depth of what is under the reticle into 4x4 targets of 512 bytes, two
|
||||
// slots used in turn, and the CPU reads them a frame later. One slot's rect target shares its page with data the CPU
|
||||
// writes every frame: without the copy its renders were never written back, the game read a zero rect every other
|
||||
// frame and the target's name label blinked. TOTK_WB_EXACT=0 restores the page-only decision.
|
||||
static int storage_rewritten(const Tex *e, uint32_t since, uint32_t *now) {
|
||||
uint64_t lo = (e->pub.addr + 0xfff) & ~0xfffull, hi = (e->pub.addr + e->t.L.size) & ~0xfffull;
|
||||
if (hi <= lo) lo = e->pub.addr, hi = e->pub.addr + e->t.L.size;
|
||||
return vk_mem_changed(lo, hi - lo, since, now);
|
||||
int ch = vk_mem_changed(lo, hi - lo, since, now);
|
||||
if (ch != 1 || !since || !e->snap || memcmp(e->snap, (const void *)(uintptr_t)e->pub.addr, e->t.L.size)) return ch;
|
||||
log_limited(&n_kept, "write-back of %#llx kept: fmt %#x %ux%u, %llu bytes; a neighbour wrote its page",
|
||||
(unsigned long long)e->pub.addr, e->t.d.format, e->t.d.width, e->t.d.height,
|
||||
(unsigned long long)e->t.L.size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// A mirrored texture is uploaded on first use and again when its storage pages carry a newer write generation.
|
||||
@@ -947,6 +970,10 @@ void vk_texture_gpu_written(const tex_t *t) {
|
||||
if (e->pub.mirrored) {
|
||||
e->gpu_dirty = 1;
|
||||
storage_rewritten(e, 0, &e->gpu_gen); // CPU writes up to here are older than this render
|
||||
if (wb_exact < 0) wb_exact = cfg_bool("TOTK_WB_EXACT");
|
||||
if (wb_exact && !e->snap && e->t.L.size <= SNAP_MAX && fills_no_page(e) && !(e->snap = malloc(SNAP_MAX)))
|
||||
rt_fatal("vk: out of memory for a texture's storage copy");
|
||||
if (e->snap) memcpy(e->snap, (const void *)(uintptr_t)e->pub.addr, e->t.L.size);
|
||||
e->gpu_seq = ++gpu_writes;
|
||||
e->gpu_fence = vk_exec_recording_fence();
|
||||
wb_cfg_init();
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@ WINDRES = UCRT / 'windres.exe' # runtime/totk.rc: the manifest of totk.exe
|
||||
TOOLCHAIN_BIN = None # llvm-mingw bin directory, set by main() for --toolchain
|
||||
DEPS = None # the deps directory of an llvm-mingw build
|
||||
# tools/data/expected-<display version>.json for each build version name.
|
||||
DISPLAY_VERSIONS = {'v0': '1.0.0', 'v112': '1.1.2', 'v121': '1.2.1', 'v143': '1.4.3'}
|
||||
DISPLAY_VERSIONS = {'v0': '1.0.0', 'v111': '1.1.1', 'v112': '1.1.2', 'v121': '1.2.1', 'v143': '1.4.3'}
|
||||
|
||||
GiB = 1 << 30
|
||||
# Peak commit of one compile, 1.2.1 at -O1 (2026-10-20): 120-180 MiB per ~3 MiB chunk, 568 MiB for main_table.c
|
||||
|
||||
@@ -51,7 +51,7 @@ def mng_pin() -> str:
|
||||
"""The MacNativeGames commit tools/fetch_refs.sh pins."""
|
||||
for line in (ROOT / "tools" / "fetch_refs.sh").read_text(encoding="utf-8").splitlines():
|
||||
f = line.split()
|
||||
if len(f) == 4 and f[:2] == ["fetch", "MacNativeGames"]:
|
||||
if len(f) >= 4 and f[:2] == ["fetch", "MacNativeGames"]: # a fifth word asks for the history
|
||||
return f[3]
|
||||
sys.exit("tools/fetch_refs.sh pins no MacNativeGames commit")
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"_comment": "Written locally for the player's own 1.1.x update (version value 0x20000, seen as 1.1.1). RomFS hashes filled in after the first merge; no game data here. No rewritten_sha256: this dump was not run through MacNativeGames' NSO rewriter, so the three digests of the other versions have no reference here and none of the tools read them.",
|
||||
"title_id": "0100F2C0115B6000",
|
||||
"version": 131072,
|
||||
"display_version": "1.1.1",
|
||||
"exefs_sha256": {
|
||||
"main": "1664b8cf664524d90ca64deb3ce41709266457df966dcb499485267554eb8cec",
|
||||
"main.npdm": "fa3f60922406ab9e2488c2b8d77b12e9869b435033fd8c27c543c347bc185587",
|
||||
"rtld": "28317605926caa40a5cbee3e3cb290c65783b4d9f8c3697b43ca41ab2866a44d",
|
||||
"sdk": "91573d4444d0dcd07080db78fece2b098b76080d77cdb2e1d301a2a41d7c4eea",
|
||||
"subsdk0": "9d04c98997977e99c12a48a2dbfd3064b8da96b5152e0119064957a43f6c3f99"
|
||||
},
|
||||
"romfs": {
|
||||
"size": 17051891304,
|
||||
"sha256": "02ed5792f2f6ccff563c035d4a5408a49c1e941760785e8321b42c98e96dbe9b"
|
||||
},
|
||||
"shared_fonts_sha256": {
|
||||
"standard": "823913c9d80807979381e327846466f561f31ba1842cea8f7fedc5ad7d45d2ad",
|
||||
"chinese-simplified": "a04eb490283d8393b2c49c52e315132461d790ac73996cb43ac869ce72003938",
|
||||
"chinese-simplified-extension": "f5784e7a553fef9a05b3b7dba94e77e79162ace9db8dafc715ace0be1fd9b9ff",
|
||||
"chinese-traditional": "9d59b74e59a2dc92296d6bebad7cce791ba94688bd436304aaae174a58bd7457",
|
||||
"korean": "5a4e694782ead5ddcbc0a226aab24362e2d641ca33bac7bdf8cda36ff157c9e8",
|
||||
"nintendo-extension": "f9f62e025c9bbe0e9ae25a54ba5011202b1e3a3f3c33b2d876c40aff6174af68"
|
||||
}
|
||||
}
|
||||
+14
-4
@@ -6,23 +6,33 @@ set -eu
|
||||
cd "$(dirname "$0")/.."
|
||||
mkdir -p refs
|
||||
|
||||
# fetch <name> <url> <commit> [history]: a shallow checkout of the commit, or with "history" the commit and all its
|
||||
# ancestors (tools/shader_list.py walks the log of MacNativeGames' shader list). origin/HEAD names the commit either
|
||||
# way, so tools can read the pinned revision as origin/HEAD.
|
||||
fetch() {
|
||||
name=$1 url=$2 commit=$3
|
||||
name=$1 url=$2 commit=$3 history=${4:-}
|
||||
dir=refs/$name
|
||||
if [ "$(git -C "$dir" rev-parse HEAD 2>/dev/null)" = "$commit" ]; then
|
||||
if [ "$(git -C "$dir" rev-parse HEAD 2>/dev/null)" = "$commit" ] &&
|
||||
[ "$(git -C "$dir" rev-parse -q --verify refs/remotes/origin/HEAD 2>/dev/null)" = "$commit" ] &&
|
||||
{ [ -z "$history" ] || [ "$(git -C "$dir" rev-parse --is-shallow-repository)" = false ]; }; then
|
||||
echo "$name: up to date"
|
||||
return
|
||||
fi
|
||||
rm -rf "$dir"
|
||||
git init -q "$dir"
|
||||
git -C "$dir" remote add origin "$url"
|
||||
git -C "$dir" fetch -q --depth 1 origin "$commit"
|
||||
if [ -n "$history" ]; then
|
||||
git -C "$dir" fetch -q origin "$commit"
|
||||
else
|
||||
git -C "$dir" fetch -q --depth 1 origin "$commit"
|
||||
fi
|
||||
git -C "$dir" checkout -q FETCH_HEAD
|
||||
git -C "$dir" update-ref refs/remotes/origin/HEAD "$commit"
|
||||
echo "$name: $commit"
|
||||
}
|
||||
|
||||
fetch LASRecomp https://github.com/peiikai/LASRecomp.git da35b83a9611d046a9a681aae668d7085954d37f
|
||||
fetch MacNativeGames https://github.com/ly05010419/MacNativeGames.git b75be8cfa704df3701e86130c8ced268e79d6b11
|
||||
fetch MacNativeGames https://github.com/ly05010419/MacNativeGames.git b75be8cfa704df3701e86130c8ced268e79d6b11 history
|
||||
fetch d2r-mobile https://github.com/jhaizhou-ops/d2r-mobile.git 53e7c5bb9ca3ea53e2160005be8fbe920cd9cb53
|
||||
fetch nxnative https://github.com/seedds/nxnative.git d5b380b3a80495609c2e751bb994911a638935ad
|
||||
fetch SwitchRecomp https://github.com/bgyss/SwitchRecomp.git 0f99e1c5c1e78fdc8e01e58a0f8dc8622e82976b
|
||||
|
||||
+26
-5
@@ -38,6 +38,7 @@ import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
@@ -122,15 +123,35 @@ def fetch(name):
|
||||
url, want = PINS[name]
|
||||
DL.mkdir(parents=True, exist_ok=True)
|
||||
path = DL / Path(url.split('?')[0]).name.replace('%7E', '~')
|
||||
if name in ('tcl-linux-src', 'tk-linux-src'):
|
||||
path = DL / f'{name}-9.0.4.tar.gz' # upstream archives have identical basenames
|
||||
if name == 'libvpx-src':
|
||||
path = DL / 'libvpx-1.17.0.tar.gz'
|
||||
if not path.is_file():
|
||||
print(f'download {url}')
|
||||
with urllib.request.urlopen(url) as r, open(path, 'wb') as f:
|
||||
shutil.copyfileobj(r, f)
|
||||
if path.is_file():
|
||||
if sha256_file(path) == want:
|
||||
return path
|
||||
print(f'{path}: not the pinned file (a cut download or an older pin); downloading it again')
|
||||
path.unlink()
|
||||
# into <name>.part, renamed once complete: a cut connection leaves a .part that the next run resumes, never a
|
||||
# short file under the final name
|
||||
part = path.with_name(path.name + '.part')
|
||||
have = part.stat().st_size if part.is_file() else 0
|
||||
print(f'download {url}' + (f' (resuming at {have} bytes)' if have else ''))
|
||||
req = urllib.request.Request(url, headers={'Range': f'bytes={have}-'} if have else {})
|
||||
try:
|
||||
with urllib.request.urlopen(req) as r:
|
||||
if have and r.status != 206: # the server ignored the range: start over
|
||||
have = 0
|
||||
with open(part, 'ab' if have else 'wb') as f:
|
||||
shutil.copyfileobj(r, f)
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code != 416: # 416: the .part already holds the whole file
|
||||
raise
|
||||
part.replace(path)
|
||||
got = sha256_file(path)
|
||||
if got != want:
|
||||
sys.exit(f'{path}: sha256 {got}, pinned {want}')
|
||||
path.unlink() # a .part resumed from another upload, or the URL now serves another file: start clean next run
|
||||
sys.exit(f'{path}: sha256 {got}, pinned {want}; deleted, run again to download it anew')
|
||||
return path
|
||||
|
||||
|
||||
|
||||
+73
-11
@@ -61,6 +61,10 @@ PINS = {
|
||||
'python-linux': ('https://github.com/astral-sh/python-build-standalone/releases/download/20261026/'
|
||||
'cpython-3.14.10%2B20261026-x86_64-unknown-linux-gnu-install_only.tar.gz',
|
||||
'1b38024924eb7f4cf0cab45d7b871d1f1991354190fccd2625ac7529a43786e2'),
|
||||
'tcl-linux-src': ('https://github.com/tcltk/tcl/archive/refs/tags/core-9-0-4.tar.gz',
|
||||
'8c01bac92a9a8ce271b63d3aa28f09cde2c6762d6719ca671cdc8aee25391234'),
|
||||
'tk-linux-src': ('https://github.com/tcltk/tk/archive/refs/tags/core-9-0-4.tar.gz',
|
||||
'ab9bd89fcff2e1248b7a9c097035f817fa0725285028c95686fdb92db65bc148'),
|
||||
'ninja-linux': ('https://github.com/ninja-build/ninja/releases/download/v1.13.2/ninja-linux.zip',
|
||||
'5749cbc4e668273514150a80e387a957f933c6ed3f5f11e03fb30955e2bbead6'),
|
||||
'zstd-src': ('https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz',
|
||||
@@ -68,12 +72,12 @@ PINS = {
|
||||
'vulkan-headers-src': ('https://github.com/KhronosGroup/Vulkan-Headers/archive/refs/tags/v1.4.363.tar.gz',
|
||||
'cbaf687d3c59b9666fe080e7f8396b6b4f4d344a768ee6b337c59852f4526b68'),
|
||||
'vma-header': build_game.VMA_FETCH,
|
||||
# appimagetool and the runtime are published only under the moving "continuous" tag: pinned by hash, so a new
|
||||
# upload stops the build until the pins are updated on purpose
|
||||
'appimagetool': ('https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage',
|
||||
'ac7fd56feade6002c2f57db39587113275d351fce911a5fbb29ea84cd0666cab'),
|
||||
'appimage-runtime': ('https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-x86_64',
|
||||
'617aa77314d57c8b8e4fae9c3f0b35af7b30d5529cb69e98f64f92510ed98a46'),
|
||||
# appimagetool and the type-2 runtime from release tags, not "continuous", whose uploads change under the same
|
||||
# URL and would stop the build at the hash check
|
||||
'appimagetool': ('https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage',
|
||||
'ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0'),
|
||||
'appimage-runtime': ('https://github.com/AppImage/type2-runtime/releases/download/20251108/runtime-x86_64',
|
||||
'2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d'),
|
||||
}
|
||||
mr.PINS.update(PINS) # one cache (toolchain/release-dl) and one fetch for both releases
|
||||
|
||||
@@ -81,10 +85,11 @@ mr.PINS.update(PINS) # one cache (toolchain/release-dl) and one fetch for both
|
||||
LLVM_MEMBERS = [f'bin/clang-{LLVM_MAJOR}', 'bin/clang', 'bin/clang++', 'bin/lld', 'bin/ld.lld', 'bin/llvm-ar',
|
||||
'bin/llvm-ranlib', 'bin/llvm-objcopy', 'bin/llvm-strip', f'lib/clang/{LLVM_MAJOR}/include']
|
||||
# The parts of the sniper SDK a C/C++ compile and link for x86_64 can read.
|
||||
SNIPER_MEMBERS = ['usr/include', 'usr/lib/x86_64-linux-gnu', 'usr/lib/gcc/x86_64-linux-gnu',
|
||||
SNIPER_MEMBERS = ['usr/include', 'usr/share/pkgconfig', 'usr/lib/x86_64-linux-gnu', 'usr/lib/gcc/x86_64-linux-gnu',
|
||||
'lib/x86_64-linux-gnu', 'lib64', 'usr/share/doc/libsdl3-0', 'usr/share/doc/libopus0',
|
||||
'usr/share/doc/libvpx6', 'usr/share/doc/libc6', 'usr/share/doc/gcc-10-base',
|
||||
'usr/share/doc/libvulkan1']
|
||||
'usr/share/doc/libvulkan1',
|
||||
'usr/share/doc/libxft2', 'usr/share/doc/libxrender1']
|
||||
# glibc's link libraries and their scripts (lib<name>.so, .a, _nonshared.a), and the GCC runtime pieces clang links.
|
||||
GLIBC_LIBS = ['c', 'm', 'mvec', 'dl', 'pthread', 'rt', 'util']
|
||||
GCC_LIBS = {'libgcc.a', 'libgcc_eh.a', 'libgcc_s.so', 'libstdc++.a', 'libsupc++.a'}
|
||||
@@ -139,8 +144,11 @@ def full_sysroot():
|
||||
"""The sniper SDK's x86_64 compile and link files in toolchain/release-work/sniper, absolute symlinks made relative
|
||||
(the SDK points some at /lib and /usr/lib, which outside its container are the host's)."""
|
||||
root = WORK / 'sniper'
|
||||
if (root / '.relinked').is_file():
|
||||
if (root / '.relinked').is_file() and (root / 'usr/share/doc/libxft2/copyright').is_file():
|
||||
return root
|
||||
# Older cached SDK extractions omitted the font libraries' architecture-independent pkg-config files and the
|
||||
# libXft and libXrender copyright files.
|
||||
(root / '.unpacked').unlink(missing_ok=True)
|
||||
tar_extract('sniper-sdk', root, SNIPER_MEMBERS)
|
||||
for p in root.rglob('*'):
|
||||
if p.is_symlink() and os.readlink(p).startswith('/'):
|
||||
@@ -309,7 +317,50 @@ def build_sysroot(out, sysroot, files):
|
||||
|
||||
# ---- python/ ---------------------------------------------------------------------------------------------------------
|
||||
|
||||
def build_python(out):
|
||||
def build_tk_xft(out, llvm, sysroot):
|
||||
"""Replace standalone Python's no-Xft Tcl/Tk with the same version using system TrueType fonts (#227)."""
|
||||
prefix = WORK / 'tcltk-xft'
|
||||
env = dict(os.environ, CC=sysroot_cc(llvm, sysroot),
|
||||
PKG_CONFIG_SYSROOT_DIR=str(sysroot),
|
||||
PKG_CONFIG_LIBDIR=os.pathsep.join(str(sysroot / p) for p in
|
||||
('usr/lib/x86_64-linux-gnu/pkgconfig', 'usr/share/pkgconfig')),
|
||||
PKG_CONFIG_PATH='')
|
||||
sources = {}
|
||||
for name in ('tcl', 'tk'):
|
||||
src = mr.unpack(f'{name}-linux-src') / f'{name}-core-9-0-4'
|
||||
sources[name] = src
|
||||
build = src / 'unix'
|
||||
options = [f'--prefix={prefix}', '--enable-shared', '--disable-zipfs', '--disable-rpath']
|
||||
if name == 'tk':
|
||||
options += [f'--with-tcl={prefix / "lib"}', '--enable-xft', '--disable-libcups', '--disable-xss']
|
||||
run([build / 'configure', *options], cwd=build, env=env)
|
||||
run(['make', f'-j{os.cpu_count() or 4}'], cwd=build, env=env)
|
||||
run(['make', 'install'], cwd=build, env=env)
|
||||
lib = prefix / 'lib'
|
||||
# A successful GUI selftest alone did not catch missing glyphs. Reject a no-Xft build before packaging it.
|
||||
symbols = subprocess.run(['nm', '-D', str(lib / 'libtcl9tk9.0.so')],
|
||||
capture_output=True, text=True, check=True).stdout
|
||||
if not re.search(r'\bU Xft', symbols):
|
||||
sys.exit('Tcl/Tk build has no Xft font rendering')
|
||||
dst = out / 'python' / 'lib'
|
||||
for name in ('libtcl9.0.so', 'libtcl9tk9.0.so'):
|
||||
shutil.copy2(lib / name, dst / name)
|
||||
# libXft is not on every desktop (the old Tk did not need it) and without it _tkinter fails to import, so the
|
||||
# window would not open at all. python's RPATH ($ORIGIN/../lib) finds these beside the Tk library.
|
||||
sys_lib = sysroot / 'usr' / 'lib' / 'x86_64-linux-gnu'
|
||||
for pattern in ('libXft.so.2*', 'libXrender.so.1*'):
|
||||
for f in sys_lib.glob(pattern):
|
||||
shutil.copy2(f, dst / f.name, follow_symlinks=False)
|
||||
for name in ('tcl9.0', 'tk9.0'):
|
||||
shutil.rmtree(dst / name)
|
||||
shutil.copytree(lib / name, dst / name, symlinks=True)
|
||||
licenses = out / 'licenses'
|
||||
licenses.mkdir(exist_ok=True)
|
||||
for name, src in sources.items():
|
||||
shutil.copy2(src / 'license.terms', licenses / f'{name}-license.terms')
|
||||
|
||||
|
||||
def build_python(out, llvm, sysroot):
|
||||
src = mr.unpack('python-linux') / 'python'
|
||||
py = out / 'python'
|
||||
shutil.copytree(src, py, symlinks=True,
|
||||
@@ -319,6 +370,7 @@ def build_python(out):
|
||||
run([exe, '-m', 'pip', 'install', '--no-compile', '--only-binary', ':all:', '-r',
|
||||
ROOT / 'tools' / 'requirements-installer.txt'], env=env, stdout=subprocess.DEVNULL)
|
||||
run([exe, '-m', 'pip', 'uninstall', '-y', 'pip'], stdout=subprocess.DEVNULL) # the setup installs nothing
|
||||
build_tk_xft(out, llvm, sysroot)
|
||||
strip(py, out / 'llvm' / 'bin' / 'llvm-strip')
|
||||
site = next(py.glob('lib/python3.*/site-packages'))
|
||||
# The release folder, tools/ and recomp/ on sys.path, as the Windows python's ._pth does: `python -m installer` and
|
||||
@@ -346,6 +398,8 @@ def collect_licenses(out, sysroot):
|
||||
(doc / 'libc6' / 'copyright', 'glibc-copyright.txt'),
|
||||
(doc / 'gcc-10-base' / 'copyright', 'libstdc++-copyright.txt'),
|
||||
(doc / 'libvulkan1' / 'copyright', 'vulkan-loader-copyright.txt'),
|
||||
(doc / 'libxft2' / 'copyright', 'libXft-copyright.txt'),
|
||||
(doc / 'libxrender1' / 'copyright', 'libXrender-copyright.txt'),
|
||||
(WORK / 'vulkan-headers' / 'LICENSE.md', 'vulkan-headers-LICENSE.txt'),
|
||||
(WORK / 'zstd' / 'LICENSE', 'zstd-LICENSE.txt'),
|
||||
(out / 'python' / 'lib' / f'python{sys_version(out)}' / 'LICENSE.txt', 'python-LICENSE.txt'),
|
||||
@@ -370,6 +424,8 @@ APPRUN = '''#!/bin/sh
|
||||
# The AppImage entry: the setup window, run by the bundled Python from the read-only mount (no bytecode written there).
|
||||
HERE="$(dirname "$(readlink -f "$0")")"
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
export TCL_LIBRARY="$HERE/python/lib/tcl9.0"
|
||||
export TK_LIBRARY="$HERE/python/lib/tk9.0"
|
||||
exec "$HERE/python/bin/python3" -m installer.gui "$@"
|
||||
'''
|
||||
|
||||
@@ -452,6 +508,12 @@ def check_bundle(out):
|
||||
f'{r.stderr[-2000:]}')
|
||||
print(f'bundle check: the installer ran the host check from the folder and stopped at the game files '
|
||||
f'(exit {r.returncode})')
|
||||
run([py, '-c', 'import tkinter as tk; from tkinter import font; '
|
||||
'root = tk.Tk(); root.withdraw(); '
|
||||
'fonts = [font.nametofont("TkDefaultFont"), font.Font(family="DejaVu Sans"), '
|
||||
'font.Font(family="DejaVu Sans Mono")]; '
|
||||
'missing = [(f.actual("family"), c) for f in fonts for c in "УстановкаЁёЙй" if f.measure(c) <= 0]; '
|
||||
'root.destroy(); assert not missing, f"Cyrillic glyphs missing: {missing}"'], cwd='/', env=env)
|
||||
r = subprocess.run([str(py), '-m', 'installer.gui', '--selftest', '--root', str(out)], cwd='/', env=env,
|
||||
capture_output=True, text=True, timeout=300)
|
||||
if r.returncode:
|
||||
@@ -511,7 +573,7 @@ def main():
|
||||
build_bin(out, llvm, sysroot)
|
||||
rtlib = runtime_lib(out, llvm, sysroot)
|
||||
build_sysroot(out, sysroot, used_sysroot_files(out, llvm, sysroot, rtlib))
|
||||
build_python(out)
|
||||
build_python(out, llvm, sysroot)
|
||||
mr.build_sources(out)
|
||||
collect_licenses(out, sysroot)
|
||||
build_entry(out)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Adapted from MacNativeGames games/totk/scripts/make_romfs_patches.py @ ebe621d (MIT, Copyright (c) 2026
|
||||
# MacNativeGames contributors).
|
||||
"""Build the 60 fps RomFS overlay for TotK 1.2.1 or 1.4.3 (runtime/romfs.c, TOTK_ROMFS_PATCHES).
|
||||
"""Build the 60 fps RomFS overlay for TotK 1.1.1, 1.2.1 or 1.4.3 (runtime/romfs.c, TOTK_ROMFS_PATCHES).
|
||||
|
||||
UI/LayoutArchive/Common.Product.<110 or 140>.Nin_NX_NVN.blarc.zs: the weapon / shield / material quick menus capture the game
|
||||
picture for their background when the panes C_CaptureBG_00, C_CaptureClearBG_00 and P_CaptureClearBG_00 become
|
||||
@@ -8,7 +8,7 @@ visible at animation frame 1.0 (anim/ShortCutBG_00_In.bflan), AutoBuilder the sa
|
||||
(anim/AutoBuilder_00_In.bflan). UI animations advance 0.5 frames per game frame at 60 fps, so the capture lands after
|
||||
the world stopped drawing and the background can be black. The visibility keys move to frame 0.0: the live world is
|
||||
captured at once (upstream checked weapon, shield, material and ability wheel at 60 fps, and 30 fps).
|
||||
The game-over flow fix of the same upstream script is 1.0.0 only (1.2.1 and 1.4.3 run DmF_SY_GameOver.110) and is
|
||||
The game-over flow fix of the same upstream script is 1.0.0 only (1.1.1, 1.2.1 and 1.4.3 run DmF_SY_GameOver.110) and is
|
||||
left out.
|
||||
|
||||
--aspect <w>x<h> (a game resolution of another shape than 16:9, such as 3440x1440 or 1280x800) also lays the UI out
|
||||
@@ -44,7 +44,7 @@ from pathlib import Path
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
ap.add_argument('--version', choices=('v121', 'v143'), default='v121')
|
||||
ap.add_argument('--version', choices=('v111', 'v121', 'v143'), default='v121')
|
||||
ap.add_argument('--romfs', type=Path, help='default build/extracted/<version>/romfs')
|
||||
ap.add_argument('--out', type=Path,
|
||||
help='default build/<version>/romfs-patches (romfs-patches-xbox with --xbox-prompts)')
|
||||
@@ -309,7 +309,7 @@ def sarc_replace(data, new):
|
||||
return bytes(out)
|
||||
|
||||
|
||||
COMMON = {'v121': '110', 'v143': '140'} # the version in the name of each game's Common archive
|
||||
COMMON = {'v111': '110', 'v121': '110', 'v143': '140'} # the version in the name of each game's Common archive
|
||||
ARCHIVE = f'UI/LayoutArchive/Common.Product.{COMMON[args.version]}.Nin_NX_NVN.blarc.zs'
|
||||
MEMBERS = {
|
||||
'anim/ShortCutBG_00_In.bflan': ('C_CaptureBG_00', 'C_CaptureClearBG_00', 'P_CaptureClearBG_00'),
|
||||
|
||||
@@ -79,6 +79,11 @@ def pack(cache, keep=False):
|
||||
out = cache / PACK
|
||||
packed = read_pack(out)
|
||||
loose = loose_files(cache)
|
||||
if not loose:
|
||||
# Nothing to fold in: every file of the cache is already in the pack, so writing it again copies the same
|
||||
# bytes over the same entries (1.37 GiB here, 2.6 s, and a file system that then has the old pack's blocks to
|
||||
# release as well). The installer runs this after every prepare.
|
||||
return len(packed), 0, 0, out.stat().st_size if out.is_file() else 0
|
||||
keys = sorted(set(packed) | set(loose))
|
||||
tmp = out.with_name(PACK + '.tmp')
|
||||
index, written, skipped, offset = [], [], 0, 16 + 48 * len(keys)
|
||||
|
||||
@@ -140,10 +140,17 @@ def main():
|
||||
t0 = time.monotonic()
|
||||
variants, prio = parse_list(read_list(a.list))
|
||||
extracted = ROOT / 'build' / 'extracted' / a.version
|
||||
shaders, _ = inventory(a.romfs or extracted / 'romfs', a.exefs or extracted / 'exefs')
|
||||
listed = len(variants)
|
||||
shaders, _ = inventory(a.romfs or extracted / 'romfs', a.exefs or extracted / 'exefs', everything=False)
|
||||
# A missing list row says nothing about the walk: the list covers several versions, and 1.1.1 holds 17,587 of its
|
||||
# 41,414 rows, so most rows are absent because the game does not have them, not because the walk missed them. A walk
|
||||
# that found next to nothing does say something: the shader directories have moved.
|
||||
if len(shaders) < max(64, listed // 20):
|
||||
print(f'only {len(shaders)} shaders found of the {listed} listed variants: reading every RomFS file instead',
|
||||
flush=True)
|
||||
shaders, _ = inventory(a.romfs or extracted / 'romfs', a.exefs or extracted / 'exefs')
|
||||
t1 = time.monotonic()
|
||||
progs, recs, seen, skipped = {}, [], set(), {'not in this version': 0, 'stage': 0}
|
||||
listed = len(variants)
|
||||
variants += [(key, DEFAULT) for key in sorted(shaders)]
|
||||
for key, deps in variants:
|
||||
s = shaders.get(key)
|
||||
|
||||
@@ -172,7 +172,13 @@ def startup_package(name):
|
||||
name.startswith('Shader/') and name.endswith('.zs'))
|
||||
|
||||
|
||||
def inventory(romfs, exefs):
|
||||
# The RomFS directories that can hold shaders: the agl and gsys packages, the effects, terrain, the UI layout archives
|
||||
# and the game's own shader packs. Every shader of 1.1.1 (27,576) comes from these six; they are 1,996 entries and
|
||||
# 147 MiB, 0.9% of a RomFS whose every file the walk below used to read and decompress first (65 s against 2.8 s).
|
||||
SHADER_DIRS = ('Shader/', 'Effect/', 'Lib/agl/', 'Lib/Terrain/', 'Lib/gsys/', 'UI/LayoutArchive/')
|
||||
|
||||
|
||||
def inventory(romfs, exefs, everything=True):
|
||||
"""Every unique shader: key -> dict(stage, control, code, source, origin, startup). As in MNG, a shader is a startup
|
||||
shader when it comes from an agl package, a game module, or a startup_package() RomFS file; the others come from
|
||||
effect and UI layout resources, which MNG translates only for bind states players have met. Also returns the number
|
||||
@@ -228,6 +234,8 @@ def inventory(romfs, exefs):
|
||||
pos += max(size, 8)
|
||||
|
||||
for name in sorted(entries):
|
||||
if not everything and not name.startswith(SHADER_DIRS):
|
||||
continue
|
||||
raw = read(name)
|
||||
if raw[:4] not in (ZSTD, b'SARC', b'FSHA', b'BNSH', b'BAHS') and b'BNSH\0\0\0\0' not in raw:
|
||||
continue
|
||||
|
||||
@@ -8,6 +8,7 @@ our play meets: at 1340bee the list alone has 134 rows of 1.4.3 shaders, its his
|
||||
25,238. A row no draw asks for costs one translation. No copy of the list goes into the repository.
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
PATH = 'games/totk/assets/shader_list.tsv'
|
||||
|
||||
@@ -18,6 +19,9 @@ def git(repo, *args):
|
||||
|
||||
def history(repo, rev='origin/HEAD'):
|
||||
"""rev's list, then each V row of an older revision that no newer one has, newest first."""
|
||||
if git(repo, 'rev-parse', '--is-shallow-repository').strip() == 'true':
|
||||
# a shallow clone has rev's list only: preparing from it skips most variants and shows only as stutter in play
|
||||
sys.exit(f'{repo} is a shallow clone without the shader list\'s history; run tools/fetch_refs.sh')
|
||||
lines = git(repo, 'show', f'{rev}:{PATH}').splitlines()
|
||||
seen = set(lines)
|
||||
for h in git(repo, 'log', '--format=%H', rev, '--', PATH).split()[1:]:
|
||||
|
||||
+15
-2
@@ -26,7 +26,8 @@
|
||||
# async_preempt = 1
|
||||
# Replay NVN submissions on their own thread, at most one frame behind the game (1 = on). Adds up to a frame of latency.
|
||||
# replay_async = 0
|
||||
# 1 lifts the virtual core gate: guest threads run without the console's limit of one thread per core.
|
||||
# 1 lifts the virtual core gate: guest threads run without the console's limit of one thread per core. For experiments
|
||||
# only: the game keeps per-core data without locks and can crash.
|
||||
# free_cores = 0
|
||||
# Windows timer resolution of 1 ms for steadier frame pacing (0 = the Windows default).
|
||||
# timer_1ms = 1
|
||||
@@ -88,7 +89,7 @@
|
||||
# Vulkan.
|
||||
# Vulkan validation layer: checks every call and slows the game down a lot. Needs the layer installed (Vulkan SDK).
|
||||
# vk_validation = 0
|
||||
# A thread of its own makes the Vulkan calls of each frame, 0 or 1. Default: on, except on AMD GPUs.
|
||||
# A thread of its own makes the Vulkan calls of each frame, 0 or 1. Default: on.
|
||||
# vk_recorder = 1
|
||||
# Dynamic vertex input, 0 or 1. Default: on, except on Intel's older integrated GPU drivers (Vulkan 1.3.215 and older).
|
||||
# vk_dyn_vertex = 1
|
||||
@@ -140,6 +141,18 @@
|
||||
# window is no longer stretched as it used to be; stretch fills the window. Changes while the game runs take effect at
|
||||
# once.
|
||||
# aspect = fit
|
||||
# Field of view, in percent of the game's own: 70 to 140, 100 (the default) leaves the camera as it is. Higher shows
|
||||
# more of the world around Link. Game version 1.4.3 only. Changes while the game runs take effect at once.
|
||||
# fov = 100
|
||||
# Colour adjustment of the picture, HUD included, before it is scaled to the window: brightness (-30 to 30, added in %),
|
||||
# contrast and saturation (70 to 150 and 50 to 200 %), gamma (0.8 to 1.2, below 1 brightens dark tones). At 0, 100,
|
||||
# 100 and 1 nothing runs. color_preset is the menu's profile (default, vivid, clarity, custom), which sets the four.
|
||||
# Changes while the game runs take effect at once.
|
||||
# color_preset = default
|
||||
# color_brightness = 0
|
||||
# color_contrast = 100
|
||||
# color_saturation = 100
|
||||
# color_gamma = 1
|
||||
|
||||
[language]
|
||||
# Language: a Windows locale such as de-DE, or a Switch code (en-US, ja, zh-Hant, ...). Default: the Windows locale.
|
||||
|
||||
Reference in new issue
Block a user