-
Notifications
You must be signed in to change notification settings - Fork 185
Speedhack
Speedhack lets you speed up or slow down a game by scaling the time functions it reads. This is useful for games where animations, physics or timers are tied to the system clock. You can make them run faster for speedrunning or slower for precision work.
PINCE has two separate speedhack backends: one for native Linux processes and one for WINE/Proton processes. The right backend is selected automatically based on the attached process.
Speedhack only affects code that reads the time functions PINCE hooks. It does not change CPU speed, GPU speed, network timing or vsync.
Some games won't be affected much. Common reasons:
- The game uses frame-based timing instead of time-based timing
- The game reads time through an unsupported function
- The game reads the CPU timestamp counter directly
- The game is waiting on real-time kernel waits, vsync or network messages
- WINE/Proton game uses something other than
QueryPerformanceCounterfor pacing
If a game crashes or behaves strangely, try a value closer to 1.0.
The speedhack controls are in the scan area of the main window:
-
Enable Speedhack: checkbox that turns the speedhack on and off - Speed value spinbox: appears next to the checkbox, disabled until speedhack is enabled
To use speedhack:
- Attach to a process
- Check
Enable Speedhack - Set the speed multiplier in the spinbox (range
0.2to10.0, step0.1, default1.0) - Uncheck
Enable Speedhackto disable it
Values above 1.0 speed the game up. Values below 1.0 slow it down. For example, 2.0 makes the game run twice as fast, 0.5 makes it run at half speed.
Changing the spinbox value while speedhack is active applies the new speed immediately. You don't need to toggle it off and on again.
When you uncheck Enable Speedhack, the speed resets to 1.0 but the hooks stay installed. This avoids re-patching if you toggle it rapidly. The hooks are fully removed when you detach from the process, launch a new process or close PINCE.
If speedhack fails to enable, you'll see Speedhack couldn't be enabled for this process. and the widgets reset to their default state.
Speedhack has three global hotkeys that work even when PINCE is not the focused window:
- Ctrl+Shift+S: toggle speedhack on/off
-
Ctrl+Shift+Up: increase speed by
0.1 -
Ctrl+Shift+Down: decrease speed by
0.1
The increase and decrease hotkeys automatically enable speedhack if it's not already on. They do nothing if no process is attached.
Hotkey bindings can be changed in the hotkey settings.
For native Linux processes, speedhack hooks three functions in libc:
-
clock_gettime: scaled forCLOCK_REALTIME,CLOCK_MONOTONIC,CLOCK_MONOTONIC_RAWandCLOCK_BOOTTIME. CPU clocks (CLOCK_PROCESS_CPUTIME_ID,CLOCK_THREAD_CPUTIME_ID) and coarse clocks are passed through unchanged -
gettimeofday: always scaled -
nanosleep: inversely scaled. This means sleep duration is divided by the speed multiplier, so sleeping at2xspeed takes half the real time
The hooks try to call the original function through the vDSO first. If the vDSO can't be resolved, they fall back to raw syscalls.
Both x86_64 and i386 processes are supported.
For WINE and Proton processes, speedhack hooks a single function:
-
RtlQueryPerformanceCounterinntdll.dll
This is the only clock that can be safely scaled in WINE. Scaling system time or sleep deadlines would cause hangs because the kernel would wait against a drifted clock.
What this means in practice:
- Games that pace themselves using
QPCwill speed up or slow down correctly. This is most games - Games that use
Sleep,GetTickCountor system time for pacing won't be affected - Real-time waits like frame pacing and vsync won't scale
-
QPCwill drift from system time while speedhack is active
Speedhack works by hooking time functions in the target process and injecting assembly code that scales the returned values. The scaling formula is:
fake_time = fake_base + (real_time - real_base) * numerator / denominator
The idea is to keep two timelines:
-
real_time: the real clock value returned by the OS -
fake_time: the clock value PINCE gives back to the game
When speedhack is first enabled, PINCE stores the current real clock as real_base and uses the current returned value as fake_base. From then on, every time the game asks for time, PINCE checks how much real time passed since real_base, multiplies that difference by the selected speed and adds it to fake_base.
For example, at 2.0 speed:
- Real time starts at
100 - Fake time starts at
100 - Real time later becomes
105 -
105 - 100 = 5real seconds passed -
5 * 2.0 = 10fake seconds should pass - The game receives
110
At 0.5 speed, the same 5 real seconds would become 2.5 fake seconds instead.
The speed value is converted to a fraction (numerator / denominator) for precision. For example, 1.5 becomes 3/2, so the returned time advances 1.5x faster than real time.
When you change the speed while speedhack is already enabled, PINCE re-anchors the bases. It stores the current real time and current fake time as the new base pair before applying the new multiplier. This avoids jumps in time, so changing from 0.5 to 2.0 should continue from the current fake time instead of snapping forward or backward.
Sleep functions need the opposite treatment. If the game asks to sleep for 1 second at 2.0 speed, the real sleep should only take 0.5 seconds. That is why nanosleep is divided by the speed multiplier instead of multiplied.
The hooks are installed by:
- Allocating a code cave in the target process's memory
- Writing assembly code into the cave that reads the real time and scales it before returning the fake value
- Overwriting the first few bytes of the target function with a jump to the cave
- Verifying the write by reading back the patched bytes
All of this happens while the target process is paused via ptrace, so there's no race condition with threads executing the patched code mid-install.
The Wine speedhack requires a trampoline to relocate the overwritten prologue of RtlQueryPerformanceCounter and jump back into the original function. If the prologue contains relative branches, calls, returns or RIP-relative operands that can't be safely relocated, the installation fails.
The Wine speedhack also single-steps threads out of the patch site before overwriting, since QPC is called so frequently that a thread could be mid-prologue when the patch lands.
Start with small changes (0.1 increments) and work your way up. Some games break at very high or very low speeds because their physics engines can't handle the time delta. If a game crashes or behaves strangely, try a lower speed.
Speedhack is especially useful when combined with scanning. If you're searching for a timer or countdown value, slow the game down so you have more time to scan between changes. If you're searching for an animation frame counter, speed the game up to see more values in less time.
Unchecking Enable Speedhack resets the speed to 1.0 but leaves the hooks in place. This is by design as it avoids the overhead of re-patching if you toggle speedhack frequently. The hooks are fully removed when you detach from the process, launch a new process or close PINCE.
If speedhack fails to enable, check the logs for details. Common failure reasons include:
- No time functions could be located (Linux)
-
ntdll.dllexport couldn't be resolved (WINE) - Memory allocation failed
- The function prologue couldn't be safely relocated (WINE)