A workspace for building, patching, and deploying firmware for the 1541ultimate and Spiffy_Ultimate lineages.
One ./forge command drives the whole development cycle:
- reproducible setup
- convenient build
- separation of local and upstream changes using an overlay file system
- transient and persistent deployment
- device recovery
Supported build targets: c64u, u64, ue2, u2.
Three source layers, one editable view:
upstream/— pristine checkouts pinned by SHA inmanifest.yamlpatches/— tracked local deltas, shareable and upstreamablepatches-private/— ignored local-only overlayswork/— merged overlay view; this is what you edit
Keeping upstream and patches on separate layers means the editor still sees one normal source tree, but you can export clean patches for upstream without untangling them from private tinkering.
Build state lives in .local/, output in out/; both are disposable.
Rules:
- edit under
work/1541ultimateorwork/spiffy-ultimate, neverupstream/ - if overlays are unavailable, update the matching file under
patches/<repo>/
git clone --recurse-submodules <repo-url> c64forge
cd c64forge
./forge setup # bootstraps .local/, work/, out/, mounts overlays
./forge status # sanity check
./forge build c64u # or u64, ue2, u2
./forge run c64u # transient hardware testIf you cloned without submodules: git submodule update --init --recursive.
Skip host-dep installs with ./forge setup --skip-host-deps.
A healthy status looks like:
Workspace root: /home/user/dev/c64/c64forge
Local config: present (default_target=u64)
Host deps: ready
Docker: ready
Overlay: fuse-overlayfs
U64 device: u64 reachable
C64U device: c64u reachable
[1541ultimate]
sha: 8b0ad2e99f4106a3c27773a6d233047d436a8382
lower: fresh
work: mounted at .../work/1541ultimate
[spiffy-ultimate]
sha: 3b582da78c57fb5b2d53bacb6a64dea5a4535f66
lower: fresh
work: mounted at .../work/spiffy-ultimate
Use --dry-run the first time you invoke run, stage, or flash on a
given machine.
Warning
No warranty. If you load your own build, flash the wrong image, or brick a device, the repair is yours. However, only the flash command is potentially dangerous.
| Command | Purpose |
|---|---|
./forge setup |
Bootstrap workspace, mount merged trees. --skip-host-deps supported. |
./forge status |
Config, deps, overlay, device reachability, pinned SHAs. |
./forge fix |
Rebuild local state and remount overlays. |
./forge targets |
List supported artifacts, command paths, and risk levels. |
./forge build [target...] |
Build one or more targets. Builds all top-level firmware targets when omitted. |
./forge run [target] |
Build and deploy transiently with no flash write. u64, c64u.ue2. |
./forge stage [target] |
Upload a full image only; no execution, no flash. u64, ue2, c64u.ue2. |
./forge flash [target] |
Guarded app-only persistent flash helper (DANGER ZONE). c64u.ue2 only. |
./forge recover [target] |
Upstream JTAG recovery if direct persistent flash failed. u64, ue2, c64u.ue2. --jtag-url to override. |
./forge update [repo|all] [ref] |
Advance upstream checkout(s) to the tracked origin branch or an explicit ref, then run sync. |
./forge sync [repo|all] |
Compare indexed patch files against the current upstream checkout(s). |
./forge pr 1541ultimate <path...> |
Stage selected patch files in a local 1541ultimate PR branch. |
./forge clean |
Remove disposable build state, unmount work trees. |
./forge help |
Built-in help. |
When a target is omitted, build builds all; run, stage, flash, and recover use default_target from c64forge.local.yaml.
| Name | Kind | Source | Used by | Artifact | Notes |
|---|---|---|---|---|---|
u64 |
firmware | 1541ultimate |
build, run, stage |
update_u<up3>_f<forge3>.u64 |
Ultimate 64 Elite |
ue2 |
firmware | 1541ultimate |
build, stage, recover |
update_u<up3>_f<forge3>.ue2 |
Ultimate 64 Elite II |
u2 |
firmware | 1541ultimate |
build |
update_u<up3>_f<forge3>.u2r |
Ultimate II (RiscV CPU) |
c64u.ue2 |
firmware | spiffy-ultimate |
build, stage |
update_u<up3>_f<forge3>.c64u.ue2 |
C64 Ultimate |
kstart.c64u.ue2 |
launcher helper | spiffy-ultimate |
build, run c64u.ue2 |
update_u<up3>_f<forge3>.kstart.c64u.ue2 |
app-only transient helper; no flash write |
kburn.c64u.ue2 |
launcher helper | spiffy-ultimate |
build, flash c64u.ue2 |
update_u<up3>_f<forge3>.kburn.c64u.ue2 |
app-only persistent helper; writes flash |
Artifacts staged in out/ use this structure:
update_u<up3>_f<forge3>.<suffix>
up3: first 3 hex digits of the upstream repo commit used to build the artifactforge3: first 3 hex digits of the localc64forgecommit that staged it<suffix>: target-specific artifact suffix such asu64,ue2,u2r,c64u.ue2,kstart.c64u.ue2, orkburn.c64u.ue2
Examples:
update_u8b0_f4e.u64update_u3b5_f4e.c64u.ue2update_u3b5_f4e.kstart.c64u.ue2
Full C64U image flashing is the highest-risk update path in this repo. ./forge does not remote-flash the full c64u.ue2 image directly; instead, ./forge stage c64u.ue2 uploads it, and a later device-side full-image update performs the dangerous persistent change.
Local settings live in c64forge.local.yaml, created from
c64forge.local.yaml.example on first setup:
default_target: u64
u64_host: u64
ue2_host: ue2
ue2_install_path: /USB1/Firmware/custom/
c64u_host: c64u
c64u_ftp_path: /Temp/
c64u_telnet_port: 23
build_tools_dir: ""
jtag_url: ftdi://ftdi:232h/0| Field | Role |
|---|---|
default_target |
target used when run/stage/flash/recover is called without one |
u64_host |
Ultimate 64 network host |
ue2_host |
Ultimate 64 Elite II network host |
ue2_install_path |
firmware upload path for the plain ue2 image |
c64u_host |
Commodore 64 Ultimate network host |
c64u_ftp_path |
temporary upload path for C64U flows |
c64u_telnet_port |
telnet port used to trigger temporary launches |
build_tools_dir |
optional host-side build tools path |
jtag_url |
default JTAG URL for recover |
Required host tools: docker, git, rsync, ping, mountpoint, fuse-overlayfs, fusermount3, curl, telnet, python3, make.
./forge sync 1541ultimate # or spiffy-ultimate, or all
./forge pr 1541ultimate path/to/file1 path/to/file2Typical workflow:
edit -> sync -> pr
edit -> update -> sync -> pr
sync conflict|orphaned -> edit -> sync
sync is index-based, not merge-based: it compares each file in patches/ against the base hash recorded in patches/index.tsv and the current file content in upstream/. clean means upstream still matches the recorded base, upstreamed means upstream now matches your local patch, and conflict or orphaned means the patch needs attention before export.
update first moves upstream/1541ultimate and/or upstream/spiffy-ultimate forward, then runs that same check. pr does not open GitHub automatically; it creates a local staging branch under .local/staging/1541ultimate for review, push, and manual PR creation.
c64forge itself is GPL v3 — see LICENSE.txt. Pinned upstream repositories under upstream/ retain their own notices and licenses.
