mirror of
https://github.com/JorjBauer/lua-6502.git
synced 2026-03-12 08:41:42 +00:00
250 lines
4.8 KiB
Lua
Executable File
250 lines
4.8 KiB
Lua
Executable File
#!/usr/bin/env lua5.4
|
|
|
|
local function addRelPath(dir)
|
|
local spath =
|
|
debug.getinfo(1, "S").source
|
|
:sub(2)
|
|
:gsub("^([^/])", "./%1")
|
|
:gsub("[^/]*$", "")
|
|
dir = dir and (dir .. "/") or ""
|
|
spath = spath .. dir
|
|
|
|
package.path =
|
|
spath .. "?.lua;"
|
|
.. spath .. "?/init.lua;"
|
|
.. package.path
|
|
end
|
|
|
|
addRelPath("..")
|
|
|
|
local _ENV = require("std.normalize") {
|
|
"std.strict",
|
|
"const",
|
|
"io",
|
|
"os",
|
|
"debug",
|
|
"string",
|
|
"table",
|
|
}
|
|
|
|
local MC = require("minicurses")
|
|
local posix = require("posix")
|
|
|
|
local _6502 = require("6502")
|
|
local cpu = _6502:new()
|
|
|
|
-- Per Apple-1 Operation Manual (1976)
|
|
local _c = const {
|
|
DSPCR = 0xd013,
|
|
DSP = 0xd012,
|
|
KBDCR = 0xd011,
|
|
KBD = 0xd010,
|
|
}
|
|
|
|
local SCREEN_W, SCREEN_H = 40, 24
|
|
local screenX, screenY = 0, 0
|
|
local running = true
|
|
|
|
local mmu = {
|
|
ram = {},
|
|
immutable = {},
|
|
|
|
reset = function(self)
|
|
self.ram[_c.KBDCR] = 0
|
|
self.ram[_c.DSPCR] = 0x04
|
|
self.ram[_c.DSP] = 0
|
|
self.ram[_c.KBD] = 0x80
|
|
end,
|
|
}
|
|
|
|
local mmu_metatable = {
|
|
__index = function(t, address)
|
|
if address == _c.KBD then
|
|
t.ram[_c.KBDCR] = 0x27
|
|
return t.ram[_c.KBD]
|
|
end
|
|
return t.ram[address] or 0
|
|
end,
|
|
|
|
__newindex = function(t, address, v)
|
|
if address == _c.DSP then
|
|
if ((t.ram[_c.DSPCR] or 0) & 0x04) == 0x04 then
|
|
t.ram[_c.DSP] = (v & 0x7F) | 0x80
|
|
t.ram[_c.DSPCR] = (t.ram[_c.DSPCR] or 0) & (~0x04)
|
|
end
|
|
return
|
|
end
|
|
|
|
if address == _c.KBDCR then
|
|
if t.ram[_c.KBDCR] == 0 then
|
|
v = 0x27
|
|
end
|
|
t.ram[_c.KBDCR] = v
|
|
return
|
|
end
|
|
|
|
if t.immutable[address] then
|
|
assert(false, "Tried to write to ROM")
|
|
return
|
|
end
|
|
|
|
t.ram[address] = v
|
|
end,
|
|
}
|
|
|
|
setmetatable(mmu, mmu_metatable)
|
|
cpu.ram = mmu
|
|
|
|
-- Load the monitor ROM @ 0xFF00
|
|
do
|
|
local f = assert(io.open("monitor.rom", "rb"), "Can't open monitor.rom")
|
|
local data = f:read("*a")
|
|
f:close()
|
|
assert(#data == 256)
|
|
for i = 0, #data - 1 do
|
|
cpu:writemem(0xFF00 + i, data:byte(i + 1))
|
|
mmu.immutable[0xFF00 + i] = true
|
|
end
|
|
end
|
|
|
|
-- Load the basic ROM @ 0xE000
|
|
do
|
|
local f = assert(io.open("basic.rom", "rb"), "Can't open basic.rom")
|
|
local data = f:read("*a")
|
|
f:close()
|
|
assert(#data == 4096)
|
|
for i = 0, #data - 1 do
|
|
cpu:writemem(0xE000 + i, data:byte(i + 1))
|
|
mmu.immutable[0xE000 + i] = true
|
|
end
|
|
end
|
|
|
|
cpu:init()
|
|
mmu:reset()
|
|
cpu:rst()
|
|
|
|
local stdin_fd = posix.fileno(io.stdin)
|
|
local orig_flags = posix.fcntl(stdin_fd, posix.F_GETFL, 0)
|
|
|
|
local function restore_terminal()
|
|
pcall(function()
|
|
posix.fcntl(stdin_fd, posix.F_SETFL, orig_flags)
|
|
end)
|
|
pcall(function()
|
|
MC.endwin()
|
|
end)
|
|
end
|
|
|
|
MC.initscr()
|
|
MC.cbreak()
|
|
MC.noecho()
|
|
MC.nonl()
|
|
MC.clear()
|
|
MC.refresh()
|
|
|
|
posix.fcntl(stdin_fd, posix.F_SETFL, orig_flags | posix.O_NONBLOCK)
|
|
|
|
local screen = {}
|
|
for y = 1, SCREEN_H do
|
|
screen[y] = (" "):rep(SCREEN_W)
|
|
end
|
|
|
|
local function redraw_all()
|
|
MC.clear()
|
|
for y = 0, SCREEN_H - 1 do
|
|
MC.mvaddstr(y, 0, screen[y + 1])
|
|
end
|
|
MC.refresh()
|
|
end
|
|
|
|
local function scroll_up_1()
|
|
table.remove(screen, 1)
|
|
table.insert(screen, (" "):rep(SCREEN_W))
|
|
redraw_all()
|
|
end
|
|
|
|
local function newline()
|
|
screenX = 0
|
|
screenY = screenY + 1
|
|
if screenY >= SCREEN_H then
|
|
scroll_up_1()
|
|
screenY = SCREEN_H - 1
|
|
end
|
|
end
|
|
|
|
local function put_char(byte)
|
|
local line = screen[screenY + 1]
|
|
screen[screenY + 1] = line:sub(1, screenX) .. string.char(byte) .. line:sub(screenX + 2)
|
|
MC.move(screenY, screenX)
|
|
MC.addstr(string.char(byte))
|
|
screenX = screenX + 1
|
|
if screenX >= SCREEN_W then
|
|
newline()
|
|
end
|
|
end
|
|
|
|
local function read_byte_nonblocking()
|
|
local s = posix.read(stdin_fd, 1)
|
|
if not s or #s == 0 then return nil end
|
|
return s:byte(1)
|
|
end
|
|
|
|
local function checkForInput()
|
|
if mmu[_c.KBDCR] ~= 0x27 then return end
|
|
local c = read_byte_nonblocking()
|
|
if not c or c <= 0 or c >= 256 then return end
|
|
|
|
c = c & 0x7F
|
|
if c >= 0x61 and c <= 0x7A then c = c & 0x5F end
|
|
if c >= 0x60 then return end
|
|
|
|
mmu[_c.KBD] = c | 0x80
|
|
mmu[_c.KBDCR] = 0xA7
|
|
end
|
|
|
|
local function updateScreen()
|
|
local dsp = mmu.ram[_c.DSP]
|
|
if (dsp & 0x80) ~= 0x80 then return end
|
|
|
|
dsp = dsp & 0x7F
|
|
local ch = dsp
|
|
if dsp >= 0x60 and dsp <= 0x7F then ch = dsp & 0x5F end
|
|
|
|
if ch == 0x0D or ch == 0x0A then
|
|
newline()
|
|
else
|
|
if ch >= 0x20 and ch <= 0x5F then
|
|
put_char(ch)
|
|
end
|
|
end
|
|
|
|
MC.move(screenY, screenX)
|
|
mmu.ram[_c.DSP] = dsp
|
|
mmu.ram[_c.DSPCR] = (mmu.ram[_c.DSPCR] or 0) | 0x04
|
|
MC.refresh()
|
|
end
|
|
|
|
local function err_handler(errmsg)
|
|
restore_terminal()
|
|
local msg = tostring(errmsg or "(nil error object)")
|
|
io.stderr:write("Caught an error: ", msg, "\n")
|
|
io.stderr:write(debug.traceback(msg, 2), "\n")
|
|
os.exit(2)
|
|
end
|
|
|
|
local function main()
|
|
redraw_all()
|
|
MC.move(screenY, screenX)
|
|
MC.refresh()
|
|
|
|
while running do
|
|
cpu:step()
|
|
checkForInput()
|
|
updateScreen()
|
|
end
|
|
end
|
|
|
|
local ok = xpcall(main, err_handler)
|
|
restore_terminal()
|
|
if not ok then os.exit(2) end
|