Skip to content

Commit 50a1765

Browse files
committed
Add a script for manipulating binary patches at runtime, and some patches.
1 parent dcba0ac commit 50a1765

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

binpatch.lua

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
-- Apply or remove binary patches at runtime.
2+
3+
local utils = require('utils')
4+
5+
function load_patch(name)
6+
local filename = name
7+
local auto = false
8+
if not string.match(filename, '[./\\]') then
9+
auto = true
10+
filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif'
11+
end
12+
13+
local file, err = io.open(filename, 'r')
14+
if not file then
15+
if auto and string.match(err, ': No such file or directory') then
16+
return nil, 'no patch '..name..' for '..dfhack.getDFVersion()
17+
else
18+
return nil, err
19+
end
20+
end
21+
22+
local old_bytes = {}
23+
local new_bytes = {}
24+
25+
for line in file:lines() do
26+
if string.match(line, '^%x+:') then
27+
local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$')
28+
if not offset then
29+
file:close()
30+
return nil, 'Could not parse: '..line
31+
end
32+
33+
offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16)
34+
if oldv > 255 or newv > 255 then
35+
file:close()
36+
return nil, 'Invalid byte values: '..line
37+
end
38+
39+
old_bytes[offset] = oldv
40+
new_bytes[offset] = newv
41+
end
42+
end
43+
44+
return { name = name, old_bytes = old_bytes, new_bytes = new_bytes }
45+
end
46+
47+
function rebase_table(input)
48+
local output = {}
49+
local base = dfhack.internal.getImageBase()
50+
for k,v in pairs(input) do
51+
local offset = dfhack.internal.adjustOffset(k)
52+
if not offset then
53+
return nil, string.format('invalid offset: %x', k)
54+
end
55+
output[base + offset] = v
56+
end
57+
return output
58+
end
59+
60+
function rebase_patch(patch)
61+
local nold, err = rebase_table(patch.old_bytes)
62+
if not nold then return nil, err end
63+
local nnew, err = rebase_table(patch.new_bytes)
64+
if not nnew then return nil, err end
65+
return { name = patch.name, old_bytes = nold, new_bytes = nnew }
66+
end
67+
68+
function run_command(cmd,name)
69+
local patch, err = load_patch(name)
70+
if not patch then
71+
dfhack.printerr('Could not load: '..err)
72+
return
73+
end
74+
75+
local rpatch, err = rebase_patch(patch)
76+
if not rpatch then
77+
dfhack.printerr(name..': '..err)
78+
return
79+
end
80+
81+
if cmd == 'check' then
82+
local old_ok, err, addr = dfhack.internal.patchBytes({}, rpatch.old_bytes)
83+
if old_ok then
84+
print(name..': patch is not applied.')
85+
elseif dfhack.internal.patchBytes({}, rpatch.new_bytes) then
86+
print(name..': patch is applied.')
87+
else
88+
dfhack.printerr(string.format('%s: conflict at address %x', name, addr))
89+
end
90+
elseif cmd == 'apply' then
91+
local ok, err, addr = dfhack.internal.patchBytes(rpatch.new_bytes, rpatch.old_bytes)
92+
if ok then
93+
print(name..': applied the patch.')
94+
elseif dfhack.internal.patchBytes({}, rpatch.new_bytes) then
95+
print(name..': patch is already applied.')
96+
else
97+
dfhack.printerr(string.format('%s: conflict at address %x', name, addr))
98+
end
99+
elseif cmd == 'remove' then
100+
local ok, err, addr = dfhack.internal.patchBytes(rpatch.old_bytes, rpatch.new_bytes)
101+
if ok then
102+
print(name..': removed the patch.')
103+
elseif dfhack.internal.patchBytes({}, rpatch.old_bytes) then
104+
print(name..': patch is already removed.')
105+
else
106+
dfhack.printerr(string.format('%s: conflict at address %x', name, addr))
107+
end
108+
else
109+
qerror('Invalid command: '..cmd)
110+
end
111+
end
112+
113+
local cmd,name = ...
114+
if not cmd or not name then
115+
qerror('Usage: binpatch check/apply/remove <patchname>')
116+
end
117+
run_command(cmd, name)

0 commit comments

Comments
 (0)