This is an attempt at creating an "open source Steam Input". Specifically the Gamepad APIs translating part.
This project aims to hook and re-implement various gamepad APIs such as XInput over SDL3.
The goal is to allow old and new games to support any gamepad controller supported by SDL3 (over 200+) out of the box with zero configuration.
If you are a game developper and you'd like your game to support a variety of gamepad controllers without rewriting your game, this project might be of interest to you.
XInput API calls ("Debug mode")
| API | Status |
|---|---|
| GameInput | 👷 Under development |
| XInput | ✔️ Working |
| DInput8 | |
| DInput(1-7) | |
| WinMM | ✔️ Working |
Steam Input API
Steam Input API only games. You need an action set to translate input. Hooking these API is going down the Steam Enulator rabbit hole.
Not sure yet how best to handle this. Meanwhile when using a Steam Emulator that translates Steam Input to XInput you can leverage InputFusion to then translate XInput to SDL.
HID / Raw
These APIs aren't really like the standardised Gamepad APIs like XInput. They are much akin to low level access.
SDL mostly uses these low level APIs.
If a game uses these APIs to add support for a specific Gamepad; The game devs probably have a certain experience in mind and we shouldn't interfere with it.
Many mods and other 3rd party "fix" rely on these low level access to do their job. And they often complain about the new Steam Input capabilities of hooking system wide all relevant APIs for gamepad while Steam is running.
As such, I do no think these API are relevant for my project (for now).
WGI: Windows.Gaming.Input Namespace
Originaly designed for UWP/WinRT apps, now legacy, it can be used in win32 apps with some effort.
InputFusion can be used either as
- A) a drop-in replacement (DLL side-loading) or
- B) by being injected into a target process.
Unless you know what you are doing stick with the first approach.
Use xinput*.dll or dinput8.dll or winmm.dll depending on which API the game is using.
Replace the corresponding DLL in the game directory if present, otherwise place it next to the game's executable.
In the case of XInput, this might require a little guess work to find which version of XInput the game is using: xinput1_4.dll, xinput1_3.dll, xinput9_1_0.dll.
NB: If necessary, you can opt-in to also hook/detour API calls to force the game to use the ones from the DLL. See Env Var section below.
Important
Before executing and injecting into your target process, you must specify which API you intend to hook/detour. This is achieved by setting the corresponding environment variable for the desired API.
If you do not configure the environment variable before injection, the process will only initialize SDL without performing any meaningful actions.
For details please refer to the Env Var section below.
You need a DLL injector to inject InputFusion into the target process.
A quick google search will find you plenty on GitHub.
🐧 Linux: the classic combo createRemoteThread() + LoadLibrary() from Kernel32 works under Wine/Proton.
Alternatively, here are some of my own:
-
CLI launcher with DLL Injection, Lua Scripting, Splash screen, and other goodies.
Example (xan105/Mini-Launcher)
{ "bin": "Binaries/NMS.exe", "env": { "GAMEPAD_API_XINPUT": "HOOK" }, "addons": [ { "path": "Binaries/InputFusion.asi", "required": true } ] } -
Node.js NAPI Native addon for Windows DLL injection with support for Wow64 and Unicode path.
Example (xan105/node-remote-thread)
import { env } from "node:process"; import { spawn } from "node:child_process"; import { dirname } from "node:path"; import { createRemoteThread } from "@xan105/remote-thread"; const EXECUTABLE = "G:\\METAPHOR\\METAPHOR.exe"; const ADDON = "G:\\METAPHOR\\InputFusion.dll"; const ARGS = []; const binary = spawn(EXECUTABLE, ARGS, { cwd: dirname(EXECUTABLE), stdio:[ "ignore", "ignore", "ignore" ], detached: true, env: { ...env, "GAMEPAD_API_XINPUT": "HOOK" } }); binary.once("spawn", () => { binary.unref(); createRemoteThread(binary.pid, ADDON); });
Tip
Consider changing the file extension from .dll to .asi to help prevent false positive with Windows Defender.
🧪 Mostly experimental features are behind env. var. flags.
For people unfamiliar with environment variables, you can instead use the provided InputFusion.ini config file. Place it next to the DLL.
Note that Environment variables supercede the config file!
BATTERYLVL
When enabled the LED light of the controller is used to show the battery level of the controller:
- 100% / Green
- 75% / Yellow
- 50% / Orange
- 25% / Red
Currently only available for PS4/PS5 controller in wireless.
Disable motor vibration. This includes "Impulse Triggers" on newer Xbox controllers.
Enable GameInput functions hooking / detouring.
This forces the use of the GameInput functions from the DLL when calling GameInput functions.
Enable XInput functions hooking / detouring.
This forces the use of the XInput functions from the DLL when calling XInput functions.
All XInput slots will report as connected, returning dummy data when necessary.
This helps with games that do not properly handle gamepad disconnection (hot reload), as they may not retry after receiving ERROR_DEVICE_NOT_CONNECTED.
When enabled XInputGetCapabilitiesEx() returns the real VID/PID (Vendor ID / Product ID) of the device, as reported by SDL or fallbacks to a Xbox 360 controller when they aren't available.
By default InputFusion returns the VID/PID of a Xbox 360 controller.
Enable DInput8 functions hooking / detouring. This forces the use of the DInput8 functions from the DLL when calling DInput8 functions.
Do not return any devices when enumerating devices with DInput8.
Enable DInput 1-7 functions hooking / detouring. This forces the use of the DInput functions from the DLL when calling DInput functions.
Enable WinMM functions hooking / detouring. This forces the use of the WinMM functions from the DLL when calling WinMM Joystick related functions.
Use the modern Xbox controller layout with WinMM. Useful for modern games that decided to use a legacy API for a Xbox controller such as Deadly Premonition.
SDL has the ability to change the mapping of an existing gamepad or add support for gamepads that SDL is unaware of using env. var.:
You can also set these values through InputFusion.ini config filer under [SDL] > CONTROLLER_MAPPING and [SDL] > CONTROLLER_MAPPING_FILE
The mapping string has the format: GUID,name,mapping.
Example of a valid mapping for a Dualshock4 v2:
"03008fe54c050000a00b000000016800,*,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,touchpad:b11,crc:e58f,platform:Windows,"
🎮 Buttons can be used as axes and vice versa.
Example to swap the D-PAD with the Left Joystick for retro gaming:
[...] dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0,-leftx:h0.8,+leftx:h0.2,-lefty:h0.1,+lefty:h0.4, [...]
📄 For more details, please kindly see the SDL documentation.
Tip
SDL testcontroller is a GUI tool that can be used to create a string mapping (Setup Mapping > Copy).
A precompiled build can be found in this repo /test/SDL_testcontroller folder.
Games have different layout expectation depending on their era and/or gamepads they support. For practical reasons, this project tries to mimic a Xbox controller unless otherwise specified. Very old and legacy APIs tend to have a more custom/retro layout to match their era.
- XInput:
As an Xbox controller
- DInput8:
As an Xbox controller therefore it has the same limitations as a real Xbox controller with DInput such as no individual trigger axis.
- DInput (1-7):
As DInput8.
- WinMM:
Note
After considering the games from that era that could be played with a gamepad. I somewhat deviated from the "usual" modern mapping for this implementation.
The D-Pad and the left joystick both map the X/Y axis, usually used for movement. The right joystick maps to Z/R axes and to the POV when pressed + direction, usually used to move the camera point of view. The right and left trigger are mapped to buttons. Buttons order is tweaked to allow triggers in games supporting a limited amount of buttons and start/options to usually end up as the PAUSE button.
Tip
If for some reasons you'd like the modern Xbox controller layout with WinMM, you can use the env. var. GAMEPAD_API_WINMM_LAYOUT=XBOX or set [WinMM] > XBOX_LAYOUT to true in the config file.
Wine/Proton already translates Windows gamepad API calls. So it's a bit redundant, but yes this project does work under Wine/Proton. You just need to tell Wine/Proton to load the dll instead of its built-in:
Example:
WINEDLLOVERRIDES="xinput1_4=n,b
-
While waiting for SDL to initialize, the first call to any of InputFusion's exported functions is stalled and may take longer than usual.
-
Games that support more than one input API usually, but not always, do a lot of Win32 APIs sniffing behind the scenes to determine which input API to use. Therefore even tho an input API is translated to SDL, your gamepad may still not work due to how a game engine is programmed and how it decides to handle input. The focus of this project is for now on API translation and not on compatibility for poorly engineered games.
-
What happens when InputFusion replaces an API that SDL also relies on to communicate with your gamepad? This creates a problem potentially causing an infinite loop, stack overflow, or other unexpected behavior. InputFusion avoids this problem by forcibly disabling the use of any APIs within SDL that would cause such conflict.
🆚 Visual Studio 2026
📦 Vendor dependencies:
- Microsoft Detours library
- SDL3 Library recompiled as a static lib.
Solution: ./vc/InputFusion.sln
The 6 projects inside the solution control the build output:
- InputFusion ->
InputFusion.dll(DLL injection) - GameInput ->
gameinput.dll(DLL sideloading / injection) - XInput ->
xinput1_4.dll(DLL sideloading / injection) - DInput8 ->
dinput8.dll(DLL sideloading / injection) - DInput ->
dinput.dll(DLL sideloading / injection) - WinMM ->
winmm.dll(DLL sideloading / injection)
Output: ./build/${project}/output/${platform}/${config}
./.github/workflows/vs-build-on-windows.yaml
Build all targets (debug and release) and create a release.
- Why LGPL-3.0?
The LGPL allows InputFusion to be used in proprietary software while ensuring that improvements to the project remain available under the same license.
- Can I use InputFusion commercially?
Yes! Commercial use is permitted, including in proprietary applications. InputFusion is provided as a shared library, so your application does not need to be open-source just because it uses InputFusion.
- Can I modify the source code?
Yes! You can modify InputFusion to suit your needs. However, if you distribute your modified version, you must provide the corresponding source code under the LGPL-3.0.
- Do I have to open-source my project?
No! Using InputFusion does not mean you have to publish your application's source code. You can keep your project proprietary.
- Are there any fees or royalties?
No! InputFusion is free to use, including commercially.
- Need more details?
This FAQ covers common use cases in plain English. For the full terms and conditions, including requirements that may apply when distributing InputFusion or software that uses it, please refer to the LGPL-3.0 license included with this project.

