Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Battlefield 3 Alpha and Beta on Linux

Runs the 2011 Battlefield 3 Alpha Trial and Open Beta on Linux, in rootless Podman containers, with my from-scratch reimplementation of EA's Blaze backend so the games have something to connect to.

Built to use Wine and DXVK. You need the AIO repack archive; this repository is the tooling that runs it.

Battlefield 3 Open Beta running on Linux, with the dedicated server's log in a terminal behind the game window

Operation Métro on the Open Beta. The terminal is the dedicated server's container log; the game is a second container, and both are talking to the backend in a third.


What you need

Rootless podman and a compose provider: either pip install podman-compose or the docker-compose binary on your PATH. Check with podman compose version.

Battlefield 3 Alpha-Beta AIO Repack v3.7z, in your Downloads folder.

About 11 GB of disk for the games, plus the archive.

AMD and Intel graphics work through /dev/dri and Mesa. NVIDIA needs the NVIDIA container toolkit and a CDI device added to compose.yaml.


Getting started

git clone https://github.com/patrick-brigel/bf3-alpha-beta-linux.git
cd bf3-alpha-beta-linux/deploy
./generate-env.sh
podman compose --profile install up
podman compose --profile beta up

generate-env.sh writes deploy/.env: what it can detect about this machine, plus the settings most people change, at a working default. That file is where this stack is configured. Open it if you want a different map, a faster career or a name of your own; anything else from Settings can be added to it by hand.

--profile install unpacks the repack into three volumes, which takes a few minutes. --profile beta then starts the backend, a dedicated server and your client, in that order, and drops you into a game.

Swap beta for alpha to play the Alpha Trial instead.

Expect the very first beta run to take a couple of minutes longer than later ones: it builds a wine prefix and downloads d3dcompiler_43.


Playing

On your own

podman compose --profile beta up

Everything runs on this machine. Quitting the game takes the dedicated server down with it, since they share a container; the backend keeps running until you stop the compose run with Ctrl+C.

Running the parts separately

podman compose --profile blaze up -d          # backend
podman compose --profile beta-server up       # dedicated server
podman compose --profile beta-client up       # a player

alpha-server and alpha-client do the same for the Alpha Trial. Use these split profiles rather than the all-in-one alpha and beta ones for anything you leave running.

Stop anything started with -d the same way you started it, profile included:

podman compose --profile blaze down

With other people

Everyone has to be on one network with the backend, and two kinds of network work.

Your LAN. Put the backend machine's LAN address in BLAZE_IP, in deploy/.env, on every machine:

BLAZE_IP=192.168.1.50

The host then runs the whole stack, and everyone else runs a client:

podman compose --profile beta up          # on 192.168.1.50
podman compose --profile beta-client up   # everyone else

Each machine needs the repack installed the same way. If the host runs a firewall, let the others in on:

Port Protocol What
42127 TCP the redirector
10041 TCP clients
10071 TCP dedicated servers
25100 / 25200 TCP and UDP the game itself: alpha / beta, plus the next port up for queries

A 192.168.0.x subnet is known to cause join failures; 192.168.1.x works.

An overlay VPN, for anyone who is not in the building. WireGuard, Tailscale, ZeroTier and Netbird all hand every machine a stable private address, and that address goes in BLAZE_IP exactly like a LAN one. Nothing else changes, and no port on anybody's router is opened.

Do not put any of this on the internet directly. There are no passwords anywhere in the backend — the name you log in with is the identity — so a reachable port lets a stranger play as you and overwrite your career.

The backend enforces that rather than trusting you to. By default it listens on BLAZE_IP and nothing else — BLAZE_BIND is the deliberate way to widen that — it hands the dedicated-server role only to peers arriving from a private or overlay address range, and it says so at every startup if BLAZE_IP is a public one.

Two people at one machine

Give each player their own name and their own compose project:

LOGIN_TOKEN=patrick podman compose -p bf3-patrick --profile beta-client up
LOGIN_TOKEN=sven    podman compose -p bf3-sven    --profile beta-client up

Without -p, both players are the same container, and the second up stops and removes the first player's game before starting its own. Stop a client with the same -p you started it with. The separate projects still share one install, one bf3-wine-data volume and one set of careers — but each player gets his own wine prefix inside it, because two clients in one prefix share a wineserver and the game's single-instance lock, and the second would exit at once.

The name the game shows is LOGIN_TOKEN@LOGIN_HOST, so these two are patrick@thinkpad and sven@thinkpad. The same username from two machines, patrick@thinkpad and patrick@workstation, is two players with two careers rather than one career two people overwrite in turn. generate-env.sh puts this machine's name into .env as LOGIN_HOST the first time it runs, and keeps whatever is there afterwards — so if the machine gets renamed, change that line yourself.

More than one server

SERVER_ID picks which server you are starting. Each gets its own wine prefix, instance directory and ports.

podman compose --profile beta-server up
SERVER_ID=2 SERVER_NAME="Second Server" podman compose -p bf3-server2 --profile beta-server up

The -p matters for the same reason it does for a second player: without it both invocations name the same container, and the second stops the first.

Ports go up in twos: 25200, 25202, and so on. Each server also uses the next port up for queries.


Hosting the backend elsewhere

The backend is a small Python image with no wine, no game files and no GPU in it, so a spare box, a NAS or a VPS can run it on its own, and nobody has to install the repack there:

git clone https://github.com/patrick-brigel/bf3-alpha-beta-linux.git
cd bf3-alpha-beta-linux/deploy
./generate-env.sh          # then put this machine's address in BLAZE_IP
podman compose --profile blaze up -d

BLAZE_IP is the address the players reach it at, and the only one it listens on. On a VPS that means the machine's overlay address, not its public one: join it to the same WireGuard or Tailscale network as everybody else and it behaves exactly like a box in the room.

generate-env.sh warns that the repack is missing and that it found no display, Wayland or X. Both are fine here: the backend uses neither.

Whoever runs a dedicated server has to be on that same network. That role — creating games, and submitting the round reports that write careers — is granted by where a peer connects from. There is nothing else to grant it with: the 2011 dedicated server offers no credential of any kind. On an overlay every machine qualifies, which is the point of using one.

Everyone's career lives on this machine, in the bf3-blaze-data volume. Read it with the commands under Your career.

Both builds' stat lists, rank tables and unlock ladders are built into the backend image. They were read out of the game data itself, not out of the stat list the repack ships — that one is retail's, and fits neither build. So a backend that has never seen the archive records exactly what the games define. See Stat definitions in blaze/README.md.

To update the backend later:

git pull
podman compose --profile blaze build
podman compose --profile blaze up -d

Choosing a server to join

GAME_ID=1 means matchmaking: you land on any running server of that build, picked at random. This is the default and usually what you want.

To join one specific server, give its game id:

podman compose --profile blaze up -d
podman compose --profile blaze run --rm blaze --show games
game title    started                players  server / map / mode
----------------------------------------------------------------------------------------
   3 beta     2026-08-29T14:12:04+00:00       2  Second Server (MP_007 / ConquestLarge) [running]
   2 beta     2026-08-29T14:11:38+00:00       0  BF3 (patrick) (MP_007 / ConquestLarge) [running]
GAME_ID=3 podman compose --profile beta-client up

Ids count up and are never reused. Naming one that is not running is refused rather than silently swapped for another server.


Maps and modes

The beta ships two maps:

BF3_LEVEL=Levels/MP_007/MP_007 BF3_GAMEMODE=ConquestLarge0 podman compose --profile beta up
BF3_LEVEL=Levels/MP_Subway/MP_Subway BF3_GAMEMODE=RushLarge0 podman compose --profile beta up

MP_007 is Caspian Border, MP_Subway is Operation Métro. Modes are ConquestLarge0, RushLarge0, TeamDeathmatch0 and SquadRush0.

Map and mode are per machine for the beta, not per server: both beta servers on one host read the same config file. The Alpha Trial server takes them on its command line instead, so there they follow whatever started it.

The Alpha Trial has Operation Métro only.


Your career

Stats, unlocks, ranks and awards are recorded from the round reports the dedicated server submits. Alpha and Beta are separate careers, and so are two machines. The commands below take the bare username and find every career it stands for:

podman compose --profile blaze run --rm blaze --show summary   # totals per game
podman compose --profile blaze run --rm blaze --show players   # everyone
podman compose --profile blaze run --rm blaze --show patrick   # one player
podman compose --profile blaze run --rm blaze --show patrick --title beta
podman compose --profile blaze run --rm blaze --show games     # game history

Weapons and attachments unlock as you earn them. To skip that:

podman compose --profile blaze run --rm blaze --unlock patrick   # write a maxed career
BF3_ALL_UNLOCKS=1 podman compose --profile beta up      # ignore the career

The first is permanent and survives into later sessions. The second only affects the session it starts.


Settings

deploy/.env is where these live, and ./generate-env.sh writes it with the ones worth changing already in it. Putting a variable in front of a command still works, for a one-off or for the two cases that are per-invocation by nature — a second player, and a second server.

Variable Default Meaning
BLAZE_IP 127.0.0.1 Address the backend is reached at, and the only one it listens on
LOGIN_TOKEN your username Your half of the player name
LOGIN_HOST this machine's name The other half: the player name is LOGIN_TOKEN@LOGIN_HOST
GAME_ID 1 1 matchmakes; a real id joins that game
SERVER_ID 1 Which dedicated server this is
SERVER_NAME BF3 (your username) Shown in the server list
ADMIN_PASSWORD unset RCON password; unset configures no remote admin
BF3_LEVEL build default; alpha falls back to Levels/MP_Subway/MP_Subway Map
BF3_GAMEMODE build default; alpha falls back to RushLarge0 Game mode
BF3_ALL_UNLOCKS 0 1 treats everything as owned
BF3_CLIENT_EXTRA unset Extra settings for the Beta client, appended to its command line as written
BF3_SCORE_FACTOR 25 Beta only: multiplies every score awarded (the build ships at 2)
BF3_GAME_REPORTS 1 Beta only: 0 stops stats being recorded
BF3_SERVER_WINDOW 0 1 shows the dedicated server's own window
BF3_GRAPHICS_DRIVER auto How the client draws: auto, wayland, or x11 to go through XWayland and get your window manager's titlebar
SERVER_SETTLE 15 Seconds to wait before the client joins
GAME_PORT 25100 / 25200 Game port
BF3_ARCHIVE_DIR ~/Downloads Where the repack archive is
BF3_ARCHIVE_NAME the repack's own name Use a differently named archive in that directory
BF3_CPU_LIMIT 8 Hardware threads the game is shown. The one setting .env is not read for: put it in front of the script, BF3_CPU_LIMIT=4 ./generate-env.sh
BF3_REPACK_URL unset Download the archive from here
BF3_REPACK_SHA256 unset Checked against the downloaded archive
BF3_REPACK_PASSWORD the repack's own Archive password
BF3_SKIP_CHECKSUM 0 1 unpacks without checking the archive's size and hash
BF3_FORCE_INSTALL 0 1 runs the installer past its "already populated" exit
WINEFSYNC / WINEESYNC 1 wine-staging's fast synchronisation; turning both off serialises the game
WINEDEBUG -all wine tracing
BLAZE_LOG INFO Backend log level
BLAZE_BIND BLAZE_IP Listen on something other than the announced address

Reinstalling and cleaning up

The installer skips any volume that already carries its .installed marker. BF3_FORCE_INSTALL=1 only gets it past the "every volume is already populated" exit, so it fills the volumes that are missing, not the ones that are there:

BF3_FORCE_INSTALL=1 podman compose --profile install up

To redo a volume that is intact, take that volume away first:

podman volume rm bf3-beta          # nothing is holding it
podman compose --profile install up

To take it all off the machine — the containers, the games, the wine prefixes and the images built to run them:

cd deploy
./uninstall.sh              # careers are kept
./uninstall.sh --dry-run    # print what would go, remove nothing
./uninstall.sh --yes        # do not ask first; needed where there is no terminal

It names every volume and image it removes and asks before it starts. Careers are the one thing here that cannot be reinstalled, so they survive unless you say otherwise:

./uninstall.sh --careers      # also erase everyone's rank, unlocks and stats
./uninstall.sh --keep-images  # leave the built images in place

Reinstall afterwards with podman compose --profile install up.

Every build also tags a new image and leaves the previous one untagged, so an afternoon of changing the backend leaves dozens behind:

cd deploy
./clean-up.sh              # untagged leftovers
./clean-up.sh --dry-run    # print what would go, remove nothing
./clean-up.sh --containers # also this stack's exited containers
./clean-up.sh --images     # also its own tagged images, forcing a rebuild

Never run podman volume prune. It takes every volume no container is holding, which is usually all of them: the games, the wine prefixes and every career. Neither script here does that — clean-up.sh never touches a volume, and uninstall.sh removes only the ones it names: four, or five with --careers.


Reading the logs

The dedicated server's own log goes to the container log:

podman compose --profile beta-server logs beta-server
[UTC ...] Engine/Game/Server/Core/Server.cpp(238): "GameLoop": Info: Creating server...
[UTC ...] BlazeSDK(0): "GameLoop": Info: <- resp: ID[13], UtilComponent::ping

Under the all-in-one alpha and beta profiles the same lines arrive with a [server] prefix, so you can tell them from the client's.

The Alpha Trial's server writes no log at all. BF3_SERVER_WINDOW=1 puts its console window back, which works with the alpha and beta profiles and not with alpha-server or beta-server:

BF3_SERVER_WINDOW=1 podman compose --profile alpha up

The backend log carries games registering, players joining and leaving, and round reports landing:

podman compose --profile blaze logs blaze

How the game reaches your screen

Wayland wherever there is one. The client uses wine's own Wayland driver and talks to your compositor directly, so no X server is in the picture. Nothing to configure: an X11 desktop has no Wayland socket to find, and the X session gets used instead, exactly as before.

The X socket and cookie are mounted into the client container either way, because compose cannot skip a bind mount on a condition. The Wayland path means the client no longer needs them, not that they are out of reach. Anything holding them can read every keystroke you type in any window, screenshot the session and synthesise input, so treat that container as trusted with your desktop.

What the Wayland path costs is the window frame. winewayland.drv asks for no server-side decoration, so wine draws the titlebar itself, in the Windows 2000 look, and no desktop setting reaches it — the compositor never decorates that window. BF3_GRAPHICS_DRIVER=x11 draws through XWayland instead, and then the window is the window manager's to decorate like any other, so KDE puts Breeze on it:

BF3_GRAPHICS_DRIVER=x11 podman compose --profile beta up

Put it in .env to keep it; ./generate-env.sh carries it across a re-run. The trade is the paragraph above: on that path the client is using your X socket and cookie again. It matters least in fullscreen, where there is no frame to look at either way.

The dedicated server is the exception, and the only thing here still rendering through X. It creates an 800x600 window it never shows and blocks until it has one, so it gets a throwaway Xvfb. Under the -server profiles that Xvfb lives in a container connected to nothing of yours: no X socket, no cookie, no /dev/dri, no pulse socket, and not your IPC namespace. That is why those profiles need no display from you at all. Under the all-in-one alpha and beta profiles the server shares the client's container, so it draws on the same private Xvfb but sits beside everything the client was given. BF3_SERVER_WINDOW=1 asks for the window on screen instead, and then it draws the way the client does.

X cannot leave the image altogether — wine-staging hard-depends on libx11-6, libxext6, libxkbcommon0 and libxkbregistry0 — but nothing you run renders through it except that Xvfb.


When something goes wrong

No window appears, but you can hear the game. Run ./generate-env.sh again — it finds your session, and both the Wayland socket and the X cookie move between logins.

The window has a grey Windows 2000 titlebar instead of your desktop's. That is wine drawing its own frame, which is what its Wayland driver does. Set BF3_GRAPHICS_DRIVER=x11 in .env to draw through XWayland and let your window manager decorate it — see "How the game reaches your screen" for what that path costs.

The game exits about twenty seconds in. Check deploy/cpu-online contains something like 0-7. ./generate-env.sh writes that file.

BadValue ... X_ShmPutImage and the game dies at startup. ipc: host must be present on the client services in compose.yamlalpha, beta, alpha-client, beta-client. The -server services never draw through your X server, so they do not have it and do not need it. It is intermittent, so a run that worked proves nothing.

Starting a second client stopped the first, with not all containers could be removed from pod on the way out. Give each player their own project, as Two people at one machine shows. The pod errors themselves are noisy but harmless.

The client starts, then quits without joining. Look at the backend:

podman compose --profile blaze logs blaze

asked for beta game 7, which is not running means the GAME_ID you gave is stale. Use 1 to matchmake.

missing services [blaze], and the service is plainly in compose.yaml. Every service here sits behind a profile, and podman-compose up to 1.6.0 does not enable a service's own profile when you name it. Docker Compose does, and so does podman-compose after 1.6.0, which is why the same command works elsewhere. Put the profile before the subcommand:

podman compose --profile blaze run --rm blaze --show games

It is needed even while --profile beta up is running: the flag selects what this invocation can see, not what is started. To use the container that is already up instead, name the command in full, because exec does not apply the image's entrypoint:

podman compose --profile beta exec blaze python -m blaze --show games

The installer refuses the archive. It checks the size and SHA256 of a complete copy before unpacking. Finish the download, or set BF3_SKIP_CHECKSUM=1 if you know your archive is fine.

A shell in the game container. The image has a role for it, which lands in a wine prefix of its own rather than a game's:

podman compose --profile beta-client run --rm beta-client shell

Check the GPU is visible:

podman compose --profile beta-client run --rm beta-client vulkan-check

What is in here

Path What it does
deploy/compose.yaml Every service and profile
deploy/generate-env.sh Writes .env: this machine, and the settings most people change
deploy/uninstall.sh Removes the containers, volumes and images; keeps careers
deploy/clean-up.sh Reclaims what rebuilding leaves behind; never touches volumes
wine/ The wine image and its launcher
blaze/ The Blaze backend, see blaze/README.md
installer/ Unpacks the repack into the game volumes

Volumes: bf3-alpha, bf3-beta and bf3-beta-server hold the games, bf3-wine-data the wine prefixes, and bf3-blaze-data the careers.


License

MIT — see LICENSE. That covers the code in this repository and nothing else: Battlefield 3 and everything in the repack belong to EA and DICE, and none of it is distributed here.

About

Run 2011 Battlefield 3 Alpha Trial and Open Beta on Linux

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages