To prevent the host application from crashing, your proxy DLL must intercept calls to the original function, execute your custom logic, and then pass the arguments to the real function located in the system DLL.
Example implementation for DirectInput8Create:
type FDirectInput8Create = unsafe extern "stdcall" fn(
hinst: HINSTANCE,
dwversion: u32,
riidltf: *const GUID,
ppvout: *mut *mut c_void,
punkouter: HINSTANCE,
) -> HRESULT;
struct State {
directinput8create: FDirectInput8Create,
}
unsafe impl Send for State {}
unsafe impl Sync for State {}
static STATE: LazyLock<State> = LazyLock::new(|| unsafe {
let dinput8 = LoadLibraryA(PCSTR(b"C:\\Windows\\System32\\dinput8.dll\0".as_ptr())).unwrap();
let directinput8create =
std::mem::transmute(GetProcAddress(dinput8, PCSTR(b"DirectInput8Create\0".as_ptr())));
State { directinput8create }
});
#[no_mangle]
unsafe extern "stdcall" fn DirectInput8Create(
hinst: HINSTANCE,
dwversion: u32,
riidltf: *const GUID,
ppvout: *mut *mut c_void,
punkouter: HINSTANCE,
) -> HRESULT {
patch(); // Your custom logic
(STATE.directinput8create)(hinst, dwversion, riidltf, ppvout, punkouter)
}
#[no_mangle]
#[allow(non_snake_case, unused_variables)]
extern "system" fn DllMain(dll_module: HINSTANCE, call_reason: u32, reserved: *mut c_void) -> BOOL {
match call_reason {
DLL_PROCESS_ATTACH => LazyLock::force(&STATE),
DLL_PROCESS_DETACH => (),
_ => (),
}
BOOL::from(true)
}