< Back to DIY

Hex editing for DOS games

Introductory disclaimer

Before editing any game files, make a backup. Making changes in the wrong place may make in unrunnable. Proceed at your own risk.

Hex editing in the described form worked well in many cases for old games. I don't know how's the situation today. I suppose modern games take steps to obfuscate both savegames and data files to make hex editing harder.

How it began

A good childhood friend got his hands on Threat, a DOS game from 1995 by Finnish team Fragment. A rather obscure one, but it was quite fun, because it has 3-player coop hotseat!

It's a top-down shooter, all the action taking place in a single screen, no scrolling. One game mechanic that made it interesting is line of sight. You can't see enemies that aren't in direct sight of one of the players, or far away. There's a shop between levels where you reload ammo and buy new weapons. There are 12 weapons and a first aid kit, but only three inventory slots. The default weapon is quite usable, with a decent amount of ammo. Probably the most useful are grenades, which you can throw over walls, making for some good tactical plays. But it only has four uses. A prime goal for improvement.

Back then I was savegame hacking quite a few games. Nothing overly sophisticated, just finding the right place in a savegame where the amount of some resource is saved and increasing it. It was easy for SimCity 2000, only one value to find – credits. With Jagged Alliance it was a bit more work. You have multiple characters, each with several item slots. Each slot has three pieces of information: item, amount and quality. So I had to map the slots and items. I should still have my notes with all this info somewhere.

I'm not sure how I picked up savegame hacking. I think it was through a gaming magazine's cheats section. Many games had cheat codes built in, others needed l33t HaXXorZ magic in the form of savegame editing with hex codes!!!

Threat does have two cheat codes, plainly mentioned in the in-game instructions. I only found out while writing this. Of course I never read the instructions until today. It says to start with the parameter WARP to skip a level, or with BOOST to get an energy boost. But this is not enough! We need hex codes!

My father's computer had PC Tools installed, which had a hex editor built-in. This made it relatively easy to get started. I vaguely remember that a hex cheat in a magazine had explained briefly how it works with dec-to-hex conversion – that we have to look for the hexadecimal representation of a decimal number, like the amount of cash in a savegame.

Threat has savegames, so it should be possible to get more cash for the shop or give 999 ammo. Cash is plenty, especially in single player. Giving more ammo before a level would last until the end of the next level or two. Reloading in the shop would still max out at the default. This means the default ammo amount must go up. We can also alter the prices in the shop.

How to do it ... again

Getting and running the game

First we need the game. It's shareware (first episode free, as it was common then), so it's easy to find it online, such as on the Internet Archive.

Second, you might want to try playing it vanilla first, before going idkfa on it. You'll need DOSBox. With the default display settings the DOSBox window was too small for my comfort, and Threat's aspect ratio was off. I followed these tips. For the resolution I set windowresolution=1024x768, but any 4:3 ratio should do.

Hex editor

Once you want to go commando, grab a hex editor. I use HxD. It's portable, just extract and run.

What to look for

Before we put the hex editor to use, we have to know what to look for.

Let's first make a list of relevant data from the game's shop and convert the values to hexadecimal. We'll be looking for these hex values.

Item       | Price Hex  | Ammo  Hex
-----------|------------|----------
Machinegun |  75    4B  |  300  12C
Trigun     | 125    7D  |  300  12C
Flamer     |  90    5A  |  150   96
Flamebomb  |  75    4B  |   10   0A
DblElectro | 150    96  |  200   C8
Destroyer  | 200    C8  |   20   14
Guided M   | 210    D2  |   50   32
Grenade    |  50    32  |    4   04
Mine       |  50    32  |   50   32
First aid  | 100    64  |    3   03
Fatal HIT! | 100    64  |    5   05
FireSweep  | 200    C8  |  200   C8
DoomsDay   | 255    FF  |   10   0A

Where to look

Now this is a bit of a lottery. First to find the correct file. I'd generally look at the main executable THREAT.EXE first. Take also note of any hints in file names. There are many .DAT files, hinting that they contain game data, but only a few have names related to items, such as LASER.DAT, BEAM.DAT, MINE.DAT. There are no laser and beam weapons in the shop, so I wouldn't expect to find here what we're looking for. Let's first open THREAT.EXE with the hex editor.

First let's look for any readable strings, in our case weapon names from the above table. Do a quick scroll through the file or go straight to string search.

