Merge pull request #7222 from Techjar/netplay-sync-saves
NetPlay save data synchronization
This commit is contained in:
commit
c141511c87
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -13,18 +13,23 @@
|
|||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <lzo/lzo1x.h>
|
||||
#include <mbedtls/md5.h>
|
||||
|
||||
#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<IOS::HLE::FS::FileSystem> 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<ExpansionInterface::TEXIDevices>(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<IOS::HLE::FS::HostFileSystem>(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<WiiSave::Storage::SaveFile> 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<WiiSave::Storage::SaveFile::Type>(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<MessageId>(NP_MSG_SYNC_SAVE_DATA);
|
||||
response_packet << static_cast<MessageId>(SYNC_SAVE_DATA_SUCCESS);
|
||||
|
||||
Send(response_packet);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sf::Packet response_packet;
|
||||
response_packet << static_cast<MessageId>(NP_MSG_SYNC_SAVE_DATA);
|
||||
response_packet << static_cast<MessageId>(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<u8> in_buffer(NETPLAY_LZO_OUT_LEN);
|
||||
std::vector<u8> 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<std::vector<u8>> NetPlayClient::DecompressPacketIntoBuffer(sf::Packet& packet)
|
||||
{
|
||||
u64 size = Common::PacketReadU64(packet);
|
||||
;
|
||||
|
||||
std::vector<u8> out_buffer(size);
|
||||
|
||||
if (size == 0)
|
||||
return out_buffer;
|
||||
|
||||
std::vector<u8> 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<MessageId>(NP_MSG_TIMEBASE);
|
||||
packet << static_cast<u32>(timebase);
|
||||
packet << static_cast<u32>(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<IOS::HLE::FS::FileSystem> 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<std::mutex> lk(crit_netplay_client);
|
||||
|
|
|
@ -7,10 +7,13 @@
|
|||
#include <SFML/Network/Packet.hpp>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#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<const UICommon::GameFile> 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<std::vector<u8>> 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;
|
||||
};
|
||||
|
|
|
@ -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<u8>;
|
||||
using MessageId = u8;
|
||||
using PlayerId = u8;
|
||||
|
@ -111,8 +132,10 @@ using PadMapping = s8;
|
|||
using PadMappingArray = std::array<PadMapping, 4>;
|
||||
|
||||
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<IOS::HLE::FS::FileSystem> fs);
|
||||
void ClearWiiSyncFS();
|
||||
} // namespace NetPlay
|
||||
|
|
|
@ -9,24 +9,38 @@
|
|||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <lzo/lzo1x.h>
|
||||
|
||||
#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 <sys/socket.h>
|
||||
|
@ -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<std::pair<PlayerId, u64>>& 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<MessageId>(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<u32>(g_netplay_initial_rtc);
|
||||
spac << static_cast<u32>(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<MessageId>(NP_MSG_SYNC_SAVE_DATA);
|
||||
pac << static_cast<MessageId>(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<MessageId>(NP_MSG_SYNC_SAVE_DATA);
|
||||
pac << static_cast<MessageId>(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<MessageId>(NP_MSG_SYNC_SAVE_DATA);
|
||||
pac << static_cast<MessageId>(SYNC_SAVE_DATA_GCI);
|
||||
pac << is_slot_a;
|
||||
|
||||
if (File::IsDirectory(path))
|
||||
{
|
||||
std::vector<std::string> files =
|
||||
GCMemcardDirectory::GetFileNamesForGameID(path + DIR_SEP, game->GetGameID());
|
||||
|
||||
pac << static_cast<u8>(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<u8>(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<MessageId>(NP_MSG_SYNC_SAVE_DATA);
|
||||
pac << static_cast<MessageId>(SYNC_SAVE_DATA_WII);
|
||||
|
||||
if (save->SaveExists())
|
||||
{
|
||||
const std::optional<WiiSave::Header> header = save->ReadHeader();
|
||||
const std::optional<WiiSave::BkHeader> bk_header = save->ReadBkHeader();
|
||||
const std::optional<std::vector<WiiSave::Storage::SaveFile>> 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<u8>(file.type) << file.path;
|
||||
|
||||
if (file.type == WiiSave::Storage::SaveFile::Type::File)
|
||||
{
|
||||
const std::optional<std::vector<u8>>& 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<u8> in_buffer(NETPLAY_LZO_IN_LEN);
|
||||
std::vector<u8> out_buffer(NETPLAY_LZO_OUT_LEN);
|
||||
std::vector<u8> 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<lzo_uint32>(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<u32>(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<u32>(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NetPlayServer::CompressBufferIntoPacket(const std::vector<u8>& in_buffer, sf::Packet& packet)
|
||||
{
|
||||
const u64 size = in_buffer.size();
|
||||
Common::PacketWriteU64(packet, size);
|
||||
|
||||
if (size == 0)
|
||||
return true;
|
||||
|
||||
std::vector<u8> out_buffer(NETPLAY_LZO_OUT_LEN);
|
||||
std::vector<u8> 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<lzo_uint32>(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<u32>(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<u32>(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// called from multiple threads
|
||||
void NetPlayServer::SendToClients(const sf::Packet& packet, const PlayerId skip_pid)
|
||||
{
|
||||
|
|
|
@ -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<u8>& 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<PlayerId, Client> m_players;
|
||||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -12,4 +12,4 @@ void ShutdownWiiRoot();
|
|||
// Initialize or clean up the filesystem contents.
|
||||
void InitializeWiiFileSystemContents();
|
||||
void CleanUpWiiFileSystemContents();
|
||||
}
|
||||
} // namespace Core
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<int>(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<bool> 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<std::string> path = RunOnObject(this, [this, game] {
|
||||
std::optional<std::string> 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<const UICommon::GameFile> NetPlayDialog::FindGameFile(const std::string& game)
|
||||
{
|
||||
std::optional<std::shared_ptr<const UICommon::GameFile>> 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<std::shared_ptr<const UICommon::GameFile>>(nullptr);
|
||||
});
|
||||
if (game_file)
|
||||
return *game_file;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier)
|
||||
{
|
||||
QueueOnObject(this, [this, file_identifier] {
|
||||
|
|
|
@ -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<const UICommon::GameFile> 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;
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
#include <QVBoxLayout>
|
||||
|
||||
#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
|
||||
|
|
Loading…
Reference in New Issue