]> vilimpoc.org git repositories - bl2-split/blobdiff - bl2-splitscreen.sh
Add kill command, fix Ctrl-C teardown, speed up 2nd instance startup
[bl2-split] / bl2-splitscreen.sh
index 8406345b6a261f8398b3bbcc14e02e5f8e0bc7de..ed4e04077779abc148b1bbbdf09bf86d78a173de 100755 (executable)
@@ -107,11 +107,47 @@ detect_mode() {
 MODE="$(detect_mode)"
 [ "$MODE" = unknown ] && die "Cannot find BL2 - neither $NATIVE_BIN nor $WIN_EXE_REL under $GAME_DIR"
 if [ -z "$ISOLATION" ]; then ISOLATION=bwrap; fi   # device-masking works for both modes
 MODE="$(detect_mode)"
 [ "$MODE" = unknown ] && die "Cannot find BL2 - neither $NATIVE_BIN nor $WIN_EXE_REL under $GAME_DIR"
 if [ -z "$ISOLATION" ]; then ISOLATION=bwrap; fi   # device-masking works for both modes
-GOLDBERG_SO="$BASE/goldberg/libsteam_api.so"    # native
-GOLDBERG_DLL="$BASE/goldberg/steam_api.dll"     # proton
+GOLDBERG_SO="$BASE/goldberg/libsteam_api.so"    # native (classic Mr_Goldberg)
+GOLDBERG_DLL="$BASE/goldberg/steam_api.dll"     # proton (gbe_fork)
+
+# Goldberg emulator downloads (NOT committed to git - fetched by `fetch`). Pinned
+# to known-good releases; override via env if these ever move.
+GBE_FORK_TAG="${GBE_FORK_TAG:-release-2026_05_30}"
+GBE_FORK_WIN_URL="${GBE_FORK_WIN_URL:-https://github.com/Detanup01/gbe_fork/releases/download/$GBE_FORK_TAG/emu-win-release.7z}"
+CLASSIC_GOLDBERG_URL="${CLASSIC_GOLDBERG_URL:-https://gitlab.com/Mr_Goldberg/goldberg_emulator/uploads/2524331e488ec6399c396cf48bbe9903/Goldberg_Lan_Steam_Emu_v0.2.5.zip}"
 
 resolve_pad() { local m; for m in $1; do [ -e "$m" ] && { readlink -f "$m"; return; }; done; }
 
 
 resolve_pad() { local m; for m in $1; do [ -e "$m" ] && { readlink -f "$m"; return; }; done; }
 
+# --------------------------------------------------------------- fetch deps -
+# Download the Goldberg emulator binaries (kept out of git). Proton needs the
+# gbe_fork Windows steam_api.dll; native needs the classic Mr_Goldberg Linux .so.
+cmd_fetch() {  # downloads the Goldberg binary the detected MODE needs
+  command -v bsdtar >/dev/null || die "bsdtar missing (sudo pacman -S libarchive)"
+  command -v curl   >/dev/null || die "curl missing (sudo pacman -S curl)"
+  mkdir -p "$BASE/goldberg"
+  local tmp; tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN
+
+  if [ "$MODE" = proton ]; then
+    if [ -f "$GOLDBERG_DLL" ]; then ok "gbe_fork steam_api.dll already present"; return; fi
+    say "Downloading gbe_fork (Windows) $GBE_FORK_TAG ..."
+    curl -fsSL -o "$tmp/win.7z" "$GBE_FORK_WIN_URL" || die "download failed: $GBE_FORK_WIN_URL"
+    bsdtar -xf "$tmp/win.7z" -C "$tmp" release/experimental/x86/steam_api.dll \
+      || die "could not extract steam_api.dll from the archive"
+    cp "$tmp/release/experimental/x86/steam_api.dll" "$GOLDBERG_DLL"
+    file "$GOLDBERG_DLL" | grep -qi "PE32 .*i386" && ok "steam_api.dll (proton) fetched" \
+      || die "fetched steam_api.dll is not the expected 32-bit PE"
+  else
+    if [ -f "$GOLDBERG_SO" ]; then ok "classic goldberg libsteam_api.so already present"; return; fi
+    say "Downloading classic Mr_Goldberg (Linux) ..."
+    curl -fsSL -o "$tmp/lin.zip" "$CLASSIC_GOLDBERG_URL" || die "download failed: $CLASSIC_GOLDBERG_URL"
+    bsdtar -xf "$tmp/lin.zip" -C "$tmp" linux/x86/libsteam_api.so \
+      || die "could not extract libsteam_api.so from the archive"
+    cp "$tmp/linux/x86/libsteam_api.so" "$GOLDBERG_SO"
+    file "$GOLDBERG_SO" | grep -q "ELF 32-bit" && ok "libsteam_api.so (native) fetched" \
+      || die "fetched libsteam_api.so is not the expected 32-bit ELF"
+  fi
+}
+
 # ------------------------------------------------------------------ check ---
 cmd_check() {
   local fail=0
 # ------------------------------------------------------------------ check ---
 cmd_check() {
   local fail=0
@@ -175,6 +211,13 @@ EOF
   # other via UDP broadcast, so co-op hangs at "Creating online session". Tell the
   # emu to send discovery directly to localhost so they always find each other.
   printf '127.0.0.1\n' > "$ss/custom_broadcasts.txt"
   # other via UDP broadcast, so co-op hangs at "Creating online session". Tell the
   # emu to send discovery directly to localhost so they always find each other.
   printf '127.0.0.1\n' > "$ss/custom_broadcasts.txt"
+  # BL2 stalls on "Creating online session" waiting on (unreachable) online Steam
+  # servers; offline mode makes the emu report Steam offline so it skips that wait
+  # and goes to LAN. LAN co-op still works. Remove this file if it ever breaks join.
+  cat > "$ss/configs.main.ini" <<EOF
+[main::connectivity]
+offline=1
+EOF
 }
 
 # Old games hard-crash if the emu returns interfaces they don't expect. Extract
 }
 
 # Old games hard-crash if the emu returns interfaces they don't expect. Extract
@@ -215,6 +258,7 @@ build_player() {  # $1=n  $2=account  $3=steamid
 }
 
 cmd_setup() {
 }
 
 cmd_setup() {
+  cmd_fetch                                     # download Goldberg if not present
   REQUIRE_PADS=0 cmd_check || die "check failed - resolve the above before setup"
   say ""; say "== Building player copies ($MODE) =="
   build_player 1 "Player1" "76561197960287930"
   REQUIRE_PADS=0 cmd_check || die "check failed - resolve the above before setup"
   say ""; say "== Building player copies ($MODE) =="
   build_player 1 "Player1" "76561197960287930"
@@ -295,7 +339,12 @@ launch_player() {  # $1=n  $2=pad-node  $3=vidpid  -> sets LAUNCHED_PID
   else
     wd="$pg/Binaries/Win32"
     patch_proton_config "$BASE/p$n/prefix"     # skip intro movies (if prefix exists)
   else
     wd="$pg/Binaries/Win32"
     patch_proton_config "$BASE/p$n/prefix"     # skip intro movies (if prefix exists)
-    env=( "WINEPREFIX=$BASE/p$n/prefix" "GAMEID=0" "STORE=none" )
+    mkdir -p "$BASE/dxvk-cache"
+    env=( "WINEPREFIX=$BASE/p$n/prefix" "GAMEID=0" "STORE=none"
+          "UMU_RUNTIME_UPDATE=0"                # skip umu's startup update check
+          # Shared D3D shader cache: the 2nd instance reuses shaders the 1st already
+          # compiled instead of recompiling them while the 1st is using the GPU.
+          "DXVK_STATE_CACHE_PATH=$BASE/dxvk-cache" )
     # Only use the SDL VID/PID filter in "sdl" mode. With bwrap masking it is
     # redundant AND can over-filter (it wrongly dropped the 360 pad), so skip it.
     [ "$ISOLATION" = sdl ] && env+=( "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT=$vidpid" )
     # Only use the SDL VID/PID filter in "sdl" mode. With bwrap masking it is
     # redundant AND can over-filter (it wrongly dropped the 360 pad), so skip it.
     [ "$ISOLATION" = sdl ] && env+=( "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT=$vidpid" )
@@ -390,18 +439,47 @@ cmd_run() {
   say "${c_dim}Windows settle onto $where ~${SPLASH_WAIT}s in (after the splash)."
   say "Re-place any time with:  ./bl2-splitscreen.sh tile${c_rst}"
   say ""; say "Press Ctrl-C to kill both instances."
   say "${c_dim}Windows settle onto $where ~${SPLASH_WAIT}s in (after the splash)."
   say "Re-place any time with:  ./bl2-splitscreen.sh tile${c_rst}"
   say ""; say "Press Ctrl-C to kill both instances."
-  trap 'kill "$pid1" "$pid2" 2>/dev/null' INT TERM
+  # Killing $pid1/$pid2 only hits the top of each tree; the game runs deeper inside
+  # umu/pressure-vessel and survives. kill_instances tears down the whole tree.
+  trap 'echo; kill_instances; exit 130' INT TERM
   wait "$pid1" "$pid2"
 }
 
   wait "$pid1" "$pid2"
 }
 
+# ------------------------------------------------------------------ kill ----
+# Terminate every Borderlands 2 process we launched (game exe + umu/Proton/Wine
+# tree for our prefixes), SIGTERM first then SIGKILL. Scoped to our BASE dir so it
+# won't touch unrelated Wine apps.
+kill_instances() {
+  local sig
+  for sig in TERM KILL; do
+    pkill -"$sig" -f 'Borderlands2\.exe'   2>/dev/null   # proton game
+    pkill -"$sig" -x 'Borderlands2'        2>/dev/null   # native game
+    pkill -"$sig" -f "$BASE/p[12]/"        2>/dev/null   # umu/proton/wine for our copies+prefixes
+    [ "$sig" = TERM ] && sleep 2
+  done
+}
+
+cmd_kill() {
+  # Match the process NAME (comm) as a substring, not the full cmdline - otherwise
+  # helper shells that merely have the game path in their args get counted. This
+  # matches "Borderlands2.ex" (Proton, 15-char truncated) and "Borderlands2".
+  pgrep 'Borderlands2' >/dev/null || { ok "No Borderlands 2 instances running."; return 0; }
+  say "Killing Borderlands 2 instances..."
+  kill_instances
+  local left; left=$(pgrep -c 'Borderlands2' 2>/dev/null || echo 0)
+  [ "$left" = 0 ] && ok "All instances killed." || warn "$left process(es) still lingering."
+}
+
 # ------------------------------------------------------------------ clean ---
 # ------------------------------------------------------------------ clean ---
-cmd_clean() { say "Removing generated copies/saves (real game untouched)..."; rm -rf "$BASE/p1" "$BASE/p2"; ok "Done."; }
+cmd_clean() { cmd_kill; say "Removing generated copies/saves (real game untouched)..."; rm -rf "$BASE/p1" "$BASE/p2"; ok "Done."; }
 
 case "${1:-}" in
 
 case "${1:-}" in
+  fetch) cmd_fetch ;;
   check) cmd_check ;;
   setup) cmd_setup ;;
   run)   cmd_run ;;
   tile)  cmd_tile ;;
   check) cmd_check ;;
   setup) cmd_setup ;;
   run)   cmd_run ;;
   tile)  cmd_tile ;;
+  kill)  cmd_kill ;;
   clean) cmd_clean ;;
   clean) cmd_clean ;;
-  *) say "Usage: $0 {check|setup|run|tile|clean}   (MODE=native|proton to force)"; exit 1 ;;
+  *) say "Usage: $0 {fetch|check|setup|run|tile|kill|clean}   (MODE=native|proton to force)"; exit 1 ;;
 esac
 esac