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

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