/* 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 .
*/
#include "PrecompiledHeader.h"
#include "IopCommon.h"
#include "Sio.h"
#include "sio_internal.h"
_sio sio;
static const u8 cardh[4] = { 0xFF, 0xFF, 0x5a, 0x5d };
// 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.
static int m_ForceEjectionTimeout[2];
// 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<
static void apply_xor( u8& dest, const T& src )
{
u8* buf = (u8*)&src;
for (uint x=0; x(sio.buf);
sio.buf[3] = sio.terminator;
sio.buf[2] = '+';
sio.mcdst = 99;
sio2.packet.recvVal3 = 0x8c;
break;
// FIXME : Why are there two identical cases for resetting the
// memorycard(s)? there doesn't appear to be anything dealing with
// card slots here. --air
case 0x12: // RESET
log_cmdname = "Reset2";
sio.bufcount = 8;
memset8<0xff>(sio.buf);
sio.buf[3] = sio.terminator;
sio.buf[2] = '+';
sio.mcdst = 99;
sio2.packet.recvVal3 = 0x8c;
break;
case 0x81: // COMMIT
log_cmdname = "Commit";
sio.bufcount = 8;
memset8<0xff>(sio.buf);
sio.mcdst = 99;
sio.buf[3] = sio.terminator;
sio.buf[2] = '+';
sio2.packet.recvVal3 = 0x8c;
if(value == 0x81) {
if(sio.mc_command==0x42)
sio2.packet.recvVal1 = 0x1600; // Writing
else if(sio.mc_command==0x43) sio2.packet.recvVal1 = 0x1700; // Reading
}
break;
case 0x21:
case 0x22:
case 0x23: // SECTOR SET
log_cmdname = "SetSector";
sio.bufcount = 8; sio.mcdst = 99; sio.sector=0; sio.k=0;
memset8<0xff>(sio.buf);
sio2.packet.recvVal3 = 0x8c;
sio.buf[8]=sio.terminator;
sio.buf[7]='+';
break;
case 0x24: break;
case 0x25: break;
case 0x26:
{
log_cmdname = "GetInfo";
const uint port = sio.GetMemcardIndex();
const uint slot = sio.activeMemcardSlot[port];
mc_command_0x26_tag cmd = mc_sizeinfo_8mb;
PS2E_McdSizeInfo info;
info.SectorSize = cmd.sectorSize;
info.EraseBlockSizeInSectors = cmd.eraseBlocks;
info.McdSizeInSectors = cmd.mcdSizeInSectors;
SysPlugins.McdGetSizeInfo( port, slot, info );
pxAssumeDev( cmd.mcdSizeInSectors >= mc_sizeinfo_8mb.mcdSizeInSectors,
"Mcd plugin returned an invalid memorycard size: Cards smaller than 8MB are not supported." );
cmd.sectorSize = info.SectorSize;
cmd.eraseBlocks = info.EraseBlockSizeInSectors;
cmd.mcdSizeInSectors = info.McdSizeInSectors;
// Recalculate the xor summation
// This uses a trick of removing the known xor values for a default 8mb memorycard (for which the XOR
// was calculated), and replacing it with our new values.
apply_xor( cmd.mc_xor, mc_sizeinfo_8mb.sectorSize );
apply_xor( cmd.mc_xor, mc_sizeinfo_8mb.eraseBlocks );
apply_xor( cmd.mc_xor, mc_sizeinfo_8mb.mcdSizeInSectors );
apply_xor( cmd.mc_xor, cmd.sectorSize );
apply_xor( cmd.mc_xor, cmd.eraseBlocks );
apply_xor( cmd.mc_xor, cmd.mcdSizeInSectors );
sio.bufcount = 12; sio.mcdst = 99; sio2.packet.recvVal3 = 0x83;
memset8<0xff>(sio.buf);
memcpy_fast(&sio.buf[2], &cmd, sizeof(cmd));
sio.buf[12]=sio.terminator;
}
break;
case 0x27:
case 0x28:
case 0xBF:
log_cmdname = "NotSure"; // FIXME !!
sio.bufcount = 4; sio.mcdst = 99; sio2.packet.recvVal3 = 0x8b;
memset8<0xff>(sio.buf);
sio.buf[4]=sio.terminator;
sio.buf[3]='+';
break;
// FIXME ?
// sio.lastsector and sio.mode are unused.
case 0x42: // WRITE
log_cmdname = "Write";
//sio.mode = 0;
goto __doReadWrite;
case 0x43: // READ
log_cmdname = "Read";
//sio.lastsector = sio.sector; // Reading
goto __doReadWrite;
case 0x82:
log_cmdname = "Read(?)"; // FIXME !!
//if(sio.lastsector==sio.sector) sio.mode = 2;
__doReadWrite:
sio.bufcount =133; sio.mcdst = 99;
memset8<0xff>(sio.buf);
sio.buf[133]=sio.terminator;
sio.buf[132]='+';
break;
case 0xf0:
case 0xf1:
case 0xf2:
log_cmdname = "NoClue"; // FIXME !!
sio.mcdst = 99;
break;
case 0xf3:
case 0xf7:
log_cmdname = "NoClueHereEither"; // FIXME !!
sio.bufcount = 4; sio.mcdst = 99;
memset8<0xff>(sio.buf);
sio.buf[4]=sio.terminator;
sio.buf[3]='+';
break;
case 0x52:
log_cmdname = "FixMe"; // FIXME !!
sio.rdwr = 1; memset8<0xff>(sio.buf);
sio.buf[sio.bufcount]=sio.terminator; sio.buf[sio.bufcount-1]='+';
break;
case 0x57:
log_cmdname = "FixMe"; // FIXME !!
sio.rdwr = 2; memset8<0xff>(sio.buf);
sio.buf[sio.bufcount]=sio.terminator; sio.buf[sio.bufcount-1]='+';
break;
default:
log_cmdname = "Unknown";
sio.mcdst = 0;
memset8<0xff>(sio.buf);
sio.buf[sio.bufcount]=sio.terminator; sio.buf[sio.bufcount-1]='+';
}
MEMCARDS_LOG("MC(%d) command 0x%02X [%s]", sio.GetMemcardIndex()+1, value, log_cmdname);
sio.mc_command = value;
}
return; // END CASE 1.
// FURTHER PROCESSING OF THE MEMORY CARD COMMANDS
case 99:
{
sio.packetsize++;
sio.parp++;
switch(sio.mc_command)
{
// SET_ERASE_PAGE; the next erase commands will *clear* data starting with the page set here
case 0x21:
// SET_WRITE_PAGE; the next write commands will commit data starting with the page set here
case 0x22:
// SET_READ_PAGE; the next read commands will return data starting with the page set here
case 0x23:
if (sio.parp==2)sio.sector|=(value & 0xFF)<< 0;
if (sio.parp==3)sio.sector|=(value & 0xFF)<< 8;
if (sio.parp==4)sio.sector|=(value & 0xFF)<<16;
if (sio.parp==5)sio.sector|=(value & 0xFF)<<24;
if (sio.parp==6)
{
if (sio_xor((u8 *)&sio.sector, 4) == value)
MEMCARDS_LOG("MC(%d) SET PAGE sio.sector, sector=0x%04X", sio.GetMemcardIndex()+1, sio.sector);
else
MEMCARDS_LOG("MC(%d) SET PAGE XOR value ERROR 0x%02X != ^0x%02X",
sio.GetMemcardIndex()+1, value, sio_xor((u8 *)&sio.sector, 4));
}
break;
// SET_TERMINATOR; reads the new terminator code
case 0x27:
if(sio.parp==2) {
sio.terminator = value;
sio.buf[4] = value;
MEMCARDS_LOG("MC(%d) SET TERMINATOR command, value=0x%02X", sio.GetMemcardIndex()+1, value);
}
break;
// GET_TERMINATOR; puts in position 3 the current terminator code and in 4 the default one
// depending on the param
case 0x28:
if(sio.parp == 2) {
sio.buf[2] = '+';
sio.buf[3] = sio.terminator;
//if(value == 0) sio.buf[4] = 0xFF;
sio.buf[4] = 0x55;
MEMCARDS_LOG("MC(%d) GET TERMINATOR command, value=0x%02X", sio.GetMemcardIndex()+1, value);
}
break;
// WRITE DATA
case 0x42:
if (sio.parp==2) {
sio.bufcount=5+value;
memset8<0xff>(sio.buf);
sio.buf[sio.bufcount-1]='+';
sio.buf[sio.bufcount]=sio.terminator;
MEMCARDS_LOG("MC(%d) WRITE command, size=0x%02X", sio.GetMemcardIndex()+1, value);
}
else
if ((sio.parp>2) && (sio.parp(sio.buf);
sio.buf[12] = 0; // Xor value of data from index 4 to 11
sio.buf[3]='+';
sio.buf[13] = sio.terminator;
break;
case 6:
case 7:
case 11:
sio.bufcount=13;
memset8<0xff>(sio.buf);
sio.buf[12]='+';
sio.buf[13] = sio.terminator;
break;
default:
sio.bufcount=4;
memset8<0xff>(sio.buf);
sio.buf[3]='+';
sio.buf[4] = sio.terminator;
}
}
break;
}
if (sio.bufcount<=sio.parp) sio.mcdst = 0;
}
return; // END CASE 99.
}
switch (sio.mtapst)
{
case 0x1:
sio.packetsize++;
sio.parp = 1;
SIO_INT();
switch(value) {
case 0x12:
// Query number of pads supported.
sio.buf[3] = 4;
sio.mtapst = 2;
sio.bufcount = 5;
break;
case 0x13:
// Query number of memcards supported.
sio.buf[3] = 4;
sio.mtapst = 2;
sio.bufcount = 5;
break;
case 0x21:
// Set pad slot.
sio.mtapst = value;
sio.bufcount = 6; // No idea why this is 6, saved from old code.
break;
case 0x22:
// Set memcard slot.
sio.mtapst = value;
sio.bufcount = 6; // No idea why this is 6, saved from old code.
break;
}
// Commented out values are from original code. They break multitap in bios.
sio.buf[sio.bufcount-1]=0;//'+';
sio.buf[sio.bufcount]=0;//'Z';
return;
case 0x2:
sio.packetsize++;
sio.parp++;
if (sio.bufcount<=sio.parp) sio.mcdst = 0;
SIO_INT();
return;
case 0x21:
// Set pad slot.
sio.packetsize++;
sio.parp++;
sio.mtapst = 2;
if (sio.CtrlReg & 2)
{
int port = sio.GetMultitapPort();
if (IsMtapPresent(port))
sio.activePadSlot[port] = value;
}
SIO_INT();
return;
case 0x22:
// Set memcard slot.
sio.packetsize++;
sio.parp++;
sio.mtapst = 2;
if (sio.CtrlReg & 2)
{
int port = sio.GetMultitapPort();
if (IsMtapPresent(port))
sio.activeMemcardSlot[port] = value;
}
SIO_INT();
return;
}
if(sio.count == 1 || way == 0) InitializeSIO(value);
}
void InitializeSIO(u8 value)
{
switch (value) {
case 0x01: // start pad
sio.StatReg &= ~TX_EMPTY; // Now the Buffer is not empty
sio.StatReg |= RX_RDY; // Transfer is Ready
sio.bufcount = 4; // Default size, when no pad connected.
sio.parp = 0;
sio.padst = 1;
sio.packetsize = 1;
sio.count = 0;
sio2.packet.recvVal1 = 0x1100; // Pad is present
if( (sio.CtrlReg & 2) == 2 )
{
int padslot = (sio.CtrlReg>>12) & 2; // move 0x2000 bitmask into leftmost bits
if( padslot != 1 )
{
padslot >>= 1; // transform 0/2 to be 0/1 values
if (!PADsetSlot(padslot+1, 1+sio.activePadSlot[padslot]) && sio.activePadSlot[padslot])
{
// Pad is not present. Don't send poll, just return a bunch of 0's.
sio2.packet.recvVal1 = 0x1D100;
sio.padst = 3;
}
else {
sio.buf[0] = PADstartPoll(padslot+1);
}
}
}
SIO_INT();
return;
case 0x21: // start mtap
sio.StatReg &= ~TX_EMPTY; // Now the Buffer is not empty
sio.StatReg |= RX_RDY; // Transfer is Ready
sio.parp = 0;
sio.packetsize = 1;
sio.mtapst = 1;
sio.count = 0;
sio2.packet.recvVal1 = 0x1D100; // Mtap is not connected :(
if (sio.CtrlReg & 2) // No idea if this test is needed. Pads use it, memcards don't.
{
int port = sio.GetMultitapPort();
if (!IsMtapPresent(port))
{
// If "unplug" multitap mid game, set active slots to 0.
sio.activePadSlot[port] = 0;
sio.activeMemcardSlot[port] = 0;
}
else
{
sio.bufcount = 3;
sio.buf[0] = 0xFF;
sio.buf[1] = 0x80; // Have no idea if this is correct. From PSX mtap.
sio.buf[2] = 0x5A;
sio2.packet.recvVal1 = 0x1100; // Mtap is connected :)
}
}
SIO_INT();
return;
case 0x61: // start remote control sensor
sio.StatReg &= ~TX_EMPTY; // Now the Buffer is not empty
sio.StatReg |= RX_RDY; // Transfer is Ready
sio.parp = 0;
sio.packetsize = 1;
sio.count = 0;
sio2.packet.recvVal1 = 0x1100; // Pad is present
SIO_INT();
return;
case 0x81: // start memcard
{
sio.StatReg &= ~TX_EMPTY;
sio.StatReg |= RX_RDY;
memcpy(sio.buf, cardh, 4);
sio.parp = 0;
sio.bufcount = 8;
sio.mcdst = 1;
sio.packetsize = 1;
sio.rdwr = 0;
sio.count = 0;
// Memcard presence reporting!
// Note:
// 0x01100 means Memcard is present
// 0x1D100 means Memcard is missing.
const uint port = sio.GetMemcardIndex();
const uint slot = sio.activeMemcardSlot[port];
// forced ejection logic. Technically belongs in the McdIsPresent handler for
// the plugin, once the memorycard plugin system is completed.
// (ejection is only supported for the default non-multitap cards at this time)
bool forceEject = false;
if( slot == 0 && m_ForceEjectionTimeout[port] )
{
--m_ForceEjectionTimeout[port];
forceEject = true;
}
if( !forceEject && SysPlugins.McdIsPresent( port, slot ) )
{
sio2.packet.recvVal1 = 0x1100;
PAD_LOG("START MEMCARD [port:%d, slot:%d] - Present", port, slot );
}
else
{
sio2.packet.recvVal1 = 0x1D100;
PAD_LOG("START MEMCARD [port:%d, slot:%d] - Missing", port, slot );
}
SIO_INT();
}
return;
}
}
void sioWrite8(u8 value)
{
SIO_CommandWrite(value,0);
}
void SIODMAWrite(u8 value)
{
SIO_CommandWrite(value,1);
}
void sioWriteCtrl16(u16 value) {
sio.CtrlReg = value & ~RESET_ERR;
if (value & RESET_ERR) sio.StatReg &= ~IRQ;
if ((sio.CtrlReg & SIO_RESET) || (!sio.CtrlReg))
{
sio.mtapst = 0; sio.padst = 0; sio.mcdst = 0; sio.parp = 0;
sio.StatReg = TX_RDY | TX_EMPTY;
psxRegs.interrupt &= ~(1<