feat(builder): the game's own name and icon for its exe and window, from the .nup #857

Merged
HumanBean merged 1 commit from HumanBean/Longshot:feat/game-title-icon into main 2026-11-15 17:59:42 +00:00
Owner

Type: feat · Risk: low · Tested on: not run yet · Needs: Windows (any GPU), Linux (any GPU), built from a .nup

Why

A game the builder makes is called "Longshot" and has no icon: the window's title says Longshot, and on Windows the exe shows in Task Manager, the taskbar and its Properties as a nameless longshot.exe with the default icon. On Linux the window got the desktop's generic icon, since the game looked for its icon in the wrong folder. It should look like the game it is: its own icon, and the name "The Legend of Zelda: Ocarina of Time (Longshot)". See #228.

What changed

The builder reads the game's name and icon from the .nup it builds from, keeps them in the game folder so an update keeps them, and makes them into the Windows exe's icon and the name Windows shows for it, and into the window's title and (on Linux) its icon. The exe's file name stays longshot.exe / longshot: the launcher, the folder's launcher (builder/setup/setup.c) and the driver profiles find the game by it, and Windows refuses the colon in a file name.

  • builder: nx2/game_title.py (new) reads the title's metadata in the .nup (the record at +0x20 of its title entry: kind/size records, 3 the icon JPEG, 5 the name). The .nup's name is fit for a file name, so its colon is gone ("The Legend of Zelda Ocarina of Time"); NAMES gives the name as the game writes it, by title id. It keeps longshot_title.txt (the window title, with " (Longshot)") and .longshot_icon.jpg in the game folder.
  • builder: make_game.py game_name() runs after the game is read (a .nup build reads it, an update uses what the folder kept, a build from an unpacked dump has neither and stays "Longshot"). On Windows it writes work/longshot_game.res; on Linux the folder's longshot.png is the game's icon instead of Longshot's logo. Nothing in it stops a build.
  • builder: make_icon.py is split into functions the builder reuses, and can add a VERSIONINFO (version_entry: FileDescription and ProductName). Its CLI makes the same files as before.
  • builder: build.py exe_resources() links longshot_game.res, else a developer's longshot.res, never both (both are icon 1).
  • host: hos_game_title() in host/hos_paths.h reads the first line of longshot_title.txt in the game folder. host/display.c sets it as the window's title (Unicode, through DefWindowProcW); host/posix/display_sdl.c creates the window with it (HOS_WINDOW_TITLE still wins).
  • host: the Linux window icon is now looked for in the game folder (hos_game_path_a), not beside the exe. In a packaged builder's folder the game is bin/longshot, so it looked in bin/ and never found longshot.png.
  • tests: tests/display_startup_failure_test.cpp stubs hos_game_title, which the startup loop it cuts out now calls.

Measurements

Nothing to measure: no frame-time or memory change. What needs checking is the name and icon (below).

Flags and settings

None.

Testing

Tested: the .nup layout was read by hand from an Ocarina of Time 1.1.1 .nup on Linux: records 1 (NPDM, 1828 bytes), 3 (JPEG, 38073 bytes), 4 ("1.1.1"), 5 ("The Legend of Zelda Ocarina of Time"), 8 ("ZeldaOOT"); title id 0400486024dec000. No build was run, and tests/run_all.py was not run on this PC.

Not tested: a build and a start of the game on either OS; the game-free checks.

Must be tested:

  • Any PC: python -I -B tests/run_all.py passes, expecting nothing new to fail (by hand, or the Forgejo checks).
  • Windows, any GPU: build from a .nup with the builder and start the game, expecting the window titled "The Legend of Zelda: Ocarina of Time (Longshot)" with the game's icon, and bin\longshot.exe shown under that name and icon in Task Manager and the taskbar (Test Farm: @Beanstalk windows, build from the .nup and check the window title and the exe's icon and name in Task Manager).
  • Linux, any GPU: build from a .nup with the AppImage and start the game from the folder's longshot, expecting the same window title and the game's icon on X11 (Test Farm: @Beanstalk linux, same check).
  • Either OS: an update build (no .nup) of a folder built by this PR keeps the name and icon.

Risks

What could break: a .nup whose metadata isn't laid out as above gives no name or icon, and the game stays "Longshot" as before. A JPEG Pillow can't read gives no icon. The host's headers changed (hos_paths.h), so the next build compiles the host code again.

Turning it off: revert the commit. Deleting longshot_title.txt from a game folder puts the title back to "Longshot".

Compatibility: saves, the options file, the caches and the vk ABI don't change. The game folder gets longshot_title.txt and .longshot_icon.jpg. A folder built before this PR keeps "Longshot" until it is built from its .nup again (an update alone has nothing to read the name from).

Issues

See #228 (the generic Wayland icon on the game)

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named). Not run on this PC.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Not built yet; the title is read with _wfopen on a UTF-16 path on Windows.

🤖 Generated with Claude Code

**Type:** feat · **Risk:** low · **Tested on:** not run yet · **Needs:** Windows (any GPU), Linux (any GPU), built from a .nup ## Why A game the builder makes is called "Longshot" and has no icon: the window's title says Longshot, and on Windows the exe shows in Task Manager, the taskbar and its Properties as a nameless longshot.exe with the default icon. On Linux the window got the desktop's generic icon, since the game looked for its icon in the wrong folder. It should look like the game it is: its own icon, and the name "The Legend of Zelda: Ocarina of Time (Longshot)". See #228. ## What changed The builder reads the game's name and icon from the .nup it builds from, keeps them in the game folder so an update keeps them, and makes them into the Windows exe's icon and the name Windows shows for it, and into the window's title and (on Linux) its icon. The exe's file name stays `longshot.exe` / `longshot`: the launcher, the folder's launcher (`builder/setup/setup.c`) and the driver profiles find the game by it, and Windows refuses the colon in a file name. - builder: `nx2/game_title.py` (new) reads the title's metadata in the .nup (the record at +0x20 of its title entry: kind/size records, 3 the icon JPEG, 5 the name). The .nup's name is fit for a file name, so its colon is gone ("The Legend of Zelda Ocarina of Time"); `NAMES` gives the name as the game writes it, by title id. It keeps `longshot_title.txt` (the window title, with " (Longshot)") and `.longshot_icon.jpg` in the game folder. - builder: `make_game.py` `game_name()` runs after the game is read (a .nup build reads it, an update uses what the folder kept, a build from an unpacked dump has neither and stays "Longshot"). On Windows it writes `work/longshot_game.res`; on Linux the folder's `longshot.png` is the game's icon instead of Longshot's logo. Nothing in it stops a build. - builder: `make_icon.py` is split into functions the builder reuses, and can add a VERSIONINFO (`version_entry`: FileDescription and ProductName). Its CLI makes the same files as before. - builder: `build.py` `exe_resources()` links `longshot_game.res`, else a developer's `longshot.res`, never both (both are icon 1). - host: `hos_game_title()` in `host/hos_paths.h` reads the first line of `longshot_title.txt` in the game folder. `host/display.c` sets it as the window's title (Unicode, through DefWindowProcW); `host/posix/display_sdl.c` creates the window with it (`HOS_WINDOW_TITLE` still wins). - host: the Linux window icon is now looked for in the game folder (`hos_game_path_a`), not beside the exe. In a packaged builder's folder the game is `bin/longshot`, so it looked in `bin/` and never found `longshot.png`. - tests: `tests/display_startup_failure_test.cpp` stubs `hos_game_title`, which the startup loop it cuts out now calls. ## Measurements Nothing to measure: no frame-time or memory change. What needs checking is the name and icon (below). ### Flags and settings None. ## Testing **Tested:** the .nup layout was read by hand from an Ocarina of Time 1.1.1 .nup on Linux: records 1 (NPDM, 1828 bytes), 3 (JPEG, 38073 bytes), 4 ("1.1.1"), 5 ("The Legend of Zelda Ocarina of Time"), 8 ("ZeldaOOT"); title id 0400486024dec000. No build was run, and `tests/run_all.py` was not run on this PC. **Not tested:** a build and a start of the game on either OS; the game-free checks. **Must be tested:** - Any PC: `python -I -B tests/run_all.py` passes, expecting nothing new to fail (by hand, or the Forgejo checks). - Windows, any GPU: build from a .nup with the builder and start the game, expecting the window titled "The Legend of Zelda: Ocarina of Time (Longshot)" with the game's icon, and `bin\longshot.exe` shown under that name and icon in Task Manager and the taskbar (Test Farm: `@Beanstalk windows`, build from the .nup and check the window title and the exe's icon and name in Task Manager). - Linux, any GPU: build from a .nup with the AppImage and start the game from the folder's `longshot`, expecting the same window title and the game's icon on X11 (Test Farm: `@Beanstalk linux`, same check). - Either OS: an update build (no .nup) of a folder built by this PR keeps the name and icon. ## Risks **What could break:** a .nup whose metadata isn't laid out as above gives no name or icon, and the game stays "Longshot" as before. A JPEG Pillow can't read gives no icon. The host's headers changed (`hos_paths.h`), so the next build compiles the host code again. **Turning it off:** revert the commit. Deleting `longshot_title.txt` from a game folder puts the title back to "Longshot". **Compatibility:** saves, the options file, the caches and the vk ABI don't change. The game folder gets `longshot_title.txt` and `.longshot_icon.jpg`. A folder built before this PR keeps "Longshot" until it is built from its .nup again (an update alone has nothing to read the name from). ## Issues See #228 (the generic Wayland icon on the game) ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [ ] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). Not run on this PC. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Not built yet; the title is read with `_wfopen` on a UTF-16 path on Windows. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(builder): the game's own name and icon for its exe and window, from the .nup
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Website / Website on Cloudflare (pull_request_target) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 6s
CI / Workflow lint (actionlint) (pull_request) Successful in 9s
Guide screenshots / Guide images and local UI source review (pull_request) Failing after 8s
PR description / Description follows the template (pull_request_target) Failing after 5s
4b75251c7c
The window was "Longshot" with no icon, and so was the Windows exe in Task
Manager and the taskbar. The builder now reads the title's name and icon from
the .nup's metadata, keeps them in the game folder (longshot_title.txt,
.longshot_icon.jpg) so updates keep them, links the icon and a version resource
naming "The Legend of Zelda: Ocarina of Time (Longshot)" into the Windows exe,
and writes the icon as the Linux window's longshot.png. The game reads its
window title from the folder. The Linux window icon is now looked for in the
game folder, not bin/, where the packaged builder's layout never had it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
HumanBean changed title from WIP: feat(builder): the game's own name and icon for its exe and window, from the .nup to feat(builder): the game's own name and icon for its exe and window, from the .nup 2026-11-15 17:58:59 +00:00
HumanBean deleted branch feat/game-title-icon 2026-11-15 17:59:46 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!857
No description provided.