forked from cadey/xesite
381 lines
11 KiB
Markdown
381 lines
11 KiB
Markdown
---
|
|
title: Super Bootable 64
|
|
date: 2020-05-06
|
|
series: howto
|
|
tags:
|
|
- witchcraft
|
|
- supermario64
|
|
- nixos
|
|
---
|
|
|
|
# Super Bootable 64
|
|
|
|
[Super Mario 64][sm64] was the launch title of the [Nintendo 64][n64] 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][wfrrpannenkoek].
|
|
|
|
[sm64]: https://en.wikipedia.org/wiki/Super_Mario_64
|
|
[n64]: https://en.wikipedia.org/wiki/Nintendo_64
|
|
[wfrrpannenkoek]: https://youtu.be/kpk2tdsPh0A
|
|
|
|
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][mvgo0] (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.
|
|
|
|
[mvgo0]: https://youtu.be/NKlbE2eROC0
|
|
|
|
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?
|
|
|
|
<center>![Noooo! You can't just port a Nintendo 64 game to LibGL! They're
|
|
completely different hardware! It wouldn't respect the wishes of the creators!
|
|
Hahaha porting machine go brrrrrrrr](/static/blog/portingmachinegobrrr.png)</center>
|
|
|
|
Someone did. The fruits of this effort are available [here][sm64dc]. 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][sm64browser].
|
|
|
|
[sm64dc]: https://github.com/n64decomp/sm64
|
|
[sm64browser]: https://froggi.es/mario/
|
|
|
|
[Someone did this][sm64pcnews] 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][sm66pcsauce]. My fork of it has only
|
|
minimal changes needed for it to build in NixOS.
|
|
|
|
[sm64pcnews]: https://www.videogameschronicle.com/news/a-full-mario-64-pc-port-has-been-released/
|
|
[sm66pcsauce]: https://tulpa.dev/saved/sm64pc
|
|
|
|
[nixos-generators][nixosgenerators] 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!
|
|
|
|
[nixosgenerators]: https://github.com/nix-community/nixos-generators
|
|
|
|
## 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 of `9bef1128717f958171a4afac3ed78ee2bb4e86ce`
|
|
- 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`:
|
|
|
|
```console
|
|
$ mkdir ~/code/boot2sm64
|
|
```
|
|
|
|
Then let's create a file called `configuration.nix` and put some standard
|
|
boilerplate into it:
|
|
|
|
```nix
|
|
# configuration.nix
|
|
|
|
{ pkgs, lib, ... }:
|
|
|
|
{
|
|
networking.hostName = "its-a-me";
|
|
}
|
|
```
|
|
|
|
And then let's add [dwm][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:
|
|
|
|
[dwm]: https://dwm.suckless.org/
|
|
|
|
```console
|
|
$ 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:
|
|
|
|
```nix
|
|
|
|
{ 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:
|
|
|
|
```nix
|
|
{
|
|
# ...
|
|
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][hm] to do this for us. Let's have home-manager do this
|
|
for us. First, install home-manager:
|
|
|
|
[hm]: https://rycee.gitlab.io/home-manager/
|
|
|
|
```console
|
|
$ 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:
|
|
|
|
```nix
|
|
{
|
|
# ...
|
|
|
|
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][quickserv]:
|
|
|
|
[quickserv]: https://tulpa.dev/Xe/quickserv
|
|
|
|
```console
|
|
$ 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:
|
|
|
|
```nix
|
|
# ...
|
|
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:
|
|
|
|
```nix
|
|
{
|
|
# ...
|
|
environment.systemPackages = with pkgs; [ st hack-font dwm sm64pc ];
|
|
# ...
|
|
}
|
|
```
|
|
|
|
As well as to the autostart script from before:
|
|
|
|
```nix
|
|
{
|
|
# ...
|
|
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:
|
|
|
|
```nix
|
|
{
|
|
# ...
|
|
|
|
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][confignix].
|
|
|
|
[confignix]: https://gist.github.com/Xe/935920193cfac70c718b657a088f3417#file-configuration-nix
|
|
|
|
So let's build the ISO!
|
|
|
|
```console
|
|
$ 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):
|
|
|
|
```console
|
|
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][bootinvmmp4].
|
|
|
|
[bootinvmmp4]: /static/blog/boot2mario.mp4
|
|
|
|
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.
|