Crusader_Decomp/docs/remorse-class-lift-index.md

11 KiB

Remorse Class-Lift Work Index

Purpose

This note is the easy-to-find landing page for the current Remorse C++ and class-lifting preparation work.

Use it as the starting point when the project returns to:

  • class and namespace authoring inside Ghidra
  • vtable and instance-layout promotion
  • hand-maintained C++ skeleton emission
  • ABI-safe source reconstruction planning

This index does not replace the detailed notes. It groups them into one work order so later implementation can resume quickly.

Read This First

  1. docs/remorse-cpp-decompilation-plan.md
  2. docs/remorse-class-candidate-inventory.md
  3. docs/remorse-rebuild-abi-notes.md
  4. docs/ghidra-mcp-class-lifting-endpoint-spec.md

That set gives the high-level target, the current candidate families, the rebuild constraints, and the future MCP authoring surface.

Current Note Groups

1. Overall Direction

2. Candidate Inventory And Tooling Surface

3. Family-Specific Layout Notes

4. Execution Checklists

Stage 1: Keep The Evidence Model Honest

  1. Re-read the plan, ABI note, and candidate inventory.
  2. Pick one family with bounded ambiguity.
  3. Confirm ctor, dtor, vtable root, and stable field groups before any class ownership changes in Ghidra.

Best current pilot families:

  1. EntityDispatchEntry
  2. SpriteNode
  3. EntityVmOwnerResource

Entity remains a top-priority family, but it should be split deliberately rather than promoted as one giant class too early.

Stage 2: Ghidra Authoring Pass

  1. Create class or namespace owners.
  2. Move only strongly owned methods first.
  3. Create provisional instance structs and vtable structs.
  4. Preserve slot order and unresolved fields instead of trying to beautify them.

The future MCP endpoint sequence should follow the spec note rather than ad hoc scripting.

Stage 3: First C++ Skeleton Slice

  1. Emit one header/source pair for the pilot family.
  2. Build it against the compatibility layer rather than raw host C++ alone.
  3. Keep unresolved offsets as named placeholders instead of collapsing them into speculative semantics.
  4. Record which parts are Track A safe versus Track B convenience-only.

Immediate Follow-Ups Still Open

  1. Convert the first family note into a hand-maintained C++ skeleton once the compatibility header is accepted.
  2. Implement the MCP class/vtable authoring endpoints only after the workflow and note set above are stable enough to drive them.
  3. Add one more dedicated note for the callback/object lane around 0x4588 only if later caller evidence supports a stronger subsystem name than PresentationCallbackBroker.
  4. Turn the first-class-authoring checklist into a completed execution log once the first real MCP batch lands.

Current Live Authoring Snapshot

The live CRUSADER.EXE class-authoring lane is no longer just a plan.

Current authored Remorse classes in the active database are:

  • EntityDispatchEntry
  • EntityVmOwnerResource
  • EntityVmRuntime
  • EntityVmContext
  • EntityVmSlotEntry

The VM lane is still the furthest along in actual Ghidra authoring. Recent live batches added the bounded EntityVmSlotEntry class owner plus more owned EntityVmRuntime methods (GetSlotChunkPtrAtOffset, ReleaseSlotChunkRef, TryUnloadSlotChunk, DebugDumpSlotMemory, ApplyToMatchingOwnerRows) rather than stopping at free-function naming.

The next planned pilot family is no longer purely preparatory either. Remorse::EntityDispatchEntry now exists as a real class owner in-session with a first provisional /Remorse/EntityDispatchEntryBase datatype covering the stable field block through +0x18 and a matching /Remorse/EntityDispatchEntryVtable datatype exposing only the verified +0x14 and +0x28 callback slots. The first base-method batch has also landed from the old 0008: note cluster after re-anchoring that range onto the live 11e0: process-substrate segment: InitBase, SetSourceType, SetEventTypeChecked, SetGroupId, Unlink, and IncrementGroupId now live under the class owner with provenance comments preserved.

