#define WIN32_LEAN_AND_MEAN #include #include #include #include #include "../external/IUnityInterface.h" #include "../external/IUnityGraphics.h" #include "../external/IUnityGraphicsD3D11.h" #include "../include/shared_types.h" #include "d3d11_renderer.h" #include "popout_windows.h" #include // --------------------------------------------------------------------------- // Unity plugin lifecycle // --------------------------------------------------------------------------- static IUnityInterfaces* g_unityInterfaces = nullptr; static IUnityGraphics* g_unityGraphics = nullptr; static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType) { if (eventType == kUnityGfxDeviceEventInitialize) { auto* d3d11 = g_unityInterfaces->Get(); if (d3d11) Renderer_OnDeviceInit(d3d11->GetDevice()); } else if (eventType == kUnityGfxDeviceEventShutdown) { Renderer_OnDeviceShutdown(); } } extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* interfaces) { g_unityInterfaces = interfaces; g_unityGraphics = interfaces->Get(); g_unityGraphics->RegisterDeviceEventCallback(OnGraphicsDeviceEvent); OnGraphicsDeviceEvent(kUnityGfxDeviceEventInitialize); } extern "C" UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API UnityPluginUnload() { if (g_unityGraphics) g_unityGraphics->UnregisterDeviceEventCallback(OnGraphicsDeviceEvent); } // --------------------------------------------------------------------------- // Render event callback — called by Unity on the render thread // --------------------------------------------------------------------------- // Sentinel event id for the in-game overlay. Window handles start at 1 and grow // by one (see CreatePopoutWindow), so this high value can never collide. static const int kOverlayEventId = 0x05E70001; static void UNITY_INTERFACE_API OnRenderEvent(int eventId) { if (eventId == kOverlayEventId) { Renderer_PresentOverlay(); return; } PopoutWindow* win = GetPopoutWindow(eventId); if (win) Renderer_Present(win); } // --------------------------------------------------------------------------- // Exported functions (called from C# via [DllImport]) // --------------------------------------------------------------------------- extern "C" __declspec(dllexport) int RRPOPOUT_CreateWindow(const wchar_t* title, int width, int height) { return CreatePopoutWindow(title, width, height); } extern "C" __declspec(dllexport) void RRPOPOUT_SetFrameTexture(int windowHandle, void* texturePtr, float u0, float v0, float u1, float v1) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (!win) return; win->srcU0.store(u0); win->srcV0.store(v0); win->srcU1.store(u1); win->srcV1.store(v1); win->pendingTexture.store(texturePtr); } extern "C" __declspec(dllexport) UnityRenderingEvent RRPOPOUT_GetRenderEventFunc() { return OnRenderEvent; } extern "C" __declspec(dllexport) void RRPOPOUT_GetWindowSize(int windowHandle, int* outWidth, int* outHeight) { GetPopoutWindowSize(windowHandle, outWidth, outHeight); } // --------------------------------------------------------------------------- // In-game overlay (spike) — exports // --------------------------------------------------------------------------- // Returns the event id to pass to GL.IssuePluginEvent for the overlay render. extern "C" __declspec(dllexport) int RRPOPOUT_GetOverlayEventId() { return kOverlayEventId; } // One-time: hand native a texture so it can lazily acquire Unity's D3D device. extern "C" __declspec(dllexport) void RRPOPOUT_SetOverlayDeviceTexture(void* texturePtr) { Overlay_SetDeviceTexture(texturePtr); } // Per-frame input snapshot (top-left origin, pixels). extern "C" __declspec(dllexport) void RRPOPOUT_SetOverlayInput(float displayW, float displayH, float mouseX, float mouseY, int lButton, int rButton, int wheel) { Overlay_SetInput(displayW, displayH, mouseX, mouseY, lButton != 0, rButton != 0, wheel); } extern "C" __declspec(dllexport) void RRPOPOUT_SetOverlayVisible(int visible) { Overlay_SetVisible(visible != 0); } // Map render texture to show in the in-game window (+ UV rect; V flipped for a // Unity RT). Call each frame before issuing the overlay render event. extern "C" __declspec(dllexport) void RRPOPOUT_SetOverlayMapTexture(void* texturePtr, float u0, float v0, float u1, float v1) { Overlay_SetMapTexture(texturePtr, u0, v0, u1, v1); } extern "C" __declspec(dllexport) int RRPOPOUT_OverlayWantsMouse() { return Overlay_WantsMouse() ? 1 : 0; } // Drain queued in-game map input (drag/zoom over the map image) for C# to apply. extern "C" __declspec(dllexport) int RRPOPOUT_PollOverlayInput(InputEvent* outEvents, int maxEvents) { return Overlay_PollInput(outEvents, maxEvents); } // Read back the map image region size so C# can match the camera aspect to it. extern "C" __declspec(dllexport) void RRPOPOUT_GetOverlayMapView(float* outW, float* outH) { Overlay_GetMapView(outW, outH); } // Handle of the in-game overlay's shared UI-state holder. C# pushes status text, // loco/location lists, rotation, follow flags, etc. to this handle with the same // RRPOPOUT_Set* / RRPOPOUT_PollInputEvents functions the popout uses. extern "C" __declspec(dllexport) int RRPOPOUT_GetOverlayWindowHandle() { return GetOrCreateOverlayStateHandle(); } // Poll input events — mouse-type events are suppressed while ImGui has capture // (e.g. cursor is over the toolbar) to prevent accidental map pan/zoom. extern "C" __declspec(dllexport) int RRPOPOUT_PollInputEvents(int windowHandle, InputEvent* outEvents, int maxEvents) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (!win) return 0; int n = win->inputQueue.drain(outEvents, maxEvents); if (win->imWantMouse.load()) { // Keep only non-mouse events (UICommand etc.); drop pointer motion. int kept = 0; for (int i = 0; i < n; ++i) { if (outEvents[i].type == static_cast(UICommand)) outEvents[kept++] = outEvents[i]; } return kept; } return n; } extern "C" __declspec(dllexport) void RRPOPOUT_DestroyWindow(int windowHandle) { DestroyPopoutWindow(windowHandle); } // Helper: parse a newline-separated UTF-16 string into the window's ImMenuItem list. static void SetNamedList(int windowHandle, const wchar_t* names, std::vector& list, std::mutex& mtx) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (!win || !names) return; std::vector tmp; const wchar_t* p = names; while (*p) { const wchar_t* end = p; while (*end && *end != L'\n') ++end; PopoutWindow::ImMenuItem item{}; WideCharToMultiByte(CP_UTF8, 0, p, (int)(end - p), item.label, (int)sizeof(item.label) - 1, nullptr, nullptr); tmp.push_back(item); p = (*end == L'\n') ? end + 1 : end; } std::lock_guard lk(mtx); list = std::move(tmp); } // Set the locomotive list shown in the Follow submenu. // names: UTF-16 loco display names joined by '\n'. extern "C" __declspec(dllexport) void RRPOPOUT_SetLocoList(int windowHandle, const wchar_t* names) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (win) SetNamedList(windowHandle, names, win->imLocoList, win->imLocoMutex); } // Set the location list shown in the Jump to location submenu. // names: UTF-16 location names joined by '\n'. extern "C" __declspec(dllexport) void RRPOPOUT_SetLocationList(int windowHandle, const wchar_t* names) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (win) SetNamedList(windowHandle, names, win->imLocationList, win->imLocationMutex); } // Notify native side whether MapEnhancer is installed, so the ImGui menu can // show or hide ME-specific items without polling C# each frame. extern "C" __declspec(dllexport) void RRPOPOUT_SetMapEnhancerInstalled(int windowHandle, bool installed) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (win) win->imMapEnhancerInstalled.store(installed); } // Set ME capability flags. // hasTurntable — ShowTurntableMarkers field exists (v1.6.0 or community); gates Turntable Markers toggle. // community — full community build (crossing/spawn/switch-reset); gates all other community items. extern "C" __declspec(dllexport) void RRPOPOUT_SetMapEnhancerCaps(int windowHandle, bool hasTurntable, bool community) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (!win) return; win->imMEHasTurntable.store(hasTurntable); win->imMECommunity.store(community); } // Tell native whether panning the map should cancel follow mode (for the gear menu checkmark). extern "C" __declspec(dllexport) void RRPOPOUT_SetPanDisablesFollow(int windowHandle, bool active) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (win) win->imPanDisablesFollow.store(active); } // Tell native whether right-clicking the map recenters on the player (for the gear menu checkmark). extern "C" __declspec(dllexport) void RRPOPOUT_SetRightClickRecenter(int windowHandle, bool active) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (win) win->imRightClickRecenter.store(active); } // Seed all icon-culling and EOTD settings for the gear menu Icons submenu. // Called on window/overlay init and whenever C# settings change. extern "C" __declspec(dllexport) void RRPOPOUT_SetIconCullingState(int windowHandle, bool cullEnabled, float cullThresh, bool hideMU, float locoBoost, bool eotdEnabled, bool eotdOnlyWhenCulled, float eotdSize) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (!win) return; win->imIconCullingEnabled.store(cullEnabled); win->imCullThreshold.store(cullThresh); win->imHideMuLocos.store(hideMU); win->imLocoBoostedScale.store(locoBoost); win->imEotdEnabled.store(eotdEnabled); win->imEotdOnlyWhenCulled.store(eotdOnlyWhenCulled); win->imEotdSizeScale.store(eotdSize); } // Seed the map camera clear-colour opacity slider (shared global, affects both surfaces). extern "C" __declspec(dllexport) void RRPOPOUT_SetOverlayMapBgAlpha(float alpha) { Overlay_SetMapBgAlpha(alpha); } // Sync the compass display to the current map rotation (degrees, [0,360)). // Called by C# after it applies a rotation or while in sync-to-player mode. extern "C" __declspec(dllexport) void RRPOPOUT_SetMapRotation(int windowHandle, float degrees) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (win) win->imMapRotationDeg.store(degrees); } // Tell the sync button whether sync-to-player mode is active (highlights it). extern "C" __declspec(dllexport) void RRPOPOUT_SetMapSyncPlayer(int windowHandle, bool active) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (win) win->imMapSyncPlayer.store(active); } // Highlight (or un-highlight) the follow-player toolbar button. extern "C" __declspec(dllexport) void RRPOPOUT_SetFollowPlayer(int windowHandle, bool active) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (win) win->imFollowPlayer.store(active); } // Push current map zoom level so native can persist it on close. extern "C" __declspec(dllexport) void RRPOPOUT_SetMapZoom(int windowHandle, float zoom) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (win) win->imMapZoom.store(zoom); } // Read back the C#-side persisted state that native loaded from the state file. // Call this once after RRPOPOUT_CreateWindow to restore the previous session. extern "C" __declspec(dllexport) void RRPOPOUT_GetPersistedState(int windowHandle, int32_t* followPlayer, int32_t* syncRotation, float* mapRotation, float* mapZoom) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (!win) { *followPlayer = 0; *syncRotation = 0; *mapRotation = 0.f; *mapZoom = 500.f; return; } *followPlayer = win->imFollowPlayer.load() ? 1 : 0; *syncRotation = win->imMapSyncPlayer.load() ? 1 : 0; *mapRotation = win->imMapRotationDeg.load(); *mapZoom = win->imMapZoom.load(); } // Update the ImGui toolbar status label. // text is UTF-16; converted to UTF-8 for ImGui here on the calling thread. extern "C" __declspec(dllexport) void RRPOPOUT_SetStatusText(int windowHandle, const wchar_t* text) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (!win || !text) return; char buf[256] = {}; WideCharToMultiByte(CP_UTF8, 0, text, -1, buf, (int)sizeof(buf) - 1, nullptr, nullptr); std::lock_guard lk(win->imStatusMutex); strncpy_s(win->imStatusText, buf, _TRUNCATE); } // Apply a theme preset. data is a pointer to a MapThemeData struct; presetIndex is // stored for checkmark display in the gear menu. Thread-safe: stores via mutex + atomic. extern "C" __declspec(dllexport) void RRPOPOUT_SetTheme(const MapThemeData* data, int presetIndex) { Renderer_SetTheme(data, presetIndex); } // Push MapEnhancer settings state to a window's render-thread atomics. // flags: bit-packed bool settings (see MEBoolBit in shared_types.h). // The scale values are used to initialise / update the slider positions; // the render thread writes them back during drag and only pushes UICmd::SetMEFloat // when the slider is released (IsItemDeactivatedAfterEdit). extern "C" __declspec(dllexport) void RRPOPOUT_SetMEState(int windowHandle, uint32_t flags, float flareScale, float junctionScale, float trackThickness, float crossingScale) { PopoutWindow* win = GetPopoutWindow(windowHandle); if (!win) return; win->imMEFlags.store(flags); win->imMEFlareScale.store(flareScale); win->imMEJunctionSc.store(junctionScale); win->imMETrackThick.store(trackThickness); win->imMECrossingSc.store(crossingScale); } // Set the in-game overlay's chrome (window + toolbar + compass) alpha [0.1, 1.0]. extern "C" __declspec(dllexport) void RRPOPOUT_SetOverlayAlpha(float alpha) { Overlay_SetAlpha(alpha); } // Set the map image alpha [0.0, 1.0]. Independent of chrome alpha. extern "C" __declspec(dllexport) void RRPOPOUT_SetOverlayMapAlpha(float alpha) { Overlay_SetMapAlpha(alpha); } // --------------------------------------------------------------------------- // Plugin_Initialize / Plugin_Shutdown (called from dllmain.cpp) // --------------------------------------------------------------------------- void Plugin_Initialize() { } void Plugin_Shutdown() { Renderer_OnDeviceShutdown(); }