fix(builder): a .nup name outside Windows' code page no longer stops the build (#2246) #2253
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2253
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/romfs-stamp-utf8"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #2246.
The problem
The build stops with
after the whole
.nupis extracted. The reporter's file isThe Legend of Zelda™⁚ Ocarina of Time Remake.nup.make_game.pywrites the.nup's file name into the.hookshot_romfsstamp (unpacked by this builder: <name> <size>), and opened that file with the default encoding, which is the code page on Windows. U+205A is not in cp1252. The prefix is 26 characters and⁚is the 21st character of the name, which gives position 46 exactly. The same line reproduces the error on a cp1252 Windows:The change
.hookshot_romfsand its.hookshot_romfs_checkedcopy are written as UTF-8 (write_stamp).read_stamp). A stamp an earlier builder wrote for a name such asZelda™.nupstill matches, so that game is not unpacked a second time..hookshot_romfs_checkedstays a byte copy of the stamp (tests/test_make_game_cache.pycompares the bytes)..hookshot_romfs_headsalready used UTF-8.Tests
New:
test_a_stamp_keeps_a_name_outside_the_code_pageinnx2/test_nup_zstd_heads.py.Run on Windows 11 (code page cp1252), Python 3.14:
python -I -B nx2/test_nup_zstd_heads.py: 6 tests, OKpython -I -B nx2/test_nup_stand_in.py: okpython -I -B tests/test_make_game_cache.py: 11 tests, OK (1 skipped)python -I -B tests/test_missing_nup.py: 8 tests, OKNot tested: a full build from a real
.nupwith such a name.🤖 Generated with Claude Code
I found a cache-identity edge case at
da0d302b. With Python UTF-8 mode enabled,locale.getpreferredencoding(False)returns UTF-8, so the fallback still decodes as UTF-8 and replaces invalid bytes. Two different names can then compare equal:I exercised the actual build entry path with authored inputs and the preferred encoding set to UTF-8. This head made zero extraction calls and kept the old authored assets for the different selected name. A lossless comparison control extracted once and selected the new assets. No game data or real subprocesses were used; input/save sentinels stayed intact.
Could the legacy fallback use the actual Windows ANSI encoding, with strict decoding and a cache miss for an undecodable identity?
locale.getencoding()ignores UTF-8 mode and returns the ANSI code page on Windows. The UTF-8-mode/replacement-character case would be useful regression coverage.I held my #2246 PR publication after finding this one and will coordinate the remaining coverage here.
Retested the authored entry-path collision at
a261ecdc: it now extracts once and selects the new assets instead of reusing the previous name's cache. Input and save sentinels remain intact; no game data or real subprocesses were used. Thegetencoding()+ strict fallback addresses the case I reported. Thanks for picking it up.Looks good. The diagnosis lines up (prefix of 26 +
⁚as the 21st letter = position 46), and the fix is the right shape: the stamp is written as UTF-8 withnewline="", so.hookshot_romfs_checkedstays a byte copy andtest_make_game_cache.py's byte comparison keeps holding.Checked against the UTF-8 path rules: nothing goes through the code page on the write side any more, and the code page is only used as a strict, read-only fallback for stamps an older builder wrote.
.hookshot_romfsand_checkedare only read and written inmake_game.py(the tests write them as UTF-8 already), so there's no second reader left on the default encoding.Both platforms: on Windows
locale.getencoding()gives the ANSI code page even in UTF-8 mode, so an oldZelda™.nupstamp still matches and isn't unpacked again. On Linux the old stamps were already UTF-8 in practice, so the first decode takes them, and under a C locale the fallback is a strict ASCII decode, which can only miss.getencoding()needs 3.11+, which is fine with the bundled Python and CI's 3.12.Arkex-ai's UTF-8-mode collision (two names becoming equal through U+FFFD) is fixed by the strict decode +
""on failure, and they confirmed it ata261ecdc. The second test covers exactly that case. One harmless corner: instand_in_damaged_files, if both.hookshot_romfsand_checkedare undecodable they compare equal as"", but_checkedis a byte copy of the stamp, so that's the correct answer anyway.CI: everything passes except
appimage, which fails on main too (a Launchpad connection timeout in the container setup), so it isn't from this PR. The new tests are innx2/test_*.py, whichrun_all.pyalready picks up.github-sync referenced this pull request2026-11-12 06:22:54 +00:00