1
0
Fork 0
mirror of https://github.com/notwa/mm synced 2024-11-05 04:39:03 -08:00

update lips and patch

This commit is contained in:
Connor Olding 2016-05-08 15:24:01 -07:00
parent 1f1e7418ba
commit 2edbe2135a
10 changed files with 365 additions and 167 deletions

View file

@ -108,12 +108,12 @@ function Collector:directive()
if self:is_EOL() and name == 'ALIGN' then
add(name)
else
local size = self:number()
local size = self:const(nil, 'no label')
if self:is_EOL() then
add(name, size)
else
self:optional_comma()
add(name, size, self:number())
add(name, size, self:const(nil, 'no label'))
end
end
elseif name == 'BYTE' or name == 'HALFWORD' or name == 'WORD' then

View file

@ -139,40 +139,23 @@ function Dumper:format_in(informat)
for i=1, #informat do
self.i = i
local c = informat:sub(i, i)
if c == 'd' and not args.rd then
args.rd = self:register(data.registers)
elseif c == 's' and not args.rs then
args.rs = self:register(data.registers)
elseif c == 't' and not args.rt then
args.rt = self:register(data.registers)
elseif c == 'D' and not args.fd then
args.fd = self:register(data.fpu_registers)
elseif c == 'S' and not args.fs then
args.fs = self:register(data.fpu_registers)
elseif c == 'T' and not args.ft then
args.ft = self:register(data.fpu_registers)
elseif c == 'X' and not args.rd then
args.rd = self:register(data.sys_registers)
elseif c == 'Y' and not args.rs then
args.rs = self:register(data.sys_registers)
elseif c == 'Z' and not args.rt then
args.rt = self:register(data.sys_registers)
elseif c == 'o' and not args.offset then
args.offset = self:const():set('signed')
elseif c == 'r' and not args.offset then
args.offset = self:const('relative'):set('signed')
elseif c == 'i' and not args.immediate then
args.immediate = self:const(nil, 'no label')
elseif c == 'I' and not args.index then
args.index = self:const():set('index')
elseif c == 'k' and not args.immediate then
args.immediate = self:const(nil, 'no label'):set('signed'):set('negate')
elseif c == 'K' and not args.immediate then
args.immediate = self:const(nil, 'no label'):set('signed')
elseif c == 'b' and not args.base then
args.base = self:deref():set('tt', 'REG')
else
error('Internal Error: invalid input formatting string')
if c == 'd' then args.rd = self:register(data.registers)
elseif c == 's' then args.rs = self:register(data.registers)
elseif c == 't' then args.rt = self:register(data.registers)
elseif c == 'D' then args.fd = self:register(data.fpu_registers)
elseif c == 'S' then args.fs = self:register(data.fpu_registers)
elseif c == 'T' then args.ft = self:register(data.fpu_registers)
elseif c == 'X' then args.rd = self:register(data.sys_registers)
elseif c == 'Y' then args.rs = self:register(data.sys_registers)
elseif c == 'Z' then args.rt = self:register(data.sys_registers)
elseif c == 'o' then args.offset = self:const():set('signed')
elseif c == 'r' then args.offset = self:const('relative'):set('signed')
elseif c == 'i' then args.immediate = self:const(nil, 'no label')
elseif c == 'I' then args.index = self:const():set('index')
elseif c == 'k' then args.immediate = self:const(nil, 'no label'):set('signed'):set('negate')
elseif c == 'K' then args.immediate = self:const(nil, 'no label'):set('signed')
elseif c == 'b' then args.base = self:deref():set('tt', 'REG')
else error('Internal Error: invalid input formatting string')
end
end
return args
@ -188,7 +171,7 @@ function Dumper:format_out_raw(outformat, first, args, const, formatconst)
local out = {}
for i=1, #outformat do
local c = outformat:sub(i, i)
if c == 'd' then out[#out+1] = args.rd
if c == 'd' then insert(out, args.rd)
elseif c == 's' then insert(out, args.rs)
elseif c == 't' then insert(out, args.rt)
elseif c == 'D' then insert(out, args.fd)

View file

@ -300,7 +300,10 @@ function Lexer:lex_include(_yield)
if self.options.path then
fn = self.options.path..fn
end
local sublexer = Lexer(util.readfile(fn), fn, self.options)
local new_options = setmetatable({}, {__index=self.options})
new_options.path = fn:match(".*/")
local sublexer = Lexer(util.readfile(fn), fn, new_options)
sublexer:lex(_yield)
end

View file

@ -146,6 +146,9 @@ function Preproc:process(statements)
end
insert(new_statements, s)
else
for j, t in ipairs(s) do
self:lookup(t)
end
insert(new_statements, s)
end
else
@ -191,7 +194,7 @@ end
function Preproc:pop(kind)
local ret
if kind == nil then
-- noop
ret = self.s[self.i]
elseif kind == 'CPU' then
ret = self:register(data.registers)
elseif kind == 'DEREF' then

View file

@ -2,10 +2,10 @@ local data = {}
data.registers = {
[0]=
'R0', 'AT', 'V0', 'V1', 'A0', 'A1', 'A2', 'A3',
'T0', 'T1', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7',
'S0', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7',
'T8', 'T9', 'K0', 'K1', 'GP', 'SP', 'FP', 'RA',
'ZERO', 'AT', 'V0', 'V1', 'A0', 'A1', 'A2', 'A3',
'T0', 'T1', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7',
'S0', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7',
'T8', 'T9', 'K0', 'K1', 'GP', 'SP', 'FP', 'RA',
}
data.sys_registers = {
@ -70,13 +70,11 @@ revtable(data.all_registers)
revtable(data.all_directives)
-- alternate register names
data.registers['ZERO'] = 0
data.all_registers['ZERO'] = 0
data.registers['S8'] = 30
data.all_registers['S8'] = 30
for i=0, 31 do
local r = 'REG'..tostring(i)
local r = 'R'..tostring(i)
data.registers[r] = i
data.all_registers[r] = i
end
@ -388,6 +386,10 @@ data.instructions = {
BNEZL = {21, 'sr', 's0o'}, -- BNEL RS, R0, offset
CL = { 0, 'd', '00d0C', 37}, -- OR RD, R0, R0
MOV = { 0, 'ds', 's0d0C', 37}, -- OR RD, RS, R0
DMOV = { 0, 'ds', 's0d0C', 45}, -- DADDU RD, RS, R0
-- bass does it this way
-- MOV = { 0, 'dt', '0td0C', 33}, -- ADDU RD, R0, RT
-- DMOV = { 0, 'dt', '0td0C', 45}, -- DADDU RD, R0, RT
NEG = { 0, 'dt', '0td0C', 34}, -- SUB RD, R0, RT
NEGU = { 0, 'dt', '0td0C', 35}, -- SUBU RD, R0, RT
NOP = { 0, '', '0'}, -- SLL R0, R0, 0
@ -397,6 +399,11 @@ data.instructions = {
SUBI = { 8, 'tsk', 'sti'}, -- ADDI RT, RS, -immediate
SUBIU = { 9, 'tsk', 'sti'}, -- ADDIU RT, RS, -immediate
L_D = {53, 'Tob', 'bTo'}, -- LDC1
L_S = {49, 'Tob', 'bTo'}, -- LWC1
S_D = {61, 'Tob', 'bTo'}, -- SDC1
S_S = {57, 'Tob', 'bTo'}, -- SWC1
-- ...that expand to multiple instructions
LI = __, -- only one instruction for values < 0x10000
LA = __,
@ -404,7 +411,9 @@ data.instructions = {
-- variable arguments
PUSH = __,
POP = __,
JPOP = __,
JPOP = __, -- deprecated alias of RET
CALL = __,
RET = __,
-- CL = __, overridden to take varargs
ABS = o1, -- SRA XOR SUBU
@ -434,12 +443,13 @@ data.instructions = {
BLT = o1, BLTU = o1,
BGT = o1, BGTU = o1,
BEQI = __, BEQIL = __,
BGEI = __, BGEIL = __,
BGTI = __, BGTIL = __,
BLEI = __, BLEIL = __,
BLTI = __, BLTIL = __,
BNEI = __, BNEIL = __,
-- note: signedness of BEQI/BNEI determines how the immediate is loaded
BEQI = __, BEQIU = __, BEQIL = __, BEQIUL = __,
BGEI = __, BGEIU = __, BGEIL = __, BGEIUL = __,
BGTI = __, BGTIU = __, BGTIL = __, BGTIUL = __,
BLEI = __, BLEIU = __, BLEIL = __, BLEIUL = __,
BLTI = __, BLTIU = __, BLTIL = __, BLTIUL = __,
BNEI = __, BNEIU = __, BNEIL = __, BNEIUL = __,
BGEL = o1, BGEUL = o1,
BGTL = o1, BGTUL = o1,

View file

@ -14,26 +14,7 @@ local path = string.gsub(..., "%.init$", "").."."
local util = require(path.."util")
local Parser = require(path.."Parser")
function lips.word_writer()
local buff = {}
local max = -1
return function(pos, b)
if pos then
buff[pos] = ("%02X"):format(b)
if pos > max then
max = pos
end
elseif max >= 0 then
for i=0, max, 4 do
local a = buff[i+0] or '00'
local b = buff[i+1] or '00'
local c = buff[i+2] or '00'
local d = buff[i+3] or '00'
print(a..b..c..d)
end
end
end
end
lips.writers = require(path.."writers")
function lips.assemble(fn_or_asm, writer, options)
-- assemble MIPS R4300i assembly code.
@ -41,7 +22,7 @@ function lips.assemble(fn_or_asm, writer, options)
-- returns error message on error, or nil on success.
fn_or_asm = tostring(fn_or_asm)
local default_writer = not writer
writer = writer or lips.word_writer()
writer = writer or lips.writers.make_word()
options = options or {}
local function main()

View file

@ -1,8 +1,51 @@
local insert = table.insert
local unpack = rawget(_G, 'unpack') or table.insert
local path = string.gsub(..., "[^.]+$", "")
local data = require(path.."data")
local function name_pop(name, character)
if name:sub(#name) == character then
return name:sub(1, #name - 1), character
else
return name, ''
end
end
local function li(self, buffer, dest, im)
if im.tt == 'LABELSYM' then
local im = self:token(im):set('portion', 'upperoff')
insert(buffer, {'LUI', dest, im})
im = self:token(im):set('portion', 'lower')
insert(buffer, {'ADDIU', dest, dest, im})
return
end
if im.portion then
-- FIXME: use appropriate instruction based on portion?
insert(buffer, {'ADDIU', dest, 'R0', im})
return
end
im.tok = im.tok % 0x100000000
if im.tok >= 0x10000 and im.tok <= 0xFFFF8000 then
local temp = self:token(im):set('portion', 'upper')
insert(buffer, {'LUI', dest, temp})
if im.tok % 0x10000 ~= 0 then
local temp = self:token(im):set('portion', 'lower')
insert(buffer, {'ORI', dest, dest, temp})
end
elseif im.tok >= 0x8000 and im.tok < 0x10000 then
local temp = self:token(im):set('portion', 'lower')
insert(buffer, {'ORI', dest, 'R0', temp})
else
local temp = self:token(im):set('portion', 'lower')
insert(buffer, {'ADDIU', dest, 'R0', temp})
end
return buffer
end
local overrides = {}
-- note: "self" is an instance of Preproc
@ -13,6 +56,12 @@ local function tob_override(self, name)
if self:peek('DEREF') then
offset = 0
base = self:pop('DEREF')
elseif self:peek('REG') then
local o = self:pop('CPU')
local b = self:pop('DEREF'):set('tt', 'REG')
self:push_new('ADDU', 'AT', o, b)
offset = 0
base = self:token('DEREF', 'AT')
else -- NUM or LABELSYM
local o = self:pop('CONST')
if self:peek('NUM') then
@ -60,26 +109,9 @@ function overrides:LI(name)
self:error('use LA for labels')
end
if im.portion then
-- FIXME: use appropriate instruction based on portion?
self:push_new('ADDIU', dest, 'R0', im)
return
end
im.tok = im.tok % 0x100000000
if im.tok >= 0x10000 and im.tok <= 0xFFFF8000 then
local temp = self:token(im):set('portion', 'upper')
self:push_new('LUI', dest, temp)
if im.tok % 0x10000 ~= 0 then
local temp = self:token(im):set('portion', 'lower')
self:push_new('ORI', dest, dest, temp)
end
elseif im.tok >= 0x8000 and im.tok < 0x10000 then
local temp = self:token(im):set('portion', 'lower')
self:push_new('ORI', dest, 'R0', temp)
else
local temp = self:token(im):set('portion', 'lower')
self:push_new('ADDIU', dest, 'R0', temp)
local buffer = li(self, {}, dest, im)
for i, v in ipairs(buffer) do
self:push_new(unpack(v))
end
end
@ -87,9 +119,9 @@ function overrides:LA(name)
local dest = self:pop('CPU')
local im = self:pop('CONST')
local im = self:token(im):set('portion', 'upperoff')
im = self:token(im):set('portion', 'upperoff')
self:push_new('LUI', dest, im)
local im = self:token(im):set('portion', 'lower')
im = self:token(im):set('portion', 'lower')
self:push_new('ADDIU', dest, dest, im)
end
@ -122,16 +154,94 @@ function overrides:PUSH(name)
self:push_new(w, r, offset, self:token('DEREF', 'SP'))
end
end
if name == 'JPOP' then
if name == 'JPOP' or name == 'RET' then
self:push_new('JR', 'RA')
end
if name == 'POP' or name == 'JPOP' then
if name == 'POP' or name == 'JPOP' or name == 'RET' then
local im = #stack * 4
self:push_new('ADDIU', 'SP', 'SP', im)
end
end
overrides.POP = overrides.PUSH
overrides.JPOP = overrides.PUSH
overrides.RET = overrides.PUSH
function overrides:CALL(name)
local func = nil
local stack = {}
for i, t in ipairs(self.s) do
if i == 1 then
func = self:pop()
elseif t.tt == 'REG' then
insert(stack, self:pop('CPU'))
else
insert(stack, self:pop())
end
end
if func == nil then
self:error(name..' requires at least one argument')
end
local buffer = {}
-- keep track of An registers used so that
-- we don't use an overwritten value by mistake.
-- in theory, we could set up something to swap
-- An registers around with the minimum use of AT,
-- but that's more complexity than we need for an edge case.
local used = {false, false, false, false}
local need = {false, false, false, false}
local deref_sp = self:token('DEREF', 'SP')
for i, t in ipairs(stack) do
if t.tt == 'REG' then
if i == 1 and t.tok == 'A0' then -- A0 is already A0, noop.
elseif i == 2 and t.tok == 'A1' then -- etc.
elseif i == 3 and t.tok == 'A2' then
elseif i == 4 and t.tok == 'A3' then
elseif i <= 4 then
if t.tok:sub(1, 1) == 'A' then
local n = tonumber(t.tok:sub(2, 2))
if used[n + 1] then
self:error("cannot use overwritten register A"..tostring(n), t.tok)
end
end
local dest = 'A'..tostring(i - 1)
insert(buffer, {'MOV', dest, t})
used[i] = true
else
local offset = (i - 1) * 4
insert(buffer, {'SW', t, offset, deref_sp})
end
else
if i <= 4 then
local dest = 'A'..tostring(i - 1)
li(self, buffer, dest, t)
used[i] = true
else
local dest = 'AT'
li(self, buffer, dest, t)
local offset = (i - 1) * 4
insert(buffer, {'SW', dest, offset, deref_sp})
end
end
end
-- if there was just one argument (the function/label),
-- then push a NOP to fill the delay slot.
-- (if the user wants to be efficient, they should be using JAL directly)
if #buffer == 0 then
insert(buffer, {'NOP'})
end
-- insert jal as the second to last instruction
-- to place the last instruction in the jal's delay slot.
insert(buffer, #buffer, {'JAL', func})
-- finally, write everything out
for i, v in ipairs(buffer) do
self:push_new(unpack(v))
end
end
function overrides:NAND(name)
local dest = self:pop('CPU')
@ -157,6 +267,8 @@ function overrides:NORI(name)
self:push_new('NOR', dest, dest, 'R0') -- NOT
end
-- TODO: ROLV/RORV-like versions of this
-- maybe give the same auto-register treatment to SLL/SRL/SRA too
function overrides:ROL(name)
local first = name == 'ROL' and 'SLL' or 'SRL'
local second = name == 'ROL' and 'SRL' or 'SLL'
@ -199,87 +311,107 @@ function overrides:JR(name)
end
local branch_basics = {
BEQI = 'BEQ',
BGEI = 'BEQ',
BGTI = 'BEQ',
BLEI = 'BNE',
BLTI = 'BNE',
BNEI = 'BNE',
BEQIL = 'BEQL',
BGEIL = 'BEQL',
BGTIL = 'BEQL',
BLEIL = 'BNEL',
BLTIL = 'BNEL',
BNEIL = 'BNEL',
BEQ = 'BEQ',
BGE = 'BEQ',
BGT = 'BNE',
BLE = 'BEQ',
BLT = 'BNE',
BNE = 'BNE',
}
function overrides:BEQI(name)
function overrides:BLT(name)
local likely, unsigned
name, likely = name_pop(name, 'L')
name, unsigned = name_pop(name, 'U')
local branch = branch_basics[name]
local reg = self:pop('CPU')
local im = self:pop('CONST')
local a = self:pop('CPU')
local b = self:pop('CPU')
local offset = self:pop('CONST')
if reg == 'AT' then
self:error('register cannot be AT in this pseudo-instruction')
end
self:push_new('ADDIU', 'AT', 'R0', im)
self:push_new(branch, reg, 'AT', offset)
self:push_new('SLT'..unsigned, 'AT', a, b)
self:push_new(branch..likely, 'AT', 'R0', offset)
end
function overrides:BLE(name)
local likely, unsigned
name, likely = name_pop(name, 'L')
name, unsigned = name_pop(name, 'U')
local branch = branch_basics[name]
local a = self:pop('CPU')
local b = self:pop('CPU')
local offset = self:pop('CONST')
self:push_new('SLT'..unsigned, 'AT', b, a)
self:push_new(branch..likely, 'AT', 'R0', offset)
end
overrides.BNEI = overrides.BEQI
overrides.BEQIL = overrides.BEQI
overrides.BNEIL = overrides.BEQI
function overrides:BLTI(name)
local branch = branch_basics[name]
local likely, unsigned
name, likely = name_pop(name, 'L')
name, unsigned = name_pop(name, 'U')
local branch = branch_basics[name:sub(1, #name - 1)]
local reg = self:pop('CPU')
local im = self:pop('CONST')
local offset = self:pop('CONST')
if reg == 'AT' then
self:error('register cannot be AT in this pseudo-instruction')
end
self:push_new('SLTI', 'AT', reg, im)
self:push_new(branch, 'R0', 'AT', offset)
self:push_new('SLTI'..unsigned, 'AT', reg, im)
self:push_new(branch..likely, 'AT', 'R0', offset)
end
overrides.BGEI = overrides.BLTI
overrides.BLTIL = overrides.BLTI
overrides.BGEIL = overrides.BLTI
function overrides:BLEI(name)
-- TODO: this can probably be optimized
if name:sub(#name) == 'L' then
self:error('unimplemented pseudo-instruction', name)
end
local branch = branch_basics[name]
local likely, unsigned
name, likely = name_pop(name, 'L')
name, unsigned = name_pop(name, 'U')
local branch = branch_basics[name:sub(1, #name - 1)]
local loadi = unsigned == 'U' and 'ORI' or 'ADDIU'
local reg = self:pop('CPU')
local im = self:pop('CONST')
local offset = self:pop('CONST')
if reg == 'AT' then
self:error('register cannot be AT in this pseudo-instruction')
end
self:push_new('ADDIU', 'AT', 'R0', im)
local beq_offset
if name == 'BLEI' or name =='BLEIL' then
beq_offset = offset
else
-- branch to delay slot of the next branch
beq_offset = self:token('NUM', 2):set('fixed')
end
self:push_new('BEQ', reg, 'AT', beq_offset)
self:push_new('SLT', 'AT', reg, 'AT')
self:push_new(branch, 'AT', 'R0', offset)
self:push_new(loadi, 'AT', 'R0', im)
self:push_new('SLT'..unsigned, 'AT', 'AT', reg)
self:push_new(branch..likely, 'AT', 'R0', offset)
end
function overrides:BEQI(name)
local likely, unsigned
name, likely = name_pop(name, 'L')
name, unsigned = name_pop(name, 'U')
local branch = name:sub(1, #name - 1)
local loadi = unsigned == 'U' and 'ORI' or 'ADDIU'
local reg = self:pop('CPU')
local im = self:pop('CONST')
local offset = self:pop('CONST')
self:push_new(loadi, 'AT', 'R0', im)
self:push_new(branch..likely, reg, 'AT', offset)
end
local BLT = overrides.BLT
local BLE = overrides.BLE
local BLTI = overrides.BLTI
local BLEI = overrides.BLEI
local BEQI = overrides.BEQI
for k, v in pairs{
BGE = BLT, BGEI = BLTI,
BGT = BLE, BGTI = BLEI,
BGEL = BLT, BGEIL = BLTI,
BGTL = BLE, BGTIL = BLEI,
BLEL = BLE, BLEIL = BLEI,
BLTL = BLT, BLTIL = BLTI,
BGEU = BLT, BGEIU = BLTI,
BGTU = BLE, BGTIU = BLEI,
BLEU = BLE, BLEIU = BLEI,
BLTU = BLT, BLTIU = BLTI,
BGEUL = BLT, BGEIUL = BLTI,
BGTUL = BLE, BGTIUL = BLEI,
BLEUL = BLE, BLEIUL = BLEI,
BLTUL = BLT, BLTIUL = BLTI,
BEQI = BEQI, BEQIU = BEQI,
BEQIL = BEQI, BEQIUL = BEQI,
BNEI = BEQI, BNEIU = BEQI,
BNEIL = BEQI, BNEIUL = BEQI,
} do
overrides[k] = v
end
overrides.BGTI = overrides.BLEI
overrides.BLEIL = overrides.BLEI
overrides.BGTIL = overrides.BLEI
return overrides

85
Lua/lib/lips/writers.lua Normal file
View file

@ -0,0 +1,85 @@
local writers = {}
function writers.make_word()
local buff = {}
local max = -1
return function(pos, b)
if pos then
buff[pos] = ("%02X"):format(b)
if pos > max then
max = pos
end
elseif max >= 0 then
for i=0, max, 4 do
local a = buff[i+0] or '00'
local b = buff[i+1] or '00'
local c = buff[i+2] or '00'
local d = buff[i+3] or '00'
print(a..b..c..d)
end
end
end
end
function writers.make_verbose()
local buff = {}
local max = -1
return function(pos, b)
if pos then
buff[pos] = b
if pos > max then
max = pos
end
elseif max >= 0 then
for i=0, max, 4 do
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
end
end
end
function writers.make_tester()
local buff = {}
local max = -1
return function(pos, b)
if pos then
buff[pos] = b
if pos > max then
max = pos
end
elseif max >= 0 then
local s = ''
local last_i = 0
for i=0, max, 4 do
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 '--'
if last_i ~= i - 4 then
s = s..('@%08X\n'):format(i)
end
s = s..a..b..c..d.."\n"
last_i = i
end
end
return s
end
end
end
return writers

View file

@ -76,6 +76,7 @@ copy_rom() {
cp *.lua build/
cp "$inject"/*.asm build/
cp *.asm build/
cp *.bin build/
cd build
# don't copy entire dir; avoid copying dotfiles (.git)

View file

@ -1,8 +1,6 @@
#!/usr/bin/env luajit
package.path = package.path..";./?/init.lua"
--require "test.strict"
local assemble = require "lips"
local assemble = require "lips.init"
local cereal = require "serialize"
local argparse = require "argparse"
@ -34,6 +32,8 @@ local function make_verbose_writer()
max = pos
end
elseif max >= 0 then
-- TODO: optimize. iterating 536,870,912 times to reach 0x800000000
-- isn't the fastest thing, as you can imagine.
for i=0, max, 4 do
local a = buff[i+0] or nil
local b = buff[i+1] or nil
@ -60,7 +60,7 @@ local function inject(args)
if args.output then
f = io.open(args.output, 'r+b')
if not f then
lament("file not found:", args.output)
lament("file not found: ", args.output)
return
end
end