diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt
index 2a4915db45..4ef6cdef18 100644
--- a/Source/Core/Core/CMakeLists.txt
+++ b/Source/Core/Core/CMakeLists.txt
@@ -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
diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj
index 0ea27a1b7e..79bfac5a3c 100644
--- a/Source/Core/Core/Core.vcxproj
+++ b/Source/Core/Core/Core.vcxproj
@@ -137,6 +137,8 @@
+
+
@@ -333,6 +335,8 @@
+
+
diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters
index 469b7783be..45d94d47af 100644
--- a/Source/Core/Core/Core.vcxproj.filters
+++ b/Source/Core/Core/Core.vcxproj.filters
@@ -410,6 +410,12 @@
HW %28Flipper/Hollywood%29\GCMemcard
+
+ HW %28Flipper/Hollywood%29\GCMemcard
+
+
+ HW %28Flipper/Hollywood%29\GCMemcard
+
HW %28Flipper/Hollywood%29\GCPad
@@ -933,6 +939,12 @@
HW %28Flipper/Hollywood%29\GCMemcard
+
+ HW %28Flipper/Hollywood%29\GCMemcard
+
+
+ HW %28Flipper/Hollywood%29\GCMemcard
+
HW %28Flipper/Hollywood%29\GCPad
diff --git a/Source/Core/Core/HW/EXI_Device.cpp b/Source/Core/Core/HW/EXI_Device.cpp
index 69c21cb713..53a79ed295 100644
--- a/Source/Core/Core/HW/EXI_Device.cpp
+++ b/Source/Core/Core/HW/EXI_Device.cpp
@@ -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;
diff --git a/Source/Core/Core/HW/EXI_Device.h b/Source/Core/Core/HW/EXI_Device.h
index 7cc5b16aa6..01a77b9af8 100644
--- a/Source/Core/Core/HW/EXI_Device.h
+++ b/Source/Core/Core/HW/EXI_Device.h
@@ -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
};
diff --git a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp
index 0f587dd50d..fde8868cb3 100644
--- a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp
+++ b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp
@@ -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.");
+ setupRawMemcard(sizeMb);
}
- SetCardFlashID(memory_card_content, card_index);
+
+ memory_card_size = memorycard->GetCardId() * SIZE_TO_Mb;
+ u8 header[20] = {0};
+ memorycard->Read(0, ArraySize(header), header);
+ SetCardFlashID(header, card_index);
}
-void innerFlush(FlushData* data)
+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;
- }
-
- 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 = std::make_unique(strDirectoryName + DIR_SEP, card_index, sizeMb, ascii,
+ CountryCode, CurrentGameId);
}
-// 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(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));
+}
diff --git a/Source/Core/Core/HW/EXI_DeviceMemoryCard.h b/Source/Core/Core/HW/EXI_DeviceMemoryCard.h
index 60aed6556c..6f59bb4128 100644
--- a/Source/Core/Core/HW/EXI_DeviceMemoryCard.h
+++ b/Source/Core/Core/HW/EXI_DeviceMemoryCard.h
@@ -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 memorycard;
protected:
virtual void TransferByte(u8 &byte) override;
diff --git a/Source/Core/Core/HW/GCMemcard.cpp b/Source/Core/Core/HW/GCMemcard.cpp
index 738e3c93c3..83cb98c606 100644
--- a/Source/Core/Core/HW/GCMemcard.cpp
+++ b/Source/Core/Core/HW/GCMemcard.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2
// Refer to the license.txt file included.
+#include
#include
#include
@@ -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)
{
- return;
+ 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 blocks;
while (FirstBlock != 0xFFFF && FirstBlock != 0)
@@ -670,8 +673,8 @@ u32 GCMemcard::ImportFile(DEntry& direntry, std::vector &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 &FileBuffer)
+s32 GCMemcard::FZEROGX_MakeSaveGameValid(Header &cardheader, DEntry &direntry, std::vector &FileBuffer)
{
u32 i,j;
u32 serial1,serial2;
@@ -1329,7 +1265,7 @@ s32 GCMemcard::FZEROGX_MakeSaveGameValid(DEntry& direntry, std::vector
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
/* Returns: Error code */
/***********************************************************/
-s32 GCMemcard::PSO_MakeSaveGameValid(DEntry& direntry, std::vector &FileBuffer)
+s32 GCMemcard::PSO_MakeSaveGameValid(Header &cardheader, DEntry &direntry, std::vector &FileBuffer)
{
u32 i,j;
u32 chksum;
@@ -1391,7 +1327,7 @@ s32 GCMemcard::PSO_MakeSaveGameValid(DEntry& direntry, std::vector &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;
diff --git a/Source/Core/Core/HW/GCMemcard.h b/Source/Core/Core/HW/GCMemcard.h
index 081f1ca5e3..69213983ec 100644
--- a/Source/Core/Core/HW/GCMemcard.h
+++ b/Source/Core/Core/HW/GCMemcard.h
@@ -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,132 +59,263 @@ enum
CI8,
};
+class MemoryCardBase
+{
+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;
+};
+
+struct GCMBlock
+{
+ GCMBlock() { erase(); }
+ void erase() { memset(block, 0xFF, BLOCK_SIZE); }
+ u8 block[BLOCK_SIZE];
+};
+
+void calc_checksumsBE(u16 *buf, u32 length, u16 *csum, u16 *inv_csum);
+
+#pragma pack(push,1)
+struct Header { //Offset Size Description
+ // Serial in libogc
+ u8 serial[12]; //0x0000 12 ?
+ u64 formatTime; //0x000c 8 Time of format (OSTime value)
+ u32 SramBias; //0x0014 4 SRAM bias at time of format
+ u32 SramLang; //0x0018 4 SRAM language
+ u8 Unk2[4]; //0x001c 4 ? almost always 0
+ // end Serial in libogc
+ u8 deviceID[2]; //0x0020 2 0 if formated in slot A 1 if formated in slot B
+ u8 SizeMb[2]; //0x0022 2 Size of memcard in Mbits
+ u16 Encoding; //0x0024 2 Encoding (ASCII or japanese)
+ u8 Unused1[468]; //0x0026 468 Unused (0xff)
+ u16 UpdateCounter; //0x01fa 2 Update Counter (?, probably unused)
+ u16 Checksum; //0x01fc 2 Additive Checksum
+ u16 Checksum_Inv; //0x01fe 2 Inverse Checksum
+ u8 Unused2[7680]; //0x0200 0x1e00 Unused (0xff)
+
+
+ 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)
+ u8 BIFlags; //0x07 0x01 banner gfx format and icon animation (Image Key)
+ // Bit(s) Description
+ // 2 Icon Animation 0: forward 1: ping-pong
+ // 1 [--0: No Banner 1: Banner present--] WRONG! YAGCD LIES!
+ // 0 [--Banner Color 0: RGB5A3 1: CI8--] WRONG! YAGCD LIES!
+ // bits 0 and 1: image format
+ // 00 no banner
+ // 01 CI8 banner
+ // 10 RGB5A3 banner
+ // 11 ? maybe ==00? Time Splitters 2 and 3 have it and don't have banner
+ //
+ u8 Filename[DENTRY_STRLEN]; //0x08 0x20 Filename
+ u8 ModTime[4]; //0x28 0x04 Time of file's last modification in seconds since 12am, January 1st, 2000
+ u8 ImageOffset[4]; //0x2c 0x04 image data offset
+ u8 IconFmt[2]; //0x30 0x02 icon gfx format (2bits per icon)
+ // Bits Description
+ // 00 No icon
+ // 01 CI8 with a shared color palette after the last frame
+ // 10 RGB5A3
+ // 11 CI8 with a unique color palette after itself
+ //
+ u8 AnimSpeed[2]; //0x32 0x02 Animation speed (2bits per icon) (*1)
+ // Bits Description
+ // 00 No icon
+ // 01 Icon lasts for 4 frames
+ // 10 Icon lasts for 8 frames
+ // 11 Icon lasts for 12 frames
+ //
+ u8 Permissions; //0x34 0x01 File-permissions
+ // Bit Permission Description
+ // 4 no move File cannot be moved by the IPL
+ // 3 no copy File cannot be copied by the IPL
+ // 2 public Can be read by any game
+ //
+ u8 CopyCounter; //0x35 0x01 Copy counter (*2)
+ u8 FirstBlock[2]; //0x36 0x02 Block no of first block of file (0 == offset 0)
+ u8 BlockCount[2]; //0x38 0x02 File-length (number of blocks in file)
+ u8 Unused2[2]; //0x3a 0x02 Reserved/unused (always 0xffff, has no effect)
+ u8 CommentsAddr[4]; //0x3c 0x04 Address of the two comments within the file data (*3)
+};
+
+struct Directory
+{
+ DEntry Dir[DIRLEN]; //0x0000 Directory Entries (max 127)
+ u8 Padding[0x3a];
+ u16 UpdateCounter; //0x1ffa 2 Update Counter
+ u16 Checksum; //0x1ffc 2 Additive Checksum
+ u16 Checksum_Inv; //0x1ffe 2 Inverse Checksum
+ 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); }
+};
+
+struct BlockAlloc
+{
+ u16 Checksum; //0x0000 2 Additive Checksum
+ u16 Checksum_Inv; //0x0002 2 Inverse Checksum
+ u16 UpdateCounter; //0x0004 2 Update Counter
+ u16 FreeBlocks; //0x0006 2 Free Blocks
+ u16 LastAllocated; //0x0008 2 Last allocated Block
+ u16 Map[BAT_SIZE]; //0x000a 0x1ff8 Map of allocated Blocks
+ u16 GetNextBlock(u16 Block) const;
+ u16 NextFreeBlock(u16 MaxBlock, u16 StartingBlock = MC_FST_BLOCKS) const;
+ bool ClearBlocks(u16 StartingBlock, u16 Length);
+ void fixChecksums() { calc_checksumsBE((u16 *)&UpdateCounter, 0xFFE, &Checksum, &Checksum_Inv); }
+ BlockAlloc(u16 sizeMb = MemCard2043Mb)
+ {
+ 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 m_save_data;
+ std::vector m_used_blocks;
+ int UsesBlock(u16 blocknum);
+ bool m_dirty;
+ std::string m_filename;
+};
+
class GCMemcard : NonCopyable
{
private:
- friend class CMemcardManagerDebug;
bool m_valid;
std::string m_fileName;
u32 maxBlock;
u16 m_sizeMb;
- struct GCMBlock
- {
- GCMBlock(){erase();}
- void erase() {memset(block, 0xFF, BLOCK_SIZE);}
- u8 block[BLOCK_SIZE];
- };
+
+ Header hdr;
+ Directory dir, dir_backup, *CurrentDir, *PreviousDir;
+ BlockAlloc bat, bat_backup, *CurrentBat, *PreviousBat;
+
std::vector mc_data_blocks;
-#pragma pack(push,1)
- struct Header { //Offset Size Description
- // Serial in libogc
- u8 serial[12]; //0x0000 12 ?
- u64 formatTime; //0x000c 8 Time of format (OSTime value)
- u32 SramBias; //0x0014 4 SRAM bias at time of format
- u32 SramLang; //0x0018 4 SRAM language
- u8 Unk2[4]; //0x001c 4 ? almost always 0
- // end Serial in libogc
- u8 deviceID[2]; //0x0020 2 0 if formated in slot A 1 if formated in slot B
- u8 SizeMb[2]; //0x0022 2 Size of memcard in Mbits
- u16 Encoding; //0x0024 2 Encoding (ASCII or japanese)
- u8 Unused1[468]; //0x0026 468 Unused (0xff)
- u16 UpdateCounter; //0x01fa 2 Update Counter (?, probably unused)
- u16 Checksum; //0x01fc 2 Additive Checksum
- u16 Checksum_Inv; //0x01fe 2 Inverse Checksum
- u8 Unused2[7680]; //0x0200 0x1e00 Unused (0xff)
- } hdr;
-
- struct DEntry
- {
- u8 Gamecode[4]; //0x00 0x04 Gamecode
- u8 Makercode[2]; //0x04 0x02 Makercode
- u8 Unused1; //0x06 0x01 reserved/unused (always 0xff, has no effect)
- u8 BIFlags; //0x07 0x01 banner gfx format and icon animation (Image Key)
- // Bit(s) Description
- // 2 Icon Animation 0: forward 1: ping-pong
- // 1 [--0: No Banner 1: Banner present--] WRONG! YAGCD LIES!
- // 0 [--Banner Color 0: RGB5A3 1: CI8--] WRONG! YAGCD LIES!
- // bits 0 and 1: image format
- // 00 no banner
- // 01 CI8 banner
- // 10 RGB5A3 banner
- // 11 ? maybe ==00? Time Splitters 2 and 3 have it and don't have banner
- //
- u8 Filename[DENTRY_STRLEN]; //0x08 0x20 Filename
- u8 ModTime[4]; //0x28 0x04 Time of file's last modification in seconds since 12am, January 1st, 2000
- u8 ImageOffset[4]; //0x2c 0x04 image data offset
- u8 IconFmt[2]; //0x30 0x02 icon gfx format (2bits per icon)
- // Bits Description
- // 00 No icon
- // 01 CI8 with a shared color palette after the last frame
- // 10 RGB5A3
- // 11 CI8 with a unique color palette after itself
- //
- u8 AnimSpeed[2]; //0x32 0x02 Animation speed (2bits per icon) (*1)
- // Bits Description
- // 00 No icon
- // 01 Icon lasts for 4 frames
- // 10 Icon lasts for 8 frames
- // 11 Icon lasts for 12 frames
- //
- u8 Permissions; //0x34 0x01 File-permissions
- // Bit Permission Description
- // 4 no move File cannot be moved by the IPL
- // 3 no copy File cannot be copied by the IPL
- // 2 public Can be read by any game
- //
- u8 CopyCounter; //0x35 0x01 Copy counter (*2)
- u8 FirstBlock[2]; //0x36 0x02 Block no of first block of file (0 == offset 0)
- u8 BlockCount[2]; //0x38 0x02 File-length (number of blocks in file)
- u8 Unused2[2]; //0x3a 0x02 Reserved/unused (always 0xffff, has no effect)
- u8 CommentsAddr[4]; //0x3c 0x04 Address of the two comments within the file data (*3)
- };
-
- struct Directory
- {
- DEntry Dir[DIRLEN]; //0x0000 Directory Entries (max 127)
- u8 Padding[0x3a];
- u16 UpdateCounter; //0x1ffa 2 Update Counter
- u16 Checksum; //0x1ffc 2 Additive Checksum
- u16 Checksum_Inv; //0x1ffe 2 Inverse Checksum
- } dir, dir_backup;
-
- Directory *CurrentDir, *PreviousDir;
- struct BlockAlloc
- {
- u16 Checksum; //0x0000 2 Additive Checksum
- u16 Checksum_Inv; //0x0002 2 Inverse Checksum
- u16 UpdateCounter; //0x0004 2 Update Counter
- u16 FreeBlocks; //0x0006 2 Free Blocks
- u16 LastAllocated; //0x0008 2 Last allocated Block
- u16 Map[BAT_SIZE]; //0x000a 0x1ff8 Map of allocated Blocks
- 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
- {
- Header *hdr;
- Directory *dir, *dir_backup;
- BlockAlloc *bat, *bat_backup;
- };
-#pragma pack(pop)
u32 ImportGciInternal(FILE* gcih, const std::string& inputFile, const std::string &outputFile);
- static void FormatInternal(GCMC_Header &GCP);
- void initDirBatPointers() ;
+ 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 &FileBuffer);
+ static s32 PSO_MakeSaveGameValid(Header &cardheader, DEntry &direntry, std::vector &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 &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 &FileBuffer);
- s32 PSO_MakeSaveGameValid(DEntry& direntry, std::vector &FileBuffer);
};
diff --git a/Source/Core/Core/HW/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcardDirectory.cpp
new file mode 100644
index 0000000000..d223402354
--- /dev/null
+++ b/Source/Core/Core/HW/GCMemcardDirectory.cpp
@@ -0,0 +1,614 @@
+// Copyright 2013 Dolphin Emulator Project
+// Licensed under GPLv2
+// Refer to the license.txt file included.
+#include
+
+#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(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(m_hdr);
+ p.DoPOD(m_dir1);
+ p.DoPOD(m_dir2);
+ p.DoPOD(m_bat1);
+ p.DoPOD(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(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(*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);
+ }
+ }
+ }
+}
diff --git a/Source/Core/Core/HW/GCMemcardDirectory.h b/Source/Core/Core/HW/GCMemcardDirectory.h
new file mode 100644
index 0000000000..add9cf6e4b
--- /dev/null
+++ b/Source/Core/Core/HW/GCMemcardDirectory.h
@@ -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 m_saves;
+
+ std::vector m_loaded_saves;
+ std::string m_SaveDirectory;
+};
diff --git a/Source/Core/Core/HW/GCMemcardRaw.cpp b/Source/Core/Core/HW/GCMemcardRaw.cpp
new file mode 100644
index 0000000000..9302806853
--- /dev/null
+++ b/Source/Core/Core/HW/GCMemcardRaw.cpp
@@ -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);
+}
diff --git a/Source/Core/Core/HW/GCMemcardRaw.h b/Source/Core/Core/HW/GCMemcardRaw.h
new file mode 100644
index 0000000000..80cee42c74
--- /dev/null
+++ b/Source/Core/Core/HW/GCMemcardRaw.h
@@ -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;
+};
diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp
index 966979f0fd..afd56be29e 100644
--- a/Source/Core/Core/State.cpp
+++ b/Source/Core/Core/State.cpp
@@ -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
{
diff --git a/Source/Core/DolphinWX/ConfigMain.cpp b/Source/Core/DolphinWX/ConfigMain.cpp
index 5b3227fcdc..c74400a73f 100644
--- a/Source/Core/DolphinWX/ConfigMain.cpp
+++ b/Source/Core/DolphinWX/ConfigMain.cpp
@@ -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))