This commit is contained in:
sinavir 2023-12-30 18:38:14 +01:00
commit d454340787
18 changed files with 703 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/nixos.qcow2
/emulationstation-source

5
build.sh Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
RESULT=$(nix-build -E 'import ((import ./npins).nixpkgs + "/nixos")' -A config.system.build.vm -I nixos-config=configuration.nix --no-out-link --option substituters "" $@)
$RESULT/bin/run-nixos-vm

38
configuration.nix Normal file
View file

@ -0,0 +1,38 @@
({ modulesPath, config, lib, pkgs, ... }: {
imports = [
./modules/es-config.nix
"${modulesPath}/profiles/qemu-guest.nix"
./games.nix
];
system.stateVersion = "23.11";
boot.loader.systemd-boot.enable = true;
environment.systemPackages = [
pkgs.xorg.xorgserver
pkgs.xorg.xinit
pkgs.xorg.xf86inputevdev
pkgs.xorg.xf86inputsynaptics
pkgs.xorg.xf86inputlibinput
pkgs.xorg.xf86videointel
pkgs.xorg.xf86videoati
pkgs.xorg.xf86videonouveau
];
programs.bash.loginShellInit =
"startx ${config.retronix.emulationstation.cli}";
hardware.opengl.enable = true;
hardware.opengl.driSupport = true;
services.xserver.enable = true;
services.xserver.displayManager.startx.enable = true;
services.xserver.libinput.enable = true;
users.users.arcade = {
isNormalUser = true;
home = "/home/arcade";
};
services.getty.autologinUser = "arcade";
console.keyMap = "fr";
})

61
es_input.cfg Normal file
View file

@ -0,0 +1,61 @@
<?xml version="1.0"?>
<inputList>
<inputConfig type="keyboard" deviceName="Keyboard" deviceGUID="-1">
<input name="left" type="key" id="1073741904" value="1"/>
<input name="right" type="key" id="1073741903" value="1"/>
<input name="pagedown" type="key" id="1073741921" value="1"/>
<input name="down" type="key" id="1073741905" value="1"/>
<input name="pageup" type="key" id="1073741919" value="1"/>
<input name="y" type="key" id="1073741916" value="1"/>
<input name="b" type="key" id="1073741914" value="1"/>
<input name="a" type="key" id="1073741918" value="1"/>
<input name="up" type="key" id="1073741906" value="1"/>
<input name="select" type="key" id="32" value="1"/>
<input name="start" type="key" id="13" value="1"/>
<input name="leftanalogdown" type="key" id="115" value="1"/>
<input name="leftanalogleft" type="key" id="100" value="1"/>
<input name="rightanalogdown" type="key" id="106" value="1"/>
<input name="rightanalogleft" type="key" id="104" value="1"/>
<input name="rightanalogup" type="key" id="117" value="1"/>
<input name="rightanalogright" type="key" id="108" value="1"/>
<input name="x" type="key" id="1073741920" value="1"/>
<input name="leftanalogright" type="key" id="107" value="1"/>
<input name="leftanalogup" type="key" id="113" value="1"/>
</inputConfig>
<inputConfig type="joystick" deviceName="PDP CO.,LTD. Faceoff Deluxe Wired Pro Controller for Nintendo Switch" deviceGUID="030000006f0e00008101000011010000">
<input name="rightanalogdown" type="axis" id="3" value="1"/>
<input name="left" type="hat" id="0" value="8"/>
<input name="rightanalogleft" type="axis" id="2" value="-1"/>
<input name="right" type="hat" id="0" value="2"/>
<input name="pagedown" type="button" id="5" value="1"/>
<input name="down" type="hat" id="0" value="4"/>
<input name="rightanalogup" type="axis" id="3" value="-1"/>
<input name="pageup" type="button" id="4" value="1"/>
<input name="rightanalogright" type="axis" id="2" value="1"/>
<input name="y" type="button" id="0" value="1"/>
<input name="x" type="button" id="3" value="1"/>
<input name="b" type="button" id="1" value="1"/>
<input name="a" type="button" id="2" value="1"/>
<input name="up" type="hat" id="0" value="1"/>
<input name="select" type="button" id="8" value="1"/>
<input name="start" type="button" id="9" value="1"/>
<input name="leftanalogright" type="axis" id="0" value="1"/>
<input name="leftanalogup" type="axis" id="1" value="-1"/>
<input name="leftanalogdown" type="axis" id="1" value="1"/>
<input name="leftanalogleft" type="axis" id="0" value="-1"/>
</inputConfig>
<inputConfig type="joystick" deviceName="Xinmotek Dual Controller" deviceGUID="03000000c0160000e105000001010000">
<input name="left" type="axis" id="0" value="-1"/>
<input name="right" type="axis" id="0" value="1"/>
<input name="down" type="axis" id="1" value="1"/>
<input name="pageup" type="button" id="5" value="1"/>
<input name="y" type="button" id="1" value="1"/>
<input name="x" type="button" id="4" value="1"/>
<input name="b" type="button" id="3" value="1"/>
<input name="a" type="button" id="0" value="1"/>
<input name="up" type="axis" id="1" value="-1"/>
<input name="select" type="button" id="8" value="1"/>
<input name="start" type="button" id="6" value="1"/>
<input name="pagedown" type="button" id="2" value="1"/>
</inputConfig>
</inputList>

