Просмотр исходного кода

Merge pull request #6863 from spholz/fix-lan-play

Fix LAN Play
Fernando S 5 лет назад
Родитель
Сommit
521e6ac174

+ 1 - 1
CMakeLists.txt

@@ -701,7 +701,7 @@ if (APPLE)
 elseif (WIN32)
     # WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista)
     add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600)
-    set(PLATFORM_LIBRARIES winmm ws2_32)
+    set(PLATFORM_LIBRARIES winmm ws2_32 iphlpapi)
     if (MINGW)
         # PSAPI is the Process Status API
         set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)

+ 2 - 1
src/common/settings.h

@@ -558,9 +558,10 @@ struct Values {
     BasicSetting<std::string> log_filter{"*:Info", "log_filter"};
     BasicSetting<bool> use_dev_keys{false, "use_dev_keys"};
 
-    // Services
+    // Network
     BasicSetting<std::string> bcat_backend{"none", "bcat_backend"};
     BasicSetting<bool> bcat_boxcat_local{false, "bcat_boxcat_local"};
+    BasicSetting<std::string> network_interface{std::string(), "network_interface"};
 
     // WebService
     BasicSetting<bool> enable_telemetry{true, "enable_telemetry"};

+ 2 - 0
src/core/CMakeLists.txt

@@ -636,6 +636,8 @@ add_library(core STATIC
     memory.h
     network/network.cpp
     network/network.h
+    network/network_interface.cpp
+    network/network_interface.h
     network/sockets.h
     perf_stats.cpp
     perf_stats.h

+ 31 - 14
src/core/hle/service/nifm/nifm.cpp

@@ -11,6 +11,7 @@
 #include "core/hle/service/nifm/nifm.h"
 #include "core/hle/service/service.h"
 #include "core/network/network.h"
+#include "core/network/network_interface.h"
 
 namespace Service::NIFM {
 
@@ -179,10 +180,10 @@ private:
         IPC::ResponseBuilder rb{ctx, 3};
         rb.Push(ResultSuccess);
 
-        if (Settings::values.bcat_backend.GetValue() == "none") {
-            rb.PushEnum(RequestState::NotSubmitted);
-        } else {
+        if (Network::GetHostIPv4Address().has_value()) {
             rb.PushEnum(RequestState::Connected);
+        } else {
+            rb.PushEnum(RequestState::NotSubmitted);
         }
     }
 
@@ -322,12 +323,15 @@ private:
     void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
         LOG_WARNING(Service_NIFM, "(STUBBED) called");
 
-        const auto [ipv4, error] = Network::GetHostIPv4Address();
-        UNIMPLEMENTED_IF(error != Network::Errno::SUCCESS);
+        auto ipv4 = Network::GetHostIPv4Address();
+        if (!ipv4) {
+            LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0");
+            ipv4.emplace(Network::IPv4Address{0, 0, 0, 0});
+        }
 
         IPC::ResponseBuilder rb{ctx, 3};
         rb.Push(ResultSuccess);
-        rb.PushRaw(ipv4);
+        rb.PushRaw(*ipv4);
     }
     void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
         LOG_DEBUG(Service_NIFM, "called");
@@ -354,10 +358,10 @@ private:
         static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
                       "IpConfigInfo has incorrect size.");
 
-        const IpConfigInfo ip_config_info{
+        IpConfigInfo ip_config_info{
             .ip_address_setting{
                 .is_automatic{true},
-                .current_address{192, 168, 1, 100},
+                .current_address{0, 0, 0, 0},
                 .subnet_mask{255, 255, 255, 0},
                 .gateway{192, 168, 1, 1},
             },
@@ -368,6 +372,19 @@ private:
             },
         };
 
+        const auto iface = Network::GetSelectedNetworkInterface();
+        if (iface) {
+            ip_config_info.ip_address_setting =
+                IpAddressSetting{.is_automatic{true},
+                                 .current_address{Network::TranslateIPv4(iface->ip_address)},
+                                 .subnet_mask{Network::TranslateIPv4(iface->subnet_mask)},
+                                 .gateway{Network::TranslateIPv4(iface->gateway)}};
+
+        } else {
+            LOG_ERROR(Service_NIFM,
+                      "Couldn't get host network configuration info, using default values");
+        }
+
         IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)};
         rb.Push(ResultSuccess);
         rb.PushRaw<IpConfigInfo>(ip_config_info);
