Pure-docs sweep. Cross-checked 63 hand-curated entries in
acclient_function_map.md against docs/research/named-retail/symbols.json
(the PDB-derived authoritative name table) using the new helper at
tools/pdb-extract/check_function_map.py.
Findings:
- Zero entries matched address-and-name exactly. Confirms the
PDB build is from a different revision than the binary that
produced our Ghidra chunks (~0x800-0xC10 byte delta varies by
function cluster). Match by NAME, not by raw address.
- 38 entries corrected by PDB name lookup. The "Was" column
preserves the old address for traceability against existing
code comments. Old entries pointed mid-body of the actual
function; new column heads point to function starts.
- 25 entries have no PDB match. Either inlined / non-public
(no S_PUB32 record) or our hand-derived names were synthesized
from call-site analysis and don't match the MSVC mangled form
in the PDB. Several had wrong class assignments (e.g. 0x5387C0
claimed as CTransition::find_collisions, actually
CPolygon::polygon_hits_sphere). Flagged for re-derivation in
acclient_2013_pseudo_c.txt.
Pattern: kept the table format with two address columns (PDB +
legacy) so existing code references using the old addresses can
still be looked up. Added a sweep-summary section at the bottom of
the file documenting the methodology + findings.
Helper script at tools/pdb-extract/check_function_map.py is reusable
for future re-runs (re-run after every PDB regeneration / function
map edit).
Closes #9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
149 lines
6 KiB
Python
149 lines
6 KiB
Python
"""Cross-check acclient_function_map.md addresses against symbols.json.
|
|
|
|
For each row, looks up the function name in the PDB symbols table and
|
|
flags mismatches (mid-body addresses, wrong addresses, missing names).
|
|
|
|
Run with:
|
|
py tools/pdb-extract/check_function_map.py
|
|
|
|
Used to close ISSUES.md #9 (address-correction sweep).
|
|
"""
|
|
import json
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parent.parent.parent
|
|
SYMBOLS = REPO / "docs" / "research" / "named-retail" / "symbols.json"
|
|
|
|
# (claimed_address, qualified_name) tuples from acclient_function_map.md.
|
|
# Format: lowercase hex no leading 0, then the demangled Class::Method.
|
|
ENTRIES = [
|
|
# CPhysicsObj
|
|
("515020", "CPhysicsObj::update_object"),
|
|
("5111d0", "CPhysicsObj::UpdatePhysicsInternal"),
|
|
("511420", "CPhysicsObj::calc_acceleration"),
|
|
("511ec0", "CPhysicsObj::set_velocity"),
|
|
("511fa0", "CPhysicsObj::set_local_velocity"),
|
|
("511de0", "CPhysicsObj::set_on_walkable"),
|
|
("511560", "CPhysicsObj::report_collision_start"),
|
|
("513ac0", "CPhysicsObj::report_collision_end"),
|
|
("513b60", "CPhysicsObj::handle_obj_collision"),
|
|
("515280", "CPhysicsObj::handle_collision"),
|
|
("513730", "CPhysicsObj::UpdatePositionInternal"),
|
|
("50f940", "CPhysicsObj::calc_friction"),
|
|
("510080", "CPhysicsObj::check_contact_velocity"),
|
|
# CMotionInterp
|
|
("5286b0", "CMotionInterp::get_jump_v_z"),
|
|
("528660", "CMotionInterp::jump_charge_is_allowed"),
|
|
("5285e0", "CMotionInterp::motion_allows_jump"),
|
|
("528ec0", "CMotionInterp::jump_is_allowed"),
|
|
("528cd0", "CMotionInterp::get_leave_ground_velocity"),
|
|
("529390", "CMotionInterp::jump"),
|
|
("529710", "CMotionInterp::LeaveGround"),
|
|
("5296d0", "CMotionInterp::HitGround"),
|
|
("528960", "CMotionInterp::get_state_velocity"),
|
|
("528a50", "CMotionInterp::StopCompletely"),
|
|
("5287f0", "CMotionInterp::adjust_motion"),
|
|
("5293f0", "CMotionInterp::apply_raw_movement"),
|
|
("529210", "CMotionInterp::apply_current_movement"),
|
|
("528dd0", "CMotionInterp::contact_allows_move"),
|
|
("528f70", "CMotionInterp::DoInterpretedMotion"),
|
|
("529080", "CMotionInterp::StopInterpretedMotion"),
|
|
("529140", "CMotionInterp::StopMotion"),
|
|
("529930", "CMotionInterp::DoMotion"),
|
|
("529a90", "CMotionInterp::PerformMovement"),
|
|
# CLandBlockStruct
|
|
("531d10", "CLandBlockStruct::IsSWtoNECut"),
|
|
("532a50", "CLandBlockStruct::ConstructPolygons"),
|
|
("532eb0", "CLandBlockStruct::ConstructUVs"),
|
|
("532d10", "CLandBlockStruct::unpack"),
|
|
("531f10", "CLandBlockStruct::get_packed_size"),
|
|
("532440", "CLandBlockStruct::AdjustPlanes"),
|
|
("532290", "CLandBlockStruct::CalcCellWater"),
|
|
# CLandBlock
|
|
("530690", "CLandBlock::Init"),
|
|
("5307e0", "CLandBlock::release_all"),
|
|
("531780", "CLandBlock::init_static_objs"),
|
|
("531000", "CLandBlock::release_visible_cells"),
|
|
("5301e0", "CLandBlock::grab_visible_cells"),
|
|
("530650", "CLandBlock::add_server_object"),
|
|
# LandDefs
|
|
("5aaa30", "CLandDefs::get_vars"),
|
|
("5aabb0", "CLandDefs::get_outside_lcoord"),
|
|
("5aac70", "CLandDefs::AdjustToOutside"),
|
|
("5aab50", "CLandDefs::get_block_dir"),
|
|
# Collision / Transition (some entries are CCylSphere / CSphere / CTransition / CPolygon)
|
|
("5384e0", "CSphere::SphereIntersectsRay"),
|
|
("539500", "CPolygon::sphere_intersects_poly"),
|
|
("539750", "CPolygon::sphere_intersects_solid"),
|
|
("539ba0", "CPolygon::find_time_of_collision"),
|
|
("539df0", "CPolygon::find_time_of_collision"),
|
|
("53a040", "CPolygon::find_walkable_collision"),
|
|
("539110", "CPolygon::calc_normal"),
|
|
("539060", "CPlane::ray_plane_intersect"),
|
|
("538eb0", "CSphere::slide_sphere"),
|
|
("538f50", "CSphere::land_on_sphere"),
|
|
("538180", "CTransition::collide_with_point"),
|
|
("5387c0", "CTransition::find_collisions"),
|
|
("53a230", "CPolygon::hits_walkable"),
|
|
# PhysicsEngine
|
|
("452a10", "PhysicsEngine::update"),
|
|
]
|
|
|
|
|
|
def main():
|
|
with open(SYMBOLS, "r", encoding="utf-8") as f:
|
|
symbols = json.load(f)
|
|
|
|
# Build name -> [addresses] (some names appear multiple times: overloads)
|
|
by_name = {}
|
|
for s in symbols:
|
|
by_name.setdefault(s["name"], []).append(s["address"])
|
|
|
|
# Build address -> name (lowercase hex without 0x prefix)
|
|
by_addr = {}
|
|
for s in symbols:
|
|
addr = s["address"][2:].lower().lstrip("0") or "0"
|
|
by_addr[addr] = s["name"]
|
|
|
|
confirmed = 0
|
|
addr_corrections = []
|
|
name_misses = []
|
|
for claimed_addr, claimed_name in ENTRIES:
|
|
# Look up by name
|
|
if claimed_name in by_name:
|
|
pdb_addrs = by_name[claimed_name]
|
|
normalized_claimed = claimed_addr.lower().lstrip("0") or "0"
|
|
match = any(
|
|
(a[2:].lower().lstrip("0") or "0") == normalized_claimed
|
|
for a in pdb_addrs
|
|
)
|
|
if match:
|
|
confirmed += 1
|
|
else:
|
|
addr_corrections.append((claimed_addr, claimed_name, pdb_addrs))
|
|
else:
|
|
# Name not found exactly. Try the claimed address as fallback.
|
|
normalized_claimed = claimed_addr.lower().lstrip("0") or "0"
|
|
actual_name = by_addr.get(normalized_claimed)
|
|
name_misses.append((claimed_addr, claimed_name, actual_name))
|
|
|
|
print(f"Confirmed (address + name match): {confirmed}/{len(ENTRIES)}")
|
|
print()
|
|
if addr_corrections:
|
|
print("ADDRESS CORRECTIONS (name correct, address wrong — likely mid-body):")
|
|
for claimed_addr, name, pdb_addrs in addr_corrections:
|
|
print(f" 0x{claimed_addr.upper()} {name}")
|
|
print(f" actual: {', '.join(pdb_addrs)}")
|
|
print()
|
|
if name_misses:
|
|
print("NAME MISMATCHES (name not found by exact match in PDB):")
|
|
for claimed_addr, name, actual_name in name_misses:
|
|
print(f" 0x{claimed_addr.upper()} {name}")
|
|
if actual_name:
|
|
print(f" PDB at 0x{claimed_addr.upper()}: {actual_name}")
|
|
else:
|
|
print(f" PDB has no symbol at 0x{claimed_addr.upper()}")
|
|
print()
|
|
|
|
|
|
main()
|