Branch data Line data Source code
1 : : #ifdef _WIN32
2 : : #include <windows.h>
3 : : #else
4 : : #include <signal.h>
5 : : #endif
6 : : #include <iostream>
7 : : #include <cmath>
8 : : #include <algorithm>
9 : : #include <thread>
10 : : #include <chrono>
11 : :
12 : : #include "FFBEngine.h"
13 : : #include "GuiLayer.h"
14 : : #include "Config.h"
15 : : #include "DirectInputFFB.h"
16 : : #include "GameConnector.h"
17 : : #include "Version.h"
18 : : #include "Logger.h" // Added Logger
19 : : #include "RateMonitor.h"
20 : : #include "HealthMonitor.h"
21 : : #include <optional>
22 : : #include <atomic>
23 : : #include <mutex>
24 : :
25 : : // Constants
26 : :
27 : : // Threading Globals
28 : : #ifndef LMUFFB_UNIT_TEST
29 : : std::atomic<bool> g_running(true);
30 : : std::atomic<bool> g_ffb_active(true);
31 : :
32 : : SharedMemoryObjectOut g_localData; // Local copy of shared memory
33 : :
34 : : FFBEngine g_engine;
35 : : std::recursive_mutex g_engine_mutex; // Protects settings access if GUI changes them
36 : : #else
37 : : extern std::atomic<bool> g_running;
38 : : extern std::atomic<bool> g_ffb_active;
39 : : extern SharedMemoryObjectOut g_localData;
40 : : extern FFBEngine g_engine;
41 : : extern std::recursive_mutex g_engine_mutex;
42 : : #endif
43 : :
44 : : // --- FFB Loop (High Priority 400Hz) ---
45 : 9 : void FFBThread() {
46 [ + - + - ]: 9 : std::cout << "[FFB] Loop Started." << std::endl;
47 [ + - ]: 9 : RateMonitor loopMonitor;
48 [ + - ]: 9 : RateMonitor telemMonitor;
49 [ + - ]: 9 : RateMonitor hwMonitor;
50 [ + - ]: 9 : RateMonitor torqueMonitor;
51 [ + - ]: 9 : RateMonitor genTorqueMonitor;
52 : 9 : double lastET = -1.0;
53 : 9 : double lastTorque = -9999.0;
54 : 9 : float lastGenTorque = -9999.0f;
55 : :
56 : : // Extended monitors for Issue #133
57 : : struct ChannelMonitor {
58 : : RateMonitor monitor;
59 : : double lastValue = -1e18;
60 : 62073 : void Update(double newValue) {
61 [ + + ]: 62073 : if (newValue != lastValue) {
62 : 81 : monitor.RecordEvent();
63 : 81 : lastValue = newValue;
64 : : }
65 : 62073 : }
66 : : };
67 : :
68 [ + - + - : 9 : ChannelMonitor mAccX, mAccY, mAccZ;
+ - ]
69 [ + - + - : 9 : ChannelMonitor mVelX, mVelY, mVelZ;
+ - ]
70 [ + - + - : 9 : ChannelMonitor mRotX, mRotY, mRotZ;
+ - ]
71 [ + - + - : 9 : ChannelMonitor mRotAccX, mRotAccY, mRotAccZ;
+ - ]
72 [ + - + - ]: 9 : ChannelMonitor mUnfSteer, mFilSteer;
73 [ + - ]: 9 : ChannelMonitor mRPM;
74 [ + - + - : 9 : ChannelMonitor mLoadFL, mLoadFR, mLoadRL, mLoadRR;
+ - + - ]
75 [ + - + - : 9 : ChannelMonitor mLatFL, mLatFR, mLatRL, mLatRR;
+ - + - ]
76 [ + - + - : 9 : ChannelMonitor mPosX, mPosY, mPosZ;
+ - ]
77 [ + - ]: 9 : ChannelMonitor mDtMon;
78 : :
79 : : // Precise Timing: Target 400Hz (2500 microseconds)
80 : 9 : const std::chrono::microseconds target_period(2500);
81 : 9 : auto next_tick = std::chrono::steady_clock::now();
82 : :
83 [ + + ]: 4771 : while (g_running) {
84 [ + - ]: 4762 : loopMonitor.RecordEvent();
85 [ + - + - ]: 4762 : next_tick += target_period;
86 : :
87 : 4762 : double force = 0.0;
88 : 4762 : double dt = 0.0025; // Default 400Hz
89 : 4762 : bool restricted = true;
90 : :
91 [ + - + - : 4762 : if (g_ffb_active && GameConnector::Get().IsConnected()) {
+ - + - +
- ]
92 [ + - + - ]: 4762 : bool in_realtime = GameConnector::Get().CopyTelemetry(g_localData);
93 [ + - + - ]: 4762 : bool is_stale = GameConnector::Get().IsStale(100);
94 : :
95 : : static bool was_in_menu = true;
96 [ + + + + ]: 4762 : if (was_in_menu && in_realtime) {
97 [ + - + - ]: 2 : std::cout << "[Game] User entered driving session." << std::endl;
98 [ + - + - : 2 : if (Config::m_auto_start_logging && !AsyncLogger::Get().IsLogging()) {
+ - + - ]
99 : 2 : SessionInfo info;
100 [ + - ]: 2 : info.app_version = LMUFFB_VERSION;
101 [ + - ]: 2 : std::lock_guard<std::recursive_mutex> lock(g_engine_mutex);
102 [ + - ]: 2 : info.vehicle_name = g_engine.m_vehicle_name;
103 [ + - ]: 2 : info.track_name = g_engine.m_track_name;
104 [ + - ]: 2 : info.driver_name = "Auto";
105 : 2 : info.gain = g_engine.m_gain;
106 : 2 : info.understeer_effect = g_engine.m_understeer_effect;
107 : 2 : info.sop_effect = g_engine.m_sop_effect;
108 : 2 : info.slope_enabled = g_engine.m_slope_detection_enabled;
109 : 2 : info.slope_sensitivity = g_engine.m_slope_sensitivity;
110 : 2 : info.slope_threshold = (float)g_engine.m_slope_min_threshold;
111 : 2 : info.slope_alpha_threshold = g_engine.m_slope_alpha_threshold;
112 : 2 : info.slope_decay_rate = g_engine.m_slope_decay_rate;
113 : 2 : info.torque_passthrough = g_engine.m_torque_passthrough;
114 [ + - + - ]: 2 : AsyncLogger::Get().Start(info, Config::m_log_path);
115 : 2 : }
116 [ + + + + ]: 4762 : } else if (!was_in_menu && !in_realtime) {
117 [ + - + - ]: 1 : std::cout << "[Game] User exited to menu (FFB Muted)." << std::endl;
118 [ + - + - : 1 : if (Config::m_auto_start_logging && AsyncLogger::Get().IsLogging()) {
- + - + ]
119 [ # # ]: 0 : AsyncLogger::Get().Stop();
120 : : }
121 : : }
122 : 4762 : was_in_menu = !in_realtime;
123 : :
124 : 4762 : bool should_output = false;
125 : :
126 : : // v0.7.78 FIX: Support stationary/garage soft lock (Issue #184)
127 : : // We now process FFB even if not in realtime, provided we have a player vehicle.
128 : : // This allows Soft Lock to function in the garage or when AI is driving.
129 [ + + + - ]: 4762 : if (!is_stale && g_localData.telemetry.playerHasVehicle) {
130 : 2299 : uint8_t idx = g_localData.telemetry.playerVehicleIdx;
131 [ + - ]: 2299 : if (idx < 104) {
132 : 2299 : auto& scoring = g_localData.scoring.vehScoringInfo[idx];
133 : 2299 : TelemInfoV01* pPlayerTelemetry = &g_localData.telemetry.telemInfo[idx];
134 : 2299 : dt = pPlayerTelemetry->mDeltaTime;
135 : :
136 : : // Track telemetry update rate
137 [ + + ]: 2299 : if (pPlayerTelemetry->mElapsedTime != lastET) {
138 [ + - ]: 552 : telemMonitor.RecordEvent();
139 : 552 : lastET = pPlayerTelemetry->mElapsedTime;
140 : : }
141 : :
142 : : // Track torque update rates
143 [ + + ]: 2299 : if (pPlayerTelemetry->mSteeringShaftTorque != lastTorque) {
144 [ + - ]: 552 : torqueMonitor.RecordEvent();
145 : 552 : lastTorque = pPlayerTelemetry->mSteeringShaftTorque;
146 : : }
147 [ + + ]: 2299 : if (g_localData.generic.FFBTorque != lastGenTorque) {
148 [ + - ]: 3 : genTorqueMonitor.RecordEvent();
149 : 3 : lastGenTorque = g_localData.generic.FFBTorque;
150 : : }
151 : :
152 : : // Extended monitoring (Issue #133)
153 [ + - ]: 2299 : mAccX.Update(pPlayerTelemetry->mLocalAccel.x);
154 [ + - ]: 2299 : mAccY.Update(pPlayerTelemetry->mLocalAccel.y);
155 [ + - ]: 2299 : mAccZ.Update(pPlayerTelemetry->mLocalAccel.z);
156 [ + - ]: 2299 : mVelX.Update(pPlayerTelemetry->mLocalVel.x);
157 [ + - ]: 2299 : mVelY.Update(pPlayerTelemetry->mLocalVel.y);
158 [ + - ]: 2299 : mVelZ.Update(pPlayerTelemetry->mLocalVel.z);
159 [ + - ]: 2299 : mRotX.Update(pPlayerTelemetry->mLocalRot.x);
160 [ + - ]: 2299 : mRotY.Update(pPlayerTelemetry->mLocalRot.y);
161 [ + - ]: 2299 : mRotZ.Update(pPlayerTelemetry->mLocalRot.z);
162 [ + - ]: 2299 : mRotAccX.Update(pPlayerTelemetry->mLocalRotAccel.x);
163 [ + - ]: 2299 : mRotAccY.Update(pPlayerTelemetry->mLocalRotAccel.y);
164 [ + - ]: 2299 : mRotAccZ.Update(pPlayerTelemetry->mLocalRotAccel.z);
165 [ + - ]: 2299 : mUnfSteer.Update(pPlayerTelemetry->mUnfilteredSteering);
166 [ + - ]: 2299 : mFilSteer.Update(pPlayerTelemetry->mFilteredSteering);
167 [ + - ]: 2299 : mRPM.Update(pPlayerTelemetry->mEngineRPM);
168 [ + - ]: 2299 : mLoadFL.Update(pPlayerTelemetry->mWheel[0].mTireLoad);
169 [ + - ]: 2299 : mLoadFR.Update(pPlayerTelemetry->mWheel[1].mTireLoad);
170 [ + - ]: 2299 : mLoadRL.Update(pPlayerTelemetry->mWheel[2].mTireLoad);
171 [ + - ]: 2299 : mLoadRR.Update(pPlayerTelemetry->mWheel[3].mTireLoad);
172 [ + - ]: 2299 : mLatFL.Update(pPlayerTelemetry->mWheel[0].mLateralForce);
173 [ + - ]: 2299 : mLatFR.Update(pPlayerTelemetry->mWheel[1].mLateralForce);
174 [ + - ]: 2299 : mLatRL.Update(pPlayerTelemetry->mWheel[2].mLateralForce);
175 [ + - ]: 2299 : mLatRR.Update(pPlayerTelemetry->mWheel[3].mLateralForce);
176 [ + - ]: 2299 : mPosX.Update(pPlayerTelemetry->mPos.x);
177 [ + - ]: 2299 : mPosY.Update(pPlayerTelemetry->mPos.y);
178 [ + - ]: 2299 : mPosZ.Update(pPlayerTelemetry->mPos.z);
179 [ + - ]: 2299 : mDtMon.Update(pPlayerTelemetry->mDeltaTime);
180 : :
181 [ + - ]: 2299 : std::lock_guard<std::recursive_mutex> lock(g_engine_mutex);
182 : : // Determine if full FFB is allowed.
183 : : // full_allowed requires: player control, not disqualified, and in realtime.
184 [ + - - + : 2299 : bool full_allowed = g_engine.IsFFBAllowed(scoring, g_localData.scoring.scoringInfo.mGamePhase) && in_realtime;
- - ]
185 : :
186 : : // v0.7.108: Explicitly zero force if not in realtime (Issue #174).
187 : : // v0.7.118: REMOVED the explicit zeroing (Issue #184) to allow Soft Lock in menus.
188 : : // We still call calculate_force with full_allowed=false which internally mutes
189 : : // all effects except Soft Lock.
190 : : // Force dt to 0.0025 to ensure consistent internal physics regardless of game jitter.
191 [ + - ]: 2299 : force = g_engine.calculate_force(pPlayerTelemetry, scoring.mVehicleClass, scoring.mVehicleName, g_localData.generic.FFBTorque, full_allowed, 0.0025);
192 : 2299 : should_output = true;
193 : :
194 : : // If not full_allowed, use tighter slew rate limiting
195 [ - + - - ]: 2299 : restricted = !full_allowed || (scoring.mFinishStatus != 0);
196 : 2299 : }
197 : : }
198 : :
199 [ + + ]: 4762 : if (!should_output) force = 0.0;
200 : :
201 : : // Warning for low sample rate (Issue #133)
202 [ + + + - ]: 4762 : static auto lastWarningTime = std::chrono::steady_clock::now();
203 : :
204 : 4762 : HealthStatus health;
205 : : {
206 [ + - ]: 4762 : std::lock_guard<std::recursive_mutex> lock(g_engine_mutex);
207 [ - + ]: 4762 : double t_rate = (g_engine.m_torque_source == 1) ? genTorqueMonitor.GetRate() : torqueMonitor.GetRate();
208 : 4762 : health = HealthMonitor::Check(loopMonitor.GetRate(), telemMonitor.GetRate(), t_rate, g_engine.m_torque_source);
209 : 4762 : }
210 : :
211 [ + + - + ]: 4762 : if (in_realtime && !health.is_healthy) {
212 : 0 : auto now = std::chrono::steady_clock::now();
213 [ # # # # : 0 : if (std::chrono::duration_cast<std::chrono::seconds>(now - lastWarningTime).count() >= 5) {
# # ]
214 [ # # ]: 0 : std::string reason = "";
215 [ # # # # : 0 : if (health.loop_low) reason += "Loop=" + std::to_string((int)health.loop_rate) + "Hz ";
# # # # ]
216 [ # # # # : 0 : if (health.telem_low) reason += "Telemetry=" + std::to_string((int)health.telem_rate) + "Hz ";
# # # # ]
217 [ # # # # : 0 : if (health.torque_low) reason += "Torque=" + std::to_string((int)health.torque_rate) + "Hz (Target " + std::to_string((int)health.expected_torque_rate) + "Hz) ";
# # # # #
# # # ]
218 : :
219 [ # # # # : 0 : std::cout << "[WARNING] Low Sample Rate detected: " << reason << std::endl;
# # ]
220 [ # # # # ]: 0 : Logger::Get().Log("Low Sample Rate detected: %s", reason.c_str());
221 : 0 : lastWarningTime = now;
222 : 0 : }
223 : : }
224 : : }
225 : :
226 : : // Safety Layer (v0.7.49): Slew Rate Limiting and NaN protection
227 : : // v0.7.48: Always update hardware even if disconnected/inactive to ensure zeroing
228 : : {
229 [ + - ]: 4762 : std::lock_guard<std::recursive_mutex> lock(g_engine_mutex);
230 [ - + ]: 4762 : if (dt < 0.0001) dt = 0.0025;
231 : :
232 : : // Push rates to engine for GUI/Snapshot
233 : 4762 : g_engine.m_ffb_rate = loopMonitor.GetRate();
234 : 4762 : g_engine.m_telemetry_rate = telemMonitor.GetRate();
235 : 4762 : g_engine.m_hw_rate = hwMonitor.GetRate();
236 : 4762 : g_engine.m_torque_rate = torqueMonitor.GetRate();
237 : 4762 : g_engine.m_gen_torque_rate = genTorqueMonitor.GetRate();
238 : :
239 [ + - ]: 4762 : force = g_engine.ApplySafetySlew(force, dt, restricted); // TODO: review for correctedness and bugs
240 : 4762 : }
241 : :
242 [ + - + - : 4762 : if (DirectInputFFB::Get().UpdateForce(force)) {
- + ]
243 [ # # ]: 0 : hwMonitor.RecordEvent();
244 : : }
245 : :
246 : : // Extended Logging (Issue #133)
247 [ + + + - ]: 4762 : static auto lastExtLogTime = std::chrono::steady_clock::now();
248 : 4762 : auto now = std::chrono::steady_clock::now();
249 [ + - + - : 4762 : if (std::chrono::duration_cast<std::chrono::seconds>(now - lastExtLogTime).count() >= 5) {
+ + ]
250 : 2 : lastExtLogTime = now;
251 [ + - + - : 2 : if (GameConnector::Get().IsConnected() && g_localData.telemetry.playerHasVehicle) {
+ - + - +
- ]
252 [ + - + - ]: 2 : Logger::Get().Log("--- Telemetry Sample Rates (Hz) ---");
253 [ + - + - ]: 2 : Logger::Get().Log("Loop: %.1f, ET: %.1f, HW: %.1f", loopMonitor.GetRate(), telemMonitor.GetRate(), hwMonitor.GetRate());
254 [ + - + - ]: 2 : Logger::Get().Log("Torque: Shaft=%.1f, Generic=%.1f", torqueMonitor.GetRate(), genTorqueMonitor.GetRate());
255 [ + - + - ]: 2 : Logger::Get().Log("Accel: X=%.1f, Y=%.1f, Z=%.1f", mAccX.monitor.GetRate(), mAccY.monitor.GetRate(), mAccZ.monitor.GetRate());
256 [ + - + - ]: 2 : Logger::Get().Log("Vel: X=%.1f, Y=%.1f, Z=%.1f", mVelX.monitor.GetRate(), mVelY.monitor.GetRate(), mVelZ.monitor.GetRate());
257 [ + - + - ]: 2 : Logger::Get().Log("Rot: X=%.1f, Y=%.1f, Z=%.1f", mRotX.monitor.GetRate(), mRotY.monitor.GetRate(), mRotZ.monitor.GetRate());
258 [ + - + - ]: 2 : Logger::Get().Log("RotAcc: X=%.1f, Y=%.1f, Z=%.1f", mRotAccX.monitor.GetRate(), mRotAccY.monitor.GetRate(), mRotAccZ.monitor.GetRate());
259 [ + - + - ]: 2 : Logger::Get().Log("Steering: Unf=%.1f, Fil=%.1f, RPM=%.1f", mUnfSteer.monitor.GetRate(), mFilSteer.monitor.GetRate(), mRPM.monitor.GetRate());
260 [ + - + - ]: 2 : Logger::Get().Log("Load: FL=%.1f, FR=%.1f, RL=%.1f, RR=%.1f", mLoadFL.monitor.GetRate(), mLoadFR.monitor.GetRate(), mLoadRL.monitor.GetRate(), mLoadRR.monitor.GetRate());
261 [ + - + - ]: 2 : Logger::Get().Log("LatForce: FL=%.1f, FR=%.1f, RL=%.1f, RR=%.1f", mLatFL.monitor.GetRate(), mLatFR.monitor.GetRate(), mLatRL.monitor.GetRate(), mLatRR.monitor.GetRate());
262 [ + - + - ]: 2 : Logger::Get().Log("Pos: X=%.1f, Y=%.1f, Z=%.1f, DeltaTime=%.1f", mPosX.monitor.GetRate(), mPosY.monitor.GetRate(), mPosZ.monitor.GetRate(), mDtMon.monitor.GetRate());
263 [ + - + - ]: 2 : Logger::Get().Log("-----------------------------------");
264 : : }
265 : : }
266 : :
267 : : // Precise Timing: Sleep until next tick
268 [ + - ]: 4762 : std::this_thread::sleep_until(next_tick);
269 : : }
270 : :
271 [ + - + - ]: 9 : std::cout << "[FFB] Loop Stopped." << std::endl;
272 : 9 : }
273 : :
274 : : #ifndef _WIN32
275 : 2 : void handle_sigterm(int sig) {
276 : 2 : g_running = false;
277 : 2 : }
278 : : #endif
279 : :
280 : : #ifdef LMUFFB_UNIT_TEST
281 : 4 : int lmuffb_app_main(int argc, char* argv[]) noexcept {
282 : : #else
283 : : int main(int argc, char* argv[]) noexcept {
284 : : #endif
285 : : try {
286 : : #ifdef _WIN32
287 : : timeBeginPeriod(1);
288 : : #else
289 : 4 : signal(SIGTERM, handle_sigterm);
290 : 4 : signal(SIGINT, handle_sigterm);
291 : : #endif
292 : :
293 : 4 : bool headless = false;
294 [ + + ]: 8 : for (int i = 1; i < argc; ++i) {
295 [ + - + - : 8 : if (std::string(argv[i]) == "--headless") headless = true;
+ + ]
296 : : }
297 : :
298 [ + - + - ]: 4 : std::cout << "Starting lmuFFB (C++ Port)..." << std::endl;
299 : : // Initialize persistent debug logging for crash analysis
300 [ + - + - : 8 : Logger::Get().Init("lmuffb_debug.log");
+ - ]
301 [ + - + - ]: 4 : Logger::Get().Log("Application Started. Version: %s", LMUFFB_VERSION);
302 [ + + + - : 4 : if (headless) Logger::Get().Log("Mode: HEADLESS");
+ - ]
303 [ + - + - ]: 1 : else Logger::Get().Log("Mode: GUI");
304 : :
305 [ + - ]: 4 : Preset::ApplyDefaultsToEngine(g_engine);
306 [ + - + - ]: 4 : Config::Load(g_engine);
307 : :
308 [ + + ]: 4 : if (!headless) {
309 [ + - - + ]: 1 : if (!GuiLayer::Init()) {
310 [ # # # # ]: 0 : std::cerr << "Failed to initialize GUI." << std::endl;
311 : : }
312 [ + - + - : 1 : DirectInputFFB::Get().Initialize(reinterpret_cast<HWND>(GuiLayer::GetWindowHandle()));
+ - ]
313 : : } else {
314 [ + - + - ]: 3 : std::cout << "Running in HEADLESS mode." << std::endl;
315 [ + - + - ]: 3 : DirectInputFFB::Get().Initialize(NULL);
316 : : }
317 : :
318 [ + - + - : 4 : if (GameConnector::Get().CheckLegacyConflict()) {
- + ]
319 [ # # # # ]: 0 : std::cout << "[Info] Legacy rF2 plugin detected (not a problem for LMU 1.2+)" << std::endl;
320 : : }
321 : :
322 [ + - + - : 4 : if (!GameConnector::Get().TryConnect()) {
- + ]
323 [ # # # # ]: 0 : std::cout << "Game not running or Shared Memory not ready. Waiting..." << std::endl;
324 : : }
325 : :
326 [ + - ]: 4 : std::thread ffb_thread(FFBThread);
327 [ + - + - ]: 4 : std::cout << "[GUI] Main Loop Started." << std::endl;
328 : :
329 [ + + ]: 30 : while (g_running) {
330 [ + - ]: 26 : GuiLayer::Render(g_engine);
331 : :
332 : : // Process background save requests from the FFB thread (v0.7.70)
333 [ + + ]: 26 : if (Config::m_needs_save.exchange(false)) {
334 [ + - + - ]: 2 : Config::Save(g_engine);
335 : : }
336 : :
337 : : // Maintain a consistent 60Hz message loop even when backgrounded
338 : : // to ensure DirectInput performance and reliability.
339 [ + - ]: 26 : std::this_thread::sleep_for(std::chrono::milliseconds(16));
340 : : }
341 : :
342 [ + - + - ]: 4 : Config::Save(g_engine);
343 [ + + ]: 4 : if (!headless) {
344 [ + - + - ]: 1 : Logger::Get().Log("Shutting down GUI...");
345 [ + - ]: 1 : GuiLayer::Shutdown(g_engine);
346 : : }
347 [ + - ]: 4 : if (ffb_thread.joinable()) {
348 [ + - + - ]: 4 : Logger::Get().Log("Stopping FFB Thread...");
349 : 4 : g_running = false; // Ensure loop breaks
350 [ + - ]: 4 : ffb_thread.join();
351 [ + - + - ]: 4 : Logger::Get().Log("FFB Thread Stopped.");
352 : : }
353 [ + - + - ]: 4 : DirectInputFFB::Get().Shutdown();
354 [ + - + - ]: 4 : Logger::Get().Log("Main Loop Ended. Clean Exit.");
355 : :
356 : 4 : return 0;
357 [ - - ]: 4 : } catch (const std::exception& e) {
358 : 0 : fprintf(stderr, "Fatal exception: %s\n", e.what());
359 : : // Attempt to log if possible, but don't risk more throws
360 [ - - - - ]: 0 : try { Logger::Get().Log("Fatal exception: %s", e.what()); } catch(...) { (void)0; }
361 : 0 : return 1;
362 : 0 : } catch (...) {
363 : 0 : fprintf(stderr, "Fatal unknown exception.\n");
364 [ - - - - ]: 0 : try { Logger::Get().Log("Fatal unknown exception."); } catch(...) { (void)0; }
365 : 0 : return 1;
366 : 0 : }
367 : : }
|