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:
2026-07-12 11:52:53 +02:00
parent c684a15fb9
commit 30eccf749d
155 changed files with 3333 additions and 6171 deletions

View File

@@ -17,14 +17,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;
}
}
@@ -36,8 +34,7 @@ constexpr double kPi = 3.14159265358979323846;
double source(double t)
{
const double chirp = std::sin(2.0 * kPi * (300.0 * t + 140.0 * t * t));
return 0.5 * std::sin(2.0 * kPi * 221.0 * t) + 0.28 * std::sin(2.0 * kPi * 437.0 * t + 0.6) +
0.22 * chirp;
return 0.5 * std::sin(2.0 * kPi * 221.0 * t) + 0.28 * std::sin(2.0 * kPi * 437.0 * t + 0.6) + 0.22 * chirp;
}
// Sample s(t) at `rate` for `seconds`, starting at t0 (capture-latency skew), optionally adding
@@ -48,8 +45,7 @@ std::vector<float> capture(unsigned rate, double seconds, double t0, double nois
std::vector<float> out(n);
std::mt19937 rng(seed);
std::uniform_real_distribution<float> jitter(-1.0f, 1.0f);
for (std::size_t i = 0; i < n; ++i)
{
for (std::size_t i = 0; i < n; ++i) {
const double t = t0 + static_cast<double>(i) / static_cast<double>(rate);
out[i] = static_cast<float>(source(t)) + static_cast<float>(noise) * jitter(rng);
}
@@ -64,15 +60,13 @@ double multi(double t, unsigned channel)
{
const double k = 1.0 + 0.37 * static_cast<double>(channel); // distinct frequency scale per channel
const double chirp = std::sin(2.0 * kPi * (300.0 * k * t + 140.0 * t * t));
return 0.5 * std::sin(2.0 * kPi * 221.0 * k * t) + 0.28 * std::sin(2.0 * kPi * 437.0 * k * t + 0.6) +
0.22 * chirp;
return 0.5 * std::sin(2.0 * kPi * 221.0 * k * t) + 0.28 * std::sin(2.0 * kPi * 437.0 * k * t + 0.6) + 0.22 * chirp;
}
double multi_mono(double t, unsigned channels)
{
double sum = 0.0;
for (unsigned c = 0; c < channels; ++c)
{
for (unsigned c = 0; c < channels; ++c) {
sum += multi(t, c);
}
return sum / channels;
@@ -84,20 +78,15 @@ std::vector<std::uint8_t> encode(unsigned rate, unsigned channels, unsigned bits
const unsigned bps = bits / 8;
const std::size_t frames = static_cast<std::size_t>(rate * seconds);
std::vector<std::uint8_t> out(frames * channels * bps);
for (std::size_t i = 0; i < frames; ++i)
{
for (std::size_t i = 0; i < frames; ++i) {
const double t = static_cast<double>(i) / rate;
for (unsigned c = 0; c < channels; ++c)
{
for (unsigned c = 0; c < channels; ++c) {
const double s = multi(t, c);
std::uint8_t* p = out.data() + (i * channels + c) * bps;
if (tag == coop::kWaveFormatFloat)
{
if (tag == coop::kWaveFormatFloat) {
const float f = static_cast<float>(s);
std::memcpy(p, &f, 4);
}
else
{
} else {
const std::int16_t v = static_cast<std::int16_t>(s * 30000.0);
std::memcpy(p, &v, 2);
}
@@ -121,16 +110,14 @@ coop::ChunkedCapture make_chunks(unsigned rate, unsigned ch, unsigned bits, unsi
std::mt19937 rng(123);
std::uniform_int_distribution<int> garbage(0, 255);
std::size_t f = 0;
while (f < frames)
{
while (f < frames) {
const unsigned count = static_cast<unsigned>(std::min<std::size_t>(480, frames - f));
cap.counts.push_back(count);
// The hook reads count*stride contiguous bytes: the count real frames first
// (count*real_block bytes), then count*(stride-real_block) bytes of stale over-read.
const std::uint8_t* src = clean.data() + f * real_block;
cap.bytes.insert(cap.bytes.end(), src, src + static_cast<std::size_t>(count) * real_block);
for (std::size_t p = 0; p < static_cast<std::size_t>(count) * (stride - real_block); ++p)
{
for (std::size_t p = 0; p < static_cast<std::size_t>(count) * (stride - real_block); ++p) {
cap.bytes.push_back(static_cast<std::uint8_t>(garbage(rng)));
}
f += count;
@@ -140,11 +127,11 @@ coop::ChunkedCapture make_chunks(unsigned rate, unsigned ch, unsigned bits, unsi
// One layout scenario: the hook bytes are at (true_*) and still being measured; the loopback is the
// post-mix mono of the same audio at device_rate. Assert correlate_format recovers the full layout.
void test_layout(unsigned true_rate, unsigned true_ch, unsigned true_bits, unsigned true_tag,
unsigned device_rate, const char* label)
void test_layout(unsigned true_rate, unsigned true_ch, unsigned true_bits, unsigned true_tag, unsigned device_rate,
const char* label)
{
std::printf("== layout: %s (%u Hz / %u ch / %u-bit %s -> device %u Hz) ==\n", label, true_rate, true_ch,
true_bits, true_tag == coop::kWaveFormatFloat ? "float" : "pcm", device_rate);
std::printf("== layout: %s (%u Hz / %u ch / %u-bit %s -> device %u Hz) ==\n", label, true_rate, true_ch, true_bits,
true_tag == coop::kWaveFormatFloat ? "float" : "pcm", device_rate);
// Device block 32 (8ch float) is the largest stride; every test layout's real block is <= 32.
const coop::ChunkedCapture hook = make_chunks(true_rate, true_ch, true_bits, true_tag, /*stride=*/32, 0.6);
// Loopback: the post-mix mono of the same audio, at the device rate, started ~18 ms later + noise.
@@ -152,16 +139,15 @@ void test_layout(unsigned true_rate, unsigned true_ch, unsigned true_bits, unsig
std::vector<float> loop(loop_frames);
std::mt19937 rng(5);
std::uniform_real_distribution<float> j(-1.0f, 1.0f);
for (std::size_t m = 0; m < loop_frames; ++m)
{
loop[m] = static_cast<float>(multi_mono(0.018 + static_cast<double>(m) / device_rate, true_ch)) +
0.02f * j(rng);
for (std::size_t m = 0; m < loop_frames; ++m) {
loop[m] =
static_cast<float>(multi_mono(0.018 + static_cast<double>(m) / device_rate, true_ch)) + 0.02f * j(rng);
}
const FormatCorrelation r =
correlate_format(hook, loop, device_rate, standard_audio_rates(), standard_audio_layouts());
std::printf(" picked %u Hz / %u ch / %u-bit %s score=%.3f runner_up=%.3f ok=%d\n", r.rate, r.channels,
r.bits, r.tag == coop::kWaveFormatFloat ? "float" : "pcm", r.score, r.runner_up, r.ok ? 1 : 0);
std::printf(" picked %u Hz / %u ch / %u-bit %s score=%.3f runner_up=%.3f ok=%d\n", r.rate, r.channels, r.bits,
r.tag == coop::kWaveFormatFloat ? "float" : "pcm", r.score, r.runner_up, r.ok ? 1 : 0);
check(r.ok, "layout pick is confident");
check(r.rate == true_rate, "recovered the true rate");
check(r.channels == true_ch, "recovered the true channel count");
@@ -179,8 +165,7 @@ void test_case(unsigned true_rate, unsigned device_rate, const char* label)
const std::vector<float> loop = capture(device_rate, 0.55, 0.022, 0.02, 7);
const RateCorrelation r = correlate_rate(hook, loop, device_rate, standard_audio_rates());
std::printf(" picked %u Hz score=%.3f runner_up=%.3f ok=%d\n", r.rate, r.score, r.runner_up,
r.ok ? 1 : 0);
std::printf(" picked %u Hz score=%.3f runner_up=%.3f ok=%d\n", r.rate, r.score, r.runner_up, r.ok ? 1 : 0);
check(r.rate == true_rate, "correlator picked the true rate");
check(r.ok, "pick is confident (clears threshold + beats runner-up)");
check(r.score > r.runner_up, "winner scores above the runner-up");
@@ -210,8 +195,8 @@ int main()
std::vector<float> stereo = {1.0f, 3.0f, 2.0f, 4.0f, -1.0f, 1.0f};
std::vector<float> mono;
correlate_detail::downmix(stereo.data(), 3, 2, mono);
check(mono.size() == 3 && std::fabs(mono[0] - 2.0f) < 1e-6 && std::fabs(mono[1] - 3.0f) < 1e-6 &&
std::fabs(mono[2] - 0.0f) < 1e-6,
check(mono.size() == 3 && std::fabs(mono[0] - 2.0f) < 1e-6 && std::fabs(mono[1] - 3.0f) < 1e-6
&& std::fabs(mono[2] - 0.0f) < 1e-6,
"stereo frames average to mono");
}
@@ -222,8 +207,7 @@ int main()
std::vector<float> noise(static_cast<std::size_t>(48000 * 0.5));
std::mt19937 rng(99);
std::uniform_real_distribution<float> d(-1.0f, 1.0f);
for (float& x : noise)
{
for (float& x : noise) {
x = d(rng);
}
const RateCorrelation r = correlate_rate(hook, noise, 48000, standard_audio_rates());
@@ -231,8 +215,7 @@ int main()
check(!r.ok, "unrelated loopback is not a confident match");
}
if (g_failures == 0)
{
if (g_failures == 0) {
std::printf("PASS audio_correlation_test\n");
return 0;
}

View File

@@ -34,16 +34,14 @@ using namespace coop;
using coop::tone::ToneFormat;
using coop::tone::ToneSource;
namespace
{
namespace {
int g_failures = 0;
// Returns 1 (and logs) on failure, 0 on success -- so callers can sum a tally.
int expect(bool ok, const char* what)
{
if (!ok)
{
if (!ok) {
std::printf(" FAIL: %s\n", what);
++g_failures;
return 1;
@@ -63,10 +61,8 @@ bool ring_has_nonsilent(AudioRingHeader* ring)
{
std::vector<std::uint8_t> buf(128 * 1024, 0);
const std::uint32_t got = audio_ring_pop(*ring, buf.data(), static_cast<std::uint32_t>(buf.size()));
for (std::uint32_t i = 0; i < got; ++i)
{
if (buf[i] != 0)
{
for (std::uint32_t i = 0; i < got; ++i) {
if (buf[i] != 0) {
return true;
}
}
@@ -85,15 +81,13 @@ void test_see_init(hook::IpcClient& ipc, AudioRingHeader* ring, SharedBlock* blo
{
char d[64];
reset_ring(ring, block);
if (!hook::install_audio_hooks(ipc, ring))
{
if (!hook::install_audio_hooks(ipc, ring)) {
std::printf(" SKIP see-init (audio hooks unavailable)\n");
return;
}
ToneSource tone;
if (!tone.open(want))
{
if (!tone.open(want)) {
std::printf(" SKIP see-init %s (format unavailable here)\n", fmt_desc(want, d, sizeof(d)));
hook::remove_audio_hooks();
return;
@@ -103,8 +97,7 @@ void test_see_init(hook::IpcClient& ipc, AudioRingHeader* ring, SharedBlock* blo
// Exact format publishes at registration; render briefly so capture fills the ring.
const std::uint64_t silenced_before = hook::audio_frames_silenced();
const DWORD end = GetTickCount() + 300;
while (GetTickCount() < end)
{
while (GetTickCount() < end) {
tone.render_step(50);
}
@@ -117,8 +110,7 @@ void test_see_init(hook::IpcClient& ipc, AudioRingHeader* ring, SharedBlock* blo
fail += expect(s.sample_rate == f.rate, "see-init: HookStatus rate == exact rate");
fail += expect(s.format_state == AudioFormat_Exact, "see-init: provenance == Exact");
fail += expect(ring_has_nonsilent(ring), "see-init: non-silent audio captured");
fail += expect(hook::audio_frames_silenced() > silenced_before,
"see-init: local playback muted (no echo)");
fail += expect(hook::audio_frames_silenced() > silenced_before, "see-init: local playback muted (no echo)");
std::printf(" %s see-init %s -> ring %uHz/%uch/%ubit state=%u\n", fail == 0 ? "PASS" : "FAIL",
fmt_desc(f, d, sizeof(d)), ring->sample_rate, ring->channels, ring->bits, s.format_state);
@@ -137,8 +129,7 @@ void test_guess(hook::IpcClient& ipc, AudioRingHeader* ring, SharedBlock* block,
ToneSource tone;
ToneFormat want;
want.rate = rate; // channels/bits resolve to the device's
if (!tone.open(want))
{
if (!tone.open(want)) {
std::printf(" SKIP guess %u Hz (format unavailable here)\n", rate);
return;
}
@@ -147,14 +138,12 @@ void test_guess(hook::IpcClient& ipc, AudioRingHeader* ring, SharedBlock* block,
// Let the stream reach steady state before attaching, like a game already running when
// we inject (the real case) -- not a stream we caught at its first buffer.
const DWORD warm = GetTickCount() + 300;
while (GetTickCount() < warm)
{
while (GetTickCount() < warm) {
tone.render_step(30);
}
// Hooks install *after* the client exists -> the lazy-discovery (guess) path.
if (!hook::install_audio_hooks(ipc, ring))
{
if (!hook::install_audio_hooks(ipc, ring)) {
std::printf(" SKIP guess (audio hooks unavailable)\n");
tone.close();
return;
@@ -163,16 +152,14 @@ void test_guess(hook::IpcClient& ipc, AudioRingHeader* ring, SharedBlock* block,
// Render while driving republish (the DLL's worker does this each tick) until the
// measured rate is published, then render a bit more so capture fills the ring.
const DWORD measure_deadline = GetTickCount() + 2000;
while (GetTickCount() < measure_deadline && !audio_ring_format_ready(*ring))
{
while (GetTickCount() < measure_deadline && !audio_ring_format_ready(*ring)) {
tone.render_step(30);
hook::republish_audio_format();
}
// Format is published; from here the hook must capture AND mute (the no-echo path).
const std::uint64_t silenced_before = hook::audio_frames_silenced();
const DWORD cap_end = GetTickCount() + 200;
while (GetTickCount() < cap_end)
{
while (GetTickCount() < cap_end) {
tone.render_step(30);
}
@@ -188,8 +175,8 @@ void test_guess(hook::IpcClient& ipc, AudioRingHeader* ring, SharedBlock* block,
// game plays locally AND the mirror re-renders it, slightly delayed = a metallic double.
fail += expect(hook::audio_frames_silenced() > silenced_before,
"guess: local playback muted (no echo) -- the Brotato double-audio bug");
std::printf(" %s guess %u Hz (device %uch/%ubit) -> measured %uHz state=%u\n", fail == 0 ? "PASS" : "FAIL",
rate, f.channels, f.bits, ring->sample_rate, s.format_state);
std::printf(" %s guess %u Hz (device %uch/%ubit) -> measured %uHz state=%u\n", fail == 0 ? "PASS" : "FAIL", rate,
f.channels, f.bits, ring->sample_rate, s.format_state);
tone.close();
hook::remove_audio_hooks();
@@ -200,26 +187,23 @@ void test_guess(hook::IpcClient& ipc, AudioRingHeader* ring, SharedBlock* block,
// VirtualQuery clamp must stop the copy reading past the source buffer. We can't assert a
// "correct" format here (it's fundamentally undetectable); we assert the hook survives and
// doesn't read absurd amounts, i.e. the unit test completes without an access violation.
void test_guess_mismatch_safe(hook::IpcClient& ipc, AudioRingHeader* ring, SharedBlock* block,
const ToneFormat& want, const char* label)
void test_guess_mismatch_safe(hook::IpcClient& ipc, AudioRingHeader* ring, SharedBlock* block, const ToneFormat& want,
const char* label)
{
char d[64];
reset_ring(ring, block);
ToneSource tone;
if (!tone.open(want))
{
if (!tone.open(want)) {
std::printf(" SKIP guess-mismatch %s (%s unavailable here)\n", label, fmt_desc(want, d, sizeof(d)));
return;
}
const ToneFormat& f = tone.format();
const DWORD warm = GetTickCount() + 300; // steady state before attaching
while (GetTickCount() < warm)
{
while (GetTickCount() < warm) {
tone.render_step(30);
}
if (!hook::install_audio_hooks(ipc, ring))
{
if (!hook::install_audio_hooks(ipc, ring)) {
std::printf(" SKIP guess-mismatch (audio hooks unavailable)\n");
tone.close();
return;
@@ -228,8 +212,7 @@ void test_guess_mismatch_safe(hook::IpcClient& ipc, AudioRingHeader* ring, Share
// Render and capture through the guessed (too-large) block. The clamp must keep this
// from over-reading; reaching the end of the loop is the pass (no AV).
const DWORD end = GetTickCount() + 600;
while (GetTickCount() < end)
{
while (GetTickCount() < end) {
tone.render_step(30);
hook::republish_audio_format();
}
@@ -244,8 +227,7 @@ void test_guess_mismatch_safe(hook::IpcClient& ipc, AudioRingHeader* ring, Share
int main()
{
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
{
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
std::printf("FAIL: CoInitializeEx\n");
return 1;
}
@@ -253,8 +235,7 @@ int main()
// Host side: the IPC SharedBlock (named by our pid) the hook's IpcClient connects to,
// plus one producer ring with capture enabled.
SharedMemory shm;
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock)))
{
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock))) {
std::printf("FAIL: create shared memory\n");
return 1;
}
@@ -276,8 +257,7 @@ int main()
ToneFormat dev;
{
ToneSource probe;
if (!probe.open(ToneFormat{}))
{
if (!probe.open(ToneFormat{})) {
std::printf("SKIP: no default render endpoint (no audio device?)\n");
CoUninitialize();
return 0;
@@ -297,8 +277,7 @@ int main()
{44100, 1, 16, false}, // mono PCM
{48000, 6, 32, true}, // 5.1 float
};
for (const ToneFormat& f : see_init)
{
for (const ToneFormat& f : see_init) {
test_see_init(ipc, ring, block, f);
}
@@ -306,8 +285,7 @@ int main()
// rate differs -- the hook measures + corrects it to the true rate (the Brotato/Godot
// case). Rendered at the device's channel/bit layout, so the bytes/frame match.
std::printf("== GUESS, byte-compatible (pre-existing client -> measure the true rate) ==\n");
for (unsigned rate : {44100u, 48000u, 96000u})
{
for (unsigned rate : {44100u, 48000u, 96000u}) {
test_guess(ipc, ring, block, rate);
}
@@ -316,15 +294,13 @@ int main()
// a documented limitation), but the VirtualQuery clamp must keep the capture safe rather
// than over-reading the source buffer.
std::printf("== GUESS, byte-incompatible (channels/bits differ -> capture must stay safe) ==\n");
if (dev.channels >= 2)
{
if (dev.channels >= 2) {
test_guess_mismatch_safe(ipc, ring, block, {dev.rate, 1, dev.bits, dev.is_float}, "mono");
}
{
const unsigned alt_bits = (dev.bits == 32) ? 16u : 32u;
const bool alt_float = (alt_bits == 32);
test_guess_mismatch_safe(ipc, ring, block, {dev.rate, dev.channels, alt_bits, alt_float},
"alt bit depth");
test_guess_mismatch_safe(ipc, ring, block, {dev.rate, dev.channels, alt_bits, alt_float}, "alt bit depth");
}
CoUninitialize();

View File

@@ -22,8 +22,7 @@
using namespace coop;
namespace
{
namespace {
// Directory of this test executable (coop_tone.exe is built alongside it).
std::wstring exe_dir()
@@ -40,19 +39,15 @@ bool wait_for_token(HANDLE pipe, const char* token, DWORD timeout_ms)
{
std::string acc;
const DWORD end = GetTickCount() + timeout_ms;
while (GetTickCount() < end)
{
while (GetTickCount() < end) {
DWORD avail = 0;
if (PeekNamedPipe(pipe, nullptr, 0, nullptr, &avail, nullptr) && avail > 0)
{
if (PeekNamedPipe(pipe, nullptr, 0, nullptr, &avail, nullptr) && avail > 0) {
char buf[256];
DWORD read = 0;
if (ReadFile(pipe, buf, sizeof(buf) - 1, &read, nullptr) && read > 0)
{
if (ReadFile(pipe, buf, sizeof(buf) - 1, &read, nullptr) && read > 0) {
acc.append(buf, read);
std::fwrite(buf, 1, read, stdout);
if (acc.find(token) != std::string::npos)
{
if (acc.find(token) != std::string::npos) {
return true;
}
continue;
@@ -72,8 +67,7 @@ bool capture_one(const WAVEFORMATEX* endpoint_fmt, const std::wstring& tone_args
HANDLE read_pipe = nullptr;
HANDLE write_pipe = nullptr;
SECURITY_ATTRIBUTES sa = {sizeof(sa), nullptr, TRUE};
if (!CreatePipe(&read_pipe, &write_pipe, &sa, 0))
{
if (!CreatePipe(&read_pipe, &write_pipe, &sa, 0)) {
std::printf("FAIL: CreatePipe\n");
return false;
}
@@ -88,8 +82,7 @@ bool capture_one(const WAVEFORMATEX* endpoint_fmt, const std::wstring& tone_args
PROCESS_INFORMATION pi = {};
std::vector<wchar_t> cmd_buf(cmd.begin(), cmd.end());
cmd_buf.push_back(L'\0');
if (!CreateProcessW(nullptr, cmd_buf.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi))
{
if (!CreateProcessW(nullptr, cmd_buf.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi)) {
std::printf("FAIL: CreateProcess(coop_tone) err=%lu\n", GetLastError());
CloseHandle(read_pipe);
CloseHandle(write_pipe);
@@ -98,12 +91,9 @@ bool capture_one(const WAVEFORMATEX* endpoint_fmt, const std::wstring& tone_args
CloseHandle(write_pipe); // keep only the read end
bool ok = false;
if (!wait_for_token(read_pipe, "TONE_RENDERING", 5000))
{
if (!wait_for_token(read_pipe, "TONE_RENDERING", 5000)) {
std::printf("FAIL: tone generator never started rendering\n");
}
else
{
} else {
ProcessLoopbackCapture capture;
const bool started = capture.start(pi.dwProcessId, endpoint_fmt, nullptr);
std::printf("Capture start: %s, targeting pid %lu\n", started ? "ok" : "FAILED", pi.dwProcessId);
@@ -114,8 +104,7 @@ bool capture_one(const WAVEFORMATEX* endpoint_fmt, const std::wstring& tone_args
// Expect at least ~0.2 s of non-silent audio for a 1.5 s capture.
const unsigned long long need = endpoint_fmt->nSamplesPerSec / 5;
std::printf("Non-silent frames: %llu (need >= %llu)\n", static_cast<unsigned long long>(nonsilent),
need);
std::printf("Non-silent frames: %llu (need >= %llu)\n", static_cast<unsigned long long>(nonsilent), need);
ok = nonsilent >= need;
std::printf("%s\n", ok ? "PASS" : "FAIL: too few non-silent frames");
}
@@ -132,27 +121,23 @@ bool capture_one(const WAVEFORMATEX* endpoint_fmt, const std::wstring& tone_args
int main()
{
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
{
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
std::printf("FAIL: CoInitializeEx\n");
return 1;
}
WAVEFORMATEX* fmt = default_render_format();
if (!fmt)
{
if (!fmt) {
std::printf("SKIP: no default render endpoint (no audio device?)\n");
CoUninitialize();
return 0;
}
std::printf("Endpoint format: %u Hz, %u ch, %u-bit\n", fmt->nSamplesPerSec, fmt->nChannels,
fmt->wBitsPerSample);
std::printf("Endpoint format: %u Hz, %u ch, %u-bit\n", fmt->nSamplesPerSec, fmt->nChannels, fmt->wBitsPerSample);
// Each case spawns coop_tone at a different source format; loopback should capture all
// of them correctly because it captures post-mix at the device endpoint format.
// Args: <seconds> <freq> <rate> <channels> <bits> <float|pcm>. ~8 s outlives capture.
struct Case
{
struct Case {
std::wstring args;
const char* label;
};
@@ -164,10 +149,8 @@ int main()
};
int failures = 0;
for (const Case& c : cases)
{
if (!capture_one(fmt, c.args, c.label))
{
for (const Case& c : cases) {
if (!capture_one(fmt, c.args, c.label)) {
++failures;
}
}

View File

@@ -9,13 +9,11 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
if (!ok)
{
if (!ok) {
std::printf("FAIL: %s\n", what);
++g_failures;
}
@@ -47,8 +45,7 @@ int main()
float out[4] = {};
mix_store(reinterpret_cast<std::uint8_t*>(out), acc, 4, kWaveFormatFloat, 32);
// Sum then tanh; small sums are ~unchanged.
for (int i = 0; i < 4; ++i)
{
for (int i = 0; i < 4; ++i) {
check(near_f(out[i], std::tanh(a[i] + b[i])), "float32 mix == tanh(sum)");
}
}
@@ -57,8 +54,7 @@ int main()
{
float acc[2] = {};
const float loud[2] = {0.9f, -0.9f};
for (int s = 0; s < 5; ++s)
{
for (int s = 0; s < 5; ++s) {
mix_add(acc, reinterpret_cast<const std::uint8_t*>(loud), 2, kWaveFormatFloat, 32);
}
float out[2] = {};
@@ -79,8 +75,7 @@ int main()
check(std::abs(out[0] - 1000) <= 3 && std::abs(out[1] - (-2000)) <= 3, "int16 round-trip");
}
if (g_failures == 0)
{
if (g_failures == 0) {
std::printf("PASS: audio_mix_test\n");
return 0;
}

View File

@@ -12,18 +12,14 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
if (!ok)
{
if (!ok) {
std::printf("FAIL: %s\n", what);
++g_failures;
}
else
{
} else {
std::printf(" ok: %s\n", what);
}
}
@@ -93,8 +89,7 @@ int main()
}
DeleteFileW(path.c_str());
if (g_failures == 0)
{
if (g_failures == 0) {
std::printf("PASS audio_overrides_test\n");
return 0;
}

View File

@@ -13,15 +13,13 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
if (!ok)
{
if (!ok) {
std::printf(" FAIL: %s\n", what);
++g_failures;
}
@@ -49,8 +47,7 @@ int main()
check(!audio_ring_format_ready(*h), "format not ready before set");
audio_ring_set_format(*h, 48000, 2, 32, 3 /*IEEE_FLOAT*/, 8);
check(audio_ring_format_ready(*h), "format ready after set");
check(h->sample_rate == 48000 && h->channels == 2 && h->bits == 32 && h->format_tag == 3 &&
h->block_align == 8,
check(h->sample_rate == 48000 && h->channels == 2 && h->bits == 32 && h->format_tag == 3 && h->block_align == 8,
"format fields round-trip");
}
@@ -60,8 +57,7 @@ int main()
AudioRingHeader* h = make_ring(storage, 4096);
std::uint8_t src[256];
for (int i = 0; i < 256; ++i)
{
for (int i = 0; i < 256; ++i) {
src[i] = static_cast<std::uint8_t>(i);
}
check(audio_ring_push(*h, src, sizeof(src), 32), "push 256 bytes");
@@ -83,11 +79,9 @@ int main()
std::uint8_t counter = 0;
std::uint8_t expect = 0;
const std::uint32_t chunk = 300; // not a divisor of cap, so offsets drift across the seam
for (int iter = 0; iter < 50; ++iter)
{
for (int iter = 0; iter < 50; ++iter) {
std::uint8_t buf[300];
for (std::uint32_t i = 0; i < chunk; ++i)
{
for (std::uint32_t i = 0; i < chunk; ++i) {
buf[i] = counter++;
}
check(audio_ring_push(*h, buf, chunk, chunk), "wrap push fits");
@@ -95,8 +89,7 @@ int main()
std::uint8_t out[300] = {};
check(audio_ring_pop(*h, out, chunk) == chunk, "wrap pop full chunk");
bool ok = true;
for (std::uint32_t i = 0; i < chunk; ++i)
{
for (std::uint32_t i = 0; i < chunk; ++i) {
ok = ok && out[i] == expect++;
}
check(ok, "wrap data integrity across the ring seam");
@@ -142,8 +135,7 @@ int main()
// A 200-byte push now splits across the seam; it fits, so the data must survive the split.
std::vector<std::uint8_t> wrap(200);
for (std::uint32_t i = 0; i < 200; ++i)
{
for (std::uint32_t i = 0; i < 200; ++i) {
wrap[i] = static_cast<std::uint8_t>(i);
}
check(audio_ring_push(*h, wrap.data(), 200, 1), "seam: a wrapping push that fits is accepted");
@@ -160,8 +152,7 @@ int main()
std::vector<std::uint8_t> out(200, 0);
check(audio_ring_pop(*h, out.data(), 200) == 200, "seam: pop the wrapped payload");
bool ok = true;
for (std::uint32_t i = 0; i < 200; ++i)
{
for (std::uint32_t i = 0; i < 200; ++i) {
ok = ok && out[i] == static_cast<std::uint8_t>(i);
}
check(ok, "seam: wrapping push/pop preserved data across the buffer seam");

View File

@@ -27,14 +27,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;
}
}
@@ -42,18 +40,14 @@ void check(bool ok, const char* what)
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)
{
if (HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID))
{
for (BOOL ok = Process32FirstW(snap, &pe); ok; ok = Process32NextW(snap, &pe)) {
if (_wcsicmp(pe.szExeFile, L"coop_mock_game.exe") == 0) {
if (HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID)) {
TerminateProcess(h, 0);
CloseHandle(h);
}
@@ -65,26 +59,22 @@ void kill_stray_mock_games()
bool inject(unsigned long pid)
{
const std::wstring dll = deployed_artifact_path(L"coop_hook.dll");
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 (HANDLE th = CreateRemoteThread(process, nullptr, 0, load, remote, 0, nullptr))
{
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 (HANDLE th = CreateRemoteThread(process, nullptr, 0, load, remote, 0, nullptr)) {
WaitForSingleObject(th, INFINITE);
DWORD code = 0;
GetExitCodeThread(th, &code);
@@ -92,8 +82,7 @@ bool inject(unsigned long pid)
ok = code != 0;
}
}
if (remote != nullptr)
{
if (remote != nullptr) {
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
}
CloseHandle(process);
@@ -102,10 +91,8 @@ bool inject(unsigned long pid)
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);
@@ -113,8 +100,7 @@ bool inject_retry(unsigned long pid)
return false;
}
struct Scenario
{
struct Scenario {
unsigned rate, channels, bits;
bool distinct; // distinct per-channel content (so the channel count is recoverable)
bool recover_layout; // false = rate only (step a); true = full layout (step b)
@@ -128,25 +114,21 @@ FormatVerification run(const Scenario& sc, bool& ran)
FormatVerification fv;
kill_stray_mock_games();
if (sc.distinct)
{
if (sc.distinct) {
SetEnvironmentVariableW(L"COOP_TONE_DISTINCT_CH", L"1");
}
const std::wstring exe = exe_directory() + L"coop_mock_game.exe";
std::wstring cmd = L"\"" + exe + L"\" dx11 30 " + std::to_wstring(sc.rate) + L" " +
std::to_wstring(sc.channels) + L" " + std::to_wstring(sc.bits) + L" " +
(sc.bits == 16 ? L"pcm" : L"float");
std::wstring cmd = L"\"" + exe + L"\" dx11 30 " + std::to_wstring(sc.rate) + L" " + std::to_wstring(sc.channels)
+ L" " + std::to_wstring(sc.bits) + L" " + (sc.bits == 16 ? L"pcm" : L"float");
STARTUPINFOW si{};
si.cb = sizeof(si);
PROCESS_INFORMATION pi{};
const BOOL launched = CreateProcessW(exe.c_str(), cmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr,
&si, &pi);
if (sc.distinct)
{
const BOOL launched =
CreateProcessW(exe.c_str(), cmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
if (sc.distinct) {
SetEnvironmentVariableW(L"COOP_TONE_DISTINCT_CH", nullptr);
}
if (!launched)
{
if (!launched) {
return fv;
}
auto cleanup = [&] {
@@ -160,9 +142,8 @@ FormatVerification run(const Scenario& sc, bool& ran)
SharedMemory shm;
SharedMemory ring_shm;
if (!shm.create(shared_memory_name(pi.dwProcessId), sizeof(SharedBlock)) ||
!ring_shm.create(audio_ring_name(pi.dwProcessId), audio_ring_total_size(kAudioRingCapacity)))
{
if (!shm.create(shared_memory_name(pi.dwProcessId), sizeof(SharedBlock))
|| !ring_shm.create(audio_ring_name(pi.dwProcessId), audio_ring_total_size(kAudioRingCapacity))) {
cleanup();
return fv;
}
@@ -178,8 +159,7 @@ FormatVerification run(const Scenario& sc, bool& ran)
auto* ring = ring_shm.as<AudioRingHeader>();
audio_ring_init(*ring, kAudioRingCapacity); // capture_enabled stays 0: audible + still measuring
if (!inject_retry(pi.dwProcessId))
{
if (!inject_retry(pi.dwProcessId)) {
cleanup();
return fv;
}
@@ -197,8 +177,7 @@ int main()
const bool com = SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
unsigned dev_rate = 48000, dev_channels = 2;
if (WAVEFORMATEX* dev = default_render_format())
{
if (WAVEFORMATEX* dev = default_render_format()) {
dev_rate = dev->nSamplesPerSec;
dev_channels = dev->nChannels;
CoTaskMemFree(dev);
@@ -210,21 +189,17 @@ int main()
std::printf("== (a) rate recovery: %u Hz / %u ch ==\n", mismatched, dev_channels);
bool ran = false;
FormatVerification a = run({mismatched, dev_channels, 32, /*distinct=*/false, /*recover_layout=*/false}, ran);
if (!ran)
{
if (!ran) {
std::printf(" environment can't run the mock+inject -- skipping audio_verify_test.\n");
if (com)
{
if (com) {
CoUninitialize();
}
return 0;
}
std::printf(" ok=%d rate=%u score=%.3f\n", a.ok ? 1 : 0, a.rate, a.score);
if (!a.ok && a.rate == 0 && a.score == 0.0)
{
if (!a.ok && a.rate == 0 && a.score == 0.0) {
std::printf(" no usable co-capture (no endpoint / silent) -- skipping.\n");
if (com)
{
if (com) {
CoUninitialize();
}
return 0;
@@ -238,25 +213,20 @@ int main()
FormatVerification b = run({44100, 2, 32, /*distinct=*/true, /*recover_layout=*/true}, ran);
std::printf(" ok=%d rate=%u ch=%u bits=%u tag=%u score=%.3f\n", b.ok ? 1 : 0, b.rate, b.channels, b.bits,
b.format_tag, b.score);
if (b.ok || b.score > 0.0)
{
if (b.ok || b.score > 0.0) {
check(b.layout_ok, "(b) verifier confidently recovered the layout");
check(b.rate == 44100, "(b) recovered the true rate");
check(b.channels == 2, "(b) recovered the true channel count (2, not the device's)");
check(b.bits == 32 && b.format_tag == 3, "(b) recovered 32-bit float");
}
else
{
} else {
std::printf(" no usable co-capture for (b) -- skipping that scenario.\n");
}
if (com)
{
if (com) {
CoUninitialize();
}
if (g_failures == 0)
{
if (g_failures == 0) {
std::printf("PASS audio_verify_test\n");
return 0;
}

View File

@@ -23,13 +23,11 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
if (!ok)
{
if (!ok) {
std::printf(" FAIL: %s\n", what);
++g_failures;
}
@@ -37,8 +35,7 @@ void check(bool ok, const char* what)
template <typename T>
void release(T*& p)
{
if (p)
{
if (p) {
p->Release();
p = nullptr;
}
@@ -55,8 +52,7 @@ constexpr UINT kH = 720;
int main()
{
SharedMemory shm;
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock)))
{
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock))) {
std::printf("FAIL: create shared memory\n");
return 1;
}
@@ -68,15 +64,13 @@ int main()
hook::IpcClient ipc;
check(ipc.connect(10, 5), "IPC client connect");
if (!hook::install_d3d9_hooks(ipc))
{
if (!hook::install_d3d9_hooks(ipc)) {
std::printf("SKIP: could not install the D3D9 Present hook (no d3d9.dll / device?)\n");
return 0;
}
IDirect3D9* d3d = Direct3DCreate9(D3D_SDK_VERSION);
if (d3d == nullptr)
{
if (d3d == nullptr) {
std::printf("SKIP: Direct3DCreate9 failed\n");
hook::remove_d3d9_hooks();
return 0;
@@ -101,8 +95,7 @@ int main()
IDirect3DDevice9* dev = nullptr;
HRESULT hr = d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED, &pp, &dev);
if (FAILED(hr) || dev == nullptr)
{
if (FAILED(hr) || dev == nullptr) {
std::printf("SKIP: CreateDevice failed (hr=0x%08lX)\n", static_cast<unsigned long>(hr));
release(d3d);
hook::remove_d3d9_hooks();
@@ -111,8 +104,7 @@ int main()
// Clear to a known color (R=51,G=102,B=153) and Present -> fires the detour.
const D3DCOLOR color = D3DCOLOR_XRGB(51, 102, 153);
for (int frame = 0; frame < 3; ++frame)
{
for (int frame = 0; frame < 3; ++frame) {
dev->Clear(0, nullptr, D3DCLEAR_TARGET, color, 1.0f, 0);
dev->Present(nullptr, nullptr, nullptr, nullptr);
}
@@ -132,18 +124,15 @@ int main()
ID3D11Device* devB = nullptr;
ID3D11DeviceContext* ctxB = nullptr;
if (SUCCEEDED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION,
&devB, nullptr, &ctxB)))
{
&devB, nullptr, &ctxB))) {
ID3D11Device1* dev1 = nullptr;
devB->QueryInterface(IID_PPV_ARGS(&dev1));
ID3D11Texture2D* sharedB = nullptr;
IDXGIKeyedMutex* km = nullptr;
const std::wstring name = video_share_name(GetCurrentProcessId());
if (dev1 != nullptr &&
SUCCEEDED(dev1->OpenSharedResourceByName(name.c_str(),
DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
IID_PPV_ARGS(&sharedB))))
{
if (dev1 != nullptr
&& SUCCEEDED(dev1->OpenSharedResourceByName(
name.c_str(), DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, IID_PPV_ARGS(&sharedB)))) {
sharedB->QueryInterface(IID_PPV_ARGS(&km));
D3D11_TEXTURE2D_DESC sd{};
sharedB->GetDesc(&sd);
@@ -153,32 +142,24 @@ int main()
sd.MiscFlags = 0;
ID3D11Texture2D* staging = nullptr;
check(SUCCEEDED(devB->CreateTexture2D(&sd, nullptr, &staging)), "create staging texture");
if (km != nullptr && staging != nullptr && km->AcquireSync(kVideoMutexKey, 1000) == S_OK)
{
if (km != nullptr && staging != nullptr && km->AcquireSync(kVideoMutexKey, 1000) == S_OK) {
ctxB->CopyResource(staging, sharedB);
km->ReleaseSync(kVideoMutexKey);
D3D11_MAPPED_SUBRESOURCE mapped{};
if (SUCCEEDED(ctxB->Map(staging, 0, D3D11_MAP_READ, 0, &mapped)))
{
if (SUCCEEDED(ctxB->Map(staging, 0, D3D11_MAP_READ, 0, &mapped))) {
const auto* px = static_cast<const std::uint8_t*>(mapped.pData);
std::printf("readback pixel0 = {%u,%u,%u,%u}\n", px[0], px[1], px[2], px[3]);
check(near_byte(px[0], 51) && near_byte(px[1], 102) && near_byte(px[2], 153),
"shared texture carries the rendered color (BGRA->RGBA swizzle)");
ctxB->Unmap(staging, 0);
}
else
{
} else {
check(false, "map staging texture");
}
}
else
{
} else {
check(false, "acquire keyed mutex + copy shared texture");
}
release(staging);
}
else
{
} else {
check(false, "open shared texture by name");
}
release(km);

View File

@@ -13,14 +13,12 @@
using coop::hook::DetourGate;
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;
}
}
@@ -48,8 +46,7 @@ int main()
// Release the Guard; drain() must then return promptly.
delete guard;
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(500);
while (!drained.load(std::memory_order_acquire) && std::chrono::steady_clock::now() < deadline)
{
while (!drained.load(std::memory_order_acquire) && std::chrono::steady_clock::now() < deadline) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
check(drained.load(std::memory_order_acquire), "drain() returns once the in-flight detour finishes");
@@ -62,8 +59,8 @@ int main()
DetourGate gate;
const auto t0 = std::chrono::steady_clock::now();
gate.drain();
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - t0)
.count();
const auto ms =
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - t0).count();
check(ms < 100, "drain() with no in-flight detours returns quickly");
}
@@ -79,15 +76,12 @@ int main()
std::atomic<long long> violations{0};
auto worker = [&] {
while (!stop.load(std::memory_order_relaxed))
{
if (disabled.load(std::memory_order_acquire))
{
while (!stop.load(std::memory_order_relaxed)) {
if (disabled.load(std::memory_order_acquire)) {
continue; // "hook removed" -> no new detour starts
}
DetourGate::Guard g(gate);
if (freed.load(std::memory_order_acquire))
{
if (freed.load(std::memory_order_acquire)) {
violations.fetch_add(1, std::memory_order_relaxed); // ran the body on freed state
}
guarded.fetch_add(1, std::memory_order_relaxed);
@@ -95,12 +89,10 @@ int main()
};
std::thread workers[6];
for (auto& w : workers)
{
for (auto& w : workers) {
w = std::thread(worker);
}
for (int c = 0; c < 3000; ++c)
{
for (int c = 0; c < 3000; ++c) {
disabled.store(true, std::memory_order_release); // disable: no new detours
gate.drain(); // wait for in-flight detours
freed.store(true, std::memory_order_release); // "free" the shared state
@@ -108,8 +100,7 @@ int main()
disabled.store(false, std::memory_order_release);
}
stop.store(true, std::memory_order_relaxed);
for (auto& w : workers)
{
for (auto& w : workers) {
w.join();
}
std::printf(" stress: guarded=%lld violations=%lld\n", guarded.load(), violations.load());

View File

@@ -23,14 +23,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;
}
}
@@ -44,8 +42,8 @@ HWND make_window()
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);
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
@@ -53,13 +51,11 @@ HWND make_window()
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)
{
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()))
{
if (FAILED(dev->SetDataFormat(fmt)) || FAILED(dev->SetCooperativeLevel(hwnd, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))
|| FAILED(dev->Acquire())) {
dev->Release();
return nullptr;
}
@@ -73,8 +69,7 @@ int main()
// Host side: the IPC block + a couple of forwarded events.
SharedMemory shm;
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock)))
{
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock))) {
std::printf("Could not create shared memory -- skipping.\n");
return 0;
}
@@ -99,35 +94,30 @@ int main()
IDirectInput8W* di = nullptr;
if (FAILED(DirectInput8Create(GetModuleHandleW(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8W,
reinterpret_cast<void**>(&di), nullptr)) ||
di == nullptr)
{
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))
{
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]);
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
{
} 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))
{
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),
@@ -136,20 +126,16 @@ int main()
check((m.rgbButtons[0] & 0x80) != 0, "forwarded left button appears in the DirectInput mouse state");
ms->Unacquire();
ms->Release();
}
else
{
} else {
std::printf(" mouse device unavailable -- skipping mouse assertion.\n");
}
di->Release();
hook::remove_mkb_hooks();
if (hwnd != nullptr)
{
if (hwnd != nullptr) {
DestroyWindow(hwnd);
}
if (com)
{
if (com) {
CoUninitialize();
}

View File

@@ -9,14 +9,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;
}
}

View File

@@ -26,14 +26,12 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
if (!ok)
{
if (!ok) {
std::printf(" FAIL: %s\n", what);
++g_failures;
}
@@ -42,8 +40,7 @@ void check(bool ok, const char* what)
template <typename T>
void release(T*& p)
{
if (p)
{
if (p) {
p->Release();
p = nullptr;
}
@@ -66,8 +63,7 @@ int main()
{
// --- Host side: shared block named by our pid (the hook opens the same name). ---
SharedMemory shm;
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock)))
{
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock))) {
std::printf("FAIL: create shared memory\n");
return 1;
}
@@ -78,8 +74,7 @@ int main()
// --- D3D12 device + direct queue. SKIP if the machine has no D3D12. ---
ID3D12Device* device = nullptr;
if (FAILED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device))) || device == nullptr)
{
if (FAILED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device))) || device == nullptr) {
std::printf("SKIP: no D3D12 device on this machine\n");
return 0;
}
@@ -114,8 +109,7 @@ int main()
"create D3D12 swapchain");
IDXGISwapChain* swapchain = nullptr;
IDXGISwapChain3* sc3 = nullptr; // for GetCurrentBackBufferIndex
if (sc1 != nullptr)
{
if (sc1 != nullptr) {
sc1->QueryInterface(IID_PPV_ARGS(&swapchain));
sc1->QueryInterface(IID_PPV_ARGS(&sc3));
}
@@ -128,11 +122,9 @@ int main()
device->CreateDescriptorHeap(&hd, IID_PPV_ARGS(&rtv_heap));
const UINT rtv_size = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
ID3D12Resource* render_targets[kBuffers] = {};
if (swapchain != nullptr && rtv_heap != nullptr)
{
if (swapchain != nullptr && rtv_heap != nullptr) {
D3D12_CPU_DESCRIPTOR_HANDLE rtv = rtv_heap->GetCPUDescriptorHandleForHeapStart();
for (UINT i = 0; i < kBuffers; ++i)
{
for (UINT i = 0; i < kBuffers; ++i) {
swapchain->GetBuffer(i, IID_PPV_ARGS(&render_targets[i]));
device->CreateRenderTargetView(render_targets[i], nullptr, rtv);
rtv.ptr += rtv_size;
@@ -143,8 +135,7 @@ int main()
device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator));
ID3D12GraphicsCommandList* cmdlist = nullptr;
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator, nullptr, IID_PPV_ARGS(&cmdlist));
if (cmdlist != nullptr)
{
if (cmdlist != nullptr) {
cmdlist->Close();
}
ID3D12Fence* fence = nullptr;
@@ -159,8 +150,7 @@ int main()
const bool can_render =
swapchain != nullptr && sc3 != nullptr && allocator != nullptr && cmdlist != nullptr && fence != nullptr;
for (int frame = 0; frame < 4 && can_render; ++frame)
{
for (int frame = 0; frame < 4 && can_render; ++frame) {
const UINT idx = sc3->GetCurrentBackBufferIndex();
allocator->Reset();
cmdlist->Reset(allocator, nullptr);
@@ -188,8 +178,7 @@ int main()
// Block until the GPU finished this frame (keeps the test simple + correct).
queue->Signal(fence, ++fence_value);
if (fence->GetCompletedValue() < fence_value)
{
if (fence->GetCompletedValue() < fence_value) {
fence->SetEventOnCompletion(fence_value, fence_event);
WaitForSingleObject(fence_event, 1000);
}
@@ -200,8 +189,7 @@ int main()
static_cast<unsigned long long>(hook::present_frames_shared()), block->video.generation.load(),
block->video.width, block->video.height, block->video.format);
if (can_render)
{
if (can_render) {
check(hook::present_calls() >= 3, "Present detour fired for the D3D12 swapchain");
check(hook::present_frames_shared() > 0, "D3D12 backbuffer bridged into the shared texture");
check(block->video.generation.load() > 0, "video generation published to IPC");
@@ -211,18 +199,15 @@ int main()
ID3D11Device* devB = nullptr;
ID3D11DeviceContext* ctxB = nullptr;
if (SUCCEEDED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION,
&devB, nullptr, &ctxB)))
{
&devB, nullptr, &ctxB))) {
ID3D11Device1* dev1 = nullptr;
devB->QueryInterface(IID_PPV_ARGS(&dev1));
const std::wstring name = video_share_name(GetCurrentProcessId());
ID3D11Texture2D* sharedB = nullptr;
IDXGIKeyedMutex* km = nullptr;
if (dev1 != nullptr &&
SUCCEEDED(dev1->OpenSharedResourceByName(name.c_str(),
DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
IID_PPV_ARGS(&sharedB))))
{
if (dev1 != nullptr
&& SUCCEEDED(dev1->OpenSharedResourceByName(
name.c_str(), DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, IID_PPV_ARGS(&sharedB)))) {
sharedB->QueryInterface(IID_PPV_ARGS(&km));
D3D11_TEXTURE2D_DESC sd{};
sharedB->GetDesc(&sd);
@@ -232,32 +217,24 @@ int main()
sd.MiscFlags = 0;
ID3D11Texture2D* staging = nullptr;
check(SUCCEEDED(devB->CreateTexture2D(&sd, nullptr, &staging)), "create staging texture");
if (km != nullptr && staging != nullptr && km->AcquireSync(kVideoMutexKey, 1000) == S_OK)
{
if (km != nullptr && staging != nullptr && km->AcquireSync(kVideoMutexKey, 1000) == S_OK) {
ctxB->CopyResource(staging, sharedB);
km->ReleaseSync(kVideoMutexKey);
D3D11_MAPPED_SUBRESOURCE mapped{};
if (SUCCEEDED(ctxB->Map(staging, 0, D3D11_MAP_READ, 0, &mapped)))
{
if (SUCCEEDED(ctxB->Map(staging, 0, D3D11_MAP_READ, 0, &mapped))) {
const auto* px = static_cast<const std::uint8_t*>(mapped.pData);
std::printf("readback pixel0 = {%u,%u,%u,%u}\n", px[0], px[1], px[2], px[3]);
check(near_byte(px[0], 51) && near_byte(px[1], 102) && near_byte(px[2], 153),
"shared texture carries the D3D12-rendered color");
ctxB->Unmap(staging, 0);
}
else
{
} else {
check(false, "map staging texture");
}
}
else
{
} else {
check(false, "acquire keyed mutex + copy shared texture");
}
release(staging);
}
else
{
} else {
check(false, "open shared texture by name");
}
release(km);
@@ -292,8 +269,7 @@ int main()
auto present = [&] {
swapchain->Present(0, 0);
queue->Signal(fence, ++fence_value);
if (fence->GetCompletedValue() < fence_value)
{
if (fence->GetCompletedValue() < fence_value) {
fence->SetEventOnCompletion(fence_value, fence_event);
WaitForSingleObject(fence_event, 1000);
}
@@ -309,15 +285,13 @@ int main()
hook::remove_present_hooks();
if (fence_event != nullptr)
{
if (fence_event != nullptr) {
CloseHandle(fence_event);
}
release(fence);
release(cmdlist);
release(allocator);
for (UINT i = 0; i < kBuffers; ++i)
{
for (UINT i = 0; i < kBuffers; ++i) {
release(render_targets[i]);
}
release(rtv_heap);
@@ -330,7 +304,6 @@ int main()
DestroyWindow(hwnd);
UnregisterClassW(wc.lpszClassName, wc.hInstance);
std::printf(g_failures == 0 ? "DX12 PRESENT HOOK TEST PASS\n" : "DX12 PRESENT HOOK TEST FAILED (%d)\n",
g_failures);
std::printf(g_failures == 0 ? "DX12 PRESENT HOOK TEST PASS\n" : "DX12 PRESENT HOOK TEST FAILED (%d)\n", g_failures);
return g_failures == 0 ? 0 : 1;
}

View File

@@ -14,22 +14,19 @@
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;
}
}
void pump()
{
MSG msg;
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
@@ -39,8 +36,7 @@ void pump()
int main()
{
SharedMemory shm;
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock)))
{
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock))) {
std::printf("FAIL: create shared memory\n");
return 1;
}
@@ -66,17 +62,14 @@ int main()
// Install, retrying a few times in case the window isn't enumerable yet.
bool installed = false;
for (int i = 0; i < 20 && !installed; ++i)
{
for (int i = 0; i < 20 && !installed; ++i) {
installed = hook::install_focus_spoof(ipc);
if (!installed)
{
if (!installed) {
pump();
Sleep(20);
}
}
if (!installed)
{
if (!installed) {
std::printf("SKIP: focus spoof could not find the test window\n");
DestroyWindow(win);
UnregisterClassW(wc.lpszClassName, wc.hInstance);

View File

@@ -14,14 +14,12 @@
using coop::hook::DetourGate;
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;
}
}
@@ -53,8 +51,7 @@ void install()
}
void remove()
{
if (g_hook && !g_hook.disable())
{
if (g_hook && !g_hook.disable()) {
// surface, don't discard
}
g_gate.drain(); // persistent model: disable + drain, but do NOT destroy
@@ -87,8 +84,7 @@ int main()
// Many cycles: the trampoline must never change and enable/disable must toggle cleanly.
bool reuse = true, toggles = true;
for (int i = 0; i < 50; ++i)
{
for (int i = 0; i < 50; ++i) {
remove();
toggles = toggles && !g_hook.enabled();
install();

View File

@@ -15,8 +15,7 @@
using namespace coop;
namespace
{
namespace {
constexpr std::uint16_t kButtonA = 0x1000;
constexpr std::uint16_t kButtonB = 0x2000;
@@ -25,8 +24,7 @@ int g_failures = 0;
void check(bool ok, const char* what)
{
if (!ok)
{
if (!ok) {
std::printf(" FAIL: %s\n", what);
++g_failures;
}
@@ -41,8 +39,7 @@ void check(bool ok, const char* what)
void exercise_dll(const wchar_t* dll_name)
{
HMODULE m = GetModuleHandleW(dll_name);
if (m == nullptr)
{
if (m == nullptr) {
return; // not loaded on this machine; nothing to exercise
}
char tag[96];
@@ -56,26 +53,22 @@ void exercise_dll(const wchar_t* dll_name)
auto get_caps = reinterpret_cast<GetCaps_t>(GetProcAddress(m, "XInputGetCapabilities"));
auto set_state = reinterpret_cast<SetState_t>(GetProcAddress(m, "XInputSetState"));
if (get_state != nullptr)
{
if (get_state != nullptr) {
XINPUT_STATE s = {};
std::snprintf(tag, sizeof(tag), "%ls XInputGetState forwards state", dll_name);
check(get_state(0, &s) == ERROR_SUCCESS && s.dwPacketNumber == 7, tag);
}
if (get_state_ex != nullptr)
{
if (get_state_ex != nullptr) {
XINPUT_STATE s = {};
std::snprintf(tag, sizeof(tag), "%ls XInputGetStateEx (ord 100) forwards state", dll_name);
check(get_state_ex(0, &s) == ERROR_SUCCESS && s.dwPacketNumber == 7, tag);
}
if (get_caps != nullptr)
{
if (get_caps != nullptr) {
XINPUT_CAPABILITIES c = {};
std::snprintf(tag, sizeof(tag), "%ls XInputGetCapabilities reports gamepad", dll_name);
check(get_caps(0, 0, &c) == ERROR_SUCCESS && c.Type == XINPUT_DEVTYPE_GAMEPAD, tag);
}
if (set_state != nullptr)
{
if (set_state != nullptr) {
// A game commonly rumbles in response to a button press; this is the call
// path "crashes as soon as a button is pressed" pointed at.
XINPUT_VIBRATION v = {};
@@ -89,9 +82,9 @@ void exercise_dll(const wchar_t* dll_name)
void dump_layout()
{
std::printf("LAYOUT sizeof(SharedBlock)=%zu CoopPadState=%zu\n", sizeof(SharedBlock), sizeof(CoopPadState));
std::printf("LAYOUT off pads=%zu sequence=%zu status=%zu control=%zu video=%zu\n",
offsetof(SharedBlock, pads), offsetof(SharedBlock, sequence), offsetof(SharedBlock, status),
offsetof(SharedBlock, control), offsetof(SharedBlock, video));
std::printf("LAYOUT off pads=%zu sequence=%zu status=%zu control=%zu video=%zu\n", offsetof(SharedBlock, pads),
offsetof(SharedBlock, sequence), offsetof(SharedBlock, status), offsetof(SharedBlock, control),
offsetof(SharedBlock, video));
std::printf("LAYOUT HookStatus sizeof=%zu get_state_calls=%zu attached=%zu audio_streams=%zu hook_entries=%zu\n",
sizeof(HookStatus), offsetof(HookStatus, get_state_calls), offsetof(HookStatus, attached),
offsetof(HookStatus, audio_streams), offsetof(HookStatus, hook_entries));
@@ -118,8 +111,7 @@ int main()
// --- Host side: create the section (named by our pid) and publish a pad. ---
SharedMemory shm;
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock)))
{
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock))) {
std::printf("FAIL: could not create shared memory\n");
return 1;
}
@@ -143,8 +135,7 @@ int main()
// loads exactly one, but which one varies by game age -- and the 32-bit crash
// only reproduces over the specific DLL the game uses.
const wchar_t* xinput_modules[] = {L"xinput1_4.dll", L"xinput1_3.dll", L"xinput9_1_0.dll", L"xinputuap.dll"};
for (const wchar_t* name : xinput_modules)
{
for (const wchar_t* name : xinput_modules) {
LoadLibraryW(name); // best-effort; absent variants stay unloaded
}
@@ -172,8 +163,7 @@ int main()
// Now drive every loaded variant's full export set (GetState, ordinal-100
// GetStateEx, GetCapabilities, and the rumble SetState a game calls on a button
// press) so each DLL's hooked prologue/trampoline is actually run.
for (const wchar_t* name : xinput_modules)
{
for (const wchar_t* name : xinput_modules) {
exercise_dll(name);
}

View File

@@ -15,24 +15,20 @@
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;
}
}
bool all_disabled(const SharedBlock* b)
{
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s)
{
if (b->control.subsystem_disabled[s].load(std::memory_order_acquire) != 1u)
{
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s) {
if (b->control.subsystem_disabled[s].load(std::memory_order_acquire) != 1u) {
return false;
}
}
@@ -41,10 +37,8 @@ bool all_disabled(const SharedBlock* b)
bool none_disabled(const SharedBlock* b)
{
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s)
{
if (b->control.subsystem_disabled[s].load(std::memory_order_acquire) != 0u)
{
for (std::uint32_t s = 0; s < HookSubsys_Count; ++s) {
if (b->control.subsystem_disabled[s].load(std::memory_order_acquire) != 0u) {
return false;
}
}
@@ -57,8 +51,7 @@ int main()
const unsigned long pid = GetCurrentProcessId(); // section name is per-pid; no real game needed
IpcServer server;
if (!server.start(pid))
{
if (!server.start(pid)) {
std::printf("FAIL: IpcServer::start\n");
return 1;
}
@@ -66,8 +59,7 @@ int main()
// A second view of the same section, standing in for the injected hook: it reads the control
// flags the host writes and publishes the hook registry the host reads back.
SharedMemory hook_view;
if (!hook_view.open(shared_memory_name(pid), sizeof(SharedBlock)))
{
if (!hook_view.open(shared_memory_name(pid), sizeof(SharedBlock))) {
std::printf("FAIL: open hook view\n");
return 1;
}

View File

@@ -14,14 +14,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;
}
}

View File

@@ -18,14 +18,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;
}
}
@@ -37,8 +35,7 @@ std::string make_line(unsigned thread, unsigned long long seq)
char token[32];
std::snprintf(token, sizeof(token), "T%02uS%010llu", thread, seq);
std::string s;
while (s.size() + std::strlen(token) + 1 < kLogMsgLen - 1)
{
while (s.size() + std::strlen(token) + 1 < kLogMsgLen - 1) {
s += token;
s += ' ';
}
@@ -50,29 +47,20 @@ bool line_consistent(const char* text)
{
std::string first;
std::string cur;
for (const char* p = text;; ++p)
{
if (*p == ' ' || *p == '\0')
{
if (!cur.empty())
{
if (first.empty())
{
for (const char* p = text;; ++p) {
if (*p == ' ' || *p == '\0') {
if (!cur.empty()) {
if (first.empty()) {
first = cur;
}
else if (cur != first)
{
} else if (cur != first) {
return false;
}
cur.clear();
}
if (*p == '\0')
{
if (*p == '\0') {
break;
}
}
else
{
} else {
cur.push_back(*p);
}
}
@@ -93,8 +81,7 @@ int main()
{
auto buf = make_ring(8);
auto& ring = *reinterpret_cast<LogRing*>(buf.data());
for (unsigned long long n = 0; n < 11; ++n)
{
for (unsigned long long n = 0; n < 11; ++n) {
const std::string line = make_line(0, n);
log_ring_push(ring, 1234, LogLevel_Info, n, line.c_str());
}
@@ -103,8 +90,7 @@ int main()
log_ring_drain(ring, cursor, [&](const LogRecord& rec) { got.push_back(rec.text); });
check(got.size() == 8, "wrap: drains exactly capacity lines after overflow");
bool ordered = true;
for (std::size_t k = 0; k < got.size(); ++k)
{
for (std::size_t k = 0; k < got.size(); ++k) {
ordered = ordered && got[k] == make_line(0, 3 + k); // oldest 3 (0,1,2) dropped
}
check(ordered, "wrap: keeps the newest `capacity` lines, in order, oldest dropped");
@@ -119,12 +105,10 @@ int main()
std::atomic<bool> stop{false};
std::atomic<long long> produced{0};
std::vector<std::thread> producers;
for (unsigned t = 0; t < 4; ++t)
{
for (unsigned t = 0; t < 4; ++t) {
producers.emplace_back([&, t] {
unsigned long long n = 0;
while (!stop.load(std::memory_order_relaxed))
{
while (!stop.load(std::memory_order_relaxed)) {
const std::string line = make_line(t, n++);
log_ring_push(ring, t, LogLevel_Info, n, line.c_str());
produced.fetch_add(1, std::memory_order_relaxed);
@@ -139,14 +123,12 @@ int main()
auto drain = [&] {
log_ring_drain(ring, cursor, [&](const LogRecord& rec) {
consumed.fetch_add(1, std::memory_order_relaxed);
if (!line_consistent(rec.text))
{
if (!line_consistent(rec.text)) {
torn.fetch_add(1, std::memory_order_relaxed);
}
});
};
while (!stop.load(std::memory_order_relaxed))
{
while (!stop.load(std::memory_order_relaxed)) {
drain();
std::this_thread::sleep_for(std::chrono::microseconds(50)); // fall behind so slots wrap
}
@@ -155,8 +137,7 @@ int main()
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
stop.store(true, std::memory_order_relaxed);
for (auto& p : producers)
{
for (auto& p : producers) {
p.join();
}
consumer.join();

View File

@@ -6,13 +6,11 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool cond, const char* what)
{
if (!cond)
{
if (!cond) {
std::printf("FAIL: %s\n", what);
++g_failures;
}
@@ -83,8 +81,7 @@ int main()
check(map_host_to_game_client(in, gx, gy) && gx == 100 && gy == 100, "decorated: client interior");
}
if (g_failures == 0)
{
if (g_failures == 0) {
std::printf("PASS: mkb_map_test\n");
return 0;
}

View File

@@ -5,13 +5,11 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool cond, const char* what)
{
if (!cond)
{
if (!cond) {
std::printf("FAIL: %s\n", what);
++g_failures;
}
@@ -27,13 +25,11 @@ int main()
check(!pop_mkb_event(ring, out), "pop on empty ring returns false");
// Push then pop returns the same event, FIFO.
for (std::uint32_t i = 0; i < 10; ++i)
{
for (std::uint32_t i = 0; i < 10; ++i) {
MkbEvent ev{Mkb_KeyDown, i, static_cast<int>(i) * 2, static_cast<int>(i) * 3};
check(push_mkb_event(ring, ev), "push succeeds with room");
}
for (std::uint32_t i = 0; i < 10; ++i)
{
for (std::uint32_t i = 0; i < 10; ++i) {
check(pop_mkb_event(ring, out), "pop succeeds with data");
check(out.code == i && out.x == static_cast<int>(i) * 2 && out.y == static_cast<int>(i) * 3,
"popped event matches pushed (FIFO)");
@@ -41,39 +37,32 @@ int main()
check(!pop_mkb_event(ring, out), "ring empty again after draining");
// Fill to capacity, then one more push is dropped.
for (std::uint32_t i = 0; i < kMkbQueueSize; ++i)
{
for (std::uint32_t i = 0; i < kMkbQueueSize; ++i) {
check(push_mkb_event(ring, MkbEvent{Mkb_Char, i, 0, 0}), "push fills to capacity");
}
check(!push_mkb_event(ring, MkbEvent{Mkb_Char, 999, 0, 0}), "push on full ring is dropped");
// Drain and verify order survived a full buffer.
for (std::uint32_t i = 0; i < kMkbQueueSize; ++i)
{
for (std::uint32_t i = 0; i < kMkbQueueSize; ++i) {
check(pop_mkb_event(ring, out) && out.code == i, "full-buffer drain is in order");
}
// Wrap-around: indices are free-running, so many cycles must keep working.
std::uint32_t produced = 0, consumed = 0;
for (int cycle = 0; cycle < 1000; ++cycle)
{
for (int k = 0; k < 50; ++k)
{
if (push_mkb_event(ring, MkbEvent{Mkb_MouseDown, produced, 0, 0}))
{
for (int cycle = 0; cycle < 1000; ++cycle) {
for (int k = 0; k < 50; ++k) {
if (push_mkb_event(ring, MkbEvent{Mkb_MouseDown, produced, 0, 0})) {
++produced;
}
}
while (pop_mkb_event(ring, out))
{
while (pop_mkb_event(ring, out)) {
check(out.code == consumed, "wrap-around preserves FIFO order");
++consumed;
}
}
check(produced == consumed, "all wrap-around events consumed");
if (g_failures == 0)
{
if (g_failures == 0) {
std::printf("PASS: mkb_ring_test\n");
return 0;
}

View File

@@ -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;
}

View File

@@ -28,13 +28,11 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
if (!ok)
{
if (!ok) {
std::printf(" FAIL: %s\n", what);
++g_failures;
}
@@ -42,8 +40,7 @@ void check(bool ok, const char* what)
template <typename T>
void release(T*& p)
{
if (p)
{
if (p) {
p->Release();
p = nullptr;
}
@@ -64,8 +61,7 @@ int main()
{
// --- Host side: SharedBlock so the hook's IpcClient connects. ---
SharedMemory shm;
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock)))
{
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock))) {
std::printf("FAIL: create shared memory\n");
return 1;
}
@@ -77,8 +73,7 @@ int main()
hook::IpcClient ipc;
check(ipc.connect(10, 5), "IPC client connect");
if (!hook::install_opengl_hooks(ipc))
{
if (!hook::install_opengl_hooks(ipc)) {
std::printf("SKIP: could not install the OpenGL swap hooks\n");
return 0;
}
@@ -92,8 +87,8 @@ int main()
RegisterClassExW(&wc);
// WS_POPUP so the client area is exactly kW x kH (an overlapped window can't
// shrink below its minimum caption size, which would skew the captured dims).
HWND hwnd = CreateWindowExW(0, wc.lpszClassName, L"", WS_POPUP, 0, 0, kW, kH, nullptr, nullptr,
wc.hInstance, nullptr);
HWND hwnd =
CreateWindowExW(0, wc.lpszClassName, L"", WS_POPUP, 0, 0, kW, kH, nullptr, nullptr, wc.hInstance, nullptr);
ShowWindow(hwnd, SW_SHOWNOACTIVATE); // a mapped window makes the backbuffer reliable
HDC hdc = GetDC(hwnd);
@@ -105,17 +100,15 @@ int main()
pfd.cColorBits = 32;
const int pf = ChoosePixelFormat(hdc, &pfd);
HGLRC glrc = nullptr;
if (pf == 0 || !SetPixelFormat(hdc, pf, &pfd) || (glrc = wglCreateContext(hdc)) == nullptr ||
!wglMakeCurrent(hdc, glrc))
{
if (pf == 0 || !SetPixelFormat(hdc, pf, &pfd) || (glrc = wglCreateContext(hdc)) == nullptr
|| !wglMakeCurrent(hdc, glrc)) {
std::printf("SKIP: could not create an OpenGL context on this machine\n");
hook::remove_opengl_hooks();
return 0;
}
// Clear the backbuffer to a known color, then SwapBuffers (fires the detour).
for (int frame = 0; frame < 3; ++frame)
{
for (int frame = 0; frame < 3; ++frame) {
glViewport(0, 0, kW, kH);
glClearColor(0.20f, 0.40f, 0.60f, 1.0f); // -> ~{51,102,153,255}
glClear(GL_COLOR_BUFFER_BIT);
@@ -134,23 +127,19 @@ int main()
check(block->video.width == kW && block->video.height == kH, "shared dimensions published");
// --- Consumer: open the shared texture by name, copy to staging, verify color.
if (hook::opengl_frames_shared() > 0)
{
if (hook::opengl_frames_shared() > 0) {
ID3D11Device* devB = nullptr;
ID3D11DeviceContext* ctxB = nullptr;
if (SUCCEEDED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0,
D3D11_SDK_VERSION, &devB, nullptr, &ctxB)))
{
if (SUCCEEDED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION,
&devB, nullptr, &ctxB))) {
ID3D11Device1* dev1 = nullptr;
devB->QueryInterface(IID_PPV_ARGS(&dev1));
const std::wstring name = video_share_name(GetCurrentProcessId());
ID3D11Texture2D* sharedB = nullptr;
IDXGIKeyedMutex* km = nullptr;
if (dev1 != nullptr &&
SUCCEEDED(dev1->OpenSharedResourceByName(name.c_str(),
DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
IID_PPV_ARGS(&sharedB))))
{
if (dev1 != nullptr
&& SUCCEEDED(dev1->OpenSharedResourceByName(
name.c_str(), DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, IID_PPV_ARGS(&sharedB)))) {
sharedB->QueryInterface(IID_PPV_ARGS(&km));
D3D11_TEXTURE2D_DESC sd{};
sharedB->GetDesc(&sd);
@@ -160,32 +149,24 @@ int main()
sd.MiscFlags = 0;
ID3D11Texture2D* staging = nullptr;
devB->CreateTexture2D(&sd, nullptr, &staging);
if (km != nullptr && staging != nullptr && km->AcquireSync(kVideoMutexKey, 1000) == S_OK)
{
if (km != nullptr && staging != nullptr && km->AcquireSync(kVideoMutexKey, 1000) == S_OK) {
ctxB->CopyResource(staging, sharedB);
km->ReleaseSync(kVideoMutexKey);
D3D11_MAPPED_SUBRESOURCE m{};
if (SUCCEEDED(ctxB->Map(staging, 0, D3D11_MAP_READ, 0, &m)))
{
if (SUCCEEDED(ctxB->Map(staging, 0, D3D11_MAP_READ, 0, &m))) {
const auto* px = static_cast<const std::uint8_t*>(m.pData);
std::printf("readback pixel0 = {%u,%u,%u,%u}\n", px[0], px[1], px[2], px[3]);
check(near_byte(px[0], 51) && near_byte(px[1], 102) && near_byte(px[2], 153),
"shared texture carries the rendered color");
ctxB->Unmap(staging, 0);
}
else
{
} else {
check(false, "map staging texture");
}
}
else
{
} else {
check(false, "acquire keyed mutex + copy shared texture");
}
release(staging);
}
else
{
} else {
check(false, "open shared texture by name");
}
release(km);

View File

@@ -28,15 +28,13 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
if (!ok)
{
if (!ok) {
std::printf(" FAIL: %s\n", what);
++g_failures;
}
@@ -45,8 +43,7 @@ void check(bool ok, const char* what)
template <typename T>
void release(T*& p)
{
if (p)
{
if (p) {
p->Release();
p = nullptr;
}
@@ -68,8 +65,7 @@ int main()
{
// --- Host side: SharedBlock (named by our pid) so the hook's IpcClient connects.
SharedMemory shm;
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock)))
{
if (!shm.create(shared_memory_name(GetCurrentProcessId()), sizeof(SharedBlock))) {
std::printf("FAIL: create shared memory\n");
return 1;
}
@@ -82,8 +78,7 @@ int main()
check(ipc.connect(10, 5), "IPC client connect");
// --- Install the Present hook (inline-hooks IDXGISwapChain::Present). ---
if (!hook::install_present_hooks(ipc))
{
if (!hook::install_present_hooks(ipc)) {
std::printf("SKIP: could not install the Present hook (no D3D11 device?)\n");
return 0;
}
@@ -114,22 +109,18 @@ int main()
ID3D11DeviceContext* ctx = nullptr;
HRESULT hr = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0,
D3D11_SDK_VERSION, &scd, &swapchain, &device, nullptr, &ctx);
if (FAILED(hr) || swapchain == nullptr)
{
if (FAILED(hr) || swapchain == nullptr) {
std::printf("SKIP: could not create a D3D11 swapchain (hr=0x%08lX)\n", static_cast<unsigned long>(hr));
hook::remove_present_hooks();
return 0;
}
// Clear the backbuffer to the known color, then Present (fires the detour).
for (int frame = 0; frame < 3; ++frame)
{
for (int frame = 0; frame < 3; ++frame) {
ID3D11Texture2D* back = nullptr;
if (SUCCEEDED(swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&back))))
{
if (SUCCEEDED(swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&back)))) {
ID3D11RenderTargetView* rtv = nullptr;
if (SUCCEEDED(device->CreateRenderTargetView(back, nullptr, &rtv)))
{
if (SUCCEEDED(device->CreateRenderTargetView(back, nullptr, &rtv))) {
ctx->ClearRenderTargetView(rtv, kClear);
ctx->Flush();
rtv->Release();
@@ -141,8 +132,8 @@ int main()
std::printf("present_calls=%llu frames_shared=%llu video{gen=%u %ux%u fmt=%u}\n",
static_cast<unsigned long long>(hook::present_calls()),
static_cast<unsigned long long>(hook::present_frames_shared()),
block->video.generation.load(), block->video.width, block->video.height, block->video.format);
static_cast<unsigned long long>(hook::present_frames_shared()), block->video.generation.load(),
block->video.width, block->video.height, block->video.format);
check(hook::present_calls() >= 3, "Present detour fired");
check(hook::present_frames_shared() > 0, "backbuffer copied into the shared texture");
@@ -156,19 +147,16 @@ int main()
{
ID3D11Device* devB = nullptr;
ID3D11DeviceContext* ctxB = nullptr;
if (SUCCEEDED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0,
D3D11_SDK_VERSION, &devB, nullptr, &ctxB)))
{
if (SUCCEEDED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION,
&devB, nullptr, &ctxB))) {
ID3D11Device1* dev1 = nullptr;
devB->QueryInterface(IID_PPV_ARGS(&dev1));
const std::wstring name = video_share_name(GetCurrentProcessId());
ID3D11Texture2D* sharedB = nullptr;
IDXGIKeyedMutex* km = nullptr;
if (dev1 != nullptr &&
SUCCEEDED(dev1->OpenSharedResourceByName(name.c_str(),
DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
IID_PPV_ARGS(&sharedB))))
{
if (dev1 != nullptr
&& SUCCEEDED(dev1->OpenSharedResourceByName(
name.c_str(), DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, IID_PPV_ARGS(&sharedB)))) {
sharedB->QueryInterface(IID_PPV_ARGS(&km));
D3D11_TEXTURE2D_DESC sd{};
@@ -180,33 +168,25 @@ int main()
ID3D11Texture2D* staging = nullptr;
check(SUCCEEDED(devB->CreateTexture2D(&sd, nullptr, &staging)), "create staging texture");
if (km != nullptr && staging != nullptr && km->AcquireSync(kVideoMutexKey, 1000) == S_OK)
{
if (km != nullptr && staging != nullptr && km->AcquireSync(kVideoMutexKey, 1000) == S_OK) {
ctxB->CopyResource(staging, sharedB);
km->ReleaseSync(kVideoMutexKey);
D3D11_MAPPED_SUBRESOURCE mapped{};
if (SUCCEEDED(ctxB->Map(staging, 0, D3D11_MAP_READ, 0, &mapped)))
{
if (SUCCEEDED(ctxB->Map(staging, 0, D3D11_MAP_READ, 0, &mapped))) {
const auto* px = static_cast<const std::uint8_t*>(mapped.pData);
std::printf("readback pixel0 = {%u,%u,%u,%u}\n", px[0], px[1], px[2], px[3]);
check(near_byte(px[0], 51) && near_byte(px[1], 102) && near_byte(px[2], 153),
"shared texture carries the rendered color");
ctxB->Unmap(staging, 0);
}
else
{
} else {
check(false, "map staging texture");
}
}
else
{
} else {
check(false, "acquire keyed mutex + copy shared texture");
}
release(staging);
}
else
{
} else {
check(false, "open shared texture by name");
}
release(km);
@@ -222,11 +202,9 @@ int main()
{
auto render = [&] {
ID3D11Texture2D* back = nullptr;
if (SUCCEEDED(swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&back))))
{
if (SUCCEEDED(swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&back)))) {
ID3D11RenderTargetView* rtv = nullptr;
if (SUCCEEDED(device->CreateRenderTargetView(back, nullptr, &rtv)))
{
if (SUCCEEDED(device->CreateRenderTargetView(back, nullptr, &rtv))) {
ctx->ClearRenderTargetView(rtv, kClear);
ctx->Flush();
rtv->Release();

View File

@@ -10,8 +10,7 @@
#include <windows.h>
namespace cooptest
{
namespace cooptest {
inline double now_ms()
{
LARGE_INTEGER f, c;
@@ -28,8 +27,7 @@ double avg_present_ms(int n, RenderFn render, PresentFn present)
render();
present(); // warm (first present/resource setup)
double total = 0;
for (int i = 0; i < n; ++i)
{
for (int i = 0; i < n; ++i) {
render();
const double a = now_ms();
present();

View File

@@ -16,14 +16,12 @@
using namespace coop;
using coop::hook::IpcClient;
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;
}
}
@@ -43,11 +41,9 @@ int main()
// generations, so the slots' packet values would disagree.
std::thread writer([&] {
std::uint32_t gen = 1;
while (!stop.load(std::memory_order_relaxed))
{
while (!stop.load(std::memory_order_relaxed)) {
CoopPadState pads[kMaxPads];
for (auto& p : pads)
{
for (auto& p : pads) {
p = CoopPadState{};
p.connected = 1;
p.packet = gen;
@@ -57,17 +53,13 @@ int main()
}
});
std::thread reader([&] {
while (!stop.load(std::memory_order_relaxed))
{
while (!stop.load(std::memory_order_relaxed)) {
CoopPadState out[kMaxPads];
std::uint32_t count = 0;
if (read_pads(block, out, count))
{
if (read_pads(block, out, count)) {
reads.fetch_add(1, std::memory_order_relaxed);
for (std::uint32_t i = 1; i < kMaxPads; ++i)
{
if (out[i].packet != out[0].packet)
{
for (std::uint32_t i = 1; i < kMaxPads; ++i) {
if (out[i].packet != out[0].packet) {
torn.fetch_add(1, std::memory_order_relaxed);
break;
}
@@ -90,7 +82,8 @@ int main()
block.sequence.store(1, std::memory_order_relaxed); // odd = write in progress, never completed
CoopPadState out[kMaxPads];
std::uint32_t count = 0;
check(!read_pads(block, out, count), "seqlock: read_pads returns false when stuck mid-write (bounded, no hang)");
check(!read_pads(block, out, count),
"seqlock: read_pads returns false when stuck mid-write (bounded, no hang)");
}
// --- Handshake: IpcClient::connect rejects a wrong magic / version ----------------------------

View File

@@ -13,18 +13,14 @@
using namespace coop::hook;
namespace
{
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
if (!ok)
{
if (!ok) {
std::printf("FAIL: %s\n", what);
++g_failures;
}
else
{
} else {
std::printf(" ok: %s\n", what);
}
}
@@ -32,8 +28,7 @@ void check(bool ok, const char* what)
// Drives an estimator with a controllable QPC clock. One feed per "window" (we use a
// 0.5 s step that matches the estimator's window, so each feed after the first completes
// exactly one window -- making each window's frame count individually controllable).
struct Sim
{
struct Sim {
RateEstimator est;
static constexpr std::int64_t kFreq = 1'000'000; // 1 MHz (microseconds)
std::int64_t qpc = 0;
@@ -53,11 +48,9 @@ RateEstimate run_steady(double rate, int max = 40)
{
Sim s;
RateEstimate r;
for (int i = 0; i < max; ++i)
{
for (int i = 0; i < max; ++i) {
r = s.window(rate);
if (r.done)
{
if (r.done) {
return r;
}
}
@@ -78,13 +71,10 @@ int main()
check(snap_standard_rate(45000.0) == 0, "45000 (non-standard) snaps to nothing");
// --- steady standard rates converge, confidently -----------------------------
for (double rate : {44100.0, 48000.0, 96000.0, 22050.0})
{
for (double rate : {44100.0, 48000.0, 96000.0, 22050.0}) {
const RateEstimate r = run_steady(rate);
check(r.done && r.confident && r.rate == static_cast<std::uint32_t>(rate),
"steady rate converges confidently");
if (!(r.done && r.rate == static_cast<std::uint32_t>(rate)))
{
check(r.done && r.confident && r.rate == static_cast<std::uint32_t>(rate), "steady rate converges confidently");
if (!(r.done && r.rate == static_cast<std::uint32_t>(rate))) {
std::printf(" (rate=%.0f -> done=%d confident=%d got=%u)\n", rate, r.done, r.confident, r.rate);
}
}
@@ -94,8 +84,7 @@ int main()
Sim s;
RateEstimate r;
const double wobble[] = {+150.0, -120.0, +90.0, -150.0, +60.0, -90.0, +130.0, -40.0};
for (int i = 0; i < 30 && !r.done; ++i)
{
for (int i = 0; i < 30 && !r.done; ++i) {
r = s.window(44100.0, wobble[i % 8]); // ~0.3% jitter, within the snap band
}
check(r.done && r.confident && r.rate == 44100, "44100 with small jitter -> 44100 confident");
@@ -112,8 +101,7 @@ int main()
check(!burst.done, "single burst window does not commit");
// windows 3..N: clean 44100 -> consensus on 44100.
RateEstimate r = burst;
for (int i = 0; i < 10 && !r.done; ++i)
{
for (int i = 0; i < 10 && !r.done; ++i) {
r = s.window(44100.0);
}
check(r.done && r.confident && r.rate == 44100, "burst rejected; converges to 44100, not 48000");
@@ -124,8 +112,7 @@ int main()
const RateEstimate r = run_steady(45000.0, 40);
check(r.done && !r.confident, "non-standard 45000 -> done but LOW-confidence");
check(r.rate >= 44600 && r.rate <= 45400, "low-confidence estimate is ~45000");
if (r.done)
{
if (r.done) {
std::printf(" (45000 -> confident=%d rate=%u)\n", r.confident, r.rate);
}
}
@@ -134,20 +121,17 @@ int main()
{
Sim s;
RateEstimate r;
for (int i = 0; i < 6; ++i)
{
for (int i = 0; i < 6; ++i) {
r = s.window(0.0); // silent: no frames advance
check(!r.done, "idle window never commits");
}
for (int i = 0; i < 12 && !r.done; ++i)
{
for (int i = 0; i < 12 && !r.done; ++i) {
r = s.window(48000.0); // audio resumes
}
check(r.done && r.confident && r.rate == 48000, "after idle, real audio converges to 48000");
}
if (g_failures == 0)
{
if (g_failures == 0) {
std::printf("PASS rate_estimator_test\n");
return 0;
}

View File

@@ -20,17 +20,13 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
if (ok)
{
if (ok) {
std::printf(" ok: %s\n", what);
}
else
{
} else {
std::printf("FAIL: %s\n", what);
++g_failures;
}
@@ -38,34 +34,29 @@ void check(bool ok, const char* what)
// The original policy: re-prime on any partial fill (`to_write < avail`). Kept here only to
// contrast against the shipping RenderPacer.
struct LegacyPacer
{
struct LegacyPacer {
std::uint32_t prime_frames = 0;
bool primed = false;
std::uint32_t pump(std::uint32_t avail, std::uint32_t have, std::uint32_t /*padding*/)
{
if (!primed && have >= prime_frames)
{
if (!primed && have >= prime_frames) {
primed = true;
}
if (!primed)
{
if (!primed) {
return 0;
}
const std::uint32_t to_write = std::min(avail, have);
if (to_write < avail)
{
if (to_write < avail) {
primed = false; // the bug: a partial fill forces a full re-prime
}
return to_write;
}
};
struct SimResult
{
int underruns = 0; // device couldn't supply a full period (audible silence)
struct SimResult {
int underruns = 0; // device couldn't supply a full period (audible silence)
std::uint32_t min_headroom = 0; // smallest device-buffer level seen after warm-up (cushion left)
int withheld = 0; // ticks the policy refused to feed though the ring had >=1 period
int withheld = 0; // ticks the policy refused to feed though the ring had >=1 period
};
// Run one policy over a producer schedule, modelling an event-driven WASAPI render client.
@@ -83,19 +74,14 @@ SimResult simulate(Pacer pacer, const std::vector<std::uint32_t>& producer, std:
SimResult res;
res.min_headroom = render_frames;
bool warming = true; // ignore the initial fill-up before playback starts
for (std::size_t t = 0; t < producer.size(); ++t)
{
for (std::size_t t = 0; t < producer.size(); ++t) {
ring += producer[t]; // the game pushed this tick's frames into the ring
// The device plays a period; its event then fires asking for more. If the buffer
// can't supply a full period, the renderer plays silence -- an audible under-run.
if (!warming)
{
if (device >= period)
{
if (!warming) {
if (device >= period) {
device -= period;
}
else
{
} else {
++res.underruns;
device = 0;
}
@@ -109,15 +95,12 @@ SimResult simulate(Pacer pacer, const std::vector<std::uint32_t>& producer, std:
w = static_cast<std::uint32_t>(std::min<std::uint64_t>(w, ring));
device += w;
ring -= w;
if (w > 0)
{
if (w > 0) {
warming = false; // playback has begun
}
if (!warming)
{
if (!warming) {
res.min_headroom = std::min(res.min_headroom, device);
if (w == 0 && have >= period && avail >= period)
{
if (w == 0 && have >= period && avail >= period) {
++res.withheld; // had at least a period to give and room to put it -- but didn't
}
}
@@ -137,8 +120,7 @@ std::vector<std::uint32_t> jittery_schedule(std::uint32_t period, int ticks)
p.reserve(ticks);
std::uint64_t owed = 0;
const std::uint32_t catch_up = period * 7 / 5; // 1.4x: clears the 2/7 stall backlog, but no faster
for (int t = 0; t < ticks; ++t)
{
for (int t = 0; t < ticks; ++t) {
owed += period;
const std::uint32_t cap = (t % 7 < 2) ? 0u : catch_up; // stall 2/7 ticks, else catch up gently
const std::uint32_t deliver = static_cast<std::uint32_t>(std::min<std::uint64_t>(owed, cap));
@@ -178,10 +160,10 @@ int main()
lp.prime_frames = kPrime;
const SimResult n = simulate(np, sched, kRender, kPeriod);
const SimResult l = simulate(lp, sched, kRender, kPeriod);
std::printf(" (jittery: NEW under-runs=%d min_headroom=%u withheld=%d)\n", n.underruns,
n.min_headroom, n.withheld);
std::printf(" (jittery: OLD under-runs=%d min_headroom=%u withheld=%d)\n", l.underruns,
l.min_headroom, l.withheld);
std::printf(" (jittery: NEW under-runs=%d min_headroom=%u withheld=%d)\n", n.underruns, n.min_headroom,
n.withheld);
std::printf(" (jittery: OLD under-runs=%d min_headroom=%u withheld=%d)\n", l.underruns, l.min_headroom,
l.withheld);
// The defining pathology, measured directly (not timing-marginal): the old policy refuses
// to feed data it has, repeatedly; the new policy never withholds once playing.
check(n.withheld == 0, "jittery: new policy never withholds available data");
@@ -212,8 +194,7 @@ int main()
check(!p.primed, "true starvation (padding==0 && have==0) re-primes");
}
if (g_failures == 0)
{
if (g_failures == 0) {
std::printf("PASS render_pacer_test\n");
return 0;
}

View File

@@ -10,14 +10,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;
}
}

View File

@@ -9,14 +9,12 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void expect(DXGI_FORMAT in, DXGI_FORMAT want, const char* what)
{
const DXGI_FORMAT got = srgb_to_unorm(in);
if (got != want)
{
if (got != want) {
std::printf(" FAIL: %s (got %d, want %d)\n", what, static_cast<int>(got), static_cast<int>(want));
++g_failures;
}

View File

@@ -9,14 +9,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;
}
}
@@ -57,8 +55,7 @@ int main()
// Round-trip preserves the string (ASCII and a non-ASCII code point that must survive
// persist/reload of an override key -- the reason narrow/widen exist instead of a byte mask).
for (const wchar_t* s : {L"CoopAllTheThings", L"path\\to\\Game (2).exe", L"café.exe", L"游戏.exe"})
{
for (const wchar_t* s : {L"CoopAllTheThings", L"path\\to\\Game (2).exe", L"café.exe", L"游戏.exe"}) {
check(widen(narrow(std::wstring(s))) == std::wstring(s), "narrow->widen round-trips");
}

View File

@@ -15,17 +15,13 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
void check(bool ok, const char* what)
{
if (ok)
{
if (ok) {
std::printf(" ok: %s\n", what);
}
else
{
} else {
std::printf("FAIL: %s\n", what);
++g_failures;
}
@@ -39,8 +35,7 @@ std::vector<float> make_sine(double freq, unsigned rate, double seconds, double
const std::size_t n = static_cast<std::size_t>(rate * seconds);
std::vector<float> v(n);
const double step = kTwoPi * freq / rate;
for (std::size_t i = 0; i < n; ++i)
{
for (std::size_t i = 0; i < n; ++i) {
v[i] = static_cast<float>(std::sin(step * i) * amp);
}
return v;
@@ -61,8 +56,8 @@ int main()
check(r.glitch_count == 0, "clean: no clicks");
check(r.dropout_count == 0, "clean: no dropouts");
check(std::fabs(r.peak - 0.25) < 0.01, "clean: peak ~0.25");
std::printf(" (clean: %.3f Hz, %.2f cents, SNR %.1f dB, THD %.3f%%)\n", r.dominant_hz,
r.pitch_error_cents, r.snr_db, r.thd_percent);
std::printf(" (clean: %.3f Hz, %.2f cents, SNR %.1f dB, THD %.3f%%)\n", r.dominant_hz, r.pitch_error_cents,
r.snr_db, r.thd_percent);
}
// --- Pitch-shift bug: real 44100 samples played as if 48000 -----------------------
@@ -70,28 +65,26 @@ int main()
// them at 48 kHz, shifting a 1000 Hz tone up to 1000*48000/44100 ~= 1088.4 Hz. Expected
// cents = 1200*log2(48000/44100) ~= +146.7. The analyzer must recover that.
{
auto sine = make_sine(1000.0, 44100, 2.0); // generated at the *true* rate
auto sine = make_sine(1000.0, 44100, 2.0); // generated at the *true* rate
const ToneReport r = analyze_tone(sine.data(), sine.size(), 48000, 1000.0); // analyzed at the wrong rate
const double expect_cents = 1200.0 * std::log2(48000.0 / 44100.0);
check(std::fabs(r.pitch_error_cents - expect_cents) < 5.0, "pitch-shift: ~+147 cents detected");
check(r.pitch_error_ratio > 1.05, "pitch-shift: ratio > 1.05 (audibly sharp)");
check(std::fabs(r.dominant_hz - 1088.4) < 3.0, "pitch-shift: dominant ~1088 Hz");
std::printf(" (pitch-shift: %.2f cents vs expected %.2f, dominant %.2f Hz)\n",
r.pitch_error_cents, expect_cents, r.dominant_hz);
std::printf(" (pitch-shift: %.2f cents vs expected %.2f, dominant %.2f Hz)\n", r.pitch_error_cents,
expect_cents, r.dominant_hz);
}
// --- Click injection: discontinuities the analyzer must count ---------------------
{
auto sine = make_sine(1000.0, 48000, 2.0);
const unsigned injected = 9;
for (unsigned k = 0; k < injected; ++k)
{
for (unsigned k = 0; k < injected; ++k) {
const std::size_t at = sine.size() * (k + 1) / (injected + 2);
sine[at] += 0.7f; // a sharp isolated jump (a click)
}
const ToneReport r = analyze_tone(sine.data(), sine.size(), 48000, 1000.0);
check(r.glitch_count >= injected - 1 && r.glitch_count <= injected + 1,
"clicks: counted ~9 discontinuities");
check(r.glitch_count >= injected - 1 && r.glitch_count <= injected + 1, "clicks: counted ~9 discontinuities");
check(r.dropout_count == 0, "clicks: no false dropouts");
std::printf(" (clicks: injected %u, detected %u, rate %.2f/s)\n", injected, r.glitch_count,
r.glitch_rate_per_sec);
@@ -101,11 +94,9 @@ int main()
{
auto sine = make_sine(1000.0, 48000, 2.0);
// Two ~20 ms gaps of silence.
for (int g = 0; g < 2; ++g)
{
for (int g = 0; g < 2; ++g) {
const std::size_t at = sine.size() * (g + 1) / 3;
for (std::size_t i = 0; i < 48000u * 20 / 1000; ++i)
{
for (std::size_t i = 0; i < 48000u * 20 / 1000; ++i) {
sine[at + i] = 0.0f;
}
}
@@ -129,13 +120,12 @@ int main()
const unsigned rate = 48000, ch = 2;
auto mono = make_sine(1000.0, rate, 0.5, 0.5);
std::vector<std::int16_t> inter(mono.size() * ch, 0);
for (std::size_t i = 0; i < mono.size(); ++i)
{
for (std::size_t i = 0; i < mono.size(); ++i) {
inter[i * ch + 0] = static_cast<std::int16_t>(mono[i] * 32767.0f);
}
const std::wstring path = L"tone_analysis_test_roundtrip.wav";
const bool wrote = wav_write(path, inter.data(), inter.size() * sizeof(std::int16_t), rate, ch, 16,
kToneFormatPcm);
const bool wrote =
wav_write(path, inter.data(), inter.size() * sizeof(std::int16_t), rate, ch, 16, kToneFormatPcm);
check(wrote, "wav: write ok");
WavData wd;
const bool readback = wav_read(path, wd);
@@ -149,8 +139,7 @@ int main()
std::remove("tone_analysis_test_roundtrip.wav");
}
if (g_failures == 0)
{
if (g_failures == 0) {
std::printf("PASS tone_analysis_test\n");
return 0;
}

View File

@@ -10,14 +10,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;
}
}
@@ -25,8 +23,7 @@ void check(bool ok, const char* what)
bool touch(const std::wstring& path)
{
HANDLE h = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE)
{
if (h == INVALID_HANDLE_VALUE) {
return false;
}
CloseHandle(h);
@@ -35,8 +32,7 @@ bool touch(const std::wstring& path)
std::wstring parent_of(std::wstring dir) // dir has a trailing separator
{
if (!dir.empty())
{
if (!dir.empty()) {
dir.pop_back();
}
const std::size_t slash = dir.find_last_of(L"\\/");
@@ -62,8 +58,7 @@ int main()
check(deployed_artifact_path(near_name.c_str()) == here + near_name, "resolves an artifact next to the exe");
// one directory up (the deployable root) when it isn't next to the exe.
if (have_up)
{
if (have_up) {
check(touch(up_dir + up_name), "created a marker one directory up");
check(deployed_artifact_path(up_name.c_str()) == up_dir + up_name,
"falls back to the artifact one directory up");
@@ -74,8 +69,7 @@ int main()
"missing artifact -> next-to-exe fallback path");
DeleteFileW((here + near_name).c_str());
if (have_up)
{
if (have_up) {
DeleteFileW((up_dir + up_name).c_str());
}

View File

@@ -27,8 +27,7 @@
using namespace coop;
namespace
{
namespace {
// Populate the hook back-channel with the worst case for the panels: every pad busy, and
// the maximum number of render streams, each low-confidence (the longest provenance label)
@@ -38,8 +37,7 @@ void fill_max_status(HookStatusView& st)
st.attached = true;
st.focus_spoof = true;
st.heartbeat = 123456;
for (std::uint32_t i = 0; i < kMaxPads; ++i)
{
for (std::uint32_t i = 0; i < kMaxPads; ++i) {
st.get_state[i] = 9876543;
st.get_caps[i] = 4242;
st.rumble_left[i] = 65535;
@@ -52,8 +50,7 @@ void fill_max_status(HookStatusView& st)
st.game_pid = 4242;
st.game_hwnd = 0x12345678;
st.audio_streams_seen = kMaxAudioStreams + 5; // exercises the "(showing first N)" note
for (std::uint32_t i = 0; i < kMaxAudioStreams; ++i)
{
for (std::uint32_t i = 0; i < kMaxAudioStreams; ++i) {
AudioStreamInfo& s = st.audio_streams[i];
s.is_primary = (i == 0) ? 1u : 0u;
s.sample_rate = 192000;
@@ -67,8 +64,7 @@ void fill_max_status(HookStatusView& st)
void fill_max_input(InputSnapshot& in)
{
for (std::uint32_t i = 0; i < kMaxPads; ++i)
{
for (std::uint32_t i = 0; i < kMaxPads; ++i) {
in.pads[i].connected = true;
in.pads[i].source = "XInput (RPT guest)";
in.pads[i].state.connected = 1;
@@ -90,8 +86,7 @@ bool run_size(float w, float h, AudioPanel& audio, ControllersPanel& controllers
ImGui::GetIO().DisplaySize = ImVec2(w, h);
set_layout_reference(w, h);
set_layout_debug(true); // we drive the panels with Debug details on
for (int frame = 0; frame < 4; ++frame)
{
for (int frame = 0; frame < 4; ++frame) {
ImGui::GetIO().DeltaTime = 1.0f / 60.0f;
ImGui::NewFrame();
reset_panel_fit();
@@ -103,8 +98,8 @@ bool run_size(float w, float h, AudioPanel& audio, ControllersPanel& controllers
const bool over = panel_fit_overflow(&ox, &oy);
char report[256];
panel_fit_report(report, sizeof(report));
std::printf(" %4.0fx%-4.0f %-9s %-28s (worst %.0f x, %.0f y)%s\n", w, h, over ? "OVERFLOW" : "fit",
report, ox, oy, (over && !required) ? " [best-effort]" : "");
std::printf(" %4.0fx%-4.0f %-9s %-28s (worst %.0f x, %.0f y)%s\n", w, h, over ? "OVERFLOW" : "fit", report, ox,
oy, (over && !required) ? " [best-effort]" : "");
return required ? !over : true;
}
@@ -139,8 +134,7 @@ int main()
run_size(1366, 768, audio, controllers, st, in, /*required=*/false);
ImGui::DestroyContext();
if (!ok)
{
if (!ok) {
std::printf("FAIL: a panel overflows its assigned size at 1920x1080 with Debug details on.\n");
return 1;
}

View File

@@ -30,8 +30,7 @@
#include "vk_capture.hpp"
namespace
{
namespace {
double now_ms()
{
LARGE_INTEGER f, c;
@@ -44,15 +43,13 @@ 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;
}
}
// Everything the test resolves from the device (superset of VkCapture::Fns + image-build helpers).
struct DevFns
{
struct DevFns {
PFN_vkGetDeviceProcAddr GetDeviceProcAddr;
PFN_vkGetDeviceQueue GetDeviceQueue;
PFN_vkCreateCommandPool CreateCommandPool;
@@ -94,10 +91,8 @@ VkPhysicalDeviceMemoryProperties g_memprops{};
bool find_mem(std::uint32_t type_bits, VkMemoryPropertyFlags want, std::uint32_t& out)
{
for (std::uint32_t i = 0; i < g_memprops.memoryTypeCount; ++i)
{
if ((type_bits & (1u << i)) && (g_memprops.memoryTypes[i].propertyFlags & want) == want)
{
for (std::uint32_t i = 0; i < g_memprops.memoryTypeCount; ++i) {
if ((type_bits & (1u << i)) && (g_memprops.memoryTypes[i].propertyFlags & want) == want) {
out = i;
return true;
}
@@ -146,14 +141,12 @@ int main(int argc, char** argv)
const std::uint32_t W = 1920, H = 1080; // the resolution where the stall was measured
HMODULE vk = LoadLibraryW(L"vulkan-1.dll");
if (vk == nullptr)
{
if (vk == nullptr) {
std::printf("SKIP vk_capture_perf_test (no vulkan-1.dll)\n");
return 0;
}
auto gipa = reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(vk, "vkGetInstanceProcAddr"));
if (gipa == nullptr)
{
if (gipa == nullptr) {
std::printf("SKIP vk_capture_perf_test (no vkGetInstanceProcAddr)\n");
return 0;
}
@@ -166,8 +159,7 @@ int main(int argc, char** argv)
app.apiVersion = VK_API_VERSION_1_1;
VkInstanceCreateInfo ci{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
ci.pApplicationInfo = &app;
if (create == nullptr || create(&ci, nullptr, &instance) != VK_SUCCESS)
{
if (create == nullptr || create(&ci, nullptr, &instance) != VK_SUCCESS) {
std::printf("SKIP vk_capture_perf_test (vkCreateInstance failed)\n");
return 0;
}
@@ -182,8 +174,7 @@ int main(int argc, char** argv)
std::uint32_t n = 0;
EnumeratePhysicalDevices(instance, &n, nullptr);
if (n == 0)
{
if (n == 0) {
std::printf("SKIP vk_capture_perf_test (no physical devices)\n");
DestroyInstance(instance, nullptr);
return 0;
@@ -197,16 +188,13 @@ int main(int argc, char** argv)
std::vector<VkQueueFamilyProperties> qf(qn);
GetPhysicalDeviceQueueFamilyProperties(gpu, &qn, qf.data());
std::uint32_t qfam = UINT32_MAX;
for (std::uint32_t i = 0; i < qn; ++i)
{
if (qf[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
{
for (std::uint32_t i = 0; i < qn; ++i) {
if (qf[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
qfam = i;
break;
}
}
if (qfam == UINT32_MAX)
{
if (qfam == UINT32_MAX) {
std::printf("SKIP vk_capture_perf_test (no graphics queue)\n");
DestroyInstance(instance, nullptr);
return 0;
@@ -221,8 +209,7 @@ int main(int argc, char** argv)
dci.queueCreateInfoCount = 1;
dci.pQueueCreateInfos = &qci;
VkDevice device = VK_NULL_HANDLE;
if (CreateDevice(gpu, &dci, nullptr, &device) != VK_SUCCESS)
{
if (CreateDevice(gpu, &dci, nullptr, &device) != VK_SUCCESS) {
std::printf("SKIP vk_capture_perf_test (vkCreateDevice failed)\n");
DestroyInstance(instance, nullptr);
return 0;
@@ -264,8 +251,8 @@ int main(int argc, char** argv)
d.UnmapMemory = DFN(UnmapMemory);
d.InvalidateMappedMemoryRanges = DFN(InvalidateMappedMemoryRanges);
d.DeviceWaitIdle = DFN(DeviceWaitIdle);
d.GetPhysicalDeviceMemoryProperties =
reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(gipa(instance, "vkGetPhysicalDeviceMemoryProperties"));
d.GetPhysicalDeviceMemoryProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(
gipa(instance, "vkGetPhysicalDeviceMemoryProperties"));
d.GetPhysicalDeviceMemoryProperties(gpu, &g_memprops);
@@ -300,13 +287,11 @@ int main(int argc, char** argv)
// --- Build the known source image (BGRA gradient) in PRESENT_SRC layout ------------------------
const VkDeviceSize bytes = static_cast<VkDeviceSize>(W) * H * 4;
std::vector<unsigned char> gradient(bytes);
for (std::uint32_t y = 0; y < H; ++y)
{
for (std::uint32_t x = 0; x < W; ++x)
{
for (std::uint32_t y = 0; y < H; ++y) {
for (std::uint32_t x = 0; x < W; ++x) {
unsigned char* p = &gradient[(static_cast<size_t>(y) * W + x) * 4];
p[0] = static_cast<unsigned char>(x & 0xFF); // B
p[1] = static_cast<unsigned char>(y & 0xFF); // G
p[0] = static_cast<unsigned char>(x & 0xFF); // B
p[1] = static_cast<unsigned char>(y & 0xFF); // G
p[2] = static_cast<unsigned char>((x + y) & 0xFF); // R
p[3] = 255;
}
@@ -347,8 +332,7 @@ int main(int argc, char** argv)
ici.tiling = VK_IMAGE_TILING_OPTIMAL;
ici.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
if (d.CreateImage(device, &ici, nullptr, &img) != VK_SUCCESS)
{
if (d.CreateImage(device, &ici, nullptr, &img) != VK_SUCCESS) {
std::printf("SKIP vk_capture_perf_test (CreateImage failed)\n");
return 0;
}
@@ -362,8 +346,7 @@ int main(int argc, char** argv)
d.AllocateMemory(device, &mai, nullptr, &imgmem);
d.BindImageMemory(device, img, imgmem, 0);
}
auto barrier = [&](VkCommandBuffer c, VkImageLayout from, VkImageLayout to, VkAccessFlags src,
VkAccessFlags dst) {
auto barrier = [&](VkCommandBuffer c, VkImageLayout from, VkImageLayout to, VkAccessFlags src, VkAccessFlags dst) {
VkImageMemoryBarrier b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
b.srcAccessMask = src;
b.dstAccessMask = dst;
@@ -373,8 +356,8 @@ int main(int argc, char** argv)
b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
b.image = img;
b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
d.CmdPipelineBarrier(c, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0,
nullptr, 0, nullptr, 1, &b);
d.CmdPipelineBarrier(c, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr,
0, nullptr, 1, &b);
};
{
VkCommandBufferBeginInfo bi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
@@ -385,8 +368,8 @@ int main(int argc, char** argv)
r.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
r.imageExtent = {W, H, 1};
d.CmdCopyBufferToImage(cb, upbuf, img, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &r);
barrier(cb, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT);
barrier(cb, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_MEMORY_READ_BIT);
d.EndCommandBuffer(cb);
submit_wait(cb);
}
@@ -417,24 +400,22 @@ int main(int argc, char** argv)
bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
d.ResetCommandBuffer(cb, 0);
d.BeginCommandBuffer(cb, &bi);
barrier(cb, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_MEMORY_READ_BIT, VK_ACCESS_TRANSFER_READ_BIT);
barrier(cb, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_MEMORY_READ_BIT,
VK_ACCESS_TRANSFER_READ_BIT);
VkBufferImageCopy r{};
r.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
r.imageExtent = {W, H, 1};
d.CmdCopyImageToBuffer(cb, img, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, ref_buf, 1, &r);
barrier(cb, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_MEMORY_READ_BIT);
barrier(cb, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_ACCESS_TRANSFER_READ_BIT,
VK_ACCESS_MEMORY_READ_BIT);
d.EndCommandBuffer(cb);
submit_wait(cb);
const auto* src = static_cast<const unsigned char*>(ref_mapped);
const size_t row = static_cast<size_t>(W) * 4;
for (std::uint32_t y = 0; y < H; ++y)
{
for (std::uint32_t y = 0; y < H; ++y) {
const unsigned char* in = src + static_cast<size_t>(y) * row;
unsigned char* o = ref_rgba.data() + static_cast<size_t>(y) * row;
for (std::uint32_t x = 0; x < W; ++x)
{
for (std::uint32_t x = 0; x < W; ++x) {
o[x * 4 + 0] = in[x * 4 + 2];
o[x * 4 + 1] = in[x * 4 + 1];
o[x * 4 + 2] = in[x * 4 + 0];
@@ -448,8 +429,7 @@ int main(int argc, char** argv)
sync_capture(); // warm
double sync_ms = 0;
const int iters = 8;
for (int i = 0; i < iters; ++i)
{
for (int i = 0; i < iters; ++i) {
const double t0 = now_ms();
sync_capture();
sync_ms += now_ms() - t0;
@@ -460,8 +440,7 @@ int main(int argc, char** argv)
double sut_ms = sync_ms; // in --sync repro mode the measured path IS the synchronous one
bool image_ok = true;
if (!sync_repro)
{
if (!sync_repro) {
// --- The fix under test: VkCapture (async reaper) -----------------------------------------
coop::hook::VkCapture cap;
cap.init(gpu, device, qfam, capture_fns(d), GetCurrentProcessId(), nullptr);
@@ -477,11 +456,9 @@ int main(int argc, char** argv)
};
// Warm up (first calls allocate staging / create the D3D texture on the reaper).
for (int i = 0; i < 16; ++i)
{
for (int i = 0; i < 16; ++i) {
VkSemaphore sem = VK_NULL_HANDLE;
if (cap.present(img, VK_FORMAT_B8G8R8A8_UNORM, W, H, nullptr, 0, sem))
{
if (cap.present(img, VK_FORMAT_B8G8R8A8_UNORM, W, H, nullptr, 0, sem)) {
consume(sem);
}
Sleep(2);
@@ -490,14 +467,12 @@ int main(int argc, char** argv)
int queued = 0;
double t = 0;
const int loop = 240;
for (int i = 0; i < loop; ++i)
{
for (int i = 0; i < loop; ++i) {
VkSemaphore sem = VK_NULL_HANDLE;
const double t0 = now_ms();
const bool did = cap.present(img, VK_FORMAT_B8G8R8A8_UNORM, W, H, nullptr, 0, sem);
t += now_ms() - t0;
if (did)
{
if (did) {
consume(sem);
++queued;
}
@@ -511,12 +486,9 @@ int main(int argc, char** argv)
Sleep(50);
std::vector<unsigned char> got;
std::uint32_t gw = 0, gh = 0;
if (cap.last_frame(got, gw, gh) && gw == W && gh == H)
{
if (cap.last_frame(got, gw, gh) && gw == W && gh == H) {
image_ok = std::memcmp(got.data(), ref_rgba.data(), bytes) == 0;
}
else
{
} else {
image_ok = false;
}
check(cap.frames_published() > 0, "VkCapture published frames");

View File

@@ -13,15 +13,13 @@
using namespace coop;
namespace
{
namespace {
int g_failures = 0;
int g_counter = 0;
void check(bool ok, const char* what)
{
std::printf("%s %s\n", ok ? " ok:" : "FAIL:", what);
if (!ok)
{
if (!ok) {
++g_failures;
}
}
@@ -30,13 +28,11 @@ std::wstring write_temp(const std::vector<std::uint8_t>& bytes)
{
wchar_t dir[MAX_PATH] = {};
GetTempPathW(MAX_PATH, dir);
std::wstring path = std::wstring(dir) + L"coop_wavtest_" + std::to_wstring(GetCurrentProcessId()) + L"_" +
std::to_wstring(g_counter++) + L".wav";
std::wstring path = std::wstring(dir) + L"coop_wavtest_" + std::to_wstring(GetCurrentProcessId()) + L"_"
+ std::to_wstring(g_counter++) + L".wav";
FILE* f = nullptr;
if (_wfopen_s(&f, path.c_str(), L"wb") == 0 && f != nullptr)
{
if (!bytes.empty())
{
if (_wfopen_s(&f, path.c_str(), L"wb") == 0 && f != nullptr) {
if (!bytes.empty()) {
std::fwrite(bytes.data(), 1, bytes.size(), f);
}
std::fclose(f);
@@ -52,10 +48,9 @@ void put4(std::vector<std::uint8_t>& b, const char* s)
// Build a WAV where the `data` chunk's declared size can differ from the bytes actually appended
// (declared_data_size < 0 means "use the real size"), and an optional junk chunk can be inserted
// before `data` with a chosen size field (to exercise the chunk walk).
std::vector<std::uint8_t> build_wav(std::uint16_t tag, std::uint16_t channels, std::uint32_t rate,
std::uint16_t bits, const std::vector<std::uint8_t>& data,
long long declared_data_size = -1, const char* junk_id = nullptr,
std::uint32_t junk_size = 0)
std::vector<std::uint8_t> build_wav(std::uint16_t tag, std::uint16_t channels, std::uint32_t rate, std::uint16_t bits,
const std::vector<std::uint8_t>& data, long long declared_data_size = -1,
const char* junk_id = nullptr, std::uint32_t junk_size = 0)
{
std::vector<std::uint8_t> b;
put4(b, "RIFF");
@@ -69,19 +64,17 @@ std::vector<std::uint8_t> build_wav(std::uint16_t tag, std::uint16_t channels, s
detail::wav_put_u32(b, rate * channels * (bits / 8));
detail::wav_put_u16(b, static_cast<std::uint16_t>(channels * (bits / 8)));
detail::wav_put_u16(b, bits);
if (junk_id != nullptr)
{
if (junk_id != nullptr) {
put4(b, junk_id);
detail::wav_put_u32(b, junk_size);
b.insert(b.end(), junk_size, 0xAB); // junk body
if (junk_size & 1)
{
if (junk_size & 1) {
b.push_back(0); // RIFF pads odd chunks to an even boundary (what the reader's & 1 skips)
}
}
put4(b, "data");
detail::wav_put_u32(b, declared_data_size < 0 ? static_cast<std::uint32_t>(data.size())
: static_cast<std::uint32_t>(declared_data_size));
: static_cast<std::uint32_t>(declared_data_size));
b.insert(b.end(), data.begin(), data.end());
return b;
}