Skip to content

package/rcheevos: RetroAchievements client library as a shared lib - #182

Merged
mcfbytes merged 1 commit into
masterfrom
feat/rcheevos
Sep 16, 2026
Merged

mcfbytes merged 1 commit into
masterfrom
feat/rcheevos

Conversation

@mcfbytes

@mcfbytes mcfbytes commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Adds package/rcheevos — RetroAchievements' own client library (achievement/leaderboard evaluation, the RA web API marshalling rapi, game-identification hashing rhash, and rc_client) as librcheevos.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.md so that refactor's scorecard stays honest: 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 — git grep -i rcheevos over 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 a generic-package driving $(TARGET_CC) directly, the shape package/lzma-sdk already 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. Diffing include/ across all 55 tags, three PATCH releases changed caller-allocated public struct layouts:

Release Change
v10.7.1 added char owns_self; to the public rc_runtime_t
v10.3.3 inserted const char* display_name; into the middle of rc_api_login_response_t
v6.0.1 RC_ALIGNMENT 8sizeof(void*) — shifts padding library-wide on 32-bit

A stale consumer meeting a changed layout is memory corruption, not an error — and the MiSTer binary lives on /media/fat and survives rootfs reflashes, so linux.img and 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 include md5_init/md5_append/md5_finish and six AES_* 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. libchdr gets this from its version script; rcheevos has none, so visibility is how it is obtained.

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 are hidden and the installed headers promise an API the .so does 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/ or third_party/ dir, so the tree looks like all upstream code. 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)
  • src/rhash/aes.{c,h}kokke/tiny-AES-c @ f06ac37, Unlicense

Both grant-bearing headers are in LICENSE_FILES and hash-pinned, so legal-info ships the texts rather than claiming MIT over object code they cover. Neither is unbundled to /usr/lib, and docs/main-shared-libs.md records why so it is not re-litigated: OpenSSL 3.x deprecates the low-level MD5 API that 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.

Renovate

Tracked for future bumps: a github-tags custom manager over RetroAchievements/rcheevos (loose, v prefix kept inside currentValue so the hash-sync loop's ref and filename stay in sync), the lib-pin label, and both hash-sync allow-lists (paths: trigger and HASH_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

  • 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 0001 symbols, 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.sh181 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 needed.
  • make legal-info collects all three licence texts; Buildroot's utils/check-package reports 0 warnings; shellcheck clean.
  • renovate-config-validator passes and a real --platform=local extraction resolves the dep with the v prefix preserved in replaceString — the validator checks shape only, as renovate.json itself 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 to v12.4.0 and a deliberately wrong hash — it followed the new ref, rewrote the line to 7fb1a43b… (independently confirmed against Buildroot's own .../archive/<ref>/<name>.tar.gz URL form), and left the three licence-file lines alone. The refresh branch is the one that matters: an already-current run never executes the rewrite at all, so it could not catch a stale-write bug of the kind that once left linux.hash untouched 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

@mcfbytes
mcfbytes force-pushed the feat/rcheevos branch 2 times, most recently from 872a695 to bfa4ed6 Compare September 16, 2026 15:30
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
mcfbytes merged commit 082302c into master Sep 16, 2026
8 checks passed
@mcfbytes
mcfbytes deleted the feat/rcheevos branch September 16, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant