Merge pull request #243 from LPFaint99/memcard

Add the option to use a folder of gamecube saves instead of a memcard image [issue 6599]
This commit is contained in:
Pierre Bourdon 2014-06-23 03:17:54 +02:00
commit 01fc1a6c54
15 changed files with 1279 additions and 345 deletions

View File

@ -99,6 +99,8 @@ set(SRCS ActionReplay.cpp
HW/EXI_DeviceMemoryCard.cpp
HW/EXI_DeviceMic.cpp
HW/GCMemcard.cpp
HW/GCMemcardDirectory.cpp
HW/GCMemcardRaw.cpp
HW/GCPad.cpp
HW/GCPadEmu.cpp
HW/GPFifo.cpp

View File

@ -137,6 +137,8 @@
<ClCompile Include="HW\EXI_DeviceMemoryCard.cpp" />
<ClCompile Include="HW\EXI_DeviceMic.cpp" />
<ClCompile Include="HW\GCMemcard.cpp" />
<ClCompile Include="HW\GCMemcardDirectory.cpp" />
<ClCompile Include="HW\GCMemcardRaw.cpp" />
<ClCompile Include="HW\GCPad.cpp" />
<ClCompile Include="HW\GCPadEmu.cpp" />
<ClCompile Include="HW\GPFifo.cpp" />
@ -333,6 +335,8 @@
<ClInclude Include="HW\EXI_DeviceMemoryCard.h" />
<ClInclude Include="HW\EXI_DeviceMic.h" />
<ClInclude Include="HW\GCMemcard.h" />
<ClInclude Include="HW\GCMemcardDirectory.h" />
<ClInclude Include="HW\GCMemcardRaw.h" />
<ClInclude Include="HW\GCPad.h" />
<ClInclude Include="HW\GCPadEmu.h" />
<ClInclude Include="HW\GPFifo.h" />

View File

@ -410,6 +410,12 @@
<ClCompile Include="HW\GCMemcard.cpp">
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
</ClCompile>
<ClCompile Include="HW\GCMemcardDirectory.cpp">
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
</ClCompile>
<ClCompile Include="HW\GCMemcardRaw.cpp">
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
</ClCompile>
<ClCompile Include="HW\GCPad.cpp">
<Filter>HW %28Flipper/Hollywood%29\GCPad</Filter>
</ClCompile>
@ -933,6 +939,12 @@
<ClInclude Include="HW\GCMemcard.h">
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
</ClInclude>
<ClInclude Include="HW\GCMemcardDirectory.h">
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
</ClInclude>
<ClInclude Include="HW\GCMemcardRaw.h">
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
</ClInclude>
<ClInclude Include="HW\GCPadEmu.h">
<Filter>HW %28Flipper/Hollywood%29\GCPad</Filter>
</ClInclude>

View File

@ -99,9 +99,13 @@ IEXIDevice* EXIDevice_Create(TEXIDevices device_type, const int channel_num)
break;
case EXIDEVICE_MEMORYCARD:
result = new CEXIMemoryCard(channel_num);
case EXIDEVICE_MEMORYCARDFOLDER:
{
bool gci_folder = (device_type == EXIDEVICE_MEMORYCARDFOLDER);
result = new CEXIMemoryCard(channel_num, gci_folder);
device_type = EXIDEVICE_MEMORYCARD;
break;
}
case EXIDEVICE_MASKROM:
result = new CEXIIPL();
break;

View File

@ -17,6 +17,8 @@ enum TEXIDevices
EXIDEVICE_ETH,
EXIDEVICE_AM_BASEBOARD,
EXIDEVICE_GECKO,
EXIDEVICE_MEMORYCARDFOLDER, // Only used when creating a device by EXIDevice_Create
// Converted to EXIDEVICE_MEMORYCARD internally
EXIDEVICE_NONE = (u8)-1
};

View File

