Skip to content

Commit 85e3072

Browse files
committed
DEV9: Add Lan UDP port forwarding for Sockets
1 parent c44c090 commit 85e3072

File tree

16 files changed

+765
-18
lines changed

16 files changed

+765
-18
lines changed

pcsx2-qt/Settings/DEV9SettingsWidget.cpp

Lines changed: 309 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,12 @@ DEV9SettingsWidget::DEV9SettingsWidget(SettingsWindow* settings_dialog, QWidget*
137137
//////////////////////////////////////////////////////////////////////////
138138
m_ethHost_model = new QStandardItemModel(0, 4, m_ui.ethHosts);
139139

140-
QStringList headers;
141-
headers.push_back(tr("Name"));
142-
headers.push_back(tr("Url"));
143-
headers.push_back(tr("Address"));
144-
headers.push_back(tr("Enabled"));
145-
m_ethHost_model->setHorizontalHeaderLabels(headers);
140+
QStringList hostHeaders;
141+
hostHeaders.push_back(tr("Name"));
142+
hostHeaders.push_back(tr("Url"));
143+
hostHeaders.push_back(tr("Address"));
144+
hostHeaders.push_back(tr("Enabled"));
145+
m_ethHost_model->setHorizontalHeaderLabels(hostHeaders);
146146

147147
connect(m_ethHost_model, QOverload<QStandardItem*>::of(&QStandardItemModel::itemChanged), this, &DEV9SettingsWidget::onEthHostEdit);
148148

@@ -164,6 +164,36 @@ DEV9SettingsWidget::DEV9SettingsWidget(SettingsWindow* settings_dialog, QWidget*
164164

165165
connect(m_ui.ethHostPerGame, &QPushButton::clicked, this, &DEV9SettingsWidget::onEthHostPerGame);
166166

167+
//////////////////////////////////////////////////////////////////////////
168+
// Port Settings
169+
//////////////////////////////////////////////////////////////////////////
170+
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.ethLanMode, "DEV9/Eth", "LanMode", false);
171+
172+
m_ethPort_model = new QStandardItemModel(0, 4, m_ui.ethOpenPorts);
173+
174+
QStringList portHeaders;
175+
portHeaders.push_back(tr("Name"));
176+
portHeaders.push_back(tr("Protocol"));
177+
portHeaders.push_back(tr("Port"));
178+
portHeaders.push_back(tr("Enabled"));
179+
m_ethPort_model->setHorizontalHeaderLabels(portHeaders);
180+
181+
connect(m_ethPort_model, QOverload<QStandardItem*>::of(&QStandardItemModel::itemChanged), this, &DEV9SettingsWidget::onEthPortEdit);
182+
183+
m_ethPorts_proxy = new QSortFilterProxyModel(m_ui.ethOpenPorts);
184+
m_ethPorts_proxy->setSourceModel(m_ethPort_model);
185+
186+
m_ui.ethOpenPorts->setModel(m_ethPorts_proxy);
187+
m_ui.ethOpenPorts->setItemDelegateForColumn(1, new ComboBoxItemDelegate(m_ui.ethOpenPorts, Pcsx2Config::DEV9Options::PortModeNames, "DEV9SettingsWidget"));
188+
m_ui.ethOpenPorts->setItemDelegateForColumn(2, new SpinBoxItemDelegate(m_ui.ethOpenPorts, 0, UINT16_MAX));
189+
190+
RefreshPortList();
191+
192+
m_ui.ethOpenPorts->installEventFilter(this);
193+
194+
connect(m_ui.ethPortAdd, &QPushButton::clicked, this, &DEV9SettingsWidget::onEthPortAdd);
195+
connect(m_ui.ethPortDel, &QPushButton::clicked, this, &DEV9SettingsWidget::onEthPortDel);
196+
167197
//////////////////////////////////////////////////////////////////////////
168198
// HDD Settings
169199
//////////////////////////////////////////////////////////////////////////
@@ -268,6 +298,8 @@ void DEV9SettingsWidget::onEthDeviceTypeChanged(int index)
268298
m_ui.ethInterceptDHCPLabel->setEnabled((m_adapter_options & AdapterOptions::DHCP_ForcedOn) == AdapterOptions::None);
269299
m_ui.ethInterceptDHCP->setEnabled((m_adapter_options & AdapterOptions::DHCP_ForcedOn) == AdapterOptions::None);
270300
onEthDHCPInterceptChanged(m_ui.ethInterceptDHCP->checkState());
301+
302+
m_ui.ethTabWidget->setTabVisible(2, (m_adapter_options & AdapterOptions::HasPortForwarding) == AdapterOptions::HasPortForwarding);
271303
}
272304

