committed by
GitHub
7 changed files with 762 additions and 51 deletions
-
8src/citra_qt/configuration/configure_input.cpp
-
2src/core/CMakeLists.txt
-
231src/core/hle/service/ir/extra_hid.cpp
-
48src/core/hle/service/ir/extra_hid.h
-
489src/core/hle/service/ir/ir_user.cpp
-
33src/core/hle/service/ir/ir_user.h
-
2src/core/settings.cpp
@ -0,0 +1,231 @@ |
|||
// Copyright 2017 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/alignment.h"
|
|||
#include "common/bit_field.h"
|
|||
#include "common/string_util.h"
|
|||
#include "core/core_timing.h"
|
|||
#include "core/hle/service/ir/extra_hid.h"
|
|||
#include "core/settings.h"
|
|||
|
|||
namespace Service { |
|||
namespace IR { |
|||
|
|||
enum class RequestID : u8 { |
|||
/**
|
|||
* ConfigureHIDPolling request |
|||
* Starts HID input polling, or changes the polling interval if it is already started. |
|||
* Inputs: |
|||
* byte 0: request ID |
|||
* byte 1: polling interval in ms |
|||
* byte 2: unknown |
|||
*/ |
|||
ConfigureHIDPolling = 1, |
|||
|
|||
/**
|
|||
* ReadCalibrationData request |
|||
* Reads the calibration data stored in circle pad pro. |
|||
* Inputs: |
|||
* byte 0: request ID |
|||
* byte 1: expected response time in ms? |
|||
* byte 2-3: data offset (aligned to 0x10) |
|||
* byte 4-5: data size (aligned to 0x10) |
|||
*/ |
|||
ReadCalibrationData = 2, |
|||
|
|||
// TODO(wwylele): there are three more request types (id = 3, 4 and 5)
|
|||
}; |
|||
|
|||
enum class ResponseID : u8 { |
|||
|
|||
/**
|
|||
* PollHID response |
|||
* Sends current HID status |
|||
* Output: |
|||
* byte 0: response ID |
|||
* byte 1-3: Right circle pad position. This three bytes are two little-endian 12-bit |
|||
* fields. The first one is for x-axis and the second one is for y-axis. |
|||
* byte 4: bit[0:4] battery level; bit[5] ZL button; bit[6] ZR button; bit[7] R button |
|||
* Note that for the three button fields, the bit is set when the button is NOT pressed. |
|||
* byte 5: unknown |
|||
*/ |
|||
PollHID = 0x10, |
|||
|
|||
/**
|
|||
* ReadCalibrationData response |
|||
* Sends the calibration data reads from circle pad pro. |
|||
* Output: |
|||
* byte 0: resonse ID |
|||
* byte 1-2: data offset (aligned to 0x10) |
|||
* byte 3-4: data size (aligned to 0x10) |
|||
* byte 5-...: calibration data |
|||
*/ |
|||
ReadCalibrationData = 0x11, |
|||
}; |
|||
|
|||
ExtraHID::ExtraHID(SendFunc send_func) : IRDevice(send_func) { |
|||
LoadInputDevices(); |
|||
|
|||
// The data below was retrieved from a New 3DS
|
|||
// TODO(wwylele): this data is probably writable (via request 3?) and thus should be saved to
|
|||
// and loaded from somewhere.
|
|||
calibration_data = std::array<u8, 0x40>{{ |
|||
// 0x00
|
|||
0x00, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, |
|||
// 0x08
|
|||
0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0xF5, |
|||
// 0x10
|
|||
0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, |
|||
// 0x18
|
|||
0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, |
|||
// 0x20
|
|||
0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, |
|||
// 0x28
|
|||
0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, |
|||
// 0x30
|
|||
0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, |
|||
// 0x38
|
|||
0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, |
|||
}}; |
|||
|
|||
hid_polling_callback_id = |
|||
CoreTiming::RegisterEvent("ExtraHID::SendHIDStatus", [this](u64, int cycles_late) { |
|||
SendHIDStatus(); |
|||
CoreTiming::ScheduleEvent(msToCycles(hid_period) - cycles_late, |
|||
hid_polling_callback_id); |
|||
}); |
|||
} |
|||
|
|||
ExtraHID::~ExtraHID() { |
|||
OnDisconnect(); |
|||
} |
|||
|
|||
void ExtraHID::OnConnect() {} |
|||
|
|||
void ExtraHID::OnDisconnect() { |
|||
CoreTiming::UnscheduleEvent(hid_polling_callback_id, 0); |
|||
} |
|||
|
|||
void ExtraHID::HandleConfigureHIDPollingRequest(const std::vector<u8>& request) { |
|||
if (request.size() != 3) { |
|||
LOG_ERROR(Service_IR, "Wrong request size (%zu): %s", request.size(), |
|||
Common::ArrayToString(request.data(), request.size()).c_str()); |
|||
return; |
|||
} |
|||
|
|||
// Change HID input polling interval
|
|||
CoreTiming::UnscheduleEvent(hid_polling_callback_id, 0); |
|||
hid_period = request[1]; |
|||
CoreTiming::ScheduleEvent(msToCycles(hid_period), hid_polling_callback_id); |
|||
} |
|||
|
|||
void ExtraHID::HandleReadCalibrationDataRequest(const std::vector<u8>& request_buf) { |
|||
struct ReadCalibrationDataRequest { |
|||
RequestID request_id; |
|||
u8 expected_response_time; |
|||
u16_le offset; |
|||
u16_le size; |
|||
}; |
|||
static_assert(sizeof(ReadCalibrationDataRequest) == 6, |
|||
"ReadCalibrationDataRequest has wrong size"); |
|||
|
|||
if (request_buf.size() != sizeof(ReadCalibrationDataRequest)) { |
|||
LOG_ERROR(Service_IR, "Wrong request size (%zu): %s", request_buf.size(), |
|||
Common::ArrayToString(request_buf.data(), request_buf.size()).c_str()); |
|||
return; |
|||
} |
|||
|
|||
ReadCalibrationDataRequest request; |
|||
std::memcpy(&request, request_buf.data(), sizeof(request)); |
|||
|
|||
const u16 offset = Common::AlignDown(request.offset, 16); |
|||
const u16 size = Common::AlignDown(request.size, 16); |
|||
|
|||
if (offset + size > calibration_data.size()) { |
|||
LOG_ERROR(Service_IR, "Read beyond the end of calibration data! (offset=%u, size=%u)", |
|||
offset, size); |
|||
return; |
|||
} |
|||
|
|||
std::vector<u8> response(5); |
|||
response[0] = static_cast<u8>(ResponseID::ReadCalibrationData); |
|||
std::memcpy(&response[1], &request.offset, sizeof(request.offset)); |
|||
std::memcpy(&response[3], &request.size, sizeof(request.size)); |
|||
response.insert(response.end(), calibration_data.begin() + offset, |
|||
calibration_data.begin() + offset + size); |
|||
Send(response); |
|||
} |
|||
|
|||
void ExtraHID::OnReceive(const std::vector<u8>& data) { |
|||
switch (static_cast<RequestID>(data[0])) { |
|||
case RequestID::ConfigureHIDPolling: |
|||
HandleConfigureHIDPollingRequest(data); |
|||
break; |
|||
case RequestID::ReadCalibrationData: |
|||
HandleReadCalibrationDataRequest(data); |
|||
break; |
|||
default: |
|||
LOG_ERROR(Service_IR, "Unknown request: %s", |
|||
Common::ArrayToString(data.data(), data.size()).c_str()); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
void ExtraHID::SendHIDStatus() { |
|||
if (is_device_reload_pending.exchange(false)) |
|||
LoadInputDevices(); |
|||
|
|||
struct { |
|||
union { |
|||
BitField<0, 8, u32_le> header; |
|||
BitField<8, 12, u32_le> c_stick_x; |
|||
BitField<20, 12, u32_le> c_stick_y; |
|||
} c_stick; |
|||
union { |
|||
BitField<0, 5, u8> battery_level; |
|||
BitField<5, 1, u8> zl_not_held; |
|||
BitField<6, 1, u8> zr_not_held; |
|||
BitField<7, 1, u8> r_not_held; |
|||
} buttons; |
|||
u8 unknown; |
|||
} response; |
|||
static_assert(sizeof(response) == 6, "HID status response has wrong size!"); |
|||
|
|||
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(); |
|||
|
|||
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; |
|||
|
|||
std::vector<u8> response_buffer(sizeof(response)); |
|||
memcpy(response_buffer.data(), &response, sizeof(response)); |
|||
Send(response_buffer); |
|||
} |
|||
|
|||
void ExtraHID::RequestInputDevicesReload() { |
|||
is_device_reload_pending.store(true); |
|||
} |
|||
|
|||
void ExtraHID::LoadInputDevices() { |
|||
zl = Input::CreateDevice<Input::ButtonDevice>( |
|||
Settings::values.buttons[Settings::NativeButton::ZL]); |
|||
zr = Input::CreateDevice<Input::ButtonDevice>( |
|||
Settings::values.buttons[Settings::NativeButton::ZR]); |
|||
c_stick = Input::CreateDevice<Input::AnalogDevice>( |
|||
Settings::values.analogs[Settings::NativeAnalog::CStick]); |
|||
} |
|||
|
|||
} // namespace IR
|
|||
} // namespace Service
|
|||
@ -0,0 +1,48 @@ |
|||
// Copyright 2017 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <atomic> |
|||
#include "core/frontend/input.h" |
|||
#include "core/hle/service/ir/ir_user.h" |
|||
|
|||
namespace Service { |
|||
namespace IR { |
|||
|
|||
/** |
|||
* An IRDevice emulating Circle Pad Pro or New 3DS additional HID hardware. |
|||
* This device sends periodic udates at a rate configured by the 3DS, and sends calibration data if |
|||
* requested. |
|||
*/ |
|||
class ExtraHID final : public IRDevice { |
|||
public: |
|||
explicit ExtraHID(SendFunc send_func); |
|||
~ExtraHID(); |
|||
|
|||
void OnConnect() override; |
|||
void OnDisconnect() override; |
|||
void OnReceive(const std::vector<u8>& data) override; |
|||
|
|||
/// Requests input devices reload from current settings. Called when the input settings change. |
|||
void RequestInputDevicesReload(); |
|||
|
|||
private: |
|||
void SendHIDStatus(); |
|||
void HandleConfigureHIDPollingRequest(const std::vector<u8>& request); |
|||
void HandleReadCalibrationDataRequest(const std::vector<u8>& request); |
|||
void LoadInputDevices(); |
|||
|
|||
u8 hid_period; |
|||
int hid_polling_callback_id; |
|||
std::array<u8, 0x40> calibration_data; |
|||
std::unique_ptr<Input::ButtonDevice> zl; |
|||
std::unique_ptr<Input::ButtonDevice> zr; |
|||
std::unique_ptr<Input::AnalogDevice> c_stick; |
|||
std::atomic<bool> is_device_reload_pending; |
|||
}; |
|||
|
|||
} // namespace IR |
|||
} // namespace Service |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue