Crusader_Decomp/docs/far-call-targets.md
MaddoScientisto de42fd1ea1 Add Crusader-specific USECODE data and documentation
- 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.
2026-03-22 17:26:39 +01:00

179 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Far-Call Targets: Top-104 Most-Called Functions
Content extracted from `crusader_decompilation_notes.md`. Named via systematic analysis of 11,692 NE relocation fixup entries — the functions most frequently called through the `CALLF 0x0000:ffff` thunk mechanism.
---
## Tier 1: Ranks 120 (73+ callers)
| Rank | Address | Name | Calls | Description |
|------|---------|------|-------|-------------|
| 1 | `000a:44fd` | `fatal_error_report_fmt_a_and_exit` | 331 | Reentrancy-guarded fatal report helper. Prints the shared banner at `0x44a5`, formats template `0x44cc` with caller words, then exits; earlier `0005:2c68` selector speculation is now rejected. |
| 2 | `0003:ac7e` | `mem_alloc` | 272 | Allocation wrapper → seg082:0000 (`0009:a200`) |
| 3 | `0008:dbec` | `entity_word_list_destroy` | 238 | Frees entity word-list buffer. |
| 4 | `0003:a751` | `mem_free` | 207 | Free wrapper → seg082:007a (`0009:a27a` = `mem_free_checked`) |
| 5 | `0008:bb4f` | `mem_alloc_far` | 174 | Thin wrapper → `mem_alloc` |
| 6 | `0003:a897` | `far_memcpy` | 165 | REP MOVSW + trailing MOVSB |
| 7 | `0005:088f` | `entity_get_type_word` | 130 | Returns type word from table `0x7df9` indexed by slot |
| 8 | `000b:358d` | `sprite_tree_accumulate_pos` | 122 | Recursively sums X/Y offsets (`+0x21/+0x23`) through linked child nodes (`+0x19/+0x1b`), copies 8-byte position block via `far_memcpy` |
| 9 | `0008:ce3d` | `entity_call_two_vtables` | 118 | Calls vtable[`+4`] at entity+`0x1e` and `+0x28` |
| 10 | `0004:26cd` | `nop_void_stub` | 118 | Empty function, returns void |
| 11 | `0008:ce00` | `entity_call_two_vtables_base` | 117 | Calls vtable[0] at entity+`0x1e` and `+0x28` |
| 12 | `0008:bb8c` | `entity_check_flag_0x4000` | 115 | Short-circuits if flag `0x4000` set at `+0x16` |
| 13 | `0008:cda7` | `entity_free_both_word_lists` | 115 | Frees word lists at entity+`0x1e` and `+0x28` if optional pointers at `+0x24/+0x26` and `+0x2e/+0x30` non-null. Both call `entity_word_list_free_existing`. |
| 14 | `0004:26d2` | `nop_void_stub_b` | 111 | Empty function, returns void |
| 15 | `000a:45fe` | `fatal_error_report_fmt_c_and_exit` | 108 | Sibling fatal report helper. Uses the same `0x44a4` guard and banner string, formats static template `0x4506` with caller words, then exits. |
| 16 | `0004:3324` | `nop_return_zero` | 95 | Returns 0 |
| 17 | `0009:c563` | `event_queue_push` | 82 | Circular buffer enqueue. Ring index (`+0xe`) masked `0x3f`, slot masked `0xfff8`. Writes event type word + data byte pair. |
| 18 | `0005:c448` | `list_remove_and_free` | 74 | Unlinks node from linked list via `FUN_0005_c495`, optionally calls `mem_free` if bit 0 of flags set |
| 19 | `000b:2e00` | *(no function in Ghidra)* | 74 | Analysis gap at seg109:0000. Needs manual function creation. |
| 20 | `0009:1f12` | `dos_file_lseek` | 73 | DOS LSEEK (INT 21h AH=42h) wrapper with error reporting to `0x867a` |
---
## Tier 2: Ranks 2140 (5673 callers)
| Rank | Address | Name | Calls | Description |
|------|---------|------|-------|-------------|
| 21 | `0009:3600` | `rotating_buffer_advance` | 73 | Advances 5-slot circular counter at `0x3eb6`, zeros pointer in table at `0x867c`, dispatches via jump table |
| 22 | `0009:943a` | `entity_rect_compare_and_dispatch` | 68 | Compares bounding rectangles of two entities, dispatches based on flag bits `4/2/1` at `+0x16` |
| 23 | `0009:1e61` | `dos_file_close` | 65 | DOS file close (INT 21h), error reporting, sets handle to `-1` |
| 24 | `0005:e252` | *(unnamed — unclear)* | 65 | Copies 11 words from Phar Lap extender area (`FUN_0000_12c6+5`), then calls thunk. Interrupt/trampoline setup? |
| 25 | `0003:dbcc` | `crt_format_string` | 64 | MetaWare High C formatting wrapper. Calls `FUN_0003_bb92` with runtime format dispatch table. |
| 26 | `0007:5a00` | *(no function in Ghidra)* | 64 | High-traffic raw target at `seg043:0000`. Earlier `debris_spawn` / seg001 mapping was rejected after checking relocation labels. Still needs manual function creation and direct analysis. |
| 27 | `000a:4742` | `assert_buffer_valid` | 63 | Validates handle: asserts `param_2 == cookie` at `0x45a6` and `param_1 < limit` at `0x87e0` |
| 28 | `0009:9216` | `entity_conditional_render_dispatch` | 63 | Checks entity flag bits 4 and 1 at `+0x16`, dispatches to vtable[`+0xc`] or thunk |
| 29 | `0008:cb2c` | `entity_flag20_clear_and_update_target` | 61 | Clears flag bit `0x20`, writes target `+0x12/+0x14`, calls refresh |
| 30 | `0008:cb5c` | `entity_flag20_set_and_init_target` | 61 | Sets flag bit `0x20`, inits target if zero, calls refresh |
| 31 | `0007:7306` | `entity_create_stack_object` | 58 | Allocates `0xCC` bytes on stack, inits via `object_init_zero_fields` (`0005:c400`), calls thunk |
| 32 | `0007:8709` | `entity_mark_dirty_and_sync_tile_aux` | 58 | Syncs tile aux, sets flag bit `0x04` at `+0x42` |
| 33 | `0007:87c5` | `entity_set_flag20_from_field42` | 58 | Reads entity `+0x42/+0x44`, calls `entity_flag20_set_and_init_target` with those values |
| 34 | `0007:8508` | `entity_table_lookup_and_dispatch` | 58 | Searches table at `0x2b46`, dispatches via indirect jump |
| 35 | `0007:8920` | `entity_call_vtable_slot0c` | 58 | Calls vtable entry at `+0x0c` |
| 36 | `000a:b988` | `sprite_node_get_or_traverse` | 57 | If child pointer at `+0x19/+0x1b` non-null, traverses; otherwise returns leaf value |
| 37 | `0003:a98b` | `crt_signed_div32` | 56 | Entry: adjusts near→far stack, sets `CX=0` (signed quotient), jumps to `crt_div32_impl` |
| 38 | `000a:7b44` | `nop_return_void_a` | 56 | Empty function (default vtable slot?) |
| 39 | `000a:7b49` | `nop_return_void_b` | 56 | Empty function (default vtable slot?) |
| 40 | `000a:7b53` | `nop_return_void_c` | 56 | Empty function (default vtable slot?) |
---
## Supporting Functions Discovered
| Address | Name | Description |
|---------|------|-------------|
| `000b:3a00` | `sprite_tree_sum_x_offset` | Recursive: sums field `+0x21` through child chain `+0x19/+0x1b` |
| `000b:3a35` | `sprite_tree_sum_y_offset` | Recursive: sums field `+0x23` through child chain `+0x19/+0x1b` |
| `0003:a845` | `crt_exit_wrapper` | Calls `crt_exit_impl(param,0,0)` |
| `0003:a7ee` | `crt_exit_impl` | Full C exit: atexit handlers, stdio flush, MetaWare runtime cleanup |
| `0003:a9a8` | `crt_div32_impl` | 32-bit division core. CX flags: bit0=unsigned, bit1=modulo, bit2=negate |
| `0005:c400` | `object_init_zero_fields` | Zeros fields `+0x25`, `+0x29`, `+0x31`, `+0x32` of a struct. Returns pointer. |
| `000a:4440` | `joystick_read_axes_and_buttons` | Reads PC game port `0x201`. Times axis responses, reads button nibble to `0x44a2` |
| `000b:3380` | `sprite_node_is_dirty` | Checks flags at `obj+0x29 & 3 == 1 or 3` → returns bool |
| `000b:33a6` | `sprite_node_mark_dirty` | If not dirty, calls `FUN_000b_3965` with `mode=3` to invalidate |
---
## Tier 3: Ranks 4160 (4256 callers)
| Rank | Address | Name | Calls | Description |
|------|---------|------|-------|-------------|
| 41 | `000a:7b58` | `nop_return_zero_b` | 56 | Returns 0 (default vtable slot) |
| 42 | `000b:3ab2` | `sprite_node_dispatch_event` | 56 | Large event dispatch: checks event type (`2/4/8/0x100`), updates global focus ptr at `[0x4fd0:4fd2]`, dispatches via vtable methods `[+0x14/+0x18/+0x20/+0x24]` by event code. Switch table for 16 event types. |
| 43 | `000a:48ff` | `rng_next_modulo` | 55 | Advances seg091 RNG state and returns the result modulo the requested bound; returns 0 when bound is 0. |
| 44 | `000b:3362` | `sprite_tree_unwind_check` | 55 | Validates `SS == param_2` (stack segment guard), then decrements global counter at `[0x4fd6]` |
| 45 | `000b:40ee` | `sprite_node_update_and_dispatch` | 55 | If `sprite_node_is_dirty` returns false: marks dirty, calcs accumulated bounds via `sprite_tree_get_accumulated_bounds` (`3ed8`), then dispatches via thunk |
| 46 | `000a:7b5f` | `vtable_stub_trampoline` | 55 | Calls through fixup thunk (forwarder to another function) |
| 47 | `000a:7b78` | `nop_return_void_e` | 55 | Empty function (default vtable slot) |
| 48 | `000a:7b7d` | `nop_return_void_f` | 55 | Empty function (default vtable slot) |
| 49 | `000a:7b4e` | `nop_return_void_d` | 54 | Empty function (default vtable slot) |
| 50 | `000b:330c` | `sprite_tree_dispatch_wrapper` | 52 | Pure thunk wrapper: calls through fixup |
| 51 | `0009:2034` | `dos_file_seek` | 51 | INT 21h AH=42h (LSEEK). Takes file object ptr, extracts handle at `obj+4`, seeks to offset param. Error reporting to `[0x867a]`. |
| 52 | `0005:0466` | `entity_resolve_slot_ptr` | 50 | *(pre-existing name)* |
| 53 | `0003:a880` | *(no function in Ghidra)* | 49 | Analysis gap in CRT segment |
| 54 | `0006:170c` | `tile_class_get_byte` | 47 | Looks up class data: indexes into table at `[0x7e1e]` by `(*param_1 * 0x79)`, returns byte at offset `+0xc` |
| 55 | `000b:4097` | `sprite_dispatch_with_event` | 45 | Pushes event params + global `[0x49c2:0x49c4]`, calls thunk |
| 56 | `0005:02c1` | `entity_is_type_match` | 43 | Compares `*param_1` against global at `[0x27c8]`, returns 1 if equal, 0 otherwise |
| 57 | `0003:ad75` | *(no function in Ghidra)* | 43 | Analysis gap in CRT segment |
| 58 | `000a:e709` | `render_dispatch_by_flag` | 43 | Dispatches between two thunk paths based on boolean flag at `stack+0x10` |
| 59 | `0003:d0ff` | `crt_sprintf_wrapper` | 42 | Calls `FUN_0003_bb92` (format engine) with rearranged params and string constant at `0x67ac` |
| 60 | `000b:326e` | `sprite_node_destroy` | 42 | Destructor: sets vtable ptr to `0x501a`, clears global `[0x4fd0:4fd2]` if self, releases child nodes, calls `mem_free` via thunk |
---
## Analysis Gaps
| Address | NE Segment | Callers | Notes |
|---------|-----------|---------|-------|
| `000a:44fd` | seg091:00fd | 331 | Fatal report helper now identified; remaining gap is the exact human-readable template text at `0x44cc`/`0x44a5`, not control flow. |
| `000b:2e00` | seg109:0000 | 74 | Start of segment 109. |
| `0007:5a00` | seg043:0000 | 64 | Start of segment 43. Earlier seg001 `debris_spawn` port was rejected; still needs manual function creation and direct analysis. |
| `000a:48ff` | seg091:04ff | 55 | Recovered as `rng_next_modulo`; bounded wrapper around seg091 RNG state advance. |
| `0003:a880` | seg005:0880 | 49 | In CRT segment near `far_memcpy`. |
| `0003:ad75` | seg005:0d75 | 43 | In CRT segment near `mem_alloc`. |
| `000a:454d` | seg091:014d | 32 | Buffer-normalizing fatal report sibling. Copies/clears context through the `0x45a6` global, formats template `0x44e7`, then exits. |
**seg043 reconciliation:**
- The earlier standalone seg001 port hypothesis in this subrange was wrong.
- Relocation data places raw `0007:5a00` at `seg043:0000`, and the named helper at `0007:5b6f` sits at `seg043:016f`.
- Because of that segment placement, standalone seg001 names such as `debris_spawn` (`0x7490`) and `entity_die` (`0x75ff`) should NOT be ported into this raw range.
- `0007:5b6f` no longer exists as a function after the PyGhidra repair pass. Its behavior now lines up with the repaired function `0007:5b7a = entity_set_at_target_update_facing`.
- The currently repaired functions at `0007:5a90` and `0007:5c1c` should keep their positional names until a later pass resolves the thunk-heavy bodies more clearly.
---
## Tier 4: Ranks 6180 (2942 callers)
| Rank | Address | Name | Calls | Description |
|------|---------|------|-------|-------------|
| 61 | `000b:30a5` | `sprite_tree_forward_wrapper` | 42 | Pure thunk forwarder |
| 62 | `0008:bc27` | `entity_set_event_type_checked` | 41 | Sets event code at `+0x06` with range/timer checks |
| 63 | `0008:d214` | `entity_dispatch_entry_ctor_vtbl_3aa6` | 40 | Constructor: alloc `0x40`, vtbl `3AA6`, flag `0x200` |
| 64 | `0005:1565` | `entity_action_by_type_dispatch` | 39 | Checks entity type against whitelist (`0x432,0x5a0,0x1fd,0x1fe,0x8f,0x59f,0x2b3,0x2ca`), dispatches by flags at `[0xc76]` and `[0x85f]` |
| 65 | `0008:4bba` | `channel_slot_enable` | 39 | Sets enable byte=1 in 5-slot table at `0x84ca` (slot * `0xd` stride) |
| 66 | `0009:6f5a` | `vga_palette_write` | 38 | Writes RGB triplets to VGA DAC (port `0x3C8/0x3C9`). Range `param_2..param_3` from palette data at `*param_1` |
| 67 | `0009:8ef6` | `line_draw_dispatch` | 38 | Compares `abs(dx)` vs `abs(dy)` to determine major axis, dispatches to appropriate line draw routine |
| 68 | `000a:7b30` | `nop_return_void_g` | 38 | Empty function (default vtable slot) |
| 69 | `000a:7b3f` | `nop_return_void_h` | 38 | Empty function (default vtable slot) |
| 70 | `0009:6e7f` | `palette_free_if_set` | 35 | Frees existing palette data if ptr non-null, checks alignment |
| 71 | `000a:7b35` | `nop_return_void_i` | 35 | Empty function (default vtable slot) |
| 72 | `0009:c433` | `event_queue_align_index` | 34 | Returns `param_1 & 0xFFF8` — aligns ring index to 8-byte event slot boundary |
| 73 | `0009:2156` | `dos_file_get_size` | 33 | Saves file position, does INT 21h AH=42h AL=02 (seek to end), restores position. Returns file size in DX:AX |
| 74 | `000a:2c41` | `list_iterate_next` | 33 | Linked list iterator: if `*out==0` returns first from `obj+2`; else follows next at `ptr+2/+4`. Returns bool (has more) |
| 75 | `000a:454d` | `seg091_func_014d` | 32 | Recovered boundary. Shares flag `0x44a4`; checks optional long argument against the `0x45a6` cookie/context global. |
| 76 | `000b:2446` | `sprite_clear_redraw_flag` | 31 | Clears flag at `obj+0x17e`, then dispatches via thunk |
| 77 | `0005:1238` | `entity_get_class_word` | 30 | Looks up table at `[0x7e01]` indexed by `*param_1 * 2`, returns word. Sister of `entity_get_type_word` (which uses `[0x7df9]`) |
| 78 | `000b:1446` | `display_null_check_dispatch` | 30 | Null-checks far ptr params, dispatches to different thunks based on result |
| 79 | `000d:85da` | `vga_palette_set_all_black` | 29 | Sets byte at `global_obj[0x6828]+0x40 = 1` if global non-null, then calls thunk. *(Note: earlier name `map_object_set_dirty_flag` was incorrect; corrected in seg137 analysis.)* |
| 80 | `0005:1511` | `entity_destroy_trampoline` | 29 | Pure thunk forwarder to entity destruction |
---
## Tier 5: Ranks 81104 (2429 callers)
| Rank | Address | Name | Calls | Description |
|------|---------|------|-------|-------------|
| 81 | `0009:1c00` | `dos_file_handle_init` | 29 | Inits 6-byte file handle struct: dword=0, word+4=`0xFFFF` (invalid). Aborts on null ptr |
| 82 | `0008:75f3` | `entity_get_ptr` | 29 | Looks up entity far ptr from table at `DS:0x39b0`, indexed by `id*4` |
| 83 | `0006:0208` | `entity_class_get_flag4` | 29 | Returns bit 2 of classinfo byte at `[0x7e1e]+*p1*0x79+0x13` → 0 or 1 |
| 84 | `000a:30d7` | `list_node_set_if_context` | 29 | Sets node fields `+2/+4` if params match context globals at `0x45a6/0x45a8` |
| 85 | `0009:c45f` | `object_init_and_get_next` | 29 | Calls `object_init_zero_fields` then returns `*(result+2)` — init+accessor combo |
| 86 | `0004:d7a0` | `object_deref_get_word4` | 28 | Dereferences far ptr chain: returns word at `*(*(param_1)+4)` |
| 87 | `000a:5276` | `debug_check_flag_45aa` | 28 | If byte at `DS:0x45aa` non-zero, calls thunk (diagnostic/assert check) |
| 88 | `0003:d94f` | `far_memset` | 28 | Wrapper reordering params for CRT memset impl at `0003:d92b` (odd-aligned, word-fill loop) |
| 89 | `000a:7b3a` | `nop_return_void_j` | 28 | Empty function (default vtable slot) |
| 90 | `0008:ca18` | `entity_pair_sync_b` | 27 | Pairwise sync wrapper direction B |
| 91 | `0008:bd20` | `entity_sprite_set_target_pos` | 27 | Sets flag `0x1000`, copies player pos to entity `+0x0a/+0x0c` |
| 92 | `0009:3ceb` | `buffer_release_and_dispatch` | 27 | Frees far ptr at `obj+0x3b` if set, nulls it; conditionally dispatches on bit 0 |
| 93 | `0005:09b4` | `entity_get_flags_byte` | 27 | Reads byte from `[0x7dfd]+id`, conditionally extends with classinfo byte at `[0x7e1e]+id*0x79+0xf` |
| 94 | `0005:0fbb` | `entity_lookup_sprite_word` | 27 | Returns word from `[0x7e05]+*p1*2` — sprite/visual index table |
| 95 | `0008:d27e` | `entity_dispatch_trampoline_b` | 26 | Pure forwarder thunk (CALLF thunk only) |
| 96 | `0005:0376` | `entity_resolve_base_type` | 26 | Walks entity class hierarchy (bit 8 in `[0x7e01]`) via `[0x7ded]`, returns base type from `[0x7df1]` |
| 97 | `000b:2492` | `sprite_redraw_if_needed` | 26 | If redraw flag at `+0x17e` is clear, calls update routine + thunk |
| 98 | `0003:e4d3` | `dos_file_open_wrapper` | 26 | Zeros output byte, delegates to file open impl at `0003:bb92` |
| 99 | `0005:033e` | `entity_resolve_base_parent` | 25 | Same hierarchy walk as `entity_resolve_base_type` but returns parent from `[0x7ded]` |
| 100 | `000a:87fd` | `render_clip_rect_to_viewport` | 25 | Clips 4 rect params to viewport bounds at `[0x4014]`, sets dirty flag at `0x8a16`, increments draw counter at `0x4716` |
| 101 | `0005:3cf5` | `entity_class_has_flag2000` | 25 | Returns `(entity_get_class_word(slot) & 0x2000) != 0` |
| 102 | `0009:80db` | `bbox_overlap_test` | 25 | Boolean rectangle overlap test for two 4-word bbox structs; unlike `bbox_intersect`, it does not write back an intersection |
| 103 | `000d:cc00` | `entity_compute_proximity_or_visibility_bucket` | 25 | Returns `0x40` if entity is null or projected bbox overlaps viewport; otherwise buckets world-distance from current reference entity (`0x7e22`) into `0x32/0x20/0x10/0x08` |
| 104 | `000d:d413` | `entity_refresh_recent_proximity_or_visibility_buckets` | 24 | Recomputes bucket values for the last four active entries in `0x69ac` and notifies backing handles via `000a:6343` when a bucket changes |