@@ -41,7 +41,21 @@ static bool IsKnownPID(USHORT pid)
4141 return false ;
4242}
4343
44- // --- G Hub SDK function pointers ---
44+ // --- Steering Wheel SDK function pointers ---
45+
46+ typedef bool (__cdecl *LogiSteeringInit_t)(bool );
47+ typedef bool (__cdecl *LogiUpdate_t)();
48+ typedef bool (__cdecl *LogiIsConnected_t)(int );
49+ typedef bool (__cdecl *LogiPlayLeds_t)(int , float , float , float );
50+ typedef void (__cdecl *LogiSteeringShutdown_t)();
51+
52+ static LogiSteeringInit_t g_SteeringInit = NULL ;
53+ static LogiUpdate_t g_SteeringUpdate = NULL ;
54+ static LogiIsConnected_t g_IsConnected = NULL ;
55+ static LogiPlayLeds_t g_PlayLeds = NULL ;
56+ static LogiSteeringShutdown_t g_SteeringShutdown = NULL ;
57+
58+ // --- G Hub LED SDK function pointers ---
4559
4660typedef bool (*LogiLedInit_t)();
4761typedef bool (*LogiLedInitWithName_t)(const char *);
@@ -73,6 +87,7 @@ LogitechLED::LogitechLED()
7387 , m_ledFunctionId(0 )
7488 , m_deviceIdx(0xFF )
7589 , m_sdkDll(NULL )
90+ , m_steeringDll(NULL )
7691{
7792}
7893
@@ -83,9 +98,18 @@ LogitechLED::~LogitechLED()
8398
8499void LogitechLED::Close ()
85100{
101+ if (m_method == METHOD_STEERING_SDK && g_SteeringShutdown)
102+ g_SteeringShutdown ();
103+
86104 if (m_method == METHOD_SDK && g_LedShutdown)
87105 g_LedShutdown ();
88106
107+ if (m_steeringDll)
108+ {
109+ FreeLibrary (m_steeringDll);
110+ m_steeringDll = NULL ;
111+ }
112+
89113 if (m_sdkDll)
90114 {
91115 FreeLibrary (m_sdkDll);
@@ -100,6 +124,11 @@ void LogitechLED::Close()
100124
101125 m_available = false ;
102126 m_method = METHOD_NONE;
127+ g_SteeringInit = NULL ;
128+ g_SteeringUpdate = NULL ;
129+ g_IsConnected = NULL ;
130+ g_PlayLeds = NULL ;
131+ g_SteeringShutdown = NULL ;
103132 g_LedInit = NULL ;
104133 g_LedSetLighting = NULL ;
105134 g_LedShutdown = NULL ;
@@ -110,11 +139,150 @@ bool LogitechLED::IsAvailable() const
110139 return m_available;
111140}
112141
113- // --- Phase 0: G Hub LED SDK ---
142+ // --- Phase 0: Logitech Steering Wheel SDK ---
143+
144+ bool LogitechLED::TrySteeringSDK ()
145+ {
146+ Log (" === Phase 0: Steering Wheel SDK ===" );
147+
148+ // Search paths for LogitechSteeringWheelEnginesWrapper.dll
149+ const char * searchPaths[] = {
150+ // 1. Same directory as game/DLL (LoadLibrary default search)
151+ " LogitechSteeringWheelEnginesWrapper.dll" ,
152+ // 2. Logitech Steering Wheel SDK install (x86)
153+ " C:\\ Program Files\\ Logitech\\ Logitech Steering Wheel SDK\\ Lib\\ GameEnginesWrapper\\ x86\\ LogitechSteeringWheelEnginesWrapper.dll" ,
154+ " C:\\ Program Files (x86)\\ Logitech\\ Logitech Steering Wheel SDK\\ Lib\\ GameEnginesWrapper\\ x86\\ LogitechSteeringWheelEnginesWrapper.dll" ,
155+ #ifdef _WIN64
156+ // 3. x64 variants
157+ " C:\\ Program Files\\ Logitech\\ Logitech Steering Wheel SDK\\ Lib\\ GameEnginesWrapper\\ x64\\ LogitechSteeringWheelEnginesWrapper.dll" ,
158+ " C:\\ Program Files (x86)\\ Logitech\\ Logitech Steering Wheel SDK\\ Lib\\ GameEnginesWrapper\\ x64\\ LogitechSteeringWheelEnginesWrapper.dll" ,
159+ #endif
160+ // 4. G Hub directory (unlikely but check)
161+ " C:\\ Program Files\\ LGHUB\\ LogitechSteeringWheelEnginesWrapper.dll" ,
162+ " C:\\ Program Files\\ LGHUB\\ sdks\\ LogitechSteeringWheelEnginesWrapper.dll" ,
163+ NULL
164+ };
165+
166+ for (int i = 0 ; searchPaths[i]; i++)
167+ {
168+ m_steeringDll = LoadLibraryA (searchPaths[i]);
169+ if (m_steeringDll)
170+ {
171+ Log (" Loaded: %s" , searchPaths[i]);
172+ break ;
173+ }
174+ }
175+
176+ if (!m_steeringDll)
177+ {
178+ Log (" DLL not found. Searched:" );
179+ for (int i = 0 ; searchPaths[i]; i++)
180+ Log (" - %s" , searchPaths[i]);
181+ Log (" >>> Place LogitechSteeringWheelEnginesWrapper.dll next to the game .exe <<<" );
182+ return false ;
183+ }
184+
185+ // Get function pointers - try plain names first, then mangled
186+ const char * initNames[] = { " LogiSteeringInitialize" , " _LogiSteeringInitialize" , NULL };
187+ const char * updateNames[] = { " LogiUpdate" , " _LogiUpdate" , NULL };
188+ const char * connNames[] = { " LogiIsConnected" , " _LogiIsConnected" , NULL };
189+ const char * ledsNames[] = { " LogiPlayLeds" , " _LogiPlayLeds" , NULL };
190+ const char * shutNames[] = { " LogiSteeringShutdown" , " _LogiSteeringShutdown" , NULL };
191+
192+ for (int i = 0 ; initNames[i] && !g_SteeringInit; i++)
193+ g_SteeringInit = (LogiSteeringInit_t)GetProcAddress (m_steeringDll, initNames[i]);
194+ for (int i = 0 ; updateNames[i] && !g_SteeringUpdate; i++)
195+ g_SteeringUpdate = (LogiUpdate_t)GetProcAddress (m_steeringDll, updateNames[i]);
196+ for (int i = 0 ; connNames[i] && !g_IsConnected; i++)
197+ g_IsConnected = (LogiIsConnected_t)GetProcAddress (m_steeringDll, connNames[i]);
198+ for (int i = 0 ; ledsNames[i] && !g_PlayLeds; i++)
199+ g_PlayLeds = (LogiPlayLeds_t)GetProcAddress (m_steeringDll, ledsNames[i]);
200+ for (int i = 0 ; shutNames[i] && !g_SteeringShutdown; i++)
201+ g_SteeringShutdown = (LogiSteeringShutdown_t)GetProcAddress (m_steeringDll, shutNames[i]);
202+
203+ Log (" LogiSteeringInitialize: %s" , g_SteeringInit ? " FOUND" : " NOT FOUND" );
204+ Log (" LogiUpdate: %s" , g_SteeringUpdate ? " FOUND" : " NOT FOUND" );
205+ Log (" LogiIsConnected: %s" , g_IsConnected ? " FOUND" : " NOT FOUND" );
206+ Log (" LogiPlayLeds: %s" , g_PlayLeds ? " FOUND" : " NOT FOUND" );
207+ Log (" LogiSteeringShutdown: %s" , g_SteeringShutdown ? " FOUND" : " NOT FOUND" );
208+
209+ if (!g_SteeringInit || !g_SteeringUpdate || !g_IsConnected || !g_PlayLeds)
210+ {
211+ Log (" Required functions missing" );
212+ FreeLibrary (m_steeringDll);
213+ m_steeringDll = NULL ;
214+ return false ;
215+ }
216+
217+ // Initialize - ignoreXInputControllers=false so we see Xbox wheels
218+ bool ok = g_SteeringInit (false );
219+ Log (" LogiSteeringInitialize(false) -> %s" , ok ? " OK" : " FAIL" );
220+
221+ if (!ok)
222+ {
223+ Log (" Init failed. Is G Hub running?" );
224+ FreeLibrary (m_steeringDll);
225+ m_steeringDll = NULL ;
226+ return false ;
227+ }
228+
229+ // CRITICAL: Must call LogiUpdate() multiple times for G Hub to enumerate
230+ // devices. Without this, LogiIsConnected() always returns false.
231+ for (int retry = 0 ; retry < 10 ; retry++)
232+ {
233+ Sleep (200 );
234+ g_SteeringUpdate ();
235+
236+ if (g_IsConnected (0 ))
237+ {
238+ Log (" Wheel connected at index 0 (after %d updates)" , retry + 1 );
239+ break ;
240+ }
241+ }
242+
243+ bool connected = g_IsConnected (0 );
244+ Log (" LogiIsConnected(0) -> %s" , connected ? " YES" : " NO" );
245+
246+ if (!connected)
247+ {
248+ // Try index 1
249+ connected = g_IsConnected (1 );
250+ Log (" LogiIsConnected(1) -> %s" , connected ? " YES" : " NO" );
251+ }
252+
253+ if (!connected)
254+ {
255+ Log (" No wheel detected by SDK. Trying LogiPlayLeds anyway..." );
256+ }
257+
258+ // Test LEDs - all on (redline)
259+ g_SteeringUpdate ();
260+ bool led = g_PlayLeds (0 , 100 .0f , 0 .0f , 100 .0f );
261+ Log (" LogiPlayLeds(0, 100, 0, 100) -> %s *** ALL LEDs ON ***" , led ? " OK" : " FAIL" );
262+ Sleep (1000 );
263+
264+ // Test LEDs - half
265+ g_SteeringUpdate ();
266+ led = g_PlayLeds (0 , 50 .0f , 0 .0f , 100 .0f );
267+ Log (" LogiPlayLeds(0, 50, 0, 100) -> %s *** HALF LEDs ***" , led ? " OK" : " FAIL" );
268+ Sleep (1000 );
269+
270+ // Test LEDs - off
271+ g_SteeringUpdate ();
272+ led = g_PlayLeds (0 , 0 .0f , 0 .0f , 100 .0f );
273+ Log (" LogiPlayLeds(0, 0, 0, 100) -> %s *** LEDs OFF ***" , led ? " OK" : " FAIL" );
274+
275+ m_method = METHOD_STEERING_SDK;
276+ m_available = true ;
277+ Log (" === LED CONTROL ACTIVE (Steering Wheel SDK) ===" );
278+ return true ;
279+ }
280+
281+ // --- Phase 1: G Hub LED SDK ---
114282
115283bool LogitechLED::TrySDK ()
116284{
117- Log (" === Phase 0 : G Hub LED SDK ===" );
285+ Log (" === Phase 1 : G Hub LED SDK ===" );
118286
119287#ifdef _WIN64
120288 const char * dllPath = " C:\\ Program Files\\ LGHUB\\ sdks\\ sdk_legacy_led_x64.dll" ;
@@ -452,18 +620,23 @@ bool LogitechLED::TryLegacy(HANDLE h, USHORT outLen)
452620
453621bool LogitechLED::Init ()
454622{
455- Log (" === LogitechLED Init v4 ===" );
623+ Log (" === LogitechLED Init v5 ===" );
456624 Log (" " );
457625
458626 if (m_available) return true ;
459627
460- // Phase 0: G Hub LED SDK (preferred - works with kernel drivers)
628+ // Phase 0: Steering Wheel SDK (LogiPlayLeds - standard for racing games)
629+ if (TrySteeringSDK ())
630+ return true ;
631+
632+ // Phase 1: G Hub LED SDK (keyboard/mouse - fallback)
633+ Log (" " );
461634 if (TrySDK ())
462635 return true ;
463636
464- // Phase 1 : HID++ enumeration (diagnostic only - kernel drivers block)
637+ // Phase 2 : HID++ enumeration (diagnostic only - kernel drivers block)
465638 Log (" " );
466- Log (" === Phase 1 : HID++ enumeration ===" );
639+ Log (" === Phase 2 : HID++ enumeration ===" );
467640
468641 HIDCandidate candidates[MAX_CANDIDATES];
469642 int numCandidates = 0 ;
@@ -485,9 +658,9 @@ bool LogitechLED::Init()
485658 CloseHandle (h);
486659 }
487660
488- // Phase 2 : Legacy fallback
661+ // Phase 3 : Legacy fallback
489662 Log (" " );
490- Log (" === Phase 2 : Legacy ===" );
663+ Log (" === Phase 3 : Legacy ===" );
491664
492665 for (int ci = 0 ; ci < numCandidates; ci++)
493666 {
@@ -520,19 +693,37 @@ bool LogitechLED::SetLEDs(BYTE ledMask)
520693{
521694 if (!m_available) return false ;
522695
696+ if (m_method == METHOD_STEERING_SDK && g_PlayLeds && g_SteeringUpdate)
697+ {
698+ // Map 5-bit LED mask to RPM percentage for LogiPlayLeds
699+ int numLeds = 0 ;
700+ for (int i = 0 ; i < 5 ; i++)
701+ if (ledMask & (1 << i)) numLeds++;
702+
703+ float rpm = numLeds * 20 .0f ; // 0-100
704+ g_SteeringUpdate ();
705+ bool ok = g_PlayLeds (0 , rpm, 0 .0f , 100 .0f );
706+
707+ g_setLedsCallCount++;
708+ if (g_setLedsCallCount <= 20 || !ok)
709+ Log (" SetLEDs(0x%02X) [SteeringSDK rpm=%.0f] -> %s (#%d)" ,
710+ ledMask, rpm, ok ? " OK" : " FAIL" , g_setLedsCallCount);
711+
712+ return ok;
713+ }
714+
523715 if (m_method == METHOD_SDK && g_LedSetLighting)
524716 {
525- // Map 5-bit LED mask to brightness percentage
526717 int numLeds = 0 ;
527718 for (int i = 0 ; i < 5 ; i++)
528719 if (ledMask & (1 << i)) numLeds++;
529720
530- int pct = numLeds * 20 ; // 0-100%
721+ int pct = numLeds * 20 ;
531722 bool ok = g_LedSetLighting (pct, pct, pct);
532723
533724 g_setLedsCallCount++;
534725 if (g_setLedsCallCount <= 20 || !ok)
535- Log (" SetLEDs(0x%02X) [SDK pct=%d] -> %s (#%d)" ,
726+ Log (" SetLEDs(0x%02X) [LedSDK pct=%d] -> %s (#%d)" ,
536727 ledMask, pct, ok ? " OK" : " FAIL" , g_setLedsCallCount);
537728
538729 return ok;
@@ -581,6 +772,21 @@ bool LogitechLED::SetLEDsFromPercent(double percent)
581772 if (percent < 0.0 ) percent = 0.0 ;
582773 if (percent > 1.0 ) percent = 1.0 ;
583774
775+ // For steering SDK, pass percentage directly as RPM for smooth LED progression
776+ if (m_method == METHOD_STEERING_SDK && g_PlayLeds && g_SteeringUpdate)
777+ {
778+ float rpm = (float )(percent * 100.0 );
779+ g_SteeringUpdate ();
780+ bool ok = g_PlayLeds (0 , rpm, 0 .0f , 100 .0f );
781+
782+ g_setLedsCallCount++;
783+ if (g_setLedsCallCount <= 20 || !ok)
784+ Log (" SetLEDsFromPercent(%.2f) [SteeringSDK rpm=%.0f] -> %s (#%d)" ,
785+ percent, rpm, ok ? " OK" : " FAIL" , g_setLedsCallCount);
786+
787+ return ok;
788+ }
789+
584790 BYTE mask = 0 ;
585791 if (percent >= 0.2 ) mask |= 0x01 ;
586792 if (percent >= 0.4 ) mask |= 0x02 ;
0 commit comments