summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Fernando S2021-08-16 17:10:00 +0200
committerGravatar GitHub2021-08-16 17:10:00 +0200
commit521e6ac17483e975e2fc731fed879e9f3edf7547 (patch)
treeca5a9b7559f475de1d461885f3df9ee2282bccdf /src
parentMerge pull request #6861 from yzct12345/const-mempy-is-all-the-speed (diff)
parentnetwork_interface: correct formatting (diff)
downloadyuzu-521e6ac17483e975e2fc731fed879e9f3edf7547.tar.gz
yuzu-521e6ac17483e975e2fc731fed879e9f3edf7547.tar.xz
yuzu-521e6ac17483e975e2fc731fed879e9f3edf7547.zip
Merge pull request #6863 from spholz/fix-lan-play
Fix LAN Play
Diffstat (limited to 'src')
-rw-r--r--src/common/settings.h3
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/hle/service/nifm/nifm.cpp45
-rw-r--r--src/core/network/network.cpp64
-rw-r--r--src/core/network/network.h24
-rw-r--r--src/core/network/network_interface.cpp203
-rw-r--r--src/core/network/network_interface.h29
-rw-r--r--src/yuzu/CMakeLists.txt6
-rw-r--r--src/yuzu/configuration/config.cpp6
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure.ui10
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp4
-rw-r--r--src/yuzu/configuration/configure_network.cpp (renamed from src/yuzu/configuration/configure_service.cpp)39
-rw-r--r--src/yuzu/configuration/configure_network.h (renamed from src/yuzu/configuration/configure_service.h)10
-rw-r--r--src/yuzu/configuration/configure_network.ui (renamed from src/yuzu/configuration/configure_service.ui)63
15 files changed, 408 insertions, 102 deletions
diff --git a/src/common/settings.h b/src/common/settings.h
index 1ba9b606c..c65746749 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -558,9 +558,10 @@ struct Values {
558 BasicSetting<std::string> log_filter{"*:Info", "log_filter"}; 558 BasicSetting<std::string> log_filter{"*:Info", "log_filter"};
559 BasicSetting<bool> use_dev_keys{false, "use_dev_keys"}; 559 BasicSetting<bool> use_dev_keys{false, "use_dev_keys"};
560 560
561 // Services 561 // Network
562 BasicSetting<std::string> bcat_backend{"none", "bcat_backend"}; 562 BasicSetting<std::string> bcat_backend{"none", "bcat_backend"};
563 BasicSetting<bool> bcat_boxcat_local{false, "bcat_boxcat_local"}; 563 BasicSetting<bool> bcat_boxcat_local{false, "bcat_boxcat_local"};
564 BasicSetting<std::string> network_interface{std::string(), "network_interface"};
564 565
565 // WebService 566 // WebService
566 BasicSetting<bool> enable_telemetry{true, "enable_telemetry"}; 567 BasicSetting<bool> enable_telemetry{true, "enable_telemetry"};
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 5c99c00f5..f5cf5c16a 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -636,6 +636,8 @@ add_library(core STATIC
636 memory.h 636 memory.h
637 network/network.cpp 637 network/network.cpp
638 network/network.h 638 network/network.h
639 network/network_interface.cpp
640 network/network_interface.h
639 network/sockets.h 641 network/sockets.h
640 perf_stats.cpp 642 perf_stats.cpp
641 perf_stats.h 643 perf_stats.h
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index e742db48f..0a53c0c81 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -11,6 +11,7 @@
11#include "core/hle/service/nifm/nifm.h" 11#include "core/hle/service/nifm/nifm.h"
12#include "core/hle/service/service.h" 12#include "core/hle/service/service.h"
13#include "core/network/network.h" 13#include "core/network/network.h"
14#include "core/network/network_interface.h"
14 15
15namespace Service::NIFM { 16namespace Service::NIFM {
16 17
@@ -179,10 +180,10 @@ private:
179 IPC::ResponseBuilder rb{ctx, 3}; 180 IPC::ResponseBuilder rb{ctx, 3};
180 rb.Push(ResultSuccess); 181 rb.Push(ResultSuccess);
181 182
182 if (Settings::values.bcat_backend.GetValue() == "none") { 183 if (Network::GetHostIPv4Address().has_value()) {
183 rb.PushEnum(RequestState::NotSubmitted);
184 } else {
185 rb.PushEnum(RequestState::Connected); 184 rb.PushEnum(RequestState::Connected);
185 } else {
186 rb.PushEnum(RequestState::NotSubmitted);
186 } 187 }
187 } 188 }
188 189
@@ -322,12 +323,15 @@ private:
322 void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) { 323 void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
323 LOG_WARNING(Service_NIFM, "(STUBBED) called"); 324 LOG_WARNING(Service_NIFM, "(STUBBED) called");
324 325
325 const auto [ipv4, error] = Network::GetHostIPv4Address(); 326 auto ipv4 = Network::GetHostIPv4Address();
326 UNIMPLEMENTED_IF(error != Network::Errno::SUCCESS); 327 if (!ipv4) {
328 LOG_ERROR(Service_NIFM, "Couldn't get host IPv4 address, defaulting to 0.0.0.0");
329 ipv4.emplace(Network::IPv4Address{0, 0, 0, 0});
330 }
327 331
328 IPC::ResponseBuilder rb{ctx, 3}; 332 IPC::ResponseBuilder rb{ctx, 3};
329 rb.Push(ResultSuccess); 333 rb.Push(ResultSuccess);
330 rb.PushRaw(ipv4); 334 rb.PushRaw(*ipv4);
331 } 335 }
332 void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { 336 void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
333 LOG_DEBUG(Service_NIFM, "called"); 337 LOG_DEBUG(Service_NIFM, "called");
@@ -354,10 +358,10 @@ private:
354 static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting), 358 static_assert(sizeof(IpConfigInfo) == sizeof(IpAddressSetting) + sizeof(DnsSetting),
355 "IpConfigInfo has incorrect size."); 359 "IpConfigInfo has incorrect size.");
356 360
357 const IpConfigInfo ip_config_info{ 361 IpConfigInfo ip_config_info{
358 .ip_address_setting{ 362 .ip_address_setting{
359 .is_automatic{true}, 363 .is_automatic{true},
360 .current_address{192, 168, 1, 100}, 364 .current_address{0, 0, 0, 0},
361 .subnet_mask{255, 255, 255, 0}, 365 .subnet_mask{255, 255, 255, 0},
362 .gateway{192, 168, 1, 1}, 366 .gateway{192, 168, 1, 1},
363 }, 367 },
@@ -368,6 +372,19 @@ private:
368 }, 372 },
369 }; 373 };
370 374
375 const auto iface = Network::GetSelectedNetworkInterface();
376 if (iface) {
377 ip_config_info.ip_address_setting =
378 IpAddressSetting{.is_automatic{true},
379 .current_address{Network::TranslateIPv4(iface->ip_address)},
380 .subnet_mask{Network::TranslateIPv4(iface->subnet_mask)},
381 .gateway{Network::TranslateIPv4(iface->gateway)}};
382
383 } else {
384 LOG_ERROR(Service_NIFM,
385 "Couldn't get host network configuration info, using default values");
386 }
387
371 IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)}; 388 IPC::ResponseBuilder rb{ctx, 2 + (sizeof(IpConfigInfo) + 3) / sizeof(u32)};
372 rb.Push(ResultSuccess); 389 rb.Push(ResultSuccess);
373 rb.PushRaw<IpConfigInfo>(ip_config_info); 390 rb.PushRaw<IpConfigInfo>(ip_config_info);
@@ -384,10 +401,10 @@ private:
384 401
385 IPC::ResponseBuilder rb{ctx, 3}; 402 IPC::ResponseBuilder rb{ctx, 3};
386 rb.Push(ResultSuccess); 403 rb.Push(ResultSuccess);
387 if (Settings::values.bcat_backend.GetValue() == "none") { 404 if (Network::GetHostIPv4Address().has_value()) {
388 rb.Push<u8>(0);
389 } else {
390 rb.Push<u8>(1); 405 rb.Push<u8>(1);
406 } else {
407 rb.Push<u8>(0);
391 } 408 }
392 } 409 }
393 void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) { 410 void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
@@ -395,10 +412,10 @@ private:
395 412
396 IPC::ResponseBuilder rb{ctx, 3}; 413 IPC::ResponseBuilder rb{ctx, 3};
397 rb.Push(ResultSuccess); 414 rb.Push(ResultSuccess);
398 if (Settings::values.bcat_backend.GetValue() == "none") { 415 if (Network::GetHostIPv4Address().has_value()) {
399 rb.Push<u8>(0);
400 } else {
401 rb.Push<u8>(1); 416 rb.Push<u8>(1);
417 } else {
418 rb.Push<u8>(0);
402 } 419 }
403 } 420 }
404}; 421};
diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp
index 375bc79ec..4732d4485 100644
--- a/src/core/network/network.cpp
+++ b/src/core/network/network.cpp
@@ -10,9 +10,10 @@
10#include "common/common_funcs.h" 10#include "common/common_funcs.h"
11 11
12#ifdef _WIN32 12#ifdef _WIN32
13#define _WINSOCK_DEPRECATED_NO_WARNINGS // gethostname
14#include <winsock2.h> 13#include <winsock2.h>
14#include <ws2tcpip.h>
15#elif YUZU_UNIX 15#elif YUZU_UNIX
16#include <arpa/inet.h>
16#include <errno.h> 17#include <errno.h>
17#include <fcntl.h> 18#include <fcntl.h>
18#include <netdb.h> 19#include <netdb.h>
@@ -27,7 +28,9 @@
27#include "common/assert.h" 28#include "common/assert.h"
28#include "common/common_types.h" 29#include "common/common_types.h"
29#include "common/logging/log.h" 30#include "common/logging/log.h"
31#include "common/settings.h"
30#include "core/network/network.h" 32#include "core/network/network.h"
33#include "core/network/network_interface.h"
31#include "core/network/sockets.h" 34#include "core/network/sockets.h"
32 35
33namespace Network { 36namespace Network {
@@ -47,11 +50,6 @@ void Finalize() {
47 WSACleanup(); 50 WSACleanup();
48} 51}
49 52
50constexpr IPv4Address TranslateIPv4(in_addr addr) {
51 auto& bytes = addr.S_un.S_un_b;
52 return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4};
53}
54
55sockaddr TranslateFromSockAddrIn(SockAddrIn input) { 53sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
56 sockaddr_in result; 54 sockaddr_in result;
57 55
@@ -138,12 +136,6 @@ void Initialize() {}
138 136
139void Finalize() {} 137void Finalize() {}
140 138
141constexpr IPv4Address TranslateIPv4(in_addr addr) {
142 const u32 bytes = addr.s_addr;
143 return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8),
144 static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)};
145}
146
147sockaddr TranslateFromSockAddrIn(SockAddrIn input) { 139sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
148 sockaddr_in result; 140 sockaddr_in result;
149 141
@@ -182,7 +174,7 @@ linger MakeLinger(bool enable, u32 linger_value) {
182} 174}
183 175
184bool EnableNonBlock(int fd, bool enable) { 176bool EnableNonBlock(int fd, bool enable) {
185 int flags = fcntl(fd, F_GETFD); 177 int flags = fcntl(fd, F_GETFL);
186 if (flags == -1) { 178 if (flags == -1) {
187 return false; 179 return false;
188 } 180 }
@@ -191,7 +183,7 @@ bool EnableNonBlock(int fd, bool enable) {
191 } else { 183 } else {
192 flags &= ~O_NONBLOCK; 184 flags &= ~O_NONBLOCK;
193 } 185 }
194 return fcntl(fd, F_SETFD, flags) == 0; 186 return fcntl(fd, F_SETFL, flags) == 0;
195} 187}
196 188
197Errno TranslateNativeError(int e) { 189Errno TranslateNativeError(int e) {
@@ -227,8 +219,12 @@ Errno GetAndLogLastError() {
227#else 219#else
228 int e = errno; 220 int e = errno;
229#endif 221#endif
222 const Errno err = TranslateNativeError(e);
223 if (err == Errno::AGAIN) {
224 return err;
225 }
230 LOG_ERROR(Network, "Socket operation error: {}", NativeErrorToString(e)); 226 LOG_ERROR(Network, "Socket operation error: {}", NativeErrorToString(e));
231 return TranslateNativeError(e); 227 return err;
232} 228}
233 229
234int TranslateDomain(Domain domain) { 230int TranslateDomain(Domain domain) {
@@ -353,27 +349,29 @@ NetworkInstance::~NetworkInstance() {
353 Finalize(); 349 Finalize();
354} 350}
355 351
356std::pair<IPv4Address, Errno> GetHostIPv4Address() { 352std::optional<IPv4Address> GetHostIPv4Address() {
357 std::array<char, 256> name{}; 353 const std::string& selected_network_interface = Settings::values.network_interface.GetValue();
358 if (gethostname(name.data(), static_cast<int>(name.size()) - 1) == SOCKET_ERROR) { 354 const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
359 return {IPv4Address{}, GetAndLogLastError()}; 355 if (network_interfaces.size() == 0) {
356 LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
357 return {};
360 } 358 }
361 359
362 hostent* const ent = gethostbyname(name.data()); 360 const auto res =
363 if (!ent) { 361 std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) {
364 return {IPv4Address{}, GetAndLogLastError()}; 362 return iface.name == selected_network_interface;
365 } 363 });
366 if (ent->h_addr_list == nullptr) { 364
367 UNIMPLEMENTED_MSG("No addr provided in hostent->h_addr_list"); 365 if (res != network_interfaces.end()) {
368 return {IPv4Address{}, Errno::SUCCESS}; 366 char ip_addr[16] = {};
369 } 367 ASSERT(inet_ntop(AF_INET, &res->ip_address, ip_addr, sizeof(ip_addr)) != nullptr);
370 if (ent->h_length != sizeof(in_addr)) { 368 LOG_INFO(Network, "IP address: {}", ip_addr);
371 UNIMPLEMENTED_MSG("Unexpected size={} in hostent->h_length", ent->h_length);
372 }
373 369
374 in_addr addr; 370 return TranslateIPv4(res->ip_address);
375 std::memcpy(&addr, ent->h_addr_list[0], sizeof(addr)); 371 } else {
376 return {TranslateIPv4(addr), Errno::SUCCESS}; 372 LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
373 return {};
374 }
377} 375}
378 376
379std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { 377std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
diff --git a/src/core/network/network.h b/src/core/network/network.h
index bd30f1899..fdd3e4655 100644
--- a/src/core/network/network.h
+++ b/src/core/network/network.h
@@ -5,11 +5,18 @@
5#pragma once 5#pragma once
6 6
7#include <array> 7#include <array>
8#include <optional>
8#include <utility> 9#include <utility>
9 10
10#include "common/common_funcs.h" 11#include "common/common_funcs.h"
11#include "common/common_types.h" 12#include "common/common_types.h"
12 13
14#ifdef _WIN32
15#include <winsock2.h>
16#elif YUZU_UNIX
17#include <netinet/in.h>
18#endif
19
13namespace Network { 20namespace Network {
14 21
15class Socket; 22class Socket;
@@ -92,8 +99,21 @@ public:
92 ~NetworkInstance(); 99 ~NetworkInstance();
93}; 100};
94 101
102#ifdef _WIN32
103constexpr IPv4Address TranslateIPv4(in_addr addr) {
104 auto& bytes = addr.S_un.S_un_b;
105 return IPv4Address{bytes.s_b1, bytes.s_b2, bytes.s_b3, bytes.s_b4};
106}
107#elif YUZU_UNIX
108constexpr IPv4Address TranslateIPv4(in_addr addr) {
109 const u32 bytes = addr.s_addr;
110 return IPv4Address{static_cast<u8>(bytes), static_cast<u8>(bytes >> 8),
111 static_cast<u8>(bytes >> 16), static_cast<u8>(bytes >> 24)};
112}
113#endif
114
95/// @brief Returns host's IPv4 address 115/// @brief Returns host's IPv4 address
96/// @return Pair of an array of human ordered IPv4 address (e.g. 192.168.0.1) and an error code 116/// @return human ordered IPv4 address (e.g. 192.168.0.1) as an array
97std::pair<IPv4Address, Errno> GetHostIPv4Address(); 117std::optional<IPv4Address> GetHostIPv4Address();
98 118
99} // namespace Network 119} // namespace Network
diff --git a/src/core/network/network_interface.cpp b/src/core/network/network_interface.cpp
new file mode 100644
index 000000000..cecc9aa11
--- /dev/null
+++ b/src/core/network/network_interface.cpp
@@ -0,0 +1,203 @@
1// Copyright 2021 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <fstream>
7#include <sstream>
8#include <vector>
9
10#include "common/bit_cast.h"
11#include "common/common_types.h"
12#include "common/logging/log.h"
13#include "common/settings.h"
14#include "common/string_util.h"
15#include "core/network/network_interface.h"
16
17#ifdef _WIN32
18#include <iphlpapi.h>
19#else
20#include <cerrno>
21#include <ifaddrs.h>
22#include <net/if.h>
23#endif
24
25namespace Network {
26
27#ifdef _WIN32
28
29std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
30 std::vector<IP_ADAPTER_ADDRESSES> adapter_addresses;
31 DWORD ret = ERROR_BUFFER_OVERFLOW;
32 DWORD buf_size = 0;
33
34 // retry up to 5 times
35 for (int i = 0; i < 5 && ret == ERROR_BUFFER_OVERFLOW; i++) {
36 ret = GetAdaptersAddresses(
37 AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS,
38 nullptr, adapter_addresses.data(), &buf_size);
39
40 if (ret == ERROR_BUFFER_OVERFLOW) {
41 adapter_addresses.resize((buf_size / sizeof(IP_ADAPTER_ADDRESSES)) + 1);
42 } else {
43 break;
44 }
45 }
46
47 if (ret == NO_ERROR) {
48 std::vector<NetworkInterface> result;
49
50 for (auto current_address = adapter_addresses.data(); current_address != nullptr;
51 current_address = current_address->Next) {
52 if (current_address->FirstUnicastAddress == nullptr ||
53 current_address->FirstUnicastAddress->Address.lpSockaddr == nullptr) {
54 continue;
55 }
56
57 if (current_address->OperStatus != IfOperStatusUp) {
58 continue;
59 }
60
61 const auto ip_addr = Common::BitCast<struct sockaddr_in>(
62 *current_address->FirstUnicastAddress->Address.lpSockaddr)
63 .sin_addr;
64
65 ULONG mask = 0;
66 if (ConvertLengthToIpv4Mask(current_address->FirstUnicastAddress->OnLinkPrefixLength,
67 &mask) != NO_ERROR) {
68 LOG_ERROR(Network, "Failed to convert IPv4 prefix length to subnet mask");
69 continue;
70 }
71
72 struct in_addr gateway = {.S_un{.S_addr{0}}};
73 if (current_address->FirstGatewayAddress != nullptr &&
74 current_address->FirstGatewayAddress->Address.lpSockaddr != nullptr) {
75 gateway = Common::BitCast<struct sockaddr_in>(
76 *current_address->FirstGatewayAddress->Address.lpSockaddr)
77 .sin_addr;
78 }
79
80 result.push_back(NetworkInterface{
81 .name{Common::UTF16ToUTF8(std::wstring{current_address->FriendlyName})},
82 .ip_address{ip_addr},
83 .subnet_mask = in_addr{.S_un{.S_addr{mask}}},
84 .gateway = gateway});
85 }
86
87 return result;
88 } else {
89 LOG_ERROR(Network, "Failed to get network interfaces with GetAdaptersAddresses");
90 return {};
91 }
92}
93
94#else
95
96std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
97 std::vector<NetworkInterface> result;
98
99 struct ifaddrs* ifaddr = nullptr;
100
101 if (getifaddrs(&ifaddr) != 0) {
102 LOG_ERROR(Network, "Failed to get network interfaces with getifaddrs: {}",
103 std::strerror(errno));
104 return result;
105 }
106
107 for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
108 if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr) {
109 continue;
110 }
111
112 if (ifa->ifa_addr->sa_family != AF_INET) {
113 continue;
114 }
115
116 if ((ifa->ifa_flags & IFF_UP) == 0 || (ifa->ifa_flags & IFF_LOOPBACK) != 0) {
117 continue;
118 }
119
120 std::uint32_t gateway{0};
121 std::ifstream file{"/proc/net/route"};
122 if (file.is_open()) {
123
124 // ignore header
125 file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
126
127 bool gateway_found = false;
128
129 for (std::string line; std::getline(file, line);) {
130 std::istringstream iss{line};
131
132 std::string iface_name{};
133 iss >> iface_name;
134 if (iface_name != ifa->ifa_name) {
135 continue;
136 }
137
138 iss >> std::hex;
139
140 std::uint32_t dest{0};
141 iss >> dest;
142 if (dest != 0) {
143 // not the default route
144 continue;
145 }
146
147 iss >> gateway;
148
149 std::uint16_t flags{0};
150 iss >> flags;
151
152 // flag RTF_GATEWAY (defined in <linux/route.h>)
153 if ((flags & 0x2) == 0) {
154 continue;
155 }
156
157 gateway_found = true;
158 break;
159 }
160
161 if (!gateway_found) {
162 gateway = 0;
163 }
164 } else {
165 LOG_ERROR(Network, "Failed to open \"/proc/net/route\"");
166 }
167
168 result.push_back(NetworkInterface{
169 .name{ifa->ifa_name},
170 .ip_address{Common::BitCast<struct sockaddr_in>(*ifa->ifa_addr).sin_addr},
171 .subnet_mask{Common::BitCast<struct sockaddr_in>(*ifa->ifa_netmask).sin_addr},
172 .gateway{in_addr{.s_addr = gateway}}});
173 }
174
175 freeifaddrs(ifaddr);
176
177 return result;
178}
179
180#endif
181
182std::optional<NetworkInterface> GetSelectedNetworkInterface() {
183 const std::string& selected_network_interface = Settings::values.network_interface.GetValue();
184 const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
185 if (network_interfaces.size() == 0) {
186 LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
187 return {};
188 }
189
190 const auto res =
191 std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) {
192 return iface.name == selected_network_interface;
193 });
194
195 if (res != network_interfaces.end()) {
196 return *res;
197 } else {
198 LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
199 return {};
200 }
201}
202
203} // namespace Network
diff --git a/src/core/network/network_interface.h b/src/core/network/network_interface.h
new file mode 100644
index 000000000..980edb2f5
--- /dev/null
+++ b/src/core/network/network_interface.h
@@ -0,0 +1,29 @@
1// Copyright 2021 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <optional>
8#include <string>
9#include <vector>
10
11#ifdef _WIN32
12#include <winsock2.h>
13#else
14#include <netinet/in.h>
15#endif
16
17namespace Network {
18
19struct NetworkInterface {
20 std::string name;
21 struct in_addr ip_address;
22 struct in_addr subnet_mask;
23 struct in_addr gateway;
24};
25
26std::vector<NetworkInterface> GetAvailableNetworkInterfaces();
27std::optional<NetworkInterface> GetSelectedNetworkInterface();
28
29} // namespace Network
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index cb4bdcc7e..cf68a95b5 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -102,9 +102,9 @@ add_executable(yuzu
102 configuration/configure_profile_manager.cpp 102 configuration/configure_profile_manager.cpp
103 configuration/configure_profile_manager.h 103 configuration/configure_profile_manager.h
104 configuration/configure_profile_manager.ui 104 configuration/configure_profile_manager.ui
105 configuration/configure_service.cpp 105 configuration/configure_network.cpp
106 configuration/configure_service.h 106 configuration/configure_network.h
107 configuration/configure_service.ui 107 configuration/configure_network.ui
108 configuration/configure_system.cpp 108 configuration/configure_system.cpp
109 configuration/configure_system.h 109 configuration/configure_system.h
110 configuration/configure_system.ui 110 configuration/configure_system.ui
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 380379eb4..377795326 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -692,6 +692,7 @@ void Config::ReadServiceValues() {
692 qt_config->beginGroup(QStringLiteral("Services")); 692 qt_config->beginGroup(QStringLiteral("Services"));
693 ReadBasicSetting(Settings::values.bcat_backend); 693 ReadBasicSetting(Settings::values.bcat_backend);
694 ReadBasicSetting(Settings::values.bcat_boxcat_local); 694 ReadBasicSetting(Settings::values.bcat_boxcat_local);
695 ReadBasicSetting(Settings::values.network_interface);
695 qt_config->endGroup(); 696 qt_config->endGroup();
696} 697}
697 698
@@ -1144,7 +1145,7 @@ void Config::SaveValues() {
1144 SaveDataStorageValues(); 1145 SaveDataStorageValues();
1145 SaveDebuggingValues(); 1146 SaveDebuggingValues();
1146 SaveDisabledAddOnValues(); 1147 SaveDisabledAddOnValues();
1147 SaveServiceValues(); 1148 SaveNetworkValues();
1148 SaveUIValues(); 1149 SaveUIValues();
1149 SaveWebServiceValues(); 1150 SaveWebServiceValues();
1150 SaveMiscellaneousValues(); 1151 SaveMiscellaneousValues();
@@ -1238,11 +1239,12 @@ void Config::SaveDebuggingValues() {
1238 qt_config->endGroup(); 1239 qt_config->endGroup();
1239} 1240}
1240 1241
1241void Config::SaveServiceValues() { 1242void Config::SaveNetworkValues() {
1242 qt_config->beginGroup(QStringLiteral("Services")); 1243 qt_config->beginGroup(QStringLiteral("Services"));
1243 1244
1244 WriteBasicSetting(Settings::values.bcat_backend); 1245 WriteBasicSetting(Settings::values.bcat_backend);
1245 WriteBasicSetting(Settings::values.bcat_boxcat_local); 1246 WriteBasicSetting(Settings::values.bcat_boxcat_local);
1247 WriteBasicSetting(Settings::values.network_interface);
1246 1248
1247 qt_config->endGroup(); 1249 qt_config->endGroup();
1248} 1250}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index c1d7feb9f..9555f4498 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -88,7 +88,7 @@ private:
88 void SaveCoreValues(); 88 void SaveCoreValues();
89 void SaveDataStorageValues(); 89 void SaveDataStorageValues();
90 void SaveDebuggingValues(); 90 void SaveDebuggingValues();
91 void SaveServiceValues(); 91 void SaveNetworkValues();
92 void SaveDisabledAddOnValues(); 92 void SaveDisabledAddOnValues();
93 void SaveMiscellaneousValues(); 93 void SaveMiscellaneousValues();
94 void SavePathValues(); 94 void SavePathValues();
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index fca9aed5f..6258dcf20 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -147,12 +147,12 @@
147 <string>Web</string> 147 <string>Web</string>
148 </attribute> 148 </attribute>
149 </widget> 149 </widget>
150 <widget class="ConfigureService" name="serviceTab"> 150 <widget class="ConfigureNetwork" name="networkTab">
151 <property name="accessibleName"> 151 <property name="accessibleName">
152 <string>Services</string> 152 <string>Network</string>
153 </property> 153 </property>
154 <attribute name="title"> 154 <attribute name="title">
155 <string>Services</string> 155 <string>Network</string>
156 </attribute> 156 </attribute>
157 </widget> 157 </widget>
158 </widget> 158 </widget>
@@ -242,9 +242,9 @@
242 <container>1</container> 242 <container>1</container>
243 </customwidget> 243 </customwidget>
244 <customwidget> 244 <customwidget>
245 <class>ConfigureService</class> 245 <class>ConfigureNetwork</class>
246 <extends>QWidget</extends> 246 <extends>QWidget</extends>
247 <header>configuration/configure_service.h</header> 247 <header>configuration/configure_network.h</header>
248 <container>1</container> 248 <container>1</container>
249 </customwidget> 249 </customwidget>
250 <customwidget> 250 <customwidget>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index bc009b6b3..fe4186157 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -67,7 +67,7 @@ void ConfigureDialog::ApplyConfiguration() {
67 ui->audioTab->ApplyConfiguration(); 67 ui->audioTab->ApplyConfiguration();
68 ui->debugTab->ApplyConfiguration(); 68 ui->debugTab->ApplyConfiguration();
69 ui->webTab->ApplyConfiguration(); 69 ui->webTab->ApplyConfiguration();
70 ui->serviceTab->ApplyConfiguration(); 70 ui->networkTab->ApplyConfiguration();
71 Core::System::GetInstance().ApplySettings(); 71 Core::System::GetInstance().ApplySettings();
72 Settings::LogSettings(); 72 Settings::LogSettings();
73} 73}
@@ -103,7 +103,7 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
103void ConfigureDialog::PopulateSelectionList() { 103void ConfigureDialog::PopulateSelectionList() {
104 const std::array<std::pair<QString, QList<QWidget*>>, 6> items{ 104 const std::array<std::pair<QString, QList<QWidget*>>, 6> items{
105 {{tr("General"), {ui->generalTab, ui->hotkeysTab, ui->uiTab, ui->webTab, ui->debugTab}}, 105 {{tr("General"), {ui->generalTab, ui->hotkeysTab, ui->uiTab, ui->webTab, ui->debugTab}},
106 {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}}, 106 {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->networkTab, ui->filesystemTab}},
107 {tr("CPU"), {ui->cpuTab}}, 107 {tr("CPU"), {ui->cpuTab}},
108 {tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}}, 108 {tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}},
109 {tr("Audio"), {ui->audioTab}}, 109 {tr("Audio"), {ui->audioTab}},
diff --git a/src/yuzu/configuration/configure_service.cpp b/src/yuzu/configuration/configure_network.cpp
index 4aa424803..ae22f1018 100644
--- a/src/yuzu/configuration/configure_service.cpp
+++ b/src/yuzu/configuration/configure_network.cpp
@@ -5,9 +5,11 @@
5#include <QGraphicsItem> 5#include <QGraphicsItem>
6#include <QtConcurrent/QtConcurrent> 6#include <QtConcurrent/QtConcurrent>
7#include "common/settings.h" 7#include "common/settings.h"
8#include "core/core.h"
8#include "core/hle/service/bcat/backend/boxcat.h" 9#include "core/hle/service/bcat/backend/boxcat.h"
9#include "ui_configure_service.h" 10#include "core/network/network_interface.h"
10#include "yuzu/configuration/configure_service.h" 11#include "ui_configure_network.h"
12#include "yuzu/configuration/configure_network.h"
11 13
12#ifdef YUZU_ENABLE_BOXCAT 14#ifdef YUZU_ENABLE_BOXCAT
13namespace { 15namespace {
@@ -35,8 +37,8 @@ QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
35} // Anonymous namespace 37} // Anonymous namespace
36#endif 38#endif
37 39
38ConfigureService::ConfigureService(QWidget* parent) 40ConfigureNetwork::ConfigureNetwork(QWidget* parent)
39 : QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) { 41 : QWidget(parent), ui(std::make_unique<Ui::ConfigureNetwork>()) {
40 ui->setupUi(this); 42 ui->setupUi(this);
41 43
42 ui->bcat_source->addItem(QStringLiteral("None")); 44 ui->bcat_source->addItem(QStringLiteral("None"));
@@ -47,29 +49,42 @@ ConfigureService::ConfigureService(QWidget* parent)
47 ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat")); 49 ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat"));
48#endif 50#endif
49 51
52 ui->network_interface->addItem(tr("None"));
53 for (const auto& iface : Network::GetAvailableNetworkInterfaces()) {
54 ui->network_interface->addItem(QString::fromStdString(iface.name));
55 }
56
50 connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 57 connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
51 &ConfigureService::OnBCATImplChanged); 58 &ConfigureNetwork::OnBCATImplChanged);
52 59
53 this->SetConfiguration(); 60 this->SetConfiguration();
54} 61}
55 62
56ConfigureService::~ConfigureService() = default; 63ConfigureNetwork::~ConfigureNetwork() = default;
57 64
58void ConfigureService::ApplyConfiguration() { 65void ConfigureNetwork::ApplyConfiguration() {
59 Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString(); 66 Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString();
67 Settings::values.network_interface = ui->network_interface->currentText().toStdString();
60} 68}
61 69
62void ConfigureService::RetranslateUi() { 70void ConfigureNetwork::RetranslateUi() {
63 ui->retranslateUi(this); 71 ui->retranslateUi(this);
64} 72}
65 73
66void ConfigureService::SetConfiguration() { 74void ConfigureNetwork::SetConfiguration() {
75 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
76
67 const int index = 77 const int index =
68 ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend.GetValue())); 78 ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend.GetValue()));
69 ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index); 79 ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index);
80
81 const std::string& network_interface = Settings::values.network_interface.GetValue();
82
83 ui->network_interface->setCurrentText(QString::fromStdString(network_interface));
84 ui->network_interface->setEnabled(runtime_lock);
70} 85}
71 86
72std::pair<QString, QString> ConfigureService::BCATDownloadEvents() { 87std::pair<QString, QString> ConfigureNetwork::BCATDownloadEvents() {
73#ifdef YUZU_ENABLE_BOXCAT 88#ifdef YUZU_ENABLE_BOXCAT
74 std::optional<std::string> global; 89 std::optional<std::string> global;
75 std::map<std::string, Service::BCAT::EventStatus> map; 90 std::map<std::string, Service::BCAT::EventStatus> map;
@@ -114,7 +129,7 @@ std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
114#endif 129#endif
115} 130}
116 131
117void ConfigureService::OnBCATImplChanged() { 132void ConfigureNetwork::OnBCATImplChanged() {
118#ifdef YUZU_ENABLE_BOXCAT 133#ifdef YUZU_ENABLE_BOXCAT
119 const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat"); 134 const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
120 ui->bcat_empty_header->setHidden(!boxcat); 135 ui->bcat_empty_header->setHidden(!boxcat);
@@ -133,7 +148,7 @@ void ConfigureService::OnBCATImplChanged() {
133#endif 148#endif
134} 149}
135 150
136void ConfigureService::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) { 151void ConfigureNetwork::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) {
137#ifdef YUZU_ENABLE_BOXCAT 152#ifdef YUZU_ENABLE_BOXCAT
138 const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat"); 153 const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
139 if (boxcat) { 154 if (boxcat) {
diff --git a/src/yuzu/configuration/configure_service.h b/src/yuzu/configuration/configure_network.h
index f5c1b703a..442b68e6b 100644
--- a/src/yuzu/configuration/configure_service.h
+++ b/src/yuzu/configuration/configure_network.h
@@ -9,15 +9,15 @@
9#include <QWidget> 9#include <QWidget>
10 10
11namespace Ui { 11namespace Ui {
12class ConfigureService; 12class ConfigureNetwork;
13} 13}
14 14
15class ConfigureService : public QWidget { 15class ConfigureNetwork : public QWidget {
16 Q_OBJECT 16 Q_OBJECT
17 17
18public: 18public:
19 explicit ConfigureService(QWidget* parent = nullptr); 19 explicit ConfigureNetwork(QWidget* parent = nullptr);
20 ~ConfigureService() override; 20 ~ConfigureNetwork() override;
21 21
22 void ApplyConfiguration(); 22 void ApplyConfiguration();
23 void RetranslateUi(); 23 void RetranslateUi();
@@ -29,6 +29,6 @@ private:
29 void OnBCATImplChanged(); 29 void OnBCATImplChanged();
30 void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string); 30 void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string);
31 31
32 std::unique_ptr<Ui::ConfigureService> ui; 32 std::unique_ptr<Ui::ConfigureNetwork> ui;
33 QFutureWatcher<std::pair<QString, QString>> watcher{this}; 33 QFutureWatcher<std::pair<QString, QString>> watcher{this};
34}; 34};
diff --git a/src/yuzu/configuration/configure_service.ui b/src/yuzu/configuration/configure_network.ui
index 9668dd557..5f9b7e97b 100644
--- a/src/yuzu/configuration/configure_service.ui
+++ b/src/yuzu/configuration/configure_network.ui
@@ -1,7 +1,7 @@
1<?xml version="1.0" encoding="UTF-8"?> 1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0"> 2<ui version="4.0">
3 <class>ConfigureService</class> 3 <class>ConfigureNetwork</class>
4 <widget class="QWidget" name="ConfigureService"> 4 <widget class="QWidget" name="ConfigureNetwork">
5 <property name="geometry"> 5 <property name="geometry">
6 <rect> 6 <rect>
7 <x>0</x> 7 <x>0</x>
@@ -17,21 +17,37 @@
17 <item> 17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3"> 18 <layout class="QVBoxLayout" name="verticalLayout_3">
19 <item> 19 <item>
20 <widget class="QGroupBox" name="groupBox_2">
21 <property name="title">
22 <string>General</string>
23 </property>
24 <layout class="QGridLayout" name="gridLayout_2">
25 <item row="1" column="1">
26 <widget class="QComboBox" name="network_interface"/>
27 </item>
28 <item row="1" column="0">
29 <widget class="QLabel" name="label_4">
30 <property name="text">
31 <string>Network Interface</string>
32 </property>
33 </widget>
34 </item>
35 </layout>
36 </widget>
37 </item>
38 <item>
20 <widget class="QGroupBox" name="groupBox"> 39 <widget class="QGroupBox" name="groupBox">
21 <property name="title"> 40 <property name="title">
22 <string>BCAT</string> 41 <string>BCAT</string>
23 </property> 42 </property>
24 <layout class="QGridLayout" name="gridLayout"> 43 <layout class="QGridLayout" name="gridLayout">
25 <item row="1" column="1" colspan="2"> 44 <item row="3" column="0">
26 <widget class="QLabel" name="label_2"> 45 <widget class="QLabel" name="bcat_empty_header">
27 <property name="maximumSize">
28 <size>
29 <width>260</width>
30 <height>16777215</height>
31 </size>
32 </property>
33 <property name="text"> 46 <property name="text">
34 <string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string> 47 <string/>
48 </property>
49 <property name="alignment">
50 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
35 </property> 51 </property>
36 <property name="wordWrap"> 52 <property name="wordWrap">
37 <bool>true</bool> 53 <bool>true</bool>
@@ -51,11 +67,8 @@
51 </property> 67 </property>
52 </widget> 68 </widget>
53 </item> 69 </item>
54 <item row="3" column="1" colspan="2"> 70 <item row="1" column="1" colspan="2">
55 <widget class="QLabel" name="bcat_empty_label"> 71 <widget class="QLabel" name="label_2">
56 <property name="enabled">
57 <bool>true</bool>
58 </property>
59 <property name="maximumSize"> 72 <property name="maximumSize">
60 <size> 73 <size>
61 <width>260</width> 74 <width>260</width>
@@ -63,10 +76,7 @@
63 </size> 76 </size>
64 </property> 77 </property>
65 <property name="text"> 78 <property name="text">
66 <string/> 79 <string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string>
67 </property>
68 <property name="alignment">
69 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
70 </property> 80 </property>
71 <property name="wordWrap"> 81 <property name="wordWrap">
72 <bool>true</bool> 82 <bool>true</bool>
@@ -86,8 +96,17 @@
86 <item row="0" column="1" colspan="2"> 96 <item row="0" column="1" colspan="2">
87 <widget class="QComboBox" name="bcat_source"/> 97 <widget class="QComboBox" name="bcat_source"/>
88 </item> 98 </item>
89 <item row="3" column="0"> 99 <item row="3" column="1" colspan="2">
90 <widget class="QLabel" name="bcat_empty_header"> 100 <widget class="QLabel" name="bcat_empty_label">
101 <property name="enabled">
102 <bool>true</bool>
103 </property>
104 <property name="maximumSize">
105 <size>
106 <width>260</width>
107 <height>16777215</height>
108 </size>
109 </property>
91 <property name="text"> 110 <property name="text">
92 <string/> 111 <string/>
93 </property> 112 </property>