From 07e02a1acf8f48c3379dfa17aaea46cef2c9136f Mon Sep 17 00:00:00 2001
From: Tobias <thm.frey@gmail.com>
Date: Wed, 8 Mar 2023 00:51:46 +0100
Subject: [PATCH] Port multiplayer related PRs from yuzu (yuzu-emu/yuzu#9661
 and yuzu-emu/yuzu#9713) (#6319)

Co-authored-by: SoRadGaming <sohorhab.azizdel@outlook.com>
Co-authored-by: Luke Sawczak <luke@unfamiliarplace.com>
---
 src/citra_qt/multiplayer/direct_connect.cpp | 21 ++++++-----------
 src/citra_qt/multiplayer/direct_connect.ui  | 23 +++++++++----------
 src/citra_qt/multiplayer/lobby.cpp          | 20 +++++++++++++++--
 src/citra_qt/multiplayer/lobby.h            |  2 ++
 src/citra_qt/multiplayer/lobby.ui           |  7 ++++++
 src/citra_qt/multiplayer/validation.h       | 25 +++++++++++++++++----
 6 files changed, 65 insertions(+), 33 deletions(-)

diff --git a/src/citra_qt/multiplayer/direct_connect.cpp b/src/citra_qt/multiplayer/direct_connect.cpp
index feaea69c9..6cef55845 100644
--- a/src/citra_qt/multiplayer/direct_connect.cpp
+++ b/src/citra_qt/multiplayer/direct_connect.cpp
@@ -70,20 +70,13 @@ void DirectConnectWindow::Connect() {
             }
         }
     }
-    switch (static_cast<ConnectionType>(ui->connection_type->currentIndex())) {
-    case ConnectionType::TraversalServer:
-        break;
-    case ConnectionType::IP:
-        if (!ui->ip->hasAcceptableInput()) {
-            NetworkMessage::ErrorManager::ShowError(
-                NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID);
-            return;
-        }
-        if (!ui->port->hasAcceptableInput()) {
-            NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID);
-            return;
-        }
-        break;
+    if (!ui->ip->hasAcceptableInput()) {
+        NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::IP_ADDRESS_NOT_VALID);
+        return;
+    }
+    if (!ui->port->hasAcceptableInput()) {
+        NetworkMessage::ErrorManager::ShowError(NetworkMessage::ErrorManager::PORT_NOT_VALID);
+        return;
     }
 
     // Store settings
diff --git a/src/citra_qt/multiplayer/direct_connect.ui b/src/citra_qt/multiplayer/direct_connect.ui
index 681b6bf69..4a328189a 100644
--- a/src/citra_qt/multiplayer/direct_connect.ui
+++ b/src/citra_qt/multiplayer/direct_connect.ui
@@ -26,20 +26,11 @@
          <property name="leftMargin">
           <number>0</number>
          </property>
-         <item>
-          <widget class="QComboBox" name="connection_type">
-           <item>
-            <property name="text">
-             <string>IP Address</string>
-            </property>
-           </item>
-          </widget>
-         </item>
          <item>
           <widget class="QWidget" name="ip_container" native="true">
            <layout class="QHBoxLayout" name="ip_layout">
             <property name="leftMargin">
-             <number>5</number>
+             <number>0</number>
             </property>
             <property name="topMargin">
              <number>0</number>
@@ -53,17 +44,17 @@
             <item>
              <widget class="QLabel" name="label_2">
               <property name="text">
-               <string>IP</string>
+               <string>Server Address</string>
               </property>
              </widget>
             </item>
             <item>
              <widget class="QLineEdit" name="ip">
               <property name="toolTip">
-               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;IPv4 address of the host&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Server address of the host&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
               </property>
               <property name="maxLength">
-               <number>16</number>
+               <number>253</number>
               </property>
              </widget>
             </item>
@@ -85,6 +76,12 @@
               <property name="placeholderText">
                <string>24872</string>
               </property>
+              <property name="maximumSize">
+               <size>
+                <width>65</width>
+                <height>50</height>
+               </size>
+              </property>
              </widget>
             </item>
            </layout>
