From 2443e0674552f607c43ac77aacea2884fe037bfc Mon Sep 17 00:00:00 2001
From: RedPanda4552 <redpanda4552@gmail.com>
Date: Mon, 6 Nov 2023 00:17:29 -0500
Subject: [PATCH] [SAVEVERSION+] Multitap: Make multitaps manage their own
 states

---
 pcsx2/SIO/Multitap/MultitapProtocol.cpp | 14 ++++++++++--
 pcsx2/SIO/Multitap/MultitapProtocol.h   | 10 +++++++--
 pcsx2/SIO/Pad/Pad.cpp                   | 29 ++++++++++++-------------
 pcsx2/SIO/Sio2.cpp                      | 27 +++++------------------
 pcsx2/SIO/Sio2.h                        |  1 -
 pcsx2/SaveState.cpp                     |  4 ++++
 pcsx2/SaveState.h                       |  2 +-
 7 files changed, 45 insertions(+), 42 deletions(-)

diff --git a/pcsx2/SIO/Multitap/MultitapProtocol.cpp b/pcsx2/SIO/Multitap/MultitapProtocol.cpp
index 77840029cb..31a498f8f3 100644
--- a/pcsx2/SIO/Multitap/MultitapProtocol.cpp
+++ b/pcsx2/SIO/Multitap/MultitapProtocol.cpp
@@ -16,6 +16,7 @@
 #include "PrecompiledHeader.h"
 
 #include "Common.h"
+#include "StateWrapper.h"
 
 #include "SIO/Multitap/MultitapProtocol.h"
 
@@ -25,8 +26,7 @@
 #define MT_LOG_ENABLE 0
 #define MT_LOG if (MT_LOG_ENABLE) DevCon
 
-MultitapProtocol g_MultitapPort0;
-MultitapProtocol g_MultitapPort1;
+std::array<MultitapProtocol, SIO::PORTS> g_MultitapArr;
 
 void MultitapProtocol::SupportCheck()
 {
@@ -114,6 +114,16 @@ void MultitapProtocol::FullReset()
 	this->currentMemcardSlot = 0;
 }
 
