LCOV - code coverage report
Current view: top level - src - GameConnector.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 94.6 % 92 87
Test Date: 2026-03-01 21:30:38 Functions: 100.0 % 10 10
Branches: 61.8 % 102 63

             Branch data     Line data    Source code
       1                 :             : #include "GameConnector.h"
       2                 :             : #include "Logger.h"
       3                 :             : #ifndef _WIN32
       4                 :             : #include "lmu_sm_interface/LinuxMock.h"
       5                 :             : #endif
       6                 :             : #include "lmu_sm_interface/SafeSharedMemoryLock.h"
       7                 :             : #include <iostream>
       8                 :             : 
       9                 :             : #define LEGACY_SHARED_MEMORY_NAME "$rFactor2SMMP_Telemetry$"
      10                 :             : 
      11                 :       55699 : GameConnector& GameConnector::Get() {
      12   [ +  +  +  -  :       55699 :     static GameConnector instance;
             +  -  -  - ]
      13                 :       55701 :     return instance;
      14                 :             : }
      15                 :             : 
      16                 :           1 : GameConnector::GameConnector() {}
      17                 :             : 
      18                 :           1 : GameConnector::~GameConnector() {
      19                 :           1 :     Disconnect();
      20                 :           1 : }
      21                 :             : 
      22                 :       12411 : void GameConnector::Disconnect() {
      23         [ +  - ]:       12411 :     std::lock_guard<std::mutex> lock(m_mutex);
      24                 :       12411 :     _DisconnectLocked();
      25                 :       12411 : }
      26                 :             : 
      27                 :       24822 : void GameConnector::_DisconnectLocked() {
      28                 :             : #if defined(_WIN32) || defined(HEADLESS_GUI)
      29         [ +  + ]:       24822 :     if (m_pSharedMemLayout) {
      30                 :       12409 :         UnmapViewOfFile(m_pSharedMemLayout);
      31                 :       12409 :         m_pSharedMemLayout = nullptr;
      32                 :             :     }
      33         [ +  + ]:       24822 :     if (m_hMapFile) {
      34                 :       12409 :         CloseHandle(m_hMapFile);
      35                 :       12409 :         m_hMapFile = NULL;
      36                 :             :     }
      37                 :       24822 :     m_hwndGame = NULL;
      38                 :             : #endif
      39                 :       24822 :     m_smLock.reset();
      40                 :       24822 :     m_connected = false;
      41                 :       24822 :     m_processId = 0;
      42                 :       24822 : }
      43                 :             : 
      44                 :       12415 : bool GameConnector::TryConnect() {
      45         [ +  - ]:       12415 :     std::lock_guard<std::mutex> lock(m_mutex);
      46         [ +  + ]:       12415 :     if (m_connected) return true;
      47                 :             : 
      48                 :             :     // Ensure we don't leak handles from a previous partial/failed attempt
      49                 :       12409 :     _DisconnectLocked();
      50                 :             : 
      51                 :             : #if defined(_WIN32) || defined(HEADLESS_GUI)
      52         [ +  - ]:       12409 :     m_hMapFile = OpenFileMappingA(FILE_MAP_READ, FALSE, LMU_SHARED_MEMORY_FILE);
      53                 :             :     
      54         [ -  + ]:       12409 :     if (m_hMapFile == NULL) {
      55                 :           0 :         return false;
      56                 :             :     } 
      57                 :             : 
      58         [ +  - ]:       12409 :     m_pSharedMemLayout = (SharedMemoryLayout*)MapViewOfFile(m_hMapFile, FILE_MAP_READ, 0, 0, sizeof(SharedMemoryLayout));
      59         [ -  + ]:       12409 :     if (m_pSharedMemLayout == NULL) {
      60   [ #  #  #  # ]:           0 :         std::cerr << "[GameConnector] Could not map view of file." << std::endl;
      61   [ #  #  #  # ]:           0 :         Logger::Get().LogWin32Error("MapViewOfFile", GetLastError());
      62                 :           0 :         _DisconnectLocked();
      63                 :           0 :         return false;
      64                 :             :     }
      65                 :             : 
      66   [ +  -  +  - ]:       12409 :     m_smLock = SafeSharedMemoryLock::MakeSafeSharedMemoryLock();
      67         [ +  + ]:       12409 :     if (!m_smLock.has_value()) {
      68   [ +  -  +  - ]:           1 :         std::cerr << "[GameConnector] Failed to init LMU Shared Memory Lock" << std::endl;
      69   [ +  -  +  - ]:           1 :         Logger::Get().Log("Failed to init SafeSharedMemoryLock.");
      70                 :           1 :         _DisconnectLocked();
      71                 :           1 :         return false;
      72                 :             :     }
      73                 :             : 
      74                 :       12408 :     HWND hwnd = m_pSharedMemLayout->data.generic.appInfo.mAppWindow;
      75         [ +  + ]:       12408 :     if (hwnd) {
      76                 :           4 :         m_hwndGame = hwnd; // Store HWND for liveness check (IsWindow)
      77                 :             :         // Note: multiple threads might access shared memory, but HWND is usually stable during session.
      78                 :             :         // We use IsWindow(m_hwndGame) instead of OpenProcess to avoid AV heuristics flagging "Process Access".
      79                 :             :     }
      80                 :             : 
      81                 :       12408 :     m_connected = true;
      82                 :       12408 :     m_lastUpdateLocalTime = std::chrono::steady_clock::now();
      83   [ +  -  +  - ]:       12408 :     std::cout << "[GameConnector] Connected to LMU Shared Memory." << std::endl;
      84   [ +  -  +  - ]:       12408 :     Logger::Get().Log("Connected to LMU Shared Memory.");
      85                 :       12408 :     return true;
      86                 :             : #else
      87                 :             :     return false;
      88                 :             : #endif
      89                 :       12415 : }
      90                 :             : 
      91                 :           7 : bool GameConnector::CheckLegacyConflict() {
      92                 :             : #if defined(_WIN32) || defined(HEADLESS_GUI)
      93                 :           7 :     HANDLE hLegacy = OpenFileMappingA(FILE_MAP_READ, FALSE, LEGACY_SHARED_MEMORY_NAME);
      94         [ +  + ]:           7 :     if (hLegacy) {
      95                 :           2 :         std::cout << "[Warning] Legacy rFactor 2 Shared Memory Plugin detected. This may conflict with LMU 1.2 data." << std::endl;
      96                 :           2 :         CloseHandle(hLegacy);
      97                 :           2 :         return true;
      98                 :             :     }
      99                 :             : #endif
     100                 :           5 :     return false;
     101                 :             : }
     102                 :             : 
     103                 :        5154 : bool GameConnector::IsConnected() const {
     104         [ +  + ]:        5154 :   if (!m_connected.load(std::memory_order_acquire)) return false;
     105                 :             : 
     106         [ +  - ]:        4942 :   std::lock_guard<std::mutex> lock(m_mutex);
     107         [ -  + ]:        4942 :   if (!m_connected.load(std::memory_order_relaxed)) return false;
     108                 :             : 
     109                 :             : #if defined(_WIN32) || defined(HEADLESS_GUI)
     110         [ +  + ]:        4942 :   if (m_hwndGame) {
     111         [ +  + ]:        4766 :     if (!IsWindow(m_hwndGame)) {
     112                 :             :       // Window is gone, game likely exited
     113                 :           1 :       const_cast<GameConnector*>(this)->_DisconnectLocked();
     114                 :           1 :       return false;
     115                 :             :     }
     116                 :             :   }
     117                 :             : #endif
     118                 :             : 
     119   [ +  -  +  -  :        4941 :   return m_connected.load(std::memory_order_relaxed) && m_pSharedMemLayout && m_smLock.has_value();
                   +  - ]
     120                 :        4942 : }
     121                 :             : 
     122                 :       20974 : bool GameConnector::CopyTelemetry(SharedMemoryObjectOut& dest) {
     123         [ +  + ]:       20974 :     if (!m_connected.load(std::memory_order_acquire)) return false;
     124                 :             : 
     125         [ +  - ]:       10137 :     std::lock_guard<std::mutex> lock(m_mutex);
     126   [ +  +  +  -  :       10137 :     if (!m_connected.load(std::memory_order_relaxed) || !m_pSharedMemLayout || !m_smLock.has_value()) return false;
             -  +  +  + ]
     127                 :             : 
     128         [ +  + ]:        9919 :     if (m_smLock->Lock(50)) {
     129                 :        9918 :         CopySharedMemoryObj(dest, m_pSharedMemLayout->data);
     130                 :             :         
     131         [ +  + ]:        9918 :         if (dest.telemetry.playerHasVehicle) {
     132                 :           4 :             uint8_t idx = dest.telemetry.playerVehicleIdx;
     133         [ +  - ]:           4 :             if (idx < 104) {
     134                 :           4 :                 double currentET = dest.telemetry.telemInfo[idx].mElapsedTime;
     135         [ +  + ]:           4 :                 if (currentET != m_lastElapsedTime) {
     136                 :           2 :                     m_lastElapsedTime = currentET;
     137                 :           2 :                     m_lastUpdateLocalTime = std::chrono::steady_clock::now();
     138                 :             :                 }
     139                 :             :             }
     140                 :             :         } else {
     141                 :        9914 :             m_lastUpdateLocalTime = std::chrono::steady_clock::now();
     142                 :             :         }
     143                 :             : 
     144                 :        9918 :         bool isRealtime = (m_pSharedMemLayout->data.scoring.scoringInfo.mInRealtime != 0);
     145                 :        9918 :         m_smLock->Unlock();
     146                 :        9918 :         return isRealtime;
     147                 :             :     } else {
     148                 :           1 :         return false;
     149                 :             :     }
     150                 :       10137 : }
     151                 :             : 
     152                 :        4766 : bool GameConnector::IsStale(long timeoutMs) const {
     153         [ +  + ]:        4766 :     if (!m_connected.load(std::memory_order_acquire)) return true;
     154                 :             : 
     155                 :        4765 :     auto now = std::chrono::steady_clock::now();
     156   [ +  -  +  - ]:        4765 :     auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastUpdateLocalTime).count();
     157                 :        4765 :     return (diff > timeoutMs);
     158                 :             : }
        

Generated by: LCOV version 2.0-1