mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #7222 from Techjar/netplay-sync-saves
NetPlay save data synchronization
This commit is contained in:
@@ -36,6 +36,7 @@ add_library(common
|
||||
QoSSession.cpp
|
||||
Random.cpp
|
||||
SDCardUtil.cpp
|
||||
SFMLHelper.cpp
|
||||
SettingsHandler.cpp
|
||||
StringUtil.cpp
|
||||
SymbolDB.cpp
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
<ClInclude Include="Result.h" />
|
||||
<ClInclude Include="ScopeGuard.h" />
|
||||
<ClInclude Include="SDCardUtil.h" />
|
||||
<ClInclude Include="SFMLHelper.h" />
|
||||
<ClInclude Include="Semaphore.h" />
|
||||
<ClInclude Include="SettingsHandler.h" />
|
||||
<ClInclude Include="SPSCQueue.h" />
|
||||
@@ -210,6 +211,7 @@
|
||||
<ClCompile Include="QoSSession.cpp" />
|
||||
<ClCompile Include="Random.cpp" />
|
||||
<ClCompile Include="SDCardUtil.cpp" />
|
||||
<ClCompile Include="SFMLHelper.cpp" />
|
||||
<ClCompile Include="SettingsHandler.cpp" />
|
||||
<ClCompile Include="StringUtil.cpp" />
|
||||
<ClCompile Include="SymbolDB.cpp" />
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
<ClInclude Include="Result.h" />
|
||||
<ClInclude Include="ScopeGuard.h" />
|
||||
<ClInclude Include="SDCardUtil.h" />
|
||||
<ClInclude Include="SFMLHelper.h" />
|
||||
<ClInclude Include="SettingsHandler.h" />
|
||||
<ClInclude Include="SPSCQueue.h" />
|
||||
<ClInclude Include="StringUtil.h" />
|
||||
@@ -299,6 +300,7 @@
|
||||
<ClCompile Include="Profiler.cpp" />
|
||||
<ClCompile Include="Random.cpp" />
|
||||
<ClCompile Include="SDCardUtil.cpp" />
|
||||
<ClCompile Include="SFMLHelper.cpp" />
|
||||
<ClCompile Include="SettingsHandler.cpp" />
|
||||
<ClCompile Include="StringUtil.cpp" />
|
||||
<ClCompile Include="SymbolDB.cpp" />
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
#define GC_SRAM "SRAM.raw"
|
||||
#define GC_MEMCARDA "MemoryCardA"
|
||||
#define GC_MEMCARDB "MemoryCardB"
|
||||
#define GC_MEMCARD_NETPLAY "NetPlayTemp"
|
||||
|
||||
#define WII_STATE "state.dat"
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "Common/SFMLHelper.h"
|
||||
|
||||
#include <SFML/Network/Packet.hpp>
|
||||
|
||||
namespace Common
|
||||
{
|
||||
// This only exists as a helper for BigEndianValue
|
||||
u16 PacketReadU16(sf::Packet& packet)
|
||||
{
|
||||
u16 tmp;
|
||||
packet >> tmp;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
// This only exists as a helper for BigEndianValue
|
||||
u32 PacketReadU32(sf::Packet& packet)
|
||||
{
|
||||
u32 tmp;
|
||||
packet >> tmp;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
u64 PacketReadU64(sf::Packet& packet)
|
||||
{
|
||||
u32 low, high;
|
||||
packet >> low >> high;
|
||||
return low | (static_cast<u64>(high) << 32);
|
||||
}
|
||||
|
||||
void PacketWriteU64(sf::Packet& packet, const u64 value)
|
||||
{
|
||||
packet << static_cast<u32>(value) << static_cast<u32>(value >> 32);
|
||||
}
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace sf
|
||||
{
|
||||
class Packet;
|
||||
}
|
||||
|
||||
namespace Common
|
||||
{
|
||||
template <typename value_type>
|
||||
struct BigEndianValue;
|
||||
|
||||
u16 PacketReadU16(sf::Packet& packet);
|
||||
u32 PacketReadU32(sf::Packet& packet);
|
||||
u64 PacketReadU64(sf::Packet& packet);
|
||||
void PacketWriteU64(sf::Packet& packet, u64 value);
|
||||
} // namespace Common
|
||||
@@ -36,6 +36,12 @@ const ConfigInfo<std::string> MAIN_MEMCARD_A_PATH{{System::Main, "Core", "Memcar
|
||||
const ConfigInfo<std::string> MAIN_MEMCARD_B_PATH{{System::Main, "Core", "MemcardBPath"}, ""};
|
||||
const ConfigInfo<std::string> MAIN_AGP_CART_A_PATH{{System::Main, "Core", "AgpCartAPath"}, ""};
|
||||
const ConfigInfo<std::string> MAIN_AGP_CART_B_PATH{{System::Main, "Core", "AgpCartBPath"}, ""};
|
||||
const ConfigInfo<std::string> MAIN_GCI_FOLDER_A_PATH_OVERRIDE{
|
||||
{System::Main, "Core", "GCIFolderAPathOverride"}, ""};
|
||||
const ConfigInfo<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE{
|
||||
{System::Main, "Core", "GCIFolderBPathOverride"}, ""};
|
||||
const ConfigInfo<bool> MAIN_GCI_FOLDER_CURRENT_GAME_ONLY{
|
||||
{System::Main, "Core", "GCIFolderCurrentGameOnly"}, false};
|
||||
const ConfigInfo<int> MAIN_SLOT_A{{System::Main, "Core", "SlotA"},
|
||||
ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER};
|
||||
const ConfigInfo<int> MAIN_SLOT_B{{System::Main, "Core", "SlotB"},
|
||||
|
||||
@@ -37,6 +37,9 @@ extern const ConfigInfo<std::string> MAIN_MEMCARD_A_PATH;
|
||||
extern const ConfigInfo<std::string> MAIN_MEMCARD_B_PATH;
|
||||
extern const ConfigInfo<std::string> MAIN_AGP_CART_A_PATH;
|
||||
extern const ConfigInfo<std::string> MAIN_AGP_CART_B_PATH;
|
||||
extern const ConfigInfo<std::string> MAIN_GCI_FOLDER_A_PATH_OVERRIDE;
|
||||
extern const ConfigInfo<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE;
|
||||
extern const ConfigInfo<bool> MAIN_GCI_FOLDER_CURRENT_GAME_ONLY;
|
||||
extern const ConfigInfo<int> MAIN_SLOT_A;
|
||||
extern const ConfigInfo<int> MAIN_SLOT_B;
|
||||
extern const ConfigInfo<int> MAIN_SERIAL_PORT_1;
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Config/SYSCONFSettings.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
@@ -39,6 +41,23 @@ public:
|
||||
|
||||
layer->Set(Config::SYSCONF_PROGRESSIVE_SCAN, m_settings.m_ProgressiveScan);
|
||||
layer->Set(Config::SYSCONF_PAL60, m_settings.m_PAL60);
|
||||
|
||||
if (m_settings.m_SyncSaveData)
|
||||
{
|
||||
if (!m_settings.m_IsHosting)
|
||||
{
|
||||
const std::string path = File::GetUserPath(D_GCUSER_IDX) + GC_MEMCARD_NETPLAY DIR_SEP;
|
||||
layer->Set(Config::MAIN_GCI_FOLDER_A_PATH_OVERRIDE, path + "Card A");
|
||||
layer->Set(Config::MAIN_GCI_FOLDER_B_PATH_OVERRIDE, path + "Card B");
|
||||
|
||||
const std::string file = File::GetUserPath(D_GCUSER_IDX) + GC_MEMCARD_NETPLAY + "%c." +
|
||||
m_settings.m_SaveDataRegion + ".raw";
|
||||
layer->Set(Config::MAIN_MEMCARD_A_PATH, StringFromFormat(file.c_str(), 'A'));
|
||||
layer->Set(Config::MAIN_MEMCARD_B_PATH, StringFromFormat(file.c_str(), 'B'));
|
||||
}
|
||||
|
||||
layer->Set(Config::MAIN_GCI_FOLDER_CURRENT_GAME_ONLY, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Save(Config::Layer* layer) override
|
||||
|
||||
@@ -227,8 +227,6 @@ void SConfig::SaveCoreSettings(IniFile& ini)
|
||||
core->Set("AudioLatency", iLatency);
|
||||
core->Set("AudioStretch", m_audio_stretch);
|
||||
core->Set("AudioStretchMaxLatency", m_audio_stretch_max_latency);
|
||||
core->Set("MemcardAPath", m_strMemoryCardA);
|
||||
core->Set("MemcardBPath", m_strMemoryCardB);
|
||||
core->Set("AgpCartAPath", m_strGbaCartA);
|
||||
core->Set("AgpCartBPath", m_strGbaCartB);
|
||||
core->Set("SlotA", m_EXIDevice[0]);
|
||||
@@ -505,8 +503,6 @@ void SConfig::LoadCoreSettings(IniFile& ini)
|
||||
core->Get("AudioLatency", &iLatency, 20);
|
||||
core->Get("AudioStretch", &m_audio_stretch, false);
|
||||
core->Get("AudioStretchMaxLatency", &m_audio_stretch_max_latency, 80);
|
||||
core->Get("MemcardAPath", &m_strMemoryCardA);
|
||||
core->Get("MemcardBPath", &m_strMemoryCardB);
|
||||
core->Get("AgpCartAPath", &m_strGbaCartA);
|
||||
core->Get("AgpCartBPath", &m_strGbaCartB);
|
||||
core->Get("SlotA", (int*)&m_EXIDevice[0], ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER);
|
||||
@@ -947,62 +943,12 @@ bool SConfig::SetPathsAndGameMetadata(const BootParameters& boot)
|
||||
|
||||
// Set up paths
|
||||
const std::string region_dir = GetDirectoryForRegion(ToGameCubeRegion(m_region));
|
||||
CheckMemcardPath(SConfig::GetInstance().m_strMemoryCardA, region_dir, true);
|
||||
CheckMemcardPath(SConfig::GetInstance().m_strMemoryCardB, region_dir, false);
|
||||
m_strSRAM = File::GetUserPath(F_GCSRAM_IDX);
|
||||
m_strBootROM = GetBootROMPath(region_dir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SConfig::CheckMemcardPath(std::string& memcardPath, const std::string& gameRegion,
|
||||
bool isSlotA)
|
||||
{
|
||||
std::string ext("." + gameRegion + ".raw");
|
||||
if (memcardPath.empty())
|
||||
{
|
||||
// Use default memcard path if there is no user defined name
|
||||
std::string defaultFilename = isSlotA ? GC_MEMCARDA : GC_MEMCARDB;
|
||||
memcardPath = File::GetUserPath(D_GCUSER_IDX) + defaultFilename + ext;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string filename = memcardPath;
|
||||
std::string region = filename.substr(filename.size() - 7, 3);
|
||||
bool hasregion = false;
|
||||
hasregion |= region.compare(USA_DIR) == 0;
|
||||
hasregion |= region.compare(JAP_DIR) == 0;
|
||||
hasregion |= region.compare(EUR_DIR) == 0;
|
||||
if (!hasregion)
|
||||
{
|
||||
// filename doesn't have region in the extension
|
||||
if (File::Exists(filename))
|
||||
{
|
||||
// If the old file exists we are polite and ask if we should copy it
|
||||
std::string oldFilename = filename;
|
||||
filename.replace(filename.size() - 4, 4, ext);
|
||||
if (PanicYesNoT("Memory Card filename in Slot %c is incorrect\n"
|
||||
"Region not specified\n\n"
|
||||
"Slot %c path was changed to\n"
|
||||
"%s\n"
|
||||
"Would you like to copy the old file to this new location?\n",
|
||||
isSlotA ? 'A' : 'B', isSlotA ? 'A' : 'B', filename.c_str()))
|
||||
{
|
||||
if (!File::Copy(oldFilename, filename))
|
||||
PanicAlertT("Copy failed");
|
||||
}
|
||||
}
|
||||
memcardPath = filename; // Always correct the path!
|
||||
}
|
||||
else if (region.compare(gameRegion) != 0)
|
||||
{
|
||||
// filename has region, but it's not == gameRegion
|
||||
// Just set the correct filename, the EXI Device will create it if it doesn't exist
|
||||
memcardPath = filename.replace(filename.size() - ext.size(), ext.size(), ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DiscIO::Language SConfig::GetCurrentLanguage(bool wii) const
|
||||
{
|
||||
int language_value;
|
||||
|
||||
@@ -212,7 +212,6 @@ struct SConfig
|
||||
static const char* GetDirectoryForRegion(DiscIO::Region region);
|
||||
std::string GetBootROMPath(const std::string& region_directory) const;
|
||||
bool SetPathsAndGameMetadata(const BootParameters& boot);
|
||||
void CheckMemcardPath(std::string& memcardPath, const std::string& gameRegion, bool isSlotA);
|
||||
DiscIO::Language GetCurrentLanguage(bool wii) const;
|
||||
|
||||
IniFile LoadDefaultGameIni() const;
|
||||
@@ -223,8 +222,6 @@ struct SConfig
|
||||
static IniFile LoadLocalGameIni(const std::string& id, std::optional<u16> revision);
|
||||
static IniFile LoadGameIni(const std::string& id, std::optional<u16> revision);
|
||||
|
||||
std::string m_strMemoryCardA;
|
||||
std::string m_strMemoryCardB;
|
||||
std::string m_strGbaCartA;
|
||||
std::string m_strGbaCartB;
|
||||
ExpansionInterface::TEXIDevices m_EXIDevice[3];
|
||||
|
||||
@@ -448,6 +448,7 @@
|
||||
<ClInclude Include="HW\WiimoteReal\WiimoteReal.h" />
|
||||
<ClInclude Include="HW\WiimoteReal\WiimoteRealBase.h" />
|
||||
<ClInclude Include="HW\WiiSave.h" />
|
||||
<ClInclude Include="HW\WiiSaveStructs.h" />
|
||||
<ClInclude Include="HW\WII_IPC.h" />
|
||||
<ClInclude Include="IOS\Device.h" />
|
||||
<ClInclude Include="IOS\DeviceStub.h" />
|
||||
|
||||
@@ -1272,6 +1272,9 @@
|
||||
<ClInclude Include="HW\WiiSave.h">
|
||||
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HW\WiiSaveStructs.h">
|
||||
<Filter>HW %28Flipper/Hollywood%29</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DSP\DSPAssembler.h">
|
||||
<Filter>DSPCore</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/NandPaths.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/CommonTitles.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/EXI/EXI.h"
|
||||
@@ -31,6 +33,7 @@
|
||||
#include "Core/HW/Sram.h"
|
||||
#include "Core/HW/SystemTimers.h"
|
||||
#include "Core/Movie.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
|
||||
namespace ExpansionInterface
|
||||
@@ -169,24 +172,46 @@ void CEXIMemoryCard::SetupGciFolder(u16 sizeMb)
|
||||
|
||||
std::string strDirectoryName = File::GetUserPath(D_GCUSER_IDX);
|
||||
|
||||
bool migrate = true;
|
||||
|
||||
if (Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsUsingMemcard(card_index) &&
|
||||
Movie::IsStartingFromClearSave())
|
||||
{
|
||||
strDirectoryName += "Movie" DIR_SEP;
|
||||
migrate = false;
|
||||
}
|
||||
|
||||
strDirectoryName = strDirectoryName + SConfig::GetDirectoryForRegion(region) + DIR_SEP +
|
||||
StringFromFormat("Card %c", 'A' + card_index);
|
||||
const std::string path_override =
|
||||
Config::Get(card_index == 0 ? Config::MAIN_GCI_FOLDER_A_PATH_OVERRIDE :
|
||||
Config::MAIN_GCI_FOLDER_B_PATH_OVERRIDE);
|
||||
if (!path_override.empty())
|
||||
{
|
||||
strDirectoryName = path_override;
|
||||
migrate = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
strDirectoryName = strDirectoryName + SConfig::GetDirectoryForRegion(region) + DIR_SEP +
|
||||
StringFromFormat("Card %c", 'A' + card_index);
|
||||
}
|
||||
|
||||
const File::FileInfo file_info(strDirectoryName);
|
||||
if (!file_info.Exists()) // first use of memcard folder, migrate automatically
|
||||
if (!file_info.Exists())
|
||||
{
|
||||
MigrateFromMemcardFile(strDirectoryName + DIR_SEP, card_index);
|
||||
if (migrate) // first use of memcard folder, migrate automatically
|
||||
MigrateFromMemcardFile(strDirectoryName + DIR_SEP, card_index);
|
||||
else
|
||||
File::CreateFullPath(strDirectoryName + DIR_SEP);
|
||||
}
|
||||
else if (!file_info.IsDirectory())
|
||||
{
|
||||
if (File::Rename(strDirectoryName, strDirectoryName + ".original"))
|
||||
{
|
||||
PanicAlertT("%s was not a directory, moved to *.original", strDirectoryName.c_str());
|
||||
MigrateFromMemcardFile(strDirectoryName + DIR_SEP, card_index);
|
||||
if (migrate)
|
||||
MigrateFromMemcardFile(strDirectoryName + DIR_SEP, card_index);
|
||||
else
|
||||
File::CreateFullPath(strDirectoryName + DIR_SEP);
|
||||
}
|
||||
else // we tried but the user wants to crash
|
||||
{
|
||||
@@ -204,17 +229,21 @@ void CEXIMemoryCard::SetupGciFolder(u16 sizeMb)
|
||||
|
||||
void CEXIMemoryCard::SetupRawMemcard(u16 sizeMb)
|
||||
{
|
||||
std::string filename = (card_index == 0) ? SConfig::GetInstance().m_strMemoryCardA :
|
||||
SConfig::GetInstance().m_strMemoryCardB;
|
||||
const bool is_slot_a = card_index == 0;
|
||||
std::string filename = is_slot_a ? Config::Get(Config::MAIN_MEMCARD_A_PATH) :
|
||||
Config::Get(Config::MAIN_MEMCARD_B_PATH);
|
||||
if (Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsUsingMemcard(card_index) &&
|
||||
Movie::IsStartingFromClearSave())
|
||||
filename = File::GetUserPath(D_GCUSER_IDX) +
|
||||
StringFromFormat("Movie%s.raw", (card_index == 0) ? "A" : "B");
|
||||
filename =
|
||||
File::GetUserPath(D_GCUSER_IDX) + StringFromFormat("Movie%s.raw", is_slot_a ? "A" : "B");
|
||||
|
||||
const std::string region_dir =
|
||||
SConfig::GetDirectoryForRegion(SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region));
|
||||
MemoryCard::CheckPath(filename, region_dir, is_slot_a);
|
||||
|
||||
if (sizeMb == MemCard251Mb)
|
||||
{
|
||||
filename.insert(filename.find_last_of("."), ".251");
|
||||
}
|
||||
|
||||
memorycard = std::make_unique<MemoryCard>(filename, card_index, sizeMb);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/File.h"
|
||||
#include "Common/FileSearch.h"
|
||||
#include "Common/FileUtil.h"
|
||||
@@ -23,8 +24,10 @@
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
|
||||
const int NO_INDEX = -1;
|
||||
static const char* MC_HDR = "MC_SYSTEM_AREA";
|
||||
@@ -121,6 +124,58 @@ int GCMemcardDirectory::LoadGCI(const std::string& file_name, bool current_game_
|
||||
return NO_INDEX;
|
||||
}
|
||||
|
||||
// This is only used by NetPlay but it made sense to put it here to keep the relevant code together
|
||||
std::vector<std::string> GCMemcardDirectory::GetFileNamesForGameID(const std::string& directory,
|
||||
const std::string& game_id)
|
||||
{
|
||||
std::vector<std::string> filenames;
|
||||
|
||||
u32 game_code = 0;
|
||||
if (game_id.length() >= 4 && game_id != "00000000")
|
||||
game_code = BE32(reinterpret_cast<const u8*>(game_id.c_str()));
|
||||
|
||||
std::vector<std::string> loaded_saves;
|
||||
for (const std::string& file_name : Common::DoFileSearch({directory}, {".gci"}))
|
||||
{
|
||||
File::IOFile gci_file(file_name, "rb");
|
||||
if (!gci_file)
|
||||
continue;
|
||||
|
||||
GCIFile gci;
|
||||
gci.m_filename = file_name;
|
||||
gci.m_dirty = false;
|
||||
if (!gci_file.ReadBytes(&gci.m_gci_header, DENTRY_SIZE))
|
||||
continue;
|
||||
|
||||
const std::string gci_filename = gci.m_gci_header.GCI_FileName();
|
||||
if (std::find(loaded_saves.begin(), loaded_saves.end(), gci_filename) != loaded_saves.end())
|
||||
continue;
|
||||
|
||||
const u16 num_blocks = 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 (num_blocks > 2043)
|
||||
continue;
|
||||
|
||||
const u32 size = num_blocks * BLOCK_SIZE;
|
||||
const u64 file_size = gci_file.GetSize();
|
||||
if (file_size != size + DENTRY_SIZE)
|
||||
continue;
|
||||
|
||||
// There's technically other available block checks to prevent overfilling the virtual memory
|
||||
// card (see above method), but since we're only loading the saves for one GameID here, we're
|
||||
// definitely not going to run out of space.
|
||||
|
||||
if (game_code == BE32(gci.m_gci_header.Gamecode))
|
||||
{
|
||||
loaded_saves.push_back(gci_filename);
|
||||
filenames.push_back(file_name);
|
||||
}
|
||||
}
|
||||
|
||||
return filenames;
|
||||
}
|
||||
|
||||
GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, u16 size_mbits,
|
||||
bool shift_jis, int game_id)
|
||||
: MemoryCardBase(slot, size_mbits), m_game_id(game_id), m_last_block(-1),
|
||||
@@ -151,7 +206,8 @@ GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, u
|
||||
m_save_directory.c_str());
|
||||
break;
|
||||
}
|
||||
int index = LoadGCI(gci_file, m_saves.size() > 112);
|
||||
int index = LoadGCI(gci_file, m_saves.size() > 112 ||
|
||||
Config::Get(Config::MAIN_GCI_FOLDER_CURRENT_GAME_ONLY));
|
||||
if (index != NO_INDEX)
|
||||
{
|
||||
m_loaded_saves.push_back(m_saves.at(index).m_gci_header.GCI_FileName());
|
||||
@@ -687,8 +743,8 @@ void GCIFile::DoState(PointerWrap& p)
|
||||
void MigrateFromMemcardFile(const std::string& directory_name, int card_index)
|
||||
{
|
||||
File::CreateFullPath(directory_name);
|
||||
std::string ini_memcard = (card_index == 0) ? SConfig::GetInstance().m_strMemoryCardA :
|
||||
SConfig::GetInstance().m_strMemoryCardB;
|
||||
std::string ini_memcard = (card_index == 0) ? Config::Get(Config::MAIN_MEMCARD_A_PATH) :
|
||||
Config::Get(Config::MAIN_MEMCARD_B_PATH);
|
||||
if (File::Exists(ini_memcard))
|
||||
{
|
||||
GCMemcard memcard(ini_memcard.c_str());
|
||||
|
||||
@@ -28,6 +28,8 @@ public:
|
||||
GCMemcardDirectory(GCMemcardDirectory&&) = default;
|
||||
GCMemcardDirectory& operator=(GCMemcardDirectory&&) = default;
|
||||
|
||||
static std::vector<std::string> GetFileNamesForGameID(const std::string& directory,
|
||||
const std::string& game_id);
|
||||
void FlushToFile();
|
||||
void FlushThread();
|
||||
s32 Read(u32 src_address, s32 length, u8* dest_address) override;
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
#include <thread>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/File.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
@@ -71,6 +73,53 @@ MemoryCard::~MemoryCard()
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryCard::CheckPath(std::string& memcardPath, const std::string& gameRegion, bool isSlotA)
|
||||
{
|
||||
std::string ext("." + gameRegion + ".raw");
|
||||
if (memcardPath.empty())
|
||||
{
|
||||
// Use default memcard path if there is no user defined name
|
||||
std::string defaultFilename = isSlotA ? GC_MEMCARDA : GC_MEMCARDB;
|
||||
memcardPath = File::GetUserPath(D_GCUSER_IDX) + defaultFilename + ext;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string filename = memcardPath;
|
||||
std::string region = filename.substr(filename.size() - 7, 3);
|
||||
bool hasregion = false;
|
||||
hasregion |= region.compare(USA_DIR) == 0;
|
||||
hasregion |= region.compare(JAP_DIR) == 0;
|
||||
hasregion |= region.compare(EUR_DIR) == 0;
|
||||
if (!hasregion)
|
||||
{
|
||||
// filename doesn't have region in the extension
|
||||
if (File::Exists(filename))
|
||||
{
|
||||
// If the old file exists we are polite and ask if we should copy it
|
||||
std::string oldFilename = filename;
|
||||
filename.replace(filename.size() - 4, 4, ext);
|
||||
if (PanicYesNoT("Memory Card filename in Slot %c is incorrect\n"
|
||||
"Region not specified\n\n"
|
||||
"Slot %c path was changed to\n"
|
||||
"%s\n"
|
||||
"Would you like to copy the old file to this new location?\n",
|
||||
isSlotA ? 'A' : 'B', isSlotA ? 'A' : 'B', filename.c_str()))
|
||||
{
|
||||
if (!File::Copy(oldFilename, filename))
|
||||
PanicAlertT("Copy failed");
|
||||
}
|
||||
}
|
||||
memcardPath = filename; // Always correct the path!
|
||||
}
|
||||
else if (region.compare(gameRegion) != 0)
|
||||
{
|
||||
// filename has region, but it's not == gameRegion
|
||||
// Just set the correct filename, the EXI Device will create it if it doesn't exist
|
||||
memcardPath = filename.replace(filename.size() - ext.size(), ext.size(), ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryCard::FlushThread()
|
||||
{
|
||||
if (!SConfig::GetInstance().bEnableMemcardSdWriting)
|
||||
|
||||
@@ -19,6 +19,7 @@ class MemoryCard : public MemoryCardBase
|
||||
public:
|
||||
MemoryCard(const std::string& filename, int card_index, u16 size_mbits = MemCard2043Mb);
|
||||
~MemoryCard();
|
||||
static void CheckPath(std::string& memcardPath, const std::string& gameRegion, bool isSlotA);
|
||||
void FlushThread();
|
||||
void MakeDirty();
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/CommonTitles.h"
|
||||
#include "Core/HW/WiiSaveStructs.h"
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
#include "Core/IOS/FS/FileSystem.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
@@ -48,96 +49,6 @@ constexpr Md5 s_md5_blanker{{0x0E, 0x65, 0x37, 0x81, 0x99, 0xBE, 0x45, 0x17, 0xA
|
||||
0x45, 0x1A, 0x57, 0x93}};
|
||||
constexpr u32 s_ng_id = 0x0403AC68;
|
||||
|
||||
enum
|
||||
{
|
||||
BLOCK_SZ = 0x40,
|
||||
ICON_SZ = 0x1200,
|
||||
BNR_SZ = 0x60a0,
|
||||
FULL_BNR_MIN = 0x72a0, // BNR_SZ + 1*ICON_SZ
|
||||
FULL_BNR_MAX = 0xF0A0, // BNR_SZ + 8*ICON_SZ
|
||||
BK_LISTED_SZ = 0x70, // Size before rounding to nearest block
|
||||
SIG_SZ = 0x40,
|
||||
FULL_CERT_SZ = 0x3C0, // SIG_SZ + NG_CERT_SZ + AP_CERT_SZ + 0x80?
|
||||
|
||||
BK_HDR_MAGIC = 0x426B0001,
|
||||
FILE_HDR_MAGIC = 0x03adf17e
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct Header
|
||||
{
|
||||
Common::BigEndianValue<u64> tid;
|
||||
Common::BigEndianValue<u32> banner_size; // (0x72A0 or 0xF0A0, also seen 0xBAA0)
|
||||
u8 permissions;
|
||||
u8 unk1; // maybe permissions is a be16
|
||||
std::array<u8, 0x10> md5; // md5 of plaintext header with md5 blanker applied
|
||||
Common::BigEndianValue<u16> unk2;
|
||||
u8 banner[FULL_BNR_MAX];
|
||||
};
|
||||
static_assert(sizeof(Header) == 0xf0c0, "Header has an incorrect size");
|
||||
|
||||
struct BkHeader
|
||||
{
|
||||
Common::BigEndianValue<u32> size; // 0x00000070
|
||||
// u16 magic; // 'Bk'
|
||||
// u16 magic2; // or version (0x0001)
|
||||
Common::BigEndianValue<u32> magic; // 0x426B0001
|
||||
Common::BigEndianValue<u32> ngid;
|
||||
Common::BigEndianValue<u32> number_of_files;
|
||||
Common::BigEndianValue<u32> size_of_files;
|
||||
Common::BigEndianValue<u32> unk1;
|
||||
Common::BigEndianValue<u32> unk2;
|
||||
Common::BigEndianValue<u32> total_size;
|
||||
std::array<u8, 64> unk3;
|
||||
Common::BigEndianValue<u64> tid;
|
||||
std::array<u8, 6> mac_address;
|
||||
std::array<u8, 0x12> padding;
|
||||
};
|
||||
static_assert(sizeof(BkHeader) == 0x80, "BkHeader has an incorrect size");
|
||||
|
||||
struct FileHDR
|
||||
{
|
||||
Common::BigEndianValue<u32> magic; // 0x03adf17e
|
||||
Common::BigEndianValue<u32> size;
|
||||
u8 permissions;
|
||||
u8 attrib;
|
||||
u8 type; // (1=file, 2=directory)
|
||||
std::array<char, 0x40> name;
|
||||
std::array<u8, 5> padding;
|
||||
std::array<u8, 0x10> iv;
|
||||
std::array<u8, 0x20> unk;
|
||||
};
|
||||
static_assert(sizeof(FileHDR) == 0x80, "FileHDR has an incorrect size");
|
||||
#pragma pack(pop)
|
||||
|
||||
class Storage
|
||||
{
|
||||
public:
|
||||
struct SaveFile
|
||||
{
|
||||
enum class Type : u8
|
||||
{
|
||||
File = 1,
|
||||
Directory = 2,
|
||||
};
|
||||
u8 mode, attributes;
|
||||
Type type;
|
||||
/// File name relative to the title data directory.
|
||||
std::string path;
|
||||
// Only valid for regular (i.e. non-directory) files.
|
||||
Common::Lazy<std::optional<std::vector<u8>>> data;
|
||||
};
|
||||
|
||||
virtual ~Storage() = default;
|
||||
virtual bool SaveExists() { return true; }
|
||||
virtual std::optional<Header> ReadHeader() = 0;
|
||||
virtual std::optional<BkHeader> ReadBkHeader() = 0;
|
||||
virtual std::optional<std::vector<SaveFile>> ReadFiles() = 0;
|
||||
virtual bool WriteHeader(const Header& header) = 0;
|
||||
virtual bool WriteBkHeader(const BkHeader& bk_header) = 0;
|
||||
virtual bool WriteFiles(const std::vector<SaveFile>& files) = 0;
|
||||
};
|
||||
|
||||
void StorageDeleter::operator()(Storage* p) const
|
||||
{
|
||||
delete p;
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Based off of tachtig/twintig http://git.infradead.org/?p=users/segher/wii.git
|
||||
// Copyright 2007,2008 Segher Boessenkool <segher@kernel.crashing.org>
|
||||
// Licensed under the terms of the GNU GPL, version 2
|
||||
// http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Lazy.h"
|
||||
#include "Common/Swap.h"
|
||||
|
||||
namespace WiiSave
|
||||
{
|
||||
enum
|
||||
{
|
||||
BLOCK_SZ = 0x40,
|
||||
ICON_SZ = 0x1200,
|
||||
BNR_SZ = 0x60a0,
|
||||
FULL_BNR_MIN = 0x72a0, // BNR_SZ + 1*ICON_SZ
|
||||
FULL_BNR_MAX = 0xF0A0, // BNR_SZ + 8*ICON_SZ
|
||||
BK_LISTED_SZ = 0x70, // Size before rounding to nearest block
|
||||
SIG_SZ = 0x40,
|
||||
FULL_CERT_SZ = 0x3C0, // SIG_SZ + NG_CERT_SZ + AP_CERT_SZ + 0x80?
|
||||
|
||||
BK_HDR_MAGIC = 0x426B0001,
|
||||
FILE_HDR_MAGIC = 0x03adf17e
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct Header
|
||||
{
|
||||
Common::BigEndianValue<u64> tid;
|
||||
Common::BigEndianValue<u32> banner_size; // (0x72A0 or 0xF0A0, also seen 0xBAA0)
|
||||
u8 permissions;
|
||||
u8 unk1; // maybe permissions is a be16
|
||||
std::array<u8, 0x10> md5; // md5 of plaintext header with md5 blanker applied
|
||||
Common::BigEndianValue<u16> unk2;
|
||||
u8 banner[FULL_BNR_MAX];
|
||||
};
|
||||
static_assert(sizeof(Header) == 0xf0c0, "Header has an incorrect size");
|
||||
|
||||
struct BkHeader
|
||||
{
|
||||
Common::BigEndianValue<u32> size; // 0x00000070
|
||||
// u16 magic; // 'Bk'
|
||||
// u16 magic2; // or version (0x0001)
|
||||
Common::BigEndianValue<u32> magic; // 0x426B0001
|
||||
Common::BigEndianValue<u32> ngid;
|
||||
Common::BigEndianValue<u32> number_of_files;
|
||||
Common::BigEndianValue<u32> size_of_files;
|
||||
Common::BigEndianValue<u32> unk1;
|
||||
Common::BigEndianValue<u32> unk2;
|
||||
Common::BigEndianValue<u32> total_size;
|
||||
std::array<u8, 64> unk3;
|
||||
Common::BigEndianValue<u64> tid;
|
||||
std::array<u8, 6> mac_address;
|
||||
std::array<u8, 0x12> padding;
|
||||
};
|
||||
static_assert(sizeof(BkHeader) == 0x80, "BkHeader has an incorrect size");
|
||||
|
||||
struct FileHDR
|
||||
{
|
||||
Common::BigEndianValue<u32> magic; // 0x03adf17e
|
||||
Common::BigEndianValue<u32> size;
|
||||
u8 permissions;
|
||||
u8 attrib;
|
||||
u8 type; // (1=file, 2=directory)
|
||||
std::array<char, 0x40> name;
|
||||
std::array<u8, 5> padding;
|
||||
std::array<u8, 0x10> iv;
|
||||
std::array<u8, 0x20> unk;
|
||||
};
|
||||
static_assert(sizeof(FileHDR) == 0x80, "FileHDR has an incorrect size");
|
||||
#pragma pack(pop)
|
||||
|
||||
class Storage
|
||||
{
|
||||
public:
|
||||
struct SaveFile
|
||||
{
|
||||
enum class Type : u8
|
||||
{
|
||||
File = 1,
|
||||
Directory = 2,
|
||||
};
|
||||
u8 mode, attributes;
|
||||
Type type;
|
||||
/// File name relative to the title data directory.
|
||||
std::string path;
|
||||
// Only valid for regular (i.e. non-directory) files.
|
||||
Common::Lazy<std::optional<std::vector<u8>>> data;
|
||||
};
|
||||
|
||||
virtual ~Storage() = default;
|
||||
virtual bool SaveExists() { return true; }
|
||||
virtual std::optional<Header> ReadHeader() = 0;
|
||||
virtual std::optional<BkHeader> ReadBkHeader() = 0;
|
||||
virtual std::optional<std::vector<SaveFile>> ReadFiles() = 0;
|
||||
virtual bool WriteHeader(const Header& header) = 0;
|
||||
virtual bool WriteBkHeader(const BkHeader& bk_header) = 0;
|
||||
virtual bool WriteFiles(const std::vector<SaveFile>& files) = 0;
|
||||
};
|
||||
} // namespace WiiSave
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user