feat(saves): import and export Hookshot saves; a cleaner saves script and guide #20

Merged
HumanBean merged 1 commit from HumanBean/Longshot:saves-hookshot into main 2026-11-12 23:33:09 +00:00
Owner

Why

Since the rename, scripts/oot_saves.py looks only in Longshot's folders, so a Hookshot player can't bring their save over to Longshot (or take it back). This adds Hookshot as a source and a target, and cleans up the script and its guide.

What changed

  • import hookshot / export hookshot. Hookshot's saves are found in $XDG_DATA_HOME/hookshot on Linux (Hookshot's host/hos_main.c before e2a4d453). On Windows they are in %USERPROFILE%\Games\Hookshot and the version folders under it (the 2.x builders' default game folder, and the folders the LongshotLauncher importer searches). --hookshot DIR or --from/--to point elsewhere. hookshot counts in the running-game check.
  • No conversion is needed. Longshot's save code is Hookshot's: the rename changed only comments in host/nn_fs.c and host/nn_applet.c. It is the same one user id (5a3c9e1f0b7d24680000000000000001, from user_uid), the same save/<id> layout, and the same files (account.sav, option.sav, slot_NN/progress.sav, caption.sav). Neither project writes journal or .tmp files: nn::fs::Commit is a no-op and the game writes the files directly. So the files are copied byte for byte.
  • Matches the launcher's importer (LongshotLauncher PR #1, Core/HookshotSaves.cs), without depending on it. It uses the same folders, and the same checks before a Hookshot import: only save files, no link, no empty file and none over 4 MB, at least one slot, and a progress.sav in each. Like the launcher, a copy into Longshot or Hookshot that has no save yet creates save/<user id> instead of failing; on Windows the game folder has to exist. Like the launcher, it also checks both sides for a pending NX2navi save.
  • Cleanup:
    • removed the Hookshot-era aliases to-lichii, to-longshot and the hidden --lichii;
    • Longshot's Windows side now defaults to the builder's game folder %USERPROFILE%\Games\Longshot instead of requiring --longshot;
    • slots must match slot_NN;
    • refuse_links is one loop;
    • the special-cased Windows error is gone.
  • docs/saves.md is rewritten as a player guide. It covers where Longshot's saves are, the script, importing from Hookshot, the emulators, copying by hand and a troubleshooting table, and links to the launcher's saves guide. That page lands with the launcher (LongshotLauncher PR #1), so the link 404s until then. "What was tested" keeps its facts and names Hookshot where the old text had been renamed to Longshot.
  • tests/test_oot_saves.py grows from 35 to 47 tests:
    • Hookshot discovery on Linux and Windows (including version folders);
    • a byte-for-byte import that leaves Hookshot untouched;
    • export, including to a Hookshot that has no save;
    • --hookshot/--from;
    • the Windows import into a game folder;
    • five ways a Hookshot save is refused;
    • the running Hookshot;
    • USER_ID checked against host/nn_applet.c and host/nn_fs.c;
    • the old aliases rejected.

Measurements

Before: main @ d3bd2805
After: saves-hookshot @ 1928fbab
Hardware: Linux (CachyOS), no game run
Scene: none (a script on made-up saves in temporary folders)
Method: python -I -B tests/test_oot_saves.py and LONGSHOT_VULKAN_HEADERS=/usr/include python -I -B tests/run_all.py, once each

Before After
test_oot_saves.py 35 tests, OK 47 tests, OK
import hookshot unknown emulator hookshot copies Hookshot's save into Longshot, backup first
tests/run_all.py 488 passed, 2 failed, 38 skipped 488 passed, 2 failed, 38 skipped
Ran 47 tests in 0.123s
OK
488 passed, 2 failed, 38 skipped in 85 s (linux, 24 at once)
FAIL tests/test_bake_portability.py      (zstandard missing here; fails on main too)
FAIL tests/test_builder_log_read_only.py (libtk missing here; fails on main too)

Not tested

  • A real Hookshot save played on in Longshot, and any run on Windows. The tests use made-up files. The claim that no conversion is needed rests on the source, as described above.
  • The Windows paths are tested with Linux paths and WINDOWS patched.

Screenshots

Nothing on screen.

Issues

See LongshotLauncher PR #1 (the launcher's Hookshot importer, kept consistent with this).

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or this PR is a draft. (A save script: tested on made-up saves, no game run.)
  • Measurements before and after, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. (Test output; one run each.)
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • AI assistance, if any, recorded with Assisted-by: / Co-Authored-By: trailers. (Made with an AI agent; no trailers, by the maintainer's rule for this account.)
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • Screenshots for a change that shows on screen.
  • A row in docs/tested-configurations.md for each measured run.
  • MKWVK_API_VERSION bumped (gpu/vk/vk_api.h's MkwVkHost or MkwVkRenderer changed).
  • The SPIR-V headers regenerated with glslang (a shader changed).
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (Standard-library Python, no host code.)
  • Docs updated.
## Why Since the rename, `scripts/oot_saves.py` looks only in Longshot's folders, so a Hookshot player can't bring their save over to Longshot (or take it back). This adds Hookshot as a source and a target, and cleans up the script and its guide. ## What changed - **`import hookshot` / `export hookshot`.** Hookshot's saves are found in `$XDG_DATA_HOME/hookshot` on Linux (Hookshot's `host/hos_main.c` before e2a4d453). On Windows they are in `%USERPROFILE%\Games\Hookshot` and the version folders under it (the 2.x builders' default game folder, and the folders the LongshotLauncher importer searches). `--hookshot DIR` or `--from`/`--to` point elsewhere. `hookshot` counts in the running-game check. - **No conversion is needed.** Longshot's save code is Hookshot's: the rename changed only comments in `host/nn_fs.c` and `host/nn_applet.c`. It is the same one user id (`5a3c9e1f0b7d24680000000000000001`, from `user_uid`), the same `save/<id>` layout, and the same files (`account.sav`, `option.sav`, `slot_NN/progress.sav`, `caption.sav`). Neither project writes journal or `.tmp` files: `nn::fs::Commit` is a no-op and the game writes the files directly. So the files are copied byte for byte. - **Matches the launcher's importer** (LongshotLauncher PR #1, `Core/HookshotSaves.cs`), without depending on it. It uses the same folders, and the same checks before a Hookshot import: only save files, no link, no empty file and none over 4 MB, at least one slot, and a `progress.sav` in each. Like the launcher, a copy into Longshot or Hookshot that has no save yet creates `save/<user id>` instead of failing; on Windows the game folder has to exist. Like the launcher, it also checks both sides for a pending NX2navi save. - **Cleanup:** - removed the Hookshot-era aliases `to-lichii`, `to-longshot` and the hidden `--lichii`; - Longshot's Windows side now defaults to the builder's game folder `%USERPROFILE%\Games\Longshot` instead of requiring `--longshot`; - slots must match `slot_NN`; - `refuse_links` is one loop; - the special-cased Windows error is gone. - **`docs/saves.md`** is rewritten as a player guide. It covers where Longshot's saves are, the script, importing from Hookshot, the emulators, copying by hand and a troubleshooting table, and links to [the launcher's saves guide](https://git.magicbean.dev/MagicBeanDev/LongshotLauncher/src/branch/main/docs/saves.md). That page lands with the launcher (LongshotLauncher PR #1), so the link 404s until then. "What was tested" keeps its facts and names Hookshot where the old text had been renamed to Longshot. - **`tests/test_oot_saves.py`** grows from 35 to 47 tests: - Hookshot discovery on Linux and Windows (including version folders); - a byte-for-byte import that leaves Hookshot untouched; - export, including to a Hookshot that has no save; - `--hookshot`/`--from`; - the Windows import into a game folder; - five ways a Hookshot save is refused; - the running Hookshot; - `USER_ID` checked against `host/nn_applet.c` and `host/nn_fs.c`; - the old aliases rejected. ## Measurements Before: main @ d3bd2805 After: saves-hookshot @ 1928fbab Hardware: Linux (CachyOS), no game run Scene: none (a script on made-up saves in temporary folders) Method: `python -I -B tests/test_oot_saves.py` and `LONGSHOT_VULKAN_HEADERS=/usr/include python -I -B tests/run_all.py`, once each | | Before | After | |---|---|---| | test_oot_saves.py | 35 tests, OK | 47 tests, OK | | `import hookshot` | `unknown emulator hookshot` | copies Hookshot's save into Longshot, backup first | | tests/run_all.py | 488 passed, 2 failed, 38 skipped | 488 passed, 2 failed, 38 skipped | ``` Ran 47 tests in 0.123s OK 488 passed, 2 failed, 38 skipped in 85 s (linux, 24 at once) FAIL tests/test_bake_portability.py (zstandard missing here; fails on main too) FAIL tests/test_builder_log_read_only.py (libtk missing here; fails on main too) ``` ## Not tested - A real Hookshot save played on in Longshot, and any run on Windows. The tests use made-up files. The claim that no conversion is needed rests on the source, as described above. - The Windows paths are tested with Linux paths and `WINDOWS` patched. ## Screenshots Nothing on screen. ## Issues See LongshotLauncher PR #1 (the launcher's Hookshot importer, kept consistent with this). ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or this PR is a draft. (A save script: tested on made-up saves, no game run.) - [x] Measurements before and after, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. (Test output; one run each.) - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [ ] AI assistance, if any, recorded with `Assisted-by:` / `Co-Authored-By:` trailers. (Made with an AI agent; no trailers, by the maintainer's rule for this account.) - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [ ] Screenshots for a change that shows on screen. - [ ] A row in `docs/tested-configurations.md` for each measured run. - [ ] `MKWVK_API_VERSION` bumped (`gpu/vk/vk_api.h`'s `MkwVkHost` or `MkwVkRenderer` changed). - [ ] The SPIR-V headers regenerated with glslang (a shader changed). - [x] A game-free test added for the fix, failing on `main` and passing here. - [ ] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (Standard-library Python, no host code.) - [x] Docs updated.
oot_saves.py looked only for Longshot's folders after the rename, so a Hookshot player could not bring
a save over. import hookshot and export hookshot copy between them: Linux $XDG_DATA_HOME/hookshot,
Windows %USERPROFILE%\Games\Hookshot and its version folders, or --hookshot DIR; the process
hookshot counts in the running-game check. The files are the same, so nothing is converted. A
Hookshot save is checked as the launcher checks it before it is copied.

A copy into Longshot or Hookshot with no save yet makes save/<the one user id> instead of failing.
Longshot's Windows side defaults to the builder's game folder. Gone: to-lichii, to-longshot and
--lichii. docs/saves.md is rewritten as a player guide.
HumanBean deleted branch saves-hookshot 2026-11-12 23:33:09 +00:00
Sign in to join this conversation.
No description provided.