audiogame-manager/audiogame-manager.sh
2025-05-20 17:51:20 -04:00

518 lines
19 KiB
Bash
Executable File

#!/usr/bin/env bash
# Dialog accessibility
export DIALOGOPTS='--no-lines --visit-items'
# Install games
game_installer() {
export LANG="en_US.UTF-8"
# Get list of installed games from config file
mapfile -t installedGames < <(sed -e '/^$/d' -e '/^[[:space:]]*#/d' "${configFile}" 2> /dev/null | cut -d '|' -f3)
# Create the menu of available games by reading from .install directory
declare -a menuList
# Get all .sh files from .install directory, excluding those starting with # as first line.
mapfile -t sortedGames < <(for f in "${0%/*}/.install/"*.sh; do
# Skip if first line starts with #
[[ $(head -n1 "$f") == "#"* ]] && continue
echo "${f##*/%.sh}"
done | sort)
for i in "${sortedGames[@]}"; do
local menuItem="${i%.sh}"
menuItem="${menuItem##*/}"
# Check if game is already installed
for j in "${installedGames[@]}"; do
if [[ "$j" == "$menuItem" ]]; then
unset menuItem
break
fi
done
# Add to menu if not installed
if [[ -n "$menuItem" ]]; then
menuList+=("$menuItem" "$menuItem")
fi
done
# If all games are installed, exit
if [[ ${#menuList[@]} -eq 0 ]]; then
echo "All games are already installed."
exit 0
fi
menuList+=("Donate" "Donate")
menuList+=("Become a Patron" "Become a Patron")
# Show game selection dialog
game="$(dialog --backtitle "Audio Game Installer" \
--clear \
--ok-label "Install" \
--no-tags \
--menu "Please select a game to install" 0 0 0 "${menuList[@]}" --stdout)"
[[ $? -ne 0 ]] && exit 0
# Handle selection
if [[ -n "$game" ]]; then
case "$game" in
"Donate")
open_url "https://ko-fi.com/stormux"
exit 0
;;
"Become a Patron")
open_url "https://patreon.com/stormux"
exit 0
;;
*)
# Convert game name to filename format
local installScript="${0%/*}/.install/${game}.sh"
# Check if install script exists
if [[ -f "$installScript" ]]; then
# Source and execute the install script
source "$installScript"
else
dialog --backtitle "Audio Game Installer" \
--msgbox "Installation script not found for ${game}" -1 -1
exit 1
fi
;;
esac
fi
}
# remove games
game_removal() {
source .includes/bottle.sh
if [[ "$(uname -m)" == "aarch64" ]]; then
export wine="${wine:-/usr/bin/wine}"
else
export wine="${wine:-/usr/bin/wine}"
fi
mapfile -t lines < <(sed -e '/^$/d' -e '/^[[:space:]]*#/d' "${configFile}" 2> /dev/null)
if [[ ${#lines} -eq 0 ]]; then
echo "No games found."
exit 0
fi
# Create the menu of installed games
declare -a menuList
for i in "${lines[@]}" ; do
IFS='|' read -ra gameInfo <<< "$i"
menuList+=("${gameInfo[0]}|${gameInfo[1]}" "${gameInfo[2]}")
done
menuList+=("Donate" "Donate")
menuList+=("Become a Patron" "Become a Patron")
local game
game="$(dialog --backtitle "Audio Game Removal" \
--clear \
--ok-label "Remove" \
--no-tags \
--menu "Please select a game to delete" 0 0 0 "${menuList[@]}" --stdout)"
if [[ ${#game} -gt 0 ]]; then
if [[ "$game" == "Donate" ]]; then
open_url "https://ko-fi.com/stormux"
exit 0
fi
if [[ "$game" == "Become a Patron" ]]; then
open_url "https://2mb.games/product/2mb-patron/"
exit 0
fi
local winePath="${game#*|}"
export winePath="${winePath%\\*.exe}"
local wineExec="${game#*|}"
wineExec="${wineExec%|*}"
wineExec="${wineExec##*\\}"
# Confirm removal
get_bottle "${game%|*}"
# Make sure the game can be handled by remove
if [[ "${HOME}/.local/wine/${game%|*}" == "${WINEPREFIX}" ]]; then
read -rp "To remove the wine bottle \"${WINEPREFIX##*/}\" and all of its contents press enter. To cancel press control+c. " continue
# kill any previous existing wineservers for this prefix in case they didn't shut down properly.
${wine}server -k
# remove the game
rm -rf "${WINEPREFIX}"
else
read -rp "This bottle \"${WINEPREFIX##*/}\" contains multiple entries, so only the game will be removed. To continue press enter. To cancel press control+c. " continue
rm -rf "$(winepath "${winePath}")"
fi
# remove the launcher
gawk -i inplace -vLine="${game//\\/\\\\}" '!index($0,Line)' "$configFile"
echo "The selected item has been deleted."
fi
exit 0
}
# kill games that are stuck
kill_game() {
source .includes/bottle.sh
if [[ "$(uname -m)" == "aarch64" ]]; then
export wine="${wine:-/usr/bin/wine}"
else
export wine="${wine:-/usr/bin/wine}"
fi
mapfile -t lines < <(sed '/^$/d' "${configFile}" 2> /dev/null)
if [[ ${#lines} -eq 0 ]]; then
echo "No games found."
exit 0
fi
# Create the menu of installed games
declare -a menuList
for i in "${lines[@]}" ; do
IFS='|' read -ra gameInfo <<< "$i"
menuList+=("${gameInfo[0]}|${gameInfo[1]}" "${gameInfo[2]}")
done
menuList+=("Donate" "Donate")
menuList+=("Become a Patron" "Become a Patron")
local game="$(dialog --backtitle "Audio Game Killer" \
--clear \
--ok-label "Kill" \
--no-tags \
--menu "Please select a game to force stop" 0 0 0 "${menuList[@]}" --stdout)"
if [[ ${#game} -gt 0 ]]; then
if [[ "$game" == "Donate" ]]; then
open_url "https://ko-fi.com/stormux"
exit 0
fi
if [[ "$game" == "Become a Patron" ]]; then
open_url "https://2mb.games/product/2mb-patron/"
exit 0
fi
local winePath="${game#*|}"
winePath="${winePath%\\*.exe}"
local wineExec="${game#*|}"
wineExec="${wineExec%|*}"
wineExec="${wineExec##*\\}"
# kill the wine server.
get_bottle "${game%|*}"
${wine}server -k
echo "The selected game has been stopped."
fi
exit 0
}
# for games that require custom scripts before launch or custom launch parameters
custom_launch_parameters() {
if [[ "${game[0]}" == "dragon-pong" ]]; then
"${0%/*}/speech/speak_window_title.sh" DragonPong.exe &
pushd "$(winepath "$winePath")"
wine "$wineExec"
popd
exit 0
fi
if [[ "${game[0]}" == "executioner's-rage" ]]; then
find "${WINEPREFIX}/drive_c/Program Files" -type f -name 'nvdaControllerClient64.dll' -exec cp -v "${cache}/nvda2speechd64.dll" "{}" \;
fi
if [[ "${game[0]}" == "laser-breakout" ]]; then
"${0%/*}/speech/speak_window_title.sh" play.exe &
fi
if [[ "${game[0]}" == "bokurano-daibouken-2" ]]; then
"${0%/*}/speech/clipboard_translator.sh" play.exe bokurano-daibouken2 &
fi
if [[ "${game[0]}" == "bokurano-daibouken" ]]; then
"${0%/*}/speech/clipboard_translator.sh" play.exe bokurano-daibouken &
fi
if [[ "${game[0]}" =~ "bokurano-daibouken-3" ]]; then
dictPath="$(winepath "${winePath}")"
if [[ -r "${cache}/bk3-dict.dat" ]] && [[ ! -d "${dictPath}/dict" ]]; then
cp "${cache}/bk3-dict.dat" "${dictPath}/dict.dat"
fi
if [[ -d "${dictPath}/dict" ]]; then
if [[ ! -e "${dictPath}/data/nvdaControllerClient.dll" ]]; then
cp "${cache}/nvda2speechd32.dll" "${dictPath}/data/nvdaControllerClient.dll"
fi
fi
if [[ ! -d "${dictPath}/dict" ]] && [[ ! -r "${cache}/bk3-dict.dat" ]]; then
find "${WINEPREFIX}/drive_c/nyanchangame/bk3" -type f -name 'nvdaControllerClient.dll' -exec rm -v "{}" \;
"${0%/*}/speech/clipboard_translator.sh" play.exe bokurano-daibouken3 &
fi
fi
if [[ "${game[0]}" == "bop-it-emulator" ]]; then
"${0%/*}/speech/speak_window_title.sh" bop.exe &
fi
if [[ "${game[0]}" == "road-to-rage" ]]; then
"${0%/*}/speech/speak_window_title.sh" trtr.exe &
fi
if [[ "${game[0]}" == "sequence-storm" ]]; then
"${0%/*}/speech/clipboard_reader.sh" SequenceStorm &
fi
#if [[ "${game[0]}" == "shadow-line" ]]; then
#find "${WINEPREFIX}/drive_c/" -type f -name 'nvdaControllerClient.dll' -exec rm -v "{}" \;
#"${0%/*}/speech/clipboard_translator.sh" play_sr.exe shadow-line &
#fi
if [[ "${game[0]}" == "sketchbook" ]]; then
find "${WINEPREFIX}" -type f -name 'nvdaControllerClient32.dll' -exec cp -v "${cache}/nvdaControllerClient32.dll" "{}" \;
fi
if [[ "${game[0]}" == "audiodisc" ]]; then
wine "$winePath\\$wineExec"
exit 0
fi
if [[ "${game[0]}" == "audioquake" ]]; then
wine "$winePath\\$wineExec"
exit 0
fi
if [[ "${game[0]}" == "screaming-strike-2" ]]; then
pushd "$(winepath "$winePath")"
${wine} "$wineExec"
popd
exit 0
fi
if [[ "${game[0]}" == "warsim" ]]; then
pushd "$(winepath "${game[1]%\\*}")"
wine "${game[1]##*\\}"
popd
exit 0
fi
if [[ "${game[0]}" == "interceptor" ]]; then
pushd "$(winepath "$winePath")"
wine "$wineExec"
popd
exit 0
fi
if [[ -d "${WINEPREFIX}/drive_c/windows/syswow64" ]]; then
# switch to wine64 for 64 bit prefix.
[[ "${wine}" == "/usr/bin/wine" ]] && export wine="/usr/bin/wine64"
fi
}
# Process game launcher flags
process_launcher-flags() {
flags=("${game[@]:3}")
for i in "${flags[@]}" ; do
if [[ "${i}" =~ ^export\ [a-zA-Z_][a-zA-Z0-9_]*=\'?.*\'?$ ]]; then
eval "${i}"
fi
done
}
create_game_array() {
# Game array 0 bottle, 1 path, 2 title, 3+ flags
game="$1"
for i in "${lines[@]}" ; do
# Only compare the launcher section
j="${game#*|}"
k="${i#*|}"
k="${k%%|*}"
if [[ "$j" == "$k" ]]; then
IFS='|' read -ra game <<< "$i"
break
fi
done
}
# launch games that are installed
game_launcher() {
# For use by update scripts that want to source functions in this file.
[[ "$agmNoLaunch" == "true" ]] && return
source .includes/bottle.sh
pgrep -u "$USER" nvda2speechd &> /dev/null || {
if [[ -x ${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd ]]; then
if command -v FEXLoader &> /dev/null ; then
FEXLoader -- ${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd &> /dev/null &
else
${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager/nvda2speechd &> /dev/null &
fi
fi; }
mapfile -t lines < <(sed -e '/^$/d' -e '/^ *#/d' "${configFile}" 2> /dev/null)
if [[ ${#lines} -eq 0 ]]; then
echo "Install some games first."
exit 0
fi
if [[ $# -eq 0 ]]; then
# Create the menu of installed games
declare -a menuList
for i in "${lines[@]}" ; do
IFS='|' read -ra gameInfo <<< "$i"
menuList+=("${gameInfo[0]}|${gameInfo[1]}" "${gameInfo[2]}")
done
menuList+=("Donate" "Donate")
menuList+=("Become a Patron" "Become a Patron")
local game=""
game="$(dialog --backtitle "Audio Game Launcher" \
--clear \
--extra-button \
--extra-label "Documentation" \
--ok-label "Launch" \
--no-tags \
--menu "Please select a game to play" 0 0 0 "${menuList[@]}" --stdout)"
local menuCode=$?
if [[ $menuCode -eq 1 ]] || [[ $menuCode -eq 255 ]]; then
exit 0
elif [[ $menuCode -eq 3 ]]; then
source .includes/help.sh # Make available in this function
documentation "$game" "$(echo "$game" | cut -d '|' -f2)"
fi
create_game_array "$game"
else
create_game_array "$game"
if [[ -z "$game" ]]; then
echo "Game $1 not found."
exit 1
fi
fi
if [[ ${#game[@]} -gt 0 ]]; then
if [[ "${game[0]}" == "Donate" ]]; then
open_url "https://ko-fi.com/stormux"
exit 0
fi
if [[ "${game[0]}" == "Become a Patron" ]]; then
open_url "https://2mb.games/product/2mb-patron/"
exit 0
fi
get_bottle "${game[0]}"
# make sure wine is actually set to something
if [[ "$(uname -m)" == "aarch64" ]]; then
export wine="${wine:-/usr/bin/wine}"
else
export wine="${wine:-/usr/bin/wine}"
fi
echo -n "launching "
${wine} --version
# kill any previous existing wineservers for this prefix in case they didn't shut down properly.
${wine}server -k
# launch the game
if command -v qjoypad &> /dev/null ; then
mkdir -p ~/.qjoypad3
touch "${HOME}/.qjoypad3/${game[2]}.lyt"
# A | separated list of games that should not start with qjoypad.
noQjoypadGames="A Hero's Call"
if [[ "${noqjoypad}" != "true" ]] && ! [[ "${game[2]}" =~ ${noQjoypadGames} ]]; then
if pgrep qjoypad &> /dev/null ; then
qjoypad -T "${game[2]}" 2> /dev/null
else
qjoypad -T "${game[2]}" 2> /dev/null &
fi
fi
fi
process_launcher-flags
custom_launch_parameters
${wine:-/usr/bin/wine} start /d "${game[1]%\\*}" "${game[1]##*\\}" /realtime
fi
exit 0
}
# main script
trap "exit 0" SIGINT
# If display isn't set assume we are launching from console and an X environment is running using display :0
if [[ -z "$DISPLAY" ]]; then
export DISPLAY=":0"
fi
# Settings file
export cache="${XDG_CACHE_HOME:-$HOME/.cache}/audiogame-manager"
export configFile="${XDG_CONFIG_HOME:-$HOME/.config}/storm-games/audiogame-manager/games.conf"
mkdir -p "${cache}"
mkdir -p "${configFile%/*}"
# Create the path for AGM's helper programs.
# Originally this was only winetricks, thus the name, and I'm too lazy to change it now.
winetricksPath="${XDG_DATA_HOME:-$HOME/.local/share}/audiogame-manager"
mkdir -p "${winetricksPath}"
# Load any arguments from settings.conf file
if [[ -r "${configFile%/*}/settings.conf" ]]; then
source "${configFile%/*}/settings.conf"
fi
# Update the cache for older versions of audiogame-manager
if [[ -d "${configFile%/*}/cache" ]]; then
{ mv -v "${configFile%/*}/cache/"* "${cache}"
rmdir -v "${configFile%/*}/cache/"; } | dialog \
--backtitle "Audiogame Manager" --progressbox "Updating cache, please wait..." -1 -1
fi
checkWinetricksUpdate="false"
# Turn off debug messages
export WINEDEBUG="${winedebug:--all}"
# Compatibility with box86
export BOX86_NOBANNER=1
# During installation, you can set winVer to the versions available.
# To set winetricks arguments, such as virtual desktop, set the winetricksSettings variable.
# Example: winetricksSettings="vd=1024x768"
# Files are cached unless -N no cache is set.
unset noCache
# Manual installation is not default, make sure it's unset
unset manualInstall
unset version
# ipfs gateway
export ipfsGateway="${ipfsGateway:-https://ipfs.stormux.org}"
export nvdaControllerClientDll="${ipfsGateway}/ipfs/QmWu7YdSbKMk1Qm5DKvEA5hk1YuAK8wVkwhDf2CsmPkmF1?filename=nvdaControllerClient32.dll"
# Source helper functions
source .includes/bottle.sh # Also sourced in functions that need it
source .includes/desktop.sh
source .includes/functions.sh
source .includes/help.sh
source .includes/update.sh
# Check minimum requirements
check_requirements || exit 1
# Check for updates
update
# Get latest news if available
check_news
# With no arguments, open the game launcher.
if [[ $# -eq 0 ]]; then
game_launcher
fi
# Array of command line arguments
declare -A command=(
[c]="Check your system for necessary components."
[C]="Clear the cache. All game installers will be deleted."
[D]="Create desktop shortcut. You can launch audiogame-manager from the desktop or applications menu."
[d]="Debug mode, wine will be much more verbose when games are installed with this flag."
[h]="This help screen."
[i]="Install games."
[I:]="Noninteractive game installation."
[k]="Kill a running game that is stuck."
[L]="Display license information."
[l:]="Launch given game without interactive audiogame-manager menu specified by its wine bottle."
[N]="No cache, delete the installer after it has been extracted."
[n]="No RHVoice, do not install RHVoice when the game is installed."
[P]="Print a list of packages required by audiogame-manager."
[q]="No qjoypad. Does not launch qjoypad if it is detected."
[R]="Redownload. Removes old versions of packages from cache before installing."
[r]="Remove a game. This will delete all game data."
[S:]="Speech rate. Requires -V voice name, the default is 7. Values 0-9 or A."
[t]="Total games. Show how many games are currently available."
[v:]="Select the voice to be installed, default is Bdl. Options are alan, bdl, clb, or slt."
[V:]="Select the default voice for a bottle."
)
# Convert the keys of the associative array to a format usable by getopts
args="${!command[*]}"
args="${args//[[:space:]]/}"
while getopts "${args}" i ; do
case "$i" in
c) ./.includes/checkup.sh;;
C) clear_cache;;
D) desktop_launcher;;
d)
unset WINEDEBUG
game_installer
;;
h) help;;
i) game_installer;;
I)
game="${OPTARG}"
break;;
k) kill_game;;
L) license;;
l) game_launcher "${OPTARG}";;
N) noCache="true";;
n) norh="true";;
P) checklist quiet;;
q)
noqjoypad="true"
game_launcher;;
R) redownload="true";;
r) game_removal;;
S) defaultRate="${OPTARG}";;
t)
gameCount=$(find .install -type f -iname "*.sh" | wc -l)
dialog --backtitle "Linux Game Manager" \
--infobox "There are currently ${gameCount} games available." -1 -1
exit 0
;;
v) voiceName="${OPTARG}";;
V) defaultVoice="${OPTARG}";;
esac
done
[[ ${#game} -lt 1 ]] && exit 0
[[ "$agmNoLaunch" != "true" ]] && exit 0