mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-26 04:46:53 +01:00
Support multiple controllers for local multiplayer (#1583)
* Support multiple controllers for local co-op
This commit is contained in:
+245
-124
@@ -18,6 +18,7 @@
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Controller {
|
||||
@@ -107,18 +108,19 @@ struct ControllerState {
|
||||
|
||||
class GameController {
|
||||
public:
|
||||
GameController() = default;
|
||||
explicit GameController(int player): m_player(player) {}
|
||||
|
||||
KYTY_CLASS_NO_COPY(GameController);
|
||||
|
||||
[[nodiscard]] int Handle() const { return PAD_HANDLE + m_player; }
|
||||
|
||||
int InitPort();
|
||||
int OpenPort(int user_id, int type, int index);
|
||||
int GetPortHandle(int user_id, int type, int index);
|
||||
int ClosePort(int handle);
|
||||
int CheckPortHandle(int handle);
|
||||
|
||||
void Connect(int id);
|
||||
void Disconnect(int id);
|
||||
void SetDevices(int active_id);
|
||||
void Button(int id, uint32_t button, bool down);
|
||||
void Axis(int id, Axis axis, int value);
|
||||
void RightStick(int id, int x, int y);
|
||||
@@ -128,7 +130,6 @@ public:
|
||||
void ResetOrientation();
|
||||
void ToggleGyroEmulation(int id);
|
||||
void ResetInputState();
|
||||
void ReleaseHostPads();
|
||||
void GetConnectionInfo(bool* flag, int* count);
|
||||
void SetVibration(uint8_t large_motor, uint8_t small_motor);
|
||||
int GetActiveControllerId();
|
||||
@@ -145,8 +146,10 @@ private:
|
||||
enum class PortState { Uninitialized, Closed, Open };
|
||||
|
||||
int CheckPortHandleLocked(int handle) const;
|
||||
[[nodiscard]] bool PadOpenArgsAreValid(int user_id, int type, int index) const;
|
||||
|
||||
const int m_player;
|
||||
|
||||
void CheckActive();
|
||||
void AddState();
|
||||
// Emulated tilt: the right stick drives pitch and roll instead of the stick axes.
|
||||
void UpdateEmulatedTilt();
|
||||
@@ -157,7 +160,6 @@ private:
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
PortState m_port_state = PortState::Uninitialized;
|
||||
std::vector<int> m_connected_ids;
|
||||
int m_active_id = -1;
|
||||
bool m_connected = false;
|
||||
int m_connected_count = 0;
|
||||
@@ -184,7 +186,39 @@ private:
|
||||
std::array<std::atomic<uint32_t>, 3> m_setting_steps {};
|
||||
};
|
||||
|
||||
static GameController* g_controller = nullptr;
|
||||
static std::array<std::unique_ptr<GameController>, Config::MAX_PLAYERS> g_players;
|
||||
|
||||
// Maps host gamepads to players; the keyboard follows the configured keyboard player.
|
||||
static Common::Mutex g_router_mutex;
|
||||
static std::vector<std::pair<int, int>> g_pad_players;
|
||||
|
||||
static uint32_t g_keyboard_player = 0;
|
||||
|
||||
static void RefreshPlayers();
|
||||
static void StopHostPad(SDL_Gamepad* pad);
|
||||
|
||||
static int KeyboardPlayer() {
|
||||
return g_keyboard_player == 0 ? 0 : static_cast<int>(g_keyboard_player) - 1;
|
||||
}
|
||||
|
||||
// Caller holds g_router_mutex.
|
||||
static GameController* PlayerForDevice(int id) {
|
||||
if (id == HOST_INPUT_CONTROLLER_ID) {
|
||||
return g_players[KeyboardPlayer()].get();
|
||||
}
|
||||
for (const auto& [pad, player]: g_pad_players) {
|
||||
if (pad == id) {
|
||||
return player >= 0 ? g_players[player].get() : nullptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static GameController* PlayerForHandle(int handle) {
|
||||
return handle >= PAD_HANDLE && handle < PAD_HANDLE + Config::MAX_PLAYERS
|
||||
? g_players[handle - PAD_HANDLE].get()
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
static void pad_fill_data(PadData* data, const ControllerState& state, bool connected,
|
||||
int connected_count) {
|
||||
@@ -228,13 +262,15 @@ constexpr std::array SPEAKER_VOLUME = {1.0f, 0.0f, 0.02f, 0.12f, 0.42f};
|
||||
constexpr std::array INTENSITY = {1.0f, 0.0f, 0.33f, 0.66f};
|
||||
|
||||
void CycleSetting(Setting setting) {
|
||||
if (g_controller != nullptr) {
|
||||
g_controller->CycleSetting(setting);
|
||||
for (const auto& player: g_players) {
|
||||
if (player != nullptr) {
|
||||
player->CycleSetting(setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float GetSettingScale(Setting setting) {
|
||||
return g_controller != nullptr ? g_controller->GetSettingScale(setting) : 1.0f;
|
||||
return g_players[0] != nullptr ? g_players[0]->GetSettingScale(setting) : 1.0f;
|
||||
}
|
||||
|
||||
void GameController::CycleSetting(Setting setting) {
|
||||
@@ -404,35 +440,53 @@ static int32_t TriggerEffectState(const PadTriggerEffectCommand& command, int va
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
EXIT_IF(g_controller != nullptr);
|
||||
EXIT_IF(g_players[0] != nullptr);
|
||||
|
||||
g_controller = new GameController;
|
||||
g_controller->Connect(HOST_INPUT_CONTROLLER_ID);
|
||||
for (int i = 0; i < Config::MAX_PLAYERS; i++) {
|
||||
g_players[i] = std::make_unique<GameController>(i);
|
||||
}
|
||||
g_keyboard_player = Config::GetKeyboardPlayer();
|
||||
RefreshPlayers();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
EmergencyShutdown();
|
||||
delete g_controller;
|
||||
g_controller = nullptr;
|
||||
for (auto& player: g_players) {
|
||||
player.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void EmergencyShutdown() {
|
||||
if (g_controller != nullptr) {
|
||||
g_controller->ReleaseHostPads();
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
DualSenseHaptics::Shutdown();
|
||||
std::vector<SDL_Gamepad*> pads;
|
||||
for (const auto& [id, player]: g_pad_players) {
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id))) {
|
||||
StopHostPad(pad);
|
||||
pads.push_back(pad);
|
||||
}
|
||||
}
|
||||
if (!pads.empty()) {
|
||||
SDL_Delay(RELEASE_FLUSH_MS);
|
||||
for (auto* pad: pads) {
|
||||
SDL_CloseGamepad(pad);
|
||||
}
|
||||
}
|
||||
g_pad_players.clear();
|
||||
for (const auto& player: g_players) {
|
||||
if (player != nullptr) {
|
||||
player->SetDevices(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::Connect(int id) {
|
||||
void GameController::SetDevices(int active_id) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
if (std::find(m_connected_ids.begin(), m_connected_ids.end(), id) != m_connected_ids.end()) {
|
||||
if (m_active_id == active_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connected_ids.push_back(id);
|
||||
|
||||
if (id != HOST_INPUT_CONTROLLER_ID) {
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
|
||||
if (active_id != HOST_INPUT_CONTROLLER_ID && active_id != -1) {
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(active_id));
|
||||
pad != nullptr) {
|
||||
if (const auto& color = Config::GetControllerColor()) {
|
||||
(void)SDL_SetGamepadLED(pad, (*color)[0], (*color)[1], (*color)[2]);
|
||||
@@ -446,42 +500,11 @@ void GameController::Connect(int id) {
|
||||
}
|
||||
}
|
||||
|
||||
CheckActive();
|
||||
}
|
||||
|
||||
void GameController::Disconnect(int id) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
const auto it = std::find(m_connected_ids.begin(), m_connected_ids.end(), id);
|
||||
EXIT_IF(it == m_connected_ids.end());
|
||||
|
||||
m_connected_ids.erase(it);
|
||||
|
||||
CheckActive();
|
||||
}
|
||||
|
||||
void GameController::CheckActive() {
|
||||
int new_active_id = -1;
|
||||
bool new_connected = false;
|
||||
|
||||
if (!m_connected_ids.empty()) {
|
||||
new_active_id = m_connected_ids[0];
|
||||
for (const auto id: m_connected_ids) {
|
||||
if (id != HOST_INPUT_CONTROLLER_ID) {
|
||||
new_active_id = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
new_connected = true;
|
||||
}
|
||||
|
||||
if (m_connected == new_connected && m_active_id == new_active_id) {
|
||||
return;
|
||||
}
|
||||
const bool new_connected = active_id != -1;
|
||||
if (!m_connected && new_connected) {
|
||||
m_connected_count++;
|
||||
}
|
||||
m_active_id = new_active_id;
|
||||
m_active_id = active_id;
|
||||
m_connected = new_connected;
|
||||
m_state = {};
|
||||
m_gyro_time = 0;
|
||||
@@ -513,7 +536,7 @@ void GameController::AddState() {
|
||||
void GameController::Button(int id, uint32_t button, bool down) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
// The keyboard shares the player-1 pad with the active gamepad.
|
||||
// Keyboard events are routed to the configured player's port.
|
||||
if (m_active_id == id || id == HOST_INPUT_CONTROLLER_ID) {
|
||||
m_state.time = LibKernel::KernelGetProcessTime();
|
||||
|
||||
@@ -798,39 +821,16 @@ void GameController::ResetInputState() {
|
||||
AddState();
|
||||
}
|
||||
|
||||
void GameController::ReleaseHostPads() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
DualSenseHaptics::Shutdown();
|
||||
|
||||
std::vector<SDL_Gamepad*> pads;
|
||||
for (const auto id: m_connected_ids) {
|
||||
if (id == HOST_INPUT_CONTROLLER_ID) {
|
||||
continue;
|
||||
}
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
|
||||
pad != nullptr) {
|
||||
if (SDL_GetGamepadType(pad) == SDL_GAMEPAD_TYPE_PS5) {
|
||||
DualSenseEffects effect {};
|
||||
effect.enable_bits = 0x0c;
|
||||
effect.right_trigger[0] = 0x05;
|
||||
effect.left_trigger[0] = 0x05;
|
||||
(void)SDL_SendGamepadEffect(pad, &effect, sizeof(effect));
|
||||
}
|
||||
(void)SDL_RumbleGamepad(pad, 0, 0, 0);
|
||||
(void)SDL_SetGamepadLED(pad, 0, 0, 0);
|
||||
pads.push_back(pad);
|
||||
}
|
||||
static void StopHostPad(SDL_Gamepad* pad) {
|
||||
if (SDL_GetGamepadType(pad) == SDL_GAMEPAD_TYPE_PS5) {
|
||||
DualSenseEffects effect {};
|
||||
effect.enable_bits = 0x0c;
|
||||
effect.right_trigger[0] = 0x05;
|
||||
effect.left_trigger[0] = 0x05;
|
||||
(void)SDL_SendGamepadEffect(pad, &effect, sizeof(effect));
|
||||
}
|
||||
|
||||
if (!pads.empty()) {
|
||||
SDL_Delay(RELEASE_FLUSH_MS);
|
||||
for (auto* pad: pads) {
|
||||
SDL_CloseGamepad(pad);
|
||||
}
|
||||
}
|
||||
|
||||
m_connected_ids.clear();
|
||||
CheckActive();
|
||||
(void)SDL_RumbleGamepad(pad, 0, 0, 0);
|
||||
(void)SDL_SetGamepadLED(pad, 0, 0, 0);
|
||||
}
|
||||
|
||||
void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) {
|
||||
@@ -847,7 +847,9 @@ void GameController::ApplyVibration() {
|
||||
const auto scale = duration != 0 ? GetSettingScale(Setting::VibrationIntensity) : 0.0f;
|
||||
const auto large_motor = Scale(m_vibration[0], scale);
|
||||
const auto small_motor = Scale(m_vibration[1], scale);
|
||||
if (DualSenseHaptics::SetVibration(m_active_id, large_motor, small_motor, duration)) {
|
||||
// The DualSense haptics backend drives a single pad, so only player 1 uses it.
|
||||
if (m_player == 0 &&
|
||||
DualSenseHaptics::SetVibration(m_active_id, large_motor, small_motor, duration)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -999,49 +1001,149 @@ int GameController::ReadStates(ControllerState* states, int states_num, bool* fl
|
||||
return ret_num;
|
||||
}
|
||||
|
||||
// Keep every opened pad, including those waiting for a free player slot.
|
||||
// Caller holds g_router_mutex.
|
||||
static void RefreshPlayers() {
|
||||
std::array<bool, Config::MAX_PLAYERS> taken {};
|
||||
const int keyboard = KeyboardPlayer();
|
||||
if (g_keyboard_player != 0) {
|
||||
taken[keyboard] = true;
|
||||
}
|
||||
for (auto& [id, player]: g_pad_players) {
|
||||
if (g_keyboard_player != 0 && player == keyboard) {
|
||||
player = -1;
|
||||
}
|
||||
if (player >= 0) {
|
||||
taken[player] = true;
|
||||
}
|
||||
}
|
||||
for (auto& [id, player]: g_pad_players) {
|
||||
if (player >= 0) {
|
||||
continue;
|
||||
}
|
||||
const auto free = std::find(taken.begin(), taken.end(), false);
|
||||
if (free != taken.end()) {
|
||||
player = static_cast<int>(free - taken.begin());
|
||||
*free = true;
|
||||
LOGF("Controller %d assigned to player %d\n", id, player + 1);
|
||||
}
|
||||
}
|
||||
std::array<int, Config::MAX_PLAYERS> active;
|
||||
active.fill(-1);
|
||||
active[keyboard] = HOST_INPUT_CONTROLLER_ID;
|
||||
for (const auto& [id, player]: g_pad_players) {
|
||||
if (player >= 0) {
|
||||
active[player] = id;
|
||||
}
|
||||
}
|
||||
// Stop old owners before configuring new owners of the same physical pad.
|
||||
for (int player = 0; player < Config::MAX_PLAYERS; player++) {
|
||||
const int previous = g_players[player]->GetActiveControllerId();
|
||||
if (previous != active[player]) {
|
||||
if (player == 0) {
|
||||
(void)DualSenseHaptics::SetVibration(previous, 0, 0, 0);
|
||||
}
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(previous))) {
|
||||
StopHostPad(pad);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int player = 0; player < Config::MAX_PLAYERS; player++) {
|
||||
g_players[player]->SetDevices(active[player]);
|
||||
}
|
||||
}
|
||||
|
||||
void Connect(int id) {
|
||||
g_controller->Connect(id);
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
if (std::any_of(g_pad_players.begin(), g_pad_players.end(),
|
||||
[id](const auto& pad) { return pad.first == id; })) {
|
||||
return;
|
||||
}
|
||||
g_pad_players.emplace_back(id, -1);
|
||||
RefreshPlayers();
|
||||
}
|
||||
|
||||
void Disconnect(int id) {
|
||||
g_controller->Disconnect(id);
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
std::erase_if(g_pad_players, [id](const auto& pad) { return pad.first == id; });
|
||||
RefreshPlayers();
|
||||
}
|
||||
|
||||
void CycleKeyboardPlayer() {
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
g_keyboard_player = (g_keyboard_player + 1) % (Config::MAX_PLAYERS + 1);
|
||||
RefreshPlayers();
|
||||
if (g_keyboard_player == 0) {
|
||||
LOGF("Keyboard shared with player 1\n");
|
||||
} else {
|
||||
LOGF("Keyboard assigned to player %u\n", g_keyboard_player);
|
||||
}
|
||||
}
|
||||
|
||||
void SetButton(int id, uint32_t button, bool down) {
|
||||
g_controller->Button(id, button, down);
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
if (auto* player = PlayerForDevice(id)) {
|
||||
player->Button(id, button, down);
|
||||
}
|
||||
}
|
||||
|
||||
void SetAxis(int id, Axis axis, int value) {
|
||||
g_controller->Axis(id, axis, value);
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
if (auto* player = PlayerForDevice(id)) {
|
||||
player->Axis(id, axis, value);
|
||||
}
|
||||
}
|
||||
|
||||
void SetRightStick(int id, int x, int y) {
|
||||
g_controller->RightStick(id, x, y);
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
if (auto* player = PlayerForDevice(id)) {
|
||||
player->RightStick(id, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void SetTouchPad(int id, int finger, bool down, float x, float y) {
|
||||
g_controller->TouchPad(id, finger, down, x, y);
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
if (auto* player = PlayerForDevice(id)) {
|
||||
player->TouchPad(id, finger, down, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void SetSensor(int id, Sensor sensor, const float* data, uint64_t time_us) {
|
||||
g_controller->Motion(id, sensor, data, time_us);
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
if (auto* player = PlayerForDevice(id)) {
|
||||
player->Motion(id, sensor, data, time_us);
|
||||
}
|
||||
}
|
||||
|
||||
void ToggleGyroEmulation(int id) {
|
||||
g_controller->ToggleGyroEmulation(id);
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
if (auto* player = PlayerForDevice(id)) {
|
||||
player->ToggleGyroEmulation(id);
|
||||
}
|
||||
}
|
||||
|
||||
void ResetInputState() {
|
||||
g_controller->ResetInputState();
|
||||
for (const auto& player: g_players) {
|
||||
if (player != nullptr) {
|
||||
player->ResetInputState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GetActiveControllerId() {
|
||||
return g_controller != nullptr ? g_controller->GetActiveControllerId() : -1;
|
||||
return g_players[0] != nullptr ? g_players[0]->GetActiveControllerId() : -1;
|
||||
}
|
||||
|
||||
static bool PadOpenArgsAreValid(int user_id, int type, int index) {
|
||||
// The host keyboard and gamepad feed one standard port for the configured user.
|
||||
return user_id == Config::GetUserId() && type == 0 && index == 0;
|
||||
bool IsPlayerConnected(int player) {
|
||||
if (player < 0 || player >= Config::MAX_PLAYERS || g_players[player] == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return g_players[player]->GetActiveControllerId() != -1;
|
||||
}
|
||||
|
||||
bool GameController::PadOpenArgsAreValid(int user_id, int type, int index) const {
|
||||
// The host keyboard and gamepads of a player feed one standard port for that player's user.
|
||||
return user_id == Config::GetPlayerUserId(m_player) && type == 0 && index == 0;
|
||||
}
|
||||
|
||||
int GameController::InitPort() {
|
||||
@@ -1064,7 +1166,7 @@ int GameController::OpenPort(int user_id, int type, int index) {
|
||||
return PAD_ERROR_ALREADY_OPENED;
|
||||
}
|
||||
m_port_state = PortState::Open;
|
||||
return PAD_HANDLE;
|
||||
return Handle();
|
||||
}
|
||||
|
||||
int GameController::GetPortHandle(int user_id, int type, int index) {
|
||||
@@ -1072,15 +1174,16 @@ int GameController::GetPortHandle(int user_id, int type, int index) {
|
||||
if (m_port_state == PortState::Uninitialized) {
|
||||
return PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
return m_port_state == PortState::Open && PadOpenArgsAreValid(user_id, type, index) ? PAD_HANDLE
|
||||
: PAD_ERROR_NO_HANDLE;
|
||||
return m_port_state == PortState::Open && PadOpenArgsAreValid(user_id, type, index)
|
||||
? Handle()
|
||||
: PAD_ERROR_NO_HANDLE;
|
||||
}
|
||||
|
||||
int GameController::CheckPortHandleLocked(int handle) const {
|
||||
if (m_port_state == PortState::Uninitialized) {
|
||||
return PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
return m_port_state == PortState::Open && handle == PAD_HANDLE ? OK : PAD_ERROR_INVALID_HANDLE;
|
||||
return m_port_state == PortState::Open && handle == Handle() ? OK : PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
int GameController::CheckPortHandle(int handle) {
|
||||
@@ -1098,12 +1201,19 @@ int GameController::ClosePort(int handle) {
|
||||
}
|
||||
|
||||
int PadCheckHandle(int handle) {
|
||||
return g_controller->CheckPortHandle(handle);
|
||||
if (g_players[0] == nullptr) {
|
||||
return PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
auto* player = PlayerForHandle(handle);
|
||||
return player != nullptr ? player->CheckPortHandle(handle) : PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadInit() {
|
||||
PRINT_NAME();
|
||||
return g_controller->InitPort();
|
||||
for (const auto& player: g_players) {
|
||||
(void)player->InitPort();
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
|
||||
@@ -1115,7 +1225,8 @@ int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
|
||||
"\t param = 0x%016" PRIx64 "\n",
|
||||
user_id, type, index, reinterpret_cast<uint64_t>(param));
|
||||
|
||||
return g_controller->OpenPort(user_id, type, index);
|
||||
const int player = Config::GetPlayerFromUserId(user_id);
|
||||
return player >= 0 ? g_players[player]->OpenPort(user_id, type, index) : PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
|
||||
@@ -1126,13 +1237,19 @@ int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
|
||||
"\t index = %d\n",
|
||||
user_id, type, index);
|
||||
|
||||
return g_controller->GetPortHandle(user_id, type, index);
|
||||
const int player = Config::GetPlayerFromUserId(user_id);
|
||||
return player >= 0 ? g_players[player]->GetPortHandle(user_id, type, index)
|
||||
: PAD_ERROR_NO_HANDLE;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadClose(int handle) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t handle = %d\n", handle);
|
||||
return g_controller->ClosePort(handle);
|
||||
auto* player = PlayerForHandle(handle);
|
||||
if (player == nullptr) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
return player->ClosePort(handle);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) {
|
||||
@@ -1143,7 +1260,7 @@ int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) {
|
||||
}
|
||||
|
||||
LOGF("\t enable = %s\n", (enable ? "true" : "false"));
|
||||
g_controller->SetMotionSensorState(enable);
|
||||
PlayerForHandle(handle)->SetMotionSensorState(enable);
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -1167,7 +1284,7 @@ int KYTY_SYSV_ABI PadResetOrientation(int handle) {
|
||||
return error;
|
||||
}
|
||||
|
||||
g_controller->ResetOrientation();
|
||||
PlayerForHandle(handle)->ResetOrientation();
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -1183,7 +1300,7 @@ int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformati
|
||||
|
||||
int connected_count = 0;
|
||||
bool connected = false;
|
||||
g_controller->GetConnectionInfo(&connected, &connected_count);
|
||||
PlayerForHandle(handle)->GetConnectionInfo(&connected, &connected_count);
|
||||
|
||||
std::memset(info, 0, sizeof(*info));
|
||||
|
||||
@@ -1228,7 +1345,7 @@ int KYTY_SYSV_ABI PadReadState(int handle, PadData* data) {
|
||||
bool connected = false;
|
||||
ControllerState state;
|
||||
|
||||
g_controller->ReadState(&state, &connected, &connected_count);
|
||||
PlayerForHandle(handle)->ReadState(&state, &connected, &connected_count);
|
||||
|
||||
pad_fill_data(data, state, connected, connected_count);
|
||||
|
||||
@@ -1252,11 +1369,12 @@ int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num) {
|
||||
bool connected = false;
|
||||
ControllerState states[64] = {};
|
||||
|
||||
int ret_num = g_controller->ReadStates(states, num, &connected, &connected_count);
|
||||
auto* player = PlayerForHandle(handle);
|
||||
int ret_num = player->ReadStates(states, num, &connected, &connected_count);
|
||||
|
||||
if (!connected || ret_num == 0) {
|
||||
if (connected) {
|
||||
g_controller->ReadState(&states[0], &connected, &connected_count);
|
||||
player->ReadState(&states[0], &connected, &connected_count);
|
||||
}
|
||||
ret_num = 1;
|
||||
}
|
||||
@@ -1270,6 +1388,7 @@ int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num) {
|
||||
|
||||
int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) {
|
||||
PRINT_NAME();
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
@@ -1282,7 +1401,7 @@ int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) {
|
||||
"\t small_motor = %d\n",
|
||||
static_cast<int>(param->large_motor), static_cast<int>(param->small_motor));
|
||||
|
||||
g_controller->SetVibration(param->large_motor, param->small_motor);
|
||||
PlayerForHandle(handle)->SetVibration(param->large_motor, param->small_motor);
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -1299,6 +1418,7 @@ int KYTY_SYSV_ABI PadResetLightBar(int handle) {
|
||||
|
||||
int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) {
|
||||
PRINT_NAME();
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
@@ -1307,13 +1427,14 @@ int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
g_controller->SetLightBar(param->r, param->g, param->b);
|
||||
PlayerForHandle(handle)->SetLightBar(param->r, param->g, param->b);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const PadTriggerEffectParam* param) {
|
||||
PRINT_NAME();
|
||||
Common::LockGuard lock(g_router_mutex);
|
||||
|
||||
if (const auto error = PadCheckHandle(handle); error != OK) {
|
||||
return error;
|
||||
@@ -1322,7 +1443,7 @@ int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const PadTriggerEffectParam* p
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return g_controller->SetTriggerEffect(*param) ? OK : PAD_ERROR_INVALID_ARG;
|
||||
return PlayerForHandle(handle)->SetTriggerEffect(*param) ? OK : PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadGetTriggerEffectState(int handle, PadTriggerEffectStateInformation* info) {
|
||||
@@ -1337,7 +1458,7 @@ int KYTY_SYSV_ABI PadGetTriggerEffectState(int handle, PadTriggerEffectStateInfo
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
g_controller->GetTriggerEffectState(info);
|
||||
PlayerForHandle(handle)->GetTriggerEffectState(info);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ inline int controller_get_axis(int min, int max, int value) {
|
||||
|
||||
void Connect(int id);
|
||||
void Disconnect(int id);
|
||||
// Moves the keyboard to the next player (shared with player 1, then 1-4) while the game runs.
|
||||
void CycleKeyboardPlayer();
|
||||
void SetButton(int id, uint32_t button, bool down);
|
||||
void SetAxis(int id, Axis axis, int value);
|
||||
void SetRightStick(int id, int x, int y);
|
||||
@@ -76,6 +78,8 @@ void SetSensor(int id, Sensor sensor, const float* data, uint64_t time_us);
|
||||
void ToggleGyroEmulation(int id);
|
||||
void ResetInputState();
|
||||
int GetActiveControllerId();
|
||||
// True when the player has a keyboard or gamepad assigned.
|
||||
bool IsPlayerConnected(int player);
|
||||
|
||||
enum class Setting { SpeakerVolume, VibrationIntensity, TriggerEffectIntensity };
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/stringUtils.h"
|
||||
@@ -300,15 +301,23 @@ static std::string memory_dir_name(uint32_t slot_id) {
|
||||
return "sce_sdmemory" + (slot_id == 0 ? std::string() : std::to_string(slot_id));
|
||||
}
|
||||
|
||||
static std::filesystem::path save_user_directory(std::string_view title_id, int32_t user_id) {
|
||||
auto directory = std::filesystem::path(SAVE_DATA_DIR);
|
||||
if (Config::IsGuestUserId(user_id)) {
|
||||
directory /= std::filesystem::path("[users]") / std::to_string(user_id);
|
||||
}
|
||||
return directory / title_id;
|
||||
}
|
||||
|
||||
static std::filesystem::path save_directory(std::string_view title_id, std::string_view dir_name,
|
||||
int32_t user_id) {
|
||||
const auto directory = std::filesystem::path(SAVE_DATA_DIR) / title_id / dir_name;
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
if (dir_name == memory_dir_name(slot)) {
|
||||
return directory / std::to_string(user_id);
|
||||
return std::filesystem::path(SAVE_DATA_DIR) / title_id / dir_name /
|
||||
std::to_string(user_id);
|
||||
}
|
||||
}
|
||||
return directory;
|
||||
return save_user_directory(title_id, user_id) / dir_name;
|
||||
}
|
||||
|
||||
static std::filesystem::path memory_directory(int32_t user_id, uint32_t slot_id) {
|
||||
@@ -590,7 +599,7 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const std::string title_id = cond->title_id != nullptr ? cond->title_id->data : get_title_id();
|
||||
const auto root = std::filesystem::path(SAVE_DATA_DIR) / title_id;
|
||||
const auto root = save_user_directory(title_id, cond->user_id);
|
||||
std::vector<std::string> dir_list;
|
||||
|
||||
if (Common::File::IsDirectoryExisting(root)) {
|
||||
|
||||
+12
-3
@@ -105,6 +105,8 @@ struct TrophyState {
|
||||
const Trophies::Package* package = nullptr;
|
||||
Trophies::UnlockData unlocks;
|
||||
std::filesystem::path path;
|
||||
// Guests (players 2+) have no profile: nothing is loaded, saved or announced.
|
||||
bool guest = false;
|
||||
};
|
||||
|
||||
struct TrophyContext {
|
||||
@@ -153,9 +155,12 @@ static TrophyState* LoadTrophyState(TrophyKey key) {
|
||||
package.event_rules.size(), uds_filename.c_str());
|
||||
}
|
||||
}
|
||||
auto path = Trophies::UnlocksPath({}, title_id, key.first, key.second);
|
||||
auto unlocks = Trophies::LoadUnlockData(path);
|
||||
return &g_states.emplace(key, TrophyState {&package, std::move(unlocks), std::move(path)})
|
||||
const bool guest = Config::IsGuestUserId(key.first);
|
||||
auto path = guest ? std::filesystem::path {}
|
||||
: Trophies::UnlocksPath({}, title_id, key.first, key.second);
|
||||
auto unlocks = guest ? Trophies::UnlockData {} : Trophies::LoadUnlockData(path);
|
||||
return &g_states
|
||||
.emplace(key, TrophyState {&package, std::move(unlocks), std::move(path), guest})
|
||||
.first->second;
|
||||
}
|
||||
|
||||
@@ -240,6 +245,10 @@ static void FillGroupInfo(const TrophyState& state, int group_id, const std::str
|
||||
}
|
||||
|
||||
static void RecordTrophyUnlock(TrophyState& state, int trophy_id, bool from_stat = false) {
|
||||
if (state.guest) {
|
||||
LOGF("[Trophy] guest users cannot earn trophies (trophy %d)\n", trophy_id);
|
||||
return;
|
||||
}
|
||||
const auto found = state.package->trophies.find(trophy_id);
|
||||
if (found == state.package->trophies.end()) {
|
||||
LOGF("[Trophy] ignored unknown trophy ID %d\n", trophy_id);
|
||||
|
||||
+54
-27
@@ -1,12 +1,14 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/localization.h"
|
||||
#include "libs/controller.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace Libs {
|
||||
|
||||
@@ -39,6 +41,15 @@ struct UserServiceGamePresets {
|
||||
|
||||
static_assert(sizeof(UserServiceGamePresets) == 40);
|
||||
|
||||
static bool IsPlayerLoggedIn(int player) {
|
||||
return player == 0 || (player > 0 && Controller::IsPlayerConnected(player));
|
||||
}
|
||||
|
||||
static int GetLoggedInPlayer(int user_id) {
|
||||
const int player = Config::GetPlayerFromUserId(user_id);
|
||||
return IsPlayerLoggedIn(player) ? player : -1;
|
||||
}
|
||||
|
||||
static KYTY_SYSV_ABI int UserServiceInitialize(const void* /*params*/) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -66,13 +77,16 @@ static KYTY_SYSV_ABI int UserServiceGetEvent(SceUserServiceEvent* event) {
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(event == nullptr);
|
||||
|
||||
static bool logged_in = false;
|
||||
static bool logged_in[Config::MAX_PLAYERS] = {};
|
||||
|
||||
if (!logged_in) {
|
||||
logged_in = true;
|
||||
event->event_type = UserServiceEventTypeLogin;
|
||||
event->user_id = Config::GetUserId();
|
||||
return OK;
|
||||
for (int player = 0; player < Config::MAX_PLAYERS; player++) {
|
||||
const bool connected = IsPlayerLoggedIn(player);
|
||||
if (connected != logged_in[player]) {
|
||||
logged_in[player] = connected;
|
||||
event->event_type = connected ? UserServiceEventTypeLogin : UserServiceEventTypeLogout;
|
||||
event->user_id = Config::GetPlayerUserId(player);
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
return USER_SERVICE_ERROR_NO_EVENT;
|
||||
@@ -83,24 +97,36 @@ static KYTY_SYSV_ABI int UserServiceGetLoginUserIdList(UserServiceLoginUserIdLis
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(user_id_list == nullptr);
|
||||
|
||||
user_id_list->user_id[0] = Config::GetUserId();
|
||||
user_id_list->user_id[1] = -1;
|
||||
user_id_list->user_id[2] = -1;
|
||||
user_id_list->user_id[3] = -1;
|
||||
int count = 0;
|
||||
for (int player = 0; player < Config::MAX_PLAYERS; player++) {
|
||||
if (IsPlayerLoggedIn(player)) {
|
||||
user_id_list->user_id[count++] = Config::GetPlayerUserId(player);
|
||||
}
|
||||
}
|
||||
while (count < Config::MAX_PLAYERS) {
|
||||
user_id_list->user_id[count++] = -1;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static KYTY_SYSV_ABI int UserServiceGetUserName(int user_id, char* name, size_t size) {
|
||||
if (user_id != Config::GetUserId()) {
|
||||
if (name == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
const int player = GetLoggedInPlayer(user_id);
|
||||
if (player < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(size < 5);
|
||||
|
||||
const auto& user_name = Config::GetUserName();
|
||||
int s = snprintf(name, size, "%s", user_name.c_str());
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(static_cast<size_t>(s) >= size);
|
||||
const auto user_name =
|
||||
player > 0 ? std::string(Common::Localization::Get(Config::GetConsoleLanguage(), "user.guest")) +
|
||||
" " + std::to_string(player + 1)
|
||||
: Config::GetUserName();
|
||||
if (size <= user_name.size()) {
|
||||
return USER_SERVICE_ERROR_BUFFER_TOO_SHORT;
|
||||
}
|
||||
std::memcpy(name, user_name.c_str(), user_name.size() + 1);
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -111,11 +137,12 @@ static KYTY_SYSV_ABI int UserServiceGetUserNumber(int user_id, int32_t* number)
|
||||
if (number == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != Config::GetUserId()) {
|
||||
const int player = GetLoggedInPlayer(user_id);
|
||||
if (player < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
*number = 1;
|
||||
*number = player + 1;
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -126,7 +153,7 @@ static KYTY_SYSV_ABI int UserServiceGetGamePresets(int user_id, UserServiceGameP
|
||||
if (presets == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != Config::GetUserId()) {
|
||||
if (GetLoggedInPlayer(user_id) < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
@@ -147,7 +174,7 @@ static KYTY_SYSV_ABI int UserServiceGetAccessibilityVibration(int user_id, int32
|
||||
if (vibration == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != Config::GetUserId()) {
|
||||
if (GetLoggedInPlayer(user_id) < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
@@ -163,7 +190,7 @@ static KYTY_SYSV_ABI int UserServiceGetAccessibilityTriggerEffect(int user_
|
||||
if (trigger_effect == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != Config::GetUserId()) {
|
||||
if (GetLoggedInPlayer(user_id) < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
@@ -178,7 +205,7 @@ static KYTY_SYSV_ABI int UserServiceGetAgeLevel(int user_id, uint32_t* age_level
|
||||
if (age_level == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != Config::GetUserId()) {
|
||||
if (GetLoggedInPlayer(user_id) < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
@@ -194,7 +221,7 @@ static KYTY_SYSV_ABI int UserServiceGetAccessibilityChatTranscription(int u
|
||||
if (chat_transcription == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != Config::GetUserId()) {
|
||||
if (GetLoggedInPlayer(user_id) < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
@@ -210,7 +237,7 @@ UserServiceGetAccessibilityPressAndHoldDelay(int user_id, int32_t* press_and_hol
|
||||
if (press_and_hold_delay == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != Config::GetUserId()) {
|
||||
if (GetLoggedInPlayer(user_id) < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
@@ -226,7 +253,7 @@ static KYTY_SYSV_ABI int UserServiceGetAccessibilityZoomEnabled(int user_id
|
||||
if (zoom_enabled == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != Config::GetUserId()) {
|
||||
if (GetLoggedInPlayer(user_id) < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
@@ -242,7 +269,7 @@ static KYTY_SYSV_ABI int UserServiceGetAccessibilityZoomFollowFocus(int use
|
||||
if (zoom_follow_focus == nullptr) {
|
||||
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (user_id != Config::GetUserId()) {
|
||||
if (GetLoggedInPlayer(user_id) < 0) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user