N64 ASM (r4300i) Hacking
by Viper187


Table Of Contents
Introduction
Assumptions
Basic Info
Setting Breakpoints
Regs & Opcodes
Using Niew
Using LemAsm
Getting Started
Working With ASM OPs
Hacking Various Types Of ASM Codes
Writing Routines, Jumping In And Out, Etc
Using The COP1 Registers And Instructions
An In-Depth Look At The Translation Look-Aside Buffer (TLB)


Introduction
This guide was written to teach you some basic and intermediate ASM hacking. This is only a taste of what you can do. ASM, which is short for Assembly Language, is a low-level programming language. The standard codes we all know how to hack are merely RAM addresses that hold a value. You'll soon see that you can do more than just freeze those values.

It may seem like there's a lot of information here. I try to make it as easy to understand as I can, but I, like Parasyte have been doing this stuff for so long that it becomes a little hard to write for total beginners. It should be evident that this isn't necessarily a beginners kind of hacking in the first place. Don't be overwhelmed; learn this at your own pace.

The best piece of advice I can probably give you is to get to try setting Breakpoints, and look at the ASM before you just NOP something for Infinite Somethin. Get a feel for how it actually works and what you are doing by NOPing it. See what other ways you could go about getting the same effect.

You can also take apart existing ASM codes that were hacked by people like Parasyte and myself. By dumping the RAM of a game, opening it in Niew, and setting the values of some ASM codes that are laying around, you can see what they change and what that does.

If there's something in here you still don't get, or are having trouble getting the effect you want, just can't think of how you'd even begin to do a particular code (i.e. Gravity), etc. you can post on the messageboards here or email me. One of the reasons I say this is that not all games are the same, different programmers do things different ways. It's more a matter of learning the basic coding idea behind what is being done, so you'll be able to hack the same effects for different games.

All of my ASM knowledge came from Parasyte, The Phantom, CodeMaster, and just playing around with it. I'd like to thank Parasyte in particular for putting up with all the questions I'm always asking him. :-)


Assumptions
  • You have GSCC2k2 installed on your computer and you know how to use it with a Cheat Device (v3.2 or Higher), OR you're somewhat familar with Nemu 64 0.8 if you want to go that route.
  • Obviously, if using GSCC2k2, you need to have a DB25 male to female parallel cable to hook the Cheat Device to your PC as well.
  • You should have at least some knowledge of Hex, and a little Memory Editor experience wouldn't hurt.
  • To really work with N64 ASM, you'll also need Niew, N64 ASM (R4300i) opcode documentation, and LemAsm.


Basic Info
How does this "ASM" hacking work?
You can't just search for ASM codes like normal codes, per se. For most applications, with the exception of enablers and possibly other special circumstances, you need a normal code to hack an ASM code. What we do is set a Breakpoint on a code (address) to find the ASM routine that reads/writes that address.
What are Breakpoints?
Breakpoints are used to find the ASM that reads/writes to a specific address. When you set a BP, you're causing the debugger to watch the address that you're setting the BP on. When the address is read/written to, the debugger breaks (halts).
What good are they?
You can do almost anything with breakpoints. Some of the more pratical uses are:
  • Getting more of something/causing double damage
  • Mega Jump
  • Hit Anywhere
  • Walk Through Walls
  • Killing timers
  • Infinite Health, ammo, etc
  • Gain ammo when shooting
Are there different types of Breakpoints?
Yes, there are 3 types:
  • Read (BPR) - This occurs when ASM reads the value of the address the debugger is watching. ASM will almost always read the value of your ammo and such in order to reduce it when you shoot.
  • Write (BPW) - This is the most common. This occurs when ASM writes a value to the address the debugger is watching. We'll be using this primarily.
  • Execute (BPX) - This one is a little different, and we don't use it that often. In this case, the debugger is watching a part of the ASM to see when it's executed. This is usually used to find more advanced codes.


