perkaholic.
Hybrid trainer for BO3 Zombies under Wine/Proton. An XInput proxy DLL inside the game owns reads, writes, aim, visibility, and ESP command production. A native Wayland layer-shell overlay owns drawing and input routing. Both halves communicate through a binary IPC frame at /tmp/perkaholic-ipc.bin.
SYSTEM.TOPOLOGY
LAYER 01 / LOADER
XINPUT9_1_0.dll proxy
WINEDLLOVERRIDES=xinput9_1_0=n,b
LAYER 02 / INTERNAL
MinGW C/C++ DLL
BO3 hooks · refdef projection · aimbot · ESP queue
LAYER 03 / IPC
Z:\tmp\perkaholic-ipc.bin ⇄ /tmp/perkaholic-ipc.bin
binary frame · seq + commands · Wine Z: maps Linux /tmp
LAYER 04 / OVERLAY
Rust + wgpu + wlr-layer-shell
egui menu · click-through · IPC consumer
Two processes, one trainer. The DLL owns everything that needs engine state; the overlay owns everything the user sees. Neither side crashes when the other is missing.
Hybrid Architecture
DLL-internal hooks own fragile game state reads, writes, aimbot, and ESP command production. The native overlay owns drawing and input. Keeps engine-facing work where engine state lives and Wayland rendering out of Wine.
- DLL lives inside the game process — no cross-process guessing for engine state
- Overlay is a native Linux Wayland process — no Wine windowing path for drawing
- Both halves can start and stop independently without crashing the other
- IPC frame at /tmp/perkaholic-ipc.bin bridges the two with seq numbers and overflow tracking
XInput Proxy Loader
XINPUT9_1_0.dll exports the full XInput entry-point surface and defers runtime startup until BO3 calls into it. DllMain stays minimal so loader-lock under Proton cannot bite.
- Exports XInputGetState, XInputSetState, and companion entry points
- Runtime startup deferred to first XInput call — DllMain does nothing
- Installed via WINEDLLOVERRIDES=xinput9_1_0=n,b in Steam launch options
- objdump import check verifies no eager d3d / dxgi / dwmapi dependencies
Internal ESP Queue
DLL reads zombie slots from the Zombies actor table, projects positions with BO3 refdef, runs visibility through the internal engine path, and queues primitives into a shared draw format consumed by the overlay.
- Zombie actor table walk — slots read from live BO3 engine state
- World-to-screen projection via captured refdef data
- Internal-engine visibility check where available
- Queues boxes, lines, circles, and labels into imgui_esp_text_queue format
Binary IPC Frame
DLL publishes the latest draw frame to Z:\\tmp\\perkaholic-ipc.bin — the same path the Linux overlay reads as /tmp/perkaholic-ipc.bin. Seq numbers and overflow fields let the overlay detect stale frames.
- Atomic write via temp file + rename to avoid half-read frames
- Seq number increments every publish — overlay detects staleness by seq delta
- Overflow field carries count when the command queue exceeds one frame
- Publish failures (open, move, short write) are logged and ignored — no crash
Wayland Layer-Shell Overlay
wlr-layer-shell overlay surface sits above fullscreen toplevels. Default keyboard_interactivity=NONE with an empty input region so all events pass through to BO3. Input region recomputes only when the menu opens.
- Layer: overlay — composited above fullscreen game surfaces
- Empty input region by default — click-through at all times outside the menu
- Input region set to menu rect when menu is open, cleared on close
- No mouse grab while scanning; grab only inside the active menu rectangle
Aimbot + Trainer Internals
Aimbot and trainer writes run inside the game process where engine state is live. Projection, visibility, and target picking all happen engine-side. Raw memory access is a fallback diagnostic path only.
- Aimbot target selection from the same actor table the ESP queue walks
- Trainer writes (ammo, health, round state) go through engine paths where available
- Internal engine visibility used for target filtering — avoids cross-process reads
- Verbose hook log written to AppData/Local/Temp/perkaholic-internal.log
IPC-Only Drawing
The overlay is a pure draw-only IPC consumer. When the DLL frame is missing or stale the overlay draws nothing — it logs the transition and waits. It does not attach to BO3 and does not grab mouse input.
- Draws nothing when the IPC frame is missing or stale — logs the transition, no crash, no scan
- No BO3 attach and no mouse grab — click-through always, draw-only consumer
- DLL keeps publishing even when the overlay is absent; IPC write failures are logged and ignored
- Legacy external reader crates remain in the repo as reference/diagnostic code only, not a runtime fallback
Verbose Debug Trail
Both halves emit a structured log trail. The overlay prints IPC state transitions to stdout. The DLL writes every publish result to the Proton prefix log file. Import table check confirms no eager graphics dependencies.
- Overlay: internal ipc active seq=... / internal ipc inactive or stale; falling back
- DLL: ipc publish seq=... count=... overflow=... / ipc publish open failed ...
- DLL log: AppData/Local/Temp/perkaholic-internal.log inside the Proton prefix
- objdump -p XINPUT9_1_0.dll | rg 'd3d|dxgi|dwmapi' should return nothing
Why the split, and how the halves coordinate
The hybrid split is not an aesthetic choice — it is forced by the constraints of Wine windowing, Wayland compositing, and BO3 engine state availability. A shared binary frame at /tmp/perkaholic-ipc.bin carries draw commands from the DLL to the overlay on every game tick.
Why hybrid
- Wine windowing path cannot host a wlr-layer-shell surface — Wayland extension is compositor-side only
- Engine state (actor table, refdef, visibility) is only coherent inside the game process
- A native Linux process can create a Wayland layer-shell surface above a Wine fullscreen window
- IPC file-based transport costs one rename per frame — cheap enough for game-tick rates
DLL responsibilities
- Captures live BO3 engine state via internal hooks at game-tick rate
- Reads zombie slots from the actor table; projects positions with refdef
- Runs internal-engine visibility check for ESP and aimbot target filtering
- Queues ESP primitives and publishes a binary draw frame to Z:\\tmp\\perkaholic-ipc.bin
Overlay responsibilities
- Creates a wlr-layer-shell surface above fullscreen toplevels via Wayland
- Maintains click-through (empty input region) unless the menu is open
- Reads /tmp/perkaholic-ipc.bin each frame and draws the queued ESP primitives
- Draws nothing when the IPC frame is missing or stale — logs the transition, does not scan for BO3
Failure modes handled
- DLL alive, no overlay: IPC publish failures logged and ignored — game runs normally
- Overlay alive, no DLL: overlay draws nothing, logs stale-frame transitions, no crash
- Stale IPC frame: overlay logs the transition and draws nothing until a fresh frame arrives
- Menu closed: full pointer pass-through — BO3 receives all mouse and keyboard events
How it's built
Internal DLL — hooks, BO3 engine state, refdef projection, ESP command queue (~4.3k LOC across 8 files in internal/src/)
Linux overlay process and five helper crates: overlay, game, mem, math, config (~18k LOC across the workspace)
DLL build toolchain via make internal — cross-compiles the C/C++ DLL for Win64 from Linux
Overlay rendering through egui-wgpu on a Wayland surface — egui menu, ESP draw calls, click-through input regions
Overlay layer above fullscreen toplevels — conditional input region, keyboard_interactivity=NONE by default
WINEDLLOVERRIDES=xinput9_1_0=n,b loads the proxy DLL; Z: drive maps /tmp for the IPC path shared between halves
Verification path
objdump -p target/internal/XINPUT9_1_0.dll | rg 'd3d|dxgi|dwmapi' should return nothing — the DLL must not eagerly import any graphics runtime. Run make internal-test for the focused C helper tests and cargo test --workspace for the Rust crate suite. Both must pass before installing the DLL next to the game.
Default binds
Both plain string mode (menu = "toggle") and legacy table form (menu = { key = "INSERT", mode = "toggle" }) are accepted. Config lives at ~/.config/perkaholic/config.toml.
Installation
Build the overlay
Run cargo build --release in the repo root. The perkaholic binary lands in target/release/ and is the process you run alongside BO3.
Build and install the DLL
Run make internal to cross-compile the proxy DLL with MinGW, then make internal-install to copy XINPUT9_1_0.dll next to BlackOps3.exe. Verify with objdump -p target/internal/XINPUT9_1_0.dll | rg 'd3d|dxgi|dwmapi' — output should be empty.
Launch BO3 with the proxy override
Set the Steam launch options to: PERKAHOLIC_INTERNAL_VERBOSE=1 WINEDLLOVERRIDES=xinput9_1_0=n,b gamescope -f -r 144 -w 1920 -h 1080 -- %command%
Run the overlay
Start perkaholic in a terminal alongside BO3. The overlay creates the Wayland layer-shell surface immediately and begins polling the IPC file. After any rebuild, restart both the perkaholic process and BO3 so each half loads the new binaries.
Get the trainer
Open source. Grab a release build or pull the source and build both halves with cargo build --release and make internal.