* feat(hooks): user-supplied hook file + one patching router doc (#637)
We shipped three overlapping ways to modify a game with no single door,
and the question that prompted this ("can users put their own patch files
in?") came from someone who works on this core -- so discoverability was
the bug. Answer: yes, two ways already (cheats, soft patching); the one
thing a user could NOT author was a hook. Both halves fixed.
docs/patching-a-game.md routes the four cases -- cheat code, IPS/BPS/UPS,
byte patch, "different emulator settings for one game" (which is a
per-title default, not a patch at all). Cross-linked from README and the
top of rom-patches.md / enhancement-hooks.md so any entry point reaches
the router.
src/core/hookfile.c parses <system_dir>/vj_hooks.txt:
crc=DC187F82
hook=my-patch 0x0012A4 4E71 4E75
Line-based, NOT JSON: libretro-common vendors no JSON parser, and
hand-rolling one adds a large parsing surface to a feature whose whole
job is writing bytes into a ROM. Same shape as vj_netlink.txt, which this
core already reads. Field splitting is hand-rolled rather than strtok_r --
that is POSIX, and msvc05/msvc10 on the GitLab buildbot is exactly where
our C89 lint is structurally blind.
Every fence from docs/enhancement-hooks.md is inherited, none relaxed:
- Same gate, read raw via environ_cb BEFORE the file is parsed, so a file
can never switch on its own gate.
- expect[] mandatory; all-or-nothing per title in the applier.
- Additionally all-or-nothing per FILE: any malformed line discards every
hook parsed. A half-understood patch set is worse than none, and the
applier's own all-or-nothing rule would otherwise be enforced over
hooks we only partly parsed.
- Applied once at content load, so nothing enters the savestate blob --
no new field, no version bump.
- User file wins over a built-in row for the same CRC, with a LOG_WRN
naming both, matching the DB's "user-set values always win" rule.
- Nothing allocates. Every bound is a compile-time constant, so a file
cannot drive allocation or overrun by declaring a count.
- Malformed or absent file never refuses to load the game.
13 new assertions in test/test_titlehook, including the ones that matter:
a malformed line discards the whole file, expect/patch length mismatch is
refused, over-long hex does not overrun the arena, and >TITLEDB_MAX_HOOKS
refuses the file. The parser is pure, so they drive it directly and then
feed the parsed set through the real applier against a real buffer.
Verified: make TEST_EXPORTS=1 test exit 0, zero FAILs; c89-lint clean on
all four touched .c files.
Refs #637
Co-Authored-By: Claude Opus 5 <[email protected]>
* fix(build): add hookfile.c to Package.swift; guard test fopen results (#637)
Three CI failures on the first push, all mine:
- Package.swift repeats Makefile.common's source list (SPM cannot read a
Makefile), and I updated only one side -- which is exactly what the
"duplicated lists in sync" guard exists to catch, and what broke the
SwiftPM build job too. One line, both green.
- clang-tidy flagged four clang-analyzer-nullability warnings in the new
test block: fopen results used without a NULL check. Every fixture
write is now guarded, and the first one bails out rather than
pretending to test a file it could not create.
Verified: check-package-sources.sh OK (70 core sources agree), clang-tidy
clean on all four changed files, make TEST_EXPORTS=1 test exit 0.
Refs #637
Co-Authored-By: Claude Opus 5 <[email protected]>
* fix(hooks): bounded name copy, not strcpy (#637)
clang-analyzer-security.insecureAPI.strcpy on the CI runner. The length
was already validated one branch earlier, so this was safe in fact -- but
the check is right that strcpy is unbounded at the call site regardless of
what the caller checked, and a parser reading an attacker-editable file is
the last place to argue with that.
Local clang-tidy (llvm 21) did not raise it; the CI runner's did. Worth
knowing the two disagree.
Verified: test_titlehook OK (0 failures), make TEST_EXPORTS=1 test exit 0,
c89-lint clean.
Refs #637
Co-Authored-By: Claude Opus 5 <[email protected]>
---------
Co-authored-by: Claude Opus 5 <[email protected]>