33#include < stdio.h>
44#include < stdarg.h>
55
6+ // --- Logitech LED Escape protocol (from SDK "Independent" sample) ---
7+
8+ static const DWORD ESCAPE_COMMAND_LEDS = 0 ;
9+ static const DWORD LEDS_VERSION_NUMBER = 0x00000001 ;
10+
11+ struct LedsRpmData
12+ {
13+ FLOAT currentRPM;
14+ FLOAT rpmFirstLedTurnsOn;
15+ FLOAT rpmRedLine;
16+ };
17+
18+ struct WheelData
19+ {
20+ DWORD size;
21+ DWORD versionNbr;
22+ LedsRpmData rpmData;
23+ };
24+
25+ // --- Known Logitech wheel VID/PIDs ---
26+
27+ static const DWORD LOGITECH_VID = 0x046D ;
28+ static const DWORD KNOWN_PIDS[] = {
29+ 0xC24F , // G29
30+ 0xC260 , // G920 (no RPM LEDs but detected for completeness)
31+ 0xC262 , // G920 Xbox
32+ 0xC266 , // G923 PS
33+ 0xC267 , // G923 PS (alt)
34+ 0xC26D , // G923 Xbox (alt PID)
35+ 0xC26E , // G923 Xbox
36+ };
37+ static const int NUM_PIDS = sizeof (KNOWN_PIDS) / sizeof (KNOWN_PIDS[0 ]);
38+
639// --- Logging ---
740
841static FILE* g_logFile = NULL ;
@@ -23,16 +56,20 @@ static void Log(const char* fmt, ...)
2356 OutputDebugStringA (" \n " );
2457}
2558
59+ static bool IsKnownPID (DWORD pid)
60+ {
61+ for (int i = 0 ; i < NUM_PIDS; i++)
62+ if (KNOWN_PIDS[i] == pid) return true ;
63+ return false ;
64+ }
65+
2666// --- LogitechLED ---
2767
2868LogitechLED::LogitechLED ()
29- : m_sdkModule(NULL )
69+ : m_dinputDll(NULL )
70+ , m_pDI(NULL )
71+ , m_pDevice(NULL )
3072 , m_available(false )
31- , m_pfnInit(NULL )
32- , m_pfnUpdate(NULL )
33- , m_pfnIsConnected(NULL )
34- , m_pfnPlayLeds(NULL )
35- , m_pfnShutdown(NULL )
3673{
3774}
3875
@@ -43,111 +80,217 @@ LogitechLED::~LogitechLED()
4380
4481void LogitechLED::Close ()
4582{
46- if (m_pfnShutdown && m_available)
47- m_pfnShutdown ();
83+ if (m_pDevice)
84+ {
85+ m_pDevice->Unacquire ();
86+ m_pDevice->Release ();
87+ m_pDevice = NULL ;
88+ }
89+
90+ if (m_pDI)
91+ {
92+ m_pDI->Release ();
93+ m_pDI = NULL ;
94+ }
4895
49- if (m_sdkModule )
96+ if (m_dinputDll )
5097 {
51- FreeLibrary (m_sdkModule );
52- m_sdkModule = NULL ;
98+ FreeLibrary (m_dinputDll );
99+ m_dinputDll = NULL ;
53100 }
54101
55102 m_available = false ;
56- m_pfnInit = NULL ;
57- m_pfnUpdate = NULL ;
58- m_pfnIsConnected = NULL ;
59- m_pfnPlayLeds = NULL ;
60- m_pfnShutdown = NULL ;
61103}
62104
63105bool LogitechLED::IsAvailable () const
64106{
65107 return m_available;
66108}
67109
110+ // --- Device enumeration callback ---
111+
112+ BOOL CALLBACK LogitechLED::EnumDevicesCallback (LPCDIDEVICEINSTANCEA lpddi, LPVOID pvRef)
113+ {
114+ EnumContext* ctx = (EnumContext*)pvRef;
115+
116+ DWORD vid = LOWORD (lpddi->guidProduct .Data1 );
117+ DWORD pid = HIWORD (lpddi->guidProduct .Data1 );
118+
119+ Log (" Enum: VID=0x%04X PID=0x%04X Name='%s'" , vid, pid, lpddi->tszProductName );
120+
121+ if (vid == LOGITECH_VID && IsKnownPID (pid))
122+ {
123+ Log (" -> Logitech wheel found!" );
124+ ctx->deviceGuid = lpddi->guidInstance ;
125+ ctx->found = true ;
126+ return DIENUM_STOP;
127+ }
128+
129+ return DIENUM_CONTINUE;
130+ }
131+
132+ // --- Init ---
133+
68134bool LogitechLED::Init ()
69135{
70- Log (" LogitechLED::Init() - Logitech SDK mode" );
136+ Log (" LogitechLED::Init() - DirectInput Escape mode" );
71137
72138 if (m_available)
73139 return true ;
74140
75- // Load the SDK DLL
76- m_sdkModule = LoadLibraryA (" LogitechSteeringWheel.dll" );
77- if (!m_sdkModule)
141+ // Load the REAL dinput8.dll from System32 (not our wrapper)
142+ char sysDir[MAX_PATH];
143+ GetSystemDirectoryA (sysDir, MAX_PATH);
144+ strcat_s (sysDir, " \\ dinput8.dll" );
145+
146+ m_dinputDll = LoadLibraryA (sysDir);
147+ if (!m_dinputDll)
148+ {
149+ Log (" Failed to load real dinput8.dll from %s (err=%lu)" , sysDir, GetLastError ());
150+ return false ;
151+ }
152+
153+ Log (" Real dinput8.dll loaded from %s" , sysDir);
154+
155+ // Get the real DirectInput8Create
156+ typedef HRESULT (WINAPI* PFN_DirectInput8Create)(HINSTANCE, DWORD, REFIID, LPVOID*, LPUNKNOWN);
157+ PFN_DirectInput8Create pfnCreate = (PFN_DirectInput8Create)GetProcAddress (m_dinputDll, " DirectInput8Create" );
158+ if (!pfnCreate)
159+ {
160+ Log (" DirectInput8Create not found in real dinput8.dll" );
161+ Close ();
162+ return false ;
163+ }
164+
165+ // Create DirectInput interface
166+ HRESULT hr = pfnCreate (GetModuleHandle (NULL ), DIRECTINPUT_VERSION, IID_IDirectInput8A, (LPVOID*)&m_pDI, NULL );
167+ if (FAILED (hr))
78168 {
79- Log (" LogitechSteeringWheel.dll not found (err=%lu )" , GetLastError () );
80- Log ( " Ensure Logitech G Hub is installed and LogitechSteeringWheel.dll is alongside the game. " );
169+ Log (" DirectInput8Create failed (hr=0x%08X )" , hr );
170+ Close ( );
81171 return false ;
82172 }
83173
84- Log (" LogitechSteeringWheel.dll loaded" );
174+ Log (" DirectInput8 interface created" );
175+
176+ // Enumerate game controllers to find Logitech wheel
177+ EnumContext ctx = {};
178+ ctx.found = false ;
179+
180+ Log (" Enumerating game controllers..." );
181+ m_pDI->EnumDevices (DI8DEVCLASS_GAMECTRL, EnumDevicesCallback, &ctx, DIEDFL_ATTACHEDONLY);
182+
183+ if (!ctx.found )
184+ {
185+ Log (" No Logitech wheel found" );
186+ Close ();
187+ return false ;
188+ }
85189
86- // Resolve function pointers
87- m_pfnInit = (PFN_LogiSteeringInitialize)GetProcAddress (m_sdkModule, " LogiSteeringInitialize" );
88- m_pfnUpdate = (PFN_LogiUpdate)GetProcAddress (m_sdkModule, " LogiUpdate" );
89- m_pfnIsConnected = (PFN_LogiIsConnected)GetProcAddress (m_sdkModule, " LogiIsConnected" );
90- m_pfnPlayLeds = (PFN_LogiPlayLeds)GetProcAddress (m_sdkModule, " LogiPlayLeds" );
91- m_pfnShutdown = (PFN_LogiSteeringShutdown)GetProcAddress (m_sdkModule, " LogiSteeringShutdown" );
190+ // Create device
191+ hr = m_pDI->CreateDevice (ctx.deviceGuid , &m_pDevice, NULL );
192+ if (FAILED (hr))
193+ {
194+ Log (" CreateDevice failed (hr=0x%08X)" , hr);
195+ Close ();
196+ return false ;
197+ }
92198
93- if (!m_pfnInit || !m_pfnUpdate || !m_pfnPlayLeds || !m_pfnShutdown)
199+ // Set data format
200+ hr = m_pDevice->SetDataFormat (&c_dfDIJoystick2);
201+ if (FAILED (hr))
94202 {
95- Log (" Failed to resolve SDK functions (Init=%p Update=%p PlayLeds=%p Shutdown=%p)" ,
96- m_pfnInit, m_pfnUpdate, m_pfnPlayLeds, m_pfnShutdown);
97- FreeLibrary (m_sdkModule);
98- m_sdkModule = NULL ;
203+ Log (" SetDataFormat failed (hr=0x%08X)" , hr);
204+ Close ();
99205 return false ;
100206 }
101207
102- Log (" SDK functions resolved" );
208+ // Set cooperative level: background + non-exclusive (don't steal from game)
209+ HWND hwnd = GetForegroundWindow ();
210+ if (!hwnd) hwnd = GetDesktopWindow ();
103211
104- // Initialize the steering wheel SDK
105- if (! m_pfnInit ( false ))
212+ hr = m_pDevice-> SetCooperativeLevel (hwnd, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE);
213+ if (FAILED (hr ))
106214 {
107- Log (" LogiSteeringInitialize() failed - is G Hub running?" );
108- FreeLibrary (m_sdkModule);
109- m_sdkModule = NULL ;
215+ Log (" SetCooperativeLevel failed (hr=0x%08X)" , hr);
216+ Close ();
110217 return false ;
111218 }
112219
113- Log (" SDK initialized" );
220+ // Acquire the device
221+ hr = m_pDevice->Acquire ();
222+ if (FAILED (hr))
223+ {
224+ Log (" Acquire failed (hr=0x%08X)" , hr);
225+ Close ();
226+ return false ;
227+ }
114228
115- // Give the SDK a moment to enumerate devices
116- m_pfnUpdate ();
229+ Log (" Device acquired, testing Escape LED command..." );
117230
118- if (m_pfnIsConnected && m_pfnIsConnected (0 ))
119- Log (" Steering wheel detected at index 0" );
120- else
121- Log (" No wheel detected yet (will retry on first LED update)" );
231+ // Test: try to clear LEDs to verify Escape() works
232+ if (!PlayLedsEscape (0 .0f , 200 .0f , 1000 .0f ))
233+ {
234+ Log (" Escape LED command not supported on this device" );
235+ Close ();
236+ return false ;
237+ }
122238
123239 m_available = true ;
124- Log (" LogitechLED ready" );
240+ Log (" LogitechLED ready (DirectInput Escape mode)" );
241+ return true ;
242+ }
243+
244+ // --- LED control via Escape ---
245+
246+ bool LogitechLED::PlayLedsEscape (float currentRPM, float rpmFirstLed, float rpmRedLine)
247+ {
248+ if (!m_pDevice)
249+ return false ;
250+
251+ WheelData wheelData;
252+ ZeroMemory (&wheelData, sizeof (wheelData));
253+ wheelData.size = sizeof (WheelData);
254+ wheelData.versionNbr = LEDS_VERSION_NUMBER;
255+ wheelData.rpmData .currentRPM = currentRPM;
256+ wheelData.rpmData .rpmFirstLedTurnsOn = rpmFirstLed;
257+ wheelData.rpmData .rpmRedLine = rpmRedLine;
258+
259+ DIEFFESCAPE escape;
260+ ZeroMemory (&escape, sizeof (escape));
261+ escape.dwSize = sizeof (DIEFFESCAPE);
262+ escape.dwCommand = ESCAPE_COMMAND_LEDS;
263+ escape.lpvInBuffer = &wheelData;
264+ escape.cbInBuffer = sizeof (wheelData);
265+
266+ HRESULT hr = m_pDevice->Escape (&escape);
267+ if (FAILED (hr))
268+ {
269+ Log (" Escape failed (hr=0x%08X)" , hr);
270+ return false ;
271+ }
272+
125273 return true ;
126274}
127275
128276bool LogitechLED::SetLEDsFromPercent (double percent)
129277{
130- if (!m_available || !m_pfnPlayLeds || !m_pfnUpdate )
278+ if (!m_available)
131279 return false ;
132280
133281 if (percent < 0.0 ) percent = 0.0 ;
134282 if (percent > 1.0 ) percent = 1.0 ;
135283
136- m_pfnUpdate ();
137-
138- // Map FFB strength (0.0-1.0) to RPM values.
139- // rpmFirstLed=200: first LED lights at 20% strength
140- // rpmRedLine=1000: all LEDs at 100% strength
284+ // Map FFB strength (0.0-1.0) to RPM values
141285 float currentRPM = (float )(percent * 1000.0 );
142- return m_pfnPlayLeds ( 0 , currentRPM, 200 .0f , 1000 .0f );
286+ return PlayLedsEscape ( currentRPM, 200 .0f , 1000 .0f );
143287}
144288
145289bool LogitechLED::SetLEDs (BYTE ledMask)
146290{
147291 if (!m_available)
148292 return false ;
149293
150- // Map 5-bit mask to approximate percentage
151294 int count = 0 ;
152295 for (int i = 0 ; i < 5 ; i++)
153296 if (ledMask & (1 << i)) count++;
@@ -157,9 +300,8 @@ bool LogitechLED::SetLEDs(BYTE ledMask)
157300
158301bool LogitechLED::ClearLEDs ()
159302{
160- if (!m_available || !m_pfnPlayLeds || !m_pfnUpdate )
303+ if (!m_available)
161304 return false ;
162305
163- m_pfnUpdate ();
164- return m_pfnPlayLeds (0 , 0 .0f , 200 .0f , 1000 .0f );
306+ return PlayLedsEscape (0 .0f , 200 .0f , 1000 .0f );
165307}
0 commit comments