// 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 #include #include #include #define DIRECTINPUT_VERSION 0x0800 #include #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(); 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(&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(MapVirtualKeyW(vk, MAPVK_VK_TO_VSC) & 0xFF); std::printf(" keyboard GetDeviceState hr=0x%08lX dik=0x%02X state=0x%02X\n", static_cast(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(hr), static_cast(static_cast(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; }