diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index f93bd67efb..d7327a2a47 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -36,6 +36,7 @@ add_library(common QoSSession.cpp Random.cpp SDCardUtil.cpp + SFMLHelper.cpp SettingsHandler.cpp StringUtil.cpp SymbolDB.cpp diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index 2a38dd721c..9939e0ccd3 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -147,6 +147,7 @@ + @@ -210,6 +211,7 @@ + diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 76793bd9ae..3737a5a1ff 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -68,6 +68,7 @@ + @@ -299,6 +300,7 @@ + diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index 2255b594f5..3022e7c5b6 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -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" diff --git a/Source/Core/Common/SFMLHelper.cpp b/Source/Core/Common/SFMLHelper.cpp new file mode 100644 index 0000000000..367bd93a9e --- /dev/null +++ b/Source/Core/Common/SFMLHelper.cpp @@ -0,0 +1,38 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Common/SFMLHelper.h" + +#include + +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(high) << 32); +} + +void PacketWriteU64(sf::Packet& packet, const u64 value) +{ + packet << static_cast(value) << static_cast(value >> 32); +} +} // namespace Common diff --git a/Source/Core/Common/SFMLHelper.h b/Source/Core/Common/SFMLHelper.h new file mode 100644 index 0000000000..459af6203b --- /dev/null +++ b/Source/Core/Common/SFMLHelper.h @@ -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 +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 diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 8f0c32252b..027c4916a2 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -36,6 +36,12 @@ const ConfigInfo MAIN_MEMCARD_A_PATH{{System::Main, "Core", "Memcar const ConfigInfo MAIN_MEMCARD_B_PATH{{System::Main, "Core", "MemcardBPath"}, ""}; const ConfigInfo MAIN_AGP_CART_A_PATH{{System::Main, "Core", "AgpCartAPath"}, ""}; const ConfigInfo MAIN_AGP_CART_B_PATH{{System::Main, "Core", "AgpCartBPath"}, ""}; +const ConfigInfo MAIN_GCI_FOLDER_A_PATH_OVERRIDE{ + {System::Main, "Core", "GCIFolderAPathOverride"}, ""}; +const ConfigInfo MAIN_GCI_FOLDER_B_PATH_OVERRIDE{ + {System::Main, "Core", "GCIFolderBPathOverride"}, ""}; +const ConfigInfo MAIN_GCI_FOLDER_CURRENT_GAME_ONLY{ + {System::Main, "Core", "GCIFolderCurrentGameOnly"}, false}; const ConfigInfo MAIN_SLOT_A{{System::Main, "Core", "SlotA"}, ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER}; const ConfigInfo MAIN_SLOT_B{{System::Main, "Core", "SlotB"}, diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index e23e96882d..260c16a1bc 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -37,6 +37,9 @@ extern const ConfigInfo MAIN_MEMCARD_A_PATH; extern const ConfigInfo MAIN_MEMCARD_B_PATH; extern const ConfigInfo MAIN_AGP_CART_A_PATH; extern const ConfigInfo MAIN_AGP_CART_B_PATH; +extern const ConfigInfo MAIN_GCI_FOLDER_A_PATH_OVERRIDE; +extern const ConfigInfo MAIN_GCI_FOLDER_B_PATH_OVERRIDE; +extern const ConfigInfo MAIN_GCI_FOLDER_CURRENT_GAME_ONLY; extern const ConfigInfo MAIN_SLOT_A; extern const ConfigInfo MAIN_SLOT_B; extern const ConfigInfo MAIN_SERIAL_PORT_1; diff --git a/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp b/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp index 1d959a286f..4f70d58399 100644 --- a/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp +++ b/Source/Core/Core/ConfigLoaders/NetPlayConfigLoader.cpp @@ -6,7 +6,9 @@ #include +#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 diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index e2747ee415..3eba2b6f6e 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -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; diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index c21b22e009..f461f8698d 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -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 revision); static IniFile LoadGameIni(const std::string& id, std::optional revision); - std::string m_strMemoryCardA; - std::string m_strMemoryCardB; std::string m_strGbaCartA; std::string m_strGbaCartB; ExpansionInterface::TEXIDevices m_EXIDevice[3]; diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index cb9d82584b..0fcf274b9c 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -448,6 +448,7 @@ + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index b60f775010..0e084ca51e 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -1272,6 +1272,9 @@ HW %28Flipper/Hollywood%29 + + HW %28Flipper/Hollywood%29 + DSPCore diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp index c7a57d2373..5d8eeee0ef 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp @@ -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(filename, card_index, sizeMb); } diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp index 38e24eca42..476aae5e3c 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp @@ -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 GCMemcardDirectory::GetFileNamesForGameID(const std::string& directory, + const std::string& game_id) +{ + std::vector filenames; + + u32 game_code = 0; + if (game_id.length() >= 4 && game_id != "00000000") + game_code = BE32(reinterpret_cast(game_id.c_str())); + + std::vector 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()); diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h index 363c326a8a..ce784b8101 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h @@ -28,6 +28,8 @@ public: GCMemcardDirectory(GCMemcardDirectory&&) = default; GCMemcardDirectory& operator=(GCMemcardDirectory&&) = default; + static std::vector 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; diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp index 4981aad225..1093b10d13 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp @@ -12,10 +12,12 @@ #include #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) diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h index 69d664d4bb..0659e33d3a 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h @@ -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(); diff --git a/Source/Core/Core/HW/WiiSave.cpp b/Source/Core/Core/HW/WiiSave.cpp index 67f5340ef8..ddf9eb7f72 100644 --- a/Source/Core/Core/HW/WiiSave.cpp +++ b/Source/Core/Core/HW/WiiSave.cpp @@ -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 tid; - Common::BigEndianValue banner_size; // (0x72A0 or 0xF0A0, also seen 0xBAA0) - u8 permissions; - u8 unk1; // maybe permissions is a be16 - std::array md5; // md5 of plaintext header with md5 blanker applied - Common::BigEndianValue unk2; - u8 banner[FULL_BNR_MAX]; -}; -static_assert(sizeof(Header) == 0xf0c0, "Header has an incorrect size"); - -struct BkHeader -{ - Common::BigEndianValue size; // 0x00000070 - // u16 magic; // 'Bk' - // u16 magic2; // or version (0x0001) - Common::BigEndianValue magic; // 0x426B0001 - Common::BigEndianValue ngid; - Common::BigEndianValue number_of_files; - Common::BigEndianValue size_of_files; - Common::BigEndianValue unk1; - Common::BigEndianValue unk2; - Common::BigEndianValue total_size; - std::array unk3; - Common::BigEndianValue tid; - std::array mac_address; - std::array padding; -}; -static_assert(sizeof(BkHeader) == 0x80, "BkHeader has an incorrect size"); - -struct FileHDR -{ - Common::BigEndianValue magic; // 0x03adf17e - Common::BigEndianValue size; - u8 permissions; - u8 attrib; - u8 type; // (1=file, 2=directory) - std::array name; - std::array padding; - std::array iv; - std::array 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>> data; - }; - - virtual ~Storage() = default; - virtual bool SaveExists() { return true; } - virtual std::optional
ReadHeader() = 0; - virtual std::optional ReadBkHeader() = 0; - virtual std::optional> ReadFiles() = 0; - virtual bool WriteHeader(const Header& header) = 0; - virtual bool WriteBkHeader(const BkHeader& bk_header) = 0; - virtual bool WriteFiles(const std::vector& files) = 0; -}; - void StorageDeleter::operator()(Storage* p) const { delete p; diff --git a/Source/Core/Core/HW/WiiSaveStructs.h b/Source/Core/Core/HW/WiiSaveStructs.h new file mode 100644 index 0000000000..6ef5a38feb --- /dev/null +++ b/Source/Core/Core/HW/WiiSaveStructs.h @@ -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 +// Licensed under the terms of the GNU GPL, version 2 +// http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt + +#pragma once + +#include +#include +#include +#include + +#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 tid; + Common::BigEndianValue banner_size; // (0x72A0 or 0xF0A0, also seen 0xBAA0) + u8 permissions; + u8 unk1; // maybe permissions is a be16 + std::array md5; // md5 of plaintext header with md5 blanker applied + Common::BigEndianValue unk2; + u8 banner[FULL_BNR_MAX]; +}; +static_assert(sizeof(Header) == 0xf0c0, "Header has an incorrect size"); + +struct BkHeader +{ + Common::BigEndianValue size; // 0x00000070 + // u16 magic; // 'Bk' + // u16 magic2; // or version (0x0001) + Common::BigEndianValue magic; // 0x426B0001 + Common::BigEndianValue ngid; + Common::BigEndianValue number_of_files; + Common::BigEndianValue size_of_files; + Common::BigEndianValue unk1; + Common::BigEndianValue unk2; + Common::BigEndianValue total_size; + std::array unk3; + Common::BigEndianValue tid; + std::array mac_address; + std::array padding; +}; +static_assert(sizeof(BkHeader) == 0x80, "BkHeader has an incorrect size"); + +struct FileHDR +{ + Common::BigEndianValue magic; // 0x03adf17e + Common::BigEndianValue size; + u8 permissions; + u8 attrib; + u8 type; // (1=file, 2=directory) + std::array name; + std::array padding; + std::array iv; + std::array 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>> data; + }; + + virtual ~Storage() = default; + virtual bool SaveExists() { return true; } + virtual std::optional
ReadHeader() = 0; + virtual std::optional ReadBkHeader() = 0; + virtual std::optional> ReadFiles() = 0; + virtual bool WriteHeader(const Header& header) = 0; + virtual bool WriteBkHeader(const BkHeader& bk_header) = 0; + virtual bool WriteFiles(const std::vector& files) = 0; +}; +} // namespace WiiSave diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp index 7a32e04410..d779f0a71d 100644 --- a/Source/Core/Core/Movie.cpp +++ b/Source/Core/Core/Movie.cpp @@ -1398,7 +1398,7 @@ void GetSettings() } else { - s_bClearSave = !File::Exists(SConfig::GetInstance().m_strMemoryCardA); + s_bClearSave = !File::Exists(Config::Get(Config::MAIN_MEMCARD_A_PATH)); } s_memcards |= (SConfig::GetInstance().m_EXIDevice[0] == ExpansionInterface::EXIDEVICE_MEMORYCARD || @@ -1491,4 +1491,4 @@ void Shutdown() s_currentInputCount = s_totalInputCount = s_totalFrames = s_tickCountAtLastInput = 0; s_temp_input.clear(); } -}; +} // namespace Movie diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 83a0aa0938..d29b931ae0 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -13,18 +13,23 @@ #include #include #include +#include +#include #include #include "Common/Assert.h" #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" #include "Common/ENetUtil.h" +#include "Common/File.h" #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/MD5.h" #include "Common/MsgHandler.h" +#include "Common/NandPaths.h" #include "Common/QoSSession.h" +#include "Common/SFMLHelper.h" #include "Common/StringUtil.h" #include "Common/Timer.h" #include "Common/Version.h" @@ -34,12 +39,19 @@ #include "Core/HW/SI/SI.h" #include "Core/HW/SI/SI_DeviceGCController.h" #include "Core/HW/Sram.h" +#include "Core/HW/WiiSave.h" +#include "Core/HW/WiiSaveStructs.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HW/WiimoteReal/WiimoteReal.h" +#include "Core/IOS/FS/FileSystem.h" +#include "Core/IOS/FS/HostBackend/FS.h" #include "Core/IOS/USB/Bluetooth/BTEmu.h" +#include "Core/IOS/Uids.h" #include "Core/Movie.h" #include "Core/PowerPC/PowerPC.h" +#include "Core/WiiRoot.h" #include "InputCommon/GCAdapter.h" +#include "UICommon/GameFile.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/VideoConfig.h" @@ -47,6 +59,7 @@ namespace NetPlay { static std::mutex crit_netplay_client; static NetPlayClient* netplay_client = nullptr; +static std::unique_ptr s_wii_sync_fs; // called from ---GUI--- thread NetPlayClient::~NetPlayClient() @@ -467,10 +480,11 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> tmp; m_net_settings.m_EXIDevice[1] = static_cast(tmp); - u32 time_low, time_high; - packet >> time_low; - packet >> time_high; - g_netplay_initial_rtc = time_low | ((u64)time_high << 32); + g_netplay_initial_rtc = Common::PacketReadU64(packet); + + packet >> m_net_settings.m_SyncSaveData; + packet >> m_net_settings.m_SaveDataRegion; + m_net_settings.m_IsHosting = m_dialog->IsHosting(); } m_dialog->OnMsgStartGame(); @@ -553,6 +567,194 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) } break; + case NP_MSG_SYNC_SAVE_DATA: + { + MessageId sub_id; + packet >> sub_id; + + switch (sub_id) + { + case SYNC_SAVE_DATA_NOTIFY: + { + packet >> m_sync_save_data_count; + m_sync_save_data_success_count = 0; + + if (m_sync_save_data_count == 0) + SyncSaveDataResponse(true); + else + m_dialog->AppendChat(GetStringT("Synchronizing save data...")); + } + break; + + case SYNC_SAVE_DATA_RAW: + { + if (m_dialog->IsHosting()) + return 0; + + bool is_slot_a; + std::string region; + bool mc251; + packet >> is_slot_a >> region >> mc251; + + const std::string path = File::GetUserPath(D_GCUSER_IDX) + GC_MEMCARD_NETPLAY + + (is_slot_a ? "A." : "B.") + region + (mc251 ? ".251" : "") + ".raw"; + if (File::Exists(path) && !File::Delete(path)) + { + PanicAlertT("Failed to delete NetPlay memory card. Verify your write permissions."); + SyncSaveDataResponse(false); + return 0; + } + + const bool success = DecompressPacketIntoFile(packet, path); + SyncSaveDataResponse(success); + } + break; + + case SYNC_SAVE_DATA_GCI: + { + if (m_dialog->IsHosting()) + return 0; + + bool is_slot_a; + u8 file_count; + packet >> is_slot_a >> file_count; + + const std::string path = File::GetUserPath(D_GCUSER_IDX) + GC_MEMCARD_NETPLAY DIR_SEP + + StringFromFormat("Card %c", is_slot_a ? 'A' : 'B'); + + if ((File::Exists(path) && !File::DeleteDirRecursively(path + DIR_SEP)) || + !File::CreateFullPath(path + DIR_SEP)) + { + PanicAlertT("Failed to reset NetPlay GCI folder. Verify your write permissions."); + SyncSaveDataResponse(false); + return 0; + } + + for (u8 i = 0; i < file_count; i++) + { + std::string file_name; + packet >> file_name; + + if (!DecompressPacketIntoFile(packet, path + DIR_SEP + file_name)) + { + SyncSaveDataResponse(false); + return 0; + } + } + + SyncSaveDataResponse(true); + } + break; + + case SYNC_SAVE_DATA_WII: + { + if (m_dialog->IsHosting()) + return 0; + + const auto game = m_dialog->FindGameFile(m_selected_game); + if (game == nullptr) + { + SyncSaveDataResponse(true); // whatever, we won't be booting anyways + return 0; + } + + const std::string path = File::GetUserPath(D_USER_IDX) + "Wii" GC_MEMCARD_NETPLAY DIR_SEP; + + if (File::Exists(path) && !File::DeleteDirRecursively(path)) + { + PanicAlertT("Failed to reset NetPlay NAND folder. Verify your write permissions."); + SyncSaveDataResponse(false); + return 0; + } + + auto temp_fs = std::make_unique(path); + temp_fs->CreateDirectory(IOS::PID_KERNEL, IOS::PID_KERNEL, + Common::GetTitleDataPath(game->GetTitleID()), 0, + {IOS::HLE::FS::Mode::ReadWrite, IOS::HLE::FS::Mode::ReadWrite, + IOS::HLE::FS::Mode::ReadWrite}); + auto save = WiiSave::MakeNandStorage(temp_fs.get(), game->GetTitleID()); + + bool exists; + packet >> exists; + if (exists) + { + // Header + WiiSave::Header header; + header.tid = Common::PacketReadU64(packet); + header.banner_size = Common::PacketReadU32(packet); + packet >> header.permissions; + packet >> header.unk1; + for (size_t i = 0; i < header.md5.size(); i++) + packet >> header.md5[i]; + header.unk2 = Common::PacketReadU16(packet); + for (size_t i = 0; i < header.banner_size; i++) + packet >> header.banner[i]; + + // BkHeader + WiiSave::BkHeader bk_header; + bk_header.size = Common::PacketReadU32(packet); + bk_header.magic = Common::PacketReadU32(packet); + bk_header.ngid = Common::PacketReadU32(packet); + bk_header.number_of_files = Common::PacketReadU32(packet); + bk_header.size_of_files = Common::PacketReadU32(packet); + bk_header.unk1 = Common::PacketReadU32(packet); + bk_header.unk2 = Common::PacketReadU32(packet); + bk_header.total_size = Common::PacketReadU32(packet); + for (size_t i = 0; i < bk_header.unk3.size(); i++) + packet >> bk_header.unk3[i]; + bk_header.tid = Common::PacketReadU64(packet); + for (size_t i = 0; i < bk_header.mac_address.size(); i++) + packet >> bk_header.mac_address[i]; + + // Files + std::vector files; + for (u32 i = 0; i < bk_header.number_of_files; i++) + { + WiiSave::Storage::SaveFile file; + packet >> file.mode >> file.attributes; + { + u8 tmp; + packet >> tmp; + file.type = static_cast(tmp); + } + packet >> file.path; + + if (file.type == WiiSave::Storage::SaveFile::Type::File) + { + auto buffer = DecompressPacketIntoBuffer(packet); + if (!buffer) + { + SyncSaveDataResponse(false); + return 0; + } + + file.data = std::move(*buffer); + } + + files.push_back(std::move(file)); + } + + if (!save->WriteHeader(header) || !save->WriteBkHeader(bk_header) || + !save->WriteFiles(files)) + { + PanicAlertT("Failed to write Wii save."); + SyncSaveDataResponse(false); + return 0; + } + } + + SetWiiSyncFS(std::move(temp_fs)); + SyncSaveDataResponse(true); + } + break; + + default: + PanicAlertT("Unknown SYNC_SAVE_DATA message received with id: %d", sub_id); + break; + } + } + break; + case NP_MSG_COMPUTE_MD5: { std::string file_identifier; @@ -895,6 +1097,120 @@ bool NetPlayClient::StartGame(const std::string& path) return true; } +void NetPlayClient::SyncSaveDataResponse(const bool success) +{ + m_dialog->AppendChat(success ? GetStringT("Data received!") : + GetStringT("Error processing data.")); + + if (success) + { + if (++m_sync_save_data_success_count >= m_sync_save_data_count) + { + sf::Packet response_packet; + response_packet << static_cast(NP_MSG_SYNC_SAVE_DATA); + response_packet << static_cast(SYNC_SAVE_DATA_SUCCESS); + + Send(response_packet); + } + } + else + { + sf::Packet response_packet; + response_packet << static_cast(NP_MSG_SYNC_SAVE_DATA); + response_packet << static_cast(SYNC_SAVE_DATA_FAILURE); + + Send(response_packet); + } +} + +bool NetPlayClient::DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path) +{ + u64 file_size = Common::PacketReadU64(packet); + ; + + if (file_size == 0) + return true; + + File::IOFile file(file_path, "wb"); + if (!file) + { + PanicAlertT("Failed to open file \"%s\". Verify your write permissions.", file_path.c_str()); + return false; + } + + std::vector in_buffer(NETPLAY_LZO_OUT_LEN); + std::vector out_buffer(NETPLAY_LZO_IN_LEN); + + while (true) + { + lzo_uint32 cur_len = 0; // number of bytes to read + lzo_uint new_len = 0; // number of bytes to write + + packet >> cur_len; + if (!cur_len) + break; // We reached the end of the data stream + + for (size_t j = 0; j < cur_len; j++) + { + packet >> in_buffer[j]; + } + + if (lzo1x_decompress(in_buffer.data(), cur_len, out_buffer.data(), &new_len, nullptr) != + LZO_E_OK) + { + PanicAlertT("Internal LZO Error - decompression failed"); + return false; + } + + if (!file.WriteBytes(out_buffer.data(), new_len)) + { + PanicAlertT("Error writing file: %s", file_path.c_str()); + return false; + } + } + + return true; +} + +std::optional> NetPlayClient::DecompressPacketIntoBuffer(sf::Packet& packet) +{ + u64 size = Common::PacketReadU64(packet); + ; + + std::vector out_buffer(size); + + if (size == 0) + return out_buffer; + + std::vector in_buffer(NETPLAY_LZO_OUT_LEN); + + lzo_uint i = 0; + while (true) + { + lzo_uint32 cur_len = 0; // number of bytes to read + lzo_uint new_len = 0; // number of bytes to write + + packet >> cur_len; + if (!cur_len) + break; // We reached the end of the data stream + + for (size_t j = 0; j < cur_len; j++) + { + packet >> in_buffer[j]; + } + + if (lzo1x_decompress(in_buffer.data(), cur_len, &out_buffer[i], &new_len, nullptr) != LZO_E_OK) + { + PanicAlertT("Internal LZO Error - decompression failed"); + return {}; + } + + i += new_len; + } + + return out_buffer; +} + // called from ---GUI--- thread bool NetPlayClient::ChangeGame(const std::string&) { @@ -1178,6 +1494,8 @@ bool NetPlayClient::StopGame() // stop game m_dialog->StopGame(); + ClearWiiSyncFS(); + return true; } @@ -1278,8 +1596,7 @@ void NetPlayClient::SendTimeBase() sf::Packet packet; packet << static_cast(NP_MSG_TIMEBASE); - packet << static_cast(timebase); - packet << static_cast(timebase << 32); + Common::PacketWriteU64(packet, timebase); packet << netplay_client->m_timebase_frame; netplay_client->SendAsync(std::move(packet)); @@ -1358,6 +1675,26 @@ const NetSettings& GetNetSettings() return netplay_client->GetNetSettings(); } +IOS::HLE::FS::FileSystem* GetWiiSyncFS() +{ + return s_wii_sync_fs.get(); +} + +void SetWiiSyncFS(std::unique_ptr fs) +{ + s_wii_sync_fs = std::move(fs); +} + +void ClearWiiSyncFS() +{ + // We're just assuming it will always be here because it is + const std::string path = File::GetUserPath(D_USER_IDX) + "Wii" GC_MEMCARD_NETPLAY DIR_SEP; + if (File::Exists(path)) + File::DeleteDirRecursively(path); + + s_wii_sync_fs.reset(); +} + void NetPlay_Enable(NetPlayClient* const np) { std::lock_guard lk(crit_netplay_client); diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 5306c0f369..ad9d3ee971 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -7,10 +7,13 @@ #include #include #include +#include #include +#include #include #include #include + #include "Common/CommonTypes.h" #include "Common/Event.h" #include "Common/SPSCQueue.h" @@ -18,6 +21,11 @@ #include "Core/NetPlayProto.h" #include "InputCommon/GCPadStatus.h" +namespace UICommon +{ +class GameFile; +} + namespace NetPlay { class NetPlayUI @@ -26,6 +34,7 @@ public: virtual ~NetPlayUI() {} virtual void BootGame(const std::string& filename) = 0; virtual void StopGame() = 0; + virtual bool IsHosting() const = 0; virtual void Update() = 0; virtual void AppendChat(const std::string& msg) = 0; @@ -38,8 +47,11 @@ public: virtual void OnConnectionLost() = 0; virtual void OnConnectionError(const std::string& message) = 0; virtual void OnTraversalError(TraversalClient::FailureReason error) = 0; + virtual void OnSaveDataSyncFailure() = 0; + virtual bool IsRecording() = 0; virtual std::string FindGame(const std::string& game) = 0; + virtual std::shared_ptr FindGameFile(const std::string& game) = 0; virtual void ShowMD5Dialog(const std::string& file_identifier) = 0; virtual void SetMD5Progress(int pid, int progress) = 0; virtual void SetMD5Result(int pid, const std::string& result) = 0; @@ -159,6 +171,10 @@ private: void SendStartGamePacket(); void SendStopGamePacket(); + void SyncSaveDataResponse(bool success); + bool DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path); + std::optional> DecompressPacketIntoBuffer(sf::Packet& packet); + void UpdateDevices(); void AddPadStateToPacket(int in_game_pad, const GCPadStatus& np, sf::Packet& packet); void SendWiimoteState(int in_game_pad, const NetWiimote& nw); @@ -184,6 +200,8 @@ private: bool m_should_compute_MD5 = false; Common::Event m_gc_pad_event; Common::Event m_wii_pad_event; + u8 m_sync_save_data_count = 0; + u8 m_sync_save_data_success_count = 0; u32 m_timebase_frame = 0; }; diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index b6eaad0dfa..367af26872 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -9,6 +9,10 @@ #include "Common/CommonTypes.h" #include "Core/HW/EXI/EXI_Device.h" +namespace IOS::HLE::FS +{ +class FileSystem; +} namespace PowerPC { enum class CPUCore; @@ -33,6 +37,9 @@ struct NetSettings bool m_OCEnable; float m_OCFactor; ExpansionInterface::TEXIDevices m_EXIDevice[2]; + bool m_SyncSaveData; + std::string m_SaveDataRegion; + bool m_IsHosting; }; struct NetTraversalConfig @@ -94,6 +101,7 @@ enum NP_MSG_PLAYER_PING_DATA = 0xE2, NP_MSG_SYNC_GC_SRAM = 0xF0, + NP_MSG_SYNC_SAVE_DATA = 0xF1, }; enum @@ -103,6 +111,19 @@ enum CON_ERR_VERSION_MISMATCH = 3 }; +enum +{ + SYNC_SAVE_DATA_NOTIFY = 0, + SYNC_SAVE_DATA_SUCCESS = 1, + SYNC_SAVE_DATA_FAILURE = 2, + SYNC_SAVE_DATA_RAW = 3, + SYNC_SAVE_DATA_GCI = 4, + SYNC_SAVE_DATA_WII = 5 +}; + +constexpr u32 NETPLAY_LZO_IN_LEN = 1024 * 64; +constexpr u32 NETPLAY_LZO_OUT_LEN = NETPLAY_LZO_IN_LEN + (NETPLAY_LZO_IN_LEN / 16) + 64 + 3; + using NetWiimote = std::vector; using MessageId = u8; using PlayerId = u8; @@ -111,8 +132,10 @@ using PadMapping = s8; using PadMappingArray = std::array; bool IsNetPlayRunning(); - // Precondition: A netplay client instance must be present. In other words, // IsNetPlayRunning() must be true before calling this. const NetSettings& GetNetSettings(); +IOS::HLE::FS::FileSystem* GetWiiSyncFS(); +void SetWiiSyncFS(std::unique_ptr fs); +void ClearWiiSyncFS(); } // namespace NetPlay diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 0e9116562f..a884aa2dce 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -9,24 +9,38 @@ #include #include #include +#include #include #include #include #include #include +#include + +#include "Common/CommonPaths.h" #include "Common/ENetUtil.h" +#include "Common/File.h" #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" +#include "Common/SFMLHelper.h" #include "Common/StringUtil.h" #include "Common/UPnP.h" #include "Common/Version.h" +#include "Core/Config/MainSettings.h" #include "Core/Config/NetplaySettings.h" #include "Core/ConfigManager.h" +#include "Core/HW/GCMemcard/GCMemcardDirectory.h" +#include "Core/HW/GCMemcard/GCMemcardRaw.h" #include "Core/HW/Sram.h" +#include "Core/HW/WiiSave.h" +#include "Core/HW/WiiSaveStructs.h" +#include "Core/IOS/FS/FileSystem.h" #include "Core/NetPlayClient.h" //for NetPlayUI +#include "DiscIO/Enums.h" #include "InputCommon/GCPadStatus.h" +#include "UICommon/GameFile.h" #if !defined(_WIN32) #include @@ -650,15 +664,13 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) case NP_MSG_TIMEBASE: { - u32 x, y, frame; - packet >> x; - packet >> y; + u64 timebase = Common::PacketReadU64(packet); + u32 frame; packet >> frame; if (m_desync_detected) break; - u64 timebase = x | ((u64)y << 32); std::vector>& timebases = m_timebase_by_frame[frame]; timebases.emplace_back(player.pid, timebase); if (timebases.size() >= m_players.size()) @@ -737,12 +749,50 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) } break; + case NP_MSG_SYNC_SAVE_DATA: + { + MessageId sub_id; + packet >> sub_id; + + switch (sub_id) + { + case SYNC_SAVE_DATA_SUCCESS: + { + if (m_start_pending) + { + m_save_data_synced_players++; + if (m_save_data_synced_players >= m_players.size() - 1) + { + m_dialog->AppendChat(GetStringT("All players synchronized.")); + StartGame(); + } + } + } + break; + + case SYNC_SAVE_DATA_FAILURE: + { + m_dialog->AppendChat( + StringFromFormat(GetStringT("%s failed to synchronize.").c_str(), player.name.c_str())); + m_dialog->OnSaveDataSyncFailure(); + m_start_pending = false; + } + break; + + default: + PanicAlertT( + "Unknown SYNC_SAVE_DATA message with id:%d received from player:%d Kicking player!", + sub_id, player.pid); + return 1; + } + } + break; + default: PanicAlertT("Unknown message with id:%d received from player:%d Kicking player!", mid, player.pid); // unknown message, kick the client return 1; - break; } return 0; @@ -812,6 +862,27 @@ void NetPlayServer::SetNetSettings(const NetSettings& settings) } // called from ---GUI--- thread +bool NetPlayServer::RequestStartGame() +{ + if (m_settings.m_SyncSaveData && m_players.size() > 1) + { + if (!SyncSaveData()) + { + PanicAlertT("Error synchronizing save data!"); + return false; + } + + m_start_pending = true; + } + else + { + return StartGame(); + } + + return true; +} + +// called from multiple threads bool NetPlayServer::StartGame() { m_timebase_by_frame.clear(); @@ -827,6 +898,9 @@ bool NetPlayServer::StartGame() else g_netplay_initial_rtc = Common::Timer::GetLocalTimeSinceJan1970(); + const std::string region = SConfig::GetDirectoryForRegion( + SConfig::ToGameCubeRegion(m_dialog->FindGameFile(m_selected_game)->GetRegion())); + // tell clients to start game sf::Packet spac; spac << static_cast(NP_MSG_START_GAME); @@ -847,16 +921,321 @@ bool NetPlayServer::StartGame() spac << m_settings.m_ReducePollingRate; spac << m_settings.m_EXIDevice[0]; spac << m_settings.m_EXIDevice[1]; - spac << static_cast(g_netplay_initial_rtc); - spac << static_cast(g_netplay_initial_rtc >> 32); + Common::PacketWriteU64(spac, g_netplay_initial_rtc); + spac << m_settings.m_SyncSaveData; + spac << region; SendAsyncToClients(std::move(spac)); + m_start_pending = false; m_is_running = true; return true; } +// called from ---GUI--- thread +bool NetPlayServer::SyncSaveData() +{ + m_save_data_synced_players = 0; + + u8 save_count = 0; + + constexpr size_t exi_device_count = 2; + for (size_t i = 0; i < exi_device_count; i++) + { + if (m_settings.m_EXIDevice[i] == ExpansionInterface::EXIDEVICE_MEMORYCARD || + SConfig::GetInstance().m_EXIDevice[i] == ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER) + { + save_count++; + } + } + + const auto game = m_dialog->FindGameFile(m_selected_game); + if (game == nullptr) + { + PanicAlertT("Selected game doesn't exist in game list!"); + return false; + } + + bool wii_save = false; + if (m_settings.m_CopyWiiSave && (game->GetPlatform() == DiscIO::Platform::WiiDisc || + game->GetPlatform() == DiscIO::Platform::WiiWAD)) + { + wii_save = true; + save_count++; + } + + { + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_SAVE_DATA); + pac << static_cast(SYNC_SAVE_DATA_NOTIFY); + pac << save_count; + + SendAsyncToClients(std::move(pac)); + } + + if (save_count == 0) + return true; + + const std::string region = + SConfig::GetDirectoryForRegion(SConfig::ToGameCubeRegion(game->GetRegion())); + + for (size_t i = 0; i < exi_device_count; i++) + { + const bool is_slot_a = i == 0; + + if (m_settings.m_EXIDevice[i] == ExpansionInterface::EXIDEVICE_MEMORYCARD) + { + std::string path = is_slot_a ? Config::Get(Config::MAIN_MEMCARD_A_PATH) : + Config::Get(Config::MAIN_MEMCARD_B_PATH); + + MemoryCard::CheckPath(path, region, is_slot_a); + + bool mc251; + IniFile gameIni = SConfig::LoadGameIni(game->GetGameID(), game->GetRevision()); + gameIni.GetOrCreateSection("Core")->Get("MemoryCard251", &mc251, false); + + if (mc251) + path.insert(path.find_last_of('.'), ".251"); + + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_SAVE_DATA); + pac << static_cast(SYNC_SAVE_DATA_RAW); + pac << is_slot_a << region << mc251; + + if (File::Exists(path)) + { + if (!CompressFileIntoPacket(path, pac)) + return false; + } + else + { + // No file, so we'll say the size is 0 + Common::PacketWriteU64(pac, 0); + } + + SendAsyncToClients(std::move(pac)); + } + else if (SConfig::GetInstance().m_EXIDevice[i] == + ExpansionInterface::EXIDEVICE_MEMORYCARDFOLDER) + { + const std::string path = File::GetUserPath(D_GCUSER_IDX) + region + DIR_SEP + + StringFromFormat("Card %c", is_slot_a ? 'A' : 'B'); + + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_SAVE_DATA); + pac << static_cast(SYNC_SAVE_DATA_GCI); + pac << is_slot_a; + + if (File::IsDirectory(path)) + { + std::vector files = + GCMemcardDirectory::GetFileNamesForGameID(path + DIR_SEP, game->GetGameID()); + + pac << static_cast(files.size()); + + for (const std::string& file : files) + { + pac << file.substr(file.find_last_of('/') + 1); + if (!CompressFileIntoPacket(file, pac)) + return false; + } + } + else + { + pac << static_cast(0); + } + + SendAsyncToClients(std::move(pac)); + } + } + + if (wii_save) + { + const auto configured_fs = IOS::HLE::FS::MakeFileSystem(IOS::HLE::FS::Location::Configured); + const auto save = WiiSave::MakeNandStorage(configured_fs.get(), game->GetTitleID()); + + sf::Packet pac; + pac << static_cast(NP_MSG_SYNC_SAVE_DATA); + pac << static_cast(SYNC_SAVE_DATA_WII); + + if (save->SaveExists()) + { + const std::optional header = save->ReadHeader(); + const std::optional bk_header = save->ReadBkHeader(); + const std::optional> files = save->ReadFiles(); + if (!header || !bk_header || !files) + return false; + + pac << true; // save exists + + // Header + Common::PacketWriteU64(pac, header->tid); + pac << header->banner_size << header->permissions << header->unk1; + for (size_t i = 0; i < header->md5.size(); i++) + pac << header->md5[i]; + pac << header->unk2; + for (size_t i = 0; i < header->banner_size; i++) + pac << header->banner[i]; + + // BkHeader + pac << bk_header->size << bk_header->magic << bk_header->ngid << bk_header->number_of_files + << bk_header->size_of_files << bk_header->unk1 << bk_header->unk2 + << bk_header->total_size; + for (size_t i = 0; i < bk_header->unk3.size(); i++) + pac << bk_header->unk3[i]; + Common::PacketWriteU64(pac, bk_header->tid); + for (size_t i = 0; i < bk_header->mac_address.size(); i++) + pac << bk_header->mac_address[i]; + + // Files + for (const WiiSave::Storage::SaveFile& file : *files) + { + pac << file.mode << file.attributes << static_cast(file.type) << file.path; + + if (file.type == WiiSave::Storage::SaveFile::Type::File) + { + const std::optional>& data = *file.data; + if (!data || !CompressBufferIntoPacket(*data, pac)) + return false; + } + } + } + else + { + pac << false; // save does not exist + } + + SendAsyncToClients(std::move(pac)); + } + + return true; +} + +bool NetPlayServer::CompressFileIntoPacket(const std::string& file_path, sf::Packet& packet) +{ + File::IOFile file(file_path, "rb"); + if (!file) + { + PanicAlertT("Failed to open file \"%s\".", file_path.c_str()); + return false; + } + + const u64 size = file.GetSize(); + Common::PacketWriteU64(packet, size); + + if (size == 0) + return true; + + std::vector in_buffer(NETPLAY_LZO_IN_LEN); + std::vector out_buffer(NETPLAY_LZO_OUT_LEN); + std::vector wrkmem(LZO1X_1_MEM_COMPRESS); + + lzo_uint i = 0; + while (true) + { + lzo_uint32 cur_len = 0; // number of bytes to read + lzo_uint out_len = 0; // number of bytes to write + + if ((i + NETPLAY_LZO_IN_LEN) >= size) + { + cur_len = static_cast(size - i); + } + else + { + cur_len = NETPLAY_LZO_IN_LEN; + } + + if (cur_len <= 0) + break; // EOF + + if (!file.ReadBytes(in_buffer.data(), cur_len)) + { + PanicAlertT("Error reading file: %s", file_path.c_str()); + return false; + } + + if (lzo1x_1_compress(in_buffer.data(), cur_len, out_buffer.data(), &out_len, wrkmem.data()) != + LZO_E_OK) + { + PanicAlertT("Internal LZO Error - compression failed"); + return false; + } + + // The size of the data to write is 'out_len' + packet << static_cast(out_len); + for (size_t j = 0; j < out_len; j++) + { + packet << out_buffer[j]; + } + + if (cur_len != NETPLAY_LZO_IN_LEN) + break; + + i += cur_len; + } + + // Mark end of data + packet << static_cast(0); + + return true; +} + +bool NetPlayServer::CompressBufferIntoPacket(const std::vector& in_buffer, sf::Packet& packet) +{ + const u64 size = in_buffer.size(); + Common::PacketWriteU64(packet, size); + + if (size == 0) + return true; + + std::vector out_buffer(NETPLAY_LZO_OUT_LEN); + std::vector wrkmem(LZO1X_1_MEM_COMPRESS); + + lzo_uint i = 0; + while (true) + { + lzo_uint32 cur_len = 0; // number of bytes to read + lzo_uint out_len = 0; // number of bytes to write + + if ((i + NETPLAY_LZO_IN_LEN) >= size) + { + cur_len = static_cast(size - i); + } + else + { + cur_len = NETPLAY_LZO_IN_LEN; + } + + if (cur_len <= 0) + break; // end of buffer + + if (lzo1x_1_compress(&in_buffer[i], cur_len, out_buffer.data(), &out_len, wrkmem.data()) != + LZO_E_OK) + { + PanicAlertT("Internal LZO Error - compression failed"); + return false; + } + + // The size of the data to write is 'out_len' + packet << static_cast(out_len); + for (size_t j = 0; j < out_len; j++) + { + packet << out_buffer[j]; + } + + if (cur_len != NETPLAY_LZO_IN_LEN) + break; + + i += cur_len; + } + + // Mark end of data + packet << static_cast(0); + + return true; +} + // called from multiple threads void NetPlayServer::SendToClients(const sf::Packet& packet, const PlayerId skip_pid) { diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index 1dd01ed7ad..9295cc9d7e 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -41,6 +41,7 @@ public: void SetNetSettings(const NetSettings& settings); bool StartGame(); + bool RequestStartGame(); PadMappingArray GetPadMapping() const; void SetPadMapping(const PadMappingArray& mappings); @@ -78,6 +79,10 @@ private: bool operator==(const Client& other) const { return this == &other; } }; + bool SyncSaveData(); + bool CompressFileIntoPacket(const std::string& file_path, sf::Packet& packet); + bool CompressBufferIntoPacket(const std::vector& in_buffer, sf::Packet& packet); + void SendToClients(const sf::Packet& packet, const PlayerId skip_pid = 0); void Send(ENetPeer* socket, const sf::Packet& packet); unsigned int OnConnect(ENetPeer* socket); @@ -102,6 +107,8 @@ private: unsigned int m_target_buffer_size = 0; PadMappingArray m_pad_map; PadMappingArray m_wiimote_map; + unsigned int m_save_data_synced_players = 0; + bool m_start_pending = false; std::map m_players; diff --git a/Source/Core/Core/WiiRoot.cpp b/Source/Core/Core/WiiRoot.cpp index ede9e51752..ef6cea8fe5 100644 --- a/Source/Core/Core/WiiRoot.cpp +++ b/Source/Core/Core/WiiRoot.cpp @@ -26,10 +26,10 @@ namespace Core { -static std::string s_temp_wii_root; - namespace FS = IOS::HLE::FS; +static std::string s_temp_wii_root; + static void InitializeDeterministicWiiSaves(FS::FileSystem* session_fs) { const u64 title_id = SConfig::GetInstance().GetTitleID(); @@ -52,7 +52,9 @@ static void InitializeDeterministicWiiSaves(FS::FileSystem* session_fs) (Movie::IsMovieActive() && !Movie::IsStartingFromClearSave())) { // Copy the current user's save to the Blank NAND - const auto user_save = WiiSave::MakeNandStorage(configured_fs.get(), title_id); + auto* sync_fs = NetPlay::GetWiiSyncFS(); + const auto user_save = + WiiSave::MakeNandStorage(sync_fs ? sync_fs : configured_fs.get(), title_id); const auto session_save = WiiSave::MakeNandStorage(session_fs, title_id); WiiSave::Copy(user_save.get(), session_save.get()); } @@ -62,13 +64,26 @@ void InitializeWiiRoot(bool use_temporary) { if (use_temporary) { - s_temp_wii_root = File::CreateTempDir(); - if (s_temp_wii_root.empty()) - { - ERROR_LOG(IOS_FS, "Could not create temporary directory"); - return; - } + s_temp_wii_root = File::GetUserPath(D_USER_IDX) + "WiiSession" DIR_SEP; WARN_LOG(IOS_FS, "Using temporary directory %s for minimal Wii FS", s_temp_wii_root.c_str()); + + // If directory exists, make a backup + if (File::Exists(s_temp_wii_root)) + { + const std::string backup_path = + s_temp_wii_root.substr(0, s_temp_wii_root.size() - 1) + ".backup" DIR_SEP; + WARN_LOG(IOS_FS, "Temporary Wii FS directory exists, moving to backup..."); + + // If backup exists, delete it as we don't want a mess + if (File::Exists(backup_path)) + { + WARN_LOG(IOS_FS, "Temporary Wii FS backup directory exists, deleting..."); + File::DeleteDirRecursively(backup_path); + } + + File::CopyDir(s_temp_wii_root, backup_path, true); + } + File::SetUserPath(D_SESSION_WIIROOT_IDX, s_temp_wii_root); } else @@ -148,8 +163,11 @@ void InitializeWiiFileSystemContents() void CleanUpWiiFileSystemContents() { - if (s_temp_wii_root.empty() || !SConfig::GetInstance().bEnableMemcardSdWriting) + if (s_temp_wii_root.empty() || !SConfig::GetInstance().bEnableMemcardSdWriting || + NetPlay::GetWiiSyncFS()) + { return; + } const u64 title_id = SConfig::GetInstance().GetTitleID(); @@ -157,6 +175,16 @@ void CleanUpWiiFileSystemContents() const auto session_save = WiiSave::MakeNandStorage(ios->GetFS().get(), title_id); const auto configured_fs = FS::MakeFileSystem(FS::Location::Configured); + + // FS won't write the save if the directory doesn't exist + const std::string title_path = Common::GetTitleDataPath(title_id); + if (!configured_fs->GetMetadata(IOS::PID_KERNEL, IOS::PID_KERNEL, title_path)) + { + configured_fs->CreateDirectory(IOS::PID_KERNEL, IOS::PID_KERNEL, title_path, 0, + {IOS::HLE::FS::Mode::ReadWrite, IOS::HLE::FS::Mode::ReadWrite, + IOS::HLE::FS::Mode::ReadWrite}); + } + const auto user_save = WiiSave::MakeNandStorage(configured_fs.get(), title_id); const std::string backup_path = diff --git a/Source/Core/Core/WiiRoot.h b/Source/Core/Core/WiiRoot.h index 8aab1dc25e..c62aaa103f 100644 --- a/Source/Core/Core/WiiRoot.h +++ b/Source/Core/Core/WiiRoot.h @@ -12,4 +12,4 @@ void ShutdownWiiRoot(); // Initialize or clean up the filesystem contents. void InitializeWiiFileSystemContents(); void CleanUpWiiFileSystemContents(); -} +} // namespace Core diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index ab773e9b2d..30fdaef58f 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1100,6 +1100,9 @@ bool MainWindow::NetPlayJoin() return false; } + if (Settings::Instance().GetNetPlayServer() != nullptr) + Settings::Instance().GetNetPlayServer()->SetNetPlayUI(m_netplay_dialog); + m_netplay_setup_dialog->close(); m_netplay_dialog->show(nickname, is_traversal); diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index d4cfacb605..ce296acef8 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -86,6 +86,7 @@ void NetPlayDialog::CreateMainLayout() m_buffer_size_box = new QSpinBox; m_save_sd_box = new QCheckBox(tr("Write save/SD data")); m_load_wii_box = new QCheckBox(tr("Load Wii Save")); + m_sync_save_data_box = new QCheckBox(tr("Sync Saves")); m_record_input_box = new QCheckBox(tr("Record inputs")); m_reduce_polling_rate_box = new QCheckBox(tr("Reduce Polling Rate")); m_buffer_label = new QLabel(tr("Buffer:")); @@ -95,6 +96,8 @@ void NetPlayDialog::CreateMainLayout() m_game_button->setDefault(false); m_game_button->setAutoDefault(false); + m_sync_save_data_box->setChecked(true); + auto* default_button = new QAction(tr("Calculate MD5 hash"), m_md5_button); auto* menu = new QMenu(this); @@ -140,6 +143,7 @@ void NetPlayDialog::CreateMainLayout() options_widget->addWidget(m_buffer_size_box); options_widget->addWidget(m_save_sd_box); options_widget->addWidget(m_load_wii_box); + options_widget->addWidget(m_sync_save_data_box); options_widget->addWidget(m_record_input_box); options_widget->addWidget(m_reduce_polling_rate_box); options_widget->addWidget(m_quit_button); @@ -305,9 +309,11 @@ void NetPlayDialog::OnStart() settings.m_ReducePollingRate = m_reduce_polling_rate_box->isChecked(); settings.m_EXIDevice[0] = instance.m_EXIDevice[0]; settings.m_EXIDevice[1] = instance.m_EXIDevice[1]; + settings.m_SyncSaveData = m_sync_save_data_box->isChecked(); Settings::Instance().GetNetPlayServer()->SetNetSettings(settings); - Settings::Instance().GetNetPlayServer()->StartGame(); + if (Settings::Instance().GetNetPlayServer()->RequestStartGame()) + SetOptionsEnabled(false); } void NetPlayDialog::reject() @@ -346,6 +352,7 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal) m_start_button->setHidden(!is_hosting); m_save_sd_box->setHidden(!is_hosting); m_load_wii_box->setHidden(!is_hosting); + m_sync_save_data_box->setHidden(!is_hosting); m_reduce_polling_rate_box->setHidden(!is_hosting); m_buffer_size_box->setHidden(!is_hosting); m_buffer_label->setHidden(!is_hosting); @@ -374,7 +381,6 @@ void NetPlayDialog::UpdateGUI() m_player_count = static_cast(players.size()); - int selection_pid = m_players_list->currentItem() ? m_players_list->currentItem()->data(Qt::UserRole).toInt() : -1; @@ -487,6 +493,11 @@ void NetPlayDialog::StopGame() emit Stop(); } +bool NetPlayDialog::IsHosting() const +{ + return Settings::Instance().GetNetPlayServer() != nullptr; +} + void NetPlayDialog::Update() { QueueOnObject(this, &NetPlayDialog::UpdateGUI); @@ -539,19 +550,23 @@ void NetPlayDialog::GameStatusChanged(bool running) if (!running && !m_got_stop_request) Settings::Instance().GetNetPlayClient()->RequestStopGame(); - QueueOnObject(this, [this, running] { - if (Settings::Instance().GetNetPlayServer() != nullptr) - { - m_start_button->setEnabled(!running); - m_game_button->setEnabled(!running); - m_load_wii_box->setEnabled(!running); - m_save_sd_box->setEnabled(!running); - m_assign_ports_button->setEnabled(!running); - m_reduce_polling_rate_box->setEnabled(!running); - } + QueueOnObject(this, [this, running] { SetOptionsEnabled(!running); }); +} - m_record_input_box->setEnabled(!running); - }); +void NetPlayDialog::SetOptionsEnabled(bool enabled) +{ + if (Settings::Instance().GetNetPlayServer() != nullptr) + { + m_start_button->setEnabled(enabled); + m_game_button->setEnabled(enabled); + m_load_wii_box->setEnabled(enabled); + m_save_sd_box->setEnabled(enabled); + m_sync_save_data_box->setEnabled(enabled); + m_assign_ports_button->setEnabled(enabled); + m_reduce_polling_rate_box->setEnabled(enabled); + } + + m_record_input_box->setEnabled(enabled); } void NetPlayDialog::OnMsgStartGame() @@ -618,6 +633,11 @@ void NetPlayDialog::OnTraversalError(TraversalClient::FailureReason error) }); } +void NetPlayDialog::OnSaveDataSyncFailure() +{ + QueueOnObject(this, [this] { SetOptionsEnabled(true); }); +} + bool NetPlayDialog::IsRecording() { std::optional is_recording = RunOnObject(m_record_input_box, &QCheckBox::isChecked); @@ -628,7 +648,7 @@ bool NetPlayDialog::IsRecording() std::string NetPlayDialog::FindGame(const std::string& game) { - std::optional path = RunOnObject(this, [this, game] { + std::optional path = RunOnObject(this, [this, &game] { for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++) { if (m_game_list_model->GetUniqueIdentifier(i).toStdString() == game) @@ -641,6 +661,22 @@ std::string NetPlayDialog::FindGame(const std::string& game) return std::string(""); } +std::shared_ptr NetPlayDialog::FindGameFile(const std::string& game) +{ + std::optional> game_file = + RunOnObject(this, [this, &game] { + for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++) + { + if (m_game_list_model->GetUniqueIdentifier(i).toStdString() == game) + return m_game_list_model->GetGameFile(i); + } + return static_cast>(nullptr); + }); + if (game_file) + return *game_file; + return nullptr; +} + void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier) { QueueOnObject(this, [this, file_identifier] { diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index 92ea74f38f..1603bc40a8 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -38,6 +38,7 @@ public: // NetPlayUI methods void BootGame(const std::string& filename) override; void StopGame() override; + bool IsHosting() const override; void Update() override; void AppendChat(const std::string& msg) override; @@ -50,8 +51,11 @@ public: void OnConnectionLost() override; void OnConnectionError(const std::string& message) override; void OnTraversalError(TraversalClient::FailureReason error) override; + void OnSaveDataSyncFailure() override; + bool IsRecording() override; std::string FindGame(const std::string& game) override; + std::shared_ptr FindGameFile(const std::string& game) override; void ShowMD5Dialog(const std::string& file_identifier) override; void SetMD5Progress(int pid, int progress) override; void SetMD5Result(int pid, const std::string& result) override; @@ -71,6 +75,7 @@ private: int duration = OSD::Duration::NORMAL); void UpdateGUI(); void GameStatusChanged(bool running); + void SetOptionsEnabled(bool enabled); void SetGame(const QString& game_path); @@ -97,6 +102,7 @@ private: QSpinBox* m_buffer_size_box; QCheckBox* m_save_sd_box; QCheckBox* m_load_wii_box; + QCheckBox* m_sync_save_data_box; QCheckBox* m_record_input_box; QCheckBox* m_reduce_polling_rate_box; QPushButton* m_quit_button; diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index 7b50677cfe..4dff22c4bf 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -16,8 +16,10 @@ #include #include "Common/CommonPaths.h" +#include "Common/Config/Config.h" #include "Common/FileUtil.h" +#include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HW/EXI/EXI.h" @@ -200,8 +202,8 @@ void GameCubePane::OnConfigPressed(int slot) if (other_slot_memcard) { QString path_b = - QFileInfo(QString::fromStdString(slot == 0 ? SConfig::GetInstance().m_strMemoryCardB : - SConfig::GetInstance().m_strMemoryCardA)) + QFileInfo(QString::fromStdString(slot == 0 ? Config::Get(Config::MAIN_MEMCARD_B_PATH) : + Config::Get(Config::MAIN_MEMCARD_A_PATH))) .absoluteFilePath(); if (path_abs == path_b) @@ -216,8 +218,8 @@ void GameCubePane::OnConfigPressed(int slot) if (memcard) { path_old = - QFileInfo(QString::fromStdString(slot == 0 ? SConfig::GetInstance().m_strMemoryCardA : - SConfig::GetInstance().m_strMemoryCardB)) + QFileInfo(QString::fromStdString(slot == 0 ? Config::Get(Config::MAIN_MEMCARD_A_PATH) : + Config::Get(Config::MAIN_MEMCARD_B_PATH))) .absoluteFilePath(); } else @@ -231,11 +233,11 @@ void GameCubePane::OnConfigPressed(int slot) { if (slot == SLOT_A_INDEX) { - SConfig::GetInstance().m_strMemoryCardA = path_abs.toStdString(); + Config::SetBase(Config::MAIN_MEMCARD_A_PATH, path_abs.toStdString()); } else { - SConfig::GetInstance().m_strMemoryCardB = path_abs.toStdString(); + Config::SetBase(Config::MAIN_MEMCARD_B_PATH, path_abs.toStdString()); } } else