mirror of https://github.com/PCSX2/pcsx2.git
957 lines
20 KiB
C++
957 lines
20 KiB
C++
/* PCSX2 - PS2 Emulator for PCs
|
|
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
|
*
|
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "PrecompiledHeader.h"
|
|
#include "R3000A.h"
|
|
#include "IopHw.h"
|
|
#include "IopDma.h"
|
|
|
|
#include "Common.h"
|
|
#include "Sio.h"
|
|
#include "MemoryCardProtocol.h"
|
|
#include "MultitapProtocol.h"
|
|
#include "Config.h"
|
|
#include "Host.h"
|
|
#include "PAD/Host/PAD.h"
|
|
|
|
#include "common/Timer.h"
|
|
#include "Recording/InputRecording.h"
|
|
#include "IconsFontAwesome5.h"
|
|
|
|
#define SIO0LOG_ENABLE 0
|
|
#define SIO2LOG_ENABLE 0
|
|
|
|
#define Sio0Log if (SIO0LOG_ENABLE) DevCon
|
|
#define Sio2Log if (SIO2LOG_ENABLE) DevCon
|
|
|
|
std::deque<u8> fifoIn;
|
|
std::deque<u8> fifoOut;
|
|
|
|
Sio0 sio0;
|
|
Sio2 sio2;
|
|
|
|
_mcd mcds[2][4];
|
|
_mcd *mcd;
|
|
|
|
// ============================================================================
|
|
// SIO0
|
|
// ============================================================================
|
|
|
|
void Sio0::ClearStatAcknowledge()
|
|
{
|
|
stat &= ~(SIO0_STAT::ACK);
|
|
}
|
|
|
|
Sio0::Sio0()
|
|
{
|
|
this->FullReset();
|
|
}
|
|
|
|
Sio0::~Sio0() = default;
|
|
|
|
void Sio0::SoftReset()
|
|
{
|
|
padStarted = false;
|
|
sioMode = SioMode::NOT_SET;
|
|
sioCommand = 0;
|
|
sioStage = SioStage::IDLE;
|
|
}
|
|
|
|
void Sio0::FullReset()
|
|
{
|
|
SoftReset();
|
|
|
|
port = 0;
|
|
slot = 0;
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
mcds[i][j].term = 0x55;
|
|
mcds[i][j].port = i;
|
|
mcds[i][j].slot = j;
|
|
mcds[i][j].FLAG = 0x08;
|
|
mcds[i][j].autoEjectTicks = 0;
|
|
}
|
|
}
|
|
|
|
mcd = &mcds[0][0];
|
|
}
|
|
|
|
// Simulates the ACK line on the bus. Peripherals are expected to send an ACK signal
|
|
// over this line to tell the PS1 "keep sending me things I'm not done yet". The PS1
|
|
// then uses this after it receives the peripheral's response to decide what to do.
|
|
void Sio0::Acknowledge()
|
|
{
|
|
stat |= SIO0_STAT::ACK;
|
|
}
|
|
|
|
void Sio0::Interrupt(Sio0Interrupt sio0Interrupt)
|
|
{
|
|
switch (sio0Interrupt)
|
|
{
|
|
case Sio0Interrupt::TEST_EVENT:
|
|
iopIntcIrq(7);
|
|
break;
|
|
case Sio0Interrupt::STAT_READ:
|
|
ClearStatAcknowledge();
|
|
break;
|
|
case Sio0Interrupt::TX_DATA_WRITE:
|
|
break;
|
|
default:
|
|
Console.Error("%s(%d) Invalid parameter", __FUNCTION__, sio0Interrupt);
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
if (!(psxRegs.interrupt & (1 << IopEvt_SIO)))
|
|
{
|
|
PSX_INT(IopEvt_SIO, PSXCLK / 250000); // PSXCLK/250000);
|
|
}
|
|
}
|
|
|
|
u8 Sio0::GetTxData()
|
|
{
|
|
Sio0Log.WriteLn("%s() SIO0 TX_DATA Read (%02X)", __FUNCTION__, txData);
|
|
return txData;
|
|
}
|
|
|
|
u8 Sio0::GetRxData()
|
|
{
|
|
Sio0Log.WriteLn("%s() SIO0 RX_DATA Read (%02X)", __FUNCTION__, rxData);
|
|
stat |= (SIO0_STAT::TX_READY | SIO0_STAT::TX_EMPTY);
|
|
stat &= ~(SIO0_STAT::RX_FIFO_NOT_EMPTY);
|
|
return rxData;
|
|
}
|
|
|
|
u32 Sio0::GetStat()
|
|
{
|
|
Sio0Log.WriteLn("%s() SIO0 STAT Read (%08X)", __FUNCTION__, stat);
|
|
const u32 ret = stat;
|
|
Interrupt(Sio0Interrupt::STAT_READ);
|
|
return ret;
|
|
}
|
|
|
|
u16 Sio0::GetMode()
|
|
{
|
|
Sio0Log.WriteLn("%s() SIO0 MODE Read (%08X)", __FUNCTION__, mode);
|
|
return mode;
|
|
}
|
|
|
|
u16 Sio0::GetCtrl()
|
|
{
|
|
Sio0Log.WriteLn("%s() SIO0 CTRL Read (%08X)", __FUNCTION__, ctrl);
|
|
return ctrl;
|
|
}
|
|
|
|
u16 Sio0::GetBaud()
|
|
{
|
|
Sio0Log.WriteLn("%s() SIO0 BAUD Read (%08X)", __FUNCTION__, baud);
|
|
return baud;
|
|
}
|
|
|
|
void Sio0::SetTxData(u8 value)
|
|
{
|
|
Sio0Log.WriteLn("%s(%02X) SIO0 TX_DATA Write", __FUNCTION__, value);
|
|
|
|
stat |= SIO0_STAT::TX_READY | SIO0_STAT::TX_EMPTY;
|
|
stat |= (SIO0_STAT::RX_FIFO_NOT_EMPTY);
|
|
|
|
if (!(ctrl & SIO0_CTRL::TX_ENABLE))
|
|
{
|
|
Console.Warning("%s(%02X) CTRL in illegal state, exiting instantly", __FUNCTION__, value);
|
|
return;
|
|
}
|
|
|
|
txData = value;
|
|
u8 res = 0;
|
|
|
|
switch (sioStage)
|
|
{
|
|
case SioStage::IDLE:
|
|
sioMode = value;
|
|
stat |= SIO0_STAT::TX_READY;
|
|
|
|
switch (sioMode)
|
|
{
|
|
case SioMode::PAD:
|
|
res = PADstartPoll(port, slot);
|
|
|
|
if (res)
|
|
{
|
|
Acknowledge();
|
|
}
|
|
|
|
break;
|
|
case SioMode::MEMCARD:
|
|
mcd = &mcds[port][slot];
|
|
|
|
// Check if auto ejection is active. If so, set RECV1 to DISCONNECTED,
|
|
// and zero out the fifo to simulate dead air over the wire.
|
|
if (mcd->autoEjectTicks)
|
|
{
|
|
SetRxData(0x00);
|
|
mcd->autoEjectTicks--;
|
|
|
|
if (mcd->autoEjectTicks == 0)
|
|
{
|
|
Host::AddKeyedFormattedOSDMessage(
|
|
StringUtil::StdStringFromFormat("AutoEjectSlotClear%u%u", port, slot),
|
|
10.0f,
|
|
"Memory card in port %d / slot %d reinserted",
|
|
port + 1,
|
|
slot + 1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// If memcard is missing, not PS1, or auto ejected, do not let SIO0 stage advance,
|
|
// reply with dead air and no ACK.
|
|
if (!mcd->IsPresent() || !mcd->IsPSX())
|
|
{
|
|
SetRxData(0x00);
|
|
return;
|
|
}
|
|
|
|
Acknowledge();
|
|
break;
|
|
}
|
|
|
|
SetRxData(res);
|
|
sioStage = SioStage::WAITING_COMMAND;
|
|
break;
|
|
case SioStage::WAITING_COMMAND:
|
|
stat &= ~(SIO0_STAT::TX_READY);
|
|
|
|
if (IsPadCommand(value))
|
|
{
|
|
res = PADpoll(value);
|
|
SetRxData(res);
|
|
|
|
if (!PADcomplete())
|
|
{
|
|
Acknowledge();
|
|
}
|
|
|
|
sioStage = SioStage::WORKING;
|
|
}
|
|
else if (IsMemcardCommand(value))
|
|
{
|
|
SetRxData(flag);
|
|
Acknowledge();
|
|
sioCommand = value;
|
|
sioStage = SioStage::WORKING;
|
|
}
|
|
else if (IsPocketstationCommand(value))
|
|
{
|
|
// Set the line low, no acknowledge.
|
|
SetRxData(0x00);
|
|
sioStage = SioStage::IDLE;
|
|
}
|
|
else
|
|
{
|
|
Console.Error("%s(%02X) Bad SIO command", __FUNCTION__, value);
|
|
SetRxData(0xff);
|
|
SoftReset();
|
|
}
|
|
|
|
break;
|
|
case SioStage::WORKING:
|
|
switch (sioMode)
|
|
{
|
|
case SioMode::PAD:
|
|
res = PADpoll(value);
|
|
SetRxData(res);
|
|
|
|
if (!PADcomplete())
|
|
{
|
|
Acknowledge();
|
|
}
|
|
|
|
break;
|
|
case SioMode::MEMCARD:
|
|
SetRxData(Memcard(value));
|
|
break;
|
|
default:
|
|
Console.Error("%s(%02X) Unhandled SioMode: %02X", __FUNCTION__, value, sioMode);
|
|
SetRxData(0xff);
|
|
SoftReset();
|
|
break;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
Console.Error("%s(%02X) Unhandled SioStage: %02X", __FUNCTION__, value, static_cast<u8>(sioStage));
|
|
SetRxData(0xff);
|
|
SoftReset();
|
|
break;
|
|
}
|
|
|
|
Interrupt(Sio0Interrupt::TX_DATA_WRITE);
|
|
}
|
|
|
|
void Sio0::SetRxData(u8 value)
|
|
{
|
|
Sio0Log.WriteLn("%s(%02X) SIO0 RX_DATA Write", __FUNCTION__, value);
|
|
rxData = value;
|
|
}
|
|
|
|
void Sio0::SetStat(u32 value)
|
|
{
|
|
Sio0Log.Error("%s(%08X) SIO0 STAT Write", __FUNCTION__, value);
|
|
}
|
|
|
|
void Sio0::SetMode(u16 value)
|
|
{
|
|
Sio0Log.WriteLn("%s(%04X) SIO0 MODE Write", __FUNCTION__, value);
|
|
mode = value;
|
|
}
|
|
|
|
void Sio0::SetCtrl(u16 value)
|
|
{
|
|
Sio0Log.WriteLn("%s(%04X) SIO0 CTRL Write", __FUNCTION__, value);
|
|
ctrl = value;
|
|
port = (ctrl & SIO0_CTRL::PORT) > 0;
|
|
|
|
// CTRL appears to be set to 0 between every "transaction".
|
|
// Not documented anywhere, but we'll use this to "reset"
|
|
// the SIO0 state, particularly during the annoying probes
|
|
// to memcards that occur when a game boots.
|
|
if (ctrl == 0)
|
|
{
|
|
g_MemoryCardProtocol.ResetPS1State();
|
|
SoftReset();
|
|
}
|
|
|
|
// If CTRL acknowledge, reset STAT bits 3 and 9
|
|
if (ctrl & SIO0_CTRL::ACK)
|
|
{
|
|
stat &= ~(SIO0_STAT::IRQ | SIO0_STAT::RX_PARITY_ERROR);
|
|
}
|
|
|
|
if (ctrl & SIO0_CTRL::RESET)
|
|
{
|
|
stat = 0;
|
|
ctrl = 0;
|
|
mode = 0;
|
|
SoftReset();
|
|
}
|
|
}
|
|
|
|
void Sio0::SetBaud(u16 value)
|
|
{
|
|
Sio0Log.WriteLn("%s(%04X) SIO0 BAUD Write", __FUNCTION__, value);
|
|
baud = value;
|
|
}
|
|
|
|
bool Sio0::IsPadCommand(u8 command)
|
|
{
|
|
return command >= PadCommand::UNK_0 && command <= PadCommand::ANALOG;
|
|
}
|
|
|
|
bool Sio0::IsMemcardCommand(u8 command)
|
|
{
|
|
return command == MemcardCommand::PS1_READ || command == MemcardCommand::PS1_STATE || command == MemcardCommand::PS1_WRITE;
|
|
}
|
|
|
|
bool Sio0::IsPocketstationCommand(u8 command)
|
|
{
|
|
return command == MemcardCommand::PS1_POCKETSTATION;
|
|
}
|
|
|
|
u8 Sio0::Pad(u8 value)
|
|
{
|
|
if (PADcomplete())
|
|
{
|
|
padStarted = false;
|
|
}
|
|
else if (!padStarted)
|
|
{
|
|
padStarted = true;
|
|
PADstartPoll(port, slot);
|
|
Acknowledge();
|
|
}
|
|
|
|
return PADpoll(value);
|
|
}
|
|
|
|
u8 Sio0::Memcard(u8 value)
|
|
{
|
|
switch (sioCommand)
|
|
{
|
|
case MemcardCommand::PS1_READ:
|
|
return g_MemoryCardProtocol.PS1Read(value);
|
|
case MemcardCommand::PS1_STATE:
|
|
return g_MemoryCardProtocol.PS1State(value);
|
|
case MemcardCommand::PS1_WRITE:
|
|
return g_MemoryCardProtocol.PS1Write(value);
|
|
case MemcardCommand::PS1_POCKETSTATION:
|
|
return g_MemoryCardProtocol.PS1Pocketstation(value);
|
|
default:
|
|
Console.Error("%s(%02X) Unhandled memcard command (%02X)", __FUNCTION__, value, sioCommand);
|
|
SoftReset();
|
|
break;
|
|
}
|
|
|
|
return 0xff;
|
|
}
|
|
|
|
// ============================================================================
|
|
// SIO2
|
|
// ============================================================================
|
|
|
|
|
|
Sio2::Sio2()
|
|
{
|
|
this->FullReset();
|
|
}
|
|
|
|
Sio2::~Sio2() = default;
|
|
|
|
void Sio2::SoftReset()
|
|
{
|
|
send3Read = false;
|
|
send3Position = 0;
|
|
commandLength = 0;
|
|
processedLength = 0;
|
|
// Clear dmaBlockSize, in case the next SIO2 command is not sent over DMA11.
|
|
dmaBlockSize = 0;
|
|
send3Complete = false;
|
|
|
|
// Anything in fifoIn which was not necessary to consume should be cleared out prior to the next SIO2 cycle.
|
|
while (!fifoIn.empty())
|
|
{
|
|
fifoIn.pop_front();
|
|
}
|
|
}
|
|
|
|
void Sio2::FullReset()
|
|
{
|
|
this->SoftReset();
|
|
|
|
for (size_t i = 0; i < send3.size(); i++)
|
|
{
|
|
send3.at(i) = 0;
|
|
}
|
|
|
|
for (size_t i = 0; i < send1.size(); i++)
|
|
{
|
|
send1.at(i) = 0;
|
|
send2.at(i) = 0;
|
|
}
|
|
|
|
dataIn = 0;
|
|
dataOut = 0;
|
|
SetCtrl(Sio2Ctrl::SIO2MAN_RESET);
|
|
SetRecv1(Recv1::DISCONNECTED);
|
|
recv2 = Recv2::DEFAULT;
|
|
recv3 = Recv3::DEFAULT;
|
|
unknown1 = 0;
|
|
unknown2 = 0;
|
|
iStat = 0;
|
|
|
|
port = 0;
|
|
slot = 0;
|
|
|
|
while (!fifoOut.empty())
|
|
{
|
|
fifoOut.pop_front();
|
|
}
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
mcds[i][j].term = 0x55;
|
|
mcds[i][j].port = i;
|
|
mcds[i][j].slot = j;
|
|
mcds[i][j].FLAG = 0x08;
|
|
mcds[i][j].autoEjectTicks = 0;
|
|
}
|
|
}
|
|
|
|
mcd = &mcds[0][0];
|
|
}
|
|
|
|
|
|
void Sio2::Interrupt()
|
|
{
|
|
iopIntcIrq(17);
|
|
}
|
|
|
|
void Sio2::SetCtrl(u32 value)
|
|
{
|
|
this->ctrl = value;
|
|
|
|
if (this->ctrl & Sio2Ctrl::START_TRANSFER)
|
|
{
|
|
Interrupt();
|
|
}
|
|
}
|
|
|
|
void Sio2::SetSend3(size_t position, u32 value)
|
|
{
|
|
this->send3.at(position) = value;
|
|
|
|
if (position == 0)
|
|
{
|
|
SoftReset();
|
|
}
|
|
}
|
|
|
|
void Sio2::SetRecv1(u32 value)
|
|
{
|
|
this->recv1 = value;
|
|
}
|
|
|
|
void Sio2::Pad()
|
|
{
|
|
// Send PAD our current port, and get back whatever it says the first response byte should be.
|
|
const u8 firstResponseByte = PADstartPoll(port, slot);
|
|
fifoOut.push_back(firstResponseByte);
|
|
// Some games will refuse to read ALL pads, if RECV1 is not set to the CONNECTED value when ANY pad is polled,
|
|
// REGARDLESS of whether that pad is truly connected or not.
|
|
SetRecv1(Recv1::CONNECTED);
|
|
|
|
// Then for every byte in fifoIn, pass to PAD and see what it kicks back to us.
|
|
while (!fifoIn.empty())
|
|
{
|
|
const u8 commandByte = fifoIn.front();
|
|
fifoIn.pop_front();
|
|
const u8 responseByte = PADpoll(commandByte);
|
|
fifoOut.push_back(responseByte);
|
|
}
|
|
}
|
|
|
|
void Sio2::Multitap()
|
|
{
|
|
fifoOut.push_back(0x00);
|
|
|
|
const bool multitapEnabled = (port == 0 && EmuConfig.MultitapPort0_Enabled) || (port == 1 && EmuConfig.MultitapPort1_Enabled);
|
|
SetRecv1(multitapEnabled ? Recv1::CONNECTED : Recv1::DISCONNECTED);
|
|
|
|
if (multitapEnabled)
|
|
{
|
|
g_MultitapProtocol.SendToMultitap();
|
|
}
|
|
else
|
|
{
|
|
while (fifoOut.size() < commandLength)
|
|
{
|
|
fifoOut.push_back(0x00);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sio2::Infrared()
|
|
{
|
|
SetRecv1(Recv1::DISCONNECTED);
|
|
|
|
fifoIn.pop_front();
|
|
const u8 responseByte = 0xff;
|
|
|
|
while (fifoOut.size() < commandLength)
|
|
{
|
|
fifoOut.push_back(responseByte);
|
|
}
|
|
}
|
|
|
|
void Sio2::Memcard()
|
|
{
|
|
mcd = &mcds[port][slot];
|
|
|
|
// Check if auto ejection is active. If so, set RECV1 to DISCONNECTED,
|
|
// and zero out the fifo to simulate dead air over the wire.
|
|
if (mcd->autoEjectTicks)
|
|
{
|
|
SetRecv1(Recv1::DISCONNECTED);
|
|
fifoOut.push_back(0x00); // Because Sio2::Write pops the first fifoIn member
|
|
|
|
while (!fifoIn.empty())
|
|
{
|
|
fifoIn.pop_front();
|
|
fifoOut.push_back(0x00);
|
|
}
|
|
|
|
mcd->autoEjectTicks--;
|
|
|
|
if (mcd->autoEjectTicks == 0)
|
|
{
|
|
Host::AddKeyedFormattedOSDMessage(
|
|
StringUtil::StdStringFromFormat("AutoEjectSlotClear%u%u", port, slot),
|
|
10.0f,
|
|
"Memory card in port %d / slot %d reinserted",
|
|
port + 1,
|
|
slot + 1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
SetRecv1(mcd->IsPresent() ? Recv1::CONNECTED : Recv1::DISCONNECTED);
|
|
|
|
const u8 commandByte = fifoIn.front();
|
|
fifoIn.pop_front();
|
|
const u8 responseByte = mcd->IsPresent() ? 0x00 : 0xff;
|
|
fifoOut.push_back(responseByte);
|
|
// Technically, the FLAG byte is only for PS1 memcards. However,
|
|
// since this response byte is still a dud on PS2 memcards, we can
|
|
// basically just cheat and always make this our second response byte for memcards.
|
|
fifoOut.push_back(mcd->FLAG);
|
|
u8 ps1Input = 0;
|
|
u8 ps1Output = 0;
|
|
|
|
switch (commandByte)
|
|
{
|
|
case MemcardCommand::PROBE:
|
|
g_MemoryCardProtocol.Probe();
|
|
break;
|
|
case MemcardCommand::UNKNOWN_WRITE_DELETE_END:
|
|
g_MemoryCardProtocol.UnknownWriteDeleteEnd();
|
|
break;
|
|
case MemcardCommand::SET_ERASE_SECTOR:
|
|
case MemcardCommand::SET_WRITE_SECTOR:
|
|
case MemcardCommand::SET_READ_SECTOR:
|
|
g_MemoryCardProtocol.SetSector();
|
|
break;
|
|
case MemcardCommand::GET_SPECS:
|
|
g_MemoryCardProtocol.GetSpecs();
|
|
break;
|
|
case MemcardCommand::SET_TERMINATOR:
|
|
g_MemoryCardProtocol.SetTerminator();
|
|
break;
|
|
case MemcardCommand::GET_TERMINATOR:
|
|
g_MemoryCardProtocol.GetTerminator();
|
|
break;
|
|
case MemcardCommand::WRITE_DATA:
|
|
g_MemoryCardProtocol.WriteData();
|
|
break;
|
|
case MemcardCommand::READ_DATA:
|
|
g_MemoryCardProtocol.ReadData();
|
|
break;
|
|
case MemcardCommand::PS1_READ:
|
|
g_MemoryCardProtocol.ResetPS1State();
|
|
|
|
while (!fifoIn.empty())
|
|
{
|
|
ps1Input = fifoIn.front();
|
|
ps1Output = g_MemoryCardProtocol.PS1Read(ps1Input);
|
|
fifoIn.pop_front();
|
|
fifoOut.push_back(ps1Output);
|
|
}
|
|
|
|
break;
|
|
case MemcardCommand::PS1_STATE:
|
|
g_MemoryCardProtocol.ResetPS1State();
|
|
|
|
while (!fifoIn.empty())
|
|
{
|
|
ps1Input = fifoIn.front();
|
|
ps1Output = g_MemoryCardProtocol.PS1State(ps1Input);
|
|
fifoIn.pop_front();
|
|
fifoOut.push_back(ps1Output);
|
|
}
|
|
|
|
break;
|
|
case MemcardCommand::PS1_WRITE:
|
|
g_MemoryCardProtocol.ResetPS1State();
|
|
|
|
while (!fifoIn.empty())
|
|
{
|
|
ps1Input = fifoIn.front();
|
|
ps1Output = g_MemoryCardProtocol.PS1Write(ps1Input);
|
|
fifoIn.pop_front();
|
|
fifoOut.push_back(ps1Output);
|
|
}
|
|
|
|
break;
|
|
case MemcardCommand::PS1_POCKETSTATION:
|
|
g_MemoryCardProtocol.ResetPS1State();
|
|
|
|
while (!fifoIn.empty())
|
|
{
|
|
ps1Input = fifoIn.front();
|
|
ps1Output = g_MemoryCardProtocol.PS1Pocketstation(ps1Input);
|
|
fifoIn.pop_front();
|
|
fifoOut.push_back(ps1Output);
|
|
}
|
|
|
|
break;
|
|
case MemcardCommand::READ_WRITE_END:
|
|
g_MemoryCardProtocol.ReadWriteEnd();
|
|
break;
|
|
case MemcardCommand::ERASE_BLOCK:
|
|
g_MemoryCardProtocol.EraseBlock();
|
|
break;
|
|
case MemcardCommand::UNKNOWN_BOOT:
|
|
g_MemoryCardProtocol.UnknownBoot();
|
|
break;
|
|
case MemcardCommand::AUTH_XOR:
|
|
g_MemoryCardProtocol.AuthXor();
|
|
break;
|
|
case MemcardCommand::AUTH_F3:
|
|
g_MemoryCardProtocol.AuthF3();
|
|
break;
|
|
case MemcardCommand::AUTH_F7:
|
|
g_MemoryCardProtocol.AuthF7();
|
|
break;
|
|
default:
|
|
Console.Warning("%s() Unhandled memcard command %02X, things are about to break!", __FUNCTION__, commandByte);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Sio2::Write(u8 data)
|
|
{
|
|
Sio2Log.WriteLn("%s(%02X) SIO2 DATA Write", __FUNCTION__, data);
|
|
|
|
if (!send3Read)
|
|
{
|
|
// No more SEND3 positions to access, but the game is still sending us SIO2 writes. Lets ignore them.
|
|
if (send3Position > send3.size())
|
|
{
|
|
Console.Warning("%s(%02X) Received data after exhausting all SEND3 values!", __FUNCTION__, data);
|
|
return;
|
|
}
|
|
|
|
const u32 currentSend3 = send3.at(send3Position);
|
|
port = currentSend3 & Send3::PORT;
|
|
commandLength = (currentSend3 >> 8) & Send3::COMMAND_LENGTH_MASK;
|
|
send3Read = true;
|
|
|
|
// The freshly read SEND3 position had a length of 0, so we are done handling SIO2 commands until
|
|
// the next SEND3 writes.
|
|
if (commandLength == 0)
|
|
{
|
|
send3Complete = true;
|
|
}
|
|
|
|
// If the prior command did not need to fully pop fifoIn, do so now,
|
|
// so that the next command isn't trying to read the last command's leftovers.
|
|
while (!fifoIn.empty())
|
|
{
|
|
fifoIn.pop_front();
|
|
}
|
|
}
|
|
|
|
if (send3Complete)
|
|
{
|
|
return;
|
|
}
|
|
|
|
fifoIn.push_back(data);
|
|
|
|
// We have received as many command bytes as we expect, and...
|
|
//
|
|
// ... These were from direct writes into IOP memory (DMA block size is zero when direct writes occur)
|
|
// ... These were from SIO2 DMA (DMA block size is non-zero when SIO2 DMA occurs)
|
|
if ((fifoIn.size() == sio2.commandLength && sio2.dmaBlockSize == 0) || fifoIn.size() == sio2.dmaBlockSize)
|
|
{
|
|
// Go ahead and prep so the next write triggers a load of the new SEND3 value.
|
|
sio2.send3Read = false;
|
|
sio2.send3Position++;
|
|
|
|
// Check the SIO mode
|
|
const u8 sioMode = fifoIn.front();
|
|
fifoIn.pop_front();
|
|
|
|
switch (sioMode)
|
|
{
|
|
case SioMode::PAD:
|
|
this->Pad();
|
|
break;
|
|
case SioMode::MULTITAP:
|
|
this->Multitap();
|
|
break;
|
|
case SioMode::INFRARED:
|
|
this->Infrared();
|
|
break;
|
|
case SioMode::MEMCARD:
|
|
this->Memcard();
|
|
break;
|
|
default:
|
|
Console.Error("%s(%02X) Unhandled SIO mode %02X", __FUNCTION__, data, sioMode);
|
|
fifoOut.push_back(0x00);
|
|
SetRecv1(Recv1::DISCONNECTED);
|
|
break;
|
|
}
|
|
|
|
// If command was sent over SIO2 DMA, align fifoOut to the block size
|
|
if (sio2.dmaBlockSize > 0)
|
|
{
|
|
const size_t dmaDiff = fifoOut.size() % sio2.dmaBlockSize;
|
|
|
|
if (dmaDiff > 0)
|
|
{
|
|
const size_t padding = sio2.dmaBlockSize - dmaDiff;
|
|
|
|
for (size_t i = 0; i < padding; i++)
|
|
{
|
|
fifoOut.push_back(0x00);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
u8 Sio2::Read()
|
|
{
|
|
u8 ret = 0x00;
|
|
|
|
if (!fifoOut.empty())
|
|
{
|
|
ret = fifoOut.front();
|
|
fifoOut.pop_front();
|
|
}
|
|
else
|
|
{
|
|
Console.Warning("%s() fifoOut underflow! Returning 0x00.", __FUNCTION__);
|
|
}
|
|
|
|
Sio2Log.WriteLn("%s() SIO2 DATA Read (%02X)", __FUNCTION__, ret);
|
|
return ret;
|
|
}
|
|
|
|
void sioNextFrame() {
|
|
for ( uint port = 0; port < 2; ++port ) {
|
|
for ( uint slot = 0; slot < 4; ++slot ) {
|
|
mcds[port][slot].NextFrame();
|
|
}
|
|
}
|
|
}
|
|
|
|
void sioSetGameSerial( const std::string& serial ) {
|
|
for ( uint port = 0; port < 2; ++port ) {
|
|
for ( uint slot = 0; slot < 4; ++slot ) {
|
|
if ( mcds[port][slot].ReIndex( serial ) ) {
|
|
AutoEject::Set( port, slot );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SaveStateBase::sio2Freeze()
|
|
{
|
|
FreezeTag("sio2");
|
|
|
|
Freeze(sio2);
|
|
FreezeDeque(fifoIn);
|
|
FreezeDeque(fifoOut);
|
|
|
|
// CRCs for memory cards.
|
|
// If the memory card hasn't changed when loading state, we can safely skip ejecting it.
|
|
u64 mcdCrcs[SIO::PORTS][SIO::SLOTS];
|
|
if (IsSaving())
|
|
{
|
|
for (u32 port = 0; port < SIO::PORTS; port++)
|
|
{
|
|
for (u32 slot = 0; slot < SIO::SLOTS; slot++)
|
|
mcdCrcs[port][slot] = mcds[port][slot].GetChecksum();
|
|
}
|
|
}
|
|
Freeze(mcdCrcs);
|
|
|
|
if (IsLoading())
|
|
{
|
|
bool ejected = false;
|
|
for (u32 port = 0; port < SIO::PORTS && !ejected; port++)
|
|
{
|
|
for (u32 slot = 0; slot < SIO::SLOTS; slot++)
|
|
{
|
|
if (mcdCrcs[port][slot] != mcds[port][slot].GetChecksum())
|
|
{
|
|
AutoEject::SetAll();
|
|
ejected = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SaveStateBase::sioFreeze()
|
|
{
|
|
FreezeTag("sio0");
|
|
Freeze(sio0);
|
|
}
|
|
|
|
std::tuple<u32, u32> sioConvertPadToPortAndSlot(u32 index)
|
|
{
|
|
if (index > 4) // [5,6,7]
|
|
return std::make_tuple(1, index - 4); // 2B,2C,2D
|
|
else if (index > 1) // [2,3,4]
|
|
return std::make_tuple(0, index - 1); // 1B,1C,1D
|
|
else // [0,1]
|
|
return std::make_tuple(index, 0); // 1A,2A
|
|
}
|
|
|
|
u32 sioConvertPortAndSlotToPad(u32 port, u32 slot)
|
|
{
|
|
if (slot == 0)
|
|
return port;
|
|
else if (port == 0) // slot=[0,1]
|
|
return slot + 1; // 2,3,4
|
|
else
|
|
return slot + 4; // 5,6,7
|
|
}
|
|
|
|
bool sioPadIsMultitapSlot(u32 index)
|
|
{
|
|
return (index >= 2);
|
|
}
|
|
|
|
bool sioPortAndSlotIsMultitap(u32 port, u32 slot)
|
|
{
|
|
return (slot != 0);
|
|
}
|
|
|
|
void AutoEject::Set(size_t port, size_t slot)
|
|
{
|
|
if (EmuConfig.McdEnableEjection)
|
|
{
|
|
mcds[port][slot].autoEjectTicks = 60;
|
|
}
|
|
}
|
|
|
|
void AutoEject::Clear(size_t port, size_t slot)
|
|
{
|
|
mcds[port][slot].autoEjectTicks = 0;
|
|
}
|
|
|
|
void AutoEject::SetAll()
|
|
{
|
|
Host::AddIconOSDMessage("AutoEjectAllSet", ICON_FA_SD_CARD, "Force ejecting all memory cards.", Host::OSD_INFO_DURATION);
|
|
|
|
for (size_t port = 0; port < SIO::PORTS; port++)
|
|
{
|
|
for (size_t slot = 0; slot < SIO::SLOTS; slot++)
|
|
{
|
|
AutoEject::Set(port, slot);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AutoEject::ClearAll()
|
|
{
|
|
for (size_t port = 0; port < SIO::PORTS; port++)
|
|
{
|
|
for (size_t slot = 0; slot < SIO::SLOTS; slot++)
|
|
{
|
|
AutoEject::Clear(port, slot);
|
|
}
|
|
}
|
|
} |