diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 37a1991cdb..a3f3f4dc93 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -37,6 +37,7 @@ add_library(core PatchEngine.h State.cpp State.h + SyncIdentifier.h SysConf.cpp SysConf.h TitleDatabase.cpp diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index ceefd749c9..dfe82922d6 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -709,6 +709,7 @@ + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index cb729a6f4a..91553ac3af 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -1757,7 +1757,8 @@ HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA - + + diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index e6efce1799..4b1c730a7d 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -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 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(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(status); + game_status_packet << static_cast(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(status); + player.game_status = static_cast(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 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& 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 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; diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 2e6acf4518..6af81e8d60 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -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 FindGameFile(const std::string& game) = 0; - virtual void ShowMD5Dialog(const std::string& file_identifier) = 0; + virtual std::shared_ptr + 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; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index e76d362285..aac046ed44 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -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(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(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(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(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(status); + m_players[player.pid].game_status = static_cast(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 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(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(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!"); diff --git a/Source/Core/Core/NetPlayServer.h b/Source/Core/Core/NetPlayServer.h index 62b20f7744..16034074af 100644 --- a/Source/Core/Core/NetPlayServer.h +++ b/Source/Core/Core/NetPlayServer.h @@ -5,6 +5,7 @@ #pragma once #include + #include #include #include @@ -14,19 +15,20 @@ #include #include #include + #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 m_async_queue; Common::SPSCQueue 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; diff --git a/Source/Core/Core/SyncIdentifier.h b/Source/Core/Core/SyncIdentifier.h new file mode 100644 index 0000000000..19af15bd1b --- /dev/null +++ b/Source/Core/Core/SyncIdentifier.h @@ -0,0 +1,46 @@ +// Copyright 2020 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#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 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 diff --git a/Source/Core/DiscIO/DiscExtractor.cpp b/Source/Core/DiscIO/DiscExtractor.cpp index 3c2a67568d..b12b56b5c2 100644 --- a/Source/Core/DiscIO/DiscExtractor.cpp +++ b/Source/Core/DiscIO/DiscExtractor.cpp @@ -245,19 +245,26 @@ bool ExportBI2Data(const Volume& volume, const Partition& partition, return ExportData(volume, partition, 0x440, 0x2000, export_filename); } +std::optional GetApploaderSize(const Volume& volume, const Partition& partition) +{ + constexpr u64 header_size = 0x20; + const std::optional apploader_size = volume.ReadSwapped(0x2440 + 0x14, partition); + const std::optional trailer_size = volume.ReadSwapped(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 apploader_size = volume.ReadSwapped(0x2440 + 0x14, partition); - const std::optional trailer_size = volume.ReadSwapped(0x2440 + 0x18, partition); - constexpr u32 header_size = 0x20; - if (!apploader_size || !trailer_size) + const std::optional 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); } diff --git a/Source/Core/DiscIO/DiscExtractor.h b/Source/Core/DiscIO/DiscExtractor.h index 0ca1954ee0..c7e18f83d6 100644 --- a/Source/Core/DiscIO/DiscExtractor.h +++ b/Source/Core/DiscIO/DiscExtractor.h @@ -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 GetApploaderSize(const Volume& volume, const Partition& partition); bool ExportApploader(const Volume& volume, const Partition& partition, const std::string& export_filename); std::optional GetBootDOLOffset(const Volume& volume, const Partition& partition); diff --git a/Source/Core/DiscIO/Volume.cpp b/Source/Core/DiscIO/Volume.cpp index 23df2afc5c..ab4b0e562b 100644 --- a/Source/Core/DiscIO/Volume.cpp +++ b/Source/Core/DiscIO/Volume.cpp @@ -9,12 +9,16 @@ #include #include #include +#include #include #include +#include + #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 Volume::INVALID_CERT_CHAIN{}; +template +static void AddToSyncHash(mbedtls_sha1_context* context, const T& data) +{ + static_assert(std::is_trivially_copyable_v); + mbedtls_sha1_update_ret(context, reinterpret_cast(&data), sizeof(data)); +} + +void Volume::ReadAndAddToSyncHash(mbedtls_sha1_context* context, u64 offset, u64 length, + const Partition& partition) const +{ + std::vector 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 Volume::ReadWiiNames(const std::vector& data) { std::map names; diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h index e176c5d72a..e580e4b109 100644 --- a/Source/Core/DiscIO/Volume.h +++ b/Source/Core/DiscIO/Volume.h @@ -12,6 +12,8 @@ #include #include +#include + #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 GetSyncHash() const = 0; + protected: template 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 ReadWiiNames(const std::vector& data); diff --git a/Source/Core/DiscIO/VolumeDisc.cpp b/Source/Core/DiscIO/VolumeDisc.cpp index fed82a8067..3ccea4b62f 100644 --- a/Source/Core/DiscIO/VolumeDisc.cpp +++ b/Source/Core/DiscIO/VolumeDisc.cpp @@ -4,11 +4,17 @@ #include "DiscIO/VolumeDisc.h" +#include #include #include +#include + +#include #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(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 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 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 file_info = file_system->FindFileInfo("opening.bnr"); + if (file_info && !file_info->IsDirectory()) + ReadAndAddToSyncHash(context, file_info->GetOffset(), file_info->GetSize(), partition); + } +} + } // namespace DiscIO diff --git a/Source/Core/DiscIO/VolumeDisc.h b/Source/Core/DiscIO/VolumeDisc.h index 680bbf83b0..e12ec310ae 100644 --- a/Source/Core/DiscIO/VolumeDisc.h +++ b/Source/Core/DiscIO/VolumeDisc.h @@ -7,6 +7,8 @@ #include #include +#include + #include "Common/CommonTypes.h" #include "DiscIO/Volume.h" @@ -26,6 +28,7 @@ public: protected: Region RegionCodeToRegion(std::optional region_code) const; + void AddGamePartitionToSyncHash(mbedtls_sha1_context* context) const; }; } // namespace DiscIO diff --git a/Source/Core/DiscIO/VolumeGC.cpp b/Source/Core/DiscIO/VolumeGC.cpp index 15d71c74ca..3489965395 100644 --- a/Source/Core/DiscIO/VolumeGC.cpp +++ b/Source/Core/DiscIO/VolumeGC.cpp @@ -11,6 +11,8 @@ #include #include +#include + #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 VolumeGC::GetSyncHash() const +{ + mbedtls_sha1_context context; + mbedtls_sha1_init(&context); + mbedtls_sha1_starts_ret(&context); + + AddGamePartitionToSyncHash(&context); + + std::array hash; + mbedtls_sha1_finish_ret(&context, hash.data()); + return hash; +} + VolumeGC::ConvertedGCBanner VolumeGC::LoadBannerFile() const { GCBanner banner_file; diff --git a/Source/Core/DiscIO/VolumeGC.h b/Source/Core/DiscIO/VolumeGC.h index 56e2356877..f4b2dbc6fc 100644 --- a/Source/Core/DiscIO/VolumeGC.h +++ b/Source/Core/DiscIO/VolumeGC.h @@ -51,6 +51,8 @@ public: u64 GetRawSize() const override; const BlobReader& GetBlobReader() const override; + std::array GetSyncHash() const override; + private: static const u32 GC_BANNER_WIDTH = 96; static const u32 GC_BANNER_HEIGHT = 32; diff --git a/Source/Core/DiscIO/VolumeWad.cpp b/Source/Core/DiscIO/VolumeWad.cpp index b7b8fa35cd..86aebb20b6 100644 --- a/Source/Core/DiscIO/VolumeWad.cpp +++ b/Source/Core/DiscIO/VolumeWad.cpp @@ -40,6 +40,7 @@ VolumeWAD::VolumeWAD(std::unique_ptr reader) : m_reader(std::move(re m_ticket_size = m_reader->ReadSwapped(0x10).value_or(0); m_tmd_size = m_reader->ReadSwapped(0x14).value_or(0); m_data_size = m_reader->ReadSwapped(0x18).value_or(0); + m_opening_bnr_size = m_reader->ReadSwapped(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 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 hash; + mbedtls_sha1_finish_ret(&context, hash.data()); + return hash; +} + } // namespace DiscIO diff --git a/Source/Core/DiscIO/VolumeWad.h b/Source/Core/DiscIO/VolumeWad.h index a4c311da78..8dc0cf39ef 100644 --- a/Source/Core/DiscIO/VolumeWad.h +++ b/Source/Core/DiscIO/VolumeWad.h @@ -70,6 +70,8 @@ public: u64 GetRawSize() const override; const BlobReader& GetBlobReader() const override; + std::array GetSyncHash() const override; + private: std::unique_ptr 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 diff --git a/Source/Core/DiscIO/VolumeWii.cpp b/Source/Core/DiscIO/VolumeWii.cpp index 73797fe1e3..f3a9bf5e12 100644 --- a/Source/Core/DiscIO/VolumeWii.cpp +++ b/Source/Core/DiscIO/VolumeWii.cpp @@ -364,6 +364,33 @@ const BlobReader& VolumeWii::GetBlobReader() const return *m_reader; } +std::array 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(&data_offset), sizeof(data_offset)); + + // TMD + AddTMDToSyncHash(&context, GetGamePartition()); + + // Game partition contents + AddGamePartitionToSyncHash(&context); + + std::array hash; + mbedtls_sha1_finish_ret(&context, hash.data()); + return hash; +} + bool VolumeWii::CheckH3TableIntegrity(const Partition& partition) const { auto it = m_partitions.find(partition); diff --git a/Source/Core/DiscIO/VolumeWii.h b/Source/Core/DiscIO/VolumeWii.h index 3af5884d3e..107c8175b4 100644 --- a/Source/Core/DiscIO/VolumeWii.h +++ b/Source/Core/DiscIO/VolumeWii.h @@ -92,6 +92,7 @@ public: bool IsSizeAccurate() const override; u64 GetRawSize() const override; const BlobReader& GetBlobReader() const override; + std::array 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. diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index 0b1d480375..722bdb6912 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -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); diff --git a/Source/Core/DolphinQt/GameList/GameList.h b/Source/Core/DolphinQt/GameList/GameList.h index f64ddd61f0..429f8796d7 100644 --- a/Source/Core/DolphinQt/GameList/GameList.h +++ b/Source/Core/DolphinQt/GameList/GameList.h @@ -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 game_file); void OpenGeneralSettings(); diff --git a/Source/Core/DolphinQt/GameList/GameListModel.cpp b/Source/Core/DolphinQt/GameList/GameListModel.cpp index 99cd631e92..c184c2ef60 100644 --- a/Source/Core/DolphinQt/GameList/GameListModel.cpp +++ b/Source/Core/DolphinQt/GameList/GameListModel.cpp @@ -313,16 +313,6 @@ std::shared_ptr 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& game) { beginInsertRows(QModelIndex(), m_games.size(), m_games.size()); diff --git a/Source/Core/DolphinQt/GameList/GameListModel.h b/Source/Core/DolphinQt/GameList/GameListModel.h index 28e116c64f..a476ebae82 100644 --- a/Source/Core/DolphinQt/GameList/GameListModel.h +++ b/Source/Core/DolphinQt/GameList/GameListModel.h @@ -37,10 +37,6 @@ public: int columnCount(const QModelIndex& parent) const override; std::shared_ptr 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); diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index bff3a6422c..e579db3dea 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -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(); diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index 63141ec2d3..8c15941551 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -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); diff --git a/Source/Core/DolphinQt/NetPlay/GameListDialog.cpp b/Source/Core/DolphinQt/NetPlay/GameListDialog.cpp index 9fa14e3b30..4ada80cc73 100644 --- a/Source/Core/DolphinQt/NetPlay/GameListDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/GameListDialog.cpp @@ -4,12 +4,15 @@ #include "DolphinQt/NetPlay/GameListDialog.h" +#include + #include #include #include #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 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>(); } int GameListDialog::exec() diff --git a/Source/Core/DolphinQt/NetPlay/GameListDialog.h b/Source/Core/DolphinQt/NetPlay/GameListDialog.h index da04bd4996..78e5f90ff3 100644 --- a/Source/Core/DolphinQt/NetPlay/GameListDialog.h +++ b/Source/Core/DolphinQt/NetPlay/GameListDialog.h @@ -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; }; diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index 77ceb45523..82d2f0b141 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #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 player_status{ - {NetPlay::PlayerGameStatus::Ok, tr("OK")}, - {NetPlay::PlayerGameStatus::NotFound, tr("Not Found")}, + static const std::map 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 +NetPlayDialog::FindGameFile(const NetPlay::SyncIdentifier& sync_identifier, + NetPlay::SyncIdentifierComparison* found) { - std::optional path = RunOnObject(this, [this, &game] { - for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++) - { - if (m_game_list_model->GetUniqueIdentifier(i).toStdString() == game) - 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 NetPlayDialog::FindGameFile(const std::string& game) -{ std::optional> 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>(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)); }); } diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index dd0774174e..7459be4f16 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -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 FindGameFile(const std::string& game) override; + std::shared_ptr + 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 m_external_ip_address; std::string m_nickname; GameListModel* m_game_list_model = nullptr; diff --git a/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.cpp index a9e31f5ed4..5f51f4a7cd 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.cpp @@ -4,6 +4,8 @@ #include "DolphinQt/NetPlay/NetPlaySetupDialog.h" +#include + #include #include #include @@ -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>()); } } @@ -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 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); } diff --git a/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.h index ec0d4b3c5d..a2ff67264d 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlaySetupDialog.h @@ -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(); diff --git a/Source/Core/UICommon/GameFile.cpp b/Source/Core/UICommon/GameFile.cpp index b28d54e944..bd9f621f28 100644 --- a/Source/Core/UICommon/GameFile.cpp +++ b/Source/Core/UICommon/GameFile.cpp @@ -5,6 +5,7 @@ #include "UICommon/GameFile.h" #include +#include #include #include #include @@ -13,11 +14,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include "Common/ChunkFile.h" @@ -532,7 +535,7 @@ std::vector GameFile::GetLanguages() const return languages; } -std::string GameFile::GetUniqueIdentifier() const +std::string GameFile::GetNetPlayName() const { std::vector info; if (!GetGameID().empty()) @@ -566,6 +569,66 @@ std::string GameFile::GetUniqueIdentifier() const return name + " (" + ss.str() + ")"; } +std::array GameFile::GetSyncHash() const +{ + std::array hash{}; + + if (m_platform == DiscIO::Platform::ELFOrDOL) + { + std::string buffer; + if (File::ReadFileToString(m_file_path, buffer)) + mbedtls_sha1_ret(reinterpret_cast(buffer.data()), buffer.size(), hash.data()); + } + else + { + if (std::unique_ptr 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)); diff --git a/Source/Core/UICommon/GameFile.h b/Source/Core/UICommon/GameFile.h index dcf33ee677..68ad21dec6 100644 --- a/Source/Core/UICommon/GameFile.h +++ b/Source/Core/UICommon/GameFile.h @@ -4,11 +4,13 @@ #pragma once +#include #include #include #include #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 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; }