Fix startup crash (hopefully) when windows relocates rac1_game.dll; crash log will now name functions
This commit is contained in:
+8
-1
@@ -239,7 +239,14 @@ else:
|
||||
r = subprocess.run(link, capture_output=True, text=True)
|
||||
if r.returncode:
|
||||
print(r.stderr[-6000:]); sys.exit("link failed (dll)")
|
||||
exe = os.path.join(out, "rac1_wrapper.exe")
|
||||
# the function names for crash reports (the .pdb is deleted after a launcher build)
|
||||
try:
|
||||
sys.path.insert(0, os.path.join(HERE, "tools"))
|
||||
import pdb_syms
|
||||
print("symbols:", pdb_syms.write_sym(dll, os.path.join(out, "rac1_game.pdb"), os.path.join(out, "rac1_game.sym")))
|
||||
except Exception as e:
|
||||
print("no symbol map (%s): crash reports will show offsets only" % e)
|
||||
exe =os.path.join(out, "rac1_wrapper.exe")
|
||||
L = os.path.join(HERE, "launcher")
|
||||
link = zig + ["cc", "-target", target, "-O2", "-nostdlib", "-o", exe, os.path.join(L, "launcher_win32.c"),
|
||||
os.path.join(L, "ps2ram_win32.c"), "-lkernel32", "-luser32",
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""crash_lookup.py: names the function at a crash offset in rac1_game.dll.
|
||||
|
||||
Builds from kit 2026.10.14 on name crashes in rac1_log.txt themselves (rac1_game.sym);
|
||||
this is for older builds, or offsets from elsewhere.
|
||||
|
||||
usage: python crash_lookup.py rac1_game.dll rac1_game.pdb 0x0c4f6807 [more offsets]
|
||||
|
||||
The offset is the "Fault offset" of a Windows Application Error event (or a crash
|
||||
address minus the DLL's base). The .pdb must come from the same build as the .dll:
|
||||
the launcher deletes it after building, so rebuild by hand to keep it (see below).
|
||||
A player's crash can be looked up with your own build as long as both were built
|
||||
from the same kit version: the build is reproducible.
|
||||
|
||||
To keep the .pdb, run the build yourself from the game's folder (games\\rac1):
|
||||
..\\..\\toolchain\\python\\python.exe -u build.py --platform windows --iso "C:\\path\\disc.iso" --repo decomp --levels 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18
|
||||
with ..\\..\\toolchain\\zig on PATH. It reads only the PDB's public symbols."""
|
||||
import bisect, struct, sys
|
||||
|
||||
|
||||
def sections(dll):
|
||||
d = open(dll, "rb").read(4096)
|
||||
pe = struct.unpack_from("<I", d, 0x3C)[0]
|
||||
n, opt = struct.unpack_from("<H", d, pe + 6)[0], struct.unpack_from("<H", d, pe + 20)[0]
|
||||
o = pe + 24 + opt
|
||||
return [struct.unpack_from("<8sII", d, o + i * 40) for i in range(n)] # name, vsize, rva
|
||||
|
||||
|
||||
def pdb_streams(path):
|
||||
f = open(path, "rb").read()
|
||||
if not f.startswith(b"Microsoft C/C++ MSF 7.00\r\n\x1aDS\0\0\0"): sys.exit("not a PDB: " + path)
|
||||
bs, _, nblocks, dirbytes, _, dirmap = struct.unpack_from("<6I", f, 32)
|
||||
nb = lambda n: (n + bs - 1) // bs
|
||||
blk = lambda i: f[i * bs:(i + 1) * bs]
|
||||
mapblocks = struct.unpack_from("<%dI" % nb(dirbytes), blk(dirmap))
|
||||
dirdata = b"".join(blk(b) for b in mapblocks)[:dirbytes]
|
||||
ns = struct.unpack_from("<I", dirdata)[0]
|
||||
sizes = struct.unpack_from("<%dI" % ns, dirdata, 4)
|
||||
pos, out = 4 + 4 * ns, []
|
||||
for s in sizes:
|
||||
s = 0 if s == 0xFFFFFFFF else s
|
||||
bl = struct.unpack_from("<%dI" % nb(s), dirdata, pos); pos += 4 * nb(s)
|
||||
out.append(b"".join(blk(b) for b in bl)[:s])
|
||||
return out
|
||||
|
||||
|
||||
def publics(pdb):
|
||||
st = pdb_streams(pdb)
|
||||
dbi = st[3]
|
||||
symrec = struct.unpack_from("<H", dbi, 20)[0] # DBI header: SymRecordStream
|
||||
rec, p, syms = st[symrec], 0, []
|
||||
while p + 4 <= len(rec):
|
||||
ln, kind = struct.unpack_from("<HH", rec, p)
|
||||
if kind == 0x110E: # S_PUB32: flags, offset, segment, name
|
||||
_, off, seg = struct.unpack_from("<IIH", rec, p + 4)
|
||||
name = rec[p + 14:rec.index(b"\0", p + 14)].decode("utf-8", "replace")
|
||||
syms.append((seg, off, name))
|
||||
p += 2 + ln
|
||||
return syms
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4: sys.exit(__doc__)
|
||||
secs = sections(sys.argv[1])
|
||||
table = sorted((secs[seg - 1][2] + off, name) for seg, off, name in publics(sys.argv[2]) if 0 < seg <= len(secs))
|
||||
addrs = [a for a, _ in table]
|
||||
for arg in sys.argv[3:]:
|
||||
rva = int(arg, 16)
|
||||
i = bisect.bisect_right(addrs, rva) - 1
|
||||
sec = next((s[0].rstrip(b"\0").decode() for s in secs if s[2] <= rva < s[2] + s[1]), "?")
|
||||
if i < 0: print("%s: before every symbol (section %s)" % (arg, sec)); continue
|
||||
print("%s: %s+0x%X (section %s)" % (arg, table[i][1], rva - table[i][0], sec))
|
||||
|
||||
|
||||
main()
|
||||
@@ -61,6 +61,8 @@ int uya_main(int argc, char **argv)
|
||||
setvbuf(stdout, 0, _IONBF, 0);
|
||||
atexit(plat_at_exit);
|
||||
uya_log_open();
|
||||
uya_log("Project Solana: starting");
|
||||
plat_install_trap_handler(); /* first: a crash from here on is logged */
|
||||
{ /* renderer=gl|soft in the cfg (UYA_RENDERER overrides it) */
|
||||
extern const char *uya_renderer_cfg;
|
||||
const char *r = cfg_value("renderer");
|
||||
@@ -75,7 +77,6 @@ int uya_main(int argc, char **argv)
|
||||
uya_log("the PS2 address space could not be mapped");
|
||||
return 1;
|
||||
}
|
||||
plat_install_trap_handler();
|
||||
{
|
||||
const char *iso = find_iso(argc, argv);
|
||||
if (!iso || uya_disc_open(iso) != 0) { uya_log("the disc image could not be opened"); return 1; }
|
||||
|
||||
@@ -13,19 +13,51 @@
|
||||
|
||||
extern int plat_gl_active; /* the GS presents with OpenGL (below) */
|
||||
|
||||
/* Something other than the game holds a PS2 address: say what, and stop (taking the memory
|
||||
* over would break whatever owns it: once it was this DLL's own code, relocated there). */
|
||||
static int map_conflict(uint32_t addr, uint32_t size, uint32_t at, const MEMORY_BASIC_INFORMATION *mi)
|
||||
{
|
||||
char who[MAX_PATH] = "memory of another program loaded into the game (an overlay or similar)", msg[1024];
|
||||
if (mi && mi->Type == MEM_IMAGE && GetModuleFileNameA((HMODULE)mi->AllocationBase, who, sizeof who)) {}
|
||||
uya_log("cannot map PS2 memory 0x%08X+0x%X: 0x%08X is in use by %s", addr, size, at, who);
|
||||
snprintf(msg, sizeof msg, "The game needs the memory at 0x%08X for the PS2, but it is in use by:\n\n%s\n\n"
|
||||
"If this is a program injected into games (an overlay, recorder or RGB tool), close it or exclude "
|
||||
"rac1_wrapper.exe from it, then try again.", at, who);
|
||||
MessageBoxA(0, msg, "Project Solana", MB_ICONERROR);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int plat_map_fixed(uint32_t addr, uint32_t size, int exec)
|
||||
{
|
||||
DWORD prot = exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
||||
void *p = VirtualAlloc((void *)(uintptr_t)addr, size, MEM_RESERVE | MEM_COMMIT, prot);
|
||||
if (p == (void *)(uintptr_t)addr) return 0;
|
||||
/* already there? (PS2 RAM comes from the launcher's image section) */
|
||||
MEMORY_BASIC_INFORMATION mi;
|
||||
uint32_t a = addr;
|
||||
while (a < addr + size) {
|
||||
if (!VirtualQuery((void *)(uintptr_t)a, &mi, sizeof mi) || mi.State != MEM_COMMIT) {
|
||||
uya_log("cannot map 0x%08X+0x%X (0x%08X is in use)", addr, size, a);
|
||||
static int said;
|
||||
if (!said++) { /* (normally 0x40000000; elsewhere when Windows relocated it) */
|
||||
MEMORY_BASIC_INFORMATION me;
|
||||
if (VirtualQuery((void *)plat_map_fixed, &me, sizeof me)) uya_log("rac1_game.dll loaded at %p", me.AllocationBase);
|
||||
if ((uintptr_t)me.AllocationBase < 0x40000000u) { /* (rac1_wrapper.exe keeps it above: see launcher_win32.c) */
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof msg, "rac1_game.dll was loaded at 0x%08X, but the game needs it at 0x40000000 or above "
|
||||
"(lower addresses belong to the PS2). Please report this, with rac1_log.txt.", (unsigned)(uintptr_t)me.AllocationBase);
|
||||
uya_log("%s", msg);
|
||||
MessageBoxA(0, msg, "Project Solana", MB_ICONERROR);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
MEMORY_BASIC_INFORMATION mi;
|
||||
void *p = 0;
|
||||
if (VirtualQuery((void *)(uintptr_t)addr, &mi, sizeof mi)) {
|
||||
if (mi.State == MEM_RESERVE && mi.AllocationBase == (void *)(uintptr_t)addr && mi.RegionSize >= size)
|
||||
p = VirtualAlloc((void *)(uintptr_t)addr, size, MEM_COMMIT, prot); /* rac1_wrapper.exe reserved it before loading this DLL */
|
||||
else if (mi.State == MEM_FREE)
|
||||
p = VirtualAlloc((void *)(uintptr_t)addr, size, MEM_RESERVE | MEM_COMMIT, prot);
|
||||
}
|
||||
if (p == (void *)(uintptr_t)addr) return 0;
|
||||
/* already there: only PS2 RAM, which is the wrapper's own image section (.ps2ram) */
|
||||
HMODULE self = GetModuleHandleA(0);
|
||||
uint32_t a = addr;
|
||||
while (a < addr + size) {
|
||||
if (!VirtualQuery((void *)(uintptr_t)a, &mi, sizeof mi)) return map_conflict(addr, size, a, 0);
|
||||
if (mi.State != MEM_COMMIT || mi.AllocationBase != (void *)self) return map_conflict(addr, size, a, &mi);
|
||||
DWORD old;
|
||||
VirtualProtect(mi.BaseAddress, mi.RegionSize, prot, &old);
|
||||
a = (uint32_t)(uintptr_t)mi.BaseAddress + (uint32_t)mi.RegionSize;
|
||||
@@ -60,6 +92,38 @@ void plat_at_exit(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* the function at rva in rac1_game.sym (written by build.py: "RVA name" per line, sorted),
|
||||
* read with Win32 calls only: the C library may be what crashed */
|
||||
static int sym_lookup(uint32_t rva, char *name, int cap, uint32_t *start)
|
||||
{
|
||||
char p[MAX_PATH + 32];
|
||||
DWORD n = GetModuleFileNameA(0, p, MAX_PATH);
|
||||
while (n && p[n - 1] != '\\') n--;
|
||||
lstrcpyA(p + n, "rac1_game.sym");
|
||||
HANDLE f = CreateFileA(p, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
|
||||
if (f == INVALID_HANDLE_VALUE) return 0;
|
||||
DWORD sz = GetFileSize(f, 0), got = 0;
|
||||
char *b = sz && sz < (64u << 20) ? VirtualAlloc(0, sz + 1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE) : 0;
|
||||
int ok = b && ReadFile(f, b, sz, &got, 0) && got == sz;
|
||||
CloseHandle(f);
|
||||
if (!ok) { if (b) VirtualFree(b, 0, MEM_RELEASE); return 0; }
|
||||
b[sz] = '\n';
|
||||
const char *best = 0; uint32_t ba = 0;
|
||||
for (char *q = b; q < b + sz;) {
|
||||
uint32_t a = 0; char *s = q;
|
||||
for (; s < q + 8; s++) a = a * 16 + (uint32_t)(*s <= '9' ? *s - '0' : (*s | 32) - 'a' + 10);
|
||||
if (a > rva) break;
|
||||
best = s + 1; ba = a;
|
||||
while (*q != '\n') q++;
|
||||
q++;
|
||||
}
|
||||
int k = 0;
|
||||
if (best) for (; best[k] != '\n' && best[k] != '\r' && k < cap - 1; k++) name[k] = best[k];
|
||||
name[k] = 0; *start = ba;
|
||||
VirtualFree(b, 0, MEM_RELEASE);
|
||||
return k > 0;
|
||||
}
|
||||
|
||||
static LONG CALLBACK on_exception(EXCEPTION_POINTERS *ep)
|
||||
{
|
||||
EXCEPTION_RECORD *er = ep->ExceptionRecord;
|
||||
@@ -72,8 +136,32 @@ static LONG CALLBACK on_exception(EXCEPTION_POINTERS *ep)
|
||||
c->Eip = ret; c->Esp += 4;
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
uya_log("crash: exception 0x%08lX at 0x%08X, fault address 0x%08lX", er->ExceptionCode, at,
|
||||
er->NumberParameters > 1 ? (unsigned long)er->ExceptionInformation[1] : 0ul);
|
||||
static volatile LONG in_crash;
|
||||
if (InterlockedExchange(&in_crash, 1)) ExitProcess(1); /* (a crash while reporting one) */
|
||||
{ /* where: module+offset, the form crash_lookup.py and Windows' event log use */
|
||||
char mod[MAX_PATH] = "?"; MEMORY_BASIC_INFORMATION mi;
|
||||
uintptr_t base = 0;
|
||||
if (VirtualQuery(er->ExceptionAddress, &mi, sizeof mi) && mi.Type == MEM_IMAGE) {
|
||||
base = (uintptr_t)mi.AllocationBase;
|
||||
if (GetModuleFileNameA((HMODULE)base, mod, sizeof mod)) { char *s = strrchr(mod, '\\'); if (s) memmove(mod, s + 1, strlen(s)); }
|
||||
}
|
||||
uint32_t off = (uint32_t)((uintptr_t)er->ExceptionAddress - base);
|
||||
uya_log("crash: exception 0x%08lX at 0x%08X (%s+0x%X), fault address 0x%08lX", er->ExceptionCode, at, base ? mod : "no module",
|
||||
off, er->NumberParameters > 1 ? (unsigned long)er->ExceptionInformation[1] : 0ul);
|
||||
char fn[256]; uint32_t fs;
|
||||
MEMORY_BASIC_INFORMATION me; /* (this DLL: is the crash in it?) */
|
||||
if (base && VirtualQuery((void *)sym_lookup, &me, sizeof me) && me.AllocationBase == (void *)base && sym_lookup(off, fn, sizeof fn, &fs))
|
||||
uya_log("crash: in %s+0x%X", fn, off - fs);
|
||||
/* the return addresses on the stack, named: the call chain */
|
||||
const uint32_t *sp = (const uint32_t *)(uintptr_t)c->Esp;
|
||||
for (int i = 0, shown = 0; i < 256 && shown < 12 && !IsBadReadPtr(sp + i, 4); i++) {
|
||||
uintptr_t v = sp[i];
|
||||
if (base && v > base && v < base + 0x10000000 && VirtualQuery((void *)v, &me, sizeof me) && me.AllocationBase == (void *)base &&
|
||||
(me.Protect & 0xF0) && sym_lookup((uint32_t)(v - base), fn, sizeof fn, &fs)) {
|
||||
uya_log("crash: called from %s+0x%X", fn, (uint32_t)(v - base) - fs); shown++;
|
||||
}
|
||||
}
|
||||
}
|
||||
uya_log("crash: thread %lu, eax %08lX ecx %08lX edx %08lX ebx %08lX esi %08lX edi %08lX esp %08lX ebp %08lX",
|
||||
GetCurrentThreadId(), c->Eax, c->Ecx, c->Edx, c->Ebx, c->Esi, c->Edi, c->Esp, c->Ebp);
|
||||
{ /* the stack: return addresses near its top, then the frame chain */
|
||||
|
||||
@@ -25,6 +25,8 @@ __declspec(dllimport) int __stdcall DeleteFileA(const char *);
|
||||
__declspec(dllimport) int __stdcall MoveFileExA(const char *, const char *, unsigned);
|
||||
__declspec(dllimport) void *__stdcall VirtualAlloc(void *, unsigned long, unsigned, unsigned);
|
||||
__declspec(dllimport) int __stdcall VirtualFree(void *, unsigned long, unsigned);
|
||||
typedef struct { void *base, *alloc_base; unsigned alloc_prot; unsigned long size; unsigned state, prot, type; } mbi_t;
|
||||
__declspec(dllimport) unsigned long __stdcall VirtualQuery(const void *, mbi_t *, unsigned long);
|
||||
__declspec(dllimport) unsigned __stdcall GetModuleFileNameA(HMODULE, char *, unsigned);
|
||||
|
||||
#define INVALID ((HANDLE)(long)-1)
|
||||
@@ -48,7 +50,7 @@ static void path(char *out, const char *dir, const char *name, int i)
|
||||
static void fail(const char *m) { MessageBoxA(0, m, "RAC1", 0x10); ExitProcess(1); }
|
||||
|
||||
/* rac1_game.dll from its parts, when a manifest says they are there */
|
||||
static void join_parts(void)
|
||||
static __attribute__((noinline)) void join_parts(void)
|
||||
{
|
||||
char dir[600], man[700], tmp[700], dll[700], nm[700];
|
||||
unsigned dl = GetModuleFileNameA(0, dir, 520);
|
||||
@@ -93,10 +95,36 @@ static void join_parts(void)
|
||||
|
||||
void __stdcall launcher_start(void)
|
||||
{
|
||||
/* The PS2's fixed addresses beyond RAM (EE registers, VU memory, GS registers, scratchpad),
|
||||
* reserved before rac1_game.dll loads: if Windows relocates the DLL (Mandatory ASLR, or its
|
||||
* address taken), it must not land on them. The runtime commits them (plat_map_fixed). */
|
||||
VirtualAlloc((void *)0x10000000, 0x10000, 0x2000 /* MEM_RESERVE */, 1 /* PAGE_NOACCESS */);
|
||||
VirtualAlloc((void *)0x11000000, 0x10000, 0x2000, 1);
|
||||
VirtualAlloc((void *)0x12000000, 0x2000, 0x2000, 1);
|
||||
VirtualAlloc((void *)0x70000000, 0x4000, 0x2000, 1);
|
||||
join_parts();
|
||||
/* The runtime takes addresses below 0x40000000 for PS2 ones (RAM, its uncached mirrors at
|
||||
* 0x20000000-0x3FFFFFFF, the registers), so the DLL must load at or above 0x40000000 (its own
|
||||
* base, unless something holds it or Windows forces relocation). While it loads, every free
|
||||
* range below that is reserved, then released: Windows can only place it higher. */
|
||||
void *held[64]; unsigned nheld = 0;
|
||||
for (unsigned a = 0x00010000; a < 0x40000000 && nheld < 64;) {
|
||||
mbi_t m;
|
||||
if (!VirtualQuery((void *)a, &m, sizeof m)) break;
|
||||
unsigned end = (unsigned)m.base + (unsigned)m.size;
|
||||
if (end <= a) break;
|
||||
if (m.state == 0x10000 /* MEM_FREE */) {
|
||||
unsigned lo = (a + 0xFFFF) & ~0xFFFFu, hi = end > 0x40000000 ? 0x40000000 : end;
|
||||
void *r = lo < hi ? VirtualAlloc((void *)lo, hi - lo, 0x2000, 1) : 0;
|
||||
if (r) held[nheld++] = r;
|
||||
}
|
||||
a = end;
|
||||
}
|
||||
HMODULE h = LoadLibraryA("rac1_game.dll");
|
||||
while (nheld) VirtualFree(held[--nheld], 0, 0x8000 /* MEM_RELEASE */);
|
||||
if (!h) {
|
||||
MessageBoxA(0, "rac1_game.dll was not found next to rac1_wrapper.exe.", "UYA Wrapper", 0x10);
|
||||
MessageBoxA(0, "rac1_game.dll could not be loaded. It should be next to rac1_wrapper.exe; if it is, "
|
||||
"Windows found no room for it above address 0x40000000.", "Project Solana", 0x10);
|
||||
ExitProcess(1);
|
||||
}
|
||||
uya_main_fn f = (uya_main_fn)GetProcAddress(h, "uya_main_win32");
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
"""pdb_syms.py: the public symbols of a Windows DLL, from its .pdb (no other tools needed).
|
||||
|
||||
build.py writes rac1_game.sym with it after linking: one line per function, "RVA name"
|
||||
in hex, sorted. The launcher deletes the .pdb (hundreds of MB) but keeps the .sym, and
|
||||
the crash handler names the function a crash is in from it (in rac1_log.txt)."""
|
||||
import struct
|
||||
|
||||
|
||||
def sections(dll):
|
||||
"""(name, virtual size, RVA) of each section of a PE file"""
|
||||
with open(dll, "rb") as fh: d = fh.read(4096)
|
||||
pe = struct.unpack_from("<I", d, 0x3C)[0]
|
||||
n, opt = struct.unpack_from("<H", d, pe + 6)[0], struct.unpack_from("<H", d, pe + 20)[0]
|
||||
o = pe + 24 + opt
|
||||
return [struct.unpack_from("<8sII", d, o + i * 40) for i in range(n)]
|
||||
|
||||
|
||||
def _streams(path):
|
||||
with open(path, "rb") as fh: f = fh.read()
|
||||
if not f.startswith(b"Microsoft C/C++ MSF 7.00\r\n\x1aDS\0\0\0"): raise ValueError("not a PDB: " + path)
|
||||
bs, _, _, dirbytes, _, dirmap = struct.unpack_from("<6I", f, 32)
|
||||
nb = lambda n: (n + bs - 1) // bs
|
||||
blk = lambda i: f[i * bs:(i + 1) * bs]
|
||||
mapblocks = struct.unpack_from("<%dI" % nb(dirbytes), blk(dirmap))
|
||||
dirdata = b"".join(blk(b) for b in mapblocks)[:dirbytes]
|
||||
ns = struct.unpack_from("<I", dirdata)[0]
|
||||
sizes = struct.unpack_from("<%dI" % ns, dirdata, 4)
|
||||
pos, out = 4 + 4 * ns, []
|
||||
for s in sizes:
|
||||
s = 0 if s == 0xFFFFFFFF else s
|
||||
bl = struct.unpack_from("<%dI" % nb(s), dirdata, pos); pos += 4 * nb(s)
|
||||
out.append(b"".join(blk(b) for b in bl)[:s])
|
||||
return out
|
||||
|
||||
|
||||
def symbols(dll, pdb):
|
||||
"""sorted (RVA, name) of the public symbols of dll"""
|
||||
st = _streams(pdb)
|
||||
rec = st[struct.unpack_from("<H", st[3], 20)[0]] # DBI header: the symbol record stream
|
||||
secs = sections(dll)
|
||||
p, out = 0, []
|
||||
while p + 4 <= len(rec):
|
||||
ln, kind = struct.unpack_from("<HH", rec, p)
|
||||
if kind == 0x110E: # S_PUB32: flags, offset, segment, name
|
||||
_, off, seg = struct.unpack_from("<IIH", rec, p + 4)
|
||||
if 0 < seg <= len(secs):
|
||||
name = rec[p + 14:rec.index(b"\0", p + 14)].decode("utf-8", "replace")
|
||||
out.append((secs[seg - 1][2] + off, name[1:] if name.startswith("_") else name))
|
||||
p += 2 + ln
|
||||
return sorted(out)
|
||||
|
||||
|
||||
def write_sym(dll, pdb, path):
|
||||
"""rac1_game.sym: the code symbols, "RVA name" per line"""
|
||||
text = [s for s in sections(dll) if s[0].rstrip(b"\0") == b".text"]
|
||||
lo, hi = (text[0][2], text[0][2] + text[0][1]) if text else (0, 1 << 32)
|
||||
syms = [(a, n) for a, n in symbols(dll, pdb) if lo <= a < hi]
|
||||
with open(path, "w", newline="\n") as f:
|
||||
for a, n in syms: f.write("%08x %s\n" % (a, n))
|
||||
return len(syms)
|
||||
Reference in New Issue
Block a user