Contents

Technical Documentation - File Formats - Saved Game File

Saved game file description

Note: This is a preliminary documentation. It is incomplete and may contain errors and wrong assumptions. Please tell me if you find any mistakes.

The saved game files of Dungeon Master and Chaos Strikes Back share the same general file format with some variants depending on the version and platform of the game.
The saved game files of Dungeon Master II use a different format.

In order to illustrate this specification, I wrote the Saved Game Decoder.

File names

Here is a list of the files described by this specification:

  • DMSAVE.DAT: on the save disks of Dungeon Master for PC, PC-9801 and FM-Towns.
  • DMGAME.DAT: on the save disks of Dungeon Master for Atari ST, Amiga, Apple IIGS and X68000.
  • CSBGAME.DAT: on the save disks of all versions of Chaos Strikes Back.
  • MINI.DAT: on the original disk of Chaos Strikes Back (the Utility Disk for Atari ST and Amiga versions). In Chaos Strikes Back, the dungeon is stored as a saved game file and not as a standalone dungeon.dat file like in Dungeon Master. There is also a dungeon.dat file but it contains only the 'Prison' which is the very small dungeon where you can select your champions if you do not import your own champions from a Dungeon Master saved game.
  • MINIF.DAT: The French version of the Chaos Strikes Back dungeon in the Amiga multilingual versions of the game.
  • MINIG.DAT: The German version of the Chaos Strikes Back dungeon in the Amiga multilingual versions of the game.

Encryption

A saved game file consists of several sections. Some sections are encrypted to prevent tampering. For each encrypted section, the file contains a decryption key (1 word) and a stored checksum value (1 word). The decryption algorithm requires the use of the key and computes a new checksum value. It is possible to validate the decryption process by ensuring that the computed checksum value and the stored checksum value are equal.
There is an exception for the section called 'Block2' in this specification. The 'stored checksum value' must in fact be computed based on all the data in Block1, it is not stored directly as a word. The 'computed checksum value' must also be computed separately from the decryption process whose result value is meaningless.
Note: For the purpose of decryption, all the data must be read as words (not single bytes) by following the appropriate endianness of the platform the file was created on.

Here is pseudo-code for the decryption algorithm:

Block[]: An array of word values with index starting at 0.
N: Number of words in the Block[] array
Key: The decryption key

TempValue = Key
ComputedChecksum = Key
For i = 0 To N - 1
	ComputedChecksum = (ComputedChecksum + Block[i]) MOD 65536
	Block[i] = Block[i] XOR TempValue
	ComputedChecksum = (ComputedChecksum + Block[i]) MOD 65536
	TempValue = (TempValue + N - i) MOD 65536
Next

Here is pseudo-code to compute the 'stored checksum value' for Block2 (working on the original Block1 data read from the saved game file):

Block1[]: An array of 128 word values with index starting at 0.

StoredChecksum = 0
i = 0
Do
	StoredChecksum = (StoredChecksum + Block1[i]) Mod 65536
	StoredChecksum = StoredChecksum XOR Block1[i + 1]
	StoredChecksum = (StoredChecksum - Block1[i + 2] + 65536) Mod 65536
	StoredChecksum = StoredChecksum XOR Block1[i + 3]
	i = i + 4
Until i = 128

Here is pseudo-code to compute the 'computed checksum value' for Block2 (working on the decrypted Block2 data):

Block2[]: An array of 128 word values with index starting at 0.

ComputedChecksum = 0
For i = 0 To 127
	ComputedChecksum = (ComputedChecksum + Block2[i]) MOD 65536
Next

Sections

The following table shows the structure of a saved game file. Sections are listed in the order in which they appear in the file.

