Due to the simplistic nature of this page, a lot of subject components won't be grouped with their respective subjects. This is obviously because some things need to be addressed first and the structure of the page is so that it becomes more elaborate and difficult as you traverse down it! Enjoy!
It's important to note that some values (like music/sound values in certain games such as the Zelda's) aren't in base 16 (hexadecimal). Also, no one ever got anywhere sitting and knowing about how to locate these and became a good hacker. Even if they are simple, practice makes perfect. So a beginner should constantly locate values to develop a familiarity with the the tool(s) you're using and you never know, you might come across something more difficult to locate and eventually gain knowledge for a certain game which could further fuel your intrinsic motivation. I'm sure you know the saying "practice makes perfect."
- 1 For hacker insight
- 1.1 Byte sizes, binary and the base 16 (hex) bit pattern, basic MIPS (ASM) talk -- with examples
- 1.2 Hex Values and further understanding addresses
- 1.3 GameShark Codes explained
- 1.4 Pointers
- 1.5 Float-Point
- 1.6 MIPS Starter Knowledge
- 1.7 Difficulties of hacking in comparison to older video game consoles
- 2 In conclusion
- 3 Basic value searching guides
- 4 Infinite Health
- 5 Infinite Lives
- 6 Infinite Ammo
For hacker insight
The goal here is to make things as basic as they can possibly be. A lot to do with learning how to hack is perspective just like with a lot of other arts and practices. Hell, perspective is key to mastering essentially everything in life. Here is a relational property as an example: I can imagine most people have encountered how difficult it can be at times to have someone see something your way, rather, "in the right picture." Anyhow, I won't be going into depth about hexadecimal (base 16) or it's binary relation here. That can be found in the links below! I'll just assume you get the point so I don't come off as too didactic. :P
Wikipedia page for base16/hexadecimal
Not so simple introduction to hexadecimal
Extensive, very informative introduction to number systems and binary
basic programming and understanding hexadecimal (my eyes!)
Using a hex editor to change text color
Basic hex editing guide
More practical start which provides somewhat of a vague beginners rundown. (recommended!)
Byte sizes, binary and the base 16 (hex) bit pattern, basic MIPS (ASM) talk -- with examples
Okay, all this is very important, so I'm going to make is as simple as I possibly can and will even be incredibly redundant (constant repeating). If you catch on early, just ignore the redundancies, if not read it all!
A byte (XX -- seen below) is composed of 8-bits, so we'd call half a byte (4-bits) a nibble. If you're looking at a byte that is A0, then the first nibble is A and the second 0 and would be 4-bits each, and together make up a byte (8-bits) Take a look at the char below with this example 12FF49A0000090DE:
- X = half of a byte also called a "nibble," or "nybble" (4-bits) -- e.g. 1
- XX = 1 byte (8-bits) -- 12
- XX XX = half-word (16-bits) -- e.g. 12 FF
- XX XX XX XX = word (32-bits) -- e.g. 12 FF 49 A0
- XX XX XX XX XX XX XX XX = double word (64-bits) -- e.g. 12 FF 49 A0 00 00 90 DE
When it comes to counting addresses when looking at bytes: MIPS instructions are 32-bits/a word long. To help give a mental image behind hexadecimal and it's relation with MIPS: You have those "on and off -- 1s and 0s" switches I'm sure you've heard about which are binary numbers. Well, when there are a lot of them, the on and offs produce a result of some kind through logic gates (linked to the "simple" side of Wikipedia), but ignore their purpose right now. So you've got a bunch of binary numbers, so how do you "condense" down the patterns to a more legible (clearer/easier to read), editable state??? Why with base16/hexadecimal of course! An example of binaray to hex: 1101 = D in hex. So how do you make it even more legible??? You use take the hexadecimal patterns that were taken from binary patterns and turn it into a low-level language, and in this case, that language is called MIPS (the language the Nintendo 64 processor uses). Using MIPS to program instead of binary is a lot more simple because while using symbols, programmers gain flexibility with describing a source. It's just more difficult for people to keep track of all those ones and zeros and describe what individual parts of the program would do.
- Machine Instruction: 1000 0010 0010 0001 0010 1110 1100 0011
- Hexadecimal representation: 82212EC3
- Assembly language (MIPS) statement: LB $AT, 0x2EC3($S1) ;this example has an opcode, register, base register and an immediate value
When you need a program to translate ASM to machine instruction so it can be read by the processor, you use what is called an assembler. Want further explanation with the above opcode and it's format? Then read directly under this text, if you're still struggling, then I'd recommend skipping the format talk directly below this and jump to the octal bit pattern below.
LB $AT, 0x2EC3($S1)
LB = Opcode
AT = target register (abbreviated as rt; one of the register types)
S1 = base (another register type) 0x2EC3 = Immediate value
You probably noticed that when looking at the base 16 representation that the last two bytes of "82212EC3" are that of the immediate value instead of what the register "S1" is holding. This is because the instructions follow a format (Which can be found here) Also, spoke below in a more in-depth way, there are a few 'types' of register formats. Anyway, I'll put the above example "Load Byte" example here. The following format is determined by the opcode type:
----------------------------------------------------------------- | LB | Load Byte | Opcode type |-----------|---------------------------------------------------| |100000 (32)| base | rt | offset | Function field ------6----------5---------5-------------------16---------------- Bit size Format: LB rt, offset(base) Purpose: To load a byte from memory as a signed value. Descrip: rt = byte[base+offset]
As you can tell, the format has base coming right after the opcode. Assembly statements like the one you see above are executed out of what is called a "machine cycle," more can be found with searching the net. For more miscellaneous information and required "know-how," refer to the MIPS category closer to the bottom of the page.
More on bit patterns
As noted above, hexadecimal is named that because the way you couple/group binary numbers together like 0110 (XXXX) then it's hexadecimal. As you could assume, there are other pattern names too! We're only dealing with hexadecimal, but the top example below is what you call "octal." For more information, a quick search will point you in the right direction. 01101010 = 01 101 010 = 152 (octal) 01101010 = 0110 1010 = 0x6A (hex)
Just being redundant as I said above. Back into essentially the same hex/base 16 talk as above (with decimal examples)
To be more descriptive, you can take a look into base numbers. This is how binary numbers are "made" into the pattern name "hexadecimal." The following is an example of the binary pattern for base 16 (hexadecimal): e.g. 0010 1000 1010 1100 = 28AC -- They're grouped by 4-bits (XXXX)
Also to develop a proper frame of mind, look at hexadecimal (base 16) as a more legible representation of machine code (again, binary). It's pretty simple to understand once you get used to it. It's all about shifting left and overwriting "numbers," put simply. I suppose, just look at it as a bunch of on and off switches that ultimately create a big image.
- e.g. 0000 1101 = 0D(hex) and 13(decimal)
- e.g. 0001 0000 = 10(hex) and 16(decimal).
Hex Values and further understanding addresses
Just look at these like a combination of numbers to produce a result. In this case, to produce different instructions. e.g. 0909 is NOP (no operation, however 0000 is also an alternative, but other instructions don't have another alternative). With that said, the "instructions" follow specific formats depending on the desired opcode which is covered here, but I wouldn't focus on MIPS at this point in time if you find yourself reading this page. Also, note: offset and address are synonymous/entirely interchangeable
Alright, more into instructions:
Every instruction is a fixed size of 32-bits wide (4 bytes) (that is a word: WW XX XX XX e.g. 1FC007C0). As you can see, there are 4 addresses to a row.
So if you're looking at address 800AD324, and with knowing every addresses contents are 32-bits wide (4 bytes), all you would do to count what address is which while simultaneously looking in the "Commands" window (in Nemu64) is know that the furthest most column is equal to the second furthest most column of addresses, and from there, you simply add four bytes. This creates a simplified memory model (the instructions being a static 32-bits/4 bytes). Take a look at the following:
Current Address +4 bytes +4 bytes +4 bytes | | | | | Address 800AD324-800AD328 - 800AD32C-800AD324 | | | | | V V V V V 800AD324: WWXXXXXX WWXXXXXX - WWXXXXXX WWXXXXXX 800AD334-800AD338 - 800AD33C-800AD350 800AD334: WWXXXXXX WWXXXXXX - WWXXXXXX WWXXXXXX Again, trying to be as simple as I can possibly be: ======================================================================== Addresses Data 0x0000000: WW XX YY ZZ 0x0000004: AA BB CC DD Counting addresses: WW = 0x0000000 | XX = 0x0000001 | YY = 0x0000002 | ZZ = 0x0000003 AA = 0x0000004 | BB = 0x0000005 | CC = 0x0000006 | DD = 0x0000007
To further elaborate: A lot of times code can be quite lengthy. This is obviously due to only being able to write two bytes per line (XXYY as stated above). With the above example, you obviously see that to cover an entire instruction (while in 32-bit kernel mode), it would take two lines of code. e.g.:
The following example code would cover an instruction (32-bits wide XX XX YY YY).
GameShark Codes explained
Address Value WWXXXXXX YYZZ
- Address = The location in memory to be written to
(When looking at the addresses in Nemu64 or any memory viewing software is not composed of the W and X example above. The reason it's formatted like that is because GameShark codes need more instructing. Look at it like this: A memory address is just a location, but a GameShark code is a "tool" that needs instructing to write to a certain address... More will be covered below)
- Value = The value to be written to the memory address it is assigned to
(As the name suggests, contains the "value" to be written to the specified address (Which is listed before that as shown above). The 2-bytes are used to specify an
"instruction" through what you'd call a "hex value." Disregard the second sentence until you start to learn MIPS.)
- WW = Code Type: List of code types and what they do
- XXXXXX = Rest of the address - This is only here to separate/differentiate it from the code type. It is merely the address in memory, but not the whole address as it lacks the 80/81 prefix that is found in the code type.
- YY = is the upper most of the two-bytes that make up the value. If this section of the value is greater than 00, then the code is writing 16-bits (two-bytes) to the desired memory location (which is specified as code type 81).
- ZZ = is the lower most of the two-bytes that make up the value. If these 8-bits (one-byte) is the only one with numbers (compared to YY), then the code is writing 8-bits to the desired memory location (which is specified as code type 80).
GameShark codes can sometimes be quite long depending on their purpose and their code type. As spoken about, instructions are 32-bits wide (XX XX YY YY -- 4 bytes) and it's easier to cover this length with the use the code type 81, which as stated above, writes 16-bits (YYZZ -- two bytes) to the specified address. With that said, let's look at the following code. It's used as a hook for a code that's compiled with mips-gcc from C (just ignore that for now though).
Take a gander at the above code. You can see it essentially writes 0x0C0FFC00 to address 0x803A0404 (yes, 80, remember code types!). Remember, we said instruction length is 4 bytes, and the two codes combined equal exactly 4 bytes to cover the entire instruction.
Just to provide a super brief opening to an instruction in MIPS:
Instructs the operation to do | Just see this as the other part of the instruction for the time being! | | | V V [opcode] [registers or immediate values] ADDIU SP, SP, 0x0018 OR Rd, Rs, Rt
Of course I won't be explaining to either of their functions nor are those two related to the example code. The code above is not that in MIPS, as I said, the code above is just a GameShark method that merely inserts a hook and is just a JAL instruction. If you're so interested to see it's code:
813A0404: .ORG 0x803A0404 ; the .ORG assembler directive is discussed below in the assembly category
813A0406: JAL 0x803FF000
Code types made simple
As hinted above, when looking at GameShark codes, you can easily find where they're writing by 1) looking at the gs code value and seeing if the 1 after the 8 is legitimately apart of the memory location, or if it's a code type. Meaning, just look at the value and see if it spans past thelower two bytes (lower two bytes ZZ in the first example), e.g. 0020 does not, but 0200 does. As stated, if it does, then its the 81 type, if not, 80. With that said, if you've got a code that is 8119FF05 0008, then the 81 is legitimate, and again, with that example, if the value was 8000, then it's more than likely not and is actually located at 8019FF05. It's easy to figure this out by trying either 81 or 80 in the Nemu "Debugger: Command" window. If it's the actual location, then you'll see MIPS everywhere, if not, just a bunch of stars. Important to note that because it can be located at 8119FF05 and have a value of FBBF or anything 16-bit and still be at that exact location.
Learning how to hack by reverse engineering GameShark codes can also serve as quite a viable method too! I know a few people who had done that initially before expanding their knowledge with hacking and modding. It's important to note that there are more code types than just those two.
In a computer, memory locations are accessed by their address. Imagine individual addresses like street numbers on a really long street. Often, programs need to use data that another part of the program is using, and rather than directing all the data to the desired address, it's a lot easier, and efficient to just send the address where the data is. It's basically telling you where data starts, and is incredibly helpful knowledge to know. Personally, I'd recommend getting involved in some basic programming concepts (even outside of pointers) to better understand the usage of pointers and things of the like. You should look at Nintendo 64 games as big programs that have everything usual program would have: variables, data structures such as arrays, etc, pointers and all that good stuff. It may be of avail, if not, a Google search will suffice. Don't get discouraged with any of this. 1) Don't forget my statement at the start of the Tutorial page stating that the terms for Nintendo 64 hacking can be read elsewhere and are involved with other applications, and 2) perspective is KEY! Try to develop a proper frame of thought around how pointers and other concepts work. It can be by means of imagining it visually, relating it to certain things as like I did with addresses and street numbers above; whatever helps you understand and retain the newly acquired knowledge.
With that said, you can usually locate pointers in a memory editor/viewer by looking for data with a prefix of 80. For example:
However, that is NOT always the case, but as a reminder, the primary goal here is to be simple, and give an example for most instances. Some games may use a different kernel mode, and thus utilize virtual addresses due to an inadequate amount of physical memory for whatever reason. In that theoretical case, due to the memory management unit (MMU) apart of the processor, the game would "alias" the addresses and could vary upon it's location. Being a beginner though, ignore these last few sentences as they aren't pertinent to you until you've grown a better understanding of Nintendo 64 reverse engineering.
More information: IEEE floating point
Floating Points are a hexadecimal representation of "real numbers", following the IEEE-754 standard and can make it difficult to find some things. Floating points sacrifice range for precision; and that's precisely what they're used for: precision/exactness. To put it in the simplest terms possible: Floating Points are numbers with decimal points. 100.0 and 100 are common values used by games to represent your max health. The difference is 100 is 64 in hex; 100.0 is 42C80000 in hex. It's not required you know anything about Floating Points to hack most codes, but they are a major part of games and certain, more advanced, code types can be harder to find if don't have a little understanding of this. (For float conversions, an online converter can be found navigating back a page to the "Tutorials n Tools" section. John G's FloatConvert.EXE can be used to easily convert between float and hex values.
MIPS Starter Knowledge
First and foremost, regardless of your delusional idea of creating awesome codes by jumping to this section without understanding the others: just turn back now! Alternatively, making mods and codes in MIPS can be done in a lot easier with C: It's less time-consuming, and easier to do. You can make such codes with the use of toolchains provided here. With that said, the knowledge in this specific guide isn't even that difficult, as this is the Hacking Starter section after all, but it's not going to be any easier than the information that is found prior to this category. Anyways, I'll give kind of a basic rundown in a nutshell. Somewhat of a synopsis with random helpful knowledge, if you will. I'm only not trying to break this down to such as simplistic level because if you're looking to work with MIPS, you shouldn't need this to be typed in a 'baby's first lesson' instruction format.
Brief talk of Instruction Types
As stated above about instruction's being a static length of 32-bits (but can also be 64-bits depending on the kernel mode -- which is rarely over 32), but they can be extended, having a fixed length makes it easier on us, the hackers. If certain opcodes or format types had different lengths, tools would have to account for that, and the memory model would look, well, a bit more odd in comparison to now. Along with that, you have three instruction types/formats (with random tips/know-how below them):
- R = Register -- Used for register-to-register arithmetic instructions
- the kernel registers ($K0, $K1) and the general purpose register ($GP) are normally available after booting.
- I = Immediate -- Used when an instruction has to operate on an immediate be it to load, store, branch. Must operate on an immediate value and a register value. Immediate values can be sign-extended as discussed above.
- J = Jump/Branch = Used to preform jumps or branches to labels/or symbols (read symbols below) (not actual addresses). They can be seen as the conditional statements of MIPS.
- A JAL will replace whatever value is stored in the return register ($RA) with an address to return to. With that said, it's generally good to preserve values contained in registers in the stack to be restored later if you're new to hacking and modding to not alter program flow, and be sure to update the stack after use. I say that because, if you ask for help, and your program is seemingly fine, people may not account for the location the code is placed at, and the code could be "intersecting" registers. So be sure to look around you and see exactly what the registers are doing, because at times (hardly ever), you may even need to preserve a register that is non-volatile!
- Float = I had already explained above, so just take a look at the float format
Also important to know, the "negative rule. meaning that values which are greater than 0x7FFF are considered as negative numbers, starting from -32768, and counting down as the hex number increases. (e.g. decimal: 32767 = hex: 0x7FFF -- decimal: -1 = hex 0xFFFF) -2 would be 0xFFFE and so on!
You'll also probably notice a "U" (e.g. ADDIU) on the end of some opcodes. The "U" denotes that the opcode is to preform an unsigned operation. For a quick rundown, an unsigned number can hold a larger positive value, and no negative value, while a signed number can hold both positive and negative numbers. You should also take a look at two's complement.
General needed beginner know-how
Below are things I subjectively deem necessary to know. I say "subjectively," base on an ideal theoretical level of hacker one could be, but was wareya described here, he's for sure able to make some neat hacks with minimal knowledge of MIPS. Most of the information and explanations here cannot be found around the scene for whatever freaking reason! You can learn about pseudo-codes and directives elsewhere, but no one really goes into detail about Nemu's debugging features, what a hook is and hazards associated with one, what a symbol is, and I've seen some discussion in relation to jump delay slots, but never load delay slots (but delay slots can also be learned elsewhere).
Breakpoints (debugger linebreaks)
Brief Pseudo-Code talk
It is important to note that a lot of assemblers out there support pseudo-instructions as well. Put simply, pseudo-instructions are fake instructions, as the name suggests (the format and opcode being a "fake one). The reason for such instructions is for shortening of code. For example, when loading up an address, normally you the LUI opcode to load the upper the upper half of the desired register, and then ORI to load the lower half. As you can now imagine, a pseudo-instruction would allow you to do that in one instruction.
Brief Directive talk
Moreover, you've probably looked at an ASM codes source and seen things like ".org," ".set" or ".global." These are called assembler directives. As the name suggests, it "directs," or tells the assembler to do something, and what. For example, ".org" (no quotes) sets the assembler location counter, ".set" tells the assembler to enable or disable certain options (for example you'll see "noreorder"), and so on. To further elaborate on ".org," essentially it's used to specify a location in memory where the game following directive is to be placed. We'll now use ".global" as another example: It's like that of when declaring a variable in C within a function or outside one. It's a matter of a little something called the "scope:" if you declare a variable (JS example (var test;)) outside of a function, it's considered "global," and if you do it in a function it's called "local." A scope in any programming is a region of the program where a defined variable can have its existence and beyond that variable it cannot be accessed. So, if you've got (.global symbolname) then your label/symbol will be visible to code in other files. ".asciiz" would display a string, ".data" or ".text" when seeing things like these two, it's basically saying the code that follows belongs to that specific source, and in most cases, a segment. conclusion, it is important to note that when using a GNU assembler, it's a good rule of thumb to always use ".set noreorder" in your source.
Brief Label/Symbol talk
As briefly spoken about above, you've probably heard of labels. First of all, let's stop calling them labels and instead call them symbols or symbolic labels or symbolic statements or symbolic address. A symbol identifies a location within source code and takes the form of an identifier. An identifier is, as the name suggests, is a name that identifies either a unique thing (code in this case) or a unique class of things and make sure to always append a : (colon) to the end of 'em. Moreover, as spoken about above, directives are important to develop a savviness with as you'll always find one to the left of a symbol at all time. Along with that, I'm sure you've heard the term "symbol table" used a few times too. After reading the above, you could imagine that a symbol table is the table associating symbols with program and data segments and other information in code. Even in applications outside of reverse engineering Nintendo 64 games, people usually refer to the symbol table to check what address have been assigned to global variables and known functions.
Brief Hook talk
One thing you can't find as easily on the internet, and even around the scene as it isn't ever spoken about in detail, for whatever reason, is needing to hook certain assembly codes to a function (location in memory, put simply, but very vague) to be "ran every frame." Well, you could very well find yourself making a code that doesn't work constantly, meaning, it's not written constantly to an address that is getting overwritten by something else. In order to combat such an issue, you need to locate a function in memory that is constantly (preferably), or AT LEAST called very often so that it supports a constant write to the desired address. I've heard of some using WriteBackDCacheAll (Jason777), OsViSwap (various) and even hooking to the exception vector table (most often found at 0x180 (depending on the status flag) in the boot segment in RAM). However, I wouldn't advise the latter as your code wouldn't function properly on an actual GameShark due to an identical method of hooking! Just locate a routine that is called often and you're good to go. Just make sure you don't pick anything stupid that could potentially conflict with your code. It's also incredibly important to not accidentally disrupt program flow, meaning, to not incorrectly intersect the registers in your hook with that of what the game is using for the specific location you're hooking to. There may be times where you have to preserve a register across a call as well. For further discussion on hooks.
Delay Slot talk
Both of the following delay's only last one machine cycle.
Alright, so when writing up assembly codes, there are some important things to note. After jumping or branching, you must put an instruction after the instruction (preferably a NOP). You do this to avoid encountering issues with what they call the "delay slot." However, there are instances where you can utilize opcodes outside of NOP after delay slots. For example, if you're working around 0x8010 in memory, you can continuously re-use that upper value with 'LUI.' You could utilize that, for example, instantly after your hook if you're strictly staying around that memory region! Note, it's obviously not restricted to just that opcode, you can use ANY opcode after a J instruction. Furthermore, information can be found clicking the hyperlink or searching the internet.
Load: More information!
Like that of jump and branch instructions, opcodes that load a value into a register also have a delay slot where the register which the value was just loaded into isn't present until after the one machine cycle delay is no more (so just don't try to work with 'said' newly loaded register as it can result in undefined behaviour). Load delay's aren't present in modern processors, and only were in earlier RISC (which MIPS r4300i is) due to being "highly predictable" nowadays. LW $t8, 0($t4)
ADD $t9, $t8, $t5
This code is illegal because of the load delay not yet loading the value into t8; you could put a "NOP" between them or have another instruction between them for whatever particular reason.
Difficulties of hacking in comparison to older video game consoles
Excerpt taken from the ROM page (didn't cut out any potentially confusing jargon. It's mostly meant to serve as a synopsis for hackers of older consoles):
Most N64 games follow identical data formats, but with a combination of the checksum, multithreading OS, a myriad amount of compressions, N64 CPU's MMU and TLB (not memory-mapped) hacking Nintendo 64 games can be a lot more difficult/tedious in comparison to older video game systems for those very reasons listed above. The games are not directly memory-mapped from ROM, you cannot use methods of corruption like that with earlier games as you could trigger an access violation by passing the end of a decompression routines buffer which would halt a certain thread, you could break a J instruction and send things like dialog into a permanent loop (However you can locate and NOP the loop), could end up dealing with garbage data, the ROM may not load at all. When it comes to the classical hacking approach of corruption, your best bet is to decompress a ROM first, locate file headers and tinker with them such as changing certain lengths of things. Moreover, your best bet is to learn the low-level language of the Nintendo 64, MIPS. A note along with that, there is a chance you could encounter self-modifying assembly code, but that's not very common...
I should also include that when conversing about binary, prefix (in front of) it with a %, and a $ with register values and 0x in front of hex. (E.g. %0110 and $k1 and 0x002FA1). In addition to hex and registers, I'd recommend using all caps because it creates a more 'uniform' look being in all capital letters (0x024af00 just looks 'odd' next to zeros in most peoples opinions, at least).
As noted above in the pointer category, I'd recommend reading further into computer science concepts, but most notably types of data structures and things associated with that: data structure alignment, data segments, but mostly heap and stack, arrays, but mostly bit fields, matrices and, again, other concepts. As explained above too, I'd recommend you at least utilize this knowledge outside of a hacking context in order to get used to such concepts in a more hands on manner with programming. Concocting even basic programs can save you a world of trouble when it comes to figuring out how to hack video games such as those of the Nintendo 64. With all the above said, as a reminder for the third time, perspective is key and practice makes perfect! Try to wrap your mind around it properly, don't over think it, don't get down on yourself, and you will eventually understand to a high degree and laugh at yourself for initially not comprehending such simplistic ideas. I hope everyone the best of luck, and happy hacking! B)
Basic value searching guides
To hack an Infinite Money code, first you must start up the trainer process with a Known Value search. While you have 0 money, search for value "0". Start the second part of the process, go back into the game. Now earn some cash, it doesn't need to be a large sum. Then do another search(for the value that you now have). Now, repeat the process until you find the code.
For a code like infinite health, you go through the same steps above, only you will search for an Unknown Value and you character will start with full health in step one, and less health in step two. So you choose the "less than" option. And when summarizing the quantifiers, notice that MOST(not all) games with a health bar will recognize full health at "0064"(100) or "00C8"(200). With this in mind, you can find the health code a lot easier!
Now on to infinite lives. Do the same thing you did in the first example. The first step will be the default amount of lives, and the second will be one life less. if you started with 3 lives, the first quantifier should be "0003" OR "0002". Since some games start counting at 0 instead of 1, it may be possible that "2" = "3" But this is not usually done.
Another code is Infinite Ammo. This one might be a little trickier. If you do the first search with 100 bullets(search for 100), and the second with 50(search for 50). The code should show up.