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);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue