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 : : }
|