Final tuned values baked in as defaults: HudScale=0.8, WorldLabelScale=0.8. - install.sh gains METHOD=bepinex: installs BepInEx from vendor + the prebuilt plugin, with a matching uninstall that leaves BepInEx/ alone (other mods may depend on it). Success banner is now method-aware. - Ship the prebuilt plugin DLL so users don't need a .NET SDK. - Per-game README documenting why the camera is deliberately untouched (orthographic, constant orthographicSize = already Hor+), the two-canvas UI problem, tuning, and where the plugin log actually lives. - Root README lists both games and both fix methods. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
179 lines
6.6 KiB
Bash
Executable File
179 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# UltraWidePatches - installer (Linux / Proton, also usable on Windows via
|
|
# manual extraction of the release zip)
|
|
#
|
|
# Usage:
|
|
# ./install.sh # list available games
|
|
# ./install.sh GundamBreaker4 # install that game's patch
|
|
# ./install.sh GundamBreaker4 uninstall
|
|
#
|
|
# Overrides:
|
|
# GAME_DIR=/path/to/Binaries/Win64 ./install.sh <Game> # nonstandard install
|
|
# PREFIX_INI_FILE=/path/to/Engine.ini ./install.sh <Game> # nonstandard prefix
|
|
# LOADER_NAME=version.dll ./install.sh <Game> # loader collision
|
|
# ============================================================================
|
|
set -euo pipefail
|
|
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
GAMES_DIR="$HERE/games"
|
|
|
|
list_games() {
|
|
echo "Available patches:"
|
|
for d in "$GAMES_DIR"/*/; do
|
|
[ -f "$d/game.conf" ] || continue
|
|
( TITLE=""; STATUS=""; . "$d/game.conf" >/dev/null 2>&1 || true
|
|
printf " %-22s %s\n" "$(basename "$d")" "${TITLE:-} — ${STATUS:-}" )
|
|
done
|
|
echo
|
|
echo "Usage: ./install.sh <GameName> [uninstall]"
|
|
}
|
|
|
|
GAME="${1:-}"
|
|
ACTION="${2:-install}"
|
|
if [ -z "$GAME" ]; then list_games; exit 0; fi
|
|
|
|
GAME_CONF="$GAMES_DIR/$GAME/game.conf"
|
|
if [ ! -f "$GAME_CONF" ]; then
|
|
echo "[!] Unknown game: $GAME"; echo; list_games; exit 1
|
|
fi
|
|
# shellcheck disable=SC1090
|
|
. "$GAME_CONF"
|
|
LOADER="${LOADER_NAME:-${LOADER:-dsound.dll}}"
|
|
|
|
echo "=== ${TITLE:-$GAME} ==="
|
|
|
|
# --- locate the game's exe directory across Steam libraries -------------------
|
|
find_gamedir() {
|
|
local cand
|
|
for base in \
|
|
"$HOME/.steam/steam" "$HOME/.local/share/Steam" \
|
|
"/media/$USER"/*/steam "/media/$USER"/*/SteamLibrary \
|
|
"$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam"; do
|
|
cand="$base/steamapps/common/$COMMON_SUBDIR"
|
|
[ -f "$cand/$EXE" ] && { echo "$cand"; return 0; }
|
|
done
|
|
cand=$(find /home /media /mnt -maxdepth 8 -name "$EXE" 2>/dev/null | head -1)
|
|
[ -n "$cand" ] && { dirname "$cand"; return 0; }
|
|
return 1
|
|
}
|
|
|
|
GAMEDIR="${GAME_DIR:-$(find_gamedir || true)}"
|
|
if [ -z "${GAMEDIR:-}" ] || [ ! -f "$GAMEDIR/$EXE" ]; then
|
|
echo "[!] Could not find $EXE."
|
|
echo " Set it manually: GAME_DIR=/path/to/exe/dir ./install.sh $GAME"
|
|
exit 1
|
|
fi
|
|
echo "[+] Game dir: $GAMEDIR"
|
|
|
|
# --- locate the Proton prefix ini (optional, per-game) ------------------------
|
|
find_prefix_ini() {
|
|
[ -n "${PREFIX_INI_FILE:-}" ] && { [ -f "$PREFIX_INI_FILE" ] && echo "$PREFIX_INI_FILE" && return 0; return 1; }
|
|
[ -z "${PREFIX_INI:-}" ] && return 1
|
|
local cand
|
|
for base in \
|
|
"$HOME/.steam/steam" "$HOME/.local/share/Steam" \
|
|
"/media/$USER"/*/steam "/media/$USER"/*/SteamLibrary \
|
|
"$HOME/.var/app/com.valvesoftware.Steam/.local/share/Steam"; do
|
|
cand="$base/steamapps/compatdata/$APPID/pfx/$PREFIX_INI"
|
|
[ -f "$cand" ] && { echo "$cand"; return 0; }
|
|
done
|
|
return 1
|
|
}
|
|
MARK_BEGIN="; >>> UltraWidePatches ($GAME) >>>"
|
|
MARK_END="; <<< UltraWidePatches ($GAME) <<<"
|
|
|
|
apply_ini() {
|
|
[ -z "${INI_BLOCK:-}" ] && return
|
|
local ini; ini="$(find_prefix_ini || true)"
|
|
[ -z "$ini" ] && { echo "[i] Prefix ini not found (launch the game once first); skipping config tweak."; return; }
|
|
if grep -qF "$MARK_BEGIN" "$ini"; then echo "[i] Config tweak already present."; return; fi
|
|
cp "$ini" "$ini.bak-uwp"
|
|
{ echo ""; echo "$MARK_BEGIN"; echo "$INI_BLOCK"; echo "$MARK_END"; } >> "$ini"
|
|
echo "[+] Config tweak applied (backup: $(basename "$ini").bak-uwp)"
|
|
}
|
|
|
|
remove_ini() {
|
|
local ini; ini="$(find_prefix_ini || true)"
|
|
[ -z "$ini" ] && return
|
|
if grep -qF "$MARK_BEGIN" "$ini"; then
|
|
local esc_b esc_e
|
|
esc_b=$(printf '%s' "$MARK_BEGIN" | sed 's/[][\/.*^$]/\\&/g')
|
|
esc_e=$(printf '%s' "$MARK_END" | sed 's/[][\/.*^$]/\\&/g')
|
|
sed -i "/$esc_b/,/$esc_e/d" "$ini"
|
|
echo "[+] Config tweak removed."
|
|
fi
|
|
}
|
|
|
|
METHOD="${METHOD:-suwsf}"
|
|
|
|
if [ "$ACTION" = "uninstall" ]; then
|
|
echo "[+] Uninstalling..."
|
|
if [ "$METHOD" = "bepinex" ]; then
|
|
rm -vf "$GAMEDIR/winhttp.dll" "$GAMEDIR/doorstop_config.ini" "$GAMEDIR/.doorstop_version" \
|
|
"$GAMEDIR/changelog.txt" "$GAMEDIR/BepInEx/plugins/$PLUGIN_DLL"
|
|
# leave BepInEx/ itself: the user may have other mods installed
|
|
echo "[i] Left BepInEx/ in place (other mods may rely on it). Delete it manually if unused."
|
|
else
|
|
for f in SUWSF.asi SUWSF.ini dsound.dll dinput8.dll version.dll winmm.dll; do
|
|
[ -f "$GAMEDIR/$f" ] && rm -v "$GAMEDIR/$f"
|
|
done
|
|
fi
|
|
remove_ini
|
|
echo "[+] Done. No game file was ever modified."
|
|
exit 0
|
|
fi
|
|
|
|
# --- install ------------------------------------------------------------------
|
|
if [ "$METHOD" = "bepinex" ]; then
|
|
BEPZIP=$(ls "$HERE"/vendor/BepInEx_win_x64_*.zip 2>/dev/null | head -1)
|
|
PLUGIN="$GAMES_DIR/$GAME/$PLUGIN_DLL"
|
|
[ -f "$BEPZIP" ] || { echo "[!] missing BepInEx zip in vendor/"; exit 1; }
|
|
[ -f "$PLUGIN" ] || { echo "[!] missing prebuilt plugin: $PLUGIN"; exit 1; }
|
|
|
|
if [ -f "$GAMEDIR/winhttp.dll" ]; then
|
|
echo "[i] BepInEx already present; updating plugin only."
|
|
else
|
|
command -v unzip >/dev/null || { echo "[!] unzip required"; exit 1; }
|
|
unzip -o -q "$BEPZIP" -d "$GAMEDIR"
|
|
echo "[+] Installed BepInEx ($(basename "$BEPZIP"))"
|
|
fi
|
|
mkdir -p "$GAMEDIR/BepInEx/plugins"
|
|
cp -v "$PLUGIN" "$GAMEDIR/BepInEx/plugins/"
|
|
else
|
|
ASI="$HERE/vendor/SUWSF-x64/SUWSF.asi"
|
|
LOADER_SRC="$HERE/vendor/SUWSF-x64/dsound.dll" # Ultimate ASI Loader (adapts by filename)
|
|
INI="$GAMES_DIR/$GAME/SUWSF.ini"
|
|
for f in "$ASI" "$LOADER_SRC" "$INI"; do
|
|
[ -f "$f" ] || { echo "[!] missing $f"; exit 1; }
|
|
done
|
|
|
|
cp -v "$ASI" "$GAMEDIR/SUWSF.asi"
|
|
cp -v "$INI" "$GAMEDIR/SUWSF.ini"
|
|
cp -v "$LOADER_SRC" "$GAMEDIR/$LOADER"
|
|
fi
|
|
apply_ini
|
|
|
|
cat <<EOF
|
|
|
|
[+] Installed ($METHOD): $([ "$METHOD" = "bepinex" ] && echo "BepInEx + $PLUGIN_DLL + $LOADER" || echo "SUWSF.asi, SUWSF.ini, $LOADER")
|
|
|
|
NEXT (Proton) — REQUIRED, or the loader is ignored and the fix won't apply in gameplay.
|
|
Steam > right-click ${TITLE:-$GAME} > Properties > Launch Options:
|
|
${LAUNCH_OPTION:-WINEDLLOVERRIDES="${LOADER%.dll}=n,b" %command%}
|
|
|
|
Then set the in-game resolution to your native ultrawide res, fullscreen.
|
|
|
|
Tuning (METHOD=bepinex games):
|
|
Config is generated on first launch at
|
|
<game>/BepInEx/config/*.cfg (edits apply live, ~2s, no restart)
|
|
Plugin log (proves whether it applied):
|
|
<game>/BepInEx/LogOutput.log
|
|
|
|
Troubleshooting:
|
|
Different loader name: LOADER_NAME=version.dll ./install.sh $GAME
|
|
Diagnose injection: add PROTON_LOG=1 to launch options, then
|
|
./tools/read_protonlog.sh $APPID
|
|
Remove everything: ./install.sh $GAME uninstall
|
|
EOF
|