infrastructure/modules/nixos/steam/beatsaber.nix
2024-05-13 15:31:34 -07:00

1053 lines
30 KiB
Nix

{
config,
pkgs,
lib,
inputs,
...
}: let
inherit (inputs.self.lib.lib) userIs;
inherit (config.lib.steam) mkSharePath;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault;
inherit (lib.strings) hasSuffix replaceStrings optionalString concatStringsSep escapeShellArg makeBinPath versionOlder;
inherit (lib.attrsets) attrValues filterAttrs mapAttrs mapAttrs' mapAttrsToList listToAttrs nameValuePair;
inherit (lib.lists) concatLists head last filter sort singleton;
inherit (config.services.steam) accountSwitch;
cfg = config.services.steam.beatsaber;
sortedVersions = sort (a: b: versionOlder a.version b.version) (attrValues cfg.versions);
prevVersionFor = version: let
olderVersions = filter (v: versionOlder v.version version) sortedVersions;
in
if olderVersions != []
then last olderVersions
else null;
versionModule = {
config,
name,
...
}: {
options = with lib.types; {
version = mkOption {
type = str;
default = name;
};
previousVersion = mkOption {
type = nullOr str;
};
__toString = mkOption {
type = functionTo str;
};
};
config = {
__toString = mkOptionDefault (
config: config.version
);
previousVersion = mkOptionDefault (
prevVersionFor config.version
);
};
};
fileModule = {
config,
name,
...
}: {
options = with lib.types; {
relativePath = mkOption {
type = str;
default = name;
};
type = mkOption {
type = enum ["file" "directory"];
default = "file";
};
versioned = mkOption {
type = bool;
default = false;
};
target = mkOption {
type = enum ["user" "shared" "game"];
default = "user";
};
mode = {
file = mkOption {
type = str;
default =
if hasSuffix ".exe" config.relativePath || hasSuffix ".dll" config.relativePath
then "775"
else "664";
};
dir = mkOption {
type = str;
default = "2775";
};
};
ownerFor = mkOption {
type = functionTo str;
};
srcPathFor = mkOption {
type = functionTo path;
};
srcStyle = mkOption {
type = enum ["empty" "copy" "symlink" "symlink-shallow"];
default = "symlink";
};
workingPathFor = mkOption {
type = functionTo path;
};
init = mkOption {
type = nullOr path;
};
initFor = mkOption {
type = functionTo (nullOr path);
};
initStyle = mkOption {
type = enum ["none" "copy" "symlink" "symlink-shallow"];
default = "copy";
};
setup = {
shared = mkOption {
type = functionTo lines;
internal = true;
};
script = mkOption {
type = functionTo lines;
internal = true;
};
};
};
config = let
versionPathFor = version: optionalString config.versioned "/${version}";
in {
init = mkOptionDefault (
if config.target == "game"
then null
else if config.type == "directory"
then "${emptyDir}"
else if hasSuffix ".json" config.relativePath
then "${emptyJson}"
else if hasSuffix ".dll" config.relativePath || hasSuffix ".exe" config.relativePath
then "${emptyExecutable}"
else "${emptyFile}"
);
initFor = mkOptionDefault (
{
user,
version,
}:
config.init
);
ownerFor = mkOptionDefault (
user:
if config.target == "user"
then user
else "admin"
);
srcPathFor = mkOptionDefault (
{
user,
version,
}:
{
shared = cfg.sharedDataDir + versionPathFor version;
user = cfg.dataDirFor user + versionPathFor version;
game = cfg.gameDirFor version;
}
.${config.target}
or (throw "unsupported target")
+ "/${config.relativePath}"
);
workingPathFor = mkOptionDefault (
{
user,
version,
}:
cfg.workingDirFor {inherit user version;}
+ "/${config.relativePath}"
);
# TODO: setup.shared and do inits seperately!
setup.script = {
user,
version,
} @ args: let
owner = config.ownerFor user;
srcPath = config.srcPathFor args;
workingPath = config.workingPathFor args;
initPath = config.initFor args;
parentWorkingPath = dirOf workingPath;
mkdir = dest: ''
if [[ -L ${escapeShellArg dest} ]]; then
rm -f ${escapeShellArg dest}
fi
if [[ ! -d ${escapeShellArg dest} ]]; then
mkdir -m${config.mode.dir} ${escapeShellArg dest}
else
chmod ${config.mode.dir} ${escapeShellArg dest}
fi
chown ${owner}:${cfg.group} ${escapeShellArg dest}
'';
mkStyle = {
style,
src,
}:
if
style
!= "none"
&& src
== {
file = "${emptyFile}";
directory = "${emptyDir}";
}
.${config.type}
then "empty"
else style;
doInit = {
style,
src,
dest,
}:
{
none = "true";
copy =
{
file = ''
if [[ -L ${escapeShellArg dest} ]]; then
rm -f ${escapeShellArg dest}
elif [[ -e ${escapeShellArg dest} ]]; then
echo ERR: something is in the way of copying ${escapeShellArg dest} >&2
exit 1
fi
cp -TP --no-preserve=all ${escapeShellArg src} ${escapeShellArg dest}
chmod ${config.mode.file} ${escapeShellArg dest}
chown ${owner}:${cfg.group} ${escapeShellArg dest}
'';
directory = ''
${mkdir dest}
cp -rTP --no-preserve=all ${escapeShellArg src} ${escapeShellArg dest}
chown -R ${owner}:${cfg.group} ${escapeShellArg dest}
find ${escapeShellArg dest} -type f -exec chmod -m${config.mode.file} "{}" \;
'';
}
.${config.type};
empty =
{
directory = ''
${mkdir dest}
'';
file = ''
touch ${escapeShellArg dest}
chmod ${config.mode.file} ${escapeShellArg dest}
chown ${owner}:${cfg.group} ${escapeShellArg dest}
'';
}
.${config.type};
symlink = ''
if [[ -e ${escapeShellArg dest} && ! -L ${escapeShellArg dest} ]]; then
echo ERR: something is in the way of linking ${escapeShellArg dest} >&2
exit 1
fi
ln -sfT ${escapeShellArg src} ${escapeShellArg dest}
'';
symlink-shallow =
{
directory = ''
${mkdir dest}
ln -sf ${escapeShellArg src}/* ${escapeShellArg dest}/
'';
}
.${config.type};
}
.${mkStyle {inherit style src;}};
doSetup = {
style,
src,
dest,
}:
rec {
none = "true";
copy =
{
file = ''
${empty}
'';
directory = ''
${empty}
if [[ ${escapeShellArg dest}/* != ${escapeShellArg dest}/\* ]]; then
chmod -m${config.mode.file} ${escapeShellArg dest}/*
fi
'';
}
.${config.type};
empty =
{
directory = ''
chmod ${config.mode.dir} ${escapeShellArg dest}
chown ${owner}:${cfg.group} ${escapeShellArg dest}
'';
file = ''
chmod ${config.mode.file} ${escapeShellArg dest}
chown ${owner}:${cfg.group} ${escapeShellArg dest}
'';
}
.${config.type};
symlink = "true";
symlink-shallow =
{
directory = ''
${mkdir.directory}
'';
}
.${config.type};
}
.${mkStyle {inherit style src;}};
init = doInit {
style = config.initStyle;
src = initPath;
dest = srcPath;
};
setup = doSetup {
style = config.initStyle;
src = initPath;
dest = srcPath;
};
src = doInit {
style = config.srcStyle;
src = srcPath;
dest = workingPath;
};
checkFlag =
{
file =
{
none = "e";
copy = "f";
symlink = "L";
}
.${config.initStyle};
directory =
{
none = "e";
copy = "d";
symlink-shallow = "d";
symlink = "L";
}
.${config.initStyle};
}
.${config.type};
checkParent = ''
if [[ ! -d ${escapeShellArg parentWorkingPath} ]]; then
echo ERR: parent of ${escapeShellArg workingPath} does not exist >&2
exit 1
fi
'';
check =
if initPath != null
then ''
if [[ ! -${checkFlag} ${escapeShellArg srcPath} ]]; then
${init}
else
${setup}
fi
''
else ''
if [[ ! -${checkFlag} ${escapeShellArg srcPath} ]]; then
echo ERR: src ${escapeShellArg srcPath} for ${escapeShellArg workingPath} does not exist >&2
exit 1
fi
'';
in ''
${checkParent}
${check}
${src}
'';
};
};
userModule = {
config,
name,
...
}: {
options = with lib.types; {
name = mkOption {
type = str;
default = name;
};
preferredVersion = mkOption {
type = str;
default = cfg.defaultVersion;
};
};
};
emptyFile = pkgs.writeText "empty.txt" "";
emptyJson = pkgs.writeText "empty.json" "{}";
emptyDir = pkgs.runCommand "empty" {} ''
mkdir $out
'';
emptyExecutable = pkgs.writeTextFile {
name = "empty.exe";
executable = true;
text = "";
};
bsdata = "Beat Saber_Data";
vars = let
bsUserData = mkSharePath (cfg.dataDirFor "%GENSO_STEAM_USER%");
bsWorkingData = mkSharePath (cfg.workingDirFor {
user = "%GENSO_STEAM_USER%";
version = "%GENSO_STEAM_BS_VERSION%";
});
in ''
if "%GENSO_STEAM_MACHINE%" == "" set "GENSO_STEAM_MACHINE=%COMPUTERNAME%"
if "%GENSO_STEAM_LOCAL_DATA%" == "" set "GENSO_STEAM_LOCAL_DATA=C:\Program Files\GensokyoZone"
if "%GENSO_STEAM_LOCAL_DATA_BS%" == "" set "GENSO_STEAM_LOCAL_DATA_BS=%GENSO_STEAM_LOCAL_DATA%\${cfg.dirName}"
if "%GENSO_STEAM_INSTALL%" == "" set "GENSO_STEAM_INSTALL=C:\Program Files (x86)\Steam"
if "%GENSO_STEAM_LIBRARY_BS%" == "" set "GENSO_STEAM_LIBRARY_BS=%GENSO_STEAM_INSTALL%"
if "%GENSO_STEAM_BS_VERSION%" == "" set "GENSO_STEAM_BS_VERSION=${cfg.defaultVersion}"
if "%GENSO_SMB_HOST%" == "" set "GENSO_SMB_HOST=smb.${config.networking.domain}"
if "%GENSO_SMB_SHARED_MOUNT%" == "" set "GENSO_SMB_SHARED_MOUNT=\\%GENSO_SMB_HOST%\shared"
set "STEAM_BS_LIBRARY=%GENSO_STEAM_LIBRARY_BS%\steamapps\common\Beat Saber"
set "STEAM_BS_APPDATA=%USERPROFILE%\AppData\LocalLow\Hyperbolic Magnetism\Beat Saber"
set "STEAM_BS_LOCAL_DATA=%GENSO_STEAM_LOCAL_DATA_BS%\%GENSO_STEAM_BS_VERSION%"
set "STEAM_BS_USER_DATA=${bsUserData}"
set "STEAM_BS_WORKING_DATA=${bsWorkingData}"
if "%GENSO_STEAM_BS_LOCAL%" == "1" (
set "STEAM_BS_LAUNCH=%STEAM_BS_LOCAL_DATA%"
set "STEAM_BS_LAUNCH_APPDATA=%STEAM_BS_LOCAL_DATA%\AppData"
) else (
set "STEAM_BS_LAUNCH=%STEAM_BS_WORKING_DATA%"
set "STEAM_BS_LAUNCH_APPDATA=%STEAM_BS_USER_DATA%\AppData"
)
if "%GENSO_STEAM_USER%" == "" goto NOUSER
'';
eof = ''
goto:eof
:NOUSER
echo no steam user set
'';
mount = ''
rmdir "%STEAM_BS_APPDATA%"
mklink /D "%STEAM_BS_APPDATA%" "%STEAM_BS_LAUNCH_APPDATA%"
rmdir "%STEAM_BS_LIBRARY%"
mklink /D "%STEAM_BS_LIBRARY%" "%STEAM_BS_LAUNCH%"
'';
launch =
''
cd /d "%STEAM_BS_LIBRARY%"
''
+ ''"%STEAM_BS_LIBRARY%\Beat Saber.exe"'';
setup = ''
rmdir "%STEAM_BS_APPDATA%"
rmdir "%STEAM_BS_LIBRARY%"
mkdir "%GENSO_STEAM_LOCAL_DATA_BS%"
move /Y "%STEAM_BS_LIBRARY%" "%GENSO_STEAM_LOCAL_DATA_BS%\Vanilla"
move /Y "%STEAM_BS_APPDATA%" "%GENSO_STEAM_LOCAL_DATA_BS%\Vanilla\AppData"
'';
mountbeatsaber = ''
${vars}
${mount}
${eof}
'';
launchbeatsaber = ''
${vars}
${mount}
${launch}
${eof}
'';
fpfcbeatsaber = ''
${vars}
${launch} fpfc
${eof}
'';
setupbeatsaber = ''
${vars}
${setup}
pause
${eof}
'';
localbeatsaber-mount = ''
set GENSO_STEAM_BS_LOCAL=1
${vars}
${mount}
if NOT "%GENSO_STEAM_BS_VERSION%" == "Vanilla" (
rmdir "%STEAM_BS_LIBRARY%\UserData"
mklink /D "%STEAM_BS_LIBRARY%\UserData" "%STEAM_BS_WORKING_DATA%\UserData"
)
${eof}
'';
localbeatsaber-vanilla = ''
set GENSO_STEAM_BS_VERSION=Vanilla
set GENSO_STEAM_BS_LOCAL=1
${vars}
${mount}
${eof}
'';
localbeatsaber-launch = ''
set GENSO_STEAM_BS_LOCAL=1
${vars}
${mount}
${launch}
${eof}
'';
beatsaber-user = {
user,
version,
}: ''
set GENSO_STEAM_USER=${user}
set GENSO_STEAM_BS_VERSION=${version}
${vars}
${mount}
${launch}
${eof}
'';
vanilla = ''
setx GENSO_STEAM_BS_VERSION Vanilla
'';
mksetupbeatsaber = {
user,
version,
}: let
setupFiles = mapAttrsToList (_: file: file.setup.script {inherit user version;}) cfg.files;
in
pkgs.writeShellScript "setupbeatsaber-${user}-${version}" ''
set -eu
export PATH="$PATH:${makeBinPath [pkgs.coreutils]}"
${concatStringsSep "\n" setupFiles}
'';
in {
options.services.steam.beatsaber = with lib.types; {
enable = mkEnableOption "beatsaber scripts";
setup =
mkEnableOption "beatsaber data"
// {
default = accountSwitch.setup;
};
group = mkOption {
type = str;
default = "beatsaber";
};
defaultVersion = mkOption {
type = str;
};
versions = mkOption {
type = attrsOf (submodule versionModule);
default = {};
};
setupServiceNames = mkOption {
type = listOf str;
readOnly = true;
};
files = mkOption {
type = attrsOf (submodule fileModule);
default = {};
};
users = mkOption {
type = attrsOf (submodule userModule);
};
dirName = mkOption {
type = str;
default = "BeatSaber";
};
binDir = mkOption {
type = path;
default = accountSwitch.binDir + "/beatsaber";
};
gamesDir = mkOption {
type = path;
default = accountSwitch.gamesDir + "/${cfg.dirName}";
};
gameDirFor = mkOption {
type = functionTo path;
default = version: cfg.gamesDir + "/${version}";
};
sharedDataDir = mkOption {
type = path;
default = accountSwitch.sharedDataDir + "/${cfg.dirName}";
};
userDataDir = mkOption {
type = path;
default = accountSwitch.dataDir;
};
dataDirFor = mkOption {
type = functionTo path;
default = user: cfg.userDataDir + "/${user}/${cfg.dirName}";
};
userWorkingDir = mkOption {
type = path;
default = accountSwitch.workingDir;
};
userWorkingDirFor = mkOption {
type = functionTo path;
default = user: cfg.userWorkingDir + "/${user}/${cfg.dirName}";
};
workingDirFor = mkOption {
type = functionTo path;
default = {
user,
version,
}:
cfg.userWorkingDirFor user + "/${version}";
};
};
config = {
services.steam.beatsaber = let
bsUsers = filterAttrs (_: userIs cfg.group) config.users.users;
allVersions = mapAttrsToList (_: version: version.version) cfg.versions;
gameFiles = {
"Beat Saber.exe" = {};
"UnityCrashHandler64.exe" = {};
"UnityPlayer.dll" = {};
"MonoBleedingEdge".type = "directory";
};
sharedFiles = {
DynamicOpenVR = {
type = "directory";
versioned = true;
};
IPA = {
type = "directory";
versioned = true;
};
Libs = {
type = "directory";
versioned = true;
};
Plugins = {
type = "directory";
versioned = true;
};
Logs = {
type = "directory";
versioned = true;
};
"BeatSaberVersion.txt" = {
versioned = true;
initFor = {version, ...}: pkgs.writeText "BeatSaberVersion-${version}.txt" version;
};
"IPA.exe".versioned = true;
"IPA.exe.config".versioned = true;
"IPA.runtimeconfig.json".versioned = true;
"winhttp.dll".versioned = true;
${bsdata} = {
type = "directory";
versioned = true;
#initStyle = "symlink-shallow";
#initFor = { version, ... }: cfg.gameDirFor version + "/${bsdata}";
initStyle = "none";
srcPathFor = {version, ...}: cfg.gameDirFor version + "/${bsdata}";
srcStyle = "symlink-shallow";
};
"${bsdata}/Managed" = {
type = "directory";
versioned = true;
initFor = {version, ...}: cfg.gameDirFor version + "/${bsdata}/Managed";
};
# TODO: remove this to use multiple folders
"${bsdata}/CustomLevels" = {
type = "directory";
initStyle = "none";
srcPathFor = {...}: cfg.sharedDataDir + "/CustomLevels";
};
CustomAvatars = {
type = "directory";
initStyle = "none";
};
CustomNotes = {
type = "directory";
initStyle = "none";
};
CustomPlatforms = {
type = "directory";
initStyle = "none";
};
CustomSabers = {
type = "directory";
initStyle = "none";
};
CustomWalls = {
type = "directory";
initStyle = "none";
};
Playlists = {
type = "directory";
initStyle = "none";
};
"UserData/ScoreSaber/Replays" = {
type = "directory";
initStyle = "none";
srcPathFor = {...}: cfg.sharedDataDir + "/Replays";
};
"UserData/Beat Saber IPA.json".versioned = true;
"UserData/SongCore/" = {
versioned = true;
relativePath = "UserData/SongCore";
type = "directory";
srcStyle = "empty";
};
"UserData/SongCore/folders.xml" = {
versioned = true;
};
"UserData/SongCore/SongCoreExtraData.dat" = {
versioned = true;
init = "${emptyJson}";
};
"UserData/SongCore/SongDurationCache.dat" = {
versioned = true;
init = "${emptyJson}";
};
"UserData/SongCore/SongHashData.dat" = {
versioned = true;
init = "${emptyJson}";
};
"UserData/Chroma".type = "directory";
"UserData/Nya".type = "directory";
"UserData/SongRankedBadge".type = "directory";
"UserData/DrinkWater".type = "directory";
"UserData/Enhancements".type = "directory";
"UserData/HitScoreVisualizer" = {
type = "directory";
# TODO: initStyle = "symlink"; init = "${myhitscorejsons}";
};
"UserData/Saber Factory/" = {
relativePath = "UserData/Saber Factory";
type = "directory";
srcStyle = "empty";
};
"UserData/Saber Factory/Cache".type = "directory";
"UserData/Saber Factory/Textures".type = "directory";
"UserData/BeatSaverDownloader.ini" = {};
"UserData/BeatSaverUpdater.json" = {};
"UserData/SongDetailsCache.proto".versioned = true;
"UserData/SongDetailsCache.proto.Direct.etag".versioned = true;
};
userFiles = {
"UserData" = {
type = "directory";
versioned = true;
srcStyle = "empty";
};
"UserData/Camera2".type = "directory";
"UserData/Saber Factory" = {
type = "directory";
srcStyle = "empty";
};
"UserData/Saber Factory/Presets".type = "directory";
"UserData/Saber Factory/TrailConfig.json" = {};
"UserData/SongCore" = {
type = "directory";
versioned = true;
srcStyle = "empty";
};
"UserData/SongCore/SongCore.json" = {
versioned = true;
};
"UserData/ScoreSaber" = {
type = "directory";
versioned = true;
srcStyle = "empty";
};
"UserData/ScoreSaber/ScoreSaber.json" = {
versioned = true;
};
"UserData/Disabled Mods.json".versioned = true;
"UserData/modprefs.ini".versioned = true;
"UserData/JDFixer.json".versioned = true;
};
userDataFiles = [
"modprefs.ini"
"Disabled Mods.json"
"AutoPauseStealth.json"
"BeatSaberMarkupLanguage.json"
"BeatSaviorData.ini"
"BetterSongList.json"
"BetterSongSearch.json"
"bookmarkedSongs.json"
"votedSongs.json"
"Chroma.json"
"Cinema.json"
"CountersPlus.json"
"CustomAvatars.CalibrationData.dat"
"CustomAvatars.json"
"CustomNotes.json"
"Custom Platforms.json"
"CustomWalls.json"
"DrinkWater.json"
"EasyOffset.json"
"Enhancements.json"
"FasterScroll.json"
"FastFail.json"
"Fifth Anniversary Mod.json"
"Gotta Go Fast.json"
"Heck.json"
"HitScoreVisualizer.json"
"HitsoundTweaks.json"
"Intro Skip.json"
"JDFixer.json"
"ModelDownloader.json"
"Music Spatializer.json"
"Nya.json"
"ParticleOverdrive.ini"
"PerformanceMeter.json"
"PlayFirst.json"
"PlaylistManager.json"
"PracticePlugin.json"
"Saber Factory.json"
"SaberTailor.json"
"ScorePercentage.json"
"SiraUtil.json"
"SmoothCamPlus.json"
"SongChartVisualizer.json"
"SongPlayData.json"
"SongPlayHistory.json"
"SongRankedBadge.json"
"Technicolor.json"
"Tweaks55.json"
"UITweaks.json"
];
mapSharedFile = file:
file
// {
target = "shared";
};
mapGameFile = file:
file
// {
target = "game";
};
mapUserDataFile = file:
nameValuePair "UserData/${file}" {
target = "user";
};
in {
defaultVersion = mkIf (allVersions != []) (mkOptionDefault (
head allVersions
));
users = mapAttrs (_: user: {name = mkDefault user.name;}) bsUsers;
setupServiceNames = mkOptionDefault (
mapAttrsToList (_: user: "steam-setup-beatsaber-${user.name}.service") cfg.users
);
files = mkMerge [
userFiles
(listToAttrs (map mapUserDataFile userDataFiles))
(mapAttrs (_: mapGameFile) gameFiles)
(mapAttrs (_: mapSharedFile) sharedFiles)
];
};
systemd.services.steam-setup-beatsaber = mkIf cfg.setup {
wantedBy = [
"multi-user.target"
];
after = [
"tmpfiles.service"
];
serviceConfig = {
Type = mkOptionDefault "oneshot";
RemainAfterExit = mkOptionDefault true;
ExecStart = mkMerge (mapAttrsToList (
_: user: (mapAttrsToList (
_: version: "${mksetupbeatsaber {
user = user.name;
inherit (version) version;
}}"
)
cfg.versions)
)
cfg.users);
};
};
services.tmpfiles = let
toplevel = {
owner = mkDefault "admin";
group = mkDefault cfg.group;
mode = mkDefault "3775";
};
shared = {
inherit (toplevel) owner group;
mode = mkDefault "2775";
};
personal = owner: {
inherit owner;
inherit (shared) group mode;
};
bin = {
inherit (toplevel) owner group;
mode = "0755";
type = "copy";
};
sharedFolders = [
"CustomAvatars"
"CustomLevels"
"CustomNotes"
"CustomPlatforms"
"CustomSabers"
"CustomWalls"
"Playlists"
"Replays"
"AppData"
"UserData"
];
setupFiles =
[
{
${cfg.sharedDataDir} = toplevel;
${cfg.binDir} = shared;
}
(listToAttrs (
map (
folder:
nameValuePair "${cfg.sharedDataDir}/${folder}" shared
)
sharedFolders
))
]
++ concatLists (mapAttrsToList (
_: user:
singleton {
${cfg.dataDirFor user.name} = personal user.name;
"${cfg.dataDirFor user.name}/AppData" = personal user.name;
"${cfg.dataDirFor user.name}/UserData" = personal user.name;
}
++ mapAttrsToList (_: version: {
"${cfg.dataDirFor user.name}/${version.version}" = personal user.name;
${cfg.userWorkingDirFor user.name} = personal user.name;
${
cfg.workingDirFor {
user = user.name;
inherit (version) version;
}
} =
personal user.name;
})
cfg.versions
)
cfg.users)
++ mapAttrsToList (_: version: {
"${cfg.sharedDataDir}/${version.version}" = shared;
})
cfg.versions;
versionBinFiles =
mapAttrs' (
_: version:
nameValuePair
"${cfg.binDir}/${replaceStrings ["."] ["_"] version.version}.bat"
{
inherit (bin) owner group mode type;
src = pkgs.writeTextFile {
name = "beatsaber-${version.version}.bat";
executable = true;
text = ''
setx GENSO_STEAM_BS_VERSION ${version.version}
'';
};
}
)
cfg.versions;
userBinFiles =
mapAttrs' (
_: user:
nameValuePair
"${cfg.binDir}/${user.name}.bat"
{
inherit (bin) owner group mode type;
src = pkgs.writeTextFile {
name = "beatsaber-${user.name}.bat";
executable = true;
text = beatsaber-user {
user = user.name;
version = user.preferredVersion;
};
};
}
)
cfg.users;
binFiles =
{
"${cfg.binDir}/mount.bat" = {
inherit (bin) owner group mode type;
src = pkgs.writeTextFile {
name = "beatsaber-mount.bat";
executable = true;
text = mountbeatsaber;
};
};
"${cfg.binDir}/launch.bat" = {
inherit (bin) owner group mode type;
src = pkgs.writeTextFile {
name = "beatsaber-launch.bat";
executable = true;
text = launchbeatsaber;
};
};
"${cfg.binDir}/fpfc.bat" = {
inherit (bin) owner group mode type;
src = pkgs.writeTextFile {
name = "beatsaber-fpfc.bat";
executable = true;
text = fpfcbeatsaber;
};
};
"${cfg.binDir}/setup.bat" = {
inherit (bin) owner group mode type;
src = pkgs.writeTextFile {
name = "beatsaber-setup.bat";
executable = true;
text = setupbeatsaber;
};
};
"${cfg.binDir}/local-launch.bat" = {
inherit (bin) owner group mode type;
src = pkgs.writeTextFile {
name = "beatsaber-local-launch.bat";
executable = true;
text = localbeatsaber-launch;
};
};
"${cfg.binDir}/local-mount.bat" = {
inherit (bin) owner group mode type;
src = pkgs.writeTextFile {
name = "beatsaber-local-mount.bat";
executable = true;
text = localbeatsaber-mount;
};
};
"${cfg.binDir}/local-vanilla.bat" = {
inherit (bin) owner group mode type;
src = pkgs.writeTextFile {
name = "beatsaber-local-vanilla.bat";
executable = true;
text = localbeatsaber-vanilla;
};
};
"${cfg.binDir}/vanilla.bat" = {
inherit (bin) owner group mode type;
src = pkgs.writeTextFile {
name = "beatsaber-version-vanilla.bat";
executable = true;
text = vanilla;
};
};
"${cfg.binDir}/ModAssistant.exe" = {
inherit (bin) owner group mode type;
src = pkgs.fetchurl {
url = "https://github.com/Assistant/ModAssistant/releases/download/v1.1.32/ModAssistant.exe";
hash = "sha256-ozu2gYFiz+2BjptqL80DmUopbahbyGKFO1IPd7BhVPM=";
executable = true;
};
};
}
// versionBinFiles
// userBinFiles;
in {
enable = mkIf cfg.setup true;
files = mkIf cfg.setup (mkMerge (
singleton binFiles
++ setupFiles
));
};
};
}