24 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
-ustartup 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,~, andCtrl+Qstill 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/659eand valid current-unit/runtime context. - The cleanest non-EXE exploration path is now the
-uusecode-root override, but current evidence still does not show a script-visible way to construct the break-state object or write1478: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.EXEand JP/ja/CRUSADER.EXElooking specifically for a surviving writer/bootstrap path for1478:659c/659e, a constructor caller for1408: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_NextUsecodeOpat multiple sites including1418:049e,1418:04b1,1418:0519, and several later helper windowsusecode_debugger_open_for_current_unitat13a0:00af/13a0:0165usecode_debugger_format_expression_to_shared_bufferat13a0:03db/13a0:03f4usecode_debugger_handle_eventat13a0: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 mode0 - 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 whenthis == null - builds the debugger menubar and child panes
- initializes the shared watch table
- resolves the base
usecodepath withFilespec_GetFullPath(0, s_usecode, 0, 0) - registers the debugger/control event bundle through
NewGump_1360_0f2a
Recovered registered event set from this pass:
0x13d0x4430x1420x1410x1430x23f0x43e0x41f0x4170x4310x4110x4100x4410x4210x22d
Current direct callers are still only:
13a0:009bfromusecode_debugger_open_for_current_unit13a0:0256fromusecode_debugger_open_modal
This strengthens one important boundary:
0x410is 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
0x410is rewritten to local state0x0e - case
0x0eclears 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
5. Retail still preserves a substantial debugger UI and command surface once the gump exists
The latest live CRUSADER.EXE decompile pass makes the surviving retail debugger capability map much clearer.
What retail can still do once a valid debugger object and gump already exist:
usecode_debugger_build_menubarstill builds the full hidden debugger menu bar with File, Run, Breakpoints, Search, and Data menus.usecode_debugger_translate_registered_eventstill translates the registered debugger/control event bundle into the local debugger command ids consumed byusecode_debugger_handle_event.usecode_debugger_handle_eventstill implements real debugger actions rather than decorative UI stubs: open file, run, break-next, single-step, go to line, watch, inspect, change global, search, search again, break to TDP, and watch clearing.usecode_debugger_source_pane_draw_visible_linesstill clamps the source viewport, highlights the current line, scans the breakpoint table for visible marks, and draws the loaded source rows from the.unkbuffer.usecode_debugger_source_pane_handle_pointer_eventstill converts pointer position into source line and column state and also drives scroll-style commands when the pointer is above or below the pane.- the child-pane split is now clearer in retail than in the earlier first-pass mirror list: one pane is the loaded source view, while the sibling
13a0:16ee/1791/193flane is a separate watch-pane create/draw/click surface.
The live retail file-loading path is now also much clearer than before.
usecode_debugger_source_pane_load_fileresets local source-pane cursor state, destroys any previous source buffer, allocates a fresh source-buffer object from the requested path, updates the pane's line-count field, and refreshes the child widgets.usecode_debugger_source_buffer_create_from_pathandusecode_debugger_source_buffer_open_from_pathstill build a dedicated far-memory file object, normalize the requested path, open the file, and pass it into the text-loader path.usecode_debugger_source_buffer_load_textstill reads the whole file into far memory, rejects obviously non-text inputs, and hands the buffer tousecode_debugger_source_buffer_split_lines_in_place.usecode_debugger_source_buffer_split_lines_in_placestill walks the loaded text, zero-terminates newline boundaries in place, and populates the per-line pointer table later consumed by draw, click, search, and goto-line logic.usecode_debugger_source_buffer_get_line_ptris still the shared accessor used by source draw, pointer handling, find/find-next, and source-pane command handling.
Retail source navigation helpers are now closed well enough to describe the pane behavior directly.
usecode_debugger_set_line_selectionclamps the requested line against the loaded file range, clears transient cursor state, optionally updates the anchor line, and forces a redraw.usecode_debugger_center_on_linestores the target current line, computes a top-of-window line from the visible row count, and then delegates tousecode_debugger_set_line_selection.- this means retail still preserves the ordinary source-browser workflow expected from the strings: load file, jump to line, center on current line, search through lines, and redraw the viewport around the active selection.
The watch-pane side is also more concrete now.
usecode_debugger_watch_pane_createstill allocates a real watch child gump and installs the watch-pane vtable.usecode_debugger_watch_pane_drawstill iterates the shared 10-slot watch table at1478:5580, asks the break-state callback table to format each populated watch entry, and highlights the selected row.usecode_debugger_watch_pane_handle_clickstill converts pointer Y position into a watch-row index, updates the selected watch slot, and triggers a repaint.- that is stronger evidence for
interactive watch list still survivesthan the earlier weakerwatch-related strings still existwording.
Current safest interpretation:
- retail did not merely keep a few string tables or unused menu labels
- retail still keeps a functioning debugger front-end shell with real search, breakpoint, watch, inspect, and source-view logic
- the main missing piece is still how execution ever reaches that shell with valid live state
6. Retail callback and reachability limits are still the decisive difference from Regret
The same live pass also tightened the what retail still cannot do side.
Current direct-caller state in live CRUSADER.EXE:
usecode_debugger_open_for_current_unitstill has no recovered callersusecode_debugger_open_modalstill has no recovered callersRemorse::UsecodeDebuggerBreakState::Createat1408:0000still has no recovered callersusecode_debugger_handle_eventis still only reached throughusecode_debugger_translate_registered_eventandusecode_debugger_forward_child_event
Current callback state is still the key retail blocker too:
Remorse::UsecodeDebuggerBreakState::OnBreakTriggeredNoopat1408:046fis still the live slot-0 callbackRemorse::UsecodeDebuggerBreakState::VtableSlot1ReturnZeroat1408:0474is still the live slot-1 callback- unlike Regret, retail still has no recovered bootstrap that rewires the break-state object onto a live frontend-aware vtable
So the retail-versus-Regret split is now even sharper:
- both builds preserve a large debugger UI/event subsystem
- retail still lacks the recovered object bootstrap and live callback target that would naturally open the debugger on break
- Regret keeps both the writer/bootstrap path and the vtable upgrade that turns break-state callback slot
0into a realopen_for_current_unitlaunch path
That is why retail still reads as functional debugger shell plus dormant break-state object, while Regret reads as same shell plus a surviving end-to-end open-on-break path.
One practical refinement from the latest retail pass is that the shell is not merely a static menu/window skeleton.
- the file-open path still reaches a real far-memory source-buffer loader
- the search path still walks live source lines through
usecode_debugger_source_buffer_get_line_ptr - the goto-line path still updates line selection through the same source-pane helpers used by current-line centering
- the watch path still stores, formats, selects, clears, and redraws real watch rows
So the remaining barrier is still entry/bootstrap, not lack of interior debugger behavior after entry.
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_presentat1420:0cdfis a real startup hook- it is called from
Init_Everythingat1048: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, andInterpreter_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
-uarchive 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 familiesSURCAMNS/SURCAMEWNPCTRIG
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/659ebootstrap
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-lauriejassica16~Ctrl+Q/ event0x410
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 debuggerprimitive
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 pathtoport or mimic one missing write/call - that is more likely to produce a truly minimal modification than another speculative retail patch chain
Retail Ghidra Naming Backlog
The current note corpus now supports a tighter retail seg109 naming batch than the live authoring summary currently reflects.
These are the most important retail debugger-side helpers to promote explicitly in the active CRUSADER.EXE database before any new patch design work:
13a0:2882=usecode_debugger_build_menubar13a0:088f=usecode_debugger_source_pane_create13a0:0ae8=usecode_debugger_source_pane_init_view_from_break_state13a0:0ba7=usecode_debugger_source_pane_handle_command13a0:0f16=usecode_debugger_source_pane_handle_pointer_event13a0:1088=usecode_debugger_source_line_copy_for_display13a0:1118=usecode_debugger_source_pane_draw_visible_lines13a0:1413=usecode_debugger_source_pane_clamp_viewport13a0:15ac=usecode_debugger_source_pane_load_file13a0:16ee=usecode_debugger_watch_pane_create13a0:1791=usecode_debugger_watch_pane_draw13a0:193f=usecode_debugger_watch_pane_handle_click13a0:1c2c=usecode_debugger_translate_registered_event13a0:1dc6=usecode_debugger_forward_child_event13a0:2c2e=usecode_debugger_source_buffer_create_from_path13a0:2ca0=usecode_debugger_source_buffer_destroy13a0:2d14=usecode_debugger_source_buffer_open_from_path13a0:2e0a=usecode_debugger_source_buffer_load_text13a0:2f4f=usecode_debugger_source_buffer_split_lines_in_place13a0:301d=usecode_debugger_source_buffer_get_line_ptr
Why this batch matters before more patching:
- it converts the remaining patch-target area from anonymous
FUN_13a0_xxxxbodies into named UI, event, and source-buffer lanes - it reduces the chance of patching the wrong helper when the debugger gump is already on-screen but still miswired
- it makes runtime-only experiments easier to reason about because the gump lifecycle, source loading, and event forwarding chain become legible in-session
The current strongest provenance for this retail batch is the combined retail 000b:* -> 13a0:* table in ne-segment1.md plus the one-to-one structural match against the now-closed Regret 1398:* family.
One live correction from the follow-up rename pass matters here: retail 13a0:16ee/1791/193f reads as a watch-pane constructor/draw/click trio, while the source-pane lane remains centered on 13a0:088f/0ae8/0ba7/0f16/1088/1118/1413/15ac. So the Regret-side structural match is still valuable, but the retail child-pane split is now sharper than the earlier first-pass list implied.
Practical Alternatives To Manual Hex Patching
The current blocker is no longer we do not know what to patch. It is the delivery path needs to stop depending on blind byte edits.
1. Runtime-only proof via DOSBox-X debugger or equivalent live memory tooling
This is now the cleanest first confirmation path.
Use it to:
- keep the retail executable on disk unchanged
- patch or seed the debugger object only in live memory
- prove whether a create/store/open sequence is sufficient before committing to any permanent binary patch
What this should target first:
- seeding
1478:659c/659ewith a valid debugger-state object - reusing the existing interpreter callback lane at
1418:049e..04b5 - testing whether
13a0:020dor the vtable callback path can open a stable debugger gump once state exists
This is especially attractive because it turns the current question from did we edit the NE file correctly? into does the runtime model itself actually work?
2. Scripted patch application to a writable clone, not manual hex editing
If a permanent retail patch is still wanted, the next step should be a reproducible patcher, not another manual byte-edit round.
That means:
- keep a dedicated writable clone of the executable
- store each patch as
address + expected old bytes + new bytes + reason - apply it through a scriptable patcher that validates the original bytes before writing
- regenerate the same patch on demand instead of hand-transcribing offsets each time
This can be done with PowerShell or Python against raw file offsets even if Ghidra export remains unreliable.
3. Use Ghidra only for analysis and verified byte plans
The current evidence does not support treating Ghidra export as the final patch-delivery mechanism for this lane.
What still works well:
- identify the correct callsite and byte budget in Ghidra
- annotate the patch rationale in-session
- test the control-flow hypothesis on a writable target
- then convert the verified result into an external patch manifest or launcher-side patcher
That keeps Ghidra in the role it is good at here: reverse-engineering and patch design, not blind final-binary distribution.
4. Keep -u as the low-risk data-side experiment surface
The -u override still does not solve the missing bootstrap, but it remains valuable for adjacent experiments that do not require byte writes.
Use it for:
- testing whether scripted monitor/camera/control families can get closer to debugger-adjacent compiled paths
- validating source-file and unit-name assumptions without touching the executable
- separating
data-side idea failedfrompatching workflow failed
It should stay in the toolbox, but it should not be mistaken for a direct replacement for the missing retail bootstrap.
Current Recommendation
If the goal is the minimum modification that still has a realistic chance to work, the order should now be:
- Promote the retail seg109 naming backlog above so the remaining debugger lanes are explicit in Ghidra.
- Use runtime-only memory seeding on a clean executable to prove or kill the bootstrap theory without committing file changes.
- Compare
REGRET.EXEand JP/ja/CRUSADER.EXEfor any surviving debugger bootstrap/writer that can replace a custom retail bootstrap. - Keep
-u/ replacementEUSECODE.FLXas the preferred low-risk experiment surface for any script-side proxy ideas. - Do not resume broader retail executable patching unless the runtime proof or cross-build pass yields one clear small patch plan that can be applied by script to a writable clone.
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:
- any write to the debugger global pointer equivalent of
1478:659c/659e - any caller of
1408:0000 Createor its build-specific equivalent - any direct caller of
usecode_debugger_open_for_current_unit - any direct caller of
usecode_debugger_open_modal - any non-stub debugger vtable slot replacing retail
1408:046f/1408:0474 - any command-line or cheat/debug hotkey path that lands near the seg109 wrappers or seg1408 constructor
- 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
-uand a replacementEUSECODE.FLXonly 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.