diff --git a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp index dd0b2a3e5b..bdd31551ab 100644 --- a/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp +++ b/Source/Core/Core/HW/EXI_DeviceMemoryCard.cpp @@ -257,11 +257,6 @@ void CEXIMemoryCard::SetCS(int cs) CmdDoneLater(5000); } - - // Page written to memory card, not just to buffer - let's schedule a flush 0.5b cycles into the future (1 sec) - // But first we unschedule already scheduled flushes - no point in flushing once per page for a large write. - CoreTiming::RemoveEvent(et_this_card); - CoreTiming::ScheduleEvent(500000000, et_this_card, (u64)card_index); break; } } @@ -478,6 +473,10 @@ IEXIDevice* CEXIMemoryCard::FindDevice(TEXIDevices device_type, int customIndex) void CEXIMemoryCard::DMARead(u32 _uAddr, u32 _uSize) { memorycard->Read(address, _uSize, Memory::GetPointer(_uAddr)); +#ifdef _DEBUG + if ((address + _uSize) % BLOCK_SIZE == 0) + INFO_LOG(EXPANSIONINTERFACE, "reading from block: %x", address / BLOCK_SIZE); +#endif } // DMA write are preceded by all of the necessary setup via IMMWrite @@ -485,4 +484,19 @@ void CEXIMemoryCard::DMARead(u32 _uAddr, u32 _uSize) void CEXIMemoryCard::DMAWrite(u32 _uAddr, u32 _uSize) { memorycard->Write(address, _uSize, Memory::GetPointer(_uAddr)); + + // At the end of writing to a block flush to disk + // memory card blocks are always(?) written as a whole, + // but the dma calls are by page size (0x200) at a time + // just in case this is the last block that the game will be writing for a while + if (((address + _uSize) % BLOCK_SIZE) == 0) + { + INFO_LOG(EXPANSIONINTERFACE, "writing to block: %x", address / BLOCK_SIZE); + // Page written to memory card, not just to buffer - let's schedule a flush 0.5b cycles into the future (1 sec) + // But first we unschedule already scheduled flushes - no point in flushing once per page for a large write + // Scheduling event is mainly for raw memory cards as the flush the whole 16MB to disk + // Flushing the gci folder is free in comparison + CoreTiming::RemoveEvent(et_this_card); + CoreTiming::ScheduleEvent(500000000, et_this_card, (u64)card_index); + } } diff --git a/Source/Core/Core/HW/GCMemcard.h b/Source/Core/Core/HW/GCMemcard.h index 69213983ec..9993e6b35d 100644 --- a/Source/Core/Core/HW/GCMemcard.h +++ b/Source/Core/Core/HW/GCMemcard.h @@ -4,10 +4,12 @@ #pragma once +#include #include #include "Common/Common.h" #include "Common/CommonPaths.h" +#include "Common/NandPaths.h" #include "Common/StringUtil.h" #include "Core/HW/EXI_DeviceIPL.h" @@ -156,9 +158,29 @@ struct DEntry 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"; + std::string filename = std::string((char *)Makercode, 2) + '-' + std::string((char *)Gamecode, 4) + '-' + (char *)Filename + + ".gci"; + static Common::replace_v replacements; + if (replacements.size() == 0) + { + Common::ReadReplacements(replacements); + // Cannot add \r to replacements file due to it being a line ending char + // / might be ok, but we need to verify that this is only used on filenames + // as it is a dir_sep + replacements.push_back(std::make_pair('\r', std::string("__0d__"))); + replacements.push_back(std::make_pair('/', std::string("__2f__"))); + + } + + // Replaces chars that FAT32 can't support with strings defined in /sys/replace + for (auto& replacement : replacements) + { + for (size_t j = 0; (j = filename.find(replacement.first, j)) != filename.npos; ++j) + filename.replace(j, 1, replacement.second); + } + return filename; } + u8 Gamecode[4]; //0x00 0x04 Gamecode u8 Makercode[2]; //0x04 0x02 Makercode u8 Unused1; //0x06 0x01 reserved/unused (always 0xff, has no effect) diff --git a/Source/Core/Core/HW/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcardDirectory.cpp index 13fec8b185..2f3492bc0b 100644 --- a/Source/Core/Core/HW/GCMemcardDirectory.cpp +++ b/Source/Core/Core/HW/GCMemcardDirectory.cpp @@ -11,7 +11,8 @@ const int NO_INDEX = -1; static const char *MC_HDR = "MC_SYSTEM_AREA"; -int GCMemcardDirectory::LoadGCI(std::string fileName, int region) + +int GCMemcardDirectory::LoadGCI(std::string fileName, DiscIO::IVolume::ECountry card_region) { File::IOFile gcifile(fileName, "rb"); if (gcifile) @@ -25,38 +26,33 @@ int GCMemcardDirectory::LoadGCI(std::string fileName, int region) return NO_INDEX; } + DiscIO::IVolume::ECountry gci_region; // 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; - } + gci_region = DiscIO::IVolume::COUNTRY_JAPAN; 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; - } + gci_region = DiscIO::IVolume::COUNTRY_USA; break; case 'C': // Used by Datel Action Replay Save + // can be on any regions card + gci_region = card_region; 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; - } + gci_region = DiscIO::IVolume::COUNTRY_EUROPE; break; } + if (gci_region != card_region) + { + 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; + } + std::string gci_filename = gci.m_gci_header.GCI_FileName(); for (u16 i = 0; i < m_loaded_saves.size(); ++i) { @@ -117,7 +113,7 @@ int GCMemcardDirectory::LoadGCI(std::string fileName, int region) return NO_INDEX; } -GCMemcardDirectory::GCMemcardDirectory(std::string directory, int slot, u16 sizeMb, bool ascii, int region, int gameId) +GCMemcardDirectory::GCMemcardDirectory(std::string directory, int slot, u16 sizeMb, bool ascii, DiscIO::IVolume::ECountry card_region, int gameId) : MemoryCardBase(slot, sizeMb) , m_GameId(gameId) , m_LastBlock(-1) @@ -148,7 +144,7 @@ GCMemcardDirectory::GCMemcardDirectory(std::string directory, int slot, u16 size m_SaveDirectory.c_str()); break; } - int index = LoadGCI(FST_Temp.children[j].physicalName, region); + int index = LoadGCI(FST_Temp.children[j].physicalName, card_region); if (index != NO_INDEX) { m_loaded_saves.push_back(m_saves.at(index).m_gci_header.GCI_FileName()); @@ -222,7 +218,7 @@ s32 GCMemcardDirectory::Read(u32 address, s32 length, u8 *destaddress) s32 GCMemcardDirectory::Write(u32 destaddress, s32 length, u8 *srcaddress) { if (length != 0x80) - ERROR_LOG(EXPANSIONINTERFACE, "WRITING TO %x, len %x", destaddress, length); + INFO_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 @@ -292,6 +288,7 @@ void GCMemcardDirectory::ClearBlock(u32 address) } u32 block = address / BLOCK_SIZE; + INFO_LOG(EXPANSIONINTERFACE, "clearing block %d", block); switch (block) { case 0: @@ -333,8 +330,9 @@ inline void GCMemcardDirectory::SyncSaves() for (u32 i = 0; i < DIRLEN; ++i) { - if (*(u32 *)&(current->Dir[i]) != 0xFFFFFFFF) + if (BE32(current->Dir[i].Gamecode) != 0xFFFFFFFF) { + INFO_LOG(EXPANSIONINTERFACE, "Syncing Save %x", *(u32 *)&(current->Dir[i].Gamecode)); bool added = false; while (i >= m_saves.size()) { @@ -346,12 +344,38 @@ inline void GCMemcardDirectory::SyncSaves() if (added || memcmp((u8 *)&(m_saves[i].m_gci_header), (u8 *)&(current->Dir[i]), DENTRY_SIZE)) { m_saves[i].m_dirty = true; + u32 gamecode = BE32(m_saves[i].m_gci_header.Gamecode); + u32 newGameCode = BE32(current->Dir[i].Gamecode); + u32 old_start = BE16(m_saves[i].m_gci_header.FirstBlock); + u32 new_start = BE16(current->Dir[i].FirstBlock); + + if ((gamecode != 0xFFFFFFFF) + && (gamecode != newGameCode)) + { + PanicAlertT("Game overwrote with another games save, data corruption ahead %x, %x ", + BE32(m_saves[i].m_gci_header.Gamecode), BE32(current->Dir[i].Gamecode)); + } memcpy((u8 *)&(m_saves[i].m_gci_header), (u8 *)&(current->Dir[i]), DENTRY_SIZE); + if (old_start != new_start) + { + INFO_LOG(EXPANSIONINTERFACE, "Save moved from %x to %x", old_start, new_start); + m_saves[i].m_used_blocks.clear(); + m_saves[i].m_save_data.clear(); + } + if (m_saves[i].m_used_blocks.size() == 0) + { + SetUsedBlocks(i); + } } } else if ((i < m_saves.size()) && (*(u32 *)&(m_saves[i].m_gci_header) != 0xFFFFFFFF)) { - *(u32 *)&(m_saves[i].m_gci_header.Gamecode) = 0xFFFFFFF; + INFO_LOG(EXPANSIONINTERFACE, "Clearing and/or Deleting Save %x", BE32(m_saves[i].m_gci_header.Gamecode)); + *(u32 *)&(m_saves[i].m_gci_header.Gamecode) = 0xFFFFFFFF; + m_saves[i].m_save_data.clear(); + m_saves[i].m_used_blocks.clear(); + m_saves[i].m_dirty = true; + } } } @@ -434,10 +458,10 @@ bool GCMemcardDirectory::SetUsedBlocks(int saveIndex) } u16 num_blocks = BE16(m_saves[saveIndex].m_gci_header.BlockCount); - - if (m_saves[saveIndex].m_used_blocks.size() != num_blocks) + u16 blocksFromBat = (u16)m_saves[saveIndex].m_used_blocks.size(); + if (blocksFromBat != num_blocks) { - PanicAlertT("Warning BAT number of blocks does not match file header"); + PanicAlertT("Warning BAT number of blocks %d does not match file header loaded %d", blocksFromBat, num_blocks); return false; } diff --git a/Source/Core/Core/HW/GCMemcardDirectory.h b/Source/Core/Core/HW/GCMemcardDirectory.h index add9cf6e4b..463a60f439 100644 --- a/Source/Core/Core/HW/GCMemcardDirectory.h +++ b/Source/Core/Core/HW/GCMemcardDirectory.h @@ -14,7 +14,7 @@ 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); + DiscIO::IVolume::ECountry card_region = DiscIO::IVolume::COUNTRY_EUROPE, int gameId = 0); ~GCMemcardDirectory() { Flush(true); } void Flush(bool exiting = false) override; @@ -25,7 +25,7 @@ public: void DoState(PointerWrap &p) override; private: - int LoadGCI(std::string fileName, int region); + int LoadGCI(std::string fileName, DiscIO::IVolume::ECountry card_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);