@@ -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
273305void 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+
594722void 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+
10851388DEV9SettingsWidget::~DEV9SettingsWidget () = default ;
0 commit comments