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:
commit
01cd90deef
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue