Crusader_Decomp/docs/retail-debugger-entry-options.md

12 KiB

Retail Usecode Debugger Entry Options

This note consolidates the current best entry-path analysis for the hidden retail usecode debugger in live CRUSADER.EXE.

Question:

  • What is the lowest-modification path that could realistically get the hidden debugger menu working?
  • Can ordinary usecode or a -u startup override reach it without another fragile executable patch?
  • If retail still cannot do it cleanly, what should the next comparison pass in No Regret and JP No Remorse look for?

Short Answer

Current best answer:

  • No zero-modification retail entry path is currently evidenced.
  • -debug, -laurie, jassica16, ~, and Ctrl+Q still do not provide a proven bootstrap into the hidden debugger.
  • The hidden debugger UI is real and usable, but it expects a live seg1408 break-state object at 1478:659c/659e and valid current-unit/runtime context.
  • The cleanest non-EXE exploration path is now the -u usecode-root override, but current evidence still does not show a script-visible way to construct the break-state object or write 1478:659c/659e.
  • The smallest structurally defensible executable patch is still the current interpreter-callsite-retarget family, but that remains more complex than a one-site tweak and therefore is not the preferred next move unless cross-build comparison fails.
  • The best next investigation is a comparison pass in REGRET.EXE and JP /ja/CRUSADER.EXE looking specifically for a surviving writer/bootstrap path for 1478:659c/659e, a constructor caller for 1408:0000, or a direct caller to the seg109 wrappers.

New Live-Ghidra Findings From This Pass

1. The missing retail bootstrap is still missing

Fresh live data-use recovery on 1478:659c still shows reads only.

Current confirmed reader families:

  • Interpreter_NextUsecodeOp at multiple sites including 1418:049e, 1418:04b1, 1418:0519, and several later helper windows
  • usecode_debugger_open_for_current_unit at 13a0:00af / 13a0:0165
  • usecode_debugger_format_expression_to_shared_buffer at 13a0:03db / 13a0:03f4
  • usecode_debugger_handle_event at 13a0:1e13, 13a0:1e3b, 13a0:1e5d, 13a0:20b2, 13a0:20b6
  • one additional seg109 local helper FUN_13a0_1791

What is still missing:

  • no recovered retail writer to 1478:659c/659e
  • no recovered retail caller of 1408:0000 Create
  • no recovered direct caller of 13a0:0086 usecode_debugger_open_for_current_unit
  • no recovered direct caller of 13a0:020d usecode_debugger_open_modal

That keeps the current retail model unchanged:

  • the seg109 UI is real
  • the seg1408 break-state object is real
  • the interpreter callback lane is real
  • but retail still looks like an orphaned debugger subsystem whose bootstrap/entry wiring was removed or compiled out

2. The UI wrappers are valid, but they are not safe cold-entry targets

Fresh decompile reads tighten the wrapper roles:

13a0:0086 usecode_debugger_open_for_current_unit

  • immediately calls usecode_debugger_gump_create(..., mode=1)
  • pulls the current unit name from Remorse::UsecodeDebuggerBreakState::CurrentEntryGetUnitName(_DAT_1478_659c)
  • resolves a usecode path under s_usecode
  • loads the corresponding unit file into the debugger pane
  • centers on current_line - 1
  • then enters Dispatch_ModalGump

Implication:

  • this wrapper is closest to the original intended debugger experience
  • but it absolutely expects a valid debugger object and current-entry state first

13a0:020d usecode_debugger_open_modal

  • also calls usecode_debugger_gump_create, but with generic mode 0
  • skips current-unit preload and line-centering
  • enters the same modal debugger gump

Implication:

  • this is the safer force-open target than 13a0:0086
  • but it still assumes the surrounding caller/context is sane enough for the debugger gump to live

3. usecode_debugger_gump_create proves the debugger control bundle is complete once the gump exists

Fresh decompile of 13a0:19b1 usecode_debugger_gump_create now gives the cleanest current constructor summary.

Verified behavior:

  • allocates a 0x50-byte root gump object when this == null
  • builds the debugger menubar and child panes
  • initializes the shared watch table
  • resolves the base usecode path with Filespec_GetFullPath(0, s_usecode, 0, 0)
  • registers the debugger/control event bundle through NewGump_1360_0f2a

Recovered registered event set from this pass:

  • 0x13d
  • 0x443
  • 0x142
  • 0x141
  • 0x143
  • 0x23f
  • 0x43e
  • 0x41f
  • 0x417
  • 0x431
  • 0x411
  • 0x410
  • 0x441
  • 0x421
  • 0x22d

Current direct callers are still only:

  • 13a0:009b from usecode_debugger_open_for_current_unit
  • 13a0:0256 from usecode_debugger_open_modal

This strengthens one important boundary:

  • 0x410 is a real debugger-gump event once the debugger UI has already been created
  • it is not evidence that retail gameplay already has a reachable path that creates the gump

4. 0x410 remains parallel debugger UI state, not the debugger bootstrap itself

usecode_debugger_handle_event at 13a0:1df3 still confirms the same split, but the current decompile makes it easier to summarize.

Verified cases include debugger-style commands for:

  • open file
  • resume / break-next / single-step state changes
  • go to line
  • watch / inspect / clear watches
  • change global memory
  • find / search again
  • local breakpoint/debug actions

Relevant 0x410 detail:

  • incoming event 0x410 is rewritten to local state 0x0e
  • case 0x0e clears one local selection/watch slot and refreshes debugger state

Implication:

  • the debugger's own event machine knows what to do with 0x410
  • but only after the debugger gump already exists and is registered
  • this does not give us a new no-patch retail entry path by itself

