]>
| Commit | Line | Data |
|---|---|---|
| 1 | # Borderlands 2 — 2-player split screen (Linux / KDE) | |
| 2 | ||
| 3 | Borderlands 2 has **no built-in PC split screen**. This runs **two copies** of the | |
| 4 | game side-by-side that connect over Goldberg's emulated Steam LAN — each with its | |
| 5 | own save, its own identity, and its own controller — auto-tiled on KDE (KWin). | |
| 6 | ||
| 7 | **Status: working & validated on this machine.** Both instances start, initialize | |
| 8 | Steamworks + engine, and run simultaneously. Goldberg is already downloaded and | |
| 9 | staged; `setup` has been run. You just need both controllers on, then `run`. | |
| 10 | ||
| 11 | ## What this install is, and why the setup looks the way it does | |
| 12 | ||
| 13 | Your BL2 is the **native Aspyr Linux build** (a 2012, 32-bit `Borderlands2` ELF) — | |
| 14 | not the Windows/Proton version. Getting split screen out of it took solving three | |
| 15 | real problems, all handled by the script: | |
| 16 | ||
| 17 | 1. **The binary is too old for modern system libs** → it's launched inside the | |
| 18 | **Steam scout runtime** (`ubuntu12_32/steam-runtime/run.sh`), which ships the | |
| 19 | old libraries it expects. | |
| 20 | 2. **gbe_fork (the modern Goldberg) segfaults this binary** → we use the | |
| 21 | **classic Mr_Goldberg 0.2.5** emulator instead (`goldberg/libsteam_api.so`). | |
| 22 | gbe_fork's experimental *and* regular builds both crash it at a fixed address | |
| 23 | right after Steam init; classic goldberg works. | |
| 24 | 3. **The old binary crashes if the emu returns unexpected Steam interfaces** → at | |
| 25 | setup we extract the exact interface versions from the game's original | |
| 26 | `libsteam_api.so` into each copy's `steam_settings/steam_interfaces.txt`. | |
| 27 | ||
| 28 | A Windows **proton** mode is also supported (see below); the script auto-detects | |
| 29 | which build is installed. | |
| 30 | ||
| 31 | ## Playing (native — your current install) | |
| 32 | ||
| 33 | ``` | |
| 34 | cd /home/max/Games/bl2-split | |
| 35 | # 1. QUIT STEAM COMPLETELY (see why below) | |
| 36 | # 2. turn on BOTH controllers (the wireless 360 pad idle-sleeps — wake it) | |
| 37 | ./bl2-splitscreen.sh run | |
| 38 | ``` | |
| 39 | ||
| 40 | > **Quit Steam first.** While Steam runs, Steam Input creates *virtual* gamepads | |
| 41 | > that the game grabs instead of the real pads — which breaks per-player controller | |
| 42 | > isolation (and can leave input not working). Goldberg replaces Steam, so you don't | |
| 43 | > need it running. The script warns and pauses if it detects Steam. | |
| 44 | ||
| 45 | - Two game windows open (no gamescope). During the intro logos they look | |
| 46 | unaligned — that's expected; **~45 s in (after the splash) they snap to their | |
| 47 | halves.** The delay is because the game resizes itself during the splash, so | |
| 48 | tiling earlier just gets overridden. | |
| 49 | - The split is computed in **logical** pixels (your 2560×1440 panel at 125% scale | |
| 50 | = 2048×1152 logical → two 1024×1152 halves), so it lands right despite scaling. | |
| 51 | - **Player 1** (Xbox Series S|X pad): *Play → host over LAN*. | |
| 52 | - **Player 2** (Xbox 360 wireless pad): *Play → Join → pick the LAN game*. | |
| 53 | - Each window is pinned to one controller and, thanks to SDL background events, | |
| 54 | responds even when the other window has focus. | |
| 55 | - Re-tile any time (e.g. after both reach the menu): `./bl2-splitscreen.sh tile`. | |
| 56 | - Snapping too early (still on the splash)? Raise the wait: `SPLASH_WAIT=70 ./bl2-splitscreen.sh run`. | |
| 57 | - `Ctrl-C` in the terminal kills both instances. | |
| 58 | ||
| 59 | If you change hardware or want to rebuild the copies: `./bl2-splitscreen.sh setup`. | |
| 60 | ||
| 61 | ## Commands | |
| 62 | ||
| 63 | | Command | Does | | |
| 64 | |---|---| | |
| 65 | | `./bl2-splitscreen.sh check` | verify prerequisites for the detected mode | | |
| 66 | | `./bl2-splitscreen.sh setup` | build the two patched game copies (already done) | | |
| 67 | | `./bl2-splitscreen.sh run` | launch both halves + tile | | |
| 68 | | `./bl2-splitscreen.sh tile` | (re)tile the two windows on KDE | | |
| 69 | | `./bl2-splitscreen.sh clean` | remove generated copies/saves (real game untouched) | | |
| 70 | ||
| 71 | ## How each piece works | |
| 72 | ||
| 73 | | Concern | Solution | | |
| 74 | |---|---| | |
| 75 | | Two instances at once | Each copy uses classic Mr_Goldberg (no Steam needed); they LAN together | | |
| 76 | | Runs the ancient binary | Launched via the Steam scout runtime | | |
| 77 | | No crash on Steam init | `steam_interfaces.txt` extracted from the original lib, per copy | | |
| 78 | | Separate saves | Per-player `HOME` (`p1/home`, `p2/home`) — also isolates each `~/.steam` | | |
| 79 | | Two different LAN players | `force_account_name.txt` + `force_steamid.txt` per copy | | |
| 80 | | 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 | | |
| 81 | | 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%) | | |
| 82 | | Both windows get input | `SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS=1` (SDL otherwise ignores the pad of an unfocused window) | | |
| 83 | | 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) | | |
| 84 | | 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 | | |
| 85 | | No 13 GB duplication | `cp -as` symlink-mirror; only the Steam API lib is a real copy | | |
| 86 | ||
| 87 | > The game statically links an **old SDL2** (pre-2.0.9), so it reads controllers via | |
| 88 | > SDL/udev, needs `ALLOW_BACKGROUND_EVENTS` for split focus, and can't use SDL's | |
| 89 | > newer per-VID/PID filtering — hence bwrap for isolation. | |
| 90 | ||
| 91 | ## Switching to the Windows / Proton version (optional) | |
| 92 | ||
| 93 | If the native port misbehaves, the Windows build under Proton is an alternative: | |
| 94 | ||
| 95 | 1. Steam → **Borderlands 2 → Properties → Compatibility →** *Force a specific | |
| 96 | Steam Play compatibility tool* → e.g. Proton Experimental. | |
| 97 | 2. Steam re-downloads the **Windows** depot (`Borderlands2.exe`). | |
| 98 | 3. Re-run the script — it auto-detects **proton** mode and uses the already-staged | |
| 99 | Windows `goldberg/steam_api.dll` (gbe_fork) via `umu-run`: | |
| 100 | ``` | |
| 101 | ./bl2-splitscreen.sh check # mode: proton | |
| 102 | ./bl2-splitscreen.sh setup && ./bl2-splitscreen.sh run | |
| 103 | ``` | |
| 104 | First launch downloads Proton + builds each Wine prefix (slow, one time). | |
| 105 | ||
| 106 | > Native and Windows builds share the same folder, so only one is installed at a | |
| 107 | > time. Switching modes = switching what Steam has downloaded. | |
| 108 | ||
| 109 | ## Troubleshooting | |
| 110 | ||
| 111 | - **They don't see each other on LAN:** let Player 1 host first (the script starts | |
| 112 | it ~10 s earlier). Both use classic goldberg's LAN broadcast; each player's own | |
| 113 | `~/.steam`/config is isolated via the per-player HOME. If needed, check each | |
| 114 | copy's `steam_settings/`. | |
| 115 | - **Controllers don't work at all:** make sure **Steam is fully quit** before `run` | |
| 116 | (Steam Input's virtual pads block the real ones). Native runs the game *without* | |
| 117 | gamescope for this reason — gamescope hides raw controllers from nested clients. | |
| 118 | - **Both pads control both screens / wrong window got a controller:** almost always | |
| 119 | Steam still running (virtual pads bypass the `bwrap` mask). Quit Steam. Isolation | |
| 120 | is `ISOLATION=bwrap` (default); masking real pad nodes only works with Steam off. | |
| 121 | - **Halves look uneven (one fills ~2/3, the other ~1/3):** display-scaling mismatch | |
| 122 | — the script now auto-detects the *logical* screen size (`kscreen-doctor`), but if | |
| 123 | detection fails set it explicitly, e.g. `SCREEN_W=2048 SCREEN_H=1152 ./bl2-splitscreen.sh run`. | |
| 124 | - **Windows tile while still on the splash / too early:** raise `SPLASH_WAIT` | |
| 125 | (seconds), e.g. `SPLASH_WAIT=70 ./bl2-splitscreen.sh run`, or just re-run | |
| 126 | `./bl2-splitscreen.sh tile` once both are at the menu. | |
| 127 | - **A game crashes on launch:** confirm `./bl2-splitscreen.sh check` is all green; | |
| 128 | the native path needs the scout runtime + classic goldberg + `steam_interfaces.txt` | |
| 129 | (all set up by `setup`). | |
| 130 | - **Reset everything:** `./bl2-splitscreen.sh clean`. | |
| 131 | ||
| 132 | ## Files | |
| 133 | ||
| 134 | ``` | |
| 135 | bl2-split/ | |
| 136 | ├── bl2-splitscreen.sh the launcher | |
| 137 | ├── goldberg/ | |
| 138 | │ ├── libsteam_api.so classic Mr_Goldberg 0.2.5, 32-bit (native mode) | |
| 139 | │ └── steam_api.dll gbe_fork, Windows 32-bit (proton mode) | |
| 140 | ├── p1/ , p2/ per-player: game/ (symlink mirror) + home/ (saves) | |
| 141 | └── README.md | |
| 142 | ``` |