summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Zach Hilman2019-05-01 22:42:50 -0400
committerGravatar Zach Hilman2019-09-30 17:27:23 -0400
commite8183f9ef0296cad233c6d7679f5f83b4e0dc5a8 (patch)
treebe421b2afdefef641b8e8797f5ce4a6198d7657e /src
parentbcat: Add backend function for BCAT Indirect (launch parameter) (diff)
downloadyuzu-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.cpp91
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.h2
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
27constexpr char BOXCAT_PATHNAME_DATA[] = "/boxcat/titles/{:016X}/data"; 27constexpr char BOXCAT_PATHNAME_DATA[] = "/boxcat/titles/{:016X}/data";
28constexpr char BOXCAT_PATHNAME_LAUNCHPARAM[] = "/boxcat/titles/{:016X}/launchparam";
28 29
29constexpr char BOXCAT_PATHNAME_EVENTS[] = "/boxcat/events"; 30constexpr char BOXCAT_PATHNAME_EVENTS[] = "/boxcat/events";
30 31
31constexpr char BOXCAT_API_VERSION[] = "1"; 32constexpr char BOXCAT_API_VERSION[] = "1";
33constexpr char BOXCAT_CLIENT_TYPE[] = "yuzu";
32 34
33// HTTP status codes for Boxcat 35// HTTP status codes for Boxcat
34enum class ResponseStatus { 36enum 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
75namespace { 78namespace {
76 79
80std::string GetBINFilePath(u64 title_id) {
81 return fmt::format("{}bcat/{:016X}/launchparam.bin",
82 FileUtil::GetUserPath(FileUtil::UserPath::CacheDir), title_id);
83}
84
77std::string GetZIPFilePath(u64 title_id) { 85std::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
99class Boxcat::Client { 107class Boxcat::Client {
100public: 108public:
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() { 123private:
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
157private:
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
314std::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
289Boxcat::StatusResult Boxcat::GetStatus(std::optional<std::string>& global, 347Boxcat::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,