+bool MultitapProtocol::DoState(StateWrapper& sw)
+{
+	if (!sw.DoMarker("Multitap"))
+		return false;
+
+	sw.Do(&currentPadSlot);
+	sw.Do(&currentMemcardSlot);
+	return true;
+}
+
 u8 MultitapProtocol::GetPadSlot()
 {
 	return this->currentPadSlot;
diff --git a/pcsx2/SIO/Multitap/MultitapProtocol.h b/pcsx2/SIO/Multitap/MultitapProtocol.h
index db0bd0b133..62ee3bbea5 100644
--- a/pcsx2/SIO/Multitap/MultitapProtocol.h
+++ b/pcsx2/SIO/Multitap/MultitapProtocol.h
@@ -15,6 +15,12 @@
 
 #pragma once
 
+#include "SIO/SioTypes.h"
+
+#include <array>
+
+class StateWrapper;
+
 enum class MultitapMode
 {
 	NOT_SET = 0xff,
@@ -39,6 +45,7 @@ public:
 
 	void SoftReset();
 	void FullReset();
+	bool DoState(StateWrapper& sw);
 
 	u8 GetPadSlot();
 	u8 GetMemcardSlot();
@@ -46,6 +53,5 @@ public:
 	void SendToMultitap();
 };
 
-extern MultitapProtocol g_MultitapPort0;
-extern MultitapProtocol g_MultitapPort1;
+extern std::array<MultitapProtocol, SIO::PORTS> g_MultitapArr;
 
diff --git a/pcsx2/SIO/Pad/Pad.cpp b/pcsx2/SIO/Pad/Pad.cpp
index a95e9fdca8..be87a99e06 100644
--- a/pcsx2/SIO/Pad/Pad.cpp
+++ b/pcsx2/SIO/Pad/Pad.cpp
@@ -49,8 +49,7 @@ namespace Pad
 
 	static const char* GetControllerTypeName(Pad::ControllerType type);
 
-	static std::unique_ptr<PadBase> CreatePad(u8 unifiedSlot, Pad::ControllerType controllerType);
-	static PadBase* ChangePadType(u8 unifiedSlot, Pad::ControllerType controllerType);
+	static PadBase* CreatePad(u8 unifiedSlot, Pad::ControllerType controllerType, size_t ejectTicks = 0);
 
 	static void LoadMacroButtonConfig(
 		const SettingsInterface& si, u32 pad, const ControllerInfo* ci, const std::string& section);
@@ -91,7 +90,6 @@ void Pad::LoadConfig(const SettingsInterface& si)
 {
 	s_macro_buttons = {};
 
-	// This is where we would load controller types, if onepad supported them.
 	for (u32 i = 0; i < Pad::NUM_CONTROLLER_PORTS; i++)
 	{
 		const std::string section = GetConfigSection(i);
@@ -99,11 +97,12 @@ void Pad::LoadConfig(const SettingsInterface& si)
 		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 pad settings, we want those to be applied to 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)
 		{
-			pad = Pad::ChangePadType(i, ci->type);
+			pad = Pad::CreatePad(i, ci->type);
 			pxAssert(pad);
 		}
 
@@ -471,22 +470,22 @@ std::string Pad::GetConfigSection(u32 pad_index)
 	return fmt::format("Pad{}", pad_index + 1);
 }
 
-std::unique_ptr<PadBase> Pad::CreatePad(u8 unifiedSlot, ControllerType controllerType)
+// Create a new pad instance, update the smart pointer for this pad slot, and return a dumb pointer to the new pad.
+PadBase* Pad::CreatePad(u8 unifiedSlot, ControllerType controllerType, size_t ejectTicks)
 {
 	switch (controllerType)
 	{
 		case ControllerType::DualShock2:
-			return std::make_unique<PadDualshock2>(unifiedSlot);
+			s_controllers[unifiedSlot] = std::make_unique<PadDualshock2>(unifiedSlot, ejectTicks);
+			break;
 		case ControllerType::Guitar:
-			return std::make_unique<PadGuitar>(unifiedSlot);
+			s_controllers[unifiedSlot] = std::make_unique<PadGuitar>(unifiedSlot, ejectTicks);
+			break;
 		default:
-			return std::make_unique<PadNotConnected>(unifiedSlot);
+			s_controllers[unifiedSlot] = std::make_unique<PadNotConnected>(unifiedSlot, ejectTicks);
+			break;
 	}
-}
 
-PadBase* Pad::ChangePadType(u8 unifiedSlot, ControllerType controllerType)
-{
-	s_controllers[unifiedSlot] = CreatePad(unifiedSlot, controllerType);
 	return s_controllers[unifiedSlot].get();
 }
 
@@ -532,7 +531,7 @@ bool Pad::Freeze(StateWrapper& sw)
 			if (sw.HasError())
 				return false;
 
-			std::unique_ptr<PadBase> tempPad;
+			PadBase* tempPad;
 			PadBase* pad = GetPad(unifiedSlot);
 			if (!pad || pad->GetType() != type)
 			{
@@ -551,7 +550,7 @@ bool Pad::Freeze(StateWrapper& sw)
 
 				// But we still need to pull the data from the state..
 				tempPad = CreatePad(unifiedSlot, type);
-				pad = tempPad.get();
+				pad = tempPad;
 			}
 
 			if (!pad->Freeze(sw))
diff --git a/pcsx2/SIO/Sio2.cpp b/pcsx2/SIO/Sio2.cpp
index f985bcae01..dfb0ea29fc 100644
--- a/pcsx2/SIO/Sio2.cpp
+++ b/pcsx2/SIO/Sio2.cpp
@@ -144,9 +144,7 @@ void Sio2::SetRecv1(u32 value)
 
 void Sio2::Pad()
 {
-	MultitapProtocol& mtap = (port ? g_MultitapPort1 : g_MultitapPort0);
-
-	// Send PAD our current port, and get back whatever it says the first response byte should be.
+	MultitapProtocol& mtap = g_MultitapArr.at(port);
 	PadBase* pad = Pad::GetPad(port, mtap.GetPadSlot());
 
 	// Update the third nibble with which ports have been accessed
@@ -177,10 +175,9 @@ void Sio2::Pad()
 	}
 
 	g_Sio2FifoOut.push_back(0xff);
-
-	// Then for every byte in g_Sio2FifoIn, pass to PAD and see what it kicks back to us.
 	pad->SoftReset();
 
+	// 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();
@@ -208,8 +205,8 @@ void Sio2::Multitap()
 	// This bit is always set, whether the pad is present or missing
 	this->recv1 |= Recv1::NO_DEVICES_MISSING;
 
-	// If the currently accessed pad is missing, also tick those bits.
-	// MTAPMAN is special - at least, the variant found in Gauntlet: Dark Legacy.
+	// If the currently accessed multitap is missing, also tick those bits.
+	// MTAPMAN is special though.
 	// 
 	// For PADMAN and pads, the bits represented by PORT_1_MISSING and PORT_2_MISSING
 	// are always faithful - suppose your game only opened port 2 for some reason,
@@ -223,18 +220,7 @@ void Sio2::Multitap()
 		this->recv1 |= Recv1::PORT_1_MISSING;
 	}
 
