summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar FearlessTobi2022-07-30 05:55:29 +0200
committerGravatar FearlessTobi2022-08-15 20:25:41 +0200
commit9fc03d4b91e69ab4590d6e257b41810dcf36abf9 (patch)
tree4d752863b5293edbe8fe8a2869615f5c884fd9fd /src
parentMerge pull request #8104 from Docteh/them_icons (diff)
downloadyuzu-9fc03d4b91e69ab4590d6e257b41810dcf36abf9.tar.gz
yuzu-9fc03d4b91e69ab4590d6e257b41810dcf36abf9.tar.xz
yuzu-9fc03d4b91e69ab4590d6e257b41810dcf36abf9.zip
dedicated_room: Initial implementation
Diffstat (limited to '')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/dedicated_room/CMakeLists.txt24
-rw-r--r--src/dedicated_room/yuzu-room.cpp376
-rw-r--r--src/dedicated_room/yuzu-room.rc17
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)
162add_subdirectory(network) 162add_subdirectory(network)
163add_subdirectory(input_common) 163add_subdirectory(input_common)
164add_subdirectory(shader_recompiler) 164add_subdirectory(shader_recompiler)
165add_subdirectory(dedicated_room)
165 166
166if (YUZU_TESTS) 167if (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 @@
1set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
2
3add_executable(yuzu-room
4 yuzu-room.cpp
5 yuzu-room.rc
6)
7
8create_target_directory_groups(yuzu-room)
9
10target_link_libraries(yuzu-room PRIVATE common core network)
11if (ENABLE_WEB_SERVICE)
12 target_compile_definitions(yuzu-room PRIVATE -DENABLE_WEB_SERVICE)
13 target_link_libraries(yuzu-room PRIVATE web_service)
14endif()
15
16target_link_libraries(yuzu-room PRIVATE mbedtls)
17if (MSVC)
18 target_link_libraries(yuzu-room PRIVATE getopt)
19endif()
20target_link_libraries(yuzu-room PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
21
22if(UNIX AND NOT APPLE)
23 install(TARGETS yuzu-room RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
24endif()
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
47static 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
67static 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.
73static constexpr char BanListMagic[] = "YuzuRoom-BanList-1";
74
75static constexpr char token_delimiter{':'};
76
77static 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
88static 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
99static 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
137static 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
161static 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
168int 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.
9YUZU_ICON ICON "../../dist/yuzu.ico"
10
11
12/////////////////////////////////////////////////////////////////////////////
13//
14// RT_MANIFEST
15//
16
170 RT_MANIFEST "../../dist/yuzu.manifest"