SE2Revival/tools/ds2exports.py
2026-10-23 20:47:44 -04:00

46 lines
1.8 KiB
Python

"""Exported functions of a DungeonSiege2.exe by (decorated) name, from its PE export table.
usage: python -I tools/ds2exports.py EXE [SUBSTRING] (lists matching exports with their addresses)
As a module: exports(exe) -> {decorated name: VA}; find(exe, substring) -> [(name, VA)].
The exe is not relocated (image base 0x400000), so these addresses are valid in the running game.
"""
import struct, sys
from functools import lru_cache
@lru_cache(maxsize=None)
def exports(exe):
d = open(exe, 'rb').read()
pe = struct.unpack_from('<I', d, 0x3c)[0]
base = struct.unpack_from('<I', d, pe + 0x34)[0]
nsec = struct.unpack_from('<H', d, pe + 6)[0]
opt = struct.unpack_from('<H', d, pe + 20)[0]
secs = [struct.unpack_from('<8sIIII', d, pe + 24 + opt + i * 40) for i in range(nsec)]
def off(rva):
for _, vsize, va, rsize, raw in secs:
if va <= rva < va + max(vsize, rsize):
return raw + rva - va
raise ValueError('rva %x outside sections' % rva)
erva = struct.unpack_from('<I', d, pe + 24 + 96)[0]
e = off(erva)
nfun, nnames, afun, anames, aord = struct.unpack_from('<5I', d, e + 20)
funcs = [struct.unpack_from('<I', d, off(afun) + 4 * i)[0] for i in range(nfun)]
out = {}
for i in range(nnames):
nrva = struct.unpack_from('<I', d, off(anames) + 4 * i)[0]
ordinal = struct.unpack_from('<H', d, off(aord) + 2 * i)[0]
o = off(nrva)
name = d[o:d.index(b'\0', o)].decode('latin1')
out[name] = base + funcs[ordinal]
return out
def find(exe, sub):
return sorted((n, a) for n, a in exports(exe).items() if sub in n)
if __name__ == '__main__':
for n, a in find(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else ''):
print('%08X %s' % (a, n))