Setting Breakpoints
Ok, before you can hack an ASM code, you need to know how to set a BP. If you're using GSCC2k2:
  • Open GSCC2k2
  • Go to the RAM Editor
  • Click "Set BP"
  • In the box that pops up, put in the address to BP on, e.g. 800ACE01. You only need to enter the address, not the value. Also note, the addres you enter must begin with '80' and not '81', '88', etc. Just change the address to '80' when you type it in.
  • Choose the type of BP. In most cases, you'll only want "Write" checked, but there are cases when you'll use "Read" or both.
  • Decide if you want the game to auto resume after the BP occurs. Usually, you can go ahead and auto resume, but sometimes you'll want to leave it so you can resume manually with Shift + F9. Also note that some games won't resume at all. You should be used to this. It happens when hacking normal codes too.
  • Now when you click "Set BP" on this pop up, it goes away and you get a new pop up. It asks you to click Yes when the game halts.
  • Next, cause the address you set your BP on to be read/written to (e.g. for ammo, you'd shoot a gun). The game should freeze and you can click Yes now.
  • When you click Yes, a Notepad window will open. This gives you valuable info about the asm that's reading/writing that address.

If using Nemu64 0.8:
  • Open the Memory editor (Plugins > Memory)
  • Go to the address you want to BP on
  • Decide if you want BPR or BPW ("Watch Type")
  • Rightclick the value of the address you want a BP on. It will be highlighted a different color depending on the Watch Type. You'll notice that it's going to watch all 32bits.
  • Next, cause the address to set the BP on to change (e.g. for ammo, you'd shoot). The game should freeze and the Commands window with come up (if it's not already open). The highlighted address is the ASM opcode that wrote to the address you're watching. open Registers (Plugins > Registers) to see all the values of the General Purpose regs (like the Notepad in GSCC2k2 shows you). If you've done this on GSCC2k2 before, you'll notice something cool about Nemu's Registers window... It has the COP1 regs! We'll get into those a little later.
Setting Breakpoints On Execute (BPX)
Nemu64 0.8: It's easy to set a BPX on Nemu. Just click beside the address of the opcode you want to BPX on in the Commands window, and a checkmark will appear beside it. BPX set :)

GSCC2k2: Now GSCC2k2, on the other hand, doesn't have a BPX option. But, I found my own way to do it. Originally, Parasyte/CodeMaster came of with a long routine that did it. My way is easier than that, but requires a little opcode knowledge. Take the address you want a BPX and change it and the opcode that follows it to:
LUI $K1,8000
SW $K1,0060($K1)

Now set a BPW on 80000060 and you have your BPX. What those 2 opcodes do is set $K1 to 80000000, then store $K1 to 80000060 just to cause your BPW to occur. $K1 isn't used by the game after boot up, so we don't have to worry about changing it.
What if the game halts when it shouldn't with GSCC2k2?
There are some cases where the BP will occur prematurely. If this happens, look at the Notepad window and get the value of "pc", it'll be the address of the ASM that read/wrote (e.g. 80021530). Back in GSCC2k2, you'll notice that your position in the ram editor has changed to the address of the ASM. Click in there on the address of the ASM (80021530 in this case) and change it to 2400 (Yes, that's 16-bit). Set your BP again (same address as the first time) and get the game to halt again. If it's premature, repeat the previous steps till you get it to break at the right time. Note that this is where turning auto resume off comes in handy. You can leave the game halted, set the premature BP to 2400, then when you set your BP again to try for the right one, it will resume and wait for the next break.
What if the game halts when it shouldn't with Nemu64?
As I mentioned above, there are cases where there is a constant read/write on the address you're working with. With Nemu, you can try to just resume ("GO") from the commands window, or rightclick the opcode in the commands window and "--> Memory Debugger" to bring the Memory Editor back up and bring it to that address. Then you can NOP it by setting it to 0, and go back to the Commands window to resume ("GO").
TLB Mapping
TLB mapping is pretty evil in the eyes of ASM hackers. This is the reason you don't see 1337 ASM codes for Goldeneye and very few for Perfect Dark. It's like some kind of address aliasing. When you set a breakpoint on Goldeneye, for example, the PC address returned will be something other than the real PC address; in Goldeneye's case you'll get an address beginning with something like '7F' instead of '80'. There's no known way to get around it on the console, but you can with Nemu 0.8. If you set a Breakpoint on a TLB mapped game in Nemu, you'll see all the opcodes in the TLB addresses. The trick is finding the real ones. Rightclick the break address and choose the --> Memory Debugger. This will bring up the memory editor, but notice the addresses shown are the same address you saw in the debugger (TLB mapped). Look at the 32-Bit value of the first (break) address there. Bring up the Search window and do a 32-Bit search for that value. If you got more than 1 result, you'll have to figure out which one it is by comparing the opcodes before/after the one you searched for. There you have it, you got around one of the most evil programming tricks in the book. :)


Regs and Opcodes
What are Registers?
Ok, you set a BP, you have that Notepad window (or you're looking at the Registers Window in Nemu), and I bet you're wondering what that stuff means. The Notepad doc is called a reg dump. It lists the N64 registers and their current value. Registers, or regs as we call them, are variables. ASM uses these variables to carry out various operations. Here's a sample reg dump...

r0 - 00000000   at - 41A00000   v0 - 00000008   v1 - 80110728
a0 - 00000068   a1 - 00000004   a2 - 00000000   a3 - 00006D60
t0 - FFFFFFFF   t1 - 00000004   t2 - 00000006   t3 - FFFFFFFF
t4 - 00000008   t5 - FFFFFFE0   t6 - 00680000   t7 - 8019E0F4
s0 - 801A7190   s1 - 00000000   s2 - 80112148   s3 - 80199C0C
s4 - 800DC9E4   s5 - 00000000   s6 - 00000000   s7 - 00000000
t8 - 00040000   t9 - FFFFFFFE   k0 - 80794AEC   k1 - 00000AAA
gp - 00000000   sp - 80114808   fp - 00000000   ra - 80073930
pc - 80073948


Each of those regs holds a 32-Bit value. For example, v0 holds the value 00000008. These are referenced in ASM by a dollar sign (e.g. $v0).

Notes:

  1. $pc is for our purposes ONLY (it's not an actual reg that you can use in your routines). It holds the address of the ASM that read/wrote to the address you set the Breakpoint on.
  2. $r0 is also known as $zero. It's ALWAYS zero no matter what you try to change it to.
  3. $k1 is always 00000AAA, but it doesn't have to be. Games doesn't use it after boot up, so it's a free reg for us to use.
  4. $sp is the Stack Pointer. The games uses the address in this reg to store regs temporarily while doing things. You'll rarely, if ever, want to change this.
  5. Stay away from $gp,$k0, and $fp. Those are used for special things and changing them usually isn't good.
What are COP1 Registers?
The COP1 registers, also known as Floating Point (FP) registers, are used for floating point calculations. These regs are referenced as F0, F1, etc up to F31. No Dollar signs with them, and Niew doesn't show them, as it doesn't support COP1 opcodes. Some examples of these would be Size mods, Coordinates, some Timers, some Health & Ammo, etc. These FP regs can only be viewed when using Nemu. If you need to know what they are when hacking GSCC2k2, the only thing you could really do is write a long ASM routine to store the values of the FP regs to some emtpy RAM. FP numbers in RAM are stored as 32bit values. If you've ever played with a Bond game, you've seen them. Bond's health (3F80/42C8) was an FP. You'll notice in the Bond case, that your full health is 3F80 0000, but obviosly you only need to freeze the upper half (3F80) to get Infinite Health. What exactly is 3F80? 3F800000 is actually 1.0 and 42C80000 is 100.0. How can you calculate those? Well, you can use FloatConvert. I didn't write that program. It's just something that Parasyte found floating around the net. You can also do it with Hex Workshop's "Base Converter."
What are OpCodes?
Operation Codes, or OpCodes as we call them, are the actual lines of program code within the RAM. ALL OpCodes are 32 bit, but not all ASM codes are 32 bit. ASM codes can be anything from a single 8 bit code, to as many 16 bit codes as the Cheat Device/Emulator will let you enter. The Opcodes are 32 bit, as I said, so to change one OpCode completely you need two 16 bit codes. This is why some ASM codes are so long; it takes 2 lines to change a whole opcode.
What are COP1 OpCodes/Instructions?
COP1 (Co-Processor 1) handles all the Floating Point calculations. It has Opcodes/Instructions like SWC1, ADD.S, etc. You'll see these instructions in Nemu's Commands window, but Niew doesn't support them. That's what LemAsm is for. We'll get in to this in a little more depth later...
What is a NOP?
A NOP (No Operation) OpCode does absolutely nothing. Yes, I said NOTHING. Then why is it so damn useful? Simple. When you want to stop any OpCode from executing, like to stop the game from taking away health when you get hit, you change that OpCode to a NOP. There are 2 basic ways to NOP an OpCode:
  • Long NOP - This is a 32 bit NOP, meaning it takes two 16 bit codes to use. A long NOP is (you guessed it) zero. For Example, to NOP the OpCode at 80023154, you would enter 81023154 0000 and 81023156 0000 into your Cheat Device.
  • Short NOP - This is a 16 bit NOP, so it's a single code. This is what you will want to use most times to keep your codes shorter. The value of a Short NOP is 2400, so to NOP that OpCode at 80023154 this way use 81023154 2400. The reason Short NOPs work is because they're assembled as ADDIU $zero,$zero,xxxx. That's why only 1 line is needed. The other 16 bits don't matter since $zero is always 0 no matter what you try to do to it. hehe
What are Routines?
An ASM routine is just a specific piece of code that does something. It can be any length and do pretty much anything. An example of a routine would be a piece of code that is executed when you fire a weapon. It would read your current ammo, subtract 1 from it, and store the new value.
What are Jumps and Branches?
Jumps are what ASM uses to get from one piece of code (routine) to another routine. Branches have the same affect, but they are conditional jumps. This means they only jump when something is true/false (like an IF in C++/VB6). Both of these opcodes have what is called a Delay Slot. This means the opcode directly following it is to be executed before the jump.
Bytes, Halfwords, Words, and Dwords
Opcodes read & write values to the ram in different sizes...

Byte: 8-Bit (00 - FF)
Halfword: 16-Bit (0000 - FFFF)
Word: 32-Bit (00000000 - FFFFFFFF)
Doubleword (Dword): 64-Bit (0000000000000000 - FFFFFFFFFFFFFFFF)


Using Niew
To use Niew, begin by using GSCC2k2/Nemu to get a ram dump of the game you're gonna edit the ASM of. Dump at least 800 - 802 (GSCC2k2 default), or dump all 4 megs (800 - 804) if need be. Now put that ram dump in the same folder as Niew and open a dos window. browse to the directory where niew is located and type "niew filename.bin" (filename is whatever you named the ram dump). The program will open and load that ram dump, but it'll look all garbled. Now press F4 to switch to ASM view. It'll look like this:

  • The first column is the address (duh), the second is the value (32 bit), and the rest is the ASM.
  • The ">" on the far left side indicates your current location.
  • Press F5 and you'll be able to to type in the address column there. Put in the address of the ASM you want to edit, but make the first 2 digits of the address 00 instead of 80, then hit Enter.
  • scroll up and down with the arrow keyes to look at all that ASM in that area.
  • Press F3 once and the cursor will move to the value of the specified line. If you want to see what someone else's ASM code does, you'd type the value into the proper address(es).
  • To edit the ASM, press F3 a second time. This will put the cursor in the ASM column. Edit the opcode or type in a new one, then hit Enter to go to the next line.
  • You'll notice when you press enter, that the value of that address changes to the value repesenting the edited opcode. This is the value you'll need to enter into your cheat device.


Using LemAsm
First off, note that this could be considered a more advanced topic. You *should* familarize yourself with the basic ASM opcodes and Niew first, but it's up to you. LemAsm, thankfully, is a Windows program instead of a DOS app. So just open the RAM dump you're gonna work with use File - Open (duh). Now, you'll need to change to MIPs disassembly mode with Edit Mode and Show Reg Names. All 3 of these are in the View Menu, or you can hit F6,F3,F7 in any order. Now just type in the COP1 instruction you want to assemble and hit enter (like Niew). Now, one note with this program. I've noticed a bug that occurs sometimes when I try to assemble ADD/SUB instructions. Hit enter and they may become DIV. You'll notice that the hex is something like 4406 1003. that 03 is typicly 3 for DIV, 2, for MUL, 1 for SUB, and 0 for ADD. Just plug the value into Nemu's Memory Editor and look at it in the commands window to see if you've got it right.


Getting Started
Think you're ready to hack your first ASM code? Well, let's do it. I've always found it easiest to learn by example, so I'll show you how to hack a code for Infite health, ammo, time, etc.

I'm going to use Infinite Ice for Mortal Kombat Mythologies, Sub-Zero as our example. Have you ever tried to hack a simple Infinite Health code or something of that type and found that it only works for that area of the game or is just completely random? This is one of those games. The RAM address of your health, ice, etc changes when you get to a checkpoint, get killed, etc. Despite moving around randomly, it always starts in the same place. When you boot up MK Myth and start a new game, your Ice is located at 800F0217. So we set a BPW on 800F0217, throw ice, and the game halts. Click YES for the reg dump to open. This is what we got:

r0 - 00000000   at - 80110000   v0 - 00000086   v1 - 800EFBC0
a0 - 800EFBC0   a1 - 0000FFFF   a2 - 800EFBC0   a3 - 0000009E
t0 - 00000000   t1 - 00000001   t2 - FFFFFFFF   t3 - 00000004
t4 - FFFF0000   t5 - 00000002   t6 - 00000009   t7 - 000000C5
s0 - 8010BF50   s1 - 8010BF50   s2 - 8010BF50   s3 - 0000001A
s4 - 0000001B   s5 - 00000000   s6 - 00000103   s7 - FFFF8000
t8 - 00000001   t9 - 00000001   k0 - 80794AEC   k1 - 00000AAA
gp - 00000000   sp - 800F0138   fp - 800F01E0   ra - 8004ABB8
pc - 8004AC78

The address of the ASM that wrote to 800F0217 is the address in $pc, 8004AC78. Now, NOP that address via GSCC2k2's RAM editor to see if it gives us Infinite Ice. If it doesn't, we need to set the BP on 800F0217 again, cause a break (throw ice, in this case), get the new regs, and NOP the new $pc. Repeat that til you find the one that works. The first one worked here, though.


Working With ASM OPs
This is where it gets interesting. You know that ASM opcode we NOPed above to make Infinite Ice? Well, if you think that's all you can do with ASM, think again. To really get into ASM hacking, you'll need to download Niew. It's a small dos program that's used to view N64 ASM. See the Using Niew section for instruction on using it.

For this example, I'm going to use the Ice in MK Myth again. We've got our regs from the previous example:

r0 - 00000000   at - 80110000   v0 - 00000086   v1 - 800EFBC0
a0 - 800EFBC0   a1 - 0000FFFF   a2 - 800EFBC0   a3 - 0000009E
t0 - 00000000   t1 - 00000001   t2 - FFFFFFFF   t3 - 00000004
t4 - FFFF0000   t5 - 00000002   t6 - 00000009   t7 - 000000C5
s0 - 8010BF50   s1 - 8010BF50   s2 - 8010BF50   s3 - 0000001A
s4 - 0000001B   s5 - 00000000   s6 - 00000103   s7 - FFFF8000
t8 - 00000001   t9 - 00000001   k0 - 80794AEC   k1 - 00000AAA
gp - 00000000   sp - 800F0138   fp - 800F01E0   ra - 8004ABB8
pc - 8004AC78

Now that we have our reg dump, we need to use GSCC2k2/Nemu to get a RAM Dump. After you dump the RAM to a file, open it with Niew. Now press F4 to switch to ASM view and press F5 to enter the BP address (8004AC78), but start it with 0 instesd of 8. Scroll up a few lines so you can see what happens before and after your ice is written to. Here's what it looks like:



Now the part we need to look at in this case is 0004AC68 - 0004AC78. I'll explain what each opcode does...

  • lui $v1,802F //Load Upper Immediate: This puts the hex value (immediate) into the left (upper) half of the reg specified. Meaning $v1 now equals 802F0000.
  • lw $v1,CE20($v1) //Load Word: This loads the word (32-bit) value of the reg in () + the hex offset into the reg on the left. The hex offset is signed though; the means if the offset is higher than 7FFF, the upper 16 bits are made FFFF. So, CE20 is actually FFFFCE20. The 32-bit value at 802ECE20 is "800EFBC0", so $v1 is 800EFBC0 now. Anyone familar with pointers? This is what they're for. hehe
  • lhu $v0,0656($v1) //Load Halfword Unsigned: This loads the halfword (16-bit) value from $v1 + 0656 hex (signed offset) into $v0. Now $v0 is the amount of Ice you currently have.
  • addiu $v0,$v0,FFE0 //Add Immediate Unsigned Word: This adds the 2nd reg listed + the hex value and puts that value into the 1st reg, so $v0 = $v0 + FFE0. Now you're gonna say, "how does that decrease my ice?" Well, let's say your Ice, $v0, is 00000080. Now add FFE0 to it and you get 00010060.
  • sh $v0,0656($v1) //Store Halfword: Same as when you loaded this halfword value 2 opcodes above. Now you're just storing it. So how does it decrease your ice? It's only storing the halfword value in $v0 to the RAM. That means it's only storing the 16 bits (4 digits) on the right, so it's storing 0060. Now your ice was decreased by 20 (hex).


Pretty cool huh? Info about all the opcodes and what they do can be found in the R4300i Opcode Documentation (you did download it, right???). You can use this ASM to get other effects besides infinite ice. Obviously, you can change the FFE0 so you'll lose less ice, more ice, no ice at all, or even make it increase your ice. BUT, why stop at Ice? You can make this ASM routine do just about anything. I'll show you something relatively easy and useful you can do with this...

Ok, you remember that your ice is stored at 800F0217 initially. Well, your health is always 2 below your ice (it's stored at 800F0215 initially). So by making a small alteration to this ASM, you can give yourself Infinite Ice and cause your health to be refilled when you throw ice. We only need to change 2 opcodes for this.

We change:
addiu $v0,$v0,FFE0
sh $v0,0656($v1)


to:
addiu $v0,$zero,00FF
sh $v0,0654($v1)


Now $v0 = $zero + 00FF, so $v0 is 000000FF. $zero is just that. It's ALWAYS 0. You'll notice we barely changed the 2nd line. Health is stored 2 below ice, so we changed 0656 to 0654. Now what's the code to use for this effect? That's what the 2nd comlumn in Niew tells you. When you change each of those, the values in the 2nd column change accordingly. The values that are different are highlighted since they may or may not all change. Here's what it looks like:



Ok, we can see the a few values changed. The code would be:
8004AC75 0002
8104AC76 00FF
8004AC7B 0054

That's your code. You changed the ASM so it makes your health 00FF everytime you use ice, and your ice never goes down because the asm that was decreasing it is changed to refill your health instead. We can shorten this code to 2 lines if we want to.
8104AC74 2400
8004AC7B 0054

The first line kills that ADDIU opcode and the 2nd makes it store the value of $v0 to your health instead of ice, so basicly, it copies the amount of ice you have into your health.


Hacking Various Types of ASM Codes
I showed you above how to make Infinite Ammo, Health, etc and Modify your ammo consumption & such all in pretty much the same way there. Now I'll show you how to hack some other types of ASM codes. I'll try to start out with easier codes and progress to more advanced stuff. Always remember, "there's more than one way to skin a cat." The examples I'm showing you are never the only way of doing something, and they're by no means the only types of things you can do. The only limits in ASM hacking are the hacker's mind.
Infinite Ammo For All Guns
There are times when the method I stated above won't affect all your guns. Duke Nukem 64 is an example of this. Each gun has its own routine that decrements your ammo when you shoot. Think the only way to do it is a code for each gun? Nah. Let's make 1 code do it all.

Set a BPR on the ammo for whatever gun you have currently equipped (Pistol in my case - 802A5A00). The game should halt immediately.

Breakpoint/PC Address: 8006F8E0.

Opcode: LH $A2,59FE($A2)

What it does:
Loads Halfword value $A2 from $A2 + 59FE. $A2 is 802A0002. (802A0002 + 59FE = 802A5A00) In this case, the reg that's holding the address we're going to need to write to is overwritten by the load opcode. This isn't a problem; it's actually useful in this case. That initial address in $A2 changes depending on what gun you have equipped so it can read the ammo you have for whatever gun is equipped. It's actually using this to display your ammo on the screen. Do we care? No, so let's change it and get Infinite Ammo for all weapons. :)

Since it's a Load Halfword (LH), we want it to Store Halfward (SH) instead. Sounds good, right? But what do we store? If you can find a reg that always has a useful value in it (like something between 0001 and FFFF) you can use it; otherwise you'd have to go through setting the value yourself. For now, we'll do this the easy way. Remember $K1 is always 0AAA? Well, we'll store it and always have AAA (2730) bullets for all guns.

Our new Opcode is: SH $K1,59FE($A2) When you enter that into Niew, you'll notice only 1 16-Bit address changed, 8106F8E0. The value is now A4DB. There's your code :)
Modify Initial Stats When Starting A New Game
This type of code is reminiscent of the Game Genie era. "Start With X Lives" was something that was seen for pretty much every game, but we've never seen that kinda thing on N64, have we? Well, we can now. I'll use Mario 64 as an example this time.

At the title screen/menu, set a BPW on Mario's lives (8033B21D). Now Start a new game. The break will occur at 8025500C, but this stores $zero to the address, so NOP it and try again. Now you'll get the right one.

Breakpoint/PC Address: 8025501C

Opcode: SB $T2,00AD($T3)

What It Does:
Stores Byte value $T2 to the address $t3 + 00AD.

Now we gotta make it store what we want to there. Start looking at the opcodes that come before it and find what sets $t2. Luckily, this one is actually the opcode right before it. 80255018: ADDIU $t2,$zero,0005. This is pretty simple. Just change the 0005 to whatever you want, so your code would be 8125501A ????.

Lets say, for the sake of argument, that you found a Load instead of an Add opcode there. You'd have 2 options.

1. Change the op to ADDIU $T2,$zero,????
2. Go to the address the Load op is loading $T2 from and change it there.

"But what if there's no load? I wanna modify something that you start with 0 of, and the opcode just stores $zero." This can be a bit trickier. You can use $K1 and have AAA of that item if you want -- the easy way out. Or you can find a place before the Store opcode to put an 'ADDIU $K1,$zero,????'. If you can replace an exiting op without it causing any problems, or find an empty space (NOP) somewhere there, you're in good shape. Otherwise, you'd need to Jump out of that routine to an empty area, set the reg, then Jump back. This may sound a bit complicated, but it's really quite easy once you get the hang of it. See Routines & Jumps for more details.
All Scores/Damage Counts For x Player/Team
This is easiest on sports type games (i.e. Gretzky Hocky '98); however it is possible to make player 2/CPU beat themself on a fighting game and is rather fun to watch. You simply set s BPW on the score, for example. Then it'll break when the game adds to your score. On Gretzky Hockey (Pal), you'll get the shots first, since it's watching that too (because the BPW covers more than 16-Bits). NOP that and get actually score to get it to break again. This will be the score asm. Gretzky's is at 8005ABCC.

opcode: SH $T8,3440($A2)

What you need to is look through the opcodes that come before it and find a suitable one to replace, preferably the opcode that sets $A2 in the first place (or a nice NOP to make use of). In this case we find a NOP at 8005ABA0. Now let's say we want i tto always add to team 1's score. That's 800EA870. Split that into 2 16-Bit halves, 800E and A870. Now because ASM uses signed values we need to take in account the value of the 2nd half of the address. If it's more than 7FFF, which it is, we need to add 1 to the 1st half. So 800E becomes 800F. Now we use 'LUI $A2,800F' in that NOP and change both the LH and the SH opcodes there to add A870 instead of 3440.

Before:
8005ABA0: NOP
8005ABAC: LH $T7,3440($A2)
8005ABCC: SH $T8,3440($A2)

After:
8005ABA0: LUI $A2,800F
8005ABAC: LH $T7,A870($A2)
8005ABCC: SH $T8,A870($A2)

So our code is:
8105ABA0 3C06
8105ABA2 800F
8105ABAE A870
8105ABCE A870

You can do that same thing on fighting games, but P2/CPU won't actually beat themself up, so it's less entertaining. I've never actually accomplished this myself, but the way it's done starts out the same way as this, but then requires tracing through the ASM to find where it determines what player is actually being hit. This has been done for Mortal Kombat Trilogy if you'd like an example code to look at on your own.
Invcincibility
Using an Infinite Health code but tired of getting banged around? Here's your solution. I'll use Turok Dinosaur Hunter as an example on this one. If you know me, you had to know that'd be coming eventually, considering I hacked over 30,000 codes for that game. hehe

This starts out the same as most things. BPW on Health address and, get hit by an enemy, game halts. The address of the breakpoint here is 80071494, but that's a Branch. The actual opcode that wrote it is the next one down, 80071498. NOPing that will stop your health from being decremented when hit, but doesn't make you untouchable. To do that, you need to scroll down through the OPs til you find a 'JR $xx'. Look at 80071654. Bingo! JR $RA

JR is a "Jump Register." This jumps to the address stored in $RA. Now, is there something loading $RA just before that? Yep, 8007164C. We need a BPX after that loads to find out what it is. On Nemu, this is easy, I mentioned above. On GSCC2k2, you'll have to set it up...

This is what I recommend for GSCC2k2 users:
80071498: LUI $K1,8000
80071658: SW $K1,0060($K1)

In GS code form:
81071498 3C1B
8107149A 8000
81071658 AF7B
8107165A 0060

What that does is set $K1 to 80000000 (the game won't using $K1 for anything, remember?) and stores it to 80000060, which you'll set a BPW on. Now why did I pick those addresses? Well, 80071498 is that opcode that stores helath, we don't what it does and we don't really care about it, so we can change it. Now 80071658 is the opcode directly after JR $RA. Lucky for us it's a NOP, so it works out well. Now when you get hit again, it'll break. This time, we know what the break address is going to be, no suprise there. What we wanna know is what $RA is at the time. In this case, it's 80060A24. Now when you look at the ASM here, you'll see that it immediately loads $RA from somewhere else and does another JR $RA. This is not always the case, but it is this time, so BPX again. Change 80071658 back to NOP, if you didn't didn't already, and put your SW $K1,0060($K1) at 80060A30 (the NOP following the new JR $RA). Now this time we get the breakpoint and $RA is 80061D4C; go there. Look up 2 opcodes and you should se a JAL (Jump And Link). Yep, 80061D40 is JAL 800609A4. That jumps to 800609A4, which appears to be the start of the routine that causes Turok to take damage. NOP it and see if you become Invincible. Yep, Turok is now a God. What you just did was trace some ASM. In some games, you'll have to repeat those steps and trace back even further.
Modify Jump Height (Mega Jump!)
This one's rather hard to explain. You basicly have to find your own way of doing this, but I'll give you the general concept here. You have to find the ASM the writes the Moon Jump address when you first press the jump button. That's harder than it sounds because there are usually multiple constant writes on the Moon Jump addy, and one of those could be the one you're looking for so you can't just NOP them. I've found it possible with Nemu64 to keep resuming and get my charater to jump between breaks, but this can be a bit tricky. I don't know if it's XP or what, but sometimes I can move and such on the game when the Commands window is highlighted instead of the main game window, and other times I can't. One way I've found to get this to happen is to load the Mario 64 rom, then immidiately click onto whatever window is behind Nemu; odds are, you'll be able to press whatever keys you've assigned for the controller and you'll hear Mario's face getting trashed (The little *doing* you hear when you click his face). Then you'll know this is working and you can go ahead and load the game you wanna hack. If you can, get that far, then open the Registers window (Commands window should already be open) and go to the "COP 1" tab. Now keep resuming and tryin gto jump, as I said, and everytime time it breaks look at the reg being stored (F4,F6, etc) and see what the value is. You're usually looking for the one that's 4xxxxxxxx when being stored to the jump address. Get that far? Good for you. The hard part is over. Now there are 2 ways you can change the jump height.

1. You can search for that value in the RAM. This is the easy way out, however you might need to do it more than once if the game has different types of jumps (i.e. Banjo has normal jump and backflip).

2. Change the ASM. To change COP1 regs though, you can't just use ADDIU. You have to set a normal reg (like $k1) to the value you want to use with LUI then use MTC1 to put it in the COP1 reg that the game is storing. In most cases, you could also stick with the regular ops and change the SWC1 to an SH/SW. For example sake, I'll show you the way to do it with COP1:

  • LUI  $K1,???? // Load Upper Immediate will set $K1 to ????0000
  • MTC1 $K1,Fx // Move To COP1 moves the value in $K1 to Fx (x can be 1-31, in our case it should be the reg that's being store to the Moon Jump address.

I know this description is a little rough. Imagine trying to write it up. ;)

If you really want to do this with GSCC2k2, I'd suggest setting up the BPW as HyperHacker describes for his Anti-Gravity How-To below, but jumping up instead of falling. This is just a theory on my part, as I don't have a working shark anymore.
Anti-Gravity Codes (by HyperHacker)
You will need GSCC for this. Knowing some ASM may help but isn't required.

  1. Find the character's Y Speed. If there's an L Button To Levitate or Moon Jump code for the game, chances are it works by setting the character's Y Speed to a constant value. (Also in some cases making them jump - if there's more than one line besides the activator, remove some until the code only works when you're in the air - that's the Y Speed.)
  2. Open GSCC, go into the RAM editor and start the game.
  3. Go into the breakpoint window, and create a Write BP on the character's Y Speed. Don't enable the BP yet, but use the Tab key to make sure the Set BP button will activate when you press Enter.
  4. In the game, fall off something. (Don't jump, you have to be moving down.) While in the air, press Enter on the PC to enable the BP.
  5. The game should halt immediately. Clear the message box that appears and GSCC should go to the BP address. (Check the N64regs.txt file it opens to be sure, since sometimes it goes to the wrong place. The number beside "PC" is the BP address.)
  6. Write down the 16-bit value at this address. Change it to 2400 and the gravity should be disabled! (You may still slowly fall, some games do that.) If not, repeat the process (don't change this value back) until you find it.
  7. Make a code out of it!


Note: No gravity may get annoying and make the game hard to play. I suggest you set it up like my Mario 64 codes, this way:
[Activator]
[Anti-gravity code]
[Activator]
[Enable Gravity code*]
[Activator]
[Y Speed = Positive code**]
[Activator]
[Y Speed = 0 code**]
[Activator]
[Y Speed = Negative code**]


Each activator should be different of course.
*The Enable Gravity code is simple. Take the Anti-gravity code and change the value back to what it originally was.
**These are codes to set the character's Y Speed to any positive value, 0, and any negative value, allowing the player to move up and down. Play around to find a nice speed. Also, some games crash when setting the speed to 0, in which case I recommend about 3000 for most games (which isn't stopped, but so super slow you wouldn't notice).

(C) 2003 HyperHacker
Walk Through Walls (General Theory Only)
I haven't done a great deal of looking for WTWs yet, but I thought I'd share the general concept of how you'd find them with ASM because they've always been such a hot topic.

Theory 1:

On games where you take damage from hitting walls (i.e. certain racing games) you could BPW on healh/energy and hit a wall to cause the break. Then start backtracing the ASM a bit and NOPing the Jumps. I did this with F-Zero X and was able to pass through the walls, althrough you plummit to your death if you slow down. lol

Theory 2:

Now what about the typical games, where you just bounce your head off the walls but nothing happens? This may be a little tricky. The idea here would most likely be to BPW on your X/Z coordinate and find the ASM that causes you to move. Then look around for branches that skip it when you hit a wall, or an opcode that stores your X/Z again when you hit something. I don't imagine this will be easy, but I know someone will want to try it. :)
Breaking Limits
Ever wanna carry more than 100 bullets, but the game wouldn't let you? I'll show you why and how to get around it. Let's use Turok Dinosaur Hunter v1.0 for this one. Get near a clip with something other than the pistol equipped (to avoid extra breaks). Now set a BPR and BPW on the pistol ammo. You'll get an immediate break on this one. The ASM that breaks is actually what tells if you're allowed to equip the pistol or not (this is how I made the weapons shoot without ammo). NOP that and set the BPs again. Now you can walk over the clip to cause a break. You'll find yourself at 80057DB4, which is a Branch; the Load Word is in it's delay slot. Now if you look through the next few opcodes, you'll see these:

80057DC0: SLTI $at,$t0,00C8 //Set On Less Than Immediate. If $t0 is less than 00C8, $at is 1, otherwise $at is 0
80057DC4: BNEL $at,$zero,80057F68 //If $at is Not Equal To $zero, then jump to 80057F68
80057DD8: SLTI $at,$t0,0064 //Set On Less Than Immediate. If $t0 is less than 0064, $at is 1, otherwise $at is 0
80057DDC: BNEL $at,$zero,80057F68 //If $at is Not Equal To $zero, then jump to 80057F68

C8 (200) is the maximum pistol ammo when you have a backpack and 64 (100) is the max without the backpack.

If you look at 80057DB0 and B4 you'll see it loads the word (32-Bit) value from 80128D28 (the backpack's on/off address) and branches accordingly. You can NOP the Branch there to force the pistol to always have the packpack available, or force the Branch so the backpack is never avilable to the pistol. Now, we can do a couple different things with the opcodes I showed you above. You probably noticed by now that you can change the values on the SLTIs to whatever you want to mod the maximum pistol ammo with/without backpack. Now how bout killing the limit entirely? Easy. Just force the branch. We can do this by changing it to a Jump, but that'll make it a 2 line code to replace each Branch. Instead, we'll change the branch to BEQ $zero,$zero,80057F68. I use this just like I use Short NOPs. 1000XXXX is BEQ $zero,$zero,XXXX address. :)

Wait, we're not done yet. That will allow you to pickup ammo no matter how much you have, but if you try it, you'll see you're still capped at 64/C8. This is because there's another check where it actually adds the pickup to your total, so when you have 99 bullets and pickup 10 you still end up with 100. When you get the next breakpoint (you can NOP the LW here or just resume), you'll get a useless one at 8009269C. NOP it and go again. Now you'll be at 80057B14. See anything interesting?

80057B28: SLTI  $at,$t6,00C9
80057B2C: BNE   $at,$zero,80057D10
80057B30: ADDIU $t9,$zero,00C8
80057B40: ADDIU $t1,$zero,0064
80057B44: SLTI  $at,$t0,0065
80057B48: BNE   $at,$zero,80057D10

and again you'll see it check for the backpack and Branch accordingly.

With This group, notice the ADDIUs. You can change just these and make it so when a pickup would bump you over 100/200 bullets, it'll give you like 500 instead. You could change both the SLTI and ADDIUs to change the max ammo with/without backpack. Lastly, you can break the limits entirely by forcing the Branches like I showed you.

So from 3 breakpoints, we can make all these codes, and probably more that I haven't even thought of... like adding bullet pickups to other weapons instead.

Max Bullets Modifier (Without Backpack)
81057B42 ????
81057B46 ????
81057DDA ????
Max Bullets Modifier (With Backpack)
81057B32 ????
81057B2A ????
81057DC2 ????
Pistol Can Shoot Without Ammo
810583F8 1000
Pistol Can Shoot Without Ammo (Alternate)
810583F4 240A
810583F6 0001
Break Max Bullets (Without Backpack)
81057B48 1000
81057DDC 1000
Break Max Bullets (Without Backpack)(Alternate)
81057B44 2501
81057DD8 2521
Break Max Bullets (With Backpack)
81057B2C 1000
81057DC4 1000
Break Max Bullets (With Backpack)(Alternate)
81057B28 25C1
81057DC0 2501
Always Have Backpack For Bullets
81057B20 2400
81057DB4 5400
Always Have Backpack For Bullets (Alternate)
81057B18 240F
81057DB0 2419
Always Have Backpack For Bullets (Alternate 2)
81057B22 0001
81057DB6 0001
Copy Bytes Codes
You might've noticed that PSX and all the newer systems have a code type called "Copy Bytes." What this does is copy values from one location in RAM to another. You can use ASM to do this on N64 if you want, since Datel didn't bother to include the code type in the N64 shark.

First, find an ASM routine to 'hook' into. You'll probably want something that's always executing, but there may be cases where you want something else. The easiest way I've found to do a Copy Bytes that's always copying is to find a routine that reads the button/stick activators for the game. I'll use Super Mario 64 as our example in this one....

We'll do a BPR on the button activator (00367054). We got 80323B58 as our break address. Now if we look a few opcodes down, we'll see that we can safely use $t2, $t3, $t4, and $t5. You could jump from 23B58, but I'm going from 23B54 instead -- just my preference. Now, let's jump to 80400000 so we have plenty of room to work, and I'll show you how to copy the value of one address to another.

80323B54: J 80400000 // the Jump (duh)
routine now begins at 8040000
LUI   $t2,8020
ADDIU $t3,$t2,7702
ADDIU $t2,$t2,7772
LH    $t4,0000($t2)
SH    $t4,0000($t3)
J     80323B5C
SW    $at,0000($t8)


That loads the 16-Bit value in 80207702 into $t4, then stores it to 80207772. The Store Word (SW) at the end is just the opcode that the Jump replaced from 80323B54.

Now here is an example routine that copies a lot. This will copy 80207700 - 8020776C (Star & Coin Records for File 1) to 80207770 - 802077DC (Star & Coin Records on File 2).

80323B54: J 80400000 - the Jump (again)
routine begins at 80400000
LUI   $t2,8020
ADDIU $t3,$t2,7770
ADDIU $t2,$t2,7700
ADDU  $t5,$zero,$zero
LW    $t4,0000($t2)
SW    $t4,0000($t3)
ADDIU $t2,$t2,0004
ADDIU $t3,$t3,0004
SLTIU $k1,$t5,006C
BNE   $k1,$zero,80400010
ADDIU $t5,$t5,0004
J     80323B5C
SW    $at,0000($t8)


Ever used a For Loop in other types of programming? Same concept here. If $t5 is less than 6C $k1 is set to 1. If $k1 is Not Equal To $zero then it jumps back to the LW opcode and proceeds through again.


Writing Routines, Jumping In And Out, Etc
Ok, I bet you're wondering what you can do when you want to change something but there's no room to do it within the current routine (no NOPs or useless ops to change). This is where Jumping comes in. Remember in my Invincibility code example, I showed you how to make Turok Invincible? Well, what about if we want to do somethin ga little different? Like make him take Double or Half Damage? Multiply the damage? Well, first you need to find the opcode that subtracts from Turok's health when he's hit. We found the the opcode that actually writes the new helath value (80071498), so it should be a few opcodes or so before that. Look at 80071490. SUBU $V0,$T6,$V1. This means "$V0 = $T6 - $V1" so $V1 would be the amount of damage taken. We need to find a play to jump out of this routine now, so we can manipulate $V1 a bit. This has to happen after $V1 is loaded, but before it's subtracted from $T6. Also, You can't have 2 consecutive Jump or Branch opcodes because jumps and branches all have that Delay Slot and putting another Jump/Branch in there will crash the game. With that Delay slot you also need to take into account the opcode you're replacing with a Jump. I'll show you what I mean...

80071478: LW    $V1,003C($SP)
8007147C: BEQ   $V1,$ZERO,80071538
80071480: NOP
80071484: LH    $T6,01DC($T0)
80071488: LUI   $A0,8010
8007148C: ADDIU $A0,$A0,9E60
80071490: SUBU  $V0,$T6,$V1
80071494: BGEZL $V0,800714A8
80071498: SH    $V0,01DC(T0)


Ok, see 80071488 and 8007148C? Those need executed in that order, so if you put a jump at 80071488 that next opcode that uses $A0 would be in the Delay Slot. We don't want that. 80071484 looks good here. That loads Turok's current health. Write that opcode down before you change it because you want it to still get executed at some point in your routine, so you're not losing anything. Ok, we picked our address to Jump from. Now where do we jump to? You can jump to any part of the RAM that the game doesn't use. Most games don't use the following areas after boot up (so they're free for us to play with):

80000058 - 8000007C 80000090 - 800000C0

and typicly you'll find a good amount of space somwhere in the area between 800002B4 and 800003F0. Also, if you're hacking a game that doesn't require the expansion pack, but you have it anyway, then you've got plenty of RAM to play with (804-808!).

Ok, in this example, we'll Jump to 80000060. The Opcode we'll use is:
J 80000060

That amounts to
81071484 0800
81071486 0018
as our Jump code.

The routine itself is going to double, multiply, or divide $V1. I'll show you 3 ways of accomplishing this, but there are more.

Double:
ADDU $V1,$V1,$V1 // pretty self explanatory, I think $V1 = $V1 + $V1

Multiply: (This is where it gets a little more interesting) ADDIU $K1,$ZERO,00?? - We set $K1 to the amount we want to multiply by MULT $V1,$K1 - Multiply $V1 by $K1. The result is stored in "LO" MFLO $V1 - Move the result from LO to $V1. LO and HI are used for some special calculations (like MULTU and DIVU). If used with a DIV opcode, LO is the division result and HI is the remainder.

Divide:
ADDIU $K1,$ZERO,00?? // We set $K1 to the amount we want to divide by
DIV $V1,$K1 // Divide $V1 by $K1. The result is stored in "LO"
MFLO $V1 // Move the result from LO to $V1.

Let's say you've decided on Dividing by 2 so Turok takes Half Damage. Next, we need to know where we're Jumping back to. This is normally the address of the Jump plus 8 (we need to skip the Delay Slot since it'a already done). So...

ADDIU $K1,$ZERO,0002
DIV   $V1,$K1
MFLO  $V1
J     8007148C


Wait a minute. What comes after the Jump? The Delay slot needs to have a valid ASM opcode (Like a NOP). Oh, what about that opcode that loads health? :)

