NetPlay save data synchronization
This adds the functionality of sending the host's save data (raw memory cards, as well as GCI files and Wii saves with a matching GameID) to all other clients. The data is compressed using LZO1X to greatly reduce its size while keeping compression/decompression fast. Save synchronization is enabled by default, and toggleable with a checkbox in the NetPlay dialog. On clicking start, if the option is enabled, game boot will be delayed until all players have received the save data sent by the host. If any player fails to receive it properly, boot will be cancelled to prevent desyncs.
This commit is contained in:
parent
f5e8af7b6c
commit
4407854e9c
|
@ -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