weapon damage: Ghidra decompile phase — confirms server-authoritative, no static seed
Bulk-decompiled ~17.2k combat/system functions (Ghidra 11.1.2 headless) and confirmed via a second toolchain what the capstone analysis found: - GetDamage is a pure component read (returns *(comp+0x10), 0 if absent), no constants. - PlainDamgeDealerComponent has CopyTo -> it's network-replicated (snapshot from server); the client receives damage, never computes it. - No client producer writes a damage value (GetDamage has no real combat caller; the rich calc factory has 0 callers). Conclusion documented: per-weapon damage numbers are server-authoritative runtime Entitas component values, assigned via fully-generic dispatch with no static class/index/string anchor -> not statically extractable from GameAssembly.dll. Trackable static artifacts are the model + formula RVAs. Adds reusable pipeline: reverse/ghidra_decomp_targets.py, reverse/find_damage_writes.py.
This commit is contained in:
@@ -90,6 +90,32 @@ can follow. That, plus server-authoritative resolution, is why the constant can'
|
||||
by static anchoring (in my tooling **or** Ghidra — Ghidra reads bodies but can't defeat the
|
||||
dynamic dispatch without already knowing which system to read).
|
||||
|
||||
## Ghidra deep-dive (decompilation) — what it added
|
||||
|
||||
Imported `GameAssembly.dll` into Ghidra 11.1.2 (headless, JDK17) and bulk-decompiled
|
||||
~17,200 combat/system functions to C (`ghidra/` — gitignored; pipeline = `methods.tsv`
|
||||
from `script.json` → `scripts/decomp_targets.py` → `decomp.c` → `find_damage_writes.py`).
|
||||
Findings, all consistent with the capstone analysis above:
|
||||
|
||||
- **`GetDamage` is a pure component read.** Decompiled body is the 8-way switch returning
|
||||
`*(undefined4 *)(component + 0x10)` (the per-type `value`), or `0`. No constants.
|
||||
- **`PlainDamgeDealerComponent` is network-replicated.** Its `CopyTo` (RVA 0x4BB1A50)
|
||||
copies `+0x10`(damageAmount)/`+0x14`(damageType)/`+0x15`(isMelee) — i.e. the component
|
||||
travels in **snapshots from the server**; the client receives the damage, doesn't compute it.
|
||||
- **No client producer.** Across all 17k decompiled functions, nothing writes a damage value
|
||||
into a `PlainDamgeDealer`/`DamageXxx` component from a literal or from `GetDamage`:
|
||||
`GetDamage` (0x4BAC520) has no real combat caller (only `DeathController$$OnHit` UI +
|
||||
the calc itself), and the rich calc `<GetDamage>d__12` factory (0x4BAC460) has **zero**
|
||||
callers. So the damage *computation* is not exercised in client-reachable code.
|
||||
|
||||
**Conclusion:** the per-weapon damage numbers are **server-authoritative runtime data**.
|
||||
They live as Entitas component `value` floats created/assigned by server-side code through
|
||||
fully-generic dispatch (no static class/index/string anchor) and reach the client only via
|
||||
network snapshots / `DamageEventMessage`. There is no static constant in `GameAssembly.dll`
|
||||
that an xref, pattern scan, or decompile-grep can tie to "revolver melee = N". Getting the
|
||||
literal number requires a **runtime** read (in-game weapon-inspect UI, which calls
|
||||
`GetDamage`), not static extraction.
|
||||
|
||||
## Re-deriving / tracking on updates
|
||||
|
||||
Tooling (regenerates the map from a new `dump.cs` + bundles):
|
||||
@@ -97,6 +123,9 @@ Tooling (regenerates the map from a new `dump.cs` + bundles):
|
||||
`find_rip_refs` / `find_rip_refs_batch` (RIP-relative data xrefs), `disasm_method` /
|
||||
`analyze` (resolves calls, reads float consts), `scan_movss_consts`.
|
||||
- `bundle/component_census.py`, `bundle/dump_blueprint.py`.
|
||||
- `reverse/ghidra_decomp_targets.py` (Ghidra headless post-script: decompile a target
|
||||
list to C) + `reverse/find_damage_writes.py` (scan decompiled C for the populate
|
||||
fingerprint). Ghidra install/project/output live under the gitignored `ghidra/`.
|
||||
|
||||
To diff the *formula* across patches: re-locate `HealthAndDamageExtensions.GetDamage` and
|
||||
`<GetDamage>d__12.MoveNext` by signature and compare. To get the actual *numbers*: they are
|
||||
|
||||
Reference in New Issue
Block a user