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))