8 min read

how to run FTL on a Raspberry Pi 4 with 64-bit Ubuntu

Surely I could run a few sessions of FTL in-between coding and stuff? The arm64 processor disagreed. At first.
how to run FTL on a Raspberry Pi 4 with 64-bit Ubuntu

I bought a Raspberry Pi 4 not too long ago. Intended as a lightweight, “get back into programming” development machine (and to be used as an at-home gadget central) I didn’t really investigated the state of gaming for it.

But once it was up and running with a 20.10 Ubuntu Mate 64-bit, I started wondering: can I play something on this thing? Nothing serious of course, the hardware was never intended for that (although I fully intend to push it to the limit) but surely I could run a few sessions of FTL in-between coding and stuff?

The Raspberry Pi is a computer. Just a different kind of computer.

Without going into too much detail: the Raspberry Pi 4 uses a 64-bit ARM processor. (Similar to the M1 Macs.) Which means that any and all programs compiled for an x86-based architecture (which is roughly 99,99% of binaries) won’t run on it out-of-the-box.

So, if we’re to restrict the current discussion for games, and even further to FTL in particular, I either needed an arm64 binary or a way to run an amd64 binary on my arm64 system.

Sidenote: the Raspberry Pi, by default (and ‘by default’ I mean ‘by recommendation’) runs a 32-bit OS called Raspbian. It’s based on Debian, and it’s fine – but I wanted to run a 64-bit OS on my 64-bit hardware, and take full advantage of the 8GB of RAM the board is configured with. This complicates things a bit, because most how-tos and other articles are based on using either the 32-bit Raspbian or some similar alternative (like Twister OS for games); and even then much of that information is outdated by a lot. That’s why I figured I’d record my experience with FTL (and, in the future, with other things) and contribute to the discussion from my particular viewpoint: running a 64-bit OS on the Raspberry Pi 4.

Neither FTL, nor most other applications come with arm64 binaries. Understandably: for the vast majority of developers, the niche use case of an arm64 platform isn’t worth the trouble of compiling and maintaining arm64-specific binaries. This might change with the M1 chip, but the Rosetta translation layer and other conveniences will still hold that gap open for a while.

But, as you can see from the cover image, I did manage to get FTL running on my 64-bit Raspberry Pi. So how did I do it?

box64

An amazing developer by the name of ptitSeb has created box86: an emulation layer for non-x86 systems to run x86-based applications. Now, box86 isn’t really what I was looking for, since I was running a 64-bit OS. But, as luck would have it, pitSeb also maintains box64, the 64-bit version of the same tool. Although I was fully prepared to venture into the dungeon of chrooting a 32-bit environment so I could run an x86-emulation layer… thanks to box64 I didn’t have to.

Setting up box64 on my RPi4 was a piece of cake, just needed to follow the instructions:

git clone https://github.com/ptitSeb/box64
cd box64
mkdir build; cd build; cmake .. -DRPI4ARM64=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo
make -j4
sudo make install
sudo systemctl restart systemd-binfmt

This compiles and installs box64 on the RPi4.

From this point on, I can run an x86-64 binary automagically, or specify it with box64 path/to/binary.

Awesome.

Caveat: box64 does an amazing job out of the box. But it’s not a magic bullet, and isn’t capable of turning the arm64 system into an amd64 one. Some additional assembly may be required. Which brings us to:

FTL on arm64

Now that I was capable of running amd64 applications on my arm64 RPi4, it was time to see a man about a spaceship.

I downloaded my copy for the Linux version of FTL from GoG. The GoG Linux games come in the form of a .sh file, that is the installer for the game. Unfortunately, running that didn’t work:

Not that I expected it to work on the first try...

After a bit of Googling, I found from a forum post explaining that I can unpack the GoG installer like so:

unzip path/to/gog/sh/installer -d /path/to/unpack

Which worked just fine. (I forgot to bookmark said blog post, and now for the life of me cannot find it to give credit. Sorry.)

Now I had another .sh file in the form of ~/apps/ftl/data/noarch/start.sh. However, running that didn’t work either:

Okay, perhaps not on the second try either...

From what I could tell (by running a cat start.sh) the binary would check if the system was amd64. Mine isn’t. If not, it’d default to x86. Which mine isn’t either.

As I was digging through the folders, I found a number of binaries:

dinchamion@tranquility-pi:~$ ls -l apps/ftl/data/noarch/game/data/
total 400112
-rwxrwxr-x 1 dinchamion dinchamion     16438 jan  9  2020 exe_icon.bmp
-rwxrwxr-x 1 dinchamion dinchamion       394 júl 18 11:25 FTL
-rwxrwxr-x 1 dinchamion dinchamion  72161049 jan  9  2020 FTL.amd64
-rwxrwxr-x 1 dinchamion dinchamion 278197004 jan  9  2020 ftl.dat
-rw-rw-r-- 1 dinchamion dinchamion       502 júl 18 10:42 FTL.log
-rwxrwxr-x 1 dinchamion dinchamion  59309550 jan  9  2020 FTL.x86
drwxrwxr-x 2 dinchamion dinchamion      4096 jan  9  2020 licenses

Interesting. So I tried to run the amd64 binary directly…

