Cross-platform console app that runs the emulator without any UI, rendering, audio, or user input. Driven entirely by CLI arguments and Lua scripts — useful for automation, scripting, and CI workflows.
Technologies
- UI: none.
- Rendering: none (null renderer).
- Input: none (null input handler).
- Audio: none (null audio handler).
- Scripting: Lua via MoonSharp.
- C64 — requires ROM files (Kernal, Basic, Chargen). See C64 ROM files below.
- Generic computer — built-in example 6502 programs (Snake, Scroll, HelloWorld).
The C64 system requires three ROM files: Kernal, Basic, and Character generator (Chargen).
Important
You may need a license from Commodore/Cloanto (or own a real C64) to use C64 ROM files.
The ROM files can be downloaded from zimmers.net:
| ROM | File | Direct link |
|---|---|---|
| Kernal | kernal.901227-03.bin |
download |
| Basic | basic.901226-01.bin |
download |
| Chargen | characters.901225-01.bin |
download |
By default appsettings.json expects them in %HOME%/Downloads/C64 (i.e. ~/Downloads/C64 on macOS/Linux, %USERPROFILE%\Downloads\C64 on Windows). The directory and filenames can be changed in appsettings.json:
"Highbyte.DotNet6502.C64.Headless": {
"SystemConfig": {
"ROMDirectory": "%HOME%/Downloads/C64",
"ROMs": [
{ "Name": "basic", "File": "basic.901226-01.bin" },
{ "Name": "kernal", "File": "kernal.901227-03.bin" },
{ "Name": "chargen", "File": "characters.901225-01.bin" }
]
}
}When a Lua script is supplied the script owns the full emulator lifecycle — it calls emu.start(), loads programs, and quits when done.
| Argument | Description |
|---|---|
--script <path> |
Load and run a Lua script (can be specified multiple times) |
--scriptDir <path> |
Override the script directory from appsettings.json |
--log-level <level> / -l <level> |
Set console log level (Trace/Debug/Information/Warning/Error) |
--enableExternalDebug |
Enable VS Code debug adapter (DAP) over TCP |
--debug-port <port> |
TCP port for the debug adapter (default: 6502) |
--debug-wait |
Wait for a debug client to connect before starting |
Important
--script and --scriptDir are mutually exclusive with --system, --systemVariant, --start, --waitForSystemReady, --loadPrg, and --runLoadedProgram. The script is responsible for all emulator setup and lifecycle. Combining them is an error.
Used when driving the emulator from the command line without a script — primarily by the VS Code debugger extension.
| Argument | Description |
|---|---|
--system <name> |
Select a system (e.g. C64, Generic) |
--systemVariant <name> |
Select a system variant. Requires --system. |
--start |
Auto-start the emulator after selection |
--waitForSystemReady |
Wait until the system reports ready before continuing. Requires --start. |
--loadPrg <path> |
Load a .prg file into memory. Requires --start. |
--runLoadedProgram |
Run the loaded .prg file after loading. Requires --start and --loadPrg. |
--log-level <level> / -l <level> |
Set console log level (Trace/Debug/Information/Warning/Error) |
--enableExternalDebug |
Enable VS Code debug adapter (DAP) over TCP |
--debug-port <port> |
TCP port for the debug adapter (default: 6502) |
--debug-wait |
Wait for a debug client to connect before starting |
The same Lua scripting API available in the Avalonia apps is fully supported here. Scripts are the primary way to control the emulator. Example scripts are included in the scripts/ directory:
| Script | Description |
|---|---|
example_emulator_control.lua |
Query state, pause/resume/reset, state-change event hooks |
example_c64_basic_readwrite.lua |
Write a BASIC program via keyboard buffer and read it back |
example_c64_border_cycle.lua |
Cycle C64 border color via memory writes |
example_c64_download_and_run_prg.lua |
Download and run a .prg file over HTTP |
example_c64_load_d64.lua |
Load a .d64 disk image |
example_c64_screenshot.lua |
Save a screenshot to disk |
example_frameadvance.lua |
Advance frames and read frame count |
example_input_kb.lua |
Inject keyboard input |
example_input_joystick.lua |
Inject joystick input |
example_monitor.lua |
Interact with the machine code monitor |
example_file_io.lua |
Read and write files from Lua |
example_http.lua |
Make HTTP requests from Lua |
example_tcp_client.lua |
Connect to a TCP server from Lua |
example_store.lua |
Persist data between script runs using the key-value store |
example_quit.lua |
Quit the emulator from a script |
See Scripting for the full Lua API reference.
The headless app supports the Debug Adapter Protocol (DAP) over TCP, allowing VS Code to attach a debugger while the emulator runs headless.
See the VS Code debugger extension for details.
Examples below use dotnet-6502-headless as installed via Homebrew or Scoop.
- Manual download (self-contained binary): replace with
./Highbyte.DotNet6502.App.Headless(orHighbyte.DotNet6502.App.Headless.exeon Windows) - Running from source: replace with
dotnet run --project src/apps/Highbyte.DotNet6502.App.Headless --
Start a C64 and run a Lua script (script owns all setup and lifecycle):
dotnet-6502-headless --script scripts/example_c64_basic_readwrite.lua
Start with debug adapter listening on port 6502, waiting for client (no script):
dotnet-6502-headless --system C64 --start --enableExternalDebug --debug-port 6502 --debug-wait
Example console output:
09:12:01 info: Program[0] Starting headless emulator.
09:12:01 info: Program[0] Creating configuration object.
09:12:01 info: Program[0] Initializing logging.
09:12:01 info: Program[0] Reading emulator config.
09:12:01 info: HeadlessHostApp[0] Creating headless host app.
09:12:01 info: HeadlessHostApp[0] Headless host app initialized.
09:12:01 info: HeadlessHostApp[0] Headless emulator running. Press Ctrl+C to exit.
09:12:01 info: Script[0] Waiting for C64 emulator to start...
09:12:02 info: Script[0] C64 emulator started. Waiting for BASIC to initialize...
09:12:03 info: Script[0] BASIC ready. Typing BASIC program...
09:12:05 info: Script[0] Retrieved BASIC source:
09:12:05 info: Script[0] 10 PRINT "HELLO FROM LUA"
20 GOTO 10
09:12:05 info: Script[0] Round-trip check PASSED: both lines found in retrieved source.
Edit appsettings.json in the app directory to configure ROM paths and scripting settings:
{
"Highbyte.DotNet6502.Scripting": {
"Enabled": true,
"ScriptDirectory": "scripts"
},
"Highbyte.DotNet6502.C64.Headless": {
"SystemConfig": {
"ROMDirectory": "%HOME%/Downloads/C64"
}
}
}See INSTALL_HEADLESS.md for download links and installation instructions for Windows, Linux, and macOS.
For development system requirements, see here.
Open solution dotnet-6502.sln.
Set project Highbyte.DotNet6502.App.Headless as startup and start with F5.
A launch configuration is included. Open the repo in VSCode and use the Headless launch profile.