├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── Toolchain-Windows-i686.cmake ├── Toolchain-Windows-x86_64.cmake ├── device.cpp ├── jumper.h ├── main.cpp ├── main.h ├── test ├── ditest.cpp ├── test.sh └── xitest.cpp └── xinput.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | DisableFormat: false 3 | ... 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(koku-xinput-wine) 3 | 4 | set(USING_CROSS_COMPILER OFF) 5 | if (CMAKE_C_COMPILER MATCHES "i686-w64-mingw32-gcc" OR 6 | CMAKE_C_COMPILER MATCHES "x86_64-w64-mingw32-gcc") 7 | set(USING_CROSS_COMPILER ON) 8 | endif() 9 | 10 | set(USING_64BIT_COMPILER OFF) 11 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 12 | set(USING_64BIT_COMPILER ON) 13 | endif() 14 | 15 | option(BUILD_LIBRARY "Build main library" ON) 16 | option(BUILD_32BIT_LIBRARY "Build main library for 32-bit wine" ON) 17 | option(BUILD_64BIT_LIBRARY "Build main library for 64-bit wine" ${USING_64BIT_COMPILER}) 18 | option(BUILD_TESTS "Build test executables" ON) 19 | 20 | if (BUILD_LIBRARY) 21 | if (USING_CROSS_COMPILER) 22 | message(WARNING "Called with cross-compiler; skipping main library") 23 | elseif (NOT BUILD_32BIT_LIBRARY AND NOT BUILD_64BIT_LIBRARY) 24 | message(WARNING "No architectures enabled; skipping main library") 25 | else() 26 | find_package(PkgConfig REQUIRED) 27 | pkg_check_modules(SDL2 REQUIRED sdl2) 28 | 29 | find_path(WINE_INCLUDE_DIR windows.h 30 | HINTS ${WINEROOT}/include ${WINE_ROOT}/include ${WINE_INCLUDEDIR} 31 | PATHS /opt/wine-staging/include 32 | PATH_SUFFIXES wine-development/windows wine/windows) 33 | if (WINE_INCLUDE_DIR) 34 | message(STATUS "Found wine headers: ${WINE_INCLUDE_DIR}") 35 | else() 36 | message(FATAL_ERROR "Couldn't find wine headers!") 37 | endif() 38 | 39 | set(CMAKE_CXX_STANDARD 11) 40 | set(CMAKE_SHARED_LIBRARY_PREFIX "") 41 | 42 | set(KOKU_SOURCE_FILES main.cpp xinput.cpp device.cpp) 43 | set(KOKU_INCLUDE_DIRS ${WINE_INCLUDE_DIR} ${SDL2_INCLUDE_DIR}) 44 | set(KOKU_LINK_LIBRARIES ${SDL2_LIBRARIES}) 45 | set(KOKU_COMPILE_OPTIONS -Wall -Wextra 46 | -Wno-attributes -Wno-ignored-attributes -Wno-subobject-linkage 47 | -Wno-unused-parameter -Wno-unused-variable) 48 | 49 | if (BUILD_32BIT_LIBRARY) 50 | add_library(koku-xinput-wine SHARED ${KOKU_SOURCE_FILES}) 51 | target_include_directories(koku-xinput-wine PRIVATE ${KOKU_INCLUDE_DIRS}) 52 | target_link_libraries(koku-xinput-wine PRIVATE -m32 ${KOKU_LINK_LIBRARIES}) 53 | target_compile_options(koku-xinput-wine PRIVATE -m32 ${KOKU_COMPILE_OPTIONS}) 54 | endif() 55 | 56 | if (BUILD_64BIT_LIBRARY) 57 | add_library(koku-xinput-wine64 SHARED ${KOKU_SOURCE_FILES}) 58 | target_include_directories(koku-xinput-wine64 PRIVATE ${KOKU_INCLUDE_DIRS}) 59 | target_link_libraries(koku-xinput-wine64 PRIVATE ${KOKU_LINK_LIBRARIES}) 60 | target_compile_options(koku-xinput-wine64 PRIVATE ${KOKU_COMPILE_OPTIONS}) 61 | endif() 62 | endif() 63 | endif() 64 | 65 | if (BUILD_TESTS) 66 | if (USING_CROSS_COMPILER) 67 | add_executable(ditest test/ditest.cpp) 68 | target_link_libraries(ditest PRIVATE -static -static-libgcc 69 | dinput dinput8 dxguid user32 ole32 oleaut32) 70 | add_executable(xitest test/xitest.cpp) 71 | target_link_libraries(xitest PRIVATE -static -static-libgcc 72 | xinput) 73 | else() 74 | find_program(I686_MINGW64_GCC i686-w64-mingw32-gcc) 75 | if (I686_MINGW64_GCC) 76 | execute_process( 77 | COMMAND ${CMAKE_COMMAND} 78 | -B${CMAKE_BINARY_DIR}/i686-w64-mingw32 79 | -H${CMAKE_SOURCE_DIR} 80 | -DCMAKE_TOOLCHAIN_FILE=${CMAKE_SOURCE_DIR}/Toolchain-Windows-i686.cmake) 81 | add_custom_target(i686-w64-mingw32-tests ALL 82 | COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/i686-w64-mingw32) 83 | else() 84 | message(WARNING "No 32-bit cross-compiler; skipping 32-bit tests") 85 | endif() 86 | 87 | find_program(X86_64_MINGW64_GCC x86_64-w64-mingw32-gcc) 88 | if (X86_64_MINGW64_GCC) 89 | execute_process( 90 | COMMAND ${CMAKE_COMMAND} 91 | -B${CMAKE_BINARY_DIR}/x86_64-w64-mingw32 92 | -H${CMAKE_SOURCE_DIR} 93 | -DCMAKE_TOOLCHAIN_FILE=${CMAKE_SOURCE_DIR}/Toolchain-Windows-x86_64.cmake) 94 | add_custom_target(x86_64-w64-mingw32-tests ALL 95 | COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/x86_64-w64-mingw32) 96 | else() 97 | message(WARNING "No 64-bit cross-compiler; skipping 64-bit tests") 98 | endif() 99 | endif() 100 | endif() 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Luca Béla Palkovics 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | koku-xinput-wine 2 | ================ 3 | 4 | Adds xinput support to wine, without changing the source of wine. 5 | Modified to use SDL2 gamepad mappings. 6 | 7 | Install 8 | --------------------- 9 | If you are on 64Bit you will need a 32Bit tool-chain (multilib). 10 | 11 | You will also need SDL2 libraries and development headers installed. On 64Bit systems you will need both 32Bit and 64Bit SDL2 libraries. 12 | 13 | [user@host code]$ git clone https://github.com/KoKuToru/koku-xinput-wine.git 14 | [user@host code]$ cd koku-xinput-wine 15 | [user&host code]$ cmake . 16 | [user@host code]$ make 17 | 18 | After this there will be a 'koku-xinput-wine.so' in the folder, and on 64Bit systems there will also be 'koku-xinput-wine64.so'. 19 | 20 | Usage 21 | --------------------- 22 | 23 | To hook a 32Bit application: 24 | 25 | [user@host game]$ export LD_PRELOAD=/lib-path/koku-xinput-wine.so 26 | [user@host game]$ wine game.exe 27 | 28 | To hook a 64Bit application: 29 | 30 | [user@host game]$ export LD_PRELOAD=/lib-path/koku-xinput-wine64.so 31 | [user@host game]$ wine game64.exe 32 | 33 | To hook both 32Bit and 64Bit applications (the correct hook will be automatically selected): 34 | 35 | [user@host game]$ export LD_PRELOAD="/lib-path/koku-xinput-wine.so /lib-path/koku-xinput-wine64.so" 36 | [user@host game]$ wine game.exe 37 | [user@host game]$ wine game64.exe 38 | 39 | Simple Elegance, without patching wine. 40 | Of course it would be wiser to put this code into wine.. 41 | 42 | Config 43 | --------------------- 44 | 45 | You can add SDL2 gamepad mappings by putting them in file named "gamecontrollerdb.txt" and place it next to your game executable, or via the *SDL_GAMECONTROLLERCONFIG* environment variable. 46 | 47 | You can find a premade gamecontrollerdb.txt with a lot of mappings [here](https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt). 48 | 49 | These mappings are not needed if you're already using a standard Xbox controller. 50 | 51 | Troubleshooting 52 | --------------------- 53 | 54 | When the library is properly loaded, you should notice two quick rumbles sent to the controller. 55 | 56 | When running on a 64bit system, you may see errors like this: 57 | 58 | ERROR: ld.so: object '/lib-path/koku-xinput-wine.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored. 59 | ERROR: ld.so: object '/lib-path/koku-xinput-wine64.so' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS64): ignored. 60 | 61 | These messages don't necessarily mean there is a problem. They're just saying that the 64Bit hooks aren't installed into 32Bit applications and vica-versa. If you're seeing these errors and not getting the "welcome" rumbles, double-check that you're preloading the correct library. 62 | -------------------------------------------------------------------------------- /Toolchain-Windows-i686.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Windows) 2 | set(TOOLCHAIN_PREFIX i686-w64-mingw32) 3 | 4 | set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) 5 | set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++) 6 | set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres) 7 | 8 | set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) 9 | 10 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 11 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 12 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 13 | -------------------------------------------------------------------------------- /Toolchain-Windows-x86_64.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Windows) 2 | set(TOOLCHAIN_PREFIX x86_64-w64-mingw32) 3 | 4 | set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) 5 | set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++) 6 | set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres) 7 | 8 | set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) 9 | 10 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 11 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 12 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 13 | -------------------------------------------------------------------------------- /device.cpp: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | #define _FORCENAMELESSUNION 4 | #define CINTERFACE 5 | #define INITGUID 6 | 7 | #include 8 | #include 9 | 10 | namespace koku { 11 | unsigned short wine_gamepad[] = {'V', 'I', 'D', '_', '3', 'E', 'D', '9', 12 | '&', 'P', 'I', 'D', '_', '9', 'E', '5', 13 | '7', '&', 'I', 'G', '_', '0', '0'}; 14 | unsigned short x360_gamepad[] = {'V', 'I', 'D', '_', '0', '4', '5', 'E', 15 | '&', 'P', 'I', 'D', '_', '0', '2', '8', 16 | 'E', '&', 'I', 'G', '_', '0', '0'}; 17 | 18 | HRESULT STDMETHODCALLTYPE IWbemClassObject_Get(IWbemClassObject *This, 19 | LPCWSTR wszName, LONG lFlags, 20 | VARIANT *pVal, CIMTYPE *pType, 21 | LONG *plFlavor) { 22 | debug(""); 23 | 24 | auto wszNameLen = ((uint32_t *)wszName)[-1]; 25 | if (std::memcmp(wszName, u"DeviceID", wszNameLen) == 0) { 26 | debug("DeviceID"); 27 | 28 | pVal->vt = VT_BSTR; 29 | pVal->bstrVal = x360_gamepad; 30 | 31 | return 0; 32 | } 33 | 34 | return 1; 35 | } 36 | 37 | ULONG STDMETHODCALLTYPE IWbemClassObject_Release(IWbemClassObject *This) { 38 | debug(""); 39 | delete This->lpVtbl; 40 | delete This; 41 | return 0; 42 | } 43 | 44 | koku::jumper::type> 45 | IEnumWbemClassObject_Next_Jumper; 46 | HRESULT STDMETHODCALLTYPE IEnumWbemClassObject_Next_Koku( 47 | IEnumWbemClassObject *This, LONG lTimeout, ULONG uCount, 48 | IWbemClassObject **apObjects, ULONG *puReturned) { 49 | debug(""); 50 | 51 | if (uCount == 0) 52 | return WBEM_S_FALSE; 53 | 54 | if (puReturned == nullptr || *puReturned != 0) 55 | return WBEM_E_INVALID_PARAMETER; 56 | 57 | auto wbemClassObject = new IWbemClassObject{}; 58 | wbemClassObject->lpVtbl = new IWbemClassObjectVtbl{}; 59 | 60 | wbemClassObject->lpVtbl->Get = &IWbemClassObject_Get; 61 | wbemClassObject->lpVtbl->Release = &IWbemClassObject_Release; 62 | 63 | *puReturned = 1; 64 | *apObjects = wbemClassObject; 65 | 66 | return WBEM_S_FALSE; 67 | } 68 | 69 | koku::jumper::type> 70 | IWbemServices_CreateInstanceEnum_Jumper; 71 | HRESULT STDMETHODCALLTYPE IWbemServices_CreateInstanceEnum_Koku( 72 | IWbemServices *This, const BSTR strFilter, LONG lFlags, IWbemContext *pCtx, 73 | IEnumWbemClassObject **ppEnum) { 74 | debug(""); 75 | auto result = IWbemServices_CreateInstanceEnum_Jumper(This, strFilter, lFlags, 76 | pCtx, ppEnum); 77 | 78 | auto strFilterLen = ((uint32_t *)strFilter)[-1]; 79 | if (std::memcmp(strFilter, u"Win32_PNPEntity", strFilterLen) == 0) { 80 | auto enumWbemClassObject = *ppEnum; 81 | if (enumWbemClassObject != nullptr && 82 | IEnumWbemClassObject_Next_Jumper.src != 83 | enumWbemClassObject->lpVtbl->Next) { 84 | IEnumWbemClassObject_Next_Jumper = koku::make_jumper( 85 | enumWbemClassObject->lpVtbl->Next, &IEnumWbemClassObject_Next_Koku); 86 | debug("found IEnumWbemClassObject_Next at %p, redirecting it to %p", 87 | enumWbemClassObject->lpVtbl->Next, &IEnumWbemClassObject_Next_Koku); 88 | } 89 | } 90 | 91 | return result; 92 | } 93 | 94 | koku::jumper::type> 95 | IWbemLocator_ConnectServer_Jumper; 96 | HRESULT STDMETHODCALLTYPE IWbemLocator_ConnectServer_Koku( 97 | IWbemLocator *This, const BSTR strNetworkResource, const BSTR strUser, 98 | const BSTR strPassword, const BSTR strLocale, LONG lSecurityFlags, 99 | const BSTR strAuthority, IWbemContext *pCtx, IWbemServices **ppNamespace) { 100 | debug(""); 101 | auto result = IWbemLocator_ConnectServer_Jumper( 102 | This, strNetworkResource, strUser, strPassword, strLocale, lSecurityFlags, 103 | strAuthority, pCtx, ppNamespace); 104 | 105 | auto wbemServices = *ppNamespace; 106 | if (wbemServices != nullptr && 107 | IWbemServices_CreateInstanceEnum_Jumper.src != 108 | wbemServices->lpVtbl->CreateInstanceEnum) { 109 | IWbemServices_CreateInstanceEnum_Jumper = 110 | koku::make_jumper(wbemServices->lpVtbl->CreateInstanceEnum, 111 | &IWbemServices_CreateInstanceEnum_Koku); 112 | debug("found IWbemServices_CreateInstanceEnum at %p, redirecting it to %p", 113 | wbemServices->lpVtbl->CreateInstanceEnum, 114 | &IWbemServices_CreateInstanceEnum_Koku); 115 | } 116 | 117 | return result; 118 | } 119 | 120 | koku::jumper CoCreateInstance_Jumper; 121 | HRESULT WINAPI CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter, 122 | DWORD dwClsContext, REFIID iid, LPVOID *ppv) { 123 | debug(""); 124 | auto result = 125 | CoCreateInstance_Jumper(rclsid, pUnkOuter, dwClsContext, iid, ppv); 126 | 127 | if (std::memcmp(&iid, &IID_IWbemLocator, sizeof(iid)) == 0) { 128 | auto wbemLocator = *(IWbemLocator **)ppv; 129 | if (wbemLocator != nullptr && 130 | IWbemLocator_ConnectServer_Jumper.src != 131 | wbemLocator->lpVtbl->ConnectServer) { 132 | IWbemLocator_ConnectServer_Jumper = koku::make_jumper( 133 | wbemLocator->lpVtbl->ConnectServer, &IWbemLocator_ConnectServer_Koku); 134 | debug("found IWbemLocator_ConnectServer at %p, redirecting it to %p", 135 | wbemLocator->lpVtbl->ConnectServer, 136 | &IWbemLocator_ConnectServer_Koku); 137 | } 138 | } 139 | 140 | return result; 141 | } 142 | 143 | void DeviceInit(void *handle) { 144 | if (auto address = 145 | (decltype(&CoCreateInstance))dlsym(handle, "CoCreateInstance")) { 146 | CoCreateInstance_Jumper = 147 | koku::make_jumper(address, &koku::CoCreateInstance); 148 | debug("found CoCreateInstance at %p, redirecting it to %p", address, 149 | &koku::CoCreateInstance); 150 | } 151 | } 152 | } // namespace koku 153 | -------------------------------------------------------------------------------- /jumper.h: -------------------------------------------------------------------------------- 1 | #ifndef KOKU_JUMPER_H 2 | #define KOKU_JUMPER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace koku { 14 | template struct jumper { 15 | F *src = nullptr; 16 | F *dst = nullptr; 17 | bool installed = false; 18 | 19 | #if UINTPTR_MAX == UINT64_MAX 20 | std::array header = {{0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0xff, 0xe0}}; 22 | #elif UINTPTR_MAX == UINT32_MAX 23 | std::array header = {{0xe9, 0x00, 0x00, 0x00, 0x00}}; 24 | #else 25 | #error "Unsupported architecture" 26 | #endif 27 | 28 | jumper() = default; 29 | jumper(jumper const &) = default; 30 | jumper(jumper &&) = default; 31 | jumper &operator=(jumper const &) = default; 32 | jumper &operator=(jumper &&) = default; 33 | 34 | jumper(F *src, F *dst) : src(src), dst(dst) { 35 | assert(src != nullptr); 36 | assert(dst != nullptr); 37 | 38 | #if UINTPTR_MAX == UINT64_MAX 39 | std::memcpy(&header[2], &dst, 8); 40 | #elif UINTPTR_MAX == UINT32_MAX 41 | auto distance = (uintptr_t)std::abs((intptr_t)src - (intptr_t)dst); 42 | assert(distance <= INT32_MAX); 43 | auto rel = (uintptr_t)dst - ((uintptr_t)src + header.size()); 44 | std::memcpy(&header[1], &rel, 4); 45 | #else 46 | #error "Unsupported architecture" 47 | #endif 48 | 49 | install(); 50 | } 51 | 52 | void swap_header() { 53 | assert(src != nullptr); 54 | 55 | auto pagesize = sysconf(_SC_PAGESIZE); 56 | assert((pagesize & (pagesize - 1)) == 0); 57 | 58 | auto address = (uintptr_t)src & ~(pagesize - 1); 59 | auto length = (uintptr_t)src + header.size() - address; 60 | int rc = mprotect((void *)address, length, PROT_READ|PROT_WRITE|PROT_EXEC); 61 | assert(rc == 0); 62 | 63 | auto tmp = header; 64 | std::memcpy(&header[0], (const void *)src, header.size()); 65 | std::memcpy((void *)src, &tmp[0], tmp.size()); 66 | } 67 | 68 | void install() { 69 | assert(!installed); 70 | swap_header(); 71 | installed = true; 72 | } 73 | 74 | void uninstall() { 75 | assert(installed); 76 | swap_header(); 77 | installed = false; 78 | } 79 | 80 | struct scoped_uninstall { 81 | jumper &jmp; 82 | scoped_uninstall(jumper &jmp) : jmp(jmp) { jmp.uninstall(); } 83 | ~scoped_uninstall() { jmp.install(); } 84 | }; 85 | 86 | template 87 | auto operator()(As &&... as) -> decltype(src(std::forward(as)...)) { 88 | auto _ = scoped_uninstall(*this); 89 | return src(std::forward(as)...); 90 | } 91 | }; 92 | 93 | template jumper make_jumper(F *src, F *dst) { 94 | return jumper{src, dst}; 95 | } 96 | } // namespace koku 97 | 98 | #endif // KOKU_JUMPER_H 99 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | #include 4 | 5 | extern "C" void *wine_dll_load(const char *filename, char *error, int errorsize, 6 | int *file_exists) { 7 | auto result = ((decltype(&wine_dll_load))dlsym(RTLD_NEXT, "wine_dll_load"))( 8 | filename, error, errorsize, file_exists); 9 | debug("wine_dll_load(%s, ...)", filename); 10 | 11 | if (std::string{"xinput1_3.dll"} == filename || 12 | std::string{"xinput9_1_0.dll"} == filename || 13 | std::string{"xinput1_4.dll"} == filename) { 14 | koku::XInputInit(result); 15 | } 16 | 17 | if (std::string{"ole32.dll"} == filename) { 18 | koku::DeviceInit(result); 19 | } 20 | 21 | return result; 22 | } 23 | -------------------------------------------------------------------------------- /main.h: -------------------------------------------------------------------------------- 1 | #ifndef KOKU_MAIN_H 2 | #define KOKU_MAIN_H 3 | 4 | #include 5 | 6 | #include "jumper.h" 7 | 8 | #define debug(message, ...) \ 9 | if (getenv("KOKU_XINPUT_DEBUG") != nullptr) \ 10 | std::printf("koku-xinput-wine: [%d] %s:%d %s " message "\n", getpid(), \ 11 | __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); 12 | 13 | namespace koku { 14 | void XInputInit(void *handle); 15 | void DeviceInit(void *handle); 16 | }; // namespace koku 17 | 18 | #endif // KOKU_MAIN_H 19 | -------------------------------------------------------------------------------- /test/ditest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | // #include 11 | 12 | #ifndef SAFE_RELEASE 13 | #define SAFE_RELEASE(x) \ 14 | if (x != NULL) { \ 15 | x->Release(); \ 16 | x = NULL; \ 17 | } 18 | #endif 19 | 20 | //----------------------------------------------------------------------------- 21 | // Enum each PNP device using WMI and check each device ID to see if it contains 22 | // "IG_" (ex. "VID_045E&PID_028E&IG_00"). If it does, then it's an XInput 23 | // device Unfortunately this information can not be found by just using 24 | // DirectInput 25 | //----------------------------------------------------------------------------- 26 | BOOL IsXInputDevice(const GUID *pGuidProductFromDirectInput) { 27 | IWbemLocator *pIWbemLocator = NULL; 28 | IEnumWbemClassObject *pEnumDevices = NULL; 29 | IWbemClassObject *pDevices[20] = {0}; 30 | IWbemServices *pIWbemServices = NULL; 31 | BSTR bstrNamespace = NULL; 32 | BSTR bstrDeviceID = NULL; 33 | BSTR bstrClassName = NULL; 34 | DWORD uReturned = 0; 35 | bool bIsXinputDevice = false; 36 | UINT iDevice = 0; 37 | VARIANT var; 38 | HRESULT hr; 39 | 40 | // CoInit if needed 41 | hr = CoInitialize(NULL); 42 | bool bCleanupCOM = SUCCEEDED(hr); 43 | 44 | // Create WMI 45 | hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER, 46 | __uuidof(IWbemLocator), (LPVOID *)&pIWbemLocator); 47 | if (FAILED(hr) || pIWbemLocator == NULL) 48 | goto LCleanup; 49 | 50 | bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2"); 51 | if (bstrNamespace == NULL) 52 | goto LCleanup; 53 | bstrClassName = SysAllocString(L"Win32_PNPEntity"); 54 | if (bstrClassName == NULL) 55 | goto LCleanup; 56 | bstrDeviceID = SysAllocString(L"DeviceID"); 57 | if (bstrDeviceID == NULL) 58 | goto LCleanup; 59 | 60 | // Connect to WMI 61 | hr = pIWbemLocator->ConnectServer(bstrNamespace, NULL, NULL, 0L, 0L, NULL, 62 | NULL, &pIWbemServices); 63 | if (FAILED(hr) || pIWbemServices == NULL) 64 | goto LCleanup; 65 | 66 | // Switch security level to IMPERSONATE. 67 | CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, 68 | RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 69 | EOAC_NONE); 70 | 71 | hr = 72 | pIWbemServices->CreateInstanceEnum(bstrClassName, 0, NULL, &pEnumDevices); 73 | if (FAILED(hr) || pEnumDevices == NULL) 74 | goto LCleanup; 75 | 76 | // Loop over all devices 77 | for (;;) { 78 | // Get 20 at a time 79 | hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned); 80 | if (FAILED(hr)) 81 | goto LCleanup; 82 | if (uReturned == 0) 83 | break; 84 | 85 | for (iDevice = 0; iDevice < uReturned; iDevice++) { 86 | // For each device, get its device ID 87 | hr = pDevices[iDevice]->Get(bstrDeviceID, 0L, &var, NULL, NULL); 88 | if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL) { 89 | // Check if the device ID contains "IG_". If it does, then it's an 90 | // XInput device This information can not be found from DirectInput 91 | if (wcsstr(var.bstrVal, L"IG_")) { 92 | // If it does, then get the VID/PID from var.bstrVal 93 | DWORD dwPid = 0, dwVid = 0; 94 | WCHAR *strVid = wcsstr(var.bstrVal, L"VID_"); 95 | if (strVid && swscanf(strVid, L"VID_%4X", &dwVid) != 1) 96 | dwVid = 0; 97 | WCHAR *strPid = wcsstr(var.bstrVal, L"PID_"); 98 | if (strPid && swscanf(strPid, L"PID_%4X", &dwPid) != 1) 99 | dwPid = 0; 100 | 101 | // Compare the VID/PID to the DInput device 102 | DWORD dwVidPid = MAKELONG(dwVid, dwPid); 103 | printf("Found device %04x\n", pGuidProductFromDirectInput->Data1); 104 | if (dwVidPid == pGuidProductFromDirectInput->Data1) { 105 | printf("Found XINPUT device %04x\n", 106 | pGuidProductFromDirectInput->Data1); 107 | bIsXinputDevice = true; 108 | goto LCleanup; 109 | } 110 | } 111 | } 112 | SAFE_RELEASE(pDevices[iDevice]); 113 | } 114 | } 115 | 116 | LCleanup: 117 | if (bstrNamespace) 118 | SysFreeString(bstrNamespace); 119 | if (bstrDeviceID) 120 | SysFreeString(bstrDeviceID); 121 | if (bstrClassName) 122 | SysFreeString(bstrClassName); 123 | for (iDevice = 0; iDevice < 20; iDevice++) 124 | SAFE_RELEASE(pDevices[iDevice]); 125 | SAFE_RELEASE(pEnumDevices); 126 | SAFE_RELEASE(pIWbemLocator); 127 | SAFE_RELEASE(pIWbemServices); 128 | 129 | if (bCleanupCOM) 130 | CoUninitialize(); 131 | 132 | return bIsXinputDevice; 133 | } 134 | 135 | LPDIRECTINPUT8 g_pDI; 136 | LPDIRECTINPUTDEVICE8 g_pJoystick; 137 | 138 | //----------------------------------------------------------------------------- 139 | // Name: EnumJoysticksCallback() 140 | // Desc: Called once for each enumerated joystick. If we find one, create a 141 | // device interface on it so we can play with it. 142 | //----------------------------------------------------------------------------- 143 | BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE *pdidInstance, 144 | VOID *pContext) { 145 | HRESULT hr; 146 | 147 | if (!IsXInputDevice(&pdidInstance->guidProduct)) 148 | return DIENUM_CONTINUE; 149 | 150 | // Device is verified not XInput, so add it to the list of DInput devices 151 | 152 | hr = g_pDI->CreateDevice(pdidInstance->guidInstance, &g_pJoystick, NULL); 153 | return DIENUM_CONTINUE; 154 | } 155 | 156 | BOOL CALLBACK EnumAxesCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) { 157 | DIPROPRANGE range; 158 | range.diph.dwSize = sizeof(DIPROPRANGE); 159 | range.diph.dwHeaderSize = sizeof(DIPROPHEADER); 160 | range.diph.dwHow = DIPH_BYID; 161 | range.diph.dwObj = lpddoi->dwType; 162 | range.lMin = -1000; 163 | range.lMax = +1000; 164 | 165 | HRESULT hr = g_pJoystick->SetProperty(DIPROP_RANGE, &range.diph); 166 | if (FAILED(hr)) 167 | return DIENUM_STOP; 168 | 169 | return DIENUM_CONTINUE; 170 | } 171 | 172 | void shutdown() { 173 | if (g_pJoystick) 174 | g_pJoystick->Release(); 175 | 176 | if (g_pDI) 177 | g_pDI->Release(); 178 | } 179 | 180 | int main() { 181 | HRESULT hr; 182 | 183 | hr = DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, 184 | IID_IDirectInput8, (void **)&g_pDI, NULL); 185 | if (FAILED(hr)) 186 | return 10; 187 | 188 | printf("DirectInput initialized.\n"); 189 | 190 | g_pDI->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, NULL, 191 | DIEDFL_ATTACHEDONLY); 192 | 193 | if (!g_pJoystick) { 194 | printf("Couldn't find a joystick.\n"); 195 | shutdown(); 196 | return 10; 197 | } 198 | 199 | printf("Joystick created.\n"); 200 | 201 | if (FAILED(hr = g_pJoystick->SetDataFormat(&c_dfDIJoystick2))) { 202 | printf("Failed to set joystick data format.\n"); 203 | shutdown(); 204 | return 10; 205 | } 206 | 207 | WNDCLASS wc; 208 | 209 | wc.style = 0; 210 | wc.lpfnWndProc = DefWindowProc; 211 | wc.cbClsExtra = 0; 212 | wc.cbWndExtra = 0; 213 | wc.hInstance = GetModuleHandle(NULL); 214 | wc.hIcon = NULL; 215 | wc.hCursor = NULL; 216 | wc.hbrBackground = NULL; 217 | wc.lpszMenuName = NULL; 218 | wc.lpszClassName = "foo"; 219 | 220 | HWND hwnd = CreateWindow((LPCTSTR)RegisterClass(&wc), "", WS_POPUP, 0, 0, 0, 221 | 0, NULL, NULL, wc.hInstance, NULL); 222 | 223 | if (FAILED(hr = g_pJoystick->SetCooperativeLevel( 224 | hwnd, DISCL_EXCLUSIVE | DISCL_BACKGROUND))) { 225 | printf("Failed to set cooperative level.\n"); 226 | shutdown(); 227 | return 10; 228 | } 229 | 230 | if (FAILED( 231 | hr = g_pJoystick->EnumObjects(EnumAxesCallback, NULL, DIDFT_AXIS))) { 232 | printf("Failed to enumerate axes.\n"); 233 | shutdown(); 234 | return 10; 235 | } 236 | 237 | printf("All OK!\n"); 238 | 239 | bool acq = false; 240 | 241 | int winfx = 800 << 16; 242 | int winfy = 600 << 16; 243 | int winx = 0; 244 | int winy = 0; 245 | int acscale = 0; 246 | bool left = false, right = false, abs = false; 247 | bool absmode = false; 248 | 249 | for (;;) { 250 | MSG msg; 251 | while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { 252 | TranslateMessage(&msg); 253 | DispatchMessage(&msg); 254 | } 255 | Sleep(10); 256 | 257 | hr = S_OK; 258 | if (!acq) { 259 | hr = g_pJoystick->Acquire(); 260 | acq = SUCCEEDED(hr); 261 | if (FAILED(hr)) { 262 | printf("failed to acquire! %08x\n", hr); 263 | } 264 | } 265 | if (SUCCEEDED(hr)) { 266 | hr = g_pJoystick->Poll(); 267 | if (FAILED(hr)) 268 | printf("poll failed!\n"); 269 | } 270 | if (FAILED(hr)) { 271 | hr = g_pJoystick->Acquire(); 272 | while (hr == DIERR_INPUTLOST) 273 | hr = g_pJoystick->Acquire(); 274 | if (FAILED(hr)) 275 | acq = false; 276 | else 277 | acq = true; 278 | } 279 | 280 | if (SUCCEEDED(hr)) { 281 | DIJOYSTATE2 js; 282 | HRESULT hr = g_pJoystick->GetDeviceState(sizeof js, &js); 283 | 284 | if (FAILED(hr)) 285 | continue; 286 | 287 | #if 1 288 | if (js.lX != 500 && js.lY != 500) { 289 | POINT pt; 290 | GetCursorPos(&pt); 291 | 292 | winfx = ((winfx + 0x8000) & 0xffff) + (pt.x << 16) - 0x8000; 293 | winfy = ((winfy + 0x8000) & 0xffff) + (pt.y << 16) - 0x8000; 294 | 295 | winfx += js.lX * acscale; 296 | winfy += js.lY * acscale; 297 | 298 | acscale += pow(hypot(js.lX, js.lY) / 1000.0, 2.0) * 1000.0 / 16.0; 299 | 300 | winx = (winfx + 0x8000) >> 16; 301 | winy = (winfy + 0x8000) >> 16; 302 | 303 | if (absmode) { 304 | winx = 1600 * (js.lX + 1000) / 2001; 305 | winy = 1200 * (js.lY + 1000) / 2001; 306 | } 307 | 308 | if (winfx < (0 << 16)) 309 | winfx = 0 << 16; 310 | if (winfx > (1600 << 16)) 311 | winfx = 1600 << 16; 312 | if (winfy < (0 << 16)) 313 | winfy = 0 << 16; 314 | if (winfy > (1200 << 16)) 315 | winfy = 1200 << 16; 316 | 317 | // SetCursorPos(winx + 300*js.lX/1000, winy + 318 | // 300*js.lY/1000); 319 | 320 | SetCursorPos(winx, winy); 321 | } 322 | 323 | bool nowleft = 324 | (js.rgbButtons[1] | js.rgbButtons[10] | js.rgbButtons[11]) >= 0x80 || 325 | LOWORD(js.rgdwPOV[0]) != 0xffff; 326 | bool nowright = js.rgbButtons[0] >= 0x80; 327 | bool nowabs = js.rgbButtons[2] >= 0x80; 328 | 329 | if (fabs(js.lX) < 128 && fabs(js.lY) < 128) 330 | acscale = 128; 331 | 332 | if (left && !nowleft) 333 | mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, NULL); 334 | if (!left && nowleft) 335 | mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, NULL); 336 | if (right && !nowright) 337 | mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, NULL); 338 | if (!right && nowright) 339 | mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, NULL); 340 | 341 | if (!abs && nowabs) 342 | absmode = !absmode; 343 | 344 | left = nowleft; 345 | right = nowright; 346 | abs = nowabs; 347 | #else 348 | bool nowleft = (js.rgbButtons[0] | js.rgbButtons[1] | js.rgbButtons[2] | 349 | js.rgbButtons[3]) >= 0x80; 350 | 351 | if (nowleft && !left) { 352 | keybd_event(VK_NEXT, 0, 0, 0); 353 | } else if (!nowleft && left) { 354 | keybd_event(VK_NEXT, 0, KEYEVENTF_KEYUP, 0); 355 | } 356 | left = nowleft; 357 | #endif 358 | } 359 | } 360 | shutdown(); 361 | return 0; 362 | } 363 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | SRC="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" 6 | 7 | #DIR=$SRC/build/i686-w64-mingw32 8 | DIR=$SRC/build/x86_64-w64-mingw32 9 | 10 | mkdir -p $SRC/build 11 | 12 | pushd $SRC/build &> /dev/null 13 | cmake .. 14 | make -j10 15 | popd &> /dev/null 16 | 17 | pushd $DIR &> /dev/null 18 | export KOKU_XINPUT_DEBUG=1 19 | export LD_PRELOAD="$SRC/build/koku-xinput-wine.so $SRC/build/koku-xinput-wine64.so" 20 | wine xitest.exe 21 | wine ditest.exe 22 | popd &> /dev/null 23 | -------------------------------------------------------------------------------- /test/xitest.cpp: -------------------------------------------------------------------------------- 1 | #include "xinput.h" 2 | 3 | int main(int argc, char const *argv[]) { 4 | XInputEnable(true); 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /xinput.cpp: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #define XINPUT_CAPS_FFB_SUPPORTED 0x0001 14 | 15 | DWORD WINAPI XInputGetAudioDeviceIds(DWORD dwUserIndex, LPWSTR pRenderDeviceId, 16 | UINT *pRenderCount, 17 | LPWSTR pCaptureDeviceId, 18 | UINT *pCaptureCount); 19 | 20 | #define WINE_XINPUT_AXES 8 21 | #define MAX_XINPUT_BUTTONS 14 22 | 23 | namespace koku { 24 | struct SDLGamepad { 25 | SDL_Joystick *joystick; 26 | SDL_GameController *controller; 27 | SDL_Haptic *haptic; 28 | int haptic_effects[2]; 29 | }; 30 | static std::vector gamepads; 31 | static const unsigned int xbuttons[MAX_XINPUT_BUTTONS] = { 32 | XINPUT_GAMEPAD_START, 33 | XINPUT_GAMEPAD_BACK, 34 | XINPUT_GAMEPAD_LEFT_THUMB, 35 | XINPUT_GAMEPAD_RIGHT_THUMB, 36 | XINPUT_GAMEPAD_LEFT_SHOULDER, 37 | XINPUT_GAMEPAD_RIGHT_SHOULDER, 38 | XINPUT_GAMEPAD_A, 39 | XINPUT_GAMEPAD_B, 40 | XINPUT_GAMEPAD_X, 41 | XINPUT_GAMEPAD_Y, 42 | XINPUT_GAMEPAD_DPAD_DOWN, 43 | XINPUT_GAMEPAD_DPAD_LEFT, 44 | XINPUT_GAMEPAD_DPAD_RIGHT, 45 | XINPUT_GAMEPAD_DPAD_UP}; 46 | static const SDL_GameControllerButton sdlbuttons[MAX_XINPUT_BUTTONS] = { 47 | SDL_CONTROLLER_BUTTON_START, 48 | SDL_CONTROLLER_BUTTON_BACK, 49 | SDL_CONTROLLER_BUTTON_LEFTSTICK, 50 | SDL_CONTROLLER_BUTTON_RIGHTSTICK, 51 | SDL_CONTROLLER_BUTTON_LEFTSHOULDER, 52 | SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, 53 | SDL_CONTROLLER_BUTTON_A, 54 | SDL_CONTROLLER_BUTTON_B, 55 | SDL_CONTROLLER_BUTTON_X, 56 | SDL_CONTROLLER_BUTTON_Y, 57 | SDL_CONTROLLER_BUTTON_DPAD_DOWN, 58 | SDL_CONTROLLER_BUTTON_DPAD_LEFT, 59 | SDL_CONTROLLER_BUTTON_DPAD_RIGHT, 60 | SDL_CONTROLLER_BUTTON_DPAD_UP}; 61 | 62 | static bool enabled = true; 63 | 64 | DWORD WINAPI XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration); 65 | void GamepadInitSDL() { 66 | static bool inited = false; 67 | if (inited) 68 | return; 69 | debug(""); 70 | 71 | inited = true; 72 | 73 | SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER); 74 | SDL_JoystickEventState(SDL_IGNORE); 75 | SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt"); 76 | 77 | for (int i = 0; i < SDL_NumJoysticks(); ++i) { 78 | SDL_Joystick *joy = SDL_JoystickOpen(i); 79 | if (joy) { 80 | SDLGamepad gamepad; 81 | SDL_GameController *controller = SDL_GameControllerOpen(i); 82 | 83 | if (controller) { 84 | gamepad.joystick = joy; 85 | gamepad.controller = controller; 86 | gamepad.haptic = 0; 87 | 88 | gamepad.haptic = SDL_HapticOpenFromJoystick(gamepad.joystick); 89 | if (gamepad.haptic != 0) { 90 | SDL_HapticEffect effect[2]; 91 | memset(&effect, 0, sizeof(SDL_HapticEffect) * 2); 92 | 93 | effect[0].type = SDL_HAPTIC_SINE; 94 | effect[0].periodic.direction.type = SDL_HAPTIC_CARTESIAN; 95 | effect[0].periodic.direction.dir[0] = 1; 96 | effect[0].periodic.period = 0; 97 | effect[0].periodic.magnitude = 0; 98 | effect[0].periodic.length = SDL_HAPTIC_INFINITY; 99 | 100 | effect[1] = effect[0]; 101 | effect[1].periodic.direction.dir[0] = -1; 102 | effect[1].periodic.magnitude = 0; 103 | 104 | gamepad.haptic_effects[0] = 105 | SDL_HapticNewEffect(gamepad.haptic, &(effect[0])); 106 | gamepad.haptic_effects[1] = 107 | SDL_HapticNewEffect(gamepad.haptic, &(effect[1])); 108 | 109 | SDL_HapticRunEffect(gamepad.haptic, gamepad.haptic_effects[0], 1); 110 | SDL_HapticRunEffect(gamepad.haptic, gamepad.haptic_effects[1], 1); 111 | } 112 | 113 | gamepads.push_back(gamepad); 114 | } 115 | } 116 | } 117 | 118 | XINPUT_VIBRATION welcome_vibration; 119 | welcome_vibration.wLeftMotorSpeed = 65535; 120 | welcome_vibration.wRightMotorSpeed = 0; 121 | koku::XInputSetState(0, &welcome_vibration); 122 | SDL_Delay(250); 123 | welcome_vibration.wLeftMotorSpeed = 0; 124 | welcome_vibration.wRightMotorSpeed = 0; 125 | koku::XInputSetState(0, &welcome_vibration); 126 | SDL_Delay(250); 127 | welcome_vibration.wLeftMotorSpeed = 0; 128 | welcome_vibration.wRightMotorSpeed = 65535; 129 | koku::XInputSetState(0, &welcome_vibration); 130 | SDL_Delay(250); 131 | welcome_vibration.wLeftMotorSpeed = 0; 132 | welcome_vibration.wRightMotorSpeed = 0; 133 | koku::XInputSetState(0, &welcome_vibration); 134 | } 135 | 136 | koku::jumper XInputEnableJumper; 137 | void WINAPI XInputEnable(BOOL enable) { 138 | debug(""); 139 | if (!enable) { 140 | XINPUT_VIBRATION vibration; 141 | vibration.wLeftMotorSpeed = 0; 142 | vibration.wRightMotorSpeed = 0; 143 | for (int i = 0; i < 4; ++i) 144 | koku::XInputSetState(i, &vibration); 145 | } 146 | 147 | enabled = enable; 148 | } 149 | 150 | koku::jumper XInputSetStateJumper; 151 | DWORD WINAPI XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration) { 152 | GamepadInitSDL(); 153 | debug(""); 154 | if (dwUserIndex >= gamepads.size()) 155 | return ERROR_DEVICE_NOT_CONNECTED; 156 | 157 | if (enabled && pVibration) { 158 | if (gamepads[dwUserIndex].haptic != 0) { 159 | SDL_HapticEffect effect[2]; 160 | memset(&effect, 0, sizeof(SDL_HapticEffect) * 2); 161 | 162 | effect[0].type = SDL_HAPTIC_SINE; 163 | effect[0].periodic.direction.type = SDL_HAPTIC_CARTESIAN; 164 | effect[0].periodic.direction.dir[0] = 1; 165 | effect[0].periodic.period = 0; 166 | effect[0].periodic.magnitude = pVibration->wLeftMotorSpeed >> 1; 167 | effect[0].periodic.length = SDL_HAPTIC_INFINITY; 168 | 169 | effect[1] = effect[0]; 170 | effect[1].periodic.direction.dir[0] = -1; 171 | effect[1].periodic.magnitude = pVibration->wRightMotorSpeed >> 1; 172 | 173 | SDL_HapticUpdateEffect(gamepads[dwUserIndex].haptic, 174 | gamepads[dwUserIndex].haptic_effects[0], 175 | &(effect[0])); 176 | SDL_HapticUpdateEffect(gamepads[dwUserIndex].haptic, 177 | gamepads[dwUserIndex].haptic_effects[1], 178 | &(effect[1])); 179 | } 180 | } 181 | 182 | return ERROR_SUCCESS; 183 | } 184 | 185 | koku::jumper XInputGetStateJumper; 186 | DWORD WINAPI XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState) { 187 | GamepadInitSDL(); 188 | debug(""); 189 | if (dwUserIndex >= gamepads.size()) 190 | return ERROR_DEVICE_NOT_CONNECTED; 191 | 192 | SDL_JoystickUpdate(); 193 | SDL_GameControllerUpdate(); 194 | 195 | if (pState) { 196 | SDL_GameController *controller = gamepads[dwUserIndex].controller; 197 | 198 | for (int j = 0; j < MAX_XINPUT_BUTTONS; j++) { 199 | Uint8 result = SDL_GameControllerGetButton(controller, sdlbuttons[j]); 200 | if (result) { 201 | pState->Gamepad.wButtons |= xbuttons[j]; 202 | } else { 203 | pState->Gamepad.wButtons &= ~(xbuttons[j]); 204 | } 205 | } 206 | 207 | short ly = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); 208 | short ry = 209 | SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); 210 | 211 | pState->Gamepad.sThumbLX = 212 | SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); 213 | pState->Gamepad.sThumbLY = (ly == 0 ? 0 : -ly - 1); 214 | pState->Gamepad.sThumbRX = 215 | SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); 216 | pState->Gamepad.sThumbRY = ry == 0 ? 0 : -ry - 1; 217 | pState->Gamepad.bLeftTrigger = 218 | SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT); 219 | pState->Gamepad.bRightTrigger = 220 | SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); 221 | 222 | static int dwPacketNumber = 0; 223 | pState->dwPacketNumber = ++dwPacketNumber; 224 | } 225 | 226 | return ERROR_SUCCESS; 227 | } 228 | 229 | koku::jumper XInputGetKeystrokeJumper; 230 | DWORD WINAPI XInputGetKeystroke(DWORD dwUserIndex, DWORD dwReserved, 231 | PXINPUT_KEYSTROKE pKeystroke) { 232 | GamepadInitSDL(); 233 | debug(""); 234 | if (dwUserIndex >= gamepads.size()) 235 | return ERROR_DEVICE_NOT_CONNECTED; 236 | 237 | return ERROR_EMPTY; 238 | } 239 | 240 | koku::jumper XInputGetCapabilitiesJumper; 241 | DWORD WINAPI XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, 242 | XINPUT_CAPABILITIES *pCapabilities) { 243 | GamepadInitSDL(); 244 | debug(""); 245 | if (dwUserIndex >= gamepads.size()) 246 | return ERROR_DEVICE_NOT_CONNECTED; 247 | 248 | if (pCapabilities) { 249 | pCapabilities->Type = XINPUT_DEVTYPE_GAMEPAD; 250 | pCapabilities->SubType = XINPUT_DEVSUBTYPE_GAMEPAD; 251 | pCapabilities->Flags = (gamepads[dwUserIndex].haptic != 0) 252 | ? /* XINPUT_CAPS_FFB_SUPPORTED */ 0x0001 253 | : 0; 254 | pCapabilities->Gamepad.wButtons = 0xFFFF; 255 | pCapabilities->Gamepad.bLeftTrigger = 255; 256 | pCapabilities->Gamepad.bRightTrigger = 255; 257 | pCapabilities->Gamepad.sThumbLX = 32767; 258 | pCapabilities->Gamepad.sThumbLY = 32767; 259 | pCapabilities->Gamepad.sThumbRX = 32767; 260 | pCapabilities->Gamepad.sThumbRY = 32767; 261 | if (gamepads[dwUserIndex].haptic != 0) { 262 | pCapabilities->Vibration.wLeftMotorSpeed = 65535; 263 | pCapabilities->Vibration.wRightMotorSpeed = 65535; 264 | } else { 265 | pCapabilities->Vibration.wLeftMotorSpeed = 0; 266 | pCapabilities->Vibration.wRightMotorSpeed = 0; 267 | } 268 | } 269 | return ERROR_SUCCESS; 270 | } 271 | 272 | koku::jumper 273 | XInputGetDSoundAudioDeviceGuidsJumper; 274 | DWORD WINAPI XInputGetDSoundAudioDeviceGuids(DWORD dwUserIndex, 275 | GUID *pDSoundRenderGuid, 276 | GUID *pDSoundCaptureGuid) { 277 | GamepadInitSDL(); 278 | debug(""); 279 | if (dwUserIndex >= gamepads.size()) 280 | return ERROR_DEVICE_NOT_CONNECTED; 281 | 282 | /* 283 | If there is no headset connected to the controller, 284 | the function also retrieves ERROR_SUCCESS with GUID_NULL as the values 285 | for pDSoundRenderGuid and pDSoundCaptureGuid. 286 | */ 287 | if (pDSoundRenderGuid) 288 | std::memset(pDSoundRenderGuid, 0, sizeof(GUID)); 289 | 290 | if (pDSoundCaptureGuid) 291 | std::memset(pDSoundCaptureGuid, 0, sizeof(GUID)); 292 | 293 | return ERROR_SUCCESS; 294 | } 295 | 296 | koku::jumper 297 | XInputGetBatteryInformationJumper; 298 | DWORD WINAPI 299 | XInputGetBatteryInformation(DWORD dwUserIndex, BYTE devType, 300 | XINPUT_BATTERY_INFORMATION *pBatteryInformation) { 301 | GamepadInitSDL(); 302 | debug(""); 303 | if (dwUserIndex >= gamepads.size()) 304 | return ERROR_DEVICE_NOT_CONNECTED; 305 | 306 | if (pBatteryInformation) { 307 | if (devType == BATTERY_DEVTYPE_GAMEPAD) { 308 | pBatteryInformation->BatteryType = BATTERY_TYPE_WIRED; 309 | pBatteryInformation->BatteryLevel = BATTERY_LEVEL_FULL; 310 | } else { 311 | pBatteryInformation->BatteryType = BATTERY_TYPE_DISCONNECTED; 312 | pBatteryInformation->BatteryLevel = BATTERY_LEVEL_EMPTY; 313 | } 314 | } 315 | 316 | return ERROR_SUCCESS; 317 | } 318 | 319 | koku::jumper XInputGetStateExJumper; 320 | DWORD WINAPI XInputGetStateEx(DWORD dwUserIndex, XINPUT_STATE *pState) { 321 | return koku::XInputGetState(dwUserIndex, pState); 322 | } 323 | 324 | void XInputInit(void *handle) { 325 | if (auto address = (decltype(&XInputEnable))dlsym(handle, "XInputEnable")) { 326 | debug("found XInputEnable at %p, redirecting it to %p", address, 327 | &koku::XInputEnable); 328 | XInputEnableJumper = koku::make_jumper(address, &koku::XInputEnable); 329 | } 330 | 331 | if (auto address = 332 | (decltype(&XInputSetState))dlsym(handle, "XInputSetState")) { 333 | debug("found XInputSetState at %p, redirecting it to %p", address, 334 | &koku::XInputSetState); 335 | XInputSetStateJumper = koku::make_jumper(address, koku::XInputSetState); 336 | } 337 | 338 | if (auto address = 339 | (decltype(&XInputGetState))dlsym(handle, "XInputGetState")) { 340 | debug("found XInputGetState at %p, redirecting it to %p", address, 341 | &koku::XInputGetState); 342 | XInputGetStateJumper = koku::make_jumper(address, koku::XInputGetState); 343 | } 344 | 345 | if (auto address = 346 | (decltype(&XInputGetKeystroke))dlsym(handle, "XInputGetKeystroke")) { 347 | debug("found XInputGetKeystroke at %p, redirecting it to %p", address, 348 | &koku::XInputGetKeystroke); 349 | XInputGetKeystrokeJumper = 350 | koku::make_jumper(address, koku::XInputGetKeystroke); 351 | } 352 | 353 | if (auto address = (decltype(&XInputGetCapabilities))dlsym( 354 | handle, "XInputGetCapabilities")) { 355 | debug("found XInputGetCapabilities at %p, redirecting it to %p", address, 356 | &koku::XInputGetCapabilities); 357 | XInputGetCapabilitiesJumper = 358 | koku::make_jumper(address, koku::XInputGetCapabilities); 359 | } 360 | 361 | if (auto address = (decltype(&XInputGetDSoundAudioDeviceGuids))dlsym( 362 | handle, "XInputGetDSoundAudioDeviceGuids")) { 363 | debug("found XInputGetDSoundAudioDeviceGuids at %p, redirecting it to %p", 364 | address, &koku::XInputGetDSoundAudioDeviceGuids); 365 | XInputGetDSoundAudioDeviceGuidsJumper = 366 | koku::make_jumper(address, koku::XInputGetDSoundAudioDeviceGuids); 367 | } 368 | 369 | if (auto address = (decltype(&XInputGetBatteryInformation))dlsym( 370 | handle, "XInputGetBatteryInformation")) { 371 | debug("found XInputGetBatteryInformation %p, redirecting it to %p", address, 372 | &koku::XInputGetBatteryInformation); 373 | XInputGetBatteryInformationJumper = 374 | koku::make_jumper(address, koku::XInputGetBatteryInformation); 375 | } 376 | 377 | if (auto address = 378 | (decltype(&XInputGetStateEx))dlsym(handle, "XInputGetStateEx")) { 379 | debug("found XInputGetStateEx at %p, redirecting it to %p", address, 380 | &koku::XInputGetStateEx); 381 | XInputGetStateExJumper = 382 | koku::make_jumper(address, &koku::XInputGetStateEx); 383 | } 384 | } 385 | } // namespace koku 386 | --------------------------------------------------------------------------------