On Thursday 8 February 2024

Playing Celeste with a Wireless Xbox controller on MacOS

Celeste ignores some wireless controllers because of an outdated library. Upgrading said library my pasting it inside Celeste.app fixes the issue. We have to codesign the app again to get past Apple’s security.

My controller works with other MacOS games and I tried every possible game settings. Clearly Celeste is being at fault here.

By looking at the dynamic libraries packed in Celeste.app, we might be able to pinpoint the issue.

App files are just folders with an extension. Any app can be unfolded in the Finder with a right-click.

In Celeste.app I found libSDL2-2.0.0.dylib, which indicates that Celeste is based on the SDL (or Simple Directmedia Library): a general and well known library that abstracts away graphics hardware, “input devices” and audio devices among other things.

Provided the API (the set of features provided by the SDL and the way they are accessed) has remained stable enough since 2.0.0, replacing libSDL2 by a newer version could do the trick.

Recent releases of the libSDL for MacOS consists of a folder named SDL.framework with no dylib in sight.

I found that renaming SDL2.framework/Versions/A/SDL2 to SDL2.dylib, and then to libSDL2-2.0.0.dylib so that it matches what Celeste expects does the job. Running file ./SDL2 confirms that it is a dynamic library anyway.

My first attempt was to inject the newer SDL using the DYLD_INSERT_LIBRARIES variable.

Codesigning

Having replaced the file, the game won’t load anymore. It could be that we’ve just broken it, or maybe code signing is to blame. By running the command $PATH_TO_CELESTE_APP/Contents/MacOS/Celeste in a terminal, we get the following output:

System.DllNotFoundException: libSDL2-2.0.0.dylib 

Since the file is in there, it looks like something bigger is preventing Celeste from loading it. We could try manually codesigning Celeste.app and see what happens.

I found that creating a new signing certificate in the OS Keychain and running the following command will finally make the operating system comply.

sudo codesign --deep --force --verify --sign "MyCert" $PATH_TO_CELESTE_APP

It is also possible to just sudo codesign --deep --force --sign - $PATH_TO_CELESTE_APP which works without a certificate.

I like to use one though, because it allows me to just remove the certificate and have all past self-signed binaries blocked if needed. But you can probably get away with just ad-hoc signing.

Running Celeste again, the SDL loads fine and my wireless controller is instantly recognized.