mirror of
https://github.com/notwa/mm
synced 2024-11-13 22:39:02 -08:00
159 lines
4.4 KiB
Lua
159 lines
4.4 KiB
Lua
#!/usr/bin/env luajit
|
|
--require "test.strict"
|
|
local assemble = require "lips.init"
|
|
local cereal = require "serialize"
|
|
local argparse = require "argparse"
|
|
|
|
local function lament(...)
|
|
io.stdout:write(...)
|
|
io.stdout:write('\n')
|
|
end
|
|
|
|
local function parsenum(s)
|
|
if type(s) == 'number' then
|
|
return s
|
|
end
|
|
if s:sub(1, 2) == '0x' or s:sub(1, 1) == '$' then
|
|
return tonumber(s, 16)
|
|
elseif s:sub(1, 2) == '0o' or s:sub(1, 1) == '0' then
|
|
return tonumber(s, 8)
|
|
elseif s:sub(1, 2) == '0b' or s:sub(1, 1) == '%' then
|
|
return tonumber(s, 2)
|
|
else
|
|
return tonumber(s)
|
|
end
|
|
end
|
|
|
|
local function make_verbose_writer()
|
|
-- TODO: further optimize
|
|
local buff = {}
|
|
local function write(i)
|
|
local a = buff[i+0] or nil
|
|
local b = buff[i+1] or nil
|
|
local c = buff[i+2] or nil
|
|
local d = buff[i+3] or nil
|
|
if a or b or c or d then
|
|
a = a and ("%02X"):format(a) or '--'
|
|
b = b and ("%02X"):format(b) or '--'
|
|
c = c and ("%02X"):format(c) or '--'
|
|
d = d and ("%02X"):format(d) or '--'
|
|
print(('%08X %s'):format(i, a..b..c..d))
|
|
end
|
|
end
|
|
|
|
local max = -1
|
|
local maxp = -1
|
|
return function(pos, b)
|
|
if pos then
|
|
buff[pos] = b
|
|
if pos > max then
|
|
max = pos
|
|
end
|
|
if pos < 0x80000000 and pos > maxp then
|
|
maxp = pos
|
|
end
|
|
elseif max >= 0 then
|
|
for i=0, maxp, 4 do
|
|
write(i)
|
|
end
|
|
for i=0x80000000, max, 4 do
|
|
write(i)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function inject(args)
|
|
local offset = args.offset and parsenum(args.offset) or 0
|
|
local origin = args.origin and parsenum(args.origin) or 0
|
|
local base = args.base and parsenum(args.base) or 0x80000000
|
|
|
|
local f
|
|
if args.output then
|
|
f = io.open(args.output, 'r+b')
|
|
if not f then
|
|
lament("file not found: ", args.output)
|
|
return
|
|
end
|
|
end
|
|
|
|
local state = {}
|
|
for _, import in ipairs(args.import) do
|
|
local new_state = cereal.deserialize(import)
|
|
for k, v in pairs(new_state) do
|
|
state[k] = v
|
|
end
|
|
end
|
|
|
|
local function write(pos, b)
|
|
if pos >= offset then
|
|
pos = pos - offset
|
|
end
|
|
if pos >= 1024*1024*1024 then
|
|
lament(("oops: %08X %02X"):format(pos, b))
|
|
return
|
|
end
|
|
|
|
if f then
|
|
f:seek('set', pos)
|
|
f:write(string.char(b))
|
|
else
|
|
print(("%08X %02X"):format(pos, b))
|
|
end
|
|
end
|
|
|
|
local options = {
|
|
unsafe = true,
|
|
labels = state,
|
|
debug_token = args.dump_token,
|
|
debug_pre = args.dump_pre,
|
|
debug_post = args.dump_post,
|
|
debug_asm = args.dump_asm,
|
|
}
|
|
if args.offset then
|
|
if args.origin or args.base then
|
|
error('--offset is mutually exclusive from --origin, --base')
|
|
end
|
|
options.offset = offset
|
|
else
|
|
options.origin = origin
|
|
options.base = base
|
|
end
|
|
|
|
if f then
|
|
assemble(args.input, write, options)
|
|
else
|
|
local vb = make_verbose_writer()
|
|
assemble(args.input, vb, options)
|
|
vb()
|
|
end
|
|
|
|
if args.export then
|
|
cereal.serialize(args.export, state)
|
|
end
|
|
|
|
if f then
|
|
f:close()
|
|
end
|
|
end
|
|
|
|
local ap = argparse("patch", "patch a binary file with assembly")
|
|
|
|
-- TODO: option to dump hex or gs codes when no output is given
|
|
ap:argument("input", "input assembly file")
|
|
ap:argument("output", "output binary file"):args('?')
|
|
ap:option("-o --offset", "(deprecated) offset to pass to lips", nil)
|
|
ap:option("-O --origin", "origin to pass to lips", nil)
|
|
ap:option("-b --base", "base to pass to lips", nil)
|
|
ap:option("-i --import", "import state file(s) containing labels"):count("*")
|
|
ap:option("-e --export", "export state file containing labels")
|
|
ap:flag("--dump-token", "(debug) dump statements to stdout after lexing")
|
|
ap:flag("--dump-pre", "(debug) dump statements to stdout after preprocessing")
|
|
ap:flag("--dump-post", "(debug) dump statements to stdout after expanding")
|
|
ap:flag("--dump-asm", "(debug) dump statements to stdout after assembling")
|
|
--ap:option("-s --state", "--import and --export to this file")
|
|
-- TODO: use -D defines instead
|
|
|
|
local inject_args = ap:parse()
|
|
|
|
inject(inject_args)
|