mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Merge pull request #8861 from JosJuice/netplay-hash
Make netplay's "same game" check more robust
This commit is contained in:
@@ -37,6 +37,7 @@ add_library(core
|
||||
PatchEngine.h
|
||||
State.cpp
|
||||
State.h
|
||||
SyncIdentifier.h
|
||||
SysConf.cpp
|
||||
SysConf.h
|
||||
TitleDatabase.cpp
|
||||
|
||||
@@ -673,6 +673,7 @@
|
||||
<ClInclude Include="PowerPC\PPCTables.h" />
|
||||
<ClInclude Include="PowerPC\Profiler.h" />
|
||||
<ClInclude Include="State.h" />
|
||||
<ClInclude Include="SyncIdentifier.h" />
|
||||
<ClInclude Include="SysConf.h" />
|
||||
<ClInclude Include="Titles.h" />
|
||||
<ClInclude Include="TitleDatabase.h" />
|
||||
|
||||
@@ -1757,7 +1757,8 @@
|
||||
</ClInclude>
|
||||
<ClInclude Include="HW\EXI\BBA\TAP_Win32.h">
|
||||
<Filter>HW %28Flipper/Hollywood%29\EXI - Expansion Interface\BBA</Filter>
|
||||
</ClInclude>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SyncIdentifier.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
|
||||
@@ -53,9 +53,11 @@
|
||||
#include "Core/IOS/Uids.h"
|
||||
#include "Core/Movie.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/SyncIdentifier.h"
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
|
||||
#include "InputCommon/GCAdapter.h"
|
||||
#include "InputCommon/InputConfig.h"
|
||||
#include "UICommon/GameFile.h"
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
@@ -284,6 +286,22 @@ bool NetPlayClient::Connect()
|
||||
}
|
||||
}
|
||||
|
||||
static void ReceiveSyncIdentifier(sf::Packet& spac, SyncIdentifier& sync_identifier)
|
||||
{
|
||||
// We use a temporary variable here due to a potential long vs long long mismatch
|
||||
sf::Uint64 dol_elf_size;
|
||||
spac >> dol_elf_size;
|
||||
sync_identifier.dol_elf_size = dol_elf_size;
|
||||
|
||||
spac >> sync_identifier.game_id;
|
||||
spac >> sync_identifier.revision;
|
||||
spac >> sync_identifier.disc_number;
|
||||
spac >> sync_identifier.is_datel;
|
||||
|
||||
for (u8& x : sync_identifier.sync_hash)
|
||||
spac >> x;
|
||||
}
|
||||
|
||||
// called from ---NETPLAY--- thread
|
||||
unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||
{
|
||||
@@ -572,24 +590,25 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||
|
||||
case NP_MSG_CHANGE_GAME:
|
||||
{
|
||||
std::string netplay_name;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lkg(m_crit.game);
|
||||
packet >> m_selected_game;
|
||||
ReceiveSyncIdentifier(packet, m_selected_game);
|
||||
packet >> netplay_name;
|
||||
}
|
||||
|
||||
INFO_LOG(NETPLAY, "Game changed to %s", m_selected_game.c_str());
|
||||
INFO_LOG(NETPLAY, "Game changed to %s", netplay_name.c_str());
|
||||
|
||||
// update gui
|
||||
m_dialog->OnMsgChangeGame(m_selected_game);
|
||||
m_dialog->OnMsgChangeGame(m_selected_game, netplay_name);
|
||||
|
||||
sf::Packet game_status_packet;
|
||||
game_status_packet << static_cast<MessageId>(NP_MSG_GAME_STATUS);
|
||||
|
||||
PlayerGameStatus status = m_dialog->FindGame(m_selected_game).empty() ?
|
||||
PlayerGameStatus::NotFound :
|
||||
PlayerGameStatus::Ok;
|
||||
SyncIdentifierComparison result;
|
||||
m_dialog->FindGameFile(m_selected_game, &result);
|
||||
|
||||
game_status_packet << static_cast<u32>(status);
|
||||
game_status_packet << static_cast<u32>(result);
|
||||
Send(game_status_packet);
|
||||
|
||||
sf::Packet ipl_status_packet;
|
||||
@@ -609,7 +628,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||
Player& player = m_players[pid];
|
||||
u32 status;
|
||||
packet >> status;
|
||||
player.game_status = static_cast<PlayerGameStatus>(status);
|
||||
player.game_status = static_cast<SyncIdentifierComparison>(status);
|
||||
}
|
||||
|
||||
m_dialog->Update();
|
||||
@@ -623,7 +642,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||
packet >> m_current_game;
|
||||
packet >> m_net_settings.m_CPUthread;
|
||||
|
||||
INFO_LOG(NETPLAY, "Start of game %s", m_selected_game.c_str());
|
||||
INFO_LOG(NETPLAY, "Start of game %s", m_selected_game.game_id.c_str());
|
||||
|
||||
{
|
||||
std::underlying_type_t<PowerPC::CPUCore> core;
|
||||
@@ -1172,10 +1191,10 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
||||
|
||||
case NP_MSG_COMPUTE_MD5:
|
||||
{
|
||||
std::string file_identifier;
|
||||
packet >> file_identifier;
|
||||
SyncIdentifier sync_identifier;
|
||||
ReceiveSyncIdentifier(packet, sync_identifier);
|
||||
|
||||
ComputeMD5(file_identifier);
|
||||
ComputeMD5(sync_identifier);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1382,11 +1401,15 @@ void NetPlayClient::GetPlayerList(std::string& list, std::vector<int>& pid_list)
|
||||
|
||||
switch (player.game_status)
|
||||
{
|
||||
case PlayerGameStatus::Ok:
|
||||
case SyncIdentifierComparison::SameGame:
|
||||
ss << "ready";
|
||||
break;
|
||||
|
||||
case PlayerGameStatus::NotFound:
|
||||
case SyncIdentifierComparison::DifferentVersion:
|
||||
ss << "wrong game version";
|
||||
break;
|
||||
|
||||
case SyncIdentifierComparison::DifferentGame:
|
||||
ss << "game missing";
|
||||
break;
|
||||
|
||||
@@ -2286,23 +2309,24 @@ bool NetPlayClient::DoAllPlayersHaveGame()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lkp(m_crit.players);
|
||||
|
||||
return std::all_of(std::begin(m_players), std::end(m_players),
|
||||
[](auto entry) { return entry.second.game_status == PlayerGameStatus::Ok; });
|
||||
return std::all_of(std::begin(m_players), std::end(m_players), [](auto entry) {
|
||||
return entry.second.game_status == SyncIdentifierComparison::SameGame;
|
||||
});
|
||||
}
|
||||
|
||||
void NetPlayClient::ComputeMD5(const std::string& file_identifier)
|
||||
void NetPlayClient::ComputeMD5(const SyncIdentifier& sync_identifier)
|
||||
{
|
||||
if (m_should_compute_MD5)
|
||||
return;
|
||||
|
||||
m_dialog->ShowMD5Dialog(file_identifier);
|
||||
m_dialog->ShowMD5Dialog(sync_identifier.game_id);
|
||||
m_should_compute_MD5 = true;
|
||||
|
||||
std::string file;
|
||||
if (file_identifier == WII_SDCARD)
|
||||
if (sync_identifier == GetSDCardIdentifier())
|
||||
file = File::GetUserPath(F_WIISDCARD_IDX);
|
||||
else
|
||||
file = m_dialog->FindGame(file_identifier);
|
||||
else if (auto game = m_dialog->FindGameFile(sync_identifier))
|
||||
file = game->GetFilePath();
|
||||
|
||||
if (file.empty() || !File::Exists(file))
|
||||
{
|
||||
@@ -2348,6 +2372,11 @@ void NetPlayClient::AdjustPadBufferSize(const unsigned int size)
|
||||
m_dialog->OnPadBufferChanged(size);
|
||||
}
|
||||
|
||||
SyncIdentifier NetPlayClient::GetSDCardIdentifier()
|
||||
{
|
||||
return SyncIdentifier{{}, "sd", {}, {}, {}, {}};
|
||||
}
|
||||
|
||||
bool IsNetPlayRunning()
|
||||
{
|
||||
return netplay_client != nullptr;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "Common/SPSCQueue.h"
|
||||
#include "Common/TraversalClient.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/SyncIdentifier.h"
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
|
||||
namespace UICommon
|
||||
@@ -42,7 +43,8 @@ public:
|
||||
virtual void Update() = 0;
|
||||
virtual void AppendChat(const std::string& msg) = 0;
|
||||
|
||||
virtual void OnMsgChangeGame(const std::string& filename) = 0;
|
||||
virtual void OnMsgChangeGame(const SyncIdentifier& sync_identifier,
|
||||
const std::string& netplay_name) = 0;
|
||||
virtual void OnMsgStartGame() = 0;
|
||||
virtual void OnMsgStopGame() = 0;
|
||||
virtual void OnMsgPowerButton() = 0;
|
||||
@@ -59,9 +61,10 @@ public:
|
||||
virtual void OnGolferChanged(bool is_golfer, const std::string& golfer_name) = 0;
|
||||
|
||||
virtual bool IsRecording() = 0;
|
||||
virtual std::string FindGame(const std::string& game) = 0;
|
||||
virtual std::shared_ptr<const UICommon::GameFile> FindGameFile(const std::string& game) = 0;
|
||||
virtual void ShowMD5Dialog(const std::string& file_identifier) = 0;
|
||||
virtual std::shared_ptr<const UICommon::GameFile>
|
||||
FindGameFile(const SyncIdentifier& sync_identifier,
|
||||
SyncIdentifierComparison* found = nullptr) = 0;
|
||||
virtual void ShowMD5Dialog(const std::string& title) = 0;
|
||||
virtual void SetMD5Progress(int pid, int progress) = 0;
|
||||
virtual void SetMD5Result(int pid, const std::string& result) = 0;
|
||||
virtual void AbortMD5() = 0;
|
||||
@@ -75,13 +78,6 @@ public:
|
||||
virtual void SetChunkedProgress(int pid, u64 progress) = 0;
|
||||
};
|
||||
|
||||
enum class PlayerGameStatus
|
||||
{
|
||||
Unknown,
|
||||
Ok,
|
||||
NotFound
|
||||
};
|
||||
|
||||
class Player
|
||||
{
|
||||
public:
|
||||
@@ -89,7 +85,7 @@ public:
|
||||
std::string name;
|
||||
std::string revision;
|
||||
u32 ping;
|
||||
PlayerGameStatus game_status;
|
||||
SyncIdentifierComparison game_status;
|
||||
|
||||
bool IsHost() const { return pid == 1; }
|
||||
};
|
||||
@@ -149,6 +145,8 @@ public:
|
||||
|
||||
void AdjustPadBufferSize(unsigned int size);
|
||||
|
||||
static SyncIdentifier GetSDCardIdentifier();
|
||||
|
||||
protected:
|
||||
struct AsyncQueueEntry
|
||||
{
|
||||
@@ -182,7 +180,7 @@ protected:
|
||||
ENetPeer* m_server = nullptr;
|
||||
std::thread m_thread;
|
||||
|
||||
std::string m_selected_game;
|
||||
SyncIdentifier m_selected_game;
|
||||
Common::Flag m_is_running{false};
|
||||
Common::Flag m_do_loop{true};
|
||||
|
||||
@@ -237,7 +235,7 @@ private:
|
||||
void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
|
||||
void Disconnect();
|
||||
bool Connect();
|
||||
void ComputeMD5(const std::string& file_identifier);
|
||||
void ComputeMD5(const SyncIdentifier& sync_identifier);
|
||||
void DisplayPlayersPing();
|
||||
u32 GetPlayersMaxPing() const;
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/Uids.h"
|
||||
#include "Core/NetPlayClient.h" //for NetPlayUI
|
||||
#include "Core/SyncIdentifier.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h"
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
@@ -182,7 +183,7 @@ void NetPlayServer::SetupIndex()
|
||||
session.region = Config::Get(Config::NETPLAY_INDEX_REGION);
|
||||
session.has_password = !Config::Get(Config::NETPLAY_INDEX_PASSWORD).empty();
|
||||
session.method = m_traversal_client ? "traversal" : "direct";
|
||||
session.game_id = m_selected_game.empty() ? "UNKNOWN" : m_selected_game;
|
||||
session.game_id = m_selected_game_name.empty() ? "UNKNOWN" : m_selected_game_name;
|
||||
session.player_count = static_cast<int>(m_players.size());
|
||||
session.in_game = m_is_running;
|
||||
session.port = GetPort();
|
||||
@@ -238,7 +239,7 @@ void NetPlayServer::ThreadFunc()
|
||||
SendToClients(spac);
|
||||
|
||||
m_index.SetPlayerCount(static_cast<int>(m_players.size()));
|
||||
m_index.SetGame(m_selected_game);
|
||||
m_index.SetGame(m_selected_game_name);
|
||||
m_index.SetInGame(m_is_running);
|
||||
|
||||
m_update_pings = false;
|
||||
@@ -348,6 +349,20 @@ void NetPlayServer::ThreadFunc()
|
||||
}
|
||||
} // namespace NetPlay
|
||||
|
||||
static void SendSyncIdentifier(sf::Packet& spac, const SyncIdentifier& sync_identifier)
|
||||
{
|
||||
// We cast here due to a potential long vs long long mismatch
|
||||
spac << static_cast<sf::Uint64>(sync_identifier.dol_elf_size);
|
||||
|
||||
spac << sync_identifier.game_id;
|
||||
spac << sync_identifier.revision;
|
||||
spac << sync_identifier.disc_number;
|
||||
spac << sync_identifier.is_datel;
|
||||
|
||||
for (const u8& x : sync_identifier.sync_hash)
|
||||
spac << x;
|
||||
}
|
||||
|
||||
// called from ---NETPLAY--- thread
|
||||
unsigned int NetPlayServer::OnConnect(ENetPeer* socket, sf::Packet& rpac)
|
||||
{
|
||||
@@ -413,11 +428,12 @@ unsigned int NetPlayServer::OnConnect(ENetPeer* socket, sf::Packet& rpac)
|
||||
Send(player.socket, spac);
|
||||
|
||||
// send new client the selected game
|
||||
if (!m_selected_game.empty())
|
||||
if (!m_selected_game_name.empty())
|
||||
{
|
||||
spac.clear();
|
||||
spac << static_cast<MessageId>(NP_MSG_CHANGE_GAME);
|
||||
spac << m_selected_game;
|
||||
SendSyncIdentifier(spac, m_selected_game_identifier);
|
||||
spac << m_selected_game_name;
|
||||
Send(player.socket, spac);
|
||||
}
|
||||
|
||||
@@ -913,7 +929,7 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
|
||||
u32 status;
|
||||
packet >> status;
|
||||
|
||||
m_players[player.pid].game_status = static_cast<PlayerGameStatus>(status);
|
||||
m_players[player.pid].game_status = static_cast<SyncIdentifierComparison>(status);
|
||||
|
||||
// send msg to other clients
|
||||
sf::Packet spac;
|
||||
@@ -1153,16 +1169,19 @@ void NetPlayServer::SendChatMessage(const std::string& msg)
|
||||
}
|
||||
|
||||
// called from ---GUI--- thread
|
||||
bool NetPlayServer::ChangeGame(const std::string& game)
|
||||
bool NetPlayServer::ChangeGame(const SyncIdentifier& sync_identifier,
|
||||
const std::string& netplay_name)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lkg(m_crit.game);
|
||||
|
||||
m_selected_game = game;
|
||||
m_selected_game_identifier = sync_identifier;
|
||||
m_selected_game_name = netplay_name;
|
||||
|
||||
// send changed game to clients
|
||||
sf::Packet spac;
|
||||
spac << static_cast<MessageId>(NP_MSG_CHANGE_GAME);
|
||||
spac << game;
|
||||
SendSyncIdentifier(spac, m_selected_game_identifier);
|
||||
spac << m_selected_game_name;
|
||||
|
||||
SendAsyncToClients(std::move(spac));
|
||||
|
||||
@@ -1170,11 +1189,11 @@ bool NetPlayServer::ChangeGame(const std::string& game)
|
||||
}
|
||||
|
||||
// called from ---GUI--- thread
|
||||
bool NetPlayServer::ComputeMD5(const std::string& file_identifier)
|
||||
bool NetPlayServer::ComputeMD5(const SyncIdentifier& sync_identifier)
|
||||
{
|
||||
sf::Packet spac;
|
||||
spac << static_cast<MessageId>(NP_MSG_COMPUTE_MD5);
|
||||
spac << file_identifier;
|
||||
SendSyncIdentifier(spac, sync_identifier);
|
||||
|
||||
SendAsyncToClients(std::move(spac));
|
||||
|
||||
@@ -1260,7 +1279,7 @@ bool NetPlayServer::StartGame()
|
||||
const sf::Uint64 initial_rtc = GetInitialNetPlayRTC();
|
||||
|
||||
const std::string region = SConfig::GetDirectoryForRegion(
|
||||
SConfig::ToGameCubeRegion(m_dialog->FindGameFile(m_selected_game)->GetRegion()));
|
||||
SConfig::ToGameCubeRegion(m_dialog->FindGameFile(m_selected_game_identifier)->GetRegion()));
|
||||
|
||||
// sync GC SRAM with clients
|
||||
if (!g_SRAM_netplay_initialized)
|
||||
@@ -1395,7 +1414,7 @@ bool NetPlayServer::SyncSaveData()
|
||||
}
|
||||
}
|
||||
|
||||
const auto game = m_dialog->FindGameFile(m_selected_game);
|
||||
const auto game = m_dialog->FindGameFile(m_selected_game_identifier);
|
||||
if (game == nullptr)
|
||||
{
|
||||
PanicAlertT("Selected game doesn't exist in game list!");
|
||||
@@ -1618,7 +1637,7 @@ bool NetPlayServer::SyncCodes()
|
||||
m_codes_synced = false;
|
||||
|
||||
// Get Game Path
|
||||
const auto game = m_dialog->FindGameFile(m_selected_game);
|
||||
const auto game = m_dialog->FindGameFile(m_selected_game_identifier);
|
||||
if (game == nullptr)
|
||||
{
|
||||
PanicAlertT("Selected game doesn't exist in game list!");
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <SFML/Network/Packet.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -14,19 +15,20 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "Common/Event.h"
|
||||
#include "Common/QoSSession.h"
|
||||
#include "Common/SPSCQueue.h"
|
||||
#include "Common/Timer.h"
|
||||
#include "Common/TraversalClient.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/SyncIdentifier.h"
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
#include "UICommon/NetPlayIndex.h"
|
||||
|
||||
namespace NetPlay
|
||||
{
|
||||
class NetPlayUI;
|
||||
enum class PlayerGameStatus;
|
||||
|
||||
class NetPlayServer : public TraversalClientClient
|
||||
{
|
||||
@@ -43,8 +45,8 @@ public:
|
||||
const NetTraversalConfig& traversal_config);
|
||||
~NetPlayServer();
|
||||
|
||||
bool ChangeGame(const std::string& game);
|
||||
bool ComputeMD5(const std::string& file_identifier);
|
||||
bool ChangeGame(const SyncIdentifier& sync_identifier, const std::string& netplay_name);
|
||||
bool ComputeMD5(const SyncIdentifier& sync_identifier);
|
||||
bool AbortMD5();
|
||||
void SendChatMessage(const std::string& msg);
|
||||
|
||||
@@ -80,7 +82,7 @@ private:
|
||||
PlayerId pid;
|
||||
std::string name;
|
||||
std::string revision;
|
||||
PlayerGameStatus game_status;
|
||||
SyncIdentifierComparison game_status;
|
||||
bool has_ipl_dump;
|
||||
|
||||
ENetPeer* socket;
|
||||
@@ -180,7 +182,8 @@ private:
|
||||
Common::SPSCQueue<AsyncQueueEntry, false> m_async_queue;
|
||||
Common::SPSCQueue<ChunkedDataQueueEntry, false> m_chunked_data_queue;
|
||||
|
||||
std::string m_selected_game;
|
||||
SyncIdentifier m_selected_game_identifier;
|
||||
std::string m_selected_game_name;
|
||||
std::thread m_thread;
|
||||
Common::Event m_chunked_data_event;
|
||||
Common::Event m_chunked_data_complete_event;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2020 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace NetPlay
|
||||
{
|
||||
struct SyncIdentifier
|
||||
{
|
||||
u64 dol_elf_size;
|
||||
std::string game_id;
|
||||
u16 revision;
|
||||
u8 disc_number;
|
||||
bool is_datel;
|
||||
|
||||
// This hash is intended to be (but is not guaranteed to be):
|
||||
// 1. Identical for discs with no differences that affect netplay/TAS sync
|
||||
// 2. Different for discs with differences that affect netplay/TAS sync
|
||||
// 3. Much faster than hashing the entire disc
|
||||
// The way the hash is calculated may change with updates to Dolphin.
|
||||
std::array<u8, 20> sync_hash;
|
||||
|
||||
bool operator==(const SyncIdentifier& s) const
|
||||
{
|
||||
return std::tie(dol_elf_size, game_id, revision, disc_number, is_datel, sync_hash) ==
|
||||
std::tie(s.dol_elf_size, s.game_id, s.revision, s.disc_number, s.is_datel, s.sync_hash);
|
||||
}
|
||||
bool operator!=(const SyncIdentifier& s) const { return !operator==(s); }
|
||||
};
|
||||
|
||||
enum class SyncIdentifierComparison
|
||||
{
|
||||
SameGame,
|
||||
DifferentVersion,
|
||||
DifferentGame,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
} // namespace NetPlay
|
||||
@@ -245,19 +245,26 @@ bool ExportBI2Data(const Volume& volume, const Partition& partition,
|
||||
return ExportData(volume, partition, 0x440, 0x2000, export_filename);
|
||||
}
|
||||
|
||||
std::optional<u64> GetApploaderSize(const Volume& volume, const Partition& partition)
|
||||
{
|
||||
constexpr u64 header_size = 0x20;
|
||||
const std::optional<u32> apploader_size = volume.ReadSwapped<u32>(0x2440 + 0x14, partition);
|
||||
const std::optional<u32> trailer_size = volume.ReadSwapped<u32>(0x2440 + 0x18, partition);
|
||||
if (!apploader_size || !trailer_size)
|
||||
return std::nullopt;
|
||||
|
||||
return header_size + *apploader_size + *trailer_size;
|
||||
}
|
||||
|
||||
bool ExportApploader(const Volume& volume, const Partition& partition,
|
||||
const std::string& export_filename)
|
||||
{
|
||||
if (!IsDisc(volume.GetVolumeType()))
|
||||
return false;
|
||||
|
||||
std::optional<u32> apploader_size = volume.ReadSwapped<u32>(0x2440 + 0x14, partition);
|
||||
const std::optional<u32> trailer_size = volume.ReadSwapped<u32>(0x2440 + 0x18, partition);
|
||||
constexpr u32 header_size = 0x20;
|
||||
if (!apploader_size || !trailer_size)
|
||||
const std::optional<u64> apploader_size = GetApploaderSize(volume, partition);
|
||||
if (!apploader_size)
|
||||
return false;
|
||||
*apploader_size += *trailer_size + header_size;
|
||||
DEBUG_LOG(DISCIO, "Apploader size -> %x", *apploader_size);
|
||||
|
||||
return ExportData(volume, partition, 0x2440, *apploader_size, export_filename);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ bool ExportHeader(const Volume& volume, const Partition& partition,
|
||||
const std::string& export_filename);
|
||||
bool ExportBI2Data(const Volume& volume, const Partition& partition,
|
||||
const std::string& export_filename);
|
||||
std::optional<u64> GetApploaderSize(const Volume& volume, const Partition& partition);
|
||||
bool ExportApploader(const Volume& volume, const Partition& partition,
|
||||
const std::string& export_filename);
|
||||
std::optional<u64> GetBootDOLOffset(const Volume& volume, const Partition& partition);
|
||||
|
||||
@@ -9,12 +9,16 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
#include "Core/IOS/ES/Formats.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/VolumeDisc.h"
|
||||
@@ -28,6 +32,43 @@ const IOS::ES::TicketReader Volume::INVALID_TICKET{};
|
||||
const IOS::ES::TMDReader Volume::INVALID_TMD{};
|
||||
const std::vector<u8> Volume::INVALID_CERT_CHAIN{};
|
||||
|
||||
template <typename T>
|
||||
static void AddToSyncHash(mbedtls_sha1_context* context, const T& data)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
mbedtls_sha1_update_ret(context, reinterpret_cast<const u8*>(&data), sizeof(data));
|
||||
}
|
||||
|
||||
void Volume::ReadAndAddToSyncHash(mbedtls_sha1_context* context, u64 offset, u64 length,
|
||||
const Partition& partition) const
|
||||
{
|
||||
std::vector<u8> buffer(length);
|
||||
if (Read(offset, length, buffer.data(), partition))
|
||||
mbedtls_sha1_update_ret(context, buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
void Volume::AddTMDToSyncHash(mbedtls_sha1_context* context, const Partition& partition) const
|
||||
{
|
||||
// We want to hash some important parts of the TMD, but nothing that changes when fakesigning.
|
||||
// (Fakesigned WADs are very popular, and we don't want people with properly signed WADs to
|
||||
// unnecessarily be at a disadvantage due to most netplay partners having fakesigned WADs.)
|
||||
|
||||
const IOS::ES::TMDReader& tmd = GetTMD(partition);
|
||||
if (!tmd.IsValid())
|
||||
return;
|
||||
|
||||
AddToSyncHash(context, tmd.GetIOSId());
|
||||
AddToSyncHash(context, tmd.GetTitleId());
|
||||
AddToSyncHash(context, tmd.GetTitleFlags());
|
||||
AddToSyncHash(context, tmd.GetGroupId());
|
||||
AddToSyncHash(context, tmd.GetRegion());
|
||||
AddToSyncHash(context, tmd.GetTitleVersion());
|
||||
AddToSyncHash(context, tmd.GetBootIndex());
|
||||
|
||||
for (const IOS::ES::Content& content : tmd.GetContents())
|
||||
AddToSyncHash(context, content);
|
||||
}
|
||||
|
||||
std::map<Language, std::string> Volume::ReadWiiNames(const std::vector<char16_t>& data)
|
||||
{
|
||||
std::map<Language, std::string> names;
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
@@ -143,6 +145,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
|
||||
@@ -156,6 +165,10 @@ protected:
|
||||
return CP1252ToUTF8(string);
|
||||
}
|
||||
|
||||
void ReadAndAddToSyncHash(mbedtls_sha1_context* context, u64 offset, u64 length,
|
||||
const Partition& partition) const;
|
||||
void AddTMDToSyncHash(mbedtls_sha1_context* context, const Partition& partition) const;
|
||||
|
||||
virtual u32 GetOffsetShift() const { return 0; }
|
||||
static std::map<Language, std::string> ReadWiiNames(const std::vector<char16_t>& data);
|
||||
|
||||
|
||||
@@ -4,11 +4,17 @@
|
||||
|
||||
#include "DiscIO/VolumeDisc.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "DiscIO/DiscExtractor.h"
|
||||
#include "DiscIO/Enums.h"
|
||||
#include "DiscIO/Filesystem.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
@@ -90,4 +96,35 @@ bool VolumeDisc::IsNKit() const
|
||||
return ReadSwapped<u32>(0x200, PARTITION_NONE) == NKIT_MAGIC;
|
||||
}
|
||||
|
||||
void VolumeDisc::AddGamePartitionToSyncHash(mbedtls_sha1_context* context) const
|
||||
{
|
||||
const Partition partition = GetGamePartition();
|
||||
|
||||
// All headers at the beginning of the partition, plus the apploader
|
||||
ReadAndAddToSyncHash(context, 0, 0x2440 + GetApploaderSize(*this, partition).value_or(0),
|
||||
partition);
|
||||
|
||||
// Boot DOL (may be missing if this is a Datel disc)
|
||||
const std::optional<u64> dol_offset = GetBootDOLOffset(*this, partition);
|
||||
if (dol_offset)
|
||||
{
|
||||
ReadAndAddToSyncHash(context, *dol_offset,
|
||||
GetBootDOLSize(*this, partition, *dol_offset).value_or(0), partition);
|
||||
}
|
||||
|
||||
// File system
|
||||
const std::optional<u64> fst_offset = GetFSTOffset(*this, partition);
|
||||
if (fst_offset)
|
||||
ReadAndAddToSyncHash(context, *fst_offset, GetFSTSize(*this, partition).value_or(0), partition);
|
||||
|
||||
// opening.bnr (name and banner)
|
||||
const FileSystem* file_system = GetFileSystem(partition);
|
||||
if (file_system)
|
||||
{
|
||||
std::unique_ptr<FileInfo> file_info = file_system->FindFileInfo("opening.bnr");
|
||||
if (file_info && !file_info->IsDirectory())
|
||||
ReadAndAddToSyncHash(context, file_info->GetOffset(), file_info->GetSize(), partition);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace DiscIO
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
@@ -26,6 +28,7 @@ public:
|
||||
|
||||
protected:
|
||||
Region RegionCodeToRegion(std::optional<u32> region_code) const;
|
||||
void AddGamePartitionToSyncHash(mbedtls_sha1_context* context) const;
|
||||
};
|
||||
|
||||
} // namespace DiscIO
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <mbedtls/sha1.h>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/ColorUtil.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
@@ -137,6 +139,19 @@ bool VolumeGC::IsDatelDisc() const
|
||||
return !GetBootDOLOffset(*this, PARTITION_NONE).has_value();
|
||||
}
|
||||
|
||||
std::array<u8, 20> VolumeGC::GetSyncHash() const
|
||||
{
|
||||
mbedtls_sha1_context context;
|
||||
mbedtls_sha1_init(&context);
|
||||
mbedtls_sha1_starts_ret(&context);
|
||||
|
||||
AddGamePartitionToSyncHash(&context);
|
||||
|
||||
std::array<u8, 20> hash;
|
||||
mbedtls_sha1_finish_ret(&context, hash.data());
|
||||
return hash;
|
||||
}
|
||||
|
||||
VolumeGC::ConvertedGCBanner VolumeGC::LoadBannerFile() const
|
||||
{
|
||||
GCBanner banner_file;
|
||||
|
||||
@@ -51,6 +51,8 @@ public:
|
||||
u64 GetRawSize() const override;
|
||||
const BlobReader& GetBlobReader() const override;
|
||||
|
||||
std::array<u8, 20> GetSyncHash() const override;
|
||||
|
||||
private:
|
||||
static const u32 GC_BANNER_WIDTH = 96;
|
||||
static const u32 GC_BANNER_HEIGHT = 32;
|
||||
|
||||
@@ -479,8 +479,8 @@ std::vector<Partition> VolumeVerifier::CheckPartitions()
|
||||
AddProblem(Severity::Low,
|
||||
Common::GetStringT(
|
||||
"The data partition is not at its normal position. This will affect the "
|
||||
"emulated loading times. When using NetPlay or sending input recordings to "
|
||||
"other people, you will experience desyncs if anyone is using a good dump."));
|
||||
"emulated loading times. You will be unable to share input recordings and use "
|
||||
"NetPlay with anyone who is using a good dump."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,10 +783,10 @@ void VolumeVerifier::CheckDiscSize(const std::vector<Partition>& partitions)
|
||||
{
|
||||
AddProblem(
|
||||
Severity::Low,
|
||||
Common::GetStringT("This disc image has an unusual size. This will likely make the "
|
||||
"emulated loading times longer. When using NetPlay or sending "
|
||||
"input recordings to other people, you will likely experience "
|
||||
"desyncs if anyone is using a good dump."));
|
||||
Common::GetStringT(
|
||||
"This disc image has an unusual size. This will likely make the emulated "
|
||||
"loading times longer. You will likely be unable to share input recordings "
|
||||
"and use NetPlay with anyone who is using a good dump."));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -40,6 +40,7 @@ VolumeWAD::VolumeWAD(std::unique_ptr<BlobReader> reader) : m_reader(std::move(re
|
||||
m_ticket_size = m_reader->ReadSwapped<u32>(0x10).value_or(0);
|
||||
m_tmd_size = m_reader->ReadSwapped<u32>(0x14).value_or(0);
|
||||
m_data_size = m_reader->ReadSwapped<u32>(0x18).value_or(0);
|
||||
m_opening_bnr_size = m_reader->ReadSwapped<u32>(0x1C).value_or(0);
|
||||
|
||||
m_cert_chain_offset = Common::AlignUp(m_hdr_size, 0x40);
|
||||
m_ticket_offset = m_cert_chain_offset + Common::AlignUp(m_cert_chain_size, 0x40);
|
||||
@@ -342,4 +343,22 @@ const BlobReader& VolumeWAD::GetBlobReader() const
|
||||
return *m_reader;
|
||||
}
|
||||
|
||||
std::array<u8, 20> VolumeWAD::GetSyncHash() const
|
||||
{
|
||||
// We can skip hashing the contents since the TMD contains hashes of the contents.
|
||||
// We specifically don't hash the ticket, since its console ID can differ without any problems.
|
||||
|
||||
mbedtls_sha1_context context;
|
||||
mbedtls_sha1_init(&context);
|
||||
mbedtls_sha1_starts_ret(&context);
|
||||
|
||||
AddTMDToSyncHash(&context, PARTITION_NONE);
|
||||
|
||||
ReadAndAddToSyncHash(&context, m_opening_bnr_offset, m_opening_bnr_size, PARTITION_NONE);
|
||||
|
||||
std::array<u8, 20> hash;
|
||||
mbedtls_sha1_finish_ret(&context, hash.data());
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace DiscIO
|
||||
|
||||
@@ -70,6 +70,8 @@ public:
|
||||
u64 GetRawSize() const override;
|
||||
const BlobReader& GetBlobReader() const override;
|
||||
|
||||
std::array<u8, 20> GetSyncHash() const override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<BlobReader> m_reader;
|
||||
IOS::ES::TicketReader m_ticket;
|
||||
@@ -85,6 +87,7 @@ private:
|
||||
u32 m_ticket_size = 0;
|
||||
u32 m_tmd_size = 0;
|
||||
u32 m_data_size = 0;
|
||||
u32 m_opening_bnr_size = 0;
|
||||
};
|
||||
|
||||
} // namespace DiscIO
|
||||
|
||||
@@ -362,6 +362,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);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user