What This Means For Usecode As An Entry Path

Current Best Read

The -u retail override is now the best non-EXE exploration tool, but not yet a proven debugger-entry solution.

Why it still matters:

  • startup_apply_u_override_if_present at 1420:0cdf is a real startup hook
  • it is called from Init_Everything at 1048:05d3
  • it swaps the single live usecode runtime root rather than layering a second source
  • that replacement root is then consumed by ordinary compiled paths like Usecode_ItemCallEvent, UsecodeProcess_CreateProcess, and Interpreter_NextUsecodeOp

So -u gives a practical route to:

  • replace scripted behavior with minimal executable modification
  • test whether a data-driven/usecode-side path can indirectly reach an otherwise hidden compiled control lane

But the current negative evidence is still stronger than the hopeful side:

  • no recovered usecode intrinsic or compiled helper currently reads like open usecode debugger
  • no recovered usecode-visible primitive currently reads like construct debugger break state
  • no recovered usecode-visible primitive currently writes 1478:659c/659e
  • current script/event scans still do not show a plain usecode literal/ordinal trail for event 0x410

Current safest conclusion:

  • a -u archive replacement is the least invasive experimental platform
  • it is not yet an evidence-backed direct debugger unlock

Best Script-Side Host Families If We Try -u

If the next step is an asset-only experiment, the best current targets remain:

  • MONITNS / monitor-computer families
  • SURCAMNS / SURCAMEW
  • NPCTRIG

Why these are stronger than a generic chest or one-off gadget:

  • they already sit near real modal UI / camera / event-control behavior
  • they are more plausible bridges into a hidden control/event lane than ordinary loot or animation scripts

What they still do not currently prove:

  • direct debugger construction
  • direct seg109 wrapper calls
  • direct 1478:659c/659e bootstrap

So the immediate asset-only goal should be framed narrowly:

  • not open the debugger from usecode directly
  • but test whether any scripted family can reach a compiled control path closer to the hidden debugger than the currently known public gumps

Ranked Entry Options By Modification Cost

1. Zero-modification retail route

Current status: no proven path.

Still ruled out by current evidence:

  • -debug
  • -laurie
  • jassica16
  • ~
  • Ctrl+Q / event 0x410

Why:

  • they toggle debug/cheat/display behavior
  • they do not currently create the seg1408 break-state object or enter the seg109 wrappers

2. Asset-only route via -u replacement EUSECODE.FLX

Current status: lowest-modification practical experiment, but unproven as a debugger route.

Advantages:

  • no retail EXE byte changes required
  • fully reversible by swapping the override directory/archive
  • reaches normal compiled usecode consumers

Limitations:

  • still no direct evidence that usecode can create/write the required debugger state
  • more likely to find an indirect bridge than a direct open debugger primitive

Current recommendation:

  • prefer this over new ad hoc retail EXE patching if the goal is only to test indirect control-flow ideas

3. Minimal executable route: interpreter-callsite-retarget family

Current status: still the smallest structurally defensible retail patch family.

Why it remains the floor:

  • one-site retarget ideas fail because they do not also create/store the debugger object
  • direct shared-callback patching is too global and has already caused startup failures
  • direct cold-calls into the UI wrappers use the wrong stack/context

What the current viable family still needs:

  • lazy create-or-reuse of the seg1408 break-state object
  • store into 1478:659c/659e
  • preserve deferred interpreter timing
  • sanitize wrapper arguments before the seg109 UI entry

So even the best retail EXE path is still not a tiny one-byte/two-byte unlock.

4. Cross-build bootstrap recovery

Current status: best next investigation.

Why this is now preferred over more retail patch fishing:

  • if No Regret or JP No Remorse kept any surviving debugger bootstrap, it could collapse the retail problem from invent a new path to port or mimic one missing write/call
  • that is more likely to produce a truly minimal modification than another speculative retail patch chain

Current Recommendation

If the goal is the minimum modification that still has a realistic chance to work, the order should now be:

  1. Compare REGRET.EXE and JP /ja/CRUSADER.EXE for any surviving debugger bootstrap/writer.
  2. Keep -u / replacement EUSECODE.FLX as the preferred low-risk experiment surface for any script-side proxy ideas.
  3. Do not resume broader retail executable patching unless the cross-build pass fails to yield a clearer bootstrap or the existing O/P family gets one clean runtime confirmation target.

That ranking fits both the new live evidence and the user's practical constraint that complex retail patch attempts have already been unstable.

Cross-Build Exploration Note

When this moves to No Regret and JP No Remorse, the focused targets should be:

  1. any write to the debugger global pointer equivalent of 1478:659c/659e
  2. any caller of 1408:0000 Create or its build-specific equivalent
  3. any direct caller of usecode_debugger_open_for_current_unit
  4. any direct caller of usecode_debugger_open_modal
  5. any non-stub debugger vtable slot replacing retail 1408:046f / 1408:0474
  6. any command-line or cheat/debug hotkey path that lands near the seg109 wrappers or seg1408 constructor
  7. any usecode/runtime path that seeds current-unit state for the debugger without using the orphaned retail bootstrap

Bottom Line

Retail No Remorse still looks like it shipped with a real hidden usecode debugger whose UI, event dispatcher, and break-state object all survived, but whose bootstrap path did not.

That means the lowest-modification currently evidenced route is no longer "guess one more retail patch". It is:

  • first, look for the missing bootstrap in sibling builds,
  • second, use -u and a replacement EUSECODE.FLX only as a low-risk exploration surface,
  • and only third, return to the interpreter-callsite-retarget patch family if the cross-build pass gives no smaller bootstrap to port.