#!/usr/bin/env python3 """ verify_gamedata.py — offline validator for vprof_timings.txt. Mimics the parts of SourceMod's GameConfig signature resolution and Source Scramble's MemPatch application that matter, so the gamedata can be checked against a target binary without running a server: * KeyValues/SMC parse of the gamedata (comments, nested sections, duplicate keys preserved). * `@mangled` signatures resolved from the ELF `.symtab` (the same thing MemoryUtils::ResolveSymbol does when a game reports hidden symbols, as TF2 does). Byte-pattern signatures with `\\x2A` wildcards are still supported for completeness. * Source Scramble's `offset`/`verify`/`patch` semantics. Usage: python3 verify_gamedata.py vprof_timings.txt engine_srv.so engine_srv.x64.so The two binaries are matched to the `linux` and `linux64` blocks respectively. """ import struct import sys # -------------------------------------------------------------------------- # Minimal SMC/KeyValues parser # -------------------------------------------------------------------------- def tokenize(text): i, n = 0, len(text) while i < n: c = text[i] if c in " \t\r\n": i += 1 continue if c == "/" and i + 1 < n and text[i + 1] == "/": while i < n and text[i] != "\n": i += 1 continue if c == "{": yield "{" i += 1 continue if c == "}": yield "}" i += 1 continue if c == '"': i += 1 buf = [] while i < n and text[i] != '"': if text[i] == "\\" and i + 1 < n: buf.append(text[i]) buf.append(text[i + 1]) i += 2 else: buf.append(text[i]) i += 1 i += 1 yield ("str", "".join(buf)) continue j = i while j < n and text[j] not in " \t\r\n{}": j += 1 yield ("str", text[i:j]) i = j def parse_block(it): entries = [] while True: try: t = next(it) except StopIteration: return entries if t == "}": return entries if t == "{": continue key = t[1] nxt = next(it) if nxt == "{": entries.append((key, parse_block(it))) else: entries.append((key, nxt[1])) def sub(entries, key): if entries is None: return None for k, v in entries: if k == key: return v return None # -------------------------------------------------------------------------- # ELF symbol table (`@symbol` resolution) # -------------------------------------------------------------------------- def elf_symbols(path): data = open(path, "rb").read() if data[:4] != b"\x7fELF": raise ValueError("%s is not an ELF file" % path) ei_class, ei_data = data[4], data[5] if ei_data != 1: raise ValueError("only little-endian ELF is supported") if ei_class == 1: e_shoff = struct.unpack_from("