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