The Project
This section explains how to create a valid OpenSR GAME plugin project in Visual Studio 2019.
The objective is to produce a GAME plugin (.osri) that OpenSR can load automatically.
This example uses:
- Visual Studio 2019
- native C++
- x64 DLL project
- OpenSR SDK
- C++17 (recommended)
The example plugin targets:
notepad.exe
and generates fake telemetry at 60 Hz.
1. Install The OpenSR SDK
The OpenSR installer includes an optional SDK package.
During installation:
Install OpenSR SDK
must be checked.
If installed, the SDK is copied into:
Documents/OpenSR/OpenSR_SDK1.0/
Typical SDK structure:
Documents/
└── OpenSR/
└── OpenSR_SDK1.0/
├── includes/
├── Example/
└── docs/
The most important folder is:
OpenSR_SDK1.0/includes/
because it contains:
IOpenSRPlugin.h
OpenSRContext.h
OpenSRBuffers.h
StringUtils.h
OpenSRHelper.h
OpenSRInSimData.h
OpenSROutSimData.h
IPluginSettings.h
SettingsData.h
IPluginUI.h
OpenSRBlobData.h
OpenSRLegacyHelper.h
OpenSRBuffers.cs
OpenSRContext.cs
OutSimData.cs
InSimData.cs
2. Create The Project
Open Visual Studio 2019.
Select:
Create a new project
Choose:
Dynamic-Link Library (DLL)
Language:
C++
Platform:
Windows
Project name example:
NotepadExamplePlugin
Press:
Create
3. Disable Precompiled Headers (Optional)
Inside the wizard:
Uncheck:
Precompiled Header
This is optional.
Keeping PCH enabled is also valid.
Disabling it simplifies first plugin creation.
4. Configure Platform
Open:
Build
→ Configuration Manager
Set:
Active Solution Platform = x64
OpenSR plugins should normally be built in x64.
5. Configure C++ Standard
Open:
Project Properties
→ C/C++
→ Language
→ C++ Language Standard
Recommended:
ISO C++17 Standard (/std:c++17)
C++14 also works for most plugins, but C++17 is recommended.
6. Configure Runtime Library
Open:
Project Properties
→ C/C++
→ Code Generation
→ Runtime Library
Recommended (default):
For Release:
Multi-threaded DLL (/MD)
For Debug:
Multi-threaded Debug DLL (/MDd)
7. Configure SDK Include Paths
Open:
Project Properties
→ C/C++
→ General
→ Additional Include Directories
Add:
$(USERPROFILE)\Documents\OpenSR\OpenSR_SDK1.0\includes
Your plugin must be able to include:
#include "IOpenSRPlugin.h"
#include "OpenSRBuffers.h"
#include "OpenSRContext.h"
8. Add Source Files
NotepadExamplePlugin.h
#pragma once
#include "IOpenSRPlugin.h"
#include "OpenSRBuffers.h"
#include <thread>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <cmath>
#define _TARGET_APP "notepad.exe"
class NotepadExamplePlugin : public osr::IOpenSRPlugin
{
public:
NotepadExamplePlugin();
~NotepadExamplePlugin();
// =========================================================
// OpenSR Lifecycle
// =========================================================
bool Init(
OpenSRContext* context,
void* inBuf,
const wchar_t* pluginPath) override;
bool Start() override;
void Stop() override;
void Shutdown() override;
void Pause() override
{
m_isPluginPaused = true;
}
void Resume() override
{
m_isPluginPaused = false;
m_pauseCv.notify_one();
}
bool IsRunning() const override
{
return m_isPluginRunning;
}
void OnContextChanged(
osr::OpenSRContextChange reason) override;
// =========================================================
// Mandatory Metadata
// =========================================================
void GetPluginName(
char* buffer,
size_t bufferSize) const override
{
strcpy_s(
buffer,
bufferSize,
"Notepad Example Plugin");
}
void GetAuthor(
char* buffer,
size_t bufferSize) const override
{
strcpy_s(
buffer,
bufferSize,
"AuthorName");
}
void GetVersion(
char* buffer,
size_t bufferSize) const override
{
strcpy_s(
buffer,
bufferSize,
"1.0");
}
void GetLicenseType(
char* buffer,
size_t bufferSize) const override
{
strcpy_s(
buffer,
bufferSize,
"MIT");
}
void GetDescription(
char* buffer,
size_t bufferSize) const override
{
strcpy_s(
buffer,
bufferSize,
"Example OpenSR GAME plugin using Notepad.exe as target process and generating fake telemetry.");
}
void GetTargetName(
char* buffer,
size_t bufferSize) const override
{
strcpy_s(
buffer,
bufferSize,
"Notepad");
}
void GetTargetProcessName(
char* buffer,
size_t bufferSize) const override
{
strcpy_s(
buffer,
bufferSize,
_TARGET_APP);
}
void GetPackageName(
char* buffer,
size_t bufferSize) const override
{
strncpy_s(
buffer,
bufferSize,
"com.authorname.gameplugin.example.notepadplugin",
_TRUNCATE);
}
void GetPluginGroupName(
wchar_t* buffer,
size_t bufferSize) const override
{
wcscpy_s(
buffer,
bufferSize,
L"");
}
int GetType() override
{
return GAME_PLUGIN_TYPE;
}
private:
// =========================================================
// Internal Runtime
// =========================================================
void WorkerThread();
void CheckForPause();
void GenerateFakeTelemetry(
OutSimData& data,
float timeSec);
private:
// =========================================================
// OpenSR Runtime References
// =========================================================
OpenSRContext* m_pContext = nullptr;
OpenSRBuffersIN* m_pBufferIn = nullptr;
std::wstring m_pluginPath;
// =========================================================
// Threading
// =========================================================
std::thread workerThread;
std::mutex lifecycleMutex;
std::mutex m_pauseMutex;
std::condition_variable m_pauseCv;
// =========================================================
// Runtime State
// =========================================================
std::atomic<bool> m_stopRequested = false;
std::atomic<bool> m_isPluginRunning = false;
std::atomic<bool> m_isPluginPaused = false;
};
NotepadExamplePlugin.cpp
#include "pch.h"
#include "NotepadExamplePlugin.h"
#include "OpenSRHelper.h"
#include <Windows.h>
#include <cmath>
// =========================================================
// Exported Factory Functions
// =========================================================
extern "C" __declspec(dllexport)
osr::IOpenSRPlugin * CreatePlugin()
{
return new NotepadExamplePlugin();
}
extern "C" __declspec(dllexport)
void DestroyPlugin(osr::IOpenSRPlugin * plugin)
{
if (plugin)
delete plugin;
}
// =========================================================
// Constructor / Destructor
// =========================================================
NotepadExamplePlugin::NotepadExamplePlugin()
{
}
NotepadExamplePlugin::~NotepadExamplePlugin()
{
Shutdown();
}
// =========================================================
// Init
// =========================================================
bool NotepadExamplePlugin::Init(
OpenSRContext* context,
void* inBuf,
const wchar_t* pluginPath)
{
// Store OpenSR runtime context.
m_pContext = context;
// Store writable telemetry buffers.
m_pBufferIn =
static_cast<OpenSRBuffersIN*>(inBuf);
// Store plugin path.
m_pluginPath = pluginPath;
return true;
}
// =========================================================
// Start
// =========================================================
bool NotepadExamplePlugin::Start()
{
std::lock_guard<std::mutex>
lock(lifecycleMutex);
if (!m_pContext)
return false;
// Reset runtime state.
m_stopRequested = false;
m_isPluginPaused = false;
m_isPluginRunning = true;
// Start telemetry thread.
workerThread =
std::thread(
&NotepadExamplePlugin::WorkerThread,
this);
return true;
}
// =========================================================
// Stop
// =========================================================
void NotepadExamplePlugin::Stop()
{
std::lock_guard<std::mutex>
lock(lifecycleMutex);
// Signal worker shutdown.
m_stopRequested = true;
// Wake paused thread if needed.
if (m_isPluginPaused)
{
Resume();
}
// Wait for thread termination.
if (workerThread.joinable())
{
workerThread.join();
}
m_isPluginRunning = false;
}
// =========================================================
// Shutdown
// =========================================================
void NotepadExamplePlugin::Shutdown()
{
Stop();
}
// =========================================================
// Context Change
// =========================================================
void NotepadExamplePlugin::OnContextChanged(
osr::OpenSRContextChange reason)
{
// No runtime reload logic required
// for this example plugin.
}
// =========================================================
// Pause Synchronization
// =========================================================
void NotepadExamplePlugin::CheckForPause()
{
std::unique_lock<std::mutex>
lock(m_pauseMutex);
m_pauseCv.wait(
lock,
[this]
{
return !m_isPluginPaused;
});
}
// =========================================================
// Main Worker Thread
// =========================================================
void NotepadExamplePlugin::WorkerThread()
{
if (!m_pContext ||
!m_pBufferIn ||
!m_pBufferIn->outSimDataIN)
{
return;
}
// Mark telemetry available.
m_pBufferIn
->outSimDataIN
->mPacketHeader
.reportAvailable = 1;
auto startTime =
std::chrono::steady_clock::now();
while (!m_stopRequested &&
m_pContext &&
m_pContext->isAppRunning)
{
// Validate target process.
if (!OpenSRHelper::IsProcessRunningByPIDAndName(
m_pContext->targetGamePId,
TEXT(_TARGET_APP)))
{
break;
}
// Respect OpenSR pause state.
CheckForPause();
// Compute running time.
auto now =
std::chrono::steady_clock::now();
float timeSec =
std::chrono::duration<float>(
now - startTime).count();
// Generate fake telemetry.
GenerateFakeTelemetry(
*m_pBufferIn->outSimDataIN,
timeSec);
// Submit synchronized frame.
m_pContext->submitFrameCallback(
m_pContext->userData);
// 60 Hz update rate.
std::this_thread::sleep_for(
std::chrono::milliseconds(16));
}
// Mark telemetry unavailable.
m_pBufferIn
->outSimDataIN
->mPacketHeader
.reportAvailable = 0;
}
// =========================================================
// Fake Telemetry Generator
// =========================================================
void NotepadExamplePlugin::GenerateFakeTelemetry(
OutSimData& data,
float timeSec)
{
// =====================================================
// Packet Header
// =====================================================
data.mPacketHeader.paused = false;
data.mPacketHeader.playerSlotIndex = 0;
data.mPacketHeader.elapsedTime = timeSec;
// =====================================================
// Session
// =====================================================
data.mSessionData.sessionTime = timeSec;
// =====================================================
// Players
// =====================================================
data.mPlayers.carCount = 1;
strcpy_s(
data.mPlayers.player[0].carName,
"Notepad GT3");
// Fake circular trajectory.
data.mPlayers.player[0].worldPositionX =
std::cos(timeSec) * 100.0f;
data.mPlayers.player[0].worldPositionY =
std::sin(timeSec) * 100.0f;
data.mPlayers.player[0].worldPositionZ =
0.0f;
// =====================================================
// Vehicle Data
// =====================================================
data.mVehicleData.speed =
120.0f + std::sin(timeSec) * 20.0f;
data.mVehicleData.rpm =
5000.0f + std::sin(timeSec * 2.0f) * 1500.0f;
data.mVehicleData.maxRpm = 8000.0f;
data.mVehicleData.gear =
3 + (int)(std::sin(timeSec) * 2.0f);
data.mVehicleData.maxGear = 6;
data.mVehicleData.throttle =
(std::sin(timeSec) + 1.0f) * 0.5f;
data.mVehicleData.brake =
(std::sin(timeSec * 0.5f) + 1.0f) * 0.5f;
data.mVehicleData.clutch = 0.0f;
data.mVehicleData.fuelLevel =
75.0f;
data.mVehicleData.fuelCapacity =
100.0f;
data.mVehicleData.engineTemp =
92.0f;
data.mVehicleData.oilTemp =
105.0f;
data.mVehicleData.oilPress =
4.2f;
data.mVehicleData.turboLevel =
0.8f;
data.mVehicleData.absInAction =
(std::sin(timeSec * 8.0f) > 0.8f);
data.mVehicleData.tcInAction =
(std::sin(timeSec * 6.0f) > 0.8f);
// =====================================================
// Warning Flags
// =====================================================
data.mVehicleData.warningFlag.headlights = 1;
data.mVehicleData.warningFlag.leftTurnSignal =
(std::sin(timeSec * 2.0f) > 0.0f);
data.mVehicleData.warningFlag.rightTurnSignal =
(std::sin(timeSec * 2.0f) < 0.0f);
data.mVehicleData.warningFlag.oilWarning = 0;
data.mVehicleData.warningFlag.pitLimiterActive = 0;
data.mVehicleData.warningFlag.turboStatus = 1;
// =====================================================
// Motion Data
// =====================================================
data.mMotionData.velocityX =
std::cos(timeSec) * 15.0f;
data.mMotionData.velocityY =
0.0f;
data.mMotionData.velocityZ =
std::sin(timeSec) * 15.0f;
data.mMotionData.localAccelX =
std::sin(timeSec * 5.0f);
data.mMotionData.localAccelY =
0.1f;
data.mMotionData.localAccelZ =
std::cos(timeSec * 5.0f);
data.mMotionData.pitch =
std::sin(timeSec) * 5.0f;
data.mMotionData.roll =
std::cos(timeSec) * 3.0f;
data.mMotionData.yaw =
timeSec * 10.0f;
data.mMotionData.pitchRate =
std::cos(timeSec);
data.mMotionData.rollRate =
std::sin(timeSec);
data.mMotionData.yawRate =
10.0f;
data.mMotionData.angularVelocityX =
data.mMotionData.pitchRate;
data.mMotionData.angularVelocityY =
data.mMotionData.yawRate;
data.mMotionData.angularVelocityZ =
data.mMotionData.rollRate;
data.mMotionData.angularAccelX =
std::sin(timeSec * 2.0f);
data.mMotionData.angularAccelY =
std::cos(timeSec * 2.0f);
data.mMotionData.angularAccelZ =
std::sin(timeSec * 4.0f);
data.mMotionData.upDirX = 0.0f;
data.mMotionData.upDirY = 1.0f;
data.mMotionData.upDirZ = 0.0f;
}
9. Configure Plugin Extension
IMPORTANT:
GAME plugins use:
.osri
OUT plugins use:
Open:
.osro
Project Properties
→ General
→ Target Extension
Replace:
.dll
with:
.osri
Your final binary should become:
NotepadExamplePlugin.osri
10. Configure Output Directory
OpenSR plugin directories are located in:
Documents/OpenSR/Plugins/
Recommended output directory:
$(USERPROFILE)\Documents\OpenSR\Plugins\NotepadExamplePlugin\
Configure:
Project Properties
→ General
→ Output Directory
Recommended:
$(USERPROFILE)\Documents\OpenSR\Plugins\NotepadExamplePlugin\
11. Mandatory Plugin Assets
Each plugin must provide standardized image assets.
These assets are used by:
- plugin manager
- dashboard
- plugin browser
- featured views
- thumbnail previews
Mandatory files:

