Compare commits
18
Commits
master
...
d4db802645
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4db802645 | ||
|
|
993f838404 | ||
|
|
7ff3f0b47d | ||
|
|
eee7b7e7c8 | ||
|
|
4b312099b4 | ||
|
|
7ec9ef3b87 | ||
|
|
b87245fa69 | ||
|
|
05ea1502ce | ||
|
|
c033a21211 | ||
|
|
8943688a43 | ||
|
|
92745d8af8 | ||
|
|
1f977bb1f4 | ||
|
|
53d8c10645 | ||
|
|
b58681964c | ||
|
|
2abf445637 | ||
|
|
c0e6c37f1a | ||
|
|
856415c22f | ||
|
|
7ddd7cbac4 |
@@ -69,6 +69,77 @@ winetricks() {
|
||||
fi
|
||||
}
|
||||
|
||||
install_winespeak() {
|
||||
local installerName="winespeak.exe"
|
||||
local installerPath="${cache}/${installerName}"
|
||||
local installerSha256="187d4db69f3af7c1bca1c100a04489b76732048be7c38449781da360ed7b2d66"
|
||||
local wrapperPath="${WINEPREFIX}/drive_c/Program Files (x86)/espeak-ng-sapi/EspeakSAPI.dll"
|
||||
local wrapperSha256="8009750dad82ca6665871814033dcee6844bb424f033130c109f47e188c5364b"
|
||||
local cscriptPath="${WINEPREFIX}/drive_c/windows/syswow64/cscript.exe"
|
||||
local verifyScriptPath="${WINEPREFIX}/drive_c/windows/temp/verify_winespeak.vbs"
|
||||
local actualSha256
|
||||
local registryOutput
|
||||
local voiceOutput
|
||||
local defaultToken='HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\TokenEnums\eSpeak-NG\English (America)'
|
||||
|
||||
if [[ -z "${wineSpeakInstaller:-}" ]]; then
|
||||
echo "WineSpeak download URL is not configured."
|
||||
return 1
|
||||
fi
|
||||
|
||||
download "$wineSpeakInstaller"
|
||||
actualSha256="$(sha256sum "$installerPath" | awk '{print $1}')"
|
||||
if [[ "$actualSha256" != "$installerSha256" ]]; then
|
||||
echo "WineSpeak installer failed its SHA-256 check."
|
||||
rm -f "$installerPath"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! wine "$installerPath" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-; then
|
||||
echo "WineSpeak installer failed."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$wrapperPath" ]]; then
|
||||
echo "WineSpeak did not install its 32-bit SAPI wrapper."
|
||||
return 1
|
||||
fi
|
||||
actualSha256="$(sha256sum "$wrapperPath" | awk '{print $1}')"
|
||||
if [[ "$actualSha256" != "$wrapperSha256" ]]; then
|
||||
echo "WineSpeak installed an unexpected 32-bit SAPI wrapper."
|
||||
return 1
|
||||
fi
|
||||
|
||||
registryOutput="$(wine reg query 'HKCU\Software\Microsoft\Speech\Voices' /v DefaultTokenId 2>/dev/null)"
|
||||
if ! grep -Fq "$defaultToken" <<< "$registryOutput"; then
|
||||
echo "WineSpeak did not become the default SAPI voice."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$cscriptPath" ]]; then
|
||||
echo "Wine's 32-bit cscript.exe was not found."
|
||||
return 1
|
||||
fi
|
||||
mkdir -p "${verifyScriptPath%/*}"
|
||||
cat > "$verifyScriptPath" <<'EOF'
|
||||
dim speechobject
|
||||
set speechobject=createobject("sapi.spvoice")
|
||||
wscript.echo speechobject.voice.getdescription
|
||||
EOF
|
||||
if ! voiceOutput="$(wine "$cscriptPath" //nologo 'c:\windows\temp\verify_winespeak.vbs' 2>&1)"; then
|
||||
echo "WineSpeak's 32-bit SAPI voice could not be created."
|
||||
echo "$voiceOutput"
|
||||
return 1
|
||||
fi
|
||||
if ! grep -Fq "English (America)" <<< "$voiceOutput"; then
|
||||
echo "WineSpeak's 32-bit SAPI verification returned an unexpected voice."
|
||||
echo "$voiceOutput"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "WineSpeak English (America) is the default SAPI voice."
|
||||
}
|
||||
|
||||
install_rhvoice() {
|
||||
if [[ -d "${WINEPREFIX}/drive_c/Program Files/Olga Yakovleva/" ]]; then
|
||||
return
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
run_with_optional_gamemode() {
|
||||
if command -v gamemoderun &> /dev/null; then
|
||||
gamemoderun "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
+28
-2
@@ -1,3 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2154 # Sourced by audiogame-manager with shared globals.
|
||||
|
||||
documentation() {
|
||||
if [[ "$2" == "Become a Patron" ]]; then
|
||||
return
|
||||
@@ -8,13 +11,36 @@ documentation() {
|
||||
|
||||
# Extract architecture from first parameter (format: "win64|path")
|
||||
local wineArch="${1%%|*}"
|
||||
get_bottle "$wineArch"
|
||||
if [[ "$wineArch" == "umu" ]]; then
|
||||
local launcherLine=""
|
||||
local docFlag=""
|
||||
local umuGameId=""
|
||||
local -a documentationGame=()
|
||||
launcherLine="$(grep -F -m1 "${1}|" "$configFile" 2> /dev/null || true)"
|
||||
IFS='|' read -ra documentationGame <<< "$launcherLine"
|
||||
for docFlag in "${documentationGame[@]:3}" ; do
|
||||
if [[ "$docFlag" =~ ^export\ [a-zA-Z_][a-zA-Z0-9_]*=\'?.*\'?$ ]]; then
|
||||
eval "$docFlag"
|
||||
fi
|
||||
done
|
||||
if [[ -z "$umuGameId" ]]; then
|
||||
echo "Unable to find UMU game id for documentation lookup."
|
||||
return
|
||||
fi
|
||||
get_umu_bottle "$umuGameId"
|
||||
else
|
||||
get_bottle "$wineArch"
|
||||
fi
|
||||
|
||||
echo "Loading documentation, please wait..."
|
||||
|
||||
# Try to find documentation based on common naming conventions.
|
||||
local gamePath
|
||||
gamePath="$(winepath -u "$2" 2> /dev/null)"
|
||||
if [[ "$wineArch" == "umu" ]]; then
|
||||
gamePath="$(umu_windows_path_to_unix "$2")"
|
||||
else
|
||||
gamePath="$(winepath -u "$2" 2> /dev/null)"
|
||||
fi
|
||||
gamePath="${gamePath%/*}"
|
||||
local gameDoc=""
|
||||
local isUrl="false"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Replacement map for scripts/check-ipfs-links.sh --fix.
|
||||
# Format: MATCH<TAB>REPLACEMENT
|
||||
# MATCH can be the ipfs array key, such as BG 15 Puzzle, or the old CID.
|
||||
# REPLACEMENT can be a bare CID or a full /ipfs/ URL. Bare CIDs keep the existing query string.
|
||||
|
@@ -7,6 +7,7 @@ declare -Ag ipfs=(
|
||||
[nvdaControllerClient32]="${ipfsGateway}/ipfs/QmTrRrT4QFKSkZ8ivfUawA6iJ6adEyyogccE3nLDTfSK8u?filename=nvdaControllerClient32.dll"
|
||||
[nvdaControllerClient64]="${ipfsGateway}/ipfs/QmaYE7RFDtwHCiXCVLcuA3esfFx6E7koidtvrck9AwPuuN?filename=nvdaControllerClient64.dll"
|
||||
[nvda2speechd]="${ipfsGateway}/ipfs/QmPxhoNsoFoJC7bCfioBBCcK8tEoSoYpm342z6u7KjFsVz?filename=nvda2speechd"
|
||||
[winespeak]="https://stormgames.wolfe.casa/downloads/winespeak.exe"
|
||||
|
||||
# Games (alphabetical order)
|
||||
[BG 15 Puzzle]="${ipfsGateway}/ipfs/QmQiocMpMXoxejDftKKvmrR5xxpj1qcWcgkhBBwTcyijXg?filename=FPB32Setup10a.exe"
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2034,SC2154 # Sourced by audiogame-manager and installers with shared globals.
|
||||
|
||||
require_umu() {
|
||||
if command -v umu-run &> /dev/null; then
|
||||
return 0
|
||||
fi
|
||||
local message="This game requires umu-launcher. Please install umu-launcher and try again."
|
||||
if declare -F agm_msgbox &> /dev/null; then
|
||||
agm_msgbox "Audio Game Manager" "Audio Game Manager" "$message"
|
||||
else
|
||||
echo "$message" >&2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
get_umu_bottle() {
|
||||
local gameId="$1"
|
||||
if [[ -z "$gameId" ]]; then
|
||||
echo "get_umu_bottle requires a game id." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
export umuGameId="$gameId"
|
||||
export WINEPREFIX="${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/protonBottles/${gameId}"
|
||||
export GAMEID="$gameId"
|
||||
export STORE="${umuStore:-none}"
|
||||
export DISPLAY="${DISPLAY:-:0}"
|
||||
mkdir -p "$WINEPREFIX"
|
||||
}
|
||||
|
||||
install_proton_bottle() {
|
||||
local gameId="$1"
|
||||
shift || true
|
||||
require_umu || return 1
|
||||
get_umu_bottle "$gameId" || return 1
|
||||
|
||||
if [[ ! -f "${WINEPREFIX}/system.reg" ]]; then
|
||||
umu-run ""
|
||||
fi
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
umu-run winetricks "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
install_proton_winetricks_verb() {
|
||||
local verbName="$1"
|
||||
local output=""
|
||||
local status=0
|
||||
local hadErrexit="false"
|
||||
|
||||
require_umu || return 1
|
||||
if [[ $- == *e* ]]; then
|
||||
hadErrexit="true"
|
||||
fi
|
||||
set +e
|
||||
output="$(umu-run winetricks -q "$verbName" 2>&1)"
|
||||
status=$?
|
||||
if [[ "$hadErrexit" == "true" ]]; then
|
||||
set -e
|
||||
fi
|
||||
printf '%s\n' "$output"
|
||||
|
||||
if [[ "$status" -ne 0 ]]; then
|
||||
if grep -F -q "winetricks verb '${verbName}' is already installed" <<< "$output"; then
|
||||
return 0
|
||||
fi
|
||||
return "$status"
|
||||
fi
|
||||
}
|
||||
|
||||
umu_windows_path_to_unix() {
|
||||
local windowsPath="$1"
|
||||
local relativePath=""
|
||||
if [[ "$windowsPath" =~ ^[cC]:\\ ]]; then
|
||||
relativePath="${windowsPath:3}"
|
||||
relativePath="${relativePath//\\//}"
|
||||
printf '%s/drive_c/%s\n' "$WINEPREFIX" "$relativePath"
|
||||
return 0
|
||||
fi
|
||||
winepath -u "$windowsPath"
|
||||
}
|
||||
|
||||
run_umu_game() {
|
||||
local windowsPath="$1"
|
||||
local exePath=""
|
||||
require_umu || return 1
|
||||
if [[ -z "${umuGameId:-}" ]]; then
|
||||
echo "UMU game id is not set for ${game[2]:-selected game}." >&2
|
||||
return 1
|
||||
fi
|
||||
get_umu_bottle "$umuGameId" || return 1
|
||||
exePath="$(umu_windows_path_to_unix "$windowsPath")"
|
||||
if [[ ! -f "$exePath" ]]; then
|
||||
echo "UMU executable not found: $exePath" >&2
|
||||
return 1
|
||||
fi
|
||||
pushd "${exePath%/*}" > /dev/null || return 1
|
||||
run_with_optional_gamemode umu-run "$exePath"
|
||||
popd > /dev/null || return 1
|
||||
}
|
||||
|
||||
add_umu_launcher() {
|
||||
local gameId="$1"
|
||||
local windowsPath="$2"
|
||||
shift 2
|
||||
local launchSettings="umu|${windowsPath}|${game}|export umuGameId=${gameId}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
launchSettings+="|$1"
|
||||
shift
|
||||
done
|
||||
|
||||
if ! grep -F -q -x "$launchSettings" "$configFile" 2> /dev/null; then
|
||||
echo "$launchSettings" >> "$configFile"
|
||||
sort -t '|' -k3,3f -o "$configFile" "$configFile"
|
||||
fi
|
||||
}
|
||||
|
||||
set_umu_reg_value() {
|
||||
local key="$1"
|
||||
local valueName="$2"
|
||||
local valueData="$3"
|
||||
wine reg add "$key" /v "$valueName" /t REG_SZ /d "$valueData" /f
|
||||
}
|
||||
|
||||
set_umu_app_winver() {
|
||||
local exeName="$1"
|
||||
local winVersion="$2"
|
||||
set_umu_reg_value "HKCU\\Software\\Wine\\AppDefaults\\${exeName}" "Version" "$winVersion"
|
||||
}
|
||||
|
||||
install_crlf_file() {
|
||||
local sourceFile="$1"
|
||||
local destFile="$2"
|
||||
mkdir -p "${destFile%/*}"
|
||||
perl -pe 's/\r?\n/\r\n/' "$sourceFile" > "$destFile"
|
||||
}
|
||||
|
||||
stop_umu_bottle() {
|
||||
if command -v wineserver &> /dev/null; then
|
||||
wineserver -k 2> /dev/null || true
|
||||
fi
|
||||
}
|
||||
@@ -1,9 +1,41 @@
|
||||
download "https://blindgamers.com/downloads/a-heros-call-freeware.zip" "https://stormgames.wolfe.casa/downloads/nvdaControllerClient32.dll"
|
||||
export winVer="win7"
|
||||
export winetricksSettings="vd=1024x768"
|
||||
install_wine_bottle
|
||||
# Dotnet is evil. That is all.
|
||||
LC_ALL=C DISPLAY="" winetricks -q dotnet462 xna40
|
||||
wineserver -k # Really!
|
||||
install_with_progress unzip "Extracting game files..." -d "$WINEPREFIX/drive_c/Program Files" "${cache}/a-heros-call-freeware.zip"
|
||||
add_launcher "c:\Program Files\a-heros-call\A Hero's Call.exe"
|
||||
# shellcheck shell=bash disable=SC2154 # cache, game, and helper functions are set by audiogame-manager.
|
||||
|
||||
export game="A Hero's Call"
|
||||
herosCallGameId="a-heros-call"
|
||||
herosCallPath="c:\\Program Files\\a-heros-call\\A Hero's Call.exe"
|
||||
|
||||
download "https://blindgamers.com/downloads/a-heros-call-freeware.zip" \
|
||||
"https://stormgames.wolfe.casa/downloads/nvdaControllerClient32.dll"
|
||||
|
||||
export PROTON_USE_XALIA=0
|
||||
install_proton_bottle "$herosCallGameId"
|
||||
|
||||
alert "A Hero's Call" "A Hero's Call" "If you hear a window open during installation, or if the installer seems to be taking a long time, try pressing enter to see if it will continue."
|
||||
|
||||
{
|
||||
echo "# Installing A Hero's Call dependencies..."
|
||||
WINETRICKS_FORCE=1 install_proton_winetricks_verb speechsdk
|
||||
install_proton_winetricks_verb corefonts
|
||||
install_proton_winetricks_verb dotnet462
|
||||
install_proton_winetricks_verb xna40
|
||||
install_proton_winetricks_verb win7
|
||||
|
||||
echo "# Extracting game files..."
|
||||
mkdir -p "${WINEPREFIX}/drive_c/Program Files"
|
||||
unzip -oq "${cache}/a-heros-call-freeware.zip" -d "${WINEPREFIX}/drive_c/Program Files"
|
||||
echo "# Installation complete"
|
||||
} | agm_progressbox "Installing Game" "Installing A Hero's Call with UMU/Proton (this may take several minutes)..."
|
||||
|
||||
stop_umu_bottle
|
||||
|
||||
herosCallInstallDir="${WINEPREFIX}/drive_c/Program Files/a-heros-call"
|
||||
if [[ ! -f "${herosCallInstallDir}/A Hero's Call.exe" ]]; then
|
||||
agm_msgbox "A Hero's Call" "A Hero's Call" "A Hero's Call did not install to the expected location."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "${cache}/nvdaControllerClient32.dll" "${herosCallInstallDir}/nvdaControllerClient32.dll"
|
||||
mkdir -p "${herosCallInstallDir}/lib/Tolk"
|
||||
cp "${cache}/nvdaControllerClient32.dll" "${herosCallInstallDir}/lib/Tolk/nvdaControllerClient32.dll"
|
||||
|
||||
add_umu_launcher "$herosCallGameId" "$herosCallPath" "export PROTON_USE_XALIA=0"
|
||||
|
||||
+92
-26
@@ -1,28 +1,94 @@
|
||||
#// Borken, candidate for removal
|
||||
export WINEARCH="win64" # Migrated to wine64 with WINETRICKS_FORCE=1 - complex .NET dependencies, test carefully
|
||||
download "http://blind-games.com/newentombed/EntombedSetup.exe" "https://download.microsoft.com/download/E/C/1/EC1B2340-67A0-4B87-85F0-74D987A27160/SSCERuntime-ENU.exe" "https://stormgames.wolfe.casa/downloads/Entombed.exe.config" "https://stormgames.wolfe.casa/downloads/mfplat.dll"
|
||||
export winVer="win7"
|
||||
install_wine_bottle sapi msvcrt40 gdiplus ie7 wmp11 mf
|
||||
# Ok, more dotnet.
|
||||
LC_ALL=C DISPLAY="" winetricks -q dotnet40 xna40
|
||||
wineserver -k # Sigh.
|
||||
mkdir -p "${WINEPREFIX}/drive_c/temp"
|
||||
pushd "${WINEPREFIX}/drive_c/temp"
|
||||
install_with_progress 7z "Extracting game files..." x "${cache}/SSCERuntime-ENU.exe"
|
||||
wine msiexec /i "${WINEPREFIX}/drive_c/temp/SSCERuntime_x86-ENU.msi" /q
|
||||
rm *
|
||||
popd
|
||||
pushd "${WINEPREFIX}/drive_c/Program Files/Microsoft SQL Server Compact Edition/v3.5"
|
||||
wine regsvr32 sqlceoledb35.dll
|
||||
wine regsvr32 sqlceca35.dll
|
||||
popd
|
||||
wine "${cache}/EntombedSetup.exe" /silent
|
||||
pushd "${WINEPREFIX}/drive_c/Program Files/Entombed"
|
||||
cp ../Microsoft\ SQL\ Server\ Compact\ Edition/v3.5/Private/System.Data.SqlServerCe.Entity.dll ../Microsoft\ SQL\ Server\ Compact\ Edition/v3.5/Private/System.Data.SqlServerCe.dll .
|
||||
cp ../Microsoft\ SQL\ Server\ Compact\ Edition/v3.5/sql* .
|
||||
cp "${cache}/Entombed.exe.config" .
|
||||
popd
|
||||
if [ ! -f "${WINEPREFIX}/drive_c/windows/system32/mfplat.dll" ] ; then
|
||||
# shellcheck shell=bash disable=SC2154 # cache, game, and helper functions are set by audiogame-manager.
|
||||
|
||||
export game="Entombed"
|
||||
entombedGameId="entombed"
|
||||
entombedPath='c:\Program Files (x86)\Entombed\Entombed.exe'
|
||||
|
||||
download "http://blind-games.com/newentombed/EntombedSetup.exe" \
|
||||
"https://download.microsoft.com/download/E/C/1/EC1B2340-67A0-4B87-85F0-74D987A27160/SSCERuntime-ENU.exe" \
|
||||
"https://stormgames.wolfe.casa/downloads/Entombed.exe.config" \
|
||||
"https://stormgames.wolfe.casa/downloads/mfplat.dll"
|
||||
|
||||
export PROTON_USE_XALIA=0
|
||||
install_proton_bottle "$entombedGameId"
|
||||
|
||||
entombedProfileSid="$(sed -n 's/^;; All keys relative to REGISTRY\\\\User\\\\//p' "${WINEPREFIX}/user.reg" | head -n 1)"
|
||||
if [[ -n "$entombedProfileSid" ]]; then
|
||||
umu-run reg add "HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\${entombedProfileSid}" /v ProfileImagePath /t REG_SZ /d "C:\\users\\steamuser" /f
|
||||
fi
|
||||
|
||||
mkdir -p \
|
||||
"${WINEPREFIX}/drive_c/users/steamuser/AppData/Local/IsolatedStorage" \
|
||||
"${WINEPREFIX}/drive_c/users/steamuser/AppData/Roaming/IsolatedStorage"
|
||||
|
||||
alert "Entombed" "Entombed" "If you hear a window open during installation, or if the installer seems to be taking a long time, try pressing enter to see if it will continue."
|
||||
|
||||
{
|
||||
echo "# Installing Entombed dependencies..."
|
||||
WINETRICKS_FORCE=1 install_proton_winetricks_verb speechsdk
|
||||
install_proton_winetricks_verb msvcrt40
|
||||
install_proton_winetricks_verb gdiplus
|
||||
install_proton_winetricks_verb wmp11
|
||||
install_proton_winetricks_verb mf
|
||||
install_proton_winetricks_verb dotnet40
|
||||
install_proton_winetricks_verb xna40
|
||||
install_proton_winetricks_verb win7
|
||||
|
||||
echo "# Extracting SQL Server Compact runtime..."
|
||||
entombedTempDir="${WINEPREFIX}/drive_c/temp"
|
||||
mkdir -p "$entombedTempDir"
|
||||
7z x -y "-o${entombedTempDir}" "${cache}/SSCERuntime-ENU.exe"
|
||||
|
||||
echo "# Installing SQL Server Compact runtime..."
|
||||
umu-run msiexec /i "c:\\temp\\SSCERuntime_x86-ENU.msi" /q
|
||||
umu-run regsvr32 "c:\\Program Files\\Microsoft SQL Server Compact Edition\\v3.5\\sqlceoledb35.dll"
|
||||
umu-run regsvr32 "c:\\Program Files\\Microsoft SQL Server Compact Edition\\v3.5\\sqlceca35.dll"
|
||||
|
||||
echo "# Installing Entombed..."
|
||||
umu-run "${cache}/EntombedSetup.exe" /silent
|
||||
find "$entombedTempDir" -mindepth 1 -maxdepth 1 -exec rm -rf "{}" +
|
||||
echo "# Installation complete"
|
||||
} | agm_progressbox "Installing Game" "Installing Entombed with UMU/Proton (this may take several minutes)..."
|
||||
|
||||
stop_umu_bottle
|
||||
|
||||
entombedInstallDir=""
|
||||
for entombedCandidateDir in \
|
||||
"${WINEPREFIX}/drive_c/Program Files (x86)/Entombed" \
|
||||
"${WINEPREFIX}/drive_c/Program Files/Entombed"; do
|
||||
if [[ -f "${entombedCandidateDir}/Entombed.exe" ]]; then
|
||||
entombedInstallDir="$entombedCandidateDir"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$entombedInstallDir" ]]; then
|
||||
agm_msgbox "Entombed" "Entombed" "Entombed did not install to the expected location."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
entombedSqlDir=""
|
||||
for entombedCandidateSqlDir in \
|
||||
"${WINEPREFIX}/drive_c/Program Files (x86)/Microsoft SQL Server Compact Edition/v3.5" \
|
||||
"${WINEPREFIX}/drive_c/Program Files/Microsoft SQL Server Compact Edition/v3.5"; do
|
||||
if [[ -d "$entombedCandidateSqlDir" ]]; then
|
||||
entombedSqlDir="$entombedCandidateSqlDir"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$entombedSqlDir" ]]; then
|
||||
agm_msgbox "Entombed" "Entombed" "SQL Server Compact v3.5 did not install to the expected location."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "${entombedSqlDir}/Private/System.Data.SqlServerCe.Entity.dll" "$entombedInstallDir/"
|
||||
cp "${entombedSqlDir}/Private/System.Data.SqlServerCe.dll" "$entombedInstallDir/"
|
||||
cp "${entombedSqlDir}"/sql* "$entombedInstallDir/"
|
||||
cp "${cache}/Entombed.exe.config" "${entombedInstallDir}/Entombed.exe.config"
|
||||
|
||||
if [[ ! -f "${WINEPREFIX}/drive_c/windows/system32/mfplat.dll" ]]; then
|
||||
cp "${cache}/mfplat.dll" "${WINEPREFIX}/drive_c/windows/system32/"
|
||||
fi
|
||||
add_launcher "c:\Program Files (x86)\Entombed\Entombed.exe"
|
||||
|
||||
add_umu_launcher "$entombedGameId" "$entombedPath" "export PROTON_USE_XALIA=0"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
download "http://files.l-works.net/judgmentdayfullsetup.exe"
|
||||
# shellcheck shell=bash disable=SC2154 # cache is set by audiogame-manager
|
||||
download "https://www.l-works.net/files/judgmentdayfullsetup.exe"
|
||||
install_wine_bottle vb6run dx8vb quartz
|
||||
wine "${cache}/judgmentdayfullsetup.exe" /silent
|
||||
cat << EOF > /tmp/judgementday.reg
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
export WINEARCH=win64
|
||||
# shellcheck disable=SC2154 # installer is sourced by audiogame-manager with shared globals
|
||||
export winVer="win10"
|
||||
game="${game:-Magic: The Gathering Arena}"
|
||||
|
||||
mtgaVersionUrl="https://mtgarena.downloads.wizards.com/Live/Windows32/version"
|
||||
accessibleArenaDllUrl="https://github.com/JeanStiletto/AccessibleArena/releases/latest/download/AccessibleArena.dll"
|
||||
melonLoaderZipUrl="https://github.com/LavaGang/MelonLoader/releases/latest/download/MelonLoader.x64.zip"
|
||||
tolkDllUrl="https://stormgames.wolfe.casa/downloads/Tolk.dll"
|
||||
|
||||
get_mtga_installer_url() {
|
||||
local versionJson=""
|
||||
local installerUrl=""
|
||||
|
||||
if ! versionJson="$(curl -fsSL "$mtgaVersionUrl")"; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not fetch the current MTG Arena installer URL."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
installerUrl="$(sed -n 's/.*"CurrentInstallerURL"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' <<< "$versionJson" | head -n1)"
|
||||
if [[ -z "$installerUrl" ]]; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not parse the current MTG Arena installer URL."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$installerUrl"
|
||||
}
|
||||
|
||||
download_mtga_installer() {
|
||||
local installerUrl="$1"
|
||||
local installerFile="${installerUrl##*/}"
|
||||
installerFile="${installerFile%%\?*}"
|
||||
[[ -n "$installerFile" ]] || installerFile="MTGAInstaller.msi"
|
||||
|
||||
# shellcheck disable=SC2154 # cache is set by audiogame-manager before installers are sourced
|
||||
if [[ "${redownload:-}" == "true" ]] || [[ ! -s "${cache}/${installerFile}" ]]; then
|
||||
if ! curl -L4 -C - --retry 10 --output "${cache}/${installerFile}" "$installerUrl" 2>&1 | agm_progressbox "Magic: The Gathering Arena" "Downloading MTG Arena installer..."; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not download the MTG Arena installer."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mtgaInstallerPath="${cache}/${installerFile}"
|
||||
}
|
||||
|
||||
configure_accessible_arena_loader() {
|
||||
local mtgaRoot="$1"
|
||||
local userDataPath="${mtgaRoot}/UserData"
|
||||
local loaderConfig="${userDataPath}/Loader.cfg"
|
||||
|
||||
mkdir -p "$userDataPath"
|
||||
if [[ -f "$loaderConfig" ]]; then
|
||||
if grep -Fq "hide_console = false" "$loaderConfig"; then
|
||||
sed -i 's/hide_console = false/hide_console = true/g' "$loaderConfig"
|
||||
elif ! grep -Fq "hide_console" "$loaderConfig"; then
|
||||
printf '\n[console]\nhide_console = true\n' >> "$loaderConfig"
|
||||
fi
|
||||
else
|
||||
printf '[console]\nhide_console = true\n' > "$loaderConfig"
|
||||
fi
|
||||
}
|
||||
|
||||
install_accessible_arena_support() {
|
||||
local mtgaRoot="$1"
|
||||
|
||||
if [[ -z "${nvdaControllerClient64Dll:-}" ]]; then
|
||||
# shellcheck disable=SC2154 # ipfs is sourced through audiogame-manager helpers
|
||||
nvdaControllerClient64Dll="${ipfs[nvdaControllerClient64]}"
|
||||
fi
|
||||
|
||||
download "$accessibleArenaDllUrl" "$melonLoaderZipUrl" "$tolkDllUrl" "$nvdaControllerClient64Dll"
|
||||
|
||||
install_with_progress unzip "Installing MelonLoader..." -d "$mtgaRoot" "${cache}/MelonLoader.x64.zip"
|
||||
mkdir -p "${mtgaRoot}/Mods"
|
||||
install_with_progress cp "Installing Accessible Arena..." "${cache}/AccessibleArena.dll" "${mtgaRoot}/Mods/AccessibleArena.dll"
|
||||
install_with_progress cp "Installing screen reader support DLLs..." "${cache}/Tolk.dll" "${cache}/nvdaControllerClient64.dll" "$mtgaRoot"
|
||||
configure_accessible_arena_loader "$mtgaRoot"
|
||||
}
|
||||
|
||||
install_wine_bottle dxvk
|
||||
winetricks -q $winVer
|
||||
|
||||
mtgaRoot="${WINEPREFIX}/drive_c/Program Files (x86)/Wizards of the Coast/MTGA"
|
||||
mtgaInstallerUrl="$(get_mtga_installer_url)"
|
||||
download_mtga_installer "$mtgaInstallerUrl"
|
||||
|
||||
wine msiexec /i "$mtgaInstallerPath" /q
|
||||
|
||||
if [[ ! -f "${mtgaRoot}/MTGA.exe" ]]; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "MTG Arena installation did not finish at the expected location: ${mtgaRoot}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install_accessible_arena_support "$mtgaRoot"
|
||||
add_launcher 'c:\Program Files (x86)\Wizards of the Coast\MTGA\MTGA.exe' 'export WINEDLLOVERRIDES=version=n,b'
|
||||
@@ -1,12 +1,12 @@
|
||||
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
|
||||
export WINEARCH="win64" # Migrated to wine64
|
||||
|
||||
export winVer="win7"
|
||||
|
||||
get_installer "Mist World_Setup.exe" "https://drive.google.com/uc?export=download&id=12YeUqorkkMT46ZSR5pcfWxSY8DHOLxZ-"
|
||||
get_installer "MistWorld_Setup_260323.exe" "https://download.mwgame.net/MistWorld_Setup_260323.exe"
|
||||
install_wine_bottle
|
||||
install_with_progress 7z "Extracting game files..." x -o"$WINEPREFIX/drive_c/Program Files/Mist World" "$cache/Mist World_Setup.exe"
|
||||
sed -i 's/1024m/768m/g' "$WINEPREFIX/drive_c/Program Files/Mist World/mw.exe.vmoptions"
|
||||
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'
|
||||
install_with_progress 7z "Extracting game files..." x -o"$WINEPREFIX/drive_c/Program Files (x86)/Mist World" "$cache/MistWorld_Setup_260323.exe"
|
||||
sed -i 's/1024m/768m/g' "$WINEPREFIX/drive_c/Program Files (x86)/Mist World/mw.exe.vmoptions"
|
||||
cp "$WINEPREFIX/drive_c/Program Files (x86)/Mist World/"{mw.exe.vmoptions,update.exe.vmoptions}
|
||||
mkdir "$WINEPREFIX/drive_c/Program Files (x86)/Mist World/"{user,users}
|
||||
add_launcher 'c:\Program Files (x86)\Mist World\mw.exe'
|
||||
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."
|
||||
|
||||
+35
-20
@@ -1,23 +1,38 @@
|
||||
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
|
||||
#//Disable since it's not working
|
||||
download "https://www.mm-galabo.com/sr/Download_files_srfv/shadowrine_fullvoice3.171.exe" "https://raw.githubusercontent.com/LordLuceus/sr-english-localization/master/language_en.dat"
|
||||
export WINEARCH="win64" # Migrated to wine64 with WINETRICKS_FORCE=1
|
||||
export winVer="win8"
|
||||
install_wine_bottle fakejapanese
|
||||
# Add bcrypt DLL override required for Shadow Line to run
|
||||
cat > /tmp/bcrypt_override.reg << 'EOF'
|
||||
[HKEY_CURRENT_USER\Software\Wine\DllOverrides]
|
||||
"bcryptprimitives"="native,builtin"
|
||||
EOF
|
||||
wine regedit /tmp/bcrypt_override.reg
|
||||
rm /tmp/bcrypt_override.reg
|
||||
# shellcheck shell=bash disable=SC2154 # cache, game, and helper functions are set by audiogame-manager.
|
||||
|
||||
export game="Shadow Line"
|
||||
shadowLineGameId="shadow-line"
|
||||
shadowLinePath='c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
|
||||
download "https://www.mm-galabo.com/sr/Download_files_srfv/shadowrine_fullvoice3.171.exe" \
|
||||
"https://raw.githubusercontent.com/LordLuceus/sr-english-localization/master/language_en.dat" \
|
||||
"${nvdaControllerClient32Dll}" \
|
||||
"${nvdaControllerClient64Dll}"
|
||||
|
||||
install_proton_bottle "$shadowLineGameId" fakejapanese
|
||||
shadowLineInstallDir="${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
|
||||
|
||||
set_umu_reg_value "HKCU\\Software\\Wine\\DllOverrides" "bcryptprimitives" "native,builtin"
|
||||
set_umu_app_winver "play_sr.exe" "win8"
|
||||
|
||||
{
|
||||
echo "# Installing Shadow Line..."
|
||||
timeout 300 wine "${cache}/shadowrine_fullvoice3.171.exe" /sp- /VERYSILENT /SUPPRESSMSGBOXES 2>&1 || true
|
||||
timeout 300 umu-run "${cache}/shadowrine_fullvoice3.171.exe" /sp- /VERYSILENT /SUPPRESSMSGBOXES 2>&1 || true
|
||||
echo "# Installation complete"
|
||||
} | agm_progressbox "Installing Game" "Installing Shadow Line (this may take a few minutes)..."
|
||||
# Kill any auto-launched game processes (installer lacks skipifsilent flag)
|
||||
wineserver -k 2>/dev/null || true
|
||||
mv -v "${cache}/language_en.dat" "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/SystemData/language_en.dat"
|
||||
add_launcher "c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe"
|
||||
alert "Please set the language to English when the game opens.\nGo to options and press enter.\nPress down arrow 5 times and press enter.\nPress down arrow 1 time and press enter.\nPress up arrow 2 times and press enter.\nIf everything worked as expected you should be back on the game menu and speech should work."
|
||||
} | agm_progressbox "Installing Game" "Installing Shadow Line with UMU/Proton (this may take a few minutes)..."
|
||||
|
||||
stop_umu_bottle
|
||||
|
||||
if [[ ! -f "${shadowLineInstallDir}/play_sr.exe" ]]; then
|
||||
agm_msgbox "Shadow Line" "Shadow Line" "Shadow Line did not install to the expected location: ${shadowLineInstallDir}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install_crlf_file "${cache}/language_en.dat" "${shadowLineInstallDir}/SystemData/language_en.dat"
|
||||
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient32.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient64.dll' -exec cp -v "${cache}/nvdaControllerClient64.dll" "{}" \;
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
|
||||
|
||||
add_umu_launcher "$shadowLineGameId" "$shadowLinePath"
|
||||
alert "Shadow Line" "Shadow Line" "Please set the language to English when the game opens.\nGo to options and press enter.\nPress down arrow 5 times and press enter.\nPress down arrow 1 time and press enter.\nPress up arrow 2 times and press enter.\nIf everything worked as expected you should be back on the game menu and speech should work."
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export winetricksSettings="vd=1024x768"
|
||||
install_wine_bottle
|
||||
download "https://www.kaldobsky.com/audiogames/swamp2.zip"
|
||||
|
||||
install_with_progress unzip "Extracting game files..." -d "${WINEPREFIX}/drive_c/Program Files/swamp2" "${cache}/swamp2.zip"
|
||||
|
||||
add_launcher "c:\Program Files\swamp2\swamp2.exe"
|
||||
alert "This game has native support for NVDA, but it is not the default.\nTo set it up so that it will talk to you, you need to press the tab key immediately after you launch the game."
|
||||
@@ -0,0 +1,105 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Project Purpose
|
||||
|
||||
Audiogame Manager installs and launches Windows audio games under Wine or UMU/Proton on Linux. Contributions must preserve keyboard and screen-reader accessibility in both console `dialog` and graphical `yad` modes.
|
||||
|
||||
## Repository Map
|
||||
|
||||
- `audiogame-manager.sh`: main entry point, game menu, launcher, removal flow, and shared runtime state.
|
||||
- `.install/`: one sourced Bash installer per game. The filename, minus `.sh`, is the game name shown in the install menu.
|
||||
- `.includes/`: shared bottle, UMU, download, dialog, desktop, help, update, and URL helpers.
|
||||
- `.includes/ipfs.sh`: centralized IPFS URLs for core files and games.
|
||||
- `game-scripts/`: scripts installed or used by particular games after installation.
|
||||
- `speech/`: speech-related helper and setup scripts.
|
||||
- `wine/`: distribution-specific dependency installers and Wine utilities.
|
||||
- `tests/`: isolated shell regression tests with mocked external programs.
|
||||
|
||||
## Runtime Architecture
|
||||
|
||||
- This is a Bash project, not a POSIX `sh` project. Arrays, associative arrays, `mapfile`, `[[ ... ]]`, and Bash parameter expansion are used intentionally.
|
||||
- Installer files in `.install/` are sourced by the main process. They share functions and exported state from `audiogame-manager.sh` and `.includes/`; they are not independent programs.
|
||||
- Common installer state includes `game`, `cache`, `WINEPREFIX`, `WINEARCH`, `winetricksSettings`, and helper functions such as `download`, `install_wine_bottle`, `install_proton_bottle`, `install_with_progress`, `add_launcher`, and `add_umu_launcher`.
|
||||
- The launcher configuration is pipe-delimited. Keep its field order compatible with `create_game_array()` and `process_launcher_flags()`.
|
||||
- Native Wine and UMU/Proton are separate backends. Use the helpers for the selected backend; do not mix their bottle paths, environment setup, launcher functions, or shutdown functions.
|
||||
- The main script performs dependency checks, bottle setup, update checks, and other startup work before command dispatch. Do not source it casually in tests. Source the smallest `.includes/` file needed and mock its external commands.
|
||||
|
||||
## Game Installer Conventions
|
||||
|
||||
- Name a new installer `.install/Game Name.sh`; that filename becomes the menu label.
|
||||
- A first line beginning with `#//` hides an installer from the menu. Preserve this convention when editing disabled installers.
|
||||
- Quote paths and expansions, especially game names and Windows paths containing spaces.
|
||||
- Use `download` so caching, progress reporting, retries, and validation remain consistent.
|
||||
- Use `install_with_progress` for archive extraction or copies that could otherwise prompt invisibly. Extraction must be non-interactive and safe to repeat.
|
||||
- Use `install_wine_bottle` plus `add_launcher` for the Wine backend.
|
||||
- Use `install_proton_bottle`, the UMU helpers, and `add_umu_launcher` for the UMU backend.
|
||||
- After installation, verify the expected executable exists before recording a launcher when failure would otherwise produce a broken menu entry.
|
||||
- Set game-specific environment or winetricks values in the installer rather than changing global defaults for one game.
|
||||
- Prefer idempotent installation steps. Re-running an installer should not hang on overwrite prompts or silently corrupt an existing bottle.
|
||||
- Do not delete a shared Wine or Proton bottle to remove one game. Removal code must target only the selected game's files and launcher entry.
|
||||
|
||||
## Portability and Dependencies
|
||||
|
||||
- Contributors and coding agents may use any locally installed tools, including ripgrep (`rg`), while searching, reviewing, testing, or editing the repository. This restriction applies only to commands invoked by scripts shipped to users.
|
||||
- Keep commands used by shipped scripts portable. Do not make runtime code depend on ripgrep or other modern command-line tools that are not commonly installed by default when classic Unix tools can provide the required behavior.
|
||||
- Prefer broadly available classic Unix tools such as `grep`, `sed`, `awk`, and `find` when they provide the required behavior.
|
||||
- A nonstandard runtime dependency is acceptable when there is no practical portable alternative, but it must be declared and checked rather than assumed.
|
||||
- Add every new runtime dependency to `.includes/checkup.sh`, including its `packageList` entry so `audiogame-manager.sh -P` reports it. Update the relevant distribution-specific dependency scripts under `wine/` when they manage packages for that platform.
|
||||
- If Audiogame Manager cannot perform its basic startup or core functions without a dependency, also add it to the startup checks in `check_requirements()` alongside critical commands such as `sox` and `dialog`.
|
||||
- Do not assume a developer's interactive shell aliases, local utilities, desktop session, or current working directory are available.
|
||||
- Resolve repository files relative to `scriptDir` or `BASH_SOURCE`, as appropriate.
|
||||
- Do not add compatibility fallbacks or legacy paths unless they are an explicit requirement.
|
||||
|
||||
## Shell Style
|
||||
|
||||
- Follow the surrounding file's style and keep edits narrowly scoped.
|
||||
- For new code, use camelCase variables and snake_case functions. Use PascalCase only for class-like concepts if any are introduced.
|
||||
- Quote variable expansions unless intentional splitting or pattern matching is required.
|
||||
- Prefer arrays for argument lists; do not construct commands in strings and evaluate them.
|
||||
- Treat sourced shared globals deliberately. Add a focused ShellCheck suppression with a reason when a value is populated by the caller; do not broadly silence actionable warnings.
|
||||
- Logging timestamps follow the message: `message [date]`.
|
||||
- Do not add colored output unless requested.
|
||||
|
||||
## Accessibility and Interaction
|
||||
|
||||
- Screen-reader and keyboard users are first-class users.
|
||||
- Use the `agm_*` wrappers from `.includes/dialog-interface.sh` instead of invoking `dialog` or `yad` directly. Changes must continue to work in both interfaces.
|
||||
- Keep every workflow operable without a mouse. Do not introduce keyboard traps or communicate state only through color, sound, or visual layout.
|
||||
- Do not use `spd-say` or direct Speech Dispatcher calls in graphical interfaces. Expose information through accessible controls and the existing dialog wrappers.
|
||||
- Avoid commands that can stop at an invisible prompt behind a progress box. Supply non-interactive flags and handle failures explicitly.
|
||||
- When changing accessibility behavior, verify the exact affected console and GUI workflow when those environments are available. Automated shell checks do not prove live screen-reader behavior.
|
||||
|
||||
## Downloads, URLs, and External State
|
||||
|
||||
- Treat remote URLs, archive layouts, executable names, and installer behavior as changeable external state. Verify them when working on a download or installer rather than relying on an old report.
|
||||
- Keep reusable IPFS references centralized in `.includes/ipfs.sh` and preserve their explicit `filename` query when the cache filename depends on it.
|
||||
- Never include credentials, private tokens, personal usernames, or live user paths in code, fixtures, logs, or examples.
|
||||
- Do not run destructive installer or removal tests against the real home directory, Wine prefixes, or game data.
|
||||
|
||||
## Testing and Verification
|
||||
|
||||
- For every edited Bash or `.sh` file, run:
|
||||
|
||||
```bash
|
||||
bash -n path/to/file.sh
|
||||
shellcheck path/to/file.sh
|
||||
```
|
||||
|
||||
- Fix real ShellCheck findings. A narrow suppression is acceptable for intentionally sourced globals or dynamic source paths when it includes a reason.
|
||||
- Run the smallest relevant test under `tests/`. Tests must use a temporary directory, replace external programs with stubs, and avoid network, GUI, Wine, and real user-state changes.
|
||||
- For UMU helper changes, run:
|
||||
|
||||
```bash
|
||||
bash tests/umu_backend_tests.sh
|
||||
```
|
||||
|
||||
- For changes spanning many shell files, syntax-check every changed shell file rather than assuming one successful check covers sourced code.
|
||||
- Before handing work back, run `git diff --check` and inspect `git status --short --untracked-files=all` plus the final diff.
|
||||
- Distinguish automated verification from live acceptance. Installer, Wine, audio, focus, controller, and screen-reader behavior may still require a real installation or launch test.
|
||||
|
||||
## Repository Hygiene
|
||||
|
||||
- Preserve unrelated tracked and untracked work. Do not reset, clean, overwrite, or incorporate files outside the requested scope.
|
||||
- Do not edit generated caches, Wine prefixes, downloaded game data, or logs as source changes.
|
||||
- Keep contributor changes reviewable; avoid unrelated formatting or refactors in installer fixes.
|
||||
- Do not commit, merge, push, or alter remote state unless explicitly requested.
|
||||
@@ -1,265 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
**Critical**: Be sure to keep this file up to date with new changes.
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Audiogame Manager** is a comprehensive bash-based installer and launcher system for Windows audio games running under Wine on Linux. The project focuses on accessibility, providing speech synthesis support and screen reader compatibility for audio games designed for blind and visually impaired users.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
- **Main Script**: `audiogame-manager.sh` - Entry point providing interactive menus for game installation, launching, and management with Wine32/64 bottle support
|
||||
- **Includes Directory**: `.includes/` - Modular helper scripts containing core functionality
|
||||
- `functions.sh` - Core utilities (download, cache management, validation, requirements checking)
|
||||
- `dialog-interface.sh` - UI abstraction layer supporting both console (dialog) and GUI (yad) modes
|
||||
- `bottle.sh` - Wine bottle management, RHVoice installation, and prefix management
|
||||
- `checkup.sh` - System dependency validation
|
||||
- `help.sh` - Documentation system
|
||||
- `desktop.sh` - Desktop integration
|
||||
- `update.sh` - Auto-update mechanisms
|
||||
- **Game Scripts**: `game-scripts/` - Individual game update/launch scripts
|
||||
- **Install Scripts**: `.install/` - Game installation scripts (100+ games supported)
|
||||
- **Speech Integration**: `speech/` - TTS and accessibility tools
|
||||
- `set-voice.sh` - Voice configuration with test functionality
|
||||
- Supports multiple TTS engines (RHVoice, SAPI, Cepstral)
|
||||
- Per-bottle voice configuration system
|
||||
- NVDA screen reader compatibility through custom DLL
|
||||
- **Wine Integration**: `wine/` - Wine setup utilities including bottle creation and dependency installation
|
||||
- `mkwine.sh` - Wine bottle creation
|
||||
- Distribution-specific dependency installers
|
||||
|
||||
### Wine Architecture
|
||||
|
||||
The project uses Wine with a unified architecture:
|
||||
- **Wine64 Only**: All games use wine64+WOW64 exclusively - it runs both 32-bit and 64-bit applications efficiently
|
||||
- **Wine32 ELIMINATED**: As of 2025-12-06, wine32 bottle is no longer created or used
|
||||
- **Unified bottle**: Single wine64 prefix (stored in `~/.local/wine64`) for ALL games, including SAPI games
|
||||
- **Custom bottles**: Can be stored in `~/.local/share/audiogame-manager/wineBottles/` with per-bottle configurations in `~/.config/audiogame-manager/`
|
||||
- **IMPORTANT**: Never create game-specific wine directories like `~/.local/winegamename` - use the standard bottle system
|
||||
|
||||
#### SAPI and Speech SDK Support (WINETRICKS_FORCE=1)
|
||||
|
||||
**Discovery (2025-12-06)**: Setting `WINETRICKS_FORCE=1` enables reliable speechsdk installation in wine64+WOW64 bottles, eliminating the need for wine32 for most SAPI-dependent games.
|
||||
|
||||
**Implementation:**
|
||||
- `audiogame-manager.sh:96` - wine64 bottle creation includes speechsdk with `WINETRICKS_FORCE=1`
|
||||
- `audiogame-manager.sh:99` - Restores win10 after speechsdk (see caveat below)
|
||||
- `.includes/bottle.sh:176-181` - Only speechsdk installations use `WINETRICKS_FORCE=1` (other deps use regular winetricks)
|
||||
- Both bottles install Microsoft Mike as the default SAPI voice automatically
|
||||
|
||||
**CAVEAT**: The `speechsdk` winetricks verb sets `w_set_winver winxp` at the end, trampling any previously set Windows version. Always call `winetricks win10` AFTER installing speechsdk.
|
||||
|
||||
**Migrated SAPI Games (wine64):**
|
||||
- Bloodshed - Tested and confirmed working
|
||||
- Kitchensinc Games - Tested and confirmed working (VB6)
|
||||
- Oh Shit - Migrated, requires testing
|
||||
- Dog Who Hates Toast - Migrated, requires testing (VB6)
|
||||
- Lunimals - Migrated, requires testing (VB6)
|
||||
- VIP Mud - Migrated, requires testing (VB6)
|
||||
- Entombed - Migrated, requires testing (complex .NET dependencies)
|
||||
- Skateboarder Pro - Already configured for wine64, now functional
|
||||
- Three D velocity - Already configured for wine64, now functional
|
||||
|
||||
**Migrated Self-Voicing Games (wine64):**
|
||||
- Shadow Line - Migrated, requires Windows 8 via per-app versioning
|
||||
- Villains From Beyond - Tested and confirmed working
|
||||
|
||||
#### Per-Application Windows Version
|
||||
|
||||
**Problem (2025-12-08)**: Some games require specific Windows versions (e.g., Shadow Line needs win8) but with a unified bottle, `winetricks winXX` changes the version globally for ALL games.
|
||||
|
||||
**Solution**: Use Wine's per-application settings via registry keys:
|
||||
```
|
||||
[HKEY_CURRENT_USER\Software\Wine\AppDefaults\executable.exe]
|
||||
"Version"="win8"
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- Game installers set `export winVer="win8"` (or win7, win10)
|
||||
- `install_wine_bottle()` stores this in `gameWinVer` (doesn't apply globally)
|
||||
- `add_launcher()` calls `set_app_winver()` which writes the per-exe registry key
|
||||
- Located in `.includes/bottle.sh:186-209` (`set_app_winver` function)
|
||||
|
||||
**Architecture Selection Logic:**
|
||||
- Games explicitly setting `WINEARCH=win64` will use wine64 (new behavior)
|
||||
- Games passing `sapi` dependency use wine64 with speechsdk pre-installed
|
||||
- Legacy games passing `speechsdk` dependency still use wine32 for compatibility
|
||||
- Default architecture is wine64 for all new games
|
||||
|
||||
### Interface System
|
||||
|
||||
Dialog interface automatically detects environment:
|
||||
- Console mode: Uses `dialog` for accessibility
|
||||
- GUI mode: Uses `yad` when DISPLAY is available
|
||||
- All UI functions are prefixed with `agm_` (audiogame manager)
|
||||
|
||||
## Development Commands
|
||||
|
||||
### System Check
|
||||
```bash
|
||||
./audiogame-manager.sh -c # Check system requirements and dependencies
|
||||
bash -n audiogame-manager.sh # Basic syntax check
|
||||
```
|
||||
|
||||
### Installation and Usage
|
||||
```bash
|
||||
./audiogame-manager.sh # Launch installed games menu
|
||||
./audiogame-manager.sh -i # Install games (interactive menu)
|
||||
./audiogame-manager.sh -I "Game Name" # Install a game noninteractively
|
||||
./audiogame-manager.sh -h # Show help
|
||||
```
|
||||
|
||||
### Wine Bottle Management
|
||||
```bash
|
||||
./wine/mkwine.sh [bottle_name] [architecture] # Create a new Wine bottle
|
||||
```
|
||||
|
||||
### Voice and Speech Testing
|
||||
```bash
|
||||
./speech/set-voice.sh # Configure and test voice settings
|
||||
./speech/set-voice.sh [bottle_name] # Configure voice for a specific bottle
|
||||
```
|
||||
|
||||
### Dependency Management
|
||||
```bash
|
||||
# Check all required packages
|
||||
./.includes/checkup.sh packages
|
||||
|
||||
# Install Wine dependencies for different distros
|
||||
./wine/install-dependencies-arch.sh
|
||||
./wine/install-dependencies-debian.sh
|
||||
```
|
||||
|
||||
### Testing All Include Files
|
||||
```bash
|
||||
for f in .includes/*.sh; do bash -n "$f"; done
|
||||
```
|
||||
|
||||
## Key Patterns and Conventions
|
||||
|
||||
### Coding Style - **EXTREMELY IMPORTANT - MUST BE FOLLOWED**
|
||||
|
||||
- **Variables**: Use camelCase for variable names (e.g., `gameTitle`, `wineBottle`, `installPath`)
|
||||
- **CRITICAL**: ALL variables must use camelCase - no exceptions
|
||||
- Examples: `selectedGame`, `wineArch`, `installPath`, `downloadUrl`
|
||||
- Never use: `selected_game`, `wine_arch`, `install_path`, `download_url`
|
||||
- **Functions**: Use snake_case for function names (e.g., `install_game`, `create_wine_bottle`, `check_dependencies`)
|
||||
- **CRITICAL**: ALL functions must use snake_case - no exceptions
|
||||
- Examples: `get_wine_bottle()`, `process_launcher_flags()`, `download_file()`
|
||||
- Never use: `getWineBottle()`, `processLauncherFlags()`, `downloadFile()`
|
||||
- **Shebang**: Use `#!/bin/bash` for all bash scripts
|
||||
- **CRITICAL**: .sh files in .install/ are game installers, not typical bash scripts If they contain # on the first line they are disabled and will not show up in audiogame-manager
|
||||
- **Sourcing pattern**:
|
||||
- **Main script** (`audiogame-manager.sh`): Use `source "${scriptDir}/.includes/file.sh"` (scriptDir is defined at line 4)
|
||||
- **Subdirectory scripts** (game-scripts/, wine/, speech/): Use `source "${0%/*}/../.includes/file.sh"`
|
||||
- **CRITICAL**: Never use relative paths like `source .includes/file.sh` - these fail when the current working directory differs from the script location
|
||||
- **Indentation**: Use consistent indentation (tabs or spaces, follow existing file patterns)
|
||||
- When fixing code, correct any indentation inconsistencies to match the established style
|
||||
|
||||
### Error Handling
|
||||
- Functions return 0 for success, non-zero for failure
|
||||
- Use consistent exit codes throughout scripts
|
||||
- Provide clear error messages with context
|
||||
- Critical errors vs warnings are clearly distinguished in checkup system
|
||||
- Progress feedback is provided for all long-running operations
|
||||
- Always clean up temporary files on exit
|
||||
- Log important operations for debugging
|
||||
|
||||
### File Structure
|
||||
- Game installers follow naming convention: `.install/Game Name.sh`
|
||||
- **IMPORTANT**: Games starting with hash (#) are commented out even if it's a comment
|
||||
- First line of a game installer may not start with # unless we're excluding it
|
||||
- Cache directory: `~/.local/share/audiogame-manager/cache/`
|
||||
- Wine bottles: `~/.local/wine32/` and `~/.local/wine64/`
|
||||
- Custom bottles: `~/.local/share/audiogame-manager/wineBottles/`
|
||||
- Configuration: `~/.config/audiogame-manager/`
|
||||
|
||||
### UI Functions
|
||||
All dialog functions in `.includes/dialog-interface.sh` follow pattern:
|
||||
- `agm_menu()` - Selection menus
|
||||
- `agm_msgbox()` - Message display
|
||||
- `agm_yesno()` - Confirmation dialogs
|
||||
- `agm_progressbox()` - Progress display
|
||||
- Functions automatically adapt to console/GUI environment
|
||||
|
||||
### Game Integration
|
||||
- Each game has both an installer (`.install/`) and optional update script (`game-scripts/`)
|
||||
- Games are categorized by engine type and requirements
|
||||
- SAPI games automatically use Wine32, others use Wine64
|
||||
- Games are tracked in configuration files with Wine bottle associations
|
||||
- Each game can use different Wine bottles with specific configurations
|
||||
|
||||
### Game Installation Pattern
|
||||
1. Check system requirements and dependencies
|
||||
2. Create or verify Wine bottle
|
||||
3. Install Wine dependencies via winetricks
|
||||
4. Download and validate game files (use checksums when available)
|
||||
5. Configure speech synthesis (typically RHVoice)
|
||||
6. Add game entry to launcher configuration
|
||||
|
||||
### Accessibility Features
|
||||
- Sound alerts using `sox` for important notifications
|
||||
- Screen reader compatibility through proper dialog usage
|
||||
- Keyboard navigation support throughout interface
|
||||
- Voice testing integrated into setup process
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Critical (Required)
|
||||
- wine, curl, dialog, sox
|
||||
- Archive tools: 7z, cabextract, unzip, xz
|
||||
- winetricks (for Wine component management)
|
||||
|
||||
### Optional (Warnings if missing)
|
||||
- gawk (for game removal)
|
||||
- ocrdesktop (installer debugging)
|
||||
- qjoypad (gamepad support)
|
||||
- translate-shell, sqlite3, perl (translation features)
|
||||
- w3m (documentation viewing)
|
||||
- xclip, xdotool (X11 integration)
|
||||
|
||||
### Platform Support
|
||||
- x86_64 Linux (primary)
|
||||
- aarch64 with FEX-Emu (alternative Wine implementation)
|
||||
|
||||
## Testing
|
||||
- System requirements: `./audiogame-manager.sh -c`
|
||||
- Voice functionality: Use built-in voice test in `set-voice.sh`
|
||||
- Game installation: Test with simple games first before complex ones
|
||||
- Wine bottle integrity: Check `~/.local/wine32/system.reg` and `~/.local/wine64/system.reg` exist
|
||||
- Test with different Wine versions when modifying bottle creation
|
||||
- Verify speech synthesis functionality after TTS changes
|
||||
- Test game installation scripts in clean environments
|
||||
- Check both 32-bit and 64-bit compatibility where applicable
|
||||
|
||||
## Recent Refactor Notes (2025)
|
||||
|
||||
### Major Refactor Status
|
||||
The project has undergone a significant refactor to modularize functionality. **Status: Largely Complete**
|
||||
|
||||
### Key Fixes Applied
|
||||
1. **Function naming**: Fixed `process_launcher-flags()` → `process_launcher_flags()` in `audiogame-manager.sh:270`
|
||||
2. **Variable scope**: Added `export game` in main script so `.includes/bottle.sh` functions can access it
|
||||
3. **Installation logic**: Completed the `-I` option implementation for noninteractive game installation
|
||||
4. **Code deduplication**: Removed duplicate `check_news` and launcher logic from `update.sh`
|
||||
5. **Include sourcing**: Fixed all relative path sourcing (e.g., `source .includes/bottle.sh`) to use `${scriptDir}` to ensure desktop launchers and execution from any working directory works correctly
|
||||
|
||||
### Critical Variable Handling
|
||||
- **`$game` variable**: Must be exported when set (line 489 in main script) for bottle.sh functions to work
|
||||
- **`agmNoLaunch` variable**: Used to prevent main script execution when sourced by other scripts
|
||||
- **Bottle names**: Derived from game names, converted to lowercase with spaces replaced by hyphens
|
||||
|
||||
### Important Patterns
|
||||
- **Noninteractive installation**: The `-I` option sources the appropriate `.install/GameName.sh` script
|
||||
- **Variable scope**: Include files rely on exported variables from main script
|
||||
- **Wine bottle logic**: Game-specific wine versions are handled in `bottle.sh:get_bottle()`
|
||||
|
||||
### Common Issues to Watch For
|
||||
1. **Variable exports**: Ensure variables are exported when needed by include files
|
||||
2. **Function naming**: Use snake_case for functions, camelCase for variables
|
||||
3. **Path quoting**: Always quote paths that might contain spaces
|
||||
4. **Duplicate logic**: Check main script vs includes to avoid redundant code
|
||||
+73
-46
@@ -33,7 +33,7 @@ start_nvda2speechd() {
|
||||
if [[ "$nvda2speechdStarted" == "true" ]]; then
|
||||
return
|
||||
fi
|
||||
if ! ss -ltnp | rg 3457 | grep -q 'cthulhu'; then
|
||||
if ! ss -ltnp | grep 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}"
|
||||
@@ -285,11 +285,14 @@ check_wine32() {
|
||||
# Ensure wine64 bottle exists with all dependencies (wine32 no longer needed!)
|
||||
ensure_wine_bottles() {
|
||||
local wine64Bottle="$HOME/.local/wine64"
|
||||
local setupMarker="${wine64Bottle}/.agm-winespeak-setup-pending"
|
||||
|
||||
# Create wine64 bottle if missing - now includes SAPI support via WINETRICKS_FORCE=1
|
||||
if [[ ! -d "$wine64Bottle" ]] || [[ ! -f "$wine64Bottle/system.reg" ]]; then
|
||||
if [[ ! -d "$wine64Bottle" ]] || [[ ! -f "$wine64Bottle/system.reg" ]] || [[ -f "$setupMarker" ]]; then
|
||||
{
|
||||
echo "# Creating wine64 bottle for modern games..."
|
||||
mkdir -p "$wine64Bottle"
|
||||
touch "$setupMarker"
|
||||
|
||||
# Set up environment for wine64
|
||||
export WINEPREFIX="$wine64Bottle"
|
||||
@@ -329,22 +332,15 @@ ensure_wine_bottles() {
|
||||
# Restore win10 after speechsdk (speechsdk sets winxp)
|
||||
winetricks -q win10
|
||||
|
||||
# Initialize SAPI and set Microsoft Mike as default voice
|
||||
echo "# Setting Microsoft Mike as default voice..."
|
||||
mkdir -p "${WINEPREFIX}/drive_c/windows/temp"
|
||||
cat << "EOF" > "${WINEPREFIX}/drive_c/windows/temp/init_sapi.vbs"
|
||||
dim speechobject
|
||||
set speechobject=createobject("sapi.spvoice")
|
||||
speechobject.speak ""
|
||||
EOF
|
||||
wine cscript "c:\\windows\\temp\\init_sapi.vbs"
|
||||
|
||||
wine reg add "HKCU\\SOFTWARE\\Microsoft\\Speech\\Voices" /v "DefaultTokenId" /t REG_SZ /d "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens\\MSMike" /f
|
||||
wine reg add "HKCU\\SOFTWARE\\Microsoft\\Speech\\Voices" /v "DefaultTTSRate" /t REG_DWORD /d "7" /f
|
||||
echo "Set Microsoft Mike as default voice for wine64"
|
||||
# Install and verify the default 32-bit SAPI voice.
|
||||
echo "# Installing WineSpeak English (America)..."
|
||||
if ! install_winespeak; then
|
||||
echo "# WineSpeak installation or SAPI verification failed."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Setup nvda2speechd for accessibility if needed
|
||||
if ! ss -ltnp | rg 3457 | grep -q 'cthulhu'; then
|
||||
if ! ss -ltnp | grep 3457 | grep -q 'cthulhu'; then
|
||||
echo "# Setting up accessibility support..."
|
||||
download "${nvda2speechdBinary}"
|
||||
if [[ ! -f "${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd" ]]; then
|
||||
@@ -353,6 +349,7 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$setupMarker"
|
||||
echo "# Wine64 bottle creation complete."
|
||||
} > >(agm_progressbox "Wine Bottle Setup" "Creating unified wine64 bottle with SAPI support (this may take several minutes)...")
|
||||
fi
|
||||
@@ -469,23 +466,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 +544,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
|
||||
@@ -550,14 +567,14 @@ custom_launch_parameters() {
|
||||
fi
|
||||
start_nvda2speechd
|
||||
pushd "$(winepath "$winePath")" || exit 1
|
||||
wine "$wineExec"
|
||||
run_with_optional_gamemode wine "$wineExec"
|
||||
popd || exit 1
|
||||
customLaunchHandled="true"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${game[2]}" == "Dreamland" ]]; then
|
||||
start_nvda2speechd
|
||||
"$WINE" "${game[1]}" &> /dev/null
|
||||
run_with_optional_gamemode "$WINE" "${game[1]}" &> /dev/null
|
||||
customLaunchHandled="true"
|
||||
return 0
|
||||
fi
|
||||
@@ -617,20 +634,20 @@ custom_launch_parameters() {
|
||||
# sketchbook: DLL replacement now handled by update_nvda_dlls()
|
||||
if [[ "${game[2]}" == "Audiodisc" ]]; then
|
||||
start_nvda2speechd
|
||||
wine "$winePath\\$wineExec"
|
||||
run_with_optional_gamemode wine "$winePath\\$wineExec"
|
||||
customLaunchHandled="true"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${game[2]}" == "Audioquake" ]]; then
|
||||
start_nvda2speechd
|
||||
wine "$winePath\\$wineExec"
|
||||
run_with_optional_gamemode wine "$winePath\\$wineExec"
|
||||
customLaunchHandled="true"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${game[2]}" == "Screaming Strike 2" ]]; then
|
||||
start_nvda2speechd
|
||||
pushd "$(winepath "$winePath")" || exit 1
|
||||
wine "$wineExec"
|
||||
run_with_optional_gamemode wine "$wineExec"
|
||||
popd || exit 1
|
||||
customLaunchHandled="true"
|
||||
return 0
|
||||
@@ -638,7 +655,7 @@ custom_launch_parameters() {
|
||||
if [[ "${game[2]}" == "Warsim" ]]; then
|
||||
start_nvda2speechd
|
||||
pushd "$(winepath "${game[1]%\\*}")" || exit 1
|
||||
wine "${game[1]##*\\}"
|
||||
run_with_optional_gamemode wine "${game[1]##*\\}"
|
||||
popd || exit 1
|
||||
customLaunchHandled="true"
|
||||
return 0
|
||||
@@ -646,7 +663,7 @@ custom_launch_parameters() {
|
||||
if [[ "${game[2]}" == "Interceptor" ]]; then
|
||||
start_nvda2speechd
|
||||
pushd "$(winepath "$winePath")" || exit 1
|
||||
wine "$wineExec"
|
||||
run_with_optional_gamemode wine "$wineExec"
|
||||
popd || exit 1
|
||||
customLaunchHandled="true"
|
||||
return 0
|
||||
@@ -667,12 +684,11 @@ create_game_array() {
|
||||
# Game array 0 bottle, 1 path, 2 title, 3+ flags
|
||||
local selectedGame="$1"
|
||||
for i in "${lines[@]}" ; do
|
||||
# Only compare the launcher section
|
||||
local j="${selectedGame#*|}"
|
||||
local k="${i#*|}"
|
||||
k="${k%%|*}"
|
||||
if [[ "$j" == "$k" ]]; then
|
||||
IFS='|' read -ra game <<< "$i"
|
||||
local selectedLauncher="${selectedGame#*|}"
|
||||
local candidate=()
|
||||
IFS='|' read -ra candidate <<< "$i"
|
||||
if [[ "$selectedLauncher" == "${candidate[1]}" || "$selectedGame" == "${candidate[2]}" ]]; then
|
||||
game=("${candidate[@]}")
|
||||
break
|
||||
fi
|
||||
done
|
||||
@@ -790,14 +806,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 +835,6 @@ game_launcher() {
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
process_launcher_flags
|
||||
apply_executioners_rage_focus_workaround
|
||||
customLaunchHandled="false"
|
||||
custom_launch_parameters
|
||||
@@ -824,11 +846,11 @@ game_launcher() {
|
||||
# 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]##*\\}"
|
||||
run_with_optional_gamemode 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]##*\\}"
|
||||
run_with_optional_gamemode wine "${game[1]##*\\}"
|
||||
fi
|
||||
popd > /dev/null || exit 1
|
||||
restore_executioners_rage_focus_workaround
|
||||
@@ -901,6 +923,10 @@ 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/gamemode.sh
|
||||
source "${scriptDir}/.includes/gamemode.sh"
|
||||
# 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
|
||||
@@ -916,13 +942,14 @@ source "${scriptDir}/.includes/update.sh"
|
||||
export nvdaControllerClient32Dll="${ipfs[nvdaControllerClient32]}"
|
||||
export nvdaControllerClient64Dll="${ipfs[nvdaControllerClient64]}"
|
||||
export nvda2speechdBinary="${ipfs[nvda2speechd]}"
|
||||
export wineSpeakInstaller="${ipfs[winespeak]}"
|
||||
|
||||
# Check minimum requirements
|
||||
check_requirements || exit 1
|
||||
# Wine32 no longer needed - all games use wine64 with SAPI support via WINETRICKS_FORCE=1
|
||||
# check_wine32 # Disabled - wine32 eliminated 2025-12-06
|
||||
# Ensure wine bottles exist with dependencies
|
||||
ensure_wine_bottles
|
||||
ensure_wine_bottles || exit 1
|
||||
# Check for updates
|
||||
update
|
||||
# Get latest news if available
|
||||
|
||||
@@ -0,0 +1,525 @@
|
||||
# UMU Proton Backend Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add a reusable UMU/Proton backend and migrate Shadow Line to it with English localization and NVDA controller DLL replacement.
|
||||
|
||||
**Architecture:** Keep the existing Wine backend intact and add UMU as a new launcher backend value in `games.conf`. Put generic UMU behavior in a focused include file, keep Shadow Line-specific install work in `.install/Shadow Line.sh`, and route launch/removal/kill behavior through backend-aware branches.
|
||||
|
||||
**Tech Stack:** Bash, `umu-run`, Wine/Proton prefixes, existing AGM dialog/progress helpers, `shellcheck`.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Create: `.includes/proton.sh`
|
||||
- Owns UMU dependency checks, prefix selection, UMU launch environment, Windows path conversion, UMU installer/launch helpers, and UMU launcher registration.
|
||||
- Create: `tests/umu_backend_tests.sh`
|
||||
- Self-contained shell tests with temporary HOME/config/cache paths and stubbed external commands.
|
||||
- Modify: `audiogame-manager.sh`
|
||||
- Source `.includes/proton.sh`, skip Wine-only DLL scanning for UMU prefixes, launch UMU entries through `run_umu_game`, and handle UMU kill/remove paths safely.
|
||||
- Modify: `.includes/checkup.sh`
|
||||
- Report `umu-run` as required for Proton-backed games and include `umu-launcher` in package output.
|
||||
- Modify: `.install/Shadow Line.sh`
|
||||
- Use `install_proton_bottle`, run installer via UMU, apply registry settings, copy English language file, replace NVDA controller DLLs, register with `add_umu_launcher`, and show first-run English instructions.
|
||||
|
||||
## Task 1: Add UMU Backend Tests
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/umu_backend_tests.sh`
|
||||
|
||||
- [ ] **Step 1: Write failing shell tests**
|
||||
|
||||
Create `tests/umu_backend_tests.sh`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repoRoot="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
testRoot="$(mktemp -d)"
|
||||
trap 'rm -rf "$testRoot"' EXIT
|
||||
|
||||
export HOME="${testRoot}/home"
|
||||
export XDG_DATA_HOME="${HOME}/.local/share"
|
||||
export XDG_CONFIG_HOME="${HOME}/.config"
|
||||
export XDG_CACHE_HOME="${HOME}/.cache"
|
||||
export DISPLAY=""
|
||||
export cache="${XDG_CACHE_HOME}/audiogame-manager"
|
||||
export configFile="${XDG_CONFIG_HOME}/storm-games/audiogame-manager/games.conf"
|
||||
export scriptDir="$repoRoot"
|
||||
mkdir -p "$cache" "${configFile%/*}" "${testRoot}/bin"
|
||||
touch "$configFile"
|
||||
|
||||
cat > "${testRoot}/bin/umu-run" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s|%s|%s|%s\n' "$WINEPREFIX" "$GAMEID" "${STORE:-}" "$*" >> "$UMU_STUB_LOG"
|
||||
if [[ "${1:-}" == "" ]]; then
|
||||
mkdir -p "$WINEPREFIX/drive_c"
|
||||
fi
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/umu-run"
|
||||
|
||||
cat > "${testRoot}/bin/wine" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
if [[ "${1:-}" == "winepath" || "${1:-}" == "winepath.exe" ]]; then
|
||||
shift
|
||||
fi
|
||||
if [[ "${1:-}" == "-u" ]]; then
|
||||
input="$2"
|
||||
path="${input#c:\\}"
|
||||
path="${path//\\//}"
|
||||
printf '%s/drive_c/%s\n' "$WINEPREFIX" "$path"
|
||||
exit 0
|
||||
fi
|
||||
printf 'wine %s\n' "$*" >> "$WINE_STUB_LOG"
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/wine"
|
||||
|
||||
cat > "${testRoot}/bin/wineserver" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
printf 'wineserver %s\n' "$*" >> "$WINE_STUB_LOG"
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/wineserver"
|
||||
|
||||
export PATH="${testRoot}/bin:$PATH"
|
||||
export UMU_STUB_LOG="${testRoot}/umu.log"
|
||||
export WINE_STUB_LOG="${testRoot}/wine.log"
|
||||
|
||||
# shellcheck source=.includes/proton.sh
|
||||
source "${repoRoot}/.includes/proton.sh"
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local message="$3"
|
||||
if [[ "$expected" != "$actual" ]]; then
|
||||
printf 'FAIL: %s\nexpected: %s\nactual: %s\n' "$message" "$expected" "$actual" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
local message="$3"
|
||||
if ! grep -Fq "$pattern" "$file"; then
|
||||
printf 'FAIL: %s\nmissing pattern: %s\nfile contents:\n' "$message" "$pattern" >&2
|
||||
cat "$file" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_get_umu_bottle_sets_environment() {
|
||||
get_umu_bottle "shadow-line"
|
||||
assert_equals "${XDG_DATA_HOME}/audiogame-manager/protonBottles/shadow-line" "$WINEPREFIX" "WINEPREFIX points at AGM proton bottle"
|
||||
assert_equals "shadow-line" "$GAMEID" "GAMEID is exported"
|
||||
assert_equals "none" "$STORE" "STORE defaults to none"
|
||||
assert_equals ":0" "$DISPLAY" "DISPLAY defaults to :0"
|
||||
}
|
||||
|
||||
test_add_umu_launcher_records_backend_and_game_id() {
|
||||
get_umu_bottle "shadow-line"
|
||||
add_umu_launcher "shadow-line" 'c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
assert_file_contains "$configFile" 'umu|c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe|Shadow Line|export umuGameId=shadow-line' "UMU launcher entry is recorded"
|
||||
}
|
||||
|
||||
test_run_umu_game_uses_converted_path() {
|
||||
get_umu_bottle "shadow-line"
|
||||
mkdir -p "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
|
||||
touch "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe"
|
||||
run_umu_game 'c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
assert_file_contains "$UMU_STUB_LOG" "${WINEPREFIX}|shadow-line|none|${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe" "UMU launches converted exe path"
|
||||
}
|
||||
|
||||
test_get_umu_bottle_sets_environment
|
||||
test_add_umu_launcher_records_backend_and_game_id
|
||||
test_run_umu_game_uses_converted_path
|
||||
printf 'UMU backend tests passed\n'
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and verify they fail because `.includes/proton.sh` is missing**
|
||||
|
||||
Run: `bash tests/umu_backend_tests.sh`
|
||||
|
||||
Expected: FAIL with a message that `.includes/proton.sh` cannot be sourced.
|
||||
|
||||
## Task 2: Implement Generic UMU Helpers
|
||||
|
||||
**Files:**
|
||||
- Create: `.includes/proton.sh`
|
||||
- Test: `tests/umu_backend_tests.sh`
|
||||
|
||||
- [ ] **Step 1: Implement `.includes/proton.sh`**
|
||||
|
||||
Create `.includes/proton.sh`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2034,SC2154 # sourced by audiogame-manager and installers with shared globals
|
||||
|
||||
require_umu() {
|
||||
if command -v umu-run &> /dev/null; then
|
||||
return 0
|
||||
fi
|
||||
local message="This game requires umu-launcher. Please install umu-launcher and try again."
|
||||
if declare -F agm_msgbox &> /dev/null; then
|
||||
agm_msgbox "Audio Game Manager" "Audio Game Manager" "$message"
|
||||
else
|
||||
echo "$message" >&2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
get_umu_bottle() {
|
||||
local gameId="$1"
|
||||
if [[ -z "$gameId" ]]; then
|
||||
echo "get_umu_bottle requires a game id." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
export umuGameId="$gameId"
|
||||
export WINEPREFIX="${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/protonBottles/${gameId}"
|
||||
export GAMEID="$gameId"
|
||||
export STORE="${umuStore:-none}"
|
||||
export DISPLAY="${DISPLAY:-:0}"
|
||||
export UMU_RUNTIME_UPDATE="${UMU_RUNTIME_UPDATE:-0}"
|
||||
mkdir -p "$WINEPREFIX"
|
||||
}
|
||||
|
||||
install_proton_bottle() {
|
||||
local gameId="$1"
|
||||
shift || true
|
||||
require_umu || return 1
|
||||
get_umu_bottle "$gameId" || return 1
|
||||
|
||||
if [[ ! -f "${WINEPREFIX}/system.reg" ]]; then
|
||||
umu-run ""
|
||||
fi
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
umu-run winetricks "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
umu_windows_path_to_unix() {
|
||||
local windowsPath="$1"
|
||||
wine winepath -u "$windowsPath"
|
||||
}
|
||||
|
||||
run_umu_game() {
|
||||
local windowsPath="$1"
|
||||
local exePath=""
|
||||
require_umu || return 1
|
||||
if [[ -z "${umuGameId:-}" ]]; then
|
||||
echo "UMU game id is not set for ${game[2]:-selected game}." >&2
|
||||
return 1
|
||||
fi
|
||||
get_umu_bottle "$umuGameId" || return 1
|
||||
exePath="$(umu_windows_path_to_unix "$windowsPath")"
|
||||
if [[ ! -f "$exePath" ]]; then
|
||||
echo "UMU executable not found: $exePath" >&2
|
||||
return 1
|
||||
fi
|
||||
pushd "${exePath%/*}" > /dev/null || return 1
|
||||
umu-run "$exePath"
|
||||
popd > /dev/null || return 1
|
||||
}
|
||||
|
||||
add_umu_launcher() {
|
||||
local gameId="$1"
|
||||
local windowsPath="$2"
|
||||
shift 2
|
||||
local launchSettings="umu|${windowsPath}|${game}|export umuGameId=${gameId}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
launchSettings+="|$1"
|
||||
shift
|
||||
done
|
||||
|
||||
if ! grep -F -q -x "$launchSettings" "$configFile" 2> /dev/null; then
|
||||
echo "$launchSettings" >> "$configFile"
|
||||
sort -t '|' -k3,3f -o "$configFile" "$configFile"
|
||||
fi
|
||||
}
|
||||
|
||||
set_umu_reg_value() {
|
||||
local key="$1"
|
||||
local valueName="$2"
|
||||
local valueData="$3"
|
||||
wine reg add "$key" /v "$valueName" /t REG_SZ /d "$valueData" /f
|
||||
}
|
||||
|
||||
set_umu_app_winver() {
|
||||
local exeName="$1"
|
||||
local winVersion="$2"
|
||||
set_umu_reg_value "HKCU\\Software\\Wine\\AppDefaults\\${exeName}" "Version" "$winVersion"
|
||||
}
|
||||
|
||||
stop_umu_bottle() {
|
||||
if command -v wineserver &> /dev/null; then
|
||||
wineserver -k 2> /dev/null || true
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run UMU backend tests and verify they pass**
|
||||
|
||||
Run: `bash tests/umu_backend_tests.sh`
|
||||
|
||||
Expected: PASS with `UMU backend tests passed`.
|
||||
|
||||
- [ ] **Step 3: Run shellcheck on new files**
|
||||
|
||||
Run: `shellcheck .includes/proton.sh tests/umu_backend_tests.sh`
|
||||
|
||||
Expected: no output and exit code 0.
|
||||
|
||||
## Task 3: Wire UMU Backend Into AGM Launch, Kill, and Removal
|
||||
|
||||
**Files:**
|
||||
- Modify: `audiogame-manager.sh`
|
||||
- Test: `tests/umu_backend_tests.sh`
|
||||
|
||||
- [ ] **Step 1: Source proton helpers**
|
||||
|
||||
In `audiogame-manager.sh`, after sourcing `.includes/bottle.sh`, add:
|
||||
|
||||
```bash
|
||||
# shellcheck source=.includes/proton.sh
|
||||
source "${scriptDir}/.includes/proton.sh"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Make removal backend-aware**
|
||||
|
||||
In `remove_game`, after `create_game_array "$selectedGame"` and before Wine-only setup, branch on `game[0]`:
|
||||
|
||||
```bash
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
process_launcher_flags
|
||||
get_umu_bottle "$umuGameId"
|
||||
else
|
||||
source "${scriptDir}/.includes/bottle.sh"
|
||||
get_bottle "${game[0]}"
|
||||
fi
|
||||
```
|
||||
|
||||
For directory conversion, replace the Wine-only `winepath` call with:
|
||||
|
||||
```bash
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
gameDir="$(umu_windows_path_to_unix "$winePath")"
|
||||
else
|
||||
gameDir="$(winepath "$winePath")"
|
||||
fi
|
||||
```
|
||||
|
||||
For stopping processes, use:
|
||||
|
||||
```bash
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
stop_umu_bottle
|
||||
else
|
||||
wineserver -k
|
||||
fi
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Make kill backend-aware**
|
||||
|
||||
In `kill_game`, after `create_game_array` or parsing the selected line, ensure UMU entries process launcher flags and call `stop_umu_bottle`:
|
||||
|
||||
```bash
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
process_launcher_flags
|
||||
get_umu_bottle "$umuGameId"
|
||||
stop_umu_bottle
|
||||
else
|
||||
get_bottle "${game%|*}"
|
||||
wineserver -k
|
||||
fi
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Launch UMU entries through `run_umu_game`**
|
||||
|
||||
In `game_launcher`, after `process_launcher_flags` and before Wine-only qjoypad/path work is used, add:
|
||||
|
||||
```bash
|
||||
if [[ "${game[0]}" == "umu" ]]; then
|
||||
echo "launching with umu"
|
||||
start_nvda2speechd
|
||||
run_umu_game "${game[1]}"
|
||||
exit 0
|
||||
fi
|
||||
```
|
||||
|
||||
Keep Wine launch behavior unchanged for non-UMU entries.
|
||||
|
||||
- [ ] **Step 5: Run syntax checks**
|
||||
|
||||
Run: `bash -n audiogame-manager.sh`
|
||||
|
||||
Expected: no output and exit code 0.
|
||||
|
||||
- [ ] **Step 6: Run shellcheck on touched shell files**
|
||||
|
||||
Run: `shellcheck audiogame-manager.sh .includes/proton.sh tests/umu_backend_tests.sh`
|
||||
|
||||
Expected: no new actionable errors. Existing intentional warnings may be suppressed locally only if they are not real bugs.
|
||||
|
||||
## Task 4: Update Dependency Reporting
|
||||
|
||||
**Files:**
|
||||
- Modify: `.includes/checkup.sh`
|
||||
|
||||
- [ ] **Step 1: Add UMU check to `.includes/checkup.sh`**
|
||||
|
||||
After the Wine check, add:
|
||||
|
||||
```bash
|
||||
if command -v umu-run &> /dev/null; then
|
||||
[[ $# -eq 0 ]] && echo "umu-launcher is installed."
|
||||
else
|
||||
errorList+=("Warning: umu-launcher is not installed. Games that require Proton/UMU will not install or launch.")
|
||||
fi
|
||||
packageList+=("umu-launcher")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run syntax and shellcheck**
|
||||
|
||||
Run: `bash -n .includes/checkup.sh`
|
||||
|
||||
Expected: no output and exit code 0.
|
||||
|
||||
Run: `shellcheck .includes/checkup.sh`
|
||||
|
||||
Expected: no output and exit code 0, or only pre-existing intentional warnings.
|
||||
|
||||
## Task 5: Migrate Shadow Line Installer to UMU
|
||||
|
||||
**Files:**
|
||||
- Modify: `.install/Shadow Line.sh`
|
||||
|
||||
- [ ] **Step 1: Replace installer body**
|
||||
|
||||
Update `.install/Shadow Line.sh` to:
|
||||
|
||||
```bash
|
||||
# shellcheck shell=bash disable=SC2154 # cache, game, and helper functions are set by audiogame-manager
|
||||
download "https://www.mm-galabo.com/sr/Download_files_srfv/shadowrine_fullvoice3.171.exe" \
|
||||
"https://raw.githubusercontent.com/LordLuceus/sr-english-localization/master/language_en.dat" \
|
||||
"${nvdaControllerClient32Dll}" \
|
||||
"${nvdaControllerClient64Dll}"
|
||||
|
||||
export game="Shadow Line"
|
||||
shadowLineGameId="shadow-line"
|
||||
shadowLinePath='c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
shadowLineInstallDir="${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
|
||||
|
||||
install_proton_bottle "$shadowLineGameId" fakejapanese
|
||||
shadowLineInstallDir="${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
|
||||
|
||||
set_umu_reg_value "HKCU\\Software\\Wine\\DllOverrides" "bcryptprimitives" "native,builtin"
|
||||
set_umu_app_winver "play_sr.exe" "win8"
|
||||
|
||||
{
|
||||
echo "# Installing Shadow Line..."
|
||||
timeout 300 umu-run "${cache}/shadowrine_fullvoice3.171.exe" /sp- /VERYSILENT /SUPPRESSMSGBOXES 2>&1 || true
|
||||
echo "# Installation complete"
|
||||
} | agm_progressbox "Installing Game" "Installing Shadow Line with UMU/Proton (this may take a few minutes)..."
|
||||
|
||||
stop_umu_bottle
|
||||
|
||||
if [[ ! -f "${shadowLineInstallDir}/play_sr.exe" ]]; then
|
||||
agm_msgbox "Shadow Line" "Shadow Line" "Shadow Line did not install to the expected location: ${shadowLineInstallDir}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -m 0644 "${cache}/language_en.dat" "${shadowLineInstallDir}/SystemData/language_en.dat"
|
||||
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient32.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient64.dll' -exec cp -v "${cache}/nvdaControllerClient64.dll" "{}" \;
|
||||
find "$shadowLineInstallDir" -type f -iname 'nvdaControllerClient.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
|
||||
|
||||
add_umu_launcher "$shadowLineGameId" "$shadowLinePath"
|
||||
alert "Shadow Line" "Shadow Line" "Please set the language to English when the game opens.\nGo to options and press enter.\nPress down arrow 5 times and press enter.\nPress down arrow 1 time and press enter.\nPress up arrow 2 times and press enter.\nIf everything worked as expected you should be back on the game menu and speech should work."
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run syntax check**
|
||||
|
||||
Run: `bash -n ".install/Shadow Line.sh"`
|
||||
|
||||
Expected: no output and exit code 0.
|
||||
|
||||
- [ ] **Step 3: Run shellcheck on Shadow Line installer**
|
||||
|
||||
Run: `shellcheck ".install/Shadow Line.sh"`
|
||||
|
||||
Expected: no output and exit code 0, or only intentional sourced-global warnings suppressed by the file header.
|
||||
|
||||
## Task 6: End-to-End Verification
|
||||
|
||||
**Files:**
|
||||
- Modify as needed based on verification findings.
|
||||
|
||||
- [ ] **Step 1: Run focused unit tests**
|
||||
|
||||
Run: `bash tests/umu_backend_tests.sh`
|
||||
|
||||
Expected: PASS with `UMU backend tests passed`.
|
||||
|
||||
- [ ] **Step 2: Run syntax checks for all touched scripts**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
bash -n audiogame-manager.sh
|
||||
bash -n .includes/proton.sh
|
||||
bash -n .includes/checkup.sh
|
||||
bash -n ".install/Shadow Line.sh"
|
||||
bash -n tests/umu_backend_tests.sh
|
||||
```
|
||||
|
||||
Expected: all commands exit 0.
|
||||
|
||||
- [ ] **Step 3: Run shellcheck for all touched Bash files**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
shellcheck audiogame-manager.sh .includes/proton.sh .includes/checkup.sh ".install/Shadow Line.sh" tests/umu_backend_tests.sh
|
||||
```
|
||||
|
||||
Expected: no actionable warnings.
|
||||
|
||||
- [ ] **Step 4: Optional live install verification**
|
||||
|
||||
If the user approves running the live installer, run:
|
||||
|
||||
```bash
|
||||
DISPLAY=:0 ./audiogame-manager.sh -I "Shadow Line"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- Shadow Line installs into `${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/protonBottles/shadow-line`.
|
||||
- `games.conf` contains `umu|...|Shadow Line|export umuGameId=shadow-line`.
|
||||
- `play_sr.exe` exists under the Shadow Line Proton prefix.
|
||||
- `SystemData/language_en.dat` exists.
|
||||
- `user.reg` contains `bcryptprimitives` and `AppDefaults\\play_sr.exe` with `Version=win8`.
|
||||
|
||||
- [ ] **Step 5: Commit implementation**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git add .includes/proton.sh .includes/checkup.sh ".install/Shadow Line.sh" audiogame-manager.sh tests/umu_backend_tests.sh docs/superpowers/plans/2026-05-05-umu-proton-backend.md
|
||||
git commit -m "Add UMU Proton backend for Shadow Line"
|
||||
```
|
||||
|
||||
Expected: commit succeeds and unrelated pre-existing dirty files remain unstaged.
|
||||
|
||||
## Self-Review
|
||||
|
||||
- Spec coverage: generic UMU helpers, Shadow Line migration, dependency checks, error handling, and verification are each covered by tasks.
|
||||
- Placeholder scan: no TODO/TBD placeholders remain.
|
||||
- Type consistency: helper names are consistent across tests, implementation, installer, and launcher tasks.
|
||||
@@ -0,0 +1,64 @@
|
||||
# UMU Proton Backend Design
|
||||
|
||||
## Goal
|
||||
|
||||
Add a general UMU/Proton backend to audiogame-manager so selected games can install and launch through `umu-run` with AGM-managed Proton prefixes. Shadow Line is the first supported game and should be installable, launchable, translated to English, and speech-capable without changing the existing Wine backend for other games.
|
||||
|
||||
## Scope
|
||||
|
||||
This change introduces generic UMU helpers and wires Shadow Line to use them. It does not migrate existing Wine games, add proton-voices, or reimplement `umu-launcher`. AGM depends on `umu-run` for Proton/runtime management because UMU handles Proton selection, Steam Runtime setup, prefix creation, protonfixes, and Proton winetricks routing.
|
||||
|
||||
## Architecture
|
||||
|
||||
Launcher entries gain a new backend value in the first `games.conf` field:
|
||||
|
||||
```text
|
||||
umu|c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe|Shadow Line|...
|
||||
```
|
||||
|
||||
The existing Wine values (`win64`, `win32`) continue to behave as they do now. When the backend is `umu`, launch setup uses a dedicated Proton prefix under:
|
||||
|
||||
```text
|
||||
${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/protonBottles/<game-id>
|
||||
```
|
||||
|
||||
The game id is supplied by the installer and stored in launcher flags, for example `export umuGameId=shadow-line`. Helpers will centralize backend behavior:
|
||||
|
||||
- `install_proton_bottle <game-id> [winetricks verbs...]`: create and select a UMU prefix, initialize it with `umu-run ""`, and install optional Proton winetricks verbs with `umu-run winetricks`.
|
||||
- `get_umu_bottle <game-id>`: export `WINEPREFIX`, `GAMEID`, `STORE`, and `DISPLAY`.
|
||||
- `run_umu_game <windows-path>`: convert the Windows path to a Unix executable path in the UMU prefix and run it through `umu-run`.
|
||||
- `add_umu_launcher <game-id> <windows-path> [flags...]`: append a `umu|...` launcher line.
|
||||
|
||||
## Shadow Line
|
||||
|
||||
Shadow Line uses the generic UMU backend with `umuGameId=shadow-line`. Its installer will:
|
||||
|
||||
1. Require `umu-run`.
|
||||
2. Download `shadowrine_fullvoice3.171.exe` and `language_en.dat`.
|
||||
3. Create/select the Shadow Line UMU prefix.
|
||||
4. Apply `bcryptprimitives=native,builtin`.
|
||||
5. Apply per-app `win8` for `play_sr.exe`.
|
||||
6. Run the installer silently with UMU.
|
||||
7. Stop any leftover Wine/Proton processes for the prefix.
|
||||
8. Copy `language_en.dat` into `SystemData`.
|
||||
9. Copy AGM's `nvdaControllerClient32.dll` and/or `nvdaControllerClient64.dll` over any matching game DLLs found in the Shadow Line install.
|
||||
10. Register the UMU launcher and show the first-run instructions for switching the game language to English.
|
||||
|
||||
Clipboard translation will not be used for Shadow Line in this implementation.
|
||||
|
||||
## Dependency Checks
|
||||
|
||||
`check_requirements` remains focused on core AGM dependencies, but UMU installers call a targeted `require_umu` helper so the error is clear at install time. The `-c` checkup report will also list `umu-run`: missing UMU is reported as required for Proton-backed games, not as a blocker for every Wine game.
|
||||
|
||||
## Error Handling
|
||||
|
||||
UMU helper functions fail fast with clear messages when `umu-run` is missing, prefix initialization fails, the installer does not produce the expected executable, or the translation file cannot be copied. Shadow Line should not be added to `games.conf` unless the expected executable exists.
|
||||
|
||||
## Verification
|
||||
|
||||
Verification will include:
|
||||
|
||||
- `bash -n` on changed shell scripts.
|
||||
- `shellcheck` on changed Bash files.
|
||||
- A focused install-path dry run where possible without launching the interactive game.
|
||||
- Confirmation that Shadow Line creates a `umu|...|Shadow Line` launcher entry and that the UMU prefix contains `play_sr.exe`, `language_en.dat`, registry settings, and replacement NVDA DLLs when matching DLLs exist.
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC1091,SC2034,SC2154
|
||||
|
||||
export game="Magic: The Gathering Arena"
|
||||
export WINEARCH=win64
|
||||
export winVer="win10"
|
||||
export DIALOGOPTS='--no-lines --visit-items'
|
||||
export ipfsGateway="${ipfsGateway:-https://ipfs.stormux.org}"
|
||||
|
||||
cache="${XDG_CACHE_HOME:-$HOME/.cache}/audiogame-manager"
|
||||
winetricksPath="${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager"
|
||||
mkdir -p "$cache" "$winetricksPath"
|
||||
|
||||
if [[ -z "$DISPLAY" ]]; then
|
||||
dialogType="dialog"
|
||||
export DISPLAY=":0"
|
||||
elif command -v yad &> /dev/null; then
|
||||
dialogType="yad"
|
||||
else
|
||||
dialogType="dialog"
|
||||
fi
|
||||
|
||||
source "${0%/*}/../.includes/dialog-interface.sh"
|
||||
source "${0%/*}/../.includes/functions.sh"
|
||||
source "${0%/*}/../.includes/bottle.sh"
|
||||
|
||||
export nvdaControllerClient64Dll="${ipfs[nvdaControllerClient64]}"
|
||||
|
||||
mtgaVersionUrl="https://mtgarena.downloads.wizards.com/Live/Windows32/version"
|
||||
accessibleArenaDllUrl="https://github.com/JeanStiletto/AccessibleArena/releases/latest/download/AccessibleArena.dll"
|
||||
melonLoaderZipUrl="https://github.com/LavaGang/MelonLoader/releases/latest/download/MelonLoader.x64.zip"
|
||||
tolkDllUrl="https://stormgames.wolfe.casa/downloads/Tolk.dll"
|
||||
|
||||
get_mtga_installer_url() {
|
||||
local versionJson=""
|
||||
local installerUrl=""
|
||||
|
||||
if ! versionJson="$(curl -fsSL "$mtgaVersionUrl")"; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not fetch the current MTG Arena installer URL."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
installerUrl="$(sed -n 's/.*"CurrentInstallerURL"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' <<< "$versionJson" | head -n1)"
|
||||
if [[ -z "$installerUrl" ]]; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not parse the current MTG Arena installer URL."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$installerUrl"
|
||||
}
|
||||
|
||||
download_mtga_installer() {
|
||||
local installerUrl="$1"
|
||||
local installerFile="${installerUrl##*/}"
|
||||
installerFile="${installerFile%%\?*}"
|
||||
[[ -n "$installerFile" ]] || installerFile="MTGAInstaller.msi"
|
||||
|
||||
rm -f "${cache}/${installerFile}" 2> /dev/null
|
||||
if ! curl -L4 -C - --retry 10 --output "${cache}/${installerFile}" "$installerUrl" 2>&1 | agm_progressbox "Magic: The Gathering Arena Update" "Downloading MTG Arena update..."; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "Could not download the MTG Arena update."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mtgaInstallerPath="${cache}/${installerFile}"
|
||||
}
|
||||
|
||||
configure_accessible_arena_loader() {
|
||||
local mtgaRoot="$1"
|
||||
local userDataPath="${mtgaRoot}/UserData"
|
||||
local loaderConfig="${userDataPath}/Loader.cfg"
|
||||
|
||||
mkdir -p "$userDataPath"
|
||||
if [[ -f "$loaderConfig" ]]; then
|
||||
if grep -Fq "hide_console = false" "$loaderConfig"; then
|
||||
sed -i 's/hide_console = false/hide_console = true/g' "$loaderConfig"
|
||||
elif ! grep -Fq "hide_console" "$loaderConfig"; then
|
||||
printf '\n[console]\nhide_console = true\n' >> "$loaderConfig"
|
||||
fi
|
||||
else
|
||||
printf '[console]\nhide_console = true\n' > "$loaderConfig"
|
||||
fi
|
||||
}
|
||||
|
||||
install_accessible_arena_support() {
|
||||
local mtgaRoot="$1"
|
||||
|
||||
download "$accessibleArenaDllUrl" "$melonLoaderZipUrl" "$tolkDllUrl" "$nvdaControllerClient64Dll"
|
||||
|
||||
install_with_progress unzip "Reinstalling MelonLoader..." -d "$mtgaRoot" "${cache}/MelonLoader.x64.zip"
|
||||
mkdir -p "${mtgaRoot}/Mods"
|
||||
install_with_progress cp "Installing Accessible Arena..." "${cache}/AccessibleArena.dll" "${mtgaRoot}/Mods/AccessibleArena.dll"
|
||||
install_with_progress cp "Installing screen reader support DLLs..." "${cache}/Tolk.dll" "${cache}/nvdaControllerClient64.dll" "$mtgaRoot"
|
||||
configure_accessible_arena_loader "$mtgaRoot"
|
||||
}
|
||||
|
||||
check_requirements || exit 1
|
||||
install_wine_bottle dxvk
|
||||
winetricks -q $winVer
|
||||
|
||||
mtgaRoot="${WINEPREFIX}/drive_c/Program Files (x86)/Wizards of the Coast/MTGA"
|
||||
mtgaInstallerUrl="$(get_mtga_installer_url)"
|
||||
download_mtga_installer "$mtgaInstallerUrl"
|
||||
|
||||
wine msiexec /i "$mtgaInstallerPath" /q
|
||||
|
||||
if [[ ! -f "${mtgaRoot}/MTGA.exe" ]]; then
|
||||
alert "Magic: The Gathering Arena" "Magic: The Gathering Arena" "MTG Arena was not found at the expected location: ${mtgaRoot}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install_accessible_arena_support "$mtgaRoot"
|
||||
agm_msgbox "Magic: The Gathering Arena Update" "Magic: The Gathering Arena Update" "Magic: The Gathering Arena and Accessible Arena have been updated."
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Updater script for Swamp 2.
|
||||
# For whatever reason, the updates are partially applied and I'm too lazy to figure out why Wine is barfing, so we'll just do it manually.
|
||||
cache="${XDG_CACHE_HOME:-$HOME/.cache}/audiogame-manager"
|
||||
. ../.includes/dialog-interface.sh
|
||||
. ../.includes/functions.sh
|
||||
swamphouse=~/.local/wine64/drive_c/Program\ Files/swamp2
|
||||
myver=$(cat "$swamphouse/myversion.txt")
|
||||
newver=$(curl -s https://kaldobsky.com/audiogames/swamp2version.txt)
|
||||
if [[ $newver -gt $myver ]]; then
|
||||
agm_msgbox "Our version: ${myver}, with the newer version being ${newver}"
|
||||
download "https://kaldobsky.com/audiogames/swamp2_update.zip"
|
||||
install_with_progress unzip "Extracting game files..." -d "${swamphouse}" "${cache}/swamp2_update.zip"
|
||||
rm "${cache}/swamp2_update.zip"
|
||||
fi
|
||||
@@ -7,23 +7,30 @@
|
||||
# set -Eeuo pipefail # https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/#:~:text=set%20%2Du,is%20often%20highly%20desirable%20behavior.
|
||||
shopt -s expand_aliases
|
||||
|
||||
# Debug logging
|
||||
#DEBUG_LOG="${XDG_CACHE_HOME:-$HOME/.cache}/audiogame-manager/translator-debug.log"
|
||||
#exec 2>>"$DEBUG_LOG"
|
||||
#echo "=== Translator started at $(date) ===" >&2
|
||||
#echo "Args: $@" >&2
|
||||
#set -x
|
||||
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "Usage: $0 \"application name\" \"file name\"."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
pythonScript="${scriptDir}/translate_clipboard.py"
|
||||
if [[ -z "${AGM_TRANSLATOR_FORCE_SHELL:-}" ]] && command -v python3 &> /dev/null && [[ -f "$pythonScript" ]]; then
|
||||
exec python3 "$pythonScript" "$@"
|
||||
fi
|
||||
|
||||
is_app_running() {
|
||||
local appPattern="$1"
|
||||
local selfPid="$$"
|
||||
|
||||
pgrep -af -u "$USER" "$appPattern" \
|
||||
| awk -v selfPid="$selfPid" '$1 != selfPid && $0 !~ /clipboard_translator.sh/ { found=1 } END { exit !found }'
|
||||
}
|
||||
|
||||
# Wait for the application to start
|
||||
while ! pgrep -f -u "$USER" "$1" &> /dev/null ; do
|
||||
while ! is_app_running "$1"; do
|
||||
sleep 0.05
|
||||
done
|
||||
|
||||
|
||||
fileName="${2,,}"
|
||||
fileName="${fileName//[[:space:]]/-}.sqlite"
|
||||
translationFile="${XDG_CACHE_HOME:-$HOME/.cache}/audiogame-manager/${fileName}"
|
||||
@@ -44,26 +51,8 @@ CREATE INDEX translations_text_idx ON translations (text);
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Define a function to safely query the database
|
||||
query_database() {
|
||||
local dbFile="$1"
|
||||
local sqlQuery="$2"
|
||||
sqlite3 -line "$dbFile" "$sqlQuery"
|
||||
}
|
||||
|
||||
# Define a function to safely insert into the database
|
||||
insert_database() {
|
||||
local dbFile="$1"
|
||||
local text="$2"
|
||||
local translation="$3"
|
||||
|
||||
# Use sqlite3 .import feature which is more robust for special characters
|
||||
echo "$text|$translation" | sqlite3 -separator "|" "$dbFile" ".import /dev/stdin temp_import"
|
||||
sqlite3 "$dbFile" "INSERT OR IGNORE INTO translations SELECT * FROM temp_import; DROP TABLE IF EXISTS temp_import;"
|
||||
}
|
||||
|
||||
# Read so long as the application is running
|
||||
while pgrep -f -u "$USER" "$1" &> /dev/null ; do
|
||||
while is_app_running "$1"; do
|
||||
sleep 0.05
|
||||
text="$(xclip -d "${DISPLAY:-:0}" -selection clipboard -o 2> /dev/null)"
|
||||
if [[ -f ~/.agmsilent ]]; then
|
||||
@@ -80,46 +69,50 @@ while pgrep -f -u "$USER" "$1" &> /dev/null ; do
|
||||
|
||||
# https://en.wikipedia.org/wiki/Unicode_equivalence#Combining_and_precomposed_characters
|
||||
# https://www.effectiveperlprogramming.com/2011/09/normalize-your-perl-source/
|
||||
alias nfc="perl -MUnicode::Normalize -CS -ne 'print NFC(\$_)'" # composed characters
|
||||
nfc() {
|
||||
perl -MUnicode::Normalize -CS -ne 'print NFC($_)'
|
||||
}
|
||||
|
||||
# Normalize different unicode space characters to the same space
|
||||
# https://stackoverflow.com/a/43640405
|
||||
alias normalizeSpaces="perl -CSDA -plE 's/[^\\S\\t]/ /g'"
|
||||
alias normalizeUnicode="normalizeSpaces | nfc"
|
||||
normalize_spaces() {
|
||||
perl -CSDA -plE 's/[^\S\t]/ /g'
|
||||
}
|
||||
# Remove zero-width spaces (U+200B) that some games insert between characters
|
||||
strip_zwsp() {
|
||||
perl -CSDA -plE 's/\x{200B}//g'
|
||||
}
|
||||
normalize_unicode() {
|
||||
strip_zwsp | normalize_spaces | nfc
|
||||
}
|
||||
|
||||
# Normalize text
|
||||
normalizedText="$(echo "$text" | normalizeUnicode)"
|
||||
|
||||
normalizedText="$(echo "$text" | normalize_unicode)"
|
||||
|
||||
# Create a temporary database for import
|
||||
sqlite3 "$translationFile" "CREATE TABLE IF NOT EXISTS temp_import(text TEXT, translation TEXT);"
|
||||
|
||||
|
||||
# Check if we already have a translation
|
||||
translated=$(sqlite3 "$translationFile" "SELECT translation FROM translations WHERE text = '$normalizedText' LIMIT 1;" 2>/dev/null)
|
||||
echo "DEBUG: Database lookup result: '$translated'" >&2
|
||||
|
||||
if [[ -z "$translated" ]]; then
|
||||
echo "DEBUG: No cached translation, calling trans command" >&2
|
||||
# Get translation from the trans utility
|
||||
translated="$(trans -no-autocorrect -no-warn -brief "$normalizedText" | head -1 | sed 's/\s*$//' | normalizeUnicode)"
|
||||
echo "DEBUG: trans returned: '$translated'" >&2
|
||||
translated="$(trans -no-autocorrect -no-warn -brief "$normalizedText" | head -1 | sed 's/\s*$//' | normalize_unicode)"
|
||||
|
||||
if [[ -n "$translated" ]]; then
|
||||
# Insert using echo piping to avoid escaping issues
|
||||
echo "$normalizedText|$translated" | sqlite3 -separator "|" "$translationFile" ".import /dev/stdin temp_import"
|
||||
sqlite3 "$translationFile" "INSERT OR IGNORE INTO translations SELECT * FROM temp_import; DELETE FROM temp_import;"
|
||||
echo "DEBUG: Saved to database" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we got a translation, speak it
|
||||
if [[ -n "$translated" ]]; then
|
||||
echo "DEBUG: Speaking translation: '$translated'" >&2
|
||||
spd-say -- "$translated"
|
||||
else
|
||||
echo "DEBUG: No translation available, speaking original: '$text'" >&2
|
||||
spd-say -- "$text"
|
||||
spd-say -- "$normalizedText"
|
||||
fi
|
||||
|
||||
|
||||
# Clear clipboard
|
||||
echo "" | xclip -d "${DISPLAY:-:0}" -selection clipboard 2> /dev/null
|
||||
done
|
||||
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
export WINEPREFIX="${WINEPREFIX:-${HOME}/.local/wine64}"
|
||||
export WINEDEBUG="${WINEDEBUG:--all}"
|
||||
|
||||
installerUrl="https://stormgames.wolfe.casa/downloads/winespeak.exe"
|
||||
installerName="winespeak.exe"
|
||||
installerSha256="187d4db69f3af7c1bca1c100a04489b76732048be7c38449781da360ed7b2d66"
|
||||
cacheDir="${XDG_CACHE_HOME:-${HOME}/.cache}/audiogame-manager"
|
||||
installerPath="${cacheDir}/${installerName}"
|
||||
partialPath="${installerPath}.part"
|
||||
wrapperPath="${WINEPREFIX}/drive_c/Program Files (x86)/espeak-ng-sapi/EspeakSAPI.dll"
|
||||
wrapperSha256="8009750dad82ca6665871814033dcee6844bb424f033130c109f47e188c5364b"
|
||||
voiceToken="HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\TokenEnums\\eSpeak-NG\\English (America)"
|
||||
|
||||
fail() {
|
||||
printf 'Error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_command() {
|
||||
command -v "$1" > /dev/null 2>&1 || fail "Required command not found: $1"
|
||||
}
|
||||
|
||||
verify_sha256() {
|
||||
local file="$1"
|
||||
local expected="$2"
|
||||
local actual
|
||||
|
||||
actual="$(sha256sum "$file" | awk '{print $1}')"
|
||||
[[ "$actual" == "$expected" ]]
|
||||
}
|
||||
|
||||
download_installer() {
|
||||
mkdir -p "$cacheDir"
|
||||
|
||||
if [[ -s "$installerPath" ]] && verify_sha256 "$installerPath" "$installerSha256"; then
|
||||
printf 'Using cached WineSpeak installer.\n'
|
||||
return
|
||||
fi
|
||||
|
||||
rm -f "$installerPath" "$partialPath"
|
||||
printf 'Downloading WineSpeak...\n'
|
||||
if ! curl -L4 --fail --retry 3 --output "$partialPath" "$installerUrl"; then
|
||||
rm -f "$partialPath"
|
||||
fail 'Could not download WineSpeak.'
|
||||
fi
|
||||
|
||||
if ! verify_sha256 "$partialPath" "$installerSha256"; then
|
||||
rm -f "$partialPath"
|
||||
fail 'The downloaded WineSpeak installer failed its SHA-256 check.'
|
||||
fi
|
||||
|
||||
mv "$partialPath" "$installerPath"
|
||||
}
|
||||
|
||||
install_winespeak() {
|
||||
local registryOutput
|
||||
|
||||
download_installer
|
||||
|
||||
printf 'Installing WineSpeak into %s...\n' "$WINEPREFIX"
|
||||
if ! wine "$installerPath" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-; then
|
||||
fail 'WineSpeak installation failed.'
|
||||
fi
|
||||
|
||||
if [[ ! -f "$wrapperPath" ]]; then
|
||||
fail 'WineSpeak did not install its 32-bit SAPI wrapper.'
|
||||
fi
|
||||
if ! verify_sha256 "$wrapperPath" "$wrapperSha256"; then
|
||||
fail 'WineSpeak installed an unexpected 32-bit SAPI wrapper.'
|
||||
fi
|
||||
|
||||
printf 'Setting English (America) as the default SAPI voice...\n'
|
||||
if ! wine reg add 'HKCU\Software\Microsoft\Speech\Voices' \
|
||||
/v DefaultTokenId /t REG_SZ /d "$voiceToken" /f > /dev/null; then
|
||||
fail 'Could not set the default SAPI voice.'
|
||||
fi
|
||||
|
||||
registryOutput="$(wine reg query 'HKCU\Software\Microsoft\Speech\Voices' /v DefaultTokenId 2>/dev/null)"
|
||||
if ! grep -Fq "$voiceToken" <<< "$registryOutput"; then
|
||||
fail 'Could not verify the default SAPI voice.'
|
||||
fi
|
||||
|
||||
printf 'WineSpeak is installed and English (America) is the default SAPI voice.\n'
|
||||
}
|
||||
|
||||
for commandName in curl wine sha256sum awk grep; do
|
||||
require_command "$commandName"
|
||||
done
|
||||
|
||||
install_winespeak
|
||||
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repoRoot="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
testRoot="$(mktemp -d)"
|
||||
trap 'rm -rf "$testRoot"' EXIT
|
||||
|
||||
export HOME="${testRoot}/home"
|
||||
export XDG_DATA_HOME="${HOME}/.local/share"
|
||||
export XDG_CONFIG_HOME="${HOME}/.config"
|
||||
export XDG_CACHE_HOME="${HOME}/.cache"
|
||||
export DISPLAY=""
|
||||
export cache="${XDG_CACHE_HOME}/audiogame-manager"
|
||||
export configFile="${XDG_CONFIG_HOME}/storm-games/audiogame-manager/games.conf"
|
||||
export game="Shadow Line"
|
||||
export scriptDir="$repoRoot"
|
||||
mkdir -p "$cache" "${configFile%/*}" "${testRoot}/bin"
|
||||
touch "$configFile"
|
||||
|
||||
cat > "${testRoot}/bin/umu-run" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s|%s|%s|%s\n' "$WINEPREFIX" "$GAMEID" "${STORE:-}" "$*" >> "$UMU_STUB_LOG"
|
||||
if [[ "${1:-}" == "" ]]; then
|
||||
mkdir -p "$WINEPREFIX/drive_c"
|
||||
fi
|
||||
if [[ "${1:-}" == "winetricks" && "${3:-}" == "already-installed" ]]; then
|
||||
printf "winetricks verb 'already-installed' is already installed\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/umu-run"
|
||||
|
||||
cat > "${testRoot}/bin/gamemoderun" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' "$*" >> "$GAMEMODE_STUB_LOG"
|
||||
exec "$@"
|
||||
STUB
|
||||
chmod +x "${testRoot}/bin/gamemoderun"
|
||||
|
||||
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"
|
||||
export GAMEMODE_STUB_LOG="${testRoot}/gamemode.log"
|
||||
|
||||
# shellcheck source=.includes/gamemode.sh
|
||||
source "${repoRoot}/.includes/gamemode.sh"
|
||||
# shellcheck source=.includes/proton.sh
|
||||
source "${repoRoot}/.includes/proton.sh"
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local message="$3"
|
||||
if [[ "$expected" != "$actual" ]]; then
|
||||
printf 'FAIL: %s\nexpected: %s\nactual: %s\n' "$message" "$expected" "$actual" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
local file="$1"
|
||||
local pattern="$2"
|
||||
local message="$3"
|
||||
if ! grep -Fq "$pattern" "$file"; then
|
||||
printf 'FAIL: %s\nmissing pattern: %s\nfile contents:\n' "$message" "$pattern" >&2
|
||||
cat "$file" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_get_umu_bottle_sets_environment() {
|
||||
get_umu_bottle "shadow-line"
|
||||
assert_equals "${XDG_DATA_HOME}/audiogame-manager/protonBottles/shadow-line" "$WINEPREFIX" "WINEPREFIX points at AGM proton bottle"
|
||||
assert_equals "shadow-line" "$GAMEID" "GAMEID is exported"
|
||||
assert_equals "none" "$STORE" "STORE defaults to none"
|
||||
assert_equals ":0" "$DISPLAY" "DISPLAY defaults to :0"
|
||||
}
|
||||
|
||||
test_add_umu_launcher_records_backend_and_game_id() {
|
||||
get_umu_bottle "shadow-line"
|
||||
add_umu_launcher "shadow-line" 'c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
assert_file_contains "$configFile" 'umu|c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe|Shadow Line|export umuGameId=shadow-line' "UMU launcher entry is recorded"
|
||||
}
|
||||
|
||||
test_add_umu_launcher_records_extra_environment_flags() {
|
||||
get_umu_bottle "entombed"
|
||||
game="Entombed"
|
||||
add_umu_launcher "entombed" 'c:\Program Files (x86)\Entombed\Entombed.exe' "export PROTON_USE_XALIA=0"
|
||||
assert_file_contains "$configFile" 'umu|c:\Program Files (x86)\Entombed\Entombed.exe|Entombed|export umuGameId=entombed|export PROTON_USE_XALIA=0' "UMU launcher entry records extra environment flags"
|
||||
}
|
||||
|
||||
test_run_umu_game_uses_converted_path() {
|
||||
get_umu_bottle "shadow-line"
|
||||
mkdir -p "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
|
||||
touch "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe"
|
||||
run_umu_game 'c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
|
||||
assert_file_contains "$GAMEMODE_STUB_LOG" "umu-run ${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe" "UMU launches through GameMode"
|
||||
assert_file_contains "$UMU_STUB_LOG" "${WINEPREFIX}|shadow-line|none|${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe" "UMU launches converted exe path"
|
||||
}
|
||||
|
||||
test_install_crlf_file_normalizes_line_endings() {
|
||||
local sourceFile="${testRoot}/language_en.dat"
|
||||
local destFile="${testRoot}/installed/language_en.dat"
|
||||
local expectedFile="${testRoot}/expected-language_en.dat"
|
||||
printf 'line one\nline two\n' > "$sourceFile"
|
||||
printf 'line one\r\nline two\r\n' > "$expectedFile"
|
||||
|
||||
install_crlf_file "$sourceFile" "$destFile"
|
||||
|
||||
if ! cmp -s "$expectedFile" "$destFile"; then
|
||||
printf 'FAIL: Translation file is installed with CRLF line endings\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_install_proton_winetricks_verb_ignores_already_installed() {
|
||||
get_umu_bottle "shadow-line"
|
||||
|
||||
install_proton_winetricks_verb "already-installed"
|
||||
}
|
||||
|
||||
test_get_umu_bottle_sets_environment
|
||||
test_add_umu_launcher_records_backend_and_game_id
|
||||
test_add_umu_launcher_records_extra_environment_flags
|
||||
test_run_umu_game_uses_converted_path
|
||||
test_install_crlf_file_normalizes_line_endings
|
||||
test_install_proton_winetricks_verb_ignores_already_installed
|
||||
printf 'UMU backend tests passed\n'
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Immediately exit if errors are encountered.
|
||||
set -e
|
||||
|
||||
# Wine dependencies installer for Fedora Linux
|
||||
# If this fails on your system, please contact storm_dragon@stormux.org
|
||||
|
||||
configure_fedora() {
|
||||
packageList=(
|
||||
7zip
|
||||
alsa-lib
|
||||
alsa-plugins-pulseaudio
|
||||
cabextract
|
||||
curl
|
||||
dialog
|
||||
dos2unix
|
||||
gawk
|
||||
gnutls
|
||||
gstreamer1-plugin-libav
|
||||
gstreamer1-plugins-bad-free
|
||||
gstreamer1-plugins-good
|
||||
gstreamer1-plugins-ugly-free
|
||||
libjpeg-turbo
|
||||
libpng
|
||||
libwbclient
|
||||
mesa-dri-drivers
|
||||
mpg123
|
||||
ncurses
|
||||
openal-soft
|
||||
pulseaudio-libs
|
||||
sdl2-compat
|
||||
sox
|
||||
sqlite
|
||||
translate-shell
|
||||
unzip
|
||||
w3m
|
||||
wine
|
||||
winetricks
|
||||
xdg-utils
|
||||
xz
|
||||
)
|
||||
|
||||
sudo dnf install --assumeyes "${packageList[@]}"
|
||||
}
|
||||
|
||||
configure_fedora
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user