@@ -384,10 +401,10 @@ private:
 
         IPC::ResponseBuilder rb{ctx, 3};
         rb.Push(ResultSuccess);
-        if (Settings::values.bcat_backend.GetValue() == "none") {
-            rb.Push<u8>(0);
-        } else {
+        if (Network::GetHostIPv4Address().has_value()) {
             rb.Push<u8>(1);
+        } else {
+            rb.Push<u8>(0);
         }
     }
     void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
@@ -395,10 +412,10 @@ private:
 
         IPC::ResponseBuilder rb{ctx, 3};
         rb.Push(ResultSuccess);
-        if (Settings::values.bcat_backend.GetValue() == "none") {
-            rb.Push<u8>(0);
-        } else {
+        if (Network::GetHostIPv4Address().has_value()) {
             rb.Push<u8>(1);
+        } else {
+            rb.Push<u8>(0);
         }
     }
 };

+ 31 - 33
src/core/network/network.cpp

@@ -10,9 +10,10 @@
 #include "common/common_funcs.h"
 
 #ifdef _WIN32
-#define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname
 #include <winsock2.h>
+#include <ws2tcpip.h>
 #elif YUZU_UNIX
+#include <arpa/inet.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <netdb.h>
@@ -27,7 +28,9 @@
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "core/network/network.h"
+#include "core/network/network_interface.h"
 #include "core/network/sockets.h"
 
 namespace Network {
@@ -47,11 +50,6 @@ void Finalize() {
     WSACleanup();
 }
 
-constexpr IPv4Address TranslateIPv4(in_addr addr) {
-    auto& bytes = addr.S_un.S_un_b;
-    return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4};
-}
-
 sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
     sockaddr_in result;
 
@@ -138,12 +136,6 @@ void Initialize() {}
 
 void Finalize() {}
 
-constexpr IPv4Address TranslateIPv4(in_addr addr) {
-    const u32 bytes = addr.s_addr;
-    return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8),
-                       static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)};
-}
-
 sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
     sockaddr_in result;
 
@@ -182,7 +174,7 @@ linger MakeLinger(bool enable, u32 linger_value) {
 }
 
 bool EnableNonBlock(int fd, bool enable) {
-    int flags = fcntl(fd, F_GETFD);
+    int flags = fcntl(fd, F_GETFL);
     if (flags == -1) {
         return false;
     }
@@ -191,7 +183,7 @@ bool EnableNonBlock(int fd, bool enable) {
     } else {
         flags &= ~O_NONBLOCK;
     }
-    return fcntl(fd, F_SETFD, flags) == 0;
+    return fcntl(fd, F_SETFL, flags) == 0;
 }
 
 Errno TranslateNativeError(int e) {
@@ -227,8 +219,12 @@ Errno GetAndLogLastError() {
 #else
     int e = errno;
 #endif
+    const Errno err = TranslateNativeError(e);
+    if (err == Errno::AGAIN) {
+        return err;
+    }
     LOG_ERROR(Network, "Socket operation error: {}", NativeErrorToString(e));
-    return TranslateNativeError(e);
+    return err;
 }
 
 int TranslateDomain(Domain domain) {
@@ -353,27 +349,29 @@ NetworkInstance::~NetworkInstance() {
     Finalize();
 }
 
-std::pair<IPv4Address, Errno> GetHostIPv4Address() {
-    std::array<char, 256> name{};
-    if (gethostname(name.data(), static_cast<int>(name.size()) - 1) == SOCKET_ERROR) {
-        return {IPv4Address{}, GetAndLogLastError()};
+std::optional<IPv4Address> GetHostIPv4Address() {
+    const std::string& selected_network_interface = Settings::values.network_interface.GetValue();
+    const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
+    if (network_interfaces.size() == 0) {
+        LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
+        return {};
     }
 
-    hostent* const ent = gethostbyname(name.data());
-    if (!ent) {
-        return {IPv4Address{}, GetAndLogLastError()};
-    }
-    if (ent->h_addr_list == nullptr) {
-        UNIMPLEMENTED_MSG("No addr provided in hostent->h_addr_list");
-        return {IPv4Address{}, Errno::SUCCESS};
-    }
-    if (ent->h_length != sizeof(in_addr)) {
-        UNIMPLEMENTED_MSG("Unexpected size={} in hostent->h_length", ent->h_length);
-    }
+    const auto res =
+        std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) {
+            return iface.name == selected_network_interface;
+        });
+
+    if (res != network_interfaces.end()) {
+        char ip_addr[16] = {};
+        ASSERT(inet_ntop(AF_INET, &res->ip_address, ip_addr, sizeof(ip_addr)) != nullptr);
+        LOG_INFO(Network, "IP address: {}", ip_addr);
 
-    in_addr addr;
-    std::memcpy(&addr, ent->h_addr_list[0], sizeof(addr));
-    return {TranslateIPv4(addr), Errno::SUCCESS};
+        return TranslateIPv4(res->ip_address);
+    } else {
+        LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
+        return {};
+    }
 }
 
 std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {

+ 22 - 2
src/core/network/network.h

@@ -5,11 +5,18 @@
 #pragma once
 
 #include <array>
+#include <optional>
 #include <utility>
 
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 
+#ifdef _WIN32
+#include <winsock2.h>
+#elif YUZU_UNIX
+#include <netinet/in.h>
+#endif
+
 namespace Network {
 
 class Socket;
@@ -92,8 +99,21 @@ public:
     ~NetworkInstance();
 };
 
+#ifdef _WIN32
+constexpr IPv4Address TranslateIPv4(in_addr addr) {
+    auto& bytes = addr.S_un.S_un_b;
+    return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4};
+}
+#elif YUZU_UNIX
+constexpr IPv4Address TranslateIPv4(in_addr addr) {
+    const u32 bytes = addr.s_addr;
+    return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8),
+                       static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)};
+}
+#endif
+
 /// @brief Returns host's IPv4 address
-/// @return Pair of an array of human ordered IPv4 address (e.g. 192.168.0.1) and an error code
-std::pair<IPv4Address, Errno> GetHostIPv4Address();
+/// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array
+std::optional<IPv4Address> GetHostIPv4Address();
 
 } // namespace Network

+ 203 - 0
src/core/network/network_interface.cpp

@@ -0,0 +1,203 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <fstream>
+#include <sstream>
+#include <vector>
+
+#include "common/bit_cast.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/string_util.h"
+#include "core/network/network_interface.h"
+
+#ifdef _WIN32
+#include <iphlpapi.h>
+#else
+#include <cerrno>
+#include <ifaddrs.h>
+#include <net/if.h>
+#endif
+
+namespace Network {
+
+#ifdef _WIN32
+
+std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
+    std::vector<IP_ADAPTER_ADDRESSES> adapter_addresses;
+    DWORD ret = ERROR_BUFFER_OVERFLOW;
+    DWORD buf_size = 0;
+
+    // retry up to 5 times
+    for (int i = 0; i < 5 && ret == ERROR_BUFFER_OVERFLOW; i++) {
+        ret = GetAdaptersAddresses(
+            AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS,
+            nullptr, adapter_addresses.data(), &buf_size);
+
+        if (ret == ERROR_BUFFER_OVERFLOW) {
+            adapter_addresses.resize((buf_size / sizeof(IP_ADAPTER_ADDRESSES)) + 1);
+        } else {
+            break;
+        }
+    }
+
+    if (ret == NO_ERROR) {
+        std::vector<NetworkInterface> result;
+
+        for (auto current_address = adapter_addresses.data(); current_address != nullptr;
+             current_address = current_address->Next) {
+            if (current_address->FirstUnicastAddress == nullptr ||
+                current_address->FirstUnicastAddress->Address.lpSockaddr == nullptr) {
+                continue;
+            }
+
+            if (current_address->OperStatus != IfOperStatusUp) {
+                continue;
+            }
+
+            const auto ip_addr = Common::BitCast<struct sockaddr_in>(
+                                     *current_address->FirstUnicastAddress->Address.lpSockaddr)
+                                     .sin_addr;
+
+            ULONG mask = 0;
+            if (ConvertLengthToIpv4Mask(current_address->FirstUnicastAddress->OnLinkPrefixLength,
+                                        &mask) != NO_ERROR) {
+                LOG_ERROR(Network, "Failed to convert IPv4 prefix length to subnet mask");
+                continue;
+            }
+
+            struct in_addr gateway = {.S_un{.S_addr{0}}};
+            if (current_address->FirstGatewayAddress != nullptr &&
+                current_address->FirstGatewayAddress->Address.lpSockaddr != nullptr) {
+                gateway = Common::BitCast<struct sockaddr_in>(
+                              *current_address->FirstGatewayAddress->Address.lpSockaddr)
+                              .sin_addr;
+            }
+
+            result.push_back(NetworkInterface{
+                .name{Common::UTF16ToUTF8(std::wstring{current_address->FriendlyName})},
+                .ip_address{ip_addr},
+                .subnet_mask = in_addr{.S_un{.S_addr{mask}}},
+                .gateway = gateway});
+        }
+
+        return result;
+    } else {
+        LOG_ERROR(Network, "Failed to get network interfaces with GetAdaptersAddresses");
+        return {};
+    }
+}
+
+#else
+
+std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
+    std::vector<NetworkInterface> result;
+
+    struct ifaddrs* ifaddr = nullptr;
+
+    if (getifaddrs(&ifaddr) != 0) {
+        LOG_ERROR(Network, "Failed to get network interfaces with getifaddrs: {}",
+                  std::strerror(errno));
+        return result;
+    }
+
+    for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
+        if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr) {
+            continue;
+        }
+
+        if (ifa->ifa_addr->sa_family != AF_INET) {
+            continue;
+        }
+
+        if ((ifa->ifa_flags & IFF_UP) == 0 || (ifa->ifa_flags & IFF_LOOPBACK) != 0) {
+            continue;
+        }
+
+        std::uint32_t gateway{0};
+        std::ifstream file{"/proc/net/route"};
+        if (file.is_open()) {
+
+            // ignore header
+            file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
+
+            bool gateway_found = false;
+
+            for (std::string line; std::getline(file, line);) {
+                std::istringstream iss{line};
+
+                std::string iface_name{};
+                iss >> iface_name;
+                if (iface_name != ifa->ifa_name) {
+                    continue;
+                }
+
+                iss >> std::hex;
+
+                std::uint32_t dest{0};
+                iss >> dest;
+                if (dest != 0) {
+                    // not the default route
+                    continue;
+                }
+
+                iss >> gateway;
+
+                std::uint16_t flags{0};
+                iss >> flags;
+
+                // flag RTF_GATEWAY (defined in <linux/route.h>)
+                if ((flags & 0x2) == 0) {
+                    continue;
+                }
+
+                gateway_found = true;
+                break;
+            }
+
+            if (!gateway_found) {
+                gateway = 0;
+            }
+        } else {
+            LOG_ERROR(Network, "Failed to open \"/proc/net/route\"");
+        }
+
+        result.push_back(NetworkInterface{
+            .name{ifa->ifa_name},
+            .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr},
+            .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr},
+            .gateway{in_addr{.s_addr = gateway}}});
+    }
+
+    freeifaddrs(ifaddr);
+
+    return result;
+}
+
+#endif
+
+std::optional<NetworkInterface> GetSelectedNetworkInterface() {
+    const std::string& selected_network_interface = Settings::values.network_interface.GetValue();
+    const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
+    if (network_interfaces.size() == 0) {
+        LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
+        return {};
+    }
+
+    const auto res =
+        std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) {
+            return iface.name == selected_network_interface;
+        });
+
+    if (res != network_interfaces.end()) {
+        return *res;
+    } else {
+        LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
+        return {};
+    }
+}
+
+} // namespace Network

+ 29 - 0
src/core/network/network_interface.h

@@ -0,0 +1,29 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <netinet/in.h>
+#endif
+
+namespace Network {
+
+struct NetworkInterface {
+    std::string name;
+    struct in_addr ip_address;
+    struct in_addr subnet_mask;
+    struct in_addr gateway;
+};
+
+std::vector<NetworkInterface> GetAvailableNetworkInterfaces();
+std::optional<NetworkInterface> GetSelectedNetworkInterface();
+
+} // namespace Network

+ 3 - 3
src/yuzu/CMakeLists.txt

@@ -102,9 +102,9 @@ add_executable(yuzu
     configuration/configure_profile_manager.cpp
     configuration/configure_profile_manager.h
     configuration/configure_profile_manager.ui
-    configuration/configure_service.cpp
-    configuration/configure_service.h
-    configuration/configure_service.ui
+    configuration/configure_network.cpp
+    configuration/configure_network.h
+    configuration/configure_network.ui
     configuration/configure_system.cpp
     configuration/configure_system.h
     configuration/configure_system.ui

+ 4 - 2
src/yuzu/configuration/config.cpp

@@ -692,6 +692,7 @@ void Config::ReadServiceValues() {
     qt_config->beginGroup(QStringLiteral("Services"));
     ReadBasicSetting(Settings::values.bcat_backend);
     ReadBasicSetting(Settings::values.bcat_boxcat_local);
+    ReadBasicSetting(Settings::values.network_interface);
     qt_config->endGroup();
 }
 
@@ -1144,7 +1145,7 @@ void Config::SaveValues() {
         SaveDataStorageValues();
         SaveDebuggingValues();
         SaveDisabledAddOnValues();
-        SaveServiceValues();
+        SaveNetworkValues();
         SaveUIValues();
         SaveWebServiceValues();
         SaveMiscellaneousValues();
@@ -1238,11 +1239,12 @@ void Config::SaveDebuggingValues() {
     qt_config->endGroup();
 }
 
-void Config::SaveServiceValues() {
+void Config::SaveNetworkValues() {
     qt_config->beginGroup(QStringLiteral("Services"));
 
     WriteBasicSetting(Settings::values.bcat_backend);
     WriteBasicSetting(Settings::values.bcat_boxcat_local);
+    WriteBasicSetting(Settings::values.network_interface);
 
     qt_config->endGroup();
 }

+ 1 - 1
src/yuzu/configuration/config.h

@@ -88,7 +88,7 @@ private:
     void SaveCoreValues();
     void SaveDataStorageValues();
     void SaveDebuggingValues();
-    void SaveServiceValues();
+    void SaveNetworkValues();
     void SaveDisabledAddOnValues();
     void SaveMiscellaneousValues();
     void SavePathValues();

+ 5 - 5
src/yuzu/configuration/configure.ui

@@ -147,12 +147,12 @@
          <string>Web</string>
         </attribute>
        </widget>
-       <widget class="ConfigureService" name="serviceTab">
+       <widget class="ConfigureNetwork" name="networkTab">
         <property name="accessibleName">
-         <string>Services</string>
+         <string>Network</string>
         </property>
         <attribute name="title">
-         <string>Services</string>
+         <string>Network</string>
         </attribute>
        </widget>
       </widget>
@@ -242,9 +242,9 @@
    <container>1</container>
   </customwidget>
   <customwidget>
-   <class>ConfigureService</class>
+   <class>ConfigureNetwork</class>
    <extends>QWidget</extends>
-   <header>configuration/configure_service.h</header>
+   <header>configuration/configure_network.h</header>
    <container>1</container>
   </customwidget>
   <customwidget>

+ 2 - 2
src/yuzu/configuration/configure_dialog.cpp

@@ -67,7 +67,7 @@ void ConfigureDialog::ApplyConfiguration() {
     ui->audioTab->ApplyConfiguration();
     ui->debugTab->ApplyConfiguration();
     ui->webTab->ApplyConfiguration();
-    ui->serviceTab->ApplyConfiguration();
+    ui->networkTab->ApplyConfiguration();
     Core::System::GetInstance().ApplySettings();
     Settings::LogSettings();
 }
@@ -103,7 +103,7 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
 void ConfigureDialog::PopulateSelectionList() {
     const std::array<std::pair<QString, QList<QWidget*>>, 6> items{
         {{tr("General"), {ui->generalTab, ui->hotkeysTab, ui->uiTab, ui->webTab, ui->debugTab}},
-         {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}},
+         {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->networkTab, ui->filesystemTab}},
          {tr("CPU"), {ui->cpuTab}},
          {tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}},
          {tr("Audio"), {ui->audioTab}},

+ 27 - 12
src/yuzu/configuration/configure_service.cpp → src/yuzu/configuration/configure_network.cpp

@@ -5,9 +5,11 @@
 #include <QGraphicsItem>
 #include <QtConcurrent/QtConcurrent>
 #include "common/settings.h"
+#include "core/core.h"
 #include "core/hle/service/bcat/backend/boxcat.h"
-#include "ui_configure_service.h"
-#include "yuzu/configuration/configure_service.h"
+#include "core/network/network_interface.h"
+#include "ui_configure_network.h"
+#include "yuzu/configuration/configure_network.h"
 
 #ifdef YUZU_ENABLE_BOXCAT
 namespace {
@@ -35,8 +37,8 @@ QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
 } // Anonymous namespace
 #endif
 
-ConfigureService::ConfigureService(QWidget* parent)
-    : QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) {
+ConfigureNetwork::ConfigureNetwork(QWidget* parent)
+    : QWidget(parent), ui(std::make_unique<Ui::ConfigureNetwork>()) {
     ui->setupUi(this);
 
     ui->bcat_source->addItem(QStringLiteral("None"));
@@ -47,29 +49,42 @@ ConfigureService::ConfigureService(QWidget* parent)
     ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat"));
 #endif
 
+    ui->network_interface->addItem(tr("None"));
+    for (const auto& iface : Network::GetAvailableNetworkInterfaces()) {
+        ui->network_interface->addItem(QString::fromStdString(iface.name));
+    }
+
     connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
-            &ConfigureService::OnBCATImplChanged);
+            &ConfigureNetwork::OnBCATImplChanged);
 
     this->SetConfiguration();
 }
 
-ConfigureService::~ConfigureService() = default;
+ConfigureNetwork::~ConfigureNetwork() = default;
 
-void ConfigureService::ApplyConfiguration() {
+void ConfigureNetwork::ApplyConfiguration() {
     Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString();
+    Settings::values.network_interface = ui->network_interface->currentText().toStdString();
 }
 
-void ConfigureService::RetranslateUi() {
+void ConfigureNetwork::RetranslateUi() {
     ui->retranslateUi(this);
 }
 
-void ConfigureService::SetConfiguration() {
+void ConfigureNetwork::SetConfiguration() {
+    const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
+
     const int index =
         ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend.GetValue()));
     ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index);
+
+    const std::string& network_interface = Settings::values.network_interface.GetValue();
+
+    ui->network_interface->setCurrentText(QString::fromStdString(network_interface));
+    ui->network_interface->setEnabled(runtime_lock);
 }
 
-std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
+std::pair<QString, QString> ConfigureNetwork::BCATDownloadEvents() {
 #ifdef YUZU_ENABLE_BOXCAT
     std::optional<std::string> global;
     std::map<std::string, Service::BCAT::EventStatus> map;
@@ -114,7 +129,7 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
 #endif
 }
 
-void ConfigureService::OnBCATImplChanged() {
+void ConfigureNetwork::OnBCATImplChanged() {
 #ifdef YUZU_ENABLE_BOXCAT
     const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
     ui->bcat_empty_header->setHidden(!boxcat);
@@ -133,7 +148,7 @@ void ConfigureService::OnBCATImplChanged() {
 #endif
 }
 
-void ConfigureService::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) {
+void ConfigureNetwork::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) {
 #ifdef YUZU_ENABLE_BOXCAT
     const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
     if (boxcat) {

+ 5 - 5
src/yuzu/configuration/configure_service.h → src/yuzu/configuration/configure_network.h

@@ -9,15 +9,15 @@
 #include <QWidget>
 
 namespace Ui {
-class ConfigureService;
+class ConfigureNetwork;
 }
 
-class ConfigureService : public QWidget {
+class ConfigureNetwork : public QWidget {
     Q_OBJECT
 
 public:
-    explicit ConfigureService(QWidget* parent = nullptr);
-    ~ConfigureService() override;
+    explicit ConfigureNetwork(QWidget* parent = nullptr);
+    ~ConfigureNetwork() override;
 
     void ApplyConfiguration();
     void RetranslateUi();
@@ -29,6 +29,6 @@ private:
     void OnBCATImplChanged();
     void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string);
 
-    std::unique_ptr<Ui::ConfigureService> ui;
+    std::unique_ptr<Ui::ConfigureNetwork> ui;
     QFutureWatcher<std::pair<QString, QString>> watcher{this};
 };

+ 41 - 22
src/yuzu/configuration/configure_service.ui → src/yuzu/configuration/configure_network.ui

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
- <class>ConfigureService</class>
- <widget class="QWidget" name="ConfigureService">
+ <class>ConfigureNetwork</class>
+ <widget class="QWidget" name="ConfigureNetwork">
   <property name="geometry">
    <rect>
     <x>0</x>
@@ -16,22 +16,38 @@
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <layout class="QVBoxLayout" name="verticalLayout_3">
+     <item>
+      <widget class="QGroupBox" name="groupBox_2">
+       <property name="title">
+        <string>General</string>
+       </property>
+       <layout class="QGridLayout" name="gridLayout_2">
+        <item row="1" column="1">
+         <widget class="QComboBox" name="network_interface"/>
+        </item>
+        <item row="1" column="0">
+         <widget class="QLabel" name="label_4">
+          <property name="text">
+           <string>Network Interface</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
      <item>
       <widget class="QGroupBox" name="groupBox">
        <property name="title">
         <string>BCAT</string>
        </property>
        <layout class="QGridLayout" name="gridLayout">
-        <item row="1" column="1" colspan="2">
-         <widget class="QLabel" name="label_2">
-          <property name="maximumSize">
-           <size>
-            <width>260</width>
-            <height>16777215</height>
-           </size>
-          </property>
+        <item row="3" column="0">
+         <widget class="QLabel" name="bcat_empty_header">
           <property name="text">
-           <string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string>
+           <string/>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
           </property>
           <property name="wordWrap">
            <bool>true</bool>
@@ -51,11 +67,8 @@
           </property>
          </widget>
         </item>
-        <item row="3" column="1" colspan="2">
-         <widget class="QLabel" name="bcat_empty_label">
-          <property name="enabled">
-           <bool>true</bool>
-          </property>
+        <item row="1" column="1" colspan="2">
+         <widget class="QLabel" name="label_2">
           <property name="maximumSize">
            <size>
             <width>260</width>
@@ -63,10 +76,7 @@
            </size>
           </property>
           <property name="text">
-           <string/>
-          </property>
-          <property name="alignment">
-           <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+           <string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string>
           </property>
           <property name="wordWrap">
            <bool>true</bool>
@@ -86,8 +96,17 @@
         <item row="0" column="1" colspan="2">
          <widget class="QComboBox" name="bcat_source"/>
         </item>
-        <item row="3" column="0">
-         <widget class="QLabel" name="bcat_empty_header">
+        <item row="3" column="1" colspan="2">
+         <widget class="QLabel" name="bcat_empty_label">
+          <property name="enabled">
+           <bool>true</bool>
+          </property>
+          <property name="maximumSize">
+           <size>
+            <width>260</width>
+            <height>16777215</height>
+           </size>
+          </property>
           <property name="text">
            <string/>
           </property>