Merge pull request #243 from LPFaint99/memcard
Add the option to use a folder of gamecube saves instead of a memcard image [issue 6599]
This commit is contained in:
commit
01fc1a6c54
|
@ -99,6 +99,8 @@ set(SRCS ActionReplay.cpp
|
||||||
HW/EXI_DeviceMemoryCard.cpp
|
HW/EXI_DeviceMemoryCard.cpp
|
||||||
HW/EXI_DeviceMic.cpp
|
HW/EXI_DeviceMic.cpp
|
||||||
HW/GCMemcard.cpp
|
HW/GCMemcard.cpp
|
||||||
|
HW/GCMemcardDirectory.cpp
|
||||||
|
HW/GCMemcardRaw.cpp
|
||||||
HW/GCPad.cpp
|
HW/GCPad.cpp
|
||||||
HW/GCPadEmu.cpp
|
HW/GCPadEmu.cpp
|
||||||
HW/GPFifo.cpp
|
HW/GPFifo.cpp
|
||||||
|
|
|
@ -137,6 +137,8 @@
|
||||||
<ClCompile Include="HW\EXI_DeviceMemoryCard.cpp" />
|
<ClCompile Include="HW\EXI_DeviceMemoryCard.cpp" />
|
||||||
<ClCompile Include="HW\EXI_DeviceMic.cpp" />
|
<ClCompile Include="HW\EXI_DeviceMic.cpp" />
|
||||||
<ClCompile Include="HW\GCMemcard.cpp" />
|
<ClCompile Include="HW\GCMemcard.cpp" />
|
||||||
|
<ClCompile Include="HW\GCMemcardDirectory.cpp" />
|
||||||
|
<ClCompile Include="HW\GCMemcardRaw.cpp" />
|
||||||
<ClCompile Include="HW\GCPad.cpp" />
|
<ClCompile Include="HW\GCPad.cpp" />
|
||||||
<ClCompile Include="HW\GCPadEmu.cpp" />
|
<ClCompile Include="HW\GCPadEmu.cpp" />
|
||||||
<ClCompile Include="HW\GPFifo.cpp" />
|
<ClCompile Include="HW\GPFifo.cpp" />
|
||||||
|
@ -333,6 +335,8 @@
|
||||||
<ClInclude Include="HW\EXI_DeviceMemoryCard.h" />
|
<ClInclude Include="HW\EXI_DeviceMemoryCard.h" />
|
||||||
<ClInclude Include="HW\EXI_DeviceMic.h" />
|
<ClInclude Include="HW\EXI_DeviceMic.h" />
|
||||||
<ClInclude Include="HW\GCMemcard.h" />
|
<ClInclude Include="HW\GCMemcard.h" />
|
||||||
|
<ClInclude Include="HW\GCMemcardDirectory.h" />
|
||||||
|
<ClInclude Include="HW\GCMemcardRaw.h" />
|
||||||
<ClInclude Include="HW\GCPad.h" />
|
<ClInclude Include="HW\GCPad.h" />
|
||||||
<ClInclude Include="HW\GCPadEmu.h" />
|
<ClInclude Include="HW\GCPadEmu.h" />
|
||||||
<ClInclude Include="HW\GPFifo.h" />
|
<ClInclude Include="HW\GPFifo.h" />
|
||||||
|
|
|
@ -410,6 +410,12 @@
|
||||||
<ClCompile Include="HW\GCMemcard.cpp">
|
<ClCompile Include="HW\GCMemcard.cpp">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="HW\GCMemcardDirectory.cpp">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="HW\GCMemcardRaw.cpp">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="HW\GCPad.cpp">
|
<ClCompile Include="HW\GCPad.cpp">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\GCPad</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\GCPad</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -933,6 +939,12 @@
|
||||||
<ClInclude Include="HW\GCMemcard.h">
|
<ClInclude Include="HW\GCMemcard.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="HW\GCMemcardDirectory.h">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="HW\GCMemcardRaw.h">
|
||||||
|
<Filter>HW %28Flipper/Hollywood%29\GCMemcard</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="HW\GCPadEmu.h">
|
<ClInclude Include="HW\GCPadEmu.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\GCPad</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\GCPad</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
|
|
@ -99,9 +99,13 @@ IEXIDevice* EXIDevice_Create(TEXIDevices device_type, const int channel_num)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EXIDEVICE_MEMORYCARD:
|
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;
|
break;
|
||||||
|
}
|
||||||
case EXIDEVICE_MASKROM:
|
case EXIDEVICE_MASKROM:
|
||||||
result = new CEXIIPL();
|
result = new CEXIIPL();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -17,6 +17,8 @@ enum TEXIDevices
|
||||||
EXIDEVICE_ETH,
|
EXIDEVICE_ETH,
|
||||||
EXIDEVICE_AM_BASEBOARD,
|
EXIDEVICE_AM_BASEBOARD,
|
||||||
EXIDEVICE_GECKO,
|
EXIDEVICE_GECKO,
|
||||||
|
EXIDEVICE_MEMORYCARDFOLDER, // Only used when creating a device by EXIDevice_Create
|
||||||
|
// Converted to EXIDEVICE_MEMORYCARD internally
|
||||||
EXIDEVICE_NONE = (u8)-1
|
EXIDEVICE_NONE = (u8)-1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include "Common/Common.h"
|
#include "Common/Common.h"
|
||||||
#include "Common/FileUtil.h"
|
#include "Common/FileUtil.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/CoreTiming.h"
|
#include "Core/CoreTiming.h"
|
||||||
|
@ -14,6 +13,9 @@
|
||||||
#include "Core/HW/EXI_Device.h"
|
#include "Core/HW/EXI_Device.h"
|
||||||
#include "Core/HW/EXI_DeviceMemoryCard.h"
|
#include "Core/HW/EXI_DeviceMemoryCard.h"
|
||||||
#include "Core/HW/GCMemcard.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"
|
#include "Core/HW/Sram.h"
|
||||||
|
|
||||||
#define MC_STATUS_BUSY 0x80
|
#define MC_STATUS_BUSY 0x80
|
||||||
|
@ -23,15 +25,14 @@
|
||||||
#define MC_STATUS_PROGRAMEERROR 0x08
|
#define MC_STATUS_PROGRAMEERROR 0x08
|
||||||
#define MC_STATUS_READY 0x01
|
#define MC_STATUS_READY 0x01
|
||||||
#define SIZE_TO_Mb (1024 * 8 * 16)
|
#define SIZE_TO_Mb (1024 * 8 * 16)
|
||||||
#define MC_HDR_SIZE 0xA000
|
|
||||||
|
|
||||||
void CEXIMemoryCard::FlushCallback(u64 userdata, int cyclesLate)
|
void CEXIMemoryCard::FlushCallback(u64 userdata, int cyclesLate)
|
||||||
{
|
{
|
||||||
// note that userdata is forbidden to be a pointer, due to the implementation of EventDoState
|
// note that userdata is forbidden to be a pointer, due to the implementation of EventDoState
|
||||||
int card_index = (int)userdata;
|
int card_index = (int)userdata;
|
||||||
CEXIMemoryCard* pThis = (CEXIMemoryCard*)ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARD, card_index);
|
CEXIMemoryCard* pThis = (CEXIMemoryCard*)ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARD, card_index);
|
||||||
if (pThis)
|
if (pThis && pThis->memorycard)
|
||||||
pThis->Flush();
|
pThis->memorycard->Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIMemoryCard::CmdDoneCallback(u64 userdata, int cyclesLate)
|
void CEXIMemoryCard::CmdDoneCallback(u64 userdata, int cyclesLate)
|
||||||
|
@ -42,17 +43,13 @@ void CEXIMemoryCard::CmdDoneCallback(u64 userdata, int cyclesLate)
|
||||||
pThis->CmdDone();
|
pThis->CmdDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
CEXIMemoryCard::CEXIMemoryCard(const int index)
|
CEXIMemoryCard::CEXIMemoryCard(const int index, bool gciFolder)
|
||||||
: card_index(index)
|
: card_index(index)
|
||||||
, m_bDirty(false)
|
, 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
|
// 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_this_card = CoreTiming::RegisterEvent((index == 0) ? "memcardFlushA" : "memcardFlushB", FlushCallback);
|
||||||
et_cmd_done = CoreTiming::RegisterEvent((card_index == 0) ? "memcardDoneA" : "memcardDoneB", CmdDoneCallback);
|
et_cmd_done = CoreTiming::RegisterEvent((index == 0) ? "memcardDoneA" : "memcardDoneB", CmdDoneCallback);
|
||||||
|
|
||||||
interruptSwitch = 0;
|
interruptSwitch = 0;
|
||||||
m_bInterruptSet = 0;
|
m_bInterruptSet = 0;
|
||||||
|
@ -60,7 +57,6 @@ CEXIMemoryCard::CEXIMemoryCard(const int index)
|
||||||
status = MC_STATUS_BUSY | MC_STATUS_UNLOCKED | MC_STATUS_READY;
|
status = MC_STATUS_BUSY | MC_STATUS_UNLOCKED | MC_STATUS_READY;
|
||||||
m_uPosition = 0;
|
m_uPosition = 0;
|
||||||
memset(programming_buffer, 0, sizeof(programming_buffer));
|
memset(programming_buffer, 0, sizeof(programming_buffer));
|
||||||
|
|
||||||
//Nintendo Memory Card EXI IDs
|
//Nintendo Memory Card EXI IDs
|
||||||
//0x00000004 Memory Card 59 4Mbit
|
//0x00000004 Memory Card 59 4Mbit
|
||||||
//0x00000008 Memory Card 123 8Mb
|
//0x00000008 Memory Card 123 8Mb
|
||||||
|
@ -71,6 +67,7 @@ CEXIMemoryCard::CEXIMemoryCard(const int index)
|
||||||
|
|
||||||
//0x00000510 16Mb "bigben" card
|
//0x00000510 16Mb "bigben" card
|
||||||
//card_id = 0xc243;
|
//card_id = 0xc243;
|
||||||
|
card_id = 0xc221; // It's a Nintendo brand memcard
|
||||||
|
|
||||||
// The following games have issues with memory cards bigger than 16Mb
|
// The following games have issues with memory cards bigger than 16Mb
|
||||||
// Darkened Skye GDQE6S GDQP6S
|
// Darkened Skye GDQE6S GDQP6S
|
||||||
|
@ -81,110 +78,96 @@ CEXIMemoryCard::CEXIMemoryCard(const int index)
|
||||||
bool useMC251;
|
bool useMC251;
|
||||||
IniFile gameIni = Core::g_CoreStartupParameter.LoadGameIni();
|
IniFile gameIni = Core::g_CoreStartupParameter.LoadGameIni();
|
||||||
gameIni.GetOrCreateSection("Core")->Get("MemoryCard251", &useMC251, false);
|
gameIni.GetOrCreateSection("Core")->Get("MemoryCard251", &useMC251, false);
|
||||||
nintendo_card_id = MemCard2043Mb;
|
u16 sizeMb = useMC251 ? MemCard251Mb : MemCard2043Mb;
|
||||||
if (useMC251)
|
|
||||||
|
if (gciFolder)
|
||||||
{
|
{
|
||||||
nintendo_card_id = MemCard251Mb;
|
setupGciFolder(sizeMb);
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create a new memcard
|
setupRawMemcard(sizeMb);
|
||||||
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.");
|
|
||||||
}
|
}
|
||||||
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;
|
CountryCode = DiscIO::CountrySwitch(strUniqueID.at(3));
|
||||||
SplitPath(data->filename, &dir, nullptr, nullptr);
|
memcpy((u8 *)&CurrentGameId, strUniqueID.c_str(), 4);
|
||||||
if (!File::IsDirectory(dir))
|
}
|
||||||
File::CreateFullPath(dir);
|
bool ascii = true;
|
||||||
pFile.Open(data->filename, "wb");
|
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
|
memorycard = std::make_unique<GCMemcardDirectory>(strDirectoryName + DIR_SEP, card_index, sizeMb, ascii,
|
||||||
{
|
CountryCode, CurrentGameId);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush memory card contents to disc
|
void CEXIMemoryCard::setupRawMemcard(u16 sizeMb)
|
||||||
void CEXIMemoryCard::Flush(bool exiting)
|
|
||||||
{
|
{
|
||||||
if (!m_bDirty)
|
std::string filename =
|
||||||
return;
|
(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)
|
if (sizeMb == MemCard251Mb)
|
||||||
return;
|
|
||||||
|
|
||||||
if (flushThread.joinable())
|
|
||||||
{
|
{
|
||||||
flushThread.join();
|
filename.insert(filename.find_last_of("."), ".251");
|
||||||
}
|
}
|
||||||
|
memorycard = std::make_unique<MemoryCard>(filename, card_index, sizeMb);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CEXIMemoryCard::~CEXIMemoryCard()
|
CEXIMemoryCard::~CEXIMemoryCard()
|
||||||
{
|
{
|
||||||
CoreTiming::RemoveEvent(et_this_card);
|
CoreTiming::RemoveEvent(et_this_card);
|
||||||
Flush(true);
|
memorycard->Flush(true);
|
||||||
delete[] memory_card_content;
|
memorycard.release();
|
||||||
memory_card_content = nullptr;
|
|
||||||
|
|
||||||
if (flushThread.joinable())
|
|
||||||
{
|
|
||||||
flushThread.join();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIMemoryCard::IsPresent()
|
bool CEXIMemoryCard::IsPresent()
|
||||||
|
@ -210,10 +193,7 @@ void CEXIMemoryCard::CmdDoneLater(u64 cycles)
|
||||||
void CEXIMemoryCard::SetCS(int cs)
|
void CEXIMemoryCard::SetCS(int cs)
|
||||||
{
|
{
|
||||||
// So that memory card won't be invalidated during flushing
|
// So that memory card won't be invalidated during flushing
|
||||||
if (flushThread.joinable())
|
memorycard->joinThread();
|
||||||
{
|
|
||||||
flushThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cs) // not-selected to selected
|
if (cs) // not-selected to selected
|
||||||
{
|
{
|
||||||
|
@ -226,7 +206,7 @@ void CEXIMemoryCard::SetCS(int cs)
|
||||||
case cmdSectorErase:
|
case cmdSectorErase:
|
||||||
if (m_uPosition > 2)
|
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_BUSY;
|
||||||
status &= ~MC_STATUS_READY;
|
status &= ~MC_STATUS_READY;
|
||||||
|
|
||||||
|
@ -239,7 +219,8 @@ void CEXIMemoryCard::SetCS(int cs)
|
||||||
case cmdChipErase:
|
case cmdChipErase:
|
||||||
if (m_uPosition > 2)
|
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;
|
status &= ~MC_STATUS_BUSY;
|
||||||
m_bDirty = true;
|
m_bDirty = true;
|
||||||
}
|
}
|
||||||
|
@ -254,7 +235,7 @@ void CEXIMemoryCard::SetCS(int cs)
|
||||||
|
|
||||||
while (count--)
|
while (count--)
|
||||||
{
|
{
|
||||||
memory_card_content[address] = programming_buffer[i++];
|
memorycard->Write(address, 1, &(programming_buffer[i++]));
|
||||||
i &= 127;
|
i &= 127;
|
||||||
address = (address & ~0x1FF) | ((address+1) & 0x1FF);
|
address = (address & ~0x1FF) | ((address+1) & 0x1FF);
|
||||||
}
|
}
|
||||||
|
@ -336,7 +317,7 @@ void CEXIMemoryCard::TransferByte(u8 &byte)
|
||||||
if (m_uPosition == 1)
|
if (m_uPosition == 1)
|
||||||
byte = 0x80; // dummy cycle
|
byte = 0x80; // dummy cycle
|
||||||
else
|
else
|
||||||
byte = (u8)(nintendo_card_id >> (24-(((m_uPosition-2) & 3) * 8)));
|
byte = (u8)(memorycard->GetCardId() >> (24 - (((m_uPosition - 2) & 3) * 8)));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case cmdReadArray:
|
case cmdReadArray:
|
||||||
|
@ -358,7 +339,7 @@ void CEXIMemoryCard::TransferByte(u8 &byte)
|
||||||
}
|
}
|
||||||
if (m_uPosition > 1) // not specified for 1..8, anyway
|
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,
|
// after 9 bytes, we start incrementing the address,
|
||||||
// but only the sector offset - the pointer wraps around
|
// but only the sector offset - the pointer wraps around
|
||||||
if (m_uPosition >= 9)
|
if (m_uPosition >= 9)
|
||||||
|
@ -441,10 +422,7 @@ void CEXIMemoryCard::PauseAndLock(bool doLock, bool unpauseOnUnlock)
|
||||||
{
|
{
|
||||||
// we don't exactly have anything to pause,
|
// we don't exactly have anything to pause,
|
||||||
// but let's make sure the flush thread isn't running.
|
// but let's make sure the flush thread isn't running.
|
||||||
if (flushThread.joinable())
|
memorycard->joinThread();
|
||||||
{
|
|
||||||
flushThread.join();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,11 +444,7 @@ void CEXIMemoryCard::DoState(PointerWrap &p)
|
||||||
p.Do(programming_buffer);
|
p.Do(programming_buffer);
|
||||||
p.Do(m_bDirty);
|
p.Do(m_bDirty);
|
||||||
p.Do(address);
|
p.Do(address);
|
||||||
|
memorycard->DoState(p);
|
||||||
p.Do(nintendo_card_id);
|
|
||||||
p.Do(card_id);
|
|
||||||
p.Do(memory_card_size);
|
|
||||||
p.DoArray(memory_card_content, memory_card_size);
|
|
||||||
p.Do(card_index);
|
p.Do(card_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -483,3 +457,17 @@ IEXIDevice* CEXIMemoryCard::FindDevice(TEXIDevices device_type, int customIndex)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return this;
|
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));
|
||||||
|
}
|
||||||
|
|
|
@ -3,22 +3,13 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "Common/StdMakeUnique.h"
|
||||||
|
|
||||||
#include "Common/Thread.h"
|
class MemoryCardBase;
|
||||||
|
|
||||||
// Data structure to be passed to the flushing thread.
|
|
||||||
struct FlushData
|
|
||||||
{
|
|
||||||
bool bExiting;
|
|
||||||
std::string filename;
|
|
||||||
u8 *memcardContent;
|
|
||||||
int memcardSize, memcardIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CEXIMemoryCard : public IEXIDevice
|
class CEXIMemoryCard : public IEXIDevice
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CEXIMemoryCard(const int index);
|
CEXIMemoryCard(const int index, bool gciFolder);
|
||||||
virtual ~CEXIMemoryCard();
|
virtual ~CEXIMemoryCard();
|
||||||
void SetCS(int cs) override;
|
void SetCS(int cs) override;
|
||||||
bool IsInterruptSet() override;
|
bool IsInterruptSet() override;
|
||||||
|
@ -26,8 +17,12 @@ public:
|
||||||
void DoState(PointerWrap &p) override;
|
void DoState(PointerWrap &p) override;
|
||||||
void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) override;
|
void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) override;
|
||||||
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1) 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:
|
private:
|
||||||
|
void setupGciFolder(u16 sizeMb);
|
||||||
|
void setupRawMemcard(u16 sizeMb);
|
||||||
// This is scheduled whenever a page write is issued. The this pointer is passed
|
// 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.
|
// through the userdata parameter, so that it can then call Flush on the right card.
|
||||||
static void FlushCallback(u64 userdata, int cyclesLate);
|
static void FlushCallback(u64 userdata, int cyclesLate);
|
||||||
|
@ -63,7 +58,6 @@ private:
|
||||||
cmdChipErase = 0xF4,
|
cmdChipErase = 0xF4,
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string m_strFilename;
|
|
||||||
int card_index;
|
int card_index;
|
||||||
int et_this_card, et_cmd_done;
|
int et_this_card, et_cmd_done;
|
||||||
//! memory card state
|
//! memory card state
|
||||||
|
@ -77,13 +71,10 @@ private:
|
||||||
u8 programming_buffer[128];
|
u8 programming_buffer[128];
|
||||||
bool m_bDirty;
|
bool m_bDirty;
|
||||||
//! memory card parameters
|
//! memory card parameters
|
||||||
unsigned int nintendo_card_id, card_id;
|
unsigned int card_id;
|
||||||
unsigned int address;
|
unsigned int address;
|
||||||
int memory_card_size; //! in bytes, must be power of 2.
|
u32 memory_card_size;
|
||||||
u8 *memory_card_content;
|
std::unique_ptr<MemoryCardBase> memorycard;
|
||||||
|
|
||||||
FlushData flushData;
|
|
||||||
std::thread flushThread;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void TransferByte(u8 &byte) override;
|
virtual void TransferByte(u8 &byte) override;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under GPLv2
|
// Licensed under GPLv2
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ static void ByteSwap(u8 *valueA, u8 *valueB)
|
||||||
*valueB = tmp;
|
*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_valid(false)
|
||||||
, m_fileName(filename)
|
, m_fileName(filename)
|
||||||
{
|
{
|
||||||
|
@ -24,11 +25,13 @@ GCMemcard::GCMemcard(const std::string& filename, bool forceCreation, bool sjis)
|
||||||
File::IOFile mcdFile(m_fileName, "rb");
|
File::IOFile mcdFile(m_fileName, "rb");
|
||||||
if (!mcdFile.IsOpen())
|
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;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -235,7 +238,7 @@ bool GCMemcard::Save()
|
||||||
return mcdFile.Close();
|
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;
|
*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))
|
if (!m_valid || index > DIRLEN || (BE32(CurrentDir->Dir[index].Gamecode) == 0xFFFFFFFF))
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,7 +549,7 @@ bool GCMemcard::GetDEntry(u8 index, DEntry &dest) const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
u16 GCMemcard::BlockAlloc::GetNextBlock(u16 Block) const
|
u16 BlockAlloc::GetNextBlock(u16 Block) const
|
||||||
{
|
{
|
||||||
if ((Block < MC_FST_BLOCKS) || (Block > 4091))
|
if ((Block < MC_FST_BLOCKS) || (Block > 4091))
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -554,7 +557,7 @@ u16 GCMemcard::BlockAlloc::GetNextBlock(u16 Block) const
|
||||||
return Common::swap16(Map[Block-MC_FST_BLOCKS]);
|
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)
|
if (FreeBlocks)
|
||||||
{
|
{
|
||||||
|
@ -570,7 +573,7 @@ u16 GCMemcard::BlockAlloc::NextFreeBlock(u16 MaxBlock, u16 StartingBlock) const
|
||||||
return 0xFFFF;
|
return 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GCMemcard::BlockAlloc::ClearBlocks(u16 FirstBlock, u16 BlockCount)
|
bool BlockAlloc::ClearBlocks(u16 FirstBlock, u16 BlockCount)
|
||||||
{
|
{
|
||||||
std::vector<u16> blocks;
|
std::vector<u16> blocks;
|
||||||
while (FirstBlock != 0xFFFF && FirstBlock != 0)
|
while (FirstBlock != 0xFFFF && FirstBlock != 0)
|
||||||
|
@ -670,8 +673,8 @@ u32 GCMemcard::ImportFile(DEntry& direntry, std::vector<GCMBlock> &saveBlocks)
|
||||||
|
|
||||||
int fileBlocks = BE16(direntry.BlockCount);
|
int fileBlocks = BE16(direntry.BlockCount);
|
||||||
|
|
||||||
FZEROGX_MakeSaveGameValid(direntry, saveBlocks);
|
FZEROGX_MakeSaveGameValid(hdr, direntry, saveBlocks);
|
||||||
PSO_MakeSaveGameValid(direntry, saveBlocks);
|
PSO_MakeSaveGameValid(hdr, direntry, saveBlocks);
|
||||||
|
|
||||||
BlockAlloc UpdatedBat = *CurrentBat;
|
BlockAlloc UpdatedBat = *CurrentBat;
|
||||||
u16 nextBlock;
|
u16 nextBlock;
|
||||||
|
@ -1201,29 +1204,23 @@ u32 GCMemcard::ReadAnimRGBA8(u8 index, u32* buffer, u8 *delays) const
|
||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GCMemcard::Format(u8 *card_data, bool ascii, u16 SizeMb)
|
||||||
bool GCMemcard::Format(u8 * card_data, bool sjis, u16 SizeMb)
|
|
||||||
{
|
{
|
||||||
if (!card_data)
|
if (!card_data)
|
||||||
return false;
|
return false;
|
||||||
memset(card_data, 0xFF, BLOCK_SIZE*3);
|
memset(card_data, 0xFF, BLOCK_SIZE*3);
|
||||||
memset(card_data + BLOCK_SIZE*3, 0, BLOCK_SIZE*2);
|
memset(card_data + BLOCK_SIZE*3, 0, BLOCK_SIZE*2);
|
||||||
|
|
||||||
GCMC_Header gcp;
|
*((Header *)card_data) = Header(SLOT_A, SizeMb, ascii);
|
||||||
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);
|
|
||||||
|
|
||||||
*(u16*)gcp.hdr->SizeMb = BE16(SizeMb);
|
*((Directory *)(card_data + BLOCK_SIZE)) = Directory();
|
||||||
gcp.hdr->Encoding = BE16(sjis ? 1 : 0);
|
*((Directory *)(card_data + BLOCK_SIZE * 2)) = Directory();
|
||||||
|
*((BlockAlloc *)(card_data + BLOCK_SIZE * 3)) = BlockAlloc(SizeMb);
|
||||||
FormatInternal(gcp);
|
*((BlockAlloc *)(card_data + BLOCK_SIZE * 4)) = BlockAlloc(SizeMb);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GCMemcard::Format(bool sjis, u16 SizeMb)
|
bool GCMemcard::Format(bool ascii, u16 SizeMb)
|
||||||
{
|
{
|
||||||
memset(&hdr, 0xFF, BLOCK_SIZE);
|
memset(&hdr, 0xFF, BLOCK_SIZE);
|
||||||
memset(&dir, 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, 0, BLOCK_SIZE);
|
||||||
memset(&bat_backup, 0, BLOCK_SIZE);
|
memset(&bat_backup, 0, BLOCK_SIZE);
|
||||||
|
|
||||||
GCMC_Header gcp;
|
hdr = Header(SLOT_A, SizeMb, ascii);
|
||||||
gcp.hdr = &hdr;
|
dir = dir_backup = Directory();
|
||||||
gcp.dir = &dir;
|
bat = bat_backup = BlockAlloc(SizeMb);
|
||||||
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);
|
|
||||||
|
|
||||||
m_sizeMb = SizeMb;
|
m_sizeMb = SizeMb;
|
||||||
maxBlock = (u32)m_sizeMb * MBIT_TO_BLOCKS;
|
maxBlock = (u32)m_sizeMb * MBIT_TO_BLOCKS;
|
||||||
mc_data_blocks.reserve(maxBlock - MC_FST_BLOCKS);
|
mc_data_blocks.clear();
|
||||||
for (u32 i = 0; i < (maxBlock - MC_FST_BLOCKS); ++i)
|
mc_data_blocks.resize(maxBlock - MC_FST_BLOCKS);
|
||||||
{
|
|
||||||
GCMBlock b;
|
|
||||||
mc_data_blocks.push_back(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
initDirBatPointers();
|
initDirBatPointers();
|
||||||
m_valid = true;
|
m_valid = true;
|
||||||
|
@ -1257,56 +1243,6 @@ bool GCMemcard::Format(bool sjis, u16 SizeMb)
|
||||||
return Save();
|
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 */
|
/* FZEROGX_MakeSaveGameValid */
|
||||||
/* (use just before writing a F-Zero GX system .gci file) */
|
/* (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 */
|
/* Returns: Error code */
|
||||||
/*************************************************************/
|
/*************************************************************/
|
||||||
|
|
||||||
s32 GCMemcard::FZEROGX_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &FileBuffer)
|
s32 GCMemcard::FZEROGX_MakeSaveGameValid(Header &cardheader, DEntry &direntry, std::vector<GCMBlock> &FileBuffer)
|
||||||
{
|
{
|
||||||
u32 i,j;
|
u32 i,j;
|
||||||
u32 serial1,serial2;
|
u32 serial1,serial2;
|
||||||
|
@ -1329,7 +1265,7 @@ s32 GCMemcard::FZEROGX_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock>
|
||||||
if (strcmp((char*)direntry.Filename,"f_zero.dat")!=0) return 0;
|
if (strcmp((char*)direntry.Filename,"f_zero.dat")!=0) return 0;
|
||||||
|
|
||||||
// get encrypted destination memory card serial numbers
|
// get encrypted destination memory card serial numbers
|
||||||
CARD_GetSerialNo(&serial1,&serial2);
|
cardheader.CARD_GetSerialNo(&serial1, &serial2);
|
||||||
|
|
||||||
// set new serial numbers
|
// set new serial numbers
|
||||||
*(u16*)&FileBuffer[1].block[0x0066] = BE16(BE32(serial1) >> 16);
|
*(u16*)&FileBuffer[1].block[0x0066] = BE16(BE32(serial1) >> 16);
|
||||||
|
@ -1366,7 +1302,7 @@ s32 GCMemcard::FZEROGX_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock>
|
||||||
/* Returns: Error code */
|
/* Returns: Error code */
|
||||||
/***********************************************************/
|
/***********************************************************/
|
||||||
|
|
||||||
s32 GCMemcard::PSO_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &FileBuffer)
|
s32 GCMemcard::PSO_MakeSaveGameValid(Header &cardheader, DEntry &direntry, std::vector<GCMBlock> &FileBuffer)
|
||||||
{
|
{
|
||||||
u32 i,j;
|
u32 i,j;
|
||||||
u32 chksum;
|
u32 chksum;
|
||||||
|
@ -1391,7 +1327,7 @@ s32 GCMemcard::PSO_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &Fi
|
||||||
}
|
}
|
||||||
|
|
||||||
// get encrypted destination memory card serial numbers
|
// get encrypted destination memory card serial numbers
|
||||||
CARD_GetSerialNo(&serial1,&serial2);
|
cardheader.CARD_GetSerialNo(&serial1, &serial2);
|
||||||
|
|
||||||
// set new serial numbers
|
// set new serial numbers
|
||||||
*(u32*)&FileBuffer[1].block[0x0158] = serial1;
|
*(u32*)&FileBuffer[1].block[0x0158] = serial1;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#define BE32(x) (Common::swap32(x))
|
#define BE32(x) (Common::swap32(x))
|
||||||
#define BE16(x) (Common::swap16(x))
|
#define BE16(x) (Common::swap16(x))
|
||||||
#define ArrayByteSwap(a) (ByteSwap(a, a+sizeof(u8)));
|
#define ArrayByteSwap(a) (ByteSwap(a, a+sizeof(u8)));
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
SLOT_A = 0,
|
SLOT_A = 0,
|
||||||
|
@ -58,132 +59,263 @@ enum
|
||||||
CI8,
|
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<GCMBlock> m_save_data;
|
||||||
|
std::vector<u16> m_used_blocks;
|
||||||
|
int UsesBlock(u16 blocknum);
|
||||||
|
bool m_dirty;
|
||||||
|
std::string m_filename;
|
||||||
|
};
|
||||||
|
|
||||||
class GCMemcard : NonCopyable
|
class GCMemcard : NonCopyable
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
friend class CMemcardManagerDebug;
|
|
||||||
bool m_valid;
|
bool m_valid;
|
||||||
std::string m_fileName;
|
std::string m_fileName;
|
||||||
|
|
||||||
u32 maxBlock;
|
u32 maxBlock;
|
||||||
u16 m_sizeMb;
|
u16 m_sizeMb;
|
||||||
struct GCMBlock
|
|
||||||
{
|
Header hdr;
|
||||||
GCMBlock(){erase();}
|
Directory dir, dir_backup, *CurrentDir, *PreviousDir;
|
||||||
void erase() {memset(block, 0xFF, BLOCK_SIZE);}
|
BlockAlloc bat, bat_backup, *CurrentBat, *PreviousBat;
|
||||||
u8 block[BLOCK_SIZE];
|
|
||||||
};
|
|
||||||
std::vector<GCMBlock> mc_data_blocks;
|
std::vector<GCMBlock> 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);
|
u32 ImportGciInternal(FILE* gcih, const std::string& inputFile, const std::string &outputFile);
|
||||||
static void FormatInternal(GCMC_Header &GCP);
|
void initDirBatPointers();
|
||||||
void initDirBatPointers() ;
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
GCMemcard(const std::string& fileName, bool forceCreation=false, bool sjis=false);
|
GCMemcard(const std::string& fileName, bool forceCreation=false, bool sjis=false);
|
||||||
bool IsValid() const { return m_valid; }
|
bool IsValid() const { return m_valid; }
|
||||||
bool IsAsciiEncoding() const;
|
bool IsAsciiEncoding() const;
|
||||||
bool Save();
|
bool Save();
|
||||||
bool Format(bool sjis = false, u16 SizeMb = MemCard2043Mb);
|
bool Format(bool ascii = true, u16 SizeMb = MemCard2043Mb);
|
||||||
static bool Format(u8 * card_data, bool sjis = false, 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<GCMBlock> &FileBuffer);
|
||||||
|
static s32 PSO_MakeSaveGameValid(Header &cardheader, DEntry &direntry, std::vector<GCMBlock> &FileBuffer);
|
||||||
|
|
||||||
static void calc_checksumsBE(u16 *buf, u32 length, u16 *csum, u16 *inv_csum);
|
|
||||||
u32 TestChecksums() const;
|
u32 TestChecksums() const;
|
||||||
bool FixChecksums();
|
bool FixChecksums();
|
||||||
|
|
||||||
|
@ -219,10 +351,6 @@ public:
|
||||||
// Copies a DEntry from u8 index to DEntry& data
|
// Copies a DEntry from u8 index to DEntry& data
|
||||||
bool GetDEntry(u8 index, DEntry &dest) const;
|
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<GCMBlock> &saveBlocks) const;
|
u32 GetSaveData(u8 index, std::vector<GCMBlock> &saveBlocks) const;
|
||||||
|
|
||||||
// adds the file to the directory and copies its contents
|
// adds the file to the directory and copies its contents
|
||||||
|
@ -249,8 +377,4 @@ public:
|
||||||
|
|
||||||
// reads the animation frames
|
// reads the animation frames
|
||||||
u32 ReadAnimRGBA8(u8 index, u32* buffer, u8 *delays) const;
|
u32 ReadAnimRGBA8(u8 index, u32* buffer, u8 *delays) const;
|
||||||
|
|
||||||
void CARD_GetSerialNo(u32 *serial1,u32 *serial2);
|
|
||||||
s32 FZEROGX_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &FileBuffer);
|
|
||||||
s32 PSO_MakeSaveGameValid(DEntry& direntry, std::vector<GCMBlock> &FileBuffer);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,614 @@
|
||||||
|
// Copyright 2013 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
#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<s32>(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<Header>(m_hdr);
|
||||||
|
p.DoPOD<Directory>(m_dir1);
|
||||||
|
p.DoPOD<Directory>(m_dir2);
|
||||||
|
p.DoPOD<BlockAlloc>(m_bat1);
|
||||||
|
p.DoPOD<BlockAlloc>(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<DEntry>(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<GCMBlock>(*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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<GCIFile> m_saves;
|
||||||
|
|
||||||
|
std::vector<std::string> m_loaded_saves;
|
||||||
|
std::string m_SaveDirectory;
|
||||||
|
};
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -63,7 +63,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
|
||||||
static std::thread g_save_thread;
|
static std::thread g_save_thread;
|
||||||
|
|
||||||
// Don't forget to increase this after doing changes on the savestate system
|
// 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
|
enum
|
||||||
{
|
{
|
||||||
|
|
|
@ -116,6 +116,7 @@ static const wxLanguage langIds[] =
|
||||||
#define SIDEV_AM_BB_STR _trans("AM-Baseboard")
|
#define SIDEV_AM_BB_STR _trans("AM-Baseboard")
|
||||||
|
|
||||||
#define EXIDEV_MEMCARD_STR _trans("Memory Card")
|
#define EXIDEV_MEMCARD_STR _trans("Memory Card")
|
||||||
|
#define EXIDEV_MEMDIR_STR _trans("GCI Folder")
|
||||||
#define EXIDEV_MIC_STR _trans("Mic")
|
#define EXIDEV_MIC_STR _trans("Mic")
|
||||||
#define EXIDEV_BBA_STR "BBA"
|
#define EXIDEV_BBA_STR "BBA"
|
||||||
#define EXIDEV_AM_BB_STR _trans("AM-Baseboard")
|
#define EXIDEV_AM_BB_STR _trans("AM-Baseboard")
|
||||||
|
@ -395,6 +396,8 @@ void CConfigMain::InitializeGUIValues()
|
||||||
SlotDevices.Add(_(DEV_DUMMY_STR));
|
SlotDevices.Add(_(DEV_DUMMY_STR));
|
||||||
SlotDevices.Add(_(EXIDEV_MEMCARD_STR));
|
SlotDevices.Add(_(EXIDEV_MEMCARD_STR));
|
||||||
SlotDevices.Add(_(EXIDEV_GECKO_STR));
|
SlotDevices.Add(_(EXIDEV_GECKO_STR));
|
||||||
|
SlotDevices.Add(_(EXIDEV_MEMDIR_STR));
|
||||||
|
|
||||||
#if HAVE_PORTAUDIO
|
#if HAVE_PORTAUDIO
|
||||||
SlotDevices.Add(_(EXIDEV_MIC_STR));
|
SlotDevices.Add(_(EXIDEV_MIC_STR));
|
||||||
#endif
|
#endif
|
||||||
|
@ -432,9 +435,12 @@ void CConfigMain::InitializeGUIValues()
|
||||||
case EXIDEVICE_MEMORYCARD:
|
case EXIDEVICE_MEMORYCARD:
|
||||||
isMemcard = GCEXIDevice[i]->SetStringSelection(SlotDevices[2]);
|
isMemcard = GCEXIDevice[i]->SetStringSelection(SlotDevices[2]);
|
||||||
break;
|
break;
|
||||||
case EXIDEVICE_MIC:
|
case EXIDEVICE_MEMORYCARDFOLDER:
|
||||||
GCEXIDevice[i]->SetStringSelection(SlotDevices[4]);
|
GCEXIDevice[i]->SetStringSelection(SlotDevices[4]);
|
||||||
break;
|
break;
|
||||||
|
case EXIDEVICE_MIC:
|
||||||
|
GCEXIDevice[i]->SetStringSelection(SlotDevices[5]);
|
||||||
|
break;
|
||||||
case EXIDEVICE_ETH:
|
case EXIDEVICE_ETH:
|
||||||
GCEXIDevice[i]->SetStringSelection(SP1Devices[2]);
|
GCEXIDevice[i]->SetStringSelection(SP1Devices[2]);
|
||||||
break;
|
break;
|
||||||
|
@ -1143,6 +1149,8 @@ void CConfigMain::ChooseEXIDevice(wxString deviceName, int deviceNum)
|
||||||
|
|
||||||
if (!deviceName.compare(_(EXIDEV_MEMCARD_STR)))
|
if (!deviceName.compare(_(EXIDEV_MEMCARD_STR)))
|
||||||
tempType = EXIDEVICE_MEMORYCARD;
|
tempType = EXIDEVICE_MEMORYCARD;
|
||||||
|
else if (!deviceName.compare(_(EXIDEV_MEMDIR_STR)))
|
||||||
|
tempType = EXIDEVICE_MEMORYCARDFOLDER;
|
||||||
else if (!deviceName.compare(_(EXIDEV_MIC_STR)))
|
else if (!deviceName.compare(_(EXIDEV_MIC_STR)))
|
||||||
tempType = EXIDEVICE_MIC;
|
tempType = EXIDEVICE_MIC;
|
||||||
else if (!deviceName.compare(EXIDEV_BBA_STR))
|
else if (!deviceName.compare(EXIDEV_BBA_STR))
|
||||||
|
|
Loading…
Reference in New Issue