Host helper register_vk_layer/unregister_vk_layer registers coop_vk_layer's manifest as a per-user (HKCU, no admin) implicit Vulkan layer and writes the scoping file (%TEMP%\coop_vk_target.txt) naming the target game's exe, so the layer captures only that game and stays inert for every other Vulkan app. The Injection panel gains an opt-in "Set up Vulkan layer" checkbox (next to Auto re-attach, which the too-late banner points at); it registers on tick, unregisters on untick, and the panel destructor unregisters on host exit so no stale registration is left behind. Verified live: a registry-registered layer is loaded by the loader for a fresh vk launch (no env vars) and the scoping matches (active=1), then cleans up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
80 lines
2.5 KiB
C++
80 lines
2.5 KiB
C++
#include "vk_layer_setup.hpp"
|
|
|
|
#include <windows.h>
|
|
|
|
#include "coop/tool_paths.hpp"
|
|
|
|
namespace coop
|
|
{
|
|
namespace
|
|
{
|
|
// The Vulkan loader's per-user implicit-layer registry list. Each value is a manifest path; its
|
|
// DWORD data 0 = enabled.
|
|
constexpr const wchar_t* kImplicitLayersKey = L"SOFTWARE\\Khronos\\Vulkan\\ImplicitLayers";
|
|
|
|
std::wstring manifest_path()
|
|
{
|
|
return exe_directory() + L"coop_vk_layer.json"; // staged next to coop_host.exe
|
|
}
|
|
|
|
std::wstring scoping_file()
|
|
{
|
|
wchar_t dir[MAX_PATH] = {};
|
|
const DWORD n = GetTempPathW(MAX_PATH, dir);
|
|
return (n != 0 && n < MAX_PATH) ? std::wstring(dir) + L"coop_vk_target.txt" : std::wstring();
|
|
}
|
|
} // namespace
|
|
|
|
bool register_vk_layer(const std::wstring& target_image)
|
|
{
|
|
// Write the scoping file (target image basename, UTF-8) the layer checks against its own image.
|
|
const std::wstring sf = scoping_file();
|
|
if (!sf.empty())
|
|
{
|
|
const std::size_t slash = target_image.find_last_of(L"\\/");
|
|
const std::wstring base = slash == std::wstring::npos ? target_image : target_image.substr(slash + 1);
|
|
const int n = WideCharToMultiByte(CP_UTF8, 0, base.c_str(), static_cast<int>(base.size()), nullptr, 0,
|
|
nullptr, nullptr);
|
|
std::string utf8(static_cast<std::size_t>(n), '\0');
|
|
WideCharToMultiByte(CP_UTF8, 0, base.c_str(), static_cast<int>(base.size()), utf8.data(), n, nullptr,
|
|
nullptr);
|
|
HANDLE f = CreateFileW(sf.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
|
if (f != INVALID_HANDLE_VALUE)
|
|
{
|
|
DWORD written = 0;
|
|
WriteFile(f, utf8.data(), static_cast<DWORD>(utf8.size()), &written, nullptr);
|
|
CloseHandle(f);
|
|
}
|
|
}
|
|
|
|
HKEY key = nullptr;
|
|
if (RegCreateKeyExW(HKEY_CURRENT_USER, kImplicitLayersKey, 0, nullptr, 0, KEY_SET_VALUE, nullptr, &key,
|
|
nullptr) != ERROR_SUCCESS)
|
|
{
|
|
return false;
|
|
}
|
|
const std::wstring mp = manifest_path();
|
|
DWORD enabled = 0; // 0 = enabled, per the loader's convention
|
|
const LONG r = RegSetValueExW(key, mp.c_str(), 0, REG_DWORD, reinterpret_cast<const BYTE*>(&enabled),
|
|
sizeof(enabled));
|
|
RegCloseKey(key);
|
|
return r == ERROR_SUCCESS;
|
|
}
|
|
|
|
void unregister_vk_layer()
|
|
{
|
|
HKEY key = nullptr;
|
|
if (RegOpenKeyExW(HKEY_CURRENT_USER, kImplicitLayersKey, 0, KEY_SET_VALUE, &key) == ERROR_SUCCESS)
|
|
{
|
|
RegDeleteValueW(key, manifest_path().c_str());
|
|
RegCloseKey(key);
|
|
}
|
|
const std::wstring sf = scoping_file();
|
|
if (!sf.empty())
|
|
{
|
|
DeleteFileW(sf.c_str());
|
|
}
|
|
}
|
|
|
|
} // namespace coop
|