OpenSR Shared Memory Interface

Overview

While OpenSR provides a robust internal buffering system for Plugin IN/OUT, UI, and Settings modules, it also exposes a Shared Memory Interface. This allows external applications—which may not be formal OpenSR plugins—to access real-time simulation and application data with extremely low latency.

The host application synchronizes these buffers in real-time, providing a “read-only” snapshot of the current state of the simulation and the application.

Shared Memory Channels

OpenSR utilizes three distinct named shared memory segments. External applications must use the Windows OpenFileMapping API to attach to these segments.

Channel NameSDK Header ReferenceContent DescriptionUpdate Frequency
Context Dataincludes/OpenSRContext.hApplication-level state, configuration, process IDs, and filesystem paths.Low / Event-driven
Simulation Dataincludes/OpenSROutSimData.hHigh-frequency telemetry including session, vehicle, and player data.High (Real-time)
Blob Dataincludes/OpenSRBlobData.hMulti-purpose high-frequency data used for custom solutions.High (Real-time)

1. Context Data (SMOSRCONTEXTDATA)

This channel is used to synchronize the external app with the state of the OpenSR host. Key data points include:

  • App State: Whether the application is currently running.
  • Process Tracking: The PID of the target game.
  • Environment: Absolute paths to the Application folder, Document folder, and the currently active Profile.

2. Simulation Data (SMOSROUTSIMDATA)

This is the primary telemetry stream. It mirrors the data sent to “OUT” plugins, containing:

  • Session Data: Track info, weather, and session timing.
  • Vehicle Data: Speed, RPM, Gear, Fuel, and detailed car setup/state.
  • Player Data: Position, lap times, and gap to other drivers.
  • Wheel/Motion Data: Force Feedback and motion platform vectors.

3. Blob Data (SMOSRBLOBDATA)

The Blob Data channel is reserved for custom solutions and is not part of the standard level of software support. It provides a raw memory area for developers to implement specialized data transfers.

Technical Implementation

Memory Mapping Workflow

To access the data, an external application must follow the Windows Memory Mapped File workflow:

  1. Handle Acquisition: Call OpenFileMappingW using the channel name (e.g., Local\SMOSROUTSIMDATA) with FILE_MAP_READ access.
  2. View Mapping: Call MapViewOfFile to map the shared memory segment into the application’s virtual address space.
  3. Structure Casting: Cast the resulting pointer to the corresponding SDK structure defined in the headers.

Memory Alignment & Language Support

OpenSR structures are packed to avoid compiler-added padding, ensuring the memory layout is identical across different languages.

  • C++: Use #pragma pack(push, 1) around the structure definitions to match the SDK headers.
  • C#: A complete C# wrapper is provided within the SDK. Developers are encouraged to copy the structure definitions directly from the wrapper to ensure perfect alignment.
  • Python: Use _pack_ = 1 within ctypes.Structure. A ready-to-use implementation is provided in the SDK via the osr_shared_mem_test.py script, which contains the necessary ctypes definitions for immediate use.

Critical Requirements

  • Read-Only Access: External applications must open the mapping with read-only permissions (FILE_MAP_READ). Attempting to write to these buffers can cause instability in the host application.
  • Synchronization: Data is updated by the host. Applications should treat the memory as a volatile stream and read it as a current snapshot.

Minimal Python script to read OutSimData

# IMPORTANT: Do NOT use the native 'mmap' module. Native mmap calls CreateFileMapping 
# internally, which will create a new shared memory segment if it doesn't exist.
# To avoid corrupting or creating stale buffers, we use kernel32 calls to 
# specifically OPEN an existing mapping and wait for its availability.

import ctypes
from ctypes import wintypes
import time

# Windows API Constants
FILE_MAP_READ = 0x04
ERROR_FILE_NOT_FOUND = 2

k32 = ctypes.windll.kernel32

# --- Kernel32 Function Declarations from osr_shared_mem_test.py [1] ---
k32.OpenFileMappingW.restype = wintypes.HANDLE
k32.OpenFileMappingW.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.LPCWSTR]

k32.MapViewOfFile.restype = ctypes.c_void_p
k32.MapViewOfFile.argtypes = [wintypes.HANDLE, wintypes.DWORD, wintypes.DWORD, wintypes.DWORD, ctypes.c_size_t]

