mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Merge pull request #3069 from B3n30/announce_room_webservice
Announce room webservice
This commit is contained in:
		
						commit
						3c03da12c8
					
				
					 15 changed files with 584 additions and 20 deletions
				
			
		|  | @ -166,6 +166,9 @@ void Config::ReadValues() { | ||||||
|         "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry"); |         "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry"); | ||||||
|     Settings::values.verify_endpoint_url = sdl2_config->Get( |     Settings::values.verify_endpoint_url = sdl2_config->Get( | ||||||
|         "WebService", "verify_endpoint_url", "https://services.citra-emu.org/api/profile"); |         "WebService", "verify_endpoint_url", "https://services.citra-emu.org/api/profile"); | ||||||
|  |     Settings::values.announce_multiplayer_room_endpoint_url = | ||||||
|  |         sdl2_config->Get("WebService", "announce_multiplayer_room_endpoint_url", | ||||||
|  |                          "https://services.citra-emu.org/api/multiplayer/rooms"); | ||||||
|     Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", ""); |     Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", ""); | ||||||
|     Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", ""); |     Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", ""); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -191,6 +191,8 @@ enable_telemetry = | ||||||
| telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
 | telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
 | ||||||
| # Endpoint URL to verify the username and token | # Endpoint URL to verify the username and token | ||||||
| verify_endpoint_url = https://services.citra-emu.org/api/profile
 | verify_endpoint_url = https://services.citra-emu.org/api/profile
 | ||||||
|  | # Endpoint URL for announcing public rooms | ||||||
|  | announce_multiplayer_room_endpoint_url = https://services.citra-emu.org/api/multiplayer/rooms
 | ||||||
| # Username and token for Citra Web Service | # Username and token for Citra Web Service | ||||||
| # See https://services.citra-emu.org/ for more info
 | # See https://services.citra-emu.org/ for more info
 | ||||||
| citra_username = | citra_username = | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU | ||||||
| 
 | 
 | ||||||
| add_library(common STATIC | add_library(common STATIC | ||||||
|     alignment.h |     alignment.h | ||||||
|  |     announce_multiplayer_room.h | ||||||
|     assert.h |     assert.h | ||||||
|     bit_field.h |     bit_field.h | ||||||
|     bit_set.h |     bit_set.h | ||||||
|  |  | ||||||
							
								
								
									
										143
									
								
								src/common/announce_multiplayer_room.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/common/announce_multiplayer_room.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,143 @@ | ||||||
|  | // Copyright 2017 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <array> | ||||||
|  | #include <functional> | ||||||
|  | #include <future> | ||||||
|  | #include <string> | ||||||
|  | #include <vector> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | 
 | ||||||
|  | namespace Common { | ||||||
|  | struct WebResult { | ||||||
|  |     enum class Code : u32 { | ||||||
|  |         Success, | ||||||
|  |         InvalidURL, | ||||||
|  |         CredentialsMissing, | ||||||
|  |         CprError, | ||||||
|  |         HttpError, | ||||||
|  |         WrongContent, | ||||||
|  |         NoWebservice, | ||||||
|  |     }; | ||||||
|  |     Code result_code; | ||||||
|  |     std::string result_string; | ||||||
|  | }; | ||||||
|  | } // namespace Common
 | ||||||
|  | 
 | ||||||
|  | namespace AnnounceMultiplayerRoom { | ||||||
|  | 
 | ||||||
|  | using MacAddress = std::array<u8, 6>; | ||||||
|  | 
 | ||||||
|  | struct Room { | ||||||
|  |     struct Member { | ||||||
|  |         std::string name; | ||||||
|  |         MacAddress mac_address; | ||||||
|  |         std::string game_name; | ||||||
|  |         u64 game_id; | ||||||
|  |     }; | ||||||
|  |     std::string name; | ||||||
|  |     std::string UID; | ||||||
|  |     std::string owner; | ||||||
|  |     std::string ip; | ||||||
|  |     u16 port; | ||||||
|  |     u32 max_player; | ||||||
|  |     u32 net_version; | ||||||
|  |     bool has_password; | ||||||
|  |     std::string preferred_game; | ||||||
|  |     u64 preferred_game_id; | ||||||
|  | 
 | ||||||
|  |     std::vector<Member> members; | ||||||
|  | }; | ||||||
|  | using RoomList = std::vector<Room>; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * A AnnounceMultiplayerRoom interface class. A backend to submit/get to/from a web service should | ||||||
|  |  * implement this interface. | ||||||
|  |  */ | ||||||
|  | class Backend : NonCopyable { | ||||||
|  | public: | ||||||
|  |     virtual ~Backend() = default; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Sets the Information that gets used for the announce | ||||||
|  |      * @param uid The Id of the room | ||||||
|  |      * @param name The name of the room | ||||||
|  |      * @param port The port of the room | ||||||
|  |      * @param net_version The version of the libNetwork that gets used | ||||||
|  |      * @param has_password True if the room is passowrd protected | ||||||
|  |      * @param preferred_game The preferred game of the room | ||||||
|  |      * @param preferred_game_id The title id of the preferred game | ||||||
|  |      */ | ||||||
|  |     virtual void SetRoomInformation(const std::string& uid, const std::string& name, const u16 port, | ||||||
|  |                                     const u32 max_player, const u32 net_version, | ||||||
|  |                                     const bool has_password, const std::string& preferred_game, | ||||||
|  |                                     const u64 preferred_game_id) = 0; | ||||||
|  |     /**
 | ||||||
|  |      * Adds a player information to the data that gets announced | ||||||
|  |      * @param nickname The nickname of the player | ||||||
|  |      * @param mac_address The MAC Address of the player | ||||||
|  |      * @param game_id The title id of the game the player plays | ||||||
|  |      * @param game_name The name of the game the player plays | ||||||
|  |      */ | ||||||
|  |     virtual void AddPlayer(const std::string& nickname, const MacAddress& mac_address, | ||||||
|  |                            const u64 game_id, const std::string& game_name) = 0; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Send the data to the announce service | ||||||
|  |      * @result The result of the announce attempt | ||||||
|  |      */ | ||||||
|  |     virtual std::future<Common::WebResult> Announce() = 0; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Empties the stored players | ||||||
|  |      */ | ||||||
|  |     virtual void ClearPlayers() = 0; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Get the room information from the announce service | ||||||
|  |      * @param func a function that gets exectued when the get finished. | ||||||
|  |      * Can be used as a callback | ||||||
|  |      * @result A list of all rooms the announce service has | ||||||
|  |      */ | ||||||
|  |     virtual std::future<RoomList> GetRoomList(std::function<void()> func) = 0; | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Sends a delete message to the announce service | ||||||
|  |      */ | ||||||
|  |     virtual void Delete() = 0; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Empty implementation of AnnounceMultiplayerRoom interface that drops all data. Used when a | ||||||
|  |  * functional backend implementation is not available. | ||||||
|  |  */ | ||||||
|  | class NullBackend : public Backend { | ||||||
|  | public: | ||||||
|  |     ~NullBackend() = default; | ||||||
|  |     void SetRoomInformation(const std::string& /*uid*/, const std::string& /*name*/, | ||||||
|  |                             const u16 /*port*/, const u32 /*max_player*/, const u32 /*net_version*/, | ||||||
|  |                             const bool /*has_password*/, const std::string& /*preferred_game*/, | ||||||
|  |                             const u64 /*preferred_game_id*/) override {} | ||||||
|  |     void AddPlayer(const std::string& /*nickname*/, const MacAddress& /*mac_address*/, | ||||||
|  |                    const u64 /*game_id*/, const std::string& /*game_name*/) override {} | ||||||
|  |     std::future<Common::WebResult> Announce() override { | ||||||
|  |         return std::async(std::launch::deferred, []() { | ||||||
|  |             return Common::WebResult{Common::WebResult::Code::NoWebservice, | ||||||
|  |                                      "WebService is missing"}; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     void ClearPlayers() override {} | ||||||
|  |     std::future<RoomList> GetRoomList(std::function<void()> func) override { | ||||||
|  |         return std::async(std::launch::deferred, [func]() { | ||||||
|  |             func(); | ||||||
|  |             return RoomList{}; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void Delete() override {} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace AnnounceMultiplayerRoom
 | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| add_library(core STATIC | add_library(core STATIC | ||||||
|     3ds.h |     3ds.h | ||||||
|  |     announce_multiplayer_session.cpp | ||||||
|  |     announce_multiplayer_session.h | ||||||
|     arm/arm_interface.h |     arm/arm_interface.h | ||||||
|     arm/dyncom/arm_dyncom.cpp |     arm/dyncom/arm_dyncom.cpp | ||||||
|     arm/dyncom/arm_dyncom.h |     arm/dyncom/arm_dyncom.h | ||||||
|  |  | ||||||
							
								
								
									
										108
									
								
								src/core/announce_multiplayer_session.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/core/announce_multiplayer_session.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,108 @@ | ||||||
|  | // Copyright 2017 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <chrono> | ||||||
|  | #include <vector> | ||||||
|  | #include "announce_multiplayer_session.h" | ||||||
|  | #include "common/announce_multiplayer_room.h" | ||||||
|  | #include "common/assert.h" | ||||||
|  | #include "core/settings.h" | ||||||
|  | #include "network/network.h" | ||||||
|  | 
 | ||||||
|  | #ifdef ENABLE_WEB_SERVICE | ||||||
|  | #include "web_service/announce_room_json.h" | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | namespace Core { | ||||||
|  | 
 | ||||||
|  | // Time between room is announced to web_service
 | ||||||
|  | static constexpr std::chrono::seconds announce_time_interval(15); | ||||||
|  | 
 | ||||||
|  | AnnounceMultiplayerSession::AnnounceMultiplayerSession() { | ||||||
|  | #ifdef ENABLE_WEB_SERVICE | ||||||
|  |     backend = std::make_unique<WebService::RoomJson>( | ||||||
|  |         Settings::values.announce_multiplayer_room_endpoint_url, Settings::values.citra_username, | ||||||
|  |         Settings::values.citra_token); | ||||||
|  | #else | ||||||
|  |     backend = std::make_unique<AnnounceMultiplayerRoom::NullBackend>(); | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void AnnounceMultiplayerSession::Start() { | ||||||
|  |     if (announce_multiplayer_thread) { | ||||||
|  |         Stop(); | ||||||
|  |     } | ||||||
|  |     shutdown_event.Reset(); | ||||||
|  |     announce_multiplayer_thread = | ||||||
|  |         std::make_unique<std::thread>(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void AnnounceMultiplayerSession::Stop() { | ||||||
|  |     if (announce_multiplayer_thread) { | ||||||
|  |         shutdown_event.Set(); | ||||||
|  |         announce_multiplayer_thread->join(); | ||||||
|  |         announce_multiplayer_thread.reset(); | ||||||
|  |         backend->Delete(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | AnnounceMultiplayerSession::CallbackHandle AnnounceMultiplayerSession::BindErrorCallback( | ||||||
|  |     std::function<void(const Common::WebResult&)> function) { | ||||||
|  |     std::lock_guard<std::mutex> lock(callback_mutex); | ||||||
|  |     auto handle = std::make_shared<std::function<void(const Common::WebResult&)>>(function); | ||||||
|  |     error_callbacks.insert(handle); | ||||||
|  |     return handle; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void AnnounceMultiplayerSession::UnbindErrorCallback(CallbackHandle handle) { | ||||||
|  |     std::lock_guard<std::mutex> lock(callback_mutex); | ||||||
|  |     error_callbacks.erase(handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | AnnounceMultiplayerSession::~AnnounceMultiplayerSession() { | ||||||
|  |     Stop(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() { | ||||||
|  |     auto update_time = std::chrono::steady_clock::now(); | ||||||
|  |     std::future<Common::WebResult> future; | ||||||
|  |     while (!shutdown_event.WaitUntil(update_time)) { | ||||||
|  |         update_time += announce_time_interval; | ||||||
|  |         std::shared_ptr<Network::Room> room = Network::GetRoom().lock(); | ||||||
|  |         if (!room) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         if (room->GetState() != Network::Room::State::Open) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         Network::RoomInformation room_information = room->GetRoomInformation(); | ||||||
|  |         std::vector<Network::Room::Member> memberlist = room->GetRoomMemberList(); | ||||||
|  |         backend->SetRoomInformation( | ||||||
|  |             room_information.uid, room_information.name, room_information.port, | ||||||
|  |             room_information.member_slots, Network::network_version, room->HasPassword(), | ||||||
|  |             room_information.preferred_game, room_information.preferred_game_id); | ||||||
|  |         backend->ClearPlayers(); | ||||||
|  |         for (const auto& member : memberlist) { | ||||||
|  |             backend->AddPlayer(member.nickname, member.mac_address, member.game_info.id, | ||||||
|  |                                member.game_info.name); | ||||||
|  |         } | ||||||
|  |         future = backend->Announce(); | ||||||
|  |         if (future.valid()) { | ||||||
|  |             Common::WebResult result = future.get(); | ||||||
|  |             if (result.result_code != Common::WebResult::Code::Success) { | ||||||
|  |                 std::lock_guard<std::mutex> lock(callback_mutex); | ||||||
|  |                 for (auto callback : error_callbacks) { | ||||||
|  |                     (*callback)(result); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::future<AnnounceMultiplayerRoom::RoomList> AnnounceMultiplayerSession::GetRoomList( | ||||||
|  |     std::function<void()> func) { | ||||||
|  |     return backend->GetRoomList(func); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace Core
 | ||||||
							
								
								
									
										71
									
								
								src/core/announce_multiplayer_session.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/core/announce_multiplayer_session.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | ||||||
|  | // Copyright 2017 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <functional> | ||||||
|  | #include <memory> | ||||||
|  | #include <mutex> | ||||||
|  | #include <set> | ||||||
|  | #include <thread> | ||||||
|  | #include "common/announce_multiplayer_room.h" | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/thread.h" | ||||||
|  | 
 | ||||||
|  | namespace Core { | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Instruments AnnounceMultiplayerRoom::Backend. | ||||||
|  |  * Creates a thread that regularly updates the room information and submits them | ||||||
|  |  * An async get of room information is also possible | ||||||
|  |  */ | ||||||
|  | class AnnounceMultiplayerSession : NonCopyable { | ||||||
|  | public: | ||||||
|  |     using CallbackHandle = std::shared_ptr<std::function<void(const Common::WebResult&)>>; | ||||||
|  |     AnnounceMultiplayerSession(); | ||||||
|  |     ~AnnounceMultiplayerSession(); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Allows to bind a function that will get called if the announce encounters an error | ||||||
|  |      * @param function The function that gets called | ||||||
|  |      * @return A handle that can be used the unbind the function | ||||||
|  |      */ | ||||||
|  |     CallbackHandle BindErrorCallback(std::function<void(const Common::WebResult&)> function); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Unbind a function from the error callbacks | ||||||
|  |      * @param handle The handle for the function that should get unbind | ||||||
|  |      */ | ||||||
|  |     void UnbindErrorCallback(CallbackHandle handle); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Starts the announce of a room to web services | ||||||
|  |      */ | ||||||
|  |     void Start(); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * Stops the announce to web services | ||||||
|  |      */ | ||||||
|  |     void Stop(); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      *  Returns a list of all room information the backend got | ||||||
|  |      * @param func A function that gets executed when the async get finished, e.g. a signal | ||||||
|  |      * @return a list of rooms received from the web service | ||||||
|  |      */ | ||||||
|  |     std::future<AnnounceMultiplayerRoom::RoomList> GetRoomList(std::function<void()> func); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     Common::Event shutdown_event; | ||||||
|  |     std::mutex callback_mutex; | ||||||
|  |     std::set<CallbackHandle> error_callbacks; | ||||||
|  |     std::unique_ptr<std::thread> announce_multiplayer_thread; | ||||||
|  | 
 | ||||||
|  |     /// Backend interface that logs fields
 | ||||||
|  |     std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend; | ||||||
|  | 
 | ||||||
|  |     void AnnounceMultiplayerLoop(); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace Core
 | ||||||
|  | @ -139,6 +139,7 @@ struct Values { | ||||||
|     bool enable_telemetry; |     bool enable_telemetry; | ||||||
|     std::string telemetry_endpoint_url; |     std::string telemetry_endpoint_url; | ||||||
|     std::string verify_endpoint_url; |     std::string verify_endpoint_url; | ||||||
|  |     std::string announce_multiplayer_room_endpoint_url; | ||||||
|     std::string citra_username; |     std::string citra_username; | ||||||
|     std::string citra_token; |     std::string citra_token; | ||||||
| } extern values; | } extern values; | ||||||
|  |  | ||||||
|  | @ -1,4 +1,6 @@ | ||||||
| add_library(web_service STATIC | add_library(web_service STATIC | ||||||
|  |     announce_room_json.cpp | ||||||
|  |     announce_room_json.h | ||||||
|     telemetry_json.cpp |     telemetry_json.cpp | ||||||
|     telemetry_json.h |     telemetry_json.h | ||||||
|     verify_login.cpp |     verify_login.cpp | ||||||
|  |  | ||||||
							
								
								
									
										112
									
								
								src/web_service/announce_room_json.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/web_service/announce_room_json.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,112 @@ | ||||||
|  | // Copyright 2017 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <future> | ||||||
|  | #include <json.hpp> | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "web_service/announce_room_json.h" | ||||||
|  | #include "web_service/web_backend.h" | ||||||
|  | 
 | ||||||
|  | namespace AnnounceMultiplayerRoom { | ||||||
|  | 
 | ||||||
|  | void to_json(nlohmann::json& json, const Room::Member& member) { | ||||||
|  |     json["name"] = member.name; | ||||||
|  |     json["gameName"] = member.game_name; | ||||||
|  |     json["gameId"] = member.game_id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void from_json(const nlohmann::json& json, Room::Member& member) { | ||||||
|  |     member.name = json.at("name").get<std::string>(); | ||||||
|  |     member.game_name = json.at("gameName").get<std::string>(); | ||||||
|  |     member.game_id = json.at("gameId").get<u64>(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void to_json(nlohmann::json& json, const Room& room) { | ||||||
|  |     json["id"] = room.UID; | ||||||
|  |     json["port"] = room.port; | ||||||
|  |     json["name"] = room.name; | ||||||
|  |     json["preferredGameName"] = room.preferred_game; | ||||||
|  |     json["preferredGameId"] = room.preferred_game_id; | ||||||
|  |     json["maxPlayers"] = room.max_player; | ||||||
|  |     json["netVersion"] = room.net_version; | ||||||
|  |     json["hasPassword"] = room.has_password; | ||||||
|  |     if (room.members.size() > 0) { | ||||||
|  |         nlohmann::json member_json = room.members; | ||||||
|  |         json["players"] = member_json; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void from_json(const nlohmann::json& json, Room& room) { | ||||||
|  |     room.ip = json.at("address").get<std::string>(); | ||||||
|  |     room.name = json.at("name").get<std::string>(); | ||||||
|  |     room.owner = json.at("owner").get<std::string>(); | ||||||
|  |     room.port = json.at("port").get<u16>(); | ||||||
|  |     room.preferred_game = json.at("preferredGameName").get<std::string>(); | ||||||
|  |     room.preferred_game_id = json.at("preferredGameId").get<u64>(); | ||||||
|  |     room.max_player = json.at("maxPlayers").get<u32>(); | ||||||
|  |     room.net_version = json.at("netVersion").get<u32>(); | ||||||
|  |     room.has_password = json.at("hasPassword").get<bool>(); | ||||||
|  |     try { | ||||||
|  |         room.members = json.at("players").get<std::vector<Room::Member>>(); | ||||||
|  |     } catch (const nlohmann::detail::out_of_range& e) { | ||||||
|  |         LOG_DEBUG(Network, "Out of range %s", e.what()); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace AnnounceMultiplayerRoom
 | ||||||
|  | 
 | ||||||
|  | namespace WebService { | ||||||
|  | 
 | ||||||
|  | void RoomJson::SetRoomInformation(const std::string& uid, const std::string& name, const u16 port, | ||||||
|  |                                   const u32 max_player, const u32 net_version, | ||||||
|  |                                   const bool has_password, const std::string& preferred_game, | ||||||
|  |                                   const u64 preferred_game_id) { | ||||||
|  |     room.name = name; | ||||||
|  |     room.UID = uid; | ||||||
|  |     room.port = port; | ||||||
|  |     room.max_player = max_player; | ||||||
|  |     room.net_version = net_version; | ||||||
|  |     room.has_password = has_password; | ||||||
|  |     room.preferred_game = preferred_game; | ||||||
|  |     room.preferred_game_id = preferred_game_id; | ||||||
|  | } | ||||||
|  | void RoomJson::AddPlayer(const std::string& nickname, | ||||||
|  |                          const AnnounceMultiplayerRoom::MacAddress& mac_address, const u64 game_id, | ||||||
|  |                          const std::string& game_name) { | ||||||
|  |     AnnounceMultiplayerRoom::Room::Member member; | ||||||
|  |     member.name = nickname; | ||||||
|  |     member.mac_address = mac_address; | ||||||
|  |     member.game_id = game_id; | ||||||
|  |     member.game_name = game_name; | ||||||
|  |     room.members.push_back(member); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::future<Common::WebResult> RoomJson::Announce() { | ||||||
|  |     nlohmann::json json = room; | ||||||
|  |     return PostJson(endpoint_url, json.dump(), false, username, token); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RoomJson::ClearPlayers() { | ||||||
|  |     room.members.clear(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::future<AnnounceMultiplayerRoom::RoomList> RoomJson::GetRoomList(std::function<void()> func) { | ||||||
|  |     auto DeSerialize = [func](const std::string& reply) -> AnnounceMultiplayerRoom::RoomList { | ||||||
|  |         nlohmann::json json = nlohmann::json::parse(reply); | ||||||
|  |         AnnounceMultiplayerRoom::RoomList room_list = | ||||||
|  |             json.at("rooms").get<AnnounceMultiplayerRoom::RoomList>(); | ||||||
|  |         func(); | ||||||
|  |         return room_list; | ||||||
|  |     }; | ||||||
|  |     return GetJson<AnnounceMultiplayerRoom::RoomList>(DeSerialize, endpoint_url, true, username, | ||||||
|  |                                                       token); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RoomJson::Delete() { | ||||||
|  |     nlohmann::json json; | ||||||
|  |     json["id"] = room.UID; | ||||||
|  |     DeleteJson(endpoint_url, json.dump(), username, token); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace WebService
 | ||||||
							
								
								
									
										42
									
								
								src/web_service/announce_room_json.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/web_service/announce_room_json.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | ||||||
|  | // Copyright 2017 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <functional> | ||||||
|  | #include <future> | ||||||
|  | #include <string> | ||||||
|  | #include "common/announce_multiplayer_room.h" | ||||||
|  | 
 | ||||||
|  | namespace WebService { | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Implementation of AnnounceMultiplayerRoom::Backend that (de)serializes room information into/from | ||||||
|  |  * JSON, and submits/gets it to/from the Citra web service | ||||||
|  |  */ | ||||||
|  | class RoomJson : public AnnounceMultiplayerRoom::Backend { | ||||||
|  | public: | ||||||
|  |     RoomJson(const std::string& endpoint_url, const std::string& username, const std::string& token) | ||||||
|  |         : endpoint_url(endpoint_url), username(username), token(token) {} | ||||||
|  |     ~RoomJson() = default; | ||||||
|  |     void SetRoomInformation(const std::string& uid, const std::string& name, const u16 port, | ||||||
|  |                             const u32 max_player, const u32 net_version, const bool has_password, | ||||||
|  |                             const std::string& preferred_game, | ||||||
|  |                             const u64 preferred_game_id) override; | ||||||
|  |     void AddPlayer(const std::string& nickname, | ||||||
|  |                    const AnnounceMultiplayerRoom::MacAddress& mac_address, const u64 game_id, | ||||||
|  |                    const std::string& game_name) override; | ||||||
|  |     std::future<Common::WebResult> Announce() override; | ||||||
|  |     void ClearPlayers() override; | ||||||
|  |     std::future<AnnounceMultiplayerRoom::RoomList> GetRoomList(std::function<void()> func) override; | ||||||
|  |     void Delete() override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     AnnounceMultiplayerRoom::Room room; | ||||||
|  |     std::string endpoint_url; | ||||||
|  |     std::string username; | ||||||
|  |     std::string token; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace WebService
 | ||||||
|  | @ -80,7 +80,9 @@ void TelemetryJson::Complete() { | ||||||
|     SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); |     SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); | ||||||
|     SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); |     SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); | ||||||
|     SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); |     SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); | ||||||
|     PostJson(endpoint_url, TopSection().dump(), true, username, token); | 
 | ||||||
|  |     // Send the telemetry async but don't handle the errors since they were written to the log
 | ||||||
|  |     future = PostJson(endpoint_url, TopSection().dump(), true, username, token); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace WebService
 | } // namespace WebService
 | ||||||
|  |  | ||||||
|  | @ -5,8 +5,10 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <array> | #include <array> | ||||||
|  | #include <future> | ||||||
| #include <string> | #include <string> | ||||||
| #include <json.hpp> | #include <json.hpp> | ||||||
|  | #include "common/announce_multiplayer_room.h" | ||||||
| #include "common/telemetry.h" | #include "common/telemetry.h" | ||||||
| 
 | 
 | ||||||
| namespace WebService { | namespace WebService { | ||||||
|  | @ -54,6 +56,7 @@ private: | ||||||
|     std::string endpoint_url; |     std::string endpoint_url; | ||||||
|     std::string username; |     std::string username; | ||||||
|     std::string token; |     std::string token; | ||||||
|  |     std::future<Common::WebResult> future; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace WebService
 | } // namespace WebService
 | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ | ||||||
| #include <cstdlib> | #include <cstdlib> | ||||||
| #include <thread> | #include <thread> | ||||||
| #include <cpr/cpr.h> | #include <cpr/cpr.h> | ||||||
|  | #include "common/announce_multiplayer_room.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "web_service/web_backend.h" | #include "web_service/web_backend.h" | ||||||
| 
 | 
 | ||||||
|  | @ -31,17 +32,23 @@ void Win32WSAStartup() { | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void PostJson(const std::string& url, const std::string& data, bool allow_anonymous, | std::future<Common::WebResult> PostJson(const std::string& url, const std::string& data, | ||||||
|               const std::string& username, const std::string& token) { |                                         bool allow_anonymous, const std::string& username, | ||||||
|  |                                         const std::string& token) { | ||||||
|     if (url.empty()) { |     if (url.empty()) { | ||||||
|         LOG_ERROR(WebService, "URL is invalid"); |         LOG_ERROR(WebService, "URL is invalid"); | ||||||
|         return; |         return std::async(std::launch::deferred, []() { | ||||||
|  |             return Common::WebResult{Common::WebResult::Code::InvalidURL, "URL is invalid"}; | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const bool are_credentials_provided{!token.empty() && !username.empty()}; |     const bool are_credentials_provided{!token.empty() && !username.empty()}; | ||||||
|     if (!allow_anonymous && !are_credentials_provided) { |     if (!allow_anonymous && !are_credentials_provided) { | ||||||
|         LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); |         LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); | ||||||
|         return; |         return std::async(std::launch::deferred, []() { | ||||||
|  |             return Common::WebResult{Common::WebResult::Code::CredentialsMissing, | ||||||
|  |                                      "Credentials needed"}; | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Win32WSAStartup(); |     Win32WSAStartup(); | ||||||
|  | @ -60,23 +67,26 @@ void PostJson(const std::string& url, const std::string& data, bool allow_anonym | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Post JSON asynchronously
 |     // Post JSON asynchronously
 | ||||||
|     static std::future<void> future; |     return cpr::PostCallback( | ||||||
|     future = cpr::PostCallback( |  | ||||||
|         [](cpr::Response r) { |         [](cpr::Response r) { | ||||||
|             if (r.error) { |             if (r.error) { | ||||||
|                 LOG_ERROR(WebService, "POST returned cpr error: %u:%s", |                 LOG_ERROR(WebService, "POST to %s returned cpr error: %u:%s", r.url.c_str(), | ||||||
|                           static_cast<u32>(r.error.code), r.error.message.c_str()); |                           static_cast<u32>(r.error.code), r.error.message.c_str()); | ||||||
|                 return; |                 return Common::WebResult{Common::WebResult::Code::CprError, r.error.message}; | ||||||
|             } |             } | ||||||
|             if (r.status_code >= 400) { |             if (r.status_code >= 400) { | ||||||
|                 LOG_ERROR(WebService, "POST returned error status code: %u", r.status_code); |                 LOG_ERROR(WebService, "POST to %s returned error status code: %u", r.url.c_str(), | ||||||
|                 return; |                           r.status_code); | ||||||
|  |                 return Common::WebResult{Common::WebResult::Code::HttpError, | ||||||
|  |                                          std::to_string(r.status_code)}; | ||||||
|             } |             } | ||||||
|             if (r.header["content-type"].find("application/json") == std::string::npos) { |             if (r.header["content-type"].find("application/json") == std::string::npos) { | ||||||
|                 LOG_ERROR(WebService, "POST returned wrong content: %s", |                 LOG_ERROR(WebService, "POST to %s returned wrong content: %s", r.url.c_str(), | ||||||
|                           r.header["content-type"].c_str()); |                           r.header["content-type"].c_str()); | ||||||
|                 return; |                 return Common::WebResult{Common::WebResult::Code::WrongContent, | ||||||
|  |                                          r.header["content-type"]}; | ||||||
|             } |             } | ||||||
|  |             return Common::WebResult{Common::WebResult::Code::Success, ""}; | ||||||
|         }, |         }, | ||||||
|         cpr::Url{url}, cpr::Body{data}, header); |         cpr::Url{url}, cpr::Body{data}, header); | ||||||
| } | } | ||||||
|  | @ -87,13 +97,13 @@ std::future<T> GetJson(std::function<T(const std::string&)> func, const std::str | ||||||
|                        const std::string& token) { |                        const std::string& token) { | ||||||
|     if (url.empty()) { |     if (url.empty()) { | ||||||
|         LOG_ERROR(WebService, "URL is invalid"); |         LOG_ERROR(WebService, "URL is invalid"); | ||||||
|         return std::async(std::launch::async, [func{std::move(func)}]() { return func(""); }); |         return std::async(std::launch::deferred, [func{std::move(func)}]() { return func(""); }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const bool are_credentials_provided{!token.empty() && !username.empty()}; |     const bool are_credentials_provided{!token.empty() && !username.empty()}; | ||||||
|     if (!allow_anonymous && !are_credentials_provided) { |     if (!allow_anonymous && !are_credentials_provided) { | ||||||
|         LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); |         LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); | ||||||
|         return std::async(std::launch::async, [func{std::move(func)}]() { return func(""); }); |         return std::async(std::launch::deferred, [func{std::move(func)}]() { return func(""); }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Win32WSAStartup(); |     Win32WSAStartup(); | ||||||
|  | @ -115,16 +125,17 @@ std::future<T> GetJson(std::function<T(const std::string&)> func, const std::str | ||||||
|     return cpr::GetCallback( |     return cpr::GetCallback( | ||||||
|         [func{std::move(func)}](cpr::Response r) { |         [func{std::move(func)}](cpr::Response r) { | ||||||
|             if (r.error) { |             if (r.error) { | ||||||
|                 LOG_ERROR(WebService, "GET returned cpr error: %u:%s", |                 LOG_ERROR(WebService, "GET to %s returned cpr error: %u:%s", r.url.c_str(), | ||||||
|                           static_cast<u32>(r.error.code), r.error.message.c_str()); |                           static_cast<u32>(r.error.code), r.error.message.c_str()); | ||||||
|                 return func(""); |                 return func(""); | ||||||
|             } |             } | ||||||
|             if (r.status_code >= 400) { |             if (r.status_code >= 400) { | ||||||
|                 LOG_ERROR(WebService, "GET returned error code: %u", r.status_code); |                 LOG_ERROR(WebService, "GET to %s returned error code: %u", r.url.c_str(), | ||||||
|  |                           r.status_code); | ||||||
|                 return func(""); |                 return func(""); | ||||||
|             } |             } | ||||||
|             if (r.header["content-type"].find("application/json") == std::string::npos) { |             if (r.header["content-type"].find("application/json") == std::string::npos) { | ||||||
|                 LOG_ERROR(WebService, "GET returned wrong content: %s", |                 LOG_ERROR(WebService, "GET to %s returned wrong content: %s", r.url.c_str(), | ||||||
|                           r.header["content-type"].c_str()); |                           r.header["content-type"].c_str()); | ||||||
|                 return func(""); |                 return func(""); | ||||||
|             } |             } | ||||||
|  | @ -136,5 +147,52 @@ std::future<T> GetJson(std::function<T(const std::string&)> func, const std::str | ||||||
| template std::future<bool> GetJson(std::function<bool(const std::string&)> func, | template std::future<bool> GetJson(std::function<bool(const std::string&)> func, | ||||||
|                                    const std::string& url, bool allow_anonymous, |                                    const std::string& url, bool allow_anonymous, | ||||||
|                                    const std::string& username, const std::string& token); |                                    const std::string& username, const std::string& token); | ||||||
|  | template std::future<AnnounceMultiplayerRoom::RoomList> GetJson( | ||||||
|  |     std::function<AnnounceMultiplayerRoom::RoomList(const std::string&)> func, | ||||||
|  |     const std::string& url, bool allow_anonymous, const std::string& username, | ||||||
|  |     const std::string& token); | ||||||
|  | 
 | ||||||
|  | void DeleteJson(const std::string& url, const std::string& data, const std::string& username, | ||||||
|  |                 const std::string& token) { | ||||||
|  |     if (url.empty()) { | ||||||
|  |         LOG_ERROR(WebService, "URL is invalid"); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (token.empty() || username.empty()) { | ||||||
|  |         LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Win32WSAStartup(); | ||||||
|  | 
 | ||||||
|  |     // Built request header
 | ||||||
|  |     cpr::Header header = {{"Content-Type", "application/json"}, | ||||||
|  |                           {"x-username", username.c_str()}, | ||||||
|  |                           {"x-token", token.c_str()}, | ||||||
|  |                           {"api-version", API_VERSION}}; | ||||||
|  | 
 | ||||||
|  |     // Delete JSON asynchronously
 | ||||||
|  |     static std::future<void> future; | ||||||
|  |     future = cpr::DeleteCallback( | ||||||
|  |         [](cpr::Response r) { | ||||||
|  |             if (r.error) { | ||||||
|  |                 LOG_ERROR(WebService, "Delete to %s returned cpr error: %u:%s", r.url.c_str(), | ||||||
|  |                           static_cast<u32>(r.error.code), r.error.message.c_str()); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             if (r.status_code >= 400) { | ||||||
|  |                 LOG_ERROR(WebService, "Delete to %s returned error status code: %u", r.url.c_str(), | ||||||
|  |                           r.status_code); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             if (r.header["content-type"].find("application/json") == std::string::npos) { | ||||||
|  |                 LOG_ERROR(WebService, "Delete to %s returned wrong content: %s", r.url.c_str(), | ||||||
|  |                           r.header["content-type"].c_str()); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         cpr::Url{url}, cpr::Body{data}, header); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| } // namespace WebService
 | } // namespace WebService
 | ||||||
|  |  | ||||||
|  | @ -7,6 +7,8 @@ | ||||||
| #include <functional> | #include <functional> | ||||||
| #include <future> | #include <future> | ||||||
| #include <string> | #include <string> | ||||||
|  | #include <tuple> | ||||||
|  | #include "common/announce_multiplayer_room.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| 
 | 
 | ||||||
| namespace WebService { | namespace WebService { | ||||||
|  | @ -18,9 +20,11 @@ namespace WebService { | ||||||
|  * @param allow_anonymous If true, allow anonymous unauthenticated requests. |  * @param allow_anonymous If true, allow anonymous unauthenticated requests. | ||||||
|  * @param username Citra username to use for authentication. |  * @param username Citra username to use for authentication. | ||||||
|  * @param token Citra token to use for authentication. |  * @param token Citra token to use for authentication. | ||||||
|  |  * @return future with the error or result of the POST | ||||||
|  */ |  */ | ||||||
| void PostJson(const std::string& url, const std::string& data, bool allow_anonymous, | std::future<Common::WebResult> PostJson(const std::string& url, const std::string& data, | ||||||
|               const std::string& username = {}, const std::string& token = {}); |                                         bool allow_anonymous, const std::string& username = {}, | ||||||
|  |                                         const std::string& token = {}); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Gets JSON from services.citra-emu.org. |  * Gets JSON from services.citra-emu.org. | ||||||
|  | @ -36,4 +40,14 @@ std::future<T> GetJson(std::function<T(const std::string&)> func, const std::str | ||||||
|                        bool allow_anonymous, const std::string& username = {}, |                        bool allow_anonymous, const std::string& username = {}, | ||||||
|                        const std::string& token = {}); |                        const std::string& token = {}); | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Delete JSON to services.citra-emu.org. | ||||||
|  |  * @param url URL of the services.citra-emu.org endpoint to post data to. | ||||||
|  |  * @param data String of JSON data to use for the body of the DELETE request. | ||||||
|  |  * @param username Citra username to use for authentication. | ||||||
|  |  * @param token Citra token to use for authentication. | ||||||
|  |  */ | ||||||
|  | void DeleteJson(const std::string& url, const std::string& data, const std::string& username = {}, | ||||||
|  |                 const std::string& token = {}); | ||||||
|  | 
 | ||||||
| } // namespace WebService
 | } // namespace WebService
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue