summaryrefslogtreecommitdiff
path: root/src/network/room.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/room.cpp')
-rw-r--r--src/network/room.cpp1110
1 files changed, 1110 insertions, 0 deletions
diff --git a/src/network/room.cpp b/src/network/room.cpp
new file mode 100644
index 000000000..3fc3a0383
--- /dev/null
+++ b/src/network/room.cpp
@@ -0,0 +1,1110 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <atomic>
6#include <iomanip>
7#include <mutex>
8#include <random>
9#include <regex>
10#include <shared_mutex>
11#include <sstream>
12#include <thread>
13#include "common/logging/log.h"
14#include "enet/enet.h"
15#include "network/packet.h"
16#include "network/room.h"
17#include "network/verify_user.h"
18
19namespace Network {
20
21class Room::RoomImpl {
22public:
23 // This MAC address is used to generate a 'Nintendo' like Mac address.
24 const MacAddress NintendoOUI;
25 std::mt19937 random_gen; ///< Random number generator. Used for GenerateMacAddress
26
27 ENetHost* server = nullptr; ///< Network interface.
28
29 std::atomic<State> state{State::Closed}; ///< Current state of the room.
30 RoomInformation room_information; ///< Information about this room.
31
32 std::string verify_uid; ///< A GUID which may be used for verfication.
33 mutable std::mutex verify_uid_mutex; ///< Mutex for verify_uid
34
35 std::string password; ///< The password required to connect to this room.
36
37 struct Member {
38 std::string nickname; ///< The nickname of the member.
39 std::string console_id_hash; ///< A hash of the console ID of the member.
40 GameInfo game_info; ///< The current game of the member
41 MacAddress mac_address; ///< The assigned mac address of the member.
42 /// Data of the user, often including authenticated forum username.
43 VerifyUser::UserData user_data;
44 ENetPeer* peer; ///< The remote peer.
45 };
46 using MemberList = std::vector<Member>;
47 MemberList members; ///< Information about the members of this room
48 mutable std::shared_mutex member_mutex; ///< Mutex for locking the members list
49
50 UsernameBanList username_ban_list; ///< List of banned usernames
51 IPBanList ip_ban_list; ///< List of banned IP addresses
52 mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists
53
54 RoomImpl()
55 : NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00}, random_gen(std::random_device()()) {}
56
57 /// Thread that receives and dispatches network packets
58 std::unique_ptr<std::thread> room_thread;
59
60 /// Verification backend of the room
61 std::unique_ptr<VerifyUser::Backend> verify_backend;
62
63 /// Thread function that will receive and dispatch messages until the room is destroyed.
64 void ServerLoop();
65 void StartLoop();
66
67 /**
68 * Parses and answers a room join request from a client.
69 * Validates the uniqueness of the username and assigns the MAC address
70 * that the client will use for the remainder of the connection.
71 */
72 void HandleJoinRequest(const ENetEvent* event);
73
74 /**
75 * Parses and answers a kick request from a client.
76 * Validates the permissions and that the given user exists and then kicks the member.
77 */
78 void HandleModKickPacket(const ENetEvent* event);
79
80 /**
81 * Parses and answers a ban request from a client.
82 * Validates the permissions and bans the user (by forum username or IP).
83 */
84 void HandleModBanPacket(const ENetEvent* event);
85
86 /**
87 * Parses and answers a unban request from a client.
88 * Validates the permissions and unbans the address.
89 */
90 void HandleModUnbanPacket(const ENetEvent* event);
91
92 /**
93 * Parses and answers a get ban list request from a client.
94 * Validates the permissions and returns the ban list.
95 */
96 void HandleModGetBanListPacket(const ENetEvent* event);
97
98 /**
99 * Returns whether the nickname is valid, ie. isn't already taken by someone else in the room.
100 */
101 bool IsValidNickname(const std::string& nickname) const;
102
103 /**
104 * Returns whether the MAC address is valid, ie. isn't already taken by someone else in the
105 * room.
106 */
107 bool IsValidMacAddress(const MacAddress& address) const;
108
109 /**
110 * Returns whether the console ID (hash) is valid, ie. isn't already taken by someone else in
111 * the room.
112 */
113 bool IsValidConsoleId(const std::string& console_id_hash) const;
114
115 /**
116 * Returns whether a user has mod permissions.
117 */
118 bool HasModPermission(const ENetPeer* client) const;
119
120 /**
121 * Sends a ID_ROOM_IS_FULL message telling the client that the room is full.
122 */
123 void SendRoomIsFull(ENetPeer* client);
124
125 /**
126 * Sends a ID_ROOM_NAME_COLLISION message telling the client that the name is invalid.
127 */
128 void SendNameCollision(ENetPeer* client);
129
130 /**
131 * Sends a ID_ROOM_MAC_COLLISION message telling the client that the MAC is invalid.
132 */
133 void SendMacCollision(ENetPeer* client);
134
135 /**
136 * Sends a IdConsoleIdCollison message telling the client that another member with the same
137 * console ID exists.
138 */
139 void SendConsoleIdCollision(ENetPeer* client);
140
141 /**
142 * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid.
143 */
144 void SendVersionMismatch(ENetPeer* client);
145
146 /**
147 * Sends a ID_ROOM_WRONG_PASSWORD message telling the client that the password is wrong.
148 */
149 void SendWrongPassword(ENetPeer* client);
150
151 /**
152 * Notifies the member that its connection attempt was successful,
153 * and it is now part of the room.
154 */
155 void SendJoinSuccess(ENetPeer* client, MacAddress mac_address);
156
157 /**
158 * Notifies the member that its connection attempt was successful,
159 * and it is now part of the room, and it has been granted mod permissions.
160 */
161 void SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address);
162
163 /**
164 * Sends a IdHostKicked message telling the client that they have been kicked.
165 */
166 void SendUserKicked(ENetPeer* client);
167
168 /**
169 * Sends a IdHostBanned message telling the client that they have been banned.
170 */
171 void SendUserBanned(ENetPeer* client);
172
173 /**
174 * Sends a IdModPermissionDenied message telling the client that they do not have mod
175 * permission.
176 */
177 void SendModPermissionDenied(ENetPeer* client);
178
179 /**
180 * Sends a IdModNoSuchUser message telling the client that the given user could not be found.
181 */
182 void SendModNoSuchUser(ENetPeer* client);
183
184 /**
185 * Sends the ban list in response to a client's request for getting ban list.
186 */
187 void SendModBanListResponse(ENetPeer* client);
188
189 /**
190 * Notifies the members that the room is closed,
191 */
192 void SendCloseMessage();
193
194 /**
195 * Sends a system message to all the connected clients.
196 */
197 void SendStatusMessage(StatusMessageTypes type, const std::string& nickname,
198 const std::string& username, const std::string& ip);
199
200 /**
201 * Sends the information about the room, along with the list of members
202 * to every connected client in the room.
203 * The packet has the structure:
204 * <MessageID>ID_ROOM_INFORMATION
205 * <String> room_name
206 * <String> room_description
207 * <u32> member_slots: The max number of clients allowed in this room
208 * <String> uid
209 * <u16> port
210 * <u32> num_members: the number of currently joined clients
211 * This is followed by the following three values for each member:
212 * <String> nickname of that member
213 * <MacAddress> mac_address of that member
214 * <String> game_name of that member
215 */
216 void BroadcastRoomInformation();
217
218 /**
219 * Generates a free MAC address to assign to a new client.
220 * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32
221 */
222 MacAddress GenerateMacAddress();
223
224 /**
225 * Broadcasts this packet to all members except the sender.
226 * @param event The ENet event containing the data
227 */
228 void HandleWifiPacket(const ENetEvent* event);
229
230 /**
231 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
232 * @param event The ENet event that was received.
233 */
234 void HandleChatPacket(const ENetEvent* event);
235
236 /**
237 * Extracts the game name from a received ENet packet and broadcasts it.
238 * @param event The ENet event that was received.
239 */
240 void HandleGameNamePacket(const ENetEvent* event);
241
242 /**
243 * Removes the client from the members list if it was in it and announces the change
244 * to all other clients.
245 */
246 void HandleClientDisconnection(ENetPeer* client);
247};
248
249// RoomImpl
250void Room::RoomImpl::ServerLoop() {
251 while (state != State::Closed) {
252 ENetEvent event;
253 if (enet_host_service(server, &event, 16) > 0) {
254 switch (event.type) {
255 case ENET_EVENT_TYPE_RECEIVE:
256 switch (event.packet->data[0]) {
257 case IdJoinRequest:
258 HandleJoinRequest(&event);
259 break;
260 case IdSetGameInfo:
261 HandleGameNamePacket(&event);
262 break;
263 case IdWifiPacket:
264 HandleWifiPacket(&event);
265 break;
266 case IdChatMessage:
267 HandleChatPacket(&event);
268 break;
269 // Moderation
270 case IdModKick:
271 HandleModKickPacket(&event);
272 break;
273 case IdModBan:
274 HandleModBanPacket(&event);
275 break;
276 case IdModUnban:
277 HandleModUnbanPacket(&event);
278 break;
279 case IdModGetBanList:
280 HandleModGetBanListPacket(&event);
281 break;
282 }
283 enet_packet_destroy(event.packet);
284 break;
285 case ENET_EVENT_TYPE_DISCONNECT:
286 HandleClientDisconnection(event.peer);
287 break;
288 case ENET_EVENT_TYPE_NONE:
289 case ENET_EVENT_TYPE_CONNECT:
290 break;
291 }
292 }
293 }
294 // Close the connection to all members:
295 SendCloseMessage();
296}
297
298void Room::RoomImpl::StartLoop() {
299 room_thread = std::make_unique<std::thread>(&Room::RoomImpl::ServerLoop, this);
300}
301
302void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
303 {
304 std::lock_guard lock(member_mutex);
305 if (members.size() >= room_information.member_slots) {
306 SendRoomIsFull(event->peer);
307 return;
308 }
309 }
310 Packet packet;
311 packet.Append(event->packet->data, event->packet->dataLength);
312 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
313 std::string nickname;
314 packet.Read(nickname);
315
316 std::string console_id_hash;
317 packet.Read(console_id_hash);
318
319 MacAddress preferred_mac;
320 packet.Read(preferred_mac);
321
322 u32 client_version;
323 packet.Read(client_version);
324
325 std::string pass;
326 packet.Read(pass);
327
328 std::string token;
329 packet.Read(token);
330
331 if (pass != password) {
332 SendWrongPassword(event->peer);
333 return;
334 }
335
336 if (!IsValidNickname(nickname)) {
337 SendNameCollision(event->peer);
338 return;
339 }
340
341 if (preferred_mac != NoPreferredMac) {
342 // Verify if the preferred mac is available
343 if (!IsValidMacAddress(preferred_mac)) {
344 SendMacCollision(event->peer);
345 return;
346 }
347 } else {
348 // Assign a MAC address of this client automatically
349 preferred_mac = GenerateMacAddress();
350 }
351
352 if (!IsValidConsoleId(console_id_hash)) {
353 SendConsoleIdCollision(event->peer);
354 return;
355 }
356
357 if (client_version != network_version) {
358 SendVersionMismatch(event->peer);
359 return;
360 }
361
362 // At this point the client is ready to be added to the room.
363 Member member{};
364 member.mac_address = preferred_mac;
365 member.console_id_hash = console_id_hash;
366 member.nickname = nickname;
367 member.peer = event->peer;
368
369 std::string uid;
370 {
371 std::lock_guard lock(verify_uid_mutex);
372 uid = verify_uid;
373 }
374 member.user_data = verify_backend->LoadUserData(uid, token);
375
376 std::string ip;
377 {
378 std::lock_guard lock(ban_list_mutex);
379
380 // Check username ban
381 if (!member.user_data.username.empty() &&
382 std::find(username_ban_list.begin(), username_ban_list.end(),
383 member.user_data.username) != username_ban_list.end()) {
384
385 SendUserBanned(event->peer);
386 return;
387 }
388
389 // Check IP ban
390 std::array<char, 256> ip_raw{};
391 enet_address_get_host_ip(&event->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
392 ip = ip_raw.data();
393
394 if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) != ip_ban_list.end()) {
395 SendUserBanned(event->peer);
396 return;
397 }
398 }
399
400 // Notify everyone that the user has joined.
401 SendStatusMessage(IdMemberJoin, member.nickname, member.user_data.username, ip);
402
403 {
404 std::lock_guard lock(member_mutex);
405 members.push_back(std::move(member));
406 }
407
408 // Notify everyone that the room information has changed.
409 BroadcastRoomInformation();
410 if (HasModPermission(event->peer)) {
411 SendJoinSuccessAsMod(event->peer, preferred_mac);
412 } else {
413 SendJoinSuccess(event->peer, preferred_mac);
414 }
415}
416
417void Room::RoomImpl::HandleModKickPacket(const ENetEvent* event) {
418 if (!HasModPermission(event->peer)) {
419 SendModPermissionDenied(event->peer);
420 return;
421 }
422
423 Packet packet;
424 packet.Append(event->packet->data, event->packet->dataLength);
425 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
426
427 std::string nickname;
428 packet.Read(nickname);
429
430 std::string username, ip;
431 {
432 std::lock_guard lock(member_mutex);
433 const auto target_member =
434 std::find_if(members.begin(), members.end(),
435 [&nickname](const auto& member) { return member.nickname == nickname; });
436 if (target_member == members.end()) {
437 SendModNoSuchUser(event->peer);
438 return;
439 }
440
441 // Notify the kicked member
442 SendUserKicked(target_member->peer);
443
444 username = target_member->user_data.username;
445
446 std::array<char, 256> ip_raw{};
447 enet_address_get_host_ip(&target_member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
448 ip = ip_raw.data();
449
450 enet_peer_disconnect(target_member->peer, 0);
451 members.erase(target_member);
452 }
453
454 // Announce the change to all clients.
455 SendStatusMessage(IdMemberKicked, nickname, username, ip);
456 BroadcastRoomInformation();
457}
458
459void Room::RoomImpl::HandleModBanPacket(const ENetEvent* event) {
460 if (!HasModPermission(event->peer)) {
461 SendModPermissionDenied(event->peer);
462 return;
463 }
464
465 Packet packet;
466 packet.Append(event->packet->data, event->packet->dataLength);
467 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
468
469 std::string nickname;
470 packet.Read(nickname);
471
472 std::string username, ip;
473 {
474 std::lock_guard lock(member_mutex);
475 const auto target_member =
476 std::find_if(members.begin(), members.end(),
477 [&nickname](const auto& member) { return member.nickname == nickname; });
478 if (target_member == members.end()) {
479 SendModNoSuchUser(event->peer);
480 return;
481 }
482
483 // Notify the banned member
484 SendUserBanned(target_member->peer);
485
486 nickname = target_member->nickname;
487 username = target_member->user_data.username;
488
489 std::array<char, 256> ip_raw{};
490 enet_address_get_host_ip(&target_member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
491 ip = ip_raw.data();
492
493 enet_peer_disconnect(target_member->peer, 0);
494 members.erase(target_member);
495 }
496
497 {
498 std::lock_guard lock(ban_list_mutex);
499
500 if (!username.empty()) {
501 // Ban the forum username
502 if (std::find(username_ban_list.begin(), username_ban_list.end(), username) ==
503 username_ban_list.end()) {
504
505 username_ban_list.emplace_back(username);
506 }
507 }
508
509 // Ban the member's IP as well
510 if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) == ip_ban_list.end()) {
511 ip_ban_list.emplace_back(ip);
512 }
513 }
514
515 // Announce the change to all clients.
516 SendStatusMessage(IdMemberBanned, nickname, username, ip);
517 BroadcastRoomInformation();
518}
519
520void Room::RoomImpl::HandleModUnbanPacket(const ENetEvent* event) {
521 if (!HasModPermission(event->peer)) {
522 SendModPermissionDenied(event->peer);
523 return;
524 }
525
526 Packet packet;
527 packet.Append(event->packet->data, event->packet->dataLength);
528 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
529
530 std::string address;
531 packet.Read(address);
532
533 bool unbanned = false;
534 {
535 std::lock_guard lock(ban_list_mutex);
536
537 auto it = std::find(username_ban_list.begin(), username_ban_list.end(), address);
538 if (it != username_ban_list.end()) {
539 unbanned = true;
540 username_ban_list.erase(it);
541 }
542
543 it = std::find(ip_ban_list.begin(), ip_ban_list.end(), address);
544 if (it != ip_ban_list.end()) {
545 unbanned = true;
546 ip_ban_list.erase(it);
547 }
548 }
549
550 if (unbanned) {
551 SendStatusMessage(IdAddressUnbanned, address, "", "");
552 } else {
553 SendModNoSuchUser(event->peer);
554 }
555}
556
557void Room::RoomImpl::HandleModGetBanListPacket(const ENetEvent* event) {
558 if (!HasModPermission(event->peer)) {
559 SendModPermissionDenied(event->peer);
560 return;
561 }
562
563 SendModBanListResponse(event->peer);
564}
565
566bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {
567 // A nickname is valid if it matches the regex and is not already taken by anybody else in the
568 // room.
569 const std::regex nickname_regex("^[ a-zA-Z0-9._-]{4,20}$");
570 if (!std::regex_match(nickname, nickname_regex))
571 return false;
572
573 std::lock_guard lock(member_mutex);
574 return std::all_of(members.begin(), members.end(),
575 [&nickname](const auto& member) { return member.nickname != nickname; });
576}
577
578bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const {
579 // A MAC address is valid if it is not already taken by anybody else in the room.
580 std::lock_guard lock(member_mutex);
581 return std::all_of(members.begin(), members.end(),
582 [&address](const auto& member) { return member.mac_address != address; });
583}
584
585bool Room::RoomImpl::IsValidConsoleId(const std::string& console_id_hash) const {
586 // A Console ID is valid if it is not already taken by anybody else in the room.
587 std::lock_guard lock(member_mutex);
588 return std::all_of(members.begin(), members.end(), [&console_id_hash](const auto& member) {
589 return member.console_id_hash != console_id_hash;
590 });
591}
592
593bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const {
594 std::lock_guard lock(member_mutex);
595 const auto sending_member =
596 std::find_if(members.begin(), members.end(),
597 [client](const auto& member) { return member.peer == client; });
598 if (sending_member == members.end()) {
599 return false;
600 }
601 if (room_information.enable_yuzu_mods &&
602 sending_member->user_data.moderator) { // Community moderator
603
604 return true;
605 }
606 if (!room_information.host_username.empty() &&
607 sending_member->user_data.username == room_information.host_username) { // Room host
608
609 return true;
610 }
611 return false;
612}
613
614void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
615 Packet packet;
616 packet.Write(static_cast<u8>(IdNameCollision));
617
618 ENetPacket* enet_packet =
619 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
620 enet_peer_send(client, 0, enet_packet);
621 enet_host_flush(server);
622}
623
624void Room::RoomImpl::SendMacCollision(ENetPeer* client) {
625 Packet packet;
626 packet.Write(static_cast<u8>(IdMacCollision));
627
628 ENetPacket* enet_packet =
629 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
630 enet_peer_send(client, 0, enet_packet);
631 enet_host_flush(server);
632}
633
634void Room::RoomImpl::SendConsoleIdCollision(ENetPeer* client) {
635 Packet packet;
636 packet.Write(static_cast<u8>(IdConsoleIdCollision));
637
638 ENetPacket* enet_packet =
639 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
640 enet_peer_send(client, 0, enet_packet);
641 enet_host_flush(server);
642}
643
644void Room::RoomImpl::SendWrongPassword(ENetPeer* client) {
645 Packet packet;
646 packet.Write(static_cast<u8>(IdWrongPassword));
647
648 ENetPacket* enet_packet =
649 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
650 enet_peer_send(client, 0, enet_packet);
651 enet_host_flush(server);
652}
653
654void Room::RoomImpl::SendRoomIsFull(ENetPeer* client) {
655 Packet packet;
656 packet.Write(static_cast<u8>(IdRoomIsFull));
657
658 ENetPacket* enet_packet =
659 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
660 enet_peer_send(client, 0, enet_packet);
661 enet_host_flush(server);
662}
663
664void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) {
665 Packet packet;
666 packet.Write(static_cast<u8>(IdVersionMismatch));
667 packet.Write(network_version);
668
669 ENetPacket* enet_packet =
670 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
671 enet_peer_send(client, 0, enet_packet);
672 enet_host_flush(server);
673}
674
675void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) {
676 Packet packet;
677 packet.Write(static_cast<u8>(IdJoinSuccess));
678 packet.Write(mac_address);
679 ENetPacket* enet_packet =
680 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
681 enet_peer_send(client, 0, enet_packet);
682 enet_host_flush(server);
683}
684
685void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address) {
686 Packet packet;
687 packet.Write(static_cast<u8>(IdJoinSuccessAsMod));
688 packet.Write(mac_address);
689 ENetPacket* enet_packet =
690 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
691 enet_peer_send(client, 0, enet_packet);
692 enet_host_flush(server);
693}
694
695void Room::RoomImpl::SendUserKicked(ENetPeer* client) {
696 Packet packet;
697 packet.Write(static_cast<u8>(IdHostKicked));
698
699 ENetPacket* enet_packet =
700 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
701 enet_peer_send(client, 0, enet_packet);
702 enet_host_flush(server);
703}
704
705void Room::RoomImpl::SendUserBanned(ENetPeer* client) {
706 Packet packet;
707 packet.Write(static_cast<u8>(IdHostBanned));
708
709 ENetPacket* enet_packet =
710 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
711 enet_peer_send(client, 0, enet_packet);
712 enet_host_flush(server);
713}
714
715void Room::RoomImpl::SendModPermissionDenied(ENetPeer* client) {
716 Packet packet;
717 packet.Write(static_cast<u8>(IdModPermissionDenied));
718
719 ENetPacket* enet_packet =
720 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
721 enet_peer_send(client, 0, enet_packet);
722 enet_host_flush(server);
723}
724
725void Room::RoomImpl::SendModNoSuchUser(ENetPeer* client) {
726 Packet packet;
727 packet.Write(static_cast<u8>(IdModNoSuchUser));
728
729 ENetPacket* enet_packet =
730 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
731 enet_peer_send(client, 0, enet_packet);
732 enet_host_flush(server);
733}
734
735void Room::RoomImpl::SendModBanListResponse(ENetPeer* client) {
736 Packet packet;
737 packet.Write(static_cast<u8>(IdModBanListResponse));
738 {
739 std::lock_guard lock(ban_list_mutex);
740 packet.Write(username_ban_list);
741 packet.Write(ip_ban_list);
742 }
743
744 ENetPacket* enet_packet =
745 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
746 enet_peer_send(client, 0, enet_packet);
747 enet_host_flush(server);
748}
749
750void Room::RoomImpl::SendCloseMessage() {
751 Packet packet;
752 packet.Write(static_cast<u8>(IdCloseRoom));
753 std::lock_guard lock(member_mutex);
754 if (!members.empty()) {
755 ENetPacket* enet_packet =
756 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
757 for (auto& member : members) {
758 enet_peer_send(member.peer, 0, enet_packet);
759 }
760 }
761 enet_host_flush(server);
762 for (auto& member : members) {
763 enet_peer_disconnect(member.peer, 0);
764 }
765}
766
767void Room::RoomImpl::SendStatusMessage(StatusMessageTypes type, const std::string& nickname,
768 const std::string& username, const std::string& ip) {
769 Packet packet;
770 packet.Write(static_cast<u8>(IdStatusMessage));
771 packet.Write(static_cast<u8>(type));
772 packet.Write(nickname);
773 packet.Write(username);
774 std::lock_guard lock(member_mutex);
775 if (!members.empty()) {
776 ENetPacket* enet_packet =
777 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
778 for (auto& member : members) {
779 enet_peer_send(member.peer, 0, enet_packet);
780 }
781 }
782 enet_host_flush(server);
783
784 const std::string display_name =
785 username.empty() ? nickname : fmt::format("{} ({})", nickname, username);
786
787 switch (type) {
788 case IdMemberJoin:
789 LOG_INFO(Network, "[{}] {} has joined.", ip, display_name);
790 break;
791 case IdMemberLeave:
792 LOG_INFO(Network, "[{}] {} has left.", ip, display_name);
793 break;
794 case IdMemberKicked:
795 LOG_INFO(Network, "[{}] {} has been kicked.", ip, display_name);
796 break;
797 case IdMemberBanned:
798 LOG_INFO(Network, "[{}] {} has been banned.", ip, display_name);
799 break;
800 case IdAddressUnbanned:
801 LOG_INFO(Network, "{} has been unbanned.", display_name);
802 break;
803 }
804}
805
806void Room::RoomImpl::BroadcastRoomInformation() {
807 Packet packet;
808 packet.Write(static_cast<u8>(IdRoomInformation));
809 packet.Write(room_information.name);
810 packet.Write(room_information.description);
811 packet.Write(room_information.member_slots);
812 packet.Write(room_information.port);
813 packet.Write(room_information.preferred_game.name);
814 packet.Write(room_information.host_username);
815
816 packet.Write(static_cast<u32>(members.size()));
817 {
818 std::lock_guard lock(member_mutex);
819 for (const auto& member : members) {
820 packet.Write(member.nickname);
821 packet.Write(member.mac_address);
822 packet.Write(member.game_info.name);
823 packet.Write(member.game_info.id);
824 packet.Write(member.user_data.username);
825 packet.Write(member.user_data.display_name);
826 packet.Write(member.user_data.avatar_url);
827 }
828 }
829
830 ENetPacket* enet_packet =
831 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
832 enet_host_broadcast(server, 0, enet_packet);
833 enet_host_flush(server);
834}
835
836MacAddress Room::RoomImpl::GenerateMacAddress() {
837 MacAddress result_mac =
838 NintendoOUI; // The first three bytes of each MAC address will be the NintendoOUI
839 std::uniform_int_distribution<> dis(0x00, 0xFF); // Random byte between 0 and 0xFF
840 do {
841 for (std::size_t i = 3; i < result_mac.size(); ++i) {
842 result_mac[i] = dis(random_gen);
843 }
844 } while (!IsValidMacAddress(result_mac));
845 return result_mac;
846}
847
848void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) {
849 Packet in_packet;
850 in_packet.Append(event->packet->data, event->packet->dataLength);
851 in_packet.IgnoreBytes(sizeof(u8)); // Message type
852 in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Type
853 in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Channel
854 in_packet.IgnoreBytes(sizeof(MacAddress)); // WifiPacket Transmitter Address
855 MacAddress destination_address;
856 in_packet.Read(destination_address);
857
858 Packet out_packet;
859 out_packet.Append(event->packet->data, event->packet->dataLength);
860 ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
861 ENET_PACKET_FLAG_RELIABLE);
862
863 if (destination_address == BroadcastMac) { // Send the data to everyone except the sender
864 std::lock_guard lock(member_mutex);
865 bool sent_packet = false;
866 for (const auto& member : members) {
867 if (member.peer != event->peer) {
868 sent_packet = true;
869 enet_peer_send(member.peer, 0, enet_packet);
870 }
871 }
872
873 if (!sent_packet) {
874 enet_packet_destroy(enet_packet);
875 }
876 } else { // Send the data only to the destination client
877 std::lock_guard lock(member_mutex);
878 auto member = std::find_if(members.begin(), members.end(),
879 [destination_address](const Member& member_entry) -> bool {
880 return member_entry.mac_address == destination_address;
881 });
882 if (member != members.end()) {
883 enet_peer_send(member->peer, 0, enet_packet);
884 } else {
885 LOG_ERROR(Network,
886 "Attempting to send to unknown MAC address: "
887 "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
888 destination_address[0], destination_address[1], destination_address[2],
889 destination_address[3], destination_address[4], destination_address[5]);
890 enet_packet_destroy(enet_packet);
891 }
892 }
893 enet_host_flush(server);
894}
895
896void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
897 Packet in_packet;
898 in_packet.Append(event->packet->data, event->packet->dataLength);
899
900 in_packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
901 std::string message;
902 in_packet.Read(message);
903 auto CompareNetworkAddress = [event](const Member member) -> bool {
904 return member.peer == event->peer;
905 };
906
907 std::lock_guard lock(member_mutex);
908 const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress);
909 if (sending_member == members.end()) {
910 return; // Received a chat message from a unknown sender
911 }
912
913 // Limit the size of chat messages to MaxMessageSize
914 message.resize(std::min(static_cast<u32>(message.size()), MaxMessageSize));
915
916 Packet out_packet;
917 out_packet.Write(static_cast<u8>(IdChatMessage));
918 out_packet.Write(sending_member->nickname);
919 out_packet.Write(sending_member->user_data.username);
920 out_packet.Write(message);
921
922 ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
923 ENET_PACKET_FLAG_RELIABLE);
924 bool sent_packet = false;
925 for (const auto& member : members) {
926 if (member.peer != event->peer) {
927 sent_packet = true;
928 enet_peer_send(member.peer, 0, enet_packet);
929 }
930 }
931
932 if (!sent_packet) {
933 enet_packet_destroy(enet_packet);
934 }
935
936 enet_host_flush(server);
937
938 if (sending_member->user_data.username.empty()) {
939 LOG_INFO(Network, "{}: {}", sending_member->nickname, message);
940 } else {
941 LOG_INFO(Network, "{} ({}): {}", sending_member->nickname,
942 sending_member->user_data.username, message);
943 }
944}
945
946void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) {
947 Packet in_packet;
948 in_packet.Append(event->packet->data, event->packet->dataLength);
949
950 in_packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
951 GameInfo game_info;
952 in_packet.Read(game_info.name);
953 in_packet.Read(game_info.id);
954
955 {
956 std::lock_guard lock(member_mutex);
957 auto member = std::find_if(members.begin(), members.end(),
958 [event](const Member& member_entry) -> bool {
959 return member_entry.peer == event->peer;
960 });
961 if (member != members.end()) {
962 member->game_info = game_info;
963
964 const std::string display_name =
965 member->user_data.username.empty()
966 ? member->nickname
967 : fmt::format("{} ({})", member->nickname, member->user_data.username);
968
969 if (game_info.name.empty()) {
970 LOG_INFO(Network, "{} is not playing", display_name);
971 } else {
972 LOG_INFO(Network, "{} is playing {}", display_name, game_info.name);
973 }
974 }
975 }
976 BroadcastRoomInformation();
977}
978
979void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) {
980 // Remove the client from the members list.
981 std::string nickname, username, ip;
982 {
983 std::lock_guard lock(member_mutex);
984 auto member =
985 std::find_if(members.begin(), members.end(), [client](const Member& member_entry) {
986 return member_entry.peer == client;
987 });
988 if (member != members.end()) {
989 nickname = member->nickname;
990 username = member->user_data.username;
991
992 std::array<char, 256> ip_raw{};
993 enet_address_get_host_ip(&member->peer->address, ip_raw.data(), sizeof(ip_raw) - 1);
994 ip = ip_raw.data();
995
996 members.erase(member);
997 }
998 }
999
1000 // Announce the change to all clients.
1001 enet_peer_disconnect(client, 0);
1002 if (!nickname.empty())
1003 SendStatusMessage(IdMemberLeave, nickname, username, ip);
1004 BroadcastRoomInformation();
1005}
1006
1007// Room
1008Room::Room() : room_impl{std::make_unique<RoomImpl>()} {}
1009
1010Room::~Room() = default;
1011
1012bool Room::Create(const std::string& name, const std::string& description,
1013 const std::string& server_address, u16 server_port, const std::string& password,
1014 const u32 max_connections, const std::string& host_username,
1015 const GameInfo preferred_game,
1016 std::unique_ptr<VerifyUser::Backend> verify_backend,
1017 const Room::BanList& ban_list, bool enable_yuzu_mods) {
1018 ENetAddress address;
1019 address.host = ENET_HOST_ANY;
1020 if (!server_address.empty()) {
1021 enet_address_set_host(&address, server_address.c_str());
1022 }
1023 address.port = server_port;
1024
1025 // In order to send the room is full message to the connecting client, we need to leave one
1026 // slot open so enet won't reject the incoming connection without telling us
1027 room_impl->server = enet_host_create(&address, max_connections + 1, NumChannels, 0, 0);
1028 if (!room_impl->server) {
1029 return false;
1030 }
1031 room_impl->state = State::Open;
1032
1033 room_impl->room_information.name = name;
1034 room_impl->room_information.description = description;
1035 room_impl->room_information.member_slots = max_connections;
1036 room_impl->room_information.port = server_port;
1037 room_impl->room_information.preferred_game = preferred_game;
1038 room_impl->room_information.host_username = host_username;
1039 room_impl->room_information.enable_yuzu_mods = enable_yuzu_mods;
1040 room_impl->password = password;
1041 room_impl->verify_backend = std::move(verify_backend);
1042 room_impl->username_ban_list = ban_list.first;
1043 room_impl->ip_ban_list = ban_list.second;
1044
1045 room_impl->StartLoop();
1046 return true;
1047}
1048
1049Room::State Room::GetState() const {
1050 return room_impl->state;
1051}
1052
1053const RoomInformation& Room::GetRoomInformation() const {
1054 return room_impl->room_information;
1055}
1056
1057std::string Room::GetVerifyUID() const {
1058 std::lock_guard lock(room_impl->verify_uid_mutex);
1059 return room_impl->verify_uid;
1060}
1061
1062Room::BanList Room::GetBanList() const {
1063 std::lock_guard lock(room_impl->ban_list_mutex);
1064 return {room_impl->username_ban_list, room_impl->ip_ban_list};
1065}
1066
1067std::vector<Member> Room::GetRoomMemberList() const {
1068 std::vector<Member> member_list;
1069 std::lock_guard lock(room_impl->member_mutex);
1070 for (const auto& member_impl : room_impl->members) {
1071 Member member;
1072 member.nickname = member_impl.nickname;
1073 member.username = member_impl.user_data.username;
1074 member.display_name = member_impl.user_data.display_name;
1075 member.avatar_url = member_impl.user_data.avatar_url;
1076 member.mac_address = member_impl.mac_address;
1077 member.game = member_impl.game_info;
1078 member_list.push_back(member);
1079 }
1080 return member_list;
1081}
1082
1083bool Room::HasPassword() const {
1084 return !room_impl->password.empty();
1085}
1086
1087void Room::SetVerifyUID(const std::string& uid) {
1088 std::lock_guard lock(room_impl->verify_uid_mutex);
1089 room_impl->verify_uid = uid;
1090}
1091
1092void Room::Destroy() {
1093 room_impl->state = State::Closed;
1094 room_impl->room_thread->join();
1095 room_impl->room_thread.reset();
1096
1097 if (room_impl->server) {
1098 enet_host_destroy(room_impl->server);
1099 }
1100 room_impl->room_information = {};
1101 room_impl->server = nullptr;
1102 {
1103 std::lock_guard lock(room_impl->member_mutex);
1104 room_impl->members.clear();
1105 }
1106 room_impl->room_information.member_slots = 0;
1107 room_impl->room_information.name.clear();
1108}
1109
1110} // namespace Network