libTAS is a software for Linux that provides TAS tools to games, such as frame advance, inputs recording, savestates. It is not a Linux emulator as games are running natively on the user system, but it creates an intermediate layer between the game and the operating system, feeding the game with altered data (such as inputs, system time). We can call such tool a translayer
- a code translation layer. It tries to make the game running deterministically, although it is still an issue when dealing with multithreaded games. This layer connects to an external program to provide a graphical interface to the user, with tools such as input editor or RAM watch/search.
last edit: 07/2018
I would like to present a project that I've been working on in the past months that brings TAS tools to GNU/Linux games. In short, this program works similarly to Hourglass. It consists on a program and a library. The library is loaded with the game, to override some API functions that the game calls. The program and the library communicate with a Unix socket. The program is in charge of gathering inputs and send them to the game, record/playback movie files, savestates, and offer a user interface.
This project is still highly experimental and there are many things still to implement, but it already runs a bunch of commercial games correctly. The main lacking functionality right now is savestate support. Many ideas come from the Hourglass project, thanks to all the people that took part in its development. The source code can be found here
If think there is a benefit in developing a tool for GNU/Linux even if there is already one for Windows and the catalog of games is much larger on Windows:
- OS are different, and games may be coded differently on different platform, so the compatibility list can be different even with the same approach
- Part of the design are much easier on GNU/Linux that on Windows. For example, hooking functions in Windows requires multiple operations (replacing the first instruction of the function with a jump, saving that instruction to execute the original function). On GNU/Linux, it simply consists on declaring a function with the same name as the function to hook and use the LD_PRELOAD trick
- If necessary, it is easier to mess with the core system by modifying the Linux kernel
Here is a description of the different features currently implemented in the program:
This project aims primary at supporting games based on SDL/OpenGL. This concerns many indie games (Super Meat Boy, Volgarr, VVVVVV, Limbo, Braid, Dustforce, etc.), including games written originally on XNA framework and ported to GNU/Linux using FNA
(TowerFall Ascension, FEZ). There is also a support for low-level functions, so games using other librairies (Unity, GM:S) may have basic support.
Frame Advance is performed by overriding the different draw functions. Frame boundaries are located just after the screen draw, and it is where the communication between the program and the game is done.
All sleep calls by the game are intercepted and are delayed until the next frame boundary. A sleep call is done at each frame boundary to get a normal game speed, or is bypassed to obtain a fast-forward. All OpenGL render commands are skipped during fast-forward for a big speed boost.
SDL1 Joystick and SDL2 GameController APIs are captured, as well as Linux jsdev and evdev interfaces. The program support mapping keys to joystick buttons, and there is a specific controller input window to send analog inputs (sticks, triggers).
SDL Mouse API and Xlib API are captured. The pointer is made visible to be able to perform inputs frame by frame. For games using the absolute values of the coordinates (FTL, Dustforce), it works fine. For games using an ugly combination of pointer warping and using relative movement (Braid), there is a constant offset between the OS pointer and the game pointer that needs to be addressed.
Keyboard, mouse and controllers inputs can be saved in a file and played back. There is a basic input editor to modify inputs during recording/playback (including analog inputs), and to insert or delete frames.
There is a deterministic timer implemented to advance time by 1/fps at each frame boundary. It gives the time to every function accessing time. There are several problems on multithreaded games:
- Threads can have a loop that waits for time to advance. The current solution is to advance time by a tiny amount after enough time queries have been made by this thread, which is a potential source of desyncs.
- Extra threads are basically uncontrolled because they are not related to frame increments. One example of such thread causing sync issues are loading threads, i.e. threads that perform a job while the main thread is still running, and notify the main thread to continue when it has finished.
Video coming from either an OpenGL rendering or a SDL software rendering is captured and encoded using ffmpeg's avcodec/avformat library.
Audio playback and dumping
Audio sources are captured and the mixing is done by our program. This allows us to control the playback of the audio (pause it, skip it during fast-forward, etc.). Audio can also be dumped that way, and encoded using ffmpeg. Currently SDL Audio and OpenAL librairies are supported. Also, ALSA and PulseAudio Simple are hooked so that unsupported librairies (e.g. FMOD) that output sound using one of those drivers can still be captured.
Some information can be displayed on top of the game screen. Currently frame count, inputs, log messages and ram watches are displayed. This works for OpenGL and SDL software renderers.
Savestates are implemented by suspending all threads (using signals) and dumping the whole game process memory. Savestates can be either stored on disk or on RAM. Also, there is an incremental savestate feature to lower the size of savestates: after the first savestate, every memory page that is written to by the game is flagged (using the recent soft-dirty
bit), so only modified memory is saved on a subsequent savestate. This requires extra work to save/load a savestate, but drastically reduces savestate size.
For TAS, we want to prevent the game from saving, and also store savefiles in savestates. I hooked file IO functions to detect the opening of files that are possibly savefiles (currently regular files with write access). When such a file is detected, I load the file in memory and return a file descriptor to it (using memfd_create or open_memstream). That way, modifications are made on memory and the file content is intact.
The user interface was implemented usng Qt5.
A basic RAM Search/RAM Watch feature is implemented. You can search on every kind of memory segments of the game (code/static data/heap/anonymous memory/etc.). You can search for integers (signed/unsigned 1-byte -> 8-byte) and float/double. You can filter results by comparing against the previous value or a constant value. For games with high memory usage, the memory search can break if stores too many values (> 1 GB for me).
RAM watches can also store pointers (and pointers to pointers, etc.). To be able to get watches that are still valid upon game restart, there is a pointer scanning feature (similar to Cheat Engine). You can search for chains of pointers->offset->pointer->offset->... from a static address to the address you are looking at. Finally, you can poke values into game memory.