From 39c9a2f4cd17f34bbdbd11d0c3b7739eecd4f653 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 27 May 2026 19:16:21 -0500 Subject: [PATCH] Sample Interposer plugin --- ...ommander.Interposer.Plugin.Example.vcxproj | 165 ++++++++++++++++++ .../src/dllmain.cpp | 124 +++++++++++++ .../src/resource.rc | 30 ++++ 3 files changed, 319 insertions(+) create mode 100644 LANCommander.Interposer.Plugin.Example/LANCommander.Interposer.Plugin.Example.vcxproj create mode 100644 LANCommander.Interposer.Plugin.Example/src/dllmain.cpp create mode 100644 LANCommander.Interposer.Plugin.Example/src/resource.rc diff --git a/LANCommander.Interposer.Plugin.Example/LANCommander.Interposer.Plugin.Example.vcxproj b/LANCommander.Interposer.Plugin.Example/LANCommander.Interposer.Plugin.Example.vcxproj new file mode 100644 index 0000000..da3896c --- /dev/null +++ b/LANCommander.Interposer.Plugin.Example/LANCommander.Interposer.Plugin.Example.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + 18.0 + Win32Proj + {a1b2c3d4-e5f6-7890-abcd-ef1234567890} + LANCommanderInterposerPluginExample + 10.0 + + + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)Release\Example\x86\ + + + $(SolutionDir)Release\Example\x64\ + + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;NOMINMAX;UNICODE;_UNICODE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Windows + true + + + + + Level3 + true + _DEBUG;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;NOMINMAX;UNICODE;_UNICODE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Windows + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;NOMINMAX;UNICODE;_UNICODE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Windows + true + true + true + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;NOMINMAX;UNICODE;_UNICODE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Windows + true + true + true + + + + + + powershell -NoProfile -ExecutionPolicy Bypass -File "$(SolutionDir)generate_version.ps1" -Output "$(SolutionDir)version_info.h" + + + $(SolutionDir);%(AdditionalIncludeDirectories) + + + + + + + + + + + + + + diff --git a/LANCommander.Interposer.Plugin.Example/src/dllmain.cpp b/LANCommander.Interposer.Plugin.Example/src/dllmain.cpp new file mode 100644 index 0000000..b65f182 --- /dev/null +++ b/LANCommander.Interposer.Plugin.Example/src/dllmain.cpp @@ -0,0 +1,124 @@ +// +// LANCommander.Interposer.Plugin.Example +// +// Minimal example plugin for LANCommander Interposer. Use this as a starting +// point for writing your own plugins. +// +// Build this project as a DLL and drop it (or a subfolder containing it) into +// .interposer\Plugins\ next to the Interposer DLL. The Interposer loads all +// .dll and .asi files from that directory tree automatically. +// +// Plugin lifecycle: +// 1. Interposer calls LoadLibrary on your DLL -> DllMain(DLL_PROCESS_ATTACH) +// 2. Interposer looks for an "InterposerPluginInit" export via GetProcAddress. +// If found, it calls InterposerPluginInit(hInterposer) with its own HMODULE. +// 3. Use the provided HMODULE to resolve any Interposer exports you need. +// 4. On shutdown the Interposer calls FreeLibrary -> DllMain(DLL_PROCESS_DETACH) +// +// Available Interposer exports (resolve with GetProcAddress): +// +// Configuration / Logging (wchar_t*): +// InterposerGetUsername(wchar_t* buf, DWORD bufSize) -> BOOL +// InterposerLog(const wchar_t* verb, const wchar_t* message) -> void +// InterposerGetConfigString(const wchar_t* dotPath, +// wchar_t* buf, DWORD bufSize) -> BOOL +// InterposerRegisterPluginConfig(const wchar_t* pluginName, +// const wchar_t* yamlDefaults) -> BOOL +// +// Virtual Registry (wchar_t*): +// InterposerSetRegistryValue(const wchar_t* keyPath, +// const wchar_t* valueName, +// const wchar_t* value) -> void +// InterposerSetRegistryValueBySuffix(const wchar_t* keySuffix, +// const wchar_t* valueName, +// const wchar_t* value) -> DWORD +// +// Rich Presence (char* — UTF-8): +// InterposerSetPresenceDetails(const char* details) -> void +// InterposerSetPresenceState(const char* state) -> void +// InterposerSetPresenceTimestamps(int64_t start, int64_t end) -> void +// InterposerSetPresenceLargeImage(const char* key, text) -> void +// InterposerSetPresenceSmallImage(const char* key, text) -> void +// InterposerSetPresenceParty(const char* id, int size, max) -> void +// InterposerSetPresenceButton(int index, const char* text, url) -> void +// InterposerSetPresenceType(int type) -> void +// InterposerSetPresenceName(const char* name) -> void +// InterposerUpdatePresence() -> void +// InterposerClearPresence() -> void +// +// Config.yml example for this plugin: +// +// Plugins: +// Example: +// Greeting: "Hello from the example plugin!" +// +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include + +// ---- Interposer API typedefs ------------------------------------------------ +// Declare function pointer types for each export you intend to use. +// These have no link-time dependency on the Interposer. + +using FnInterposerLog = void (WINAPI*)(const wchar_t* verb, const wchar_t* message); +using FnInterposerGetConfigString = BOOL (WINAPI*)(const wchar_t* dotPath, wchar_t* buf, DWORD bufSize); +using FnInterposerGetUsername = BOOL (WINAPI*)(wchar_t* buf, DWORD bufSize); +using FnInterposerRegisterPluginConfig = BOOL (WINAPI*)(const wchar_t* pluginName, const wchar_t* yamlDefaults); + +static FnInterposerLog pfnLog = nullptr; +static FnInterposerGetConfigString pfnGetConfig = nullptr; +static FnInterposerGetUsername pfnGetUser = nullptr; + +// ---- Plugin entry point ----------------------------------------------------- +// The Interposer calls this immediately after LoadLibrary, passing its own +// HMODULE. Use it for all GetProcAddress calls — this works regardless of +// whether the Interposer was deployed as LANCommander.Interposer.dll, +// version.dll, dinput8.dll, or an .asi file. + +extern "C" __declspec(dllexport) void WINAPI InterposerPluginInit(HMODULE hInterposer) +{ + // Resolve the exports we need. + pfnLog = reinterpret_cast(GetProcAddress(hInterposer, "InterposerLog")); + pfnGetConfig = reinterpret_cast(GetProcAddress(hInterposer, "InterposerGetConfigString")); + pfnGetUser = reinterpret_cast(GetProcAddress(hInterposer, "InterposerGetUsername")); + + if (!pfnLog) + return; // Can't even log — bail out. + + // Register default config. On first run this writes the Plugins.Example + // section into Config.yml; on subsequent runs it's a no-op. + auto pfnRegConfig = reinterpret_cast( + GetProcAddress(hInterposer, "InterposerRegisterPluginConfig")); + if (pfnRegConfig) + pfnRegConfig(L"Example", L"Greeting: 'Hello from the example plugin!'"); + + // Read a plugin-specific setting from Config.yml. + wchar_t greeting[256] = {}; + if (pfnGetConfig && pfnGetConfig(L"Plugins.Example.Greeting", greeting, ARRAYSIZE(greeting))) + { + pfnLog(L"EXAMPLE", greeting); + } + else + { + pfnLog(L"EXAMPLE", L"Plugin loaded (no Plugins.Example.Greeting configured)"); + } + + // Demonstrate reading the current username. + wchar_t username[256] = {}; + if (pfnGetUser && pfnGetUser(username, ARRAYSIZE(username))) + { + std::wstring msg = L"Current player: "; + msg += username; + pfnLog(L"EXAMPLE", msg.c_str()); + } +} + +// ---- DllMain ---------------------------------------------------------------- +// Keep DllMain minimal. Heavy work belongs in InterposerPluginInit where you +// have guaranteed access to the Interposer API. + +BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD /*fdwReason*/, LPVOID /*lpReserved*/) +{ + return TRUE; +} diff --git a/LANCommander.Interposer.Plugin.Example/src/resource.rc b/LANCommander.Interposer.Plugin.Example/src/resource.rc new file mode 100644 index 0000000..eb9f7a8 --- /dev/null +++ b/LANCommander.Interposer.Plugin.Example/src/resource.rc @@ -0,0 +1,30 @@ +#include "version_info.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VER_FILEVERSION + PRODUCTVERSION VER_PRODUCTVERSION + FILEFLAGSMASK 0x3fL + FILEFLAGS 0x0L + FILEOS 0x00040004L + FILETYPE 0x00000002L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", VER_COMPANY_STR + VALUE "FileDescription", "LANCommander Interposer Example Plugin\0" + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", "LANCommander.Interposer.Plugin.Example\0" + VALUE "LegalCopyright", VER_COPYRIGHT_STR + VALUE "OriginalFilename", "LANCommander.Interposer.Plugin.Example.dll\0" + VALUE "ProductName", VER_PRODUCT_STR + VALUE "ProductVersion", VER_PRODUCTVERSION_STR + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END