feat(menu): a Bug report row that saves what an issue needs in one press #2347

Merged
mooki000 merged 2 commits from bug-report into main 2026-11-12 01:03:02 +00:00
mooki000 commented 2026-11-11 15:22:00 +00:00 (Migrated from github.com)

What this is for: a player who meets a problem presses one row in the Insert menu, Gameplay → Bug report, and gets a folder with everything the game problem form asks for: the form's answers in report.txt (version, the map he is in, graphics card and driver version, CPU, RAM and system, the options changed and the switches set), this run's log and the run before's (the crashed one after a restart) with his home folder and user name taken out, and a picture of the game. Maintainers get reports with the log and the hardware from the start instead of asking for them. What is measured: on Linux (Omarchy, an Arch Linux; RTX 4090, NVIDIA 615.78.08), on main ba6eef70: the new test passes with g++ 16 and clang 23 and fails on main; its cases for the review's points (NUL bytes in a log, a picture that comes late, which switch values are written, the reason a folder cannot be made); tests/run_all.py; headless runs of this head that pressed the row (one pressed it in Kokiri Forest, one after a kill -9 and a restart: both reports written, read back by a script against the logs they came from (no file holds the home folder or the user name; each log copy is the original line for line apart from those two), the after-crash report's hookshot_log.1.txt is the killed start's whole log, and its picture came through #926's shot_ask); the length GitHub takes for the link, signed out. Not measured: Windows at run time (compiled and linked only), a windowed run, the link opened in a browser (signed out or in), AMD and Intel drivers' strings, a Steam Deck.

Refs #2346 (closed by the maintainer: tracked in this pull request).

Problem

  • The game problem form (.github/ISSUE_TEMPLATE/1-game-problem.yml) requires the version, where in the game, the graphics card and driver version, CPU, RAM and operating system, and hookshot_log.txt (and hookshot_log.1.txt "if you started the game again after the problem"). The player has to find each one by hand.
  • hookshot_log.txt answers only part of it: its first line has the version, but the driver is only Vulkan's packed number (vk: device 0: NVIDIA GeForce RTX 4090, Vulkan 1.4, driver 0x99d38200, type 2), there is no line for the system, the CPU or the options changed, and nothing logged where the player was (the host reads Sequence_CurrentBanc only for the debug tools).
  • After a crash a player starts the game again, and the crashed run's log becomes hookshot_log.1.txt (host/hos_main.c, the rotation added because players sent the new run's log and the crash was gone). It still has to be found and attached.
  • The logs carry the home folder and the user name in their paths (fs: mounted content: -> <home>/..., vk: pipeline cache <home>/...), which a player may not want to post.
  • So reports arrive without the log or the hardware, and docs/workflow.md scores such a report at confidence 0.4 and asks for the log first. This gathers what CONTRIBUTING.md already asks ("Include the version, your hardware, where you were and hookshot_log.txt"; "Report the version you actually ran: ... the first line of hookshot_log.txt"). It changes nothing about where reports go.

What changed

  • gpu/vk/vk_bug_report.h, vk_bug_report.cpp (new, 59 + 602 lines): the packer, without Vulkan and without the menu, so the test compiles it alone. It writes bug-reports/<YYYY-MM-DD_HH-MM-SS>/ in the game folder on a thread of its own:
    • report.txt: the form's questions under the form's own ### headings, so scripts/hardware_report.py's section() reads it as an issue: Hookshot version (HOOKSHOT_VERSION, as the log's first line); Where in the game (the last map: line of this run's log, and the run before's, and a request to add what you were doing); Graphics card and driver version (the device and VkPhysicalDeviceDriverProperties' driverName + driverInfo, e.g. NVIDIA GeForce RTX 4090, NVIDIA 615.78.08); CPU, RAM and operating system (the CPU's brand string and threads, RAM, os-release, desktop and session type on Linux; RtlGetVersion and Wine on Windows); Changes from the defaults (each Insert-menu row not at its default as the menu shows it; a microphone chosen by its name is not named; then the switches set, below); hookshot_log.txt (what is attached, the run before's version and date, each log's first FATAL, CRASH or device lost line); Screenshots or video; then a link that opens the form with version, where, GPU, system and settings filled in (GitHub fills issue-form fields from query parameters named by the fields' ids).
    • The switches. CONTRIBUTING.md lists hookshot_env.txt among the files never to share, so the report never copies it, and of the switches set (the names hookshot_env.txt takes: RX_, HOS_, HOOKSHOT_, EDEN_, NVN_, SDL_; and what hooks other code in: VK_INSTANCE_LAYERS, MANGOHUD, ENABLE_VKBASALT, LD_PRELOAD ...) it writes each name, and the value only when it cannot hold anything private: a number of at most 12 characters, a size such as 1920x1080, or on/off, true/false, yes/no, and never for a name with TOKEN, KEY, ROOM, SECRET, PASS, SERVER or USER in it. Every other value reads (value left out): paths, addresses, scripts. Other variables are not listed at all.
    • The link stays within 2000 characters and carries no switch value: the options changed and the switches' names, or, when the names do not fit, how many switches are set. A player who is not signed in is sent to GitHub's sign-in page with the whole link inside it, and that page failed for links from about 3500 characters of escaped text (table below).
    • hookshot_log.txt (this run's log up to the press) and hookshot_log.1.txt (the run before's; when this run logs elsewhere, e.g. from a terminal, the game folder's hookshot_log.txt). Read by length (fread, split on newlines), so NUL bytes, which a run ended by a power cut or a hard kill can leave, are copied as they are. Each line: the home folder (a whole folder; on Windows either slash and any case) becomes ~ or %USERPROFILE%, the user's name as a folder of any path <user>. A log over 10 MB keeps its first MB and its last 9 MB (GitHub takes files up to 25 MB).
    • picture.png: 1280x720, the game's next frame, without the menu (below). If none comes within 3 s, the report goes without it and says so; a picture that comes later is deleted again.
    • A folder that cannot be made is reported with the system's reason (GetLastError() on Windows, errno elsewhere).
  • gpu/vk/vk_menu.cpp (+89): the row before Restart and Quit (kBugReportRow = kRestartRow - 1), its help (what goes in, that no saves or game files go in and nothing is sent), its value (Press to save / Saving... / Saved / Not saved), Save in both prompt lines (the full menu's and the compact bar's), the press, changed_rows_text() (rows against a default-constructed Options), bug_report_save(), and the picture's ask, bug_report_picture(). It follows Save my pipeline list's pattern (#2447): its value in row_value_cell, its line under the help in draw_options once pressed (where the folder is and how to send it: File Explorer on Windows, a file manager on Linux; on a Steam Deck, Desktop Mode), and room for that line's longest form in measure_help. The game opens no browser or file manager: Game Mode on a Deck has neither.
  • gpu/vk/vk_replay.cpp (+2): shot_ask_next(), which calls #926's shot_ask(path, -1, done, ctx) from outside its anonymous namespace (declared in vk_internal.h). The report's picture is the control bridge's kind of shot: an image and a buffer of its own (1280x720, made at the first ask), copied from src, the game's frame, which the menu is never drawn into (menu_record_image draws only into the screenshots' image), read back once the GPU is past it and written on a thread. No other line of do_present changes.
  • gpu/vk/vk_main.cpp (+11), vk_internal.h (+6): device_report_text(), and the declaration above.
  • host/hos_main.c (+12): map: <Sequence_CurrentBanc> (child|adult) when the map or the age changes, in oot_inventory_refresh (on Link's update, at most every 500 ms, where the age is already read for the Debug tab). It is where the report's place comes from, and it puts the place into every log. The names are the game's own map names, which the debug tools' teleport lines already print.
  • readme.md: the row in the menu table, a paragraph on what it saves and which values it leaves out, a pointer from "If the problem continues".
  • tests/test_bug_report.py (new), and tests/test_menu_tune.py: its check of the compact bar's prompt line now reads the line with the Bug report in it (the check is otherwise unchanged).
  • Unchanged: nothing is sent anywhere; no saves, no game files, not hookshot_options.ini or hookshot_env.txt themselves; no new option or switch; the logs' own rotation; the screenshot path.

How to try it

Insert menu (Insert, F1 or Esc, or the menu button) → Gameplay → Bug report → Enter or A.
The row reads Saved and its help names the folder: bug-reports/<date>_<time> in the game folder, with report.txt, hookshot_log.txt, hookshot_log.1.txt and picture.png.

How it was tested

2026-11-11, after the two reviews, head a75f3ca5 on main ba6eef70. Rebased twice today: onto #926, then onto #2042, #2378, #2447 and the rest. The conflicts, each kept on both sides: #926's shot_ask_record beside the old readback (which is now gone); #2042's Debug rows where the Bug report row goes before Restart; #2378's row_value_cell() and #2447's share row, where the row's value cell and prompt went (the row now follows the share row's pattern, above); a forward declaration in host/hos_main.c. #2042's rules hold for the row: it has no value, so preset_exempt does not apply (tests/test_preset_exempt.py reads only rows with one), and the branch reads no on/off switch. #2042 also took TOTK_ out of the names hookshot_env.txt takes; the report follows (the test compares the two lists).

Environment: Omarchy (an Arch Linux), kernel 7.2.15-1-omarchy, RTX 4090 with NVIDIA 615.78.08, i9-13900K, 94 GB, g++ 16.2.1 and clang 23.1.3.

  • The new test on the branch (python -I -B tests/test_bug_report.py, with --cxx g++ and with --cxx clang++; the same line from both):

    bug report: ok (report.txt as the issue form, both logs redacted, the picture, the long log, the terminal start, switch values, NUL bytes, the folder's reason)
    

    Against main's source (the test copied into an export of ba6eef70; with both compilers): it fails at its first step, the menu row:

    at = menu.index('{TAB_GAMEPLAY, "Bug report", nullptr')
    ValueError: substring not found
    

    And the review's NUL case against the old log copy (fgets and line.back(), put back on its own):

    bug report: the copy of a log with NUL bytes differs: b'[hos host] hookshot v2.1.0-rc.3-39-gbdfe09c (source 763271ad)\n[hos Main] fs: mounted content: -> ~/Games\n[hos Main] map: KokiriForest_Child (child)\n[hos Main] half a line'
    

    It compiles vk_bug_report.cpp alone with a driver and writes reports from a made-up game folder inside a made-up home folder (.../home/alice/Games/Hookshot/), with paths of the home and of /run/media/alice/... in the logs, map lines and a CRASH: line in the run before's. It checks: the folder holds exactly the report, both logs and the picture; no file names the home folder, the user as a folder or the game folder's path (alice123 and Malice stay); hardware_report.py's section, classify and os_of read the version, NVIDIA / RTX 40 and Linux from report.txt; the place is the last map, and the run before's; the switches: values kept (HOS_HEADLESS=1, 60, off, 3840x2160, -1, 1.5) and left out (a path, a drive path, a menu script, an IP address, a host:port, a 13-digit number, and numbers under ROOM, KEY, SERVER, USER and PASSWORD names), none of those values anywhere in report.txt, PATH and a token not listed, no %3D in the link; the run before's crash line; the link's fields and its length (with 98 switches: within 2000, the options and the count in it, every switch in report.txt); the menu's way (a thread, one report at a time); no frame means no picture, said so; a picture that comes after the report went without it is deleted; a start from a terminal; two reports in one second; a 22 MB log cut to its first and last part with its middle's map still found; a log with a NUL in a line and 4 KB of NULs at its end copied byte for byte, the map after the NUL found; a folder that cannot be made, with the system's reason; the redaction's Windows cases (either slash, any case); this PC's system line. And the wiring by text: the row and what its help promises, Save in both prompt lines, the press, the picture asked of shot_ask_next and copied from the game's frame without the menu, the host's map: line, that the switches it lists are the names hookshot_env.txt takes (env_line_allowed's), the form's labels and ids. Eight runs at once at load 280 all pass (the first version of the menu's case read the report's state too late under load in the full suite; it now holds the report until it has looked).

  • The checks the review named and those that read the menu's rows, on this head (with g++ and clang++ where they compile C++): test_capture_dimensions.py, test_capture_slot_ownership.py, test_menu_env_overrides.py, test_menu_keys.py, test_pipeline_share.py, test_menu_tune.py, test_stick_dead_zone.py, test_preset_exempt.py (62 rows), test_menu_env_on.py, test_env_on.py, test_fps_stats.py, test_gpu_choice.py, test_menu_restart_slots.py, test_mouse_settings.py: all pass. vk_bug_report.cpp alone with -Wall -Wextra -Wpedantic -Werror: no warning from either compiler.

  • python -I -B tests/run_all.py on Linux, on this head:

    440 passed, 1 failed, 20 skipped in 600 s (linux, 32 at once)
    

    The one failure is test_recorder_sync.py, which ran out of its 600 s at load 250-320 (other work ran beside it) and touches nothing of this branch. An earlier run of the whole suite on this branch failed test_bug_report.py once under that load: its menu case read the report's state too late (fixed in the test, above).

  • Merged into the newest main (fce53453, nine commits later; it merges cleanly): test_bug_report.py (both compilers), test_menu_tune.py, test_pipeline_share.py, test_menu_keys.py, test_menu_env_overrides.py, test_preset_exempt.py (63 rows), test_menu_restart_slots.py, both capture tests: pass. test_env_on.py fails there and on fce53453 alone (HOS_FPS_LOG (host/oot_high_fps.c:15), from #2470), not from this branch.

  • Windows: vk_bug_report.cpp with a small driver compiles and links for x86_64-w64-mingw32 (clang 23 with llvm-mingw 20261104's headers and libraries, -Wall -Wextra -Werror: a PE32+ console executable). Not run.

  • In the game, on a build of this head's code (v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc); the head differs only in two test files), headless, RTX 4090, RX_VK_PRECOMPILE=0 (shaders compiled when first used), load 200 to 320 (other work ran beside it), the save loaded into Kokiri Forest and the menu script pressing Gameplay → Bug report:

    Run What Harness result The report
    br-menu2 the row pressed 55-63 s after the first frame exit 124 (its 120 s), fatal 0, crash 0, device lost 0, kernel driver lines 0 bug-reports/2026-11-11_16-58-06: report.txt and both logs, no picture, and it says so. Rightly: the game showed no frame from 39 s to 107 s (the save loading, shaders compiled as they came, at load 300), took the scripted keys when frames came again, and stopped once more at 109 s
    br-crash3 a first start killed with kill -9 after 110 s, then a second start; the row pressed 63 s after its first frame exit 124 (its 260 s), fatal 0, crash 0, device lost 0, kernel driver lines 0 bug-reports/2026-11-11_17-13-48: report.txt, both logs (hookshot_log.1.txt is the killed start's whole log), picture.png (present 1772)
    br-menu3 the row pressed 130-138 s after the first frame, in play exit 124 (its 200 s), fatal 0, crash 0, device lost 0, kernel driver lines 0 bug-reports/2026-11-11_17-25-32: report.txt, both logs, picture.png (present 3063, 138 s)
    The after-crash run's log lines (the picture's path shortened):
    [hos BaseProcExecutor5] map: KokiriForest_Child (child)
    [hos Presentation Thread] menu: bug report asked for
    [hos host] vk: present 1772 written to ~/Games/.../bug-reports/2026-11-11_17-13-48/picture.png (the control bridge)
    [hos host] menu: bug report written to bug-reports/2026-11-11_17-13-48: report.txt, hookshot_log.txt (245 KB), hookshot_log.1.txt (117 KB), picture.png
    

    Its folder read back against the game folder's logs (check_report.py: each copy compared line by line with its original, with the home folder and the user name replaced as the game does):

    files: hookshot_log.1.txt (119514 bytes), hookshot_log.txt (250199 bytes), picture.png (2765798 bytes), report.txt (3212 bytes)
    no file holds the home folder or the user name (any case, anywhere)   [the checker's line, with the two values left out here]
    hookshot_log.txt: 1827 of the original's 2991 lines, 12 of them with the home or the user replaced, nothing else changed
    hookshot_log.1.txt: 991 of the original's 991 lines, 11 of them with the home or the user replaced, nothing else changed
    hardware_report.py: vendor and class ('NVIDIA', 'RTX 40'), OS Linux / SteamOS
    ok
    

    br-menu2's and br-menu3's folders pass the same check (1387 of 1571 and 3230 of 3230 lines; 2861 of 3679 and 3230 of 3230 lines; only the home folder and the user name replaced). A picture's pixel data is not read as text: br-menu3's picture.png has the bytes 4d 4f 4f 4b 49 among its pixels (row 176), which an any-case byte search reads as a name; the PNG holds only IHDR, IDAT and IEND chunks. The host name is in no text file of either.

  • Pictures (headless, 1280x720; the first two are the harness's shots with the menu drawn in, the third the report's own file):

    1: the Bug report row selected, Press to save. 2: after Enter, Saved, with the folder under the help. 3: that report's picture.png: the game's frame without the menu

    1. br-menu3, the row before the press: Bug report selected, Press to save, its help (what goes in; no saves or game files; nothing is sent), the prompt bar Save Enter.
    2. After Enter: Saved, and the paragraph under the help names the folder (bug-reports/2026-11-11_17-25-32) and how to send it.
    3. That report's picture.png, taken through shot_ask at present 3063: child Link and Navi on the path in Kokiri Forest, hearts and minimap; no menu, not dimmed.

    4: after a kill -9 and a restart, Saved, with its folder under the help. 5: that report's picture.png: the game's frame without the menu

    1. br-crash3, after the kill -9 and the restart: Saved, the folder bug-reports/2026-11-11_17-13-48 under the help.
    2. That report's picture.png (present 1772): Kokiri Forest from low down, facing the bright sky, hearts and minimap; no menu.
  • The report.txt the after-crash run wrote, as a player would attach it (the switches are the test harness's: see which values are written and which are left out):

The report.txt that run wrote
Hookshot bug report, made by the Insert menu's Bug report on 2026-11-11 at 17:13.

To send it: open https://github.com/NotRexed/Hookshot/issues/new/choose and choose Game problem (or the link at the end of this file, which fills in the answers below), copy the answers below into the fields of the same names, say what happened, and drag the files of this folder, this one too, into the issue.

### Hookshot version

v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc)

### Where in the game

KokiriForest_Child (child)
(The map this run's log names last: add what you were doing there.)

### Graphics card and driver version

NVIDIA GeForce RTX 4090, NVIDIA 615.78.08

### CPU, RAM and operating system

13th Gen Intel(R) Core(TM) i9-13900K (32 threads), 94 GB, Omarchy (Linux 7.2.15-1-omarchy), Hyprland (wayland)

### Changes from the defaults

Every Insert menu option is at its default.
Switches set (the environment, hookshot_env.txt; a value only when it is a number or on/off, and never for a name with TOKEN, KEY, ROOM, SECRET, PASS, SERVER or USER in it):
HOOKSHOT_NO_UPDATE_CHECK=1
HOS_AUDIO=0
HOS_DISCORD=0
HOS_ENV_FILE=(value left out)
HOS_HEADLESS=1
HOS_INPUT_BACKGROUND=0
HOS_INPUT_EXCLUSIVE=1
HOS_INPUT_FRAME_CLOCK=1
HOS_INPUT_SCRIPT=(value left out)
HOS_PRESENT_TRACE=(value left out)
RX_CPM=(value left out)
RX_VK_DEVICE=0
RX_VK_FRAME_LOG=(value left out)
RX_VK_MENU_SCRIPT=(value left out)
RX_VK_PRECOMPILE=0
RX_VK_SHOTS=(value left out)
RX_VK_SHOT_EVERY=300
RX_VK_SHOT_MENU=1
SDL_AUDIODRIVER=(value left out)
SDL_AUDIO_DRIVER=(value left out)
VK_DRIVER_FILES=(value left out)

### hookshot_log.txt

hookshot_log.txt: this run's log as it was when the report was made, 245 KB.
hookshot_log.1.txt: the run before's log (hookshot v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc), last written 2026-11-11 17:12), 117 KB.
In both, the home folder is written ~ and the user's name <user>.

### Screenshots or video

picture.png: the game's picture when the report was made, without the menu.

### What this folder holds

report.txt (this file), hookshot_log.txt, hookshot_log.1.txt, picture.png. Nothing else from this PC: no saves, no game files, not the options file itself.

The link with the answers filled in:
https://github.com/NotRexed/Hookshot/issues/new?template=1-game-problem.yml&version=v2.1.0-rc.3-177-g9c538c91%20%28source%20c4f2afbc%29&where=KokiriForest_Child%20%28child%29&gpu=NVIDIA%20GeForce%20RTX%204090%2C%20NVIDIA%20615.78.08&system=13th%20Gen%20Intel%28R%29%20Core%28TM%29%20i9-13900K%20%2832%20threads%29%2C%2094%20GB%2C%20Omarchy%20%28Linux%207.2.15-1-omarchy%29%2C%20Hyprland%20%28wayland%29&settings=Every%20Insert%20menu%20option%20is%20at%20its%20default.%0ASwitches%20set%20%28their%20values%20are%20in%20the%20attached%20report.txt%29%3A%20HOOKSHOT_NO_UPDATE_CHECK%2C%20HOS_AUDIO%2C%20HOS_DISCORD%2C%20HOS_ENV_FILE%2C%20HOS_HEADLESS%2C%20HOS_INPUT_BACKGROUND%2C%20HOS_INPUT_EXCLUSIVE%2C%20HOS_INPUT_FRAME_CLOCK%2C%20HOS_INPUT_SCRIPT%2C%20HOS_PRESENT_TRACE%2C%20RX_CPM%2C%20RX_VK_DEVICE%2C%20RX_VK_FRAME_LOG%2C%20RX_VK_MENU_SCRIPT%2C%20RX_VK_PRECOMPILE%2C%20RX_VK_SHOTS%2C%20RX_VK_SHOT_EVERY%2C%20RX_VK_SHOT_MENU%2C%20SDL_AUDIODRIVER%2C%20SDL_AUDIO_DRIVER%2C%20VK_DRIVER_FILES.%0A
- The link's length, signed out (`curl`, dummy links to the form, 2026-11-10; nothing of this PC in them):
Link length Padding GitHub's answer
1238 the report's own link (then) sign-in page, the link kept in return_to
1999, 2500, 2998, 3199 %20 sign-in page, the link kept in return_to
3500 %20 400
4000, 4500 %20 500 after the redirect
6000 %20 sign-in page, but return_to dropped (the answers lost)
6000, 6400 x sign-in page, the link kept
6700, 7000, 7500, 7900 x 500
8192 and more x 414

Not tested

  • Windows, at run time. The report's GPU line there (VkPhysicalDeviceDriverProperties on a Windows driver), its CPU, RAM and system line (__cpuid, GlobalMemoryStatusEx, RtlGetVersion, Wine), the path of the log stderr writes to (GetFinalPathNameByHandleA), GetLastError's reason, the redaction of real C:\Users\... logs, File Explorer: compiled and linked only.
  • A windowed run. Only headless runs: the picture taken with a window, HDR (a native HDR frame is copied to 8-bit as the control bridge's shots are) and frame generation were not seen.
  • The link in a browser. Not opened in a browser, signed out or signed in, and no issue was opened from it; only curl signed out (table above). That GitHub fills the form's fields from it is its documented behaviour, not seen here.
  • A real crash's last lines in the game. The after-crash run ended its first start with kill -9, which leaves no CRASH: line, and under that load the first start had not reached a map yet; the test covers the CRASH: line and the run before's map with made-up logs.
  • AMD and Intel drivers' driverName/driverInfo text (only NVIDIA's was read; hardware_report.py classifies typical RADV, AMD and Intel strings correctly when written by hand), a Steam Deck, the AppImage, and the Linux Builder's Play button (since #2279 it gives the game its hookshot_log.txt again, so the report finds this run's log there; not run here).

Risks

  • Something private in a report. The home folder (as a whole folder; on Windows either slash and any case) and the user's name as a folder of any path are replaced in both logs; a switch's value is written only when it is a number, a size or on/off and its name holds none of the words above; other environment variables are not listed; a microphone chosen by name is not named; no saves, no options or env file, no game files go in. Guarded by the test (made-up home and user, Windows cases, alice123 kept, values kept and left out) and, on this machine's real logs, by the read-back above. Not guarded: something private a log line carries in another form (a game folder outside the home named after a person, a machine name in a network path), or a future switch whose secret is a short number under a name without those words.
  • A report written while the game is busy. The press does only cheap work on the menu's thread (flushing the log, reading the environment, /etc/os-release, the device's properties, the menu's rows); copying the logs and writing the folder run on a thread of their own, one report at a time (a second press while it saves does nothing). This run's log is copied as far as it was when the copy began. The picture is waited for at most 3 s.
  • The picture. It shares #926's queue (16 asks at most) and its one readback: a control-bridge shot being read back delays the report's by a present or two. Without a report nothing changes on the present path. The control bridge's log line for a written shot also appears for the report's picture (vk: present N written to ... (the control bridge)).
  • The map: line in every log. It is written only when the map or Link's age changes, from oot_inventory_refresh (at most every 500 ms, where the age is already read). A log that a player posts now says where he was; the names are the game's own map names, which the debug tools' teleport lines already print.
  • A crash that leaves no line (a kill, a freeze, a power cut): the run before's log is attached, but nothing in it says how it ended. A game that stops before its menu can be used cannot make a report; the readme says to attach hookshot_log.1.txt by hand then.

Overlaps

pr_overlap.py --branch bug-report (run on this head, 2026-11-11): 79 open pull requests touch one of these files; 73 merge cleanly with it. The row stores nothing (no Options field, no ini key, no option id), so no option id can clash. Six conflict in this branch's files, each hunk read:

  • #2417 (the menu in 16 languages): it wraps the menu's texts in i18n::tr(...), among them the lines of measure_help, row_value_cell's branch for rows without a value, the help line in draw_options and the compact bar's prompt (and the same line in tests/test_menu_tune.py), where this branch adds the Bug report beside the share row. Whichever lands second wraps the Bug report's texts the same way; both stay.
  • #1521, #1864, #2105, #2459, #2479 (the readme): a row each after Microphone in the menu table (#2459 moves the Microphone row), where this branch adds the Bug report's row; both stay.
  • #2317 (saves and settings in a per-user data folder, logs in logs/) now merges cleanly, but overlaps in meaning: this branch finds the game folder through ini_path() and the logs in the game folder; after #2317 the settings are in the data folder and the logs in logs/. Whichever lands second moves those two reads to game_folder() and log_path().

Questions for you

  1. Is the Insert menu the place you want this, and the game folder the place for the reports (with #2317, the data folder or logs/ could be)?
  2. The report takes the strict reading of CONTRIBUTING.md on hookshot_env.txt (names, and only harmless values). Should CONTRIBUTING.md say so, next to the line that lists the file?
  3. The link carries the form's one-line answers and, when short enough, the options and the switches' names. Do you want it at all, or only report.txt to copy from?

Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 [email protected]

**What this is for:** a player who meets a problem presses one row in the Insert menu, **Gameplay → Bug report**, and gets a folder with everything the game problem form asks for: the form's answers in `report.txt` (version, the map he is in, graphics card and driver version, CPU, RAM and system, the options changed and the switches set), this run's log and the run before's (the crashed one after a restart) with his home folder and user name taken out, and a picture of the game. Maintainers get reports with the log and the hardware from the start instead of asking for them. **What is measured:** on Linux (Omarchy, an Arch Linux; RTX 4090, NVIDIA 615.78.08), on `main` `ba6eef70`: the new test passes with g++ 16 and clang 23 and fails on `main`; its cases for the review's points (NUL bytes in a log, a picture that comes late, which switch values are written, the reason a folder cannot be made); `tests/run_all.py`; headless runs of this head that pressed the row (one pressed it in Kokiri Forest, one after a `kill -9` and a restart: both reports written, read back by a script against the logs they came from (no file holds the home folder or the user name; each log copy is the original line for line apart from those two), the after-crash report's `hookshot_log.1.txt` is the killed start's whole log, and its picture came through #926's `shot_ask`); the length GitHub takes for the link, signed out. **Not measured:** Windows at run time (compiled and linked only), a windowed run, the link opened in a browser (signed out or in), AMD and Intel drivers' strings, a Steam Deck. Refs #2346 (closed by the maintainer: tracked in this pull request). ## Problem - The game problem form (`.github/ISSUE_TEMPLATE/1-game-problem.yml`) requires the version, where in the game, the graphics card **and driver version**, CPU, RAM and operating system, and `hookshot_log.txt` (and `hookshot_log.1.txt` "if you started the game again after the problem"). The player has to find each one by hand. - `hookshot_log.txt` answers only part of it: its first line has the version, but the driver is only Vulkan's packed number (`vk: device 0: NVIDIA GeForce RTX 4090, Vulkan 1.4, driver 0x99d38200, type 2`), there is no line for the system, the CPU or the options changed, and nothing logged where the player was (the host reads `Sequence_CurrentBanc` only for the debug tools). - After a crash a player starts the game again, and the crashed run's log becomes `hookshot_log.1.txt` (`host/hos_main.c`, the rotation added because players sent the new run's log and the crash was gone). It still has to be found and attached. - The logs carry the home folder and the user name in their paths (`fs: mounted content: -> <home>/...`, `vk: pipeline cache <home>/...`), which a player may not want to post. - So reports arrive without the log or the hardware, and `docs/workflow.md` scores such a report at confidence 0.4 and asks for the log first. This gathers what CONTRIBUTING.md already asks ("Include the version, your hardware, where you were and `hookshot_log.txt`"; "Report the version you actually ran: ... the first line of `hookshot_log.txt`"). It changes nothing about where reports go. ## What changed - `gpu/vk/vk_bug_report.h`, `vk_bug_report.cpp` (new, 59 + 602 lines): the packer, without Vulkan and without the menu, so the test compiles it alone. It writes `bug-reports/<YYYY-MM-DD_HH-MM-SS>/` in the game folder on a thread of its own: - `report.txt`: the form's questions under the form's own `### ` headings, so `scripts/hardware_report.py`'s `section()` reads it as an issue: **Hookshot version** (`HOOKSHOT_VERSION`, as the log's first line); **Where in the game** (the last `map:` line of this run's log, and the run before's, and a request to add what you were doing); **Graphics card and driver version** (the device and `VkPhysicalDeviceDriverProperties`' `driverName` + `driverInfo`, e.g. `NVIDIA GeForce RTX 4090, NVIDIA 615.78.08`); **CPU, RAM and operating system** (the CPU's brand string and threads, RAM, `os-release`, desktop and session type on Linux; `RtlGetVersion` and Wine on Windows); **Changes from the defaults** (each Insert-menu row not at its default as the menu shows it; a microphone chosen by its name is not named; then the switches set, below); **hookshot_log.txt** (what is attached, the run before's version and date, each log's first `FATAL`, `CRASH` or `device lost` line); **Screenshots or video**; then a link that opens the form with version, where, GPU, system and settings filled in (GitHub fills issue-form fields from query parameters named by the fields' ids). - **The switches.** CONTRIBUTING.md lists `hookshot_env.txt` among the files never to share, so the report never copies it, and of the switches set (the names `hookshot_env.txt` takes: `RX_`, `HOS_`, `HOOKSHOT_`, `EDEN_`, `NVN_`, `SDL_`; and what hooks other code in: `VK_INSTANCE_LAYERS`, `MANGOHUD`, `ENABLE_VKBASALT`, `LD_PRELOAD` ...) it writes each name, and the value only when it cannot hold anything private: a number of at most 12 characters, a size such as `1920x1080`, or on/off, true/false, yes/no, and never for a name with `TOKEN`, `KEY`, `ROOM`, `SECRET`, `PASS`, `SERVER` or `USER` in it. Every other value reads `(value left out)`: paths, addresses, scripts. Other variables are not listed at all. - **The link** stays within 2000 characters and carries no switch value: the options changed and the switches' names, or, when the names do not fit, how many switches are set. A player who is not signed in is sent to GitHub's sign-in page with the whole link inside it, and that page failed for links from about 3500 characters of escaped text (table below). - `hookshot_log.txt` (this run's log up to the press) and `hookshot_log.1.txt` (the run before's; when this run logs elsewhere, e.g. from a terminal, the game folder's `hookshot_log.txt`). Read by length (`fread`, split on newlines), so NUL bytes, which a run ended by a power cut or a hard kill can leave, are copied as they are. Each line: the home folder (a whole folder; on Windows either slash and any case) becomes `~` or `%USERPROFILE%`, the user's name as a folder of any path `<user>`. A log over 10 MB keeps its first MB and its last 9 MB (GitHub takes files up to 25 MB). - `picture.png`: 1280x720, the game's next frame, without the menu (below). If none comes within 3 s, the report goes without it and says so; a picture that comes later is deleted again. - A folder that cannot be made is reported with the system's reason (`GetLastError()` on Windows, `errno` elsewhere). - `gpu/vk/vk_menu.cpp` (+89): the row before Restart and Quit (`kBugReportRow = kRestartRow - 1`), its help (what goes in, that no saves or game files go in and nothing is sent), its value (Press to save / Saving... / Saved / Not saved), Save in both prompt lines (the full menu's and the compact bar's), the press, `changed_rows_text()` (rows against a default-constructed `Options`), `bug_report_save()`, and the picture's ask, `bug_report_picture()`. It follows Save my pipeline list's pattern (#2447): its value in `row_value_cell`, its line under the help in `draw_options` once pressed (where the folder is and how to send it: File Explorer on Windows, a file manager on Linux; on a Steam Deck, Desktop Mode), and room for that line's longest form in `measure_help`. The game opens no browser or file manager: Game Mode on a Deck has neither. - `gpu/vk/vk_replay.cpp` (+2): `shot_ask_next()`, which calls #926's `shot_ask(path, -1, done, ctx)` from outside its anonymous namespace (declared in `vk_internal.h`). The report's picture is the control bridge's kind of shot: an image and a buffer of its own (1280x720, made at the first ask), copied from `src`, the game's frame, which the menu is never drawn into (`menu_record_image` draws only into the screenshots' image), read back once the GPU is past it and written on a thread. No other line of `do_present` changes. - `gpu/vk/vk_main.cpp` (+11), `vk_internal.h` (+6): `device_report_text()`, and the declaration above. - `host/hos_main.c` (+12): `map: <Sequence_CurrentBanc> (child|adult)` when the map or the age changes, in `oot_inventory_refresh` (on Link's update, at most every 500 ms, where the age is already read for the Debug tab). It is where the report's place comes from, and it puts the place into every log. The names are the game's own map names, which the debug tools' teleport lines already print. - `readme.md`: the row in the menu table, a paragraph on what it saves and which values it leaves out, a pointer from "If the problem continues". - `tests/test_bug_report.py` (new), and `tests/test_menu_tune.py`: its check of the compact bar's prompt line now reads the line with the Bug report in it (the check is otherwise unchanged). - Unchanged: nothing is sent anywhere; no saves, no game files, not `hookshot_options.ini` or `hookshot_env.txt` themselves; no new option or switch; the logs' own rotation; the screenshot path. ## How to try it Insert menu (Insert, F1 or Esc, or the menu button) → **Gameplay** → **Bug report** → Enter or A. The row reads Saved and its help names the folder: `bug-reports/<date>_<time>` in the game folder, with `report.txt`, `hookshot_log.txt`, `hookshot_log.1.txt` and `picture.png`. ## How it was tested **2026-11-11, after the two reviews, head `a75f3ca5` on `main` `ba6eef70`.** Rebased twice today: onto #926, then onto #2042, #2378, #2447 and the rest. The conflicts, each kept on both sides: #926's `shot_ask_record` beside the old readback (which is now gone); #2042's Debug rows where the Bug report row goes before Restart; #2378's `row_value_cell()` and #2447's share row, where the row's value cell and prompt went (the row now follows the share row's pattern, above); a forward declaration in `host/hos_main.c`. #2042's rules hold for the row: it has no value, so `preset_exempt` does not apply (`tests/test_preset_exempt.py` reads only rows with one), and the branch reads no on/off switch. #2042 also took `TOTK_` out of the names `hookshot_env.txt` takes; the report follows (the test compares the two lists). Environment: Omarchy (an Arch Linux), kernel 7.2.15-1-omarchy, RTX 4090 with NVIDIA 615.78.08, i9-13900K, 94 GB, g++ 16.2.1 and clang 23.1.3. - The new test on the branch (`python -I -B tests/test_bug_report.py`, with `--cxx g++` and with `--cxx clang++`; the same line from both): ``` bug report: ok (report.txt as the issue form, both logs redacted, the picture, the long log, the terminal start, switch values, NUL bytes, the folder's reason) ``` Against `main`'s source (the test copied into an export of `ba6eef70`; with both compilers): it fails at its first step, the menu row: ``` at = menu.index('{TAB_GAMEPLAY, "Bug report", nullptr') ValueError: substring not found ``` And the review's NUL case against the old log copy (`fgets` and `line.back()`, put back on its own): ``` bug report: the copy of a log with NUL bytes differs: b'[hos host] hookshot v2.1.0-rc.3-39-gbdfe09c (source 763271ad)\n[hos Main] fs: mounted content: -> ~/Games\n[hos Main] map: KokiriForest_Child (child)\n[hos Main] half a line' ``` It compiles `vk_bug_report.cpp` alone with a driver and writes reports from a made-up game folder inside a made-up home folder (`.../home/alice/Games/Hookshot/`), with paths of the home and of `/run/media/alice/...` in the logs, map lines and a `CRASH:` line in the run before's. It checks: the folder holds exactly the report, both logs and the picture; no file names the home folder, the user as a folder or the game folder's path (`alice123` and `Malice` stay); `hardware_report.py`'s `section`, `classify` and `os_of` read the version, `NVIDIA` / `RTX 40` and Linux from `report.txt`; the place is the last map, and the run before's; the switches: values kept (`HOS_HEADLESS=1`, `60`, `off`, `3840x2160`, `-1`, `1.5`) and left out (a path, a drive path, a menu script, an IP address, a `host:port`, a 13-digit number, and numbers under ROOM, KEY, SERVER, USER and PASSWORD names), none of those values anywhere in `report.txt`, `PATH` and a token not listed, no `%3D` in the link; the run before's crash line; the link's fields and its length (with 98 switches: within 2000, the options and the count in it, every switch in `report.txt`); the menu's way (a thread, one report at a time); no frame means no picture, said so; a picture that comes after the report went without it is deleted; a start from a terminal; two reports in one second; a 22 MB log cut to its first and last part with its middle's map still found; a log with a NUL in a line and 4 KB of NULs at its end copied byte for byte, the map after the NUL found; a folder that cannot be made, with the system's reason; the redaction's Windows cases (either slash, any case); this PC's system line. And the wiring by text: the row and what its help promises, Save in both prompt lines, the press, the picture asked of `shot_ask_next` and copied from the game's frame without the menu, the host's `map:` line, that the switches it lists are the names `hookshot_env.txt` takes (`env_line_allowed`'s), the form's labels and ids. Eight runs at once at load 280 all pass (the first version of the menu's case read the report's state too late under load in the full suite; it now holds the report until it has looked). - The checks the review named and those that read the menu's rows, on this head (with g++ and clang++ where they compile C++): `test_capture_dimensions.py`, `test_capture_slot_ownership.py`, `test_menu_env_overrides.py`, `test_menu_keys.py`, `test_pipeline_share.py`, `test_menu_tune.py`, `test_stick_dead_zone.py`, `test_preset_exempt.py` (62 rows), `test_menu_env_on.py`, `test_env_on.py`, `test_fps_stats.py`, `test_gpu_choice.py`, `test_menu_restart_slots.py`, `test_mouse_settings.py`: all pass. `vk_bug_report.cpp` alone with `-Wall -Wextra -Wpedantic -Werror`: no warning from either compiler. - `python -I -B tests/run_all.py` on Linux, on this head: ``` 440 passed, 1 failed, 20 skipped in 600 s (linux, 32 at once) ``` The one failure is `test_recorder_sync.py`, which ran out of its 600 s at load 250-320 (other work ran beside it) and touches nothing of this branch. An earlier run of the whole suite on this branch failed `test_bug_report.py` once under that load: its menu case read the report's state too late (fixed in the test, above). - Merged into the newest `main` (`fce53453`, nine commits later; it merges cleanly): `test_bug_report.py` (both compilers), `test_menu_tune.py`, `test_pipeline_share.py`, `test_menu_keys.py`, `test_menu_env_overrides.py`, `test_preset_exempt.py` (63 rows), `test_menu_restart_slots.py`, both capture tests: pass. `test_env_on.py` fails there and on `fce53453` alone (`HOS_FPS_LOG (host/oot_high_fps.c:15)`, from #2470), not from this branch. - Windows: `vk_bug_report.cpp` with a small driver compiles and links for x86_64-w64-mingw32 (clang 23 with llvm-mingw 20261104's headers and libraries, `-Wall -Wextra -Werror`: a PE32+ console executable). Not run. - In the game, on a build of this head's code (`v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc)`; the head differs only in two test files), headless, RTX 4090, `RX_VK_PRECOMPILE=0` (shaders compiled when first used), load 200 to 320 (other work ran beside it), the save loaded into Kokiri Forest and the menu script pressing **Gameplay → Bug report**: | Run | What | Harness result | The report | |---|---|---|---| | br-menu2 | the row pressed 55-63 s after the first frame | exit 124 (its 120 s), fatal 0, crash 0, device lost 0, kernel driver lines 0 | `bug-reports/2026-11-11_16-58-06`: report.txt and both logs, **no picture**, and it says so. Rightly: the game showed no frame from 39 s to 107 s (the save loading, shaders compiled as they came, at load 300), took the scripted keys when frames came again, and stopped once more at 109 s | | br-crash3 | a first start killed with `kill -9` after 110 s, then a second start; the row pressed 63 s after its first frame | exit 124 (its 260 s), fatal 0, crash 0, device lost 0, kernel driver lines 0 | `bug-reports/2026-11-11_17-13-48`: report.txt, both logs (`hookshot_log.1.txt` is the killed start's whole log), picture.png (present 1772) | | br-menu3 | the row pressed 130-138 s after the first frame, in play | exit 124 (its 200 s), fatal 0, crash 0, device lost 0, kernel driver lines 0 | `bug-reports/2026-11-11_17-25-32`: report.txt, both logs, picture.png (present 3063, 138 s) | The after-crash run's log lines (the picture's path shortened): ``` [hos BaseProcExecutor5] map: KokiriForest_Child (child) [hos Presentation Thread] menu: bug report asked for [hos host] vk: present 1772 written to ~/Games/.../bug-reports/2026-11-11_17-13-48/picture.png (the control bridge) [hos host] menu: bug report written to bug-reports/2026-11-11_17-13-48: report.txt, hookshot_log.txt (245 KB), hookshot_log.1.txt (117 KB), picture.png ``` Its folder read back against the game folder's logs (`check_report.py`: each copy compared line by line with its original, with the home folder and the user name replaced as the game does): ``` files: hookshot_log.1.txt (119514 bytes), hookshot_log.txt (250199 bytes), picture.png (2765798 bytes), report.txt (3212 bytes) no file holds the home folder or the user name (any case, anywhere) [the checker's line, with the two values left out here] hookshot_log.txt: 1827 of the original's 2991 lines, 12 of them with the home or the user replaced, nothing else changed hookshot_log.1.txt: 991 of the original's 991 lines, 11 of them with the home or the user replaced, nothing else changed hardware_report.py: vendor and class ('NVIDIA', 'RTX 40'), OS Linux / SteamOS ok ``` br-menu2's and br-menu3's folders pass the same check (1387 of 1571 and 3230 of 3230 lines; 2861 of 3679 and 3230 of 3230 lines; only the home folder and the user name replaced). A picture's pixel data is not read as text: br-menu3's picture.png has the bytes `4d 4f 4f 4b 49` among its pixels (row 176), which an any-case byte search reads as a name; the PNG holds only IHDR, IDAT and IEND chunks. The host name is in no text file of either. - Pictures (headless, 1280x720; the first two are the harness's shots with the menu drawn in, the third the report's own file): ![1: the Bug report row selected, Press to save. 2: after Enter, Saved, with the folder under the help. 3: that report's picture.png: the game's frame without the menu](https://gist.githubusercontent.com/mooki000/b86bd3f35969803fb032f71f035844f8/raw/a943230353c26f026eef40ea371a06b754cc084f/hookshot-bugreport-shotask.jpg) 1. br-menu3, the row before the press: **Bug report** selected, **Press to save**, its help (what goes in; no saves or game files; nothing is sent), the prompt bar **Save Enter**. 2. After Enter: **Saved**, and the paragraph under the help names the folder (`bug-reports/2026-11-11_17-25-32`) and how to send it. 3. That report's `picture.png`, taken through `shot_ask` at present 3063: child Link and Navi on the path in Kokiri Forest, hearts and minimap; no menu, not dimmed. ![4: after a kill -9 and a restart, Saved, with its folder under the help. 5: that report's picture.png: the game's frame without the menu](https://gist.githubusercontent.com/mooki000/b86bd3f35969803fb032f71f035844f8/raw/a943230353c26f026eef40ea371a06b754cc084f/hookshot-bugreport-aftercrash.jpg) 4. br-crash3, after the `kill -9` and the restart: **Saved**, the folder `bug-reports/2026-11-11_17-13-48` under the help. 5. That report's `picture.png` (present 1772): Kokiri Forest from low down, facing the bright sky, hearts and minimap; no menu. - The `report.txt` the after-crash run wrote, as a player would attach it (the switches are the test harness's: see which values are written and which are left out): <details><summary>The report.txt that run wrote</summary> ```text Hookshot bug report, made by the Insert menu's Bug report on 2026-11-11 at 17:13. To send it: open https://github.com/NotRexed/Hookshot/issues/new/choose and choose Game problem (or the link at the end of this file, which fills in the answers below), copy the answers below into the fields of the same names, say what happened, and drag the files of this folder, this one too, into the issue. ### Hookshot version v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc) ### Where in the game KokiriForest_Child (child) (The map this run's log names last: add what you were doing there.) ### Graphics card and driver version NVIDIA GeForce RTX 4090, NVIDIA 615.78.08 ### CPU, RAM and operating system 13th Gen Intel(R) Core(TM) i9-13900K (32 threads), 94 GB, Omarchy (Linux 7.2.15-1-omarchy), Hyprland (wayland) ### Changes from the defaults Every Insert menu option is at its default. Switches set (the environment, hookshot_env.txt; a value only when it is a number or on/off, and never for a name with TOKEN, KEY, ROOM, SECRET, PASS, SERVER or USER in it): HOOKSHOT_NO_UPDATE_CHECK=1 HOS_AUDIO=0 HOS_DISCORD=0 HOS_ENV_FILE=(value left out) HOS_HEADLESS=1 HOS_INPUT_BACKGROUND=0 HOS_INPUT_EXCLUSIVE=1 HOS_INPUT_FRAME_CLOCK=1 HOS_INPUT_SCRIPT=(value left out) HOS_PRESENT_TRACE=(value left out) RX_CPM=(value left out) RX_VK_DEVICE=0 RX_VK_FRAME_LOG=(value left out) RX_VK_MENU_SCRIPT=(value left out) RX_VK_PRECOMPILE=0 RX_VK_SHOTS=(value left out) RX_VK_SHOT_EVERY=300 RX_VK_SHOT_MENU=1 SDL_AUDIODRIVER=(value left out) SDL_AUDIO_DRIVER=(value left out) VK_DRIVER_FILES=(value left out) ### hookshot_log.txt hookshot_log.txt: this run's log as it was when the report was made, 245 KB. hookshot_log.1.txt: the run before's log (hookshot v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc), last written 2026-11-11 17:12), 117 KB. In both, the home folder is written ~ and the user's name <user>. ### Screenshots or video picture.png: the game's picture when the report was made, without the menu. ### What this folder holds report.txt (this file), hookshot_log.txt, hookshot_log.1.txt, picture.png. Nothing else from this PC: no saves, no game files, not the options file itself. The link with the answers filled in: https://github.com/NotRexed/Hookshot/issues/new?template=1-game-problem.yml&version=v2.1.0-rc.3-177-g9c538c91%20%28source%20c4f2afbc%29&where=KokiriForest_Child%20%28child%29&gpu=NVIDIA%20GeForce%20RTX%204090%2C%20NVIDIA%20615.78.08&system=13th%20Gen%20Intel%28R%29%20Core%28TM%29%20i9-13900K%20%2832%20threads%29%2C%2094%20GB%2C%20Omarchy%20%28Linux%207.2.15-1-omarchy%29%2C%20Hyprland%20%28wayland%29&settings=Every%20Insert%20menu%20option%20is%20at%20its%20default.%0ASwitches%20set%20%28their%20values%20are%20in%20the%20attached%20report.txt%29%3A%20HOOKSHOT_NO_UPDATE_CHECK%2C%20HOS_AUDIO%2C%20HOS_DISCORD%2C%20HOS_ENV_FILE%2C%20HOS_HEADLESS%2C%20HOS_INPUT_BACKGROUND%2C%20HOS_INPUT_EXCLUSIVE%2C%20HOS_INPUT_FRAME_CLOCK%2C%20HOS_INPUT_SCRIPT%2C%20HOS_PRESENT_TRACE%2C%20RX_CPM%2C%20RX_VK_DEVICE%2C%20RX_VK_FRAME_LOG%2C%20RX_VK_MENU_SCRIPT%2C%20RX_VK_PRECOMPILE%2C%20RX_VK_SHOTS%2C%20RX_VK_SHOT_EVERY%2C%20RX_VK_SHOT_MENU%2C%20SDL_AUDIODRIVER%2C%20SDL_AUDIO_DRIVER%2C%20VK_DRIVER_FILES.%0A ``` </details> - The link's length, signed out (`curl`, dummy links to the form, 2026-11-10; nothing of this PC in them): | Link length | Padding | GitHub's answer | |---|---|---| | 1238 | the report's own link (then) | sign-in page, the link kept in `return_to` | | 1999, 2500, 2998, 3199 | `%20` | sign-in page, the link kept in `return_to` | | 3500 | `%20` | 400 | | 4000, 4500 | `%20` | 500 after the redirect | | 6000 | `%20` | sign-in page, but `return_to` dropped (the answers lost) | | 6000, 6400 | `x` | sign-in page, the link kept | | 6700, 7000, 7500, 7900 | `x` | 500 | | 8192 and more | `x` | 414 | ## Not tested - **Windows, at run time.** The report's GPU line there (`VkPhysicalDeviceDriverProperties` on a Windows driver), its CPU, RAM and system line (`__cpuid`, `GlobalMemoryStatusEx`, `RtlGetVersion`, Wine), the path of the log stderr writes to (`GetFinalPathNameByHandleA`), `GetLastError`'s reason, the redaction of real `C:\Users\...` logs, File Explorer: compiled and linked only. - **A windowed run.** Only headless runs: the picture taken with a window, HDR (a native HDR frame is copied to 8-bit as the control bridge's shots are) and frame generation were not seen. - **The link in a browser.** Not opened in a browser, signed out or signed in, and no issue was opened from it; only `curl` signed out (table above). That GitHub fills the form's fields from it is its documented behaviour, not seen here. - **A real crash's last lines in the game.** The after-crash run ended its first start with `kill -9`, which leaves no `CRASH:` line, and under that load the first start had not reached a map yet; the test covers the `CRASH:` line and the run before's map with made-up logs. - AMD and Intel drivers' `driverName`/`driverInfo` text (only NVIDIA's was read; `hardware_report.py` classifies typical RADV, AMD and Intel strings correctly when written by hand), a Steam Deck, the AppImage, and the Linux Builder's Play button (since #2279 it gives the game its `hookshot_log.txt` again, so the report finds this run's log there; not run here). ## Risks - **Something private in a report.** The home folder (as a whole folder; on Windows either slash and any case) and the user's name as a folder of any path are replaced in both logs; a switch's value is written only when it is a number, a size or on/off and its name holds none of the words above; other environment variables are not listed; a microphone chosen by name is not named; no saves, no options or env file, no game files go in. Guarded by the test (made-up home and user, Windows cases, `alice123` kept, values kept and left out) and, on this machine's real logs, by the read-back above. Not guarded: something private a log line carries in another form (a game folder outside the home named after a person, a machine name in a network path), or a future switch whose secret is a short number under a name without those words. - **A report written while the game is busy.** The press does only cheap work on the menu's thread (flushing the log, reading the environment, `/etc/os-release`, the device's properties, the menu's rows); copying the logs and writing the folder run on a thread of their own, one report at a time (a second press while it saves does nothing). This run's log is copied as far as it was when the copy began. The picture is waited for at most 3 s. - **The picture.** It shares #926's queue (16 asks at most) and its one readback: a control-bridge shot being read back delays the report's by a present or two. Without a report nothing changes on the present path. The control bridge's log line for a written shot also appears for the report's picture (`vk: present N written to ... (the control bridge)`). - **The `map:` line in every log.** It is written only when the map or Link's age changes, from `oot_inventory_refresh` (at most every 500 ms, where the age is already read). A log that a player posts now says where he was; the names are the game's own map names, which the debug tools' teleport lines already print. - A crash that leaves no line (a kill, a freeze, a power cut): the run before's log is attached, but nothing in it says how it ended. A game that stops before its menu can be used cannot make a report; the readme says to attach `hookshot_log.1.txt` by hand then. ## Overlaps `pr_overlap.py --branch bug-report` (run on this head, 2026-11-11): 79 open pull requests touch one of these files; 73 merge cleanly with it. The row stores nothing (no `Options` field, no ini key, no option id), so no option id can clash. Six conflict in this branch's files, each hunk read: - #2417 (the menu in 16 languages): it wraps the menu's texts in `i18n::tr(...)`, among them the lines of `measure_help`, `row_value_cell`'s branch for rows without a value, the help line in `draw_options` and the compact bar's prompt (and the same line in `tests/test_menu_tune.py`), where this branch adds the Bug report beside the share row. Whichever lands second wraps the Bug report's texts the same way; both stay. - #1521, #1864, #2105, #2459, #2479 (the readme): a row each after Microphone in the menu table (#2459 moves the Microphone row), where this branch adds the Bug report's row; both stay. - #2317 (saves and settings in a per-user data folder, logs in `logs/`) now merges cleanly, but overlaps in meaning: this branch finds the game folder through `ini_path()` and the logs in the game folder; after #2317 the settings are in the data folder and the logs in `logs/`. Whichever lands second moves those two reads to `game_folder()` and `log_path()`. ## Questions for you 1. Is the Insert menu the place you want this, and the game folder the place for the reports (with #2317, the data folder or `logs/` could be)? 2. The report takes the strict reading of CONTRIBUTING.md on `hookshot_env.txt` (names, and only harmless values). Should CONTRIBUTING.md say so, next to the line that lists the file? 3. The link carries the form's one-line answers and, when short enough, the options and the switches' names. Do you want it at all, or only `report.txt` to copy from? Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <[email protected]>
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 20:08:10 +00:00
Joey-Magnus (Migrated from github.com) left a comment

This is a thoughtful feature, and the PR is unusually well documented. The redaction rules, the link-length table and the test that compiles vk_bug_report.cpp on its own are all good. I focused on the riskiest parts: the readback on the present path, the threading between the menu, the writer and the replay, the log copy, and what can leak into a report. At the PR's head on Linux, tests/test_bug_report.py, tests/test_capture_dimensions.py and tests/test_capture_slot_ownership.py pass. CI hasn't run yet (only the title check).

  • should fix: merge conflict with main in gpu/vk/vk_replay.cpp and gpu/vk/vk_menu.cpp. The menu one is trivial: the Debug tab gained rows, and Row has an extra trailing field. The replay one matters more: #926 (32693f02) is merged and added shot_ask()/shot_ask_record()/shot_ask_take() at the same spot in do_present. That's an on-demand screenshot with its own image and buffer, read back only once completed() passes it, and written on a thread, which is almost exactly what ReportShot does here. When you rebase, I'd drop ReportShot and have bug_report call shot_ask(dir + "picture.png", -1, done, ctx) with done calling picture_written. That removes the duplicate readback, and it also avoids the next point.
  • minor gpu/vk/vk_replay.cpp:3108 (PR head): with a window, the report's first picture calls offscreen_init(), which allocates off_image at the swapchain's size (about 33 MB at 4K) plus the two shot buffers, and keeps them for the rest of the run, although only the headless path uses off_image. shot_ask_init() on main allocates only the 1280x720 image and one buffer, so switching to it fixes this too.
  • should fix gpu/vk/vk_bug_report.cpp:200-202: if fgets reads a chunk that starts with a NUL byte, buf.data() is an empty C string, line stays empty, and line.back() is undefined behaviour (an abort under _GLIBCXX_ASSERTIONS, garbage otherwise). Logs from a run that ended in a power loss or a hard kill often end in a run of NUL bytes, and the run before's log is exactly what this report is for. It also undercounts at, which shifts the head/tail cut. Read with fread into the buffer and split on '\n' yourself, or at least guard with if (line.empty() || (line.back() != '\n' && ...)) and add n bytes from fgets by length rather than strlen. A test case with a log that ends in a few KB of \0 would cover it.
  • minor gpu/vk/vk_bug_report.cpp:422: on Windows, CreateDirectoryA sets GetLastError(), not errno, so strerror(errno) in the "could not be made" message will be stale or "No error". Use GetLastError() there (or skip the reason on Windows).
  • minor gpu/vk/vk_bug_report.cpp:223-243: switches are filtered by prefix and only paths are left out. As you note, a future switch holding a secret (co-op's room code, a token) would go into report.txt and the link. A cheap guard now: leave out the value of any switch whose name contains TOKEN, KEY, SECRET, PASS or ROOM, the same way as paths. It's cheaper than remembering to update this file when such a switch lands.
  • question (for the maintainer, echoing your own question): CONTRIBUTING.md lists hookshot_env.txt among the files never to share. The report doesn't attach the file, but it does copy its switch names and non-path values into report.txt and the link. That seems fine to me, since the values are toggles and numbers, but the maintainer should decide it explicitly, and if accepted, a line in CONTRIBUTING.md would avoid future confusion.

I checked the rest and it holds up: the picture request and ticket handshake under pic_mu (including withdrawing a request no present took), start()'s compare-exchange allowing one report at a time, the menu row's press placed before the generic !r.value page branch, and the map: line in oot_inventory_refresh (game thread, at most every 500 ms, and only logged on change). Windows' A functions and fopen are fine with UTF-8 paths given the game's UTF-8 manifest (tests/test_utf8_paths.py). Once rebased, please re-run the capture tests, test_bug_report.py and anything that counts the Gameplay or Debug rows.

This is a thoughtful feature, and the PR is unusually well documented. The redaction rules, the link-length table and the test that compiles `vk_bug_report.cpp` on its own are all good. I focused on the riskiest parts: the readback on the present path, the threading between the menu, the writer and the replay, the log copy, and what can leak into a report. At the PR's head on Linux, `tests/test_bug_report.py`, `tests/test_capture_dimensions.py` and `tests/test_capture_slot_ownership.py` pass. CI hasn't run yet (only the title check). - **should fix**: merge conflict with main in `gpu/vk/vk_replay.cpp` and `gpu/vk/vk_menu.cpp`. The menu one is trivial: the Debug tab gained rows, and `Row` has an extra trailing field. The replay one matters more: #926 (`32693f02`) is merged and added `shot_ask()`/`shot_ask_record()`/`shot_ask_take()` at the same spot in `do_present`. That's an on-demand screenshot with its own image and buffer, read back only once `completed()` passes it, and written on a thread, which is almost exactly what `ReportShot` does here. When you rebase, I'd drop `ReportShot` and have `bug_report` call `shot_ask(dir + "picture.png", -1, done, ctx)` with `done` calling `picture_written`. That removes the duplicate readback, and it also avoids the next point. - **minor** `gpu/vk/vk_replay.cpp:3108` (PR head): with a window, the report's first picture calls `offscreen_init()`, which allocates `off_image` at the swapchain's size (about 33 MB at 4K) plus the two shot buffers, and keeps them for the rest of the run, although only the headless path uses `off_image`. `shot_ask_init()` on main allocates only the 1280x720 image and one buffer, so switching to it fixes this too. - **should fix** `gpu/vk/vk_bug_report.cpp:200-202`: if `fgets` reads a chunk that starts with a NUL byte, `buf.data()` is an empty C string, `line` stays empty, and `line.back()` is undefined behaviour (an abort under `_GLIBCXX_ASSERTIONS`, garbage otherwise). Logs from a run that ended in a power loss or a hard kill often end in a run of NUL bytes, and the run before's log is exactly what this report is for. It also undercounts `at`, which shifts the head/tail cut. Read with `fread` into the buffer and split on `'\n'` yourself, or at least guard with `if (line.empty() || (line.back() != '\n' && ...))` and add `n` bytes from `fgets` by length rather than `strlen`. A test case with a log that ends in a few KB of `\0` would cover it. - **minor** `gpu/vk/vk_bug_report.cpp:422`: on Windows, `CreateDirectoryA` sets `GetLastError()`, not `errno`, so `strerror(errno)` in the "could not be made" message will be stale or "No error". Use `GetLastError()` there (or skip the reason on Windows). - **minor** `gpu/vk/vk_bug_report.cpp:223-243`: switches are filtered by prefix and only paths are left out. As you note, a future switch holding a secret (co-op's room code, a token) would go into `report.txt` and the link. A cheap guard now: leave out the value of any switch whose name contains `TOKEN`, `KEY`, `SECRET`, `PASS` or `ROOM`, the same way as paths. It's cheaper than remembering to update this file when such a switch lands. - **question** (for the maintainer, echoing your own question): CONTRIBUTING.md lists `hookshot_env.txt` among the files never to share. The report doesn't attach the file, but it does copy its switch names and non-path values into `report.txt` and the link. That seems fine to me, since the values are toggles and numbers, but the maintainer should decide it explicitly, and if accepted, a line in CONTRIBUTING.md would avoid future confusion. I checked the rest and it holds up: the picture request and ticket handshake under `pic_mu` (including withdrawing a request no present took), `start()`'s compare-exchange allowing one report at a time, the menu row's press placed before the generic `!r.value` page branch, and the `map:` line in `oot_inventory_refresh` (game thread, at most every 500 ms, and only logged on change). Windows' `A` functions and `fopen` are fine with UTF-8 paths given the game's UTF-8 manifest (`tests/test_utf8_paths.py`). Once rebased, please re-run the capture tests, `test_bug_report.py` and anything that counts the Gameplay or Debug rows.
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 21:24:57 +00:00
Joey-Magnus (Migrated from github.com) left a comment

The earlier review is still open, and the branch now conflicts in six files:

  • gpu/vk/vk_bug_report.cpp:206-208: fgets then line.back() on an empty string is UB when a log contains NUL bytes, which hard-kill logs often do.
  • ReportShot repeats #926's shot_ask() readback and calls offscreen_init() in windowed runs, keeping a swapchain-sized image (~33 MB at 4K) for the rest of the run.
  • strerror(errno) after CreateDirectoryA (:428) gives a stale reason; use GetLastError().
  • report.txt and the pre-filled issue link include hookshot_env.txt names and values, which CONTRIBUTING says never to share. At least drop values whose names contain TOKEN, KEY or ROOM.
The earlier review is still open, and the branch now conflicts in six files: - `gpu/vk/vk_bug_report.cpp:206-208`: `fgets` then `line.back()` on an empty string is UB when a log contains NUL bytes, which hard-kill logs often do. - `ReportShot` repeats #926's `shot_ask()` readback and calls `offscreen_init()` in windowed runs, keeping a swapchain-sized image (~33 MB at 4K) for the rest of the run. - `strerror(errno)` after `CreateDirectoryA` (`:428`) gives a stale reason; use `GetLastError()`. - `report.txt` and the pre-filled issue link include `hookshot_env.txt` names and values, which CONTRIBUTING says never to share. At least drop values whose names contain TOKEN, KEY or ROOM.
hckrman101 commented 2026-11-11 22:55:51 +00:00 (Migrated from github.com)

This now conflicts with main in gpu/vk/vk_menu.cpp, gpu/vk/vk_replay.cpp, host/hos_main.c. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.

This now conflicts with main in `gpu/vk/vk_menu.cpp`, `gpu/vk/vk_replay.cpp`, `host/hos_main.c`. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.
mooki000 commented 2026-11-11 23:44:16 +00:00 (Migrated from github.com)

Thank you for both reviews. Everything is in the new head a75f3ca5 (still one commit, rebased onto main ba6eef70). Point by point:

  1. ReportShot repeats shot_ask(), and offscreen_init() keeps a swapchain-sized image in a windowed run (should fix, and the minor of the first review): done as you suggested. ReportShot, its two blocks in do_present and its include are gone. vk_replay.cpp now differs from main by one line: shot_ask_next(), which calls #926's shot_ask(path, -1, done, ctx) from outside its anonymous namespace. The menu's bug_report_picture() asks it for <folder>/picture.png with the report's ticket as ctx, and done calls bug_report::picture_written. So there is one readback path, shot_ask_init()'s 1280x720 image and buffer, and nothing at the swapchain's size. The picture is still the frame without the menu: shot_ask_record copies src, the game's frame, into ask_image; the menu is drawn only into shot_image (with RX_VK_SHOT_MENU) and onto the window. The test now checks that shot_ask_record copies from src and draws no menu. One thing changed in behaviour: a queued ask cannot be withdrawn, so when no picture comes within 3 s the report goes without one as before, and a picture that arrives later is deleted again (new test case). In the game (headless, RTX 4090), a report after a kill -9 and a restart got its picture this way (present 1772), and one pressed while the save was still loading, with no frame for 68 s, said it had none.
  2. fgets then line.back() on an empty string (should fix): done. copy_log reads with fread and splits on '\n' itself; every count is by length, and NUL bytes are copied as they are. New test case: a log with a NUL in the middle of a line (the map: line after it is still found) and 4 KB of NUL bytes at its end. The copy equals the log byte for byte, apart from the redacted home folder. With the old copy_log put back on its own, that case fails (the copy of a log with NUL bytes differs).
  3. strerror(errno) after CreateDirectoryA (minor): done. make_dir returns the reason itself: on Windows GetLastError() with FormatMessageA ("Windows error 3: ..."), elsewhere strerror(errno) right after mkdir. New test case: bug-reports is a file, and the report says why it was not written ((Not a directory) on Linux). The Windows branch compiles and links for x86_64-w64-mingw32 (llvm-mingw 20261104, -Wall -Wextra -Werror); it was not run on Windows.
  4. hookshot_env.txt names and values in report.txt and in the link (should fix, and the question in the first review): I took the strict reading of CONTRIBUTING.md. The report still never copies the file. A switch is listed by name, and its value is written only when it cannot hold anything private: a number of at most 12 characters, a size such as 1920x1080, or on/off, true/false, yes/no. A name containing TOKEN, KEY, ROOM, SECRET, PASS, SERVER or USER never gets its value, whatever it is. Every other value reads (value left out), which covers paths, addresses and scripts. The pre-filled issue link carries no switch values at all, only the names, or just how many switches are set when the names do not fit. The test covers each part: values kept (HOS_HEADLESS=1, RX_VK_SHOT_EVERY=60, off, 3840x2160, -1, 1.5), values left out (a path, a drive path, a menu script, an IP address, a host:port, a 13-digit number, and numbers under ROOM, KEY, SERVER, USER and PASSWORD names), none of those values anywhere in report.txt, and no %3D in the link. The readme says the same. Whether CONTRIBUTING.md should mention the report is the maintainers' call, so I did not touch it (it is a question in the description).
  5. Re-run the capture tests, test_bug_report.py and anything that counts the Gameplay or Debug rows: done on this head (those that compile C++ with both g++ 16 and clang 23): test_capture_dimensions.py, test_capture_slot_ownership.py, test_bug_report.py, test_menu_env_overrides.py, test_menu_keys.py, test_pipeline_share.py, test_stick_dead_zone.py, test_preset_exempt.py (62 rows), test_menu_env_on.py, test_env_on.py, test_fps_stats.py, test_gpu_choice.py, test_menu_restart_slots.py, test_mouse_settings.py: all pass. My first resolution of the rebase broke test_pipeline_share.py, which looks for is_share_row(selr) ? "Save" : "Open" by its text. The prompt lines now read &selr == &rows[kBugReportRow] || is_share_row(selr) ? "Save" : "Open", so that check holds unchanged, and test_bug_report.py checks the Bug report's half the same way.

The rebase: vk_menu.cpp conflicted with #2378 (the value cells moved into row_value_cell()) and #2447 (Save my pipeline list). The Bug report now follows the share row's pattern: its value in row_value_cell's branch for rows without a value, Save in both prompt lists (the full menu's and the compact bar's), its line under the help in draw_options, and room for its longest line in measure_help. host/hos_main.c conflicted only in the forward declarations; both are kept.

Thank you for both reviews. Everything is in the new head `a75f3ca5` (still one commit, rebased onto `main` `ba6eef70`). Point by point: 1. **`ReportShot` repeats `shot_ask()`, and `offscreen_init()` keeps a swapchain-sized image in a windowed run** (should fix, and the minor of the first review): done as you suggested. `ReportShot`, its two blocks in `do_present` and its include are gone. `vk_replay.cpp` now differs from `main` by one line: `shot_ask_next()`, which calls #926's `shot_ask(path, -1, done, ctx)` from outside its anonymous namespace. The menu's `bug_report_picture()` asks it for `<folder>/picture.png` with the report's ticket as `ctx`, and `done` calls `bug_report::picture_written`. So there is one readback path, `shot_ask_init()`'s 1280x720 image and buffer, and nothing at the swapchain's size. The picture is still the frame without the menu: `shot_ask_record` copies `src`, the game's frame, into `ask_image`; the menu is drawn only into `shot_image` (with `RX_VK_SHOT_MENU`) and onto the window. The test now checks that `shot_ask_record` copies from `src` and draws no menu. One thing changed in behaviour: a queued ask cannot be withdrawn, so when no picture comes within 3 s the report goes without one as before, and a picture that arrives later is deleted again (new test case). In the game (headless, RTX 4090), a report after a `kill -9` and a restart got its picture this way (present 1772), and one pressed while the save was still loading, with no frame for 68 s, said it had none. 2. **`fgets` then `line.back()` on an empty string** (should fix): done. `copy_log` reads with `fread` and splits on `'\n'` itself; every count is by length, and NUL bytes are copied as they are. New test case: a log with a NUL in the middle of a line (the `map:` line after it is still found) and 4 KB of NUL bytes at its end. The copy equals the log byte for byte, apart from the redacted home folder. With the old `copy_log` put back on its own, that case fails (`the copy of a log with NUL bytes differs`). 3. **`strerror(errno)` after `CreateDirectoryA`** (minor): done. `make_dir` returns the reason itself: on Windows `GetLastError()` with `FormatMessageA` ("Windows error 3: ..."), elsewhere `strerror(errno)` right after `mkdir`. New test case: `bug-reports` is a file, and the report says why it was not written (`(Not a directory)` on Linux). The Windows branch compiles and links for x86_64-w64-mingw32 (llvm-mingw 20261104, `-Wall -Wextra -Werror`); it was not run on Windows. 4. **`hookshot_env.txt` names and values in `report.txt` and in the link** (should fix, and the question in the first review): I took the strict reading of CONTRIBUTING.md. The report still never copies the file. A switch is listed by name, and its value is written only when it cannot hold anything private: a number of at most 12 characters, a size such as `1920x1080`, or on/off, true/false, yes/no. A name containing `TOKEN`, `KEY`, `ROOM`, `SECRET`, `PASS`, `SERVER` or `USER` never gets its value, whatever it is. Every other value reads `(value left out)`, which covers paths, addresses and scripts. The pre-filled issue link carries no switch values at all, only the names, or just how many switches are set when the names do not fit. The test covers each part: values kept (`HOS_HEADLESS=1`, `RX_VK_SHOT_EVERY=60`, `off`, `3840x2160`, `-1`, `1.5`), values left out (a path, a drive path, a menu script, an IP address, a `host:port`, a 13-digit number, and numbers under ROOM, KEY, SERVER, USER and PASSWORD names), none of those values anywhere in `report.txt`, and no `%3D` in the link. The readme says the same. Whether CONTRIBUTING.md should mention the report is the maintainers' call, so I did not touch it (it is a question in the description). 5. **Re-run the capture tests, `test_bug_report.py` and anything that counts the Gameplay or Debug rows**: done on this head (those that compile C++ with both g++ 16 and clang 23): `test_capture_dimensions.py`, `test_capture_slot_ownership.py`, `test_bug_report.py`, `test_menu_env_overrides.py`, `test_menu_keys.py`, `test_pipeline_share.py`, `test_stick_dead_zone.py`, `test_preset_exempt.py` (62 rows), `test_menu_env_on.py`, `test_env_on.py`, `test_fps_stats.py`, `test_gpu_choice.py`, `test_menu_restart_slots.py`, `test_mouse_settings.py`: all pass. My first resolution of the rebase broke `test_pipeline_share.py`, which looks for `is_share_row(selr) ? "Save" : "Open"` by its text. The prompt lines now read `&selr == &rows[kBugReportRow] || is_share_row(selr) ? "Save" : "Open"`, so that check holds unchanged, and `test_bug_report.py` checks the Bug report's half the same way. The rebase: `vk_menu.cpp` conflicted with #2378 (the value cells moved into `row_value_cell()`) and #2447 (Save my pipeline list). The Bug report now follows the share row's pattern: its value in `row_value_cell`'s branch for rows without a value, Save in both prompt lists (the full menu's and the compact bar's), its line under the help in `draw_options`, and room for its longest line in `measure_help`. `host/hos_main.c` conflicted only in the forward declarations; both are kept.
mooki000 commented 2026-11-11 23:44:17 +00:00 (Migrated from github.com)

The after-crash case now works in the game, on a build of this head's code (v2.1.0-rc.3-177-g9c538c91; the head a75f3ca5 differs from it only in two test files). Headless, RTX 4090, NVIDIA 615.78.08, 2026-11-11 17:10, load 320 -> 235 (other work ran beside it). The game was started twice the way the icon starts it (stderr not a file, so the game keeps its own hookshot_log.txt and moves the last one to hookshot_log.1.txt). The first start was killed with kill -9 after 110 s. The second start loaded the save, and the menu script pressed Gameplay → Bug report 63 s after its first frame.

hs-run br-crash3 exit=124 wall=261.2s ... fatal=0 crash=0 devlost=0 nocore=0 kernel=0 nvrm=0
wrapper: first start 2026-11-11 17:10:48
wrapper: first start ended by kill -9 (status 137)
wrapper: second start 2026-11-11 17:12:39

The second start's hookshot_log.txt (the picture's path shortened here):

[hos BaseProcExecutor5] map: KokiriForest_Child (child)
[hos Presentation Thread] menu: bug report asked for
[hos host] vk: present 1772 written to ~/Games/.../bug-reports/2026-11-11_17-13-48/picture.png (the control bridge)
[hos host] menu: bug report written to bug-reports/2026-11-11_17-13-48: report.txt, hookshot_log.txt (245 KB), hookshot_log.1.txt (117 KB), picture.png

The folder, read back against the game folder's logs (check_report.py, which compares each copy line by line with its original after replacing the home folder and user name the way the game does):

files: hookshot_log.1.txt (119514 bytes), hookshot_log.txt (250199 bytes), picture.png (2765798 bytes), report.txt (3212 bytes)
no file holds the home folder or the user name (any case, anywhere)
hookshot_log.txt: 1827 of the original's 2991 lines, 12 of them with the home or the user replaced, nothing else changed
hookshot_log.1.txt: 991 of the original's 991 lines, 11 of them with the home or the user replaced, nothing else changed
hardware_report.py: vendor and class ('NVIDIA', 'RTX 40'), OS Linux / SteamOS
ok

So the report's hookshot_log.1.txt is the killed start's whole log, and report.txt names it: hookshot_log.1.txt: the run before's log (hookshot v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc), last written 2026-11-11 17:12), 117 KB. The kernel logged no driver line during the run. Two things this run does not show. Under that load the killed start was still loading when it was killed, so its log has no map: line, and the report's "Where" has no "the run before was last in" part. And kill -9 leaves no CRASH: line, so the report names no "first sign of trouble". A real crash's CRASH: line is covered only by the test, with a made-up log. Two earlier attempts on older builds of this branch (br-crash1, br-crash2) never reached the menu: they were still preparing shaders.

1: after a kill -9 and a restart, Saved, with its folder under the help. 2: that report's picture.png: the game's frame without the menu

The after-crash case now works in the game, on a build of this head's code (`v2.1.0-rc.3-177-g9c538c91`; the head `a75f3ca5` differs from it only in two test files). Headless, RTX 4090, NVIDIA 615.78.08, 2026-11-11 17:10, load 320 -> 235 (other work ran beside it). The game was started twice the way the icon starts it (stderr not a file, so the game keeps its own `hookshot_log.txt` and moves the last one to `hookshot_log.1.txt`). The first start was killed with `kill -9` after 110 s. The second start loaded the save, and the menu script pressed **Gameplay → Bug report** 63 s after its first frame. ``` hs-run br-crash3 exit=124 wall=261.2s ... fatal=0 crash=0 devlost=0 nocore=0 kernel=0 nvrm=0 wrapper: first start 2026-11-11 17:10:48 wrapper: first start ended by kill -9 (status 137) wrapper: second start 2026-11-11 17:12:39 ``` The second start's `hookshot_log.txt` (the picture's path shortened here): ``` [hos BaseProcExecutor5] map: KokiriForest_Child (child) [hos Presentation Thread] menu: bug report asked for [hos host] vk: present 1772 written to ~/Games/.../bug-reports/2026-11-11_17-13-48/picture.png (the control bridge) [hos host] menu: bug report written to bug-reports/2026-11-11_17-13-48: report.txt, hookshot_log.txt (245 KB), hookshot_log.1.txt (117 KB), picture.png ``` The folder, read back against the game folder's logs (`check_report.py`, which compares each copy line by line with its original after replacing the home folder and user name the way the game does): ``` files: hookshot_log.1.txt (119514 bytes), hookshot_log.txt (250199 bytes), picture.png (2765798 bytes), report.txt (3212 bytes) no file holds the home folder or the user name (any case, anywhere) hookshot_log.txt: 1827 of the original's 2991 lines, 12 of them with the home or the user replaced, nothing else changed hookshot_log.1.txt: 991 of the original's 991 lines, 11 of them with the home or the user replaced, nothing else changed hardware_report.py: vendor and class ('NVIDIA', 'RTX 40'), OS Linux / SteamOS ok ``` So the report's `hookshot_log.1.txt` is the killed start's whole log, and `report.txt` names it: `hookshot_log.1.txt: the run before's log (hookshot v2.1.0-rc.3-177-g9c538c91 (source c4f2afbc), last written 2026-11-11 17:12), 117 KB.` The kernel logged no driver line during the run. Two things this run does not show. Under that load the killed start was still loading when it was killed, so its log has no `map:` line, and the report's "Where" has no "the run before was last in" part. And `kill -9` leaves no `CRASH:` line, so the report names no "first sign of trouble". A real crash's `CRASH:` line is covered only by the test, with a made-up log. Two earlier attempts on older builds of this branch (br-crash1, br-crash2) never reached the menu: they were still preparing shaders. ![1: after a kill -9 and a restart, Saved, with its folder under the help. 2: that report's picture.png: the game's frame without the menu](https://gist.githubusercontent.com/mooki000/b86bd3f35969803fb032f71f035844f8/raw/a943230353c26f026eef40ea371a06b754cc084f/hookshot-bugreport-aftercrash.jpg)
hckrman101 (Migrated from github.com) reviewed 2026-11-11 23:59:20 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks for the update, and for taking both of Joey-Magnus's reviews point by point (https://github.com/NotRexed/Hookshot/pull/2347#pullrequestreview-5864773066 and the follow-up). I read the new head and the discussion.

Conflict: resolved. GitHub reports the PR as mergeable now (14 commits behind main, no conflict in vk_menu.cpp, vk_replay.cpp or host/hos_main.c), so my earlier merge request is answered.

Joey-Magnus's points: all four look addressed in the code.

  • The duplicate readback is gone: vk_replay.cpp is +2 lines, and the picture goes through #926's shot_ask, so nothing swapchain-sized is kept.
  • copy_log now reads with fread and splits on \n itself, with every count by length.
  • make_dir returns the system's reason (GetLastError() on Windows).
  • Switch values are written only when they cannot hold anything private, and the link carries only names.
    The CONTRIBUTING.md question (should it mention the report's switch list) is still the maintainer's to answer.

Privacy / CONTRIBUTING.md: I checked what goes into the folder. It holds report.txt, the two logs and a screenshot of the game: no saves, no game files, no hookshot_options.ini or hookshot_env.txt. Logs and screenshots are allowed by CONTRIBUTING.md, and the home folder and user name are removed from the logs. The environment is filtered by name, with values mostly left out. Nothing sensitive beyond what an issue needs. The residual risk you list (a game folder outside the home named after a person, a machine name in a network path) is real but small, and the README help text states what is in the report.

Still open (blocks merging): CI. tests/test_bug_report.py fails on Windows (Python source checks, windows-2022, part 2/2):
bug report: the copy of a log with NUL bytes differs: b''
So on Windows the new NUL case (case 6) gets an empty hookshot_log.txt copy, while it passes on Linux with both compilers. Either the Windows copy really loses a log with NULs or the test reads it wrongly; please find out which, since the run before's log with NULs is the case this report exists for. It is the same case that fixes Joey-Magnus's fgets point, so I'd like to see it green on Windows. I could not run it on Windows to say why.

The other CI failures are not this PR's: test_env_on.py fails on HOS_FPS_LOG (host/oot_high_fps.c:16) (that file is not touched here, and it is as on main, from #2470), and test_psychov35.py and test_pcache_header_read.py are the known failures on main.

Nit (host/hos_main.c, the new map: block): memcpy(logged, line, sizeof logged) copies the 100 bytes of line including the ones after the terminator, which are uninitialized. strcpy/snprintf into logged would be cleaner.

So: COMMENT for now. Once the Windows test is green I'd be happy to approve; the design and the code otherwise look good to me.

Thanks for the update, and for taking both of Joey-Magnus's reviews point by point (https://github.com/NotRexed/Hookshot/pull/2347#pullrequestreview-5864773066 and the follow-up). I read the new head and the discussion. **Conflict:** resolved. GitHub reports the PR as mergeable now (14 commits behind main, no conflict in `vk_menu.cpp`, `vk_replay.cpp` or `host/hos_main.c`), so my earlier merge request is answered. **Joey-Magnus's points:** all four look addressed in the code. - The duplicate readback is gone: `vk_replay.cpp` is +2 lines, and the picture goes through #926's `shot_ask`, so nothing swapchain-sized is kept. - `copy_log` now reads with `fread` and splits on `\n` itself, with every count by length. - `make_dir` returns the system's reason (`GetLastError()` on Windows). - Switch values are written only when they cannot hold anything private, and the link carries only names. The CONTRIBUTING.md question (should it mention the report's switch list) is still the maintainer's to answer. **Privacy / CONTRIBUTING.md:** I checked what goes into the folder. It holds `report.txt`, the two logs and a screenshot of the game: no saves, no game files, no `hookshot_options.ini` or `hookshot_env.txt`. Logs and screenshots are allowed by CONTRIBUTING.md, and the home folder and user name are removed from the logs. The environment is filtered by name, with values mostly left out. Nothing sensitive beyond what an issue needs. The residual risk you list (a game folder outside the home named after a person, a machine name in a network path) is real but small, and the README help text states what is in the report. **Still open (blocks merging): CI.** `tests/test_bug_report.py` fails on Windows (Python source checks, windows-2022, part 2/2): `bug report: the copy of a log with NUL bytes differs: b''` So on Windows the new NUL case (case 6) gets an empty `hookshot_log.txt` copy, while it passes on Linux with both compilers. Either the Windows copy really loses a log with NULs or the test reads it wrongly; please find out which, since the run before's log with NULs is the case this report exists for. It is the same case that fixes Joey-Magnus's `fgets` point, so I'd like to see it green on Windows. I could not run it on Windows to say why. The other CI failures are not this PR's: `test_env_on.py` fails on `HOS_FPS_LOG (host/oot_high_fps.c:16)` (that file is not touched here, and it is as on main, from #2470), and `test_psychov35.py` and `test_pcache_header_read.py` are the known failures on main. Nit (`host/hos_main.c`, the new `map:` block): `memcpy(logged, line, sizeof logged)` copies the 100 bytes of `line` including the ones after the terminator, which are uninitialized. `strcpy`/`snprintf` into `logged` would be cleaner. So: COMMENT for now. Once the Windows test is green I'd be happy to approve; the design and the code otherwise look good to me.
mooki000 commented 2026-11-12 00:03:01 +00:00 (Migrated from github.com)

@hckrman101 thanks. It is the test, not the copy, as far as I can tell without a Windows machine.

The Windows failure. Case 6 named its log nul.log. On Windows NUL with any extension is the null device (the project's own guest_path_ok check in tests/test_fs_links.py refuses a/nul.txt for that reason), so the test's write went nowhere, the report then read an empty file, and the copy was b''. The copy itself opens both files in binary mode and counts by length on both systems (copy_log: "rb", "wb", fread with a length).

In f011b21c the log is power-cut.log, and the test now reads its own log back before it asks for the report, so a file that was not written as given fails with a message that says so instead of blaming the copy.

I could not run it on Windows, so Python source checks (windows-2022, part 2/2) on this head is the check. If test_bug_report.py still fails there, my reading is wrong and the copy is at fault after all. I'll look when the job has finished and say which it is.

On Linux, with g++ and with clang++: the test passes; with the copy changed for one run to stop at a NUL, it fails with the copy of a log with NUL bytes differs.

Nit. Taken: snprintf(logged, sizeof logged, "%s", line) instead of the memcpy of the whole buffer.

The other failures. #2575 is the one-line fix for test_env_on.py (HOS_FPS_LOG read with env_on); the two fixtures are fixed by #2562.

@hckrman101 thanks. It is the test, not the copy, as far as I can tell without a Windows machine. **The Windows failure.** Case 6 named its log `nul.log`. On Windows `NUL` with any extension is the null device (the project's own `guest_path_ok` check in `tests/test_fs_links.py` refuses `a/nul.txt` for that reason), so the test's write went nowhere, the report then read an empty file, and the copy was `b''`. The copy itself opens both files in binary mode and counts by length on both systems (`copy_log`: `"rb"`, `"wb"`, `fread` with a length). In `f011b21c` the log is `power-cut.log`, and the test now reads its own log back before it asks for the report, so a file that was not written as given fails with a message that says so instead of blaming the copy. I could not run it on Windows, so `Python source checks (windows-2022, part 2/2)` on this head is the check. If `test_bug_report.py` still fails there, my reading is wrong and the copy is at fault after all. I'll look when the job has finished and say which it is. On Linux, with `g++` and with `clang++`: the test passes; with the copy changed for one run to stop at a NUL, it fails with `the copy of a log with NUL bytes differs`. **Nit.** Taken: `snprintf(logged, sizeof logged, "%s", line)` instead of the `memcpy` of the whole buffer. **The other failures.** #2575 is the one-line fix for `test_env_on.py` (`HOS_FPS_LOG` read with `env_on`); the two fixtures are fixed by #2562.
mooki000 commented 2026-11-12 00:08:09 +00:00 (Migrated from github.com)

The Windows job on f011b21c has finished, and it was the file name: tests/test_bug_report.py passes there now.

pass      10.9s  tests/test_bug_report.py
221 passed, 1 failed, 19 skipped in 138 s (windows, part 2/2, 4 at once)

Python source checks (windows-2022, part 2/2). The one failure left in that job is tests/test_env_on.py (HOS_FPS_LOG on main, #2575), nothing of this pull request.

The Windows job on `f011b21c` has finished, and it was the file name: `tests/test_bug_report.py` passes there now. ``` pass 10.9s tests/test_bug_report.py 221 passed, 1 failed, 19 skipped in 138 s (windows, part 2/2, 4 at once) ``` [Python source checks (windows-2022, part 2/2)](https://github.com/NotRexed/Hookshot/actions/runs/43124382030/job/130872627842). The one failure left in that job is `tests/test_env_on.py` (`HOS_FPS_LOG` on main, #2575), nothing of this pull request.
hckrman101 (Migrated from github.com) approved these changes 2026-11-12 01:02:29 +00:00
hckrman101 (Migrated from github.com) left a comment

Both open points are fixed in f011b21c.

  • The Windows failure: you were right that it was the test. nul.log is the null device on Windows, so nothing was written. The file is now power-cut.log, and the added read-back check will say so if a name ever does that again. tests/test_bug_report.py passes on Windows part 2/2 now (and on Linux).
  • The memcpy nit: the map: block now uses snprintf(logged, sizeof logged, "%s", line), so no more fixed-size copy from line.

The only red checks are tests/test_env_on.py (the HOS_FPS_LOG switch, being fixed by #2575), which fails on main too and is not from this PR. The earlier review's checks (conflict gone, Joey-Magnus's points fixed, privacy: report.txt, two logs and a screenshot with home folder and user name redacted and env values filtered) still hold. Good to merge.

Both open points are fixed in f011b21c. - The Windows failure: you were right that it was the test. `nul.log` is the null device on Windows, so nothing was written. The file is now `power-cut.log`, and the added read-back check will say so if a name ever does that again. `tests/test_bug_report.py` passes on Windows part 2/2 now (and on Linux). - The `memcpy` nit: the `map:` block now uses `snprintf(logged, sizeof logged, "%s", line)`, so no more fixed-size copy from `line`. The only red checks are `tests/test_env_on.py` (the HOS_FPS_LOG switch, being fixed by #2575), which fails on main too and is not from this PR. The earlier review's checks (conflict gone, Joey-Magnus's points fixed, privacy: report.txt, two logs and a screenshot with home folder and user name redacted and env values filtered) still hold. Good to merge.
Commenting is not possible because the repository is archived.
No description provided.