ADDIU $K1,$ZERO,0002
DIV   $V1,$K1
MFLO  $V1
J     8007148C
LH    $T6,01DC($T0)
That's more like it. Note that in some cases, you may want that opcode to execute first (like if you needed the health value for something there), but for this purpose we don't need to. When you can, I recommend putting the last opcode of your routine in the Delay Slot. If you wanted that health load first then you'd do it like this:
LH   $T6,01DC($T0)
ADDIU $K1,$ZERO,0002
DIV   $V1,$K1
J     8007148C
MFLO  $V1
That would work just the same. Only difference here is that the LH is done first instead of last. Once you get done typing that into Niew, you'll see your code is:

81071484 0800
81071486 0018
81000060 241B
81000062 0002
81000064 007B
81000066 001A
81000068 0000
8100006A 1812
8100006C 0801
8100006E C523
81000070 850E
81000072 01DC

There you have it. "Turok Takes Half Damage"
Tip: Jump And Link
Once you get used to jumping and jumping back, you may want to do it the easy way. Jump And Link (JAL) works like J but it stores the return address in $RA for you. Note that you can't always do this because the game may not load $RA again before it uses it. If you look down through the opcodes in the routine you're jumping out of, you should be able to fins a JR $xx at the end. If this is something other than $RA or it loads $RA from somewhere first, then you're in good shape. Otherwise you'd have to back up that reg somewhere if you're going to do it. A quick way to tell if you can do this other than lookin for the JR $xx is to see if you notice a JAL right in the area. If the game uses JAL there, then you can pretty well bet it's gonna load $RA before it uses a JR $RA. So to do the above code the way I like to do them....

