diff options
| author | 2019-05-01 22:42:50 -0400 | |
|---|---|---|
| committer | 2019-09-30 17:27:23 -0400 | |
| commit | e8183f9ef0296cad233c6d7679f5f83b4e0dc5a8 (patch) | |
| tree | be421b2afdefef641b8e8797f5ce4a6198d7657e /src | |
| parent | bcat: Add backend function for BCAT Indirect (launch parameter) (diff) | |
| download | yuzu-e8183f9ef0296cad233c6d7679f5f83b4e0dc5a8.tar.gz yuzu-e8183f9ef0296cad233c6d7679f5f83b4e0dc5a8.tar.xz yuzu-e8183f9ef0296cad233c6d7679f5f83b4e0dc5a8.zip | |
boxcat: Add downloading and client for launch parameter data
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/hle/service/bcat/backend/boxcat.cpp | 91 | ||||
| -rw-r--r-- | src/core/hle/service/bcat/backend/boxcat.h | 2 |
2 files changed, 77 insertions, 16 deletions
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp index 539140f30..f37f92bf4 100644 --- a/src/core/hle/service/bcat/backend/boxcat.cpp +++ b/src/core/hle/service/bcat/backend/boxcat.cpp | |||
| @@ -25,13 +25,16 @@ constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org"; | |||
| 25 | 25 | ||
| 26 | // Formatted using fmt with arg[0] = hex title id | 26 | // Formatted using fmt with arg[0] = hex title id |
| 27 | constexpr char BOXCAT_PATHNAME_DATA[] = "/boxcat/titles/{:016X}/data"; | 27 | constexpr char BOXCAT_PATHNAME_DATA[] = "/boxcat/titles/{:016X}/data"; |
| 28 | constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/boxcat/titles/{:016X}/launchparam"; | ||
| 28 | 29 | ||
| 29 | constexpr char BOXCAT_PATHNAME_EVENTS[] = "/boxcat/events"; | 30 | constexpr char BOXCAT_PATHNAME_EVENTS[] = "/boxcat/events"; |
| 30 | 31 | ||
| 31 | constexpr char BOXCAT_API_VERSION[] = "1"; | 32 | constexpr char BOXCAT_API_VERSION[] = "1"; |
| 33 | constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu"; | ||
| 32 | 34 | ||
| 33 | // HTTP status codes for Boxcat | 35 | // HTTP status codes for Boxcat |
| 34 | enum class ResponseStatus { | 36 | enum class ResponseStatus { |
| 37 | Ok = 200, ///< Operation completed successfully. | ||
| 35 | BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server. | 38 | BadClientVersion = 301, ///< The Boxcat-Client-Version doesn't match the server. |
| 36 | NoUpdate = 304, ///< The digest provided would match the new data, no need to update. | 39 | NoUpdate = 304, ///< The digest provided would match the new data, no need to update. |
| 37 | NoMatchTitleId = 404, ///< The title ID provided doesn't have a boxcat implementation. | 40 | 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 | |||
| 74 | 77 | ||
| 75 | namespace { | 78 | namespace { |
| 76 | 79 | ||
| 80 | std::string GetBINFilePath(u64 title_id) { | ||
| 81 | return fmt::format("{}bcat/{:016X}/launchparam.bin", | ||
| 82 | FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); | ||
| 83 | } | ||
| 84 | |||
| 77 | std::string GetZIPFilePath(u64 title_id) { | 85 | std::string GetZIPFilePath(u64 title_id) { |
| 78 | return fmt::format("{}bcat/{:016X}/data.zip", | 86 | return fmt::format("{}bcat/{:016X}/data.zip", |
| 79 | FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); | 87 | FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id); |
| @@ -98,27 +106,40 @@ void HandleDownloadDisplayResult(DownloadResult res) { | |||
| 98 | 106 | ||
| 99 | class Boxcat::Client { | 107 | class Boxcat::Client { |
| 100 | public: | 108 | public: |
| 101 | Client(std::string zip_path, u64 title_id, u64 build_id) | 109 | Client(std::string path, u64 title_id, u64 build_id) |
| 102 | : zip_path(std::move(zip_path)), title_id(title_id), build_id(build_id) {} | 110 | : path(std::move(path)), title_id(title_id), build_id(build_id) {} |
| 111 | |||
| 112 | DownloadResult DownloadDataZip() { | ||
| 113 | return DownloadInternal(fmt::format(BOXCAT_PATHNAME_DATA, title_id), TIMEOUT_SECONDS, | ||
| 114 | "Boxcat-Data-Digest", "application/zip"); | ||
| 115 | } | ||
| 116 | |||
| 117 | DownloadResult DownloadLaunchParam() { | ||
| 118 | return DownloadInternal(fmt::format(BOXCAT_PATHNAME_LAUNCHPARAM, title_id), | ||
| 119 | TIMEOUT_SECONDS / 3, "Boxcat-LaunchParam-Digest", | ||
| 120 | "application/octet-stream"); | ||
| 121 | } | ||
| 103 | 122 | ||
| 104 | DownloadResult Download() { | 123 | private: |
| 105 | const auto resolved_path = fmt::format(BOXCAT_PATHNAME_DATA, title_id); | 124 | DownloadResult DownloadInternal(const std::string& resolved_path, u32 timeout_seconds, |
| 125 | const std::string& digest_header_name, | ||
| 126 | const std::string& content_type_name) { | ||
| 106 | if (client == nullptr) { | 127 | if (client == nullptr) { |
| 107 | client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, TIMEOUT_SECONDS); | 128 | client = std::make_unique<httplib::SSLClient>(BOXCAT_HOSTNAME, PORT, timeout_seconds); |
| 108 | } | 129 | } |
| 109 | 130 | ||
| 110 | httplib::Headers headers{ | 131 | httplib::Headers headers{ |
| 111 | {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)}, | 132 | {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)}, |
| 133 | {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, | ||
| 112 | {std::string("Boxcat-Build-Id"), fmt::format("{:016X}", build_id)}, | 134 | {std::string("Boxcat-Build-Id"), fmt::format("{:016X}", build_id)}, |
| 113 | }; | 135 | }; |
| 114 | 136 | ||
| 115 | if (FileUtil::Exists(zip_path)) { | 137 | if (FileUtil::Exists(path)) { |
| 116 | FileUtil::IOFile file{zip_path, "rb"}; | 138 | FileUtil::IOFile file{path, "rb"}; |
| 117 | std::vector<u8> bytes(file.GetSize()); | 139 | std::vector<u8> bytes(file.GetSize()); |
| 118 | file.ReadBytes(bytes.data(), bytes.size()); | 140 | file.ReadBytes(bytes.data(), bytes.size()); |
| 119 | const auto digest = DigestFile(bytes); | 141 | const auto digest = DigestFile(bytes); |
| 120 | headers.insert({std::string("Boxcat-Current-Zip-Digest"), | 142 | headers.insert({digest_header_name, Common::HexArrayToString(digest, false)}); |
| 121 | Common::HexArrayToString(digest, false)}); | ||
| 122 | } | 143 | } |
| 123 | 144 | ||
| 124 | const auto response = client->Get(resolved_path.c_str(), headers); | 145 | const auto response = client->Get(resolved_path.c_str(), headers); |
| @@ -133,17 +154,17 @@ public: | |||
| 133 | return DownloadResult::NoMatchTitleId; | 154 | return DownloadResult::NoMatchTitleId; |
| 134 | if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId)) | 155 | if (response->status == static_cast<int>(ResponseStatus::NoMatchBuildId)) |
| 135 | return DownloadResult::NoMatchBuildId; | 156 | return DownloadResult::NoMatchBuildId; |
| 136 | if (response->status >= 400) | 157 | if (response->status != static_cast<int>(ResponseStatus::Ok)) |
| 137 | return DownloadResult::GeneralWebError; | 158 | return DownloadResult::GeneralWebError; |
| 138 | 159 | ||
| 139 | const auto content_type = response->headers.find("content-type"); | 160 | const auto content_type = response->headers.find("content-type"); |
| 140 | if (content_type == response->headers.end() || | 161 | if (content_type == response->headers.end() || |
| 141 | content_type->second.find("application/zip") == std::string::npos) { | 162 | content_type->second.find(content_type_name) == std::string::npos) { |
| 142 | return DownloadResult::InvalidContentType; | 163 | return DownloadResult::InvalidContentType; |
| 143 | } | 164 | } |
| 144 | 165 | ||
| 145 | FileUtil::CreateFullPath(zip_path); | 166 | FileUtil::CreateFullPath(path); |
| 146 | FileUtil::IOFile file{zip_path, "wb"}; | 167 | FileUtil::IOFile file{path, "wb"}; |
| 147 | if (!file.IsOpen()) | 168 | if (!file.IsOpen()) |
| 148 | return DownloadResult::GeneralFSError; | 169 | return DownloadResult::GeneralFSError; |
| 149 | if (!file.Resize(response->body.size())) | 170 | if (!file.Resize(response->body.size())) |
| @@ -154,7 +175,6 @@ public: | |||
| 154 | return DownloadResult::Success; | 175 | return DownloadResult::Success; |
| 155 | } | 176 | } |
| 156 | 177 | ||
| 157 | private: | ||
| 158 | using Digest = std::array<u8, 0x20>; | 178 | using Digest = std::array<u8, 0x20>; |
| 159 | static Digest DigestFile(std::vector<u8> bytes) { | 179 | static Digest DigestFile(std::vector<u8> bytes) { |
| 160 | Digest out{}; | 180 | Digest out{}; |
| @@ -163,7 +183,7 @@ private: | |||
| 163 | } | 183 | } |
| 164 | 184 | ||
| 165 | std::unique_ptr<httplib::Client> client; | 185 | std::unique_ptr<httplib::Client> client; |
| 166 | std::string zip_path; | 186 | std::string path; |
| 167 | u64 title_id; | 187 | u64 title_id; |
| 168 | u64 build_id; | 188 | u64 build_id; |
| 169 | }; | 189 | }; |
| @@ -191,9 +211,14 @@ void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title, | |||
| 191 | const auto zip_path{GetZIPFilePath(title.title_id)}; | 211 | const auto zip_path{GetZIPFilePath(title.title_id)}; |
| 192 | Boxcat::Client client{zip_path, title.title_id, title.build_id}; | 212 | Boxcat::Client client{zip_path, title.title_id, title.build_id}; |
| 193 | 213 | ||
| 194 | const auto res = client.Download(); | 214 | const auto res = client.DownloadDataZip(); |
| 195 | if (res != DownloadResult::Success) { | 215 | if (res != DownloadResult::Success) { |
| 196 | LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); | 216 | LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); |
| 217 | |||
| 218 | if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { | ||
| 219 | FileUtil::Delete(zip_path); | ||
| 220 | } | ||
| 221 | |||
| 197 | HandleDownloadDisplayResult(res); | 222 | HandleDownloadDisplayResult(res); |
| 198 | failure(); | 223 | failure(); |
| 199 | return; | 224 | return; |
| @@ -286,6 +311,39 @@ void Boxcat::SetPassphrase(u64 title_id, const Passphrase& passphrase) { | |||
| 286 | Common::HexArrayToString(passphrase)); | 311 | Common::HexArrayToString(passphrase)); |
| 287 | } | 312 | } |
| 288 | 313 | ||
| 314 | std::optional<std::vector<u8>> Boxcat::GetLaunchParameter(TitleIDVersion title) { | ||
| 315 | const auto path{GetBINFilePath(title.title_id)}; | ||
| 316 | |||
| 317 | if (Settings::values.bcat_boxcat_local) { | ||
| 318 | LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); | ||
| 319 | } else { | ||
| 320 | Boxcat::Client client{path, title.title_id, title.build_id}; | ||
| 321 | |||
| 322 | const auto res = client.DownloadLaunchParam(); | ||
| 323 | if (res != DownloadResult::Success) { | ||
| 324 | LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); | ||
| 325 | |||
| 326 | if (res == DownloadResult::NoMatchBuildId || res == DownloadResult::NoMatchTitleId) { | ||
| 327 | FileUtil::Delete(path); | ||
| 328 | } | ||
| 329 | |||
| 330 | HandleDownloadDisplayResult(res); | ||
| 331 | return std::nullopt; | ||
| 332 | } | ||
| 333 | } | ||
| 334 | |||
| 335 | FileUtil::IOFile bin{path, "rb"}; | ||
| 336 | const auto size = bin.GetSize(); | ||
| 337 | std::vector<u8> bytes(size); | ||
| 338 | if (size == 0 || bin.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { | ||
| 339 | LOG_ERROR(Service_BCAT, "Boxcat failed to read launch parameter binary at path '{}'!", | ||
| 340 | path); | ||
| 341 | return std::nullopt; | ||
| 342 | } | ||
| 343 | |||
| 344 | return bytes; | ||
| 345 | } | ||
| 346 | |||
| 289 | Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, | 347 | Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, |
| 290 | std::map<std::string, EventStatus>& games) { | 348 | std::map<std::string, EventStatus>& games) { |
| 291 | httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT), | 349 | httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast<int>(PORT), |
| @@ -293,6 +351,7 @@ Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, | |||
| 293 | 351 | ||
| 294 | httplib::Headers headers{ | 352 | httplib::Headers headers{ |
| 295 | {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)}, | 353 | {std::string("Boxcat-Client-Version"), std::string(BOXCAT_API_VERSION)}, |
| 354 | {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, | ||
| 296 | }; | 355 | }; |
| 297 | 356 | ||
| 298 | const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers); | 357 | const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers); |
diff --git a/src/core/hle/service/bcat/backend/boxcat.h b/src/core/hle/service/bcat/backend/boxcat.h index f4e60f264..1148a4eca 100644 --- a/src/core/hle/service/bcat/backend/boxcat.h +++ b/src/core/hle/service/bcat/backend/boxcat.h | |||
| @@ -36,6 +36,8 @@ public: | |||
| 36 | 36 | ||
| 37 | void SetPassphrase(u64 title_id, const Passphrase& passphrase) override; | 37 | void SetPassphrase(u64 title_id, const Passphrase& passphrase) override; |
| 38 | 38 | ||
| 39 | std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) override; | ||
| 40 | |||
| 39 | enum class StatusResult { | 41 | enum class StatusResult { |
| 40 | Success, | 42 | Success, |
| 41 | Offline, | 43 | Offline, |