Преглед изворни кода

configuration: add option to select network interface
This commit renames the "Services" tab to "Network" and adds a combobox that allows the user to select the network interface that yuzu should use. This new setting is now used to get the local IP address in Network::GetHostIPv4Address. This prevents yuzu from selecting the wrong network interface and thus using the wrong IP address. The return type of Network::GetHostIPv4Adress has also been changed.

spholz пре 5 година
родитељ
комит
1e98e73828

+ 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

@@ -433,9 +433,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

+ 21 - 15
src/core/hle/service/nifm/nifm.cpp

@@ -179,10 +179,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 +322,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_CRITICAL(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.value());
     }
     void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
         LOG_DEBUG(Service_NIFM, "called");
@@ -354,13 +357,16 @@ private:
         static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
                       "IpConfigInfo has incorrect size.");
 
-        const auto [ipv4, error] = Network::GetHostIPv4Address();
-        ASSERT_MSG(error == Network::Errno::SUCCESS, "Couldn't get host IPv4 address");
+        auto ipv4 = Network::GetHostIPv4Address();
+        if (!ipv4) {
+            LOG_CRITICAL(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0");
+            ipv4.emplace(Network::IPv4Address{0, 0, 0, 0});
+        }
 
         const IpConfigInfo ip_config_info{
             .ip_address_setting{
                 .is_automatic{true},
-                .current_address{ipv4},
+                .current_address{ipv4.value()},
                 .subnet_mask{255, 255, 255, 0},
                 .gateway{192, 168, 1, 1},
             },
@@ -387,10 +393,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) {
@@ -398,10 +404,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);
         }
     }
 };

+ 23 - 20
src/core/network/network.cpp

@@ -10,8 +10,8 @@
 #include "common/common_funcs.h"
 
 #ifdef _WIN32
-#define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname
 #include <winsock2.h>
+#include <ws2tcpip.h>
 #elif YUZU_UNIX
 #include <errno.h>
 #include <fcntl.h>
@@ -19,6 +19,7 @@
 #include <netinet/in.h>
 #include <poll.h>
 #include <sys/socket.h>
+#include <arpa/inet.h>
 #include <unistd.h>
 #else
 #error "Unimplemented platform"
@@ -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 {
@@ -357,27 +360,27 @@ 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();
+    ASSERT_MSG(network_interfaces.size() > 0, "GetAvailableNetworkInterfaces returned no interfaces");
 
-    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);
-    }
 
-    in_addr addr;
-    std::memcpy(&addr, ent->h_addr_list[0], sizeof(addr));
-    return {TranslateIPv4(addr), Errno::SUCCESS};
+    const auto res = std::ranges::find_if(network_interfaces,
+                                          [&selected_network_interface](const auto& interface) {
+                                              return interface.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);
+
+        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) {

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

@@ -5,6 +5,7 @@
 #pragma once
 
 #include <array>
+#include <optional>
 #include <utility>
 
 #include "common/common_funcs.h"
@@ -93,7 +94,7 @@ public:
 };
 
 /// @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

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

@@ -0,0 +1,113 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <vector>
+
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/network/network_interface.h"
+
+#ifdef _WIN32
+#include <iphlpapi.h>
+#else
+#include <ifaddrs.h>
+#include <net/if.h>
+#include <cerrno>
+#endif
+
+namespace Network {
+
+#ifdef _WIN32
+
+std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
+    std::vector<NetworkInterface> result;
+
+    std::vector<u8> adapter_addresses_raw;
+    auto adapter_addresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(adapter_addresses_raw.data());
+    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,
+                                   nullptr, adapter_addresses, &buf_size);
+
+        if (ret == ERROR_BUFFER_OVERFLOW) {
+            adapter_addresses_raw.resize(buf_size);
+            adapter_addresses =
+                reinterpret_cast<PIP_ADAPTER_ADDRESSES>(adapter_addresses_raw.data());
+        } else {
+            break;
+        }
+    }
+
+    if (ret == NO_ERROR) {
+        for (auto current_address = adapter_addresses; 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 = std::bit_cast<struct sockaddr_in>(
+                                     *current_address->FirstUnicastAddress->Address.lpSockaddr)
+                                     .sin_addr;
+
+            result.push_back(NetworkInterface{
+                .name{Common::UTF16ToUTF8(std::wstring{current_address->FriendlyName})},
+                .ip_address{ip_addr}
+            });
+        }
+    } else {
+        LOG_ERROR(Network, "Failed to get network interfaces with GetAdaptersAddresses");
+    }
+
+    return result;
+}
+
+#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) {
+            continue;
+        }
+
+        if (ifa->ifa_addr->sa_family != AF_INET) {
+            continue;
+        }
+
+        if (!(ifa->ifa_flags & IFF_UP) || ifa->ifa_flags & IFF_LOOPBACK) {
+            continue;
+        }
+
+        result.push_back(NetworkInterface{
+            .name{ifa->ifa_name},
+            .ip_address{std::bit_cast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr}
+        });
+    }
+
+    freeifaddrs(ifaddr);
+
+    return result;
+}
+
+#endif
+
+} // namespace Network

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

@@ -0,0 +1,25 @@
+// Copyright 2021 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#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;
+};
+
+std::vector<NetworkInterface> GetAvailableNetworkInterfaces();
+
+} // 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}},

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

@@ -5,9 +5,10 @@
 #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 "ui_configure_network.h"
+#include "yuzu/configuration/configure_network.h"
 
 #ifdef YUZU_ENABLE_BOXCAT
 namespace {
@@ -35,8 +36,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 +48,42 @@ ConfigureService::ConfigureService(QWidget* parent)
     ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat"));
 #endif
 
+    ui->network_interface->addItem(QStringLiteral("None"));
+    for (const auto& interface : Network::GetAvailableNetworkInterfaces()) {
+        ui->network_interface->addItem(QString::fromStdString(interface.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 +128,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 +147,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) {

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

@@ -8,16 +8,18 @@
 #include <QFutureWatcher>
 #include <QWidget>
 
+#include "core/network/network_interface.h"
+
 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 +31,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>