25
games.nix Normal file
View file

@ -0,0 +1,25 @@
{
retronix.emulationstation = {
systems = {
"Arcade" = {
command = "bash -c 'echo $XINIT'";
games = [
{
src = ./emulationstation/roms/Arcade/atetris.zip;
filename = "atetris.zip";
name = "Tetris";
desc = "Tetris, le classique des jeux de puzzle que tout le monde connait (et auquel tout le monde joue quand on met les affaires dans le coffre en partant en vacances). Tetris est devenu un phénomène mondial lorsqu'il a été vendu en pack avec l'achat de la Gameboy originale, en 1989. L'OST est célèbre, avec ses origines classiques russes qui sont maintenant célèbres. ";
rating = "0.9";
releasedate = "19880101T000000";
developer = "bootleg";
publisher = "Atari";
genre = "Puzzle-Game";
players = "1-2";
}
];
};
};
inputCfg = builtins.readFile ./es_input.cfg;
};
}

189
modules/es-config.nix Normal file
View file

@ -0,0 +1,189 @@
{ lib, config, pkgs, ... }:
let
t = v: builtins.trace v v;
filterAttrs' = l: lib.filterAttrs (v: _: builtins.elem v l);
cfg = config.retronix.emulationstation;
attrToXml = attrs: lib.concatLines (lib.mapAttrsToList (k: v: "<${k}>${builtins.toString v}</${k}>") attrs);
gameTemplate = args: let
attr = [
"path"
"name"
"desc"
"image"
"thumbnail"
"video"
"rating"
"releasedate"
"developer"
"publisher"
"genre"
"players"
"sortname"
];
in ''
<game>
${attrToXml (filterAttrs' attr args)}
</game>
'';
systemTemplate = args: let
attr = [
"name"
"fullname"
"path"
"extension"
"command"
"platform"
"theme"
];
in ''
<system>
${attrToXml (filterAttrs' attr args)}
</system>
'';
mkSystemPath = name: games: let
gamelist = ''
<?xml version="1.0"?>
<gameList>
${lib.concatLines (builtins.map gameTemplate games)}
</gameList>
'';
symlinkCommands = builtins.map (
v: "ln -s ${v.src} $out/${v.filename}"
) games;
in pkgs.runCommand "${name}-roms" {
inherit gamelist symlinkCommands;
passAsFile = [
"gamelist"
];
} ''
mkdir -p $out
cp $gamelistPath $out/gamelist.xml
substituteInPlace $out/gamelist.xml --subst-var out
${lib.concatLines symlinkCommands}
'';
sectionOptions = lib.types.submodule ({config, name, ...}: {
freeformType = with lib.types; attrsOf str;
options = {
name = lib.mkOption {
type = lib.types.str;
default = name;
};
fullname = lib.mkOption {
type = lib.types.str;
default = name;
};
path = lib.mkOption { # TODO: check it works
type = lib.types.path;
};
command = lib.mkOption {
type = lib.types.str;
default = "echo \"Hello world !\"";
};
extension = lib.mkOption {
type = lib.types.str;
default = ".zip";
};
games = lib.mkOption {
type = lib.types.listOf gameOptions;
description = "Takes attributes of `games` tag in gamelist.xml";
};
};
config = {
path = mkSystemPath config.name config.games;
};
});
gameOptions = lib.types.submodule ({config, name, ... }: {
freeformType = with lib.types; attrsOf str;
options = {
src = lib.mkOption {
type = lib.types.path;
};
name = lib.mkOption {
type = lib.types.str;
};
desc = lib.mkOption {
type = lib.types.str;
default = "";
};
path = lib.mkOption {
type = lib.types.str;
default = "@out@/${config.filename}";
};
filename = lib.mkOption {
type = lib.types.str;
};
};
});
in {
options = {
retronix.emulationstation = {
systems = lib.mkOption {
type = lib.types.attrsOf (sectionOptions);
default = {};
};
systemCfgFile = lib.mkOption {
type = lib.types.path;
};
inputCfgFile = lib.mkOption {
type = lib.types.path;
};
systemsDir = lib.mkOption {
internal = true;
type = lib.types.path;
description = "Directory containing the symlinks to roms and gamelists.xml files";
};
themesDir = lib.mkOption {
type = lib.types.path;
};
inputCfg = lib.mkOption {
type = lib.types.str;
default = "";
};
homeDir = lib.mkOption {
internal = true;
type = lib.types.path;
};
extraConfigFiles = lib.mkOption {
type = with lib.types; listOf path;
default = [];
description = ''
Must be store paths.
Directory structure will be merged.
Must contain .emulationstation folder
'';
};
cli = lib.mkOption {
type = lib.types.str;
};
};
};
config = {
retronix.emulationstation = {
systemCfgFile = writeESDir "es_systems.cfg" ''
<?xml version="1.0"?>
<systemList>
${lib.concatLines (lib.mapAttrsToList (_: systemTemplate) cfg.systems)}
</systemList>
'';
inputCfgFile = lib.mkDefault (writeTextDir "es_input.cfg" cfg.inputCfg);
homeDir = pkgs.symlinkJoinSubDir {
name = "es-config-dir";
paths = [
cfg.systemCfgFile
cfg.inputCfgFile
] ++ cfg.extraConfigFiles;
};
cli = t "${pkgs.emulationstation}/bin/emulationstation --home ${cfg.homeDir}";
};
};
}

47
npins/default.nix Normal file
View file

@ -0,0 +1,47 @@
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
mkSource = spec:
assert spec ? type; let
path =
if spec.type == "Git" then mkGitSource spec
else if spec.type == "GitRelease" then mkGitSource spec
else if spec.type == "PyPi" then mkPyPiSource spec
else if spec.type == "Channel" then mkChannelSource spec
else builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = path; };
mkGitSource = { repository, revision, url ? null, hash, ... }:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null then
(builtins.fetchTarball {
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
})
else assert repository.type == "Git"; builtins.fetchGit {
url = repository.url;
rev = revision;
# hash = hash;
};
mkPyPiSource = { url, hash, ... }:
builtins.fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource = { url, hash, ... }:
builtins.fetchTarball {
inherit url;
sha256 = hash;
};
in
if version == 3 then
builtins.mapAttrs (_: mkSource) data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"

11
npins/sources.json Normal file
View file

@ -0,0 +1,11 @@
{
"pins": {
"nixpkgs": {
"type": "Channel",
"name": "nixpkgs-unstable",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-24.05pre558296.e97b3e4186bc/nixexprs.tar.xz",
"hash": "1vjj32q29irc02f7g9iwqz0xdmkgkzg5xjqsj0w4556awb8cq7la"
}
},
"version": 3
}

4
pkgs/default.nix Normal file
View file

@ -0,0 +1,4 @@
{ nixpkgs ? (import ../npins).nixpkgs
, pkgs ? import nixpkgs {}
}:
pkgs.extend (import ./overlay.nix)

View file

@ -0,0 +1,21 @@
diff --git a/nix/store/mkcc7j9r5a9cb45zvb25fa4vgks10z21-source/es-core/src/Log.cpp b/./es-core/src/Log.cpp
index bcae384..5b8426a 100644
--- a/nix/store/mkcc7j9r5a9cb45zvb25fa4vgks10z21-source/es-core/src/Log.cpp
+++ b/./es-core/src/Log.cpp
@@ -53,6 +53,7 @@ void Log::flush()
void Log::close()
{
+ if(file == NULL) return;
fclose(file);
file = NULL;
}
@@ -69,7 +70,7 @@ Log::~Log()
if(getOutput() == NULL)
{
// not open yet, print to stdout
- std::cerr << "ERROR - tried to write to log file before it was open! The following won't be logged:\n";
+ // std::cerr << "ERROR - tried to write to log file before it was open! The following won't be logged:\n";
std::cerr << os.str();
return;
}

47
pkgs/joy2key.nix Normal file
View file

@ -0,0 +1,47 @@
{ lib
, joy2keyd
, writeShellApplication
}:
writeShellApplication {
name = "joy2key";
runtimeInputs = [ joy2keyd ];
text = ''
mode="$1"
[[ -z "$mode" ]] && mode="start"
shift
# allow overriding joystick device via __joy2key_dev env
# (by default will use /dev/input/jsX which will scan all)
device="/dev/input/jsX"
[[ -n "$__joy2key_dev" ]] && device="$__joy2key_dev"
params=("$@")
if [[ "''${#params[@]}" -eq 0 ]]; then
# Default button-to-keyboard mappings:
# * cursor keys for axis/dpad
# * enter, space, esc and tab for buttons 'a', 'b', 'x' and 'y'
# * page up/page down for buttons 5,6 (shoulder buttons)
params=(kcub1 kcuf1 kcuu1 kcud1 0x0a 0x20 0x1b 0x09 kpp knp)
fi
function kill_deamon() {
pkill -f joy2keyd
sleep 1
}
case "$mode" in
start)
if pgrep -f "joy2keyd" &>/dev/null; then
kill_deamon
fi
joy2keyd "$device" "''${params[@]}" || exit 1
;;
stop)
kill_deamon
;;
esac
exit 0
'';
}

36
pkgs/joy2keyd/default.nix Normal file
View file

@ -0,0 +1,36 @@
{ lib
, buildPythonApplication
, retropieSetup
, hatchling
, pysdl2
}:
buildPythonApplication {
pname = "joykeyd";
version = "4.8";
pyproject = true;
unpackPhase = ''
mkdir source
cp ${./pyproject.toml} source/pyproject.toml
cp ${retropieSetup}/scriptmodules/admin/joy2key/joy2key_sdl.py source/
cp ${retropieSetup}/LICENSE.md source/
cd source
'';
patchPhase = ''
substituteAllInPlace pyproject.toml
substituteInPlace joy2key_sdl.py --replace "CONFIG_DIR = '/opt/retropie/configs'" $'import os\nCONFIG_DIR = os.getenv("JOY2KEY_CONFIG_DIR", "/etc/joy2key")'
#TODO provide default config files
#TODO finer config (JS_CFG_DIR and RETROARCH_CONFIG)
'';
nativeBuildInputs = [
hatchling
];
propagatedBuildInputs = [
pysdl2
];
}

View file

@ -0,0 +1,26 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "joy2key_sdl"
version = "4.8"
dependencies = [
"PySDL2",
]
requires-python = ">=3.8"
description = "Command line joystick to keyboard translator, using SDL2 for event handling"
license = {file = "LICENSE.md"}
classifiers = [
"Programming Language :: Python"
]
[project.urls]
Repository = "https://github.com/RetroPie/RetroPie-Setup"
[project.scripts]
joy2keyd = "joy2key_sdl:main"
[tool.hatch.build]
include=["joy2key_sdl.py"]

32
pkgs/overlay.nix Normal file
View file

@ -0,0 +1,32 @@
final: prev: {
joy2keyd = final.python3.pkgs.callPackage ./joy2keyd {};
retropieSetup = final.callPackage ./retropie-setup.nix {};
joy2key = final.callPackage ./joy2key.nix {};
runcommand = final.callPackage ./runcommand.nix {};
emulationstationPatched = final.emulationStation.overrideAttrs (_: previous: {
patches = previous.patches ++ [ ./emulationstation.patch ];
});
symlinkJoinSubdir =
args_@{ name
, paths
, subdir ? ""
, preferLocalBuild ? true
, allowSubstitutes ? false
, postBuild ? ""
, ...
}:
let
args = removeAttrs args_ [ "name" "postBuild" ]
// {
inherit preferLocalBuild allowSubstitutes;
passAsFile = [ "paths" ];
}; # pass the defaults
in final.runCommand name args
''
mkdir -p $out
for i in $(cat $pathsPath); do
${final.xorg.lndir}/bin/lndir -silent $i $out/${subdir}
done
${postBuild}
'';
}

