summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Zach Hilman2019-05-13 18:51:02 -0400
committerGravatar Zach Hilman2019-09-30 17:27:23 -0400
commit2d410ddf4d9c0109d64fdf3319efeb9e6cc0bce1 (patch)
tree81e11426f742b8e8605782095999dc3a3ec5524b /src
parentboxcat: Use Etag header names for file digest (diff)
downloadyuzu-2d410ddf4d9c0109d64fdf3319efeb9e6cc0bce1.tar.gz
yuzu-2d410ddf4d9c0109d64fdf3319efeb9e6cc0bce1.tar.xz
yuzu-2d410ddf4d9c0109d64fdf3319efeb9e6cc0bce1.zip
bcat: Implement DeliveryCacheProgressImpl structure
Huge thanks to lioncash for re-ing this for me.
Diffstat (limited to 'src')
-rw-r--r--src/core/file_sys/vfs_libzip.cpp8
-rw-r--r--src/core/hle/service/bcat/backend/backend.cpp88
-rw-r--r--src/core/hle/service/bcat/backend/backend.h97
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp147
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.h6
-rw-r--r--src/core/hle/service/bcat/module.cpp56
6 files changed, 314 insertions, 88 deletions
diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp
index e34474ae0..8bdaa7e4a 100644
--- a/src/core/file_sys/vfs_libzip.cpp
+++ b/src/core/file_sys/vfs_libzip.cpp
@@ -15,13 +15,13 @@ VirtualDir ExtractZIP(VirtualFile file) {
15 zip_error_t error{}; 15 zip_error_t error{};
16 16
17 const auto data = file->ReadAllBytes(); 17 const auto data = file->ReadAllBytes();
18 std::unique_ptr<zip_source_t, decltype(&zip_source_free)> src{ 18 std::unique_ptr<zip_source_t, decltype(&zip_source_close)> src{
19 zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_free}; 19 zip_source_buffer_create(data.data(), data.size(), 0, &error), zip_source_close};
20 if (src == nullptr) 20 if (src == nullptr)
21 return nullptr; 21 return nullptr;
22 22
23 std::unique_ptr<zip_t, decltype(&zip_discard)> zip{zip_open_from_source(src.get(), 0, &error), 23 std::unique_ptr<zip_t, decltype(&zip_close)> zip{zip_open_from_source(src.get(), 0, &error),
24 zip_discard}; 24 zip_close};
25 if (zip == nullptr) 25 if (zip == nullptr)
26 return nullptr; 26 return nullptr;
27 27
diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp
index 9a67da2ef..e389ad568 100644
--- a/src/core/hle/service/bcat/backend/backend.cpp
+++ b/src/core/hle/service/bcat/backend/backend.cpp
@@ -4,10 +4,90 @@
4 4
5#include "common/hex_util.h" 5#include "common/hex_util.h"
6#include "common/logging/log.h" 6#include "common/logging/log.h"
7#include "core/core.h"
8#include "core/hle/lock.h"
7#include "core/hle/service/bcat/backend/backend.h" 9#include "core/hle/service/bcat/backend/backend.h"
8 10
9namespace Service::BCAT { 11namespace Service::BCAT {
10 12
13ProgressServiceBackend::ProgressServiceBackend(std::string event_name) : impl{} {
14 auto& kernel{Core::System::GetInstance().Kernel()};
15 event = Kernel::WritableEvent::CreateEventPair(
16 kernel, Kernel::ResetType::OneShot, "ProgressServiceBackend:UpdateEvent:" + event_name);
17}
18
19Kernel::SharedPtr<Kernel::ReadableEvent> ProgressServiceBackend::GetEvent() {
20 return event.readable;
21}
22
23DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() {
24 return impl;
25}
26
27void ProgressServiceBackend::SetNeedHLELock(bool need) {
28 need_hle_lock = need;
29}
30
31void ProgressServiceBackend::SetTotalSize(u64 size) {
32 impl.total_bytes = size;
33 SignalUpdate();
34}
35
36void ProgressServiceBackend::StartConnecting() {
37 impl.status = DeliveryCacheProgressImpl::Status::Connecting;
38 SignalUpdate();
39}
40
41void ProgressServiceBackend::StartProcessingDataList() {
42 impl.status = DeliveryCacheProgressImpl::Status::ProcessingDataList;
43 SignalUpdate();
44}
45
46void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name,
47 std::string_view file_name, u64 file_size) {
48 impl.status = DeliveryCacheProgressImpl::Status::Downloading;
49 impl.current_downloaded_bytes = 0;
50 impl.current_total_bytes = file_size;
51 std::memcpy(impl.current_directory.data(), dir_name.data(), std::min(dir_name.size(), 0x31ull));
52 std::memcpy(impl.current_file.data(), file_name.data(), std::min(file_name.size(), 0x31ull));
53 SignalUpdate();
54}
55
56void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) {
57 impl.current_downloaded_bytes = downloaded;
58 SignalUpdate();
59}
60
61void ProgressServiceBackend::FinishDownloadingFile() {
62 impl.total_downloaded_bytes += impl.current_total_bytes;
63 SignalUpdate();
64}
65
66void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) {
67 impl.status = DeliveryCacheProgressImpl::Status::Committing;
68 impl.current_file.fill(0);
69 impl.current_downloaded_bytes = 0;
70 impl.current_total_bytes = 0;
71 std::memcpy(impl.current_directory.data(), dir_name.data(), std::min(dir_name.size(), 0x31ull));
72 SignalUpdate();
73}
74
75void ProgressServiceBackend::FinishDownload(ResultCode result) {
76 impl.total_downloaded_bytes = impl.total_bytes;
77 impl.status = DeliveryCacheProgressImpl::Status::Done;
78 impl.result = result;
79 SignalUpdate();
80}
81
82void ProgressServiceBackend::SignalUpdate() const {
83 if (need_hle_lock) {
84 std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
85 event.writable->Signal();
86 } else {
87 event.writable->Signal();
88 }
89}
90
11Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {} 91Backend::Backend(DirectoryGetter getter) : dir_getter(std::move(getter)) {}
12 92
13Backend::~Backend() = default; 93Backend::~Backend() = default;
@@ -16,20 +96,20 @@ NullBackend::NullBackend(const DirectoryGetter& getter) : Backend(std::move(gett
16 96
17NullBackend::~NullBackend() = default; 97NullBackend::~NullBackend() = default;
18 98
19bool NullBackend::Synchronize(TitleIDVersion title, CompletionCallback callback) { 99bool NullBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
20 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, 100 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id,
21 title.build_id); 101 title.build_id);
22 102
23 callback(true); 103 progress.FinishDownload(RESULT_SUCCESS);
24 return true; 104 return true;
25} 105}
26 106
27bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name, 107bool NullBackend::SynchronizeDirectory(TitleIDVersion title, std::string name,
28 CompletionCallback callback) { 108 ProgressServiceBackend& progress) {
29 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id, 109 LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id,
30 title.build_id, name); 110 title.build_id, name);
31 111
32 callback(true); 112 progress.FinishDownload(RESULT_SUCCESS);
33 return true; 113 return true;
34} 114}
35 115
diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h
index 5b4118814..50973a13a 100644
--- a/src/core/hle/service/bcat/backend/backend.h
+++ b/src/core/hle/service/bcat/backend/backend.h
@@ -8,10 +8,14 @@
8#include <optional> 8#include <optional>
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "core/file_sys/vfs_types.h" 10#include "core/file_sys/vfs_types.h"
11#include "core/hle/kernel/readable_event.h"
12#include "core/hle/kernel/writable_event.h"
13#include "core/hle/result.h"
11 14
12namespace Service::BCAT { 15namespace Service::BCAT {
13 16
14using CompletionCallback = std::function<void(bool)>; 17struct DeliveryCacheProgressImpl;
18
15using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>; 19using DirectoryGetter = std::function<FileSys::VirtualDir(u64)>;
16using Passphrase = std::array<u8, 0x20>; 20using Passphrase = std::array<u8, 0x20>;
17 21
@@ -20,33 +24,116 @@ struct TitleIDVersion {
20 u64 build_id; 24 u64 build_id;
21}; 25};
22 26
27using DirectoryName = std::array<char, 0x20>;
28using FileName = std::array<char, 0x20>;
29
30struct DeliveryCacheProgressImpl {
31 enum class Status : s32 {
32 None = 0x0,
33 Queued = 0x1,
34 Connecting = 0x2,
35 ProcessingDataList = 0x3,
36 Downloading = 0x4,
37 Committing = 0x5,
38 Done = 0x9,
39 };
40
41 Status status;
42 ResultCode result = RESULT_SUCCESS;
43 DirectoryName current_directory;
44 FileName current_file;
45 s64 current_downloaded_bytes; ///< Bytes downloaded on current file.
46 s64 current_total_bytes; ///< Bytes total on current file.
47 s64 total_downloaded_bytes; ///< Bytes downloaded on overall download.
48 s64 total_bytes; ///< Bytes total on overall download.
49 INSERT_PADDING_BYTES(
50 0x198); ///< Appears to be unused in official code, possibly reserved for future use.
51};
52static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
53 "DeliveryCacheProgressImpl has incorrect size.");
54
55// A class to manage the signalling to the game about BCAT download progress.
56// Some of this class is implemented in module.cpp to avoid exposing the implementation structure.
57class ProgressServiceBackend {
58 friend class IBcatService;
59
60 ProgressServiceBackend(std::string event_name);
61
62 Kernel::SharedPtr<Kernel::ReadableEvent> GetEvent();
63 DeliveryCacheProgressImpl& GetImpl();
64
65public:
66 // Clients should call this with true if any of the functions are going to be called from a
67 // non-HLE thread and this class need to lock the hle mutex. (default is false)
68 void SetNeedHLELock(bool need);
69
70 // Sets the number of bytes total in the entire download.
71 void SetTotalSize(u64 size);
72
73 // Notifies the application that the backend has started connecting to the server.
74 void StartConnecting();
75 // Notifies the application that the backend has begun accumulating and processing metadata.
76 void StartProcessingDataList();
77
78 // Notifies the application that a file is starting to be downloaded.
79 void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size);
80 // Updates the progress of the current file to the size passed.
81 void UpdateFileProgress(u64 downloaded);
82 // Notifies the application that the current file has completed download.
83 void FinishDownloadingFile();
84
85 // Notifies the application that all files in this directory have completed and are being
86 // finalized.
87 void CommitDirectory(std::string_view dir_name);
88
89 // Notifies the application that the operation completed with result code result.
90 void FinishDownload(ResultCode result);
91
92private:
93 void SignalUpdate() const;
94
95 DeliveryCacheProgressImpl impl;
96 Kernel::EventPair event;
97 bool need_hle_lock = false;
98};
99
100// A class representing an abstract backend for BCAT functionality.
23class Backend { 101class Backend {
24public: 102public:
25 explicit Backend(DirectoryGetter getter); 103 explicit Backend(DirectoryGetter getter);
26 virtual ~Backend(); 104 virtual ~Backend();
27 105
28 virtual bool Synchronize(TitleIDVersion title, CompletionCallback callback) = 0; 106 // Called when the backend is needed to synchronize the data for the game with title ID and
107 // version in title. A ProgressServiceBackend object is provided to alert the application of
108 // status.
109 virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0;
110 // Very similar to Synchronize, but only for the directory provided. Backends should not alter
111 // the data for any other directories.
29 virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name, 112 virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name,
30 CompletionCallback callback) = 0; 113 ProgressServiceBackend& progress) = 0;
31 114
115 // Removes all cached data associated with title id provided.
32 virtual bool Clear(u64 title_id) = 0; 116 virtual bool Clear(u64 title_id) = 0;
33 117
118 // Sets the BCAT Passphrase to be used with the associated title ID.
34 virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0; 119 virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0;
35 120
121 // Gets the launch parameter used by AM associated with the title ID and version provided.
36 virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0; 122 virtual std::optional<std::vector<u8>> GetLaunchParameter(TitleIDVersion title) = 0;
37 123
38protected: 124protected:
39 DirectoryGetter dir_getter; 125 DirectoryGetter dir_getter;
40}; 126};
41 127
128// A backend of BCAT that provides no operation.
42class NullBackend : public Backend { 129class NullBackend : public Backend {
43public: 130public:
44 explicit NullBackend(const DirectoryGetter& getter); 131 explicit NullBackend(const DirectoryGetter& getter);
45 ~NullBackend() override; 132 ~NullBackend() override;
46 133
47 bool Synchronize(TitleIDVersion title, CompletionCallback callback) override; 134 bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
48 bool SynchronizeDirectory(TitleIDVersion title, std::string name, 135 bool SynchronizeDirectory(TitleIDVersion title, std::string name,
49 CompletionCallback callback) override; 136 ProgressServiceBackend& progress) override;
50 137
51 bool Clear(u64 title_id) override; 138 bool Clear(u64 title_id) override;
52 139
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp
index 31d2e045c..3754594df 100644
--- a/src/core/hle/service/bcat/backend/boxcat.cpp
+++ b/src/core/hle/service/bcat/backend/boxcat.cpp
@@ -14,13 +14,28 @@
14#include "core/file_sys/vfs_libzip.h" 14#include "core/file_sys/vfs_libzip.h"
15#include "core/file_sys/vfs_vector.h" 15#include "core/file_sys/vfs_vector.h"
16#include "core/frontend/applets/error.h" 16#include "core/frontend/applets/error.h"
17#include "core/hle/lock.h"
18#include "core/hle/service/am/applets/applets.h" 17#include "core/hle/service/am/applets/applets.h"
19#include "core/hle/service/bcat/backend/boxcat.h" 18#include "core/hle/service/bcat/backend/boxcat.h"
20#include "core/settings.h" 19#include "core/settings.h"
21 20
21namespace {
22
23// Prevents conflicts with windows macro called CreateFile
24FileSys::VirtualFile VfsCreateFileWrap(FileSys::VirtualDir dir, std::string_view name) {
25 return dir->CreateFile(name);
26}
27
28// Prevents conflicts with windows macro called DeleteFile
29bool VfsDeleteFileWrap(FileSys::VirtualDir dir, std::string_view name) {
30 return dir->DeleteFile(name);
31}
32
33} // Anonymous namespace
34
22namespace Service::BCAT { 35namespace Service::BCAT {
23 36
37constexpr ResultCode ERROR_GENERAL_BCAT_FAILURE{ErrorModule::BCAT, 1};
38
24constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org"; 39constexpr char BOXCAT_HOSTNAME[] = "api.yuzu-emu.org";
25 40
26// Formatted using fmt with arg[0] = hex title id 41// Formatted using fmt with arg[0] = hex title id
@@ -102,7 +117,68 @@ void HandleDownloadDisplayResult(DownloadResult res) {
102 DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {}); 117 DOWNLOAD_RESULT_LOG_MESSAGES[static_cast<std::size_t>(res)], [] {});
103} 118}
104 119
105} // namespace 120bool VfsRawCopyProgress(FileSys::VirtualFile src, FileSys::VirtualFile dest,
121 std::string_view dir_name, ProgressServiceBackend& progress,
122 std::size_t block_size = 0x1000) {
123 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
124 return false;
125 if (!dest->Resize(src->GetSize()))
126 return false;
127
128 progress.StartDownloadingFile(dir_name, src->GetName(), src->GetSize());
129
130 std::vector<u8> temp(std::min(block_size, src->GetSize()));
131 for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
132 const auto read = std::min(block_size, src->GetSize() - i);
133
134 if (src->Read(temp.data(), read, i) != read) {
135 return false;
136 }
137
138 if (dest->Write(temp.data(), read, i) != read) {
139 return false;
140 }
141
142 progress.UpdateFileProgress(i);
143 }
144
145 progress.FinishDownloadingFile();
146
147 return true;
148}
149
150bool VfsRawCopyDProgressSingle(FileSys::VirtualDir src, FileSys::VirtualDir dest,
151 ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
152 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
153 return false;
154
155 for (const auto& file : src->GetFiles()) {
156 const auto out_file = VfsCreateFileWrap(dest, file->GetName());
157 if (!VfsRawCopyProgress(file, out_file, src->GetName(), progress, block_size)) {
158 return false;
159 }
160 }
161 progress.CommitDirectory(src->GetName());
162
163 return true;
164}
165
166bool VfsRawCopyDProgress(FileSys::VirtualDir src, FileSys::VirtualDir dest,
167 ProgressServiceBackend& progress, std::size_t block_size = 0x1000) {
168 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
169 return false;
170
171 for (const auto& dir : src->GetSubdirectories()) {
172 const auto out = dest->CreateSubdirectory(dir->GetName());
173 if (!VfsRawCopyDProgressSingle(dir, out, progress, block_size)) {
174 return false;
175 }
176 }
177
178 return true;
179}
180
181} // Anonymous namespace
106 182
107class Boxcat::Client { 183class Boxcat::Client {
108public: 184public:
@@ -194,24 +270,24 @@ Boxcat::Boxcat(DirectoryGetter getter) : Backend(std::move(getter)) {}
194Boxcat::~Boxcat() = default; 270Boxcat::~Boxcat() = default;
195 271
196void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title, 272void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
197 CompletionCallback callback, std::optional<std::string> dir_name = {}) { 273 ProgressServiceBackend& progress,
198 const auto failure = [&callback] { 274 std::optional<std::string> dir_name = {}) {
199 // Acquire the HLE mutex 275 progress.SetNeedHLELock(true);
200 std::lock_guard lock{HLE::g_hle_lock};
201 callback(false);
202 };
203 276
204 if (Settings::values.bcat_boxcat_local) { 277 if (Settings::values.bcat_boxcat_local) {
205 LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download."); 278 LOG_INFO(Service_BCAT, "Boxcat using local data by override, skipping download.");
206 // Acquire the HLE mutex 279 const auto dir = dir_getter(title.title_id);
207 std::lock_guard lock{HLE::g_hle_lock}; 280 if (dir)
208 callback(true); 281 progress.SetTotalSize(dir->GetSize());
282 progress.FinishDownload(RESULT_SUCCESS);
209 return; 283 return;
210 } 284 }
211 285
212 const auto zip_path{GetZIPFilePath(title.title_id)}; 286 const auto zip_path{GetZIPFilePath(title.title_id)};
213 Boxcat::Client client{zip_path, title.title_id, title.build_id}; 287 Boxcat::Client client{zip_path, title.title_id, title.build_id};
214 288
289 progress.StartConnecting();
290
215 const auto res = client.DownloadDataZip(); 291 const auto res = client.DownloadDataZip();
216 if (res != DownloadResult::Success) { 292 if (res != DownloadResult::Success) {
217 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res); 293 LOG_ERROR(Service_BCAT, "Boxcat synchronization failed with error '{}'!", res);
@@ -221,68 +297,85 @@ void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
221 } 297 }
222 298
223 HandleDownloadDisplayResult(res); 299 HandleDownloadDisplayResult(res);
224 failure(); 300 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
225 return; 301 return;
226 } 302 }
227 303
304 progress.StartProcessingDataList();
305
228 FileUtil::IOFile zip{zip_path, "rb"}; 306 FileUtil::IOFile zip{zip_path, "rb"};
229 const auto size = zip.GetSize(); 307 const auto size = zip.GetSize();
230 std::vector<u8> bytes(size); 308 std::vector<u8> bytes(size);
231 if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) { 309 if (!zip.IsOpen() || size == 0 || zip.ReadBytes(bytes.data(), bytes.size()) != bytes.size()) {
232 LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path); 310 LOG_ERROR(Service_BCAT, "Boxcat failed to read ZIP file at path '{}'!", zip_path);
233 failure(); 311 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
234 return; 312 return;
235 } 313 }
236 314
237 const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes)); 315 const auto extracted = FileSys::ExtractZIP(std::make_shared<FileSys::VectorVfsFile>(bytes));
238 if (extracted == nullptr) { 316 if (extracted == nullptr) {
239 LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!"); 317 LOG_ERROR(Service_BCAT, "Boxcat failed to extract ZIP file!");
240 failure(); 318 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
241 return; 319 return;
242 } 320 }
243 321
244 if (dir_name == std::nullopt) { 322 if (dir_name == std::nullopt) {
323 progress.SetTotalSize(extracted->GetSize());
324
245 const auto target_dir = dir_getter(title.title_id); 325 const auto target_dir = dir_getter(title.title_id);
246 if (target_dir == nullptr || 326 if (target_dir == nullptr || !VfsRawCopyDProgress(extracted, target_dir, progress)) {
247 !FileSys::VfsRawCopyD(extracted, target_dir, VFS_COPY_BLOCK_SIZE)) {
248 LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!"); 327 LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
249 failure(); 328 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
250 return; 329 return;
251 } 330 }
252 } else { 331 } else {
253 const auto target_dir = dir_getter(title.title_id); 332 const auto target_dir = dir_getter(title.title_id);
254 if (target_dir == nullptr) { 333 if (target_dir == nullptr) {
255 LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!"); 334 LOG_ERROR(Service_BCAT, "Boxcat failed to get directory for title ID!");
256 failure(); 335 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
257 return; 336 return;
258 } 337 }
259 338
260 const auto target_sub = target_dir->GetSubdirectory(*dir_name); 339 const auto target_sub = target_dir->GetSubdirectory(*dir_name);
261 const auto source_sub = extracted->GetSubdirectory(*dir_name); 340 const auto source_sub = extracted->GetSubdirectory(*dir_name);
262 341
342 progress.SetTotalSize(source_sub->GetSize());
343
344 std::vector<std::string> filenames;
345 {
346 const auto files = target_sub->GetFiles();
347 std::transform(files.begin(), files.end(), std::back_inserter(filenames),
348 [](const auto& vfile) { return vfile->GetName(); });
349 }
350
351 for (const auto& filename : filenames) {
352 VfsDeleteFileWrap(target_sub, filename);
353 }
354
263 if (target_sub == nullptr || source_sub == nullptr || 355 if (target_sub == nullptr || source_sub == nullptr ||
264 !FileSys::VfsRawCopyD(source_sub, target_sub, VFS_COPY_BLOCK_SIZE)) { 356 !VfsRawCopyDProgressSingle(source_sub, target_sub, progress)) {
265 LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!"); 357 LOG_ERROR(Service_BCAT, "Boxcat failed to copy extracted ZIP to target directory!");
266 failure(); 358 progress.FinishDownload(ERROR_GENERAL_BCAT_FAILURE);
267 return; 359 return;
268 } 360 }
269 } 361 }
270 362
271 // Acquire the HLE mutex 363 progress.FinishDownload(RESULT_SUCCESS);
272 std::lock_guard lock{HLE::g_hle_lock};
273 callback(true);
274} 364}
275 365
276bool Boxcat::Synchronize(TitleIDVersion title, CompletionCallback callback) { 366bool Boxcat::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) {
277 is_syncing.exchange(true); 367 is_syncing.exchange(true);
278 std::thread(&SynchronizeInternal, dir_getter, title, callback, std::nullopt).detach(); 368 std::thread([this, title, &progress] { SynchronizeInternal(dir_getter, title, progress); })
369 .detach();
279 return true; 370 return true;
280} 371}
281 372
282bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name, 373bool Boxcat::SynchronizeDirectory(TitleIDVersion title, std::string name,
283 CompletionCallback callback) { 374 ProgressServiceBackend& progress) {
284 is_syncing.exchange(true); 375 is_syncing.exchange(true);
285 std::thread(&SynchronizeInternal, dir_getter, title, callback, name).detach(); 376 std::thread(
377 [this, title, name, &progress] { SynchronizeInternal(dir_getter, title, progress, name); })
378 .detach();
286 return true; 379 return true;
287} 380}
288 381
diff --git a/src/core/hle/service/bcat/backend/boxcat.h b/src/core/hle/service/bcat/backend/boxcat.h
index 1148a4eca..601151189 100644
--- a/src/core/hle/service/bcat/backend/boxcat.h
+++ b/src/core/hle/service/bcat/backend/boxcat.h
@@ -21,16 +21,16 @@ struct EventStatus {
21/// doesn't require a switch or nintendo account. The content is controlled by the yuzu team. 21/// doesn't require a switch or nintendo account. The content is controlled by the yuzu team.
22class Boxcat final : public Backend { 22class Boxcat final : public Backend {
23 friend void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title, 23 friend void SynchronizeInternal(DirectoryGetter dir_getter, TitleIDVersion title,
24 CompletionCallback callback, 24 ProgressServiceBackend& progress,
25 std::optional<std::string> dir_name); 25 std::optional<std::string> dir_name);
26 26
27public: 27public:
28 explicit Boxcat(DirectoryGetter getter); 28 explicit Boxcat(DirectoryGetter getter);
29 ~Boxcat() override; 29 ~Boxcat() override;
30 30
31 bool Synchronize(TitleIDVersion title, CompletionCallback callback) override; 31 bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override;
32 bool SynchronizeDirectory(TitleIDVersion title, std::string name, 32 bool SynchronizeDirectory(TitleIDVersion title, std::string name,
33 CompletionCallback callback) override; 33 ProgressServiceBackend& progress) override;
34 34
35 bool Clear(u64 title_id) override; 35 bool Clear(u64 title_id) override;
36 36
diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp
index a8d545992..d5f9e9d3b 100644
--- a/src/core/hle/service/bcat/module.cpp
+++ b/src/core/hle/service/bcat/module.cpp
@@ -33,20 +33,6 @@ constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400};
33 33
34using BCATDigest = std::array<u8, 0x10>; 34using BCATDigest = std::array<u8, 0x10>;
35 35
36struct DeliveryCacheProgressImpl {
37 enum class Status : u8 {
38 Incomplete = 0x1,
39 Complete = 0x9,
40 };
41
42 Status status = Status::Incomplete;
43 INSERT_PADDING_BYTES(
44 0x1FF); ///< TODO(DarkLordZach): RE this structure. It just seems to convey info about the
45 ///< progress of the BCAT sync, but for us just setting completion works.
46};
47static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200,
48 "DeliveryCacheProgressImpl has incorrect size.");
49
50namespace { 36namespace {
51 37
52u64 GetCurrentBuildID() { 38u64 GetCurrentBuildID() {
@@ -84,19 +70,16 @@ bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x
84 return true; 70 return true;
85} 71}
86 72
87bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name) { 73bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, DirectoryName name) {
88 return VerifyNameValidInternal(ctx, name, '-'); 74 return VerifyNameValidInternal(ctx, name, '-');
89} 75}
90 76
91bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name) { 77bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, FileName name) {
92 return VerifyNameValidInternal(ctx, name, '.'); 78 return VerifyNameValidInternal(ctx, name, '.');
93} 79}
94 80
95} // Anonymous namespace 81} // Anonymous namespace
96 82
97using DirectoryName = std::array<char, 0x20>;
98using FileName = std::array<char, 0x20>;
99
100struct DeliveryCacheDirectoryEntry { 83struct DeliveryCacheDirectoryEntry {
101 FileName name; 84 FileName name;
102 u64 size; 85 u64 size;
@@ -162,15 +145,6 @@ public:
162 }; 145 };
163 // clang-format on 146 // clang-format on
164 RegisterHandlers(functions); 147 RegisterHandlers(functions);
165
166 auto& kernel{Core::System::GetInstance().Kernel()};
167 progress.at(static_cast<std::size_t>(SyncType::Normal)).event =
168 Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
169 "BCAT::IDeliveryCacheProgressEvent");
170 progress.at(static_cast<std::size_t>(SyncType::Directory)).event =
171 Kernel::WritableEvent::CreateEventPair(
172 kernel, Kernel::ResetType::OneShot,
173 "BCAT::IDeliveryCacheProgressEvent::DirectoryName");
174 } 148 }
175 149
176private: 150private:
@@ -180,24 +154,17 @@ private:
180 Count, 154 Count,
181 }; 155 };
182 156
183 std::function<void(bool)> CreateCallback(SyncType type) {
184 return [this, type](bool success) {
185 auto& pair{progress.at(static_cast<std::size_t>(type))};
186 pair.impl.status = DeliveryCacheProgressImpl::Status::Complete;
187 pair.event.writable->Signal();
188 };
189 }
190
191 std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) { 157 std::shared_ptr<IDeliveryCacheProgressService> CreateProgressService(SyncType type) {
192 const auto& pair{progress.at(static_cast<std::size_t>(type))}; 158 auto& backend{progress.at(static_cast<std::size_t>(type))};
193 return std::make_shared<IDeliveryCacheProgressService>(pair.event.readable, pair.impl); 159 return std::make_shared<IDeliveryCacheProgressService>(backend.GetEvent(),
160 backend.GetImpl());
194 } 161 }
195 162
196 void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) { 163 void RequestSyncDeliveryCache(Kernel::HLERequestContext& ctx) {
197 LOG_DEBUG(Service_BCAT, "called"); 164 LOG_DEBUG(Service_BCAT, "called");
198 165
199 backend.Synchronize({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()}, 166 backend.Synchronize({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
200 CreateCallback(SyncType::Normal)); 167 progress.at(static_cast<std::size_t>(SyncType::Normal)));
201 168
202 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 169 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
203 rb.Push(RESULT_SUCCESS); 170 rb.Push(RESULT_SUCCESS);
@@ -213,7 +180,8 @@ private:
213 LOG_DEBUG(Service_BCAT, "called, name={}", name); 180 LOG_DEBUG(Service_BCAT, "called, name={}", name);
214 181
215 backend.SynchronizeDirectory({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()}, 182 backend.SynchronizeDirectory({Core::CurrentProcess()->GetTitleID(), GetCurrentBuildID()},
216 name, CreateCallback(SyncType::Directory)); 183 name,
184 progress.at(static_cast<std::size_t>(SyncType::Directory)));
217 185
218 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 186 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
219 rb.Push(RESULT_SUCCESS); 187 rb.Push(RESULT_SUCCESS);
@@ -278,12 +246,10 @@ private:
278 246
279 Backend& backend; 247 Backend& backend;
280 248
281 struct ProgressPair { 249 std::array<ProgressServiceBackend, static_cast<std::size_t>(SyncType::Count)> progress{
282 Kernel::EventPair event; 250 ProgressServiceBackend{"Normal"},
283 DeliveryCacheProgressImpl impl; 251 ProgressServiceBackend{"Directory"},
284 }; 252 };
285
286 std::array<ProgressPair, static_cast<std::size_t>(SyncType::Count)> progress{};
287}; 253};
288 254
289void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { 255void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) {