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:
JosJuice 2020-06-07 22:58:03 +02:00
parent 25ebc3c07c
commit a41166bb37
34 changed files with 502 additions and 148 deletions

View File

@ -37,6 +37,7 @@ add_library(core
PatchEngine.h
State.cpp
State.h
SyncIdentifier.h
SysConf.cpp
SysConf.h
TitleDatabase.cpp

View File

@ -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" />

View File

@ -1758,6 +1758,7 @@
<ClInclude Include="HW\EXI\BBA\TAP_Win32.h">
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA</Filter>
</ClInclude>
<ClInclude Include="SyncIdentifier.h" />
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />

View File

@ -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;

View File

@ -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;

View File

@ -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!");

View File

@ -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;

View File

@ -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

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;

View 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;

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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.

View File

@ -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);

View File

@ -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();

View File

@ -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());

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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()

View File

@ -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;
};

View File

@ -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));
});
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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();

View File

@ -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));

View File

@ -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; }