commit
de1b6cc695
@ -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
|
Loading…
Reference in New Issue