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.
This commit is contained in:
MaddoScientisto 2026-03-22 14:27:38 +01:00
commit 3daffbf113
58 changed files with 30295 additions and 2504 deletions

108
docs/phar-lap-extender.md Normal file
View file

@ -0,0 +1,108 @@
# Crusader: No Remorse — Phar Lap DOS Extender Analysis
This file covers the Phar Lap 286 DOS extender code portion of `CRUSADER.EXE` — the outer MZ/P2 stub that bootstraps the NE game image.
## Architecture Notes
### Correction: The Game Ships As A Bound NE Executable
**Important**: The installed copy does **not** contain a separate `.EXP` file. `CRUSADER.EXE` is a bound executable with an outer DOS `MZ` stub and an internal `NE` executable image. The Phar Lap loader/runtime code and the game's real segment layout are both described inside this same file.
The flow is:
1. `entry` → checks DOS version, CPU type
2. `init_dos_extender` → sets up protected mode (VCPI/DPMI)
3. `load_exp_file` → opens the game's `.EXP` file
4. `load_executable_image` → parses P2/MZ headers, creates segments, applies relocations
5. `task_switch_to_child` → transfers control to the actual game code
For the installed retail copy, this means the currently loaded Ghidra program is only one interpretation of `CRUSADER.EXE`. The next import should target the **NE layer of the same file**, not a missing external `.EXP`.
### Segment 1339: Fast Memory Operations
`FUN_1339_02a8` contains an unrolled loop (Duff's device pattern with 57 iterations) — a hand-optimized **fast memory fill/add** routine, typical in DOS game graphics engines.
### EMS Memory (Segment 1677)
The game uses **EMS (Expanded Memory Specification)** via INT 67h for additional memory beyond the 1MB real-mode limit. Functions in segment 1677 manage EMS page frames and handle allocation/deallocation.
## Named Functions
### Entry & Startup
| Address | Name | Description |
|---------|------|-------------|
| `10da:7c40` | `entry` | Program entry point — checks CPU, parses command line, launches game |
| `10da:1816` | `main_init_and_run` | Main initialization — loads child EXP, sets up subsystems, runs game |
| `1760:1432` | `parse_cmdline_and_run` | Parses command-line args and invokes main_init_and_run |
| `1760:42fa` | `init_dos_extender` | Initializes Phar Lap 286 DOS extender (CPU check, VCPI/DPMI setup) |
### Executable Loading
| Address | Name | Description |
|---------|------|-------------|
| `1760:2cdf` | `load_exp_file` | Loads .EXP executable — opens file, reads headers, allocates memory |
| `1760:1dfc` | `load_executable_image` | Parses P2/MZ headers, loads segments, creates LDT entries |
| `1760:24a6` | `apply_relocations` | Applies segment relocations to loaded executable |
| `1760:5eca` | `exec_child_process` | Executes child process with command-line arguments |
| `1760:5fee` | `exec_program_with_args` | Builds command line, locates and executes a program |
| `10da:1f7e` | `load_and_run_child` | Wrapper: loads child EXP and initializes it |
### System Services
| Address | Name | Description |
|---------|------|-------------|
| `10da:2330` | `dos_exit` | Calls INT 21h AH=4Ch (terminate program) |
| `1760:42aa` | `detect_cpu_type` | Detects CPU: 0=8086, 2=286, 3=386+ |
| `1339:04a6` | `dpmi_set_interrupt_vector` | INT 31h — DPMI set interrupt vector |
| `1339:06ca` | `switch_to_real_mode` | Switches CPU from protected to real mode |
| `1339:06f2` | `switch_to_protected_mode` | Switches CPU from real to protected mode |
| `1339:0076` | `setup_interrupt_handlers` | Configures interrupt vectors via INT 21h |
| `1339:0a38` | `dos_int21h_wrapper` | Simple INT 21h call wrapper |
| `1339:0a82` | `dos_int21h_with_regs` | INT 21h call with register parameters |
| `10da:2360` | `get_flags_register` | Returns CPU FLAGS register |
| `10da:2363` | `set_flags_register` | Sets CPU FLAGS register |
### Memory Management
| Address | Name | Description |
|---------|------|-------------|
| `1677:0d12` | `cleanup_ems_memory` | Frees EMS (INT 67h) memory handles |
| `10da:14fc` | `init_stack_fill_cc` | Fills stack with 0xCC (INT 3) for debugging/guard |
| `10da:1706` | `get_segment_base_addr` | Computes linear base address from segment descriptor |
### Task Management
| Address | Name | Description |
|---------|------|-------------|
| `10da:19ca` | `task_switch_to_child` | Context switch to child process |
| `10da:1946` | `task_switch_from_child` | Context switch back from child process |
| `10da:1af4` | `call_termination_handler` | Calls registered termination callback |
### I/O & Output
| Address | Name | Description |
|---------|------|-------------|
| `10da:00d6` | `flush_output_buffer` | Flushes buffered output via function pointer |
| `10da:0132` | `putchar_buffered` | Writes character to buffer, flushes on newline |
| `10da:0808` | `memcopy_to_buffer` | Copies N bytes from source to destination buffer |
| `10da:178c` | `print_error_message` | Formats and prints load error (references "not loaded: %s") |
| `10da:09e4` | `print_fatal_error` | Prints "Fatal Error" prefix + message |
| `10da:192a` | `print_internal_error` | Prints "Internal Error" message |
### Interrupt Management
| Address | Name | Description |
|---------|------|-------------|
| `10da:1ec0` | `restore_interrupt_vectors` | Restores INT 2Fh and INT 67h vectors |
| `10da:2249` | `restore_int_2f_67` | Restores INT 15h vector if saved |
| `1760:3d86` | `init_system_check` | Validates system (CPU, DOS version, VCPI/DPMI, memory) |
### Utility
| Address | Name | Description |
|---------|------|-------------|
| `10da:15ea` | `check_ds_segment` | Returns true if DS == 0x10 (checks data segment selector) |
| `1760:3c9e` | `nop_stub` | Always returns 0 (unused hook) |
## Key String References
| Address | String | Context |
|---------|--------|---------|
| `13fc:0016` | `$Id: comhighc.c 1.1 91/08/06...` | Phar Lap C runtime source ID |
| `13fc:0048` | `$Id: comutils.c 1.1 91/08/06...` | Phar Lap utility functions source ID |
| `13fc:0078` | `Serial Number ` | DOS extender serial validation |
| `13fc:14ca` | `Internal Error` | Error class prefix |
| `13fc:14da` | `Fatal Error` | Fatal error class prefix |
| `13fc:156a-1628` | File error messages | Not found, bad format, no memory, etc. |
| `1760:665c` | `Copyright (C) 1986-93 Phar Lap Software, Inc.` | DOS extender copyright |
| `1760:73da` | `-LDTSIZE 4096 -EXTHIGH D0_0000h -NI 18 -ISTKSIZE 3` | Default extender config |
| `1760:76fc-7c5a` | Numbered error messages | System requirement errors (1000-2170) |