mirror of
https://gitlab.com/suyu-emu/suyu.git
synced 2024-03-15 23:15:44 +00:00
commit
521e6ac174
|
@ -701,7 +701,7 @@ if (APPLE)
|
||||||
elseif (WIN32)
|
elseif (WIN32)
|
||||||
# WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista)
|
# WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista)
|
||||||
add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600)
|
add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600)
|
||||||
set(PLATFORM_LIBRARIES winmm ws2_32)
|
set(PLATFORM_LIBRARIES winmm ws2_32 iphlpapi)
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
# PSAPI is the Process Status API
|
# PSAPI is the Process Status API
|
||||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
|
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
|
||||||
|
|
|
@ -558,9 +558,10 @@ struct Values {
|
||||||
BasicSetting<std::string> log_filter{"*:Info", "log_filter"};
|
BasicSetting<std::string> log_filter{"*:Info", "log_filter"};
|
||||||
BasicSetting<bool> use_dev_keys{false, "use_dev_keys"};
|
BasicSetting<bool> use_dev_keys{false, "use_dev_keys"};
|
||||||
|
|
||||||
// Services
|
// Network
|
||||||
BasicSetting<std::string> bcat_backend{"none", "bcat_backend"};
|
BasicSetting<std::string> bcat_backend{"none", "bcat_backend"};
|
||||||
BasicSetting<bool> bcat_boxcat_local{false, "bcat_boxcat_local"};
|
BasicSetting<bool> bcat_boxcat_local{false, "bcat_boxcat_local"};
|
||||||
|
BasicSetting<std::string> network_interface{std::string(), "network_interface"};
|
||||||
|
|
||||||
// WebService
|
// WebService
|
||||||
BasicSetting<bool> enable_telemetry{true, "enable_telemetry"};
|
BasicSetting<bool> enable_telemetry{true, "enable_telemetry"};
|
||||||
|
|
|
@ -636,6 +636,8 @@ add_library(core STATIC
|
||||||
memory.h
|
memory.h
|
||||||
network/network.cpp
|
network/network.cpp
|
||||||
network/network.h
|
network/network.h
|
||||||
|
network/network_interface.cpp
|
||||||
|
network/network_interface.h
|
||||||
network/sockets.h
|
network/sockets.h
|
||||||
perf_stats.cpp
|
perf_stats.cpp
|
||||||
perf_stats.h
|
perf_stats.h
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "core/hle/service/nifm/nifm.h"
|
#include "core/hle/service/nifm/nifm.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
#include "core/network/network.h"
|
#include "core/network/network.h"
|
||||||
|
#include "core/network/network_interface.h"
|
||||||
|
|
||||||
namespace Service::NIFM {
|
namespace Service::NIFM {
|
||||||
|
|
||||||
|
@ -179,10 +180,10 @@ private:
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
|
|
||||||
if (Settings::values.bcat_backend.GetValue() == "none") {
|
if (Network::GetHostIPv4Address().has_value()) {
|
||||||
rb.PushEnum(RequestState::NotSubmitted);
|
|
||||||
} else {
|
|
||||||
rb.PushEnum(RequestState::Connected);
|
rb.PushEnum(RequestState::Connected);
|
||||||
|
} else {
|
||||||
|
rb.PushEnum(RequestState::NotSubmitted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,12 +323,15 @@ private:
|
||||||
void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
|
void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
|
||||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||||
|
|
||||||
const auto [ipv4, error] = Network::GetHostIPv4Address();
|
auto ipv4 = Network::GetHostIPv4Address();
|
||||||
UNIMPLEMENTED_IF(error != Network::Errno::SUCCESS);
|
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};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.PushRaw(ipv4);
|
rb.PushRaw(*ipv4);
|
||||||
}
|
}
|
||||||
void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
|
void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
|
||||||
LOG_DEBUG(Service_NIFM, "called");
|
LOG_DEBUG(Service_NIFM, "called");
|
||||||
|
@ -354,10 +358,10 @@ private:
|
||||||
static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
|
static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
|
||||||
"IpConfigInfo has incorrect size.");
|
"IpConfigInfo has incorrect size.");
|
||||||
|
|
||||||
const IpConfigInfo ip_config_info{
|
IpConfigInfo ip_config_info{
|
||||||
.ip_address_setting{
|
.ip_address_setting{
|
||||||
.is_automatic{true},
|
.is_automatic{true},
|
||||||
.current_address{192, 168, 1, 100},
|
.current_address{0, 0, 0, 0},
|
||||||
.subnet_mask{255, 255, 255, 0},
|
.subnet_mask{255, 255, 255, 0},
|
||||||
.gateway{192, 168, 1, 1},
|
.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)};
|
IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.PushRaw<IpConfigInfo>(ip_config_info);
|
rb.PushRaw<IpConfigInfo>(ip_config_info);
|
||||||
|
@ -384,10 +401,10 @@ private:
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
if (Settings::values.bcat_backend.GetValue() == "none") {
|
if (Network::GetHostIPv4Address().has_value()) {
|
||||||
rb.Push<u8>(0);
|
|
||||||
} else {
|
|
||||||
rb.Push<u8>(1);
|
rb.Push<u8>(1);
|
||||||
|
} else {
|
||||||
|
rb.Push<u8>(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
|
void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -395,10 +412,10 @@ private:
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
if (Settings::values.bcat_backend.GetValue() == "none") {
|
if (Network::GetHostIPv4Address().has_value()) {
|
||||||
rb.Push<u8>(0);
|
|
||||||
} else {
|
|
||||||
rb.Push<u8>(1);
|
rb.Push<u8>(1);
|
||||||
|
} else {
|
||||||
|
rb.Push<u8>(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,9 +10,10 @@
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname
|
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
#elif YUZU_UNIX
|
#elif YUZU_UNIX
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
|
@ -27,7 +28,9 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/settings.h"
|
||||||
#include "core/network/network.h"
|
#include "core/network/network.h"
|
||||||
|
#include "core/network/network_interface.h"
|
||||||
#include "core/network/sockets.h"
|
#include "core/network/sockets.h"
|
||||||
|
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
@ -47,11 +50,6 @@ void Finalize() {
|
||||||
WSACleanup();
|
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 TranslateFromSockAddrIn(SockAddrIn input) {
|
||||||
sockaddr_in result;
|
sockaddr_in result;
|
||||||
|
|
||||||
|
@ -138,12 +136,6 @@ void Initialize() {}
|
||||||
|
|
||||||
void Finalize() {}
|
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 TranslateFromSockAddrIn(SockAddrIn input) {
|
||||||
sockaddr_in result;
|
sockaddr_in result;
|
||||||
|
|
||||||
|
@ -182,7 +174,7 @@ linger MakeLinger(bool enable, u32 linger_value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EnableNonBlock(int fd, bool enable) {
|
bool EnableNonBlock(int fd, bool enable) {
|
||||||
int flags = fcntl(fd, F_GETFD);
|
int flags = fcntl(fd, F_GETFL);
|
||||||
if (flags == -1) {
|
if (flags == -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -191,7 +183,7 @@ bool EnableNonBlock(int fd, bool enable) {
|
||||||
} else {
|
} else {
|
||||||
flags &= ~O_NONBLOCK;
|
flags &= ~O_NONBLOCK;
|
||||||
}
|
}
|
||||||
return fcntl(fd, F_SETFD, flags) == 0;
|
return fcntl(fd, F_SETFL, flags) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Errno TranslateNativeError(int e) {
|
Errno TranslateNativeError(int e) {
|
||||||
|
@ -227,8 +219,12 @@ Errno GetAndLogLastError() {
|
||||||
#else
|
#else
|
||||||
int e = errno;
|
int e = errno;
|
||||||
#endif
|
#endif
|
||||||
|
const Errno err = TranslateNativeError(e);
|
||||||
|
if (err == Errno::AGAIN) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
LOG_ERROR(Network, "Socket operation error: {}", NativeErrorToString(e));
|
LOG_ERROR(Network, "Socket operation error: {}", NativeErrorToString(e));
|
||||||
return TranslateNativeError(e);
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TranslateDomain(Domain domain) {
|
int TranslateDomain(Domain domain) {
|
||||||
|
@ -353,27 +349,29 @@ NetworkInstance::~NetworkInstance() {
|
||||||
Finalize();
|
Finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<IPv4Address, Errno> GetHostIPv4Address() {
|
std::optional<IPv4Address> GetHostIPv4Address() {
|
||||||
std::array<char, 256> name{};
|
const std::string& selected_network_interface = Settings::values.network_interface.GetValue();
|
||||||
if (gethostname(name.data(), static_cast<int>(name.size()) - 1) == SOCKET_ERROR) {
|
const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
|
||||||
return {IPv4Address{}, GetAndLogLastError()};
|
if (network_interfaces.size() == 0) {
|
||||||
|
LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
hostent* const ent = gethostbyname(name.data());
|
const auto res =
|
||||||
if (!ent) {
|
std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) {
|
||||||
return {IPv4Address{}, GetAndLogLastError()};
|
return iface.name == selected_network_interface;
|
||||||
}
|
});
|
||||||
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;
|
if (res != network_interfaces.end()) {
|
||||||
std::memcpy(&addr, ent->h_addr_list[0], sizeof(addr));
|
char ip_addr[16] = {};
|
||||||
return {TranslateIPv4(addr), Errno::SUCCESS};
|
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) {
|
std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
|
||||||
|
|
|
@ -5,11 +5,18 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#elif YUZU_UNIX
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
class Socket;
|
class Socket;
|
||||||
|
@ -92,8 +99,21 @@ public:
|
||||||
~NetworkInstance();
|
~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
|
/// @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
|
/// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array
|
||||||
std::pair<IPv4Address, Errno> GetHostIPv4Address();
|
std::optional<IPv4Address> GetHostIPv4Address();
|
||||||
|
|
||||||
} // namespace Network
|
} // namespace Network
|
||||||
|
|
203
src/core/network/network_interface.cpp
Normal file
203
src/core/network/network_interface.cpp
Normal file
|
@ -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
src/core/network/network_interface.h
Normal file
29
src/core/network/network_interface.h
Normal file
|
@ -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
|
|
@ -102,9 +102,9 @@ add_executable(yuzu
|
||||||
configuration/configure_profile_manager.cpp
|
configuration/configure_profile_manager.cpp
|
||||||
configuration/configure_profile_manager.h
|
configuration/configure_profile_manager.h
|
||||||
configuration/configure_profile_manager.ui
|
configuration/configure_profile_manager.ui
|
||||||
configuration/configure_service.cpp
|
configuration/configure_network.cpp
|
||||||
configuration/configure_service.h
|
configuration/configure_network.h
|
||||||
configuration/configure_service.ui
|
configuration/configure_network.ui
|
||||||
configuration/configure_system.cpp
|
configuration/configure_system.cpp
|
||||||
configuration/configure_system.h
|
configuration/configure_system.h
|
||||||
configuration/configure_system.ui
|
configuration/configure_system.ui
|
||||||
|
|
|
@ -692,6 +692,7 @@ void Config::ReadServiceValues() {
|
||||||
qt_config->beginGroup(QStringLiteral("Services"));
|
qt_config->beginGroup(QStringLiteral("Services"));
|
||||||
ReadBasicSetting(Settings::values.bcat_backend);
|
ReadBasicSetting(Settings::values.bcat_backend);
|
||||||
ReadBasicSetting(Settings::values.bcat_boxcat_local);
|
ReadBasicSetting(Settings::values.bcat_boxcat_local);
|
||||||
|
ReadBasicSetting(Settings::values.network_interface);
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1144,7 +1145,7 @@ void Config::SaveValues() {
|
||||||
SaveDataStorageValues();
|
SaveDataStorageValues();
|
||||||
SaveDebuggingValues();
|
SaveDebuggingValues();
|
||||||
SaveDisabledAddOnValues();
|
SaveDisabledAddOnValues();
|
||||||
SaveServiceValues();
|
SaveNetworkValues();
|
||||||
SaveUIValues();
|
SaveUIValues();
|
||||||
SaveWebServiceValues();
|
SaveWebServiceValues();
|
||||||
SaveMiscellaneousValues();
|
SaveMiscellaneousValues();
|
||||||
|
@ -1238,11 +1239,12 @@ void Config::SaveDebuggingValues() {
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::SaveServiceValues() {
|
void Config::SaveNetworkValues() {
|
||||||
qt_config->beginGroup(QStringLiteral("Services"));
|
qt_config->beginGroup(QStringLiteral("Services"));
|
||||||
|
|
||||||
WriteBasicSetting(Settings::values.bcat_backend);
|
WriteBasicSetting(Settings::values.bcat_backend);
|
||||||
WriteBasicSetting(Settings::values.bcat_boxcat_local);
|
WriteBasicSetting(Settings::values.bcat_boxcat_local);
|
||||||
|
WriteBasicSetting(Settings::values.network_interface);
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ private:
|
||||||
void SaveCoreValues();
|
void SaveCoreValues();
|
||||||
void SaveDataStorageValues();
|
void SaveDataStorageValues();
|
||||||
void SaveDebuggingValues();
|
void SaveDebuggingValues();
|
||||||
void SaveServiceValues();
|
void SaveNetworkValues();
|
||||||
void SaveDisabledAddOnValues();
|
void SaveDisabledAddOnValues();
|
||||||
void SaveMiscellaneousValues();
|
void SaveMiscellaneousValues();
|
||||||
void SavePathValues();
|
void SavePathValues();
|
||||||
|
|
|
@ -147,12 +147,12 @@
|
||||||
<string>Web</string>
|
<string>Web</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="ConfigureService" name="serviceTab">
|
<widget class="ConfigureNetwork" name="networkTab">
|
||||||
<property name="accessibleName">
|
<property name="accessibleName">
|
||||||
<string>Services</string>
|
<string>Network</string>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>Services</string>
|
<string>Network</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -242,9 +242,9 @@
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>ConfigureService</class>
|
<class>ConfigureNetwork</class>
|
||||||
<extends>QWidget</extends>
|
<extends>QWidget</extends>
|
||||||
<header>configuration/configure_service.h</header>
|
<header>configuration/configure_network.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|
|
@ -67,7 +67,7 @@ void ConfigureDialog::ApplyConfiguration() {
|
||||||
ui->audioTab->ApplyConfiguration();
|
ui->audioTab->ApplyConfiguration();
|
||||||
ui->debugTab->ApplyConfiguration();
|
ui->debugTab->ApplyConfiguration();
|
||||||
ui->webTab->ApplyConfiguration();
|
ui->webTab->ApplyConfiguration();
|
||||||
ui->serviceTab->ApplyConfiguration();
|
ui->networkTab->ApplyConfiguration();
|
||||||
Core::System::GetInstance().ApplySettings();
|
Core::System::GetInstance().ApplySettings();
|
||||||
Settings::LogSettings();
|
Settings::LogSettings();
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
|
||||||
void ConfigureDialog::PopulateSelectionList() {
|
void ConfigureDialog::PopulateSelectionList() {
|
||||||
const std::array<std::pair<QString, QList<QWidget*>>, 6> items{
|
const std::array<std::pair<QString, QList<QWidget*>>, 6> items{
|
||||||
{{tr("General"), {ui->generalTab, ui->hotkeysTab, ui->uiTab, ui->webTab, ui->debugTab}},
|
{{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("CPU"), {ui->cpuTab}},
|
||||||
{tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}},
|
{tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}},
|
||||||
{tr("Audio"), {ui->audioTab}},
|
{tr("Audio"), {ui->audioTab}},
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
#include <QGraphicsItem>
|
#include <QGraphicsItem>
|
||||||
#include <QtConcurrent/QtConcurrent>
|
#include <QtConcurrent/QtConcurrent>
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/hle/service/bcat/backend/boxcat.h"
|
#include "core/hle/service/bcat/backend/boxcat.h"
|
||||||
#include "ui_configure_service.h"
|
#include "core/network/network_interface.h"
|
||||||
#include "yuzu/configuration/configure_service.h"
|
#include "ui_configure_network.h"
|
||||||
|
#include "yuzu/configuration/configure_network.h"
|
||||||
|
|
||||||
#ifdef YUZU_ENABLE_BOXCAT
|
#ifdef YUZU_ENABLE_BOXCAT
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -35,8 +37,8 @@ QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ConfigureService::ConfigureService(QWidget* parent)
|
ConfigureNetwork::ConfigureNetwork(QWidget* parent)
|
||||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) {
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureNetwork>()) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
ui->bcat_source->addItem(QStringLiteral("None"));
|
ui->bcat_source->addItem(QStringLiteral("None"));
|
||||||
|
@ -47,29 +49,42 @@ ConfigureService::ConfigureService(QWidget* parent)
|
||||||
ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat"));
|
ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat"));
|
||||||
#endif
|
#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,
|
connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||||
&ConfigureService::OnBCATImplChanged);
|
&ConfigureNetwork::OnBCATImplChanged);
|
||||||
|
|
||||||
this->SetConfiguration();
|
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.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);
|
ui->retranslateUi(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureService::SetConfiguration() {
|
void ConfigureNetwork::SetConfiguration() {
|
||||||
|
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
|
||||||
|
|
||||||
const int index =
|
const int index =
|
||||||
ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend.GetValue()));
|
ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend.GetValue()));
|
||||||
ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index);
|
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
|
#ifdef YUZU_ENABLE_BOXCAT
|
||||||
std::optional<std::string> global;
|
std::optional<std::string> global;
|
||||||
std::map<std::string, Service::BCAT::EventStatus> map;
|
std::map<std::string, Service::BCAT::EventStatus> map;
|
||||||
|
@ -114,7 +129,7 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureService::OnBCATImplChanged() {
|
void ConfigureNetwork::OnBCATImplChanged() {
|
||||||
#ifdef YUZU_ENABLE_BOXCAT
|
#ifdef YUZU_ENABLE_BOXCAT
|
||||||
const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
|
const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
|
||||||
ui->bcat_empty_header->setHidden(!boxcat);
|
ui->bcat_empty_header->setHidden(!boxcat);
|
||||||
|
@ -133,7 +148,7 @@ void ConfigureService::OnBCATImplChanged() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureService::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) {
|
void ConfigureNetwork::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) {
|
||||||
#ifdef YUZU_ENABLE_BOXCAT
|
#ifdef YUZU_ENABLE_BOXCAT
|
||||||
const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
|
const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
|
||||||
if (boxcat) {
|
if (boxcat) {
|
|
@ -9,15 +9,15 @@
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ConfigureService;
|
class ConfigureNetwork;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigureService : public QWidget {
|
class ConfigureNetwork : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ConfigureService(QWidget* parent = nullptr);
|
explicit ConfigureNetwork(QWidget* parent = nullptr);
|
||||||
~ConfigureService() override;
|
~ConfigureNetwork() override;
|
||||||
|
|
||||||
void ApplyConfiguration();
|
void ApplyConfiguration();
|
||||||
void RetranslateUi();
|
void RetranslateUi();
|
||||||
|
@ -29,6 +29,6 @@ private:
|
||||||
void OnBCATImplChanged();
|
void OnBCATImplChanged();
|
||||||
void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string);
|
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};
|
QFutureWatcher<std::pair<QString, QString>> watcher{this};
|
||||||
};
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>ConfigureService</class>
|
<class>ConfigureNetwork</class>
|
||||||
<widget class="QWidget" name="ConfigureService">
|
<widget class="QWidget" name="ConfigureNetwork">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
|
@ -16,22 +16,38 @@
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<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>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="groupBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>BCAT</string>
|
<string>BCAT</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="1" column="1" colspan="2">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="bcat_empty_header">
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>260</width>
|
|
||||||
<height>16777215</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<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>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -51,11 +67,8 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1" colspan="2">
|
<item row="1" column="1" colspan="2">
|
||||||
<widget class="QLabel" name="bcat_empty_label">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="enabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>260</width>
|
<width>260</width>
|
||||||
|
@ -63,10 +76,7 @@
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string/>
|
<string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string>
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
@ -86,8 +96,17 @@
|
||||||
<item row="0" column="1" colspan="2">
|
<item row="0" column="1" colspan="2">
|
||||||
<widget class="QComboBox" name="bcat_source"/>
|
<widget class="QComboBox" name="bcat_source"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="3" column="1" colspan="2">
|
||||||
<widget class="QLabel" name="bcat_empty_header">
|
<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">
|
<property name="text">
|
||||||
<string/>
|
<string/>
|
||||||
</property>
|
</property>
|
Loading…
Reference in a new issue