// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include "android_config.h" #include "common/android/android_common.h" #include "common/android/id_cache.h" #include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" #include "input_common/drivers/android.h" #include "input_common/drivers/touch_screen.h" #include "input_common/drivers/virtual_amiibo.h" #include "input_common/drivers/virtual_gamepad.h" #include "native.h" ankerl::unordered_dense::map> map_profiles; bool IsHandheldOnly() { const auto npad_style_set = EmulationSession::GetInstance().System().HIDCore().GetSupportedStyleTag(); if (npad_style_set.fullkey == 1) { return false; } if (npad_style_set.handheld == 0) { return false; } return !Settings::IsDockedMode(); } std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) { return filename.replace_extension(); } bool IsProfileNameValid(std::string_view profile_name) { return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos; } bool ProfileExistsInFilesystem(std::string_view profile_name) { return Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir) / "input" / fmt::format("{}.ini", profile_name)); } bool ProfileExistsInMap(const std::string& profile_name) { return map_profiles.find(profile_name) != map_profiles.end(); } bool SaveProfile(const std::string& profile_name, std::size_t player_index) { if (!ProfileExistsInMap(profile_name)) { return false; } Settings::values.players.GetValue()[player_index].profile_name = profile_name; map_profiles[profile_name]->SaveAndroidControlPlayerValues(player_index); return true; } bool LoadProfile(std::string& profile_name, std::size_t player_index) { if (!ProfileExistsInMap(profile_name)) { return false; } if (!ProfileExistsInFilesystem(profile_name)) { map_profiles.erase(profile_name); return false; } LOG_INFO(Config, "Loading input profile `{}`", profile_name); Settings::values.players.GetValue()[player_index].profile_name = profile_name; map_profiles[profile_name]->ReadAndroidControlPlayerValues(player_index); return true; } void ApplyControllerConfig(size_t player_index, const std::function& apply) { auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); if (player_index == 0) { auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); handheld->EnableConfiguration(); player_one->EnableConfiguration(); apply(handheld); apply(player_one); handheld->DisableConfiguration(); player_one->DisableConfiguration(); handheld->SaveCurrentConfig(); player_one->SaveCurrentConfig(); } else { auto* controller = hid_core.GetEmulatedControllerByIndex(player_index); controller->EnableConfiguration(); apply(controller); controller->DisableConfiguration(); controller->SaveCurrentConfig(); } } std::vector GetSupportedStyles(int player_index) { auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); const auto npad_style_set = hid_core.GetSupportedStyleTag(); std::vector supported_indexes; if (npad_style_set.fullkey == 1) { supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::Fullkey)); } if (npad_style_set.joycon_dual == 1) { supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::JoyconDual)); } if (npad_style_set.joycon_left == 1) { supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::JoyconLeft)); } if (npad_style_set.joycon_right == 1) { supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::JoyconRight)); } if (player_index == 0 && npad_style_set.handheld == 1) { supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::Handheld)); } if (npad_style_set.gamecube == 1) { supported_indexes.push_back(static_cast(Core::HID::NpadStyleIndex::GameCube)); } return supported_indexes; } void ConnectController(size_t player_index, bool connected) { auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); ApplyControllerConfig(player_index, [&](Core::HID::EmulatedController* controller) { auto supported_styles = GetSupportedStyles(player_index); auto controller_style = controller->GetNpadStyleIndex(true); auto style = std::find(supported_styles.begin(), supported_styles.end(), static_cast(controller_style)); if (style == supported_styles.end() && !supported_styles.empty()) { controller->SetNpadStyleIndex( static_cast(supported_styles[0])); } }); if (player_index == 0) { auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); handheld->EnableConfiguration(); player_one->EnableConfiguration(); if (player_one->GetNpadStyleIndex(true) == Core::HID::NpadStyleIndex::Handheld) { if (connected) { handheld->Connect(); } else { handheld->Disconnect(); } player_one->Disconnect(); } else { if (connected) { player_one->Connect(); } else { player_one->Disconnect(); } handheld->Disconnect(); } handheld->DisableConfiguration(); player_one->DisableConfiguration(); handheld->SaveCurrentConfig(); player_one->SaveCurrentConfig(); } else { auto* controller = hid_core.GetEmulatedControllerByIndex(player_index); controller->EnableConfiguration(); if (connected) { controller->Connect(); } else { controller->Disconnect(); } controller->DisableConfiguration(); controller->SaveCurrentConfig(); } } extern "C" { jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isHandheldOnly(JNIEnv* env, jobject j_obj) { return IsHandheldOnly(); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadButtonEvent( JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jint j_button_id, jint j_action) { EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetButtonState( Common::Android::GetJString(env, j_guid), j_port, j_button_id, j_action != 0); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadAxisEvent( JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jint j_stick_id, jfloat j_value) { EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetAxisPosition( Common::Android::GetJString(env, j_guid), j_port, j_stick_id, j_value); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadMotionEvent( JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jlong j_delta_timestamp, jfloat j_x_gyro, jfloat j_y_gyro, jfloat j_z_gyro, jfloat j_x_accel, jfloat j_y_accel, jfloat j_z_accel) { EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetMotionState( Common::Android::GetJString(env, j_guid), j_port, j_delta_timestamp, j_x_gyro, j_y_gyro, j_z_gyro, j_x_accel, j_y_accel, j_z_accel); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onReadNfcTag(JNIEnv* env, jobject j_obj, jbyteArray j_data) { jboolean isCopy{false}; std::span data(reinterpret_cast(env->GetByteArrayElements(j_data, &isCopy)), static_cast(env->GetArrayLength(j_data))); if (EmulationSession::GetInstance().IsRunning()) { EmulationSession::GetInstance().GetInputSubsystem().GetVirtualAmiibo()->LoadAmiibo(data); } } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onRemoveNfcTag(JNIEnv* env, jobject j_obj) { if (EmulationSession::GetInstance().IsRunning()) { EmulationSession::GetInstance().GetInputSubsystem().GetVirtualAmiibo()->CloseAmiibo(); } } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchPressed(JNIEnv* env, jobject j_obj, jint j_id, jfloat j_x_axis, jfloat j_y_axis) { if (EmulationSession::GetInstance().IsRunning()) { EmulationSession::GetInstance().Window().OnTouchPressed(j_id, j_x_axis, j_y_axis); } } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchMoved(JNIEnv* env, jobject j_obj, jint j_id, jfloat j_x_axis, jfloat j_y_axis) { if (EmulationSession::GetInstance().IsRunning()) { EmulationSession::GetInstance().Window().OnTouchMoved(j_id, j_x_axis, j_y_axis); } } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchReleased(JNIEnv* env, jobject j_obj, jint j_id) { if (EmulationSession::GetInstance().IsRunning()) { EmulationSession::GetInstance().Window().OnTouchReleased(j_id); } } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onOverlayButtonEventImpl( JNIEnv* env, jobject j_obj, jint j_port, jint j_button_id, jint j_action) { if (EmulationSession::GetInstance().IsRunning()) { EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetButtonState( j_port, j_button_id, j_action == 1); } } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onOverlayJoystickEventImpl( JNIEnv* env, jobject j_obj, jint j_port, jint j_stick_id, jfloat j_x_axis, jfloat j_y_axis) { if (EmulationSession::GetInstance().IsRunning()) { EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetStickPosition( j_port, j_stick_id, j_x_axis, j_y_axis); } } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onDeviceMotionEvent( JNIEnv* env, jobject j_obj, jint j_port, jlong j_delta_timestamp, jfloat j_x_gyro, jfloat j_y_gyro, jfloat j_z_gyro, jfloat j_x_accel, jfloat j_y_accel, jfloat j_z_accel) { if (EmulationSession::GetInstance().IsRunning()) { EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetMotionState( j_port, j_delta_timestamp, j_x_gyro, j_y_gyro, j_z_gyro, j_x_accel, j_y_accel, j_z_accel); } } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_reloadInputDevices(JNIEnv* env, jobject j_obj) { EmulationSession::GetInstance().System().HIDCore().ReloadInputDevices(); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_registerController(JNIEnv* env, jobject j_obj, jobject j_device) { EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->RegisterController(j_device); } jobjectArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getInputDevices(JNIEnv* env, jobject j_obj) { auto devices = EmulationSession::GetInstance().GetInputSubsystem().GetInputDevices(); jobjectArray jdevices = env->NewObjectArray(devices.size(), Common::Android::GetStringClass(), Common::Android::ToJString(env, "")); for (size_t i = 0; i < devices.size(); ++i) { env->SetObjectArrayElement(jdevices, i, Common::Android::ToJString(env, devices[i].Serialize())); } return jdevices; } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadInputProfiles(JNIEnv* env, jobject j_obj) { map_profiles.clear(); const auto input_profile_loc = Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir) / "input"; if (Common::FS::IsDir(input_profile_loc)) { Common::FS::IterateDirEntries( input_profile_loc, [&](const std::filesystem::path& full_path) { const auto filename = full_path.filename(); const auto name_without_ext = Common::FS::PathToUTF8String(GetNameWithoutExtension(filename)); if (filename.extension() == ".ini" && IsProfileNameValid(name_without_ext)) { map_profiles.insert_or_assign( name_without_ext, std::make_unique( name_without_ext, Config::ConfigType::InputProfile)); } return true; }, Common::FS::DirEntryFilter::File); } } jobjectArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getInputProfileNames( JNIEnv* env, jobject j_obj) { std::vector profile_names; profile_names.reserve(map_profiles.size()); auto it = map_profiles.cbegin(); while (it != map_profiles.cend()) { const auto& [profile_name, config] = *it; if (!ProfileExistsInFilesystem(profile_name)) { it = map_profiles.erase(it); continue; } profile_names.push_back(profile_name); ++it; } std::stable_sort(profile_names.begin(), profile_names.end()); jobjectArray j_profile_names = env->NewObjectArray(profile_names.size(), Common::Android::GetStringClass(), Common::Android::ToJString(env, "")); for (size_t i = 0; i < profile_names.size(); ++i) { env->SetObjectArrayElement(j_profile_names, i, Common::Android::ToJString(env, profile_names[i])); } return j_profile_names; } jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isProfileNameValid(JNIEnv* env, jobject j_obj, jstring j_name) { return Common::Android::GetJString(env, j_name).find_first_of("<>:;\"/\\|,.!?*") == std::string::npos; } jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_createProfile(JNIEnv* env, jobject j_obj, jstring j_name, jint j_player_index) { auto profile_name = Common::Android::GetJString(env, j_name); if (ProfileExistsInMap(profile_name)) { return false; } map_profiles.insert_or_assign( profile_name, std::make_unique(profile_name, Config::ConfigType::InputProfile)); return SaveProfile(profile_name, j_player_index); } jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_deleteProfile(JNIEnv* env, jobject j_obj, jstring j_name, jint j_player_index) { auto profile_name = Common::Android::GetJString(env, j_name); if (!ProfileExistsInMap(profile_name)) { return false; } if (!ProfileExistsInFilesystem(profile_name) || Common::FS::RemoveFile(map_profiles[profile_name]->GetConfigFilePath())) { map_profiles.erase(profile_name); } Settings::values.players.GetValue()[j_player_index].profile_name = ""; return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name); } jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadProfile(JNIEnv* env, jobject j_obj, jstring j_name, jint j_player_index) { auto profile_name = Common::Android::GetJString(env, j_name); return LoadProfile(profile_name, j_player_index); } jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_saveProfile(JNIEnv* env, jobject j_obj, jstring j_name, jint j_player_index) { auto profile_name = Common::Android::GetJString(env, j_name); return SaveProfile(profile_name, j_player_index); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadPerGameConfiguration( JNIEnv* env, jobject j_obj, jint j_player_index, jint j_selected_index, jstring j_selected_profile_name) { static constexpr size_t HANDHELD_INDEX = 8; auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); Settings::values.players.SetGlobal(false); auto profile_name = Common::Android::GetJString(env, j_selected_profile_name); auto* emulated_controller = hid_core.GetEmulatedControllerByIndex(j_player_index); if (j_selected_index == 0) { Settings::values.players.GetValue()[j_player_index].profile_name = ""; if (j_player_index == 0) { Settings::values.players.GetValue()[HANDHELD_INDEX] = {}; } Settings::values.players.SetGlobal(true); emulated_controller->ReloadFromSettings(); return; } if (profile_name.empty()) { return; } auto& player = Settings::values.players.GetValue()[j_player_index]; auto& global_player = Settings::values.players.GetValue(true)[j_player_index]; player.profile_name = profile_name; global_player.profile_name = profile_name; // Read from the profile into the custom player settings LoadProfile(profile_name, j_player_index); // Make sure the controller is connected player.connected = true; emulated_controller->ReloadFromSettings(); if (j_player_index > 0) { return; } // Handle Handheld cases auto& handheld_player = Settings::values.players.GetValue()[HANDHELD_INDEX]; auto* handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); if (player.controller_type == Settings::ControllerType::Handheld) { handheld_player = player; } else { handheld_player = {}; } handheld_controller->ReloadFromSettings(); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_beginMapping(JNIEnv* env, jobject j_obj, jint jtype) { EmulationSession::GetInstance().GetInputSubsystem().BeginMapping( static_cast(jtype)); } jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getNextInput(JNIEnv* env, jobject j_obj) { return Common::Android::ToJString( env, EmulationSession::GetInstance().GetInputSubsystem().GetNextInput().Serialize()); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_stopMapping(JNIEnv* env, jobject j_obj) { EmulationSession::GetInstance().GetInputSubsystem().StopMapping(); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_updateMappingsWithDefaultImpl( JNIEnv* env, jobject j_obj, jint j_player_index, jstring j_device_params, jstring j_display_name) { auto& input_subsystem = EmulationSession::GetInstance().GetInputSubsystem(); // Clear all previous mappings for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { controller->SetButtonParam(button_id, {}); }); } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { controller->SetStickParam(analog_id, {}); }); } // Apply new mappings auto device = Common::ParamPackage(Common::Android::GetJString(env, j_device_params)); auto button_mappings = input_subsystem.GetButtonMappingForDevice(device); auto analog_mappings = input_subsystem.GetAnalogMappingForDevice(device); auto display_name = Common::Android::GetJString(env, j_display_name); for (const auto& button_mapping : button_mappings) { const std::size_t index = button_mapping.first; auto named_mapping = button_mapping.second; named_mapping.Set("display", display_name); ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { controller->SetButtonParam(index, named_mapping); }); } for (const auto& analog_mapping : analog_mappings) { const std::size_t index = analog_mapping.first; auto named_mapping = analog_mapping.second; named_mapping.Set("display", display_name); ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { controller->SetStickParam(index, named_mapping); }); } } jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getButtonParamImpl(JNIEnv* env, jobject j_obj, jint j_player_index, jint j_button) { return Common::Android::ToJString(env, EmulationSession::GetInstance() .System() .HIDCore() .GetEmulatedControllerByIndex(j_player_index) ->GetButtonParam(j_button) .Serialize()); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setButtonParamImpl( JNIEnv* env, jobject j_obj, jint j_player_index, jint j_button_id, jstring j_param) { ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { controller->SetButtonParam(j_button_id, Common::ParamPackage(Common::Android::GetJString(env, j_param))); }); } jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getStickParamImpl(JNIEnv* env, jobject j_obj, jint j_player_index, jint j_stick) { return Common::Android::ToJString(env, EmulationSession::GetInstance() .System() .HIDCore() .GetEmulatedControllerByIndex(j_player_index) ->GetStickParam(j_stick) .Serialize()); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setStickParamImpl( JNIEnv* env, jobject j_obj, jint j_player_index, jint j_stick_id, jstring j_param) { ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { controller->SetStickParam(j_stick_id, Common::ParamPackage(Common::Android::GetJString(env, j_param))); }); } jint Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getButtonNameImpl(JNIEnv* env, jobject j_obj, jstring j_param) { return static_cast(EmulationSession::GetInstance().GetInputSubsystem().GetButtonName( Common::ParamPackage(Common::Android::GetJString(env, j_param)))); } jintArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getSupportedStyleTagsImpl( JNIEnv* env, jobject j_obj, jint j_player_index) { auto supported_styles = GetSupportedStyles(j_player_index); jintArray j_supported_indexes = env->NewIntArray(supported_styles.size()); env->SetIntArrayRegion(j_supported_indexes, 0, supported_styles.size(), supported_styles.data()); return j_supported_indexes; } jint Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getStyleIndexImpl(JNIEnv* env, jobject j_obj, jint j_player_index) { return static_cast(EmulationSession::GetInstance() .System() .HIDCore() .GetEmulatedControllerByIndex(j_player_index) ->GetNpadStyleIndex(true)); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setStyleIndexImpl(JNIEnv* env, jobject j_obj, jint j_player_index, jint j_style_index) { auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); auto type = static_cast(j_style_index); ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { controller->SetNpadStyleIndex(type); }); if (j_player_index == 0) { auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); ConnectController(j_player_index, player_one->IsConnected(true) || handheld->IsConnected(true)); } } jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isControllerImpl(JNIEnv* env, jobject j_obj, jstring jparams) { return static_cast(EmulationSession::GetInstance().GetInputSubsystem().IsController( Common::ParamPackage(Common::Android::GetJString(env, jparams)))); } jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getIsConnected(JNIEnv* env, jobject j_obj, jint j_player_index) { auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); auto* controller = hid_core.GetEmulatedControllerByIndex(static_cast(j_player_index)); if (j_player_index == 0 && controller->GetNpadStyleIndex(true) == Core::HID::NpadStyleIndex::Handheld) { return hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld)->IsConnected(true); } return controller->IsConnected(true); } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_connectControllersImpl( JNIEnv* env, jobject j_obj, jbooleanArray j_connected) { jboolean isCopy = false; auto j_connected_array_size = env->GetArrayLength(j_connected); jboolean* j_connected_array = env->GetBooleanArrayElements(j_connected, &isCopy); for (int i = 0; i < j_connected_array_size; ++i) { ConnectController(i, j_connected_array[i]); } } void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_resetControllerMappings( JNIEnv* env, jobject j_obj, jint j_player_index) { // Clear all previous mappings for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { controller->SetButtonParam(button_id, {}); }); } for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { controller->SetStickParam(analog_id, {}); }); } } } // extern "C"