This guide is to make you better acquainted with the way various video games store variables. Since this guide jumps between hex and decimal values frequently, all hex values will be prefixed with a dollar sign ($).
Remember that most games store all of their memory in RAM which is different for every platform.
The following variable types store a single value.
Single-Byte, Hex Value
Variables of this type are stored as a hex value in a single byte of memory. This is the native way any 8-bit CPU handles values, so it is the easiest way, and by far the most commonly used in older games. No special code is needed to perform math on variables stored in this way. Each byte can hold 256 discrete values, and they are displayed in a hex editor as $00-$FF.
In The Legend of Zelda, Link's horizontal position on the screen is stored as a single byte hex value in memory address $0070. If you load up the game and move Link horizontally across the screen, you'll see this number grow and shrink as he moves.
As long as the value never needs to be displayed to the player, this is the best way to handle such values. However, if the player needs to see the value in number form, it must first be converted to decimal, which takes extra CPU time. A good example of this in action is Link's rupees in The Legend of Zelda. They are stored in memory as a hex value, but they are displayed to the player in decimal form. If you load The Legend of Zelda and start a new game, you can adjust the number of rupees (memory address $066D) using a hex editor and see the value update immediately on the screen. For example, type "01" into the address, and you'll suddenly have 1 rupee. Type "0D" (the hex value for 13), and you'll have 13 rupees. Type "FF" (the hex value for 255) and you'll have a full compliment of 255 rupees. The game does this by running a conversion from hex to decimal every time the value updates.
Single-Byte, Decimal Value
Variables of this type are stored as a decimal value in a single byte of memory. Because the NES CPU doesn't have native decimal mode, programmers must write a special routine to perform decimal math on these values every time they add or subtract on them. Because of this extra overhead, programmers usually only use this type of variable for values that will be seen by the player.
For example, the player's scores in Tecmo Super Bowl are stored as decimal values in memory. P1 is at address $0399 and P2 is at address $039E. When a player scores 14 points, the value won't read $0E (the hex value for 14), but rather 14 in decimal. This way, the value won't need to be converted when it's displayed on the screen. So, if you want to have a score of 99 points, you need only type "99" into a hex editor, not $63, the hex equivalent.
The programmers handled this by writing special addition routines when a player scores. Instead of simply using the native ADC opcode, which adds in hex, the game has to convert the decimal value into hex, then add the points, and then convert it back to decimal every time a score changes.
The CPU used by the NES originally had decimal mode, but Nintendo engineers removed it so it wouldn't have to pay royalties on the patent. If decimal mode remained, programmers wouldn't have had to write all the extra code to convert between hex and decimal and add and subtract decimal values.
Single-Byte, Hex Value, Signed
In computer terms, "signed," means that a variable can hold negative numbers as well as positive numbers, and "unsigned" means it can only hold positive numbers. The word "sign" refers to a "negative sign" on the number. While an unsigned byte can hold values from 0 to 255, a signed byte can hold values from -128 to 127. It does this by storing the negative sign in the 7th bit of the byte, leaving bits 6-0 to store the value.
+--- The sign bit. | v 0b00000000 \ / --+-- | +--- The value portion.
When the sign bit is cleared (0), the value is positive, when it's set (1), the value is negative. Also, when the bit is cleared, the value counts forward, just like an unsigned value, but when the bit is set, the number counts in reverse. For example:
0b00000000 = 0 0b00000001 = 1 0b00000010 = 2 0b00000011 = 3 0b00000100 = 4 ... 0b01111111 = 127 0b11111111 = -1 0b11111110 = -2 0b11111101 = -3 0b11111100 = -4 0b11111011 = -5 ... 0b10000000 = -128
This system is very useful because, when you have a memory address with $00 stored in it, and you subtract $01 from it, the CPU automatically wraps the value back around to $FF, which, when viewing the data as a signed byte, is -1. Likewise, if you have $FF stored in a memory address, and you add $01 to it, the CPU wraps it to $00. Each time the value rolls like this, the Negative Flag in the Processor Status Register is updated. Because of this, the CPU of the NES natively handles math using signed variable.
An example of a signed 1-byte hex value is the horizontal momentum of Mario in Super Mario Bros. which is stored in memory address $0057. Setting it to a positive 50 will make Mario run forward, but setting it to negative 50 will make him skid backward. You can test this by typing into a hex editor, the hex values "32" (50) and "CE" (-50).
Multiple-Byte, Hex Value
When a game needs a variable to store more than 256 values, two or more bytes must be used. But, since the CPU of the NES only supports 8-bit math, any variables that are two or more bytes must be handled manually through code. The most common way of handling this is to have each byte be a multiple of 256. Using this method, one byte can store 256 values, two bytes can store 65536 values, three bytes can store 16,777,216 values, four bytes can store 4,294,967,296, and so forth. Most games that use this method only use two bytes.
Because 16-bit (and higher) math is not a built-in operation of the CPU, every game has the option of handling it differently. Sometimes the multiple bytes go left-to-right, sometimes right-to-left, but they're almost always next to each other.
Dragon Warrior is a good example of 2-byte hex values. The player's gold pieces are stored in two addresses, $00BC and $00BD. But any value below 256 can be stored in a single byte as normal. For example, 200 GP would look like this in a hex editor:
Because $C8 is equal to 200 in decimal. But, if the player gets another 100 gold pieces, bringing the total up to 300, we have surpassed the 256 values that a single byte can hold, so the second byte must be employed. 300 GP is represented as:
The first byte is the ×1 multiplier and second is the ×256 multiplier. So, the variable's value is determined by adding together the second byte ×256 and the first byte ×1. To better illustrate this, I'll first convert the hex into decimal:
$2C = 44 $01 = 1
Perform the multiplication:
44 × 1 = 44 1 × 256 = 256
And then we add the two:
256 + 44 ---- 300
Since the player's gold pieces are displayed to the player, the game must convert the hex value into decimal before it is displayed on the screen, but the variable itself is stored in memory as hex. Because two bytes can only hold 65,536 unique values, the player will max out gold at 65,535. In fact, if you wanted the max gold, simply type "FF FF" into memory addresses $0BC-$0BD, and you will have 65,535.
The actual addition needed for 2-byte variables must be programmed custom for each game, and different routines are often created for adding a 1-byte variable to a 2-byte variable and adding together two 2-byte variables.
You can determine the necessary hex value from a decimal value by performing a similar equation. Lets say you wanted to have 20,000 GP. First, divide 20,000 by 256:
20,000 / 256 = 78.125
Next, floor the quotient. In mathematics, "floor" means to discard any decimal values without rounding. In mathematical notation, flooring is represented with these special brackets, ⌊⌋:
⌊78.125⌋ = 78
This is the number that will have to go into the second byte. If there was no decimal value, the first byte will be 0, but if you had to floor off the decimal, you'll need to do an extra step to determine the first byte. Multiply the floored value by 256. This will give you a number that is less than 256 away from your original number:
256 * 78 = 19,968
Finally, subtract your original number from that product:
20,000 - 19,968 = 32
This is the number that will go into the first byte. However, you must first convert them into hex:
78 = $4E 32 = $20
Thus, by typing "20 4E" into the memory addresses $0BC-$0BD, your player will have 20,000 GP.
By using this method, you can handle numbers as large as you like simply by the number of bytes. For example, Kid Icarus: Angel Land Story uses three bytes to store the player's score (in addresses $0144-$0146).
An example of a game which stores 2-bytes left-to-right is Zelda II - The Adventure of Link which stores Link's experience in memory addresses $0775-$0776, but as ×256 then ×1.
Gauntlet, however, uses 2-bytes which are neither left-to-right nor right-to-left, but staggered. The first player's treasure amount is stored at memory address $00B7 for ×1 and $00B9 for ×256. This occurs because the second player's treasure is between the addresses.
Multiple-Byte, Hex Value, Signed
You can also have a multiple-byte hex value that is signed by making the 7th bit of the last byte the sign bit. This results in 2-bytes being able to hold a value of -32,768 to 32,767, 3-bytes holding a value from -8,388,608 to 8,388,607, and so forth. Like all multiple-byte variables, this is not handled natively by the CPU, so every game must have it implemented custom. I am not aware of any NES games that actually uses signed multiple-bit values, but there is nothing preventing it from being possible.
Multiple-Byte, Decimal Values
If a variable must store more than 256 unique values, and those values are going to be displayed to the player, the programmer may often store the variable in decimal format across multiple bytes.
Super Mario Bros. uses one digit per byte to store the level's time in memory addresses $07F8-$7FA. So, if the player has 273 seconds left before time's up, it will be stored in memory like this:
02 07 03
As the time ticks down, the third byte will decrease from 3 to 2, 2 to 1, and 1 to 0. The next tick will decrement the second byte to 6, and set the third byte back to 9. The CPU of the NES doesn't natively handle decimal variables, so all of this must be manually coded by the programmer.
Because the implementation of multiple-byte decimal values is at the discretion of the programmer, you will find various implementations in various games. For example, Bomber Man II also uses one digit per byte to store the player's time (in memory addresses $55B-$55D), but it stores its values right-to-left. So, if the player has 273 seconds remaining, it will be stored in memory like this:
03 07 02
Another implementation of decimal values across multiple bytes is to store two digits per byte. This requires more operations to read and write, making it slower, but it only takes half the memory. For example, Duck Hunt uses two digits per byte to store the player's score in memory addresses $00C4-$00C6. So, if the score is 35,698, it will be stored in memory like this:
03 56 98
If the player were to get two more points, increasing the score to 35,700, the second digit of the third byte would increment by 2, setting it to 0 and triggering a carry of 1 into the first digits of the third byte. This would increment to 9 into a 0 and carry 1 into the second digit of the second byte, incrementing the 6 to a 7. The final result would look like this:
03 57 00
Most games store their digits left-to-right in sequential order. Right-to-left is rare, and even more rare is games that don't use a contiguous block of memory.
A bit flag variable is a way of storing multiple yes/no values into a single byte in order to save memory. It requires slightly more code to read and write the values, but it saves 7 bytes of memory compared to using a single bit for each value.
0b00000000 |||||||| |||||||+- Bit 0 (+$01) ||||||+- Bit 1 (+$02) |||||+- Bit 2 (+$04) ||||+- Bit 3 (+$08) |||+- Bit 4 (+$10) ||+- Bit 5 (+$20) |+- Bit 6 (+$40) +- Bit 7 (+$80)
The game Metroid uses a bit flag to store the player's items in memory address $6878. The items are laid out in the following way:
0b00000000 |||||||| |||||||+- Bombs ||||||+- High Jump |||||+- Long Beam ||||+- Screw Attack |||+- Maru Mari (Ball) ||+- Varia |+- Wave Beam +- Ice Beam
So, if you want to have Bombs, High Jump, Long Beam, Maru Mari, and the Wave Beam, you would create a binary flag list to match, which would look like this:
The hex value for this binary value is $57, so, going to memory address $6878 and typing "57" will give the player all these items.
The following are structures that store multiple variables.
A stack is a way of storing multiple variables in a structure that keeps a history of each variable that is added until it is removed in a last-in, first-out order. Adding a variable onto the stack is called a "push," removing a value off the stack is called a "pop," and reading the last value on the stack without removing it is called a "peek." A popular analogy to help illustrate this concept is the back button on a smartphone. Each time you move to a new program or menu, the phone pushes the previous screen onto the stack, and each time you press the back button, the phone pops the last screen off the stack and sends you back to the previous screen.
For an example of a stack that can grow to a max of 4 bytes, imagine a game with a menu system that is several menus deep. Each new menu has a number and a back button. The users begins on menu number 1, which has two other menus numbered 2 and 3. The user selects menu item 3, so the current menu, 1, is pushed onto the stack which could be seen in memory as:
00 00 00 01
The used size of the stack grows from 0 to 1, and somewhere in memory the size must also be maintained. Menu 3 has two other menus on it numbered 8 and 9. The user selects menu 8, and the current menu, 3, is added to the stack and the stack size grows to 2:
00 00 03 01
Notice that this stack accumulates in reverse. It doesn't have to, but this is the most common way to handle a stack. In menu 8, there are three more options, 15, 16, and 17. The user chooses 17, so the current menu, 8, is pushed onto the stack and the stack grows to 3:
00 08 03 01
Now, the user realizes that he has made a mistake, and selects the back button. Because the program has kept a history of their menu selections in the stack, the program need only pop the stack based on how big it has grown. Since the stack is now 3 deep the program reads the 3rd byte from the end, which is 8, and loads that menu. It then replaces the 3rd byte with 0, and decrements the stack size to 2.
Stacks are extremely useful in computing for allowing a program to call subroutines and know where to return when the subroutine finishes. The CPU of the NES has a built-in stack which begins at memory address $01FF and runs backward up to $0100. Anytime a game executes a Jump to Location Save Return Address opcode, the 16-bit Program Counter Register is pushed onto the stack, and the Stack Pointer Register is increased by 2. When the game executes a Return opcode, the last 16-bits of the stack are popped into the program counter register. There are a couple other opcodes which can push values onto the stack and pop them off.
Because stacks have to grow dynamically in memory, you must make sure that they never expand beyond the amount of memory you've allocated for them or they will overwrite memory. It's also important to always pop values off the stack in the opposite order that they're added, and to always pop a value off the stack when you're finished with it.