81071484 becomes JAL 80000060 and 8100006C becomes JR $RA

The new code would be:
81071484 0C00
81071486 0018
81000060 241B
81000062 0002
81000064 007B
81000066 001A
81000068 0000
8100006A 1812
8100006C 03E0
8100006E 0008
81000070 850E
81000072 01DC
Tip: Using Regs Other Than $K1
If you need more than just 1 free reg to use in a routine, you'll have to look for ones that aren't in use. The safest thing to do, is use regs that you you will be set right after your routine. In the Tutok example, you could use $T6 since it's set at the end of your routine anyway. You could also safely use $V0, $T7, $AT, and $T8 in this example (look at routine to see why).
Expample Of A Longer ASM Routine
Well, you've seen some of the things you can do here already, but just for good measure I'm going to show you a couple longer, slightly more advanced routines that I wrote for WWF No Mercy.

Example 1: Universal P1 Ultra Code.

Ever play a game where the player & CPU codes can become switched because of how the game is setup? The breakpoint for this was a BPR on P1's Health/Spirit. The general idea here to to find which wrestler is human controlled and keep giving that wrestler Special, Full Spirit, Full Health, Max Health, and Full Health For All Body Parts (so you don't submit).

800FE770: JAL   80400000 //Jump to 80400000 And Link, $RA is now 800FE778
80400000: LB    $V0,00E9($A0) //Load's the value of the CPU/Human address
80400004: ANDI  $V0,$V0,000F //$V0 = $V0 AND 000F, if this is 0, it's human
80400008: BNEZ  $V0,80400038 //Branch On Not Equal, skip to addy if $V0 != 0
8040000C: ADDIU $V0,$ZERO,0004 //$V0 = 0 + 0004
80400010: SB    $V0,00EE($A0) //Store Byte $V0 to $A0 + 00EE
80400014: ADDIU $V0,$ZERO,0064 //$V0 = 0 + 0064
80400018: SH    $V0,00AE($A0) // Store Halfword $V0 to $A0 + 00AE
8040001C: SH    $V0,00AC($A0) // the rest of this is the same way: set, store
80400020: SH    $V0,00AA($A0)
80400024: ADDIU $V0,$ZERO,4248
80400028: SH    $V0,02C4($A0)
8040002C: SH    $V0,02C8($A0)
80400030: SH    $V0,02CC($A0)
80400034: SH    $V0,02D0($A0)
80400038: SH    $V0,02D4($A0)
8040003C: JR    $RA //Jump Register: Jumps to address stored in $RA
80400040: NOP //We'll use a NOP as a Delay Slot since we don't always want that SH to execute.


