Tuxinjector is NOT legal to use in speedrun.com submissions or MCSR Ranked yet. Do not use it in runs you intend to submit or in ranked matches.
Tux Injector is an overlay written in Rust that injects into Minecraft's rendering pipeline on Linux and macOS. It uses LD_PRELOAD (Linux) or DYLD_INSERT_LIBRARIES (macOS) to hook into the game's OpenGL and GLFW calls, rendering directly into the backbuffer with no external capture or compositing overhead.
Tuxinjector compiles to a per-architecture shared library on Linux (e.g. tuxinjector_x64.so, tuxinjector_aarch64.so) or a universal binary on macOS (tuxinjector.dylib) that gets loaded before the game starts. When the JVM calls dlsym to resolve OpenGL and GLFW functions, tuxinjector intercepts those lookups and returns its own wrappers. The wrappers stash the real function pointers and add overlay logic before/after forwarding to the originals.
Game launch:
LD_PRELOAD=tuxinjector_x64.so minecraft # Linux
DYLD_INSERT_LIBRARIES=tuxinjector.dylib minecraft # macOS
1. Game's JVM loads -> dlsym("eglSwapBuffers") -> tuxinjector's hooked dlsym
2. Hooked dlsym: stash real eglSwapBuffers, return hooked_egl_swap_buffers
3. Every frame: game calls hooked_egl_swap_buffers
4. Hook: render_overlay() -> draw scene into backbuffer -> call real eglSwapBuffers
5. Buffer is presented with overlay composited on top
Input works the same way - dlsym("glfwSetKeyCallback") gets intercepted, the game's callback is stashed, and our wrapper gets installed instead. The wrapper handles hotkeys and key rebinds before forwarding events to the game.
On macOS the hook mechanism is slightly different: dlsym itself is interposed via __DATA,__interpose (Mach-O linker feature) instead of PLT hooking, and GLSL shaders are patched down from 300 ES to 1.20 at runtime for Apple's GL 2.1 compatibility context.
For a deeper look at the injection, rendering, and input systems, check the architecture docs.
Set a Wrapper Command in your instance settings under Custom Commands:
env LD_PRELOAD=/path/to/tuxinjector_x64.so
You can also set LD_PRELOAD under the Environment Variables tab instead.
Set the environment variable in your instance settings:
DYLD_INSERT_LIBRARIES=/path/to/tuxinjector.dylib
See the usage docs for full setup instructions.
Everything is configured through Lua files in ~/.config/tuxinjector/. The default config is init.lua, with additional profiles stored in profiles/<name>.lua. It returns a table with nested sub-configs:
return {
display = {
defaultMode = "Fullscreen",
fpsLimit = 0,
},
input = {
mouseSensitivity = 1.0,
keyRebinds = { enabled = true, rebinds = { ... } },
},
theme = {
fontPath = "/usr/share/fonts/truetype/DejaVuSans.ttf",
appearance = { theme = "Purple", guiScale = 0.8 },
},
overlays = {
mirrors = { ... },
images = { ... },
},
modes = { ... },
}Hot-reload is supported - editing any config file (including profile files in profiles/) while the game is running applies changes immediately without needing to restart. Profiles can also be switched live through the in-game GUI.
The Lua API reference covers all the scripting functions for keybinds, mode switching, sensitivity, and more.
Tuxinjector is set up as a Rust workspace split into 12 crates. Splitting things up keeps compile times low and isolates the unsafe GL stuff from everything else.
| Crate | Purpose |
|---|---|
tuxinjector |
Main library: hooks, overlay state, mode system, mirror capture, plugin loader |
tuxinjector-core |
Shared types: Color, geometry, lock-free primitives (RCU) |
tuxinjector-config |
Config types, Lua hot-reload, serde defaults |
tuxinjector-input |
GLFW callback interception, key rebinding, sensitivity scaling |
tuxinjector-render |
Image loading (PNG/JPEG/GIF animation) |
tuxinjector-gl-interop |
Direct GL renderer, GL state save/restore, scene compositor |
tuxinjector-gui |
imgui-rs settings UI (14 tabs), toast notifications |
tuxinjector-lua |
Lua scripting runtime, hotkey actions, config loader |
tuxinjector-capture |
Window overlay capture: PipeWire on Linux, CoreGraphics on macOS |
tuxinjector-plugin-api |
C ABI plugin trait, declare_plugin! macro |
imgui-glow-renderer |
Local fork of imgui-glow-renderer with GLSL 1.20 shader path for macOS GL 2.1 |
libspa |
Local fork of libspa with 32-bit cross-compilation fix (missing flags field on older PipeWire headers) |
nix develop
./build.sh# Ensure pkg-config and OpenGL dev headers are installed
./build.shOn Linux this produces an architecture-suffixed binary (e.g. target/release/tuxinjector_x64.so, tuxinjector_aarch64.so, tuxinjector_aarch32.so, or tuxinjector_x86.so).
On macOS this produces target/release/tuxinjector.dylib as a universal binary (Nix store rpaths are rewritten to system paths automatically).
cargo test # 153 tests across all cratesThis project would never have been possible without the work of the linux and mcsr communities as a whole, but i would like to give a special thanks to:
- tesselslate - for waywall, which toolscreen was modeled around, and which tux injector's Lua API is based off of.
- jojoe77777 - for toolscreen, which laid the groundwork and modeled out the idea for what an injection overlay tool should look like, and how it interacts with the game.
And to everyone who tested any early builds of tux injector, which greatly helped find and iron out various bugs from the codebase.