summaryrefslogtreecommitdiff
path: root/src/network
diff options
context:
space:
mode:
Diffstat (limited to 'src/network')
-rw-r--r--src/network/CMakeLists.txt16
-rw-r--r--src/network/network.cpp50
-rw-r--r--src/network/network.h33
-rw-r--r--src/network/packet.cpp262
-rw-r--r--src/network/packet.h165
-rw-r--r--src/network/room.cpp1110
-rw-r--r--src/network/room.h151
-rw-r--r--src/network/room_member.cpp696
-rw-r--r--src/network/room_member.h318
-rw-r--r--src/network/verify_user.cpp17
-rw-r--r--src/network/verify_user.h45
11 files changed, 2863 insertions, 0 deletions
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
new file mode 100644
index 000000000..382a69e2f
--- /dev/null
+++ b/src/network/CMakeLists.txt
@@ -0,0 +1,16 @@
1add_library(network STATIC
2 network.cpp
3 network.h
4 packet.cpp
5 packet.h
6 room.cpp
7 room.h
8 room_member.cpp
9 room_member.h
10 verify_user.cpp
11 verify_user.h
12)
13
14create_target_directory_groups(network)
15
16target_link_libraries(network PRIVATE common enet Boost::boost)
diff --git a/src/network/network.cpp b/src/network/network.cpp
new file mode 100644
index 000000000..0841e4134
--- /dev/null
+++ b/src/network/network.cpp
@@ -0,0 +1,50 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/assert.h"
5#include "common/logging/log.h"
6#include "enet/enet.h"
7#include "network/network.h"
8
9namespace Network {
10
11RoomNetwork::RoomNetwork() {
12 m_room = std::make_shared<Room>();
13 m_room_member = std::make_shared<RoomMember>();
14}
15
16bool RoomNetwork::Init() {
17 if (enet_initialize() != 0) {
18 LOG_ERROR(Network, "Error initalizing ENet");
19 return false;
20 }
21 m_room = std::make_shared<Room>();
22 m_room_member = std::make_shared<RoomMember>();
23 LOG_DEBUG(Network, "initialized OK");
24 return true;
25}
26
27std::weak_ptr<Room> RoomNetwork::GetRoom() {
28 return m_room;
29}
30
31std::weak_ptr<RoomMember> RoomNetwork::GetRoomMember() {
32 return m_room_member;
33}
34
35void RoomNetwork::Shutdown() {
36 if (m_room_member) {
37 if (m_room_member->IsConnected())
38 m_room_member->Leave();
39 m_room_member.reset();
40 }
41 if (m_room) {
42 if (m_room->GetState() == Room::State::Open)
43 m_room->Destroy();
44 m_room.reset();
45 }
46 enet_deinitialize();
47 LOG_DEBUG(Network, "shutdown OK");
48}
49
50} // namespace Network
diff --git a/src/network/network.h b/src/network/network.h
new file mode 100644
index 000000000..e4de207b2
--- /dev/null
+++ b/src/network/network.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include "network/room.h"
8#include "network/room_member.h"
9
10namespace Network {
11
12class RoomNetwork {
13public:
14 RoomNetwork();
15
16 /// Initializes and registers the network device, the room, and the room member.
17 bool Init();
18
19 /// Returns a pointer to the room handle
20 std::weak_ptr<Room> GetRoom();
21
22 /// Returns a pointer to the room member handle
23 std::weak_ptr<RoomMember> GetRoomMember();
24
25 /// Unregisters the network device, the room, and the room member and shut them down.
26 void Shutdown();
27
28private:
29 std::shared_ptr<RoomMember> m_room_member; ///< RoomMember (Client) for network games
30 std::shared_ptr<Room> m_room; ///< Room (Server) for network games
31};
32
33} // namespace Network
diff --git a/src/network/packet.cpp b/src/network/packet.cpp
new file mode 100644
index 000000000..0e22f1eb4
--- /dev/null
+++ b/src/network/packet.cpp
@@ -0,0 +1,262 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#ifdef _WIN32
5#include <winsock2.h>
6#else
7#include <arpa/inet.h>
8#endif
9#include <cstring>
10#include <string>
11#include "network/packet.h"
12
13namespace Network {
14
15#ifndef htonll
16static u64 htonll(u64 x) {
17 return ((1 == htonl(1)) ? (x) : ((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32));
18}
19#endif
20
21#ifndef ntohll
22static u64 ntohll(u64 x) {
23 return ((1 == ntohl(1)) ? (x) : ((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32));
24}
25#endif
26
27void Packet::Append(const void* in_data, std::size_t size_in_bytes) {
28 if (in_data && (size_in_bytes > 0)) {
29 std::size_t start = data.size();
30 data.resize(start + size_in_bytes);
31 std::memcpy(&data[start], in_data, size_in_bytes);
32 }
33}
34
35void Packet::Read(void* out_data, std::size_t size_in_bytes) {
36 if (out_data && CheckSize(size_in_bytes)) {
37 std::memcpy(out_data, &data[read_pos], size_in_bytes);
38 read_pos += size_in_bytes;
39 }
40}
41
42void Packet::Clear() {
43 data.clear();
44 read_pos = 0;
45 is_valid = true;
46}
47
48const void* Packet::GetData() const {
49 return !data.empty() ? &data[0] : nullptr;
50}
51
52void Packet::IgnoreBytes(u32 length) {
53 read_pos += length;
54}
55
56std::size_t Packet::GetDataSize() const {
57 return data.size();
58}
59
60bool Packet::EndOfPacket() const {
61 return read_pos >= data.size();
62}
63
64Packet::operator bool() const {
65 return is_valid;
66}
67
68Packet& Packet::Read(bool& out_data) {
69 u8 value{};
70 if (Read(value)) {
71 out_data = (value != 0);
72 }
73 return *this;
74}
75
76Packet& Packet::Read(s8& out_data) {
77 Read(&out_data, sizeof(out_data));
78 return *this;
79}
80
81Packet& Packet::Read(u8& out_data) {
82 Read(&out_data, sizeof(out_data));
83 return *this;
84}
85
86Packet& Packet::Read(s16& out_data) {
87 s16 value{};
88 Read(&value, sizeof(value));
89 out_data = ntohs(value);
90 return *this;
91}
92
93Packet& Packet::Read(u16& out_data) {
94 u16 value{};
95 Read(&value, sizeof(value));
96 out_data = ntohs(value);
97 return *this;
98}
99
100Packet& Packet::Read(s32& out_data) {
101 s32 value{};
102 Read(&value, sizeof(value));
103 out_data = ntohl(value);
104 return *this;
105}
106
107Packet& Packet::Read(u32& out_data) {
108 u32 value{};
109 Read(&value, sizeof(value));
110 out_data = ntohl(value);
111 return *this;
112}
113
114Packet& Packet::Read(s64& out_data) {
115 s64 value{};
116 Read(&value, sizeof(value));
117 out_data = ntohll(value);
118 return *this;
119}
120
121Packet& Packet::Read(u64& out_data) {
122 u64 value{};
123 Read(&value, sizeof(value));
124 out_data = ntohll(value);
125 return *this;
126}
127
128Packet& Packet::Read(float& out_data) {
129 Read(&out_data, sizeof(out_data));
130 return *this;
131}
132
133Packet& Packet::Read(double& out_data) {
134 Read(&out_data, sizeof(out_data));
135 return *this;
136}
137
138Packet& Packet::Read(char* out_data) {
139 // First extract string length
140 u32 length = 0;
141 Read(length);
142
143 if ((length > 0) && CheckSize(length)) {
144 // Then extract characters
145 std::memcpy(out_data, &data[read_pos], length);
146 out_data[length] = '\0';
147
148 // Update reading position
149 read_pos += length;
150 }
151
152 return *this;
153}
154
155Packet& Packet::Read(std::string& out_data) {
156 // First extract string length
157 u32 length = 0;
158 Read(length);
159
160 out_data.clear();
161 if ((length > 0) && CheckSize(length)) {
162 // Then extract characters
163 out_data.assign(&data[read_pos], length);
164
165 // Update reading position
166 read_pos += length;
167 }
168
169 return *this;
170}
171
172Packet& Packet::Write(bool in_data) {
173 Write(static_cast<u8>(in_data));
174 return *this;
175}
176
177Packet& Packet::Write(s8 in_data) {
178 Append(&in_data, sizeof(in_data));
179 return *this;
180}
181
182Packet& Packet::Write(u8 in_data) {
183 Append(&in_data, sizeof(in_data));
184 return *this;
185}
186
187Packet& Packet::Write(s16 in_data) {
188 s16 toWrite = htons(in_data);
189 Append(&toWrite, sizeof(toWrite));
190 return *this;
191}
192
193Packet& Packet::Write(u16 in_data) {
194 u16 toWrite = htons(in_data);
195 Append(&toWrite, sizeof(toWrite));
196 return *this;
197}
198
199Packet& Packet::Write(s32 in_data) {
200 s32 toWrite = htonl(in_data);
201 Append(&toWrite, sizeof(toWrite));
202 return *this;
203}
204
205Packet& Packet::Write(u32 in_data) {
206 u32 toWrite = htonl(in_data);
207 Append(&toWrite, sizeof(toWrite));
208 return *this;
209}
210
211Packet& Packet::Write(s64 in_data) {
212 s64 toWrite = htonll(in_data);
213 Append(&toWrite, sizeof(toWrite));
214 return *this;
215}
216
217Packet& Packet::Write(u64 in_data) {
218 u64 toWrite = htonll(in_data);
219 Append(&toWrite, sizeof(toWrite));
220 return *this;
221}
222
223Packet& Packet::Write(float in_data) {
224 Append(&in_data, sizeof(in_data));
225 return *this;
226}
227
228Packet& Packet::Write(double in_data) {
229 Append(&in_data, sizeof(in_data));
230 return *this;
231}
232
233Packet& Packet::Write(const char* in_data) {
234 // First insert string length
235 u32 length = static_cast<u32>(std::strlen(in_data));
236 Write(length);
237
238 // Then insert characters
239 Append(in_data, length * sizeof(char));
240
241 return *this;
242}
243
244Packet& Packet::Write(const std::string& in_data) {
245 // First insert string length
246 u32 length = static_cast<u32>(in_data.size());
247 Write(length);
248
249 // Then insert characters
250 if (length > 0)
251 Append(in_data.c_str(), length * sizeof(std::string::value_type));
252
253 return *this;
254}
255
256bool Packet::CheckSize(std::size_t size) {
257 is_valid = is_valid && (read_pos + size <= data.size());
258
259 return is_valid;
260}
261
262} // namespace Network
diff --git a/src/network/packet.h b/src/network/packet.h
new file mode 100644
index 000000000..e69217488
--- /dev/null
+++ b/src/network/packet.h
@@ -0,0 +1,165 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8#include "common/common_types.h"
9
10namespace Network {
11
12/// A class that serializes data for network transfer. It also handles endianess
13class Packet {
14public:
15 Packet() = default;
16 ~Packet() = default;
17
18 /**
19 * Append data to the end of the packet
20 * @param data Pointer to the sequence of bytes to append
21 * @param size_in_bytes Number of bytes to append
22 */
23 void Append(const void* data, std::size_t size_in_bytes);
24
25 /**
26 * Reads data from the current read position of the packet
27 * @param out_data Pointer where the data should get written to
28 * @param size_in_bytes Number of bytes to read
29 */
30 void Read(void* out_data, std::size_t size_in_bytes);
31
32 /**
33 * Clear the packet
34 * After calling Clear, the packet is empty.
35 */
36 void Clear();
37
38 /**
39 * Ignores bytes while reading
40 * @param length THe number of bytes to ignore
41 */
42 void IgnoreBytes(u32 length);
43
44 /**
45 * Get a pointer to the data contained in the packet
46 * @return Pointer to the data
47 */
48 const void* GetData() const;
49
50 /**
51 * This function returns the number of bytes pointed to by
52 * what getData returns.
53 * @return Data size, in bytes
54 */
55 std::size_t GetDataSize() const;
56
57 /**
58 * This function is useful to know if there is some data
59 * left to be read, without actually reading it.
60 * @return True if all data was read, false otherwise
61 */
62 bool EndOfPacket() const;
63
64 explicit operator bool() const;
65
66 /// Overloads of read function to read data from the packet
67 Packet& Read(bool& out_data);
68 Packet& Read(s8& out_data);
69 Packet& Read(u8& out_data);
70 Packet& Read(s16& out_data);
71 Packet& Read(u16& out_data);
72 Packet& Read(s32& out_data);
73 Packet& Read(u32& out_data);
74 Packet& Read(s64& out_data);
75 Packet& Read(u64& out_data);
76 Packet& Read(float& out_data);
77 Packet& Read(double& out_data);
78 Packet& Read(char* out_data);
79 Packet& Read(std::string& out_data);
80 template <typename T>
81 Packet& Read(std::vector<T>& out_data);
82 template <typename T, std::size_t S>
83 Packet& Read(std::array<T, S>& out_data);
84
85 /// Overloads of write function to write data into the packet
86 Packet& Write(bool in_data);
87 Packet& Write(s8 in_data);
88 Packet& Write(u8 in_data);
89 Packet& Write(s16 in_data);
90 Packet& Write(u16 in_data);
91 Packet& Write(s32 in_data);
92 Packet& Write(u32 in_data);
93 Packet& Write(s64 in_data);
94 Packet& Write(u64 in_data);
95 Packet& Write(float in_data);
96 Packet& Write(double in_data);
97 Packet& Write(const char* in_data);
98 Packet& Write(const std::string& in_data);
99 template <typename T>
100 Packet& Write(const std::vector<T>& in_data);
101 template <typename T, std::size_t S>
102 Packet& Write(const std::array<T, S>& data);
103
104private:
105 /**
106 * Check if the packet can extract a given number of bytes
107 * This function updates accordingly the state of the packet.
108 * @param size Size to check
109 * @return True if size bytes can be read from the packet
110 */
111 bool CheckSize(std::size_t size);
112
113 // Member data
114 std::vector<char> data; ///< Data stored in the packet
115 std::size_t read_pos = 0; ///< Current reading position in the packet
116 bool is_valid = true; ///< Reading state of the packet
117};
118
119template <typename T>
120Packet& Packet::Read(std::vector<T>& out_data) {
121 // First extract the size
122 u32 size = 0;
123 Read(size);
124 out_data.resize(size);
125
126 // Then extract the data
127 for (std::size_t i = 0; i < out_data.size(); ++i) {
128 T character;
129 Read(character);
130 out_data[i] = character;
131 }
132 return *this;
133}
134
135template <typename T, std::size_t S>
136Packet& Packet::Read(std::array<T, S>& out_data) {
137 for (std::size_t i = 0; i < out_data.size(); ++i) {
138 T character;
139 Read(character);
140 out_data[i] = character;
141 }
142 return *this;
143}
144
145template <typename T>
146Packet& Packet::Write(const std::vector<T>& in_data) {
147 // First insert the size
148 Write(static_cast<u32>(in_data.size()));
149
150 // Then insert the data
151 for (std::size_t i = 0; i < in_data.size(); ++i) {
152 Write(in_data[i]);
153 }
154 return *this;
155}
156
157template <typename T, std::size_t S>
158Packet& Packet::Write(const std::array<T, S>& in_data) {
159 for (std::size_t i = 0; i < in_data.size(); ++i) {
160 Write(in_data[i]);
161 }
162 return *this;
163}
164
165} // namespace Network
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
diff --git a/src/network/room.h b/src/network/room.h
new file mode 100644
index 000000000..6f7e3b5b5
--- /dev/null
+++ b/src/network/room.h
@@ -0,0 +1,151 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <memory>
8#include <string>
9#include <vector>
10#include "common/announce_multiplayer_room.h"
11#include "common/common_types.h"
12#include "network/verify_user.h"
13
14namespace Network {
15
16using AnnounceMultiplayerRoom::GameInfo;
17using AnnounceMultiplayerRoom::MacAddress;
18using AnnounceMultiplayerRoom::Member;
19using AnnounceMultiplayerRoom::RoomInformation;
20
21constexpr u32 network_version = 1; ///< The version of this Room and RoomMember
22
23constexpr u16 DefaultRoomPort = 24872;
24
25constexpr u32 MaxMessageSize = 500;
26
27/// Maximum number of concurrent connections allowed to this room.
28static constexpr u32 MaxConcurrentConnections = 254;
29
30constexpr std::size_t NumChannels = 1; // Number of channels used for the connection
31
32/// A special MAC address that tells the room we're joining to assign us a MAC address
33/// automatically.
34constexpr MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
35
36// 802.11 broadcast MAC address
37constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
38
39// The different types of messages that can be sent. The first byte of each packet defines the type
40enum RoomMessageTypes : u8 {
41 IdJoinRequest = 1,
42 IdJoinSuccess,
43 IdRoomInformation,
44 IdSetGameInfo,
45 IdWifiPacket,
46 IdChatMessage,
47 IdNameCollision,
48 IdMacCollision,
49 IdVersionMismatch,
50 IdWrongPassword,
51 IdCloseRoom,
52 IdRoomIsFull,
53 IdConsoleIdCollision,
54 IdStatusMessage,
55 IdHostKicked,
56 IdHostBanned,
57 /// Moderation requests
58 IdModKick,
59 IdModBan,
60 IdModUnban,
61 IdModGetBanList,
62 // Moderation responses
63 IdModBanListResponse,
64 IdModPermissionDenied,
65 IdModNoSuchUser,
66 IdJoinSuccessAsMod,
67};
68
69/// Types of system status messages
70enum StatusMessageTypes : u8 {
71 IdMemberJoin = 1, ///< Member joining
72 IdMemberLeave, ///< Member leaving
73 IdMemberKicked, ///< A member is kicked from the room
74 IdMemberBanned, ///< A member is banned from the room
75 IdAddressUnbanned, ///< A username / ip address is unbanned from the room
76};
77
78/// This is what a server [person creating a server] would use.
79class Room final {
80public:
81 enum class State : u8 {
82 Open, ///< The room is open and ready to accept connections.
83 Closed, ///< The room is not opened and can not accept connections.
84 };
85
86 Room();
87 ~Room();
88
89 /**
90 * Gets the current state of the room.
91 */
92 State GetState() const;
93
94 /**
95 * Gets the room information of the room.
96 */
97 const RoomInformation& GetRoomInformation() const;
98
99 /**
100 * Gets the verify UID of this room.
101 */
102 std::string GetVerifyUID() const;
103
104 /**
105 * Gets a list of the mbmers connected to the room.
106 */
107 std::vector<Member> GetRoomMemberList() const;
108
109 /**
110 * Checks if the room is password protected
111 */
112 bool HasPassword() const;
113
114 using UsernameBanList = std::vector<std::string>;
115 using IPBanList = std::vector<std::string>;
116
117 using BanList = std::pair<UsernameBanList, IPBanList>;
118
119 /**
120 * Creates the socket for this room. Will bind to default address if
121 * server is empty string.
122 */
123 bool Create(const std::string& name, const std::string& description = "",
124 const std::string& server = "", u16 server_port = DefaultRoomPort,
125 const std::string& password = "",
126 const u32 max_connections = MaxConcurrentConnections,
127 const std::string& host_username = "", const GameInfo = {},
128 std::unique_ptr<VerifyUser::Backend> verify_backend = nullptr,
129 const BanList& ban_list = {}, bool enable_yuzu_mods = false);
130
131 /**
132 * Sets the verification GUID of the room.
133 */
134 void SetVerifyUID(const std::string& uid);
135
136 /**
137 * Gets the ban list (including banned forum usernames and IPs) of the room.
138 */
139 BanList GetBanList() const;
140
141 /**
142 * Destroys the socket
143 */
144 void Destroy();
145
146private:
147 class RoomImpl;
148 std::unique_ptr<RoomImpl> room_impl;
149};
150
151} // namespace Network
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
new file mode 100644
index 000000000..e4f823e98
--- /dev/null
+++ b/src/network/room_member.cpp
@@ -0,0 +1,696 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <atomic>
5#include <list>
6#include <mutex>
7#include <set>
8#include <thread>
9#include "common/assert.h"
10#include "enet/enet.h"
11#include "network/packet.h"
12#include "network/room_member.h"
13
14namespace Network {
15
16constexpr u32 ConnectionTimeoutMs = 5000;
17
18class RoomMember::RoomMemberImpl {
19public:
20 ENetHost* client = nullptr; ///< ENet network interface.
21 ENetPeer* server = nullptr; ///< The server peer the client is connected to
22
23 /// Information about the clients connected to the same room as us.
24 MemberList member_information;
25 /// Information about the room we're connected to.
26 RoomInformation room_information;
27
28 /// The current game name, id and version
29 GameInfo current_game_info;
30
31 std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
32 void SetState(const State new_state);
33 void SetError(const Error new_error);
34 bool IsConnected() const;
35
36 std::string nickname; ///< The nickname of this member.
37
38 std::string username; ///< The username of this member.
39 mutable std::mutex username_mutex; ///< Mutex for locking username.
40
41 MacAddress mac_address; ///< The mac_address of this member.
42
43 std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
44 /// Thread that receives and dispatches network packets
45 std::unique_ptr<std::thread> loop_thread;
46 std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable.
47 std::list<Packet> send_list; ///< A list that stores all packets to send the async
48
49 template <typename T>
50 using CallbackSet = std::set<CallbackHandle<T>>;
51 std::mutex callback_mutex; ///< The mutex used for handling callbacks
52
53 class Callbacks {
54 public:
55 template <typename T>
56 CallbackSet<T>& Get();
57
58 private:
59 CallbackSet<WifiPacket> callback_set_wifi_packet;
60 CallbackSet<ChatEntry> callback_set_chat_messages;
61 CallbackSet<StatusMessageEntry> callback_set_status_messages;
62 CallbackSet<RoomInformation> callback_set_room_information;
63 CallbackSet<State> callback_set_state;
64 CallbackSet<Error> callback_set_error;
65 CallbackSet<Room::BanList> callback_set_ban_list;
66 };
67 Callbacks callbacks; ///< All CallbackSets to all events
68
69 void MemberLoop();
70
71 void StartLoop();
72
73 /**
74 * Sends data to the room. It will be send on channel 0 with flag RELIABLE
75 * @param packet The data to send
76 */
77 void Send(Packet&& packet);
78
79 /**
80 * Sends a request to the server, asking for permission to join a room with the specified
81 * nickname and preferred mac.
82 * @params nickname The desired nickname.
83 * @params console_id_hash A hash of the Console ID.
84 * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells
85 * @params password The password for the room
86 * the server to assign one for us.
87 */
88 void SendJoinRequest(const std::string& nickname_, const std::string& console_id_hash,
89 const MacAddress& preferred_mac = NoPreferredMac,
90 const std::string& password = "", const std::string& token = "");
91
92 /**
93 * Extracts a MAC Address from a received ENet packet.
94 * @param event The ENet event that was received.
95 */
96 void HandleJoinPacket(const ENetEvent* event);
97 /**
98 * Extracts RoomInformation and MemberInformation from a received ENet packet.
99 * @param event The ENet event that was received.
100 */
101 void HandleRoomInformationPacket(const ENetEvent* event);
102
103 /**
104 * Extracts a WifiPacket from a received ENet packet.
105 * @param event The ENet event that was received.
106 */
107 void HandleWifiPackets(const ENetEvent* event);
108
109 /**
110 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
111 * @param event The ENet event that was received.
112 */
113 void HandleChatPacket(const ENetEvent* event);
114
115 /**
116 * Extracts a system message entry from a received ENet packet and adds it to the system message
117 * queue.
118 * @param event The ENet event that was received.
119 */
120 void HandleStatusMessagePacket(const ENetEvent* event);
121
122 /**
123 * Extracts a ban list request response from a received ENet packet.
124 * @param event The ENet event that was received.
125 */
126 void HandleModBanListResponsePacket(const ENetEvent* event);
127
128 /**
129 * Disconnects the RoomMember from the Room
130 */
131 void Disconnect();
132
133 template <typename T>
134 void Invoke(const T& data);
135
136 template <typename T>
137 CallbackHandle<T> Bind(std::function<void(const T&)> callback);
138};
139
140// RoomMemberImpl
141void RoomMember::RoomMemberImpl::SetState(const State new_state) {
142 if (state != new_state) {
143 state = new_state;
144 Invoke<State>(state);
145 }
146}
147
148void RoomMember::RoomMemberImpl::SetError(const Error new_error) {
149 Invoke<Error>(new_error);
150}
151
152bool RoomMember::RoomMemberImpl::IsConnected() const {
153 return state == State::Joining || state == State::Joined || state == State::Moderator;
154}
155
156void RoomMember::RoomMemberImpl::MemberLoop() {
157 // Receive packets while the connection is open
158 while (IsConnected()) {
159 std::lock_guard lock(network_mutex);
160 ENetEvent event;
161 if (enet_host_service(client, &event, 16) > 0) {
162 switch (event.type) {
163 case ENET_EVENT_TYPE_RECEIVE:
164 switch (event.packet->data[0]) {
165 case IdWifiPacket:
166 HandleWifiPackets(&event);
167 break;
168 case IdChatMessage:
169 HandleChatPacket(&event);
170 break;
171 case IdStatusMessage:
172 HandleStatusMessagePacket(&event);
173 break;
174 case IdRoomInformation:
175 HandleRoomInformationPacket(&event);
176 break;
177 case IdJoinSuccess:
178 case IdJoinSuccessAsMod:
179 // The join request was successful, we are now in the room.
180 // If we joined successfully, there must be at least one client in the room: us.
181 ASSERT_MSG(member_information.size() > 0,
182 "We have not yet received member information.");
183 HandleJoinPacket(&event); // Get the MAC Address for the client
184 if (event.packet->data[0] == IdJoinSuccessAsMod) {
185 SetState(State::Moderator);
186 } else {
187 SetState(State::Joined);
188 }
189 break;
190 case IdModBanListResponse:
191 HandleModBanListResponsePacket(&event);
192 break;
193 case IdRoomIsFull:
194 SetState(State::Idle);
195 SetError(Error::RoomIsFull);
196 break;
197 case IdNameCollision:
198 SetState(State::Idle);
199 SetError(Error::NameCollision);
200 break;
201 case IdMacCollision:
202 SetState(State::Idle);
203 SetError(Error::MacCollision);
204 break;
205 case IdConsoleIdCollision:
206 SetState(State::Idle);
207 SetError(Error::ConsoleIdCollision);
208 break;
209 case IdVersionMismatch:
210 SetState(State::Idle);
211 SetError(Error::WrongVersion);
212 break;
213 case IdWrongPassword:
214 SetState(State::Idle);
215 SetError(Error::WrongPassword);
216 break;
217 case IdCloseRoom:
218 SetState(State::Idle);
219 SetError(Error::LostConnection);
220 break;
221 case IdHostKicked:
222 SetState(State::Idle);
223 SetError(Error::HostKicked);
224 break;
225 case IdHostBanned:
226 SetState(State::Idle);
227 SetError(Error::HostBanned);
228 break;
229 case IdModPermissionDenied:
230 SetError(Error::PermissionDenied);
231 break;
232 case IdModNoSuchUser:
233 SetError(Error::NoSuchUser);
234 break;
235 }
236 enet_packet_destroy(event.packet);
237 break;
238 case ENET_EVENT_TYPE_DISCONNECT:
239 if (state == State::Joined || state == State::Moderator) {
240 SetState(State::Idle);
241 SetError(Error::LostConnection);
242 }
243 break;
244 case ENET_EVENT_TYPE_NONE:
245 break;
246 case ENET_EVENT_TYPE_CONNECT:
247 // The ENET_EVENT_TYPE_CONNECT event can not possibly happen here because we're
248 // already connected
249 ASSERT_MSG(false, "Received unexpected connect event while already connected");
250 break;
251 }
252 }
253 std::list<Packet> packets;
254 {
255 std::lock_guard send_lock(send_list_mutex);
256 packets.swap(send_list);
257 }
258 for (const auto& packet : packets) {
259 ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
260 ENET_PACKET_FLAG_RELIABLE);
261 enet_peer_send(server, 0, enetPacket);
262 }
263 enet_host_flush(client);
264 }
265 Disconnect();
266};
267
268void RoomMember::RoomMemberImpl::StartLoop() {
269 loop_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::MemberLoop, this);
270}
271
272void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
273 std::lock_guard lock(send_list_mutex);
274 send_list.push_back(std::move(packet));
275}
276
277void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname_,
278 const std::string& console_id_hash,
279 const MacAddress& preferred_mac,
280 const std::string& password,
281 const std::string& token) {
282 Packet packet;
283 packet.Write(static_cast<u8>(IdJoinRequest));
284 packet.Write(nickname_);
285 packet.Write(console_id_hash);
286 packet.Write(preferred_mac);
287 packet.Write(network_version);
288 packet.Write(password);
289 packet.Write(token);
290 Send(std::move(packet));
291}
292
293void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* event) {
294 Packet packet;
295 packet.Append(event->packet->data, event->packet->dataLength);
296
297 // Ignore the first byte, which is the message id.
298 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
299
300 RoomInformation info{};
301 packet.Read(info.name);
302 packet.Read(info.description);
303 packet.Read(info.member_slots);
304 packet.Read(info.port);
305 packet.Read(info.preferred_game.name);
306 packet.Read(info.host_username);
307 room_information.name = info.name;
308 room_information.description = info.description;
309 room_information.member_slots = info.member_slots;
310 room_information.port = info.port;
311 room_information.preferred_game = info.preferred_game;
312 room_information.host_username = info.host_username;
313
314 u32 num_members;
315 packet.Read(num_members);
316 member_information.resize(num_members);
317
318 for (auto& member : member_information) {
319 packet.Read(member.nickname);
320 packet.Read(member.mac_address);
321 packet.Read(member.game_info.name);
322 packet.Read(member.game_info.id);
323 packet.Read(member.username);
324 packet.Read(member.display_name);
325 packet.Read(member.avatar_url);
326
327 {
328 std::lock_guard lock(username_mutex);
329 if (member.nickname == nickname) {
330 username = member.username;
331 }
332 }
333 }
334 Invoke(room_information);
335}
336
337void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
338 Packet packet;
339 packet.Append(event->packet->data, event->packet->dataLength);
340
341 // Ignore the first byte, which is the message id.
342 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
343
344 // Parse the MAC Address from the packet
345 packet.Read(mac_address);
346}
347
348void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
349 WifiPacket wifi_packet{};
350 Packet packet;
351 packet.Append(event->packet->data, event->packet->dataLength);
352
353 // Ignore the first byte, which is the message id.
354 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
355
356 // Parse the WifiPacket from the packet
357 u8 frame_type;
358 packet.Read(frame_type);
359 WifiPacket::PacketType type = static_cast<WifiPacket::PacketType>(frame_type);
360
361 wifi_packet.type = type;
362 packet.Read(wifi_packet.channel);
363 packet.Read(wifi_packet.transmitter_address);
364 packet.Read(wifi_packet.destination_address);
365 packet.Read(wifi_packet.data);
366
367 Invoke<WifiPacket>(wifi_packet);
368}
369
370void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
371 Packet packet;
372 packet.Append(event->packet->data, event->packet->dataLength);
373
374 // Ignore the first byte, which is the message id.
375 packet.IgnoreBytes(sizeof(u8));
376
377 ChatEntry chat_entry{};
378 packet.Read(chat_entry.nickname);
379 packet.Read(chat_entry.username);
380 packet.Read(chat_entry.message);
381 Invoke<ChatEntry>(chat_entry);
382}
383
384void RoomMember::RoomMemberImpl::HandleStatusMessagePacket(const ENetEvent* event) {
385 Packet packet;
386 packet.Append(event->packet->data, event->packet->dataLength);
387
388 // Ignore the first byte, which is the message id.
389 packet.IgnoreBytes(sizeof(u8));
390
391 StatusMessageEntry status_message_entry{};
392 u8 type{};
393 packet.Read(type);
394 status_message_entry.type = static_cast<StatusMessageTypes>(type);
395 packet.Read(status_message_entry.nickname);
396 packet.Read(status_message_entry.username);
397 Invoke<StatusMessageEntry>(status_message_entry);
398}
399
400void RoomMember::RoomMemberImpl::HandleModBanListResponsePacket(const ENetEvent* event) {
401 Packet packet;
402 packet.Append(event->packet->data, event->packet->dataLength);
403
404 // Ignore the first byte, which is the message id.
405 packet.IgnoreBytes(sizeof(u8));
406
407 Room::BanList ban_list = {};
408 packet.Read(ban_list.first);
409 packet.Read(ban_list.second);
410 Invoke<Room::BanList>(ban_list);
411}
412
413void RoomMember::RoomMemberImpl::Disconnect() {
414 member_information.clear();
415 room_information.member_slots = 0;
416 room_information.name.clear();
417
418 if (!server) {
419 return;
420 }
421 enet_peer_disconnect(server, 0);
422
423 ENetEvent event;
424 while (enet_host_service(client, &event, ConnectionTimeoutMs) > 0) {
425 switch (event.type) {
426 case ENET_EVENT_TYPE_RECEIVE:
427 enet_packet_destroy(event.packet); // Ignore all incoming data
428 break;
429 case ENET_EVENT_TYPE_DISCONNECT:
430 server = nullptr;
431 return;
432 case ENET_EVENT_TYPE_NONE:
433 case ENET_EVENT_TYPE_CONNECT:
434 break;
435 }
436 }
437 // didn't disconnect gracefully force disconnect
438 enet_peer_reset(server);
439 server = nullptr;
440}
441
442template <>
443RoomMember::RoomMemberImpl::CallbackSet<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
444 return callback_set_wifi_packet;
445}
446
447template <>
448RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>&
449RoomMember::RoomMemberImpl::Callbacks::Get() {
450 return callback_set_state;
451}
452
453template <>
454RoomMember::RoomMemberImpl::CallbackSet<RoomMember::Error>&
455RoomMember::RoomMemberImpl::Callbacks::Get() {
456 return callback_set_error;
457}
458
459template <>
460RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>&
461RoomMember::RoomMemberImpl::Callbacks::Get() {
462 return callback_set_room_information;
463}
464
465template <>
466RoomMember::RoomMemberImpl::CallbackSet<ChatEntry>& RoomMember::RoomMemberImpl::Callbacks::Get() {
467 return callback_set_chat_messages;
468}
469
470template <>
471RoomMember::RoomMemberImpl::CallbackSet<StatusMessageEntry>&
472RoomMember::RoomMemberImpl::Callbacks::Get() {
473 return callback_set_status_messages;
474}
475
476template <>
477RoomMember::RoomMemberImpl::CallbackSet<Room::BanList>&
478RoomMember::RoomMemberImpl::Callbacks::Get() {
479 return callback_set_ban_list;
480}
481
482template <typename T>
483void RoomMember::RoomMemberImpl::Invoke(const T& data) {
484 std::lock_guard lock(callback_mutex);
485 CallbackSet<T> callback_set = callbacks.Get<T>();
486 for (auto const& callback : callback_set) {
487 (*callback)(data);
488 }
489}
490
491template <typename T>
492RoomMember::CallbackHandle<T> RoomMember::RoomMemberImpl::Bind(
493 std::function<void(const T&)> callback) {
494 std::lock_guard lock(callback_mutex);
495 CallbackHandle<T> handle;
496 handle = std::make_shared<std::function<void(const T&)>>(callback);
497 callbacks.Get<T>().insert(handle);
498 return handle;
499}
500
501// RoomMember
502RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {}
503
504RoomMember::~RoomMember() {
505 ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected");
506 if (room_member_impl->loop_thread) {
507 Leave();
508 }
509}
510
511RoomMember::State RoomMember::GetState() const {
512 return room_member_impl->state;
513}
514
515const RoomMember::MemberList& RoomMember::GetMemberInformation() const {
516 return room_member_impl->member_information;
517}
518
519const std::string& RoomMember::GetNickname() const {
520 return room_member_impl->nickname;
521}
522
523const std::string& RoomMember::GetUsername() const {
524 std::lock_guard lock(room_member_impl->username_mutex);
525 return room_member_impl->username;
526}
527
528const MacAddress& RoomMember::GetMacAddress() const {
529 ASSERT_MSG(IsConnected(), "Tried to get MAC address while not connected");
530 return room_member_impl->mac_address;
531}
532
533RoomInformation RoomMember::GetRoomInformation() const {
534 return room_member_impl->room_information;
535}
536
537void RoomMember::Join(const std::string& nick, const std::string& console_id_hash,
538 const char* server_addr, u16 server_port, u16 client_port,
539 const MacAddress& preferred_mac, const std::string& password,
540 const std::string& token) {
541 // If the member is connected, kill the connection first
542 if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) {
543 Leave();
544 }
545 // If the thread isn't running but the ptr still exists, reset it
546 else if (room_member_impl->loop_thread) {
547 room_member_impl->loop_thread.reset();
548 }
549
550 if (!room_member_impl->client) {
551 room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
552 ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
553 }
554
555 room_member_impl->SetState(State::Joining);
556
557 ENetAddress address{};
558 enet_address_set_host(&address, server_addr);
559 address.port = server_port;
560 room_member_impl->server =
561 enet_host_connect(room_member_impl->client, &address, NumChannels, 0);
562
563 if (!room_member_impl->server) {
564 room_member_impl->SetState(State::Idle);
565 room_member_impl->SetError(Error::UnknownError);
566 return;
567 }
568
569 ENetEvent event{};
570 int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs);
571 if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
572 room_member_impl->nickname = nick;
573 room_member_impl->StartLoop();
574 room_member_impl->SendJoinRequest(nick, console_id_hash, preferred_mac, password, token);
575 SendGameInfo(room_member_impl->current_game_info);
576 } else {
577 enet_peer_disconnect(room_member_impl->server, 0);
578 room_member_impl->SetState(State::Idle);
579 room_member_impl->SetError(Error::CouldNotConnect);
580 }
581}
582
583bool RoomMember::IsConnected() const {
584 return room_member_impl->IsConnected();
585}
586
587void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) {
588 Packet packet;
589 packet.Write(static_cast<u8>(IdWifiPacket));
590 packet.Write(static_cast<u8>(wifi_packet.type));
591 packet.Write(wifi_packet.channel);
592 packet.Write(wifi_packet.transmitter_address);
593 packet.Write(wifi_packet.destination_address);
594 packet.Write(wifi_packet.data);
595 room_member_impl->Send(std::move(packet));
596}
597
598void RoomMember::SendChatMessage(const std::string& message) {
599 Packet packet;
600 packet.Write(static_cast<u8>(IdChatMessage));
601 packet.Write(message);
602 room_member_impl->Send(std::move(packet));
603}
604
605void RoomMember::SendGameInfo(const GameInfo& game_info) {
606 room_member_impl->current_game_info = game_info;
607 if (!IsConnected())
608 return;
609
610 Packet packet;
611 packet.Write(static_cast<u8>(IdSetGameInfo));
612 packet.Write(game_info.name);
613 packet.Write(game_info.id);
614 room_member_impl->Send(std::move(packet));
615}
616
617void RoomMember::SendModerationRequest(RoomMessageTypes type, const std::string& nickname) {
618 ASSERT_MSG(type == IdModKick || type == IdModBan || type == IdModUnban,
619 "type is not a moderation request");
620 if (!IsConnected())
621 return;
622
623 Packet packet;
624 packet.Write(static_cast<u8>(type));
625 packet.Write(nickname);
626 room_member_impl->Send(std::move(packet));
627}
628
629void RoomMember::RequestBanList() {
630 if (!IsConnected())
631 return;
632
633 Packet packet;
634 packet.Write(static_cast<u8>(IdModGetBanList));
635 room_member_impl->Send(std::move(packet));
636}
637
638RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged(
639 std::function<void(const RoomMember::State&)> callback) {
640 return room_member_impl->Bind(callback);
641}
642
643RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError(
644 std::function<void(const RoomMember::Error&)> callback) {
645 return room_member_impl->Bind(callback);
646}
647
648RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived(
649 std::function<void(const WifiPacket&)> callback) {
650 return room_member_impl->Bind(callback);
651}
652
653RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged(
654 std::function<void(const RoomInformation&)> callback) {
655 return room_member_impl->Bind(callback);
656}
657
658RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageRecieved(
659 std::function<void(const ChatEntry&)> callback) {
660 return room_member_impl->Bind(callback);
661}
662
663RoomMember::CallbackHandle<StatusMessageEntry> RoomMember::BindOnStatusMessageReceived(
664 std::function<void(const StatusMessageEntry&)> callback) {
665 return room_member_impl->Bind(callback);
666}
667
668RoomMember::CallbackHandle<Room::BanList> RoomMember::BindOnBanListReceived(
669 std::function<void(const Room::BanList&)> callback) {
670 return room_member_impl->Bind(callback);
671}
672
673template <typename T>
674void RoomMember::Unbind(CallbackHandle<T> handle) {
675 std::lock_guard lock(room_member_impl->callback_mutex);
676 room_member_impl->callbacks.Get<T>().erase(handle);
677}
678
679void RoomMember::Leave() {
680 room_member_impl->SetState(State::Idle);
681 room_member_impl->loop_thread->join();
682 room_member_impl->loop_thread.reset();
683
684 enet_host_destroy(room_member_impl->client);
685 room_member_impl->client = nullptr;
686}
687
688template void RoomMember::Unbind(CallbackHandle<WifiPacket>);
689template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
690template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
691template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
692template void RoomMember::Unbind(CallbackHandle<ChatEntry>);
693template void RoomMember::Unbind(CallbackHandle<StatusMessageEntry>);
694template void RoomMember::Unbind(CallbackHandle<Room::BanList>);
695
696} // namespace Network
diff --git a/src/network/room_member.h b/src/network/room_member.h
new file mode 100644
index 000000000..bbb7d13d4
--- /dev/null
+++ b/src/network/room_member.h
@@ -0,0 +1,318 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <functional>
7#include <memory>
8#include <string>
9#include <vector>
10#include "common/announce_multiplayer_room.h"
11#include "common/common_types.h"
12#include "network/room.h"
13
14namespace Network {
15
16using AnnounceMultiplayerRoom::GameInfo;
17using AnnounceMultiplayerRoom::RoomInformation;
18
19/// Information about the received WiFi packets.
20/// Acts as our own 802.11 header.
21struct WifiPacket {
22 enum class PacketType : u8 {
23 Beacon,
24 Data,
25 Authentication,
26 AssociationResponse,
27 Deauthentication,
28 NodeMap
29 };
30 PacketType type; ///< The type of 802.11 frame.
31 std::vector<u8> data; ///< Raw 802.11 frame data, starting at the management frame header
32 /// for management frames.
33 MacAddress transmitter_address; ///< Mac address of the transmitter.
34 MacAddress destination_address; ///< Mac address of the receiver.
35 u8 channel; ///< WiFi channel where this frame was transmitted.
36};
37
38/// Represents a chat message.
39struct ChatEntry {
40 std::string nickname; ///< Nickname of the client who sent this message.
41 /// Web services username of the client who sent this message, can be empty.
42 std::string username;
43 std::string message; ///< Body of the message.
44};
45
46/// Represents a system status message.
47struct StatusMessageEntry {
48 StatusMessageTypes type; ///< Type of the message
49 /// Subject of the message. i.e. the user who is joining/leaving/being banned, etc.
50 std::string nickname;
51 std::string username;
52};
53
54/**
55 * This is what a client [person joining a server] would use.
56 * It also has to be used if you host a game yourself (You'd create both, a Room and a
57 * RoomMembership for yourself)
58 */
59class RoomMember final {
60public:
61 enum class State : u8 {
62 Uninitialized, ///< Not initialized
63 Idle, ///< Default state (i.e. not connected)
64 Joining, ///< The client is attempting to join a room.
65 Joined, ///< The client is connected to the room and is ready to send/receive packets.
66 Moderator, ///< The client is connnected to the room and is granted mod permissions.
67 };
68
69 enum class Error : u8 {
70 // Reasons why connection was closed
71 LostConnection, ///< Connection closed
72 HostKicked, ///< Kicked by the host
73
74 // Reasons why connection was rejected
75 UnknownError, ///< Some error [permissions to network device missing or something]
76 NameCollision, ///< Somebody is already using this name
77 MacCollision, ///< Somebody is already using that mac-address
78 ConsoleIdCollision, ///< Somebody in the room has the same Console ID
79 WrongVersion, ///< The room version is not the same as for this RoomMember
80 WrongPassword, ///< The password doesn't match the one from the Room
81 CouldNotConnect, ///< The room is not responding to a connection attempt
82 RoomIsFull, ///< Room is already at the maximum number of players
83 HostBanned, ///< The user is banned by the host
84
85 // Reasons why moderation request failed
86 PermissionDenied, ///< The user does not have mod permissions
87 NoSuchUser, ///< The nickname the user attempts to kick/ban does not exist
88 };
89
90 struct MemberInformation {
91 std::string nickname; ///< Nickname of the member.
92 std::string username; ///< The web services username of the member. Can be empty.
93 std::string display_name; ///< The web services display name of the member. Can be empty.
94 std::string avatar_url; ///< Url to the member's avatar. Can be empty.
95 GameInfo game_info; ///< Name of the game they're currently playing, or empty if they're
96 /// not playing anything.
97 MacAddress mac_address; ///< MAC address associated with this member.
98 };
99 using MemberList = std::vector<MemberInformation>;
100
101 // The handle for the callback functions
102 template <typename T>
103 using CallbackHandle = std::shared_ptr<std::function<void(const T&)>>;
104
105 /**
106 * Unbinds a callback function from the events.
107 * @param handle The connection handle to disconnect
108 */
109 template <typename T>
110 void Unbind(CallbackHandle<T> handle);
111
112 RoomMember();
113 ~RoomMember();
114
115 /**
116 * Returns the status of our connection to the room.
117 */
118 State GetState() const;
119
120 /**
121 * Returns information about the members in the room we're currently connected to.
122 */
123 const MemberList& GetMemberInformation() const;
124
125 /**
126 * Returns the nickname of the RoomMember.
127 */
128 const std::string& GetNickname() const;
129
130 /**
131 * Returns the username of the RoomMember.
132 */
133 const std::string& GetUsername() const;
134
135 /**
136 * Returns the MAC address of the RoomMember.
137 */
138 const MacAddress& GetMacAddress() const;
139
140 /**
141 * Returns information about the room we're currently connected to.
142 */
143 RoomInformation GetRoomInformation() const;
144
145 /**
146 * Returns whether we're connected to a server or not.
147 */
148 bool IsConnected() const;
149
150 /**
151 * Attempts to join a room at the specified address and port, using the specified nickname.
152 * A console ID hash is passed in to check console ID conflicts.
153 * This may fail if the username or console ID is already taken.
154 */
155 void Join(const std::string& nickname, const std::string& console_id_hash,
156 const char* server_addr = "127.0.0.1", u16 server_port = DefaultRoomPort,
157 u16 client_port = 0, const MacAddress& preferred_mac = NoPreferredMac,
158 const std::string& password = "", const std::string& token = "");
159
160 /**
161 * Sends a WiFi packet to the room.
162 * @param packet The WiFi packet to send.
163 */
164 void SendWifiPacket(const WifiPacket& packet);
165
166 /**
167 * Sends a chat message to the room.
168 * @param message The contents of the message.
169 */
170 void SendChatMessage(const std::string& message);
171
172 /**
173 * Sends the current game info to the room.
174 * @param game_info The game information.
175 */
176 void SendGameInfo(const GameInfo& game_info);
177
178 /**
179 * Sends a moderation request to the room.
180 * @param type Moderation request type.
181 * @param nickname The subject of the request. (i.e. the user you want to kick/ban)
182 */
183 void SendModerationRequest(RoomMessageTypes type, const std::string& nickname);
184
185 /**
186 * Attempts to retrieve ban list from the room.
187 * If success, the ban list callback would be called. Otherwise an error would be emitted.
188 */
189 void RequestBanList();
190
191 /**
192 * Binds a function to an event that will be triggered every time the State of the member
193 * changed. The function wil be called every time the event is triggered. The callback function
194 * must not bind or unbind a function. Doing so will cause a deadlock
195 * @param callback The function to call
196 * @return A handle used for removing the function from the registered list
197 */
198 CallbackHandle<State> BindOnStateChanged(std::function<void(const State&)> callback);
199
200 /**
201 * Binds a function to an event that will be triggered every time an error happened. The
202 * function wil be called every time the event is triggered. The callback function must not bind
203 * or unbind a function. Doing so will cause a deadlock
204 * @param callback The function to call
205 * @return A handle used for removing the function from the registered list
206 */
207 CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback);
208
209 /**
210 * Binds a function to an event that will be triggered every time a WifiPacket is received.
211 * The function wil be called everytime the event is triggered.
212 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
213 * @param callback The function to call
214 * @return A handle used for removing the function from the registered list
215 */
216 CallbackHandle<WifiPacket> BindOnWifiPacketReceived(
217 std::function<void(const WifiPacket&)> callback);
218
219 /**
220 * Binds a function to an event that will be triggered every time the RoomInformation changes.
221 * The function wil be called every time the event is triggered.
222 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
223 * @param callback The function to call
224 * @return A handle used for removing the function from the registered list
225 */
226 CallbackHandle<RoomInformation> BindOnRoomInformationChanged(
227 std::function<void(const RoomInformation&)> callback);
228
229 /**
230 * Binds a function to an event that will be triggered every time a ChatMessage is received.
231 * The function wil be called every time the event is triggered.
232 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
233 * @param callback The function to call
234 * @return A handle used for removing the function from the registered list
235 */
236 CallbackHandle<ChatEntry> BindOnChatMessageRecieved(
237 std::function<void(const ChatEntry&)> callback);
238
239 /**
240 * Binds a function to an event that will be triggered every time a StatusMessage is
241 * received. The function will be called every time the event is triggered. The callback
242 * function must not bind or unbind a function. Doing so will cause a deadlock
243 * @param callback The function to call
244 * @return A handle used for removing the function from the registered list
245 */
246 CallbackHandle<StatusMessageEntry> BindOnStatusMessageReceived(
247 std::function<void(const StatusMessageEntry&)> callback);
248
249 /**
250 * Binds a function to an event that will be triggered every time a requested ban list
251 * received. The function will be called every time the event is triggered. The callback
252 * function must not bind or unbind a function. Doing so will cause a deadlock
253 * @param callback The function to call
254 * @return A handle used for removing the function from the registered list
255 */
256 CallbackHandle<Room::BanList> BindOnBanListReceived(
257 std::function<void(const Room::BanList&)> callback);
258
259 /**
260 * Leaves the current room.
261 */
262 void Leave();
263
264private:
265 class RoomMemberImpl;
266 std::unique_ptr<RoomMemberImpl> room_member_impl;
267};
268
269inline const char* GetStateStr(const RoomMember::State& s) {
270 switch (s) {
271 case RoomMember::State::Uninitialized:
272 return "Uninitialized";
273 case RoomMember::State::Idle:
274 return "Idle";
275 case RoomMember::State::Joining:
276 return "Joining";
277 case RoomMember::State::Joined:
278 return "Joined";
279 case RoomMember::State::Moderator:
280 return "Moderator";
281 }
282 return "Unknown";
283}
284
285inline const char* GetErrorStr(const RoomMember::Error& e) {
286 switch (e) {
287 case RoomMember::Error::LostConnection:
288 return "LostConnection";
289 case RoomMember::Error::HostKicked:
290 return "HostKicked";
291 case RoomMember::Error::UnknownError:
292 return "UnknownError";
293 case RoomMember::Error::NameCollision:
294 return "NameCollision";
295 case RoomMember::Error::MacCollision:
296 return "MaxCollision";
297 case RoomMember::Error::ConsoleIdCollision:
298 return "ConsoleIdCollision";
299 case RoomMember::Error::WrongVersion:
300 return "WrongVersion";
301 case RoomMember::Error::WrongPassword:
302 return "WrongPassword";
303 case RoomMember::Error::CouldNotConnect:
304 return "CouldNotConnect";
305 case RoomMember::Error::RoomIsFull:
306 return "RoomIsFull";
307 case RoomMember::Error::HostBanned:
308 return "HostBanned";
309 case RoomMember::Error::PermissionDenied:
310 return "PermissionDenied";
311 case RoomMember::Error::NoSuchUser:
312 return "NoSuchUser";
313 default:
314 return "Unknown";
315 }
316}
317
318} // namespace Network
diff --git a/src/network/verify_user.cpp b/src/network/verify_user.cpp
new file mode 100644
index 000000000..f84cfe59b
--- /dev/null
+++ b/src/network/verify_user.cpp
@@ -0,0 +1,17 @@
1// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "network/verify_user.h"
5
6namespace Network::VerifyUser {
7
8Backend::~Backend() = default;
9
10NullBackend::~NullBackend() = default;
11
12UserData NullBackend::LoadUserData([[maybe_unused]] const std::string& verify_uid,
13 [[maybe_unused]] const std::string& token) {
14 return {};
15}
16
17} // namespace Network::VerifyUser
diff --git a/src/network/verify_user.h b/src/network/verify_user.h
new file mode 100644
index 000000000..6fc64d8a3
--- /dev/null
+++ b/src/network/verify_user.h
@@ -0,0 +1,45 @@
1// SPDX-FileCopyrightText: Copyright 2018 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7#include "common/logging/log.h"
8
9namespace Network::VerifyUser {
10
11struct UserData {
12 std::string username;
13 std::string display_name;
14 std::string avatar_url;
15 bool moderator = false; ///< Whether the user is a yuzu Moderator.
16};
17
18/**
19 * A backend used for verifying users and loading user data.
20 */
21class Backend {
22public:
23 virtual ~Backend();
24
25 /**
26 * Verifies the given token and loads the information into a UserData struct.
27 * @param verify_uid A GUID that may be used for verification.
28 * @param token A token that contains user data and verification data. The format and content is
29 * decided by backends.
30 */
31 virtual UserData LoadUserData(const std::string& verify_uid, const std::string& token) = 0;
32};
33
34/**
35 * A null backend where the token is ignored.
36 * No verification is performed here and the function returns an empty UserData.
37 */
38class NullBackend final : public Backend {
39public:
40 ~NullBackend();
41
42 UserData LoadUserData(const std::string& verify_uid, const std::string& token) override;
43};
44
45} // namespace Network::VerifyUser