From 55748d7d1ab79ff12338cc00a9a0ffc266d0c7a8 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Tue, 16 Jul 2024 22:00:21 +0200 Subject: [PATCH] Artic Base: Add Artic Controller support (#195) --- .../features/settings/model/IntSetting.kt | 6 +- .../settings/ui/SettingsFragmentPresenter.kt | 10 + src/android/app/src/main/jni/config.cpp | 2 + src/android/app/src/main/jni/default_ini.h | 3 + .../app/src/main/res/values-es/strings.xml | 3 + .../app/src/main/res/values/strings.xml | 3 + src/citra_qt/configuration/config.cpp | 4 + .../configuration/configure_dialog.cpp | 2 +- .../configuration/configure_input.cpp | 12 +- src/citra_qt/configuration/configure_input.h | 3 +- src/citra_qt/configuration/configure_input.ui | 7 + src/common/settings.cpp | 1 + src/common/settings.h | 1 + src/core/hle/service/hid/hid.cpp | 472 +++++++++++++----- src/core/hle/service/hid/hid.h | 49 +- src/core/hle/service/ir/extra_hid.cpp | 47 +- src/core/hle/service/ir/extra_hid.h | 10 + src/core/hle/service/ir/ir_rst.cpp | 46 +- src/core/hle/service/ir/ir_rst.h | 10 + src/core/hle/service/ir/ir_user.cpp | 6 + src/core/hle/service/ir/ir_user.h | 6 + src/core/loader/artic.cpp | 8 + src/network/artic_base/artic_base_client.cpp | 109 +++- src/network/artic_base/artic_base_client.h | 79 ++- 24 files changed, 741 insertions(+), 158 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index 378c0eb05..f0726f665 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -41,7 +41,8 @@ enum class IntSetting( DEBUG_RENDERER("renderer_debug", Settings.SECTION_DEBUG, 0), TEXTURE_FILTER("texture_filter", Settings.SECTION_RENDERER, 0), USE_FRAME_LIMIT("use_frame_limit", Settings.SECTION_RENDERER, 1), - DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0); + DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0), + USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, 0); override var int: Int = defaultValue @@ -69,7 +70,8 @@ enum class IntSetting( DEBUG_RENDERER, CPU_JIT, ASYNC_CUSTOM_LOADING, - AUDIO_INPUT_TYPE + AUDIO_INPUT_TYPE, + USE_ARTIC_BASE_CONTROLLER ) fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index f949950bc..8f104f0b5 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -626,6 +626,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) val button = getInputObject(key) add(InputBindingSetting(button, Settings.hotkeyTitles[i])) } + add(HeaderSetting(R.string.miscellaneous)) + add( + SwitchSetting( + IntSetting.USE_ARTIC_BASE_CONTROLLER, + R.string.use_artic_base_controller, + R.string.use_artic_base_controller_desc, + IntSetting.USE_ARTIC_BASE_CONTROLLER.key, + IntSetting.USE_ARTIC_BASE_CONTROLLER.defaultValue + ) + ) } } diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index bafac3129..e565c8ec8 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -128,6 +128,8 @@ void Config::ReadValues() { static_cast(sdl2_config->GetInteger("Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT)); + ReadSetting("Controls", Settings::values.use_artic_base_controller); + // Core ReadSetting("Core", Settings::values.use_cpu_jit); ReadSetting("Core", Settings::values.cpu_clock_percentage); diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index 7bf546e03..acf42a73f 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -86,6 +86,9 @@ udp_input_port= # The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0) udp_pad_index= +# Use Artic Controller when connected to Artic Base Server. (Default 0) +use_artic_base_controller= + [Core] # Whether to use the Just-In-Time (JIT) compiler for CPU emulation # 0: Interpreter (slow), 1 (default): JIT (fast) diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index c4c9db23a..2b83bc36d 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -665,5 +665,8 @@ Se esperan fallos gráficos temporales cuando ésta esté activado. Introduce la dirección del servidor Artic Base Retrasa el hilo de dibujado del juego Retrasa el hilo de dibujado del juego cuando envía datos a la GPU. Ayuda con problemas de rendimiento en los (muy pocos) juegos de fps dinámicos. + Misceláneo + Usar Artic Controller cuando se está conectado a Artic Base Server + Usa los controles proporcionados por Artic Base Server cuando esté conectado a él en lugar del dispositivo de entrada configurado. diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index a01418870..aad33db15 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -700,5 +700,8 @@ Saving… Loading… No Quicksave available. + Miscellaneous + Use Artic Controller when connected to Artic Base Server + Use the controls provided by Artic Base Server when connected to it instead of the configured input device. diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 76ae4934d..886b66aa2 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -327,6 +327,8 @@ void Config::ReadCameraValues() { void Config::ReadControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); + ReadBasicSetting(Settings::values.use_artic_base_controller); + int num_touch_from_button_maps = qt_config->beginReadArray(QStringLiteral("touch_from_button_maps")); @@ -924,6 +926,8 @@ void Config::SaveCameraValues() { void Config::SaveControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); + WriteBasicSetting(Settings::values.use_artic_base_controller); + WriteSetting(QStringLiteral("profile"), Settings::values.current_input_profile_index, 0); qt_config->beginWriteArray(QStringLiteral("profiles")); for (std::size_t p = 0; p < Settings::values.input_profiles.size(); ++p) { diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index 896009352..dd00e932b 100644 --- a/src/citra_qt/configuration/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -29,7 +29,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor system{system_}, is_powered_on{system.IsPoweredOn()}, general_tab{std::make_unique(this)}, system_tab{std::make_unique(system, this)}, - input_tab{std::make_unique(this)}, + input_tab{std::make_unique(system, this)}, hotkeys_tab{std::make_unique(this)}, graphics_tab{ std::make_unique(gl_renderer, physical_devices, is_powered_on, this)}, diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index da98da0d0..158885ca5 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -16,6 +16,7 @@ #include "citra_qt/configuration/configure_input.h" #include "citra_qt/configuration/configure_motion_touch.h" #include "common/param_package.h" +#include "core/core.h" #include "ui_configure_input.h" const std::array @@ -145,8 +146,8 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string return QObject::tr("[unknown]"); } -ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique()), +ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) + : QWidget(parent), system(_system), ui(std::make_unique()), timeout_timer(std::make_unique()), poll_timer(std::make_unique()) { ui->setupUi(this); setFocusPolicy(Qt::ClickFocus); @@ -400,6 +401,9 @@ ConfigureInput::ConfigureInput(QWidget* parent) ConfigureInput::~ConfigureInput() = default; void ConfigureInput::ApplyConfiguration() { + + Settings::values.use_artic_base_controller = ui->use_artic_controller->isChecked(); + std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.current_input_profile.buttons.begin(), [](const Common::ParamPackage& param) { return param.Serialize(); }); @@ -444,6 +448,10 @@ QList ConfigureInput::GetUsedKeyboardKeys() { } void ConfigureInput::LoadConfiguration() { + + ui->use_artic_controller->setChecked(Settings::values.use_artic_base_controller.GetValue()); + ui->use_artic_controller->setEnabled(!system.IsPoweredOn()); + std::transform(Settings::values.current_input_profile.buttons.begin(), Settings::values.current_input_profile.buttons.end(), buttons_param.begin(), [](const std::string& str) { return Common::ParamPackage(str); }); diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h index fb00444c6..45a7a8329 100644 --- a/src/citra_qt/configuration/configure_input.h +++ b/src/citra_qt/configuration/configure_input.h @@ -30,7 +30,7 @@ class ConfigureInput : public QWidget { Q_OBJECT public: - explicit ConfigureInput(QWidget* parent = nullptr); + explicit ConfigureInput(Core::System& system, QWidget* parent = nullptr); ~ConfigureInput() override; /// Save all button configurations to settings file @@ -50,6 +50,7 @@ signals: void InputKeysChanged(QList new_key_list); private: + Core::System& system; std::unique_ptr ui; std::unique_ptr timeout_timer; diff --git a/src/citra_qt/configuration/configure_input.ui b/src/citra_qt/configuration/configure_input.ui index 2d199e667..7168b7190 100644 --- a/src/citra_qt/configuration/configure_input.ui +++ b/src/citra_qt/configuration/configure_input.ui @@ -841,6 +841,13 @@ + + + + Use Artic Controller when connected to Artic Base Server + + + diff --git a/src/common/settings.cpp b/src/common/settings.cpp index e2f432a71..69cc6f4ef 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -83,6 +83,7 @@ void LogSettings() { LOG_INFO(Config, "Citra Configuration:"); log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue()); log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue()); + log_setting("Controller_UseArticController", values.use_artic_base_controller.GetValue()); log_setting("Renderer_UseGLES", values.use_gles.GetValue()); log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue())); log_setting("Renderer_AsyncShaders", values.async_shader_compilation.GetValue()); diff --git a/src/common/settings.h b/src/common/settings.h index b7ac91a0b..b61957a6a 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -425,6 +425,7 @@ struct Values { int current_input_profile_index; ///< The current input profile index std::vector input_profiles; ///< The list of input profiles std::vector touch_from_button_maps; + Setting use_artic_base_controller{false, "use_artic_base_controller"}; SwitchableSetting enable_gamemode{true, "enable_gamemode"}; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 9013fd4b1..1d11acd20 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -20,6 +20,8 @@ #include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid_spvr.h" #include "core/hle/service/hid/hid_user.h" +#include "core/hle/service/ir/ir_rst.h" +#include "core/hle/service/ir/ir_user.h" #include "core/hle/service/service.h" #include "core/movie.h" @@ -53,6 +55,32 @@ void Module::serialize(Archive& ar, const unsigned int file_version) { } SERIALIZE_IMPL(Module) +ArticBaseController::ArticBaseController( + const std::shared_ptr& client) { + + udp_stream = + client->NewUDPStream("ArticController", sizeof(ArticBaseController::ControllerData), + std::chrono::milliseconds(2)); + if (udp_stream.get()) { + udp_stream->Start(); + } +} + +ArticBaseController::ControllerData ArticBaseController::GetControllerData() { + + if (udp_stream.get() && udp_stream->IsReady()) { + auto data = udp_stream->GetLastPacket(); + if (data.size() == sizeof(ControllerData)) { + u32 id = *reinterpret_cast(data.data()); + if ((id - last_packet_id) < (std::numeric_limits::max() / 2)) { + last_packet_id = id; + memcpy(&last_controller_data, data.data(), data.size()); + } + } + } + return last_controller_data; +} + constexpr float accelerometer_coef = 512.0f; // measured from hw test result constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call @@ -111,96 +139,151 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { LoadInputDevices(); using namespace Settings::NativeButton; - state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); - state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); - state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); - state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); - state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); - state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); - state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); - state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); - state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); - state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); - state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); - state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); - state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus()); - state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus()); - // Get current circle pad position and update circle pad direction - float circle_pad_x_f, circle_pad_y_f; - std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); + if (artic_controller.get() && artic_controller->IsReady()) { + constexpr u32 HID_VALID_KEYS = 0xF0003FFF; + constexpr u32 LIBCTRU_TOUCH_KEY = (1 << 20); - // xperia64: 0x9A seems to be the calibrated limit of the circle pad - // Verified by using Input Redirector with very large-value digital inputs - // on the circle pad and calibrating using the system settings application - constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position + ArticBaseController::ControllerData data = artic_controller->GetControllerData(); - // These are rounded rather than truncated on actual hardware - s16 circle_pad_new_x = static_cast(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS)); - s16 circle_pad_new_y = static_cast(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS)); - s16 circle_pad_x = - (circle_pad_new_x + std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) / - CIRCLE_PAD_AVERAGING; - s16 circle_pad_y = - (circle_pad_new_y + std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) / - CIRCLE_PAD_AVERAGING; - circle_pad_old_x.erase(circle_pad_old_x.begin()); - circle_pad_old_x.push_back(circle_pad_new_x); - circle_pad_old_y.erase(circle_pad_old_y.begin()); - circle_pad_old_y.push_back(circle_pad_new_y); + state.hex = data.pad & HID_VALID_KEYS; - system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y); + s16 circle_pad_x = data.c_pad_x; + s16 circle_pad_y = data.c_pad_y; - const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); - state.circle_up.Assign(direction.up); - state.circle_down.Assign(direction.down); - state.circle_left.Assign(direction.left); - state.circle_right.Assign(direction.right); + system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y); - mem->pad.current_state.hex = state.hex; - mem->pad.index = next_pad_index; - next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); + mem->pad.current_state.hex = state.hex; + mem->pad.index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); - // Get the previous Pad state - u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); - PadState old_state = mem->pad.entries[last_entry_index].current_state; + // Get the previous Pad state + u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); + PadState old_state = mem->pad.entries[last_entry_index].current_state; - // Compute bitmask with 1s for bits different from the old state - PadState changed = {{(state.hex ^ old_state.hex)}}; + // Compute bitmask with 1s for bits different from the old state + PadState changed = {{(state.hex ^ old_state.hex)}}; - // Get the current Pad entry - PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; + // Get the current Pad entry + PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; - // Update entry properties - pad_entry.current_state.hex = state.hex; - pad_entry.delta_additions.hex = changed.hex & state.hex; - pad_entry.delta_removals.hex = changed.hex & old_state.hex; - pad_entry.circle_pad_x = circle_pad_x; - pad_entry.circle_pad_y = circle_pad_y; + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.circle_pad_x = circle_pad_x; + pad_entry.circle_pad_y = circle_pad_y; - // If we just updated index 0, provide a new timestamp - if (mem->pad.index == 0) { - mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; - mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks(); + // If we just updated index 0, provide a new timestamp + if (mem->pad.index == 0) { + mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; + mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks(); + } + + mem->touch.index = next_touch_index; + next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); + + // Get the current touch entry + TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; + bool pressed = (data.pad & LIBCTRU_TOUCH_KEY) != 0; + + touch_entry.x = static_cast(data.touch_x); + touch_entry.y = static_cast(data.touch_y); + touch_entry.valid.Assign(pressed ? 1 : 0); + + system.Movie().HandleTouchStatus(touch_entry); + } else { + state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); + state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); + state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); + state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); + state.right.Assign(buttons[Right - BUTTON_HID_BEGIN]->GetStatus()); + state.left.Assign(buttons[Left - BUTTON_HID_BEGIN]->GetStatus()); + state.up.Assign(buttons[Up - BUTTON_HID_BEGIN]->GetStatus()); + state.down.Assign(buttons[Down - BUTTON_HID_BEGIN]->GetStatus()); + state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); + state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); + state.start.Assign(buttons[Start - BUTTON_HID_BEGIN]->GetStatus()); + state.select.Assign(buttons[Select - BUTTON_HID_BEGIN]->GetStatus()); + state.debug.Assign(buttons[Debug - BUTTON_HID_BEGIN]->GetStatus()); + state.gpio14.Assign(buttons[Gpio14 - BUTTON_HID_BEGIN]->GetStatus()); + + // Get current circle pad position and update circle pad direction + float circle_pad_x_f, circle_pad_y_f; + std::tie(circle_pad_x_f, circle_pad_y_f) = circle_pad->GetStatus(); + + // xperia64: 0x9A seems to be the calibrated limit of the circle pad + // Verified by using Input Redirector with very large-value digital inputs + // on the circle pad and calibrating using the system settings application + constexpr int MAX_CIRCLEPAD_POS = 0x9A; // Max value for a circle pad position + + // These are rounded rather than truncated on actual hardware + s16 circle_pad_new_x = static_cast(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS)); + s16 circle_pad_new_y = static_cast(std::roundf(circle_pad_y_f * MAX_CIRCLEPAD_POS)); + s16 circle_pad_x = (circle_pad_new_x + + std::accumulate(circle_pad_old_x.begin(), circle_pad_old_x.end(), 0)) / + CIRCLE_PAD_AVERAGING; + s16 circle_pad_y = (circle_pad_new_y + + std::accumulate(circle_pad_old_y.begin(), circle_pad_old_y.end(), 0)) / + CIRCLE_PAD_AVERAGING; + circle_pad_old_x.erase(circle_pad_old_x.begin()); + circle_pad_old_x.push_back(circle_pad_new_x); + circle_pad_old_y.erase(circle_pad_old_y.begin()); + circle_pad_old_y.push_back(circle_pad_new_y); + + system.Movie().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y); + + const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); + state.circle_up.Assign(direction.up); + state.circle_down.Assign(direction.down); + state.circle_left.Assign(direction.left); + state.circle_right.Assign(direction.right); + + mem->pad.current_state.hex = state.hex; + mem->pad.index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->pad.entries.size(); + + // Get the previous Pad state + u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); + PadState old_state = mem->pad.entries[last_entry_index].current_state; + + // Compute bitmask with 1s for bits different from the old state + PadState changed = {{(state.hex ^ old_state.hex)}}; + + // Get the current Pad entry + PadDataEntry& pad_entry = mem->pad.entries[mem->pad.index]; + + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.circle_pad_x = circle_pad_x; + pad_entry.circle_pad_y = circle_pad_y; + + // If we just updated index 0, provide a new timestamp + if (mem->pad.index == 0) { + mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; + mem->pad.index_reset_ticks = (s64)system.CoreTiming().GetTicks(); + } + + mem->touch.index = next_touch_index; + next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); + + // Get the current touch entry + TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; + bool pressed = false; + float x, y; + std::tie(x, y, pressed) = touch_device->GetStatus(); + if (!pressed && touch_btn_device) { + std::tie(x, y, pressed) = touch_btn_device->GetStatus(); + } + touch_entry.x = static_cast(x * Core::kScreenBottomWidth); + touch_entry.y = static_cast(y * Core::kScreenBottomHeight); + touch_entry.valid.Assign(pressed ? 1 : 0); + + system.Movie().HandleTouchStatus(touch_entry); } - mem->touch.index = next_touch_index; - next_touch_index = (next_touch_index + 1) % mem->touch.entries.size(); - - // Get the current touch entry - TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; - bool pressed = false; - float x, y; - std::tie(x, y, pressed) = touch_device->GetStatus(); - if (!pressed && touch_btn_device) { - std::tie(x, y, pressed) = touch_btn_device->GetStatus(); - } - touch_entry.x = static_cast(x * Core::kScreenBottomWidth); - touch_entry.y = static_cast(y * Core::kScreenBottomHeight); - touch_entry.valid.Assign(pressed ? 1 : 0); - - system.Movie().HandleTouchStatus(touch_entry); - // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being // converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8). @@ -231,19 +314,27 @@ void Module::UpdateAccelerometerCallback(std::uintptr_t user_data, s64 cycles_la mem->accelerometer.index = next_accelerometer_index; next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); - Common::Vec3 accel; - std::tie(accel, std::ignore) = motion_device->GetStatus(); - accel *= accelerometer_coef; - // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback - // The time stretch formula should be like - // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity - AccelerometerDataEntry& accelerometer_entry = mem->accelerometer.entries[mem->accelerometer.index]; - accelerometer_entry.x = static_cast(accel.x); - accelerometer_entry.y = static_cast(accel.y); - accelerometer_entry.z = static_cast(accel.z); + if (artic_controller.get() && artic_controller->IsReady()) { + ArticBaseController::ControllerData data = artic_controller->GetControllerData(); + + accelerometer_entry.x = data.accel_x; + accelerometer_entry.y = data.accel_y; + accelerometer_entry.z = data.accel_z; + } else { + Common::Vec3 accel; + std::tie(accel, std::ignore) = motion_device->GetStatus(); + accel *= accelerometer_coef; + // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback + // The time stretch formula should be like + // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity + + accelerometer_entry.x = static_cast(accel.x); + accelerometer_entry.y = static_cast(accel.y); + accelerometer_entry.z = static_cast(accel.z); + } system.Movie().HandleAccelerometerStatus(accelerometer_entry); @@ -278,13 +369,21 @@ void Module::UpdateGyroscopeCallback(std::uintptr_t user_data, s64 cycles_late) GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; - Common::Vec3 gyro; - std::tie(std::ignore, gyro) = motion_device->GetStatus(); - double stretch = system.perf_stats->GetLastFrameTimeScale(); - gyro *= gyroscope_coef * static_cast(stretch); - gyroscope_entry.x = static_cast(gyro.x); - gyroscope_entry.y = static_cast(gyro.y); - gyroscope_entry.z = static_cast(gyro.z); + if (artic_controller.get() && artic_controller->IsReady()) { + ArticBaseController::ControllerData data = artic_controller->GetControllerData(); + + gyroscope_entry.x = data.gyro_x; + gyroscope_entry.y = data.gyro_y; + gyroscope_entry.z = data.gyro_z; + } else { + Common::Vec3 gyro; + std::tie(std::ignore, gyro) = motion_device->GetStatus(); + double stretch = system.perf_stats->GetLastFrameTimeScale(); + gyro *= gyroscope_coef * static_cast(stretch); + gyroscope_entry.x = static_cast(gyro.x); + gyroscope_entry.y = static_cast(gyro.y); + gyroscope_entry.z = static_cast(gyro.z); + } system.Movie().HandleGyroscopeStatus(gyroscope_entry); @@ -316,6 +415,23 @@ void Module::Interface::GetIPCHandles(Kernel::HLERequestContext& ctx) { void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_EnableAccelerometer"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + ++hid->enable_accelerometer_count; // Schedules the accelerometer update event if the accelerometer was just enabled @@ -324,15 +440,29 @@ void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) { hid->accelerometer_update_event); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_DisableAccelerometer"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + --hid->enable_accelerometer_count; // Unschedules the accelerometer update event if the accelerometer was just disabled @@ -340,15 +470,29 @@ void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) { hid->system.CoreTiming().UnscheduleEvent(hid->accelerometer_update_event, 0); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_EnableGyroscope"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + ++hid->enable_gyroscope_count; // Schedules the gyroscope update event if the gyroscope was just enabled @@ -356,15 +500,29 @@ void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) { hid->system.CoreTiming().ScheduleEvent(gyroscope_update_ticks, hid->gyroscope_update_event); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_DisableGyroscope"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + } else { + rb.Push(Result{static_cast(resp->GetMethodResult())}); + } + } else { + rb.Push(ResultSuccess); + } + --hid->enable_gyroscope_count; // Unschedules the gyroscope update event if the gyroscope was just disabled @@ -372,9 +530,6 @@ void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) { hid->system.CoreTiming().UnscheduleEvent(hid->gyroscope_update_event, 0); } - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - LOG_DEBUG(Service_HID, "called"); } @@ -382,25 +537,90 @@ void Module::Interface::GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestCon IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - rb.Push(ResultSuccess); - rb.Push(gyroscope_coef); + + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + auto req = artic_client->NewRequest("HIDUSER_GetGyroRawToDpsCoef"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + rb.Push(0.f); + return; + } + + Result res = Result{static_cast(resp->GetMethodResult())}; + if (res.IsError()) { + rb.Push(res); + rb.Push(0.f); + return; + } + + auto coef = resp->GetResponseFloat(0); + if (!coef.has_value()) { + rb.Push(ResultUnknown); + rb.Push(0.f); + return; + } + + rb.Push(res); + rb.Push(*coef); + } else { + rb.Push(ResultSuccess); + rb.Push(gyroscope_coef); + } } void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); IPC::RequestBuilder rb = rp.MakeBuilder(6, 0); - rb.Push(ResultSuccess); - const s16 param_unit = 6700; // an approximate value taken from hw - GyroscopeCalibrateParam param = { - {0, param_unit, -param_unit}, - {0, param_unit, -param_unit}, - {0, param_unit, -param_unit}, - }; - rb.PushRaw(param); + auto& artic_client = GetModule()->artic_client; + if (artic_client.get()) { + GyroscopeCalibrateParam param; - LOG_WARNING(Service_HID, "(STUBBED) called"); + auto req = artic_client->NewRequest("HIDUSER_GetGyroCalibrateParam"); + + auto resp = artic_client->Send(req); + + if (!resp.has_value()) { + rb.Push(ResultUnknown); + rb.PushRaw(param); + return; + } + + Result res = Result{static_cast(resp->GetMethodResult())}; + if (res.IsError()) { + rb.Push(res); + rb.PushRaw(param); + return; + } + + auto param_buf = resp->GetResponseBuffer(0); + if (!param_buf.has_value() || param_buf->second != sizeof(param)) { + rb.Push(ResultUnknown); + rb.PushRaw(param); + return; + } + memcpy(¶m, param_buf->first, sizeof(param)); + + rb.Push(res); + rb.PushRaw(param); + } else { + rb.Push(ResultSuccess); + + const s16 param_unit = 6700; // an approximate value taken from hw + GyroscopeCalibrateParam param = { + {0, param_unit, -param_unit}, + {0, param_unit, -param_unit}, + {0, param_unit, -param_unit}, + }; + rb.PushRaw(param); + + LOG_WARNING(Service_HID, "(STUBBED) called"); + } } void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) { @@ -454,6 +674,24 @@ Module::Module(Core::System& system) : system(system) { timing.ScheduleEvent(pad_update_ticks, pad_update_event); } +void Module::UseArticClient(const std::shared_ptr& client) { + artic_client = client; + artic_controller = std::make_shared(client); + if (!artic_controller->IsCreated()) { + artic_controller.reset(); + } else { + auto ir_user = system.ServiceManager().GetService("ir:USER"); + if (ir_user.get()) { + ir_user->UseArticController(artic_controller); + } + + auto ir_rst = system.ServiceManager().GetService("ir:rst"); + if (ir_rst.get()) { + ir_rst->UseArticController(artic_controller); + } + } +} + void Module::ReloadInputDevices() { is_device_reload_pending.store(true); } diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 609cb9276..79713ca69 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -14,13 +14,11 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/settings.h" +#include "core/core.h" #include "core/core_timing.h" #include "core/frontend/input.h" #include "core/hle/service/service.h" - -namespace Core { -class System; -} +#include "network/artic_base/artic_base_client.h" namespace Kernel { class Event; @@ -199,6 +197,44 @@ struct DirectionState { /// Translates analog stick axes to directions. This is exposed for ir_rst module to use. DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y); +class ArticBaseController { +public: + struct ControllerData { + u32 index{}; + u32 pad{}; + s16 c_pad_x{}; + s16 c_pad_y{}; + u16 touch_x{}; + u16 touch_y{}; + s16 c_stick_x{}; + s16 c_stick_y{}; + s16 accel_x{}; + s16 accel_y{}; + s16 accel_z{}; + s16 gyro_x{}; + s16 gyro_y{}; + s16 gyro_z{}; + }; + static_assert(sizeof(ControllerData) == 0x20, "Incorrect ControllerData size"); + + ArticBaseController(const std::shared_ptr& client); + + bool IsCreated() { + return udp_stream.get(); + } + + bool IsReady() { + return udp_stream.get() ? udp_stream->IsReady() : false; + } + + ControllerData GetControllerData(); + +private: + std::shared_ptr udp_stream; + u32 last_packet_id{}; + ControllerData last_controller_data{}; +}; + class Module final { public: explicit Module(Core::System& system); @@ -296,6 +332,8 @@ public: std::shared_ptr hid; }; + void UseArticClient(const std::shared_ptr& client); + void ReloadInputDevices(); const PadState& GetState() const; @@ -355,6 +393,9 @@ private: std::unique_ptr touch_device; std::unique_ptr touch_btn_device; + std::shared_ptr artic_controller; + std::shared_ptr artic_client; + template void serialize(Archive& ar, const unsigned int); friend class boost::serialization::access; diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp index 2483a27f0..e5dc486cb 100644 --- a/src/core/hle/service/ir/extra_hid.cpp +++ b/src/core/hle/service/ir/extra_hid.cpp @@ -6,6 +6,7 @@ #include "common/alignment.h" #include "common/settings.h" #include "core/core_timing.h" +#include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/extra_hid.h" #include "core/movie.h" @@ -230,23 +231,47 @@ void ExtraHID::SendHIDStatus() { if (is_device_reload_pending.exchange(false)) LoadInputDevices(); + constexpr u32 ZL_BUTTON = (1 << 14); + constexpr u32 ZR_BUTTON = (1 << 15); + constexpr int C_STICK_CENTER = 0x800; // TODO(wwylele): this value is not accurately measured. We currently assume that the axis can // take values in the whole range of a 12-bit integer. constexpr int C_STICK_RADIUS = 0x7FF; - float x, y; - std::tie(x, y) = c_stick->GetStatus(); - ExtraHIDResponse response{}; - response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); - response.c_stick.c_stick_x.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * x)); - response.c_stick.c_stick_y.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * y)); - response.buttons.battery_level.Assign(0x1F); - response.buttons.zl_not_held.Assign(!zl->GetStatus()); - response.buttons.zr_not_held.Assign(!zr->GetStatus()); - response.buttons.r_not_held.Assign(1); - response.unknown = 0; + + if (artic_controller.get() && artic_controller->IsReady()) { + Service::HID::ArticBaseController::ControllerData data = + artic_controller->GetControllerData(); + + constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius + + response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); + response.c_stick.c_stick_x.Assign(static_cast( + (static_cast(data.c_stick_x) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS + + C_STICK_CENTER)); + response.c_stick.c_stick_y.Assign(static_cast( + (static_cast(data.c_stick_y) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS + + C_STICK_CENTER)); + response.buttons.battery_level.Assign(0x1F); + response.buttons.zl_not_held.Assign((data.pad & ZL_BUTTON) == 0); + response.buttons.zr_not_held.Assign((data.pad & ZR_BUTTON) == 0); + response.buttons.r_not_held.Assign(1); + response.unknown = 0; + } else { + float x, y; + std::tie(x, y) = c_stick->GetStatus(); + + response.c_stick.header.Assign(static_cast(ResponseID::PollHID)); + response.c_stick.c_stick_x.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * x)); + response.c_stick.c_stick_y.Assign(static_cast(C_STICK_CENTER + C_STICK_RADIUS * y)); + response.buttons.battery_level.Assign(0x1F); + response.buttons.zl_not_held.Assign(!zl->GetStatus()); + response.buttons.zr_not_held.Assign(!zr->GetStatus()); + response.buttons.r_not_held.Assign(1); + response.unknown = 0; + } movie.HandleExtraHidResponse(response); diff --git a/src/core/hle/service/ir/extra_hid.h b/src/core/hle/service/ir/extra_hid.h index 5dc36b4cd..4cceec580 100644 --- a/src/core/hle/service/ir/extra_hid.h +++ b/src/core/hle/service/ir/extra_hid.h @@ -19,6 +19,10 @@ class Timing; class Movie; } // namespace Core +namespace Service::HID { +class ArticBaseController; +}; + namespace Service::IR { struct ExtraHIDResponse { @@ -54,6 +58,10 @@ public: /// Requests input devices reload from current settings. Called when the input settings change. void RequestInputDevicesReload(); + void UseArticController(const std::shared_ptr& ac) { + artic_controller = ac; + } + private: void SendHIDStatus(); void HandleConfigureHIDPollingRequest(std::span request); @@ -70,6 +78,8 @@ private: std::unique_ptr c_stick; std::atomic is_device_reload_pending; + std::shared_ptr artic_controller = nullptr; + template void serialize(Archive& ar, const unsigned int) { ar& hid_period; diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 2e2cd5b94..be7408619 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -72,25 +72,41 @@ void IR_RST::UpdateCallback(std::uintptr_t user_data, s64 cycles_late) { if (is_device_reload_pending.exchange(false)) LoadInputDevices(); + constexpr u32 VALID_EXTRAHID_KEYS = 0xF00C000; + PadState state; - state.zl.Assign(zl_button->GetStatus()); - state.zr.Assign(zr_button->GetStatus()); + s16 c_stick_x, c_stick_y; - // Get current c-stick position and update c-stick direction - float c_stick_x_f, c_stick_y_f; - std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); - constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius - s16 c_stick_x = static_cast(c_stick_x_f * MAX_CSTICK_RADIUS); - s16 c_stick_y = static_cast(c_stick_y_f * MAX_CSTICK_RADIUS); + if (artic_controller.get() && artic_controller->IsReady()) { + Service::HID::ArticBaseController::ControllerData data = + artic_controller->GetControllerData(); - system.Movie().HandleIrRst(state, c_stick_x, c_stick_y); + state.hex = data.pad & VALID_EXTRAHID_KEYS; - if (!raw_c_stick) { - const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); - state.c_stick_up.Assign(direction.up); - state.c_stick_down.Assign(direction.down); - state.c_stick_left.Assign(direction.left); - state.c_stick_right.Assign(direction.right); + c_stick_x = data.c_stick_x; + c_stick_y = data.c_stick_y; + + system.Movie().HandleIrRst(state, c_stick_x, c_stick_y); + } else { + state.zl.Assign(zl_button->GetStatus()); + state.zr.Assign(zr_button->GetStatus()); + + // Get current c-stick position and update c-stick direction + float c_stick_x_f, c_stick_y_f; + std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); + constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius + c_stick_x = static_cast(c_stick_x_f * MAX_CSTICK_RADIUS); + c_stick_y = static_cast(c_stick_y_f * MAX_CSTICK_RADIUS); + + system.Movie().HandleIrRst(state, c_stick_x, c_stick_y); + + if (!raw_c_stick) { + const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); + state.c_stick_up.Assign(direction.up); + state.c_stick_down.Assign(direction.down); + state.c_stick_left.Assign(direction.left); + state.c_stick_right.Assign(direction.right); + } } // TODO (wwylele): implement raw C-stick data for raw_c_stick = true diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index 2514ab6f9..d02d34aa3 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -21,6 +21,10 @@ namespace Core { struct TimingEventType; }; +namespace Service::HID { +class ArticBaseController; +}; + namespace Service::IR { union PadState { @@ -42,6 +46,10 @@ public: ~IR_RST(); void ReloadInputDevices(); + void UseArticController(const std::shared_ptr& ac) { + artic_controller = ac; + } + private: /** * GetHandles service function @@ -88,6 +96,8 @@ private: bool raw_c_stick{false}; int update_period{0}; + std::shared_ptr artic_controller = nullptr; + template void serialize(Archive& ar, const unsigned int); friend class boost::serialization::access; diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index 597fdc4b6..d6ced4b4c 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -480,6 +480,12 @@ void IR_USER::ReloadInputDevices() { extra_hid->RequestInputDevicesReload(); } +void IR_USER::UseArticController(const std::shared_ptr& ac) { + if (extra_hid.get()) { + extra_hid->UseArticController(ac); + } +} + IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {} IRDevice::~IRDevice() = default; diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h index e724e9dc5..fac49edfa 100644 --- a/src/core/hle/service/ir/ir_user.h +++ b/src/core/hle/service/ir/ir_user.h @@ -14,6 +14,10 @@ class Event; class SharedMemory; } // namespace Kernel +namespace Service::HID { +class ArticBaseController; +}; + namespace Service::IR { class BufferManager; @@ -57,6 +61,8 @@ public: void ReloadInputDevices(); + void UseArticController(const std::shared_ptr& ac); + private: /** * InitializeIrNopShared service function diff --git a/src/core/loader/artic.cpp b/src/core/loader/artic.cpp index d4ba70060..80365ed6e 100644 --- a/src/core/loader/artic.cpp +++ b/src/core/loader/artic.cpp @@ -27,6 +27,7 @@ #include "core/hle/service/cfg/cfg_u.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/fs_user.h" +#include "core/hle/service/hid/hid_user.h" #include "core/loader/artic.h" #include "core/loader/smdh.h" #include "core/memory.h" @@ -361,6 +362,13 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr& process) { amapp->UseArticClient(client); } + if (Settings::values.use_artic_base_controller.GetValue()) { + auto hid_user = system.ServiceManager().GetService("hid:USER"); + if (hid_user.get()) { + hid_user->GetModule()->UseArticClient(client); + } + } + ParseRegionLockoutInfo(ncch_program_id); return ResultStatus::Success; diff --git a/src/network/artic_base/artic_base_client.cpp b/src/network/artic_base/artic_base_client.cpp index fd5974d11..edc587f35 100644 --- a/src/network/artic_base/artic_base_client.cpp +++ b/src/network/artic_base/artic_base_client.cpp @@ -121,6 +121,81 @@ Client::Request::Request(u32 request_id, const std::string& method, size_t max_p std::min(request_packet.method.size(), method.size())); } +void Client::UDPStream::Start() { + thread_run = true; + handle_thread = std::thread(&Client::UDPStream::Handle, this); +} + +void Client::UDPStream::Handle() { + struct sockaddr_in* servaddr = reinterpret_cast(serv_sockaddr_in.data()); + socklen_t serv_sockaddr_len = static_cast(serv_sockaddr_in.size()); + memcpy(servaddr, client.GetServerAddr().data(), client.GetServerAddr().size()); + servaddr->sin_port = htons(port); + + main_socket = ::socket(AF_INET, SOCK_DGRAM, 0); + if (main_socket == static_cast(-1) || !thread_run) { + LOG_ERROR(Network, "Failed to create socket"); + return; + } + + if (!SetNonBlock(main_socket, true) || !thread_run) { + closesocket(main_socket); + LOG_ERROR(Network, "Cannot set non-blocking socket mode"); + return; + } + + // Limit receive buffer so that packets don't get qeued and are dropped instead. + int buffer_size_int = static_cast(buffer_size); + if (::setsockopt(main_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&buffer_size_int), + sizeof(buffer_size_int)) || + !thread_run) { + closesocket(main_socket); + LOG_ERROR(Network, "Cannot change receive buffer size"); + return; + } + + // Send data to server so that it knows client address. + char zero = '\0'; + int send_res = + ::sendto(main_socket, &zero, sizeof(char), 0, + reinterpret_cast(serv_sockaddr_in.data()), serv_sockaddr_len); + if (send_res < 0 || !thread_run) { + closesocket(main_socket); + LOG_ERROR(Network, "Cannot send data to socket"); + return; + } + + ready = true; + std::vector buffer(buffer_size); + while (thread_run) { + std::chrono::steady_clock::time_point before = std::chrono::steady_clock::now(); + + int packet_size = ::recvfrom( + main_socket, reinterpret_cast(buffer.data()), static_cast(buffer.size()), 0, + reinterpret_cast(serv_sockaddr_in.data()), &serv_sockaddr_len); + if (packet_size > 0) { + if (client.report_traffic_callback) { + client.report_traffic_callback(packet_size); + } + + buffer.resize(packet_size); + { + std::scoped_lock l(current_buffer_mutex); + current_buffer = buffer; + } + } + + auto elapsed = std::chrono::steady_clock::now() - before; + + std::unique_lock lk(thread_cv_mutex); + thread_cv.wait_for(lk, elapsed < read_interval ? (read_interval - elapsed) + : std::chrono::microseconds(50)); + } + ready = false; + + closesocket(main_socket); +} + Client::~Client() { StopImpl(false); @@ -182,6 +257,7 @@ bool Client::Connect() { servaddr.sin_addr.s_addr = ((struct sockaddr_in*)(addrinfo->ai_addr))->sin_addr.s_addr; servaddr.sin_port = htons(port); freeaddrinfo(addrinfo); + memcpy(last_sockaddr_in.data(), &servaddr, last_sockaddr_in.size()); if (!ConnectWithTimeout(main_socket, &servaddr, sizeof(servaddr), 10)) { closesocket(main_socket); @@ -249,15 +325,15 @@ bool Client::Connect() { std::string str_port; std::stringstream ss_port(worker_ports.value()); while (std::getline(ss_port, str_port, ',')) { - int port = str_to_int(str_port); - if (port < 0 || port > static_cast(USHRT_MAX)) { + int port_curr = str_to_int(str_port); + if (port_curr < 0 || port_curr > static_cast(USHRT_MAX)) { shutdown(main_socket, SHUT_RDWR); closesocket(main_socket); LOG_ERROR(Network, "Couldn't parse server worker ports"); SignalCommunicationError(); return false; } - ports.push_back(static_cast(port)); + ports.push_back(static_cast(port_curr)); } if (ports.empty()) { shutdown(main_socket, SHUT_RDWR); @@ -294,6 +370,29 @@ bool Client::Connect() { return true; } +std::shared_ptr Client::NewUDPStream( + const std::string stream_id, size_t buffer_size, + const std::chrono::milliseconds& read_interval) { + + auto req = NewRequest("#" + stream_id); + + auto resp = Send(req); + + if (!resp.has_value()) { + return nullptr; + } + + auto port_udp = resp->GetResponseS32(0); + if (!port_udp.has_value()) { + return nullptr; + } + + udp_streams.push_back(std::make_shared(*this, static_cast(*port_udp), + buffer_size, read_interval)); + + return udp_streams.back(); +} + void Client::StopImpl(bool from_error) { bool expected = false; if (!stopped.compare_exchange_strong(expected, true)) @@ -303,6 +402,10 @@ void Client::StopImpl(bool from_error) { SendSimpleRequest("STOP"); } + for (auto it = udp_streams.begin(); it != udp_streams.end(); it++) { + it->get()->Stop(); + } + if (ping_thread.joinable()) { std::scoped_lock l2(ping_cv_mutex); ping_run = false; diff --git a/src/network/artic_base/artic_base_client.h b/src/network/artic_base/artic_base_client.h index 23079f832..040d50c9c 100644 --- a/src/network/artic_base/artic_base_client.h +++ b/src/network/artic_base/artic_base_client.h @@ -60,6 +60,61 @@ public: std::vector> pending_big_buffers; }; + class UDPStream { + public: + std::vector GetLastPacket() { + std::scoped_lock l(current_buffer_mutex); + return current_buffer; + } + + bool IsReady() { + return ready; + } + + void Start(); + void Stop() { + if (thread_run && handle_thread.joinable()) { + std::scoped_lock l2(thread_cv_mutex); + thread_run = false; + thread_cv.notify_one(); + } + } + + UDPStream(Client& _client, u16 _port, size_t _buffer_size, + const std::chrono::milliseconds& _read_interval) + : client(_client), port(_port), buffer_size(_buffer_size), + read_interval(_read_interval) {} + + ~UDPStream() { + Stop(); + if (handle_thread.joinable()) { + handle_thread.join(); + } + } + + private: + void Handle(); + + Client& client; + u16 port; + size_t buffer_size; + std::chrono::milliseconds read_interval; + + std::array serv_sockaddr_in{}; + bool ready = false; + + std::mutex current_buffer_mutex; + std::vector current_buffer; + + SocketHolder main_socket = -1; + + std::thread handle_thread; + std::condition_variable thread_cv; + std::mutex thread_cv_mutex; + std::atomic thread_run = true; + }; + friend class UDPStream; + Client(const std::string& _address, u16 _port) : address(_address), port(_port) { SocketManager::EnableSockets(); } @@ -76,6 +131,10 @@ public: return Request(GetNextRequestID(), method, max_parameter_count); } + std::shared_ptr NewUDPStream( + const std::string stream_id, size_t buffer_size, + const std::chrono::milliseconds& read_interval = std::chrono::milliseconds(0)); + void Stop() { StopImpl(false); } @@ -97,11 +156,17 @@ public: report_artic_event_callback = callback; } + // Returns the server address as a sockaddr_in struct + const std::array& GetServerAddr() { + return last_sockaddr_in; + } + private: - static constexpr const int SERVER_VERSION = 1; + static constexpr const int SERVER_VERSION = 2; std::string address; u16 port; + std::array last_sockaddr_in; SocketHolder main_socket = -1; std::atomic currRequestID; @@ -124,7 +189,7 @@ private: std::thread ping_thread; std::condition_variable ping_cv; std::mutex ping_cv_mutex; - bool ping_run = true; + std::atomic ping_run = true; void StopImpl(bool from_error); @@ -145,6 +210,8 @@ private: const std::chrono::nanoseconds& read_timeout = std::chrono::nanoseconds(0)); std::optional SendSimpleRequest(const std::string& method); + std::vector> udp_streams; + class Handler { public: Handler(Client& _client, u32 _addr, u16 _port, int _id); @@ -242,6 +309,14 @@ public: return *reinterpret_cast(buf->first); } + std::optional GetResponseFloat(u32 buffer_id) const { + auto buf = GetResponseBuffer(buffer_id); + if (!buf.has_value() || buf->second != sizeof(float)) { + return std::nullopt; + } + return *reinterpret_cast(buf->first); + } + private: friend class Client; friend class Client::Handler;