Ocarina of Time-C to GS

From en64 wiki
Jump to navigation Jump to search

Tutorial brought to you by Jason777

This tutorial is NOT recommended for beginners to hacking/modding Zelda 64. It was written for somewhat
experienced modders and programmers who already have a grip on the various file formats of OoT.

With this in mind, I will inform you that I will not answer stupid questions that I feel an experienced person 
should already know if they feel they are capable of attempting this tutorial. Also, from here on out, it would do
you much good if you were start thinking of this caliber of hacking as having little to do with the ROM, but instead
having everything to do with the RAM. You will find that the ROM and the RAM run very differently...

Before we start, let me point out a couple of things. I had originally wanted to write a tutorial on the usage of
spinout's animated textures and 0x0A scene command hack, z64-tex-ext, but then I realized that I would have to
explain hacking in C... and that would take quite awhile. So, instead I have decided to write a tutorial on the basics
of writing hacks in C (and ASM at a later time) which will serve as a reference guide in later, more complex tutorials
and will write a tutorial for z64-tex-ext shortly afterwards. For sure, I will write a tutorial on patching and if I ever feel 
up to it, I may write a tutorial on the basics of writing custom actors/overlays.

You may have noticed that according to the title, this tutorial applies to any version of any N64 Zelda ROM. In 
fact, if you understand this tutorial well enough, the knowledge that is gained can be geared towards hacking N64 
games in general as long as you are well acquainted with its RAM Map and/or functions. The reason why I say 
this tutorial is meant for OoT/MM is because the hacks we will be writing and understanding are specifically made 
for those ROMs... You'll probably understand later in the tutorial.

First off, let me list the expectations you'll need to meet in order to proceed with this document:


You need to running Nemu64. If you don't like Nemu... well too bad. It can handle a lot of shit. I recommend
downloading it from here since it's already set up (thanks, spinout): spinout182.com/ar/nemu64.zip

You need to be running 32-bit Windows OS. However, I may end up modifying this tutorial to work with Linux at
a later time. Also, I don't have access to and have never used a 64-bit OS so I really don't think I can speak on their
part. I believe SanguinettiMods runs a 64-bit OS and I have helped compile a hack made in C and for this reason
I may update this tutorial to work for 64-bit, too.

Extrememly important: For hacking with C you need to have basic knowledge of programming in C and how to 
use pointers. The syntax and declaration of structs and unions are pretty beneficial, too.

Extremely Important: For hacking with ASM you need to have basic knowledge of the usage of registers in MIPS
Assembly, the meaning and usage of opcodes, and minimal knowledge of how the N64 CPU works; i.e. whenever a 
JAL opcode is used, the return address is stored in register $RA and $HI and $LO are used for storing results of 
multiplication and division routines. Another example would be how functions which require more than one 
argument use registers $A0 through $A3 and values from $SP (stack pointer) + 0x10 for any additional arguments. 
The list goes on and on...

This is really imporant: for god's sake, it is absolutely required of you to know how a Makefile works! 

This is really important: you must know how to insert GS codes into Nemu's Cheat file.

You know simple MSYS commands such as 'pwd' and 'cd'

You have access to the internet. We need it to download a compiler and certain software.

Alright... let's move on!

We need to go grab the installer for the MinGW compiler (GCC for Windows) so let's download it from this page: 

At the moment of writing this, the latest installer is "mingw-get-inst-20120426.exe." Download it 

Once you have the installer downloaded, run it, make sure to install to C:\MinGW (or whatever your main HDD letter
is...), agree to everything, yada yada yada. When you get to the Repository Catalogues, click the bubble to
"Download latest repository catalogues." When you get to the "Select Components" Page, all you really need to 
check mark is the "C Compiler" and the "MinGW Developer Toolkit" since it includes the "MSYS Basic System."

