committed by
Narr the Reg
14 changed files with 1679 additions and 26 deletions
-
10src/core/CMakeLists.txt
-
18src/core/hle/kernel/kernel.cpp
-
6src/core/hle/kernel/kernel.h
-
27src/core/hle/service/hid/hid.cpp
-
529src/core/hle/service/hid/hidbus.cpp
-
131src/core/hle/service/hid/hidbus.h
-
72src/core/hle/service/hid/hidbus/hidbus_base.cpp
-
178src/core/hle/service/hid/hidbus/hidbus_base.h
-
306src/core/hle/service/hid/hidbus/ringcon.cpp
-
247src/core/hle/service/hid/hidbus/ringcon.h
-
51src/core/hle/service/hid/hidbus/starlink.cpp
-
39src/core/hle/service/hid/hidbus/starlink.h
-
52src/core/hle/service/hid/hidbus/stubbed.cpp
-
39src/core/hle/service/hid/hidbus/stubbed.h
@ -0,0 +1,529 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/core_timing.h"
|
||||
|
#include "core/core_timing_util.h"
|
||||
|
#include "core/hid/hid_types.h"
|
||||
|
#include "core/hle/ipc_helpers.h"
|
||||
|
#include "core/hle/kernel/k_event.h"
|
||||
|
#include "core/hle/kernel/k_readable_event.h"
|
||||
|
#include "core/hle/kernel/k_shared_memory.h"
|
||||
|
#include "core/hle/kernel/k_transfer_memory.h"
|
||||
|
#include "core/hle/service/hid/hidbus.h"
|
||||
|
#include "core/hle/service/hid/hidbus/ringcon.h"
|
||||
|
#include "core/hle/service/hid/hidbus/starlink.h"
|
||||
|
#include "core/hle/service/hid/hidbus/stubbed.h"
|
||||
|
#include "core/hle/service/service.h"
|
||||
|
#include "core/memory.h"
|
||||
|
|
||||
|
namespace Service::HID { |
||||
|
// (15ms, 66Hz)
|
||||
|
constexpr auto hidbus_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; |
||||
|
|
||||
|
HidBus::HidBus(Core::System& system_) |
||||
|
: ServiceFramework{system_, "hidbus"}, service_context{system_, service_name} { |
||||
|
|
||||
|
// clang-format off
|
||||
|
static const FunctionInfo functions[] = { |
||||
|
{1, &HidBus::GetBusHandle, "GetBusHandle"}, |
||||
|
{2, &HidBus::IsExternalDeviceConnected, "IsExternalDeviceConnected"}, |
||||
|
{3, &HidBus::Initialize, "Initialize"}, |
||||
|
{4, &HidBus::Finalize, "Finalize"}, |
||||
|
{5, &HidBus::EnableExternalDevice, "EnableExternalDevice"}, |
||||
|
{6, &HidBus::GetExternalDeviceId, "GetExternalDeviceId"}, |
||||
|
{7, &HidBus::SendCommandAsync, "SendCommandAsync"}, |
||||
|
{8, &HidBus::GetSendCommandAsynceResult, "GetSendCommandAsynceResult"}, |
||||
|
{9, &HidBus::SetEventForSendCommandAsycResult, "SetEventForSendCommandAsycResult"}, |
||||
|
{10, &HidBus::GetSharedMemoryHandle, "GetSharedMemoryHandle"}, |
||||
|
{11, &HidBus::EnableJoyPollingReceiveMode, "EnableJoyPollingReceiveMode"}, |
||||
|
{12, &HidBus::DisableJoyPollingReceiveMode, "DisableJoyPollingReceiveMode"}, |
||||
|
{13, nullptr, "GetPollingData"}, |
||||
|
{14, &HidBus::SetStatusManagerType, "SetStatusManagerType"}, |
||||
|
}; |
||||
|
// clang-format on
|
||||
|
|
||||
|
RegisterHandlers(functions); |
||||
|
|
||||
|
// Register update callbacks
|
||||
|
hidbus_update_event = Core::Timing::CreateEvent( |
||||
|
"Hidbus::UpdateCallback", |
||||
|
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { |
||||
|
const auto guard = LockService(); |
||||
|
UpdateHidbus(user_data, ns_late); |
||||
|
}); |
||||
|
|
||||
|
system_.CoreTiming().ScheduleEvent(hidbus_update_ns, hidbus_update_event); |
||||
|
} |
||||
|
|
||||
|
HidBus::~HidBus() { |
||||
|
system.CoreTiming().UnscheduleEvent(hidbus_update_event, 0); |
||||
|
} |
||||
|
|
||||
|
void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { |
||||
|
auto& core_timing = system.CoreTiming(); |
||||
|
|
||||
|
if (is_hidbus_enabled) { |
||||
|
for (std::size_t i = 0; i < devices.size(); ++i) { |
||||
|
if (!devices[i].is_device_initializated) { |
||||
|
continue; |
||||
|
} |
||||
|
auto& device = devices[i].device; |
||||
|
device->OnUpdate(); |
||||
|
auto& cur_entry = hidbus_status.entries[devices[i].handle.internal_index]; |
||||
|
cur_entry.is_polling_mode = device->IsPollingMode(); |
||||
|
cur_entry.polling_mode = device->GetPollingMode(); |
||||
|
cur_entry.is_enabled = device->IsEnabled(); |
||||
|
|
||||
|
u8* shared_memory = system.Kernel().GetHidBusSharedMem().GetPointer(); |
||||
|
std::memcpy(shared_memory + (i * sizeof(HidbusStatusManagerEntry)), &hidbus_status, |
||||
|
sizeof(HidbusStatusManagerEntry)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// If ns_late is higher than the update rate ignore the delay
|
||||
|
if (ns_late > hidbus_update_ns) { |
||||
|
ns_late = {}; |
||||
|
} |
||||
|
|
||||
|
core_timing.ScheduleEvent(hidbus_update_ns - ns_late, hidbus_update_event); |
||||
|
} |
||||
|
|
||||
|
std::optional<std::size_t> HidBus::GetDeviceIndexFromHandle(BusHandle handle) const { |
||||
|
for (std::size_t i = 0; i < devices.size(); ++i) { |
||||
|
const auto& device_handle = devices[i].handle; |
||||
|
if (handle.abstracted_pad_id == device_handle.abstracted_pad_id && |
||||
|
handle.internal_index == device_handle.internal_index && |
||||
|
handle.player_number == device_handle.player_number && |
||||
|
handle.bus_type == device_handle.bus_type && |
||||
|
handle.is_valid == device_handle.is_valid) { |
||||
|
return i; |
||||
|
} |
||||
|
} |
||||
|
return std::nullopt; |
||||
|
} |
||||
|
|
||||
|
void HidBus::GetBusHandle(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
struct Parameters { |
||||
|
Core::HID::NpadIdType npad_id; |
||||
|
INSERT_PADDING_WORDS_NOINIT(1); |
||||
|
BusType bus_type; |
||||
|
u64 applet_resource_user_id; |
||||
|
}; |
||||
|
static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); |
||||
|
|
||||
|
const auto parameters{rp.PopRaw<Parameters>()}; |
||||
|
|
||||
|
LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}", |
||||
|
parameters.npad_id, parameters.bus_type, parameters.applet_resource_user_id); |
||||
|
|
||||
|
bool is_handle_found = 0; |
||||
|
std::size_t handle_index = 0; |
||||
|
|
||||
|
for (std::size_t i = 0; i < devices.size(); i++) { |
||||
|
const auto& handle = devices[i].handle; |
||||
|
if (!handle.is_valid) { |
||||
|
continue; |
||||
|
} |
||||
|
if (static_cast<Core::HID::NpadIdType>(handle.player_number) == parameters.npad_id && |
||||
|
handle.bus_type == parameters.bus_type) { |
||||
|
is_handle_found = true; |
||||
|
handle_index = i; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Handle not found. Create a new one
|
||||
|
if (!is_handle_found) { |
||||
|
for (std::size_t i = 0; i < devices.size(); i++) { |
||||
|
if (devices[i].handle.is_valid) { |
||||
|
continue; |
||||
|
} |
||||
|
devices[i].handle = { |
||||
|
.abstracted_pad_id = static_cast<u8>(i), |
||||
|
.internal_index = static_cast<u8>(i), |
||||
|
.player_number = static_cast<u8>(parameters.npad_id), |
||||
|
.bus_type = parameters.bus_type, |
||||
|
.is_valid = true, |
||||
|
}; |
||||
|
handle_index = i; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
struct OutData { |
||||
|
bool is_valid; |
||||
|
INSERT_PADDING_BYTES(7); |
||||
|
BusHandle handle; |
||||
|
}; |
||||
|
static_assert(sizeof(OutData) == 0x10, "OutData has incorrect size."); |
||||
|
|
||||
|
const OutData out_data{ |
||||
|
.is_valid = true, |
||||
|
.handle = devices[handle_index].handle, |
||||
|
}; |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 6}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
rb.PushRaw(out_data); |
||||
|
} |
||||
|
|
||||
|
void HidBus::IsExternalDeviceConnected(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()}; |
||||
|
|
||||
|
LOG_INFO(Service_HID, |
||||
|
"Called, abstracted_pad_id={}, bus_type={}, internal_index={}, " |
||||
|
"player_number={}, is_valid={}", |
||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, |
||||
|
bus_handle_.player_number, bus_handle_.is_valid); |
||||
|
|
||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_); |
||||
|
|
||||
|
if (device_index) { |
||||
|
const auto& device = devices[device_index.value()].device; |
||||
|
const bool is_attached = device->IsDeviceActivated(); |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 3}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
rb.Push(is_attached); |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_HID, "Invalid handle"); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
void HidBus::Initialize(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()}; |
||||
|
const auto applet_resource_user_id{rp.Pop<u64>()}; |
||||
|
|
||||
|
LOG_INFO(Service_HID, |
||||
|
"called, abstracted_pad_id={} bus_type={} internal_index={} " |
||||
|
"player_number={} is_valid={}, applet_resource_user_id={}", |
||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, |
||||
|
bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id); |
||||
|
|
||||
|
is_hidbus_enabled = true; |
||||
|
|
||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_); |
||||
|
|
||||
|
if (device_index) { |
||||
|
const auto entry_index = devices[device_index.value()].handle.internal_index; |
||||
|
auto& cur_entry = hidbus_status.entries[entry_index]; |
||||
|
|
||||
|
if (bus_handle_.internal_index == 0) { |
||||
|
MakeDevice<RingController>(bus_handle_); |
||||
|
devices[device_index.value()].is_device_initializated = true; |
||||
|
devices[device_index.value()].device->ActivateDevice(); |
||||
|
cur_entry.is_in_focus = true; |
||||
|
cur_entry.is_connected = true; |
||||
|
cur_entry.is_connected_result = ResultSuccess; |
||||
|
cur_entry.is_enabled = false; |
||||
|
cur_entry.is_polling_mode = false; |
||||
|
} else { |
||||
|
MakeDevice<HidbusStubbed>(bus_handle_); |
||||
|
devices[device_index.value()].is_device_initializated = true; |
||||
|
cur_entry.is_in_focus = true; |
||||
|
cur_entry.is_connected = false; |
||||
|
cur_entry.is_connected_result = ResultSuccess; |
||||
|
cur_entry.is_enabled = false; |
||||
|
cur_entry.is_polling_mode = false; |
||||
|
} |
||||
|
|
||||
|
std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status, |
||||
|
sizeof(hidbus_status)); |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_HID, "Invalid handle"); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
void HidBus::Finalize(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()}; |
||||
|
const auto applet_resource_user_id{rp.Pop<u64>()}; |
||||
|
|
||||
|
LOG_INFO(Service_HID, |
||||
|
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, " |
||||
|
"player_number={}, is_valid={}, applet_resource_user_id={}", |
||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, |
||||
|
bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id); |
||||
|
|
||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_); |
||||
|
|
||||
|
if (device_index) { |
||||
|
const auto entry_index = devices[device_index.value()].handle.internal_index; |
||||
|
auto& cur_entry = hidbus_status.entries[entry_index]; |
||||
|
auto& device = devices[device_index.value()].device; |
||||
|
devices[device_index.value()].is_device_initializated = false; |
||||
|
device->DeactivateDevice(); |
||||
|
|
||||
|
cur_entry.is_in_focus = true; |
||||
|
cur_entry.is_connected = false; |
||||
|
cur_entry.is_connected_result = ResultSuccess; |
||||
|
cur_entry.is_enabled = false; |
||||
|
cur_entry.is_polling_mode = false; |
||||
|
std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status, |
||||
|
sizeof(hidbus_status)); |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_HID, "Invalid handle"); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
struct Parameters { |
||||
|
bool enable; |
||||
|
INSERT_PADDING_BYTES_NOINIT(7); |
||||
|
BusHandle bus_handle; |
||||
|
u64 inval; |
||||
|
u64 applet_resource_user_id; |
||||
|
}; |
||||
|
static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size."); |
||||
|
|
||||
|
const auto parameters{rp.PopRaw<Parameters>()}; |
||||
|
|
||||
|
LOG_INFO(Service_HID, |
||||
|
"called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " |
||||
|
"player_number={}, is_valid={}, inval={}, applet_resource_user_id{}", |
||||
|
parameters.enable, parameters.bus_handle.abstracted_pad_id, |
||||
|
parameters.bus_handle.bus_type, parameters.bus_handle.internal_index, |
||||
|
parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval, |
||||
|
parameters.applet_resource_user_id); |
||||
|
|
||||
|
const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle); |
||||
|
|
||||
|
if (device_index) { |
||||
|
auto& device = devices[device_index.value()].device; |
||||
|
device->Enable(parameters.enable); |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_HID, "Invalid handle"); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()}; |
||||
|
|
||||
|
LOG_INFO(Service_HID, |
||||
|
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " |
||||
|
"is_valid={}", |
||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, |
||||
|
bus_handle_.player_number, bus_handle_.is_valid); |
||||
|
|
||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_); |
||||
|
|
||||
|
if (device_index) { |
||||
|
const auto& device = devices[device_index.value()].device; |
||||
|
u32 device_id = device->GetDeviceId(); |
||||
|
IPC::ResponseBuilder rb{ctx, 3}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
rb.Push<u32>(device_id); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_HID, "Invalid handle"); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
void HidBus::SendCommandAsync(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
const auto data = ctx.ReadBuffer(); |
||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()}; |
||||
|
|
||||
|
LOG_DEBUG(Service_HID, |
||||
|
"called, data_size={}, abstracted_pad_id={}, bus_type={}, internal_index={}, " |
||||
|
"player_number={}, is_valid={}", |
||||
|
data.size(), bus_handle_.abstracted_pad_id, bus_handle_.bus_type, |
||||
|
bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid); |
||||
|
|
||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_); |
||||
|
|
||||
|
if (device_index) { |
||||
|
auto& device = devices[device_index.value()].device; |
||||
|
device->SetCommand(data); |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_HID, "Invalid handle"); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
}; |
||||
|
|
||||
|
void HidBus::GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()}; |
||||
|
|
||||
|
LOG_DEBUG(Service_HID, |
||||
|
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " |
||||
|
"is_valid={}", |
||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, |
||||
|
bus_handle_.player_number, bus_handle_.is_valid); |
||||
|
|
||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_); |
||||
|
|
||||
|
if (device_index) { |
||||
|
const auto& device = devices[device_index.value()].device; |
||||
|
const std::vector<u8> data = device->GetReply(); |
||||
|
const u64 data_size = ctx.WriteBuffer(data); |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 4}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
rb.Push<u64>(data_size); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_HID, "Invalid handle"); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
}; |
||||
|
|
||||
|
void HidBus::SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()}; |
||||
|
|
||||
|
LOG_INFO(Service_HID, |
||||
|
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " |
||||
|
"is_valid={}", |
||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, |
||||
|
bus_handle_.player_number, bus_handle_.is_valid); |
||||
|
|
||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_); |
||||
|
|
||||
|
if (device_index) { |
||||
|
const auto& device = devices[device_index.value()].device; |
||||
|
IPC::ResponseBuilder rb{ctx, 2, 1}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
rb.PushCopyObjects(device->GetSendCommandAsycEvent()); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_HID, "Invalid handle"); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
}; |
||||
|
|
||||
|
void HidBus::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) { |
||||
|
LOG_DEBUG(Service_HID, "called"); |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 2, 1}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
rb.PushCopyObjects(&system.Kernel().GetHidBusSharedMem()); |
||||
|
} |
||||
|
|
||||
|
void HidBus::EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
const auto t_mem_size{rp.Pop<u32>()}; |
||||
|
const auto t_mem_handle{ctx.GetCopyHandle(0)}; |
||||
|
const auto polling_mode_{rp.PopEnum<JoyPollingMode>()}; |
||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()}; |
||||
|
|
||||
|
ASSERT_MSG(t_mem_size == 0x1000, "t_mem_size is not 0x1000 bytes"); |
||||
|
|
||||
|
auto t_mem = |
||||
|
system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); |
||||
|
|
||||
|
if (t_mem.IsNull()) { |
||||
|
LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
ASSERT_MSG(t_mem->GetSize() == 0x1000, "t_mem has incorrect size"); |
||||
|
|
||||
|
LOG_INFO(Service_HID, |
||||
|
"called, t_mem_handle=0x{:08X}, polling_mode={}, abstracted_pad_id={}, bus_type={}, " |
||||
|
"internal_index={}, player_number={}, is_valid={}", |
||||
|
t_mem_handle, polling_mode_, bus_handle_.abstracted_pad_id, bus_handle_.bus_type, |
||||
|
bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid); |
||||
|
|
||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_); |
||||
|
|
||||
|
if (device_index) { |
||||
|
auto& device = devices[device_index.value()].device; |
||||
|
device->SetPollingMode(polling_mode_); |
||||
|
device->SetTransferMemoryPointer(system.Memory().GetPointer(t_mem->GetSourceAddress())); |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_HID, "Invalid handle"); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
void HidBus::DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
const auto bus_handle_{rp.PopRaw<BusHandle>()}; |
||||
|
|
||||
|
LOG_INFO(Service_HID, |
||||
|
"called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, " |
||||
|
"is_valid={}", |
||||
|
bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index, |
||||
|
bus_handle_.player_number, bus_handle_.is_valid); |
||||
|
|
||||
|
const auto device_index = GetDeviceIndexFromHandle(bus_handle_); |
||||
|
|
||||
|
if (device_index) { |
||||
|
auto& device = devices[device_index.value()].device; |
||||
|
device->DisablePollingMode(); |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
LOG_ERROR(Service_HID, "Invalid handle"); |
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultUnknown); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
void HidBus::SetStatusManagerType(Kernel::HLERequestContext& ctx) { |
||||
|
IPC::RequestParser rp{ctx}; |
||||
|
const auto manager_type{rp.PopEnum<StatusManagerType>()}; |
||||
|
|
||||
|
LOG_WARNING(Service_HID, "(STUBBED) called, manager_type={}", manager_type); |
||||
|
|
||||
|
IPC::ResponseBuilder rb{ctx, 2}; |
||||
|
rb.Push(ResultSuccess); |
||||
|
}; |
||||
|
} // namespace Service::HID
|
||||
@ -0,0 +1,131 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <functional> |
||||
|
|
||||
|
#include "core/hle/service/hid/hidbus/hidbus_base.h" |
||||
|
#include "core/hle/service/kernel_helpers.h" |
||||
|
#include "core/hle/service/service.h" |
||||
|
|
||||
|
namespace Core::Timing { |
||||
|
struct EventType; |
||||
|
} // namespace Core::Timing |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} // namespace Core |
||||
|
|
||||
|
namespace Service::HID { |
||||
|
|
||||
|
class HidBus final : public ServiceFramework<HidBus> { |
||||
|
public: |
||||
|
explicit HidBus(Core::System& system_); |
||||
|
~HidBus() override; |
||||
|
|
||||
|
private: |
||||
|
static const std::size_t max_number_of_handles = 0x13; |
||||
|
|
||||
|
enum class HidBusDeviceId : std::size_t { |
||||
|
RingController = 0x20, |
||||
|
FamicomRight = 0x21, |
||||
|
Starlink = 0x28, |
||||
|
}; |
||||
|
|
||||
|
// This is nn::hidbus::detail::StatusManagerType |
||||
|
enum class StatusManagerType : u32 { |
||||
|
None, |
||||
|
Type16, |
||||
|
Type32, |
||||
|
}; |
||||
|
|
||||
|
// This is nn::hidbus::BusType |
||||
|
enum class BusType : u8 { |
||||
|
LeftJoyRail, |
||||
|
RightJoyRail, |
||||
|
InternalBus, // Lark microphone |
||||
|
|
||||
|
MaxBusType, |
||||
|
}; |
||||
|
|
||||
|
// This is nn::hidbus::BusHandle |
||||
|
struct BusHandle { |
||||
|
u32 abstracted_pad_id; |
||||
|
u8 internal_index; |
||||
|
u8 player_number; |
||||
|
BusType bus_type; |
||||
|
bool is_valid; |
||||
|
}; |
||||
|
static_assert(sizeof(BusHandle) == 0x8, "BusHandle is an invalid size"); |
||||
|
|
||||
|
// This is nn::hidbus::JoyPollingReceivedData |
||||
|
struct JoyPollingReceivedData { |
||||
|
std::array<u8, 0x30> data; |
||||
|
u64 out_size; |
||||
|
u64 sampling_number; |
||||
|
}; |
||||
|
static_assert(sizeof(JoyPollingReceivedData) == 0x40, |
||||
|
"JoyPollingReceivedData is an invalid size"); |
||||
|
|
||||
|
struct HidbusStatusManagerEntry { |
||||
|
u8 is_connected{}; |
||||
|
INSERT_PADDING_BYTES(0x3); |
||||
|
ResultCode is_connected_result{0}; |
||||
|
u8 is_enabled{}; |
||||
|
u8 is_in_focus{}; |
||||
|
u8 is_polling_mode{}; |
||||
|
u8 reserved{}; |
||||
|
JoyPollingMode polling_mode{}; |
||||
|
INSERT_PADDING_BYTES(0x70); // Unknown |
||||
|
}; |
||||
|
static_assert(sizeof(HidbusStatusManagerEntry) == 0x80, |
||||
|
"HidbusStatusManagerEntry is an invalid size"); |
||||
|
|
||||
|
struct HidbusStatusManager { |
||||
|
std::array<HidbusStatusManagerEntry, max_number_of_handles> entries{}; |
||||
|
INSERT_PADDING_BYTES(0x680); // Unused |
||||
|
}; |
||||
|
static_assert(sizeof(HidbusStatusManager) <= 0x1000, "HidbusStatusManager is an invalid size"); |
||||
|
|
||||
|
struct HidbusDevice { |
||||
|
bool is_device_initializated{}; |
||||
|
BusHandle handle{}; |
||||
|
std::unique_ptr<HidbusBase> device{nullptr}; |
||||
|
}; |
||||
|
|
||||
|
void GetBusHandle(Kernel::HLERequestContext& ctx); |
||||
|
void IsExternalDeviceConnected(Kernel::HLERequestContext& ctx); |
||||
|
void Initialize(Kernel::HLERequestContext& ctx); |
||||
|
void Finalize(Kernel::HLERequestContext& ctx); |
||||
|
void EnableExternalDevice(Kernel::HLERequestContext& ctx); |
||||
|
void GetExternalDeviceId(Kernel::HLERequestContext& ctx); |
||||
|
void SendCommandAsync(Kernel::HLERequestContext& ctx); |
||||
|
void GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx); |
||||
|
void SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx); |
||||
|
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx); |
||||
|
void EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx); |
||||
|
void DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx); |
||||
|
void SetStatusManagerType(Kernel::HLERequestContext& ctx); |
||||
|
|
||||
|
void UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); |
||||
|
std::optional<std::size_t> GetDeviceIndexFromHandle(BusHandle handle) const; |
||||
|
|
||||
|
template <typename T> |
||||
|
void MakeDevice(BusHandle handle) { |
||||
|
const auto device_index = GetDeviceIndexFromHandle(handle); |
||||
|
if (device_index) { |
||||
|
devices[device_index.value()].device = |
||||
|
std::make_unique<T>(system.HIDCore(), service_context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool is_hidbus_enabled{false}; |
||||
|
HidbusStatusManager hidbus_status{}; |
||||
|
std::array<HidbusDevice, max_number_of_handles> devices{}; |
||||
|
std::shared_ptr<Core::Timing::EventType> hidbus_update_event; |
||||
|
KernelHelpers::ServiceContext service_context; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Service::HID |
||||
@ -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
|
||||
@ -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 |
||||
@ -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
|
||||
@ -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 |
||||
@ -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
|
||||
@ -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 |
||||
@ -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
|
||||
@ -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 |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue