hid: Implement GameCube Controller Vibrations

Implements both SendVibrationGcErmCommand and GetActualVibrationGcErmCommand, and modifies GetVibrationDeviceInfo to account for additional controllers.
master
Morph 2021-02-16 04:46:56 +07:00
parent 3d0394681c
commit ec19a85890
2 changed files with 130 additions and 3 deletions

@ -273,8 +273,8 @@ Hid::Hid(Core::System& system_) : ServiceFramework{system_, "hid"} {
{204, &Hid::PermitVibration, "PermitVibration"}, {204, &Hid::PermitVibration, "PermitVibration"},
{205, &Hid::IsVibrationPermitted, "IsVibrationPermitted"}, {205, &Hid::IsVibrationPermitted, "IsVibrationPermitted"},
{206, &Hid::SendVibrationValues, "SendVibrationValues"}, {206, &Hid::SendVibrationValues, "SendVibrationValues"},
{207, nullptr, "SendVibrationGcErmCommand"}, {207, &Hid::SendVibrationGcErmCommand, "SendVibrationGcErmCommand"},
{208, nullptr, "GetActualVibrationGcErmCommand"}, {208, &Hid::GetActualVibrationGcErmCommand, "GetActualVibrationGcErmCommand"},
{209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
{210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"}, {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
{211, &Hid::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"}, {211, &Hid::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"},
@ -1093,7 +1093,22 @@ void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
VibrationDeviceInfo vibration_device_info; VibrationDeviceInfo vibration_device_info;
switch (vibration_device_handle.npad_type) {
case Controller_NPad::NpadType::ProController:
case Controller_NPad::NpadType::Handheld:
case Controller_NPad::NpadType::JoyconDual:
case Controller_NPad::NpadType::JoyconLeft:
case Controller_NPad::NpadType::JoyconRight:
default:
vibration_device_info.type = VibrationDeviceType::LinearResonantActuator; vibration_device_info.type = VibrationDeviceType::LinearResonantActuator;
break;
case Controller_NPad::NpadType::GameCube:
vibration_device_info.type = VibrationDeviceType::GcErm;
break;
case Controller_NPad::NpadType::Pokeball:
vibration_device_info.type = VibrationDeviceType::Unknown;
break;
}
switch (vibration_device_handle.device_index) { switch (vibration_device_handle.device_index) {
case Controller_NPad::DeviceIndex::Left: case Controller_NPad::DeviceIndex::Left:
@ -1215,6 +1230,108 @@ void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
void Hid::SendVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle vibration_device_handle;
u64 applet_resource_user_id;
VibrationGcErmCommand gc_erm_command;
};
static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
/**
* Note: This uses yuzu-specific behavior such that the StopHard command produces
* vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined below,
* in order to differentiate between Stop and StopHard commands.
* This is done to reuse the controller vibration functions made for regular controllers.
*/
const auto vibration_value = [parameters] {
switch (parameters.gc_erm_command) {
case VibrationGcErmCommand::Stop:
return Controller_NPad::VibrationValue{
.amp_low = 0.0f,
.freq_low = 160.0f,
.amp_high = 0.0f,
.freq_high = 320.0f,
};
case VibrationGcErmCommand::Start:
return Controller_NPad::VibrationValue{
.amp_low = 1.0f,
.freq_low = 160.0f,
.amp_high = 1.0f,
.freq_high = 320.0f,
};
case VibrationGcErmCommand::StopHard:
return Controller_NPad::VibrationValue{
.amp_low = 0.0f,
.freq_low = 0.0f,
.amp_high = 0.0f,
.freq_high = 0.0f,
};
default:
return Controller_NPad::DEFAULT_VIBRATION_VALUE;
}
}();
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.VibrateController(parameters.vibration_device_handle, vibration_value);
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}, "
"gc_erm_command={}",
parameters.vibration_device_handle.npad_type,
parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id,
parameters.gc_erm_command);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void Hid::GetActualVibrationGcErmCommand(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
struct Parameters {
Controller_NPad::DeviceHandle vibration_device_handle;
INSERT_PADDING_WORDS_NOINIT(1);
u64 applet_resource_user_id;
};
const auto parameters{rp.PopRaw<Parameters>()};
const auto last_vibration = applet_resource->GetController<Controller_NPad>(HidController::NPad)
.GetLastVibration(parameters.vibration_device_handle);
const auto gc_erm_command = [last_vibration] {
if (last_vibration.amp_low != 0.0f || last_vibration.amp_high != 0.0f) {
return VibrationGcErmCommand::Start;
}
/**
* Note: This uses yuzu-specific behavior such that the StopHard command produces
* vibrations where freq_low == 0.0f and freq_high == 0.0f, as defined in the HID function
* SendVibrationGcErmCommand, in order to differentiate between Stop and StopHard commands.
* This is done to reuse the controller vibration functions made for regular controllers.
*/
if (last_vibration.freq_low == 0.0f && last_vibration.freq_high == 0.0f) {
return VibrationGcErmCommand::StopHard;
}
return VibrationGcErmCommand::Stop;
}();
LOG_DEBUG(Service_HID,
"called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
parameters.vibration_device_handle.npad_type,
parameters.vibration_device_handle.npad_id,
parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id);
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
rb.PushEnum(gc_erm_command);
}
void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()}; const auto applet_resource_user_id{rp.Pop<u64>()};

@ -136,6 +136,8 @@ private:
void PermitVibration(Kernel::HLERequestContext& ctx); void PermitVibration(Kernel::HLERequestContext& ctx);
void IsVibrationPermitted(Kernel::HLERequestContext& ctx); void IsVibrationPermitted(Kernel::HLERequestContext& ctx);
void SendVibrationValues(Kernel::HLERequestContext& ctx); void SendVibrationValues(Kernel::HLERequestContext& ctx);
void SendVibrationGcErmCommand(Kernel::HLERequestContext& ctx);
void GetActualVibrationGcErmCommand(Kernel::HLERequestContext& ctx);
void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx); void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
void EndPermitVibrationSession(Kernel::HLERequestContext& ctx); void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
void IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx); void IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx);
@ -154,7 +156,9 @@ private:
void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx); void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
enum class VibrationDeviceType : u32 { enum class VibrationDeviceType : u32 {
Unknown = 0,
LinearResonantActuator = 1, LinearResonantActuator = 1,
GcErm = 2,
}; };
enum class VibrationDevicePosition : u32 { enum class VibrationDevicePosition : u32 {
@ -163,6 +167,12 @@ private:
Right = 2, Right = 2,
}; };
enum class VibrationGcErmCommand : u64 {
Stop = 0,
Start = 1,
StopHard = 2,
};
struct VibrationDeviceInfo { struct VibrationDeviceInfo {
VibrationDeviceType type{}; VibrationDeviceType type{};
VibrationDevicePosition position{}; VibrationDevicePosition position{};