diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt
index 7bf685454d..a490c3b75a 100644
--- a/pcsx2-qt/CMakeLists.txt
+++ b/pcsx2-qt/CMakeLists.txt
@@ -91,6 +91,9 @@ target_sources(pcsx2-qt PRIVATE
Settings/MemoryCardSettingsWidget.cpp
Settings/MemoryCardSettingsWidget.h
Settings/MemoryCardSettingsWidget.ui
+ Settings/DEV9DnsHostDialog.cpp
+ Settings/DEV9DnsHostDialog.h
+ Settings/DEV9DnsHostDialog.ui
Settings/DEV9SettingsWidget.cpp
Settings/DEV9SettingsWidget.h
Settings/DEV9SettingsWidget.ui
diff --git a/pcsx2-qt/Settings/DEV9DnsHostDialog.cpp b/pcsx2-qt/Settings/DEV9DnsHostDialog.cpp
new file mode 100644
index 0000000000..06417723ab
--- /dev/null
+++ b/pcsx2-qt/Settings/DEV9DnsHostDialog.cpp
@@ -0,0 +1,146 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include
+#include
+#include
+#include
+
+#include "common/StringUtil.h"
+#include "DEV9DnsHostDialog.h"
+#include "EmuThread.h"
+#include "QtUtils.h"
+#include "SettingWidgetBinder.h"
+#include "SettingsDialog.h"
+
+//Figure out lists
+//On export, we take list from settings (or are given it from the DEV9 panel)
+//We display, then export
+
+//On import, we read file
+//we display, then pass list back to main DEV9 panel
+
+DEV9DnsHostDialog::DEV9DnsHostDialog(std::vector hosts, QWidget* parent)
+ : QDialog(parent)
+{
+ m_ui.setupUi(this);
+
+ m_ethHost_model = new QStandardItemModel(0, 5, m_ui.hostList);
+
+ QStringList headers;
+ headers.push_back(tr("Selected"));
+ headers.push_back(tr("Name"));
+ headers.push_back(tr("Url"));
+ headers.push_back(tr("Address"));
+ headers.push_back(tr("Enabled"));
+ m_ethHost_model->setHorizontalHeaderLabels(headers);
+
+ m_ethHosts_proxy = new QSortFilterProxyModel(m_ui.hostList);
+ m_ethHosts_proxy->setSourceModel(m_ethHost_model);
+
+ m_ui.hostList->setModel(m_ethHosts_proxy);
+ m_ui.hostList->setItemDelegateForColumn(3, new IPItemDelegate(m_ui.hostList));
+
+ for (size_t i = 0; i < hosts.size(); i++)
+ {
+ HostEntryUi entry = hosts[i];
+ const int row = m_ethHost_model->rowCount();
+ m_ethHost_model->insertRow(row);
+
+ QSignalBlocker sb(m_ethHost_model);
+
+ QStandardItem* includeItem = new QStandardItem();
+ includeItem->setEditable(false);
+ includeItem->setCheckable(true);
+ includeItem->setCheckState(Qt::CheckState::Checked);
+ m_ethHost_model->setItem(row, 0, includeItem);
+
+ QStandardItem* nameItem = new QStandardItem();
+ nameItem->setText(QString::fromStdString(entry.Desc));
+ nameItem->setEnabled(false);
+ m_ethHost_model->setItem(row, 1, nameItem);
+
+ QStandardItem* urlItem = new QStandardItem();
+ urlItem->setText(QString::fromStdString(entry.Url));
+ urlItem->setEnabled(false);
+ m_ethHost_model->setItem(row, 2, urlItem);
+
+ QStandardItem* addressItem = new QStandardItem();
+ addressItem->setText(QString::fromStdString(entry.Address));
+ addressItem->setEnabled(false);
+ m_ethHost_model->setItem(row, 3, addressItem);
+
+ QStandardItem* enabledItem = new QStandardItem();
+ enabledItem->setEditable(false);
+ enabledItem->setCheckable(true);
+ enabledItem->setCheckState(entry.Enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
+ enabledItem->setEnabled(false);
+ m_ethHost_model->setItem(row, 4, enabledItem);
+ }
+
+ m_ui.hostList->sortByColumn(1, Qt::AscendingOrder);
+
+ m_ui.hostList->installEventFilter(this);
+
+ connect(m_ui.btnOK, &QPushButton::clicked, this, &DEV9DnsHostDialog::onOK);
+ connect(m_ui.btnCancel, &QPushButton::clicked, this, &DEV9DnsHostDialog::onCancel);
+
+ m_hosts = hosts;
+}
+
+std::optional> DEV9DnsHostDialog::PromptList()
+{
+ int ret = exec();
+
+ if (ret != Accepted)
+ return std::nullopt;
+
+ std::vector selectedList;
+
+ for (int i = 0; i < m_ethHost_model->rowCount(); i++)
+ {
+ if (m_ethHost_model->item(i, 0)->checkState() == Qt::CheckState::Checked)
+ selectedList.push_back(m_hosts[i]);
+ }
+
+ return selectedList;
+}
+
+void DEV9DnsHostDialog::onOK()
+{
+ accept();
+}
+
+void DEV9DnsHostDialog::onCancel()
+{
+ reject();
+}
+
+bool DEV9DnsHostDialog::eventFilter(QObject* object, QEvent* event)
+{
+ if (object == m_ui.hostList)
+ {
+ //Check isVisible to avoind an unnessecery call to ResizeColumnsForTableView()
+ if (event->type() == QEvent::Resize && m_ui.hostList->isVisible())
+ QtUtils::ResizeColumnsForTableView(m_ui.hostList, {80, -1, 170, 90, 80});
+ else if (event->type() == QEvent::Show)
+ QtUtils::ResizeColumnsForTableView(m_ui.hostList, {80, -1, 170, 90, 80});
+ }
+ return false;
+}
+
+DEV9DnsHostDialog::~DEV9DnsHostDialog() = default;
diff --git a/pcsx2-qt/Settings/DEV9DnsHostDialog.h b/pcsx2-qt/Settings/DEV9DnsHostDialog.h
new file mode 100644
index 0000000000..947bd4dd39
--- /dev/null
+++ b/pcsx2-qt/Settings/DEV9DnsHostDialog.h
@@ -0,0 +1,51 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "ui_DEV9DnsHostDialog.h"
+
+#include "DEV9UiCommon.h"
+
+class SettingsDialog;
+
+class DEV9DnsHostDialog : public QDialog
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void onOK();
+ void onCancel();
+
+public:
+ DEV9DnsHostDialog(std::vector hosts, QWidget* parent);
+ ~DEV9DnsHostDialog();
+
+ std::optional> PromptList();
+
+protected:
+ bool eventFilter(QObject* object, QEvent* event);
+
+private:
+ Ui::DEV9DnsHostDialog m_ui;
+
+ std::vector m_hosts;
+
+ QStandardItemModel* m_ethHost_model;
+ QSortFilterProxyModel* m_ethHosts_proxy;
+};
diff --git a/pcsx2-qt/Settings/DEV9DnsHostDialog.ui b/pcsx2-qt/Settings/DEV9DnsHostDialog.ui
new file mode 100644
index 0000000000..736a036b3a
--- /dev/null
+++ b/pcsx2-qt/Settings/DEV9DnsHostDialog.ui
@@ -0,0 +1,69 @@
+
+
+ DEV9DnsHostDialog
+
+
+
+ 0
+ 0
+ 600
+ 300
+
+
+
+ Network DNS Hosts Inport/Export
+
+
+ -
+
+
+ Select Hosts
+
+
+
+ -
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ OK
+
+
+
+ -
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
diff --git a/pcsx2-qt/Settings/DEV9SettingsWidget.cpp b/pcsx2-qt/Settings/DEV9SettingsWidget.cpp
index 10eb0f56a3..862f2a0c3b 100644
--- a/pcsx2-qt/Settings/DEV9SettingsWidget.cpp
+++ b/pcsx2-qt/Settings/DEV9SettingsWidget.cpp
@@ -28,6 +28,7 @@
#include "QtUtils.h"
#include "SettingWidgetBinder.h"
#include "SettingsDialog.h"
+#include "Frontend/INISettingsInterface.h"
#include "HddCreateQt.h"
@@ -245,6 +246,9 @@ DEV9SettingsWidget::DEV9SettingsWidget(SettingsDialog* dialog, QWidget* parent)
connect(m_ui.ethHostAdd, &QPushButton::clicked, this, &DEV9SettingsWidget::onEthHostAdd);
connect(m_ui.ethHostDel, &QPushButton::clicked, this, &DEV9SettingsWidget::onEthHostDel);
+ connect(m_ui.ethHostExport, &QPushButton::clicked, this, &DEV9SettingsWidget::onEthHostExport);
+ connect(m_ui.ethHostImport, &QPushButton::clicked, this, &DEV9SettingsWidget::onEthHostImport);
+
if (m_dialog->isPerGameSettings())
m_ui.ethTabWidget->setTabEnabled(1, false);
@@ -487,6 +491,122 @@ void DEV9SettingsWidget::onEthHostDel()
}
}
+void DEV9SettingsWidget::onEthHostExport()
+{
+ std::vector hosts = ListHostsConfig();
+
+ DEV9DnsHostDialog exportDialog(hosts, this);
+
+ std::optional> selectedHosts = exportDialog.PromptList();
+
+ if (!selectedHosts.has_value())
+ return;
+
+ hosts = selectedHosts.value();
+
+ if (hosts.size() == 0)
+ return;
+
+ QString path =
+ QDir::toNativeSeparators(QFileDialog::getSaveFileName(QtUtils::GetRootWidget(this), tr("Hosts File"),
+ "hosts.ini", tr("ini (*.ini)"), nullptr));
+
+ if (path.isEmpty())
+ return;
+
+ std::unique_ptr exportFile = std::make_unique(path.toUtf8().constData());
+
+ //Count is not exported
+ for (size_t i = 0; i < hosts.size(); i++)
+ {
+ std::string section = "Host" + std::to_string(i);
+ HostEntryUi entry = hosts[i];
+
+ // clang-format off
+ exportFile->SetStringValue(section.c_str(), "Url", entry.Url.c_str());
+ exportFile->SetStringValue(section.c_str(), "Desc", entry.Desc.c_str());
+ exportFile->SetStringValue(section.c_str(), "Address", entry.Address.c_str());
+ exportFile->SetBoolValue(section.c_str(), "Enabled", entry.Enabled);
+ // clang-format on
+ }
+
+ exportFile->Save();
+
+ QMessageBox::information(this, tr("DNS Hosts"),
+ tr("Exported Successfully"),
+ QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
+}
+
+void DEV9SettingsWidget::onEthHostImport()
+{
+ std::vector hosts;
+
+ QString path =
+ QDir::toNativeSeparators(QFileDialog::getOpenFileName(QtUtils::GetRootWidget(this), tr("Hosts File"),
+ "hosts.ini", tr("ini (*.ini)"), nullptr));
+
+ if (path.isEmpty())
+ return;
+
+ std::unique_ptr importFile = std::make_unique(path.toUtf8().constData());
+
+ if (!importFile->Load())
+ {
+ QMessageBox::warning(this, tr("DNS Hosts"),
+ tr("Failed to open file"),
+ QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
+ return;
+ }
+
+ size_t count = 0;
+ while (true)
+ {
+ std::string section = "Host" + std::to_string(count);
+ HostEntryUi entry;
+
+ entry.Url = importFile->GetStringValue(section.c_str(), "Url");
+
+ if (entry.Url.empty())
+ break;
+
+ // clang-format off
+ entry.Desc = importFile->GetStringValue(section.c_str(), "Desc");
+ entry.Address = importFile->GetStringValue(section.c_str(), "Address");
+ entry.Enabled = importFile->GetBoolValue (section.c_str(), "Enabled");
+ // clang-format on
+
+ hosts.push_back(entry);
+ count++;
+ }
+
+ if (hosts.size() == 0)
+ {
+ QMessageBox::warning(this, tr("DNS Hosts"),
+ tr("No Hosts in file"),
+ QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
+ return;
+ }
+
+ DEV9DnsHostDialog exportDialog(hosts, this);
+
+ std::optional> selectedHosts = exportDialog.PromptList();
+
+ if (!selectedHosts.has_value())
+ return;
+
+ hosts = selectedHosts.value();
+
+ if (hosts.size() == 0)
+ return;
+
+ for (size_t i = 0; i < hosts.size(); i++)
+ AddNewHostConfig(hosts[i]);
+
+ QMessageBox::information(this, tr("DNS Hosts"),
+ tr("Imported Successfully"),
+ QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
+}
+
void DEV9SettingsWidget::onEthHostEdit(QStandardItem* item)
{
const int row = item->row();
@@ -711,22 +831,9 @@ void DEV9SettingsWidget::RefreshHostList()
m_ethHost_model->removeRow(0);
//Load list
- std::vector hosts;
+ std::vector hosts = ListHostsConfig();
- const int hostLength = CountHostsConfig();
- for (int i = 0; i < hostLength; i++)
- {
- std::string section = "DEV9/Eth/Hosts/Host" + std::to_string(i);
-
- HostEntryUi entry;
- entry.Url = m_dialog->getStringValue(section.c_str(), "Url", "").value();
- entry.Desc = m_dialog->getStringValue(section.c_str(), "Desc", "").value();
- entry.Address = m_dialog->getStringValue(section.c_str(), "Address", "").value();
- entry.Enabled = m_dialog->getBoolValue(section.c_str(), "Enabled", false).value();
- hosts.push_back(entry);
- }
-
- for (int i = 0; i < hostLength; i++)
+ for (size_t i = 0; i < hosts.size(); i++)
{
HostEntryUi entry = hosts[i];
const int row = m_ethHost_model->rowCount();
@@ -761,6 +868,26 @@ int DEV9SettingsWidget::CountHostsConfig()
return m_dialog->getIntValue("DEV9/Eth/Hosts", "Count", 0).value();
}
+std::vector DEV9SettingsWidget::ListHostsConfig()
+{
+ std::vector hosts;
+
+ const int hostLength = CountHostsConfig();
+ for (int i = 0; i < hostLength; i++)
+ {
+ std::string section = "DEV9/Eth/Hosts/Host" + std::to_string(i);
+
+ HostEntryUi entry;
+ entry.Url = m_dialog->getStringValue(section.c_str(), "Url", "").value();
+ entry.Desc = m_dialog->getStringValue(section.c_str(), "Desc", "").value();
+ entry.Address = m_dialog->getStringValue(section.c_str(), "Address", "").value();
+ entry.Enabled = m_dialog->getBoolValue(section.c_str(), "Enabled", false).value();
+ hosts.push_back(entry);
+ }
+
+ return hosts;
+}
+
void DEV9SettingsWidget::AddNewHostConfig(const HostEntryUi& host)
{
const int hostLength = CountHostsConfig();
diff --git a/pcsx2-qt/Settings/DEV9SettingsWidget.h b/pcsx2-qt/Settings/DEV9SettingsWidget.h
index 69883231b6..f4338520d7 100644
--- a/pcsx2-qt/Settings/DEV9SettingsWidget.h
+++ b/pcsx2-qt/Settings/DEV9SettingsWidget.h
@@ -20,6 +20,8 @@
#include "ui_DEV9SettingsWidget.h"
+#include "DEV9UiCommon.h"
+#include "DEV9DnsHostDialog.h"
#include "DEV9/net.h"
class SettingsDialog;
@@ -38,6 +40,8 @@ private Q_SLOTS:
void onEthDNSModeChanged(QComboBox* sender, int index, QLineEdit* input, const char* section, const char* key);
void onEthHostAdd();
void onEthHostDel();
+ void onEthHostExport();
+ void onEthHostImport();
void onEthHostEdit(QStandardItem* item);
void onHddEnabledChanged(int state);
@@ -60,6 +64,7 @@ private:
void AddAdapter(const AdapterEntry& adapter);
void RefreshHostList();
int CountHostsConfig();
+ std::vector ListHostsConfig();
void AddNewHostConfig(const HostEntryUi& host);
void DeleteHostConfig(int index);
diff --git a/pcsx2-qt/Settings/DEV9SettingsWidget.ui b/pcsx2-qt/Settings/DEV9SettingsWidget.ui
index 5dfbaea000..bfbbd8cda5 100644
--- a/pcsx2-qt/Settings/DEV9SettingsWidget.ui
+++ b/pcsx2-qt/Settings/DEV9SettingsWidget.ui
@@ -192,6 +192,20 @@
+ -
+
+
+ Export
+
+
+
+ -
+
+
+ Import
+
+
+
-
diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj
index 4e6ee34696..e2c09299cc 100644
--- a/pcsx2-qt/pcsx2-qt.vcxproj
+++ b/pcsx2-qt/pcsx2-qt.vcxproj
@@ -154,6 +154,7 @@
+
@@ -190,6 +191,7 @@
+
@@ -235,6 +237,7 @@
+
@@ -309,6 +312,9 @@
Document
+
+ Document
+
Document
diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters
index bf637a1c21..a21ac8c3a7 100644
--- a/pcsx2-qt/pcsx2-qt.vcxproj.filters
+++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters
@@ -170,6 +170,9 @@
moc
+
+ moc
+
moc
@@ -191,6 +194,9 @@
Settings
+
+ Settings
+
Settings
@@ -297,6 +303,9 @@
Settings
+
+ Settings
+
Settings
@@ -369,6 +378,9 @@
Settings
+
+ Settings
+
Settings