summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/hid/emulated_controller.cpp2
-rw-r--r--src/core/hle/service/hid/hid.cpp3
-rw-r--r--src/core/hle/service/ldn/lan_discovery.cpp633
-rw-r--r--src/core/hle/service/ldn/lan_discovery.h134
-rw-r--r--src/core/hle/service/ldn/ldn.cpp227
-rw-r--r--src/core/hle/service/ldn/ldn_types.h48
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp7
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h6
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp15
-rw-r--r--src/core/hle/service/vi/display/vi_display.h14
-rw-r--r--src/core/hle/service/vi/vi.cpp40
-rw-r--r--src/core/hle/service/vi/vi_results.h13
-rw-r--r--src/core/internal_network/network_interface.cpp12
-rw-r--r--src/core/internal_network/network_interface.h1
-rw-r--r--src/core/internal_network/socket_proxy.cpp8
-rw-r--r--src/dedicated_room/yuzu_room.cpp13
-rw-r--r--src/network/room.cpp63
-rw-r--r--src/network/room.h1
-rw-r--r--src/network/room_member.cpp57
-rw-r--r--src/network/room_member.h35
-rw-r--r--src/video_core/host_shaders/astc_decoder.comp2
-rw-r--r--src/video_core/macro/macro_hle.cpp63
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp2
-rw-r--r--src/video_core/textures/astc.cpp2
-rw-r--r--src/yuzu/main.cpp12
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/main.ui24
-rw-r--r--src/yuzu/multiplayer/chat_room.cpp12
-rw-r--r--src/yuzu/multiplayer/client_room.cpp3
-rw-r--r--src/yuzu/multiplayer/direct_connect.cpp2
-rw-r--r--src/yuzu/multiplayer/direct_connect.h1
-rw-r--r--src/yuzu/multiplayer/host_room.cpp1
-rw-r--r--src/yuzu/multiplayer/host_room.h3
-rw-r--r--src/yuzu/multiplayer/lobby.cpp67
-rw-r--r--src/yuzu/multiplayer/lobby.h8
-rw-r--r--src/yuzu/multiplayer/lobby_p.h18
-rw-r--r--src/yuzu/multiplayer/message.cpp6
-rw-r--r--src/yuzu/multiplayer/state.cpp80
-rw-r--r--src/yuzu/multiplayer/state.h14
-rw-r--r--src/yuzu/uisettings.h2
41 files changed, 1439 insertions, 218 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 7fd2d0276..8e3fd4505 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -496,6 +496,8 @@ add_library(core STATIC
496 hle/service/jit/jit.h 496 hle/service/jit/jit.h
497 hle/service/lbl/lbl.cpp 497 hle/service/lbl/lbl.cpp
498 hle/service/lbl/lbl.h 498 hle/service/lbl/lbl.h
499 hle/service/ldn/lan_discovery.cpp
500 hle/service/ldn/lan_discovery.h
499 hle/service/ldn/ldn_results.h 501 hle/service/ldn/ldn_results.h
500 hle/service/ldn/ldn.cpp 502 hle/service/ldn/ldn.cpp
501 hle/service/ldn/ldn.h 503 hle/service/ldn/ldn.h
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 142c39003..e27d84734 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -93,7 +93,7 @@ void EmulatedController::ReloadFromSettings() {
93 .body = GetNpadColor(player.body_color_left), 93 .body = GetNpadColor(player.body_color_left),
94 .button = GetNpadColor(player.button_color_left), 94 .button = GetNpadColor(player.button_color_left),
95 }; 95 };
96 controller.colors_state.left = { 96 controller.colors_state.right = {
97 .body = GetNpadColor(player.body_color_right), 97 .body = GetNpadColor(player.body_color_right),
98 .button = GetNpadColor(player.button_color_right), 98 .button = GetNpadColor(player.button_color_right),
99 }; 99 };
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index de3fae2cb..46bad7871 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -36,7 +36,8 @@ namespace Service::HID {
36 36
37// Updating period for each HID device. 37// Updating period for each HID device.
38// Period time is obtained by measuring the number of samples in a second on HW using a homebrew 38// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
39constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz) 39// Correct pad_update_ns is 4ms this is overclocked to lower input lag
40constexpr auto pad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz)
40constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz) 41constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
41constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz) 42constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
42 43
diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp
new file mode 100644
index 000000000..8f3c04550
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.cpp
@@ -0,0 +1,633 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/service/ldn/lan_discovery.h"
5#include "core/internal_network/network.h"
6#include "core/internal_network/network_interface.h"
7
8namespace Service::LDN {
9
10LanStation::LanStation(s8 node_id_, LANDiscovery* discovery_)
11 : node_info(nullptr), status(NodeStatus::Disconnected), node_id(node_id_),
12 discovery(discovery_) {}
13
14LanStation::~LanStation() = default;
15
16NodeStatus LanStation::GetStatus() const {
17 return status;
18}
19
20void LanStation::OnClose() {
21 LOG_INFO(Service_LDN, "OnClose {}", node_id);
22 Reset();
23 discovery->UpdateNodes();
24}
25
26void LanStation::Reset() {
27 status = NodeStatus::Disconnected;
28};
29
30void LanStation::OverrideInfo() {
31 bool connected = GetStatus() == NodeStatus::Connected;
32 node_info->node_id = node_id;
33 node_info->is_connected = connected ? 1 : 0;
34}
35
36LANDiscovery::LANDiscovery(Network::RoomNetwork& room_network_)
37 : stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}),
38 room_network{room_network_} {}
39
40LANDiscovery::~LANDiscovery() {
41 if (inited) {
42 Result rc = Finalize();
43 LOG_INFO(Service_LDN, "Finalize: {}", rc.raw);
44 }
45}
46
47void LANDiscovery::InitNetworkInfo() {
48 network_info.common.bssid = GetFakeMac();
49 network_info.common.channel = WifiChannel::Wifi24_6;
50 network_info.common.link_level = LinkLevel::Good;
51 network_info.common.network_type = PackedNetworkType::Ldn;
52 network_info.common.ssid = fake_ssid;
53
54 auto& nodes = network_info.ldn.nodes;
55 for (std::size_t i = 0; i < NodeCountMax; i++) {
56 nodes[i].node_id = static_cast<s8>(i);
57 nodes[i].is_connected = 0;
58 }
59}
60
61void LANDiscovery::InitNodeStateChange() {
62 for (auto& node_update : node_changes) {
63 node_update.state_change = NodeStateChange::None;
64 }
65 for (auto& node_state : node_last_states) {
66 node_state = 0;
67 }
68}
69
70State LANDiscovery::GetState() const {
71 return state;
72}
73
74void LANDiscovery::SetState(State new_state) {
75 state = new_state;
76}
77
78Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const {
79 if (state == State::AccessPointCreated || state == State::StationConnected) {
80 std::memcpy(&out_network, &network_info, sizeof(network_info));
81 return ResultSuccess;
82 }
83
84 return ResultBadState;
85}
86
87Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network,
88 std::vector<NodeLatestUpdate>& out_updates,
89 std::size_t buffer_count) {
90 if (buffer_count > NodeCountMax) {
91 return ResultInvalidBufferCount;
92 }
93
94 if (state == State::AccessPointCreated || state == State::StationConnected) {
95 std::memcpy(&out_network, &network_info, sizeof(network_info));
96 for (std::size_t i = 0; i < buffer_count; i++) {
97 out_updates[i].state_change = node_changes[i].state_change;
98 node_changes[i].state_change = NodeStateChange::None;
99 }
100 return ResultSuccess;
101 }
102
103 return ResultBadState;
104}
105
106DisconnectReason LANDiscovery::GetDisconnectReason() const {
107 return disconnect_reason;
108}
109
110Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
111 const ScanFilter& filter) {
112 if (!IsFlagSet(filter.flag, ScanFilterFlag::NetworkType) ||
113 filter.network_type <= NetworkType::All) {
114 if (!IsFlagSet(filter.flag, ScanFilterFlag::Ssid) && filter.ssid.length >= SsidLengthMax) {
115 return ResultBadInput;
116 }
117 }
118
119 {
120 std::scoped_lock lock{packet_mutex};
121 scan_results.clear();
122
123 SendBroadcast(Network::LDNPacketType::Scan);
124 }
125
126 LOG_INFO(Service_LDN, "Waiting for scan replies");
127 std::this_thread::sleep_for(std::chrono::seconds(1));
128
129 std::scoped_lock lock{packet_mutex};
130 for (const auto& [key, info] : scan_results) {
131 if (count >= networks.size()) {
132 break;
133 }
134
135 if (IsFlagSet(filter.flag, ScanFilterFlag::LocalCommunicationId)) {
136 if (filter.network_id.intent_id.local_communication_id !=
137 info.network_id.intent_id.local_communication_id) {
138 continue;
139 }
140 }
141 if (IsFlagSet(filter.flag, ScanFilterFlag::SessionId)) {
142 if (filter.network_id.session_id != info.network_id.session_id) {
143 continue;
144 }
145 }
146 if (IsFlagSet(filter.flag, ScanFilterFlag::NetworkType)) {
147 if (filter.network_type != static_cast<NetworkType>(info.common.network_type)) {
148 continue;
149 }
150 }
151 if (IsFlagSet(filter.flag, ScanFilterFlag::Ssid)) {
152 if (filter.ssid != info.common.ssid) {
153 continue;
154 }
155 }
156 if (IsFlagSet(filter.flag, ScanFilterFlag::SceneId)) {
157 if (filter.network_id.intent_id.scene_id != info.network_id.intent_id.scene_id) {
158 continue;
159 }
160 }
161
162 networks[count++] = info;
163 }
164
165 return ResultSuccess;
166}
167
168Result LANDiscovery::SetAdvertiseData(std::span<const u8> data) {
169 std::scoped_lock lock{packet_mutex};
170 const std::size_t size = data.size();
171 if (size > AdvertiseDataSizeMax) {
172 return ResultAdvertiseDataTooLarge;
173 }
174
175 std::memcpy(network_info.ldn.advertise_data.data(), data.data(), size);
176 network_info.ldn.advertise_data_size = static_cast<u16>(size);
177
178 UpdateNodes();
179
180 return ResultSuccess;
181}
182
183Result LANDiscovery::OpenAccessPoint() {
184 std::scoped_lock lock{packet_mutex};
185 disconnect_reason = DisconnectReason::None;
186 if (state == State::None) {
187 return ResultBadState;
188 }
189
190 ResetStations();
191 SetState(State::AccessPointOpened);
192
193 return ResultSuccess;
194}
195
196Result LANDiscovery::CloseAccessPoint() {
197 std::scoped_lock lock{packet_mutex};
198 if (state == State::None) {
199 return ResultBadState;
200 }
201
202 if (state == State::AccessPointCreated) {
203 DestroyNetwork();
204 }
205
206 ResetStations();
207 SetState(State::Initialized);
208
209 return ResultSuccess;
210}
211
212Result LANDiscovery::OpenStation() {
213 std::scoped_lock lock{packet_mutex};
214 disconnect_reason = DisconnectReason::None;
215 if (state == State::None) {
216 return ResultBadState;
217 }
218
219 ResetStations();
220 SetState(State::StationOpened);
221
222 return ResultSuccess;
223}
224
225Result LANDiscovery::CloseStation() {
226 std::scoped_lock lock{packet_mutex};
227 if (state == State::None) {
228 return ResultBadState;
229 }
230
231 if (state == State::StationConnected) {
232 Disconnect();
233 }
234
235 ResetStations();
236 SetState(State::Initialized);
237
238 return ResultSuccess;
239}
240
241Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config,
242 const UserConfig& user_config,
243 const NetworkConfig& network_config) {
244 std::scoped_lock lock{packet_mutex};
245
246 if (state != State::AccessPointOpened) {
247 return ResultBadState;
248 }
249
250 InitNetworkInfo();
251 network_info.ldn.node_count_max = network_config.node_count_max;
252 network_info.ldn.security_mode = security_config.security_mode;
253
254 if (network_config.channel == WifiChannel::Default) {
255 network_info.common.channel = WifiChannel::Wifi24_6;
256 } else {
257 network_info.common.channel = network_config.channel;
258 }
259
260 std::independent_bits_engine<std::mt19937, 64, u64> bits_engine;
261 network_info.network_id.session_id.high = bits_engine();
262 network_info.network_id.session_id.low = bits_engine();
263 network_info.network_id.intent_id = network_config.intent_id;
264
265 NodeInfo& node0 = network_info.ldn.nodes[0];
266 const Result rc2 = GetNodeInfo(node0, user_config, network_config.local_communication_version);
267 if (rc2.IsError()) {
268 return ResultAccessPointConnectionFailed;
269 }
270
271 SetState(State::AccessPointCreated);
272
273 InitNodeStateChange();
274 node0.is_connected = 1;
275 UpdateNodes();
276
277 return rc2;
278}
279
280Result LANDiscovery::DestroyNetwork() {
281 for (auto local_ip : connected_clients) {
282 SendPacket(Network::LDNPacketType::DestroyNetwork, local_ip);
283 }
284
285 ResetStations();
286
287 SetState(State::AccessPointOpened);
288 lan_event();
289
290 return ResultSuccess;
291}
292
293Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
294 u16 local_communication_version) {
295 std::scoped_lock lock{packet_mutex};
296 if (network_info_.ldn.node_count == 0) {
297 return ResultInvalidNodeCount;
298 }
299
300 Result rc = GetNodeInfo(node_info, user_config, local_communication_version);
301 if (rc.IsError()) {
302 return ResultConnectionFailed;
303 }
304
305 Ipv4Address node_host = network_info_.ldn.nodes[0].ipv4_address;
306 std::reverse(std::begin(node_host), std::end(node_host)); // htonl
307 host_ip = node_host;
308 SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip);
309
310 InitNodeStateChange();
311
312 std::this_thread::sleep_for(std::chrono::seconds(1));
313
314 return ResultSuccess;
315}
316
317Result LANDiscovery::Disconnect() {
318 if (host_ip) {
319 SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip);
320 }
321
322 SetState(State::StationOpened);
323 lan_event();
324
325 return ResultSuccess;
326}
327
328Result LANDiscovery::Initialize(LanEventFunc lan_event_, bool listening) {
329 std::scoped_lock lock{packet_mutex};
330 if (inited) {
331 return ResultSuccess;
332 }
333
334 for (auto& station : stations) {
335 station.discovery = this;
336 station.node_info = &network_info.ldn.nodes[station.node_id];
337 station.Reset();
338 }
339
340 connected_clients.clear();
341 lan_event = lan_event_;
342
343 SetState(State::Initialized);
344
345 inited = true;
346 return ResultSuccess;
347}
348
349Result LANDiscovery::Finalize() {
350 std::scoped_lock lock{packet_mutex};
351 Result rc = ResultSuccess;
352
353 if (inited) {
354 if (state == State::AccessPointCreated) {
355 DestroyNetwork();
356 }
357 if (state == State::StationConnected) {
358 Disconnect();
359 }
360
361 ResetStations();
362 inited = false;
363 }
364
365 SetState(State::None);
366
367 return rc;
368}
369
370void LANDiscovery::ResetStations() {
371 for (auto& station : stations) {
372 station.Reset();
373 }
374 connected_clients.clear();
375}
376
377void LANDiscovery::UpdateNodes() {
378 u8 count = 0;
379 for (auto& station : stations) {
380 bool connected = station.GetStatus() == NodeStatus::Connected;
381 if (connected) {
382 count++;
383 }
384 station.OverrideInfo();
385 }
386 network_info.ldn.node_count = count + 1;
387
388 for (auto local_ip : connected_clients) {
389 SendPacket(Network::LDNPacketType::SyncNetwork, network_info, local_ip);
390 }
391
392 OnNetworkInfoChanged();
393}
394
395void LANDiscovery::OnSyncNetwork(const NetworkInfo& info) {
396 network_info = info;
397 if (state == State::StationOpened) {
398 SetState(State::StationConnected);
399 }
400 OnNetworkInfoChanged();
401}
402
403void LANDiscovery::OnDisconnectFromHost() {
404 LOG_INFO(Service_LDN, "OnDisconnectFromHost state: {}", static_cast<int>(state));
405 host_ip = std::nullopt;
406 if (state == State::StationConnected) {
407 SetState(State::StationOpened);
408 lan_event();
409 }
410}
411
412void LANDiscovery::OnNetworkInfoChanged() {
413 if (IsNodeStateChanged()) {
414 lan_event();
415 }
416 return;
417}
418
419Network::IPv4Address LANDiscovery::GetLocalIp() const {
420 Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF};
421 if (auto room_member = room_network.GetRoomMember().lock()) {
422 if (room_member->IsConnected()) {
423 local_ip = room_member->GetFakeIpAddress();
424 }
425 }
426 return local_ip;
427}
428
429template <typename Data>
430void LANDiscovery::SendPacket(Network::LDNPacketType type, const Data& data,
431 Ipv4Address remote_ip) {
432 Network::LDNPacket packet;
433 packet.type = type;
434
435 packet.broadcast = false;
436 packet.local_ip = GetLocalIp();
437 packet.remote_ip = remote_ip;
438
439 packet.data.resize(sizeof(data));
440 std::memcpy(packet.data.data(), &data, sizeof(data));
441 SendPacket(packet);
442}
443
444void LANDiscovery::SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip) {
445 Network::LDNPacket packet;
446 packet.type = type;
447
448 packet.broadcast = false;
449 packet.local_ip = GetLocalIp();
450 packet.remote_ip = remote_ip;
451
452 SendPacket(packet);
453}
454
455template <typename Data>
456void LANDiscovery::SendBroadcast(Network::LDNPacketType type, const Data& data) {
457 Network::LDNPacket packet;
458 packet.type = type;
459
460 packet.broadcast = true;
461 packet.local_ip = GetLocalIp();
462
463 packet.data.resize(sizeof(data));
464 std::memcpy(packet.data.data(), &data, sizeof(data));
465 SendPacket(packet);
466}
467
468void LANDiscovery::SendBroadcast(Network::LDNPacketType type) {
469 Network::LDNPacket packet;
470 packet.type = type;
471
472 packet.broadcast = true;
473 packet.local_ip = GetLocalIp();
474
475 SendPacket(packet);
476}
477
478void LANDiscovery::SendPacket(const Network::LDNPacket& packet) {
479 if (auto room_member = room_network.GetRoomMember().lock()) {
480 if (room_member->IsConnected()) {
481 room_member->SendLdnPacket(packet);
482 }
483 }
484}
485
486void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) {
487 std::scoped_lock lock{packet_mutex};
488 switch (packet.type) {
489 case Network::LDNPacketType::Scan: {
490 LOG_INFO(Frontend, "Scan packet received!");
491 if (state == State::AccessPointCreated) {
492 // Reply to the sender
493 SendPacket(Network::LDNPacketType::ScanResp, network_info, packet.local_ip);
494 }
495 break;
496 }
497 case Network::LDNPacketType::ScanResp: {
498 LOG_INFO(Frontend, "ScanResp packet received!");
499
500 NetworkInfo info{};
501 std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
502 scan_results.insert({info.common.bssid, info});
503
504 break;
505 }
506 case Network::LDNPacketType::Connect: {
507 LOG_INFO(Frontend, "Connect packet received!");
508
509 NodeInfo info{};
510 std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
511
512 connected_clients.push_back(packet.local_ip);
513
514 for (LanStation& station : stations) {
515 if (station.status != NodeStatus::Connected) {
516 *station.node_info = info;
517 station.status = NodeStatus::Connected;
518 break;
519 }
520 }
521
522 UpdateNodes();
523
524 break;
525 }
526 case Network::LDNPacketType::Disconnect: {
527 LOG_INFO(Frontend, "Disconnect packet received!");
528
529 connected_clients.erase(
530 std::remove(connected_clients.begin(), connected_clients.end(), packet.local_ip),
531 connected_clients.end());
532
533 NodeInfo info{};
534 std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
535
536 for (LanStation& station : stations) {
537 if (station.status == NodeStatus::Connected &&
538 station.node_info->mac_address == info.mac_address) {
539 station.OnClose();
540 break;
541 }
542 }
543
544 break;
545 }
546 case Network::LDNPacketType::DestroyNetwork: {
547 ResetStations();
548 OnDisconnectFromHost();
549 break;
550 }
551 case Network::LDNPacketType::SyncNetwork: {
552 if (state == State::StationOpened || state == State::StationConnected) {
553 LOG_INFO(Frontend, "SyncNetwork packet received!");
554 NetworkInfo info{};
555 std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
556
557 OnSyncNetwork(info);
558 } else {
559 LOG_INFO(Frontend, "SyncNetwork packet received but in wrong State!");
560 }
561
562 break;
563 }
564 default: {
565 LOG_INFO(Frontend, "ReceivePacket unhandled type {}", static_cast<int>(packet.type));
566 break;
567 }
568 }
569}
570
571bool LANDiscovery::IsNodeStateChanged() {
572 bool changed = false;
573 const auto& nodes = network_info.ldn.nodes;
574 for (int i = 0; i < NodeCountMax; i++) {
575 if (nodes[i].is_connected != node_last_states[i]) {
576 if (nodes[i].is_connected) {
577 node_changes[i].state_change |= NodeStateChange::Connect;
578 } else {
579 node_changes[i].state_change |= NodeStateChange::Disconnect;
580 }
581 node_last_states[i] = nodes[i].is_connected;
582 changed = true;
583 }
584 }
585 return changed;
586}
587
588bool LANDiscovery::IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const {
589 const auto flag_value = static_cast<u32>(flag);
590 const auto search_flag_value = static_cast<u32>(search_flag);
591 return (flag_value & search_flag_value) == search_flag_value;
592}
593
594int LANDiscovery::GetStationCount() const {
595 return static_cast<int>(
596 std::count_if(stations.begin(), stations.end(), [](const auto& station) {
597 return station.GetStatus() != NodeStatus::Disconnected;
598 }));
599}
600
601MacAddress LANDiscovery::GetFakeMac() const {
602 MacAddress mac{};
603 mac.raw[0] = 0x02;
604 mac.raw[1] = 0x00;
605
606 const auto ip = GetLocalIp();
607 memcpy(mac.raw.data() + 2, &ip, sizeof(ip));
608
609 return mac;
610}
611
612Result LANDiscovery::GetNodeInfo(NodeInfo& node, const UserConfig& userConfig,
613 u16 localCommunicationVersion) {
614 const auto network_interface = Network::GetSelectedNetworkInterface();
615
616 if (!network_interface) {
617 LOG_ERROR(Service_LDN, "No network interface available");
618 return ResultNoIpAddress;
619 }
620
621 node.mac_address = GetFakeMac();
622 node.is_connected = 1;
623 std::memcpy(node.user_name.data(), userConfig.user_name.data(), UserNameBytesMax + 1);
624 node.local_communication_version = localCommunicationVersion;
625
626 Ipv4Address current_address = GetLocalIp();
627 std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
628 node.ipv4_address = current_address;
629
630 return ResultSuccess;
631}
632
633} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h
new file mode 100644
index 000000000..3833cd764
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.h
@@ -0,0 +1,134 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <cstring>
8#include <functional>
9#include <memory>
10#include <mutex>
11#include <optional>
12#include <random>
13#include <span>
14#include <thread>
15#include <unordered_map>
16
17#include "common/logging/log.h"
18#include "common/socket_types.h"
19#include "core/hle/result.h"
20#include "core/hle/service/ldn/ldn_results.h"
21#include "core/hle/service/ldn/ldn_types.h"
22#include "network/network.h"
23
24namespace Service::LDN {
25
26class LANDiscovery;
27
28class LanStation {
29public:
30 LanStation(s8 node_id_, LANDiscovery* discovery_);
31 ~LanStation();
32
33 void OnClose();
34 NodeStatus GetStatus() const;
35 void Reset();
36 void OverrideInfo();
37
38protected:
39 friend class LANDiscovery;
40 NodeInfo* node_info;
41 NodeStatus status;
42 s8 node_id;
43 LANDiscovery* discovery;
44};
45
46class LANDiscovery {
47public:
48 using LanEventFunc = std::function<void()>;
49
50 LANDiscovery(Network::RoomNetwork& room_network_);
51 ~LANDiscovery();
52
53 State GetState() const;
54 void SetState(State new_state);
55
56 Result GetNetworkInfo(NetworkInfo& out_network) const;
57 Result GetNetworkInfo(NetworkInfo& out_network, std::vector<NodeLatestUpdate>& out_updates,
58 std::size_t buffer_count);
59
60 DisconnectReason GetDisconnectReason() const;
61 Result Scan(std::vector<NetworkInfo>& networks, u16& count, const ScanFilter& filter);
62 Result SetAdvertiseData(std::span<const u8> data);
63
64 Result OpenAccessPoint();
65 Result CloseAccessPoint();
66
67 Result OpenStation();
68 Result CloseStation();
69
70 Result CreateNetwork(const SecurityConfig& security_config, const UserConfig& user_config,
71 const NetworkConfig& network_config);
72 Result DestroyNetwork();
73
74 Result Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
75 u16 local_communication_version);
76 Result Disconnect();
77
78 Result Initialize(LanEventFunc lan_event_ = empty_func, bool listening = true);
79 Result Finalize();
80
81 void ReceivePacket(const Network::LDNPacket& packet);
82
83protected:
84 friend class LanStation;
85
86 void InitNetworkInfo();
87 void InitNodeStateChange();
88
89 void ResetStations();
90 void UpdateNodes();
91
92 void OnSyncNetwork(const NetworkInfo& info);
93 void OnDisconnectFromHost();
94 void OnNetworkInfoChanged();
95
96 bool IsNodeStateChanged();
97 bool IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const;
98 int GetStationCount() const;
99 MacAddress GetFakeMac() const;
100 Result GetNodeInfo(NodeInfo& node, const UserConfig& user_config,
101 u16 local_communication_version);
102
103 Network::IPv4Address GetLocalIp() const;
104 template <typename Data>
105 void SendPacket(Network::LDNPacketType type, const Data& data, Ipv4Address remote_ip);
106 void SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip);
107 template <typename Data>
108 void SendBroadcast(Network::LDNPacketType type, const Data& data);
109 void SendBroadcast(Network::LDNPacketType type);
110 void SendPacket(const Network::LDNPacket& packet);
111
112 static const LanEventFunc empty_func;
113 static constexpr Ssid fake_ssid{"YuzuFakeSsidForLdn"};
114
115 bool inited{};
116 std::mutex packet_mutex;
117 std::array<LanStation, StationCountMax> stations;
118 std::array<NodeLatestUpdate, NodeCountMax> node_changes{};
119 std::array<u8, NodeCountMax> node_last_states{};
120 std::unordered_map<MacAddress, NetworkInfo, MACAddressHash> scan_results{};
121 NodeInfo node_info{};
122 NetworkInfo network_info{};
123 State state{State::None};
124 DisconnectReason disconnect_reason{DisconnectReason::None};
125
126 // TODO (flTobi): Should this be an std::set?
127 std::vector<Ipv4Address> connected_clients;
128 std::optional<Ipv4Address> host_ip;
129
130 LanEventFunc lan_event;
131
132 Network::RoomNetwork& room_network;
133};
134} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index c11daff54..ea3e7e55a 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -4,11 +4,13 @@
4#include <memory> 4#include <memory>
5 5
6#include "core/core.h" 6#include "core/core.h"
7#include "core/hle/service/ldn/lan_discovery.h"
7#include "core/hle/service/ldn/ldn.h" 8#include "core/hle/service/ldn/ldn.h"
8#include "core/hle/service/ldn/ldn_results.h" 9#include "core/hle/service/ldn/ldn_results.h"
9#include "core/hle/service/ldn/ldn_types.h" 10#include "core/hle/service/ldn/ldn_types.h"
10#include "core/internal_network/network.h" 11#include "core/internal_network/network.h"
11#include "core/internal_network/network_interface.h" 12#include "core/internal_network/network_interface.h"
13#include "network/network.h"
12 14
13// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent 15// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent
14#undef CreateEvent 16#undef CreateEvent
@@ -105,13 +107,13 @@ class IUserLocalCommunicationService final
105public: 107public:
106 explicit IUserLocalCommunicationService(Core::System& system_) 108 explicit IUserLocalCommunicationService(Core::System& system_)
107 : ServiceFramework{system_, "IUserLocalCommunicationService", ServiceThreadType::CreateNew}, 109 : ServiceFramework{system_, "IUserLocalCommunicationService", ServiceThreadType::CreateNew},
108 service_context{system, "IUserLocalCommunicationService"}, room_network{ 110 service_context{system, "IUserLocalCommunicationService"},
109 system_.GetRoomNetwork()} { 111 room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} {
110 // clang-format off 112 // clang-format off
111 static const FunctionInfo functions[] = { 113 static const FunctionInfo functions[] = {
112 {0, &IUserLocalCommunicationService::GetState, "GetState"}, 114 {0, &IUserLocalCommunicationService::GetState, "GetState"},
113 {1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"}, 115 {1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"},
114 {2, nullptr, "GetIpv4Address"}, 116 {2, &IUserLocalCommunicationService::GetIpv4Address, "GetIpv4Address"},
115 {3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"}, 117 {3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"},
116 {4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"}, 118 {4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"},
117 {5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"}, 119 {5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"},
@@ -119,7 +121,7 @@ public:
119 {101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"}, 121 {101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"},
120 {102, &IUserLocalCommunicationService::Scan, "Scan"}, 122 {102, &IUserLocalCommunicationService::Scan, "Scan"},
121 {103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"}, 123 {103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"},
122 {104, nullptr, "SetWirelessControllerRestriction"}, 124 {104, &IUserLocalCommunicationService::SetWirelessControllerRestriction, "SetWirelessControllerRestriction"},
123 {200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"}, 125 {200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"},
124 {201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"}, 126 {201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"},
125 {202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"}, 127 {202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"},
@@ -148,16 +150,30 @@ public:
148 } 150 }
149 151
150 ~IUserLocalCommunicationService() { 152 ~IUserLocalCommunicationService() {
153 if (is_initialized) {
154 if (auto room_member = room_network.GetRoomMember().lock()) {
155 room_member->Unbind(ldn_packet_received);
156 }
157 }
158
151 service_context.CloseEvent(state_change_event); 159 service_context.CloseEvent(state_change_event);
152 } 160 }
153 161
162 /// Callback to parse and handle a received LDN packet.
163 void OnLDNPacketReceived(const Network::LDNPacket& packet) {
164 lan_discovery.ReceivePacket(packet);
165 }
166
154 void OnEventFired() { 167 void OnEventFired() {
155 state_change_event->GetWritableEvent().Signal(); 168 state_change_event->GetWritableEvent().Signal();
156 } 169 }
157 170
158 void GetState(Kernel::HLERequestContext& ctx) { 171 void GetState(Kernel::HLERequestContext& ctx) {
159 State state = State::Error; 172 State state = State::Error;
160 LOG_WARNING(Service_LDN, "(STUBBED) called, state = {}", state); 173
174 if (is_initialized) {
175 state = lan_discovery.GetState();
176 }
161 177
162 IPC::ResponseBuilder rb{ctx, 3}; 178 IPC::ResponseBuilder rb{ctx, 3};
163 rb.Push(ResultSuccess); 179 rb.Push(ResultSuccess);
@@ -175,7 +191,7 @@ public:
175 } 191 }
176 192
177 NetworkInfo network_info{}; 193 NetworkInfo network_info{};
178 const auto rc = ResultSuccess; 194 const auto rc = lan_discovery.GetNetworkInfo(network_info);
179 if (rc.IsError()) { 195 if (rc.IsError()) {
180 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); 196 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
181 IPC::ResponseBuilder rb{ctx, 2}; 197 IPC::ResponseBuilder rb{ctx, 2};
@@ -183,28 +199,50 @@ public:
183 return; 199 return;
184 } 200 }
185 201
186 LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
187 network_info.common.ssid.GetStringValue(), network_info.ldn.node_count);
188
189 ctx.WriteBuffer<NetworkInfo>(network_info); 202 ctx.WriteBuffer<NetworkInfo>(network_info);
190 IPC::ResponseBuilder rb{ctx, 2}; 203 IPC::ResponseBuilder rb{ctx, 2};
191 rb.Push(rc); 204 rb.Push(ResultSuccess);
192 } 205 }
193 206
194 void GetDisconnectReason(Kernel::HLERequestContext& ctx) { 207 void GetIpv4Address(Kernel::HLERequestContext& ctx) {
195 const auto disconnect_reason = DisconnectReason::None; 208 const auto network_interface = Network::GetSelectedNetworkInterface();
209
210 if (!network_interface) {
211 LOG_ERROR(Service_LDN, "No network interface available");
212 IPC::ResponseBuilder rb{ctx, 2};
213 rb.Push(ResultNoIpAddress);
214 return;
215 }
196 216
197 LOG_WARNING(Service_LDN, "(STUBBED) called, disconnect_reason={}", disconnect_reason); 217 Ipv4Address current_address{Network::TranslateIPv4(network_interface->ip_address)};
218 Ipv4Address subnet_mask{Network::TranslateIPv4(network_interface->subnet_mask)};
219
220 // When we're connected to a room, spoof the hosts IP address
221 if (auto room_member = room_network.GetRoomMember().lock()) {
222 if (room_member->IsConnected()) {
223 current_address = room_member->GetFakeIpAddress();
224 }
225 }
226
227 std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
228 std::reverse(std::begin(subnet_mask), std::end(subnet_mask)); // ntohl
229
230 IPC::ResponseBuilder rb{ctx, 4};
231 rb.Push(ResultSuccess);
232 rb.PushRaw(current_address);
233 rb.PushRaw(subnet_mask);
234 }
198 235
236 void GetDisconnectReason(Kernel::HLERequestContext& ctx) {
199 IPC::ResponseBuilder rb{ctx, 3}; 237 IPC::ResponseBuilder rb{ctx, 3};
200 rb.Push(ResultSuccess); 238 rb.Push(ResultSuccess);
201 rb.PushEnum(disconnect_reason); 239 rb.PushEnum(lan_discovery.GetDisconnectReason());
202 } 240 }
203 241
204 void GetSecurityParameter(Kernel::HLERequestContext& ctx) { 242 void GetSecurityParameter(Kernel::HLERequestContext& ctx) {
205 SecurityParameter security_parameter{}; 243 SecurityParameter security_parameter{};
206 NetworkInfo info{}; 244 NetworkInfo info{};
207 const Result rc = ResultSuccess; 245 const Result rc = lan_discovery.GetNetworkInfo(info);
208 246
209 if (rc.IsError()) { 247 if (rc.IsError()) {
210 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); 248 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
@@ -217,8 +255,6 @@ public:
217 std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(), 255 std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(),
218 sizeof(SecurityParameter::data)); 256 sizeof(SecurityParameter::data));
219 257
220 LOG_WARNING(Service_LDN, "(STUBBED) called");
221
222 IPC::ResponseBuilder rb{ctx, 10}; 258 IPC::ResponseBuilder rb{ctx, 10};
223 rb.Push(rc); 259 rb.Push(rc);
224 rb.PushRaw<SecurityParameter>(security_parameter); 260 rb.PushRaw<SecurityParameter>(security_parameter);
@@ -227,7 +263,7 @@ public:
227 void GetNetworkConfig(Kernel::HLERequestContext& ctx) { 263 void GetNetworkConfig(Kernel::HLERequestContext& ctx) {
228 NetworkConfig config{}; 264 NetworkConfig config{};
229 NetworkInfo info{}; 265 NetworkInfo info{};
230 const Result rc = ResultSuccess; 266 const Result rc = lan_discovery.GetNetworkInfo(info);
231 267
232 if (rc.IsError()) { 268 if (rc.IsError()) {
233 LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw); 269 LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw);
@@ -241,12 +277,6 @@ public:
241 config.node_count_max = info.ldn.node_count_max; 277 config.node_count_max = info.ldn.node_count_max;
242 config.local_communication_version = info.ldn.nodes[0].local_communication_version; 278 config.local_communication_version = info.ldn.nodes[0].local_communication_version;
243 279
244 LOG_WARNING(Service_LDN,
245 "(STUBBED) called, intent_id={}/{}, channel={}, node_count_max={}, "
246 "local_communication_version={}",
247 config.intent_id.local_communication_id, config.intent_id.scene_id,
248 config.channel, config.node_count_max, config.local_communication_version);
249
250 IPC::ResponseBuilder rb{ctx, 10}; 280 IPC::ResponseBuilder rb{ctx, 10};
251 rb.Push(rc); 281 rb.Push(rc);
252 rb.PushRaw<NetworkConfig>(config); 282 rb.PushRaw<NetworkConfig>(config);
@@ -265,17 +295,17 @@ public:
265 const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate); 295 const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate);
266 296
267 if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) { 297 if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) {
268 LOG_ERROR(Service_LDN, "Invalid buffer size {}, {}", network_buffer_size, 298 LOG_ERROR(Service_LDN, "Invalid buffer, size = {}, count = {}", network_buffer_size,
269 node_buffer_count); 299 node_buffer_count);
270 IPC::ResponseBuilder rb{ctx, 2}; 300 IPC::ResponseBuilder rb{ctx, 2};
271 rb.Push(ResultBadInput); 301 rb.Push(ResultBadInput);
272 return; 302 return;
273 } 303 }
274 304
275 NetworkInfo info; 305 NetworkInfo info{};
276 std::vector<NodeLatestUpdate> latest_update(node_buffer_count); 306 std::vector<NodeLatestUpdate> latest_update(node_buffer_count);
277 307
278 const auto rc = ResultSuccess; 308 const auto rc = lan_discovery.GetNetworkInfo(info, latest_update, latest_update.size());
279 if (rc.IsError()) { 309 if (rc.IsError()) {
280 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); 310 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
281 IPC::ResponseBuilder rb{ctx, 2}; 311 IPC::ResponseBuilder rb{ctx, 2};
@@ -283,9 +313,6 @@ public:
283 return; 313 return;
284 } 314 }
285 315
286 LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
287 info.common.ssid.GetStringValue(), info.ldn.node_count);
288
289 ctx.WriteBuffer(info, 0); 316 ctx.WriteBuffer(info, 0);
290 ctx.WriteBuffer(latest_update, 1); 317 ctx.WriteBuffer(latest_update, 1);
291 318
@@ -317,92 +344,78 @@ public:
317 344
318 u16 count = 0; 345 u16 count = 0;
319 std::vector<NetworkInfo> network_infos(network_info_size); 346 std::vector<NetworkInfo> network_infos(network_info_size);
347 Result rc = lan_discovery.Scan(network_infos, count, scan_filter);
320 348
321 LOG_WARNING(Service_LDN, 349 LOG_INFO(Service_LDN,
322 "(STUBBED) called, channel={}, filter_scan_flag={}, filter_network_type={}", 350 "called, channel={}, filter_scan_flag={}, filter_network_type={}, is_private={}",
323 channel, scan_filter.flag, scan_filter.network_type); 351 channel, scan_filter.flag, scan_filter.network_type, is_private);
324 352
325 ctx.WriteBuffer(network_infos); 353 ctx.WriteBuffer(network_infos);
326 354
327 IPC::ResponseBuilder rb{ctx, 3}; 355 IPC::ResponseBuilder rb{ctx, 3};
328 rb.Push(ResultSuccess); 356 rb.Push(rc);
329 rb.Push<u32>(count); 357 rb.Push<u32>(count);
330 } 358 }
331 359
332 void OpenAccessPoint(Kernel::HLERequestContext& ctx) { 360 void SetWirelessControllerRestriction(Kernel::HLERequestContext& ctx) {
333 LOG_WARNING(Service_LDN, "(STUBBED) called"); 361 LOG_WARNING(Service_LDN, "(STUBBED) called");
334 362
335 IPC::ResponseBuilder rb{ctx, 2}; 363 IPC::ResponseBuilder rb{ctx, 2};
336 rb.Push(ResultSuccess); 364 rb.Push(ResultSuccess);
337 } 365 }
338 366
367 void OpenAccessPoint(Kernel::HLERequestContext& ctx) {
368 LOG_INFO(Service_LDN, "called");
369
370 IPC::ResponseBuilder rb{ctx, 2};
371 rb.Push(lan_discovery.OpenAccessPoint());
372 }
373
339 void CloseAccessPoint(Kernel::HLERequestContext& ctx) { 374 void CloseAccessPoint(Kernel::HLERequestContext& ctx) {
340 LOG_WARNING(Service_LDN, "(STUBBED) called"); 375 LOG_INFO(Service_LDN, "called");
341 376
342 IPC::ResponseBuilder rb{ctx, 2}; 377 IPC::ResponseBuilder rb{ctx, 2};
343 rb.Push(ResultSuccess); 378 rb.Push(lan_discovery.CloseAccessPoint());
344 } 379 }
345 380
346 void CreateNetwork(Kernel::HLERequestContext& ctx) { 381 void CreateNetwork(Kernel::HLERequestContext& ctx) {
347 IPC::RequestParser rp{ctx}; 382 LOG_INFO(Service_LDN, "called");
348 struct Parameters {
349 SecurityConfig security_config;
350 UserConfig user_config;
351 INSERT_PADDING_WORDS_NOINIT(1);
352 NetworkConfig network_config;
353 };
354 static_assert(sizeof(Parameters) == 0x98, "Parameters has incorrect size.");
355 383
356 const auto parameters{rp.PopRaw<Parameters>()}; 384 CreateNetworkImpl(ctx);
385 }
357 386
358 LOG_WARNING(Service_LDN, 387 void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) {
359 "(STUBBED) called, passphrase_size={}, security_mode={}, " 388 LOG_INFO(Service_LDN, "called");
360 "local_communication_version={}",
361 parameters.security_config.passphrase_size,
362 parameters.security_config.security_mode,
363 parameters.network_config.local_communication_version);
364 389
365 IPC::ResponseBuilder rb{ctx, 2}; 390 CreateNetworkImpl(ctx, true);
366 rb.Push(ResultSuccess);
367 } 391 }
368 392
369 void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) { 393 void CreateNetworkImpl(Kernel::HLERequestContext& ctx, bool is_private = false) {
370 IPC::RequestParser rp{ctx}; 394 IPC::RequestParser rp{ctx};
371 struct Parameters {
372 SecurityConfig security_config;
373 SecurityParameter security_parameter;
374 UserConfig user_config;
375 NetworkConfig network_config;
376 };
377 static_assert(sizeof(Parameters) == 0xB8, "Parameters has incorrect size.");
378
379 const auto parameters{rp.PopRaw<Parameters>()};
380 395
381 LOG_WARNING(Service_LDN, 396 const auto security_config{rp.PopRaw<SecurityConfig>()};
382 "(STUBBED) called, passphrase_size={}, security_mode={}, " 397 [[maybe_unused]] const auto security_parameter{is_private ? rp.PopRaw<SecurityParameter>()
383 "local_communication_version={}", 398 : SecurityParameter{}};
384 parameters.security_config.passphrase_size, 399 const auto user_config{rp.PopRaw<UserConfig>()};
385 parameters.security_config.security_mode, 400 rp.Pop<u32>(); // Padding
386 parameters.network_config.local_communication_version); 401 const auto network_Config{rp.PopRaw<NetworkConfig>()};
387 402
388 IPC::ResponseBuilder rb{ctx, 2}; 403 IPC::ResponseBuilder rb{ctx, 2};
389 rb.Push(ResultSuccess); 404 rb.Push(lan_discovery.CreateNetwork(security_config, user_config, network_Config));
390 } 405 }
391 406
392 void DestroyNetwork(Kernel::HLERequestContext& ctx) { 407 void DestroyNetwork(Kernel::HLERequestContext& ctx) {
393 LOG_WARNING(Service_LDN, "(STUBBED) called"); 408 LOG_INFO(Service_LDN, "called");
394 409
395 IPC::ResponseBuilder rb{ctx, 2}; 410 IPC::ResponseBuilder rb{ctx, 2};
396 rb.Push(ResultSuccess); 411 rb.Push(lan_discovery.DestroyNetwork());
397 } 412 }
398 413
399 void SetAdvertiseData(Kernel::HLERequestContext& ctx) { 414 void SetAdvertiseData(Kernel::HLERequestContext& ctx) {
400 std::vector<u8> read_buffer = ctx.ReadBuffer(); 415 std::vector<u8> read_buffer = ctx.ReadBuffer();
401 416
402 LOG_WARNING(Service_LDN, "(STUBBED) called, size {}", read_buffer.size());
403
404 IPC::ResponseBuilder rb{ctx, 2}; 417 IPC::ResponseBuilder rb{ctx, 2};
405 rb.Push(ResultSuccess); 418 rb.Push(lan_discovery.SetAdvertiseData(read_buffer));
406 } 419 }
407 420
408 void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) { 421 void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) {
@@ -420,17 +433,17 @@ public:
420 } 433 }
421 434
422 void OpenStation(Kernel::HLERequestContext& ctx) { 435 void OpenStation(Kernel::HLERequestContext& ctx) {
423 LOG_WARNING(Service_LDN, "(STUBBED) called"); 436 LOG_INFO(Service_LDN, "called");
424 437
425 IPC::ResponseBuilder rb{ctx, 2}; 438 IPC::ResponseBuilder rb{ctx, 2};
426 rb.Push(ResultSuccess); 439 rb.Push(lan_discovery.OpenStation());
427 } 440 }
428 441
429 void CloseStation(Kernel::HLERequestContext& ctx) { 442 void CloseStation(Kernel::HLERequestContext& ctx) {
430 LOG_WARNING(Service_LDN, "(STUBBED) called"); 443 LOG_INFO(Service_LDN, "called");
431 444
432 IPC::ResponseBuilder rb{ctx, 2}; 445 IPC::ResponseBuilder rb{ctx, 2};
433 rb.Push(ResultSuccess); 446 rb.Push(lan_discovery.CloseStation());
434 } 447 }
435 448
436 void Connect(Kernel::HLERequestContext& ctx) { 449 void Connect(Kernel::HLERequestContext& ctx) {
@@ -445,16 +458,13 @@ public:
445 458
446 const auto parameters{rp.PopRaw<Parameters>()}; 459 const auto parameters{rp.PopRaw<Parameters>()};
447 460
448 LOG_WARNING(Service_LDN, 461 LOG_INFO(Service_LDN,
449 "(STUBBED) called, passphrase_size={}, security_mode={}, " 462 "called, passphrase_size={}, security_mode={}, "
450 "local_communication_version={}", 463 "local_communication_version={}",
451 parameters.security_config.passphrase_size, 464 parameters.security_config.passphrase_size,
452 parameters.security_config.security_mode, 465 parameters.security_config.security_mode, parameters.local_communication_version);
453 parameters.local_communication_version);
454 466
455 const std::vector<u8> read_buffer = ctx.ReadBuffer(); 467 const std::vector<u8> read_buffer = ctx.ReadBuffer();
456 NetworkInfo network_info{};
457
458 if (read_buffer.size() != sizeof(NetworkInfo)) { 468 if (read_buffer.size() != sizeof(NetworkInfo)) {
459 LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!"); 469 LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!");
460 IPC::ResponseBuilder rb{ctx, 2}; 470 IPC::ResponseBuilder rb{ctx, 2};
@@ -462,40 +472,47 @@ public:
462 return; 472 return;
463 } 473 }
464 474
475 NetworkInfo network_info{};
465 std::memcpy(&network_info, read_buffer.data(), read_buffer.size()); 476 std::memcpy(&network_info, read_buffer.data(), read_buffer.size());
466 477
467 IPC::ResponseBuilder rb{ctx, 2}; 478 IPC::ResponseBuilder rb{ctx, 2};
468 rb.Push(ResultSuccess); 479 rb.Push(lan_discovery.Connect(network_info, parameters.user_config,
480 static_cast<u16>(parameters.local_communication_version)));
469 } 481 }
470 482
471 void Disconnect(Kernel::HLERequestContext& ctx) { 483 void Disconnect(Kernel::HLERequestContext& ctx) {
472 LOG_WARNING(Service_LDN, "(STUBBED) called"); 484 LOG_INFO(Service_LDN, "called");
473 485
474 IPC::ResponseBuilder rb{ctx, 2}; 486 IPC::ResponseBuilder rb{ctx, 2};
475 rb.Push(ResultSuccess); 487 rb.Push(lan_discovery.Disconnect());
476 } 488 }
477 void Initialize(Kernel::HLERequestContext& ctx) {
478 LOG_WARNING(Service_LDN, "(STUBBED) called");
479 489
490 void Initialize(Kernel::HLERequestContext& ctx) {
480 const auto rc = InitializeImpl(ctx); 491 const auto rc = InitializeImpl(ctx);
492 if (rc.IsError()) {
493 LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
494 }
481 495
482 IPC::ResponseBuilder rb{ctx, 2}; 496 IPC::ResponseBuilder rb{ctx, 2};
483 rb.Push(rc); 497 rb.Push(rc);
484 } 498 }
485 499
486 void Finalize(Kernel::HLERequestContext& ctx) { 500 void Finalize(Kernel::HLERequestContext& ctx) {
487 LOG_WARNING(Service_LDN, "(STUBBED) called"); 501 if (auto room_member = room_network.GetRoomMember().lock()) {
502 room_member->Unbind(ldn_packet_received);
503 }
488 504
489 is_initialized = false; 505 is_initialized = false;
490 506
491 IPC::ResponseBuilder rb{ctx, 2}; 507 IPC::ResponseBuilder rb{ctx, 2};
492 rb.Push(ResultSuccess); 508 rb.Push(lan_discovery.Finalize());
493 } 509 }
494 510
495 void Initialize2(Kernel::HLERequestContext& ctx) { 511 void Initialize2(Kernel::HLERequestContext& ctx) {
496 LOG_WARNING(Service_LDN, "(STUBBED) called");
497
498 const auto rc = InitializeImpl(ctx); 512 const auto rc = InitializeImpl(ctx);
513 if (rc.IsError()) {
514 LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
515 }
499 516
500 IPC::ResponseBuilder rb{ctx, 2}; 517 IPC::ResponseBuilder rb{ctx, 2};
501 rb.Push(rc); 518 rb.Push(rc);
@@ -508,14 +525,26 @@ public:
508 return ResultAirplaneModeEnabled; 525 return ResultAirplaneModeEnabled;
509 } 526 }
510 527
528 if (auto room_member = room_network.GetRoomMember().lock()) {
529 ldn_packet_received = room_member->BindOnLdnPacketReceived(
530 [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
531 } else {
532 LOG_ERROR(Service_LDN, "Couldn't bind callback!");
533 return ResultAirplaneModeEnabled;
534 }
535
536 lan_discovery.Initialize([&]() { OnEventFired(); });
511 is_initialized = true; 537 is_initialized = true;
512 // TODO (flTobi): Change this to ResultSuccess when LDN is fully implemented 538 return ResultSuccess;
513 return ResultAirplaneModeEnabled;
514 } 539 }
515 540
516 KernelHelpers::ServiceContext service_context; 541 KernelHelpers::ServiceContext service_context;
517 Kernel::KEvent* state_change_event; 542 Kernel::KEvent* state_change_event;
518 Network::RoomNetwork& room_network; 543 Network::RoomNetwork& room_network;
544 LANDiscovery lan_discovery;
545
546 // Callback identifier for the OnLDNPacketReceived event.
547 Network::RoomMember::CallbackHandle<Network::LDNPacket> ldn_packet_received;
519 548
520 bool is_initialized{}; 549 bool is_initialized{};
521}; 550};
diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h
index 6231e936d..44c2c773b 100644
--- a/src/core/hle/service/ldn/ldn_types.h
+++ b/src/core/hle/service/ldn/ldn_types.h
@@ -31,6 +31,8 @@ enum class NodeStateChange : u8 {
31 DisconnectAndConnect, 31 DisconnectAndConnect,
32}; 32};
33 33
34DECLARE_ENUM_FLAG_OPERATORS(NodeStateChange)
35
34enum class ScanFilterFlag : u32 { 36enum class ScanFilterFlag : u32 {
35 None = 0, 37 None = 0,
36 LocalCommunicationId = 1 << 0, 38 LocalCommunicationId = 1 << 0,
@@ -100,13 +102,13 @@ enum class AcceptPolicy : u8 {
100 102
101enum class WifiChannel : s16 { 103enum class WifiChannel : s16 {
102 Default = 0, 104 Default = 0,
103 wifi24_1 = 1, 105 Wifi24_1 = 1,
104 wifi24_6 = 6, 106 Wifi24_6 = 6,
105 wifi24_11 = 11, 107 Wifi24_11 = 11,
106 wifi50_36 = 36, 108 Wifi50_36 = 36,
107 wifi50_40 = 40, 109 Wifi50_40 = 40,
108 wifi50_44 = 44, 110 Wifi50_44 = 44,
109 wifi50_48 = 48, 111 Wifi50_48 = 48,
110}; 112};
111 113
112enum class LinkLevel : s8 { 114enum class LinkLevel : s8 {
@@ -116,6 +118,11 @@ enum class LinkLevel : s8 {
116 Excellent, 118 Excellent,
117}; 119};
118 120
121enum class NodeStatus : u8 {
122 Disconnected,
123 Connected,
124};
125
119struct NodeLatestUpdate { 126struct NodeLatestUpdate {
120 NodeStateChange state_change; 127 NodeStateChange state_change;
121 INSERT_PADDING_BYTES(0x7); // Unknown 128 INSERT_PADDING_BYTES(0x7); // Unknown
@@ -150,7 +157,7 @@ struct Ssid {
150 157
151 Ssid() = default; 158 Ssid() = default;
152 159
153 explicit Ssid(std::string_view data) { 160 constexpr explicit Ssid(std::string_view data) {
154 length = static_cast<u8>(std::min(data.size(), SsidLengthMax)); 161 length = static_cast<u8>(std::min(data.size(), SsidLengthMax));
155 data.copy(raw.data(), length); 162 data.copy(raw.data(), length);
156 raw[length] = 0; 163 raw[length] = 0;
@@ -159,19 +166,18 @@ struct Ssid {
159 std::string GetStringValue() const { 166 std::string GetStringValue() const {
160 return std::string(raw.data()); 167 return std::string(raw.data());
161 } 168 }
162};
163static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
164 169
165struct Ipv4Address { 170 bool operator==(const Ssid& b) const {
166 union { 171 return (length == b.length) && (std::memcmp(raw.data(), b.raw.data(), length) == 0);
167 u32 raw{}; 172 }
168 std::array<u8, 4> bytes;
169 };
170 173
171 std::string GetStringValue() const { 174 bool operator!=(const Ssid& b) const {
172 return fmt::format("{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]); 175 return !operator==(b);
173 } 176 }
174}; 177};
178static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
179
180using Ipv4Address = std::array<u8, 4>;
175static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size"); 181static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size");
176 182
177struct MacAddress { 183struct MacAddress {
@@ -181,6 +187,14 @@ struct MacAddress {
181}; 187};
182static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size"); 188static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size");
183 189
190struct MACAddressHash {
191 size_t operator()(const MacAddress& address) const {
192 u64 value{};
193 std::memcpy(&value, address.raw.data(), sizeof(address.raw));
194 return value;
195 }
196};
197
184struct ScanFilter { 198struct ScanFilter {
185 NetworkId network_id; 199 NetworkId network_id;
186 NetworkType network_type; 200 NetworkType network_type;
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 9b382bf56..93057e800 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -22,6 +22,7 @@
22#include "core/hle/service/nvflinger/ui/graphic_buffer.h" 22#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
23#include "core/hle/service/vi/display/vi_display.h" 23#include "core/hle/service/vi/display/vi_display.h"
24#include "core/hle/service/vi/layer/vi_layer.h" 24#include "core/hle/service/vi/layer/vi_layer.h"
25#include "core/hle/service/vi/vi_results.h"
25#include "video_core/gpu.h" 26#include "video_core/gpu.h"
26 27
27namespace Service::NVFlinger { 28namespace Service::NVFlinger {
@@ -163,15 +164,15 @@ std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) {
163 return layer->GetBinderId(); 164 return layer->GetBinderId();
164} 165}
165 166
166Kernel::KReadableEvent* NVFlinger::FindVsyncEvent(u64 display_id) { 167ResultVal<Kernel::KReadableEvent*> NVFlinger::FindVsyncEvent(u64 display_id) {
167 const auto lock_guard = Lock(); 168 const auto lock_guard = Lock();
168 auto* const display = FindDisplay(display_id); 169 auto* const display = FindDisplay(display_id);
169 170
170 if (display == nullptr) { 171 if (display == nullptr) {
171 return nullptr; 172 return VI::ResultNotFound;
172 } 173 }
173 174
174 return &display->GetVSyncEvent(); 175 return display->GetVSyncEvent();
175} 176}
176 177
177VI::Display* NVFlinger::FindDisplay(u64 display_id) { 178VI::Display* NVFlinger::FindDisplay(u64 display_id) {
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 044ac6ac8..3bbe5d92b 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -11,6 +11,7 @@
11#include <vector> 11#include <vector>
12 12
13#include "common/common_types.h" 13#include "common/common_types.h"
14#include "core/hle/result.h"
14#include "core/hle/service/kernel_helpers.h" 15#include "core/hle/service/kernel_helpers.h"
15 16
16namespace Common { 17namespace Common {
@@ -71,8 +72,9 @@ public:
71 72
72 /// Gets the vsync event for the specified display. 73 /// Gets the vsync event for the specified display.
73 /// 74 ///
74 /// If an invalid display ID is provided, then nullptr is returned. 75 /// If an invalid display ID is provided, then VI::ResultNotFound is returned.
75 [[nodiscard]] Kernel::KReadableEvent* FindVsyncEvent(u64 display_id); 76 /// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned.
77 [[nodiscard]] ResultVal<Kernel::KReadableEvent*> FindVsyncEvent(u64 display_id);
76 78
77 /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when 79 /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
78 /// finished. 80 /// finished.
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index b34febb50..aa49aa775 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -19,6 +19,7 @@
19#include "core/hle/service/nvflinger/hos_binder_driver_server.h" 19#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
20#include "core/hle/service/vi/display/vi_display.h" 20#include "core/hle/service/vi/display/vi_display.h"
21#include "core/hle/service/vi/layer/vi_layer.h" 21#include "core/hle/service/vi/layer/vi_layer.h"
22#include "core/hle/service/vi/vi_results.h"
22 23
23namespace Service::VI { 24namespace Service::VI {
24 25
@@ -55,8 +56,18 @@ const Layer& Display::GetLayer(std::size_t index) const {
55 return *layers.at(index); 56 return *layers.at(index);
56} 57}
57 58
58Kernel::KReadableEvent& Display::GetVSyncEvent() { 59ResultVal<Kernel::KReadableEvent*> Display::GetVSyncEvent() {
59 return vsync_event->GetReadableEvent(); 60 if (got_vsync_event) {
61 return ResultPermissionDenied;
62 }
63
64 got_vsync_event = true;
65
66 return GetVSyncEventUnchecked();
67}
68
69Kernel::KReadableEvent* Display::GetVSyncEventUnchecked() {
70 return &vsync_event->GetReadableEvent();
60} 71}
61 72
62void Display::SignalVSyncEvent() { 73void Display::SignalVSyncEvent() {
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
index 3838bb599..8dbb0ef80 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -9,6 +9,7 @@
9 9
10#include "common/common_funcs.h" 10#include "common/common_funcs.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "core/hle/result.h"
12 13
13namespace Kernel { 14namespace Kernel {
14class KEvent; 15class KEvent;
@@ -73,8 +74,16 @@ public:
73 return layers.size(); 74 return layers.size();
74 } 75 }
75 76
76 /// Gets the readable vsync event. 77 /**
77 Kernel::KReadableEvent& GetVSyncEvent(); 78 * Gets the internal vsync event.
79 *
80 * @returns The internal Vsync event if it has not yet been retrieved,
81 * VI::ResultPermissionDenied otherwise.
82 */
83 [[nodiscard]] ResultVal<Kernel::KReadableEvent*> GetVSyncEvent();
84
85 /// Gets the internal vsync event.
86 Kernel::KReadableEvent* GetVSyncEventUnchecked();
78 87
79 /// Signals the internal vsync event. 88 /// Signals the internal vsync event.
80 void SignalVSyncEvent(); 89 void SignalVSyncEvent();
@@ -118,6 +127,7 @@ private:
118 127
119 std::vector<std::unique_ptr<Layer>> layers; 128 std::vector<std::unique_ptr<Layer>> layers;
120 Kernel::KEvent* vsync_event{}; 129 Kernel::KEvent* vsync_event{};
130 bool got_vsync_event{false};
121}; 131};
122 132
123} // namespace Service::VI 133} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 546879648..f083811ec 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -29,16 +29,12 @@
29#include "core/hle/service/service.h" 29#include "core/hle/service/service.h"
30#include "core/hle/service/vi/vi.h" 30#include "core/hle/service/vi/vi.h"
31#include "core/hle/service/vi/vi_m.h" 31#include "core/hle/service/vi/vi_m.h"
32#include "core/hle/service/vi/vi_results.h"
32#include "core/hle/service/vi/vi_s.h" 33#include "core/hle/service/vi/vi_s.h"
33#include "core/hle/service/vi/vi_u.h" 34#include "core/hle/service/vi/vi_u.h"
34 35
35namespace Service::VI { 36namespace Service::VI {
36 37
37constexpr Result ERR_OPERATION_FAILED{ErrorModule::VI, 1};
38constexpr Result ERR_PERMISSION_DENIED{ErrorModule::VI, 5};
39constexpr Result ERR_UNSUPPORTED{ErrorModule::VI, 6};
40constexpr Result ERR_NOT_FOUND{ErrorModule::VI, 7};
41
42struct DisplayInfo { 38struct DisplayInfo {
43 /// The name of this particular display. 39 /// The name of this particular display.
44 char display_name[0x40]{"Default"}; 40 char display_name[0x40]{"Default"};
@@ -348,7 +344,7 @@ private:
348 if (!layer_id) { 344 if (!layer_id) {
349 LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display); 345 LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display);
350 IPC::ResponseBuilder rb{ctx, 2}; 346 IPC::ResponseBuilder rb{ctx, 2};
351 rb.Push(ERR_NOT_FOUND); 347 rb.Push(ResultNotFound);
352 return; 348 return;
353 } 349 }
354 350
@@ -498,7 +494,7 @@ private:
498 if (!display_id) { 494 if (!display_id) {
499 LOG_ERROR(Service_VI, "Display not found! display_name={}", name); 495 LOG_ERROR(Service_VI, "Display not found! display_name={}", name);
500 IPC::ResponseBuilder rb{ctx, 2}; 496 IPC::ResponseBuilder rb{ctx, 2};
501 rb.Push(ERR_NOT_FOUND); 497 rb.Push(ResultNotFound);
502 return; 498 return;
503 } 499 }
504 500
@@ -554,14 +550,14 @@ private:
554 550
555 if (scaling_mode > NintendoScaleMode::PreserveAspectRatio) { 551 if (scaling_mode > NintendoScaleMode::PreserveAspectRatio) {
556 LOG_ERROR(Service_VI, "Invalid scaling mode provided."); 552 LOG_ERROR(Service_VI, "Invalid scaling mode provided.");
557 rb.Push(ERR_OPERATION_FAILED); 553 rb.Push(ResultOperationFailed);
558 return; 554 return;
559 } 555 }
560 556
561 if (scaling_mode != NintendoScaleMode::ScaleToWindow && 557 if (scaling_mode != NintendoScaleMode::ScaleToWindow &&
562 scaling_mode != NintendoScaleMode::PreserveAspectRatio) { 558 scaling_mode != NintendoScaleMode::PreserveAspectRatio) {
563 LOG_ERROR(Service_VI, "Unsupported scaling mode supplied."); 559 LOG_ERROR(Service_VI, "Unsupported scaling mode supplied.");
564 rb.Push(ERR_UNSUPPORTED); 560 rb.Push(ResultNotSupported);
565 return; 561 return;
566 } 562 }
567 563
@@ -594,7 +590,7 @@ private:
594 if (!display_id) { 590 if (!display_id) {
595 LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id); 591 LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id);
596 IPC::ResponseBuilder rb{ctx, 2}; 592 IPC::ResponseBuilder rb{ctx, 2};
597 rb.Push(ERR_NOT_FOUND); 593 rb.Push(ResultNotFound);
598 return; 594 return;
599 } 595 }
600 596
@@ -602,7 +598,7 @@ private:
602 if (!buffer_queue_id) { 598 if (!buffer_queue_id) {
603 LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id); 599 LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id);
604 IPC::ResponseBuilder rb{ctx, 2}; 600 IPC::ResponseBuilder rb{ctx, 2};
605 rb.Push(ERR_NOT_FOUND); 601 rb.Push(ResultNotFound);
606 return; 602 return;
607 } 603 }
608 604
@@ -640,7 +636,7 @@ private:
640 if (!layer_id) { 636 if (!layer_id) {
641 LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id); 637 LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id);
642 IPC::ResponseBuilder rb{ctx, 2}; 638 IPC::ResponseBuilder rb{ctx, 2};
643 rb.Push(ERR_NOT_FOUND); 639 rb.Push(ResultNotFound);
644 return; 640 return;
645 } 641 }
646 642
@@ -648,7 +644,7 @@ private:
648 if (!buffer_queue_id) { 644 if (!buffer_queue_id) {
649 LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id); 645 LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id);
650 IPC::ResponseBuilder rb{ctx, 2}; 646 IPC::ResponseBuilder rb{ctx, 2};
651 rb.Push(ERR_NOT_FOUND); 647 rb.Push(ResultNotFound);
652 return; 648 return;
653 } 649 }
654 650
@@ -675,19 +671,23 @@ private:
675 IPC::RequestParser rp{ctx}; 671 IPC::RequestParser rp{ctx};
676 const u64 display_id = rp.Pop<u64>(); 672 const u64 display_id = rp.Pop<u64>();
677 673
678 LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id); 674 LOG_DEBUG(Service_VI, "called. display_id={}", display_id);
679 675
680 const auto vsync_event = nv_flinger.FindVsyncEvent(display_id); 676 const auto vsync_event = nv_flinger.FindVsyncEvent(display_id);
681 if (!vsync_event) { 677 if (vsync_event.Failed()) {
682 LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id); 678 const auto result = vsync_event.Code();
679 if (result == ResultNotFound) {
680 LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
681 }
682
683 IPC::ResponseBuilder rb{ctx, 2}; 683 IPC::ResponseBuilder rb{ctx, 2};
684 rb.Push(ERR_NOT_FOUND); 684 rb.Push(result);
685 return; 685 return;
686 } 686 }
687 687
688 IPC::ResponseBuilder rb{ctx, 2, 1}; 688 IPC::ResponseBuilder rb{ctx, 2, 1};
689 rb.Push(ResultSuccess); 689 rb.Push(ResultSuccess);
690 rb.PushCopyObjects(vsync_event); 690 rb.PushCopyObjects(*vsync_event);
691 } 691 }
692 692
693 void ConvertScalingMode(Kernel::HLERequestContext& ctx) { 693 void ConvertScalingMode(Kernel::HLERequestContext& ctx) {
@@ -764,7 +764,7 @@ private:
764 return ConvertedScaleMode::PreserveAspectRatio; 764 return ConvertedScaleMode::PreserveAspectRatio;
765 default: 765 default:
766 LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode); 766 LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode);
767 return ERR_OPERATION_FAILED; 767 return ResultOperationFailed;
768 } 768 }
769 } 769 }
770 770
@@ -794,7 +794,7 @@ void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System&
794 if (!IsValidServiceAccess(permission, policy)) { 794 if (!IsValidServiceAccess(permission, policy)) {
795 LOG_ERROR(Service_VI, "Permission denied for policy {}", policy); 795 LOG_ERROR(Service_VI, "Permission denied for policy {}", policy);
796 IPC::ResponseBuilder rb{ctx, 2}; 796 IPC::ResponseBuilder rb{ctx, 2};
797 rb.Push(ERR_PERMISSION_DENIED); 797 rb.Push(ResultPermissionDenied);
798 return; 798 return;
799 } 799 }
800 800
diff --git a/src/core/hle/service/vi/vi_results.h b/src/core/hle/service/vi/vi_results.h
new file mode 100644
index 000000000..a46c247d2
--- /dev/null
+++ b/src/core/hle/service/vi/vi_results.h
@@ -0,0 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/result.h"
5
6namespace Service::VI {
7
8constexpr Result ResultOperationFailed{ErrorModule::VI, 1};
9constexpr Result ResultPermissionDenied{ErrorModule::VI, 5};
10constexpr Result ResultNotSupported{ErrorModule::VI, 6};
11constexpr Result ResultNotFound{ErrorModule::VI, 7};
12
13} // namespace Service::VI
diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp
index 0f0a66160..057fd3661 100644
--- a/src/core/internal_network/network_interface.cpp
+++ b/src/core/internal_network/network_interface.cpp
@@ -188,7 +188,7 @@ std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
188std::optional<NetworkInterface> GetSelectedNetworkInterface() { 188std::optional<NetworkInterface> GetSelectedNetworkInterface() {
189 const auto& selected_network_interface = Settings::values.network_interface.GetValue(); 189 const auto& selected_network_interface = Settings::values.network_interface.GetValue();
190 const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); 190 const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
191 if (network_interfaces.size() == 0) { 191 if (network_interfaces.empty()) {
192 LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces"); 192 LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
193 return std::nullopt; 193 return std::nullopt;
194 } 194 }
@@ -206,4 +206,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() {
206 return *res; 206 return *res;
207} 207}
208 208
209void SelectFirstNetworkInterface() {
210 const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
211
212 if (network_interfaces.empty()) {
213 return;
214 }
215
216 Settings::values.network_interface.SetValue(network_interfaces[0].name);
217}
218
209} // namespace Network 219} // namespace Network
diff --git a/src/core/internal_network/network_interface.h b/src/core/internal_network/network_interface.h
index 9b98b6b42..175e61b1f 100644
--- a/src/core/internal_network/network_interface.h
+++ b/src/core/internal_network/network_interface.h
@@ -24,5 +24,6 @@ struct NetworkInterface {
24 24
25std::vector<NetworkInterface> GetAvailableNetworkInterfaces(); 25std::vector<NetworkInterface> GetAvailableNetworkInterfaces();
26std::optional<NetworkInterface> GetSelectedNetworkInterface(); 26std::optional<NetworkInterface> GetSelectedNetworkInterface();
27void SelectFirstNetworkInterface();
27 28
28} // namespace Network 29} // namespace Network
diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp
index 0c746bd82..7d5d37bbc 100644
--- a/src/core/internal_network/socket_proxy.cpp
+++ b/src/core/internal_network/socket_proxy.cpp
@@ -6,6 +6,7 @@
6 6
7#include "common/assert.h" 7#include "common/assert.h"
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "common/zstd_compression.h"
9#include "core/internal_network/network.h" 10#include "core/internal_network/network.h"
10#include "core/internal_network/network_interface.h" 11#include "core/internal_network/network_interface.h"
11#include "core/internal_network/socket_proxy.h" 12#include "core/internal_network/socket_proxy.h"
@@ -32,8 +33,11 @@ void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) {
32 return; 33 return;
33 } 34 }
34 35
36 auto decompressed = packet;
37 decompressed.data = Common::Compression::DecompressDataZSTD(packet.data);
38
35 std::lock_guard guard(packets_mutex); 39 std::lock_guard guard(packets_mutex);
36 received_packets.push(packet); 40 received_packets.push(decompressed);
37} 41}
38 42
39template <typename T> 43template <typename T>
@@ -185,6 +189,8 @@ std::pair<s32, Errno> ProxySocket::Send(const std::vector<u8>& message, int flag
185void ProxySocket::SendPacket(ProxyPacket& packet) { 189void ProxySocket::SendPacket(ProxyPacket& packet) {
186 if (auto room_member = room_network.GetRoomMember().lock()) { 190 if (auto room_member = room_network.GetRoomMember().lock()) {
187 if (room_member->IsConnected()) { 191 if (room_member->IsConnected()) {
192 packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(),
193 packet.data.size());
188 room_member->SendProxyPacket(packet); 194 room_member->SendProxyPacket(packet);
189 } 195 }
190 } 196 }
diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp
index 7b6deba41..359891883 100644
--- a/src/dedicated_room/yuzu_room.cpp
+++ b/src/dedicated_room/yuzu_room.cpp
@@ -76,7 +76,18 @@ static constexpr char BanListMagic[] = "YuzuRoom-BanList-1";
76static constexpr char token_delimiter{':'}; 76static constexpr char token_delimiter{':'};
77 77
78static void PadToken(std::string& token) { 78static void PadToken(std::string& token) {
79 while (token.size() % 4 != 0) { 79 std::size_t outlen = 0;
80
81 std::array<unsigned char, 512> output{};
82 std::array<unsigned char, 2048> roundtrip{};
83 for (size_t i = 0; i < 3; i++) {
84 mbedtls_base64_decode(output.data(), output.size(), &outlen,
85 reinterpret_cast<const unsigned char*>(token.c_str()),
86 token.length());
87 mbedtls_base64_encode(roundtrip.data(), roundtrip.size(), &outlen, output.data(), outlen);
88 if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) {
89 break;
90 }
80 token.push_back('='); 91 token.push_back('=');
81 } 92 }
82} 93}
diff --git a/src/network/room.cpp b/src/network/room.cpp
index 8c63b255b..dc5dbce7f 100644
--- a/src/network/room.cpp
+++ b/src/network/room.cpp
@@ -212,6 +212,12 @@ public:
212 void HandleProxyPacket(const ENetEvent* event); 212 void HandleProxyPacket(const ENetEvent* event);
213 213
214 /** 214 /**
215 * Broadcasts this packet to all members except the sender.
216 * @param event The ENet event containing the data
217 */
218 void HandleLdnPacket(const ENetEvent* event);
219
220 /**
215 * Extracts a chat entry from a received ENet packet and adds it to the chat queue. 221 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
216 * @param event The ENet event that was received. 222 * @param event The ENet event that was received.
217 */ 223 */
@@ -247,6 +253,9 @@ void Room::RoomImpl::ServerLoop() {
247 case IdProxyPacket: 253 case IdProxyPacket:
248 HandleProxyPacket(&event); 254 HandleProxyPacket(&event);
249 break; 255 break;
256 case IdLdnPacket:
257 HandleLdnPacket(&event);
258 break;
250 case IdChatMessage: 259 case IdChatMessage:
251 HandleChatPacket(&event); 260 HandleChatPacket(&event);
252 break; 261 break;
@@ -861,6 +870,60 @@ void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) {
861 enet_host_flush(server); 870 enet_host_flush(server);
862} 871}
863 872
873void Room::RoomImpl::HandleLdnPacket(const ENetEvent* event) {
874 Packet in_packet;
875 in_packet.Append(event->packet->data, event->packet->dataLength);
876
877 in_packet.IgnoreBytes(sizeof(u8)); // Message type
878
879 in_packet.IgnoreBytes(sizeof(u8)); // LAN packet type
880 in_packet.IgnoreBytes(sizeof(IPv4Address)); // Local IP
881
882 IPv4Address remote_ip;
883 in_packet.Read(remote_ip); // Remote IP
884
885 bool broadcast;
886 in_packet.Read(broadcast); // Broadcast
887
888 Packet out_packet;
889 out_packet.Append(event->packet->data, event->packet->dataLength);
890 ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
891 ENET_PACKET_FLAG_RELIABLE);
892
893 const auto& destination_address = remote_ip;
894 if (broadcast) { // Send the data to everyone except the sender
895 std::lock_guard lock(member_mutex);
896 bool sent_packet = false;
897 for (const auto& member : members) {
898 if (member.peer != event->peer) {
899 sent_packet = true;
900 enet_peer_send(member.peer, 0, enet_packet);
901 }
902 }
903
904 if (!sent_packet) {
905 enet_packet_destroy(enet_packet);
906 }
907 } else {
908 std::lock_guard lock(member_mutex);
909 auto member = std::find_if(members.begin(), members.end(),
910 [destination_address](const Member& member_entry) -> bool {
911 return member_entry.fake_ip == destination_address;
912 });
913 if (member != members.end()) {
914 enet_peer_send(member->peer, 0, enet_packet);
915 } else {
916 LOG_ERROR(Network,
917 "Attempting to send to unknown IP address: "
918 "{}.{}.{}.{}",
919 destination_address[0], destination_address[1], destination_address[2],
920 destination_address[3]);
921 enet_packet_destroy(enet_packet);
922 }
923 }
924 enet_host_flush(server);
925}
926
864void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { 927void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
865 Packet in_packet; 928 Packet in_packet;
866 in_packet.Append(event->packet->data, event->packet->dataLength); 929 in_packet.Append(event->packet->data, event->packet->dataLength);
diff --git a/src/network/room.h b/src/network/room.h
index c2a4b1a70..edbd3ecfb 100644
--- a/src/network/room.h
+++ b/src/network/room.h
@@ -40,6 +40,7 @@ enum RoomMessageTypes : u8 {
40 IdRoomInformation, 40 IdRoomInformation,
41 IdSetGameInfo, 41 IdSetGameInfo,
42 IdProxyPacket, 42 IdProxyPacket,
43 IdLdnPacket,
43 IdChatMessage, 44 IdChatMessage,
44 IdNameCollision, 45 IdNameCollision,
45 IdIpCollision, 46 IdIpCollision,
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
index 06818af78..b94cb24ad 100644
--- a/src/network/room_member.cpp
+++ b/src/network/room_member.cpp
@@ -58,6 +58,7 @@ public:
58 58
59 private: 59 private:
60 CallbackSet<ProxyPacket> callback_set_proxy_packet; 60 CallbackSet<ProxyPacket> callback_set_proxy_packet;
61 CallbackSet<LDNPacket> callback_set_ldn_packet;
61 CallbackSet<ChatEntry> callback_set_chat_messages; 62 CallbackSet<ChatEntry> callback_set_chat_messages;
62 CallbackSet<StatusMessageEntry> callback_set_status_messages; 63 CallbackSet<StatusMessageEntry> callback_set_status_messages;
63 CallbackSet<RoomInformation> callback_set_room_information; 64 CallbackSet<RoomInformation> callback_set_room_information;
@@ -108,6 +109,12 @@ public:
108 void HandleProxyPackets(const ENetEvent* event); 109 void HandleProxyPackets(const ENetEvent* event);
109 110
110 /** 111 /**
112 * Extracts an LdnPacket from a received ENet packet.
113 * @param event The ENet event that was received.
114 */
115 void HandleLdnPackets(const ENetEvent* event);
116
117 /**
111 * Extracts a chat entry from a received ENet packet and adds it to the chat queue. 118 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
112 * @param event The ENet event that was received. 119 * @param event The ENet event that was received.
113 */ 120 */
@@ -166,6 +173,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
166 case IdProxyPacket: 173 case IdProxyPacket:
167 HandleProxyPackets(&event); 174 HandleProxyPackets(&event);
168 break; 175 break;
176 case IdLdnPacket:
177 HandleLdnPackets(&event);
178 break;
169 case IdChatMessage: 179 case IdChatMessage:
170 HandleChatPacket(&event); 180 HandleChatPacket(&event);
171 break; 181 break;
@@ -372,6 +382,27 @@ void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) {
372 Invoke<ProxyPacket>(proxy_packet); 382 Invoke<ProxyPacket>(proxy_packet);
373} 383}
374 384
385void RoomMember::RoomMemberImpl::HandleLdnPackets(const ENetEvent* event) {
386 LDNPacket ldn_packet{};
387 Packet packet;
388 packet.Append(event->packet->data, event->packet->dataLength);
389
390 // Ignore the first byte, which is the message id.
391 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
392
393 u8 packet_type;
394 packet.Read(packet_type);
395 ldn_packet.type = static_cast<LDNPacketType>(packet_type);
396
397 packet.Read(ldn_packet.local_ip);
398 packet.Read(ldn_packet.remote_ip);
399 packet.Read(ldn_packet.broadcast);
400
401 packet.Read(ldn_packet.data);
402
403 Invoke<LDNPacket>(ldn_packet);
404}
405
375void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { 406void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
376 Packet packet; 407 Packet packet;
377 packet.Append(event->packet->data, event->packet->dataLength); 408 packet.Append(event->packet->data, event->packet->dataLength);
@@ -450,6 +481,11 @@ RoomMember::RoomMemberImpl::CallbackSet<ProxyPacket>& RoomMember::RoomMemberImpl
450} 481}
451 482
452template <> 483template <>
484RoomMember::RoomMemberImpl::CallbackSet<LDNPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
485 return callback_set_ldn_packet;
486}
487
488template <>
453RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>& 489RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>&
454RoomMember::RoomMemberImpl::Callbacks::Get() { 490RoomMember::RoomMemberImpl::Callbacks::Get() {
455 return callback_set_state; 491 return callback_set_state;
@@ -607,6 +643,21 @@ void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) {
607 room_member_impl->Send(std::move(packet)); 643 room_member_impl->Send(std::move(packet));
608} 644}
609 645
646void RoomMember::SendLdnPacket(const LDNPacket& ldn_packet) {
647 Packet packet;
648 packet.Write(static_cast<u8>(IdLdnPacket));
649
650 packet.Write(static_cast<u8>(ldn_packet.type));
651
652 packet.Write(ldn_packet.local_ip);
653 packet.Write(ldn_packet.remote_ip);
654 packet.Write(ldn_packet.broadcast);
655
656 packet.Write(ldn_packet.data);
657
658 room_member_impl->Send(std::move(packet));
659}
660
610void RoomMember::SendChatMessage(const std::string& message) { 661void RoomMember::SendChatMessage(const std::string& message) {
611 Packet packet; 662 Packet packet;
612 packet.Write(static_cast<u8>(IdChatMessage)); 663 packet.Write(static_cast<u8>(IdChatMessage));
@@ -663,6 +714,11 @@ RoomMember::CallbackHandle<ProxyPacket> RoomMember::BindOnProxyPacketReceived(
663 return room_member_impl->Bind(callback); 714 return room_member_impl->Bind(callback);
664} 715}
665 716
717RoomMember::CallbackHandle<LDNPacket> RoomMember::BindOnLdnPacketReceived(
718 std::function<void(const LDNPacket&)> callback) {
719 return room_member_impl->Bind(std::move(callback));
720}
721
666RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged( 722RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged(
667 std::function<void(const RoomInformation&)> callback) { 723 std::function<void(const RoomInformation&)> callback) {
668 return room_member_impl->Bind(callback); 724 return room_member_impl->Bind(callback);
@@ -699,6 +755,7 @@ void RoomMember::Leave() {
699} 755}
700 756
701template void RoomMember::Unbind(CallbackHandle<ProxyPacket>); 757template void RoomMember::Unbind(CallbackHandle<ProxyPacket>);
758template void RoomMember::Unbind(CallbackHandle<LDNPacket>);
702template void RoomMember::Unbind(CallbackHandle<RoomMember::State>); 759template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
703template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>); 760template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
704template void RoomMember::Unbind(CallbackHandle<RoomInformation>); 761template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
diff --git a/src/network/room_member.h b/src/network/room_member.h
index f578f7f6a..0d6417294 100644
--- a/src/network/room_member.h
+++ b/src/network/room_member.h
@@ -17,7 +17,24 @@ namespace Network {
17using AnnounceMultiplayerRoom::GameInfo; 17using AnnounceMultiplayerRoom::GameInfo;
18using AnnounceMultiplayerRoom::RoomInformation; 18using AnnounceMultiplayerRoom::RoomInformation;
19 19
20/// Information about the received WiFi packets. 20enum class LDNPacketType : u8 {
21 Scan,
22 ScanResp,
23 Connect,
24 SyncNetwork,
25 Disconnect,
26 DestroyNetwork,
27};
28
29struct LDNPacket {
30 LDNPacketType type;
31 IPv4Address local_ip;
32 IPv4Address remote_ip;
33 bool broadcast;
34 std::vector<u8> data;
35};
36
37/// Information about the received proxy packets.
21struct ProxyPacket { 38struct ProxyPacket {
22 SockAddrIn local_endpoint; 39 SockAddrIn local_endpoint;
23 SockAddrIn remote_endpoint; 40 SockAddrIn remote_endpoint;
@@ -152,6 +169,12 @@ public:
152 void SendProxyPacket(const ProxyPacket& packet); 169 void SendProxyPacket(const ProxyPacket& packet);
153 170
154 /** 171 /**
172 * Sends an LDN packet to the room.
173 * @param packet The WiFi packet to send.
174 */
175 void SendLdnPacket(const LDNPacket& packet);
176
177 /**
155 * Sends a chat message to the room. 178 * Sends a chat message to the room.
156 * @param message The contents of the message. 179 * @param message The contents of the message.
157 */ 180 */
@@ -205,6 +228,16 @@ public:
205 std::function<void(const ProxyPacket&)> callback); 228 std::function<void(const ProxyPacket&)> callback);
206 229
207 /** 230 /**
231 * Binds a function to an event that will be triggered every time an LDNPacket is received.
232 * The function wil be called everytime the event is triggered.
233 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
234 * @param callback The function to call
235 * @return A handle used for removing the function from the registered list
236 */
237 CallbackHandle<LDNPacket> BindOnLdnPacketReceived(
238 std::function<void(const LDNPacket&)> callback);
239
240 /**
208 * Binds a function to an event that will be triggered every time the RoomInformation changes. 241 * Binds a function to an event that will be triggered every time the RoomInformation changes.
209 * The function wil be called every time the event is triggered. 242 * The function wil be called every time the event is triggered.
210 * The callback function must not bind or unbind a function. Doing so will cause a deadlock 243 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp
index 3441a5fe5..d608678a3 100644
--- a/src/video_core/host_shaders/astc_decoder.comp
+++ b/src/video_core/host_shaders/astc_decoder.comp
@@ -1065,7 +1065,7 @@ TexelWeightParams DecodeBlockInfo() {
1065void FillError(ivec3 coord) { 1065void FillError(ivec3 coord) {
1066 for (uint j = 0; j < block_dims.y; j++) { 1066 for (uint j = 0; j < block_dims.y; j++) {
1067 for (uint i = 0; i < block_dims.x; i++) { 1067 for (uint i = 0; i < block_dims.x; i++) {
1068 imageStore(dest_image, coord + ivec3(i, j, 0), vec4(1.0, 1.0, 0.0, 1.0)); 1068 imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0));
1069 } 1069 }
1070 } 1070 }
1071} 1071}
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index 58382755b..cabe8dcbf 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -3,6 +3,8 @@
3 3
4#include <array> 4#include <array>
5#include <vector> 5#include <vector>
6#include "common/scope_exit.h"
7#include "video_core/dirty_flags.h"
6#include "video_core/engines/maxwell_3d.h" 8#include "video_core/engines/maxwell_3d.h"
7#include "video_core/macro/macro.h" 9#include "video_core/macro/macro.h"
8#include "video_core/macro/macro_hle.h" 10#include "video_core/macro/macro_hle.h"
@@ -58,6 +60,7 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
58 maxwell3d.regs.index_array.first = parameters[3]; 60 maxwell3d.regs.index_array.first = parameters[3];
59 maxwell3d.regs.reg_array[0x446] = element_base; // vertex id base? 61 maxwell3d.regs.reg_array[0x446] = element_base; // vertex id base?
60 maxwell3d.regs.index_array.count = parameters[1]; 62 maxwell3d.regs.index_array.count = parameters[1];
63 maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
61 maxwell3d.regs.vb_element_base = element_base; 64 maxwell3d.regs.vb_element_base = element_base;
62 maxwell3d.regs.vb_base_instance = base_instance; 65 maxwell3d.regs.vb_base_instance = base_instance;
63 maxwell3d.mme_draw.instance_count = instance_count; 66 maxwell3d.mme_draw.instance_count = instance_count;
@@ -80,10 +83,67 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
80 maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined; 83 maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
81} 84}
82 85
83constexpr std::array<std::pair<u64, HLEFunction>, 3> hle_funcs{{ 86// Multidraw Indirect
87void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
88 SCOPE_EXIT({
89 // Clean everything.
90 maxwell3d.regs.reg_array[0x446] = 0x0; // vertex id base?
91 maxwell3d.regs.index_array.count = 0;
92 maxwell3d.regs.vb_element_base = 0x0;
93 maxwell3d.regs.vb_base_instance = 0x0;
94 maxwell3d.mme_draw.instance_count = 0;
95 maxwell3d.CallMethodFromMME(0x8e3, 0x640);
96 maxwell3d.CallMethodFromMME(0x8e4, 0x0);
97 maxwell3d.CallMethodFromMME(0x8e5, 0x0);
98 maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
99 maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
100 });
101 const u32 start_indirect = parameters[0];
102 const u32 end_indirect = parameters[1];
103 if (start_indirect >= end_indirect) {
104 // Nothing to do.
105 return;
106 }
107 const auto topology =
108 static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[2]);
109 maxwell3d.regs.draw.topology.Assign(topology);
110 const u32 padding = parameters[3];
111 const std::size_t max_draws = parameters[4];
112
113 const u32 indirect_words = 5 + padding;
114 const std::size_t first_draw = start_indirect;
115 const std::size_t effective_draws = end_indirect - start_indirect;
116 const std::size_t last_draw = start_indirect + std::min(effective_draws, max_draws);
117
118 for (std::size_t index = first_draw; index < last_draw; index++) {
119 const std::size_t base = index * indirect_words + 5;
120 const u32 num_vertices = parameters[base];
121 const u32 instance_count = parameters[base + 1];
122 const u32 first_index = parameters[base + 2];
123 const u32 base_vertex = parameters[base + 3];
124 const u32 base_instance = parameters[base + 4];
125 maxwell3d.regs.index_array.first = first_index;
126 maxwell3d.regs.reg_array[0x446] = base_vertex;
127 maxwell3d.regs.index_array.count = num_vertices;
128 maxwell3d.regs.vb_element_base = base_vertex;
129 maxwell3d.regs.vb_base_instance = base_instance;
130 maxwell3d.mme_draw.instance_count = instance_count;
131 maxwell3d.CallMethodFromMME(0x8e3, 0x640);
132 maxwell3d.CallMethodFromMME(0x8e4, base_vertex);
133 maxwell3d.CallMethodFromMME(0x8e5, base_instance);
134 maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
135 if (maxwell3d.ShouldExecute()) {
136 maxwell3d.Rasterizer().Draw(true, true);
137 }
138 maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
139 }
140}
141
142constexpr std::array<std::pair<u64, HLEFunction>, 4> hle_funcs{{
84 {0x771BB18C62444DA0, &HLE_771BB18C62444DA0}, 143 {0x771BB18C62444DA0, &HLE_771BB18C62444DA0},
85 {0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD}, 144 {0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD},
86 {0x0217920100488FF7, &HLE_0217920100488FF7}, 145 {0x0217920100488FF7, &HLE_0217920100488FF7},
146 {0x3F5E74B9C9A50164, &HLE_3F5E74B9C9A50164},
87}}; 147}};
88 148
89class HLEMacroImpl final : public CachedMacro { 149class HLEMacroImpl final : public CachedMacro {
@@ -99,6 +159,7 @@ private:
99 Engines::Maxwell3D& maxwell3d; 159 Engines::Maxwell3D& maxwell3d;
100 HLEFunction func; 160 HLEFunction func;
101}; 161};
162
102} // Anonymous namespace 163} // Anonymous namespace
103 164
104HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {} 165HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {}
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 32450ee1d..08f4d69ab 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -168,7 +168,7 @@ void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) {
168 if (has_unified_vertex_buffers) { 168 if (has_unified_vertex_buffers) {
169 buffer.MakeResident(GL_READ_ONLY); 169 buffer.MakeResident(GL_READ_ONLY);
170 glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset, 170 glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset,
171 static_cast<GLsizeiptr>(size)); 171 static_cast<GLsizeiptr>(Common::AlignUp(size, 4)));
172 } else { 172 } else {
173 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle()); 173 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle());
174 index_buffer_offset = offset; 174 index_buffer_offset = offset;
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp
index b159494c5..e3742ddf5 100644
--- a/src/video_core/textures/astc.cpp
+++ b/src/video_core/textures/astc.cpp
@@ -1413,7 +1413,7 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 b
1413static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) { 1413static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
1414 for (u32 j = 0; j < blockHeight; j++) { 1414 for (u32 j = 0; j < blockHeight; j++) {
1415 for (u32 i = 0; i < blockWidth; i++) { 1415 for (u32 i = 0; i < blockWidth; i++) {
1416 outBuf[j * blockWidth + i] = 0xFFFF00FF; 1416 outBuf[j * blockWidth + i] = 0x00000000;
1417 } 1417 }
1418 } 1418 }
1419} 1419}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 632f7c9c9..c63ce3a30 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -899,8 +899,8 @@ void GMainWindow::InitializeWidgets() {
899 } 899 }
900 900
901 // TODO (flTobi): Add the widget when multiplayer is fully implemented 901 // TODO (flTobi): Add the widget when multiplayer is fully implemented
902 // statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0); 902 statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
903 // statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0); 903 statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
904 904
905 tas_label = new QLabel(); 905 tas_label = new QLabel();
906 tas_label->setObjectName(QStringLiteral("TASlabel")); 906 tas_label->setObjectName(QStringLiteral("TASlabel"));
@@ -1299,6 +1299,7 @@ void GMainWindow::ConnectMenuEvents() {
1299 &MultiplayerState::OnDirectConnectToRoom); 1299 &MultiplayerState::OnDirectConnectToRoom);
1300 connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state, 1300 connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state,
1301 &MultiplayerState::OnOpenNetworkRoom); 1301 &MultiplayerState::OnOpenNetworkRoom);
1302 connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &GMainWindow::OnSaveConfig);
1302 1303
1303 // Tools 1304 // Tools
1304 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, 1305 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
@@ -1339,6 +1340,8 @@ void GMainWindow::UpdateMenuState() {
1339 } else { 1340 } else {
1340 ui->action_Pause->setText(tr("&Pause")); 1341 ui->action_Pause->setText(tr("&Pause"));
1341 } 1342 }
1343
1344 multiplayer_state->UpdateNotificationStatus();
1342} 1345}
1343 1346
1344void GMainWindow::OnDisplayTitleBars(bool show) { 1347void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -2770,6 +2773,11 @@ void GMainWindow::OnExit() {
2770 OnStopGame(); 2773 OnStopGame();
2771} 2774}
2772 2775
2776void GMainWindow::OnSaveConfig() {
2777 system->ApplySettings();
2778 config->Save();
2779}
2780
2773void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { 2781void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) {
2774 OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"), 2782 OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"),
2775 Qt::AlignLeft | Qt::AlignVCenter); 2783 Qt::AlignLeft | Qt::AlignVCenter);
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 716aef063..f7aa8e417 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -169,6 +169,7 @@ public slots:
169 void OnLoadComplete(); 169 void OnLoadComplete();
170 void OnExecuteProgram(std::size_t program_index); 170 void OnExecuteProgram(std::size_t program_index);
171 void OnExit(); 171 void OnExit();
172 void OnSaveConfig();
172 void ControllerSelectorReconfigureControllers( 173 void ControllerSelectorReconfigureControllers(
173 const Core::Frontend::ControllerParameters& parameters); 174 const Core::Frontend::ControllerParameters& parameters);
174 void SoftwareKeyboardInitialize( 175 void SoftwareKeyboardInitialize(
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index cdf31b417..74d49dbd4 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -120,6 +120,20 @@
120 <addaction name="menu_Reset_Window_Size"/> 120 <addaction name="menu_Reset_Window_Size"/>
121 <addaction name="menu_View_Debugging"/> 121 <addaction name="menu_View_Debugging"/>
122 </widget> 122 </widget>
123 <widget class="QMenu" name="menu_Multiplayer">
124 <property name="enabled">
125 <bool>true</bool>
126 </property>
127 <property name="title">
128 <string>&amp;Multiplayer</string>
129 </property>
130 <addaction name="action_View_Lobby"/>
131 <addaction name="action_Start_Room"/>
132 <addaction name="action_Connect_To_Room"/>
133 <addaction name="separator"/>
134 <addaction name="action_Show_Room"/>
135 <addaction name="action_Leave_Room"/>
136 </widget>
123 <widget class="QMenu" name="menu_Tools"> 137 <widget class="QMenu" name="menu_Tools">
124 <property name="title"> 138 <property name="title">
125 <string>&amp;Tools</string> 139 <string>&amp;Tools</string>
@@ -251,7 +265,7 @@
251 <bool>true</bool> 265 <bool>true</bool>
252 </property> 266 </property>
253 <property name="text"> 267 <property name="text">
254 <string>Browse Public Game Lobby</string> 268 <string>&amp;Browse Public Game Lobby</string>
255 </property> 269 </property>
256 </action> 270 </action>
257 <action name="action_Start_Room"> 271 <action name="action_Start_Room">
@@ -259,7 +273,7 @@
259 <bool>true</bool> 273 <bool>true</bool>
260 </property> 274 </property>
261 <property name="text"> 275 <property name="text">
262 <string>Create Room</string> 276 <string>&amp;Create Room</string>
263 </property> 277 </property>
264 </action> 278 </action>
265 <action name="action_Leave_Room"> 279 <action name="action_Leave_Room">
@@ -267,12 +281,12 @@
267 <bool>false</bool> 281 <bool>false</bool>
268 </property> 282 </property>
269 <property name="text"> 283 <property name="text">
270 <string>Leave Room</string> 284 <string>&amp;Leave Room</string>
271 </property> 285 </property>
272 </action> 286 </action>
273 <action name="action_Connect_To_Room"> 287 <action name="action_Connect_To_Room">
274 <property name="text"> 288 <property name="text">
275 <string>Direct Connect to Room</string> 289 <string>&amp;Direct Connect to Room</string>
276 </property> 290 </property>
277 </action> 291 </action>
278 <action name="action_Show_Room"> 292 <action name="action_Show_Room">
@@ -280,7 +294,7 @@
280 <bool>false</bool> 294 <bool>false</bool>
281 </property> 295 </property>
282 <property name="text"> 296 <property name="text">
283 <string>Show Current Room</string> 297 <string>&amp;Show Current Room</string>
284 </property> 298 </property>
285 </action> 299 </action>
286 <action name="action_Fullscreen"> 300 <action name="action_Fullscreen">
diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp
index 9e672f82e..dec9696c1 100644
--- a/src/yuzu/multiplayer/chat_room.cpp
+++ b/src/yuzu/multiplayer/chat_room.cpp
@@ -61,7 +61,10 @@ public:
61 61
62 /// Format the message using the players color 62 /// Format the message using the players color
63 QString GetPlayerChatMessage(u16 player) const { 63 QString GetPlayerChatMessage(u16 player) const {
64 auto color = player_color[player % 16]; 64 const bool is_dark_theme = QIcon::themeName().contains(QStringLiteral("dark")) ||
65 QIcon::themeName().contains(QStringLiteral("midnight"));
66 auto color =
67 is_dark_theme ? player_color_dark[player % 16] : player_color_default[player % 16];
65 QString name; 68 QString name;
66 if (username.isEmpty() || username == nickname) { 69 if (username.isEmpty() || username == nickname) {
67 name = nickname; 70 name = nickname;
@@ -84,9 +87,12 @@ public:
84 } 87 }
85 88
86private: 89private:
87 static constexpr std::array<const char*, 16> player_color = { 90 static constexpr std::array<const char*, 16> player_color_default = {
88 {"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222", 91 {"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222",
89 "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}}; 92 "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "#FFFF00"}};
93 static constexpr std::array<const char*, 16> player_color_dark = {
94 {"#559AD1", "#4EC9A8", "#D69D85", "#C6C923", "#B975B5", "#D81F1F", "#7EAE39", "#4F8733",
95 "#F7CD8A", "#6FCACF", "#CE4897", "#8A2BE2", "#D2691E", "#9ACD32", "#FF7F50", "#152ccd"}};
90 static constexpr char ping_color[] = "#FFFF00"; 96 static constexpr char ping_color[] = "#FFFF00";
91 97
92 QString timestamp; 98 QString timestamp;
diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp
index b34a8d004..caf34a414 100644
--- a/src/yuzu/multiplayer/client_room.cpp
+++ b/src/yuzu/multiplayer/client_room.cpp
@@ -97,8 +97,9 @@ void ClientRoomWindow::UpdateView() {
97 auto memberlist = member->GetMemberInformation(); 97 auto memberlist = member->GetMemberInformation();
98 ui->chat->SetPlayerList(memberlist); 98 ui->chat->SetPlayerList(memberlist);
99 const auto information = member->GetRoomInformation(); 99 const auto information = member->GetRoomInformation();
100 setWindowTitle(QString(tr("%1 (%2/%3 members) - connected")) 100 setWindowTitle(QString(tr("%1 - %2 (%3/%4 members) - connected"))
101 .arg(QString::fromStdString(information.name)) 101 .arg(QString::fromStdString(information.name))
102 .arg(QString::fromStdString(information.preferred_game.name))
102 .arg(memberlist.size()) 103 .arg(memberlist.size())
103 .arg(information.member_slots)); 104 .arg(information.member_slots));
104 ui->description->setText(QString::fromStdString(information.description)); 105 ui->description->setText(QString::fromStdString(information.description));
diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp
index 017063074..10bf0a4fb 100644
--- a/src/yuzu/multiplayer/direct_connect.cpp
+++ b/src/yuzu/multiplayer/direct_connect.cpp
@@ -106,6 +106,8 @@ void DirectConnectWindow::Connect() {
106 UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault(); 106 UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault();
107 } 107 }
108 108
109 emit SaveConfig();
110
109 // attempt to connect in a different thread 111 // attempt to connect in a different thread
110 QFuture<void> f = QtConcurrent::run([&] { 112 QFuture<void> f = QtConcurrent::run([&] {
111 if (auto room_member = room_network.GetRoomMember().lock()) { 113 if (auto room_member = room_network.GetRoomMember().lock()) {
diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h
index e39dd1e0d..b8f66cfb2 100644
--- a/src/yuzu/multiplayer/direct_connect.h
+++ b/src/yuzu/multiplayer/direct_connect.h
@@ -31,6 +31,7 @@ signals:
31 * connections that it might have. 31 * connections that it might have.
32 */ 32 */
33 void Closed(); 33 void Closed();
34 void SaveConfig();
34 35
35private slots: 36private slots:
36 void OnConnection(); 37 void OnConnection();
diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp
index 0c6adfd04..a8faa5b24 100644
--- a/src/yuzu/multiplayer/host_room.cpp
+++ b/src/yuzu/multiplayer/host_room.cpp
@@ -232,6 +232,7 @@ void HostRoomWindow::Host() {
232 } 232 }
233 UISettings::values.multiplayer_room_description = ui->room_description->toPlainText(); 233 UISettings::values.multiplayer_room_description = ui->room_description->toPlainText();
234 ui->host->setEnabled(true); 234 ui->host->setEnabled(true);
235 emit SaveConfig();
235 close(); 236 close();
236 } 237 }
237} 238}
diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h
index 034cb2eef..ae816e2e0 100644
--- a/src/yuzu/multiplayer/host_room.h
+++ b/src/yuzu/multiplayer/host_room.h
@@ -46,6 +46,9 @@ public:
46 void UpdateGameList(QStandardItemModel* list); 46 void UpdateGameList(QStandardItemModel* list);
47 void RetranslateUi(); 47 void RetranslateUi();
48 48
49signals:
50 void SaveConfig();
51
49private: 52private:
50 void Host(); 53 void Host();
51 std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const; 54 std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const;
diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp
index 107d40547..08c275696 100644
--- a/src/yuzu/multiplayer/lobby.cpp
+++ b/src/yuzu/multiplayer/lobby.cpp
@@ -7,6 +7,7 @@
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "common/settings.h" 8#include "common/settings.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/hle/service/acc/profile_manager.h"
10#include "core/internal_network/network_interface.h" 11#include "core/internal_network/network_interface.h"
11#include "network/network.h" 12#include "network/network.h"
12#include "ui_lobby.h" 13#include "ui_lobby.h"
@@ -26,9 +27,9 @@
26Lobby::Lobby(QWidget* parent, QStandardItemModel* list, 27Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
27 std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_) 28 std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_)
28 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), 29 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
29 ui(std::make_unique<Ui::Lobby>()), 30 ui(std::make_unique<Ui::Lobby>()), announce_multiplayer_session(session),
30 announce_multiplayer_session(session), system{system_}, room_network{ 31 profile_manager(std::make_unique<Service::Account::ProfileManager>()), system{system_},
31 system.GetRoomNetwork()} { 32 room_network{system.GetRoomNetwork()} {
32 ui->setupUi(this); 33 ui->setupUi(this);
33 34
34 // setup the watcher for background connections 35 // setup the watcher for background connections
@@ -60,9 +61,17 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
60 61
61 ui->nickname->setValidator(validation.GetNickname()); 62 ui->nickname->setValidator(validation.GetNickname());
62 ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue()); 63 ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
63 if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) { 64
64 // Use yuzu Web Service user name as nickname by default 65 // Try find the best nickname by default
65 ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue())); 66 if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("yuzu")) {
67 if (!Settings::values.yuzu_username.GetValue().empty()) {
68 ui->nickname->setText(
69 QString::fromStdString(Settings::values.yuzu_username.GetValue()));
70 } else if (!GetProfileUsername().empty()) {
71 ui->nickname->setText(QString::fromStdString(GetProfileUsername()));
72 } else {
73 ui->nickname->setText(QStringLiteral("yuzu"));
74 }
66 } 75 }
67 76
68 // UI Buttons 77 // UI Buttons
@@ -76,12 +85,6 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
76 // Actions 85 // Actions
77 connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this, 86 connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
78 &Lobby::OnRefreshLobby); 87 &Lobby::OnRefreshLobby);
79
80 // manually start a refresh when the window is opening
81 // TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
82 // part of the constructor, but offload the refresh until after the window shown. perhaps emit a
83 // refreshroomlist signal from places that open the lobby
84 RefreshLobby();
85} 88}
86 89
87Lobby::~Lobby() = default; 90Lobby::~Lobby() = default;
@@ -96,6 +99,7 @@ void Lobby::UpdateGameList(QStandardItemModel* list) {
96 } 99 }
97 if (proxy) 100 if (proxy)
98 proxy->UpdateGameList(game_list); 101 proxy->UpdateGameList(game_list);
102 ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder);
99} 103}
100 104
101void Lobby::RetranslateUi() { 105void Lobby::RetranslateUi() {
@@ -117,6 +121,11 @@ void Lobby::OnExpandRoom(const QModelIndex& index) {
117 121
118void Lobby::OnJoinRoom(const QModelIndex& source) { 122void Lobby::OnJoinRoom(const QModelIndex& source) {
119 if (!Network::GetSelectedNetworkInterface()) { 123 if (!Network::GetSelectedNetworkInterface()) {
124 LOG_INFO(WebService, "Automatically selected network interface for room network.");
125 Network::SelectFirstNetworkInterface();
126 }
127
128 if (!Network::GetSelectedNetworkInterface()) {
120 NetworkMessage::ErrorManager::ShowError( 129 NetworkMessage::ErrorManager::ShowError(
121 NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); 130 NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
122 return; 131 return;
@@ -197,16 +206,16 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
197 proxy->data(connection_index, LobbyItemHost::HostIPRole).toString(); 206 proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
198 UISettings::values.multiplayer_port = 207 UISettings::values.multiplayer_port =
199 proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt(); 208 proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt();
209 emit SaveConfig();
200} 210}
201 211
202void Lobby::ResetModel() { 212void Lobby::ResetModel() {
203 model->clear(); 213 model->clear();
204 model->insertColumns(0, Column::TOTAL); 214 model->insertColumns(0, Column::TOTAL);
205 model->setHeaderData(Column::EXPAND, Qt::Horizontal, QString(), Qt::DisplayRole); 215 model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole);
206 model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole); 216 model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole);
207 model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole); 217 model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole);
208 model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole); 218 model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole);
209 model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole);
210} 219}
211 220
212void Lobby::RefreshLobby() { 221void Lobby::RefreshLobby() {
@@ -229,6 +238,7 @@ void Lobby::OnRefreshLobby() {
229 for (int r = 0; r < game_list->rowCount(); ++r) { 238 for (int r = 0; r < game_list->rowCount(); ++r) {
230 auto index = game_list->index(r, 0); 239 auto index = game_list->index(r, 0);
231 auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong(); 240 auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong();
241
232 if (game_id != 0 && room.information.preferred_game.id == game_id) { 242 if (game_id != 0 && room.information.preferred_game.id == game_id) {
233 smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>(); 243 smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>();
234 } 244 }
@@ -243,17 +253,16 @@ void Lobby::OnRefreshLobby() {
243 members.append(var); 253 members.append(var);
244 } 254 }
245 255
246 auto first_item = new LobbyItem(); 256 auto first_item = new LobbyItemGame(
257 room.information.preferred_game.id,
258 QString::fromStdString(room.information.preferred_game.name), smdh_icon);
247 auto row = QList<QStandardItem*>({ 259 auto row = QList<QStandardItem*>({
248 first_item, 260 first_item,
249 new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)), 261 new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)),
250 new LobbyItemGame(room.information.preferred_game.id, 262 new LobbyItemMemberList(members, room.information.member_slots),
251 QString::fromStdString(room.information.preferred_game.name),
252 smdh_icon),
253 new LobbyItemHost(QString::fromStdString(room.information.host_username), 263 new LobbyItemHost(QString::fromStdString(room.information.host_username),
254 QString::fromStdString(room.ip), room.information.port, 264 QString::fromStdString(room.ip), room.information.port,
255 QString::fromStdString(room.verify_uid)), 265 QString::fromStdString(room.verify_uid)),
256 new LobbyItemMemberList(members, room.information.member_slots),
257 }); 266 });
258 model->appendRow(row); 267 model->appendRow(row);
259 // To make the rows expandable, add the member data as a child of the first column of the 268 // To make the rows expandable, add the member data as a child of the first column of the
@@ -283,6 +292,26 @@ void Lobby::OnRefreshLobby() {
283 ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true); 292 ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true);
284 } 293 }
285 } 294 }
295
296 ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder);
297}
298
299std::string Lobby::GetProfileUsername() {
300 const auto& current_user = profile_manager->GetUser(Settings::values.current_user.GetValue());
301 Service::Account::ProfileBase profile{};
302
303 if (!current_user.has_value()) {
304 return "";
305 }
306
307 if (!profile_manager->GetProfileBase(*current_user, profile)) {
308 return "";
309 }
310
311 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
312 reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
313
314 return text;
286} 315}
287 316
288LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list) 317LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list)
diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h
index 2696aec21..300dad13e 100644
--- a/src/yuzu/multiplayer/lobby.h
+++ b/src/yuzu/multiplayer/lobby.h
@@ -24,6 +24,10 @@ namespace Core {
24class System; 24class System;
25} 25}
26 26
27namespace Service::Account {
28class ProfileManager;
29}
30
27/** 31/**
28 * Listing of all public games pulled from services. The lobby should be simple enough for users to 32 * Listing of all public games pulled from services. The lobby should be simple enough for users to
29 * find the game they want to play, and join it. 33 * find the game they want to play, and join it.
@@ -75,8 +79,11 @@ private slots:
75 79
76signals: 80signals:
77 void StateChanged(const Network::RoomMember::State&); 81 void StateChanged(const Network::RoomMember::State&);
82 void SaveConfig();
78 83
79private: 84private:
85 std::string GetProfileUsername();
86
80 /** 87 /**
81 * Removes all entries in the Lobby before refreshing. 88 * Removes all entries in the Lobby before refreshing.
82 */ 89 */
@@ -96,6 +103,7 @@ private:
96 103
97 QFutureWatcher<AnnounceMultiplayerRoom::RoomList> room_list_watcher; 104 QFutureWatcher<AnnounceMultiplayerRoom::RoomList> room_list_watcher;
98 std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; 105 std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
106 std::unique_ptr<Service::Account::ProfileManager> profile_manager;
99 QFutureWatcher<void>* watcher; 107 QFutureWatcher<void>* watcher;
100 Validation validation; 108 Validation validation;
101 Core::System& system; 109 Core::System& system;
diff --git a/src/yuzu/multiplayer/lobby_p.h b/src/yuzu/multiplayer/lobby_p.h
index 8071cede4..068c95aca 100644
--- a/src/yuzu/multiplayer/lobby_p.h
+++ b/src/yuzu/multiplayer/lobby_p.h
@@ -11,11 +11,10 @@
11 11
12namespace Column { 12namespace Column {
13enum List { 13enum List {
14 EXPAND,
15 ROOM_NAME,
16 GAME_NAME, 14 GAME_NAME,
17 HOST, 15 ROOM_NAME,
18 MEMBER, 16 MEMBER,
17 HOST,
19 TOTAL, 18 TOTAL,
20}; 19};
21} 20}
@@ -91,6 +90,8 @@ public:
91 setData(game_name, GameNameRole); 90 setData(game_name, GameNameRole);
92 if (!smdh_icon.isNull()) { 91 if (!smdh_icon.isNull()) {
93 setData(smdh_icon, GameIconRole); 92 setData(smdh_icon, GameIconRole);
93 } else {
94 setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(32), GameIconRole);
94 } 95 }
95 } 96 }
96 97
@@ -98,7 +99,12 @@ public:
98 if (role == Qt::DecorationRole) { 99 if (role == Qt::DecorationRole) {
99 auto val = data(GameIconRole); 100 auto val = data(GameIconRole);
100 if (val.isValid()) { 101 if (val.isValid()) {
101 val = val.value<QPixmap>().scaled(16, 16, Qt::KeepAspectRatio); 102 val = val.value<QPixmap>().scaled(32, 32, Qt::KeepAspectRatio,
103 Qt::TransformationMode::SmoothTransformation);
104 } else {
105 auto blank_image = QPixmap(32, 32);
106 blank_image.fill(Qt::black);
107 val = blank_image;
102 } 108 }
103 return val; 109 return val;
104 } else if (role != Qt::DisplayRole) { 110 } else if (role != Qt::DisplayRole) {
@@ -191,8 +197,8 @@ public:
191 return LobbyItem::data(role); 197 return LobbyItem::data(role);
192 } 198 }
193 auto members = data(MemberListRole).toList(); 199 auto members = data(MemberListRole).toList();
194 return QStringLiteral("%1 / %2").arg(QString::number(members.size()), 200 return QStringLiteral("%1 / %2 ")
195 data(MaxPlayerRole).toString()); 201 .arg(QString::number(members.size()), data(MaxPlayerRole).toString());
196 } 202 }
197 203
198 bool operator<(const QStandardItem& other) const override { 204 bool operator<(const QStandardItem& other) const override {
diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp
index 758b5b731..6d8f18274 100644
--- a/src/yuzu/multiplayer/message.cpp
+++ b/src/yuzu/multiplayer/message.cpp
@@ -49,9 +49,9 @@ const ConnectionError ErrorManager::PERMISSION_DENIED(
49 QT_TR_NOOP("You do not have enough permission to perform this action.")); 49 QT_TR_NOOP("You do not have enough permission to perform this action."));
50const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP( 50const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP(
51 "The user you are trying to kick/ban could not be found.\nThey may have left the room.")); 51 "The user you are trying to kick/ban could not be found.\nThey may have left the room."));
52const ConnectionError ErrorManager::NO_INTERFACE_SELECTED( 52const ConnectionError ErrorManager::NO_INTERFACE_SELECTED(QT_TR_NOOP(
53 QT_TR_NOOP("No network interface is selected.\nPlease go to Configure -> System -> Network and " 53 "No valid network interface is selected.\nPlease go to Configure -> System -> Network and "
54 "make a selection.")); 54 "make a selection."));
55 55
56static bool WarnMessage(const std::string& title, const std::string& text) { 56static bool WarnMessage(const std::string& title, const std::string& text) {
57 return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()), 57 return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()),
diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp
index 66e098296..ae2738ad4 100644
--- a/src/yuzu/multiplayer/state.cpp
+++ b/src/yuzu/multiplayer/state.cpp
@@ -44,9 +44,6 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
44 44
45 status_text = new ClickableLabel(this); 45 status_text = new ClickableLabel(this);
46 status_icon = new ClickableLabel(this); 46 status_icon = new ClickableLabel(this);
47 status_text->setToolTip(tr("Current connection status"));
48 status_text->setText(tr("Not Connected. Click here to find a room!"));
49 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
50 47
51 connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); 48 connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
52 connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); 49 connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
@@ -57,6 +54,8 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
57 HideNotification(); 54 HideNotification();
58 } 55 }
59 }); 56 });
57
58 retranslateUi();
60} 59}
61 60
62MultiplayerState::~MultiplayerState() = default; 61MultiplayerState::~MultiplayerState() = default;
@@ -90,14 +89,7 @@ void MultiplayerState::Close() {
90void MultiplayerState::retranslateUi() { 89void MultiplayerState::retranslateUi() {
91 status_text->setToolTip(tr("Current connection status")); 90 status_text->setToolTip(tr("Current connection status"));
92 91
93 if (current_state == Network::RoomMember::State::Uninitialized) { 92 UpdateNotificationStatus();
94 status_text->setText(tr("Not Connected. Click here to find a room!"));
95 } else if (current_state == Network::RoomMember::State::Joined ||
96 current_state == Network::RoomMember::State::Moderator) {
97 status_text->setText(tr("Connected"));
98 } else {
99 status_text->setText(tr("Not Connected"));
100 }
101 93
102 if (lobby) { 94 if (lobby) {
103 lobby->RetranslateUi(); 95 lobby->RetranslateUi();
@@ -113,21 +105,55 @@ void MultiplayerState::retranslateUi() {
113 } 105 }
114} 106}
115 107
108void MultiplayerState::SetNotificationStatus(NotificationStatus status) {
109 notification_status = status;
110 UpdateNotificationStatus();
111}
112
113void MultiplayerState::UpdateNotificationStatus() {
114 switch (notification_status) {
115 case NotificationStatus::Unitialized:
116 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
117 status_text->setText(tr("Not Connected. Click here to find a room!"));
118 leave_room->setEnabled(false);
119 show_room->setEnabled(false);
120 break;
121 case NotificationStatus::Disconnected:
122 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
123 status_text->setText(tr("Not Connected"));
124 leave_room->setEnabled(false);
125 show_room->setEnabled(false);
126 break;
127 case NotificationStatus::Connected:
128 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16));
129 status_text->setText(tr("Connected"));
130 leave_room->setEnabled(true);
131 show_room->setEnabled(true);
132 break;
133 case NotificationStatus::Notification:
134 status_icon->setPixmap(
135 QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16));
136 status_text->setText(tr("New Messages Received"));
137 leave_room->setEnabled(true);
138 show_room->setEnabled(true);
139 break;
140 }
141
142 // Clean up status bar if game is running
143 if (system.IsPoweredOn()) {
144 status_text->clear();
145 }
146}
147
116void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) { 148void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) {
117 LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state)); 149 LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state));
118 if (state == Network::RoomMember::State::Joined || 150 if (state == Network::RoomMember::State::Joined ||
119 state == Network::RoomMember::State::Moderator) { 151 state == Network::RoomMember::State::Moderator) {
120 152
121 OnOpenNetworkRoom(); 153 OnOpenNetworkRoom();
122 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); 154 SetNotificationStatus(NotificationStatus::Connected);
123 status_text->setText(tr("Connected"));
124 leave_room->setEnabled(true);
125 show_room->setEnabled(true);
126 } else { 155 } else {
127 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); 156 SetNotificationStatus(NotificationStatus::Disconnected);
128 status_text->setText(tr("Not Connected"));
129 leave_room->setEnabled(false);
130 show_room->setEnabled(false);
131 } 157 }
132 158
133 current_state = state; 159 current_state = state;
@@ -185,6 +211,10 @@ void MultiplayerState::OnAnnounceFailed(const WebService::WebResult& result) {
185 QMessageBox::Ok); 211 QMessageBox::Ok);
186} 212}
187 213
214void MultiplayerState::OnSaveConfig() {
215 emit SaveConfig();
216}
217
188void MultiplayerState::UpdateThemedIcons() { 218void MultiplayerState::UpdateThemedIcons() {
189 if (show_notification) { 219 if (show_notification) {
190 status_icon->setPixmap( 220 status_icon->setPixmap(
@@ -209,13 +239,16 @@ static void BringWidgetToFront(QWidget* widget) {
209void MultiplayerState::OnViewLobby() { 239void MultiplayerState::OnViewLobby() {
210 if (lobby == nullptr) { 240 if (lobby == nullptr) {
211 lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system); 241 lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system);
242 connect(lobby, &Lobby::SaveConfig, this, &MultiplayerState::OnSaveConfig);
212 } 243 }
244 lobby->RefreshLobby();
213 BringWidgetToFront(lobby); 245 BringWidgetToFront(lobby);
214} 246}
215 247
216void MultiplayerState::OnCreateRoom() { 248void MultiplayerState::OnCreateRoom() {
217 if (host_room == nullptr) { 249 if (host_room == nullptr) {
218 host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system); 250 host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system);
251 connect(host_room, &HostRoomWindow::SaveConfig, this, &MultiplayerState::OnSaveConfig);
219 } 252 }
220 BringWidgetToFront(host_room); 253 BringWidgetToFront(host_room);
221} 254}
@@ -249,14 +282,13 @@ void MultiplayerState::ShowNotification() {
249 return; // Do not show notification if the chat window currently has focus 282 return; // Do not show notification if the chat window currently has focus
250 show_notification = true; 283 show_notification = true;
251 QApplication::alert(nullptr); 284 QApplication::alert(nullptr);
252 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16)); 285 QApplication::beep();
253 status_text->setText(tr("New Messages Received")); 286 SetNotificationStatus(NotificationStatus::Notification);
254} 287}
255 288
256void MultiplayerState::HideNotification() { 289void MultiplayerState::HideNotification() {
257 show_notification = false; 290 show_notification = false;
258 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); 291 SetNotificationStatus(NotificationStatus::Connected);
259 status_text->setText(tr("Connected"));
260} 292}
261 293
262void MultiplayerState::OnOpenNetworkRoom() { 294void MultiplayerState::OnOpenNetworkRoom() {
@@ -279,6 +311,8 @@ void MultiplayerState::OnOpenNetworkRoom() {
279void MultiplayerState::OnDirectConnectToRoom() { 311void MultiplayerState::OnDirectConnectToRoom() {
280 if (direct_connect == nullptr) { 312 if (direct_connect == nullptr) {
281 direct_connect = new DirectConnectWindow(system, this); 313 direct_connect = new DirectConnectWindow(system, this);
314 connect(direct_connect, &DirectConnectWindow::SaveConfig, this,
315 &MultiplayerState::OnSaveConfig);
282 } 316 }
283 BringWidgetToFront(direct_connect); 317 BringWidgetToFront(direct_connect);
284} 318}
diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h
index c92496413..5d681c5c6 100644
--- a/src/yuzu/multiplayer/state.h
+++ b/src/yuzu/multiplayer/state.h
@@ -22,6 +22,13 @@ class MultiplayerState : public QWidget {
22 Q_OBJECT; 22 Q_OBJECT;
23 23
24public: 24public:
25 enum class NotificationStatus {
26 Unitialized,
27 Disconnected,
28 Connected,
29 Notification,
30 };
31
25 explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room, 32 explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room,
26 QAction* show_room, Core::System& system_); 33 QAction* show_room, Core::System& system_);
27 ~MultiplayerState(); 34 ~MultiplayerState();
@@ -31,6 +38,10 @@ public:
31 */ 38 */
32 void Close(); 39 void Close();
33 40
41 void SetNotificationStatus(NotificationStatus state);
42
43 void UpdateNotificationStatus();
44
34 ClickableLabel* GetStatusText() const { 45 ClickableLabel* GetStatusText() const {
35 return status_text; 46 return status_text;
36 } 47 }
@@ -64,6 +75,7 @@ public slots:
64 void OnOpenNetworkRoom(); 75 void OnOpenNetworkRoom();
65 void OnDirectConnectToRoom(); 76 void OnDirectConnectToRoom();
66 void OnAnnounceFailed(const WebService::WebResult&); 77 void OnAnnounceFailed(const WebService::WebResult&);
78 void OnSaveConfig();
67 void UpdateThemedIcons(); 79 void UpdateThemedIcons();
68 void ShowNotification(); 80 void ShowNotification();
69 void HideNotification(); 81 void HideNotification();
@@ -72,6 +84,7 @@ signals:
72 void NetworkStateChanged(const Network::RoomMember::State&); 84 void NetworkStateChanged(const Network::RoomMember::State&);
73 void NetworkError(const Network::RoomMember::Error&); 85 void NetworkError(const Network::RoomMember::Error&);
74 void AnnounceFailed(const WebService::WebResult&); 86 void AnnounceFailed(const WebService::WebResult&);
87 void SaveConfig();
75 88
76private: 89private:
77 Lobby* lobby = nullptr; 90 Lobby* lobby = nullptr;
@@ -85,6 +98,7 @@ private:
85 QAction* show_room; 98 QAction* show_room;
86 std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; 99 std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
87 Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized; 100 Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized;
101 NotificationStatus notification_status = NotificationStatus::Unitialized;
88 bool has_mod_perms = false; 102 bool has_mod_perms = false;
89 Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle; 103 Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle;
90 Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle; 104 Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle;
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index e12d414d9..753797efc 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -102,7 +102,7 @@ struct Values {
102 Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"}; 102 Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"};
103 103
104 // multiplayer settings 104 // multiplayer settings
105 Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"}; 105 Settings::Setting<QString> multiplayer_nickname{{}, "nickname"};
106 Settings::Setting<QString> multiplayer_ip{{}, "ip"}; 106 Settings::Setting<QString> multiplayer_ip{{}, "ip"};
107 Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"}; 107 Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"};
108 Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"}; 108 Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"};