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

@@ -42,23 +42,21 @@
#include "coop/wav.hpp"
#include "tone_source.hpp" // in-process sine renderer (shared with coop_tone), for --selfcheck
namespace
{
namespace {
struct Options
{
unsigned long pid = 0; // attach to this pid instead of spawning coop_tone
unsigned long listen = 0; // --listen: passively loopback-capture this pid's output (e.g. coop_host)
double freq = 1000.0; // the tone frequency (for pitch analysis)
unsigned rate = 44100; // tone render rate (the Brotato/Godot non-device case by default)
struct Options {
unsigned long pid = 0; // attach to this pid instead of spawning coop_tone
unsigned long listen = 0; // --listen: passively loopback-capture this pid's output (e.g. coop_host)
double freq = 1000.0; // the tone frequency (for pitch analysis)
unsigned rate = 44100; // tone render rate (the Brotato/Godot non-device case by default)
unsigned channels = 2;
unsigned bits = 32; // 32 = float, 16 = pcm
int seconds = 6; // capture duration
bool render = false; // --render: measure the host RENDER path (run_hooked), not just capture
bool baseline = false; // --baseline: loopback-capture the tone directly (no hook/mirror) as a floor
bool selfcheck = false; // --selfcheck: render a clean tone in-process + self-capture (control for self-capture)
std::wstring wav_in; // analyze this .wav instead of capturing
std::wstring wav_out; // where to dump the captured audio (default next to the exe)
unsigned bits = 32; // 32 = float, 16 = pcm
int seconds = 6; // capture duration
bool render = false; // --render: measure the host RENDER path (run_hooked), not just capture
bool baseline = false; // --baseline: loopback-capture the tone directly (no hook/mirror) as a floor
bool selfcheck = false; // --selfcheck: render a clean tone in-process + self-capture (control for self-capture)
std::wstring wav_in; // analyze this .wav instead of capturing
std::wstring wav_out; // where to dump the captured audio (default next to the exe)
};
std::wstring sibling(const std::wstring& path, const wchar_t* name)
@@ -71,20 +69,17 @@ std::wstring sibling(const std::wstring& path, const wchar_t* name)
std::wstring find_coop_tone()
{
const std::wstring here = coop::exe_directory() + L"coop_tone.exe";
if (GetFileAttributesW(here.c_str()) != INVALID_FILE_ATTRIBUTES)
{
if (GetFileAttributesW(here.c_str()) != INVALID_FILE_ATTRIBUTES) {
return here;
}
std::wstring dir = coop::exe_directory();
if (!dir.empty())
{
if (!dir.empty()) {
dir.pop_back();
}
const std::size_t slash = dir.find_last_of(L"\\/");
const std::wstring root = (slash == std::wstring::npos) ? std::wstring() : dir.substr(0, slash + 1);
const std::wstring in_tests = root + L"tests\\coop_tone.exe";
if (GetFileAttributesW(in_tests.c_str()) != INVALID_FILE_ATTRIBUTES)
{
if (GetFileAttributesW(in_tests.c_str()) != INVALID_FILE_ATTRIBUTES) {
return in_tests;
}
return here;
@@ -96,9 +91,8 @@ bool inject_via_helper(unsigned long pid, const std::wstring& dll_path)
{
const std::wstring helper = sibling(dll_path, L"coop_inject_x86.exe");
const std::wstring x86_dll = sibling(dll_path, L"coop_hook_x86.dll");
if (GetFileAttributesW(helper.c_str()) == INVALID_FILE_ATTRIBUTES ||
GetFileAttributesW(x86_dll.c_str()) == INVALID_FILE_ATTRIBUTES)
{
if (GetFileAttributesW(helper.c_str()) == INVALID_FILE_ATTRIBUTES
|| GetFileAttributesW(x86_dll.c_str()) == INVALID_FILE_ATTRIBUTES) {
std::printf("ERROR: x86 helper/dll missing next to the tool.\n");
return false;
}
@@ -106,8 +100,7 @@ bool inject_via_helper(unsigned long pid, const std::wstring& dll_path)
STARTUPINFOW si{};
si.cb = sizeof(si);
PROCESS_INFORMATION pi{};
if (!CreateProcessW(helper.c_str(), cmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi))
{
if (!CreateProcessW(helper.c_str(), cmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
std::printf("ERROR: CreateProcess(coop_inject_x86) failed (%lu).\n", GetLastError());
return false;
}
@@ -121,35 +114,30 @@ bool inject_via_helper(unsigned long pid, const std::wstring& dll_path)
bool inject(unsigned long pid, const std::wstring& dll_path)
{
if (GetFileAttributesW(dll_path.c_str()) == INVALID_FILE_ATTRIBUTES)
{
if (GetFileAttributesW(dll_path.c_str()) == INVALID_FILE_ATTRIBUTES) {
std::printf("ERROR: coop_hook.dll not found next to the tool.\n");
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) {
std::printf("ERROR: OpenProcess(%lu) failed (%lu). Run as administrator?\n", pid, GetLastError());
return false;
}
USHORT proc_machine = IMAGE_FILE_MACHINE_UNKNOWN, native_machine = IMAGE_FILE_MACHINE_UNKNOWN;
if (IsWow64Process2(process, &proc_machine, &native_machine) && proc_machine != IMAGE_FILE_MACHINE_UNKNOWN)
{
if (IsWow64Process2(process, &proc_machine, &native_machine) && proc_machine != IMAGE_FILE_MACHINE_UNKNOWN) {
CloseHandle(process);
return inject_via_helper(pid, dll_path);
}
const SIZE_T bytes = (dll_path.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_path.c_str(), bytes, nullptr))
{
auto load_library = reinterpret_cast<LPTHREAD_START_ROUTINE>(
GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"));
if (remote != nullptr && WriteProcessMemory(process, remote, dll_path.c_str(), bytes, nullptr)) {
auto load_library =
reinterpret_cast<LPTHREAD_START_ROUTINE>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"));
HANDLE thread = CreateRemoteThread(process, nullptr, 0, load_library, remote, 0, nullptr);
if (thread != nullptr)
{
if (thread != nullptr) {
WaitForSingleObject(thread, INFINITE);
DWORD exit_code = 0;
GetExitCodeThread(thread, &exit_code);
@@ -157,8 +145,7 @@ bool inject(unsigned long pid, const std::wstring& dll_path)
ok = (exit_code != 0);
}
}
if (remote != nullptr)
{
if (remote != nullptr) {
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
}
CloseHandle(process);
@@ -168,13 +155,11 @@ bool inject(unsigned long pid, const std::wstring& dll_path)
void enable_hook_trace()
{
wchar_t dir[MAX_PATH] = {};
if (GetTempPathW(MAX_PATH, dir) != 0)
{
if (GetTempPathW(MAX_PATH, dir) != 0) {
const std::wstring sentinel = std::wstring(dir) + L"coop_hook.log.on";
HANDLE h = CreateFileW(sentinel.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, nullptr);
if (h != INVALID_HANDLE_VALUE)
{
if (h != INVALID_HANDLE_VALUE) {
CloseHandle(h);
}
}
@@ -186,24 +171,22 @@ void enable_hook_trace()
HANDLE spawn_tone(const Options& o, unsigned long& tone_pid)
{
const std::wstring exe = find_coop_tone();
if (GetFileAttributesW(exe.c_str()) == INVALID_FILE_ATTRIBUTES)
{
if (GetFileAttributesW(exe.c_str()) == INVALID_FILE_ATTRIBUTES) {
std::printf("ERROR: coop_tone.exe not found (looked next to the tool and in ../tests/).\n");
return nullptr;
}
HANDLE rd = nullptr, wr = nullptr;
SECURITY_ATTRIBUTES sa{sizeof(sa), nullptr, TRUE};
if (!CreatePipe(&rd, &wr, &sa, 0))
{
if (!CreatePipe(&rd, &wr, &sa, 0)) {
return nullptr;
}
SetHandleInformation(rd, HANDLE_FLAG_INHERIT, 0);
// coop_tone [seconds] [freq] [rate] [channels] [bits] [float|pcm]
const wchar_t* kind = (o.bits == 32) ? L"float" : L"pcm";
std::wstring cmd = L"\"" + exe + L"\" " + std::to_wstring(o.seconds + 4) + L" " +
std::to_wstring(static_cast<long>(o.freq)) + L" " + std::to_wstring(o.rate) + L" " +
std::to_wstring(o.channels) + L" " + std::to_wstring(o.bits) + L" " + kind;
std::wstring cmd = L"\"" + exe + L"\" " + std::to_wstring(o.seconds + 4) + L" "
+ std::to_wstring(static_cast<long>(o.freq)) + L" " + std::to_wstring(o.rate) + L" "
+ std::to_wstring(o.channels) + L" " + std::to_wstring(o.bits) + L" " + kind;
STARTUPINFOW si{};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
@@ -213,8 +196,7 @@ HANDLE spawn_tone(const Options& o, unsigned long& tone_pid)
const BOOL launched =
CreateProcessW(exe.c_str(), cmd.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi);
CloseHandle(wr);
if (!launched)
{
if (!launched) {
std::printf("ERROR: CreateProcess(coop_tone) failed (%lu).\n", GetLastError());
CloseHandle(rd);
return nullptr;
@@ -227,27 +209,20 @@ HANDLE spawn_tone(const Options& o, unsigned long& tone_pid)
char ch = 0;
DWORD got = 0;
const DWORD start = GetTickCount();
while (GetTickCount() - start < 5000)
{
if (ReadFile(rd, &ch, 1, &got, nullptr) && got == 1)
{
if (ch == '\n')
{
while (GetTickCount() - start < 5000) {
if (ReadFile(rd, &ch, 1, &got, nullptr) && got == 1) {
if (ch == '\n') {
break;
}
if (ch != '\r')
{
if (ch != '\r') {
line.push_back(ch);
}
}
else
{
} else {
break;
}
}
CloseHandle(rd);
if (line.rfind("TONE_RENDERING", 0) == 0)
{
if (line.rfind("TONE_RENDERING", 0) == 0) {
std::printf("coop_tone: %s\n", line.c_str());
return pi.hProcess;
}
@@ -261,16 +236,13 @@ HANDLE spawn_tone(const Options& o, unsigned long& tone_pid)
// Capture the hook's audio ring for `seconds`, draining frequently so the tool itself
// never causes an overrun -- the captured buffer is then exactly what the hook produced.
// Fills `pcm` (interleaved) and reports the declared format. Returns false if no format.
bool capture_ring(coop::AudioRingHeader* ring, int seconds, std::vector<std::uint8_t>& pcm,
std::uint32_t& rate, std::uint32_t& channels, std::uint32_t& bits,
std::uint32_t& format_tag, std::uint64_t& overruns)
bool capture_ring(coop::AudioRingHeader* ring, int seconds, std::vector<std::uint8_t>& pcm, std::uint32_t& rate,
std::uint32_t& channels, std::uint32_t& bits, std::uint32_t& format_tag, std::uint64_t& overruns)
{
// Wait up to 8 s for the hook to publish a format (late attach measures the rate first).
const DWORD wait_end = GetTickCount() + 8000;
while (!coop::audio_ring_format_ready(*ring))
{
if (GetTickCount() >= wait_end)
{
while (!coop::audio_ring_format_ready(*ring)) {
if (GetTickCount() >= wait_end) {
std::printf("ERROR: hook never published an audio format (no stream captured).\n");
return false;
}
@@ -280,48 +252,40 @@ bool capture_ring(coop::AudioRingHeader* ring, int seconds, std::vector<std::uin
channels = ring->channels;
bits = ring->bits;
format_tag = ring->format_tag;
std::printf("Hook published format: %u Hz / %u ch / %u-bit / tag %u. Capturing %d s...\n", rate, channels,
bits, format_tag, seconds);
std::printf("Hook published format: %u Hz / %u ch / %u-bit / tag %u. Capturing %d s...\n", rate, channels, bits,
format_tag, seconds);
std::vector<std::uint8_t> scratch(coop::kAudioRingCapacity);
const DWORD cap_end = GetTickCount() + static_cast<DWORD>(seconds) * 1000;
while (GetTickCount() < cap_end)
{
while (GetTickCount() < cap_end) {
std::uint32_t got = coop::audio_ring_pop(*ring, scratch.data(), static_cast<std::uint32_t>(scratch.size()));
if (got > 0)
{
if (got > 0) {
pcm.insert(pcm.end(), scratch.begin(), scratch.begin() + got);
}
else
{
} else {
Sleep(2); // ring momentarily empty; poll again shortly
}
}
// Drain any tail.
std::uint32_t got = 0;
while ((got = coop::audio_ring_pop(*ring, scratch.data(), static_cast<std::uint32_t>(scratch.size()))) > 0)
{
while ((got = coop::audio_ring_pop(*ring, scratch.data(), static_cast<std::uint32_t>(scratch.size()))) > 0) {
pcm.insert(pcm.end(), scratch.begin(), scratch.begin() + got);
}
overruns = ring->overruns.load(std::memory_order_relaxed);
return !pcm.empty();
}
void print_report(const coop::ToneReport& r, double expected_hz, std::uint32_t declared_rate,
std::uint64_t overruns)
void print_report(const coop::ToneReport& r, double expected_hz, std::uint32_t declared_rate, std::uint64_t overruns)
{
std::printf("\n================ FIDELITY REPORT ================\n");
std::printf(" samples analyzed : %zu frames (%.2f s @ %u Hz)\n", r.frames, r.duration_sec, r.sample_rate);
std::printf(" level : RMS %.4f peak %.4f clipped %.3f%%\n", r.rms, r.peak,
r.clipped_fraction * 100.0);
if (expected_hz > 0.0)
{
std::printf(" PITCH : %.2f Hz captured vs %.2f Hz played -> %+.1f cents (x%.4f)\n",
r.dominant_hz, expected_hz, r.pitch_error_cents, r.pitch_error_ratio);
if (expected_hz > 0.0) {
std::printf(" PITCH : %.2f Hz captured vs %.2f Hz played -> %+.1f cents (x%.4f)\n", r.dominant_hz,
expected_hz, r.pitch_error_cents, r.pitch_error_ratio);
// If the pitch is off, the most likely cause is a wrong declared rate. Show the rate
// the captured pitch implies, so a misdetection is obvious at a glance.
if (r.pitch_error_ratio > 0.0)
{
if (r.pitch_error_ratio > 0.0) {
const double implied_true_rate = declared_rate / r.pitch_error_ratio;
std::printf(" implied true rate: ~%.0f Hz (declared %u Hz)%s\n", implied_true_rate, declared_rate,
std::fabs(r.pitch_error_cents) > 15.0 ? " <-- MISMATCH" : "");
@@ -330,69 +294,58 @@ void print_report(const coop::ToneReport& r, double expected_hz, std::uint32_t d
}
std::printf(" discontinuities : %u clicks (%.2f/s)\n", r.glitch_count, r.glitch_rate_per_sec);
std::printf(" dropouts : %u gaps, %.1f ms total\n", r.dropout_count, r.dropout_ms);
if (overruns != UINT64_MAX)
{
if (overruns != UINT64_MAX) {
std::printf(" ring overruns : %llu (host fell behind -> dropped packets)\n",
static_cast<unsigned long long>(overruns));
}
std::printf("------------------- VERDICT --------------------\n");
int problems = 0;
if (expected_hz > 0.0 && std::fabs(r.pitch_error_cents) > 15.0)
{
if (expected_hz > 0.0 && std::fabs(r.pitch_error_cents) > 15.0) {
std::printf(" [X] PITCH SHIFT: captured rate is wrong (audible). Likely a mis-measured\n"
" late-attach rate -- see implied true rate above.\n");
++problems;
}
if (r.dropout_count > 0)
{
std::printf(" [X] DROPOUTS: %u silence gap(s) -- choppy / 'metallic' under-run artifacts.\n",
r.dropout_count);
if (r.dropout_count > 0) {
std::printf(" [X] DROPOUTS: %u silence gap(s) -- choppy / 'metallic' under-run artifacts.\n", r.dropout_count);
++problems;
}
if (r.glitch_rate_per_sec > 1.0)
{
if (r.glitch_rate_per_sec > 1.0) {
std::printf(" [X] CLICKS: %.1f discontinuities/s -- torn/dropped packets.\n", r.glitch_rate_per_sec);
++problems;
}
if (expected_hz > 0.0 && r.snr_db < 40.0)
{
if (expected_hz > 0.0 && r.snr_db < 40.0) {
std::printf(" [X] DISTORTION: SNR %.1f dB is low for a pure tone.\n", r.snr_db);
++problems;
}
if (problems == 0)
{
if (problems == 0) {
std::printf(" [OK] Captured audio is faithful (pitch, purity, continuity all good).\n");
}
std::printf("=================================================\n");
}
// Resolve a WAVEFORMATEX (possibly EXTENSIBLE) to the scalar fields the analyzer wants.
void resolve_waveformat(const WAVEFORMATEX* w, std::uint32_t& rate, std::uint32_t& channels,
std::uint32_t& bits, std::uint32_t& tag)
void resolve_waveformat(const WAVEFORMATEX* w, std::uint32_t& rate, std::uint32_t& channels, std::uint32_t& bits,
std::uint32_t& tag)
{
rate = w->nSamplesPerSec;
channels = w->nChannels;
bits = w->wBitsPerSample;
tag = w->wFormatTag;
if (w->wFormatTag == WAVE_FORMAT_EXTENSIBLE && w->cbSize >= 22)
{
if (w->wFormatTag == WAVE_FORMAT_EXTENSIBLE && w->cbSize >= 22) {
const auto* ext = reinterpret_cast<const WAVEFORMATEXTENSIBLE*>(w);
tag = (ext->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) ? coop::kToneFormatFloat
: coop::kToneFormatPcm;
tag = (ext->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) ? coop::kToneFormatFloat : coop::kToneFormatPcm;
}
}
// Loopback-capture `pid`'s render output (device-clock faithful, gaps included) for
// `seconds`, into `pcm`, and report the device format. Shared by --render (self) and
// --baseline (the tone directly). Assumes COM is already initialized on this thread.
bool loopback_capture_pid(unsigned long pid, int seconds, std::vector<std::uint8_t>& pcm,
std::uint32_t& rate, std::uint32_t& channels, std::uint32_t& bits,
std::uint32_t& tag, std::uint32_t& block_align)
bool loopback_capture_pid(unsigned long pid, int seconds, std::vector<std::uint8_t>& pcm, std::uint32_t& rate,
std::uint32_t& channels, std::uint32_t& bits, std::uint32_t& tag, std::uint32_t& block_align)
{
WAVEFORMATEX* mix = coop::default_render_format();
if (mix == nullptr)
{
if (mix == nullptr) {
std::printf("ERROR: could not get the default render format.\n");
return false;
}
@@ -403,17 +356,13 @@ bool loopback_capture_pid(unsigned long pid, int seconds, std::vector<std::uint8
const bool ok = cap.start(pid, mix, [&](const BYTE* data, std::uint32_t frames, bool silent) {
const std::size_t bytes = static_cast<std::size_t>(frames) * mix->nBlockAlign;
std::lock_guard<std::mutex> lk(m);
if (silent || data == nullptr)
{
if (silent || data == nullptr) {
pcm.insert(pcm.end(), bytes, 0);
}
else
{
} else {
pcm.insert(pcm.end(), data, data + bytes);
}
});
if (ok)
{
if (ok) {
Sleep(static_cast<DWORD>(seconds) * 1000);
}
cap.stop();
@@ -429,36 +378,32 @@ bool loopback_capture_pid(unsigned long pid, int seconds, std::vector<std::uint8
int run_listen_mode(const Options& o)
{
const bool com_ok = SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
std::printf("Listening to pid %lu's render output for %d s (e.g. the live coop_host mirror)...\n",
o.listen, o.seconds);
std::printf("Listening to pid %lu's render output for %d s (e.g. the live coop_host mirror)...\n", o.listen,
o.seconds);
std::vector<std::uint8_t> pcm;
std::uint32_t rate = 0, channels = 0, bits = 0, tag = 0, block = 0;
const bool ok = loopback_capture_pid(o.listen, o.seconds, pcm, rate, channels, bits, tag, block);
int rc = 1;
if (ok && !pcm.empty())
{
if (ok && !pcm.empty()) {
std::wstring out = o.wav_out.empty() ? (coop::exe_directory() + L"coop_listen.wav") : o.wav_out;
if (coop::wav_write(out, pcm.data(), pcm.size(), rate, channels, bits, tag))
{
if (coop::wav_write(out, pcm.data(), pcm.size(), rate, channels, bits, tag)) {
std::wprintf(L"Wrote captured output: %ls\n", out.c_str());
}
// Trim the first ~0.7 s for analysis (loopback capture ramp-up) -- the .wav keeps it all.
const std::size_t skip =
std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) * block * 7 / 10);
const std::size_t skip = std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) * block * 7 / 10);
auto mono = coop::decode_channel(pcm.data() + skip, pcm.size() - skip, tag, bits, channels, 0);
if (!mono.empty())
{
if (!mono.empty()) {
std::printf("\n[LISTEN] pid %lu render output (what the guest hears):\n", o.listen);
const coop::ToneReport r = coop::analyze_tone(mono.data(), mono.size(), rate, o.freq);
print_report(r, o.freq, rate, UINT64_MAX);
rc = 0;
}
}
else
{
} else {
std::printf("ERROR: no audio captured from pid %lu (is it rendering?).\n", o.listen);
}
if (com_ok) { CoUninitialize(); }
if (com_ok) {
CoUninitialize();
}
return rc;
}
@@ -471,16 +416,13 @@ int run_selfcheck_mode(const Options& o)
const bool com_ok = SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
std::atomic<bool> stop{false};
std::thread renderer([&]() {
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
{
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
return;
}
coop::tone::ToneSource tone;
coop::tone::ToneFormat tf; // {} = device mix format (no resample), cleanest reference
if (tone.open(tf, o.freq))
{
while (!stop.load(std::memory_order_relaxed))
{
if (tone.open(tf, o.freq)) {
while (!stop.load(std::memory_order_relaxed)) {
tone.render_step(100);
}
tone.close();
@@ -497,23 +439,21 @@ int run_selfcheck_mode(const Options& o)
renderer.join();
int rc = 1;
if (ok && !pcm.empty())
{
if (ok && !pcm.empty()) {
const std::size_t skip = std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) * block * 7 / 10);
auto mono = coop::decode_channel(pcm.data() + skip, pcm.size() - skip, tag, bits, channels, 0);
if (!mono.empty())
{
if (!mono.empty()) {
std::printf("\n[SELFCHECK] in-process tone via the self-capture path (control):\n");
const coop::ToneReport r = coop::analyze_tone(mono.data(), mono.size(), rate, o.freq);
print_report(r, o.freq, rate, UINT64_MAX);
rc = 0;
}
}
else
{
} else {
std::printf("ERROR: selfcheck produced no audio.\n");
}
if (com_ok) { CoUninitialize(); }
if (com_ok) {
CoUninitialize();
}
return rc;
}
@@ -529,28 +469,25 @@ int run_baseline_mode(const Options& o, HANDLE tone_proc, unsigned long target_p
std::uint32_t rate = 0, channels = 0, bits = 0, tag = 0, block = 0;
const bool ok = loopback_capture_pid(target_pid, o.seconds, pcm, rate, channels, bits, tag, block);
int rc = 1;
if (ok && !pcm.empty())
{
if (ok && !pcm.empty()) {
const std::size_t skip = std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) * block * 7 / 10);
auto mono = coop::decode_channel(pcm.data() + skip, pcm.size() - skip, tag, bits, channels, 0);
if (!mono.empty())
{
if (!mono.empty()) {
std::printf("\n[BASELINE] tone direct (measurement floor):\n");
const coop::ToneReport r = coop::analyze_tone(mono.data(), mono.size(), rate, o.freq);
print_report(r, o.freq, rate, UINT64_MAX);
rc = 0;
}
}
else
{
} else {
std::printf("ERROR: baseline loopback capture produced no audio.\n");
}
if (tone_proc != nullptr)
{
if (tone_proc != nullptr) {
TerminateProcess(tone_proc, 0);
CloseHandle(tone_proc);
}
if (com_ok) { CoUninitialize(); }
if (com_ok) {
CoUninitialize();
}
return rc;
}
@@ -565,10 +502,11 @@ int run_render_mode(const Options& o, HANDLE tone_proc, unsigned long target_pid
const bool com_ok = SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
WAVEFORMATEX* mix = coop::default_render_format();
if (mix == nullptr)
{
if (mix == nullptr) {
std::printf("ERROR: could not get the default render format.\n");
if (com_ok) { CoUninitialize(); }
if (com_ok) {
CoUninitialize();
}
return 1;
}
std::uint32_t rate = 0, channels = 0, bits = 0, tag = 0;
@@ -580,38 +518,34 @@ int run_render_mode(const Options& o, HANDLE tone_proc, unsigned long target_pid
std::vector<std::uint8_t> rendered;
std::mutex rendered_mutex;
coop::ProcessLoopbackCapture selfcap;
const bool cap_ok = selfcap.start(GetCurrentProcessId(), mix,
[&](const BYTE* data, std::uint32_t frames, bool silent) {
const std::size_t bytes = static_cast<std::size_t>(frames) * mix->nBlockAlign;
std::lock_guard<std::mutex> lk(rendered_mutex);
if (silent || data == nullptr)
{
rendered.insert(rendered.end(), bytes, 0);
}
else
{
rendered.insert(rendered.end(), data, data + bytes);
}
});
if (!cap_ok)
{
const bool cap_ok =
selfcap.start(GetCurrentProcessId(), mix, [&](const BYTE* data, std::uint32_t frames, bool silent) {
const std::size_t bytes = static_cast<std::size_t>(frames) * mix->nBlockAlign;
std::lock_guard<std::mutex> lk(rendered_mutex);
if (silent || data == nullptr) {
rendered.insert(rendered.end(), bytes, 0);
} else {
rendered.insert(rendered.end(), data, data + bytes);
}
});
if (!cap_ok) {
std::printf("ERROR: self-loopback capture failed to start.\n");
CoTaskMemFree(mix);
if (com_ok) { CoUninitialize(); }
if (com_ok) {
CoUninitialize();
}
return 1;
}
// Drive the real mirror: it discovers the hook's ring, re-renders it (silencing the game).
coop::AudioMirror mirror;
if (!mirror.start(target_pid))
{
if (!mirror.start(target_pid)) {
std::printf("ERROR: AudioMirror failed to start.\n");
}
std::printf("Rendering through the real AudioMirror for %d s (source warms up, then measure)...\n",
o.seconds);
std::printf("Rendering through the real AudioMirror for %d s (source warms up, then measure)...\n", o.seconds);
Sleep(static_cast<DWORD>(o.seconds) * 1000);
std::printf(" mirror: source=%s status=\"%s\" buffered=%u ms\n", mirror.source_name(),
mirror.status().c_str(), mirror.buffered_ms());
std::printf(" mirror: source=%s status=\"%s\" buffered=%u ms\n", mirror.source_name(), mirror.status().c_str(),
mirror.buffered_ms());
mirror.stop();
selfcap.stop();
@@ -623,100 +557,70 @@ int run_render_mode(const Options& o, HANDLE tone_proc, unsigned long target_pid
// Trim the first ~0.7 s: it contains start-up priming / the loopback warming up, which
// would otherwise read as a spurious leading dropout.
const std::size_t skip = std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) *
mix->nBlockAlign * 7 / 10);
const std::size_t skip =
std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) * mix->nBlockAlign * 7 / 10);
const std::uint8_t* body = pcm.data() + skip;
const std::size_t body_bytes = pcm.size() - skip;
std::wstring out = o.wav_out.empty() ? (coop::exe_directory() + L"coop_render.wav") : o.wav_out;
if (coop::wav_write(out, body, body_bytes, rate, channels, bits, tag))
{
if (coop::wav_write(out, body, body_bytes, rate, channels, bits, tag)) {
std::wprintf(L"Wrote rendered output: %ls\n", out.c_str());
}
auto mono = coop::decode_channel(body, body_bytes, tag, bits, channels, 0);
if (mono.empty())
{
if (mono.empty()) {
std::printf("NOTE: render format isn't float32/int16; WAV written, analysis skipped.\n");
}
else
{
} else {
std::printf("\n[RENDER PATH] what the guest actually hears (real AudioMirror output):\n");
const coop::ToneReport r = coop::analyze_tone(mono.data(), mono.size(), rate, o.freq);
print_report(r, o.freq, rate, UINT64_MAX);
}
CoTaskMemFree(mix);
if (tone_proc != nullptr)
{
if (tone_proc != nullptr) {
TerminateProcess(tone_proc, 0);
CloseHandle(tone_proc);
}
if (com_ok) { CoUninitialize(); }
if (com_ok) {
CoUninitialize();
}
return mono.empty() ? 1 : 0;
}
bool parse_args(int argc, wchar_t** argv, Options& o)
{
for (int i = 1; i < argc; ++i)
{
for (int i = 1; i < argc; ++i) {
const std::wstring a = argv[i];
auto next = [&](unsigned& dst) {
if (i + 1 < argc)
{
if (i + 1 < argc) {
dst = static_cast<unsigned>(_wtoi(argv[++i]));
}
};
if (a == L"--pid" && i + 1 < argc)
{
if (a == L"--pid" && i + 1 < argc) {
o.pid = std::wcstoul(argv[++i], nullptr, 10);
}
else if (a == L"--listen" && i + 1 < argc)
{
} else if (a == L"--listen" && i + 1 < argc) {
o.listen = std::wcstoul(argv[++i], nullptr, 10);
}
else if (a == L"--freq" && i + 1 < argc)
{
} else if (a == L"--freq" && i + 1 < argc) {
o.freq = _wtof(argv[++i]);
}
else if (a == L"--rate")
{
} else if (a == L"--rate") {
next(o.rate);
}
else if (a == L"--channels")
{
} else if (a == L"--channels") {
next(o.channels);
}
else if (a == L"--bits")
{
} else if (a == L"--bits") {
next(o.bits);
}
else if (a == L"--seconds" && i + 1 < argc)
{
} else if (a == L"--seconds" && i + 1 < argc) {
o.seconds = std::max(1, _wtoi(argv[++i]));
}
else if (a == L"--render")
{
} else if (a == L"--render") {
o.render = true;
}
else if (a == L"--baseline")
{
} else if (a == L"--baseline") {
o.baseline = true;
}
else if (a == L"--selfcheck")
{
} else if (a == L"--selfcheck") {
o.selfcheck = true;
}
else if (a == L"--wav" && i + 1 < argc)
{
} else if (a == L"--wav" && i + 1 < argc) {
o.wav_in = argv[++i];
}
else if (a == L"--out" && i + 1 < argc)
{
} else if (a == L"--out" && i + 1 < argc) {
o.wav_out = argv[++i];
}
else if (a == L"--help" || a == L"-h")
{
} else if (a == L"--help" || a == L"-h") {
return false;
}
}
@@ -728,8 +632,7 @@ bool parse_args(int argc, wchar_t** argv, Options& o)
int wmain(int argc, wchar_t** argv)
{
Options o;
if (!parse_args(argc, argv, o))
{
if (!parse_args(argc, argv, o)) {
std::printf("usage: coop_audio_validate [--pid N] [--listen N] [--freq Hz] [--rate Hz]\n"
" [--channels N] [--bits 16|32] [--seconds N] [--render | --baseline | --selfcheck]\n"
" [--wav file] [--out file]\n"
@@ -745,19 +648,16 @@ int wmain(int argc, wchar_t** argv)
}
// --- Mode C: analyze a recorded .wav ---------------------------------------------
if (!o.wav_in.empty())
{
if (!o.wav_in.empty()) {
coop::WavData wd;
if (!coop::wav_read(o.wav_in, wd))
{
if (!coop::wav_read(o.wav_in, wd)) {
std::wprintf(L"ERROR: could not read WAV '%ls'.\n", o.wav_in.c_str());
return 1;
}
std::printf("Loaded WAV: %u Hz / %u ch / %u-bit / tag %u, %zu bytes\n", wd.sample_rate, wd.channels,
wd.bits, wd.format_tag, wd.pcm.size());
std::printf("Loaded WAV: %u Hz / %u ch / %u-bit / tag %u, %zu bytes\n", wd.sample_rate, wd.channels, wd.bits,
wd.format_tag, wd.pcm.size());
auto mono = coop::decode_channel(wd.pcm.data(), wd.pcm.size(), wd.format_tag, wd.bits, wd.channels, 0);
if (mono.empty())
{
if (mono.empty()) {
std::printf("ERROR: unsupported WAV sample format (need 16-bit PCM or 32-bit float).\n");
return 1;
}
@@ -767,44 +667,36 @@ int wmain(int argc, wchar_t** argv)
}
// --- Control: render a clean tone in-process + self-capture (no target needed) ----
if (o.selfcheck)
{
if (o.selfcheck) {
return run_selfcheck_mode(o);
}
// --- Live: passively record an already-running process's output (e.g. coop_host) --
if (o.listen != 0)
{
if (o.listen != 0) {
return run_listen_mode(o);
}
// --- Acquire a target: spawn coop_tone, or attach to a given pid ------------------
HANDLE tone_proc = nullptr;
unsigned long target_pid = o.pid;
if (target_pid == 0)
{
if (target_pid == 0) {
tone_proc = spawn_tone(o, target_pid);
if (tone_proc == nullptr)
{
if (tone_proc == nullptr) {
return 1;
}
Sleep(700); // let the tone reach steady state before we inject
}
else
{
} else {
std::printf("Attaching to existing pid %lu (tone freq assumed %.0f Hz).\n", target_pid, o.freq);
}
// --- Mode: measurement floor (no hook, no mirror) ---------------------------------
if (o.baseline)
{
if (o.baseline) {
return run_baseline_mode(o, tone_proc, target_pid);
}
// --- Set up the IPC the hook expects, then inject (late attach: ring AFTER inject) -
coop::SharedMemory ipc;
if (!ipc.create(coop::shared_memory_name(target_pid), sizeof(coop::SharedBlock)))
{
if (!ipc.create(coop::shared_memory_name(target_pid), sizeof(coop::SharedBlock))) {
std::printf("ERROR: create input mapping failed (%lu).\n", GetLastError());
return 1;
}
@@ -817,15 +709,13 @@ int wmain(int argc, wchar_t** argv)
enable_hook_trace();
std::printf("Injecting coop_hook.dll into pid %lu ...\n", target_pid);
if (!inject(target_pid, coop::deployed_artifact_path(L"coop_hook.dll")))
{
if (!inject(target_pid, coop::deployed_artifact_path(L"coop_hook.dll"))) {
std::printf("ERROR: injection failed.\n");
return 1;
}
// --- Mode B: measure the host RENDER path (real AudioMirror) ----------------------
if (o.render)
{
if (o.render) {
Sleep(1200); // let the hook register the stream before the mirror reads it
const int rc = run_render_mode(o, tone_proc, target_pid);
block->magic = 0;
@@ -837,9 +727,7 @@ int wmain(int argc, wchar_t** argv)
// hook's late-attach guess+measure path (the exact Brotato scenario).
Sleep(1500);
coop::SharedMemory ring_shm;
if (!ring_shm.create(coop::audio_ring_name(target_pid),
coop::audio_ring_total_size(coop::kAudioRingCapacity)))
{
if (!ring_shm.create(coop::audio_ring_name(target_pid), coop::audio_ring_total_size(coop::kAudioRingCapacity))) {
std::printf("ERROR: create audio ring mapping failed (%lu).\n", GetLastError());
return 1;
}
@@ -853,30 +741,24 @@ int wmain(int argc, wchar_t** argv)
std::uint64_t overruns = 0;
const bool captured = capture_ring(ring, o.seconds, pcm, rate, channels, bits, format_tag, overruns);
if (captured)
{
if (captured) {
// Dump the captured audio so it can be listened to.
std::wstring out = o.wav_out.empty() ? (coop::exe_directory() + L"coop_capture.wav") : o.wav_out;
if (coop::wav_write(out, pcm.data(), pcm.size(), rate, channels, bits, format_tag))
{
if (coop::wav_write(out, pcm.data(), pcm.size(), rate, channels, bits, format_tag)) {
std::wprintf(L"Wrote captured audio: %ls\n", out.c_str());
}
auto mono = coop::decode_channel(pcm.data(), pcm.size(), format_tag, bits, channels, 0);
if (mono.empty())
{
if (mono.empty()) {
std::printf("NOTE: captured format isn't float32/int16, can't decode for analysis (WAV still written).\n");
}
else
{
} else {
const coop::ToneReport r = coop::analyze_tone(mono.data(), mono.size(), rate, o.freq);
print_report(r, o.freq, rate, overruns);
}
}
block->magic = 0; // invalidate so a late hook read won't trust stale data
if (tone_proc != nullptr)
{
if (tone_proc != nullptr) {
TerminateProcess(tone_proc, 0);
CloseHandle(tone_proc);
}