Skip to content

Commit 655e9f8

Browse files
authored
Feature/lua scripting (#167)
* Add Lua scripting via MoonSharp library to Avalonia Desktop and Browser apps. * Update doc
1 parent 5c7a3b1 commit 655e9f8

79 files changed

Lines changed: 6921 additions & 40 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,4 +284,7 @@ BenchmarkDotNet.Artifacts/
284284

285285
src/apps/Highbyte.DotNet6502.App.SadConsole/appsettings.Development.json
286286
src/apps/Highbyte.DotNet6502.App.SilkNetNative/appsettings.Development.json
287+
# Lua example scripts are generated at build time from resources/scripts/
288+
src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Browser/wwwroot/scripts/
289+
287290
src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Desktop/appsettings.Development.json

Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
<!-- Microsoft Testing packages -->
4747
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
4848
<PackageVersion Include="Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers" Version="18.0.36525.3" />
49+
<!-- MoonSharp Lua interpreter -->
50+
<PackageVersion Include="MoonSharp" Version="2.0.0" />
4951
<!-- MonoGame -->
5052
<PackageVersion Include="MonoGame.Framework.DesktopGL" Version="3.8.4.1" />
5153
<!-- NAudio -->

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,21 @@ See [Desktop Apps](doc/DESKTOP_APPS.md) for download links for pre-built executa
5151
| ----------------------------------------------- | ------------------------------------------------- |
5252
| <img src="doc/Screenshots/VSCode_source_debug.png" title="VSCode source debug"/> | <img src="doc/Screenshots/VSCode_disassembly_debug.png" title="VSCode disassembly debug"/> |
5353

54+
## Lua scripting
55+
56+
The Avalonia desktop and browser apps support [Lua scripting](doc/SCRIPTING.md) for automating the emulator — selecting systems, controlling emulation, reading/writing memory, injecting input, and more.
57+
58+
| [Lua scripts in desktop app](doc/SCRIPTING.md)| [Lua scripts in browser app](doc/SCRIPTING.md) |
59+
| ----------------------------------------------- | ------------------------------------------------- |
60+
| <img src="doc/Screenshots/AvaloniaDesktop_C64_Scripting.png" title="VLua scripts in Desktop app"/> | <img src="doc/Screenshots/AvaloniaBrowser_C64_Scripting.png" title="Lua scripts in Browser app"/> |
61+
5462
## Other features
5563

5664
| [Run 6502 machine code in your own .NET apps](doc/CPU_LIBRARY.md) | [Machine code monitor](doc/MONITOR.md) | [C64 Basic AI code completion](doc/SYSTEMS_C64_AI_CODE_COMPLETION.md) |
5765
| -------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------- |
5866
| ![Code integration](doc/Screenshots/Code_integration.png 'Code integration') | ![SilkNet native app, C64 monitor](doc/Screenshots/SilkNetNative_Monitor.png 'SilkNet native app, C64 monitor') | ![C64 Basic AI code completion](doc/Screenshots/BlazorWASM_C64_Basic_AI.png 'C64 Basic AI code completion') |
5967

68+
6069
## Common libraries
6170
- [`Highbyte.DotNet6502`](doc/CPU_LIBRARY.md)
6271
- Core library for executing 6502 machine code, not bound to any specific emulated system/computer, and does not have any UI or I/O code.

doc/APPS_AVALONIA.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ See [here](DESKTOP_APPS.md) how to download and run pre-built executables.
4747
## System: Generic computer
4848
The example 6502 machine code that is loaded and run by default for the _Generic_ computer is this a assembled version of [this 6502 assembly code](../samples/Assembler/Generic/hostinteraction_scroll_text_and_cycle_colors.asm)
4949

50+
## VSCode debug adapter
51+
See [here](../tools/vscode-extension/README.md).
52+
53+
## Scripting: Lua scripting via MoonSharp
54+
See [here](SCRIPTING.md).
5055

5156
## UI
5257

@@ -81,7 +86,7 @@ TODO
8186
## Run browser app from command line (Windows, Linux, Mac)
8287
### Run Debug build (very slow)
8388
```shell
84-
cd ./src/apps/Avalonia/Highbyte.DotNet6502.App.Browser
89+
cd ./src/apps/Avalonia/Highbyte.DotNet6502.App.Avalonia.Browser
8590
dotnet run
8691
```
8792
Open browser at http://localhost:5000.

doc/SCRIPTING.md

Lines changed: 716 additions & 0 deletions
Large diffs are not rendered by default.
510 KB
Loading
400 KB
Loading

dotnet-6502.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Highbyte.DotNet6502.Systems
4949
EndProject
5050
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Highbyte.DotNet6502.AI", "src\libraries\Highbyte.DotNet6502.AI\Highbyte.DotNet6502.AI.csproj", "{B04E0DA2-F0EB-4E71-BFF9-3D3B17E30688}"
5151
EndProject
52+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Highbyte.DotNet6502.Scripting.MoonSharp", "src\libraries\Highbyte.DotNet6502.Scripting.MoonSharp\Highbyte.DotNet6502.Scripting.MoonSharp.csproj", "{B34EF892-C5D6-5907-A04B-23BC45DE67F0}"
53+
EndProject
5254
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Avalonia", "Avalonia", "{709DA26C-ACCB-4EF0-8897-A15AF07476F1}"
5355
EndProject
5456
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Highbyte.DotNet6502.App.Avalonia.Core", "src\apps\Avalonia\Highbyte.DotNet6502.App.Avalonia.Core\Highbyte.DotNet6502.App.Avalonia.Core.csproj", "{970EDA9E-A32C-6765-CC51-8992FE61F3CC}"
@@ -392,6 +394,18 @@ Global
392394
{9F990386-A478-8D9B-ACAC-9DBE917978F1}.Release|x64.Build.0 = Release|Any CPU
393395
{9F990386-A478-8D9B-ACAC-9DBE917978F1}.Release|x86.ActiveCfg = Release|Any CPU
394396
{9F990386-A478-8D9B-ACAC-9DBE917978F1}.Release|x86.Build.0 = Release|Any CPU
397+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
398+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
399+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Debug|x64.ActiveCfg = Debug|Any CPU
400+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Debug|x64.Build.0 = Debug|Any CPU
401+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Debug|x86.ActiveCfg = Debug|Any CPU
402+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Debug|x86.Build.0 = Debug|Any CPU
403+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
404+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Release|Any CPU.Build.0 = Release|Any CPU
405+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Release|x64.ActiveCfg = Release|Any CPU
406+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Release|x64.Build.0 = Release|Any CPU
407+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Release|x86.ActiveCfg = Release|Any CPU
408+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0}.Release|x86.Build.0 = Release|Any CPU
395409
EndGlobalSection
396410
GlobalSection(SolutionProperties) = preSolution
397411
HideSolutionNode = FALSE
@@ -424,6 +438,7 @@ Global
424438
{20B7124C-9E72-4352-8A89-582E1FB008BF} = {B406AD5D-CB8D-45F2-A5A2-4C0AD82A410A}
425439
{7E54BF4B-2BCC-F3BF-F277-421C381600ED} = {179A14B2-B918-4B64-8314-3551D83551D0}
426440
{9F990386-A478-8D9B-ACAC-9DBE917978F1} = {B406AD5D-CB8D-45F2-A5A2-4C0AD82A410A}
441+
{B34EF892-C5D6-5907-A04B-23BC45DE67F0} = {B406AD5D-CB8D-45F2-A5A2-4C0AD82A410A}
427442
EndGlobalSection
428443
GlobalSection(ExtensibilityGlobals) = postSolution
429444
SolutionGuid = {0F55B6C2-E4B4-4F2C-9D2A-D63A17F3B5C4}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
-- example_file_io.lua
2+
-- Demonstrates the file I/O API available to Lua scripts.
3+
--
4+
-- All paths are relative to the configured base directory (ScriptDirectory by default).
5+
-- Paths that attempt to escape the base directory (e.g. "../") are blocked and return nil.
6+
--
7+
-- Read operations are always allowed:
8+
-- file.read(name) -> string (text content) or nil
9+
-- file.read_bytes(name) -> table of integers 0-255 (1-indexed) or nil
10+
-- file.exists(name) -> boolean
11+
-- file.list([pattern]) -> table of filenames (1-indexed), pattern defaults to "*"
12+
--
13+
-- Write operations require AllowFileWrite: true in the "Highbyte.DotNet6502.Scripting" config section:
14+
-- file.write(name, text) -> writes/overwrites a text file
15+
-- file.append(name, text) -> appends text to a file (creates if not exists)
16+
-- file.delete(name) -> deletes a file (no-op if not found)
17+
--
18+
-- Binary loading into emulator memory:
19+
-- emu.load(name) -> loads binary file; reads 2-byte little-endian PRG load address from header
20+
-- emu.load(name, address) -> loads raw binary file at the given address (no header parsing)
21+
22+
-- ---- List files in the script directory ----
23+
log.info("Files in script directory:")
24+
local files = file.list("*.lua")
25+
for i, name in ipairs(files) do
26+
log.info(" " .. i .. ": " .. name)
27+
end
28+
29+
-- ---- Read a text file ----
30+
local readme = "README.txt"
31+
if file.exists(readme) then
32+
local content = file.read(readme)
33+
log.info("README.txt: " .. (content or "(empty)"))
34+
else
35+
log.info("README.txt not found (skipping read)")
36+
end
37+
38+
-- ---- Write a log file (requires AllowFileWrite: true) ----
39+
-- If AllowFileWrite is false, file.write() raises a runtime error and this script is auto-disabled.
40+
local log_file = "frame_log.csv"
41+
file.write(log_file, "frame,pc,a,x,y\n")
42+
log.info("Logging CPU state every 60 frames to " .. log_file)
43+
44+
-- ---- Binary load example (commented out — requires a PRG file in the scripts directory) ----
45+
-- emu.load("my_program.prg") -- auto-detects load address from 2-byte PRG header
46+
-- emu.load("raw_data.bin", 0xC000) -- loads raw binary at address $C000 (no header)
47+
48+
-- ---- Read binary bytes ----
49+
-- local bytes = file.read_bytes("patch.bin")
50+
-- if bytes then
51+
-- log.info("patch.bin: " .. #bytes .. " bytes")
52+
-- -- Example: apply the first 16 bytes as a patch at $8000
53+
-- for i = 1, math.min(16, #bytes) do
54+
-- mem.write(0x8000 + i - 1, bytes[i])
55+
-- end
56+
-- end
57+
58+
-- ---- Coroutine loop: append a CSV row every 60 frames ----
59+
while true do
60+
emu.frameadvance()
61+
local frame = emu.framecount()
62+
if frame % 60 == 0 then
63+
-- log.debug(string.format("Frame %d: PC=$%04X A=$%02X X=$%02X Y=$%02X",
64+
-- frame, cpu.pc, cpu.a, cpu.x, cpu.y))
65+
local row = string.format("%d,$%04X,$%02X,$%02X,$%02X\n",
66+
frame, cpu.pc, cpu.a, cpu.x, cpu.y)
67+
file.append(log_file, row)
68+
end
69+
end
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
-- example_quit.lua
2+
-- Automation pipeline example for use when launching the Avalonia Desktop app
3+
-- from the command line (e.g. via --script).
4+
--
5+
-- Flow:
6+
-- 1. Start the C64 emulator.
7+
-- 2. Wait for BASIC to be ready.
8+
-- 3. Log instructions telling the user to POKE 49152,255 to signal completion.
9+
-- 4. Poll address 49152 (0xC000) each frame; quit when value 255 is detected.
10+
-- 5. Quit automatically after a 2-minute timeout if no signal is received.
11+
12+
local QUIT_ADDR = 0xC000 -- address to POKE 255 to signal completion
13+
local QUIT_VALUE = 255
14+
local TIMEOUT_SECS = 120.0
15+
16+
-- Wait until the C64 system is selected (may be set via --system C64 CLI arg)
17+
while emu.selected_system() ~= "C64" do
18+
emu.yield()
19+
end
20+
21+
-- Start the C64 emulator
22+
log.info("Starting C64 emulator...")
23+
emu.start()
24+
25+
-- Wait until the emulator is actually running
26+
while emu.state() ~= "running" do
27+
emu.yield()
28+
end
29+
30+
-- Wait until BASIC has completed its initialization
31+
log.info("Waiting for BASIC to initialize...")
32+
while not c64.basic_started() do
33+
emu.frameadvance()
34+
end
35+
36+
log.info("BASIC is ready.")
37+
log.info("Quit emulator by poking value 255 to address 49152")
38+
39+
local start_time = emu.time()
40+
41+
while true do
42+
emu.frameadvance()
43+
44+
if mem.read(QUIT_ADDR) == QUIT_VALUE then
45+
log.info("Quit signal received (POKE 49152,255). Quitting application...")
46+
emu.quit()
47+
break
48+
end
49+
50+
if emu.time() - start_time > TIMEOUT_SECS then
51+
log.info("Timeout reached. Quitting application...")
52+
emu.quit()
53+
break
54+
end
55+
end

0 commit comments

Comments
 (0)