Skip to content

Commit 57d237c

Browse files
committed
ControllerHotPlug: allow game to detect newly added controllers
1 parent ec2b521 commit 57d237c

File tree

6 files changed

+96
-4
lines changed

6 files changed

+96
-4
lines changed

Outrun2006Tweaks.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ FixFullPedalChecks = true
156156
# Hides text related to the now-defunct online service
157157
HideOnlineSigninText = true
158158

159+
# Allows game to detect newly plugged in devices, rather than needing a restart
160+
ControllerHotPlug = true
161+
159162
[CDTracks]
160163
# Defines tracks and the order they can be selected via the CDSwitcher above
161164
# Tracks can be added/removed as desired, along with any custom WAV/OGG files

src/dllmain.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ namespace Settings
9797
spdlog::info(" - FixLensFlarePath: {}", FixLensFlarePath);
9898
spdlog::info(" - FixFullPedalChecks: {}", FixFullPedalChecks);
9999
spdlog::info(" - HideOnlineSigninText: {}", HideOnlineSigninText);
100+
spdlog::info(" - ControllerHotPlug: {}", ControllerHotPlug);
100101
}
101102

102103
bool read(std::filesystem::path& iniPath)
@@ -172,6 +173,7 @@ namespace Settings
172173
FixLensFlarePath = ini.Get("Bugfixes", "FixLensFlarePath", std::move(FixLensFlarePath));
173174
FixFullPedalChecks = ini.Get("Bugfixes", "FixFullPedalChecks", std::move(FixFullPedalChecks));
174175
HideOnlineSigninText = ini.Get("Bugfixes", "HideOnlineSigninText", std::move(HideOnlineSigninText));
176+
ControllerHotPlug = ini.Get("Bugfixes", "ControllerHotPlug", std::move(ControllerHotPlug));
175177

176178
// INIReader doesn't preserve the order of the keys/values in a section
177179
// Will need to try opening INI ourselves to grab cd tracks...

src/hooks_bugfixes.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,16 +241,15 @@ class HideOnlineSigninText : public Hook
241241

242242
bool apply() override
243243
{
244-
// Don't allow "sign in" action button text to show
245-
Sumo_DrawActionButtonName = safetyhook::create_inline(Module::exe_ptr(Sumo_DrawActionButtonName_Addr), Sumo_DrawActionButtonName_dest);
246-
247244
// Hide "Not Signed In" text
248245
Memory::VP::Patch(Module::exe_ptr(Sumo_PrintSignedInStatus_Addr), { 0xC3 });
249246

250247
// Hide box that contains the message above
251248
Memory::VP::Patch(Module::exe_ptr(Sumo_DrawSignedInStatusBox_PatchAddr), { 0xEB });
252249

253-
// TODO: hide the F1 button prompt, doesn't seem handled by the same code as above...
250+
// Don't allow "sign in" action button text to show
251+
// TODO: disabled for now until we can also hide the F1 button prompt for it, doesn't seem handled by the same code as this...
252+
// Sumo_DrawActionButtonName = safetyhook::create_inline(Module::exe_ptr(Sumo_DrawActionButtonName_Addr), Sumo_DrawActionButtonName_dest);
254253

255254
return !!Sumo_DrawActionButtonName;
256255
}

src/hooks_framerate.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ class ReplaceGameUpdateLoop : public Hook
131131
// Reset vibration if we're not in main game state
132132
if (Settings::VibrationMode != 0 && CurGameState != GameState::STATE_GAME)
133133
SetVibration(Settings::VibrationControllerId, 0.0f, 0.0f);
134+
135+
DInput_RegisterNewDevices();
134136
}
135137

136138
for (int curUpdateIdx = 0; curUpdateIdx < numUpdates; curUpdateIdx++)

src/hooks_input.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <winioctl.h>
55
#include <hidsdi.h>
66

