Add Input Profiles. (#4)

* Add Input Profiles.

* Add capability of setting custom profiles.

* Remove leftover early return.
This commit is contained in:
Darío
2025-07-24 11:24:22 -03:00
committed by GitHub
parent 61c842cbc8
commit 01638373c0
7 changed files with 416 additions and 298 deletions
+4 -3
View File
@@ -14,10 +14,11 @@ namespace banjo {
void load_config();
void save_config();
void initialize_input_bindings();
void reset_input_bindings();
void reset_cont_input_bindings(int controller_num);
void reset_kb_input_bindings(int controller_num);
void reset_single_input_binding(int controller_num, recomp::InputDevice device, recomp::GameInput input);
void reset_cont_input_bindings(int profile_index);
void reset_kb_input_bindings(int profile_index);
void reset_single_input_binding(int profile_index, recomp::InputDevice device, recomp::GameInput input);
std::filesystem::path get_app_folder_path();
+27 -15
View File
@@ -98,6 +98,11 @@ namespace recomp {
COUNT
};
NLOHMANN_JSON_SERIALIZE_ENUM(recomp::InputDevice, {
{ recomp::InputDevice::Controller, "Controller" },
{ recomp::InputDevice::Keyboard, "Keyboard" },
});
void start_scanning_input(InputDevice device);
void stop_scanning_input();
void finish_scanning_input(InputField scanned_field);
@@ -171,31 +176,38 @@ namespace recomp {
const std::string& get_input_name(GameInput input);
const std::string& get_input_enum_name(GameInput input);
GameInput get_input_from_enum_name(const std::string_view name);
InputField& get_input_binding(int controller_num, GameInput input, size_t binding_index, InputDevice device);
void set_input_binding(int controller_num, GameInput input, size_t binding_index, InputDevice device, InputField value);
InputField& get_input_binding(int profile_index, GameInput input, size_t binding_index);
void set_input_binding(int profile_index, GameInput input, size_t binding_index, InputField value);
int add_input_profile(const std::string &key, const std::string &name, InputDevice device, bool custom);
int get_input_profile_by_key(const std::string &key);
const std::string &get_input_profile_key(int profile_index);
const std::string &get_input_profile_name(int profile_index);
InputDevice get_input_profile_device(int profile_index);
bool is_input_profile_custom(int profile_index);
int get_input_profile_count();
const std::vector<int> get_indices_for_custom_profiles(InputDevice device);
void set_input_profile_for_player(int player_index, int profile_index, InputDevice device);
int get_input_profile_for_player(int player_index, InputDevice device);
struct ControllerGUID {
uint64_t hash;
std::string serial;
int vendor{};
int product{};
int version{};
int crc16{};
int player_index{};
};
void set_input_controller_guid(int controller_num, const ControllerGUID &guid);
ControllerGUID get_input_controller_guid(int controller_num);
int add_controller(ControllerGUID guid, int profile_index);
const ControllerGUID &get_controller_guid(int controller_index);
int get_controller_profile_index(int controller_index);
int get_controller_by_guid(ControllerGUID guid);
int get_controller_count();
ControllerGUID get_guid_from_sdl_controller(SDL_GameController* game_controller);
std::string get_string_from_controller_guid(ControllerGUID guid);
struct ControllerOption {
std::string name;
ControllerGUID guid;
};
void refresh_controller_options();
const std::vector<ControllerOption> &get_controller_options();
bool get_n64_input(int controller_num, uint16_t* buttons_out, float* x_out, float* y_out);
void set_rumble(int controller_num, bool);
bool get_n64_input(int player_index, uint16_t* buttons_out, float* x_out, float* y_out);
void set_rumble(int player_index, bool);
void update_rumble();
void handle_events();
+115 -83
View File
@@ -34,6 +34,13 @@ constexpr int ds_default = 1;
constexpr int rr_manual_default = 60;
constexpr bool developer_mode_default = false;
static std::string keyboard_sp_profile_key = "keyboard_sp";
static std::string controller_sp_profile_key = "controller_sp";
static std::string keyboard_sp_profile_name = "Keyboard (SP)";
static std::string controller_sp_profile_name = "Controller (SP)";
static int keyboard_sp_profile_index = -1;
static int controller_sp_profile_index = -1;
static bool is_steam_deck = false;
ultramodern::renderer::WindowMode wm_default() {
@@ -131,7 +138,7 @@ namespace recomp {
}
void to_json(json& j, const ControllerGUID& guid) {
j = json{ {"serial", guid.serial}, {"vendor", guid.vendor}, {"product", guid.product}, {"version", guid.version}, {"crc16", guid.crc16}, {"player_index", guid.player_index} };
j = json{ {"serial", guid.serial}, {"vendor", guid.vendor}, {"product", guid.product}, {"version", guid.version}, {"crc16", guid.crc16} };
}
void from_json(const json& j, ControllerGUID& guid) {
@@ -140,7 +147,6 @@ namespace recomp {
j.at("product").get_to(guid.product);
j.at("version").get_to(guid.version);
j.at("crc16").get_to(guid.crc16);
j.at("player_index").get_to(guid.player_index);
}
}
@@ -279,69 +285,74 @@ bool load_general_config(const std::filesystem::path& path) {
return true;
}
void assign_mapping(int controller_num, recomp::InputDevice device, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
void assign_mapping(int profile_index, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
for (size_t binding_index = 0; binding_index < std::min(value.size(), recomp::bindings_per_input); binding_index++) {
recomp::set_input_binding(controller_num, input, binding_index, device, value[binding_index]);
recomp::set_input_binding(profile_index, input, binding_index, value[binding_index]);
}
};
// same as assign_mapping, except will clear unassigned bindings if not in value
void assign_mapping_complete(int controller_num, recomp::InputDevice device, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
void assign_mapping_complete(int profile_index, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
if (binding_index >= value.size()) {
recomp::set_input_binding(controller_num, input, binding_index, device, recomp::InputField{});
recomp::set_input_binding(profile_index, input, binding_index, recomp::InputField{});
} else {
recomp::set_input_binding(controller_num, input, binding_index, device, value[binding_index]);
recomp::set_input_binding(profile_index, input, binding_index, value[binding_index]);
}
}
};
void assign_all_mappings(int controller_num, recomp::InputDevice device, const recomp::DefaultN64Mappings& values) {
assign_mapping_complete(controller_num, device, recomp::GameInput::A, values.a);
assign_mapping_complete(controller_num, device, recomp::GameInput::B, values.b);
assign_mapping_complete(controller_num, device, recomp::GameInput::Z, values.z);
assign_mapping_complete(controller_num, device, recomp::GameInput::START, values.start);
assign_mapping_complete(controller_num, device, recomp::GameInput::DPAD_UP, values.dpad_up);
assign_mapping_complete(controller_num, device, recomp::GameInput::DPAD_DOWN, values.dpad_down);
assign_mapping_complete(controller_num, device, recomp::GameInput::DPAD_LEFT, values.dpad_left);
assign_mapping_complete(controller_num, device, recomp::GameInput::DPAD_RIGHT, values.dpad_right);
assign_mapping_complete(controller_num, device, recomp::GameInput::L, values.l);
assign_mapping_complete(controller_num, device, recomp::GameInput::R, values.r);
assign_mapping_complete(controller_num, device, recomp::GameInput::C_UP, values.c_up);
assign_mapping_complete(controller_num, device, recomp::GameInput::C_DOWN, values.c_down);
assign_mapping_complete(controller_num, device, recomp::GameInput::C_LEFT, values.c_left);
assign_mapping_complete(controller_num, device, recomp::GameInput::C_RIGHT, values.c_right);
void assign_all_mappings(int profile_index, const recomp::DefaultN64Mappings& values) {
assign_mapping_complete(profile_index, recomp::GameInput::A, values.a);
assign_mapping_complete(profile_index, recomp::GameInput::B, values.b);
assign_mapping_complete(profile_index, recomp::GameInput::Z, values.z);
assign_mapping_complete(profile_index, recomp::GameInput::START, values.start);
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_UP, values.dpad_up);
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_DOWN, values.dpad_down);
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_LEFT, values.dpad_left);
assign_mapping_complete(profile_index, recomp::GameInput::DPAD_RIGHT, values.dpad_right);
assign_mapping_complete(profile_index, recomp::GameInput::L, values.l);
assign_mapping_complete(profile_index, recomp::GameInput::R, values.r);
assign_mapping_complete(profile_index, recomp::GameInput::C_UP, values.c_up);
assign_mapping_complete(profile_index, recomp::GameInput::C_DOWN, values.c_down);
assign_mapping_complete(profile_index, recomp::GameInput::C_LEFT, values.c_left);
assign_mapping_complete(profile_index, recomp::GameInput::C_RIGHT, values.c_right);
assign_mapping_complete(controller_num, device, recomp::GameInput::X_AXIS_NEG, values.analog_left);
assign_mapping_complete(controller_num, device, recomp::GameInput::X_AXIS_POS, values.analog_right);
assign_mapping_complete(controller_num, device, recomp::GameInput::Y_AXIS_NEG, values.analog_down);
assign_mapping_complete(controller_num, device, recomp::GameInput::Y_AXIS_POS, values.analog_up);
assign_mapping_complete(profile_index, recomp::GameInput::X_AXIS_NEG, values.analog_left);
assign_mapping_complete(profile_index, recomp::GameInput::X_AXIS_POS, values.analog_right);
assign_mapping_complete(profile_index, recomp::GameInput::Y_AXIS_NEG, values.analog_down);
assign_mapping_complete(profile_index, recomp::GameInput::Y_AXIS_POS, values.analog_up);
assign_mapping_complete(controller_num, device, recomp::GameInput::TOGGLE_MENU, values.toggle_menu);
assign_mapping_complete(controller_num, device, recomp::GameInput::ACCEPT_MENU, values.accept_menu);
assign_mapping_complete(controller_num, device, recomp::GameInput::APPLY_MENU, values.apply_menu);
assign_mapping_complete(profile_index, recomp::GameInput::TOGGLE_MENU, values.toggle_menu);
assign_mapping_complete(profile_index, recomp::GameInput::ACCEPT_MENU, values.accept_menu);
assign_mapping_complete(profile_index, recomp::GameInput::APPLY_MENU, values.apply_menu);
};
void banjo::initialize_input_bindings() {
keyboard_sp_profile_index = recomp::add_input_profile(keyboard_sp_profile_key, keyboard_sp_profile_name, recomp::InputDevice::Keyboard, false);
controller_sp_profile_index = recomp::add_input_profile(controller_sp_profile_key, controller_sp_profile_name, recomp::InputDevice::Controller, false);
// Set Player 1 to the SP profiles by default.
recomp::set_input_profile_for_player(0, keyboard_sp_profile_index, recomp::InputDevice::Keyboard);
recomp::set_input_profile_for_player(0, controller_sp_profile_index, recomp::InputDevice::Controller);
}
void banjo::reset_input_bindings() {
assign_all_mappings(0, recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
for (int i = 0; i < 4; i++) {
assign_all_mappings(i, recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
}
assign_all_mappings(keyboard_sp_profile_index, recomp::default_n64_keyboard_mappings);
assign_all_mappings(controller_sp_profile_index, recomp::default_n64_controller_mappings);
}
void banjo::reset_cont_input_bindings(int controller_num) {
assign_all_mappings(controller_num, recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
void banjo::reset_cont_input_bindings(int profile_index) {
assign_all_mappings(profile_index, recomp::default_n64_controller_mappings);
}
void banjo::reset_kb_input_bindings(int controller_num) {
assign_all_mappings(controller_num, recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
void banjo::reset_kb_input_bindings(int profile_index) {
assign_all_mappings(profile_index, recomp::default_n64_keyboard_mappings);
}
void banjo::reset_single_input_binding(int controller_num, recomp::InputDevice device, recomp::GameInput input) {
void banjo::reset_single_input_binding(int profile_index, recomp::InputDevice device, recomp::GameInput input) {
assign_mapping_complete(
controller_num,
device,
profile_index,
input,
recomp::get_default_mapping_for_input(
device == recomp::InputDevice::Keyboard ?
@@ -385,39 +396,50 @@ bool load_graphics_config(const std::filesystem::path& path) {
return true;
}
void add_input_bindings(nlohmann::json& out, int controller_num, recomp::GameInput input, recomp::InputDevice device) {
void add_input_bindings(nlohmann::json& out, int profile_index, recomp::GameInput input) {
const std::string& input_name = recomp::get_input_enum_name(input);
nlohmann::json& out_array = out[input_name];
out_array = nlohmann::json::array();
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
out_array[binding_index] = recomp::get_input_binding(controller_num, input, binding_index, device);
out_array[binding_index] = recomp::get_input_binding(profile_index, input, binding_index);
}
};
constexpr int controls_version = 2;
constexpr int controls_version = 3;
bool save_controls_config(const std::filesystem::path& path) {
int profile_count = recomp::get_input_profile_count();
int controller_count = recomp::get_controller_count();
nlohmann::json config_json{};
config_json["version"] = controls_version;
config_json["players"] = std::vector<nlohmann::json>(4);
for (size_t i = 0; i < config_json["players"].size(); i++) {
nlohmann::json &player = config_json["players"][i];
player["keyboard"] = {};
player["controller"] = {};
config_json["profiles"] = std::vector<nlohmann::json>(profile_count);
config_json["controllers"] = std::vector<nlohmann::json>(controller_count);
for (size_t j = 0; j < recomp::get_num_inputs(); j++) {
recomp::GameInput cur_input = static_cast<recomp::GameInput>(j);
add_input_bindings(player["keyboard"], i, cur_input, recomp::InputDevice::Keyboard);
add_input_bindings(player["controller"], i, cur_input, recomp::InputDevice::Controller);
nlohmann::json &profiles = config_json["profiles"];
for (int i = 0; i < profile_count; i++) {
nlohmann::json &profile = profiles[i];
profile["key"] = recomp::get_input_profile_key(i);
profile["name"] = recomp::get_input_profile_name(i);
profile["device"] = recomp::get_input_profile_device(i);
profile["custom"] = recomp::is_input_profile_custom(i);
profile["mappings"] = nlohmann::json();
for (int j = 0; j < (int)(recomp::GameInput::COUNT); j++) {
add_input_bindings(profile["mappings"], i, (recomp::GameInput)(j));
}
}
player["controller"]["guid"] = recomp::get_input_controller_guid(i);
nlohmann::json &controllers = config_json["controllers"];
for (int i = 0; i < controller_count; i++) {
nlohmann::json &controller = controllers[i];
controller["guid"] = recomp::get_controller_guid(i);
controller["profile"] = recomp::get_input_profile_key(recomp::get_controller_profile_index(i));
}
return save_json_with_backups(path, config_json);
}
bool load_input_device_from_json(const nlohmann::json& config_json, int controller_num, recomp::InputDevice device, const std::string& key) {
bool load_input_device_from_json(const nlohmann::json& config_json, int profile_index, recomp::InputDevice device, const std::string& key) {
// Check if the json object for the given key exists.
auto find_it = config_json.find(key);
if (find_it == config_json.end()) {
@@ -434,8 +456,7 @@ bool load_input_device_from_json(const nlohmann::json& config_json, int controll
auto find_input_it = mappings_json.find(input_name);
if (find_input_it == mappings_json.end() || !find_input_it->is_array()) {
assign_mapping(
controller_num,
device,
profile_index,
cur_input,
recomp::get_default_mapping_for_input(
device == recomp::InputDevice::Keyboard ?
@@ -452,23 +473,13 @@ bool load_input_device_from_json(const nlohmann::json& config_json, int controll
for (size_t binding_index = 0; binding_index < std::min(recomp::bindings_per_input, input_json.size()); binding_index++) {
recomp::InputField cur_field{};
recomp::from_json(input_json[binding_index], cur_field);
recomp::set_input_binding(controller_num, cur_input, binding_index, device, cur_field);
recomp::set_input_binding(profile_index, cur_input, binding_index, cur_field);
}
}
return true;
}
void load_bindings_config(const nlohmann::json &config_json, int controller_num) {
if (!load_input_device_from_json(config_json, controller_num, recomp::InputDevice::Keyboard, "keyboard")) {
assign_all_mappings(controller_num, recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
}
if (!load_input_device_from_json(config_json, controller_num, recomp::InputDevice::Controller, "controller")) {
assign_all_mappings(controller_num, recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
}
}
bool load_controls_config(const std::filesystem::path& path) {
nlohmann::json config_json{};
if (!read_json_with_backups(path, config_json)) {
@@ -477,29 +488,48 @@ bool load_controls_config(const std::filesystem::path& path) {
auto version_it = config_json.find("version");
if (version_it != config_json.end()) {
auto players_it = config_json.find("players");
for (size_t i = 0; i < 4; i++) {
const bool player_exists = players_it != config_json.end() && players_it->is_array() && (players_it->size() > i);
const nlohmann::json &player = player_exists ? (*players_it)[i] : nlohmann::json();
load_bindings_config(player, (int)(i));
auto profiles = config_json.find("profiles");
if (profiles == config_json.end() || !profiles->is_array()) {
return false;
}
auto controller_it = player.find("controller");
if (controller_it != player.end()) {
const nlohmann::json &controller = *controller_it;
auto guid_it = controller.find("guid");
if (guid_it != controller.end()) {
recomp::set_input_controller_guid((int)(i), *guid_it);
for (const nlohmann::json &profile : *profiles) {
std::string key = profile.value("key", std::string());
std::string name = profile.value("name", std::string());
recomp::InputDevice device = profile.value("device", recomp::InputDevice::COUNT);
bool custom = profile.value("custom", false);
if (!key.empty() && !name.empty() && device != recomp::InputDevice::COUNT) {
int profile_index = recomp::add_input_profile(key, name, device, custom);
if (!load_input_device_from_json(profile, profile_index, device, "mappings")) {
assign_all_mappings(profile_index, device == recomp::InputDevice::Keyboard ? recomp::default_n64_keyboard_mappings : recomp::default_n64_controller_mappings);
}
}
}
auto controllers = config_json.find("controllers");
if (controllers == config_json.end() || !controllers->is_array()) {
return false;
}
for (const nlohmann::json &controller : *controllers) {
auto guid = controller.find("guid");
auto profile = controller.find("profile");
if (guid != controller.end() && guid->is_object() && profile != controller.end() && profile->is_string()) {
int profile_index = recomp::get_input_profile_by_key(*profile);
if (profile_index >= 0) {
recomp::add_controller(*guid, profile_index);
}
}
}
}
else {
// Version 1 of the format only had bindings for Player 1 on the root element.
load_bindings_config(config_json, 0);
if (!load_input_device_from_json(config_json, keyboard_sp_profile_index, recomp::InputDevice::Keyboard, "keyboard")) {
assign_all_mappings(keyboard_sp_profile_index, recomp::default_n64_keyboard_mappings);
}
for (int i = 1; i < 4; i++) {
// Assign defaults for every other controller.
assign_all_mappings(i, recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
if (!load_input_device_from_json(config_json, controller_sp_profile_index, recomp::InputDevice::Controller, "controller")) {
assign_all_mappings(controller_sp_profile_index, recomp::default_n64_controller_mappings);
}
}
@@ -553,6 +583,8 @@ void banjo::load_config() {
save_graphics_config(graphics_path);
}
banjo::initialize_input_bindings();
if (!load_controls_config(controls_path)) {
banjo::reset_input_bindings();
save_controls_config(controls_path);
+224 -32
View File
@@ -1,5 +1,7 @@
#include <array>
#include "xxHash/xxh3.h"
#include "librecomp/helpers.hpp"
#include "recomp_input.h"
#include "ultramodern/ultramodern.hpp"
@@ -7,9 +9,27 @@
// Arrays that hold the mappings for every input for keyboard and controller respectively.
using input_mapping = std::array<recomp::InputField, recomp::bindings_per_input>;
using input_mapping_array = std::array<input_mapping, static_cast<size_t>(recomp::GameInput::COUNT)>;
static std::array<input_mapping_array, 4> keyboard_input_mappings{};
static std::array<input_mapping_array, 4> controller_input_mappings{};
static std::array<recomp::ControllerGUID, 4> controller_guids{};
struct InputProfile {
std::string key;
std::string name;
recomp::InputDevice device;
input_mapping_array mappings;
bool custom = false;
};
static std::vector<InputProfile> input_profiles{};
static std::array<std::pair<int, int>, 4> players_input_profile_indices{};
static std::unordered_map<std::string, int> input_profile_key_index_map{};
static std::vector<int> input_profile_custom_indices[2]{};
struct Controller {
recomp::ControllerGUID guid;
int profile_index;
};
static std::vector<Controller> controllers;
static std::unordered_map<uint64_t, int> controller_hash_index_map{};
// Make the button value array, which maps a button index to its bit field.
#define DEFINE_INPUT(name, value, readable) uint16_t(value##u),
@@ -54,10 +74,8 @@ recomp::GameInput recomp::get_input_from_enum_name(const std::string_view enum_n
}
// Due to an RmlUi limitation this can't be const. Ideally it would return a const reference or even just a straight up copy.
recomp::InputField& recomp::get_input_binding(int controller_num, GameInput input, size_t binding_index, recomp::InputDevice device) {
input_mapping_array& device_mappings = (device == recomp::InputDevice::Controller) ? controller_input_mappings[controller_num] : keyboard_input_mappings[controller_num];
input_mapping& cur_input_mapping = device_mappings.at(static_cast<size_t>(input));
recomp::InputField& recomp::get_input_binding(int profile_index, GameInput input, size_t binding_index) {
input_mapping& cur_input_mapping = input_profiles[profile_index].mappings.at(static_cast<size_t>(input));
if (binding_index < cur_input_mapping.size()) {
return cur_input_mapping[binding_index];
}
@@ -67,49 +85,223 @@ recomp::InputField& recomp::get_input_binding(int controller_num, GameInput inpu
}
}
void recomp::set_input_binding(int controller_num, recomp::GameInput input, size_t binding_index, recomp::InputDevice device, recomp::InputField value) {
input_mapping_array &device_mappings = (device == recomp::InputDevice::Controller) ? controller_input_mappings[controller_num] : keyboard_input_mappings[controller_num];
input_mapping& cur_input_mapping = device_mappings.at(static_cast<size_t>(input));
void recomp::set_input_binding(int profile_index, recomp::GameInput input, size_t binding_index, recomp::InputField value) {
input_mapping& cur_input_mapping = input_profiles[profile_index].mappings.at(static_cast<size_t>(input));
if (binding_index < cur_input_mapping.size()) {
cur_input_mapping[binding_index] = value;
}
}
void recomp::set_input_controller_guid(int controller_num, const ControllerGUID &guid) {
controller_guids[controller_num] = guid;
int recomp::add_input_profile(const std::string &key, const std::string &name, InputDevice device, bool custom) {
auto it = input_profile_key_index_map.find(key);
if (it != input_profile_key_index_map.end()) {
return it->second;
}
int index = (int)(input_profiles.size());
InputProfile profile;
profile.key = key;
profile.name = name;
profile.device = device;
profile.custom = custom;
input_profiles.emplace_back(profile);
input_profile_key_index_map.emplace(key, index);
if (custom) {
switch (device) {
case InputDevice::Controller:
input_profile_custom_indices[0].emplace_back(index);
break;
case InputDevice::Keyboard:
input_profile_custom_indices[1].emplace_back(index);
break;
default:
assert(false && "Unknown input device.");
break;
}
}
return index;
}
recomp::ControllerGUID recomp::get_input_controller_guid(int controller_num) {
return controller_guids[controller_num];
int recomp::get_input_profile_by_key(const std::string &key) {
auto it = input_profile_key_index_map.find(key);
if (it != input_profile_key_index_map.end()) {
return it->second;
}
else {
return -1;
}
}
bool recomp::get_n64_input(int controller_num, uint16_t* buttons_out, float* x_out, float* y_out) {
const std::string &recomp::get_input_profile_key(int profile_index) {
return input_profiles[profile_index].key;
}
const std::string &recomp::get_input_profile_name(int profile_index) {
return input_profiles[profile_index].name;
}
recomp::InputDevice recomp::get_input_profile_device(int profile_index) {
return input_profiles[profile_index].device;
}
bool recomp::is_input_profile_custom(int profile_index) {
return input_profiles[profile_index].custom;
}
int recomp::get_input_profile_count() {
return (int)(input_profiles.size());
}
const std::vector<int> recomp::get_indices_for_custom_profiles(InputDevice device) {
static std::vector<int> empty;
switch (device) {
case InputDevice::Controller:
return input_profile_custom_indices[0];
case InputDevice::Keyboard:
return input_profile_custom_indices[1];
default:
assert(false && "Unknown input device.");
return empty;
}
}
void recomp::set_input_profile_for_player(int player_index, int profile_index, InputDevice device) {
switch (device) {
case InputDevice::Controller:
players_input_profile_indices[player_index].first = profile_index;
break;
case InputDevice::Keyboard:
players_input_profile_indices[player_index].second = profile_index;
break;
default:
assert(false && "Unknown input device.");
break;
}
}
int recomp::get_input_profile_for_player(int player_index, InputDevice device) {
switch (device) {
case InputDevice::Controller:
return players_input_profile_indices[player_index].first;
case InputDevice::Keyboard:
return players_input_profile_indices[player_index].second;
default:
assert(false && "Unknown input device.");
return -1;
}
}
int recomp::add_controller(ControllerGUID guid, int profile_index) {
auto it = controller_hash_index_map.find(guid.hash);
if (it != controller_hash_index_map.end()) {
controllers[it->second].profile_index = profile_index;
return it->second;
}
int index = (int)(controllers.size());
Controller controller;
controller.guid = guid;
controller.profile_index = profile_index;
controllers.emplace_back(controller);
controller_hash_index_map.emplace(guid.hash, index);
return index;
}
const recomp::ControllerGUID &recomp::get_controller_guid(int controller_index) {
return controllers[controller_index].guid;
}
int recomp::get_controller_profile_index(int controller_index) {
return controllers[controller_index].profile_index;
}
int recomp::get_controller_by_guid(ControllerGUID guid) {
auto it = controller_hash_index_map.find(guid.hash);
if (it != controller_hash_index_map.end()) {
return it->second;
}
else {
return -1;
}
}
int recomp::get_controller_count() {
return (int)(controllers.size());
}
recomp::ControllerGUID recomp::get_guid_from_sdl_controller(SDL_GameController *game_controller) {
if (game_controller == nullptr) {
return {};
}
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(game_controller);
if (joystick == nullptr) {
return {};
}
Uint16 vendor, product, version, crc16;
const char *joystick_serial = SDL_JoystickGetSerial(joystick);
SDL_JoystickGUID joystick_guid = SDL_JoystickGetGUID(joystick);
SDL_GetJoystickGUIDInfo(joystick_guid, &vendor, &product, &version, &crc16);
recomp::ControllerGUID guid;
guid.serial = joystick_serial != nullptr ? joystick_serial : "";
guid.vendor = vendor;
guid.product = product;
guid.version = version;
guid.crc16 = crc16;
// Compute the hash from the GUID.
XXH3_state_t state;
XXH3_64bits_reset(&state);
XXH3_64bits_update(&state, &guid.vendor, sizeof(guid.vendor));
XXH3_64bits_update(&state, &guid.product, sizeof(guid.product));
XXH3_64bits_update(&state, &guid.version, sizeof(guid.version));
XXH3_64bits_update(&state, &guid.crc16, sizeof(guid.crc16));
guid.hash = XXH3_64bits_digest(&state);
return guid;
}
std::string recomp::get_string_from_controller_guid(ControllerGUID guid) {
return "SERIAL_" + guid.serial + "_VID_" + std::to_string(guid.vendor) + "_PID_" + std::to_string(guid.product) +"_VERSION_" + std::to_string(guid.version) +"_CRC16_" + std::to_string(guid.crc16);
}
bool recomp::get_n64_input(int player_index, uint16_t* buttons_out, float* x_out, float* y_out) {
uint16_t cur_buttons = 0;
float cur_x = 0.0f;
float cur_y = 0.0f;
if (!recomp::game_input_disabled()) {
for (size_t i = 0; i < n64_button_values.size(); i++) {
size_t input_index = (size_t)GameInput::N64_BUTTON_START + i;
cur_buttons |= recomp::get_input_digital(controller_num, keyboard_input_mappings[controller_num][input_index]) ? n64_button_values[i] : 0;
cur_buttons |= recomp::get_input_digital(controller_num, controller_input_mappings[controller_num][input_index]) ? n64_button_values[i] : 0;
}
auto check_buttons = [&](int profile_index) {
if (profile_index < 0) {
return;
}
float joystick_deadzone = recomp::get_joystick_deadzone() / 100.0f;
const input_mapping_array &mappings = input_profiles[profile_index].mappings;
for (size_t i = 0; i < n64_button_values.size(); i++) {
cur_buttons |= recomp::get_input_digital(player_index, mappings[(size_t)(GameInput::N64_BUTTON_START) + i]) ? n64_button_values[i] : 0;
}
};
float joystick_x = recomp::get_input_analog(controller_num, controller_input_mappings[controller_num][(size_t)GameInput::X_AXIS_POS])
- recomp::get_input_analog(controller_num, controller_input_mappings[controller_num][(size_t)GameInput::X_AXIS_NEG]);
auto check_joystick = [&](int profile_index) {
if (profile_index < 0) {
return;
}
float joystick_y = recomp::get_input_analog(controller_num, controller_input_mappings[controller_num][(size_t)GameInput::Y_AXIS_POS])
- recomp::get_input_analog(controller_num, controller_input_mappings[controller_num][(size_t)GameInput::Y_AXIS_NEG]);
const input_mapping_array &mappings = input_profiles[profile_index].mappings;
cur_x += recomp::get_input_analog(player_index, mappings[(size_t)GameInput::X_AXIS_POS]) - recomp::get_input_analog(player_index, mappings[(size_t)GameInput::X_AXIS_NEG]);
cur_y += recomp::get_input_analog(player_index, mappings[(size_t)GameInput::Y_AXIS_POS]) - recomp::get_input_analog(player_index, mappings[(size_t)GameInput::Y_AXIS_NEG]);
};
recomp::apply_joystick_deadzone(joystick_x, joystick_y, &joystick_x, &joystick_y);
check_buttons(players_input_profile_indices[player_index].first);
check_buttons(players_input_profile_indices[player_index].second);
cur_x = recomp::get_input_analog(controller_num, keyboard_input_mappings[controller_num][(size_t)GameInput::X_AXIS_POS])
- recomp::get_input_analog(controller_num, keyboard_input_mappings[controller_num][(size_t)GameInput::X_AXIS_NEG]) + joystick_x;
cur_y = recomp::get_input_analog(controller_num, keyboard_input_mappings[controller_num][(size_t)GameInput::Y_AXIS_POS])
- recomp::get_input_analog(controller_num, keyboard_input_mappings[controller_num][(size_t)GameInput::Y_AXIS_NEG]) + joystick_y;
check_joystick(players_input_profile_indices[player_index].first);
recomp::apply_joystick_deadzone(cur_x, cur_y, &cur_x, &cur_y);
check_joystick(players_input_profile_indices[player_index].second);
}
*buttons_out = cur_buttons;
+16 -135
View File
@@ -31,7 +31,6 @@ static struct {
std::atomic_int32_t mouse_wheel_pos = 0;
std::mutex controllers_mutex;
std::vector<SDL_GameController*> detected_controllers{};
std::vector<recomp::ControllerOption> detected_controller_options{};
std::array<recompinput::AssignedPlayer, recompinput::temp_max_players> assigned_controllers{}; // Only used when Multiplayer is enabled.
std::unordered_map<SDL_JoystickID, ControllerState> controller_states;
bool single_controller = false;
@@ -153,8 +152,6 @@ void process_player_assignment(SDL_Event* event) {
return;
}
recomp::refresh_controller_options();
switch (event->type) {
case SDL_EventType::SDL_KEYDOWN: {
SDL_KeyboardEvent* keyevent = &event->key;
@@ -303,6 +300,18 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
SDL_GameControllerSetSensorEnabled(controller, SDL_SensorType::SDL_SENSOR_ACCEL, SDL_TRUE);
}
}
recomp::ControllerGUID guid = recomp::get_guid_from_sdl_controller(controller);
if (recomp::get_controller_by_guid(guid) < 0) {
std::string default_profile_key = recomp::get_string_from_controller_guid(guid);
int profile_index = recomp::get_input_profile_by_key(default_profile_key);
if (profile_index < 0) {
profile_index = recomp::add_input_profile(default_profile_key, "Controller", recomp::InputDevice::Controller, false);
banjo::reset_cont_input_bindings(profile_index);
}
recomp::add_controller(guid, profile_index);
}
}
break;
case SDL_EventType::SDL_CONTROLLERDEVICEREMOVED:
@@ -331,9 +340,9 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
break;
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN:
if (scanning_device != recomp::InputDevice::COUNT) {
// TODO: Needs the active controller tab index.
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
// TODO: Needs the controller profile index.
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0);
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1);
// note - magic number: 0 is InputType::None
if ((menuToggleBinding0.input_type != recomp::InputType::None && event->cbutton.button == menuToggleBinding0.input_id) ||
(menuToggleBinding1.input_type != recomp::InputType::None && event->cbutton.button == menuToggleBinding1.input_id)) {
@@ -629,33 +638,6 @@ const recomp::DefaultN64Mappings recomp::default_n64_controller_mappings = {
}
};
// Returns true if the guid can be fetched.
static bool get_controller_guid_from_sdl_controller(SDL_GameController* controller, recomp::ControllerGUID *guid) {
if (controller == nullptr) {
return false;
}
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);
if (joystick == nullptr) {
return false;
}
Uint16 vendor, product, version, crc16;
const char *joystick_name = SDL_JoystickName(joystick);
const char *joystick_serial = SDL_JoystickGetSerial(joystick);
std::string joystick_name_string = joystick_name != nullptr ? std::string(joystick_name) : "Unknown controller";
SDL_JoystickGUID joystick_guid = SDL_JoystickGetGUID(joystick);
int joystick_player_index = SDL_JoystickGetPlayerIndex(joystick);
SDL_GetJoystickGUIDInfo(joystick_guid, &vendor, &product, &version, &crc16);
*guid = { joystick_serial != nullptr ? std::string(joystick_serial) : std::string(), vendor, product, version, crc16, joystick_player_index };
return true;
}
static bool compare_controller_guid(const recomp::ControllerGUID& a, const recomp::ControllerGUID& b) {
return a.vendor == b.vendor && a.product == b.product && a.version == b.version && a.crc16 == b.crc16 && a.serial == b.serial;
}
void recomp::poll_inputs() {
InputState.keys = SDL_GetKeyboardState(&InputState.numkeys);
InputState.keymod = SDL_GetModState();
@@ -676,71 +658,6 @@ void recomp::poll_inputs() {
InputState.detected_controllers.push_back(controller);
}
}
if (first_poll) {
first_poll = false;
// Assign controllers based on configuration.
for (auto& player : InputState.assigned_controllers) {
player.controller = nullptr;
player.is_assigned = false;
player.keyboard_enabled = false;
}
// FIXME: Use active player count instead of iterating on all possible players.
Uint16 vendor, product, version, crc16;
for (size_t i = 0; i < 4; i++) {
recomp::ControllerGUID controller_guid = recomp::get_input_controller_guid(i);
int min_index_difference = INT_MAX;
size_t j = 0;
while (j < free_controllers.size()) {
SDL_GameController *controller = InputState.detected_controllers[free_controllers[j]];
recomp::ControllerGUID controller_guid_from_sdl;
if (!get_controller_guid_from_sdl_controller(controller, &controller_guid_from_sdl)) {
// If we can't get the controller guid, skip this controller.
j++;
continue;
}
if (!compare_controller_guid(controller_guid, controller_guid_from_sdl)) {
// If the controller guid doesn't match, skip this controller.
j++;
continue;
}
// The controller seems to be a match, but we use the controller with the least difference in player index to sort out potential duplicates.
int index_difference = abs(controller_guid.player_index - controller_guid_from_sdl.player_index);
if (min_index_difference > index_difference) {
InputState.assigned_controllers[i].controller = controller;
min_index_difference = index_difference;
free_controllers.erase(free_controllers.begin() + j);
continue;
}
j++;
}
}
}
// Do a second pass to assign controllers that are currently unused to the remaining players that failed to be assigned a controller.
for (int i = 0; i < 4; i++) {
if (i == 0) {
InputState.assigned_controllers[i].keyboard_enabled = true;
}
if (InputState.assigned_controllers[i].controller != nullptr) {
continue;
}
if (!free_controllers.empty()) {
// Assign a free controller only.
InputState.assigned_controllers[i].controller = InputState.detected_controllers[free_controllers.front()];
free_controllers.erase(free_controllers.begin());
}
else {
// Prefer not to assign a controller if none of them are free.
break;
}
}
}
// Read the deltas while resetting them to zero.
@@ -1182,40 +1099,4 @@ std::string recomp::InputField::to_string() const {
default:
return std::to_string((uint32_t)input_type) + "," + std::to_string(input_id);
}
}
void recomp::refresh_controller_options() {
std::lock_guard lock{ InputState.controllers_mutex };
InputState.detected_controllers.clear();
for (const auto& [id, state] : InputState.controller_states) {
(void)id; // Avoid unused variable warning.
SDL_GameController* controller = state.controller;
if (controller != nullptr) {
InputState.detected_controllers.push_back(controller);
}
}
InputState.detected_controller_options.clear();
for (SDL_GameController* controller : InputState.detected_controllers) {
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);
if (joystick == nullptr) {
continue;
}
Uint16 vendor, product, version, crc16;
const char *joystick_name = SDL_JoystickName(joystick);
const char *joystick_serial = SDL_JoystickGetSerial(joystick);
std::string joystick_name_string = joystick_name != nullptr ? std::string(joystick_name) : "Unknown controller";
SDL_JoystickGUID joystick_guid = SDL_JoystickGetGUID(joystick);
int joystick_player_index = SDL_JoystickGetPlayerIndex(joystick);
SDL_GetJoystickGUIDInfo(joystick_guid, &vendor, &product, &version, &crc16);
ControllerGUID guid = { joystick_serial != nullptr ? std::string(joystick_serial) : std::string(), vendor, product, version, crc16, joystick_player_index};
InputState.detected_controller_options.emplace_back(ControllerOption{ joystick_name_string, guid });
}
}
const std::vector<recomp::ControllerOption> &recomp::get_controller_options() {
return InputState.detected_controller_options;
}
}
+20 -20
View File
@@ -110,8 +110,8 @@ int recomp::get_scanned_input_index() {
}
void recomp::finish_scanning_input(recomp::InputField scanned_field) {
// TODO: Needs the active controller tab.
recomp::set_input_binding(0, static_cast<recomp::GameInput>(scanned_input_index), scanned_binding_index, cur_device, scanned_field);
// TODO: Needs the profile index.
recomp::set_input_binding(0, static_cast<recomp::GameInput>(scanned_input_index), scanned_binding_index, scanned_field);
scanned_input_index = -1;
scanned_binding_index = -1;
controls_model_handle.DirtyVariable("inputs");
@@ -594,14 +594,14 @@ public:
constructor.BindFunc("gfx_help__apply", [](Rml::Variant& out) {
if (cont_active) {
// TODO: Needs the active controller tab.
// TODO: Needs the profile index.
out = \
(recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0, recomp::InputDevice::Controller).to_string() != "" ?
" " + recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0, recomp::InputDevice::Controller).to_string() :
(recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0).to_string() != "" ?
" " + recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0).to_string() :
""
) + \
(recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 1, recomp::InputDevice::Controller).to_string() != "" ?
" " + recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 1, recomp::InputDevice::Controller).to_string() :
(recomp::get_input_binding(1, recomp::GameInput::APPLY_MENU, 1).to_string() != "" ?
" " + recomp::get_input_binding(1, recomp::GameInput::APPLY_MENU, 1).to_string() :
""
);
} else {
@@ -646,10 +646,10 @@ public:
constructor.BindEventCallback("reset_input_bindings_to_defaults",
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
if (cur_device == recomp::InputDevice::Controller) {
// TODO: Needs the active controller tab.
// TODO: Needs the profile index.
banjo::reset_cont_input_bindings(0);
} else {
// TODO: Needs the active controller tab.
// TODO: Needs the profile index.
banjo::reset_kb_input_bindings(0);
}
model_handle.DirtyAllVariables();
@@ -662,8 +662,8 @@ public:
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
recomp::GameInput input = static_cast<recomp::GameInput>(inputs.at(0).Get<size_t>());
for (size_t binding_index = 0; binding_index < recomp::bindings_per_input; binding_index++) {
// TODO: Needs the active controller tab.
recomp::set_input_binding(0, input, binding_index, cur_device, recomp::InputField{});
// TODO: Needs the profile index.
recomp::set_input_binding(0, input, binding_index, recomp::InputField{});
}
model_handle.DirtyVariable("inputs");
graphics_model_handle.DirtyVariable("gfx_help__apply");
@@ -671,7 +671,7 @@ public:
constructor.BindEventCallback("reset_single_input_binding_to_default",
[](Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
// TODO: Needs the active controller tab.
// TODO: Needs the profile index.
recomp::GameInput input = static_cast<recomp::GameInput>(inputs.at(0).Get<size_t>());
banjo::reset_single_input_binding(0, cur_device, input);
model_handle.DirtyVariable("inputs");
@@ -710,8 +710,8 @@ public:
virtual int Size(void* ptr) override { return recomp::bindings_per_input; }
virtual Rml::DataVariable Child(void* ptr, const Rml::DataAddressEntry& address) override {
recomp::GameInput input = static_cast<recomp::GameInput>((uintptr_t)ptr);
// TODO: Needs the active controller tab.
return Rml::DataVariable{&input_field_definition_instance, &recomp::get_input_binding(0, input, address.index, cur_device)};
// TODO: Needs the profile index.
return Rml::DataVariable{&input_field_definition_instance, &recomp::get_input_binding(0, input, address.index)};
}
};
// Static instance of the InputField array variable definition to have a fixed pointer to return to RmlUi.
@@ -801,22 +801,22 @@ public:
});
constructor.BindFunc("nav_help__accept", [](Rml::Variant& out) {
// TODO: Needs the active controller tab.
// TODO: Needs the profile index.
if (cont_active) {
out = \
recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 0, recomp::InputDevice::Controller).to_string() + \
recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 1, recomp::InputDevice::Controller).to_string();
recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 0).to_string() + \
recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 1).to_string();
} else {
out = PF_KEYBOARD_ENTER;
}
});
constructor.BindFunc("nav_help__exit", [](Rml::Variant& out) {
// TODO: Needs the active controller tab.
// TODO: Needs the profile index.
if (cont_active) {
out = \
recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller).to_string() + \
recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller).to_string();
recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0).to_string() + \
recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1).to_string();
} else {
out = PF_KEYBOARD_ESCAPE;
}
+10 -10
View File
@@ -466,10 +466,10 @@ bool recompui::try_deque_event(SDL_Event& out) {
}
int cont_button_to_key(SDL_ControllerButtonEvent& button) {
// TODO: Needs the active controller tab.
// TODO: Needs the profile index.
// Configurable accept button in menu
auto menuAcceptBinding0 = recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 0, recomp::InputDevice::Controller);
auto menuAcceptBinding1 = recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 1, recomp::InputDevice::Controller);
auto menuAcceptBinding0 = recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 0);
auto menuAcceptBinding1 = recomp::get_input_binding(0, recomp::GameInput::ACCEPT_MENU, 1);
// note - magic number: 0 is InputType::None
if ((menuAcceptBinding0.input_type != recomp::InputType::None && button.button == menuAcceptBinding0.input_id) ||
(menuAcceptBinding1.input_type != recomp::InputType::None && button.button == menuAcceptBinding1.input_id)) {
@@ -477,8 +477,8 @@ int cont_button_to_key(SDL_ControllerButtonEvent& button) {
}
// Configurable apply button in menu
auto menuApplyBinding0 = recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0, recomp::InputDevice::Controller);
auto menuApplyBinding1 = recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 1, recomp::InputDevice::Controller);
auto menuApplyBinding0 = recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 0);
auto menuApplyBinding1 = recomp::get_input_binding(0, recomp::GameInput::APPLY_MENU, 1);
// note - magic number: 0 is InputType::None
if ((menuApplyBinding0.input_type != recomp::InputType::None && button.button == menuApplyBinding0.input_id) ||
(menuApplyBinding1.input_type != recomp::InputType::None && button.button == menuApplyBinding1.input_id)) {
@@ -486,8 +486,8 @@ int cont_button_to_key(SDL_ControllerButtonEvent& button) {
}
// Allows closing the menu
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0);
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1);
// note - magic number: 0 is InputType::None
if ((menuToggleBinding0.input_type != recomp::InputType::None && button.button == menuToggleBinding0.input_id) ||
(menuToggleBinding1.input_type != recomp::InputType::None && button.button == menuToggleBinding1.input_id)) {
@@ -716,9 +716,9 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
}
break;
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN:
// TODO: Needs the active controller tab.
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
// TODO: Needs the profile index.
auto menuToggleBinding0 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 0);
auto menuToggleBinding1 = recomp::get_input_binding(0, recomp::GameInput::TOGGLE_MENU, 1);
// note - magic number: 0 is InputType::None
if ((menuToggleBinding0.input_type != recomp::InputType::None && cur_event.cbutton.button == menuToggleBinding0.input_id) ||
(menuToggleBinding1.input_type != recomp::InputType::None && cur_event.cbutton.button == menuToggleBinding1.input_id)) {