diff options
Diffstat (limited to '')
| -rw-r--r-- | src/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/dedicated_room/CMakeLists.txt | 24 | ||||
| -rw-r--r-- | src/dedicated_room/yuzu-room.cpp | 376 | ||||
| -rw-r--r-- | src/dedicated_room/yuzu-room.rc | 17 |
4 files changed, 418 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fc177fa52..54de1dc94 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt | |||
| @@ -162,6 +162,7 @@ add_subdirectory(video_core) | |||
| 162 | add_subdirectory(network) | 162 | add_subdirectory(network) |
| 163 | add_subdirectory(input_common) | 163 | add_subdirectory(input_common) |
| 164 | add_subdirectory(shader_recompiler) | 164 | add_subdirectory(shader_recompiler) |
| 165 | add_subdirectory(dedicated_room) | ||
| 165 | 166 | ||
| 166 | if (YUZU_TESTS) | 167 | if (YUZU_TESTS) |
| 167 | add_subdirectory(tests) | 168 | add_subdirectory(tests) |
diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt new file mode 100644 index 000000000..7a29bd015 --- /dev/null +++ b/src/dedicated_room/CMakeLists.txt | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) | ||
| 2 | |||
| 3 | add_executable(yuzu-room | ||
| 4 | yuzu-room.cpp | ||
| 5 | yuzu-room.rc | ||
| 6 | ) | ||
| 7 | |||
| 8 | create_target_directory_groups(yuzu-room) | ||
| 9 | |||
| 10 | target_link_libraries(yuzu-room PRIVATE common core network) | ||
| 11 | if (ENABLE_WEB_SERVICE) | ||
| 12 | target_compile_definitions(yuzu-room PRIVATE -DENABLE_WEB_SERVICE) | ||
| 13 | target_link_libraries(yuzu-room PRIVATE web_service) | ||
| 14 | endif() | ||
| 15 | |||
| 16 | target_link_libraries(yuzu-room PRIVATE mbedtls) | ||
| 17 | if (MSVC) | ||
| 18 | target_link_libraries(yuzu-room PRIVATE getopt) | ||
| 19 | endif() | ||
| 20 | target_link_libraries(yuzu-room PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) | ||
| 21 | |||
| 22 | if(UNIX AND NOT APPLE) | ||
| 23 | install(TARGETS yuzu-room RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") | ||
| 24 | endif() | ||
diff --git a/src/dedicated_room/yuzu-room.cpp b/src/dedicated_room/yuzu-room.cpp new file mode 100644 index 000000000..9c1bfaab9 --- /dev/null +++ b/src/dedicated_room/yuzu-room.cpp | |||
| @@ -0,0 +1,376 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <chrono> | ||
| 6 | #include <fstream> | ||
| 7 | #include <iostream> | ||
| 8 | #include <memory> | ||
| 9 | #include <regex> | ||
| 10 | #include <string> | ||
| 11 | #include <thread> | ||
| 12 | |||
| 13 | #ifdef _WIN32 | ||
| 14 | // windows.h needs to be included before shellapi.h | ||
| 15 | #include <windows.h> | ||
| 16 | |||
| 17 | #include <shellapi.h> | ||
| 18 | #endif | ||
| 19 | |||
| 20 | #include <mbedtls/base64.h> | ||
| 21 | #include "common/common_types.h" | ||
| 22 | #include "common/detached_tasks.h" | ||
| 23 | #include "common/fs/file.h" | ||
| 24 | #include "common/fs/fs.h" | ||
| 25 | #include "common/fs/path_util.h" | ||
| 26 | #include "common/logging/backend.h" | ||
| 27 | #include "common/logging/log.h" | ||
| 28 | #include "common/scm_rev.h" | ||
| 29 | #include "common/settings.h" | ||
| 30 | #include "common/string_util.h" | ||
| 31 | #include "core/announce_multiplayer_session.h" | ||
| 32 | #include "core/core.h" | ||
| 33 | #include "network/network.h" | ||
| 34 | #include "network/room.h" | ||
| 35 | #include "network/verify_user.h" | ||
| 36 | |||
| 37 | #ifdef ENABLE_WEB_SERVICE | ||
| 38 | #include "web_service/verify_user_jwt.h" | ||
| 39 | #endif | ||
| 40 | |||
| 41 | #undef _UNICODE | ||
| 42 | #include <getopt.h> | ||
| 43 | #ifndef _MSC_VER | ||
| 44 | #include <unistd.h> | ||
| 45 | #endif | ||
| 46 | |||
| 47 | static void PrintHelp(const char* argv0) { | ||
| 48 | std::cout << "Usage: " << argv0 | ||
| 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 | } | ||
| 66 | |||
| 67 | static void PrintVersion() { | ||
| 68 | std::cout << "yuzu dedicated room " << Common::g_scm_branch << " " << Common::g_scm_desc | ||
| 69 | << " Libnetwork: " << Network::network_version << std::endl; | ||
| 70 | } | ||
| 71 | |||
| 72 | /// The magic text at the beginning of a yuzu-room ban list file. | ||
| 73 | static constexpr char BanListMagic[] = "YuzuRoom-BanList-1"; | ||
| 74 | |||
| 75 | static constexpr char token_delimiter{':'}; | ||
| 76 | |||
| 77 | static std::string UsernameFromDisplayToken(const std::string& display_token) { | ||
| 78 | std::size_t outlen; | ||
| 79 | |||
| 80 | std::array<unsigned char, 512> output; | ||
| 81 | mbedtls_base64_decode(output.data(), output.size(), &outlen, | ||
| 82 | reinterpret_cast<const unsigned char*>(display_token.c_str()), | ||
| 83 | display_token.length()); | ||
| 84 | std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen); | ||
| 85 | return decoded_display_token.substr(0, decoded_display_token.find(token_delimiter)); | ||
| 86 | } | ||
| 87 | |||
| 88 | static std::string TokenFromDisplayToken(const std::string& display_token) { | ||
| 89 | std::size_t outlen; | ||
| 90 | |||
| 91 | std::array<unsigned char, 512> output; | ||
| 92 | mbedtls_base64_decode(output.data(), output.size(), &outlen, | ||
| 93 | reinterpret_cast<const unsigned char*>(display_token.c_str()), | ||
| 94 | display_token.length()); | ||
| 95 | std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen); | ||
| 96 | return decoded_display_token.substr(decoded_display_token.find(token_delimiter) + 1); | ||
| 97 | } | ||
| 98 | |||
| 99 | static Network::Room::BanList LoadBanList(const std::string& path) { | ||
| 100 | std::ifstream file; | ||
| 101 | Common::FS::OpenFileStream(file, path, std::ios_base::in); | ||
| 102 | if (!file || file.eof()) { | ||
| 103 | std::cout << "Could not open ban list!\n\n"; | ||
| 104 | return {}; | ||
| 105 | } | ||
| 106 | std::string magic; | ||
| 107 | std::getline(file, magic); | ||
| 108 | if (magic != BanListMagic) { | ||
| 109 | std::cout << "Ban list is not valid!\n\n"; | ||
| 110 | return {}; | ||
| 111 | } | ||
| 112 | |||
| 113 | // false = username ban list, true = ip ban list | ||
| 114 | bool ban_list_type = false; | ||
| 115 | Network::Room::UsernameBanList username_ban_list; | ||
| 116 | Network::Room::IPBanList ip_ban_list; | ||
| 117 | while (!file.eof()) { | ||
| 118 | std::string line; | ||
| 119 | std::getline(file, line); | ||
| 120 | line.erase(std::remove(line.begin(), line.end(), '\0'), line.end()); | ||
| 121 | line = Common::StripSpaces(line); | ||
| 122 | if (line.empty()) { | ||
| 123 | // An empty line marks start of the IP ban list | ||
| 124 | ban_list_type = true; | ||
| 125 | continue; | ||
| 126 | } | ||
| 127 | if (ban_list_type) { | ||
| 128 | ip_ban_list.emplace_back(line); | ||
| 129 | } else { | ||
| 130 | username_ban_list.emplace_back(line); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | return {username_ban_list, ip_ban_list}; | ||
| 135 | } | ||
| 136 | |||
| 137 | static void SaveBanList(const Network::Room::BanList& ban_list, const std::string& path) { | ||
| 138 | std::ofstream file; | ||
| 139 | Common::FS::OpenFileStream(file, path, std::ios_base::out); | ||
| 140 | if (!file) { | ||
| 141 | std::cout << "Could not save ban list!\n\n"; | ||
| 142 | return; | ||
| 143 | } | ||
| 144 | |||
| 145 | file << BanListMagic << "\n"; | ||
| 146 | |||
| 147 | // Username ban list | ||
| 148 | for (const auto& username : ban_list.first) { | ||
| 149 | file << username << "\n"; | ||
| 150 | } | ||
| 151 | file << "\n"; | ||
| 152 | |||
| 153 | // IP ban list | ||
| 154 | for (const auto& ip : ban_list.second) { | ||
| 155 | file << ip << "\n"; | ||
| 156 | } | ||
| 157 | |||
| 158 | file.flush(); | ||
| 159 | } | ||
| 160 | |||
| 161 | static void InitializeLogging(const std::string& log_file) { | ||
| 162 | Common::Log::Initialize(); | ||
| 163 | Common::Log::SetColorConsoleBackendEnabled(true); | ||
| 164 | Common::Log::Start(); | ||
| 165 | } | ||
| 166 | |||
| 167 | /// Application entry point | ||
| 168 | int main(int argc, char** argv) { | ||
| 169 | Common::DetachedTasks detached_tasks; | ||
| 170 | int option_index = 0; | ||
| 171 | char* endarg; | ||
| 172 | |||
| 173 | std::string room_name; | ||
| 174 | std::string room_description; | ||
| 175 | std::string password; | ||
| 176 | std::string preferred_game; | ||
| 177 | std::string username; | ||
| 178 | std::string token; | ||
| 179 | std::string web_api_url; | ||
| 180 | std::string ban_list_file; | ||
| 181 | std::string log_file = "yuzu-room.log"; | ||
| 182 | u64 preferred_game_id = 0; | ||
| 183 | u32 port = Network::DefaultRoomPort; | ||
| 184 | u32 max_members = 16; | ||
| 185 | bool enable_yuzu_mods = false; | ||
| 186 | |||
| 187 | static struct option long_options[] = { | ||
| 188 | {"room-name", required_argument, 0, 'n'}, | ||
| 189 | {"room-description", required_argument, 0, 'd'}, | ||
| 190 | {"port", required_argument, 0, 'p'}, | ||
| 191 | {"max_members", required_argument, 0, 'm'}, | ||
| 192 | {"password", required_argument, 0, 'w'}, | ||
| 193 | {"preferred-game", required_argument, 0, 'g'}, | ||
| 194 | {"preferred-game-id", required_argument, 0, 'i'}, | ||
| 195 | {"username", optional_argument, 0, 'u'}, | ||
| 196 | {"token", required_argument, 0, 't'}, | ||
| 197 | {"web-api-url", required_argument, 0, 'a'}, | ||
| 198 | {"ban-list-file", required_argument, 0, 'b'}, | ||
| 199 | {"log-file", required_argument, 0, 'l'}, | ||
| 200 | {"enable-yuzu-mods", no_argument, 0, 'e'}, | ||
| 201 | {"help", no_argument, 0, 'h'}, | ||
| 202 | {"version", no_argument, 0, 'v'}, | ||
| 203 | {0, 0, 0, 0}, | ||
| 204 | }; | ||
| 205 | |||
| 206 | while (optind < argc) { | ||
| 207 | int arg = getopt_long(argc, argv, "n:d:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index); | ||
| 208 | if (arg != -1) { | ||
| 209 | switch (static_cast<char>(arg)) { | ||
| 210 | case 'n': | ||
| 211 | room_name.assign(optarg); | ||
| 212 | break; | ||
| 213 | case 'd': | ||
| 214 | room_description.assign(optarg); | ||
| 215 | break; | ||
| 216 | case 'p': | ||
| 217 | port = strtoul(optarg, &endarg, 0); | ||
| 218 | break; | ||
| 219 | case 'm': | ||
| 220 | max_members = strtoul(optarg, &endarg, 0); | ||
| 221 | break; | ||
| 222 | case 'w': | ||
| 223 | password.assign(optarg); | ||
| 224 | break; | ||
| 225 | case 'g': | ||
| 226 | preferred_game.assign(optarg); | ||
| 227 | break; | ||
| 228 | case 'i': | ||
| 229 | preferred_game_id = strtoull(optarg, &endarg, 16); | ||
| 230 | break; | ||
| 231 | case 'u': | ||
| 232 | username.assign(optarg); | ||
| 233 | break; | ||
| 234 | case 't': | ||
| 235 | token.assign(optarg); | ||
| 236 | break; | ||
| 237 | case 'a': | ||
| 238 | web_api_url.assign(optarg); | ||
| 239 | break; | ||
| 240 | case 'b': | ||
| 241 | ban_list_file.assign(optarg); | ||
| 242 | break; | ||
| 243 | case 'l': | ||
| 244 | log_file.assign(optarg); | ||
| 245 | break; | ||
| 246 | case 'e': | ||
| 247 | enable_yuzu_mods = true; | ||
| 248 | break; | ||
| 249 | case 'h': | ||
| 250 | PrintHelp(argv[0]); | ||
| 251 | return 0; | ||
| 252 | case 'v': | ||
| 253 | PrintVersion(); | ||
| 254 | return 0; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | if (room_name.empty()) { | ||
| 260 | std::cout << "room name is empty!\n\n"; | ||
| 261 | PrintHelp(argv[0]); | ||
| 262 | return -1; | ||
| 263 | } | ||
| 264 | if (preferred_game.empty()) { | ||
| 265 | std::cout << "preferred game is empty!\n\n"; | ||
| 266 | PrintHelp(argv[0]); | ||
| 267 | return -1; | ||
| 268 | } | ||
| 269 | if (preferred_game_id == 0) { | ||
| 270 | std::cout << "preferred-game-id not set!\nThis should get set to allow users to find your " | ||
| 271 | "room.\nSet with --preferred-game-id id\n\n"; | ||
| 272 | } | ||
| 273 | if (max_members > Network::MaxConcurrentConnections || max_members < 2) { | ||
| 274 | std::cout << "max_members needs to be in the range 2 - " | ||
| 275 | << Network::MaxConcurrentConnections << "!\n\n"; | ||
| 276 | PrintHelp(argv[0]); | ||
| 277 | return -1; | ||
| 278 | } | ||
| 279 | if (port > 65535) { | ||
| 280 | std::cout << "port needs to be in the range 0 - 65535!\n\n"; | ||
| 281 | PrintHelp(argv[0]); | ||
| 282 | return -1; | ||
| 283 | } | ||
| 284 | if (ban_list_file.empty()) { | ||
| 285 | std::cout << "Ban list file not set!\nThis should get set to load and save room ban " | ||
| 286 | "list.\nSet with --ban-list-file <file>\n\n"; | ||
| 287 | } | ||
| 288 | bool announce = true; | ||
| 289 | if (token.empty() && announce) { | ||
| 290 | announce = false; | ||
| 291 | std::cout << "token is empty: Hosting a private room\n\n"; | ||
| 292 | } | ||
| 293 | if (web_api_url.empty() && announce) { | ||
| 294 | announce = false; | ||
| 295 | std::cout << "endpoint url is empty: Hosting a private room\n\n"; | ||
| 296 | } | ||
| 297 | if (announce) { | ||
| 298 | if (username.empty()) { | ||
| 299 | std::cout << "Hosting a public room\n\n"; | ||
| 300 | Settings::values.web_api_url = web_api_url; | ||
| 301 | Settings::values.yuzu_username = UsernameFromDisplayToken(token); | ||
| 302 | username = Settings::values.yuzu_username.GetValue(); | ||
| 303 | Settings::values.yuzu_token = TokenFromDisplayToken(token); | ||
| 304 | } else { | ||
| 305 | std::cout << "Hosting a public room\n\n"; | ||
| 306 | Settings::values.web_api_url = web_api_url; | ||
| 307 | Settings::values.yuzu_username = username; | ||
| 308 | Settings::values.yuzu_token = token; | ||
| 309 | } | ||
| 310 | } | ||
| 311 | if (!announce && enable_yuzu_mods) { | ||
| 312 | enable_yuzu_mods = false; | ||
| 313 | std::cout << "Can not enable yuzu Moderators for private rooms\n\n"; | ||
| 314 | } | ||
| 315 | |||
| 316 | InitializeLogging(log_file); | ||
| 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 | std::cout | ||
| 331 | << "yuzu Web Services is not available with this build: validation is disabled.\n\n"; | ||
| 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 | Core::System system{}; | ||
| 339 | auto& network = system.GetRoomNetwork(); | ||
| 340 | network.Init(); | ||
| 341 | if (std::shared_ptr<Network::Room> room = network.GetRoom().lock()) { | ||
| 342 | AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game, | ||
| 343 | .id = preferred_game_id}; | ||
| 344 | if (!room->Create(room_name, room_description, "", port, password, max_members, username, | ||
| 345 | preferred_game_info, std::move(verify_backend), ban_list, | ||
| 346 | enable_yuzu_mods)) { | ||
| 347 | std::cout << "Failed to create room: \n\n"; | ||
| 348 | return -1; | ||
| 349 | } | ||
| 350 | std::cout << "Room is open. Close with Q+Enter...\n\n"; | ||
| 351 | auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>(network); | ||
| 352 | if (announce) { | ||
| 353 | announce_session->Start(); | ||
| 354 | } | ||
| 355 | while (room->GetState() == Network::Room::State::Open) { | ||
| 356 | std::string in; | ||
| 357 | std::cin >> in; | ||
| 358 | if (in.size() > 0) { | ||
| 359 | break; | ||
| 360 | } | ||
| 361 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); | ||
| 362 | } | ||
| 363 | if (announce) { | ||
| 364 | announce_session->Stop(); | ||
| 365 | } | ||
| 366 | announce_session.reset(); | ||
| 367 | // Save the ban list | ||
| 368 | if (!ban_list_file.empty()) { | ||
| 369 | SaveBanList(room->GetBanList(), ban_list_file); | ||
| 370 | } | ||
| 371 | room->Destroy(); | ||
| 372 | } | ||
| 373 | network.Shutdown(); | ||
| 374 | detached_tasks.WaitForAllTasks(); | ||
| 375 | return 0; | ||
| 376 | } | ||
diff --git a/src/dedicated_room/yuzu-room.rc b/src/dedicated_room/yuzu-room.rc new file mode 100644 index 000000000..b616a5764 --- /dev/null +++ b/src/dedicated_room/yuzu-room.rc | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | #include "winresrc.h" | ||
| 2 | ///////////////////////////////////////////////////////////////////////////// | ||
| 3 | // | ||
| 4 | // Icon | ||
| 5 | // | ||
| 6 | |||
| 7 | // Icon with lowest ID value placed first to ensure application icon | ||
| 8 | // remains consistent on all systems. | ||
| 9 | YUZU_ICON ICON "../../dist/yuzu.ico" | ||
| 10 | |||
| 11 | |||
| 12 | ///////////////////////////////////////////////////////////////////////////// | ||
| 13 | // | ||
| 14 | // RT_MANIFEST | ||
| 15 | // | ||
| 16 | |||
| 17 | 0 RT_MANIFEST "../../dist/yuzu.manifest" | ||