@ -5,7 +5,6 @@
#include "Common/Common.h"
#include "Common/FileUtil.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
@ -14,6 +13,9 @@
#include "Core/HW/EXI_Device.h"
#include "Core/HW/EXI_DeviceMemoryCard.h"
#include "Core/HW/GCMemcard.h"
#include "Core/HW/GCMemcardDirectory.h"
#include "Core/HW/GCMemcardRaw.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/Sram.h"
#define MC_STATUS_BUSY 0x80
@ -23,15 +25,14 @@
#define MC_STATUS_PROGRAMEERROR 0x08
#define MC_STATUS_READY 0x01
#define SIZE_TO_Mb (1024 * 8 * 16)
#define MC_HDR_SIZE 0xA000
void CEXIMemoryCard::FlushCallback(u64 userdata, int cyclesLate)
{
// note that userdata is forbidden to be a pointer, due to the implementation of EventDoState
int card_index = (int)userdata;
CEXIMemoryCard* pThis = (CEXIMemoryCard*)ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARD, card_index);
if (pThis)
pThis->Flush();
if (pThis && pThis->memorycard)
pThis->memorycard->Flush();
}
void CEXIMemoryCard::CmdDoneCallback(u64 userdata, int cyclesLate)
@ -42,17 +43,13 @@ void CEXIMemoryCard::CmdDoneCallback(u64 userdata, int cyclesLate)
pThis->CmdDone();
}
CEXIMemoryCard::CEXIMemoryCard(const int index)
CEXIMemoryCard::CEXIMemoryCard(const int index, bool gciFolder)
: card_index(index)
, m_bDirty(false)
{
m_strFilename = (card_index == 0) ? SConfig::GetInstance().m_strMemoryCardA : SConfig::GetInstance().m_strMemoryCardB;
if (Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsUsingMemcard() && Movie::IsStartingFromClearSave())
m_strFilename = File::GetUserPath(D_GCUSER_IDX) + "Movie.raw";
// we're potentially leaking events here, since there's no UnregisterEvent until emu shutdown, but I guess it's inconsequential
et_this_card = CoreTiming::RegisterEvent((card_index == 0) ? "memcardFlushA" : "memcardFlushB", FlushCallback);
et_cmd_done = CoreTiming::RegisterEvent((card_index == 0) ? "memcardDoneA" : "memcardDoneB", CmdDoneCallback);
et_this_card = CoreTiming::RegisterEvent((index == 0) ? "memcardFlushA" : "memcardFlushB", FlushCallback);
et_cmd_done = CoreTiming::RegisterEvent((index == 0) ? "memcardDoneA" : "memcardDoneB", CmdDoneCallback);
interruptSwitch = 0;
m_bInterruptSet = 0;
@ -60,7 +57,6 @@ CEXIMemoryCard::CEXIMemoryCard(const int index)
status = MC_STATUS_BUSY | MC_STATUS_UNLOCKED | MC_STATUS_READY;
m_uPosition = 0;
memset(programming_buffer, 0, sizeof(programming_buffer));
//Nintendo Memory Card EXI IDs
//0x00000004 Memory Card 59 4Mbit
//0x00000008 Memory Card 123 8Mb
@ -71,6 +67,7 @@ CEXIMemoryCard::CEXIMemoryCard(const int index)
//0x00000510 16Mb "bigben" card
//card_id = 0xc243;
card_id = 0xc221; // It's a Nintendo brand memcard
// The following games have issues with memory cards bigger than 16Mb
// Darkened Skye GDQE6S GDQP6S
@ -81,110 +78,96 @@ CEXIMemoryCard::CEXIMemoryCard(const int index)
bool useMC251;
IniFile gameIni = Core::g_CoreStartupParameter.LoadGameIni();
gameIni.GetOrCreateSection("Core")->Get("MemoryCard251", &useMC251, false);
nintendo_card_id = MemCard2043Mb;
if (useMC251)
u16 sizeMb = useMC251 ? MemCard251Mb : MemCard2043Mb;
if (gciFolder)
{
nintendo_card_id = MemCard251Mb;
m_strFilename.insert(m_strFilename.find_last_of("."), ".251");
}
card_id = 0xc221; // It's a Nintendo brand memcard
File::IOFile pFile(m_strFilename, "rb");
if (pFile)
{
// Measure size of the memcard file.
memory_card_size = (int)pFile.GetSize();
nintendo_card_id = memory_card_size / SIZE_TO_Mb;
memory_card_content = new u8[memory_card_size];
memset(memory_card_content, 0xFF, memory_card_size);
INFO_LOG(EXPANSIONINTERFACE, "Reading memory card %s", m_strFilename.c_str());
pFile.ReadBytes(memory_card_content, memory_card_size);
setupGciFolder(sizeMb);
}
else
{
// Create a new memcard
memory_card_size = nintendo_card_id * SIZE_TO_Mb;
memory_card_content = new u8[memory_card_size];
GCMemcard::Format(memory_card_content, m_strFilename.find(".JAP.raw") != std::string::npos, nintendo_card_id);
memset(memory_card_content+MC_HDR_SIZE, 0xFF, memory_card_size-MC_HDR_SIZE);
WARN_LOG(EXPANSIONINTERFACE, "No memory card found. Will create a new one.");
}
SetCardFlashID(memory_card_content, card_index);
setupRawMemcard(sizeMb);
}
void innerFlush(FlushData* data)
memory_card_size = memorycard->GetCardId() * SIZE_TO_Mb;
u8 header[20] = {0};
memorycard->Read(0, ArraySize(header), header);
SetCardFlashID(header, card_index);
}
void CEXIMemoryCard::setupGciFolder(u16 sizeMb)
{
File::IOFile pFile(data->filename, "r+b");
if (!pFile)
DiscIO::IVolume::ECountry CountryCode = DiscIO::IVolume::COUNTRY_UNKNOWN;
auto strUniqueID = Core::g_CoreStartupParameter.m_strUniqueID;
u32 CurrentGameId = 0;
if (strUniqueID.length() >= 4)
{
std::string dir;
SplitPath(data->filename, &dir, nullptr, nullptr);
if (!File::IsDirectory(dir))
File::CreateFullPath(dir);
pFile.Open(data->filename, "wb");
CountryCode = DiscIO::CountrySwitch(strUniqueID.at(3));
memcpy((u8 *)&CurrentGameId, strUniqueID.c_str(), 4);
}
bool ascii = true;
std::string strDirectoryName = File::GetUserPath(D_GCUSER_IDX);
switch (CountryCode)
{
case DiscIO::IVolume::COUNTRY_JAPAN:
ascii = false;
strDirectoryName += JAP_DIR DIR_SEP;
break;
case DiscIO::IVolume::COUNTRY_USA:
strDirectoryName += USA_DIR DIR_SEP;
break;
default:
CountryCode = DiscIO::IVolume::COUNTRY_EUROPE;
strDirectoryName += EUR_DIR DIR_SEP;
}
strDirectoryName += StringFromFormat("Card %c", 'A' + card_index);
if (!File::Exists(strDirectoryName)) // first use of memcard folder, migrate automatically
{
MigrateFromMemcardFile(strDirectoryName + DIR_SEP, card_index);
}
else if (!File::IsDirectory(strDirectoryName))
{
if (File::Rename(strDirectoryName, strDirectoryName + ".original"))
{
PanicAlertT("%s was not a directory, moved to *.original", strDirectoryName.c_str());
MigrateFromMemcardFile(strDirectoryName + DIR_SEP, card_index);
}
else // we tried but the user wants to crash
{
// TODO more user friendly abort
PanicAlertT("%s is not a directory, failed to move to *.original.\n Verify your write permissions or move "
"the file outside of dolphin",
strDirectoryName.c_str());
exit(0);
}
}
if (!pFile) // Note - pFile changed inside above if
{
PanicAlertT("Could not write memory card file %s.\n\n"
"Are you running Dolphin from a CD/DVD, or is the save file maybe write protected?\n\n"
"Are you receiving this after moving the emulator directory?\nIf so, then you may "
"need to re-specify your memory card location in the options.", data->filename.c_str());
return;
memorycard = std::make_unique<GCMemcardDirectory>(strDirectoryName + DIR_SEP, card_index, sizeMb, ascii,
CountryCode, CurrentGameId);
}
pFile.WriteBytes(data->memcardContent, data->memcardSize);
if (!data->bExiting)
Core::DisplayMessage(StringFromFormat("Wrote memory card %c contents to %s",
data->memcardIndex ? 'B' : 'A', data->filename.c_str()).c_str(), 4000);
return;
}
// Flush memory card contents to disc
void CEXIMemoryCard::Flush(bool exiting)
void CEXIMemoryCard::setupRawMemcard(u16 sizeMb)
{
if (!m_bDirty)
return;
std::string filename =
(card_index == 0) ? SConfig::GetInstance().m_strMemoryCardA : SConfig::GetInstance().m_strMemoryCardB;
if (Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsUsingMemcard() &&
Movie::IsStartingFromClearSave())
filename = File::GetUserPath(D_GCUSER_IDX) + "Movie.raw";
if (!Core::g_CoreStartupParameter.bEnableMemcardSaving)
return;
if (flushThread.joinable())
if (sizeMb == MemCard251Mb)
{
flushThread.join();
filename.insert(filename.find_last_of("."), ".251");
}
if (!exiting)
Core::DisplayMessage(StringFromFormat("Writing to memory card %c", card_index ? 'B' : 'A'), 1000);
flushData.filename = m_strFilename;
flushData.memcardContent = memory_card_content;
flushData.memcardIndex = card_index;
flushData.memcardSize = memory_card_size;
flushData.bExiting = exiting;
flushThread = std::thread(innerFlush, &flushData);
if (exiting)
flushThread.join();
m_bDirty = false;
memorycard = std::make_unique<MemoryCard>(filename, card_index, sizeMb);
}
CEXIMemoryCard::~CEXIMemoryCard()
{
CoreTiming::RemoveEvent(et_this_card);
Flush(true);
delete[] memory_card_content;
memory_card_content = nullptr;
if (flushThread.joinable())
{
flushThread.join();
}
memorycard->Flush(true);
memorycard.release();
}
bool CEXIMemoryCard::IsPresent()
@ -210,10 +193,7 @@ void CEXIMemoryCard::CmdDoneLater(u64 cycles)
void CEXIMemoryCard::SetCS(int cs)
{
// So that memory card won't be invalidated during flushing
if (flushThread.joinable())
{
flushThread.join();
}
memorycard->joinThread();
if (cs) // not-selected to selected
{
@ -226,7 +206,7 @@ void CEXIMemoryCard::SetCS(int cs)
case cmdSectorErase:
if (m_uPosition > 2)
{
memset(memory_card_content + (address & (memory_card_size-1)), 0xFF, 0x2000);
memorycard->ClearBlock(address & (memory_card_size - 1));
status |= MC_STATUS_BUSY;
status &= ~MC_STATUS_READY;
@ -239,7 +219,8 @@ void CEXIMemoryCard::SetCS(int cs)
case cmdChipErase:
if (m_uPosition > 2)
{
memset(memory_card_content, 0xFF, memory_card_size);
// TODO: Investigate on HW, I (LPFaint99) believe that this only erases the system area (Blocks 0-4)
memorycard->ClearAll();
status &= ~MC_STATUS_BUSY;
m_bDirty = true;
}
@ -254,7 +235,7 @@ void CEXIMemoryCard::SetCS(int cs)
while (count--)
{
memory_card_content[address] = programming_buffer[i++];
memorycard->Write(address, 1, &(programming_buffer[i++]));
i &= 127;
address = (address & ~0x1FF) | ((address+1) & 0x1FF);
}
@ -336,7 +317,7 @@ void CEXIMemoryCard::TransferByte(u8 &byte)
if (m_uPosition == 1)
byte = 0x80; // dummy cycle
else
byte = (u8)(nintendo_card_id >> (24-(((m_uPosition-2) & 3) * 8)));
byte = (u8)(memorycard->GetCardId() >> (24 - (((m_uPosition - 2) & 3) * 8)));
break;
case cmdReadArray:
@ -358,7 +339,7 @@ void CEXIMemoryCard::TransferByte(u8 &byte)
}
if (m_uPosition > 1) // not specified for 1..8, anyway
{
byte = memory_card_content[address & (memory_card_size-1)];
memorycard->Read(address & (memory_card_size - 1), 1, &byte);
// after 9 bytes, we start incrementing the address,
// but only the sector offset - the pointer wraps around
if (m_uPosition >= 9)
@ -441,10 +422,7 @@ void CEXIMemoryCard::PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
// we don't exactly have anything to pause,
// but let's make sure the flush thread isn't running.
if (flushThread.joinable())
{
flushThread.join();
}
memorycard->joinThread();
}
}
@ -466,11 +444,7 @@ void CEXIMemoryCard::DoState(PointerWrap &p)
p.Do(programming_buffer);
p.Do(m_bDirty);
p.Do(address);
p.Do(nintendo_card_id);
p.Do(card_id);
p.Do(memory_card_size);
p.DoArray(memory_card_content, memory_card_size);
memorycard->DoState(p);
p.Do(card_index);
}
}
@ -483,3 +457,17 @@ IEXIDevice* CEXIMemoryCard::FindDevice(TEXIDevices device_type, int customIndex)
return nullptr;
return this;
}
// DMA reads are preceded by all of the necessary setup via IMMRead
// read all at once instead of single byte at a time as done by IEXIDevice::DMARead
void CEXIMemoryCard::DMARead(u32 _uAddr, u32 _uSize)
{
memorycard->Read(address, _uSize, Memory::GetPointer(_uAddr));
}
// DMA write are preceded by all of the necessary setup via IMMWrite
// write all at once instead of single byte at a time as done by IEXIDevice::DMAWrite
void CEXIMemoryCard::DMAWrite(u32 _uAddr, u32 _uSize)
{
memorycard->Write(address, _uSize, Memory::GetPointer(_uAddr));
}

View File

@ -3,22 +3,13 @@
// Refer to the license.txt file included.
#pragma once
#include "Common/StdMakeUnique.h"
#include "Common/Thread.h"
// Data structure to be passed to the flushing thread.
struct FlushData
{
bool bExiting;
std::string filename;
u8 *memcardContent;
int memcardSize, memcardIndex;
};
class MemoryCardBase;
class CEXIMemoryCard : public IEXIDevice
{
public:
CEXIMemoryCard(const int index);
CEXIMemoryCard(const int index, bool gciFolder);
virtual ~CEXIMemoryCard();
void SetCS(int cs) override;
bool IsInterruptSet() override;
@ -26,8 +17,12 @@ public:
void DoState(PointerWrap &p) override;
void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) override;
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1) override;
void DMARead(u32 _uAddr, u32 _uSize) override;
void DMAWrite(u32 _uAddr, u32 _uSize) override;
private:
void setupGciFolder(u16 sizeMb);
void setupRawMemcard(u16 sizeMb);
// This is scheduled whenever a page write is issued. The this pointer is passed
// through the userdata parameter, so that it can then call Flush on the right card.
static void FlushCallback(u64 userdata, int cyclesLate);
@ -63,7 +58,6 @@ private:
cmdChipErase = 0xF4,
};
std::string m_strFilename;
int card_index;
int et_this_card, et_cmd_done;
//! memory card state
@ -77,13 +71,10 @@ private:
u8 programming_buffer[128];
bool m_bDirty;
//! memory card parameters
unsigned int nintendo_card_id, card_id;
unsigned int card_id;
unsigned int address;
int memory_card_size; //! in bytes, must be power of 2.
u8 *memory_card_content;
FlushData flushData;
std::thread flushThread;
u32 memory_card_size;
std::unique_ptr<MemoryCardBase> memorycard;
protected:
virtual void TransferByte(u8 &byte) override;

View File

@ -2,6 +2,7 @@
// Licensed under GPLv2
// Refer to the license.txt file included.
#include <algorithm>
#include <cinttypes>
#include <string>
@ -15,7 +16,7 @@ static void ByteSwap(u8 *valueA, u8 *valueB)
*valueB = tmp;
}
GCMemcard::GCMemcard(const std::string& filename, bool forceCreation, bool sjis)
GCMemcard::GCMemcard(const std::string &filename, bool forceCreation, bool ascii)
: m_valid(false)
, m_fileName(filename)
{
@ -24,11 +25,13 @@ GCMemcard::GCMemcard(const std::string& filename, bool forceCreation, bool sjis)
File::IOFile mcdFile(m_fileName, "rb");
if (!mcdFile.IsOpen())
{
if (!forceCreation && !AskYesNoT("\"%s\" does not exist.\n Create a new 16MB Memcard?", filename.c_str()))
if (!forceCreation)
{
if (!AskYesNoT("\"%s\" does not exist.\n Create a new 16MB Memcard?", filename.c_str()))
return;
ascii = AskYesNoT("Format as ascii (NTSC\\PAL)?\nChoose no for sjis (NTSC-J)");
}
Format(forceCreation ? sjis : !AskYesNoT("Format as ascii (NTSC\\PAL)?\nChoose no for sjis (NTSC-J)"));
Format(ascii);
return;
}
else
@ -235,7 +238,7 @@ bool GCMemcard::Save()
return mcdFile.Close();
}
void GCMemcard::calc_checksumsBE(u16 *buf, u32 length, u16 *csum, u16 *inv_csum)
void calc_checksumsBE(u16 *buf, u32 length, u16 *csum, u16 *inv_csum)
{
*csum = *inv_csum = 0;
@ -366,7 +369,7 @@ bool GCMemcard::GCI_FileName(u8 index, std::string &filename) const
if (!m_valid || index > DIRLEN || (BE32(CurrentDir->Dir[index].Gamecode) == 0xFFFFFFFF))
return false;
filename = std::string((char*)CurrentDir->Dir[index].Gamecode, 4) + '_' + (char*)CurrentDir->Dir[index].Filename + ".gci";
filename = CurrentDir->Dir[index].GCI_FileName();
return true;
}
@ -546,7 +549,7 @@ bool GCMemcard::GetDEntry(u8 index, DEntry &dest) const
return true;
}
u16 GCMemcard::BlockAlloc::GetNextBlock(u16 Block) const
u16 BlockAlloc::GetNextBlock(u16 Block) const
{
if ((Block < MC_FST_BLOCKS) || (Block > 4091))
return 0;
@ -554,7 +557,7 @@ u16 GCMemcard::BlockAlloc::GetNextBlock(u16 Block) const
return Common::swap16(Map[Block-MC_FST_BLOCKS]);
}
u16 GCMemcard::BlockAlloc::NextFreeBlock(u16 MaxBlock, u16 StartingBlock) const
u16 BlockAlloc::NextFreeBlock(u16 MaxBlock, u16 StartingBlock) const
{
if (FreeBlocks)
{
@ -570,7 +573,7 @@ u16 GCMemcard::BlockAlloc::NextFreeBlock(u16 MaxBlock, u16 StartingBlock) const
return 0xFFFF;
}
bool GCMemcard::BlockAlloc::ClearBlocks(u16 FirstBlock, u16 BlockCount)
bool BlockAlloc::ClearBlocks(u16 FirstBlock, u16 BlockCount)
{
std::vector<u16> blocks;
while (FirstBlock != 0xFFFF && FirstBlock != 0)
@ -670,8 +673,8 @@ u32 GCMemcard::ImportFile(DEntry& direntry, std::vector<GCMBlock> &saveBlocks)
int fileBlocks = BE16(direntry.BlockCount);
FZEROGX_MakeSaveGameValid(direntry, saveBlocks);
PSO_MakeSaveGameValid(direntry, saveBlocks);
FZEROGX_MakeSaveGameValid(hdr, direntry, saveBlocks);
PSO_MakeSaveGameValid(hdr, direntry, saveBlocks);
BlockAlloc UpdatedBat = *CurrentBat;
u16 nextBlock;
@ -1201,29 +1204,23 @@ u32 GCMemcard::ReadAnimRGBA8(u8 index, u32* buffer, u8 *delays) const
return frames;
}
bool GCMemcard::Format(u8 * card_data, bool sjis, u16 SizeMb)
bool GCMemcard::Format(u8 *card_data, bool ascii, u16 SizeMb)
{
if (!card_data)
return false;
memset(card_data, 0xFF, BLOCK_SIZE*3);
memset(card_data + BLOCK_SIZE*3, 0, BLOCK_SIZE*2);
GCMC_Header gcp;
gcp.hdr = (Header*)card_data;
gcp.dir = (Directory *)(card_data + BLOCK_SIZE);
gcp.dir_backup = (Directory *)(card_data + BLOCK_SIZE*2);
gcp.bat = (BlockAlloc *)(card_data + BLOCK_SIZE*3);
gcp.bat_backup = (BlockAlloc *)(card_data + BLOCK_SIZE*4);
*((Header *)card_data) = Header(SLOT_A, SizeMb, ascii);
*(u16*)gcp.hdr->SizeMb = BE16(SizeMb);
gcp.hdr->Encoding = BE16(sjis ? 1 : 0);
FormatInternal(gcp);
*((Directory *)(card_data + BLOCK_SIZE)) = Directory();
*((Directory *)(card_data + BLOCK_SIZE * 2)) = Directory();
*((BlockAlloc *)(card_data + BLOCK_SIZE * 3)) = BlockAlloc(SizeMb);
*((BlockAlloc *)(card_data + BLOCK_SIZE * 4)) = BlockAlloc(SizeMb);
return true;
}
bool GCMemcard::Format(bool sjis, u16 SizeMb)
bool GCMemcard::Format(bool ascii, u16 SizeMb)
{
memset(&hdr, 0xFF, BLOCK_SIZE);
memset(&dir, 0xFF, BLOCK_SIZE);
@ -1231,25 +1228,14 @@ bool GCMemcard::Format(bool sjis, u16 SizeMb)
memset(&bat, 0, BLOCK_SIZE);
memset(&bat_backup, 0, BLOCK_SIZE);
GCMC_Header gcp;
gcp.hdr = &hdr;
gcp.dir = &dir;
gcp.dir_backup = &dir_backup;
gcp.bat = &bat;
gcp.bat_backup = &bat_backup;
*(u16*)hdr.SizeMb = BE16(SizeMb);
hdr.Encoding = BE16(sjis ? 1 : 0);
FormatInternal(gcp);
hdr = Header(SLOT_A, SizeMb, ascii);
dir = dir_backup = Directory();
bat = bat_backup = BlockAlloc(SizeMb);
m_sizeMb = SizeMb;
maxBlock = (u32)m_sizeMb * MBIT_TO_BLOCKS;
mc_data_blocks.reserve(maxBlock - MC_FST_BLOCKS);
for (u32 i = 0; i < (maxBlock - MC_FST_BLOCKS); ++i)
{
GCMBlock b;
mc_data_blocks.push_back(b);
}
mc_data_blocks.clear();
mc_data_blocks.resize(maxBlock - MC_FST_BLOCKS);
initDirBatPointers();
m_valid = true;
@ -1257,56 +1243,6 @@ bool GCMemcard::Format(bool sjis, u16 SizeMb)
return Save();
}
void GCMemcard::FormatInternal(GCMC_Header &GCP)
{
Header *p_hdr = GCP.hdr;
u64 rand = CEXIIPL::GetGCTime();
p_hdr->formatTime = Common::swap64(rand);
for (int i = 0; i < 12; i++)
{
rand = (((rand * (u64)0x0000000041c64e6dULL) + (u64)0x0000000000003039ULL) >> 16);
p_hdr->serial[i] = (u8)(g_SRAM.flash_id[0][i] + (u32)rand);
rand = (((rand * (u64)0x0000000041c64e6dULL) + (u64)0x0000000000003039ULL) >> 16);
rand &= (u64)0x0000000000007fffULL;
}
p_hdr->SramBias = g_SRAM.counter_bias;
p_hdr->SramLang = g_SRAM.lang;
// TODO: determine the purpose of Unk2 1 works for slot A, 0 works for both slot A and slot B
*(u32*)&p_hdr->Unk2 = 0; // = _viReg[55]; static vu16* const _viReg = (u16*)0xCC002000;
*(u16*)&p_hdr->deviceID = 0;
calc_checksumsBE((u16*)p_hdr, 0xFE, &p_hdr->Checksum, &p_hdr->Checksum_Inv);
Directory *p_dir = GCP.dir,
*p_dir_backup = GCP.dir_backup;
*(u16*)&p_dir->UpdateCounter = 0;
p_dir_backup->UpdateCounter = BE16(1);
calc_checksumsBE((u16*)p_dir, 0xFFE, &p_dir->Checksum, &p_dir->Checksum_Inv);
calc_checksumsBE((u16*)p_dir_backup, 0xFFE, &p_dir_backup->Checksum, &p_dir_backup->Checksum_Inv);
BlockAlloc *p_bat = GCP.bat,
*p_bat_backup = GCP.bat_backup;
p_bat_backup->UpdateCounter = BE16(1);
p_bat->FreeBlocks = *(u16*)&p_bat_backup->FreeBlocks = BE16(( BE16(p_hdr->SizeMb) * MBIT_TO_BLOCKS) - MC_FST_BLOCKS);
p_bat->LastAllocated = p_bat_backup->LastAllocated = BE16(4);
calc_checksumsBE((u16*)p_bat+2, 0xFFE, &p_bat->Checksum, &p_bat->Checksum_Inv);
calc_checksumsBE((u16*)p_bat_backup+2, 0xFFE, &p_bat_backup->Checksum, &p_bat_backup->Checksum_Inv);
}
void GCMemcard::CARD_GetSerialNo(u32 *serial1,u32 *serial2)
{
u32 serial[8];
for (int i = 0; i < 8; i++)
{
memcpy(&serial[i], (u8 *) &hdr+(i*4), 4);
}
*serial1 = serial[0]^serial[2]^serial[4]^serial[6];
*serial2 = serial[1]^serial[3]^serial[5]^serial[7];
}
/*************************************************************/
/* FZEROGX_MakeSaveGameValid */
/* (use just before writing a F-Zero GX system .gci file) */
@ -1318,7 +1254,7 @@ void GCMemcard::CARD_GetSerialNo(u32 *serial1,u32 *serial2)
/* Returns: Error code */
/*************************************************************/
s32 GCMemcard::FZEROGX_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &FileBuffer)
s32 GCMemcard::FZEROGX_MakeSaveGameValid(Header &cardheader, DEntry &direntry, std::vector<GCMBlock> &FileBuffer)
{
u32 i,j;
u32 serial1,serial2;
@ -1329,7 +1265,7 @@ s32 GCMemcard::FZEROGX_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock>
if (strcmp((char*)direntry.Filename,"f_zero.dat")!=0) return 0;
// get encrypted destination memory card serial numbers
CARD_GetSerialNo(&serial1,&serial2);
cardheader.CARD_GetSerialNo(&serial1, &serial2);
// set new serial numbers
*(u16*)&FileBuffer[1].block[0x0066] = BE16(BE32(serial1) >> 16);
@ -1366,7 +1302,7 @@ s32 GCMemcard::FZEROGX_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock>
/* Returns: Error code */
/***********************************************************/
s32 GCMemcard::PSO_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &FileBuffer)
s32 GCMemcard::PSO_MakeSaveGameValid(Header &cardheader, DEntry &direntry, std::vector<GCMBlock> &FileBuffer)
{
u32 i,j;
u32 chksum;
@ -1391,7 +1327,7 @@ s32 GCMemcard::PSO_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &Fi
}
// get encrypted destination memory card serial numbers
CARD_GetSerialNo(&serial1,&serial2);
cardheader.CARD_GetSerialNo(&serial1, &serial2);
// set new serial numbers
*(u32*)&FileBuffer[1].block[0x0158] = serial1;

