Files
CoopAllTheThings/tests/dinput_hook_test.cpp
BlackMark 30eccf749d Apply clang-format across the whole tree
Run clang-format (the repo's .clang-format: LLVM base, 120 cols, tabs,
Allman functions) over every source file so the tree is formatter-clean.
Whitespace only -- no behavior change; full x64 + x86 suites pass.

Also set SortIncludes: false in .clang-format. Windows include order is
load-bearing (windows.h must precede tlhelp32.h / mmreg.h / xinput.h /
dinput.h; winsock2.h must precede windows.h), and the default
alphabetical sort reorders tlhelp32.h ahead of windows.h -- a build
break. Leaving order alone keeps the manual, correct grouping.
2026-07-12 11:52:53 +02:00

145 lines
5.0 KiB
C++

// In-process self-test for the DirectInput forwarding path of the MKB subsystem.
//
// Drives the REAL shipping code (mkb_hook.cpp): creates the IPC block + MKB ring, installs the MKB
// hooks (which vtable-swap IDirectInputDevice8::GetDeviceState via a probe device), pushes a
// synthetic key + mouse-button event through the ring, pumps it (so the hook's synthesized state is
// set), then creates its *own* DirectInput keyboard + mouse device and calls GetDeviceState -- and
// asserts the forwarded input shows up in the returned device state. This process plays host + game.
// Skips cleanly if DirectInput can't acquire a device (e.g. no input stack on a CI box).
#include <cstdint>
#include <cstdio>
#include <windows.h>
#include <objbase.h>
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
#include "coop/protocol.hpp"
#include "coop/shared_memory.hpp"
#include "ipc_client.hpp"
#include "mkb_hook.hpp"
using namespace coop;
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
std::printf("%s %s\n", ok ? " ok:" : "FAIL:", what);
if (!ok) {
++g_failures;
}
}
// A hidden window so DirectInput devices can SetCooperativeLevel/Acquire.
HWND make_window()
{
WNDCLASSEXW wc{};
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = DefWindowProcW;
wc.hInstance = GetModuleHandleW(nullptr);
wc.lpszClassName = L"coop_dinput_test";
RegisterClassExW(&wc);
return CreateWindowExW(0, wc.lpszClassName, L"", WS_OVERLAPPEDWINDOW, 0, 0, 16, 16, nullptr, nullptr, wc.hInstance,
nullptr);
}
// Create + acquire a DI device of `kind` (GUID_SysKeyboard / GUID_SysMouse) with `fmt`. Returns null
// on any failure (treated as a skip).
IDirectInputDevice8W* make_device(IDirectInput8W* di, const GUID& kind, const DIDATAFORMAT* fmt, HWND hwnd)
{
IDirectInputDevice8W* dev = nullptr;
if (FAILED(di->CreateDevice(kind, &dev, nullptr)) || dev == nullptr) {
return nullptr;
}
if (FAILED(dev->SetDataFormat(fmt)) || FAILED(dev->SetCooperativeLevel(hwnd, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))
|| FAILED(dev->Acquire())) {
dev->Release();
return nullptr;
}
return dev;
}
} // namespace
int main()
{
const bool com = SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));
// Host side: the IPC block + a couple of forwarded events.
SharedMemory shm;
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock))) {
std::printf("Could not create shared memory -- skipping.\n");
return 0;
}
auto* block = shm.as<SharedBlock>();
block->version = kProtocolVersion;
block->sequence.store(0, std::memory_order_relaxed);
block->magic = kProtocolMagic;
hook::IpcClient ipc;
check(ipc.connect(10, 5), "IPC client connect");
check(hook::install_mkb_hooks(ipc), "install MKB hooks");
// Forward 'A' down and a left-mouse-button down, then pump them into the hook's synth state.
const UINT vk = 'A';
MkbEvent key{Mkb_KeyDown, vk, 0, 0};
MkbEvent mouse{Mkb_MouseDown, 0 /*left*/, 100, 80};
push_mkb_event(block->mkb, key);
push_mkb_event(block->mkb, mouse);
hook::mkb_pump(ipc);
HWND hwnd = make_window();
IDirectInput8W* di = nullptr;
if (FAILED(DirectInput8Create(GetModuleHandleW(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8W,
reinterpret_cast<void**>(&di), nullptr))
|| di == nullptr) {
std::printf("DirectInput8Create failed -- skipping.\n");
hook::remove_mkb_hooks();
return 0;
}
// Keyboard: GetDeviceState must show our forwarded key (at its DIK = scan code).
if (IDirectInputDevice8W* kbd = make_device(di, GUID_SysKeyboard, &c_dfDIKeyboard, hwnd)) {
BYTE keys[256] = {};
const HRESULT hr = kbd->GetDeviceState(sizeof(keys), keys);
const BYTE dik = static_cast<BYTE>(MapVirtualKeyW(vk, MAPVK_VK_TO_VSC) & 0xFF);
std::printf(" keyboard GetDeviceState hr=0x%08lX dik=0x%02X state=0x%02X\n", static_cast<unsigned long>(hr),
dik, keys[dik]);
check(SUCCEEDED(hr), "keyboard GetDeviceState succeeded");
check((keys[dik] & 0x80) != 0, "forwarded 'A' appears in the DirectInput keyboard state");
kbd->Unacquire();
kbd->Release();
} else {
std::printf(" keyboard device unavailable -- skipping keyboard assertion.\n");
}
// Mouse: GetDeviceState must show our forwarded left button.
if (IDirectInputDevice8W* ms = make_device(di, GUID_SysMouse, &c_dfDIMouse, hwnd)) {
DIMOUSESTATE m{};
const HRESULT hr = ms->GetDeviceState(sizeof(m), &m);
std::printf(" mouse GetDeviceState hr=0x%08lX btn0=0x%02X\n", static_cast<unsigned long>(hr),
static_cast<unsigned>(static_cast<BYTE>(m.rgbButtons[0])));
check(SUCCEEDED(hr), "mouse GetDeviceState succeeded");
check((m.rgbButtons[0] & 0x80) != 0, "forwarded left button appears in the DirectInput mouse state");
ms->Unacquire();
ms->Release();
} else {
std::printf(" mouse device unavailable -- skipping mouse assertion.\n");
}
di->Release();
hook::remove_mkb_hooks();
if (hwnd != nullptr) {
DestroyWindow(hwnd);
}
if (com) {
CoUninitialize();
}
std::printf(g_failures == 0 ? "PASS dinput_hook_test\n" : "FAILED dinput_hook_test (%d)\n", g_failures);
return g_failures == 0 ? 0 : 1;
}