Difference between revisions of "Variables"
(→Multiple-Byte, Decimal Values)
(→Multiple-Byte, Decimal Values)
|Line 139:||Line 139:|
03 57 00
03 57 00
Revision as of 17:52, 28 December 2018
This guide is to make you better acquainted with the way various games store variables in the NES.
Remember that most games store all of their memory in the basic RAM of the NES, which is located from 0x0000 to 0x07FF.
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 the CPU handles values, so it is the easiest way, and by far the most commonly used in games. No special code is needed to perform math on variable stored in this way. Each byte can hold 256 discrete values, and they are displayed in a hex editor as 0x00-0xFF.
In The Legend of Zelda, Link's horizontal position on the screen is stored as a single byte hex value in memory address 0x0070. 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 0x066D) 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 "0A" (the hex value for 10), and you'll have 10 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 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 0x0399 and P2 is at address 0x039E. When a player scores 14 points, the value won't read 0x0E (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 0x63, 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 the remaining 6 bits to store the value.
+--- The sign bit. | v 0b00000000 \ / --+-- | +--- The value portion.
When the sign bit is cleared (0), the number is positive, when it's set (1), the number 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 0x00 stored in it, and you subtract 0x01 from it, the CPU automatically wraps the value back around to 0xFF, which is -1 in decimal. Likewise, if you have 0xFF stored in a memory address (-1 in decimal), and you add 0x01 to is, the CPU wraps it to 0x00, which is 0 in decimal. Each time the value rolls like this, the Negative Flag in the Processor Status Register flips. 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 0x0057. 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, 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, 0x00BC and 0x00BD. 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 0xC8 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:
0x2C = 44 0x01 = 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 0x0BC-0x0BD, 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 = 0x4E 32 = 0x20
Thus, by typing "20 4E" into the memory addresses 0x0BC-0x0BD, 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 0x0144-0x0146).
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 0x0775-0x0776, 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 0x00B7 for ×1 and 0x00B9 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 0x07F8-0x7FA. 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 0x55B-0x55D), 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 0x00C4-0x00C6. 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 (+0x01) ||||||+- Bit 1 (+0x02) |||||+- Bit 2 (+0x04) ||||+- Bit 3 (+0x08) |||+- Bit 4 (+0x10) ||+- Bit 5 (+0x20) |+- Bit 6 (+0x40) +- Bit 7 (+0x80)
The game Metroid uses a bit flag to store the player's items in memory address 0x6878. 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 0x57, so, going to memory address 0x6878 and typing "57" will give the player all these items.