mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu-mainline.git
				synced 2025-03-21 01:53:15 +00:00 
			
		
		
		
	boxcat: Add downloading and client for launch parameter data
This commit is contained in:
		
							parent
							
								
									b8ce87103d
								
							
						
					
					
						commit
						e8183f9ef0
					
				| @ -25,13 +25,16 @@ constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org"; | |||||||
| 
 | 
 | ||||||
| // Formatted using fmt with arg[0] = hex title id
 | // Formatted using fmt with arg[0] = hex title id
 | ||||||
| constexpr char BOXCAT_PATHNAME_DATA[] = "/boxcat/titles/{:016X}/data"; | constexpr char BOXCAT_PATHNAME_DATA[] = "/boxcat/titles/{:016X}/data"; | ||||||
|  | constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/boxcat/titles/{:016X}/launchparam"; | ||||||
| 
 | 
 | ||||||
| constexpr char BOXCAT_PATHNAME_EVENTS[] = "/boxcat/events"; | constexpr char BOXCAT_PATHNAME_EVENTS[] = "/boxcat/events"; | ||||||
| 
 | 
 | ||||||
| constexpr char BOXCAT_API_VERSION[] = "1"; | constexpr char BOXCAT_API_VERSION[] = "1"; | ||||||
|  | constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu"; | ||||||
| 
 | 
 | ||||||
| // HTTP status codes for Boxcat
 | // HTTP status codes for Boxcat
 | ||||||
