update readme
This commit is contained in:
parent
0bc70ac309
commit
3de955c4ff
1 changed files with 11 additions and 11 deletions
22
README.md
22
README.md
|
|
@ -68,7 +68,7 @@ The artifacts answer four different questions, and most useful work joins two or
|
|||
- **`abi-<game>.json` — whether it is safe to call.** A declared prototype joined to the register footprint measured in *this* build, with a verdict per function. `verified` and `lower-bound` are callable; `mismatch` says the prototype in circulation is wrong for this binary.
|
||||
- **`bindings-<game>.json` — what the binary declares about itself.** Console commands with flags and handler addresses, entity inputs and outputs, map classname → C++ class, and the typed Pulse registry.
|
||||
|
||||
One thing to internalise before reading further, because it shapes every capability below: **there are no ConVars in these artifacts, in either game.** The command surface is 784 ConCommands on CS2 and 855 on Dota, and not one `sv_airaccelerate`, `mp_freezetime`, `bot_quota` or `dota_gold_per_tick` appears anywhere. This is a *function-and-layout* release, not a configuration release. A plugin built on it changes behaviour by detouring code and writing fields — never by setting a cvar.
|
||||
One thing shapes every capability below: **there are no ConVars in these artifacts, in either game.** The command surface is 784 ConCommands on CS2 and 855 on Dota, and not one `sv_airaccelerate`, `mp_freezetime`, `bot_quota` or `dota_gold_per_tick` appears anywhere. This is a *function-and-layout* release, not a configuration release. A plugin built on it changes behaviour by detouring code and writing fields — never by setting a cvar.
|
||||
|
||||
Tiers stay visible throughout. `core` and `high_confidence` are buildable today; the `experimental` band is fenced off at the end and is a different kind of thing entirely.
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ What you do not get: `TryPlayerMove`, `WalkMove`, `Accelerate` and `TracePlayerB
|
|||
|
||||
#### Combat, damage and tracing
|
||||
|
||||
`CBaseEntity::TakeDamage` is the funnel and `CCSPlayerPawn::OnTakeDamage_Alive` the player-specific override, but the interesting part is that you do not need a constructor to build a damage packet: `CTakeDamageInfo` is laid out completely — 22 fields over 280 bytes — and `CTakeDamageResult` (15 fields) tells you what the engine actually did, including `m_flPreModifiedDamage` beside `m_flDamageDealt` and a `m_bWasDamageSuppressed` flag. `DamageTypes_t`, `HitGroup_t` and the 21-flag `TakeDamageFlags_t` (`DFLAG_PREVENT_DEATH`, `DFLAG_IGNORE_ARMOR`, …) give you the switchboard. Two ABI notes worth respecting: `CBaseEntity::DispatchTraceAttack` and `CBaseEntity::Event_Killed` are `verified` — and `Event_Killed` measures as the CS2-shaped `(CCSPlayerPawn*, CTakeDamageResult*)`, not the Source-1 `CTakeDamageInfo const&` everyone assumes — while `abi:CBaseEntity::TakeDamage` is tier `core` but verdict **`unverified`**: the declaration was never checked against this build. Build the struct by offsets and prefer the verified entry points.
|
||||
`CBaseEntity::TakeDamage` is the funnel and `CCSPlayerPawn::OnTakeDamage_Alive` the player-specific override, but the interesting part is that you do not need a constructor to build a damage packet: `CTakeDamageInfo` is laid out completely — 22 fields over 280 bytes — and `CTakeDamageResult` (15 fields) tells you what the engine actually did, including `m_flPreModifiedDamage` beside `m_flDamageDealt` and a `m_bWasDamageSuppressed` flag. `DamageTypes_t`, `HitGroup_t` and the 21-flag `TakeDamageFlags_t` (`DFLAG_PREVENT_DEATH`, `DFLAG_IGNORE_ARMOR`, …) give you the switchboard. Two ABI notes: `CBaseEntity::DispatchTraceAttack` and `CBaseEntity::Event_Killed` are `verified` — and `Event_Killed` measures as the CS2-shaped `(CCSPlayerPawn*, CTakeDamageResult*)`, not the Source-1 `CTakeDamageInfo const&` everyone assumes — while `abi:CBaseEntity::TakeDamage` is tier `core` but verdict **`unverified`**: the declaration was never checked against this build. Build the struct by offsets and prefer the verified entry points.
|
||||
|
||||
Weapon rebalancing is entirely schema work. `CCSWeaponBaseVData` is 84 fields over 2,216 bytes: damage, headshot multiplier, armour ratio, penetration, range falloff, cycle time, seven per-stance inaccuracy terms, four recoil terms, the spray-recovery transition bullets, price, kill award — and `m_nRecoilSeed`/`m_nSpreadSeed`, the per-weapon RNG seeds that generate CS2's deterministic spray patterns. Reach it with `FindWeaponVDataByName` (`verified`); there is no member offset from a weapon entity to its VData.
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ Tracing has one fully `verified`, params-complete entry point — `TraceShape(co
|
|||
|
||||
For inventory and loadout rules, eight `CCSPlayer_WeaponServices` entry points carry `verified`, params-complete prototypes including `this` (`EquipWeapon`, `DropWeapon`, `SwitchWeapon`, `GetWeaponBySlot`, and the `CanEquip`/`CanSwitch`/`CanUse` predicates), and `CCSPlayer_ItemServices::CanAcquire` is a verified buy gate. Skins and StatTrak are pure offsets: the `CEconEntity` fallback block, `CAttributeContainer::m_Item` → a fully laid-out `CEconItemView`, and `CAttributeList::SetOrAddAttributeValueByName` (`verified`) to write.
|
||||
|
||||
Two things this domain is unusually clean about. The entire combat pipeline lands in `core` and `high_confidence` — the experimental band contributes nothing to it. And `CCSScript_EntityScript` comes out as a contiguous named vtable run with a three-stage damage pipeline — `OnBeforePlayerDamage`, `OnModifyPlayerDamage`, `OnPlayerDamage` (veto / scale / observe) — which is exactly the shape a perk or RPG mod wants, though none of those slots has a typed prototype, only a measured register count.
|
||||
Two things make this domain unusually workable. The entire combat pipeline lands in `core` and `high_confidence` — the experimental band contributes nothing to it. And `CCSScript_EntityScript` comes out as a contiguous named vtable run with a three-stage damage pipeline — `OnBeforePlayerDamage`, `OnModifyPlayerDamage`, `OnPlayerDamage` (veto / scale / observe) — which is exactly the shape a perk or RPG mod wants, though none of those slots has a typed prototype, only a measured register count.
|
||||
|
||||
#### Bots and the nav mesh
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ Two things this domain is unusually clean about. The entire combat pipeline land
|
|||
|
||||
The aim and behaviour functions — `UpdateLookAngles`, `UpdateReactionQueue`, `BendLineOfSight`, `FindMostDangerousThreat`, plus the direct verbs `SetBotEnemy`, `SetState`, `Panic`, `Blind`, `Retreat` — are `core` or `high_confidence` byte signatures, but **none has a declared prototype**: `abi-cs2.json` has no entry for any `CCSBot::` method. You get an address and a register count.
|
||||
|
||||
Be precise about the state machine, because it is easy to oversell. All 16 behaviour states appear as `core` entries and a `OnEnter=0 / OnUpdate=1 / OnExit=2 / GetName=3` slot convention is visible across them. But those offset-form entries carry no class binding — `IdleState::OnEnter`, `HideState::OnEnter` and `PickupHostageState::OnEnter` are all bare "slot 0" — so resolving a specific state's vtable needs a class pointer the artifacts do not supply. Eleven of the state methods (including `CCSBot::SetState` and eight `OnUpdate` slots) ship as byte signatures instead, and those you can resolve directly.
|
||||
The state machine is more limited than it first looks. All 16 behaviour states appear as `core` entries and a `OnEnter=0 / OnUpdate=1 / OnExit=2 / GetName=3` slot convention is visible across them. But those offset-form entries carry no class binding — `IdleState::OnEnter`, `HideState::OnEnter` and `PickupHostageState::OnEnter` are all bare "slot 0" — so resolving a specific state's vtable needs a class pointer the artifacts do not supply. Eleven of the state methods (including `CCSBot::SetState` and eight `OnUpdate` slots) ship as byte signatures instead, and those you can resolve directly.
|
||||
|
||||
Nav work is better served from the sides than the middle. `CPulseServerFuncs::GetEntityNavMeshPosition` and `GetEntityHeightAboveNavMesh` are typed with no signature dependency at all; `CCSBot::m_playerTravelDistance` is a `float32[64]` of nav travel distance from that bot to every player slot, maintained by the engine every tick — real walk distances for free. Runtime map flow is `CFuncNavBlocker` with `m_nBlockedTeamNumber` (per-team blocking) driven by `BlockNav`/`UnblockNav`, whose handlers are `verified` and params-complete. The 44 `nav_*` editor commands survive in the dedicated server with verified `void(CCommandContext*, CCommand*)` handlers, including `nav_check_connectivity` — but nearly all of them carry the `cheat` flag, so a CI map-validation harness runs with `sv_cheats` on, not on a public server. There is no `CNavArea` or `CNavMesh` schema class: area-level data means calling engine functions with pointers you obtained from other engine functions.
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ Entity I/O is where one hook shape covers an enormous surface. 715 CS2 inputs ar
|
|||
|
||||
Trigger volumes have a single global arbiter — `CBaseTrigger::PassesTriggerFilters` is `verified` `bool(CBaseEntity*)` at vtable 270 — and a passive reader: `m_hTouchingEntities` is a live handle vector at a fixed offset. Collision surgery is netvar-only and complete: `VPhysicsCollisionAttribute_t` exposes `m_nInteractsAs`/`m_nInteractsWith`/`m_nInteractsExclude` as three `uint64` masks, which is exactly the knob a no-block plugin flips.
|
||||
|
||||
`func_mover` is worth flagging as a discovery: it has zero entity inputs and zero entity outputs, so anyone auditing the IO graph would conclude it is inert. Its entire runtime surface is 44 typed `CFuncMoverAPI` Pulse methods plus 98 netvar fields. CS2's spline-mover system lives outside the classic IO graph.
|
||||
`func_mover` is a trap for anyone working from the IO graph: it has zero entity inputs and zero entity outputs, so an audit of that graph concludes it is inert. Its entire runtime surface is 44 typed `CFuncMoverAPI` Pulse methods plus 98 netvar fields. CS2's spline-mover system lives outside the classic IO graph.
|
||||
|
||||
#### Game rules, teams and economy
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ Trigger volumes have a single global arbiter — `CBaseTrigger::PassesTriggerFil
|
|||
|
||||
Money is a plain `int32`: `m_pInGameMoneyServices` → `m_iAccount`, with start/spent/next-round fields beside it and the whole team loss-bonus model on the rules object. Note there is **no per-player money function at any tier** — no `AddAccount`, no `GiveMoney` anywhere in the search — so an economy mod writes the ledger and replicates with `NetworkStateChanged` (`verified`; its sibling `StateChanged` is verdict `mismatch`, so use the right one).
|
||||
|
||||
Team moves have three independent paths — `CBaseEntity::ChangeTeam` at vtable 102, `CBasePlayerController::SwitchSteam` (`verified`), and the command path — gated by `CCSGameRules::WillTeamHaveRoomForPlayer` (`verified`). Two of the alternatives are weaker than they look: `CCSPlayerController::SwitchTeam` and `HandleCommandJoinTeam` are `unverified`, and `abi:CCSPlayerController::ChangeTeam` is `return-only` (no parameter list). Respawn waves are two arrays on the rules object (`m_TeamRespawnWaveTimes`, `m_flNextRespawnWave`), and spawn-point pools with round-robin cursors sit beside them — though the engine's *choice* of spawn point is not exposed as a hookable function. One defect to verify before shipping: `CCSPlayerController::Respawn` and `CCSPlayerController::RoundRespawn` are both reported at vtable 272 while the base-class `RoundRespawn` is at 270; two names cannot share one slot.
|
||||
Team moves have three independent paths — `CBaseEntity::ChangeTeam` at vtable 102, `CBasePlayerController::SwitchSteam` (`verified`), and the command path — gated by `CCSGameRules::WillTeamHaveRoomForPlayer` (`verified`). Two of the alternatives are weaker than they look: `CCSPlayerController::SwitchTeam` and `HandleCommandJoinTeam` are `unverified`, and `abi:CCSPlayerController::ChangeTeam` is `return-only` (no parameter list). Respawn waves are two arrays on the rules object (`m_TeamRespawnWaveTimes`, `m_flNextRespawnWave`), and spawn-point pools with round-robin cursors sit beside them — though the engine's *choice* of spawn point is not exposed as a hookable function. One inconsistency to resolve in-engine before relying on either: `CCSPlayerController::Respawn` and `CCSPlayerController::RoundRespawn` are both reported at vtable 272 while the base-class `RoundRespawn` is at 270 — two names cannot share one slot.
|
||||
|
||||
Stats need no events at all. `CSMatchStats_t` derives from `CSPerRoundStats_t` and already tracks `m_i1v1Count`/`m_i1v1Wins`, `m_i1v2*`, `m_iEntryCount`/`m_iEntryWins`, `m_iEnemy5Ks` through `m_iEnemy2Ks`, and shots fired vs shots on target — the clutch and entry numbers most stats plugins recompute from kill events.
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ The lowest-risk half of this domain needs no signatures: `env_shake`, `env_fade`
|
|||
|
||||
`UTIL_DispatchEffect`/`UTIL_DispatchEffectFilter` are `core`, `verified`, params-complete, and their one non-trivial argument — `CEffectData` — is fully mapped (20 fields, 112 bytes, SysV `memory`). That pairing is the ideal case these artifacts exist to produce.
|
||||
|
||||
Be honest about recipient filters, because several attractive claims depend on them. The filtered dispatchers (`UTIL_DispatchParticleEffectFilter_Position`/`_Attachment`, `UTIL_SayTextFilter`, `UTIL_SayText2Filter`, `SoundOpGameSystem::StartSoundEventString`) are real and verified — but the *only* recipient-filter symbol anywhere in `gamedata-cs2.json` is `CRecipientFilter::AddAllPlayers`, whose ABI entry is `unverified` with an empty parameter list. There is no `AddRecipient`, no per-team filter, no single-user filter. You get "dispatch to everyone" and a filter-shaped hole you must fill from your own framework. The genuinely per-client route that *is* covered is the `point_soundevent` entity: `StartSoundOnSingleClient` targets one player index and fires an `m_onSoundFinished` output when the sound ends.
|
||||
Recipient filters are the gap in this domain, and several otherwise-attractive routes run through them. The filtered dispatchers (`UTIL_DispatchParticleEffectFilter_Position`/`_Attachment`, `UTIL_SayTextFilter`, `UTIL_SayText2Filter`, `SoundOpGameSystem::StartSoundEventString`) are real and verified — but the *only* recipient-filter symbol anywhere in `gamedata-cs2.json` is `CRecipientFilter::AddAllPlayers`, whose ABI entry is `unverified` with an empty parameter list. There is no `AddRecipient`, no per-team filter, no single-user filter. You get "dispatch to everyone" and a filter-shaped hole you must fill from your own framework. The genuinely per-client route that *is* covered is the `point_soundevent` entity: `StartSoundOnSingleClient` targets one player index and fires an `m_onSoundFinished` output when the sound ends.
|
||||
|
||||
The sound-operator system itself is unusually complete — start-by-string, the 11-argument raw start, set-param-string, and stop-with-filter are all `core` with `verified`, params-complete prototypes from a single provenance. The exception is flagged loudly: `SoundOpGameSystem::StopSoundEvent` is verdict `mismatch` (measured footprint exceeds declared), so use `StopSoundEventFilter`.
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ Reading is the strong direction. There is **no gold or XP mutator** at any usabl
|
|||
|
||||
`CDOTA_ScriptedSpawner` has a 22-method Pulse API — `SpawnNPC`, `SetNPCType`, `SetCustomNPCName`, `SetHealth`, `SetInvulnerable`, `SetNPCWaypoint`, `UseAbility`, `SetAutomaticallyRespawn` — backed by an 18-field entity with three IO outputs (`m_OnAllUnitsKilled`, `m_OnUnitKilled`, `m_OnHealthLow`). Around it: `CDOTA_MapTree_API` cuts and regrows individual trees, `CDOTA_SimpleObstruction_API` toggles blockers whose schema separately controls FoW-blocking versus nav-blocking, `CDOTA_BaseNPC_API` issues movement orders and speech bubbles, `CDOTA_BaseNPC_Building_API` pushes invulnerability refcounts. That is a fairly complete undocumented encounter-scripting system, and the whole map's tree state is one `uint64[256]` bitfield — 16,384 trees in 2 KB.
|
||||
|
||||
The Pulse registry is self-documenting: all 500 Dota bindings are typed with parameter names and, for many, Valve's own English description read out of the binary. Note the tier is per-game, though — Dota's Pulse *runtime* entry points (`CPulseSystem::CreateInstance`, `CPulseGraphInstance::Unserialize`, `CPulseTypeManager::FindTypeByName`) are all experimental, where CS2 has `core` equivalents. The same trap applies to `CGameEntitySystem::AddEntityIOEvent`: `core` in CS2, experimental in Dota. Any sentence that says "core" about a shared engine symbol without naming the game is unsafe.
|
||||
The Pulse registry is self-documenting: all 500 Dota bindings are typed with parameter names and, for many, Valve's own English description read out of the binary. Note the tier is per-game, though — Dota's Pulse *runtime* entry points (`CPulseSystem::CreateInstance`, `CPulseGraphInstance::Unserialize`, `CPulseTypeManager::FindTypeByName`) are all experimental, where CS2 has `core` equivalents. The same applies to `CGameEntitySystem::AddEntityIOEvent`: `core` in CS2, experimental in Dota. Tiers are per-game, so check the tier in the artifact for the game you are targeting — a shared engine symbol can be first-class in one and a guess in the other.
|
||||
|
||||
`CLuaVM` comes out as a near-complete `IScriptVM` map — vtable slots 0 through 63, with `RegisterFunction`, `SetValue`, `CreateScope`, `LookupFunction`, `ExecuteFunction` and `RegisterScriptClass` all `verified` — so a native plugin can compile and run Lua into a live custom game, register C++ functions into the addon namespace, and redirect script output. Several slots carry an honest `mismatch` verdict (`GetRootTable`, `CreateTable`, `RegisterInstance`, `CScriptManager::CreateVM`): the slot index is good, the declaration is not. And `CBaseEntity_SharedAPI` exposes `RunScriptCode`, `CallScriptFunction` and `CallGlobalScriptFunction` as Pulse methods, so the two scripting systems bridge in both directions.
|
||||
|
||||
|
|
@ -210,9 +210,9 @@ A flat offset dump cannot do any of the following, and each one is a real failur
|
|||
|
||||
**A field's `name_hash` is stable across builds *and* across games.** 10,363 `Class::field` pairs exist in both artifacts; all 10,363 have identical hashes, and 2,589 of them sit at different offsets. So ship one hash-keyed table of the fields your plugin touches and bind offsets per build and per game at load. A hash that vanishes means a rename; a hash that moves means a rebind.
|
||||
|
||||
**A checked prototype is worth more than a declared one, and the verdict is the product.** `verified` (2,054 CS2 / 3,633 Dota) means declared arity matches the footprint measured in this build. `lower-bound` (82/99) means the declaration passes registers the callee never reads — compatible, but not the same claim. **`mismatch` (55/44) is arguably the most valuable output in the file**: it names community-circulated prototypes that are wrong for this binary and will load the wrong registers. `ambiguous` lists the surviving overloads for you to separate; `return-only` gives a return type and no arity claim; `unverified` means nothing checked it.
|
||||
**A checked prototype is worth more than a declared one, and the verdict is the product.** `verified` (2,054 CS2 / 3,633 Dota) means declared arity matches the footprint measured in this build. `lower-bound` (82/99) means the declaration passes registers the callee never reads — compatible, but not the same claim. **`mismatch` (55/44) is the most immediately useful of the six**: it names community-circulated prototypes that are wrong for this binary and will load the wrong registers. `ambiguous` lists the surviving overloads for you to separate; `return-only` gives a return type and no arity claim; `unverified` means nothing checked it.
|
||||
|
||||
Two structural cross-checks worth knowing about because they cost you nothing: all **226 CS2 entity outputs agree exactly with netvars** on class, member and byte offset, independently derived; and for all 759 CS2 commands present in both files, the dispatch form in `bindings` agrees with the prototype in `abi` — 674 `direct`, 81 `member` (extra leading `this`), 4 `interface`, zero disagreements. Hooking a member-form command with the free-function signature shifts every argument by one, and nothing in the command's name tells you which it is.
|
||||
Two structural cross-checks come for free: all **226 CS2 entity outputs agree exactly with netvars** on class, member and byte offset, independently derived; and for all 759 CS2 commands present in both files, the dispatch form in `bindings` agrees with the prototype in `abi` — 674 `direct`, 81 `member` (extra leading `this`), 4 `interface`, zero disagreements. Hooking a member-form command with the free-function signature shifts every argument by one, and nothing in the command's name tells you which it is.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -226,7 +226,7 @@ The two games' bands are not the same product. CS2's is 3,230 vtable locators ac
|
|||
|
||||
Dota's band *does* reach gameplay: 1,402 byte signatures in `libserver`, roughly 350 of them DOTA-named — `CDOTAGameRules::KillCreeps`, `CDOTATurboGameMode::FilterModifyGold`, `CDOTA_Ability_*::OnSpellStart`. If those names are right it is a gold mine for custom-game work. Treat every one as a hypothesis.
|
||||
|
||||
One sub-band is genuinely self-checking and worth calling out: the `CNetMessagePB<id, MessageType, (SignonGroup_t)g, …>` template instantiations bake a wire id, a protobuf class name, a signon group and a reliability flag into the mangled name. Unlike a bare `CFoo::Bar` guess, that is structured data you can falsify against live traffic in one command (`net_listallmessages`, `net_messageinfo`). Note that for Dota the *authoritative* message-id source is not this band at all — it is the schema enums `EDotaUserMessages`, `EBaseUserMessages` and `EDotaClientMessages`, which are deterministic. Use those for ids and the templates as corroboration.
|
||||
One sub-band is self-checking, which makes it usable on different terms: the `CNetMessagePB<id, MessageType, (SignonGroup_t)g, …>` template instantiations bake a wire id, a protobuf class name, a signon group and a reliability flag into the mangled name. Unlike a bare `CFoo::Bar` guess, that is structured data you can falsify against live traffic in one command (`net_listallmessages`, `net_messageinfo`). Note that for Dota the *authoritative* message-id source is not this band at all — it is the schema enums `EDotaUserMessages`, `EBaseUserMessages` and `EDotaClientMessages`, which are deterministic. Use those for ids and the templates as corroboration.
|
||||
|
||||
The only defensible workflow for anything in this band: pick a candidate, check the measured footprint matches the semantics you expect, then confirm behaviour in-engine yourself before shipping.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue