Make netplay's "same game" check more robust
Instead of comparing the game ID, revision, disc number and name, we can compare a hash of important parts of the disc including all the aforementioned data but also additional data such as the FST. The primary reason why I'm making this change is to let us catch more desyncs before they happen, but this should also fix https://bugs.dolphin-emu.org/issues/12115. As a bonus, the UI can now distinguish the case where a client doesn't have the game at all from the case where a client has the wrong version of the game.
This commit is contained in:
parent
25ebc3c07c
commit
a41166bb37
|
@ -37,6 +37,7 @@ add_library(core
|
||||||
PatchEngine.h
|
PatchEngine.h
|
||||||
State.cpp
|
State.cpp
|
||||||
State.h
|
State.h
|
||||||
|
SyncIdentifier.h
|
||||||
SysConf.cpp
|
SysConf.cpp
|
||||||
SysConf.h
|
SysConf.h
|
||||||
TitleDatabase.cpp
|
TitleDatabase.cpp
|
||||||
|
|
|
@ -709,6 +709,7 @@
|
||||||
<ClInclude Include="PowerPC\PPCTables.h" />
|
<ClInclude Include="PowerPC\PPCTables.h" />
|
||||||
<ClInclude Include="PowerPC\Profiler.h" />
|
<ClInclude Include="PowerPC\Profiler.h" />
|
||||||
<ClInclude Include="State.h" />
|
<ClInclude Include="State.h" />
|
||||||
|
<ClInclude Include="SyncIdentifier.h" />
|
||||||
<ClInclude Include="SysConf.h" />
|
<ClInclude Include="SysConf.h" />
|
||||||
<ClInclude Include="Titles.h" />
|
<ClInclude Include="Titles.h" />
|
||||||
<ClInclude Include="TitleDatabase.h" />
|
<ClInclude Include="TitleDatabase.h" />
|
||||||
|
|
|
@ -1758,6 +1758,7 @@
|
||||||
<ClInclude Include="HW\EXI\BBA\TAP_Win32.h">
|
<ClInclude Include="HW\EXI\BBA\TAP_Win32.h">
|
||||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA</Filter>
|
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="SyncIdentifier.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Text Include="CMakeLists.txt" />
|
<Text Include="CMakeLists.txt" />
|
||||||
|
|
|
@ -53,9 +53,11 @@
|
||||||
#include "Core/IOS/Uids.h"
|
#include "Core/IOS/Uids.h"
|
||||||
#include "Core/Movie.h"
|
#include "Core/Movie.h"
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
|
#include "Core/SyncIdentifier.h"
|
||||||
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
|
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
|
||||||
#include "InputCommon/GCAdapter.h"
|
#include "InputCommon/GCAdapter.h"
|
||||||
#include "InputCommon/InputConfig.h"
|
#include "InputCommon/InputConfig.h"
|
||||||
|
#include "UICommon/GameFile.h"
|
||||||
#include "VideoCommon/OnScreenDisplay.h"
|
#include "VideoCommon/OnScreenDisplay.h"
|
||||||
#include "VideoCommon/VideoConfig.h"
|
#include "VideoCommon/VideoConfig.h"
|
||||||
|
|
||||||
|
@ -284,6 +286,22 @@ bool NetPlayClient::Connect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ReceiveSyncIdentifier(sf::Packet& spac, SyncIdentifier& sync_identifier)
|
||||||
|
{
|
||||||
|
// We use a temporary variable here due to a potential long vs long long mismatch
|
||||||
|
sf::Uint64 dol_elf_size;
|
||||||
|
spac >> dol_elf_size;
|
||||||
|
sync_identifier.dol_elf_size = dol_elf_size;
|
||||||
|
|
||||||
|
spac >> sync_identifier.game_id;
|
||||||
|
spac >> sync_identifier.revision;
|
||||||
|
spac >> sync_identifier.disc_number;
|
||||||
|
spac >> sync_identifier.is_datel;
|
||||||
|
|
||||||
|
for (u8& x : sync_identifier.sync_hash)
|
||||||
|
spac >> x;
|
||||||
|
}
|
||||||
|
|
||||||
// called from ---NETPLAY--- thread
|
// called from ---NETPLAY--- thread
|
||||||
unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||||
{
|
{
|
||||||
|
@ -572,24 +590,25 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||||
|
|
||||||
case NP_MSG_CHANGE_GAME:
|
case NP_MSG_CHANGE_GAME:
|
||||||
{
|
{
|
||||||
|
std::string netplay_name;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lkg(m_crit.game);
|
std::lock_guard<std::recursive_mutex> lkg(m_crit.game);
|
||||||
packet >> m_selected_game;
|
ReceiveSyncIdentifier(packet, m_selected_game);
|
||||||
|
packet >> netplay_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
INFO_LOG(NETPLAY, "Game changed to %s", m_selected_game.c_str());
|
INFO_LOG(NETPLAY, "Game changed to %s", netplay_name.c_str());
|
||||||
|
|
||||||
// update gui
|
// update gui
|
||||||
m_dialog->OnMsgChangeGame(m_selected_game);
|
m_dialog->OnMsgChangeGame(m_selected_game, netplay_name);
|
||||||
|
|
||||||
sf::Packet game_status_packet;
|
sf::Packet game_status_packet;
|
||||||
game_status_packet << static_cast<MessageId>(NP_MSG_GAME_STATUS);
|
game_status_packet << static_cast<MessageId>(NP_MSG_GAME_STATUS);
|
||||||
|
|
||||||
PlayerGameStatus status = m_dialog->FindGame(m_selected_game).empty() ?
|
SyncIdentifierComparison result;
|
||||||
PlayerGameStatus::NotFound :
|
m_dialog->FindGameFile(m_selected_game, &result);
|
||||||
PlayerGameStatus::Ok;
|
|
||||||
|
|
||||||
game_status_packet << static_cast<u32>(status);
|
game_status_packet << static_cast<u32>(result);
|
||||||
Send(game_status_packet);
|
Send(game_status_packet);
|
||||||
|
|
||||||
sf::Packet ipl_status_packet;
|
sf::Packet ipl_status_packet;
|
||||||
|
@ -609,7 +628,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||||
Player& player = m_players[pid];
|
Player& player = m_players[pid];
|
||||||
u32 status;
|
u32 status;
|
||||||
packet >> status;
|
packet >> status;
|
||||||
player.game_status = static_cast<PlayerGameStatus>(status);
|
player.game_status = static_cast<SyncIdentifierComparison>(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_dialog->Update();
|
m_dialog->Update();
|
||||||
|
@ -623,7 +642,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||||
packet >> m_current_game;
|
packet >> m_current_game;
|
||||||
packet >> m_net_settings.m_CPUthread;
|
packet >> m_net_settings.m_CPUthread;
|
||||||
|
|
||||||
INFO_LOG(NETPLAY, "Start of game %s", m_selected_game.c_str());
|
INFO_LOG(NETPLAY, "Start of game %s", m_selected_game.game_id.c_str());
|
||||||
|
|
||||||
{
|
{
|
||||||
std::underlying_type_t<PowerPC::CPUCore> core;
|
std::underlying_type_t<PowerPC::CPUCore> core;
|
||||||
|
@ -1172,10 +1191,10 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||||
|
|
||||||
case NP_MSG_COMPUTE_MD5:
|
case NP_MSG_COMPUTE_MD5:
|
||||||
{
|
{
|
||||||
std::string file_identifier;
|
SyncIdentifier sync_identifier;
|
||||||
packet >> file_identifier;
|
ReceiveSyncIdentifier(packet, sync_identifier);
|
||||||
|
|
||||||
ComputeMD5(file_identifier);
|
ComputeMD5(sync_identifier);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1382,11 +1401,15 @@ void NetPlayClient::GetPlayerList(std::string& list, std::vector<int>& pid_list)
|
||||||
|
|
||||||
switch (player.game_status)
|
switch (player.game_status)
|
||||||
{
|
{
|
||||||
case PlayerGameStatus::Ok:
|
case SyncIdentifierComparison::SameGame:
|
||||||
ss << "ready";
|
ss << "ready";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlayerGameStatus::NotFound:
|
case SyncIdentifierComparison::DifferentVersion:
|
||||||
|
ss << "wrong game version";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SyncIdentifierComparison::DifferentGame:
|
||||||
ss << "game missing";
|
ss << "game missing";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -2286,23 +2309,24 @@ bool NetPlayClient::DoAllPlayersHaveGame()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lkp(m_crit.players);
|
std::lock_guard<std::recursive_mutex> lkp(m_crit.players);
|
||||||
|
|
||||||
return std::all_of(std::begin(m_players), std::end(m_players),
|
return std::all_of(std::begin(m_players), std::end(m_players), [](auto entry) {
|
||||||
[](auto entry) { return entry.second.game_status == PlayerGameStatus::Ok; });
|
return entry.second.game_status == SyncIdentifierComparison::SameGame;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetPlayClient::ComputeMD5(const std::string& file_identifier)
|
void NetPlayClient::ComputeMD5(const SyncIdentifier& sync_identifier)
|
||||||
{
|
{
|
||||||
if (m_should_compute_MD5)
|
if (m_should_compute_MD5)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_dialog->ShowMD5Dialog(file_identifier);
|
m_dialog->ShowMD5Dialog(sync_identifier.game_id);
|
||||||
m_should_compute_MD5 = true;
|
m_should_compute_MD5 = true;
|
||||||
|
|
||||||
std::string file;
|
std::string file;
|
||||||
if (file_identifier == WII_SDCARD)
|
if (sync_identifier == GetSDCardIdentifier())
|
||||||
file = File::GetUserPath(F_WIISDCARD_IDX);
|
file = File::GetUserPath(F_WIISDCARD_IDX);
|
||||||
else
|
else if (auto game = m_dialog->FindGameFile(sync_identifier))
|
||||||
file = m_dialog->FindGame(file_identifier);
|
file = game->GetFilePath();
|
||||||
|
|
||||||
if (file.empty() || !File::Exists(file))
|
if (file.empty() || !File::Exists(file))
|
||||||
{
|
{
|
||||||
|
@ -2348,6 +2372,11 @@ void NetPlayClient::AdjustPadBufferSize(const unsigned int size)
|
||||||
m_dialog->OnPadBufferChanged(size);
|
m_dialog->OnPadBufferChanged(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncIdentifier NetPlayClient::GetSDCardIdentifier()
|
||||||
|
{
|
||||||
|
return SyncIdentifier{{}, "sd", {}, {}, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
bool IsNetPlayRunning()
|
bool IsNetPlayRunning()
|
||||||
{
|
{
|
||||||
return netplay_client != nullptr;
|
return netplay_client != nullptr;
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "Common/SPSCQueue.h"
|
#include "Common/SPSCQueue.h"
|
||||||
#include "Common/TraversalClient.h"
|
#include "Common/TraversalClient.h"
|
||||||
#include "Core/NetPlayProto.h"
|
#include "Core/NetPlayProto.h"
|
||||||
|
#include "Core/SyncIdentifier.h"
|
||||||
#include "InputCommon/GCPadStatus.h"
|
#include "InputCommon/GCPadStatus.h"
|
||||||
|
|
||||||
namespace UICommon
|
namespace UICommon
|
||||||
|
@ -42,7 +43,8 @@ public:
|
||||||
virtual void Update() = 0;
|
virtual void Update() = 0;
|
||||||
virtual void AppendChat(const std::string& msg) = 0;
|
virtual void AppendChat(const std::string& msg) = 0;
|
||||||
|
|
||||||
virtual void OnMsgChangeGame(const std::string& filename) = 0;
|
virtual void OnMsgChangeGame(const SyncIdentifier& sync_identifier,
|
||||||
|
const std::string& netplay_name) = 0;
|
||||||
virtual void OnMsgStartGame() = 0;
|
virtual void OnMsgStartGame() = 0;
|
||||||
virtual void OnMsgStopGame() = 0;
|
virtual void OnMsgStopGame() = 0;
|
||||||
virtual void OnMsgPowerButton() = 0;
|
virtual void OnMsgPowerButton() = 0;
|
||||||
|
@ -59,9 +61,10 @@ public:
|
||||||
virtual void OnGolferChanged(bool is_golfer, const std::string& golfer_name) = 0;
|
virtual void OnGolferChanged(bool is_golfer, const std::string& golfer_name) = 0;
|
||||||
|
|
||||||
virtual bool IsRecording() = 0;
|
virtual bool IsRecording() = 0;
|
||||||
virtual std::string FindGame(const std::string& game) = 0;
|
virtual std::shared_ptr<const UICommon::GameFile>
|
||||||
virtual std::shared_ptr<const UICommon::GameFile> FindGameFile(const std::string& game) = 0;
|
FindGameFile(const SyncIdentifier& sync_identifier,
|
||||||
virtual void ShowMD5Dialog(const std::string& file_identifier) = 0;
|
SyncIdentifierComparison* found = nullptr) = 0;
|
||||||
|
virtual void ShowMD5Dialog(const std::string& title) = 0;
|
||||||
virtual void SetMD5Progress(int pid, int progress) = 0;
|
virtual void SetMD5Progress(int pid, int progress) = 0;
|
||||||
virtual void SetMD5Result(int pid, const std::string& result) = 0;
|
virtual void SetMD5Result(int pid, const std::string& result) = 0;
|
||||||
virtual void AbortMD5() = 0;
|
virtual void AbortMD5() = 0;
|
||||||
|
@ -75,13 +78,6 @@ public:
|
||||||
virtual void SetChunkedProgress(int pid, u64 progress) = 0;
|
virtual void SetChunkedProgress(int pid, u64 progress) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class PlayerGameStatus
|
|
||||||
{
|
|
||||||
Unknown,
|
|
||||||
Ok,
|
|
||||||
NotFound
|
|
||||||
};
|
|
||||||
|
|
||||||
class Player
|
class Player
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -89,7 +85,7 @@ public:
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string revision;
|
std::string revision;
|
||||||
u32 ping;
|
u32 ping;
|
||||||
PlayerGameStatus game_status;
|
SyncIdentifierComparison game_status;
|
||||||
|
|
||||||
bool IsHost() const { return pid == 1; }
|
bool IsHost() const { return pid == 1; }
|
||||||
};
|
};
|
||||||
|
@ -149,6 +145,8 @@ public:
|
||||||
|
|
||||||
void AdjustPadBufferSize(unsigned int size);
|
void AdjustPadBufferSize(unsigned int size);
|
||||||
|
|
||||||
|
static SyncIdentifier GetSDCardIdentifier();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct AsyncQueueEntry
|
struct AsyncQueueEntry
|
||||||
{
|
{
|
||||||
|
@ -182,7 +180,7 @@ protected:
|
||||||
ENetPeer* m_server = nullptr;
|
ENetPeer* m_server = nullptr;
|
||||||
std::thread m_thread;
|
std::thread m_thread;
|
||||||
|
|
||||||
std::string m_selected_game;
|
SyncIdentifier m_selected_game;
|
||||||
Common::Flag m_is_running{false};
|
Common::Flag m_is_running{false};
|
||||||
Common::Flag m_do_loop{true};
|
Common::Flag m_do_loop{true};
|
||||||
|
|
||||||
|
@ -237,7 +235,7 @@ private:
|
||||||
void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
|
void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
|
||||||
void Disconnect();
|
void Disconnect();
|
||||||
bool Connect();
|
bool Connect();
|
||||||
void ComputeMD5(const std::string& file_identifier);
|
void ComputeMD5(const SyncIdentifier& sync_identifier);
|
||||||
void DisplayPlayersPing();
|
void DisplayPlayersPing();
|
||||||
u32 GetPlayersMaxPing() const;
|
u32 GetPlayersMaxPing() const;
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
#include "Core/IOS/IOS.h"
|
#include "Core/IOS/IOS.h"
|
||||||
#include "Core/IOS/Uids.h"
|
#include "Core/IOS/Uids.h"
|
||||||
#include "Core/NetPlayClient.h" //for NetPlayUI
|
#include "Core/NetPlayClient.h" //for NetPlayUI
|
||||||
|
#include "Core/SyncIdentifier.h"
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
|
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
|
||||||
#include "InputCommon/GCPadStatus.h"
|
#include "InputCommon/GCPadStatus.h"
|
||||||
|
@ -182,7 +183,7 @@ void NetPlayServer::SetupIndex()
|
||||||
session.region = Config::Get(Config::NETPLAY_INDEX_REGION);
|
session.region = Config::Get(Config::NETPLAY_INDEX_REGION);
|
||||||
session.has_password = !Config::Get(Config::NETPLAY_INDEX_PASSWORD).empty();
|
session.has_password = !Config::Get(Config::NETPLAY_INDEX_PASSWORD).empty();
|
||||||
session.method = m_traversal_client ? "traversal" : "direct";
|
session.method = m_traversal_client ? "traversal" : "direct";
|
||||||
session.game_id = m_selected_game.empty() ? "UNKNOWN" : m_selected_game;
|
session.game_id = m_selected_game_name.empty() ? "UNKNOWN" : m_selected_game_name;
|
||||||
session.player_count = static_cast<int>(m_players.size());
|
session.player_count = static_cast<int>(m_players.size());
|
||||||
session.in_game = m_is_running;
|
session.in_game = m_is_running;
|
||||||
session.port = GetPort();
|
session.port = GetPort();
|
||||||
|
@ -238,7 +239,7 @@ void NetPlayServer::ThreadFunc()
|
||||||
SendToClients(spac);
|
SendToClients(spac);
|
||||||
|
|
||||||
m_index.SetPlayerCount(static_cast<int>(m_players.size()));
|
m_index.SetPlayerCount(static_cast<int>(m_players.size()));
|
||||||
m_index.SetGame(m_selected_game);
|
m_index.SetGame(m_selected_game_name);
|
||||||
m_index.SetInGame(m_is_running);
|
m_index.SetInGame(m_is_running);
|
||||||
|
|
||||||
m_update_pings = false;
|
m_update_pings = false;
|
||||||
|
@ -348,6 +349,20 @@ void NetPlayServer::ThreadFunc()
|
||||||
}
|
}
|
||||||
} // namespace NetPlay
|
} // namespace NetPlay
|
||||||
|
|
||||||
|
static void SendSyncIdentifier(sf::Packet& spac, const SyncIdentifier& sync_identifier)
|
||||||
|
{
|
||||||
|
// We cast here due to a potential long vs long long mismatch
|
||||||
|
spac << static_cast<sf::Uint64>(sync_identifier.dol_elf_size);
|
||||||
|
|
||||||
|
spac << sync_identifier.game_id;
|
||||||
|
spac << sync_identifier.revision;
|
||||||
|
spac << sync_identifier.disc_number;
|
||||||
|
spac << sync_identifier.is_datel;
|
||||||
|
|
||||||
|
for (const u8& x : sync_identifier.sync_hash)
|
||||||
|
spac << x;
|
||||||
|
}
|
||||||
|
|
||||||
// called from ---NETPLAY--- thread
|
// called from ---NETPLAY--- thread
|
||||||
unsigned int NetPlayServer::OnConnect(ENetPeer* socket, sf::Packet& rpac)
|
unsigned int NetPlayServer::OnConnect(ENetPeer* socket, sf::Packet& rpac)
|
||||||
{
|
{
|
||||||
|
@ -413,11 +428,12 @@ unsigned int NetPlayServer::OnConnect(ENetPeer* socket, sf::Packet& rpac)
|
||||||
Send(player.socket, spac);
|
Send(player.socket, spac);
|
||||||
|
|
||||||
// send new client the selected game
|
// send new client the selected game
|
||||||
if (!m_selected_game.empty())
|
if (!m_selected_game_name.empty())
|
||||||
{
|
{
|
||||||
spac.clear();
|
spac.clear();
|
||||||
spac << static_cast<MessageId>(NP_MSG_CHANGE_GAME);
|
spac << static_cast<MessageId>(NP_MSG_CHANGE_GAME);
|
||||||
spac << m_selected_game;
|
SendSyncIdentifier(spac, m_selected_game_identifier);
|
||||||
|
spac << m_selected_game_name;
|
||||||
Send(player.socket, spac);
|
Send(player.socket, spac);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -913,7 +929,7 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
|
||||||
u32 status;
|
u32 status;
|
||||||
packet >> status;
|
packet >> status;
|
||||||
|
|
||||||
m_players[player.pid].game_status = static_cast<PlayerGameStatus>(status);
|
m_players[player.pid].game_status = static_cast<SyncIdentifierComparison>(status);
|
||||||
|
|
||||||
// send msg to other clients
|
// send msg to other clients
|
||||||
sf::Packet spac;
|
sf::Packet spac;
|
||||||
|
@ -1153,16 +1169,19 @@ void NetPlayServer::SendChatMessage(const std::string& msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// called from ---GUI--- thread
|
// called from ---GUI--- thread
|
||||||
bool NetPlayServer::ChangeGame(const std::string& game)
|
bool NetPlayServer::ChangeGame(const SyncIdentifier& sync_identifier,
|
||||||
|
const std::string& netplay_name)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lkg(m_crit.game);
|
std::lock_guard<std::recursive_mutex> lkg(m_crit.game);
|
||||||
|
|
||||||
m_selected_game = game;
|
m_selected_game_identifier = sync_identifier;
|
||||||
|
m_selected_game_name = netplay_name;
|
||||||
|
|
||||||
// send changed game to clients
|
// send changed game to clients
|
||||||
sf::Packet spac;
|
sf::Packet spac;
|
||||||
spac << static_cast<MessageId>(NP_MSG_CHANGE_GAME);
|
spac << static_cast<MessageId>(NP_MSG_CHANGE_GAME);
|
||||||
spac << game;
|
SendSyncIdentifier(spac, m_selected_game_identifier);
|
||||||
|
spac << m_selected_game_name;
|
||||||
|
|
||||||
SendAsyncToClients(std::move(spac));
|
SendAsyncToClients(std::move(spac));
|
||||||
|
|
||||||
|
@ -1170,11 +1189,11 @@ bool NetPlayServer::ChangeGame(const std::string& game)
|
||||||
}
|
}
|
||||||
|
|
||||||
// called from ---GUI--- thread
|
// called from ---GUI--- thread
|
||||||
bool NetPlayServer::ComputeMD5(const std::string& file_identifier)
|
bool NetPlayServer::ComputeMD5(const SyncIdentifier& sync_identifier)
|
||||||
{
|
{
|
||||||
sf::Packet spac;
|
sf::Packet spac;
|
||||||
spac << static_cast<MessageId>(NP_MSG_COMPUTE_MD5);
|
spac << static_cast<MessageId>(NP_MSG_COMPUTE_MD5);
|
||||||
spac << file_identifier;
|
SendSyncIdentifier(spac, sync_identifier);
|
||||||
|
|
||||||
SendAsyncToClients(std::move(spac));
|
SendAsyncToClients(std::move(spac));
|
||||||
|
|
||||||
|
@ -1260,7 +1279,7 @@ bool NetPlayServer::StartGame()
|
||||||
const sf::Uint64 initial_rtc = GetInitialNetPlayRTC();
|
const sf::Uint64 initial_rtc = GetInitialNetPlayRTC();
|
||||||
|
|
||||||
const std::string region = SConfig::GetDirectoryForRegion(
|
const std::string region = SConfig::GetDirectoryForRegion(
|
||||||
SConfig::ToGameCubeRegion(m_dialog->FindGameFile(m_selected_game)->GetRegion()));
|
SConfig::ToGameCubeRegion(m_dialog->FindGameFile(m_selected_game_identifier)->GetRegion()));
|
||||||
|
|
||||||
// sync GC SRAM with clients
|
// sync GC SRAM with clients
|
||||||
if (!g_SRAM_netplay_initialized)
|
if (!g_SRAM_netplay_initialized)
|
||||||
|
@ -1395,7 +1414,7 @@ bool NetPlayServer::SyncSaveData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto game = m_dialog->FindGameFile(m_selected_game);
|
const auto game = m_dialog->FindGameFile(m_selected_game_identifier);
|
||||||
if (game == nullptr)
|
if (game == nullptr)
|
||||||
{
|
{
|
||||||
PanicAlertT("Selected game doesn't exist in game list!");
|
PanicAlertT("Selected game doesn't exist in game list!");
|
||||||
|
@ -1618,7 +1637,7 @@ bool NetPlayServer::SyncCodes()
|
||||||
m_codes_synced = false;
|
m_codes_synced = false;
|
||||||
|
|
||||||
// Get Game Path
|
// Get Game Path
|
||||||
const auto game = m_dialog->FindGameFile(m_selected_game);
|
const auto game = m_dialog->FindGameFile(m_selected_game_identifier);
|
||||||
if (game == nullptr)
|
if (game == nullptr)
|
||||||
{
|
{
|
||||||
PanicAlertT("Selected game doesn't exist in game list!");
|
PanicAlertT("Selected game doesn't exist in game list!");
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SFML/Network/Packet.hpp>
|
#include <SFML/Network/Packet.hpp>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
@ -14,19 +15,20 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "Common/Event.h"
|
#include "Common/Event.h"
|
||||||
#include "Common/QoSSession.h"
|
#include "Common/QoSSession.h"
|
||||||
#include "Common/SPSCQueue.h"
|
#include "Common/SPSCQueue.h"
|
||||||
#include "Common/Timer.h"
|
#include "Common/Timer.h"
|
||||||
#include "Common/TraversalClient.h"
|
#include "Common/TraversalClient.h"
|
||||||
#include "Core/NetPlayProto.h"
|
#include "Core/NetPlayProto.h"
|
||||||
|
#include "Core/SyncIdentifier.h"
|
||||||
#include "InputCommon/GCPadStatus.h"
|
#include "InputCommon/GCPadStatus.h"
|
||||||
#include "UICommon/NetPlayIndex.h"
|
#include "UICommon/NetPlayIndex.h"
|
||||||
|
|
||||||
namespace NetPlay
|
namespace NetPlay
|
||||||
{
|
{
|
||||||
class NetPlayUI;
|
class NetPlayUI;
|
||||||
enum class PlayerGameStatus;
|
|
||||||
|
|
||||||
class NetPlayServer : public TraversalClientClient
|
class NetPlayServer : public TraversalClientClient
|
||||||
{
|
{
|
||||||
|
@ -43,8 +45,8 @@ public:
|
||||||
const NetTraversalConfig& traversal_config);
|
const NetTraversalConfig& traversal_config);
|
||||||
~NetPlayServer();
|
~NetPlayServer();
|
||||||
|
|
||||||
bool ChangeGame(const std::string& game);
|
bool ChangeGame(const SyncIdentifier& sync_identifier, const std::string& netplay_name);
|
||||||
bool ComputeMD5(const std::string& file_identifier);
|
bool ComputeMD5(const SyncIdentifier& sync_identifier);
|
||||||
bool AbortMD5();
|
bool AbortMD5();
|
||||||
void SendChatMessage(const std::string& msg);
|
void SendChatMessage(const std::string& msg);
|
||||||
|
|
||||||
|
@ -80,7 +82,7 @@ private:
|
||||||
PlayerId pid;
|
PlayerId pid;
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string revision;
|
std::string revision;
|
||||||
PlayerGameStatus game_status;
|
SyncIdentifierComparison game_status;
|
||||||
bool has_ipl_dump;
|
bool has_ipl_dump;
|
||||||
|
|
||||||
ENetPeer* socket;
|
ENetPeer* socket;
|
||||||
|
@ -180,7 +182,8 @@ private:
|
||||||
Common::SPSCQueue<AsyncQueueEntry, false> m_async_queue;
|
Common::SPSCQueue<AsyncQueueEntry, false> m_async_queue;
|
||||||
Common::SPSCQueue<ChunkedDataQueueEntry, false> m_chunked_data_queue;
|
Common::SPSCQueue<ChunkedDataQueueEntry, false> m_chunked_data_queue;
|
||||||
|
|
||||||
std::string m_selected_game;
|
SyncIdentifier m_selected_game_identifier;
|
||||||
|
std::string m_selected_game_name;
|
||||||
std::thread m_thread;
|
std::thread m_thread;
|
||||||
Common::Event m_chunked_data_event;
|
Common::Event m_chunked_data_event;
|
||||||
Common::Event m_chunked_data_complete_event;
|
Common::Event m_chunked_data_complete_event;
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2020 Dolphin Emulator Project
|
||||||
|
// Licensed under GPLv2+
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
namespace NetPlay
|
||||||
|
{
|
||||||
|
struct SyncIdentifier
|
||||||
|
{
|
||||||
|
u64 dol_elf_size;
|
||||||
|
std::string game_id;
|
||||||
|
u16 revision;
|
||||||
|
u8 disc_number;
|
||||||
|
bool is_datel;
|
||||||
|
|
||||||
|
// This hash is intended to be (but is not guaranteed to be):
|
||||||
|
// 1. Identical for discs with no differences that affect netplay/TAS sync
|
||||||
|
// 2. Different for discs with differences that affect netplay/TAS sync
|
||||||
|
// 3. Much faster than hashing the entire disc
|
||||||
|
// The way the hash is calculated may change with updates to Dolphin.
|
||||||
|
std::array<u8, 20> sync_hash;
|
||||||
|
|
||||||
|
bool operator==(const SyncIdentifier& s) const
|
||||||
|
{
|
||||||
|
return std::tie(dol_elf_size, game_id, revision, disc_number, is_datel, sync_hash) ==
|
||||||
|
std::tie(s.dol_elf_size, s.game_id, s.revision, s.disc_number, s.is_datel, s.sync_hash);
|
||||||
|
}
|
||||||
|
bool operator!=(const SyncIdentifier& s) const { return !operator==(s); }
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SyncIdentifierComparison
|
||||||
|
{
|
||||||
|
SameGame,
|
||||||
|
DifferentVersion,
|
||||||
|
DifferentGame,
|
||||||
|
Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace NetPlay
|
|
@ -245,19 +245,26 @@ bool ExportBI2Data(const Volume& volume, const Partition& partition,
|
||||||
return ExportData(volume, partition, 0x440, 0x2000, export_filename);
|
return ExportData(volume, partition, 0x440, 0x2000, export_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<u64> GetApploaderSize(const Volume& volume, const Partition& partition)
|
||||||
|
{
|
||||||
|
constexpr u64 header_size = 0x20;
|
||||||
|
const std::optional<u32> apploader_size = volume.ReadSwapped<u32>(0x2440 + 0x14, partition);
|
||||||
|
const std::optional<u32> trailer_size = volume.ReadSwapped<u32>(0x2440 + 0x18, partition);
|
||||||
|
if (!apploader_size || !trailer_size)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return header_size + *apploader_size + *trailer_size;
|
||||||
|
}
|
||||||
|
|
||||||
bool ExportApploader(const Volume& volume, const Partition& partition,
|
bool ExportApploader(const Volume& volume, const Partition& partition,
|
||||||
const std::string& export_filename)
|
const std::string& export_filename)
|
||||||
{
|
{
|
||||||
if (!IsDisc(volume.GetVolumeType()))
|
if (!IsDisc(volume.GetVolumeType()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::optional<u32> apploader_size = volume.ReadSwapped<u32>(0x2440 + 0x14, partition);
|
const std::optional<u64> apploader_size = GetApploaderSize(volume, partition);
|
||||||
const std::optional<u32> trailer_size = volume.ReadSwapped<u32>(0x2440 + 0x18, partition);
|
if (!apploader_size)
|
||||||
constexpr u32 header_size = 0x20;
|
|
||||||
if (!apploader_size || !trailer_size)
|
|
||||||
return false;
|
return false;
|
||||||
*apploader_size += *trailer_size + header_size;
|
|
||||||
DEBUG_LOG(DISCIO, "Apploader size -> %x", *apploader_size);
|
|
||||||
|
|
||||||
return ExportData(volume, partition, 0x2440, *apploader_size, export_filename);
|
return ExportData(volume, partition, 0x2440, *apploader_size, export_filename);
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ bool ExportHeader(const Volume& volume, const Partition& partition,
|
||||||
const std::string& export_filename);
|
const std::string& export_filename);
|
||||||
bool ExportBI2Data(const Volume& volume, const Partition& partition,
|
bool ExportBI2Data(const Volume& volume, const Partition& partition,
|
||||||
const std::string& export_filename);
|
const std::string& export_filename);
|
||||||
|
std::optional<u64> GetApploaderSize(const Volume& volume, const Partition& partition);
|
||||||
bool ExportApploader(const Volume& volume, const Partition& partition,
|
bool ExportApploader(const Volume& volume, const Partition& partition,
|
||||||
const std::string& export_filename);
|
const std::string& export_filename);
|
||||||
std::optional<u64> GetBootDOLOffset(const Volume& volume, const Partition& partition);
|
std::optional<u64> GetBootDOLOffset(const Volume& volume, const Partition& partition);
|
||||||
|
|
|
@ -9,12 +9,16 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <mbedtls/sha1.h>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
|
||||||
|
#include "Core/IOS/ES/Formats.h"
|
||||||
#include "DiscIO/Blob.h"
|
#include "DiscIO/Blob.h"
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
#include "DiscIO/VolumeDisc.h"
|
#include "DiscIO/VolumeDisc.h"
|
||||||
|
@ -28,6 +32,43 @@ const IOS::ES::TicketReader Volume::INVALID_TICKET{};
|
||||||
const IOS::ES::TMDReader Volume::INVALID_TMD{};
|
const IOS::ES::TMDReader Volume::INVALID_TMD{};
|
||||||
const std::vector<u8> Volume::INVALID_CERT_CHAIN{};
|
const std::vector<u8> Volume::INVALID_CERT_CHAIN{};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void AddToSyncHash(mbedtls_sha1_context* context, const T& data)
|
||||||
|
{
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>);
|
||||||
|
mbedtls_sha1_update_ret(context, reinterpret_cast<const u8*>(&data), sizeof(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Volume::ReadAndAddToSyncHash(mbedtls_sha1_context* context, u64 offset, u64 length,
|
||||||
|
const Partition& partition) const
|
||||||
|
{
|
||||||
|
std::vector<u8> buffer(length);
|
||||||
|
if (Read(offset, length, buffer.data(), partition))
|
||||||
|
mbedtls_sha1_update_ret(context, buffer.data(), buffer.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Volume::AddTMDToSyncHash(mbedtls_sha1_context* context, const Partition& partition) const
|
||||||
|
{
|
||||||
|
// We want to hash some important parts of the TMD, but nothing that changes when fakesigning.
|
||||||
|
// (Fakesigned WADs are very popular, and we don't want people with properly signed WADs to
|
||||||
|
// unnecessarily be at a disadvantage due to most netplay partners having fakesigned WADs.)
|
||||||
|
|
||||||
|
const IOS::ES::TMDReader& tmd = GetTMD(partition);
|
||||||
|
if (!tmd.IsValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddToSyncHash(context, tmd.GetIOSId());
|
||||||
|
AddToSyncHash(context, tmd.GetTitleId());
|
||||||
|
AddToSyncHash(context, tmd.GetTitleFlags());
|
||||||
|
AddToSyncHash(context, tmd.GetGroupId());
|
||||||
|
AddToSyncHash(context, tmd.GetRegion());
|
||||||
|
AddToSyncHash(context, tmd.GetTitleVersion());
|
||||||
|
AddToSyncHash(context, tmd.GetBootIndex());
|
||||||
|
|
||||||
|
for (const IOS::ES::Content& content : tmd.GetContents())
|
||||||
|
AddToSyncHash(context, content);
|
||||||
|
}
|
||||||
|
|
||||||
std::map<Language, std::string> Volume::ReadWiiNames(const std::vector<char16_t>& data)
|
std::map<Language, std::string> Volume::ReadWiiNames(const std::vector<char16_t>& data)
|
||||||
{
|
{
|
||||||
std::map<Language, std::string> names;
|
std::map<Language, std::string> names;
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <mbedtls/sha1.h>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Common/Swap.h"
|
#include "Common/Swap.h"
|
||||||
|
@ -138,6 +140,13 @@ public:
|
||||||
virtual u64 GetRawSize() const = 0;
|
virtual u64 GetRawSize() const = 0;
|
||||||
virtual const BlobReader& GetBlobReader() const = 0;
|
virtual const BlobReader& GetBlobReader() const = 0;
|
||||||
|
|
||||||
|
// This hash is intended to be (but is not guaranteed to be):
|
||||||
|
// 1. Identical for discs with no differences that affect netplay/TAS sync
|
||||||
|
// 2. Different for discs with differences that affect netplay/TAS sync
|
||||||
|
// 3. Much faster than hashing the entire disc
|
||||||
|
// The way the hash is calculated may change with updates to Dolphin.
|
||||||
|
virtual std::array<u8, 20> GetSyncHash() const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
template <u32 N>
|
template <u32 N>
|
||||||
std::string DecodeString(const char (&data)[N]) const
|
std::string DecodeString(const char (&data)[N]) const
|
||||||
|
@ -151,6 +160,10 @@ protected:
|
||||||
return CP1252ToUTF8(string);
|
return CP1252ToUTF8(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ReadAndAddToSyncHash(mbedtls_sha1_context* context, u64 offset, u64 length,
|
||||||
|
const Partition& partition) const;
|
||||||
|
void AddTMDToSyncHash(mbedtls_sha1_context* context, const Partition& partition) const;
|
||||||
|
|
||||||
virtual u32 GetOffsetShift() const { return 0; }
|
virtual u32 GetOffsetShift() const { return 0; }
|
||||||
static std::map<Language, std::string> ReadWiiNames(const std::vector<char16_t>& data);
|
static std::map<Language, std::string> ReadWiiNames(const std::vector<char16_t>& data);
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,17 @@
|
||||||
|
|
||||||
#include "DiscIO/VolumeDisc.h"
|
#include "DiscIO/VolumeDisc.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <mbedtls/sha1.h>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "DiscIO/DiscExtractor.h"
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
|
#include "DiscIO/Filesystem.h"
|
||||||
|
|
||||||
namespace DiscIO
|
namespace DiscIO
|
||||||
{
|
{
|
||||||
|
@ -90,4 +96,35 @@ bool VolumeDisc::IsNKit() const
|
||||||
return ReadSwapped<u32>(0x200, PARTITION_NONE) == NKIT_MAGIC;
|
return ReadSwapped<u32>(0x200, PARTITION_NONE) == NKIT_MAGIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VolumeDisc::AddGamePartitionToSyncHash(mbedtls_sha1_context* context) const
|
||||||
|
{
|
||||||
|
const Partition partition = GetGamePartition();
|
||||||
|
|
||||||
|
// All headers at the beginning of the partition, plus the apploader
|
||||||
|
ReadAndAddToSyncHash(context, 0, 0x2440 + GetApploaderSize(*this, partition).value_or(0),
|
||||||
|
partition);
|
||||||
|
|
||||||
|
// Boot DOL (may be missing if this is a Datel disc)
|
||||||
|
const std::optional<u64> dol_offset = GetBootDOLOffset(*this, partition);
|
||||||
|
if (dol_offset)
|
||||||
|
{
|
||||||
|
ReadAndAddToSyncHash(context, *dol_offset,
|
||||||
|
GetBootDOLSize(*this, partition, *dol_offset).value_or(0), partition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// File system
|
||||||
|
const std::optional<u64> fst_offset = GetFSTOffset(*this, partition);
|
||||||
|
if (fst_offset)
|
||||||
|
ReadAndAddToSyncHash(context, *fst_offset, GetFSTSize(*this, partition).value_or(0), partition);
|
||||||
|
|
||||||
|
// opening.bnr (name and banner)
|
||||||
|
const FileSystem* file_system = GetFileSystem(partition);
|
||||||
|
if (file_system)
|
||||||
|
{
|
||||||
|
std::unique_ptr<FileInfo> file_info = file_system->FindFileInfo("opening.bnr");
|
||||||
|
if (file_info && !file_info->IsDirectory())
|
||||||
|
ReadAndAddToSyncHash(context, file_info->GetOffset(), file_info->GetSize(), partition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace DiscIO
|
} // namespace DiscIO
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <mbedtls/sha1.h>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "DiscIO/Volume.h"
|
#include "DiscIO/Volume.h"
|
||||||
|
|
||||||
|
@ -26,6 +28,7 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Region RegionCodeToRegion(std::optional<u32> region_code) const;
|
Region RegionCodeToRegion(std::optional<u32> region_code) const;
|
||||||
|
void AddGamePartitionToSyncHash(mbedtls_sha1_context* context) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace DiscIO
|
} // namespace DiscIO
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <mbedtls/sha1.h>
|
||||||
|
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
#include "Common/ColorUtil.h"
|
#include "Common/ColorUtil.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
@ -137,6 +139,19 @@ bool VolumeGC::IsDatelDisc() const
|
||||||
return !GetBootDOLOffset(*this, PARTITION_NONE).has_value();
|
return !GetBootDOLOffset(*this, PARTITION_NONE).has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::array<u8, 20> VolumeGC::GetSyncHash() const
|
||||||
|
{
|
||||||
|
mbedtls_sha1_context context;
|
||||||
|
mbedtls_sha1_init(&context);
|
||||||
|
mbedtls_sha1_starts_ret(&context);
|
||||||
|
|
||||||
|
AddGamePartitionToSyncHash(&context);
|
||||||
|
|
||||||
|
std::array<u8, 20> hash;
|
||||||
|
mbedtls_sha1_finish_ret(&context, hash.data());
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
VolumeGC::ConvertedGCBanner VolumeGC::LoadBannerFile() const
|
VolumeGC::ConvertedGCBanner VolumeGC::LoadBannerFile() const
|
||||||
{
|
{
|
||||||
GCBanner banner_file;
|
GCBanner banner_file;
|
||||||
|
|
|
@ -51,6 +51,8 @@ public:
|
||||||
u64 GetRawSize() const override;
|
u64 GetRawSize() const override;
|
||||||
const BlobReader& GetBlobReader() const override;
|
const BlobReader& GetBlobReader() const override;
|
||||||
|
|
||||||
|
std::array<u8, 20> GetSyncHash() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const u32 GC_BANNER_WIDTH = 96;
|
static const u32 GC_BANNER_WIDTH = 96;
|
||||||
static const u32 GC_BANNER_HEIGHT = 32;
|
static const u32 GC_BANNER_HEIGHT = 32;
|
||||||
|
|
|
@ -40,6 +40,7 @@ VolumeWAD::VolumeWAD(std::unique_ptr<BlobReader> reader) : m_reader(std::move(re
|
||||||
m_ticket_size = m_reader->ReadSwapped<u32>(0x10).value_or(0);
|
m_ticket_size = m_reader->ReadSwapped<u32>(0x10).value_or(0);
|
||||||
m_tmd_size = m_reader->ReadSwapped<u32>(0x14).value_or(0);
|
m_tmd_size = m_reader->ReadSwapped<u32>(0x14).value_or(0);
|
||||||
m_data_size = m_reader->ReadSwapped<u32>(0x18).value_or(0);
|
m_data_size = m_reader->ReadSwapped<u32>(0x18).value_or(0);
|
||||||
|
m_opening_bnr_size = m_reader->ReadSwapped<u32>(0x1C).value_or(0);
|
||||||
|
|
||||||
m_cert_chain_offset = Common::AlignUp(m_hdr_size, 0x40);
|
m_cert_chain_offset = Common::AlignUp(m_hdr_size, 0x40);
|
||||||
m_ticket_offset = m_cert_chain_offset + Common::AlignUp(m_cert_chain_size, 0x40);
|
m_ticket_offset = m_cert_chain_offset + Common::AlignUp(m_cert_chain_size, 0x40);
|
||||||
|
@ -342,4 +343,22 @@ const BlobReader& VolumeWAD::GetBlobReader() const
|
||||||
return *m_reader;
|
return *m_reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::array<u8, 20> VolumeWAD::GetSyncHash() const
|
||||||
|
{
|
||||||
|
// We can skip hashing the contents since the TMD contains hashes of the contents.
|
||||||
|
// We specifically don't hash the ticket, since its console ID can differ without any problems.
|
||||||
|
|
||||||
|
mbedtls_sha1_context context;
|
||||||
|
mbedtls_sha1_init(&context);
|
||||||
|
mbedtls_sha1_starts_ret(&context);
|
||||||
|
|
||||||
|
AddTMDToSyncHash(&context, PARTITION_NONE);
|
||||||
|
|
||||||
|
ReadAndAddToSyncHash(&context, m_opening_bnr_offset, m_opening_bnr_size, PARTITION_NONE);
|
||||||
|
|
||||||
|
std::array<u8, 20> hash;
|
||||||
|
mbedtls_sha1_finish_ret(&context, hash.data());
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace DiscIO
|
} // namespace DiscIO
|
||||||
|
|
|
@ -70,6 +70,8 @@ public:
|
||||||
u64 GetRawSize() const override;
|
u64 GetRawSize() const override;
|
||||||
const BlobReader& GetBlobReader() const override;
|
const BlobReader& GetBlobReader() const override;
|
||||||
|
|
||||||
|
std::array<u8, 20> GetSyncHash() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<BlobReader> m_reader;
|
std::unique_ptr<BlobReader> m_reader;
|
||||||
IOS::ES::TicketReader m_ticket;
|
IOS::ES::TicketReader m_ticket;
|
||||||
|
@ -85,6 +87,7 @@ private:
|
||||||
u32 m_ticket_size = 0;
|
u32 m_ticket_size = 0;
|
||||||
u32 m_tmd_size = 0;
|
u32 m_tmd_size = 0;
|
||||||
u32 m_data_size = 0;
|
u32 m_data_size = 0;
|
||||||
|
u32 m_opening_bnr_size = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace DiscIO
|
} // namespace DiscIO
|
||||||
|
|
|
@ -364,6 +364,33 @@ const BlobReader& VolumeWii::GetBlobReader() const
|
||||||
return *m_reader;
|
return *m_reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::array<u8, 20> VolumeWii::GetSyncHash() const
|
||||||
|
{
|
||||||
|
mbedtls_sha1_context context;
|
||||||
|
mbedtls_sha1_init(&context);
|
||||||
|
mbedtls_sha1_starts_ret(&context);
|
||||||
|
|
||||||
|
// Disc header
|
||||||
|
ReadAndAddToSyncHash(&context, 0, 0x80, PARTITION_NONE);
|
||||||
|
|
||||||
|
// Region code
|
||||||
|
ReadAndAddToSyncHash(&context, 0x4E000, 4, PARTITION_NONE);
|
||||||
|
|
||||||
|
// The data offset of the game partition - an important factor for disc drive timings
|
||||||
|
const u64 data_offset = PartitionOffsetToRawOffset(0, GetGamePartition());
|
||||||
|
mbedtls_sha1_update_ret(&context, reinterpret_cast<const u8*>(&data_offset), sizeof(data_offset));
|
||||||
|
|
||||||
|
// TMD
|
||||||
|
AddTMDToSyncHash(&context, GetGamePartition());
|
||||||
|
|
||||||
|
// Game partition contents
|
||||||
|
AddGamePartitionToSyncHash(&context);
|
||||||
|
|
||||||
|
std::array<u8, 20> hash;
|
||||||
|
mbedtls_sha1_finish_ret(&context, hash.data());
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
bool VolumeWii::CheckH3TableIntegrity(const Partition& partition) const
|
bool VolumeWii::CheckH3TableIntegrity(const Partition& partition) const
|
||||||
{
|
{
|
||||||
auto it = m_partitions.find(partition);
|
auto it = m_partitions.find(partition);
|
||||||
|
|
|
@ -92,6 +92,7 @@ public:
|
||||||
bool IsSizeAccurate() const override;
|
bool IsSizeAccurate() const override;
|
||||||
u64 GetRawSize() const override;
|
u64 GetRawSize() const override;
|
||||||
const BlobReader& GetBlobReader() const override;
|
const BlobReader& GetBlobReader() const override;
|
||||||
|
std::array<u8, 20> GetSyncHash() const override;
|
||||||
|
|
||||||
// The in parameter can either contain all the data to begin with,
|
// The in parameter can either contain all the data to begin with,
|
||||||
// or read_function can write data into the in parameter when called.
|
// or read_function can write data into the in parameter when called.
|
||||||
|
|
|
@ -403,9 +403,7 @@ void GameList::ShowContextMenu(const QPoint&)
|
||||||
|
|
||||||
QAction* netplay_host = new QAction(tr("Host with NetPlay"), menu);
|
QAction* netplay_host = new QAction(tr("Host with NetPlay"), menu);
|
||||||
|
|
||||||
connect(netplay_host, &QAction::triggered, [this, game] {
|
connect(netplay_host, &QAction::triggered, [this, game] { emit NetPlayHost(*game); });
|
||||||
emit NetPlayHost(QString::fromStdString(game->GetUniqueIdentifier()));
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, menu, [=](Core::State state) {
|
connect(&Settings::Instance(), &Settings::EmulationStateChanged, menu, [=](Core::State state) {
|
||||||
netplay_host->setEnabled(state == Core::State::Uninitialized);
|
netplay_host->setEnabled(state == Core::State::Uninitialized);
|
||||||
|
|
|
@ -47,7 +47,7 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void GameSelected();
|
void GameSelected();
|
||||||
void NetPlayHost(const QString& game_id);
|
void NetPlayHost(const UICommon::GameFile& game);
|
||||||
void SelectionChanged(std::shared_ptr<const UICommon::GameFile> game_file);
|
void SelectionChanged(std::shared_ptr<const UICommon::GameFile> game_file);
|
||||||
void OpenGeneralSettings();
|
void OpenGeneralSettings();
|
||||||
|
|
||||||
|
|
|
@ -313,16 +313,6 @@ std::shared_ptr<const UICommon::GameFile> GameListModel::GetGameFile(int index)
|
||||||
return m_games[index];
|
return m_games[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
QString GameListModel::GetPath(int index) const
|
|
||||||
{
|
|
||||||
return QString::fromStdString(m_games[index]->GetFilePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GameListModel::GetUniqueIdentifier(int index) const
|
|
||||||
{
|
|
||||||
return QString::fromStdString(m_games[index]->GetUniqueIdentifier());
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameListModel::AddGame(const std::shared_ptr<const UICommon::GameFile>& game)
|
void GameListModel::AddGame(const std::shared_ptr<const UICommon::GameFile>& game)
|
||||||
{
|
{
|
||||||
beginInsertRows(QModelIndex(), m_games.size(), m_games.size());
|
beginInsertRows(QModelIndex(), m_games.size(), m_games.size());
|
||||||
|
|
|
@ -37,10 +37,6 @@ public:
|
||||||
int columnCount(const QModelIndex& parent) const override;
|
int columnCount(const QModelIndex& parent) const override;
|
||||||
|
|
||||||
std::shared_ptr<const UICommon::GameFile> GetGameFile(int index) const;
|
std::shared_ptr<const UICommon::GameFile> GetGameFile(int index) const;
|
||||||
// Path of the game at the specified index.
|
|
||||||
QString GetPath(int index) const;
|
|
||||||
// Unique identifier of the game at the specified index.
|
|
||||||
QString GetUniqueIdentifier(int index) const;
|
|
||||||
bool ShouldDisplayGameListItem(int index) const;
|
bool ShouldDisplayGameListItem(int index) const;
|
||||||
void SetSearchTerm(const QString& term);
|
void SetSearchTerm(const QString& term);
|
||||||
|
|
||||||
|
|
|
@ -1374,7 +1374,7 @@ bool MainWindow::NetPlayJoin()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::NetPlayHost(const QString& game_id)
|
bool MainWindow::NetPlayHost(const UICommon::GameFile& game)
|
||||||
{
|
{
|
||||||
if (Core::IsRunning())
|
if (Core::IsRunning())
|
||||||
{
|
{
|
||||||
|
@ -1419,7 +1419,8 @@ bool MainWindow::NetPlayHost(const QString& game_id)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::Instance().GetNetPlayServer()->ChangeGame(game_id.toStdString());
|
Settings::Instance().GetNetPlayServer()->ChangeGame(game.GetSyncIdentifier(),
|
||||||
|
game.GetNetPlayName());
|
||||||
|
|
||||||
// Join our local server
|
// Join our local server
|
||||||
return NetPlayJoin();
|
return NetPlayJoin();
|
||||||
|
|
|
@ -157,7 +157,7 @@ private:
|
||||||
|
|
||||||
void NetPlayInit();
|
void NetPlayInit();
|
||||||
bool NetPlayJoin();
|
bool NetPlayJoin();
|
||||||
bool NetPlayHost(const QString& game_id);
|
bool NetPlayHost(const UICommon::GameFile& game);
|
||||||
void NetPlayQuit();
|
void NetPlayQuit();
|
||||||
|
|
||||||
void OnBootGameCubeIPL(DiscIO::Region region);
|
void OnBootGameCubeIPL(DiscIO::Region region);
|
||||||
|
|
|
@ -4,12 +4,15 @@
|
||||||
|
|
||||||
#include "DolphinQt/NetPlay/GameListDialog.h"
|
#include "DolphinQt/NetPlay/GameListDialog.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
#include <QListWidget>
|
#include <QListWidget>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "DolphinQt/GameList/GameListModel.h"
|
#include "DolphinQt/GameList/GameListModel.h"
|
||||||
#include "DolphinQt/Settings.h"
|
#include "DolphinQt/Settings.h"
|
||||||
|
#include "UICommon/GameFile.h"
|
||||||
|
|
||||||
GameListDialog::GameListDialog(QWidget* parent) : QDialog(parent)
|
GameListDialog::GameListDialog(QWidget* parent) : QDialog(parent)
|
||||||
{
|
{
|
||||||
|
@ -35,12 +38,8 @@ void GameListDialog::CreateWidgets()
|
||||||
|
|
||||||
void GameListDialog::ConnectWidgets()
|
void GameListDialog::ConnectWidgets()
|
||||||
{
|
{
|
||||||
connect(m_game_list, &QListWidget::itemSelectionChanged, [this] {
|
connect(m_game_list, &QListWidget::itemSelectionChanged,
|
||||||
int row = m_game_list->currentRow();
|
[this] { m_button_box->setEnabled(m_game_list->currentRow() != -1); });
|
||||||
|
|
||||||
m_button_box->setEnabled(row != -1);
|
|
||||||
m_game_id = m_game_list->currentItem()->text();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_game_list, &QListWidget::itemDoubleClicked, this, &GameListDialog::accept);
|
connect(m_game_list, &QListWidget::itemDoubleClicked, this, &GameListDialog::accept);
|
||||||
connect(m_button_box, &QDialogButtonBox::accepted, this, &GameListDialog::accept);
|
connect(m_button_box, &QDialogButtonBox::accepted, this, &GameListDialog::accept);
|
||||||
|
@ -54,16 +53,20 @@ void GameListDialog::PopulateGameList()
|
||||||
|
|
||||||
for (int i = 0; i < game_list_model->rowCount(QModelIndex()); i++)
|
for (int i = 0; i < game_list_model->rowCount(QModelIndex()); i++)
|
||||||
{
|
{
|
||||||
auto* item = new QListWidgetItem(game_list_model->GetUniqueIdentifier(i));
|
std::shared_ptr<const UICommon::GameFile> game = game_list_model->GetGameFile(i);
|
||||||
|
|
||||||
|
auto* item = new QListWidgetItem(QString::fromStdString(game->GetNetPlayName()));
|
||||||
|
item->setData(Qt::UserRole, QVariant::fromValue(std::move(game)));
|
||||||
m_game_list->addItem(item);
|
m_game_list->addItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_game_list->sortItems();
|
m_game_list->sortItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString& GameListDialog::GetSelectedUniqueID() const
|
const UICommon::GameFile& GameListDialog::GetSelectedGame() const
|
||||||
{
|
{
|
||||||
return m_game_id;
|
auto items = m_game_list->selectedItems();
|
||||||
|
return *items[0]->data(Qt::UserRole).value<std::shared_ptr<const UICommon::GameFile>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameListDialog::exec()
|
int GameListDialog::exec()
|
||||||
|
|
|
@ -11,6 +11,11 @@ class QVBoxLayout;
|
||||||
class QListWidget;
|
class QListWidget;
|
||||||
class QDialogButtonBox;
|
class QDialogButtonBox;
|
||||||
|
|
||||||
|
namespace UICommon
|
||||||
|
{
|
||||||
|
class GameFile;
|
||||||
|
}
|
||||||
|
|
||||||
class GameListDialog : public QDialog
|
class GameListDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -18,7 +23,7 @@ public:
|
||||||
explicit GameListDialog(QWidget* parent);
|
explicit GameListDialog(QWidget* parent);
|
||||||
|
|
||||||
int exec() override;
|
int exec() override;
|
||||||
const QString& GetSelectedUniqueID() const;
|
const UICommon::GameFile& GetSelectedGame() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateWidgets();
|
void CreateWidgets();
|
||||||
|
@ -28,5 +33,4 @@ private:
|
||||||
QVBoxLayout* m_main_layout;
|
QVBoxLayout* m_main_layout;
|
||||||
QListWidget* m_game_list;
|
QListWidget* m_game_list;
|
||||||
QDialogButtonBox* m_button_box;
|
QDialogButtonBox* m_button_box;
|
||||||
QString m_game_id;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <QTableWidget>
|
#include <QTableWidget>
|
||||||
#include <QTextBrowser>
|
#include <QTextBrowser>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "Common/CommonPaths.h"
|
#include "Common/CommonPaths.h"
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/NetPlayServer.h"
|
#include "Core/NetPlayServer.h"
|
||||||
|
#include "Core/SyncIdentifier.h"
|
||||||
|
|
||||||
#include "DolphinQt/GameList/GameListModel.h"
|
#include "DolphinQt/GameList/GameListModel.h"
|
||||||
#include "DolphinQt/NetPlay/ChunkedProgressDialog.h"
|
#include "DolphinQt/NetPlay/ChunkedProgressDialog.h"
|
||||||
|
@ -153,17 +155,19 @@ void NetPlayDialog::CreateMainLayout()
|
||||||
|
|
||||||
m_md5_menu = m_menu_bar->addMenu(tr("Checksum"));
|
m_md5_menu = m_menu_bar->addMenu(tr("Checksum"));
|
||||||
m_md5_menu->addAction(tr("Current game"), this, [this] {
|
m_md5_menu->addAction(tr("Current game"), this, [this] {
|
||||||
Settings::Instance().GetNetPlayServer()->ComputeMD5(m_current_game);
|
Settings::Instance().GetNetPlayServer()->ComputeMD5(m_current_game_identifier);
|
||||||
});
|
});
|
||||||
m_md5_menu->addAction(tr("Other game..."), this, [this] {
|
m_md5_menu->addAction(tr("Other game..."), this, [this] {
|
||||||
GameListDialog gld(this);
|
GameListDialog gld(this);
|
||||||
|
|
||||||
if (gld.exec() != QDialog::Accepted)
|
if (gld.exec() != QDialog::Accepted)
|
||||||
return;
|
return;
|
||||||
Settings::Instance().GetNetPlayServer()->ComputeMD5(gld.GetSelectedUniqueID().toStdString());
|
Settings::Instance().GetNetPlayServer()->ComputeMD5(gld.GetSelectedGame().GetSyncIdentifier());
|
||||||
|
});
|
||||||
|
m_md5_menu->addAction(tr("SD Card"), this, [] {
|
||||||
|
Settings::Instance().GetNetPlayServer()->ComputeMD5(
|
||||||
|
NetPlay::NetPlayClient::GetSDCardIdentifier());
|
||||||
});
|
});
|
||||||
m_md5_menu->addAction(tr("SD Card"), this,
|
|
||||||
[] { Settings::Instance().GetNetPlayServer()->ComputeMD5(WII_SDCARD); });
|
|
||||||
|
|
||||||
m_other_menu = m_menu_bar->addMenu(tr("Other"));
|
m_other_menu = m_menu_bar->addMenu(tr("Other"));
|
||||||
m_record_input_action = m_other_menu->addAction(tr("Record Inputs"));
|
m_record_input_action = m_other_menu->addAction(tr("Record Inputs"));
|
||||||
|
@ -321,9 +325,11 @@ void NetPlayDialog::ConnectWidgets()
|
||||||
GameListDialog gld(this);
|
GameListDialog gld(this);
|
||||||
if (gld.exec() == QDialog::Accepted)
|
if (gld.exec() == QDialog::Accepted)
|
||||||
{
|
{
|
||||||
auto unique_id = gld.GetSelectedUniqueID();
|
const UICommon::GameFile& game = gld.GetSelectedGame();
|
||||||
Settings::Instance().GetNetPlayServer()->ChangeGame(unique_id.toStdString());
|
const std::string netplay_name = game.GetNetPlayName();
|
||||||
Settings::GetQSettings().setValue(QStringLiteral("netplay/hostgame"), unique_id);
|
Settings::Instance().GetNetPlayServer()->ChangeGame(game.GetSyncIdentifier(), netplay_name);
|
||||||
|
Settings::GetQSettings().setValue(QStringLiteral("netplay/hostgame"),
|
||||||
|
QString::fromStdString(netplay_name));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -416,7 +422,7 @@ void NetPlayDialog::OnStart()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto game = FindGameFile(m_current_game);
|
const auto game = FindGameFile(m_current_game_identifier);
|
||||||
if (!game)
|
if (!game)
|
||||||
{
|
{
|
||||||
PanicAlertT("Selected game doesn't exist in game list!");
|
PanicAlertT("Selected game doesn't exist in game list!");
|
||||||
|
@ -583,11 +589,12 @@ void NetPlayDialog::UpdateDiscordPresence()
|
||||||
{
|
{
|
||||||
#ifdef USE_DISCORD_PRESENCE
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
// both m_current_game and m_player_count need to be set for the status to be displayed correctly
|
// both m_current_game and m_player_count need to be set for the status to be displayed correctly
|
||||||
if (m_player_count == 0 || m_current_game.empty())
|
if (m_player_count == 0 || m_current_game_name.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto use_default = [this]() {
|
const auto use_default = [this]() {
|
||||||
Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::Empty, "", m_current_game);
|
Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::Empty, "",
|
||||||
|
m_current_game_name);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Core::IsRunning())
|
if (Core::IsRunning())
|
||||||
|
@ -602,7 +609,8 @@ void NetPlayDialog::UpdateDiscordPresence()
|
||||||
return use_default();
|
return use_default();
|
||||||
|
|
||||||
Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::RoomID,
|
Discord::UpdateDiscordPresence(m_player_count, Discord::SecretType::RoomID,
|
||||||
std::string(host_id.begin(), host_id.end()), m_current_game);
|
std::string(host_id.begin(), host_id.end()),
|
||||||
|
m_current_game_name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -612,7 +620,7 @@ void NetPlayDialog::UpdateDiscordPresence()
|
||||||
|
|
||||||
Discord::UpdateDiscordPresence(
|
Discord::UpdateDiscordPresence(
|
||||||
m_player_count, Discord::SecretType::IPAddress,
|
m_player_count, Discord::SecretType::IPAddress,
|
||||||
Discord::CreateSecretFromIPAddress(*m_external_ip_address, port), m_current_game);
|
Discord::CreateSecretFromIPAddress(*m_external_ip_address, port), m_current_game_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -660,9 +668,10 @@ void NetPlayDialog::UpdateGUI()
|
||||||
return '|' + str + '|';
|
return '|' + str + '|';
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::map<NetPlay::PlayerGameStatus, QString> player_status{
|
static const std::map<NetPlay::SyncIdentifierComparison, QString> player_status{
|
||||||
{NetPlay::PlayerGameStatus::Ok, tr("OK")},
|
{NetPlay::SyncIdentifierComparison::SameGame, tr("OK")},
|
||||||
{NetPlay::PlayerGameStatus::NotFound, tr("Not Found")},
|
{NetPlay::SyncIdentifierComparison::DifferentVersion, tr("Wrong Version")},
|
||||||
|
{NetPlay::SyncIdentifierComparison::DifferentGame, tr("Not Found")},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < m_player_count; i++)
|
for (int i = 0; i < m_player_count; i++)
|
||||||
|
@ -805,15 +814,17 @@ void NetPlayDialog::AppendChat(const std::string& msg)
|
||||||
QApplication::alert(this);
|
QApplication::alert(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetPlayDialog::OnMsgChangeGame(const std::string& title)
|
void NetPlayDialog::OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifier,
|
||||||
|
const std::string& netplay_name)
|
||||||
{
|
{
|
||||||
QString qtitle = QString::fromStdString(title);
|
QString qname = QString::fromStdString(netplay_name);
|
||||||
QueueOnObject(this, [this, qtitle, title] {
|
QueueOnObject(this, [this, qname, netplay_name, &sync_identifier] {
|
||||||
m_game_button->setText(qtitle);
|
m_game_button->setText(qname);
|
||||||
m_current_game = title;
|
m_current_game_identifier = sync_identifier;
|
||||||
|
m_current_game_name = netplay_name;
|
||||||
UpdateDiscordPresence();
|
UpdateDiscordPresence();
|
||||||
});
|
});
|
||||||
DisplayMessage(tr("Game changed to \"%1\"").arg(qtitle), "magenta");
|
DisplayMessage(tr("Game changed to \"%1\"").arg(qname), "magenta");
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetPlayDialog::GameStatusChanged(bool running)
|
void NetPlayDialog::GameStatusChanged(bool running)
|
||||||
|
@ -859,7 +870,12 @@ void NetPlayDialog::OnMsgStartGame()
|
||||||
auto client = Settings::Instance().GetNetPlayClient();
|
auto client = Settings::Instance().GetNetPlayClient();
|
||||||
|
|
||||||
if (client)
|
if (client)
|
||||||
client->StartGame(FindGame(m_current_game));
|
{
|
||||||
|
if (auto game = FindGameFile(m_current_game_identifier))
|
||||||
|
client->StartGame(game->GetFilePath());
|
||||||
|
else
|
||||||
|
PanicAlertT("Selected game doesn't exist in game list!");
|
||||||
|
}
|
||||||
UpdateDiscordPresence();
|
UpdateDiscordPresence();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1017,29 +1033,24 @@ bool NetPlayDialog::IsRecording()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string NetPlayDialog::FindGame(const std::string& game)
|
std::shared_ptr<const UICommon::GameFile>
|
||||||
|
NetPlayDialog::FindGameFile(const NetPlay::SyncIdentifier& sync_identifier,
|
||||||
|
NetPlay::SyncIdentifierComparison* found)
|
||||||
{
|
{
|
||||||
std::optional<std::string> path = RunOnObject(this, [this, &game] {
|
NetPlay::SyncIdentifierComparison temp;
|
||||||
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
|
if (!found)
|
||||||
{
|
found = &temp;
|
||||||
if (m_game_list_model->GetUniqueIdentifier(i).toStdString() == game)
|
|
||||||
return m_game_list_model->GetPath(i).toStdString();
|
*found = NetPlay::SyncIdentifierComparison::DifferentGame;
|
||||||
}
|
|
||||||
return std::string("");
|
|
||||||
});
|
|
||||||
if (path)
|
|
||||||
return *path;
|
|
||||||
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 =
|
std::optional<std::shared_ptr<const UICommon::GameFile>> game_file =
|
||||||
RunOnObject(this, [this, &game] {
|
RunOnObject(this, [this, &sync_identifier, found] {
|
||||||
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
|
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
|
||||||
{
|
{
|
||||||
if (m_game_list_model->GetUniqueIdentifier(i).toStdString() == game)
|
auto game_file = m_game_list_model->GetGameFile(i);
|
||||||
return m_game_list_model->GetGameFile(i);
|
*found = std::min(*found, game_file->CompareSyncIdentifier(sync_identifier));
|
||||||
|
if (*found == NetPlay::SyncIdentifierComparison::SameGame)
|
||||||
|
return game_file;
|
||||||
}
|
}
|
||||||
return static_cast<std::shared_ptr<const UICommon::GameFile>>(nullptr);
|
return static_cast<std::shared_ptr<const UICommon::GameFile>>(nullptr);
|
||||||
});
|
});
|
||||||
|
@ -1126,15 +1137,15 @@ void NetPlayDialog::SaveSettings()
|
||||||
Config::SetBase(Config::NETPLAY_NETWORK_MODE, network_mode);
|
Config::SetBase(Config::NETPLAY_NETWORK_MODE, network_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier)
|
void NetPlayDialog::ShowMD5Dialog(const std::string& title)
|
||||||
{
|
{
|
||||||
QueueOnObject(this, [this, file_identifier] {
|
QueueOnObject(this, [this, title] {
|
||||||
m_md5_menu->setEnabled(false);
|
m_md5_menu->setEnabled(false);
|
||||||
|
|
||||||
if (m_md5_dialog->isVisible())
|
if (m_md5_dialog->isVisible())
|
||||||
m_md5_dialog->close();
|
m_md5_dialog->close();
|
||||||
|
|
||||||
m_md5_dialog->show(QString::fromStdString(file_identifier));
|
m_md5_dialog->show(QString::fromStdString(title));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,8 @@ public:
|
||||||
void Update() override;
|
void Update() override;
|
||||||
void AppendChat(const std::string& msg) override;
|
void AppendChat(const std::string& msg) override;
|
||||||
|
|
||||||
void OnMsgChangeGame(const std::string& filename) override;
|
void OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifier,
|
||||||
|
const std::string& netplay_name) override;
|
||||||
void OnMsgStartGame() override;
|
void OnMsgStartGame() override;
|
||||||
void OnMsgStopGame() override;
|
void OnMsgStopGame() override;
|
||||||
void OnMsgPowerButton() override;
|
void OnMsgPowerButton() override;
|
||||||
|
@ -65,13 +66,14 @@ public:
|
||||||
void OnIndexRefreshFailed(const std::string error) override;
|
void OnIndexRefreshFailed(const std::string error) override;
|
||||||
|
|
||||||
bool IsRecording() override;
|
bool IsRecording() override;
|
||||||
std::string FindGame(const std::string& game) override;
|
std::shared_ptr<const UICommon::GameFile>
|
||||||
std::shared_ptr<const UICommon::GameFile> FindGameFile(const std::string& game) override;
|
FindGameFile(const NetPlay::SyncIdentifier& sync_identifier,
|
||||||
|
NetPlay::SyncIdentifierComparison* found = nullptr) override;
|
||||||
|
|
||||||
void LoadSettings();
|
void LoadSettings();
|
||||||
void SaveSettings();
|
void SaveSettings();
|
||||||
|
|
||||||
void ShowMD5Dialog(const std::string& file_identifier) override;
|
void ShowMD5Dialog(const std::string& title) override;
|
||||||
void SetMD5Progress(int pid, int progress) override;
|
void SetMD5Progress(int pid, int progress) override;
|
||||||
void SetMD5Result(int pid, const std::string& result) override;
|
void SetMD5Result(int pid, const std::string& result) override;
|
||||||
void AbortMD5() override;
|
void AbortMD5() override;
|
||||||
|
@ -145,7 +147,8 @@ private:
|
||||||
MD5Dialog* m_md5_dialog;
|
MD5Dialog* m_md5_dialog;
|
||||||
ChunkedProgressDialog* m_chunked_progress_dialog;
|
ChunkedProgressDialog* m_chunked_progress_dialog;
|
||||||
PadMappingDialog* m_pad_mapping;
|
PadMappingDialog* m_pad_mapping;
|
||||||
std::string m_current_game;
|
NetPlay::SyncIdentifier m_current_game_identifier;
|
||||||
|
std::string m_current_game_name;
|
||||||
Common::Lazy<std::string> m_external_ip_address;
|
Common::Lazy<std::string> m_external_ip_address;
|
||||||
std::string m_nickname;
|
std::string m_nickname;
|
||||||
GameListModel* m_game_list_model = nullptr;
|
GameListModel* m_game_list_model = nullptr;
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
#include "DolphinQt/NetPlay/NetPlaySetupDialog.h"
|
#include "DolphinQt/NetPlay/NetPlaySetupDialog.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
|
@ -24,6 +26,7 @@
|
||||||
#include "DolphinQt/QtUtils/UTF8CodePointCountValidator.h"
|
#include "DolphinQt/QtUtils/UTF8CodePointCountValidator.h"
|
||||||
#include "DolphinQt/Settings.h"
|
#include "DolphinQt/Settings.h"
|
||||||
|
|
||||||
|
#include "UICommon/GameFile.h"
|
||||||
#include "UICommon/NetPlayIndex.h"
|
#include "UICommon/NetPlayIndex.h"
|
||||||
|
|
||||||
NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent)
|
NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent)
|
||||||
|
@ -347,7 +350,7 @@ void NetPlaySetupDialog::accept()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit Host(items[0]->text());
|
emit Host(*items[0]->data(Qt::UserRole).value<std::shared_ptr<const UICommon::GameFile>>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,11 +361,10 @@ void NetPlaySetupDialog::PopulateGameList()
|
||||||
m_host_games->clear();
|
m_host_games->clear();
|
||||||
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
|
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
|
||||||
{
|
{
|
||||||
auto title = m_game_list_model->GetUniqueIdentifier(i);
|
std::shared_ptr<const UICommon::GameFile> game = m_game_list_model->GetGameFile(i);
|
||||||
auto path = m_game_list_model->GetPath(i);
|
|
||||||
|
|
||||||
auto* item = new QListWidgetItem(title);
|
auto* item = new QListWidgetItem(QString::fromStdString(game->GetNetPlayName()));
|
||||||
item->setData(Qt::UserRole, path);
|
item->setData(Qt::UserRole, QVariant::fromValue(std::move(game)));
|
||||||
m_host_games->addItem(item);
|
m_host_games->addItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,11 @@ class QPushButton;
|
||||||
class QSpinBox;
|
class QSpinBox;
|
||||||
class QTabWidget;
|
class QTabWidget;
|
||||||
|
|
||||||
|
namespace UICommon
|
||||||
|
{
|
||||||
|
class GameFile;
|
||||||
|
}
|
||||||
|
|
||||||
class NetPlaySetupDialog : public QDialog
|
class NetPlaySetupDialog : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -29,7 +34,7 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
bool Join();
|
bool Join();
|
||||||
bool Host(const QString& game_identifier);
|
bool Host(const UICommon::GameFile& game);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateMainLayout();
|
void CreateMainLayout();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "UICommon/GameFile.h"
|
#include "UICommon/GameFile.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
@ -13,11 +14,13 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
#include <mbedtls/sha1.h>
|
||||||
#include <pugixml.hpp>
|
#include <pugixml.hpp>
|
||||||
|
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
|
@ -532,7 +535,7 @@ std::vector<DiscIO::Language> GameFile::GetLanguages() const
|
||||||
return languages;
|
return languages;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GameFile::GetUniqueIdentifier() const
|
std::string GameFile::GetNetPlayName() const
|
||||||
{
|
{
|
||||||
std::vector<std::string> info;
|
std::vector<std::string> info;
|
||||||
if (!GetGameID().empty())
|
if (!GetGameID().empty())
|
||||||
|
@ -566,6 +569,66 @@ std::string GameFile::GetUniqueIdentifier() const
|
||||||
return name + " (" + ss.str() + ")";
|
return name + " (" + ss.str() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::array<u8, 20> GameFile::GetSyncHash() const
|
||||||
|
{
|
||||||
|
std::array<u8, 20> hash{};
|
||||||
|
|
||||||
|
if (m_platform == DiscIO::Platform::ELFOrDOL)
|
||||||
|
{
|
||||||
|
std::string buffer;
|
||||||
|
if (File::ReadFileToString(m_file_path, buffer))
|
||||||
|
mbedtls_sha1_ret(reinterpret_cast<unsigned char*>(buffer.data()), buffer.size(), hash.data());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (std::unique_ptr<DiscIO::Volume> volume = DiscIO::CreateVolume(m_file_path))
|
||||||
|
hash = volume->GetSyncHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetPlay::SyncIdentifier GameFile::GetSyncIdentifier() const
|
||||||
|
{
|
||||||
|
const u64 dol_elf_size = m_platform == DiscIO::Platform::ELFOrDOL ? m_file_size : 0;
|
||||||
|
return NetPlay::SyncIdentifier{dol_elf_size, m_game_id, m_revision,
|
||||||
|
m_disc_number, m_is_datel_disc, GetSyncHash()};
|
||||||
|
}
|
||||||
|
|
||||||
|
NetPlay::SyncIdentifierComparison
|
||||||
|
GameFile::CompareSyncIdentifier(const NetPlay::SyncIdentifier& sync_identifier) const
|
||||||
|
{
|
||||||
|
const bool is_elf_or_dol = m_platform == DiscIO::Platform::ELFOrDOL;
|
||||||
|
if ((is_elf_or_dol ? m_file_size : 0) != sync_identifier.dol_elf_size)
|
||||||
|
return NetPlay::SyncIdentifierComparison::DifferentGame;
|
||||||
|
|
||||||
|
const auto trim = [](const std::string& str, size_t n) {
|
||||||
|
return std::string_view(str.data(), std::min(n, str.size()));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (trim(m_game_id, 3) != trim(sync_identifier.game_id, 3))
|
||||||
|
return NetPlay::SyncIdentifierComparison::DifferentGame;
|
||||||
|
|
||||||
|
if (m_disc_number != sync_identifier.disc_number || m_is_datel_disc != sync_identifier.is_datel)
|
||||||
|
return NetPlay::SyncIdentifierComparison::DifferentGame;
|
||||||
|
|
||||||
|
const NetPlay::SyncIdentifierComparison mismatch_result =
|
||||||
|
is_elf_or_dol || m_is_datel_disc ? NetPlay::SyncIdentifierComparison::DifferentGame :
|
||||||
|
NetPlay::SyncIdentifierComparison::DifferentVersion;
|
||||||
|
|
||||||
|
if (m_game_id != sync_identifier.game_id)
|
||||||
|
{
|
||||||
|
const bool game_id_is_title_id = m_game_id.size() > 6 || sync_identifier.game_id.size() > 6;
|
||||||
|
return game_id_is_title_id ? NetPlay::SyncIdentifierComparison::DifferentGame : mismatch_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_revision != sync_identifier.revision)
|
||||||
|
return mismatch_result;
|
||||||
|
|
||||||
|
return GetSyncHash() == sync_identifier.sync_hash ? NetPlay::SyncIdentifierComparison::SameGame :
|
||||||
|
mismatch_result;
|
||||||
|
}
|
||||||
|
|
||||||
std::string GameFile::GetWiiFSPath() const
|
std::string GameFile::GetWiiFSPath() const
|
||||||
{
|
{
|
||||||
ASSERT(DiscIO::IsWii(m_platform));
|
ASSERT(DiscIO::IsWii(m_platform));
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Core/SyncIdentifier.h"
|
||||||
#include "DiscIO/Blob.h"
|
#include "DiscIO/Blob.h"
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
|
|
||||||
|
@ -80,7 +82,16 @@ public:
|
||||||
u16 GetRevision() const { return m_revision; }
|
u16 GetRevision() const { return m_revision; }
|
||||||
// 0 is the first disc, 1 is the second disc
|
// 0 is the first disc, 1 is the second disc
|
||||||
u8 GetDiscNumber() const { return m_disc_number; }
|
u8 GetDiscNumber() const { return m_disc_number; }
|
||||||
std::string GetUniqueIdentifier() const;
|
std::string GetNetPlayName() const;
|
||||||
|
|
||||||
|
// This function is slow
|
||||||
|
std::array<u8, 20> GetSyncHash() const;
|
||||||
|
// This function is slow
|
||||||
|
NetPlay::SyncIdentifier GetSyncIdentifier() const;
|
||||||
|
// This function is slow if all of game_id, revision, disc_number, is_datel are identical
|
||||||
|
NetPlay::SyncIdentifierComparison
|
||||||
|
CompareSyncIdentifier(const NetPlay::SyncIdentifier& sync_identifier) const;
|
||||||
|
|
||||||
std::string GetWiiFSPath() const;
|
std::string GetWiiFSPath() const;
|
||||||
DiscIO::Region GetRegion() const { return m_region; }
|
DiscIO::Region GetRegion() const { return m_region; }
|
||||||
DiscIO::Country GetCountry() const { return m_country; }
|
DiscIO::Country GetCountry() const { return m_country; }
|
||||||
|
|
Loading…
Reference in New Issue