mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
Show keyboard and mouse icons in the prompts when prompts = keyboard
Add keyboard and mouse button icons to tools/button_icons.py and tools/make_romfs_patches.py --prompts keyboard, with a third 'Keyboard and mouse' choice under Controls > Button prompts (prompts = keyboard). Keycaps cut their labels out of the game's own text fonts, mouse clicks and right-stick camera look draw clean mouse silhouettes, and make_romfs_patches.py --ini reads any bind_ overrides from totk.ini.
This commit is contained in:
1 parent
ff49f6b62f
commit
cd35ffaf36
23 files changed
+472
-96
No files matched your search
@@ -30,7 +30,7 @@ Report problems as issues, following [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
- Setup and in-game menu in English, Russian, Brazilian Portuguese and Simplified Chinese
|
||||
- Keyboard, mouse and gamepad with remapping, rumble and gyro
|
||||
- Stereo or 5.1 surround sound output
|
||||
- Xbox button prompts: LB, RB, LT, RT, Menu and View, and A, B, X and Y as labelled on the button to press, with either button layout
|
||||
- Xbox or keyboard button prompts: Xbox (LB, RB, LT, RT, Menu, View, A/B/X/Y by layout) or keyboard and mouse (keycaps and mouse icons)
|
||||
- Shaders prepared at install instead of during play
|
||||
- Mods made for emulators: unpack a mod into the `mods` folder next to `totk.exe`; switch and order them in the menu. RomFS
|
||||
mods, TKMM's merged output and ExeFS code patches (`.ips`, `.pchtxt`) work; the game rebuilds itself for code patches
|
||||
|
||||
+4
-3
@@ -113,9 +113,10 @@ of them.
|
||||
```
|
||||
`--patches` builds in the frame-rate, resolution and other patch sets, switchable at run time. The link copies
|
||||
`SDL3.dll`, `libiconv-2.dll` and `libopus-0.dll` next to the exe.
|
||||
For Xbox button prompts, run `make_romfs_patches.py` twice more, with `--prompts xbox` and `--prompts xbox-label`:
|
||||
they write `build\<ver>\romfs-patches-xbox` and `romfs-patches-xbox-label` ([controls.md](controls.md), "Xbox
|
||||
button prompts"), which `build_game.py` copies next to `totk.exe` like `romfs-patches`.
|
||||
For Xbox or keyboard button prompts, run `make_romfs_patches.py` with `--prompts xbox`, `--prompts xbox-label`, or
|
||||
`--prompts keyboard [--ini totk.ini]`: they write `build\<ver>\romfs-patches-xbox`, `romfs-patches-xbox-label` and
|
||||
`romfs-patches-keyboard` ([controls.md](controls.md), "Button prompts"), which `build_game.py` copies next to `totk.exe`
|
||||
like `romfs-patches`.
|
||||
|
||||
6. Prepare the shaders (once per build folder and translator). It needs `refs\MacNativeGames` (`tools\fetch_refs.sh`)
|
||||
for the shared shader list:
|
||||
|
||||
+23
-19
@@ -8,7 +8,7 @@ Input comes from the first SDL3 gamepad, the keyboard and the mouse, merged into
|
||||
- `xbox`: south A, east B, west X, north Y, as an Xbox pad labels them; on a PlayStation pad cross is A, circle B, square X, triangle Y.
|
||||
- `auto` (default): `nintendo` for a pad whose east button SDL reports labelled A (Switch Pro Controller, Nintendo-layout pads), `xbox` for the others.
|
||||
|
||||
The log names the layout in effect (`input: gamepad layout ...`); the menu shows it under *Automatic*. Which controller's buttons the game's prompts show is `TOTK_PROMPTS` (see Xbox button prompts below). 0.1.9's `pad_layout = xbox_prompts` is read as `pad_layout = nintendo` with `prompts = xbox`, and the menu writes it that way.
|
||||
The log names the layout in effect (`input: gamepad layout ...`); the menu shows it under *Automatic*. Which controller's buttons the game's prompts show is `TOTK_PROMPTS` (see Button prompts below). 0.1.9's `pad_layout = xbox_prompts` is read as `pad_layout = nintendo` with `prompts = xbox`, and the menu writes it that way.
|
||||
|
||||
| Switch | Gamepad (by position, Nintendo layout) | Keyboard and mouse | Action in the world |
|
||||
|---|---|---|---|
|
||||
@@ -30,31 +30,35 @@ The log names the layout in effect (`input: gamepad layout ...`); the menu shows
|
||||
|
||||
Esc is unbound in the game; it opens the settings menu and releases the mouse cursor (see Mouse camera). Z and X duplicate ZL and ZR so menu pages turn with two neighbouring keys; the menu tabs (L, R) are Q and R. The mouse wheel is unbound. The bindings are the tables of `input_map` in `runtime/input_map.c`: the keys (platform-neutral key ids) and one gamepad table per layout (gamepad inputs by position); `runtime/input_win.c` translates Win32 virtual keys and SDL buttons.
|
||||
|
||||
## Xbox button prompts
|
||||
## Button prompts
|
||||
|
||||
`TOTK_PROMPTS` (`prompts`, the *Button prompts* row on the Controls tab, read at start) picks the controller whose buttons the game's prompts show: `nintendo` (default) keeps the game's own, `xbox` shows an Xbox controller's.
|
||||
`TOTK_PROMPTS` (`prompts`, the *Button prompts* row on the Controls tab, read at start) picks the controller whose buttons the game's prompts show: `nintendo` (default) keeps the game's own, `xbox` shows an Xbox controller's, and `keyboard` shows keyboard keycaps and mouse icons.
|
||||
|
||||
- L, R, ZL, ZR, Plus and Minus become LB, RB, LT, RT, Menu and View, and the d-pad prompts a cross with an arrow for each direction meant. The letters on the keys are cut out in the game's own text font.
|
||||
- A, B, X and Y name the label of the button the pad layout presses, so each prompt names the button to press. `pad_layout = nintendo` plays an Xbox pad by position: the right button, labelled B, does Switch A (interact, confirm), and the bottom one, labelled A, does Switch B (run, cancel), so the prompts swap A with B and X with Y. `xbox` and `auto` put Switch A on the button labelled A, and the letters stay.
|
||||
- Xbox:
|
||||
- L, R, ZL, ZR, Plus and Minus become LB, RB, LT, RT, Menu and View, and the d-pad prompts a cross with an arrow for each direction meant. The letters on the keys are cut out in the game's own text font.
|
||||
- A, B, X and Y name the label of the button the pad layout presses, so each prompt names the button to press. `pad_layout = nintendo` plays an Xbox pad by position: the right button, labelled B, does Switch A (interact, confirm), and the bottom one, labelled A, does Switch B (run, cancel), so the prompts swap A with B and X with Y. `xbox` and `auto` put Switch A on the button labelled A, and the letters stay.
|
||||
- Keyboard:
|
||||
- Each action shows a rounded keycap with its key's label cut out in the game's font (square for 1-2 character labels, wide for 3+ characters like Space, Shift, Ctrl, Tab), arrow polygons for arrow keys, a horizontal bar for unbound keys, and mouse icons for mouse clicks and camera look.
|
||||
- The movement prompts show WASD (or the stick keys' labels) on a wide keycap; camera look prompts show a mouse with look arrows.
|
||||
- By default it reflects the default bindings; `tools/make_romfs_patches.py --prompts keyboard --ini <path>` reads `totk.ini`'s `bind_` lines to match custom bindings.
|
||||
|
||||
| Switch | `nintendo` layout | `xbox` or `auto` layout |
|
||||
|---|---|---|
|
||||
| A, B, X, Y | B, A, Y, X | A, B, X, Y |
|
||||
| L, R | LB, RB | LB, RB |
|
||||
| ZL, ZR | LT, RT | LT, RT |
|
||||
| Plus, Minus | Menu, View | Menu, View |
|
||||
| D-pad | cross | cross |
|
||||
| Switch | `nintendo` layout | `xbox` or `auto` layout | Keyboard (default) |
|
||||
|---|---|---|---|
|
||||
| A, B, X, Y | B, A, Y, X | A, B, X, Y | E, Shift, Space, Left Mouse |
|
||||
| L, R | LB, RB | LB, RB | Q, R |
|
||||
| ZL, ZR | LT, RT | LT, RT | Right Mouse, F |
|
||||
| Plus, Minus | Menu, View | Menu, View | Tab, M |
|
||||
| D-pad | cross | cross | 1, 2, 3, 4 |
|
||||
| Left / Right Stick | sticks | sticks | WASD / Mouse |
|
||||
|
||||
The action prompt (`DoCommand_00`) shows its button in a diamond whose shape gives the face buttons' positions as on a Switch, with the other three letters around it. With `nintendo` its letters are swapped with the rest and every one names the button in that place. With `xbox` and `auto` the letters are right but the diamond keeps the Switch positions: A shows on its right, while the button labelled A is at the bottom. The sticks keep the Switch icons, and so do prompts drawn as fixed images (some tutorial animations).
|
||||
The action prompt (`DoCommand_00`) shows its button in a diamond whose shape gives the face buttons' positions as on a Switch, with the other three letters around it. With `nintendo` its letters are swapped with the rest and every one names the button in that place. With `xbox` and `auto` the letters are right but the diamond keeps the Switch positions: A shows on its right, while the button labelled A is at the bottom. Keyboard prompts leave the diamond untouched. Fixed tutorial animations keep the Switch images.
|
||||
|
||||
The prompts are part of the game's fonts, textures and messages, so they follow the layout and not the bindings. A button remapped with a `bind_` line or on the Controls tab keeps the prompt the game draws for it, which then names the wrong button. Use the Xbox prompts with the default bindings.
|
||||
The prompts are part of the game's fonts and textures. The game reads its UI archive and fonts once at boot, so the prompts change at the next start, also after `pad_layout` changes between `nintendo` and the others (the menu says *prompts change after restart*); the buttons change at once. Under that note the menu offers *Restart now*: after a confirmation it writes `totk.ini`, closes the game as *Quit game* does and starts the game again with the same command line. With `prompts = xbox` the game loads `romfs-patches-xbox` (`pad_layout = nintendo`) or `romfs-patches-xbox-label` (`xbox`, `auto`), and with `prompts = keyboard` it loads `romfs-patches-keyboard` next to `romfs-index.bin` in place of `romfs-patches` (`runtime/romfs.c`, `overlay_init`; `runtime/input_map.c`, `input_prompts_folder`): the same 60 fps patches, with the prompts drawn from the game's own files by `tools/make_romfs_patches.py --prompts xbox`, `--prompts xbox-label` or `--prompts keyboard` (`tools/button_icons.py`), which the setup runs next to the plain one:
|
||||
|
||||
The game reads its UI archive and fonts once at boot, so the prompts change at the next start, also after `pad_layout` changes between `nintendo` and the others (the menu says *prompts change after restart*); the buttons change at once. Under that note the menu offers *Restart now*: after a confirmation it writes `totk.ini`, closes the game as *Quit game* does and starts the game again with the same command line, which waits until the old process is gone before it opens the log and the saves (`runtime/gfx_present.c`, `gfx_window_closed`; `plat_restart` and `plat_restart_wait` in `runtime/platform/win32.c` and `linux.c`). Progress since the last save is lost, as with *Quit game*; while the game writes a save, the confirmation waits. With `prompts = xbox` the game loads `romfs-patches-xbox` (`pad_layout = nintendo`) or `romfs-patches-xbox-label` (`xbox`, `auto`) next to `romfs-index.bin` in place of `romfs-patches` (`runtime/romfs.c`, `overlay_init`; `runtime/input_map.c`, `input_prompts_folder`): the same 60 fps patches, with the prompts drawn from the game's own files by `tools/make_romfs_patches.py --prompts xbox` or `--prompts xbox-label` (`tools/button_icons.py`, after MacNativeGames' `make_button_icons.py`), which the setup runs next to the plain one:
|
||||
- every prompt but the button guide in play is text in the button font, `scft/External.bfotf` in each `Font/Font*.Nin_NX_NVN.bfarc.zs`. fontTools writes the font again, so the archives grow and the folder also carries the resource size table (`System/Resource/ResourceSizeTable.Product.<n>...rsizetable.zs`) with their entries raised by as much; without that the game stops at start.
|
||||
- the button guide in play draws L and ZL from the images `Nt_KeyTexL_00` and `Nt_KeyTexZL_00` of the Common archive's `timg/__Combined.bntx`, painted with the same icons (keyboard prompts also paint `Nt_KeyTexA/B/X/Y_00` and `Nt_KeyTexLSt_00/01`).
|
||||
|
||||
- every prompt but the button guide in play is text in the button font, `scft/External.bfotf` in each `Font/Font*.Nin_NX_NVN.bfarc.zs`: glyphs U+E040 to U+E043 (A B X Y), U+E044 to U+E047 (L R ZL ZR), U+E04B and U+E04C (Plus, Minus), U+E049 and U+E060 to U+E065 (the d-pad). fontTools writes the font again, so the archives grow and the folder also carries the resource size table (`System/Resource/ResourceSizeTable.Product.<n>...rsizetable.zs`) with their entries raised by as much; without that the game stops at start.
|
||||
- the button guide in play draws L and ZL from the images `Nt_KeyTexL_00` and `Nt_KeyTexZL_00` of the Common archive's `timg/__Combined.bntx`, painted with the same icons. The action prompt draws its button from `Nt_KeyTexA/B/X/Y_00`, whose image data is swapped for `nintendo`, and the diamond's letters are text the game takes from `LayoutMsg/DoCommand_00.msbt` of each language's `Mals` archive (and the layout's own text panes behind it), swapped the same way.
|
||||
|
||||
The Switch prompts stay, with a note under *Button prompts* and a `RomFS overlay: ...` line in the log, when the folder is missing (an install made by an older setup, or one where the setup could not prepare it and showed a warning; running the setup again on the install folder writes it), when it fails its check, with a game resolution of another shape than 16:9 (its own UI layout wins; 16:10 and 21:9), and when the `romfs_mods` folder replaces one of its files: `romfs-patches` then loads in its place, where two overlays replacing one file would stop the game. In a source build, `.venv-py314\Scripts\python tools\make_romfs_patches.py --version <ver> --prompts xbox` (and `--prompts xbox-label`) writes `build\<ver>\romfs-patches-xbox` (`-label`); it needs fontTools from `tools/requirements-py314.txt`. `--xbox-prompts` is the older name of `--prompts xbox`.
|
||||
The Switch prompts stay, with a note under *Button prompts* and a `RomFS overlay: ...` line in the log, when the folder is missing (an install made by an older setup, or one where the setup could not prepare it and showed a warning; running the setup again on the install folder writes it), when it fails its check, with a game resolution of another shape than 16:9 (its own UI layout wins; 16:10 and 21:9), and when the `romfs_mods` folder replaces one of its files: `romfs-patches` then loads in its place. In a source build, `.venv-py314\Scripts\python tools\make_romfs_patches.py --version <ver> --prompts keyboard` (or `xbox`, `xbox-label`) writes `build\<ver>\romfs-patches-<style>`; it needs fontTools from `tools/requirements-py314.txt`. `--xbox-prompts` is the older name of `--prompts xbox`.
|
||||
|
||||
## Window keys
|
||||
|
||||
|
||||
+3
-3
@@ -67,9 +67,9 @@ loads the saves 1.4.3 wrote.
|
||||
`totk.exe --prepare-shaders`) in the install folder, then fold the shader cache's loose files (four per shader, about
|
||||
117,000) into `shader-cache/shader-cache.pack` (`tools/pack_shader_cache.py`). totk.exe reads the pack first and
|
||||
writes the translations it makes in play as loose files; running the tool again with the game closed folds those in.
|
||||
The patches are written three times: `romfs-patches`, and `romfs-patches-xbox` and `romfs-patches-xbox-label`
|
||||
(`--prompts`: Xbox button prompts for `prompts = xbox`, the first for `pad_layout = nintendo`, the second for `xbox`
|
||||
and `auto`). The two Xbox folders are optional: when one cannot be made, the setup warns and finishes.
|
||||
The patches are written four times: `romfs-patches`, `romfs-patches-xbox`, `romfs-patches-xbox-label` and
|
||||
`romfs-patches-keyboard` (`--prompts`: button prompts for `prompts = xbox` or `prompts = keyboard`). The extra
|
||||
prompt folders are optional: when one cannot be made, the setup warns and finishes.
|
||||
When the graphics driver crashes while building the shaders, `prepare_shaders.py` runs that part again without the
|
||||
variants it was building ([building.md](building.md), step 6), and the setup finishes with a warning that names how
|
||||
many were left out.
|
||||
|
||||
+10
-8
@@ -7,7 +7,7 @@ recomp/gen.py, tools/build_game.py, tools/make_romfs_patches.py and tools/prepar
|
||||
with the bundled clang (hostcheck.c).
|
||||
|
||||
Install folder: exefs/, romfs/ (romfs.bin, index.json), profile/, shared-fonts/ (Chinese and Korean text), totk.exe
|
||||
and its DLLs, nvngx_dlss.dll (with --dlss), romfs-index.bin, romfs-patches/, romfs-patches-xbox/, romfs-patches-xbox-label/, totk_xlate.exe, the
|
||||
and its DLLs, nvngx_dlss.dll (with --dlss), romfs-index.bin, romfs-patches/, romfs-patches-xbox/, romfs-patches-xbox-label/, romfs-patches-keyboard/, totk_xlate.exe, the
|
||||
shader state of totk.exe --prepare-shaders, totk.ini, totk-install.json.
|
||||
Temp folder (<temp>/<dest name>-<hash>): check/ (ExeFS of both inputs, the host check), unpacked/ (the NSP of an NSZ
|
||||
or XCZ input until the extraction is done), gen/ (C), build/ (objects), logs/. The cleanup keeps gen/, build/ and
|
||||
@@ -1063,18 +1063,20 @@ class Prepare(Step):
|
||||
dest / f'romfs-patches-{size}', '--aspect', size])
|
||||
if code:
|
||||
raise InstallError('prepare.romfs_patches_failed', code=code, log=str(log))
|
||||
# The same with Xbox button icons in the prompts, for prompts = xbox (make_romfs_patches.py --prompts):
|
||||
# romfs-patches-xbox for the Nintendo pad layout, romfs-patches-xbox-label for xbox and auto (runtime/input_map.c
|
||||
# input_prompts_folder). Both are written on every install, so the menu choices work without another run of
|
||||
# the setup. Optional: when one fails, the game keeps the Switch prompts with that layout and the menu says the
|
||||
# files are missing, so the setup warns once and goes on.
|
||||
# The same with Xbox or keyboard button icons in the prompts (make_romfs_patches.py --prompts):
|
||||
# romfs-patches-xbox for the Nintendo pad layout, romfs-patches-xbox-label for xbox and auto,
|
||||
# romfs-patches-keyboard for keyboard and mouse (runtime/input_map.c input_prompts_folder). All are written on
|
||||
# every install, so the menu choices work without another run of the setup. Optional: when one fails, the game
|
||||
# keeps the Switch prompts with that layout and the menu says the files are missing, so the setup warns once
|
||||
# and goes on.
|
||||
failed = None
|
||||
for style in ('xbox', 'xbox-label'):
|
||||
for style in ('xbox', 'xbox-label', 'keyboard'):
|
||||
out = dest / f'romfs-patches-{style}'
|
||||
remove(out)
|
||||
extra = ['--ini', dest / 'totk.ini'] if style == 'keyboard' and (dest / 'totk.ini').is_file() else []
|
||||
code, log = run_tool(ctx, f'prepare-{style}', layout.tools / 'make_romfs_patches.py',
|
||||
['--version', version_tag(game_version(ctx)), '--romfs', dest / 'romfs', '--out', out,
|
||||
'--prompts', style])
|
||||
'--prompts', style, *extra])
|
||||
if code:
|
||||
remove(out)
|
||||
failed = failed or (code, log)
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"warn.npdm_differs": "main.npdm differs from the reference {version} dump; only the code modules are used.",
|
||||
"warn.cheats_skipped": "The cheats of the mods folder that change the game's code could not be built; the game installs without them. Details: {log}",
|
||||
"warn.exefs_skipped": "The mods' ExeFS patches (.ips, .pchtxt) could not be built; the game installs without them. Details: {log}",
|
||||
"warn.xbox_prompts_failed": "The Xbox button prompts could not be prepared (exit code {code}); the game keeps the Switch prompts. Details: {log}",
|
||||
"warn.xbox_prompts_failed": "The Xbox or keyboard button prompts could not be prepared (exit code {code}); the game keeps the Switch prompts. Details: {log}",
|
||||
"warn.shaders_skipped": "The graphics driver crashed while building some shaders, so {count} were left out of the preparation. The game builds them when it first needs them, and the driver may crash there too; a driver update may fix it. Details: {log}",
|
||||
"warn.kernel_old": "Linux {version} lacks the write tracking the game relies on (6.7 or later); the game may run very slowly. Updating the system fixes this.",
|
||||
"warn.dlss_kept": "A DLSS library is already in the install folder ({path}); the setup keeps it.",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"warn.cheats_skipped": "Não foi possível compilar os cheats da pasta de mods que alteram o código do jogo; o jogo é instalado sem eles. Detalhes: {log}",
|
||||
"warn.exefs_skipped": "Não foi possível compilar os patches ExeFS dos mods (.ips, .pchtxt); o jogo é instalado sem eles. Detalhes: {log}",
|
||||
"warn.npdm_differs": "O main.npdm difere do dump de referência da versão {version}; só os módulos de código são usados.",
|
||||
"warn.xbox_prompts_failed": "Não foi possível preparar os ícones de botão do Xbox (código de saída {code}); o jogo mantém os ícones do Switch. Detalhes: {log}",
|
||||
"warn.xbox_prompts_failed": "Não foi possível preparar os ícones de botão do Xbox ou do teclado (código de saída {code}); o jogo mantém os ícones do Switch. Detalhes: {log}",
|
||||
"warn.shaders_skipped": "O driver gráfico travou ao compilar alguns shaders, então {count} ficaram fora da preparação. O jogo os compila quando precisar deles pela primeira vez, e o driver pode travar ali também; uma atualização do driver pode resolver. Detalhes: {log}",
|
||||
"warn.kernel_old": "O Linux {version} não tem o rastreamento de escrita de que o jogo depende (6.7 ou mais novo); o jogo pode rodar muito devagar. Atualizar o sistema resolve.",
|
||||
"warn.dlss_kept": "Já existe uma biblioteca DLSS na pasta de instalação ({path}); a instalação a mantém.",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"warn.npdm_differs": "main.npdm отличается от эталонного дампа {version}; используются только модули с кодом.",
|
||||
"warn.cheats_skipped": "Не удалось собрать читы из папки модов, меняющие код игры; игра установится без них. Подробности: {log}",
|
||||
"warn.exefs_skipped": "Не удалось собрать ExeFS-патчи модов (.ips, .pchtxt); игра установится без них. Подробности: {log}",
|
||||
"warn.xbox_prompts_failed": "Не удалось подготовить подсказки кнопок Xbox (код выхода {code}); игра оставит подсказки Switch. Подробности: {log}",
|
||||
"warn.xbox_prompts_failed": "Не удалось подготовить подсказки кнопок Xbox или клавиатуры (код выхода {code}); игра оставит подсказки Switch. Подробности: {log}",
|
||||
"warn.shaders_skipped": "Графический драйвер аварийно завершился при сборке части шейдеров, поэтому {count} из них пропущены при подготовке. Игра соберёт их, когда они впервые понадобятся, и драйвер может упасть и там; обновление драйвера может это исправить. Подробности: {log}",
|
||||
"warn.kernel_old": "В Linux {version} нет отслеживания записи, на которое опирается игра (нужна версия 6.7 или новее); игра может работать очень медленно. Обновление системы это исправит.",
|
||||
"warn.dlss_kept": "В папке установки уже есть библиотека DLSS ({path}); установка её оставляет.",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"warn.cheats_skipped": "无法构建 mods 文件夹中修改游戏代码的金手指;游戏将不含它们进行安装。详情:{log}",
|
||||
"warn.exefs_skipped": "无法构建模组的 ExeFS 补丁(.ips、.pchtxt);游戏将不含它们进行安装。详情:{log}",
|
||||
"warn.npdm_differs": "main.npdm 与参考的 {version} 导出不同;只使用其中的代码模块。",
|
||||
"warn.xbox_prompts_failed": "无法准备 Xbox 按键提示(退出代码 {code});游戏将继续使用 Switch 按键提示。详情:{log}",
|
||||
"warn.xbox_prompts_failed": "无法准备 Xbox 或键盘按键提示(退出代码 {code});游戏将继续使用 Switch 按键提示。详情:{log}",
|
||||
"warn.shaders_skipped": "显卡驱动在编译部分着色器时崩溃,因此有 {count} 个着色器未被预先准备。游戏会在首次需要时编译它们,驱动在那时也可能崩溃;更新驱动可能会解决。详情:{log}",
|
||||
"warn.kernel_old": "Linux {version} 缺少游戏依赖的写入跟踪功能(需要 6.7 或更新版本);游戏可能运行得很慢。更新系统即可解决。",
|
||||
"warn.dlss_kept": "安装文件夹中已有 DLSS 库({path});安装程序将保留它。",
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@ static const CfgMeta metas[] = {
|
||||
{"VOLUME", "100", "0..100", LIVE}, {"AUDIO_CHANNELS", "stereo", "auto|stereo|5.1", RESTART},
|
||||
{"MOTION", "1", "0|1", LIVE}, {"RUMBLE_STRENGTH", "130", "0..300", LIVE}, {"RUMBLE_CURVE", "0.6", "0.2..1.5", LIVE},
|
||||
{"RUMBLE_TRIGGERS", "0", "0|1", LIVE}, {"PAD_LAYOUT", "auto", "auto|nintendo|xbox", LIVE},
|
||||
{"PROMPTS", "nintendo", "nintendo|xbox", RESTART},
|
||||
{"PROMPTS", "nintendo", "nintendo|xbox|keyboard", RESTART},
|
||||
{"STICK_DEADZONE", "8", "0..50", LIVE}, {"STICK_SENS", "1", "0.1..3", RESTART},
|
||||
{"LSTICK_DEADZONE", "", "0..50", LIVE}, {"RSTICK_DEADZONE", "", "0..50", LIVE},
|
||||
{"MOUSE_MODE", "stick", "stick|direct", LIVE},
|
||||
|
||||
@@ -303,6 +303,9 @@ int probe_cfg(void) {
|
||||
"prompts: the game's own unless set to Xbox");
|
||||
CHECK(input_prompts_pick(NULL, "xbox_prompts") == PROMPTS_XBOX && input_prompts_pick("nintendo", "xbox_prompts") == PROMPTS_NINTENDO,
|
||||
"prompts: 0.1.9's pad_layout = xbox_prompts means Xbox while prompts is unset");
|
||||
CHECK(input_prompts_pick("keyboard", "nintendo") == PROMPTS_KEYBOARD &&
|
||||
input_prompts_pick("Keyboard", "xbox") == PROMPTS_KEYBOARD,
|
||||
"prompts: keyboard returns PROMPTS_KEYBOARD");
|
||||
CHECK(!input_prompts_folder(PROMPTS_NINTENDO, "nintendo") && !input_prompts_folder(PROMPTS_NINTENDO, "xbox"),
|
||||
"prompts: the game's own need no folder");
|
||||
CHECK(EQ(input_prompts_folder(PROMPTS_XBOX, "nintendo"), "romfs-patches-xbox") &&
|
||||
@@ -311,6 +314,13 @@ int probe_cfg(void) {
|
||||
EQ(input_prompts_folder(PROMPTS_XBOX, "auto"), "romfs-patches-xbox-label") &&
|
||||
EQ(input_prompts_folder(PROMPTS_XBOX, NULL), "romfs-patches-xbox-label"),
|
||||
"prompts: the face buttons by position for the Nintendo layout, by label for xbox and auto");
|
||||
CHECK(EQ(input_prompts_folder(PROMPTS_KEYBOARD, "nintendo"), "romfs-patches-keyboard") &&
|
||||
EQ(input_prompts_folder(PROMPTS_KEYBOARD, "xbox"), "romfs-patches-keyboard") &&
|
||||
EQ(input_prompts_folder(PROMPTS_KEYBOARD, NULL), "romfs-patches-keyboard"),
|
||||
"prompts: keyboard prompts use romfs-patches-keyboard for every layout");
|
||||
const CfgMeta *pmeta = cfg_meta("PROMPTS");
|
||||
CHECK(pmeta && pmeta->values && strstr(pmeta->values, "keyboard"),
|
||||
"cfg_meta PROMPTS values include keyboard");
|
||||
// romfs-patches-xbox shows A as B (tests/romfs_prompts_test.py checks the folders' contents). It is right exactly
|
||||
// where the layout puts A on the east button, which an Xbox pad labels B.
|
||||
input_map_reset();
|
||||
|
||||
+5
-1
@@ -289,11 +289,15 @@ int input_pad_layout_pick(const char *setting, int east_labelled_a) {
|
||||
}
|
||||
|
||||
int input_prompts_pick(const char *prompts, const char *pad_layout) {
|
||||
if (prompts && *prompts) return !strcasecmp(prompts, "xbox") ? PROMPTS_XBOX : PROMPTS_NINTENDO;
|
||||
if (prompts && *prompts) {
|
||||
if (!strcasecmp(prompts, "keyboard")) return PROMPTS_KEYBOARD;
|
||||
return !strcasecmp(prompts, "xbox") ? PROMPTS_XBOX : PROMPTS_NINTENDO;
|
||||
}
|
||||
return pad_layout && !strcasecmp(pad_layout, "xbox_prompts") ? PROMPTS_XBOX : PROMPTS_NINTENDO;
|
||||
}
|
||||
|
||||
const char *input_prompts_folder(int prompts, const char *pad_layout) {
|
||||
if (prompts == PROMPTS_KEYBOARD) return "romfs-patches-keyboard";
|
||||
if (prompts != PROMPTS_XBOX) return NULL;
|
||||
// The folders are read at start, before a pad is open: auto counts as Xbox, as for a pad without Nintendo labels.
|
||||
// A Nintendo-labelled pad under auto plays by label too (its A is on the button labelled A), so it matches as well.
|
||||
|
||||
+5
-3
@@ -87,15 +87,17 @@ int input_pad_layout_pick(const char *setting, int east_labelled_a);
|
||||
|
||||
// Button prompts (TOTK_PROMPTS): the controller whose buttons the game's prompts show. Nintendo: the game's own. Xbox:
|
||||
// LB RB LT RT, Menu, View and a d-pad cross for the other buttons, and the face buttons named by the label of the
|
||||
// button the pad layout presses (runtime/romfs.c overlay_init, tools/make_romfs_patches.py --prompts).
|
||||
enum { PROMPTS_NINTENDO, PROMPTS_XBOX };
|
||||
// The prompts for a TOTK_PROMPTS value ("nintendo" or "xbox", any case). Unset or empty: Xbox while the pad layout
|
||||
// button the pad layout presses (runtime/romfs.c overlay_init, tools/make_romfs_patches.py --prompts). Keyboard:
|
||||
// keycaps for the player's keys and mouse icons for clicks and camera look (prompts = keyboard).
|
||||
enum { PROMPTS_NINTENDO, PROMPTS_XBOX, PROMPTS_KEYBOARD };
|
||||
// The prompts for a TOTK_PROMPTS value ("nintendo", "xbox" or "keyboard", any case). Unset or empty: Xbox while the pad layout
|
||||
// setting is "xbox_prompts", else Nintendo.
|
||||
int input_prompts_pick(const char *prompts, const char *pad_layout);
|
||||
// The folder next to romfs-index.bin that holds those prompts for a TOTK_PAD_LAYOUT value, NULL for the game's own. The
|
||||
// Nintendo layout plays an Xbox pad by position, so its folder names the face buttons the other way round
|
||||
// (romfs-patches-xbox: the right button does A and is labelled B); xbox and auto put every Switch button on the button
|
||||
// labelled with its letter (auto picks the layout per pad by its labels), so they share romfs-patches-xbox-label.
|
||||
// Keyboard and mouse prompts use romfs-patches-keyboard.
|
||||
const char *input_prompts_folder(int prompts, const char *pad_layout);
|
||||
// down[pad] != 0 for the gamepad inputs held. Returns the Switch button bits (0..15) the layout's pad table presses.
|
||||
uint64_t input_map_pad_buttons(const uint8_t down[PAD_COUNT], int layout);
|
||||
|
||||
+12
-5
@@ -750,14 +750,14 @@ static void camera_overlay(void) {
|
||||
// The button prompts' folders (input_prompts_folder) and whether each had a manifest next to romfs-index.bin at start;
|
||||
// the one the settings asked for at start, the one overlay_init loaded (NULL: the game's own prompts) and why the first
|
||||
// did not load (ROMFS_PROMPTS_*). The menu compares them with the settings in effect (ui/menu.cpp prompts_note).
|
||||
static struct { const char *name; int ready; } prompts_folders[2];
|
||||
static struct { const char *name; int ready; } prompts_folders[3];
|
||||
static const char *prompts_wanted, *prompts_loaded;
|
||||
static int prompts_off;
|
||||
|
||||
const char *romfs_prompts(void) { return prompts_loaded; }
|
||||
|
||||
int romfs_prompts_ready(const char *folder) {
|
||||
for (int i = 0; i < 2; i++)
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (folder && prompts_folders[i].name && !strcmp(prompts_folders[i].name, folder)) return prompts_folders[i].ready;
|
||||
return 0;
|
||||
}
|
||||
@@ -820,6 +820,10 @@ static void overlay_init(void) {
|
||||
snprintf(path, sizeof path, "%s/%s/manifest.tsv", index_dir, prompts_folders[i].name) < (int)sizeof path &&
|
||||
plat_file_exists(path);
|
||||
}
|
||||
prompts_folders[2].name = input_prompts_folder(PROMPTS_KEYBOARD, NULL);
|
||||
prompts_folders[2].ready =
|
||||
snprintf(path, sizeof path, "%s/%s/manifest.tsv", index_dir, prompts_folders[2].name) < (int)sizeof path &&
|
||||
plat_file_exists(path);
|
||||
const char *layout = cfg_value("TOTK_PAD_LAYOUT");
|
||||
prompts_wanted = input_prompts_folder(input_prompts_pick(cfg_get("TOTK_PROMPTS"), layout), layout);
|
||||
prompts_off = !prompts_wanted ? ROMFS_PROMPTS_OK
|
||||
@@ -829,17 +833,19 @@ static void overlay_init(void) {
|
||||
: ROMFS_PROMPTS_OK;
|
||||
restbl_find();
|
||||
size_t first = noverlay;
|
||||
int is_kb = prompts_wanted && !strcmp(prompts_wanted, "romfs-patches-keyboard");
|
||||
if (prompts_wanted && prompts_off == ROMFS_PROMPTS_OK) {
|
||||
snprintf(path, sizeof path, "%s/%s", index_dir, prompts_wanted); // fits: its manifest path did
|
||||
if (overlay_load(path, 1, 0)) {
|
||||
prompts_loaded = prompts_wanted;
|
||||
rt_log("RomFS overlay: Xbox button prompts (%s)", path);
|
||||
rt_log("RomFS overlay: %s button prompts (%s)", is_kb ? "keyboard and mouse" : "Xbox", path);
|
||||
} else {
|
||||
prompts_off = ROMFS_PROMPTS_FAILED;
|
||||
rt_log("RomFS overlay: %s failed its check (run the setup again); romfs-patches loads instead", path);
|
||||
}
|
||||
} else if (prompts_wanted) {
|
||||
rt_log("RomFS overlay: prompts = xbox, but %s; Switch prompts stay",
|
||||
rt_log("RomFS overlay: prompts = %s, but %s; Switch prompts stay",
|
||||
is_kb ? "keyboard" : "xbox",
|
||||
prompts_off == ROMFS_PROMPTS_SHAPE ? "the game resolution takes its own UI layout"
|
||||
: prompts_off == ROMFS_PROMPTS_PATCHES ? "TOTK_ROMFS_PATCHES is set"
|
||||
: "their folder has no manifest.tsv (run the setup again)");
|
||||
@@ -856,7 +862,8 @@ static void overlay_init(void) {
|
||||
mods_overlay_init();
|
||||
const char *clash = prompts_loaded ? overlay_clash(first, prompts_end, mods_first, noverlay) : NULL;
|
||||
if (clash) {
|
||||
rt_log("RomFS overlay: a RomFS mod replaces %s, which the Xbox prompts change too; Switch prompts stay", clash);
|
||||
rt_log("RomFS overlay: a RomFS mod replaces %s, which %s change too; Switch prompts stay", clash,
|
||||
is_kb ? "the keyboard and mouse prompts" : "the Xbox prompts");
|
||||
overlay_drop(first, prompts_end);
|
||||
prompts_loaded = NULL;
|
||||
prompts_off = ROMFS_PROMPTS_MODS;
|
||||
|
||||
+2
-2
@@ -827,10 +827,10 @@ static void tab_controls(float scale) {
|
||||
opt_begin("##controls", scale);
|
||||
static const UiTextId layout[] = {UI_MENU_PAD_LAYOUT_AUTO, UI_MENU_PAD_LAYOUT_NINTENDO, UI_MENU_PAD_LAYOUT_XBOX};
|
||||
opt_choice("TOTK_PAD_LAYOUT", UI_MENU_PAD_LAYOUT, layout, 3, UI_MENU_PAD_LAYOUT_DESC, pad_layout_note());
|
||||
static const UiTextId prompts[] = {UI_MENU_PROMPTS_NINTENDO, UI_MENU_PROMPTS_XBOX};
|
||||
static const UiTextId prompts[] = {UI_MENU_PROMPTS_NINTENDO, UI_MENU_PROMPTS_XBOX, UI_MENU_PROMPTS_KEYBOARD};
|
||||
bool restart;
|
||||
const char *note = prompts_note(&restart);
|
||||
opt_choice("TOTK_PROMPTS", UI_MENU_PROMPTS, prompts, 2, UI_MENU_PROMPTS_DESC, note);
|
||||
opt_choice("TOTK_PROMPTS", UI_MENU_PROMPTS, prompts, 3, UI_MENU_PROMPTS_DESC, note);
|
||||
bool ask = restart && opt_button("##restart", UI_MENU_RESTART, UI_MENU_RESTART_DESC);
|
||||
opt_toggle("TOTK_MOTION", UI_MENU_MOTION, UI_MENU_MOTION_DESC);
|
||||
opt_slider("TOTK_RUMBLE_STRENGTH", UI_MENU_RUMBLE_STRENGTH, 0, 300, UI_MENU_RUMBLE_STRENGTH_DESC, 10, "%.0f %%");
|
||||
|
||||
+11
-4
@@ -241,10 +241,12 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_PROMPTS_DESC] = "Which controller's buttons the game's prompts show. Nintendo: the game's own. Xbox: LB, "
|
||||
"RB, LT, RT, Menu, View and a d-pad cross, and A, B, X and Y as labelled on the button "
|
||||
"the layout above presses (with Nintendo (A right) the right button is labelled B, so the "
|
||||
"prompts for A show B). The game reads its prompts at start, so they change after a "
|
||||
"prompts for A show B). Keyboard and mouse: keycaps for your keys and mouse icons for clicks "
|
||||
"and camera look. The game reads its prompts at start, so they change after a "
|
||||
"restart. They are part of the game's files, so a button you remap in Controls keeps its "
|
||||
"old prompt on screen.",
|
||||
[UI_MENU_PROMPTS_NINTENDO] = "Nintendo", [UI_MENU_PROMPTS_XBOX] = "Xbox",
|
||||
[UI_MENU_PROMPTS_KEYBOARD] = "Keyboard and mouse",
|
||||
[UI_MENU_PROMPTS_MISSING] = "prompt files missing: run the setup again",
|
||||
[UI_MENU_PROMPTS_FAILED] = "prompt files damaged: run the setup again",
|
||||
[UI_MENU_PROMPTS_RESTART] = "prompts change after restart",
|
||||
@@ -696,10 +698,12 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_PROMPTS_DESC] = "Кнопки какого контроллера показывают подсказки игры. Nintendo: как в самой игре. Xbox: "
|
||||
"LB, RB, LT, RT, Menu, View и крестовина, а A, B, X и Y подписаны так же, как кнопка, "
|
||||
"которую нажимает раскладка выше (в Nintendo (A справа) правая кнопка подписана B, и "
|
||||
"подсказки A показывают B). Игра читает подсказки при запуске, так что они меняются после "
|
||||
"подсказки A показывают B). Клавиатура и мышь: клавиши для назначенных кнопок и значки мыши "
|
||||
"для кликов и обзора. Игра читает подсказки при запуске, так что они меняются после "
|
||||
"перезапуска. Подсказки записаны в файлах игры, поэтому у кнопки, переназначенной на "
|
||||
"вкладке управления, на экране остаётся прежняя подсказка.",
|
||||
[UI_MENU_PROMPTS_NINTENDO] = "Nintendo", [UI_MENU_PROMPTS_XBOX] = "Xbox",
|
||||
[UI_MENU_PROMPTS_KEYBOARD] = "Клавиатура и мышь",
|
||||
[UI_MENU_PROMPTS_MISSING] = "нет файлов подсказок: запустите установку снова",
|
||||
[UI_MENU_PROMPTS_FAILED] = "файлы подсказок повреждены: запустите установку снова",
|
||||
[UI_MENU_PROMPTS_RESTART] = "подсказки сменятся после перезапуска",
|
||||
@@ -1325,11 +1329,13 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_PROMPTS_DESC] = "Os botões de qual controle os ícones do jogo mostram. Nintendo: os do próprio jogo. "
|
||||
"Xbox: LB, RB, LT, RT, Menu, Exibir e uma cruz direcional, e A, B, X e Y com a letra do "
|
||||
"botão que o layout acima aperta (com Nintendo (A à direita) o botão da direita tem a "
|
||||
"letra B, então os ícones de A mostram B). O jogo lê os ícones ao iniciar, então eles "
|
||||
"letra B, então os ícones de A mostram B). Teclado e mouse: teclas para as suas ações e "
|
||||
"ícones de mouse para cliques e câmera. O jogo lê os ícones ao iniciar, então eles "
|
||||
"mudam depois de reiniciar. Eles fazem parte dos arquivos do jogo, então um botão "
|
||||
"remapeado em Controles continua com o ícone antigo na tela.",
|
||||
[UI_MENU_PROMPTS_NINTENDO] = "Nintendo",
|
||||
[UI_MENU_PROMPTS_XBOX] = "Xbox",
|
||||
[UI_MENU_PROMPTS_KEYBOARD] = "Teclado e mouse",
|
||||
[UI_MENU_PROMPTS_MISSING] = "faltam os arquivos dos ícones: execute a instalação de novo",
|
||||
[UI_MENU_PROMPTS_FAILED] = "arquivos dos ícones danificados: execute a instalação de novo",
|
||||
[UI_MENU_PROMPTS_RESTART] = "os ícones mudam após reiniciar",
|
||||
@@ -1923,9 +1929,10 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_PROMPTS] = "按键提示",
|
||||
[UI_MENU_PROMPTS_DESC] = "游戏的按键提示显示哪种手柄的按键。Nintendo:游戏原有的提示。Xbox:LB、RB、LT、RT、Menu、View 和十字键,以及上方布局所按下的按键上标注的 "
|
||||
"A、B、X、Y(使用 Nintendo(A 在右)时右侧按键标为 B,因此 A 的提示显示 "
|
||||
"B)。游戏在启动时读取按键提示,因此重启后才会改变。提示属于游戏文件,在“操作”中重新映射的按键在屏幕上仍显示原来的提示。",
|
||||
"B)。键盘和鼠标:按键显示对应键帽,鼠标点击与视角移动显示鼠标图标。游戏在启动时读取按键提示,因此重启后才会改变。提示属于游戏文件,在“操作”中重新映射的按键在屏幕上仍显示原来的提示。",
|
||||
[UI_MENU_PROMPTS_NINTENDO] = "Nintendo",
|
||||
[UI_MENU_PROMPTS_XBOX] = "Xbox",
|
||||
[UI_MENU_PROMPTS_KEYBOARD] = "键盘和鼠标",
|
||||
[UI_MENU_PROMPTS_MISSING] = "缺少按键提示文件:请重新运行安装程序",
|
||||
[UI_MENU_PROMPTS_FAILED] = "按键提示文件已损坏:请重新运行安装程序",
|
||||
[UI_MENU_PROMPTS_RESTART] = "按键提示重启后生效",
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ typedef enum {
|
||||
UI_MENU_RUMBLE_TRIGGERS, UI_MENU_RUMBLE_TRIGGERS_DESC,
|
||||
UI_MENU_PAD_LAYOUT, UI_MENU_PAD_LAYOUT_DESC, UI_MENU_PAD_LAYOUT_AUTO, UI_MENU_PAD_LAYOUT_NINTENDO,
|
||||
UI_MENU_PAD_LAYOUT_XBOX, UI_MENU_PAD_LAYOUT_IN_USE,
|
||||
UI_MENU_PROMPTS, UI_MENU_PROMPTS_DESC, UI_MENU_PROMPTS_NINTENDO, UI_MENU_PROMPTS_XBOX,
|
||||
UI_MENU_PROMPTS, UI_MENU_PROMPTS_DESC, UI_MENU_PROMPTS_NINTENDO, UI_MENU_PROMPTS_XBOX, UI_MENU_PROMPTS_KEYBOARD,
|
||||
// the notes under the prompts: their files are missing or damaged, they change after restart, or why they are off
|
||||
UI_MENU_PROMPTS_MISSING, UI_MENU_PROMPTS_FAILED, UI_MENU_PROMPTS_RESTART, UI_MENU_PROMPTS_OFF_SHAPE,
|
||||
UI_MENU_PROMPTS_OFF_MODS, UI_MENU_PROMPTS_OFF_PATCHES,
|
||||
|
||||
@@ -294,7 +294,9 @@ BUTTONS = {cp: (600 + 4 * (cp - 0xE040), lambda pen, i=cp - 0xE040: rect(pen, 20
|
||||
LETTERS_1 = {ord('L'): (560, polygon((60, 0), (480, 0), (480, 100), (160, 100), (160, 700), (60, 700))),
|
||||
ord('B'): (560, lambda pen: [rect(pen, 60, 0, 460, 700), rect(pen, 160, 100, 360, 300, True),
|
||||
rect(pen, 160, 400, 360, 600, True)]),
|
||||
ord('R'): (560, lambda pen: [rect(pen, 60, 0, 460, 700), rect(pen, 160, 400, 360, 600, True)])}
|
||||
ord('R'): (560, lambda pen: [rect(pen, 60, 0, 460, 700), rect(pen, 160, 400, 360, 600, True)]),
|
||||
**{c: (560, lambda pen: rect(pen, 60, 100, 460, 600)) for c in range(32, 127)
|
||||
if c not in (ord('L'), ord('B'), ord('R'), ord('T'))}}
|
||||
LETTERS_2 = {ord('T'): (560, polygon((230, 0), (330, 0), (330, 600), (520, 600), (520, 700), (40, 700), (40, 600), (230, 600)))}
|
||||
|
||||
|
||||
@@ -317,6 +319,8 @@ class Romfs:
|
||||
images.update({'Nt_KeyTexL_00^z': (l_format, size, rnd.randbytes(IMAGE)),
|
||||
'Nt_KeyTexZL_00': (R8_UNORM, size, rnd.randbytes(IMAGE)),
|
||||
'Nt_KeyTexLSt_00': (R8_UNORM, size, rnd.randbytes(IMAGE))})
|
||||
if version == 'v143':
|
||||
images['Nt_KeyTexLSt_01'] = (R8_UNORM, size, rnd.randbytes(IMAGE))
|
||||
self.text_fonts = [bfotf(cff_font('Text1', LETTERS_1), 0x01020304), bfotf(cff_font('Text2', LETTERS_2), 0x0a0b0c0d)]
|
||||
default = {'scft/External.bfotf': bfotf(cff_font('External', BUTTONS), 0x5a17c3e9), 'fnt/rest.bin': rnd.randbytes(700)}
|
||||
if text_fonts:
|
||||
@@ -388,7 +392,7 @@ class PromptsTest(unittest.TestCase):
|
||||
cls.romfs = {v: Romfs(cls.base / v / 'romfs', v) for v in VERSIONS}
|
||||
cls.outs = {}
|
||||
for v, romfs in cls.romfs.items():
|
||||
for style in ('plain', 'xbox', 'xbox-label'):
|
||||
for style in ('plain', 'xbox', 'xbox-label', 'keyboard'):
|
||||
out = cls.base / v / style
|
||||
proc = romfs.run(out, *(() if style == 'plain' else ('--prompts', style)))
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
@@ -485,7 +489,7 @@ class PromptsTest(unittest.TestCase):
|
||||
self.assertEqual(after[member], before[member], member)
|
||||
|
||||
def test_resource_size_table_grows_with_the_fonts(self):
|
||||
for romfs, style, out, summary in self.each():
|
||||
for romfs, style, out, summary in self.each(('xbox', 'xbox-label', 'keyboard')):
|
||||
with self.subTest(version=romfs.version, style=style):
|
||||
table = restbl_sizes(romfs.read(out, romfs.restbl))
|
||||
original = restbl_sizes(romfs.contents[romfs.restbl])
|
||||
@@ -501,7 +505,7 @@ class PromptsTest(unittest.TestCase):
|
||||
self.assertEqual(set(table), set(original))
|
||||
|
||||
def test_manifest_lists_what_changed_with_both_hashes(self):
|
||||
for romfs, style, out, _ in self.each(('plain', 'xbox', 'xbox-label')):
|
||||
for romfs, style, out, _ in self.each(('plain', 'xbox', 'xbox-label', 'keyboard')):
|
||||
with self.subTest(version=romfs.version, style=style):
|
||||
expect = {romfs.common}
|
||||
if style != 'plain':
|
||||
@@ -513,6 +517,56 @@ class PromptsTest(unittest.TestCase):
|
||||
self.assertEqual((int(size), sha), (len(data), hashlib.sha256(data).hexdigest()), path)
|
||||
self.assertEqual(original, hashlib.sha256(romfs.files[path]).hexdigest(), path)
|
||||
|
||||
def test_keyboard_prompts_drawings_and_textures(self):
|
||||
for romfs, style, out, summary in self.each(('keyboard',)):
|
||||
with self.subTest(version=romfs.version):
|
||||
after = sarc_files(romfs.read(out, romfs.common))
|
||||
panes = pane_texts(after['blyt/DoCommand_00.bflyt'])
|
||||
self.assertEqual({b: panes[f'T_{b}_00'] for b in FACE}, {'A': 'A', 'B': 'B', 'X': 'X', 'Y': 'Y'})
|
||||
for font_path in (DEFAULT_FONT, REGIONAL_FONT):
|
||||
drawn = font(sarc_files(romfs.read(out, font_path))['scft/External.bfotf'])
|
||||
self.assertEqual(glyph(drawn, 0xE040)[1], 640) # A -> 'E' (640)
|
||||
self.assertEqual(glyph(drawn, 0xE041)[1], 880) # B -> 'Shift' (880)
|
||||
self.assertEqual(glyph(drawn, 0xE042)[1], 880) # X -> 'Space' (880)
|
||||
self.assertEqual(glyph(drawn, 0xE043)[1], 700) # Y -> mouse_l (700)
|
||||
self.assertEqual(glyph(drawn, 0xE044)[1], 640) # L -> 'Q' (640)
|
||||
self.assertEqual(glyph(drawn, 0xE046)[1], 700) # ZL -> mouse_r (700)
|
||||
self.assertEqual(glyph(drawn, 0xE05E)[1], 880) # LClick -> 'Ctrl' (880)
|
||||
self.assertEqual(glyph(drawn, 0xE050)[1], 880) # Stick -> 'WASD' (880)
|
||||
self.assertEqual(glyph(drawn, 0xE051)[1], 700) # Look -> mouse (700)
|
||||
self.assertEqual(glyph(drawn, 0xE068)[1], 700) # Look -> mouse (700)
|
||||
self.assertEqual(glyph(drawn, 0xE049)[1], 640) # All D-pad -> (640)
|
||||
self.assertEqual(glyph(drawn, 0xE064)[1], 640) # Up/Down -> (640)
|
||||
self.assertEqual(glyph(drawn, 0xE065)[1], 640) # Left/Right -> (640)
|
||||
bntx = bntx_images(after['timg/__Combined.bntx'])
|
||||
for name, cp in icons.HUD_KEYBOARD_TEXTURES.items():
|
||||
if name == 'Nt_KeyTexLSt_01' and romfs.version == 'v121':
|
||||
self.assertNotIn(name, bntx)
|
||||
else:
|
||||
self.assertIn(name, bntx)
|
||||
pixels = bntx[name][2]
|
||||
self.assertEqual(len(pixels), IMAGE)
|
||||
self.assertIn(255, pixels)
|
||||
|
||||
def test_keyboard_prompts_custom_ini(self):
|
||||
romfs = self.romfs['v121']
|
||||
ini_file = self.base / 'custom.ini'
|
||||
ini_file.write_text("""
|
||||
[input]
|
||||
bind_a = key:space
|
||||
bind_b = none
|
||||
bind_y = key:r
|
||||
bind_lstick_up = key:up
|
||||
""", encoding='utf-8')
|
||||
out = self.base / 'v121' / 'custom-keyboard'
|
||||
proc = romfs.run(out, '--prompts', 'keyboard', '--ini', str(ini_file))
|
||||
self.assertEqual(proc.returncode, 0, proc.stderr)
|
||||
drawn = font(sarc_files(romfs.read(out, DEFAULT_FONT))['scft/External.bfotf'])
|
||||
self.assertEqual(glyph(drawn, 0xE040)[1], 880) # Space (880)
|
||||
self.assertEqual(glyph(drawn, 0xE041)[1], 640) # none bar (640)
|
||||
self.assertEqual(glyph(drawn, 0xE043)[1], 640) # R (640)
|
||||
self.assertEqual(glyph(drawn, 0xE050)[1], 880) # Move (880)
|
||||
|
||||
def test_same_input_same_output_and_the_older_flag(self):
|
||||
romfs = self.romfs['v121']
|
||||
for flags in (('--prompts', 'xbox'), ('--xbox-prompts',)):
|
||||
|
||||
+1
-1
@@ -244,7 +244,7 @@ def copy_romfs_patches(version, out):
|
||||
menus' captured background turned black). Copies them when their manifest differs; romfs-patches-xbox and
|
||||
romfs-patches-xbox-label (the same with Xbox button prompts, make_romfs_patches.py --prompts) the same way when they
|
||||
were made."""
|
||||
for name in ('romfs-patches', 'romfs-patches-xbox', 'romfs-patches-xbox-label'):
|
||||
for name in ('romfs-patches', 'romfs-patches-xbox', 'romfs-patches-xbox-label', 'romfs-patches-keyboard'):
|
||||
src = ROOT / 'build' / version / name
|
||||
dst = out / name
|
||||
if src.resolve() == dst.resolve() or not (src / 'manifest.tsv').is_file():
|
||||
|
||||
+283
-10
@@ -1,25 +1,28 @@
|
||||
# Adapted from MacNativeGames games/totk/scripts/make_button_icons.py @ 933b6a0 (MIT, Copyright (c) 2026
|
||||
# MacNativeGames contributors).
|
||||
"""Xbox button icons for the game's prompts, drawn on the player's machine for tools/make_romfs_patches.py --prompts.
|
||||
The outlines are our own shapes with letters cut out of them in the game's own text fonts: nothing of the game is in
|
||||
the repository.
|
||||
"""Xbox and keyboard button icons for the game's prompts, drawn on the player's machine for
|
||||
tools/make_romfs_patches.py --prompts. The outlines are our own shapes with letters cut out of them in the game's own
|
||||
text fonts: nothing of the game is in the repository.
|
||||
|
||||
Every prompt except the button guide in play is text in the button font, scft/External.bfotf of each
|
||||
Font/Font*.Nin_NX_NVN.bfarc.zs: private-use code points from U+E040 (A, B, X, Y, then L, R, ZL, ZR, the d-pad, Plus and
|
||||
Minus, the sticks). A .bfotf is an OpenType (CFF) font after an 8-byte header; from offset 4 on, every byte is XORed
|
||||
with a 4-byte key, which the size in the header's second word gives away (bfotf_decode). redraw_font draws the shoulder
|
||||
buttons, the triggers, Plus, Minus and the d-pad as an Xbox controller names them (LB RB, LT RT, Menu and View, a
|
||||
cross), can swap the glyphs of the face buttons (A with B, X with Y), and has fontTools write the font again. The
|
||||
archive then grows by a kilobyte or two, more than the game's resource size table allows it: grow_restbl raises those
|
||||
entries (without it the game stops at start, MacNativeGames found).
|
||||
cross), can swap the glyphs of the face buttons (A with B, X with Y), or draws keycaps and mouse icons for keyboard
|
||||
and mouse prompts, and has fontTools write the font again. The archive then grows by a kilobyte or two, more than the
|
||||
game's resource size table allows it: grow_restbl raises those entries (without it the game stops at start,
|
||||
MacNativeGames found).
|
||||
|
||||
The button guide in play (bottom right) draws L and ZL from images of the common layout archive instead
|
||||
(timg/__Combined.bntx, Nt_KeyTexL_00 and Nt_KeyTexZL_00): bntx_redraw paints the same icons into them, the size
|
||||
unchanged. The sticks keep the Switch icons, and so do prompts drawn as fixed images (some tutorial animations).
|
||||
(timg/__Combined.bntx, Nt_KeyTexL_00 and Nt_KeyTexZL_00; keyboard prompts also redraw Nt_KeyTexA/B/X/Y_00 and
|
||||
Nt_KeyTexLSt_00/01): bntx_redraw paints the same icons into them, the size unchanged. The sticks keep the Switch
|
||||
icons, and so do prompts drawn as fixed images (some tutorial animations).
|
||||
|
||||
Needs fontTools (tools/requirements-installer.txt, tools/requirements-py314.txt).
|
||||
"""
|
||||
import io
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import zlib
|
||||
|
||||
@@ -37,6 +40,112 @@ TEXT_ARCHIVE = 'Font/Font.Nin_NX_NVN.bfarc.zs'
|
||||
TEXT_FONTS = ('scft/ninP_RodinNTLG-B_DH_003_subset.bfotf', 'scft/nintendo_NTLG-DB_DH_002.bfotf')
|
||||
# The button guide's images and the glyph each one shows.
|
||||
HUD_TEXTURES = {'Nt_KeyTexL_00': 0xE044, 'Nt_KeyTexZL_00': 0xE046}
|
||||
HUD_KEYBOARD_TEXTURES = {'Nt_KeyTexA_00': 0xE040, 'Nt_KeyTexB_00': 0xE041, 'Nt_KeyTexX_00': 0xE042, 'Nt_KeyTexY_00': 0xE043,
|
||||
'Nt_KeyTexL_00': 0xE044, 'Nt_KeyTexZL_00': 0xE046, 'Nt_KeyTexLSt_00': 0xE05E, 'Nt_KeyTexLSt_01': 0xE05E}
|
||||
HUD_KEYBOARD_OPTIONAL = frozenset({'Nt_KeyTexLSt_01'})
|
||||
|
||||
DEFAULT_KEYS = {
|
||||
'a': 'e', 'b': 'lshift', 'x': 'space', 'y': 'mouse_l', 'l': 'q', 'r': 'r', 'zl': 'mouse_r', 'zr': 'f',
|
||||
'plus': 'tab', 'minus': 'm', 'lclick': 'lctrl', 'rclick': 'c', 'left': '1', 'up': '2', 'right': '3', 'down': '4',
|
||||
'lstick_up': 'w', 'lstick_left': 'a', 'lstick_down': 's', 'lstick_right': 'd'
|
||||
}
|
||||
|
||||
KNOWN_KEYS = frozenset({
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'space', 'tab', 'lshift', 'lctrl', 'up', 'down', 'left', 'right',
|
||||
'mouse_l', 'mouse_r', 'mouse_m', 'mouse_x1', 'mouse_x2',
|
||||
'enter', 'backspace', 'rshift', 'rctrl', 'lalt', 'capslock', 'insert', 'delete', 'home', 'end',
|
||||
'pageup', 'pagedown',
|
||||
'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f9', 'f12',
|
||||
'num0', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7', 'num8', 'num9',
|
||||
'num_add', 'num_sub', 'num_mul', 'num_div', 'num_decimal',
|
||||
'minus', 'equals', 'lbracket', 'rbracket', 'semicolon', 'apostrophe', 'comma', 'period', 'slash',
|
||||
'backslash', 'grave',
|
||||
})
|
||||
|
||||
KNOWN_PADS = frozenset({
|
||||
'south', 'east', 'west', 'north', 'lstick', 'rstick', 'l', 'r', 'start', 'back',
|
||||
'dpad_left', 'dpad_up', 'dpad_right', 'dpad_down', 'zl', 'zr',
|
||||
'lpaddle1', 'rpaddle1', 'lpaddle2', 'rpaddle2',
|
||||
})
|
||||
|
||||
KEY_LABELS = {
|
||||
**{c: c.upper() for c in 'abcdefghijklmnopqrstuvwxyz'},
|
||||
**{str(i): str(i) for i in range(10)},
|
||||
'space': 'Space', 'tab': 'Tab', 'lshift': 'Shift', 'rshift': 'Shift',
|
||||
'lctrl': 'Ctrl', 'rctrl': 'Ctrl', 'lalt': 'Alt', 'capslock': 'Caps',
|
||||
'enter': 'Enter', 'backspace': 'Bksp', 'insert': 'Ins', 'delete': 'Del',
|
||||
'home': 'Home', 'end': 'End', 'pageup': 'PgUp', 'pagedown': 'PgDn',
|
||||
**{f'f{i}': f'F{i}' for i in (2, 3, 4, 5, 6, 7, 9, 12)},
|
||||
**{f'num{i}': f'N{i}' for i in range(10)},
|
||||
'num_add': 'N+', 'num_sub': 'N-', 'num_mul': 'N*', 'num_div': 'N/', 'num_decimal': 'N.',
|
||||
'minus': '-', 'equals': '=', 'lbracket': '[', 'rbracket': ']',
|
||||
'semicolon': ';', 'apostrophe': "'", 'comma': ',', 'period': '.',
|
||||
'slash': '/', 'backslash': '\\', 'grave': '`',
|
||||
}
|
||||
|
||||
|
||||
def read_ini_keys(path=None):
|
||||
"""Action -> primary key name from totk.ini at path (Path or str), or DEFAULT_KEYS when None or missing."""
|
||||
keys = dict(DEFAULT_KEYS)
|
||||
if path is None:
|
||||
return keys
|
||||
p = Path(path)
|
||||
if not p.is_file():
|
||||
return keys
|
||||
text = p.read_text(encoding='utf-8-sig')
|
||||
ini_binds = {}
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith(('#', ';', '[')):
|
||||
continue
|
||||
if '=' not in line:
|
||||
continue
|
||||
k, v = line.split('=', 1)
|
||||
k = k.strip().lower()
|
||||
if k.startswith('totk_'):
|
||||
k = k[5:]
|
||||
v = v.strip()
|
||||
if len(v) >= 2 and v[0] == '"' and v[-1] == '"':
|
||||
v = v[1:-1]
|
||||
if not v: # empty value leaves earlier/default value
|
||||
continue
|
||||
if k.startswith('bind_'):
|
||||
ini_binds[k[5:]] = v
|
||||
|
||||
stick_actions = frozenset({'lstick_up', 'lstick_left', 'lstick_down', 'lstick_right'})
|
||||
for action, default_val in DEFAULT_KEYS.items():
|
||||
if action not in ini_binds:
|
||||
continue
|
||||
val_str = ini_binds[action]
|
||||
tokens = [t.strip() for t in val_str.split(',') if t.strip()]
|
||||
valid_entries = []
|
||||
for tok in tokens:
|
||||
low = tok.lower()
|
||||
if low == 'none':
|
||||
valid_entries.append(('none', None))
|
||||
elif low.startswith('key:'):
|
||||
kname = low[4:].strip()
|
||||
if kname == 'default':
|
||||
valid_entries.append(('key_default', None))
|
||||
elif kname in KNOWN_KEYS:
|
||||
valid_entries.append(('key', kname))
|
||||
elif low.startswith('pad:') and action not in stick_actions:
|
||||
pname = low[4:].strip()
|
||||
if pname == 'default' or pname in KNOWN_PADS:
|
||||
valid_entries.append(('pad', pname))
|
||||
if not valid_entries:
|
||||
continue
|
||||
key_list = []
|
||||
for kind, kval in valid_entries:
|
||||
if kind == 'key_default':
|
||||
key_list.append(default_val)
|
||||
elif kind == 'key':
|
||||
key_list.append(kval)
|
||||
keys[action] = key_list[0] if key_list else 'none'
|
||||
return keys
|
||||
|
||||
|
||||
def xor(data, key):
|
||||
@@ -180,6 +289,170 @@ def xbox_icons(g):
|
||||
}
|
||||
|
||||
|
||||
def mouse_icon(button=None, look=False, advance=700):
|
||||
"""Draw a mouse icon with advance 700. button: 'mouse_l', 'mouse_r', 'mouse_m', 'mouse_x1', 'mouse_x2', or None.
|
||||
look: True to draw left and right outward arrows flanking the mouse body."""
|
||||
def draw(pen):
|
||||
scale = (Y1 - Y0) / 640.0
|
||||
sx = lambda x: 350.0 + (x - 350.0) * scale
|
||||
sy = lambda y: Y0 + y * scale
|
||||
sr = lambda r: r * scale
|
||||
|
||||
# 1. Outer mouse shell (CCW filled)
|
||||
rounded(pen, sx(150), sy(0), sx(550), sy(640), sr(180))
|
||||
|
||||
# 2. Inner mouse cavity (CW hole via ReverseContourPen)
|
||||
rounded(ReverseContourPen(pen), sx(190), sy(45), sx(510), sy(595), sr(140))
|
||||
|
||||
# 3. Horizontal button seam bar (CCW filled)
|
||||
rounded(pen, sx(199), sy(335), sx(501), sy(357), sr(8))
|
||||
|
||||
# 4. Scroll wheel pill (larger when mouse_m)
|
||||
is_m = button == 'mouse_m'
|
||||
wx0, wx1 = (328, 372) if is_m else (337, 363)
|
||||
wy0, wy1 = (414, 532) if is_m else (428, 518)
|
||||
rounded(pen, sx(wx0), sy(wy0), sx(wx1), sy(wy1), sr(12))
|
||||
|
||||
# 5. Upper/lower vertical divider segments split around seam & wheel so contours do not overlap
|
||||
rounded(pen, sx(339), sy(357), sx(361), sy(wy0), sr(5))
|
||||
rounded(pen, sx(339), sy(wy1), sx(361), sy(592), sr(5))
|
||||
|
||||
# 6. Active button fill
|
||||
if button == 'mouse_l':
|
||||
rounded(pen, sx(190), sy(376), sx(328), sy(574), sr(48))
|
||||
elif button == 'mouse_r':
|
||||
rounded(pen, sx(372), sy(376), sx(510), sy(574), sr(48))
|
||||
|
||||
# 7. Left-side thumb buttons (mouse_x1 / mouse_x2)
|
||||
if button in ('mouse_x1', 'mouse_x2'):
|
||||
rounded(pen, sx(91), sy(250), sx(138), sy(342), sr(16))
|
||||
rounded(pen, sx(91), sy(370), sx(138), sy(462), sr(16))
|
||||
if button == 'mouse_x1':
|
||||
rounded(ReverseContourPen(pen), sx(102), sy(382), sx(127), sy(450), sr(8))
|
||||
else:
|
||||
rounded(ReverseContourPen(pen), sx(102), sy(262), sx(127), sy(330), sr(8))
|
||||
|
||||
# 8. Outward arrows flanking mouse body for camera look
|
||||
if look:
|
||||
left = [(35, 310), (85, 354), (85, 328), (126, 328), (126, 292), (85, 292), (85, 266)]
|
||||
right = [(665, 310), (615, 354), (615, 328), (574, 328), (574, 292), (615, 292), (615, 266)]
|
||||
polygon(pen, [(sx(x), sy(y)) for x, y in left[::-1]])
|
||||
polygon(pen, [(sx(x), sy(y)) for x, y in right])
|
||||
|
||||
return advance, draw
|
||||
|
||||
|
||||
def arrow_polygon(direction, cx=320, cy=CY):
|
||||
"""Clockwise (hole) single-headed arrow centered at (cx, cy)."""
|
||||
pts = [
|
||||
(148, 14), (148, -14), (71, -90), (43, -62), (82, -23),
|
||||
(-145, -23), (-145, 23), (82, 23), (43, 62), (71, 90)
|
||||
]
|
||||
if direction == 'right':
|
||||
rot = pts
|
||||
elif direction == 'up':
|
||||
rot = [(-y, x) for x, y in pts]
|
||||
elif direction == 'left':
|
||||
rot = [(-x, -y) for x, y in pts]
|
||||
elif direction == 'down':
|
||||
rot = [(y, -x) for x, y in pts]
|
||||
else:
|
||||
raise ValueError(f'unknown arrow direction: {direction}')
|
||||
return [(cx + x, cy + y) for x, y in rot]
|
||||
|
||||
|
||||
def double_arrow(vertical=False, cx=320, cy=CY, length=155, head=75, barb=80, stem=24):
|
||||
"""Clockwise (hole) double-headed arrow centered at (cx, cy)."""
|
||||
pts = [
|
||||
(length, 0), (head, -barb), (head, -stem), (-head, -stem), (-head, -barb),
|
||||
(-length, 0), (-head, barb), (-head, stem), (head, stem), (head, barb)
|
||||
]
|
||||
if vertical:
|
||||
pts = [(-y, x) for x, y in pts]
|
||||
return [(cx + x, cy + y) for x, y in pts]
|
||||
|
||||
|
||||
def keyboard_icons(g, keys=None):
|
||||
"""Code point -> (advance, draw(pen)) for keyboard and mouse button prompts.
|
||||
g: Glyphs for the letters. keys: action -> primary key name (read_ini_keys())."""
|
||||
keys = read_ini_keys() if keys is None else keys
|
||||
|
||||
def square_keycap(draw_inner, advance=640):
|
||||
def draw(pen):
|
||||
rounded(pen, 40, Y0, advance - 40, Y1, 90)
|
||||
draw_inner(pen)
|
||||
return advance, draw
|
||||
|
||||
def wide_keycap(draw_inner, advance=880):
|
||||
def draw(pen):
|
||||
rounded(pen, 40, Y0, advance - 40, Y1, 90)
|
||||
draw_inner(pen)
|
||||
return advance, draw
|
||||
|
||||
def key_glyph(label):
|
||||
if len(label) >= 3 or label.startswith('N'):
|
||||
return wide_keycap(lambda pen: g.hole(pen, label, 440, CY, 250, 660))
|
||||
return square_keycap(lambda pen: g.hole(pen, label, 320, CY, 290, 410))
|
||||
|
||||
def arrow_glyph(direction):
|
||||
return square_keycap(lambda pen: polygon(pen, arrow_polygon(direction, 320, CY)))
|
||||
|
||||
def unbound_glyph():
|
||||
return square_keycap(lambda pen: rounded(ReverseContourPen(pen), 160, CY - 28, 480, CY + 28, 20))
|
||||
|
||||
def action_glyph(k):
|
||||
if k == 'none':
|
||||
return unbound_glyph()
|
||||
if k in ('mouse_l', 'mouse_r', 'mouse_m', 'mouse_x1', 'mouse_x2'):
|
||||
return mouse_icon(button=k)
|
||||
if k in ('up', 'down', 'left', 'right'):
|
||||
return arrow_glyph(k)
|
||||
return key_glyph(KEY_LABELS.get(k, k.upper()))
|
||||
|
||||
# Movement labels (WASD or Move)
|
||||
u = KEY_LABELS.get(keys.get('lstick_up', 'w'), 'W')
|
||||
l = KEY_LABELS.get(keys.get('lstick_left', 'a'), 'A')
|
||||
d = KEY_LABELS.get(keys.get('lstick_down', 's'), 'S')
|
||||
r = KEY_LABELS.get(keys.get('lstick_right', 'd'), 'D')
|
||||
move_label = u + l + d + r if len(u) == 1 and len(l) == 1 and len(d) == 1 and len(r) == 1 else 'Move'
|
||||
move_glyph = wide_keycap(lambda pen: g.hole(pen, move_label, 440, CY, 250, 660))
|
||||
|
||||
mouse_look = mouse_icon(look=True)
|
||||
|
||||
v_arrow = square_keycap(lambda pen: polygon(pen, double_arrow(vertical=True, cx=320, cy=CY, length=155, head=75, barb=80, stem=24)))
|
||||
h_arrow = square_keycap(lambda pen: polygon(pen, double_arrow(vertical=False, cx=320, cy=CY, length=155, head=75, barb=80, stem=24)))
|
||||
all_dpad = square_keycap(lambda pen: [
|
||||
polygon(pen, double_arrow(vertical=False, cx=200, cy=CY, length=100, head=50, barb=52, stem=16)),
|
||||
polygon(pen, double_arrow(vertical=True, cx=440, cy=CY, length=100, head=50, barb=52, stem=16))
|
||||
])
|
||||
|
||||
out = {
|
||||
0xE040: action_glyph(keys.get('a', 'e')),
|
||||
0xE041: action_glyph(keys.get('b', 'lshift')),
|
||||
0xE042: action_glyph(keys.get('x', 'space')),
|
||||
0xE043: action_glyph(keys.get('y', 'mouse_l')),
|
||||
0xE044: action_glyph(keys.get('l', 'q')),
|
||||
0xE045: action_glyph(keys.get('r', 'r')),
|
||||
0xE046: action_glyph(keys.get('zl', 'mouse_r')),
|
||||
0xE047: action_glyph(keys.get('zr', 'f')),
|
||||
0xE04B: action_glyph(keys.get('plus', 'tab')),
|
||||
0xE04C: action_glyph(keys.get('minus', 'm')),
|
||||
0xE05E: action_glyph(keys.get('lclick', 'lctrl')),
|
||||
0xE05F: action_glyph(keys.get('rclick', 'c')),
|
||||
0xE060: action_glyph(keys.get('up', '2')),
|
||||
0xE061: action_glyph(keys.get('down', '4')),
|
||||
0xE062: action_glyph(keys.get('left', '1')),
|
||||
0xE063: action_glyph(keys.get('right', '3')),
|
||||
0xE064: v_arrow,
|
||||
0xE065: h_arrow,
|
||||
0xE049: all_dpad,
|
||||
0xE068: mouse_look,
|
||||
}
|
||||
for cp in range(0xE050, 0xE05E):
|
||||
out[cp] = move_glyph if cp % 2 == 0 else mouse_look
|
||||
return out
|
||||
|
||||
|
||||
def redraw_font(otf, icons, swaps=()):
|
||||
"""The OpenType font otf with the glyphs of each (code point, code point) pair of swaps trading places (outlines and
|
||||
horizontal metrics), then every code point of icons (code point -> (advance, draw)) drawn anew, written again by
|
||||
@@ -269,7 +542,7 @@ def rasterize(draw, size):
|
||||
return bytes(min(255, c * 255 // (SUPERSAMPLE * SUPERSAMPLE)) for c in cover)
|
||||
|
||||
|
||||
def bntx_redraw(data, base, textures, icons):
|
||||
def bntx_redraw(data, base, textures, icons, optional=()):
|
||||
"""Paint the icon of each texture of textures (name without the ^ suffix -> code point; icons: code point ->
|
||||
(advance, draw)) over its image in the BNTX at data[base:] (a bytearray), in place. Returns the names painted."""
|
||||
assert data[base:base + 4] == b'BNTX' and data[base + 0x20:base + 0x24] == b'NX '
|
||||
@@ -294,7 +567,7 @@ def bntx_redraw(data, base, textures, icons):
|
||||
assert at < size, f'{name}: pixel ({x}, {y}) past the image'
|
||||
data[start + at] = mask[y * w + x]
|
||||
done.append(name)
|
||||
missing = sorted(set(textures) - set(done))
|
||||
missing = sorted(set(textures) - set(done) - set(optional))
|
||||
assert not missing, f'not in the texture list: {", ".join(missing)}'
|
||||
return done
|
||||
|
||||
|
||||
+34
-22
@@ -17,14 +17,15 @@ cover the screen and the HUD moves to the screen's corners (wider) or its top an
|
||||
loads that overlay from romfs-patches-<w>x<h> when the game resolution is that size (runtime/romfs.c overlay_init);
|
||||
the camera's side of it is the patch set aspect (recomp/patches-v121.txt, patches-v143.txt).
|
||||
|
||||
--prompts xbox | xbox-label: the same overlay with Xbox button icons in the game's prompts, for prompts = xbox
|
||||
(runtime/romfs.c overlay_init loads romfs-patches-xbox or romfs-patches-xbox-label in place of romfs-patches). Drawn
|
||||
from the game's own files on the player's machine, nothing added (tools/button_icons.py, which needs fontTools):
|
||||
- both: L, R, ZL, ZR, Plus, Minus and the d-pad become LB, RB, LT, RT, Menu, View and a cross in the button font
|
||||
(scft/External.bfotf, the same font in each Font/Font*.Nin_NX_NVN.bfarc.zs), and L and ZL in the button guide's images
|
||||
(Nt_KeyTexL_00 and Nt_KeyTexZL_00 of timg/__Combined.bntx in the Common archive). The fonts grow, so the overlay also
|
||||
carries the resource size table (System/Resource/ResourceSizeTable.Product.<n>...rsizetable.zs) with their entries
|
||||
raised by as much.
|
||||
--prompts xbox | xbox-label | keyboard: button icons in the game's prompts, for prompts = xbox or prompts = keyboard
|
||||
(runtime/romfs.c overlay_init loads romfs-patches-xbox, romfs-patches-xbox-label or romfs-patches-keyboard in place of
|
||||
romfs-patches). Drawn from the game's own files on the player's machine, nothing added (tools/button_icons.py, which
|
||||
needs fontTools):
|
||||
- xbox & xbox-label: L, R, ZL, ZR, Plus, Minus and the d-pad become LB, RB, LT, RT, Menu, View and a cross in the
|
||||
button font (scft/External.bfotf, the same font in each Font/Font*.Nin_NX_NVN.bfarc.zs), and L and ZL in the button
|
||||
guide's images (Nt_KeyTexL_00 and Nt_KeyTexZL_00 of timg/__Combined.bntx in the Common archive). The fonts grow, so
|
||||
the overlay also carries the resource size table (System/Resource/ResourceSizeTable.Product.<n>...rsizetable.zs) with
|
||||
their entries raised by as much.
|
||||
- xbox, for pad_layout = nintendo. That layout plays an Xbox pad by position: the button that does Switch A is
|
||||
labelled B, so the face buttons' prompts are swapped to name the labels, A with B and X with Y:
|
||||
- the action prompt (blyt/DoCommand_00.bflyt) draws the letter images Nt_KeyTexA/B/X/Y_00 of timg/__Combined.bntx in
|
||||
@@ -37,6 +38,9 @@ from the game's own files on the player's machine, nothing added (tools/button_i
|
||||
- xbox-label, for pad_layout = xbox and auto. Those layouts put Switch A on the button labelled A (auto picks xbox for
|
||||
every pad not labelled the Nintendo way), so the face buttons keep their letters; the action prompt's diamond keeps
|
||||
the Switch positions (A on the right).
|
||||
- keyboard: keycaps for the player's keys and mouse icons for clicks and camera look, drawn into scft/External.bfotf
|
||||
and the button guide's images (Nt_KeyTexA/B/X/Y_00, Nt_KeyTexL_00, Nt_KeyTexZL_00 and Nt_KeyTexLSt_00/01). Reads the
|
||||
bind_ lines of totk.ini (--ini); without an ini, defaults to the default bindings.
|
||||
--xbox-prompts is the older name of --prompts xbox.
|
||||
|
||||
Input: build/extracted/<version>/romfs (romfs.bin and index.json, verified by tools/extract.py). Output:
|
||||
@@ -58,9 +62,12 @@ ap.add_argument('--romfs', type=Path, help='default build/extracted/<version>/ro
|
||||
ap.add_argument('--out', type=Path,
|
||||
help='default build/<version>/romfs-patches (romfs-patches-<style> with --prompts)')
|
||||
ap.add_argument('--aspect', help='<w>x<h> of a game resolution of another shape than 16:9: also lay the UI out for it')
|
||||
ap.add_argument('--prompts', choices=('xbox', 'xbox-label'),
|
||||
help='Xbox button icons in the prompts: xbox for pad_layout = nintendo (A shown as B, X as Y), '
|
||||
'xbox-label for pad_layout = xbox or auto (the face buttons keep their letters)')
|
||||
ap.add_argument('--prompts', choices=('xbox', 'xbox-label', 'keyboard'),
|
||||
help='Button icons in the prompts: xbox for pad_layout = nintendo, '
|
||||
'xbox-label for pad_layout = xbox or auto, '
|
||||
'keyboard for prompts = keyboard')
|
||||
ap.add_argument('--ini', type=Path,
|
||||
help='totk.ini whose bind_ lines --prompts keyboard reads (default: default bindings)')
|
||||
ap.add_argument('--xbox-prompts', dest='prompts', action='store_const', const='xbox', help='the same as --prompts xbox')
|
||||
args = ap.parse_args()
|
||||
args.romfs = args.romfs or root / 'build/extracted' / args.version / 'romfs'
|
||||
@@ -297,19 +304,24 @@ if args.prompts:
|
||||
text = sarc_members(archives[icons.TEXT_ARCHIVE])
|
||||
missing = [name for name in icons.TEXT_FONTS if name not in text]
|
||||
assert not missing, f'no {", ".join(missing)} in {icons.TEXT_ARCHIVE}'
|
||||
drawn = icons.xbox_icons(icons.text_glyphs(archives[icons.TEXT_ARCHIVE][slice(*text[name])]
|
||||
for name in icons.TEXT_FONTS))
|
||||
glyphs = icons.text_glyphs(archives[icons.TEXT_ARCHIVE][slice(*text[name])]
|
||||
for name in icons.TEXT_FONTS)
|
||||
bntx = members['timg/__Combined.bntx'][0]
|
||||
letters = {}
|
||||
if by_position:
|
||||
bntx_swap(blarc, bntx, KEY_TEXTURES)
|
||||
for member, (lo, hi) in members.items():
|
||||
if member.endswith('.bflyt'):
|
||||
changed = bflyt_letters(blarc, lo, hi, KEY_LETTERS)
|
||||
if changed:
|
||||
letters[member] = changed
|
||||
assert 'blyt/DoCommand_00.bflyt' in letters, 'no letter panes in blyt/DoCommand_00.bflyt'
|
||||
hud = icons.bntx_redraw(blarc, bntx, icons.HUD_TEXTURES, drawn)
|
||||
if args.prompts == 'keyboard':
|
||||
drawn = icons.keyboard_icons(glyphs, icons.read_ini_keys(args.ini))
|
||||
hud = icons.bntx_redraw(blarc, bntx, icons.HUD_KEYBOARD_TEXTURES, drawn, optional=icons.HUD_KEYBOARD_OPTIONAL)
|
||||
else:
|
||||
drawn = icons.xbox_icons(glyphs)
|
||||
if by_position:
|
||||
bntx_swap(blarc, bntx, KEY_TEXTURES)
|
||||
for member, (lo, hi) in members.items():
|
||||
if member.endswith('.bflyt'):
|
||||
changed = bflyt_letters(blarc, lo, hi, KEY_LETTERS)
|
||||
if changed:
|
||||
letters[member] = changed
|
||||
assert 'blyt/DoCommand_00.bflyt' in letters, 'no letter panes in blyt/DoCommand_00.bflyt'
|
||||
hud = icons.bntx_redraw(blarc, bntx, icons.HUD_TEXTURES, drawn)
|
||||
outputs[ARCHIVE] = bytes(blarc)
|
||||
fonts = {} # each distinct font is drawn once (1.4.3's four archives hold the same one)
|
||||
for path, bfarc in archives.items():
|
||||
|
||||
@@ -93,7 +93,7 @@ def main():
|
||||
else:
|
||||
print(f'package_build: {f} not in {build}, skipped')
|
||||
n = copy_tree(build / 'romfs-patches', out / 'romfs-patches')
|
||||
for name in ('romfs-patches-xbox', 'romfs-patches-xbox-label'): # prompts = xbox (make_romfs_patches.py --prompts)
|
||||
for name in ('romfs-patches-xbox', 'romfs-patches-xbox-label', 'romfs-patches-keyboard'): # prompts = xbox | keyboard
|
||||
if (build / name).is_dir():
|
||||
n += copy_tree(build / name, out / name)
|
||||
n += copy_tree(exefs, out / 'exefs')
|
||||
|
||||
+3
-3
@@ -283,9 +283,9 @@
|
||||
# labels, xbox for the others). The bind_ lines below apply on top. Changes while the game runs take effect at once.
|
||||
# 0.1.9's xbox_prompts is read as nintendo with prompts = xbox.
|
||||
# pad_layout = auto
|
||||
# The buttons the game's prompts show: nintendo (the game's own) or xbox (LB RB LT RT, Menu, View and a d-pad cross,
|
||||
# and A B X Y as labelled on the button pad_layout presses: with nintendo, A shows as B and X as Y). Read at start
|
||||
# (docs/controls.md, "Xbox button prompts").
|
||||
# The buttons the game's prompts show: nintendo (the game's own), xbox (LB RB LT RT, Menu, View and a d-pad cross,
|
||||
# and A B X Y as labelled on the button pad_layout presses: with nintendo, A shows as B and X as Y) or keyboard (keycaps
|
||||
# for your keys and mouse icons for clicks and camera look). Read at start (docs/controls.md, "Button prompts").
|
||||
# prompts = nintendo
|
||||
# Gamepad stick dead zone in percent of full deflection (0 to 50). Raise it for a stick that drifts: inside the circle
|
||||
# the stick reads centred, and the rest of its travel is scaled to start there.
|
||||
|
||||
Reference in new issue
Block a user