summaryrefslogtreecommitdiff
path: root/src/dedicated_room/yuzu_room.cpp
diff options
context:
space:
mode:
authorGravatar german772022-08-13 13:11:01 -0500
committerGravatar FearlessTobi2022-08-15 20:25:42 +0200
commit72b90a5bbf7a9308f7172f38be88e29bab58a21b (patch)
tree981faf5a3a9c568b0ff00ba0ae1511e73368e649 /src/dedicated_room/yuzu_room.cpp
parentyuzu: Fix crash on shutdown (diff)
downloadyuzu-72b90a5bbf7a9308f7172f38be88e29bab58a21b.tar.gz
yuzu-72b90a5bbf7a9308f7172f38be88e29bab58a21b.tar.xz
yuzu-72b90a5bbf7a9308f7172f38be88e29bab58a21b.zip
core: network: Address review comments
Diffstat (limited to 'src/dedicated_room/yuzu_room.cpp')
-rw-r--r--src/dedicated_room/yuzu_room.cpp375
1 files changed, 375 insertions, 0 deletions
diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp
new file mode 100644
index 000000000..482e772fb
--- /dev/null
+++ b/src/dedicated_room/yuzu_room.cpp
@@ -0,0 +1,375 @@
1// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <chrono>
5#include <fstream>
6#include <iostream>
7#include <memory>
8#include <regex>
9#include <string>
10#include <thread>
11
12#ifdef _WIN32
13// windows.h needs to be included before shellapi.h
14#include <windows.h>
15
16#include <shellapi.h>
17#endif
18
19#include <mbedtls/base64.h>
20#include "common/common_types.h"
21#include "common/detached_tasks.h"
22#include "common/fs/file.h"
23#include "common/fs/fs.h"
24#include "common/fs/path_util.h"
25#include "common/logging/backend.h"
26#include "common/logging/log.h"
27#include "common/scm_rev.h"
28#include "common/settings.h"
29#include "common/string_util.h"
30#include "core/announce_multiplayer_session.h"
31#include "core/core.h"
32#include "network/network.h"
33#include "network/room.h"
34#include "network/verify_user.h"
35
36#ifdef ENABLE_WEB_SERVICE
37#include "web_service/verify_user_jwt.h"
38#endif
39
40#undef _UNICODE
41#include <getopt.h>
42#ifndef _MSC_VER
43#include <unistd.h>
44#endif
45
46static void PrintHelp(const char* argv0) {
47 LOG_INFO(Network,
48 "Usage: {}"
49 " [options] <filename>\n"
50 "--room-name The name of the room\n"
51 "--room-description The room description\n"
52 "--port The port used for the room\n"
53 "--max_members The maximum number of players for this room\n"
54 "--password The password for the room\n"
55 "--preferred-game The preferred game for this room\n"
56 "--preferred-game-id The preferred game-id for this room\n"
57 "--username The username used for announce\n"
58 "--token The token used for announce\n"
59 "--web-api-url yuzu Web API url\n"
60 "--ban-list-file The file for storing the room ban list\n"
61 "--log-file The file for storing the room log\n"
62 "--enable-yuzu-mods Allow yuzu Community Moderators to moderate on your room\n"
63 "-h, --help Display this help and exit\n"
64 "-v, --version Output version information and exit\n",
65 argv0);
66}
67
68static void PrintVersion() {
69 LOG_INFO(Network, "yuzu dedicated room {} {} Libnetwork: {}", Common::g_scm_branch,
70 Common::g_scm_desc, Network::network_version);
71}
72
73/// The magic text at the beginning of a yuzu-room ban list file.
74static constexpr char BanListMagic[] = "YuzuRoom-BanList-1";
75
76static constexpr char token_delimiter{':'};
77
78static std::string UsernameFromDisplayToken(const std::string& display_token) {
79 std::size_t outlen;
80
81 std::array<unsigned char, 512> output{};
82 mbedtls_base64_decode(output.data(), output.size(), &outlen,
83 reinterpret_cast<const unsigned char*>(display_token.c_str()),
84 display_token.length());
85 std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
86 return decoded_display_token.substr(0, decoded_display_token.find(token_delimiter));
87}
88
89static std::string TokenFromDisplayToken(const std::string& display_token) {
90 std::size_t outlen;
91
92 std::array<unsigned char, 512> output{};
93 mbedtls_base64_decode(output.data(), output.size(), &outlen,
94 reinterpret_cast<const unsigned char*>(display_token.c_str()),
95 display_token.length());
96 std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
97 return decoded_display_token.substr(decoded_display_token.find(token_delimiter) + 1);
98}
99
100static Network::Room::BanList LoadBanList(const std::string& path) {
101 std::ifstream file;
102 Common::FS::OpenFileStream(file, path, std::ios_base::in);
103 if (!file || file.eof()) {
104 LOG_ERROR(Network, "Could not open ban list!");
105 return {};
106 }
107 std::string magic;
108 std::getline(file, magic);
109 if (magic != BanListMagic) {
110 LOG_ERROR(Network, "Ban list is not valid!");
111 return {};
112 }
113
114 // false = username ban list, true = ip ban list
115 bool ban_list_type = false;
116 Network::Room::UsernameBanList username_ban_list;
117 Network::Room::IPBanList ip_ban_list;
118 while (!file.eof()) {
119 std::string line;
120 std::getline(file, line);
121 line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
122 line = Common::StripSpaces(line);
123 if (line.empty()) {
124 // An empty line marks start of the IP ban list
125 ban_list_type = true;
126 continue;
127 }
128 if (ban_list_type) {
129 ip_ban_list.emplace_back(line);
130 } else {
131 username_ban_list.emplace_back(line);
132 }
133 }
134
135 return {username_ban_list, ip_ban_list};
136}
137
138static void SaveBanList(const Network::Room::BanList& ban_list, const std::string& path) {
139 std::ofstream file;
140 Common::FS::OpenFileStream(file, path, std::ios_base::out);
141 if (!file) {
142 LOG_ERROR(Network, "Could not save ban list!");
143 return;
144 }
145
146 file << BanListMagic << "\n";
147
148 // Username ban list
149 for (const auto& username : ban_list.first) {
150 file << username << "\n";
151 }
152 file << "\n";
153
154 // IP ban list
155 for (const auto& ip : ban_list.second) {
156 file << ip << "\n";
157 }
158}
159
160static void InitializeLogging(const std::string& log_file) {
161 Common::Log::Initialize();
162 Common::Log::SetColorConsoleBackendEnabled(true);
163 Common::Log::Start();
164}
165
166/// Application entry point
167int main(int argc, char** argv) {
168 Common::DetachedTasks detached_tasks;
169 int option_index = 0;
170 char* endarg;
171
172 std::string room_name;
173 std::string room_description;
174 std::string password;
175 std::string preferred_game;
176 std::string username;
177 std::string token;
178 std::string web_api_url;
179 std::string ban_list_file;
180 std::string log_file = "yuzu-room.log";
181 u64 preferred_game_id = 0;
182 u32 port = Network::DefaultRoomPort;
183 u32 max_members = 16;
184 bool enable_yuzu_mods = false;
185
186 static struct option long_options[] = {
187 {"room-name", required_argument, 0, 'n'},
188 {"room-description", required_argument, 0, 'd'},
189 {"port", required_argument, 0, 'p'},
190 {"max_members", required_argument, 0, 'm'},
191 {"password", required_argument, 0, 'w'},
192 {"preferred-game", required_argument, 0, 'g'},
193 {"preferred-game-id", required_argument, 0, 'i'},
194 {"username", optional_argument, 0, 'u'},
195 {"token", required_argument, 0, 't'},
196 {"web-api-url", required_argument, 0, 'a'},
197 {"ban-list-file", required_argument, 0, 'b'},
198 {"log-file", required_argument, 0, 'l'},
199 {"enable-yuzu-mods", no_argument, 0, 'e'},
200 {"help", no_argument, 0, 'h'},
201 {"version", no_argument, 0, 'v'},
202 {0, 0, 0, 0},
203 };
204
205 InitializeLogging(log_file);
206
207 while (optind < argc) {
208 int arg = getopt_long(argc, argv, "n:d:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index);
209 if (arg != -1) {
210 switch (static_cast<char>(arg)) {
211 case 'n':
212 room_name.assign(optarg);
213 break;
214 case 'd':
215 room_description.assign(optarg);
216 break;
217 case 'p':
218 port = strtoul(optarg, &endarg, 0);
219 break;
220 case 'm':
221 max_members = strtoul(optarg, &endarg, 0);
222 break;
223 case 'w':
224 password.assign(optarg);
225 break;
226 case 'g':
227 preferred_game.assign(optarg);
228 break;
229 case 'i':
230 preferred_game_id = strtoull(optarg, &endarg, 16);
231 break;
232 case 'u':
233 username.assign(optarg);
234 break;
235 case 't':
236 token.assign(optarg);
237 break;
238 case 'a':
239 web_api_url.assign(optarg);
240 break;
241 case 'b':
242 ban_list_file.assign(optarg);
243 break;
244 case 'l':
245 log_file.assign(optarg);
246 break;
247 case 'e':
248 enable_yuzu_mods = true;
249 break;
250 case 'h':
251 PrintHelp(argv[0]);
252 return 0;
253 case 'v':
254 PrintVersion();
255 return 0;
256 }
257 }
258 }
259
260 if (room_name.empty()) {
261 LOG_ERROR(Network, "Room name is empty!");
262 PrintHelp(argv[0]);
263 return -1;
264 }
265 if (preferred_game.empty()) {
266 LOG_ERROR(Network, "Preferred game is empty!");
267 PrintHelp(argv[0]);
268 return -1;
269 }
270 if (preferred_game_id == 0) {
271 LOG_ERROR(Network,
272 "preferred-game-id not set!\nThis should get set to allow users to find your "
273 "room.\nSet with --preferred-game-id id");
274 }
275 if (max_members > Network::MaxConcurrentConnections || max_members < 2) {
276 LOG_ERROR(Network, "max_members needs to be in the range 2 - {}!",
277 Network::MaxConcurrentConnections);
278 PrintHelp(argv[0]);
279 return -1;
280 }
281 if (port > UINT16_MAX) {
282 LOG_ERROR(Network, "Port needs to be in the range 0 - 65535!");
283 PrintHelp(argv[0]);
284 return -1;
285 }
286 if (ban_list_file.empty()) {
287 LOG_ERROR(Network, "Ban list file not set!\nThis should get set to load and save room ban "
288 "list.\nSet with --ban-list-file <file>");
289 }
290 bool announce = true;
291 if (token.empty() && announce) {
292 announce = false;
293 LOG_INFO(Network, "Token is empty: Hosting a private room");
294 }
295 if (web_api_url.empty() && announce) {
296 announce = false;
297 LOG_INFO(Network, "Endpoint url is empty: Hosting a private room");
298 }
299 if (announce) {
300 if (username.empty()) {
301 LOG_INFO(Network, "Hosting a public room");
302 Settings::values.web_api_url = web_api_url;
303 Settings::values.yuzu_username = UsernameFromDisplayToken(token);
304 username = Settings::values.yuzu_username.GetValue();
305 Settings::values.yuzu_token = TokenFromDisplayToken(token);
306 } else {
307 LOG_INFO(Network, "Hosting a public room");
308 Settings::values.web_api_url = web_api_url;
309 Settings::values.yuzu_username = username;
310 Settings::values.yuzu_token = token;
311 }
312 }
313 if (!announce && enable_yuzu_mods) {
314 enable_yuzu_mods = false;
315 LOG_INFO(Network, "Can not enable yuzu Moderators for private rooms");
316 }
317
318 // Load the ban list
319 Network::Room::BanList ban_list;
320 if (!ban_list_file.empty()) {
321 ban_list = LoadBanList(ban_list_file);
322 }
323
324 std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
325 if (announce) {
326#ifdef ENABLE_WEB_SERVICE
327 verify_backend =
328 std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
329#else
330 LOG_INFO(Network,
331 "yuzu Web Services is not available with this build: validation is disabled.");
332 verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
333#endif
334 } else {
335 verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
336 }
337
338 Network::RoomNetwork network{};
339 network.Init();
340 if (auto room = network.GetRoom().lock()) {
341 AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game,
342 .id = preferred_game_id};
343 if (!room->Create(room_name, room_description, "", port, password, max_members, username,
344 preferred_game_info, std::move(verify_backend), ban_list,
345 enable_yuzu_mods)) {
346 LOG_INFO(Network, "Failed to create room: ");
347 return -1;
348 }
349 LOG_INFO(Network, "Room is open. Close with Q+Enter...");
350 auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>(network);
351 if (announce) {
352 announce_session->Start();
353 }
354 while (room->GetState() == Network::Room::State::Open) {
355 std::string in;
356 std::cin >> in;
357 if (in.size() > 0) {
358 break;
359 }
360 std::this_thread::sleep_for(std::chrono::milliseconds(100));
361 }
362 if (announce) {
363 announce_session->Stop();
364 }
365 announce_session.reset();
366 // Save the ban list
367 if (!ban_list_file.empty()) {
368 SaveBanList(room->GetBanList(), ban_list_file);
369 }
370 room->Destroy();
371 }
372 network.Shutdown();
373 detached_tasks.WaitForAllTasks();
374 return 0;
375}