dfhack/scripts/binpatch.lua

118 lines
3.7 KiB
Lua

-- Apply or remove binary patches at runtime.
local utils = require('utils')
function load_patch(name)
local filename = name
local auto = false
if not string.match(filename, '[./\\]') then
auto = true
filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif'
end
local file, err = io.open(filename, 'r')
if not file then
if auto and string.match(err, ': No such file or directory') then
return nil, 'no patch '..name..' for '..dfhack.getDFVersion()
else
return nil, err
end
end
local old_bytes = {}
local new_bytes = {}
for line in file:lines() do
if string.match(line, '^%x+:') then
local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$')
if not offset then
file:close()
return nil, 'Could not parse: '..line
end
offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16)
if oldv > 255 or newv > 255 then
file:close()
return nil, 'Invalid byte values: '..line
end
old_bytes[offset] = oldv
new_bytes[offset] = newv
end
end
return { name = name, old_bytes = old_bytes, new_bytes = new_bytes }
end
function rebase_table(input)
local output = {}
local base = dfhack.internal.getImageBase()
for k,v in pairs(input) do
local offset = dfhack.internal.adjustOffset(k)
if not offset then
return nil, string.format('invalid offset: %x', k)
end
output[base + offset] = v
end
return output
end
function rebase_patch(patch)
local nold, err = rebase_table(patch.old_bytes)
if not nold then return nil, err end
local nnew, err = rebase_table(patch.new_bytes)
if not nnew then return nil, err end
return { name = patch.name, old_bytes = nold, new_bytes = nnew }
end
function run_command(cmd,name)
local patch, err = load_patch(name)
if not patch then
dfhack.printerr('Could not load: '..err)
return
end
local rpatch, err = rebase_patch(patch)
if not rpatch then
dfhack.printerr(name..': '..err)
return
end
if cmd == 'check' then
local old_ok, err, addr = dfhack.internal.patchBytes({}, rpatch.old_bytes)
if old_ok then
print(name..': patch is not applied.')
elseif dfhack.internal.patchBytes({}, rpatch.new_bytes) then
print(name..': patch is applied.')
else
dfhack.printerr(string.format('%s: conflict at address %x', name, addr))
end
elseif cmd == 'apply' then
local ok, err, addr = dfhack.internal.patchBytes(rpatch.new_bytes, rpatch.old_bytes)
if ok then
print(name..': applied the patch.')
elseif dfhack.internal.patchBytes({}, rpatch.new_bytes) then
print(name..': patch is already applied.')
else
dfhack.printerr(string.format('%s: conflict at address %x', name, addr))
end
elseif cmd == 'remove' then
local ok, err, addr = dfhack.internal.patchBytes(rpatch.old_bytes, rpatch.new_bytes)
if ok then
print(name..': removed the patch.')
elseif dfhack.internal.patchBytes({}, rpatch.old_bytes) then
print(name..': patch is already removed.')
else
dfhack.printerr(string.format('%s: conflict at address %x', name, addr))
end
else
qerror('Invalid command: '..cmd)
end
end
local cmd,name = ...
if not cmd or not name then
qerror('Usage: binpatch check/apply/remove <patchname>')
end
run_command(cmd, name)