XInput proxy DLL that filters controllers per game instance, surviving Steam overlay function hooking.
Built for Nucleus Co-op split-screen scenarios where each instance should see only its assigned controller.
Fixed issue emerged by latest Windows/Steam updates (circa 15.04.2026), where controller input goes to every Nucleus instance, regardless of settings or HidHide.
Copy whole repo or just get xinput1_3.dl_ files from x64/x86 dirs. Backup NucleusCoop\utils\XInputPlus\x86 and x64 dirs. Copy and replace those two files to NucleusCoop\utils\XInputPlus\x86 and x64 respectively.
When using XInputPlus-style proxy DLLs to separate controllers between game instances, Steam overlay (gameoverlayrenderer.dll) overwrites the proxy's XInputGetState export with a JMP detour to its own handler. Steam's handler calls the system xinput1_4.dll directly, completely bypassing the proxy's controller filtering. This causes all instances to respond to all controllers.
This happens even when the Steam overlay is disabled in Steam settings — the DLL is still injected and hooks are still installed.
| Approach | Why it fails |
|---|---|
| Using HidHide to hide duplicated controlers in system | No duplicated controlers in system, but input still goes to every instance |
| Disabling Steam Input for the given game | No effect, still duplicated input |
| Restoring original function bytes | Steam re-hooks immediately (every frame) |
| Reading bytes from DLL on disk | The compiler emits JMP thunks — disk bytes are also JMPs |
| Goldberg Steam Emulator | Bundled version too old for modern Steamworks SDK |
Stub gameoverlayrenderer.dll in game folder |
Steam injects via full absolute path, ignores local DLL |
| Disabling overlay in Steam settings | DLL is still injected and hooks still installed |
Instead of fighting Steam's hook, take it over:
- A background thread sleeps 3 seconds (giving Steam time to install its hooks)
- Reads the
JMP rel32target address from our hookedXInputGetState— this is Steam's internal handler - Overwrites the
JMPto point at our filtering trampoline instead - Our trampoline applies the controller map from
XInputPlus.ini, then calls Steam's handler with the remapped index - Monitors for 60 seconds in case Steam re-hooks
Game calls XInputGetState(0)
→ [our xinput1_3.dll export]
→ [JMP — rewritten to point at our trampoline]
→ hooked_getstate(0)
→ g_map[0] == -1? → return ERROR_DEVICE_NOT_CONNECTED
→ g_map[0] == 2? → call Steam_handler(2, pState)
→ Steam overlay internals
→ system xinput1_4.dll
Requires Visual Studio Build Tools (or full Visual Studio) with the C++ desktop workload.
build.batProduces 4 DLLs:
x86\xinput1_3.dll x86\xinput1_4.dll
x64\xinput1_3.dll x64\xinput1_4.dll
Choose the DLL matching your game's architecture (x86/x64) and XInput version (1_3/1_4).
Most older games (XNA, Unity 4) use xinput1_3.dll. Newer games often use xinput1_4.dll.
- Build or download the DLLs
- Pick the correct variant for your game (e.g.
x86\xinput1_3.dllfor Terraria) - Replace
NucleusCoop\utils\XInputPlus\x86\xinput1_3.dl_(or the x64 equivalent) with the built DLL
(Nucleus renames.dl_→.dllwhen copying to game instances) - Ensure the game handler has
Game.XInputPlusDll = ["xinput1_3.dll"](orxinput1_4.dll) - Launch via Nucleus as usual
The DLL writes a log file (xinput_proxy.log) next to itself for diagnostics.
The proxy writes xinput_proxy.log in the same directory as the DLL. Nucleus cleans up instance folders after the game exits, so copy the log while the game is still running.
Instance folders are located at: NucleusCoop\content\<GameName>\Instance0\, Instance1\, etc.
| Log line | Meaning |
|---|---|
XInput proxy loaded |
DLL is being loaded — basic setup works |
Counter-hooked XInputGetState: Steam(...) -> filter(...) |
Steam overlay was detected and counter-hooked — this is the fix working |
XInputGetState not hooked by Steam |
No Steam hook found — proxy filtering works directly without counter-hook |
Re-hooked XInputGetState (pass N) |
Steam re-applied its hook and we patched it again |
Failed to load xinput1_4.dll |
System XInput DLL not found — should not happen on any Windows 10/11 |
- Log file is empty or missing — the DLL isn't being loaded. Check that the
.dl_file inutils\XInputPlus\x86\(orx64\) was replaced correctly and thatGame.XInputPlusDllin the handler matches the DLL name. - Log shows
Controller1 = 0for all controllers — Nucleus assigned no controllers to this instance (keyboard+mouse instance). This is correct behavior. - Counter-hook installs but controller still controls both instances — the game may load XInput from a different DLL name. Check which
xinput*.dllthe game imports (use a tool like Dependencies ordumpbin /imports).
| File | Purpose |
|---|---|
xinput1_3.c |
Proxy DLL source — filtering + counter-hook (x86 & x64) |
xinput1_3.def |
Export definitions for xinput1_3.dll |
xinput1_4.def |
Export definitions for xinput1_4.dll (adds XInputGetAudioDeviceIds) |
build.bat |
Build script — produces all 4 variants |
- Terraria (XNA Framework 4.0, x86, xinput1_3) via Nucleus Co-op v2.4.1
- Windows 11 Pro, Steam overlay enabled and disabled
- Single physical Xbox controller, one instance keyboard+mouse / one controller
- Single physical Playstation Dualsense/DualSense Edge + DS4Windows, one instance keyboard+mouse / one controller
- x86: Steam overlay uses
E9(JMP rel32) hooks — fully tested - x64: Steam overlay uses
FF 25(indirect JMP with 8-byte address) orE9— supported, not yet field-tested
MIT