8 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
11 changed files with 1150 additions and 37 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
+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 -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
+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'
+35 -20
View File
@@ -1,23 +1,38 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
#//Disable since it's not working
download "https://www.mm-galabo.com/sr/Download_files_srfv/shadowrine_fullvoice3.171.exe" "https://raw.githubusercontent.com/LordLuceus/sr-english-localization/master/language_en.dat"
export WINEARCH="win64" # Migrated to wine64 with WINETRICKS_FORCE=1
export winVer="win8"
install_wine_bottle fakejapanese
# Add bcrypt DLL override required for Shadow Line to run
cat > /tmp/bcrypt_override.reg << 'EOF'
[HKEY_CURRENT_USER\Software\Wine\DllOverrides]
"bcryptprimitives"="native,builtin"
EOF
wine regedit /tmp/bcrypt_override.reg
rm /tmp/bcrypt_override.reg
# shellcheck shell=bash disable=SC2154 # cache, game, and helper functions are set by audiogame-manager.
export game="Shadow Line"
shadowLineGameId="shadow-line"
shadowLinePath='c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
download "https://www.mm-galabo.com/sr/Download_files_srfv/shadowrine_fullvoice3.171.exe" \
"https://raw.githubusercontent.com/LordLuceus/sr-english-localization/master/language_en.dat" \
"${nvdaControllerClient32Dll}" \
"${nvdaControllerClient64Dll}"
install_proton_bottle "$shadowLineGameId" fakejapanese
shadowLineInstallDir="${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
set_umu_reg_value "HKCU\\Software\\Wine\\DllOverrides" "bcryptprimitives" "native,builtin"
set_umu_app_winver "play_sr.exe" "win8"
{
echo "# Installing Shadow Line..."
timeout 300 wine "${cache}/shadowrine_fullvoice3.171.exe" /sp- /VERYSILENT /SUPPRESSMSGBOXES 2>&1 || true
timeout 300 umu-run "${cache}/shadowrine_fullvoice3.171.exe" /sp- /VERYSILENT /SUPPRESSMSGBOXES 2>&1 || true
echo "# Installation complete"
} | agm_progressbox "Installing Game" "Installing Shadow Line (this may take a few minutes)..."
# Kill any auto-launched game processes (installer lacks skipifsilent flag)
wineserver -k 2>/dev/null || true
mv -v "${cache}/language_en.dat" "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/SystemData/language_en.dat"
add_launcher "c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe"
alert "Please set the language to English when the game opens.\nGo to options and press enter.\nPress down arrow 5 times and press enter.\nPress down arrow 1 time and press enter.\nPress up arrow 2 times and press enter.\nIf everything worked as expected you should be back on the game menu and speech should work."
} | agm_progressbox "Installing Game" "Installing Shadow Line with UMU/Proton (this may take a few minutes)..."
stop_umu_bottle
if [[ ! -f "${shadowLineInstallDir}/play_sr.exe" ]]; then
agm_msgbox "Shadow Line" "Shadow Line" "Shadow Line did not install to the expected location: ${shadowLineInstallDir}"
exit 1
fi
install_crlf_file "${cache}/language_en.dat" "${shadowLineInstallDir}/SystemData/language_en.dat"
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient32.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient64.dll' -exec cp -v "${cache}/nvdaControllerClient64.dll" "{}" \;
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
add_umu_launcher "$shadowLineGameId" "$shadowLinePath"
alert "Shadow Line" "Shadow Line" "Please set the language to English when the game opens.\nGo to options and press enter.\nPress down arrow 5 times and press enter.\nPress down arrow 1 time and press enter.\nPress up arrow 2 times and press enter.\nIf everything worked as expected you should be back on the game menu and speech should work."
+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'