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.
145 lines
5.0 KiB
C++
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;
|
|
}
|