diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt
index 608caaea8b..04726b8434 100644
--- a/Source/Core/DolphinQt2/CMakeLists.txt
+++ b/Source/Core/DolphinQt2/CMakeLists.txt
@@ -66,6 +66,11 @@ set(SRCS
GameList/GridProxyModel.cpp
GameList/ListProxyModel.cpp
QtUtils/BlockUserInputFilter.cpp
+ NetPlay/GameListDialog.cpp
+ NetPlay/MD5Dialog.cpp
+ NetPlay/NetPlayDialog.cpp
+ NetPlay/NetPlaySetupDialog.cpp
+ NetPlay/PadMappingDialog.cpp
QtUtils/DoubleClickEventFilter.cpp
QtUtils/ElidedButton.cpp
QtUtils/ListTabWidget.cpp
diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
index 347a5c9a5e..b9c97c4ccc 100644
--- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj
+++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj
@@ -1,4 +1,4 @@
-
+
@@ -47,7 +47,7 @@
avrt.lib;iphlpapi.lib;winmm.lib;setupapi.lib;opengl32.lib;glu32.lib;rpcrt4.lib;comctl32.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies)
- $(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Graphics;$(ProjectDir)QtUtils;%(AdditionalIncludeDirectories)
+ $(ProjectDir)VideoInterface;$(ProjectDir)GameList;$(ProjectDir)Settings;$(ProjectDir)Config;$(ProjectDir)Config\Mapping;$(ProjectDir)Config\Graphics;$(ProjectDir)NetPlay;$(ProjectDir)QtUtils;%(AdditionalIncludeDirectories)
DolphinQt2.manifest;%(AdditionalManifestFiles)
@@ -93,6 +93,11 @@
+
+
+
+
+
@@ -110,6 +115,7 @@
+
@@ -133,7 +139,11 @@
+
+
+
+
@@ -190,6 +200,11 @@
+
+
+
+
+
@@ -308,4 +323,4 @@
-
\ No newline at end of file
+
diff --git a/Source/Core/DolphinQt2/GameList/GameFile.cpp b/Source/Core/DolphinQt2/GameList/GameFile.cpp
index c072b07f44..394b8b81a7 100644
--- a/Source/Core/DolphinQt2/GameList/GameFile.cpp
+++ b/Source/Core/DolphinQt2/GameList/GameFile.cpp
@@ -11,6 +11,7 @@
#include "Common/Assert.h"
#include "Common/FileUtil.h"
#include "Common/NandPaths.h"
+#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
#include "Core/HW/WiiSaveCrypted.h"
#include "Core/IOS/ES/ES.h"
@@ -305,6 +306,51 @@ QString GameFile::GetLanguage(DiscIO::Language lang) const
}
}
+QString GameFile::GetUniqueID() const
+{
+ std::vector info;
+ if (!GetGameID().isEmpty())
+ info.push_back(GetGameID().toStdString());
+
+ if (GetRevision() != 0)
+ {
+ info.push_back("Revision " + std::to_string(GetRevision()));
+ }
+
+ std::string name = m_long_names[DiscIO::Language::LANGUAGE_ENGLISH].toStdString();
+
+ if (name.empty())
+ {
+ if (!m_long_names.isEmpty())
+ name = m_long_names.begin().value().toStdString();
+ else
+ {
+ std::string filename, extension;
+ name = SplitPath(m_path.toStdString(), nullptr, &filename, &extension);
+ name = filename + extension;
+ }
+ }
+
+ int disc_number = GetDiscNumber() + 1;
+
+ std::string lower_name = name;
+ std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), ::tolower);
+ if (disc_number > 1 &&
+ lower_name.find(std::string("disc ") + std::to_string(disc_number)) == std::string::npos &&
+ lower_name.find(std::string("disc") + std::to_string(disc_number)) == std::string::npos)
+ {
+ info.push_back("Disc " + std::to_string(disc_number));
+ }
+
+ if (info.empty())
+ return QString::fromStdString(name);
+
+ std::ostringstream ss;
+ std::copy(info.begin(), info.end() - 1, std::ostream_iterator(ss, ", "));
+ ss << info.back();
+ return QString::fromStdString(name + " (" + ss.str() + ")");
+}
+
bool GameFile::IsInstalled() const
{
_assert_(m_platform == DiscIO::Platform::WII_WAD);
diff --git a/Source/Core/DolphinQt2/GameList/GameFile.h b/Source/Core/DolphinQt2/GameList/GameFile.h
index 46a45c6674..786274c091 100644
--- a/Source/Core/DolphinQt2/GameList/GameFile.h
+++ b/Source/Core/DolphinQt2/GameList/GameFile.h
@@ -41,6 +41,7 @@ public:
u64 GetTitleID() const { return m_title_id; }
u16 GetRevision() const { return m_revision; }
QString GetInternalName() const { return m_internal_name; }
+ QString GetUniqueID() const;
u8 GetDiscNumber() const { return m_disc_number; }
u64 GetRawSize() const { return m_raw_size; }
QPixmap GetBanner() const { return m_banner; }
diff --git a/Source/Core/DolphinQt2/GameList/GameList.cpp b/Source/Core/DolphinQt2/GameList/GameList.cpp
index b20629945f..8d09f63091 100644
--- a/Source/Core/DolphinQt2/GameList/GameList.cpp
+++ b/Source/Core/DolphinQt2/GameList/GameList.cpp
@@ -34,7 +34,7 @@ static bool CompressCB(const std::string&, float, void*);
GameList::GameList(QWidget* parent) : QStackedWidget(parent)
{
- m_model = new GameListModel(this);
+ m_model = Settings::Instance().GetGameListModel();
m_list_proxy = new ListProxyModel(this);
m_list_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
m_list_proxy->setSortRole(Qt::InitialSortOrderRole);
@@ -197,6 +197,20 @@ void GameList::ShowContextMenu(const QPoint&)
menu->addAction(tr("Open &containing folder"), this, &GameList::OpenContainingFolder);
menu->addAction(tr("Delete File..."), this, &GameList::DeleteFile);
+
+ QAction* netplay_host = new QAction(tr("Host with NetPlay"), menu);
+
+ connect(netplay_host, &QAction::triggered,
+ [this, game] { emit NetPlayHost(GameFile(game).GetUniqueID()); });
+
+ connect(this, &GameList::EmulationStarted, netplay_host,
+ [netplay_host] { netplay_host->setEnabled(false); });
+ connect(this, &GameList::EmulationStopped, netplay_host,
+ [netplay_host] { netplay_host->setEnabled(true); });
+ netplay_host->setEnabled(!Core::IsRunning());
+
+ menu->addAction(netplay_host);
+
menu->exec(QCursor::pos());
}
diff --git a/Source/Core/DolphinQt2/GameList/GameList.h b/Source/Core/DolphinQt2/GameList/GameList.h
index 8a02eecd9c..44af5b58d4 100644
--- a/Source/Core/DolphinQt2/GameList/GameList.h
+++ b/Source/Core/DolphinQt2/GameList/GameList.h
@@ -31,6 +31,7 @@ signals:
void GameSelected();
void EmulationStarted();
void EmulationStopped();
+ void NetPlayHost(const QString& game_id);
private:
void ShowContextMenu(const QPoint&);
diff --git a/Source/Core/DolphinQt2/GameList/GameListModel.h b/Source/Core/DolphinQt2/GameList/GameListModel.h
index da807cc83c..63a2d83919 100644
--- a/Source/Core/DolphinQt2/GameList/GameListModel.h
+++ b/Source/Core/DolphinQt2/GameList/GameListModel.h
@@ -27,6 +27,8 @@ public:
// Path of the Game at the specified index.
QString GetPath(int index) const { return m_games[index]->GetFilePath(); }
+ // Unique ID of the Game at the specified index
+ QString GetUniqueID(int index) const { return m_games[index]->GetUniqueID(); }
bool ShouldDisplayGameListItem(int index) const;
enum
{
diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp
index cb49592b1e..e3468ee753 100644
--- a/Source/Core/DolphinQt2/MainWindow.cpp
+++ b/Source/Core/DolphinQt2/MainWindow.cpp
@@ -18,6 +18,7 @@
#include "Core/Boot/Boot.h"
#include "Core/BootManager.h"
#include "Core/CommonTitles.h"
+#include "Core/Config/NetplaySettings.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/HW/GCKeyboard.h"
@@ -27,12 +28,13 @@
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
#include "Core/HotkeyManager.h"
#include "Core/Movie.h"
+#include "Core/NetPlayClient.h"
#include "Core/NetPlayProto.h"
+#include "Core/NetPlayServer.h"
#include "Core/State.h"
#include "DolphinQt2/AboutDialog.h"
#include "DolphinQt2/Config/ControllersWindow.h"
-
#include "DolphinQt2/Config/Graphics/GraphicsWindow.h"
#include "DolphinQt2/Config/LoggerWidget.h"
#include "DolphinQt2/Config/Mapping/MappingWindow.h"
@@ -40,6 +42,8 @@
#include "DolphinQt2/Host.h"
#include "DolphinQt2/HotkeyScheduler.h"
#include "DolphinQt2/MainWindow.h"
+#include "DolphinQt2/NetPlay/NetPlayDialog.h"
+#include "DolphinQt2/NetPlay/NetPlaySetupDialog.h"
#include "DolphinQt2/QtUtils/WindowActivationEventFilter.h"
#include "DolphinQt2/Resources.h"
#include "DolphinQt2/Settings.h"
@@ -71,6 +75,8 @@ MainWindow::MainWindow() : QMainWindow(nullptr)
InitControllers();
InitCoreCallbacks();
+
+ NetPlayInit();
}
MainWindow::~MainWindow()
@@ -195,6 +201,7 @@ void MainWindow::ConnectMenuBar()
// Tools
connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate);
connect(m_menu_bar, &MenuBar::BootWiiSystemMenu, this, &MainWindow::BootWiiSystemMenu);
+ connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog);
// View
connect(m_menu_bar, &MenuBar::ShowList, m_game_list, &GameList::SetListView);
@@ -258,6 +265,7 @@ void MainWindow::ConnectToolBar()
void MainWindow::ConnectGameList()
{
connect(m_game_list, &GameList::GameSelected, this, &MainWindow::Play);
+ connect(m_game_list, &GameList::NetPlayHost, this, &MainWindow::NetPlayHost);
connect(this, &MainWindow::EmulationStarted, m_game_list, &GameList::EmulationStarted);
connect(this, &MainWindow::EmulationStopped, m_game_list, &GameList::EmulationStopped);
}
@@ -355,8 +363,9 @@ bool MainWindow::RequestStop()
if (SConfig::GetInstance().bConfirmStop)
{
const Core::State state = Core::GetState();
- // TODO: Set to false when Netplay is running as a CPU thread
- bool pause = true;
+
+ // Only pause the game, if NetPlay is not running
+ bool pause = Settings::Instance().GetNetPlayClient() != nullptr;
if (pause)
Core::SetState(Core::State::Paused);
@@ -546,6 +555,13 @@ void MainWindow::ShowGraphicsWindow()
m_graphics_window->activateWindow();
}
+void MainWindow::ShowNetPlaySetupDialog()
+{
+ m_netplay_setup_dialog->show();
+ m_netplay_setup_dialog->raise();
+ m_netplay_setup_dialog->activateWindow();
+}
+
void MainWindow::StateLoad()
{
QString path = QFileDialog::getOpenFileName(this, tr("Select a File"), QDir::currentPath(),
@@ -616,6 +632,143 @@ void MainWindow::BootWiiSystemMenu()
Common::GetTitleContentPath(Titles::SYSTEM_MENU, Common::FROM_CONFIGURED_ROOT)));
}
+void MainWindow::NetPlayInit()
+{
+ m_netplay_setup_dialog = new NetPlaySetupDialog(this);
+ m_netplay_dialog = new NetPlayDialog(this);
+
+ connect(m_netplay_dialog, &NetPlayDialog::Boot, this, &MainWindow::StartGame);
+ connect(m_netplay_dialog, &NetPlayDialog::Stop, this, &MainWindow::RequestStop);
+ connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit);
+ connect(this, &MainWindow::EmulationStopped, m_netplay_dialog, &NetPlayDialog::EmulationStopped);
+ connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin);
+ connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Host, this, &MainWindow::NetPlayHost);
+}
+
+bool MainWindow::NetPlayJoin()
+{
+ if (Core::IsRunning())
+ {
+ QMessageBox::critical(
+ nullptr, QObject::tr("Error"),
+ QObject::tr("Can't start a NetPlay Session while a game is still running!"));
+ return false;
+ }
+
+ if (m_netplay_dialog->isVisible())
+ {
+ QMessageBox::critical(nullptr, QObject::tr("Error"),
+ QObject::tr("A NetPlay Session is already in progress!"));
+ return false;
+ }
+
+ // Settings
+ std::string host_ip, traversal_host, nickname;
+ int host_port, traversal_port;
+ bool is_traversal;
+ if (Settings::Instance().GetNetPlayServer() != nullptr)
+ {
+ host_ip = "127.0.0.1";
+ host_port = Settings::Instance().GetNetPlayServer()->GetPort();
+ }
+ else
+ {
+ host_ip = Config::Get(Config::NETPLAY_HOST_CODE);
+ host_port = Config::Get(Config::NETPLAY_HOST_PORT);
+ }
+
+ std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
+ is_traversal = traversal_choice == "traversal";
+
+ traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER);
+ traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT);
+ nickname = Config::Get(Config::NETPLAY_NICKNAME);
+
+ // Create Client
+ Settings::Instance().ResetNetPlayClient(
+ new NetPlayClient(host_ip, host_port, m_netplay_dialog, nickname,
+ Settings::Instance().GetNetPlayServer() != nullptr ? false : is_traversal,
+ traversal_host, traversal_port));
+
+ if (!Settings::Instance().GetNetPlayClient()->IsConnected())
+ {
+ QMessageBox::critical(nullptr, QObject::tr("Error"),
+ QObject::tr("Failed to connect to server"));
+ return false;
+ }
+
+ m_netplay_setup_dialog->close();
+ m_netplay_dialog->show(nickname, is_traversal);
+
+ return true;
+}
+
+bool MainWindow::NetPlayHost(const QString& game_id)
+{
+ if (Core::IsRunning())
+ {
+ QMessageBox::critical(
+ nullptr, QObject::tr("Error"),
+ QObject::tr("Can't start a NetPlay Session while a game is still running!"));
+ return false;
+ }
+
+ if (m_netplay_dialog->isVisible())
+ {
+ QMessageBox::critical(nullptr, QObject::tr("Error"),
+ QObject::tr("A NetPlay Session is already in progress!"));
+ return false;
+ }
+
+ // Settings
+ std::string traversal_host, nickname;
+ int host_port, traversal_port;
+ bool is_traversal, use_upnp;
+
+ host_port = Config::Get(Config::NETPLAY_HOST_PORT);
+ std::string traversal_choice;
+ traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
+ is_traversal = traversal_choice == "traversal";
+ use_upnp = Config::Get(Config::NETPLAY_USE_UPNP);
+
+ traversal_host = Config::Get(Config::NETPLAY_TRAVERSAL_SERVER);
+ traversal_port = Config::Get(Config::NETPLAY_TRAVERSAL_PORT);
+ nickname = Config::Get(Config::NETPLAY_NICKNAME);
+
+ if (is_traversal)
+ host_port = Config::Get(Config::NETPLAY_LISTEN_PORT);
+
+ // Create Server
+ Settings::Instance().ResetNetPlayServer(
+ new NetPlayServer(host_port, is_traversal, traversal_host, traversal_port));
+
+ if (!Settings::Instance().GetNetPlayServer()->is_connected)
+ {
+ QMessageBox::critical(
+ nullptr, QObject::tr("Failed to open server"),
+ QObject::tr(
+ "Failed to listen on port %1. Is another instance of the NetPlay server running?")
+ .arg(host_port));
+ return false;
+ }
+
+ Settings::Instance().GetNetPlayServer()->ChangeGame(game_id.toStdString());
+
+#ifdef USE_UPNP
+ if (use_upnp)
+ Settings::Instance().GetNetPlayServer()->TryPortmapping(host_port);
+#endif
+
+ // Join our local server
+ return NetPlayJoin();
+}
+
+void MainWindow::NetPlayQuit()
+{
+ Settings::Instance().ResetNetPlayClient();
+ Settings::Instance().ResetNetPlayServer();
+}
+
bool MainWindow::eventFilter(QObject* object, QEvent* event)
{
if (event->type() == QEvent::Close)
diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h
index 1987a95de2..c9804d940d 100644
--- a/Source/Core/DolphinQt2/MainWindow.h
+++ b/Source/Core/DolphinQt2/MainWindow.h
@@ -17,6 +17,10 @@
class HotkeyScheduler;
class LoggerWidget;
class MappingWindow;
+class NetPlayClient;
+class NetPlayDialog;
+class NetPlayServer;
+class NetPlaySetupDialog;
class SettingsWindow;
class ControllersWindow;
class DragEnterEvent;
@@ -88,6 +92,12 @@ private:
void ShowGraphicsWindow();
void ShowAboutDialog();
void ShowHotkeyDialog();
+ void ShowNetPlaySetupDialog();
+
+ void NetPlayInit();
+ bool NetPlayJoin();
+ bool NetPlayHost(const QString& game_id);
+ void NetPlayQuit();
void OnStopComplete();
void dragEnterEvent(QDragEnterEvent* event) override;
@@ -109,6 +119,8 @@ private:
ControllersWindow* m_controllers_window;
SettingsWindow* m_settings_window;
MappingWindow* m_hotkey_window;
+ NetPlayDialog* m_netplay_dialog;
+ NetPlaySetupDialog* m_netplay_setup_dialog;
GraphicsWindow* m_graphics_window;
LoggerWidget* m_logger_widget;
};
diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp
index 8008997abf..56391845bc 100644
--- a/Source/Core/DolphinQt2/MenuBar.cpp
+++ b/Source/Core/DolphinQt2/MenuBar.cpp
@@ -88,6 +88,9 @@ void MenuBar::AddToolsMenu()
QMenu* tools_menu = addMenu(tr("&Tools"));
m_wad_install_action = tools_menu->addAction(tr("Install WAD..."), this, &MenuBar::InstallWAD);
+ tools_menu->addAction(tr("Start NetPlay..."), this, &MenuBar::StartNetPlay);
+ tools_menu->addSeparator();
+
// Label will be set by a NANDRefresh later
m_boot_sysmenu = tools_menu->addAction(QStringLiteral(""), [this] { emit BootWiiSystemMenu(); });
m_boot_sysmenu->setEnabled(false);
diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h
index bb247f9bcb..db490271ea 100644
--- a/Source/Core/DolphinQt2/MenuBar.h
+++ b/Source/Core/DolphinQt2/MenuBar.h
@@ -38,6 +38,7 @@ signals:
void Fullscreen();
void FrameAdvance();
void Screenshot();
+ void StartNetPlay();
void StateLoad();
void StateSave();
void StateLoadSlot();
diff --git a/Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp b/Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp
new file mode 100644
index 0000000000..71155abab5
--- /dev/null
+++ b/Source/Core/DolphinQt2/NetPlay/GameListDialog.cpp
@@ -0,0 +1,70 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/NetPlay/GameListDialog.h"
+
+#include
+#include
+#include
+
+#include "DolphinQt2/GameList/GameListModel.h"
+#include "DolphinQt2/Settings.h"
+
+GameListDialog::GameListDialog(QWidget* parent) : QDialog(parent)
+{
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ CreateWidgets();
+ ConnectWidgets();
+}
+
+void GameListDialog::CreateWidgets()
+{
+ m_main_layout = new QVBoxLayout;
+ m_game_list = new QListWidget;
+ m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok);
+ m_button_box->setEnabled(false);
+
+ m_main_layout->addWidget(m_game_list);
+ m_main_layout->addWidget(m_button_box);
+
+ setLayout(m_main_layout);
+}
+
+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_button_box, &QDialogButtonBox::accepted, this, &GameListDialog::accept);
+}
+
+void GameListDialog::PopulateGameList()
+{
+ auto* game_list_model = Settings::Instance().GetGameListModel();
+
+ m_game_list->clear();
+
+ for (int i = 0; i < game_list_model->rowCount(QModelIndex()); i++)
+ {
+ auto* item = new QListWidgetItem(game_list_model->GetUniqueID(i));
+ m_game_list->addItem(item);
+ }
+
+ m_game_list->sortItems();
+}
+
+const QString& GameListDialog::GetSelectedUniqueID()
+{
+ return m_game_id;
+}
+
+int GameListDialog::exec()
+{
+ PopulateGameList();
+ return QDialog::exec();
+}
diff --git a/Source/Core/DolphinQt2/NetPlay/GameListDialog.h b/Source/Core/DolphinQt2/NetPlay/GameListDialog.h
new file mode 100644
index 0000000000..d3be480680
--- /dev/null
+++ b/Source/Core/DolphinQt2/NetPlay/GameListDialog.h
@@ -0,0 +1,32 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+
+class GameListModel;
+class QVBoxLayout;
+class QListWidget;
+class QDialogButtonBox;
+
+class GameListDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit GameListDialog(QWidget* parent);
+
+ int exec();
+ const QString& GetSelectedUniqueID();
+
+private:
+ void CreateWidgets();
+ void ConnectWidgets();
+ void PopulateGameList();
+
+ QVBoxLayout* m_main_layout;
+ QListWidget* m_game_list;
+ QDialogButtonBox* m_button_box;
+ QString m_game_id;
+};
diff --git a/Source/Core/DolphinQt2/NetPlay/MD5Dialog.cpp b/Source/Core/DolphinQt2/NetPlay/MD5Dialog.cpp
new file mode 100644
index 0000000000..b304b73adc
--- /dev/null
+++ b/Source/Core/DolphinQt2/NetPlay/MD5Dialog.cpp
@@ -0,0 +1,134 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "MD5Dialog.h"
+
+#include "DolphinQt2/Settings.h"
+
+#include
+#include
+#include
+#include
+#include
+
+static QString GetPlayerNameFromPID(int pid)
+{
+ QString player_name = QObject::tr("Invalid Player ID");
+ for (const auto* player : Settings::Instance().GetNetPlayClient()->GetPlayers())
+ {
+ if (player->pid == pid)
+ {
+ player_name = QString::fromStdString(player->name);
+ break;
+ }
+ }
+ return player_name;
+}
+
+MD5Dialog::MD5Dialog(QWidget* parent) : QDialog(parent)
+{
+ CreateWidgets();
+ ConnectWidgets();
+ setWindowTitle(tr("MD5 Checksum"));
+}
+
+void MD5Dialog::CreateWidgets()
+{
+ m_main_layout = new QVBoxLayout;
+ m_progress_box = new QGroupBox;
+ m_progress_layout = new QVBoxLayout;
+ m_button_box = new QDialogButtonBox(QDialogButtonBox::Close);
+ m_check_label = new QLabel;
+
+ m_progress_box->setLayout(m_progress_layout);
+
+ m_main_layout->addWidget(m_progress_box);
+ m_main_layout->addWidget(m_check_label);
+ m_main_layout->addWidget(m_button_box);
+ setLayout(m_main_layout);
+}
+
+void MD5Dialog::ConnectWidgets()
+{
+ connect(m_button_box, &QDialogButtonBox::rejected, this, &MD5Dialog::reject);
+}
+
+void MD5Dialog::show(const QString& title)
+{
+ m_progress_box->setTitle(title);
+
+ for (auto& pair : m_progress_bars)
+ {
+ m_progress_layout->removeWidget(pair.second);
+ pair.second->deleteLater();
+ }
+
+ for (auto& pair : m_status_labels)
+ {
+ m_progress_layout->removeWidget(pair.second);
+ pair.second->deleteLater();
+ }
+
+ m_progress_bars.clear();
+ m_status_labels.clear();
+
+ for (const auto* player : Settings::Instance().GetNetPlayClient()->GetPlayers())
+ {
+ m_progress_bars[player->pid] = new QProgressBar;
+ m_status_labels[player->pid] = new QLabel;
+
+ m_progress_layout->addWidget(m_progress_bars[player->pid]);
+ m_progress_layout->addWidget(m_status_labels[player->pid]);
+ }
+
+ m_last_result = "";
+
+ QDialog::show();
+}
+
+void MD5Dialog::SetProgress(int pid, int progress)
+{
+ QString player_name = GetPlayerNameFromPID(pid);
+
+ if (!m_status_labels.count(pid))
+ return;
+
+ m_status_labels[pid]->setText(
+ tr("%1[%2]: %3 %").arg(player_name, QString::number(pid), QString::number(progress)));
+ m_progress_bars[pid]->setValue(progress);
+}
+
+void MD5Dialog::SetResult(int pid, const std::string& result)
+{
+ QString player_name = GetPlayerNameFromPID(pid);
+
+ if (!m_status_labels.count(pid))
+ return;
+
+ m_status_labels[pid]->setText(
+ tr("%1[%2]: %3").arg(player_name, QString::number(pid), QString::fromStdString(result)));
+
+ if (m_last_result == "")
+ {
+ m_check_label->setText(tr("The hashes match!"));
+ return;
+ }
+
+ if (m_last_result != result)
+ {
+ m_check_label->setText(tr("The hashes do not match!"));
+ }
+
+ m_last_result = result;
+}
+
+void MD5Dialog::reject()
+{
+ auto* server = Settings::Instance().GetNetPlayServer();
+
+ if (server)
+ server->AbortMD5();
+
+ QDialog::reject();
+}
diff --git a/Source/Core/DolphinQt2/NetPlay/MD5Dialog.h b/Source/Core/DolphinQt2/NetPlay/MD5Dialog.h
new file mode 100644
index 0000000000..30d163163a
--- /dev/null
+++ b/Source/Core/DolphinQt2/NetPlay/MD5Dialog.h
@@ -0,0 +1,42 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+
+class QDialogButtonBox;
+class QGroupBox;
+class QLabel;
+class QProgressBar;
+class QVBoxLayout;
+class QWidget;
+
+class MD5Dialog : public QDialog
+{
+ Q_OBJECT
+public:
+ MD5Dialog(QWidget* parent);
+
+ void show(const QString& title);
+ void SetProgress(int pid, int progress);
+ void SetResult(int pid, const std::string& md5);
+
+ void reject() override;
+
+private:
+ void CreateWidgets();
+ void ConnectWidgets();
+
+ std::map m_progress_bars;
+ std::map m_status_labels;
+
+ std::string m_last_result;
+
+ QGroupBox* m_progress_box;
+ QVBoxLayout* m_progress_layout;
+ QVBoxLayout* m_main_layout;
+ QLabel* m_check_label;
+ QDialogButtonBox* m_button_box;
+};
diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp
new file mode 100644
index 0000000000..e0c16eedd3
--- /dev/null
+++ b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.cpp
@@ -0,0 +1,575 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/NetPlay/NetPlayDialog.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "Common/CommonPaths.h"
+#include "Common/TraversalClient.h"
+#include "Core/ConfigManager.h"
+#include "Core/Core.h"
+#include "Core/NetPlayServer.h"
+#include "DolphinQt2/GameList/GameList.h"
+#include "DolphinQt2/NetPlay/GameListDialog.h"
+#include "DolphinQt2/NetPlay/MD5Dialog.h"
+#include "DolphinQt2/NetPlay/PadMappingDialog.h"
+#include "DolphinQt2/QtUtils/QueueOnObject.h"
+#include "DolphinQt2/QtUtils/RunOnObject.h"
+#include "DolphinQt2/Settings.h"
+#include "VideoCommon/VideoConfig.h"
+
+NetPlayDialog::NetPlayDialog(QWidget* parent)
+ : QDialog(parent), m_game_list_model(Settings::Instance().GetGameListModel())
+{
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ setWindowTitle(tr("Dolphin NetPlay"));
+
+ m_pad_mapping = new PadMappingDialog(this);
+ m_md5_dialog = new MD5Dialog(this);
+
+ CreateChatLayout();
+ CreatePlayersLayout();
+ CreateMainLayout();
+ ConnectWidgets();
+}
+
+void NetPlayDialog::CreateMainLayout()
+{
+ m_main_layout = new QGridLayout;
+ m_game_button = new QPushButton;
+ m_md5_box = new QComboBox;
+ m_start_button = new QPushButton(tr("Start"));
+ m_buffer_size_box = new QSpinBox;
+ m_save_sd_box = new QCheckBox(tr("Write save / SD-Card data"));
+ m_load_wii_box = new QCheckBox(tr("Load Wii Save"));
+ m_record_input_box = new QCheckBox(tr("Record inputs"));
+ m_buffer_label = new QLabel(tr("Buffer:"));
+ m_quit_button = new QPushButton(tr("Quit"));
+
+ m_game_button->setDefault(false);
+ m_game_button->setAutoDefault(false);
+
+ for (const QString& text :
+ {tr("MD5 Check:"), tr("Current game"), tr("Other game"), tr("SD card")})
+ m_md5_box->addItem(text);
+
+ m_main_layout->addWidget(m_game_button, 0, 0);
+ m_main_layout->addWidget(m_md5_box, 0, 1);
+ m_main_layout->addWidget(m_chat_box, 1, 0);
+ m_main_layout->addWidget(m_players_box, 1, 1);
+
+ auto* options_widget = new QHBoxLayout;
+
+ options_widget->addWidget(m_start_button);
+ options_widget->addWidget(m_buffer_label);
+ options_widget->addWidget(m_buffer_size_box);
+ options_widget->addWidget(m_save_sd_box);
+ options_widget->addWidget(m_load_wii_box);
+ options_widget->addWidget(m_record_input_box);
+ options_widget->addWidget(m_quit_button);
+ m_main_layout->addLayout(options_widget, 2, 0, 1, -1, Qt::AlignRight);
+
+ setLayout(m_main_layout);
+}
+
+void NetPlayDialog::CreateChatLayout()
+{
+ m_chat_box = new QGroupBox(tr("Chat"));
+ m_chat_edit = new QTextEdit;
+ m_chat_type_edit = new QLineEdit;
+ m_chat_send_button = new QPushButton(tr("Send"));
+
+ m_chat_send_button->setDefault(false);
+ m_chat_send_button->setAutoDefault(false);
+
+ m_chat_edit->setReadOnly(true);
+
+ auto* layout = new QGridLayout;
+
+ layout->addWidget(m_chat_edit, 0, 0, 1, -1);
+ layout->addWidget(m_chat_type_edit, 1, 0);
+ layout->addWidget(m_chat_send_button, 1, 1);
+
+ m_chat_box->setLayout(layout);
+}
+
+void NetPlayDialog::CreatePlayersLayout()
+{
+ m_players_box = new QGroupBox(tr("Players"));
+ m_room_box = new QComboBox;
+ m_hostcode_label = new QLabel;
+ m_hostcode_action_button = new QPushButton(tr("Copy"));
+ m_players_list = new QListWidget;
+ m_kick_button = new QPushButton(tr("Kick Player"));
+ m_assign_ports_button = new QPushButton(tr("Assign Controller Ports"));
+
+ auto* layout = new QGridLayout;
+
+ layout->addWidget(m_room_box, 0, 0);
+ layout->addWidget(m_hostcode_label, 0, 1);
+ layout->addWidget(m_hostcode_action_button, 0, 2);
+ layout->addWidget(m_players_list, 1, 0, 1, -1);
+ layout->addWidget(m_kick_button, 2, 0, 1, -1);
+ layout->addWidget(m_assign_ports_button, 3, 0, 1, -1);
+
+ m_players_box->setLayout(layout);
+}
+
+void NetPlayDialog::ConnectWidgets()
+{
+ // Players
+ connect(m_room_box, static_cast(&QComboBox::currentIndexChanged), this,
+ &NetPlayDialog::UpdateGUI);
+ connect(m_hostcode_action_button, &QPushButton::clicked, [this] {
+ if (m_is_copy_button_retry && m_room_box->currentIndex() == 0)
+ g_TraversalClient->ReconnectToServer();
+ else
+ QApplication::clipboard()->setText(m_hostcode_label->text());
+ });
+ connect(m_players_list, &QListWidget::itemSelectionChanged, [this] {
+ int row = m_players_list->currentRow();
+ m_kick_button->setEnabled(row > 0 &&
+ !m_players_list->currentItem()->data(Qt::UserRole).isNull());
+ });
+ connect(m_kick_button, &QPushButton::clicked, [this] {
+ auto id = m_players_list->currentItem()->data(Qt::UserRole).toInt();
+ Settings::Instance().GetNetPlayServer()->KickPlayer(id);
+ });
+ connect(m_assign_ports_button, &QPushButton::clicked, [this] {
+ m_pad_mapping->exec();
+
+ Settings::Instance().GetNetPlayServer()->SetPadMapping(m_pad_mapping->GetGCPadArray());
+ Settings::Instance().GetNetPlayServer()->SetWiimoteMapping(m_pad_mapping->GetWiimoteArray());
+ });
+
+ // Chat
+ connect(m_chat_send_button, &QPushButton::clicked, this, &NetPlayDialog::OnChat);
+ connect(m_chat_type_edit, &QLineEdit::returnPressed, this, &NetPlayDialog::OnChat);
+
+ // Other
+ connect(m_buffer_size_box, static_cast(&QSpinBox::valueChanged),
+ [this](int value) {
+ if (Settings::Instance().GetNetPlayServer() != nullptr)
+ Settings::Instance().GetNetPlayServer()->AdjustPadBufferSize(value);
+ });
+
+ connect(m_start_button, &QPushButton::clicked, this, &NetPlayDialog::OnStart);
+ connect(m_quit_button, &QPushButton::clicked, this, &NetPlayDialog::reject);
+ connect(m_md5_box, static_cast(&QComboBox::currentIndexChanged), this,
+ &NetPlayDialog::OnMD5Combo);
+
+ connect(m_game_button, &QPushButton::clicked, [this] {
+ GameListDialog gld(this);
+ if (gld.exec() == QDialog::Accepted)
+ {
+ auto unique_id = gld.GetSelectedUniqueID();
+ Settings::Instance().GetNetPlayServer()->ChangeGame(unique_id.toStdString());
+ }
+ });
+
+ connect(this, &NetPlayDialog::EmulationStopped, this, [this] {
+ if (isVisible())
+ GameStatusChanged(false);
+ });
+}
+
+void NetPlayDialog::OnChat()
+{
+ QueueOnObject(this, [this] {
+ auto msg = m_chat_type_edit->text().toStdString();
+ Settings::Instance().GetNetPlayClient()->SendChatMessage(msg);
+ m_chat_type_edit->clear();
+
+ DisplayMessage(QStringLiteral("%1: %2").arg(QString::fromStdString(m_nickname),
+ QString::fromStdString(msg)),
+ "blue");
+ });
+}
+
+void NetPlayDialog::OnStart()
+{
+ if (!Settings::Instance().GetNetPlayClient()->DoAllPlayersHaveGame())
+ {
+ if (QMessageBox::question(this, tr("Warning"),
+ tr("Not all players have the game. Do you really want to start?")) ==
+ QMessageBox::No)
+ return;
+ }
+
+ NetSettings settings;
+
+ // Copy all relevant settings
+ SConfig& instance = SConfig::GetInstance();
+ settings.m_CPUthread = instance.bCPUThread;
+ settings.m_CPUcore = instance.iCPUCore;
+ settings.m_EnableCheats = instance.bEnableCheats;
+ settings.m_SelectedLanguage = instance.SelectedLanguage;
+ settings.m_OverrideGCLanguage = instance.bOverrideGCLanguage;
+ settings.m_ProgressiveScan = instance.bProgressive;
+ settings.m_PAL60 = instance.bPAL60;
+ settings.m_DSPHLE = instance.bDSPHLE;
+ settings.m_DSPEnableJIT = instance.m_DSPEnableJIT;
+ settings.m_WriteToMemcard = m_save_sd_box->isChecked();
+ settings.m_CopyWiiSave = m_load_wii_box->isChecked();
+ settings.m_OCEnable = instance.m_OCEnable;
+ settings.m_OCFactor = instance.m_OCFactor;
+ settings.m_EXIDevice[0] = instance.m_EXIDevice[0];
+ settings.m_EXIDevice[1] = instance.m_EXIDevice[1];
+
+ Settings::Instance().GetNetPlayServer()->SetNetSettings(settings);
+ Settings::Instance().GetNetPlayServer()->StartGame();
+}
+
+void NetPlayDialog::OnMD5Combo(int index)
+{
+ std::string identifier;
+
+ switch (index)
+ {
+ case 0:
+ return;
+ case 1: // Current game
+ identifier = m_current_game;
+ break;
+ case 2: // Other game
+ {
+ GameListDialog gld(this);
+
+ if (gld.exec() == QDialog::Accepted)
+ {
+ identifier = gld.GetSelectedUniqueID().toStdString();
+ break;
+ }
+ else
+ {
+ m_md5_box->setCurrentIndex(0);
+ return;
+ }
+ }
+ case 3: // SD Card
+ identifier = WII_SDCARD;
+ break;
+ }
+
+ Settings::Instance().GetNetPlayServer()->ComputeMD5(identifier);
+}
+
+void NetPlayDialog::reject()
+{
+ if (QMessageBox::question(this, tr("Confirmation"),
+ tr("Are you sure you want to quit NetPlay?")) == QMessageBox::Yes)
+ {
+ QDialog::reject();
+ }
+}
+
+void NetPlayDialog::show(std::string nickname, bool use_traversal)
+{
+ m_nickname = nickname;
+ m_use_traversal = use_traversal;
+
+ m_room_box->clear();
+ m_chat_edit->clear();
+ m_chat_type_edit->clear();
+
+ bool is_hosting = Settings::Instance().GetNetPlayServer() != nullptr;
+
+ if (is_hosting)
+ {
+ if (use_traversal)
+ m_room_box->addItem(tr("Room ID"));
+
+ for (const auto& iface : Settings::Instance().GetNetPlayServer()->GetInterfaceSet())
+ m_room_box->addItem(QString::fromStdString(iface));
+ }
+
+ m_start_button->setHidden(!is_hosting);
+ m_save_sd_box->setHidden(!is_hosting);
+ m_load_wii_box->setHidden(!is_hosting);
+ m_buffer_size_box->setHidden(!is_hosting);
+ m_buffer_label->setHidden(!is_hosting);
+ m_kick_button->setHidden(!is_hosting);
+ m_assign_ports_button->setHidden(!is_hosting);
+ m_md5_box->setHidden(!is_hosting);
+ m_room_box->setHidden(!is_hosting);
+ m_hostcode_label->setHidden(!is_hosting);
+ m_hostcode_action_button->setHidden(!is_hosting);
+ m_game_button->setEnabled(is_hosting);
+ m_kick_button->setEnabled(false);
+
+ QDialog::show();
+ UpdateGUI();
+}
+
+void NetPlayDialog::UpdateGUI()
+{
+ // Update player list
+ std::vector player_ids;
+ std::string tmp;
+
+ Settings::Instance().GetNetPlayClient()->GetPlayerList(tmp, player_ids);
+
+ std::istringstream ss(tmp);
+
+ int row = m_players_list->currentRow();
+ unsigned int i = 0;
+
+ m_players_list->clear();
+
+ while (std::getline(ss, tmp))
+ {
+ auto text = QString::fromStdString(tmp);
+ if (!text.isEmpty())
+ {
+ QListWidgetItem* item = new QListWidgetItem(text);
+
+ if (player_ids.size() > i && !text.startsWith(QStringLiteral("Ping:")) &&
+ !text.startsWith(QStringLiteral("Status:")))
+ {
+ item->setData(Qt::UserRole, player_ids[i]);
+ i++;
+ }
+ m_players_list->addItem(item);
+ }
+ }
+
+ if (row != -1)
+ m_players_list->setCurrentRow(row, QItemSelectionModel::SelectCurrent);
+
+ // Update Room ID / IP label
+ if (m_use_traversal && m_room_box->currentIndex() == 0)
+ {
+ switch (g_TraversalClient->m_State)
+ {
+ case TraversalClient::Connecting:
+ m_hostcode_label->setText(tr("..."));
+ m_hostcode_action_button->setEnabled(false);
+ break;
+ case TraversalClient::Connected:
+ m_hostcode_label->setText(QString::fromStdString(
+ std::string(g_TraversalClient->m_HostId.data(), g_TraversalClient->m_HostId.size())));
+ m_hostcode_action_button->setEnabled(true);
+ m_hostcode_action_button->setText(tr("Copy"));
+ m_is_copy_button_retry = false;
+ break;
+ case TraversalClient::Failure:
+ m_hostcode_label->setText(tr("Error"));
+ m_hostcode_action_button->setText(tr("Retry"));
+ m_hostcode_action_button->setEnabled(true);
+ m_is_copy_button_retry = true;
+ break;
+ }
+ }
+ else if (Settings::Instance().GetNetPlayServer())
+ {
+ m_hostcode_label->setText(
+ QString::fromStdString(Settings::Instance().GetNetPlayServer()->GetInterfaceHost(
+ m_room_box->currentText().toStdString())));
+ m_hostcode_action_button->setText(tr("Copy"));
+ m_hostcode_action_button->setEnabled(true);
+ }
+}
+
+// NetPlayUI methods
+
+void NetPlayDialog::BootGame(const std::string& filename)
+{
+ emit Boot(QString::fromStdString(filename));
+}
+
+void NetPlayDialog::StopGame()
+{
+ emit Stop();
+}
+
+void NetPlayDialog::Update()
+{
+ QueueOnObject(this, [this] { UpdateGUI(); });
+}
+
+void NetPlayDialog::DisplayMessage(const QString& msg, const std::string& color, int duration)
+{
+ QueueOnObject(m_chat_edit, [this, color, msg] {
+ m_chat_edit->append(
+ QStringLiteral("%2").arg(QString::fromStdString(color), msg));
+ });
+
+ if (g_ActiveConfig.bShowNetPlayMessages && Core::IsRunning())
+ {
+ u32 osd_color;
+
+ // Convert the color string to a OSD color
+ if (color == "red")
+ osd_color = OSD::Color::RED;
+ else if (color == "cyan")
+ osd_color = OSD::Color::CYAN;
+ else if (color == "green")
+ osd_color = OSD::Color::GREEN;
+ else
+ osd_color = OSD::Color::YELLOW;
+
+ OSD::AddTypedMessage(OSD::MessageType::NetPlayBuffer, msg.toStdString(), OSD::Duration::NORMAL,
+ osd_color);
+ }
+}
+
+void NetPlayDialog::AppendChat(const std::string& msg)
+{
+ DisplayMessage(QString::fromStdString(msg), "");
+}
+
+void NetPlayDialog::OnMsgChangeGame(const std::string& title)
+{
+ QString qtitle = QString::fromStdString(title);
+ QueueOnObject(this, [this, qtitle, title] {
+ m_game_button->setText(qtitle);
+ m_current_game = title;
+ });
+ DisplayMessage(tr("Game changed to \"%1\"").arg(qtitle), "pink");
+}
+
+void NetPlayDialog::GameStatusChanged(bool running)
+{
+ QueueOnObject(this, [this, running] {
+ if (Settings::Instance().GetNetPlayServer() != nullptr)
+ {
+ m_start_button->setEnabled(!running);
+ m_game_button->setEnabled(!running);
+ m_load_wii_box->setEnabled(!running);
+ m_save_sd_box->setEnabled(!running);
+ m_assign_ports_button->setEnabled(!running);
+ }
+
+ m_record_input_box->setEnabled(!running);
+ });
+}
+
+void NetPlayDialog::OnMsgStartGame()
+{
+ DisplayMessage(tr("Started game"), "green");
+ GameStatusChanged(true);
+
+ QueueOnObject(this, [this] {
+ Settings::Instance().GetNetPlayClient()->StartGame(FindGame(m_current_game));
+ });
+}
+
+void NetPlayDialog::OnMsgStopGame()
+{
+ DisplayMessage(tr("Stopped game"), "red");
+ GameStatusChanged(false);
+}
+
+void NetPlayDialog::OnPadBufferChanged(u32 buffer)
+{
+ QueueOnObject(this, [this, buffer] { m_buffer_size_box->setValue(buffer); });
+ DisplayMessage(tr("Pad size changed to %1").arg(buffer), "gray");
+}
+
+void NetPlayDialog::OnDesync(u32 frame, const std::string& player)
+{
+ DisplayMessage(tr("Possible desync detected: %1 might have desynced at frame %2")
+ .arg(QString::fromStdString(player), QString::number(frame)),
+ "red", OSD::Duration::VERY_LONG);
+}
+
+void NetPlayDialog::OnConnectionLost()
+{
+ DisplayMessage(tr("Lost connection to NetPlay server..."), "red");
+}
+
+void NetPlayDialog::OnTraversalError(int error)
+{
+ QueueOnObject(this, [this, error] {
+ switch (error)
+ {
+ case TraversalClient::BadHost:
+ QMessageBox::critical(this, tr("Traversal Error"), tr("Couldn't look up central server"));
+ QDialog::reject();
+ break;
+ case TraversalClient::VersionTooOld:
+ QMessageBox::critical(this, tr("Traversal Error"),
+ tr("Dolphin is too old for traversal server"));
+ QDialog::reject();
+ break;
+ case TraversalClient::ServerForgotAboutUs:
+ case TraversalClient::SocketSendError:
+ case TraversalClient::ResendTimeout:
+ UpdateGUI();
+ break;
+ }
+ });
+}
+
+bool NetPlayDialog::IsRecording()
+{
+ return RunOnObject(this, [this] { return m_record_input_box->isChecked(); });
+}
+
+std::string NetPlayDialog::FindGame(const std::string& game)
+{
+ return RunOnObject(this, [this, game] {
+ for (int i = 0; i < m_game_list_model->rowCount(QModelIndex()); i++)
+ {
+ if (m_game_list_model->GetUniqueID(i).toStdString() == game)
+ return m_game_list_model->GetPath(i).toStdString();
+ }
+ return std::string("");
+ });
+}
+
+void NetPlayDialog::ShowMD5Dialog(const std::string& file_identifier)
+{
+ QueueOnObject(this, [this, file_identifier] {
+ m_md5_box->setEnabled(false);
+ m_md5_box->setCurrentIndex(0);
+
+ if (m_md5_dialog->isVisible())
+ m_md5_dialog->close();
+
+ m_md5_dialog->show(QString::fromStdString(file_identifier));
+ });
+}
+
+void NetPlayDialog::SetMD5Progress(int pid, int progress)
+{
+ QueueOnObject(this, [this, pid, progress] {
+ if (m_md5_dialog->isVisible())
+ m_md5_dialog->SetProgress(pid, progress);
+ });
+}
+
+void NetPlayDialog::SetMD5Result(int pid, const std::string& result)
+{
+ QueueOnObject(this, [this, pid, result] {
+ m_md5_dialog->SetResult(pid, result);
+ m_md5_box->setEnabled(true);
+ });
+}
+
+void NetPlayDialog::AbortMD5()
+{
+ QueueOnObject(this, [this] {
+ m_md5_dialog->close();
+ m_md5_box->setEnabled(true);
+ });
+}
diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.h
new file mode 100644
index 0000000000..9915ab7940
--- /dev/null
+++ b/Source/Core/DolphinQt2/NetPlay/NetPlayDialog.h
@@ -0,0 +1,110 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+
+#include "Core/NetPlayClient.h"
+#include "VideoCommon/OnScreenDisplay.h"
+
+class MD5Dialog;
+class GameListModel;
+class NetPlayServer;
+class PadMappingDialog;
+class QCheckBox;
+class QComboBox;
+class QGridLayout;
+class QGroupBox;
+class QLabel;
+class QLineEdit;
+class QListWidget;
+class QPushButton;
+class QSpinBox;
+class QTextEdit;
+
+class NetPlayDialog : public QDialog, public NetPlayUI
+{
+ Q_OBJECT
+public:
+ NetPlayDialog(QWidget* parent);
+
+ void show(std::string nickname, bool use_traversal);
+ void reject() override;
+
+ // NetPlayUI methods
+ void BootGame(const std::string& filename) override;
+ void StopGame() override;
+
+ void Update() override;
+ void AppendChat(const std::string& msg) override;
+
+ void OnMsgChangeGame(const std::string& filename) override;
+ void OnMsgStartGame() override;
+ void OnMsgStopGame() override;
+ void OnPadBufferChanged(u32 buffer) override;
+ void OnDesync(u32 frame, const std::string& player) override;
+ void OnConnectionLost() override;
+ void OnTraversalError(int error) override;
+ bool IsRecording() override;
+ std::string FindGame(const std::string& game) override;
+ void ShowMD5Dialog(const std::string& file_identifier) override;
+ void SetMD5Progress(int pid, int progress) override;
+ void SetMD5Result(int pid, const std::string& result) override;
+ void AbortMD5() override;
+signals:
+ void EmulationStopped();
+ void Boot(const QString& filename);
+ void Stop();
+
+private:
+ void CreateChatLayout();
+ void CreatePlayersLayout();
+ void CreateMainLayout();
+ void ConnectWidgets();
+ void OnChat();
+ void OnStart();
+ void OnMD5Combo(int index);
+ void DisplayMessage(const QString& msg, const std::string& color,
+ int duration = OSD::Duration::NORMAL);
+ void UpdateGUI();
+ void GameStatusChanged(bool running);
+
+ void SetGame(const QString& game_path);
+
+ // Chat
+ QGroupBox* m_chat_box;
+ QTextEdit* m_chat_edit;
+ QLineEdit* m_chat_type_edit;
+ QPushButton* m_chat_send_button;
+
+ // Players
+ QGroupBox* m_players_box;
+ QComboBox* m_room_box;
+ QLabel* m_hostcode_label;
+ QPushButton* m_hostcode_action_button;
+ QListWidget* m_players_list;
+ QPushButton* m_kick_button;
+ QPushButton* m_assign_ports_button;
+
+ // Other
+ QPushButton* m_game_button;
+ QComboBox* m_md5_box;
+ QPushButton* m_start_button;
+ QLabel* m_buffer_label;
+ QSpinBox* m_buffer_size_box;
+ QCheckBox* m_save_sd_box;
+ QCheckBox* m_load_wii_box;
+ QCheckBox* m_record_input_box;
+ QPushButton* m_quit_button;
+
+ QGridLayout* m_main_layout;
+ MD5Dialog* m_md5_dialog;
+ PadMappingDialog* m_pad_mapping;
+ std::string m_current_game;
+ std::string m_nickname;
+ GameListModel* m_game_list_model = nullptr;
+ bool m_use_traversal = false;
+ bool m_is_copy_button_retry = false;
+};
diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp b/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp
new file mode 100644
index 0000000000..1179d2c06f
--- /dev/null
+++ b/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.cpp
@@ -0,0 +1,267 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/NetPlay/NetPlaySetupDialog.h"
+
+#include "Core/Config/NetplaySettings.h"
+#include "DolphinQt2/GameList/GameListModel.h"
+#include "DolphinQt2/Settings.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+NetPlaySetupDialog::NetPlaySetupDialog(QWidget* parent)
+ : QDialog(parent), m_game_list_model(Settings::Instance().GetGameListModel())
+{
+ setWindowTitle(tr("Dolphin NetPlay Setup"));
+
+ CreateMainLayout();
+
+ std::string nickname = Config::Get(Config::NETPLAY_NICKNAME);
+ std::string traversal_choice = Config::Get(Config::NETPLAY_TRAVERSAL_CHOICE);
+ int connect_port = Config::Get(Config::NETPLAY_CONNECT_PORT);
+ int host_port = Config::Get(Config::NETPLAY_HOST_PORT);
+ int host_listen_port = Config::Get(Config::NETPLAY_LISTEN_PORT);
+#ifdef USE_UPNP
+ bool use_upnp = Config::Get(Config::NETPLAY_USE_UPNP);
+
+ m_host_upnp->setChecked(use_upnp);
+#endif
+
+ m_nickname_edit->setText(QString::fromStdString(nickname));
+ m_connection_type->setCurrentIndex(traversal_choice == "direct" ? 0 : 1);
+ m_connect_port_box->setValue(connect_port);
+ m_host_port_box->setValue(host_port);
+
+ m_host_force_port_check->setChecked(false);
+ m_host_force_port_box->setValue(host_listen_port);
+ m_host_force_port_box->setEnabled(false);
+
+ OnConnectionTypeChanged(m_connection_type->currentIndex());
+
+ int selected_game = QSettings().value(QStringLiteral("netplay/hostgame"), 0).toInt();
+
+ if (selected_game >= m_host_games->count())
+ selected_game = 0;
+
+ m_host_games->setCurrentItem(m_host_games->item(selected_game));
+
+ ConnectWidgets();
+}
+
+void NetPlaySetupDialog::CreateMainLayout()
+{
+ m_main_layout = new QGridLayout;
+ m_button_box = new QDialogButtonBox(QDialogButtonBox::Cancel);
+ m_nickname_edit = new QLineEdit;
+ m_connection_type = new QComboBox;
+ m_reset_traversal_button = new QPushButton(tr("Reset Traversal Settings"));
+ m_tab_widget = new QTabWidget;
+
+ // Connection widget
+ auto* connection_widget = new QWidget;
+ auto* connection_layout = new QGridLayout;
+
+ m_ip_label = new QLabel;
+ m_ip_edit = new QLineEdit;
+ m_connect_port_label = new QLabel(tr("Port:"));
+ m_connect_port_box = new QSpinBox;
+ m_connect_button = new QPushButton(tr("Connect"));
+
+ m_connect_port_box->setMaximum(65535);
+
+ connection_layout->addWidget(m_ip_label, 0, 0);
+ connection_layout->addWidget(m_ip_edit, 0, 1);
+ connection_layout->addWidget(m_connect_port_label, 0, 2);
+ connection_layout->addWidget(m_connect_port_box, 0, 3);
+ connection_layout->addWidget(
+ new QLabel(tr(
+ "ALERT:\n\n"
+ "All players must use the same Dolphin version.\n"
+ "All memory cards, SD cards and cheats must be identical between players or disabled.\n"
+ "If DSP LLE is used, DSP ROMs must be identical between players.\n"
+ "If connecting directly, the host must have the chosen UDP port open/forwarded!\n"
+ "\n"
+ "Wii Remote support in netplay is experimental and should not be expected to work.\n")),
+ 1, 0, -1, -1);
+ connection_layout->addWidget(m_connect_button, 3, 3, Qt::AlignRight);
+
+ connection_widget->setLayout(connection_layout);
+
+ // Host widget
+ auto* host_widget = new QWidget;
+ auto* host_layout = new QGridLayout;
+ m_host_port_label = new QLabel(tr("Port:"));
+ m_host_port_box = new QSpinBox;
+ m_host_force_port_check = new QCheckBox(tr("Force Listen Port:"));
+ m_host_force_port_box = new QSpinBox;
+
+#ifdef USE_UPNP
+ m_host_upnp = new QCheckBox(tr("Forward port (UPnP)"));
+#endif
+ m_host_games = new QListWidget;
+ m_host_button = new QPushButton(tr("Host"));
+
+ m_host_port_box->setMaximum(65535);
+ m_host_force_port_box->setMaximum(65535);
+
+ host_layout->addWidget(m_host_port_label, 0, 0);
+ host_layout->addWidget(m_host_port_box, 0, 1);
+#ifdef USE_UPNP
+ host_layout->addWidget(m_host_upnp, 0, 2);
+#endif
+ host_layout->addWidget(m_host_games, 1, 0, 1, -1);
+ host_layout->addWidget(m_host_force_port_check, 2, 0);
+ host_layout->addWidget(m_host_force_port_box, 2, 1, Qt::AlignLeft);
+ host_layout->addWidget(m_host_button, 2, 2, Qt::AlignRight);
+
+ host_widget->setLayout(host_layout);
+
+ m_connection_type->addItem(tr("Direct Connection"));
+ m_connection_type->addItem(tr("Traversal Server"));
+
+ m_main_layout->addWidget(new QLabel(tr("Connection Type:")), 0, 0);
+ m_main_layout->addWidget(m_connection_type, 0, 1);
+ m_main_layout->addWidget(m_reset_traversal_button, 0, 2);
+ m_main_layout->addWidget(new QLabel(tr("Nickname:")), 1, 0);
+ m_main_layout->addWidget(m_nickname_edit, 1, 1);
+ m_main_layout->addWidget(m_tab_widget, 2, 0, 1, -1);
+ m_main_layout->addWidget(m_button_box, 3, 0, 1, -1);
+
+ // Tabs
+ m_tab_widget->addTab(connection_widget, tr("Connect"));
+ m_tab_widget->addTab(host_widget, tr("Host"));
+
+ setLayout(m_main_layout);
+}
+
+void NetPlaySetupDialog::ConnectWidgets()
+{
+ connect(m_connection_type, static_cast(&QComboBox::currentIndexChanged),
+ this, &NetPlaySetupDialog::OnConnectionTypeChanged);
+ connect(m_nickname_edit, &QLineEdit::textChanged, this, &NetPlaySetupDialog::SaveSettings);
+
+ // Connect widget
+ connect(m_ip_edit, &QLineEdit::textChanged, this, &NetPlaySetupDialog::SaveSettings);
+ connect(m_connect_port_box, static_cast(&QSpinBox::valueChanged), this,
+ &NetPlaySetupDialog::SaveSettings);
+ // Host widget
+ connect(m_host_port_box, static_cast(&QSpinBox::valueChanged), this,
+ &NetPlaySetupDialog::SaveSettings);
+ connect(m_host_games, static_cast(&QListWidget::currentRowChanged),
+ [](int index) { QSettings().setValue(QStringLiteral("netplay/hostgame"), index); });
+ connect(m_host_force_port_check, &QCheckBox::toggled,
+ [this](int value) { m_host_force_port_box->setEnabled(value); });
+#ifdef USE_UPNP
+ connect(m_host_upnp, &QCheckBox::stateChanged, this, &NetPlaySetupDialog::SaveSettings);
+#endif
+
+ connect(m_connect_button, &QPushButton::clicked, this, &QDialog::accept);
+ connect(m_host_button, &QPushButton::clicked, this, &QDialog::accept);
+ connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
+ connect(m_reset_traversal_button, &QPushButton::clicked, this,
+ &NetPlaySetupDialog::ResetTraversalHost);
+}
+
+void NetPlaySetupDialog::SaveSettings()
+{
+ Config::SetBaseOrCurrent(Config::NETPLAY_NICKNAME, m_nickname_edit->text().toStdString());
+ Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, m_ip_edit->text().toStdString());
+ Config::SetBaseOrCurrent(Config::NETPLAY_CONNECT_PORT,
+ static_cast(m_connect_port_box->value()));
+ Config::SetBaseOrCurrent(Config::NETPLAY_HOST_PORT, static_cast(m_host_port_box->value()));
+ Config::SetBaseOrCurrent(Config::NETPLAY_USE_UPNP, m_host_upnp->isChecked());
+
+ if (m_host_force_port_check->isChecked())
+ Config::SetBaseOrCurrent(Config::NETPLAY_LISTEN_PORT,
+ static_cast(m_host_force_port_box->value()));
+}
+
+void NetPlaySetupDialog::OnConnectionTypeChanged(int index)
+{
+ m_connect_port_box->setHidden(index != 0);
+ m_connect_port_label->setHidden(index != 0);
+
+ m_host_port_label->setHidden(index != 0);
+ m_host_port_box->setHidden(index != 0);
+ m_host_upnp->setHidden(index != 0);
+ m_host_force_port_check->setHidden(index == 0);
+ m_host_force_port_box->setHidden(index == 0);
+
+ m_reset_traversal_button->setHidden(index == 0);
+
+ std::string address = Config::Get(Config::NETPLAY_HOST_CODE);
+
+ m_ip_label->setText(index == 0 ? tr("IP Address:") : tr("Host Code:"));
+ m_ip_edit->setText(QString::fromStdString(address));
+
+ Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE,
+ std::string(index == 0 ? "direct" : "traversal"));
+}
+
+void NetPlaySetupDialog::show()
+{
+ PopulateGameList();
+ QDialog::show();
+}
+
+void NetPlaySetupDialog::accept()
+{
+ SaveSettings();
+ if (m_tab_widget->currentIndex() == 0)
+ {
+ emit Join();
+ }
+ else
+ {
+ auto items = m_host_games->selectedItems();
+ if (items.size() == 0)
+ {
+ QMessageBox::critical(this, tr("Error"), tr("You must select a game to host!"));
+ return;
+ }
+
+ emit Host(items[0]->text());
+ }
+}
+
+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->GetUniqueID(i);
+ auto path = m_game_list_model->GetPath(i);
+
+ auto* item = new QListWidgetItem(title);
+ item->setData(Qt::UserRole, path);
+ m_host_games->addItem(item);
+ }
+
+ m_host_games->sortItems();
+}
+
+void NetPlaySetupDialog::ResetTraversalHost()
+{
+ Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_SERVER,
+ Config::NETPLAY_TRAVERSAL_SERVER.default_value);
+ Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_PORT,
+ Config::NETPLAY_TRAVERSAL_PORT.default_value);
+
+ QMessageBox::information(
+ this, tr("Reset Traversal Server"),
+ tr("Reset Traversal Server to %1:%2")
+ .arg(QString::fromStdString(Config::NETPLAY_TRAVERSAL_SERVER.default_value),
+ QString::number(Config::NETPLAY_TRAVERSAL_PORT.default_value)));
+}
diff --git a/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.h b/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.h
new file mode 100644
index 0000000000..a73eccf8b1
--- /dev/null
+++ b/Source/Core/DolphinQt2/NetPlay/NetPlaySetupDialog.h
@@ -0,0 +1,72 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+
+class GameListModel;
+class QCheckBox;
+class QComboBox;
+class QDialogButtonBox;
+class QLabel;
+class QLineEdit;
+class QListWidget;
+class QGridLayout;
+class QPushButton;
+class QSpinBox;
+class QTabWidget;
+
+class NetPlaySetupDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ NetPlaySetupDialog(QWidget* parent);
+
+ void accept() override;
+ void show();
+
+signals:
+ bool Join();
+ bool Host(const QString& game_identifier);
+
+private:
+ void CreateMainLayout();
+ void ConnectWidgets();
+ void PopulateGameList();
+ void ResetTraversalHost();
+
+ void SaveSettings();
+
+ void OnConnectionTypeChanged(int index);
+
+ // Main Widget
+ QDialogButtonBox* m_button_box;
+ QComboBox* m_connection_type;
+ QLineEdit* m_nickname_edit;
+ QGridLayout* m_main_layout;
+ QTabWidget* m_tab_widget;
+ QPushButton* m_reset_traversal_button;
+
+ // Connection Widget
+ QLabel* m_ip_label;
+ QLineEdit* m_ip_edit;
+ QLabel* m_connect_port_label;
+ QSpinBox* m_connect_port_box;
+ QPushButton* m_connect_button;
+
+ // Host Widget
+ QLabel* m_host_port_label;
+ QSpinBox* m_host_port_box;
+ QListWidget* m_host_games;
+ QPushButton* m_host_button;
+ QCheckBox* m_host_force_port_check;
+ QSpinBox* m_host_force_port_box;
+
+#ifdef USE_UPNP
+ QCheckBox* m_host_upnp;
+#endif
+
+ GameListModel* m_game_list_model;
+};
diff --git a/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.cpp b/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.cpp
new file mode 100644
index 0000000000..a149b48b30
--- /dev/null
+++ b/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.cpp
@@ -0,0 +1,94 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "DolphinQt2/NetPlay/PadMappingDialog.h"
+#include "DolphinQt2/Settings.h"
+
+#include "Core/NetPlayClient.h"
+
+#include
+#include
+#include
+#include
+
+PadMappingDialog::PadMappingDialog(QWidget* parent) : QDialog(parent)
+{
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ CreateWidgets();
+ ConnectWidgets();
+}
+
+void PadMappingDialog::CreateWidgets()
+{
+ m_main_layout = new QGridLayout;
+ m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok);
+
+ for (unsigned int i = 0; i < m_wii_boxes.size(); i++)
+ {
+ m_gc_boxes[i] = new QComboBox;
+ m_wii_boxes[i] = new QComboBox;
+
+ m_main_layout->addWidget(new QLabel(tr("GC Port %1").arg(i + 1)), 0, i);
+ m_main_layout->addWidget(new QLabel(tr("Wii Remote Port %1").arg(i + 1)), 0, 4 + i);
+ m_main_layout->addWidget(m_gc_boxes[i], 1, i);
+ m_main_layout->addWidget(m_wii_boxes[i], 1, 4 + i);
+ }
+
+ m_main_layout->addWidget(m_button_box, 2, 0, 1, -1);
+
+ setLayout(m_main_layout);
+}
+
+void PadMappingDialog::ConnectWidgets()
+{
+ connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept);
+}
+
+int PadMappingDialog::exec()
+{
+ m_players = Settings::Instance().GetNetPlayClient()->GetPlayers();
+
+ QStringList players;
+
+ players.append(tr("None"));
+
+ for (const auto& player : m_players)
+ players.append(QString::fromStdString(player->name));
+
+ for (auto& combo_group : {m_gc_boxes, m_wii_boxes})
+ {
+ for (auto& combo : combo_group)
+ {
+ combo->clear();
+ combo->addItems(players);
+ connect(combo, static_cast(&QComboBox::currentIndexChanged), this,
+ &PadMappingDialog::OnMappingChanged);
+ }
+ }
+
+ return QDialog::exec();
+}
+
+PadMappingArray PadMappingDialog::GetGCPadArray()
+{
+ return m_pad_mapping;
+}
+
+PadMappingArray PadMappingDialog::GetWiimoteArray()
+{
+ return m_wii_mapping;
+}
+
+void PadMappingDialog::OnMappingChanged()
+{
+ for (unsigned int i = 0; i < m_wii_boxes.size(); i++)
+ {
+ int gc_id = m_gc_boxes[i]->currentIndex();
+ int wii_id = m_wii_boxes[i]->currentIndex();
+
+ m_pad_mapping[i] = gc_id > 0 ? m_players[gc_id - 1]->pid : -1;
+ m_wii_mapping[i] = wii_id > 0 ? m_players[wii_id - 1]->pid : -1;
+ }
+}
diff --git a/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.h b/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.h
new file mode 100644
index 0000000000..ebf2c2f48b
--- /dev/null
+++ b/Source/Core/DolphinQt2/NetPlay/PadMappingDialog.h
@@ -0,0 +1,41 @@
+// Copyright 2017 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+
+#include "Core/NetPlayProto.h"
+
+class NetPlayClient;
+class Player;
+class QGridLayout;
+class QComboBox;
+class QDialogButtonBox;
+
+class PadMappingDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit PadMappingDialog(QWidget* widget);
+
+ int exec();
+
+ PadMappingArray GetGCPadArray();
+ PadMappingArray GetWiimoteArray();
+
+private:
+ void CreateWidgets();
+ void ConnectWidgets();
+ void OnMappingChanged();
+
+ PadMappingArray m_pad_mapping;
+ PadMappingArray m_wii_mapping;
+
+ QGridLayout* m_main_layout;
+ std::array m_gc_boxes;
+ std::array m_wii_boxes;
+ std::vector m_players;
+ QDialogButtonBox* m_button_box;
+};
diff --git a/Source/Core/DolphinQt2/Settings.cpp b/Source/Core/DolphinQt2/Settings.cpp
index 99632fa78b..505614eae3 100644
--- a/Source/Core/DolphinQt2/Settings.cpp
+++ b/Source/Core/DolphinQt2/Settings.cpp
@@ -11,6 +11,7 @@
#include "Common/FileUtil.h"
#include "Common/StringUtil.h"
#include "Core/ConfigManager.h"
+#include "DolphinQt2/GameList/GameListModel.h"
#include "DolphinQt2/Settings.h"
#include "InputCommon/InputConfig.h"
@@ -171,3 +172,29 @@ void Settings::SetLogConfigVisible(bool visible)
emit LogConfigVisibilityChanged(visible);
}
}
+
+GameListModel* Settings::GetGameListModel() const
+{
+ static GameListModel* model = new GameListModel;
+ return model;
+}
+
+NetPlayClient* Settings::GetNetPlayClient()
+{
+ return m_client.get();
+}
+
+void Settings::ResetNetPlayClient(NetPlayClient* client)
+{
+ m_client.reset(client);
+}
+
+NetPlayServer* Settings::GetNetPlayServer()
+{
+ return m_server.get();
+}
+
+void Settings::ResetNetPlayServer(NetPlayServer* server)
+{
+ m_server.reset(server);
+}
diff --git a/Source/Core/DolphinQt2/Settings.h b/Source/Core/DolphinQt2/Settings.h
index be9e32dec4..d3d800668b 100644
--- a/Source/Core/DolphinQt2/Settings.h
+++ b/Source/Core/DolphinQt2/Settings.h
@@ -4,16 +4,22 @@
#pragma once
+#include
+
#include
#include
#include "Common/NonCopyable.h"
+#include "Core/NetPlayClient.h"
+#include "Core/NetPlayServer.h"
+
namespace DiscIO
{
enum class Language;
}
+class GameListModel;
class InputConfig;
// UI settings to be stored in the config directory.
@@ -57,6 +63,15 @@ public:
void IncreaseVolume(int volume);
void DecreaseVolume(int volume);
+ // NetPlay
+ NetPlayClient* GetNetPlayClient();
+ void ResetNetPlayClient(NetPlayClient* client = nullptr);
+ NetPlayServer* GetNetPlayServer();
+ void ResetNetPlayServer(NetPlayServer* server = nullptr);
+
+ // Other
+ GameListModel* GetGameListModel() const;
+
signals:
void ThemeChanged();
void PathAdded(const QString&);
@@ -68,5 +83,7 @@ signals:
void LogConfigVisibilityChanged(bool visible);
private:
+ std::unique_ptr m_client;
+ std::unique_ptr m_server;
Settings();
};