Skip to content

Commit

Permalink
Pad: Add controller ejection
Browse files Browse the repository at this point in the history
  • Loading branch information
RedPanda4552 authored and stenzek committed Dec 4, 2023
1 parent 2443e06 commit 86c42a0
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 39 deletions.
80 changes: 57 additions & 23 deletions pcsx2/SIO/Pad/Pad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "IconsFontAwesome5.h"

#include "VMManager.h"
#include "common/Assertions.h"
#include "common/FileSystem.h"
#include "common/Path.h"
Expand Down Expand Up @@ -57,6 +58,9 @@ namespace Pad

static std::array<std::array<MacroButton, NUM_MACRO_BUTTONS_PER_CONTROLLER>, NUM_CONTROLLER_PORTS> s_macro_buttons;
static std::array<std::unique_ptr<PadBase>, NUM_CONTROLLER_PORTS> s_controllers;

bool mtapPort0LastState;
bool mtapPort1LastState;
}

bool Pad::Initialize()
Expand Down Expand Up @@ -90,19 +94,24 @@ void Pad::LoadConfig(const SettingsInterface& si)
{
s_macro_buttons = {};

const bool mtapPort0Changed = EmuConfig.Pad.MultitapPort0_Enabled != Pad::mtapPort0LastState;
const bool mtapPort1Changed = EmuConfig.Pad.MultitapPort1_Enabled != Pad::mtapPort1LastState;

for (u32 i = 0; i < Pad::NUM_CONTROLLER_PORTS; i++)
{
const std::string section = GetConfigSection(i);
const ControllerInfo* ci = GetControllerInfo(EmuConfig.Pad.Ports[i].Type);
pxAssert(ci);

// If a pad is not yet constructed, at minimum place a NotConnected pad in the slot.
// Do not abort the for loop - If there are pad settings, we want those to be applied to the slot.
PadBase* pad = Pad::GetPad(i);

if (!pad || pad->GetType() != ci->type)
// If pad pointer is not occupied yet, type in settings no longer matches the current type, or a multitap was slotted in/out,
// then reconstruct a new pad.
if (!pad || pad->GetType() != ci->type || (mtapPort0Changed && (i <= 4 && i != 1)) || (mtapPort1Changed && (i >= 5 || i == 1)))
{
pad = Pad::CreatePad(i, ci->type);
// Create the new pad. If the VM is in any kind of running state at all, set eject ticks so the PS2 will think
// there was some kind of pad ejection event and properly detect the new one, and properly initiate its config sequence.
pad = Pad::CreatePad(i, ci->type, (VMManager::GetState() != VMState::Shutdown ? Pad::DEFAULT_EJECT_TICKS : 0));
pxAssert(pad);
}

Expand All @@ -129,6 +138,9 @@ void Pad::LoadConfig(const SettingsInterface& si)
pad->SetAnalogInvertR((invert_r & 1) != 0, (invert_r & 2) != 0);
LoadMacroButtonConfig(si, i, ci, section);
}

Pad::mtapPort0LastState = EmuConfig.Pad.MultitapPort0_Enabled;
Pad::mtapPort1LastState = EmuConfig.Pad.MultitapPort1_Enabled;
}

Pad::ControllerType Pad::GetDefaultPadType(u32 pad)
Expand Down Expand Up @@ -526,35 +538,57 @@ bool Pad::Freeze(StateWrapper& sw)

for (u32 unifiedSlot = 0; unifiedSlot < NUM_CONTROLLER_PORTS; unifiedSlot++)
{
ControllerType type;
sw.Do(&type);
PadBase* currentPad = GetPad(unifiedSlot);
ControllerType statePadType;

sw.Do(&statePadType);

if (sw.HasError())
return false;

PadBase* tempPad;
PadBase* pad = GetPad(unifiedSlot);
if (!pad || pad->GetType() != type)

if (!currentPad)
{
pxAssertMsg(false, fmt::format("Pad::Freeze (on read) Existing Pad {0} was nullptr", unifiedSlot).c_str());
}
// If the currently configured pad is of a different type than the pad which was used during the savestate...
else if (currentPad->GetType() != statePadType)
{
const ControllerType currentPadType = currentPad->GetType();

const auto& [port, slot] = sioConvertPadToPortAndSlot(unifiedSlot);
Host::AddIconOSDMessage(fmt::format("UnfreezePad{}Changed", unifiedSlot), ICON_FA_GAMEPAD,
fmt::format(TRANSLATE_FS("Pad",
"Controller port {0}, slot {1} has a {2} connected, but the save state has a "
"{3}.\nLeaving the original controller type connected, but this may cause issues."),
"{3}.\nEjecting {3} and replacing it with {2}."),
port, slot,
GetControllerTypeName(pad ? pad->GetType() : Pad::ControllerType::NotConnected),
GetControllerTypeName(type)));

// Reset the transfer etc state of the pad, at least it has a better chance of surviving.
if (pad)
pad->SoftReset();

// But we still need to pull the data from the state..
tempPad = CreatePad(unifiedSlot, type);
pad = tempPad;
GetControllerTypeName(currentPad ? currentPad->GetType() : Pad::ControllerType::NotConnected),
GetControllerTypeName(statePadType)));

// Run the freeze, using a new pad instance of the old type just so we make sure all those attributes
// from the state are read out and we aren't going to run into some sort of consistency problem.
currentPad = CreatePad(unifiedSlot, statePadType);

if (currentPad)
{
currentPad->Freeze(sw);

// Now immediately discard whatever malformed pad state we just created, and replace it with a fresh pad loaded
// using whatever the current user settings are. Savestates are, by definition, never going to occur in the middle
// of a transfer between SIO2 and the peripheral, since they aren't captured until the VM is at a point where everything
// is "stoppable". For all intents and purposes, by the time a savestate is captured, the IOP is "done" and there is no
// "pending work" left hanging in SIO2 or the pads. So there is nothing actually lost from just throwing the pad away and making a new one here.
currentPad = CreatePad(unifiedSlot, currentPadType, Pad::DEFAULT_EJECT_TICKS);
}
else
{
pxAssertMsg(false, fmt::format("Pad::Freeze (on read) State Pad {0} was nullptr", unifiedSlot).c_str());
}
}

if (!pad->Freeze(sw))
// ... else, just run the freeze normally.
else if (currentPad && !currentPad->Freeze(sw))
{
return false;
}
}
}
else
Expand Down
2 changes: 2 additions & 0 deletions pcsx2/SIO/Pad/Pad.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class StateWrapper;

namespace Pad
{
constexpr size_t DEFAULT_EJECT_TICKS = 50;

bool Initialize();
void Shutdown();

Expand Down
3 changes: 2 additions & 1 deletion pcsx2/SIO/Pad/PadBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@

#include "SIO/Pad/PadBase.h"

PadBase::PadBase(u8 unifiedSlot)
PadBase::PadBase(u8 unifiedSlot, size_t ejectTicks)
{
this->unifiedSlot = unifiedSlot;
this->ejectTicks = ejectTicks;
}

PadBase::~PadBase() = default;
Expand Down
10 changes: 9 additions & 1 deletion pcsx2/SIO/Pad/PadBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@

class PadBase
{
public:
// How many commands the pad should pretend to be ejected for.
// Since changing pads in the UI or ejecting a multitap is instant,
// this simulates the time it would take for a human to plug in the pad.
// That gives games a chance to see a pad get inserted rather than the
// pad's type magically changing from one frame to the next.
size_t ejectTicks = 0;

protected:
std::array<u8, 32> rawInputs = {};
u8 unifiedSlot;
Expand All @@ -32,7 +40,7 @@ class PadBase
size_t commandBytesReceived = 0;

public: // Public members
PadBase(u8 unifiedSlot);
PadBase(u8 unifiedSlot, size_t ejectTicks = 0);
virtual ~PadBase();

void SoftReset();
Expand Down
4 changes: 2 additions & 2 deletions pcsx2/SIO/Pad/PadDualshock2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,8 @@ u8 PadDualshock2::ResponseBytes(u8 commandByte)
}
}

PadDualshock2::PadDualshock2(u8 unifiedSlot)
: PadBase(unifiedSlot)
PadDualshock2::PadDualshock2(u8 unifiedSlot, size_t ejectTicks)
: PadBase(unifiedSlot, ejectTicks)
{
currentMode = Pad::Mode::DIGITAL;
}
Expand Down
2 changes: 1 addition & 1 deletion pcsx2/SIO/Pad/PadDualshock2.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class PadDualshock2 final : public PadBase
u8 ResponseBytes(u8 commandByte);

public:
PadDualshock2(u8 unifiedSlot);
PadDualshock2(u8 unifiedSlot, size_t ejectTicks);
~PadDualshock2() override;

static inline bool IsAnalogKey(int index)
Expand Down
4 changes: 2 additions & 2 deletions pcsx2/SIO/Pad/PadGuitar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,8 @@ u8 PadGuitar::VibrationMap(u8 commandByte)
return 0xff;
}

PadGuitar::PadGuitar(u8 unifiedSlot)
: PadBase(unifiedSlot)
PadGuitar::PadGuitar(u8 unifiedSlot, size_t ejectTicks)
: PadBase(unifiedSlot, ejectTicks)
{
currentMode = Pad::Mode::DIGITAL;
}
Expand Down
2 changes: 1 addition & 1 deletion pcsx2/SIO/Pad/PadGuitar.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class PadGuitar final : public PadBase
u8 VibrationMap(u8 commandByte);

public:
PadGuitar(u8 unifiedSlot);
PadGuitar(u8 unifiedSlot, size_t ejectTicks);
~PadGuitar() override;

Pad::ControllerType GetType() const override;
Expand Down
4 changes: 2 additions & 2 deletions pcsx2/SIO/Pad/PadNotConnected.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
const Pad::ControllerInfo PadNotConnected::ControllerInfo = {Pad::ControllerType::NotConnected, "None",
TRANSLATE_NOOP("Pad", "Not Connected"), {}, {}, Pad::VibrationCapabilities::NoVibration };

PadNotConnected::PadNotConnected(u8 unifiedSlot)
: PadBase(unifiedSlot)
PadNotConnected::PadNotConnected(u8 unifiedSlot, size_t ejectTicks)
: PadBase(unifiedSlot, ejectTicks)
{

}
Expand Down
2 changes: 1 addition & 1 deletion pcsx2/SIO/Pad/PadNotConnected.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
class PadNotConnected final : public PadBase
{
public:
PadNotConnected(u8 unifiedSlot);
PadNotConnected(u8 unifiedSlot, size_t ejectTicks = 0);
~PadNotConnected() override;

Pad::ControllerType GetType() const override;
Expand Down
28 changes: 23 additions & 5 deletions pcsx2/SIO/Sio2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ void Sio2::Pad()
this->recv1 |= Recv1::NO_DEVICES_MISSING;

// If the currently accessed pad is missing, also tick those bits
if (pad->GetType() == Pad::ControllerType::NotConnected)
if (pad->GetType() == Pad::ControllerType::NotConnected || pad->ejectTicks)
{
if (!port)
{
Expand All @@ -180,10 +180,28 @@ void Sio2::Pad()
// Then for every byte in g_Sio2FifoIn, pass to PAD and see what it kicks back to us.
while (!g_Sio2FifoIn.empty())
{
const u8 commandByte = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
const u8 responseByte = pad->SendCommandByte(commandByte);
g_Sio2FifoOut.push_back(responseByte);
// If the pad is "ejected", respond with nothing
if (pad->ejectTicks)
{
g_Sio2FifoIn.pop_front();
g_Sio2FifoOut.push_back(0xff);
}
// Else, actually forward to the pad.
else
{
const u8 commandByte = g_Sio2FifoIn.front();
g_Sio2FifoIn.pop_front();
const u8 responseByte = pad->SendCommandByte(commandByte);
g_Sio2FifoOut.push_back(responseByte);
}
}

// If the pad is "ejected", then decrement one tick.
// This needs to happen AFTER anything else which might
// consider if the pad is "ejected"!
if (pad->ejectTicks)
{
pad->ejectTicks -= 1;
}
}

Expand Down

0 comments on commit 86c42a0

Please sign in to comment.