DS2Revival/mods/common/vreg.h
2026-10-23 15:37:43 -04:00

110 lines
4.9 KiB
C++

// Virtual registry keys, shared by several plugins.
//
// Steam installs have none of the retail registry keys. Plugins serve the values they need themselves: each
// plugin hooks the registry imports (the hooks chain through the exe's import table) and answers only the
// values it owns. A key that is missing for real opens as a fixed fake handle that every plugin recognises, so
// values served by different plugins (DS2Multiplayer: CD key; DS2BrokenWorld: install paths) share one key.
// A real key always wins: its values are never touched.
#pragma once
#include "patch.h"
namespace ds2::vreg {
// HKLM\Software\Microsoft\Microsoft Games\DungeonSiege2 (retail DS2 install key)
const HKEY kDs2Key = reinterpret_cast<HKEY>(static_cast<ULONG_PTR>(0x7D520001));
// HKLM\Software\2K Games\Dungeon Siege 2 Broken World (the expansion's install key)
const HKEY kBwKey = reinterpret_cast<HKEY>(static_cast<ULONG_PTR>(0x7D520002));
constexpr char kDs2Path[] = "Software\\Microsoft\\Microsoft Games\\DungeonSiege2";
constexpr char kBwPath[] = "Software\\2K Games\\Dungeon Siege 2 Broken World";
// Answers a value of a fake key, or ERROR_FILE_NOT_FOUND when it is not this plugin's.
using Server = LSTATUS (*)(HKEY key, const char* name, LPDWORD type, LPBYTE data, LPDWORD size);
// Copies a value out RegQueryValueEx-style (size query, ERROR_MORE_DATA).
inline LSTATUS Serve(DWORD type, const void* data, DWORD size, LPDWORD outType, LPBYTE out, LPDWORD outSize) {
if (outType) *outType = type;
if (!outSize) return out ? ERROR_INVALID_PARAMETER : ERROR_SUCCESS;
DWORD cap = *outSize;
*outSize = size;
if (!out) return ERROR_SUCCESS;
if (cap < size) return ERROR_MORE_DATA;
std::memcpy(out, data, size);
return ERROR_SUCCESS;
}
inline LSTATUS ServeString(const char* s, LPDWORD type, LPBYTE data, LPDWORD size) {
return Serve(REG_SZ, s, static_cast<DWORD>(std::strlen(s) + 1), type, data, size);
}
namespace detail {
using RegOpenKeyExA_t = LSTATUS(WINAPI*)(HKEY, LPCSTR, DWORD, REGSAM, PHKEY);
using RegOpenKeyA_t = LSTATUS(WINAPI*)(HKEY, LPCSTR, PHKEY);
using RegQueryValueExA_t = LSTATUS(WINAPI*)(HKEY, LPCSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD);
using RegCloseKey_t = LSTATUS(WINAPI*)(HKEY);
struct State {
Server serve = nullptr;
bool ds2 = false, bw = false; // which keys this plugin fakes
RegOpenKeyExA_t openEx = nullptr;
RegOpenKeyA_t open = nullptr;
RegQueryValueExA_t query = nullptr;
RegCloseKey_t close = nullptr;
};
inline State& state() {
static State s;
return s;
}
inline bool IsFake(HKEY k) { return k == kDs2Key || k == kBwKey; }
inline LSTATUS Fake(LSTATUS r, HKEY root, LPCSTR sub, PHKEY out) {
if (r == ERROR_SUCCESS || root != HKEY_LOCAL_MACHINE || !sub || !out) return r;
if (state().ds2 && _stricmp(sub, kDs2Path) == 0)
*out = kDs2Key;
else if (state().bw && _stricmp(sub, kBwPath) == 0)
*out = kBwKey;
else
return r;
return ERROR_SUCCESS;
}
inline LSTATUS WINAPI OpenEx(HKEY root, LPCSTR sub, DWORD opt, REGSAM sam, PHKEY out) {
return Fake(state().openEx(root, sub, opt, sam, out), root, sub, out);
}
inline LSTATUS WINAPI Open(HKEY root, LPCSTR sub, PHKEY out) { // the older API (Broken World imports it)
return Fake(state().open(root, sub, out), root, sub, out);
}
inline LSTATUS WINAPI Query(HKEY key, LPCSTR name, LPDWORD res, LPDWORD type, LPBYTE data, LPDWORD size) {
if (!IsFake(key)) return state().query(key, name, res, type, data, size);
LSTATUS r = state().serve(key, name ? name : "", type, data, size);
if (r != ERROR_FILE_NOT_FOUND) return r;
r = state().query(key, name, res, type, data, size); // another plugin's value, or the real API
return r == ERROR_INVALID_HANDLE ? ERROR_FILE_NOT_FOUND : r;
}
inline LSTATUS WINAPI Close(HKEY key) { return IsFake(key) ? ERROR_SUCCESS : state().close(key); }
} // namespace detail
// Hook the registry imports; `ds2Key` / `bwKey` choose which missing keys this plugin fakes.
inline void Install(Server serve, bool ds2Key, bool bwKey, const char* who) {
auto& s = detail::state();
s.serve = serve;
s.ds2 = ds2Key;
s.bw = bwKey;
char what[96];
sprintf_s(what, "RegOpenKeyExA (%s)", who);
s.openEx = static_cast<detail::RegOpenKeyExA_t>(HookImport("ADVAPI32.dll", "RegOpenKeyExA", reinterpret_cast<void*>(detail::OpenEx), what));
sprintf_s(what, "RegQueryValueExA (%s)", who);
s.query = static_cast<detail::RegQueryValueExA_t>(HookImport("ADVAPI32.dll", "RegQueryValueExA", reinterpret_cast<void*>(detail::Query), what));
sprintf_s(what, "RegCloseKey (%s)", who);
s.close = static_cast<detail::RegCloseKey_t>(HookImport("ADVAPI32.dll", "RegCloseKey", reinterpret_cast<void*>(detail::Close), what));
if (ImportSlot("ADVAPI32.dll", "RegOpenKeyA")) {
sprintf_s(what, "RegOpenKeyA (%s)", who);
s.open = static_cast<detail::RegOpenKeyA_t>(HookImport("ADVAPI32.dll", "RegOpenKeyA", reinterpret_cast<void*>(detail::Open), what));
}
}
} // namespace ds2::vreg