- Introduced new file `vm_mask_ladder.tsv` containing detailed mappings for Crusader USECODE VM masks and their associated descriptors. - Added comprehensive documentation in `scummvm-crusader-reference.md` outlining the structure, findings, and implications for reverse-engineering the Crusader engine within ScummVM. - Created `usecode-roundtrip-ir.md` to document the plan for converting Crusader USECODE bytes into a human-readable format, detailing the container layout, event names, and intrinsic tables. - Implemented a PowerShell script `temp_usecode_sample.ps1` for extracting and analyzing USECODE data from the Crusader FLX files, providing insights into class and event structures.
20 KiB
ScummVM Crusader Reference
Purpose
This note catalogs the Crusader-specific code inside ScummVM's Ultima 8 engine so it can be used as a planning aid for Crusader reverse-engineering work.
Primary source tree: K:\misc\scummvm\engines\ultima\ultima8
Important limitation: this is a high-level reimplementation, not a symbol map for the original DOS binaries. It is most useful for:
- identifying original data files and container formats
- naming likely subsystem boundaries
- understanding USECODE VM and event structure
- spotting Remorse versus Regret divergences
- finding concrete file-format footholds for parsers and validators
It is not sufficient on its own for direct raw-function renaming.
Highest-Value Findings
-
ScummVM keeps a Crusader-specific USECODE description layer with named event ids and large intrinsic signature tables. Files:
usecode/uc_machine.cpp,usecode/usecode_flex.cpp,convert/crusader/convert_usecode_crusader.h,convert/crusader/convert_usecode_regret.h,usecode/remorse_intrinsics.h,usecode/regret_intrinsics.h. -
ScummVM has explicit parsers for the core Crusader container families used by gameplay assets: FLEX archives, raw archives, USECODE containers, shapes, sound archives, speech archives, save files, and movie subtitle files. Files:
filesys/flex_file.cpp,filesys/archive.cpp,filesys/raw_archive.cpp,usecode/usecode_flex.cpp,audio/sound_flex.cpp,audio/speech_flex.cpp,filesys/savegame.cpp,gumps/movie_gump.cpp. -
Crusader-specific gameplay metadata is loaded centrally from a predictable file set. File:
games/game_data.cpp. This is the best ScummVM-side inventory of original asset families to compare against current RE notes. -
World and item loading diverge for Crusader in a few concrete ways that likely reflect real original-engine differences. Files:
world/map.cpp,world/current_map.cpp,world/item_factory.cpp,gfx/shape_info.cpp,world/weapon_info.h,world/world.cpp,world/egg.cpp. -
Crusader UI, media, and player-control code is separated into clear game-specific files. Files:
gumps/cru_*.cpp,world/actors/cru_avatar_mover_process.cpp,audio/cru_music_process.cpp,games/start_crusader_process.cpp,games/cru_game.cpp.
Detection, Boot, and Game Split
metaengine.cpp
- ScummVM treats Ultima 8 and Crusader as one engine family but gives Crusader its own control map.
- The Crusader keymap is a useful external reference for action vocabulary: weapon cycling, inventory cycling, medikit, energy cube, bomb detonation, search/select item, use selection, grab item, attack, center camera on player, jump/roll/crouch, sidesteps, rolls, and crouch toggle.
querySaveMetaInfos()usesSavegameReader, which is the entry point for ScummVM-side Crusader save metadata.
ultima8.cpp
- Engine startup registers Crusader-specific process loaders such as
CruAvatarMoverProcess,CruPathfinderProcess, andCruMusicProcess. initializePath()explicitly adds adatasubdirectory for at least one Regret variant.
games/cru_game.cpp
loadFiles()loads Crusader palettes fromstatic/gamepal.pal,cred.pal,diff.pal,misc.pal,misc2.pal, and optionallystar.pal.loadFiles()then callsGameData::loadRemorseData(), which is the central Crusader asset-loader in ScummVM.startGame()creates the main actor with shape1, reserves object ids384..511, initializes HP and energy-like stats fromNPCDat, and switches to map0.playIntroMovie()usesT01andT02for Remorse,originandANIM01for Regret, and warns thatFLICSandSOUNDdirectories must be copied from the CD.
games/start_crusader_process.cpp
- Startup sequence is explicit: intro movie 1, intro movie 2, difficulty menu, then live game setup.
- ScummVM creates the Crusader HUD gumps (
CruStatusGump,CruPickupAreaGump) before normal play begins. - It seeds inventory with shape
0x4d4(datalink) and0x598(smiley), sets shield type, teleports the actor through map1, egg0x1e, and applies a Regret-specific combat-ready start state. - This file is a good checklist for early-game object ids, item shapes, and startup-only side effects.
Core Asset Loading
games/game_data.cpp
GameData::loadRemorseData() is the single best source-file summary of original Crusader asset families known to ScummVM.
Loaded files and why they matter:
static/fixed.dat: fixed-object archive for world/map loading.usecode/<lang>usecode.flx: main USECODE container.static/shapes.flx: main shape archive, loaded with Crusader-specific shape format.remorseweapons.iniorregretweapons.ini: ScummVM-maintained weapon metadata overlays.remorsegame.ini: ScummVM-maintained game config overlay.static/typeflag.dat: per-shape type flags.static/anim.dat: animation metadata.static/wpnovlay.dat: weapon overlay metadata.static/glob.flx: glob data loaded intoMapGlobobjects.static/fonts.flx: font archive.static/mouse.shp: cursor shapes.static/gumps.flx: UI art.static/dtable.flx: NPC data table (NPCDat).static/damage.flx: damage data consumed by main shape logic.sound/sound.flx: sound archive.sound/<lang><shape>.flx: speech per shape, loaded lazily bygetSpeechFlex().
Implication for RE:
- This gives a concrete file-driven decomposition of the engine: world placement, usecode, shape/type metadata, overlay metadata, NPC tables, damage rules, UI art, sound, and speech are all separated.
dtable.flx,damage.flx,glob.flx, andwpnovlay.datshould be treated as high-value parser targets if they are not already covered in local tooling.
Container and File-Format Evidence
filesys/flex_file.cpp
- FLEX detection looks for a padded header region filled with
0x1A. - Metadata reader uses:
- table offset
0x80 - entry count at file offset
0x54 - 8-byte table entries of
<offset, size>
- table offset
- ScummVM rejects counts above
4095and notes that the largest observed Crusader/U8 FLEX has3074entries.
Implication for RE:
- This strongly matches the currently validated EUSECODE/FLEX structure already recovered locally.
- It also gives a second independent implementation to compare against any local extractor edge cases.
filesys/archive.cpp and filesys/raw_archive.cpp
Archivelayers multipleFlexFilesources and resolves objects from newest source to oldest source.RawArchivecaches raw object bytes and exposes them as memory streams.
Implication for RE:
- If any Crusader resources use overlay-style replacement behavior, ScummVM already models that archive precedence.
- This is worth checking before assuming a single-file source of truth for a given object id.
usecode/usecode_flex.cpp
- USECODE classes are addressed as
classid + 2inside the archive. - Class names are read from object
1atname_object + 4 + 13 * classid. - For Crusader, class base offset is read from bytes
8..11of the class object and decremented by1. - Crusader event count is computed as
(get_class_base_offset(classid) + 19) / 6.
Implication for RE:
- This is directly relevant to current USECODE work. It provides ScummVM's concrete interpretation of the Crusader class header layout and event-table sizing.
- If local EUSECODE or USECODE parsing still has uncertainties around header size, entry table layout, or event count, this file is the first external cross-check to apply.
USECODE VM, Events, and Intrinsics
usecode/uc_machine.cpp
- Crusader uses a
ByteSet(0x1000)global-state store, unlike the U8BitSetpath. - Remorse initializes global
0x003cto avatar number1; Regret initializes global0x001e. - The VM selects
ConvertUsecodeCrusaderfor Remorse andConvertUsecodeRegretfor Regret.
Implication for RE:
- This is concrete evidence that the Crusader VM/global model diverges from U8 enough that it should not be treated as a drop-in match.
- The initialized global slots are worth comparing against already-known runtime globals in the raw executable.
convert/crusader/convert_usecode_crusader.h
- ScummVM ships a named Crusader event table for event ids
0x00..0x1f. - Named events include
look,use,anim,setActivity,cachein,hit,gotHit,hatch,schedule,release,equip,unequip,combine,calledFromAnim,enterFastArea,leaveFastArea,avatarStoleSomething,animGetHit, andunhatch. - The same file also includes a large 512-entry intrinsic signature table with many behavior comments extracted from prior Pentagram reverse-engineering.
convert/crusader/convert_usecode_regret.h
- Regret reuses the Crusader event-name table but has a different intrinsic numbering/signature map.
usecode/remorse_intrinsics.h and usecode/regret_intrinsics.h
- These provide the live intrinsic dispatch tables used by the engine.
- High-value entries for current RE include weapon firing, status/quality accessors, object creation/destruction, camera moves, palette fades, movie playback, teleport-to-egg, keycard clearing, damage reception, and Crusader-specific audio calls.
High-value USECODE bridge examples from ScummVM's tables:
Item::I_fireWeaponAudioProcess::I_playSFXCruAudioProcess::I_playAmbientSFXCruStatusGump::I_hideStatusGump/I_showStatusGumpMovieGump::I_playMovieOverlayWorld::I_setControlledNPCNumMainActor::I_clrKeycardsPaletteFaderProcessfade/jump helpersEgg::I_getEggId,I_getEggXRange,I_setEggXRange
Implication for RE:
- These files are an immediate planning aid for USECODE annotation. Even where names are approximate, they constrain argument counts, broad behavior, and event purpose.
convert_usecode_crusader.his especially valuable because it records many comments of the form "based on disasm" or "same coff as", which likely came from earlier source-level Crusader RE.
Shapes, Type Flags, Weapons, and Item Families
convert/crusader/convert_shape_crusader.cpp
- ScummVM declares two Crusader-specific shape layouts:
CrusaderShapeFormatandCrusader2DShapeFormat. - The main 3D-ish shape format uses:
- 6-byte header
- 8-byte frame header
- 28-byte secondary frame header
- explicit width/height/xoff/yoff fields
- The 2D shape format uses a 20-byte secondary frame header.
Implication for RE:
- This is the quickest external reference for main-world versus UI/mouse/gump shape decoding.
gfx/shape_info.cpp
- Crusader type flags are decoded with a different bit layout than U8.
- ScummVM treats Crusader type-flag space as extending to at least bit
71, with several still-marked unknown ranges.
Implication for RE:
- Any local typeflag decoder should treat Crusader as its own layout, not as the U8 layout with extra cases.
world/weapon_info.h
- Crusader-specific weapon fields include
_sound,_reloadSound,_ammoType,_ammoShape,_displayGumpShape,_displayGumpFrame,_small,_clipSize,_energyUse,_field8, and_shotDelay.
Implication for RE:
- This header is a good target schema for interpreting weapon-related tables and shape metadata in the original data.
_field8is still uncertain in ScummVM, which is a useful warning not to over-claim its meaning in the raw game.
world/item_factory.cpp
- Crusader item families include
SF_CRUWEAPON,SF_CRUAMMO,SF_CRUBOMB, andSF_CRUINVITEM. - Item construction applies Crusader-only defaults:
- damage points from shape damage info
- weapon clip size copied into initial quality
- ammo and bomb quality initialized to
1
Implication for RE:
- This ties together shape family, shape damage info, weapon tables, and runtime item state.
- The quality field is confirmed as overloaded for ammo/clip counts and inventory stack-like quantities.
World, Maps, Eggs, and Cache-In Behavior
world/map.cpp
- Fixed and nonfixed map objects are read as 16-byte records.
- ScummVM reads each record as:
x= uint16y= uint16z= uint8shape= uint16frame= uint8flags= uint16quality= uint16npcNum= uint8mapNum= uint8next= uint16
- It then applies
World_FromUsecodeXY(x, y)before constructing items. - Container nesting is not read from a separate structure: the on-disk
xfield is temporarily treated as container depth while reading hierarchical contents.
Implication for RE:
- This is one of the most concrete format descriptions in the ScummVM codebase.
- It is directly useful for validating fixed/nonfixed parsers and for checking whether any currently unnamed raw loader functions correspond to this record layout.
world/current_map.cpp
- Crusader uses
_mapChunkSize = 1024; U8 uses512. - When loading a map, ScummVM always calls cache-in events in Crusader (
callCacheIn = (_currentMap != nullptr || GAME_IS_CRUSADER)). - It also explicitly calls actor cache-in events for Crusader after actor scheduling.
Implication for RE:
- Cache-in behavior appears more aggressive or more semantically important in Crusader than in U8.
- This may help explain some map-enter or object-activation behavior currently attributed to general dispatch code.
world/egg.cpp
- Crusader supports
unhatch()as a real egg event path; U8 does not. - Eggs store a
_hatchedstate and exposeget/set egg x/y rangeplusget/set egg idintrinsics.
Implication for RE:
unhatchis a strong clue for interpreting Crusader trigger/reset behavior.
world/world.cpp
- Crusader save/load stores extra world fields beyond the shared baseline:
- alert active
- difficulty
- controlled NPC number
- Vargas shield value
setAlertActiveRemorse()andsetAlertActiveRegret()search for concrete shape ids and mutate frames/shapes to update world-state visuals.setGameDifficulty()contains a Remorse-specific BA-40 ammo patch that modifies weapon metadata at runtime.
Implication for RE:
- Alert-state and difficulty are not just UI globals; ScummVM models them as world-affecting state with concrete shape mutations.
UI, Interaction, and Player-Control Code
gumps/cru_status_gump.cpp
- Crusader HUD is composed from five child gumps: weapon, ammo, inventory, health, and energy.
gumps/cru_weapon_gump.cpp, cru_ammo_gump.cpp, cru_inventory_gump.cpp
- HUD display is driven by weapon metadata fields such as
_displayGumpShape,_displayGumpFrame,_ammoShape, and livequalityvalues. CruAmmoGumpconfirms bullets are current weapon quality and reserve clips are counted from the first inventory item matchingammoShape.CruInventoryGumprenders the active inventory item through the weapon-info display fields and shows quantity whenquality > 1.
Implication for RE:
- These files are a good external model for active-weapon, ammo-reserve, and active-inventory state fields.
gumps/game_map_gump.cpp
- Double-click
userange is512in Crusader versus128in the shared path.
world/actors/cru_avatar_mover_process.cpp
- Crusader movement logic is explicitly different from U8 and models combat movement, one-shot moves, short jump, crouch, sidesteps, rolls, rebel-base special cases, and combat-angle smoothing.
Implication for RE:
- This file is a practical behavioral checklist when classifying input/combat locomotion code in the raw executable.
Audio, Speech, and Movies
audio/sound_flex.cpp
- Crusader
sound.flxdiffers from U8:- object
0contains an index whose entries start with a leading0x00or0xFF, then 3 bytes of extra data, then a null-terminated sound name ASFXentries are interpreted as 32-byte header plus raw 11025 Hz sample data
- object
- Non-
ASFXentries fall back to Sonarc decoding.
Implication for RE:
- This is one of the strongest container-format anchors in the ScummVM codebase.
- If local tooling still treats Crusader audio as opaque FLEX payloads, this file should drive the next parser pass.
audio/speech_flex.cpp
- Speech FLEX object
0is parsed as a sequence of null-terminated phrases. - Playback lookup is phrase-prefix based: ScummVM normalizes text and searches phrase table entries to map text to sound samples.
Implication for RE:
- Speech archives are not just sample banks; they embed text phrase indices.
- This can help tie dialog strings back to per-shape voice resources.
audio/cru_music_process.cpp
- Remorse and Regret have separate track name tables.
- Regret track
0x45means "use the current map's default track" via a hardcoded map-to-track table. - Remorse track
16cycles throughM16A,M16B, andM16C. - Music is loaded from
sound/<track>.amf.
Implication for RE:
- This is useful for identifying music-selection logic and map-to-music linkage in the original executable.
gumps/movie_gump.cpp
- Crusader movie playback uses AVI files under
flics/. - Subtitle loading accepts either
.txtor.iffsidecar files. - ScummVM normalizes certain movie names because USECODE references
mva1,mva3a,mva5a, etc., while files on disk may bemva01,mva03a,mva05a.
Implication for RE:
- This is a concrete example of ScummVM compensating for original asset-name/usecode mismatches.
- The subtitle
.ifffallback is a useful clue for unexplained IFF-like resources.
Save/Load Format
filesys/savegame.cpp
- ScummVM supports two save formats:
- native
VMU8saves with versioned file-entry archive payloads - older Pentagram zip-based saves
- native
- Native saves use a 12-byte file name field and per-entry size/data blocks.
Implication for RE:
- This is mostly relevant to ScummVM compatibility, not original DOS save format recovery.
- It still matters because ScummVM serializes engine state explicitly enough to reveal which runtime fields it considers necessary for Crusader continuity.
Best Files For Immediate RE Follow-Up
If time is limited, the most valuable ScummVM files to mine first are:
-
games/game_data.cppWhy: best single inventory of Crusader data files and subsystems. -
usecode/usecode_flex.cppWhy: concrete Crusader USECODE class header and event-count interpretation. -
convert/crusader/convert_usecode_crusader.hWhy: named event ids plus a large intrinsic-signature table with comments. -
audio/sound_flex.cppWhy: concrete Crusader sound archive interpretation. -
world/map.cppWhy: concrete fixed/nonfixed map record layout and container nesting behavior. -
world/weapon_info.handworld/item_factory.cppWhy: practical schema for weapon/ammo/inventory metadata. -
gumps/movie_gump.cppWhy: movie filename normalization and subtitle sidecar handling. -
world/current_map.cppandworld/world.cppWhy: Crusader-only cache-in, alert-state, difficulty, and map chunk differences.
Suggested RE Uses In This Repo
USECODE parsing
- Compare local USECODE/EUSECODE container assumptions against
usecode/usecode_flex.cpp. - Import ScummVM's event-name table as a conservative annotation source for event ids
0x00..0x1f. - Use
convert_usecode_crusader.handremorse_intrinsics.has a cross-check for intrinsic numbering, argument counts, and broad semantics. - Compare Remorse versus Regret intrinsic numbering before assuming one numbering scheme is universal.
Data-format work
- Validate local FLEX readers against
filesys/flex_file.cpp. - Prioritize parsers for
dtable.flx,damage.flx,glob.flx, andwpnovlay.datbecause ScummVM treats them as core runtime inputs. - Split shape decoding between Crusader main shapes and 2D/gump shapes using
convert_shape_crusader.cpp. - Treat
sound.flxand speech FLEX files as structured formats, not opaque blob stores.
Raw executable classification
- Use ScummVM's subsystem boundaries to guide search targets for:
- cache-in and unhatch event paths
- alert-state world mutations
- map chunking and area search behavior
- weapon clip/ammo/energy metadata consumers
- movie name normalization and subtitle loading
- Regret map-to-track music selection
Conservative Takeaways
- ScummVM does not directly solve raw-symbol naming, but it materially sharpens the planning surface for Crusader RE.
- The most actionable ScummVM contributions are format schemas, event/intrinsic vocabularies, and subsystem boundaries.
- For current repo priorities, the strongest leverage is on USECODE parsing, data-file parser expansion, and validation of world/object metadata structures.