package/rcheevos: RetroAchievements client library as a shared lib - #182
Merged
Merged
Conversation
mcfbytes
force-pushed
the
feat/rcheevos
branch
2 times, most recently
from
September 16, 2026 15:30
872a695 to
bfa4ed6
Compare
Adds the RetroAchievements client library -- achievement/leaderboard
evaluation, the RA web API marshalling (rapi), game-identification
hashing (rhash) and rc_client -- as librcheevos.so, enabled in the
DE10-Nano userspace profile at 273 KiB stripped.
WHY IT IS NOT PART OF THE SHARED-LIB REFACTOR, since it lands in that
menu: libchdr and lzma-sdk exist to replace code Main_MiSTer already
vendors, and each is done when Main stops carrying a copy. rcheevos
replaces nothing and nothing links it yet -- git grep over Main and
this tree is empty. It is shipped so a consumer can link it, the way a
distro ships a library ahead of its dependents, and it is tracked
separately in docs/main-shared-libs.md so the refactor's own scorecard
stays honest.
Upstream ships no Unix build system (no CMakeLists, no top-level
Makefile -- only a MinGW/x86 unit-test Makefile and Package.swift), so
this is a generic-package driving $(TARGET_CC) directly, the shape
package/lzma-sdk already established here. No dependencies beyond libc,
libm and pthread: rhash's hash_zip.c walks the zip central directory
and never inflates, so unlike libchdr there is no bundled zlib to
unbundle.
THE SONAME IS THE FULL VERSION, librcheevos.so.12.5.0, and unlike
lzma-sdk's identical policy this one is backed by upstream's own
history rather than by analogy. Diffing include/ across all 55 tags,
three PATCH releases changed caller-allocated public struct layouts:
v10.7.1 added a field to rc_runtime_t, v10.3.3 inserted one into the
MIDDLE of rc_api_login_response_t, and v6.0.1 redefined RC_ALIGNMENT
from 8 to sizeof(void*), which shifts padding library-wide on a 32-bit
target. A stale consumer meeting a changed layout is memory corruption,
not an error, and the Main binary lives on /media/fat and survives
rootfs reflashes -- so linux.img and the binary that links this are
routinely out of sync. Every bump must be a clean refuse-to-load.
BUILT -DRC_SHARED -fvisibility=hidden, which is the point of the
package: 264 exported symbols instead of 477. The 213 suppressed
include md5_init/md5_append/md5_finish and six AES_* entry points, and
ELF interposition is first-definition-wins, so exporting those into a
process carrying its own md5 -- which Main does, lib/md5 -- is a silent
wrong-function bind rather than a link error. libchdr gets the same
guarantee from its version script; rcheevos has none, so visibility is
how it is obtained. Patch 0001 is load-bearing for that pairing:
rc_util.h is the only public header upstream left un-annotated, so
without it nine declared entry points would be hidden and the installed
headers would promise an API the .so does not export. It is written to
be upstreamable and not yet submitted.
LICENSE IS COMPOUND, FLAGGED PER CONTRIBUTING SECTION 6. There is no
deps/ or third_party/ dir, so the tree looks like it is all upstream's
own code -- but two vendored files compile into the shipped .so and
carry their own grants: src/rhash/md5.{c,h} (L. Peter Deutsch, the zlib
licence verbatim; upstream Buildroot spells this same file "Zlib (md5)"
in package/rtty) and src/rhash/aes.{c,h} (kokke/tiny-AES-c, Unlicense).
Both grant-bearing headers are in LICENSE_FILES and hash-pinned, so
legal-info ships the texts instead of claiming MIT over object code
they cover. Neither is unbundled to /usr/lib and
docs/main-shared-libs.md records why: OpenSSL 3.x deprecates the
low-level MD5 API rcheevos threads through 12 files, and aes is reached
only by hash_encrypted.c, which is entirely Nintendo 3DS -- so the real
lever there is -DRC_HASH_NO_ENCRYPTED (measured: 8,088 bytes), left on
deliberately.
Tested:
- Builds clean from dirclean; -Wl,--no-undefined caught a missing -lm
(fmod) during development, which is what it is there for.
- A consumer compiles -Wall -Wextra against pkg-config, links, and RUNS
under qemu-arm: runtime init, rapi URL building, console/error
lookups, the patch-0001 symbols and rc_version_string() -> "12.5".
That test is what caught rc_version.h living in src/ rather than
include/ despite being exported; it is now installed.
- scripts/ci-tests.sh: 181 passed, 0 failed, 7 skipped, including the
new librcheevos.so.* rootfs assertion.
- scripts/check-defconfigs.sh OK -- the profile select lands in the
resolved config with no defconfig edit. It adds exactly ONE bare line
to package/mister-userspace/Config.in, with no comment, matching the
four other BR2_EXTERNAL packages that profile selects which carry
their own "depends on" -- munt and midilink have the identical
BR2_TOOLCHAIN_HAS_THREADS dependency, dualsensectl and 7zip have
four or five lines apiece, and not one of them is annotated there.
The threads rationale lives in package/rcheevos/{Config.in,rcheevos.mk},
which is where munt keeps its own.
- make legal-info collects all three licence texts; Buildroot's
utils/check-package reports 0 warnings; shellcheck clean.
- Renovate: renovate-config-validator passes AND a real
--platform=local extraction resolves the dep, preserving the "v"
prefix in replaceString (validator checks shape only -- renovate.json
says so). The hash-sync generic loop was run against the real pin: it
derived the same URL, filename and sha256 and reported
already-current -- and, against a fixture with the pin moved back to
v12.4.0 and a deliberately wrong hash, it followed the new ref and
rewrote the line correctly while leaving the three licence lines
alone. That second branch is the one that matters: an
already-current run never executes the rewrite at all.
Signed-off-by: Michael C. Ferguson <michael.christopher.ferguson@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mcfbytes
force-pushed
the
feat/rcheevos
branch
from
September 16, 2026 15:34
bfa4ed6 to
5d9a1d6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
package/rcheevos— RetroAchievements' own client library (achievement/leaderboard evaluation, the RA web API marshallingrapi, game-identification hashingrhash, andrc_client) aslibrcheevos.so.12.5.0, enabled in the DE10-Nano userspace profile at 273 KiB stripped.Why, and why it is not part of the shared-lib refactor
It lands in the "Main_MiSTer shared libraries" menu, but it is the odd one out and is tracked separately in
docs/main-shared-libs.mdso that refactor's scorecard stays honest:libchdrandlzma-sdkexist to replace code Main_MiSTer already vendors, and each is done when Main stops carrying a copy. rcheevos replaces nothing —git grep -i rcheevosover both Main and this tree is empty. It ships so a consumer can link it, the way a distro ships a library ahead of its dependents.Upstream ships no Unix build system (no CMakeLists, no top-level Makefile — only a MinGW/x86 unit-test Makefile and
Package.swift), so this is ageneric-packagedriving$(TARGET_CC)directly, the shapepackage/lzma-sdkalready established. No dependencies beyond libc, libm and pthread.The two decisions worth reviewing
Full-version SONAME. Same policy as
lzma-sdk, but backed by upstream's own history rather than by analogy. Diffinginclude/across all 55 tags, three PATCH releases changed caller-allocated public struct layouts:v10.7.1char owns_self;to the publicrc_runtime_tv10.3.3const char* display_name;into the middle ofrc_api_login_response_tv6.0.1RC_ALIGNMENT8→sizeof(void*)— shifts padding library-wide on 32-bitA stale consumer meeting a changed layout is memory corruption, not an error — and the
MiSTerbinary lives on/media/fatand survives rootfs reflashes, solinux.imgand the binary linking this are routinely out of sync. Every bump must be a clean refuse-to-load. The cost is accepted and stated: a pure-bugfix patch release also forces a consumer rebuild.-DRC_SHARED -fvisibility=hidden, which is the point of the package: 264 exported symbols instead of 477. The 213 suppressed includemd5_init/md5_append/md5_finishand sixAES_*entry points. ELF interposition is first-definition-wins, so exporting those into a process carrying its own md5 — which Main does,lib/md5— is a silent wrong-function bind, not a link error.libchdrgets this from its version script; rcheevos has none, so visibility is how it is obtained.0001is load-bearing for that pairing:rc_util.his the only public header upstream left un-annotated, so without it nine declared entry points are hidden and the installed headers promise an API the.sodoes not export. Written to be upstreamable; not submitted — this repo does not open upstream PRs without being asked.Licence is compound — flagged per CONTRIBUTING §6
There is no
deps/orthird_party/dir, so the tree looks like all upstream code. Two vendored files compile into the shipped.soand carry their own grants:src/rhash/md5.{c,h}— L. Peter Deutsch, the zlib licence verbatim (upstream Buildroot spells this same fileZlib (md5)inpackage/rtty)src/rhash/aes.{c,h}—kokke/tiny-AES-c@f06ac37, UnlicenseBoth grant-bearing headers are in
LICENSE_FILESand hash-pinned, solegal-infoships the texts rather than claiming MIT over object code they cover. Neither is unbundled to/usr/lib, anddocs/main-shared-libs.mdrecords why so it is not re-litigated: OpenSSL 3.x deprecates the low-level MD5 API that rcheevos threads through 12 files, andaesis reached only byhash_encrypted.c, which is entirely Nintendo 3DS — so the real lever there is-DRC_HASH_NO_ENCRYPTED(measured: 8,088 bytes), left on deliberately.Renovate
Tracked for future bumps: a
github-tagscustom manager overRetroAchievements/rcheevos(loose,vprefix kept insidecurrentValueso the hash-sync loop's ref and filename stay in sync), thelib-pinlabel, and both hash-sync allow-lists (paths:trigger andHASH_SYNC_PACKAGES— it must be in both or the refresh fails silently). The manager's description tells a future reviewer to read every bump diff for public struct changes.Testing
dirclean.-Wl,--no-undefinedcaught a missing-lm(fmod) during development — which is what it is there for.-Wall -Wextraagainstpkg-config, links, and runs underqemu-arm: runtime init, rapi URL building, console/error lookups, the0001symbols,rc_version_string()→12.5. That test is what caughtrc_version.hliving insrc/rather thaninclude/despite being exported; it is now installed.scripts/ci-tests.sh— 181 passed, 0 failed, 7 skipped, including the newlibrcheevos.so.*rootfs assertion.scripts/check-defconfigs.shOK — the profileselectlands in the resolved config with no defconfig edit needed.make legal-infocollects all three licence texts; Buildroot'sutils/check-packagereports 0 warnings;shellcheckclean.renovate-config-validatorpasses and a real--platform=localextraction resolves the dep with thevprefix preserved inreplaceString— the validator checks shape only, asrenovate.jsonitself warns. The hash-sync generic loop was exercised on both its branches: already-current against the real pin (same URL, filename and sha256), and refreshed against a fixture with the pin moved back tov12.4.0and a deliberately wrong hash — it followed the new ref, rewrote the line to7fb1a43b…(independently confirmed against Buildroot's own.../archive/<ref>/<name>.tar.gzURL form), and left the three licence-file lines alone. The refresh branch is the one that matters: analready-currentrun never executes the rewrite at all, so it could not catch a stale-write bug of the kind that once leftlinux.hashuntouched while the job reported success.Incidental
While re-deriving Renovate counts I found two pre-existing stale numbers that predated the 2026-09-10 Realtek package deletion (
renovate.json's extraction stats,docs/renovate.md's "23 customManagers"). Recounted rather than incremented, with the drift noted rather than papered over.TASKS.md's hash-sync item gets partial credit — the loop's URL/filename/hash logic is now exercised against a real pin, though not yet inside a real PR run, so the item stays open.🤖 Generated with Claude Code