- 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.
20 KiB
Crusader: No Remorse — Raw Import Porting Progress & Gameplay Batches
This file covers the raw CRUSADER-RAW.EXE porting batches: seg091 RNG helpers, the 0x4588 runtime callback lifecycle batches, and raw 0007 gameplay analysis batches.
Raw seg091 Boundary Recovery (init/context + RNG helpers)
Conservative PyGhidra boundary repair created the missing seg091 functions in CRUSADER-RAW.EXE:
000a:44fd=seg091_func_00fd, body000a:44fd-000a:454c000a:454d=seg091_func_014d, body000a:454d-000a:45fd000a:48a0=rng_advance_state, body000a:48a0-000a:48e2000a:48ff=rng_next_modulo, body000a:48ff-000a:4912
Additional adjacent helper identified directly in the raw import:
000a:48e3=rng_set_seed
Verified behavior:
rng_set_seedwrites the 32-bit RNG seed/state pair at0x4584:0x4586and forces the low word odd.rng_advance_stateupdates the same 32-bit state with a simple multiply/add step.rng_next_moduloadvances the RNG state and returns the result modulo the requested bound, or0when the bound is zero.seg091_func_00fdshares runtime flag0x44a4withruntime_init_or_abort; if the flag is clear it sets it and dispatches through an unresolved far thunk.seg091_func_014dshares flag0x44a4; it checks an optional long argument against the global context/cookie at0x45a6, zeroes the pointed byte when the argument is null, then dispatches through an unresolved far thunk.
Raw 0x4588 Runtime Callback Lifecycle Batch
New conservative runtime-callback lifecycle renames (direct analysis):
000a:4913=runtime_callback_object_init_once000a:4a56=runtime_callback_object_teardown_once0009:b1c3=runtime_callback_object_phase_finalize
Boundary repair applied with MCP edit-plan API:
- Rebuilt
000a:b988assprite_node_get_or_traversewith full body000a:b988-000a:bab5.
Verified callback-object behavior:
runtime_callback_object_init_oncesets one-time guard0x4594, snapshots state words (0x458c/0x4590) viavideo_bios_state_snapshot, installs the object FAR pointer at0x4588, and ensures fallback buffer allocation at0x45a6.runtime_callback_object_teardown_oncesets one-time guard0x4595, clears0x4588, conditionally emits vtable+0x0ccallback when current/previous state differ, then calls vtable+0x04release path.runtime_callback_object_phase_finalizeinvokes vtable+0x08twice and sweeps table entries viaallocator_head_finalize_sweep.- Large caller
FUN_000d_9afdcontains both additional vtable+0x0ccallsites (000d:9d5eand000d:a3b7) and remains the best next target for concrete subsystem naming.
Raw 0x4588 Follow-up Batch (allocator/video helper clarification)
New conservative helper renames:
0009:a961=allocator_head_finalize_sweep000a:4a1f=video_bios_state_snapshot
Verified behavior:
allocator_head_finalize_sweepperforms per-head chain compaction/finalize work over allocator table entries used byruntime_callback_object_phase_finalize.video_bios_state_snapshotexecutes BIOS video interrupts (INT 10hwithAX=4F03andAX=1130,BH=3) and returns packed state inDX:AX; callers store/compare this pair around callback emissions.
Raw 0x4588 Follow-up Batch 2 (cleanup + mode-state wrapper)
New conservative structural renames:
000a:4972=video_mode_set_and_record_state000d:9afd=entity_cleanup_resources_and_dispatch
Verified behavior:
video_mode_set_and_record_statestores requested mode/state to0x4590, handles VBE-style mode values (0x101/0x103/0x105) via helper checks, and falls back toINT 10hmode path for other values.entity_cleanup_resources_and_dispatchis a large teardown/finalize path for an entity-like object: it clears flags, frees multiple owned buffers/palette handles, performs conditional callback dispatch through0x4588vtable+0x0c, then destroys object word-list structures.
Raw 0x4588 Follow-up Batch 3 (cleanup-callee helper classification)
New conservative helper renames:
0009:7853=palette_buffer_alloc_and_init_2560009:1c3a=file_handle_alloc_init_and_open0009:1d6a=file_handle_open_with_mode0009:8d7b=surface_release_internal0009:8e0a=surface_release_and_maybe_free000d:9231=sprite_redraw_global_if_active
Verified behavior:
palette_buffer_alloc_and_init_256ensures a caller-provided far buffer exists, allocates/initializes a0x100-entry palette/work block, and fills it from static table data.file_handle_alloc_init_and_openallocates a handle structure on demand, seeds sentinels, then delegates tofile_handle_open_with_mode.file_handle_open_with_modeperforms path/open initialization with optional pre-delete behavior and stores DOS open result metadata into the handle structure.surface_release_and_maybe_freewrapssurface_release_internaland conditionally frees memory when(flags & 1) != 0.sprite_redraw_global_if_activeredraws the global sprite/object pointer at0x4f38only when the global gate byte0x68e5is enabled.
Raw 0x4588 Follow-up Batch 4 (function-object recovery around 000d:7e00)
Missing function objects recovered:
000d:7e00-000d:8077created and renamed toentity_dispatch_entry_init_runtime_state000d:8078-000d:819frenamed toentity_dispatch_entry_release_runtime_state0003:a880-0003:a896created asFUN_0003_a880(arithmetic helper)0003:b8e2-0003:bb39created and renamed tofar_buffer_alloc_with_mode_flags
Verified behavior:
entity_dispatch_entry_init_runtime_stateinitializes runtime fields (+0x41/+0x42/+0x44), clears and allocates paired work/palette buffers (+0x46/+0x48and+0x4a/+0x4c), applies event/setup calls through seg061 helpers, then finalizes activation.entity_dispatch_entry_release_runtime_statefrees the same paired buffers, propagates active-state changes via global0x6828, and destroys embedded word-list members.far_buffer_alloc_with_mode_flagsis a low-level far-buffer utility that allocates/reuses a destination pointer and dispatches mode-dependent copy/fill behavior via an internal flag table.
Raw 0x4588 Follow-up Batch 5 (seg061/064/076 helper stabilization)
New conservative helper renames:
0009:6ec7=vga_palette_read0008:d3ba=timer_entity_enable_wrapper
Additional evidence-preserving decompiler comments added on: 0008:eb43, 0008:ebe7, 0008:eac8, 0008:ec23.
Verified behavior:
vga_palette_readmirrorsvga_palette_writeand reads DAC entries through ports0x3c7/0x3c9into a far palette buffer.timer_entity_enable_wrapperis a thin forwarder totimer_entity_enableand is widely used in lifecycle/setup paths.- The seg064 gate helpers (
0008:eb43/0008:ebe7/0008:ec23) control one-shot global flag transitions at0x3b72/0x3b73, then dispatch via unresolved thunk paths.
Callback callsite clarification:
entity_cleanup_resources_and_dispatchvtable+0x0ccall at000d:9d5epasses object fields+0x12d/+0x12f.- Matching vtable
+0x0ccall at000d:a3b7passes object fields+0x74f/+0x751.
Raw 0x4588 Follow-up Batch 6 (constructor lane naming + callback globals)
New conservative helper renames:
0008:d27e=entity_set_update_period_and_reschedule0009:7905=palette_buffer_alloc_copy_from_source
Verified behavior:
entity_set_update_period_and_reschedulestores timing/update-period fields (+0x36/+0x38/+0x3a), clears deferred fields (+0x3c/+0x3e), then triggers timer recompute/reschedule helpers.palette_buffer_alloc_copy_from_sourceallocates/replaces destination palette buffer metadata and copies RGB triplets from a source far pointer (entry_count * 3bytes).
Global data labels promoted:
g_active_dispatch_entry_farptrat0x6828- callback-state/object globals at
0x4588/0x458c/0x4590/0x4594/0x4595/0x45a6 - dispatch callback-table pointer at
0x39ca
Raw 0007 Gameplay Helper Batch (entity/tile aux state)
New conservative gameplay-side helper renames (direct analysis from field writes and call structure):
0007:85f6=entity_sync_tile_aux_state0007:8865=entity_sync_tile_aux_if_linked0007:8709=entity_mark_dirty_and_sync_tile_aux
Verified behavior:
entity_sync_tile_aux_statereads entity tile index at+0x4, toggles bit0x04in tile record+0x59based on entity byte+0x54, and copies entity word+0x55into tile record+0x0d.entity_sync_tile_aux_if_linkedonly performs the sync when entity link/pointer+0x50/+0x52is non-null.entity_mark_dirty_and_sync_tile_auxcalls the linked-sync helper, sets entity flag bit0x04at+0x42, then calls through0000:ffffwith args(SS:&tile_index, entity[+0x57])— annotated asentity_tile_type_notify(tile_index_ptr, type_byte).
New entity field found: entity[+0x57] (byte) = entity type/class byte (passed to tile-type notification).
Raw 0007 Gameplay Helper Batch (facing/direction)
New gameplay helper rename:
0007:8bd9=entity_set_facing_direction
Verified behavior:
- Updates entity facing byte
+0x38using incoming direction/event code values (notably0x10/0x11/0x12) with parity-aware adjustment. - Uses entity flags at
+0x4dto select increment/decrement behavior for clockwise/counterclockwise facing updates.
Raw 0007 Gameplay Helper Deep Dive: snap_entity_to_ground
- Function:
0007:2207=snap_entity_to_ground - Caller in gameplay flow:
spawn_entity_checked(0007:22de, call at0007:2366) - Purpose (high confidence): pre-spawn position adjustment for a small allow-list of entity types so they land on valid ground/height context before normal spawn allocation.
Variable replacement pass (applied in Ghidra)
param_1→entity_typelocal_48→snap_entity_type_tablelocal_34→snap_dispatch_seg_tablelocal_20→snap_dispatch_off_tablelocal_c→entity_type_cursorlocal_4→dispatch_index
What the function does structurally
- Copies three 10-entry static tables into stack-local scratch buffers: from
0x2910(off),0x2924(seg),0x2938(entity type IDs). - Performs a linear scan across 10 entity IDs in
snap_entity_type_table. - If
entity_typematches an entry, it calls into the real callee (world_to_screen_coordsat0004:e7bdafter the far-call repair pass) with spawn coordinate-derived arguments. - If no table entry matches, it exits without modifying the request.
Entity ID allow-list
Exactly 10 entity IDs: 0x31c, 0x31f, 0x320, 0x321, 0x322, 0x323, 0x324, 0x325, 0x326, 0x327.
Working pseudocode (behavioral)
void snap_entity_to_ground(entity_type, spawn_x, spawn_y, spawn_layer) {
copy_10_words(local_off_table, DATA_2910);
copy_10_words(local_seg_table, DATA_2924);
copy_10_words(local_type_table, DATA_2938);
for (dispatch_index = 0; dispatch_index < 10; dispatch_index++) {
if (local_type_table[dispatch_index] == entity_type) {
// Repaired: CALLF 0004:e7bd = world_to_screen_coords
call_thunk_with_spawn_context(spawn_x, spawn_y, ...);
}
}
}
Raw 0007 Gameplay Helper Follow-up: AI sweep + checked spawn path
spawn_entity_checked (0007:22de) refinements
- Function signature expanded to 7 arguments:
entity_type,spawn_flags_a,spawn_flags_b,spawn_flags_c,spawn_x,spawn_y,spawn_layer_arg - New comments added:
0007:22f8: allow-list gate for ground-snap mode (0x27fe != 0+ entity IDs0x31c..0x327subset)0007:2366: explicitsnap_entity_to_ground(entity_type, &spawn_x, &spawn_y, &spawn_layer)handoff0007:247e: fallback path that calls coreentity_spawnwith original arguments
entity_ai_update_loop (0007:0fb6) structural recovery
- Reads player entity FAR pointer from global
0x2de4. - Copies player world position fields (
+0x40,+0x42) into globals0x27e7/0x27e9(AI focus position cache). - Iterates entity IDs from
2through255and dispatches per-entity processing through two sequential thunked calls per entity. - After the NE far-call repair pass, the first call at
0007:101cnow decompiles directly asentity_resolve_slot_ptr(0005:0466) instead ofCALLF 0000:ffff.
Repaired call chain helpers now exposed:
0005:42c8=entity_projected_bbox_overlaps_viewport— projects entity slot viaworld_to_screen_coords, derives sprite/flag context, tests against the active viewport rectangle at global0x4014.0005:3cf5=entity_class_has_flag2000— class-word flag test overentity_get_class_word(slot) & 0x2000.0005:ff2d=entity_class_get_flag8— returns bit0x08from entity-class detail byte0x7e1e[type*0x79 + 0x59].0006:1305=entity_class_get_word_02— raw accessor for word+0x02in the0x7e1eclass-detail record.0006:0ca4=entity_class_get_word_0a— raw accessor for word+0x0ain the same class-detail record.0006:11a1=entity_class_clear_flag8_and_dispatch— clears bit0x08in class-detail byte+0x59, then performs follow-up entity/type checks and callback dispatch.
Dispatch call sites annotated:
0007:101c:entity_slot_fetch(SS:&entity_id)— resolves entity slot/pointer from loop ID0007:1093:entity_tick_dispatch(SS:&entity_id, g_0x27c8)— per-entity AI tick with global0x27c8mode/context word
Global 0x27c8 confirmed as the current targeted/current entity handle.
Raw 0007 Gameplay Logic: animation / range / command globals
is_player_in_range (0007:0f79)
- Prototype:
int is_player_in_range(int entity_x, int entity_y) - Reads player world position from
g_player_entity_farptr(0x2de4, fields+0x40(x) and+0x42(y)`). - Computes unsigned delta from AI focus globals
g_ai_focus_pos_x(0x27e7) /g_ai_focus_pos_y(0x27e9). - Returns 1 if player Y delta == 0 AND player X delta < 0xF0 (240 world units), else 0.
entity_animation_frame_update (0007:26e2)
- Prototype:
void entity_animation_frame_update(int *entity_ptr) - Key globals:
g_anim_tick_counter(0x3a00),g_anim_tick_overdrive_flag(0x3a02),g_speed_double_flag(0x27fd). - Entity struct fields confirmed:
[0x1b](byte+0x36) = frame_min;[0x1c](byte+0x38) = frame_max;[0x1d](byte+0x3a) = current_frame[0x1e](byte+0x3c) = loop_flag (0 = animation disabled)[0x1f](byte+0x3e) = reverse_direction_flag / double-speed flag+0x3f(word) = completion handle/sentinel (-1= none,0x2802= player entity)+0x00(far ptr) = vtable pointer
Disassembly comments added:
0007:27dc:entity_completion_callback(handle)— fires when loop wraps; skips player handle0007:27fd: vtable indirectentity->vtable[+8](entity, 0, 0)—on_loop_completevirtual method0007:281e:notify_frame_progress(handle, current_frame)— per-frame notification0007:2851:entity_sprite_advance(entity_far_ptr, advance_amount, 0)
entity_command_dispatch (0007:0990)
- Prototype:
void entity_command_dispatch(int entity_handle, int target_seg, int command_type, byte absolute_pos_flag) - When
absolute_pos_flag == 0: computes player-relative delta usingg_player_entity_farptrand stores result intog_player_delta_x(0x27f5) andg_player_delta_y(0x27f7). - Clears cached origin globals
g_cmd_effect_origin_x(0x27f1) andg_cmd_effect_origin_y(0x27f3) after use.
Enemy spawn helper cluster
Existing raw names align with prior standalone seg001 notes:
0007:505d=map_find_spawn_point(seg001 + 0x6aed)0007:5259=enemy_spawn_with_target(seg001 + 0x6ce9)0007:5275=enemy_spawn_no_target(seg001 + 0x6d05)0007:5291=enemy_spawn_at_position(seg001 + 0x6d21)
Global map additions (renamed in Ghidra)
| Address | Name | Evidence |
|---|---|---|
0x27c8 |
g_current_entity_handle |
Compared directly by entity_is_type_match; captured by entity_ai_update_loop, map_find_spawn_point, and enemy_spawn_at_position |
0x2de4 |
g_player_entity_farptr |
FAR ptr to player entity; +0x40/+0x42 are world X/Y |
0x27e7 |
g_ai_focus_pos_x |
Set by entity_ai_update_loop from player entity +0x40 |
0x27e9 |
g_ai_focus_pos_y |
Set by entity_ai_update_loop from player entity +0x42 |
0x27f1 |
g_cmd_effect_origin_x |
Cached effect origin X, cleared after delta in entity_command_dispatch |
0x27f3 |
g_cmd_effect_origin_y |
Cached effect origin Y |
0x27f5 |
g_player_delta_x |
Player X delta from last effect origin |
0x27f7 |
g_player_delta_y |
Player Y delta from last effect origin |
0x27fd |
g_speed_double_flag |
0 = normal, 1 = double speed animation |
0x27fe |
g_ground_snap_mode_flag |
Non-zero = ground-snap prepass active for placements |
0x27d0 |
g_entity_update_max_id |
Max entity ID used by entity_ai_update_loop sweep |
0x3a00 |
g_anim_tick_counter |
Animation tick counter for frame-advance step budget |
0x3a02 |
g_anim_tick_overdrive_flag |
0 = normal, non-zero = force max frame advance step |
0x2802 |
g_player_entity_handle |
Player entity handle (used as sentinel in animation completion checks) |
seg043 Standalone Boundary Recovery
Direct disassembly of NE_segments/seg043_code_off_75A00_len_336F.bin shows the first non-zero bytes at offset 0x0090; offsets 0x0000..0x008f are all zero.
First three clean 16-bit prologues:
seg043:0090→ raw0007:5a90seg043:017a→ raw0007:5b7aseg043:021c→ raw0007:5c1c
Repair status: applied in CRUSADER-RAW.EXE via the local PyGhidra toolkit:
0007:5a90=seg043_func_0090with body0007:5a90..0007:5b790007:5b7a=entity_set_at_target_update_facingwith body0007:5b7a..0007:5c1b0007:5c1c=seg043_func_021cwith body0007:5c1c..0007:5c80
Verified behavior:
entity_set_at_target_update_facingsets entity+0x3ato 1, callsentity_set_facing_direction, clears class-detail bit0x10at0x7e1e[type*0x79+0x59], then continues into downstream dispatch.0007:5a90allocates an object when the incoming far pointer is null (literal0x98), runs a far setup helper using DS:0x4b48..0x4b4eand the second incoming far pointer, writes0x4c13at the object base, callsentity_set_at_target_update_facing.0007:5c1coptionally calls a virtual method through[object->vtable + 0x4c]whenobject+0x44/+0x46is non-null, then dispatches one or two downstream far helpers usingobject+0x48.
Additional resolved call targets inside the missing seg043 block (from relocation data):
0007:5a8a→entity_set_event_type_checked0007:5a98→FUN_0008_cc01(timer-related flag/event helper)0007:5b36→entity_get_type_word0007:5b44→saveslot_read_entry_flags0007:5bb8→entity_is_type_match0007:5c49→entity_class_get_flag200007:5c8b→mem_alloc_far
Additional Raw 0007 Helpers
Entity Class Flag Helper
0006:02cc=entity_class_get_flag20— Returns((class_detail[type*0x79 + 0x59] & 0x20) >> 5).
Animation Start Frame Helper
0007:71b2=entity_set_anim_start_frame_from_flags— Reads entity+0x4bflags. If bit 1 set: uses type table+0x59 & 4(attack active) to select last frame (+0x39 - 1), zero, or half-frame (+0x39 >> 1). Writes computed value to type table+0x10.
Combat Helper
0007:894b=entity_check_attack_flags_and_dispatch— Guards on entity+0x4bbit 1 AND target object+5bits0x1c. If both set: dispatches thunk attack event.
Vtable Dispatch Helpers
0007:8920=entity_call_vtable_slot0c— Calls(*param_1)[vtable+0xc]().0007:8cb8=entity_call_vtable_slot08— Calls(*param_1)[vtable+0x8]().0007:ccf1=entity_call_vtable_slot28— Calls(*param_1)[vtable+0x28]().
Active Flag / Counter
0007:8854=entity_set_active_flag— Sets entity+0x40 = 1(active); increments global0x2800.
Dispatch Table Lookup
0007:8508=entity_table_lookup_and_dispatch— Searches 1-entry table at0x2b46for(param_3, param_4)key pair; on match, calls the entry's function pointer at[2].