In this example, we used $A0, which the game sets to a different wresters' pointer each time it goes through. So we load the CPU/Control info for whichever wrestler the game is looking at and check if that wrestler is human. If it is, we go through our routine of writing that wrestler's stats. If not, we Branch (skip) over it and just Jump Back.


Using The COP1 Registers And Instructions
If you've gotten the hang of this stuff and maybe tried finding some more advanced types of codes, or even stopping timers on some games, you've probably noticed COP1 instructions. COP1 has its own set of opcodes and registers for working with floating point values.
How do values get into and out of the floating point registers (FPRs)?
They can either be loaded just like we typicly see, using LWC1, or by moving values from the main regs.

Loading from RAM:
LUI  $V0,8014 //Load Upper Immediate, we already know. $V0 = 80140000 now.
LWC1 F2,5420($V0) //Load Word COP1 works just like LW but puts the value into an FPR, F2 in this case.
LUI  $V1,8014 //Load Upper Immediate. $V1 = 80140000
LDC1 F4,5420($V1) //Load Doubleword COP1. Same as above. Used to load a 64bit value as a double precision floating point value, I guess.

Moving from the main regs:
MTC1 $V0,F2 //Move To COP1. F2 = $V0

Another example: LUI  $V0,3FAB //Load Upper Immediate. $V0 = 3FAB0000
ORI  $V0,22D1 //OR Immediate performs a bitwise logical OR on $V0. If you haven't used OR before, it sets any bits that aren't set. Since the lower bits aren't set (3FAB0000), this is just like adding 22D1 to $V0. If $V0 is 3FAB2200 and you OR it by 22D1, then it'll be 3FAB22D1, as this only sets what wasn't already there.
MTC1 $V0,F2 //Move $V0 to FPR 2.

