Files
pcsx2/pcsx2/Sio.cpp
T
Admiral H. Curtiss c651d28cc0 Clear memory card ejection timeout when a game boots.
This eliminates prompts at the start of a game complaining about no
memory card being inserted.
I'm honestly not entirely sure if this is safe (is there some memory
card driver that could cache results between different executables?) but
if it isn't we'll see it soon enough!
2015-07-12 14:38:14 +02:00

949 lines
20 KiB
C++

/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2010 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 "IopCommon.h"
#include "Sio.h"
#include "sio_internal.h"
_sio sio;
_mcd mcds[2][4];
_mcd *mcd;
SIO_MODE siomode = SIO_START;
static void sioWrite8inl(u8 data);
#define SIO_WRITE void inline
// Magic psx values from nocash info
static const u8 memcard_psx[] = {0x5A, 0x5D, 0x5C, 0x5D, 0x04, 0x00, 0x00, 0x80};
// Memory Card Specs for standard Sony 8mb carts:
// Flags (magic sio '+' thingie!), Sector size, eraseBlockSize (in pages), card size (in pages), xor checksum (superblock?), terminator (unused?).
static const mc_command_0x26_tag mc_sizeinfo_8mb= {'+', 512, 16, 0x4000, 0x52, 0x5A};
// Ejection timeout management belongs in the MemoryCardFile plugin, except the plugin
// interface is not yet complete.
//Reinsert the card after auto-eject: after max tries or after min tries + XXX milliseconds, whichever comes first.
//E.g. if the game polls the card 100 times/sec and max tries=100, then after 1 second it will see the card as inserted (ms timeout not reached).
//E.g. if the game polls the card 1 time/sec, then it will see the card ejected 4 times, and on the 5th it will see it as inserted (4 secs from the initial access).
//(A 'try' in this context is the game accessing SIO)
static const int FORCED_MCD_EJECTION_MIN_TRIES =2;
static const int FORCED_MCD_EJECTION_MAX_TRIES =128;
static const float FORCED_MCD_EJECTION_MAX_MS_AFTER_MIN_TRIES =2800;
wxString GetTimeMsStr(){
wxDateTime unow=wxDateTime::UNow();
wxString res;
res.Printf(L"%s.%03d", WX_STR(unow.Format(L"%H:%M:%S")), (int)unow.GetMillisecond() );
return res;
}
//allow timeout also for the mcd manager panel
void SetForceMcdEjectTimeoutNow()
{
for( u8 port=0; port<2; ++port )
for( u8 slot=0; slot<4; ++slot )
mcds[port][slot].ForceEjection_Timeout = FORCED_MCD_EJECTION_MAX_TRIES;
}
void ClearMcdEjectTimeoutNow()
{
for( u8 port=0; port<2; ++port )
for( u8 slot=0; slot<4; ++slot )
mcds[port][slot].ForceEjection_Timeout = 0;
}
// SIO Inline'd IRQs : Calls the SIO interrupt handlers directly instead of
// feeding them through the IOP's branch test. (see SIO.H for details)
#ifdef SIO_INLINE_IRQS
#define SIO_INT() sioInterrupt()
#define SIO_FORCEINLINE __fi
#else
__fi void SIO_INT()
{
if( !(psxRegs.interrupt & (1<<IopEvt_SIO)) )
PSX_INT(IopEvt_SIO, 64 ); // PSXCLK/250000);
}
#define SIO_FORCEINLINE __fi
#endif
// Currently only check if pad wants mtap to be active.
// Could lets PCSX2 have its own options, if anyone ever
// wants to add support for using the extra memcard slots.
static bool IsMtapPresent( uint port )
{
return EmuConfig.MultitapEnabled( port );
//return (0 != PADqueryMtap(port+1));
}
void sioInit()
{
memzero(sio);
sio.bufSize = 4;
siomode = SIO_START;
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].ForceEjection_Timeout = 0;
}
sio.slot[i] = 0;
}
sio.port = 0;
mcd = &mcds[0][0];
// Transfer(?) Ready and the Buffer is Empty
sio.StatReg = TX_RDY | TX_EMPTY;
sio.packetsize = 0;
}
void SIO_FORCEINLINE sioInterrupt()
{
PAD_LOG("Sio Interrupt");
sio.StatReg|= IRQ;
psxHu32(0x1070)|=0x80;
}
SIO_WRITE sioWriteStart(u8 data)
{
u32 sioreg = sio2.packet.sendArray3[sio2.cmdport ? (sio2.cmdport - 1) : 0];
//u16 size1 = (sioreg >> 8) & 0x1FF;
u16 size2 = (sioreg >> 18) & 0x1FF;
//if(size1 != size2)
// DevCon.Warning("SIO: Bad command length [%02X] (%02X|%02X)", data, size1, size2);
// On mismatch, sio2.cmdlength (size1) is smaller than what it should (Persona 3)
// while size2 is the proper length. -KrossX
sio.bufSize = size2; //std::max(size1, size2);
if(sio.bufSize)
sio.bufSize--;
switch(data)
{
case 0x01: siomode = SIO_CONTROLLER; break;
case 0x21: siomode = SIO_MULTITAP; break;
case 0x61: siomode = SIO_INFRARED; break;
case 0x81: siomode = SIO_MEMCARD; break;
default:
DevCon.Warning("%s cmd: %02X??\n", __FUNCTION__, data);
DEVICE_UNPLUGGED();
siomode = SIO_DUMMY;
break;
}
sioWrite8inl(data);
}
SIO_WRITE sioWriteController(u8 data)
{
switch(sio.bufCount)
{
case 0:
SIO_STAT_READY();
DEVICE_PLUGGED();
sio.buf[0] = PADstartPoll(sio.port + 1);
break;
default:
sio.buf[sio.bufCount] = PADpoll(data);
break;
}
SIO_INT();
}
SIO_WRITE sioWriteMultitap(u8 data)
{
static u8 siocmd = 0;
//sio.packetsize++;
switch(sio.bufCount)
{
case 0:
if(IsMtapPresent(sio.port))
{
SIO_STAT_READY();
DEVICE_PLUGGED();
sio.buf[0] = 0xFF;
sio.buf[1] = 0x80;
sio.buf[2] = 0x5A;
}
else
{
DEVICE_UNPLUGGED();
sio.buf[0] = 0x00;
siomode = SIO_DUMMY;
}
break;
case 1:
siocmd = data;
switch(data)
{
case 0x12: // Pads supported /// slots supported, port 0,1
case 0x13: // Memcards supported //// slots supported, port 2,3
sio.buf[3] = 0x04;
sio.buf[4] = 0x00;
sio.buf[5] = 0x5A; // 0x66 here, disables the thing.
//sio.bufSize = 5;
break;
case 0x21: // Select pad
case 0x22: // Select memcard
sio.buf[3] = 0x00;
sio.buf[4] = 0x00;
sio.buf[5] = 0x00;
sio.buf[6] = 0x5A;
//sio.bufSize = 6;
break;
default:
DevCon.Warning("%s cmd: %02X??\n", __FUNCTION__, data);
sio.buf[3] = 0x00;
sio.buf[4] = 0x00;
sio.buf[5] = 0x00;
sio.buf[6] = 0x00;
break;
}
break;
case 2: // Respond to 0x21/0x22 with requested port
switch(siocmd)
{
case 0x21:
{
sio.slot[sio.port] = data;
u32 ret = PADsetSlot(sio.port+1, data+1);
sio.buf[5] = ret? data : 0xFF;
sio.buf[6] = ret? 0x5A : 0x66;
}
break;
case 0x22:
{
sio.slot[sio.port] = data;
sio.buf[5] = data;
}
break;
}
break;
case 3: break;
case 5: break;
case 6: break;
//default: sio.buf[sio.bufCount] = 0x00; break;
}
SIO_INT();
}
SIO_WRITE MemcardResponse()
{
if(sio.bufSize > 1)
{
sio.buf[sio.bufSize - 1] = 0x2B;
sio.buf[sio.bufSize - 0] = mcd->term;
}
}
SIO_WRITE memcardAuth(u8 data)
{
static bool doXorCheck = false;
static u8 xorResult = 0;
if(sio.bufCount == 2)
{
switch(data)
{
case 0x01: case 0x02: case 0x04:
case 0x0F: case 0x11: case 0x13:
doXorCheck = true;
xorResult = 0;
sio.buf[3] = 0x2B;
sio.buf[sio.bufSize] = mcd->term;
break;
default:
doXorCheck = false;
MemcardResponse();
break;
}
}
else if(doXorCheck)
{
switch(sio.bufCount)
{
case 3: break;
case 12: sio.buf[12] = xorResult; break;
default: xorResult ^= data; break;
};
}
}
SIO_WRITE memcardErase(u8 data)
{
switch(sio.bufCount)
{
case 0:
if(data != 0x81) sio.bufCount = -1;
break;
case 1:
{
u8 header[] = {0xFF, 0xFF, 0xFF, 0x2B, mcd->term};
switch(data)
{
case 0x82: // Erase
//siomode = SIO_DUMMY; // Nothing more to do here.
memcpy(sio.buf, &header[1], 4);
sio.bufSize = 3;
mcd->EraseBlock();
break;
default:
DevCon.Warning("%s cmd: %02X??\n", __FUNCTION__, data);
sio.bufCount = -1;
//sio.bufSize = 3;
//sio.bufCount = 4;
break;
}
}
SIO_INT();
break;
default:
if(sio.bufCount > sio.bufSize)
{
if(data == 0x81)
{
SIO_STAT_READY();
sio.bufCount = 0x00;
}
}
break;
}
}
SIO_WRITE memcardWrite(u8 data)
{
static u8 checksum_pos = 0;
static u8 transfer_size = 0;
static bool once = false;
switch(sio.bufCount)
{
case 0:
if(data != 0x81) sio.bufCount = -1;
break;
case 1:
{
u8 header[] = {0xFF, 0xFF, 0xFF, 0x2B, mcd->term};
switch(data)
{
case 0x42: // Write
memcpy(sio.buf, header, 4);
once = true;
break;
case 0x81: // Commit
if(once)
{
siomode = SIO_DUMMY; // Nothing more to do here.
memcpy(sio.buf, &header[1], 4);
sio.bufSize = 3;
sio2.packet.recvVal1 = 0x1600; // Writing
sio2.packet.recvVal3 = 0x8C;
once = false;
break;
}
default:
DevCon.Warning("%s cmd: %02X??\n", __FUNCTION__, data);
sio.bufCount = -1;
//sio.bufSize = 3;
//sio.bufCount = 4;
break;
}
}
SIO_INT();
break;
case 2:
transfer_size = data;
sio.buf[data + 5] = mcd->term;
sio.bufSize = data + 5;
checksum_pos = data + 4;
break;
default:
if(sio.bufCount < checksum_pos)
{
sio.buf[sio.bufCount+1] = data;
}
else if(sio.bufCount == checksum_pos)
{
u8 xor_check = mcd->DoXor(&sio.buf[4], checksum_pos - 4);
if(xor_check != sio.buf[sio.bufCount])
Console.Warning("MemWrite: Checksum invalid! XOR: %02X, IN: %02X\n", xor_check, sio.buf[sio.bufCount]);
sio.buf[sio.bufCount] = xor_check;
mcd->Write(&sio.buf[4], transfer_size);
mcd->transferAddr += transfer_size;
}
if(sio.bufCount > sio.bufSize)
{
if(data == 0x81)
{
SIO_STAT_READY();
sio.bufCount = 0x00;
}
}
break;
}
}
SIO_WRITE memcardRead(u8 data)
{
static u8 checksum_pos = 0;
static u8 transfer_size = 0;
static bool once = false;
switch(sio.bufCount)
{
case 0:
if(data != 0x81) sio.bufCount = -1;
break;
case 1:
{
u8 header[] = {0xFF, 0xFF, 0xFF, 0x2B, mcd->term};
switch(data)
{
case 0x43: // Read
memcpy(sio.buf, header, 4);
once = true;
break;
case 0x81: // Commit
if(once)
{
siomode = SIO_DUMMY; // Nothing more to do here.
memcpy(sio.buf, &header[1], 4);
sio.bufSize = 3;
sio2.packet.recvVal1 = 0x1700; // Reading
sio2.packet.recvVal3 = 0x8C;
once = false;
break;
}
default:
DevCon.Warning("%s cmd: %02X??\n", __FUNCTION__, data);
sio.bufCount = -1;
//sio.bufSize = 3;
//sio.bufCount = 4;
break;
}
}
SIO_INT();
break;
case 2:
transfer_size = data;
mcd->Read(&sio.buf[4], transfer_size);
mcd->transferAddr += transfer_size;
sio.buf[transfer_size + 4] = mcd->DoXor(&sio.buf[4], transfer_size);
sio.buf[transfer_size + 5] = mcd->term;
sio.bufSize = transfer_size + 5;
break;
default:
if(sio.bufCount > sio.bufSize)
{
if(data == 0x81)
{
SIO_STAT_READY();
sio.bufCount = 0x00;
}
}
break;
}
}
SIO_WRITE memcardSector(u8 data)
{
static u8 xor_check = 0;
switch(sio.bufCount)
{
case 2: mcd->sectorAddr = data << 0; xor_check = data; break;
case 3: mcd->sectorAddr |= data << 8; xor_check ^= data; break;
case 4: mcd->sectorAddr |= data << 16; xor_check ^= data; break;
case 5: mcd->sectorAddr |= data << 24; xor_check ^= data; break;
case 6: mcd->goodSector = data == xor_check; break;
case 8: mcd->transferAddr = (512+16) * mcd->sectorAddr; break;
case 9:
{
switch(sio.cmd)
{
case 0x21: siomode = SIO_MEMCARD_ERASE; break;
case 0x22: siomode = SIO_MEMCARD_WRITE; break;
case 0x23: siomode = SIO_MEMCARD_READ; break;
}
memset8<0xFF>(sio.buf);
sio.bufCount = -1;
}
}
}
SIO_WRITE memcardInit()
{
mcd = &mcds[sio.GetPort()][sio.GetSlot()];
// forced ejection logic. Technically belongs in the McdIsPresent handler for
// the plugin, once the memorycard plugin system is completed.
bool forceEject = false;
if(mcd->ForceEjection_Timeout)
{
if(mcd->ForceEjection_Timeout == FORCED_MCD_EJECTION_MAX_TRIES && mcd->IsPresent())
Console.WriteLn( Color_Green, L"[%s] Auto-ejecting memcard [port:%d, slot:%d]", WX_STR(GetTimeMsStr()), sio.GetPort(), sio.GetSlot());
mcd->ForceEjection_Timeout--;
forceEject = true;
u32 numTimesAccessed = FORCED_MCD_EJECTION_MAX_TRIES - mcd->ForceEjection_Timeout;
//minimum tries reached. start counting millisec timeout.
if(numTimesAccessed == FORCED_MCD_EJECTION_MIN_TRIES)
mcd->ForceEjection_Timestamp = wxDateTime::UNow();
if(numTimesAccessed > FORCED_MCD_EJECTION_MIN_TRIES)
{
wxTimeSpan delta = wxDateTime::UNow().Subtract(mcd->ForceEjection_Timestamp);
if(delta.GetMilliseconds() >= FORCED_MCD_EJECTION_MAX_MS_AFTER_MIN_TRIES)
{
DevCon.Warning( L"[%s] Auto-eject: Timeout reached after mcd was accessed %d times [port:%d, slot:%d]", WX_STR(GetTimeMsStr()), numTimesAccessed, sio.GetPort(), sio.GetSlot());
mcd->ForceEjection_Timeout = 0; //Done. on next sio access the card will be seen as inserted.
}
}
if(mcd->ForceEjection_Timeout == 0 && mcd->IsPresent())
Console.WriteLn( Color_Green, L"[%s] Re-inserting auto-ejected memcard [port:%d, slot:%d]", WX_STR(GetTimeMsStr()), sio.GetPort(), sio.GetSlot());
}
if(!forceEject && mcd->IsPresent())
{
DEVICE_PLUGGED();
siomode = mcd->IsPSX() ? SIO_MEMCARD_PSX : SIO_MEMCARD;
}
else
{
DEVICE_UNPLUGGED();
siomode = SIO_DUMMY;
}
}
SIO_WRITE sioWriteMemcard(u8 data)
{
switch(sio.bufCount)
{
case 0:
SIO_STAT_READY();
memcardInit();
SIO_INT();
break;
case 1:
sio.cmd = data;
switch(data)
{
case 0x21: // SET_SECTOR_ERASE
case 0x22: // SET_SECTOR_WRITE
case 0x23: // SET_SECTOR_READ
sio2.packet.recvVal3 = 0x8C;
MemcardResponse();
siomode = SIO_MEMCARD_SECTOR;
break;
case 0x26: // GET_SPECS ?
{
sio2.packet.recvVal3 = 0x83;
mc_command_0x26_tag cmd;
PS2E_McdSizeInfo info;
mcd->GetSizeInfo(info);
cmd.field_151 = 0x2B;
cmd.sectorSize = info.SectorSize;
cmd.eraseBlocks = info.EraseBlockSizeInSectors;
cmd.mcdSizeInSectors = info.McdSizeInSectors;
cmd.mc_xor = info.Xor;
cmd.Z = mcd->term;
memcpy(&sio.buf[2], &cmd, sizeof(mc_command_0x26_tag));
}
break;
case 0x27: // SET_TERMINATOR
sio2.packet.recvVal3 = 0x8B;
break;
case 0x28: // GET_TERMINATOR
sio2.packet.recvVal3 = 0x8B;
sio.buf[2] = 0x2B;
sio.buf[3] = mcd->term;
sio.buf[4] = 0x55; // 0x55 or 0xFF ?
break;
// If the PS2 commands fail, it falls back into PSX mode
case 0x52: // PSX 'R'ead
case 0x53: // PSX 'S'tate
case 0x57: // PSX 'W'rite
siomode = SIO_DUMMY;
break;
case 0xF0: // Auth stuff
siomode = SIO_MEMCARD_AUTH;
break;
case 0x11: // On Boot/Probe
case 0x12: // On Write/Delete/Recheck?
sio2.packet.recvVal3 = 0x8C;
case 0x81: // Checked right after copy/delete
case 0xBF: // Wtf?? On game booting?
case 0xF3: // Reset?
case 0xF7: // No idea
MemcardResponse();
siomode = SIO_DUMMY;
break;
default:
DevCon.Warning("%s cmd: %02X??\n", __FUNCTION__, data);
siomode = SIO_DUMMY;
break;
}
SIO_INT();
break;
case 2:
switch(sio.cmd)
{
case 0x27: // SET_TERMINATOR
mcd->term = data;
MemcardResponse();
break;
}
break;
}
}
SIO_WRITE sioWriteMemcardPSX(u8 data)
{
switch(sio.bufCount)
{
case 0: // Same init stuff...
SIO_STAT_READY();
memcardInit();
SIO_INT();
break;
case 1:
sio.cmd = data;
switch(data)
{
case 0x53: // PSX 'S'tate // haven't seen it happen yet
sio.buf[1] = mcd->FLAG;
memcpy(&sio.buf[2], memcard_psx, 8);
siomode = SIO_DUMMY;
break;
case 0x52: // PSX 'R'ead / Probe
case 0x57: // PSX 'W'rite
sio.buf[1] = 0x00; //mcd->FLAG;
sio.buf[2] = 0x5A; // probe end, success "0x5A"
sio.buf[3] = 0x5D;
sio.buf[4] = 0x00;
break;
case 0x58: // POCKETSTATION!! Grrrr // Lots of love to the PS2DEV/ps2sdk
DEVICE_UNPLUGGED(); // Check is for 0x01000 on stat
siomode = SIO_DUMMY;
break;
default:
//printf("%s cmd: %02X??\n", __FUNCTION__, data);
siomode = SIO_DUMMY;
break;
}
SIO_INT();
break;
case 2: break;
case 3: break;
case 4:
sio.buf[5] = data;
mcd->sectorAddr = data << 8;
break;
case 5:
sio.buf[6] = data;
mcd->sectorAddr |= data;
mcd->goodSector = !(mcd->sectorAddr > 0x3FF);
mcd->transferAddr = 128 * mcd->sectorAddr;
break;
case 6:
if(sio.cmd == 0x52)
{
// READ
if(!mcd->goodSector)
{
memset8<0xFF>(sio.buf);
siomode = SIO_DUMMY;
}
else
{
sio.buf[8] = sio.buf[5];
sio.buf[9] = sio.buf[6];
sio.buf[6] = 0x5C;
sio.buf[7] = 0x5D;
mcd->Read(&sio.buf[10], 0x80);
sio.buf[138] = mcd->DoXor(&sio.buf[8], 0x80 + 2);
sio.buf[139] = 0x47;
siomode = SIO_DUMMY;
}
}
else
{
sio.buf[sio.bufCount+1] = data;
}
break;
default:
// WRITE
sio.buf[sio.bufCount+1] = data;
if(sio.bufCount == 134)
{
u8 xorcheck = mcd->DoXor(&sio.buf[5], 0x80+2);
sio.buf[135] = 0x5C;
sio.buf[136] = 0x5D;
// (47h=Good, 4Eh=BadChecksum, FFh=BadSector)
sio.buf[137] = data == xorcheck ? 0x47 : 0x4E;
if(!mcd->goodSector) sio.buf[137] = 0xFF;
else mcd->Write(&sio.buf[7], 0x80);
siomode = SIO_DUMMY;
}
break;
}
}
SIO_WRITE sioWriteInfraRed(u8 data)
{
SIO_STAT_READY();
DEVICE_PLUGGED();
siomode = SIO_DUMMY;
SIO_INT();
}
static void sioWrite8inl(u8 data)
{
switch(siomode)
{
case SIO_START: sioWriteStart(data); break;
case SIO_CONTROLLER: sioWriteController(data); break;
case SIO_MULTITAP: sioWriteMultitap(data); break;
case SIO_INFRARED: sioWriteInfraRed(data); break;
case SIO_MEMCARD: sioWriteMemcard(data); break;
case SIO_MEMCARD_AUTH: memcardAuth(data); break;
case SIO_MEMCARD_ERASE: memcardErase(data); break;
case SIO_MEMCARD_WRITE: memcardWrite(data); break;
case SIO_MEMCARD_READ: memcardRead(data); break;
case SIO_MEMCARD_SECTOR: memcardSector(data); break;
case SIO_MEMCARD_PSX: sioWriteMemcardPSX(data); break;
case SIO_DUMMY: break;
};
}
void sioWriteCtrl16(u16 value)
{
static u8 tcount[2];
tcount[sio.port] = sio.bufCount;
sio.port = (value >> 13) & 1;
//printf("RegCtrl: %04X, %d\n", value, sio.bufCount);
sio.CtrlReg = value & ~RESET_ERR;
if (value & RESET_ERR) sio.StatReg &= ~IRQ;
if ((sio.CtrlReg & SIO_RESET) || (!sio.CtrlReg))
{
siomode = SIO_START;
tcount[0] = 0;
tcount[1] = 0;
sio.StatReg = TX_RDY | TX_EMPTY;
psxRegs.interrupt &= ~(1<<IopEvt_SIO);
}
sio.bufCount = tcount[sio.port];
}
u8 sioRead8()
{
u8 ret;
if(sio.StatReg & RX_RDY)
{
ret = sio.buf[sio.bufCount];
if(sio.bufCount == sio.bufSize) SIO_STAT_EMPTY();
sio.bufCount++;
}
else
{
ret = sio.ret;
}
return ret;
}
void sioWrite8(u8 value)
{
sioWrite8inl(value);
}
void SIODMAWrite(u8 value)
{
sioWrite8inl(value);
}
void sioNextFrame() {
for ( uint port = 0; port < 2; ++port ) {
for ( uint slot = 0; slot < 4; ++slot ) {
mcds[port][slot].NextFrame();
}
}
}
// Used to figure out when a new game boots, so that memory cards can re-index themselves and only load data relevant to that game.
wxString SioCurrentGameSerial = L"";
void sioSetGameSerial( const wxString& serial ) {
if ( serial == SioCurrentGameSerial ) { return; }
SioCurrentGameSerial = serial;
for ( uint port = 0; port < 2; ++port ) {
for ( uint slot = 0; slot < 4; ++slot ) {
mcds[port][slot].ReIndex( serial );
}
}
SetForceMcdEjectTimeoutNow();
}
void SaveStateBase::sioFreeze()
{
// CRCs for memory cards.
u64 m_mcdCRCs[2][8];
FreezeTag( "sio" );
Freeze( sio );
// TODO : This stuff should all be moved to the memorycard plugin eventually,
// but that requires adding memorycard plugin to the savestate, and I'm not in
// the mood to do that (let's plan it for 0.9.8) --air
if( IsSaving() )
{
for( uint port=0; port<2; ++port )
for( uint slot=0; slot<4; ++slot )
m_mcdCRCs[port][slot] = mcds[port][slot].GetChecksum();
}
Freeze( m_mcdCRCs );
if( IsLoading() && EmuConfig.McdEnableEjection )
{
// Notes on the ForceEjectionTimeout:
// * TOTA works with values as low as 20 here.
// It "times out" with values around 1800 (forces user to check the memcard
// twice to find it). Other games could be different. :|
//
// * At 64: Disgaea 1 and 2, and Grandia 2 end up displaying a quick "no memcard!"
// notice before finding the memorycard and re-enumerating it. A very minor
// annoyance, but no breakages.
// * GuitarHero will break completely with almost any value here, by design, because
// it has a "rule" that the memcard should never be ejected during a song. So by
// ejecting it, the game freezes (which is actually good emulation, but annoying!)
for( u8 port=0; port<2; ++port )
for( u8 slot=0; slot<4; ++slot )
{
u64 checksum = mcds[port][slot].GetChecksum();
if( checksum != m_mcdCRCs[port][slot] )
mcds[port][slot].ForceEjection_Timeout = FORCED_MCD_EJECTION_MAX_TRIES;
}
}
}