244 lines
12 KiB
Markdown
244 lines
12 KiB
Markdown
|
|
# EntityDispatchEntry Class Layout
|
||
|
|
|
||
|
|
## Purpose
|
||
|
|
|
||
|
|
This note is the first focused class-layout working paper for the Remorse C++ lift.
|
||
|
|
|
||
|
|
It takes the broad `EntityDispatchEntry*` inventory entry and narrows it into a base/derived object model that can later be pushed into Ghidra as class namespaces, instance structs, vtable structs, and method ownership.
|
||
|
|
|
||
|
|
The goal is not to claim a final C++ API. The goal is to lock down the pieces that are already stable enough to support later implementation work.
|
||
|
|
|
||
|
|
## Why This Family Goes First
|
||
|
|
|
||
|
|
`EntityDispatchEntry` is the strongest current pilot family because it already has:
|
||
|
|
|
||
|
|
- a clear constructor-style base init path
|
||
|
|
- multiple derived constructor variants
|
||
|
|
- explicit owned state and word-list teardown
|
||
|
|
- stable field groups with known offsets
|
||
|
|
- repeated virtual-slot dispatch through known offsets
|
||
|
|
- strong caller evidence across scheduler, runtime-state, palette, and startup/display lanes
|
||
|
|
|
||
|
|
That makes it the best place to prototype the full later workflow:
|
||
|
|
|
||
|
|
- class namespace creation
|
||
|
|
- method ownership
|
||
|
|
- instance-struct typing
|
||
|
|
- vtable typing
|
||
|
|
- base/derived split
|
||
|
|
- later C++ skeleton emission
|
||
|
|
|
||
|
|
## Candidate High-Level Model
|
||
|
|
|
||
|
|
Current best working split:
|
||
|
|
|
||
|
|
- `EntityDispatchEntryBase`
|
||
|
|
- `EntityDispatchEntryTimed` or `EntityDispatchEntryPeriodic` for the `0x3aa6` timing/period variant
|
||
|
|
- `EntityDispatchEntryRuntimeState` for the later `000d:7e00/8078` runtime-state owned-buffer family
|
||
|
|
|
||
|
|
This should stay a working model, not a hard rename, until the class work lands in Ghidra.
|
||
|
|
|
||
|
|
## Base Constructor Surface
|
||
|
|
|
||
|
|
### `0008:ba00` `entity_dispatch_entry_init`
|
||
|
|
|
||
|
|
Current best read:
|
||
|
|
|
||
|
|
- optional allocate/init path for a `0x32`-byte base object
|
||
|
|
- stamps base vtable/list-link state using `0x3b06`, `0x2d10`, and `0x3afe`
|
||
|
|
- zeroes core state fields
|
||
|
|
- seeds the group/layer byte through `entity_set_group_id`
|
||
|
|
|
||
|
|
This is the strongest current candidate for the base constructor-style init method.
|
||
|
|
|
||
|
|
### Derived constructor variants
|
||
|
|
|
||
|
|
#### `0008:cefb` `entity_dispatch_entry_ctor_vtbl_3ad2`
|
||
|
|
|
||
|
|
- allocates if null
|
||
|
|
- reinitializes through `entity_dispatch_entry_init`
|
||
|
|
- sets vtable `0x3ad2`
|
||
|
|
- sets flag `0x100` at `+0x16`
|
||
|
|
- zeroes extension words `+0x32/+0x34`
|
||
|
|
|
||
|
|
#### `0008:d214` `entity_dispatch_entry_ctor_vtbl_3aa6`
|
||
|
|
|
||
|
|
- allocates `0x40` bytes if null
|
||
|
|
- reuses `0008:cefb`
|
||
|
|
- sets vtable `0x3aa6`
|
||
|
|
- sets flag `0x200` at `+0x16`
|
||
|
|
- zeroes fields `+0x38..+0x3e`
|
||
|
|
|
||
|
|
#### Related alloc/init helpers
|
||
|
|
|
||
|
|
- `0004:ea00 entity_dispatch_entry_alloc_type_0f5e`
|
||
|
|
- `0004:eb1f entity_dispatch_entry_ctor_0f3a_with_cache_reset`
|
||
|
|
|
||
|
|
These look more like subtype-specific factory/create helpers than pure base constructors, but they still belong in the family map.
|
||
|
|
|
||
|
|
## Destroy / Release Surface
|
||
|
|
|
||
|
|
### Base-owned word-list destruction
|
||
|
|
|
||
|
|
#### `0008:dbec` `entity_word_list_destroy`
|
||
|
|
|
||
|
|
- resets vtable to `0x2d10`
|
||
|
|
- frees list storage if present
|
||
|
|
- optionally frees object when destroy flag bit `1` is set
|
||
|
|
|
||
|
|
This is the clearest current destructor-style path on the base object.
|
||
|
|
|
||
|
|
### Runtime-state release
|
||
|
|
|
||
|
|
#### `000d:8078` `entity_dispatch_entry_release_runtime_state`
|
||
|
|
|
||
|
|
- frees paired owned buffers
|
||
|
|
- updates shared hold/owner propagation through `g_active_dispatch_entry_farptr`
|
||
|
|
- destroys embedded word-list members
|
||
|
|
|
||
|
|
This reads as the release/destructor path for the runtime-state derived family rather than for the whole base type.
|
||
|
|
|
||
|
|
## Current Base Layout
|
||
|
|
|
||
|
|
This table is a working layout, not a finished header.
|
||
|
|
|
||
|
|
| Offset | Current name | Confidence | Current meaning |
|
||
|
|
|---|---|---|---|
|
||
|
|
| `+0x00` | `type_or_kind` | Medium | Constructor/factory helpers stamp type words such as `0x0f3a`, `0x0f5e`, or `0x051e` here in some subfamilies. Base-vtable interpretation remains separate. |
|
||
|
|
| `+0x02` | `slot_index_or_count` | Medium | Used as entry slot/index in several wrappers; also used as count in the base word-list family, so exact role may vary by subtype or overlay. |
|
||
|
|
| `+0x04` | `source_type` | High | Written by `entity_set_source_type`. |
|
||
|
|
| `+0x06` | `event_type_or_list_ptr_lo` | Medium | Written by `entity_set_event_type_checked`, but also participates in word-list storage in the list-owning variant. This is likely one of the current overlay collisions to resolve later. |
|
||
|
|
| `+0x08` | `group_id_byte` | High | Low 5-bit group/layer value managed by `entity_set_group_id`. |
|
||
|
|
| `+0x0a/+0x0c/+0x0e/+0x10` | `link_or_state_words` | High | Cleared by `entity_dispatch_entry_unlink`; belong to link/extent/target/reset state. |
|
||
|
|
| `+0x12/+0x14` | `target_farptr` | High | Managed by `entity_flag20_*_target` helpers. |
|
||
|
|
| `+0x16` | `flags1` | High | Holds bits `0x10`, `0x20`, `0x100`, `0x200`, `0x4000`, and other subtype/state gates. |
|
||
|
|
| `+0x18` | `flags2` | High | Holds bits `0x40`, `0x80`, `0x100`, `0x400`, `0x1000`; used by unlink, periodic, and refresh paths. |
|
||
|
|
| `+0x1e/+0x28` | `embedded_dispatch_or_word_list_members` | Medium | Many callsites treat these as subobject or vtable-dispatch bases. Exact split still needs a dedicated subobject note. |
|
||
|
|
| `+0x24/+0x26`, `+0x2e/+0x30` | `optional_member_ptrs` | Medium | Checked before freeing both embedded word-list members. |
|
||
|
|
| `+0x32/+0x34` | `extension_words_a` | High | Zeroed by the `0x3ad2` constructor variant; also used by later runtime/VM helper flows. |
|
||
|
|
| `+0x36/+0x38/+0x3a` | `period_or_schedule_words` | Medium | Written by `entity_set_update_period_and_reschedule`; clearly timing-related in the periodic variant. |
|
||
|
|
| `+0x3c/+0x3e` | `accumulator_words` | High | Used by `entity_periodic_accumulate_and_dispatch`. |
|
||
|
|
| `+0x40` | `hold_token` | High | Shared/borrowed hold byte in startup/display and runtime-state families. |
|
||
|
|
| `+0x41/+0x42/+0x44` | `runtime_state_flags` | High | Initialized by `entity_dispatch_entry_init_runtime_state`. |
|
||
|
|
| `+0x46/+0x48` | `owned_buffer_a` | High | Runtime-state owned work/palette-like buffer. |
|
||
|
|
| `+0x4a/+0x4c` | `owned_buffer_b` | High | Second runtime-state owned buffer. |
|
||
|
|
| `+0x49` | `file_family_selector` | High for the seg126 subtype | Local selector state in startup/display transition family. Likely subtype-specific, not general base meaning. |
|
||
|
|
| `+0x5b` | `state_flags` | High for the seg126 subtype | State-machine bits in the `000c` startup/display lane. Likely subtype-specific overlay. |
|
||
|
|
| `+0x520` | `selected_resource` | Medium | Loaded file/resource object in the transition-file-family subtype. |
|
||
|
|
|
||
|
|
## Important Layout Caveat
|
||
|
|
|
||
|
|
This family is almost certainly not one flat struct with universally stable semantics at every offset. Current evidence already shows subtype overlays:
|
||
|
|
|
||
|
|
- base scheduler/dispatch-entry state
|
||
|
|
- word-list-owning variants
|
||
|
|
- periodic/timer variants
|
||
|
|
- startup/display transition variants
|
||
|
|
- runtime-state/palette-backed variants
|
||
|
|
|
||
|
|
So the safest future Ghidra modeling strategy is:
|
||
|
|
|
||
|
|
1. create a minimal `EntityDispatchEntryBase`
|
||
|
|
2. create derived or overlay structs for subtype-specific tails
|
||
|
|
3. avoid prematurely forcing every offset into one monolithic universal class layout
|
||
|
|
|
||
|
|
## Candidate Method Map
|
||
|
|
|
||
|
|
### Strong base methods
|
||
|
|
|
||
|
|
| Address | Current function | Candidate method role |
|
||
|
|
|---|---|---|
|
||
|
|
| `0008:ba00` | `entity_dispatch_entry_init` | `InitBase()` |
|
||
|
|
| `0008:bbb6` | `entity_set_source_type` | `SetSourceType()` |
|
||
|
|
| `0008:bc27` | `entity_set_event_type_checked` | `SetEventTypeChecked()` |
|
||
|
|
| `0008:bca8` | `entity_set_group_id` | `SetGroupId()` |
|
||
|
|
| `0008:bd53` | `entity_dispatch_entry_unlink` | `Unlink()` |
|
||
|
|
| `0008:be05` | `entity_increment_group_id` | `IncrementGroupId()` |
|
||
|
|
| `0008:c01d` | `entity_refresh_dispatch_state` | `RefreshDispatchState()` |
|
||
|
|
| `0008:bfb2` | `entity_clear_status_bits_from_flags` | `ClearStatusBitsFromFlags()` |
|
||
|
|
| `0008:bf8e` | `entity_call_update_vfunc14` | `CallUpdateSlot14()` |
|
||
|
|
| `0008:beee` | `entity_run_flagged_handlers` | `RunFlaggedHandlers()` |
|
||
|
|
|
||
|
|
### Pair/link/target helpers
|
||
|
|
|
||
|
|
| Address | Current function | Candidate method role |
|
||
|
|
|---|---|---|
|
||
|
|
| `0008:c7f1` | `entity_pair_update_link_slot_a` | `UpdateLinkSlotA()` |
|
||
|
|
| `0008:c890` | `entity_pair_update_link_slot_b` | `UpdateLinkSlotB()` |
|
||
|
|
| `0008:c92f` | `entity_pair_sync_a` | `PairSyncA()` |
|
||
|
|
| `0008:ca18` | `entity_pair_sync_b` | `PairSyncB()` |
|
||
|
|
| `0008:c9ee` | `entity_pair_mark_and_sync_a` | `MarkAndPairSyncA()` |
|
||
|
|
| `0008:cad7` | `entity_pair_mark_and_sync_b` | `MarkAndPairSyncB()` |
|
||
|
|
| `0008:cb2c` | `entity_flag20_clear_and_update_target` | `ClearFlag20AndUpdateTarget()` |
|
||
|
|
| `0008:cb5c` | `entity_flag20_set_and_init_target` | `SetFlag20AndInitTarget()` |
|
||
|
|
|
||
|
|
### Periodic/timed subtype methods
|
||
|
|
|
||
|
|
| Address | Current function | Candidate method role |
|
||
|
|
|---|---|---|
|
||
|
|
| `0008:cefb` | `entity_dispatch_entry_ctor_vtbl_3ad2` | `ConstructVtable3AD2()` |
|
||
|
|
| `0008:d214` | `entity_dispatch_entry_ctor_vtbl_3aa6` | `ConstructVtable3AA6()` |
|
||
|
|
| `0008:d313` | `entity_periodic_accumulate_and_dispatch` | `TickPeriodic()` |
|
||
|
|
| `0008:d3e6` | `entity_set_flag2000_and_update_active_counters` | `EnableActiveCounters()` |
|
||
|
|
| `0008:d433` | `entity_clear_flag2000_and_update_active_counters` | `DisableActiveCounters()` |
|
||
|
|
| `0008:d27e` | `entity_set_update_period_and_reschedule` | `SetUpdatePeriodAndReschedule()` |
|
||
|
|
|
||
|
|
### Word-list-owning subtype methods
|
||
|
|
|
||
|
|
| Address | Current function | Candidate method role |
|
||
|
|
|---|---|---|
|
||
|
|
| `0008:da00` | `entity_word_list_set_0408_terminated` | `SetWordList0408Terminated()` |
|
||
|
|
| `0008:dba3` | `entity_word_list_free_existing` | `FreeWordList()` |
|
||
|
|
| `0008:dbec` | `entity_word_list_destroy` | `Destroy()` |
|
||
|
|
| `0008:dc38` | `entity_word_list_ensure_contains` | `EnsureWordListContains()` |
|
||
|
|
| `0008:dcab` | `entity_word_list_append_unique` | `AppendUniqueWord()` |
|
||
|
|
| `0008:ddaf` | `entity_word_list_remove_value` | `RemoveWordValue()` |
|
||
|
|
| `0008:deea` | `entity_word_list_get_at` | `GetWordAt()` |
|
||
|
|
| `0008:df1b` | `entity_word_list_set_at` | `SetWordAt()` |
|
||
|
|
| `0008:dfa1` | `entity_word_list_find_unflagged_by_id10` | `FindUnflaggedWordById10()` |
|
||
|
|
|
||
|
|
### Runtime-state subtype methods
|
||
|
|
|
||
|
|
| Address | Current function | Candidate method role |
|
||
|
|
|---|---|---|
|
||
|
|
| `000d:7e00` | `entity_dispatch_entry_init_runtime_state` | `InitRuntimeState()` |
|
||
|
|
| `000d:8078` | `entity_dispatch_entry_release_runtime_state` | `ReleaseRuntimeState()` |
|
||
|
|
|
||
|
|
## Candidate Virtual Surface
|
||
|
|
|
||
|
|
The current evidence does not justify a fully named vtable yet, but some slot use is already real:
|
||
|
|
|
||
|
|
- `+0x14` = update callback slot used by `entity_call_update_vfunc14`
|
||
|
|
- `+0x28` = callback slot used by the periodic and proximity-style dispatch helpers
|
||
|
|
- embedded subobject/member surfaces at `+0x1e` and `+0x28` are also dispatched through helper wrappers in `far-call-targets.md`
|
||
|
|
|
||
|
|
Recommended future vtable note shape:
|
||
|
|
|
||
|
|
| Slot offset | Current best role | Evidence quality |
|
||
|
|
|---|---|---|
|
||
|
|
| `+0x14` | update/refresh callback | High |
|
||
|
|
| `+0x28` | periodic/dispatch callback | High |
|
||
|
|
| others | unknown/default stubs | Low |
|
||
|
|
|
||
|
|
## Safe Future Ghidra Modeling Steps
|
||
|
|
|
||
|
|
When manual class work starts, the safest order for this family is:
|
||
|
|
|
||
|
|
1. create class namespace `EntityDispatchEntry`
|
||
|
|
2. move only the strong base methods first
|
||
|
|
3. create minimal `EntityDispatchEntryBase` struct with the stable fields through `+0x18`
|
||
|
|
4. create subtype overlay structs for word-list, timed, and runtime-state tails
|
||
|
|
5. create a small provisional vtable for only the verified slots
|
||
|
|
|
||
|
|
Do not start by forcing one complete 0x520-byte monolithic class.
|
||
|
|
|
||
|
|
## Questions To Close Later
|
||
|
|
|
||
|
|
- whether `+0x00` should be modeled as a literal `kind` field in all variants or only in some factory-built subtypes
|
||
|
|
- exact ownership split between the base object and the embedded surfaces at `+0x1e` and `+0x28`
|
||
|
|
- whether the seg126 startup/display subtype is truly part of the same inheritance family or only shares a lower-level dispatch-entry substrate
|
||
|
|
- final base-size versus subtype-size boundaries once class namespaces exist in Ghidra
|
||
|
|
|
||
|
|
## Immediate Next Documentation Value
|
||
|
|
|
||
|
|
The next best companion note after this one is a slot-focused `SpriteNode` virtual table note, because that gives a second family with a cleaner explicit virtual surface and helps calibrate how aggressive the first Ghidra class conversion should be.
|