16 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
Storm Dragon f9394f90c9 To comment out a game, it's now #// on the first line. Updated executioner's rage and hopefully fixed new keyboard problems. 2026-02-22 14:30:23 -05:00
Storm Dragon 1fd3fcd21f Updated shabang for dependency installation scripts. 2026-02-18 15:19:13 -05:00
abolfazl ebrahimi cf1377d9e8 Added Linux Mint dependency installer script. 2026-02-18 15:16:19 -05:00
Storm Dragon 528ee7cd56 Added game Chopper Challenge. 2026-02-03 20:45:00 -05:00
Storm Dragon 9ebb52f48f Fix directory permissions in Shooter RW. 2026-01-12 17:52:51 -05:00
Storm Dragon 9d4e9b9a7f Shooter RW added. 2026-01-12 16:23:39 -05:00
Storm Dragon 0c8a749240 Added support for cthulhu's window reader plugin. 2026-01-12 11:52:07 -05:00
42 changed files with 1549 additions and 149 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"
+1 -1
View File
@@ -55,9 +55,9 @@ declare -Ag ipfs=(
[BG Word Yahtzee]="${ipfsGateway}/ipfs/QmdicAVDegDktY3euVAC2PPn4YBGz96KedxYXNe4WDQaoq?filename=BWY32Setup10.exe"
[BG Yahtzee]="${ipfsGateway}/ipfs/QmZebvkKgFAADnb1cgW6Bz7wTYdUh82X61QdtW66KcvmpF?filename=BGY32Setup10a.exe"
[Bloodshed]="${ipfsGateway}/ipfs/QmcTCTMep4zp5zTw8ZaXYpjtu9inNPn8bNzwhW6cX97egw?file=bloodshed.exe"
[Chopper Challenge]="${ipfsGateway}/ipfs/QmY1jmwVkmwEcqBs84i4mpancD3R1W4Vu6w1RdcrFUnbGx?filename=chopper%20challenge.zip"
[Christmas Chaos]="${ipfsGateway}/ipfs/QmYx11vsMDBgjPd1coZPGHxMXf2qtf4icqmB3Q9iUazyQv?filename=ChristmasChaos.zip"
[Kitchensinc Games]="${ipfsGateway}/ipfs/QmdkLPig6Kp3AZTwKAhjrhhsEuvhFCFhm6SHLUQVeNNYCb?filename=kitchen.tar.xz"
[Oh Shit]="${ipfsGateway}/ipfs/QmQnAJJrt5uABFziQc7enXYrJ74J9GKQSMi8Ry8ebsxfPV?filename=OhShit.zip"
[Villains From Beyond]="${ipfsGateway}/ipfs/QmWx271xuk3Mv9XTBoVu5BDJvXFZdasawC2nhtV21WAaUU?filename=villains_en.zip"
)
+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."
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
download "https://oriolgomez.com/games/beat_windows.zip"
# Sapi is broken on win64 for now, and this game doesn't support nvda it seems.
export WINEARCH=win64
+1 -1
View File
@@ -1,4 +1,4 @@
#Disable
#//Disable
download "https://nibblenerds.com/static/blades_of_glory.zip"
install_wine_bottle
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files/Blades of Glory" "${cache}/blades_of_glory.zip"
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
download "https://hirotaka2014.sakura.ne.jp/mh0406/game/breed_memorial.zip" "${nvdaControllerClient32Dll}"
export winVer="win7"
install_wine_bottle cjkfonts
+3 -8
View File
@@ -1,9 +1,4 @@
#
# Freezes at menu
download "https://www.agarchive.net/games/XSight/chopper%20challenge%20setup.exe"
download "${ipfs[Chopper Challenge]}"
install_wine_bottle vb6run dx8vb
wine "${cache}/chopper challenge setup.exe" /silent &
xdotool sleep 5 key y 2> /dev/null
wineserver -w
echo "$USER|n/a" >> "$WINEPREFIX/drive_c/Program Files/x-sight interactive/chopper challenge/config.dat"
add_launcher "c:\Program Files (x86)\x-sight interactive\chopper challenge\Chopper.exe"
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files" "${cache}/chopper challenge.zip"
add_launcher "c:\Program Files\chopper challenge\Chopper.exe"
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
export winVer="win7"
download "https://renovagames.com/bc/BC-Setup.exe"
install_wine_bottle cjkfonts
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
# No custom bottle needed - use standard wine path based on architecture
download "https://www.agarchive.net/games/pb/Dark-Destroyer-Setup.exe"
install_wine_bottle ie6
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
export winVer="win7"
install_wine_bottle
download "https://www.iamtalon.me/games/dragonpong.zip"
+1 -1
View File
@@ -1,4 +1,4 @@
# Borken, candidate for removal
#// 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"
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
export winVer="win7"
export winetricksSettings="vd=1024x768"
download "https://www.stefankiss.sk/files/eurofly2/Launcher_1.2.zip" "https://www.stefankiss.sk/files/eurofly2/Eurofly_2_ful_setup.exe"
+6 -5
View File
@@ -1,13 +1,14 @@
download "https://dl.tweesecake.app/rage/rage1.5.0.zip"
# 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
export winVer="win10"
install_wine_bottle
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files/" "${cache}/rage1.5.0.zip"
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"
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
download "http://www.agarchive.net/games/bpc/fartman.exe"
install_wine_bottle dx8vb vb6run
wine "${cache}/fartman.exe" /silent
+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'
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
# Uses standard wine path based on architecture (win32/win64)
export winVer="win7"
install_wine_bottle
+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."
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#//!/bin/bash
case "${game}" in
"MudSplat English")
a="a"
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
download "http://www.vgstorm.com/psycho_strike_installer.exe"
install_wine_bottle
wine "${cache}/psycho_strike_installer.exe" /silent
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
export winVer="win7"
download "http://www.samtupy.com/games/SCSetup.exe" "${nvdaControllerClient32Dll}"
install_wine_bottle
+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."
+7
View File
@@ -0,0 +1,7 @@
export WINEARCH=win64
export winVer="win7"
get_installer "shooterrw-win.zip" "https://masonasons.itch.io/shooterrw"
install_wine_bottle
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files/shooter_rw" "${cache}/shooterrw-win.zip"
find "$WINEPREFIX/drive_c/Program Files/shooter_rw" -type d -exec chmod 755 "{}" +
add_launcher "c:\Program Files\shooter_rw\Shooter.exe"
+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
+1 -1
View File
@@ -1,4 +1,4 @@
#
#//
export winVer="win7"
download "http://www.vgstorm.com/the_gate_installer.exe"
install_wine_bottle
+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."
+335 -50
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
# shellcheck disable=SC1091
# Get script directory for relative paths
scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -11,6 +12,234 @@ export DIALOGOPTS='--no-lines --visit-items'
# Wine32 version for SAPI compatibility
wineThirtyTwoVersion="9.0"
# Track nvda2speechd lifecycle for this launcher run.
nvda2speechdPid=""
nvda2speechdStarted="false"
customLaunchHandled="false"
cthulhuTitleReaderEnabled="false"
cthulhuTitleReaderTried="false"
takeFocusWorkaroundActive="false"
takeFocusRestoreMode="none"
takeFocusRestoreValue=""
log_msg() {
local logFile="${scriptDir}/game.log"
local timestamp=""
timestamp="$(date)"
echo "${1} [${timestamp}]" >> "$logFile"
}
start_nvda2speechd() {
if [[ "$nvda2speechdStarted" == "true" ]]; then
return
fi
if ! ss -ltnp | rg 3457 | grep -q 'cthulhu'; then
if [[ -x "${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd" ]]; then
local translateSetting="${TRANSLATE:-unset}"
local translateFromSetting="${TRANSLATE_FROM:-unset}"
local translateToSetting="${TRANSLATE_TO:-unset}"
log_msg "Preparing nvda2speechd with TRANSLATE=${translateSetting} TRANSLATE_FROM=${translateFromSetting} TRANSLATE_TO=${translateToSetting}."
if [[ "${translateSetting,,}" == "true" ]] || [[ "$translateSetting" == "1" ]]; then
if ! command -v trans &> /dev/null; then
log_msg "TRANSLATE is enabled but 'trans' is missing; nvda2speechd may fail to start."
fi
fi
"${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd" &> /dev/null &
nvda2speechdPid=$!
nvda2speechdStarted="true"
log_msg "Started nvda2speechd (pid ${nvda2speechdPid})."
else
log_msg "nvda2speechd binary not found or not executable; skipping start."
fi
else
log_msg "cthulhu is already listening on port 3457; skipping nvda2speechd start."
fi
}
stop_nvda2speechd() {
if [[ "$nvda2speechdStarted" != "true" ]]; then
return
fi
if [[ -n "$nvda2speechdPid" ]] && kill -0 "$nvda2speechdPid" 2>/dev/null; then
kill "$nvda2speechdPid" 2>/dev/null
wait "$nvda2speechdPid" 2>/dev/null
fi
nvda2speechdPid=""
nvda2speechdStarted="false"
}
enable_cthulhu_window_title_reader() {
if [[ "$cthulhuTitleReaderEnabled" == "true" ]]; then
return 0
fi
if [[ "$cthulhuTitleReaderTried" == "true" ]]; then
return 1
fi
cthulhuTitleReaderTried="true"
if ! command -v gdbus &> /dev/null; then
log_msg "gdbus not found; cannot enable Cthulhu WindowTitleReader."
return 1
fi
local dbusService="org.stormux.Cthulhu.Service"
local modulePath="/org/stormux/Cthulhu/Service/PluginSystemManager"
local titleModulePath="/org/stormux/Cthulhu/Service/Plugin_WindowTitleReader"
local moduleInterface="org.stormux.Cthulhu.Module"
local enableResult=""
local titleEnableResult=""
gdbus call --session --dest "$dbusService" \
--object-path "$modulePath" \
--method "${moduleInterface}.ExecuteCommand" \
"RescanPlugins" false &> /dev/null || true
enableResult="$(gdbus call --session --dest "$dbusService" \
--object-path "$modulePath" \
--method "${moduleInterface}.ExecuteParameterizedCommand" \
"SetPluginActive" \
'{"plugin_name": <"WindowTitleReader">, "active": <true>}' \
false 2>/dev/null || true)"
if [[ "$enableResult" != *"true"* ]]; then
log_msg "Failed to activate Cthulhu WindowTitleReader plugin via D-Bus."
return 1
fi
titleEnableResult="$(gdbus call --session --dest "$dbusService" \
--object-path "$titleModulePath" \
--method "${moduleInterface}.ExecuteParameterizedCommand" \
"SetEnabled" \
'{"enabled": <true>}' \
false 2>/dev/null || true)"
if [[ "$titleEnableResult" == *"true"* ]]; then
cthulhuTitleReaderEnabled="true"
log_msg "Enabled Cthulhu WindowTitleReader tracking via D-Bus."
return 0
fi
log_msg "Failed to enable Cthulhu WindowTitleReader tracking via D-Bus."
return 1
}
kill_nvda2speechd_listener() {
local nvdaPids=()
mapfile -t nvdaPids < <(pgrep -u "${USER}" -x nvda2speechd 2>/dev/null)
if [[ ${#nvdaPids[@]} -eq 0 ]]; then
log_msg "No nvda2speechd process found to stop."
return
fi
log_msg "Stopping nvda2speechd process(es): ${nvdaPids[*]}."
for pid in "${nvdaPids[@]}"; do
kill "$pid" 2>/dev/null
done
local remainingPids=()
mapfile -t remainingPids < <(pgrep -u "${USER}" -x nvda2speechd 2>/dev/null)
if [[ ${#remainingPids[@]} -gt 0 ]]; then
log_msg "nvda2speechd still running after stop attempt: ${remainingPids[*]}."
fi
}
cleanup_and_exit() {
restore_executioners_rage_focus_workaround
stop_nvda2speechd
}
set_use_take_focus_value() {
local takeFocusValue="${1^^}"
if [[ "$takeFocusValue" != "Y" ]] && [[ "$takeFocusValue" != "N" ]]; then
return 1
fi
wine reg add "HKCU\\Software\\Wine\\X11 Driver" /v UseTakeFocus /t REG_SZ /d "$takeFocusValue" /f &> /dev/null
}
unset_use_take_focus_value() {
wine reg delete "HKCU\\Software\\Wine\\X11 Driver" /v UseTakeFocus /f &> /dev/null
}
capture_use_take_focus_state() {
local queryOutput=""
local currentValue=""
takeFocusRestoreMode="none"
takeFocusRestoreValue=""
if ! wine reg query "HKCU\\Software\\Wine" &> /dev/null; then
takeFocusRestoreMode="unknown"
return 1
fi
queryOutput="$(wine reg query "HKCU\\Software\\Wine\\X11 Driver" /v UseTakeFocus 2>/dev/null || true)"
if [[ "$queryOutput" == *"UseTakeFocus"* ]]; then
currentValue="$(awk '/UseTakeFocus/ {print toupper($NF); exit}' <<< "$queryOutput")"
if [[ "$currentValue" == "Y" ]] || [[ "$currentValue" == "N" ]]; then
takeFocusRestoreMode="value"
takeFocusRestoreValue="$currentValue"
return 0
fi
takeFocusRestoreMode="unknown"
return 1
fi
takeFocusRestoreMode="delete"
return 0
}
apply_executioners_rage_focus_workaround() {
if [[ "${game[2]}" != "Executioner's Rage" ]]; then
return
fi
takeFocusWorkaroundActive="false"
if ! capture_use_take_focus_state; then
log_msg "Could not capture current UseTakeFocus state before launching Executioner's Rage."
fi
if set_use_take_focus_value "Y"; then
takeFocusWorkaroundActive="true"
log_msg "Enabled UseTakeFocus=Y before launching Executioner's Rage."
else
log_msg "Failed to enable UseTakeFocus=Y for Executioner's Rage."
fi
}
restore_executioners_rage_focus_workaround() {
if [[ "$takeFocusWorkaroundActive" != "true" ]]; then
return
fi
if [[ "${game[2]}" != "Executioner's Rage" ]]; then
takeFocusWorkaroundActive="false"
takeFocusRestoreMode="none"
takeFocusRestoreValue=""
return
fi
case "$takeFocusRestoreMode" in
value)
if set_use_take_focus_value "$takeFocusRestoreValue"; then
log_msg "Restored UseTakeFocus=${takeFocusRestoreValue} after closing Executioner's Rage."
else
log_msg "Failed to restore UseTakeFocus=${takeFocusRestoreValue} after closing Executioner's Rage."
fi
;;
delete)
if unset_use_take_focus_value; then
log_msg "Removed UseTakeFocus override after closing Executioner's Rage."
else
log_msg "Failed to remove UseTakeFocus override after closing Executioner's Rage."
fi
;;
*)
log_msg "Skipping UseTakeFocus restore after closing Executioner's Rage because prior state was unknown."
;;
esac
takeFocusWorkaroundActive="false"
takeFocusRestoreMode="none"
takeFocusRestoreValue=""
}
# Check and manage wine32 installation
check_wine32() {
local wine32Dir="${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/wine32"
@@ -137,10 +366,10 @@ game_installer() {
mapfile -t installedGames < <(sed -e '/^$/d' -e '/^[[:space:]]*#/d' "${configFile}" 2> /dev/null | cut -d '|' -f3)
# Create the menu of available games by reading from .install directory
declare -a menuList
# Get all .sh files from .install directory, excluding those starting with # as first line.
# Get all .sh files from .install directory, excluding those starting with #// as first line.
mapfile -t sortedGames < <(for f in "${scriptDir}/.install/"*.sh; do
# Skip if first line starts with #
[[ $(head -n1 "$f") == "#"* ]] && continue
# Skip if first line starts with #//
[[ $(head -n1 "$f") == "#//"* ]] && continue
echo "${f##*/%.sh}"
done | sort)
for i in "${sortedGames[@]}"; do
@@ -240,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
@@ -305,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
@@ -316,27 +565,33 @@ kill_game() {
# for games that require custom scripts before launch or custom launch parameters
custom_launch_parameters() {
if [[ "${game[2]}" == "Dragon Pong" ]]; then
"${scriptDir}/speech/speak_window_title.sh" DragonPong.exe &
if ! enable_cthulhu_window_title_reader; then
"${scriptDir}/speech/speak_window_title.sh" DragonPong.exe &
fi
start_nvda2speechd
pushd "$(winepath "$winePath")" || exit 1
wine "$wineExec"
popd || exit 1
exit 0
customLaunchHandled="true"
return 0
fi
if [[ "${game[2]}" == "Dreamland" ]]; then
"$WINE" "${game[1]}" &> /dev/null &
exit 0
start_nvda2speechd
"$WINE" "${game[1]}" &> /dev/null
customLaunchHandled="true"
return 0
fi
# executioner's-rage: DLL replacement now handled by update_nvda_dlls()
if [[ "${game[2]}" == "Laser Breakout" ]]; then
"${scriptDir}/speech/speak_window_title.sh" play.exe &
if ! enable_cthulhu_window_title_reader; then
"${scriptDir}/speech/speak_window_title.sh" play.exe &
fi
fi
if [[ "${game[2]}" == "Bokurano Daibouken 2" ]]; then
find "${WINEPREFIX}/drive_c/nyanchangame/bk2" -type f -name 'nvdaControllerClient.dll' -exec rm -v "{}" \;
"${scriptDir}/speech/clipboard_translator.sh" "play.exe" bokurano-daibouken2 &
export TRANSLATE=true
fi
if [[ "${game[2]}" == "Bokurano Daibouken" ]]; then
find "${WINEPREFIX}/drive_c/nyanchangame/bk" -type f -name 'nvdaControllerClient.dll' -exec rm -v "{}" \;
"${scriptDir}/speech/clipboard_translator.sh" "play.exe" bokurano-daibouken &
export TRANSLATE=true
fi
if [[ "${game[2]}" == "Bokurano Daibouken 3" ]]; then
dictPath="$(winepath "${winePath}")"
@@ -358,13 +613,19 @@ custom_launch_parameters() {
fi
fi
if [[ "${game[2]}" == "Bop It Emulator" ]]; then
"${scriptDir}/speech/speak_window_title.sh" bop.exe &
if ! enable_cthulhu_window_title_reader; then
"${scriptDir}/speech/speak_window_title.sh" bop.exe &
fi
fi
if [[ "${game[2]}" == "Road to Rage" ]]; then
"${scriptDir}/speech/speak_window_title.sh" trtr.exe &
if ! enable_cthulhu_window_title_reader; then
"${scriptDir}/speech/speak_window_title.sh" trtr.exe &
fi
fi
if [[ "${game[2]}" == "Axel Pong" ]]; then
"${scriptDir}/speech/speak_window_title.sh" axel_pong.exe &
if ! enable_cthulhu_window_title_reader; then
"${scriptDir}/speech/speak_window_title.sh" axel_pong.exe &
fi
fi
if [[ "${game[2]}" == "Sequence Storm" ]]; then
"${scriptDir}/speech/clipboard_reader.sh" SequenceStorm &
@@ -375,30 +636,40 @@ custom_launch_parameters() {
#fi
# sketchbook: DLL replacement now handled by update_nvda_dlls()
if [[ "${game[2]}" == "Audiodisc" ]]; then
start_nvda2speechd
wine "$winePath\\$wineExec"
exit 0
customLaunchHandled="true"
return 0
fi
if [[ "${game[2]}" == "Audioquake" ]]; then
start_nvda2speechd
wine "$winePath\\$wineExec"
exit 0
customLaunchHandled="true"
return 0
fi
if [[ "${game[2]}" == "Screaming Strike 2" ]]; then
start_nvda2speechd
pushd "$(winepath "$winePath")" || exit 1
wine "$wineExec"
popd || exit 1
exit 0
customLaunchHandled="true"
return 0
fi
if [[ "${game[2]}" == "Warsim" ]]; then
start_nvda2speechd
pushd "$(winepath "${game[1]%\\*}")" || exit 1
wine "${game[1]##*\\}"
popd || exit 1
exit 0
customLaunchHandled="true"
return 0
fi
if [[ "${game[2]}" == "Interceptor" ]]; then
start_nvda2speechd
pushd "$(winepath "$winePath")" || exit 1
wine "$wineExec"
popd || exit 1
exit 0
customLaunchHandled="true"
return 0
fi
}
@@ -474,14 +745,8 @@ game_launcher() {
[[ "$agmNoLaunch" == "true" ]] && return
# shellcheck source=.includes/bottle.sh
source "${scriptDir}/.includes/bottle.sh"
# Start nvda2speechd if available
if ! ss -ltnp | rg 3457 | grep -q 'cthulhu'; then
if [[ -x "${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd" ]]; then
"${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd" &> /dev/null &
fi
fi
# Ensure a clean nvda2speechd state before launching a new game.
kill_nvda2speechd_listener
# Replace NVDA controller client DLLs in wine64 bottle
update_nvda_dlls
mapfile -t lines < <(sed -e '/^$/d' -e '/^ *#/d' "${configFile}" 2> /dev/null | sort -t '|' -k3,3f)
@@ -545,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
@@ -567,16 +839,25 @@ game_launcher() {
fi
fi
fi
process_launcher_flags
apply_executioners_rage_focus_workaround
customLaunchHandled="false"
custom_launch_parameters
if [[ "$debugGdb" == "1" ]]; then
# Change to game directory before launching
pushd "$(winepath "${game[1]%\\*}")" > /dev/null || exit 1
winedbg --gdb "${game[1]##*\\}"
popd > /dev/null || exit 1
else
wine start /d "${game[1]%\\*}" "${game[1]##*\\}" /realtime
if [[ "$customLaunchHandled" == "true" ]]; then
restore_executioners_rage_focus_workaround
exit 0
fi
start_nvda2speechd
# Change to game directory before launching (required for proper game operation)
pushd "$(winepath "${game[1]%\\*}")" > /dev/null || exit 1
if [[ "$debugGdb" == "1" ]]; then
winedbg --gdb "${game[1]##*\\}"
else
# Use direct wine execution instead of 'wine start' to ensure clipboard works
# See: https://bugs.winehq.org/show_bug.cgi?id=50598
wine "${game[1]##*\\}"
fi
popd > /dev/null || exit 1
restore_executioners_rage_focus_workaround
fi
exit 0
}
@@ -584,10 +865,12 @@ game_launcher() {
# main script
trap "exit 0" SIGINT
trap cleanup_and_exit EXIT
trap "cleanup_and_exit; exit 0" SIGINT SIGTERM
# Detect dialog interface type BEFORE potentially setting DISPLAY
# This must happen before we modify DISPLAY to preserve console detection
# shellcheck disable=SC2034
if [[ -z "$DISPLAY" ]]; then
dialogType="dialog"
elif command -v yad &> /dev/null; then
@@ -644,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'
+2 -2
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# Immediately exit if errors are encountered.
set -e
@@ -72,4 +72,4 @@ configure_arch() {
configure_arch
exit 0
exit 0
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
# Immediately exit if errors are encountered.
set -e
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# Immediately exit if errors are encountered.
set -e
# Wine dependencies installer for Debian/Ubuntu
# If this fails on your system, please contact storm_dragon@stormux.org
configure_debian() {
packageList=(
curl
dialog
gawk
gstreamer1.0-plugins-bad:i386
gstreamer1.0-plugins-good:i386
gstreamer1.0-plugins-ugly:i386
mono-complete
libncurses5-dev
w3m
winehq-stable
)
# Make sure 32-bit libraries are available.
sudo dpkg --add-architecture i386
sudo apt install --install-recommends "${packageList[@]}"
}
configure_debian
exit 0