1
pkgs/result Symbolic link
View file

@ -0,0 +1 @@
/nix/store/vcx21sz1cq4rp0avi81iblbxhfm3pjfc-emulationstation-2.11.2

10
pkgs/retropie-setup.nix Normal file
View file

@ -0,0 +1,10 @@
{
fetchFromGitHub,
}:
fetchFromGitHub {
name = "Retropie-Setup";
owner = "RetroPie";
repo = "RetroPie-Setup";
rev = "4.8";
hash = "sha256-3kVxA6y+jXOP+bF/KmHHoYjDkw2rh+qV9DIcelOd4+E=";
}

52
pkgs/runcommand.nix Normal file
View file

@ -0,0 +1,52 @@
{ lib
, stdenv
, writeText
, feh
, joy2key
, xrandr
, mesa
, retropieSetup
}:
stdenv.mkDerivation {
name = "runcommand";
src = retropieSetup;
patches = [
./runcommand.patch
];
dontBuild = true;
installPhase = ''
mkdir -p $out/bin
mkdir -p $out/lib
cp scriptmodules/supplementary/runcommand/runcommand.sh $out/bin
cp scriptmodules/inifuncs.sh $out/lib
export inifunc="$out/lib/inifuncs.sh"
substituteInPlace $out/bin/runcommand.sh --replace '$CONFIGDIR/all/runcommand-menu' '@runcommandUserMenu@'
substituteAllInPlace $out/bin/runcommand.sh
wrapProgram $out/bin/runcommand.sh --prefix PATH : ${lib.makeBinPath [ feh ]}
'';
joy2key = "${joy2key}/bin/joy2key";
modetest = "${mesa}/bin/modetest";
xrandr = "${xrandr}/bin/xrandr";
runcommandCfg = writeText "runcommand.cfg" "";
videomodesCfg = writeText "videomodes.cfg" "";
emulatorsCfg = writeText "emulators.cfg" "";
backendsCfg = writeText "backends.cfg" "";
retronetplayCfg = writeText "retronetplay.cfg" "";
sysCfgDir = null;
runcommandUserMenu = null;
libretroCores = null;
imagesDir = "/dev/null";
runcommandLaunchDialogRc = writeText "dialogrc.cfg" "";
userscripts = "/dev/null";
}

