Nintendo 64 console with EverDrive cartridge

Nintendo 64 Part 1: Land of Pain

, Nintendo 64, Programming

I must be searching pain out at this point. There’s a Nintendo 64 game jam about to start. Should I participate? Probably not. But it sounds hard. It sounds crazy. It’s completely nuts.

I found a list of Nintendo 64 homebrew games. There are NINE games on that list. Dexanoid, Flappy Bird, MMR, OMSK Pong, Penguins Luv Melons, Pyoro 64, Simon 64, Tetris Demo Beta, and Twintris. Some of these look like tech demos or proofs of concept.

Note: This is filed under “blog” for a reason. It’s not intended to be a guide or walkthrough, but just a record of how I did things.

What is the N64 Like?

Just some quick opinions.

The N64 is proper intimidating though.

Creating a Cloud VM for Development

I’m using Google Cloud this time. After a bunch of false starts and fiddling about with OS Login (and not able to log in to the machine), I got a decent setup working. I normally don’t use cloud VMs for development, so I was interested in trying it out.

The e2-standard-4 instance type seems fine. I’ll shut it off when I’m not using it, and use an ephemeral IP.

$ gcloud auth login
$ gcloud config set project <project-name>
$ gcloud config set compute/zone us-central1-a
$ gcloud config set compute/region us-central1
$ gcloud compute instances create dev01 \
  --machine-type e2-standard-4 \
  --image-family debian-10 --image-project debian-cloud \
  --service-account <name@domain>
  --scopes default,storage-rw
$ gcloud compute config-ssh
$ ssh <host>
# apt install tmux zsh git python3

And I do a few setup tasks on the machine.

Building a Cross Compiler

I found the instructions on Wikibooks: N64 Programming/Compiling. They seem to be from the GCC 6.3.0 era, which puts it sometime around 2017, most likely. Fairly recent! But there were some problems.

First, the instructions conflict with the instructions from the GCC documentation, which tell you that you do an out of tree build (i.e. outside the source directory). Second, I could find no evidence that the architecture mips64vr4300 is recognized by current versions of GCC, except as a host environment.

So, with some help from reading the actual GCC docs and from OSDev Wiki: GCC Cross-Compiler, let’s try to get this working.

After spending an hour wrangling with GCE trying to SSH into a VM, and having to turn off OS Login, I’ve got a Debian 10 VM with 4 CPUs.

Install basic prerequisites.

$ apt install build-essential bison flex texinfo \
  libgmp-dev libmpfr-dev libmpc-dev libisl-dev libzstd-dev

Let’s define some variables to use later. For prefix, just choose a location you’d like to install the binaries. As a reminder: you don’t want to install them to /usr.

$ target=mips64-elf
$ prefix=/opt/n64
$ PATH=$prefix/bin:$PATH
$ export MAKEOPTS=-j4 # Or the number of CPUs you have

Start by building binutils.

$ tar xf binutils-2.35.1.tar.gz
$ mkdir build-binutils
$ cd build-binutils
$ ../binutils-2.35.1/configure \
  --target=$target --prefix=$prefix \
  --program-prefix=mips64- --with-cpu=vr4300 \
  --with-sysroot --disable-nls --disable-werror
$ make
# make install

This took about one minute. Next is GCC, it will take longer. Before building GCC, I stopped my VM instance and changed it to a much bigger instance type. You can change it back with the same steps

$ gcloud compute instances stop dev01
$ gcloud compute instances set-machine-type \
  --machine-type e2-standard-32 dev01
$ gcloud compute instances start dev

If we’re building GCC, we don’t actually want to build everything, and that won’t work anyway since it relies on system headers that don’t exist.

$ tar xf gcc-10.2.0.tar.gz
$ mkdir build-gcc
$ cd build-gcc
$ ../gcc-10.2.0/configure \
  --target=$target --prefix=$prefix \
  --program-prefix=mips64- --with-arch=vr4300 \
  -with-languages=c,c++ --disable-threads \
  --disable-nls --without-headers
$ make all-gcc
$ make all-target-libgcc
# make install-gcc
# make install-target-libgcc

Now I have a MIPS toolchain installed in /opt/n64 and I can try it out to compile something.

int main(int argc, char **argv) {
    return 5;
}
$ mips64-gcc test.c
  /opt/n64/lib/gcc/mips64-elf/10.2.0/../../../../mips64-elf/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400040
$ file a.out
  a.out: ELF 32-bit MSB executable, MIPS, MIPS-III version 1 (SYSV), statically linked, not stripped

It’s obviously not runnable—it’s got no entry point and my system can’t run MIPS code.

$ ./a.out
  zsh: exec format error: ./a.out

So, I’ve certainly built something. Life is an adventure! At this point it’s about midnight and it’s time for me to go to bed.