11 KiB
title | date | series | tags | |||
---|---|---|---|---|---|---|
Super Bootable 64 | 2020-05-06 | howto |
|
Super Bootable 64
Super Mario 64 was the launch title of the Nintendo 64 in 1996. This game revolutionized an entire generation and everything following it by delivering fast, smooth and fun 3d platforming gameplay to gamers all over the world. This game is still played today by speedrunners, who do everything from beating it while collecting every star, the minimum amount of stars normally required, 0 stars and without pressing the A jump button.
This game was the launch title of the Nintendo 64. As such, the SDK used to develop it was pre-release and had an optimization bug that forced the game to be shipped without optimizations due to random crashiness issues (watch the linked video for more information on this than I can summarize here). Remember that the Nintendo 64 shipped games on write-once ROM cartridges, so any bug that could cause the game to crash randomly was fatal.
When compiling something without optimizations, the output binary is effectively a 1:1 copy of the input source code. This means that exceptionally clever people could theoretically go in, decompile your code and then create identical source code that could be used to create a byte-for-byte identical copy of your program's binary. But surely nobody would do that, that would be crazy, wouldn't it?
Someone did. The fruits of this effort are available here. This was mostly a proof of concept and is a masterpiece in its own right. However, because it was decompiled, this means that the engine itself could theoretically be ported to run on any other platform such as Windows, Linux, the Nintendo Switch or even a browser.
Someone did this and ended up posting it on 4chan. Thanks to a friend, I got my hands on the Linux-compatible source code of this port and made an archive of it on my git server. My fork of it has only minimal changes needed for it to build in NixOS.
nixos-generators is a tool that lets you create custom NixOS system definitions based on a NixOS module as input. So, let's create a bootable ISO of Super Mario 64 running on Linux!
Setup
You will need an amd64 Linux system. NixOS is preferable, but any Linux system should theoretically work. You will also need the following things:
sm64.us.z64
(the release rom of Super Mario 64 in the US version 1.0) with an sha1 sum of9bef1128717f958171a4afac3ed78ee2bb4e86ce
- nixos-generators installed (
nix-env -f https://github.com/nix-community/nixos-generators/archive/master.tar.gz -i
)
So, let's begin by creating a folder named boot2sm64
:
$ mkdir ~/code/boot2sm64
Then let's create a file called configuration.nix
and put some standard
boilerplate into it:
# configuration.nix
{ pkgs, lib, ... }:
{
networking.hostName = "its-a-me";
}
And then let's add dwm as the window manager. This setup will be a little
bit more complicated because we are going to need to add a custom configuration
as well as a patch to the source code for auto-starting Super Mario 64. Create a
folder called dwm
and run the following commands in it to download the config
we need and the autostart patch:
$ mkdir dwm
$ cd dwm
$ wget -O autostart.patch https://dwm.suckless.org/patches/autostart/dwm-autostart-20161205-bb3bd6f.diff
$ wget -O config.h https://gist.githubusercontent.com/Xe/f5fae8b7a0d996610707189d2133041f/raw/7043ca2ab5f8cf9d986aaa79c5c505841945766c/dwm_config.h
And then add the following before the opening curly brace:
{ pkgs, lib, ... }:
let
dwm = with pkgs;
let name = "dwm-6.2";
in stdenv.mkDerivation {
inherit name;
src = fetchurl {
url = "https://dl.suckless.org/dwm/${name}.tar.gz";
sha256 = "03hirnj8saxnsfqiszwl2ds7p0avg20izv9vdqyambks00p2x44p";
};
buildInputs = with pkgs; [ xorg.libX11 xorg.libXinerama xorg.libXft ];
prePatch = ''sed -i "s@/usr/local@$out@" config.mk'';
postPatch = ''
cp ${./dwm/config.h} ./config.h
'';
patches = [ ./dwm/autostart.patch ];
buildPhase = " make ";
meta = {
homepage = "https://suckless.org/";
description = "Dynamic window manager for X";
license = stdenv.lib.licenses.mit;
maintainers = with stdenv.lib.maintainers; [ viric ];
platforms = with stdenv.lib.platforms; all;
};
};
in {
environment.systemPackages = with pkgs; [ hack-font st dwm ];
networking.hostName = "its-a-me";
}
Now let's create the mario user:
{
# ...
users.users.mario = { isNormalUser = true; };
system.activationScripts = {
base-dirs = {
text = ''
mkdir -p /nix/var/nix/profiles/per-user/mario
'';
deps = [ ];
};
};
services.xserver.windowManager.session = lib.singleton {
name = "dwm";
start = ''
${dwm}/bin/dwm &
waitPID=$!
'';
};
services.xserver.enable = true;
services.xserver.displayManager.defaultSession = "none+dwm";
services.xserver.displayManager.lightdm.enable = true;
services.xserver.displayManager.lightdm.autoLogin.enable = true;
services.xserver.displayManager.lightdm.autoLogin.user = "mario";
}
The autostart file is going to be located in /home/mario/.dwm/autostart.sh
. We
could try and place it manually on the filesystem with a NixOS module, or we
could use home-manager to do this for us. Let's have home-manager do this
for us. First, install home-manager:
$ nix-channel --add https://github.com/rycee/home-manager/archive/release-20.03.tar.gz home-manager
$ nix-channel --update
Then let's add home-manager to this config:
{
# ...
imports = [ <home-manager/nixos> ];
home-manager.users.mario = { config, pkgs, ... }: {
home.file = {
".dwm/autostart.sh" = {
executable = true;
text = ''
#!/bin/sh
export LIBGL_ALWAYS_SOFTWARE=1 # will be relevant later
'';
};
};
};
}
Now, for the creme de la creme of this project, let's build Super Mario 64. You will need to get the base rom into your system's Nix store somehow. A half decent way to do this is with quickserv:
$ nix-env -if https://tulpa.dev/Xe/quickserv/archive/master.tar.gz
$ cd /path/to/folder/with/baserom.us.z64
$ quickserv -dir . -port 9001 &
$ nix-prefetch-url http://127.0.0.1:9001/baserom.us.z64
This will pre-populate your Nix store with the rom and should return the following hash:
148xna5lq2s93zm0mi2pmb98qb5n9ad6sv9dky63y4y68drhgkhp
If this hash is wrong, then you need to find the correct rom. I cannot help you with this.
Now, let's create a simple derivation for the Super Mario 64 PC port. I have a
tweaked version that is optimized for NixOS, which we will use for this. Add the
following between the dwm
package define and the in
statement:
# ...
sm64pc = with pkgs;
let
baserom = fetchurl {
url = "http://127.0.0.1:9001/baserom.us.z64";
sha256 = "148xna5lq2s93zm0mi2pmb98qb5n9ad6sv9dky63y4y68drhgkhp";
};
in stdenv.mkDerivation rec {
pname = "sm64pc";
version = "latest";
buildInputs = [
gnumake
python3
audiofile
pkg-config
SDL2
libusb1
glfw3
libgcc
xorg.libX11
xorg.libXrandr
libpulseaudio
alsaLib
glfw
libGL
unixtools.hexdump
];
src = fetchgit {
url = "https://tulpa.dev/saved/sm64pc";
rev = "c69c75bf9beed9c7f7c8e9612e5e351855065120";
sha256 = "148pk9iqpcgzwnxlcciqz0ngy6vsvxiv5lp17qg0bs7ph8ly3k4l";
};
buildPhase = ''
chmod +x ./extract_assets.py
cp ${baserom} ./baserom.us.z64
make
'';
installPhase = ''
mkdir -p $out/bin
cp ./build/us_pc/sm64.us.f3dex2e $out/bin/sm64pc
'';
meta = with stdenv.lib; {
description = "Super Mario 64 PC port, requires rom :)";
};
};
# ...
And then add sm64pc
to the system packages:
{
# ...
environment.systemPackages = with pkgs; [ st hack-font dwm sm64pc ];
# ...
}
As well as to the autostart script from before:
{
# ...
home-manager.users.mario = { config, pkgs, ... }: {
home.file = {
".dwm/autostart.sh" = {
executable = true;
text = ''
#!/bin/sh
export LIBGL_ALWAYS_SOFTWARE=1
${sm64pc}/bin/sm64pc
'';
};
};
};
}
Finally let's enable some hardware support so it's easier to play this bootable game:
{
# ...
hardware.pulseaudio.enable = true;
virtualisation.virtualbox.guest.enable = true;
virtualisation.vmware.guest.enable = true;
}
Altogether you should have a configuration.nix
that looks like
this.
So let's build the ISO!
$ nixos-generate -f iso -c configuration.nix
Much output later, you will end up with a path that will look something like this:
/nix/store/fzk3psrd3m6x437m6xh9pc7bnv2v44ax-nixos.iso/iso/nixos.iso
This is your bootable image of Super Mario 64. Copy it to a good temporary folder (like your downloads folder):
cp /nix/store/fzk3psrd3m6x437m6xh9pc7bnv2v44ax-nixos.iso/iso/nixos.iso ~/Downloads/mario64.iso
Now you are free to do whatever you want with this, including booting it in a virtual machine.
This is why I use NixOS. It enables me to do absolutely crazy things like creating a bootable ISO of Super Mario 64 without having to understand how to create ISO files by hand or how bootloaders on Linux work in ISO files.
It Just Works.