1# Mesen 2, launched with a set of settings.json keys ENFORCED. 2# 3# Mesen owns settings.json: it rewrites all ~220 KB of it (key maps, 4# per-system config, window state) on startup and exit, so the file cannot 5# be a store symlink. What can be declared is the handful of keys this 6# project depends on, merged in before every launch. Everything else in the 7# file stays Mesen's. 8# 9# None of these keys has a command-line equivalent that works: 10# 11# - `Video.UseSoftwareRenderer` is read in the `MainWindow` constructor 12# (UI/Windows/MainWindow.axaml.cs:81), before the command line is 13# parsed, so a switch arrives after the renderer is already chosen. 14# The GPU renderer gives a BLACK game surface with working audio 15# whenever Mesa falls back to llvmpipe, which is what an ordinary WSLg 16# session on this host gets (measured 2026-09-20, mesen 2.1.1; the same 17# build renders correctly with the two variables the devshell sets — 18# see `flake.nix`). The software renderer works in both cases. 19# - `Debug.ScriptWindow.AllowIoOsAccess` / `AllowNetworkAccess` gate 20# LuaSocket, default false, and have no switch at all 21# (UI/Config/Debugger/ScriptWindowConfig.cs:29-30). Network needs I/O 22# on as well (Core/Debugger/ScriptingContext.cpp:73). 23# 24# THE SEED, when no settings file exists yet 25# 26# - `ConfigUpgrade = 1` is `ConfigUpgradeHint.FirstRun`, the only value 27# for which `Configuration.InitializeDefaults` populates controller 28# mappings. A bare `{}` deserialises as 0 (Uninitialized) and leaves 29# every port UNMAPPED. 30# - A settings file existing at all is what suppresses the first-run 31# wizard — including under `--testRunner`, which otherwise hangs on it 32# (UI/Program.cs:56-76). 33# 34# Mesen writes the file with a UTF-8 BOM, which jq rejects, hence the sed. 35{ 36 lib, 37 writeShellApplication, 38 writeText, 39 mesen, 40 jq, 41 gnused, 42 coreutils, 43 # Attrset deep-merged over settings.json before every launch. 44 enforce, 45}: 46let 47 enforced = writeText "mesen-enforced.json" (builtins.toJSON enforce); 48 seed = writeText "mesen-seed.json" ( 49 builtins.toJSON ( 50 lib.recursiveUpdate { 51 ConfigUpgrade = 1; 52 DefaultKeyMappings = "Xbox, ArrowKeys"; 53 } enforce 54 ) 55 ); 56in 57writeShellApplication { 58 # Same name as the upstream binary, which is deliberately NOT also put on 59 # PATH: there is then no unwrapped `Mesen` to launch by mistake. 60 name = "Mesen"; 61 runtimeInputs = [ 62 jq 63 gnused 64 coreutils 65 ]; 66 text = '' 67 settings="''${XDG_CONFIG_HOME:-$HOME/.config}/Mesen2/settings.json" 68 strip_bom() { sed '1s/^\xEF\xBB\xBF//' "$settings"; } 69 70 if [ ! -s "$settings" ]; then 71 mkdir -p "$(dirname "$settings")" 72 cat ${seed} > "$settings" 73 elif ! strip_bom | jq -e --slurpfile e ${enforced} '(. * $e[0]) == .' > /dev/null; then 74 tmp="$(mktemp "$settings.XXXXXX")" 75 trap 'rm -f "$tmp"' EXIT 76 strip_bom | jq --slurpfile e ${enforced} '. * $e[0]' > "$tmp" 77 mv "$tmp" "$settings" 78 trap - EXIT 79 fi 80 81 exec ${mesen}/bin/Mesen "$@" 82 ''; 83}