+++
title = "Stardew Valley on aarch64"
date = "2020-01-15"
tags = ["PBP", "hacking"]
+++

#### Stardew Valley on aarch64

At the end of last year I got a [Pinebook Pro](https://pine64.org/pinebook-pro) -
mostly for reasons of paranoia. So far, it's been pretty good, but there was
one thing that I couldn't get working: [Stardew Valley](https://stardewvalley.com/)

You could call me a little bit addicted to this game, but it's proprietary,
closed-source, and the authors don't release binaries compiled for aarch64 -
although they do very kindly release x86_32 and x86_64 Linux binaries, which
is more than most companies do.

I [left a message on the forum](https://community.playstarbound.com/threads/arm-arm64-aarch64-linux-support.158840/)
and moved on, confident that it wouldn't ever happen. I vaguely knew it was
written in C#, but it's not an ecosystem I have any experience in. I figured it
was going to be the kind of thing that comes under "possible, but not trivial" -
and aarch64 + linux is super-niche.

Fast forward a few weeks, I mentioned it in passing on the `#pinebook` IRC
channel,  which went a little like:

```
<lupine> stardew valley never got back to me *sob*
<halosghost> lupine: trying to get Stardew Valley on the pbp as well?
<Nadia> The game itself is written in C# so runs through Mono
<lupine> they don't distribute  aarch64-linux executables
<Nadia> You just need to build the libraries it needs and it should run
<lupine> it's closed source, so "just need to build" is rather an issue
```

They rather roundly assured me that it wasn't an issue at all, and literally
10 minutes later I had a working Stardew Valley setup. This is incredible.

I'm documenting the steps I took so I can come back to this in the future, but
maybe it'll be useful for others too.

First, you need a copy of the game. I was working with v1.4 as shipppd by
[GOG Games](https://www.gog.com/game/stardew_valley). It comes as one of those
`.sh` files that contains an archive. That doesn't have aarch64 support, and
doesn't run on the PBP. Fortunately, I'd already installed it on an amd64 laptop
so I just rsynced that over:

```
lupine@pbp:~$ rsync -avzP '10.0.1.104:GOG Games' .

# Might as well grab my savegames at the same time
lupine@pbp:~$ rsync -avzP 10.0.1.104:.config/StardewValley .config/StardewValley
```

I'm sure it's possible to make it run, I'm just being lazy. I'll update this in
the future if I work out how to go from the `.sh` file.

What does this give us?:

```
lupine@pbp:~/GOG Games/Stardew Valley/game$ ls -lh
total 438M
-rwxrwxr-x  1 lupine lupine  12K Dec  8 23:58 BmFont.dll
drwx--x--x 17 lupine lupine 4.0K Dec  8 23:58 Content
-rwxrwxr-x  1 lupine lupine 330K Dec  8 23:58 GalaxyCSharp.dll
-rwxrwxr-x  1 lupine lupine  336 Dec  8 23:58 GalaxyCSharp.dll.config
-rwxrwxr-x  1 lupine lupine  91K Dec  8 23:58 goggame-1453375253.hashdb
-rwxrwxr-x  1 lupine lupine  782 Dec  8 23:58 goggame-1453375253.info
drwx--x--x  2 lupine lupine 4.0K Dec  8 23:58 lib
drwx--x--x  2 lupine lupine 4.0K Dec  8 23:58 lib64
-rwxrwxr-x  1 lupine lupine 197M Dec  8 23:59 libGalaxyPeer64.so
-rwxrwxr-x  1 lupine lupine 177M Dec  8 23:59 libGalaxyPeer.so
-rwxrwxr-x  1 lupine lupine 5.3M Dec  8 23:59 libSkiaSharp.dll
-rwxrwxr-x  1 lupine lupine 119K Dec  8 23:58 Lidgren.Network.dll
lrwxrwxrwx  1 lupine lupine   14 Dec  9 00:00 mcs -> mcs.bin.x86_64
-rwxrwxr-x  1 lupine lupine  17M Dec  8 23:59 mcs.bin.x86
-rwxrwxr-x  1 lupine lupine  16M Dec  8 23:59 mcs.bin.x86_64
drwx--x--x  3 lupine lupine 4.0K Dec  8 23:59 mono
-rwxrwxr-x  1 lupine lupine 2.5K Dec  8 23:59 monoconfig
-rwxrwxr-x  1 lupine lupine 1.3M Dec  8 23:58 MonoGame.Framework.dll
-rwxrwxr-x  1 lupine lupine  527 Dec  8 23:58 MonoGame.Framework.dll.config
-rwxrwxr-x  1 lupine lupine 203K Dec  8 23:58 Mono.Posix.dll
-rwxrwxr-x  1 lupine lupine 328K Dec  8 23:58 Mono.Security.dll
-rwxrwxr-x  1 lupine lupine 3.6M Dec  8 23:59 mscorlib.dll
-rwxrwxr-x  1 lupine lupine 267K Dec  8 23:58 SkiaSharp.dll
-rwxrwxr-x  1 lupine lupine 1.2K Dec  8 23:58 StardewValley
-rwxrwxr-x  1 lupine lupine 4.0M Dec  8 23:58 StardewValley.bin.x86
-rwxrwxr-x  1 lupine lupine 3.8M Dec  8 23:58 StardewValley.bin.x86_64
-rwxrwxr-x  1 lupine lupine 4.1M Dec  8 23:58 StardewValley.exe
-rwxrwxr-x  1 lupine lupine 6.5K Dec  8 23:58 StardewValley.GameData.dll
-rwxrwxr-x  1 lupine lupine 127K Dec  8 23:58 System.Configuration.dll
-rwxrwxr-x  1 lupine lupine 879K Dec  8 23:58 System.Core.dll
-rwxrwxr-x  1 lupine lupine 2.0M Dec  8 23:58 System.Data.dll
-rwxrwxr-x  1 lupine lupine 2.2M Dec  8 23:58 System.dll
-rwxrwxr-x  1 lupine lupine 442K Dec  8 23:58 System.Drawing.dll
-rwxrwxr-x  1 lupine lupine 966K Dec  8 23:58 System.Runtime.Serialization.dll
-rwxrwxr-x  1 lupine lupine 130K Dec  8 23:58 System.Security.dll
-rwxrwxr-x  1 lupine lupine 3.1M Dec  8 23:58 System.Xml.dll
-rwxrwxr-x  1 lupine lupine 131K Dec  8 23:58 System.Xml.Linq.dll
-rwxrwxr-x  1 lupine lupine 161K Dec  8 23:58 WindowsBase.dll
-rwxrwxr-x  1 lupine lupine  48K Dec  8 23:59 xTile.dll
-rwxrwxr-x  1 lupine lupine 9.0K Dec  8 23:59 xTilePipeline.dll
```

The magic here is that not all the `.dll` and `.exe` files here are **Windows**
object files. Instead, many of them are just Mono bytecode, which is analogous
to JVM bytecode:

```
lupine@pbp:~/GOG Games/Stardew Valley/game$ file StardewValley*
StardewValley:              Bourne-Again shell script, ASCII text executable
StardewValley.bin.x86:      ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4801f8881feefa8aa515f9fadc02c01598c44131, not stripped
StardewValley.bin.x86_64:   ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=04fe4f2c2ca8b4dc7faf70c643417bf0df632a9e, not stripped
StardewValley.exe:          PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
StardewValley.GameData.dll: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows
```

We need Mono to run these assemblies. I guess the `StardewValley.bin.*` files
ar just stripped-down Mono runtimes that invoke `StardewValley.exe`!

The `mcs.*` binaries are "Mono C Sharp" - I don't know C#, but I assume it's
another essential part of the runtime.

Debian has Mono + MCS already, so...

```
lupine@pbp~/GOG Games/Stardew Valley/game$ sudo apt install mono-runtime mono-rcs
lupine@pbp~/GOG Games/Stardew Valley/game$ ln -sf `which mcs`
```

We can then try to run the game:

```
lupine@pbp~/GOG Games/Stardew Valley/game$ mono StardewValley.exe
```

Amazingly, that's **almost sufficient**, all by itself, to get a fully working
game, at least for me. It starts up, and the only obviously broken thing is
sound. There are some complaints on the comamnd line that don't seem to get in
the way of actually playing it.

Wat.

No sound is annoying though, how about we fix that?

Turns out Stardew Valley only **requires** two external libraries: SDL and
libasound. I've no idea if the graphics is working even though it can't find
SDL, or if it can find my native SDL libary but not the libasound one, or what,
but it's trivial to fix. Edit `MonoGame.Framework.dll.config` and add these
two lines:

```
  <dllmap dll="SDL2.dll" os="linux" cpu="armv8" target="./libaarch64/libSDL2-2.0.so.0"/>
  <dllmap dll="soft_oal.dll" os="linux" cpu="armv8" target="./libaarch64/libopenal.so.1" />
```

Now you just need to put those two .so files into that directory locally, and
sound begins to work!

(I just symlinked `/usr/lib/aarch64-linux-gnu` into place, which does the same
job).

At this point the game works perfectly, including LAN multiplayer -  which is
ridiculous - and despite worries about endianness, it can load and run my saves
as well.

There are a few complaints on the console though. Let's see what we can do
about them.


```
Your mono runtime and class libraries are out of sync.
The out of sync library is: /home/lupine/GOG Games/Stardew Valley/game/System.dll
```

OK, these are shipped with `mono-runtime` (actually in `libmono-system4.0-cil`)
anyway. The complaint is that these assemblies were compiled with a different
version of Mono, but it's falling back to the main ones anyway, so we can just
move these out of the way.

The only `System.*.dll` file we need to keep is `System.Runtime.Serialization.dll` -
the rest can be moved out of the way.

```
System.TypeInitializationException: The type initializer for 'Galaxy.Api.GalaxyInstance' threw an exception. ---> System.TypeInitializationException: The type initializer for 'CustomExceptionHelper' threw an exception. ---> System.DllNotFoundException: GalaxyCSharpGlue
  at (wrapper managed-to-native) Galaxy.Api.GalaxyInstance+CustomExceptionHelper.CustomExceptionRegisterCallback(Galaxy.Api.GalaxyInstance/CustomExceptionHelper/CustomExceptionDelegate)
  at Galaxy.Api.GalaxyInstance+CustomExceptionHelper..cctor () [0x00011] in <22373852dcce42128dc7e065ea92368d>:0 
   --- End of inner exception stack trace ---
  at (wrapper managed-to-native) System.Object.__icall_wrapper_mono_generic_class_init(intptr)
  at Galaxy.Api.GalaxyInstance..cctor () [0x00000] in <22373852dcce42128dc7e065ea92368d>:0 
   --- End of inner exception stack trace ---
  at StardewValley.SDKs.GalaxyHelper.Initialize () [0x00000] in <1ed49e648be548bcae8e4508597c9f4c>:0 

```

I am **astonished** that this one isn't a fatal error - but the game runs fine
even though it can't find an external library. Ridiculous.

Galaxy is GOG's multiplayer gubbins. If you've got a Steam game, it's different,
I'm sure, but the functionality this stuff is *for* is to negotiate multiplayer
games with strangers.

I have no use for this myself, but `libGalaxyCSharpGlue.so` is looked up via
another dllmap in `GalaxyCSharp.dll.config` - it's not packaged by Debian, and
it may even be proprietary GOG code, but if we can get an aarch64 version of it,
making it work should be as simple as adding an entry there.

If this did become a fatal error at some point, the minimum work would be a stub
implementation that meets the ABI but always says "no games available" or some
such.

And... that's all the errors. Despite a different architecture, despite being
short some libraries, and despite running reverse-engineered (Panfrost) graphics
drivers with only a bare whisper of OpenGL support, my favourite game is running
at normal speed on an architecture its authors and publishers didn't even think
about.

Again I say: ridiculous

...maybe I should learn some CSharp?