-	switch (this->port)
-	{
-		case 0:
-			g_MultitapPort0.SendToMultitap();
-			break;
-		case 1:
-			g_MultitapPort1.SendToMultitap();
-			break;
-		default:
-			Console.Warning("%s() Port value from SEND3 out of bounds! (%d)", __FUNCTION__, this->port);
-			break;
-	}
+	g_MultitapArr.at(this->port).SendToMultitap();
 }
 
 void Sio2::Infrared()
@@ -252,7 +238,7 @@ void Sio2::Infrared()
 
 void Sio2::Memcard()
 {
-	MultitapProtocol& mtap = (port ? g_MultitapPort1 : g_MultitapPort0);
+	MultitapProtocol& mtap = g_MultitapArr.at(this->port);
 
 	mcd = &mcds[port][mtap.GetMemcardSlot()];
 
@@ -511,7 +497,6 @@ bool Sio2::DoState(StateWrapper& sw)
 	sw.Do(&unknown2);
 	sw.Do(&iStat);
 	sw.Do(&port);
-	sw.Do(&slot);
 	sw.Do(&send3Read);
 	sw.Do(&send3Position);
 	sw.Do(&commandLength);
diff --git a/pcsx2/SIO/Sio2.h b/pcsx2/SIO/Sio2.h
index 2f0caac08a..ffe3f5ea62 100644
--- a/pcsx2/SIO/Sio2.h
+++ b/pcsx2/SIO/Sio2.h
@@ -40,7 +40,6 @@ public:
 	u32 iStat; // 0x1f808280
 
 	u8 port = 0;
-	u8 slot = 0;
 
 	// The current working index of SEND3. The SEND3 register is a 16 position
 	// array of command descriptors. Each descriptor describes the port the command
diff --git a/pcsx2/SaveState.cpp b/pcsx2/SaveState.cpp
index bed63cc1bd..fbe54cc801 100644
--- a/pcsx2/SaveState.cpp
+++ b/pcsx2/SaveState.cpp
@@ -33,6 +33,7 @@
 #include "R3000A.h"
 #include "SIO/Sio0.h"
 #include "SIO/Sio2.h"
+#include "SIO/Multitap/MultitapProtocol.h"
 #include "SPU2/spu2.h"
 #include "SaveState.h"
 #include "StateWrapper.h"
@@ -259,6 +260,9 @@ bool SaveStateBase::FreezeInternals()
 
 		okay = okay && g_Sio0.DoState(sw);
 		okay = okay && g_Sio2.DoState(sw);
+		okay = okay && g_MultitapArr.at(0).DoState(sw);
+		okay = okay && g_MultitapArr.at(1).DoState(sw);
+
 		if (!okay || !sw.IsGood())
 			return false;
 
diff --git a/pcsx2/SaveState.h b/pcsx2/SaveState.h
index 6b5c804c9e..5ee8130d52 100644
--- a/pcsx2/SaveState.h
+++ b/pcsx2/SaveState.h
@@ -37,7 +37,7 @@ enum class FreezeAction
 // [SAVEVERSION+]
 // This informs the auto updater that the users savestates will be invalidated.
 
-static const u32 g_SaveVersion = (0x9A48 << 16) | 0x0000;
+static const u32 g_SaveVersion = (0x9A49 << 16) | 0x0000;
 
 
 // the freezing data between submodules and core