28 KiB
Crusader: No Remorse — Decompilation Notes
This file is an index. Detailed notes have been split into the docs/ folder by topic.
Active live analysis target is now CRUSADER.EXE. Existing CRUSADER-RAW.EXE notes remain in scope as cross-reference evidence and should be cited alongside live NE addresses when they support a rename, variable role, or behavior claim.
Recent verified PSX pre-alpha batch: docs/psx/prealpha.md now records a focused Ghidra pass on /psx/prealpha/SLUS_002.68 plus a disc-tree comparison against the released PlayStation Crusader: No Remorse build. Current best read is that this pre-pre alpha still looks much more like a trimmed early No Remorse PSX branch than a clearly rebranded Crusader 2 executable: it still carries direct Crusader: No Remorse save/quit text, the renamed wdl_resource_bundle_load_by_index still embeds the full retail \LSET1\L through \LSET7\L prefix table and the same 10/20/30/40/50/60 threshold ladder, and the mission/passcode UI scaffolding is still present with the same visible 15 mission briefing strings and consonant/digit passcode alphabet. The main concrete differences in this batch are the heavily reduced shipped content (3 level bundles, 1 XA, no .STR movies) and the surviving architectural leftovers that no longer match the current disc literally, especially the missing-file \AUDIO\TALK1.XA;1 path and the LoadExec helper for MENU.EXE / ENGINE.EXE / PSX.EXE.
Recent verified PSX executable batch: docs/psx/psx.md now records a focused Ghidra pass on SLUS_002.68 for mission/map inventory, passcode handling, and catalog text. Current best read is that the PSX loader hardcodes seven \LSETn\L folder prefixes and the extracted disc ships 62 level bundles (L0..L58, L62..L64) with a real gap at L59..L61, while the executable still exposes only 15 plain-text Mission Briefing ^Mission N strings. The same pass closes the visible passcode-generation side too: mission-complete flow synthesizes 4-character passcodes from the alphabet BCDFGHJKLMNPQRSTVWXZ0123456789, and the executable preserves direct ammo/item/weapon name tables. The hidden password-screen cheat codes remain less direct: public PSX references point to XXXX and L0SR/L0SER, but those values are not stored as plain ASCII in SLUS_002.68, so the compare path still looks numeric or transformed rather than table-driven.
Recent verified Japanese-build batch: docs/jp-remorse-windows9x-investigation.md now records a focused live-Ghidra investigation of /ja/CRUSADER.EXE around the claim that the Japanese release runs natively on Windows 95 / Windows 9x instead of requiring a DOS boot path. Current best static-analysis read is strongly in favor: the JP executable is a flat Win32 image with PE-style sections, a Windows import table, native window creation, DirectDraw/DirectSound initialization, registry-backed config under Software\Electronic Arts\Crusader: No Remorse\J1.21, and a meaningful GetVersion-based Win9x compatibility branch that changes TLS allocation behavior when the classic Win9x high bit is set. The only remaining uncertainty is practical deployment rather than architecture: this pass did not runtime-test on real Win95 or prove which DirectX/runtime prerequisites are required.
Recent verified Japanese-build follow-up: docs/jp-remorse-cheats-and-launch-params.md now records a focused pass on the surviving cheat/debug and startup-argument lanes in /ja/CRUSADER.EXE. Current best read is that the JP Win32 build kept real executable cheat/debug machinery, not just leftover strings: -laurie is still a special parser case, the hidden JASSICA16 sequence matcher still toggles the cheat-active state with live Cheats are now active/inactive. messages, the option-key handler still contains the immortality toggle path, and the command-line parser still executes live handlers for -debug, -u <arg>, -warp <mission>, -skill <n>, -mapoff <delta>, -egg <id>, and -demo. The same pass also narrows one important difference from older DOS-side notes: the JP Win32 parser has not yet been proven to support positional -warp <mission> <x> <y> <z> consumption, so that form should not currently be assumed for this build.
Recent verified localized-build batch: docs/spanish-cheat-differences.md now records a tighter live-Ghidra comparison against /es/CRUSADER.EXE for the known cheat/debug control areas. Current best read is now narrower than the earlier "moved matcher" theory: the Spanish executable still preserves the same broad cheat/debug framework as the English build with relocated addresses rather than different behavior, but it does not preserve the English jassica16 table as the same static data object and this pass also failed to recover any replacement compiled matcher or any translated ~ cheat-latch toggle. The -laurie parser still sets the broad cheat/debug gate (1478:0910), the gameplay-input gate still exists at 1478:0927, and Hack Mover still toggles through 13e8:24a5; but the old English-side slot at 1478:2833 now contains pointer-like words, the old English immortality-string slots at 1478:2850/2866 are also repurposed as non-string data in Spanish, 1478:0910 has only the -laurie write at 1050:0985, 1478:5fb3 only has the Laurie-hint helper writes at 13e8:0071/0077, World_HandleKeyboardInput does not expose a recovered 0x7e / tilde branch, and 1478:8ad6 still has no recovered writer even though Hack Mover checks it. The new keyboard-side conclusion is stronger too: 1478:5fb3 does not act like a live positive enable latch in Spanish, because every recovered consumer requires it to be zero and the Laurie-hint helper pulses it back to zero immediately, while the nearby 8ad7/8ad8/8ad9 runtime-state writes still do not explain 8ad6. The Hack Mover runtime chain is also tighter now: 1478:5fb2 is the actual on/off toggle, 13e8:0ef9 / 13e8:0f77 clear it, 13e8:282f is the adjacent runtime helper using 1478:8ad9, and 13e8:2f0e / 13e8:3009 bracket the active drag state via 1478:8ac0, 1478:8acc, and 1478:8ace. Current safest localized-build read is therefore -laurie is the only recovered positive enabler for the surviving broad Spanish cheat/debug family; no replacement hidden matcher, no runtime keyboard-latch bootstrap, and no direct Spanish F10 cheat branch have been recovered, with the remaining open question narrowed to whether 1478:8ad6 is written through an analysis-dark path or is just a dead leftover gate.
Recent verified batch: docs/retail-debug-arg.md now records the live NE proof that retail CRUSADER.EXE still recognizes and executes a real -debug command-line branch. That branch prints Debugging mode ON., sets g_debugMsgLevel at 1478:87e0, and toggles two debug globals at 1478:0845/0859. The later sink pass also closes the text-output target more tightly: ProbablyPrintDebugMessage formats through the static stdio-style table at 1478:6c32..6c81 and writes to the handle-1 entry at 1478:6c46, so the non-video side is ordinary DOS stdout gated by the debug threshold, plus the already-confirmed AVI timing overlay. Current best read remains surviving debug-output / instrumentation switch, not the missing bootstrap for the hidden seg109/seg1408 usecode debugger. The same batch also leaves the earlier -laurie and 0x659c/659e debugger-state conclusions intact: -debug is a separate switch and is not currently evidenced as constructing the hidden usecode-debugger break-state object.
Recent tooling batch: docs/map-rendering.md now starts a dedicated offline map-rendering lane. tools/render_crusader_map.py can load FIXED.DAT, expand GLOB.FLX, decode the required SHAPES.FLX entries with Crusader frame headers, apply GAMEPAL.PAL, and write a first-pass PNG, with a --fixed-dat override so the same pipeline can be pointed at either game's map file. The current renderer is intentionally limited to fixed-map content and a simple deterministic painter rather than the full Pentagram/ScummVM dependency sorter, and the current workspace caveat is that STATIC_REGRET still lacks a copied FIXED.DAT, so No Regret rendering needs that file supplied explicitly.
Recent map/editor visibility batch: docs/editor-object-visibility.md now records a focused live-Ghidra pass on whether retail CRUSADER.EXE explicitly hides editor-only map objects and whether any built-in switch re-enables them. The current best read is now tighter than the first pass: Item_PaintSprite at 1198:02e4 does contain a real downstream flags2 & 1 (SI_EDITOR) early-out, but the active world-item renderer also has an upstream controlling skip at 1180:0951..095c that filters editor-tagged shapes before draw-node allocation. That corrected render-path model explains why the first executable patch attempt, which only flipped the downstream draw-time branch, produced no visible change in-game. The same note still closes the negative side of the question more tightly: no recovered retail -debug, cheat/debug hotkey, Laurie/usecode-debugger path, or 0x410 event behavior currently reaches either gate or exposes a show editor items state. The closest confirmed toggle remains ScummVM's own _showEditorItems debugger command, which is an engine-added reimplementation feature rather than evidence of a retail built-in toggle.
Recent map-viewer trigger batch: docs/map_renderer/trigger-usecode-links.md now records the evidence-backed editor/controller-object -> USECODE mapping used by the viewer when opening readable pseudocode from pinned tooltips. Current best read is that the stable viewer targets are BOX_EW, PANELNS, CARD_NS, and SPANEL -> use; FASTSKIL -> enterFastArea; SKILLBOX, EVENT, ALARMHAT, and ALRMTRIG -> equip; TRIGPAD and NPC_ONLY -> gotHit; and the 0x04B1 cmd helper itself -> TRIGGER.slot_20, the shared high-slot fan-out lane that nearby controller families keep spawning.
Recent startup/map-selection batch: docs/first-mission-map-selection.md now records the live proof that fresh-game map choice is code-selected rather than read from CRUSADER.CFG or another external mission-mapping file. For CRUSADER.EXE, the normal fresh-game path still hardcodes map 1, egg 0x1e inside Game_Start. For REGRET.EXE, the same values are hardcoded twice: once in an early Game_Start selector and again in the later FUN_1030_032d mission-start path that actually controls a real new game. The same note also captures the separate debug -warp mission path: it indexes a small executable-embedded mission-to-map word table at 1478:0488 (0,1,3,5,...,0x1d,0x28) and then applies -mapoff, while the actual map contents remain external in FIXED.DAT.
New REGRET startup-flow batch: docs/regret-game-start.md now documents the live REGRET.EXE Game_Start neighborhood more thoroughly. That note promotes HandleCommandlineArgs, Game_RunNewGameFlow, Game_DrawCenteredStartupSplash, Game_EnterFrontendMenuViewport, and Game_RestoreGameplayViewport in the live database, records the startup-state globals used by the new-game and -warp lanes, and explains the current best reason map 1 is set twice in No Regret: two separate live startup entry paths still own their own teleporter literals instead of sharing one final startup-map source.
New command-line argument batch: docs/command-line-parameters.md now consolidates the currently recovered startup/debug argument set across the retail Crusader executables. The key new closure is the actual direct-warp syntax in REGRET.EXE: -warp <mission> [x y z] rather than separate -x/-y/-z switches. The same note also records the now-proven precedence rule that nonnegative -egg overrides beat the X/Y/Z teleport path, the practical parameter-only route into eggless maps (-warp <mission> <x> <y> <z> plus -mapoff, with -egg omitted), and the current best read of -setver as a displayed version/build-string override rather than a gameplay compatibility switch.
Follow-up No Remorse cross-check: the same command-line note and docs/first-mission-map-selection.md now record the matching live CRUSADER.EXE proof. HandleCommandlineArgs at 1048:0adc uses the same positional -warp <mission> [x y z] syntax as Regret, and Game_Start at 1020:029e / 1020:02d0 applies the same precedence rule where nonnegative -egg overrides beat the direct-coordinate NPC_Teleport path.
Latest warp-table follow-up: the same docs/first-mission-map-selection.md and docs/regret-game-start.md notes now close the missing No Regret table details directly. Live REGRET.EXE Game_RunNewGameFlow indexes the -warp mission base-map table at 1480:075c, and retail byte checks now show the same 17-word payload as No Remorse: 0,1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,40, followed by a 0,0 terminator. The public renderer project now also has a dedicated extractor that writes both retail tables into Crusader_Decomp_Public/map_renderer/.cache/mission-map-data.generated.json for scene-metadata use.
Latest command-line follow-up: that same docs/command-line-parameters.md note now closes the retail non-Japanese -u lane as well. In live CRUSADER.EXE, the parser case at 1048:0a46 copies the following token into 1478:065a, and the newly named startup_apply_u_override_if_present at 1420:0cdf consumes that buffer to load an alternate usecode/EUSECODE source into 1478:6611/6613 before rebuilding the cumulative slot-base words. Current best read is therefore real startup usecode override, not JP-only feature and not dead parser-table residue. The same follow-up also means the older consolidated -setver note is now weaker on the CRUSADER side and should be treated as needing a direct retail re-close.
Latest -u deep dive: new note docs/usecode-startup-override.md now follows that retail override into the live usecode runtime itself. Current best read is that -u replaces the single live usecode root at 1478:6611/6613 rather than adding a parallel overlay. The same root is later consumed by Usecode_ItemCallEvent, UsecodeProcess_CreateProcess, Interpreter_NextUsecodeOp, and Item_GetDamaged, so the override reaches ordinary scripted gameplay behavior, not just a startup-only side lane. Current safest tooling implication is runtime replacement for the existing Crusader usecode VM, not arbitrary native plug-in system.
Latest -u token-shape follow-up: the same docs/usecode-startup-override.md note now tightens the argument semantics materially. In live retail CRUSADER.EXE, startup_apply_u_override_if_present does not pass the copied argv token through as an arbitrary final filename. It loads the mutable filename template eusecode.flx from 1478:07a0 via the far pointer at 1478:06d6/06d8, forces the first byte to 'e', and calls Filespec_GetFullPath(0, s_usecode, "eusecode.flx", 0). Current safest read is therefore path/root override for the standard EUSECODE archive family, not free-form arbitrary filename switch. The same note now also separates the stock-path status more cleanly: the raw-side VM bootstrap is strongly cross-referenced, but the exact live-NE writer that seeds 1478:6611/6613 without -u is still not directly closed.
Latest -u loader-layout follow-up: the same docs/usecode-startup-override.md note now records the direct constructor/loader pair behind the override in the live NE session. 1420:1499 is now renamed entity_vm_runtime_create and currently reads as a 0x1319-byte runtime-object constructor with a 0x1300-byte front region that behaves like 0x80 stride-0x26 slot/runtime records plus tail metadata at 0x1300..0x1318. 1430:0000 is now renamed entity_vm_runtime_owner_resource_create and currently reads as the compact 0x14-byte file-backed helper allocated from the resolved eusecode.flx path and attached to the runtime object at +0x1315/+0x1317.
Latest doc-reconciliation batch: docs/ne-segment1.md now has a combined hidden-debugger component table that explicitly separates the seg109/raw-reference UI wrappers (000b:9a86, 000b:9c0d, 000b:b3b1, 000b:b62c, 000b:2882) from the live seg1408 breakpoint-state helpers (1408:0000, 1408:0053, 1408:00dd, 1408:029e, 1408:03b0, 1408:03f7, 1408:0419, 1408:0432, 1408:0444) and the interpreter hook at 1418:04aa..04b5. Current best read remains two connected layers of one hidden usecode debugger, not conflicting address claims for the same function family.
Follow-up cheat-key correction pass: docs/ne-segment1.md now also records a live NE cleanup of several folklore keyboard-cheat claims. ~ is a real runtime cheat-latch toggle at 13e8:203d, Ctrl+C is wrong for this build and should be Ctrl+L for the coordinate popup at 13e8:255e, and the third F7-family overlay really does exist as a separate Ctrl+F7 path at 13e8:1a20 alongside the other two cheat-gated F7 overlay toggles.
That same note now also separates ~ from jassica16 more cleanly: jassica16 is the raw scan-code unlock path that toggles both 1478:0844 and 1478:6045 and sets the extra post-sequence latch 1478:8c52, while ~ is only the later translated logical-0x7e hotkey that flips 1478:6045 after 1478:0844 is already on. The F7-family clarification is tighter too: Ctrl+F7 is best read as an egg-hatcher trigger-range overlay rather than a third generic background grid.
The same docs/ne-segment1.md note now also has the first consolidated cheat/debug key matrix for the live NE target, including which paths need the broader Laurie/debug master gate (1478:0844), which ones need the full keyboard-cheat latch (1478:6045), and which ones depend on the extra post-jassica16 latch (1478:8c52). That pass also expands the egg-hatcher explanation: Ctrl+F7 is now documented as a live EggHatcherProcess range visualizer, with practical guidance on where to look for egg-trigger regions in gameplay.
Documentation Structure
| File | Contents |
|---|---|
| docs/overview.md | Binary overview, installed copy findings, address space layout, NE fixup placeholder, segment map, NE import details, next steps |
| docs/phar-lap-extender.md | DOS extender architecture, named functions (entry, loading, memory, I/O, interrupts), key string references |
| docs/ne-segment1.md | NE Segment 1 full analysis: cursor, input, entity system, shot lifecycle, combat, weapons, AI, player/HUD, destruction, entity constants, vtable index, cheat system |
| docs/jp-remorse-windows9x-investigation.md | Focused note on the Japanese /ja/CRUSADER.EXE Windows-native claim: PE/Win32 image evidence, Win32 windowing, DirectDraw/DirectSound, registry config under J1.21, IME/DBCS clues, and the GetVersion-driven Win9x compatibility branch |
| docs/jp-remorse-cheats-and-launch-params.md | Focused note on surviving JP /ja/CRUSADER.EXE cheat/debug and startup-argument lanes: -laurie, JASSICA16, immortality, the recovered Win32 parser table, the live -u usecode override, and the current caution that JP -warp is only directly proven in mission-only form |
| docs/spanish-cheat-differences.md | Focused comparison note for /es/CRUSADER.EXE versus the English build's known cheat/debug lanes: -laurie, broad cheat gate, gameplay-input gate, low-level keyboard latch, Ctrl+Q, Hack Mover, and the current status of the unresolved secret sequence |
| docs/raw-porting-progress.md | seg091 RNG, 0x4588 callback lifecycle batches 1-6, 0007 gameplay helper batches, snap_entity_to_ground, AI sweep, animation/range/command globals, seg043 boundary recovery |
| docs/raw-000e.md | 000e parser helper cluster (record table init/parse/dispatch), 000e RIFF/animation cluster (animation object field map, RIFF format, constructor variants) |
| docs/raw-0007-rendering.md | Draw list node format and functions, world-to-screen isometric, tile visibility system, scroll/camera functions, scroll region table, save slot system, string/memory utilities, coordinate transform deep analysis |
| docs/raw-0008-000c.md | 0008 dispatch helpers (init, pair-sync, flag helpers, word-list, gate-callbacks) and 000c state machine (tick dispatch, flag guards, palette fade, mini-VM, cursor nav) |
| docs/raw-000a-000d.md | 000d proximity/visibility buckets, 000a tracked handles, cache manager, init/shutdown, seg082 allocator, seg137/138 palette helpers, seg004/005 startup, 0x4588 object-role evidence, 000d VM owner/resource loader follow-up |
| docs/far-call-targets.md | Top-104 most-called far-call targets (Tiers 1-5, ranks 1-104), supporting functions discovered, analysis gaps and seg043 reconciliation |
| docs/crusader-disasm-reference.md | Local auxiliary disassembly corpus at K:/ghidra/crusader-disasm: handwritten notes, shape tables, map dumps, opcode lists, intrinsic/function dumps, and the safe reuse rules for porting into CRUSADER.EXE |
| docs/ne-hole-filling-priorities.md | Ranked CRUSADER.EXE hole-filling tracker: NE-side unclear lanes, the verified raw-side knowledge that can close them, and the recommended order for old-to-new porting passes |
| docs/retail-debugger-patch-attempts.md | Chronological log of retail CRUSADER.EXE debugger-unlock patch attempts, byte-level designs, runtime failures, root-cause findings, and the current live candidate |
| docs/retail-debug-arg.md | Focused note on the retail -debug command-line switch: live parser evidence, exact startup message, surviving globals, segment 1468 instrumentation path, and why it is currently separate from the hidden usecode debugger bootstrap |
| docs/scummvm-crusader-reference.md | ScummVM Ultima8/Pentagram Crusader integration survey: USECODE/event tables, FLEX/resource formats, world/map loaders, HUD/media, and RE follow-up priorities |
| docs/pentagram-crusader-reference.md | Pentagram-source Crusader/U8 reference: direct Crusader USECODE parser and VM evidence, U8 usecode docs, runtime-confidence limits, and cross-checks against the ScummVM note |
| docs/map-rendering.md | Offline map-rendering lane: FIXED.DAT/GLOB.FLX/SHAPES.FLX/GAMEPAL.PAL format notes, current Python renderer, supported inputs, and fidelity gaps |
| docs/editor-object-visibility.md | Focused note on retail editor-only map object hiding: the live 1198:02e4 SI_EDITOR early-out in the normal item paint path, the lack of a recovered retail visibility toggle, and the ScummVM/Pentagram cross-check that treats show editor items as an engine-side debug feature |
| docs/map_renderer/trigger-usecode-links.md | Evidence-backed map-viewer note for editor/controller shapes that now expose direct USECODE navigation, including the stable class/event targets and the special TRIGGER.slot_20 handling for 0x04B1 cmd helpers |
| docs/first-mission-map-selection.md | Focused note on fresh-game startup map selection: No Remorse Game_Start, No Regret's early and later mission-start selectors, the separate embedded -warp mission table, and the split between code-selected startup and external FIXED.DAT map content |
| docs/regret-game-start.md | Detailed REGRET.EXE startup-flow note: Game_Start, Game_RunNewGameFlow, newly named helpers, startup override globals, and the current best explanation for the duplicated map-1 selector |
| docs/command-line-parameters.md | Consolidated startup/debug argument reference for the retail Crusader executables: live retail -u usecode override, the current -setver caution, -debug, -asylum, -warp, -skill, -mapoff, -egg, -demo, the -laurie cross-reference, and the evidence-backed direct-coordinate warp syntax/limits |
| docs/psx/psx.md | PlayStation SLUS_002.68 and disc-resource note: boot/load layout, LSET/menu WDL structure, executable-backed map inventory, passcode alphabet/display path, recovered PSX ammo/item/weapon tables, and current unresolved enemy/password-compare gaps |
| docs/psx/prealpha.md | PlayStation pre-pre alpha /psx/prealpha/SLUS_002.68 comparison note: reduced disc inventory, retained retail-style LSET loader, surviving No Remorse branding, stale TALK1.XA and LoadExec leftovers, and the current read that this build is closer to an unfinished No Remorse PSX branch than to a visibly rebranded sequel executable |
| docs/usecode-startup-override.md | Focused retail -u deep dive: startup call order, why the override looks like full live-root replacement rather than addition, which event/process/interpreter consumers use that root, and what that implies for future custom usecode experiments |
| docs/usecode-roundtrip-ir.md | ScummVM-to-binary USECODE cross-walk, owner-loaded class-layout and header/event-count reconciliation, conservative IR v0 plan, and the generated class-event/body-window outputs that now ground reversible _BOOT, SURCAM*, and environmental family decompile artifacts plus repeated-family regression checks |
| docs/usecode-pentagram-ghidra-path.md | Pentagram-derived Crusader USECODE parser plan, proof-of-concept workflow, canonical IR v1 goals, and the Ghidra-side annotation import path |
| docs/usecode-tooling-comparison.md | Comparison of Pentagram's converter/disassembler, the local crusader-disasm corpus/scripts, and the current workspace parser/pseudocode exporter, with emphasis on assumptions, strengths, and repo-specific differences |
| docs/usecode-tool-improvement-plan.md | Concrete next-step plan for the local USECODE parser/decompiler, distilled from the Pentagram and crusader-disasm comparison into prioritized parser, loop-decoding, intrinsic, trailer, corpus, and runtime-bridge upgrades |
| docs/usecode-jelyhack-analysis.md | Focused analysis of exported JELYHACK / JELYH2 pseudocode, the tiny shared use stub, and why the current best model remains referent anchor + neighboring event-bearing attachment |
| docs/usecode-equipment-system.md | Evidence-backed note on Crusader's surviving equip / unequip event system, including live compiled-side dispatcher proof, corpus-wide slot counts, actor/turret/environment examples, and the current best model of equip as a generalized inherited Ultima-style item event |
| docs/usecode-alarmhat-analysis.md | Focused analysis of exported ALARMHAT::equip, the nearby shape 0x04D0 equip loops, alarm-family comparisons, and the current gameplay-facing read of ALARMHAT as a local alarm-state driver |
| docs/usecode/windsurf-regret-vs-remorse.md | Side-by-side comparison of WINDSURF in Regret and No Remorse, including shared slot behavior, helper-family drift, body-size differences, and the current best read of WINDSURF as a directional wind-force helper used by vent scripts |
| docs/removed_items.md | Evidence summary for suspicious removed item shapes in old No Remorse maps: grenade-family leftovers 0343/034E/034F/0350, the inventory-labeled 0548 Invalid item, and unresolved non-pickup shapes 0110/0112 |