322 lines
9.3 KiB
Lua
322 lines
9.3 KiB
Lua
|
|
local function trim(s) return s:gsub("^ +", ""):gsub(" +$", "").."" end
|
|
|
|
local function getCodeDir()
|
|
local f0 = arg[0]:gsub("/", "\\")
|
|
local d0 = f0:match("(.+)[\\][^\\]+$") or "."
|
|
return d0
|
|
end
|
|
|
|
local function popenResult(cmd)
|
|
local fp = io.popen(cmd)
|
|
local out = fp:read("*a")
|
|
fp:close()
|
|
return out
|
|
end
|
|
|
|
local function memFromCustomasmHexstr(out)
|
|
local mem = {}
|
|
local addr = 0
|
|
for hexS in out:gmatch("[0-9a-fA-F][0-9a-fA-F]") do
|
|
local val = tonumber(hexS, 16)
|
|
if val~=0 then
|
|
mem[addr] = val
|
|
end
|
|
addr = addr + 1
|
|
end
|
|
local code = nil
|
|
local symbols = nil
|
|
return mem, code, symbols
|
|
end
|
|
|
|
local function toData(s)
|
|
local d = {}
|
|
for p in s:gmatch("[0-9a-zA-Z][0-9a-zA-Z]") do
|
|
table.insert(d, tonumber(p, 16))
|
|
end
|
|
return d
|
|
end
|
|
local function memFromCustomasmAnnotated(out)
|
|
local mem = {}
|
|
local code = {}
|
|
local symbols = {}
|
|
|
|
local lastLabel = {}
|
|
|
|
for line in out:gmatch("[^\r\n]+") do
|
|
if not line:find("^ outp | addr | data %(base 16%)$") then
|
|
local addrS, dataS, comment = line:match("^[^|]+ | ([^|]+) | ([^;]+) ; (.+)$")
|
|
local addr = tonumber(addrS, 16)
|
|
local data = toData(dataS)
|
|
if comment:sub(#comment, #comment)==":" then
|
|
assert(#data==0)
|
|
local label = comment:sub(1, #comment-1)
|
|
local noDots = label:gsub("^%.+", "")
|
|
local numDots = #label - #noDots
|
|
local fullLabel = numDots>0 and lastLabel[numDots-1].."."..noDots or label
|
|
lastLabel[numDots] = fullLabel
|
|
symbols[addr] = fullLabel
|
|
else
|
|
if comment:match("^[a-z]+") then
|
|
for i, v in ipairs(data) do
|
|
mem [addr+i-1] = v
|
|
code[addr+i-1] = true
|
|
end
|
|
else
|
|
for i, v in ipairs(data) do
|
|
mem [addr+i-1] = v
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return mem, code, symbols
|
|
end
|
|
|
|
local function fullPath(fn)
|
|
return popenResult(getCodeDir().."\\fullPath.bat \""..fn.."\"")
|
|
end
|
|
|
|
local function assembleFile(fn, arch)
|
|
local fnf = fullPath(fn)
|
|
--local out = popenResult("customasm -p -q --format hexstr \""..fnf.."\"")
|
|
--return memFromCustomasmHexstr(out)
|
|
local out = popenResult("customasm -p -q --format annotated,base:16,group:2 \""..fnf.."\"")
|
|
return memFromCustomasmAnnotated(out)
|
|
end
|
|
|
|
local function mnemsFromArch(arch)
|
|
local mnems = {}
|
|
for _, instr in ipairs(arch.instructions) do
|
|
if instr.mnem then
|
|
local len = 1
|
|
for l in instr.mnem:gmatch("imm([0-9]+)") do len = len + tonumber(l)/8 end
|
|
mnems[instr.opcode] = { mnem = instr.mnem, rel = instr.rel, jmp = instr.jmp, len = len, }
|
|
end
|
|
end
|
|
return mnems
|
|
end
|
|
|
|
local function symbolCrunchDots(s) return s:gsub("[^%.]+%.", "%.").."" end
|
|
|
|
local function toSigned8(x) return x>=128 and x-256 or x end
|
|
local function disassembleMemory(arch, mem, code, symbols)
|
|
local mnems = mnemsFromArch(arch)
|
|
|
|
local addr = 0
|
|
local function nextByte(d) local b = mem[addr]; addr = addr+1; return b or d; end
|
|
local lastaddr = 0
|
|
local jmpaddrs = {}
|
|
while addr<=0xFFFF do
|
|
local startaddr = addr
|
|
local opcode = nextByte()
|
|
if opcode and ((not code) or code[startaddr]) then
|
|
local mnem = mnems[opcode]
|
|
if mnem then
|
|
if mnem.jmp then
|
|
local jmpdest
|
|
if mnem.rel then jmpdest = toSigned8(nextByte(0)) + addr
|
|
else jmpdest = nextByte(0)*256 + nextByte(0)
|
|
end
|
|
if jmpdest then
|
|
if not jmpaddrs[jmpdest] then
|
|
jmpaddrs[jmpdest] = { rel = mnem.rel, from = {}, }
|
|
end
|
|
table.insert(jmpaddrs[jmpdest].from, startaddr)
|
|
jmpaddrs[jmpdest].rel = jmpaddrs[jmpdest].rel and mnem.rel
|
|
end
|
|
else
|
|
addr = addr + mnem.len - 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local labelnum, subnum = 0, 0
|
|
for dest, jmp in pairs(jmpaddrs) do
|
|
if symbols[dest] then jmp.name = symbolCrunchDots(symbols[dest])
|
|
elseif jmp.rel then jmp.name = "label_" ..labelnum; labelnum = labelnum+1;
|
|
else jmp.name = "subroutine_"..subnum ; subnum = subnum +1; end
|
|
end
|
|
|
|
local maxLabelLen = 20
|
|
local lines = {}
|
|
addr = 0
|
|
while addr<=0xFFFF do
|
|
local startaddr = addr
|
|
local opcode = nextByte()
|
|
if opcode and ((not code) or code[startaddr]) then
|
|
local line = {}
|
|
local mnem = mnems[opcode].mnem or "???"
|
|
table.insert(line, trim(mnem:gsub("imm[0-9]+", "")))
|
|
local tlen = 1
|
|
for lens in mnem:gmatch("imm([0-9]+)") do local len = tonumber(lens)/8
|
|
if len==1 then
|
|
local data = nextByte(0)
|
|
local jmp
|
|
if mnems[opcode].rel then
|
|
local jmpdest = (addr + toSigned8(data))%65536
|
|
jmp = jmpaddrs[jmpdest]
|
|
if jmp then
|
|
table.insert(line, jmp.name)
|
|
--table.insert(line, ";")
|
|
--table.insert(line, "$"..string.format("%04X", jmpdest)..",")
|
|
end
|
|
end
|
|
if not jmp then table.insert(line, "$"..string.format("%02X", data)) end
|
|
elseif len==2 then
|
|
local data = nextByte(0)*256 + nextByte(0)
|
|
local jmp
|
|
if mnems[opcode].jmp then
|
|
local jmpdest = data
|
|
jmp = jmpaddrs[jmpdest]
|
|
if jmp then
|
|
table.insert(line, jmp.name)
|
|
--table.insert(line, ";")
|
|
end
|
|
end
|
|
if not jmp then table.insert(line, "$"..string.format("%04X", data)) end
|
|
else error("invalid imm len") end
|
|
tlen = tlen + len
|
|
end
|
|
local lineb = {}
|
|
for i = addr-tlen, addr-1 do
|
|
table.insert(lineb, string.format("%02X", mem[i] or 0))
|
|
end
|
|
local label = ""
|
|
local jmp = jmpaddrs[startaddr]
|
|
if symbols[startaddr] then label = symbolCrunchDots(symbols[startaddr])..":"
|
|
elseif jmp then label = jmp.name..":" end
|
|
local lb = table.concat(lineb, " ")
|
|
if lastaddr~=addr-tlen then table.insert(lines, "...") end
|
|
table.insert(lines, string.format("%04X", addr-tlen).." | "..(" "):rep(8-#lb)..lb.." | "..(" "):rep(maxLabelLen-#label)..label.." "..table.concat(line, " "))
|
|
lastaddr = addr
|
|
elseif opcode and opcode~=0 then
|
|
table.insert(lines, "data: "..string.format("%02X", opcode))
|
|
end
|
|
end
|
|
return table.concat(lines, "\n")
|
|
end
|
|
local function memToHex(hex)
|
|
local mem = {}
|
|
local addr = 0
|
|
for d in hex:gmatch("[0-9a-fA-F][0-9a-fA-F]") do
|
|
mem[addr] = tonumber(d, 16)
|
|
addr = addr+1
|
|
end
|
|
return mem
|
|
end
|
|
local function disassembleHex(hex, arch)
|
|
return disassembleMemory(arch, memToHex(hex), nil, nil)
|
|
end
|
|
|
|
local printableCharsS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`-=[]\\;\',./~!@#$%^&*()_+{}|:\"<> "
|
|
local printableChars = {}; for i = 1, #printableCharsS do printableChars[printableCharsS:sub(i, i)] = true end;
|
|
local function toPrintableChar(n)
|
|
local c = string.char(n)
|
|
return printableChars[c] and c or "?"
|
|
end
|
|
local function printMemory(mem)
|
|
local anynonempty = false
|
|
local lastbase = -16
|
|
local lastline = ""
|
|
local numreps = 0
|
|
local lines = {}
|
|
local function closereps(base)
|
|
if numreps~=0 then
|
|
table.insert(lines, "(repeated "..numreps.." more times, up to "..string.format("%04X", base+15)..")")
|
|
numreps = 0
|
|
end
|
|
end
|
|
for base = 0, 0xFFF0, 16 do
|
|
local line = {}
|
|
local strt = {}
|
|
local nonempty = false
|
|
for addr = base, base+15 do
|
|
if addr%4==0 then table.insert(line, " ") end
|
|
if mem[addr]==false then
|
|
nonempty = true
|
|
table.insert(line, "XX ")
|
|
table.insert(strt, "X")
|
|
elseif mem[addr] then
|
|
nonempty = true
|
|
table.insert(line, string.format("%02X", mem[addr]).." ")
|
|
table.insert(strt, toPrintableChar(mem[addr]))
|
|
else
|
|
table.insert(line, "-- ")
|
|
table.insert(strt, "-")
|
|
end
|
|
end
|
|
if nonempty then
|
|
local l = table.concat(line)
|
|
if l~=lastline or base~=lastbase+16 then
|
|
closereps(base-16)
|
|
if base ~= lastbase+16 then table.insert(lines, "...") end
|
|
table.insert(lines, string.format("%04X", base).." | "..l.." | "..table.concat(strt))
|
|
else
|
|
numreps = numreps+1
|
|
end
|
|
lastline = l
|
|
lastbase = base
|
|
anynonempty = true
|
|
end
|
|
end
|
|
closereps(lastbase)
|
|
if not anynonempty then table.insert(lines, "Empty") end
|
|
|
|
return table.concat(lines, "\n")
|
|
end
|
|
|
|
local HasTs = ts~=nil
|
|
local ts = ts or {
|
|
call = function() end,
|
|
eval = function() end,
|
|
}
|
|
ts.eval [[
|
|
function commandShiftBrick(%x, %y, %z) { commandToServer('shiftBrick', %x, %y, %z); }
|
|
function commandPlantBrick() { commandToServer('plantBrick'); }
|
|
]]
|
|
local function plantBrickAt(brickpos, pos)
|
|
local dx, dy, dz = pos[1]-brickpos[1], pos[2]-brickpos[2], pos[3]-brickpos[3]
|
|
ts.call("commandShiftBrick", dy, -dx, dz)
|
|
ts.call("commandPlantBrick")
|
|
brickpos[1], brickpos[2], brickpos[3] = pos[1], pos[2], pos[3]
|
|
end
|
|
local function buildMemory(mem, romsize, offset, len)
|
|
offset = offset or 0
|
|
|
|
local rombytes = romsize[1]*romsize[2]*romsize[3]/8
|
|
if len and len>rombytes then error("rom not big enough to hold "..len.." bytes (holds "..rombytes..")") end
|
|
if not len then
|
|
for i = 0, 0xFFFF do
|
|
if mem[i] and (i<offset or i>=offset+rombytes) then error("memory does not fit in rom at addr "..string.format("%04X", i)) end
|
|
end
|
|
end
|
|
|
|
local brickpos = {0, 0, 0}
|
|
for x = 0, romsize[1]-1 do
|
|
for y = 0, romsize[2]-1 do
|
|
for z = 0, romsize[3]-1 do
|
|
local addr = offset + ((romsize[3]/8)*(x + y*romsize[1]) + math.floor(z/8))
|
|
local pow = math.pow(2, z%8)
|
|
local data = (addr>=offset and ((not len) or addr<offset+len) and mem[addr]) or 0
|
|
local bit = math.floor(data/pow)%2
|
|
if bit==1 then plantBrickAt(brickpos, {x, -y, z}) end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if arg[1] then
|
|
local fn = arg[1]
|
|
local mem, code, symbols = assembleFile(fn)
|
|
local arch = dofile(getCodeDir().."\\rom-8608-defs.lua")
|
|
print(disassembleMemory(arch, mem, code, symbols))
|
|
else
|
|
return {
|
|
assembleFile = assembleFile,
|
|
disassembleMemory = disassembleMemory,
|
|
printMemory = printMemory,
|
|
}
|
|
end
|