diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index a2d7835..e8e4e18 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -596,6 +596,8 @@ set(SOURCE_FILES ${SOURCE_FILES} rawinput/smxstage.h rawinput/smxdedicab.cpp rawinput/smxdedicab.h + rawinput/xinput.cpp + rawinput/xinput.h # reader reader/reader.cpp diff --git a/src/spice2x/build_all.sh b/src/spice2x/build_all.sh index d40c6a8..039200b 100755 --- a/src/spice2x/build_all.sh +++ b/src/spice2x/build_all.sh @@ -70,6 +70,8 @@ DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip" DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.' TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm" TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux" +TARGETS_XP32="spicetools_cfg spicetools_spice" +TARGETS_XP64="spicetools_spice64" # determine build type BUILD_TYPE="Release" @@ -169,7 +171,7 @@ time ( fi mkdir -p ${BUILDDIR_WINXP_32} pushd ${BUILDDIR_WINXP_32} > /dev/null - CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_32} + CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP32} popd > /dev/null # 64 bit Windows XP @@ -182,7 +184,7 @@ time ( fi mkdir -p ${BUILDDIR_WINXP_64} pushd ${BUILDDIR_WINXP_64} > /dev/null - CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64} + CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64} popd > /dev/null else echo "Skipping WinXP builds, toolchain not specified" diff --git a/src/spice2x/cfg/analog.cpp b/src/spice2x/cfg/analog.cpp index 44abb41..7e89e9a 100644 --- a/src/spice2x/cfg/analog.cpp +++ b/src/spice2x/cfg/analog.cpp @@ -90,6 +90,8 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) { return "MIDI Unknown Index " + indexString + " (" + device->desc + ")"; } } + case rawinput::XINPUT_GAMEPAD: + return xinput::get_analog_string(static_cast(index)) + " (" + device->desc + ")"; case rawinput::DESTROYED: return "Device unplugged (" + indexString + ")"; default: diff --git a/src/spice2x/cfg/api.cpp b/src/spice2x/cfg/api.cpp index 27df2bd..6f467e9 100644 --- a/src/spice2x/cfg/api.cpp +++ b/src/spice2x/cfg/api.cpp @@ -1,5 +1,6 @@ #include "api.h" +#include #include #include "launcher/superexit.h" @@ -389,6 +390,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma } case rawinput::PIUIO_DEVICE: { state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED; + break; + } + case rawinput::XINPUT_GAMEPAD: { + assert(reinterpret_cast(device->handle) < XUSER_MAX_COUNT); + const auto player = static_cast(reinterpret_cast(device->handle)); + state = manager->XINPUT_MGR->is_button_pressed( + player, + static_cast(vKey)) ? + BUTTON_PRESSED : BUTTON_NOT_PRESSED; + break; } default: break; @@ -586,7 +597,7 @@ float Buttons::getVelocity(std::unique_ptr &manager, } } -float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) { +float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog) { float value = 0.5f; if (!device) { return value; @@ -767,6 +778,18 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) { } break; } + case rawinput::XINPUT_GAMEPAD: { + assert(reinterpret_cast(device->handle) < XUSER_MAX_COUNT); + const auto player = static_cast(reinterpret_cast(device->handle)); + value = manager->XINPUT_MGR->get_analog_state( + player, + static_cast(index)); + // invert value + if (inverted) { + value = 1.f - value; + } + break; + } default: break; } @@ -822,7 +845,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &ana return analog.getLastState(); } - float state = getState(device, analog); + float state = getState(manager, device, analog); analog.setLastState(state); return state; diff --git a/src/spice2x/cfg/api.h b/src/spice2x/cfg/api.h index c23d7eb..3cb4d37 100644 --- a/src/spice2x/cfg/api.h +++ b/src/spice2x/cfg/api.h @@ -103,7 +103,7 @@ namespace GameAPI { } } - float getState(rawinput::Device *device, Analog &analog); + float getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog); float getState(rawinput::RawInputManager *manager, Analog &analog); float getState(std::unique_ptr &manager, Analog &analog); } diff --git a/src/spice2x/cfg/button.cpp b/src/spice2x/cfg/button.cpp index 9dfd54e..40a234e 100644 --- a/src/spice2x/cfg/button.cpp +++ b/src/spice2x/cfg/button.cpp @@ -417,6 +417,8 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) { } case rawinput::PIUIO_DEVICE: return "PIUIO " + vKeyString; + case rawinput::XINPUT_GAMEPAD: + return xinput::get_button_string(static_cast(vKey)); case rawinput::DESTROYED: return "Device unplugged (" + vKeyString + ")"; default: diff --git a/src/spice2x/overlay/windows/config.cpp b/src/spice2x/overlay/windows/config.cpp index 1b099f7..b875a1b 100644 --- a/src/spice2x/overlay/windows/config.cpp +++ b/src/spice2x/overlay/windows/config.cpp @@ -2165,6 +2165,9 @@ namespace overlay::windows { case rawinput::MIDI: this->analogs_devices.emplace_back(&device); break; + case rawinput::XINPUT_GAMEPAD: + this->analogs_devices.emplace_back(&device); + break; default: continue; } @@ -2289,6 +2292,15 @@ namespace overlay::windows { onoff.size()); } } + break; + } + + case rawinput::XINPUT_GAMEPAD: { + for (int i = 0; i < static_cast(xinput::XInputAnalogEnum::COUNT); i++) { + const auto v = static_cast(i); + control_names.push_back(xinput::get_analog_string(v)); + } + break; } default: break; diff --git a/src/spice2x/overlay/windows/control.cpp b/src/spice2x/overlay/windows/control.cpp index 0ccadf7..5e34f82 100644 --- a/src/spice2x/overlay/windows/control.cpp +++ b/src/spice2x/overlay/windows/control.cpp @@ -732,6 +732,10 @@ namespace overlay::windows { ImGui::Text("Type: PIUIO"); break; } + case rawinput::XINPUT_GAMEPAD: { + ImGui::Text("Type: XInput"); + break; + } case rawinput::DESTROYED: { ImGui::Text("Disconnected."); break; diff --git a/src/spice2x/rawinput/device.h b/src/spice2x/rawinput/device.h index 5990c12..5a080a3 100644 --- a/src/spice2x/rawinput/device.h +++ b/src/spice2x/rawinput/device.h @@ -31,7 +31,8 @@ namespace rawinput { SEXTET_OUTPUT, PIUIO_DEVICE, SMX_STAGE, - SMX_DEDICAB + SMX_DEDICAB, + XINPUT_GAMEPAD, }; enum MouseKeys { diff --git a/src/spice2x/rawinput/rawinput.cpp b/src/spice2x/rawinput/rawinput.cpp index c3207c0..ae5568c 100644 --- a/src/spice2x/rawinput/rawinput.cpp +++ b/src/spice2x/rawinput/rawinput.cpp @@ -70,6 +70,8 @@ void rawinput::set_midi_algorithm(rawinput::MidiNoteAlgorithm new_algo) { rawinput::RawInputManager::RawInputManager() { + XINPUT_MGR = std::make_unique(); + // create input window and load in devices this->input_hwnd_create(); this->devices_reload(); @@ -106,6 +108,8 @@ void rawinput::RawInputManager::stop() { // destruct all devices and input window this->devices_destruct(); this->input_hwnd_destroy(); + + XINPUT_MGR.reset(); } void rawinput::RawInputManager::input_hwnd_create() { @@ -191,6 +195,8 @@ void rawinput::RawInputManager::devices_reload() { this->devices_scan_smxdedicab(); } + this->devices_scan_xinput(); + // check for LIT Board sextet_register("COM54", "LIT Board", false); @@ -1009,6 +1015,27 @@ void rawinput::RawInputManager::devices_scan_smxdedicab() { } } +void rawinput::RawInputManager::devices_scan_xinput() { + log_misc("rawinput", "scan XInput devices..."); + const auto players = XINPUT_MGR->get_available_players(); + for (const auto player : players) { + auto *new_xinput_device = new Device(); + new_xinput_device->type = XINPUT_GAMEPAD; + new_xinput_device->name = fmt::format(";XINPUT;{}", player); + new_xinput_device->desc = fmt::format("XInput Gamepad P{}", player + 1); + new_xinput_device->handle = reinterpret_cast(player); + new_xinput_device->mutex = new std::mutex(); + new_xinput_device->mutex_out = new std::mutex(); + + auto &device = this->devices.emplace_back(*new_xinput_device); + + // notify add + for (auto &cb : this->callback_add) { + cb.f(cb.data, &device); + } + } +} + void rawinput::RawInputManager::flush_start() { // start flush thread @@ -2734,6 +2761,10 @@ void rawinput::RawInputManager::devices_print() { log_misc("rawinput", "device type: SMX_DEDICAB"); break; } + case XINPUT_GAMEPAD: { + log_misc("rawinput", "device type: XINPUT"); + break; + } case UNKNOWN: default: log_warning("rawinput", "device type: UNKNOWN"); diff --git a/src/spice2x/rawinput/rawinput.h b/src/spice2x/rawinput/rawinput.h index e6537c3..e91ee73 100644 --- a/src/spice2x/rawinput/rawinput.h +++ b/src/spice2x/rawinput/rawinput.h @@ -10,6 +10,7 @@ #include "device.h" #include "hotplug.h" +#include "rawinput/xinput.h" #include "util/scope_guard.h" namespace rawinput { @@ -84,6 +85,7 @@ namespace rawinput { void devices_scan_piuio(); void devices_scan_smxstage(); void devices_scan_smxdedicab(); + void devices_scan_xinput(); void devices_destruct(); void devices_destruct(Device *device, bool log = true); void flush_start(); @@ -101,6 +103,7 @@ namespace rawinput { public: HWND input_hwnd = nullptr; + std::unique_ptr XINPUT_MGR = nullptr; RawInputManager(); ~RawInputManager(); diff --git a/src/spice2x/rawinput/xinput.cpp b/src/spice2x/rawinput/xinput.cpp new file mode 100644 index 0000000..155a7e4 --- /dev/null +++ b/src/spice2x/rawinput/xinput.cpp @@ -0,0 +1,329 @@ +#include "rawinput/xinput.h" +#include "util/logging.h" + +namespace xinput { + +// this is all we need to emulate xinput.h which we avoid including here... + +#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD (30 / 255.0f) +#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849 +#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 + +#define XINPUT_GAMEPAD_DPAD_UP 0x0001 +#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002 +#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004 +#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008 +#define XINPUT_GAMEPAD_START 0x0010 +#define XINPUT_GAMEPAD_BACK 0x0020 +#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040 +#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080 +#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100 +#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200 +#define XINPUT_GAMEPAD_A 0x1000 +#define XINPUT_GAMEPAD_B 0x2000 +#define XINPUT_GAMEPAD_X 0x4000 +#define XINPUT_GAMEPAD_Y 0x8000 + +typedef struct { + uint32_t dwPacketNumber; + XINPUT_GAMEPAD_STATE Gamepad; +} XINPUT_STATE; + +DWORD +XInputGetState( + DWORD dwUserIndex, + XINPUT_STATE *pState +); + +// end xinput definitions + + std::string get_button_string(XInputButtonEnum button) { + switch (button) { + case XInputButtonEnum::DPAD_UP: + return "Up"; + case XInputButtonEnum::DPAD_DOWN: + return "Down"; + case XInputButtonEnum::DPAD_LEFT: + return "Left"; + case XInputButtonEnum::DPAD_RIGHT: + return "Right"; + case XInputButtonEnum::START: + return "Start"; + case XInputButtonEnum::BACK: + return "Back"; + case XInputButtonEnum::LEFT_STICK: + return "Left Stick Click"; + case XInputButtonEnum::RIGHT_STICK: + return "Right Stick Click"; + case XInputButtonEnum::LEFT_SHOULDER: + return "Left Bumper"; + case XInputButtonEnum::RIGHT_SHOULDER: + return "Right Bumper"; + case XInputButtonEnum::BUTTON_A: + return "Button A"; + case XInputButtonEnum::BUTTON_B: + return "Button B"; + case XInputButtonEnum::BUTTON_X: + return "Button X"; + case XInputButtonEnum::BUTTON_Y: + return "Button Y"; + case XInputButtonEnum::LEFT_TRIGGER: + return "Left Trigger"; + case XInputButtonEnum::RIGHT_TRIGGER: + return "Right Trigger"; + case XInputButtonEnum::LEFT_STICK_UP: + return "Left Stick Up"; + case XInputButtonEnum::LEFT_STICK_DOWN: + return "Left Stick Down"; + case XInputButtonEnum::LEFT_STICK_LEFT: + return "Left Stick Left"; + case XInputButtonEnum::LEFT_STICK_RIGHT: + return "Left Stick Right"; + case XInputButtonEnum::RIGHT_STICK_UP: + return "Right Stick Up"; + case XInputButtonEnum::RIGHT_STICK_DOWN: + return "Right Stick Down"; + case XInputButtonEnum::RIGHT_STICK_LEFT: + return "Right Stick Left"; + case XInputButtonEnum::RIGHT_STICK_RIGHT: + return "Right Stick Right"; + default: + break; + } + return fmt::format("Unknown Button ({})", static_cast(button)); + } + + std::string get_analog_string(XInputAnalogEnum analog) { + switch (analog) { + case XInputAnalogEnum::LEFT_TRIGGER: + return "Left Trigger"; + case XInputAnalogEnum::RIGHT_TRIGGER: + return "Right Trigger"; + case XInputAnalogEnum::LEFT_STICK_X: + return "Left Stick X"; + case XInputAnalogEnum::LEFT_STICK_Y: + return "Left Stick Y"; + case XInputAnalogEnum::RIGHT_STICK_X: + return "Right Stick X"; + case XInputAnalogEnum::RIGHT_STICK_Y: + return "Right Stick Y"; + default: + break; + } + return fmt::format("Unknown Analog ({})", static_cast(analog)); + } + +#if defined(SPICE_XP) + + XInputManager::XInputManager() {} + XInputManager::~XInputManager() {} + void XInputManager::stop() {} + std::vector XInputManager::get_available_players() { + return {}; + } + float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) { + return 0.5f; + } + bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) { + return false; + } + +#else + + static decltype(XInputGetState) *XInputGetState_addr = nullptr; + + XInputManager::XInputManager() { + log_info("xinput", "initialize..."); + this->xinput_lib = LoadLibraryA("xinput1_3.dll"); + if (!this->xinput_lib) { + log_warning("xinput", "failed to load xinput1_3.dll"); + return; + } + XInputGetState_addr = reinterpret_cast( + GetProcAddress(this->xinput_lib, "XInputGetState")); + if (!XInputGetState_addr) { + log_warning("xinput", "failed to get XInputGetState address"); + this->stop(); + return; + } + initialized = true; + log_info("xinput", "initialized"); + } + + XInputManager::~XInputManager() { + this->stop(); + } + + void XInputManager::stop() { + if (this->xinput_lib) { + FreeLibrary(this->xinput_lib); + this->xinput_lib = nullptr; + } + XInputGetState_addr = nullptr; + this->initialized = false; + log_info("xinput", "destroyed"); + } + + std::vector XInputManager::get_available_players() { + std::vector players; + if (!this->initialized) { + return players; + } + for (uint8_t i = 0; i < XUSER_MAX_COUNT; i++) { + XINPUT_STATE x; + if (XInputGetState_addr(i, &x) == ERROR_SUCCESS) { + players.emplace_back(i); + } + } + return players; + } + + void XInputManager::get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED *state) { + *state = {}; + if (!this->initialized) { + return; + } + + XINPUT_STATE x; + if (XInputGetState_addr(player, &x) != ERROR_SUCCESS) { + return; + } + + state->wButtons = x.Gamepad.wButtons; + state->bLeftTrigger = x.Gamepad.bLeftTrigger / 255.0f; + state->bRightTrigger = x.Gamepad.bRightTrigger / 255.0f; + + // left stick circular dead zone + { + const auto x_raw = x.Gamepad.sThumbLX; + const auto y_raw = x.Gamepad.sThumbLY; + const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw); + if (magnitude > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) { + const float scaled = + (magnitude - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) / + (32767.0f - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); + + state->sThumbLX = (x_raw / magnitude) * scaled; + state->sThumbLY = (y_raw / magnitude) * scaled; + + // normalize to [0, 1] + state->sThumbLX = std::clamp((state->sThumbLX + 1.f) / 2.f, 0.f, 1.f); + state->sThumbLY = std::clamp((state->sThumbLY + 1.f) / 2.f, 0.f, 1.f); + } else { + // within deadzone + state->sThumbLX = 0.5f; + state->sThumbLY = 0.5f; + } + } + + // right stick circular dead zone + { + const auto x_raw = x.Gamepad.sThumbRX; + const auto y_raw = x.Gamepad.sThumbRY; + const float magnitude = sqrtf(x_raw * x_raw + y_raw * y_raw); + if (magnitude > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) { + const float scaled = + (magnitude - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) / + (32767.0f - XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); + + state->sThumbRX = (x_raw / magnitude) * scaled; + state->sThumbRY = (y_raw / magnitude) * scaled; + + // normalize to [0, 1] + state->sThumbRX = std::clamp((state->sThumbRX + 1.f) / 2.f, 0.f, 1.f); + state->sThumbRY = std::clamp((state->sThumbRY + 1.f) / 2.f, 0.f, 1.f); + } else { + // within deadzone + state->sThumbRX = 0.5f; + state->sThumbRY = 0.5f; + } + } + } + + bool XInputManager::is_button_pressed(uint8_t player, XInputButtonEnum button) { + XINPUT_GAMEPAD_STATE_NORMALIZED state; + + get_state(player, &state); + switch (button) { + case XInputButtonEnum::DPAD_UP: + return (state.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0; + case XInputButtonEnum::DPAD_DOWN: + return (state.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0; + case XInputButtonEnum::DPAD_LEFT: + return (state.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0; + case XInputButtonEnum::DPAD_RIGHT: + return (state.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0; + case XInputButtonEnum::START: + return (state.wButtons & XINPUT_GAMEPAD_START) != 0; + case XInputButtonEnum::BACK: + return (state.wButtons & XINPUT_GAMEPAD_BACK) != 0; + case XInputButtonEnum::LEFT_STICK: + return (state.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0; + case XInputButtonEnum::RIGHT_STICK: + return (state.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0; + case XInputButtonEnum::LEFT_SHOULDER: + return (state.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0; + case XInputButtonEnum::RIGHT_SHOULDER: + return (state.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0; + case XInputButtonEnum::BUTTON_A: + return (state.wButtons & XINPUT_GAMEPAD_A) != 0; + case XInputButtonEnum::BUTTON_B: + return (state.wButtons & XINPUT_GAMEPAD_B) != 0; + case XInputButtonEnum::BUTTON_X: + return (state.wButtons & XINPUT_GAMEPAD_X) != 0; + case XInputButtonEnum::BUTTON_Y: + return (state.wButtons & XINPUT_GAMEPAD_Y) != 0; + case XInputButtonEnum::LEFT_TRIGGER: + return state.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; + case XInputButtonEnum::RIGHT_TRIGGER: + return state.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; + case XInputButtonEnum::LEFT_STICK_UP: + return state.sThumbLY > 0.6f; + case XInputButtonEnum::LEFT_STICK_DOWN: + return state.sThumbLY < 0.4f; + case XInputButtonEnum::LEFT_STICK_LEFT: + return state.sThumbLX < 0.4f; + case XInputButtonEnum::LEFT_STICK_RIGHT: + return state.sThumbLX > 0.6f; + case XInputButtonEnum::RIGHT_STICK_UP: + return state.sThumbRY > 0.6f; + case XInputButtonEnum::RIGHT_STICK_DOWN: + return state.sThumbRY < 0.4f; + case XInputButtonEnum::RIGHT_STICK_LEFT: + return state.sThumbRX < 0.4f; + case XInputButtonEnum::RIGHT_STICK_RIGHT: + return state.sThumbRX > 0.6f; + default: + break; + } + + return false; + } + + float XInputManager::get_analog_state(uint8_t player, XInputAnalogEnum analog) { + XINPUT_GAMEPAD_STATE_NORMALIZED state; + + get_state(player, &state); + switch (analog) { + case XInputAnalogEnum::LEFT_TRIGGER: + return state.bLeftTrigger; + case XInputAnalogEnum::RIGHT_TRIGGER: + return state.bRightTrigger; + case XInputAnalogEnum::LEFT_STICK_X: + return state.sThumbLX; + case XInputAnalogEnum::LEFT_STICK_Y: + return state.sThumbLY; + case XInputAnalogEnum::RIGHT_STICK_X: + return state.sThumbRX; + case XInputAnalogEnum::RIGHT_STICK_Y: + return state.sThumbRY; + default: + break; + } + + return 0.5f; + } + +#endif + +} diff --git a/src/spice2x/rawinput/xinput.h b/src/spice2x/rawinput/xinput.h new file mode 100644 index 0000000..fbf9b83 --- /dev/null +++ b/src/spice2x/rawinput/xinput.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace xinput { + + #define XUSER_MAX_COUNT 4 + + typedef struct _XINPUT_GAMEPAD_STATE { + uint16_t wButtons; + uint8_t bLeftTrigger; + uint8_t bRightTrigger; + int16_t sThumbLX; + int16_t sThumbLY; + int16_t sThumbRX; + int16_t sThumbRY; + } XINPUT_GAMEPAD_STATE; + + typedef struct _XINPUT_GAMEPAD_STATE_NORMALIZED { + uint16_t wButtons; + float bLeftTrigger; + float bRightTrigger; + float sThumbLX; + float sThumbLY; + float sThumbRX; + float sThumbRY; + } XINPUT_GAMEPAD_STATE_NORMALIZED; + + enum class XInputButtonEnum { + // actual buttons + DPAD_UP, + DPAD_DOWN, + DPAD_LEFT, + DPAD_RIGHT, + START, + BACK, + LEFT_STICK, + RIGHT_STICK, + LEFT_SHOULDER, + RIGHT_SHOULDER, + BUTTON_A, + BUTTON_B, + BUTTON_X, + BUTTON_Y, + + // analog values that can be used as buttons + LEFT_TRIGGER, + RIGHT_TRIGGER, + LEFT_STICK_UP, + LEFT_STICK_DOWN, + LEFT_STICK_LEFT, + LEFT_STICK_RIGHT, + RIGHT_STICK_UP, + RIGHT_STICK_DOWN, + RIGHT_STICK_LEFT, + RIGHT_STICK_RIGHT, + + COUNT + }; + + enum class XInputAnalogEnum { + // analog values + LEFT_TRIGGER, + RIGHT_TRIGGER, + LEFT_STICK_X, + LEFT_STICK_Y, + RIGHT_STICK_X, + RIGHT_STICK_Y, + + COUNT + }; + + std::string get_button_string(XInputButtonEnum button); + std::string get_analog_string(XInputAnalogEnum analog); + + class XInputManager { + private: + bool initialized = false; + HMODULE xinput_lib = nullptr; + void get_state(uint8_t player, XINPUT_GAMEPAD_STATE_NORMALIZED *state); + public: + XInputManager(); + ~XInputManager(); + void stop(); + std::vector get_available_players(); + bool is_button_pressed(uint8_t player, XInputButtonEnum button); + float get_analog_state(uint8_t player, XInputAnalogEnum analog); + }; +}