The site's one-liners pointed at develop, so any installer change went live
to users the moment it merged, before a release. Point every installer URL at
master: the site's RAW_URL and source links, the README, the scripts' own usage
text, the nightly notes, and install.sh's RAW, which --auto-update re-fetches.
Co-authored-by: Claude Opus 5.5 <[email protected]>
* feat(app): sha256 for verified downloads
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(app): CRC-keyed community cheat set in CheatManager
CheatManager loads <communityDir>/<CRC32>.cht for the running cartridge by CRC,
independent of the filename/title-matched database set. The pack is guarded by
vj_rom_crc32 like a revision-bound database set (unbound or mismatched: every
cheat unsupported plus a restriction), crc 0 (discs) never loads one.
Toggles persist under cp.<id> (database keeps on.<id>); ids are unchanged so an
updated pack keeps toggles for unchanged cheats and a changed cheat comes back
off, and cp.* keys that match no cheat are pruned on load. apply() orders
database, community (5000+i), then manual codes (10000+i). New public API:
loadChtText, setCommunityDir, reloadCommunity, communitySet,
removeCommunityToggles, CheatOrigin on isEnabled/setEnabled.
Also corrects two stale comments in cheats_test.cpp (sha256 vectors, linked
sources).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(app): drop dead prune loop, document setEnabledByName fall-through
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(app): community cheat store service
CheatStore fetches the community manifest, installs a cartridge's pack after
verifying it (size, sha256, vj_rom_crc32 guard, parse), tracks per-pack state,
and feeds CheatManager. Network and worker are injected hooks; results are
applied on the main thread through pump().
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(app): harden cheat store against throwing hooks, racing remove, message floods
A throwing fetch hook can no longer leave stop() hanging (RAII job slot plus
catch); remove() refuses while an install for the same CRC is in flight; and
manifest messages are capped at 8 entries of 200 bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(app): Community cheats section in the Cheats window
Wires CheatStore into the app (#1071): the store, its one-request worker and
the http::get fetch hook (any status but 200 is an error, the store's cancel
flag is passed through) are set up in cheatsInit with downloads gated by the
single Library "Download art and cheat packs" switch (library.art, label
renamed) and cheats.community.url as the base URL override. The store is
pumped every frame, told of game load/close, and stopped (cancel first, then
the worker) before the cheat manager goes.
The Cheats window gains a "Community cheats" section between Database cheats
and Manual codes (install / update / remove / check states, credit line,
collapsed conversion notes, 256-write warning, hardcore line); both sections
share drawCheatRows, which now passes CheatOrigin. Buttons are disabled while
a request is in flight and stay live in hardcore, where only toggles are off.
--cheats-community-install is the headless twin (fetch the manifest, install
the loaded cartridge's pack); cheat_community_install runs it against a
loopback server serving site/cheats, then offline, then with nothing to
install from.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(cheats): describe the community cheat store (#1071)
Privacy page: a row for the project website's cheat store (when it is asked,
what is sent: the pack index and the one pack named by the cartridge's CRC32,
nothing else) and cheats/community/ in the local-files list; the art row
follows the renamed "Download art and cheat packs" switch. App page, app
README, the converter README and WHATSNEW describe installing packs from the
Cheats window.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(app): marshal cheat store game events to the main thread, report turned-off cheats
The store is main-thread only but cheatsAfterLoad / cheatsOnClose run on the
emulation thread in threaded mode. They now post the load / close
(CheatStore::postGameLoaded / postGameClosed, last post wins) and pump()
applies it before finished requests, so refresh(), announce() and the message
toasts stay on the main thread.
After an Update, the section says how many cheats that were on changed and
came back off (CheatManager::lastPrunedEnabled, CheatStore::turnedOff; cleared
by the next load or Remove). A store that was never reached says so instead of
"No community pack", and Check for updates is shown disabled rather than
hidden while checking or with downloads off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(app): keep an unreadable community pack removable, pump the store during a halt
The Cheats window derived Remove (and Update) from cm.communitySet(), so a
<CRC>.cht that exists but does not parse showed sidecar text with no way out.
Remove and Update now follow the store state alone; only the cheat rows need
the parsed set, and an unreadable or restricted pack gets a one-line notice.
The database "no set matches" status is dimmed when a community pack already
supplies cheats, a greyed Update says downloads are off, and haltedFrame pumps
the store so installs finish during a debugger halt.
Also documents the emulation-thread toast/notice contract in cheatsAfterLoad
and tidies style (comma-operator statements, std::set, include order).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(app): keep the community store fixtures byte-exact on Windows
The fixture pack's sha256 and size are pinned in the fixture manifest.
Git for Windows' autocrlf turned its LF into CRLF on checkout, so
cheats_unit failed verifyPack on every Windows app job. Mark the
fixtures -text so they are checked out exactly as committed.
Refs #1071
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(app): headless community install repairs a locally edited pack
--cheats-community-install skipped install() when the installed pack's
bytes no longer matched its sidecar (EditedLocally), then failed its own
Installed check, although the Cheats window offers Update in that state.
Install there too when the store offers the pack. The loopback smoke test
now hand-edits the installed pack and expects the command to replace it.
Refs #1071
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* docs(cheats): community cheat store design and plan (#1071)
Refs #1071
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs(cheats): map printf_notify to app toasts
Refs #1071
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(cheat-import): BigPEmu API map; int_full settings omitted; printf_notify ignored
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(cheat-import): refuse side effects in message args; scope Unsupported.call to API gaps
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(cheat-import): committed cartridge identity table (hashes only)
scan_cartridges.py walks a ROM directory (read-only) and writes a sorted
FNV1a64 -> CRC32 table with size, title and name; no ROM bytes or paths.
load_cartridges rejects duplicate fnv/crc, bad hex and format != 1. The
generated cartridges.json (152 rows) lets CI map community script hashes
to the CRC32 the app binds packs to without any ROMs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(cheat-import): community cheat pack generator (#1071)
build_packs.py converts every pinned community C cheat source with convert.py
and writes the site/cheats tree: per-cartridge .cht packs (one per CRC32, sources
merged, identical cheats deduped, same-name cheats suffixed), manifest.json,
the unsupported.json worklist with per-call API usage, index.html and README.
Deterministic, bounded fetch of one codeload tarball per source, --source-dir for
local checkouts, --summary and --bump for the bump workflow. sources.json pins
jguffer/BigPEmu_Scripts at 2421150.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* data(cheats): generate community packs from jguffer/BigPEmu_Scripts@2421150
23 cartridge packs (72 cheats, 9 partial) plus the unsupported.json worklist
(55 entries), manifest and index, generated with build_packs.py from the pinned
codeload tarball; byte-identical to a local checkout run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(cheat-import): worklist unmatched literals beside a hit; bound fetch errors and member count
- identity row for table-missing literals in a file that also has a hit
- http.client.HTTPException during fetch is a FetchError (exit 2)
- tar expansion cap counts a header block per member and caps member count
- Collector uses the api_map given to build()
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* data(cheats): regenerate for generator fix (unmatched-literal identity rows)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(site): publish community cheat packs under /cheats/
build_site.py copies the committed site/cheats tree verbatim; check_site.py
gains check_cheats (manifest format/sha256/bytes/CRC guard, unlisted .cht,
unsupported.json, index.html links, 256 KiB manifest and 1 MiB tree limits)
with unit tests. pages.yml watches site/cheats and tools/cheat-import. The
converter README now documents the author's permission, the credit, the
sources -> cartridges -> packs workflow and how to add a source; the
community_test harness and its fixtures are removed (the committed packs and
the regeneration diff are the conversion contract).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* ci(cheats): regen-diff job, concurrency, weekly pin-bump PR
cheat-import.yml: add site/cheats/** to the path filters, a per-ref
concurrency group, drop the community_test.py fetch step (the files are
gone), and add a packs job that regenerates twice, diffs the runs and the
committed tree, flags unmapped BigPEmu calls, then runs the site-check tests
and build_site + check_site.
cheat-packs-bump.yml: weekly (Mon 06:23 UTC) poll of each pinned github
source; on movement, bump the pin, regenerate, validate, force-push the fixed
branch cheats/pin-bump and open or refresh a develop PR (validation failures
head the body instead of being dropped), then dispatch cheat-import.yml on the
branch because GITHUB_TOKEN PRs do not fire pull_request workflows.
test_committed_sources no longer pins the commit/date value: every bump PR
would otherwise fail the unit tests; only the shape is asserted.
Refs #1071
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* ci(cheats): gate bump workflow to the upstream repo, drop bot trailer
Only libretro/virtualjaguar-libretro opens bump PRs (the Provenance-Emu
fork runs crons too), with a 30 minute job timeout. The bot-authored bump
commit no longer carries a Claude co-author trailer.
Refs #1071
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* ci(cheats): pin pycparser, force LF, add job timeouts
Pin pycparser==3.11 (newest release that installs on the Python 3.10 CI leg;
2.22, 2.23, 3.0, 3.11 and 3.12 all regenerate byte-identical packs). Add a
root .gitattributes forcing LF for the generator and generated tree so pack
bytes cannot drift by checkout. Give both jobs a 20 minute timeout.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(cheats): dedupe pack header title, refuse control-character paths
The pack's first line printed the title twice when the cartridge name began
with the title. Print the name alone in that case. Fetch and local-dir reads
now fail closed on any path containing a control character, since names can
reach ::warning:: annotations and PR bodies.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* data(cheats): regenerate packs with deduplicated header title
Line 1 of each pack no longer repeats the title when the cartridge name
already starts with it. Regenerated from the pinned source commit; two runs
and a --source-dir run are byte-identical.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* ci(cheats): even indentation on a bump-workflow continuation line
editorconfig-checker wants multiples of 2 in workflow files.
Refs #1071
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(installer): retain portable data and qualify published packages
* docs: record published Mac and clean Linux qualification evidence
* test(packaging): add clean Intel macOS qualification lane
* fix(ci): use distro audio packages and authenticated qualification metadata
* test(packaging): qualify downloaded Windows setup upgrade and uninstall
* test(packaging): require real upgrades, core loading and immutable candidate evidence
* test(installer): run Linux core qualification with a virtual GL display
* test(installer): resolve release tags for qualification provenance
LLDB still attaches and reads memory against the m68k-description stub
but has no m68k triple. The jag-* lldb helpers called a nonexistent
monitor command; use process plugin packet monitor. A watched 32-bit
access outside main RAM stops twice at the same pc (documented, not
de-duplicated: that would add work to the memory hot path). The design
doc now states the stop-reply thread rule once.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
brew install gdb has m68k; lldb attaches but has no m68k triple
(memory only); detach/disconnect both work; watchpoints work (one
slot); set endian big is still required and now lives in jconnect
rather than a global top-level setting; GPU/DSP are monitor targets.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
The zip holds the notarized, stapled Virtual Jaguar.app as its only top-level
entry; CI unpacks it and re-verifies signature and staple. install.sh loses its
DMG path (--app-format dmg is now rejected) and its tests follow.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
- install.ps1: the interactive channel choice is a local assignment ($script:
missed the parameter under & ([scriptblock]::Create(...))); silence progress
for 5.1; portable zip is one top folder, its CONTENTS go to the destination;
setup default args are /VERYSILENT /NORESTART /SUPPRESSMSGBOXES and the Inno
AppId uninstall key is checked first.
- install.sh: AppImage menu entry and icon use io.github.libretro.VirtualJaguar
(hicolor tree extracted from the AppImage); FUSE probe checks /sbin/ldconfig
and multiarch dirs without globs (zsh); a nightly receipt beats the project
version Info.plist/dpkg report, so a release is still offered over a nightly.
- tests and CI: portable file:// URLs, narrower dry-run snapshots, 5.1-safe
child-process error handling, nightly-vs-release checks, documented
PSScriptAnalyzer exclusions.
Refs #840
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
install.sh gains --what core|app|both (and --core/--app/--both), --app-dest,
--app-format, --force, --allow-unsigned and the VJ_INSTALL_BASE_URL /
VJ_INSTALL_RETROARCH / VJ_INSTALL_NO_TTY overrides. The app is the macOS zip
or DMG (/Applications, else ~/Applications), the Linux AppImage
(~/.local/bin + .desktop) or the .deb via apt/dpkg, picked from the userland,
not the kernel. Updates are detected from the machine (Info.plist, dpkg, the
AppImage path) with an install receipt as a hash cache for nightlies. Every
download, core included, is checked against SHA256SUMS.txt; a channel with no
app build says so instead of erroring. Unattended runs stay core-only unless
this script installed the app. Gatekeeper protections are never stripped
silently. Also: --auto-update off no longer reinstalls first, and --help works
under zsh.
install.ps1 is the Windows twin (5.1 and 7, irm | iex friendly): core into the
cores folder discovered from retroarch.cfg / registry with the word size read
from the retroarch.exe PE header, app as the portable zip (+ Start-menu
shortcut) or the silent setup.exe, -DryRun, -Force, SHA-256 verification.
test/installer/ holds hermetic suites for both over a fake file:// release
tree; .github/workflows/installer.yml runs them (plus shellcheck and
PSScriptAnalyzer) on Linux, macOS and Windows PowerShell 5.1 / 7.
Refs #840
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
* feat(debug): GDB remote stub Phase 2/3/4 -- breakpoints, GPU/DSP threads, docs
Phase 2: RSP ack/nak byte handling (the gap that stalled every real
m68k-elf-gdb on qSupported), Z0-Z4 breakpoints/watchpoints, c/s/vCont,
register and memory writes, the blocking halt loop (GDBHalt() freezes
retro_run() in place until continue/step/disconnect), a client
disconnect that disarms everything and resumes, and the
virtualjaguar_gdb_wait / virtualjaguar_gdb_halt_timeout core options.
Per-target armed counters (gdbArmed68K/GPU/DSP) plus a 256-entry
direct-mapped PC cache gate the three per-instruction hooks
(M68KInstructionHook, GPUExec, DSPExec) -- one load and one
never-taken branch when nothing is armed, never patching emulated
memory. Memory watchpoints ride jaguar.c's pre-existing (but
previously dead) bpmActive/bpmAddress1 pair via the new
GDBMemWatchHit(), single-slot by design.
Phase 3: GPU and DSP as GDB threads 2/3 with a custom
org.atari.jaguar.risc target description (R0-R31=0-31, PC=32,
FLAGS=33, fixed forever) served over qXfer:features:read, thread
enumeration/selection (qfThreadInfo/Hg/Hc/qC), and monitor commands
(disasm/regs/halt/trace/watch) via qRcmd. The RISC disassembler is
shared, not duplicated: src/debug/gdbdisasm.h is used by both
gdbtarget.c (live) and the refactored test/tools/gpu_disasm_dump.c
(offline).
Phase 4: docs/gdb-stub-guide.md, a user-facing guide covering attach,
the frontend-freeze halt model, the RISC disassembly gap, and the
symbol-level (not source-level) debugging ceiling.
New accessors added or un-gated from VJ_TRACE-only in gpu.c/dsp.c
(GPUGetControl, GPUSetReg/PC/Flags, DSPGetPC/Control,
DSPSetReg/PC/Flags, DSPGetReg/GPUGetFlags un-gated) so the stub can
read and write GPU/DSP state in every build.
Verification: make -j4 clean; make TEST_EXPORTS=1 test green (0
failures, 75 suites, including 45/45 gdbstub protocol tests);
c89-lint clean on every touched src/ file; savestate digests
byte-identical across stub-disabled / enabled-unattached /
enabled-attached-zero-breakpoints (test/tools/gdb_determinism_probe);
a live RSP client verifies the ack byte and no-ack transition end to
end (test/tools/gdb_attach_probe.py) and a real Z0 breakpoint halting
real (synthetic, injected) 68K execution at the exact armed PC
(test/tools/gdb_breakpoint_probe.py).
Perf A/B NOT measured this pass: the host stayed under sustained
unrelated load (other processes, load average 10-26 over repeated
checks) for the whole session. VJ_GDB_STUB_DISABLE_HOOKS is wired at
all three hook sites so the A/B can be run later on an idle machine --
see the PR body.
Refs #652
* feat(debug): add gdb/lldb tooling for the GDB stub (connect scripts, plugins, installer)
tools/gdb/connect.sh (+ connect.cmd for Windows) finds a gdb on the
machine and launches it pre-loaded with jaguar.gdbinit (set defaults
plus jconnect/jdisasm/jregs/jhalt/jtrace/jwatch aliases over the
stub's `monitor` commands) and jaguar_gdb.py (richer jag-disasm,
jag-regs, jag-trace, jag-halt, jag-watch, jag-info-registers commands
with argument checking and a pretty-printed, FLAGS-decoded register
dump for the GPU/DSP threads). jaguar_lldb.py is the same convenience
layer against LLDB's Python API, since LLDB speaks the same RSP wire
protocol. install.sh appends an idempotent, clearly-marked block to
~/.gdbinit and ~/.lldbinit (never clobbering existing content, never
duplicating on re-run), following tools/install.sh's as_root() guard
pattern for the same reason: this user's shell aliases rm/cp/mv to
their -i forms, and a prompt with no TTY blocks forever.
GPU/DSP register numbering (R0-31=0-31, PC=32, FLAGS=33) is read from
src/debug/gdbstub.h, not reinvented. Every command degrades gracefully
-- no connection, an older core, or an unsupported monitor subcommand
all print a short message instead of a traceback.
While testing this against a live core, found and filed two stub-side
protocol bugs as follow-up tasks (not fixed here -- out of scope for
tooling, and each needs its own test coverage): GDBTargetResetState()
never resets gdbSession.noAckMode, breaking the second debugger
connection in a session; and unsupported RSP packets get no reply at
all instead of the required empty packet, which hangs a real LLDB
gdb-remote attach (confirmed via a proxied, byte-logged connection)
and plausibly a real gdb too.
Refs #652
* docs: point README and the gdb-stub guide at tools/gdb/
README.md's only prior GDB mention was the unrelated Jaguar GameDrive
GDBIOS bindings. Add a short section linking docs/gdb-stub-guide.md
and the new tools/gdb/ helper scripts.
The guide gets a "Faster: tools/gdb/" pointer under Quick start, plus
an honest "Known limitations" bullet for a stub-side bug found while
building that tooling: an unsupported RSP packet gets no reply at all
(should be an empty $#00 packet), which hangs a real LLDB gdb-remote
attach and plausibly a real gdb's target remote too -- confirmed via a
proxied, byte-logged LLDB connection. Filed separately for a fix; this
is a documentation-only change.
Refs #652
Consistency with the repo's convention for the other guard scripts, raised in
review on #625.
rm/cp/mv are commonly aliased to the interactive -i variants. A prompt with
nothing attached to answer it does not fail -- it blocks forever, which is the
worst failure mode for an installer someone is running over SSH.
Aliases do not expand in a non-interactive script, so this was not a live bug
here. It is still the convention, and the reason it exists (a hung `rm -i` once
sat for nine hours in this repo) applies to anything that might later be
sourced, run under `sh -i`, or copied as a starting point.
New as_root helper picks the right form per path:
- no sudo needed -> `command cp`, which bypasses any alias or shell function
- sudo needed -> plain `sudo cp`, which needs no guard because sudo execs
the binary and never a shell alias, and could not take
one anyway since `command` is a shell builtin
The two remaining bare `cp` mentions are inside printed undo instructions --
those are text a human types, where `command cp` would only confuse.
Verified the guard actually does something: with rm/cp shadowed by prompting
stubs, the unguarded call hits the stub and the guarded one does not.
syntax and build selection under sh, bash, zsh, dash identical
fresh install / re-install with backup works, RetroArch loads it
error paths (bad --dest, --auto-update off) unchanged
Co-Authored-By: Claude Opus 5 <[email protected]>
Users are hand-picking .so files off the releases page and getting it wrong.
The failure is silent: RetroArch says only "Failed to open libretro core".
The specific trap is that builds are named by board AND word size -- rpi4 is
32-bit armhf, rpi4_64 is aarch64 -- and `uname -m` is the wrong way to choose.
Raspberry Pi OS shipped a 64-bit kernel with a 32-bit userland for years, so
uname reports aarch64 while every binary on the system is 32-bit. The core has
to match RETROARCH, not the kernel.
tools/install.sh reads the word size and machine out of the frontend's own
binary (ELF class + e_machine on Linux, the running slice on macOS) and aborts
before writing anything if the downloaded core does not match.
Finds the core directory by reading RetroArch's own libretro_directory out of
retroarch.cfg rather than carrying a per-distro table of paths. That is
authoritative everywhere, including the read-only-root distributions where a
guessed path would simply be wrong, and it does not rot when a distro moves
things. RetroPie is the one special case, because it keeps each core in its own
package directory that emulators.cfg points at instead.
POSIX sh, safe to pipe into a shell. Offers latest/nightly/pinned-tag, an
optional systemd-timer or cron auto-updater, --dry-run, --list, and --yes.
Prompts read /dev/tty because stdin is the script when piped; with no tty it
takes defaults rather than hanging.
Testing found two bugs worth recording:
- Under zsh -- macOS's default shell, so a likely invocation -- `for a in
$archs` does not word-split, so the universal-binary slice picker never
matched and silently chose x86_64 on an Apple Silicon Mac. Verified: sh
said macos-arm64 and zsh said macos-x86_64 on the same machine. Now a
split-free `case`, and all four shells agree.
- Piped into a shell, $0 is the shell name and not a file, so --help ran
`sed` on "sh" and the re-run hints printed "sh --dest ...". Both now go
through a SELF that falls back to the curl one-liner.
Verified: ELF detection agrees with file(1) on all 6 real Linux builds across
i386/arm/x86_64/aarch64; all 14 board+wordsize combinations map to assets that
exist in the release; syntax clean under sh, bash, zsh and dash, which all
choose the same build; discovery finds the real RetroArch on this machine by
config; install, re-install with backup, and the post-install load check all
work end to end -- RetroArch loaded the freshly downloaded core.
build_site.py gains {{RAW_URL}}, derived from REPO_URL so the curl one-liner on
the site cannot drift from the repository the rest of the page points at.
Supersedes the RetroPie-only script this replaces, which was never released.
Co-Authored-By: Claude Opus 5 <[email protected]>
Consolidated fix pass over the 9-task Jaguar toolchain plan's final
whole-branch review:
- Add a no-push "docker build" validation step to jaguar-toolchain.yml so
the Dockerfile is actually exercised in CI; correct
jaguar-toolchain-image.yml's header comment, which falsely claimed that
coverage already existed.
- Add tools/jaguar-toolchain/setup.sh to jaguar-toolchain-image.yml's
paths: trigger -- the Dockerfile COPYs it, so editing it must republish.
- Align jaguar-toolchain.yml on actions/checkout@v7 (repo convention,
matches its sibling workflow).
- Add an OCI description LABEL to the Dockerfile's final stage crediting
42Bastian for new_bjl/lyxass, since the published image itself (not just
the README) redistributes those Unlicense-credited binaries.
- README: state plainly that BootIntro-style demos needing
`jagcrypt -tursi` still fail to build against the pinned pc_jagcrypt (a
real upstream gap, 38/54 JaguarDemos Makefiles affected) without
retracting the true claim that toolchain discovery/build wiring works;
add the jagcrypt binary to the pin-refresh verification checklist.
- Add timeout-minutes + a concurrency block (PR-triggered workflow) to
guard against rln's plain-HTTP fetch hanging; tighten the smoke step's
`command -v` check to a per-tool loop so a missing tool actually fails
the build.
Docker was unavailable in this environment (as in Task 7), so the new
validation step and LABEL are verified by YAML/shell syntax checks only,
not an actual build -- documented in
.superpowers/sdd/2026-08-23-jaguar-toolchain/final-fix-report.md.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
New `Jaguar toolchain` workflow: builds the pinned rmac/rln/lyxass/
pc_jagcrypt via `make jaguar-toolchain-build`, then assembles and links
two minimal smoke sources to prove the toolchain actually works. Gates
PIN / setup.sh / Dockerfile changes.
Both smoke sources were iterated against the real assemblers, not
written from memory:
* hello68k.s uses `.text`, not `.org` -- rmac rejects `.org` in a 68K
section without `-fr` ("permitted only in GPU/DSP/OP, 56001, 6502 and
68k (with -fr switch) sections"). The load address goes on `rln -a`
instead, and `-fb` (BSD) is the object format rln consumes.
* hellogpu.s puts `.run` BEFORE the code. lyxass gates every byte-emit
on `Global.genesis`, which only `p_run` sets, so code assembled before
`.run` is silently discarded -- lyxass still exits 0 and still writes
a 12-byte header-only file whose code-length field is zero.
That second behaviour is why the workflow does not use a bare `test -s`
on the lyxass output: it asserts the file is larger than the 12-byte
BS94 header and that the header's code-length field is non-zero. Both
assertions were verified to fail on a deliberately-broken source, so the
gate is not vacuous.
Verified on macOS by extracting the workflow step's own `run:` block
from the YAML and executing it: hello68k.j64 links to `60 FE` (bra.s to
self), hellogpu.o assembles to 16 bytes ending `D7E0 E400` (jr to self +
delay-slot nop).
Ports tools/jaguar-toolchain/setup.sh's fetch/build/env logic to
PowerShell so Windows contributors and CI runners can use the same
pinned toolchain sources. Reads the same PIN file next to this
script -- no duplicated tool URLs/SHAs.
Reconciled against the real setup.sh (Tasks 1-3, not the pre-Tasks-1-3
draft in the plan): matches JAGUAR_TOOLCHAIN_DIR override, the
cat-file-e probe + fallback fetch for a pinned SHA that a partial
clone's ref-only fetch may not have reachable, and setup.sh's
build_one/fail-count-and-continue build loop (one tool failing does
not abort the others; -Build exits 1 if any failed, 2 if a tool was
never fetched or no make/mingw32-make is on PATH).
Per-tool make invocations pass explicit variable overrides derived
from reading each pinned tool's actual Makefile, in place of each
Makefile's own uname/OSTYPE-based Windows autodetection (which only
works inside an MSYS2 shell, not guaranteed when mingw32-make is
invoked from a plain PowerShell prompt): STD=gnu99 for rmac,
SYSTYPE=WIN32 for rln (rln.h has real WIN32 vs __GCCUNIX__ code path
differences, not just a build flag), SYSTYPE=__GCCWIN32__ for
pc_jagcrypt. lyxass needs no override (its OSTYPE branches only affect
a macOS linker flag and the unused `install` target).
pc_jagcrypt's Makefile unconditionally runs `strip --strip-all` after
linking regardless of SYSTYPE -- on macOS that needs setup.sh's
strip-shim workaround because BSD strip only takes short options, but
MinGW-w64's strip is GNU strip and supports --strip-all natively, so
that specific workaround does not apply on Windows. The real reason to
force SYSTYPE=__GCCWIN32__ there is the Makefile's UPX-not-found
fallback, which otherwise defaults to Unix `ls`/`which` -- neither a
native Windows command -- and would fail the final recipe line right
after a successful link+strip.
UNVERIFIED ON WINDOWS: this session runs on macOS with no Windows host
to actually build rmac/rln/lyxass/pc_jagcrypt against. What WAS
verified on this host: pwsh 7 was installed via `brew install
powershell` specifically to check this script (not assumed available);
the file parses cleanly via
[System.Management.Automation.Language.Parser]::ParseFile with zero
errors; -Env's PATH/BJL_ROOT output matches setup.sh's env output
structurally (semicolon vs colon separator only); Read-Pin was
exercised against the real PIN file and returned correct URL/REF/SHA
for all 5 tools; -Fetch was run for real against all 5 upstream git
remotes (including the nonstandard tiddly.mooo.com:5000 rln host) and
landed at the exact pinned SHAs; -Build's control flow (per-tool
try/catch, fail-count aggregation, exit 1 vs exit 2) was verified
against real failures obtained by forcing the Windows-specific
SYSTYPE=__GCCWIN32__ path through this Mac's native (non-MinGW) make
and strip -- a failure there is an artifact of testing a
Windows-targeted build config on a non-Windows host, not evidence
against the Windows-targeted reasoning itself. None of this exercises
an actual MinGW/MSYS2 toolchain or produces a working Windows binary.
A Windows contributor or CI runner must confirm -Build actually
produces working rmac/rln/lyxass/pc_jagcrypt.exe before this caveat
can be removed.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Adds the build subcommand: plain make -C <dest> -j1 (default all target)
for rmac, rln, lyxass, verified against each tool's own makefile. pc_jagcrypt
needs a strip shim -- its makefile hardcodes GNU strip's --strip-all, which
BSD/macOS strip only accepts as a short-option build (rejects the long
option outright), aborting the recipe right after a successful link. The
shim drops the flag and calls the real strip, which already fully strips a
standalone non-shared executable like jagcrypt on both GNU and BSD.
new_bjl ships prebuilt binaries and needs no build step.
Adds tools/jaguar-toolchain/setup.sh (fetch subcommand only; build/env
land in later tasks) plus a PIN file pinning rmac, rln, lyxass,
pc_jagcrypt, and new_bjl to real commit SHAs, mirroring
test/jaguar-demos/'s clone-on-demand-into-gitignored-vendor-dir pattern.
lyxass's PIN REF was corrected from master to main -- the repo has no
master branch (only main and vs-update), confirmed via git ls-remote
and a real clone. rln's fetch from its primary host
(tiddly.mooo.com:5000), a personal server outside GitHub's uptime
guarantees, succeeded on every run in this session; no fallback source
was needed.
Verified: bash -n syntax check, shellcheck clean, real `git clone`
against all five upstreams, a full teardown-and-refetch against the
newly pinned SHAs reproduced identical commit hashes, and
`tools/vendor/` confirmed gitignored via git check-ignore.
Refuse pre-15886 rips, vendor libchdr, and ship pinned chdman helpers
on tagged releases and nightlies so users can convert CUE/BIN themselves.
Co-authored-by: Cursor <[email protected]>