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

294 lines
No EOL
12 KiB
Markdown

# 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.