This is a python based autosplitting tool for speedrunning. Currently only Linux is supported, but adding Windows support should be possible, when I come around to do it. In theory MacOS and other Unix systems work, but this is completely untested.
While LiveSplit does work inside wine, the autosplit component does not work on linux machines. In addition to this, it is not possible to create breakpoints and read values in CPU registers with the autosplit component of LiveSplit. PyAutoSplit can do that. This is especially useful in a game like VVVVVV (2.2 and below), where all relevant information are on the stack, and therefore at nonstatic locations in memory.
Current strategies involve scanning the processes memory for specific values, to guess the location of information like gamestate etc. Unfortunately this is rather error prone. With PyAutoSplit one can set a breakpoint at the (static) instruction where the game object is created, and read the cpu registers to get an exact location of the game object.
The program can be installed via pip
pip install --user git+https://github.com/christofsteel/pyautosplit.git
PyAutoSplit has three different front-ends: console out, LiveSplit and LiveSplit One. You select one or multiple front-end with the -f
/--front-end
flag. If no front-end flag is given, LiveSplit is automatically selected.
If you want to connect PyAutoSplit to the server component of LiveSplit, you first have to start LiveSplit, and the LiveSplit server component. After that you can launch PyAutoSplit with
pyautosplit -f livesplit -- routefile.json
Run PyAutoSplit with
pyautosplit -f livesplitone -- routefile.json
After that, go to https://one.livesplit.org and connect to ws://localhost:5000
.
This is more of a debug output, but you can launch PyAutoSplit with
pyautosplit -f console -- routefile.json
To use PyAutoSplit for a game, you have to have it installed and two files. One specific for the game you are playing and one specific for your route. In this repository, there is an example for the game VVVVVV and a route for glitchless 100%.
A game file is a json file with the following fields:
name
, the name of the gamecommand
, the launch command to run the gamecwd
, the working directory of the command (optional)env
, additional environment variables for the game (optional)frequency
, how many times a second should the memory be readtime
, how does one calculate the ingame time in seconds (optional). If not present, realtime will be used.variables
, variables, that can be used to define other components (see below)events
, events, that can trigger splits, resets etc. (see also below)
A variable in the variables
field can be either a the state of the stack pointer at a specific instruction (rsp
), the state of the base pointer at a specific instruction (rbp
) or an integer value at an address in memory (memory
). Addresses of variables can use the values of variables, that were defined prior, and the stack and base pointer can define an offset to be added to the value.
"game_object": {
"type": "rsp",
"address": "0x416da8",
"offset": "0xe20"
}
This defines the variable game_object
to be the value of the stack pointer at instruction 0x416da8
incremented by 0xe20
.
"frames": {
"type": "memory",
"address": "game_object + 0xa8",
"length": 4,
"signed": false,
"byteorder": "little"
}
This defines the variable frames
to be the value in memory at address game_object + 0xa8
. The variable game_object
must be defined prior. Optionally it can specify the length
of the variable, the signed
ness and the endianess (byteorder
). The defaults are 4
and false
for length
and signed
. For byteorder
the native endianess of the system is used (little
on x86
machines).
With the help of the memory access, one can define multi-level variables, meaning variables, whos addresses depend on the value of other variables.
"some_pointer": {
"type": "memory",
"address": "0x123456",
"length": 8,
}
"some_variable": {
"type": "memory",
"address": "some_pointer + 0xc3",
"length": 4
}
Since there is no distinction between variables, that are used as pointers, and variables whose value is directly used, one can chain this to arbitrary length. Note: You want to choose a length, that fits your pointer size. For x86_64
, this is 8
.
An event in the events
field is a json object with the fields name
and trigger
, bound to an identifier.
"gamestart": {
"name": "Game Start",
"trigger": "state.gamestate == 0"
}
This defines the event gamestart
with the name Game Start
to be triggered, if the variable gamestate
is equal to 0
at the current state.
Events can access variables of the current state by prefixing them with state.
. They also can access variables of the state juste before the current state by prefixing them with oldstate.
. This is useful to track changes in the state.
"secret_to_nobody": {
"name": "Trinket - It's a Secret to Nobody",
"trigger": "state.trinkets != oldstate.trinkets"
}
This triggers if the variables trinkets
changes.
A route file defines the route for your speedrun. It is also a json file with the following fields:
name
, the name of your attemtet categorygamefile
, the location of the respective json file for the gamestart
, the event to start the timerreset
, the event to reset the timerroute
, this defines the triggers for the actual route (see below)
The field route
containes the splits. Each split is itself a json object, that can contain subsplits.
'first_level': {},
'second_level': {
'mid_boss' : {}
}
In this example the first split is triggered when the event first_level
happens. The next split would be the first subsplit of second_level
, namely mid_boss
, the next split would be second_level
itself.
The names of the splits reference events as defined in the game file.