View File

@ -17,6 +17,7 @@
#define BE32(x) (Common::swap32(x))
#define BE16(x) (Common::swap16(x))
#define ArrayByteSwap(a) (ByteSwap(a, a+sizeof(u8)));
enum
{
SLOT_A = 0,
@ -58,22 +59,39 @@ enum
CI8,
};
class GCMemcard : NonCopyable
class MemoryCardBase
{
private:
friend class CMemcardManagerDebug;
bool m_valid;
std::string m_fileName;
public:
MemoryCardBase(int _card_index = 0, int sizeMb = MemCard2043Mb)
: card_index(_card_index)
, nintendo_card_id(sizeMb)
{
}
virtual ~MemoryCardBase() {}
virtual void Flush(bool exiting = false) = 0;
virtual s32 Read(u32 address, s32 length, u8 *destaddress) = 0;
virtual s32 Write(u32 destaddress, s32 length, u8 *srcaddress) = 0;
virtual void ClearBlock(u32 address) = 0;
virtual void ClearAll() = 0;
virtual void DoState(PointerWrap &p) = 0;
virtual void joinThread() {};
u32 GetCardId() { return nintendo_card_id; }
protected:
int card_index;
u16 nintendo_card_id;
u32 memory_card_size;
};
u32 maxBlock;
u16 m_sizeMb;
struct GCMBlock
{
GCMBlock() { erase(); }
void erase() { memset(block, 0xFF, BLOCK_SIZE); }
u8 block[BLOCK_SIZE];
};
std::vector<GCMBlock> mc_data_blocks;
void calc_checksumsBE(u16 *buf, u32 length, u16 *csum, u16 *inv_csum);
#pragma pack(push,1)
struct Header { //Offset Size Description
// Serial in libogc
@ -91,10 +109,56 @@ private:
u16 Checksum; //0x01fc 2 Additive Checksum
u16 Checksum_Inv; //0x01fe 2 Inverse Checksum
u8 Unused2[7680]; //0x0200 0x1e00 Unused (0xff)
} hdr;
void CARD_GetSerialNo(u32 *serial1, u32 *serial2) const
{
u32 _serial[8];
for (int i = 0; i < 8; i++)
{
memcpy(&_serial[i], (u8 *)this + (i * 4), 4);
}
*serial1 = _serial[0] ^ _serial[2] ^ _serial[4] ^ _serial[6];
*serial2 = _serial[1] ^ _serial[3] ^ _serial[5] ^ _serial[7];
}
// Nintendo format algorithm.
// Constants are fixed by the GC SDK
// Changing the constants will break memorycard support
Header(int slot = 0, u16 sizeMb = MemCard2043Mb, bool ascii = true)
{
memset(this, 0xFF, BLOCK_SIZE);
*(u16 *)SizeMb = BE16(sizeMb);
Encoding = BE16(ascii ? 0 : 1);
u64 rand = CEXIIPL::GetGCTime();
formatTime = Common::swap64(rand);
for (int i = 0; i < 12; i++)
{
rand = (((rand * (u64)0x0000000041c64e6dULL) + (u64)0x0000000000003039ULL) >> 16);
serial[i] = (u8)(g_SRAM.flash_id[slot][i] + (u32)rand);
rand = (((rand * (u64)0x0000000041c64e6dULL) + (u64)0x0000000000003039ULL) >> 16);
rand &= (u64)0x0000000000007fffULL;
}
SramBias = g_SRAM.counter_bias;
SramLang = BE32(g_SRAM.lang);
// TODO: determine the purpose of Unk2 1 works for slot A, 0 works for both slot A and slot B
*(u32 *)&Unk2 = 0; // = _viReg[55]; static vu16* const _viReg = (u16*)0xCC002000;
*(u16 *)&deviceID = 0;
calc_checksumsBE((u16 *)this, 0xFE, &Checksum, &Checksum_Inv);
}
};
struct DEntry
{
static const u8 DENTRY_SIZE = 0x40;
DEntry() { memset(this, 0xFF, DENTRY_SIZE); }
std::string GCI_FileName() const
{
return std::string((char *)Makercode, 2) + '-' + std::string((char *)Gamecode, 4) + '-' + (char *)Filename +
".gci";
}
u8 Gamecode[4]; //0x00 0x04 Gamecode
u8 Makercode[2]; //0x04 0x02 Makercode
u8 Unused1; //0x06 0x01 reserved/unused (always 0xff, has no effect)
@ -146,9 +210,21 @@ private:
u16 UpdateCounter; //0x1ffa 2 Update Counter
u16 Checksum; //0x1ffc 2 Additive Checksum
u16 Checksum_Inv; //0x1ffe 2 Inverse Checksum
} dir, dir_backup;
Directory()
{
memset(this, 0xFF, BLOCK_SIZE);
UpdateCounter = 0;
Checksum = BE16(0xF003);
Checksum_Inv = 0;
}
void Replace(DEntry d, int idx)
{
Dir[idx] = d;
fixChecksums();
}
void fixChecksums() { calc_checksumsBE((u16 *)this, 0xFFE, &Checksum, &Checksum_Inv); }
};
Directory *CurrentDir, *PreviousDir;
struct BlockAlloc
{
u16 Checksum; //0x0000 2 Additive Checksum
@ -160,30 +236,86 @@ private:
u16 GetNextBlock(u16 Block) const;
u16 NextFreeBlock(u16 MaxBlock, u16 StartingBlock = MC_FST_BLOCKS) const;
bool ClearBlocks(u16 StartingBlock, u16 Length);
} bat,bat_backup;
BlockAlloc *CurrentBat, *PreviousBat;
struct GCMC_Header
void fixChecksums() { calc_checksumsBE((u16 *)&UpdateCounter, 0xFFE, &Checksum, &Checksum_Inv); }
BlockAlloc(u16 sizeMb = MemCard2043Mb)
{
Header *hdr;
Directory *dir, *dir_backup;
BlockAlloc *bat, *bat_backup;
memset(this, 0, BLOCK_SIZE);
// UpdateCounter = 0;
FreeBlocks = BE16((sizeMb * MBIT_TO_BLOCKS) - MC_FST_BLOCKS);
LastAllocated = BE16(4);
fixChecksums();
}
u16 AssignBlocksContiguous(u16 length)
{
u16 starting = BE16(LastAllocated) + 1;
if (length > BE16(FreeBlocks))
return 0xFFFF;
u16 current = starting;
while ((current - starting + 1) < length)
{
Map[current - 5] = BE16(current + 1);
current++;
}
Map[current - 5] = 0xFFFF;
LastAllocated = BE16(current);
FreeBlocks = BE16(BE16(FreeBlocks) - length);
fixChecksums();
return BE16(starting);
}
};
#pragma pack(pop)
class GCIFile
{
public:
bool LoadSaveBlocks();
bool HasCopyProtection()
{
if ((strcmp((char *)m_gci_header.Filename, "PSO_SYSTEM") == 0) ||
(strcmp((char *)m_gci_header.Filename, "PSO3_SYSTEM") == 0) ||
(strcmp((char *)m_gci_header.Filename, "f_zero.dat") == 0))
return true;
return false;
}
void DoState(PointerWrap &p);
DEntry m_gci_header;
std::vector<GCMBlock> m_save_data;
std::vector<u16> m_used_blocks;
int UsesBlock(u16 blocknum);
bool m_dirty;
std::string m_filename;
};
class GCMemcard : NonCopyable
{
private:
bool m_valid;
std::string m_fileName;
u32 maxBlock;
u16 m_sizeMb;
Header hdr;
Directory dir, dir_backup, *CurrentDir, *PreviousDir;
BlockAlloc bat, bat_backup, *CurrentBat, *PreviousBat;
std::vector<GCMBlock> mc_data_blocks;
u32 ImportGciInternal(FILE* gcih, const std::string& inputFile, const std::string &outputFile);
static void FormatInternal(GCMC_Header &GCP);
void initDirBatPointers();
public:
GCMemcard(const std::string& fileName, bool forceCreation=false, bool sjis=false);
bool IsValid() const { return m_valid; }
bool IsAsciiEncoding() const;
bool Save();
bool Format(bool sjis = false, u16 SizeMb = MemCard2043Mb);
static bool Format(u8 * card_data, bool sjis = false, u16 SizeMb = MemCard2043Mb);
bool Format(bool ascii = true, u16 SizeMb = MemCard2043Mb);
static bool Format(u8 *card_data, bool ascii = true, u16 SizeMb = MemCard2043Mb);
static s32 FZEROGX_MakeSaveGameValid(Header &cardheader, DEntry &direntry, std::vector<GCMBlock> &FileBuffer);
static s32 PSO_MakeSaveGameValid(Header &cardheader, DEntry &direntry, std::vector<GCMBlock> &FileBuffer);
static void calc_checksumsBE(u16 *buf, u32 length, u16 *csum, u16 *inv_csum);
u32 TestChecksums() const;
bool FixChecksums();
@ -219,10 +351,6 @@ public:
// Copies a DEntry from u8 index to DEntry& data
bool GetDEntry(u8 index, DEntry &dest) const;
// assumes there's enough space in buffer
// old determines if function uses old or new method of copying data
// some functions only work with old way, some only work with new way
// TODO: find a function that works for all calls or split into 2 functions
u32 GetSaveData(u8 index, std::vector<GCMBlock> &saveBlocks) const;
// adds the file to the directory and copies its contents
@ -249,8 +377,4 @@ public:
// reads the animation frames
u32 ReadAnimRGBA8(u8 index, u32* buffer, u8 *delays) const;
void CARD_GetSerialNo(u32 *serial1,u32 *serial2);
s32 FZEROGX_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &FileBuffer);
s32 PSO_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &FileBuffer);
};

