bcat: Implement IDeliveryCacheProgressService commands
Used to query completion status and events for the current delivery task.
This commit is contained in:
		| @@ -2,13 +2,144 @@ | |||||||
| // Licensed under GPLv2 or any later version | // Licensed under GPLv2 or any later version | ||||||
| // Refer to the license.txt file included. | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <cctype> | ||||||
|  | #include <mbedtls/md5.h> | ||||||
|  | #include "backend/boxcat.h" | ||||||
|  | #include "common/hex_util.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
|  | #include "common/string_util.h" | ||||||
|  | #include "core/file_sys/vfs.h" | ||||||
| #include "core/hle/ipc_helpers.h" | #include "core/hle/ipc_helpers.h" | ||||||
|  | #include "core/hle/kernel/process.h" | ||||||
|  | #include "core/hle/kernel/readable_event.h" | ||||||
|  | #include "core/hle/kernel/writable_event.h" | ||||||
|  | #include "core/hle/service/bcat/backend/backend.h" | ||||||
| #include "core/hle/service/bcat/bcat.h" | #include "core/hle/service/bcat/bcat.h" | ||||||
| #include "core/hle/service/bcat/module.h" | #include "core/hle/service/bcat/module.h" | ||||||
|  | #include "core/hle/service/filesystem/filesystem.h" | ||||||
|  | #include "core/settings.h" | ||||||
|  |  | ||||||
| namespace Service::BCAT { | namespace Service::BCAT { | ||||||
|  |  | ||||||
|  | constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::BCAT, 1}; | ||||||
|  | constexpr ResultCode ERROR_FAILED_OPEN_ENTITY{ErrorModule::BCAT, 2}; | ||||||
|  | constexpr ResultCode ERROR_ENTITY_ALREADY_OPEN{ErrorModule::BCAT, 6}; | ||||||
|  | constexpr ResultCode ERROR_NO_OPEN_ENTITY{ErrorModule::BCAT, 7}; | ||||||
|  |  | ||||||
|  | // The command to clear the delivery cache just calls fs IFileSystem DeleteFile on all of the files | ||||||
|  | // and if any of them have a non-zero result it just forwards that result. This is the FS error code | ||||||
|  | // for permission denied, which is the closest approximation of this scenario. | ||||||
|  | constexpr ResultCode ERROR_FAILED_CLEAR_CACHE{ErrorModule::FS, 6400}; | ||||||
|  |  | ||||||
|  | using BCATDigest = std::array<u8, 0x10>; | ||||||
|  |  | ||||||
|  | struct DeliveryCacheProgressImpl { | ||||||
|  |     enum class Status : u8 { | ||||||
|  |         Incomplete = 0x1, | ||||||
|  |         Complete = 0x9, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     Status status = Status::Incomplete; | ||||||
|  |     INSERT_PADDING_BYTES( | ||||||
|  |         0x1FF); ///< TODO(DarkLordZach): RE this structure. It just seems to convey info about the | ||||||
|  |                 ///< progress of the BCAT sync, but for us just setting completion works. | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(DeliveryCacheProgressImpl) == 0x200, | ||||||
|  |               "DeliveryCacheProgressImpl has incorrect size."); | ||||||
|  |  | ||||||
|  | namespace { | ||||||
|  |  | ||||||
|  | u64 GetCurrentBuildID() { | ||||||
|  |     const auto& id = Core::System::GetInstance().GetCurrentProcessBuildID(); | ||||||
|  |     u64 out{}; | ||||||
|  |     std::memcpy(&out, id.data(), sizeof(u64)); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // The digest is only used to determine if a file is unique compared to others of the same name. | ||||||
|  | // Since the algorithm isn't ever checked in game, MD5 is safe. | ||||||
|  | BCATDigest DigestFile(const FileSys::VirtualFile& file) { | ||||||
|  |     BCATDigest out{}; | ||||||
|  |     const auto bytes = file->ReadAllBytes(); | ||||||
|  |     mbedtls_md5(bytes.data(), bytes.size(), out.data()); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // For a name to be valid it must be non-empty, must have a null terminating character as the final | ||||||
|  | // char, can only contain numbers, letters, underscores and a hyphen if directory and a period if | ||||||
|  | // file. | ||||||
|  | bool VerifyNameValidInternal(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name, | ||||||
|  |                              char match_char) { | ||||||
|  |     const auto null_chars = std::count(name.begin(), name.end(), 0); | ||||||
|  |     const auto bad_chars = std::count_if(name.begin(), name.end(), [match_char](char c) { | ||||||
|  |         return !std::isalnum(static_cast<u8>(c)) && c != '_' && c != match_char && c != '\0'; | ||||||
|  |     }); | ||||||
|  |     if (null_chars == 0x20 || null_chars == 0 || bad_chars != 0 || name[0x1F] != '\0') { | ||||||
|  |         LOG_ERROR(Service_BCAT, "Name passed was invalid!"); | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|  |         rb.Push(ERROR_INVALID_ARGUMENT); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VerifyNameValidDir(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name) { | ||||||
|  |     return VerifyNameValidInternal(ctx, name, '-'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool VerifyNameValidFile(Kernel::HLERequestContext& ctx, std::array<char, 0x20> name) { | ||||||
|  |     return VerifyNameValidInternal(ctx, name, '.'); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // Anonymous namespace | ||||||
|  |  | ||||||
|  | using DirectoryName = std::array<char, 0x20>; | ||||||
|  | using FileName = std::array<char, 0x20>; | ||||||
|  |  | ||||||
|  | struct DeliveryCacheDirectoryEntry { | ||||||
|  |     FileName name; | ||||||
|  |     u64 size; | ||||||
|  |     BCATDigest digest; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class IDeliveryCacheProgressService final : public ServiceFramework<IDeliveryCacheProgressService> { | ||||||
|  | public: | ||||||
|  |     IDeliveryCacheProgressService(Kernel::SharedPtr<Kernel::ReadableEvent> event, | ||||||
|  |                                   const DeliveryCacheProgressImpl& impl) | ||||||
|  |         : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) { | ||||||
|  |         // clang-format off | ||||||
|  |         static const FunctionInfo functions[] = { | ||||||
|  |             {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"}, | ||||||
|  |             {1, &IDeliveryCacheProgressService::GetImpl, "GetImpl"}, | ||||||
|  |         }; | ||||||
|  |         // clang-format on | ||||||
|  |  | ||||||
|  |         RegisterHandlers(functions); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     void GetEvent(Kernel::HLERequestContext& ctx) { | ||||||
|  |         LOG_DEBUG(Service_BCAT, "called"); | ||||||
|  |  | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 2, 1}; | ||||||
|  |         rb.Push(RESULT_SUCCESS); | ||||||
|  |         rb.PushCopyObjects(event); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void GetImpl(Kernel::HLERequestContext& ctx) { | ||||||
|  |         LOG_DEBUG(Service_BCAT, "called"); | ||||||
|  |  | ||||||
|  |         ctx.WriteBuffer(&impl, sizeof(DeliveryCacheProgressImpl)); | ||||||
|  |  | ||||||
|  |         IPC::ResponseBuilder rb{ctx, 2}; | ||||||
|  |         rb.Push(RESULT_SUCCESS); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Kernel::SharedPtr<Kernel::ReadableEvent> event; | ||||||
|  |     const DeliveryCacheProgressImpl& impl; | ||||||
|  | }; | ||||||
|  |  | ||||||
| class IBcatService final : public ServiceFramework<IBcatService> { | class IBcatService final : public ServiceFramework<IBcatService> { | ||||||
| public: | public: | ||||||
|     IBcatService(Backend& backend) : ServiceFramework("IBcatService"), backend(backend) { |     IBcatService(Backend& backend) : ServiceFramework("IBcatService"), backend(backend) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user