Working backwards from the finished product to understand how it ticks.
File List
AFTER.CON afterlife tile images AFTER.MAP afterlife tile data BOAT.DAT ??? CASTLE.MAP castle tile data DUNGEON.CON talisman and Blackeye dungeon tile images DUNGEON.MAP talisman and Blackeye dungeon tile data DUNGMON.CON talisman and Blackeye dungeon NPC images DUNGMON.DAT talisman and Blackeye dungeon monster data ENCONTER.SET ??? NPC dialogue ENDMON.CON end sequence NPC images ITEM.001 menu image; forest at night ITEM.002 menu image; castle ITEM.003 menu image; sword in front of axe ITEM.004 menu image; man at campfire during night ITEM.005 menu image; club, shield, and morning star ITEM.006 menu image; some design ITEM.007 menu image; landscape LAND.CON overworld tile images LANDMON.CON overworld NPC images LANDMON.DAT overworld NPC data LIFEMON.CON afterlife NPC images LIFEMON.DAT afterlife NPC data MSCROLL.PIC scroll end image from title screen PLAYER.CON player images PLAYER.SAV Player data RUINMON.CON ruin NPC images RUINMON.DAT ruin NPC data SCROLL.PIC Dalagash poem on start screen SIGN.DAT ??? THEEND.CON ending sequence tile images THEEND.MAP ending sequence tile data TITLE.001 top left of title screen background TITLE.002 top middle of title screen background TITLE.003 top right of title screen background TITLE.004 bottom left of title screen background TITLE.005 bottom middle of title screen background TITLE.006 bottom right of title screen background TOWN.CON town tile images TOWN.MAP town tile data; all six towns are in this file TOWNMON.CON town NPC images TOWNMON.DAT town NPC data UNIV.CON universal tile images; contains water, grass, FX tiles, and more VAMPYR.001 logo on game hud VAMPYR.DOC game info VAMPYR.EXE executable; ??? VCASTLE.MAP vampyr's castle tile data WORLD.MAP overworld tile data PLAYER.SAV player data refer to hax.xcf for specifics x.MAP raw tile data each byte represents 1 tile can be paired with a tileset to produce and image of the map does NOT contain monster data map dimensions divisible by 10; results in "interesting" file sizes x.CON graphic files three different file sizes 4860 (player.con, univ.con) 6480 8424 (*mon.con) file sizes divisible by 18^2, the size of graphics BOAT.DAT file size: 500 UNKNOWN FUNCTION XMON.DAT all have filesize of 364 monster data, stored in table fashion 13 entries of 28 bytes each 1 byte, 15 byte name, 12 bytes of data (including weapon/armor) ENCONTER.SET dialogue UNKNOWN ORGANIZATION filesize: 43806 ITEM.001 - ITEM.007 icons
ENCOUNTER.SET
This is an array of records which defines which NPCs exist, where, and what they say.
struct npc { char icon; char unknown; // maybe map ID? char stack_size; char x; char y; char dialog1_length; char[70] dialog1; char dialog_2_length; char[70] dialog2; };
The icon corresponds to an index in the sprite sheet for a given level (e.g. if a town then it's TOWNMAN.CON, and a value of 1 corresponds to a merchant, 2 is the guard, etc.
The stack size says how many NPCs will be part of the battle. For example, this is typically 8 (the max) for guards.
x and y are self explanatory although the top-left corner of the map is (1,1). (The is called out because the intuitive way is to have the top-left as 0,0)
dialog1 and dialog2 always has space for 70 characters, even if the length is smaller. This means the rest of the buffer typically has garbage. It's interesting to note that some of the garbage looks a lot like Pascal (combine that with the fact that the binary appears to has BGI statically linked in suggests that this was coded in Borland Turbo Pascal circa 1989 or before).
Shops utilize the dialog not as text but as specially formatted strings:
- Armor shops look like: ARM XX YY ZZ ... where XX, YY, ZZ .. are 2 character IDs of the armor being sold (see the list of IDs below)
- Weapon shops look like: WPN XX YY ZZ ... where XX, YY, ZZ .. are 2 character IDs of the weapons being sold (see the list of IDs below)
- Pubs look like: PUB X Y Z
- Inns look like: INN X Y Z
- Trainers look like: TRN X Y Z
- Travel agencies look like TNP ID COST
Save File
The player save is stored in a file called PLAYER.SAV. It has 77 bytes in the following structure:
struct player_save { char name_length; char name[10]; char race; unsigned char level; // game max is 15 signed short max_life; signed short current_life; signed short gold; unsigned int xp; unsigned char max_magic; // note that life is signed but magic is not unsigned char current_magic; unsigned char physical_strength; unsigned char mental_strength; unsigned char dexterity; unsigned char constitution; unsigned char luck; // some spells buff stats so this tracks what to reset those stats to when the buff runs off char base_fighting_attack; char base_fighting_defense; char base_magic_offensive; char base_magic_defense; char fighting_attack; char fighting_defense; char magic_offensive; char magic_defense; char magic_miscellaneous; char lock_picking; char climbing; char stealing; char perception; char held_weapon; // weapons in range 0x00 to 0x35 char worn_armor; // armor in range 0x00 to 0x?? char mission_one_flag; // whether you spoke to the evil claric char mission_two_flag; // whether you talked to Tulik char mission_three_flag; // whether you beat the vampire in myron char mission_four_flag; // whether you have the talisman char mission_five_flag; // whether you've talked to the sage char mission_six_flag; // probably if the basement in calatiki is open char mission_one_complete; char mission_two_complete; char mission_three_complete; char mission_four_complete; char mission_five_complete; char blue_rose; char met_dalagash; char unknown; // probably a flag, unremarked upon by the Gods in Heaven... char learned_rust_armor; char x; // x coord on overworld char y; // y coord on overworld char current_spell; // which buff is currently applied (e.g. wizard eye,etc) char mystery; // turn counter? goes up by one if you move 1 step on the overworld... // reset on escape from heaven? char weapon_inventory[5]; // restrictions on weapon range apply signed char held_weapon_durability; signed char weapon_inventory_durability[5]; // 1 to 1 map with weapon_inventory signed char worn_armor_durability; };
Name
The player name is split into two parts: the length, followed by that many characters in the name.
The player character generator will always set the length to 10. If the name has less than 10 letters, the name will be padded out with spaces (0x20). Regardless of the length, null characters (0x00) will prematurely terminate the string.
The letters of the name can be anything on Code page 437 . The null character will prematurely terminate the string. Alt codes will work for entering non-ASCII characters.
Race
A valid race is one of 6 values:
- 0 - Human
- 1 - Dwarf
- 2 - Elf
- 3 - Corintir
- 4 - Superior
- 5 - Superior
The lines up with the images in PLAYER.CON. Any value between 0x06 and 0x0E will change the player graphic to the appropriate sprite on that sprite sheet but will not show anything in the Race field of the character sheet.
Values of 0x0F and above make you look like a spider, but the game will crash trying to enter towns or dungeons. Saving will fail and erase your file so don't do that.
"Base" Stats VS Apparent Stats
Some spells buff a stat for a period of time. For example, Mystical Boost will raise your Magic Offensive stat until it wears off. The game tracks this temporarily augmented stat as a separate field in the save data from the actual value of that stat. The real value is tracked as the "base".
Current Buff
Along with the stats that are affected, the sve file tracks which buff is currently applied. It mostly likely uses this to know, when the time is up, which stat to reset. Each spell has it's own value:
00 (Normal) 01 Wizard Eye 02 Mystical Boost (buff to magic offensive, reflected on ztats screen) 03 Heroism 04 Spell Protection 05 Shield 06 Iron Skin ... ?
Weapon IDs
Each weapon is represented by a unique value. This includes not only items you can buy in store but also weapons that NPCs use:
00 Hands 01 Dagger 02 Staff 03 Mace 04 Morning star 05 Axe 06 Long Sword 07 2H Sword 08 Long Bow 09 Sling 0A Club 0B Dagger +1 0C Staff +1 0D Mace +1 0E Morning Star +1 0F Axe +1 10 Long Sword +1 11 2H Sword +1 12 Long Bow +1 13 Sling +1 14 Club +1 15 Dagger +2 16 Staff +2 17 Mace +2 18 Morn. Star +2 19 Axe +2 1A Long Sword +2 1B 2H Sword +2 1C Long Bow +2 1D Sling +2 1E Club +2 1F Dagger +3 20 Staff +3 21 Mace +3 22 Morn. Star +3 23 Axe +3 24 Long Sword +3 25 2H Sword +3 26 Long Bow +3 27 Sling +3 28 Club +3 29 Slime 2A Bite 2B Big Bite 2C Giant Bite 2D Fist 2E Big Fist 2F Giant Fist 30 Claws 31 Big Claws 32 Giant Claws 33 Vampire Touch 34 Vampyr Touch 35 Big Broom 36 255 ... ?
Armor IDs
Each armor in the game is represented with a unique ID. This includes things you can buy in-store as well as monster armor:
00 Nude 01 Cloth 02 Padded 03 Leather 04 Studded 05 Ring Mail 06 Scale Mail 07 Chain Mail 08 Splint Mail 09 Plate Mail 0A Full Plate 0B Cloth +1 ... 15 Cloth +2 ... 1F Cloth +3 .. 28 Full Plate +3 29 Bones 2A Thick Skin 2B Transparency 2C Fur 2D Thick Fur 2E Scales 2F Magic Robe 30 Hard Bark 31 Slime Coating ... ?
The "Mystery" Counter
There is one byte between the current buff and the player inventory whose use is unknown. My guess is that it's a turn counter because when save, take a single step in the overworld, and save again then it will increment by 1. Although, I'm not sure why the game cares. It seems to reset when you are deemed worthy in Heaven.
Unknown Bytes
Between the mission completion flags and the current overworld location are 4 bytes. They never seem to change.