acdream/tools/pdb-extract
Erik c1f1582576 fix(chat): Campaign CH user-gate round 2 -- portal notice rerouted to SpewBox, verbatim /help extraction, jump-in-air evidence
Item 2: retail's portal-space "In Portal Space..." notice is the SpewBox
(ECM_UI::SendNotice_DisplayStringInfo(0x1A,...) -> AddTextToScroll(str,
0x1A, 1, 0), hardcoded to the SpewBox per the decomp), not a dedicated
centered overlay. PortalWaitNoticeController and its lease are deleted;
PortalTunnelPresentation's per-rotation-segment cadence now writes
straight into RuntimeCommunicationState.AddText(ClientLocal) -- the
SpewBox's own dedupe-at-index-0 handles the repetition exactly as
retail's does. Register row AP-184 records the surface fix and the AP-178
scope extension.

Items 4+5: /help text was partially fabricated -- the user caught the
"/help death" meta-message. Generalized
tools/pdb-extract/sweep_weenie_strings.py to decode narrow
PStringBase<char> literals (the ClientCommunicationSystem::Help* family's
shape) alongside its original UTF-16LE support, then swept every
HelpXxxGroup function's exact byte extent against the PDB-paired
acclient.exe. 4 of 7 group topics (death/status/text/allegiances) are now
complete verbatim listings; the other 3 (channels/chatting/commands) keep
an honest UNVERIFIED note citing HelpStupidChannelHack @0x0056f290 (a
genuinely undecodable BN-mislabeled-fragment mechanism) instead of the
old fabricated sentinel. 7 of ~35 channel one-liners are also now
verbatim. ISSUES.md #364 tracks the remainder;
RetailCommandHelpTableTests.cs pins every result byte-exact.

Item 1: jump-in-air refusal still silent live is NOT reproduced and NOT
speculatively fixed. Exhaustive static re-audit found the mechanism
correct by construction (single-writer OnWalkable, exactly-once-per-frame
Update()/Capture(), no interfering edge-history resets). A live headless
repro (new jump-probe bot policy, real ACE connect) was blocked --
probeaccount2 has no character, and the graphical client already owned
testaccount this session so the task's own fallback rule forbade using
it. Two temporary probes are left behind ACDREAM_PROBE_JUMP=1 (blocked
entirely in Headless by the existing multi-session static-state guard --
graphical-only for the next round).

Item 3 confirmed fixed, no regression. Item 6 (resize: no diagonal
cursors, cannot grow Y from bottom-right) folded into CH6a's existing
scope.

Full Release suite: 12,267 passed / 4 skipped / 0 failed (up from
12,221/4/0).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 08:40:24 +02:00
..
check_exe_pdb.py feat(physics): #32 L.5 30Hz physics tick + retail debugger toolchain (#35) + Phase 3 retail-faithful kill_velocity 2026-04-30 22:41:12 +02:00
check_function_map.py docs(research): #9 sweep acclient_function_map.md against PDB symbols 2026-04-25 17:44:07 +02:00
dump_pdb_info.py feat(physics): #32 L.5 30Hz physics tick + retail debugger toolchain (#35) + Phase 3 retail-faithful kill_velocity 2026-04-30 22:41:12 +02:00
pdb_extract.py CLAUDE.md: adopt the condensed structure, updated to current truth (-503/+176) 2026-06-12 12:42:27 +02:00
README.md tools(pdb-extract): #8 PDB -> symbols.json + types.json sidecar 2026-04-25 17:31:52 +02:00
sweep_weenie_strings.py fix(chat): Campaign CH user-gate round 2 -- portal notice rerouted to SpewBox, verbatim /help extraction, jump-in-air evidence 2026-08-10 08:40:24 +02:00

pdb-extract — pure-Python MSF 7.00 PDB extractor

