# find-CEntitySystem_Init-decompiles

> Final-guarantee fallback for the find-CEntitySystem_Init-decompiles preprocessor. Recovers the struct members, indirect virtual-function offsets, and de-inlined helpers that CEntitySystem::Init sets up, by decompiling CEntitySystem_Init in CS2 server.dll / libserver.so and following de-inlined callees when a target is no longer accessed directly. Use this skill when the deterministic/LLM preprocessor (ida_preprocessor_scripts/find-CEntitySystem_Init-decompiles.py) could not resolve every target because a member or vfunc was inlined or de-inlined in a way the LLM_DECOMPILE references do not cover. Trigger: CEntitySystem_m_sEntSystemName, CEntitySystem_m_eNetworkSerializationMode, CEntitySystem_m_Symbols, CEntitySystem_m_ComponentUnserializerInfoAllocator, CEntitySystem_m_pNetworkFieldChangedEventQueue, CEntitySystem_m_pNetworkFieldScratchData, CEntitySystem_m_pFieldChangeLimitSpew, CEntitySystem_m_EntityMaterialAttributes, INetworkMessages_SetNetworkSerializationContextData, IFlattenedSerializers_CreateFieldCh

- Skill: `hlnd2t/find-centitysystem-init-decompiles` (Agent Skill)
- Install (CLI): `npx skillmds@latest add hlnd2t/find-centitysystem-init-decompiles`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hlnd2t/find-centitysystem-init-decompiles/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: HLND2T (https://skillmd.com/u/hlnd2t)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/hlnd2t/find-centitysystem-init-decompiles

---


# Find CEntitySystem_Init-decompiles (final-guarantee fallback)

Recover every symbol that `CEntitySystem::Init` wires up, in CS2 `server.dll` / `libserver.so`, using IDA Pro
MCP tools. This is the **Agent fallback** for the `find-CEntitySystem_Init-decompiles` skill: it only runs when
the preprocessor script returned failure, which almost always means one target's access pattern **moved** —
either it was inlined into `CEntitySystem_Init` where the reference expected a separate function, or it was
de-inlined out of `CEntitySystem_Init` into a helper the reference does not know about.

Your job is to produce the missing output YAMLs regardless of that inline/de-inline boundary.

## Realworld Function References

Read the platform-relevant real-world YAMLs before searching in IDA. They provide concrete disassembly,
decompiler output, semantic anchors, and both sides of the known inline/de-inline boundary. Treat their
addresses and offsets as reference-build values only; verify every result against the current binary.

- Windows inline baseline: `ida_preprocessor_scripts/references/server/CEntitySystem_Init.windows.yaml`
- Linux baseline: `ida_preprocessor_scripts/references/server/CEntitySystem_Init.linux.yaml`
- Windows de-inlined `CEntitySystem_Init` variant:
  `ida_preprocessor_scripts/references/server/CEntitySystem_Init-noinline.windows.yaml`
- Windows de-inlined material helper:
  `ida_preprocessor_scripts/references/server/CEntitySystem_InitEntityMaterialAttributes.windows.yaml`
- Linux registration helper:
  `ida_preprocessor_scripts/references/server/CEntitySystem_ProcessEntityRegistration.linux.yaml`

## Background — what CEntitySystem_Init does

`CEntitySystem_Init(this, const char *name, int a3, int mode, char a5)` is a long constructor-style routine.
Along the way it:

- copies the system name into `this->m_sEntSystemName`;
- stores the serialization `mode` into `this->m_eNetworkSerializationMode`;
- initializes `this->m_ComponentUnserializerInfoAllocator` (a `CUtlScratchMemoryPool`);
- registers all entity/component classes;
- calls `g_pNetworkMessages->SetNetworkSerializationContextData("string_t_table", m_eNetworkSerializationMode, &m_Symbols)`
  through the **`INetworkMessages` vtable** (an indirect virtual call);
- when `m_eNetworkSerializationMode` is set, allocates a `CNetworkFieldScratchData` into
  `this->m_pNetworkFieldScratchData` and calls
  `g_pFlattenedSerializers->CreateFieldChangedEventQueue(m_pNetworkFieldScratchData, m_pFieldChangeLimitSpew)`
  through the **`IFlattenedSerializers` vtable**, storing the result into `this->m_pNetworkFieldChangedEventQueue`;
- (Linux) tail-calls the de-inlined `CEntitySystem_ProcessEntityRegistration`;
- (Windows) runs the entity-material-attributes registration loop that touches `this->m_EntityMaterialAttributes`.

All member accesses are **relative to `this`** (the first argument — `rcx` on Windows, `rdi` on Linux — usually
copied to `rsi`/`rbx`). This is the key to robustness: `this + offset` is stable whether the access sits in
`CEntitySystem_Init` itself or in a helper that received `this` as its first argument.

## Robustness principle — follow the de-inline boundary

For **every** target below:

1. First look for its access pattern **inside `CEntitySystem_Init`**.
2. If it is not there, it has been **de-inlined into a callee**. Enumerate the functions that
   `CEntitySystem_Init` calls (read the pseudocode / disassembly and collect each `sub_*` / named `call`
   target), decompile the plausible ones, and search there. The owning helper takes `this` (the
   `CEntitySystem *`) as its first parameter, so the same `this + offset` pattern appears. Recurse one or two
   levels if needed.
3. Conversely, a target the reference expected in a **separate function** (e.g. Linux
   `CEntitySystem_ProcessEntityRegistration`, or Windows `m_EntityMaterialAttributes`) may have been **inlined
   back into `CEntitySystem_Init`** — in that case find the pattern directly in `CEntitySystem_Init`.

Anchor each target by its **semantic fingerprint** (the strings, constants, and neighboring calls listed
below), not by a fixed address or a fixed containing function.

## Output inventory

`struct_name` is always `CEntitySystem`. Offsets are **reference values from the 14168 build — verify against
the binary, do not assume**; they change across updates and differ per platform.

| # | Output symbol | Kind | Windows | Linux | Writer skill |
|---|---------------|------|---------|-------|--------------|
| 1 | `CEntitySystem_m_sEntSystemName` | struct member | `0xA88` | `0xA88` | `/write-structoffset-as-yaml` |
| 2 | `CEntitySystem_m_eNetworkSerializationMode` | struct member | `0xBBC` | `0xBBC` | `/write-structoffset-as-yaml` |
| 3 | `CEntitySystem_m_ComponentUnserializerInfoAllocator` | struct member | `0xCB8` | `0xCC0` | `/write-structoffset-as-yaml` |
| 4 | `CEntitySystem_m_Symbols` | struct member | `0x1EC8` | `0x1ED0` | `/write-structoffset-as-yaml` |
| 5 | `CEntitySystem_m_pNetworkFieldChangedEventQueue` | struct member | `0xC80` | `0xC88` | `/write-structoffset-as-yaml` |
| 6 | `CEntitySystem_m_pNetworkFieldScratchData` | struct member | `0xC88` | `0xC90` | `/write-structoffset-as-yaml` |
| 7 | `CEntitySystem_m_pFieldChangeLimitSpew` | struct member | `0xC90` | `0xC98` | `/write-structoffset-as-yaml` |
| 8 | `INetworkMessages_SetNetworkSerializationContextData` | indirect vcall | vtable `INetworkMessages`, offset `0xA0`, index `20` | same | `/write-vfunc-as-yaml` |
| 9 | `IFlattenedSerializers_CreateFieldChangedEventQueue` | indirect vcall | vtable `IFlattenedSerializers`, offset `0x118`, index `35` | same | `/write-vfunc-as-yaml` |
| 10 | `CEntitySystem_ProcessEntityRegistration` | function | *inlined — do NOT emit* | separate func (`func_sig`) | `/write-func-as-yaml` |
| 11 | `CEntitySystem_m_EntityMaterialAttributes` | struct member | `0x2070` | *inlined — do NOT emit* | `/write-structoffset-as-yaml` |

Platform gating: symbols 1–9 are produced on **both** platforms. Symbol 10 is **Linux-only** (on Windows the
routine is inlined into `CEntitySystem_Init`). Symbol 11 is **Windows-only** (on Linux the access lives inside
`CEntitySystem_ProcessEntityRegistration`, symbol 10).

## Step 0. Skip targets already produced

Some outputs may already exist beside the binary — written by the preprocessor before it failed, or by the
`find-CEntitySystem_InitEntityMaterialAttributes` / `find-CEntitySystem_m_EntityMaterialAttributes` fallback
pair that runs before this skill. For each output, if `<name>.<platform>.yaml` already exists next to the
binary and parses to a non-empty mapping, **skip it** and spend effort only on the missing ones. You can list
the binary directory with:

```
mcp__ida-pro-mcp__py_eval code="import idaapi, os; d=os.path.dirname(idaapi.get_input_file_path()); print('\n'.join(sorted(f for f in os.listdir(d) if f.endswith('.yaml'))))"
```

`/get-func-from-yaml` also reports existence for functions/vfuncs (returns an error when absent).

## Step 1. Load and decompile the predecessor

**ALWAYS** Use SKILL `/get-func-from-yaml` with `func_name=CEntitySystem_Init` to obtain its `func_va`.

If the skill returns an error, **STOP** and report to user (this fallback cannot run without the predecessor).

Decompile it, and keep the list of functions it calls for the de-inline search in later steps:

```
mcp__ida-pro-mcp__decompile addr="<CEntitySystem_Init.func_va>"
```

Confirm the `this` register (first argument) and note where it is copied (typically `rsi` on Windows, `rbx` on
Linux). Every struct-member offset below is `this + offset`.

## Step 2. Resolve the struct members

For each member: locate the access, read the **displacement** in the instruction (that is the offset), confirm
it is relative to `this` (the `CEntitySystem *`), and confirm `struct_name = CEntitySystem`. Then generate a
signature and write the YAML (Step 5).

### 2a. `m_sEntSystemName`

The very first meaningful call in `CEntitySystem_Init`:

```c
CUtlString::Set((CUtlString *)(this + 0xA88), name);   // name == arg a2
```

Anchor: the `lea`/`add` that forms `this + off` immediately before the `CUtlString::Set` call whose second
argument is the incoming name string. `off` is `m_sEntSystemName` (ref `0xA88`).

### 2b. `m_eNetworkSerializationMode`

The **int/enum** mode stored from the `mode` parameter (`a4`) near the top:

```c
*(_DWORD *)(this + 0xBBC) = mode;                      // ref 0xBBC
```

It is read again later as the middle argument to the `SetNetworkSerializationContextData` vcall (Step 3a),
right next to the `"string_t_table"` string — that read site is an equally good anchor.

> Decoy: a **byte** flag `*(_BYTE *)(this + 0xBDA) = (a3 == 1 && g_pNetworkMessages != nullptr)` is set a few
> instructions away. That `0xBDA` boolean is **not** `m_eNetworkSerializationMode` — the target is the DWORD at
> `0xBBC`. Do not confuse them.

### 2c. `m_ComponentUnserializerInfoAllocator`

The `CUtlScratchMemoryPool` initialized near the top:

```c
CUtlScratchMemoryPool::Init((CUtlScratchMemoryPool *)(this + 0xCB8/*win*/), 0x400, 0, nullptr, 0);
```

Anchor: the `lea rcx/rdi, [this + off]` feeding the `CUtlScratchMemoryPool::Init` call whose first immediate is
`0x400`. `off` is `m_ComponentUnserializerInfoAllocator` (ref `0xCB8` Windows / `0xCC0` Linux).

### 2d. `m_Symbols`

The last argument to the `SetNetworkSerializationContextData` vcall (Step 3a):

```c
g_pNetworkMessages->SetNetworkSerializationContextData("string_t_table", m_eNetworkSerializationMode, this + 0x1EC8/*win*/);
```

Anchor: the `lea` that forms `this + off` as the third argument, in the block that also loads
`"string_t_table"`. `off` is `m_Symbols` (ref `0x1EC8` Windows / `0x1ED0` Linux).

### 2e. The field-change trio — `m_pNetworkFieldScratchData`, `m_pFieldChangeLimitSpew`, `m_pNetworkFieldChangedEventQueue`

All three live in the `if (m_eNetworkSerializationMode)` block, around the `CreateFieldChangedEventQueue` vcall
(Step 3b):

```c
// m_pNetworkFieldScratchData: object from operator new(0x58) with a CNetworkFieldScratchData vtable,
// stored at this + off, and passed as the 1st data argument to CreateFieldChangedEventQueue.
*(_QWORD *)(this + 0xC88/*win*/) = scratch;            // ref 0xC88 win / 0xC90 linux

m_pNetworkFieldChangedEventQueue =                     // stored from the vcall's return value
    g_pFlattenedSerializers->CreateFieldChangedEventQueue(
        m_pNetworkFieldScratchData,                    // this + 0xC88 win / 0xC90 linux
        m_pFieldChangeLimitSpew);                       // this + 0xC90 win / 0xC98 linux
*(_QWORD *)(this + 0xC80/*win*/) = <return value>;     // m_pNetworkFieldChangedEventQueue, ref 0xC80 win / 0xC88 linux
```

Distinguish them by role:

- `m_pNetworkFieldScratchData` — the object **written** just above the call (the `operator new(0x58)` result with
  a vtable) and read as the vcall's **second** argument (`rdx`/2nd data reg). Ref `0xC88` win / `0xC90` linux.
- `m_pFieldChangeLimitSpew` — read as the vcall's **third** argument (`r8`/3rd data reg). Ref `0xC90` win /
  `0xC98` linux.
- `m_pNetworkFieldChangedEventQueue` — the member the vcall's **return value** is stored into. Ref `0xC80` win /
  `0xC88` linux.

> These three signatures may need to span a function boundary (the block is easily de-inlined). If
> `/generate-signature-for-structoffset` cannot find a unique signature, it is acceptable to write the member
> with the **offset only** (omit `offset_sig`) — the offset is the required output.

## Step 3. Resolve the indirect virtual-call offsets

Symbols 8 and 9 are **indirect virtual calls**, not concrete functions. The YAML records the interface vtable
and slot, and a `vfunc_sig` that pins the call instruction — there is **no `func_va`**. Do not try to resolve a
concrete implementation address.

For each: find the `call qword ptr [reg + disp]` instruction, take `vfunc_offset = disp` and
`vfunc_index = disp / 8`.

### 3a. `INetworkMessages_SetNetworkSerializationContextData`

- Call site: `call qword ptr [reg + 0xA0]` where `reg` holds `g_pNetworkMessages` (type `INetworkMessages *`),
  in the block that loads the `"string_t_table"` string and passes `m_eNetworkSerializationMode` and
  `&m_Symbols`.
- `vtable_name = INetworkMessages`, `vfunc_offset = 0xA0` (ref), `vfunc_index = 20`.

### 3b. `IFlattenedSerializers_CreateFieldChangedEventQueue`

- Call site: `call qword ptr [reg + 0x118]` where `reg` holds `g_pFlattenedSerializers`
  (type `IFlattenedSerializers *`), passing `m_pNetworkFieldScratchData` and `m_pFieldChangeLimitSpew`, with the
  return stored into `m_pNetworkFieldChangedEventQueue`.
- `vtable_name = IFlattenedSerializers`, `vfunc_offset = 0x118` (ref), `vfunc_index = 35`.

`g_pNetworkMessages` and `g_pFlattenedSerializers` are already-named globals in the database; use their type to
confirm the interface, and confirm the semantic role from the neighboring arguments described above. If the
whole call has been de-inlined into a helper, follow the callees (Robustness principle) — the `[reg + disp]`
instruction and its argument setup move together.

## Step 4. Platform-specific de-inlined targets

### 4a. Linux only — `CEntitySystem_ProcessEntityRegistration` (symbol 10)

On Linux, `CEntitySystem_Init` tail-calls this de-inlined helper near its end (after the class-table loop):

```c
CEntitySystem_ProcessEntityRegistration(this);         // last call before Init returns
```

Anchor: the last `call` in `CEntitySystem_Init` that takes only `this`. Decompile it to confirm it performs the
entity-material-attributes registration (the FNV-1a hashing loop over material names — constants
`0x811C9DC5` and `0x1000193`). Rename it to `CEntitySystem_ProcessEntityRegistration` with
`mcp__ida-pro-mcp__rename`, then treat it as a normal function (Step 5c).

> If (on some future Windows build) this routine is emitted as a **separate function** too, apply the same
> method on Windows. On the current Windows build it is inlined into `CEntitySystem_Init` — do **not** emit it
> there.

### 4b. Windows only — `m_EntityMaterialAttributes` (symbol 11)

On Windows this member is accessed inside the material-attributes loop, whether that loop is inlined in
`CEntitySystem_Init` or de-inlined into `CEntitySystem_InitEntityMaterialAttributes` (a helper taking `this`):

```c
lea reg, [this + 0x2070]                                // ref 0x2070 = m_EntityMaterialAttributes
```

Anchor: the `lea this + off` used as the map/dictionary base in the loop that FNV-1a-hashes material name
strings (constants `0x811C9DC5`, `0x1000193`; a small default string blob is the hash seed). Follow the callees
of `CEntitySystem_Init` if the loop is not inlined. `off` is `m_EntityMaterialAttributes` (ref `0x2070`).

> On Linux this access lives inside `CEntitySystem_ProcessEntityRegistration` (symbol 10) and is **not** emitted
> as a separate member YAML.

## Step 5. Generate signatures and write the YAMLs

### 5a. Struct members (symbols 1–7, and 11 on Windows)

For each resolved member:

1. **ALWAYS** Use SKILL `/generate-signature-for-structoffset` on the instruction that contains the offset to
   obtain `offset_sig` / `offset_sig_disp`. (Best-effort for the field-change trio — see the note in Step 2e.)
2. **ALWAYS** Use SKILL `/write-structoffset-as-yaml` with:
   - `struct_name`: `CEntitySystem`
   - `member_name`: the member (e.g. `m_sEntSystemName`)
   - `offset`: the resolved hex offset
   - `size`: `None`
   - `offset_sig`: the signature from step 1 (or `None` if not found)
   - `offset_sig_disp`: from step 1 (or `None`)

### 5b. Indirect vcalls (symbols 8–9)

For each:

1. **ALWAYS** Use SKILL `/generate-signature-for-vfuncoffset` on the `call qword ptr [reg + offset]` instruction
   to obtain `vfunc_sig` (the offset bytes are fixed in the signature; `vfunc_sig_disp` is `0`).
2. **ALWAYS** Use SKILL `/write-vfunc-as-yaml` with:
   - `func_name`: e.g. `INetworkMessages_SetNetworkSerializationContextData`
   - `vtable_name`: `INetworkMessages` / `IFlattenedSerializers`
   - `vfunc_offset`: the resolved offset (`0xA0` / `0x118`)
   - `vfunc_index`: `vfunc_offset / 8` (`20` / `35`)
   - `vfunc_sig`: from step 1
   - `func_addr`: `None`   (no concrete implementation address for an interface vcall)
   - `func_sig`: `None`

### 5c. Function (symbol 10, Linux only)

1. **ALWAYS** Use SKILL `/generate-signature-for-function` with `addr=<CEntitySystem_ProcessEntityRegistration_addr>`.
2. **ALWAYS** Use SKILL `/write-func-as-yaml` with:
   - `func_name`: `CEntitySystem_ProcessEntityRegistration`
   - `func_addr`: the resolved address
   - `func_sig`: the validated signature from step 1

## Failure handling

- If the **predecessor** `CEntitySystem_Init` YAML is missing → **STOP** and report to user.
- If an individual required target cannot be located even after following callees → resolve the ones you can,
  then **STOP** and report exactly which output(s) could not be found, so the user can extend the references.
- Never emit a platform-gated symbol on the wrong platform (symbol 10 Windows, symbol 11 Linux).

## Output YAML filenames

Written beside the binary by the writer skills, one file per symbol:

- Windows (`server.dll`): `<symbol>.windows.yaml`
- Linux (`libserver.so`): `<symbol>.linux.yaml`

e.g. `CEntitySystem_m_Symbols.windows.yaml`, `INetworkMessages_SetNetworkSerializationContextData.linux.yaml`,
`CEntitySystem_ProcessEntityRegistration.linux.yaml`, `CEntitySystem_m_EntityMaterialAttributes.windows.yaml`.

## Why this is robust

- Members are anchored to `this + offset`, so they are recoverable whether the access is inlined in
  `CEntitySystem_Init` or de-inlined into a helper that receives `this`.
- Each target has a **semantic fingerprint** (a string, a constant, a neighboring interface call) that survives
  offset changes and function-boundary moves across updates.
- The indirect vcalls are pinned by their call-site displacement, which is stable even when the surrounding code
  is reorganized.
- Already-produced outputs are skipped, so this fallback composes with the preprocessor and with the
  windows de-inline fallback pair instead of fighting them.

