Phase 1a: input forwarding via DLL injection + XInput hook
The host can now inject coop_hook.dll into a running game and forward controller state to it over shared memory, so the game reads the host's (eventually the guest's) input and nothing else. - hook/: coop_hook.dll. DllMain spawns a worker that opens the shared-memory channel (named by the game's pid) and installs SafetyHook inline hooks on XInputGetState/GetStateEx/GetCapabilities/SetState. Detours synthesize state from shared memory; unmanaged slots report disconnected, hiding physical pads. - host/: process picker (Toolhelp32), CreateRemoteThread(LoadLibraryW) injector with an IsWow64Process2 bitness guard, IPC server publishing pads each frame, and an ImGui Injection panel wiring it together. - tests/: hook_selftest exercises the IPC seqlock + hook detours in-process (no game/controller needed); passes. Build: SafetyHook wired in (COOP_BUILD_HOOK=ON), Zydis via FetchContent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
174
host/src/injection_panel.cpp
Normal file
174
host/src/injection_panel.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "injection_panel.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "inject/injector.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
const ImVec4 kGreen(0.4f, 1.0f, 0.4f, 1.0f);
|
||||
const ImVec4 kRed(1.0f, 0.45f, 0.4f, 1.0f);
|
||||
const ImVec4 kGrey(0.7f, 0.7f, 0.7f, 1.0f);
|
||||
|
||||
std::string narrow(const std::wstring& w)
|
||||
{
|
||||
if (w.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const int len = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), nullptr, 0, nullptr, nullptr);
|
||||
std::string out(static_cast<std::size_t>(len), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), out.data(), len, nullptr, nullptr);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool contains_ci(const std::wstring& haystack, const char* needle_utf8)
|
||||
{
|
||||
if (needle_utf8 == nullptr || needle_utf8[0] == '\0')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const std::string hay = narrow(haystack);
|
||||
std::string h = hay, n = needle_utf8;
|
||||
for (char& c : h)
|
||||
{
|
||||
c = static_cast<char>(::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
for (char& c : n)
|
||||
{
|
||||
c = static_cast<char>(::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
return h.find(n) != std::string::npos;
|
||||
}
|
||||
|
||||
// Absolute path to coop_hook.dll, assumed to sit next to the host executable.
|
||||
std::wstring hook_dll_path()
|
||||
{
|
||||
wchar_t buffer[MAX_PATH] = {};
|
||||
const DWORD len = GetModuleFileNameW(nullptr, buffer, MAX_PATH);
|
||||
std::wstring path(buffer, len);
|
||||
const std::size_t slash = path.find_last_of(L"\\/");
|
||||
if (slash != std::wstring::npos)
|
||||
{
|
||||
path.resize(slash + 1);
|
||||
}
|
||||
path += L"coop_hook.dll";
|
||||
return path;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
InjectionPanel::InjectionPanel()
|
||||
{
|
||||
refresh_processes();
|
||||
}
|
||||
|
||||
void InjectionPanel::refresh_processes()
|
||||
{
|
||||
processes_ = list_processes();
|
||||
}
|
||||
|
||||
void InjectionPanel::inject_selected()
|
||||
{
|
||||
if (selected_pid_ == 0)
|
||||
{
|
||||
status_ = "Select a target process first.";
|
||||
status_color_ = kRed;
|
||||
return;
|
||||
}
|
||||
|
||||
// Bring up the shared-memory channel before injecting so the hook finds it
|
||||
// immediately on load.
|
||||
if (!server_.start(selected_pid_))
|
||||
{
|
||||
status_ = "Failed to create shared memory.";
|
||||
status_color_ = kRed;
|
||||
return;
|
||||
}
|
||||
|
||||
const InjectResult result = inject_dll(selected_pid_, hook_dll_path());
|
||||
if (result.status == InjectStatus::Ok)
|
||||
{
|
||||
status_ = "Injected into " + narrow(selected_name_) + " (pid " + std::to_string(selected_pid_) + ").";
|
||||
status_color_ = kGreen;
|
||||
}
|
||||
else
|
||||
{
|
||||
server_.stop();
|
||||
status_ = std::string("Injection failed: ") + to_string(result.status);
|
||||
if (result.os_error != 0)
|
||||
{
|
||||
status_ += " [err " + std::to_string(result.os_error) + "]";
|
||||
}
|
||||
status_color_ = kRed;
|
||||
}
|
||||
}
|
||||
|
||||
void InjectionPanel::draw()
|
||||
{
|
||||
ImGui::SetNextWindowPos(ImVec2(24, 360), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(420, 380), ImGuiCond_FirstUseEver);
|
||||
ImGui::Begin("Injection");
|
||||
|
||||
if (server_.running())
|
||||
{
|
||||
ImGui::TextColored(kGreen, "Forwarding input to pid %lu", server_.target_pid());
|
||||
if (ImGui::Button("Stop forwarding"))
|
||||
{
|
||||
server_.stop();
|
||||
status_ = "Stopped.";
|
||||
status_color_ = kGrey;
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted("Target process");
|
||||
if (ImGui::Button("Refresh"))
|
||||
{
|
||||
refresh_processes();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(-1.0f);
|
||||
ImGui::InputTextWithHint("##filter", "filter by name...", filter_, sizeof(filter_));
|
||||
|
||||
if (ImGui::BeginListBox("##processes", ImVec2(-1.0f, 200.0f)))
|
||||
{
|
||||
for (const ProcessEntry& entry : processes_)
|
||||
{
|
||||
if (!contains_ci(entry.exe_name, filter_))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const bool selected = entry.pid == selected_pid_;
|
||||
char label[300];
|
||||
snprintf(label, sizeof(label), "%-40s %lu", narrow(entry.exe_name).c_str(), entry.pid);
|
||||
if (ImGui::Selectable(label, selected))
|
||||
{
|
||||
selected_pid_ = entry.pid;
|
||||
selected_name_ = entry.exe_name;
|
||||
}
|
||||
}
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
|
||||
const bool can_inject = selected_pid_ != 0;
|
||||
ImGui::BeginDisabled(!can_inject);
|
||||
if (ImGui::Button("Inject & Connect", ImVec2(-1.0f, 0.0f)))
|
||||
{
|
||||
inject_selected();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (!status_.empty())
|
||||
{
|
||||
ImGui::TextColored(status_color_, "%s", status_.c_str());
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace coop
|
||||
Reference in New Issue
Block a user