Getting values out of FPRs:
SWC1 F5,5420($V0) //Store Word COP1. The usual method of writing the value to a RAM location.
MFC1 F5,$V1 //Move From COP1. Moves the value from an FPR reg to a main reg. Thus, $V1 would now be set to the value in F5.
How do I do math operations with FPRs?
Again, this is the same basic concept as you do normally, it's just more confusing. COP1 supports both single precision floating points and double precision floating points. You should have a fair idea of what the 2 mean, if you've programmed much.

Adding:
ADD.S F1,F4,F8 //Floating Point Add, Single Precision. F1 = F4 + F8.
ADD.D F1,F4,F8 //Floating Point Add, Double Precision. F1 = F4 + F8.

Subtracting:
SUB.S F1,F4,F8 //Floating Point Subtract, Single Precision. F1 = F4 + F8.
SUB.D F1,F4,F8 //Floating Point Subtract, Double Precision. F1 = F4 + F8.


Multiplying:
MUL.S F1,F6,F8 //Floating Point Multiply, Single Precision. F1 = F6 * F8
MUL.D F1,F6,F8 //Floating Point Multiply, Double Precision. F1 = F6 * F8

Dividing:
DIV.S F3,F9,F16 //Floating Point Divide, Single Precision. F3 = F9 / F16
DIV.D F3,F9,F16 //Floating Point Divide, Double Precision. F3 = F9 / F16