96
pkgs/runcommand.patch Normal file
View file

@ -0,0 +1,96 @@
diff --git a/scriptmodules/supplementary/runcommand/runcommand.sh b/scriptmodules/supplementary/runcommand/runcommand.sh
index d12ef436..217fec7b 100755
--- a/scriptmodules/supplementary/runcommand/runcommand.sh
+++ b/scriptmodules/supplementary/runcommand/runcommand.sh
@@ -77,23 +77,22 @@
## pressed the GUI is shown, where a user can set video modes, default emulators
## and other options (depending what is being launched).
-ROOTDIR="/opt/retropie"
-CONFIGDIR="$ROOTDIR/configs"
+CONFIGDIR="@sysCfgDir@"
LOG="/dev/shm/runcommand.log"
-RUNCOMMAND_CONF="$CONFIGDIR/all/runcommand.cfg"
-VIDEO_CONF="$CONFIGDIR/all/videomodes.cfg"
-EMU_CONF="$CONFIGDIR/all/emulators.cfg"
-BACKENDS_CONF="$CONFIGDIR/all/backends.cfg"
-RETRONETPLAY_CONF="$CONFIGDIR/all/retronetplay.cfg"
-JOY2KEY="$ROOTDIR/admin/joy2key/joy2key"
+RUNCOMMAND_CONF="@runcommandCfg@"
+VIDEO_CONF="@videomodesCfg@"
+EMU_CONF="@emulatorsCfg@"
+BACKENDS_CONF="@backendsCfg@"
+RETRONETPLAY_CONF="@retronetplayCfg@"
+JOY2KEY="@joy2key@"
# modesetting tools
TVSERVICE="/opt/vc/bin/tvservice"
-KMSTOOL="$ROOTDIR/supplementary/mesa-drm/modetest"
-XRANDR="xrandr"
+KMSTOOL="@modetest@"
+XRANDR="@xrandr@"
-source "$ROOTDIR/lib/inifuncs.sh"
+source "@inifunc@"
# disable the `patsub_replacement` shell option, it breaks the string substitution when replacement contains '&'
if shopt -s patsub_replacement 2>/dev/null; then
@@ -1109,8 +1108,8 @@ function retroarch_append_config() {
fi
# set `libretro_directory` to the core parent folder
- local core_dir=$(echo "$COMMAND" | grep -Eo "$ROOTDIR/libretrocores/.*libretro\.so" | head -n 1)
- core_dir=$(dirname "$core_dir")
+ local core_dir
+ core_dir="@libretroCores@"
[[ -n "$core_dir" ]] && iniSet "libretro_directory" "$core_dir"
# if verbose logging is on, set core logging to INFO
@@ -1219,25 +1218,9 @@ function get_sys_command() {
function show_launch() {
local images=()
- if [[ "$IS_SYS" -eq 1 && "$USE_ART" -eq 1 ]]; then
- # if using art look for images in paths for es art.
- images+=(
- "$HOME/RetroPie/roms/$SYSTEM/images/${ROM_BN}-image"
- "$HOME/.emulationstation/downloaded_images/$SYSTEM/${ROM_BN}-image"
- "$HOME/.emulationstation/downloaded_media/$SYSTEM/screenshots/${ROM_BN}"
- "$HOME/RetroPie/roms/$SYSTEM/media/screenshots/${ROM_BN}"
- )
- fi
-
- # look for custom launching images
- if [[ "$IS_SYS" -eq 1 ]]; then
- images+=(
- "$HOME/RetroPie/roms/$SYSTEM/images/${ROM_BN}-launching"
- "$CONF_ROOT/launching"
- )
- fi
- [[ "$IS_PORT" -eq 1 ]] && images+=("$CONFIGDIR/ports/launching")
- images+=("$CONFIGDIR/all/launching")
+ images+=(
+ "@imagesDir@/$SYSTEM/${ROM_BN}"
+ )
local image
local path
@@ -1267,7 +1250,7 @@ function show_launch() {
else
launch_name="$EMULATOR"
fi
- DIALOGRC="$CONFIGDIR/all/runcommand-launch-dialog.cfg" dialog --infobox "\nLaunching $launch_name ...\n\nPress a button to configure\n\nErrors are logged to $LOG" 9 60
+ DIALOGRC="@runcommandLaunchDialogCfg@" dialog --infobox "\nLaunching $launch_name ...\n\nPress a button to configure\n\nErrors are logged to $LOG" 9 60
fi
}
@@ -1288,7 +1271,7 @@ function check_menu() {
# calls script with parameters SYSTEM, EMULATOR, ROM, and commandline
function user_script() {
- local script="$CONFIGDIR/all/$1"
+ local script="@userscripts@/$1"
if [[ -f "$script" ]]; then
bash "$script" "$SYSTEM" "$EMULATOR" "$ROM" "$COMMAND" </dev/tty 2>>"$LOG"
fi