Skip to content

Save File Formats

Allofich edited this page Apr 26, 2024 · 37 revisions

SAVEGAME.0x

BYTE ScreenBuffer[64000];
BYTE Palette[768];
GAMESTATE GameState; // 3559 bytes
WORD GameLevel[0xC000];

GAMESTATE

BYTE junk;
BYTE WeatherFlags; // 0x80 precipitation, 1 rain, 2 snow
BYTE PlayerFloor;
BYTE OldFloor;
BYTE junk[388];
WORD LightsCount;
LIGHT Lights[256]; // autogenerated lights, 6 bytes
DWORD junk;
char LevelName[33];
char InfName[13];
BYTE junk[128];
MIFHEADER LevelHeader; // 61 byte; same as in MIF files
char MifName[13];
DWORD junk;
WORD WalkSpeed;
WORD TurnSpeed;
DWORD junk;
WORD Flags5;
WORD Flags4;
BYTE junk[48];
WORD LevelEntryX, LevelEntryY;
WORD PlayerX, PlayerZ, PlayerY;
DWORD unknown1;
WORD PlayerAngle;
BYTE junk[6];
WORD LightRadius;
BYTE junk[576];
LOCK Locks[64]; // 3 bytes
BYTE LockCount;
TARGET Targets[64]; // 2 bytes
BYTE TargetCount;
TRIGGER Triggers[64]; // 4 bytes
BYTE TriggerCount;
BYTE junk[136];

Important flags

Flags4

Value Comment
0x200 Player is in the wilderness

SAVEENGN.0x

Scrambling

The first 2 structures in SAVEENGN are scrambled in one pass.