Section name Size in bytes Encrypted Description
Initial word 2 bytes No This Initial Word is only found in:
MINI.DAT from Chaos Strikes Back for X68000 (Value = 5223h)
MINI.DAT from Chaos Strikes Back for Amiga Utility Disk English Releases 1,2 and 3 (Value = DEADh)
MINIF.DAT from Chaos Strikes Back for Amiga Utility Disk French (Value = 5223h)
MINIG.DAT from Chaos Strikes Back for Amiga Utility Disk German (Value = 5223h)
This Initial Word is never found in saved games made while playing, even in those created with Chaos Strikes Back for Amiga and X68000.
This word does not seem to be used and should be ignored if it is present when reading the file.
Block1 256 bytes No Block1 contains random data generated when the game is saved. Only two words are not random: one that contains the Key required to decode Block2 (at offset 20 for Dungeon Master and at offset 58 for Chaos Strikes Back) and the last word of Block1 which value is computed when the game is saved so that the stored checksum value of Block 2 can be computed correctly.
Block2 256 bytes Yes Contains the keys and checksums of all other encrypted sections. The structure of this block is slightly different in Dungeon Master and in Chaos Strikes Back. For example, the offsets of keys and checksums are not the same.
Block3 128 bytes Yes Contains data about the saved game.
Creatures Extended Data 16 bytes per creature located on the same level as the party. Yes This 16 bytes structure is called ITEM16 in CSBuild source code. The number of records is stored in Block3.
Champion Data 3328 bytes (4 x 800 + 128)
or 3324 bytes (4 x 799 + 128)
or 1404 bytes (4 x 319 + 128)
or 1408 bytes (4 x 320 + 128)
Yes The size of Champion Data is not the same is all game versions. In some versions, this section contains the champion portraits (size is then 3324 or 3328 bytes) while in other versions the portraits are stored in their own section (size is then 1404 or 1408 bytes).
Note that the size of this section does not depend on the number of champions in the party. There is always enough space for 4 champions, filled with zeros when not used.
Timers Data 10 bytes per timer entry (All platforms except Apple IIGS) or 16 bytes per timer entry (Apple IIGS) Yes The number of Timers is specified in Block3.
Timers Queue 2 bytes per timer entry Yes The number of Timers is specified in Block3.
Champion Portraits 4x464 bytes No Contains champion portraits when they are not part of the 'Champions Data' section.
This block does not exist when champion portraits are part of the 'Champions Data' section.
Dungeon Data x bytes No Dungeon Data follows exactly the same format as the dungeon.dat files. It is always uncompressed and with a checksum at the end.
Note that it can contain entries in the Clouds and Missile sections, which are always empty in standalone dungeon.dat files but not in saved games.

Versions

Game version Offset of key to decrypt Block2 Champion portraits included in Character Data Size of character data Endian Format of Block1/Block2 in CSBwin
Dungeon Master for Atari ST 1.0, 1.1, 1.2, 1.3 20 Yes 3328 Big PCGAMEBLOCK1
Dungeon Master for Amiga 2.0, 2.1, 2.2 20 Yes 3328 Big PCGAMEBLOCK1
Dungeon Master for Amiga 3.6 20 No 1408 Big PCGAMEBLOCK1
Dungeon Master for X68000 3.0 20 No 1408 Big PCGAMEBLOCK1
Dungeon Master for Apple IIGS 2.0, 2.1 20 Yes 3324 Little PCGAMEBLOCK1
Dungeon Master for PC 3.4 20 No 1404 Little PCGAMEBLOCK1
Dungeon Master for PC-9801 2.0 20 No 1404 Little PCGAMEBLOCK1
Dungeon Master for FM-Towns 2.0 20 No 1404 Little PCGAMEBLOCK1
Chaos Strikes Back for Atari ST 2.0, 2.1 58 Yes 3328 Big GAMEBLOCK1
Chaos Strikes Back for Amiga 3.1, 3.3,
3.5
58 No 1408 Big GAMEBLOCK1
Chaos Strikes Back for X68000 3.1 58 No 1408 Big GAMEBLOCK1
Chaos Strikes Back for PC-9801 3.1 58 No 1404 Little GAMEBLOCK1
Chaos Strikes Back for FM-Towns 3.1 58 No 1404 Little GAMEBLOCK1

Note: The checksum of the Timers Data section is incorrect in the original MINIG.DAT from Chaos Strikes Back for Amiga (German dungeon). All other checksums in this file are correct. Consequently the checksum error must be ignored when decoding this file.

Detailed structure of sections

Many information in this section comes directly from the CSBuild source code.
If you want to look at this source code, you can refer to the function named LoadDungeon in LoadDungeon.cpp and to stuctures defined in types.h (like GAMEBLOCK1, PCGAMEBLOCK1 and GAMEBLOCK2).

Block1 (for Dungeon Master)

10 Words: Random data generated each time the game is saved.
1 Word: Key to decrypt Block2
116 Words: Random data generated each time the game is saved.
1 Word: The value of this word is computed so that the checksum value computed on Block1 is identical to the checksum value computed on Block2.

