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
|
||||
State.cpp
|
||||
State.h
|
||||
SyncIdentifier.h
|
||||
SysConf.cpp
|
||||
SysConf.h
|
||||
TitleDatabase.cpp
|
||||
|
|
|
@ -709,6 +709,7 @@
|
|||
<ClInclude Include="PowerPC\PPCTables.h" />
|
||||
<ClInclude Include="PowerPC\Profiler.h" />
|
||||
<ClInclude Include="State.h" />
|
||||
<ClInclude Include="SyncIdentifier.h" />
|
||||
<ClInclude Include="SysConf.h" />
|
||||
<ClInclude Include="Titles.h" />
|
||||
<ClInclude Include="TitleDatabase.h" />
|
||||
|
|
|
@ -1757,7 +1757,8 @@
|
|||
</ClInclude>
|
||||
<ClInclude Include="HW\EXI\BBA\TAP_Win32.h">
|
||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA</Filter>
|
||||
</ClInclude>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SyncIdentifier.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
|
|
|
@ -53,9 +53,11 @@
|
|||
#include "Core/IOS/Uids.h"
|
||||
#include "Core/Movie.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/SyncIdentifier.h"
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
|
||||
#include "InputCommon/GCAdapter.h"
|
||||
#include "InputCommon/InputConfig.h"
|
||||
#include "UICommon/GameFile.h"
|
||||
#include "VideoCommon/OnScreenDisplay.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
|
||||
unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||
{
|
||||
|
@ -572,24 +590,25 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
|||
|
||||
case NP_MSG_CHANGE_GAME:
|
||||
{
|
||||
std::string netplay_name;
|
||||
{
|
||||
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
|
||||
m_dialog->OnMsgChangeGame(m_selected_game);
|
||||
m_dialog->OnMsgChangeGame(m_selected_game, netplay_name);
|
||||
|
||||
sf::Packet game_status_packet;
|
||||
game_status_packet << static_cast<MessageId>(NP_MSG_GAME_STATUS);
|
||||
|
||||
PlayerGameStatus status = m_dialog->FindGame(m_selected_game).empty() ?
|
||||
PlayerGameStatus::NotFound :
|
||||
PlayerGameStatus::Ok;
|
||||
SyncIdentifierComparison result;
|
||||
m_dialog->FindGameFile(m_selected_game, &result);
|
||||
|
||||
game_status_packet << static_cast<u32>(status);
|
||||
game_status_packet << static_cast<u32>(result);
|
||||
Send(game_status_packet);
|
||||
|
||||
sf::Packet ipl_status_packet;
|
||||
|
@ -609,7 +628,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
|||
Player& player = m_players[pid];
|
||||
u32 status;
|
||||
packet >> status;
|
||||
player.game_status = static_cast<PlayerGameStatus>(status);
|
||||
player.game_status = static_cast<SyncIdentifierComparison>(status);
|
||||
}
|
||||
|
||||
m_dialog->Update();
|
||||
|
@ -623,7 +642,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
|||
packet >> m_current_game;
|
||||
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;
|
||||
|
@ -1172,10 +1191,10 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
|||
|
||||
case NP_MSG_COMPUTE_MD5:
|
||||
{
|
||||
std::string file_identifier;
|
||||
packet >> file_identifier;
|
||||
SyncIdentifier sync_identifier;
|
||||
ReceiveSyncIdentifier(packet, sync_identifier);
|
||||
|
||||
ComputeMD5(file_identifier);
|
||||
ComputeMD5(sync_identifier);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -1382,11 +1401,15 @@ void NetPlayClient::GetPlayerList(std::string& list, std::vector<int>& pid_list)
|
|||
|
||||
switch (player.game_status)
|
||||
{
|
||||
case PlayerGameStatus::Ok:
|
||||
case SyncIdentifierComparison::SameGame:
|
||||
ss << "ready";
|
||||
break;
|
||||
|
||||
case PlayerGameStatus::NotFound:
|
||||
case SyncIdentifierComparison::DifferentVersion:
|
||||
ss << "wrong game version";
|
||||
break;
|
||||
|
||||
case SyncIdentifierComparison::DifferentGame:
|
||||
ss << "game missing";
|
||||
break;
|
||||
|
||||
|
@ -2286,23 +2309,24 @@ bool NetPlayClient::DoAllPlayersHaveGame()
|
|||
{
|
||||
std::lock_guard<std::recursive_mutex> lkp(m_crit.players);
|
||||
|
||||
return std::all_of(std::begin(m_players), std::end(m_players),
|
||||
[](auto entry) { return entry.second.game_status == PlayerGameStatus::Ok; });
|
||||
return std::all_of(std::begin(m_players), std::end(m_players), [](auto entry) {
|
||||
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)
|
||||
return;
|
||||
|
||||
m_dialog->ShowMD5Dialog(file_identifier);
|
||||
m_dialog->ShowMD5Dialog(sync_identifier.game_id);
|
||||
m_should_compute_MD5 = true;
|
||||
|
||||
std::string file;
|
||||
if (file_identifier == WII_SDCARD)
|
||||
if (sync_identifier == GetSDCardIdentifier())
|
||||
file = File::GetUserPath(F_WIISDCARD_IDX);
|
||||
else
|
||||
file = m_dialog->FindGame(file_identifier);
|
||||
else if (auto game = m_dialog->FindGameFile(sync_identifier))
|
||||
file = game->GetFilePath();
|
||||
|
||||
if (file.empty() || !File::Exists(file))
|
||||
{
|
||||
|
@ -2348,6 +2372,11 @@ void NetPlayClient::AdjustPadBufferSize(const unsigned int size)
|
|||
m_dialog->OnPadBufferChanged(size);
|
||||
}
|
||||
|
||||
SyncIdentifier NetPlayClient::GetSDCardIdentifier()
|
||||
{
|
||||
return SyncIdentifier{{}, "sd", {}, {}, {}, {}};
|
||||
}
|
||||
|
||||
bool IsNetPlayRunning()
|
||||
{
|
||||
return netplay_client != nullptr;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "Common/SPSCQueue.h"
|
||||
#include "Common/TraversalClient.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/SyncIdentifier.h"
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
|
||||
namespace UICommon
|
||||
|
@ -42,7 +43,8 @@ public:
|
|||
virtual void Update() = 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 OnMsgStopGame() = 0;
|
||||
virtual void OnMsgPowerButton() = 0;
|
||||
|
@ -59,9 +61,10 @@ public:
|
|||
virtual void OnGolferChanged(bool is_golfer, const std::string& golfer_name) = 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 std::shared_ptr<const UICommon::GameFile>
|
||||
FindGameFile(const SyncIdentifier& sync_identifier,
|
||||
SyncIdentifierComparison* found = nullptr) = 0;
|
||||
virtual void ShowMD5Dialog(const std::string& title) = 0;
|
||||
virtual void SetMD5Progress(int pid, int progress) = 0;
|
||||
virtual void SetMD5Result(int pid, const std::string& result) = 0;
|
||||
virtual void AbortMD5() = 0;
|
||||
|
@ -75,13 +78,6 @@ public:
|
|||
virtual void SetChunkedProgress(int pid, u64 progress) = 0;
|
||||
};
|
||||
|
||||
enum class PlayerGameStatus
|
||||
{
|
||||
Unknown,
|
||||
Ok,
|
||||
NotFound
|
||||
};
|
||||
|
||||
class Player
|
||||
{
|
||||
public:
|
||||
|
@ -89,7 +85,7 @@ public:
|
|||
std::string name;
|
||||
std::string revision;
|
||||
u32 ping;
|
||||
PlayerGameStatus game_status;
|
||||
SyncIdentifierComparison game_status;
|
||||
|
||||
bool IsHost() const { return pid == 1; }
|
||||
};
|
||||
|
@ -149,6 +145,8 @@ public:
|
|||
|
||||
void AdjustPadBufferSize(unsigned int size);
|
||||
|
||||
static SyncIdentifier GetSDCardIdentifier();
|
||||
|
||||
protected:
|
||||
struct AsyncQueueEntry
|
||||
{
|
||||
|
@ -182,7 +180,7 @@ protected:
|
|||
ENetPeer* m_server = nullptr;
|
||||
std::thread m_thread;
|
||||
|
||||
std::string m_selected_game;
|
||||
SyncIdentifier m_selected_game;
|
||||
Common::Flag m_is_running{false};
|
||||
Common::Flag m_do_loop{true};
|
||||
|
||||
|
@ -237,7 +235,7 @@ private:
|
|||
void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
|
||||
void Disconnect();
|
||||
bool Connect();
|
||||
void ComputeMD5(const std::string& file_identifier);
|
||||
void ComputeMD5(const SyncIdentifier& sync_identifier);
|
||||
void DisplayPlayersPing();
|
||||
u32 GetPlayersMaxPing() const;
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/Uids.h"
|
||||
#include "Core/NetPlayClient.h" //for NetPlayUI
|
||||
#include "Core/SyncIdentifier.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
|
@ -182,7 +183,7 @@ void NetPlayServer::SetupIndex()
|
|||
session.region = Config::Get(Config::NETPLAY_INDEX_REGION);
|
||||
session.has_password = !Config::Get(Config::NETPLAY_INDEX_PASSWORD).empty();
|
||||
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.in_game = m_is_running;
|
||||
session.port = GetPort();
|
||||
|
@ -238,7 +239,7 @@ void NetPlayServer::ThreadFunc()
|
|||
SendToClients(spac);
|
||||
|
||||
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_update_pings = false;
|
||||
|
@ -348,6 +349,20 @@ void NetPlayServer::ThreadFunc()
|
|||
}
|
||||
} // 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
|
||||
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 new client the selected game
|
||||
if (!m_selected_game.empty())
|
||||
if (!m_selected_game_name.empty())
|
||||
{
|
||||
spac.clear();
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -913,7 +929,7 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
|
|||
u32 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
|
||||
sf::Packet spac;
|
||||
|
@ -1153,16 +1169,19 @@ void NetPlayServer::SendChatMessage(const std::string& msg)
|
|||
}
|
||||
|
||||
// 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);
|
||||
|
||||
m_selected_game = game;
|
||||
m_selected_game_identifier = sync_identifier;
|
||||
m_selected_game_name = netplay_name;
|
||||
|
||||
// send changed game to clients
|
||||
sf::Packet spac;
|
||||
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));
|
||||
|
||||
|
@ -1170,11 +1189,11 @@ bool NetPlayServer::ChangeGame(const std::string& game)
|
|||
}
|
||||
|
||||
// called from ---GUI--- thread
|
||||
bool NetPlayServer::ComputeMD5(const std::string& file_identifier)
|
||||
bool NetPlayServer::ComputeMD5(const SyncIdentifier& sync_identifier)
|
||||
{
|
||||
sf::Packet spac;
|
||||
spac << static_cast<MessageId>(NP_MSG_COMPUTE_MD5);
|
||||
spac << file_identifier;
|
||||
SendSyncIdentifier(spac, sync_identifier);
|
||||
|
||||
SendAsyncToClients(std::move(spac));
|
||||
|
||||
|
@ -1260,7 +1279,7 @@ bool NetPlayServer::StartGame()
|
|||
const sf::Uint64 initial_rtc = GetInitialNetPlayRTC();
|
||||
|
||||
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
|
||||
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)
|
||||
{
|
||||
PanicAlertT("Selected game doesn't exist in game list!");
|
||||
|
@ -1618,7 +1637,7 @@ bool NetPlayServer::SyncCodes()
|
|||
m_codes_synced = false;
|
||||
|
||||
// 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)
|
||||
{
|
||||
PanicAlertT("Selected game doesn't exist in game list!");
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <SFML/Network/Packet.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
@ -14,19 +15,20 @@
|
|||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "Common/Event.h"
|
||||
#include "Common/QoSSession.h"
|
||||
#include "Common/SPSCQueue.h"
|
||||
#include "Common/Timer.h"
|
||||
#include "Common/TraversalClient.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/SyncIdentifier.h"
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
#include "UICommon/NetPlayIndex.h"
|
||||
|
||||
namespace NetPlay
|
||||
{
|
||||
class NetPlayUI;
|
||||
enum class PlayerGameStatus;
|
||||
|
||||
class NetPlayServer : public TraversalClientClient
|
||||
{
|
||||
|
@ -43,8 +45,8 @@ public:
|
|||
const NetTraversalConfig& traversal_config);
|
||||
~NetPlayServer();
|
||||
|
||||
bool ChangeGame(const std::string& game);
|
||||
bool ComputeMD5(const std::string& file_identifier);
|
||||
bool ChangeGame(const SyncIdentifier& sync_identifier, const std::string& netplay_name);
|
||||
bool ComputeMD5(const SyncIdentifier& sync_identifier);
|
||||
bool AbortMD5();
|
||||
void SendChatMessage(const std::string& msg);
|
||||
|
||||
|
@ -80,7 +82,7 @@ private:
|
|||
PlayerId pid;
|
||||
std::string name;
|
||||
std::string revision;
|
||||
PlayerGameStatus game_status;
|
||||
SyncIdentifierComparison game_status;
|
||||
bool has_ipl_dump;
|
||||
|
||||
ENetPeer* socket;
|
||||
|
@ -180,7 +182,8 @@ private:
|
|||
Common::SPSCQueue<AsyncQueueEntry, false> m_async_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;
|
||||
Common::Event m_chunked_data_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);
|
||||
}
|
||||
|
||||
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,
|
||||
const std::string& export_filename)
|
||||
{
|
||||
if (!IsDisc(volume.GetVolumeType()))
|
||||
return false;
|
||||
|
||||
std::optional<u32> apploader_size = volume.ReadSwapped<u32>(0x2440 + 0x14, partition);
|
||||
const std::optional<u32> trailer_size = volume.ReadSwapped<u32>(0x2440 + 0x18, partition);
|
||||
constexpr u32 header_size = 0x20;
|
||||
if (!apploader_size || !trailer_size)
|
||||
const std::optional<u64> apploader_size = GetApploaderSize(volume, partition);
|
||||
if (!apploader_size)
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ bool ExportHeader(const Volume& volume, const Partition& partition,
|
|||
const std::string& export_filename);
|
||||
bool ExportBI2Data(const Volume& volume, const Partition& partition,
|
||||
const std::string& export_filename);
|
||||
std::optional<u64> GetApploaderSize(const Volume& volume, const Partition& partition);
|
||||
bool ExportApploader(const Volume& volume, const Partition& partition,
|
||||
const std::string& export_filename);
|
||||
std::optional<u64> GetBootDOLOffset(const Volume& volume, const Partition& partition);
|
||||
|
|
|
@ -9,12 +9,16 @@
|
|||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/VolumeDisc.h"
|
||||
|
@ -28,6 +32,43 @@ const IOS::ES::TicketReader Volume::INVALID_TICKET{};
|
|||
const IOS::ES::TMDReader Volume::INVALID_TMD{};
|
||||
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> names;
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
|
@ -138,6 +140,13 @@ public:
|
|||
virtual u64 GetRawSize() 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:
|
||||
template <u32 N>
|
||||
std::string DecodeString(const char (&data)[N]) const
|
||||
|
@ -151,6 +160,10 @@ protected:
|
|||
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; }
|
||||
static std::map<Language, std::string> ReadWiiNames(const std::vector<char16_t>& data);
|
||||
|
||||
|
|
|
@ -4,11 +4,17 @@
|
|||
|
||||
#include "DiscIO/VolumeDisc.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "DiscIO/DiscExtractor.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
|
@ -90,4 +96,35 @@ bool VolumeDisc::IsNKit() const
|
|||
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
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
|
@ -26,6 +28,7 @@ public:
|
|||
|
||||
protected:
|
||||
Region RegionCodeToRegion(std::optional<u32> region_code) const;
|
||||
void AddGamePartitionToSyncHash(mbedtls_sha1_context* context) const;
|
||||
};
|
||||
|
||||
} // namespace DiscIO
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/ColorUtil.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
@ -137,6 +139,19 @@ bool VolumeGC::IsDatelDisc() const
|
|||
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
|
||||
{
|
||||
GCBanner banner_file;
|
||||
|
|
|
@ -51,6 +51,8 @@ public:
|
|||
u64 GetRawSize() const override;
|
||||
const BlobReader& GetBlobReader() const override;
|
||||
|
||||
std::array<u8, 20> GetSyncHash() const override;
|
||||
|
||||
private:
|
||||
static const u32 GC_BANNER_WIDTH = 96;
|
||||
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_tmd_size = m_reader->ReadSwapped<u32>(0x14).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_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;
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -70,6 +70,8 @@ public:
|
|||
u64 GetRawSize() const override;
|
||||
const BlobReader& GetBlobReader() const override;
|
||||
|
||||
std::array<u8, 20> GetSyncHash() const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<BlobReader> m_reader;
|
||||
IOS::ES::TicketReader m_ticket;
|
||||
|
@ -85,6 +87,7 @@ private:
|
|||
u32 m_ticket_size = 0;
|
||||
u32 m_tmd_size = 0;
|
||||
u32 m_data_size = 0;
|
||||
u32 m_opening_bnr_size = 0;
|
||||
};
|
||||
|
||||
} // namespace DiscIO
|
||||
|
|
|
@ -364,6 +364,33 @@ const BlobReader& VolumeWii::GetBlobReader() const
|
|||
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
|
||||
{
|
||||
auto it = m_partitions.find(partition);
|
||||
|
|
|
@ -92,6 +92,7 @@ public:
|
|||
bool IsSizeAccurate() const override;
|
||||
u64 GetRawSize() 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,
|
||||
// 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);
|
||||
|
||||
connect(netplay_host, &QAction::triggered, [this, game] {
|
||||
emit NetPlayHost(QString::fromStdString(game->GetUniqueIdentifier()));
|
||||
});
|
||||
connect(netplay_host, &QAction::triggered, [this, game] { emit NetPlayHost(*game); });
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, menu, [=](Core::State state) {
|
||||
netplay_host->setEnabled(state == Core::State::Uninitialized);
|
||||
|
|
|
@ -47,7 +47,7 @@ public:
|
|||
|
||||
signals:
|
||||
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 OpenGeneralSettings();
|
||||
|
||||
|
|
|
@ -313,16 +313,6 @@ std::shared_ptr<const UICommon::GameFile> GameListModel::GetGameFile(int 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)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), m_games.size(), m_games.size());
|
||||
|
|
|
@ -37,10 +37,6 @@ public:
|
|||
int columnCount(const QModelIndex& parent) const override;
|
||||
|
||||
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;
|
||||
void SetSearchTerm(const QString& term);
|
||||
|
||||
|
|
|
@ -1374,7 +1374,7 @@ bool MainWindow::NetPlayJoin()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MainWindow::NetPlayHost(const QString& game_id)
|
||||
bool MainWindow::NetPlayHost(const UICommon::GameFile& game)
|
||||
{
|
||||
if (Core::IsRunning())
|
||||
{
|
||||
|
@ -1419,7 +1419,8 @@ bool MainWindow::NetPlayHost(const QString& game_id)
|
|||
return false;
|
||||
}
|
||||
|
||||
Settings::Instance().GetNetPlayServer()->ChangeGame(game_id.toStdString());
|
||||
Settings::Instance().GetNetPlayServer()->ChangeGame(game.GetSyncIdentifier(),
|
||||
game.GetNetPlayName());
|
||||
|
||||
// Join our local server
|
||||
return NetPlayJoin();
|
||||
|
|
|
@ -157,7 +157,7 @@ private:
|
|||
|
||||
void NetPlayInit();
|
||||
bool NetPlayJoin();
|
||||
bool NetPlayHost(const QString& game_id);
|
||||
bool NetPlayHost(const UICommon::GameFile& game);
|
||||
void NetPlayQuit();
|
||||
|
||||
void OnBootGameCubeIPL(DiscIO::Region region);
|
||||
|
|
|
@ -4,12 +4,15 @@
|
|||
|
||||
#include "DolphinQt/NetPlay/GameListDialog.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QListWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "DolphinQt/GameList/GameListModel.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
GameListDialog::GameListDialog(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
|
@ -35,12 +38,8 @@ void GameListDialog::CreateWidgets()
|
|||
|
||||
void GameListDialog::ConnectWidgets()
|
||||
{
|
||||
connect(m_game_list, &QListWidget::itemSelectionChanged, [this] {
|
||||
int row = m_game_list->currentRow();
|
||||
|
||||
m_button_box->setEnabled(row != -1);
|
||||
m_game_id = m_game_list->currentItem()->text();
|
||||
});
|
||||
connect(m_game_list, &QListWidget::itemSelectionChanged,
|
||||
[this] { m_button_box->setEnabled(m_game_list->currentRow() != -1); });
|
||||
|
||||
connect(m_game_list, &QListWidget::itemDoubleClicked, 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++)
|
||||
{
|
||||
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->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()
|
||||
|
|
|
@ -11,6 +11,11 @@ class QVBoxLayout;
|
|||
class QListWidget;
|
||||
class QDialogButtonBox;
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
}
|
||||
|
||||
class GameListDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -18,7 +23,7 @@ public:
|
|||
explicit GameListDialog(QWidget* parent);
|
||||
|
||||
int exec() override;
|
||||
const QString& GetSelectedUniqueID() const;
|
||||
const UICommon::GameFile& GetSelectedGame() const;
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
|
@ -28,5 +33,4 @@ private:
|
|||
QVBoxLayout* m_main_layout;
|
||||
QListWidget* m_game_list;
|
||||
QDialogButtonBox* m_button_box;
|
||||
QString m_game_id;
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <QTableWidget>
|
||||
#include <QTextBrowser>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
|
@ -37,6 +38,7 @@
|
|||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/NetPlayServer.h"
|
||||
#include "Core/SyncIdentifier.h"
|
||||
|
||||
#include "DolphinQt/GameList/GameListModel.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->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] {
|
||||
GameListDialog gld(this);
|
||||
|
||||
if (gld.exec() != QDialog::Accepted)
|
||||
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_record_input_action = m_other_menu->addAction(tr("Record Inputs"));
|
||||
|
@ -321,9 +325,11 @@ void NetPlayDialog::ConnectWidgets()
|
|||
GameListDialog gld(this);
|
||||
if (gld.exec() == QDialog::Accepted)
|
||||
{
|
||||
auto unique_id = gld.GetSelectedUniqueID();
|
||||
Settings::Instance().GetNetPlayServer()->ChangeGame(unique_id.toStdString());
|
||||
Settings::GetQSettings().setValue(QStringLiteral("netplay/hostgame"), unique_id);
|
||||
const UICommon::GameFile& game = gld.GetSelectedGame();
|
||||
const std::string netplay_name = game.GetNetPlayName();
|
||||
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;
|
||||
}
|
||||
|
||||
const auto game = FindGameFile(m_current_game);
|
||||
const auto game = FindGameFile(m_current_game_identifier);
|
||||
if (!game)
|
||||
{
|
||||
PanicAlertT("Selected game doesn't exist in game list!");
|
||||
|
@ -583,11 +589,12 @@ void NetPlayDialog::UpdateDiscordPresence()
|
|||
{
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
// 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;
|
||||
|
||||
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())
|
||||
|
@ -602,7 +609,8 @@ void NetPlayDialog::UpdateDiscordPresence()
|
|||
return use_default();
|
||||
|
||||
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
|
||||
{
|
||||
|
@ -612,7 +620,7 @@ void NetPlayDialog::UpdateDiscordPresence()
|
|||
|
||||
Discord::UpdateDiscordPresence(
|
||||
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
|
||||
|
@ -660,9 +668,10 @@ void NetPlayDialog::UpdateGUI()
|
|||
return '|' + str + '|';
|
||||
};
|
||||
|
||||
static const std::map<NetPlay::PlayerGameStatus, QString> player_status{
|
||||
{NetPlay::PlayerGameStatus::Ok, tr("OK")},
|
||||
{NetPlay::PlayerGameStatus::NotFound, tr("Not Found")},
|
||||
static const std::map<NetPlay::SyncIdentifierComparison, QString> player_status{
|
||||
{NetPlay::SyncIdentifierComparison::SameGame, tr("OK")},
|
||||
{NetPlay::SyncIdentifierComparison::DifferentVersion, tr("Wrong Version")},
|
||||
{NetPlay::SyncIdentifierComparison::DifferentGame, tr("Not Found")},
|
||||
};
|
||||
|
||||
for (int i = 0; i < m_player_count; i++)
|
||||
|
@ -805,15 +814,17 @@ void NetPlayDialog::AppendChat(const std::string& msg)
|
|||
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);
|
||||
QueueOnObject(this, [this, qtitle, title] {
|
||||
m_game_button->setText(qtitle);
|
||||
m_current_game = title;
|
||||
QString qname = QString::fromStdString(netplay_name);
|
||||
QueueOnObject(this, [this, qname, netplay_name, &sync_identifier] {
|
||||
m_game_button->setText(qname);
|
||||
m_current_game_identifier = sync_identifier;
|
||||
m_current_game_name = netplay_name;
|
||||
UpdateDiscordPresence();
|
||||
});
|
||||
DisplayMessage(tr("Game changed to \"%1\"").arg(qtitle), "magenta");
|
||||
DisplayMessage(tr("Game changed to \"%1\"").arg(qname), "magenta");
|
||||
}
|
||||
|
||||
void NetPlayDialog::GameStatusChanged(bool running)
|
||||
|
@ -859,7 +870,12 @@ void NetPlayDialog::OnMsgStartGame()
|
|||
auto client = Settings::Instance().GetNetPlayClient();
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
@ -1017,29 +1033,24 @@ bool NetPlayDialog::IsRecording()
|
|||
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] {
|
||||
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->GetPath(i).toStdString();
|
||||
}
|
||||
return std::string("");
|
||||
});
|
||||
if (path)
|
||||
return *path;
|
||||
return std::string("");
|
||||
}
|
||||
NetPlay::SyncIdentifierComparison temp;
|
||||
if (!found)
|
||||
found = &temp;
|
||||
|
||||
*found = NetPlay::SyncIdentifierComparison::DifferentGame;
|
||||
|
||||
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] {
|
||||
RunOnObject(this, [this, &sync_identifier, found] {
|
||||
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);
|
||||
auto game_file = 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);
|
||||
});
|
||||
|
@ -1126,15 +1137,15 @@ void NetPlayDialog::SaveSettings()
|
|||
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);
|
||||
|
||||
if (m_md5_dialog->isVisible())
|
||||
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 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 OnMsgStopGame() override;
|
||||
void OnMsgPowerButton() override;
|
||||
|
@ -65,13 +66,14 @@ public:
|
|||
void OnIndexRefreshFailed(const std::string error) override;
|
||||
|
||||
bool IsRecording() override;
|
||||
std::string FindGame(const std::string& game) override;
|
||||
std::shared_ptr<const UICommon::GameFile> FindGameFile(const std::string& game) override;
|
||||
std::shared_ptr<const UICommon::GameFile>
|
||||
FindGameFile(const NetPlay::SyncIdentifier& sync_identifier,
|
||||
NetPlay::SyncIdentifierComparison* found = nullptr) override;
|
||||
|
||||
void LoadSettings();
|
||||
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 SetMD5Result(int pid, const std::string& result) override;
|
||||
void AbortMD5() override;
|
||||
|
@ -145,7 +147,8 @@ private:
|
|||
MD5Dialog* m_md5_dialog;
|
||||
ChunkedProgressDialog* m_chunked_progress_dialog;
|
||||
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;
|
||||
std::string m_nickname;
|
||||
GameListModel* m_game_list_model = nullptr;
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include "DolphinQt/NetPlay/NetPlaySetupDialog.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
|
@ -24,6 +26,7 @@
|
|||
#include "DolphinQt/QtUtils/UTF8CodePointCountValidator.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
#include "UICommon/NetPlayIndex.h"
|
||||
|
||||
NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent)
|
||||
|
@ -347,7 +350,7 @@ void NetPlaySetupDialog::accept()
|
|||
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();
|
||||
for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
|
||||
{
|
||||
auto title = m_game_list_model->GetUniqueIdentifier(i);
|
||||
auto path = m_game_list_model->GetPath(i);
|
||||
std::shared_ptr<const UICommon::GameFile> game = m_game_list_model->GetGameFile(i);
|
||||
|
||||
auto* item = new QListWidgetItem(title);
|
||||
item->setData(Qt::UserRole, path);
|
||||
auto* item = new QListWidgetItem(QString::fromStdString(game->GetNetPlayName()));
|
||||
item->setData(Qt::UserRole, QVariant::fromValue(std::move(game)));
|
||||
m_host_games->addItem(item);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,11 @@ class QPushButton;
|
|||
class QSpinBox;
|
||||
class QTabWidget;
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
}
|
||||
|
||||
class NetPlaySetupDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -29,7 +34,7 @@ public:
|
|||
|
||||
signals:
|
||||
bool Join();
|
||||
bool Host(const QString& game_identifier);
|
||||
bool Host(const UICommon::GameFile& game);
|
||||
|
||||
private:
|
||||
void CreateMainLayout();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "UICommon/GameFile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
@ -13,11 +14,13 @@
|
|||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <mbedtls/sha1.h>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
|
@ -532,7 +535,7 @@ std::vector<DiscIO::Language> GameFile::GetLanguages() const
|
|||
return languages;
|
||||
}
|
||||
|
||||
std::string GameFile::GetUniqueIdentifier() const
|
||||
std::string GameFile::GetNetPlayName() const
|
||||
{
|
||||
std::vector<std::string> info;
|
||||
if (!GetGameID().empty())
|
||||
|
@ -566,6 +569,66 @@ std::string GameFile::GetUniqueIdentifier() const
|
|||
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
|
||||
{
|
||||
ASSERT(DiscIO::IsWii(m_platform));
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/SyncIdentifier.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
|
||||
|
@ -80,7 +82,16 @@ public:
|
|||
u16 GetRevision() const { return m_revision; }
|
||||
// 0 is the first disc, 1 is the second disc
|
||||
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;
|
||||
DiscIO::Region GetRegion() const { return m_region; }
|
||||
DiscIO::Country GetCountry() const { return m_country; }
|
||||
|
|
Loading…
Reference in New Issue