View File

@ -0,0 +1,614 @@
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include <cinttypes>
#include "Common/CommonTypes.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/GCMemcardDirectory.h"
#include "DiscIO/Volume.h"
const int NO_INDEX = -1;
const char *MC_HDR = "MC_SYSTEM_AREA";
int GCMemcardDirectory::LoadGCI(std::string fileName, int region)
{
File::IOFile gcifile(fileName, "rb");
if (gcifile)
{
GCIFile gci;
gci.m_filename = fileName;
gci.m_dirty = false;
if (!gcifile.ReadBytes(&(gci.m_gci_header), DENTRY_SIZE))
{
ERROR_LOG(EXPANSIONINTERFACE, "%s failed to read header", fileName.c_str());
return NO_INDEX;
}
// check region
switch (gci.m_gci_header.Gamecode[3])
{
case 'J':
if (region != DiscIO::IVolume::COUNTRY_JAPAN)
{
PanicAlertT("GCI save file was not loaded because it is the wrong region for this memory card:\n%s",
fileName.c_str());
return NO_INDEX;
}
break;
case 'E':
if (region != DiscIO::IVolume::COUNTRY_USA)
{
PanicAlertT("GCI save file was not loaded because it is the wrong region for this memory card:\n%s",
fileName.c_str());
return NO_INDEX;
}
break;
case 'C':
// Used by Datel Action Replay Save
break;
default:
if (region != DiscIO::IVolume::COUNTRY_EUROPE)
{
PanicAlertT("GCI save file was not loaded because it is the wrong region for this memory card:\n%s",
fileName.c_str());
return NO_INDEX;
}
break;
}
std::string gci_filename = gci.m_gci_header.GCI_FileName();
for (u16 i = 0; i < m_loaded_saves.size(); ++i)
{
if (m_loaded_saves[i] == gci_filename)
{
PanicAlertT(
"%s\nwas not loaded because it has the same internal filename as previously loaded save\n%s",
gci.m_filename.c_str(), m_saves[i].m_filename.c_str());
return NO_INDEX;
}
}
u16 numBlocks = BE16(gci.m_gci_header.BlockCount);
// largest number of free blocks on a memory card
// in reality, there are not likely any valid gci files > 251 blocks
if (numBlocks > 2043)
{
PanicAlertT("%s\nwas not loaded because it is an invalid gci.\n Number of blocks claimed to be %d",
gci.m_filename.c_str(), numBlocks);
return NO_INDEX;
}
u32 size = numBlocks * BLOCK_SIZE;
u64 file_size = gcifile.GetSize();
if (file_size != size + DENTRY_SIZE)
{
PanicAlertT("%s\nwas not loaded because it is an invalid gci.\n File size (%" PRIx64
") does not match the size recorded in the header (%d)",
gci.m_filename.c_str(), file_size, size + DENTRY_SIZE);
return NO_INDEX;
}
if (m_GameId == BE32(gci.m_gci_header.Gamecode))
{
gci.LoadSaveBlocks();
}
u16 first_block = m_bat1.AssignBlocksContiguous(numBlocks);
if (first_block == 0xFFFF)
{
PanicAlertT("%s\nwas not loaded because there are not enough free blocks on virtual memorycard",
fileName.c_str());
return NO_INDEX;
}
*(u16 *)&gci.m_gci_header.FirstBlock = first_block;
if (gci.HasCopyProtection() && gci.LoadSaveBlocks())
{
GCMemcard::PSO_MakeSaveGameValid(m_hdr, gci.m_gci_header, gci.m_save_data);
GCMemcard::FZEROGX_MakeSaveGameValid(m_hdr, gci.m_gci_header, gci.m_save_data);
}
int idx = (int)m_saves.size();
m_dir1.Replace(gci.m_gci_header, idx);
m_saves.push_back(std::move(gci));
SetUsedBlocks(idx);
return idx;
}
return NO_INDEX;
}
GCMemcardDirectory::GCMemcardDirectory(std::string directory, int slot, u16 sizeMb, bool ascii, int region, int gameId)
: MemoryCardBase(slot, sizeMb)
, m_GameId(gameId)
, m_LastBlock(-1)
, m_hdr(slot, sizeMb, ascii)
, m_bat1(sizeMb)
, m_saves(0)
, m_SaveDirectory(directory)
{
// Use existing header data if available
if (File::Exists(m_SaveDirectory + MC_HDR))
{
File::IOFile hdrfile((m_SaveDirectory + MC_HDR), "rb");
hdrfile.ReadBytes(&m_hdr, BLOCK_SIZE);
}
File::FSTEntry FST_Temp;
File::ScanDirectoryTree(m_SaveDirectory, FST_Temp);
for (u32 j = 0; j < FST_Temp.children.size(); j++)
{
std::string ext;
std::string const &name = FST_Temp.children[j].virtualName;
SplitPath(name, NULL, NULL, &ext);
if (strcasecmp(ext.c_str(), ".gci") == 0)
{
if (m_saves.size() == DIRLEN)
{
PanicAlertT("There are too many gci files in the folder\n%s\nOnly the first 127 will be available",
m_SaveDirectory.c_str());
break;
}
int index = LoadGCI(FST_Temp.children[j].physicalName, region);
if (index != NO_INDEX)
{
m_loaded_saves.push_back(m_saves.at(index).m_gci_header.GCI_FileName());
}
}
}
m_loaded_saves.clear();
m_dir1.fixChecksums();
m_dir2 = m_dir1;
m_bat2 = m_bat1;
}
s32 GCMemcardDirectory::Read(u32 address, s32 length, u8 *destaddress)
{
s32 block = address / BLOCK_SIZE;
u32 offset = address % BLOCK_SIZE;
s32 extra = 0; // used for read calls that are across multiple blocks
if (offset + length > BLOCK_SIZE)
{
extra = length + offset - BLOCK_SIZE;
length -= extra;
// verify that we haven't calculated a length beyond BLOCK_SIZE
_dbg_assert_msg_(EXPANSIONINTERFACE, (address + length) % BLOCK_SIZE == 0,
"Memcard directory Read Logic Error");
}
if (m_LastBlock != block)
{
switch (block)
{
case 0:
m_LastBlock = block;
m_LastBlockAddress = (u8 *)&m_hdr;
break;
case 1:
m_LastBlock = -1;
m_LastBlockAddress = (u8 *)&m_dir1;
break;
case 2:
m_LastBlock = -1;
m_LastBlockAddress = (u8 *)&m_dir2;
break;
case 3:
m_LastBlock = block;
m_LastBlockAddress = (u8 *)&m_bat1;
break;
case 4:
m_LastBlock = block;
m_LastBlockAddress = (u8 *)&m_bat2;
break;
default:
m_LastBlock = SaveAreaRW(block);
if (m_LastBlock == -1)
{
memset(destaddress, 0xFF, length);
return 0;
}
}
}
memcpy(destaddress, m_LastBlockAddress + offset, length);
if (extra)
extra = Read(address + length, extra, destaddress + length);
return length + extra;
}
s32 GCMemcardDirectory::Write(u32 destaddress, s32 length, u8 *srcaddress)
{
if (length != 0x80)
ERROR_LOG(EXPANSIONINTERFACE, "WRITING TO %x, len %x", destaddress, length);
s32 block = destaddress / BLOCK_SIZE;
u32 offset = destaddress % BLOCK_SIZE;
s32 extra = 0; // used for write calls that are across multiple blocks
if (offset + length > BLOCK_SIZE)
{
extra = length + offset - BLOCK_SIZE;
length -= extra;
// verify that we haven't calculated a length beyond BLOCK_SIZE
_dbg_assert_msg_(EXPANSIONINTERFACE, (destaddress + length) % BLOCK_SIZE == 0,
"Memcard directory Write Logic Error");
}
if (m_LastBlock != block)
{
switch (block)
{
case 0:
m_LastBlock = block;
m_LastBlockAddress = (u8 *)&m_hdr;
break;
case 1:
case 2:
{
m_LastBlock = -1;
s32 bytes_written = 0;
while (length > 0)
{
s32 to_write = std::min<s32>(DENTRY_SIZE, length);
bytes_written += DirectoryWrite(destaddress + bytes_written, to_write, srcaddress + bytes_written);
length -= to_write;
}
return bytes_written;
}
case 3:
m_LastBlock = block;
m_LastBlockAddress = (u8 *)&m_bat1;
break;
case 4:
m_LastBlock = block;
m_LastBlockAddress = (u8 *)&m_bat2;
break;
default:
m_LastBlock = SaveAreaRW(block, true);
if (m_LastBlock == -1)
{
PanicAlertT("Report: GCIFolder Writing to unallocated block %x", block);
exit(0);
}
}
}
memcpy(m_LastBlockAddress + offset, srcaddress, length);
if (extra)
extra = Write(destaddress + length, extra, srcaddress + length);
return length + extra;
}
void GCMemcardDirectory::ClearBlock(u32 address)
{
if (address % BLOCK_SIZE)
{
PanicAlertT("GCMemcardDirectory: ClearBlock called with invalid block address");
return;
}
u32 block = address / BLOCK_SIZE;
switch (block)
{
case 0:
m_LastBlock = block;
m_LastBlockAddress = (u8 *)&m_hdr;
break;
case 1:
m_LastBlock = -1;
m_LastBlockAddress = (u8 *)&m_dir1;
break;
case 2:
m_LastBlock = -1;
m_LastBlockAddress = (u8 *)&m_dir2;
break;
case 3:
m_LastBlock = block;
m_LastBlockAddress = (u8 *)&m_bat1;
break;
case 4:
m_LastBlock = block;
m_LastBlockAddress = (u8 *)&m_bat2;
break;
default:
m_LastBlock = SaveAreaRW(block, true);
if (m_LastBlock == -1)
return;
}
((GCMBlock *)m_LastBlockAddress)->erase();
}
inline void GCMemcardDirectory::SyncSaves()
{
Directory *current = &m_dir2;
if (BE16(m_dir1.UpdateCounter) > BE16(m_dir2.UpdateCounter))
{
current = &m_dir1;
}
for (u32 i = 0; i < DIRLEN; ++i)
{
if (*(u32 *)&(current->Dir[i]) != 0xFFFFFFFF)
{
bool added = false;
while (i >= m_saves.size())
{
GCIFile temp;
m_saves.push_back(temp);
added = true;
}
if (added || memcmp((u8 *)&(m_saves[i].m_gci_header), (u8 *)&(current->Dir[i]), DENTRY_SIZE))
{
m_saves[i].m_dirty = true;
memcpy((u8 *)&(m_saves[i].m_gci_header), (u8 *)&(current->Dir[i]), DENTRY_SIZE);
}
}
else if ((i < m_saves.size()) && (*(u32 *)&(m_saves[i].m_gci_header) != 0xFFFFFFFF))
{
*(u32 *)&(m_saves[i].m_gci_header.Gamecode) = 0xFFFFFFF;
}
}
}
inline s32 GCMemcardDirectory::SaveAreaRW(u32 block, bool writing)
{
for (u16 i = 0; i < m_saves.size(); ++i)
{
if (BE32(m_saves[i].m_gci_header.Gamecode) != 0xFFFFFFFF)
{
if (m_saves[i].m_used_blocks.size() == 0)
{
SetUsedBlocks(i);
}
int idx = m_saves[i].UsesBlock(block);
if (idx != -1)
{
if (!m_saves[i].LoadSaveBlocks())
{
int num_blocks = BE16(m_saves[i].m_gci_header.BlockCount);
while (num_blocks)
{
m_saves[i].m_save_data.push_back(GCMBlock());
num_blocks--;
}
}
if (writing)
{
m_saves[i].m_dirty = true;
}
m_LastBlock = block;
m_LastBlockAddress = m_saves[i].m_save_data[idx].block;
return m_LastBlock;
}
}
}
return -1;
}
s32 GCMemcardDirectory::DirectoryWrite(u32 destaddress, u32 length, u8 *srcaddress)
{
u32 block = destaddress / BLOCK_SIZE;
u32 offset = destaddress % BLOCK_SIZE;
Directory *dest = (block == 1) ? &m_dir1 : &m_dir2;
u16 Dnum = offset / DENTRY_SIZE;
if (Dnum == DIRLEN)
{
// first 58 bytes should always be 0xff
// needed to update the update ctr, checksums
// could check for writes to the 6 important bytes but doubtful that it improves performance noticably
memcpy((u8 *)(dest)+offset, srcaddress, length);
SyncSaves();
}
else
memcpy((u8 *)(dest)+offset, srcaddress, length);
return length;
}
bool GCMemcardDirectory::SetUsedBlocks(int saveIndex)
{
BlockAlloc *currentBat;
if (BE16(m_bat2.UpdateCounter) > BE16(m_bat1.UpdateCounter))
currentBat = &m_bat2;
else
currentBat = &m_bat1;
u16 block = BE16(m_saves[saveIndex].m_gci_header.FirstBlock);
while (block != 0xFFFF)
{
m_saves[saveIndex].m_used_blocks.push_back(block);
block = currentBat->GetNextBlock(block);
if (block == 0)
{
PanicAlertT("BAT Incorrect, Dolphin will now exit");
exit(0);
}
}
u16 num_blocks = BE16(m_saves[saveIndex].m_gci_header.BlockCount);
if (m_saves[saveIndex].m_used_blocks.size() != num_blocks)
{
PanicAlertT("Warning BAT number of blocks does not match file header");
return false;
}
return true;
}
void GCMemcardDirectory::Flush(bool exiting)
{
int errors = 0;
DEntry invalid;
for (u16 i = 0; i < m_saves.size(); ++i)
{
if (m_saves[i].m_dirty)
{
if (BE32(m_saves[i].m_gci_header.Gamecode) != 0xFFFFFFFF)
{
m_saves[i].m_dirty = false;
if (m_saves[i].m_filename.empty())
{
std::string defaultSaveName = m_SaveDirectory + m_saves[i].m_gci_header.GCI_FileName();
// Check to see if another file is using the same name
// This seems unlikely except in the case of file corruption
// otherwise what user would name another file this way?
for (int j = 0; File::Exists(defaultSaveName) && j < 10; ++j)
{
defaultSaveName.insert(defaultSaveName.end() - 4, '0');
}
if (File::Exists(defaultSaveName))
PanicAlertT("Failed to find new filename\n %s\n will be overwritten", defaultSaveName.c_str());
m_saves[i].m_filename = defaultSaveName;
}
File::IOFile GCI(m_saves[i].m_filename, "wb");
if (GCI)
{
GCI.WriteBytes(&m_saves[i].m_gci_header, DENTRY_SIZE);
GCI.WriteBytes(m_saves[i].m_save_data.data(), BLOCK_SIZE * m_saves[i].m_save_data.size());
if (!exiting)
{
if (GCI.IsGood())
{
Core::DisplayMessage(
StringFromFormat("Wrote save contents to %s", m_saves[i].m_filename.c_str()), 4000);
}
else
{
++errors;
Core::DisplayMessage(
StringFromFormat("Failed to write save contents to %s", m_saves[i].m_filename.c_str()),
4000);
ERROR_LOG(EXPANSIONINTERFACE, "Failed to save data to %s", m_saves[i].m_filename.c_str());
}
}
}
}
else if (m_saves[i].m_filename.length() != 0)
{
m_saves[i].m_dirty = false;
std::string &oldname = m_saves[i].m_filename;
std::string deletedname = oldname + ".deleted";
if (File::Exists(deletedname))
File::Delete(deletedname);
File::Rename(oldname, deletedname);
m_saves[i].m_filename.clear();
m_saves[i].m_save_data.clear();
m_saves[i].m_used_blocks.clear();
}
}
// Unload the save data for any game that is not running
// we could use !m_dirty, but some games have multiple gci files and may not write to them simultaneously
// this ensures that the save data for all of the current games gci files are stored in the savestate
u32 gamecode = BE32(m_saves[i].m_gci_header.Gamecode);
if (gamecode != m_GameId && gamecode != 0xFFFFFFFF && m_saves[i].m_save_data.size())
{
INFO_LOG(EXPANSIONINTERFACE, "Flushing savedata to disk for %s", m_saves[i].m_filename.c_str());
m_saves[i].m_save_data.clear();
}
}
#if _WRITE_MC_HEADER
u8 mc[BLOCK_SIZE * MC_FST_BLOCKS];
Read(0, BLOCK_SIZE * MC_FST_BLOCKS, mc);
File::IOFile hdrfile(m_SaveDirectory + MC_HDR, "wb");
hdrfile.WriteBytes(mc, BLOCK_SIZE * MC_FST_BLOCKS);
#endif
}
void GCMemcardDirectory::DoState(PointerWrap &p)
{
m_LastBlock = -1;
m_LastBlockAddress = 0;
p.Do(m_SaveDirectory);
p.DoPOD<Header>(m_hdr);
p.DoPOD<Directory>(m_dir1);
p.DoPOD<Directory>(m_dir2);
p.DoPOD<BlockAlloc>(m_bat1);
p.DoPOD<BlockAlloc>(m_bat2);
int numSaves = (int)m_saves.size();
p.Do(numSaves);
m_saves.resize(numSaves);
for (auto itr = m_saves.begin(); itr != m_saves.end(); ++itr)
{
itr->DoState(p);
}
}
bool GCIFile::LoadSaveBlocks()
{
if (m_save_data.size() == 0)
{
if (m_filename.empty())
return false;
File::IOFile savefile(m_filename, "rb");
if (!savefile)
return false;
INFO_LOG(EXPANSIONINTERFACE, "Reading savedata from disk for %s", m_filename.c_str());
savefile.Seek(DENTRY_SIZE, SEEK_SET);
u16 num_blocks = BE16(m_gci_header.BlockCount);
m_save_data.resize(num_blocks);
if (!savefile.ReadBytes(m_save_data.data(), num_blocks * BLOCK_SIZE))
{
PanicAlertT("Failed to read data from gci file %s", m_filename.c_str());
m_save_data.clear();
return false;
}
}
return true;
}
int GCIFile::UsesBlock(u16 blocknum)
{
for (u16 i = 0; i < m_used_blocks.size(); ++i)
{
if (m_used_blocks[i] == blocknum)
return i;
}
return -1;
}
void GCIFile::DoState(PointerWrap &p)
{
p.DoPOD<DEntry>(m_gci_header);
p.Do(m_dirty);
p.Do(m_filename);
int numBlocks = (int)m_save_data.size();
p.Do(numBlocks);
m_save_data.resize(numBlocks);
for (auto itr = m_save_data.begin(); itr != m_save_data.end(); ++itr)
{
p.DoPOD<GCMBlock>(*itr);
}
p.Do(m_used_blocks);
}
void MigrateFromMemcardFile(std::string strDirectoryName, int card_index)
{
File::CreateFullPath(strDirectoryName);
std::string ini_memcard =
(card_index == 0) ? SConfig::GetInstance().m_strMemoryCardA : SConfig::GetInstance().m_strMemoryCardB;
if (File::Exists(ini_memcard))
{
GCMemcard memcard(ini_memcard.c_str());
if (memcard.IsValid())
{
for (u8 i = 0; i < DIRLEN; i++)
{
memcard.ExportGci(i, "", strDirectoryName);
}
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#pragma once
#include "Core/HW/GCMemcard.h"
// Uncomment this to write the system data of the memorycard from directory to disc
//#define _WRITE_MC_HEADER 1
void MigrateFromMemcardFile(std::string strDirectoryName, int card_index);
class GCMemcardDirectory : public MemoryCardBase, NonCopyable
{
public:
GCMemcardDirectory(std::string directory, int slot = 0, u16 sizeMb = MemCard2043Mb, bool ascii = true,
int region = 0, int gameId = 0);
~GCMemcardDirectory() { Flush(true); }
void Flush(bool exiting = false) override;
s32 Read(u32 address, s32 length, u8 *destaddress) override;
s32 Write(u32 destaddress, s32 length, u8 *srcaddress) override;
void ClearBlock(u32 address) override;
void ClearAll() override { ; }
void DoState(PointerWrap &p) override;
private:
int LoadGCI(std::string fileName, int region);
inline s32 SaveAreaRW(u32 block, bool writing = false);
// s32 DirectoryRead(u32 offset, u32 length, u8* destaddress);
s32 DirectoryWrite(u32 destaddress, u32 length, u8 *srcaddress);
inline void SyncSaves();
bool SetUsedBlocks(int saveIndex);
u32 m_GameId;
s32 m_LastBlock;
u8 *m_LastBlockAddress;
Header m_hdr;
Directory m_dir1, m_dir2;
BlockAlloc m_bat1, m_bat2;
std::vector<GCIFile> m_saves;
std::vector<std::string> m_loaded_saves;
std::string m_SaveDirectory;
};

View File

@ -0,0 +1,162 @@
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#include "Core/Core.h"
#include "Core/HW/GCMemcard.h"
#include "Core/HW/GCMemcardRaw.h"
#define SIZE_TO_Mb (1024 * 8 * 16)
#define MC_HDR_SIZE 0xA000
void innerFlush(FlushData *data)
{
File::IOFile pFile(data->filename, "r+b");
if (!pFile)
{
std::string dir;
SplitPath(data->filename, &dir, nullptr, nullptr);
if (!File::IsDirectory(dir))
File::CreateFullPath(dir);
pFile.Open(data->filename, "wb");
}
if (!pFile) // Note - pFile changed inside above if
{
PanicAlertT("Could not write memory card file %s.\n\n"
"Are you running Dolphin from a CD/DVD, or is the save file maybe write protected?\n\n"
"Are you receiving this after moving the emulator directory?\nIf so, then you may "
"need to re-specify your memory card location in the options.",
data->filename.c_str());
return;
}
pFile.WriteBytes(data->memcardContent, data->memcardSize);
if (!data->bExiting)
Core::DisplayMessage(StringFromFormat("Wrote memory card %c contents to %s", data->memcardIndex ? 'B' : 'A',
data->filename.c_str()).c_str(),
4000);
return;
}
MemoryCard::MemoryCard(std::string filename, int _card_index, u16 sizeMb)
: MemoryCardBase(_card_index, sizeMb)
, m_bDirty(false)
, m_strFilename(filename)
{
File::IOFile pFile(m_strFilename, "rb");
if (pFile)
{
// Measure size of the memcard file.
memory_card_size = (int)pFile.GetSize();
nintendo_card_id = memory_card_size / SIZE_TO_Mb;
memory_card_content = new u8[memory_card_size];
memset(memory_card_content, 0xFF, memory_card_size);
INFO_LOG(EXPANSIONINTERFACE, "Reading memory card %s", m_strFilename.c_str());
pFile.ReadBytes(memory_card_content, memory_card_size);
}
else
{
// Create a new 128Mb memcard
nintendo_card_id = sizeMb;
memory_card_size = sizeMb * SIZE_TO_Mb;
memory_card_content = new u8[memory_card_size];
GCMemcard::Format(memory_card_content, m_strFilename.find(".JAP.raw") != std::string::npos, sizeMb);
memset(memory_card_content + MC_HDR_SIZE, 0xFF, memory_card_size - MC_HDR_SIZE);
WARN_LOG(EXPANSIONINTERFACE, "No memory card found. Will create a new one.");
}
}
void MemoryCard::joinThread()
{
if (flushThread.joinable())
{
flushThread.join();
}
}
// Flush memory card contents to disc
void MemoryCard::Flush(bool exiting)
{
if (!m_bDirty)
return;
if (!Core::g_CoreStartupParameter.bEnableMemcardSaving)
return;
if (flushThread.joinable())
{
flushThread.join();
}
if (!exiting)
Core::DisplayMessage(StringFromFormat("Writing to memory card %c", card_index ? 'B' : 'A'), 1000);
flushData.filename = m_strFilename;
flushData.memcardContent = memory_card_content;
flushData.memcardIndex = card_index;
flushData.memcardSize = memory_card_size;
flushData.bExiting = exiting;
flushThread = std::thread(innerFlush, &flushData);
if (exiting)
flushThread.join();
m_bDirty = false;
}
s32 MemoryCard::Read(u32 srcaddress, s32 length, u8 *destaddress)
{
if (!memory_card_content)
return -1;
if (srcaddress > (memory_card_size - 1))
{
PanicAlertT("MemoryCard: Read called with invalid source address, %x", srcaddress);
return -1;
}
memcpy(destaddress, &(memory_card_content[srcaddress]), length);
return length;
}
s32 MemoryCard::Write(u32 destaddress, s32 length, u8 *srcaddress)
{
if (!memory_card_content)
return -1;
if (destaddress > (memory_card_size - 1))
{
PanicAlertT("MemoryCard: Write called with invalid destination address, %x", destaddress);
return -1;
}
m_bDirty = true;
memcpy(&(memory_card_content[destaddress]), srcaddress, length);
return length;
}
void MemoryCard::ClearBlock(u32 address)
{
if (address & (BLOCK_SIZE - 1) || address > (memory_card_size - 1))
PanicAlertT("MemoryCard: ClearBlock called on invalid address %x", address);
else
{
m_bDirty = true;
memset(memory_card_content + address, 0xFF, BLOCK_SIZE);
}
}
void MemoryCard::ClearAll()
{
m_bDirty = true;
memset(memory_card_content, 0xFF, memory_card_size);
}
void MemoryCard::DoState(PointerWrap &p)
{
p.Do(card_index);
p.Do(memory_card_size);
p.DoArray(memory_card_content, memory_card_size);
}

View File

@ -0,0 +1,41 @@
// Copyright 2013 Dolphin Emulator Project
// Licensed under GPLv2
// Refer to the license.txt file included.
#pragma once
#include "Common/ChunkFile.h"
#include "Common/Thread.h"
#include "Core/HW/GCMemcard.h"
// Data structure to be passed to the flushing thread.
struct FlushData
{
bool bExiting;
std::string filename;
u8 *memcardContent;
int memcardSize, memcardIndex;
};
class MemoryCard : public MemoryCardBase
{
public:
MemoryCard(std::string filename, int _card_index, u16 sizeMb = MemCard2043Mb);
~MemoryCard() { Flush(true); }
void Flush(bool exiting = false) override;
s32 Read(u32 address, s32 length, u8 *destaddress) override;
s32 Write(u32 destaddress, s32 length, u8 *srcaddress) override;
void ClearBlock(u32 address) override;
void ClearAll() override;
void DoState(PointerWrap &p) override;
void joinThread() override;
private:
u8 *memory_card_content;
bool m_bDirty;
std::string m_strFilename;
FlushData flushData;
std::thread flushThread;
};

View File

@ -63,7 +63,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 25;
static const u32 STATE_VERSION = 26;
enum
{

View File

@ -116,6 +116,7 @@ static const wxLanguage langIds[] =
#define SIDEV_AM_BB_STR _trans("AM-Baseboard")
#define EXIDEV_MEMCARD_STR _trans("Memory Card")
#define EXIDEV_MEMDIR_STR _trans("GCI Folder")
#define EXIDEV_MIC_STR _trans("Mic")
#define EXIDEV_BBA_STR "BBA"
#define EXIDEV_AM_BB_STR _trans("AM-Baseboard")
@ -395,6 +396,8 @@ void CConfigMain::InitializeGUIValues()
SlotDevices.Add(_(DEV_DUMMY_STR));
SlotDevices.Add(_(EXIDEV_MEMCARD_STR));
SlotDevices.Add(_(EXIDEV_GECKO_STR));
SlotDevices.Add(_(EXIDEV_MEMDIR_STR));
#if HAVE_PORTAUDIO
SlotDevices.Add(_(EXIDEV_MIC_STR));
#endif
@ -432,9 +435,12 @@ void CConfigMain::InitializeGUIValues()
case EXIDEVICE_MEMORYCARD:
isMemcard = GCEXIDevice[i]->SetStringSelection(SlotDevices[2]);
break;
case EXIDEVICE_MIC:
case EXIDEVICE_MEMORYCARDFOLDER:
GCEXIDevice[i]->SetStringSelection(SlotDevices[4]);
break;
case EXIDEVICE_MIC:
GCEXIDevice[i]->SetStringSelection(SlotDevices[5]);
break;
case EXIDEVICE_ETH:
GCEXIDevice[i]->SetStringSelection(SP1Devices[2]);
break;
@ -1143,6 +1149,8 @@ void CConfigMain::ChooseEXIDevice(wxString deviceName, int deviceNum)
if (!deviceName.compare(_(EXIDEV_MEMCARD_STR)))
tempType = EXIDEVICE_MEMORYCARD;
else if (!deviceName.compare(_(EXIDEV_MEMDIR_STR)))
tempType = EXIDEVICE_MEMORYCARDFOLDER;
else if (!deviceName.compare(_(EXIDEV_MIC_STR)))
tempType = EXIDEVICE_MIC;
else if (!deviceName.compare(EXIDEV_BBA_STR))