| enum class ResponseStatus { | enum class ResponseStatus { | ||||||
|  |     Ok = 200,               ///< Operation completed successfully.
 | ||||||
|     BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server.
 |     BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server.
 | ||||||
|     NoUpdate = 304,         ///< The digest provided would match the new data, no need to update.
 |     NoUpdate = 304,         ///< The digest provided would match the new data, no need to update.
 | ||||||
|     NoMatchTitleId = 404,   ///< The title ID provided doesn't have a boxcat implementation.
 |     NoMatchTitleId = 404,   ///< The title ID provided doesn't have a boxcat implementation.
 | ||||||
| @ -74,6 +77,11 @@ constexpr u64 VFS_COPY_BLOCK_SIZE = 1ull << 24; // 4MB | |||||||
| 
 | 
 | ||||||
| namespace { | namespace { | ||||||
| 
 | 
 | ||||||
|  | std::string GetBINFilePath(u64 title_id) { | ||||||
|  |     return fmt::format("{}bcat/{:016X}/launchparam.bin", | ||||||
|  |                        FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| std::string GetZIPFilePath(u64 title_id) { | std::string GetZIPFilePath(u64 title_id) { | ||||||
|     return fmt::format("{}bcat/{:016X}/data.zip", |     return fmt::format("{}bcat/{:016X}/data.zip", | ||||||
|                        FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); |                        FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); | ||||||
| @ -98,27 +106,40 @@ void HandleDownloadDisplayResult(DownloadResult res) { | |||||||
| 
 | 
 | ||||||
| class Boxcat::Client { | class Boxcat::Client { | ||||||
| public: | public: | ||||||
|     Client(std::string zip_path, u64 title_id, u64 build_id) |     Client(std::string path, u64 title_id, u64 build_id) | ||||||
|         : zip_path(std::move(zip_path)), title_id(title_id), build_id(build_id) {} |         : path(std::move(path)), title_id(title_id), build_id(build_id) {} | ||||||
| 
 | 
 | ||||||
|     DownloadResult Download() { |     DownloadResult DownloadDataZip() { | ||||||
|         const auto resolved_path = fmt::format(BOXCAT_PATHNAME_DATA, title_id); |         return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS, | ||||||
|  |                                 "Boxcat-Data-Digest", "application/zip"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     DownloadResult DownloadLaunchParam() { | ||||||
|  |         return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id), | ||||||
|  |                                 TIMEOUT_SECONDS / 3, "Boxcat-LaunchParam-Digest", | ||||||
|  |                                 "application/octet-stream"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds, | ||||||
|  |                                     const std::string& digest_header_name, | ||||||
|  |                                     const std::string& content_type_name) { | ||||||
|         if (client == nullptr) { |         if (client == nullptr) { | ||||||
|             client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, TIMEOUT_SECONDS); |             client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         httplib::Headers headers{ |         httplib::Headers headers{ | ||||||
|             {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)}, |             {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)}, | ||||||
|  |             {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, | ||||||
|             {std::string("Boxcat-Build-Id"), fmt::format("{:016X}", build_id)}, |             {std::string("Boxcat-Build-Id"), fmt::format("{:016X}", build_id)}, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         if (FileUtil::Exists(zip_path)) { |         if (FileUtil::Exists(path)) { | ||||||
|             FileUtil::IOFile file{zip_path, "rb"}; |             FileUtil::IOFile file{path, "rb"}; | ||||||
|             std::vector<u8> bytes(file.GetSize()); |             std::vector<u8> bytes(file.GetSize()); | ||||||
|             file.ReadBytes(bytes.data(), bytes.size()); |             file.ReadBytes(bytes.data(), bytes.size()); | ||||||
|             const auto digest = DigestFile(bytes); |             const auto digest = DigestFile(bytes); | ||||||
|             headers.insert({std::string("Boxcat-Current-Zip-Digest"), |             headers.insert({digest_header_name, Common::HexArrayToString(digest, false)}); | ||||||
|                             Common::HexArrayToString(digest, false)}); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const auto response = client->Get(resolved_path.c_str(), headers); |         const auto response = client->Get(resolved_path.c_str(), headers); | ||||||
| @ -133,17 +154,17 @@ public: | |||||||
|             return DownloadResult::NoMatchTitleId; |             return DownloadResult::NoMatchTitleId; | ||||||
|         if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId)) |         if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId)) | ||||||
|             return DownloadResult::NoMatchBuildId; |             return DownloadResult::NoMatchBuildId; | ||||||
|         if (response->status >= 400) |         if (response->status != static_cast<int>(ResponseStatus::Ok)) | ||||||
|             return DownloadResult::GeneralWebError; |             return DownloadResult::GeneralWebError; | ||||||
| 
 | 
 | ||||||
|         const auto content_type = response->headers.find("content-type"); |         const auto content_type = response->headers.find("content-type"); | ||||||
|         if (content_type == response->headers.end() || |         if (content_type == response->headers.end() || | ||||||
|             content_type->second.find("application/zip") == std::string::npos) { |             content_type->second.find(content_type_name) == std::string::npos) { | ||||||
|             return DownloadResult::InvalidContentType; |             return DownloadResult::InvalidContentType; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         FileUtil::CreateFullPath(zip_path); |         FileUtil::CreateFullPath(path); | ||||||
|         FileUtil::IOFile file{zip_path, "wb"}; |         FileUtil::IOFile file{path, "wb"}; | ||||||
|         if (!file.IsOpen()) |         if (!file.IsOpen()) | ||||||
|             return DownloadResult::GeneralFSError; |             return DownloadResult::GeneralFSError; | ||||||
|         if (!file.Resize(response->body.size())) |         if (!file.Resize(response->body.size())) | ||||||
| @ -154,7 +175,6 @@ public: | |||||||
|         return DownloadResult::Success; |         return DownloadResult::Success; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: |  | ||||||
|     using Digest = std::array<u8, 0x20>; |     using Digest = std::array<u8, 0x20>; | ||||||
|     static Digest DigestFile(std::vector<u8> bytes) { |     static Digest DigestFile(std::vector<u8> bytes) { | ||||||
|         Digest out{}; |         Digest out{}; | ||||||
| @ -163,7 +183,7 @@ private: | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::unique_ptr<httplib::Client> client; |     std::unique_ptr<httplib::Client> client; | ||||||
|     std::string zip_path; |     std::string path; | ||||||
|     u64 title_id; |     u64 title_id; | ||||||
|     u64 build_id; |     u64 build_id; | ||||||
| }; | }; | ||||||
| @ -191,9 +211,14 @@ void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title, | |||||||
|     const auto zip_path{GetZIPFilePath(title.title_id)}; |     const auto zip_path{GetZIPFilePath(title.title_id)}; | ||||||
|     Boxcat::Client client{zip_path, title.title_id, title.build_id}; |     Boxcat::Client client{zip_path, title.title_id, title.build_id}; | ||||||
| 
 | 
 | ||||||
|     const auto res = client.Download(); |     const auto res = client.DownloadDataZip(); | ||||||
|     if (res != DownloadResult::Success) { |     if (res != DownloadResult::Success) { | ||||||
|         LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); |         LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); | ||||||
|  | 
 | ||||||
|  |         if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { | ||||||
|  |             FileUtil::Delete(zip_path); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         HandleDownloadDisplayResult(res); |         HandleDownloadDisplayResult(res); | ||||||
|         failure(); |         failure(); | ||||||
|         return; |         return; | ||||||
| @ -286,6 +311,39 @@ void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) { | |||||||
|               Common::HexArrayToString(passphrase)); |               Common::HexArrayToString(passphrase)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) { | ||||||
|  |     const auto path{GetBINFilePath(title.title_id)}; | ||||||
|  | 
 | ||||||
|  |     if (Settings::values.bcat_boxcat_local) { | ||||||
|  |         LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); | ||||||
|  |     } else { | ||||||
|  |         Boxcat::Client client{path, title.title_id, title.build_id}; | ||||||
|  | 
 | ||||||
|  |         const auto res = client.DownloadLaunchParam(); | ||||||
|  |         if (res != DownloadResult::Success) { | ||||||
|  |             LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); | ||||||
|  | 
 | ||||||
|  |             if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { | ||||||
|  |                 FileUtil::Delete(path); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             HandleDownloadDisplayResult(res); | ||||||
|  |             return std::nullopt; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     FileUtil::IOFile bin{path, "rb"}; | ||||||
|  |     const auto size = bin.GetSize(); | ||||||
|  |     std::vector<u8> bytes(size); | ||||||
|  |     if (size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { | ||||||
|  |         LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!", | ||||||
|  |                   path); | ||||||
|  |         return std::nullopt; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return bytes; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, | Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, | ||||||
|                                        std::map<std::string, EventStatus>& games) { |                                        std::map<std::string, EventStatus>& games) { | ||||||
|     httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT), |     httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT), | ||||||
| @ -293,6 +351,7 @@ Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, | |||||||
| 
 | 
 | ||||||
|     httplib::Headers headers{ |     httplib::Headers headers{ | ||||||
|         {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)}, |         {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)}, | ||||||
|  |         {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers); |     const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers); | ||||||
|  | |||||||
| @ -36,6 +36,8 @@ public: | |||||||
| 
 | 
 | ||||||
|     void SetPassphrase(u64 title_id, const Passphrase& passphrase) override; |     void SetPassphrase(u64 title_id, const Passphrase& passphrase) override; | ||||||
| 
 | 
 | ||||||
|  |     std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override; | ||||||
|  | 
 | ||||||
|     enum class StatusResult { |     enum class StatusResult { | ||||||
|         Success, |         Success, | ||||||
|         Offline, |         Offline, | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Zach Hilman
						Zach Hilman