9 Commits

Author SHA1 Message Date
Storm Dragon 92745d8af8 Fix Shadow Line translation line endings 2026-05-05 02:58:12 -04:00
Storm Dragon 1f977bb1f4 Add UMU Proton backend for Shadow Line 2026-05-05 02:58:12 -04:00
Storm Dragon 53d8c10645 Plan UMU Proton backend implementation 2026-05-05 02:58:12 -04:00
Storm Dragon b58681964c Document UMU Proton backend design 2026-05-05 02:58:12 -04:00
lilmike 2abf445637 Fix error installing mtga if a previous game was using win xp. 2026-05-02 16:57:54 -07:00
lilmike c0e6c37f1a Add Magic: the Gathering Arena 2026-05-02 13:03:40 -07:00
Storm Dragon 856415c22f Forgot the shellcheck statement. 2026-04-23 13:34:38 -04:00
Storm Dragon 7ddd7cbac4 Judgement Day download link updated. 2026-04-23 13:32:37 -04:00
Storm Dragon 951fa7c1ba Update alert function messaging
Show the actual guidance in a single acknowledgement dialog instead of a generic OK prompt, and update alert-based installer instructions to use the new helper contract.
2026-03-02 23:01:50 -05:00
22 changed files with 1196 additions and 85 deletions
+8
View File
@@ -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
+24 -8
View File
@@ -1,13 +1,31 @@
#!/usr/bin/env bash
# shellcheck disable=SC2034,SC2154 # sourced by audiogame-manager with shared globals and exported state
# Source IPFS game URLs
source "${BASH_SOURCE[0]%/*}/ipfs.sh"
# Alerts, for when user needs to read something.
alert() {
local title="Alert"
local backTitle=""
local message="Press OK to continue."
if [[ $# -eq 1 ]]; then
message="$1"
elif [[ $# -eq 2 ]]; then
title="$1"
backTitle="$1"
message="$2"
elif [[ $# -ge 3 ]]; then
title="$1"
backTitle="$2"
shift 2
message="$*"
fi
play -qnV0 synth 3 pluck D3 pluck A3 pluck D4 pluck F4 pluck A4 delay 0 .1 .2 .3 .4 remix - chorus 0.9 0.9 38 0.75 0.3 0.5 -t
echo
agm_msgbox "Alert" "" "Press OK to continue."
agm_msgbox "${title}" "${backTitle}" "${message}"
}
check_requirements() {
@@ -54,7 +72,7 @@ clear_cache() {
}
download() {
local source=($@)
local source=("$@")
for i in "${source[@]}" ; do
local dest="${i##*/}"
dest="${dest//%20/ }"
@@ -120,8 +138,7 @@ download() {
esac
if [[ $downloadError -ne 0 ]]; then
rm -fv "${cache}/${dest}"
agm_infobox "Audio Game Manager" "Audio Game Manager" "Error downloading \"${dest}\". Installation cannot continue."
alert
alert "Audio Game Manager" "Audio Game Manager" "Error downloading \"${dest}\". Installation cannot continue."
exit 1
fi
done
@@ -142,10 +159,10 @@ get_installer() {
if echo "$2" | xclip -selection clipboard 2> /dev/null ; then
message+="\n\nThe URL has been copied to the clipboard."
fi
agm_msgbox "Audiogame Manager" "Audiogame Manager" "$message"
alert "Audiogame Manager" "Audiogame Manager" "$message"
# Search the Desktop and Downloads directories for the installation file
for i in ~/Downloads ~/Desktop ; do
find $i -type f -name "$1" -exec cp -v {} "${cache}/" \;
find "$i" -type f -name "$1" -exec cp -v {} "${cache}/" \;
done
# If the file is still not available abort.
if [[ ! -f "${cache}/$1" ]]; then
@@ -157,8 +174,7 @@ get_installer() {
get_steam() {
# Arguments: $1 id of item for download, $2 url for game
trap "exit 0" SIGINT
echo "manual intervention required."
alert
alert "Audiogame Manager" "Audiogame Manager" "Manual intervention required."
agm_yesno "Audiogame Manager" "Audiogame Manager" "To install the game manually, place files in \"${WINEPREFIX}/drive_c/Program Files/${game}\". Continue with Steam installation?"
case $? in
0) echo "The next steps will install through steamcmd." ;;
+28 -2
View File
@@ -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"
+119
View File
@@ -0,0 +1,119 @@
#!/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
}
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
}
+2 -3
View File
@@ -1,9 +1,8 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
download "https://github.com/matatk/agrip/releases/download/2020.0-beta1/AudioQuake+LDL_2020.0-beta1_Windows.zip" "https://stormgames.wolfe.casa/downloads/quake.zip"
export winVer="win7"
install_wine_bottle
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files" "${cache}/AudioQuake+LDL_2020.0-beta1_Windows.zip"
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files/AudioQuake/id1" "${cache}/quake.zip"
add_launcher "c:\Program Files\AudioQuake\AudioQuake.exe"
echo
echo "After you launch the game, press tab then enter and it should begin speaking."
alert
alert "After you launch the game, press tab then enter and it should begin speaking."
+3 -3
View File
@@ -1,3 +1,4 @@
# shellcheck shell=bash disable=SC2154 # cache, WINEPREFIX, and game are set by audiogame-manager
gameVersion=2.4.1
download "https://dl.tweesecake.app/rage/rage${gameVersion}.zip"
export WINEARCH=win64
@@ -6,9 +7,8 @@ install_wine_bottle
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files/" "${cache}/rage${gameVersion}.zip"
add_launcher "c:\Program Files\rage\rage.exe"
url="https://techcake.games/games/executioners-rage/"
echo "Before you can login, you need to create an account at:"
echo "$url"
message="Before you can login, you need to create an account at:\n${url}"
if echo "$url" | xclip -selection clipboard 2> /dev/null ; then
message+="\n\nThe URL has been copied to the clipboard."
fi
alert
alert "Executioner's Rage" "Executioner's Rage" "$message"
+2 -3
View File
@@ -1,8 +1,7 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
get_installer "Galactic Strike 1.2.zip" "https://fusion-forged-games.itch.io/galactic-strike"
export winVer="win10"
install_wine_bottle
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files/Galactic Strike" "${cache}/Galactic Strike 1.2.zip"
add_launcher "c:\Program Files\Galactic Strike\Galactic Strike.exe"
echo "Use controls wasd to movi and navigate the menu."
echo "Use m to fire and select items from the menu."
alert
alert "Use controls wasd to movi and navigate the menu.\nUse m to fire and select items from the menu."
+2 -2
View File
@@ -1,6 +1,6 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
download "https://stormgames.wolfe.casa/downloads/grizzly-gulch.zip"
install_wine_bottle vb6run mfc42
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files" "$cache/grizzly-gulch.zip"
add_launcher "c:\Program Files\grizzly-gulch\Grizzly Gulch.exe"
echo "If the combat sequences happen too slow for your tastes, while in the town square, you can use f12 to turn up the combat speed and f11 to lower it."
alert
alert "If the combat sequences happen too slow for your tastes, while in the town square, you can use f12 to turn up the combat speed and f11 to lower it."
+2 -1
View File
@@ -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
+2 -2
View File
@@ -1,3 +1,4 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
export WINEARCH="win64" # Migrated to wine64 with WINETRICKS_FORCE=1
export winVer="win7"
export winetricksSettings="vd=1024x768"
@@ -6,5 +7,4 @@ install_wine_bottle sapi vb6run dx8vb quartz
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files/lunimals" "${cache}/lunimals.zip"
wine 'c:\Program Files\lunimals\checkup.exe' /verysilent
add_launcher "c:\Program Files\lunimals\Lunimals.exe"
echo "Note: Lunimals installed. Once you start the game, you must press tab until you hear sapi on to get speech." >&2
alert
alert "Note: Lunimals installed. Once you start the game, you must press tab until you hear sapi on to get speech."
+95
View File
@@ -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'
+2 -10
View File
@@ -1,3 +1,4 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
export WINEARCH="win64" # Migrated to wine64
export winVer="win7"
@@ -8,13 +9,4 @@ sed -i 's/1024m/768m/g' "$WINEPREFIX/drive_c/Program Files/Mist World/mw.exe.vmo
cp "$WINEPREFIX/drive_c/Program Files/Mist World/"{mw.exe.vmoptions,update.exe.vmoptions}
mkdir "$WINEPREFIX/drive_c/Program Files/Mist World/"{user,users}
add_launcher 'c:\Program Files\Mist World\mw.exe'
echo
echo "If you do not have an account, There is a script in game-scripts to help."
echo "Launch the game, press enter on create account, then drop into a console so the game window does not lose focus."
echo "Change to the game-scripts directory and run"
echo "./mist_world_account_creator.sh and follow the prompts."
echo
echo "To login, type your email address, press tab, and type your password."
echo "If you want to enable automatic login, press tab two times followed by space, then tab and enter."
echo "If you do not want to auto login, you can just press enter after typing your password."
alert
alert "If you do not have an account, There is a script in game-scripts to help.\nLaunch the game, press enter on create account, then drop into a console so the game window does not lose focus.\nChange to the game-scripts directory and run\n./mist_world_account_creator.sh and follow the prompts.\n\nTo login, type your email address, press tab, and type your password.\nIf you want to enable automatic login, press tab two times followed by space, then tab and enter.\nIf you do not want to auto login, you can just press enter after typing your password."
+3 -2
View File
@@ -1,8 +1,9 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
# shellcheck disable=SC2034 # consumed by shared bottle setup
winetricksSettings="vd=1024x768"
export winVer="win7"
download "https://stevend.net/downloads/scramble_win32.zip"
install_wine_bottle
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files/" "${cache}/scramble_win32.zip"
echo "Note: When you first start the game, it will say that tts initialization failed. Please answer that you do not want to attempt initialization of tts when the game starts to allow easy speech through speech dispatcher."
alert
alert "Note: When you first start the game, it will say that tts initialization failed. Please answer that you do not want to attempt initialization of tts when the game starts to allow easy speech through speech dispatcher."
add_launcher "c:\Program Files\scramble_win32\scramble.exe"
+35 -25
View File
@@ -1,28 +1,38 @@
#//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"
echo "Please set the language to English when the game opens."
echo "Go to options and press enter."
echo "Press down arrow 5 times and press enter."
echo "Press down arrow 1 time and press enter."
echo "Press up arrow 2 times and press enter."
echo "If everything worked as expected you should be back on the game menu and speech should work."
alert
} | 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."
+2 -2
View File
@@ -1,3 +1,4 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
download "http://www.danielzingaro.com/superdeekout_setup.exe" "http://www.danielzingaro.com/sd_full.exe"
install_wine_bottle vb6run dx8vb
wine "${cache}/superdeekout_setup.exe" &
@@ -13,8 +14,7 @@ xdotool sleep 15 key --delay 250 alt+f 2> /dev/null
xdotool sleep 3 key --delay 250 n 2> /dev/null
wineserver -k
install_with_progress 7z "Extracting game files..." x -y -o"$WINEPREFIX/drive_c/Program Files/Super Deekout" "${cache}/sd_full.exe"
echo "Super Deekout needs some information before it will run:"
alert
alert "Super Deekout needs some information before it will run."
read -erp "Please enter your first name: " fname
read -erp "Please enter your last name: " lname
read -erp "Please enter your email address: " email
+2 -2
View File
@@ -1,3 +1,4 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
get_installer "theyll-come-from-the-moon-windows.zip" "https://soundrascal.itch.io/theyll-come-from-the-moon"
export WINEARCH=win64
export winVer="win8"
@@ -7,5 +8,4 @@ install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/P
winetricks -q usetakefocus=y
winetricks -q usetakefocus=n
add_launcher "c:\Program Files\tcftm\They'll Come from the Moon!.exe"
echo "When the game launches, press the enter key to load the game which will begin to speak."
alert
alert "When the game launches, press the enter key to load the game which will begin to speak."
+2 -6
View File
@@ -1,12 +1,8 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
export WINEARCH="win64" # Migrated to wine64 with WINETRICKS_FORCE=1
export winVer="win7"
install_wine_bottle sapi vb6run dx8vb
wine "${cache}/vipmud20016.exe" /silent
mkdir -p "${HOME}/.local/wine/vip-mud/drive_c/users/${USER}/Documents/VIP Mud"
add_launcher "c:\Program Files (x86)\VIPMud 2.0\vipmud2.exe"
echo
echo "When you first launch VIP Mud You will be presented with several inaccessible dialogs."
echo "To bypass these dialogs, press alt+o, then press enter until speech starts."
echo "To be sure that each new dialog has had time to complete, wait a second between each press of enter."
echo "In each subsequent launch, you will need to hit enter a couple times before it starts speaking."
alert
alert "When you first launch VIP Mud You will be presented with several inaccessible dialogs.\nTo bypass these dialogs, press alt+o, then press enter until speech starts.\nTo be sure that each new dialog has had time to complete, wait a second between each press of enter.\nIn each subsequent launch, you will need to hit enter a couple times before it starts speaking."
+42 -14
View File
@@ -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
@@ -790,14 +810,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 +839,6 @@ game_launcher() {
fi
fi
fi
process_launcher_flags
apply_executioners_rage_focus_workaround
customLaunchHandled="false"
custom_launch_parameters
@@ -901,6 +927,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
View File
@@ -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."
+120
View File
@@ -0,0 +1,120 @@
#!/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
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_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_get_umu_bottle_sets_environment
test_add_umu_launcher_records_backend_and_game_id
test_run_umu_game_uses_converted_path
test_install_crlf_file_normalizes_line_endings
printf 'UMU backend tests passed\n'