Block1 (for Chaos Strikes Back)

29 Words: Random data generated each time the game is saved.
1 Word: Key to decrypt Block2
97 Words: Random data generated each time the game is saved.
1 Word: The value of this word is computed so that the checksum value computed on Block1 is identical to the checksum value computed on Block2.

Block2 (for Dungeon Master)

struct PCGAMEBLOCK1
{
  // Data below this line is 512 bytes long.  This
  // block is the first read from the game save file.
  i8  Byte740[300];  //000
  i8  Byte22598;     //300
  ui8 Byte22596;     //301
  i8  FILL438[4];    //302
  i16 SaveOption;    //306
  i16 RandomGameID;  //308 //i32 RandomGameID;  //308 reversed
  i16 Block2Hash;    //310 swapped
  i16 ITEM16Hash;    //312 swapped
  i16 CharacterHash; //314 swapped
  i16 TimersHash;    //316 swapped
  i16 TimerQueHash;  //318 swapped
  i32 totalMoveCount;//320
  i16 Hash326;       //324
  i16 Hash328;       //326
  i16 Hash330;       //328
  i16 Hash332;       //330
  i16 Hash334;       //332
  i16 Hash336;       //334
  i16 Hash338;       //335
  i16 Hash340;       //338
  i16 Hash342;       //340
  i16 Block2Checksum;//342swapped
  i16 ITEM16Checksum;   //344 swapped
  i16 CharacterChecksum;//346 swapped
  i16 TimersChecksum;   //348 swapped
  i16 TimerQueChecksum; //350 swapped
  i16 Checksum354;      //352
  i16 Checksum356;      //354
  i16 Checksum358;      //356
  i16 Checksum360;      //358
  i16 Checksum362;      //360
  i16 Checksum364;      //362
  i16 Checksum366;      //364
  i16 Checksum368;      //366
  i16 Checksum370;      //368
  i16 Checksum372;      //370
  i16 Checksum374;      //372
  i16 Word22594;        //374 swapped
  i16 Word22592;        //376 swapped
  i8  Byte22808[134];   //378 moved as a unit
};

Block2 (for Chaos Strikes Back)

struct GAMEBLOCK1
{
  // Data below this line is 512 bytes long.  This
  // block is the first read from the game save file.
  i8  Byte740[300];  //000
  i8  Byte22598;     //300
  ui8 Byte22596;     //301
  i8  FILL438[4];    //302
  i16 SaveOption;    //306
  i32 RandomGameID;  //308 reversed
  i16 Block2Hash;    //312 swapped
  i16 ITEM16Hash;    //314 swapped
  i16 CharacterHash; //316 swapped
  i16 TimersHash;    //318 swapped
  i16 TimerQueHash;  //320 swapped
  i32 totalMoveCount;//322
  i16 Hash326;       //326
  i16 Hash328;       //328
  i16 Hash330;       //330
  i16 Hash332;       //332
  i16 Hash334;       //334
  i16 Hash336;       //336
  i16 Hash338;       //338
  i16 Hash340;       //340
  i16 Hash342;       //342
  i16 Block2Checksum;//344swapped
  i16 ITEM16Checksum;   //346 swapped
  i16 CharacterChecksum;//348 swapped
  i16 TimersChecksum;   //350 swapped
  i16 TimerQueChecksum; //352 swapped
  i16 Checksum354;      //354
  i16 Checksum356;      //356
  i16 Checksum358;      //358
  i16 Checksum360;      //360
  i16 Checksum362;      //362
  i16 Checksum364;      //364
  i16 Checksum366;      //366
  i16 Checksum368;      //368
  i16 Checksum370;      //370
  i16 Checksum372;      //372
  i16 Checksum374;      //374
  i16 Word22594;        //376 swapped
  i16 Word22592;        //378 swapped
  i8  Byte22808[132];   //380 moved as a unit
};

Block3