7+
#include <queue>
8+
79
#include "hook_mgr.hpp"
810
#include "plugin.hpp"
911
#include "game_addrs.hpp"
@@ -116,3 +118,85 @@ class ImpulseVibration : public Hook
116118
static ImpulseVibration instance;
117119
};
118120
ImpulseVibration ImpulseVibration::instance;
121+
122+
class ControllerHotPlug : public Hook
123+
{
124+
const static int DInputInit_CallbackPtr_Addr = 0x3E10;
125+
126+
public:
127+
inline static std::mutex mtx;
128+
inline static std::unique_ptr<std::thread> DeviceEnumerationThreadHandle;
129+
inline static std::vector<GUID> KnownDevices;
130+
inline static std::queue<DIDEVICEINSTANCE> NewDevices;
131+
132+
static BOOL __stdcall DInput_EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance, VOID* pContext)
133+
{
134+
std::lock_guard<std::mutex> lock(mtx);
135+
if (std::find(KnownDevices.begin(), KnownDevices.end(), pdidInstance->guidInstance) == KnownDevices.end())
136+
{
137+
// GUID not found, add to the vector
138+
KnownDevices.push_back(pdidInstance->guidInstance);
139+
140+
// Add the new device instance to the queue
141+
NewDevices.push(*pdidInstance);
142+
}
143+
144+
return DIENUM_CONTINUE;
145+
}
146+
147+
static void DeviceEnumerationThread()
148+
{
149+
SetThreadDescription(GetCurrentThread(), L"DeviceEnumerationThread");
150+
151+
while (true)
152+
{
153+
if (Game::DirectInput8())
154+
Game::DirectInput8()->EnumDevices(DI8DEVCLASS_GAMECTRL, DInput_EnumJoysticksCallback, nullptr, DIEDFL_ATTACHEDONLY);
155+
156+
std::this_thread::sleep_for(std::chrono::seconds(2)); // Poll every 2 seconds
157+
}
158+
}
159+
160+
std::string_view description() override
161+
{
162+
return "ControllerHotPlug";
163+
}
164+
165+
bool validate() override
166+
{
167+
return Settings::ControllerHotPlug;
168+
}
169+
170+
bool apply() override
171+
{
172+
// Patch game to go through our DInput_EnumJoysticksCallback func, so we can learn GUID of any already connected pads
173+
Memory::VP::Patch(Module::exe_ptr(DInputInit_CallbackPtr_Addr + 1), DInput_EnumJoysticksCallback);
174+
175+
return true;
176+
}
177+
178+
static ControllerHotPlug instance;
179+
};
180+
ControllerHotPlug ControllerHotPlug::instance;
181+
182+
void DInput_RegisterNewDevices()
183+
{
184+
if (!ControllerHotPlug::DeviceEnumerationThreadHandle)
185+
{
186+
ControllerHotPlug::DeviceEnumerationThreadHandle = std::make_unique<std::thread>(ControllerHotPlug::DeviceEnumerationThread);
187+
ControllerHotPlug::DeviceEnumerationThreadHandle->detach();
188+
}
189+
190+
std::lock_guard<std::mutex> lock(ControllerHotPlug::mtx);
191+
while (!ControllerHotPlug::NewDevices.empty())
192+
{
193+
DIDEVICEINSTANCE deviceInstance = ControllerHotPlug::NewDevices.front();
194+
ControllerHotPlug::NewDevices.pop();
195+
196+
// Tell game about it
197+
Game::DInput_EnumJoysticksCallback(&deviceInstance, 0);
198+
199+
// Sets some kind of active-device-number to let it work
200+
*Module::exe_ptr<int>(0x3398D4) = 0;
201+
}
202+
}

src/plugin.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "game.hpp"
66

7+
extern void DInput_RegisterNewDevices(); // hooks_input.cpp
78
extern void SetVibration(int userId, float leftMotor, float rightMotor); // hooks_forcefeedback.cpp
89
extern void CDSwitcher_Draw(int numUpdates); // hooks_audio.cpp
910
extern void CDSwitcher_ReadIni(const std::filesystem::path& iniPath);
@@ -89,6 +90,7 @@ namespace Settings
8990
inline bool FixLensFlarePath = true;
9091
inline bool FixFullPedalChecks = true;
9192
inline bool HideOnlineSigninText = true;
93+
inline bool ControllerHotPlug = true;
9294
}
9395

9496
namespace Util

0 commit comments

Comments
 (0)