Sample Interposer plugin

This commit is contained in:
Pat Hartl 2026-05-27 19:16:21 -05:00
parent a35d553481
commit 39c9a2f4cd
3 changed files with 319 additions and 0 deletions

View file

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>18.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{a1b2c3d4-e5f6-7890-abcd-ef1234567890}</ProjectGuid>
<RootNamespace>LANCommanderInterposerPluginExample</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="Shared" />
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>$(SolutionDir)Release\Example\x86\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(SolutionDir)Release\Example\x64\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;NOMINMAX;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;NOMINMAX;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;NOMINMAX;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;NOMINMAX;UNICODE;_UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup>
<PreBuildEvent>
<Command>powershell -NoProfile -ExecutionPolicy Bypass -File "$(SolutionDir)generate_version.ps1" -Output "$(SolutionDir)version_info.h"</Command>
</PreBuildEvent>
<ResourceCompile>
<AdditionalIncludeDirectories>$(SolutionDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ResourceCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ResourceCompile Include="src\resource.rc" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\dllmain.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
</Project>

View file

@ -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 <windows.h>
#include <string>
// ---- 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<FnInterposerLog>(GetProcAddress(hInterposer, "InterposerLog"));
pfnGetConfig = reinterpret_cast<FnInterposerGetConfigString>(GetProcAddress(hInterposer, "InterposerGetConfigString"));
pfnGetUser = reinterpret_cast<FnInterposerGetUsername>(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<FnInterposerRegisterPluginConfig>(
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;
}

View file

@ -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