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:
@@ -7,8 +7,7 @@
|
||||
#include "coop/protocol.hpp"
|
||||
#include "coop/shared_memory.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
bool hook_dll_alive(unsigned long pid, int timeout_ms)
|
||||
{
|
||||
@@ -18,17 +17,14 @@ bool hook_dll_alive(unsigned long pid, int timeout_ms)
|
||||
// Poll rather than sample once: the worker only beats ~4x/s, so a single short read can straddle
|
||||
// a gap and miss it; return the instant a beat lands, and give up after the timeout.
|
||||
SharedMemory shm;
|
||||
if (!shm.open(shared_memory_name(pid), sizeof(SharedBlock)))
|
||||
{
|
||||
if (!shm.open(shared_memory_name(pid), sizeof(SharedBlock))) {
|
||||
return false;
|
||||
}
|
||||
auto* block = shm.as<SharedBlock>();
|
||||
const std::uint32_t h0 = block->status.heartbeat.load(std::memory_order_acquire);
|
||||
for (int waited = 0; waited < timeout_ms; waited += 25)
|
||||
{
|
||||
for (int waited = 0; waited < timeout_ms; waited += 25) {
|
||||
Sleep(25);
|
||||
if (block->status.heartbeat.load(std::memory_order_acquire) != h0)
|
||||
{
|
||||
if (block->status.heartbeat.load(std::memory_order_acquire) != h0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
// crash, since a connected DLL keeps the per-pid IPC section alive.
|
||||
#pragma once
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
// True if `pid` already hosts a live coop_hook DLL: the per-pid IPC section exists and its heartbeat
|
||||
// advances within `timeout_ms` (the DLL's worker is still beating). Returns as soon as a beat lands,
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
const char* to_string(InjectStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
switch (status) {
|
||||
case InjectStatus::Ok:
|
||||
return "OK";
|
||||
case InjectStatus::OpenProcessFailed:
|
||||
@@ -33,8 +31,7 @@ const char* to_string(InjectStatus status)
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace {
|
||||
|
||||
InjectResult fail(InjectStatus status)
|
||||
{
|
||||
@@ -46,16 +43,14 @@ bool is_wow64_process(HANDLE process)
|
||||
{
|
||||
USHORT process_machine = IMAGE_FILE_MACHINE_UNKNOWN;
|
||||
USHORT native_machine = IMAGE_FILE_MACHINE_UNKNOWN;
|
||||
if (IsWow64Process2(process, &process_machine, &native_machine))
|
||||
{
|
||||
if (IsWow64Process2(process, &process_machine, &native_machine)) {
|
||||
return process_machine != IMAGE_FILE_MACHINE_UNKNOWN;
|
||||
}
|
||||
// IsWow64Process2 failed -- fall back to the legacy query rather than guessing "native", since
|
||||
// guessing wrong sends the x64 DLL into a 32-bit target (which can't load it). Only if BOTH
|
||||
// queries fail do we fall back to permissive.
|
||||
BOOL wow64 = FALSE;
|
||||
if (IsWow64Process(process, &wow64))
|
||||
{
|
||||
if (IsWow64Process(process, &wow64)) {
|
||||
return wow64 != FALSE;
|
||||
}
|
||||
return false; // both queries failed; best-effort assume native
|
||||
@@ -75,9 +70,8 @@ InjectResult 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) {
|
||||
return InjectResult{InjectStatus::HelperNotFound, 0};
|
||||
}
|
||||
|
||||
@@ -87,9 +81,8 @@ InjectResult 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, CREATE_NO_WINDOW, nullptr, nullptr,
|
||||
&si, &pi))
|
||||
{
|
||||
if (!CreateProcessW(helper.c_str(), cmd.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si,
|
||||
&pi)) {
|
||||
return fail(InjectStatus::HelperFailed);
|
||||
}
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
@@ -98,8 +91,7 @@ InjectResult inject_via_helper(unsigned long pid, const std::wstring& dll_path)
|
||||
const DWORD err = got ? exit_code : GetLastError(); // on a failed query, surface the OS error
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
if (!got || exit_code != 0)
|
||||
{
|
||||
if (!got || exit_code != 0) {
|
||||
return InjectResult{InjectStatus::HelperFailed, err};
|
||||
}
|
||||
return InjectResult{InjectStatus::Ok, 0};
|
||||
@@ -109,63 +101,51 @@ InjectResult inject_via_helper(unsigned long pid, const std::wstring& dll_path)
|
||||
|
||||
InjectResult inject_dll(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) {
|
||||
return fail(InjectStatus::DllNotFound);
|
||||
}
|
||||
|
||||
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 fail(InjectStatus::OpenProcessFailed);
|
||||
}
|
||||
|
||||
struct HandleGuard
|
||||
{
|
||||
struct HandleGuard {
|
||||
HANDLE h;
|
||||
~HandleGuard()
|
||||
{
|
||||
if (h != nullptr)
|
||||
{
|
||||
if (h != nullptr) {
|
||||
CloseHandle(h);
|
||||
}
|
||||
}
|
||||
} process_guard{process};
|
||||
|
||||
if (is_wow64_process(process))
|
||||
{
|
||||
if (is_wow64_process(process)) {
|
||||
// The x64 host can't inject a 32-bit target directly; delegate to the helper.
|
||||
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);
|
||||
if (remote == nullptr)
|
||||
{
|
||||
if (remote == nullptr) {
|
||||
return fail(InjectStatus::AllocFailed);
|
||||
}
|
||||
|
||||
InjectResult result{InjectStatus::Ok, 0};
|
||||
|
||||
if (!WriteProcessMemory(process, remote, dll_path.c_str(), bytes, nullptr))
|
||||
{
|
||||
if (!WriteProcessMemory(process, remote, dll_path.c_str(), bytes, nullptr)) {
|
||||
result = fail(InjectStatus::WriteFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// kernel32 is mapped at the same address in every process, so LoadLibraryW's
|
||||
// address in this process is valid as the remote thread's start routine.
|
||||
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) {
|
||||
result = fail(InjectStatus::RemoteThreadFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
DWORD exit_code = 0;
|
||||
GetExitCodeThread(thread, &exit_code);
|
||||
@@ -173,8 +153,7 @@ InjectResult inject_dll(unsigned long pid, const std::wstring& dll_path)
|
||||
// LoadLibraryW returns the module handle; 0 means it failed to load.
|
||||
// (The handle is truncated to 32 bits here, but zero vs non-zero is
|
||||
// all we need to distinguish success from failure.)
|
||||
if (exit_code == 0)
|
||||
{
|
||||
if (exit_code == 0) {
|
||||
result = InjectResult{InjectStatus::RemoteLoadFailed, 0};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
enum class InjectStatus
|
||||
{
|
||||
enum class InjectStatus {
|
||||
Ok,
|
||||
OpenProcessFailed, // insufficient rights (try running the host as admin)
|
||||
BitnessMismatch, // 32-bit target; the x86 hook/helper aren't available
|
||||
@@ -21,8 +19,7 @@ enum class InjectStatus
|
||||
HelperFailed, // the x86 injector helper ran but reported failure
|
||||
};
|
||||
|
||||
struct InjectResult
|
||||
{
|
||||
struct InjectResult {
|
||||
InjectStatus status = InjectStatus::OpenProcessFailed;
|
||||
unsigned long os_error = 0; // GetLastError at the point of failure, if any
|
||||
};
|
||||
|
||||
@@ -6,80 +6,120 @@
|
||||
#include "injection_panel.hpp"
|
||||
#include "inject/mkb_map.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace {
|
||||
|
||||
// Map an ImGui key to a Win32 virtual-key. Returns 0 for keys we don't forward.
|
||||
int imgui_key_to_vk(ImGuiKey k)
|
||||
{
|
||||
if (k >= ImGuiKey_A && k <= ImGuiKey_Z)
|
||||
{
|
||||
if (k >= ImGuiKey_A && k <= ImGuiKey_Z) {
|
||||
return 'A' + (k - ImGuiKey_A);
|
||||
}
|
||||
if (k >= ImGuiKey_0 && k <= ImGuiKey_9)
|
||||
{
|
||||
if (k >= ImGuiKey_0 && k <= ImGuiKey_9) {
|
||||
return '0' + (k - ImGuiKey_0);
|
||||
}
|
||||
if (k >= ImGuiKey_Keypad0 && k <= ImGuiKey_Keypad9)
|
||||
{
|
||||
if (k >= ImGuiKey_Keypad0 && k <= ImGuiKey_Keypad9) {
|
||||
return VK_NUMPAD0 + (k - ImGuiKey_Keypad0);
|
||||
}
|
||||
if (k >= ImGuiKey_F1 && k <= ImGuiKey_F12)
|
||||
{
|
||||
if (k >= ImGuiKey_F1 && k <= ImGuiKey_F12) {
|
||||
return VK_F1 + (k - ImGuiKey_F1);
|
||||
}
|
||||
switch (k)
|
||||
{
|
||||
case ImGuiKey_Tab: return VK_TAB;
|
||||
case ImGuiKey_LeftArrow: return VK_LEFT;
|
||||
case ImGuiKey_RightArrow: return VK_RIGHT;
|
||||
case ImGuiKey_UpArrow: return VK_UP;
|
||||
case ImGuiKey_DownArrow: return VK_DOWN;
|
||||
case ImGuiKey_PageUp: return VK_PRIOR;
|
||||
case ImGuiKey_PageDown: return VK_NEXT;
|
||||
case ImGuiKey_Home: return VK_HOME;
|
||||
case ImGuiKey_End: return VK_END;
|
||||
case ImGuiKey_Insert: return VK_INSERT;
|
||||
case ImGuiKey_Delete: return VK_DELETE;
|
||||
case ImGuiKey_Backspace: return VK_BACK;
|
||||
case ImGuiKey_Space: return VK_SPACE;
|
||||
case ImGuiKey_Enter: return VK_RETURN;
|
||||
case ImGuiKey_Escape: return VK_ESCAPE;
|
||||
case ImGuiKey_LeftCtrl: return VK_LCONTROL;
|
||||
case ImGuiKey_LeftShift: return VK_LSHIFT;
|
||||
case ImGuiKey_LeftAlt: return VK_LMENU;
|
||||
case ImGuiKey_LeftSuper: return VK_LWIN;
|
||||
case ImGuiKey_RightCtrl: return VK_RCONTROL;
|
||||
case ImGuiKey_RightShift: return VK_RSHIFT;
|
||||
case ImGuiKey_RightAlt: return VK_RMENU;
|
||||
case ImGuiKey_RightSuper: return VK_RWIN;
|
||||
case ImGuiKey_Menu: return VK_APPS;
|
||||
case ImGuiKey_Apostrophe: return VK_OEM_7;
|
||||
case ImGuiKey_Comma: return VK_OEM_COMMA;
|
||||
case ImGuiKey_Minus: return VK_OEM_MINUS;
|
||||
case ImGuiKey_Period: return VK_OEM_PERIOD;
|
||||
case ImGuiKey_Slash: return VK_OEM_2;
|
||||
case ImGuiKey_Semicolon: return VK_OEM_1;
|
||||
case ImGuiKey_Equal: return VK_OEM_PLUS;
|
||||
case ImGuiKey_LeftBracket: return VK_OEM_4;
|
||||
case ImGuiKey_Backslash: return VK_OEM_5;
|
||||
case ImGuiKey_RightBracket: return VK_OEM_6;
|
||||
case ImGuiKey_GraveAccent: return VK_OEM_3;
|
||||
case ImGuiKey_CapsLock: return VK_CAPITAL;
|
||||
case ImGuiKey_ScrollLock: return VK_SCROLL;
|
||||
case ImGuiKey_NumLock: return VK_NUMLOCK;
|
||||
case ImGuiKey_PrintScreen: return VK_SNAPSHOT;
|
||||
case ImGuiKey_Pause: return VK_PAUSE;
|
||||
case ImGuiKey_KeypadDecimal: return VK_DECIMAL;
|
||||
case ImGuiKey_KeypadDivide: return VK_DIVIDE;
|
||||
case ImGuiKey_KeypadMultiply: return VK_MULTIPLY;
|
||||
case ImGuiKey_KeypadSubtract: return VK_SUBTRACT;
|
||||
case ImGuiKey_KeypadAdd: return VK_ADD;
|
||||
case ImGuiKey_KeypadEnter: return VK_RETURN;
|
||||
default: return 0;
|
||||
switch (k) {
|
||||
case ImGuiKey_Tab:
|
||||
return VK_TAB;
|
||||
case ImGuiKey_LeftArrow:
|
||||
return VK_LEFT;
|
||||
case ImGuiKey_RightArrow:
|
||||
return VK_RIGHT;
|
||||
case ImGuiKey_UpArrow:
|
||||
return VK_UP;
|
||||
case ImGuiKey_DownArrow:
|
||||
return VK_DOWN;
|
||||
case ImGuiKey_PageUp:
|
||||
return VK_PRIOR;
|
||||
case ImGuiKey_PageDown:
|
||||
return VK_NEXT;
|
||||
case ImGuiKey_Home:
|
||||
return VK_HOME;
|
||||
case ImGuiKey_End:
|
||||
return VK_END;
|
||||
case ImGuiKey_Insert:
|
||||
return VK_INSERT;
|
||||
case ImGuiKey_Delete:
|
||||
return VK_DELETE;
|
||||
case ImGuiKey_Backspace:
|
||||
return VK_BACK;
|
||||
case ImGuiKey_Space:
|
||||
return VK_SPACE;
|
||||
case ImGuiKey_Enter:
|
||||
return VK_RETURN;
|
||||
case ImGuiKey_Escape:
|
||||
return VK_ESCAPE;
|
||||
case ImGuiKey_LeftCtrl:
|
||||
return VK_LCONTROL;
|
||||
case ImGuiKey_LeftShift:
|
||||
return VK_LSHIFT;
|
||||
case ImGuiKey_LeftAlt:
|
||||
return VK_LMENU;
|
||||
case ImGuiKey_LeftSuper:
|
||||
return VK_LWIN;
|
||||
case ImGuiKey_RightCtrl:
|
||||
return VK_RCONTROL;
|
||||
case ImGuiKey_RightShift:
|
||||
return VK_RSHIFT;
|
||||
case ImGuiKey_RightAlt:
|
||||
return VK_RMENU;
|
||||
case ImGuiKey_RightSuper:
|
||||
return VK_RWIN;
|
||||
case ImGuiKey_Menu:
|
||||
return VK_APPS;
|
||||
case ImGuiKey_Apostrophe:
|
||||
return VK_OEM_7;
|
||||
case ImGuiKey_Comma:
|
||||
return VK_OEM_COMMA;
|
||||
case ImGuiKey_Minus:
|
||||
return VK_OEM_MINUS;
|
||||
case ImGuiKey_Period:
|
||||
return VK_OEM_PERIOD;
|
||||
case ImGuiKey_Slash:
|
||||
return VK_OEM_2;
|
||||
case ImGuiKey_Semicolon:
|
||||
return VK_OEM_1;
|
||||
case ImGuiKey_Equal:
|
||||
return VK_OEM_PLUS;
|
||||
case ImGuiKey_LeftBracket:
|
||||
return VK_OEM_4;
|
||||
case ImGuiKey_Backslash:
|
||||
return VK_OEM_5;
|
||||
case ImGuiKey_RightBracket:
|
||||
return VK_OEM_6;
|
||||
case ImGuiKey_GraveAccent:
|
||||
return VK_OEM_3;
|
||||
case ImGuiKey_CapsLock:
|
||||
return VK_CAPITAL;
|
||||
case ImGuiKey_ScrollLock:
|
||||
return VK_SCROLL;
|
||||
case ImGuiKey_NumLock:
|
||||
return VK_NUMLOCK;
|
||||
case ImGuiKey_PrintScreen:
|
||||
return VK_SNAPSHOT;
|
||||
case ImGuiKey_Pause:
|
||||
return VK_PAUSE;
|
||||
case ImGuiKey_KeypadDecimal:
|
||||
return VK_DECIMAL;
|
||||
case ImGuiKey_KeypadDivide:
|
||||
return VK_DIVIDE;
|
||||
case ImGuiKey_KeypadMultiply:
|
||||
return VK_MULTIPLY;
|
||||
case ImGuiKey_KeypadSubtract:
|
||||
return VK_SUBTRACT;
|
||||
case ImGuiKey_KeypadAdd:
|
||||
return VK_ADD;
|
||||
case ImGuiKey_KeypadEnter:
|
||||
return VK_RETURN;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +135,8 @@ bool g_key_down[256] = {}; // indexed by VK
|
||||
|
||||
void release_held_keys(InjectionPanel& injection)
|
||||
{
|
||||
for (int vk = 0; vk < 256; ++vk)
|
||||
{
|
||||
if (g_key_down[vk])
|
||||
{
|
||||
for (int vk = 0; vk < 256; ++vk) {
|
||||
if (g_key_down[vk]) {
|
||||
injection.push_mkb(MkbEvent{Mkb_KeyUp, static_cast<std::uint32_t>(vk), 0, 0});
|
||||
g_key_down[vk] = false;
|
||||
}
|
||||
@@ -107,10 +145,8 @@ void release_held_keys(InjectionPanel& injection)
|
||||
|
||||
void release_held_mouse(InjectionPanel& injection)
|
||||
{
|
||||
for (int b = 0; b < 3; ++b)
|
||||
{
|
||||
if (g_mouse_down[b])
|
||||
{
|
||||
for (int b = 0; b < 3; ++b) {
|
||||
if (g_mouse_down[b]) {
|
||||
injection.push_mkb(MkbEvent{Mkb_MouseUp, static_cast<std::uint32_t>(b), g_last_gx, g_last_gy});
|
||||
g_mouse_down[b] = false;
|
||||
}
|
||||
@@ -126,8 +162,7 @@ void forward_mkb_frame(InjectionPanel& injection, HWND host_hwnd, bool mirroring
|
||||
// own desktop use isn't injected. If we can't forward for ANY reason -- subsystem off, we lost
|
||||
// focus, or the game is gone -- release everything we're still holding first, so a key/button
|
||||
// held at that moment doesn't stick down in the guest.
|
||||
if (!injection.mkb_enabled() || GetForegroundWindow() != host_hwnd || game == nullptr || !IsWindow(game))
|
||||
{
|
||||
if (!injection.mkb_enabled() || GetForegroundWindow() != host_hwnd || game == nullptr || !IsWindow(game)) {
|
||||
release_held_keys(injection);
|
||||
release_held_mouse(injection);
|
||||
return;
|
||||
@@ -136,35 +171,26 @@ void forward_mkb_frame(InjectionPanel& injection, HWND host_hwnd, bool mirroring
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// --- Keyboard (unless ImGui is using it for e.g. a text field -- then release what we hold) ---
|
||||
if (io.WantCaptureKeyboard)
|
||||
{
|
||||
if (io.WantCaptureKeyboard) {
|
||||
release_held_keys(injection);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ImGuiKey k = ImGuiKey_NamedKey_BEGIN; k < ImGuiKey_NamedKey_END; k = static_cast<ImGuiKey>(k + 1))
|
||||
{
|
||||
} else {
|
||||
for (ImGuiKey k = ImGuiKey_NamedKey_BEGIN; k < ImGuiKey_NamedKey_END; k = static_cast<ImGuiKey>(k + 1)) {
|
||||
const int vk = imgui_key_to_vk(k);
|
||||
if (vk == 0)
|
||||
{
|
||||
if (vk == 0) {
|
||||
continue;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(k, false))
|
||||
{
|
||||
if (ImGui::IsKeyPressed(k, false)) {
|
||||
injection.push_mkb(MkbEvent{Mkb_KeyDown, static_cast<std::uint32_t>(vk), 0, 0});
|
||||
g_key_down[vk & 0xFF] = true;
|
||||
}
|
||||
if (ImGui::IsKeyReleased(k))
|
||||
{
|
||||
if (ImGui::IsKeyReleased(k)) {
|
||||
injection.push_mkb(MkbEvent{Mkb_KeyUp, static_cast<std::uint32_t>(vk), 0, 0});
|
||||
g_key_down[vk & 0xFF] = false;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < io.InputQueueCharacters.Size; ++i)
|
||||
{
|
||||
for (int i = 0; i < io.InputQueueCharacters.Size; ++i) {
|
||||
const ImWchar c = io.InputQueueCharacters[i];
|
||||
if (c != 0)
|
||||
{
|
||||
if (c != 0) {
|
||||
injection.push_mkb(MkbEvent{Mkb_Char, static_cast<std::uint32_t>(c), 0, 0});
|
||||
}
|
||||
}
|
||||
@@ -173,8 +199,7 @@ void forward_mkb_frame(InjectionPanel& injection, HWND host_hwnd, bool mirroring
|
||||
// --- Mouse (clicks + wheel only, and only while mirroring and ImGui isn't using the mouse) ---
|
||||
// When we're not forwarding the mouse, still release any button we hold (below), so it can't stick.
|
||||
const bool forwarding_mouse = mirroring && !io.WantCaptureMouse;
|
||||
if (!forwarding_mouse)
|
||||
{
|
||||
if (!forwarding_mouse) {
|
||||
release_held_mouse(injection);
|
||||
return;
|
||||
}
|
||||
@@ -187,15 +212,12 @@ void forward_mkb_frame(InjectionPanel& injection, HWND host_hwnd, bool mirroring
|
||||
m.host_y = static_cast<int>(io.MousePos.y);
|
||||
m.dst_w = host_client.right;
|
||||
m.dst_h = host_client.bottom;
|
||||
if (source_hooked)
|
||||
{
|
||||
if (source_hooked) {
|
||||
// Hooked capture mirrors the backbuffer (client area), no decorations.
|
||||
const VideoShareView v = injection.video_share();
|
||||
m.src_w = m.client_w = static_cast<int>(v.width);
|
||||
m.src_h = m.client_h = static_cast<int>(v.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// WGC captures the whole window; the client area sits at a decoration offset.
|
||||
RECT wr{}, cr{};
|
||||
POINT client_origin{0, 0};
|
||||
@@ -212,8 +234,7 @@ void forward_mkb_frame(InjectionPanel& injection, HWND host_hwnd, bool mirroring
|
||||
|
||||
int gx = 0, gy = 0;
|
||||
const bool on_game = map_host_to_game_client(m, gx, gy);
|
||||
if (on_game)
|
||||
{
|
||||
if (on_game) {
|
||||
g_last_gx = gx;
|
||||
g_last_gy = gy;
|
||||
}
|
||||
@@ -222,8 +243,7 @@ void forward_mkb_frame(InjectionPanel& injection, HWND host_hwnd, bool mirroring
|
||||
|
||||
for (int button = 0; button < 3; ++button) // 0=left, 1=right, 2=middle
|
||||
{
|
||||
if (on_game && ImGui::IsMouseClicked(button))
|
||||
{
|
||||
if (on_game && ImGui::IsMouseClicked(button)) {
|
||||
injection.push_mkb(MkbEvent{Mkb_MouseDown, static_cast<std::uint32_t>(button), mx, my});
|
||||
g_mouse_down[button] = true;
|
||||
}
|
||||
@@ -233,8 +253,7 @@ void forward_mkb_frame(InjectionPanel& injection, HWND host_hwnd, bool mirroring
|
||||
g_mouse_down[button] = false;
|
||||
}
|
||||
}
|
||||
if (on_game && io.MouseWheel != 0.0f)
|
||||
{
|
||||
if (on_game && io.MouseWheel != 0.0f) {
|
||||
const int delta = static_cast<int>(io.MouseWheel * WHEEL_DELTA);
|
||||
injection.push_mkb(MkbEvent{Mkb_Wheel, static_cast<std::uint32_t>(delta), mx, my});
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
class InjectionPanel;
|
||||
|
||||
|
||||
@@ -12,44 +12,38 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
struct MkbMapInput
|
||||
{
|
||||
int host_x = 0, host_y = 0; // mouse in host-window client pixels
|
||||
int dst_w = 0, dst_h = 0; // host window client size
|
||||
int src_w = 0, src_h = 0; // captured frame size (WGC=window, Hooked=backbuffer)
|
||||
struct MkbMapInput {
|
||||
int host_x = 0, host_y = 0; // mouse in host-window client pixels
|
||||
int dst_w = 0, dst_h = 0; // host window client size
|
||||
int src_w = 0, src_h = 0; // captured frame size (WGC=window, Hooked=backbuffer)
|
||||
int client_off_x = 0, client_off_y = 0; // client-area top-left within the frame
|
||||
int client_w = 0, client_h = 0; // game client size within the frame
|
||||
int client_w = 0, client_h = 0; // game client size within the frame
|
||||
};
|
||||
|
||||
// Returns true and writes gx,gy (game client px) if the point lands on the game's
|
||||
// client area; false if it falls on a letterbox bar or the window decorations.
|
||||
inline bool map_host_to_game_client(const MkbMapInput& in, int& gx, int& gy)
|
||||
{
|
||||
if (in.src_w <= 0 || in.src_h <= 0 || in.dst_w <= 0 || in.dst_h <= 0 || in.client_w <= 0 || in.client_h <= 0)
|
||||
{
|
||||
if (in.src_w <= 0 || in.src_h <= 0 || in.dst_w <= 0 || in.dst_h <= 0 || in.client_w <= 0 || in.client_h <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Invert the letterbox: the frame is fit (aspect-preserved) and centered in dst.
|
||||
const double scale =
|
||||
std::min(static_cast<double>(in.dst_w) / in.src_w, static_cast<double>(in.dst_h) / in.src_h);
|
||||
const double scale = std::min(static_cast<double>(in.dst_w) / in.src_w, static_cast<double>(in.dst_h) / in.src_h);
|
||||
const double ox = (in.dst_w - in.src_w * scale) * 0.5;
|
||||
const double oy = (in.dst_h - in.src_h * scale) * 0.5;
|
||||
|
||||
const double fx = (in.host_x - ox) / scale; // position in captured-frame pixels
|
||||
const double fy = (in.host_y - oy) / scale;
|
||||
if (fx < 0.0 || fy < 0.0 || fx >= in.src_w || fy >= in.src_h)
|
||||
{
|
||||
if (fx < 0.0 || fy < 0.0 || fx >= in.src_w || fy >= in.src_h) {
|
||||
return false; // on a letterbox bar
|
||||
}
|
||||
|
||||
const double cx = fx - in.client_off_x; // into client space
|
||||
const double cy = fy - in.client_off_y;
|
||||
if (cx < 0.0 || cy < 0.0 || cx >= in.client_w || cy >= in.client_h)
|
||||
{
|
||||
if (cx < 0.0 || cy < 0.0 || cx >= in.client_w || cy >= in.client_h) {
|
||||
return false; // on the window decorations
|
||||
}
|
||||
|
||||
|
||||
@@ -5,27 +5,22 @@
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
std::vector<ProcessEntry> list_processes()
|
||||
{
|
||||
std::vector<ProcessEntry> result;
|
||||
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snapshot == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (snapshot == INVALID_HANDLE_VALUE) {
|
||||
return result;
|
||||
}
|
||||
|
||||
PROCESSENTRY32W entry = {};
|
||||
entry.dwSize = sizeof(entry);
|
||||
if (Process32FirstW(snapshot, &entry))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (entry.th32ProcessID == 0)
|
||||
{
|
||||
if (Process32FirstW(snapshot, &entry)) {
|
||||
do {
|
||||
if (entry.th32ProcessID == 0) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(ProcessEntry{entry.th32ProcessID, entry.szExeFile});
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
struct ProcessEntry
|
||||
{
|
||||
struct ProcessEntry {
|
||||
unsigned long pid = 0;
|
||||
std::wstring exe_name; // image base name, e.g. "game.exe"
|
||||
};
|
||||
|
||||
@@ -7,14 +7,11 @@
|
||||
|
||||
#include "inject/process_list.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace {
|
||||
|
||||
struct EnumCtx
|
||||
{
|
||||
struct EnumCtx {
|
||||
std::vector<WindowEntry>* out;
|
||||
const std::unordered_map<unsigned long, std::wstring>* names;
|
||||
DWORD self_pid;
|
||||
@@ -25,23 +22,19 @@ BOOL CALLBACK enum_proc(HWND hwnd, LPARAM lparam)
|
||||
auto* ctx = reinterpret_cast<EnumCtx*>(lparam);
|
||||
|
||||
// Keep only "alt-tab" windows: visible, titled, root-owner, non-tool, not ours.
|
||||
if (!IsWindowVisible(hwnd) || GetAncestor(hwnd, GA_ROOTOWNER) != hwnd)
|
||||
{
|
||||
if (!IsWindowVisible(hwnd) || GetAncestor(hwnd, GA_ROOTOWNER) != hwnd) {
|
||||
return TRUE;
|
||||
}
|
||||
const int len = GetWindowTextLengthW(hwnd);
|
||||
if (len <= 0)
|
||||
{
|
||||
if (len <= 0) {
|
||||
return TRUE;
|
||||
}
|
||||
if ((GetWindowLongW(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) != 0)
|
||||
{
|
||||
if ((GetWindowLongW(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) != 0) {
|
||||
return TRUE;
|
||||
}
|
||||
DWORD pid = 0;
|
||||
GetWindowThreadProcessId(hwnd, &pid);
|
||||
if (pid == 0 || pid == ctx->self_pid)
|
||||
{
|
||||
if (pid == 0 || pid == ctx->self_pid) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -49,8 +42,7 @@ BOOL CALLBACK enum_proc(HWND hwnd, LPARAM lparam)
|
||||
GetWindowTextW(hwnd, title.data(), len + 1);
|
||||
|
||||
std::wstring exe;
|
||||
if (const auto it = ctx->names->find(pid); it != ctx->names->end())
|
||||
{
|
||||
if (const auto it = ctx->names->find(pid); it != ctx->names->end()) {
|
||||
exe = it->second;
|
||||
}
|
||||
ctx->out->push_back(WindowEntry{pid, hwnd, std::move(title), std::move(exe)});
|
||||
@@ -64,8 +56,7 @@ std::vector<WindowEntry> list_windows()
|
||||
// pid -> image name, so each window can show its owning process without a separate
|
||||
// OpenProcess per window.
|
||||
std::unordered_map<unsigned long, std::wstring> names;
|
||||
for (const ProcessEntry& p : list_processes())
|
||||
{
|
||||
for (const ProcessEntry& p : list_processes()) {
|
||||
names.emplace(p.pid, p.exe_name);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,13 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace coop
|
||||
{
|
||||
namespace coop {
|
||||
|
||||
struct WindowEntry
|
||||
{
|
||||
unsigned long pid = 0; // owning process id
|
||||
void* hwnd = nullptr; // HWND (opaque here to keep windows.h out of the header)
|
||||
std::wstring title; // window caption
|
||||
std::wstring exe_name; // owning process image base name, e.g. "game.exe"
|
||||
struct WindowEntry {
|
||||
unsigned long pid = 0; // owning process id
|
||||
void* hwnd = nullptr; // HWND (opaque here to keep windows.h out of the header)
|
||||
std::wstring title; // window caption
|
||||
std::wstring exe_name; // owning process image base name, e.g. "game.exe"
|
||||
};
|
||||
|
||||
// Snapshot of the visible, titled, non-tool top-level (alt-tab-style) windows, with
|
||||
|
||||
Reference in New Issue
Block a user