LCOV - code coverage report
Current view: top level - src - GameConnector.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 94.8 % 97 92
Test Date: 2026-03-03 13:56:25 Functions: 100.0 % 10 10
Branches: 60.5 % 114 69

             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                 :       56536 : GameConnector& GameConnector::Get() {
      12   [ +  +  +  -  :       56536 :     static GameConnector instance;
             +  -  -  - ]
      13                 :       56535 :     return instance;
      14                 :             : }
      15                 :             : 
      16                 :           1 : GameConnector::GameConnector() {}
      17                 :             : 
      18                 :           1 : GameConnector::~GameConnector() {
      19                 :           1 :     Disconnect();
      20                 :           1 : }
      21                 :             : 
      22                 :       10054 : void GameConnector::Disconnect() {
      23         [ +  - ]:       10054 :     std::lock_guard<std::mutex> lock(m_mutex);
      24         [ +  - ]:       10054 :     _DisconnectLocked();
      25                 :       10054 : }
      26                 :             : 
      27                 :       20107 : void GameConnector::_DisconnectLocked() {
      28                 :             : #if defined(_WIN32) || defined(HEADLESS_GUI)
      29         [ +  + ]:       20107 :     if (m_pSharedMemLayout) {
      30                 :       10051 :         UnmapViewOfFile(m_pSharedMemLayout);
      31                 :       10051 :         m_pSharedMemLayout = nullptr;
      32                 :             :     }
      33         [ +  + ]:       20107 :     if (m_hMapFile) {
      34                 :       10051 :         CloseHandle(m_hMapFile);
      35                 :       10051 :         m_hMapFile = NULL;
      36                 :             :     }
      37                 :       20107 :     m_hwndGame = NULL;
      38                 :             : #endif
      39                 :       20107 :     m_smLock.reset();
      40                 :       20107 :     m_connected = false;
      41                 :       20107 :     m_processId = 0;
      42                 :             : 
      43                 :             :     // Reset heartbeat state
      44                 :       20107 :     m_lastElapsedTime = -1.0;
      45                 :       20107 :     m_lastSteer = 0.0;
      46                 :       20107 :     m_lastUpdateLocalTime = std::chrono::steady_clock::time_point();
      47                 :       20107 : }
      48                 :             : 
      49                 :       10057 : bool GameConnector::TryConnect() {
      50         [ +  - ]:       10057 :     std::lock_guard<std::mutex> lock(m_mutex);
      51         [ +  + ]:       10057 :     if (m_connected) return true;
      52                 :             : 
      53                 :             :     // Ensure we don't leak handles from a previous partial/failed attempt
      54         [ +  - ]:       10051 :     _DisconnectLocked();
      55                 :             : 
      56                 :             : #if defined(_WIN32) || defined(HEADLESS_GUI)
      57         [ +  - ]:       10051 :     m_hMapFile = OpenFileMappingA(FILE_MAP_READ, FALSE, LMU_SHARED_MEMORY_FILE);
      58                 :             :     
      59         [ -  + ]:       10051 :     if (m_hMapFile == NULL) {
      60                 :           0 :         return false;
      61                 :             :     } 
      62                 :             : 
      63         [ +  - ]:       10051 :     m_pSharedMemLayout = (SharedMemoryLayout*)MapViewOfFile(m_hMapFile, FILE_MAP_READ, 0, 0, sizeof(SharedMemoryLayout));
      64         [ -  + ]:       10051 :     if (m_pSharedMemLayout == NULL) {
      65   [ #  #  #  # ]:           0 :         std::cerr << "[GameConnector] Could not map view of file." << std::endl;
      66   [ #  #  #  # ]:           0 :         Logger::Get().LogWin32Error("MapViewOfFile", GetLastError());
      67         [ #  # ]:           0 :         _DisconnectLocked();
      68                 :           0 :         return false;
      69                 :             :     }
      70                 :             : 
      71   [ +  -  +  - ]:       10051 :     m_smLock = SafeSharedMemoryLock::MakeSafeSharedMemoryLock();
      72         [ +  + ]:       10051 :     if (!m_smLock.has_value()) {
      73   [ +  -  +  - ]:           1 :         std::cerr << "[GameConnector] Failed to init LMU Shared Memory Lock" << std::endl;
      74   [ +  -  +  - ]:           1 :         Logger::Get().Log("Failed to init SafeSharedMemoryLock.");
      75         [ +  - ]:           1 :         _DisconnectLocked();
      76                 :           1 :         return false;
      77                 :             :     }
      78                 :             : 
      79                 :       10050 :     HWND hwnd = m_pSharedMemLayout->data.generic.appInfo.mAppWindow;
      80         [ +  + ]:       10050 :     if (hwnd) {
      81                 :           4 :         m_hwndGame = hwnd; // Store HWND for liveness check (IsWindow)
      82                 :             :         // Note: multiple threads might access shared memory, but HWND is usually stable during session.
      83                 :             :         // We use IsWindow(m_hwndGame) instead of OpenProcess to avoid AV heuristics flagging "Process Access".
      84                 :             :     }
      85                 :             : 
      86                 :       10050 :     m_connected = true;
      87                 :       10050 :     m_lastUpdateLocalTime = std::chrono::steady_clock::now();
      88   [ +  -  +  - ]:       10050 :     std::cout << "[GameConnector] Connected to LMU Shared Memory." << std::endl;
      89   [ +  -  +  - ]:       10050 :     Logger::Get().Log("Connected to LMU Shared Memory.");
      90                 :       10050 :     return true;
      91                 :             : #else
      92                 :             :     return false;
      93                 :             : #endif
      94                 :       10057 : }
      95                 :             : 
      96                 :           7 : bool GameConnector::CheckLegacyConflict() {
      97                 :             : #if defined(_WIN32) || defined(HEADLESS_GUI)
      98                 :           7 :     HANDLE hLegacy = OpenFileMappingA(FILE_MAP_READ, FALSE, LEGACY_SHARED_MEMORY_NAME);
      99         [ +  + ]:           7 :     if (hLegacy) {
     100                 :           2 :         std::cout << "[Warning] Legacy rFactor 2 Shared Memory Plugin detected. This may conflict with LMU 1.2 data." << std::endl;
     101                 :           2 :         CloseHandle(hLegacy);
     102                 :           2 :         return true;
     103                 :             :     }
     104                 :             : #endif
     105                 :           5 :     return false;
     106                 :             : }
     107                 :             : 
     108                 :        5155 : bool GameConnector::IsConnected() const {
     109         [ +  + ]:        5155 :   if (!m_connected.load(std::memory_order_acquire)) return false;
     110                 :             : 
     111         [ +  - ]:        4943 :   std::lock_guard<std::mutex> lock(m_mutex);
     112         [ -  + ]:        4943 :   if (!m_connected.load(std::memory_order_relaxed)) return false;
     113                 :             : 
     114                 :             : #if defined(_WIN32) || defined(HEADLESS_GUI)
     115         [ +  + ]:        4943 :   if (m_hwndGame) {
     116         [ +  + ]:        4767 :     if (!IsWindow(m_hwndGame)) {
     117                 :             :       // Window is gone, game likely exited
     118         [ +  - ]:           1 :       const_cast<GameConnector*>(this)->_DisconnectLocked();
     119                 :           1 :       return false;
     120                 :             :     }
     121                 :             :   }
     122                 :             : #endif
     123                 :             : 
     124   [ +  -  +  -  :        4942 :   return m_connected.load(std::memory_order_relaxed) && m_pSharedMemLayout && m_smLock.has_value();
                   +  - ]
     125                 :        4943 : }
     126                 :             : 
     127                 :       26520 : bool GameConnector::CopyTelemetry(SharedMemoryObjectOut& dest) {
     128         [ +  + ]:       26520 :     if (!m_connected.load(std::memory_order_acquire)) return false;
     129                 :             : 
     130         [ +  - ]:        9476 :     std::lock_guard<std::mutex> lock(m_mutex);
     131   [ +  +  +  -  :        9476 :     if (!m_connected.load(std::memory_order_relaxed) || !m_pSharedMemLayout || !m_smLock.has_value()) return false;
             -  +  +  + ]
     132                 :             : 
     133         [ +  + ]:        9032 :     if (m_smLock->Lock(50)) {
     134                 :        9031 :         CopySharedMemoryObj(dest, m_pSharedMemLayout->data);
     135                 :             :         
     136         [ +  + ]:        9031 :         if (dest.telemetry.playerHasVehicle) {
     137                 :        4768 :             uint8_t idx = dest.telemetry.playerVehicleIdx;
     138         [ +  - ]:        4768 :             if (idx < 104) {
     139                 :        4768 :                 double currentET = dest.telemetry.telemInfo[idx].mElapsedTime;
     140                 :        4768 :                 double currentSteer = dest.telemetry.telemInfo[idx].mUnfilteredSteering;
     141                 :             : 
     142                 :             :                 // v0.7.118: Heartbeat from both game time AND user input (Issue #184).
     143                 :             :                 // This keeps telemetry "fresh" in menus as long as the wheel is moving.
     144   [ +  +  +  + ]:        4768 :                 if (currentET != m_lastElapsedTime || currentSteer != m_lastSteer) {
     145                 :         556 :                     m_lastElapsedTime = currentET;
     146                 :         556 :                     m_lastSteer = currentSteer;
     147                 :         556 :                     m_lastUpdateLocalTime = std::chrono::steady_clock::now();
     148                 :             :                 }
     149                 :             :             }
     150                 :             :         } else {
     151                 :        4263 :             m_lastUpdateLocalTime = std::chrono::steady_clock::now();
     152                 :             :         }
     153                 :             : 
     154                 :        9031 :         bool isRealtime = (m_pSharedMemLayout->data.scoring.scoringInfo.mInRealtime != 0);
     155                 :        9031 :         m_smLock->Unlock();
     156                 :        9031 :         return isRealtime;
     157                 :             :     } else {
     158                 :           1 :         return false;
     159                 :             :     }
     160                 :        9476 : }
     161                 :             : 
     162                 :        4770 : bool GameConnector::IsStale(long timeoutMs) const {
     163         [ +  + ]:        4770 :     if (!m_connected.load(std::memory_order_acquire)) return true;
     164                 :             : 
     165                 :        4769 :     auto now = std::chrono::steady_clock::now();
     166   [ +  -  +  - ]:        4769 :     auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastUpdateLocalTime).count();
     167                 :        4769 :     return (diff > timeoutMs);
     168                 :             : }
        

Generated by: LCOV version 2.0-1