I like multiplication and division better in COP1 because it doesn't require you to use the extra OP to extract the result. There are also some other neat COP1 OPs like NEG, ROUND, FLOOR, CEIL, etc. This was more of a basic intro to using COP1.


An In-Depth Look at the Translation Look-aside Buffer (TLB) - by Parasyte
Formats

EntryLo0 & EntryLo1:
00 pppppppppppppppppppppppp ccc d v g
(32-bit: 2.24.3.1.1.1)

p = Page frame number; the upper bits of the physical address.
c = Specifies the TLB page coherency attribute. (See below)
d = Dirty. If this bit is set, the page is marked as dirty and, therefore, writable This bit is actually a write-protect bit that software can use to prevent alteration of data.
v = Valid. If this bit is set, it indicated that the TLB entry is valid; otherwise, a TLBL or TLBS miss occurs.
g = Global. If this bit is set in both Lo0 and Lo1, then the process ignores the ASID during TLB lookup.

PageMask:
0000000 mmmmmmmmmmmm 0000000000000
(32-bit: 7.12.13)

m = Page comparison mask


EntryHi:
vvvvvvvvvvvvvvvvvvv 00000 aaaaaaaa
(32-bit: 19.5.8)

v = Virtual page number divided by 2 (maps to two pages)
a = Address space ID field. (ASID) An 8-bit field that lets multiple processes share the TLB; each process has a distinct mapping of otherwise identical virtual page numbers.