Found the correct file

Bingo! Searching for one of the weapon names, say Trigun, found it towards the end of the file. We see also the other names, But wait, there are 15 items, in the game shop are only 13. It seems that two of them, Fireball and Missile were left unused.

Strings in THREAT.EXE in a hex editor

Notice the uneven "gaps" between the names, hinting that there are "slots" for other unimplemented items. These unused slots are an important hint for the next step.

Also notice that the order of the names in the EXE is different than in the shop. Another important piece of information for the next step.

Now we found the names. It looks like we could change them. I never tried. If you try it, note that it looks like the byte before each name defines the length of each string. The byte before Grenade is 07 because it has 7 characters. If you change the item name that not the same length, I'd also change the "length" byte accordingly.

Ammo

Let's first find ammo amounts. We have the hex values in the table. We can assume the order will be the same as the names. But recall that in the EXE we have a different order than in the shop. Let's rearrange the table to match the EXE, including the two unimplemented items. The order will help us confirm that we are looking at the correct sequence of bytes.

Item        | Ammo  Hex
------------|-----------
Grenade     |    4   04
Fireball    |    ?
Missile     |    ?
Flamer      |  150   96
Guided M    |   50   32
Fatal HIT!  |    5   05
Flamebomb   |   10   0A
Machinegun  |  300  12C
Mine        |   50   32
Destroyer   |   20   14
Trigun      |  300  12C
First aid   |    3   03
DblElectro  |  200   C8
FireSweep   |  200   C8
DoomsDay    |   10   0A

Strings are easy to find, numbers can be tricky to get started. I'd recommend focusing on numbers above 255, because they take two bytes, making them easier to find. 255 in hex is FF, so the last two-digit hexadecimal number. 256 in hex is 100, or to be more precise 0100 in two-byte notation. Knowing this, let's focus on the Machinegun or Trigun, which both have 300 ammo, or 012C in hex. This also tells us that ammo is stored with two bytes. Let's assume that all the items have 2 bytes for storing the ammo amount, even if they are using just one byte.

Now there is a catch here and it has to do with endianness. The value 012C that we want to find is actually stored in a swapped byte order, that is as 2C01. How's that? Split after every second character (01 2C) then reverse the order of the pairs (2C 01). This is because x86 architecture is little-endian. Consequently, also any edits we make have to be in "reverse" order. For instance, if we wanted to set the amount to 256 (0100 in hex), we have to enter 0001.

We'll be searching for 2C01. In the version I have there are 5 hits in the EXE. Two are towards the end, a bit after the item names. These last two hits are only 4 bytes apart. Remember, we are assuming that each item's ammo uses two bytes. This means there are two other items in between – and our table confirms that! Also the number match up.

Side note: In HxD, I like to set Byte Group Size to 2 (in View menu). This way it easier to see each item's two bytes as one unit or "item slot".

We have this sequence: 2C01 3200 1400 2C01. It exactly matches the numbers for Machinegun, Mine, Destroyer, Trigun. It looks like we're in the right place. Let's verify by changing one of the values and checking in-game. For instance, change the first instance of 2C01 to 2D01. This should give the Machinegun 301 ammo. Make a copy of THREAT.EXE before you make the change, then change this one byte, save, run the game and check the shop if we got it right.

Shop showing Machinegun with 301 ammo

Nice! We're in the right place. Now to map the rest. Let's look for 04 (Grenade ammo) nearby. There is the group 0400 28 bytes (or 14 "item slots") back. This is the start of the first item, so the rest should be down from here. The next item in the shop, Flamer, is 5 slots (10 bytes) down with 9600. There are 4 unused slots in between. Two were probably intended for Fireball and Missile, two remain unnamed.

Ammo amounts in THREAT.EXE in a hex editor

Let's continue mapping until we get to DoomsDay's 0A00, a whoppin' 22 slots down from Grenade. This is hinting that the developers wanted to include up to 23 items in the shop.

You'll notice that there are three 0A00 in a row. One of them should belong to Flamebomb, but which? Let's again look at the section with item names. Fatal HIT! and Flamebomb are direct neighbors, no gap in between. It's then relatively safe to assume that the first ammo slot after Fatal HIT! 0500 will be the slot for Flamebomb.

