diff options
| author | 2022-08-13 13:11:01 -0500 | |
|---|---|---|
| committer | 2022-08-15 20:25:42 +0200 | |
| commit | 72b90a5bbf7a9308f7172f38be88e29bab58a21b (patch) | |
| tree | 981faf5a3a9c568b0ff00ba0ae1511e73368e649 /src/dedicated_room/yuzu_room.cpp | |
| parent | yuzu: Fix crash on shutdown (diff) | |
| download | yuzu-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.cpp | 375 |
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 | |||
| 46 | static 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 | |||
| 68 | static 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. | ||
| 74 | static constexpr char BanListMagic[] = "YuzuRoom-BanList-1"; | ||
| 75 | |||
| 76 | static constexpr char token_delimiter{':'}; | ||
| 77 | |||
| 78 | static 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 | |||
| 89 | static 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 | |||
| 100 | static 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 | |||
| 138 | static 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 | |||
| 160 | static 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 | ||
| 167 | int 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 | } | ||