Pseudocode and stuff
This commit is contained in:
parent
7310c4fe96
commit
ee33f94b4b
466 changed files with 27770 additions and 276 deletions
|
|
@ -320,6 +320,81 @@ What `-laurie` appears to enable by itself:
|
|||
- It is also enough to permit the separate event-`0x7e` runtime toggle path, whose entire job is to flip `0x6045` later and post the `0x6087` / `0x6091` notifications.
|
||||
- It is **not** enough for low-level keyboard-only cheat branches that check `0x6045` directly. That is why the user can see the `0x844`-gated debug-box behavior while plain F10 still behaves as if full keyboard cheats are off.
|
||||
|
||||
### Keyboard folklore correction pass (`CRUSADER.EXE` live NE)
|
||||
|
||||
- `~` is real in this build. In `World_HandleKeyboardInput_13e8_14b4`, the branch at `13e8:202d..203d` requires `0x844 != 0`, flips the live keyboard-cheat latch `0x6045`, and posts the paired on/off messages at `0x6087` / `0x6091`.
|
||||
- `Ctrl+C` is **not** the current-location popup in this build. The actual location-display branch is `Ctrl+L` (`0x426`), which formats the avatar's X/Y/Z into the `1478:610c` string and displays it at `13e8:255e..25ac`.
|
||||
- The third F7-family overlay is also real. The three cheat-gated overlay toggles live at `13e8:1a7c` (`0x141`), `13e8:1a50` (`0x241`), and `13e8:1a20` (`0x441`), writing `1478:2bca`, `1478:2bc9`, and `1478:0ee0` respectively before forcing a camera refresh through `[g_cameraProcess+0x2c]`.
|
||||
- The unresolved online entry is therefore best corrected as `Ctrl+F7 exists but is cheat-gated`; the obviously bogus list item is `Ctrl+C`, which should be `Ctrl+L` for the coordinate popup.
|
||||
|
||||
### `~` versus `jassica16`
|
||||
|
||||
- These are **not** the same mechanism. `jassica16` is handled earlier in `Key_CheckCheatToggle` as a raw scan-code matcher over `1478:2833`, while `~` is handled later in `World_HandleKeyboardInput_13e8_14b4` as a translated logical key value (`0x7e`).
|
||||
- That means Shift is probably normal behavior here, not a DOSBox-only artifact. On a standard US DOS keyboard layout, plain grave is `` ` `` (`0x60`) and Shift+grave is `~` (`0x7e`); the live handler we recovered is the `0x7e` branch, and no separate backtick/`0x60` hotkey path was recovered in this handler.
|
||||
- DOSBox can still affect host-to-DOS layout mapping, but the recovered game logic is asking for the translated `~` character value, not the raw grave-key scancode. So on a US layout the expected in-game gesture is Shift+grave.
|
||||
- `jassica16` is the stronger unlock path. On success it sets `0x8c52 = 1`, toggles both `0x844` and `0x6045`, emits the `0x103` voice/helper side effect, and shows the `1478:287b` / `1478:2892` cheat-state modals.
|
||||
- Plain `~` is narrower. It only works after the broader `0x844` gate is already on, and then it flips just the live keyboard-cheat latch `0x6045` while showing the `0x6087` / `0x6091` on/off messages.
|
||||
- Practically: `jassica16` can bootstrap cheats from a cold state because it toggles the master gate and the low-level latch together; `~` cannot bootstrap that state by itself because its own branch first requires `0x844 != 0`.
|
||||
- The extra `0x8c52` write also means `jassica16` is not just a different UI for the same toggle. It leaves a second latch behind that later keyboard-side cheat/debug branches can test, while plain `~` does not touch that latch.
|
||||
|
||||
### What `Ctrl+F7` is supposed to show
|
||||
|
||||
- `Ctrl+F7` (`13e8:1a20` -> `1478:0ee0`) is not just a third generic grid. In the camera redraw path, `Camera_1180_15ef` treats `0x0ee0` differently from the simpler `0x2bca` grid flag.
|
||||
- Plain `F7` (`1478:2bca`) draws a coarse `3 x 3` isometric world-cell grid around the camera by stepping `0x200` world units and drawing diamond tile boundaries.
|
||||
- `Alt+F7` (`1478:2bc9`) calls `Snap_1058_0814`, which walks the snap-process egg list and draws per-entry diamonds from packed egg range data. That is closer to a snap/trigger coverage overlay than to a camera-aligned background grid.
|
||||
- `Ctrl+F7` (`1478:0ee0`) walks `EggHatcherProcess` objects and calls `EggHatcher_1090_0921`, which draws diamond trigger ranges using `Egg_GetXRange`, `Egg_GetYRange`, and the shared diamond helper at `1180:1ce5`.
|
||||
- So the intended visual for `Ctrl+F7` is an **egg / hatch trigger-range overlay**. It should highlight live egg-hatcher or monster-egg regions, not fill the screen with a regular map grid.
|
||||
- That also explains why it can appear to do nothing. If the current map has no live `EggHatcherProcess` objects, or a non-monster-egg trigger has already hatched while normal gameplay input is active, the draw loop has nothing eligible to outline even though the toggle flag itself changed.
|
||||
|
||||
### Egg-Hatcher Runtime Notes
|
||||
|
||||
- The live NE runtime has a dedicated egg-hatcher process type: `EggHatcher_CreateProcess` builds process type `0x20f`, stores one target item number in the process, and names it `EggHatcher`.
|
||||
- The corresponding runner `EggHatcherProcess_Run` reads the avatar footprint and compares it against the egg item's center, `Egg_GetXRange`, `Egg_GetYRange`, and a vertical window of about `+/- 0x30` Z units.
|
||||
- For non-monster egg families, entering that range sets the process `ishatched` byte and calls `Item_CallHatchEvent`; leaving the range clears `ishatched` and calls `Item_CallUnhatchEvent`.
|
||||
- The overlay helper `EggHatcher_1090_0921` uses the same range values to draw the visible diamond outlines for `Ctrl+F7`. In other words, the visualizer is showing the same trigger footprint the runtime is testing against.
|
||||
- Current safest gameplay-facing read: these "eggs" are hidden trigger items that fire enter/leave behaviors when the avatar crosses their footprint. Some are likely classic trap/trigger/controller eggs rather than literal spawn objects.
|
||||
- The wider workspace evidence agrees with that interpretation. The extracted USECODE corpus contains egg-like labels such as `TRIGEGG`, `ONCEEGG`, `DOOREGG`, `LAZEREGG`, `STEAMEGG`, `MISS1EGG`, `GRENEGG`, and `REB_EGG`, while the older `crusader-disasm` notes explicitly call out `SnapEgg` as a fast-area participant.
|
||||
|
||||
### How To Find One In Practice
|
||||
|
||||
- The most reliable bootstrap is now: start with `-laurie`, then press `Shift+~` to flip the live keyboard cheat latch on, then use the F7-family overlays.
|
||||
- Use `Ctrl+F7` when you want true egg-hatcher ranges. If nothing appears, switch to `Alt+F7` too; that overlay walks the snap-process egg list and can reveal related trigger coverage that is not currently present in the live egg-hatcher process list.
|
||||
- Search in gameplay spaces that are likely controlled by hidden trigger items rather than by obvious UI state: door thresholds, ambush spawn zones, laser or steam hazards, missile/grenade trap corridors, teleporter pads, and one-shot scripted encounter rooms.
|
||||
- If a region only reacts once, walk out of the area and re-enter it. The live runner explicitly tracks enter/leave transitions and only calls the hatch/unhatch events when the avatar crosses the trigger boundary.
|
||||
- Monster-egg handling still looks special-cased, so a blank `Ctrl+F7` result does not prove a map has no egg-related logic at all; it only proves there are no eligible live `EggHatcherProcess` outlines being drawn at that moment.
|
||||
|
||||
### Cheat / Debug Key Matrix
|
||||
|
||||
Cheat-state legend used below:
|
||||
|
||||
- `master gate (0x844)`: broader Laurie/debug permission state. `-laurie` sets this directly.
|
||||
- `keyboard latch (0x6045)`: full keyboard-cheat latch used by the low-level F10 path and some other direct keyboard branches.
|
||||
- `sequence latch (0x8c52)`: extra post-sequence latch left behind by `jassica16`; plain `~` does not set it.
|
||||
- `input-active gate (0x85f)`: broader gameplay-input state. This is not a cheat bit, but it still suppresses some option-key behavior when the game is in modal/non-gameplay states.
|
||||
|
||||
| Input | State Needed | Effect | Evidence / Notes |
|
||||
|------|--------------|--------|------------------|
|
||||
| `-laurie` | none at startup | Sets the `master gate (0x844)` only | This is the easiest way to unlock the broader debug/event family without entering the hidden key sequence. |
|
||||
| `jassica16` | none | Toggles `0x844` and `0x6045`, sets `0x8c52`, emits `0x103`, shows cheat active/inactive modal | Raw scan-code matcher, not a translated text string. Can bootstrap full cheat state from cold. |
|
||||
| `Shift+~` / `~` | `0x844` already on | Toggles only `0x6045`; shows the on/off cheat-latch modal | On a US layout the game is checking logical `0x7e`, so Shift is the expected normal gesture. With `-laurie`, this becomes a practical way to enable full keyboard cheats. |
|
||||
| `F10` | `0x85f` and `0x6045` | Refill/loadout branch: restore/refill, credits, items, ammo, weapon set | This is the plain full-keyboard-cheat branch in `Key_HandleOptionKeys`. |
|
||||
| `Ctrl+F10` | `0x85f` and `0x6045`; current NPC alive | Toggles the current controlled NPC's immortal flag | Same F10 branch, but only the modifier-gated immortality sub-path. |
|
||||
| `Ctrl+L` | no cheat gate recovered | Shows current avatar X/Y/Z popup | Online `Ctrl+C` folklore is wrong for this build. |
|
||||
| `Ctrl+C` | no live branch recovered | No confirmed effect in this build | Best current correction is `Ctrl+L`. |
|
||||
| `Ctrl+V` | no cheat gate recovered | Displays internal stats / version / memory info | This branch appears to be available without the cheat gates. |
|
||||
| `Ctrl+Q` | `0x844` | Toggles CD transfer display state (`0x604f`) | Distinct from immortality. Uses the broader master gate, not the full keyboard latch. |
|
||||
| `F7` | `0x844` | Draws coarse `3 x 3` camera-aligned world-cell grid | Simple isometric background grid. |
|
||||
| `Alt+F7` | `0x844` | Draws snap-process egg diamonds | Better read as snap/trigger coverage overlay than generic grid. |
|
||||
| `Ctrl+F7` | `0x844` | Draws egg-hatcher trigger diamonds | Visualizes live `EggHatcherProcess` ranges; can look blank on maps without eligible live processes. |
|
||||
| `F` | `0x844` | Toggles object/framework paint overlay | Laurie/debug-side visual lane, not a full keyboard-cheat-latch feature. |
|
||||
| `H` | `0x844` and `0x8c52` | Toggles Hack Mover | Strongest current read is that this needs the broader Laurie/debug gate plus the extra post-`jassica16` sequence latch. |
|
||||
|
||||
Practical state recipes:
|
||||
|
||||
- `-laurie` by itself gives you the broader Laurie/debug event family (`0x844`) but not full keyboard cheats.
|
||||
- `-laurie` plus `Shift+~` is enough to reach the full keyboard-cheat state in live play, because `-laurie` supplies `0x844` and `~` then flips `0x6045`.
|
||||
- `jassica16` is still the most complete single-step unlock, because it toggles both cheat bytes together and also leaves the extra `0x8c52` sequence latch behind for later branches such as Hack Mover.
|
||||
|
||||
### Cheat-related string table (seg014 / `000e:xxxx`)
|
||||
|
||||
| Address | String | Notes |
|
||||
|
|
@ -411,30 +486,48 @@ Current strongest read:
|
|||
|
||||
**Secondary handler (000b:b62c):**
|
||||
|
||||
`cheat_event_listener_handle_event` (`000b:b62c`) receives event 0x410 through the registration installed by `cheat_event_listener_create` at `000b:b3b1`. When event 0x410 arrives, it writes state code `0xe` (decimal 14) into the event object's field `+0x6` and passes it to `000b:b7f3` for processing. This is a parallel state-machine path that runs alongside the 000c toggle; likely it drives an associated USECODE process or animation object into state 14.
|
||||
`usecode_debugger_handle_event` (`000b:b62c`, live `13a0:1df3`) receives event 0x410 through the registration installed by `usecode_debugger_gump_create` at `000b:b3b1` (`13a0:19b1`). When event 0x410 arrives, it writes state code `0xe` (decimal 14) into the event object's field `+0x6` and passes it to the shared debugger tail. This is a parallel state-machine path that runs alongside the 000c toggle; current best read is that it drives the hidden usecode-debugger state machine rather than a standalone cheat-only listener.
|
||||
|
||||
| Address | Symbol | Role |
|
||||
|-------------|-------------------------------|------|
|
||||
| `000b:b3b1` | `cheat_event_listener_create` | Allocates one seg109 listener object and subscribes it to the shared cheat/control event bundle that includes `0x410`. |
|
||||
| `000b:b62c` | `cheat_event_listener_handle_event` | Subscriber-side event mapper: rewrites incoming `0x410` to local state `0x0e` before entering the shared listener state machine. |
|
||||
| `000b:b3b1` | `usecode_debugger_gump_create` | Allocates the seg109 debugger gump object, initializes its panes/watch state, and subscribes it to the shared debugger/control event bundle that includes `0x410`. |
|
||||
| `000b:b62c` | `usecode_debugger_handle_event` | Debugger-side event mapper: rewrites incoming `0x410` to local state `0x0e` before entering the shared debugger state machine. |
|
||||
| `DS:0x604f` | `g_cdTransferDisplayActive` | Set/cleared by event `0x410`. |
|
||||
| `DS:0x60d2` | CD-transfer-on notification ptr | Near pointer in DS; resolves to far ptr → "CD TRANSFER DISPLAY ACTIVE." |
|
||||
| `DS:0x60ee` | CD-transfer-off notification ptr | Near pointer in DS; resolves to far ptr → "CD TRANSFER DISPLAY INACTIVE." |
|
||||
| `000a:b988` | `video_bios_state_snapshot` | Called after notification display in the 0x410 toggle to refresh screen state. |
|
||||
|
||||
### Hidden cheat menu investigation (seg109 UI lane)
|
||||
### Hidden usecode debugger investigation (seg109 UI lane)
|
||||
|
||||
New compiled-side evidence shows that the old "hidden cheat menu" label is misleading. The seg109 UI path is much closer to a hidden **usecode debugger / unit inspector** than to a retail cheat list:
|
||||
New compiled-side evidence shows that the older "hidden cheat menu" label was misleading. The recovered path is much closer to a hidden **usecode debugger / unit inspector** than to a retail cheat list.
|
||||
|
||||
| Address | Symbol | Role |
|
||||
|-------------|-------------------------------------|------|
|
||||
| `000b:9a86` | `usecode_debugger_open_for_current_unit` | Builds the seg109 debugger gump in current-unit mode, derives a usecode path from the live runtime context (`0x659c/0x659e`), loads that file, centers the current line, and then runs a modal UI loop. |
|
||||
| `000b:9c0d` | `usecode_debugger_open_modal` | Smaller generic modal wrapper that opens the same debugger UI without first preloading a current unit/file. |
|
||||
| `000b:b3b1` | `usecode_debugger_gump_create` | Constructor for the debugger object. Builds the panes/menu bar, initializes watch state, resolves the base `usecode` path, and registers the shared control-event bundle including `0x23f`, `0x410`, `0x411`, and `0x441`. |
|
||||
| `000b:b62c` | `usecode_debugger_handle_event` | Debugger event mapper; recovered cases are debugger-style commands (open unit/file, go to line, watch, inspect, clear watches, change global, find/search again, break to debugger). Event `0x23f` is reused as a local debugger-state command; event `0x410` remaps to local state `0x0e` before entering the shared tail. |
|
||||
| `000b:2882` | `usecode_debugger_build_menubar` | Builds the top-level debugger menus: File, Run, Breakpoints, Search, and Data. Recovered entries include `Open Unit`, `View File`, `Run to cursor`, `Trace into`, `Step over`, `Run until return`, `Toggle F2`, `Break to TDP`, `Find`, `Search again`, `Go to line`, `Watch`, `Inspect`, `Change Global`, and `Quit`. |
|
||||
The two address families that looked inconsistent in older notes are actually two connected layers of the same subsystem, not competing identifications:
|
||||
|
||||
This better explains the long-running negative result about the "infamous scrollable cheat menu": the hidden UI we can actually recover is not a plain scrollable cheat list at all. It is a modal debugger/unit-inspector front-end that expects valid usecode file context and developer-style command routing.
|
||||
- the `000b:*` / live `13a0:*` functions build and drive the modal debugger UI,
|
||||
- the `1408:*` helpers hold breakpoint/callstack state,
|
||||
- and the interpreter hook at `1418:04aa..04b5` ties the runtime back into that debugger state.
|
||||
|
||||
Combined debugger component table:
|
||||
|
||||
| Layer | Raw/reference anchor | Live NE Ghidra | Symbol | Verified role |
|
||||
|-------|----------------------|----------------|--------|---------------|
|
||||
| UI wrapper | `000b:9a86` | `13a0:0086` | `usecode_debugger_open_for_current_unit` | Builds the debugger gump in current-unit mode, resolves the active unit name from the live debugger state at `0x659c/0x659e`, loads the corresponding usecode file, centers on the current line, and enters the modal debugger UI. |
|
||||
| UI wrapper | `000b:9c0d` | `13a0:020d` | `usecode_debugger_open_modal` | Smaller generic modal wrapper for the same debugger gump; it skips current-unit preload and simply opens the modal UI. |
|
||||
| UI constructor | `000b:b3b1` | `13a0:19b1` | `usecode_debugger_gump_create` | Allocates the root debugger gump, builds the menu bar and panes, initializes watch state, resolves the base `usecode` path, and registers the shared debugger/control event bundle including `0x23f`, `0x410`, `0x411`, and `0x441`. |
|
||||
| UI dispatcher | `000b:b62c` | `13a0:1df3` | `usecode_debugger_handle_event` | Main debugger event dispatcher. Recovered cases are debugger-style commands: open unit/file, go to line, watch, inspect, clear watches, change global, find/search again, and break to TDP. Incoming event `0x410` is remapped to local state `0x0e`. |
|
||||
| UI menu builder | `000b:2882` | `13a0:2882` | `usecode_debugger_build_menubar` | Builds the hidden debugger menu bar with File, Run, Breakpoints, Search, and Data menus plus entries such as `Open Unit`, `View File`, `Run to cursor`, `Trace into`, `Step over`, `Run until return`, `Toggle F2`, `Break to TDP`, `Find`, `Search again`, `Go to line`, `Watch`, `Inspect`, `Change Global`, and `Quit`. |
|
||||
| Break-state constructor | n/a | `1408:0000` | `usecode_debugger_break_state_create` | Allocates and initializes the seg1408 debugger-state object: vtable, breakpoint table, current-entry stack, and run/step flags. This is the object the rest of the breakpoint lane appears to expect at `0x659c/0x659e`. |
|
||||
| Breakpoint gate | n/a | `1408:0053` | `usecode_debugger_maybe_break_on_current_line` | Stores the current interpreted line, resolves the active unit name through `1408:0444`, checks file+line breakpoints through `1408:029e`, evaluates run/step flags, and callbacks through the object's vtable when a break condition is met. |
|
||||
| Breakpoint helper | n/a | `1408:00dd` | `usecode_debugger_breakpoint_insert_sorted` | Inserts `(file,line)` breakpoint entries into the seg1408 table in sorted order. |
|
||||
| Breakpoint helper | n/a | `1408:029e` | `usecode_debugger_has_breakpoint` | Exact `(file,line)` membership test over the seg1408 breakpoint table. |
|
||||
| Callstack helper | n/a | `1408:03b0` | `usecode_debugger_callstack_push_entry` | Pushes the current unit/line debugger entry onto the seg1408 callstack. |
|
||||
| Callstack helper | n/a | `1408:03f7` | `usecode_debugger_callstack_pop_entry` | Pops one debugger callstack entry and asserts on underflow. |
|
||||
| Step-state helper | n/a | `1408:0419` | `usecode_debugger_enable_single_step` | Arms the single-step mode flags in the debugger-state object. |
|
||||
| Step-state helper | n/a | `1408:0432` | `usecode_debugger_clear_step_state` | Clears the break/step flags in the debugger-state object. |
|
||||
| Current-entry helper | n/a | `1408:0444` | `usecode_debugger_current_entry_get_unit_name` | Returns the active unit/file name pointer from the current debugger entry stack. |
|
||||
| Interpreter hook | n/a | `1418:04aa..04b5` | interpreter-side break callback site | Checks whether `0x659c/0x659e` is non-null, pushes the current interpreted line, then calls `1408:0053`. This is the concrete runtime handoff that links usecode execution to the hidden debugger state. |
|
||||
|
||||
This better explains the long-running negative result about the supposed scrollable cheat menu: the hidden UI we can actually recover is not a plain scrollable cheat list at all. It is a modal debugger/unit-inspector front-end that expects valid usecode file context and developer-style command routing.
|
||||
|
||||
#### Reachability status in retail binary
|
||||
|
||||
|
|
@ -443,27 +536,13 @@ This better explains the long-running negative result about the "infamous scroll
|
|||
- The cheat-code matcher `cheat_code_check` (`0007:0d0a`) toggles `0x844/0x6045` and emits event `0x103`; it does **not** call these menu wrappers directly.
|
||||
- The 000c handler for `0x103` (`000c:99dd`) executes a status/refresh lane and notification path; no direct call to `usecode_debugger_gump_create` appears there.
|
||||
|
||||
Current best read: this debugger path is compiled and functional at object level, but likely orphaned/hidden in final gameplay flow (possibly dev-only entry removed, or only reachable through non-recovered data-driven callback wiring). That orphaned status is a better fit for the missing retail cheat menu than assuming a still-live player-facing scrollable cheat list.
|
||||
Current best read: this debugger path is compiled and functional at object level, but likely orphaned/hidden in final gameplay flow (possibly dev-only entry removed, or only reachable through non-recovered data-driven callback wiring). That orphaned status is a better fit for the missing retail debugger entry than assuming a still-live player-facing scrollable cheat list.
|
||||
|
||||
#### Breakpoint callback lane (new strongest orphan candidate)
|
||||
|
||||
The next live NE pass shifts the strongest likely entry point away from the cheat-toggle helper and toward a surviving **usecode breakpoint callback lane** in seg1408/seg1418.
|
||||
The strongest likely original entry point is no longer the cheat-toggle helper. It is the surviving **usecode breakpoint callback lane** summarized in the combined table above.
|
||||
|
||||
New live renames in this lane:
|
||||
|
||||
| Live NE | Name | Role |
|
||||
|---------|------|------|
|
||||
| `1408:0000` | `usecode_debugger_break_state_create` | Allocates and initializes the seg1408 debugger-state object: breakpoint table, current-entry stack, and run-mode flags. |
|
||||
| `1408:0053` | `usecode_debugger_maybe_break_on_current_line` | Runtime breakpoint gate. Stores the current line into the debugger state, resolves the current unit/file name through `1408:0444`, checks the breakpoint table through `1408:029e`, and callbacks through the object's vtable when a break condition is met. |
|
||||
| `1408:00dd` | `usecode_debugger_breakpoint_insert_sorted` | Inserts `(file,line)` breakpoint entries into the seg1408 table in sorted order. |
|
||||
| `1408:029e` | `usecode_debugger_has_breakpoint` | Exact `(file,line)` membership test over the seg1408 breakpoint table. |
|
||||
| `1408:03b0` | `usecode_debugger_callstack_push_entry` | Pushes one current-unit/current-line debugger entry into the seg1408 stack. |
|
||||
| `1408:03f7` | `usecode_debugger_callstack_pop_entry` | Pops one debugger callstack/current-entry record. |
|
||||
| `1408:0419` | `usecode_debugger_enable_single_step` | Arms the step/run-state flags that make the next interpreter lane callback eligible. |
|
||||
| `1408:0432` | `usecode_debugger_clear_step_state` | Clears the step/run flags. |
|
||||
| `1408:0444` | `usecode_debugger_current_entry_get_unit_name` | Returns the active unit/file name pointer from the current debugger entry stack. |
|
||||
|
||||
This matters because `1418:04aa..04b5` is now comment-backed as a concrete interpreter-side handoff into `usecode_debugger_maybe_break_on_current_line`:
|
||||
The key live proof is the interpreter-side handoff at `1418:04aa..04b5`:
|
||||
|
||||
- it first checks whether `0x659c/0x659e` is non-null,
|
||||
- then pushes the current interpreted line,
|
||||
|
|
@ -521,8 +600,8 @@ Verified retail anchor points:
|
|||
| `0x70d75` | `0007:0d75` | cheat matcher emits event `0x103` | retail bytes = `68 03 01 9A FF FF 00 00 83 C4 02`; NE fixup source = `0007:0d79` -> `seg092:0476` |
|
||||
| `0x71d68` | fixup entry for `0007:0d79` | seg039 relocation record | exact retail entry: addr_type `0x03`, rel_type `0x00`, chain_off `0x2b79`, target `seg092:0476` |
|
||||
| `0xc99dd` | `000c:99dd` | later controller-side handler that also executes `push 0x103 / call 000a:5276` | retail fixup source = `000c:99e0` -> `seg092:0476`; this is the first materially safer deferred hook candidate after the direct matcher path failed |
|
||||
| `0xb9a8d` | `000b:9a8d` | arg setup inside `cheat_menu_open_from_current_slot` | original wrapper uses caller stack words `[BP+8]` and `[BP+6]` plus local armed flag `1` |
|
||||
| `0xb9c48` | `000b:9c48` | modal wrapper prologue; the inherited caller-word patch subsite is `000b:9c4e` / live `13a0:024a` | original wrapper still feeds caller stack words `[BP+8]` and `[BP+6]` into `cheat_event_listener_create`, but starts with local defaults `-1`, `-1`, `0` |
|
||||
| `0xb9a8d` | `000b:9a8d` | arg setup inside `usecode_debugger_open_for_current_unit` | wrapper-local constructor setup uses caller stack words `[BP+8]` and `[BP+6]` plus the current-unit mode flag `1` before calling `usecode_debugger_gump_create` |
|
||||
| `0xb9c48` | `000b:9c48` | modal wrapper prologue; the inherited caller-word patch subsite is `000b:9c4e` / live `13a0:024a` | wrapper-local setup still feeds caller stack words `[BP+8]` and `[BP+6]` into `usecode_debugger_gump_create`, but starts with local defaults `-1`, `-1`, `0` |
|
||||
|
||||
#### Live NE `CRUSADER.EXE` mapping in Ghidra
|
||||
|
||||
|
|
@ -565,7 +644,7 @@ One remaining function-hygiene caveat:
|
|||
What failed and why:
|
||||
|
||||
- Direct retarget of `0007:0d79` to `000b:9a86` crashed at startup when the NE relocation table was patched incorrectly as a raw far pointer. That was a file-format problem, not a semantic proof.
|
||||
- After the patcher was made NE-fixup-aware, direct retarget to `000b:9a86` no longer broke startup, but the game hung when the cheat actually fired. Disassembly shows why: `cheat_menu_open_from_current_slot` consumes caller-supplied words at `[BP+8]` and `[BP+6]`, so the cheat matcher context is the wrong stack shape.
|
||||
- After the patcher was made NE-fixup-aware, direct retarget to `000b:9a86` no longer broke startup, but the game hung when the cheat actually fired. Disassembly shows why: `usecode_debugger_open_for_current_unit` consumes caller-supplied words at `[BP+8]` and `[BP+6]`, so the cheat matcher context is the wrong stack shape.
|
||||
- Retargeting the same early cheat-matcher call to `000b:9c0d` got farther: the mouse pointer appeared, proving the hidden menu/display path was being entered. But it still hung with looping music, which points to **timing/context**, not a bad target address. The modal path appears unsafe when entered directly from the keyboard matcher even after the constructor args are forced to zero.
|
||||
- The narrower direct current-slot patch was then runtime-tested on `/Writable/CRUSADER-PATCHED.EXE` with bytes verified as `1130:2b78 = 9A 86 00 A0 13` and `13a0:008d = 6A 01 6A 00 6A 00 90 90`. User test result: the normal cheat-toggle path still appeared, but no hidden menu appeared. That closes the direct current-slot route as a practical candidate, not just a theoretical one.
|
||||
|
||||
|
|
@ -573,13 +652,13 @@ Current best patch rationale:
|
|||
|
||||
- `0007:0d75` remains the verified cheat-sequence success site, but the direct `1130:2b78 -> 13a0:0086` retarget is no longer the best live patch because it has now failed both analytically and at runtime.
|
||||
- The first materially safer deferred hook remains the controller-side `000c:99dd` lane, where the live call opcode begins at `13e8:25e0`. That path preserves the real `0x103` event context instead of substituting `0x42f`, which is the strongest evidence-backed difference from the rejected deferred experiment.
|
||||
- The chosen writable patch therefore restores `1130:2b78` to `CALLF 12d8:0476`, restores `13a0:008d` to the original current-slot wrapper bytes, retargets `13e8:25e0` to `13a0:020d` (`cheat_menu_open_modal`), and zeros only the inherited caller-word pushes at `13a0:024a` while preserving the modal wrapper's leading local defaults (`PUSH -1`, `PUSH -1`, `PUSH 0`).
|
||||
- The chosen writable patch therefore restores `1130:2b78` to `CALLF 12d8:0476`, restores `13a0:008d` to the original current-slot wrapper bytes, retargets `13e8:25e0` to `13a0:020d` (`usecode_debugger_open_modal`), and zeros only the inherited caller-word pushes at `13a0:024a` while preserving the modal wrapper's leading local defaults (`PUSH -1`, `PUSH -1`, `PUSH 0`).
|
||||
- The deferred `0x42f` branch remains negative evidence only: it proved the modal wrapper can enter the hidden UI path, but it also proved that substituting the event id or landing in the wrong deferred context trips the retail `FLEX.C` failure.
|
||||
|
||||
Rejected follow-up patch design:
|
||||
|
||||
- Site 1 tried changing `0007:0d75` from `push 0x103` to `push 0x42f`, keeping the original event-dispatch helper call intact.
|
||||
- Site 2 retargeted the `000c:99e1` relocation so the `0x42f` handler's internal `push 0x103 / call 000a:5276` sequence called `cheat_menu_open_modal` instead.
|
||||
- Site 2 retargeted the `000c:99e1` relocation so the `0x42f` handler's internal `push 0x103 / call 000a:5276` sequence called `usecode_debugger_open_modal` instead.
|
||||
- Site 3 patched `000b:9c48` from `6A 00 FF 76 08 FF 76 06` to `6A 00 6A 00 6A 00 90 90`.
|
||||
|
||||
Observed result on retail test build:
|
||||
|
|
@ -592,8 +671,8 @@ Current Ghidra-side patch plan for a copy:
|
|||
|
||||
1. Open the writable `/Writable/CRUSADER-PATCHED.EXE` program in Ghidra or PyGhidra, not the raw full-EXE database.
|
||||
2. Restore the disproven direct-hook sites: `1130:2b78` back to `9A 76 04 D8 12` (`CALLF 12d8:0476`) and `13a0:008d` back to `6A 01 FF 76 08 FF 76 06`.
|
||||
3. Navigate to the later controller-side `0x103` lane at `13e8:25e0` and retarget that `CALLF 12d8:0476` operand to `13a0:020d` (`cheat_menu_open_modal`), yielding bytes `9A 0D 02 A0 13`.
|
||||
4. Navigate to `13a0:024a` inside `cheat_menu_open_modal`. Replace only the inherited caller-frame pushes with `PUSH 0` / `PUSH 0` (`6A 00 6A 00 90 90`) and leave the leading `PUSH -1`, `PUSH -1`, `PUSH 0` defaults intact.
|
||||
3. Navigate to the later controller-side `0x103` lane at `13e8:25e0` and retarget that `CALLF 12d8:0476` operand to `13a0:020d` (`usecode_debugger_open_modal`), yielding bytes `9A 0D 02 A0 13`.
|
||||
4. Navigate to `13a0:024a` inside `usecode_debugger_open_modal`. Replace only the inherited caller-frame pushes with `PUSH 0` / `PUSH 0` (`6A 00 6A 00 90 90`) and leave the leading `PUSH -1`, `PUSH -1`, `PUSH 0` defaults intact.
|
||||
5. Do not reintroduce the `0x42f` substitution or the direct `13a0:0086` current-slot hook in the same test build. They are now negative evidence, not live candidates.
|
||||
|
||||
These edits are now applied and byte-verified on `/Writable/CRUSADER-PATCHED.EXE`. The live NE `CRUSADER.EXE` analysis database remains documentation-only for this batch.
|
||||
|
|
@ -601,7 +680,7 @@ These edits are now applied and byte-verified on `/Writable/CRUSADER-PATCHED.EXE
|
|||
Rationale for the revised wrapper patch:
|
||||
|
||||
- Earlier direct-hook attempts proved that inheriting the two caller-frame words at `000b:9a8f/9a92` is unsafe from the cheat matcher context.
|
||||
- But later decompilation of `cheat_event_listener_create` showed that the leading `push 0x1` at `000b:9a8d` is a distinct mode byte used by the constructor path, so zeroing all three pushed values was too aggressive.
|
||||
- But later decompilation of `usecode_debugger_gump_create` showed that the leading `push 0x1` at `000b:9a8d` is a distinct current-unit mode byte used by the constructor path, so zeroing all three pushed values was too aggressive.
|
||||
- The current patch therefore preserves the leading `1` and only forces the two ambiguous 16-bit parameters to zero.
|
||||
|
||||
Risk notes:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue