There’s an interesting problem with old (like ~10 years) games for Linux made with Unity that stopped working under Steam. I wrote a workaround for the bug. It is confirmed to work for:
- Infinifactory
- Motorsport Manager
- Creeper World 3: Arc Eternal
- Shadowrun Returns
- Shadowrun: Dragonfall – Director’s Cut
- Shadowrun: Hong Kong – Extended Edition
- Beatbuddy: Tale of the Guardians
- The Novelist
- Journey of a Roach
- The Bridge
I expect it to work for some other games (potentially many) as well.
The description and the fix below are heavily based on this excellent analysis, that I would never have been able to come up with. Thanks, janinko!
Do I have this problem?
For some games, Unity writes the string „Player is already running“ to the file ~/.config/unity3d/Player.log. So you can…
- delete the file ~/.config/unity3d/Player.log
- start the game
- look into ~/.config/unity3d/Player.log
- if you see „Player is already running“ there, you’re very likely at the right place here.
Unfortunately, I didn’t see the same behaviour in every case for the (very recommended!) Shadowrun series.
The problem
There’s a bug in an old Unity version (it might stem from the underlying mono as well, I’m not sure about that). Here’s the technical description. You don’t need to understand or even read it for the workaround of course.
There’s a switch in the Unity game engine to ensure that only one instance of a game is running. Sounds useful to me, problems might arise otherwise. But the implementation of this feature is strange – and buggy.
There’s the „/proc/“ „file system“, that’s not a bunch of actual files like on would imagine. They present the current state of the processes, and they are not stored on any disc. A special entry is the /proc/self/ directory, that for every process shows its own information – so it is different depending on who is reading it.
Unity is going through every entry in the proc directory to look for an already started instance of itself. To do so, it reads every command line used to start the respective processes and compares the executable to its own.
Now there’s Chrome, which seems to start a second instance by running /proc/self/exe – it is starting itself again this way, no matter where the executable sits. The newly started process gets exactly this noted as it’s starting command line: /proc/self/exe (and then some parameters).
But when the game made with Unity looks up what executable this might be, it reads its own /proc/self – and finds itself! So the game believes the process started by Chrome would be an instance of the game started earlier. It is writing „Player is already running“ to the Unity log file under ~/.config/unity3d/Player.log and quits.
The problem getting worse over time
10 years ago, the tip was to just leave Chrome/Chromium, the game probably would be running just fine then. But nowadays, there’s Chrome underneath a lot of programs in the form of Chromium Embedded Framework (CEF). It’s not unusal that desktop programs are actually web sites that bring their own hidden bowser. Including… Steam. Since mid 2023, the Steam client is fully CEF based. (According to reports, games with the bug stopped working only some weeks ago though, April or May 2025.)
The solution
The actual solution could be done by the game developers. The easy change would be to disable the „Force Single Instance“ option, as shown in the documentation. The other, probably more difficult way would be to update to a fixed version of the engine. The bug is supposed to be fixed in Unity 5.4.0.
Both is probably not going to happen for many games after a decade. But, it did happen for the game janinko was analyzing, Pillars of Eternity. Props to Obsidian!
Workaound
First things first: Do not compile and execute code from foreign people on the net on your computer! (Especially if you cannot really judge what it is doing…)
… while on the other hand, this is exactly what you need to do for this workaround.
What we’ll be doing is that those Chromium processes are not reported with „/proc/self/exe“ as their starting command to the game’s process, but with something else. (Every other process still get’s the correct value.) It doesn’t need to make sense, it just needs to be something that doesn’t point at the game’s executable in the end.
For this to happen, we intercept the respective file open and read calls Unity uses and change the data having been read where we need to. The dynamic library doing this is injected into the game’s process.
You need to have a C compiler and the standard C headers. In Debian derived distributions (including Ubuntu and Mint), you can ensure that by issuing:
sudo apt install build-essential
Then you save the following code to a file called unitybugworkaround.c:
#define _GNU_SOURCE #include <stdio.h> #include <string.h> #include <dlfcn.h> static FILE *opened_file = NULL; FILE *fopen(const char *pathname, const char *mode) { static FILE *(*real_fopen)(const char *, const char *) = NULL; if (!real_fopen) { printf("Intecepting fopen() calls! \n"); real_fopen = dlsym(RTLD_NEXT, "fopen"); } opened_file = NULL; FILE* ret_val = real_fopen(pathname, mode); if (strncmp(pathname, "/proc/", 6) == 0) { // remember fopen() calls to /proc... opened_file = ret_val; } return ret_val; } FILE *fopen64(const char *pathname, const char *mode) { static FILE *(*real_fopen64)(const char *, const char *) = NULL; if (!real_fopen64) { printf("Intecepting fopen64() calls! \n"); real_fopen64 = dlsym(RTLD_NEXT, "fopen64"); } opened_file = NULL; FILE* ret_val = real_fopen64(pathname, mode); if (strncmp(pathname, "/proc/", 6) == 0) { // remember fopen() calls to /proc... opened_file = ret_val; } return ret_val; } size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { static size_t (*real_fread)(void *, size_t, size_t, FILE *) = NULL; if (!real_fread) { printf("Intecepting fread() calls! \n"); real_fread = dlsym(RTLD_NEXT, "fread"); } size_t ret_val = real_fread(ptr, size, nmemb, stream); if (stream == opened_file && strncmp(ptr, "/proc/self/exe", 14) == 0) { printf("Faking fread() return value! \n"); memcpy(ptr, "/wutevawutevawuteva", 14); } return ret_val; }
Compile the code to a shared library, for 64 bit and for 32 bit:
gcc -shared -fPIC -ldl -o libunitybugworkaround.so unitybugworkaround.c gcc -m32 -shared -fPIC -ldl -o libunitybugworkaround-32.so unitybugworkaround.c
Now go to the game’s start options (right click on the game in the game list in Steam and then choose properties) and enter the library to be preloaded.
We’re preloading both the 64 and the 32 libraries, so the wrong one will be ignored and the matching one will work. You need to use your path, not mine, of course. And do not use „~“, but a fully qualified path.
LD_PRELOAD="/home/you/your-dir/libunitybugworkaround.so /home/you/your-dir/libunitybugworkaround-32.so" %command%
Close the properties window and start the game.
If you try this for any game with the symptoms („Player is already running“), with or without success, I’d be happy to read from you in the comments!