X-Git-Url: https://vilimpoc.org/repos/bl2-split/blobdiff_plain/cb05cf8e0e0cab3cbd1b77f233aaedbd30ecffa2..HEAD:/README.md?ds=inline diff --git a/README.md b/README.md index b94f4c7..0e9eed8 100644 --- a/README.md +++ b/README.md @@ -1,142 +1,153 @@ -# Borderlands 2 — 2-player split screen (Linux / KDE) +# Borderlands 2 — 2-player split-screen / dual-monitor co-op on Linux -Borderlands 2 has **no built-in PC split screen**. This runs **two copies** of the -game side-by-side that connect over Goldberg's emulated Steam LAN — each with its -own save, its own identity, and its own controller — auto-tiled on KDE (KWin). +Runs **two local instances** of Borderlands 2 that connect to each other over +Goldberg's emulated Steam LAN, so two people can play co-op on one PC — each with +their own controller, save, and screen. Tested on **Arch + KDE Plasma 6 (Wayland)**. -**Status: working & validated on this machine.** Both instances start, initialize -Steamworks + engine, and run simultaneously. Goldberg is already downloaded and -staged; `setup` has been run. You just need both controllers on, then `run`. +- **Two monitors:** one instance fullscreen on each (auto-detected). +- **One monitor:** side-by-side split. +- Each Xbox controller drives only its own instance. -## What this install is, and why the setup looks the way it does +> BL2 has no native PC split-screen; this launches two copies networked over LAN. +> Only legitimate because you own the game. No game files or saves are committed here. -Your BL2 is the **native Aspyr Linux build** (a 2012, 32-bit `Borderlands2` ELF) — -not the Windows/Proton version. Getting split screen out of it took solving three -real problems, all handled by the script: +![Two Borderlands 2 instances running co-op across two monitors, each on its own main menu in the shared LAN lobby with per-player Xbox controller prompts](sxs.jpg) -1. **The binary is too old for modern system libs** → it's launched inside the - **Steam scout runtime** (`ubuntu12_32/steam-runtime/run.sh`), which ships the - old libraries it expects. -2. **gbe_fork (the modern Goldberg) segfaults this binary** → we use the - **classic Mr_Goldberg 0.2.5** emulator instead (`goldberg/libsteam_api.so`). - gbe_fork's experimental *and* regular builds both crash it at a fixed address - right after Steam init; classic goldberg works. -3. **The old binary crashes if the emu returns unexpected Steam interfaces** → at - setup we extract the exact interface versions from the game's original - `libsteam_api.so` into each copy's `steam_settings/steam_interfaces.txt`. +## Requirements -A Windows **proton** mode is also supported (see below); the script auto-detects -which build is installed. +- **Borderlands 2 installed via Steam, forced to the Windows build under Proton** + (see setup step 1). The native Linux (Aspyr) build is supported as a fallback but + is crash-prone with two instances — Proton is the recommended path. +- Packages: `umu-launcher`, `gamescope` *(optional)*, `qt6-tools` (for `qdbus6`), + `bubblewrap`, `libarchive` (`bsdtar`), `curl`, `binutils` (`strings`). + ``` + sudo pacman -S umu-launcher qt6-tools bubblewrap libarchive curl binutils + ``` +- *Optional:* `gamemode` (+ `lib32-gamemode`) — the script runs each game under + `gamemoderun` if present (performance CPU governor while playing). Not required. +- Two Xbox controllers (any two distinguishable pads). -## Playing (native — your current install) +## Setup -``` -cd /home/max/Games/bl2-split -# 1. QUIT STEAM COMPLETELY (see why below) -# 2. turn on BOTH controllers (the wireless 360 pad idle-sleeps — wake it) -./bl2-splitscreen.sh run -``` +1. **Point Steam at the Windows build.** Steam → *Borderlands 2 → Properties → + Compatibility →* tick **"Force the use of a specific Steam Play compatibility + tool"** → pick **Proton Experimental** (or a stable Proton). Steam re-downloads + the Windows depot. (To use the native Linux build instead, skip this — the script + auto-detects which is installed.) + +2. **Build the two copies:** + ``` + cd bl2-split + ./bl2-splitscreen.sh check # verifies prerequisites; shows detected mode + ./bl2-splitscreen.sh setup # downloads Goldberg + builds p1/ and p2/ + ``` + `setup` auto-downloads the Goldberg emulator (it is **not** committed to this + repo) and builds two symlink-mirror copies of the game with per-player Steam + identities. First `run` also makes umu download Proton + build each prefix (slow, + one time). -> **Quit Steam first.** While Steam runs, Steam Input creates *virtual* gamepads -> that the game grabs instead of the real pads — which breaks per-player controller -> isolation (and can leave input not working). Goldberg replaces Steam, so you don't -> need it running. The script warns and pauses if it detects Steam. - -- Two game windows open (no gamescope). During the intro logos they look - unaligned — that's expected; **~45 s in (after the splash) they snap to their - halves.** The delay is because the game resizes itself during the splash, so - tiling earlier just gets overridden. -- The split is computed in **logical** pixels (your 2560×1440 panel at 125% scale - = 2048×1152 logical → two 1024×1152 halves), so it lands right despite scaling. -- **Player 1** (Xbox Series S|X pad): *Play → host over LAN*. -- **Player 2** (Xbox 360 wireless pad): *Play → Join → pick the LAN game*. -- Each window is pinned to one controller and, thanks to SDL background events, - responds even when the other window has focus. -- Re-tile any time (e.g. after both reach the menu): `./bl2-splitscreen.sh tile`. -- Snapping too early (still on the splash)? Raise the wait: `SPLASH_WAIT=70 ./bl2-splitscreen.sh run`. -- `Ctrl-C` in the terminal kills both instances. - -If you change hardware or want to rebuild the copies: `./bl2-splitscreen.sh setup`. +3. **Play** (turn on both controllers first): + ``` + ./bl2-splitscreen.sh run + ``` + Both instances launch at once. While they load, **wiggle the mouse** until both + reach the main menu (the script prints a loud reminder), and leave the windows + alone — after ~30 s the script moves each onto its screen (fullscreen per monitor + with two displays, side-by-side with one). In-game: **Player 1** *Play → host over + LAN*; **Player 2** *Play → Join → pick the LAN game*. `Ctrl-C` kills both. ## Commands | Command | Does | |---|---| -| `./bl2-splitscreen.sh check` | verify prerequisites for the detected mode | -| `./bl2-splitscreen.sh setup` | build the two patched game copies (already done) | -| `./bl2-splitscreen.sh run` | launch both halves + tile | -| `./bl2-splitscreen.sh tile` | (re)tile the two windows on KDE | -| `./bl2-splitscreen.sh clean` | remove generated copies/saves (real game untouched) | +| `./bl2-splitscreen.sh fetch` | download the Goldberg emulator into `goldberg/` | +| `./bl2-splitscreen.sh check` | verify prerequisites; print the detected mode | +| `./bl2-splitscreen.sh setup` | fetch Goldberg + build `p1/`, `p2/` | +| `./bl2-splitscreen.sh run` | launch both instances + place windows | +| `./bl2-splitscreen.sh tile` | re-place the two windows | +| `./bl2-splitscreen.sh kill` | terminate all running Borderlands 2 instances | +| `./bl2-splitscreen.sh clean` | kill instances, then remove `p1/`, `p2/` (copies + saves + prefixes) | + +Useful env overrides: `MODE=native|proton`, `DISPLAY_MODE=dual|split`, +`ISOLATION=bwrap|sdl`, `SPLASH_WAIT=`, `STAGGER=`. -## How each piece works +## How it works | Concern | Solution | |---|---| -| Two instances at once | Each copy uses classic Mr_Goldberg (no Steam needed); they LAN together | -| Runs the ancient binary | Launched via the Steam scout runtime | -| No crash on Steam init | `steam_interfaces.txt` extracted from the original lib, per copy | -| Separate saves | Per-player `HOME` (`p1/home`, `p2/home`) — also isolates each `~/.steam` | -| Two different LAN players | `force_account_name.txt` + `force_steamid.txt` per copy | -| Half-screen windows | Game runs windowed at the **logical** half size (`-ResX/-ResY` + config), **no gamescope** — gamescope doesn't pass raw controllers to nested clients, so the SDL game would see no pad | -| Correct size on a scaled display | Split computed from KDE's **logical** screen size (physical ÷ scale), auto-detected via `kscreen-doctor`. Using physical px tiles at the wrong fraction (2/3 + 1/3 at 125%) | -| Both windows get input | `SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS=1` (SDL otherwise ignores the pad of an unfocused window) | -| One controller per instance | `bwrap` masks the other pad's `/dev/input` nodes — reliable only with Steam quit (else Steam Input's virtual pads leak through) | -| Side-by-side placement | KWin script (`qdbus6`) sizes each window to a logical half + removes borders, fired **after** the splash so the game has stopped resizing itself | -| No 13 GB duplication | `cp -as` symlink-mirror; only the Steam API lib is a real copy | - -> The game statically links an **old SDL2** (pre-2.0.9), so it reads controllers via -> SDL/udev, needs `ALLOW_BACKGROUND_EVENTS` for split focus, and can't use SDL's -> newer per-VID/PID filtering — hence bwrap for isolation. - -## Switching to the Windows / Proton version (optional) - -If the native port misbehaves, the Windows build under Proton is an alternative: - -1. Steam → **Borderlands 2 → Properties → Compatibility →** *Force a specific - Steam Play compatibility tool* → e.g. Proton Experimental. -2. Steam re-downloads the **Windows** depot (`Borderlands2.exe`). -3. Re-run the script — it auto-detects **proton** mode and uses the already-staged - Windows `goldberg/steam_api.dll` (gbe_fork) via `umu-run`: - ``` - ./bl2-splitscreen.sh check # mode: proton - ./bl2-splitscreen.sh setup && ./bl2-splitscreen.sh run - ``` - First launch downloads Proton + builds each Wine prefix (slow, one time). +| Two instances at once | Each copy uses **Goldberg** (`steam_api.dll`) instead of real Steam; they connect over emulated LAN | +| Goldberg actually loads | The `.exe` is copied **real** (not symlinked) so Wine loads DLLs from our dir, not the real install's `steam_api.dll` | +| Two different LAN players | Per-copy `force_account_name.txt` + `force_steamid.txt` | +| No startup crash | `steam_interfaces.txt` extracted from the original Steam lib per copy | +| Separate saves | Each instance has its own Proton prefix (`p1/prefix`, `p2/prefix`) | +| Co-op finds each other | Goldberg `custom_broadcasts.txt` → `127.0.0.1` (broadcast doesn't cross the two Proton containers) | +| Skips slow "Creating online session" | Goldberg `configs.main.ini` → `offline=1` | +| Windowed at the right size | `-windowed -ResX -ResY`; **no gamescope** (it hides raw controllers from nested clients) | +| One controller per instance | **bwrap** masks the other pad's `/dev/input` nodes (the Xbox pads are evdev-only, no hidraw) | +| Window placement | KWin script via `qdbus6` — one monitor each (dual) or side-by-side (single) | +| No intro-logo wait | Intro movies disabled in each prefix's `WillowEngine.ini` | +| No 13 GB duplication | `cp -as` symlink-mirror; only `steam_api.dll` + the `.exe` are real copies | + +## What's in git (and what isn't) + +**Committed** (enough for anyone to reproduce): +``` +bl2-splitscreen.sh the launcher/setup script +README.md this file +.gitignore +goldberg/.gitkeep empty dir; setup downloads binaries into it +p1/.gitkeep p2/.gitkeep +``` -> Native and Windows builds share the same folder, so only one is installed at a -> time. Switching modes = switching what Steam has downloaded. +**Never committed** (`.gitignore`): +- `goldberg/steam_api.dll`, `goldberg/libsteam_api.so` — the Goldberg emulator + binaries. They implement Steam's proprietary API, so they aren't redistributed + here; `setup` downloads them from the upstream releases (gbe_fork / Mr_Goldberg). +- `p1/`, `p2/` (except `.gitkeep`) — the game copies, **your save games**, and Wine + prefixes. Machine-specific and large. +- `*.log`. -## Troubleshooting +So another person clones the repo, forces Proton on their own BL2 in Steam, runs +`./bl2-splitscreen.sh setup`, and everything else is pulled/generated locally. -- **They don't see each other on LAN:** let Player 1 host first (the script starts - it ~10 s earlier). Both use classic goldberg's LAN broadcast; each player's own - `~/.steam`/config is isolated via the per-player HOME. If needed, check each - copy's `steam_settings/`. -- **Controllers don't work at all:** make sure **Steam is fully quit** before `run` - (Steam Input's virtual pads block the real ones). Native runs the game *without* - gamescope for this reason — gamescope hides raw controllers from nested clients. -- **Both pads control both screens / wrong window got a controller:** almost always - Steam still running (virtual pads bypass the `bwrap` mask). Quit Steam. Isolation - is `ISOLATION=bwrap` (default); masking real pad nodes only works with Steam off. -- **Halves look uneven (one fills ~2/3, the other ~1/3):** display-scaling mismatch - — the script now auto-detects the *logical* screen size (`kscreen-doctor`), but if - detection fails set it explicitly, e.g. `SCREEN_W=2048 SCREEN_H=1152 ./bl2-splitscreen.sh run`. -- **Windows tile while still on the splash / too early:** raise `SPLASH_WAIT` - (seconds), e.g. `SPLASH_WAIT=70 ./bl2-splitscreen.sh run`, or just re-run - `./bl2-splitscreen.sh tile` once both are at the menu. -- **A game crashes on launch:** confirm `./bl2-splitscreen.sh check` is all green; - the native path needs the scout runtime + classic goldberg + `steam_interfaces.txt` - (all set up by `setup`). -- **Reset everything:** `./bl2-splitscreen.sh clean`. - -## Files +## Troubleshooting -``` -bl2-split/ -├── bl2-splitscreen.sh the launcher -├── goldberg/ -│ ├── libsteam_api.so classic Mr_Goldberg 0.2.5, 32-bit (native mode) -│ └── steam_api.dll gbe_fork, Windows 32-bit (proton mode) -├── p1/ , p2/ per-player: game/ (symlink mirror) + home/ (saves) -└── README.md -``` +- **Both pads control both screens / keyboard controls both:** the games read input + globally. Controllers are isolated by `bwrap` device-masking (default). Keyboard & + mouse are *not* isolated — use a controller per player; kb/mouse is only for menus. +- **A pad drives the wrong screen or none:** ensure `ISOLATION=bwrap` (default). If + **Steam is running**, its Steam Input virtual pads can interfere — quit Steam. +- **Stuck on "Creating online session":** should be quick with `offline=1` (set by + setup). If a co-op *join* ever breaks, remove the offline setting: + `rm p{1,2}/game/Binaries/Win32/steam_settings/configs.main.ini` +- **They don't see each other on LAN:** Player 1 hosts first (the script staggers + launches). Discovery uses `custom_broadcasts.txt` (localhost). +- **The 2nd instance is visible but stuck on the loading screen — and only advances + when you jiggle the mouse.** Root cause: BL2's `bPauseOnLossOfFocus=TRUE` — the + game **pauses itself whenever its window isn't focused**. In split-screen only one + window is focused at a time, so the unfocused instance freezes until an input event + wakes it. `setup`/`run` patch each prefix's `WillowEngine.ini` to + `bPauseOnLossOfFocus=FALSE` so every instance keeps running unfocused. (This was + *not* CPU scaling, entropy, or window occlusion — those were dead ends.) The + instances also share a DXVK shader cache so the 2nd reuses the 1st's compiled + shaders; `STAGGER=45` further separates their loads if a cold first run is slow. +- **Two soundtracks at once:** each instance sets `bMuteAudioWhenNotInFocus=TRUE`, so + only the focused window plays audio — the sound follows whichever instance you're + focused on, instead of both playing together. +- **In-game "Quit Borderlands 2 → Yes" does nothing / hangs:** the game's shutdown + path stalls under Goldberg (Steam-networking cleanup, worse with offline mode). + Don't use in-game Quit — close with **`./bl2-splitscreen.sh kill`** or **Ctrl-C** + in the run terminal (safe: you're at the menu, nothing is saving). +- **Ctrl-C / kill left something running:** `./bl2-splitscreen.sh kill` tears down + the whole game + Proton/Wine tree. +- **A window won't sit on its monitor:** re-run `./bl2-splitscreen.sh tile`; raise + `SPLASH_WAIT` if it fires before the game settles. +- **Reset everything:** `./bl2-splitscreen.sh clean` (removes copies + saves; the + real Steam install is untouched). + +## Native (Aspyr Linux) fallback + +If Steam has the native Linux build installed, the script auto-detects `native` mode: +it uses the **classic Mr_Goldberg** `.so` (gbe_fork crashes that old binary), runs +inside the Steam scout runtime, and isolates controllers the same way. It works, but +the second instance is prone to crashing — prefer the Proton path.