Dungeons of the Unforgiven Player Character
Format type | Saved game |
---|---|
Save locations | Unknown |
Elements saved | Player character data |
Games |
The saved game format for Dungeons of the Unforgiven appears to be a simple dump of the game's internal structures, with a couple of checksum bytes added on, presumably to slow cheaters down a bit. Some of the chunks of data are in a very peculiar order (such as some of the items being in radically different spots of the file), which makes it somewhat annoying to try to directly load into a C struct or similar data structure.
If you're parsing this format, be very mindful of the data types. Signed and unsigned data types, as well as differing field sizes, are used even with related fields. For example, Rings of Protection are stored in an unsigned byte while Potions of Healing are stored in a signed word.
File Layout
Offset (hex) | Data type | Name | Description |
---|---|---|---|
0x0000 | char[18] | playerName | The name of the player character (including a null terminator). |
0x0012 | BYTE[22] | unknown | ! Unknown. |
0x0028 | INT8 | playerRace | The race of the player character. |
0x0029 | INT8 | playerGender | The player character's gender (0x00 == male; 0x01 == female) |
0x002A | INT8 | playerClass | The player character's class. |
0x002B | BYTE[6] | unknown | ! Unknown. |
0x0031 | INT16LE | playerCurrentHP | The player character's current hit points. |
0x0033 | INT16LE | playerMaxHP | The player character's maximum hit points. |
0x0035 | Single | playerCurrentSP | The player character's current spell points. |
0x0039 | Single | playerMaxSP | The player character's maximum spell points. |
0x003D | INT16LE | playerHeight | The player character's height, divided by 4. (The game multiplies this value by 4 to give the stated number of inches.) |
0x003F | INT16LE | nakedWeight | The player's base weight. |
0x0041 | INT16LE | loadedWeight | The player's loaded weight (note: this always gets updated by the game). |
0x0043 | BYTE[62] | unknown | ! Unknown. |
0x0081 | BYTE | weaponFists | Player character has their fists (normally these cannot be dropped). |
0x0082 | BYTE | weaponStick | Player character has the stick. |
0x0083 | BYTE | weaponClub | Player character has the club. |
0x0084 | BYTE | weaponMace | Player character has the mace. |
0x0085 | BYTE | weaponKnife | Player character has the knife. |
0x0086 | BYTE | weaponShortSword | Player character has the short sword. |
0x0087 | BYTE | weaponLongSword | Player character has the long sword. |
0x0088 | BYTE | weaponGreatSword | Player character has the great sword. |
0x0089 | BYTE[5] | unknown | ! Unknown. |
0x008E | INT8 | levelFists | The permanent enchantment level of the fists. |
0x008F | INT8 | levelStick | The permanent enchantment level of the stick. |
0x0090 | INT8 | levelClub | The permanent enchantment level of the club. |
0x0091 | INT8 | levelMace | The permanent enchantment level of the mace. |
0x0092 | INT8 | levelKnife | The permanent enchantment level of the knife. |
0x0093 | INT8 | levelShortSword | The permanent enchantment level of the short sword. |
0x0094 | INT8 | levelLongSword | The permanent enchantment level of the long sword. |
0x0095 | INT8 | levelGreatSword | The permanent enchantment level of the great sword. |
0x0096 | BYTE[5] | unknown | ! Unknown. |
0x009B | INT8 | equippedWeapon | The weapon that the player character currently has equipped. See the Weapon IDs section for valid values. |
0x009C | BYTE[20] | unknown | ! Unknown. |
0x00B0 | BYTE | armorSkin | Player character has their skin (normally this cannot be dropped). |
0x00B1 | BYTE | armorLeather | Player character has leather armor. |
0x00B2 | BYTE | armorChain | Player character has chain armor. |
0x00B3 | BYTE | armorScale | Player character has scale armor. |
0x00B4 | BYTE | armorBreastPlate | Player character has the breast plate. |
0x00B5 | BYTE | armorFieldPlate | Player character has field plate. |
0x00B6 | BYTE | armorTitanium | Player character has the titanium armor. |
0x00B7 | BYTE | armorSlot8 | This "armor" is not implemented in the game and points to garbage data. See the notes below for a little bit more information. ! Figure out exactly what in the executable this is pointing to, as it is technically fully usable. |
0x00B8 | INT8 | levelSkin | The permanent enchantment level of the skin. |
0x00B9 | INT8 | levelLeather | The permanent enchantment level of the leather armor. |
0x00BA | INT8 | levelChain | The permanent enchantment level of the chain armor. |
0x00BB | INT8 | levelScale | The permanent enchantment level of the scale armor. |
0x00BC | INT8 | levelBreastPlate | The permanent enchantment level of the breast plate. |
0x00BD | INT8 | levelFieldPlate | The permanent enchantment level of the field plate. |
0x00BE | INT8 | levelTitanium | The permanent enchantment level of the titanium armor. |
0x00BF | INT8 | levelSlot8 | The permanent enchantment level of the glitchy armor. |
0x00C0 | INT8 | equippedArmor | The armor that the player character currently has equipped. See the Armor IDs section for valid values. |
0x00C1 | BYTE[156] | unknown | ! Unknown. |
0x015D | INT8 | orangePotions | The number of orange potions in the player's inventory (+6 Strength / -3 Luck) |
0x015E | INT8 | greenPotions | The number of green potions in the player's inventory (+6 Intelligence / -3 Agility) |
0x015F | INT8 | bluePotions | The number of blue potions in the player's inventory (+6 Wisdom / -3 Constitution) |
0x0160 | INT8 | redPotions | The number of red potions in the player's inventory (+6 Constitution / -3 Wisdom) |
0x0161 | INT8 | whitePotions | The number of white potions in the player's inventory (+6 Agility / -3 Intelligence) |
0x0162 | INT8 | yellowPotions | The number of yellow potions in the player's inventory (+6 Luck / -3 Strength) |
0x0163 | BYTE[20] | unknown | ! Unknown. |
0x0177 | SPELL_LIST | spellbook | The spells that the player has learned and can cast at any time at the cost of spell power. |
0x022B | SPELL_LIST | scrolls | The spells that the player can cast from a scroll. |
0x02DF | SPELL_LIST | wands | The spell wands that the player possesses. |
0x0393 | SPELL_LIST | papers | The spell papers that the player owns. |
0x0447 | BYTE[13] | unknown | ! Unknown. |
0x0454 | INT32LE | rublesInPocket | The number of rubles that the player has on hand. |
0x0458 | INT32LE | rublesInBank | The number of rubles that the player has in the bank. |
0x045C | BYTE[8] | unknown | ! Unknown. |
0x0464 | INT32LE | cultureStock | The amount of culture stock that the player owns. |
0x0468 | INT32LE | childrenHelped | The number of children that the player helped (at the temple). |
0x046C | INT32LE | magicCrystals | The number of magic crystals that the player owns. |
0x0470 | INT32LE | americanDollars | The number of American Dollars that the player has. |
0x0474 | BYTE[816] | unknown | ! Unknown. |
0x07A4 | Double | experiencePoints | The number of experience points that the player has. |
0x07AC | INT16LE | playerLevel | The player character's current level. |
0x07AE | INT16LE | playerDirection | The direction that the player character is facing (0x00 == north; 0x01 == south; 0x02 == west; 0x03 == east). |
0x07B0 | INT16LE | playerPositionX | The X coordinate of the player in the current dungeon floor. |
0x07B2 | INT16LE | playerPositionY | The Y coordinate of the player in the current dungeon floor. |
0x07B4 | INT16LE | playerFloor | The floor of the dungeon that the player is currently on (0 is the town). |
0x07B6 | INT16LE | playerModule | The module that the player is currently playing. |
0x07B8 | BYTE[18] | unknown | ! Unknown. |
0x07CA | UINT8 | ringsOfRegeneration | The number of rings of regeneration that the player has. |
0x07CB | BYTE | unknown | ! Unknown. |
0x07CC | UINT8 | nuclearHandGrenade | The number of nuclear hand grenades that the player has. |
0x07CD | UINT8 | stonesOfSeeing | The number of stones of seeing that the player has. |
0x07CE | INT16LE | diseaseTimer | The number of turns until disease damages constitution (0 or -1 disables the timer). |
0x07D0 | INT16LE | poisonTimer | The number of turns until poison damages strength (0 or -1 disables the timer). |
0x07D2 | UINT8 | enchantWeaponLevel | The level of the current "Enchant Weapon" preparation spell in effect. |
0x07D3 | UINT8 | enchantArmorLevel | The level of the current "Enchant Armor" preparation spell in effect. |
0x07D4 | UINT8 | bodyArmor | The number of body armor items that the player has (not related to the gear, listed above). |
0x07D5 | UINT8 | ringOfProtection | The number of rings of protection that the player has. |
0x07D6 | UINT8 | antiMagicRing | The number of anti-magic rings that the player has. |
0x07D7 | BYTE | feather | Spell "Feather" is in effect. 0x01 == the preparation version; 0x64 == "Permanent Feather." |
0x07D8 | BYTE | fastMove | Preparation spell "Fast Move" is in effect. Values greater than 0x01 make it permanent despite there being no such permanent spell! |
0x07D9 | BYTE | invisibility | Spell "Invisibility" is in effect. 0x01 == the preparation version; 0x64 == "Permanent Invisibility." |
0x07DA | INT32LE | playerAge | The current age of the player character. |
0x07DE | UINT8 | preparationStrength | Preparation spell "Strength" is in effect. Normally set to 0x05 (yet changing it or any of the following strength/agility spell values to another nonzero value will not change the number of strength/agility points deducted by an inn stay). |
0x07DF | UINT8 | preparationAgility | Preparation spell "Agility" is in effect. Normally set to 0x05. |
0x07E0 | UINT8 | superStrength | Preparation spell "Super Strength" is in effect. Normally set to 0x0A. |
0x07E1 | UINT8 | superAgility | Preparation spell "Super Agility" is in effect. Normally set to 0x0A. |
0x07E2 | INT16LE | battleStrengthTimer | The number of turns the battle spell "Strength" remains in effect. |
0x07E4 | INT16LE | battleSpeedTimer | The number of turns the battle spell "Speed" remains in effect. |
0x07E6 | INT16LE | slowMonsterTimer | The number of turns the battle spell "Slow Enemies" remains in effect. |
0x07E8 | UINT8 | powerWeaponLevel | The level of the current "Power Weapon" battle spell in effect. |
0x07E9 | INT16LE | powerWeaponTimer | The number of turns the current "Power Weapon" battle spell remains in effect. If this is set to zero or negative while powerWeaponLevel is nonzero, the spell will never wear off until an inn stay! |
0x07EB | UINT8 | protectLevel | The level of the current "Protection" battle spell in effect (0x01 == "Minor Protection;" 0x02 == "Protection;" 0x03 == "Major Protection;" 0x04 == "Ultra Protection"). |
0x07EC | INT16LE | protectTimer | The number of turns the current "Protection" battle spell remains in effect. If this is set to zero or negative while protectLevel is nonzero, the spell will never wear off until an inn stay! |
0x07EE | INT16LE | resistPoisonTimer | The number of turns the battle spell "Resist Poison" remains in effect. |
0x07F0 | INT16LE | resistDiseaseTimer | The number of turns the battle spell "Resist Disease" remains in effect. |
0x07F2 | INT16LE | antiColdTimer | The number of turns the battle spell "Anti-Cold" remains in effect. |
0x07F4 | INT16LE | antiFireTimer | The number of turns the battle spell "Anti-Fire" remains in effect. |
0x07F6 | INT16LE | resistDrainTimer | The number of turns the battle spell "Resist Level Drain" remains in effect. |
0x07F8 | INT16LE | stopMonsterTimer | The number of turns the battle spell "Sleep" could remain in effect (though it often wears off much earlier due to the vagaries of the spell). |
0x07FA | INT16LE | holdMonsterTimer | The number of turns the battle spell "Hold Monster" remains in effect. |
0x07FC | BYTE[6] | unknown | ! Unknown. |
0x0802 | INT8 | floorSloshers | The number of floor sloshers that the player has. |
0x0803 | BYTE[15] | unknown | ! Unknown. |
0x0812 | INT16LE | potionsOfHealing | The number of potions of healing that the player has. |
0x0814 | INT16LE | stonesOfTeleportation | The number of stones of teleportation that the player has. |
0x0816 | INT16LE | playerStrength | The number of strength points that the player has. |
0x0818 | INT16LE | playerIntelligence | The number of intelligence points that the player has. |
0x081A | INT16LE | playerWisdom | The number of wisdom points that the player has. |
0x081C | INT16LE | playerConstitution | The number of constitution points that the player has. |
0x081E | INT16LE | playerAgility | The number of agility points that the player has. |
0x0820 | INT16LE | playerLuck | The number of luck points that the player has. |
0x0822 | BYTE[21] | trapDoorKey | The trapdoor keys owned by the player. For each nonzero value in trapDoorKey[n], the player is able to use any trapdoors leading to floor (n * 5). |
0x0837 | BYTE[28] | unknown | ! Unknown. |
0x0853 | INT8 | gauntlets | The number of gauntlets that the player has. |
0x0854 | INT16LE | fillHpAndSpOnLoad | If this is set to a non-zero value, the player's HP and SP will be reset to their maximum values when the saved game is loaded. This value is reset to 0 after the character is loaded, so the effect will only happen once. This does not appear to be set anywhere else in the game, so its purpose is unknown (debugging, perhaps?). |
0x0856 | BYTE[160] | unknown | ! Unknown. |
0x08F6 | BYTE | difficulty | The difficulty level that the player is playing on. (0x00 == normal; 0x01 == "I can handle anything!") |
0x08F7 | BYTE[122] | unknown | ! Unknown. |
0x0971 | UINT32LE | gameTimer | The amount of total time, in PC timer ticks (note: the Intel 8253 PIT tick rate is roughly 18.2hz), that the player has been playing on this saved game. ! Figure out whether or not this actually does anything. |
0x0975 | BYTE[274] | unknown | ! Unknown. |
0x0A87 | UINT8[2] | checksum | The checksum of the saved game file. See below for more information. |
Races
The following races can be chosen:
ID | Race |
---|---|
0x00 | Humanoid |
0x01 | Ape |
0x02 | Childman |
0x03 | Rodent |
0x04 | Hobo |
0x05 | Giant |
0x06 | Midget |
0x07 | Shrimp |
Classes
The following classes can be chosen:
ID | Class |
---|---|
0x00 | Fighter |
0x01 | Worshipper |
0x02 | Monk |
0x03 | Wizard |
0x04 | Priest |
0x05 | Sage |
0x06 | Mage |
Spells
Spells are stored in the save file per the following table. In all cases, the spells are stored in order from left-to-right, top-to-bottom, with respect to the in-game spell browser. Each spell category has 15 unused bytes at the end; Monks have all these bytes filled with 0x01 in their spellbook section nonetheless.
Data type | Name | Description |
---|---|---|
INT8[45] | spellsPermanent | The permanent spells that the player has access to. |
INT8[45] | spellsPreparation | The preparation spells that the player has access to. |
INT8[45] | spellsWizard | The wizard battle spells that the player has access to. |
INT8[45] | spellsPriest | The priest battle spells that the player has access to. |
Weapon IDs
The following weapon IDs can be used in the equippedWeapon field.
ID | Weapon Type |
---|---|
0x00 | Fists |
0x01 | Stick |
0x02 | Club |
0x03 | Mace |
0x04 | Knife |
0x05 | Short Sword |
0x06 | Long Sword |
0x07 | Great Sword |
- Note: the player does not have to own the specified weapon type in order to have it equipped. The equipment check is only performed when the player switches their weapon in-game.
Armor IDs
The following weapon IDs can be used in the equippedArmor field.
ID | Armor Type |
---|---|
0x00 | Skin |
0x01 | Leather |
0x02 | Chain |
0x03 | Scale |
0x04 | Breast Plate |
0x05 | Field Plate |
0x06 | Titanium |
0x07 | Glitchy Armor* |
- Note: the player does not have to own the specified armor type in order to have it equipped. The equipment check is only performed when the player switches their armor in-game.
- *The eighth armor slot always appears in the armor list of the game as a blank slot, but only seven armor types appear within the game world. If the set of armor is hacked into the game with a hex editor or memory editor, it appears as jumbled text and provides a currently-unknown amount of protection.
Checksum
The checksum is calculated as follows (example in C# and assuming saveBytes is the saved game data, sans checksum—that is, the first 2695 (0xA87) bytes in the file):
checksum[0] = checksum[1] = 0;
foreach(var b in saveBytes) {
checksum[0] += b;
checksum[1] += checksum[0] - checksum[0] * checksum[0];
}
If the checksum is incorrect for the saved game file, the player will be immediately booted back to the DOS prompt with a "corrupted character" error.
Credits
This file format was reverse engineered and documented by Spectere and Bag of Magic Food. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)