diff --git a/.gitmodules b/.gitmodules index 86c33d903..a5388af69 100644 --- a/.gitmodules +++ b/.gitmodules @@ -94,3 +94,6 @@ [submodule "third_party/tabulate"] path = third_party/tabulate url = https://github.com/p-ranav/tabulate.git +[submodule "third_party/rapidcsv"] + path = third_party/rapidcsv + url = https://github.com/d99kris/rapidcsv diff --git a/src/xenia/hid/sdl/sdl_input_driver.cc b/src/xenia/hid/sdl/sdl_input_driver.cc index b77bf6891..cbbbd5f01 100644 --- a/src/xenia/hid/sdl/sdl_input_driver.cc +++ b/src/xenia/hid/sdl/sdl_input_driver.cc @@ -111,36 +111,87 @@ X_STATUS SDLInputDriver::Setup() { if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) { return; } + sdl_gamecontroller_initialized_ = true; - if (!cvars::mappings_file.empty()) { - if (!std::filesystem::exists(cvars::mappings_file)) { - XELOGW("SDL GameControllerDB: file '{}' does not exist.", - xe::path_to_utf8(cvars::mappings_file)); - } else { - auto mappings_file = filesystem::OpenFile(cvars::mappings_file, "rb"); - if (!mappings_file) { - XELOGE("SDL GameControllerDB: failed to open file '{}'.", - xe::path_to_utf8(cvars::mappings_file)); - } else { - auto mappings_result = SDL_GameControllerAddMappingsFromRW( - SDL_RWFromFP(mappings_file, SDL_TRUE), 1); - if (mappings_result < 0) { - XELOGE("SDL GameControllerDB: error loading file '{}': {}.", - xe::path_to_utf8(cvars::mappings_file), mappings_result); - } else { - XELOGI("SDL GameControllerDB: loaded {} mappings.", - mappings_result); - } - } - } - } + LoadGameControllerDB(); }); + return (sdl_events_initialized_ && sdl_gamecontroller_initialized_) ? X_STATUS_SUCCESS : X_STATUS_UNSUCCESSFUL; } +void SDLInputDriver::LoadGameControllerDB() { + if (cvars::mappings_file.empty()) { + return; + } + + if (!std::filesystem::exists(cvars::mappings_file)) { + XELOGW("SDL GameControllerDB: file '{}' does not exist.", + xe::path_to_utf8(cvars::mappings_file)); + return; + } + + XELOGI("SDL GameControllerDB: Loading {}", + xe::path_to_utf8(cvars::mappings_file)); + + uint32_t updated_mappings = 0; + uint32_t added_mappings = 0; + + rapidcsv::Document mappings( + xe::path_to_utf8(cvars::mappings_file), rapidcsv::LabelParams(-1, -1), + rapidcsv::SeparatorParams(), rapidcsv::ConverterParams(), + rapidcsv::LineReaderParams(true /* pSkipCommentLines */, + '#' /* pCommentPrefix */, + true /* pSkipEmptyLines */)); + + for (size_t i = 0; i < mappings.GetRowCount(); i++) { + std::vector row = mappings.GetRow(i); + + if (row.size() < 2) { + continue; + } + + std::string guid = row[0]; + std::string controller_name = row[1]; + + auto format = [](std::string& ss, std::string& s) { + return ss.empty() ? s : ss + "," + s; + }; + + std::string mapping_str = + std::accumulate(row.begin(), row.end(), std::string{}, format); + + int updated = SDL_GameControllerAddMapping(mapping_str.c_str()); + + switch (updated) { + case 0: { + XELOGI("SDL GameControllerDB: Updated {}, {}", controller_name, guid); + updated_mappings++; + } break; + case 1: { + added_mappings++; + } break; + default: + XELOGW("SDL GameControllerDB: error loading mapping '{}'", mapping_str); + break; + } + } + + for (uint32_t i = 0; i < HID_SDL_USER_COUNT; i++) { + auto controller = GetControllerState(i); + + if (controller) { + XELOGI("SDL Controller {}: {}", i, + SDL_GameControllerMapping(controller->sdl)); + } + } + + XELOGI("SDL GameControllerDB: Updated {} mappings.", updated_mappings); + XELOGI("SDL GameControllerDB: Added {} mappings.", added_mappings); +} + X_RESULT SDLInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags, X_INPUT_CAPABILITIES* out_caps) { assert(sdl_events_initialized_ && sdl_gamecontroller_initialized_); @@ -419,12 +470,20 @@ void SDLInputDriver::OnControllerDeviceAdded(const SDL_Event& event) { assert_always(); return; } + + char guid_str[33]; + + SDL_JoystickGetGUIDString( + SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(controller)), guid_str, + 33); + XELOGI( "SDL OnControllerDeviceAdded: \"{}\", " "JoystickType({}), " "GameControllerType({}), " "VendorID(0x{:04X}), " - "ProductID(0x{:04X})", + "ProductID(0x{:04X}), " + "GUID({})", SDL_GameControllerName(controller), SDL_JoystickGetType(SDL_GameControllerGetJoystick(controller)), #if SDL_VERSION_ATLEAST(2, 0, 12) @@ -434,10 +493,11 @@ void SDLInputDriver::OnControllerDeviceAdded(const SDL_Event& event) { #endif #if SDL_VERSION_ATLEAST(2, 0, 6) SDL_GameControllerGetVendor(controller), - SDL_GameControllerGetProduct(controller)); + SDL_GameControllerGetProduct(controller), #else - "?", "?"); + "?", "?", #endif + guid_str); int user_id = -1; #if SDL_VERSION_ATLEAST(2, 0, 9) // Check if the controller has a player index LED. @@ -468,6 +528,8 @@ void SDLInputDriver::OnControllerDeviceAdded(const SDL_Event& event) { UpdateXCapabilities(state); XELOGI("SDL OnControllerDeviceAdded: Added at index {}.", user_id); + XELOGI("SDL Controller {}: {}", user_id, + SDL_GameControllerMapping(controller)); } else { // No more controllers needed, close it. SDL_GameControllerClose(controller); diff --git a/src/xenia/hid/sdl/sdl_input_driver.h b/src/xenia/hid/sdl/sdl_input_driver.h index 1dfcd756b..1b8d2e9b1 100644 --- a/src/xenia/hid/sdl/sdl_input_driver.h +++ b/src/xenia/hid/sdl/sdl_input_driver.h @@ -16,6 +16,7 @@ #include #include "SDL.h" +#include "third_party/rapidcsv/src/rapidcsv.h" #include "xenia/hid/input_driver.h" #define HID_SDL_USER_COUNT 4 @@ -35,6 +36,8 @@ class SDLInputDriver final : public InputDriver { X_STATUS Setup() override; + void LoadGameControllerDB(); + X_RESULT GetCapabilities(uint32_t user_index, uint32_t flags, X_INPUT_CAPABILITIES* out_caps) override; X_RESULT GetState(uint32_t user_index, X_INPUT_STATE* out_state) override; diff --git a/third_party/rapidcsv b/third_party/rapidcsv new file mode 160000 index 000000000..7e87d8cac --- /dev/null +++ b/third_party/rapidcsv @@ -0,0 +1 @@ +Subproject commit 7e87d8cac42a03c323d06c8414d2afddd21788f2