dinchamion@tranquility-pi:~$ apps/ftl/data/noarch/game/data/FTL.amd64 
Box64 with Dynarec v0.1.3 ba33593 built on Jul 17 2021 14:21:17
Using default BOX64_LD_LIBRARY_PATH: ./:lib/:lib64/:x86_64/:bin64/:libs64/
Using default BOX64_PATH: ./:bin/
Counted 61 Env var
Looking for apps/ftl/data/noarch/game/data/FTL.amd64
Using native(wrapped) libGL.so.1
Using native(wrapped) libX11.so.6
Using native(wrapped) libasound.so.2
Using native(wrapped) libdl.so.2
Using native(wrapped) libpthread.so.0
Using native(wrapped) librt.so.1
Using native(wrapped) libm.so.6
Using native(wrapped) libc.so.6
Using native(wrapped) ld-linux-x86-64.so.2
Using native(wrapped) libXi.so.6
Using native(wrapped) libXext.so.6
Using native(wrapped) libxcb.so.1
Using native(wrapped) libXau.so.6
Using native(wrapped) libXdmcp.so.6
Using native(wrapped) libXinerama.so.1
Using native(wrapped) libXrandr.so.2
Using native(wrapped) libXrender.so.1
Using native(wrapped) libXxf86vm.so.1
Version: 1.6.12
Loading settings
Initializing Crash Catcher...
Starting up
Loading text
Initializing Video
Couldn't find in dictionary: window_title
Video: 1280x720, windowed
Video Initialized
Renderer: OpenGL version 2.1 (GL_VERSION: 2.1 Mesa 20.2.6)
Creating FBO...
But on the third?

… aaaaaaand no dice. :-(

At this point, Google was useless, as I didn’t have anything specific to search for, and – as mentioned – most hits would be for a 32-bit system.

I was close to giving up. “It’s fine,” I told myself. “You have FTL on your iPad. And this was meant to be a work computer anyhow.

But I’m the stubborn sort when it comes to problem solving. And my brain just couldn’t let it go.

And the secret ingredient is…

After a day or two of pointedly not messing with it, I went back into the unzipped folders, looking for a logfile or some other piece of information I could use for a Google search. (Because we don’t know things these days. We know how to google for them. External memory, y’all, we’re cyborgs.) I didn’t find any.

What I did find, however, was that the FTL file (that I thought was just a generic executable binary) had a size that didn’t really make sense. 394 bytes. Hm.

So I opened it in VS Code. Because why not.

I spy with my little eye an intriguing IF statement

As I was looking at it, I noticed the IF statement conditions on line 11:

  && ( [ "$machine" = 'x86_64' ] || [ "$machine" = 'amd64' ] )

And I thought to myself: can it be that easy? So I added another condition:

  && ( [ "$machine" = 'x86_64' ] || [ "$machine" = 'amd64' ] || [ "$machine" = 'aarch64' ] )

So my “unknown” (for the game) architecture would be interpreted as amd64, prompting the loading of the binary. Save, and pray:

dinchamion@tranquility-pi:~$ apps/ftl/data/noarch/start.sh 
Running FTL: Advanced Edition
Loading Arch = amd64
Box64 with Dynarec v0.1.3 ba33593 built on Jul 17 2021 14:21:17
Using default BOX64_LD_LIBRARY_PATH: ./:lib/:lib64/:x86_64/:bin64/:libs64/
Using default BOX64_PATH: ./:bin/
Counted 62 Env var
Looking for /home/dinchamion/apps/ftl/data/noarch/game/data/FTL.amd64
Using native(wrapped) libGL.so.1
Using native(wrapped) libX11.so.6
Using native(wrapped) libasound.so.2
Using native(wrapped) libdl.so.2
Using native(wrapped) libpthread.so.0
Using native(wrapped) librt.so.1
Using native(wrapped) libm.so.6
Using native(wrapped) libc.so.6
Using native(wrapped) ld-linux-x86-64.so.2
Using native(wrapped) libXi.so.6
Using native(wrapped) libXext.so.6
Using native(wrapped) libxcb.so.1
Using native(wrapped) libXau.so.6
Using native(wrapped) libXdmcp.so.6
Using native(wrapped) libXinerama.so.1
Using native(wrapped) libXrandr.so.2
Using native(wrapped) libXrender.so.1
Using native(wrapped) libXxf86vm.so.1
Version: 1.6.12
Loading settings
Initializing Crash Catcher...
Starting up
Loading text
Initializing Video
Video: 1280x720, windowed
Video Initialized
Renderer: OpenGL version 2.1 (GL_VERSION: 2.1 Mesa 20.2.6)
Creating FBO...
Starting audio library...
Audio Initialized!
Resource Preload: 16.310
Initializing animations...
Animations Initialized!
Loading Ship Blueprints....
Blueprints Loaded!
Initializing Sound Data....
Generating world...
Loading achievements...
Loading score file...
Running Game!
IT'S ALIVE!

Victory!

MesaGL>3.0

Daniel Walker reached out to me on Twitter, and suggested adding a note for folks using MesaGL 3.1 or similar.

FTL apparently doesn't like MesaGL above 3.0, so you'll need to add the following to the startup script:

$MESA_GL_VERSION_OVERRIDE=3.0 box64 /home/username/GOG Games/FTL Avanced Edition/game/FTL"```

Amed your path as needed.

Thanks again Daniel for the tip!

Lessons… learned?

I’m super happy to be able to play FTL, smoothly and with no issues, on my RPi4. And I’m even happier to have been able to figure this out on my own.

Granted, I’m not entirely sure why it worked. And I’m definitely standing on the shoulder of giants: without ptitSeb’s box64, I couldn’t have run it at all.

Still, I count this as a victory. I can figure out the exact paramaters of the “why” later. I can experiment and learn more from this as I progress. Every journey starts with the first step, and I’m happy I took this one.

Now, let’s see about that spaceship.