| Image | File | Purpose |
|---|---|---|
| icon.png | Icon 32 x 32 (for Sidebar & Header) | |
| thumbnail.png | Thumbnail 228 x 86 (for game profiles) | |
![]() | featured.png | Image 512 x 282 (for Info page) |
Recommended final plugin directory:
Documents/
└── OpenSR/
└── Plugins/
└── NotepadExamplePlugin/
├── NotepadExamplePlugin.osri
├── icon.png
├── thumbnail.png
└── featured.png
12. Exported Plugin Functions
Every GAME plugin must export:
extern "C" __declspec(dllexport)
osr::IOpenSRPlugin* CreatePlugin()
and:
extern "C" __declspec(dllexport)
void DestroyPlugin(osr::IOpenSRPlugin* plugin)
Without these exports OpenSR cannot instantiate the plugin.
13. Verify Exports
Open:
Developer Command Prompt for VS2019
Run:
dumpbin /exports NotepadExamplePlugin.osri
You MUST see:
CreatePlugin
DestroyPlugin
14. Adding Runtime Settings Support
The Notepad example can also demonstrate runtime settings support.
Create:
settings.xml
inside the plugin directory.
Example:
<settings>
<option
name="change telemetry rate"
tooltip="change rate"
type="slider"
range="1,16">16</option>
</settings>
Final directory:
Documents/
└── OpenSR/
└── Plugins/
└── NotepadExamplePlugin/
├── NotepadExamplePlugin.osri
├── settings.xml
├── icon.png
├── thumbnail.png
└── featured.png
15. Building The Settings Path
Inside Init() the plugin should build the path to settings.xml.
The SDK provides StringUtils.
Example:
#include "StringUtils.h" // included in the SDK
bool NotepadExamplePlugin::Init(
OpenSRContext* context,
void* inBuf,
const wchar_t* pluginPath)
{
m_pContext = context;
m_pBufferIn =
static_cast<OpenSRBuffersIN*>(inBuf);
m_pluginPath = pluginPath;
// Build:
// Documents/OpenSR/Plugins/NotepadExamplePlugin/settings.xml
xmlPath = StringUtils::createWString(
m_pContext->osrDocFolder,
L"\\Plugins\\",
pluginPath,
L"\\settings.xml");
return true;
}
This is the recommended SDK way to build plugin-relative paths.
16. Loading Settings
The OpenSR SDK examples use TinyXML2 for XML parsing.
Before implementing the settings loader, include:
#include "tinyxml2.h"
and add:
using namespace tinyxml2;
Example runtime settings structure:
struct PluginSettings
{
int telemetryRate = 16;
};
Example loading function:
bool LoadPluginSettings(
const std::wstring& filepath,
PluginSettings& s)
{
XMLDocument doc;
FILE* file =
_wfsopen(
filepath.c_str(),
L"rb",
_SH_DENYNO);
if (!file)
return false;
if (doc.LoadFile(file) != XML_SUCCESS)
{
fclose(file);
return false;
}
fclose(file);
XMLElement* root =
doc.FirstChildElement("settings");
if (!root)
return false;
for (XMLElement* opt =
root->FirstChildElement("option");
opt;
opt = opt->NextSiblingElement("option"))
{
const char* name =
opt->Attribute("name");
if (!name)
continue;
if (strcmp(name,
"change telemetry rate") == 0)
{
s.telemetryRate =
opt->IntText(16);
}
}
return true;
}
Recommended additional include list:
#include "IOpenSRPlugin.h"
#include "OpenSRBuffers.h"
#include "OpenSRContext.h"
#include "tinyxml2.h"
#include <thread>
#include <atomic>
#include <mutex>
#include <condition_variable>
TinyXML2 is lightweight, fast, portable, and recommended for OpenSR plugin settings parsing.
17. Using The Runtime Setting
Inside the worker thread:
std::this_thread::sleep_for(
std::chrono::milliseconds(
m_settings.telemetryRate));
This allows runtime modification of telemetry frequency.
18. Responding To Runtime Settings Changes
It is recommended to load plugin settings inside Start().
This guarantees that every runtime restart uses the latest configuration automatically.
Recommended flow:
OnContextChanged()
↓
Stop()
↓
Start()
↓
LoadPluginSettings()
This keeps runtime reload logic simple and centralized.
Recommended Start() Pattern
Example:
bool NotepadExamplePlugin::Start()
{
std::lock_guard<std::mutex>
lock(lifecycleMutex);
if (!m_pContext)
return false;
// Reload latest settings every time
// the plugin starts.
LoadPluginSettings(
xmlPath,
m_settings);
m_stopRequested = false;
m_isPluginPaused = false;
m_isPluginRunning = true;
workerThread =
std::thread(
&NotepadExamplePlugin::WorkerThread,
this);
return true;
}
This guarantees that runtime configuration changes are always applied consistently.
Recommended OnContextChanged()
Example:
void NotepadExamplePlugin::OnContextChanged(
osr::OpenSRContextChange reason)
{
if (reason ==
osr::OpenSRContextChange
::SettingsChanged)
{
// Restart plugin runtime.
// Settings will automatically
// be reloaded inside Start().
Stop();
Start();
}
}
This is the recommended production pattern for most GAME plugins because:
- settings logic remains centralized
- runtime reload stays predictable
- no duplicated settings code exists
- all telemetry systems restart consistently
- sockets/SDKs are recreated cleanly
- worker threads restart safely
19. Build The Plugin
Build:
Build
→ Build Solution
Expected result:
Documents/OpenSR/Plugins/NotepadExamplePlugin/
└── NotepadExamplePlugin.osri
20. Launch OpenSR
Launch OpenSR.
Open:
Notepad.exe
OpenSR should:
- detect the process
- load the plugin
- start the worker thread
- generate telemetry
- submit frames at runtime
When Notepad closes:
the plugin should stop automatically.
21. Final Recommended Directory
Final recommended plugin directory:
Documents/
└── OpenSR/
└── Plugins/
└── NotepadExamplePlugin/
├── NotepadExamplePlugin.osri
├── settings.xml
├── icon.png
├── thumbnail.png
└── featured.png
This is a complete valid OpenSR GAME plugin package.

