Importing C Code Into SM64 Using n64chain and armips
#1
Getting C code into N64 ROMs is easier than you might think using n64chain and armips. This thread will show you how to set them up and build a Hello World example for SM64.

Setting up n64chain and armips

Follow these steps to install n64chain and armips and prepare them for command line usage.

  1. Download n64chain from https://mega.nz/#!RTg0yaCJ!8ZAzK98XRFw0d...tW64UHB-PA and extract its contents to to C:\n64chain\
  2. Download the latest armips build from https://buildbot.orphis.net/armips/ and extract armips.exe to C:\n64chain\bin\armips.exe
  3. Add n64chain and armips to the system path by doing the following:
    1. Click the Windows start button, type "environment variables" in the search bar and open "Edit environment variables for your account"
    2. You should see a variable called "PATH" under user variables, click it and click "Edit..."
    3. (Win7) Add ;C:\n64chain\bin to the end of the variable value and click OK
      (Win10) Click "New", add C:\n64chain\bin and click OK
Note: My n64chain upload is identical to the original, but with the addition of n64crc.

After the path variable has been updated, you will need to restart your computer for the change to take effect.

Simple Hello World

Here is a minimalist Hello World example that overwrites one of the game's useless functions with code of our own. Create a folder containing the following files, and also drop an original SM64 ROM named "Super Mario 64 (U) [!].z64" in the folder.
You can download this archive which contains everything except the ROM.

sm64.h
/*
Here we create declarations for a few functions and variables in SM64 so they can be referenced from our C code.
Later we will tell armips their addresses by defining labels for them in sm64.asm.
*/
extern void PrintStr(int x, int y, const char* text); // 0x802D6554
extern short g_MarioCoins; // 0x8033B218
extern short g_MarioHealth; // 0x8033B21E

sm64.asm
// Tell armips' linker where the assets we declared in sm64.h are located
.definelabel PrintStr, 0x802D6554
.definelabel g_MarioCoins, 0x8033B218
.definelabel g_MarioHealth, 0x8033B21E

hello_world.c
#include "sm64.h"

// This hello_world function will overwrite one of SM64's useless functions, which happens to be called once every frame

void hello_world(void)
{
    // Print HELLO WORLD to the screen
    PrintStr(20, 20, "HELLO WORLD");
    
    // Also kill Mario if he gets 5 or more coins, for science
    if(g_MarioCoins >= 5)
    {
        g_MarioHealth = 0;
    }
}

Note: armips' linker exposes symbols both ways, so if we wanted to make our own call to this function from custom assembly code we could just use jal hello_world.

main.asm
.n64 // Let armips know we're coding for the N64 architecture
.open "Super Mario 64 (U) [!].z64", "Super Mario 64 (U) [!].mod.z64", 0 // Open the ROM file
.include "sm64.asm" // Include sm64.asm to tell armips' linker where to find the game's function(s)
.headersize 0x80245000 // Set the displacement between ROM and RAM addresses
.org 0x802CB1C0 // Set the origin to the RAM address of the useless debug function
.area 0xA4, 0 // Define an area the size of the useless function to ensure we can't overwrite anything else
.importobj "hello_world.o" // Import and link the compiled C object, overwriting the useless function with our new code
.endarea
.close // Close the ROM file

build.bat
mips64-elf-gcc -Wall -O1 -mtune=vr4300 -march=vr4300 -mabi=32 -fomit-frame-pointer -G0 -c hello_world.c
armips main.asm
n64crc "Super Mario 64 (U) [!].mod.z64"

After the files are prepared, run build.bat. This should build a new ROM file called "Super Mario 64 (U) [!].mod.z64" which contains the Hello World program. If you run the ROM and go to the castle grounds, you should see this:


Spoiler alert: Mario does die if he grabs 5 coins.

For reference, here's the assembly and data n64chain generates. Note that armips' linker will put the data sections (in this case, the "HELLO WORLD" text) immediately after the code.

simer, and Valandil211 liked this post
Reply
#2
I made a more advanced makefile setup here: https://github.com/shygoo/sm64-c-injection. This setup combines all C objects into a single library and imports it over the padding bytes in ROM / the unused 80370000 area in RAM. It can be configured to load elsewhere by editing this line. (So no need to import over unused functions!)
Reply
#3
This was very helpful, great work!
Reply




Users browsing this thread: 1 Guest(s)