Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8943688a43 | |||
| 92745d8af8 | |||
| 1f977bb1f4 | |||
| 53d8c10645 | |||
| b58681964c | |||
| 2abf445637 | |||
| c0e6c37f1a | |||
| 856415c22f | |||
| 7ddd7cbac4 |
@@ -1,3 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
declare -a errorList
|
||||
declare -a packageList
|
||||
if [[ $# -eq 0 ]]; then
|
||||
@@ -10,6 +12,12 @@ else
|
||||
errorList+=("Critical: Wine is not installed. You will not be able to play any games.")
|
||||
fi
|
||||
packageList+=("wine")
|
||||
if command -v umu-run &> /dev/null ; then
|
||||
[[ $# -eq 0 ]] && echo "umu-launcher is installed."
|
||||
else
|
||||
errorList+=("Warning: umu-launcher is not installed. Games that require Proton/UMU will not install or launch.")
|
||||
fi
|
||||
packageList+=("umu-launcher")
|
||||
if command -v curl &> /dev/null ; then
|
||||
[[ $# -eq 0 ]] && echo "Curl is installed."
|
||||
else
|
||||
|
||||
+28
-2
@@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2154 # Sourced by audiogame-manager with shared globals.
|
||||
|
||||
documentation() {
|
||||
if [[ "$2" == "Become a Patron" ]]; then
|
||||
return
|
||||
@@ -8,13 +11,36 @@ documentation() {
|
||||
|
||||
# Extract architecture from first parameter (format: "win64|path")
|
||||
local wineArch="${1%%|*}"
|
||||
get_bottle "$wineArch"
|
||||
if [[ "$wineArch" == "umu" ]]; then
|
||||
local launcherLine=""
|
||||
local docFlag=""
|
||||
local umuGameId=""
|
||||
local -a documentationGame=()
|
||||
launcherLine="$(grep -F -m1 "${1}|" "$configFile" 2> /dev/null || true)"
|
||||
IFS='|' read -ra documentationGame <<< "$launcherLine"
|
||||
for docFlag in "${documentationGame[@]:3}" ; do
|
||||
if [[ "$docFlag" =~ ^export\ [a-zA-Z_][a-zA-Z0-9_]*=\'?.*\'?$ ]]; then
|
||||
eval "$docFlag"
|
||||
fi
|
||||
done
|
||||
if [[ -z "$umuGameId" ]]; then
|
||||
echo "Unable to find UMU game id for documentation lookup."
|
||||
return
|
||||
fi
|
||||
get_umu_bottle "$umuGameId"
|
||||
else
|
||||
get_bottle "$wineArch"
|
||||
fi
|
||||
|
||||
echo "Loading documentation, please wait..."
|
||||
|
||||
# Try to find documentation based on common naming conventions.
|
||||
local gamePath
|
||||
gamePath="$(winepath -u "$2" 2> /dev/null)"
|
||||
if [[ "$wineArch" == "umu" ]]; then
|
||||
gamePath="$(umu_windows_path_to_unix "$2")"
|
||||
else
|
||||
gamePath="$(winepath -u "$2" 2> /dev/null)"
|
||||
fi
|
||||
gamePath="${gamePath%/*}"
|
||||
local gameDoc=""
|
||||
local isUrl="false"
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2034,SC2154 # Sourced by audiogame-manager and installers with shared globals.
|
||||
|
||||
require_umu() {
|
||||
if command -v umu-run &> /dev/null; then
|
||||
return 0
|
||||
fi
|
||||
local message="This game requires umu-launcher. Please install umu-launcher and try again."
|
||||
if declare -F agm_msgbox &> /dev/null; then
|
||||
agm_msgbox "Audio Game Manager" "Audio Game Manager" "$message"
|
||||
else
|
||||
echo "$message" >&2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
get_umu_bottle() {
|
||||
local gameId="$1"
|
||||
if [[ -z "$gameId" ]]; then
|
||||
echo "get_umu_bottle requires a game id." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
export umuGameId="$gameId"
|
||||
export WINEPREFIX="${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/protonBottles/${gameId}"
|
||||
export GAMEID="$gameId"
|
||||
export STORE="${umuStore:-none}"
|
||||
export DISPLAY="${DISPLAY:-:0}"
|
||||
mkdir -p "$WINEPREFIX"
|
||||
}
|
||||
|
||||
install_proton_bottle() {
|
||||
local gameId="$1"
|
||||
shift || true
|
||||
require_umu || return 1
|
||||
get_umu_bottle "$gameId" || return 1
|
||||
|
||||
if [[ ! -f "${WINEPREFIX}/system.reg" ]]; then
|
||||
umu-run ""
|
||||
fi
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
umu-run winetricks "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
install_proton_winetricks_verb() {
|
||||
local verbName="$1"
|
||||
local output=""
|
||||
local status=0
|
||||
local hadErrexit="false"
|
||||
|
||||
require_umu || return 1
|
||||
if [[ $- == *e* ]]; then
|
||||
hadErrexit="true"
|
||||
fi
|
||||
set +e
|
||||
output="$(umu-run winetricks -q "$verbName" 2>&1)"
|
||||
status=$?
|
||||
if [[ "$hadErrexit" == "true" ]]; then
|
||||
set -e
|
||||
fi
|
||||
printf '%s\n' "$output"
|
||||
|
||||
if [[ "$status" -ne 0 ]]; then
|
||||
if grep -F -q "winetricks verb '${verbName}' is already installed" <<< "$output"; then
|
||||
return 0
|
||||
fi
|
||||
return "$status"
|
||||
fi
|
||||
}
|
||||
|
||||
umu_windows_path_to_unix() {
|
||||
local windowsPath="$1"
|
||||
local relativePath=""
|
||||
if [[ "$windowsPath" =~ ^[cC]:\\ ]]; then
|
||||
relativePath="${windowsPath:3}"
|
||||
relativePath="${relativePath//\\//}"
|
||||
printf '%s/drive_c/%s\n' "$WINEPREFIX" "$relativePath"
|
||||
return 0
|
||||
fi
|
||||
winepath -u "$windowsPath"
|
||||
}
|
||||
|
||||
run_umu_game() {
|
||||
local windowsPath="$1"
|
||||
local exePath=""
|
||||
require_umu || return 1
|
||||
if [[ -z "${umuGameId:-}" ]]; then
|
||||
echo "UMU game id is not set for ${game[2]:-selected game}." >&2
|
||||
return 1
|
||||
fi
|
||||
get_umu_bottle "$umuGameId" || return 1
|
||||
exePath="$(umu_windows_path_to_unix "$windowsPath")"
|
||||
if [[ ! -f "$exePath" ]]; then
|
||||
echo "UMU executable not found: $exePath" >&2
|
||||
return 1
|
||||
fi
|
||||
pushd "${exePath%/*}" > /dev/null || return 1
|
||||
umu-run "$exePath"
|
||||
popd > /dev/null || return 1
|
||||
}
|
||||
|
||||
add_umu_launcher() {
|
||||
local gameId="$1"
|
||||
local windowsPath="$2"
|
||||
shift 2
|
||||
local launchSettings="umu|${windowsPath}|${game}|export umuGameId=${gameId}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
launchSettings+="|$1"
|
||||
shift
|
||||
done
|
||||
|
||||
if ! grep -F -q -x "$launchSettings" "$configFile" 2> /dev/null; then
|
||||
echo "$launchSettings" >> "$configFile"
|
||||
sort -t '|' -k3,3f -o "$configFile" "$configFile"
|
||||
fi
|
||||
}
|
||||
|
||||
set_umu_reg_value() {
|
||||
local key="$1"
|
||||
local valueName="$2"
|
||||
local valueData="$3"
|
||||
wine reg add "$key" /v "$valueName" /t REG_SZ /d "$valueData" /f
|
||||
}
|
||||
|
||||
set_umu_app_winver() {
|
||||
local exeName="$1"
|
||||
local winVersion="$2"
|
||||
set_umu_reg_value "HKCU\\Software\\Wine\\AppDefaults\\${exeName}" "Version" "$winVersion"
|
||||
}
|
||||
|
||||
install_crlf_file() {
|
||||
local sourceFile="$1"
|
||||
local destFile="$2"
|
||||
mkdir -p "${destFile%/*}"
|
||||
perl -pe 's/\r?\n/\r\n/' "$sourceFile" > "$destFile"
|
||||
}
|
||||
|
||||
stop_umu_bottle() {
|
||||
if command -v wineserver &> /dev/null; then
|
||||
wineserver -k 2> /dev/null || true
|
||||
fi
|
||||
}
|
||||
@@ -1,9 +1,41 @@
|
||||
download "https://blindgamers.com/downloads/a-heros-call-freeware.zip" "https://stormgames.wolfe.casa/downloads/nvdaControllerClient32.dll"
|
||||
export winVer="win7"
|
||||
export winetricksSettings="vd=1024x768"
|
||||
install_wine_bottle
|
||||
# Dotnet is evil. That is all.
|
||||
LC_ALL=C DISPLAY="" winetricks -q dotnet462 xna40
|
||||
wineserver -k # Really!
|
||||
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files" "${cache}/a-heros-call-freeware.zip"
|
||||
add_launcher "c:\Program Files\a-heros-call\A Hero's Call.exe"
|
||||
# shellcheck shell=bash disable=SC2154 # cache, game, and helper functions are set by audiogame-manager.
|
||||
|
||||
export game="A Hero's Call"
|
||||
herosCallGameId="a-heros-call"
|
||||
herosCallPath="c:\\Program Files\\a-heros-call\\A Hero's Call.exe"
|
||||
|
||||
download "https://blindgamers.com/downloads/a-heros-call-freeware.zip" \
|
||||
"https://stormgames.wolfe.casa/downloads/nvdaControllerClient32.dll"
|
||||
|
||||
export PROTON_USE_XALIA=0
|
||||
install_proton_bottle "$herosCallGameId"
|
||||
|
||||
alert "A Hero's Call" "A Hero's Call" "If you hear a window open during installation, or if the installer seems to be taking a long time, try pressing enter to see if it will continue."
|
||||
|
||||
{
|
||||
echo "# Installing A Hero's Call dependencies..."
|
||||
WINETRICKS_FORCE=1 install_proton_winetricks_verb speechsdk
|
||||
install_proton_winetricks_verb corefonts
|
||||
install_proton_winetricks_verb dotnet462
|
||||
install_proton_winetricks_verb xna40
|
||||
install_proton_winetricks_verb win7
|
||||
|
||||
echo "# Extracting game files..."
|
||||
mkdir -p "${WINEPREFIX}/drive_c/Program Files"
|
||||
unzip -oq "${cache}/a-heros-call-freeware.zip" -d "${WINEPREFIX}/drive_c/Program Files"
|
||||
echo "# Installation complete"
|
||||
} | agm_progressbox "Installing Game" "Installing A Hero's Call with UMU/Proton (this may take several minutes)..."
|
||||
|
||||
stop_umu_bottle
|
||||
|
||||
herosCallInstallDir="${WINEPREFIX}/drive_c/Program Files/a-heros-call"
|
||||
if [[ ! -f "${herosCallInstallDir}/A Hero's Call.exe" ]]; then
|
||||
agm_msgbox "A Hero's Call" "A Hero's Call" "A Hero's Call did not install to the expected location."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "${cache}/nvdaControllerClient32.dll" "${herosCallInstallDir}/nvdaControllerClient32.dll"
|
||||
mkdir -p "${herosCallInstallDir}/lib/Tolk"
|
||||
cp "${cache}/nvdaControllerClient32.dll" "${herosCallInstallDir}/lib/Tolk/nvdaControllerClient32.dll"
|
||||
|
||||
add_umu_launcher "$herosCallGameId" "$herosCallPath" "export PROTON_USE_XALIA=0"
|
||||
|
||||
+92
-26
@@ -1,28 +1,94 @@
|
||||
#// Borken, candidate for removal
|
||||
export WINEARCH="win64" # Migrated to wine64 with WINETRICKS_FORCE=1 - complex .NET dependencies, test carefully
|
||||
download "http://blind-games.com/newentombed/EntombedSetup.exe" "https://download.microsoft.com/download/E/C/1/EC1B2340-67A0-4B87-85F0-74D987A27160/SSCERuntime-ENU.exe" "https://stormgames.wolfe.casa/downloads/Entombed.exe.config" "https://stormgames.wolfe.casa/downloads/mfplat.dll"
|
||||
export winVer="win7"
|
||||
install_wine_bottle sapi msvcrt40 gdiplus ie7 wmp11 mf
|
||||
# Ok, more dotnet.
|
||||
LC_ALL=C DISPLAY="" winetricks -q dotnet40 xna40
|
||||
wineserver -k # Sigh.
|
||||
mkdir -p "${WINEPREFIX}/drive_c/temp"
|
||||
pushd "${WINEPREFIX}/drive_c/temp"
|
||||
install_with_progress 7z "Extracting game files..." x "${cache}/SSCERuntime-ENU.exe"
|
||||
wine msiexec /i "${WINEPREFIX}/drive_c/temp/SSCERuntime_x86-ENU.msi" /q
|
||||
rm *
|
||||
popd
|
||||
pushd "${WINEPREFIX}/drive_c/Program Files/Microsoft SQL Server Compact Edition/v3.5"
|
||||
wine regsvr32 sqlceoledb35.dll
|
||||
wine regsvr32 sqlceca35.dll
|
||||
popd
|
||||
wine "${cache}/EntombedSetup.exe" /silent
|
||||
pushd "${WINEPREFIX}/drive_c/Program Files/Entombed"
|
||||
cp ../Microsoft\ SQL\ Server\ Compact\ Edition/v3.5/Private/System.Data.SqlServerCe.Entity.dll ../Microsoft\ SQL\ Server\ Compact\ Edition/v3.5/Private/System.Data.SqlServerCe.dll .
|
||||
cp ../Microsoft\ SQL\ Server\ Compact\ Edition/v3.5/sql* .
|
||||
cp "${cache}/Entombed.exe.config" .
|
||||
popd
|
||||
if [ ! -f "${WINEPREFIX}/drive_c/windows/system32/mfplat.dll" ] ; then
|
||||
# shellcheck shell=bash disable=SC2154 # cache, game, and helper functions are set by audiogame-manager.
|
||||
|
||||
export game="Entombed"
|
||||
entombedGameId="entombed"
|
||||
entombedPath='c:\Program Files (x86)\Entombed\Entombed.exe'
|
||||
|
||||
download "http://blind-games.com/newentombed/EntombedSetup.exe" \
|
||||
"https://download.microsoft.com/download/E/C/1/EC1B2340-67A0-4B87-85F0-74D987A27160/SSCERuntime-ENU.exe" \
|
||||
"https://stormgames.wolfe.casa/downloads/Entombed.exe.config" \
|
||||
"https://stormgames.wolfe.casa/downloads/mfplat.dll"
|
||||
|
||||
export PROTON_USE_XALIA=0
|
||||
install_proton_bottle "$entombedGameId"
|
||||
|
||||
entombedProfileSid="$(sed -n 's/^;; All keys relative to REGISTRY\\\\User\\\\//p' "${WINEPREFIX}/user.reg" | head -n 1)"
|
||||
if [[ -n "$entombedProfileSid" ]]; then
|
||||
umu-run reg add "HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\${entombedProfileSid}" /v ProfileImagePath /t REG_SZ /d "C:\\users\\steamuser" /f
|
||||
fi
|
||||
|
||||
mkdir -p \
|
||||
"${WINEPREFIX}/drive_c/users/steamuser/AppData/Local/IsolatedStorage" \
|
||||
"${WINEPREFIX}/drive_c/users/steamuser/AppData/Roaming/IsolatedStorage"
|
||||
|
||||
alert "Entombed" "Entombed" "If you hear a window open during installation, or if the installer seems to be taking a long time, try pressing enter to see if it will continue."
|
||||
|
||||
{
|
||||
echo "# Installing Entombed dependencies..."
|
||||
WINETRICKS_FORCE=1 install_proton_winetricks_verb speechsdk
|
||||
install_proton_winetricks_verb msvcrt40
|
||||
install_proton_winetricks_verb gdiplus
|
||||
install_proton_winetricks_verb wmp11
|
||||
install_proton_winetricks_verb mf
|
||||
install_proton_winetricks_verb dotnet40
|
||||
install_proton_winetricks_verb xna40
|
||||
install_proton_winetricks_verb win7
|
||||
|
||||
echo "# Extracting SQL Server Compact runtime..."
|
||||
entombedTempDir="${WINEPREFIX}/drive_c/temp"
|
||||
mkdir -p "$entombedTempDir"
|
||||
7z x -y "-o${entombedTempDir}" "${cache}/SSCERuntime-ENU.exe"
|
||||
|
||||
echo "# Installing SQL Server Compact runtime..."
|
||||
umu-run msiexec /i "c:\\temp\\SSCERuntime_x86-ENU.msi" /q
|
||||
umu-run regsvr32 "c:\\Program Files\\Microsoft SQL Server Compact Edition\\v3.5\\sqlceoledb35.dll"
|
||||
umu-run regsvr32 "c:\\Program Files\\Microsoft SQL Server Compact Edition\\v3.5\\sqlceca35.dll"
|
||||
|
||||
echo "# Installing Entombed..."
|
||||
umu-run "${cache}/EntombedSetup.exe" /silent
|
||||
find "$entombedTempDir" -mindepth 1 -maxdepth 1 -exec rm -rf "{}" +
|
||||
echo "# Installation complete"
|
||||
} | agm_progressbox "Installing Game" "Installing Entombed with UMU/Proton (this may take several minutes)..."
|
||||
|
||||
stop_umu_bottle
|
||||
|
||||
entombedInstallDir=""
|
||||
for entombedCandidateDir in \
|
||||
"${WINEPREFIX}/drive_c/Program Files (x86)/Entombed" \
|
||||
"${WINEPREFIX}/drive_c/Program Files/Entombed"; do
|
||||
if [[ -f "${entombedCandidateDir}/Entombed.exe" ]]; then
|
||||
entombedInstallDir="$entombedCandidateDir"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$entombedInstallDir" ]]; then
|
||||
agm_msgbox "Entombed" "Entombed" "Entombed did not install to the expected location."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
entombedSqlDir=""
|
||||
for entombedCandidateSqlDir in \
|
||||
"${WINEPREFIX}/drive_c/Program Files (x86)/Microsoft SQL Server Compact Edition/v3.5" \
|
||||
"${WINEPREFIX}/drive_c/Program Files/Microsoft SQL Server Compact Edition/v3.5"; do
|
||||
if [[ -d "$entombedCandidateSqlDir" ]]; then
|
||||
entombedSqlDir="$entombedCandidateSqlDir"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$entombedSqlDir" ]]; then
|
||||
agm_msgbox "Entombed" "Entombed" "SQL Server Compact v3.5 did not install to the expected location."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "${entombedSqlDir}/Private/System.Data.SqlServerCe.Entity.dll" "$entombedInstallDir/"
|
||||
cp "${entombedSqlDir}/Private/System.Data.SqlServerCe.dll" "$entombedInstallDir/"
|
||||
cp "${entombedSqlDir}"/sql* "$entombedInstallDir/"
|
||||
cp "${cache}/Entombed.exe.config" "${entombedInstallDir}/Entombed.exe.config"
|
||||
|
||||
if [[ ! -f "${WINEPREFIX}/drive_c/windows/system32/mfplat.dll" ]]; then
|
||||
cp "${cache}/mfplat.dll" "${WINEPREFIX}/drive_c/windows/system32/"
|
||||
fi
|
||||
add_launcher "c:\Program Files (x86)\Entombed\Entombed.exe"
|
||||
|
||||
add_umu_launcher "$entombedGameId" "$entombedPath" "export PROTON_USE_XALIA=0"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
download "http://files.l-works.net/judgmentdayfullsetup.exe"
|
||||
# shellcheck shell=bash disable=SC2154 # cache is set by audiogame-manager
|
||||
download "https://www.l-works.net/files/judgmentdayfullsetup.exe"
|
||||
install_wine_bottle vb6run dx8vb quartz
|
||||
wine "${cache}/judgmentdayfullsetup.exe" /silent
|
||||
cat << EOF > /tmp/judgementday.reg
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
export WINEARCH=win64
|
||||
# shellcheck disable=SC2154 # installer is sourced by audiogame-manager with shared globals
|
||||
export winVer="win10"
|
||||
game="${game:-Magic: The Gathering Arena}"
|
||||
|
||||
mtgaVersionUrl="https://mtgarena.downloads.wizards.com/Live/Windows32/version"
|
||||
accessibleArenaDllUrl="https://github.com/JeanStiletto/AccessibleArena/releases/latest/download/AccessibleArena.dll"
|
||||
melonLoaderZipUrl="https://github.com/LavaGang/MelonLoader/releases/latest/download/MelonLoader.x64.zip"
|
||||
tolkDllUrl="https://stormgames.wolfe.casa/downloads/Tolk.dll"
|
||||
|
||||
get_mtga_installer_url() {
|
||||
local versionJson=""
|
||||
local installerUrl=""
|
||||
|
||||
if ! versionJson="$(curl -fsSL "$mtgaVersionUrl")"; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not fetch the current MTG Arena installer URL."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
installerUrl="$(sed -n 's/.*"CurrentInstallerURL"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' <<< "$versionJson" | head -n1)"
|
||||
if [[ -z "$installerUrl" ]]; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not parse the current MTG Arena installer URL."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$installerUrl"
|
||||
}
|
||||
|
||||
download_mtga_installer() {
|
||||
local installerUrl="$1"
|
||||
local installerFile="${installerUrl##*/}"
|
||||
installerFile="${installerFile%%\?*}"
|
||||
[[ -n "$installerFile" ]] || installerFile="MTGAInstaller.msi"
|
||||
|
||||
# shellcheck disable=SC2154 # cache is set by audiogame-manager before installers are sourced
|
||||
if [[ "${redownload:-}" == "true" ]] || [[ ! -s "${cache}/${installerFile}" ]]; then
|
||||
if ! curl -L4 -C - --retry 10 --output "${cache}/${installerFile}" "$installerUrl" 2>&1 | agm_progressbox "Magic: The Gathering Arena" "Downloading MTG Arena installer..."; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not download the MTG Arena installer."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mtgaInstallerPath="${cache}/${installerFile}"
|
||||
}
|
||||
|
||||
configure_accessible_arena_loader() {
|
||||
local mtgaRoot="$1"
|
||||
local userDataPath="${mtgaRoot}/UserData"
|
||||
local loaderConfig="${userDataPath}/Loader.cfg"
|
||||
|
||||
mkdir -p "$userDataPath"
|
||||
if [[ -f "$loaderConfig" ]]; then
|
||||
if grep -Fq "hide_console = false" "$loaderConfig"; then
|
||||
sed -i 's/hide_console = false/hide_console = true/g' "$loaderConfig"
|
||||
elif ! grep -Fq "hide_console" "$loaderConfig"; then
|
||||
printf '\n[console]\nhide_console = true\n' >> "$loaderConfig"
|
||||
fi
|
||||
else
|
||||
printf '[console]\nhide_console = true\n' > "$loaderConfig"
|
||||
fi
|
||||
}
|
||||
|
||||
install_accessible_arena_support() {
|
||||
local mtgaRoot="$1"
|
||||
|
||||
if [[ -z "${nvdaControllerClient64Dll:-}" ]]; then
|
||||
# shellcheck disable=SC2154 # ipfs is sourced through audiogame-manager helpers
|
||||
nvdaControllerClient64Dll="${ipfs[nvdaControllerClient64]}"
|
||||
fi
|
||||
|
||||
download "$accessibleArenaDllUrl" "$melonLoaderZipUrl" "$tolkDllUrl" "$nvdaControllerClient64Dll"
|
||||
|
||||
install_with_progress unzip "Installing MelonLoader..." -d "$mtgaRoot" "${cache}/MelonLoader.x64.zip"
|
||||
mkdir -p "${mtgaRoot}/Mods"
|
||||
install_with_progress cp "Installing Accessible Arena..." "${cache}/AccessibleArena.dll" "${mtgaRoot}/Mods/AccessibleArena.dll"
|
||||
install_with_progress cp "Installing screen reader support DLLs..." "${cache}/Tolk.dll" "${cache}/nvdaControllerClient64.dll" "$mtgaRoot"
|
||||
configure_accessible_arena_loader "$mtgaRoot"
|
||||
}
|
||||
|
||||
install_wine_bottle dxvk
|
||||
winetricks -q $winVer
|
||||
|
||||
mtgaRoot="${WINEPREFIX}/drive_c/Program Files (x86)/Wizards of the Coast/MTGA"
|
||||
mtgaInstallerUrl="$(get_mtga_installer_url)"
|
||||
download_mtga_installer "$mtgaInstallerUrl"
|
||||
|
||||
wine msiexec /i "$mtgaInstallerPath" /q
|
||||
|
||||
if [[ ! -f "${mtgaRoot}/MTGA.exe" ]]; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "MTG Arena installation did not finish at the expected location: ${mtgaRoot}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install_accessible_arena_support "$mtgaRoot"
|
||||
add_launcher 'c:\Program Files (x86)\Wizards of the Coast\MTGA\MTGA.exe' 'export WINEDLLOVERRIDES=version=n,b'
|
||||
+35
-20
@@ -1,23 +1,38 @@
|
||||
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
|
||||
#//Disable since it's not working
|
||||
download "https://www.mm-galabo.com/sr/Download_files_srfv/shadowrine_fullvoice3.171.exe" "https://raw.githubusercontent.com/LordLuceus/sr-english-localization/master/language_en.dat"
|
||||
export WINEARCH="win64" # Migrated to wine64 with WINETRICKS_FORCE=1
|
||||
export winVer="win8"
|
||||
install_wine_bottle fakejapanese
|
||||
# Add bcrypt DLL override required for Shadow Line to run
|
||||
cat > /tmp/bcrypt_override.reg << 'EOF'
|
||||
[HKEY_CURRENT_USER\Software\Wine\DllOverrides]
|
||||
"bcryptprimitives"="native,builtin"
|
||||
EOF
|
||||
wine regedit /tmp/bcrypt_override.reg
|
||||
rm /tmp/bcrypt_override.reg
|
||||
# shellcheck shell=bash disable=SC2154 # cache, game, and helper functions are set by audiogame-manager.
|
||||
|
||||
export game="Shadow Line"
|
||||
shadowLineGameId="shadow-line"
|
||||
shadowLinePath='c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
|
||||
download "https://www.mm-galabo.com/sr/Download_files_srfv/shadowrine_fullvoice3.171.exe" \
|
||||
"https://raw.githubusercontent.com/LordLuceus/sr-english-localization/master/language_en.dat" \
|
||||
"${nvdaControllerClient32Dll}" \
|
||||
"${nvdaControllerClient64Dll}"
|
||||
|
||||
install_proton_bottle "$shadowLineGameId" fakejapanese
|
||||
shadowLineInstallDir="${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
|
||||
|
||||
set_umu_reg_value "HKCU\\Software\\Wine\\DllOverrides" "bcryptprimitives" "native,builtin"
|
||||
set_umu_app_winver "play_sr.exe" "win8"
|
||||
|
||||
{
|
||||
echo "# Installing Shadow Line..."
|
||||
timeout 300 wine "${cache}/shadowrine_fullvoice3.171.exe" /sp- /VERYSILENT /SUPPRESSMSGBOXES 2>&1 || true
|
||||
timeout 300 umu-run "${cache}/shadowrine_fullvoice3.171.exe" /sp- /VERYSILENT /SUPPRESSMSGBOXES 2>&1 || true
|
||||
echo "# Installation complete"
|
||||
} | agm_progressbox "Installing Game" "Installing Shadow Line (this may take a few minutes)..."
|
||||
# Kill any auto-launched game processes (installer lacks skipifsilent flag)
|
||||
wineserver -k 2>/dev/null || true
|
||||
mv -v "${cache}/language_en.dat" "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/SystemData/language_en.dat"
|
||||
add_launcher "c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe"
|
||||
alert "Please set the language to English when the game opens.\nGo to options and press enter.\nPress down arrow 5 times and press enter.\nPress down arrow 1 time and press enter.\nPress up arrow 2 times and press enter.\nIf everything worked as expected you should be back on the game menu and speech should work."
|
||||
} | agm_progressbox "Installing Game" "Installing Shadow Line with UMU/Proton (this may take a few minutes)..."
|
||||
|
||||
stop_umu_bottle
|
||||
|
||||
if [[ ! -f "${shadowLineInstallDir}/play_sr.exe" ]]; then
|
||||
agm_msgbox "Shadow Line" "Shadow Line" "Shadow Line did not install to the expected location: ${shadowLineInstallDir}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install_crlf_file "${cache}/language_en.dat" "${shadowLineInstallDir}/SystemData/language_en.dat"
|
||||
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient32.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient64.dll' -exec cp -v "${cache}/nvdaControllerClient64.dll" "{}" \;
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
|
||||
|
||||
add_umu_launcher "$shadowLineGameId" "$shadowLinePath"
|
||||
alert "Shadow Line" "Shadow Line" "Please set the language to English when the game opens.\nGo to options and press enter.\nPress down arrow 5 times and press enter.\nPress down arrow 1 time and press enter.\nPress up arrow 2 times and press enter.\nIf everything worked as expected you should be back on the game menu and speech should work."
|
||||
|
||||
@@ -1,265 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
**Critical**: Be sure to keep this file up to date with new changes.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Audiogame Manager** is a comprehensive bash-based installer and launcher system for Windows audio games running under Wine on Linux. The project focuses on accessibility, providing speech synthesis support and screen reader compatibility for audio games designed for blind and visually impaired users.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
- **Main Script**: `audiogame-manager.sh` - Entry point providing interactive menus for game installation, launching, and management with Wine32/64 bottle support
|
||||
- **Includes Directory**: `.includes/` - Modular helper scripts containing core functionality
|
||||
- `functions.sh` - Core utilities (download, cache management, validation, requirements checking)
|
||||
- `dialog-interface.sh` - UI abstraction layer supporting both console (dialog) and GUI (yad) modes
|
||||
- `bottle.sh` - Wine bottle management, RHVoice installation, and prefix management
|
||||
- `checkup.sh` - System dependency validation
|
||||
- `help.sh` - Documentation system
|
||||
- `desktop.sh` - Desktop integration
|
||||
- `update.sh` - Auto-update mechanisms
|
||||
- **Game Scripts**: `game-scripts/` - Individual game update/launch scripts
|
||||
- **Install Scripts**: `.install/` - Game installation scripts (100+ games supported)
|
||||
- **Speech Integration**: `speech/` - TTS and accessibility tools
|
||||
- `set-voice.sh` - Voice configuration with test functionality
|
||||
- Supports multiple TTS engines (RHVoice, SAPI, Cepstral)
|
||||
- Per-bottle voice configuration system
|
||||
- NVDA screen reader compatibility through custom DLL
|
||||
- **Wine Integration**: `wine/` - Wine setup utilities including bottle creation and dependency installation
|
||||
- `mkwine.sh` - Wine bottle creation
|
||||
- Distribution-specific dependency installers
|
||||
|
||||
### Wine Architecture
|
||||
|
||||
The project uses Wine with a unified architecture:
|
||||
- **Wine64 Only**: All games use wine64+WOW64 exclusively - it runs both 32-bit and 64-bit applications efficiently
|
||||
- **Wine32 ELIMINATED**: As of 2025-12-06, wine32 bottle is no longer created or used
|
||||
- **Unified bottle**: Single wine64 prefix (stored in `~/.local/wine64`) for ALL games, including SAPI games
|
||||
- **Custom bottles**: Can be stored in `~/.local/share/audiogame-manager/wineBottles/` with per-bottle configurations in `~/.config/audiogame-manager/`
|
||||
- **IMPORTANT**: Never create game-specific wine directories like `~/.local/winegamename` - use the standard bottle system
|
||||
|
||||
#### SAPI and Speech SDK Support (WINETRICKS_FORCE=1)
|
||||
|
||||
**Discovery (2025-12-06)**: Setting `WINETRICKS_FORCE=1` enables reliable speechsdk installation in wine64+WOW64 bottles, eliminating the need for wine32 for most SAPI-dependent games.
|
||||
|
||||
**Implementation:**
|
||||
- `audiogame-manager.sh:96` - wine64 bottle creation includes speechsdk with `WINETRICKS_FORCE=1`
|
||||
- `audiogame-manager.sh:99` - Restores win10 after speechsdk (see caveat below)
|
||||
- `.includes/bottle.sh:176-181` - Only speechsdk installations use `WINETRICKS_FORCE=1` (other deps use regular winetricks)
|
||||
- Both bottles install Microsoft Mike as the default SAPI voice automatically
|
||||
|
||||
**CAVEAT**: The `speechsdk` winetricks verb sets `w_set_winver winxp` at the end, trampling any previously set Windows version. Always call `winetricks win10` AFTER installing speechsdk.
|
||||
|
||||
**Migrated SAPI Games (wine64):**
|
||||
- Bloodshed - Tested and confirmed working
|
||||
- Kitchensinc Games - Tested and confirmed working (VB6)
|
||||
- Oh Shit - Migrated, requires testing
|
||||
- Dog Who Hates Toast - Migrated, requires testing (VB6)
|
||||
- Lunimals - Migrated, requires testing (VB6)
|
||||
- VIP Mud - Migrated, requires testing (VB6)
|
||||
- Entombed - Migrated, requires testing (complex .NET dependencies)
|
||||
- Skateboarder Pro - Already configured for wine64, now functional
|
||||
- Three D velocity - Already configured for wine64, now functional
|
||||
|
||||
**Migrated Self-Voicing Games (wine64):**
|
||||
- Shadow Line - Migrated, requires Windows 8 via per-app versioning
|
||||
- Villains From Beyond - Tested and confirmed working
|
||||
|
||||
#### Per-Application Windows Version
|
||||
|
||||
**Problem (2025-12-08)**: Some games require specific Windows versions (e.g., Shadow Line needs win8) but with a unified bottle, `winetricks winXX` changes the version globally for ALL games.
|
||||
|
||||
**Solution**: Use Wine's per-application settings via registry keys:
|
||||
```
|
||||
[HKEY_CURRENT_USER\Software\Wine\AppDefaults\executable.exe]
|
||||
"Version"="win8"
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Game installers set `export winVer="win8"` (or win7, win10)
|
||||
- `install_wine_bottle()` stores this in `gameWinVer` (doesn't apply globally)
|
||||
- `add_launcher()` calls `set_app_winver()` which writes the per-exe registry key
|
||||
- Located in `.includes/bottle.sh:186-209` (`set_app_winver` function)
|
||||
|
||||
**Architecture Selection Logic:**
|
||||
- Games explicitly setting `WINEARCH=win64` will use wine64 (new behavior)
|
||||
- Games passing `sapi` dependency use wine64 with speechsdk pre-installed
|
||||
- Legacy games passing `speechsdk` dependency still use wine32 for compatibility
|
||||
- Default architecture is wine64 for all new games
|
||||
|
||||
### Interface System
|
||||
|
||||
Dialog interface automatically detects environment:
|
||||
- Console mode: Uses `dialog` for accessibility
|
||||
- GUI mode: Uses `yad` when DISPLAY is available
|
||||
- All UI functions are prefixed with `agm_` (audiogame manager)
|
||||
|
||||
## Development Commands
|
||||
|
||||
### System Check
|
||||
```bash
|
||||
./audiogame-manager.sh -c # Check system requirements and dependencies
|
||||
bash -n audiogame-manager.sh # Basic syntax check
|
||||
```
|
||||
|
||||
### Installation and Usage
|
||||
```bash
|
||||
./audiogame-manager.sh # Launch installed games menu
|
||||
./audiogame-manager.sh -i # Install games (interactive menu)
|
||||
./audiogame-manager.sh -I "Game Name" # Install a game noninteractively
|
||||
./audiogame-manager.sh -h # Show help
|
||||
```
|
||||
|
||||
### Wine Bottle Management
|
||||
```bash
|
||||
./wine/mkwine.sh [bottle_name] [architecture] # Create a new Wine bottle
|
||||
```
|
||||
|
||||
### Voice and Speech Testing
|
||||
```bash
|
||||
./speech/set-voice.sh # Configure and test voice settings
|
||||
./speech/set-voice.sh [bottle_name] # Configure voice for a specific bottle
|
||||
```
|
||||
|
||||
### Dependency Management
|
||||
```bash
|
||||
# Check all required packages
|
||||
./.includes/checkup.sh packages
|
||||
|
||||
# Install Wine dependencies for different distros
|
||||
./wine/install-dependencies-arch.sh
|
||||
./wine/install-dependencies-debian.sh
|
||||
```
|
||||
|
||||
### Testing All Include Files
|
||||
```bash
|
||||
for f in .includes/*.sh; do bash -n "$f"; done
|
||||
```
|
||||
|
||||
## Key Patterns and Conventions
|
||||
|
||||
### Coding Style - **EXTREMELY IMPORTANT - MUST BE FOLLOWED**
|
||||
|
||||
- **Variables**: Use camelCase for variable names (e.g., `gameTitle`, `wineBottle`, `installPath`)
|
||||
- **CRITICAL**: ALL variables must use camelCase - no exceptions
|
||||
- Examples: `selectedGame`, `wineArch`, `installPath`, `downloadUrl`
|
||||
- Never use: `selected_game`, `wine_arch`, `install_path`, `download_url`
|
||||
- **Functions**: Use snake_case for function names (e.g., `install_game`, `create_wine_bottle`, `check_dependencies`)
|
||||
- **CRITICAL**: ALL functions must use snake_case - no exceptions
|
||||
- Examples: `get_wine_bottle()`, `process_launcher_flags()`, `download_file()`
|
||||
- Never use: `getWineBottle()`, `processLauncherFlags()`, `downloadFile()`
|
||||
- **Shebang**: Use `#!/bin/bash` for all bash scripts
|
||||
- **CRITICAL**: .sh files in .install/ are game installers, not typical bash scripts If they contain # on the first line they are disabled and will not show up in audiogame-manager
|
||||
- **Sourcing pattern**:
|
||||
- **Main script** (`audiogame-manager.sh`): Use `source "${scriptDir}/.includes/file.sh"` (scriptDir is defined at line 4)
|
||||
- **Subdirectory scripts** (game-scripts/, wine/, speech/): Use `source "${0%/*}/../.includes/file.sh"`
|
||||
- **CRITICAL**: Never use relative paths like `source .includes/file.sh` - these fail when the current working directory differs from the script location
|
||||
- **Indentation**: Use consistent indentation (tabs or spaces, follow existing file patterns)
|
||||
- When fixing code, correct any indentation inconsistencies to match the established style
|
||||
|
||||
### Error Handling
|
||||
- Functions return 0 for success, non-zero for failure
|
||||
- Use consistent exit codes throughout scripts
|
||||
- Provide clear error messages with context
|
||||
- Critical errors vs warnings are clearly distinguished in checkup system
|
||||
- Progress feedback is provided for all long-running operations
|
||||
- Always clean up temporary files on exit
|
||||
- Log important operations for debugging
|
||||
|
||||
### File Structure
|
||||
- Game installers follow naming convention: `.install/Game Name.sh`
|
||||
- **IMPORTANT**: Games starting with hash (#) are commented out even if it's a comment
|
||||
- First line of a game installer may not start with # unless we're excluding it
|
||||
- Cache directory: `~/.local/share/audiogame-manager/cache/`
|
||||
- Wine bottles: `~/.local/wine32/` and `~/.local/wine64/`
|
||||
- Custom bottles: `~/.local/share/audiogame-manager/wineBottles/`
|
||||
- Configuration: `~/.config/audiogame-manager/`
|
||||
|
||||
### UI Functions
|
||||
All dialog functions in `.includes/dialog-interface.sh` follow pattern:
|
||||
- `agm_menu()` - Selection menus
|
||||
- `agm_msgbox()` - Message display
|
||||
- `agm_yesno()` - Confirmation dialogs
|
||||
- `agm_progressbox()` - Progress display
|
||||
- Functions automatically adapt to console/GUI environment
|
||||
|
||||
### Game Integration
|
||||
- Each game has both an installer (`.install/`) and optional update script (`game-scripts/`)
|
||||
- Games are categorized by engine type and requirements
|
||||
- SAPI games automatically use Wine32, others use Wine64
|
||||
- Games are tracked in configuration files with Wine bottle associations
|
||||
- Each game can use different Wine bottles with specific configurations
|
||||
|
||||
### Game Installation Pattern
|
||||
1. Check system requirements and dependencies
|
||||
2. Create or verify Wine bottle
|
||||
3. Install Wine dependencies via winetricks
|
||||
4. Download and validate game files (use checksums when available)
|
||||
5. Configure speech synthesis (typically RHVoice)
|
||||
6. Add game entry to launcher configuration
|
||||
|
||||
### Accessibility Features
|
||||
- Sound alerts using `sox` for important notifications
|
||||
- Screen reader compatibility through proper dialog usage
|
||||
- Keyboard navigation support throughout interface
|
||||
- Voice testing integrated into setup process
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Critical (Required)
|
||||
- wine, curl, dialog, sox
|
||||
- Archive tools: 7z, cabextract, unzip, xz
|
||||
- winetricks (for Wine component management)
|
||||
|
||||
### Optional (Warnings if missing)
|
||||
- gawk (for game removal)
|
||||
- ocrdesktop (installer debugging)
|
||||
- qjoypad (gamepad support)
|
||||
- translate-shell, sqlite3, perl (translation features)
|
||||
- w3m (documentation viewing)
|
||||
- xclip, xdotool (X11 integration)
|
||||
|
||||
### Platform Support
|
||||
- x86_64 Linux (primary)
|
||||
- aarch64 with FEX-Emu (alternative Wine implementation)
|
||||
|
||||
## Testing
|
||||
- System requirements: `./audiogame-manager.sh -c`
|
||||
- Voice functionality: Use built-in voice test in `set-voice.sh`
|
||||
- Game installation: Test with simple games first before complex ones
|
||||
- Wine bottle integrity: Check `~/.local/wine32/system.reg` and `~/.local/wine64/system.reg` exist
|
||||
- Test with different Wine versions when modifying bottle creation
|
||||
- Verify speech synthesis functionality after TTS changes
|
||||
- Test game installation scripts in clean environments
|
||||
- Check both 32-bit and 64-bit compatibility where applicable
|
||||
|
||||
## Recent Refactor Notes (2025)
|
||||
|
||||
### Major Refactor Status
|
||||
The project has undergone a significant refactor to modularize functionality. **Status: Largely Complete**
|
||||
|
||||
### Key Fixes Applied
|
||||
1. **Function naming**: Fixed `process_launcher-flags()` → `process_launcher_flags()` in `audiogame-manager.sh:270`
|
||||
2. **Variable scope**: Added `export game` in main script so `.includes/bottle.sh` functions can access it
|
||||
3. **Installation logic**: Completed the `-I` option implementation for noninteractive game installation
|
||||
4. **Code deduplication**: Removed duplicate `check_news` and launcher logic from `update.sh`
|
||||
5. **Include sourcing**: Fixed all relative path sourcing (e.g., `source .includes/bottle.sh`) to use `${scriptDir}` to ensure desktop launchers and execution from any working directory works correctly
|
||||
|
||||
### Critical Variable Handling
|
||||
- **`$game` variable**: Must be exported when set (line 489 in main script) for bottle.sh functions to work
|
||||
- **`agmNoLaunch` variable**: Used to prevent main script execution when sourced by other scripts
|
||||
- **Bottle names**: Derived from game names, converted to lowercase with spaces replaced by hyphens
|
||||
|
||||
### Important Patterns
|
||||
- **Noninteractive installation**: The `-I` option sources the appropriate `.install/GameName.sh` script
|
||||
- **Variable scope**: Include files rely on exported variables from main script
|
||||
- **Wine bottle logic**: Game-specific wine versions are handled in `bottle.sh:get_bottle()`
|
||||
|
||||
### Common Issues to Watch For
|
||||
1. **Variable exports**: Ensure variables are exported when needed by include files
|
||||
2. **Function naming**: Use snake_case for functions, camelCase for variables
|
||||
3. **Path quoting**: Always quote paths that might contain spaces
|
||||
4. **Duplicate logic**: Check main script vs includes to avoid redundant code
|
||||
+47
-20
@@ -469,23 +469,36 @@ game_removal() {
|
||||
# With shared bottles, always remove only the game files, never the entire bottle
|
||||
create_game_array "$selectedGame"
|
||||
if [[ ${#game[@]} -gt 0 ]]; then
|
||||
# Set up wine environment for this game
|
||||
# shellcheck source=.includes/bottle.sh
|
||||
source "${scriptDir}/.includes/bottle.sh"
|
||||
get_bottle "${game[0]}"
|
||||
process_launcher_flags
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
get_umu_bottle "$umuGameId"
|
||||
else
|
||||
# Set up wine environment for this game
|
||||
# shellcheck source=.includes/bottle.sh
|
||||
source "${scriptDir}/.includes/bottle.sh"
|
||||
get_bottle "${game[0]}"
|
||||
fi
|
||||
|
||||
if ! agm_yesno "Confirm Removal" "Audio Game Removal" "Are you sure you want to remove \"${game[2]}\"?"; then
|
||||
agm_msgbox "Audio Game Removal" "" "Removal cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# kill any previous existing wineservers for this prefix in case they didn't shut down properly.
|
||||
wineserver -k
|
||||
# kill any previous existing servers for this prefix in case they didn't shut down properly.
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
stop_umu_bottle
|
||||
else
|
||||
wineserver -k
|
||||
fi
|
||||
|
||||
# Remove only the game's installation directory
|
||||
if [[ -n "$winePath" ]]; then
|
||||
local gameDir
|
||||
gameDir="$(winepath "$winePath")"
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
gameDir="$(umu_windows_path_to_unix "$winePath")"
|
||||
else
|
||||
gameDir="$(winepath "$winePath")"
|
||||
fi
|
||||
if [[ -d "$gameDir" ]]; then
|
||||
rm -rfv "$gameDir" | agm_progressbox "Removing Game" "Removing \"${game[2]}\" files..."
|
||||
else
|
||||
@@ -534,9 +547,16 @@ kill_game() {
|
||||
local wineExec="${game#*|}"
|
||||
wineExec="${wineExec%|*}"
|
||||
wineExec="${wineExec##*\\}"
|
||||
# kill the wine server.
|
||||
get_bottle "${game%|*}"
|
||||
wineserver -k
|
||||
create_game_array "$game"
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
process_launcher_flags
|
||||
get_umu_bottle "$umuGameId"
|
||||
stop_umu_bottle
|
||||
else
|
||||
# kill the wine server.
|
||||
get_bottle "${game[0]}"
|
||||
wineserver -k
|
||||
fi
|
||||
agm_msgbox "Audio Game Killer" "" "The selected game has been stopped."
|
||||
fi
|
||||
exit 0
|
||||
@@ -667,12 +687,11 @@ create_game_array() {
|
||||
# Game array 0 bottle, 1 path, 2 title, 3+ flags
|
||||
local selectedGame="$1"
|
||||
for i in "${lines[@]}" ; do
|
||||
# Only compare the launcher section
|
||||
local j="${selectedGame#*|}"
|
||||
local k="${i#*|}"
|
||||
k="${k%%|*}"
|
||||
if [[ "$j" == "$k" ]]; then
|
||||
IFS='|' read -ra game <<< "$i"
|
||||
local selectedLauncher="${selectedGame#*|}"
|
||||
local candidate=()
|
||||
IFS='|' read -ra candidate <<< "$i"
|
||||
if [[ "$selectedLauncher" == "${candidate[1]}" || "$selectedGame" == "${candidate[2]}" ]]; then
|
||||
game=("${candidate[@]}")
|
||||
break
|
||||
fi
|
||||
done
|
||||
@@ -790,14 +809,21 @@ game_launcher() {
|
||||
open_url "https://2mb.games/product/2mb-patron/"
|
||||
exit 0
|
||||
fi
|
||||
# Set default path/exec for custom launch handlers.
|
||||
winePath="${game[1]%\\*.exe}"
|
||||
wineExec="${game[1]##*\\}"
|
||||
process_launcher_flags
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
echo "launching with umu"
|
||||
start_nvda2speechd
|
||||
run_umu_game "${game[1]}"
|
||||
exit 0
|
||||
fi
|
||||
get_bottle "${game[0]}"
|
||||
echo -n "launching "
|
||||
wine --version
|
||||
# kill any previous existing wineservers for this prefix in case they didn't shut down properly.
|
||||
wineserver -k
|
||||
# Set default path/exec for custom launch handlers.
|
||||
winePath="${game[1]%\\*.exe}"
|
||||
wineExec="${game[1]##*\\}"
|
||||
# launch the game
|
||||
if command -v qjoypad &> /dev/null ; then
|
||||
mkdir -p ~/.qjoypad3
|
||||
@@ -812,7 +838,6 @@ game_launcher() {
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
process_launcher_flags
|
||||
apply_executioners_rage_focus_workaround
|
||||
customLaunchHandled="false"
|
||||
custom_launch_parameters
|
||||
@@ -901,6 +926,8 @@ export ipfsGateway="${ipfsGateway:-https://ipfs.stormux.org}"
|
||||
# Source helper functions
|
||||
# shellcheck source=.includes/bottle.sh
|
||||
source "${scriptDir}/.includes/bottle.sh" # Also sourced in functions that need it
|
||||
# shellcheck source=.includes/proton.sh
|
||||
source "${scriptDir}/.includes/proton.sh"
|
||||
# shellcheck source=.includes/desktop.sh
|
||||
source "${scriptDir}/.includes/desktop.sh"
|
||||
# dialog-interface.sh already sourced earlier
|
||||
|
||||
@@ -0,0 +1,525 @@
|
||||
# UMU Proton Backend Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add a reusable UMU/Proton backend and migrate Shadow Line to it with English localization and NVDA controller DLL replacement.
|
||||
|
||||
**Architecture:** Keep the existing Wine backend intact and add UMU as a new launcher backend value in `games.conf`. Put generic UMU behavior in a focused include file, keep Shadow Line-specific install work in `.install/Shadow Line.sh`, and route launch/removal/kill behavior through backend-aware branches.
|
||||
|
||||
**Tech Stack:** Bash, `umu-run`, Wine/Proton prefixes, existing AGM dialog/progress helpers, `shellcheck`.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Create: `.includes/proton.sh`
|
||||
- Owns UMU dependency checks, prefix selection, UMU launch environment, Windows path conversion, UMU installer/launch helpers, and UMU launcher registration.
|
||||
- Create: `tests/umu_backend_tests.sh`
|
||||
- Self-contained shell tests with temporary HOME/config/cache paths and stubbed external commands.
|
||||
- Modify: `audiogame-manager.sh`
|
||||
- Source `.includes/proton.sh`, skip Wine-only DLL scanning for UMU prefixes, launch UMU entries through `run_umu_game`, and handle UMU kill/remove paths safely.
|
||||
- Modify: `.includes/checkup.sh`
|
||||
- Report `umu-run` as required for Proton-backed games and include `umu-launcher` in package output.
|
||||
- Modify: `.install/Shadow Line.sh`
|
||||
- Use `install_proton_bottle`, run installer via UMU, apply registry settings, copy English language file, replace NVDA controller DLLs, register with `add_umu_launcher`, and show first-run English instructions.
|
||||
|
||||
## Task 1: Add UMU Backend Tests
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/umu_backend_tests.sh`
|
||||
|
||||
- [ ] **Step 1: Write failing shell tests**
|
||||
|
||||
Create `tests/umu_backend_tests.sh`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repoRoot="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
testRoot="$(mktemp -d)"
|
||||
trap 'rm -rf "$testRoot"' EXIT
|
||||
|
||||
export HOME="${testRoot}/home"
|
||||
export XDG_DATA_HOME="${HOME}/.local/share"
|
||||
export XDG_CONFIG_HOME="${HOME}/.config"
|
||||
export XDG_CACHE_HOME="${HOME}/.cache"
|
||||
export DISPLAY=""
|
||||
export cache="${XDG_CACHE_HOME}/audiogame-manager"
|
||||
export configFile="${XDG_CONFIG_HOME}/storm-games/audiogame-manager/games.conf"
|
||||
export scriptDir="$repoRoot"
|
||||
mkdir -p "$cache" "${configFile%/*}" "${testRoot}/bin"
|
||||
touch "$configFile"
|
||||
|
||||
cat > "${testRoot}/bin/umu-run" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s|%s|%s|%s\n' "$WINEPREFIX" "$GAMEID" "${STORE:-}" "$*" >> "$UMU_STUB_LOG"
|
||||
if [[ "${1:-}" == "" ]]; then
|
||||
mkdir -p "$WINEPREFIX/drive_c"
|
||||
fi
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/umu-run"
|
||||
|
||||
cat > "${testRoot}/bin/wine" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
if [[ "${1:-}" == "winepath" || "${1:-}" == "winepath.exe" ]]; then
|
||||
shift
|
||||
fi
|
||||
if [[ "${1:-}" == "-u" ]]; then
|
||||
input="$2"
|
||||
path="${input#c:\\}"
|
||||
path="${path//\\//}"
|
||||
printf '%s/drive_c/%s\n' "$WINEPREFIX" "$path"
|
||||
exit 0
|
||||
fi
|
||||
printf 'wine %s\n' "$*" >> "$WINE_STUB_LOG"
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/wine"
|
||||
|
||||
cat > "${testRoot}/bin/wineserver" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
printf 'wineserver %s\n' "$*" >> "$WINE_STUB_LOG"
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/wineserver"
|
||||
|
||||
export PATH="${testRoot}/bin:$PATH"
|
||||
export UMU_STUB_LOG="${testRoot}/umu.log"
|
||||
export WINE_STUB_LOG="${testRoot}/wine.log"
|
||||
|
||||
# shellcheck source=.includes/proton.sh
|
||||
source "${repoRoot}/.includes/proton.sh"
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local message="$3"
|
||||
if [[ "$expected" != "$actual" ]]; then
|
||||
printf 'FAIL: %s\nexpected: %s\nactual: %s\n' "$message" "$expected" "$actual" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
local message="$3"
|
||||
if ! grep -Fq "$pattern" "$file"; then
|
||||
printf 'FAIL: %s\nmissing pattern: %s\nfile contents:\n' "$message" "$pattern" >&2
|
||||
cat "$file" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_get_umu_bottle_sets_environment() {
|
||||
get_umu_bottle "shadow-line"
|
||||
assert_equals "${XDG_DATA_HOME}/audiogame-manager/protonBottles/shadow-line" "$WINEPREFIX" "WINEPREFIX points at AGM proton bottle"
|
||||
assert_equals "shadow-line" "$GAMEID" "GAMEID is exported"
|
||||
assert_equals "none" "$STORE" "STORE defaults to none"
|
||||
assert_equals ":0" "$DISPLAY" "DISPLAY defaults to :0"
|
||||
}
|
||||
|
||||
test_add_umu_launcher_records_backend_and_game_id() {
|
||||
get_umu_bottle "shadow-line"
|
||||
add_umu_launcher "shadow-line" 'c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
assert_file_contains "$configFile" 'umu|c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe|Shadow Line|export umuGameId=shadow-line' "UMU launcher entry is recorded"
|
||||
}
|
||||
|
||||
test_run_umu_game_uses_converted_path() {
|
||||
get_umu_bottle "shadow-line"
|
||||
mkdir -p "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
|
||||
touch "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe"
|
||||
run_umu_game 'c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
assert_file_contains "$UMU_STUB_LOG" "${WINEPREFIX}|shadow-line|none|${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe" "UMU launches converted exe path"
|
||||
}
|
||||
|
||||
test_get_umu_bottle_sets_environment
|
||||
test_add_umu_launcher_records_backend_and_game_id
|
||||
test_run_umu_game_uses_converted_path
|
||||
printf 'UMU backend tests passed\n'
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and verify they fail because `.includes/proton.sh` is missing**
|
||||
|
||||
Run: `bash tests/umu_backend_tests.sh`
|
||||
|
||||
Expected: FAIL with a message that `.includes/proton.sh` cannot be sourced.
|
||||
|
||||
## Task 2: Implement Generic UMU Helpers
|
||||
|
||||
**Files:**
|
||||
- Create: `.includes/proton.sh`
|
||||
- Test: `tests/umu_backend_tests.sh`
|
||||
|
||||
- [ ] **Step 1: Implement `.includes/proton.sh`**
|
||||
|
||||
Create `.includes/proton.sh`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2034,SC2154 # sourced by audiogame-manager and installers with shared globals
|
||||
|
||||
require_umu() {
|
||||
if command -v umu-run &> /dev/null; then
|
||||
return 0
|
||||
fi
|
||||
local message="This game requires umu-launcher. Please install umu-launcher and try again."
|
||||
if declare -F agm_msgbox &> /dev/null; then
|
||||
agm_msgbox "Audio Game Manager" "Audio Game Manager" "$message"
|
||||
else
|
||||
echo "$message" >&2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
get_umu_bottle() {
|
||||
local gameId="$1"
|
||||
if [[ -z "$gameId" ]]; then
|
||||
echo "get_umu_bottle requires a game id." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
export umuGameId="$gameId"
|
||||
export WINEPREFIX="${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/protonBottles/${gameId}"
|
||||
export GAMEID="$gameId"
|
||||
export STORE="${umuStore:-none}"
|
||||
export DISPLAY="${DISPLAY:-:0}"
|
||||
export UMU_RUNTIME_UPDATE="${UMU_RUNTIME_UPDATE:-0}"
|
||||
mkdir -p "$WINEPREFIX"
|
||||
}
|
||||
|
||||
install_proton_bottle() {
|
||||
local gameId="$1"
|
||||
shift || true
|
||||
require_umu || return 1
|
||||
get_umu_bottle "$gameId" || return 1
|
||||
|
||||
if [[ ! -f "${WINEPREFIX}/system.reg" ]]; then
|
||||
umu-run ""
|
||||
fi
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
umu-run winetricks "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
umu_windows_path_to_unix() {
|
||||
local windowsPath="$1"
|
||||
wine winepath -u "$windowsPath"
|
||||
}
|
||||
|
||||
run_umu_game() {
|
||||
local windowsPath="$1"
|
||||
local exePath=""
|
||||
require_umu || return 1
|
||||
if [[ -z "${umuGameId:-}" ]]; then
|
||||
echo "UMU game id is not set for ${game[2]:-selected game}." >&2
|
||||
return 1
|
||||
fi
|
||||
get_umu_bottle "$umuGameId" || return 1
|
||||
exePath="$(umu_windows_path_to_unix "$windowsPath")"
|
||||
if [[ ! -f "$exePath" ]]; then
|
||||
echo "UMU executable not found: $exePath" >&2
|
||||
return 1
|
||||
fi
|
||||
pushd "${exePath%/*}" > /dev/null || return 1
|
||||
umu-run "$exePath"
|
||||
popd > /dev/null || return 1
|
||||
}
|
||||
|
||||
add_umu_launcher() {
|
||||
local gameId="$1"
|
||||
local windowsPath="$2"
|
||||
shift 2
|
||||
local launchSettings="umu|${windowsPath}|${game}|export umuGameId=${gameId}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
launchSettings+="|$1"
|
||||
shift
|
||||
done
|
||||
|
||||
if ! grep -F -q -x "$launchSettings" "$configFile" 2> /dev/null; then
|
||||
echo "$launchSettings" >> "$configFile"
|
||||
sort -t '|' -k3,3f -o "$configFile" "$configFile"
|
||||
fi
|
||||
}
|
||||
|
||||
set_umu_reg_value() {
|
||||
local key="$1"
|
||||
local valueName="$2"
|
||||
local valueData="$3"
|
||||
wine reg add "$key" /v "$valueName" /t REG_SZ /d "$valueData" /f
|
||||
}
|
||||
|
||||
set_umu_app_winver() {
|
||||
local exeName="$1"
|
||||
local winVersion="$2"
|
||||
set_umu_reg_value "HKCU\\Software\\Wine\\AppDefaults\\${exeName}" "Version" "$winVersion"
|
||||
}
|
||||
|
||||
stop_umu_bottle() {
|
||||
if command -v wineserver &> /dev/null; then
|
||||
wineserver -k 2> /dev/null || true
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run UMU backend tests and verify they pass**
|
||||
|
||||
Run: `bash tests/umu_backend_tests.sh`
|
||||
|
||||
Expected: PASS with `UMU backend tests passed`.
|
||||
|
||||
- [ ] **Step 3: Run shellcheck on new files**
|
||||
|
||||
Run: `shellcheck .includes/proton.sh tests/umu_backend_tests.sh`
|
||||
|
||||
Expected: no output and exit code 0.
|
||||
|
||||
## Task 3: Wire UMU Backend Into AGM Launch, Kill, and Removal
|
||||
|
||||
**Files:**
|
||||
- Modify: `audiogame-manager.sh`
|
||||
- Test: `tests/umu_backend_tests.sh`
|
||||
|
||||
- [ ] **Step 1: Source proton helpers**
|
||||
|
||||
In `audiogame-manager.sh`, after sourcing `.includes/bottle.sh`, add:
|
||||
|
||||
```bash
|
||||
# shellcheck source=.includes/proton.sh
|
||||
source "${scriptDir}/.includes/proton.sh"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Make removal backend-aware**
|
||||
|
||||
In `remove_game`, after `create_game_array "$selectedGame"` and before Wine-only setup, branch on `game[0]`:
|
||||
|
||||
```bash
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
process_launcher_flags
|
||||
get_umu_bottle "$umuGameId"
|
||||
else
|
||||
source "${scriptDir}/.includes/bottle.sh"
|
||||
get_bottle "${game[0]}"
|
||||
fi
|
||||
```
|
||||
|
||||
For directory conversion, replace the Wine-only `winepath` call with:
|
||||
|
||||
```bash
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
gameDir="$(umu_windows_path_to_unix "$winePath")"
|
||||
else
|
||||
gameDir="$(winepath "$winePath")"
|
||||
fi
|
||||
```
|
||||
|
||||
For stopping processes, use:
|
||||
|
||||
```bash
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
stop_umu_bottle
|
||||
else
|
||||
wineserver -k
|
||||
fi
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Make kill backend-aware**
|
||||
|
||||
In `kill_game`, after `create_game_array` or parsing the selected line, ensure UMU entries process launcher flags and call `stop_umu_bottle`:
|
||||
|
||||
```bash
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
process_launcher_flags
|
||||
get_umu_bottle "$umuGameId"
|
||||
stop_umu_bottle
|
||||
else
|
||||
get_bottle "${game%|*}"
|
||||
wineserver -k
|
||||
fi
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Launch UMU entries through `run_umu_game`**
|
||||
|
||||
In `game_launcher`, after `process_launcher_flags` and before Wine-only qjoypad/path work is used, add:
|
||||
|
||||
```bash
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
echo "launching with umu"
|
||||
start_nvda2speechd
|
||||
run_umu_game "${game[1]}"
|
||||
exit 0
|
||||
fi
|
||||
```
|
||||
|
||||
Keep Wine launch behavior unchanged for non-UMU entries.
|
||||
|
||||
- [ ] **Step 5: Run syntax checks**
|
||||
|
||||
Run: `bash -n audiogame-manager.sh`
|
||||
|
||||
Expected: no output and exit code 0.
|
||||
|
||||
- [ ] **Step 6: Run shellcheck on touched shell files**
|
||||
|
||||
Run: `shellcheck audiogame-manager.sh .includes/proton.sh tests/umu_backend_tests.sh`
|
||||
|
||||
Expected: no new actionable errors. Existing intentional warnings may be suppressed locally only if they are not real bugs.
|
||||
|
||||
## Task 4: Update Dependency Reporting
|
||||
|
||||
**Files:**
|
||||
- Modify: `.includes/checkup.sh`
|
||||
|
||||
- [ ] **Step 1: Add UMU check to `.includes/checkup.sh`**
|
||||
|
||||
After the Wine check, add:
|
||||
|
||||
```bash
|
||||
if command -v umu-run &> /dev/null; then
|
||||
[[ $# -eq 0 ]] && echo "umu-launcher is installed."
|
||||
else
|
||||
errorList+=("Warning: umu-launcher is not installed. Games that require Proton/UMU will not install or launch.")
|
||||
fi
|
||||
packageList+=("umu-launcher")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run syntax and shellcheck**
|
||||
|
||||
Run: `bash -n .includes/checkup.sh`
|
||||
|
||||
Expected: no output and exit code 0.
|
||||
|
||||
Run: `shellcheck .includes/checkup.sh`
|
||||
|
||||
Expected: no output and exit code 0, or only pre-existing intentional warnings.
|
||||
|
||||
## Task 5: Migrate Shadow Line Installer to UMU
|
||||
|
||||
**Files:**
|
||||
- Modify: `.install/Shadow Line.sh`
|
||||
|
||||
- [ ] **Step 1: Replace installer body**
|
||||
|
||||
Update `.install/Shadow Line.sh` to:
|
||||
|
||||
```bash
|
||||
# shellcheck shell=bash disable=SC2154 # cache, game, and helper functions are set by audiogame-manager
|
||||
download "https://www.mm-galabo.com/sr/Download_files_srfv/shadowrine_fullvoice3.171.exe" \
|
||||
"https://raw.githubusercontent.com/LordLuceus/sr-english-localization/master/language_en.dat" \
|
||||
"${nvdaControllerClient32Dll}" \
|
||||
"${nvdaControllerClient64Dll}"
|
||||
|
||||
export game="Shadow Line"
|
||||
shadowLineGameId="shadow-line"
|
||||
shadowLinePath='c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
shadowLineInstallDir="${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
|
||||
|
||||
install_proton_bottle "$shadowLineGameId" fakejapanese
|
||||
shadowLineInstallDir="${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
|
||||
|
||||
set_umu_reg_value "HKCU\\Software\\Wine\\DllOverrides" "bcryptprimitives" "native,builtin"
|
||||
set_umu_app_winver "play_sr.exe" "win8"
|
||||
|
||||
{
|
||||
echo "# Installing Shadow Line..."
|
||||
timeout 300 umu-run "${cache}/shadowrine_fullvoice3.171.exe" /sp- /VERYSILENT /SUPPRESSMSGBOXES 2>&1 || true
|
||||
echo "# Installation complete"
|
||||
} | agm_progressbox "Installing Game" "Installing Shadow Line with UMU/Proton (this may take a few minutes)..."
|
||||
|
||||
stop_umu_bottle
|
||||
|
||||
if [[ ! -f "${shadowLineInstallDir}/play_sr.exe" ]]; then
|
||||
agm_msgbox "Shadow Line" "Shadow Line" "Shadow Line did not install to the expected location: ${shadowLineInstallDir}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -m 0644 "${cache}/language_en.dat" "${shadowLineInstallDir}/SystemData/language_en.dat"
|
||||
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient32.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient64.dll' -exec cp -v "${cache}/nvdaControllerClient64.dll" "{}" \;
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
|
||||
|
||||
add_umu_launcher "$shadowLineGameId" "$shadowLinePath"
|
||||
alert "Shadow Line" "Shadow Line" "Please set the language to English when the game opens.\nGo to options and press enter.\nPress down arrow 5 times and press enter.\nPress down arrow 1 time and press enter.\nPress up arrow 2 times and press enter.\nIf everything worked as expected you should be back on the game menu and speech should work."
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run syntax check**
|
||||
|
||||
Run: `bash -n ".install/Shadow Line.sh"`
|
||||
|
||||
Expected: no output and exit code 0.
|
||||
|
||||
- [ ] **Step 3: Run shellcheck on Shadow Line installer**
|
||||
|
||||
Run: `shellcheck ".install/Shadow Line.sh"`
|
||||
|
||||
Expected: no output and exit code 0, or only intentional sourced-global warnings suppressed by the file header.
|
||||
|
||||
## Task 6: End-to-End Verification
|
||||
|
||||
**Files:**
|
||||
- Modify as needed based on verification findings.
|
||||
|
||||
- [ ] **Step 1: Run focused unit tests**
|
||||
|
||||
Run: `bash tests/umu_backend_tests.sh`
|
||||
|
||||
Expected: PASS with `UMU backend tests passed`.
|
||||
|
||||
- [ ] **Step 2: Run syntax checks for all touched scripts**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
bash -n audiogame-manager.sh
|
||||
bash -n .includes/proton.sh
|
||||
bash -n .includes/checkup.sh
|
||||
bash -n ".install/Shadow Line.sh"
|
||||
bash -n tests/umu_backend_tests.sh
|
||||
```
|
||||
|
||||
Expected: all commands exit 0.
|
||||
|
||||
- [ ] **Step 3: Run shellcheck for all touched Bash files**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
shellcheck audiogame-manager.sh .includes/proton.sh .includes/checkup.sh ".install/Shadow Line.sh" tests/umu_backend_tests.sh
|
||||
```
|
||||
|
||||
Expected: no actionable warnings.
|
||||
|
||||
- [ ] **Step 4: Optional live install verification**
|
||||
|
||||
If the user approves running the live installer, run:
|
||||
|
||||
```bash
|
||||
DISPLAY=:0 ./audiogame-manager.sh -I "Shadow Line"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- Shadow Line installs into `${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/protonBottles/shadow-line`.
|
||||
- `games.conf` contains `umu|...|Shadow Line|export umuGameId=shadow-line`.
|
||||
- `play_sr.exe` exists under the Shadow Line Proton prefix.
|
||||
- `SystemData/language_en.dat` exists.
|
||||
- `user.reg` contains `bcryptprimitives` and `AppDefaults\\play_sr.exe` with `Version=win8`.
|
||||
|
||||
- [ ] **Step 5: Commit implementation**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git add .includes/proton.sh .includes/checkup.sh ".install/Shadow Line.sh" audiogame-manager.sh tests/umu_backend_tests.sh docs/superpowers/plans/2026-05-05-umu-proton-backend.md
|
||||
git commit -m "Add UMU Proton backend for Shadow Line"
|
||||
```
|
||||
|
||||
Expected: commit succeeds and unrelated pre-existing dirty files remain unstaged.
|
||||
|
||||
## Self-Review
|
||||
|
||||
- Spec coverage: generic UMU helpers, Shadow Line migration, dependency checks, error handling, and verification are each covered by tasks.
|
||||
- Placeholder scan: no TODO/TBD placeholders remain.
|
||||
- Type consistency: helper names are consistent across tests, implementation, installer, and launcher tasks.
|
||||
@@ -0,0 +1,64 @@
|
||||
# UMU Proton Backend Design
|
||||
|
||||
## Goal
|
||||
|
||||
Add a general UMU/Proton backend to audiogame-manager so selected games can install and launch through `umu-run` with AGM-managed Proton prefixes. Shadow Line is the first supported game and should be installable, launchable, translated to English, and speech-capable without changing the existing Wine backend for other games.
|
||||
|
||||
## Scope
|
||||
|
||||
This change introduces generic UMU helpers and wires Shadow Line to use them. It does not migrate existing Wine games, add proton-voices, or reimplement `umu-launcher`. AGM depends on `umu-run` for Proton/runtime management because UMU handles Proton selection, Steam Runtime setup, prefix creation, protonfixes, and Proton winetricks routing.
|
||||
|
||||
## Architecture
|
||||
|
||||
Launcher entries gain a new backend value in the first `games.conf` field:
|
||||
|
||||
```text
|
||||
umu|c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe|Shadow Line|...
|
||||
```
|
||||
|
||||
The existing Wine values (`win64`, `win32`) continue to behave as they do now. When the backend is `umu`, launch setup uses a dedicated Proton prefix under:
|
||||
|
||||
```text
|
||||
${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/protonBottles/<game-id>
|
||||
```
|
||||
|
||||
The game id is supplied by the installer and stored in launcher flags, for example `export umuGameId=shadow-line`. Helpers will centralize backend behavior:
|
||||
|
||||
- `install_proton_bottle <game-id> [winetricks verbs...]`: create and select a UMU prefix, initialize it with `umu-run ""`, and install optional Proton winetricks verbs with `umu-run winetricks`.
|
||||
- `get_umu_bottle <game-id>`: export `WINEPREFIX`, `GAMEID`, `STORE`, and `DISPLAY`.
|
||||
- `run_umu_game <windows-path>`: convert the Windows path to a Unix executable path in the UMU prefix and run it through `umu-run`.
|
||||
- `add_umu_launcher <game-id> <windows-path> [flags...]`: append a `umu|...` launcher line.
|
||||
|
||||
## Shadow Line
|
||||
|
||||
Shadow Line uses the generic UMU backend with `umuGameId=shadow-line`. Its installer will:
|
||||
|
||||
1. Require `umu-run`.
|
||||
2. Download `shadowrine_fullvoice3.171.exe` and `language_en.dat`.
|
||||
3. Create/select the Shadow Line UMU prefix.
|
||||
4. Apply `bcryptprimitives=native,builtin`.
|
||||
5. Apply per-app `win8` for `play_sr.exe`.
|
||||
6. Run the installer silently with UMU.
|
||||
7. Stop any leftover Wine/Proton processes for the prefix.
|
||||
8. Copy `language_en.dat` into `SystemData`.
|
||||
9. Copy AGM's `nvdaControllerClient32.dll` and/or `nvdaControllerClient64.dll` over any matching game DLLs found in the Shadow Line install.
|
||||
10. Register the UMU launcher and show the first-run instructions for switching the game language to English.
|
||||
|
||||
Clipboard translation will not be used for Shadow Line in this implementation.
|
||||
|
||||
## Dependency Checks
|
||||
|
||||
`check_requirements` remains focused on core AGM dependencies, but UMU installers call a targeted `require_umu` helper so the error is clear at install time. The `-c` checkup report will also list `umu-run`: missing UMU is reported as required for Proton-backed games, not as a blocker for every Wine game.
|
||||
|
||||
## Error Handling
|
||||
|
||||
UMU helper functions fail fast with clear messages when `umu-run` is missing, prefix initialization fails, the installer does not produce the expected executable, or the translation file cannot be copied. Shadow Line should not be added to `games.conf` unless the expected executable exists.
|
||||
|
||||
## Verification
|
||||
|
||||
Verification will include:
|
||||
|
||||
- `bash -n` on changed shell scripts.
|
||||
- `shellcheck` on changed Bash files.
|
||||
- A focused install-path dry run where possible without launching the interactive game.
|
||||
- Confirmation that Shadow Line creates a `umu|...|Shadow Line` launcher entry and that the UMU prefix contains `play_sr.exe`, `language_en.dat`, registry settings, and replacement NVDA DLLs when matching DLLs exist.
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC1091,SC2034,SC2154
|
||||
|
||||
export game="Magic: The Gathering Arena"
|
||||
export WINEARCH=win64
|
||||
export winVer="win10"
|
||||
export DIALOGOPTS='--no-lines --visit-items'
|
||||
export ipfsGateway="${ipfsGateway:-https://ipfs.stormux.org}"
|
||||
|
||||
cache="${XDG_CACHE_HOME:-$HOME/.cache}/audiogame-manager"
|
||||
winetricksPath="${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager"
|
||||
mkdir -p "$cache" "$winetricksPath"
|
||||
|
||||
if [[ -z "$DISPLAY" ]]; then
|
||||
dialogType="dialog"
|
||||
export DISPLAY=":0"
|
||||
elif command -v yad &> /dev/null; then
|
||||
dialogType="yad"
|
||||
else
|
||||
dialogType="dialog"
|
||||
fi
|
||||
|
||||
source "${0%/*}/../.includes/dialog-interface.sh"
|
||||
source "${0%/*}/../.includes/functions.sh"
|
||||
source "${0%/*}/../.includes/bottle.sh"
|
||||
|
||||
export nvdaControllerClient64Dll="${ipfs[nvdaControllerClient64]}"
|
||||
|
||||
mtgaVersionUrl="https://mtgarena.downloads.wizards.com/Live/Windows32/version"
|
||||
accessibleArenaDllUrl="https://github.com/JeanStiletto/AccessibleArena/releases/latest/download/AccessibleArena.dll"
|
||||
melonLoaderZipUrl="https://github.com/LavaGang/MelonLoader/releases/latest/download/MelonLoader.x64.zip"
|
||||
tolkDllUrl="https://stormgames.wolfe.casa/downloads/Tolk.dll"
|
||||
|
||||
get_mtga_installer_url() {
|
||||
local versionJson=""
|
||||
local installerUrl=""
|
||||
|
||||
if ! versionJson="$(curl -fsSL "$mtgaVersionUrl")"; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not fetch the current MTG Arena installer URL."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
installerUrl="$(sed -n 's/.*"CurrentInstallerURL"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' <<< "$versionJson" | head -n1)"
|
||||
if [[ -z "$installerUrl" ]]; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not parse the current MTG Arena installer URL."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$installerUrl"
|
||||
}
|
||||
|
||||
download_mtga_installer() {
|
||||
local installerUrl="$1"
|
||||
local installerFile="${installerUrl##*/}"
|
||||
installerFile="${installerFile%%\?*}"
|
||||
[[ -n "$installerFile" ]] || installerFile="MTGAInstaller.msi"
|
||||
|
||||
rm -f "${cache}/${installerFile}" 2> /dev/null
|
||||
if ! curl -L4 -C - --retry 10 --output "${cache}/${installerFile}" "$installerUrl" 2>&1 | agm_progressbox "Magic: The Gathering Arena Update" "Downloading MTG Arena update..."; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not download the MTG Arena update."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mtgaInstallerPath="${cache}/${installerFile}"
|
||||
}
|
||||
|
||||
configure_accessible_arena_loader() {
|
||||
local mtgaRoot="$1"
|
||||
local userDataPath="${mtgaRoot}/UserData"
|
||||
local loaderConfig="${userDataPath}/Loader.cfg"
|
||||
|
||||
mkdir -p "$userDataPath"
|
||||
if [[ -f "$loaderConfig" ]]; then
|
||||
if grep -Fq "hide_console = false" "$loaderConfig"; then
|
||||
sed -i 's/hide_console = false/hide_console = true/g' "$loaderConfig"
|
||||
elif ! grep -Fq "hide_console" "$loaderConfig"; then
|
||||
printf '\n[console]\nhide_console = true\n' >> "$loaderConfig"
|
||||
fi
|
||||
else
|
||||
printf '[console]\nhide_console = true\n' > "$loaderConfig"
|
||||
fi
|
||||
}
|
||||
|
||||
install_accessible_arena_support() {
|
||||
local mtgaRoot="$1"
|
||||
|
||||
download "$accessibleArenaDllUrl" "$melonLoaderZipUrl" "$tolkDllUrl" "$nvdaControllerClient64Dll"
|
||||
|
||||
install_with_progress unzip "Reinstalling MelonLoader..." -d "$mtgaRoot" "${cache}/MelonLoader.x64.zip"
|
||||
mkdir -p "${mtgaRoot}/Mods"
|
||||
install_with_progress cp "Installing Accessible Arena..." "${cache}/AccessibleArena.dll" "${mtgaRoot}/Mods/AccessibleArena.dll"
|
||||
install_with_progress cp "Installing screen reader support DLLs..." "${cache}/Tolk.dll" "${cache}/nvdaControllerClient64.dll" "$mtgaRoot"
|
||||
configure_accessible_arena_loader "$mtgaRoot"
|
||||
}
|
||||
|
||||
check_requirements || exit 1
|
||||
install_wine_bottle dxvk
|
||||
winetricks -q $winVer
|
||||
|
||||
mtgaRoot="${WINEPREFIX}/drive_c/Program Files (x86)/Wizards of the Coast/MTGA"
|
||||
mtgaInstallerUrl="$(get_mtga_installer_url)"
|
||||
download_mtga_installer "$mtgaInstallerUrl"
|
||||
|
||||
wine msiexec /i "$mtgaInstallerPath" /q
|
||||
|
||||
if [[ ! -f "${mtgaRoot}/MTGA.exe" ]]; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "MTG Arena was not found at the expected location: ${mtgaRoot}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install_accessible_arena_support "$mtgaRoot"
|
||||
agm_msgbox "Magic: The Gathering Arena Update" "Magic: The Gathering Arena Update" "Magic: The Gathering Arena and Accessible Arena have been updated."
|
||||
@@ -7,20 +7,27 @@
|
||||
# set -Eeuo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/#:~:text=set%20%2Du,is%20often%20highly%20desirable%20behavior.
|
||||
shopt -s expand_aliases
|
||||
|
||||
# Debug logging
|
||||
#DEBUG_LOG="${XDG_CACHE_HOME:-$HOME/.cache}/audiogame-manager/translator-debug.log"
|
||||
#exec 2>>"$DEBUG_LOG"
|
||||
#echo "=== Translator started at $(date) ===" >&2
|
||||
#echo "Args: $@" >&2
|
||||
#set -x
|
||||
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "Usage: $0 \"application name\" \"file name\"."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
pythonScript="${scriptDir}/translate_clipboard.py"
|
||||
if [[ -z "${AGM_TRANSLATOR_FORCE_SHELL:-}" ]] && command -v python3 &> /dev/null && [[ -f "$pythonScript" ]]; then
|
||||
exec python3 "$pythonScript" "$@"
|
||||
fi
|
||||
|
||||
is_app_running() {
|
||||
local appPattern="$1"
|
||||
local selfPid="$$"
|
||||
|
||||
pgrep -af -u "$USER" "$appPattern" \
|
||||
| awk -v selfPid="$selfPid" '$1 != selfPid && $0 !~ /clipboard_translator.sh/ { found=1 } END { exit !found }'
|
||||
}
|
||||
|
||||
# Wait for the application to start
|
||||
while ! pgrep -f -u "$USER" "$1" &> /dev/null ; do
|
||||
while ! is_app_running "$1"; do
|
||||
sleep 0.05
|
||||
done
|
||||
|
||||
@@ -44,26 +51,8 @@ CREATE INDEX translations_text_idx ON translations (text);
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Define a function to safely query the database
|
||||
query_database() {
|
||||
local dbFile="$1"
|
||||
local sqlQuery="$2"
|
||||
sqlite3 -line "$dbFile" "$sqlQuery"
|
||||
}
|
||||
|
||||
# Define a function to safely insert into the database
|
||||
insert_database() {
|
||||
local dbFile="$1"
|
||||
local text="$2"
|
||||
local translation="$3"
|
||||
|
||||
# Use sqlite3 .import feature which is more robust for special characters
|
||||
echo "$text|$translation" | sqlite3 -separator "|" "$dbFile" ".import /dev/stdin temp_import"
|
||||
sqlite3 "$dbFile" "INSERT OR IGNORE INTO translations SELECT * FROM temp_import; DROP TABLE IF EXISTS temp_import;"
|
||||
}
|
||||
|
||||
# Read so long as the application is running
|
||||
while pgrep -f -u "$USER" "$1" &> /dev/null ; do
|
||||
while is_app_running "$1"; do
|
||||
sleep 0.05
|
||||
text="$(xclip -d "${DISPLAY:-:0}" -selection clipboard -o 2> /dev/null)"
|
||||
if [[ -f ~/.agmsilent ]]; then
|
||||
@@ -80,44 +69,48 @@ while pgrep -f -u "$USER" "$1" &> /dev/null ; do
|
||||
|
||||
# https://en.wikipedia.org/wiki/Unicode_equivalence#Combining_and_precomposed_characters
|
||||
# https://www.effectiveperlprogramming.com/2011/09/normalize-your-perl-source/
|
||||
alias nfc="perl -MUnicode::Normalize -CS -ne 'print NFC(\$_)'" # composed characters
|
||||
nfc() {
|
||||
perl -MUnicode::Normalize -CS -ne 'print NFC($_)'
|
||||
}
|
||||
|
||||
# Normalize different unicode space characters to the same space
|
||||
# https://stackoverflow.com/a/43640405
|
||||
alias normalizeSpaces="perl -CSDA -plE 's/[^\\S\\t]/ /g'"
|
||||
alias normalizeUnicode="normalizeSpaces | nfc"
|
||||
normalize_spaces() {
|
||||
perl -CSDA -plE 's/[^\S\t]/ /g'
|
||||
}
|
||||
# Remove zero-width spaces (U+200B) that some games insert between characters
|
||||
strip_zwsp() {
|
||||
perl -CSDA -plE 's/\x{200B}//g'
|
||||
}
|
||||
normalize_unicode() {
|
||||
strip_zwsp | normalize_spaces | nfc
|
||||
}
|
||||
|
||||
# Normalize text
|
||||
normalizedText="$(echo "$text" | normalizeUnicode)"
|
||||
normalizedText="$(echo "$text" | normalize_unicode)"
|
||||
|
||||
# Create a temporary database for import
|
||||
sqlite3 "$translationFile" "CREATE TABLE IF NOT EXISTS temp_import(text TEXT, translation TEXT);"
|
||||
|
||||
# Check if we already have a translation
|
||||
translated=$(sqlite3 "$translationFile" "SELECT translation FROM translations WHERE text = '$normalizedText' LIMIT 1;" 2>/dev/null)
|
||||
echo "DEBUG: Database lookup result: '$translated'" >&2
|
||||
|
||||
if [[ -z "$translated" ]]; then
|
||||
echo "DEBUG: No cached translation, calling trans command" >&2
|
||||
# Get translation from the trans utility
|
||||
translated="$(trans -no-autocorrect -no-warn -brief "$normalizedText" | head -1 | sed 's/\s*$//' | normalizeUnicode)"
|
||||
echo "DEBUG: trans returned: '$translated'" >&2
|
||||
translated="$(trans -no-autocorrect -no-warn -brief "$normalizedText" | head -1 | sed 's/\s*$//' | normalize_unicode)"
|
||||
|
||||
if [[ -n "$translated" ]]; then
|
||||
# Insert using echo piping to avoid escaping issues
|
||||
echo "$normalizedText|$translated" | sqlite3 -separator "|" "$translationFile" ".import /dev/stdin temp_import"
|
||||
sqlite3 "$translationFile" "INSERT OR IGNORE INTO translations SELECT * FROM temp_import; DELETE FROM temp_import;"
|
||||
echo "DEBUG: Saved to database" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we got a translation, speak it
|
||||
if [[ -n "$translated" ]]; then
|
||||
echo "DEBUG: Speaking translation: '$translated'" >&2
|
||||
spd-say -- "$translated"
|
||||
else
|
||||
echo "DEBUG: No translation available, speaking original: '$text'" >&2
|
||||
spd-say -- "$text"
|
||||
spd-say -- "$normalizedText"
|
||||
fi
|
||||
|
||||
# Clear clipboard
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repoRoot="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
testRoot="$(mktemp -d)"
|
||||
trap 'rm -rf "$testRoot"' EXIT
|
||||
|
||||
export HOME="${testRoot}/home"
|
||||
export XDG_DATA_HOME="${HOME}/.local/share"
|
||||
export XDG_CONFIG_HOME="${HOME}/.config"
|
||||
export XDG_CACHE_HOME="${HOME}/.cache"
|
||||
export DISPLAY=""
|
||||
export cache="${XDG_CACHE_HOME}/audiogame-manager"
|
||||
export configFile="${XDG_CONFIG_HOME}/storm-games/audiogame-manager/games.conf"
|
||||
export game="Shadow Line"
|
||||
export scriptDir="$repoRoot"
|
||||
mkdir -p "$cache" "${configFile%/*}" "${testRoot}/bin"
|
||||
touch "$configFile"
|
||||
|
||||
cat > "${testRoot}/bin/umu-run" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s|%s|%s|%s\n' "$WINEPREFIX" "$GAMEID" "${STORE:-}" "$*" >> "$UMU_STUB_LOG"
|
||||
if [[ "${1:-}" == "" ]]; then
|
||||
mkdir -p "$WINEPREFIX/drive_c"
|
||||
fi
|
||||
if [[ "${1:-}" == "winetricks" && "${3:-}" == "already-installed" ]]; then
|
||||
printf "winetricks verb 'already-installed' is already installed\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/umu-run"
|
||||
|
||||
cat > "${testRoot}/bin/wine" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
if [[ "${1:-}" == "winepath" || "${1:-}" == "winepath.exe" ]]; then
|
||||
shift
|
||||
fi
|
||||
if [[ "${1:-}" == "-u" ]]; then
|
||||
input="$2"
|
||||
path="${input#c:\\}"
|
||||
path="${path//\\//}"
|
||||
printf '%s/drive_c/%s\n' "$WINEPREFIX" "$path"
|
||||
exit 0
|
||||
fi
|
||||
printf 'wine %s\n' "$*" >> "$WINE_STUB_LOG"
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/wine"
|
||||
|
||||
cat > "${testRoot}/bin/wineserver" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
printf 'wineserver %s\n' "$*" >> "$WINE_STUB_LOG"
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/wineserver"
|
||||
|
||||
export PATH="${testRoot}/bin:$PATH"
|
||||
export UMU_STUB_LOG="${testRoot}/umu.log"
|
||||
export WINE_STUB_LOG="${testRoot}/wine.log"
|
||||
|
||||
# shellcheck source=.includes/proton.sh
|
||||
source "${repoRoot}/.includes/proton.sh"
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local message="$3"
|
||||
if [[ "$expected" != "$actual" ]]; then
|
||||
printf 'FAIL: %s\nexpected: %s\nactual: %s\n' "$message" "$expected" "$actual" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
local message="$3"
|
||||
if ! grep -Fq "$pattern" "$file"; then
|
||||
printf 'FAIL: %s\nmissing pattern: %s\nfile contents:\n' "$message" "$pattern" >&2
|
||||
cat "$file" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_get_umu_bottle_sets_environment() {
|
||||
get_umu_bottle "shadow-line"
|
||||
assert_equals "${XDG_DATA_HOME}/audiogame-manager/protonBottles/shadow-line" "$WINEPREFIX" "WINEPREFIX points at AGM proton bottle"
|
||||
assert_equals "shadow-line" "$GAMEID" "GAMEID is exported"
|
||||
assert_equals "none" "$STORE" "STORE defaults to none"
|
||||
assert_equals ":0" "$DISPLAY" "DISPLAY defaults to :0"
|
||||
}
|
||||
|
||||
test_add_umu_launcher_records_backend_and_game_id() {
|
||||
get_umu_bottle "shadow-line"
|
||||
add_umu_launcher "shadow-line" 'c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
assert_file_contains "$configFile" 'umu|c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe|Shadow Line|export umuGameId=shadow-line' "UMU launcher entry is recorded"
|
||||
}
|
||||
|
||||
test_add_umu_launcher_records_extra_environment_flags() {
|
||||
get_umu_bottle "entombed"
|
||||
game="Entombed"
|
||||
add_umu_launcher "entombed" 'c:\Program Files (x86)\Entombed\Entombed.exe' "export PROTON_USE_XALIA=0"
|
||||
assert_file_contains "$configFile" 'umu|c:\Program Files (x86)\Entombed\Entombed.exe|Entombed|export umuGameId=entombed|export PROTON_USE_XALIA=0' "UMU launcher entry records extra environment flags"
|
||||
}
|
||||
|
||||
test_run_umu_game_uses_converted_path() {
|
||||
get_umu_bottle "shadow-line"
|
||||
mkdir -p "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
|
||||
touch "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe"
|
||||
run_umu_game 'c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
assert_file_contains "$UMU_STUB_LOG" "${WINEPREFIX}|shadow-line|none|${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe" "UMU launches converted exe path"
|
||||
}
|
||||
|
||||
test_install_crlf_file_normalizes_line_endings() {
|
||||
local sourceFile="${testRoot}/language_en.dat"
|
||||
local destFile="${testRoot}/installed/language_en.dat"
|
||||
local expectedFile="${testRoot}/expected-language_en.dat"
|
||||
printf 'line one\nline two\n' > "$sourceFile"
|
||||
printf 'line one\r\nline two\r\n' > "$expectedFile"
|
||||
|
||||
install_crlf_file "$sourceFile" "$destFile"
|
||||
|
||||
if ! cmp -s "$expectedFile" "$destFile"; then
|
||||
printf 'FAIL: Translation file is installed with CRLF line endings\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_install_proton_winetricks_verb_ignores_already_installed() {
|
||||
get_umu_bottle "shadow-line"
|
||||
|
||||
install_proton_winetricks_verb "already-installed"
|
||||
}
|
||||
|
||||
test_get_umu_bottle_sets_environment
|
||||
test_add_umu_launcher_records_backend_and_game_id
|
||||
test_add_umu_launcher_records_extra_environment_flags
|
||||
test_run_umu_game_uses_converted_path
|
||||
test_install_crlf_file_normalizes_line_endings
|
||||
test_install_proton_winetricks_verb_ignores_already_installed
|
||||
printf 'UMU backend tests passed\n'
|
||||
Reference in New Issue
Block a user