Reads refs/acclient.pdb (Sept 2013 EoR build, 28 MB) and writes two grep-friendly JSON sidecars to docs/research/named-retail/:

  • symbols.json — 18,366 named public function symbols from the PDB's S_PUB32 records. Each entry has address (image VA), name (MSVC-demangled Class::Method form), and mangled (raw C++ ABI symbol for callers that need exact mangling).
  • types.json — 5,371 unique named struct/class type records from the TPI stream (LF_CLASS / LF_STRUCTURE). Each entry has name, size (bytes), and kind (class or struct).

Usage

py tools\pdb-extract\pdb_extract.py refs\acclient.pdb

Runs in <1 second. No external dependencies — uses Python stdlib only.

Schema

symbols.json:

[
  {
    "address": "0x00594570",
    "name": "CEnchantmentRegistry::EnchantAttribute",
    "mangled": "?EnchantAttribute@CEnchantmentRegistry@@QBEHKAAK@Z"
  },
  ...
]

types.json:

[
  {
    "name": "CEnchantmentRegistry",
    "size": 32,
    "kind": "class"
  },
  ...
]

Workflow integration

The committed JSON sidecars are the named-retail counterpart to the acclient_2013_pseudo_c.txt text dump. Pseudo-C is for reading function bodies; symbols.json is for programmatic lookups. Use jq to query:

# Find a function by exact name
cat docs/research/named-retail/symbols.json | jq '.[] | select(.name == "CEnchantmentRegistry::EnchantAttribute")'

# Find all functions on a class
cat docs/research/named-retail/symbols.json | jq '.[] | select(.name | startswith("CACQualities::"))'

# Reverse lookup by address (e.g. mid-body fix-up)
cat docs/research/named-retail/symbols.json | jq '.[] | select(.address == "0x00594570")'

# Find a type by name
cat docs/research/named-retail/types.json | jq '.[] | select(.name == "Enchantment")'

Address mapping caveat

The PDB is from the Sept 2013 EoR build. Addresses generally match the binary used to produce our docs/research/decompiled/ Ghidra chunks within ~0xC00 bytes (different build runs of the same source revision). When using symbols.json to correct entries in acclient_function_map.md, match by name, not by raw address.

Implementation notes

The script is a self-contained MSF 7.00 reader. References used:

  • LLVM PDB documentation (https://llvm.org/docs/PDB/) — file format spec
  • Microsoft pdbparse (community) — implementation cross-check

Streams consumed:

  • 3 (DBI) — parses the header to extract the symbol-record stream index + the optional debug-header sub-stream's section-headers index.
  • 9 (section headers) — parses IMAGE_SECTION_HEADER entries to build a section-base table for VA computation.
  • 8 (sym record stream) — iterates records, picks S_PUB32 with the PUBSYM_FLAG_CODE bit set, computes VA = section_base + offset.
  • 2 (TPI) — iterates type records, picks LF_CLASS / LF_STRUCTURE that aren't forward-declared, parses size leaf + name.

The MSVC name demangler (_demangle) is best-effort: handles the common ?Method@Class@Outer@@<sig> patterns, constructors (??0), and destructors (??1). Returns the mangled string unchanged for operator overloads (??2, ??3), vtables (??_), and other forms where a partial demangle would be misleading. Both name (demangled) and mangled (raw) are emitted in symbols.json so consumers can choose.

When to regenerate

  • Whenever refs/acclient.pdb is updated (rare).
  • Whenever pdb_extract.py is changed (e.g. better demangler, more type info recovery).

The output JSONs are committed because they're stable + small (~3 MB combined) and grep-faster than re-parsing the PDB on every session.

Future work (out of scope here)

The current types.json only carries name + size. A more ambitious version would walk LF_FIELDLIST records to recover field names + offsets + types — giving us a JSON-encoded acclient.h. Not done yet because acclient.h already exists committed at docs/research/named-retail/acclient.h. Consider this if a future panel needs offsetof() at runtime.