Notes On TLB Page Coherency (c bits)

The TLB page coherency attribute (C) bits specify whether references to the
page should be cached; if cached, the algorithm selects between several
coherency attributes. The table below shows the coherency attributes
selected by the C bits.

TLB Page Coherency (C) Bit Values
0: Reserved
1: Reserved
2: Uncached
3: Cacheable noncoherent (noncoherent)
4: Cacheable coherent exclusive (exclusive)
5: Cacheable coherent exclusive on write (sharable)
6: Cacheable coherent update on write (update)
7: Reserved


Translating Virtual Addresses To Physical Addresses

In order to translate virtual to physical addresses, you must be able to read each TLB entry, looking for a virtual address hit.

Start by dumping the TLB entries one-by-one. First, load the CP0 Index register with the TLB entry index you wish to dump. You should start with 0x00, and work your way up to 0x2F. That gives 48 total TLB entries. After loading CP0 Index with the entry number, call a TLBR instruction. This
will dump the TLB entry to the EntryLo0, EntryLo1, PageMask, and EntryHi registers in the formats described above.

After the TLB entry has been dumped, begin decoding the data in each register.

To decode a TLB entry, start with the p bits from the PageMask register. This value tells the size of a virtual page for the TLB entry. The page size can be determined by checking the values against the following table.

0x0000: 4KB (0x1000 bytes)
0x0003: 16KB (0x4000 bytes)
0x000F: 64KB (0x10000 bytes)
0x003F: 256KB (0x40000 bytes)
0x00FF: 1MB (0x100000 bytes)
0x03FF: 4MB (0x400000 bytes)
0x0FFF: 16MB (0x1000000 bytes)

If the value is anything other than listed, the page size is undefined\erroneous. Here is a bit of C which will allow you to grab the page size and a page mask:

u32 mask = (PageMask >> 1) | 0x0FFF;
u32 pagesize = mask+1;


Next, extract the virtual page number from EntryHi, based on pagesize.

u16 tmp = pagesize >> 12;
u32 vpn = (((EntryHi >> 13) / tmp) * tmp) << 13;


Then use the virtual page number as a mask to the virtual address. If the result is the same as the vpn, it's counted as a hit. If not, restart the process with the next TLB index.

if ((vaddr & vpn) != vpn) continue;


If it's a hit, find out if the virtual address is on an odd or even page, based on the TLB page size.

u32 odd = vaddr & pagesize;


Now perform a logical AND on EntryLo0 or EntryLo1, depending on the 'odd' variable. If the result is 0, the page frame is invalid. If successful, extract the page frame number. If not, restart the process with the next TLB index.

u32 pfn;
if (!odd) {
 if (!(EntryLo0 & 0x02)) continue;
 pfn = (EntryLo0 >> 6) & 0x00FFFFFF;
}
else {
 if (!(EntryLo1 & 0x02)) continue;
 pfn = (EntryLo1 >> 6) & 0x00FFFFFF;
}


Finally, if it gets this far, mask the virtual address with the 'mask' variable created earlier. Then attach the physical page number, and or with 0x80000000 to complete the process. Lastly, exit the loop. Checking the remaining entries for a hit is not required.

u32 paddr = (0x80000000 | (pfn * pagesize) | (vaddr & mask));
break;


And there you have it! Your paddr variable will be a pointer to the
translated address.