That family also has its first derived slice now. The old 000d:7e00/8078 runtime-state pair is re-anchored in the live 1440: fade/palette cluster as InitRuntimeState and ReleaseRuntimeState, and /Remorse/EntityDispatchEntryRuntimeState now exists as a provisional overlay datatype with the recovered +0x40..+0x4c runtime-state tail fields. That is a meaningful pause point because the pilot family now has a class owner, a base datatype, a vtable shell, a first base-method batch, and one concrete derived/runtime-state batch rather than just one isolated constructor lane.

The latest signature-recovery pass also tightened two of those runtime methods materially:

  • GetSlotChunkPtrAtOffset(runtime_farptr, slot_index, chunk_index, intra_chunk_offset) now reads as a real slot-chunk accessor instead of a five-word anonymous wrapper.
  • ApplyToMatchingOwnerRows(runtime_farptr, slot_index_filter, chunk_index_filter) now reads as a real iterator/filter helper instead of a split-word scratch signature.

The next live batch pushed that further still: most of the EntityVmRuntime method cluster now carries an explicit 4-byte EntityVmRuntime * this in-session, including Create. The main remaining type gap inside that class is no longer the runtime object itself, but the exact far slot-entry pointer positions on AcquireSlotForEntity and InitSlotOwnerBuffers.

That VM-side gap is now closed too: AcquireSlotForEntity returns EntityVmSlotEntry *32 in DX:AX, InitSlotOwnerBuffers now accepts EntityVmSlotEntry *32, EntityVmOwnerResource::{Create,Destroy} now carry explicit 4-byte this, and the simple EntityVmContext lifecycle methods now do the same.

The next family switch has also landed in the live database: Remorse::UsecodeDebuggerBreakState now exists as a real class owner with a provisional 0x2f2 datatype and a stronger method batch (Create, MaybeBreakOnCurrentLine, BreakpointInsertSorted, BreakpointRemove, HasBreakpoint, CallstackPushFrame, CallstackPushEntry, CallstackPopEntry, EnableSingleStep, ClearStepState, CurrentEntryGetUnitName).

That debugger family is no longer just a top-level shell. The internal record shapes are now recovered and applied live well enough to treat the two tables as real fixed-size arrays in-session: breakpoint entries are 0x0b bytes with unit_name_inline[9] + line_number, and callstack entries are 0x15 bytes with unit_name_inline[9] plus the currently safest trailing fields source_stream_cursor_farptr, current_frame_payload_farptr, and still-neutral aux_farptr.

The next debugger pass tightened the bounded helper and callback edges too. 1408:0230 now lives under Remorse::UsecodeDebuggerBreakState::BreakpointFindFirstForUnitAtOrAfterLine instead of as an anonymous seg1408 helper, and the retail vtable root at 1478:65ab is no longer a blind spot: slot 0 resolves to OnBreakTriggeredNoop and slot 1 resolves to VtableSlot1ReturnZero, which keeps the class surface honest about the shipped build's inert break callbacks while leaving non-retail behavior open.

The follow-up seg109 consumer pass is also done now. 13a0:0291 and its local helper 13a0:045c are documented in-session as the first concrete consumers of the current callstack entry's trailing payload: they read entry +0x09 as a descriptor/source-stream cursor and entry +0x0d as the current-frame payload context while formatting debugger dump/watch text. That cursor name is now promoted into the live /Remorse/UsecodeDebuggerCallstackEntry datatype and the CallstackPushFrame signature too, which means the remaining open callstack question is mostly the unused aux_farptr lane rather than the first two dwords.

The VM lane also advanced one more selective step without overpromoting inheritance: Remorse::EntityVmContext::CreateFromSlotIndex now has a caller-backed mixed parameter pack (owner_source_farptr, pitemno_farptr, mode_flags, slot_index, value_add_offset, intra_chunk_offset, ucparam_farptr, ucparamsize) and an explicit far return restored in AX:DX, even though the current live endpoint still textualizes that repaired signature conservatively as plain dword __cdecl.

Bottom Line

The current prep work is now large enough that it should be treated as one coordinated lane rather than scattered notes.

Use this file as the resume point for future class-lift and C++ reconstruction work.