struct GAMEBLOCK2
{
  // The following 128 bytes are the second thing
  // read from the game file.
  i32 Time;          //000; reversed
  i32 ranseed;       //004; reversed
  ui16 ObjectInHand; //008; swapped
  i16 numcharacter;  //010; swapped
  i16 partyx;        //012; swapped
  i16 partyy;        //014; swapped
  i16 partyfacing;   //016; swapped
  i16 partyLevel;    //018; swapped
  i16 handChar;      //020; swapped Character index
  i16 MagicCaster;   //022; swapped
  i16 NumTimer;      //024; swapped
  i16 NextAvailTimer;//026; swapped
  i16 MaxTimers;     //028; swapped
  i16 ITEM16QueLen;  //030; swapped
  i32 Long12950;     //032; reversed
  i32 Long12954;     //036; A timestamp. reversed
  i16 Word11710;     //040; swapped
  i16 Word11712;     //042; swapped
  i16 Word11714;     //044; swapped
  i16 MaxITEM16;     //046; swapped
  i8  FILL180[80];   //048;
};

 Offset   Size in
in block   bytes   Description
------------------------------
   00        4     B402_longint_GameClock
   04        4     B367_longint-LastRandomNumber
   08        2     B271_int_PartyLeaderHandObjectID
   0A        2     B410_int_PartyChampionsCount
   0C        2     B409_int_PartyX
   0E        2     B408_int_PartyY
   10        2     B407_int_PartyFacing
   12        2     B406_int_PartyMapIndex
   14        2     B274_int_PartyLeaderChampionIndex
   16        2     B162_int_MagicCasterChampionIndex
   18        2     B342_int_TimersCount
   1A        2     B341_int_FirstUnusedTimerDataIndex
   1C        2     B345_uint_TimersMaximumCount
   1E        2     B337_uint_ExtendedCreatureDataCount
   20        4     B353_longint_LastCreatureAttackTime
   24        4     B352_longint_LastPartyMovementTime
   28        2     B405_int_DisabledPartyMovementClockTicksCount
   2A        2     B404_int_DisabledPartyMovementInThrowDirectionClockTicksCount
   2C        2     B403_int_DirectionOfLastThrow
   2E        2     B338_int_ExtendedCreatureDataMaximumCount
   30       80     00h bytes (unused)

Creatures Extended Data

struct ITEM16
{
  i16 word0;    // monster (DB4) index //swapped when read
private:
  ui8 facing_2; // Four 2-bit fields.
                // Each member of group is in 2 bits starting from bottom.
  ui8 pos_3;    // Four 2-bit fields
                // The position within the room of each
                // member of group.  If the monster occupies 2
                // corners of the room then its position
                // is either 0 or 2.
public:
  ui8 uByte4;   // Least significant byte of d.Time
                // set to d.Time-127
  ui8 uByte5;   // Direction facing??? Giggler got this set to
                // Random(64) + 20.  Time to pause????
  ui8 uByte6;   // mapX square being approached
  ui8 uByte7;   // mapY square being approached
  ui8 uByte8;   // compared to new mapX
  ui8 uByte9;   // compared to new mapY
  ui8 uByte10;  // mapX
  ui8 uByte11;  // mapY
  ui8 uByte12[4];// Treated as array by TAG00ecca(ProcessTimer25)
  inline ui8& positions(void) {return pos_3;};
  inline ui8& facings(void) {return facing_2;};
};

Champion Data

  // ************* NOTE WELL !! *************
  // 3328 bytes read into this part of the structure.
  // This includes the four characters and 128 additional
  // bytes of global data.
  CHARDESC m_Characters[4];
  i16 brightness;
  i8  SeeThruWalls;
  i8  Byte13279;
  i16 Word13278;       //Party shield
  i16 Word13276;       //Spell effect
  i16 Word13274;       //Spell effect
  i8  NumHistEnt;      // #entries in 13268???
  ui8 uByte13271;      //Life frozen time
  i8  Byte13270;       //index of entry in 13268
  i8  Byte13269;       //index of entry in 13268
  i16 Word13268[24];   //history of moves???
                       // bits 0-4 = mapX
                       // bits 5_9 = mapY
                       // bits 10_15 = loaded level
  i8  Byte13220[24];   //parallels 13268??
                       // Is this a once-only kind of thing?
  i8  Invisible;       // 13196;
  i8  Byte13195[41];
  /////////////  End of Character portion of save file /////////////
  //  ***************************************************

