- Introduced new file `vm_mask_ladder.tsv` containing detailed mappings for Crusader USECODE VM masks and their associated descriptors. - Added comprehensive documentation in `scummvm-crusader-reference.md` outlining the structure, findings, and implications for reverse-engineering the Crusader engine within ScummVM. - Created `usecode-roundtrip-ir.md` to document the plan for converting Crusader USECODE bytes into a human-readable format, detailing the container layout, event names, and intrinsic tables. - Implemented a PowerShell script `temp_usecode_sample.ps1` for extracting and analyzing USECODE data from the Crusader FLX files, providing insights into class and event structures.
28 KiB
Raw 000a & 000d: Tracked Handles, Cache Manager & Proximity Buckets
Content extracted from crusader_decompilation_notes.md. Covers the 000d proximity/visibility bucket cluster, 000a tracked-handle table, generic cache manager, seg082 allocator, seg137/138 palette helpers, and seg004/seg005 startup paths.
Raw 000d Proximity/Visibility Bucket Cluster
Small conservative rename batch from the 000d:cc00-d413 region.
| Address | Name | Evidence |
|---|---|---|
000d:cc00 |
entity_compute_proximity_or_visibility_bucket |
Returns bucket 0x40 for null or on-screen entities (entity_projected_bbox_overlaps_viewport), else computes a distance bucket from the current reference entity at 0x7e22 with thresholds 0x17d, 0x281, 0x3c1 mapping to 0x32, 0x20, 0x10, 0x08 |
000d:d413 |
entity_refresh_recent_proximity_or_visibility_buckets |
Walks the last four active records in the 0x69ac array, recomputes the same bucket, stores it back to each entry, and calls 000a:6343 when the bucket changes |
000d:cdd0 |
tracked_entity_bucket_prune_invalid_entries |
Walks the 0x69ac array, validates backing handles through 000a:637a, and clears entry handles to 0xffff when the backing object is gone |
000d:cd62 |
tracked_entity_bucket_find_free_main_slot |
Finds the first free entry in the main portion of the 0x69ac array (0 .. count-4) |
000d:cd9a |
tracked_entity_bucket_find_free_aux_slot |
Finds the first free entry in the auxiliary tail portion of the 0x69ac array (count-4 .. count-1) |
Supporting caller notes
000d:ce1epopulates one0x69acentry by reserving a free slot, computing the initial bucket throughentity_compute_proximity_or_visibility_bucket, storing both current and previous bucket fields, then allocating/linking the backing handle through000a:5f36.000d:d409is a thin wrapper that only callsentity_refresh_recent_proximity_or_visibility_buckets.000d:cfadis an update-or-allocate helper for(param_1,param_2)pairs: it tries to update an existing tracked entry through000a:606a, clears dead entries, and falls back to000d:ce1eallocation when no live match remains.000d:cec5is the auxiliary-slot allocator: it prunes invalid entries, usestracked_entity_bucket_find_free_aux_slot, tags the new entry with byte+0x0a = 1, and seeds its handle via000a:5f36(..., flag=1).000a:606a=tracked_entity_bucket_handle_update_or_alloc— updates the backing handle for an existing tracked bucket entry when possible, or falls back to allocation via000a:5f36if the handle has gone stale.000d:d350=tracked_entity_bucket_set_value— finds a tracked(entity_id, entity_ref)entry and pushes a new bucket value into its backing handle through000a:6343.000d:d10b=tracked_entity_bucket_clear_ref_field— clears only the+0x02reference field for all matching entries.000d:d151=tracked_entity_bucket_remove_by_ref— marks matching entries' backing handles for removal and clears the local entry handle/reference fields.000d:d1b1=tracked_entity_bucket_remove_tagged_by_ref— same removal path, but only for entries whose byte+0x0atag is set.
Raw 000a Tracked-Handle Table
The 0x4673 table is the backing handle registry for the 0x69ac tracked-entry bucket subsystem. That client layer sits on top of a separate generic cache manager rooted at 0x4688..0x46b7.
| Address | Name | Evidence |
|---|---|---|
000a:5f02 |
tracked_entity_handle_find_slot |
Linear scan over 12 entries in the 0x4673 table for a matching 32-bit handle id |
000a:602b |
tracked_entity_handle_is_live |
Returns true only when a handle exists in 0x4673 and its flag word at +0x0a does not have bit 0x0002 set |
000a:60eb |
tracked_entity_handle_mark_remove |
Sets bit 0x0002 in the handle-table flag word and dispatches through the unresolved cleanup path |
000a:612e |
tracked_entity_handle_mark_remove_all |
Iterates all 12 handle-table entries and marks each live handle for removal |
000a:6167 |
tracked_entity_handle_alloc_slot |
Allocates a slot in one of two ranges (0..7 or 8..11) depending on the aux flag; when full, wraps in a ring and evicts via tracked_entity_handle_mark_remove before reusing the slot |
000a:6228 |
tracked_entity_handle_prune_removed |
Reaps entries previously marked with bit 0x0002, clears dead slots, and refreshes high-index entries through 000a:6b2d |
000a:63bc |
tracked_entity_handle_find_by_entity |
Finds the first live handle-table entry whose key/entity word at +0x04 matches the requested entity id |
Handle entry layout (stride 0x0c)
| Offset | Field |
|---|---|
+0x00 |
32-bit handle id |
+0x04 |
key/entity id |
+0x06 |
class/group/source-style selector |
+0x08 |
current bucket/value |
+0x0a |
flags (bit0 = aux-slot allocation, bit1 = pending removal) |
Thin public wrappers
| Address | Name |
|---|---|
000a:5276 |
entity_bucket_track_default_main — gated by 0x45aa; creates or refreshes a main-slot tracked handle with bucket 0x40 and selector 0xff |
000a:5294 |
entity_bucket_track_main — same path, but takes the bucket value as an argument for the main-slot range |
000a:52d0 |
entity_bucket_track_default_aux — aux-slot variant with default bucket 0x40 |
000a:52ee |
entity_bucket_track_aux — aux-slot variant with explicit bucket argument |
Raw 000a Generic Cache Manager
Follow-up analysis of 000a:6b2d and the 0x4688..0x46b7 globals shows that this region is a generic cache manager used by the tracked-handle layer, not part of the tracked-entity subsystem itself.
| Address | Name | Evidence |
|---|---|---|
000a:6b2d |
cache_lookup_or_load_entry_by_id |
Fast-paths the last id via 0x46af/0x46b1, otherwise searches 0x469d, evicts older cache slots until there is room under byte budget 0x46a5, allocates a block from the free-list, clears/initializes the payload, records the id, and dispatches through the loader interface at 0x468c |
000a:6a95 |
cache_release_entry_by_slot |
Releases a cached slot by index, clears any client references through 000a:62d8, frees its backing block through cache_free_block_by_slot, and marks the slot id in 0x469d as unused (0xffff) |
000a:6d07 |
cache_alloc_block_for_slot |
Allocates or splits a block from the free-list anchored at 0x4688, tags it with the owning cache slot index, and updates the in-use byte count at 0x46a9 |
000a:6f4d |
cache_free_block_by_slot |
Finds the free-list node for a cache slot, marks it free, subtracts its size from 0x46a9, and coalesces adjacent free blocks |
000a:67d9 |
cache_shutdown |
Tears down the generic cache manager: flushes/reset state, frees slot arrays at 0x4699/0x469d/0x46b3, frees the free-list container at 0x4688, and closes backing state at 0x4691 |
000a:6898 |
cache_set_loader_interface |
Installs the backend loader/callback interface pointer at 0x468c |
Cache globals
| Address | Name | Notes |
|---|---|---|
0x4688 |
free-list/block-list head | Used by cache_alloc_block_for_slot and cache_free_block_by_slot |
0x468c |
cache_loader_interface | Backend callback table; +0x34 = size query, +0x0c = load/bind callback |
0x4695 |
arena base pointer | Base for the raw cache payload arena |
0x4699 |
per-slot payload-pointer table | |
0x469d |
per-slot cached id table | 0xffff = unused |
0x46a5 |
byte budget / arena capacity | |
0x46a9 |
bytes currently in use | |
0x46af/0x46b1 |
fast-path cache | Last requested id and slot index |
0x46b3 |
per-slot block metadata mirror | Used when releasing or refreshing slots |
Follow-up: Cache Init and Runtime State
| Address | Name | Notes |
|---|---|---|
000a:6600 |
cache_init |
Stores slot count in 0x46ad; allocates per-slot payload-pointer table; seeds each slot; queries/derives arena size; allocates arena backing object at 0x4691; allocates per-slot metadata mirrors; initializes free-list head at 0x4688; calls cache_reset_runtime_state |
000a:68aa |
cache_reset_runtime_state |
Shared cache reset/bootstrap helper called from cache_init, cache_shutdown, and external reset paths. Allocates per-slot arena-header nodes, rebinds slot pointers to arena base, clears the cached-id table, seeds the free-list head, and resets 0x46a9 (bytes in use) plus 0x46af (last-id fast path) |
000a:703e |
cache_compact_arena_blocks |
Compacts live cache arena blocks into earlier free holes when allocation would fail, updates per-slot payload pointers, and merges adjacent free-list headers afterward |
Follow-up: Tracked-Handle Table Init/Shutdown
| Address | Name | Notes |
|---|---|---|
000a:5e00 |
tracked_entity_handle_table_init |
If 0x4672 is clear, allocates 0x90 bytes at 0x4673/0x4675, aborts through runtime_init_or_abort on failure, calls 000a:577d and local helper 000a:5e95, then sets 0x4672 = 1 |
000a:5e59 |
tracked_entity_handle_table_shutdown |
Matching teardown for tracked_entity_handle_table_init |
000a:5e95 |
tracked_entity_handle_table_clear_and_dispatch |
When tracked_entity_handle_table_active is set, zeroes the full 0x90-byte handle table at 0x4673, resets adjacent local state at 0x4677/0x4679/0x467b, then dispatches through the remaining thunked follow-up path |
000a:5339 |
tracked_entity_handle_mark_remove_all_if_enabled |
Thin gate wrapper that only forwards to tracked_entity_handle_mark_remove_all when tracked_entity_bucket_system_enabled is set |
Table globals: 0x4672 = tracked_entity_handle_table_active, 0x4673/0x4675 = tracked_entity_handle_table (12 entries × 0x0c = 0x90 bytes).
Follow-up: Tracked Bucket System Init/Shutdown
| Address | Name | Notes |
|---|---|---|
000a:5186 |
tracked_entity_bucket_system_init |
Allocates a rotating buffer via 0009:3600, lazily creates tracked_entity_bucket_backend_object through 0009:5600 when absent, installs that object into cache_loader_interface, allocates the tracked handle table via 000a:5e00, allocates the 32-entry 0x69ac bucket array via 000d:cca3(0x20), then sets tracked_entity_bucket_system_enabled |
000a:538e |
tracked_entity_bucket_system_init_if_configured |
Only calls the init routine when config/feature gate 0x89f4 is non-zero |
000a:5223 |
tracked_entity_bucket_system_shutdown |
Tears down the tracked handle table, frees the 0x69ac bucket array, calls backend-object vtable slot +0x38 with (3, backend_object), clears tracked_entity_bucket_backend_object; called from the wider engine teardown routine at 0004:621b |
System globals: 0x45aa = tracked_entity_bucket_system_enabled, 0x45ab/0x45ad = tracked_entity_bucket_backend_object.
Public thin gate wrappers that feed the 0x69ac tracked-entry layer:
0005:3b34=tracked_entity_bucket_alloc_main_if_enabled0005:3b53=tracked_entity_bucket_alloc_aux_if_enabled0005:3b72=tracked_entity_bucket_remove_by_entity_and_ref_if_enabled→ forwards into000d:d086 = tracked_entity_bucket_remove_by_entity_and_refwhen0x45aais set.
Follow-up: Backend Object Constructor
| Address | Name | Notes |
|---|---|---|
0009:5600 |
cache_backend_object_init |
Allocates a 0x20-byte object when caller passes null; initializes embedded DOS file-handle state via dos_file_handle_init; seeds internal method-table / state fields at object offsets +0x08, +0x0c, +0x10, +0x14, +0x16, +0x18, and +0x1c; dispatches through the object method table during construction; returns the object pointer cached at 0x45ab/0x45ad |
Verified callback roles inside cache_lookup_or_load_entry_by_id:
- backend vtable
+0x34= size query callback for a cache entry id (used before allocation/eviction) - backend vtable
+0x0c= load/bind callback that populates the newly allocated slot buffer for the requested id
Follow-up: External Reset Paths
- The path around
0004:25a9classifies as an external reset sequence: it callscache_reset_runtime_state, thentracked_entity_handle_table_clear_and_dispatch, then continues through additional tracked-entry/cache-side refresh helpers (000d:cd22,000d:44b3,0006:ae66,0006:ae00, etc.). - The path around
0004:eb80is a conditional tracked-bucket reset/update sequence: whentracked_entity_bucket_system_enabledis set, it callstracked_entity_handle_mark_remove_all_if_enabled, thentracked_entity_handle_table_clear_and_dispatch, thencache_compact_arena_blocks, before resuming its outer flow.
Follow-up: Repaired seg004 Reset-Path Function Objects
| Address | Name | Notes |
|---|---|---|
0004:2592 |
runtime_cache_reset_sequence |
Calls 0008:7bfe; calls game_mode_init(*(0x27c4)); calls import-resolved site 0004:25a4, now verified from the separately imported ASYLUM.DLL as ordinal 24 = _ASS_StopAllSFX; then resets cache runtime state, clears tracked handles, refreshes tracked-entry/cache helpers. Known caller: 0004:262d inside the tiny wrapper at 0004:2620, which sets byte +0x40 on the object at 0x6828 before invoking the reset sequence. |
0004:eb1f |
entity_dispatch_entry_ctor_0f3a_with_cache_reset |
Allocates/initializes an entity dispatch entry; stamps entry type 0x0f3a; stores its two word payload fields; runs local setup through embedded helper at 0004:ebf4 (which dispatches entity_dispatch_reset_all(*0x7e22, 0x00f0) and — when the local flag plus global 0x0ee1 allow it — allocates a type 0x0f5e dispatch entry and passes it to entity_pair_sync_b); when tracked_entity_bucket_system_enabled is set, performs the tracked-handle removal / clear / cache-compaction sequence before finalizing through 0009:b1c3 in phase 0. |
0004:ea00 |
entity_dispatch_entry_alloc_type_0f5e |
Reuses the incoming FAR pointer when non-null; otherwise allocates 0x33 bytes through mem_alloc_far; initializes the entry through entity_dispatch_entry_init; stamps the entry type word at +0x00 to 0x0f5e. |
Follow-up: seg082 Allocator Cluster
| Address | Name | Notes |
|---|---|---|
0009:a229 |
(size-only wrapper) | Public size-only wrapper around the seg082 allocator. Lazily initializes the allocator on first use through 0009:bcb9, then calls allocator_try_alloc_from_head_table(size, default_tag, 0xff). |
0009:bcb9 |
(lazy initializer) | One-time lazy initializer. Parses an optional -x tuning value from the PSP command line, clamps derived percentage into 0x14..0x50, seeds local seg082 helpers, then sets init flag 0x4096 = 1. |
0009:b06b |
allocator_try_alloc_from_head_table |
Validates requested size, reserves a temporary work token through 0009:e15f, scans the 0x8724 allocator head table in 0x0c-byte entries via allocator_head_try_alloc_block. On success, commits the result through 0009:e2b4, clears failure flag 0x4098. When a pass does not find a fit, interleaves up to two finalize phases through allocator_phase_finalize_pass(phase) before the final retry. |
0009:a336 |
allocator_head_try_alloc_block |
Per-head first-fit allocator. Normalizes requested size (rounds odd small requests up, page-aligns large non-page-aligned requests), adds 0x0a node header overhead, enforces minimum of 0x10 bytes. Walks the node chain for one allocator head until it finds a free span large enough. On success, unlinks the chosen free node, either consumes it whole or splits off a remainder when >= 0x10 bytes remain. On failure, returns 0. |
0009:a5d1 |
allocator_head_free_block |
Per-head free paired with allocator_head_try_alloc_block. Rebuilds the node header from a payload pointer (payload - 0x0a), validates the owner/tag word, reinserts the block into one allocator head, and coalesces with adjacent free neighbors when possible. |
0009:b224 |
allocator_free_block_by_ptr |
Converts the payload pointer back through local header helpers, scans the 0x8724 head table for the owning range, dispatches to allocator_head_free_block, and aborts if no owning head is found. Known wrappers 0009:a24f and 0009:a27a are small checked entry points into this path. |
0009:b1c3 |
allocator_phase_finalize_pass |
Accepts phase bytes 0 or 1. Forwards that byte twice to the object rooted at 0x4588 through vtable slot +0x08. Then sweeps the allocator head table at 0x8724 up to the active head count at 0x879c, calling allocator_head_finalize_sweep on each entry. |
0009:af87 |
(free-space probe) | Walks the node chain rooted at 0x8724. For each node, accumulates node_size - 9 into a running total and tracks the largest single free block. Used by cache_init and seg013 path at 0004:833b. |
0009:a961 |
(per-head finalize sweep) | Walks one 0x8724 head's node chain, skips odd-tagged spans, coalesces or rewrites eligible spans, and updates head/back-pointer links when deferred space needs to be merged back into the chain. |
Allocator head table structure:
0x8724= array of0x0c-byte allocator heads0x879c= active head count / table limit- Per-node size/value encoding manipulated through
0009:c628and0009:c6ae, which read/write a packed 32-bit quantity split acrossword + byte + bytefields
Follow-up: seg137 Palette and Dispatch-Entry Helper Family
A coherent palette-write and palette-backed dispatch-entry emission family tied to the same runtime-state constructor lane.
| Address | Name | Evidence |
|---|---|---|
000d:85da |
vga_palette_set_all_black |
Allocates a 0x100-entry palette buffer filled with zero RGB triplets, writes it to VGA, frees the scratch buffer. (Previously mis-named map_object_set_dirty_flag.) |
000d:8653 |
vga_palette_set_all_white |
Same shape as black — all three RGB components initialized to 0x3f, then written through vga_palette_write. |
000d:86cc |
vga_palette_set_all_rgb |
Takes caller-supplied RGB bytes, replicates them across a 0x100-entry palette buffer, writes the result to VGA, frees the scratch palette. |
000d:82ea |
dispatch_entry_create_black_palette_state_active |
Builds a runtime-state dispatch entry of type 0x051e from a black 0x100-entry palette buffer; first sets g_active_dispatch_entry_farptr[+0x40] = 1. |
000d:8a47 |
dispatch_entry_create_black_palette_state |
Same as above without marking the active dispatch entry. |
000d:83be |
dispatch_entry_create_grayscale_palette_state_active |
Reads the current VGA palette, normalizes each triplet by copying the first channel across all three RGB bytes, then builds a runtime-state dispatch entry from that grayscale palette while marking the active dispatch entry. |
000d:875d |
dispatch_entry_create_solid_palette_state_active |
Validates 0..0x3f RGB inputs, fills a scratch 0x100-entry palette buffer with that solid color, builds the same 0x051e runtime-state dispatch entry, marks the active entry. |
000d:88b2 |
dispatch_entry_create_solid_palette_state |
Same as above without marking the active dispatch entry. |
Additional caller-side comments (not renamed) added on:
000d:84f4— current-palette dispatch entry paired with a second object of type0x68bfthroughentity_pair_sync_b000d:89c6— parameterized current-palette runtime-state wrapper with active-state flags
Follow-up: seg138 Caller-Side Dispatch-Entry Emission Helper
FUN_000d_938c (000d:938c-000d:9583) — a real caller-side helper with an evidence-preserving decompiler comment added in Ghidra instead of forcing a speculative rename.
Current verified behavior:
- When the mode/global gate is not already in the
0x13:0x0008state and entity byte+0x33is clear, it allocates a scratch palette buffer, constructs a dispatch entry, sets type0x051e, and initializes runtime state throughentity_dispatch_entry_init_runtime_statewith entry kind0x3c. - Later in the same helper it constructs a second dispatch entry from the current palette globals at
0x4e4:0x4e6, again sets type0x051e, and initializes runtime state with entry kind0x14and active-state parameters(1,0,1). - Both created entries are polled until their runtime flag word clears bit
0x0002, after which the helper redraws the global sprite path, syncs display-state byte0x58efrom the entity when the global display object exists, callsFUN_0006_16e1, clearsg_active_dispatch_entry_farptr[+0x40], and finally dispatches through the input object's vtable slot+0x08.
Follow-up: seg005 Startup/Display Orchestration
| Address | Name | Notes |
|---|---|---|
0004:60c0 |
FUN_0004_60c0 |
Startup/display orchestration path: broad setup calls, reads the live VGA palette, validates a caller-provided object through vtable slot +0x0c, drives the sprite/object lane through 0x4f38, runs the seg137 palette and dispatch-entry helper family, creates the default active dispatch entry through active_dispatch_entry_create_default, programs mouse interrupt state via seg056 INT 33h wrappers, then hands off into the still-unrecovered 0004:1e00 routine. |
000d:7600 |
active_dispatch_entry_mark_enabled |
Marks the active dispatch entry enabled |
000d:760e |
active_dispatch_entry_mark_disabled |
Marks the active dispatch entry disabled |
000d:761c |
active_dispatch_entry_create_default |
Creates the default active dispatch entry |
Follow-up: 0x4588 Object-Role Evidence
The 0x4588 FAR object is a runtime-installed callback/dispatch object that participates in conditional render or presentation-side flow. It has an explicit install, clear, callback, and teardown lifecycle.
Verified lifecycle
- Install:
000a:4932and000a:4936store the incoming dword into0x4590and0x458c, then000a:493estores the incoming FAR object pointer into0x4588. - Clear:
0004:5b8cand0004:5bbfboth clear0x4588immediately before the fatal/reporting-style seg091 call through000a:454d;0004:5ea7and0004:6430both clear0x4588and then immediately run the one-shot teardown path000a:4a56(1). - Teardown:
000a:4a56checks a once-flag at0x4595, clears0x4588when non-null, optionally performs a vtable+0x0ccallback when0x4590 != 0x458c, then calls vtable slot+0x04followed byFUN_0009_0d30(). - Callbacks:
000a:b9e5,000a:ba66,000d:9d5e, and000d:a3b7all push a two-word value pair followed by the0x4588FAR pointer and call vtable slot+0x0c.entity_conditional_render_dispatchcalls the same vtable slot with a single literal0x0101argument.
Payload pairs from payload sync callsites
000d:9d5e→ vtable+0x0cpayload from object fields+0x12d/+0x12f000d:a3b7→ vtable+0x0cpayload from object fields+0x74f/+0x751000a:b9e5,000a:ba66→ emitting only when the candidate two-word pair differs from the current pair, then mirroring that pair through000b:1e39using global sprite/object pointer0x4f38/0x4f3a
Globals
| Address | Name |
|---|---|
0x4588 |
runtime FAR object pointer (nullable) |
0x458c |
callback sync field (compared against 0x4590 in teardown) |
0x4590 |
paired sync field |
0x4594/0x4595 |
state flags |
0x45a6 |
clock/cookie global used by assert_buffer_valid |
0x39ca |
dispatch callback-table pointer |
0x6828 |
g_active_dispatch_entry_farptr |
Follow-up: VM Owner/Resource Loader and Owner-Loaded Class Validation
The next ScummVM-guided validation step now confirms that the sampled owner-loaded EUSECODE classes are compatible with the ScummVM indexing model even though one header detail remains open.
Sampled class-record findings
- Using the extracted chunks plus the live raw path
000d:44df -> 000d:4c99 -> 000d:7000, the large chunk at table offset0x88behaves as object1. - For representative class bodies, deriving
object_index = (table_offset - 0x80) / 8, thenclass_id = object_index - 2, and then reading object1at4 + 13 * class_idyields the expected names:EVENT,NPCTRIG,SURCAMNS,JELYHACK,REE_BOOT,SURCAMEW, andSFXTRIG. - This is the first direct local confirmation that the owner-loaded records match the ScummVM
object 1name-table plusclassid + 2body lookup at the indexing level.
Header and event-table shape
- The sampled class records do contain a stable 4-byte header field at bytes
8..11. - The observed values are small boundaries:
0x00d4,0x00da, and0x00e6in the current sample set. - Treating that dword directly as the first post-event-table offset makes the layout line up cleanly:
(dword_at_8 - 20) / 6yields valid tables of 32, 33, or 35 slots before inline payload/name data begins. - The region at
class + 0x14is therefore now directly confirmed as repeated 6-byte slots withu16 unknown_word + u32 code_or_payload_fieldlayout. - Representative low-slot examples are
JELYHACKslot1={word=0x002a, dword=0x00000001},SURCAMNSslot1={word=0x0051, dword=0x000000d2},SURCAMEWslot1={word=0x00f7, dword=0x000000d2},EVENTslot10={word=0x1fd6, dword=0x00000001}, andREE_BOOTslots10/15/16={0x034b,1},{0x025c,0x034c},{0x003b,0x05a8}. - The leading event word is still not decoded semantically.
What remains open
- Scanning with the previously noted ScummVM-style
(base_offset + 19) / 6interpretation overruns into inline payload/name bytes on these owner-loaded records, so the local sample set does not support that exact event-count formula as written. - The best current arithmetic fit is now tighter: ScummVM's decremented
base_offsetis also used as the live code-stream base inuc_machine.cpp, so the local owner-loaded records fit best if bytes8..11are the first code-byte offset and event-count derivation is(base_offset - 19) / 6, which is exactly equivalent here to(raw_u32_at_8_11 - 20) / 6. - Current
000dloader evidence does not point to a header rewrite before VM consumption.entity_vm_runtime_init_from_path_if_configured(000d:44df) only builds the external path and creates the runtime,entity_vm_runtime_create(000d:4c99) only installs the helper returned by000d:7000,entity_vm_runtime_owner_resource_create(000d:7000) only allocates the child owner table and fills it through helper vtable+0x0c, andentity_vm_context_create_from_slot_index(000d:46ec) directly reads slot-backed source data from that owner table. No local step is yet verified as rewriting the sampled class headers. entity_vm_runtime_owner_resource_create(000d:7000) still does not expose a direct binary-side class-name lookup or explicitclassid + 2arithmetic. What it does expose is an indexed file-set loader contract: helper-owned count at+0x14, far-pointer table at+0x10, paired per-entry word table at+0x18, vtable+0x04size query, and vtable+0x0cmaterialization of the0x0d-stride owner records later consumed byentity_vm_context_create_from_slot_index.- Safe event-label correlation remains intentionally narrow after this pass. The sampled low slot ids are now concrete, but none of them yet have a verified binary-side behavior match strong enough to promote a ScummVM label like
look,use, orcachein.
Conservative parser rule from this batch
- For current owner-loaded/raw EUSECODE work, keep bytes
8..11raw and derive event count only with(raw_u32_at_8_11 - 20) / 6when divisibility and object-size bounds checks succeed. - Keep the decremented
code_base_minus_one = raw_u32_at_8_11 - 1as a separate code-addressing field rather than collapsing it into the event-count rule. - Preserve the 6-byte event rows and their leading word verbatim until the event-entry word semantics are verified.