Merge pull request #544 from LPFaint99/memcard

GCI Folder fix compatibility with games that relocate the save after first use
This commit is contained in:
Lioncash 2014-07-15 12:09:56 -04:00
commit 01cd90deef
4 changed files with 96 additions and 36 deletions

View File

@ -257,11 +257,6 @@ void CEXIMemoryCard::SetCS(int cs)
CmdDoneLater(5000); 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; break;
} }
} }
@ -478,6 +473,10 @@ IEXIDevice* CEXIMemoryCard::FindDevice(TEXIDevices device_type, int customIndex)
void CEXIMemoryCard::DMARead(u32 _uAddr, u32 _uSize) void CEXIMemoryCard::DMARead(u32 _uAddr, u32 _uSize)
{ {
memorycard->Read(address, _uSize, Memory::GetPointer(_uAddr)); 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 // 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) void CEXIMemoryCard::DMAWrite(u32 _uAddr, u32 _uSize)
{ {
memorycard->Write(address, _uSize, Memory::GetPointer(_uAddr)); 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);
}
} }

View File

@ -4,10 +4,12 @@
#pragma once #pragma once
#include <algorithm>
#include <string> #include <string>
#include "Common/Common.h" #include "Common/Common.h"
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/NandPaths.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Core/HW/EXI_DeviceIPL.h" #include "Core/HW/EXI_DeviceIPL.h"
@ -156,9 +158,29 @@ struct DEntry
DEntry() { memset(this, 0xFF, DENTRY_SIZE); } DEntry() { memset(this, 0xFF, DENTRY_SIZE); }
std::string GCI_FileName() const std::string GCI_FileName() const
{ {
return std::string((char *)Makercode, 2) + '-' + std::string((char *)Gamecode, 4) + '-' + (char *)Filename + std::string filename = std::string((char *)Makercode, 2) + '-' + std::string((char *)Gamecode, 4) + '-' + (char *)Filename +
".gci"; ".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 Gamecode[4]; //0x00 0x04 Gamecode
u8 Makercode[2]; //0x04 0x02 Makercode u8 Makercode[2]; //0x04 0x02 Makercode
u8 Unused1; //0x06 0x01 reserved/unused (always 0xff, has no effect) u8 Unused1; //0x06 0x01 reserved/unused (always 0xff, has no effect)

View File

@ -11,7 +11,8 @@
const int NO_INDEX = -1; const int NO_INDEX = -1;
static const char *MC_HDR = "MC_SYSTEM_AREA"; 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"); File::IOFile gcifile(fileName, "rb");
if (gcifile) if (gcifile)
@ -25,37 +26,32 @@ int GCMemcardDirectory::LoadGCI(std::string fileName, int region)
return NO_INDEX; return NO_INDEX;
} }
DiscIO::IVolume::ECountry gci_region;
// check region // check region
switch (gci.m_gci_header.Gamecode[3]) switch (gci.m_gci_header.Gamecode[3])
{ {
case 'J': case 'J':
if (region != DiscIO::IVolume::COUNTRY_JAPAN) gci_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; break;
case 'E': case 'E':
if (region != DiscIO::IVolume::COUNTRY_USA) gci_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; break;
case 'C': case 'C':
// Used by Datel Action Replay Save // Used by Datel Action Replay Save
// can be on any regions card
gci_region = card_region;
break; break;
default: default:
if (region != DiscIO::IVolume::COUNTRY_EUROPE) 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", PanicAlertT("GCI save file was not loaded because it is the wrong region for this memory card:\n%s",
fileName.c_str()); fileName.c_str());
return NO_INDEX; return NO_INDEX;
} }
break;
}
std::string gci_filename = gci.m_gci_header.GCI_FileName(); std::string gci_filename = gci.m_gci_header.GCI_FileName();
for (u16 i = 0; i < m_loaded_saves.size(); ++i) 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; 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) : MemoryCardBase(slot, sizeMb)
, m_GameId(gameId) , m_GameId(gameId)
, m_LastBlock(-1) , m_LastBlock(-1)
@ -148,7 +144,7 @@ GCMemcardDirectory::GCMemcardDirectory(std::string directory, int slot, u16 size
m_SaveDirectory.c_str()); m_SaveDirectory.c_str());
break; break;
} }
int index = LoadGCI(FST_Temp.children[j].physicalName, region); int index = LoadGCI(FST_Temp.children[j].physicalName, card_region);
if (index != NO_INDEX) if (index != NO_INDEX)
{ {
m_loaded_saves.push_back(m_saves.at(index).m_gci_header.GCI_FileName()); 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) s32 GCMemcardDirectory::Write(u32 destaddress, s32 length, u8 *srcaddress)
{ {
if (length != 0x80) 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; s32 block = destaddress / BLOCK_SIZE;
u32 offset = destaddress % BLOCK_SIZE; u32 offset = destaddress % BLOCK_SIZE;
s32 extra = 0; // used for write calls that are across multiple blocks 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; u32 block = address / BLOCK_SIZE;
INFO_LOG(EXPANSIONINTERFACE, "clearing block %d", block);
switch (block) switch (block)
{ {
case 0: case 0:
@ -333,8 +330,9 @@ inline void GCMemcardDirectory::SyncSaves()
for (u32 i = 0; i < DIRLEN; ++i) 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; bool added = false;
while (i >= m_saves.size()) 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)) if (added || memcmp((u8 *)&(m_saves[i].m_gci_header), (u8 *)&(current->Dir[i]), DENTRY_SIZE))
{ {
m_saves[i].m_dirty = true; 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); 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)) 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); u16 num_blocks = BE16(m_saves[saveIndex].m_gci_header.BlockCount);
u16 blocksFromBat = (u16)m_saves[saveIndex].m_used_blocks.size();
if (m_saves[saveIndex].m_used_blocks.size() != num_blocks) 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; return false;
} }

View File

@ -14,7 +14,7 @@ class GCMemcardDirectory : public MemoryCardBase, NonCopyable
{ {
public: public:
GCMemcardDirectory(std::string directory, int slot = 0, u16 sizeMb = MemCard2043Mb, bool ascii = true, 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); } ~GCMemcardDirectory() { Flush(true); }
void Flush(bool exiting = false) override; void Flush(bool exiting = false) override;
@ -25,7 +25,7 @@ public:
void DoState(PointerWrap &p) override; void DoState(PointerWrap &p) override;
private: 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); inline s32 SaveAreaRW(u32 block, bool writing = false);
// s32 DirectoryRead(u32 offset, u32 length, u8* destaddress); // s32 DirectoryRead(u32 offset, u32 length, u8* destaddress);
s32 DirectoryWrite(u32 destaddress, u32 length, u8 *srcaddress); s32 DirectoryWrite(u32 destaddress, u32 length, u8 *srcaddress);