committed by
Narr the Reg
8 changed files with 848 additions and 827 deletions
-
6src/input_common/CMakeLists.txt
-
419src/input_common/drivers/gc_adapter.cpp
-
128src/input_common/drivers/gc_adapter.h
-
320src/input_common/drivers/tas_input.cpp
-
200src/input_common/drivers/tas_input.h
-
168src/input_common/gcadapter/gc_adapter.h
-
356src/input_common/gcadapter/gc_poller.cpp
-
78src/input_common/gcadapter/gc_poller.h
@ -0,0 +1,128 @@ |
|||||
|
// Copyright 2014 Dolphin Emulator Project |
||||
|
// Licensed under GPLv2+ |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <mutex> |
||||
|
#include <stop_token> |
||||
|
#include <thread> |
||||
|
|
||||
|
#include "input_common/input_engine.h" |
||||
|
|
||||
|
struct libusb_context; |
||||
|
struct libusb_device; |
||||
|
struct libusb_device_handle; |
||||
|
|
||||
|
namespace InputCommon { |
||||
|
|
||||
|
class LibUSBContext; |
||||
|
class LibUSBDeviceHandle; |
||||
|
|
||||
|
class GCAdapter : public InputCommon::InputEngine { |
||||
|
public: |
||||
|
explicit GCAdapter(const std::string input_engine_); |
||||
|
~GCAdapter(); |
||||
|
|
||||
|
bool SetRumble(const PadIdentifier& identifier, |
||||
|
const Input::VibrationStatus vibration) override; |
||||
|
|
||||
|
/// Used for automapping features |
||||
|
std::vector<Common::ParamPackage> GetInputDevices() const override; |
||||
|
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; |
||||
|
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; |
||||
|
std::string GetUIName(const Common::ParamPackage& params) const override; |
||||
|
|
||||
|
private: |
||||
|
enum class PadButton { |
||||
|
Undefined = 0x0000, |
||||
|
ButtonLeft = 0x0001, |
||||
|
ButtonRight = 0x0002, |
||||
|
ButtonDown = 0x0004, |
||||
|
ButtonUp = 0x0008, |
||||
|
TriggerZ = 0x0010, |
||||
|
TriggerR = 0x0020, |
||||
|
TriggerL = 0x0040, |
||||
|
ButtonA = 0x0100, |
||||
|
ButtonB = 0x0200, |
||||
|
ButtonX = 0x0400, |
||||
|
ButtonY = 0x0800, |
||||
|
ButtonStart = 0x1000, |
||||
|
}; |
||||
|
|
||||
|
enum class PadAxes : u8 { |
||||
|
StickX, |
||||
|
StickY, |
||||
|
SubstickX, |
||||
|
SubstickY, |
||||
|
TriggerLeft, |
||||
|
TriggerRight, |
||||
|
Undefined, |
||||
|
}; |
||||
|
|
||||
|
enum class ControllerTypes { |
||||
|
None, |
||||
|
Wired, |
||||
|
Wireless, |
||||
|
}; |
||||
|
|
||||
|
struct GCController { |
||||
|
ControllerTypes type = ControllerTypes::None; |
||||
|
PadIdentifier identifier{}; |
||||
|
bool enable_vibration = false; |
||||
|
u8 rumble_amplitude{}; |
||||
|
std::array<u8, 6> axis_origin{}; |
||||
|
u8 reset_origin_counter{}; |
||||
|
}; |
||||
|
|
||||
|
using AdapterPayload = std::array<u8, 37>; |
||||
|
|
||||
|
void UpdatePadType(std::size_t port, ControllerTypes pad_type); |
||||
|
void UpdateControllers(const AdapterPayload& adapter_payload); |
||||
|
void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); |
||||
|
void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); |
||||
|
|
||||
|
void AdapterInputThread(std::stop_token stop_token); |
||||
|
|
||||
|
void AdapterScanThread(std::stop_token stop_token); |
||||
|
|
||||
|
bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); |
||||
|
|
||||
|
/// For use in initialization, querying devices to find the adapter |
||||
|
bool Setup(); |
||||
|
|
||||
|
/// Returns true if we successfully gain access to GC Adapter |
||||
|
bool CheckDeviceAccess(); |
||||
|
|
||||
|
/// Captures GC Adapter endpoint address |
||||
|
/// Returns true if the endpoint was set correctly |
||||
|
bool GetGCEndpoint(libusb_device* device); |
||||
|
|
||||
|
/// Returns true if there is a device connected to port |
||||
|
bool DeviceConnected(std::size_t port) const; |
||||
|
|
||||
|
/// For shutting down, clear all data, join all threads, release usb |
||||
|
void Reset(); |
||||
|
|
||||
|
void UpdateVibrations(); |
||||
|
// Updates vibration state of all controllers |
||||
|
void SendVibrations(); |
||||
|
std::unique_ptr<LibUSBDeviceHandle> usb_adapter_handle; |
||||
|
std::array<GCController, 4> pads; |
||||
|
|
||||
|
std::jthread adapter_input_thread; |
||||
|
std::jthread adapter_scan_thread; |
||||
|
bool restart_scan_thread{}; |
||||
|
|
||||
|
std::unique_ptr<LibUSBContext> libusb_ctx; |
||||
|
|
||||
|
u8 input_endpoint{0}; |
||||
|
u8 output_endpoint{0}; |
||||
|
u8 input_error_counter{0}; |
||||
|
u8 output_error_counter{0}; |
||||
|
int vibration_counter{0}; |
||||
|
|
||||
|
bool rumble_enabled{true}; |
||||
|
bool vibration_changed{true}; |
||||
|
}; |
||||
|
} // namespace InputCommon |
||||
@ -0,0 +1,320 @@ |
|||||
|
// Copyright 2021 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2+
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <cstring>
|
||||
|
#include <regex>
|
||||
|
#include <fmt/format.h>
|
||||
|
|
||||
|
#include "common/fs/file.h"
|
||||
|
#include "common/fs/fs_types.h"
|
||||
|
#include "common/fs/path_util.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "common/settings.h"
|
||||
|
#include "input_common/drivers/tas_input.h"
|
||||
|
|
||||
|
namespace InputCommon::TasInput { |
||||
|
|
||||
|
enum TasAxes : u8 { |
||||
|
StickX, |
||||
|
StickY, |
||||
|
SubstickX, |
||||
|
SubstickY, |
||||
|
Undefined, |
||||
|
}; |
||||
|
|
||||
|
// Supported keywords and buttons from a TAS file
|
||||
|
constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = { |
||||
|
std::pair{"KEY_A", TasButton::BUTTON_A}, |
||||
|
{"KEY_B", TasButton::BUTTON_B}, |
||||
|
{"KEY_X", TasButton::BUTTON_X}, |
||||
|
{"KEY_Y", TasButton::BUTTON_Y}, |
||||
|
{"KEY_LSTICK", TasButton::STICK_L}, |
||||
|
{"KEY_RSTICK", TasButton::STICK_R}, |
||||
|
{"KEY_L", TasButton::TRIGGER_L}, |
||||
|
{"KEY_R", TasButton::TRIGGER_R}, |
||||
|
{"KEY_PLUS", TasButton::BUTTON_PLUS}, |
||||
|
{"KEY_MINUS", TasButton::BUTTON_MINUS}, |
||||
|
{"KEY_DLEFT", TasButton::BUTTON_LEFT}, |
||||
|
{"KEY_DUP", TasButton::BUTTON_UP}, |
||||
|
{"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, |
||||
|
{"KEY_DDOWN", TasButton::BUTTON_DOWN}, |
||||
|
{"KEY_SL", TasButton::BUTTON_SL}, |
||||
|
{"KEY_SR", TasButton::BUTTON_SR}, |
||||
|
{"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, |
||||
|
{"KEY_HOME", TasButton::BUTTON_HOME}, |
||||
|
{"KEY_ZL", TasButton::TRIGGER_ZL}, |
||||
|
{"KEY_ZR", TasButton::TRIGGER_ZR}, |
||||
|
}; |
||||
|
|
||||
|
Tas::Tas(const std::string input_engine_) : InputCommon::InputEngine(input_engine_) { |
||||
|
for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) { |
||||
|
PadIdentifier identifier{ |
||||
|
.guid = Common::UUID{}, |
||||
|
.port = player_index, |
||||
|
.pad = 0, |
||||
|
}; |
||||
|
PreSetController(identifier); |
||||
|
} |
||||
|
ClearInput(); |
||||
|
if (!Settings::values.tas_enable) { |
||||
|
needs_reset = true; |
||||
|
return; |
||||
|
} |
||||
|
LoadTasFiles(); |
||||
|
} |
||||
|
|
||||
|
Tas::~Tas() { |
||||
|
Stop(); |
||||
|
}; |
||||
|
|
||||
|
void Tas::LoadTasFiles() { |
||||
|
script_length = 0; |
||||
|
for (size_t i = 0; i < commands.size(); i++) { |
||||
|
LoadTasFile(i); |
||||
|
if (commands[i].size() > script_length) { |
||||
|
script_length = commands[i].size(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void Tas::LoadTasFile(size_t player_index) { |
||||
|
if (!commands[player_index].empty()) { |
||||
|
commands[player_index].clear(); |
||||
|
} |
||||
|
std::string file = |
||||
|
Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / |
||||
|
fmt::format("script0-{}.txt", player_index + 1), |
||||
|
Common::FS::FileType::BinaryFile); |
||||
|
std::stringstream command_line(file); |
||||
|
std::string line; |
||||
|
int frame_no = 0; |
||||
|
while (std::getline(command_line, line, '\n')) { |
||||
|
if (line.empty()) { |
||||
|
continue; |
||||
|
} |
||||
|
std::smatch m; |
||||
|
|
||||
|
std::stringstream linestream(line); |
||||
|
std::string segment; |
||||
|
std::vector<std::string> seglist; |
||||
|
|
||||
|
while (std::getline(linestream, segment, ' ')) { |
||||
|
seglist.push_back(segment); |
||||
|
} |
||||
|
|
||||
|
if (seglist.size() < 4) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
while (frame_no < std::stoi(seglist.at(0))) { |
||||
|
commands[player_index].push_back({}); |
||||
|
frame_no++; |
||||
|
} |
||||
|
|
||||
|
TASCommand command = { |
||||
|
.buttons = ReadCommandButtons(seglist.at(1)), |
||||
|
.l_axis = ReadCommandAxis(seglist.at(2)), |
||||
|
.r_axis = ReadCommandAxis(seglist.at(3)), |
||||
|
}; |
||||
|
commands[player_index].push_back(command); |
||||
|
frame_no++; |
||||
|
} |
||||
|
LOG_INFO(Input, "TAS file loaded! {} frames", frame_no); |
||||
|
} |
||||
|
|
||||
|
void Tas::WriteTasFile(std::u8string file_name) { |
||||
|
std::string output_text; |
||||
|
for (size_t frame = 0; frame < record_commands.size(); frame++) { |
||||
|
const TASCommand& line = record_commands[frame]; |
||||
|
output_text += fmt::format("{} {} {} {} {}\n", frame, WriteCommandButtons(line.buttons), |
||||
|
WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis)); |
||||
|
} |
||||
|
const auto bytes_written = Common::FS::WriteStringToFile( |
||||
|
Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name, |
||||
|
Common::FS::FileType::TextFile, output_text); |
||||
|
if (bytes_written == output_text.size()) { |
||||
|
LOG_INFO(Input, "TAS file written to file!"); |
||||
|
} else { |
||||
|
LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written, |
||||
|
output_text.size()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void Tas::RecordInput(u32 buttons, TasAnalog left_axis, TasAnalog right_axis) { |
||||
|
last_input = { |
||||
|
.buttons = buttons, |
||||
|
.l_axis = FlipAxisY(left_axis), |
||||
|
.r_axis = FlipAxisY(right_axis), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
TasAnalog Tas::FlipAxisY(TasAnalog old) { |
||||
|
return { |
||||
|
.x = old.x, |
||||
|
.y = -old.y, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
std::tuple<TasState, size_t, size_t> Tas::GetStatus() const { |
||||
|
TasState state; |
||||
|
if (is_recording) { |
||||
|
return {TasState::Recording, 0, record_commands.size()}; |
||||
|
} |
||||
|
|
||||
|
if (is_running) { |
||||
|
state = TasState::Running; |
||||
|
} else { |
||||
|
state = TasState::Stopped; |
||||
|
} |
||||
|
|
||||
|
return {state, current_command, script_length}; |
||||
|
} |
||||
|
|
||||
|
void Tas::UpdateThread() { |
||||
|
if (!Settings::values.tas_enable) { |
||||
|
if (is_running) { |
||||
|
Stop(); |
||||
|
} |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (is_recording) { |
||||
|
record_commands.push_back(last_input); |
||||
|
} |
||||
|
if (needs_reset) { |
||||
|
current_command = 0; |
||||
|
needs_reset = false; |
||||
|
LoadTasFiles(); |
||||
|
LOG_DEBUG(Input, "tas_reset done"); |
||||
|
} |
||||
|
|
||||
|
if (!is_running) { |
||||
|
ClearInput(); |
||||
|
return; |
||||
|
} |
||||
|
if (current_command < script_length) { |
||||
|
LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); |
||||
|
size_t frame = current_command++; |
||||
|
for (size_t player_index = 0; player_index < commands.size(); player_index++) { |
||||
|
TASCommand command{}; |
||||
|
if (frame < commands[player_index].size()) { |
||||
|
command = commands[player_index][frame]; |
||||
|
} |
||||
|
|
||||
|
PadIdentifier identifier{ |
||||
|
.guid = Common::UUID{}, |
||||
|
.port = player_index, |
||||
|
.pad = 0, |
||||
|
}; |
||||
|
for (std::size_t i = 0; i < sizeof(command.buttons); ++i) { |
||||
|
const bool button_status = (command.buttons & (1U << i)) != 0; |
||||
|
const int button = static_cast<int>(i); |
||||
|
SetButton(identifier, button, button_status); |
||||
|
} |
||||
|
SetAxis(identifier, TasAxes::StickX, command.l_axis.x); |
||||
|
SetAxis(identifier, TasAxes::StickY, command.l_axis.y); |
||||
|
SetAxis(identifier, TasAxes::SubstickX, command.r_axis.x); |
||||
|
SetAxis(identifier, TasAxes::SubstickY, command.r_axis.y); |
||||
|
} |
||||
|
} else { |
||||
|
is_running = Settings::values.tas_loop.GetValue(); |
||||
|
current_command = 0; |
||||
|
ClearInput(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void Tas::ClearInput() { |
||||
|
ResetButtonState(); |
||||
|
ResetAnalogState(); |
||||
|
} |
||||
|
|
||||
|
TasAnalog Tas::ReadCommandAxis(const std::string& line) const { |
||||
|
std::stringstream linestream(line); |
||||
|
std::string segment; |
||||
|
std::vector<std::string> seglist; |
||||
|
|
||||
|
while (std::getline(linestream, segment, ';')) { |
||||
|
seglist.push_back(segment); |
||||
|
} |
||||
|
|
||||
|
const float x = std::stof(seglist.at(0)) / 32767.0f; |
||||
|
const float y = std::stof(seglist.at(1)) / 32767.0f; |
||||
|
|
||||
|
return {x, y}; |
||||
|
} |
||||
|
|
||||
|
u32 Tas::ReadCommandButtons(const std::string& data) const { |
||||
|
std::stringstream button_text(data); |
||||
|
std::string line; |
||||
|
u32 buttons = 0; |
||||
|
while (std::getline(button_text, line, ';')) { |
||||
|
for (auto [text, tas_button] : text_to_tas_button) { |
||||
|
if (text == line) { |
||||
|
buttons |= static_cast<u32>(tas_button); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return buttons; |
||||
|
} |
||||
|
|
||||
|
std::string Tas::WriteCommandButtons(u32 buttons) const { |
||||
|
std::string returns = ""; |
||||
|
for (auto [text_button, tas_button] : text_to_tas_button) { |
||||
|
if ((buttons & static_cast<u32>(tas_button)) != 0) |
||||
|
returns += fmt::format("{};", text_button.substr(4)); |
||||
|
} |
||||
|
return returns.empty() ? "NONE" : returns.substr(2); |
||||
|
} |
||||
|
|
||||
|
std::string Tas::WriteCommandAxis(TasAnalog analog) const { |
||||
|
return fmt::format("{};{}", analog.x * 32767, analog.y * 32767); |
||||
|
} |
||||
|
|
||||
|
void Tas::StartStop() { |
||||
|
if (!Settings::values.tas_enable) { |
||||
|
return; |
||||
|
} |
||||
|
if (is_running) { |
||||
|
Stop(); |
||||
|
} else { |
||||
|
is_running = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void Tas::Stop() { |
||||
|
is_running = false; |
||||
|
} |
||||
|
|
||||
|
void Tas::Reset() { |
||||
|
if (!Settings::values.tas_enable) { |
||||
|
return; |
||||
|
} |
||||
|
needs_reset = true; |
||||
|
} |
||||
|
|
||||
|
bool Tas::Record() { |
||||
|
if (!Settings::values.tas_enable) { |
||||
|
return true; |
||||
|
} |
||||
|
is_recording = !is_recording; |
||||
|
return is_recording; |
||||
|
} |
||||
|
|
||||
|
void Tas::SaveRecording(bool overwrite_file) { |
||||
|
if (is_recording) { |
||||
|
return; |
||||
|
} |
||||
|
if (record_commands.empty()) { |
||||
|
return; |
||||
|
} |
||||
|
WriteTasFile(u8"record.txt"); |
||||
|
if (overwrite_file) { |
||||
|
WriteTasFile(u8"script0-1.txt"); |
||||
|
} |
||||
|
needs_reset = true; |
||||
|
record_commands.clear(); |
||||
|
} |
||||
|
|
||||
|
} // namespace InputCommon::TasInput
|
||||
@ -0,0 +1,200 @@ |
|||||
|
// Copyright 2020 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 "common/settings_input.h" |
||||
|
#include "input_common/input_engine.h" |
||||
|
#include "input_common/main.h" |
||||
|
|
||||
|
/* |
||||
|
To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below |
||||
|
Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt |
||||
|
for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players). |
||||
|
|
||||
|
A script file has the same format as TAS-nx uses, so final files will look like this: |
||||
|
|
||||
|
1 KEY_B 0;0 0;0 |
||||
|
6 KEY_ZL 0;0 0;0 |
||||
|
41 KEY_ZL;KEY_Y 0;0 0;0 |
||||
|
43 KEY_X;KEY_A 32767;0 0;0 |
||||
|
44 KEY_A 32767;0 0;0 |
||||
|
45 KEY_A 32767;0 0;0 |
||||
|
46 KEY_A 32767;0 0;0 |
||||
|
47 KEY_A 32767;0 0;0 |
||||
|
|
||||
|
After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey |
||||
|
CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file |
||||
|
has. Playback can be started or stopped using CTRL+F5. |
||||
|
|
||||
|
However, for playback to actually work, the correct input device has to be selected: In the Controls |
||||
|
menu, select TAS from the device list for the controller that the script should be played on. |
||||
|
|
||||
|
Recording a new script file is really simple: Just make sure that the proper device (not TAS) is |
||||
|
connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke |
||||
|
again (CTRL+F7). The new script will be saved at the location previously selected, as the filename |
||||
|
record.txt. |
||||
|
|
||||
|
For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller |
||||
|
P1). |
||||
|
*/ |
||||
|
|
||||
|
namespace InputCommon::TasInput { |
||||
|
|
||||
|
constexpr size_t PLAYER_NUMBER = 10; |
||||
|
|
||||
|
enum class TasButton : u32 { |
||||
|
BUTTON_A = 1U << 0, |
||||
|
BUTTON_B = 1U << 1, |
||||
|
BUTTON_X = 1U << 2, |
||||
|
BUTTON_Y = 1U << 3, |
||||
|
STICK_L = 1U << 4, |
||||
|
STICK_R = 1U << 5, |
||||
|
TRIGGER_L = 1U << 6, |
||||
|
TRIGGER_R = 1U << 7, |
||||
|
TRIGGER_ZL = 1U << 8, |
||||
|
TRIGGER_ZR = 1U << 9, |
||||
|
BUTTON_PLUS = 1U << 10, |
||||
|
BUTTON_MINUS = 1U << 11, |
||||
|
BUTTON_LEFT = 1U << 12, |
||||
|
BUTTON_UP = 1U << 13, |
||||
|
BUTTON_RIGHT = 1U << 14, |
||||
|
BUTTON_DOWN = 1U << 15, |
||||
|
BUTTON_SL = 1U << 16, |
||||
|
BUTTON_SR = 1U << 17, |
||||
|
BUTTON_HOME = 1U << 18, |
||||
|
BUTTON_CAPTURE = 1U << 19, |
||||
|
}; |
||||
|
|
||||
|
struct TasAnalog { |
||||
|
float x{}; |
||||
|
float y{}; |
||||
|
}; |
||||
|
|
||||
|
enum class TasState { |
||||
|
Running, |
||||
|
Recording, |
||||
|
Stopped, |
||||
|
}; |
||||
|
|
||||
|
class Tas final : public InputCommon::InputEngine { |
||||
|
public: |
||||
|
explicit Tas(const std::string input_engine_); |
||||
|
~Tas(); |
||||
|
|
||||
|
/** |
||||
|
* Changes the input status that will be stored in each frame |
||||
|
* @param buttons: bitfield with the status of the buttons |
||||
|
* @param left_axis: value of the left axis |
||||
|
* @param right_axis: value of the right axis |
||||
|
*/ |
||||
|
void RecordInput(u32 buttons, TasAnalog left_axis, TasAnalog right_axis); |
||||
|
|
||||
|
// Main loop that records or executes input |
||||
|
void UpdateThread(); |
||||
|
|
||||
|
// Sets the flag to start or stop the TAS command excecution and swaps controllers profiles |
||||
|
void StartStop(); |
||||
|
|
||||
|
// Stop the TAS and reverts any controller profile |
||||
|
void Stop(); |
||||
|
|
||||
|
// Sets the flag to reload the file and start from the begining in the next update |
||||
|
void Reset(); |
||||
|
|
||||
|
/** |
||||
|
* Sets the flag to enable or disable recording of inputs |
||||
|
* @return Returns true if the current recording status is enabled |
||||
|
*/ |
||||
|
bool Record(); |
||||
|
|
||||
|
/** |
||||
|
* Saves contents of record_commands on a file |
||||
|
* @param overwrite_file: Indicates if player 1 should be overwritten |
||||
|
*/ |
||||
|
void SaveRecording(bool overwrite_file); |
||||
|
|
||||
|
/** |
||||
|
* Returns the current status values of TAS playback/recording |
||||
|
* @return Tuple of |
||||
|
* TasState indicating the current state out of Running ; |
||||
|
* Current playback progress ; |
||||
|
* Total length of script file currently loaded or being recorded |
||||
|
*/ |
||||
|
std::tuple<TasState, size_t, size_t> GetStatus() const; |
||||
|
|
||||
|
private: |
||||
|
struct TASCommand { |
||||
|
u32 buttons{}; |
||||
|
TasAnalog l_axis{}; |
||||
|
TasAnalog r_axis{}; |
||||
|
}; |
||||
|
|
||||
|
/// Loads TAS files from all players |
||||
|
void LoadTasFiles(); |
||||
|
|
||||
|
/** Loads TAS file from the specified player |
||||
|
* @param player_index: player number where data is going to be stored |
||||
|
*/ |
||||
|
void LoadTasFile(size_t player_index); |
||||
|
|
||||
|
/** Writes a TAS file from the recorded commands |
||||
|
* @param file_name: name of the file to be written |
||||
|
*/ |
||||
|
void WriteTasFile(std::u8string file_name); |
||||
|
|
||||
|
/** Inverts the Y axis polarity |
||||
|
* @param old: value of the axis |
||||
|
* @return new value of the axis |
||||
|
*/ |
||||
|
TasAnalog FlipAxisY(TasAnalog old); |
||||
|
|
||||
|
/** |
||||
|
* Parses a string containing the axis values. X and Y have a range from -32767 to 32767 |
||||
|
* @param line: string containing axis values with the following format "x;y" |
||||
|
* @return Returns a TAS analog object with axis values with range from -1.0 to 1.0 |
||||
|
*/ |
||||
|
TasAnalog ReadCommandAxis(const std::string& line) const; |
||||
|
|
||||
|
/** |
||||
|
* Parses a string containing the button values. Each button is represented by it's text format |
||||
|
* specified in text_to_tas_button array |
||||
|
* @param line: string containing button name with the following format "a;b;c;d..." |
||||
|
* @return Returns a u32 with each bit representing the status of a button |
||||
|
*/ |
||||
|
u32 ReadCommandButtons(const std::string& line) const; |
||||
|
|
||||
|
/** |
||||
|
* Reset state of all players |
||||
|
*/ |
||||
|
void ClearInput(); |
||||
|
|
||||
|
/** |
||||
|
* Converts an u32 containing the button status into the text equivalent |
||||
|
* @param buttons: bitfield with the status of the buttons |
||||
|
* @return Returns a string with the name of the buttons to be written to the file |
||||
|
*/ |
||||
|
std::string WriteCommandButtons(u32 buttons) const; |
||||
|
|
||||
|
/** |
||||
|
* Converts an TAS analog object containing the axis status into the text equivalent |
||||
|
* @param data: value of the axis |
||||
|
* @return A string with the value of the axis to be written to the file |
||||
|
*/ |
||||
|
std::string WriteCommandAxis(TasAnalog data) const; |
||||
|
|
||||
|
size_t script_length{0}; |
||||
|
bool is_old_input_saved{false}; |
||||
|
bool is_recording{false}; |
||||
|
bool is_running{false}; |
||||
|
bool needs_reset{false}; |
||||
|
std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{}; |
||||
|
std::vector<TASCommand> record_commands{}; |
||||
|
size_t current_command{0}; |
||||
|
TASCommand last_input{}; // only used for recording |
||||
|
}; |
||||
|
} // namespace InputCommon::TasInput |
||||
@ -1,168 +0,0 @@ |
|||||
// Copyright 2014 Dolphin Emulator Project |
|
||||
// Licensed under GPLv2+ |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
#include <algorithm> |
|
||||
#include <functional> |
|
||||
#include <mutex> |
|
||||
#include <thread> |
|
||||
#include <unordered_map> |
|
||||
#include "common/common_types.h" |
|
||||
#include "common/threadsafe_queue.h" |
|
||||
#include "input_common/main.h" |
|
||||
|
|
||||
struct libusb_context; |
|
||||
struct libusb_device; |
|
||||
struct libusb_device_handle; |
|
||||
|
|
||||
namespace GCAdapter { |
|
||||
|
|
||||
enum class PadButton { |
|
||||
Undefined = 0x0000, |
|
||||
ButtonLeft = 0x0001, |
|
||||
ButtonRight = 0x0002, |
|
||||
ButtonDown = 0x0004, |
|
||||
ButtonUp = 0x0008, |
|
||||
TriggerZ = 0x0010, |
|
||||
TriggerR = 0x0020, |
|
||||
TriggerL = 0x0040, |
|
||||
ButtonA = 0x0100, |
|
||||
ButtonB = 0x0200, |
|
||||
ButtonX = 0x0400, |
|
||||
ButtonY = 0x0800, |
|
||||
ButtonStart = 0x1000, |
|
||||
// Below is for compatibility with "AxisButton" type |
|
||||
Stick = 0x2000, |
|
||||
}; |
|
||||
|
|
||||
enum class PadAxes : u8 { |
|
||||
StickX, |
|
||||
StickY, |
|
||||
SubstickX, |
|
||||
SubstickY, |
|
||||
TriggerLeft, |
|
||||
TriggerRight, |
|
||||
Undefined, |
|
||||
}; |
|
||||
|
|
||||
enum class ControllerTypes { |
|
||||
None, |
|
||||
Wired, |
|
||||
Wireless, |
|
||||
}; |
|
||||
|
|
||||
struct GCPadStatus { |
|
||||
std::size_t port{}; |
|
||||
|
|
||||
PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits |
|
||||
|
|
||||
PadAxes axis{PadAxes::Undefined}; |
|
||||
s16 axis_value{}; |
|
||||
u8 axis_threshold{50}; |
|
||||
}; |
|
||||
|
|
||||
struct GCController { |
|
||||
ControllerTypes type{}; |
|
||||
bool enable_vibration{}; |
|
||||
u8 rumble_amplitude{}; |
|
||||
u16 buttons{}; |
|
||||
PadButton last_button{}; |
|
||||
std::array<s16, 6> axis_values{}; |
|
||||
std::array<u8, 6> axis_origin{}; |
|
||||
u8 reset_origin_counter{}; |
|
||||
}; |
|
||||
|
|
||||
class Adapter { |
|
||||
public: |
|
||||
Adapter(); |
|
||||
~Adapter(); |
|
||||
|
|
||||
/// Request a vibration for a controller |
|
||||
bool RumblePlay(std::size_t port, u8 amplitude); |
|
||||
|
|
||||
/// Used for polling |
|
||||
void BeginConfiguration(); |
|
||||
void EndConfiguration(); |
|
||||
|
|
||||
Common::SPSCQueue<GCPadStatus>& GetPadQueue(); |
|
||||
const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const; |
|
||||
|
|
||||
GCController& GetPadState(std::size_t port); |
|
||||
const GCController& GetPadState(std::size_t port) const; |
|
||||
|
|
||||
/// Returns true if there is a device connected to port |
|
||||
bool DeviceConnected(std::size_t port) const; |
|
||||
|
|
||||
/// Used for automapping features |
|
||||
std::vector<Common::ParamPackage> GetInputDevices() const; |
|
||||
InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; |
|
||||
InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; |
|
||||
|
|
||||
private: |
|
||||
using AdapterPayload = std::array<u8, 37>; |
|
||||
|
|
||||
void UpdatePadType(std::size_t port, ControllerTypes pad_type); |
|
||||
void UpdateControllers(const AdapterPayload& adapter_payload); |
|
||||
void UpdateYuzuSettings(std::size_t port); |
|
||||
void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); |
|
||||
void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); |
|
||||
void UpdateVibrations(); |
|
||||
|
|
||||
void AdapterInputThread(); |
|
||||
|
|
||||
void AdapterScanThread(); |
|
||||
|
|
||||
bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); |
|
||||
|
|
||||
// Updates vibration state of all controllers |
|
||||
void SendVibrations(); |
|
||||
|
|
||||
/// For use in initialization, querying devices to find the adapter |
|
||||
void Setup(); |
|
||||
|
|
||||
/// Resets status of all GC controller devices to a disconnected state |
|
||||
void ResetDevices(); |
|
||||
|
|
||||
/// Resets status of device connected to a disconnected state |
|
||||
void ResetDevice(std::size_t port); |
|
||||
|
|
||||
/// Returns true if we successfully gain access to GC Adapter |
|
||||
bool CheckDeviceAccess(); |
|
||||
|
|
||||
/// Captures GC Adapter endpoint address |
|
||||
/// Returns true if the endpoint was set correctly |
|
||||
bool GetGCEndpoint(libusb_device* device); |
|
||||
|
|
||||
/// For shutting down, clear all data, join all threads, release usb |
|
||||
void Reset(); |
|
||||
|
|
||||
// Join all threads |
|
||||
void JoinThreads(); |
|
||||
|
|
||||
// Release usb handles |
|
||||
void ClearLibusbHandle(); |
|
||||
|
|
||||
libusb_device_handle* usb_adapter_handle = nullptr; |
|
||||
std::array<GCController, 4> pads; |
|
||||
Common::SPSCQueue<GCPadStatus> pad_queue; |
|
||||
|
|
||||
std::thread adapter_input_thread; |
|
||||
std::thread adapter_scan_thread; |
|
||||
bool adapter_input_thread_running; |
|
||||
bool adapter_scan_thread_running; |
|
||||
bool restart_scan_thread; |
|
||||
|
|
||||
libusb_context* libusb_ctx; |
|
||||
|
|
||||
u8 input_endpoint{0}; |
|
||||
u8 output_endpoint{0}; |
|
||||
u8 input_error_counter{0}; |
|
||||
u8 output_error_counter{0}; |
|
||||
int vibration_counter{0}; |
|
||||
|
|
||||
bool configuring{false}; |
|
||||
bool rumble_enabled{true}; |
|
||||
bool vibration_changed{true}; |
|
||||
}; |
|
||||
} // namespace GCAdapter |
|
||||
@ -1,356 +0,0 @@ |
|||||
// Copyright 2020 yuzu Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <atomic>
|
|
||||
#include <list>
|
|
||||
#include <mutex>
|
|
||||
#include <utility>
|
|
||||
#include "common/assert.h"
|
|
||||
#include "common/threadsafe_queue.h"
|
|
||||
#include "input_common/gcadapter/gc_adapter.h"
|
|
||||
#include "input_common/gcadapter/gc_poller.h"
|
|
||||
|
|
||||
namespace InputCommon { |
|
||||
|
|
||||
class GCButton final : public Input::ButtonDevice { |
|
||||
public: |
|
||||
explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter) |
|
||||
: port(port_), button(button_), gcadapter(adapter) {} |
|
||||
|
|
||||
~GCButton() override; |
|
||||
|
|
||||
bool GetStatus() const override { |
|
||||
if (gcadapter->DeviceConnected(port)) { |
|
||||
return (gcadapter->GetPadState(port).buttons & button) != 0; |
|
||||
} |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
const u32 port; |
|
||||
const s32 button; |
|
||||
const GCAdapter::Adapter* gcadapter; |
|
||||
}; |
|
||||
|
|
||||
class GCAxisButton final : public Input::ButtonDevice { |
|
||||
public: |
|
||||
explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_, |
|
||||
const GCAdapter::Adapter* adapter) |
|
||||
: port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), |
|
||||
gcadapter(adapter) {} |
|
||||
|
|
||||
bool GetStatus() const override { |
|
||||
if (gcadapter->DeviceConnected(port)) { |
|
||||
const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis); |
|
||||
const float axis_value = current_axis_value / 128.0f; |
|
||||
if (trigger_if_greater) { |
|
||||
// TODO: Might be worthwile to set a slider for the trigger threshold. It is
|
|
||||
// currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
|
|
||||
return axis_value > threshold; |
|
||||
} |
|
||||
return axis_value < -threshold; |
|
||||
} |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
const u32 port; |
|
||||
const u32 axis; |
|
||||
float threshold; |
|
||||
bool trigger_if_greater; |
|
||||
const GCAdapter::Adapter* gcadapter; |
|
||||
}; |
|
||||
|
|
||||
GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) |
|
||||
: adapter(std::move(adapter_)) {} |
|
||||
|
|
||||
GCButton::~GCButton() = default; |
|
||||
|
|
||||
std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) { |
|
||||
const auto button_id = params.Get("button", 0); |
|
||||
const auto port = static_cast<u32>(params.Get("port", 0)); |
|
||||
|
|
||||
constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick); |
|
||||
|
|
||||
// button is not an axis/stick button
|
|
||||
if (button_id != PAD_STICK_ID) { |
|
||||
return std::make_unique<GCButton>(port, button_id, adapter.get()); |
|
||||
} |
|
||||
|
|
||||
// For Axis buttons, used by the binary sticks.
|
|
||||
if (button_id == PAD_STICK_ID) { |
|
||||
const int axis = params.Get("axis", 0); |
|
||||
const float threshold = params.Get("threshold", 0.25f); |
|
||||
const std::string direction_name = params.Get("direction", ""); |
|
||||
bool trigger_if_greater; |
|
||||
if (direction_name == "+") { |
|
||||
trigger_if_greater = true; |
|
||||
} else if (direction_name == "-") { |
|
||||
trigger_if_greater = false; |
|
||||
} else { |
|
||||
trigger_if_greater = true; |
|
||||
LOG_ERROR(Input, "Unknown direction {}", direction_name); |
|
||||
} |
|
||||
return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater, |
|
||||
adapter.get()); |
|
||||
} |
|
||||
|
|
||||
return nullptr; |
|
||||
} |
|
||||
|
|
||||
Common::ParamPackage GCButtonFactory::GetNextInput() const { |
|
||||
Common::ParamPackage params; |
|
||||
GCAdapter::GCPadStatus pad; |
|
||||
auto& queue = adapter->GetPadQueue(); |
|
||||
while (queue.Pop(pad)) { |
|
||||
// This while loop will break on the earliest detected button
|
|
||||
params.Set("engine", "gcpad"); |
|
||||
params.Set("port", static_cast<s32>(pad.port)); |
|
||||
if (pad.button != GCAdapter::PadButton::Undefined) { |
|
||||
params.Set("button", static_cast<u16>(pad.button)); |
|
||||
} |
|
||||
|
|
||||
// For Axis button implementation
|
|
||||
if (pad.axis != GCAdapter::PadAxes::Undefined) { |
|
||||
params.Set("axis", static_cast<u8>(pad.axis)); |
|
||||
params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick)); |
|
||||
params.Set("threshold", "0.25"); |
|
||||
if (pad.axis_value > 0) { |
|
||||
params.Set("direction", "+"); |
|
||||
} else { |
|
||||
params.Set("direction", "-"); |
|
||||
} |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
return params; |
|
||||
} |
|
||||
|
|
||||
void GCButtonFactory::BeginConfiguration() { |
|
||||
polling = true; |
|
||||
adapter->BeginConfiguration(); |
|
||||
} |
|
||||
|
|
||||
void GCButtonFactory::EndConfiguration() { |
|
||||
polling = false; |
|
||||
adapter->EndConfiguration(); |
|
||||
} |
|
||||
|
|
||||
class GCAnalog final : public Input::AnalogDevice { |
|
||||
public: |
|
||||
explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_, |
|
||||
float deadzone_, float range_, const GCAdapter::Adapter* adapter) |
|
||||
: port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_), |
|
||||
deadzone(deadzone_), range(range_), gcadapter(adapter) {} |
|
||||
|
|
||||
float GetAxis(u32 axis) const { |
|
||||
if (gcadapter->DeviceConnected(port)) { |
|
||||
std::lock_guard lock{mutex}; |
|
||||
const auto axis_value = |
|
||||
static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis)); |
|
||||
return (axis_value) / (100.0f * range); |
|
||||
} |
|
||||
return 0.0f; |
|
||||
} |
|
||||
|
|
||||
std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { |
|
||||
float x = GetAxis(analog_axis_x); |
|
||||
float y = GetAxis(analog_axis_y); |
|
||||
if (invert_x) { |
|
||||
x = -x; |
|
||||
} |
|
||||
if (invert_y) { |
|
||||
y = -y; |
|
||||
} |
|
||||
// Make sure the coordinates are in the unit circle,
|
|
||||
// otherwise normalize it.
|
|
||||
float r = x * x + y * y; |
|
||||
if (r > 1.0f) { |
|
||||
r = std::sqrt(r); |
|
||||
x /= r; |
|
||||
y /= r; |
|
||||
} |
|
||||
|
|
||||
return {x, y}; |
|
||||
} |
|
||||
|
|
||||
std::tuple<float, float> GetStatus() const override { |
|
||||
const auto [x, y] = GetAnalog(axis_x, axis_y); |
|
||||
const float r = std::sqrt((x * x) + (y * y)); |
|
||||
if (r > deadzone) { |
|
||||
return {x / r * (r - deadzone) / (1 - deadzone), |
|
||||
y / r * (r - deadzone) / (1 - deadzone)}; |
|
||||
} |
|
||||
return {0.0f, 0.0f}; |
|
||||
} |
|
||||
|
|
||||
std::tuple<float, float> GetRawStatus() const override { |
|
||||
const float x = GetAxis(axis_x); |
|
||||
const float y = GetAxis(axis_y); |
|
||||
return {x, y}; |
|
||||
} |
|
||||
|
|
||||
Input::AnalogProperties GetAnalogProperties() const override { |
|
||||
return {deadzone, range, 0.5f}; |
|
||||
} |
|
||||
|
|
||||
bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { |
|
||||
const auto [x, y] = GetStatus(); |
|
||||
const float directional_deadzone = 0.5f; |
|
||||
switch (direction) { |
|
||||
case Input::AnalogDirection::RIGHT: |
|
||||
return x > directional_deadzone; |
|
||||
case Input::AnalogDirection::LEFT: |
|
||||
return x < -directional_deadzone; |
|
||||
case Input::AnalogDirection::UP: |
|
||||
return y > directional_deadzone; |
|
||||
case Input::AnalogDirection::DOWN: |
|
||||
return y < -directional_deadzone; |
|
||||
} |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
const u32 port; |
|
||||
const u32 axis_x; |
|
||||
const u32 axis_y; |
|
||||
const bool invert_x; |
|
||||
const bool invert_y; |
|
||||
const float deadzone; |
|
||||
const float range; |
|
||||
const GCAdapter::Adapter* gcadapter; |
|
||||
mutable std::mutex mutex; |
|
||||
}; |
|
||||
|
|
||||
/// An analog device factory that creates analog devices from GC Adapter
|
|
||||
GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) |
|
||||
: adapter(std::move(adapter_)) {} |
|
||||
|
|
||||
/**
|
|
||||
* Creates analog device from joystick axes |
|
||||
* @param params contains parameters for creating the device: |
|
||||
* - "port": the nth gcpad on the adapter |
|
||||
* - "axis_x": the index of the axis to be bind as x-axis |
|
||||
* - "axis_y": the index of the axis to be bind as y-axis |
|
||||
*/ |
|
||||
std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) { |
|
||||
const auto port = static_cast<u32>(params.Get("port", 0)); |
|
||||
const auto axis_x = static_cast<u32>(params.Get("axis_x", 0)); |
|
||||
const auto axis_y = static_cast<u32>(params.Get("axis_y", 1)); |
|
||||
const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); |
|
||||
const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); |
|
||||
const std::string invert_x_value = params.Get("invert_x", "+"); |
|
||||
const std::string invert_y_value = params.Get("invert_y", "+"); |
|
||||
const bool invert_x = invert_x_value == "-"; |
|
||||
const bool invert_y = invert_y_value == "-"; |
|
||||
|
|
||||
return std::make_unique<GCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range, |
|
||||
adapter.get()); |
|
||||
} |
|
||||
|
|
||||
void GCAnalogFactory::BeginConfiguration() { |
|
||||
polling = true; |
|
||||
adapter->BeginConfiguration(); |
|
||||
} |
|
||||
|
|
||||
void GCAnalogFactory::EndConfiguration() { |
|
||||
polling = false; |
|
||||
adapter->EndConfiguration(); |
|
||||
} |
|
||||
|
|
||||
Common::ParamPackage GCAnalogFactory::GetNextInput() { |
|
||||
GCAdapter::GCPadStatus pad; |
|
||||
Common::ParamPackage params; |
|
||||
auto& queue = adapter->GetPadQueue(); |
|
||||
while (queue.Pop(pad)) { |
|
||||
if (pad.button != GCAdapter::PadButton::Undefined) { |
|
||||
params.Set("engine", "gcpad"); |
|
||||
params.Set("port", static_cast<s32>(pad.port)); |
|
||||
params.Set("button", static_cast<u16>(pad.button)); |
|
||||
return params; |
|
||||
} |
|
||||
if (pad.axis == GCAdapter::PadAxes::Undefined || |
|
||||
std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) { |
|
||||
continue; |
|
||||
} |
|
||||
// An analog device needs two axes, so we need to store the axis for later and wait for
|
|
||||
// a second input event. The axes also must be from the same joystick.
|
|
||||
const u8 axis = static_cast<u8>(pad.axis); |
|
||||
if (axis == 0 || axis == 1) { |
|
||||
analog_x_axis = 0; |
|
||||
analog_y_axis = 1; |
|
||||
controller_number = static_cast<s32>(pad.port); |
|
||||
break; |
|
||||
} |
|
||||
if (axis == 2 || axis == 3) { |
|
||||
analog_x_axis = 2; |
|
||||
analog_y_axis = 3; |
|
||||
controller_number = static_cast<s32>(pad.port); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
if (analog_x_axis == -1) { |
|
||||
analog_x_axis = axis; |
|
||||
controller_number = static_cast<s32>(pad.port); |
|
||||
} else if (analog_y_axis == -1 && analog_x_axis != axis && |
|
||||
controller_number == static_cast<s32>(pad.port)) { |
|
||||
analog_y_axis = axis; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
if (analog_x_axis != -1 && analog_y_axis != -1) { |
|
||||
params.Set("engine", "gcpad"); |
|
||||
params.Set("port", controller_number); |
|
||||
params.Set("axis_x", analog_x_axis); |
|
||||
params.Set("axis_y", analog_y_axis); |
|
||||
params.Set("invert_x", "+"); |
|
||||
params.Set("invert_y", "+"); |
|
||||
analog_x_axis = -1; |
|
||||
analog_y_axis = -1; |
|
||||
controller_number = -1; |
|
||||
return params; |
|
||||
} |
|
||||
return params; |
|
||||
} |
|
||||
|
|
||||
class GCVibration final : public Input::VibrationDevice { |
|
||||
public: |
|
||||
explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter) |
|
||||
: port(port_), gcadapter(adapter) {} |
|
||||
|
|
||||
u8 GetStatus() const override { |
|
||||
return gcadapter->RumblePlay(port, 0); |
|
||||
} |
|
||||
|
|
||||
bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high, |
|
||||
[[maybe_unused]] f32 freq_high) const override { |
|
||||
const auto mean_amplitude = (amp_low + amp_high) * 0.5f; |
|
||||
const auto processed_amplitude = |
|
||||
static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); |
|
||||
|
|
||||
return gcadapter->RumblePlay(port, processed_amplitude); |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
const u32 port; |
|
||||
GCAdapter::Adapter* gcadapter; |
|
||||
}; |
|
||||
|
|
||||
/// An vibration device factory that creates vibration devices from GC Adapter
|
|
||||
GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) |
|
||||
: adapter(std::move(adapter_)) {} |
|
||||
|
|
||||
/**
|
|
||||
* Creates a vibration device from a joystick |
|
||||
* @param params contains parameters for creating the device: |
|
||||
* - "port": the nth gcpad on the adapter |
|
||||
*/ |
|
||||
std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create( |
|
||||
const Common::ParamPackage& params) { |
|
||||
const auto port = static_cast<u32>(params.Get("port", 0)); |
|
||||
|
|
||||
return std::make_unique<GCVibration>(port, adapter.get()); |
|
||||
} |
|
||||
|
|
||||
} // namespace InputCommon
|
|
||||
@ -1,78 +0,0 @@ |
|||||
// Copyright 2020 yuzu Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include "core/frontend/input.h" |
|
||||
#include "input_common/gcadapter/gc_adapter.h" |
|
||||
|
|
||||
namespace InputCommon { |
|
||||
|
|
||||
/** |
|
||||
* A button device factory representing a gcpad. It receives gcpad events and forward them |
|
||||
* to all button devices it created. |
|
||||
*/ |
|
||||
class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> { |
|
||||
public: |
|
||||
explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); |
|
||||
|
|
||||
/** |
|
||||
* Creates a button device from a button press |
|
||||
* @param params contains parameters for creating the device: |
|
||||
* - "code": the code of the key to bind with the button |
|
||||
*/ |
|
||||
std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; |
|
||||
|
|
||||
Common::ParamPackage GetNextInput() const; |
|
||||
|
|
||||
/// For device input configuration/polling |
|
||||
void BeginConfiguration(); |
|
||||
void EndConfiguration(); |
|
||||
|
|
||||
bool IsPolling() const { |
|
||||
return polling; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
std::shared_ptr<GCAdapter::Adapter> adapter; |
|
||||
bool polling = false; |
|
||||
}; |
|
||||
|
|
||||
/// An analog device factory that creates analog devices from GC Adapter |
|
||||
class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> { |
|
||||
public: |
|
||||
explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); |
|
||||
|
|
||||
std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; |
|
||||
Common::ParamPackage GetNextInput(); |
|
||||
|
|
||||
/// For device input configuration/polling |
|
||||
void BeginConfiguration(); |
|
||||
void EndConfiguration(); |
|
||||
|
|
||||
bool IsPolling() const { |
|
||||
return polling; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
std::shared_ptr<GCAdapter::Adapter> adapter; |
|
||||
int analog_x_axis = -1; |
|
||||
int analog_y_axis = -1; |
|
||||
int controller_number = -1; |
|
||||
bool polling = false; |
|
||||
}; |
|
||||
|
|
||||
/// A vibration device factory creates vibration devices from GC Adapter |
|
||||
class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> { |
|
||||
public: |
|
||||
explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); |
|
||||
|
|
||||
std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override; |
|
||||
|
|
||||
private: |
|
||||
std::shared_ptr<GCAdapter::Adapter> adapter; |
|
||||
}; |
|
||||
|
|
||||
} // namespace InputCommon |
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue