Skip to content

Commit be5b15c

Browse files
committed
refactor: use DirectInput Escape for LED control instead of SDK DLL
1 parent 2d667fd commit be5b15c

File tree

2 files changed

+218
-75
lines changed

2 files changed

+218
-75
lines changed

Common Files/LogitechLED.cpp

Lines changed: 201 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,39 @@
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

841
static 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

2868
LogitechLED::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

4481
void 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

63105
bool 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+
68134
bool 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

128276
bool 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

145289
bool 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

158301
bool 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
}

Common Files/LogitechLED.h

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
#pragma once
22

3+
#define DIRECTINPUT_VERSION 0x0800
34
#include <windows.h>
5+
#include <dinput.h>
46

5-
// Logitech G923/G29 RPM LED controller via Logitech Steering Wheel SDK.
6-
// Requires Logitech G Hub to be running.
7-
// Uses dynamic loading: no hard dependency on the SDK DLL.
7+
// Logitech G923/G29 RPM LED controller via DirectInput Escape().
8+
// Uses the same mechanism as the Logitech SDK "Independent" sample.
9+
// Requires Logitech G Hub running (provides the FF driver that handles Escape).
10+
// No external DLL dependency.
811

912
class LogitechLED
1013
{
@@ -21,19 +24,17 @@ class LogitechLED
2124
bool IsAvailable() const;
2225

2326
private:
24-
HMODULE m_sdkModule;
27+
HMODULE m_dinputDll;
28+
LPDIRECTINPUT8A m_pDI;
29+
LPDIRECTINPUTDEVICE8A m_pDevice;
2530
bool m_available;
2631

27-
// SDK function pointers
28-
typedef bool (__cdecl *PFN_LogiSteeringInitialize)(bool);
29-
typedef bool (__cdecl *PFN_LogiUpdate)();
30-
typedef bool (__cdecl *PFN_LogiIsConnected)(int);
31-
typedef bool (__cdecl *PFN_LogiPlayLeds)(int, float, float, float);
32-
typedef void (__cdecl *PFN_LogiSteeringShutdown)();
33-
34-
PFN_LogiSteeringInitialize m_pfnInit;
35-
PFN_LogiUpdate m_pfnUpdate;
36-
PFN_LogiIsConnected m_pfnIsConnected;
37-
PFN_LogiPlayLeds m_pfnPlayLeds;
38-
PFN_LogiSteeringShutdown m_pfnShutdown;
32+
struct EnumContext
33+
{
34+
GUID deviceGuid;
35+
bool found;
36+
};
37+
38+
bool PlayLedsEscape(float currentRPM, float rpmFirstLed, float rpmRedLine);
39+
static BOOL CALLBACK EnumDevicesCallback(LPCDIDEVICEINSTANCEA lpddi, LPVOID pvRef);
3940
};

0 commit comments

Comments
 (0)