scramble(data, length) <-
   buffer <- length
   i <- 0
   while i < length
      key <- ( ror16(buffer, buffer&0xF) & 0xFF
      data[i] <- data[i] xor key
      i <- i + 1
      buffer <- buffer - 1

File structure

NPCDATA Player; // 1054 bytes
PLAYERDATA PlayerData; // 2609 bytes
// End of the scrambled part
NPCDATA Enemies[8];
LOOTITEM Loot[200]; // 28 bytes
INTERNALSTATE GameState2; // 288 bytes

NPCDATA

DWORD RandomSeed;
BYTE Race;
BYTE Class;
BYTE Level;
BYTE IsFemale;
BYTE HomeCityId;
union {
   char Name[32];
   CREATUREDATA CrData; // 32 bytes
}
BYTE CurrentAttrs[8];
BYTE BaseAttrs[8];
WORD ActorValues[16];
WORD HP, MaxHP, Stamina;
BYTE misc[7];
WORD Mana, MaxMana;
BYTE misc2;
BYTE face; // face index in appropriate CIF file
BYTE misc3[2];
INVENTORYITEM Inventory[40];
BYTE KnownSpellCount;
BYTE KnownSpellIDs[160];
WORD Gold;
DWORD Experience; // NPC only
WORD StatusFlags;
BYTE StatusCounters[10];
BYTE EncumbranceMod;
WORD ActiveEffects;
WORD ShieldValue;

NPC attributes are stored in the internal format, where 255 corresponds to a displayed value of 100. ActorValues are internally multiplied by 5.

Actor values

0  Damage bonus
1  Carry limit
5  Magic defense
7  Defense bonus
8  Attack bonus
9  Bonus AC
11 Bonus health
13 Bonus healing
14 Charisma
15 Bonus luck

Status flags

0x01 - diseased
0x02 - poisoned
0x04 - cursed
0x08 - blessed
0x10 - fortified
0x20 - drunk
0x40 - paralyzed
0x80 - regenerating
0x100 - dead
0x200 - being drained
0x1000 - is a creature
0x4000 - is the player (?)

Active effects

0x01 - invisible
0x02 - a non-target
0x04 - resistant to fire
0x08 - resistant to cold
0x10 - resistant to shock
0x20 - resistant to acid
0x40 - resistant to poison
0x80 - levitating
0x0100 - Probably the Light effect
0x0200 - silenced
0x0400 - able to absorb spells
0x0800 - able to reflect spells
0x1000 - resistant to spells

(to be continued)

PLAYERDATA

DWORD PCGold;
WORD Blessing;
WORD Flags2;
WORD GameOptions;
DWORD GameTime;
WORD DateTime[5]; // YMDHm
DWORD junk;
WORD MovementAngle;
WORD Flags6;
BYTE junk;
BYTE JumpPhase;
BYTE Flags7; // 1 if in the starting dungeon
BYTE junk[23];
DWORD RandomDungeonSeed;
WORD WildernessX, WildernessY;
BYTE WildernessBlocks[4];
WORD WildernessBlockX, WildernessBlockY;
WORD ReturnBlockX, ReturnBlockY;
WORD ReturnX, ReturnY;
WORD ReturnAngle;
DWORD WildernessSeed;
BYTE junk[8];
DYNTRIGGER DynamicTriggers[8]; // 6 bytes
BYTE Keyring[14];
BUFF PlayerBuffs[16]; // 60 bytes
BYTE junk[2];
CITYGENDATA CurrentCity; // 56 bytes
BYTE Weather[36];
BYTE junk[53];
NPCSPRITE NPCs[15];
NPCSPRITE Enemies[8]; // 28 bytes
BYTE junk[147];
BASEQUEST CityQuests[4]; // 41 bytes
EXTQUEST PalaceQuests[4]; // 52 bytes
EXTQUEST RumorsBuffer;
DWORD unk1;
MQDATA MainQuest; // 10 bytes
BYTE junk2[8];
WORD PickedLocks[32];
BYTE junk3[6];
WORD WorldX, WorldY;
WORD junk4;
BYTE Portrait[9];
AQDATA ArtifactQuest; // 19 bytes
BYTE junk1[2];
BYTE LootRecordCount;

Flags2

Value Comment
0x800 Player is in a dungeon
0x2000 Draw compass
0x4000 Dungeon entered from wilderness, need to return to the wilderness pos

Flags6

Value Comment
0x10 Player's weapon/fists are readied
0x100 Have shown holiday text. Cleared on day change.
0x2000 Player is moving

LOOTITEM

WORD unknown1;
WORD ContainerPosition;
BYTE Floor;
WORD GoldValue;
WORD unknown2;
INVENTORYITEM Item; // 19 bytes

INVENTORYITEM

BYTE SlotID;
WORD Weight;
BYTE Hands; // Also # of charges left? (could be wrong because there is `Health` field)
BYTE Param1;
BYTE Param2;
WORD Health; // Also amount of money left for King Orgnum's coffer
WORD MaxHealth; // Also # of max charges
DWORD Price; // Repair cost * Repair time is related to this field.
BYTE Flags;
BYTE X;
BYTE Material;
BYTE Y;
BYTE Attribute;

Interpretation of SlotID, Param1, Param2, Flags, X and Y depends on the item type.

Flag byte: evttccnm

  • e Equipped
  • v This item record is in use
  • t Subtype
  • c Class
  • n Not identified
  • m Has a material
c               t                  X            Y       Param1  Param2    Weight
0   Armor       Plt/Chn/Lthr       Enchantment  Spell   AC      -         Weight
1   Weapon      -                  Enchantment  Spell   MinDmg  MaxDmg    Weight
2   Trinket     Spell/AttrBonus/AC (see below)
3   Potion      -                  -            Spell   -       -         Stack size

For spell-casting trinkets, 'Material' shows its classification: offensive, defensive or misc, and 'Hands' stores the charges left.

For attribute-enchancing trinkets, Y contains the decimal bonus value.

CITYGENDATA

char CityName[20];
char MIFName[13];
BYTE CitySize; // 4..6
WORD BlockOffset;
BYTE Province;
BYTE CityType;
WORD LocalX, LocalY;
BYTE CityID;
BYTE unk1;
WORD AbsLatitude;
BYTE TerrainType;
BYTE Quarter;
DWORD RulerSeed;
DWORD CitySeed;

BASEQUEST

DWORD QuestSeed;
WORD Location1;
WORD Item1;
DWORD StartDate;
DWORD DueDate;
DWORD TavernData;
DWORD DestinationData;
DWORD Reward;
WORD Portrait;
DWORD QuestGiverSeed;
WORD OpponentFaction;
BYTE DestCityID;
BYTE QuestCityID;
BYTE unk1;
BYTE Relationship;
BYTE EscorteeIsFemale;

EXTQUEST

BASEQUEST q;
BYTE unk1[5];
BYTE Faction;
BYTE NPCRace;
BYTE MonsterRace;
BYTE LocationName;
BYTE LocNameTmpl;

AQDATA

BYTE CurrentArtifact;
DWORD TavernLocation;
BYTE MapDungeonId;
BYTE MapProvince;
BYTE ArtifactDungeonId;
BYTE ArtifactProvince;
BYTE ArtifactQuestFlags;
WORD ArtifactBitmask;
DWORD ArtifactQuestStarted;
BYTE ArtifactDays;
WORD ArtPriceOrOffset;

MQDATA

BYTE CanHaveVision;
BYTE NextStep;
BYTE DungeonLocationKnown;
BYTE AcceptedKeyQuest;
BYTE HadVision;
BYTE TalkedToRuler;
BYTE StepDone;
BYTE HasKeyItem;
BYTE HasStaffPiece;
BYTE ShowKey;

NPCSPRITE

This is an extremely versatile structure, and it is used in many different ways.

WORD X, Z, Y;
PVOID pDynamicLight;
WORD Speed;
WORD Angle;
BYTE Flat;
BYTE Frame;
BYTE Param1;
WORD Flags;
BYTE Param2;
WORD Data;
WORD Param3;
WORD unk1;
WORD hasPlayedBodyFallSound;
WORD Param4;

If Flags have 0x4000 bit set, the structure is not used.

For town NPCs, Data is a random value used for color transformation. For enemies, this is a pointer to a NPCDATA structure.

If Flat has two highest bits set, the (lower bits + 1) specify the animation index, 1 to 5. Param2 specifies the slot the animation was loaded into. There are many preloaded animations, and NPCs use only the slots 52 and 57. Apparently, only a single type of creature can be shown at one time by Arena.

(more structs to come)

STATES.0x

State of locks and triggers for the MQ dungeons only.

struct {
   BYTE TriggerCount;
   TRIGGER Triggers[64]; // 4 bytes
   BYTE LockCount;
   LOCK Locks[64];
} LevelHashTable[64];

id <- (Province*2 + DungeonID)*4 + Level

SPELLS.0x and SPELLSG.0x

SPELLDATA Spells[N]; // 85 bytes

N = 32 for SPELLS (custom spells) and 128 for SPELLSG (standard spells).

Indices of custom spells have the highest bit set.

SPELLDATA

WORD Param0[3]; // Spell damage range start (chance)?
WORD Param1[3]; // Spell damage range end (chance)?
WORD Param2[3]; // Spell damage increase range start?
WORD Param3[3]; // Spell damage increase range end?
WORD Param4[3]; // Number of levels to spell damage increase (chance)?
WORD Param5[3]; // Number of uses to spell damage increase?
BYTE TargetType;
BYTE unk;
BYTE Element;
WORD Flags;
BYTE Effects[3]; // e.g. 'Fortify'; 0xFF = no effect
BYTE SubEffects[3]; // e.g. 'Attribute:'
BYTE AffectedAttr[3]; // e.g. 'Strength'
WORD Cost;
char Name[33];

BUFF (Fortify spells, drunkenness, poison, disease, etc. that affect attributes)

WORD RemainingDuration2 (Not sure, is 1 higher than RemainingDuration and counts down with it)
WORD RemainingDuration (Remaining duration in game minutes before the effect starts wearing off)
WORD
SBYTE STRModifyAmount
SBYTE INTModifyAmount
SBYTE WILModifyAmount
SBYTE AGIModifyAmount
SBYTE SPDModifyAmount
SBYTE ENDModifyAmount
SBYTE PERModifyAmount
SBYTE LUCModifyAmount
Unknown (6 bytes)
WORD
WORD
WORD
Unknown (14 bytes)		
DWORD npcDataPtr (Points to the NPCDATA of the affected entity)
WORD
WORD
WORD
Unknown (6 bytes)	
BYTE	
BYTE
BYTE Index (0xFF when this BUFF slot is unused. 0-based index of this status effect (see the order in "Status Flags"))
BYTE ReleasedAmount (0xFF when this BUFF slot is unused. "1" while the effect is active, then starts increasing as the effect wears off, counting the number of points that the affected attribute has returned from its modified value to the original value. When the count reaches the full amount that the effect was modified while active, the effect has completely worn off and the BUFF slot is cleared to an unused state.)

The parameters likely depend on the spell type.

INn.0x

Inn room reservation.

WORD HoursLeft;
DWORD TimeLimit;

n is the third and following digits of the tavern identifier:

  • For cities, door coordinates: (Y<<8)+X.
  • For the wilderness, block coordinates: (wY<<16)+wX

REn.0x

Items being repaired.

REPAIRJOB jobs[5];

REPAIRJOB

BYTE Valid;
DWORD DueTo;
INVENTORYITEM item;

AUTOMAP.0x

FoW caches.

struct {
   DWORD lvlhash;
   struct {
      WORD x, y;
      char text[60];
   } notes[64];
   BYTE bitmap[4096]; // 2 bits per block
} cache[16];

If x has the 0x8000 bit set, then it is absolute pixel coordinates. Otherwise, it is the automap block coordinates.

Bitmap values range from 0 - hidden to 3 - completely visible.

The hash value is 0xC0000000 for the starting dungeon, and the following 32-bit value otherwise:

tttpppff cityid Y X

where X and Y are the wilderness block coordinates if it is a wilderness dungeon, cityid is a dungeon or city id, t is the dungeon type (3), p the province, and f is the floor.

LOG.0x

The log file consists of records terminated by " *". Each record starts with a header, which starts with & and ends with \r\n.

Clone this wiki locally