|
|
|
@@ -0,0 +1,305 @@
|
|
|
|
|
// BSO Loader - dinput8.dll-Proxy (wie Unity Doorstop bei MSCO, nur fuer die Vision Engine).
|
|
|
|
|
//
|
|
|
|
|
// Game.vPlugin importiert DINPUT8.dll, Windows sucht sie zuerst im Spielordner. Diese DLL reicht alles an die echte
|
|
|
|
|
// dinput8.dll aus System32 weiter und
|
|
|
|
|
// 1. startet beim ersten DirectInput8Create .NET (hostfxr) und ruft BSO.Bootstrap.Start in BSO\BSO.dll auf,
|
|
|
|
|
// 2. haengt sich in GetDeviceState/GetDeviceData der Tastatur: das Spiel fragt die Tastatur einmal pro Bild im
|
|
|
|
|
// Hauptthread ab - dort wird BSO.Bootstrap.OnFrame aufgerufen. Kein Spielcode wird gepatcht.
|
|
|
|
|
// Protokoll: <Spielordner>\BSO\BSO_Loader.txt (schreibt auch die Mod ueber HostApi.Log).
|
|
|
|
|
|
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
|
#define NETHOST_USE_AS_STATIC
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <nethost.h>
|
|
|
|
|
#include <hostfxr.h>
|
|
|
|
|
#include <coreclr_delegates.h>
|
|
|
|
|
|
|
|
|
|
static HMODULE self, real;
|
|
|
|
|
static FILE* logFile;
|
|
|
|
|
static CRITICAL_SECTION logLock;
|
|
|
|
|
static std::wstring bsoDir;
|
|
|
|
|
|
|
|
|
|
static void LogLine(const char* text)
|
|
|
|
|
{
|
|
|
|
|
EnterCriticalSection(&logLock);
|
|
|
|
|
if (!logFile)
|
|
|
|
|
{
|
|
|
|
|
CreateDirectoryW(bsoDir.c_str(), nullptr);
|
|
|
|
|
logFile = _wfsopen((bsoDir + L"\\BSO_Loader.txt").c_str(), L"w", _SH_DENYWR);
|
|
|
|
|
}
|
|
|
|
|
if (logFile)
|
|
|
|
|
{
|
|
|
|
|
SYSTEMTIME t;
|
|
|
|
|
GetLocalTime(&t);
|
|
|
|
|
fprintf(logFile, "%02d:%02d:%02d %s\n", t.wHour, t.wMinute, t.wSecond, text);
|
|
|
|
|
fflush(logFile);
|
|
|
|
|
}
|
|
|
|
|
LeaveCriticalSection(&logLock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void Log(const char* fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
char buf[1024];
|
|
|
|
|
va_list args;
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
|
vsnprintf(buf, sizeof(buf), fmt, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
LogLine(buf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------
|
|
|
|
|
// .NET starten
|
|
|
|
|
|
|
|
|
|
/// <summary>Was die Mod vom Loader bekommt (Gegenstueck: BSO.HostApi).</summary>
|
|
|
|
|
struct HostApi
|
|
|
|
|
{
|
|
|
|
|
void (*log)(const char* utf8);
|
|
|
|
|
const wchar_t* gameDir;
|
|
|
|
|
const wchar_t* bsoDir;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
typedef void (CORECLR_DELEGATE_CALLTYPE* StartFn)(HostApi*);
|
|
|
|
|
typedef void (CORECLR_DELEGATE_CALLTYPE* FrameFn)();
|
|
|
|
|
|
|
|
|
|
static HostApi hostApi;
|
|
|
|
|
static std::wstring gameDir;
|
|
|
|
|
static FrameFn onFrame;
|
|
|
|
|
static bool managedTried;
|
|
|
|
|
|
|
|
|
|
static void StartManaged()
|
|
|
|
|
{
|
|
|
|
|
if (managedTried) return;
|
|
|
|
|
managedTried = true;
|
|
|
|
|
|
|
|
|
|
std::wstring assembly = bsoDir + L"\\BSO.dll";
|
|
|
|
|
std::wstring config = bsoDir + L"\\BSO.runtimeconfig.json";
|
|
|
|
|
if (GetFileAttributesW(assembly.c_str()) == INVALID_FILE_ATTRIBUTES)
|
|
|
|
|
{
|
|
|
|
|
Log("BSO\\BSO.dll fehlt - nur Proxy, keine Mod");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char_t fxrPath[MAX_PATH];
|
|
|
|
|
size_t fxrSize = MAX_PATH;
|
|
|
|
|
int rc = get_hostfxr_path(fxrPath, &fxrSize, nullptr);
|
|
|
|
|
if (rc != 0) { Log(".NET nicht gefunden (get_hostfxr_path 0x%08X) - .NET 10 Runtime installieren", rc); return; }
|
|
|
|
|
|
|
|
|
|
HMODULE fxr = LoadLibraryW(fxrPath);
|
|
|
|
|
auto init = (hostfxr_initialize_for_runtime_config_fn)GetProcAddress(fxr, "hostfxr_initialize_for_runtime_config");
|
|
|
|
|
auto getDelegate = (hostfxr_get_runtime_delegate_fn)GetProcAddress(fxr, "hostfxr_get_runtime_delegate");
|
|
|
|
|
auto close = (hostfxr_close_fn)GetProcAddress(fxr, "hostfxr_close");
|
|
|
|
|
if (!init || !getDelegate || !close) { Log("hostfxr unvollstaendig"); return; }
|
|
|
|
|
|
|
|
|
|
hostfxr_handle ctx = nullptr;
|
|
|
|
|
rc = init(config.c_str(), nullptr, &ctx);
|
|
|
|
|
if (rc < 0 || !ctx) { Log("hostfxr_initialize_for_runtime_config 0x%08X", rc); if (ctx) close(ctx); return; }
|
|
|
|
|
|
|
|
|
|
load_assembly_and_get_function_pointer_fn load = nullptr;
|
|
|
|
|
rc = getDelegate(ctx, hdt_load_assembly_and_get_function_pointer, (void**)&load);
|
|
|
|
|
close(ctx);
|
|
|
|
|
if (rc != 0 || !load) { Log("hostfxr_get_runtime_delegate 0x%08X", rc); return; }
|
|
|
|
|
|
|
|
|
|
StartFn start = nullptr;
|
|
|
|
|
rc = load(assembly.c_str(), L"BSO.Bootstrap, BSO", L"Start", UNMANAGEDCALLERSONLY_METHOD, nullptr, (void**)&start);
|
|
|
|
|
if (rc != 0 || !start) { Log("BSO.Bootstrap.Start nicht gefunden (0x%08X)", rc); return; }
|
|
|
|
|
rc = load(assembly.c_str(), L"BSO.Bootstrap, BSO", L"OnFrame", UNMANAGEDCALLERSONLY_METHOD, nullptr, (void**)&onFrame);
|
|
|
|
|
if (rc != 0) { Log("BSO.Bootstrap.OnFrame nicht gefunden (0x%08X)", rc); onFrame = nullptr; }
|
|
|
|
|
|
|
|
|
|
hostApi.log = LogLine;
|
|
|
|
|
hostApi.gameDir = gameDir.c_str();
|
|
|
|
|
hostApi.bsoDir = bsoDir.c_str();
|
|
|
|
|
start(&hostApi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------
|
|
|
|
|
// Bild-Takt ueber die Tastatur-Abfrage
|
|
|
|
|
|
|
|
|
|
static const GUID GUID_SysKeyboard_ = { 0x6F1D2B61, 0xD5A0, 0x11CF, { 0xBF, 0xC7, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00 } };
|
|
|
|
|
|
|
|
|
|
// Feste Plaetze in den COM-vtables (IDirectInput8 / IDirectInputDevice8, A und W gleich)
|
|
|
|
|
enum { SlotCreateDevice = 3, SlotGetDeviceState = 9, SlotGetDeviceData = 10 };
|
|
|
|
|
|
|
|
|
|
typedef HRESULT (WINAPI* CreateDeviceFn)(void* self, REFGUID guid, void** device, void* outer);
|
|
|
|
|
typedef HRESULT (WINAPI* GetDeviceStateFn)(void* self, DWORD size, void* data);
|
|
|
|
|
typedef HRESULT (WINAPI* GetDeviceDataFn)(void* self, DWORD size, void* data, DWORD* count, DWORD flags);
|
|
|
|
|
|
|
|
|
|
/// <summary>Originalfunktionen je vtable (A- und W-Schnittstellen haben eigene vtables).</summary>
|
|
|
|
|
struct Patched { void** vtable; void* original[3]; };
|
|
|
|
|
static Patched patched[16];
|
|
|
|
|
static int patchedCount;
|
|
|
|
|
|
|
|
|
|
static Patched* Find(void* object)
|
|
|
|
|
{
|
|
|
|
|
void** vtable = *(void***)object;
|
|
|
|
|
for (int i = 0; i < patchedCount; i++) if (patched[i].vtable == vtable) return &patched[i];
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Ersetzt Eintraege einer vtable (liegt schreibgeschuetzt in dinput8.dll) und merkt sich die Originale.</summary>
|
|
|
|
|
static Patched* Patch(void* object, const int* slots, void* const* hooks, int n)
|
|
|
|
|
{
|
|
|
|
|
if (Patched* p = Find(object)) return p;
|
|
|
|
|
if (patchedCount == 16) return nullptr;
|
|
|
|
|
Patched* p = &patched[patchedCount++];
|
|
|
|
|
p->vtable = *(void***)object;
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
|
{
|
|
|
|
|
void** entry = &p->vtable[slots[i]];
|
|
|
|
|
p->original[i] = *entry;
|
|
|
|
|
DWORD old;
|
|
|
|
|
VirtualProtect(entry, sizeof(void*), PAGE_READWRITE, &old);
|
|
|
|
|
*entry = hooks[i];
|
|
|
|
|
VirtualProtect(entry, sizeof(void*), old, &old);
|
|
|
|
|
}
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void* keyboard;
|
|
|
|
|
static DWORD mainThread; // Thread, der Game.vPlugin (und damit uns) geladen hat
|
|
|
|
|
static int frameSlot; // 0 = noch offen, sonst SlotGetDeviceState/SlotGetDeviceData - nur einer zaehlt
|
|
|
|
|
static DWORD statStart, statMain, statOther;
|
|
|
|
|
static bool statDone;
|
|
|
|
|
|
|
|
|
|
// Das Spiel fragt die Tastatur mehrmals pro Bild ab (1. Lauf: ~7400/s). Die Mod erkennt ein neues Bild selbst an der
|
|
|
|
|
// Engine-Uhr - hier wird nur sichergestellt, dass sie ausschliesslich im Hauptthread laeuft.
|
|
|
|
|
static void Frame(void* device, int slot)
|
|
|
|
|
{
|
|
|
|
|
if (device != keyboard) return;
|
|
|
|
|
if (!frameSlot)
|
|
|
|
|
{
|
|
|
|
|
frameSlot = slot;
|
|
|
|
|
Log("Takt ueber Tastatur-%s", slot == SlotGetDeviceState ? "GetDeviceState" : "GetDeviceData");
|
|
|
|
|
statStart = GetTickCount();
|
|
|
|
|
}
|
|
|
|
|
if (slot != frameSlot) return;
|
|
|
|
|
|
|
|
|
|
bool main = GetCurrentThreadId() == mainThread;
|
|
|
|
|
if (!statDone)
|
|
|
|
|
{
|
|
|
|
|
if (main) statMain++; else statOther++;
|
|
|
|
|
if (GetTickCount() - statStart >= 5000)
|
|
|
|
|
{
|
|
|
|
|
statDone = true;
|
|
|
|
|
Log("Takt: in 5 s %lu Aufrufe im Hauptthread, %lu in anderen Threads", statMain, statOther);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (main && onFrame) onFrame();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static HRESULT WINAPI HookGetDeviceState(void* self, DWORD size, void* data)
|
|
|
|
|
{
|
|
|
|
|
HRESULT hr = ((GetDeviceStateFn)Find(self)->original[0])(self, size, data);
|
|
|
|
|
Frame(self, SlotGetDeviceState);
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static HRESULT WINAPI HookGetDeviceData(void* self, DWORD size, void* data, DWORD* count, DWORD flags)
|
|
|
|
|
{
|
|
|
|
|
HRESULT hr = ((GetDeviceDataFn)Find(self)->original[1])(self, size, data, count, flags);
|
|
|
|
|
Frame(self, SlotGetDeviceData);
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static HRESULT WINAPI HookCreateDevice(void* self, REFGUID guid, void** device, void* outer)
|
|
|
|
|
{
|
|
|
|
|
HRESULT hr = ((CreateDeviceFn)Find(self)->original[0])(self, guid, device, outer);
|
|
|
|
|
if (SUCCEEDED(hr) && device && *device && IsEqualGUID(guid, GUID_SysKeyboard_))
|
|
|
|
|
{
|
|
|
|
|
keyboard = *device;
|
|
|
|
|
static const int slots[] = { SlotGetDeviceState, SlotGetDeviceData };
|
|
|
|
|
static void* const hooks[] = { (void*)HookGetDeviceState, (void*)HookGetDeviceData };
|
|
|
|
|
Patch(*device, slots, hooks, 2);
|
|
|
|
|
Log("Tastatur-Geraet erstellt");
|
|
|
|
|
}
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------
|
|
|
|
|
// Exporte der echten dinput8.dll
|
|
|
|
|
|
|
|
|
|
static FARPROC Real(const char* name)
|
|
|
|
|
{
|
|
|
|
|
if (!real)
|
|
|
|
|
{
|
|
|
|
|
wchar_t path[MAX_PATH];
|
|
|
|
|
GetSystemDirectoryW(path, MAX_PATH);
|
|
|
|
|
wcscat_s(path, L"\\dinput8.dll");
|
|
|
|
|
real = LoadLibraryW(path);
|
|
|
|
|
if (!real) Log("Echte dinput8.dll nicht ladbar (%lu)", GetLastError());
|
|
|
|
|
}
|
|
|
|
|
return real ? GetProcAddress(real, name) : nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" HRESULT WINAPI Proxy_DirectInput8Create(HINSTANCE inst, DWORD version, REFIID iid, void** out, void* outer)
|
|
|
|
|
{
|
|
|
|
|
typedef HRESULT (WINAPI* Fn)(HINSTANCE, DWORD, REFIID, void**, void*);
|
|
|
|
|
Fn fn = (Fn)Real("DirectInput8Create");
|
|
|
|
|
if (!fn) return E_FAIL;
|
|
|
|
|
HRESULT hr = fn(inst, version, iid, out, outer);
|
|
|
|
|
|
|
|
|
|
StartManaged();
|
|
|
|
|
if (SUCCEEDED(hr) && out && *out)
|
|
|
|
|
{
|
|
|
|
|
static const int slots[] = { SlotCreateDevice };
|
|
|
|
|
static void* const hooks[] = { (void*)HookCreateDevice };
|
|
|
|
|
Patch(*out, slots, hooks, 1);
|
|
|
|
|
}
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" HRESULT WINAPI Proxy_DllCanUnloadNow()
|
|
|
|
|
{
|
|
|
|
|
typedef HRESULT (WINAPI* Fn)();
|
|
|
|
|
Fn fn = (Fn)Real("DllCanUnloadNow");
|
|
|
|
|
return fn ? fn() : S_FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" HRESULT WINAPI Proxy_DllGetClassObject(REFCLSID clsid, REFIID iid, void** out)
|
|
|
|
|
{
|
|
|
|
|
typedef HRESULT (WINAPI* Fn)(REFCLSID, REFIID, void**);
|
|
|
|
|
Fn fn = (Fn)Real("DllGetClassObject");
|
|
|
|
|
return fn ? fn(clsid, iid, out) : CLASS_E_CLASSNOTAVAILABLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" HRESULT WINAPI Proxy_DllRegisterServer()
|
|
|
|
|
{
|
|
|
|
|
typedef HRESULT (WINAPI* Fn)();
|
|
|
|
|
Fn fn = (Fn)Real("DllRegisterServer");
|
|
|
|
|
return fn ? fn() : E_FAIL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" HRESULT WINAPI Proxy_DllUnregisterServer()
|
|
|
|
|
{
|
|
|
|
|
typedef HRESULT (WINAPI* Fn)();
|
|
|
|
|
Fn fn = (Fn)Real("DllUnregisterServer");
|
|
|
|
|
return fn ? fn() : E_FAIL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" const void* WINAPI Proxy_GetdfDIJoystick()
|
|
|
|
|
{
|
|
|
|
|
typedef const void* (WINAPI* Fn)();
|
|
|
|
|
Fn fn = (Fn)Real("GetdfDIJoystick");
|
|
|
|
|
return fn ? fn() : nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, void*)
|
|
|
|
|
{
|
|
|
|
|
if (reason == DLL_PROCESS_ATTACH)
|
|
|
|
|
{
|
|
|
|
|
// Unter dem Loader-Lock nur Pfade merken - .NET startet erst im ersten DirectInput8Create
|
|
|
|
|
self = module;
|
|
|
|
|
mainThread = GetCurrentThreadId();
|
|
|
|
|
DisableThreadLibraryCalls(module);
|
|
|
|
|
InitializeCriticalSection(&logLock);
|
|
|
|
|
wchar_t path[MAX_PATH];
|
|
|
|
|
GetModuleFileNameW(module, path, MAX_PATH);
|
|
|
|
|
gameDir = path;
|
|
|
|
|
gameDir = gameDir.substr(0, gameDir.find_last_of(L'\\'));
|
|
|
|
|
bsoDir = gameDir + L"\\BSO";
|
|
|
|
|
Log("BSO Loader geladen aus %ls", path);
|
|
|
|
|
}
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|