Now we need to modify the computer's $PATH variable to fix location issues when running the MSYS and accessing
tools of the MinGW package. Right click "My Computer", go to the "Advanced" tab and click the "Environment
Variables" button. In the "Environment Variables" window that pops up, look at the "System Variables" section, 
click the text entitled "Path" under the variable column (it should now be selected), and then click the "Edit" button 
that is below this list. Now a "Edit System Variable" window will come up. There is a box right next to the words 
"Variable value"; this is what we want to edit. Go to the very end of the text in this box and add a semi-colon (;)at the
end; this means that we are adding another path. Finally, paste this right after the semi-colon:


(Adjust the path according to your harddrive letter if need be.) 

We are done downloading, installing, and setting up the MinGW compiler. Let us move on to getting the N64
Toolchain and writing our first example hack in C!

Alright, go to this page to download "n64tree-win32.7z":

Extract the downloaded 7-Zip archive to "C:\n64tree-win32" (Again, adjust according to your harddrive's letter). We
have to add this directory to the $PATH variable, too. This time, however, we will be doing it a different way; we'll
be using MSYS (the command-line type executable for running MinGW tools) since you already have it installed.
In your "Start" menu, it should be under "All Programs->MinGW->MinGW Shell.exe." Once you have MSYS open,
(make sure you're running "As Administrator) type in this command to navigate to "n64tree-win32" folder:

cd /c/n64tree-win32

Adjust the "/c/" part according to your HDD letter. Then, we need to navigate to N64 Toolchain's compiler and tools

cd root/bin

Then, we add this directory to the $PATH variable like so...:

export PATH=$PATH: 'pwd'

Yup. Messing with the $PATH variable is pretty useful if you're tired of writing out full locations to certain 
executables. Keep in mind that this is only a temporary change for only this session running MinGW, though. 
If you want it to be permanent, modify the $PATH variable as you would with the MinGW path explained a couple
of paragraphs above. The N64 Toolchain is ready to go! Time to write our first hack in C.

So, like any programming guide, we're going to start off with a simple "Hello World" hack. This will teach you how
to use functions in OoT, linker scripts, and writing Makefiles.

Okay, use Notepad to open up "Hello.c" in C:\n64tree-win32\Hello
You should see the following:

#include <mips.h>

void SetTextRGBA(void * DList, u8 r, u8 g, u8 b, u8 a);
void SetTextXY(void * DList, u16 x, u16 y);
void SetTextString(void * DList, char * format, ...);

void _start (void * a0) 
	asm volatile("la $gp, _gp");

	SetTextRGBA(a0, 255, 255, 255, 255);
	SetTextXY(a0, 15, 15);
	SetTextString(a0, "Hello, World!\n");

^^ This is our hack.

Time for me to explain some things... as stated earlier, it is absolutely essential to have some experience with
programming in C and to hold some understanding over the concepts of using pointers and references.

#include <mips.h>

It is really important that we include the "mips.h" header file. Without it, we wouldn't be able to access many important
values and register information. Also, it gives little abbreviations for variable definitions with specified bit-lengths (uint16_t == u16).
It is also noted that mips.h includes the "stdint.h" header file which gives definitions for bit-lengths.

void SetTextRGBA(void * DList, u8 r, u8 g, u8 b, u8 a);

This is the function prototype for the SetTextRGBA function that can be found within the actual game.
It's first argument... I really can't say for sure other than it takes the address to "a text object that is 0x2C bytes long.
It's structure is still yet to be determined." The second argument is the red value of the text. The third argument is
the green value of the text. The fourth argument is the blue value of the text. The fifth argument is the alpha value
for the text. All arguments besides the first one are unsigned 8-bit values.

void SetTextXY(void * DList, u16 x, u16 y);

This is the function prototype for the SetTextXY function that can be found within the actual game.
It's first argument is the same as in SetTextRGBA so I don't really need to explain that. The second argument is the
X coordinate on the screen relative to where you would like to print the text. The third argument is the Y coordinate 
on the screen relative to where you would like to print the text. Remember that the actual flat part of the screen that
I am referring to (the parts of the screen which display the interface, magic bar, hearts, etc) is 2-dimensional. This
means that there is no Z axis.

void SetTextString(void * DList, char * format, ...);

This is the function prototype for the SetTextString function that can be found within the actual game.
It's first argument is the same as the past two functions. The second argument is the text string to print. Any
additional arguments are for formatting the string and/or supplying more strings.

void _start(void * a0)

This is the start of the hack. We should keep it called "_start" for simplicities sake. It is defined as the entry point of
the hack in the linker script. I will explain this later on. Also, as you can see, the hack takes one argument. The argument
is supplied within the hook file "hook_text." The reason for this is because the first argument used by the text functions
is quite tricky to define. So, to get around this, we have hooked into some code which has already set up the first
argument and have replaced a JAL to SetTextString with a JAL to our hack (0x80600000). I'll explain this some more
when we are discussing the hook.

asm volatile("la $gp, _gp");

To tell you the truth, I have no idea why this is needed. Some people say it's needed and some say that it isn't. I've
seen working hacks which both contain or do not contain this line of code. It is ALWAYS put as the first line of code
in the main hack. According to frauber, "to ensure that the data (ie, #define values, static variables, etc) will be loaded

SetTextRGBA(a0, 255, 255, 255, 255);

Here we are setting the RGB values of the text string before it prints. We are supplying it with the RGB combination
to print white text. We are leaving the alpha value (the fifth argument) as 255 or 0xFF.

SetTextXY(a0, 15, 15);

Here we are setting the XY coordinates of the text string before it prints. We are setting the X coordinate to 15 and 
the Y coordinate to 15. This should be the center of the screen. Additionally, it is safe to assume that the screen is
made up of 30 by 30 units.

SetTextString(a0, "Hello, World!\n");

Here we are supplying the text string to be printed. We are printing: "Hello World!"

...And that is the end of the hack. Now we must discuss the linker script.

Okay, use Notepad to open up "Hello.x" in C:\n64tree-win32\Hello
You should see the following:

/* ========================================================================
 * n64ld.x
 * GNU Linker script for building an image that is set up for the N64
 * but still has the data factored into sections.  It is not directly
 * runnable, and it contains the debug info if available.  It will need
 * a 'loader' to perform the final stage of transformation to produce
 * a raw image.
 * Copyright (c) 1999 Ground Zero Development, All rights reserved.
 * Developed by Frank Somers <frank@g0dev.com>
 * Modifications by hcs (halleyscometsoftware@hotmail.com)
 * $Header: /cvsroot/n64dev/n64dev/lib/alt-libn64/n64ld.x,v 1.2 2006/08/11 15:54:11 halleyscometsw Exp $
 * ========================================================================

OUTPUT_FORMAT ("elf32-bigmips", "elf32-bigmips", "elf32-littlemips")
EXTERN (_start)
ENTRY (_start)

   /* Start address of code is 1K up in uncached, unmapped RAM.  We have
    * to be at least this far up in order to not interfere with the cart
    * boot code which is copying it down from the cart

   . = 0x80600000;
   SetTextRGBA   = 0x800FB3AC;
   SetTextXY     = 0x800FB41C;
   SetTextString = 0x800FBCB4;
   /* The text section carries the app code and its relocation addr is
    * the first byte of the cart domain in cached, unmapped memory

   .text : {
      FILL (0)

      __text_start = .;
      __text_end  = .;

   /* Data section has relocation address at start of RAM in cached,
    * unmapped memory, but is loaded just at the end of the text segment,
    * and must be copied to the correct location at startup

   .data : {
      /* Gather all initialised data together.  The memory layout
       * will place the global initialised data at the lowest addrs.
       * The lit8, lit4, sdata and sbss sections have to be placed
       * together in that order from low to high addrs with the _gp symbol 
       * positioned (aligned) at the start of the sdata section.
       * We then finish off with the standard bss section

      FILL (0xaa)

      __data_start = .;
     /* _gp = ALIGN(16) + 0x7ff0;*/
/*  _gp = . + 0x7ff0; */
  . = ALIGN(16);
  _gp = .;
      __data_end = .;
      __bss_start = .;
      /* XXX Force 8-byte end alignment and update startup code */

      __bss_end = .;
   .bss (NOLOAD) :  {
        __bss_start = .;
	__bss_end = .;
	end = .;


^^ This is our linker script.

Okay let me start off with this. You don't have to pay attention to any code that comes before and after this part:

OUTPUT_FORMAT ("elf32-bigmips", "elf32-bigmips", "elf32-littlemips")
EXTERN (_start)
ENTRY (_start)

   /* Start address of code is 1K up in uncached, unmapped RAM.  We have
    * to be at least this far up in order to not interfere with the cart
    * boot code which is copying it down from the cart

   . = 0x80600000;
   SetTextRGBA   = 0x800FB3AC;
   SetTextXY     = 0x800FB41C;
   SetTextString = 0x800FBCB4;
   /* The text section carries the app code and its relocation addr is
    * the first byte of the cart domain in cached, unmapped memory

Locate the above lines in your linker script to follow along.

I'll try to explain this the best I can since most of the stuff that I am talking about is merely by speculation since
nobody else bothered to explain these things to to me; I had to figure out this bit myself by looking at other peoples'

OUTPUT_FORMAT ("elf32-bigmips", "elf32-bigmips", "elf32-littlemips")

This specifies that the output file after linking objects will be a 32-bit MIPS ELF binary. It can be formatted to
produce big and little endian. It is big endian by default. You should always leave these lines alone.

EXTERN (_start)
ENTRY (_start)

This specifies the entry point of our code. This is why we named our main function "_start" -- Leave it alone.

. = 0x80600000;

After skipping a couple of lines, we come to this. The "." refers to the entry point; this is the function _start. We are
saying that we would like the _start function to be located at the RAM address 0x80600000. There is alot of free
space in that part of memory. I would highly recommend leaving this line alone.

SetTextRGBA   = 0x800FB3AC;
SetTextXY     = 0x800FB41C;
SetTextString = 0x800FBCB4;

These few lines are like the above explanation. They specify where these functions are located. SetTextRGBA is
located at 0x800FB3AC, SetTextXY is located at 0x800FB41C, and SetTextString is located at 0x800FBCB4. You can
find the locations to quite a few other game and OS functions on the RAM Map.

Okay, that's it for the linker script... Now on to the Makefile.

Okay, use Notepad to open up "Makefile" in C:\n64tree-win32\Hello
You should see the following:

# Hello  --   OOT Debug ROM #


# Directories
N64BIN	= ../root/bin

# Environment variables
CC      = $(N64BIN)/mips64-gcc
AS     = $(N64BIN)/mips64-as
LD      = $(N64BIN)/mips64-ld
OBJCOPY = $(N64BIN)/mips64-objcopy
BINCODE = $(N64BIN)/bin2code
CHEAT   = $(N64BIN)/nemucheat

# Flags
CFLAGS  =  -G 0 -O3 --std=gnu99 -mtune=vr4300 -march=vr4300 -I../include -I.
ASFLAGS = -march=vr4300 -mabi=32

# Compile binary
all: $(PROGRAM).bin
	$(BINCODE) $(PROGRAM).bin 80600000 codes.txt
	cat hook_text codes.txt | $(CHEAT) "Hello, World" 0 > final.txt

$(PROGRAM).bin: $(PROGRAM).elf
	$(OBJCOPY) $(PROGRAM).elf $(PROGRAM).bin -O binary

$(PROGRAM).elf: $(PARTS)
	$(LD) -o $(PROGRAM).elf	$(PARTS) $(LDFLAGS) 
	rm *.o *.elf *~ *.bin final.txt codes.txt -vf

^^ This is our Makefile.

All that really needs to be changed from compiling C code the normal way are the compiler, the linker, and their
flags. You may use an assembler if you feel the need to assemble a hack you made in ASM or if a hack requires
C and mixed assembly.

And now we come to our compiler, linker, assembler, and other program definers.

CC      = $(N64BIN)/mips64-gcc

Our compiler (CC) is mips64-gcc. It is located at "../root/bin/mips64-gcc.exe"
Keep in mind that "CC" is a keyterm in Makefiles.

AS     = $(N64BIN)/mips64-as

Our assembler (AS) is mips64-as. It is located at "../root/bin/mips64-as.exe"
Keep in mind that "AS" is a keyterm in Makefiles.

LD      = $(N64BIN)/mips64-ld

Our linker (LD) is mips64-ld. It is located at "../root/bin/mips64-ld.exe"
Keep in mind that "LD" is a keyterm in Makefiles.

OBJCOPY = $(N64BIN)/mips64-objcopy

Our binary converter (OBJCOPY) is mips64-objcopy. It is located at "../root/bin/mips64-objcopy.exe"

BINCODE = $(N64BIN)/bin2code

Our normal GS code creator (BINCODE) is bin2code. It is located at "../root/bin/bin2code.exe"

CHEAT   = $(N64BIN)/nemucheat

Our Nemu GS code creator (CHEAT) is nemucheat. It is located "../root/bin/nemucheat.exe"

And now we come to our compiler, linker, assembler, and other program flags/arguments.
Before you read any further, it is recommended that you look at these pages to specify flags for the MIPS
compilers, linkers, and assemblers:


^^ You must study the information on the first link. Anything else having to do with linking and optimiziation is
quite necessary, too. Alright, let's go on.


These are our linker flags. We are stating that our linker script is "Hello.x" 

CFLAGS  =  -G 0 -O3 --std=gnu99 -mtune=vr4300 -march=vr4300 -mabi=32 -I../include -I.

These are our compiler flags. We have our code on optimization level 3, our language standard is that of the GNU
dialect of ISO C99, we are generating code that will run on MIPS VR4300, we are optimizing our code for MIPS
VR4300, we are generating code for 32-bit ABI, and our include directory is "../include" (for include statements).

ASFLAGS = -march=vr4300 -mabi=32

These are our assembler flags... as you can see, they are very much like the compiler flags. We are generating code
that will run on MIPS VR4300 and for 32-bit ABI.

Now we come to running the other programs after compilation... I'll try to explain the best I can. You can see the labels
"all" and "clean"-- these are basically like arguments for use when running the makefile. Typing "make [label]" in 
MSYS will jump to that label and execute its commands...

And that's all I have to say about the Makefile. If you know how a Makefile works like I had listed in the
requirements, everything else should be easy to understand on your own.

Since I don't have a section on the process of creating a hook, I have included one already, it is a GS code in the file
called "hook_text." Now... Let's move on to compiling! 

Open up MSYS and navigate to the hack directory. It should be "C:\n64tree-win32\Hello"
Simply type:


The program should compile successfully with this output:

Administrator@homexp ~
$ cd /c/n64tree-win32/Hello

Administrator@homexp /c/n64tree-win32/Hello
$ make
../root/bin/mips64-gcc -G 0 -O3 --std=gnu99 -mtune=vr4300 -march=vr4300 -I../include -I.   -c -o Hello.o Hello.c
../root/bin/mips64-ld -o Hello.elf      Hello.o -T Hello.x -L .
../root/bin/mips64-objcopy Hello.elf Hello.bin -O binary
../root/bin/bin2code Hello.bin 80600000 codes.txt
Processed 28 instructions.
cat hook_text codes.txt | ../root/bin/nemucheat "Hello, World" 0 > final.txt

Administrator@homexp /c/n64tree-win32/Hello

What you want is the text file called "final.txt"-- this holds the Nemu GS code.

I will not explain how to insert the GS code into Nemu's cheat directory as you should learn that for yourself. To
use the code, make sure to have it active on booting the ROM. Anyways, here is how your GS code should look
like in "final.txt":

CheatName0=Hello, World
CheatName0Code0=81070F6C 2400
CheatName0Code1=81070784 0C18
CheatName0Code2=81070786 0000
CheatName0Code3=8107079C 2400
CheatName0Code4=810707E0 2400
CheatName0Code5=810708CC 2400
CheatName0Code6=810708CC 2400
CheatName0Code7=81070914 2400
CheatName0Code8=81070928 2400
CheatName0Code9=8107098C 2400
CheatName0Code10=810709D0 2400
CheatName0Code11=81070ABC 2400
CheatName0Code12=81600000 27BD
CheatName0Code13=81600002 FFC8
CheatName0Code14=81600004 FFB0
CheatName0Code15=81600006 0028
CheatName0Code16=81600008 FFBF
CheatName0Code17=8160000A 0030
CheatName0Code18=8160000C 0080
CheatName0Code19=8160000E 802D
CheatName0Code20=81600010 3C1C
CheatName0Code21=81600012 8060
CheatName0Code22=81600014 279C
CheatName0Code23=81600016 0070
CheatName0Code24=81600018 2402
CheatName0Code25=8160001A 00FF
CheatName0Code26=8160001C 2405
CheatName0Code27=8160001E 00FF
CheatName0Code28=81600020 2406
CheatName0Code29=81600022 00FF
CheatName0Code30=81600024 2407
CheatName0Code31=81600026 00FF
CheatName0Code32=81600028 0C03
CheatName0Code33=8160002A ECEB
CheatName0Code34=8160002C AFA2
CheatName0Code35=8160002E 0024
CheatName0Code36=81600030 0200
CheatName0Code37=81600032 202D
CheatName0Code38=81600034 2405
CheatName0Code39=81600036 000F
CheatName0Code40=81600038 0C03
CheatName0Code41=8160003A ED07
CheatName0Code42=8160003C 2406
CheatName0Code43=8160003E 000F
CheatName0Code44=81600040 3C05
CheatName0Code45=81600042 8060
CheatName0Code46=81600044 0200
CheatName0Code47=81600046 202D
CheatName0Code48=81600048 DFBF
CheatName0Code49=8160004A 0030
CheatName0Code50=8160004C DFB0
CheatName0Code51=8160004E 0028
CheatName0Code52=81600050 24A5
CheatName0Code53=81600052 0060
CheatName0Code54=81600054 0803
CheatName0Code55=81600056 EF2D
CheatName0Code56=81600058 27BD
CheatName0Code57=8160005A 0038
CheatName0Code58=8160005C 0000
CheatName0Code59=8160005E 0000
CheatName0Code60=81600060 4865
CheatName0Code61=81600062 6C6C
CheatName0Code62=81600064 6F2C
CheatName0Code63=81600066 2057
CheatName0Code64=81600068 6F72
CheatName0Code65=8160006A 6C64
CheatName0Code66=8160006C 210A
CheatName0Code67=8160006E 00AA

^^ This is our GS code formatted for Nemu.

If you want to use the hack while the ROM is currently running, you will need to know how to use Nemu's
debugger and the usage of breakpoints:

After getting the code into the Cheats.ini of Nemu - whichever way you choose to do it - boot up Ocarina of Time 
Debug and get to anywhere in the game where you have control of Link. Then, activate Link and set a PC (BPX) 
breakpoint on 0x80600000. The reason we're doing this is just so that Nemu's dynarec can recognize the code 

Anyways, when you have the code enabled, this is what should print on your screen!:

^^ As you can see, there are some unknown text strings that print along with it. I don't know why this happens...
To tell you the truth, the text functions are somewhat tricky to manipulate.

TO-DO: Make a section on creating hooks...

TO-DO: Make another example on hacking with C... this time using pointers and button input.

TO-DO: Make a section on hacking with ASM with two expamples...

TO-DO: Explain the uses and meaning behind certain files such as the ones located in the "include" folder...

-- END NOTE --
This is a really difficult subject to explain. I find it extremely difficult to write a tutorial with detailed information on
how to hack with C or ASM. So, instead I explained how to correctly format your hack, setup the linker script, create
a hook, and discuss what needs to be done in the Makefile. Not to mention I supplied you with the necessary tools
to get the hack to compile. Hope this helps someone.

Happy hacking with C