-
-
Notifications
You must be signed in to change notification settings - Fork 4
DM1 Reverse Engineering
This page documents the reverse-engineering knowledge base backing Firestaff's
Dungeon Master 1 (DOS PC 3.4, "I34E") implementation. The canonical reference
source is ReDMCSB, a reconstructed C decompilation of the original
Dungeon Master / Chaos Strikes Back engine. Firestaff comments cite ReDMCSB
files and F-numbered functions directly (ReDMCSB FILENAME.C:LINE,
F0NNN_FUNCTION_NAME), and DM1-specific compatibility modules live under
src/dm1/ and include/dm1_v1_*_pc34_compat.h.
All data below was extracted from the current Firestaff source tree with:
grep -rohE 'ReDMCSB [A-Z0-9_]+\.[CH]' include/dm1_v1_* src/dm1/ | sort -u
grep -rohE 'F[0-9]{4}_[A-Za-z0-9_]+' include/dm1_v1_* src/dm1/ | sort -u96 distinct ReDMCSB source/header filenames are referenced across DM1 compatibility headers and sources. They are grouped below by subsystem. Note that DM1 and CSB (Chaos Strikes Back) share the same PC 3.4 engine codebase in ReDMCSB, so many files serve both games.
| File | Purpose |
|---|---|
MAINLIB.C |
Core primitives: copy/clear bytes, blit-to-screen, random number generators (F0002-F0030) |
GAMELOOP.C |
Main game loop (F0002_MAIN_GameLoop), tick dispatch |
BASE.C |
Base runtime services, error display |
COMPILE.H |
Compile-time configuration switches |
DEFS.H |
Global constant/macro definitions (element types, thing types, sensor ranges) |
MEMORY.C |
Memory allocator (used-list / defrag model for GRAPHICS.DAT) |
DATA.C |
Static game data tables |
COORD.C |
Coordinate/direction math |
| File | Purpose |
|---|---|
STARTUP2.C |
Startup sequence, F0800-F0910 range functions |
ENTRANCE.C |
Dungeon entrance sequence |
TITLE.C |
Title screen |
SWSH.C |
FTL logo "swoosh" animation |
SWITCH.C / SWITCHMM.C
|
Startup/menu state machine switches |
ENDGAME.C |
Endgame/victory sequence |
AMIGINIT.C |
Amiga-specific init (ReDMCSB is multi-platform; PC34 reuses shared init logic) |
| File | Purpose |
|---|---|
DUNGEON.C |
Core dungeon data access: squares, things, thing lists (F0139-F0174) |
DUNVIEW.C |
Dungeon-view-adjacent dungeon helpers |
NEWMAP.C |
New-map / map-transition handling |
MOVESENS.C |
Movement + sensor interaction |
GROUP.C |
Monster group data and AI (F0175-F0212) |
| File | Purpose |
|---|---|
VIEWPORT.C |
Viewport frame orchestration |
DRAWVIEW.C |
Dungeon-view drawing (F0093-F0135: floor/wall/door/creature blit chain) |
DRAW.C |
General drawing primitives |
BLIT.C / BLITMASK.C
|
Raw bitmap blit + masked blit routines |
IMAGE.C / IMAGE2.C
|
Bitmap/image management |
SCREEN.C |
Screen buffer management |
PALETTE.C |
Palette load/animate |
DARKCOLR.C |
Darkness/lighting color reduction |
PRIM1.C |
Low-level graphics primitives |
VIDEODRV.C |
Video driver abstraction |
MIRROR.C |
Horizontal-flip bitmap helper (used for D2R/D3R mirrored wall tiles) |
SYMBOL.C |
Symbol/glyph bitmap handling |
| File | Purpose |
|---|---|
OBJECT.C |
Object type/icon resolution (F0031-F0039) |
INVENTORY.C / INVNTORY.C
|
Inventory panel drawing and interaction (F0332-F0349) |
CHEST.C |
Chest/container open/close |
AMMO.C |
Ammunition compatibility checks |
| File | Purpose |
|---|---|
CHAMPION.C / CHAMPION.H
|
Champion stats, slots, damage, skills (F0284-F0331) |
CHAMDRAW.C |
Champion portrait/state drawing |
CLIKCHAM.C |
Mouse click routing onto champion panel |
REVIVE.C |
Resurrection/reincarnation |
| File | Purpose |
|---|---|
MELEE.C |
Melee combat resolution |
PROJECTILE.C |
Projectile creation/flight |
PROJEXPL.C |
Projectile explosion resolution |
ACTIDRAW.C |
Action icon drawing (attack/spell UI) |
SPELDRAW.C |
Spell casting UI drawing |
| File | Purpose |
|---|---|
TIMELINE.C |
Scheduled-event queue (F0233-F0240) |
| File | Purpose |
|---|---|
SWITCHMM.C |
Wall/floor switch dispatch (shared with menu switches) |
(sensor logic is embedded in DUNGEON.C/MOVESENS.C, wrapped by Firestaff F07xx_*_Compat) |
Door toggling, sensor effect dispatch |
| File | Purpose |
|---|---|
SOUND.C |
Sound effect request/playback queue (F0064-F0065) |
MUSIC.C |
Music driver: pause, play, track select, update (F0740-F0743) |
| File | Purpose |
|---|---|
MOUSE.C |
Mouse pointer state, enable/disable, screen area (F0066-F0079) |
MOUSESET.C |
Mouse pointer bitmap sets |
CLIKMENU.C |
Menu click routing |
CLIKVIEW.C |
Viewport click routing |
COMMAND.C |
Command dispatch table |
| File | Purpose |
|---|---|
LOADSAVE.C |
Save/load orchestration (F0417-F0435 checksum/obfuscation, dungeon tail) |
SAVEHEAD.C |
Save header checksum/obfuscation (F0429/F0430) |
SAVEPATH.C |
Save file path resolution |
SAVEUTIL.C |
F0417_SAVEUTIL_GetChecksumAndObfuscate and related utilities |
READWRIT.C |
Low-level read/write + obfuscation helpers |
FIO1.C / FIO1MAIN.C
|
File I/O primitives |
FLOPPYAM.C / FLOPPYST.C
|
Amiga/Atari ST floppy I/O (multi-platform ReDMCSB) |
| File | Purpose |
|---|---|
GRF1.C |
GRAPHICS.DAT access layer |
LZW.C |
LZW decompression of IMG3 graphic entries |
COPYPROE.C |
Copy-protection related graphics/data checks |
| File | Purpose |
|---|---|
TEXT.C |
Message area, text printing (F0040-F0054) |
MENU.C / MENUDRAW.C
|
Menu system and drawing |
DIALOG.C |
Dialog box rendering |
PANEL.C |
Side panel drawing |
| File | Purpose |
|---|---|
HINT001.C, HINT004.C, HINTCASE.C, HINTGRAP.C, HINTHINT.C, HINTIORQ.C
|
CSB hint-oracle subsystem (shared engine, CSB-specific content) |
CEDT004.C, CEDTINCD.C, CEDTINCI.C
|
Character/portrait editor tooling |
| File | Purpose |
|---|---|
ATARIST.H |
Atari ST platform header (multi-platform ReDMCSB build) |
NEC816.C / NECIO.C
|
NEC/PC-98 platform I/O (Japanese release support) |
JAPANESE.C |
Japanese text/localization handling |
USIO1.C |
US/international I/O variant |
UTAMSCR.C, UTSTGRAP.C, UTSTVDI3.C
|
Atari ST utility/graphics/VDI helpers |
INT1.C |
Interrupt handler shim |
APPA.C |
Appendix/miscellaneous support routines |
912 distinct F-numbered identifiers are referenced in DM1 code (functions, constants, and struct/field markers combined). The tables below cover the ~300 function-shaped F-numbers in the ranges most central to DM1 parity work, grouped by subsystem. Names are the canonical Firestaff spelling found in source comments; the originating ReDMCSB file is noted per group above.
| F# | Function |
|---|---|
| F0002 | MAIN_GameLoop |
| F0007 | MAIN_CopyBytes |
| F0008 | MAIN_ClearBytes |
| F0010 | MAIN_WriteSpacedWords |
| F0020 | MAIN_BlitToViewport |
| F0021 | MAIN_BlitToScreen |
| F0022 | MAIN_Delay |
| F0024 | MAIN_GetMinimumValue |
| F0025 | MAIN_GetMaximumValue |
| F0026 | MAIN_GetBoundedValue |
| F0027 | MAIN_Get16bitRandomNumber |
| F0028 | MAIN_Get1BitRandomNumber |
| F0030 | MAIN_GetScaledProduct |
| F# | Function |
|---|---|
| F0031 | OBJECT_LoadNames |
| F0032 | OBJECT_GetType |
| F0033 | OBJECT_GetIconIndex |
| F0034 | OBJECT_DrawLeaderHandObjectName |
| F0035 | OBJECT_ClearLeaderHandObjectName |
| F0036 | OBJECT_ExtractIconFromBitmap |
| F0037 | OBJECT_DrawIconToScreen |
| F0038 | OBJECT_DrawIconInSlotBox |
| F0039 | OBJECT_GetIconIndexInSlotBox |
| F# | Function |
|---|---|
| F0040 | TEXT_Print |
| F0042 | TEXT_MESSAGEAREA_MoveCursor |
| F0043 | TEXT_MESSAGEAREA_ClearAllRows |
| F0044 | TEXT_MESSAGEAREA_ClearExpiredRows |
| F0045 | TEXT_MESSAGEAREA_CreateNewRow |
| F0046 | TEXT_MESSAGEAREA_PrintString |
| F0047 | TEXT_MESSAGEAREA_PrintMessage |
| F0048 | TEXT_MESSAGEAREA_PrintCharacter |
| F0051 | TEXT_MESSAGEAREA_PrintLineFeed |
| F0052 | TEXT_PrintToViewport |
| F0053 | TEXT_PrintToLogicalScreen |
| F0054 | TEXT_Initialize |
| F# | Function |
|---|---|
| F0064 | SOUND_RequestPlay |
| F0065 | SOUND_PlayPendingSound |
| F# | Function |
|---|---|
| F0067 | MOUSE_SetPointerToNormal |
| F0068 | MOUSE_SetPointerToObject |
| F0073 | MOUSE_BuildPointerScreenArea |
| F0077 | MOUSE_EnableScreen |
| F0078 | MOUSE_Disable |
| F# | Function |
|---|---|
| F0093 | DUNGEONVIEW_DrawDungeon |
| F0094 | DUNGEONVIEW_LoadFloorSet |
| F0095 | DUNGEONVIEW_LoadDoorSet |
| F0096 | DUNGEONVIEW_LoadCurrentMapGraphics |
| F0097 | DUNGEONVIEW_DrawViewport |
| F0098 |
DUNGEONVIEW_DrawFloorAnd (ceiling) |
| F0099 | DUNGEONVIEW_CopyBitmapAndFlipHorizontal |
| F0100 | DrawWallSetBitmap |
| F0101 | DUNGEONVIEW_DrawWallSetBitmapWithoutTransparency |
| F0102 | DUNGEONVIEW_DrawDoorBitmap |
| F0103 | DUNGEONVIEW_DrawDoorFrameBitmapFlippedHorizontally |
| F0104 | DrawFloorPitOrStairsBitmap |
| F0105 | DUNGEONVIEW_DrawFloorPitOrStairsBitmapFlipped |
| F0106 | DUNGEONVIEW_TestResetToStep1 |
| F0107 | IsDrawnWallOrnamentAnAlcove |
| F0108 | DrawFloorOrnament |
| F0109 | DUNGEONVIEW_DrawDoorOrnament |
| F0110 | DUNGEONVIEW_DrawDoorButton |
| F0111 | DrawD2C |
| F0112 | DUNGEONVIEW_DrawCeilingPit |
| F0113 | DUNGEONVIEW_DrawField |
| F0114 | GetExplosionBitmap |
| F0115 | DUNGEONVIEW_DrawObjectsCreatures |
| F0116-F0121 |
DrawD3L/D3R/D3C/D2L/D2R/D2C (distance-3 and distance-2 wall column draws) |
| F0122-F0127 |
DrawSquareD1L/D1R/D1C/D0L/D0R/D0C (distance-1 and distance-0/foreground column draws) |
| F0128 | G0076GetPc34Compat |
| F0129 | VIDEO_BlitShrinkWithPaletteChanges |
| F0130 | VIDEO_FlipHorizontal |
| F0132 | VIDEO_Blit |
| F0133 | VIDEO_BlitBoxFilledWithMaskedBitmap |
| F0134 | VIDEO_FillBitmap |
| F0135 | VIDEO_FillBox |
This F0093-F0135 range is the material matrix: the D0/D1/D2/D3 naming
encodes viewport column distance (D0 = nearest row, D3 = farthest) and
L/R/C encode left/right/center screen position — this is the routing table
behind m11_draw_viewport and the CSB viewport callback path described in
CLAUDE.md.
| F# | Function |
|---|---|
| F0139 |
MakeThingPc34 (creature-allowed-on-map gate) |
| F0140, F0141 | ObjectWorldPc34 |
| F0142 | DUNGEON_GetProjectileAspect |
| F0143 |
ArmourInfoPc34 / DUNGEON_GetArmourDefense
|
| F0144 |
MakeThingPc34 (creature attributes) |
| F0145 | DUNGEON_GetGroupCells |
| F0146 | DUNGEON_SetGroupCells |
| F0147 | DUNGEON_GetGroupDirections |
| F0148 | DUNGEON_SetGroupDirections |
| F0149 | DUNGEON_IsWallOrnamentAnAlcove |
| F0150 | DUNGEON_UpdateMapCoordinatesAfterRelativeMovement |
| F0151 | DUNGEON_GetSquare |
| F0152 | DUNGEON_GetRelativeSquare |
| F0153 | DUNGEON_GetRelativeSquareType |
| F0154 | DUNGEON_GetLocationAfterLevelChange |
| F0155 | DUNGEON_GetStairsExitDirection |
| F0156 | DUNGEON_GetThingData |
| F0157 | DUNGEON_GetSquareFirstThingData |
| F0158 | DUNGEON_GetWeaponInfo |
| F0159 | DUNGEON_GetNextThing |
| F0160 | DUNGEON_GetSquareFirstThingIndex |
| F0161 | DUNGEON_GetSquareFirstThing |
| F0162 | DUNGEON_GetSquareFirstObject |
| F0163 | DUNGEON_LinkThingToList |
| F0164 | DUNGEON_UnlinkThingFromList |
| F0165 |
DungeonOps (discard-thing route) |
| F0166 | DUNGEON_GetUnusedThing |
| F0167 | DUNGEON_GetObjectForProjectileLauncherOrObjectGenerator |
| F0169 | DUNGEON_GetRandomOrnamentIndex |
| F0170 | DUNGEON_GetRandomOrnamentOrdinal |
| F0171 | DUNGEON_SetSquareAspectRandomWallOrnament |
| F0172 | SetSquareAspect |
| F0173 | DUNGEON_SetCurrentMap |
| F0174 | DUNGEON_SetCurrentMapAndPartyMap |
| F# | Function |
|---|---|
| F0175 | GROUP_GetThing |
| F0176 | GROUP_GetCreatureOrdinalInCell |
| F0177 | GROUP_GetMeleeTargetCreatureOrdinal |
| F0178 | GROUP_GetGroupValueUpdatedWithCreatureValue |
| F0179 | CreatureAspectUpdateReceipt |
| F0180 | GROUP_StartWandering |
| F0181 | GROUP_DeleteEvents |
| F0182 | GROUP_StopAttacking |
| F0183 | GROUP_AddActiveGroup |
| F0184 | GROUP_RemoveActiveGroup |
| F0185 | GROUP_GetGenerated |
| F0186 | GROUP_DropCreatureFixedPossessions |
| F0187 | GROUP_DropMovingCreatureFixedPossessions |
| F0188 | GROUP_DropGroupPossessions |
| F0189 | GROUP_Delete |
| F0190 | GROUP_GetDamageCreatureOutcome |
| F0191 | GROUP_GetDamageAllCreaturesOutcome |
| F0192 | GROUP_GetResistanceAdjustedPoisonAttack |
| F0193 | GROUP_StealFromChampion |
| F0194 | GROUP_RemoveAllActiveGroups |
| F0195 | GroupEventPc34Compat |
| F0196 | ActiveGroupSlotPc34Compat |
| F0197 | GROUP_IsViewPartyBlocked |
| F0198 | GROUP_IsSmellPartyBlocked |
| F0199 | GROUP_GetDistanceBetweenUnblockedSquares |
| F0200 | GROUP_GetDistanceToVisibleParty |
| F0201 | GROUP_GetSmelledPartyPrimaryDirectionOrdinal |
| F0202 | GROUP_IsMovementPossible |
| F0203 | GROUP_GetFirstPossibleMovementDirectionOrdinal |
| F0205 | GROUP_SetDirection |
| F0206 | GROUP_SetDirectionGroup |
| F0207 | GROUP_IsCreatureAttacking |
| F0208 | GROUP_AddEvent |
| F0209 | GROUP_ProcessEvents29to41 |
| F0212 | PROJECTILE_Create |
| F# | Function |
|---|---|
| F0213 | EXPLOSION_Create |
| F0216 | PROJECTILE_GetImpactAttack |
| F0218 | PROJECTILE_GetImpactCount |
| F# | Function |
|---|---|
| F0233 | TIMELINE_Initialize |
| F0234 | TIMELINE_IsEventABeforeEventB |
| F0235 | TIMELINE_GetIndex |
| F0236 | TIMELINE_FixPlacement |
| F0237 | TIMELINE_DeleteEvent |
| F0238 | TIMELINE_AddEvent |
| F0239 | TIMELINE_ExtractFirstEvent |
| F0240 | TIMELINE_IsFirstEventExpired |
| F# | Function |
|---|---|
| F0267 | MOVE_GetMoveResult |
| F0268 | SENSOR_AddEvent |
| F0269 | SENSOR_AddSkillExperience |
| F0270 | SENSOR_TriggerLocalEffect |
| F0271 | SENSOR_ProcessRotationEffect |
| F0272 | SENSOR_TriggerEffect |
| F0273 | SENSOR_GetObjectOfTypeInCell |
| F0274 | SENSOR_IsObjectInPartyPossession |
| F0275 | SENSOR_IsTriggeredByClickOnWall |
| F0276 | SENSOR_ProcessThingAdditionOrRemoval |
| F# | Function |
|---|---|
| F0284 | CHAMPION_SetPartyDirection |
| F0285 | CHAMPION_GetIndexInCell |
| F0286 | CHAMPION_GetTargetChampionIndex |
| F0287 | CHAMPION_DrawBarGraphs |
| F0289 | CHAMPION_DrawHealthOrStaminaOrManaValue |
| F0290 | CHAMPION_DrawHealthStaminaManaValues |
| F0291 | CHAMPION_DrawSlot |
| F0292 | CHAMPION_DrawState |
| F0293 | CHAMPION_DrawAllChampionStates |
| F0294 | CHAMPION_IsAmmunitionCompatibleWithWeapon |
| F0295 | CHAMPION_HasObjectIconInSlotBoxChanged |
| F0296 | CHAMPION_DrawChangedObjectIcons |
| F0297 | CHAMPION_PutObjectInLeaderHand |
| F0298 | CHAMPION_GetObjectRemovedFromLeaderHand |
| F0299 | ApplyObjectModifiersToStatistics |
| F0300 | CHAMPION_GetObjectRemovedFromSlot |
| F0301 | CHAMPION_AddObjectInSlot |
| F0302 | INVENTORY_ProcessCommands28To65_ClickOnSlotBox |
| F0303 | CHAMPION_GetSkillLevel |
| F0304 | CHAMPION_AddSkillExperience |
| F0305 | CHAMPION_GetThrowingStaminaCost |
| F0306 | CHAMPION_GetStaminaAdjustedValue |
| F0307 | CHAMPION_GetStatisticAdjustedAttack |
| F0308 | CHAMPION_IsLucky |
| F0309 | CHAMPION_GetMaximumLoad |
| F0310 | CHAMPION_GetMovementTicks |
| F0311 | CHAMPION_GetDexterity |
| F0312 | CHAMPION_GetStrength |
| F0313 | CHAMPION_GetWoundDefense |
| F0314 | CHAMPION_WakeUp |
| F0318 | CHAMPION_DropAllObjects |
| F0319 | CHAMPION_Kill |
| F0320 | CHAMPION_ApplyAndDrawPendingDamageAndWounds |
| F0321 | CHAMPION_AddPendingDamageAndWounds |
| F0322 | CHAMPION_Poison |
| F0323 | CHAMPION_Unpoison |
| F0324 | CHAMPION_DamageAll_GetDamagedChampionCount |
| F0325 | CHAMPION_DecrementStamina |
| F0326 | CHAMPION_ShootProjectile |
| F0327 | CHAMPION_IsProjectileSpellCast |
| F0328 | CHAMPION_IsObjectThrown |
| F0329 | CHAMPION_AddObjectInLeaderHand |
| F0330 | CHAMPION_DisableAction |
| F0331 |
CHAMPION_InitializeParty / CHAMPION_ApplyTimeEffects
|
| F0332 | INVENTORY_DrawIconToViewport |
| F0333 | INVENTORY_OpenAndDrawChest |
| F0334 | INVENTORY_CloseChest |
| F0335 | INVENTORY_DestroyChest |
| F0336 | INVENTORY_DrawPanel_BuildObjectAttributesString |
| F0337 | INVENTORY_SetDungeonViewPalette |
| F0338 | INVENTORY_DecreaseTorchesLightPower |
| F0339 | INVENTORY_DrawPanel_ArrowOrEye |
| F0340 | INVENTORY_DrawPanel_ScrollTextLine |
| F0341 | INVENTORY_DrawPanel_Scroll |
| F0344 | INVENTORY_DrawPanel_FoodOrWaterBar |
| F0345 | INVENTORY_DrawPanel_FoodWaterPoisoned |
| F0346 | INVENTORY_DrawPanel_Resurrect |
| F0347 | INVENTORY_DrawPanel |
| F0349 | INVENTORY_ProcessCommand70_ClickOnMouth |
These are Firestaff-authored bridge functions (not raw ReDMCSB F-numbers)
that wrap DUNGEON.C/MOVESENS.C logic into a callback-oriented,
testable API. They still carry F-numbers to preserve traceability to the
functional area they compat-wrap.
| F# | Function |
|---|---|
| F0700 | TriggerImmediateMouseEvent |
| F0701 | MOVEMENT_GetStepDelta_Compat |
| F0702 | MOVEMENT_TryMove_Compat |
| F0703 | ReleaseChampionIcon |
| F0704 | MOVEMENT_ResolvePostMoveEnvironment_Compat |
| F0707 | SoundDescriptorPc34 |
| F0708 | MOVEMENT_IsPartyStepBlockedByGroup_Compat |
| F0711 | ConvertScanCodeToASCII |
| F0712 | AnyKeyboardOrMouseInput |
| F0715 | DOOR_ResolveToggleAction_Compat |
| F0716 | DOOR_RouteFrontCellClick_Compat |
| F0718 | SENSOR_ProcessPartyEnterLeave_Compat |
| F0720 | TIMELINE_Init_Compat |
| F0721 | TIMELINE_Schedule_Compat |
| F0722 | SENSOR_EvaluateFloor_Compat |
| F0723 | SENSOR_EvaluateWall_Compat |
| F0724 | SENSOR_ResolveEffectDispatch_Compat |
| F0725 | SENSOR_ProcessFloorSquare_Compat |
| F0726 | SENSOR_ProcessWallClick_Compat |
| F0727 | SENSOR_SquareTypeToEventType_Compat |
| F0728 | SENSOR_ResolveHoldEffect_Compat |
| F0729 | SENSOR_EvaluateWallCountdownEvent_Compat |
| F0730 | NextCell_Compat |
| F# | Function |
|---|---|
| F0740 | MUSIC_Pause |
| F0741 | MUSIC_PlayGameMusic |
| F0742 |
MUSIC_SetTrack (bound on stairs/teleporter transition) |
| F0743 |
MUSIC_Update (per-tick driver update) |
Wired into M11_GameViewState via dm1MusicSource / dm1MusicState /
dm1MusicDriver fields (see CLAUDE.md). Only the game-won track (C2) is
currently proven playable end-to-end; other tracks remain fail-closed.
| F# | Function |
|---|---|
| F0802 | IsMagicMap |
| F0803 | DrawMagicMapIcon |
| F0804 | DrawMagicMap |
| F0805 | CreatureNameScroll |
| F0810 | DM1_GROUP_DispatchBehavior_Compat |
| F0811 | DM1_GROUP_IsMovementPossible_Compat |
| F0812 | DM1_GROUP_GetFirstPossibleMovementDir_Compat |
| F0813 | DM1_GROUP_PickSingleSquareMove_Compat |
| F0814 | DM1_GROUP_ShouldAttack_Compat |
| F0815 | DM1_GROUP_IsMeleeRange_Compat |
| F0816 | DM1_GROUP_ShouldUseProjectile_Compat |
| F0817 | DM1_GROUP_SetGroupDirection_Compat |
| F0818 | DM1_GROUP_GetDistanceToVisibleParty_Compat |
| F0819 | DM1_GROUP_GetSmelledPartyDirOrdinal_Compat |
| F0820 | DM1_GROUP_GetFleeDirection_Compat |
| F0821 | DM1_GROUP_ShouldFrighten_Compat |
| F0822 | DM1_GIGGLER_ResolveStealAttempt_Compat |
| F0823 | DM1_GROUP_ResolveProjectileAttack_Compat |
| F0824 | DM1_GROUP_ResolveFixedPossessionDrops_Compat |
| F0833 | HungerThirstInput_Compat |
| F0834 | LIFECYCLE_ClampFoodWater_Compat |
| F0841 | LIFECYCLE_ComputeMoveTicks_Compat |
| F0859 | LIFECYCLE_Init_Compat |
| F0860 | RESURRECTION_ComputeBonesCreation_Compat |
| F0861 | RESURRECTION_ShouldTriggerViAltarRebirth_Compat |
| F0862 | RESURRECTION_GetChampionIndexFromBones_Compat |
| F0863 | RESURRECTION_ComputeRebirthHealth_Compat |
| F0864 | RESURRECTION_ComputeReincarnation_Compat |
| F0865 | RESURRECTION_IsCommandValid_Compat |
| F0866 | RESURRECTION_RouteChampionPortraitClick_Compat |
| F0867 | RESURRECTION_ProcessCandidatePanelCommand_Compat |
| F0868 | RESURRECTION_RunViAltarFullCycle_Compat |
| F0869 | RESURRECTION_DecodeChampionValue_Compat |
| F0870 | RESURRECTION_ResetDataToStartGamePlan_Compat |
| F0871-F0873 | RESURRECTION_BuildHocMirrorCandidate{Selection,Finalize,Runtime}Receipt_Compat |
| F0881 | WORLD_InitDefault_Compat |
| F0883 | WORLD_Free_Compat |
| F0891 | ORCH_WorldHash_Compat |
| F0897-F0899 |
WORLD_Serialize_Compat / WORLD_Deserialize_Compat / WORLD_SerializedSize_Compat
|
| F0902 | DrawFTLLogo |
| F0903 | DrawErrorMessage |
| F0904 | PaletteAnimation |
Reference: include/memory_dungeon_dat_pc34_compat.h, ReDMCSB DUNGEON.C.
The on-disk layout (little-endian, Watcom/Borland LSB-first bitfields) is:
Offset 0: DUNGEON_HEADER (44 bytes)
Offset 44: MAP[header.mapCount] (16 bytes each)
Then: raw map (square) data, thing arrays, square-first-thing table, text data
| Field | Type | Notes |
|---|---|---|
ornamentRandomSeed |
uint16 | Seed for random wall/floor ornament placement (F0169-F0171) |
rawMapDataByteCount |
uint16 | Byte size of the packed square-data blob |
mapCount |
uint8 | Number of maps (levels) |
unreferenced |
uint8 | Padding, unused |
textDataWordCount |
uint16 | Word count of the text data section |
initialPartyLocation |
uint16 | Bitfield: direction (bits 11-10), y (bits 9-5), x (bits 4-0) — decoded by F0501 |
squareFirstThingCount |
uint16 | Number of compact SquareFirstThings entries |
thingCounts[16] |
uint16[16] | Per-type thing counts (indexed by THING_TYPE_*) |
Known dungeon IDs (from DEFS.H): DUNGEON_ID_DM=10,
DUNGEON_ID_CSB_PRISON=12, DUNGEON_ID_CSB_GAME=13, DUNGEON_ID_KID=50.
A compressed-dungeon (save-game) signature of 0x8104 is explicitly
rejected by the DUNGEON.DAT-only loader.
| Field | Notes |
|---|---|
rawMapDataByteOffset |
Offset into the raw square-data blob |
aUnreferenced, bUnreferenced
|
Padding |
offsetMapX, offsetMapY
|
Map placement offsets |
level, width, height
|
Decoded from bitfield A; width/height are stored value + 1 |
rawBitfieldB/C/D |
Raw 16-bit bitfields, kept alongside decoded fields |
wallOrnamentCount, randomWallOrnamentCount, floorOrnamentCount, randomFloorOrnamentCount
|
Decoded from bitfield B |
doorOrnamentCount, creatureTypeCount, difficulty, allowedCreatureTypes[16]
|
Decoded from bitfield C |
floorSet, wallSet, doorSet0, doorSet1
|
Decoded from bitfield D — selects which GRAPHICS.DAT graphic set to bind |
M034_SQUARE_TYPE(square) = square >> 5. Each square byte packs:
| Mask | Meaning |
|---|---|
0xE0 (DUNGEON_SQUARE_MASK_TYPE) |
3-bit element type |
0x1F (DUNGEON_SQUARE_MASK_ATTRIBS) |
5-bit element-specific attributes |
0x10 (DUNGEON_SQUARE_MASK_THING_LIST) |
Set if the square has an entry in SquareFirstThings
|
| Value | Name |
|---|---|
| 0 | Wall |
| 1 | Corridor |
| 2 | Pit |
| 3 | Stairs |
| 4 | Door |
| 5 | Teleporter |
| 6 | Fake wall |
| Value | Type | Bytes on disk |
|---|---|---|
| 0 | Door | 4 |
| 1 | Teleporter | 6 |
| 2 | TextString | 4 |
| 3 | Sensor | 8 |
| 4 | Group | 16 |
| 5 | Weapon | 4 |
| 6 | Armour | 4 |
| 7 | Scroll | 4 |
| 8 | Potion | 4 |
| 9 | Container | 8 |
| 10 | Junk | 4 |
| 11-13 | (unused) | 0 |
| 14 | Projectile | 8 |
| 15 | Explosion | 4 |
Per-type byte counts mirror ReDMCSB's G0235_auc_Graphic559_ThingDataByteCount.
bits 15:14 direction (used at runtime only, not in on-disk thing arrays)
bits 13:10 thing type (0-15, see table above)
bits 9:0 index into ThingData[type] array
Special sentinel values: THING_NONE = 0xFFFF (unused slot),
THING_ENDOFLIST = 0xFFFE (end of a linked list).
Each square flagged with DUNGEON_SQUARE_MASK_THING_LIST has a compact
entry in the SquareFirstThings array (one entry per flagged square, not
one per tile — looked up via F0160/F0161). Each decoded thing record
carries a next field (a THING handle) forming a singly linked list per
square; walking stops at THING_ENDOFLIST. F0163/F0164
(DUNGEON_LinkThingToList / DUNGEON_UnlinkThingFromList) mutate these
lists; both fail closed if the loaded tables cannot admit the exact
requested mutation, including reproducing the original engine's
BUG0_08 full-SFT-overflow corruption case for diagnostic purposes
(F0502b_DUNGEON_CheckBug0_08SftOverfill_Compat).
-
Door (4B):
next,type(1b: set0/set1),ornamentOrdinal(4b),vertical(1b),button(1b),magicDestructible(1b),meleeDestructible(1b) -
TextString (4B):
next,visible(1b),textDataWordOffset(13b) -
Teleporter (6B):
next,targetMapX/Y(5b each),rotation(2b),absoluteRotation(1b),scope(2b),audible(1b),targetMapIndex(8b) -
Sensor (8B):
next,sensorType(7b,M039_TYPE),sensorData(9b,M040_DATA),onceOnly(1b),effect(2b),revertEffect(1b),audible(1b),value(4b),localEffect(1b),ornamentOrdinal(4b), plus a Remote/Local union over the last 2 bytes: Remote =targetCell(2b) +targetMapX/Y(5b each); Local =localMultiple(12b, health multiplier / tick count / kinetic energy depending on sensor type) -
Group (16B):
next,slot(possession chain),creatureType(8b, 0-26 for DM),cells(8b, packs up to 4 creature cell positions),health[4],behavior(4b),count(2b, actual count = value + 1),direction(2b),doNotDiscard(1b) -
Weapon (4B):
next,type(7b),doNotDiscard(1b),cursed(1b),poisoned(1b),chargeCount(4b),broken(1b),lit(1b, torches) -
Armour (4B):
next,type(7b),doNotDiscard(1b),cursed(1b),chargeCount(4b),broken(1b) -
Scroll (4B):
next,textStringThingIndex(10b),closed(6b) -
Potion (4B):
next,power(8b),type(7b),doNotDiscard(1b) -
Container (8B):
next,slot(contents chain),type(2b) -
Junk (4B):
next,type(7b),doNotDiscard(1b),cursed(1b),chargeCount(2b) -
Projectile (8B):
next,slot,kineticEnergy(8b),attack(8b),eventIndex(16b) -
Explosion (4B):
next,type(7b),centered(1b),attack(8b)
Each 16-bit word packs three 5-bit codes (code[0] = bits 14-10,
code[1] = bits 9-5, code[2] = bits 4-0; bit 15 unused):
| Code | Meaning |
|---|---|
| 0-25 |
'A'-'Z'
|
| 26 | Space |
| 27 | Period |
| 28 | Separator (newline / 0x80 depending on context) |
| 29 | Escape — next code indexes the symbol table |
| 30 | Escape — next code indexes the word table ("THE ", "YOU ", ...) |
| 31 | End of text |
Strings are exposed by index (not content) since the table is
language-dependent. Text types: inscription (0), message (1), scroll (2),
with an optional 0x8000 mask to force decode even if the underlying
TextString thing is marked invisible.
DM1 PC 3.4 and CSB PC 3.4 both use GRAPHICS.DAT, but the on-disk compression differs by platform port in the ReDMCSB source tree:
-
PC IMG3 — the PC 3.4 (DOS) graphic entry format. Entries are
LZW-compressed (ReDMCSB
LZW.C), decoded through theF0687-F0691chain implemented in Firestaff asredmcsb_f0687_f0688_img3_pc34_compat,redmcsb_f0689_img3_expand_pc34_compat,redmcsb_f0685_img3_line_fill_pc34_compat, andredmcsb_f0691_draw_compressed_img3_pc34_compat. Firestaff's DM1 path reads GRAPHICS.DAT headers viamemory_graphics_dat_header_pc34_compat.h, which stores parallelcompressedByteCounts/decompressedByteCountsarrays per entry — confirming each entry can be independently compressed/uncompressed. -
Amiga IMG1 — CSB's Amiga GRAPHICS.DAT uses a nibble RLE scheme,
distinct from PC IMG3 (see
csb_v1_amiga_graphics_dat.h,dm1_v1_amiga_graphics_dat.h,csb_v1_csbgraphics_dat_lzw_boundary.h,csb_v1_csbgraphics_dat_classify.h). This is not interchangeable with the PC LZW format even though both are called "GRAPHICS.DAT". -
FM Towns variants exist for DM2 (
dm2_v1_fmtowns_graphics_dat.h,csb_v1_fmtowns_graphics_dat.h). The DM1 FM Towns port ships on a BIN/CUE disc image and drives all in-game presentation through the TownsOS EGB graphics library and its own Phar Lap P3 executable (EDM.EXP/JDM.EXP); see the dedicated DM1 FM Towns guide and the disassembly evidence atparity-evidence/dm1_fmtowns_menu_p3_disassembly.md.
Firestaff's memory-allocator model for GRAPHICS.DAT (memory_graphics_dat_*
modules — allocator entry/boundary/orchestrator, defrag loop/entry, used
list, slots, bitmap reuse) mirrors ReDMCSB MEMORY.C's used-list +
defragmentation allocator so that graphic bitmap lifetime and memory
pressure match the original engine's caching behavior (entries can be
evicted and re-decoded from the compressed source, matching the original's
memory-constrained DOS environment).
Reference: ReDMCSB LOADSAVE.C, SAVEHEAD.C, SAVEUTIL.C, READWRIT.C;
Firestaff headers include/memory_savegame_pc34_compat.h,
include/redmcsb_f0435_save_tail_pc34_compat.h,
include/dm1_v1_original_save_pc34_handoff.h.
F0417_SAVEUTIL_GetChecksumAndObfuscate (SAVEUTIL.C) is the shared
XOR-obfuscation-plus-checksum primitive used across the save format. The
save header uses a minimal subset: 10 uint16 "Noise" entries are fed
through the F0417 XOR+checksum loop (SAVEHEAD.C:44,97,104). The dungeon
tail and other sections reuse the same primitive via READWRIT.C
F0417/F0419/F0420.
-
SAVEHEAD.CF0429/F0430 — read and deobfuscate/checksum the save header. -
READWRIT.CF0433 — dungeon-tail checksum/obfuscation pass. -
LOADSAVE.CF0435 — continuation after the header's first three parts: reads EVENTS (with per-partKeys[C3]/Checksums[C3]), then the uint16 TIMELINE, then the dungeon tail proper. Firestaff models this as an indexed part sequence:REDMCSB_F0435_PC34_EVENTS_PART_INDEX = 3,REDMCSB_F0435_PC34_TIMELINE_PART_INDEX = 4, with distinct failure codes (EVENTS_FAILED = -1,TIMELINE_FAILED = -2,DUNGEON_TAIL_FAILED = -3) so a byte-identical original save can be diagnosed at the exact failing part.
DUNGEON_HEADER
MAP[]
column-cumulative SquareFirstThings counts
SquareFirstThings
text data
thing data for all 16 types
raw map (square) data
checksum
Firestaff retains the original tail bytes verbatim
(originalSaveTailBytes/originalSaveTailByteCount/
originalSaveTailPristine in DungeonDatState_Compat) until a live world
serializer explicitly replaces them, so F0433 can round-trip an original
save without lossy decode/re-encode of reserved dungeon fields — this is
the mechanism behind the project's byte-identical DOS savegame
compatibility requirement.
Aggregates all decoded thing data for a loaded dungeon:
-
squareFirstThings[]+squareFirstThingCount - Per-type decoded arrays + counts:
doors,textStrings,teleporters,sensors,groups,weapons,armours,scrolls,potions,containers,junks,projectiles,explosions -
rawThingData[16]+thingCounts[16]— raw byte blobs for all 16 thing-type slots (kept even for types not yet fully decoded) -
textData[]+textDataWordCount -
loadedflag
16-byte on-disk map descriptor, decoded into level/width/height, ornament counts, difficulty, allowed creature types, and floor/wall/door graphic set selectors (see §3).
Top-level loader state: header, maps[], per-column cumulative
square-first-thing counts (mirrors ReDMCSB G0280), tiles[] (loaded
lazily by F0502, separate from header/map load by F0500), file size,
loaded/tilesLoaded flags, and the pristine save-tail byte cache described
above.
| Constant | Value |
|---|---|
DUNGEON_THING_TYPE_COUNT |
16 |
DUNGEON_CREATURE_TYPE_MAX |
26 (27 types including 0) |
DUNGEON_WEAPON_TYPE_MAX |
45 |
DUNGEON_ARMOUR_TYPE_MAX |
57 |
DUNGEON_JUNK_TYPE_MAX |
52 |
DUNGEON_POTION_TYPE_MAX |
20 |
DUNGEON_CONTAINER_TYPE_MAX |
3 |
DUNGEON_MAX_MAPS |
32 (sanity cap) |
DM1 defines DUNGEON_CREATURE_TYPE_MAX = 26, meaning creature type indices
run 0-26 inclusive (27 distinct types). Per-type attributes (used for
combat resolution, AI behavior gating, and sound selection) are held in
DM1_V1_F0144_CreatureInfoPc34 records (include/dm1_v1_creature_attributes_f0144_pc34_compat.h,
sourced from ReDMCSB DUNGEON.C F0144), keyed by creatureType (uint8,
0-26). Related lookup tables:
-
Map eligibility:
dm1_v1_creature_allowed_on_map_f0139_pc34_compat.h(F0139) — cross-referencesDungeonMapDesc_Compat.allowedCreatureTypes[16]andcreatureTypeCountfrom the map descriptor. -
AI behavior:
dm1_v1_creature_ai_behavior_pc34_compat.h,dm1_v1_creature_behavior_bootstrap_pc34_compat.h— behavior dispatch mirrored in the F0810-F0824 range (movement, melee/projectile attack selection, flee/frighten logic, giggler steal-attempt resolution). -
Rendering:
dm1_v1_creature_render_pc34_compat.h,dm1_v1_creature_viewport_pc34_compat.h,dm1_v1_f0115_f0219_creature_item_material_pc34_compat.h— viewport creature draw path feeding into F0115 (DUNGEONVIEW_DrawObjectsCreatures). -
Sound:
dm1_v1_creature_sound_pc34_compat.h. -
Palette:
redmcsb_f8160_creature_palette_c25_pc34_compat.h(F8160, creature replacement-color palette, C25) andredmcsb_f0695_set_creature_replacement_colors_pc34_compat.h(F0695). -
Name scroll UI:
redmcsb_f0805_creature_name_scroll_pc34_compat.h(F0805, used by the magic-map / bestiary display).
Groups reference creatureType (8-bit field, 0-26) directly in the
on-disk DungeonGroup_Compat record (§3), with up to 4 creatures per
group packed via the cells and health[4] fields.
Note: Firestaff exposes these tables by index, not by hardcoded English name — matching the project's index-first design used for text strings (§3) so that future localization work is not blocked by name-string assumptions baked into the reverse-engineered data layer.
The parity-evidence/ directory contains the cross-game pass corpus. The
exact current count is repository-derived rather than maintained as a
narrative number; the DM1-specific subset is identified by
pass{NNN}_dm1_v1_* filenames,
following the naming convention pass{NNN}_{description}.md described in
CLAUDE.md.
Each pass document is a source-lock evidence record: it ties a specific
Firestaff implementation change to the exact ReDMCSB source lines it
reproduces, and typically records the pc34_compat module(s) touched.
Examples visible in the current tree span the full breadth of subsystems
covered above — inventory slot placement and drop order (pass1091,
pass863), viewport occlusion gating (pass404), initial-hall capture
paths (pass466), door/entrance graphics (pass840), palette and cursor
bitmap handling (pass850-pass858, pass870-pass871), mandatory
graphic indices (pass852), wound defense factor (pass853), champion
rename/reincarnate character-string handling (pass854-pass856), square
type → event type mapping (pass859, matching F0727 above), champion
icon and bar-graph byte offsets (pass860-pass862), and box
screen-edge layout constants (pass864-pass866).
This evidence corpus is the practical cross-reference for any future
reverse-engineering work: before re-deriving behavior from ReDMCSB source,
check whether a parity-evidence/pass*_dm1_v1_* document already records
the exact lines and the Firestaff module that encodes them.