273305
void DEV9SettingsWidget::onEthDeviceChanged(int index)
@@ -591,6 +623,102 @@ void DEV9SettingsWidget::onEthHostEdit(QStandardItem* item)
591623
}
592624
}
593625

626+
void DEV9SettingsWidget::onEthPortAdd()
627+
{
628+
PortEntryUi port;
629+
port.Desc = "New Port";
630+
port.Protocol = "UDP";
631+
port.Enabled = false;
632+
AddNewPortConfig(port);
633+
634+
//Select new Item
635+
const QModelIndex viewIndex = m_ethPorts_proxy->mapFromSource(m_ethPort_model->index(m_ethPort_model->rowCount() - 1, 1));
636+
m_ui.ethOpenPorts->scrollTo(viewIndex, QAbstractItemView::EnsureVisible);
637+
m_ui.ethOpenPorts->selectionModel()->setCurrentIndex(viewIndex, QItemSelectionModel::ClearAndSelect);
638+
}
639+
640+
void DEV9SettingsWidget::onEthPortDel()
641+
{
642+
if (m_ui.ethOpenPorts->selectionModel()->hasSelection())
643+
{
644+
const QModelIndex selectedIndex = m_ui.ethOpenPorts->selectionModel()->currentIndex();
645+
const int modelRow = m_ethPorts_proxy->mapToSource(selectedIndex).row();
646+
DeletePortConfig(modelRow);
647+
}
648+
}
649+
650+
void DEV9SettingsWidget::onEthPortPerGame()
651+
{
652+
const std::optional<int> portLengthOpt = dialog()->getIntValue("DEV9/Eth/Ports", "Count", std::nullopt);
653+
if (!portLengthOpt.has_value())
654+
{
655+
QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Per Game Port list"),
656+
tr("Copy global settings?"),
657+
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No | QMessageBox::StandardButton::Cancel, QMessageBox::StandardButton::Yes);
658+
659+
switch (ret)
660+
{
661+
case QMessageBox::StandardButton::No:
662+
dialog()->setIntSettingValue("DEV9/Eth/Ports", "Count", 0);
663+
break;
664+
665+
case QMessageBox::StandardButton::Yes:
666+
{
667+
dialog()->setIntSettingValue("DEV9/Eth/Ports", "Count", 0);
668+
std::vector<PortEntryUi> ports = ListBasePortsConfig();
669+
for (size_t i = 0; i < ports.size(); i++)
670+
AddNewPortConfig(ports[i]);
671+
break;
672+
}
673+
674+
case QMessageBox::StandardButton::Cancel:
675+
return;
676+
677+
default:
678+
return;
679+
}
680+
}
681+
else
682+
{
683+
QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Per Game Port list"),
684+
tr("Delete per game port list?"),
685+
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::Cancel, QMessageBox::StandardButton::Yes);
686+
687+
if (ret == QMessageBox::StandardButton::Yes)
688+
{
689+
const int hostLength = CountPortsConfig();
690+
for (int i = hostLength - 1; i >= 0; i--)
691+
DeletePortConfig(i);
692+
}
693+
dialog()->setIntSettingValue("DEV9/Eth/Ports", nullptr, std::nullopt);
694+
}
695+
696+
RefreshHostList();
697+
}
698+
699+
void DEV9SettingsWidget::onEthPortEdit(QStandardItem* item)
700+
{
701+
const int row = item->row();
702+
std::string section = "DEV9/Eth/Ports/Port" + std::to_string(row);
703+
switch (item->column())
704+
{
705+
case 0: //Name
706+
dialog()->setStringSettingValue(section.c_str(), "Desc", item->text().toUtf8().constData());
707+
break;
708+
case 1: //Protocol
709+
dialog()->setStringSettingValue(section.c_str(), "Protocol", item->text().toUtf8().constData());
710+
break;
711+
case 2: //Port
712+
dialog()->setIntSettingValue(section.c_str(), "Port", item->data(Qt::EditRole).toInt());
713+
break;
714+
case 3: //Enabled
715+
dialog()->setBoolSettingValue(section.c_str(), "Enabled", item->checkState() == Qt::CheckState::Checked);
716+
break;
717+
default:
718+
break;
719+
}
720+
}
721+
594722
void DEV9SettingsWidget::onHddEnabledChanged(Qt::CheckState state)
595723
{
596724
const bool enabled = state == Qt::CheckState::PartiallyChecked ? Host::GetBaseBoolSettingValue("DEV9/Hdd", "HddEnable", false) : state;
@@ -807,6 +935,14 @@ bool DEV9SettingsWidget::eventFilter(QObject* object, QEvent* event)
807935
else if (event->type() == QEvent::Show)
808936
QtUtils::ResizeColumnsForTableView(m_ui.ethHosts, {-1, 170, 90, 80});
809937
}
938+
if (object == m_ui.ethOpenPorts)
939+
{
940+
//Check isVisible to avoind an unnessecery call to ResizeColumnsForTableView()
941+
if (event->type() == QEvent::Resize && m_ui.ethOpenPorts->isVisible())
942+
QtUtils::ResizeColumnsForTableView(m_ui.ethOpenPorts, {-1, 90, 170, 80});
943+
else if (event->type() == QEvent::Show)
944+
QtUtils::ResizeColumnsForTableView(m_ui.ethOpenPorts, {-1, 90, 170, 80});
945+
}
810946
return false;
811947
}
812948

