mirror of
https://github.com/PabloMK7/citra
synced 2024-11-14 20:58:23 +00:00
Artic Base: Add Artic Controller support (#195)
This commit is contained in:
parent
9de19ff7a1
commit
55748d7d1a
24 changed files with 741 additions and 158 deletions
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -128,6 +128,8 @@ void Config::ReadValues() {
|
|||
static_cast<u16>(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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -665,5 +665,8 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
|
|||
<string name="artic_base_enter_address">Introduce la dirección del servidor Artic Base</string>
|
||||
<string name="delay_render_thread">Retrasa el hilo de dibujado del juego</string>
|
||||
<string name="delay_render_thread_description">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.</string>
|
||||
<string name="miscellaneous">Misceláneo</string>
|
||||
<string name="use_artic_base_controller">Usar Artic Controller cuando se está conectado a Artic Base Server</string>
|
||||
<string name="use_artic_base_controller_desc">Usa los controles proporcionados por Artic Base Server cuando esté conectado a él en lugar del dispositivo de entrada configurado.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -700,5 +700,8 @@
|
|||
<string name="quicksave_saving">Saving…</string>
|
||||
<string name="quickload_loading">Loading…</string>
|
||||
<string name="quickload_not_found">No Quicksave available.</string>
|
||||
<string name="miscellaneous">Miscellaneous</string>
|
||||
<string name="use_artic_base_controller">Use Artic Controller when connected to Artic Base Server</string>
|
||||
<string name="use_artic_base_controller_desc">Use the controls provided by Artic Base Server when connected to it instead of the configured input device.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -29,7 +29,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor
|
|||
system{system_}, is_powered_on{system.IsPoweredOn()},
|
||||
general_tab{std::make_unique<ConfigureGeneral>(this)},
|
||||
system_tab{std::make_unique<ConfigureSystem>(system, this)},
|
||||
input_tab{std::make_unique<ConfigureInput>(this)},
|
||||
input_tab{std::make_unique<ConfigureInput>(system, this)},
|
||||
hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)},
|
||||
graphics_tab{
|
||||
std::make_unique<ConfigureGraphics>(gl_renderer, physical_devices, is_powered_on, this)},
|
||||
|
|
|
@ -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<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM>
|
||||
|
@ -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<Ui::ConfigureInput>()),
|
||||
ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent)
|
||||
: QWidget(parent), system(_system), ui(std::make_unique<Ui::ConfigureInput>()),
|
||||
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
|
||||
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<QKeySequence> 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); });
|
||||
|
|
|
@ -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<QKeySequence> new_key_list);
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
std::unique_ptr<Ui::ConfigureInput> ui;
|
||||
|
||||
std::unique_ptr<QTimer> timeout_timer;
|
||||
|
|
|
@ -841,6 +841,13 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="use_artic_controller">
|
||||
<property name="text">
|
||||
<string>Use Artic Controller when connected to Artic Base Server</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -425,6 +425,7 @@ struct Values {
|
|||
int current_input_profile_index; ///< The current input profile index
|
||||
std::vector<InputProfile> input_profiles; ///< The list of input profiles
|
||||
std::vector<TouchFromButtonMap> touch_from_button_maps;
|
||||
Setting<bool> use_artic_base_controller{false, "use_artic_base_controller"};
|
||||
|
||||
SwitchableSetting<bool> enable_gamemode{true, "enable_gamemode"};
|
||||
|
||||
|
|
|
@ -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<Network::ArticBase::Client>& 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<u32*>(data.data());
|
||||
if ((id - last_packet_id) < (std::numeric_limits<u32>::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<s16>(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS));
|
||||
s16 circle_pad_new_y = static_cast<s16>(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<u16>(data.touch_x);
|
||||
touch_entry.y = static_cast<u16>(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<s16>(std::roundf(circle_pad_x_f * MAX_CIRCLEPAD_POS));
|
||||
s16 circle_pad_new_y = static_cast<s16>(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<u16>(x * Core::kScreenBottomWidth);
|
||||
touch_entry.y = static_cast<u16>(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<u16>(x * Core::kScreenBottomWidth);
|
||||
touch_entry.y = static_cast<u16>(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<float> 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<s16>(accel.x);
|
||||
accelerometer_entry.y = static_cast<s16>(accel.y);
|
||||
accelerometer_entry.z = static_cast<s16>(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<float> 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<s16>(accel.x);
|
||||
accelerometer_entry.y = static_cast<s16>(accel.y);
|
||||
accelerometer_entry.z = static_cast<s16>(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<float> gyro;
|
||||
std::tie(std::ignore, gyro) = motion_device->GetStatus();
|
||||
double stretch = system.perf_stats->GetLastFrameTimeScale();
|
||||
gyro *= gyroscope_coef * static_cast<float>(stretch);
|
||||
gyroscope_entry.x = static_cast<s16>(gyro.x);
|
||||
gyroscope_entry.y = static_cast<s16>(gyro.y);
|
||||
gyroscope_entry.z = static_cast<s16>(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<float> gyro;
|
||||
std::tie(std::ignore, gyro) = motion_device->GetStatus();
|
||||
double stretch = system.perf_stats->GetLastFrameTimeScale();
|
||||
gyro *= gyroscope_coef * static_cast<float>(stretch);
|
||||
gyroscope_entry.x = static_cast<s16>(gyro.x);
|
||||
gyroscope_entry.y = static_cast<s16>(gyro.y);
|
||||
gyroscope_entry.z = static_cast<s16>(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<u32>(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<u32>(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<u32>(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<u32>(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<u32>(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<u32>(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<Network::ArticBase::Client>& client) {
|
||||
artic_client = client;
|
||||
artic_controller = std::make_shared<ArticBaseController>(client);
|
||||
if (!artic_controller->IsCreated()) {
|
||||
artic_controller.reset();
|
||||
} else {
|
||||
auto ir_user = system.ServiceManager().GetService<Service::IR::IR_USER>("ir:USER");
|
||||
if (ir_user.get()) {
|
||||
ir_user->UseArticController(artic_controller);
|
||||
}
|
||||
|
||||
auto ir_rst = system.ServiceManager().GetService<Service::IR::IR_RST>("ir:rst");
|
||||
if (ir_rst.get()) {
|
||||
ir_rst->UseArticController(artic_controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Module::ReloadInputDevices() {
|
||||
is_device_reload_pending.store(true);
|
||||
}
|
||||
|
|
|
@ -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<Network::ArticBase::Client>& client);
|
||||
|
||||
bool IsCreated() {
|
||||
return udp_stream.get();
|
||||
}
|
||||
|
||||
bool IsReady() {
|
||||
return udp_stream.get() ? udp_stream->IsReady() : false;
|
||||
}
|
||||
|
||||
ControllerData GetControllerData();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Network::ArticBase::Client::UDPStream> 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<Module> hid;
|
||||
};
|
||||
|
||||
void UseArticClient(const std::shared_ptr<Network::ArticBase::Client>& client);
|
||||
|
||||
void ReloadInputDevices();
|
||||
|
||||
const PadState& GetState() const;
|
||||
|
@ -355,6 +393,9 @@ private:
|
|||
std::unique_ptr<Input::TouchDevice> touch_device;
|
||||
std::unique_ptr<Input::TouchDevice> touch_btn_device;
|
||||
|
||||
std::shared_ptr<ArticBaseController> artic_controller;
|
||||
std::shared_ptr<Network::ArticBase::Client> artic_client;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int);
|
||||
friend class boost::serialization::access;
|
||||
|
|
|
@ -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<u8>(ResponseID::PollHID));
|
||||
response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x));
|
||||
response.c_stick.c_stick_y.Assign(static_cast<u32>(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<u8>(ResponseID::PollHID));
|
||||
response.c_stick.c_stick_x.Assign(static_cast<u32>(
|
||||
(static_cast<float>(data.c_stick_x) / MAX_CSTICK_RADIUS) * C_STICK_RADIUS +
|
||||
C_STICK_CENTER));
|
||||
response.c_stick.c_stick_y.Assign(static_cast<u32>(
|
||||
(static_cast<float>(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<u8>(ResponseID::PollHID));
|
||||
response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x));
|
||||
response.c_stick.c_stick_y.Assign(static_cast<u32>(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);
|
||||
|
||||
|
|
|
@ -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<Service::HID::ArticBaseController>& ac) {
|
||||
artic_controller = ac;
|
||||
}
|
||||
|
||||
private:
|
||||
void SendHIDStatus();
|
||||
void HandleConfigureHIDPollingRequest(std::span<const u8> request);
|
||||
|
@ -70,6 +78,8 @@ private:
|
|||
std::unique_ptr<Input::AnalogDevice> c_stick;
|
||||
std::atomic<bool> is_device_reload_pending;
|
||||
|
||||
std::shared_ptr<Service::HID::ArticBaseController> artic_controller = nullptr;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& hid_period;
|
||||
|
|
|
@ -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<s16>(c_stick_x_f * MAX_CSTICK_RADIUS);
|
||||
s16 c_stick_y = static_cast<s16>(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<s16>(c_stick_x_f * MAX_CSTICK_RADIUS);
|
||||
c_stick_y = static_cast<s16>(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
|
||||
|
|
|
@ -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<Service::HID::ArticBaseController>& 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<Service::HID::ArticBaseController> artic_controller = nullptr;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int);
|
||||
friend class boost::serialization::access;
|
||||
|
|
|
@ -480,6 +480,12 @@ void IR_USER::ReloadInputDevices() {
|
|||
extra_hid->RequestInputDevicesReload();
|
||||
}
|
||||
|
||||
void IR_USER::UseArticController(const std::shared_ptr<Service::HID::ArticBaseController>& ac) {
|
||||
if (extra_hid.get()) {
|
||||
extra_hid->UseArticController(ac);
|
||||
}
|
||||
}
|
||||
|
||||
IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {}
|
||||
IRDevice::~IRDevice() = default;
|
||||
|
||||
|
|
|
@ -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<Service::HID::ArticBaseController>& ac);
|
||||
|
||||
private:
|
||||
/**
|
||||
* InitializeIrNopShared service function
|
||||
|
|
|
@ -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<Kernel::Process>& process) {
|
|||
amapp->UseArticClient(client);
|
||||
}
|
||||
|
||||
if (Settings::values.use_artic_base_controller.GetValue()) {
|
||||
auto hid_user = system.ServiceManager().GetService<Service::HID::User>("hid:USER");
|
||||
if (hid_user.get()) {
|
||||
hid_user->GetModule()->UseArticClient(client);
|
||||
}
|
||||
}
|
||||
|
||||
ParseRegionLockoutInfo(ncch_program_id);
|
||||
|
||||
return ResultStatus::Success;
|
||||
|
|
|
@ -121,6 +121,81 @@ Client::Request::Request(u32 request_id, const std::string& method, size_t max_p
|
|||
std::min<size_t>(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<sockaddr_in*>(serv_sockaddr_in.data());
|
||||
socklen_t serv_sockaddr_len = static_cast<socklen_t>(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<SocketHolder>(-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<int>(buffer_size);
|
||||
if (::setsockopt(main_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&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<struct sockaddr*>(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<u8> 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<char*>(buffer.data()), static_cast<int>(buffer.size()), 0,
|
||||
reinterpret_cast<struct sockaddr*>(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<int>(USHRT_MAX)) {
|
||||
int port_curr = str_to_int(str_port);
|
||||
if (port_curr < 0 || port_curr > static_cast<int>(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<u16>(port));
|
||||
ports.push_back(static_cast<u16>(port_curr));
|
||||
}
|
||||
if (ports.empty()) {
|
||||
shutdown(main_socket, SHUT_RDWR);
|
||||
|
@ -294,6 +370,29 @@ bool Client::Connect() {
|
|||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Client::UDPStream> 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<UDPStream>(*this, static_cast<u16>(*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;
|
||||
|
|
|
@ -60,6 +60,61 @@ public:
|
|||
std::vector<std::pair<const void*, size_t>> pending_big_buffers;
|
||||
};
|
||||
|
||||
class UDPStream {
|
||||
public:
|
||||
std::vector<u8> 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<u8, 16> serv_sockaddr_in{};
|
||||
bool ready = false;
|
||||
|
||||
std::mutex current_buffer_mutex;
|
||||
std::vector<u8> current_buffer;
|
||||
|
||||
SocketHolder main_socket = -1;
|
||||
|
||||
std::thread handle_thread;
|
||||
std::condition_variable thread_cv;
|
||||
std::mutex thread_cv_mutex;
|
||||
std::atomic<bool> 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<UDPStream> 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<u8, 16>& 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<u8, 16> last_sockaddr_in;
|
||||
|
||||
SocketHolder main_socket = -1;
|
||||
std::atomic<u32> 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<bool> 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<std::string> SendSimpleRequest(const std::string& method);
|
||||
|
||||
std::vector<std::shared_ptr<UDPStream>> udp_streams;
|
||||
|
||||
class Handler {
|
||||
public:
|
||||
Handler(Client& _client, u32 _addr, u16 _port, int _id);
|
||||
|
@ -242,6 +309,14 @@ public:
|
|||
return *reinterpret_cast<u64*>(buf->first);
|
||||
}
|
||||
|
||||
std::optional<float> GetResponseFloat(u32 buffer_id) const {
|
||||
auto buf = GetResponseBuffer(buffer_id);
|
||||
if (!buf.has_value() || buf->second != sizeof(float)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return *reinterpret_cast<float*>(buf->first);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Client;
|
||||
friend class Client::Handler;
|
||||
|
|
Loading…
Reference in a new issue