summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar FearlessTobi2022-07-31 04:46:26 +0200
committerGravatar FearlessTobi2022-09-09 14:30:22 +0200
commitf5e635addaef59159bf6bc529b17954eda3684a1 (patch)
tree6d6b518611b377b6bd79e3156eb16f60f5045698 /src
parentMerge pull request #8819 from liamwhite/cash-money (diff)
downloadyuzu-f5e635addaef59159bf6bc529b17954eda3684a1.tar.gz
yuzu-f5e635addaef59159bf6bc529b17954eda3684a1.tar.xz
yuzu-f5e635addaef59159bf6bc529b17954eda3684a1.zip
ldn: Initial implementation
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/hle/service/ldn/lan_discovery.cpp644
-rw-r--r--src/core/hle/service/ldn/lan_discovery.h133
-rw-r--r--src/core/hle/service/ldn/ldn.cpp229
-rw-r--r--src/core/hle/service/ldn/ldn_types.h50
-rw-r--r--src/core/internal_network/socket_proxy.cpp8
-rw-r--r--src/dedicated_room/yuzu_room.cpp3
-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/yuzu/main.cpp4
-rw-r--r--src/yuzu/main.ui14
-rw-r--r--src/yuzu/multiplayer/chat_room.cpp12
-rw-r--r--src/yuzu/multiplayer/state.cpp1
15 files changed, 1132 insertions, 124 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 806e7ff6c..52017878c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -500,6 +500,8 @@ add_library(core STATIC
500 hle/service/jit/jit.h 500 hle/service/jit/jit.h
501 hle/service/lbl/lbl.cpp 501 hle/service/lbl/lbl.cpp
502 hle/service/lbl/lbl.h 502 hle/service/lbl/lbl.h
503 hle/service/ldn/lan_discovery.cpp
504 hle/service/ldn/lan_discovery.h
503 hle/service/ldn/ldn_results.h 505 hle/service/ldn/ldn_results.h
504 hle/service/ldn/ldn.cpp 506 hle/service/ldn/ldn.cpp
505 hle/service/ldn/ldn.h 507 hle/service/ldn/ldn.h
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..b04c99077
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.cpp
@@ -0,0 +1,644 @@
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 LOG_INFO(Service_LDN, "LANDiscovery");
40}
41
42LANDiscovery::~LANDiscovery() {
43 LOG_INFO(Service_LDN, "~LANDiscovery");
44 if (inited) {
45 Result rc = Finalize();
46 LOG_INFO(Service_LDN, "Finalize: {}", rc.raw);
47 }
48}
49
50void LANDiscovery::InitNetworkInfo() {
51 network_info.common.bssid = GetFakeMac();
52 network_info.common.channel = WifiChannel::Wifi24_6;
53 network_info.common.link_level = LinkLevel::Good;
54 network_info.common.network_type = PackedNetworkType::Ldn;
55 network_info.common.ssid = fake_ssid;
56
57 auto& nodes = network_info.ldn.nodes;
58 for (std::size_t i = 0; i < NodeCountMax; i++) {
59 nodes[i].node_id = static_cast<s8>(i);
60 nodes[i].is_connected = 0;
61 }
62}
63
64void LANDiscovery::InitNodeStateChange() {
65 for (auto& node_update : nodeChanges) {
66 node_update.state_change = NodeStateChange::None;
67 }
68 for (auto& node_state : node_last_states) {
69 node_state = 0;
70 }
71}
72
73State LANDiscovery::GetState() const {
74 return state;
75}
76
77void LANDiscovery::SetState(State new_state) {
78 state = new_state;
79}
80
81Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const {
82 if (state == State::AccessPointCreated || state == State::StationConnected) {
83 std::memcpy(&out_network, &network_info, sizeof(network_info));
84 return ResultSuccess;
85 }
86
87 return ResultBadState;
88}
89
90Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network,
91 std::vector<NodeLatestUpdate>& out_updates,
92 std::size_t buffer_count) {
93 if (buffer_count > NodeCountMax) {
94 return ResultInvalidBufferCount;
95 }
96
97 if (state == State::AccessPointCreated || state == State::StationConnected) {
98 std::memcpy(&out_network, &network_info, sizeof(network_info));
99 for (std::size_t i = 0; i < buffer_count; i++) {
100 out_updates[i].state_change = nodeChanges[i].state_change;
101 nodeChanges[i].state_change = NodeStateChange::None;
102 }
103 return ResultSuccess;
104 }
105
106 return ResultBadState;
107}
108
109DisconnectReason LANDiscovery::GetDisconnectReason() const {
110 return disconnect_reason;
111}
112
113Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
114 const ScanFilter& filter) {
115 if (!IsFlagSet(filter.flag, ScanFilterFlag::NetworkType) ||
116 filter.network_type <= NetworkType::All) {
117 if (!IsFlagSet(filter.flag, ScanFilterFlag::Ssid) && filter.ssid.length >= SsidLengthMax) {
118 return ResultBadInput;
119 }
120 }
121
122 {
123 std::scoped_lock lock{packet_mutex};
124 scan_results.clear();
125
126 SendBroadcast(Network::LDNPacketType::Scan);
127 }
128
129 LOG_INFO(Service_LDN, "Waiting for scan replies");
130 std::this_thread::sleep_for(std::chrono::seconds(1));
131
132 std::scoped_lock lock{packet_mutex};
133 for (const auto& [key, info] : scan_results) {
134 if (count >= networks.size()) {
135 break;
136 }
137
138 if (IsFlagSet(filter.flag, ScanFilterFlag::LocalCommunicationId)) {
139 if (filter.network_id.intent_id.local_communication_id !=
140 info.network_id.intent_id.local_communication_id) {
141 continue;
142 }
143 }
144 if (IsFlagSet(filter.flag, ScanFilterFlag::SessionId)) {
145 if (filter.network_id.session_id != info.network_id.session_id) {
146 continue;
147 }
148 }
149 if (IsFlagSet(filter.flag, ScanFilterFlag::NetworkType)) {
150 if (filter.network_type != static_cast<NetworkType>(info.common.network_type)) {
151 continue;
152 }
153 }
154 if (IsFlagSet(filter.flag, ScanFilterFlag::Ssid)) {
155 if (filter.ssid != info.common.ssid) {
156 continue;
157 }
158 }
159 if (IsFlagSet(filter.flag, ScanFilterFlag::SceneId)) {
160 if (filter.network_id.intent_id.scene_id != info.network_id.intent_id.scene_id) {
161 continue;
162 }
163 }
164
165 networks[count++] = info;
166 }
167
168 return ResultSuccess;
169}
170
171Result LANDiscovery::SetAdvertiseData(std::vector<u8>& data) {
172 std::scoped_lock lock{packet_mutex};
173 std::size_t size = data.size();
174 if (size > AdvertiseDataSizeMax) {
175 return ResultAdvertiseDataTooLarge;
176 }
177
178 std::memcpy(network_info.ldn.advertise_data.data(), data.data(), size);
179 network_info.ldn.advertise_data_size = static_cast<u16>(size);
180
181 UpdateNodes();
182
183 return ResultSuccess;
184}
185
186Result LANDiscovery::OpenAccessPoint() {
187 std::scoped_lock lock{packet_mutex};
188 disconnect_reason = DisconnectReason::None;
189 if (state == State::None) {
190 return ResultBadState;
191 }
192
193 ResetStations();
194 SetState(State::AccessPointOpened);
195
196 return ResultSuccess;
197}
198
199Result LANDiscovery::CloseAccessPoint() {
200 std::scoped_lock lock{packet_mutex};
201 if (state == State::None) {
202 return ResultBadState;
203 }
204
205 if (state == State::AccessPointCreated) {
206 DestroyNetwork();
207 }
208
209 ResetStations();
210 SetState(State::Initialized);
211
212 return ResultSuccess;
213}
214
215Result LANDiscovery::OpenStation() {
216 std::scoped_lock lock{packet_mutex};
217 disconnect_reason = DisconnectReason::None;
218 if (state == State::None) {
219 return ResultBadState;
220 }
221
222 ResetStations();
223 SetState(State::StationOpened);
224
225 return ResultSuccess;
226}
227
228Result LANDiscovery::CloseStation() {
229 std::scoped_lock lock{packet_mutex};
230 if (state == State::None) {
231 return ResultBadState;
232 }
233
234 if (state == State::StationConnected) {
235 Disconnect();
236 }
237
238 ResetStations();
239 SetState(State::Initialized);
240
241 return ResultSuccess;
242}
243
244Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config,
245 const UserConfig& user_config,
246 const NetworkConfig& network_config) {
247 std::scoped_lock lock{packet_mutex};
248
249 if (state != State::AccessPointOpened) {
250 return ResultBadState;
251 }
252
253 InitNetworkInfo();
254 network_info.ldn.node_count_max = network_config.node_count_max;
255 network_info.ldn.security_mode = security_config.security_mode;
256
257 if (network_config.channel == WifiChannel::Default) {
258 network_info.common.channel = WifiChannel::Wifi24_6;
259 } else {
260 network_info.common.channel = network_config.channel;
261 }
262
263 std::independent_bits_engine<std::mt19937, 64, u64> bits_engine;
264 network_info.network_id.session_id.high = bits_engine();
265 network_info.network_id.session_id.low = bits_engine();
266 network_info.network_id.intent_id = network_config.intent_id;
267
268 NodeInfo& node0 = network_info.ldn.nodes[0];
269 const Result rc2 = GetNodeInfo(node0, user_config, network_config.local_communication_version);
270 if (rc2.IsError()) {
271 return ResultAccessPointConnectionFailed;
272 }
273
274 SetState(State::AccessPointCreated);
275
276 InitNodeStateChange();
277 node0.is_connected = 1;
278 UpdateNodes();
279
280 return rc2;
281}
282
283Result LANDiscovery::DestroyNetwork() {
284 for (auto local_ip : connected_clients) {
285 SendPacket(Network::LDNPacketType::DestroyNetwork, local_ip);
286 }
287
288 ResetStations();
289
290 SetState(State::AccessPointOpened);
291 LanEvent();
292
293 return ResultSuccess;
294}
295
296Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
297 u16 local_communication_version) {
298 std::scoped_lock lock{packet_mutex};
299 if (network_info_.ldn.node_count == 0) {
300 return ResultInvalidNodeCount;
301 }
302
303 Result rc = GetNodeInfo(node_info, user_config, local_communication_version);
304 if (rc.IsError()) {
305 return ResultConnectionFailed;
306 }
307
308 Ipv4Address node_host = network_info_.ldn.nodes[0].ipv4_address;
309 std::reverse(std::begin(node_host), std::end(node_host)); // htonl
310 host_ip = node_host;
311 SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip);
312
313 InitNodeStateChange();
314
315 std::this_thread::sleep_for(std::chrono::seconds(1));
316
317 return ResultSuccess;
318}
319
320Result LANDiscovery::Disconnect() {
321 if (host_ip) {
322 SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip);
323 }
324
325 SetState(State::StationOpened);
326 LanEvent();
327
328 return ResultSuccess;
329}
330
331Result LANDiscovery::Initialize(LanEventFunc lan_event, bool listening) {
332 std::scoped_lock lock{packet_mutex};
333 if (inited) {
334 return ResultSuccess;
335 }
336
337 for (auto& station : stations) {
338 station.discovery = this;
339 station.node_info = &network_info.ldn.nodes[station.node_id];
340 station.Reset();
341 }
342
343 connected_clients.clear();
344 LanEvent = lan_event;
345
346 SetState(State::Initialized);
347
348 inited = true;
349 return ResultSuccess;
350}
351
352Result LANDiscovery::Finalize() {
353 std::scoped_lock lock{packet_mutex};
354 Result rc = ResultSuccess;
355
356 if (inited) {
357 if (state == State::AccessPointCreated) {
358 DestroyNetwork();
359 }
360 if (state == State::StationConnected) {
361 Disconnect();
362 }
363
364 ResetStations();
365 inited = false;
366 }
367
368 SetState(State::None);
369
370 return rc;
371}
372
373void LANDiscovery::ResetStations() {
374 for (auto& station : stations) {
375 station.Reset();
376 }
377 connected_clients.clear();
378}
379
380void LANDiscovery::UpdateNodes() {
381 u8 count = 0;
382 for (auto& station : stations) {
383 bool connected = station.GetStatus() == NodeStatus::Connected;
384 if (connected) {
385 count++;
386 }
387 station.OverrideInfo();
388 }
389 network_info.ldn.node_count = count + 1;
390
391 for (auto local_ip : connected_clients) {
392 SendPacket(Network::LDNPacketType::SyncNetwork, network_info, local_ip);
393 }
394
395 OnNetworkInfoChanged();
396}
397
398void LANDiscovery::OnSyncNetwork(const NetworkInfo& info) {
399 network_info = info;
400 if (state == State::StationOpened) {
401 SetState(State::StationConnected);
402 }
403 OnNetworkInfoChanged();
404}
405
406void LANDiscovery::OnDisconnectFromHost() {
407 LOG_INFO(Service_LDN, "OnDisconnectFromHost state: {}", static_cast<int>(state));
408 host_ip = std::nullopt;
409 if (state == State::StationConnected) {
410 SetState(State::StationOpened);
411 LanEvent();
412 }
413}
414
415void LANDiscovery::OnNetworkInfoChanged() {
416 if (IsNodeStateChanged()) {
417 LanEvent();
418 }
419 return;
420}
421
422Network::IPv4Address LANDiscovery::GetLocalIp() const {
423 Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF};
424 if (auto room_member = room_network.GetRoomMember().lock()) {
425 if (room_member->IsConnected()) {
426 local_ip = room_member->GetFakeIpAddress();
427 }
428 }
429 return local_ip;
430}
431
432template <typename Data>
433void LANDiscovery::SendPacket(Network::LDNPacketType type, const Data& data,
434 Ipv4Address remote_ip) {
435 Network::LDNPacket packet;
436 packet.type = type;
437
438 packet.broadcast = false;
439 packet.local_ip = GetLocalIp();
440 packet.remote_ip = remote_ip;
441
442 packet.data.clear();
443 packet.data.resize(sizeof(data));
444 std::memcpy(packet.data.data(), &data, sizeof(data));
445 SendPacket(packet);
446}
447
448void LANDiscovery::SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip) {
449 Network::LDNPacket packet;
450 packet.type = type;
451
452 packet.broadcast = false;
453 packet.local_ip = GetLocalIp();
454 packet.remote_ip = remote_ip;
455
456 packet.data.clear();
457 SendPacket(packet);
458}
459
460template <typename Data>
461void LANDiscovery::SendBroadcast(Network::LDNPacketType type, const Data& data) {
462 Network::LDNPacket packet;
463 packet.type = type;
464
465 packet.broadcast = true;
466 packet.local_ip = GetLocalIp();
467
468 packet.data.clear();
469 packet.data.resize(sizeof(data));
470 std::memcpy(packet.data.data(), &data, sizeof(data));
471 SendPacket(packet);
472}
473
474void LANDiscovery::SendBroadcast(Network::LDNPacketType type) {
475 Network::LDNPacket packet;
476 packet.type = type;
477
478 packet.broadcast = true;
479 packet.local_ip = GetLocalIp();
480
481 packet.data.clear();
482 SendPacket(packet);
483}
484
485void LANDiscovery::SendPacket(const Network::LDNPacket& packet) {
486 if (auto room_member = room_network.GetRoomMember().lock()) {
487 if (room_member->IsConnected()) {
488 room_member->SendLdnPacket(packet);
489 }
490 }
491}
492
493void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) {
494 std::scoped_lock lock{packet_mutex};
495 switch (packet.type) {
496 case Network::LDNPacketType::Scan: {
497 LOG_INFO(Frontend, "Scan packet received!");
498 if (state == State::AccessPointCreated) {
499 // Reply to the sender
500 SendPacket(Network::LDNPacketType::ScanResp, network_info, packet.local_ip);
501 }
502 break;
503 }
504 case Network::LDNPacketType::ScanResp: {
505 LOG_INFO(Frontend, "ScanResp packet received!");
506
507 NetworkInfo info{};
508 std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
509 scan_results.insert({info.common.bssid, info});
510
511 break;
512 }
513 case Network::LDNPacketType::Connect: {
514 LOG_INFO(Frontend, "Connect packet received!");
515
516 NodeInfo info{};
517 std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
518
519 connected_clients.push_back(packet.local_ip);
520
521 for (LanStation& station : stations) {
522 if (station.status != NodeStatus::Connected) {
523 *station.node_info = info;
524 station.status = NodeStatus::Connected;
525 break;
526 }
527 }
528
529 UpdateNodes();
530
531 break;
532 }
533 case Network::LDNPacketType::Disconnect: {
534 LOG_INFO(Frontend, "Disconnect packet received!");
535
536 connected_clients.erase(
537 std::remove(connected_clients.begin(), connected_clients.end(), packet.local_ip),
538 connected_clients.end());
539
540 NodeInfo info{};
541 std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
542
543 for (LanStation& station : stations) {
544 if (station.status == NodeStatus::Connected &&
545 station.node_info->mac_address == info.mac_address) {
546 station.OnClose();
547 break;
548 }
549 }
550
551 break;
552 }
553 case Network::LDNPacketType::DestroyNetwork: {
554 ResetStations();
555 OnDisconnectFromHost();
556 break;
557 }
558 case Network::LDNPacketType::SyncNetwork: {
559 if (state == State::StationOpened || state == State::StationConnected) {
560 LOG_INFO(Frontend, "SyncNetwork packet received!");
561 NetworkInfo info{};
562 std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
563
564 OnSyncNetwork(info);
565 } else {
566 LOG_INFO(Frontend, "SyncNetwork packet received but in wrong State!");
567 }
568
569 break;
570 }
571 default: {
572 LOG_INFO(Frontend, "ReceivePacket unhandled type {}", static_cast<int>(packet.type));
573 break;
574 }
575 }
576}
577
578bool LANDiscovery::IsNodeStateChanged() {
579 bool changed = false;
580 const auto& nodes = network_info.ldn.nodes;
581 for (int i = 0; i < NodeCountMax; i++) {
582 if (nodes[i].is_connected != node_last_states[i]) {
583 if (nodes[i].is_connected) {
584 nodeChanges[i].state_change |= NodeStateChange::Connect;
585 } else {
586 nodeChanges[i].state_change |= NodeStateChange::Disconnect;
587 }
588 node_last_states[i] = nodes[i].is_connected;
589 changed = true;
590 }
591 }
592 return changed;
593}
594
595bool LANDiscovery::IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const {
596 const auto flag_value = static_cast<u32>(flag);
597 const auto search_flag_value = static_cast<u32>(search_flag);
598 return (flag_value & search_flag_value) == search_flag_value;
599}
600
601int LANDiscovery::GetStationCount() {
602 int count = 0;
603 for (const auto& station : stations) {
604 if (station.GetStatus() != NodeStatus::Disconnected) {
605 count++;
606 }
607 }
608
609 return count;
610}
611
612MacAddress LANDiscovery::GetFakeMac() const {
613 MacAddress mac{};
614 mac.raw[0] = 0x02;
615 mac.raw[1] = 0x00;
616
617 const auto ip = GetLocalIp();
618 memcpy(mac.raw.data() + 2, &ip, sizeof(ip));
619
620 return mac;
621}
622
623Result LANDiscovery::GetNodeInfo(NodeInfo& node, const UserConfig& userConfig,
624 u16 localCommunicationVersion) {
625 const auto network_interface = Network::GetSelectedNetworkInterface();
626
627 if (!network_interface) {
628 LOG_ERROR(Service_LDN, "No network interface available");
629 return ResultNoIpAddress;
630 }
631
632 node.mac_address = GetFakeMac();
633 node.is_connected = 1;
634 std::memcpy(node.user_name.data(), userConfig.user_name.data(), UserNameBytesMax + 1);
635 node.local_communication_version = localCommunicationVersion;
636
637 Ipv4Address current_address = GetLocalIp();
638 std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
639 node.ipv4_address = current_address;
640
641 return ResultSuccess;
642}
643
644} // 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..255342456
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.h
@@ -0,0 +1,133 @@
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 <thread>
14#include <unordered_map>
15
16#include "common/logging/log.h"
17#include "common/socket_types.h"
18#include "core/hle/result.h"
19#include "core/hle/service/ldn/ldn_results.h"
20#include "core/hle/service/ldn/ldn_types.h"
21#include "network/network.h"
22
23namespace Service::LDN {
24
25class LANDiscovery;
26
27class LanStation {
28public:
29 LanStation(s8 node_id_, LANDiscovery* discovery_);
30 ~LanStation();
31
32 void OnClose();
33 NodeStatus GetStatus() const;
34 void Reset();
35 void OverrideInfo();
36
37protected:
38 friend class LANDiscovery;
39 NodeInfo* node_info;
40 NodeStatus status;
41 s8 node_id;
42 LANDiscovery* discovery;
43};
44
45class LANDiscovery {
46public:
47 typedef std::function<void()> LanEventFunc;
48
49 LANDiscovery(Network::RoomNetwork& room_network_);
50 ~LANDiscovery();
51
52 State GetState() const;
53 void SetState(State new_state);
54
55 Result GetNetworkInfo(NetworkInfo& out_network) const;
56 Result GetNetworkInfo(NetworkInfo& out_network, std::vector<NodeLatestUpdate>& out_updates,
57 std::size_t buffer_count);
58
59 DisconnectReason GetDisconnectReason() const;
60 Result Scan(std::vector<NetworkInfo>& networks, u16& count, const ScanFilter& filter);
61 Result SetAdvertiseData(std::vector<u8>& data);
62
63 Result OpenAccessPoint();
64 Result CloseAccessPoint();
65
66 Result OpenStation();
67 Result CloseStation();
68
69 Result CreateNetwork(const SecurityConfig& security_config, const UserConfig& user_config,
70 const NetworkConfig& network_config);
71 Result DestroyNetwork();
72
73 Result Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
74 u16 local_communication_version);
75 Result Disconnect();
76
77 Result Initialize(LanEventFunc lan_event = empty_func, bool listening = true);
78 Result Finalize();
79
80 void ReceivePacket(const Network::LDNPacket& packet);
81
82protected:
83 friend class LanStation;
84
85 void InitNetworkInfo();
86 void InitNodeStateChange();
87
88 void ResetStations();
89 void UpdateNodes();
90
91 void OnSyncNetwork(const NetworkInfo& info);
92 void OnDisconnectFromHost();
93 void OnNetworkInfoChanged();
94
95 bool IsNodeStateChanged();
96 bool IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const;
97 int GetStationCount();
98 MacAddress GetFakeMac() const;
99 Result GetNodeInfo(NodeInfo& node, const UserConfig& user_config,
100 u16 local_communication_version);
101
102 Network::IPv4Address GetLocalIp() const;
103 template <typename Data>
104 void SendPacket(Network::LDNPacketType type, const Data& data, Ipv4Address remote_ip);
105 void SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip);
106 template <typename Data>
107 void SendBroadcast(Network::LDNPacketType type, const Data& data);
108 void SendBroadcast(Network::LDNPacketType type);
109 void SendPacket(const Network::LDNPacket& packet);
110
111 static const LanEventFunc empty_func;
112 const Ssid fake_ssid{"YuzuFakeSsidForLdn"};
113
114 bool inited{};
115 std::mutex packet_mutex;
116 std::array<LanStation, StationCountMax> stations;
117 std::array<NodeLatestUpdate, NodeCountMax> nodeChanges{};
118 std::array<u8, NodeCountMax> node_last_states{};
119 std::unordered_map<MacAddress, NetworkInfo, MACAddressHash> scan_results{};
120 NodeInfo node_info{};
121 NetworkInfo network_info{};
122 State state{State::None};
123 DisconnectReason disconnect_reason{DisconnectReason::None};
124
125 // TODO (flTobi): Should this be an std::set?
126 std::vector<Ipv4Address> connected_clients;
127 std::optional<Ipv4Address> host_ip = std::nullopt;
128
129 LanEventFunc LanEvent;
130
131 Network::RoomNetwork& room_network;
132};
133} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index c11daff54..6537f49cf 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,52 @@ 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 LOG_CRITICAL(Service_LDN, "called");
196 209
197 LOG_WARNING(Service_LDN, "(STUBBED) called, disconnect_reason={}", disconnect_reason); 210 const auto network_interface = Network::GetSelectedNetworkInterface();
211
212 if (!network_interface) {
213 LOG_ERROR(Service_LDN, "No network interface available");
214 IPC::ResponseBuilder rb{ctx, 2};
215 rb.Push(ResultNoIpAddress);
216 return;
217 }
218
219 Ipv4Address current_address{Network::TranslateIPv4(network_interface->ip_address)};
220 Ipv4Address subnet_mask{Network::TranslateIPv4(network_interface->subnet_mask)};
221
222 // When we're connected to a room, spoof the hosts IP address
223 if (auto room_member = room_network.GetRoomMember().lock()) {
224 if (room_member->IsConnected()) {
225 current_address = room_member->GetFakeIpAddress();
226 }
227 }
228
229 std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
230 std::reverse(std::begin(subnet_mask), std::end(subnet_mask)); // ntohl
231
232 IPC::ResponseBuilder rb{ctx, 4};
233 rb.Push(ResultSuccess);
234 rb.PushRaw(current_address);
235 rb.PushRaw(subnet_mask);
236 }
198 237
238 void GetDisconnectReason(Kernel::HLERequestContext& ctx) {
199 IPC::ResponseBuilder rb{ctx, 3}; 239 IPC::ResponseBuilder rb{ctx, 3};
200 rb.Push(ResultSuccess); 240 rb.Push(ResultSuccess);
201 rb.PushEnum(disconnect_reason); 241 rb.PushEnum(lan_discovery.GetDisconnectReason());
202 } 242 }
203 243
204 void GetSecurityParameter(Kernel::HLERequestContext& ctx) { 244 void GetSecurityParameter(Kernel::HLERequestContext& ctx) {
205 SecurityParameter security_parameter{}; 245 SecurityParameter security_parameter{};
206 NetworkInfo info{}; 246 NetworkInfo info{};
207 const Result rc = ResultSuccess; 247 const Result rc = lan_discovery.GetNetworkInfo(info);
208 248
209 if (rc.IsError()) { 249 if (rc.IsError()) {
210 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); 250 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
@@ -217,8 +257,6 @@ public:
217 std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(), 257 std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(),
218 sizeof(SecurityParameter::data)); 258 sizeof(SecurityParameter::data));
219 259
220 LOG_WARNING(Service_LDN, "(STUBBED) called");
221
222 IPC::ResponseBuilder rb{ctx, 10}; 260 IPC::ResponseBuilder rb{ctx, 10};
223 rb.Push(rc); 261 rb.Push(rc);
224 rb.PushRaw<SecurityParameter>(security_parameter); 262 rb.PushRaw<SecurityParameter>(security_parameter);
@@ -227,7 +265,7 @@ public:
227 void GetNetworkConfig(Kernel::HLERequestContext& ctx) { 265 void GetNetworkConfig(Kernel::HLERequestContext& ctx) {
228 NetworkConfig config{}; 266 NetworkConfig config{};
229 NetworkInfo info{}; 267 NetworkInfo info{};
230 const Result rc = ResultSuccess; 268 const Result rc = lan_discovery.GetNetworkInfo(info);
231 269
232 if (rc.IsError()) { 270 if (rc.IsError()) {
233 LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw); 271 LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw);
@@ -241,12 +279,6 @@ public:
241 config.node_count_max = info.ldn.node_count_max; 279 config.node_count_max = info.ldn.node_count_max;
242 config.local_communication_version = info.ldn.nodes[0].local_communication_version; 280 config.local_communication_version = info.ldn.nodes[0].local_communication_version;
243 281
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}; 282 IPC::ResponseBuilder rb{ctx, 10};
251 rb.Push(rc); 283 rb.Push(rc);
252 rb.PushRaw<NetworkConfig>(config); 284 rb.PushRaw<NetworkConfig>(config);
@@ -265,17 +297,17 @@ public:
265 const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate); 297 const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate);
266 298
267 if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) { 299 if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) {
268 LOG_ERROR(Service_LDN, "Invalid buffer size {}, {}", network_buffer_size, 300 LOG_ERROR(Service_LDN, "Invalid buffer, size = {}, count = {}", network_buffer_size,
269 node_buffer_count); 301 node_buffer_count);
270 IPC::ResponseBuilder rb{ctx, 2}; 302 IPC::ResponseBuilder rb{ctx, 2};
271 rb.Push(ResultBadInput); 303 rb.Push(ResultBadInput);
272 return; 304 return;
273 } 305 }
274 306
275 NetworkInfo info; 307 NetworkInfo info{};
276 std::vector<NodeLatestUpdate> latest_update(node_buffer_count); 308 std::vector<NodeLatestUpdate> latest_update(node_buffer_count);
277 309
278 const auto rc = ResultSuccess; 310 const auto rc = lan_discovery.GetNetworkInfo(info, latest_update, latest_update.size());
279 if (rc.IsError()) { 311 if (rc.IsError()) {
280 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); 312 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
281 IPC::ResponseBuilder rb{ctx, 2}; 313 IPC::ResponseBuilder rb{ctx, 2};
@@ -283,9 +315,6 @@ public:
283 return; 315 return;
284 } 316 }
285 317
286 LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
287 info.common.ssid.GetStringValue(), info.ldn.node_count);
288
289 ctx.WriteBuffer(info, 0); 318 ctx.WriteBuffer(info, 0);
290 ctx.WriteBuffer(latest_update, 1); 319 ctx.WriteBuffer(latest_update, 1);
291 320
@@ -317,92 +346,78 @@ public:
317 346
318 u16 count = 0; 347 u16 count = 0;
319 std::vector<NetworkInfo> network_infos(network_info_size); 348 std::vector<NetworkInfo> network_infos(network_info_size);
349 Result rc = lan_discovery.Scan(network_infos, count, scan_filter);
320 350
321 LOG_WARNING(Service_LDN, 351 LOG_INFO(Service_LDN,
322 "(STUBBED) called, channel={}, filter_scan_flag={}, filter_network_type={}", 352 "called, channel={}, filter_scan_flag={}, filter_network_type={}, is_private={}",
323 channel, scan_filter.flag, scan_filter.network_type); 353 channel, scan_filter.flag, scan_filter.network_type, is_private);
324 354
325 ctx.WriteBuffer(network_infos); 355 ctx.WriteBuffer(network_infos);
326 356
327 IPC::ResponseBuilder rb{ctx, 3}; 357 IPC::ResponseBuilder rb{ctx, 3};
328 rb.Push(ResultSuccess); 358 rb.Push(rc);
329 rb.Push<u32>(count); 359 rb.Push<u32>(count);
330 } 360 }
331 361
332 void OpenAccessPoint(Kernel::HLERequestContext& ctx) { 362 void SetWirelessControllerRestriction(Kernel::HLERequestContext& ctx) {
333 LOG_WARNING(Service_LDN, "(STUBBED) called"); 363 LOG_WARNING(Service_LDN, "(STUBBED) called");
334 364
335 IPC::ResponseBuilder rb{ctx, 2}; 365 IPC::ResponseBuilder rb{ctx, 2};
336 rb.Push(ResultSuccess); 366 rb.Push(ResultSuccess);
337 } 367 }
338 368
369 void OpenAccessPoint(Kernel::HLERequestContext& ctx) {
370 LOG_INFO(Service_LDN, "called");
371
372 IPC::ResponseBuilder rb{ctx, 2};
373 rb.Push(lan_discovery.OpenAccessPoint());
374 }
375
339 void CloseAccessPoint(Kernel::HLERequestContext& ctx) { 376 void CloseAccessPoint(Kernel::HLERequestContext& ctx) {
340 LOG_WARNING(Service_LDN, "(STUBBED) called"); 377 LOG_INFO(Service_LDN, "called");
341 378
342 IPC::ResponseBuilder rb{ctx, 2}; 379 IPC::ResponseBuilder rb{ctx, 2};
343 rb.Push(ResultSuccess); 380 rb.Push(lan_discovery.CloseAccessPoint());
344 } 381 }
345 382
346 void CreateNetwork(Kernel::HLERequestContext& ctx) { 383 void CreateNetwork(Kernel::HLERequestContext& ctx) {
347 IPC::RequestParser rp{ctx}; 384 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 385
356 const auto parameters{rp.PopRaw<Parameters>()}; 386 CreateNetworkImpl(ctx);
387 }
357 388
358 LOG_WARNING(Service_LDN, 389 void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) {
359 "(STUBBED) called, passphrase_size={}, security_mode={}, " 390 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 391
365 IPC::ResponseBuilder rb{ctx, 2}; 392 CreateNetworkImpl(ctx, true);
366 rb.Push(ResultSuccess);
367 } 393 }
368 394
369 void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) { 395 void CreateNetworkImpl(Kernel::HLERequestContext& ctx, bool is_private = false) {
370 IPC::RequestParser rp{ctx}; 396 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 397
381 LOG_WARNING(Service_LDN, 398 const auto security_config{rp.PopRaw<SecurityConfig>()};
382 "(STUBBED) called, passphrase_size={}, security_mode={}, " 399 [[maybe_unused]] const auto security_parameter{is_private ? rp.PopRaw<SecurityParameter>()
383 "local_communication_version={}", 400 : SecurityParameter{}};
384 parameters.security_config.passphrase_size, 401 const auto user_config{rp.PopRaw<UserConfig>()};
385 parameters.security_config.security_mode, 402 rp.Pop<u32>(); // Padding
386 parameters.network_config.local_communication_version); 403 const auto network_Config{rp.PopRaw<NetworkConfig>()};
387 404
388 IPC::ResponseBuilder rb{ctx, 2}; 405 IPC::ResponseBuilder rb{ctx, 2};
389 rb.Push(ResultSuccess); 406 rb.Push(lan_discovery.CreateNetwork(security_config, user_config, network_Config));
390 } 407 }
391 408
392 void DestroyNetwork(Kernel::HLERequestContext& ctx) { 409 void DestroyNetwork(Kernel::HLERequestContext& ctx) {
393 LOG_WARNING(Service_LDN, "(STUBBED) called"); 410 LOG_INFO(Service_LDN, "called");
394 411
395 IPC::ResponseBuilder rb{ctx, 2}; 412 IPC::ResponseBuilder rb{ctx, 2};
396 rb.Push(ResultSuccess); 413 rb.Push(lan_discovery.DestroyNetwork());
397 } 414 }
398 415
399 void SetAdvertiseData(Kernel::HLERequestContext& ctx) { 416 void SetAdvertiseData(Kernel::HLERequestContext& ctx) {
400 std::vector<u8> read_buffer = ctx.ReadBuffer(); 417 std::vector<u8> read_buffer = ctx.ReadBuffer();
401 418
402 LOG_WARNING(Service_LDN, "(STUBBED) called, size {}", read_buffer.size());
403
404 IPC::ResponseBuilder rb{ctx, 2}; 419 IPC::ResponseBuilder rb{ctx, 2};
405 rb.Push(ResultSuccess); 420 rb.Push(lan_discovery.SetAdvertiseData(read_buffer));
406 } 421 }
407 422
408 void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) { 423 void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) {
@@ -420,17 +435,17 @@ public:
420 } 435 }
421 436
422 void OpenStation(Kernel::HLERequestContext& ctx) { 437 void OpenStation(Kernel::HLERequestContext& ctx) {
423 LOG_WARNING(Service_LDN, "(STUBBED) called"); 438 LOG_INFO(Service_LDN, "called");
424 439
425 IPC::ResponseBuilder rb{ctx, 2}; 440 IPC::ResponseBuilder rb{ctx, 2};
426 rb.Push(ResultSuccess); 441 rb.Push(lan_discovery.OpenStation());
427 } 442 }
428 443
429 void CloseStation(Kernel::HLERequestContext& ctx) { 444 void CloseStation(Kernel::HLERequestContext& ctx) {
430 LOG_WARNING(Service_LDN, "(STUBBED) called"); 445 LOG_INFO(Service_LDN, "called");
431 446
432 IPC::ResponseBuilder rb{ctx, 2}; 447 IPC::ResponseBuilder rb{ctx, 2};
433 rb.Push(ResultSuccess); 448 rb.Push(lan_discovery.CloseStation());
434 } 449 }
435 450
436 void Connect(Kernel::HLERequestContext& ctx) { 451 void Connect(Kernel::HLERequestContext& ctx) {
@@ -445,16 +460,13 @@ public:
445 460
446 const auto parameters{rp.PopRaw<Parameters>()}; 461 const auto parameters{rp.PopRaw<Parameters>()};
447 462
448 LOG_WARNING(Service_LDN, 463 LOG_INFO(Service_LDN,
449 "(STUBBED) called, passphrase_size={}, security_mode={}, " 464 "called, passphrase_size={}, security_mode={}, "
450 "local_communication_version={}", 465 "local_communication_version={}",
451 parameters.security_config.passphrase_size, 466 parameters.security_config.passphrase_size,
452 parameters.security_config.security_mode, 467 parameters.security_config.security_mode, parameters.local_communication_version);
453 parameters.local_communication_version);
454 468
455 const std::vector<u8> read_buffer = ctx.ReadBuffer(); 469 const std::vector<u8> read_buffer = ctx.ReadBuffer();
456 NetworkInfo network_info{};
457
458 if (read_buffer.size() != sizeof(NetworkInfo)) { 470 if (read_buffer.size() != sizeof(NetworkInfo)) {
459 LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!"); 471 LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!");
460 IPC::ResponseBuilder rb{ctx, 2}; 472 IPC::ResponseBuilder rb{ctx, 2};
@@ -462,40 +474,47 @@ public:
462 return; 474 return;
463 } 475 }
464 476
477 NetworkInfo network_info{};
465 std::memcpy(&network_info, read_buffer.data(), read_buffer.size()); 478 std::memcpy(&network_info, read_buffer.data(), read_buffer.size());
466 479
467 IPC::ResponseBuilder rb{ctx, 2}; 480 IPC::ResponseBuilder rb{ctx, 2};
468 rb.Push(ResultSuccess); 481 rb.Push(lan_discovery.Connect(network_info, parameters.user_config,
482 static_cast<u16>(parameters.local_communication_version)));
469 } 483 }
470 484
471 void Disconnect(Kernel::HLERequestContext& ctx) { 485 void Disconnect(Kernel::HLERequestContext& ctx) {
472 LOG_WARNING(Service_LDN, "(STUBBED) called"); 486 LOG_INFO(Service_LDN, "called");
473 487
474 IPC::ResponseBuilder rb{ctx, 2}; 488 IPC::ResponseBuilder rb{ctx, 2};
475 rb.Push(ResultSuccess); 489 rb.Push(lan_discovery.Disconnect());
476 } 490 }
477 void Initialize(Kernel::HLERequestContext& ctx) {
478 LOG_WARNING(Service_LDN, "(STUBBED) called");
479 491
492 void Initialize(Kernel::HLERequestContext& ctx) {
480 const auto rc = InitializeImpl(ctx); 493 const auto rc = InitializeImpl(ctx);
494 if (rc.IsError()) {
495 LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
496 }
481 497
482 IPC::ResponseBuilder rb{ctx, 2}; 498 IPC::ResponseBuilder rb{ctx, 2};
483 rb.Push(rc); 499 rb.Push(rc);
484 } 500 }
485 501
486 void Finalize(Kernel::HLERequestContext& ctx) { 502 void Finalize(Kernel::HLERequestContext& ctx) {
487 LOG_WARNING(Service_LDN, "(STUBBED) called"); 503 if (auto room_member = room_network.GetRoomMember().lock()) {
504 room_member->Unbind(ldn_packet_received);
505 }
488 506
489 is_initialized = false; 507 is_initialized = false;
490 508
491 IPC::ResponseBuilder rb{ctx, 2}; 509 IPC::ResponseBuilder rb{ctx, 2};
492 rb.Push(ResultSuccess); 510 rb.Push(lan_discovery.Finalize());
493 } 511 }
494 512
495 void Initialize2(Kernel::HLERequestContext& ctx) { 513 void Initialize2(Kernel::HLERequestContext& ctx) {
496 LOG_WARNING(Service_LDN, "(STUBBED) called");
497
498 const auto rc = InitializeImpl(ctx); 514 const auto rc = InitializeImpl(ctx);
515 if (rc.IsError()) {
516 LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
517 }
499 518
500 IPC::ResponseBuilder rb{ctx, 2}; 519 IPC::ResponseBuilder rb{ctx, 2};
501 rb.Push(rc); 520 rb.Push(rc);
@@ -508,14 +527,26 @@ public:
508 return ResultAirplaneModeEnabled; 527 return ResultAirplaneModeEnabled;
509 } 528 }
510 529
530 if (auto room_member = room_network.GetRoomMember().lock()) {
531 ldn_packet_received = room_member->BindOnLdnPacketReceived(
532 [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
533 } else {
534 LOG_ERROR(Service_LDN, "Couldn't bind callback!");
535 return ResultAirplaneModeEnabled;
536 }
537
538 lan_discovery.Initialize([&]() { OnEventFired(); });
511 is_initialized = true; 539 is_initialized = true;
512 // TODO (flTobi): Change this to ResultSuccess when LDN is fully implemented 540 return ResultSuccess;
513 return ResultAirplaneModeEnabled;
514 } 541 }
515 542
516 KernelHelpers::ServiceContext service_context; 543 KernelHelpers::ServiceContext service_context;
517 Kernel::KEvent* state_change_event; 544 Kernel::KEvent* state_change_event;
518 Network::RoomNetwork& room_network; 545 Network::RoomNetwork& room_network;
546 LANDiscovery lan_discovery;
547
548 // Callback identifier for the OnLDNPacketReceived event.
549 Network::RoomMember::CallbackHandle<Network::LDNPacket> ldn_packet_received;
519 550
520 bool is_initialized{}; 551 bool is_initialized{};
521}; 552};
diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h
index 6231e936d..d6609fff5 100644
--- a/src/core/hle/service/ldn/ldn_types.h
+++ b/src/core/hle/service/ldn/ldn_types.h
@@ -31,6 +31,14 @@ enum class NodeStateChange : u8 {
31 DisconnectAndConnect, 31 DisconnectAndConnect,
32}; 32};
33 33
34inline NodeStateChange operator|(NodeStateChange a, NodeStateChange b) {
35 return static_cast<NodeStateChange>(static_cast<u8>(a) | static_cast<u8>(b));
36}
37
38inline NodeStateChange operator|=(NodeStateChange& a, NodeStateChange b) {
39 return a = a | b;
40}
41
34enum class ScanFilterFlag : u32 { 42enum class ScanFilterFlag : u32 {
35 None = 0, 43 None = 0,
36 LocalCommunicationId = 1 << 0, 44 LocalCommunicationId = 1 << 0,
@@ -100,13 +108,13 @@ enum class AcceptPolicy : u8 {
100 108
101enum class WifiChannel : s16 { 109enum class WifiChannel : s16 {
102 Default = 0, 110 Default = 0,
103 wifi24_1 = 1, 111 Wifi24_1 = 1,
104 wifi24_6 = 6, 112 Wifi24_6 = 6,
105 wifi24_11 = 11, 113 Wifi24_11 = 11,
106 wifi50_36 = 36, 114 Wifi50_36 = 36,
107 wifi50_40 = 40, 115 Wifi50_40 = 40,
108 wifi50_44 = 44, 116 Wifi50_44 = 44,
109 wifi50_48 = 48, 117 Wifi50_48 = 48,
110}; 118};
111 119
112enum class LinkLevel : s8 { 120enum class LinkLevel : s8 {
@@ -116,6 +124,11 @@ enum class LinkLevel : s8 {
116 Excellent, 124 Excellent,
117}; 125};
118 126
127enum class NodeStatus : u8 {
128 Disconnected,
129 Connected,
130};
131
119struct NodeLatestUpdate { 132struct NodeLatestUpdate {
120 NodeStateChange state_change; 133 NodeStateChange state_change;
121 INSERT_PADDING_BYTES(0x7); // Unknown 134 INSERT_PADDING_BYTES(0x7); // Unknown
@@ -159,19 +172,14 @@ struct Ssid {
159 std::string GetStringValue() const { 172 std::string GetStringValue() const {
160 return std::string(raw.data()); 173 return std::string(raw.data());
161 } 174 }
162};
163static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
164
165struct Ipv4Address {
166 union {
167 u32 raw{};
168 std::array<u8, 4> bytes;
169 };
170 175
171 std::string GetStringValue() const { 176 bool operator==(const Ssid& b) const {
172 return fmt::format("{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]); 177 return (length == b.length) && (std::memcmp(raw.data(), b.raw.data(), length) == 0);
173 } 178 }
174}; 179};
180static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
181
182using Ipv4Address = std::array<u8, 4>;
175static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size"); 183static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size");
176 184
177struct MacAddress { 185struct MacAddress {
@@ -181,6 +189,14 @@ struct MacAddress {
181}; 189};
182static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size"); 190static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size");
183 191
192struct MACAddressHash {
193 size_t operator()(const MacAddress& address) const {
194 u64 value{};
195 std::memcpy(&value, address.raw.data(), sizeof(address.raw));
196 return value;
197 }
198};
199
184struct ScanFilter { 200struct ScanFilter {
185 NetworkId network_id; 201 NetworkId network_id;
186 NetworkType network_type; 202 NetworkType network_type;
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..8d8ac1ed7 100644
--- a/src/dedicated_room/yuzu_room.cpp
+++ b/src/dedicated_room/yuzu_room.cpp
@@ -76,7 +76,8 @@ 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 const auto remainder = token.size() % 3;
80 for (size_t i = 0; i < (3 - remainder); i++) {
80 token.push_back('='); 81 token.push_back('=');
81 } 82 }
82} 83}
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..572e55a5b 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(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/yuzu/main.cpp b/src/yuzu/main.cpp
index a85adc072..9dfa8d639 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -896,8 +896,8 @@ void GMainWindow::InitializeWidgets() {
896 } 896 }
897 897
898 // TODO (flTobi): Add the widget when multiplayer is fully implemented 898 // TODO (flTobi): Add the widget when multiplayer is fully implemented
899 // statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0); 899 statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
900 // statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0); 900 statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
901 901
902 tas_label = new QLabel(); 902 tas_label = new QLabel();
903 tas_label->setObjectName(QStringLiteral("TASlabel")); 903 tas_label->setObjectName(QStringLiteral("TASlabel"));
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index cdf31b417..60a8deab1 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>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>
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/state.cpp b/src/yuzu/multiplayer/state.cpp
index 66e098296..3ad846028 100644
--- a/src/yuzu/multiplayer/state.cpp
+++ b/src/yuzu/multiplayer/state.cpp
@@ -249,6 +249,7 @@ void MultiplayerState::ShowNotification() {
249 return; // Do not show notification if the chat window currently has focus 249 return; // Do not show notification if the chat window currently has focus
250 show_notification = true; 250 show_notification = true;
251 QApplication::alert(nullptr); 251 QApplication::alert(nullptr);
252 QApplication::beep();
252 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16)); 253 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16));
253 status_text->setText(tr("New Messages Received")); 254 status_text->setText(tr("New Messages Received"));
254} 255}