mirror of
https://github.com/yuzu-emu/yuzu-android.git
synced 2025-07-10 23:07:51 -05:00
hidbus: Implement hidbus and ringcon
This commit is contained in:
72
src/core/hle/service/hid/hidbus/hidbus_base.cpp
Normal file
72
src/core/hle/service/hid/hidbus/hidbus_base.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/service/hid/hidbus/hidbus_base.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
HidbusBase::HidbusBase(KernelHelpers::ServiceContext& service_context_)
|
||||
: service_context(service_context_) {
|
||||
send_command_asyc_event = service_context.CreateEvent("hidbus:SendCommandAsycEvent");
|
||||
}
|
||||
HidbusBase::~HidbusBase() = default;
|
||||
|
||||
void HidbusBase::ActivateDevice() {
|
||||
if (is_activated) {
|
||||
return;
|
||||
}
|
||||
is_activated = true;
|
||||
OnInit();
|
||||
}
|
||||
|
||||
void HidbusBase::DeactivateDevice() {
|
||||
if (is_activated) {
|
||||
OnRelease();
|
||||
}
|
||||
is_activated = false;
|
||||
}
|
||||
|
||||
bool HidbusBase::IsDeviceActivated() const {
|
||||
return is_activated;
|
||||
}
|
||||
|
||||
void HidbusBase::Enable(bool enable) {
|
||||
device_enabled = enable;
|
||||
}
|
||||
|
||||
bool HidbusBase::IsEnabled() const {
|
||||
return device_enabled;
|
||||
}
|
||||
|
||||
bool HidbusBase::IsPollingMode() const {
|
||||
return polling_mode_enabled;
|
||||
}
|
||||
|
||||
JoyPollingMode HidbusBase::GetPollingMode() const {
|
||||
return polling_mode;
|
||||
}
|
||||
|
||||
void HidbusBase::SetPollingMode(JoyPollingMode mode) {
|
||||
polling_mode = mode;
|
||||
polling_mode_enabled = true;
|
||||
}
|
||||
|
||||
void HidbusBase::DisablePollingMode() {
|
||||
polling_mode_enabled = false;
|
||||
}
|
||||
|
||||
void HidbusBase::SetTransferMemoryPointer(u8* t_mem) {
|
||||
is_transfer_memory_set = true;
|
||||
transfer_memory = t_mem;
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const {
|
||||
return send_command_asyc_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
} // namespace Service::HID
|
178
src/core/hle/service/hid/hidbus/hidbus_base.h
Normal file
178
src/core/hle/service/hid/hidbus/hidbus_base.h
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Service::KernelHelpers {
|
||||
class ServiceContext;
|
||||
}
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
// This is nn::hidbus::JoyPollingMode
|
||||
enum class JoyPollingMode : u32 {
|
||||
SixAxisSensorDisable,
|
||||
SixAxisSensorEnable,
|
||||
ButtonOnly,
|
||||
};
|
||||
|
||||
struct DataAccessorHeader {
|
||||
ResultCode result{ResultUnknown};
|
||||
INSERT_PADDING_WORDS(0x1);
|
||||
std::array<u8, 0x18> unused{};
|
||||
u64 latest_entry{};
|
||||
u64 total_entries{};
|
||||
};
|
||||
static_assert(sizeof(DataAccessorHeader) == 0x30, "DataAccessorHeader is an invalid size");
|
||||
|
||||
struct JoyDisableSixAxisPollingData {
|
||||
std::array<u8, 0x26> data;
|
||||
u8 out_size;
|
||||
INSERT_PADDING_BYTES(0x1);
|
||||
u64 sampling_number;
|
||||
};
|
||||
static_assert(sizeof(JoyDisableSixAxisPollingData) == 0x30,
|
||||
"JoyDisableSixAxisPollingData is an invalid size");
|
||||
|
||||
struct JoyEnableSixAxisPollingData {
|
||||
std::array<u8, 0x8> data;
|
||||
u8 out_size;
|
||||
INSERT_PADDING_BYTES(0x7);
|
||||
u64 sampling_number;
|
||||
};
|
||||
static_assert(sizeof(JoyEnableSixAxisPollingData) == 0x18,
|
||||
"JoyEnableSixAxisPollingData is an invalid size");
|
||||
|
||||
struct JoyButtonOnlyPollingData {
|
||||
std::array<u8, 0x2c> data;
|
||||
u8 out_size;
|
||||
INSERT_PADDING_BYTES(0x3);
|
||||
u64 sampling_number;
|
||||
};
|
||||
static_assert(sizeof(JoyButtonOnlyPollingData) == 0x38,
|
||||
"JoyButtonOnlyPollingData is an invalid size");
|
||||
|
||||
struct JoyDisableSixAxisPollingEntry {
|
||||
u64 sampling_number;
|
||||
JoyDisableSixAxisPollingData polling_data;
|
||||
};
|
||||
static_assert(sizeof(JoyDisableSixAxisPollingEntry) == 0x38,
|
||||
"JoyDisableSixAxisPollingEntry is an invalid size");
|
||||
|
||||
struct JoyEnableSixAxisPollingEntry {
|
||||
u64 sampling_number;
|
||||
JoyEnableSixAxisPollingData polling_data;
|
||||
};
|
||||
static_assert(sizeof(JoyEnableSixAxisPollingEntry) == 0x20,
|
||||
"JoyEnableSixAxisPollingEntry is an invalid size");
|
||||
|
||||
struct JoyButtonOnlyPollingEntry {
|
||||
u64 sampling_number;
|
||||
JoyButtonOnlyPollingData polling_data;
|
||||
};
|
||||
static_assert(sizeof(JoyButtonOnlyPollingEntry) == 0x40,
|
||||
"JoyButtonOnlyPollingEntry is an invalid size");
|
||||
|
||||
struct JoyDisableSixAxisDataAccessor {
|
||||
DataAccessorHeader header{};
|
||||
std::array<JoyDisableSixAxisPollingEntry, 0xb> entries{};
|
||||
};
|
||||
static_assert(sizeof(JoyDisableSixAxisDataAccessor) == 0x298,
|
||||
"JoyDisableSixAxisDataAccessor is an invalid size");
|
||||
|
||||
struct JoyEnableSixAxisDataAccessor {
|
||||
DataAccessorHeader header{};
|
||||
std::array<JoyEnableSixAxisPollingEntry, 0xb> entries{};
|
||||
};
|
||||
static_assert(sizeof(JoyEnableSixAxisDataAccessor) == 0x190,
|
||||
"JoyEnableSixAxisDataAccessor is an invalid size");
|
||||
|
||||
struct ButtonOnlyPollingDataAccessor {
|
||||
DataAccessorHeader header;
|
||||
std::array<JoyButtonOnlyPollingEntry, 0xb> entries;
|
||||
};
|
||||
static_assert(sizeof(ButtonOnlyPollingDataAccessor) == 0x2F0,
|
||||
"ButtonOnlyPollingDataAccessor is an invalid size");
|
||||
|
||||
class HidbusBase {
|
||||
public:
|
||||
explicit HidbusBase(KernelHelpers::ServiceContext& service_context_);
|
||||
virtual ~HidbusBase();
|
||||
|
||||
void ActivateDevice();
|
||||
|
||||
void DeactivateDevice();
|
||||
|
||||
bool IsDeviceActivated() const;
|
||||
|
||||
// Enables/disables the device
|
||||
void Enable(bool enable);
|
||||
|
||||
// returns true if device is enabled
|
||||
bool IsEnabled() const;
|
||||
|
||||
// returns true if polling mode is enabled
|
||||
bool IsPollingMode() const;
|
||||
|
||||
// returns polling mode
|
||||
JoyPollingMode GetPollingMode() const;
|
||||
|
||||
// Sets and enables JoyPollingMode
|
||||
void SetPollingMode(JoyPollingMode mode);
|
||||
|
||||
// Disables JoyPollingMode
|
||||
void DisablePollingMode();
|
||||
|
||||
// Called on EnableJoyPollingReceiveMode
|
||||
void SetTransferMemoryPointer(u8* t_mem);
|
||||
|
||||
Kernel::KReadableEvent& GetSendCommandAsycEvent() const;
|
||||
|
||||
virtual void OnInit() {}
|
||||
|
||||
virtual void OnRelease() {}
|
||||
|
||||
// Updates device transfer memory
|
||||
virtual void OnUpdate() {}
|
||||
|
||||
// Returns the device ID of the joycon
|
||||
virtual u8 GetDeviceId() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Assigns a command from data
|
||||
virtual bool SetCommand(const std::vector<u8>& data) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Returns a reply from a command
|
||||
virtual std::vector<u8> GetReply() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected:
|
||||
bool is_activated{};
|
||||
bool device_enabled{};
|
||||
bool polling_mode_enabled{};
|
||||
JoyPollingMode polling_mode = {};
|
||||
JoyDisableSixAxisDataAccessor disable_sixaxis_data{};
|
||||
JoyEnableSixAxisDataAccessor enable_sixaxis_data{};
|
||||
ButtonOnlyPollingDataAccessor button_only_data{};
|
||||
|
||||
u8* transfer_memory{nullptr};
|
||||
bool is_transfer_memory_set{};
|
||||
|
||||
Kernel::KEvent* send_command_asyc_event;
|
||||
KernelHelpers::ServiceContext& service_context;
|
||||
};
|
||||
} // namespace Service::HID
|
306
src/core/hle/service/hid/hidbus/ringcon.cpp
Normal file
306
src/core/hle/service/hid/hidbus/ringcon.cpp
Normal file
@ -0,0 +1,306 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/service/hid/hidbus/ringcon.h"
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
RingController::RingController(Core::HID::HIDCore& hid_core_,
|
||||
KernelHelpers::ServiceContext& service_context_)
|
||||
: HidbusBase(service_context_) {
|
||||
// Use the horizontal axis of left stick for emulating input
|
||||
// There is no point on adding a frontend implementation since Ring Fit Adventure doesn't work
|
||||
input = hid_core_.GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
}
|
||||
|
||||
RingController::~RingController() = default;
|
||||
|
||||
void RingController::OnInit() {
|
||||
return;
|
||||
}
|
||||
|
||||
void RingController::OnRelease() {
|
||||
return;
|
||||
};
|
||||
|
||||
void RingController::OnUpdate() {
|
||||
if (!is_activated) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!device_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!polling_mode_enabled || !is_transfer_memory_set) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (polling_mode) {
|
||||
case JoyPollingMode::SixAxisSensorEnable: {
|
||||
enable_sixaxis_data.header.total_entries = 10;
|
||||
enable_sixaxis_data.header.result = ResultSuccess;
|
||||
const auto& last_entry =
|
||||
enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
|
||||
|
||||
enable_sixaxis_data.header.latest_entry =
|
||||
(enable_sixaxis_data.header.latest_entry + 1) % 10;
|
||||
auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
|
||||
|
||||
curr_entry.sampling_number = last_entry.sampling_number + 1;
|
||||
curr_entry.polling_data.sampling_number = curr_entry.sampling_number;
|
||||
|
||||
const RingConData ringcon_value = GetSensorValue();
|
||||
curr_entry.polling_data.out_size = sizeof(ringcon_value);
|
||||
std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value));
|
||||
|
||||
std::memcpy(transfer_memory, &enable_sixaxis_data, sizeof(enable_sixaxis_data));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RingController::RingConData RingController::GetSensorValue() const {
|
||||
RingConData ringcon_sensor_value{
|
||||
.status = DataValid::Valid,
|
||||
.data = 0,
|
||||
};
|
||||
|
||||
const f32 stick_value = static_cast<f32>(input->GetSticks().left.x) / 32767.0f;
|
||||
|
||||
ringcon_sensor_value.data = static_cast<s16>(stick_value * range) + idle_value;
|
||||
|
||||
return ringcon_sensor_value;
|
||||
}
|
||||
|
||||
u8 RingController::GetDeviceId() const {
|
||||
return device_id;
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetReply() const {
|
||||
const RingConCommands current_command = command;
|
||||
|
||||
switch (current_command) {
|
||||
case RingConCommands::GetFirmwareVersion:
|
||||
return GetFirmwareVersionReply();
|
||||
case RingConCommands::ReadId:
|
||||
return GetReadIdReply();
|
||||
case RingConCommands::c20105:
|
||||
return GetC020105Reply();
|
||||
case RingConCommands::ReadUnkCal:
|
||||
return GetReadUnkCalReply();
|
||||
case RingConCommands::ReadFactoryCal:
|
||||
return GetReadFactoryCalReply();
|
||||
case RingConCommands::ReadUserCal:
|
||||
return GetReadUserCalReply();
|
||||
case RingConCommands::ReadRepCount:
|
||||
return GetReadRepCountReply();
|
||||
case RingConCommands::ReadTotalPushCount:
|
||||
return GetReadTotalPushCountReply();
|
||||
case RingConCommands::SaveCalData:
|
||||
return GetSaveDataReply();
|
||||
default:
|
||||
return GetErrorReply();
|
||||
}
|
||||
}
|
||||
|
||||
bool RingController::SetCommand(const std::vector<u8>& data) {
|
||||
if (data.size() < 4) {
|
||||
LOG_ERROR(Service_HID, "Command size not supported {}", data.size());
|
||||
command = RingConCommands::Error;
|
||||
return false;
|
||||
}
|
||||
|
||||
// There must be a better way to do this
|
||||
const u32 command_id =
|
||||
u32{data[0]} + (u32{data[1]} << 8) + (u32{data[2]} << 16) + (u32{data[3]} << 24);
|
||||
static constexpr std::array supported_commands = {
|
||||
RingConCommands::GetFirmwareVersion,
|
||||
RingConCommands::ReadId,
|
||||
RingConCommands::c20105,
|
||||
RingConCommands::ReadUnkCal,
|
||||
RingConCommands::ReadFactoryCal,
|
||||
RingConCommands::ReadUserCal,
|
||||
RingConCommands::ReadRepCount,
|
||||
RingConCommands::ReadTotalPushCount,
|
||||
RingConCommands::SaveCalData,
|
||||
};
|
||||
|
||||
for (RingConCommands cmd : supported_commands) {
|
||||
if (command_id == static_cast<u32>(cmd)) {
|
||||
return ExcecuteCommand(cmd, data);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_HID, "Command not implemented {}", command_id);
|
||||
command = RingConCommands::Error;
|
||||
// Signal a reply to avoid softlocking
|
||||
send_command_asyc_event->GetWritableEvent().Signal();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RingController::ExcecuteCommand(RingConCommands cmd, const std::vector<u8>& data) {
|
||||
switch (cmd) {
|
||||
case RingConCommands::GetFirmwareVersion:
|
||||
case RingConCommands::ReadId:
|
||||
case RingConCommands::c20105:
|
||||
case RingConCommands::ReadUnkCal:
|
||||
case RingConCommands::ReadFactoryCal:
|
||||
case RingConCommands::ReadUserCal:
|
||||
case RingConCommands::ReadRepCount:
|
||||
case RingConCommands::ReadTotalPushCount:
|
||||
ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
|
||||
command = cmd;
|
||||
send_command_asyc_event->GetWritableEvent().Signal();
|
||||
return true;
|
||||
case RingConCommands::SaveCalData: {
|
||||
ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes");
|
||||
|
||||
SaveCalData save_info{};
|
||||
std::memcpy(&save_info, &data, sizeof(SaveCalData));
|
||||
user_calibration = save_info.calibration;
|
||||
|
||||
command = cmd;
|
||||
send_command_asyc_event->GetWritableEvent().Signal();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Service_HID, "Command not implemented {}", cmd);
|
||||
command = RingConCommands::Error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetFirmwareVersionReply() const {
|
||||
const FirmwareVersionReply reply{
|
||||
.status = DataValid::Valid,
|
||||
.firmware = version,
|
||||
};
|
||||
|
||||
return GetDataVector(reply);
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetReadIdReply() const {
|
||||
// The values are hardcoded from a real joycon
|
||||
const ReadIdReply reply{
|
||||
.status = DataValid::Valid,
|
||||
.id_l_x0 = 8,
|
||||
.id_l_x0_2 = 41,
|
||||
.id_l_x4 = 22294,
|
||||
.id_h_x0 = 19777,
|
||||
.id_h_x0_2 = 13621,
|
||||
.id_h_x4 = 8245,
|
||||
};
|
||||
|
||||
return GetDataVector(reply);
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetC020105Reply() const {
|
||||
const Cmd020105Reply reply{
|
||||
.status = DataValid::Valid,
|
||||
.data = 1,
|
||||
};
|
||||
|
||||
return GetDataVector(reply);
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetReadUnkCalReply() const {
|
||||
const ReadUnkCalReply reply{
|
||||
.status = DataValid::Valid,
|
||||
.data = 0,
|
||||
};
|
||||
|
||||
return GetDataVector(reply);
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetReadFactoryCalReply() const {
|
||||
const ReadFactoryCalReply reply{
|
||||
.status = DataValid::Valid,
|
||||
.calibration = factory_calibration,
|
||||
};
|
||||
|
||||
return GetDataVector(reply);
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetReadUserCalReply() const {
|
||||
const ReadUserCalReply reply{
|
||||
.status = DataValid::Valid,
|
||||
.calibration = user_calibration,
|
||||
};
|
||||
|
||||
return GetDataVector(reply);
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetReadRepCountReply() const {
|
||||
// The values are hardcoded from a real joycon
|
||||
const GetThreeByteReply reply{
|
||||
.status = DataValid::Valid,
|
||||
.data = {30, 0, 0},
|
||||
.crc = GetCrcValue({30, 0, 0, 0}),
|
||||
};
|
||||
|
||||
return GetDataVector(reply);
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetReadTotalPushCountReply() const {
|
||||
// The values are hardcoded from a real joycon
|
||||
const GetThreeByteReply reply{
|
||||
.status = DataValid::Valid,
|
||||
.data = {30, 0, 0},
|
||||
.crc = GetCrcValue({30, 0, 0, 0}),
|
||||
};
|
||||
|
||||
return GetDataVector(reply);
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetSaveDataReply() const {
|
||||
const StatusReply reply{
|
||||
.status = DataValid::Valid,
|
||||
};
|
||||
|
||||
return GetDataVector(reply);
|
||||
}
|
||||
|
||||
std::vector<u8> RingController::GetErrorReply() const {
|
||||
const ErrorReply reply{
|
||||
.status = DataValid::BadCRC,
|
||||
};
|
||||
|
||||
return GetDataVector(reply);
|
||||
}
|
||||
|
||||
u8 RingController::GetCrcValue(const std::vector<u8>& data) const {
|
||||
u8 crc = 0;
|
||||
for (std::size_t index = 0; index < data.size(); index++) {
|
||||
for (u8 i = 0x80; i > 0; i >>= 1) {
|
||||
bool bit = (crc & 0x80) != 0;
|
||||
if ((data[index] & i) != 0) {
|
||||
bit = !bit;
|
||||
}
|
||||
crc <<= 1;
|
||||
if (bit) {
|
||||
crc ^= 0x8d;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<u8> RingController::GetDataVector(const T& reply) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
std::vector<u8> data;
|
||||
data.resize(sizeof(reply));
|
||||
std::memcpy(data.data(), &reply, sizeof(reply));
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace Service::HID
|
247
src/core/hle/service/hid/hidbus/ringcon.h
Normal file
247
src/core/hle/service/hid/hidbus/ringcon.h
Normal file
@ -0,0 +1,247 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/hid/hidbus/hidbus_base.h"
|
||||
|
||||
namespace Core::HID {
|
||||
class EmulatedController;
|
||||
} // namespace Core::HID
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
class RingController final : public HidbusBase {
|
||||
public:
|
||||
explicit RingController(Core::HID::HIDCore& hid_core_,
|
||||
KernelHelpers::ServiceContext& service_context_);
|
||||
~RingController() override;
|
||||
|
||||
void OnInit() override;
|
||||
|
||||
void OnRelease() override;
|
||||
|
||||
// Updates ringcon transfer memory
|
||||
void OnUpdate() override;
|
||||
|
||||
// Returns the device ID of the joycon
|
||||
u8 GetDeviceId() const override;
|
||||
|
||||
// Assigns a command from data
|
||||
bool SetCommand(const std::vector<u8>& data) override;
|
||||
|
||||
// Returns a reply from a command
|
||||
std::vector<u8> GetReply() const override;
|
||||
|
||||
private:
|
||||
// These values are obtained from a real ring controller
|
||||
static constexpr s16 idle_value = 2280;
|
||||
static constexpr s16 idle_deadzone = 120;
|
||||
static constexpr s16 range = 2500;
|
||||
|
||||
enum class RingConCommands : u32 {
|
||||
GetFirmwareVersion = 0x00020000,
|
||||
ReadId = 0x00020100,
|
||||
JoyPolling = 0x00020101,
|
||||
Unknown1 = 0x00020104,
|
||||
c20105 = 0x00020105,
|
||||
Unknown2 = 0x00020204,
|
||||
Unknown3 = 0x00020304,
|
||||
Unknown4 = 0x00020404,
|
||||
ReadUnkCal = 0x00020504,
|
||||
ReadFactoryCal = 0x00020A04,
|
||||
Unknown5 = 0x00021104,
|
||||
Unknown6 = 0x00021204,
|
||||
Unknown7 = 0x00021304,
|
||||
ReadUserCal = 0x00021A04,
|
||||
ReadRepCount = 0x00023104,
|
||||
ReadTotalPushCount = 0x00023204,
|
||||
Unknown9 = 0x04013104,
|
||||
Unknown10 = 0x04011104,
|
||||
Unknown11 = 0x04011204,
|
||||
Unknown12 = 0x04011304,
|
||||
SaveCalData = 0x10011A04,
|
||||
Error = 0xFFFFFFFF,
|
||||
};
|
||||
|
||||
enum class DataValid : u32 {
|
||||
Valid,
|
||||
BadCRC,
|
||||
Cal,
|
||||
};
|
||||
|
||||
struct FirmwareVersion {
|
||||
u8 sub;
|
||||
u8 main;
|
||||
};
|
||||
static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
|
||||
|
||||
struct FactoryCalibration {
|
||||
s32_le os_max;
|
||||
s32_le hk_max;
|
||||
s32_le zero_min;
|
||||
s32_le zero_max;
|
||||
};
|
||||
static_assert(sizeof(FactoryCalibration) == 0x10, "FactoryCalibration is an invalid size");
|
||||
|
||||
struct CalibrationValue {
|
||||
s16 value;
|
||||
u16 crc;
|
||||
};
|
||||
static_assert(sizeof(CalibrationValue) == 0x4, "CalibrationValue is an invalid size");
|
||||
|
||||
struct UserCalibration {
|
||||
CalibrationValue os_max;
|
||||
CalibrationValue hk_max;
|
||||
CalibrationValue zero;
|
||||
};
|
||||
static_assert(sizeof(UserCalibration) == 0xC, "UserCalibration is an invalid size");
|
||||
|
||||
struct SaveCalData {
|
||||
RingConCommands command;
|
||||
UserCalibration calibration;
|
||||
INSERT_PADDING_BYTES_NOINIT(4);
|
||||
};
|
||||
static_assert(sizeof(SaveCalData) == 0x14, "SaveCalData is an invalid size");
|
||||
static_assert(std::is_trivially_copyable_v<SaveCalData>,
|
||||
"SaveCalData must be trivially copyable");
|
||||
|
||||
struct FirmwareVersionReply {
|
||||
DataValid status;
|
||||
FirmwareVersion firmware;
|
||||
INSERT_PADDING_BYTES(0x2);
|
||||
};
|
||||
static_assert(sizeof(FirmwareVersionReply) == 0x8, "FirmwareVersionReply is an invalid size");
|
||||
|
||||
struct Cmd020105Reply {
|
||||
DataValid status;
|
||||
u8 data;
|
||||
INSERT_PADDING_BYTES(0x3);
|
||||
};
|
||||
static_assert(sizeof(Cmd020105Reply) == 0x8, "Cmd020105Reply is an invalid size");
|
||||
|
||||
struct StatusReply {
|
||||
DataValid status;
|
||||
};
|
||||
static_assert(sizeof(StatusReply) == 0x4, "StatusReply is an invalid size");
|
||||
|
||||
struct GetThreeByteReply {
|
||||
DataValid status;
|
||||
std::array<u8, 3> data;
|
||||
u8 crc;
|
||||
};
|
||||
static_assert(sizeof(GetThreeByteReply) == 0x8, "GetThreeByteReply is an invalid size");
|
||||
|
||||
struct ReadUnkCalReply {
|
||||
DataValid status;
|
||||
u16 data;
|
||||
INSERT_PADDING_BYTES(0x2);
|
||||
};
|
||||
static_assert(sizeof(ReadUnkCalReply) == 0x8, "ReadUnkCalReply is an invalid size");
|
||||
|
||||
struct ReadFactoryCalReply {
|
||||
DataValid status;
|
||||
FactoryCalibration calibration;
|
||||
};
|
||||
static_assert(sizeof(ReadFactoryCalReply) == 0x14, "ReadFactoryCalReply is an invalid size");
|
||||
|
||||
struct ReadUserCalReply {
|
||||
DataValid status;
|
||||
UserCalibration calibration;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
};
|
||||
static_assert(sizeof(ReadUserCalReply) == 0x14, "ReadUserCalReply is an invalid size");
|
||||
|
||||
struct ReadIdReply {
|
||||
DataValid status;
|
||||
u16 id_l_x0;
|
||||
u16 id_l_x0_2;
|
||||
u16 id_l_x4;
|
||||
u16 id_h_x0;
|
||||
u16 id_h_x0_2;
|
||||
u16 id_h_x4;
|
||||
};
|
||||
static_assert(sizeof(ReadIdReply) == 0x10, "ReadIdReply is an invalid size");
|
||||
|
||||
struct ErrorReply {
|
||||
DataValid status;
|
||||
INSERT_PADDING_BYTES(0x3);
|
||||
};
|
||||
static_assert(sizeof(ErrorReply) == 0x8, "ErrorReply is an invalid size");
|
||||
|
||||
struct RingConData {
|
||||
DataValid status;
|
||||
s16_le data;
|
||||
INSERT_PADDING_BYTES(0x2);
|
||||
};
|
||||
static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size");
|
||||
|
||||
// Executes the command requested
|
||||
bool ExcecuteCommand(RingConCommands cmd, const std::vector<u8>& data);
|
||||
|
||||
// Returns RingConData struct with pressure sensor values
|
||||
RingConData GetSensorValue() const;
|
||||
|
||||
// Returns 8 byte reply with firmware version
|
||||
std::vector<u8> GetFirmwareVersionReply() const;
|
||||
|
||||
// Returns 16 byte reply with ID values
|
||||
std::vector<u8> GetReadIdReply() const;
|
||||
|
||||
// (STUBBED) Returns 8 byte reply
|
||||
std::vector<u8> GetC020105Reply() const;
|
||||
|
||||
// (STUBBED) Returns 8 byte empty reply
|
||||
std::vector<u8> GetReadUnkCalReply() const;
|
||||
|
||||
// Returns 20 byte reply with factory calibration values
|
||||
std::vector<u8> GetReadFactoryCalReply() const;
|
||||
|
||||
// Returns 20 byte reply with user calibration values
|
||||
std::vector<u8> GetReadUserCalReply() const;
|
||||
|
||||
// (STUBBED) Returns 8 byte reply
|
||||
std::vector<u8> GetReadRepCountReply() const;
|
||||
|
||||
// (STUBBED) Returns 8 byte reply
|
||||
std::vector<u8> GetReadTotalPushCountReply() const;
|
||||
|
||||
// Returns 4 byte save data reply
|
||||
std::vector<u8> GetSaveDataReply() const;
|
||||
|
||||
// Returns 8 byte error reply
|
||||
std::vector<u8> GetErrorReply() const;
|
||||
|
||||
// Returns 8 bit redundancy check from provided data
|
||||
u8 GetCrcValue(const std::vector<u8>& data) const;
|
||||
|
||||
// Converts structs to an u8 vector equivalent
|
||||
template <typename T>
|
||||
std::vector<u8> GetDataVector(const T& reply) const;
|
||||
|
||||
RingConCommands command{RingConCommands::Error};
|
||||
|
||||
const u8 device_id = 0x20;
|
||||
const FirmwareVersion version = {
|
||||
.sub = 0x0,
|
||||
.main = 0x2c,
|
||||
};
|
||||
const FactoryCalibration factory_calibration = {
|
||||
.os_max = idle_value + range + idle_deadzone,
|
||||
.hk_max = idle_value - range - idle_deadzone,
|
||||
.zero_min = idle_value - idle_deadzone,
|
||||
.zero_max = idle_value + idle_deadzone,
|
||||
};
|
||||
UserCalibration user_calibration = {
|
||||
.os_max = {.value = range, .crc = 228},
|
||||
.hk_max = {.value = -range, .crc = 239},
|
||||
.zero = {.value = idle_value, .crc = 225},
|
||||
};
|
||||
|
||||
Core::HID::EmulatedController* input;
|
||||
};
|
||||
} // namespace Service::HID
|
51
src/core/hle/service/hid/hidbus/starlink.cpp
Normal file
51
src/core/hle/service/hid/hidbus/starlink.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/service/hid/hidbus/starlink.h"
|
||||
|
||||
namespace Service::HID {
|
||||
constexpr u8 DEVICE_ID = 0x28;
|
||||
|
||||
Starlink::Starlink(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_)
|
||||
: HidbusBase(service_context_) {}
|
||||
Starlink::~Starlink() = default;
|
||||
|
||||
void Starlink::OnInit() {
|
||||
return;
|
||||
}
|
||||
|
||||
void Starlink::OnRelease() {
|
||||
return;
|
||||
};
|
||||
|
||||
void Starlink::OnUpdate() {
|
||||
if (!is_activated) {
|
||||
return;
|
||||
}
|
||||
if (!device_enabled) {
|
||||
return;
|
||||
}
|
||||
if (!polling_mode_enabled || !is_transfer_memory_set) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
|
||||
}
|
||||
|
||||
u8 Starlink::GetDeviceId() const {
|
||||
return DEVICE_ID;
|
||||
}
|
||||
|
||||
std::vector<u8> Starlink::GetReply() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Starlink::SetCommand(const std::vector<u8>& data) {
|
||||
LOG_ERROR(Service_HID, "Command not implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Service::HID
|
39
src/core/hle/service/hid/hidbus/starlink.h
Normal file
39
src/core/hle/service/hid/hidbus/starlink.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/hid/hidbus/hidbus_base.h"
|
||||
|
||||
namespace Core::HID {
|
||||
class EmulatedController;
|
||||
} // namespace Core::HID
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
class Starlink final : public HidbusBase {
|
||||
public:
|
||||
explicit Starlink(Core::HID::HIDCore& hid_core_,
|
||||
KernelHelpers::ServiceContext& service_context_);
|
||||
~Starlink() override;
|
||||
|
||||
void OnInit() override;
|
||||
|
||||
void OnRelease() override;
|
||||
|
||||
// Updates ringcon transfer memory
|
||||
void OnUpdate() override;
|
||||
|
||||
// Returns the device ID of the joycon
|
||||
u8 GetDeviceId() const override;
|
||||
|
||||
// Assigns a command from data
|
||||
bool SetCommand(const std::vector<u8>& data) override;
|
||||
|
||||
// Returns a reply from a command
|
||||
std::vector<u8> GetReply() const override;
|
||||
};
|
||||
|
||||
} // namespace Service::HID
|
52
src/core/hle/service/hid/hidbus/stubbed.cpp
Normal file
52
src/core/hle/service/hid/hidbus/stubbed.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hle/service/hid/hidbus/stubbed.h"
|
||||
|
||||
namespace Service::HID {
|
||||
constexpr u8 DEVICE_ID = 0xFF;
|
||||
|
||||
HidbusStubbed::HidbusStubbed(Core::HID::HIDCore& hid_core_,
|
||||
KernelHelpers::ServiceContext& service_context_)
|
||||
: HidbusBase(service_context_) {}
|
||||
HidbusStubbed::~HidbusStubbed() = default;
|
||||
|
||||
void HidbusStubbed::OnInit() {
|
||||
return;
|
||||
}
|
||||
|
||||
void HidbusStubbed::OnRelease() {
|
||||
return;
|
||||
};
|
||||
|
||||
void HidbusStubbed::OnUpdate() {
|
||||
if (!is_activated) {
|
||||
return;
|
||||
}
|
||||
if (!device_enabled) {
|
||||
return;
|
||||
}
|
||||
if (!polling_mode_enabled || !is_transfer_memory_set) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
|
||||
}
|
||||
|
||||
u8 HidbusStubbed::GetDeviceId() const {
|
||||
return DEVICE_ID;
|
||||
}
|
||||
|
||||
std::vector<u8> HidbusStubbed::GetReply() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool HidbusStubbed::SetCommand(const std::vector<u8>& data) {
|
||||
LOG_ERROR(Service_HID, "Command not implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Service::HID
|
39
src/core/hle/service/hid/hidbus/stubbed.h
Normal file
39
src/core/hle/service/hid/hidbus/stubbed.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2021 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/hid/hidbus/hidbus_base.h"
|
||||
|
||||
namespace Core::HID {
|
||||
class EmulatedController;
|
||||
} // namespace Core::HID
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
class HidbusStubbed final : public HidbusBase {
|
||||
public:
|
||||
explicit HidbusStubbed(Core::HID::HIDCore& hid_core_,
|
||||
KernelHelpers::ServiceContext& service_context_);
|
||||
~HidbusStubbed() override;
|
||||
|
||||
void OnInit() override;
|
||||
|
||||
void OnRelease() override;
|
||||
|
||||
// Updates ringcon transfer memory
|
||||
void OnUpdate() override;
|
||||
|
||||
// Returns the device ID of the joycon
|
||||
u8 GetDeviceId() const override;
|
||||
|
||||
// Assigns a command from data
|
||||
bool SetCommand(const std::vector<u8>& data) override;
|
||||
|
||||
// Returns a reply from a command
|
||||
std::vector<u8> GetReply() const override;
|
||||
};
|
||||
|
||||
} // namespace Service::HID
|
Reference in New Issue
Block a user