├── .gitignore ├── .gitmodules ├── .editorconfig ├── ProconXInputTE ├── CMakeLists.txt ├── ProconX360Bridge.h ├── ProconX360Bridge.cpp └── main.cpp ├── ViGEmClient ├── CMakeLists.txt ├── README.md ├── LICENSE ├── ViGEmClientCpp.h └── ViGEmClientCpp.cpp ├── LICENSE ├── CMakeSettings.json ├── CMakeLists.txt ├── README.ja.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.vs 2 | /out 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ThirdParty/ViGEmClient"] 2 | path = ThirdParty/ViGEmClient 3 | url = https://github.com/ViGEm/ViGEmClient.git 4 | [submodule "ProControllerHid"] 5 | path = ProControllerHid 6 | url = ../ProControllerHid.git 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | 11 | [CMakeSettings.json] 12 | charset = utf-8 13 | end_of_line = crlf 14 | insert_final_newline = false 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /ProconXInputTE/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) 2 | 3 | add_executable( 4 | ${PROJECT_NAME} 5 | main.cpp 6 | ProconX360Bridge.cpp 7 | ) 8 | 9 | target_include_directories( 10 | ${PROJECT_NAME} PRIVATE 11 | ../ 12 | ) 13 | 14 | target_link_libraries( 15 | ${PROJECT_NAME} 16 | ProControllerHid 17 | ViGEmClient 18 | ) 19 | -------------------------------------------------------------------------------- /ViGEmClient/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) 2 | 3 | add_library( 4 | ${PROJECT_NAME} STATIC 5 | ../ThirdParty/ViGEmClient/include/ViGEm/Client.h 6 | ../ThirdParty/ViGEmClient/include/ViGEm/Common.h 7 | ../ThirdParty/ViGEmClient/include/ViGEm/Util.h 8 | ../ThirdParty/ViGEmClient/include/ViGEm/km/BusShared.h 9 | ../ThirdParty/ViGEmClient/src/Internal.h 10 | ../ThirdParty/ViGEmClient/src/ViGEmClient.cpp 11 | ViGEmClientCpp.h 12 | ViGEmClientCpp.cpp 13 | ) 14 | 15 | target_include_directories( 16 | ${PROJECT_NAME} PRIVATE 17 | ../ThirdParty/ViGemClient/include 18 | ) 19 | -------------------------------------------------------------------------------- /ViGEmClient/README.md: -------------------------------------------------------------------------------- 1 | # ViGEmClient - A cpp(RAII) wrapper of ViGEm Client 2 | 3 | ## Source files 4 | - [ViGEmClientCpp.h](ViGEmClientCpp.h) wrapped public header. 5 | - [ViGEmClientCpp.cpp](ViGEmClientCpp.cpp) implemantation. 6 | - [../ThirdParty/ViGEmClient](../ThirdParty/ViGEmClient) ViGEm client library: from [https://github.com/ViGEm/ViGEmClient](https://github.com/ViGEm/ViGEmClient) 7 | 8 | --- 9 | [MIT License](LICESNSE) 10 | Copyright (c) 2019 ttsuki 11 | 12 | The files in the directory `../ThirdParty/ViGEmClient` are separated LICENSEed by [../ThirdParty/ViGEmClient/LICENSE](../ThirdParty/ViGEmClient/LICENSE) . 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ttsuki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ViGEmClient/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ttsuki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "buildRoot": "${projectDir}\\out\\build\\${name}", 8 | "installRoot": "${projectDir}\\out\\install\\${name}", 9 | "inheritEnvironments": [ "msvc_x64_x64" ] 10 | }, 11 | { 12 | "name": "x64-Release", 13 | "generator": "Ninja", 14 | "configurationType": "RelWithDebInfo", 15 | "buildRoot": "${projectDir}\\out\\build\\${name}", 16 | "installRoot": "${projectDir}\\out\\install\\${name}", 17 | "inheritEnvironments": [ "msvc_x64_x64" ] 18 | }, 19 | { 20 | "name": "x86-Debug", 21 | "generator": "Ninja", 22 | "configurationType": "Debug", 23 | "buildRoot": "${projectDir}\\out\\build\\${name}", 24 | "installRoot": "${projectDir}\\out\\install\\${name}", 25 | "inheritEnvironments": [ "msvc_x86" ] 26 | }, 27 | { 28 | "name": "x86-Release", 29 | "generator": "Ninja", 30 | "configurationType": "RelWithDebInfo", 31 | "buildRoot": "${projectDir}\\out\\build\\${name}", 32 | "installRoot": "${projectDir}\\out\\install\\${name}", 33 | "inheritEnvironments": [ "msvc_x86" ] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.12) 2 | project(ProconXInputTE LANGUAGES CXX) 3 | cmake_policy(SET CMP0091 NEW) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_CXX_EXTENSIONS OFF) 8 | 9 | if ( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC" 10 | OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel" 11 | OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "IntelLLVM") 12 | add_compile_options(/W4) 13 | string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Warning compile option 14 | string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG} ) # Use static CRT 15 | string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_MINSIZEREL ${CMAKE_CXX_FLAGS_MINSIZEREL} ) # Use static CRT 16 | string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} ) # Use static CRT 17 | string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELWITHDEBINFO ${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ) # Use static CRT 18 | else() 19 | add_compile_options(-Wall -Wextra -Wpedantic) # Warning compile option 20 | endif() 21 | 22 | add_subdirectory(ProControllerHid) 23 | add_subdirectory(ViGemClient) 24 | add_subdirectory(ProconXInputTE) 25 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ProconXInputTE) 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.ja.md: -------------------------------------------------------------------------------- 1 | # ProconXInputTE 2 | 3 | [TETRIS EFFECT](http://www.tetriseffect.game/)を**振動ありで**SwitchのProCon🎮で遊びたかったので作りました! 4 | 5 | --- 6 | 7 | ## About - なんこれ? 8 | 9 | ViGEm busを経由して、USB接続のNintendo Switch Pro ControllerをXBox controllerの入力に変換するドライバーアプリです。 10 | 11 | なんと、**振動に対応しています!** 12 | 13 | 14 | ## Features - 主な機能 15 | 16 | - スティック・ボタン入力 17 | - 振動出力 18 | - A/B, X/Y ボタン入れ替え: `--use-x360-layout`オプションをつけて起動 19 | 20 | ## Download - ダウンロード 21 | 22 | [Releases](https://github.com/ttsuki/ProconXInputTE/releases/latest)ページから最新版をダウンロードできます. 23 | 24 | ## How to use - つかいかた 25 | 26 | 0. [ViGEm Bus Driver](https://github.com/ViGEm/ViGEmBus/releases)が必要なので、インストールします。 27 | 1. 必要に応じて [HidHide](https://github.com/ViGEm/HidHide/releases) をインストールして設定します。(特定アプリ以外から、ProConを見えなくしてくれます) 28 | 2. プロコンをUSBケーブルでPCにつなぎます。 29 | 3. [ProconXInputTE_x86.exe](https://github.com/ttsuki/ProconXInputTE/releases/latest)を起動します。 30 | 4. お気に入りのゲームを遊びましょう😊 31 | 32 | --- 33 | 34 | 35 | ## For developpers 36 | 37 | ### Build Environment 38 | 39 | - Visual Studio 2019/2022 with CMake 40 | 41 | 42 | ### Requirements 43 | 44 | - ViGEm Bus Driver: https://github.com/ViGEm/ViGEmBus 45 | 46 | 47 | ### Third-party library (submodules) 48 | 49 | - ViGEm Client: https://github.com/ViGEm/ViGEmClient 50 | 51 | 52 | ### Sub-projects - 副産物 53 | 54 | - ViGEm C++ Client (RAII wrapper) 55 | 56 | [`ViGEmClient/`](ViGEmClient/) ディレクトリにあります。 57 | 58 | - Pro Controller user-mode driver 59 | 60 | [`ProControllerHid/`](https://github.com/ttsuki/ProControllerHid) ディレクトリにあります。 61 | 62 | - スティック・ボタン入力の他、6軸IMU(加速度・ジャイロ)センサ入力、振動出力をサポートしています。 63 | 64 | - このプロジェクト単体で利用する場合は、ViGEm Clientは不要です。 65 | 66 | 67 | ### Thanks to 68 | 69 | - Reverse Engineering Note: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering 70 | 71 | 72 | --- 73 | 74 | ## License 75 | 76 | MIT (C) 2019 ttsuki 77 | https://github.com/ttsuki/ProconXInputTE/ 78 | 79 | The files in the directory `ThirdParty/ViGEmClient` are separated LICENSEed by [ThirdParty/ViGEmClient/LICENSE](ThirdParty/ViGEmClient/LICENSE). -------------------------------------------------------------------------------- /ViGEmClient/ViGEmClientCpp.h: -------------------------------------------------------------------------------- 1 | // ViGEm Client RAII wrapper 2 | 3 | #pragma once 4 | #include 5 | #include 6 | 7 | namespace ViGEm 8 | { 9 | struct X360InputStatus 10 | { 11 | struct ButtonStatus 12 | { 13 | unsigned Up : 1; 14 | unsigned Down : 1; 15 | unsigned Left : 1; 16 | unsigned Right : 1; 17 | unsigned Start : 1; 18 | unsigned Back : 1; 19 | unsigned LStick : 1; 20 | unsigned RStick : 1; 21 | 22 | unsigned LButton : 1; 23 | unsigned RButton : 1; 24 | unsigned GuideButton : 1; 25 | unsigned _reserved : 1; 26 | unsigned AButton : 1; 27 | unsigned BButton : 1; 28 | unsigned XButton : 1; 29 | unsigned YButton : 1; 30 | 31 | unsigned LTrigger : 8; 32 | unsigned RTrigger : 8; 33 | } Buttons; 34 | 35 | int16_t LeftStickAxisX; 36 | int16_t LeftStickAxisY; 37 | int16_t RightStickAxisX; 38 | int16_t RightStickAxisY; 39 | }; 40 | 41 | struct X360OutputStatus 42 | { 43 | uint8_t LargeRumble; 44 | uint8_t SmallRumble; 45 | uint8_t LedNumber; 46 | }; 47 | 48 | using X360OutputCallback = std::function; 49 | 50 | class X360Controller 51 | { 52 | public: 53 | X360Controller() = default; 54 | X360Controller(const X360Controller& other) = delete; 55 | X360Controller(X360Controller&& other) noexcept = delete; 56 | X360Controller& operator=(const X360Controller& other) = delete; 57 | X360Controller& operator=(X360Controller&& other) noexcept = delete; 58 | virtual ~X360Controller() = default; 59 | 60 | virtual unsigned long GetDeviceIndex() const = 0; 61 | 62 | virtual void SendReport(X360InputStatus inputStatus) = 0; 63 | virtual void ReceiveReport(X360OutputStatus* outputStatus) = 0; 64 | 65 | virtual void StartNotification(X360OutputCallback callback) = 0; 66 | virtual void StopNotification() = 0; 67 | }; 68 | 69 | class ViGEmClient 70 | { 71 | public: 72 | ViGEmClient() = default; 73 | ViGEmClient(const ViGEmClient& other) = delete; 74 | ViGEmClient(ViGEmClient&& other) noexcept = delete; 75 | ViGEmClient& operator=(const ViGEmClient& other) = delete; 76 | ViGEmClient& operator=(ViGEmClient&& other) noexcept = delete; 77 | virtual ~ViGEmClient() = default; 78 | 79 | virtual std::unique_ptr AddX360Controller() = 0; 80 | }; 81 | 82 | std::unique_ptr ConnectToViGEm(); 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [日本語READMEはこちら](README.ja.md) 2 | 3 | # ProconXInputTE 4 | 5 | I wanted to play [TETRIS EFFECT](http://www.tetriseffect.game/) with Nintendo Switch Pro Controller 🎮 with vibration!!! 6 | 7 | 8 | --- 9 | 10 | ## About 11 | 12 | An XInput user-mode driver for Nintendo Switch Pro Controller (USB-wired mode) using ViGEm bus. 13 | 14 | This driver application supports VIBRATION! 15 | 16 | 17 | ## Features 18 | 19 | - Input sticks and buttons 20 | - Output Vibration 21 | - Swap buttons (A/B, X/Y) as x360 layout: start app with `--use-x360-layout` option. 22 | 23 | 24 | ## Download 25 | 26 | See [Releases](https://github.com/ttsuki/ProconXInputTE/releases/latest) page. 27 | 28 | 29 | ## How to use 30 | 31 | 0. Install [ViGEm Bus Driver](https://github.com/ViGEm/ViGEmBus/releases). 32 | 1. (optional) Install [HidHide](https://github.com/ViGEm/HidHide/releases) and configure for hiding controllers from other apps. 33 | 2. Connect your Nintendo Switch Pro Controller with USB cable to PC. 34 | 3. Start the driver application [ProconXInputTE_x86.exe](https://github.com/ttsuki/ProconXInputTE/releases/latest). 35 | 4. Play your favorite game 😊 36 | 37 | --- 38 | 39 | ## For developpers 40 | 41 | ### Build Environment 42 | 43 | - Visual Studio 2019/2022 with CMake 44 | 45 | 46 | ### Requirements 47 | 48 | - ViGEm Bus Driver: https://github.com/ViGEm/ViGEmBus 49 | 50 | 51 | ### Third-party library (submodules) 52 | 53 | - ViGEm Client: https://github.com/ViGEm/ViGEmClient 54 | 55 | 56 | ### Sub-projects 57 | 58 | - ViGEm C++ Client (RAII wrapper) 59 | 60 | is in [`ViGEmClient/`](ViGEmClient/) directory. 61 | 62 | - Pro Controller user-mode driver 63 | 64 | is in [`ProControllerHid/`](https://github.com/ttsuki/ProControllerHid) submodule. 65 | 66 | - It supports sticks/buttons input, 6-axis IMU sensor input, and basic rumbling output. 67 | 68 | - If you use it directly in your project, you do not need ViGEm Client. 69 | 70 | 71 | ### Thanks to 72 | 73 | - Reverse Engineering Note: https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering 74 | 75 | --- 76 | 77 | ## License 78 | [MIT (C) 2019 ttsuki](LICENSE) 79 | https://github.com/ttsuki/ProconXInputTE/ 80 | 81 | The files in the directory `ThirdParty/ViGEmClient` are separated LICENSEed by [ThirdParty/ViGEmClient/LICENSE](ThirdParty/ViGEmClient/LICENSE) . 82 | -------------------------------------------------------------------------------- /ProconXInputTE/ProconX360Bridge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ProControllerHid/ProController.h" 3 | #include "ViGEmClient/ViGEmClientCpp.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace ProconXInputTE 10 | { 11 | class ProconX360Bridge final 12 | { 13 | public: 14 | using Clock = ProControllerHid::Clock; 15 | 16 | using Timestamp = ProControllerHid::Timestamp; 17 | 18 | struct Options 19 | { 20 | bool ReplaceButtonsAsX360Layout{}; 21 | }; 22 | 23 | template 24 | struct StatusWithTimeStamp 25 | { 26 | Timestamp Timestamp{}; 27 | T Status{}; 28 | }; 29 | 30 | struct RumbleParameters 31 | { 32 | uint8_t Frequency; 33 | uint8_t DecaySpeed; 34 | uint8_t MaxAmplitude; 35 | }; 36 | 37 | struct RumbleParams 38 | { 39 | struct 40 | { 41 | uint8_t Frequency; 42 | uint8_t DecaySpeed; 43 | uint8_t MaxAmplitude; 44 | } Left, Right; 45 | }; 46 | 47 | struct RumbleStatus 48 | { 49 | Timestamp Timestamp; 50 | 51 | struct 52 | { 53 | int Left, Right; 54 | } Large, Small; 55 | 56 | ProControllerHid::ProController::BasicRumble Output; 57 | }; 58 | 59 | private: 60 | mutable std::mutex mutex_{}; 61 | 62 | std::unique_ptr controller_{}; 63 | std::unique_ptr x360_{}; 64 | 65 | std::atomic last_input_{}; 66 | std::atomic> last_input_sent_{}; 67 | std::atomic> last_output_{}; 68 | std::atomic last_output_sent_{}; 69 | 70 | std::thread rumble_thread_{}; 71 | std::atomic_flag rumble_thread_running_{}; 72 | RumbleParams large_rumble_parameter_ = {{130, 20, 216}, {142, 20, 216}}; 73 | RumbleParams small_rumble_parameter_ = {{72, 30, 176}, {100, 30, 176}}; 74 | 75 | public: 76 | ProconX360Bridge( 77 | const char* procon_device_path, 78 | ViGEm::ViGEmClient* client, 79 | Options options = Options{}, 80 | std::function log = nullptr); 81 | 82 | ~ProconX360Bridge(); 83 | 84 | [[nodiscard]] int GetIndex() const; 85 | 86 | void SetRumbleParameter(RumbleParams large_to_low, RumbleParams small_to_high); 87 | 88 | [[nodiscard]] ProControllerHid::InputStatus GetLastInput() const { return last_input_.load(std::memory_order_acquire); } 89 | [[nodiscard]] StatusWithTimeStamp GetLastInputSent() const { return last_input_sent_.load(std::memory_order_acquire); } 90 | [[nodiscard]] StatusWithTimeStamp GetLastOutputIn() const { return last_output_.load(std::memory_order_acquire); } 91 | [[nodiscard]] RumbleStatus GetLastOutputOut() const { return last_output_sent_.load(std::memory_order_acquire); } 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /ProconXInputTE/ProconX360Bridge.cpp: -------------------------------------------------------------------------------- 1 | #include "ProconX360Bridge.h" 2 | #include 3 | #include 4 | 5 | namespace ProconXInputTE 6 | { 7 | using ProControllerHid::ProController; 8 | using ProControllerHid::InputStatus; 9 | using ViGEm::X360InputStatus; 10 | using ViGEm::X360OutputStatus; 11 | 12 | ProconX360Bridge::ProconX360Bridge( 13 | const char* procon_device_path, 14 | ViGEm::ViGEmClient* client, 15 | Options options, 16 | std::function log) 17 | { 18 | x360_ = client->AddX360Controller(); 19 | 20 | controller_ = ProController::Connect( 21 | procon_device_path, 22 | false, 23 | std::move(log) 24 | ); 25 | 26 | if (!controller_) 27 | throw std::runtime_error("Failed to open ProController."); 28 | 29 | controller_->SetPlayerLed(static_cast(1u << static_cast(x360_->GetDeviceIndex()))); 30 | 31 | x360_->StartNotification([this](const X360OutputStatus& x360Output) 32 | { 33 | last_output_.store({Clock::now(), x360Output}, std::memory_order_release); 34 | }); 35 | 36 | controller_->SetInputStatusCallback([this, use_x360_layout = options.ReplaceButtonsAsX360Layout](const InputStatus& in) 37 | { 38 | auto timestamp = Clock::now(); 39 | InputStatus input = in; 40 | 41 | if (use_x360_layout) 42 | { 43 | auto a = input.Buttons.AButton; 44 | input.Buttons.AButton = input.Buttons.BButton; 45 | input.Buttons.BButton = a; 46 | 47 | auto x = input.Buttons.XButton; 48 | input.Buttons.XButton = input.Buttons.YButton; 49 | input.Buttons.YButton = x; 50 | } 51 | 52 | X360InputStatus status = 53 | { 54 | { 55 | input.Buttons.UpButton, 56 | input.Buttons.DownButton, 57 | input.Buttons.LeftButton, 58 | input.Buttons.RightButton, 59 | input.Buttons.PlusButton, 60 | input.Buttons.MinusButton, 61 | input.Buttons.LStick, 62 | input.Buttons.RStick, 63 | input.Buttons.LButton, 64 | input.Buttons.RButton, 65 | input.Buttons.HomeButton, 66 | false, 67 | input.Buttons.AButton, 68 | input.Buttons.BButton, 69 | input.Buttons.XButton, 70 | input.Buttons.YButton, 71 | 72 | static_cast(input.Buttons.LZButton ? 255 : 0), 73 | static_cast(input.Buttons.RZButton ? 255 : 0), 74 | }, 75 | static_cast(input.LeftStick.X * 32767), 76 | static_cast(input.LeftStick.Y * 32767), 77 | static_cast(input.RightStick.X * 32767), 78 | static_cast(input.RightStick.Y * 32767), 79 | }; 80 | 81 | x360_->SendReport(status); 82 | 83 | last_input_.store(input, std::memory_order_release); 84 | last_input_sent_.store({timestamp, status}, std::memory_order_release); 85 | }); 86 | 87 | rumble_thread_ = std::thread([this] 88 | { 89 | auto clock = std::chrono::steady_clock::now(); 90 | decltype(RumbleStatus::Large) large{}; 91 | decltype(RumbleStatus::Small) small{}; 92 | rumble_thread_running_.test_and_set(); 93 | 94 | while (rumble_thread_running_.test_and_set()) 95 | { 96 | const auto latest = last_output_.load(std::memory_order_acquire).Status; 97 | 98 | large = { 99 | std::max(large.Left - large_rumble_parameter_.Left.DecaySpeed, latest.LargeRumble), 100 | std::max(large.Right - large_rumble_parameter_.Right.DecaySpeed, latest.LargeRumble) 101 | }; 102 | 103 | small = { 104 | std::max(small.Left - small_rumble_parameter_.Left.DecaySpeed, latest.SmallRumble), 105 | std::max(small.Right - small_rumble_parameter_.Right.DecaySpeed, latest.SmallRumble) 106 | }; 107 | 108 | const ProController::BasicRumble output = { 109 | { 110 | {small_rumble_parameter_.Left.Frequency, static_cast(small.Left * small_rumble_parameter_.Left.MaxAmplitude / 255)}, 111 | {large_rumble_parameter_.Left.Frequency, static_cast(large.Left * large_rumble_parameter_.Left.MaxAmplitude / 255)} 112 | }, 113 | { 114 | {small_rumble_parameter_.Right.Frequency, static_cast(small.Right * small_rumble_parameter_.Right.MaxAmplitude / 255)}, 115 | {large_rumble_parameter_.Right.Frequency, static_cast(large.Right * large_rumble_parameter_.Right.MaxAmplitude / 255)} 116 | } 117 | }; 118 | 119 | controller_->SetRumble(output); 120 | last_output_sent_.store({Clock::now(), large, small, output}, std::memory_order_release); 121 | 122 | std::this_thread::sleep_until(clock += std::chrono::milliseconds(16)); 123 | } 124 | 125 | controller_->SetRumble(ProController::BasicRumble{}); 126 | }); 127 | } 128 | 129 | ProconX360Bridge::~ProconX360Bridge() 130 | { 131 | controller_->SetInputStatusCallback(nullptr); 132 | x360_->StopNotification(); 133 | rumble_thread_running_.clear(); 134 | rumble_thread_.join(); 135 | controller_.reset(); 136 | x360_.reset(); 137 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); // waits for exit of x360 thread 138 | } 139 | 140 | int ProconX360Bridge::GetIndex() const 141 | { 142 | return static_cast(x360_->GetDeviceIndex()); 143 | } 144 | 145 | void ProconX360Bridge::SetRumbleParameter(RumbleParams large_to_low, RumbleParams small_to_high) 146 | { 147 | large_rumble_parameter_ = large_to_low; 148 | small_rumble_parameter_ = small_to_high; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /ProconXInputTE/main.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #define WIN32_LEAN_AND_MEAN 3 | #define NOMINMAX 4 | #include 5 | #include 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include "ProconX360Bridge.h" 24 | 25 | #define LF "\n\x1b[2K" 26 | 27 | static void SetupConsoleWindow(); 28 | static void WaitEscapeOrCtrlC(); 29 | 30 | int main(int argc, const char* argv[]) 31 | { 32 | SetupConsoleWindow(); 33 | 34 | struct CommandLineOptions 35 | { 36 | bool use_x360_layout{false}; 37 | } command_line_options = [](int argc, const char* argv[]) 38 | { 39 | CommandLineOptions ret{}; 40 | std::vector args(argv + 1, argv + argc); 41 | for (auto&& s : args) 42 | { 43 | if (s == "--use-x360-layout") { ret.use_x360_layout = true; } 44 | else 45 | { 46 | std::cerr << "Unknown command line option: " << s << std::endl; 47 | std::exit(EXIT_FAILURE); 48 | } 49 | } 50 | return ret; 51 | }(argc, argv); 52 | 53 | using namespace ProControllerHid; 54 | using namespace ViGEm; 55 | using namespace ProconXInputTE; 56 | 57 | std::cout << "Starting ViGEm Client..." << LF; 58 | std::unique_ptr client; 59 | try 60 | { 61 | client = ViGEm::ConnectToViGEm(); 62 | } 63 | catch (const std::runtime_error& re) 64 | { 65 | std::cout << "Failed to connect ViGEm Bus. ERROR=" << re.what() << LF; 66 | std::cout << "Please make sure ViGEm Bus Driver installed." << LF; 67 | return 1; 68 | } 69 | std::cout << "ViGEm Client Ready." << LF; 70 | 71 | std::vector> bridges; 72 | { 73 | std::cout << "Finding all ProControllers..." << LF; 74 | 75 | auto devices = ProController::EnumerateProControllerDevicePaths(); 76 | for (size_t i = 0; i < devices.size(); i++) 77 | { 78 | const auto path = devices[i]; 79 | 80 | const auto options = ProconX360Bridge::Options{ 81 | command_line_options.use_x360_layout, 82 | }; 83 | 84 | try 85 | { 86 | std::cout << "- Device found:" << LF; 87 | std::cout << " Path: " << path << LF; 88 | const auto logger = [i](const char* text) { std::cout << " ProCon[" << std::to_string(i) << "]: " << text << LF; }; 89 | bridges.emplace_back(std::make_unique(path.c_str(), client.get(), options, logger)); 90 | std::cout << " Connected as Virtual X360 Controller" << " index[" << bridges.back()->GetIndex() << "]" << LF; 91 | } 92 | catch (const std::exception& e) 93 | { 94 | std::cout << " Error: " << e.what() << LF; 95 | } 96 | catch (...) 97 | { 98 | std::cout << " Unknown error occurred." << LF; 99 | } 100 | } 101 | 102 | if (bridges.empty()) 103 | { 104 | std::cout << "No Pro Controllers found." << LF; 105 | return 1; 106 | } 107 | } 108 | 109 | std::cout << LF; 110 | std::cout << "Controller Bridges started." << LF; 111 | 112 | // monitor 113 | std::atomic_flag monitor_thread_running{}; 114 | auto monitor_thread = std::thread([&]() 115 | { 116 | monitor_thread_running.test_and_set(); 117 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 118 | 119 | while (monitor_thread_running.test_and_set()) 120 | { 121 | std::stringstream output; 122 | 123 | output << LF; 124 | for (auto&& b : bridges) 125 | { 126 | auto input = b->GetLastInput(); // controller input 127 | auto outputIn = b->GetLastOutputIn().Status; // x360 input value 128 | auto outputOut = b->GetLastOutputOut(); // sent to controller value 129 | 130 | output << b->GetIndex() << ">"; 131 | output << "Vib"; 132 | output << " L:" 133 | << std::setw(3) << static_cast(outputOut.Large.Left) << "/" 134 | << std::setw(3) << static_cast(outputIn.LargeRumble); 135 | output << " H:" 136 | << std::setw(3) << static_cast(outputOut.Small.Right) << "/" 137 | << std::setw(3) << static_cast(outputIn.SmallRumble); 138 | output << " In " << InputStatusAsString(input); 139 | output << LF; 140 | } 141 | 142 | for (size_t i = 0; i < bridges.size() + 2; ++i) 143 | { 144 | output << "\x1b[1A"; // cursor move up 145 | } 146 | 147 | std::cout << output.str() << LF; 148 | 149 | std::this_thread::sleep_for(std::chrono::milliseconds(20)); 150 | } 151 | }); 152 | 153 | WaitEscapeOrCtrlC(); 154 | 155 | monitor_thread_running.clear(); 156 | monitor_thread.join(); 157 | 158 | std::cout << "Closing controllers..." << LF; 159 | bridges.clear(); 160 | std::cout << "Closed." << LF; 161 | } 162 | 163 | static void SetupConsoleWindow() 164 | { 165 | #ifdef _WIN32 166 | DWORD mode = {}; 167 | GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &mode); 168 | SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); 169 | 170 | CONSOLE_SCREEN_BUFFER_INFO info; 171 | GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); 172 | info.dwSize.X = std::max(info.dwSize.X, SHORT{120}); 173 | SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), info.dwSize); 174 | #endif 175 | } 176 | 177 | static void WaitEscapeOrCtrlC() 178 | { 179 | #ifdef _WIN32 180 | std::cout << "Press Ctrl+C or ESCAPE to exit.\n" << std::endl; 181 | while (int ch = _getch()) 182 | { 183 | if (ch == 3 || ch == 27) { break; } 184 | } 185 | #else 186 | std::cout << "Press Enter to exit.\n" << std::endl; 187 | (void)getchar(); 188 | #endif 189 | } 190 | -------------------------------------------------------------------------------- /ViGEmClient/ViGEmClientCpp.cpp: -------------------------------------------------------------------------------- 1 | // ViGEm Client RAII wrapper 2 | 3 | #include "ViGEmClientCpp.h" 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "ViGEm/Client.h" 10 | 11 | namespace ViGEm 12 | { 13 | using ViGEmClientPtr = std::shared_ptr>; 14 | using ViGEmTargetPtr = std::shared_ptr>; 15 | 16 | struct ViGEmErrorException : std::runtime_error 17 | { 18 | VIGEM_ERROR e; 19 | explicit ViGEmErrorException(VIGEM_ERROR e) : std::runtime_error(GetErrorMessage(e)), e(e) { } 20 | 21 | static const char* GetErrorMessage(VIGEM_ERROR e) 22 | { 23 | switch (e) 24 | { 25 | //#define CASE(e) case e: return #e; 26 | case VIGEM_ERROR_NONE: return "VIGEM_ERROR_NONE"; 27 | case VIGEM_ERROR_BUS_NOT_FOUND: return "VIGEM_ERROR_BUS_NOT_FOUND"; 28 | case VIGEM_ERROR_NO_FREE_SLOT: return "VIGEM_ERROR_NO_FREE_SLOT"; 29 | case VIGEM_ERROR_INVALID_TARGET: return "VIGEM_ERROR_INVALID_TARGET"; 30 | case VIGEM_ERROR_REMOVAL_FAILED: return "VIGEM_ERROR_REMOVAL_FAILED"; 31 | case VIGEM_ERROR_ALREADY_CONNECTED: return "VIGEM_ERROR_ALREADY_CONNECTED"; 32 | case VIGEM_ERROR_TARGET_UNINITIALIZED: return "VIGEM_ERROR_TARGET_UNINITIALIZED"; 33 | case VIGEM_ERROR_TARGET_NOT_PLUGGED_IN: return "VIGEM_ERROR_TARGET_NOT_PLUGGED_IN"; 34 | case VIGEM_ERROR_BUS_VERSION_MISMATCH: return "VIGEM_ERROR_BUS_VERSION_MISMATCH"; 35 | case VIGEM_ERROR_BUS_ACCESS_FAILED: return "VIGEM_ERROR_BUS_ACCESS_FAILED"; 36 | case VIGEM_ERROR_CALLBACK_ALREADY_REGISTERED: return "VIGEM_ERROR_CALLBACK_ALREADY_REGISTERED"; 37 | case VIGEM_ERROR_CALLBACK_NOT_FOUND: return "VIGEM_ERROR_CALLBACK_NOT_FOUND"; 38 | case VIGEM_ERROR_BUS_ALREADY_CONNECTED: return "VIGEM_ERROR_BUS_ALREADY_CONNECTED"; 39 | case VIGEM_ERROR_BUS_INVALID_HANDLE: return "VIGEM_ERROR_BUS_INVALID_HANDLE"; 40 | case VIGEM_ERROR_XUSB_USERINDEX_OUT_OF_RANGE: return "VIGEM_ERROR_XUSB_USERINDEX_OUT_OF_RANGE"; 41 | case VIGEM_ERROR_INVALID_PARAMETER: return "VIGEM_ERROR_INVALID_PARAMETER"; 42 | case VIGEM_ERROR_NOT_SUPPORTED: return "VIGEM_ERROR_NOT_SUPPORTED"; 43 | } 44 | return "VIGEM_ERROR_UNKOWN"; 45 | } 46 | }; 47 | 48 | static void ThrowOnError(VIGEM_ERROR e) 49 | { 50 | if (e != VIGEM_ERROR_NONE) 51 | { 52 | if (IsDebuggerPresent()) DebugBreak(); 53 | throw ViGEmErrorException(e); 54 | } 55 | } 56 | 57 | struct ViGEmClientImpl final : public ViGEmClient 58 | { 59 | ViGEmClientPtr client_; 60 | 61 | struct X360ControllerImpl final : public X360Controller 62 | { 63 | ViGEmClientPtr client_{}; 64 | ViGEmTargetPtr target_{}; 65 | 66 | std::mutex mutex_{}; 67 | X360OutputStatus lastOutputStatus_{}; 68 | X360OutputCallback callback_{}; 69 | 70 | public: 71 | X360ControllerImpl(ViGEmClientPtr client) 72 | : client_(std::move(client)) 73 | , target_(vigem_target_x360_alloc(), &vigem_target_free) 74 | { 75 | ThrowOnError( 76 | vigem_target_add(client_.get(), target_.get()) 77 | ); 78 | 79 | ThrowOnError( 80 | vigem_target_x360_register_notification( 81 | client_.get(), target_.get(), []( 82 | PVIGEM_CLIENT, PVIGEM_TARGET, 83 | UCHAR LargeMotor, UCHAR SmallMotor, UCHAR LedNumber, 84 | LPVOID UserData) 85 | { 86 | X360OutputStatus status{}; 87 | status.LargeRumble = LargeMotor; 88 | status.SmallRumble = SmallMotor; 89 | status.LedNumber = LedNumber; 90 | static_cast(UserData)->NotificationCallbackProc(status); 91 | }, this) 92 | ); 93 | } 94 | 95 | void NotificationCallbackProc(X360OutputStatus output) 96 | { 97 | std::lock_guard lock(mutex_); 98 | lastOutputStatus_ = output; 99 | if (callback_) callback_(lastOutputStatus_); 100 | } 101 | 102 | ~X360ControllerImpl() override 103 | { 104 | StopNotification(); 105 | vigem_target_x360_unregister_notification(target_.get()); 106 | vigem_target_remove(client_.get(), target_.get()); 107 | } 108 | 109 | unsigned long GetDeviceIndex() const override 110 | { 111 | return vigem_target_get_index(target_.get()); 112 | } 113 | 114 | void SendReport(X360InputStatus inputStatus) override 115 | { 116 | static_assert( 117 | sizeof(XUSB_REPORT) == sizeof(X360InputStatus) && 118 | std::is_trivial_v && 119 | std::is_trivial_v, 120 | "type punning check failed."); 121 | XUSB_REPORT report{}; 122 | memcpy(&report, &inputStatus, sizeof(report)); // type punning 123 | 124 | vigem_target_x360_update(client_.get(), target_.get(), report); 125 | } 126 | 127 | void ReceiveReport(X360OutputStatus* outputStatus) override 128 | { 129 | std::lock_guard lock(mutex_); 130 | *outputStatus = lastOutputStatus_; 131 | } 132 | 133 | void StartNotification(X360OutputCallback callback) override 134 | { 135 | std::lock_guard lock(mutex_); 136 | callback_ = callback; 137 | } 138 | 139 | void StopNotification() override 140 | { 141 | std::lock_guard lock(mutex_); 142 | callback_ = nullptr; 143 | } 144 | }; 145 | 146 | public: 147 | ViGEmClientImpl() 148 | : client_(ViGEmClientPtr(vigem_alloc(), &vigem_free)) 149 | { 150 | ThrowOnError( 151 | vigem_connect(client_.get()) 152 | ); 153 | } 154 | 155 | std::unique_ptr AddX360Controller() override 156 | { 157 | return std::make_unique(client_); 158 | } 159 | }; 160 | 161 | std::unique_ptr ConnectToViGEm() 162 | { 163 | return std::make_unique(); 164 | } 165 | } 166 | --------------------------------------------------------------------------------