The first-ever Celeste autosplitter for macOS, SwiftSplit allows fine-grained breakdowns of Celeste speedruns by directly reading the game’s memory to detect screen transitions, chapter events, and more. It acts as a WebSocket split server for the LiveSplit One web app, sending timing and split events based on a JSON route file.
Having an autosplitter for a game like Celeste is vital for speedrunning, since it creates detailed breakdowns to judge what portions of the game to focus on when practicing. During runs, it also gives you feedback as you go and lets you judge whether you’ve fallen too far behind to recover and might as well reset.
The two primary difficulties when creating SwiftSplit were reading from process memory in the first place, but even more difficult than accessing the memory was trying to find the data once I did. Celeste is written in C# using the Unity game engine. C# uses a garbage collector and manages memory internally, so there’s no fixed location for the data I needed.
On Windows, this is solved by creating a “fingerprint” that includes the object header, which points to a fixed location in the application binary. The autosplitter then scans through the application memory to locate that header. This method doesn’t work on macOS, however, because address space layout randomization means every time the game launches the object header is different.
After much experimenting, I determined that by some miracle the uninitialized state of the object is unique enough to work as a signature. Once I worked this out, all I had to do was detect when the app launched, scan for the uninitialized object, then back up and get the object header as a signature for use if the object ever moves.