5

Sometimes a Linux program requiring fast user interaction/manual dexterity runs too fast for me to keep up. Examples include video games and video players that don't let you control the speed.

How can I slow down the clock for that one process, so that I can interact with the program at a slower rate?

This question is the same as How can I slow down the framerate of a Flash game?, except that that question's answer is a Windows-only program to do this. I'm therefore asking a related question for Linux.

Mechanical snail
  • 7,963
  • 5
  • 48
  • 67

1 Answers1

3

The answer to this question would be to write replacement functions for the POSIX function that the Flash plugin (or the JavaScript engine for a modern game) is using for timing purposes. Since you probably don't know which function it is, and there's more than one function on Linux that could be used for this purpose, it'll take some trial and error.

These same functions are likely to be used by OpenSSL for cryptographic purposses. Therefore, overriding them with versions that lie may cause errors with HTTPS and JWT.

You would put the definitions in a shared-object file (.so), and then put the path to that file in your LD_PRELOAD envirionment variable, so that your alternate definitions take precedence over the real ones.

Functions that might be used to control a game's speed.

  • gettimeofday - returns the time with microsecond precision.
  • clock_gettime / clock_settime - like gettimeofday but more complicated.
  • select (can be used to implement sleep() and usleep())

Signal-based timers probably aren't being used for this, but just in case:

  • sigtimedwait
  • getitimer / setitimer

There are also sleep functions that I wouldn't use to control the speed of a game, but others might attempt it, such as usleep.

There may be others, but I didn't find any. On DOS, games used a function called clock() for this, and Linux has this function, but on Linux it measures CPU time used by the currently running program, not the amount of time that has passed on the system clock.

Overriding technique

Initialization

Define a function with __attribute__(constructor) in your library. It is responsible for loading the real versions of the library functions you're going to replace. Your functions will just call the real functions, and then change the output to reflect the time you want to have elapsed instead of the time measured by the hardware. Below, I'll make the function gettimeofday report half the time that has actually passed, which would make a game run at half speed:

#define TIME_SCALING_FACTOR 2.0

#include <sys/time.h> #include <dlfcn.h>

void libc; int (real_gettime)(struct timeval tv, struct timezone tz);

double tv2dbl(struct timeval *tv) { return (double)tv->tv_sec + (double)tv->tv_usec / 1000000.0; }

void dbl2tv(double time, struct timeval tv) { tv->tv_sec = time; double usec = ((double)(tv->tv_sec) - time) 1000000.0; usec = -usec; usec += 1; tv->tv_usec = usec; }

/* The "restrict" is required for this function to match

  • the type signature from the GNU Libc header. It might
  • not be required if you're using a different libc.

*/

int gettimeofday(struct timeval restrict tv, void restrict tz) { /* For illustrative purposes only. This is not * thread-safe and would probably break if used * in a Web browser. But the real solution is * complicated and pulls in another dependency. */ static double last_time; static double purported_time; static int have_last_time = 0; int status = real_gettime(tv, tz);

    if(status == -1) return -1;

    if(!have_last_time) {
            have_last_time = 1;
            last_time = tv2dbl(tv);
            purported_time = last_time;
    } else {
            double this_time = tv2dbl(tv);
            double real_elapsed = this_time - last_time;
            purported_time += (real_elapsed / TIME_SCALING_FACTOR);
            dbl2tv(purported_time, tv);
            last_time = this_time;
    }

    return 0;

}

attribute((constructor)) void halftime_init() { libc = dlopen("libc.so.6", RTLD_LAZY);
real_gettime = dlsym(libc, "gettimeofday"); /* No printing functions of any kind are available when this function runs. Therefore, your only indication of a failure is a SIGSEGV. Also note that the filename of libc differs from system to system. Most Linux systems will have libc.so.6. */ }

Compiling

cc -shared -fPIC -o libhalftime.so -c halftime.c

The LD_PRELOAD variable

It must first specify libdl because of dlopen and dlsym, and then it names the library file containing the alternate version.

export LD_PRELOAD=libdl.so.2:./libhalftime.so
exec chromium

There used to be a way to do it without using dlopen, but I don't remember the details, and it might be disallowed now.

There are security rules in ld.so that might cause it to ignore LD_PRELOAD. They mostly pertain to setuid/setgid binaries and "capabilities". The ld.so man page has details.