struct CHARDESC // character info???
{ i8  name[8];  // 00
  i8  title[16];// 08 // size??
  i16 wordx24; // Not swapped because I don't see it used.
  ui8 FILL26[28-26];
  ui8 facing;  //28
  ui8 position; //29
  ui8 byte30;
  ui8 byte31;
   i8 attackType; //32Signed so check for -1 works.
   i8 byte33;
   i8 incantation[4];//34;
        // [0] = power 96 through 111 --> 1 through 6
        // [1] =      102 through 107
        // [2] =      108 through 113
        // [3] =      114 through 119
  ui8 FILL38[40-38];
  ui8 facing3; //40
  ui8 uByte41;
  ui8 uByte42; //Poison count.  A timer for each one??
  ui8 uByte43;
  i16 busyTimer;// 44 // Timer Index. Signed so -1 works.
  i16 timerIndex;//46;
  i16 word48; // ORed with 0x280
              // 0x8000 while selecting attack option
  i16 ouches;     //50;// mask of damaged body parts.
  i16 HP;         //52
  i16 maxHP;      //54
  i16 stamina;    //56;
  i16 maxStamina; //58;
  i16 mana;       //60;
  i16 maxMana;    //62;
  i16 word64;
  i16 food;       //66;
  i16 water;      //68;
  ATTRIBUTE Attributes[7]; // 70 (maximum, current, minimum)
                           // 70 = [0] Luck??
                           // 73 = [1] Strength
                           // 76 = [2] Dexterity
                           // 79 = [3] Wisdom
                           // 82 = [4] Vitality
                           // 85 = [5] AntiMagic
                           // 88 = [6] AntiFire
  i8  FILL91;
  SKILL skills92[20]; // 92 //0 and 4-7  =Fighter
                            //1 and 8-11 =Ninja
                            //2 and 12-15=Priest
                            //3 and 16-19=Wizard
  RN  possessions[30]; //212
  ui16 load;//272; In 10ths of KG
  ui16 shieldStrength; //274;
  ui8 FILL276[336-276];
  i8  portrait[1]; //336
  ui8 FILL337[800-337];
};

class ATTRIBUTE // 3-byte structure in character
{
private:
  // Let me tell you why I hid these variables.....
  // From what I can tell, these are treated as unsigned
  // bytes.  But at least one of them can go negative.
  // The Luck-Minimum starts at 10 and is decreased by
  // three for each cursed object.  Gather four such
  // objects and you have set the minimum to a very
  // large number.  So I want to handle them exactly the
  // same except that when you ask for a number and it
  // has gone negative, I will return a zero.  But its
  // value will export to the Atari perfectly.
  ui8 ubMaximum;
  ui8 ubCurrent;
  ui8 ubMinimum;
}

struct SKILL // 6-byte structure in character
{
  i16 word0;
  i32 Long2;  // Experience in this skill
};

Timers Data

