1 # Borderlands 2 — 2-player split screen (Linux / KDE)
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).
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`.
11 ## What this install is, and why the setup looks the way it does
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:
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`.
28 A Windows **proton** mode is also supported (see below); the script auto-detects
29 which build is installed.
31 ## Playing (native — your current install)
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
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.
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.
59 If you change hardware or want to rebuild the copies: `./bl2-splitscreen.sh setup`.
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) |
71 ## How each piece works
73 | Concern | Solution |
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 |
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.
91 ## Switching to the Windows / Proton version (optional)
93 If the native port misbehaves, the Windows build under Proton is an alternative:
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`:
101 ./bl2-splitscreen.sh check # mode: proton
102 ./bl2-splitscreen.sh setup && ./bl2-splitscreen.sh run
104 First launch downloads Proton + builds each Wine prefix (slow, one time).
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.
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`.
136 ├── bl2-splitscreen.sh the launcher
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)