Running Factorio with musl libc
Factorio: Space Age has recently been released, and I have been meaning to start a new run with my friends on a dedicated server. It was meant to run on my NAS which runs Alpine Linux, a small and simple distribution. Factorio has a headless client, which contains the entire game logic without the assets. This is great for servers, but upon running the client, I was greeted with the following errors:
|
|
The game apparently relies on some functions which musl does not implement.
The Alpine Linux wiki
suggests installing gcompat,
a compatibility layer which provides bare minimum implementations for many non-standard glibc additions.
Unfortunately, it does not implement res_nquery
, dn_expand
or pthread_cond_clockwait
.
Let’s try to resolve this, as I really want to build yet another spaghetti factory which my friends and I will abandon after 10 or so hours.
dn_expand
So I began reading the sources of musl
and gcompat
. Apparently musl does implement
dn_expand
. The following test
code works as expected too:
|
|
|
|
After some digging around I’ve found that musl does not export the original symbol __dn_expand
.
Here is what glibc on my Artix box exports:
|
|
And here is what is exported by musl on Alpine:
|
|
On a second glance, musl doesn’t seem to export the internal symbols for anything, which makes perfect sense, after all, they are internal. You can read some more about the topic in this SO thread.
We should also take into account that the game actually links to libresolv.so.2
as seen here:
|
|
Which, if you have it installed, is provided by gcompat
, so I suppose that’s the best place to export it,
via a thunk function just like __res_search
right above it:
|
|
res_nquery
gcompat uses a clever hack to implement res_ninit
,
and we can use something similar to reuse the non-reentrant but already implemented res_query
:
|
|
We simply swap the global context _res
to the provided one statep
, call the non-reentrant version,
and restore things to their original place. I did not bother to read the implementation, I don’t actually know
if res_query
can modify the state, so the memcpy(statep, &_res, sizeof(_res))
line could be unnecessary.
res_state
is an incomplete type, the actual definition of the struct is internal and mustn’t be relied upon.
Thankfully we can still use its size by calling sizeof
on a dereferenced pointer (I am not actually sure
how standard this is, but gcompat
seems to use it and it links just fine).
You may think this implementation is prone to race conditions, and you’d be right! The non-reentrant versions of the resolver API use a global context anyway, so I hope Factorio doesn’t use both APIs at the same time.
After grabbing the latest gcompat
commit and applying my patches, we are down to a single blocking error:
|
|
pthread_cond_clockwait
This one’s virtually impossible to semi-correctly implement without patching musl itself,
as much of the threading code we’d need to touch is internal to the actual libc on the system. According to
the POSIX standard,
pthread_cond_clockwait
is just like pthread_cond_timedwait
(which musl does implement!), except it lets you
specify a clock different than CLOCK_REALTIME
. The main reason to use it (that I can think of) is
using CLOCK_MONOTONIC
, so time increases monotonically. Fun fact, I did find a
patch to implement this in musl,
it might even get merged one day!
In the meantime, though…
|
|
I’m sorry, I know, this can go terribly wrong. I never checked which clock Factorio uses, and I don’t take responsibility for any corrupt save files.
However, it works!
The most ideal solution would of course be if the developers made the game compatible with musl
libc, however, I couldn’t think of an alternative to pthread_cond_clockwait
off the top of my head.
I’m also not sure what exactly the DNS related code in the game is for, but maybe getaddrinfo
can
serve the same purpose.
On the other hand, musl-based distributions are already pretty much impossible to game on, so the extra effort is most likely better spent on developing everyone’s favourite factory builder.
It did crash once, and I will definitely need to test it a lot, but it was a fun afternoon hack which finally got me writing code again. I of course uploaded my branch to Github. Until later!