Introduction
Tyrian, the vertical shooter published by Epic MegaGames in 1995 (later released as
freeware, and ported as the open-source OpenTyrian), keeps every saved game and every high score in one file,
TYRIAN.SAV. It is always 2,502 bytes: 22 save slots of 109 bytes, a 100-byte
block that the ship editor shares, and four checksum bytes. All but the last four bytes are
scrambled with a running XOR, so the file looks like noise until the scrambling is undone.
Nothing about the file was published by the makers. The layout here is the one in
OpenTyrian’s source (config.c, config.h, player.h), which reads and writes the
same file as the DOS game, and it was checked against the one real file found on this machine,
a TYRIAN.SAV beside Tyrian 2.1 / Tyrian 2000 data, whose four checksums come out right once the
scrambling is undone. The names of the items are those of Tyrian’s own item table
(Item numbers).
A file dropped on any page opens in the explorer, which undoes the scrambling, checks the four checksums and shows every slot field by field, the items by name, and the high-score tables.
The files
| File | What it holds | Here |
|---|---|---|
TYRIAN.SAV | The 22 save slots and the high scores, 2,502 bytes | This page |
TYRIAN.CFG | Detail, speed, volumes, joystick buttons, keys: 28 bytes, not scrambled | Configuration |
TYRIAN.CDT | The end credits, not a saved game | Tyrian credits |
TYRIAN.HDT | The item table and the game’s texts (read by the game, never written) | Names only, Item numbers |
opentyrian.cfg | OpenTyrian’s own text settings (video, keys) | Not covered (text) |
There is no separate high-score file in any version found: the high scores live inside
TYRIAN.SAV, one entry in each of the 22 slots (see High scores).
Nor is there a separate file for the secret Destruct game or for Super Arcade: they use the
same items and the same slots, marked in the item bytes (Items).
Structure
Numbers are little-endian, except the editor level, which is stored high byte first.
0x0000Slots 1 to 11 one-player: saves 1 to 10 and the autosave11 × 1090x04AFSlots 12 to 22 two-player: saves 12 to 21 and the autosave11 × 1090x095EShip editor data 98 availability flags and the editor level1000x09C2Checksums sum, negated sum, running product, XOR: not scrambled4
Slot 11 and slot 22 are written by the game itself, under the name LAST LEVEL, each time a
level begins (slot 22 in a two-player game). A level script’s explicit autosave command (used before bonus
games) writes slot 11 even in a two-player game. The player cannot save into either: the game refuses any slot
whose number is a multiple of 11. In the game’s load menu the one-player and two-player slots are two
pages of 11, and a slot whose level number is 0 is shown as an empty slot.
The scrambling
The first 2,498 bytes are scrambled, byte by byte, with a 10-byte key and the scrambled byte before:
key = 15 50 89 240 147 34 86 9 32 208
scramble: for i = 0 to 2497: data[i] = data[i] XOR key[(i + 1) mod 10]
if i > 0: data[i] = data[i] XOR data[i - 1] (the new, scrambled byte before)
unscramble: for i = 2497 down to 0: data[i] = data[i] XOR key[(i + 1) mod 10]
if i > 0: data[i] = data[i] XOR data[i - 1] (the byte before, not yet unscrambled)
So a run of equal plain bytes gives a pattern with a period of 10. The start of the real file is
32 6B 9B 08 2A 7C 75 55 85 8A: byte 0 is 32 XOR 32 = 00 (key byte 1 is 50 = 32),
byte 1 is 6B XOR 59 XOR 32 = 00 (key byte 2 is 89 = 59), and so on: an empty slot is all zeros at the start.
The Tyrian credits file and the help text in TYRIAN.HDT use the same kind of scrambling with a different key
(Tyrian credits).
The checksums
The last four bytes hold four checks of the 2,498 unscrambled bytes, themselves stored as they are:
| Offset | Check | How it is made |
|---|---|---|
0x09C2 | Sum | Add all the bytes, modulo 256. |
0x09C3 | Negated sum | Subtract all the bytes from 0, modulo 256. |
0x09C4 | Running product | Start at 1; for each byte, multiply by it and add 1, modulo 256. |
0x09C5 | XOR | XOR all the bytes together. |
When the game starts it reads 2,502 bytes, undoes the scrambling and compares the four bytes. It does not
check the length: a file with fewer bytes fails to read, and one with more is read from its first 2,502 bytes.
If a read fails or any check differs, it throws the file away: it starts with no saved games, makes a
new set of high scores and the ship editor’s defaults, and writes a new file at the next save. So an edited file
must have its checksums put right. The real file here gives
CB 35 65 25, which match.
A save slot
Each of the 22 slots is 109 bytes, in this order (offsets from the start of the slot; the slot n starts at
(n − 1) × 109).
| Offset | Size | Name | Description |
|---|---|---|---|
0x00 | 2 | Encode | Written as 0 and never read for anything: OpenTyrian reads it into its save table and writes it back unchanged, nothing else uses it, and the game never sets it (config.c is the only file that mentions it). It is 0 in all five real files examined, in every slot. |
0x02 | 2 | Level | The level the game goes on from, as a number in the episode’s level file. 0 means the slot is empty. |
0x04 | 12 | Items | The player’s ship and equipment now: see Items. |
0x10 | 4 | Score | Player 1’s cash, which is also the score of the game. |
0x14 | 4 | Score 2 | Player 2’s cash (0 in a one-player game). |
0x18 | 1 | Level name length | How many of the next 9 characters are used (at most 9). |
0x19 | 9 | Level name | The name of the level the game goes on from, such as SAVARA V; Completed when the player has finished the episode (the game then goes on in the next one). |
0x22 | 14 | Save name | What the player called the save, padded with spaces. The autosaves are LAST LEVEL. |
0x30 | 1 | Data cubes | How many data cubes were found in the episode (0 to 4). |
0x31 | 2 | Weapon power | The power level (1 to 11) of the front weapon, then of the rear weapon. In a two-player save the first is player 1’s front gun and the second is player 2’s rear gun. |
0x33 | 1 | Episode | The episode, 1 to 4 (and 5 for the bonus “Episode ?”). When the level name is Completed the game saves the episode just finished and, on loading, moves to the next, going from 4 back to 1. |
0x34 | 12 | Items at the start of the level | One-player: the items the player had when the level began, which the game restores if the level is replayed. Two-player: the items of player 2. |
0x40 | 1 | Difficulty | The difficulty being played (see Difficulty). |
0x41 | 1 | Secret hint | 1, 2 or 3 (0 in an empty slot): which of three secret hints this game gets. The game picks it at random when a new game starts (secretHint = rand mod 3 + 1) and saves and restores it. At the end of an episode (the level script command Q) the game shows one hint line, chosen as secretHint + 3 × (random 0 to 2) counting from the start of the hint lines of the episode’s text, so the value fixes which third of the hints appears. Values 1 to 3 are seen in the real files. |
0x42 | 1 | Player 1 input | 0 any device, 1 keyboard, 2 mouse, 3 and up joysticks. |
0x43 | 1 | Player 2 input | The same for player 2. |
0x44 | 1 | Game has repeated | 1 once the game has gone round from the last episode back to episode 1; such a game cannot enter the high scores. |
0x45 | 1 | Initial difficulty | The difficulty the game was started at (the game keeps it for its high-score rules). |
0x46 | 4 | High score | A signed 32-bit score: this slot’s entry in the high-score tables (High scores). |
0x4A | 4 | Second high score | A signed 32-bit score, marked unused in OpenTyrian’s source. A new file gives it a value for the team entries and 0 for the others. |
0x4E | 1 | High score name length | How many of the next 29 characters are used. |
0x4F | 29 | High score name | The name of the player, or of the team in a two-player entry. |
0x6C | 1 | High score difficulty | The rank shown beside the score (see Difficulty). |
The two names are a Pascal string (a length byte, then the characters) for the level name and the high-score name, and a fixed run of characters padded with spaces for the save name. Bytes after the length in a Pascal string are not cleared, so they can hold leftovers.
Items
The twelve item bytes, at 0x04 and again at 0x34, are the player’s ship and equipment. Each is
a number that counts from 1 into one of the game’s tables (0 is none); the names are in
Item numbers.
| Byte | Name | Table or meaning |
|---|---|---|
| 0 | Front weapon | Weapon ports (1 to 42) |
| 1 | Rear weapon | Weapon ports |
| 2 | Super Arcade mode | 0 in a normal game; 1 to 7 the Super Arcade ship (1 Ninja Star, 2 StormWind, 3 The Experimental PQZ, 4 Captured U-Fighter, 5 FoodShip Nine, 6 TX SilverCloud, 7 Nort-Ship Z); 254 Super Tyrian, the secret game; 255 a one-player arcade game |
| 3 | Left sidekick | Sidekicks (1 to 30) |
| 4 | Right sidekick | Sidekicks |
| 5 | Generator | Generators (1 to 6) |
| 6 | Sidekick level | OpenTyrian’s comment says “Dragonwing only” (the second player’s ship). It counts 101, 102 and 103 for the steps of a series of sidekick upgrades. When a file is loaded and the second player’s value is below 101, the game sets it to 101 and sets the series to that player’s left sidekick. |
| 7 | Sidekick series | The sidekick that began the series (a sidekick number) |
| 8 | First episode | The episode the game was begun in |
| 9 | Shield | Shields (1 to 10) |
| 10 | Special weapon | Special weapons (1 to 46) |
| 11 | Ship | Ships (1 to 13) |
Armour, shield power and generator power are not stored: they follow from the ship, shield and generator numbers. The power
levels of the two weapons are the two bytes at 0x31; a sidekick’s ammunition is not stored.
A two-player save keeps player 1 in the first twelve bytes and player 2 in the second twelve; the front weapon power of player 1 and
the rear weapon power of player 2 are the two bytes at 0x31.
Difficulty
The difficulty bytes use OpenTyrian’s numbering:
| Value | Name | Value | Name |
|---|---|---|---|
| 0 | Wimp | 6 | Suicide |
| 1 | Easy | 7 | Maniacal |
| 2 | Normal | 8 | Zinglon (the menu’s Lord of the Game) |
| 3 | Hard | 9 | Nortaneous |
| 4 | Impossible | 10 | Unnamed (difficulty 10) |
| 5 | Insanity |
The menu offers Easy, Normal, Hard, Impossible, Suicide and Lord of the Game (1, 2, 3, 4, 6 and 8); the others are reached by the
game raising the difficulty as it is played. The high-score byte at 0x6C is looked up in a different list in the game’s
text, which begins Unranked, Wimp, Easy, Normal, Hard, Impossible, Insanity, Suicide, Maniacal, Zinglon, Nortaneous, with the
value limited to 10; read as an index it names each level one place later than the table above. The source states both; the explorer
shows the high-score byte with the high-score list’s names.
High scores
Every slot holds one high-score entry (score, name, rank), whether or not a game is saved in it. The tables are made from the slots in order: for each of the first three episodes there are six slots, three for one player (the best three) and three for two players:
| Slots | Table |
|---|---|
| 1, 2, 3 | Episode 1, one player, ranks 1 to 3 |
| 4, 5, 6 | Episode 1, two players |
| 7, 8, 9 | Episode 2, one player |
| 10, 11, 12 | Episode 2, two players |
| 13, 14, 15 | Episode 3, one player |
| 16, 17, 18 | Episode 3, two players |
| 19 to 22 | No table: the game never enters a score here, never sorts these slots and does not list them; they keep what the file was made with |
This is why the slots and the high scores are in one file and why a slot that holds a saved game also holds a high score. A new entry shifts the lower
scores and names of its table down one place (the rank byte is not moved with them), and the game sorts the six tables of three, moving score,
name and rank. The rank saved with a new entry is the difficulty being played at the end, not the initial one. A one-player score includes
the cost of the player’s upgrades, not only the cash. The game offers an entry only when the episode just finished is the one the game was begun in (item byte 8), the game
has not repeated (byte 0x44), the episode is not the fourth, and it is not a network or constant-play game.
A new file’s entries are made up: each score is a random number from 1 to 20 thousand, and the names are drawn from fixed lists (34 names of characters, from The Prime Chair and Transon Lohk to Rennis the Rat Guard, for the one-player entries, and 22 team names, from Jackrabbits and Team Tyrian to Carlos’ Crawlers, for the two-player ones). So a file that has never had a score entered shows these in the tables: the real file here has entries such as “Rennis the Rat Guard” 19,000 and “Eva Earlong” 17,000.
The ship editor block
The last 100 bytes before the checksums (0x095E) are data of the ship editor (SHIPEDIT) that the game shares: 98 availability flags, one byte
per position, then a 16-bit number stored high byte first, the editor level, which starts at 800 and has a largest
value of 1,680. OpenTyrian’s comments number the flags from 1 and give the positions 1 to 38 as weapons, 51 to 68 as sidekicks and 81 to 93
as special weapons, with a new file allowing a few of each; the explorer lists the flagged items. The comment also says the editor level divides into armour (by 60,
1 to 28), shield (by 168, 1 to 12) and engine (by 280, 1 to 6). The real file has the editor level 892.
A real file
The TYRIAN.SAV found here is a 2.1 / Tyrian 2000 file with two games in it, both written by the game as autosaves, and 20 empty slots whose high-score entries are the random ones above.
| Field | Slot 11 (one player) | Slot 22 (two players) |
|---|---|---|
| Save name | LAST LEVEL | LAST LEVEL |
| Level | 1, NEW GAME | 35, SAVARA V |
| Episode | 2 (Treachery) | 1 (Escape) |
| Score | 15,000 and 0 | 39,410 and 80,425 |
| Difficulty (initial) | Hard (Hard) | Insanity (Normal) |
| Front, rear weapon | Pulse-Cannon, none; power 1 and 1 | Guided Bombs, none; power 3 and 8 |
| Generator, shield | Advanced MR-12, Gencore High Energy Shield | Advanced MR-12, Gencore High Energy Shield |
| Special, ship | none, USP Talon | 8-Way Microbomb, Silver Ship |
| Items of player 2 | (the items at the start of the level) | USP Talon; Pulse-Cannon and Wild Ball; sidekicks Companion Ship Warfly and Beno Protron System -B-; sidekick level 103 of series 7 (Atom Bombs) |
| Data cubes | 3 | 0 |
Versions
OpenTyrian, which reads the DOS game’s files, expects exactly this layout, and the file here was written by the DOS game (its file date is 2001, with the Tyrian 2.1 data beside it) and passes all four checksums under it, so the DOS game of that release and
OpenTyrian share the file. Tyrian 2000 adds the fourth episode, whose item data is in TYRIAN4.LVL instead of TYRIAN.HDT; the item tables of the two on this machine are identical, so the names are the same for all four episodes.
Other releases, checked in the archive.org dumps (Tyrian 1.0, 1.1, 2.0 and 2.1 shareware, and the 2.1 freeware package from camanis.net, whose TYRIAN.HDT is byte for byte the one here): the program FILE0001.EXE of every one of them contains the 10-byte scrambling key
0F 32 59 F0 93 22 56 09 20 D0 and the size constants 2,502, 2,498 and 2,398 (0x09C6, 0x09C2, 0x095E), and the 2.1 package’s own TYRIAN.SAV (2,502 bytes, checksums right) and TYRIAN.CFG read as described. The 1.0, 1.1 and 2.0 dumps hold no saved file to read.
Two saved games from a Tyrian 2000 player (archive.org item tyrian2klinux, May 2026) are 4,722 bytes: a normal 2,502-byte save with right checksums, then 2,220 unscrambled bytes that begin with 30 records of 35 bytes (a 32-bit score, a name with its length byte, one more byte; names such as “The Prime Chair”, “Shadow”, “Baker Bob”) like further high-score entries, then leftovers of older data and zero bytes. The game reads only the first 2,502 bytes, so such a file loads; the explorer shows the rest as bytes. What writes the extra bytes is not known (OpenTyrian writes 2,502).
Telling it apart
.sav is one of the most common extensions. Of 100,000 random files of 48 to 6,000 bytes named .sav, none was taken for a Tyrian save. A Tyrian file is 2,502 bytes (or longer, up to 1 MiB, the game ignoring the rest) and, once unscrambled, its first 2,502 bytes pass
all four checksums (a chance of 1 in 4 billion for a file of another kind). The explorer also accepts a file of that size
whose save names all read as text. A damaged file is also accepted and shown with the problem marked: one with a wrong checksum or an edited byte, one with bytes
added (the game reads the first 2,502 and ignores the rest, so it accepts such a file), and one cut short (down to the first save name, 48 bytes), read as far as it goes. Of the 1,590 other .sav and .cfg files found on this machine none is taken for Tyrian’s. A text file named
.sav, or any file whose save names do not read as text, is reported as not a Tyrian file.
Not described
Every field of the 2,502 bytes is named and decoded, and the format was checked against one real file and against files built the way OpenTyrian writes them. What follows is what that does not cover.
Known, not yet covered
- Viewer: the ship is not drawn. Its picture is in the game’s shape files (
NEWSH*.SHP,TYRIAN.SHP), which a saved game does not contain, so the viewer shows the ship, its weapons and the rest by name only. - Viewer: the level is shown by its saved name and number; the name is not looked up from the level number, because the list of names is in the game’s
LEVELS?.DATfiles. - Reference: the sidekick table’s ammunition, costs and the other columns of the item table (damage, cost, graphics) are not given: only the names. The columns are in OpenTyrian’s documentation of
TYRIAN.HDT. - Reference: OpenTyrian’s own
opentyrian.cfg(a text settings file) and the demo and network files.
Unknown
- Reference: no saved game written by Tyrian 1.0, 1.1 or 2.0 has been found: the archive.org dumps of those releases are fresh installs with no
TYRIAN.SAVorTYRIAN.CFG. What is known (see Versions) is that all four releases’ programs carry the same scrambling key and the same size constants, so the layout is very probably the same, but that is not confirmed by a file. - Reference: the Android and iOS ports. The Android OpenTyrian port is a build of OpenTyrian’s code (it is listed among the SDL ports) and so uses the same file names and layout; no port’s saved file was obtained, and no separate save format for a port was found.
- Reference: what the 2,220 bytes after the saves in the two known 4,722-byte Tyrian 2000 files are (see Versions), and how the original game fills the fourth episode’s high-score slots.
- Reference: whether the DOS game accepts everything the file allows (an item number outside its table, a level name of 9 characters with a length above 9): only OpenTyrian’s code was read, not the DOS program.
The names that come from Tyrian’s own data file TYRIAN.HDT are not in this file and are not repeated as unknown: the Super Arcade ships 1 to 7
(the values 0, 7, 254 and 255 are OpenTyrian’s definitions in varz.h), the episode names including “Episode ?: Bonus” for 5, the 11 rank names of the high-score tables (the count of 11 and the clamp to 10 are in
OpenTyrian’s code) and the item names. What decides their meaning is that data file, and the viewer shows the raw number beside the name and says where the name comes from: in the note of each item and episode field, in the heading of the Ship, Difficulty and item columns, and in a “Names” line on the file, slot-details and high-score tabs. Reference and viewer stay at partial until an independent check says otherwise.
Links
Software
- Tyrian and Tyrian 2000 – Epic MegaGames’ game for MS-DOS (1995), which writes these files
- OpenTyrian – the open-source port (GPL), which reads and writes the same files
Reference
- OpenTyrian src/config.c – loadSaves, saveSaves, the scrambling and the four checksums, JE_saveGame and JE_loadGame
- OpenTyrian doc/tyrian.hdt.txt – the layout of the item table the names come from
- Tyrian credits – the other scrambled file, on this site