mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 06:09:50 -06:00
Merge pull request #8861 from JosJuice/netplay-hash
Make netplay's "same game" check more robust
This commit is contained in:
@ -398,9 +398,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);
|
||||
@ -740,6 +738,11 @@ GameList::FindSecondDisc(const UICommon::GameFile& game) const
|
||||
return m_model->FindSecondDisc(game);
|
||||
}
|
||||
|
||||
std::string GameList::GetNetPlayName(const UICommon::GameFile& game) const
|
||||
{
|
||||
return m_model->GetNetPlayName(game);
|
||||
}
|
||||
|
||||
void GameList::SetViewColumn(int col, bool view)
|
||||
{
|
||||
m_list->setColumnHidden(col, !view);
|
||||
|
@ -32,6 +32,7 @@ public:
|
||||
bool HasMultipleSelected() const;
|
||||
std::shared_ptr<const UICommon::GameFile> FindGame(const std::string& path) const;
|
||||
std::shared_ptr<const UICommon::GameFile> FindSecondDisc(const UICommon::GameFile& game) const;
|
||||
std::string GetNetPlayName(const UICommon::GameFile& game) const;
|
||||
|
||||
void SetListView() { SetPreferredView(true); }
|
||||
void SetGridView() { SetPreferredView(false); }
|
||||
@ -47,7 +48,7 @@ public:
|
||||
|
||||
signals:
|
||||
void GameSelected();
|
||||
void NetPlayHost(const QString& game_id);
|
||||
void NetPlayHost(const UICommon::GameFile& game);
|
||||
void SelectionChanged(std::shared_ptr<const UICommon::GameFile> game_file);
|
||||
void OpenGeneralSettings();
|
||||
|
||||
|
@ -313,14 +313,9 @@ std::shared_ptr<const UICommon::GameFile> GameListModel::GetGameFile(int index)
|
||||
return m_games[index];
|
||||
}
|
||||
|
||||
QString GameListModel::GetPath(int index) const
|
||||
std::string GameListModel::GetNetPlayName(const UICommon::GameFile& game) const
|
||||
{
|
||||
return QString::fromStdString(m_games[index]->GetFilePath());
|
||||
}
|
||||
|
||||
QString GameListModel::GetUniqueIdentifier(int index) const
|
||||
{
|
||||
return QString::fromStdString(m_games[index]->GetUniqueIdentifier());
|
||||
return game.GetNetPlayName(m_title_database);
|
||||
}
|
||||
|
||||
void GameListModel::AddGame(const std::shared_ptr<const UICommon::GameFile>& game)
|
||||
|
@ -37,10 +37,7 @@ 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;
|
||||
std::string GetNetPlayName(const UICommon::GameFile& game) const;
|
||||
bool ShouldDisplayGameListItem(int index) const;
|
||||
void SetSearchTerm(const QString& term);
|
||||
|
||||
|
@ -1374,7 +1374,7 @@ bool MainWindow::NetPlayJoin()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MainWindow::NetPlayHost(const QString& game_id)
|
||||
bool MainWindow::NetPlayHost(const UICommon::GameFile& game)
|
||||
{
|
||||
if (Core::IsRunning())
|
||||
{
|
||||
@ -1419,7 +1419,8 @@ bool MainWindow::NetPlayHost(const QString& game_id)
|
||||
return false;
|
||||
}
|
||||
|
||||
Settings::Instance().GetNetPlayServer()->ChangeGame(game_id.toStdString());
|
||||
Settings::Instance().GetNetPlayServer()->ChangeGame(game.GetSyncIdentifier(),
|
||||
m_game_list->GetNetPlayName(game));
|
||||
|
||||
// Join our local server
|
||||
return NetPlayJoin();
|
||||
|
@ -158,7 +158,7 @@ private:
|
||||
|
||||
void NetPlayInit();
|
||||
bool NetPlayJoin();
|
||||
bool NetPlayHost(const QString& game_id);
|
||||
bool NetPlayHost(const UICommon::GameFile& game);
|
||||
void NetPlayQuit();
|
||||
|
||||
void OnBootGameCubeIPL(DiscIO::Region region);
|
||||
|
@ -4,12 +4,15 @@
|
||||
|
||||
#include "DolphinQt/NetPlay/GameListDialog.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QListWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "DolphinQt/GameList/GameListModel.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
#include "UICommon/GameFile.h"
|
||||
|
||||
GameListDialog::GameListDialog(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
@ -35,12 +38,8 @@ void GameListDialog::CreateWidgets()
|
||||
|
||||
void GameListDialog::ConnectWidgets()
|
||||
{
|
||||
connect(m_game_list, &QListWidget::itemSelectionChanged, [this] {
|
||||
int row = m_game_list->currentRow();
|
||||
|
||||
m_button_box->setEnabled(row != -1);
|
||||
m_game_id = m_game_list->currentItem()->text();
|
||||
});
|
||||
connect(m_game_list, &QListWidget::itemSelectionChanged,
|
||||
[this] { m_button_box->setEnabled(m_game_list->currentRow() != -1); });
|
||||
|
||||
connect(m_game_list, &QListWidget::itemDoubleClicked, this, &GameListDialog::accept);
|
||||
connect(m_button_box, &QDialogButtonBox::accepted, this, &GameListDialog::accept);
|
||||
@ -54,16 +53,21 @@ 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_list_model->GetNetPlayName(*game)));
|
||||
item->setData(Qt::UserRole, QVariant::fromValue(std::move(game)));
|
||||
m_game_list->addItem(item);
|
||||
}
|
||||
|
||||
m_game_list->sortItems();
|
||||
}
|
||||
|
||||
const QString& GameListDialog::GetSelectedUniqueID() const
|
||||
const UICommon::GameFile& GameListDialog::GetSelectedGame() const
|
||||
{
|
||||
return m_game_id;
|
||||
auto items = m_game_list->selectedItems();
|
||||
return *items[0]->data(Qt::UserRole).value<std::shared_ptr<const UICommon::GameFile>>();
|
||||
}
|
||||
|
||||
int GameListDialog::exec()
|
||||
|
@ -11,6 +11,11 @@ class QVBoxLayout;
|
||||
class QListWidget;
|
||||
class QDialogButtonBox;
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
}
|
||||
|
||||
class GameListDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -18,7 +23,7 @@ public:
|
||||
explicit GameListDialog(QWidget* parent);
|
||||
|
||||
int exec() override;
|
||||
const QString& GetSelectedUniqueID() const;
|
||||
const UICommon::GameFile& GetSelectedGame() const;
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
@ -28,5 +33,4 @@ private:
|
||||
QVBoxLayout* m_main_layout;
|
||||
QListWidget* m_game_list;
|
||||
QDialogButtonBox* m_button_box;
|
||||
QString m_game_id;
|
||||
};
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <QTableWidget>
|
||||
#include <QTextBrowser>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
@ -37,6 +38,7 @@
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/NetPlayServer.h"
|
||||
#include "Core/SyncIdentifier.h"
|
||||
|
||||
#include "DolphinQt/GameList/GameListModel.h"
|
||||
#include "DolphinQt/NetPlay/ChunkedProgressDialog.h"
|
||||
@ -153,17 +155,19 @@ void NetPlayDialog::CreateMainLayout()
|
||||
|
||||
m_md5_menu = m_menu_bar->addMenu(tr("Checksum"));
|
||||
m_md5_menu->addAction(tr("Current game"), this, [this] {
|
||||
Settings::Instance().GetNetPlayServer()->ComputeMD5(m_current_game);
|
||||
Settings::Instance().GetNetPlayServer()->ComputeMD5(m_current_game_identifier);
|
||||
});
|
||||
m_md5_menu->addAction(tr("Other game..."), this, [this] {
|
||||
GameListDialog gld(this);
|
||||
|
||||
if (gld.exec() != QDialog::Accepted)
|
||||
return;
|
||||
Settings::Instance().GetNetPlayServer()->ComputeMD5(gld.GetSelectedUniqueID().toStdString());
|
||||
Settings::Instance().GetNetPlayServer()->ComputeMD5(gld.GetSelectedGame().GetSyncIdentifier());
|
||||
});
|
||||
m_md5_menu->addAction(tr("SD Card"), this, [] {
|
||||
Settings::Instance().GetNetPlayServer()->ComputeMD5(
|
||||
NetPlay::NetPlayClient::GetSDCardIdentifier());
|
||||
});
|
||||
m_md5_menu->addAction(tr("SD Card"), this,
|
||||
[] { Settings::Instance().GetNetPlayServer()->ComputeMD5(WII_SDCARD); });
|
||||
|
||||
m_other_menu = m_menu_bar->addMenu(tr("Other"));
|
||||
m_record_input_action = m_other_menu->addAction(tr("Record Inputs"));
|
||||
@ -321,9 +325,14 @@ 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);
|
||||
Settings& settings = Settings::Instance();
|
||||
|
||||
const UICommon::GameFile& game = gld.GetSelectedGame();
|
||||
const std::string netplay_name = settings.GetGameListModel()->GetNetPlayName(game);
|
||||
|
||||
settings.GetNetPlayServer()->ChangeGame(game.GetSyncIdentifier(), netplay_name);
|
||||
Settings::GetQSettings().setValue(QStringLiteral("netplay/hostgame"),
|
||||
QString::fromStdString(netplay_name));
|
||||
}
|
||||
});
|
||||
|
||||
@ -416,7 +425,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 +592,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 +612,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 +623,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 +671,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 +817,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 +873,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 +1036,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 +1140,15 @@ void NetPlayDialog::SaveSettings()
|
||||
Config::SetBase(Config::NETPLAY_NETWORK_MODE, network_mode);
|
||||
}
|
||||
|
||||
void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier)
|
||||
void NetPlayDialog::ShowMD5Dialog(const std::string& title)
|
||||
{
|
||||
QueueOnObject(this, [this, file_identifier] {
|
||||
QueueOnObject(this, [this, title] {
|
||||
m_md5_menu->setEnabled(false);
|
||||
|
||||
if (m_md5_dialog->isVisible())
|
||||
m_md5_dialog->close();
|
||||
|
||||
m_md5_dialog->show(QString::fromStdString(file_identifier));
|
||||
m_md5_dialog->show(QString::fromStdString(title));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,8 @@ public:
|
||||
void Update() override;
|
||||
void AppendChat(const std::string& msg) override;
|
||||
|
||||
void OnMsgChangeGame(const std::string& filename) override;
|
||||
void OnMsgChangeGame(const NetPlay::SyncIdentifier& sync_identifier,
|
||||
const std::string& netplay_name) override;
|
||||
void OnMsgStartGame() override;
|
||||
void OnMsgStopGame() override;
|
||||
void OnMsgPowerButton() override;
|
||||
@ -65,13 +66,14 @@ public:
|
||||
void OnIndexRefreshFailed(const std::string error) override;
|
||||
|
||||
bool IsRecording() override;
|
||||
std::string FindGame(const std::string& game) override;
|
||||
std::shared_ptr<const UICommon::GameFile> FindGameFile(const std::string& game) override;
|
||||
std::shared_ptr<const UICommon::GameFile>
|
||||
FindGameFile(const NetPlay::SyncIdentifier& sync_identifier,
|
||||
NetPlay::SyncIdentifierComparison* found = nullptr) override;
|
||||
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
|
||||
void ShowMD5Dialog(const std::string& file_identifier) override;
|
||||
void ShowMD5Dialog(const std::string& title) override;
|
||||
void SetMD5Progress(int pid, int progress) override;
|
||||
void SetMD5Result(int pid, const std::string& result) override;
|
||||
void AbortMD5() override;
|
||||
@ -145,7 +147,8 @@ private:
|
||||
MD5Dialog* m_md5_dialog;
|
||||
ChunkedProgressDialog* m_chunked_progress_dialog;
|
||||
PadMappingDialog* m_pad_mapping;
|
||||
std::string m_current_game;
|
||||
NetPlay::SyncIdentifier m_current_game_identifier;
|
||||
std::string m_current_game_name;
|
||||
Common::Lazy<std::string> m_external_ip_address;
|
||||
std::string m_nickname;
|
||||
GameListModel* m_game_list_model = nullptr;
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "DolphinQt/NetPlay/NetPlaySetupDialog.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
@ -24,6 +26,7 @@
|
||||
#include "DolphinQt/QtUtils/UTF8CodePointCountValidator.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "UICommon/GameFile.h"
|
||||
#include "UICommon/NetPlayIndex.h"
|
||||
|
||||
NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent)
|
||||
@ -347,7 +350,7 @@ void NetPlaySetupDialog::accept()
|
||||
return;
|
||||
}
|
||||
|
||||
emit Host(items[0]->text());
|
||||
emit Host(*items[0]->data(Qt::UserRole).value<std::shared_ptr<const UICommon::GameFile>>());
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,11 +361,11 @@ 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(m_game_list_model->GetNetPlayName(*game)));
|
||||
item->setData(Qt::UserRole, QVariant::fromValue(std::move(game)));
|
||||
m_host_games->addItem(item);
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,11 @@ class QPushButton;
|
||||
class QSpinBox;
|
||||
class QTabWidget;
|
||||
|
||||
namespace UICommon
|
||||
{
|
||||
class GameFile;
|
||||
}
|
||||
|
||||
class NetPlaySetupDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -29,7 +34,7 @@ public:
|
||||
|
||||
signals:
|
||||
bool Join();
|
||||
bool Host(const QString& game_identifier);
|
||||
bool Host(const UICommon::GameFile& game);
|
||||
|
||||
private:
|
||||
void CreateMainLayout();
|
||||
|
Reference in New Issue
Block a user