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.
This commit is contained in:
@@ -34,14 +34,12 @@
|
||||
|
||||
using namespace coop;
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace {
|
||||
int g_failures = 0;
|
||||
void check(bool ok, const char* what)
|
||||
{
|
||||
std::printf("%s %s\n", ok ? " ok:" : "FAIL:", what);
|
||||
if (!ok)
|
||||
{
|
||||
if (!ok) {
|
||||
++g_failures;
|
||||
}
|
||||
}
|
||||
@@ -56,19 +54,15 @@ std::wstring tool_path(const wchar_t* name)
|
||||
void kill_stray_mock_games()
|
||||
{
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (snap == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
PROCESSENTRY32W pe{};
|
||||
pe.dwSize = sizeof(pe);
|
||||
for (BOOL ok = Process32FirstW(snap, &pe); ok; ok = Process32NextW(snap, &pe))
|
||||
{
|
||||
if (_wcsicmp(pe.szExeFile, L"coop_mock_game.exe") == 0)
|
||||
{
|
||||
for (BOOL ok = Process32FirstW(snap, &pe); ok; ok = Process32NextW(snap, &pe)) {
|
||||
if (_wcsicmp(pe.szExeFile, L"coop_mock_game.exe") == 0) {
|
||||
HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID);
|
||||
if (h != nullptr)
|
||||
{
|
||||
if (h != nullptr) {
|
||||
TerminateProcess(h, 0);
|
||||
CloseHandle(h);
|
||||
}
|
||||
@@ -81,27 +75,23 @@ void kill_stray_mock_games()
|
||||
bool inject(unsigned long pid)
|
||||
{
|
||||
const std::wstring dll = deployed_artifact_path(L"coop_hook.dll"); // root is one dir up from tests/
|
||||
if (GetFileAttributesW(dll.c_str()) == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
if (GetFileAttributesW(dll.c_str()) == INVALID_FILE_ATTRIBUTES) {
|
||||
return false;
|
||||
}
|
||||
const DWORD access = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
|
||||
PROCESS_VM_WRITE | PROCESS_VM_READ;
|
||||
const DWORD access =
|
||||
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ;
|
||||
HANDLE process = OpenProcess(access, FALSE, pid);
|
||||
if (process == nullptr)
|
||||
{
|
||||
if (process == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const SIZE_T bytes = (dll.size() + 1) * sizeof(wchar_t);
|
||||
void* remote = VirtualAllocEx(process, nullptr, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
bool ok = false;
|
||||
if (remote != nullptr && WriteProcessMemory(process, remote, dll.c_str(), bytes, nullptr))
|
||||
{
|
||||
auto load = reinterpret_cast<LPTHREAD_START_ROUTINE>(
|
||||
GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"));
|
||||
if (remote != nullptr && WriteProcessMemory(process, remote, dll.c_str(), bytes, nullptr)) {
|
||||
auto load =
|
||||
reinterpret_cast<LPTHREAD_START_ROUTINE>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"));
|
||||
HANDLE th = CreateRemoteThread(process, nullptr, 0, load, remote, 0, nullptr);
|
||||
if (th != nullptr)
|
||||
{
|
||||
if (th != nullptr) {
|
||||
WaitForSingleObject(th, INFINITE);
|
||||
DWORD code = 0;
|
||||
GetExitCodeThread(th, &code);
|
||||
@@ -109,8 +99,7 @@ bool inject(unsigned long pid)
|
||||
ok = code != 0;
|
||||
}
|
||||
}
|
||||
if (remote != nullptr)
|
||||
{
|
||||
if (remote != nullptr) {
|
||||
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
|
||||
}
|
||||
CloseHandle(process);
|
||||
@@ -120,10 +109,8 @@ bool inject(unsigned long pid)
|
||||
// Inject with a few retries: a freshly-launched process can briefly refuse a remote thread.
|
||||
bool inject_retry(unsigned long pid)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (inject(pid))
|
||||
{
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (inject(pid)) {
|
||||
return true;
|
||||
}
|
||||
Sleep(300);
|
||||
@@ -131,8 +118,7 @@ bool inject_retry(unsigned long pid)
|
||||
return false;
|
||||
}
|
||||
|
||||
struct MockGame
|
||||
{
|
||||
struct MockGame {
|
||||
PROCESS_INFORMATION pi{};
|
||||
bool ok = false;
|
||||
|
||||
@@ -144,31 +130,22 @@ struct MockGame
|
||||
std::wstring cmd = L"\"" + exe + L"\" " + args;
|
||||
STARTUPINFOW si{};
|
||||
si.cb = sizeof(si);
|
||||
g.ok = CreateProcessW(exe.c_str(), cmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si,
|
||||
&g.pi) != 0;
|
||||
g.ok = CreateProcessW(exe.c_str(), cmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &g.pi) != 0;
|
||||
return g;
|
||||
}
|
||||
unsigned long pid() const
|
||||
{
|
||||
return pi.dwProcessId;
|
||||
}
|
||||
bool alive() const
|
||||
{
|
||||
return pi.hProcess != nullptr && WaitForSingleObject(pi.hProcess, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
unsigned long pid() const { return pi.dwProcessId; }
|
||||
bool alive() const { return pi.hProcess != nullptr && WaitForSingleObject(pi.hProcess, 0) == WAIT_TIMEOUT; }
|
||||
unsigned long exit_code() const
|
||||
{
|
||||
DWORD code = 0;
|
||||
if (pi.hProcess != nullptr)
|
||||
{
|
||||
if (pi.hProcess != nullptr) {
|
||||
GetExitCodeProcess(pi.hProcess, &code);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
void kill()
|
||||
{
|
||||
if (pi.hProcess != nullptr)
|
||||
{
|
||||
if (pi.hProcess != nullptr) {
|
||||
TerminateProcess(pi.hProcess, 0);
|
||||
WaitForSingleObject(pi.hProcess, 2000);
|
||||
CloseHandle(pi.hThread);
|
||||
@@ -182,16 +159,14 @@ struct MockGame
|
||||
// HookSubsystem). Keeps the mapping alive in `shm`.
|
||||
SharedBlock* make_ipc(SharedMemory& shm, unsigned long pid, std::uint32_t disabled_mask)
|
||||
{
|
||||
if (!shm.create(shared_memory_name(pid), sizeof(SharedBlock)))
|
||||
{
|
||||
if (!shm.create(shared_memory_name(pid), sizeof(SharedBlock))) {
|
||||
return nullptr;
|
||||
}
|
||||
auto* block = shm.as<SharedBlock>();
|
||||
block->version = kProtocolVersion;
|
||||
block->pad_count = 0;
|
||||
block->sequence.store(0, std::memory_order_relaxed);
|
||||
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s)
|
||||
{
|
||||
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s) {
|
||||
block->control.subsystem_disabled[s].store((disabled_mask >> s) & 1u, std::memory_order_release);
|
||||
}
|
||||
block->magic = kProtocolMagic;
|
||||
@@ -202,9 +177,8 @@ ID3D11Device* make_device()
|
||||
{
|
||||
ID3D11Device* dev = nullptr;
|
||||
const D3D_FEATURE_LEVEL fl[] = {D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0};
|
||||
if (FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, fl,
|
||||
static_cast<UINT>(std::size(fl)), D3D11_SDK_VERSION, &dev, nullptr, nullptr)))
|
||||
{
|
||||
if (FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, fl, static_cast<UINT>(std::size(fl)),
|
||||
D3D11_SDK_VERSION, &dev, nullptr, nullptr))) {
|
||||
return nullptr;
|
||||
}
|
||||
return dev;
|
||||
@@ -254,8 +228,7 @@ void test_video_capture(const char* backend, ID3D11Device* device)
|
||||
}
|
||||
std::wstring args = wbackend + L" 30";
|
||||
MockGame game = MockGame::launch(args);
|
||||
if (!game.ok)
|
||||
{
|
||||
if (!game.ok) {
|
||||
check(false, "launch coop_mock_game");
|
||||
return;
|
||||
}
|
||||
@@ -263,11 +236,10 @@ void test_video_capture(const char* backend, ID3D11Device* device)
|
||||
|
||||
// Only the video subsystem (disable input/focus/audio/mkb to keep the test focused).
|
||||
SharedMemory shm;
|
||||
const std::uint32_t disabled = (1u << HookSubsys_Input) | (1u << HookSubsys_Focus) |
|
||||
(1u << HookSubsys_Audio) | (1u << HookSubsys_Mkb);
|
||||
const std::uint32_t disabled =
|
||||
(1u << HookSubsys_Input) | (1u << HookSubsys_Focus) | (1u << HookSubsys_Audio) | (1u << HookSubsys_Mkb);
|
||||
SharedBlock* block = make_ipc(shm, game.pid(), disabled);
|
||||
if (block == nullptr || !inject_retry(game.pid()))
|
||||
{
|
||||
if (block == nullptr || !inject_retry(game.pid())) {
|
||||
check(false, "inject into mock game");
|
||||
game.kill();
|
||||
return;
|
||||
@@ -284,18 +256,15 @@ void test_video_capture(const char* backend, ID3D11Device* device)
|
||||
{
|
||||
Sleep(50);
|
||||
const VideoShareView share = read_video_share(block);
|
||||
if (!src.update(share, game.pid()))
|
||||
{
|
||||
if (!src.update(share, game.pid())) {
|
||||
continue;
|
||||
}
|
||||
std::uint8_t px[4] = {};
|
||||
if (!src.read_pixel(coop::mock::kFrameBlock / 2, coop::mock::kFrameBlock / 2, px))
|
||||
{
|
||||
if (!src.read_pixel(coop::mock::kFrameBlock / 2, coop::mock::kFrameBlock / 2, px)) {
|
||||
continue;
|
||||
}
|
||||
const std::uint32_t f = coop::mock::rgb_to_frame(px[0], px[1], px[2]);
|
||||
if (have_last && f < last)
|
||||
{
|
||||
if (have_last && f < last) {
|
||||
++backward; // a stale / wrong (rotated) buffer -> frame number went backwards
|
||||
}
|
||||
last = f;
|
||||
@@ -312,8 +281,7 @@ void test_video_capture(const char* backend, ID3D11Device* device)
|
||||
check(src.frames_copied() >= 5, "host copied multiple shared frames");
|
||||
check(seq.size() >= 5, "decoded multiple frame numbers from the captured pixels");
|
||||
check(backward == 0, "captured frame numbers never go backwards (no stale/rotated buffer)");
|
||||
if (!seq.empty())
|
||||
{
|
||||
if (!seq.empty()) {
|
||||
const std::uint32_t span = seq.back() - seq.front();
|
||||
std::printf(" frame# %u..%u (span %u)\n", seq.front(), seq.back(), span);
|
||||
check(span >= 20, "captured frame numbers advance (mirror gets fresh frames)");
|
||||
@@ -321,8 +289,7 @@ void test_video_capture(const char* backend, ID3D11Device* device)
|
||||
check(!all_same, "captured frames are not stuck on one number");
|
||||
}
|
||||
|
||||
if (game.alive())
|
||||
{
|
||||
if (game.alive()) {
|
||||
check_capture_present_rate(block, backend);
|
||||
}
|
||||
game.kill();
|
||||
@@ -345,15 +312,14 @@ void test_vk_capture(ID3D11Device* device)
|
||||
const BOOL ok =
|
||||
CreateProcessW(exe.c_str(), cmd.data(), nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi);
|
||||
SetEnvironmentVariableW(L"COOP_MOCK_VK_EARLY", nullptr);
|
||||
if (!ok)
|
||||
{
|
||||
if (!ok) {
|
||||
check(false, "launch suspended vk mock");
|
||||
return;
|
||||
}
|
||||
|
||||
SharedMemory shm;
|
||||
const std::uint32_t disabled = (1u << HookSubsys_Input) | (1u << HookSubsys_Focus) |
|
||||
(1u << HookSubsys_Audio) | (1u << HookSubsys_Mkb);
|
||||
const std::uint32_t disabled =
|
||||
(1u << HookSubsys_Input) | (1u << HookSubsys_Focus) | (1u << HookSubsys_Audio) | (1u << HookSubsys_Mkb);
|
||||
SharedBlock* block = make_ipc(shm, pi.dwProcessId, disabled);
|
||||
const bool injected = block != nullptr && inject_retry(pi.dwProcessId);
|
||||
ResumeThread(pi.hThread); // the mock loads vulkan + waits, then renders
|
||||
@@ -363,8 +329,7 @@ void test_vk_capture(ID3D11Device* device)
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
};
|
||||
if (!injected)
|
||||
{
|
||||
if (!injected) {
|
||||
check(false, "inject suspended vk mock");
|
||||
cleanup();
|
||||
return;
|
||||
@@ -387,31 +352,26 @@ void test_vk_capture(ID3D11Device* device)
|
||||
{
|
||||
Sleep(50);
|
||||
const VideoShareView share = read_video_share(block);
|
||||
if (!src.update(share, pi.dwProcessId))
|
||||
{
|
||||
if (!src.update(share, pi.dwProcessId)) {
|
||||
continue;
|
||||
}
|
||||
std::uint8_t px[4] = {};
|
||||
if (!src.read_pixel(coop::mock::kFrameBlock / 2, coop::mock::kFrameBlock / 2, px))
|
||||
{
|
||||
if (!src.read_pixel(coop::mock::kFrameBlock / 2, coop::mock::kFrameBlock / 2, px)) {
|
||||
continue;
|
||||
}
|
||||
const std::uint32_t f = coop::mock::rgb_to_frame(px[0], px[1], px[2]);
|
||||
if (have_last && f < last)
|
||||
{
|
||||
if (have_last && f < last) {
|
||||
++backward;
|
||||
}
|
||||
last = f;
|
||||
have_last = true;
|
||||
seq.push_back(f);
|
||||
if (seq.size() >= 40)
|
||||
{
|
||||
if (seq.size() >= 40) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alive() && exit_code() == 2 && seq.empty())
|
||||
{
|
||||
if (!alive() && exit_code() == 2 && seq.empty()) {
|
||||
std::printf(" Vulkan unavailable on this machine -- skipping vk capture\n");
|
||||
cleanup();
|
||||
return;
|
||||
@@ -424,12 +384,10 @@ void test_vk_capture(ID3D11Device* device)
|
||||
check(src.frames_copied() >= 5, "host copied multiple shared frames (vk)");
|
||||
check(seq.size() >= 5, "decoded multiple frame numbers from the captured pixels (vk)");
|
||||
check(backward == 0, "captured vk frame numbers never go backwards");
|
||||
if (!seq.empty())
|
||||
{
|
||||
if (!seq.empty()) {
|
||||
check(seq.back() - seq.front() >= 10, "captured vk frame numbers advance");
|
||||
}
|
||||
if (alive())
|
||||
{
|
||||
if (alive()) {
|
||||
check_capture_present_rate(block, "vk (inject)");
|
||||
}
|
||||
cleanup();
|
||||
@@ -443,8 +401,7 @@ void test_vk_layer_capture(ID3D11Device* device)
|
||||
{
|
||||
std::printf("== video capture: vk implicit layer ==\n");
|
||||
const std::wstring manifest = deployed_artifact_path(L"coop_vk_layer.json");
|
||||
if (GetFileAttributesW(manifest.c_str()) == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
if (GetFileAttributesW(manifest.c_str()) == INVALID_FILE_ATTRIBUTES) {
|
||||
check(false, "coop_vk_layer.json staged");
|
||||
return;
|
||||
}
|
||||
@@ -460,18 +417,16 @@ void test_vk_layer_capture(ID3D11Device* device)
|
||||
SetEnvironmentVariableW(L"COOP_VK_LAYER_FORCE", nullptr);
|
||||
};
|
||||
unset_env();
|
||||
if (!game.ok)
|
||||
{
|
||||
if (!game.ok) {
|
||||
check(false, "launch vk mock (layer)");
|
||||
return;
|
||||
}
|
||||
|
||||
SharedMemory shm;
|
||||
const std::uint32_t disabled = (1u << HookSubsys_Input) | (1u << HookSubsys_Focus) |
|
||||
(1u << HookSubsys_Audio) | (1u << HookSubsys_Mkb);
|
||||
const std::uint32_t disabled =
|
||||
(1u << HookSubsys_Input) | (1u << HookSubsys_Focus) | (1u << HookSubsys_Audio) | (1u << HookSubsys_Mkb);
|
||||
SharedBlock* block = make_ipc(shm, game.pid(), disabled); // the layer connects to this + publishes
|
||||
if (block == nullptr)
|
||||
{
|
||||
if (block == nullptr) {
|
||||
check(false, "ipc block (layer)");
|
||||
game.kill();
|
||||
return;
|
||||
@@ -487,30 +442,25 @@ void test_vk_layer_capture(ID3D11Device* device)
|
||||
{
|
||||
Sleep(50);
|
||||
const VideoShareView share = read_video_share(block);
|
||||
if (!src.update(share, game.pid()))
|
||||
{
|
||||
if (!src.update(share, game.pid())) {
|
||||
continue;
|
||||
}
|
||||
std::uint8_t px[4] = {};
|
||||
if (!src.read_pixel(coop::mock::kFrameBlock / 2, coop::mock::kFrameBlock / 2, px))
|
||||
{
|
||||
if (!src.read_pixel(coop::mock::kFrameBlock / 2, coop::mock::kFrameBlock / 2, px)) {
|
||||
continue;
|
||||
}
|
||||
const std::uint32_t f = coop::mock::rgb_to_frame(px[0], px[1], px[2]);
|
||||
if (have_last && f < last)
|
||||
{
|
||||
if (have_last && f < last) {
|
||||
++backward;
|
||||
}
|
||||
last = f;
|
||||
have_last = true;
|
||||
seq.push_back(f);
|
||||
if (seq.size() >= 40)
|
||||
{
|
||||
if (seq.size() >= 40) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!game.alive() && game.exit_code() == 2 && seq.empty())
|
||||
{
|
||||
if (!game.alive() && game.exit_code() == 2 && seq.empty()) {
|
||||
std::printf(" Vulkan unavailable on this machine -- skipping layer capture\n");
|
||||
game.kill();
|
||||
return;
|
||||
@@ -520,12 +470,10 @@ void test_vk_layer_capture(ID3D11Device* device)
|
||||
check(src.frames_copied() >= 5, "layer copied multiple shared frames");
|
||||
check(seq.size() >= 5, "decoded multiple frame numbers via the layer");
|
||||
check(backward == 0, "layer-captured frame numbers never go backwards");
|
||||
if (!seq.empty())
|
||||
{
|
||||
if (!seq.empty()) {
|
||||
check(seq.back() - seq.front() >= 10, "layer-captured frame numbers advance");
|
||||
}
|
||||
if (game.alive())
|
||||
{
|
||||
if (game.alive()) {
|
||||
check_capture_present_rate(block, "vk (layer)");
|
||||
}
|
||||
game.kill();
|
||||
@@ -539,30 +487,24 @@ void test_vk_too_late()
|
||||
{
|
||||
std::printf("== vk too-late detection (late inject) ==\n");
|
||||
MockGame game = MockGame::launch(L"vk 30");
|
||||
if (!game.ok)
|
||||
{
|
||||
if (!game.ok) {
|
||||
check(false, "launch vk mock (too-late)");
|
||||
return;
|
||||
}
|
||||
Sleep(1200); // let it create its instance/device and start presenting
|
||||
if (!game.alive() && game.exit_code() == 2)
|
||||
{
|
||||
if (!game.alive() && game.exit_code() == 2) {
|
||||
std::printf(" Vulkan unavailable on this machine -- skipping\n");
|
||||
game.kill();
|
||||
return;
|
||||
}
|
||||
SharedMemory shm;
|
||||
const std::uint32_t disabled = (1u << HookSubsys_Input) | (1u << HookSubsys_Focus) |
|
||||
(1u << HookSubsys_Audio) | (1u << HookSubsys_Mkb);
|
||||
const std::uint32_t disabled =
|
||||
(1u << HookSubsys_Input) | (1u << HookSubsys_Focus) | (1u << HookSubsys_Audio) | (1u << HookSubsys_Mkb);
|
||||
SharedBlock* block = make_ipc(shm, game.pid(), disabled);
|
||||
if (block == nullptr || !inject_retry(game.pid()))
|
||||
{
|
||||
if (!game.alive() && game.exit_code() == 2)
|
||||
{
|
||||
if (block == nullptr || !inject_retry(game.pid())) {
|
||||
if (!game.alive() && game.exit_code() == 2) {
|
||||
std::printf(" Vulkan unavailable -- skipping\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
check(false, "inject vk mock (too-late)");
|
||||
}
|
||||
game.kill();
|
||||
@@ -572,8 +514,7 @@ void test_vk_too_late()
|
||||
for (int i = 0; i < 160 && game.alive(); ++i) // ~8 s (past the hook's 4 s grace)
|
||||
{
|
||||
Sleep(50);
|
||||
if (block->status.vk_too_late != 0)
|
||||
{
|
||||
if (block->status.vk_too_late != 0) {
|
||||
too_late = true;
|
||||
break;
|
||||
}
|
||||
@@ -592,8 +533,7 @@ void test_audio_variant(unsigned rate, unsigned channels, unsigned bits, const w
|
||||
swprintf(args, static_cast<int>(std::size(args)), L"dx11 30 %u %u %u %ls", rate, channels, bits, fmt);
|
||||
std::printf("== audio variant: %u Hz %u ch %u-bit %ls ==\n", rate, channels, bits, fmt);
|
||||
MockGame game = MockGame::launch(args);
|
||||
if (!game.ok)
|
||||
{
|
||||
if (!game.ok) {
|
||||
check(false, "launch coop_mock_game (audio variant)");
|
||||
return;
|
||||
}
|
||||
@@ -601,19 +541,17 @@ void test_audio_variant(unsigned rate, unsigned channels, unsigned bits, const w
|
||||
|
||||
// Audio subsystem only.
|
||||
SharedMemory shm;
|
||||
const std::uint32_t disabled = (1u << HookSubsys_Input) | (1u << HookSubsys_Focus) |
|
||||
(1u << HookSubsys_Video) | (1u << HookSubsys_Mkb);
|
||||
const std::uint32_t disabled =
|
||||
(1u << HookSubsys_Input) | (1u << HookSubsys_Focus) | (1u << HookSubsys_Video) | (1u << HookSubsys_Mkb);
|
||||
SharedBlock* block = make_ipc(shm, game.pid(), disabled);
|
||||
SharedMemory ring_shm;
|
||||
AudioRingHeader* ring = nullptr;
|
||||
if (ring_shm.create(audio_ring_name(game.pid()), audio_ring_total_size(kAudioRingCapacity)))
|
||||
{
|
||||
if (ring_shm.create(audio_ring_name(game.pid()), audio_ring_total_size(kAudioRingCapacity))) {
|
||||
ring = ring_shm.as<AudioRingHeader>();
|
||||
audio_ring_init(*ring, kAudioRingCapacity);
|
||||
ring->capture_enabled.store(1, std::memory_order_release);
|
||||
}
|
||||
if (block == nullptr || ring == nullptr || !inject_retry(game.pid()))
|
||||
{
|
||||
if (block == nullptr || ring == nullptr || !inject_retry(game.pid())) {
|
||||
check(false, "inject into mock game (audio variant)");
|
||||
game.kill();
|
||||
return;
|
||||
@@ -629,35 +567,26 @@ void test_audio_variant(unsigned rate, unsigned channels, unsigned bits, const w
|
||||
{
|
||||
Sleep(50);
|
||||
std::uint32_t got = 0;
|
||||
while ((got = audio_ring_pop(*ring, drain.data(), static_cast<std::uint32_t>(drain.size()))) > 0)
|
||||
{
|
||||
if (ring->bits == 32 && ring->format_tag == 3)
|
||||
{
|
||||
while ((got = audio_ring_pop(*ring, drain.data(), static_cast<std::uint32_t>(drain.size()))) > 0) {
|
||||
if (ring->bits == 32 && ring->format_tag == 3) {
|
||||
const auto* f = reinterpret_cast<const float*>(drain.data());
|
||||
for (std::uint32_t k = 0; k < got / 4; ++k)
|
||||
{
|
||||
for (std::uint32_t k = 0; k < got / 4; ++k) {
|
||||
peak = std::max(peak, static_cast<double>(std::fabs(f[k])));
|
||||
}
|
||||
}
|
||||
else if (ring->bits == 16)
|
||||
{
|
||||
} else if (ring->bits == 16) {
|
||||
const auto* s = reinterpret_cast<const std::int16_t*>(drain.data());
|
||||
for (std::uint32_t k = 0; k < got / 2; ++k)
|
||||
{
|
||||
for (std::uint32_t k = 0; k < got / 2; ++k) {
|
||||
peak = std::max(peak, std::abs(s[k]) / 32768.0);
|
||||
}
|
||||
}
|
||||
if (got < drain.size())
|
||||
{
|
||||
if (got < drain.size()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
state = block->status.audio_streams[0].format_state;
|
||||
if (state == AudioFormat_Measured || state == AudioFormat_LowConfidence || state == AudioFormat_Exact)
|
||||
{
|
||||
if (state == AudioFormat_Measured || state == AudioFormat_LowConfidence || state == AudioFormat_Exact) {
|
||||
measured = block->status.audio_streams[0].sample_rate;
|
||||
if (measured != 0 && peak > 0.01)
|
||||
{
|
||||
if (measured != 0 && peak > 0.01) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -675,8 +604,7 @@ void test_av_and_hook_cycles(ID3D11Device* device)
|
||||
{
|
||||
std::printf("== A/V + hook/unhook stress (dx11 + audio) ==\n");
|
||||
MockGame game = MockGame::launch(L"dx11 30 48000 2 32 float");
|
||||
if (!game.ok)
|
||||
{
|
||||
if (!game.ok) {
|
||||
check(false, "launch coop_mock_game (A/V)");
|
||||
return;
|
||||
}
|
||||
@@ -686,14 +614,12 @@ void test_av_and_hook_cycles(ID3D11Device* device)
|
||||
SharedBlock* block = make_ipc(shm, game.pid(), /*disabled=*/0); // all subsystems on
|
||||
SharedMemory ring_shm;
|
||||
AudioRingHeader* ring = nullptr;
|
||||
if (ring_shm.create(audio_ring_name(game.pid()), audio_ring_total_size(kAudioRingCapacity)))
|
||||
{
|
||||
if (ring_shm.create(audio_ring_name(game.pid()), audio_ring_total_size(kAudioRingCapacity))) {
|
||||
ring = ring_shm.as<AudioRingHeader>();
|
||||
audio_ring_init(*ring, kAudioRingCapacity);
|
||||
ring->capture_enabled.store(1, std::memory_order_release);
|
||||
}
|
||||
if (block == nullptr || ring == nullptr || !inject_retry(game.pid()))
|
||||
{
|
||||
if (block == nullptr || ring == nullptr || !inject_retry(game.pid())) {
|
||||
check(false, "inject into mock game (A/V)");
|
||||
game.kill();
|
||||
return;
|
||||
@@ -710,32 +636,25 @@ void test_av_and_hook_cycles(ID3D11Device* device)
|
||||
{
|
||||
Sleep(50);
|
||||
const VideoShareView share = read_video_share(block);
|
||||
if (src.update(share, game.pid()))
|
||||
{
|
||||
if (src.update(share, game.pid())) {
|
||||
std::uint8_t px[4] = {};
|
||||
if (src.read_pixel(coop::mock::kFrameBlock / 2, coop::mock::kFrameBlock / 2, px))
|
||||
{
|
||||
if (src.read_pixel(coop::mock::kFrameBlock / 2, coop::mock::kFrameBlock / 2, px)) {
|
||||
const std::uint32_t f = coop::mock::rgb_to_frame(px[0], px[1], px[2]);
|
||||
if (first_frame == 0)
|
||||
{
|
||||
if (first_frame == 0) {
|
||||
first_frame = f;
|
||||
}
|
||||
last_frame = f;
|
||||
}
|
||||
}
|
||||
std::uint32_t got = 0;
|
||||
while ((got = audio_ring_pop(*ring, drain.data(), static_cast<std::uint32_t>(drain.size()))) > 0)
|
||||
{
|
||||
if (ring->bits == 32 && ring->format_tag == 3)
|
||||
{
|
||||
while ((got = audio_ring_pop(*ring, drain.data(), static_cast<std::uint32_t>(drain.size()))) > 0) {
|
||||
if (ring->bits == 32 && ring->format_tag == 3) {
|
||||
const auto* f = reinterpret_cast<const float*>(drain.data());
|
||||
for (std::uint32_t k = 0; k < got / 4; ++k)
|
||||
{
|
||||
for (std::uint32_t k = 0; k < got / 4; ++k) {
|
||||
peak = std::max(peak, static_cast<double>(std::fabs(f[k])));
|
||||
}
|
||||
}
|
||||
if (got < drain.size())
|
||||
{
|
||||
if (got < drain.size()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -747,16 +666,14 @@ void test_av_and_hook_cycles(ID3D11Device* device)
|
||||
// the worker's ~250 ms reconcile tick, so the install/remove fully completes each time
|
||||
// (this is the realistic cadence -- an operator toggling a checkbox, not thrashing it).
|
||||
const std::uint32_t hb_start = block->status.heartbeat.load(std::memory_order_relaxed);
|
||||
for (int c = 0; c < 3 && game.alive(); ++c)
|
||||
{
|
||||
for (int c = 0; c < 3 && game.alive(); ++c) {
|
||||
block->control.subsystem_disabled[HookSubsys_Audio].store(1, std::memory_order_release);
|
||||
Sleep(400);
|
||||
block->control.subsystem_disabled[HookSubsys_Audio].store(0, std::memory_order_release);
|
||||
Sleep(400);
|
||||
}
|
||||
const std::uint32_t hb_end = block->status.heartbeat.load(std::memory_order_relaxed);
|
||||
if (!game.alive())
|
||||
{
|
||||
if (!game.alive()) {
|
||||
std::printf(" game exit code = 0x%08lX\n", game.exit_code());
|
||||
}
|
||||
check(game.alive(), "game survived hook/unhook cycles (no crash)");
|
||||
@@ -770,18 +687,14 @@ void test_av_and_hook_cycles(ID3D11Device* device)
|
||||
{
|
||||
Sleep(50);
|
||||
std::uint32_t got = 0;
|
||||
while ((got = audio_ring_pop(*ring, drain.data(), static_cast<std::uint32_t>(drain.size()))) > 0)
|
||||
{
|
||||
if (ring->bits == 32 && ring->format_tag == 3)
|
||||
{
|
||||
while ((got = audio_ring_pop(*ring, drain.data(), static_cast<std::uint32_t>(drain.size()))) > 0) {
|
||||
if (ring->bits == 32 && ring->format_tag == 3) {
|
||||
const auto* f = reinterpret_cast<const float*>(drain.data());
|
||||
for (std::uint32_t k = 0; k < got / 4; ++k)
|
||||
{
|
||||
for (std::uint32_t k = 0; k < got / 4; ++k) {
|
||||
peak2 = std::max(peak2, static_cast<double>(std::fabs(f[k])));
|
||||
}
|
||||
}
|
||||
if (got < drain.size())
|
||||
{
|
||||
if (got < drain.size()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -817,19 +730,16 @@ void test_hook_storm(const char* backend, ID3D11Device* device, bool vk_early)
|
||||
si.cb = sizeof(si);
|
||||
const std::wstring exe = tool_path(L"coop_mock_game.exe");
|
||||
std::wstring cmd = L"\"" + exe + L"\" " + wbackend + L" 60";
|
||||
if (vk_early)
|
||||
{
|
||||
if (vk_early) {
|
||||
SetEnvironmentVariableW(L"COOP_MOCK_VK_EARLY", L"1");
|
||||
}
|
||||
const DWORD launch_flags = vk_early ? CREATE_SUSPENDED : 0;
|
||||
const BOOL launched =
|
||||
CreateProcessW(exe.c_str(), cmd.data(), nullptr, nullptr, FALSE, launch_flags, nullptr, nullptr, &si, &pi);
|
||||
if (vk_early)
|
||||
{
|
||||
if (vk_early) {
|
||||
SetEnvironmentVariableW(L"COOP_MOCK_VK_EARLY", nullptr);
|
||||
}
|
||||
if (!launched)
|
||||
{
|
||||
if (!launched) {
|
||||
check(false, "launch mock game (storm)");
|
||||
return;
|
||||
}
|
||||
@@ -850,25 +760,19 @@ void test_hook_storm(const char* backend, ID3D11Device* device, bool vk_early)
|
||||
SharedBlock* block = make_ipc(shm, pi.dwProcessId, /*disabled=*/0); // all subsystems on
|
||||
SharedMemory ring_shm;
|
||||
AudioRingHeader* ring = nullptr;
|
||||
if (ring_shm.create(audio_ring_name(pi.dwProcessId), audio_ring_total_size(kAudioRingCapacity)))
|
||||
{
|
||||
if (ring_shm.create(audio_ring_name(pi.dwProcessId), audio_ring_total_size(kAudioRingCapacity))) {
|
||||
ring = ring_shm.as<AudioRingHeader>();
|
||||
audio_ring_init(*ring, kAudioRingCapacity);
|
||||
ring->capture_enabled.store(1, std::memory_order_release);
|
||||
}
|
||||
const bool injected = block != nullptr && inject_retry(pi.dwProcessId);
|
||||
if (vk_early)
|
||||
{
|
||||
if (vk_early) {
|
||||
ResumeThread(pi.hThread); // the mock loads Vulkan + waits, then renders
|
||||
}
|
||||
if (!injected)
|
||||
{
|
||||
if (vk_early && !alive() && exit_code() == 2)
|
||||
{
|
||||
if (!injected) {
|
||||
if (vk_early && !alive() && exit_code() == 2) {
|
||||
std::printf(" Vulkan unavailable -- skipping storm\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
check(false, "inject mock game (storm)");
|
||||
}
|
||||
cleanup();
|
||||
@@ -876,8 +780,7 @@ void test_hook_storm(const char* backend, ID3D11Device* device, bool vk_early)
|
||||
}
|
||||
|
||||
Sleep(vk_early ? 2500 : 1000); // let the hook attach + the game start presenting
|
||||
if (vk_early && !alive() && exit_code() == 2)
|
||||
{
|
||||
if (vk_early && !alive() && exit_code() == 2) {
|
||||
std::printf(" Vulkan unavailable -- skipping storm\n");
|
||||
cleanup();
|
||||
return;
|
||||
@@ -890,11 +793,9 @@ void test_hook_storm(const char* backend, ID3D11Device* device, bool vk_early)
|
||||
std::atomic<bool> stop{false};
|
||||
std::thread storm([&] {
|
||||
bool off = false;
|
||||
while (!stop.load(std::memory_order_relaxed))
|
||||
{
|
||||
while (!stop.load(std::memory_order_relaxed)) {
|
||||
off = !off;
|
||||
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s)
|
||||
{
|
||||
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s) {
|
||||
block->control.subsystem_disabled[s].store(off ? 1u : 0u, std::memory_order_release);
|
||||
}
|
||||
Sleep(60);
|
||||
@@ -905,28 +806,24 @@ void test_hook_storm(const char* backend, ID3D11Device* device, bool vk_early)
|
||||
for (int i = 0; i < 170 && !crashed; ++i) // ~10 s of storming
|
||||
{
|
||||
Sleep(60);
|
||||
if (!alive())
|
||||
{
|
||||
if (!alive()) {
|
||||
crashed = true;
|
||||
}
|
||||
}
|
||||
stop.store(true, std::memory_order_relaxed);
|
||||
storm.join();
|
||||
|
||||
if (crashed)
|
||||
{
|
||||
if (crashed) {
|
||||
std::printf(" game CRASHED during the storm (exit 0x%08lX)\n", exit_code());
|
||||
}
|
||||
check(!crashed, "game survived the hook/unhook storm (no crash)");
|
||||
if (crashed)
|
||||
{
|
||||
if (crashed) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-enable everything and confirm the game is still alive + the hook still beating.
|
||||
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s)
|
||||
{
|
||||
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s) {
|
||||
block->control.subsystem_disabled[s].store(0, std::memory_order_release);
|
||||
}
|
||||
Sleep(600);
|
||||
@@ -936,8 +833,7 @@ void test_hook_storm(const char* backend, ID3D11Device* device, bool vk_early)
|
||||
|
||||
// Capture must resume (the rehook works end-to-end). Vulkan can't re-arm after a toggle (its
|
||||
// present pointer was cached at init), so only assert resume for the other backends.
|
||||
if (!vk_early)
|
||||
{
|
||||
if (!vk_early) {
|
||||
SharedTextureSource src;
|
||||
src.init(device);
|
||||
const std::uint64_t frames0 = src.frames_copied();
|
||||
@@ -946,8 +842,7 @@ void test_hook_storm(const char* backend, ID3D11Device* device, bool vk_early)
|
||||
{
|
||||
Sleep(50);
|
||||
const VideoShareView share = read_video_share(block);
|
||||
if (src.update(share, pi.dwProcessId) && src.frames_copied() > frames0 + 3)
|
||||
{
|
||||
if (src.update(share, pi.dwProcessId) && src.frames_copied() > frames0 + 3) {
|
||||
advanced = true;
|
||||
break;
|
||||
}
|
||||
@@ -963,13 +858,11 @@ void test_hook_storm(const char* backend, ID3D11Device* device, bool vk_early)
|
||||
std::uint32_t installed_hook_count(const SharedBlock* block)
|
||||
{
|
||||
std::uint32_t count = block->status.hook_entry_count;
|
||||
if (count > kMaxHookEntries)
|
||||
{
|
||||
if (count > kMaxHookEntries) {
|
||||
count = kMaxHookEntries;
|
||||
}
|
||||
std::uint32_t installed = 0;
|
||||
for (std::uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
for (std::uint32_t i = 0; i < count; ++i) {
|
||||
installed += block->status.hook_entries[i].installed != 0 ? 1u : 0u;
|
||||
}
|
||||
return installed;
|
||||
@@ -983,13 +876,11 @@ void test_graceful_disconnect(const char* backend)
|
||||
{
|
||||
std::printf("== graceful disconnect: %s ==\n", backend);
|
||||
std::wstring wbackend;
|
||||
for (const char* p = backend; *p != '\0'; ++p)
|
||||
{
|
||||
for (const char* p = backend; *p != '\0'; ++p) {
|
||||
wbackend.push_back(static_cast<wchar_t>(*p));
|
||||
}
|
||||
MockGame game = MockGame::launch(wbackend + L" 30");
|
||||
if (!game.ok)
|
||||
{
|
||||
if (!game.ok) {
|
||||
check(false, "launch mock game (graceful disconnect)");
|
||||
return;
|
||||
}
|
||||
@@ -999,8 +890,7 @@ void test_graceful_disconnect(const char* backend)
|
||||
// test doesn't depend on an audio endpoint. Then inject.
|
||||
SharedMemory shm;
|
||||
SharedBlock* block = make_ipc(shm, game.pid(), 1u << HookSubsys_Audio);
|
||||
if (block == nullptr || !inject_retry(game.pid()))
|
||||
{
|
||||
if (block == nullptr || !inject_retry(game.pid())) {
|
||||
check(false, "inject mock game (graceful disconnect)");
|
||||
game.kill();
|
||||
return;
|
||||
@@ -1017,8 +907,7 @@ void test_graceful_disconnect(const char* backend)
|
||||
const std::uint32_t hb0 = block->status.heartbeat.load(std::memory_order_relaxed);
|
||||
|
||||
// Graceful disconnect: request every subsystem removed (what request_unhook_all writes).
|
||||
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s)
|
||||
{
|
||||
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s) {
|
||||
block->control.subsystem_disabled[s].store(1u, std::memory_order_release);
|
||||
}
|
||||
|
||||
@@ -1049,13 +938,11 @@ void test_reconnect(const char* backend)
|
||||
{
|
||||
std::printf("== reconnect to an already-injected DLL: %s ==\n", backend);
|
||||
std::wstring wbackend;
|
||||
for (const char* p = backend; *p != '\0'; ++p)
|
||||
{
|
||||
for (const char* p = backend; *p != '\0'; ++p) {
|
||||
wbackend.push_back(static_cast<wchar_t>(*p));
|
||||
}
|
||||
MockGame game = MockGame::launch(wbackend + L" 30");
|
||||
if (!game.ok)
|
||||
{
|
||||
if (!game.ok) {
|
||||
check(false, "launch mock game (reconnect)");
|
||||
return;
|
||||
}
|
||||
@@ -1064,16 +951,14 @@ void test_reconnect(const char* backend)
|
||||
const std::uint32_t disabled = 1u << HookSubsys_Audio; // input+focus+video+mkb on; audio off
|
||||
SharedMemory shm_a;
|
||||
SharedBlock* block_a = make_ipc(shm_a, game.pid(), disabled);
|
||||
if (block_a == nullptr || !inject_retry(game.pid()))
|
||||
{
|
||||
if (block_a == nullptr || !inject_retry(game.pid())) {
|
||||
check(false, "inject mock game (reconnect)");
|
||||
game.kill();
|
||||
return;
|
||||
}
|
||||
|
||||
bool installed = false;
|
||||
for (int i = 0; i < 100 && game.alive() && !installed; ++i)
|
||||
{
|
||||
for (int i = 0; i < 100 && game.alive() && !installed; ++i) {
|
||||
Sleep(50);
|
||||
installed = installed_hook_count(block_a) > 0;
|
||||
}
|
||||
@@ -1082,12 +967,10 @@ void test_reconnect(const char* backend)
|
||||
|
||||
// Graceful disconnect: unhook everything, then simulate the host going away (drop our handle;
|
||||
// the DLL keeps the section alive). This stands in for both an explicit disconnect and a restart.
|
||||
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s)
|
||||
{
|
||||
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s) {
|
||||
block_a->control.subsystem_disabled[s].store(1u, std::memory_order_release);
|
||||
}
|
||||
for (int i = 0; i < 100 && installed_hook_count(block_a) != 0; ++i)
|
||||
{
|
||||
for (int i = 0; i < 100 && installed_hook_count(block_a) != 0; ++i) {
|
||||
Sleep(50);
|
||||
}
|
||||
check(installed_hook_count(block_a) == 0, "graceful disconnect unhooked the game");
|
||||
@@ -1103,15 +986,13 @@ void test_reconnect(const char* backend)
|
||||
// ...and reconnect by re-attaching to the SAME section (no re-inject) and re-enabling subsystems.
|
||||
SharedMemory shm_b;
|
||||
SharedBlock* block_b = make_ipc(shm_b, game.pid(), 0u); // re-attach, all subsystems on
|
||||
if (block_b == nullptr)
|
||||
{
|
||||
if (block_b == nullptr) {
|
||||
check(false, "reconnect: re-attach to the section");
|
||||
game.kill();
|
||||
return;
|
||||
}
|
||||
bool reinstalled = false;
|
||||
for (int i = 0; i < 100 && game.alive() && !reinstalled; ++i)
|
||||
{
|
||||
for (int i = 0; i < 100 && game.alive() && !reinstalled; ++i) {
|
||||
Sleep(50);
|
||||
reinstalled = installed_hook_count(block_b) > 0;
|
||||
}
|
||||
@@ -1128,8 +1009,7 @@ int main()
|
||||
kill_stray_mock_games(); // clean slate: no leftover game holding coop_hook.dll
|
||||
|
||||
ID3D11Device* device = make_device();
|
||||
if (device == nullptr)
|
||||
{
|
||||
if (device == nullptr) {
|
||||
std::printf("No D3D11 device -- skipping mock_game_test.\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1174,8 +1054,7 @@ int main()
|
||||
device->Release();
|
||||
kill_stray_mock_games(); // belt-and-suspenders: ensure nothing is left running
|
||||
|
||||
if (g_failures == 0)
|
||||
{
|
||||
if (g_failures == 0) {
|
||||
std::printf("PASS mock_game_test\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user