class TIMER
{ //  We read them from
  // DUNGEON.DAT when game reloaded.
public:
  TIMER(void) {uByte5 = uByte6 = uByte7 = uByte8 = uByte9 = 0xcd;};
  ui32 time; // Top 8 bits=partyLevel, bottom 24 bits = time
  ui8  timerFunction;
    //  1
    //    Happened when I pressed open door switch.
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         byte 9 =
    //  2 Delayed door bashing.
    //         byte 5 = 0
    //         byte 6 = mapX
    //         byte 7 = mapY
    //  5
    //
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         byte 9 = function
    //              for DB2 fiddle bit 0 of word 2
    //                = 0 means set
    //                = 1 means clear
    //                = 2 means toggle bit 0 of word 2
    //              for DB3 (Actuator)
    //  6 Fiddle with DB2 and DB3
    //    Appears to toggle things.
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         byte 8 = position when sent to object
    //                    of DB type 2 (text??)
    //                  For DB type 3 (Actuators) the
    //                  position of the actuator is
    //                  ignored.  All actuators in the
    //                  target cell receive the timer.
    //         byte 9 = 0 means set bit
    //                = 1 means clear
    //                = 2 means toggle
    //    When sent to actuator type 14 ...(see actuator type 14)
    //  7 Change bit 0x04 in CELLFLAG
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         byte 9 = 0 = Set Unconditionally
    //                = 1 = If party at X,Y requeue
    //                      If material monster at X,Y requeue
    //                      else clear
    //                = 2 = Toggle (same rules as set/clear)
    //  8 Change bit 0x08 in CELLFLAG.
    //    If the final state of the bit is a one then the
    //    function 'ProcessNewlyActiveRoom' is called.
    //    The timer goes away without restarting anything.
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         byte 9 = tmrAct
    //                = 0 = tmrAct_SET
    //                = 1 = tmrAct_CLEAR
    //                = 2 = tmrAct_TOGGLE
    //  9
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         byte 9 =
    // 10
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         byte 9 =
    // 11 Disable action after casting spell
    //         byte 5 = chIdx
    //         byte 6 = 0
    // 12 When party moves. Byte 5=chIdx.  Removes timerIndex from chIdx
    // 13 Happened when I put bones in Altar of Vi.
    //         byte 5 = character index
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         byte 8 = position
    //         uByte9 = 0, 1, or 2;
    //         case 2: TAG00dea8(RNffe4) and requeue type 1 in 5 ticks.
    // 22
    //         Processing for function 22 disabled.
    // 24
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         obj  8 = object name
    //
    // 25
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         object name in word 8
    // 29 to 41 have everything to do with monster movement
    //         This is the heart of the monster AI.  Each monster
    //         gets to do its thing when its timer expires.
    // 32 Causes monster action
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         byte 8 = additional time until function changed to 37
    // 37 When character dies and bones
    //       put on floor???
    //         byte5 comes from Item9660.byte2[0]
    //         byte6 = mapX
    //         byte7 = mapY
    //         bz`z yte 8 = 0
    // 48 and 49TAG01826c  Missile????
    //         byte5 = 0
    //         word6 objectID
    //         word8 bits 10-11 direction
    //               bits  5- 9 y
    //               bits  0- 4 x
    // 53 = Watchdog Timer
    // 60 and 61 = ???
    //    After processing this is reset to time+5
    //    This appears to be a pending monster generator.
    //    A monster was generated where a monster already
    //    existed.  Wait a while and try again.
    //         byte6 = mapX
    //         byte7 = mapY
    //         obj8  = object to move
    // 65
    //         byte 6 = mapX
    //         byte 7 = mapY
    //         All actuators at (X,Y) with actuatorType==0
    //           get set to actuatorType 6.  Used to
    //           reactivate monster generators and such.
    // 70 Adjust Brightness (light level)
    //    Timer is reset to d.Time+4 if index has
    //    not been reduced to zero.
    //         word6 = (+/-)index to d.Byte1074
    //                 Add or subtract from d.13282
    //
    // 71 Set by 3-2-6 spell (OH EW SAR) invisibility
    // 72 Character has used shield potion (Ya potion)
    //    It is time to remove the shield.
    //         byte 5 = character index
    //         word 6 = shield increment to be removed
    // 73 set by 3-2-5 spell (OH EW RA See thru walls)
    // 74 Set by protection spell
    // 75 Character hit by missile??
    ///   Character Poison effect??
    //         byte 5 = character index
    //         word 6 = damage
    //         Byte 42 of the character has the timer index.
    // 77 Some sort of spell will wear off
    //    d.Word13274 was incremented by word6
    //         byte 6 = value to subtract from d.Word13274
    // 78 Some sort of spell will wear off
    //    d.Word13276 was incremented by word6
    //         byte 6 = value to subtract from d.Word13276
    // 79 Result of 1-1-5-2 spell ya-bro-ros (magic footprints)
  ui8  uByte5;
  ui8  uByte6;
  ui8  uByte7;
  ui8  uByte8;
  ui8  uByte9;

Timers Queue

No information yet.

Champion Portraits

Champion Portraits are stored in the same format as they are stored in .CMP files. Each portrait consists of 232 words (464 bytes).
Please refer to the Technical Documentation - File Formats - Portrait Files (.CMP) for details.

Dungeon Data

Dungeon Data follows exactly the same format as the dungeon.dat files. It is always uncompressed and with a checksum at the end.
Note that it can contain entries in the Clouds and Missile sections, which are always empty in standalone dungeon.dat files (not in saved games).
There are also more entries in many other object categories (weapons, armor, etc.) than in the original dungeon file. The engine adds free entries to make place for objects created while playing the game, for example when a creature is killed.
Please refer to the Technical Documentation - File Formats - Dungeon File (DUNGEON.DAT) for details.

Special Thanks

I wish to thank Paul R. Stevens for the source code of Chaos Strikes Back for Windows and CSBuild that helped me a lot.

History of this document

Version 1.0 - May 20, 2008
First release