├── .gitignore ├── DashFix.sln ├── DashFix ├── Custom.manifest ├── DashFix.cpp ├── DashFix.rc ├── DashFix.vcxproj ├── DashFix.vcxproj.filters └── resource.h ├── License.txt ├── ReadMe.md ├── inject ├── inject.cpp ├── inject.vcxproj └── inject.vcxproj.filters └── install.iss /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | .DS_Store 3 | *.o 4 | *.aps 5 | *.opt 6 | *.ncb 7 | *.plg 8 | *.suo 9 | *.vcproj.*.user 10 | *.vcxproj.user 11 | *.sdf 12 | *.opensdf 13 | *.opendb 14 | *.VC.db 15 | ipch/ 16 | [Dd]ebug/ 17 | [Rr]elease/ 18 | /DashFixSetup_*.exe 19 | -------------------------------------------------------------------------------- /DashFix.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DashFix", "DashFix\DashFix.vcxproj", "{6A4E0B46-D7F2-4C46-93C6-7900F07A3B5A}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {C10D7779-70C9-4D6C-8486-768908A3E769} = {C10D7779-70C9-4D6C-8486-768908A3E769} 9 | EndProjectSection 10 | EndProject 11 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "inject", "inject\inject.vcxproj", "{C10D7779-70C9-4D6C-8486-768908A3E769}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E092A9E4-C507-4382-88A2-EA2B7131A7C6}" 14 | ProjectSection(SolutionItems) = preProject 15 | ReadMe.txt = ReadMe.txt 16 | EndProjectSection 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|x86 = Debug|x86 21 | Release|x86 = Release|x86 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {6A4E0B46-D7F2-4C46-93C6-7900F07A3B5A}.Debug|x86.ActiveCfg = Debug|Win32 25 | {6A4E0B46-D7F2-4C46-93C6-7900F07A3B5A}.Debug|x86.Build.0 = Debug|Win32 26 | {6A4E0B46-D7F2-4C46-93C6-7900F07A3B5A}.Release|x86.ActiveCfg = Release|Win32 27 | {6A4E0B46-D7F2-4C46-93C6-7900F07A3B5A}.Release|x86.Build.0 = Release|Win32 28 | {C10D7779-70C9-4D6C-8486-768908A3E769}.Debug|x86.ActiveCfg = Debug|Win32 29 | {C10D7779-70C9-4D6C-8486-768908A3E769}.Debug|x86.Build.0 = Debug|Win32 30 | {C10D7779-70C9-4D6C-8486-768908A3E769}.Release|x86.ActiveCfg = Release|Win32 31 | {C10D7779-70C9-4D6C-8486-768908A3E769}.Release|x86.Build.0 = Release|Win32 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /DashFix/Custom.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | OpenVR Dashboard Fixer 4 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /DashFix/DashFix.cpp: -------------------------------------------------------------------------------- 1 | // DashFix: https://github.com/simonowen/dashfix 2 | // 3 | // Source code released under MIT License. 4 | 5 | #include 6 | #define _WIN32_WINNT _WIN32_WINNT_WIN7 7 | #include 8 | 9 | #define WIN32_LEAN_AND_MEAN 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "SDL2\SDL.h" 19 | #include "resource.h" 20 | 21 | 22 | const auto APP_NAME{ TEXT("DashFix") }; 23 | const auto SETTINGS_KEY{ TEXT(R"(Software\SimonOwen\DashFix)") }; 24 | const auto STEAM_KEY{ TEXT(R"(Software\Valve\Steam)") }; 25 | 26 | HWND g_hDlg{ NULL }; 27 | 28 | //////////////////////////////////////////////////////////////////////////////// 29 | 30 | _Success_(return) 31 | bool GetProcessId( 32 | _In_ const std::wstring &strProcess, 33 | _Out_ DWORD & dwProcessId, 34 | _In_opt_ DWORD dwExcludeProcessId = 0) 35 | { 36 | bool ret{ false }; 37 | dwProcessId = 0; 38 | 39 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 40 | if (hSnapshot != INVALID_HANDLE_VALUE) 41 | { 42 | PROCESSENTRY32 pe{ sizeof(pe) }; 43 | 44 | auto f = Process32First(hSnapshot, &pe); 45 | while (f) 46 | { 47 | if (!lstrcmpi(pe.szExeFile, strProcess.c_str()) && 48 | pe.th32ProcessID != dwExcludeProcessId) 49 | { 50 | dwProcessId = pe.th32ProcessID; 51 | ret = true; 52 | break; 53 | } 54 | 55 | f = Process32Next(hSnapshot, &pe); 56 | } 57 | 58 | CloseHandle(hSnapshot); 59 | } 60 | 61 | return ret; 62 | } 63 | 64 | _Success_(return) 65 | bool InjectRemoteThread( 66 | _In_ DWORD dwProcessId, 67 | _In_ const std::wstring &strDLL) 68 | { 69 | bool ret{ false }; 70 | 71 | auto hProcess = OpenProcess( 72 | PROCESS_ALL_ACCESS, 73 | FALSE, 74 | dwProcessId); 75 | 76 | if (hProcess != NULL) 77 | { 78 | auto pfnLoadLibrary = GetProcAddress( 79 | GetModuleHandle(TEXT("kernel32.dll")), 80 | "LoadLibraryW"); 81 | 82 | auto uStringSize = (strDLL.length() + 1) * sizeof(strDLL[0]); 83 | auto pszRemoteString = VirtualAllocEx( 84 | hProcess, 85 | NULL, 86 | uStringSize, 87 | MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 88 | 89 | if (pszRemoteString) 90 | { 91 | WriteProcessMemory(hProcess, pszRemoteString, strDLL.c_str(), uStringSize, NULL); 92 | 93 | auto hThread = CreateRemoteThread( 94 | hProcess, NULL, 0, 95 | reinterpret_cast(pfnLoadLibrary), 96 | pszRemoteString, 97 | 0, 98 | NULL); 99 | 100 | if (hThread != NULL) 101 | { 102 | WaitForSingleObject(hThread, INFINITE); 103 | CloseHandle(hThread); 104 | ret = true; 105 | } 106 | 107 | VirtualFreeEx(hProcess, pszRemoteString, 0, MEM_RELEASE); 108 | } 109 | 110 | WaitForSingleObject(hProcess, INFINITE); 111 | CloseHandle(hProcess); 112 | } 113 | 114 | return ret; 115 | } 116 | 117 | 118 | void InjectModules( 119 | _In_ const std::vector &mods) 120 | { 121 | WCHAR szEXE[MAX_PATH]; 122 | GetModuleFileName(NULL, szEXE, _countof(szEXE)); 123 | 124 | // The injection DLL is in the same directory as the EXE. 125 | std::wstring strDLL{ szEXE }; 126 | strDLL = strDLL.substr(0, strDLL.rfind('\\')); 127 | strDLL += TEXT("\\inject.dll"); 128 | 129 | std::vector threads{}; 130 | for (auto &mod : mods) 131 | { 132 | std::thread([=] 133 | { 134 | // Loop forever watching 135 | for (;;) 136 | { 137 | DWORD dwProcessId; 138 | 139 | // Poll for process start, with 3 seconds between checks. 140 | if (!GetProcessId(mod, dwProcessId)) 141 | { 142 | Sleep(3000); 143 | } 144 | // Inject the patch DLL into the process and wait for it to 145 | // terminate. 1 second between injection attempts. 146 | else if (!InjectRemoteThread(dwProcessId, strDLL)) 147 | { 148 | Sleep(1000); 149 | } 150 | } 151 | }).detach(); 152 | } 153 | } 154 | 155 | HMODULE LoadSDL2() 156 | { 157 | WCHAR szPath[MAX_PATH]{}; 158 | 159 | HKEY hkey; 160 | if (RegCreateKey( 161 | HKEY_CURRENT_USER, 162 | STEAM_KEY, 163 | &hkey) == ERROR_SUCCESS) 164 | { 165 | DWORD cchValue{ _countof(szPath) }; 166 | DWORD dwType{ REG_SZ }; 167 | 168 | RegQueryValueEx( 169 | hkey, 170 | TEXT("SteamPath"), 171 | NULL, 172 | &dwType, 173 | reinterpret_cast(szPath), 174 | &cchValue); 175 | 176 | RegCloseKey(hkey); 177 | } 178 | 179 | if (szPath[0]) 180 | { 181 | wcscat_s(szPath, _countof(szPath), TEXT(R"(\SDL2.dll)")); 182 | return LoadLibrary(szPath); 183 | } 184 | 185 | return NULL; 186 | } 187 | 188 | 189 | void PopulateControllerList( 190 | _In_ HWND hListView) 191 | { 192 | std::map joy_list; 193 | ListView_DeleteAllItems(hListView); 194 | 195 | // We need SDL2 for a connected controller list, and should be able to load 196 | // the one Steam itself uses. 197 | if (GetModuleHandle(TEXT("SDL2.dll")) || LoadSDL2()) 198 | { 199 | SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); 200 | int numControllers = SDL_NumJoysticks(); 201 | 202 | for (int i = 0; i < numControllers; ++i) 203 | { 204 | auto pJoystick = SDL_JoystickOpen(i); 205 | if (pJoystick) 206 | { 207 | auto pJoyName = SDL_JoystickName(pJoystick); 208 | if (pJoyName && *pJoyName) 209 | { 210 | // Controller enabled by default. 211 | joy_list[pJoyName] = TRUE; 212 | } 213 | } 214 | 215 | SDL_JoystickClose(pJoystick); 216 | } 217 | 218 | SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); 219 | } 220 | 221 | HKEY hkey; 222 | if (RegCreateKey( 223 | HKEY_CURRENT_USER, 224 | SETTINGS_KEY, 225 | &hkey) == ERROR_SUCCESS) 226 | { 227 | // Populate from settings stored in the registry. 228 | for (DWORD idx = 0; ; ++idx) 229 | { 230 | char szValue[256]; 231 | DWORD dwType{ REG_DWORD }; 232 | DWORD cchValue{ _countof(szValue) }; 233 | DWORD dwData{ 0 }, cbData{ sizeof(dwData) }; 234 | 235 | if (RegEnumValueA( 236 | hkey, 237 | idx, 238 | szValue, &cchValue, 239 | NULL, 240 | &dwType, 241 | reinterpret_cast(&dwData), &cbData) != ERROR_SUCCESS) 242 | { 243 | break; 244 | } 245 | 246 | if (szValue[0]) 247 | joy_list[szValue] = (dwData != 0); 248 | } 249 | 250 | RegCloseKey(hkey); 251 | } 252 | 253 | // Add the combined list to the listview control, with its checked status. 254 | for (auto &joy : joy_list) 255 | { 256 | LVFINDINFOA lfi{}; 257 | lfi.flags = LVFI_STRING; 258 | lfi.psz = joy.first.c_str(); 259 | auto index = SendMessage(hListView, LVM_FINDITEMA, 0, reinterpret_cast(&lfi)); 260 | 261 | if (index < 0) 262 | { 263 | LVITEMA lvi{}; 264 | lvi.mask = LVIF_TEXT; 265 | lvi.pszText = const_cast(lfi.psz); 266 | index = SendMessage(hListView, LVM_INSERTITEMA, 0, reinterpret_cast(&lvi)); 267 | 268 | ListView_SetCheckState(hListView, index, !joy.second); 269 | } 270 | } 271 | } 272 | 273 | void SaveControllerList( 274 | _In_ HWND hListView) 275 | { 276 | HKEY hkey; 277 | if (RegCreateKey( 278 | HKEY_CURRENT_USER, 279 | SETTINGS_KEY, 280 | &hkey) == ERROR_SUCCESS) 281 | { 282 | // Save list entries and checked status to the registry. 283 | for (int idx = 0; ; ++idx) 284 | { 285 | char szItem[MAX_PATH]{}; 286 | 287 | LV_ITEMA li{}; 288 | li.mask = LVIF_TEXT; 289 | li.iItem = idx; 290 | li.pszText = szItem; 291 | li.cchTextMax = _countof(szItem); 292 | 293 | if (!SendMessage(hListView, LVM_GETITEMA, 0, reinterpret_cast(&li))) 294 | break; 295 | 296 | DWORD dwData = !ListView_GetCheckState(hListView, idx); 297 | RegSetValueExA( 298 | hkey, 299 | szItem, 300 | 0, 301 | REG_DWORD, 302 | reinterpret_cast(&dwData), 303 | sizeof(dwData)); 304 | } 305 | 306 | RegCloseKey(hkey); 307 | } 308 | 309 | } 310 | 311 | INT_PTR CALLBACK DialogProc( 312 | _In_ HWND hDlg, 313 | _In_ UINT uMsg, 314 | _In_ WPARAM wParam, 315 | _In_ LPARAM /*lParam*/) 316 | { 317 | switch (uMsg) 318 | { 319 | case WM_INITDIALOG: 320 | { 321 | // Enable listbox items to have checkboxes. 322 | HWND hwndList = GetDlgItem(hDlg, IDL_CONTROLLERS); 323 | ListView_SetExtendedListViewStyle(hwndList, LVS_EX_CHECKBOXES); 324 | 325 | LVCOLUMN col{}; 326 | col.mask = LVCF_WIDTH; 327 | col.cx = 300; 328 | ListView_InsertColumn(hwndList, 0, &col); 329 | 330 | // Populate the controller list from connected devices and the registry. 331 | PopulateControllerList(hwndList); 332 | 333 | return TRUE; 334 | } 335 | 336 | case WM_DESTROY: 337 | g_hDlg = NULL; 338 | return TRUE; 339 | 340 | case WM_COMMAND: 341 | { 342 | switch (LOWORD(wParam)) 343 | { 344 | case IDOK: 345 | { 346 | // Save the controller checkbox states to the registry. 347 | SaveControllerList(GetDlgItem(hDlg, IDL_CONTROLLERS)); 348 | 349 | DestroyWindow(hDlg); 350 | return TRUE; 351 | } 352 | 353 | case IDCANCEL: 354 | { 355 | DestroyWindow(hDlg); 356 | return TRUE; 357 | } 358 | } 359 | break; 360 | } 361 | 362 | case WM_DEVICECHANGE: 363 | { 364 | // Refresh the list if a controller is hotplugged, saving any 365 | // modified entries from the existing list before it's updated. 366 | HWND hwndListView = GetDlgItem(hDlg, IDL_CONTROLLERS); 367 | SaveControllerList(hwndListView); 368 | PopulateControllerList(hwndListView); 369 | 370 | return TRUE; 371 | } 372 | } 373 | 374 | return FALSE; 375 | } 376 | 377 | void ShowGUI( 378 | _In_ HINSTANCE hInstance, 379 | _In_ int nCmdShow) 380 | { 381 | INITCOMMONCONTROLSEX icce{ sizeof(icce), ICC_LISTVIEW_CLASSES }; 382 | InitCommonControlsEx(&icce); 383 | 384 | g_hDlg = CreateDialog( 385 | hInstance, MAKEINTRESOURCE(IDD_CONTROLLERS), NULL, DialogProc); 386 | 387 | ShowWindow(g_hDlg, nCmdShow); 388 | } 389 | 390 | //////////////////////////////////////////////////////////////////////////////// 391 | 392 | int CALLBACK WinMain( 393 | _In_ HINSTANCE hInstance, 394 | _In_opt_ HINSTANCE /*hPrevInstance*/, 395 | _In_ LPSTR lpCmdLine, 396 | _In_ int nCmdShow) 397 | { 398 | auto uRegMsg = RegisterWindowMessage(APP_NAME); 399 | 400 | // Check for a running instance. 401 | HANDLE hMutex = CreateMutex(NULL, TRUE, APP_NAME); 402 | if (hMutex && GetLastError() == ERROR_ALREADY_EXISTS) 403 | { 404 | ReleaseMutex(hMutex); 405 | 406 | // Ask the existing instance to show the GUI. 407 | DWORD dwRecipients = BSM_APPLICATIONS; 408 | BroadcastSystemMessage( 409 | BSF_POSTMESSAGE, 410 | &dwRecipients, 411 | uRegMsg, 412 | 0, 413 | 0L); 414 | } 415 | else 416 | { 417 | // Inject the hook DLL into the known affected applications. 418 | InjectModules({ TEXT("steam.exe"), TEXT("vrdashboard.exe") }); 419 | 420 | // Show the GUI if not launched with a parameter (startup mode). 421 | if (!lpCmdLine[0]) 422 | { 423 | ShowGUI(hInstance, nCmdShow); 424 | } 425 | 426 | // Dummy window to listen for broadcast messages. 427 | CreateWindow(TEXT("static"), TEXT(""), 0, 0, 0, 0, 0, NULL, NULL, hInstance, 0L); 428 | 429 | MSG msg; 430 | while (GetMessage(&msg, NULL, 0, 0)) 431 | { 432 | // Show the GUI if asked to by another instance, and not already active. 433 | if (msg.message == uRegMsg && !g_hDlg) 434 | { 435 | ShowGUI(hInstance, SW_SHOWNORMAL); 436 | } 437 | 438 | TranslateMessage(&msg); 439 | DispatchMessage(&msg); 440 | } 441 | } 442 | 443 | return 0; 444 | } 445 | -------------------------------------------------------------------------------- /DashFix/DashFix.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonowen/dashfix/cfa4c260fd98e2a94c80cc0f71ea976607d4a325/DashFix/DashFix.rc -------------------------------------------------------------------------------- /DashFix/DashFix.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {6A4E0B46-D7F2-4C46-93C6-7900F07A3B5A} 15 | Win32Proj 16 | DashFix 17 | 8.1 18 | 19 | 20 | 21 | Application 22 | true 23 | v140 24 | Unicode 25 | 26 | 27 | Application 28 | false 29 | v140 30 | true 31 | Unicode 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | true 47 | 48 | 49 | false 50 | 51 | 52 | 53 | Level4 54 | Disabled 55 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 56 | 57 | 58 | Windows 59 | true 60 | sdl2.lib;comctl32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 61 | SDL2.dll 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | Level4 71 | MaxSpeed 72 | true 73 | true 74 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 75 | MultiThreaded 76 | 77 | 78 | Windows 79 | true 80 | true 81 | true 82 | sdl2.lib;comctl32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 83 | SDL2.dll 84 | 85 | 86 | @if exist c:\batch\sign.bat c:\batch\sign.bat "$(TargetPath)" 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Designer 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /DashFix/DashFix.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {2832af53-b0ab-4d4c-acd4-002e8dc51e7f} 10 | 11 | 12 | 13 | 14 | Source Files 15 | 16 | 17 | 18 | 19 | Resource Files 20 | 21 | 22 | 23 | 24 | Resource Files 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /DashFix/resource.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonowen/dashfix/cfa4c260fd98e2a94c80cc0f71ea976607d4a325/DashFix/resource.h -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Simon Owen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # DashFix (OpenVR DashBoard Fixer) v1.1 2 | 3 | ## Introduction 4 | 5 | Inputs from connected game controllers can interfere with OpenVR Dashboard 6 | pointer navigation. The problem manifests itself as the inability to click 7 | buttons and other elements in the dashboard user interface. 8 | 9 | Steering wheels and HOTAS devices are usually the cause, as they often have a 10 | rest position with a non-zero axis value, which the dashboard continually acts 11 | upon. It's inconvenient to disconnect them during dashboard use, and there's 12 | currently no way to tell Steam to ignore them. 13 | 14 | DashFix lets you ignore the unwanted inputs, and use the dashboard normally. 15 | 16 | ## Install 17 | 18 | - Connect any problematic game controllers 19 | - Download and run the [installer for the latest version](https://github.com/simonowen/dashfix/releases/latest). 20 | - Select the controllers to block from the list 21 | - Click OK 22 | 23 | DashFix will continue running in the background, and start with Windows. 24 | 25 | To change which controllers are blocked, re-launch DashFix from the Start 26 | Menu shortcut. To deactivate it, uninstall from "Add or Remove Programs". 27 | 28 | ## Upgrade 29 | 30 | To upgrade an earlier version simply over-install with the latest version. 31 | 32 | ## Internals 33 | 34 | DashFix injects a DLL into Steam.exe and vrdashboard.exe, hooking calls to 35 | SDL_GetJoystickAxis so they return zero for some controllers. This process 36 | injection technique could upset some runtime virus scanners. 37 | 38 | Source code is available from the [DashFix project page](https://github.com/simonowen/dashfix) on GitHub. 39 | 40 | Please let me know if you have any problems, or find other places where 41 | controllers are interfering with normal use. 42 | 43 | ## Changelog 44 | 45 | ### v1.1 46 | - added installer/uninstaller to simplify use 47 | - removed option to start with Windows as it's the default behaviour 48 | - inverted checkboxes, so selected means blocked (thanks ljford7!) 49 | 50 | ### v1.0 51 | - added individual controller selection 52 | - added optional launch on Windows startup 53 | - improved pre-hook checks and error handling 54 | - added MIT license 55 | 56 | ### v0.1 57 | - first public test release, blocking all controllers 58 | 59 | --- 60 | 61 | Simon Owen 62 | https://github.com/simonowen/dashfix 63 | -------------------------------------------------------------------------------- /inject/inject.cpp: -------------------------------------------------------------------------------- 1 | // DashFix: https://github.com/simonowen/dashfix 2 | // 3 | // Source code released under MIT License. 4 | 5 | #include 6 | #define _WIN32_WINNT _WIN32_WINNT_WIN7 7 | #include 8 | 9 | #define WIN32_LEAN_AND_MEAN 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #pragma comment(lib, "SDL2.lib") 17 | 18 | const auto APP_NAME{ TEXT("DashFix") }; 19 | const auto SETTINGS_KEY{ TEXT(R"(Software\SimonOwen\DashFix)") }; 20 | 21 | 22 | typedef Sint16 (SDLCALL *PFNSDL_JOYSTICKGETAXIS)(SDL_Joystick *, int); 23 | typedef const char * (SDLCALL *PFNSDL_JOYSTICKNAME)(SDL_Joystick *); 24 | 25 | PFNSDL_JOYSTICKGETAXIS pfnOrig_SDL_JoystickGetAxis; 26 | PFNSDL_JOYSTICKGETAXIS g_pfnJoystickGetAxis; 27 | PFNSDL_JOYSTICKNAME g_pfnJoystickName; 28 | 29 | HKEY g_hkey; 30 | HANDLE g_hEvent; 31 | 32 | 33 | Sint16 SDLCALL Hooked_SDL_JoystickGetAxis(SDL_Joystick * joystick, int axis) 34 | { 35 | static std::map g_mapSettings; 36 | 37 | // The event is signalled when the GUI has changed a controller setting, 38 | // or on the first pass through this loop as it's created signalled. 39 | if (WaitForSingleObject(g_hEvent, 0) != WAIT_TIMEOUT) 40 | { 41 | // Clear out the settings cache. 42 | g_mapSettings.clear(); 43 | 44 | // Start a new watch for registry changes. 45 | RegNotifyChangeKeyValue( 46 | g_hkey, 47 | FALSE, 48 | REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_THREAD_AGNOSTIC, 49 | g_hEvent, 50 | TRUE); 51 | } 52 | 53 | // Look for cached settings for this controller. 54 | auto it = g_mapSettings.find(joystick); 55 | if (it == g_mapSettings.end()) 56 | { 57 | DWORD dwType = REG_DWORD; 58 | DWORD dwData = 1; // not blocked 59 | DWORD cbData{ sizeof(dwData) }; 60 | 61 | // Look up the controller name. 62 | auto pJoystickName = g_pfnJoystickName(joystick); 63 | if (pJoystickName) 64 | { 65 | // Read the current registry setting for this controller. 66 | // Unrecognised controllers will be allowed by default. 67 | RegQueryValueExA( 68 | g_hkey, 69 | pJoystickName, 70 | NULL, 71 | &dwType, 72 | reinterpret_cast(&dwData), 73 | &cbData); 74 | } 75 | 76 | g_mapSettings[joystick] = (dwData != 0); 77 | } 78 | 79 | // If this controller is to be blocked, return zero axis movement. 80 | if (g_mapSettings[joystick] == FALSE) 81 | { 82 | return 0; 83 | } 84 | 85 | // Chain unblocked controllers through to the original handler. 86 | return pfnOrig_SDL_JoystickGetAxis(joystick, axis); 87 | } 88 | 89 | void InstallHook() 90 | { 91 | // The original target is an indirect call, which means it points to an 92 | // address holding the address of the function. We need to do the same. 93 | static auto pfnHookedFunction = Hooked_SDL_JoystickGetAxis; 94 | auto ppfnHookedFunction = &pfnHookedFunction; 95 | 96 | // The address to patch is 6 bytes into the target function preamble. 97 | auto pb = reinterpret_cast(g_pfnJoystickGetAxis); 98 | auto ppfn = reinterpret_cast(pb + 6); 99 | 100 | // Ensure the hook point is compatible and not already installed. 101 | if (memcmp(pb, "\x55\x8b\xec\x5d\xff\x25", 6) == 0 && 102 | *ppfn != &pfnHookedFunction) 103 | { 104 | // Save the original function address. 105 | pfnOrig_SDL_JoystickGetAxis = **ppfn; 106 | 107 | // Write our own address to intercept calls to it. 108 | WriteProcessMemory( 109 | GetCurrentProcess(), 110 | ppfn, 111 | &ppfnHookedFunction, 112 | sizeof(pfnHookedFunction), 113 | NULL); 114 | } 115 | 116 | } 117 | 118 | BOOL APIENTRY DllMain( 119 | _In_ HMODULE /*hinstDLL*/, 120 | _In_ DWORD fdwReason, 121 | _In_ LPVOID /*lpvReserved*/) 122 | { 123 | if (fdwReason == DLL_PROCESS_ATTACH) 124 | { 125 | #ifdef _DEBUG 126 | // Convenient delay point to allow us to attach the debugger. 127 | MessageBox(NULL, TEXT("In DllMain for inject.dll"), APP_NAME, MB_OK | MB_ICONINFORMATION); 128 | #endif 129 | 130 | // The target process will have loaded SDL2 already, so find it. 131 | auto hmodSDL2 = LoadLibrary(TEXT("SDL2.dll")); 132 | if (hmodSDL2) 133 | { 134 | // One function to hook, one to use to find the controller name. 135 | g_pfnJoystickGetAxis = reinterpret_cast( 136 | GetProcAddress(hmodSDL2, "SDL_JoystickGetAxis")); 137 | g_pfnJoystickName = reinterpret_cast( 138 | GetProcAddress(hmodSDL2, "SDL_JoystickName")); 139 | } 140 | 141 | // Event used for registry change monitoring, signalled by default. 142 | g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL); 143 | 144 | // Registry containing settings, written to by the GUI. 145 | auto status = RegCreateKey( 146 | HKEY_CURRENT_USER, 147 | SETTINGS_KEY, 148 | &g_hkey); 149 | 150 | // Ensure we have everything needed before installing the hook. 151 | if (g_pfnJoystickGetAxis && g_pfnJoystickName && g_hEvent && !status) 152 | { 153 | InstallHook(); 154 | } 155 | else 156 | { 157 | MessageBox( 158 | NULL, 159 | TEXT("Failed to install hook!"), 160 | APP_NAME, 161 | MB_ICONEXCLAMATION | MB_OK); 162 | } 163 | } 164 | 165 | return TRUE; 166 | } 167 | -------------------------------------------------------------------------------- /inject/inject.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {C10D7779-70C9-4D6C-8486-768908A3E769} 15 | Win32Proj 16 | inject 17 | 8.1 18 | 19 | 20 | 21 | DynamicLibrary 22 | true 23 | v140 24 | Unicode 25 | 26 | 27 | DynamicLibrary 28 | false 29 | v140 30 | true 31 | Unicode 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | true 47 | 48 | 49 | false 50 | 51 | 52 | 53 | Level4 54 | Disabled 55 | WIN32;_DEBUG;_WINDOWS;_USRDLL;INJECT_EXPORTS;%(PreprocessorDefinitions) 56 | 57 | 58 | Windows 59 | true 60 | 61 | 62 | 63 | 64 | Level4 65 | MaxSpeed 66 | true 67 | true 68 | WIN32;NDEBUG;_WINDOWS;_USRDLL;INJECT_EXPORTS;%(PreprocessorDefinitions) 69 | MultiThreaded 70 | 71 | 72 | Windows 73 | true 74 | true 75 | true 76 | 77 | 78 | @if exist c:\batch\sign.bat c:\batch\sign.bat "$(TargetPath)" 79 | 80 | 81 | 82 | 83 | false 84 | 85 | 86 | false 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /inject/inject.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | 10 | 11 | Source Files 12 | 13 | 14 | -------------------------------------------------------------------------------- /install.iss: -------------------------------------------------------------------------------- 1 | ; Inno Setup script for DashFix installer 2 | 3 | #define MyAppName "DashFix" 4 | #define MyAppVersion "1.1" 5 | #define MyAppPublisher "Simon Owen" 6 | #define MyAppURL "https://github.com/simonowen/dashfix" 7 | #define MyAppExeName "DashFix.exe" 8 | 9 | [Setup] 10 | ; NOTE: The value of AppId uniquely identifies this application. 11 | ; Do not use the same AppId value in installers for other applications. 12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 13 | AppId={{E693D1E7-CF41-48CE-8E10-8A7FB6D6AE03} 14 | AppName={#MyAppName} 15 | AppVersion={#MyAppVersion} 16 | AppPublisher={#MyAppPublisher} 17 | AppPublisherURL={#MyAppURL} 18 | AppSupportURL={#MyAppURL} 19 | AppUpdatesURL={#MyAppURL} 20 | DefaultDirName={pf}\{#MyAppName} 21 | DefaultGroupName={#MyAppName} 22 | UninstallDisplayName={#MyAppName} 23 | DisableProgramGroupPage=auto 24 | InfoBeforeFile=License.txt 25 | OutputDir=. 26 | OutputBaseFilename=DashFixSetup_{#StringChange(MyAppVersion, '.', '')} 27 | Compression=lzma 28 | SolidCompression=yes 29 | SignTool=signtool $f 30 | SignedUninstaller=yes 31 | 32 | [Languages] 33 | Name: "english"; MessagesFile: "compiler:Default.isl" 34 | 35 | [Messages] 36 | SetupAppTitle=Setup {#MyAppName} 37 | SetupWindowTitle=Setup - {#MyAppName} v{#MyAppVersion} 38 | 39 | [Files] 40 | Source: "Release\DashFix.exe"; DestDir: "{app}"; Flags: ignoreversion 41 | Source: "Release\inject.dll"; DestDir: "{app}"; Flags: ignoreversion 42 | Source: "ReadMe.md"; DestDir: "{app}"; Flags: ignoreversion 43 | Source: "License.txt"; DestDir: "{app}"; Flags: ignoreversion 44 | 45 | [Icons] 46 | Name: "{commonprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 47 | Name: "{commonprograms}\{#MyAppName} Website"; Filename: "https://github.com/simonowen/dashfix" 48 | 49 | [Registry] 50 | Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "{#MyAppName}"; ValueData: """{app}\{#MyAppExeName}"" --startup"; Flags: uninsdeletevalue 51 | Root: HKCU; Subkey: "Software\SimonOwen"; Flags: uninsdeletekeyifempty 52 | Root: HKCU; Subkey: "Software\SimonOwen\DashFix"; Flags: uninsdeletekey 53 | 54 | [UninstallRun] 55 | Filename: "taskkill.exe"; Parameters: "/f /im ""{#MyAppExeName}"""; Flags: runhidden 56 | 57 | [Code] 58 | 59 | function GetAppPid(const ExeName : string): Integer; 60 | var 61 | WbemLocator: Variant; 62 | WbemServices: Variant; 63 | WbemObjectSet: Variant; 64 | begin 65 | WbemLocator := CreateOleObject('WBEMScripting.SWBEMLocator'); 66 | WbemServices := WbemLocator.ConnectServer('.', 'root\CIMV2'); 67 | WbemObjectSet := WbemServices.ExecQuery(Format('SELECT ProcessId FROM Win32_Process Where Name="%s"',[ExeName])); 68 | if WbemObjectSet.Count > 0 then 69 | Result := WbemObjectSet.ItemIndex(0).ProcessId 70 | else 71 | Result := 0; 72 | end; 73 | 74 | function CloseSteamApps(const Operation: String): Boolean; 75 | var 76 | Response: Integer; 77 | begin 78 | Result := True 79 | 80 | while Result and ((GetAppPid('Steam.exe') <> 0) or (GetAppPid('vrdashboard.exe') <> 0)) do 81 | begin 82 | Response := MsgBox('Please close Steam and SteamVR to ' + Operation + ' {#MyAppName}.', mbInformation, MB_OKCANCEL); 83 | if Response = IDCANCEL then 84 | Result := False 85 | end; 86 | end; 87 | 88 | procedure CloseApplication(const ExeName: String); 89 | var 90 | ResultCode: Integer; 91 | begin 92 | Exec(ExpandConstant('taskkill.exe'), '/f /im ' + '"' + ExeName + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); 93 | end; 94 | 95 | 96 | function PrepareToInstall(var NeedsRestart: Boolean): String; 97 | begin 98 | Result := '' 99 | 100 | if GetAppPid('DashFix.exe') <> 0 then 101 | begin 102 | if not CloseSteamApps('upgrade') then 103 | Result := 'Steam or SteamVR are still running.' 104 | else 105 | CloseApplication('{#MyAppExeName}') 106 | end; 107 | end; 108 | 109 | function NextButtonClick(CurPageID: Integer): Boolean; 110 | var 111 | ResultCode: Integer; 112 | begin 113 | Result := True; 114 | 115 | if (CurPageID = wpFinished) and ((not WizardForm.YesRadio.Visible) or 116 | (not WizardForm.YesRadio.Checked)) 117 | then 118 | ExecAsOriginalUser(ExpandConstant('{app}\{#MyAppExeName}'), '', '', 119 | SW_SHOWNORMAL, ewNoWait, ResultCode); 120 | end; 121 | 122 | function InitializeUninstall(): Boolean; 123 | begin 124 | Result := CloseSteamApps('uninstall') 125 | end; 126 | --------------------------------------------------------------------------------