Add UMU Proton backend for Shadow Line

This commit is contained in:
Storm Dragon
2026-05-05 02:22:36 -04:00
parent 53d8c10645
commit 1f977bb1f4
6 changed files with 329 additions and 36 deletions
+8
View File
@@ -1,3 +1,5 @@
#!/usr/bin/env bash
declare -a errorList
declare -a packageList
if [[ $# -eq 0 ]]; then
@@ -10,6 +12,12 @@ else
errorList+=("Critical: Wine is not installed. You will not be able to play any games.")
fi
packageList+=("wine")
if command -v umu-run &> /dev/null ; then
[[ $# -eq 0 ]] && echo "umu-launcher is installed."
else
errorList+=("Warning: umu-launcher is not installed. Games that require Proton/UMU will not install or launch.")
fi
packageList+=("umu-launcher")
if command -v curl &> /dev/null ; then
[[ $# -eq 0 ]] && echo "Curl is installed."
else
+28 -2
View File
@@ -1,3 +1,6 @@
#!/usr/bin/env bash
# shellcheck disable=SC2154 # Sourced by audiogame-manager with shared globals.
documentation() {
if [[ "$2" == "Become a Patron" ]]; then
return
@@ -8,13 +11,36 @@ documentation() {
# Extract architecture from first parameter (format: "win64|path")
local wineArch="${1%%|*}"
get_bottle "$wineArch"
if [[ "$wineArch" == "umu" ]]; then
local launcherLine=""
local docFlag=""
local umuGameId=""
local -a documentationGame=()
launcherLine="$(grep -F -m1 "${1}|" "$configFile" 2> /dev/null || true)"
IFS='|' read -ra documentationGame <<< "$launcherLine"
for docFlag in "${documentationGame[@]:3}" ; do
if [[ "$docFlag" =~ ^export\ [a-zA-Z_][a-zA-Z0-9_]*=\'?.*\'?$ ]]; then
eval "$docFlag"
fi
done
if [[ -z "$umuGameId" ]]; then
echo "Unable to find UMU game id for documentation lookup."
return
fi
get_umu_bottle "$umuGameId"
else
get_bottle "$wineArch"
fi
echo "Loading documentation, please wait..."
# Try to find documentation based on common naming conventions.
local gamePath
gamePath="$(winepath -u "$2" 2> /dev/null)"
if [[ "$wineArch" == "umu" ]]; then
gamePath="$(umu_windows_path_to_unix "$2")"
else
gamePath="$(winepath -u "$2" 2> /dev/null)"
fi
gamePath="${gamePath%/*}"
local gameDoc=""
local isUrl="false"
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# shellcheck disable=SC2034,SC2154 # Sourced by audiogame-manager and installers with shared globals.
require_umu() {
if command -v umu-run &> /dev/null; then
return 0
fi
local message="This game requires umu-launcher. Please install umu-launcher and try again."
if declare -F agm_msgbox &> /dev/null; then
agm_msgbox "Audio Game Manager" "Audio Game Manager" "$message"
else
echo "$message" >&2
fi
return 1
}
get_umu_bottle() {
local gameId="$1"
if [[ -z "$gameId" ]]; then
echo "get_umu_bottle requires a game id." >&2
return 1
fi
export umuGameId="$gameId"
export WINEPREFIX="${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/protonBottles/${gameId}"
export GAMEID="$gameId"
export STORE="${umuStore:-none}"
export DISPLAY="${DISPLAY:-:0}"
mkdir -p "$WINEPREFIX"
}
install_proton_bottle() {
local gameId="$1"
shift || true
require_umu || return 1
get_umu_bottle "$gameId" || return 1
if [[ ! -f "${WINEPREFIX}/system.reg" ]]; then
umu-run ""
fi
if [[ $# -gt 0 ]]; then
umu-run winetricks "$@"
fi
}
umu_windows_path_to_unix() {
local windowsPath="$1"
local relativePath=""
if [[ "$windowsPath" =~ ^[cC]:\\ ]]; then
relativePath="${windowsPath:3}"
relativePath="${relativePath//\\//}"
printf '%s/drive_c/%s\n' "$WINEPREFIX" "$relativePath"
return 0
fi
winepath -u "$windowsPath"
}
run_umu_game() {
local windowsPath="$1"
local exePath=""
require_umu || return 1
if [[ -z "${umuGameId:-}" ]]; then
echo "UMU game id is not set for ${game[2]:-selected game}." >&2
return 1
fi
get_umu_bottle "$umuGameId" || return 1
exePath="$(umu_windows_path_to_unix "$windowsPath")"
if [[ ! -f "$exePath" ]]; then
echo "UMU executable not found: $exePath" >&2
return 1
fi
pushd "${exePath%/*}" > /dev/null || return 1
umu-run "$exePath"
popd > /dev/null || return 1
}
add_umu_launcher() {
local gameId="$1"
local windowsPath="$2"
shift 2
local launchSettings="umu|${windowsPath}|${game}|export umuGameId=${gameId}"
while [[ $# -gt 0 ]]; do
launchSettings+="|$1"
shift
done
if ! grep -F -q -x "$launchSettings" "$configFile" 2> /dev/null; then
echo "$launchSettings" >> "$configFile"
sort -t '|' -k3,3f -o "$configFile" "$configFile"
fi
}
set_umu_reg_value() {
local key="$1"
local valueName="$2"
local valueData="$3"
wine reg add "$key" /v "$valueName" /t REG_SZ /d "$valueData" /f
}
set_umu_app_winver() {
local exeName="$1"
local winVersion="$2"
set_umu_reg_value "HKCU\\Software\\Wine\\AppDefaults\\${exeName}" "Version" "$winVersion"
}
stop_umu_bottle() {
if command -v wineserver &> /dev/null; then
wineserver -k 2> /dev/null || true
fi
}
+35 -20
View File
@@ -1,23 +1,38 @@
# shellcheck shell=bash disable=SC2154 # cache and WINEPREFIX are set by audiogame-manager
#//Disable since it's not working
download "https://www.mm-galabo.com/sr/Download_files_srfv/shadowrine_fullvoice3.171.exe" "https://raw.githubusercontent.com/LordLuceus/sr-english-localization/master/language_en.dat"
export WINEARCH="win64" # Migrated to wine64 with WINETRICKS_FORCE=1
export winVer="win8"
install_wine_bottle fakejapanese
# Add bcrypt DLL override required for Shadow Line to run
cat > /tmp/bcrypt_override.reg << 'EOF'
[HKEY_CURRENT_USER\Software\Wine\DllOverrides]
"bcryptprimitives"="native,builtin"
EOF
wine regedit /tmp/bcrypt_override.reg
rm /tmp/bcrypt_override.reg
# shellcheck shell=bash disable=SC2154 # cache, game, and helper functions are set by audiogame-manager.
export game="Shadow Line"
shadowLineGameId="shadow-line"
shadowLinePath='c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
download "https://www.mm-galabo.com/sr/Download_files_srfv/shadowrine_fullvoice3.171.exe" \
"https://raw.githubusercontent.com/LordLuceus/sr-english-localization/master/language_en.dat" \
"${nvdaControllerClient32Dll}" \
"${nvdaControllerClient64Dll}"
install_proton_bottle "$shadowLineGameId" fakejapanese
shadowLineInstallDir="${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
set_umu_reg_value "HKCU\\Software\\Wine\\DllOverrides" "bcryptprimitives" "native,builtin"
set_umu_app_winver "play_sr.exe" "win8"
{
echo "# Installing Shadow Line..."
timeout 300 wine "${cache}/shadowrine_fullvoice3.171.exe" /sp- /VERYSILENT /SUPPRESSMSGBOXES 2>&1 || true
timeout 300 umu-run "${cache}/shadowrine_fullvoice3.171.exe" /sp- /VERYSILENT /SUPPRESSMSGBOXES 2>&1 || true
echo "# Installation complete"
} | agm_progressbox "Installing Game" "Installing Shadow Line (this may take a few minutes)..."
# Kill any auto-launched game processes (installer lacks skipifsilent flag)
wineserver -k 2>/dev/null || true
mv -v "${cache}/language_en.dat" "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/SystemData/language_en.dat"
add_launcher "c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe"
alert "Please set the language to English when the game opens.\nGo to options and press enter.\nPress down arrow 5 times and press enter.\nPress down arrow 1 time and press enter.\nPress up arrow 2 times and press enter.\nIf everything worked as expected you should be back on the game menu and speech should work."
} | agm_progressbox "Installing Game" "Installing Shadow Line with UMU/Proton (this may take a few minutes)..."
stop_umu_bottle
if [[ ! -f "${shadowLineInstallDir}/play_sr.exe" ]]; then
agm_msgbox "Shadow Line" "Shadow Line" "Shadow Line did not install to the expected location: ${shadowLineInstallDir}"
exit 1
fi
install -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."
+42 -14
View File
@@ -469,23 +469,36 @@ game_removal() {
# With shared bottles, always remove only the game files, never the entire bottle
create_game_array "$selectedGame"
if [[ ${#game[@]} -gt 0 ]]; then
# Set up wine environment for this game
# shellcheck source=.includes/bottle.sh
source "${scriptDir}/.includes/bottle.sh"
get_bottle "${game[0]}"
process_launcher_flags
if [[ "${game[0]}" == "umu" ]]; then
get_umu_bottle "$umuGameId"
else
# Set up wine environment for this game
# shellcheck source=.includes/bottle.sh
source "${scriptDir}/.includes/bottle.sh"
get_bottle "${game[0]}"
fi
if ! agm_yesno "Confirm Removal" "Audio Game Removal" "Are you sure you want to remove \"${game[2]}\"?"; then
agm_msgbox "Audio Game Removal" "" "Removal cancelled."
exit 0
fi
# kill any previous existing wineservers for this prefix in case they didn't shut down properly.
wineserver -k
# kill any previous existing servers for this prefix in case they didn't shut down properly.
if [[ "${game[0]}" == "umu" ]]; then
stop_umu_bottle
else
wineserver -k
fi
# Remove only the game's installation directory
if [[ -n "$winePath" ]]; then
local gameDir
gameDir="$(winepath "$winePath")"
if [[ "${game[0]}" == "umu" ]]; then
gameDir="$(umu_windows_path_to_unix "$winePath")"
else
gameDir="$(winepath "$winePath")"
fi
if [[ -d "$gameDir" ]]; then
rm -rfv "$gameDir" | agm_progressbox "Removing Game" "Removing \"${game[2]}\" files..."
else
@@ -534,9 +547,16 @@ kill_game() {
local wineExec="${game#*|}"
wineExec="${wineExec%|*}"
wineExec="${wineExec##*\\}"
# kill the wine server.
get_bottle "${game%|*}"
wineserver -k
create_game_array "$game"
if [[ "${game[0]}" == "umu" ]]; then
process_launcher_flags
get_umu_bottle "$umuGameId"
stop_umu_bottle
else
# kill the wine server.
get_bottle "${game[0]}"
wineserver -k
fi
agm_msgbox "Audio Game Killer" "" "The selected game has been stopped."
fi
exit 0
@@ -790,14 +810,21 @@ game_launcher() {
open_url "https://2mb.games/product/2mb-patron/"
exit 0
fi
# Set default path/exec for custom launch handlers.
winePath="${game[1]%\\*.exe}"
wineExec="${game[1]##*\\}"
process_launcher_flags
if [[ "${game[0]}" == "umu" ]]; then
echo "launching with umu"
start_nvda2speechd
run_umu_game "${game[1]}"
exit 0
fi
get_bottle "${game[0]}"
echo -n "launching "
wine --version
# kill any previous existing wineservers for this prefix in case they didn't shut down properly.
wineserver -k
# Set default path/exec for custom launch handlers.
winePath="${game[1]%\\*.exe}"
wineExec="${game[1]##*\\}"
# launch the game
if command -v qjoypad &> /dev/null ; then
mkdir -p ~/.qjoypad3
@@ -812,7 +839,6 @@ game_launcher() {
fi
fi
fi
process_launcher_flags
apply_executioners_rage_focus_workaround
customLaunchHandled="false"
custom_launch_parameters
@@ -901,6 +927,8 @@ export ipfsGateway="${ipfsGateway:-https://ipfs.stormux.org}"
# Source helper functions
# shellcheck source=.includes/bottle.sh
source "${scriptDir}/.includes/bottle.sh" # Also sourced in functions that need it
# shellcheck source=.includes/proton.sh
source "${scriptDir}/.includes/proton.sh"
# shellcheck source=.includes/desktop.sh
source "${scriptDir}/.includes/desktop.sh"
# dialog-interface.sh already sourced earlier
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
set -euo pipefail
repoRoot="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
testRoot="$(mktemp -d)"
trap 'rm -rf "$testRoot"' EXIT
export HOME="${testRoot}/home"
export XDG_DATA_HOME="${HOME}/.local/share"
export XDG_CONFIG_HOME="${HOME}/.config"
export XDG_CACHE_HOME="${HOME}/.cache"
export DISPLAY=""
export cache="${XDG_CACHE_HOME}/audiogame-manager"
export configFile="${XDG_CONFIG_HOME}/storm-games/audiogame-manager/games.conf"
export game="Shadow Line"
export scriptDir="$repoRoot"
mkdir -p "$cache" "${configFile%/*}" "${testRoot}/bin"
touch "$configFile"
cat > "${testRoot}/bin/umu-run" <<'STUB'
#!/usr/bin/env bash
printf '%s|%s|%s|%s\n' "$WINEPREFIX" "$GAMEID" "${STORE:-}" "$*" >> "$UMU_STUB_LOG"
if [[ "${1:-}" == "" ]]; then
mkdir -p "$WINEPREFIX/drive_c"
fi
STUB
chmod +x "${testRoot}/bin/umu-run"
cat > "${testRoot}/bin/wine" <<'STUB'
#!/usr/bin/env bash
if [[ "${1:-}" == "winepath" || "${1:-}" == "winepath.exe" ]]; then
shift
fi
if [[ "${1:-}" == "-u" ]]; then
input="$2"
path="${input#c:\\}"
path="${path//\\//}"
printf '%s/drive_c/%s\n' "$WINEPREFIX" "$path"
exit 0
fi
printf 'wine %s\n' "$*" >> "$WINE_STUB_LOG"
STUB
chmod +x "${testRoot}/bin/wine"
cat > "${testRoot}/bin/wineserver" <<'STUB'
#!/usr/bin/env bash
printf 'wineserver %s\n' "$*" >> "$WINE_STUB_LOG"
STUB
chmod +x "${testRoot}/bin/wineserver"
export PATH="${testRoot}/bin:$PATH"
export UMU_STUB_LOG="${testRoot}/umu.log"
export WINE_STUB_LOG="${testRoot}/wine.log"
# shellcheck source=.includes/proton.sh
source "${repoRoot}/.includes/proton.sh"
assert_equals() {
local expected="$1"
local actual="$2"
local message="$3"
if [[ "$expected" != "$actual" ]]; then
printf 'FAIL: %s\nexpected: %s\nactual: %s\n' "$message" "$expected" "$actual" >&2
exit 1
fi
}
assert_file_contains() {
local file="$1"
local pattern="$2"
local message="$3"
if ! grep -Fq "$pattern" "$file"; then
printf 'FAIL: %s\nmissing pattern: %s\nfile contents:\n' "$message" "$pattern" >&2
cat "$file" >&2
exit 1
fi
}
test_get_umu_bottle_sets_environment() {
get_umu_bottle "shadow-line"
assert_equals "${XDG_DATA_HOME}/audiogame-manager/protonBottles/shadow-line" "$WINEPREFIX" "WINEPREFIX points at AGM proton bottle"
assert_equals "shadow-line" "$GAMEID" "GAMEID is exported"
assert_equals "none" "$STORE" "STORE defaults to none"
assert_equals ":0" "$DISPLAY" "DISPLAY defaults to :0"
}
test_add_umu_launcher_records_backend_and_game_id() {
get_umu_bottle "shadow-line"
add_umu_launcher "shadow-line" 'c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
assert_file_contains "$configFile" 'umu|c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe|Shadow Line|export umuGameId=shadow-line' "UMU launcher entry is recorded"
}
test_run_umu_game_uses_converted_path() {
get_umu_bottle "shadow-line"
mkdir -p "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice"
touch "${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe"
run_umu_game 'c:\Program Files (x86)\GalaxyLaboratory\ShadowRine_FullVoice\play_sr.exe'
assert_file_contains "$UMU_STUB_LOG" "${WINEPREFIX}|shadow-line|none|${WINEPREFIX}/drive_c/Program Files (x86)/GalaxyLaboratory/ShadowRine_FullVoice/play_sr.exe" "UMU launches converted exe path"
}
test_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'