diff --git a/src/citra_qt/multiplayer/lobby.cpp b/src/citra_qt/multiplayer/lobby.cpp
index d74bbf3ab..72f11b9db 100644
--- a/src/citra_qt/multiplayer/lobby.cpp
+++ b/src/citra_qt/multiplayer/lobby.cpp
@@ -66,6 +66,7 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
     // UI Buttons
     connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby);
     connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
+    connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty);
     connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull);
     connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
     connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom);
@@ -282,12 +283,22 @@ bool LobbyFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& s
         return true;
     }
 
+    // filter by empty rooms
+    if (filter_empty) {
+        QModelIndex member_list = sourceModel()->index(sourceRow, Column::MEMBER, sourceParent);
+        const int player_count =
+            sourceModel()->data(member_list, LobbyItemMemberList::MemberListRole).toList().size();
+        if (player_count == 0) {
+            return false;
+        }
+    }
+
     // filter by filled rooms
     if (filter_full) {
         QModelIndex member_list = sourceModel()->index(sourceRow, Column::MEMBER, sourceParent);
-        int player_count =
+        const int player_count =
             sourceModel()->data(member_list, LobbyItemMemberList::MemberListRole).toList().size();
-        int max_players =
+        const int max_players =
             sourceModel()->data(member_list, LobbyItemMemberList::MaxPlayerRole).toInt();
         if (player_count >= max_players) {
             return false;
@@ -352,6 +363,11 @@ void LobbyFilterProxyModel::SetFilterOwned(bool filter) {
     invalidate();
 }
 
+void LobbyFilterProxyModel::SetFilterEmpty(bool filter) {
+    filter_empty = filter;
+    invalidate();
+}
+
 void LobbyFilterProxyModel::SetFilterFull(bool filter) {
     filter_full = filter;
     invalidate();
diff --git a/src/citra_qt/multiplayer/lobby.h b/src/citra_qt/multiplayer/lobby.h
index 985b82ba7..129b25497 100644
--- a/src/citra_qt/multiplayer/lobby.h
+++ b/src/citra_qt/multiplayer/lobby.h
@@ -116,12 +116,14 @@ public:
 
 public slots:
     void SetFilterOwned(bool);
+    void SetFilterEmpty(bool);
     void SetFilterFull(bool);
     void SetFilterSearch(const QString&);
 
 private:
     QStandardItemModel* game_list;
     bool filter_owned = false;
+    bool filter_empty = false;
     bool filter_full = false;
     QString filter_search;
 };
diff --git a/src/citra_qt/multiplayer/lobby.ui b/src/citra_qt/multiplayer/lobby.ui
index 4c9901c9a..0ef0ef762 100644
--- a/src/citra_qt/multiplayer/lobby.ui
+++ b/src/citra_qt/multiplayer/lobby.ui
@@ -77,6 +77,13 @@
            </property>
           </widget>
          </item>
+         <item>
+          <widget class="QCheckBox" name="hide_empty">
+           <property name="text">
+            <string>Hide Empty Rooms</string>
+           </property>
+          </widget>
+         </item>
          <item>
           <widget class="QCheckBox" name="hide_full">
            <property name="text">
diff --git a/src/citra_qt/multiplayer/validation.h b/src/citra_qt/multiplayer/validation.h
index 1c215a190..ecf3069d9 100644
--- a/src/citra_qt/multiplayer/validation.h
+++ b/src/citra_qt/multiplayer/validation.h
@@ -37,11 +37,28 @@ private:
     QRegExp nickname_regex = QRegExp(QStringLiteral("^[a-zA-Z0-9._- ]{4,20}$"));
     QRegExpValidator nickname;
 
-    /// ipv4 address only
-    // TODO remove this when we support hostnames in direct connect
+    /// ipv4 / ipv6 / hostnames
     QRegExp ip_regex = QRegExp(QStringLiteral(
-        "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|"
-        "2[0-4][0-9]|25[0-5])"));
+        // IPv4 regex
+        "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|"
+        // IPv6 regex
+        "^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|"
+        "(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-"
+        "5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|"
+        "(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)"
+        "(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|"
+        "(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]"
+        "\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|"
+        "(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2["
+        "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|"
+        "(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2["
+        "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|"
+        "(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2["
+        "0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|"
+        "(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?"
+        "\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$|"
+        // Hostname regex
+        "^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}$"));
     QRegExpValidator ip;
 
     /// port must be between 0 and 65535