# Borderlands 2 — 2-player split screen (Linux / KDE)

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).

**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`.

## What this install is, and why the setup looks the way it does

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:

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`.

A Windows **proton** mode is also supported (see below); the script auto-detects
which build is installed.

## Playing (native — your current install)

```
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
```

> **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`.

## 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) |

## How each piece 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).

> Native and Windows builds share the same folder, so only one is installed at a
> time. Switching modes = switching what Steam has downloaded.

## Troubleshooting

- **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

```
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
```
