Game Emulation on NixOS

2026-25-7

Sorry to keep you waiting!

It’s been a long while. I just haven’t had motivation to write, but I thought I’d at least try. This is something I’ve been obsessing over for the past few days and I thought writing about my experiences might help someone. Modern games are pretty cool (Deltarune), but I’ve been really enjoying some classics that I’ve been meaning to play.


# sm64coopdx

This isn’t emulation, but I still think its valuable info. sm64coopdx is a fork of the Super Mario 64 PC Port with a ton of QOL, multiplayer, and great mod support. The intended way to use the package is to legally obtain your own ROM and manually add it to the Nix store–I didn’t do that. I hacked up a derivation which downloads a ROM in the format expected by the package:

{
  stdenv,
  fetchurl,
  unzip,
  region ? "us",
  showRegionMessage ? true,
}: let
  rom = stdenv.mkDerivation {
    pname = "baserom-us";
    version = "1.0";

    src = fetchurl {
      url = "https://archive.org/download/nointro-merged/no-intro-merged/Nintendo%20-%20Nintendo%2064%20%28BigEndian%29%20%28Parent-Clone%29%20%2820210924-131351%29.zip/Super%20Mario%2064%20%28USA%29.zip";
      sha256 = "sha256-SUWwae27Xo6od8aDzJa9FRXJ11hngRDBg+Lvy2aei00=";
    };

    nativeBuildInputs = [unzip];

    dontConfigure = true;
    dontBuild = true;
    dontUnpack = true;

    installPhase = ''
      unzip "$src"
      install -Dm644 "Super Mario 64 (USA).z64" "$out/baserom.us.z64"
    '';
  };
in
  rom
  // {
    romPath = "${rom}/baserom.${region}.z64";
  }

Then I feed it to the package none the wiser:

pkgs.sm64coopdx.override {
    sm64baserom = pkgs.callPackage ./sm64baserom.nix {};
};

# Dusklight

Similarly, Dusklight is a port of The Legend of Zelda: Twilight Princess available for all platforms. Instead of declaratively setting the ROM, I set the path to it (~/Games/Roms/gc/) since I don’t want a one gigabyte game in my store.


# ES-DE

ES-DE is by far the best emulation frontend which unfortunately isn’t packaged anymore. I hacked together my own package based on the AppImage:

{
  appimageTools,
  fetchurl,
  makeDesktopItem,
  symlinkJoin,
  lib,
}: let
  es-de = appimageTools.wrapType2 {
    pname = "es-de";
    version = "3.4.1";
    src = fetchurl {
      url = "https://gitlab.com/es-de/emulationstation-de/-/package_files/288156961/download";
      sha256 = "sha256-PGGkTXONVRY9qljt5wcgtCWg32JGDATcI908pYZyNYE=";
    };
  };
  desktopItem = makeDesktopItem {
    name = "es-de";
    desktopName = "ES-DE";
    exec = "${lib.getExe es-de}";
    categories = ["Game"];
    comment = "A frontend for browsing and launching games.";
    icon = fetchurl {
      name = "icon.png";
      url = "https://impro.usercontent.one/appid/oneComWsb/domain/es-de.org/media/es-de.org/onewebmedia/ES-DE_logo.png?etag=%226071-6041244a%22&sourceContentType=image%2Fpng&ignoreAspectRatio&resize=240%2B168";
      hash = "sha256-2Q9Iy6zk6L3tITtqYSVwky6KjLTvovDjP+DBJGyUWK8=";
    };
  };
in
  symlinkJoin {
    name = "es-de";
    paths = [
      es-de
      desktopItem
    ];
  }

I then configure ES-DE with hjem. Notably, you can even declaratively install themes by sourcing it with fetchGit– I do this with Art Book Next.


# RetroArch

RetroArch is the premiere emulator frontend for older systems. I currently use it for SNES (snes9x), PS1 (Swanstation), N64 (ParaLLEl), and PS2 (PCSX2). You can easily install these cores using the pkgs.retroarch.withCores function, or install all cores with pkgs.retroarch.full. I then also configure this with hjem. Something to note is that RetroArch, for whatever reason, doesn’t let you use shaders that are symlinks. I tried to declaratively setup crt-royale, but it just wouldn’t budge.


# Dolphin

Dolphin is the best Gamecube and Wii emulator available. It’s trivial to setup, and can even be configured fully via CLI arguments for all you wrapper fans. I personally still choose to use hjem for convenience.


# Cemu

Cemu is, fortunately, the only emulator here which requires keys. Even more fortunately, they’re really easy to setup with NixOS. I just use fetchurl to download a pastebin of all Wii U game keys, then use hjem to put them at ~/.local/share/Cemu/keys.txt:

hjem.users.poacher = {
  files = {
    ".local/share/Cemu/keys.txt".source = pkgs.fetchurl {
      url = "https://pastebin.com/raw/w6GxMMNX";
      hash = "sha256-88ea2nNVP1MBl4p+wri+Y2+Ii/lm5n60gxbwp5w7AGw=";
    };
  };
};

# ShadPS4

What a pain in the ass. Not to say the software is bad, its the best (and only) PS4 emulator, but it can’t extract PKGs because of legal issues. So, I had to setup a distrobox container with an older version which could. After that though, it plays games just fine.


# The Steam Controller

Not really a NixOS thing (at least I think?), but a lot of this software doesn’t like using the Steam controller if it isn’t run through Steam. ES-DE especially hates it and refuses to map to it in Gamepad mode.