mirror of
https://github.com/notwa/mm
synced 2024-11-05 02:59:03 -08:00
flesh out patch.lua and use the new features
This commit is contained in:
parent
b72b6f3dc3
commit
e74da86e3e
11 changed files with 1373 additions and 84 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,3 +7,4 @@
|
||||||
__pycache__/*
|
__pycache__/*
|
||||||
cm mm save.lua
|
cm mm save.lua
|
||||||
cm oot save.lua
|
cm oot save.lua
|
||||||
|
z64yaz0
|
||||||
|
|
1
patch/.gitignore
vendored
1
patch/.gitignore
vendored
|
@ -2,3 +2,4 @@ patchme
|
||||||
lips
|
lips
|
||||||
entrances.asm
|
entrances.asm
|
||||||
crc32.asm
|
crc32.asm
|
||||||
|
labels.lua
|
||||||
|
|
1180
patch/argparse.lua
Normal file
1180
patch/argparse.lua
Normal file
File diff suppressed because it is too large
Load diff
|
@ -24,35 +24,14 @@
|
||||||
li a0, @start ; 1 (just make sure @start can be a LUI!)
|
li a0, @start ; 1 (just make sure @start can be a LUI!)
|
||||||
|
|
||||||
.org 0xC4808 ; 0x8016A2C8
|
.org 0xC4808 ; 0x8016A2C8
|
||||||
/*
|
j dma_hook ; 1
|
||||||
; if we've already loaded once, don't load again
|
|
||||||
lbu t0, @start ; 2
|
|
||||||
bnez t0, + ; 1
|
|
||||||
nop ; 1
|
|
||||||
push 4, a0, a1, a2, ra ; 5
|
|
||||||
li a0, @start ; 1
|
|
||||||
li a1, @vstart ; 2
|
|
||||||
li a2, @size ; 2
|
|
||||||
jal @DMARomToRam ; 1
|
|
||||||
nop ; 1
|
|
||||||
pop 4, a0, a1, a2, ra ; 5
|
|
||||||
+:
|
|
||||||
j @dma_hook ; 1
|
|
||||||
nop ; 1
|
|
||||||
; total overwriten instructions: 23
|
|
||||||
; the original function is in setup.asm,
|
|
||||||
; and is moved into our extra file.
|
|
||||||
; we have (0x944 / 4 - 23) = 570 words of space here, should we need it.
|
|
||||||
.word 0xDEADBEEF
|
|
||||||
*/
|
|
||||||
j @dma_hook ; 1
|
|
||||||
nop ; 1
|
nop ; 1
|
||||||
|
|
||||||
.org 0x9F9A4 ; JR of starting_exit's function
|
.org 0x9F9A4 ; JR of starting_exit's function
|
||||||
j @load_hook ; tail call
|
j load_hook ; tail call
|
||||||
|
|
||||||
.org 0x80710
|
.org 0x80710
|
||||||
j @tunic_color_hook
|
j tunic_color_hook
|
||||||
lhu t1, 0x1DB0(t1); original code
|
lhu t1, 0x1DB0(t1); original code
|
||||||
|
|
||||||
.org @starting_exit
|
.org @starting_exit
|
||||||
|
|
|
@ -22,11 +22,3 @@
|
||||||
;[link_object_ptr]: 0x244
|
;[link_object_ptr]: 0x244
|
||||||
|
|
||||||
[scene_record_size]: 0x14
|
[scene_record_size]: 0x14
|
||||||
|
|
||||||
; TODO: use arithmetic to do this (add that to lips)
|
|
||||||
; TODO: better yet, add proper label exports to lips
|
|
||||||
; so you can write the routines anywhere
|
|
||||||
[dma_hook]: 0x807DA000
|
|
||||||
[load_hook]: 0x807DA010
|
|
||||||
[setup_hook]: 0x807DA020
|
|
||||||
[tunic_color_hook]: 0x807DA030
|
|
||||||
|
|
|
@ -349,29 +349,3 @@ setup_hook:
|
||||||
jpop 4, a0, ra
|
jpop 4, a0, ra
|
||||||
|
|
||||||
.word 0xDEADBEEF
|
.word 0xDEADBEEF
|
||||||
|
|
||||||
.org @dma_hook
|
|
||||||
j dma_hook
|
|
||||||
nop
|
|
||||||
.word dma_hook
|
|
||||||
nop
|
|
||||||
|
|
||||||
.org @setup_hook
|
|
||||||
j setup_hook
|
|
||||||
nop
|
|
||||||
.word setup_hook
|
|
||||||
nop
|
|
||||||
|
|
||||||
.org @load_hook
|
|
||||||
j load_hook
|
|
||||||
nop
|
|
||||||
.word load_hook
|
|
||||||
nop
|
|
||||||
|
|
||||||
.org @tunic_color_hook
|
|
||||||
j tunic_color_hook
|
|
||||||
nop
|
|
||||||
.word tunic_color_hook
|
|
||||||
nop
|
|
||||||
|
|
||||||
.align 0x10
|
|
||||||
|
|
62
patch/extra.lua
Normal file
62
patch/extra.lua
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
local function strpad(num, count, pad)
|
||||||
|
num = tostring(num)
|
||||||
|
return (pad:rep(count)..num):sub(#num)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function add_zeros(num, count)
|
||||||
|
return strpad(num, count - 1, '0')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function mixed_sorter(a, b)
|
||||||
|
a = type(a) == 'number' and add_zeros(a, 16) or tostring(a)
|
||||||
|
b = type(b) == 'number' and add_zeros(b, 16) or tostring(b)
|
||||||
|
return a < b
|
||||||
|
end
|
||||||
|
|
||||||
|
-- loosely based on http://lua-users.org/wiki/SortedIteration
|
||||||
|
-- the original didn't make use of closures for who knows why
|
||||||
|
local function order_keys(t)
|
||||||
|
local oi = {}
|
||||||
|
for key in pairs(t) do
|
||||||
|
table.insert(oi, key)
|
||||||
|
end
|
||||||
|
table.sort(oi, mixed_sorter)
|
||||||
|
return oi
|
||||||
|
end
|
||||||
|
|
||||||
|
local function opairs(t, cache)
|
||||||
|
local oi = cache and cache[t] or order_keys(t)
|
||||||
|
if cache then
|
||||||
|
cache[t] = oi
|
||||||
|
end
|
||||||
|
local i = 0
|
||||||
|
return function()
|
||||||
|
i = i + 1
|
||||||
|
local key = oi[i]
|
||||||
|
if key then return key, t[key] end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function traverse(path)
|
||||||
|
if not path then return end
|
||||||
|
local parent = _G
|
||||||
|
local key
|
||||||
|
for w in path:gfind("[%w_]+") do
|
||||||
|
if key then
|
||||||
|
parent = rawget(parent, key)
|
||||||
|
if type(parent) ~= 'table' then return end
|
||||||
|
end
|
||||||
|
key = w
|
||||||
|
end
|
||||||
|
if not key then return end
|
||||||
|
return {parent=parent, key=key}
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
strpad = strpad,
|
||||||
|
add_zeros = add_zeros,
|
||||||
|
mixed_sorter = mixed_sorter,
|
||||||
|
order_keys = order_keys,
|
||||||
|
opairs = opairs,
|
||||||
|
traverse = traverse,
|
||||||
|
}
|
|
@ -37,6 +37,8 @@ comp() {
|
||||||
rm patchme/"$1"
|
rm patchme/"$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ ! -s ../z64yaz0 ] && cc -O3 ../z64yaz0.c -o ../z64yaz0
|
||||||
|
|
||||||
if [ $fast -eq 0 ] || [ ! -d patchme ]; then
|
if [ $fast -eq 0 ] || [ ! -d patchme ]; then
|
||||||
[ -d patchme ] && rm -r patchme
|
[ -d patchme ] && rm -r patchme
|
||||||
(cd ..; ./z64dump.py -c "$rom")
|
(cd ..; ./z64dump.py -c "$rom")
|
||||||
|
@ -50,11 +52,12 @@ mkdir -p lips
|
||||||
cp "$lips"/* lips
|
cp "$lips"/* lips
|
||||||
cp "$inject/"{crc32,entrances}.asm .
|
cp "$inject/"{crc32,entrances}.asm .
|
||||||
|
|
||||||
touch "extra"
|
dd if=/dev/zero of=extra bs=370688 count=1 2>/dev/null
|
||||||
|
|
||||||
luajit patch.lua code.asm patchme/"$code" 0
|
luajit patch.lua -e labels.lua -o 0x80780000 extra.asm extra
|
||||||
luajit patch.lua extra.asm extra 0x80780000
|
luajit patch.lua -i labels.lua code.asm patchme/"$code"
|
||||||
|
|
||||||
|
# ensure the file is the proper size (Lua seems to expand it?)
|
||||||
dd if=extra of=patchme/"$extra" bs=370688 count=1 2>/dev/null
|
dd if=extra of=patchme/"$extra" bs=370688 count=1 2>/dev/null
|
||||||
rm extra
|
rm extra
|
||||||
|
|
||||||
|
|
2
patch/oot-widescreen
Normal file → Executable file
2
patch/oot-widescreen
Normal file → Executable file
|
@ -10,6 +10,6 @@ lips=../Lua/lib/lips
|
||||||
mkdir -p lips
|
mkdir -p lips
|
||||||
cp "$lips"/* lips
|
cp "$lips"/* lips
|
||||||
|
|
||||||
luajit patch.lua widescreen-inline.asm oot-widescreen.z64 0 0x035D0000 0x80700000
|
luajit patch.lua -o 0 --extra-rom 0x035D0000 --extra-ram 0x80700000 widescreen-inline.asm oot-widescreen.z64
|
||||||
|
|
||||||
(cd ..; ./z64dump.py -f patch/oot-widescreen.z64)
|
(cd ..; ./z64dump.py -f patch/oot-widescreen.z64)
|
||||||
|
|
|
@ -1,22 +1,32 @@
|
||||||
package.path = package.path..";./?/init.lua"
|
package.path = package.path..";./?/init.lua"
|
||||||
|
|
||||||
local assemble = require "lips"
|
local assemble = require "lips"
|
||||||
|
local cereal = require "serialize"
|
||||||
|
local argparse = require "argparse"
|
||||||
|
|
||||||
local function inject(patch, target, offset, erom, eram)
|
local function inject(args)
|
||||||
offset = offset or 0
|
args.offset = args.offset or 0
|
||||||
|
|
||||||
local f = io.open(target, 'r+b')
|
local f = io.open(args.output, 'r+b')
|
||||||
if not f then
|
if not f then
|
||||||
print("file not found:", target)
|
print("file not found:", args.output)
|
||||||
return
|
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, line)
|
local function write(pos, line)
|
||||||
assert(#line == 2, "that ain't const")
|
assert(#line == 2, "that ain't const")
|
||||||
if erom and eram and pos >= eram then
|
if args.extra_rom and args.extra_ram and pos >= args.extra_ram then
|
||||||
pos = pos - eram + erom
|
pos = pos - args.extra_ram + args.extra_rom
|
||||||
elseif pos >= offset then
|
elseif pos >= args.offset then
|
||||||
pos = pos - offset
|
pos = pos - args.offset
|
||||||
end
|
end
|
||||||
if pos >= 1024*1024*1024 then
|
if pos >= 1024*1024*1024 then
|
||||||
print("you probably don't want to do this:")
|
print("you probably don't want to do this:")
|
||||||
|
@ -26,30 +36,41 @@ local function inject(patch, target, offset, erom, eram)
|
||||||
f:seek('set', pos)
|
f:seek('set', pos)
|
||||||
|
|
||||||
-- TODO: write hex dump format of written bytes
|
-- TODO: write hex dump format of written bytes
|
||||||
print(("%08X %s"):format(pos, line))
|
--print(("%08X %s"):format(pos, line))
|
||||||
|
|
||||||
f:write(string.char(tonumber(line, 16)))
|
f:write(string.char(tonumber(line, 16)))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- offset assembly labels so they work properly, and assemble!
|
assemble(args.input, write, {unsafe=true, offset=args.offset, labels=state})
|
||||||
assemble(patch, write, {unsafe=true, offset=offset})
|
|
||||||
|
if args.export then
|
||||||
|
cereal.serialize(args.export, state)
|
||||||
|
end
|
||||||
|
|
||||||
f:close()
|
f:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function parsenum(s)
|
local function parsenum(s)
|
||||||
if s:sub(2) == '0x' then
|
if s:sub(1, 2) == '0x' then
|
||||||
s = tonumber(s, 16)
|
return tonumber(s, 16)
|
||||||
|
elseif s:sub(1, 1) == '0' then
|
||||||
|
return tonumber(s, 8)
|
||||||
else
|
else
|
||||||
s = tonumber(s)
|
return tonumber(s)
|
||||||
end
|
end
|
||||||
return s
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local offset = arg[3]
|
local ap = argparse("patch", "patch a binary file with assembly")
|
||||||
local extra_rom = arg[4]
|
|
||||||
local extra_ram = arg[5]
|
ap:argument("input", "input assembly file")
|
||||||
if offset then offset = parsenum(offset) end
|
ap:argument("output", "output binary file")
|
||||||
if extra_rom then extra_rom = parsenum(extra_rom) end
|
ap:option("-o --offset", "offset to pass to lips", "0"):convert(parsenum)
|
||||||
if extra_ram then extra_ram = parsenum(extra_ram) end
|
ap:option("-i --import", "import state file(s) containing labels"):count("*")
|
||||||
inject(arg[1], arg[2], offset, extra_rom, extra_ram)
|
ap:option("-e --export", "export state file containing labels")
|
||||||
|
--ap:option("-s --state", "--import and --export to this file")
|
||||||
|
ap:option("--extra-rom", "dumb stuff"):convert(parsenum)
|
||||||
|
ap:option("--extra-ram", "dumb stuff"):convert(parsenum)
|
||||||
|
|
||||||
|
local inject_args = ap:parse()
|
||||||
|
|
||||||
|
inject(inject_args)
|
||||||
|
|
76
patch/serialize.lua
Normal file
76
patch/serialize.lua
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
-- it's simple, dumb, unsafe, incomplete, and it gets the damn job done
|
||||||
|
|
||||||
|
local type = type
|
||||||
|
local extra = require "extra"
|
||||||
|
local opairs = extra.opairs
|
||||||
|
local tostring = tostring
|
||||||
|
local open = io.open
|
||||||
|
local strfmt = string.format
|
||||||
|
local strrep = string.rep
|
||||||
|
|
||||||
|
local function kill_bom(s)
|
||||||
|
if #s >= 3 and s:byte(1)==0xEF and s:byte(2)==0xBB and s:byte(3)==0xBF then
|
||||||
|
return s:sub(4)
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
local function sanitize(v)
|
||||||
|
local force = type(v) == 'string' and v:sub(1, 1):match('%d')
|
||||||
|
force = force and true or false
|
||||||
|
return type(v) == 'string' and strfmt('%q', v) or tostring(v), force
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _serialize(value, writer, level)
|
||||||
|
level = level or 1
|
||||||
|
if type(value) == 'table' then
|
||||||
|
local indent = strrep('\t', level)
|
||||||
|
writer('{\n')
|
||||||
|
for key,value in opairs(value) do
|
||||||
|
local sane, force = sanitize(key)
|
||||||
|
local keyval = (sane == '"'..key..'"' and not force) and key or '['..sane..']'
|
||||||
|
writer(indent..keyval..' = ')
|
||||||
|
_serialize(value, writer, level + 1)
|
||||||
|
writer(',\n')
|
||||||
|
end
|
||||||
|
writer(strrep('\t', level - 1)..'}')
|
||||||
|
else
|
||||||
|
local sane, force = sanitize(value)
|
||||||
|
writer(sane)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _deserialize(script)
|
||||||
|
local f = loadstring(kill_bom(script))
|
||||||
|
if f ~= nil then
|
||||||
|
return f()
|
||||||
|
else
|
||||||
|
print('WARNING: no function to deserialize with')
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function serialize(path, value)
|
||||||
|
local file = open(path, 'w')
|
||||||
|
if not file then return end
|
||||||
|
file:write("return ")
|
||||||
|
_serialize(value, function(...)
|
||||||
|
file:write(...)
|
||||||
|
end)
|
||||||
|
file:write("\n")
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function deserialize(path)
|
||||||
|
local file = open(path, 'r')
|
||||||
|
if not file then return end
|
||||||
|
local script = file:read('*a')
|
||||||
|
local value = _deserialize(script)
|
||||||
|
file:close()
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
serialize = serialize,
|
||||||
|
deserialize = deserialize,
|
||||||
|
}
|
Loading…
Reference in a new issue