The final mapping of items' ammo looks like this: (I didn't add the exact offset, because it varies between versions of THREAT.EXE.)

Item         | Ammo   Hex 
             |        (byte swap'd)
-------------|----------------------
Grenade      |    4   0400
 (unused)    |        (0100)
 (Fireball)* |        (0100)
 (Missile)*  |        (3200)
 (unused)    |        (0500)
Flamer       |   150  9600
 (unused)    |        (0500)
Guided M     |    50  3200
 (unused)    |        (0100)
Fatal HIT!   |     5  0500    
Flamebomb    |    10  0A00
 (unused)    |        (0A00)
 (unused)    |        (0A00)
 (unused)    |        (0100)
Machinegun   |   300  2C01
Mine         |    50  3200
Destroyer    |    20  1400
Trigun       |   300  2C01
First aid    |     3  0300
 (unused)    |        (1E00)
DblElectro   |   200  C800
FireSweep    |   200  C800
DoomsDay     |    10  0A00

* Location of Fireball and Missile presumed based on gaps between names. Not that it matters ...

The final step is to update the amounts. The maximum the display can handle is three digits, so 999. Which is 3e7 in hex. First add leading zeros to get two bytes: 03E7. Then swap the bytes: E703. Replace all the relevant slots with this or any other amount you consider fair against the alien threat.

More than 999 seems to work too. I tried setting the maximum that fits into two bytes FFFF (65535) for grenades. The game didn't crash in the shop or in the first level, so it will probably be fine later on. The only downside is that longer values cause display issues. The fourth and fifth digit overflow the intended box and are not cleared. But who cares if you don't need to reload, ever.

Shop prices

If ammo is not enough cheating, you can also change item prices in the shop. The process is very similar as with ammo.

We need a good number, something distinctive. DoomsDay's prices of 255 (FF) might seem a good one to start. But you'll soon notice that FF is very frequent. The developers' choice of 255 as the price for the game's BFG is again a hint. It's likely that only one byte is allocated for price, as opposed to two bytes for ammo. This means we have to look at single bytes. Now is a good time to change the byte group size in the hex editor to 1.

Let's first update the full table (with unused items) with prices and their hex values.

Item       | Price  Hex 
-----------|-----------
Grenade    |    50  32
 (unused)  |        
 *Fireball |        
 *Missile  |        
 (unused)  |        
Flamer     |    90  5A
 (unused)  |        
Guided M   |   210  D2
 (unused)  |        
Fatal HIT! |   100  64
Flamebomb  |    75  4B
 (unused)  |        
 (unused)  |        
 (unused)  |        
Machinegun |    75  4B
Mine       |    50  32
Destroyer  |   200  C8
Trigun     |   125  7D
First aid  |   100  64
 (unused)  |        
DblElectro |   150  96
FireSweep  |   200  C8
DoomsDay   |   255  FF

There are two approaches you can take here to find the correct place:

Prices in THREAT.EXE in a hex editor

Similarly like we did for ammo, let's look for the first item's price (32 for Grenade) from this starting point. Then we only have to map all the known prices and mind the gaps.

Here the full table with ammo and prices, including values for the unused items. Now you can go full idkfa.

Item         | Price  Hex | Ammo   Hex
             |            |        (byte swap'd)
-------------| ---------- |---------------------
Grenade      |    50  32  |    4   0400
 (unused)    |        01  |        (0100)
 (Fireball)  |        02  |        (0100)
 (Missile)   |        03  |        (3200)
 (unused)    |        04  |        (0500)
Flamer       |    90  5A  |   150  9600
 (unused)    |        06  |        (0500)
Guided M     |   210  D2  |    50  3200
 (unused)    |        08  |        (0100)
Fatal HIT!   |   100  64  |     5  0500
Flamebomb    |    75  4b  |    10  0A00
 (unused)    |        32  |        (0A00)
 (unused)    |        0C  |        (0A00)
 (unused)    |        0D  |        (0100)
Machinegun   |    75  4B  |   300  2C01
Mine         |    50  32  |    50  3200
Destroyer    |   200  C8  |    20  1400
Trigun       |   125  7D  |   300  2C01
First aid    |   100  64  |     3  0300
 (unused)    |        13  |        (1E00)
DblElectro   |   150  96  |   200  C800
FireSweep    |   200  C8  |   200  C800
DoomsDay     |   255  FF  |    10  0A00