Split out core code

This commit is contained in:
Empathic Qubit 2021-11-03 17:53:25 +01:00
parent 224e83c7ac
commit 1aed1a48df
3 changed files with 923 additions and 873 deletions

608
commands.lua Normal file
View file

@ -0,0 +1,608 @@
local _p = emu.log
local function print(data)
_p(data .. "")
end
return function(server)
local me = {}
local function responseCheckpointInfo(requestId, checkpt, hit)
local r = {}
r[#r+1] = server.uint32ToLittleEndian(checkpt.num)
r[#r+1] = server.boolToLittleEndian(hit)
r[#r+1] = server.uint16ToLittleEndian(checkpt.start)
r[#r+1] = server.uint16ToLittleEndian(checkpt.finish)
r[#r+1] = server.boolToLittleEndian(checkpt.stop)
r[#r+1] = server.boolToLittleEndian(checkpt.enabled)
r[#r+1] = server.uint8ToLittleEndian(checkpt.op)
r[#r+1] = server.boolToLittleEndian(checkpt.temp)
r[#r+1] = server.uint32ToLittleEndian(checkpt.hitCount)
r[#r+1] = server.uint32ToLittleEndian(checkpt.ignoreCount)
r[#r+1] = server.boolToLittleEndian(checkpt.condition ~= nil)
r[#r+1] = server.uint32ToLittleEndian(checkpt.memspace)
server.response(server.responseType.CHECKPOINT_INFO, server.errorType.OK, requestId, table.concat(r))
end
local regMeta = {
a = { name = "A", id = 0, size = 1 },
x = { name = "X", id = 1, size = 1 },
y = { name = "Y", id = 2, size = 1 },
pc = { name = "PC", id = 3, size = 2 },
sp = { name = "SP", id = 4, size = 1 },
status = { name = "FL", id = 5, size = 1 },
}
local function responseRegisterInfo(requestId)
if requestId == nil then
error("request id was nil", 2)
end
local r = {}
local regs = emu.getState().cpu
local count = 0
for name, meta in pairs(regMeta) do
count = count + 1
end
r[#r+1] = server.uint16ToLittleEndian(count)
local itemSize = 3
for name, meta in pairs(regMeta) do
r[#r+1] = server.uint8ToLittleEndian(itemSize)
r[#r+1] = server.uint8ToLittleEndian(meta.id)
r[#r+1] = server.uint16ToLittleEndian(regs[name])
end
server.response(server.responseType.REGISTER_INFO, server.errorType.OK, requestId, table.concat(r))
end
local function responseStopped()
local pc = emu.getState().cpu.pc
server.response(server.responseType.STOPPED, server.errorType.OK, server.EVENT_ID, server.uint16ToLittleEndian(pc))
end
local function responseResumed()
local pc = emu.getState().cpu.pc
server.response(server.responseType.RESUMED, server.errorType.OK, server.EVENT_ID, server.uint16ToLittleEndian(pc))
end
function me.monitorOpened()
print("Monitor opened")
responseRegisterInfo(server.EVENT_ID)
responseStopped(server.EVENT_ID)
end
function me.monitorClosed()
print("Monitor closed")
responseRegisterInfo(server.EVENT_ID)
responseResumed(server.EVENT_ID)
end
local function processAdvanceInstructions(command)
if command.length < 3 then
server.errorResponse(server.errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local stepOverSubroutines = server.readBool(command.body, 1)
local count = server.readUint16(command.body, 2)
server.stepping = true
if stepOverSubroutines then
-- FIXME
emu.execute(count, emu.executeCountType.cpuInstructions)
else
emu.execute(count, emu.executeCountType.cpuInstructions)
end
server.running = true
server.response(server.responseType.ADVANCE_INSTRUCTIONS, server.errorType.OK, command.requestId, nil)
me.monitorClosed()
end
local function processPing(command)
server.response(server.responseType.PING, server.errorType.OK, command.requestId, nil)
end
local function processExit(command)
server.running = true
server.response(server.responseType.EXIT, server.errorType.OK, command.requestId, nil)
me.monitorClosed()
end
local function processReset(command)
server.running = true
emu.reset()
server.response(server.responseType.RESET, server.errorType.OK, command.requestId, nil)
me.monitorClosed()
end
local memspaces = {
MAIN = 0,
INVALID = -1,
}
local function getRequestedMemspace(requestedMemspace)
if requestedMemspace == 0 then
return memspaces.MAIN
else
return memspaces.INVALID
end
end
local banks = {
default = 0,
cpu = 1,
ppu = 2,
palette = 3,
oam = 4,
secondaryOam = 5
}
local function validateBanknum(memspace, banknum)
return memspace == memspaces.MAIN and banknum >= banks.default and banknum <= banks.secondaryOam
end
local function processMemorySet(command)
local newSidefx = server.readBool(command.body, 1)
local startAddress = server.readUint16(command.body, 2)
local endAddress = server.readUint16(command.body, 4)
if startAddress > endAddress then
server.errorResponse(server.errorType.INVALID_PARAMETER, command.requestId)
return
end
local length = endAddress - startAddress + 1
if command.length < length + 8 then
server.errorResponse(server.errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local requestedMemspace = server.readUint8(command.body, 6)
local requestedBanknum = server.readUint16(command.body, 7)
local memspace = getRequestedMemspace(requestedMemspace)
if memspace == memspaces.INVALID then
server.errorResponse(server.errorType.INVALID_MEMSPACE, command.requestId)
return
end
if not validateBanknum(memspace, requestedBanknum) then
server.errorResponse(server.errorType.INVALID_PARAMETER, command.requestId)
return
end
local banknum = requestedBanknum
if banknum == banks.default then
banknum = banks.cpu
end
if not newSidefx and ( banknum == banks.cpu or banknum == banks.ppu ) then
banknum = banknum + 0x100
end
banknum = banknum - 1
for i = 0,length - 1,1 do
local val = command.body:byte(8 + i + 1)
print(val)
emu.write(startAddress + i, val, banknum)
end
server.response(server.responseType.MEM_SET, server.errorType.OK, command.requestId, nil)
end
local function processMemoryGet(command)
if command.length < 8 then
server.errorResponse(server.errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local newSidefx = server.readBool(command.body, 1)
local startAddress = server.readUint16(command.body, 2)
local endAddress = server.readUint16(command.body, 4)
if startAddress > endAddress then
server.errorResponse(server.errorType.INVALID_PARAMETER, command.requestId)
return
end
local requestedMemspace = server.readUint8(command.body, 6)
local memspace = getRequestedMemspace(requestedMemspace)
if memspace == memspaces.INVALID then
server.errorResponse(server.errorType.INVALID_MEMSPACE, command.requestId)
return
end
local length = endAddress - startAddress + 1
local requestedBanknum = server.readUint16(command.body, 7)
if not validateBanknum(memspace, requestedBanknum) then
server.errorResponse(server.errorType.INVALID_PARAMETER, command.requestId)
return
end
local banknum = requestedBanknum
local r = {}
r[#r+1] = server.uint16ToLittleEndian(length)
if banknum == banks.default then
banknum = banks.cpu
end
if not newSidefx and ( banknum == banks.cpu or banknum == banks.ppu ) then
banknum = banknum + 0x100
end
banknum = banknum - 1
local remainingByte = length % 2 ~= 0
for addr=startAddress,endAddress-1,2 do
r[#r+1] = server.uint16ToLittleEndian(emu.readWord(addr, banknum))
end
if remainingByte then
r[#r+1] = server.uint8ToLittleEndian(emu.read(addr, banknum))
end
server.response(server.responseType.MEM_GET, server.errorType.OK, command.requestId, table.concat(r))
end
nextTrap = 1
traps = {}
local operation = {
READ = 0x01,
WRITE = 0x02,
EXEC = 0x04,
}
local trapHandle
local function addCheckpoint(start, finish, stop, enabled, op, temp)
local num = nextTrap
nextTrap = nextTrap+1
local trap = {
start = start,
finish = finish,
stop = stop,
enabled = enabled,
op = op,
temp = temp,
hitCount = 0,
ignoreCount = 0,
condition = nil,
memspace = 0,
num = num,
}
traps[#traps+1] = trap
if trap.op & operation.EXEC ~= 0 then
trap.execRegistration = emu.addMemoryCallback(function()
if trap.enabled then
trapHandle(trap)
end
end, emu.memCallbackType.cpuExec, trap.start, trap.finish)
end
if trap.op & operation.READ ~= 0 then
trap.readRegistration = emu.addMemoryCallback(function()
if trap.enabled then
trapHandle(trap)
end
end, emu.memCallbackType.cpuRead, trap.start, trap.finish)
end
if trap.op & operation.WRITE ~= 0 then
trap.writeRegistration = emu.addMemoryCallback(function()
if trap.enabled then
trapHandle(trap)
end
end, emu.memCallbackType.cpuRead, trap.start, trap.finish)
end
return trap
end
local function removeCheckpoint(num)
local trap
for i=#traps,1,-1 do
trap = traps[i]
if trap.num == num then
table.remove(traps, i)
break
end
trap = nil
end
if trap == nil then
return false
end
if trap.execRegistration ~= nil then
emu.removeMemoryCallback(trap.execRegistration, emu.memCallbackType.cpuExec, trap.start, trap.finish)
end
if trap.readRegistration ~= nil then
emu.removeMemoryCallback(trap.readRegistration, emu.memCallbackType.cpuRead, trap.start, trap.finish)
end
if trap.writeRegistration ~= nil then
emu.removeMemoryCallback(trap.writeRegistration, emu.memCallbackType.cpuWrite, trap.start, trap.finish)
end
return true
end
local function toggleCheckpoint(enable, num)
for i=#traps,1,-1 do
trap = traps[i]
if trap.num == num then
break
end
trap = nil
end
if trap == nil then
return false
end
trap.enabled = enable
return true
end
local function processCheckpointGet(command)
if command.length < 4 then
server.errorResponse(server.errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local num = server.readUint32(command.body, 1)
for i=#traps,1,-1 do
trap = traps[i]
if trap.num == num then
break
end
trap = nil
end
if not trap then
server.errorResponse(server.errorType.OBJECT_MISSING, command.requestId)
return
end
responseCheckpointInfo(command.requestId, trap, false)
end
local function processCheckpointSet(command)
if command.length < 8 then
server.errorResponse(server.errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
-- Ignore the memspace - byte 9
local checkpt = addCheckpoint(
server.readUint16(command.body, 1),
server.readUint16(command.body, 3),
server.readBool(command.body, 5),
server.readBool(command.body, 6),
server.readUint8(command.body, 7),
server.readBool(command.body, 8)
)
responseCheckpointInfo(command.requestId, checkpt, 0)
end
local function processCheckpointDelete(command)
if command.length < 4 then
server.errorResponse(server.errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local brkNum = server.readUint32(command.body, 1)
local success = removeCheckpoint(brkNum)
if not success then
server.errorResponse(server.errorType.OBJECT_MISSING, command.requestId)
return
end
server.response(server.responseType.CHECKPOINT_DELETE, server.errorType.OK, command.requestId, nil)
end
local function processCheckpointList(command)
for i = 1,#traps,1 do
responseCheckpointInfo(command.requestId, traps[i], false)
end
local r = server.uint32ToLittleEndian(#traps)
server.response(server.responseType.CHECKPOINT_LIST, server.errorType.OK, command.requestId, r)
end
local function processCheckpointToggle(command)
if command.length < 5 then
server.errorResponse(server.errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local num = server.readUint32(command.body, 1)
local enable = server.readBool(command.body, 5)
if not toggleCheckpoint(enable, num) then
server.errorResponse(server.errorType.OBJECT_MISSING, command.requestId)
return
end
server.response(server.responseType.CHECKPOINT_TOGGLE, server.errorType.OK, command.requestId, nil)
end
local function validateRegister(memspace, regId)
return memspace == memspaces.MAIN and regId >= regMeta.a.id and regId <= regMeta.status.id
end
local function processRegistersGet(command)
if command.length < 1 then
server.errorResponse(server.errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local requestedMemspace = server.readUint8(command.body, 1)
local memspace = getRequestedMemspace(requestedMemspace)
if memspace == memspaces.INVALID then
server.errorResponse(server.errorType.INVALID_MEMSPACE, command.requestId)
return
end
responseRegisterInfo(command.requestId, memspace)
end
local function processRegistersSet(command)
local headerSize = 3
local count = server.readUint16(command.body, 2)
if command.length < headerSize + count * (3 + 1) then
server.errorResponse(server.errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local requestedMemspace = server.readUint8(command.body, 1)
local memspace = getRequestedMemspace(requestedMemspace)
if memspace == memspaces.INVALID then
server.errorResponse(server.errorType.INVALID_MEMSPACE, command.requestId)
return
end
local bodyCursor = headerSize + 1
local state = emu.getState()
for i=1,count do
local itemSize = server.readUint8(command.body, bodyCursor + 0)
local regId = server.readUint8(command.body, bodyCursor + 1)
local regVal = server.readUint16(command.body, bodyCursor + 2)
if itemSize < 3 then
server.errorResponse(server.errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
if not validateRegister(memspace, regId) then
server.errorResponse(server.errorType.OBJECT_MISSING, command.requestId)
return
end
for name, meta in pairs(regMeta) do
if meta.id == regId then
state.cpu[name] = regVal
break
end
end
bodyCursor = bodyCursor + itemSize + 1
end
emu.setState(state)
responseRegisterInfo(command.requestId, memspace)
end
function me.processCommand(apiVersion, bodyLength, remainingHeader, body)
local command = {}
command.apiVersion = apiVersion
if command.apiVersion < 0x01 or command.apiVersion > 0x02 then
server.errorResponse(server.errorType.INVALID_API_VERSION, command.requestId)
end
command.length = bodyLength
command.requestId = server.readUint32(remainingHeader, 1)
command.type = server.readUint8(remainingHeader, 5)
command.body = body
print(string.format("Command start: %02x", command.type))
local ct = command.type
if ct == server.commandType.MEM_GET then
processMemoryGet(command)
elseif ct == server.commandType.MEM_SET then
processMemorySet(command)
elseif ct == server.commandType.CHECKPOINT_GET then
processCheckpointGet(command)
elseif ct == server.commandType.CHECKPOINT_SET then
processCheckpointSet(command)
elseif ct == server.commandType.CHECKPOINT_DELETE then
processCheckpointDelete(command)
elseif ct == server.commandType.CHECKPOINT_LIST then
processCheckpointList(command)
elseif ct == server.commandType.CHECKPOINT_TOGGLE then
processCheckpointToggle(command)
elseif ct == server.commandType.REGISTERS_GET then
processRegistersGet(command)
elseif ct == server.commandType.REGISTERS_SET then
processRegistersSet(command)
elseif ct == server.commandType.ADVANCE_INSTRUCTIONS then
processAdvanceInstructions(command)
elseif ct == server.commandType.PING then
processPing(command)
elseif ct == server.commandType.EXIT then
processExit(command)
elseif ct == server.commandType.RESET then
processReset(command)
else
server.errorResponse(server.errorType.CMD_INVALID_TYPE, command.requestId)
print(string.format("unknown command: %d, skipping command length %d", command.type, command.length))
end
print(string.format("Command finished: %02x", command.type))
end
function trapHandle(trap)
print("Trap")
if conn == nil then
return
end
if trap.stop then
me.monitorOpened()
end
responseCheckpointInfo(requestId, trap, true)
if not trap.stop then
return
end
server.running = false
server.deregisterFrameCallback()
emu.breakExecution()
server.registerFrameCallback()
end
return me
end

876
main.lua
View file

@ -3,881 +3,11 @@ local function print(data)
_p(data .. "")
end
local socket = require("socket.core")
local baseDir = os.getenv("MESEN_REMOTE_BASEDIR")
local running = true
local regMeta = {
a = { name = "A", id = 0, size = 1 },
x = { name = "X", id = 1, size = 1 },
y = { name = "Y", id = 2, size = 1 },
pc = { name = "PC", id = 3, size = 2 },
sp = { name = "SP", id = 4, size = 1 },
status = { name = "FL", id = 5, size = 1 },
}
local server = dofile(baseDir.."/server.lua")
local host = os.getenv("MESEN_REMOTE_HOST") or "localhost"
local port = os.getenv("MESEN_REMOTE_PORT") or 9355
print("Binding to host '" ..host.. "' and port " ..port.. "...")
local server = assert(socket.tcp())
assert(server:bind(host, port))
assert(server:listen(32))
server:settimeout(0)
local i, p = server:getsockname()
assert(i, p)
print("Waiting for connection on " .. i .. ":" .. p .. "...")
local conn = nil
local function boolToLittleEndian(b)
local value = 0
if b then
value = 1
end
return string.char(value & 0xff)
end
local function uint8ToLittleEndian(value)
return string.char(value & 0xff)
end
local function uint16ToLittleEndian(value)
return string.char(value & 0xff, (value >> 8) & 0xff)
end
local function uint32ToLittleEndian(value)
return string.char(value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, (value >> 24) & 0xff)
end
local function readUint32(data, index)
return data:byte(index) + (data:byte(index + 1) << 8) + (data:byte(index + 2) << 16) + (data:byte(index + 3) << 24)
end
local function readUint16(data, index)
return data:byte(index) + (data:byte(index + 1) << 8)
end
local function readUint8(data, index)
return data:byte(index)
end
local function readBool(data, index)
return data:byte(index) > 0
end
local errorType = {
OK = 0x00,
OBJECT_MISSING = 0x01,
INVALID_MEMSPACE = 0x02,
CMD_INVALID_LENGTH = 0x80,
INVALID_PARAMETER = 0x81,
CMD_INVALID_API_VERSION = 0x82,
CMD_INVALID_TYPE = 0x83,
CMD_FAILURE = 0x8f,
};
local commandType = {
INVALID = 0x00,
MEM_GET = 0x01,
MEM_SET = 0x02,
CHECKPOINT_GET = 0x11,
CHECKPOINT_SET = 0x12,
CHECKPOINT_DELETE = 0x13,
CHECKPOINT_LIST = 0x14,
CHECKPOINT_TOGGLE = 0x15,
CONDITION_SET = 0x22,
REGISTERS_GET = 0x31,
REGISTERS_SET = 0x32,
DUMP = 0x41,
UNDUMP = 0x42,
RESOURCE_GET = 0x51,
RESOURCE_SET = 0x52,
ADVANCE_INSTRUCTIONS = 0x71,
KEYBOARD_FEED = 0x72,
EXECUTE_UNTIL_RETURN = 0x73,
PING = 0x81,
BANKS_AVAILABLE = 0x82,
REGISTERS_AVAILABLE = 0x83,
DISPLAY_GET = 0x84,
VICE_INFO = 0x85,
PALETTE_GET = 0x91,
EXIT = 0xaa,
QUIT = 0xbb,
RESET = 0xcc,
AUTOSTART = 0xdd,
}
local responseType = {
MEM_GET = 0x01,
MEM_SET = 0x02,
CHECKPOINT_INFO = 0x11,
CHECKPOINT_DELETE = 0x13,
CHECKPOINT_LIST = 0x14,
CHECKPOINT_TOGGLE = 0x15,
CONDITION_SET = 0x22,
REGISTER_INFO = 0x31,
DUMP = 0x41,
UNDUMP = 0x42,
RESOURCE_GET = 0x51,
RESOURCE_SET = 0x52,
JAM = 0x61,
STOPPED = 0x62,
RESUMED = 0x63,
ADVANCE_INSTRUCTIONS = 0x71,
KEYBOARD_FEED = 0x72,
EXECUTE_UNTIL_RETURN = 0x73,
PING = 0x81,
BANKS_AVAILABLE = 0x82,
REGISTERS_AVAILABLE = 0x83,
DISPLAY_GET = 0x84,
VICE_INFO = 0x85,
PALETTE_GET = 0x91,
EXIT = 0xaa,
QUIT = 0xbb,
RESET = 0xcc,
AUTOSTART = 0xdd,
}
local API_VERSION = 0x02
local EVENT_ID = 0xffffffff
local function response(responseType, errorCode, requestId, body)
if conn == nil then
return
end
local r = {}
r[#r+1] = string.char(0x02)
r[#r+1] = uint8ToLittleEndian(API_VERSION)
if body ~= nil then
r[#r+1] = uint32ToLittleEndian(body:len())
else
r[#r+1] = uint32ToLittleEndian(0)
end
r[#r+1] = uint8ToLittleEndian(responseType)
r[#r+1] = uint8ToLittleEndian(errorCode)
r[#r+1] = uint32ToLittleEndian(requestId)
conn:send(table.concat(r))
if body ~= nil then
conn:send(body)
end
end
local function responseCheckpointInfo(requestId, checkpt, hit)
local r = {}
r[#r+1] = uint32ToLittleEndian(checkpt.num)
r[#r+1] = boolToLittleEndian(hit)
r[#r+1] = uint16ToLittleEndian(checkpt.start)
r[#r+1] = uint16ToLittleEndian(checkpt.finish)
r[#r+1] = boolToLittleEndian(checkpt.stop)
r[#r+1] = boolToLittleEndian(checkpt.enabled)
r[#r+1] = uint8ToLittleEndian(checkpt.op)
r[#r+1] = boolToLittleEndian(checkpt.temp)
r[#r+1] = uint32ToLittleEndian(checkpt.hitCount)
r[#r+1] = uint32ToLittleEndian(checkpt.ignoreCount)
r[#r+1] = boolToLittleEndian(checkpt.condition ~= nil)
r[#r+1] = uint32ToLittleEndian(checkpt.memspace)
response(responseType.CHECKPOINT_INFO, errorType.OK, requestId, table.concat(r))
end
local function responseRegisterInfo(requestId)
local r = {}
local regs = emu.getState().cpu
local count = 0
for name, meta in pairs(regMeta) do
count = count + 1
end
r[#r+1] = uint16ToLittleEndian(count)
local itemSize = 3
for name, meta in pairs(regMeta) do
r[#r+1] = uint8ToLittleEndian(itemSize)
r[#r+1] = uint8ToLittleEndian(meta.id)
r[#r+1] = uint16ToLittleEndian(regs[name])
end
response(responseType.REGISTER_INFO, errorType.OK, requestId, table.concat(r))
end
local function responseStopped()
local pc = emu.getState().cpu.pc
response(responseType.STOPPED, errorType.OK, EVENT_ID, uint16ToLittleEndian(pc))
end
local function responseResumed()
local pc = emu.getState().cpu.pc
response(responseType.RESUMED, errorType.OK, EVENT_ID, uint16ToLittleEndian(pc))
end
local function monitorOpened()
print("Monitor opened")
responseRegisterInfo(EVENT_ID)
responseStopped(EVENT_ID)
end
local function monitorClosed()
print("Monitor closed")
responseRegisterInfo(EVENT_ID)
responseResumed(EVENT_ID)
end
local function errorResponse(errorCode, requestId)
response(0, errorCode, requestId, nil)
end
local stepping = false
local function processAdvanceInstructions(command)
if command.length < 3 then
errorResponse(errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local stepOverSubroutines = readBool(command.body, 1);
local count = readUint16(command.body, 2);
stepping = true
if stepOverSubroutines then
-- FIXME
emu.execute(count, emu.executeCountType.cpuInstructions)
else
emu.execute(count, emu.executeCountType.cpuInstructions)
end
running = true
response(responseType.ADVANCE_INSTRUCTIONS, errorType.OK, command.requestId, nil);
monitorClosed()
end
local function processPing(command)
response(responseType.PING, errorType.OK, command.requestId, nil)
end
local function processExit(command)
running = true
response(responseType.EXIT, errorType.OK, command.requestId, nil)
monitorClosed()
end
local function processReset(command)
running = true
emu.reset()
response(responseType.RESET, errorType.OK, command.requestId, nil)
monitorClosed()
end
local memspaces = {
MAIN = 0,
INVALID = -1,
}
local function getRequestedMemspace(requestedMemspace)
if requestedMemspace == 0 then
return memspaces.MAIN
else
return memspaces.INVALID
end
end
local banks = {
default = 0,
cpu = 1,
ppu = 2,
palette = 3,
oam = 4,
secondaryOam = 5
}
local function validateBanknum(memspace, banknum)
return memspace == memspaces.MAIN and banknum >= banks.default and banknum <= banks.secondaryOam
end
local function processMemorySet(command)
local newSidefx = readBool(command.body, 1)
local startAddress = readUint16(command.body, 2);
local endAddress = readUint16(command.body, 4);
if startAddress > endAddress then
errorResponse(errorType.INVALID_PARAMETER, command.requestId)
return
end
local length = endAddress - startAddress + 1;
if command.length < length + 8 then
errorResponse(errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local requestedMemspace = readUint8(command.body, 6);
local requestedBanknum = readUint16(command.body, 7);
local memspace = getRequestedMemspace(requestedMemspace);
if memspace == memspaces.INVALID then
errorResponse(errorType.INVALID_MEMSPACE, command.requestId)
return
end
if not validateBanknum(memspace, requestedBanknum) then
errorResponse(errorType.INVALID_PARAMETER, command.requestId)
return
end
local banknum = requestedBanknum;
if banknum == banks.default then
banknum = banks.cpu
end
if not newSidefx and ( banknum == banks.cpu or banknum == banks.ppu ) then
banknum = banknum + 0x100
end
banknum = banknum - 1
for i = 0,length - 1,1 do
local val = command.body:byte(8 + i + 1)
print(val)
emu.write(startAddress + i, val, banknum)
end
response(responseType.MEM_SET, errorType.OK, command.requestId, nil);
end
local function processMemoryGet(command)
if command.length < 8 then
errorResponse(errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local newSidefx = readBool(command.body, 1)
local startAddress = readUint16(command.body, 2);
local endAddress = readUint16(command.body, 4);
if startAddress > endAddress then
errorResponse(errorType.INVALID_PARAMETER, command.requestId)
return
end
local requestedMemspace = readUint8(command.body, 6);
local memspace = getRequestedMemspace(requestedMemspace);
if memspace == memspaces.INVALID then
errorResponse(errorType.INVALID_MEMSPACE, command.requestId)
return
end
local length = endAddress - startAddress + 1;
local requestedBanknum = readUint16(command.body, 7);
if not validateBanknum(memspace, requestedBanknum) then
errorResponse(errorType.INVALID_PARAMETER, command.requestId)
return
end
local banknum = requestedBanknum;
local r = {}
r[#r+1] = uint16ToLittleEndian(length);
if banknum == banks.default then
banknum = banks.cpu
end
if not newSidefx and ( banknum == banks.cpu or banknum == banks.ppu ) then
banknum = banknum + 0x100
end
banknum = banknum - 1
local remainingByte = length % 2 ~= 0
for addr=startAddress,endAddress-1,2 do
r[#r+1] = uint16ToLittleEndian(emu.readWord(addr, banknum))
end
if remainingByte then
r[#r+1] = uint8ToLittleEndian(emu.read(addr, banknum))
end
response(responseType.MEM_GET, errorType.OK, command.requestId, table.concat(r))
end
nextTrap = 1
traps = {}
local operation = {
READ = 0x01,
WRITE = 0x02,
EXEC = 0x04,
}
local trapHandle
local function addCheckpoint(start, finish, stop, enabled, op, temp)
local num = nextTrap
nextTrap = nextTrap+1
local trap = {
start = start,
finish = finish,
stop = stop,
enabled = enabled,
op = op,
temp = temp,
hitCount = 0,
ignoreCount = 0,
condition = nil,
memspace = 0,
num = num,
}
traps[#traps+1] = trap
if trap.op & operation.EXEC ~= 0 then
trap.execRegistration = emu.addMemoryCallback(function()
if trap.enabled then
trapHandle(trap)
end
end, emu.memCallbackType.cpuExec, trap.start, trap.finish)
end
if trap.op & operation.READ ~= 0 then
trap.readRegistration = emu.addMemoryCallback(function()
if trap.enabled then
trapHandle(trap)
end
end, emu.memCallbackType.cpuRead, trap.start, trap.finish)
end
if trap.op & operation.WRITE ~= 0 then
trap.writeRegistration = emu.addMemoryCallback(function()
if trap.enabled then
trapHandle(trap)
end
end, emu.memCallbackType.cpuRead, trap.start, trap.finish)
end
return trap
end
local function removeCheckpoint(num)
local trap
for i=#traps,1,-1 do
trap = traps[i]
if trap.num == num then
table.remove(traps, i)
break
end
trap = nil
end
if trap == nil then
return false
end
if trap.execRegistration ~= nil then
emu.removeMemoryCallback(trap.execRegistration, emu.memCallbackType.cpuExec, trap.start, trap.finish)
end
if trap.readRegistration ~= nil then
emu.removeMemoryCallback(trap.readRegistration, emu.memCallbackType.cpuRead, trap.start, trap.finish)
end
if trap.writeRegistration ~= nil then
emu.removeMemoryCallback(trap.writeRegistration, emu.memCallbackType.cpuWrite, trap.start, trap.finish)
end
return true
end
local function toggleCheckpoint(enable, num)
for i=#traps,1,-1 do
trap = traps[i]
if trap.num == num then
break
end
trap = nil
end
if trap == nil then
return false
end
trap.enabled = enable
return true
end
local function processCheckpointGet(command)
if command.length < 4 then
errorResponse(errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local num = readUint32(command.body, 1);
for i=#traps,1,-1 do
trap = traps[i]
if trap.num == num then
break
end
trap = nil
end
if not trap then
errorResponse(errorType.OBJECT_MISSING, command.requestId)
return
end
responseCheckpointInfo(command.requestId, trap, false);
end
local function processCheckpointSet(command)
if command.length < 8 then
errorResponse(errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
-- Ignore the memspace - byte 9
local checkpt = addCheckpoint(
readUint16(command.body, 1),
readUint16(command.body, 3),
readBool(command.body, 5),
readBool(command.body, 6),
readUint8(command.body, 7),
readBool(command.body, 8)
)
responseCheckpointInfo(command.requestId, checkpt, 0)
end
local function processCheckpointDelete(command)
if command.length < 4 then
errorResponse(errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local brkNum = readUint32(command.body, 1)
local success = removeCheckpoint(brkNum)
if not success then
errorResponse(errorType.OBJECT_MISSING, command.requestId)
return
end
response(responseType.CHECKPOINT_DELETE, errorType.OK, command.requestId, nil)
end
local function processCheckpointList(command)
for i = 1,#traps,1 do
responseCheckpointInfo(command.requestId, traps[i], false);
end
local r = uint32ToLittleEndian(#traps)
response(responseType.CHECKPOINT_LIST, errorType.OK, command.requestId, r);
end
local function processCheckpointToggle(command)
if command.length < 5 then
errorResponse(errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local num = readUint32(command.body, 1);
local enable = readBool(command.body, 5);
if not toggleCheckpoint(enable, num) then
errorResponse(errorType.OBJECT_MISSING, command.requestId)
return
end
response(responseType.CHECKPOINT_TOGGLE, errorType.OK, command.requestId, nil);
end
local function validateRegister(memspace, regId)
return memspace == memspaces.MAIN and regId >= regMeta.a.id and regId <= regMeta.status.id
end
local function processRegistersGet(command)
if command.length < 1 then
errorResponse(errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local requestedMemspace = readUint8(command.body, 1);
local memspace = getRequestedMemspace(requestedMemspace);
if memspace == memspaces.INVALID then
errorResponse(errorType.INVALID_MEMSPACE, command.requestId)
return
end
responseRegisterInfo(command.requestId, memspace);
end
local function processRegistersSet(command)
local headerSize = 3
local count = readUint16(command.body, 2);
if command.length < headerSize + count * (3 + 1) then
errorResponse(errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
local requestedMemspace = readUint8(command.body, 1);
local memspace = getRequestedMemspace(requestedMemspace);
if memspace == memspaces.INVALID then
errorResponse(errorType.INVALID_MEMSPACE, command.requestId)
return
end
local bodyCursor = headerSize + 1;
local state = emu.getState()
for i=1,count do
local itemSize = readUint8(command.body, bodyCursor + 0)
local regId = readUint8(command.body, bodyCursor + 1)
local regVal = readUint16(command.body, bodyCursor + 2)
if itemSize < 3 then
errorResponse(errorType.CMD_INVALID_LENGTH, command.requestId)
return
end
if not validateRegister(memspace, regId) then
errorResponse(errorType.OBJECT_MISSING, command.requestId)
return
end
for name, meta in pairs(regMeta) do
if meta.id == regId then
state.cpu[name] = regVal
break
end
end
bodyCursor = bodyCursor + itemSize + 1;
end
emu.setState(state)
responseRegisterInfo(command.requestId, memspace);
end
local function processCommand(apiVersion, bodyLength, remainingHeader, body)
local command = {}
command.apiVersion = apiVersion
if command.apiVersion < 0x01 or command.apiVersion > 0x02 then
errorResponse(errorType.INVALID_API_VERSION, command.requestId)
end
command.length = bodyLength
command.requestId = readUint32(remainingHeader, 1)
command.type = readUint8(remainingHeader, 5)
command.body = body
print(string.format("Command start: %02x", command.type))
local ct = command.type
if ct == commandType.MEM_GET then
processMemoryGet(command)
elseif ct == commandType.MEM_SET then
processMemorySet(command)
elseif ct == commandType.CHECKPOINT_GET then
processCheckpointGet(command)
elseif ct == commandType.CHECKPOINT_SET then
processCheckpointSet(command)
elseif ct == commandType.CHECKPOINT_DELETE then
processCheckpointDelete(command)
elseif ct == commandType.CHECKPOINT_LIST then
processCheckpointList(command)
elseif ct == commandType.CHECKPOINT_TOGGLE then
processCheckpointToggle(command)
elseif ct == commandType.REGISTERS_GET then
processRegistersGet(command)
elseif ct == commandType.REGISTERS_SET then
processRegistersSet(command)
elseif ct == commandType.ADVANCE_INSTRUCTIONS then
processAdvanceInstructions(command)
elseif ct == commandType.PING then
processPing(command)
elseif ct == commandType.EXIT then
processExit(command)
elseif ct == commandType.RESET then
processReset(command)
else
errorResponse(errorType.CMD_INVALID_TYPE, command.requestId)
print(string.format("unknown command: %d, skipping command length %d", command.type, command.length))
end
print(string.format("Command finished: %02x", command.type))
end
local function prepareCommand()
if conn == nil then
running = true
return
end
if running then
conn:settimeout(0)
else
conn:settimeout(-1)
end
local data, status, partial = conn:receive(1)
if status == "timeout" then
return
elseif status == "closed" then
conn = nil
running = true
return
end
if data:byte(1) ~= 0x02 then
return
end
if running then
running = false
monitorOpened()
end
conn:settimeout(-1)
data, status = conn:receive(1 + 4)
local apiVersion = readUint8(data, 1)
local bodyLength = readUint32(data, 2)
local remainingHeaderSize = 5
local remainingHeader, status = conn:receive(remainingHeaderSize)
local body
if bodyLength > 0 then
body, status = conn:receive(bodyLength)
end
processCommand(apiVersion, bodyLength, remainingHeader, body)
end
local function initConnection()
if conn == nil then
conn, err = server:accept()
if conn == nil or err ~= nil then
return
end
end
end
local deregisterFrameCallback
local registerFrameCallback
local function frameHandle()
initConnection()
if conn == nil then
return
end
prepareCommand()
if not running then
deregisterFrameCallback()
emu.breakExecution()
registerFrameCallback()
end
end
local frameCallback = nil
function registerFrameCallback()
frameCallback = emu.addEventCallback(frameHandle, emu.eventType.inputPolled)
end
function deregisterFrameCallback()
emu.removeEventCallback(frameCallback, emu.eventType.inputPolled)
end
function trapHandle(trap)
print("Trap")
if conn == nil then
return
end
if trap.stop then
monitorOpened()
end
responseCheckpointInfo(requestId, trap, true)
if not trap.stop then
return
end
running = false
deregisterFrameCallback()
emu.breakExecution()
registerFrameCallback()
end
local function breakHandle()
print("Break")
if stepping then
stepping = false
monitorOpened()
end
repeat
prepareCommand()
until running
emu.resume()
end
registerFrameCallback()
emu.addEventCallback(breakHandle, emu.eventType.codeBreak)
server.start(host, port)

312
server.lua Normal file
View file

@ -0,0 +1,312 @@
local _p = emu.log
local function print(data)
_p(data .. "")
end
local baseDir = os.getenv("MESEN_REMOTE_BASEDIR")
local socket = require("socket.core")
local me = {}
local commands = dofile(baseDir.."/commands.lua")(me)
me.running = false
me.stepping = false
me.conn = nil
me.server = nil
function me.boolToLittleEndian(b)
local value = 0
if b then
value = 1
end
return string.char(value & 0xff)
end
function me.uint8ToLittleEndian(value)
if value == nil then
error("value is nil", 2)
end
return string.char(value & 0xff)
end
function me.uint16ToLittleEndian(value)
if value == nil then
error("value is nil", 2)
end
return string.char(value & 0xff, (value >> 8) & 0xff)
end
function me.uint32ToLittleEndian(value)
if value == nil then
error("value is nil", 2)
end
return string.char(value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, (value >> 24) & 0xff)
end
function me.readUint32(data, index)
return data:byte(index) + (data:byte(index + 1) << 8) + (data:byte(index + 2) << 16) + (data:byte(index + 3) << 24)
end
function me.readUint16(data, index)
return data:byte(index) + (data:byte(index + 1) << 8)
end
function me.readUint8(data, index)
return data:byte(index)
end
function me.readBool(data, index)
return data:byte(index) > 0
end
me.errorType = {
OK = 0x00,
OBJECT_MISSING = 0x01,
INVALID_MEMSPACE = 0x02,
CMD_INVALID_LENGTH = 0x80,
INVALID_PARAMETER = 0x81,
CMD_INVALID_API_VERSION = 0x82,
CMD_INVALID_TYPE = 0x83,
CMD_FAILURE = 0x8f,
};
me.commandType = {
INVALID = 0x00,
MEM_GET = 0x01,
MEM_SET = 0x02,
CHECKPOINT_GET = 0x11,
CHECKPOINT_SET = 0x12,
CHECKPOINT_DELETE = 0x13,
CHECKPOINT_LIST = 0x14,
CHECKPOINT_TOGGLE = 0x15,
CONDITION_SET = 0x22,
REGISTERS_GET = 0x31,
REGISTERS_SET = 0x32,
DUMP = 0x41,
UNDUMP = 0x42,
RESOURCE_GET = 0x51,
RESOURCE_SET = 0x52,
ADVANCE_INSTRUCTIONS = 0x71,
KEYBOARD_FEED = 0x72,
EXECUTE_UNTIL_RETURN = 0x73,
PING = 0x81,
BANKS_AVAILABLE = 0x82,
REGISTERS_AVAILABLE = 0x83,
DISPLAY_GET = 0x84,
VICE_INFO = 0x85,
PALETTE_GET = 0x91,
EXIT = 0xaa,
QUIT = 0xbb,
RESET = 0xcc,
AUTOSTART = 0xdd,
}
me.responseType = {
MEM_GET = 0x01,
MEM_SET = 0x02,
CHECKPOINT_INFO = 0x11,
CHECKPOINT_DELETE = 0x13,
CHECKPOINT_LIST = 0x14,
CHECKPOINT_TOGGLE = 0x15,
CONDITION_SET = 0x22,
REGISTER_INFO = 0x31,
DUMP = 0x41,
UNDUMP = 0x42,
RESOURCE_GET = 0x51,
RESOURCE_SET = 0x52,
JAM = 0x61,
STOPPED = 0x62,
RESUMED = 0x63,
ADVANCE_INSTRUCTIONS = 0x71,
KEYBOARD_FEED = 0x72,
EXECUTE_UNTIL_RETURN = 0x73,
PING = 0x81,
BANKS_AVAILABLE = 0x82,
REGISTERS_AVAILABLE = 0x83,
DISPLAY_GET = 0x84,
VICE_INFO = 0x85,
PALETTE_GET = 0x91,
EXIT = 0xaa,
QUIT = 0xbb,
RESET = 0xcc,
AUTOSTART = 0xdd,
}
me.API_VERSION = 0x02
me.EVENT_ID = 0xffffffff
function me.response(responseType, errorCode, requestId, body)
if responseType == nil or errorCode == nil or requestId == nil then
error("A required response value was nil", 2)
end
if me.conn == nil then
return
end
local r = {}
r[#r+1] = string.char(0x02)
r[#r+1] = me.uint8ToLittleEndian(me.API_VERSION)
if body ~= nil then
r[#r+1] = me.uint32ToLittleEndian(body:len())
else
r[#r+1] = me.uint32ToLittleEndian(0)
end
r[#r+1] = me.uint8ToLittleEndian(responseType)
r[#r+1] = me.uint8ToLittleEndian(errorCode)
r[#r+1] = me.uint32ToLittleEndian(requestId)
me.conn:send(table.concat(r))
if body ~= nil then
me.conn:send(body)
end
end
function me.errorResponse(errorCode, requestId)
me.response(0, errorCode, requestId, nil)
end
local function initConnection()
if me.conn == nil then
me.conn, err = me.server:accept()
if me.conn == nil or err ~= nil then
return
end
print("Connected")
end
end
local function prepareCommand()
if me.conn == nil then
me.running = true
return
end
if me.running then
me.conn:settimeout(0)
else
me.conn:settimeout(-1)
end
local data, status, partial = me.conn:receive(1)
if status == "timeout" then
return
elseif status == "closed" then
me.conn = nil
me.running = true
return
end
if data:byte(1) ~= 0x02 then
return
end
if me.running then
me.running = false
commands.monitorOpened()
end
me.conn:settimeout(-1)
data, status = me.conn:receive(1 + 4)
local apiVersion = me.readUint8(data, 1)
local bodyLength = me.readUint32(data, 2)
local remainingHeaderSize = 5
local remainingHeader, status = me.conn:receive(remainingHeaderSize)
local body
if bodyLength > 0 then
body, status = me.conn:receive(bodyLength)
end
commands.processCommand(apiVersion, bodyLength, remainingHeader, body)
end
me.deregisterFrameCallback = nil
me.registerFrameCallback = nil
local function frameHandle()
initConnection()
if me.conn == nil then
return
end
prepareCommand()
if not me.running then
me.deregisterFrameCallback()
emu.breakExecution()
me.registerFrameCallback()
end
end
local frameCallback = nil
function me.registerFrameCallback()
frameCallback = emu.addEventCallback(frameHandle, emu.eventType.inputPolled)
end
function me.deregisterFrameCallback()
emu.removeEventCallback(frameCallback, emu.eventType.inputPolled)
end
local function breakHandle()
print("Break")
if me.stepping then
me.stepping = false
commands.monitorOpened()
end
repeat
prepareCommand()
until me.running
emu.resume()
end
function me.start(host, port)
me.stepping = false
me.running = true
print("Binding to host '" ..host.. "' and port " ..port.. "...")
me.server = assert(socket.tcp())
assert(me.server:bind(host, port))
assert(me.server:listen(32))
me.server:settimeout(0)
local i, p = me.server:getsockname()
assert(i, p)
print("Waiting for connection on " .. i .. ":" .. p .. "...")
me.registerFrameCallback()
emu.addEventCallback(breakHandle, emu.eventType.codeBreak)
end
return me