@@ -1082,4 +1218,171 @@ void DEV9SettingsWidget::DeleteHostConfig(int index)
10821218
RefreshHostList();
10831219
}
10841220

1221+
void DEV9SettingsWidget::RefreshPortList()
1222+
{
1223+
while (m_ethPort_model->rowCount() > 0)
1224+
m_ethPort_model->removeRow(0);
1225+
1226+
bool enableHostsUi;
1227+
1228+
std::vector<PortEntryUi> ports;
1229+
1230+
if (dialog()->isPerGameSettings())
1231+
{
1232+
m_ui.ethPortPerGame->setVisible(true);
1233+
1234+
std::optional<std::vector<PortEntryUi>> portOpt = ListPortsConfig();
1235+
if (portOpt.has_value())
1236+
{
1237+
m_ui.ethPortPerGame->setText(tr("Use Global"));
1238+
ports = portOpt.value();
1239+
enableHostsUi = true;
1240+
}
1241+
else
1242+
{
1243+
m_ui.ethPortPerGame->setText(tr("Override"));
1244+
ports = ListBasePortsConfig();
1245+
enableHostsUi = false;
1246+
}
1247+
}
1248+
else
1249+
{
1250+
m_ui.ethPortPerGame->setVisible(false);
1251+
ports = ListPortsConfig().value();
1252+
enableHostsUi = true;
1253+
}
1254+
1255+
m_ui.ethOpenPorts->setEnabled(enableHostsUi);
1256+
m_ui.ethPortAdd->setEnabled(enableHostsUi);
1257+
m_ui.ethPortDel->setEnabled(enableHostsUi);
1258+
1259+
//Load list
1260+
for (size_t i = 0; i < ports.size(); i++)
1261+
{
1262+
PortEntryUi entry = ports[i];
1263+
const int row = m_ethPort_model->rowCount();
1264+
m_ethPort_model->insertRow(row);
1265+
1266+
QSignalBlocker sb(m_ethPort_model);
1267+
1268+
QStandardItem* nameItem = new QStandardItem();
1269+
nameItem->setText(QString::fromStdString(entry.Desc));
1270+
m_ethPort_model->setItem(row, 0, nameItem);
1271+
1272+
QStandardItem* protocolItem = new QStandardItem();
1273+
protocolItem->setText(QString::fromStdString(entry.Protocol));
1274+
protocolItem->setEditable(false); // Only UDP Supported
1275+
m_ethPort_model->setItem(row, 1, protocolItem);
1276+
1277+
QStandardItem* addressItem = new QStandardItem();
1278+
addressItem->setData(entry.Port, Qt::EditRole);
1279+
m_ethPort_model->setItem(row, 2, addressItem);
1280+
1281+
QStandardItem* enabledItem = new QStandardItem();
1282+
enabledItem->setEditable(false);
1283+
enabledItem->setCheckable(true);
1284+
enabledItem->setCheckState(entry.Enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
1285+
m_ethPort_model->setItem(row, 3, enabledItem);
1286+
}
1287+
1288+
m_ui.ethOpenPorts->sortByColumn(0, Qt::AscendingOrder);
1289+
}
1290+
1291+
int DEV9SettingsWidget::CountPortsConfig()
1292+
{
1293+
return dialog()->getIntValue("DEV9/Eth/Ports", "Count", 0).value();
1294+
}
1295+
1296+
std::optional<std::vector<PortEntryUi>> DEV9SettingsWidget::ListPortsConfig()
1297+
{
1298+
std::vector<PortEntryUi> ports;
1299+
1300+
std::optional<int> hostLengthOpt;
1301+
if (dialog()->isPerGameSettings())
1302+
{
1303+
hostLengthOpt = dialog()->getIntValue("DEV9/Eth/Ports", "Count", std::nullopt);
1304+
if (!hostLengthOpt.has_value())
1305+
return std::nullopt;
1306+
}
1307+
else
1308+
hostLengthOpt = dialog()->getIntValue("DEV9/Eth/Ports", "Count", 0);
1309+
1310+
const int hostLength = hostLengthOpt.value();
1311+
for (int i = 0; i < hostLength; i++)
1312+
{
1313+
std::string section = "DEV9/Eth/Ports/Port" + std::to_string(i);
1314+
1315+
PortEntryUi entry;
1316+
entry.Protocol = dialog()->getStringValue(section.c_str(), "Protocol", "UDP").value();
1317+
entry.Desc = dialog()->getStringValue(section.c_str(), "Desc", "").value();
1318+
entry.Port = dialog()->getIntValue(section.c_str(), "Port", 0).value();
1319+
entry.Enabled = dialog()->getBoolValue(section.c_str(), "Enabled", false).value();
1320+
ports.push_back(entry);
1321+
}
1322+
1323+
return ports;
1324+
}
1325+
1326+
std::vector<PortEntryUi> DEV9SettingsWidget::ListBasePortsConfig()
1327+
{
1328+
std::vector<PortEntryUi> hosts;
1329+
1330+
const int hostLength = Host::GetBaseIntSettingValue("DEV9/Eth/Ports", "Count", 0);
1331+
for (int i = 0; i < hostLength; i++)
1332+
{
1333+
std::string section = "DEV9/Eth/Ports/Port" + std::to_string(i);
1334+
1335+
PortEntryUi entry;
1336+
entry.Protocol = Host::GetBaseStringSettingValue(section.c_str(), "Protocol", "UDP");
1337+
entry.Desc = Host::GetBaseStringSettingValue(section.c_str(), "Desc", "");
1338+
entry.Port = Host::GetBaseIntSettingValue(section.c_str(), "Address", 0);
1339+
entry.Enabled = Host::GetBaseBoolSettingValue(section.c_str(), "Enabled", false);
1340+
hosts.push_back(entry);
1341+
}
1342+
1343+
return hosts;
1344+
}
1345+
1346+
void DEV9SettingsWidget::AddNewPortConfig(const PortEntryUi& port)
1347+
{
1348+
const int portLength = CountPortsConfig();
1349+
std::string section = "DEV9/Eth/Ports/Port" + std::to_string(portLength);
1350+
// clang-format off
1351+
dialog()->setIntSettingValue (section.c_str(), "Port", port.Port);
1352+
dialog()->setStringSettingValue(section.c_str(), "Desc", port.Desc.c_str());
1353+
dialog()->setStringSettingValue(section.c_str(), "Protocol", port.Protocol.c_str());
1354+
dialog()->setBoolSettingValue (section.c_str(), "Enabled", port.Enabled);
1355+
// clang-format on
1356+
dialog()->setIntSettingValue("DEV9/Eth/Ports", "Count", portLength + 1);
1357+
RefreshPortList();
1358+
}
1359+
1360+
void DEV9SettingsWidget::DeletePortConfig(int index)
1361+
{
1362+
const int portLength = CountPortsConfig();
1363+
1364+
//Shuffle entries down to ovewrite deleted entry
1365+
for (int i = index; i < portLength - 1; i++)
1366+
{
1367+
std::string section = "DEV9/Eth/Ports/Port" + std::to_string(i);
1368+
std::string sectionAhead = "DEV9/Eth/Ports/Port" + std::to_string(i + 1);
1369+
1370+
// clang-format off
1371+
dialog()->setIntSettingValue (section.c_str(), "Port", dialog()->getIntValue (sectionAhead.c_str(), "Url", 0).value());
1372+
dialog()->setStringSettingValue(section.c_str(), "Desc", dialog()->getStringValue(sectionAhead.c_str(), "Desc", "").value().c_str());
1373+
dialog()->setStringSettingValue(section.c_str(), "Protocol", dialog()->getStringValue(sectionAhead.c_str(), "Address", "UDP").value().c_str());
1374+
dialog()->setBoolSettingValue (section.c_str(), "Enabled", dialog()->getBoolValue (sectionAhead.c_str(), "Enabled", false).value());
1375+
// clang-format on
1376+
}
1377+
1378+
//Delete last entry
1379+
std::string section = "DEV9/Eth/Ports/Port" + std::to_string(portLength - 1);
1380+
//Specifying a value of nullopt will delete the key
1381+
//if the key is a nullptr, the whole section is deleted
1382+
dialog()->setStringSettingValue(section.c_str(), nullptr, std::nullopt);
1383+
1384+
dialog()->setIntSettingValue("DEV9/Eth/Ports", "Count", portLength - 1);
1385+
RefreshPortList();
1386+
}
1387+
10851388
DEV9SettingsWidget::~DEV9SettingsWidget() = default;

pcsx2-qt/Settings/DEV9SettingsWidget.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ private Q_SLOTS:
3131
void onEthHostImport();
3232
void onEthHostPerGame();
3333
void onEthHostEdit(QStandardItem* item);
34+
void onEthPortAdd();
35+
void onEthPortDel();
36+
void onEthPortPerGame();
37+
void onEthPortEdit(QStandardItem* item);
3438

3539
void onHddEnabledChanged(Qt::CheckState state);
3640
void onHddBrowseFileClicked();
@@ -59,6 +63,13 @@ private Q_SLOTS:
5963
void AddNewHostConfig(const HostEntryUi& host);
6064
void DeleteHostConfig(int index);
6165

66+
void RefreshPortList();
67+
int CountPortsConfig();
68+
std::optional<std::vector<PortEntryUi>> ListPortsConfig();
69+
std::vector<PortEntryUi> ListBasePortsConfig();
70+
void AddNewPortConfig(const PortEntryUi& port);
71+
void DeletePortConfig(int index);
72+
6273
void UpdateHddSizeUIEnabled();
6374
void UpdateHddSizeUIValues();
6475

@@ -69,6 +80,9 @@ private Q_SLOTS:
6980
QStandardItemModel* m_ethHost_model;
7081
QSortFilterProxyModel* m_ethHosts_proxy;
7182

83+
QStandardItemModel* m_ethPort_model;
84+
QSortFilterProxyModel* m_ethPorts_proxy;
85+
7286
bool m_adaptersLoaded{false};
7387
std::vector<Pcsx2Config::DEV9Options::NetApi> m_api_list;
7488
std::vector<const char*> m_api_namelist;

0 commit comments

Comments
 (0)