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:
@@ -31,26 +31,21 @@
|
||||
|
||||
using namespace coop;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr const wchar_t* kDefaultExe =
|
||||
L"G:\\SteamLibrary\\steamapps\\common\\Sphere Spectacle\\sphere.exe";
|
||||
namespace {
|
||||
constexpr const wchar_t* kDefaultExe = L"G:\\SteamLibrary\\steamapps\\common\\Sphere Spectacle\\sphere.exe";
|
||||
constexpr const wchar_t* kSteamUrl = L"steam://rungameid/1123040";
|
||||
|
||||
unsigned long find_pid(const wchar_t* image)
|
||||
{
|
||||
unsigned long pid = 0;
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (snap == INVALID_HANDLE_VALUE) {
|
||||
return 0;
|
||||
}
|
||||
PROCESSENTRY32W pe{};
|
||||
pe.dwSize = sizeof(pe);
|
||||
for (BOOL ok = Process32FirstW(snap, &pe); ok; ok = Process32NextW(snap, &pe))
|
||||
{
|
||||
if (_wcsicmp(pe.szExeFile, image) == 0)
|
||||
{
|
||||
for (BOOL ok = Process32FirstW(snap, &pe); ok; ok = Process32NextW(snap, &pe)) {
|
||||
if (_wcsicmp(pe.szExeFile, image) == 0) {
|
||||
pid = pe.th32ProcessID;
|
||||
break;
|
||||
}
|
||||
@@ -61,8 +56,7 @@ unsigned long find_pid(const wchar_t* image)
|
||||
|
||||
void kill_pid(unsigned long pid)
|
||||
{
|
||||
if (HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid))
|
||||
{
|
||||
if (HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid)) {
|
||||
TerminateProcess(h, 0);
|
||||
CloseHandle(h);
|
||||
}
|
||||
@@ -73,24 +67,21 @@ void kill_pid(unsigned long pid)
|
||||
void layer_register(const std::wstring& image_basename)
|
||||
{
|
||||
wchar_t tmp[MAX_PATH] = {};
|
||||
if (GetTempPathW(MAX_PATH, tmp) != 0)
|
||||
{
|
||||
if (GetTempPathW(MAX_PATH, tmp) != 0) {
|
||||
const std::wstring sf = std::wstring(tmp) + L"coop_vk_target.txt";
|
||||
char utf8[260] = {};
|
||||
const int n = WideCharToMultiByte(CP_UTF8, 0, image_basename.c_str(), -1, utf8, sizeof(utf8), nullptr,
|
||||
nullptr);
|
||||
const int n = WideCharToMultiByte(CP_UTF8, 0, image_basename.c_str(), -1, utf8, sizeof(utf8), nullptr, nullptr);
|
||||
HANDLE f = CreateFileW(sf.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
|
||||
if (f != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (f != INVALID_HANDLE_VALUE) {
|
||||
DWORD wr = 0;
|
||||
WriteFile(f, utf8, n > 0 ? static_cast<DWORD>(n - 1) : 0, &wr, nullptr); // drop the NUL
|
||||
CloseHandle(f);
|
||||
}
|
||||
}
|
||||
HKEY key = nullptr;
|
||||
if (RegCreateKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers", 0, nullptr, 0,
|
||||
KEY_SET_VALUE, nullptr, &key, nullptr) == ERROR_SUCCESS)
|
||||
{
|
||||
if (RegCreateKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers", 0, nullptr, 0, KEY_SET_VALUE,
|
||||
nullptr, &key, nullptr)
|
||||
== ERROR_SUCCESS) {
|
||||
const std::wstring mp = deployed_artifact_path(L"coop_vk_layer.json");
|
||||
DWORD enabled = 0;
|
||||
RegSetValueExW(key, mp.c_str(), 0, REG_DWORD, reinterpret_cast<const BYTE*>(&enabled), sizeof(enabled));
|
||||
@@ -101,15 +92,13 @@ void layer_register(const std::wstring& image_basename)
|
||||
void layer_unregister()
|
||||
{
|
||||
HKEY key = nullptr;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers", 0, KEY_SET_VALUE, &key) ==
|
||||
ERROR_SUCCESS)
|
||||
{
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers", 0, KEY_SET_VALUE, &key)
|
||||
== ERROR_SUCCESS) {
|
||||
RegDeleteValueW(key, deployed_artifact_path(L"coop_vk_layer.json").c_str());
|
||||
RegCloseKey(key);
|
||||
}
|
||||
wchar_t tmp[MAX_PATH] = {};
|
||||
if (GetTempPathW(MAX_PATH, tmp) != 0)
|
||||
{
|
||||
if (GetTempPathW(MAX_PATH, tmp) != 0) {
|
||||
DeleteFileW((std::wstring(tmp) + L"coop_vk_target.txt").c_str());
|
||||
}
|
||||
}
|
||||
@@ -117,22 +106,19 @@ void layer_unregister()
|
||||
bool inject(unsigned long pid)
|
||||
{
|
||||
const std::wstring dll = deployed_artifact_path(L"coop_hook.dll");
|
||||
HANDLE process = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
|
||||
PROCESS_VM_WRITE | PROCESS_VM_READ,
|
||||
HANDLE process = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION
|
||||
| PROCESS_VM_WRITE | PROCESS_VM_READ,
|
||||
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);
|
||||
@@ -140,8 +126,7 @@ bool inject(unsigned long pid)
|
||||
ok = code != 0;
|
||||
}
|
||||
}
|
||||
if (remote != nullptr)
|
||||
{
|
||||
if (remote != nullptr) {
|
||||
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
|
||||
}
|
||||
CloseHandle(process);
|
||||
@@ -150,16 +135,14 @@ bool inject(unsigned long pid)
|
||||
|
||||
SharedBlock* make_ipc(SharedMemory& shm, unsigned long pid)
|
||||
{
|
||||
if (!shm.create(shared_memory_name(pid), sizeof(SharedBlock)))
|
||||
{
|
||||
if (!shm.create(shared_memory_name(pid), sizeof(SharedBlock))) {
|
||||
return nullptr;
|
||||
}
|
||||
auto* b = shm.as<SharedBlock>();
|
||||
b->version = kProtocolVersion;
|
||||
b->pad_count = 0;
|
||||
b->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) {
|
||||
b->control.subsystem_disabled[s].store(0, std::memory_order_release); // all on (video included)
|
||||
}
|
||||
b->magic = kProtocolMagic;
|
||||
@@ -170,9 +153,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;
|
||||
@@ -195,8 +177,7 @@ bool write_bmp(const std::wstring& path, const std::vector<std::uint8_t>& rgba,
|
||||
ih.biCompression = BI_RGB;
|
||||
ih.biSizeImage = imgsize;
|
||||
HANDLE f = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
|
||||
if (f == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (f == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
DWORD wr = 0;
|
||||
@@ -205,8 +186,7 @@ bool write_bmp(const std::wstring& path, const std::vector<std::uint8_t>& rgba,
|
||||
std::vector<std::uint8_t> line(row, 0);
|
||||
for (int y = static_cast<int>(h) - 1; y >= 0; --y) // BMP is bottom-up
|
||||
{
|
||||
for (std::uint32_t x = 0; x < w; ++x)
|
||||
{
|
||||
for (std::uint32_t x = 0; x < w; ++x) {
|
||||
const std::uint8_t* p = &rgba[(static_cast<std::size_t>(y) * w + x) * 4];
|
||||
line[x * 3 + 0] = p[2]; // B
|
||||
line[x * 3 + 1] = p[1]; // G
|
||||
@@ -235,46 +215,38 @@ int main(int argc, char** argv)
|
||||
const std::string mode = argc > 1 ? argv[1] : "layer";
|
||||
const int seconds = argc > 2 ? std::atoi(argv[2]) : 12;
|
||||
std::wstring exe = kDefaultExe;
|
||||
if (argc > 3)
|
||||
{
|
||||
if (argc > 3) {
|
||||
const std::string a = argv[3];
|
||||
exe.assign(a.begin(), a.end());
|
||||
}
|
||||
const bool layer_mode = mode != "inject";
|
||||
std::printf("== Vulkan backend validation: method=%s game=%ls ==\n", layer_mode ? "layer" : "inject",
|
||||
exe.c_str());
|
||||
std::printf("== Vulkan backend validation: method=%s game=%ls ==\n", layer_mode ? "layer" : "inject", exe.c_str());
|
||||
|
||||
int failures = 0;
|
||||
auto check = [&](bool ok, const char* what) {
|
||||
std::printf("%s %s\n", ok ? " ok:" : "FAIL:", what);
|
||||
if (!ok)
|
||||
{
|
||||
if (!ok) {
|
||||
++failures;
|
||||
}
|
||||
};
|
||||
|
||||
// Clean slate.
|
||||
if (unsigned long old = find_pid(L"sphere.exe"))
|
||||
{
|
||||
if (unsigned long old = find_pid(L"sphere.exe")) {
|
||||
kill_pid(old);
|
||||
Sleep(1000);
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION pi{};
|
||||
unsigned long pid = 0;
|
||||
if (layer_mode)
|
||||
{
|
||||
if (layer_mode) {
|
||||
layer_register(L"sphere.exe");
|
||||
ShellExecuteW(nullptr, L"open", kSteamUrl, nullptr, nullptr, SW_SHOWNORMAL);
|
||||
std::printf(" launched via Steam; waiting for sphere.exe...\n");
|
||||
for (int i = 0; i < 40 && pid == 0; ++i)
|
||||
{
|
||||
for (int i = 0; i < 40 && pid == 0; ++i) {
|
||||
Sleep(500);
|
||||
pid = find_pid(L"sphere.exe");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
STARTUPINFOW si{};
|
||||
si.cb = sizeof(si);
|
||||
std::wstring cmd = exe;
|
||||
@@ -284,16 +256,14 @@ int main(int argc, char** argv)
|
||||
const std::size_t slash = exe.find_last_of(L"\\/");
|
||||
const std::wstring workdir = slash != std::wstring::npos ? exe.substr(0, slash) : std::wstring();
|
||||
if (!CreateProcessW(exe.c_str(), cmd.data(), nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr,
|
||||
workdir.empty() ? nullptr : workdir.c_str(), &si, &pi))
|
||||
{
|
||||
workdir.empty() ? nullptr : workdir.c_str(), &si, &pi)) {
|
||||
check(false, "suspended-launch the game exe directly");
|
||||
return 1;
|
||||
}
|
||||
pid = pi.dwProcessId;
|
||||
}
|
||||
|
||||
if (pid == 0)
|
||||
{
|
||||
if (pid == 0) {
|
||||
check(false, "game process appeared");
|
||||
layer_unregister();
|
||||
return 1;
|
||||
@@ -303,23 +273,19 @@ int main(int argc, char** argv)
|
||||
// Create the IPC block right away so the layer/hook can connect + publish present counts.
|
||||
SharedMemory shm;
|
||||
SharedBlock* block = make_ipc(shm, pid);
|
||||
if (block == nullptr)
|
||||
{
|
||||
if (block == nullptr) {
|
||||
check(false, "create IPC block");
|
||||
kill_pid(pid);
|
||||
layer_unregister();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!layer_mode)
|
||||
{
|
||||
if (!layer_mode) {
|
||||
const bool injected = inject(pid);
|
||||
ResumeThread(pi.hThread);
|
||||
check(injected, "inject coop_hook.dll early (pre-vkCreateInstance)");
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// The layer does video; also inject coop_hook.dll (late) so its FOCUS subsystem keeps the
|
||||
// game rendering at full rate while unfocused -- the realistic co-injected setup, and
|
||||
// required for a meaningful present-rate measurement (an unfocused game throttles itself,
|
||||
@@ -332,8 +298,7 @@ int main(int argc, char** argv)
|
||||
}
|
||||
|
||||
ID3D11Device* device = make_device();
|
||||
if (device == nullptr)
|
||||
{
|
||||
if (device == nullptr) {
|
||||
check(false, "create a D3D11 device to read the shared texture");
|
||||
kill_pid(pid);
|
||||
layer_unregister();
|
||||
@@ -350,46 +315,39 @@ int main(int argc, char** argv)
|
||||
std::uint32_t shot_w = 0, shot_h = 0;
|
||||
const DWORD end = GetTickCount() + static_cast<DWORD>(seconds) * 1000;
|
||||
bool alive = true;
|
||||
while (GetTickCount() < end && (alive = find_pid(L"sphere.exe") == pid))
|
||||
{
|
||||
while (GetTickCount() < end && (alive = find_pid(L"sphere.exe") == pid)) {
|
||||
Sleep(50);
|
||||
const VideoShareView sv = read_share(block);
|
||||
if (!src.update(sv, pid))
|
||||
{
|
||||
if (!src.update(sv, pid)) {
|
||||
continue;
|
||||
}
|
||||
cap_w = src.width();
|
||||
cap_h = src.height();
|
||||
if (first_gen == 0)
|
||||
{
|
||||
if (first_gen == 0) {
|
||||
first_gen = sv.generation;
|
||||
}
|
||||
last_gen = sv.generation;
|
||||
// Sample a few pixels for non-black; grab a full screenshot mid-run.
|
||||
std::uint8_t px[4] = {};
|
||||
if (src.read_pixel(cap_w / 2, cap_h / 2, px) && (px[0] | px[1] | px[2]) != 0)
|
||||
{
|
||||
if (src.read_pixel(cap_w / 2, cap_h / 2, px) && (px[0] | px[1] | px[2]) != 0) {
|
||||
++nonblack_frames;
|
||||
}
|
||||
if (shot.empty() && src.frames_copied() > 10)
|
||||
{
|
||||
if (shot.empty() && src.frames_copied() > 10) {
|
||||
src.read_frame(shot, shot_w, shot_h);
|
||||
}
|
||||
}
|
||||
|
||||
const std::uint64_t presents = block->video.present_calls;
|
||||
std::printf(" captured %ux%u copied=%llu present_calls=%llu gen %u..%u nonblack=%llu\n", cap_w, cap_h,
|
||||
static_cast<unsigned long long>(src.frames_copied()),
|
||||
static_cast<unsigned long long>(presents), first_gen, last_gen,
|
||||
static_cast<unsigned long long>(nonblack_frames));
|
||||
static_cast<unsigned long long>(src.frames_copied()), static_cast<unsigned long long>(presents),
|
||||
first_gen, last_gen, static_cast<unsigned long long>(nonblack_frames));
|
||||
|
||||
// Not-applicable skip: some titles produce no Vulkan presents when their exe is suspended-launched
|
||||
// directly (e.g. they refuse to run without a real Steam launch). For those the inject method
|
||||
// can't reach the game and the layer method should be used. (Sphere Spectacle does NOT need this:
|
||||
// it runs fine launched directly with its own folder as the working directory -- see the inject
|
||||
// launch above -- so this branch should not trigger for it.)
|
||||
if (!layer_mode && presents == 0 && src.frames_copied() == 0)
|
||||
{
|
||||
if (!layer_mode && presents == 0 && src.frames_copied() == 0) {
|
||||
std::printf(" the directly-launched exe produced no Vulkan presents -- it may require a real Steam\n"
|
||||
" launch (or failed to initialize), so the suspended-inject method can't reach it here;\n"
|
||||
" use the layer method. (The early-inject mechanism itself is covered by mock_game_test.)\n");
|
||||
@@ -407,16 +365,12 @@ int main(int argc, char** argv)
|
||||
check(nonblack_frames >= 5, "captured frames are non-black (real image content)");
|
||||
|
||||
// Screenshot for visual confirmation of correctness (colors / brightness / no swizzle).
|
||||
if (!shot.empty())
|
||||
{
|
||||
if (!shot.empty()) {
|
||||
const std::wstring out = exe_directory() + (layer_mode ? L"vk_validate_layer.bmp" : L"vk_validate_inject.bmp");
|
||||
if (write_bmp(out, shot, shot_w, shot_h))
|
||||
{
|
||||
if (write_bmp(out, shot, shot_w, shot_h)) {
|
||||
std::printf(" screenshot: %ls (%ux%u)\n", out.c_str(), shot_w, shot_h);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
check(false, "grabbed a screenshot frame");
|
||||
}
|
||||
|
||||
@@ -425,8 +379,7 @@ int main(int argc, char** argv)
|
||||
// so the game must keep a healthy present rate while we mirror. The capture is NOT throttled --
|
||||
// it follows the present rate, which vsync paces -- so the mirror rate tracks the present rate. A
|
||||
// present rate that collapses (the bug was ~3/s) fails here.
|
||||
if (alive && find_pid(L"sphere.exe") == pid)
|
||||
{
|
||||
if (alive && find_pid(L"sphere.exe") == pid) {
|
||||
const std::uint64_t p0 = block->video.present_calls;
|
||||
const std::uint32_t g0 = block->video.generation.load(std::memory_order_acquire);
|
||||
Sleep(3000);
|
||||
@@ -444,8 +397,7 @@ int main(int argc, char** argv)
|
||||
|
||||
kill_pid(pid);
|
||||
device->Release();
|
||||
if (layer_mode)
|
||||
{
|
||||
if (layer_mode) {
|
||||
layer_unregister();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user