Cheater Cheater

2023-10-22

First of all, let me say bless all the YouTube tutorial creators, they are doing the Lord's Work. Recently they helped me fix a broken flush lever in my toilet, and this weekend, figure out how to use Cheat Engine and write an auto splitting script for LiveSplit to automatically time my runs.

The tl;dr of an auto splitter is this: if you don't have one, you have to manually press keys to start/split/stop the timer on your speedruns. This is error-prone and imprecise - two things you want to avoid while trying to concentrate on trying to shave seconds or even milliseconds off a run time.

And the tl;dr of how to make one is: use Cheat Engine to find the regions of in-game memory that correspond to variables and state changes that should trigger splits, and then write an auto splitter script with some basic logic to watch those identified memory regions for a change and split when appropriate.

There is an alternative method which appears to rely on image comparison of screen regions, i.e., you give it like "diff" images so it can tell when the screen changes from not having the blue key to having the blue key and splits that way, but that seems inelegant compared to glimpsing memory. I'm sure it has its use cases though, although I suspect it's kinda resource-intensive.

So I chose the former method.

The Process

I essentially followed these steps:

Utilizing Cheat Engine

Progress! At that point, I needed to figure out how to identify what part of memory related to what keys Binkley has. This was much more awkward because basically I was looking for a 0 flipping to a 1 in a sea of binary. Through some trial and error with Cheat Engine plus a little common sense, I was able to figure it out. Although I was getting a huge number of candidates, I noticed one that had a memory address very close to Binkley's coins/health - so took a stab at it and that turned out to be the one. Thankfully, all the other keys were represented in the subsequent memory bytes so I didn't have to hunt each of them down.

From there, it was pretty straightforward to get the autosplitter going. By following the GitHub tutorial, reviewing the example Alan Wake auto splitter script, and the video I linked earlier, I came up with this:

state("DOSBox") {
    ushort energy: 0x0193C370, 0x18199;
    byte key1: 0x0193C370, 0x181B5;
    byte key2: 0x0193C370, 0x181B6;
    byte key3: 0x0193C370, 0x181B7;
    byte key4: 0x0193C370, 0x181B8;
    byte key5: 0x0193C370, 0x181B9;
    byte key6: 0x0193C370, 0x181BA;
    byte key7: 0x0193C370, 0x181BB;
    byte key8: 0x0193C370, 0x181BC;
}

startup {
    print("[LiveSplit] Starting...");
}

init {
    print("[LiveSplit] Init...");
    print("[LiveSplit] Energy " + current.energy);
}

split {
    //keys
    if (old.key1 == 0x0 && current.key1 == 0x1) {
        return true;
    } else if (old.key2 == 0x0 && current.key2 == 0x1) {
        return true;
    } else if (old.key3 == 0x0 && current.key3 == 0x1) {
        return true;
    } else if (old.key4 == 0x0 && current.key4 == 0x1) {
        return true;
    } else if (old.key5 == 0x0 && current.key5 == 0x1) {
        return true;
    } else if (old.key6 == 0x0 && current.key6 == 0x1) {
        return true;
    } else if (old.key7 == 0x0 && current.key7 == 0x1) {
        return true;
    } else if (old.key8 == 0x0 && current.key8 == 0x1) {
        return true;
    }

    // fountain
    if (old.key8 == 0x1 && old.energy < current.energy) {
        return true;
    }
}
        

Pretty minimal despite the fact that it took me the better part of a day to figure everything out. The final split was a little more complex since it wasn't a key, but detecting an increase in health AFTER getting key #8 indicates that Binkley just hit The Fountain of Youth so a bit of logic along those lines was all that was needed.

I actually really enjoyed the process though, and think it would be quick to do again now that I understand the tools and concepts reasonably well. If you need an auto splitter script made for something, HMU!

Also, I should note I did all this in a dev VM because I suspect even having Cheat Engine on disk might get you flagged for DRM/VAC/etc. violations on some games even if you aren't attaching it to the process. That might be paranoid, but I'm used to using a VM to do dev anyway. I ported the auto split script over to my host once complete.

The Result

A working autosplitter! I reset all my existing splits, put in a run to test it out and get a baseline, then put in a second run which was recorded and ended up being a new record. Getting close to 6m 30s so I think the "plateau" for my current strat without modification will definitely be closer to 6m than 6m 30s like I previously predicted. Anyway, that's all TBD after putting more runs in.

Next Steps

In a project like this I think it's important to keep in mind ongoing priorities. For now:

That's all for now folks!