Artic Base: Add Artic Controller support (#195)

This commit is contained in:
PabloMK7 2024-07-16 22:00:21 +02:00 committed by GitHub
parent 9de19ff7a1
commit 55748d7d1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 741 additions and 158 deletions

View file

@ -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(&param, 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);
}

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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;