Fix for old Unity games for Linux not starting anymore

There’s an interesting problem with old (like ~10 years) games for Linux made with Unity that stopped working. I made a workaround that’s been verified with Infinifactory and Motorsport Manager, but should work with 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?

To find out if your game doesn’t start anymore due to the problem in question, do the following:

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

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. So you cannot play a game that needs Steam and at the same time not having sort of Chromium running. Gotcha!

The solution

The actual solution would be that the game developers update to a newer, fixed Unity engine version. But let’s face it: This is 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!

The workaound

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 only needs to be something that doesn’t point at the game’s executable in the end.

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 *fopen64(const char *pathname, const char *mode) {
  static FILE *(*real_fopen64)(const char *, const char *) = NULL;
  if (!real_fopen64) 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) 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)
  {
    // fake return value "/proc/self/exe" from Chrom(ium)/CEF stuff
    memcpy(ptr, "/wutevawutevawuteva", 14);
  }

  return ret_val;
}

Compile the code to a shared library:

gcc -shared -fPIC -ldl -o libunitybugworkaround.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. Do enter your path, not mine!

LD_PRELOAD=~/Bug-Fix-Wrapper/libunitybugworkaround.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!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert