Crusader_Decomp/docs/raw-000a-000d.md
MaddoScientisto 3daffbf113 Add extractor for Crusader's EUSECODE.FLX container
- Implemented a Python script to extract data from the EUSECODE.FLX file format.
- Defined data structures for candidate entries and extracted chunks using dataclasses.
- Added functions to read and parse the FLX table, extract candidate data, and generate human-readable output files.
- Included functionality for analyzing extracted data, including generating summaries, descriptors, and event family reports.
- Implemented utilities for calculating printable ratios, zero ratios, and identifying text-like data.
- Added support for writing various output formats, including JSON, TSV, and Markdown.
2026-03-22 14:27:38 +01:00

23 KiB
Raw Blame History

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:ce1e populates one 0x69ac entry by reserving a free slot, computing the initial bucket through entity_compute_proximity_or_visibility_bucket, storing both current and previous bucket fields, then allocating/linking the backing handle through 000a:5f36.
  • 000d:d409 is a thin wrapper that only calls entity_refresh_recent_proximity_or_visibility_buckets.
  • 000d:cfad is an update-or-allocate helper for (param_1,param_2) pairs: it tries to update an existing tracked entry through 000a:606a, clears dead entries, and falls back to 000d:ce1e allocation when no live match remains.
  • 000d:cec5 is the auxiliary-slot allocator: it prunes invalid entries, uses tracked_entity_bucket_find_free_aux_slot, tags the new entry with byte +0x0a = 1, and seeds its handle via 000a: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 via 000a:5f36 if 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 through 000a:6343.
  • 000d:d10b = tracked_entity_bucket_clear_ref_field — clears only the +0x02 reference 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 +0x0a tag 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_enabled
  • 0005:3b53 = tracked_entity_bucket_alloc_aux_if_enabled
  • 0005:3b72 = tracked_entity_bucket_remove_by_entity_and_ref_if_enabled → forwards into 000d:d086 = tracked_entity_bucket_remove_by_entity_and_ref when 0x45aa is 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:25a9 classifies as an external reset sequence: it calls cache_reset_runtime_state, then tracked_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:eb80 is a conditional tracked-bucket reset/update sequence: when tracked_entity_bucket_system_enabled is set, it calls tracked_entity_handle_mark_remove_all_if_enabled, then tracked_entity_handle_table_clear_and_dispatch, then cache_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 of 0x0c-byte allocator heads
  • 0x879c = active head count / table limit
  • Per-node size/value encoding manipulated through 0009:c628 and 0009:c6ae, which read/write a packed 32-bit quantity split across word + byte + byte fields

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 type 0x68bf through entity_pair_sync_b
  • 000d: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:0x0008 state and entity byte +0x33 is clear, it allocates a scratch palette buffer, constructs a dispatch entry, sets type 0x051e, and initializes runtime state through entity_dispatch_entry_init_runtime_state with entry kind 0x3c.
  • Later in the same helper it constructs a second dispatch entry from the current palette globals at 0x4e4:0x4e6, again sets type 0x051e, and initializes runtime state with entry kind 0x14 and 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 byte 0x58e from the entity when the global display object exists, calls FUN_0006_16e1, clears g_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:4932 and 000a:4936 store the incoming dword into 0x4590 and 0x458c, then 000a:493e stores the incoming FAR object pointer into 0x4588.
  • Clear: 0004:5b8c and 0004:5bbf both clear 0x4588 immediately before the fatal/reporting-style seg091 call through 000a:454d; 0004:5ea7 and 0004:6430 both clear 0x4588 and then immediately run the one-shot teardown path 000a:4a56(1).
  • Teardown: 000a:4a56 checks a once-flag at 0x4595, clears 0x4588 when non-null, optionally performs a vtable +0x0c callback when 0x4590 != 0x458c, then calls vtable slot +0x04 followed by FUN_0009_0d30().
  • Callbacks: 000a:b9e5, 000a:ba66, 000d:9d5e, and 000d:a3b7 all push a two-word value pair followed by the 0x4588 FAR pointer and call vtable slot +0x0c. entity_conditional_render_dispatch calls the same vtable slot with a single literal 0x0101 argument.

Payload pairs from payload sync callsites

  • 000d:9d5e → vtable +0x0c payload from object fields +0x12d/+0x12f
  • 000d:a3b7 → vtable +0x0c payload from object fields +0x74f/+0x751
  • 000a:b9e5, 000a:ba66 → emitting only when the candidate two-word pair differs from the current pair, then mirroring that pair through 000b:1e39 using global sprite/object pointer 0x4f38/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