k32.UnmapViewOfFile.restype = wintypes.BOOL
k32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]

k32.CloseHandle.restype = wintypes.BOOL
k32.CloseHandle.argtypes = [wintypes.HANDLE]

# --- Data Structures ---
class PacketHeader(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ("reportAvailable",   ctypes.c_uint8),
        ("paused",            ctypes.c_uint8),
        ("elapsedTime",       ctypes.c_uint64),
        ("frameId",           ctypes.c_uint64),
        ("frameRateFromGame", ctypes.c_uint32),
        ("majorVersion",      ctypes.c_uint8),
        ("minorVersion",      ctypes.c_uint8),
        ("packetType",        ctypes.c_uint8),
        ("packetIndex",       ctypes.c_uint8),
        ("playerSlotIndex",   ctypes.c_uint8),
        ("_padding",          ctypes.c_uint8 * 5),
    ]

# Minimal placeholders for OutSimData sub-structures to keep example clean
class PlayerRecords(ctypes.Structure): _pack_ = 1; _fields_ = [("dummy", ctypes.c_byte * 64520)]
class MotionData(ctypes.Structure): _pack_ = 1; _fields_ = [("dummy", ctypes.c_byte * 448)]
class SessionData(ctypes.Structure): _pack_ = 1; _fields_ = [("dummy", ctypes.c_byte * 528)]
class VehicleData(ctypes.Structure): 
    _pack_ = 1
    _fields_ = [("speed", ctypes.c_float), ("gear", ctypes.c_int8), ("dummy", ctypes.c_byte * 915)]
class WheelData(ctypes.Structure): _pack_ = 1; _fields_ = [("dummy", ctypes.c_byte * 864)]
class ExtensionData(ctypes.Structure): _pack_ = 1; _fields_ = [("extension", ctypes.c_byte * 1024)]

class OutSimData(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ("mPacketHeader", PacketHeader),
        ("mPlayers",      PlayerRecords),
        ("mMotionData",   MotionData),
        ("mSessionData",   SessionData),
        ("mVehicleData",   VehicleData),
        ("mWheelData",     WheelData),
        ("mExtensionData", ExtensionData),
    ]

HEADER_SIZE = ctypes.sizeof(PacketHeader)
STRUCT_SIZE = ctypes.sizeof(OutSimData)
assert HEADER_SIZE == 32, "PacketHeader layout is wrong"
assert STRUCT_SIZE == 68336, "OutSimData layout is wrong"

def main():
    shared_mem_name = "Local\\SMOSROUTSIMDATA"
    print(f"Waiting for {shared_mem_name}...")

    while True:
        # 1. Open existing mapping [1]
        h_map = k32.OpenFileMappingW(FILE_MAP_READ, False, shared_mem_name)
        if not h_map:
            if ctypes.get_last_error() == ERROR_FILE_NOT_FOUND:
                time.sleep(1)
                continue
            else:
                print(f"Error opening mapping: {ctypes.get_last_error()}")
                return

        # 2. Map the view [1]
        ptr = k32.MapViewOfFile(h_map, FILE_MAP_READ, 0, 0, 0)
        if not ptr:
            k32.CloseHandle(h_map)
            return

        print("Connected. Reading data...")
        try:
            # Map the pointer to the structure [1]
            # data = OutSimData.from_address(ptr)
            
            while True:
                 # 1. Get the snapshot
                data = OutSimData.from_buffer_copy(ctypes.string_at(ptr, ctypes.sizeof(OutSimData)))

                # Check if report is available and not a torn read [1]
                if data.mPacketHeader.reportAvailable == 1:
                    # Check if game is paused
                    status = "PAUSED" if data.mPacketHeader.paused else "RUNNING"
                    print(f"[{status}] FRAME ID: {data.mPacketHeader.frameId} | SPEED: {data.mVehicleData.speed*3.6:.0f}  km/h | GEAR: {(data.mVehicleData.gear):.0f}", end="\r")
                else:
                    print("Data not available or updating...", end="\r")
                
                time.sleep(0.01)
        except KeyboardInterrupt:
            print("\nStopping...")
        finally:
            k32.UnmapViewOfFile(ptr)
            k32.CloseHandle(h_map)
            break

if __name__ == "__main__":
    main()