diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 15c532c87..0906adc0f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -236,6 +236,8 @@ add_library(citra_core STATIC hle/service/boss/boss_p.h hle/service/boss/boss_u.cpp hle/service/boss/boss_u.h + hle/service/boss/spotpass_utils.cpp + hle/service/boss/spotpass_utils.h hle/service/cam/cam.cpp hle/service/cam/cam.h hle/service/cam/cam_c.cpp diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h index 0fada0f23..8dc0f14d8 100644 --- a/src/core/file_sys/archive_backend.h +++ b/src/core/file_sys/archive_backend.h @@ -41,6 +41,7 @@ class Path { public: Path() : type(LowPathType::Invalid) {} Path(const char* path) : type(LowPathType::Char), string(path) {} + Path(std::string path) : type(LowPathType::Char), string(std::move(path)) {} Path(std::vector binary_data) : type(LowPathType::Binary), binary(std::move(binary_data)) {} template Path(const std::array& binary_data) diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp index 7ab54d39c..643821987 100644 --- a/src/core/file_sys/archive_extsavedata.cpp +++ b/src/core/file_sys/archive_extsavedata.cpp @@ -217,7 +217,13 @@ Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low) { ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location, bool shared) - : shared(shared), mount_point(GetExtDataContainerPath(mount_location, shared)) { + : shared(shared), boss(false), mount_point(GetExtDataContainerPath(mount_location, shared)) { + LOG_DEBUG(Service_FS, "Directory {} set as base for ExtSaveData.", mount_point); +} + +ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location, + bool shared, bool boss) + : shared(shared), boss(boss), mount_point(GetExtDataContainerPath(mount_location, shared)) { LOG_DEBUG(Service_FS, "Directory {} set as base for ExtSaveData.", mount_point); } @@ -242,7 +248,8 @@ Path ArchiveFactory_ExtSaveData::GetCorrectedPath(const Path& path) { ResultVal> ArchiveFactory_ExtSaveData::Open(const Path& path, u64 program_id) { - std::string fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + "user/"; + std::string fullpath = + GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + (boss ? "boss/" : "user/"); if (!FileUtil::Exists(fullpath)) { // TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData. // ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist. diff --git a/src/core/file_sys/archive_extsavedata.h b/src/core/file_sys/archive_extsavedata.h index dec84ade9..2c6b7c8d5 100644 --- a/src/core/file_sys/archive_extsavedata.h +++ b/src/core/file_sys/archive_extsavedata.h @@ -19,6 +19,7 @@ namespace FileSys { class ArchiveFactory_ExtSaveData final : public ArchiveFactory { public: ArchiveFactory_ExtSaveData(const std::string& mount_point, bool shared); + ArchiveFactory_ExtSaveData(const std::string& mount_point, bool shared, bool boss); std::string GetName() const override { return "ExtSaveData"; @@ -44,6 +45,7 @@ public: private: bool shared; ///< Whether this archive represents an ExtSaveData archive or a SharedExtSaveData /// archive + bool boss; ///< Whether this archive is a spotpass archive or not /** * This holds the full directory path for this archive, it is only set after a successful call diff --git a/src/core/hle/service/boss/boss.cpp b/src/core/hle/service/boss/boss.cpp index ea29786cd..64d1af59a 100644 --- a/src/core/hle/service/boss/boss.cpp +++ b/src/core/hle/service/boss/boss.cpp @@ -2,25 +2,152 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" +#include +#include +#include +#include +#include "common/string_util.h" #include "core/core.h" +#include "core/file_sys/archive_extsavedata.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/file_backend.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/result.h" #include "core/hle/service/boss/boss.h" #include "core/hle/service/boss/boss_p.h" #include "core/hle/service/boss/boss_u.h" +#include "core/hw/aes/key.h" namespace Service::BOSS { void Module::Interface::InitializeSession(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const u64 programID = rp.Pop(); + const u64 provided_program_id = rp.Pop(); rp.PopPID(); + util.cur_props = BossTaskProperties(); + // I'm putting this here for now because I don't know where else to put it; + // the BOSS service saves data in its BOSS_A(Archive? A list of program ids and some + // properties that are keyed on program), BOSS_SS (Saved Strings? Includes the url and the + // other string properties, and also some other properties?, keyed on task_id) and BOSS_SV + // (Saved Values? Includes task id and most properties, keyed on task_id) databases in the + // following format: A four byte header (always 00 80 34 12?) followed by any number of + // 0x800(BOSS_A) and 0xC00(BOSS_SS and BOSS_SV) entries. + + if (provided_program_id != 0) { + util.program_id = provided_program_id; + } + + LOG_DEBUG(Service_BOSS, "called, program_id={:#018X}", util.program_id); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + // We can always return success from this as the boss dbs are not properly used atm rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_BOSS, "(STUBBED) programID={:#018X}", programID); + const std::string& nand_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); + FileSys::ArchiveFactory_SystemSaveData systemsavedata_factory(nand_directory); + + // Open the SystemSaveData archive 0x00010034 + FileSys::Path archive_path(BOSS_SYSTEM_SAVEDATA_ID); + auto archive_result = systemsavedata_factory.Open(archive_path, 0); + + std::unique_ptr boss_system_save_data_archive; + + // If the archive didn't exist, create the files inside + if (archive_result.Code() == FileSys::ERROR_NOT_FOUND) { + // Format the archive to create the directories + systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0); + + // Open it again to get a valid archive now that the folder exists + auto create_archive_result = systemsavedata_factory.Open(archive_path, 0); + if (create_archive_result.Succeeded()) { + boss_system_save_data_archive = std::move(create_archive_result).Unwrap(); + } else { + LOG_ERROR(Service_BOSS, "Could not open boss savedata"); + return; + } + } else if (archive_result.Succeeded()) { + boss_system_save_data_archive = std::move(archive_result).Unwrap(); + } else { + LOG_ERROR(Service_BOSS, "Could not open boss savedata"); + return; + } + FileSys::Path boss_a_path("/BOSS_A.db"); + FileSys::Mode open_mode = {}; + open_mode.read_flag.Assign(1); + auto boss_a_result = boss_system_save_data_archive->OpenFile(boss_a_path, open_mode); + + // Read the file if it already exists + if (boss_a_result.Succeeded()) { + auto boss_a = std::move(boss_a_result).Unwrap(); + const u64 boss_a_size = boss_a->GetSize(); + // Check the file has a valid size (multiple of the number of entries, plus header) + if (!(boss_a_size > BOSS_SAVE_HEADER_SIZE && + ((boss_a_size - BOSS_SAVE_HEADER_SIZE) % BOSS_A_ENTRY_SIZE) == 0)) { + } else { + u64 num_entries = (boss_a_size - BOSS_SAVE_HEADER_SIZE) / BOSS_A_ENTRY_SIZE; + // Print the program ids of the entries + for (u64 i = 0; i < num_entries; i++) { + u64 entry_offset = i * BOSS_A_ENTRY_SIZE + BOSS_SAVE_HEADER_SIZE; + u64 prog_id; + boss_a->Read(entry_offset, sizeof(prog_id), reinterpret_cast(&prog_id)); + LOG_DEBUG(Service_BOSS, "Id in entry {} is {:#018X}", i, prog_id); + } + } + } + + FileSys::Path boss_sv_path("/BOSS_SV.db"); + auto boss_sv_result = boss_system_save_data_archive->OpenFile(boss_sv_path, open_mode); + + FileSys::Path boss_ss_path("/BOSS_SS.db"); + auto boss_ss_result = boss_system_save_data_archive->OpenFile(boss_ss_path, open_mode); + + // Read the files if they already exist + if (boss_sv_result.Succeeded() && boss_ss_result.Succeeded()) { + auto boss_sv = std::move(boss_sv_result).Unwrap(); + auto boss_ss = std::move(boss_ss_result).Unwrap(); + + // Check the file length is valid. Both files should have the same number of entries + if (!(boss_sv->GetSize() > BOSS_SAVE_HEADER_SIZE && + ((boss_sv->GetSize() - BOSS_SAVE_HEADER_SIZE) % BOSS_S_ENTRY_SIZE) == 0 && + boss_sv->GetSize() == boss_ss->GetSize())) { + LOG_WARNING(Service_BOSS, "Boss dbs have incorrect size"); + return; + } else { + u64 num_entries = (boss_sv->GetSize() - BOSS_SAVE_HEADER_SIZE) / BOSS_S_ENTRY_SIZE; + for (u64 i = 0; i < num_entries; i++) { + u64 entry_offset = i * BOSS_S_ENTRY_SIZE + BOSS_SAVE_HEADER_SIZE; + + // Print the program id and task id to debug + u64 prog_id; + boss_sv->Read(entry_offset + BOSS_S_PROG_ID_OFFSET, sizeof(prog_id), + reinterpret_cast(&prog_id)); + LOG_DEBUG(Service_BOSS, "Id sv in entry {} is {:#018X}", i, prog_id); + + std::string task_id(TASK_ID_SIZE, 0); + boss_sv->Read(entry_offset + BOSS_S_TASK_ID_OFFSET, TASK_ID_SIZE, + reinterpret_cast(task_id.data())); + LOG_DEBUG(Service_BOSS, "Task id in entry {} is {}", i, task_id); + + std::vector url(URL_SIZE); + boss_ss->Read(entry_offset + BOSS_S_URL_OFFSET, URL_SIZE, url.data()); + LOG_DEBUG(Service_BOSS, "Url for task {} is {}", task_id, + std::string_view(reinterpret_cast(url.data()), url.size())); + + // If the task is for the current program, store the download url for the session. + // In the future we could store more properties, including from the sv db. + if (prog_id == util.program_id) { + LOG_DEBUG(Service_BOSS, "Storing download url"); + util.cur_props.props[PropertyID::Url] = url; + if (util.task_id_list.contains(task_id)) { + LOG_WARNING(Service_BOSS, "Task id already in list, will be replaced"); + util.task_id_list.erase(task_id); + } + util.task_id_list.emplace(task_id, std::move(util.cur_props)); + util.cur_props = BossTaskProperties(); + } + } + } + } } void Module::Interface::SetStorageInfo(Kernel::HLERequestContext& ctx) { @@ -131,12 +258,22 @@ void Module::Interface::RegisterTask(Kernel::HLERequestContext& ctx) { const u8 unk_param3 = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + std::string task_id(size, 0); + buffer.Read(task_id.data(), 0, size); + if (util.task_id_list.contains(task_id)) { + LOG_WARNING(Service_BOSS, "Task id already in list, will be replaced"); + util.task_id_list.erase(task_id); + } + util.task_id_list.emplace(task_id, std::move(util.cur_props)); + util.cur_props = BossTaskProperties(); + LOG_DEBUG(Service_BOSS, "Read task id {}", task_id); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}, unk_param2={:#04X}, unk_param3={:#04X}", - size, unk_param2, unk_param3); + LOG_DEBUG(Service_BOSS, "called, size={:#010X}, unk_param2={:#04X}, unk_param3={:#04X}", size, + unk_param2, unk_param3); } void Module::Interface::UnregisterTask(Kernel::HLERequestContext& ctx) { @@ -145,11 +282,31 @@ void Module::Interface::UnregisterTask(Kernel::HLERequestContext& ctx) { const u8 unk_param2 = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + ResultCode result = RESULT_FAILED; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(RESULT_SUCCESS); + + if (size > TASK_ID_SIZE) { + LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8"); + rb.Push(result); + rb.PushMappedBuffer(buffer); + return; + } else { + std::string task_id(size, 0); + buffer.Read(task_id.data(), 0, size); + LOG_DEBUG(Service_BOSS, "Read task id {}", task_id); + if (util.task_id_list.erase(task_id) == 0) { + LOG_WARNING(Service_BOSS, "Task Id not in list"); + } else { + LOG_DEBUG(Service_BOSS, "Task Id erased"); + result = RESULT_SUCCESS; + } + } + + rb.Push(result); rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}, unk_param2={:#04X}", size, unk_param2); + LOG_DEBUG(Service_BOSS, "called, size={:#010X}, unk_param2={:#04X}", size, unk_param2); } void Module::Interface::ReconfigureTask(Kernel::HLERequestContext& ctx) { @@ -168,10 +325,37 @@ void Module::Interface::ReconfigureTask(Kernel::HLERequestContext& ctx) { void Module::Interface::GetTaskIdList(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); + const u16 num_task_ids = static_cast(util.task_id_list.size()); + util.cur_props.props[PropertyID::TotalTasks] = num_task_ids; + LOG_DEBUG(Service_BOSS, "Prepared total_tasks = {}", num_task_ids); + + u16 num_returned_task_ids = 0; + std::vector> task_ids(TASKIDLIST_SIZE / TASK_ID_SIZE); + + for (const auto& iter : util.task_id_list) { + const std::string_view cur_task_id = iter.first; + if (cur_task_id.size() > TASK_ID_SIZE || + num_returned_task_ids >= TASKIDLIST_SIZE / TASK_ID_SIZE) { + LOG_WARNING(Service_BOSS, "task id {} too long or too many task ids", cur_task_id); + } else { + std::memcpy(task_ids[num_returned_task_ids].data(), cur_task_id.data(), TASK_ID_SIZE); + num_returned_task_ids++; + LOG_TRACE(Service_BOSS, "wrote task id {}", cur_task_id); + } + } + + const auto task_list_prop = + std::get_if>(&util.cur_props.props[PropertyID::TaskIdList]); + if (task_list_prop && task_list_prop->size() == TASKIDLIST_SIZE) { + + std::memcpy(task_list_prop->data(), task_ids.data(), TASKIDLIST_SIZE); + LOG_DEBUG(Service_BOSS, "wrote out {} task ids", num_returned_task_ids); + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_BOSS, "(STUBBED) called"); + LOG_DEBUG(Service_BOSS, "called"); } void Module::Interface::GetStepIdList(Kernel::HLERequestContext& ctx) { @@ -194,16 +378,18 @@ void Module::Interface::GetNsDataIdList(Kernel::HLERequestContext& ctx) { const u32 start_ns_data_id = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + const u16 entries_count = util.GetOutputEntries(filter, max_entries, buffer); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); rb.Push(RESULT_SUCCESS); - rb.Push(0); /// Actual number of output entries - rb.Push(0); /// Last word-index copied to output in the internal NsDataId list. + rb.Push(entries_count); /// Actual number of output entries + rb.Push(0); /// Last word-index copied to output in the internal NsDataId list. rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, - "(STUBBED) filter={:#010X}, max_entries={:#010X}, " - "word_index_start={:#06X}, start_ns_data_id={:#010X}", - filter, max_entries, word_index_start, start_ns_data_id); + LOG_DEBUG(Service_BOSS, + "filter={:#010X}, max_entries={:#010X}, " + "word_index_start={:#06X}, start_ns_data_id={:#010X}", + filter, max_entries, word_index_start, start_ns_data_id); } void Module::Interface::GetNsDataIdList1(Kernel::HLERequestContext& ctx) { @@ -214,16 +400,18 @@ void Module::Interface::GetNsDataIdList1(Kernel::HLERequestContext& ctx) { const u32 start_ns_data_id = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + const u16 entries_count = util.GetOutputEntries(filter, max_entries, buffer); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); rb.Push(RESULT_SUCCESS); - rb.Push(0); /// Actual number of output entries - rb.Push(0); /// Last word-index copied to output in the internal NsDataId list. + rb.Push(entries_count); /// Actual number of output entries + rb.Push(0); /// Last word-index copied to output in the internal NsDataId list. rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, - "(STUBBED) filter={:#010X}, max_entries={:#010X}, " - "word_index_start={:#06X}, start_ns_data_id={:#010X}", - filter, max_entries, word_index_start, start_ns_data_id); + LOG_DEBUG(Service_BOSS, + "filter={:#010X}, max_entries={:#010X}, " + "word_index_start={:#06X}, start_ns_data_id={:#010X}", + filter, max_entries, word_index_start, start_ns_data_id); } void Module::Interface::GetNsDataIdList2(Kernel::HLERequestContext& ctx) { @@ -234,16 +422,18 @@ void Module::Interface::GetNsDataIdList2(Kernel::HLERequestContext& ctx) { const u32 start_ns_data_id = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + const u16 entries_count = util.GetOutputEntries(filter, max_entries, buffer); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); rb.Push(RESULT_SUCCESS); - rb.Push(0); /// Actual number of output entries - rb.Push(0); /// Last word-index copied to output in the internal NsDataId list. + rb.Push(entries_count); /// Actual number of output entries + rb.Push(0); /// Last word-index copied to output in the internal NsDataId list. rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, - "(STUBBED) filter={:#010X}, max_entries={:#010X}, " - "word_index_start={:#06X}, start_ns_data_id={:#010X}", - filter, max_entries, word_index_start, start_ns_data_id); + LOG_DEBUG(Service_BOSS, + "filter={:#010X}, max_entries={:#010X}, " + "word_index_start={:#06X}, start_ns_data_id={:#010X}", + filter, max_entries, word_index_start, start_ns_data_id); } void Module::Interface::GetNsDataIdList3(Kernel::HLERequestContext& ctx) { @@ -254,29 +444,38 @@ void Module::Interface::GetNsDataIdList3(Kernel::HLERequestContext& ctx) { const u32 start_ns_data_id = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + const u16 entries_count = util.GetOutputEntries(filter, max_entries, buffer); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); rb.Push(RESULT_SUCCESS); - rb.Push(0); /// Actual number of output entries - rb.Push(0); /// Last word-index copied to output in the internal NsDataId list. + rb.Push(entries_count); /// Actual number of output entries + rb.Push(0); /// Last word-index copied to output in the internal NsDataId list. rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, - "(STUBBED) filter={:#010X}, max_entries={:#010X}, " - "word_index_start={:#06X}, start_ns_data_id={:#010X}", - filter, max_entries, word_index_start, start_ns_data_id); + LOG_DEBUG(Service_BOSS, + "filter={:#010X}, max_entries={:#010X}, " + "word_index_start={:#06X}, start_ns_data_id={:#010X}", + filter, max_entries, word_index_start, start_ns_data_id); } void Module::Interface::SendProperty(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const u16 property_id = rp.Pop(); + const PropertyID property_id = static_cast(rp.Pop()); const u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(RESULT_SUCCESS); - rb.PushMappedBuffer(buffer); + LOG_DEBUG(Service_BOSS, "called, property_id={:#06X}, size={:#010X}", property_id, size); - LOG_WARNING(Service_BOSS, "(STUBBED) property_id={:#06X}, size={:#010X}", property_id, size); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + + ResultCode result = RESULT_FAILED; + + if (util.ReadWriteProperties(false, property_id, size, buffer)) { + result = RESULT_SUCCESS; + } + + rb.Push(result); + rb.PushMappedBuffer(buffer); } void Module::Interface::SendPropertyHandle(Kernel::HLERequestContext& ctx) { @@ -292,16 +491,22 @@ void Module::Interface::SendPropertyHandle(Kernel::HLERequestContext& ctx) { void Module::Interface::ReceiveProperty(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const u16 property_id = rp.Pop(); + const PropertyID property_id = static_cast(rp.Pop()); const u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + LOG_DEBUG(Service_BOSS, "called, property_id={:#06X}, size={:#010X}", property_id, size); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - rb.Push(RESULT_SUCCESS); - rb.Push(size); /// Should be actual read size - rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, "(STUBBED) property_id={:#06X}, size={:#010X}", property_id, size); + ResultCode result = RESULT_FAILED; + + if (util.ReadWriteProperties(true, property_id, size, buffer)) { + result = RESULT_SUCCESS; + } + + rb.Push(result); + rb.Push(size); // The size of the property per id, not how much data + rb.PushMappedBuffer(buffer); } void Module::Interface::UpdateTaskInterval(Kernel::HLERequestContext& ctx) { @@ -323,6 +528,14 @@ void Module::Interface::UpdateTaskCount(Kernel::HLERequestContext& ctx) { const u32 unk_param2 = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + if (size > TASK_ID_SIZE) { + LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8"); + } else { + std::string task_id(size, 0); + buffer.Read(task_id.data(), 0, size); + LOG_DEBUG(Service_BOSS, "Read task id {}", task_id); + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); rb.PushMappedBuffer(buffer); @@ -348,6 +561,14 @@ void Module::Interface::GetTaskCount(Kernel::HLERequestContext& ctx) { const u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + if (size > TASK_ID_SIZE) { + LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8"); + } else { + std::string task_id(size, 0); + buffer.Read(task_id.data(), 0, size); + LOG_DEBUG(Service_BOSS, "Read task id {}", task_id); + } + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(RESULT_SUCCESS); rb.Push(0); // stub 0 ( 32bit value) @@ -361,9 +582,13 @@ void Module::Interface::GetTaskServiceStatus(Kernel::HLERequestContext& ctx) { const u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + // Not sure what this is but it's not the task status. Maybe it's the status of the service + // after running the task? + u8 task_service_status = 1; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(RESULT_SUCCESS); - rb.Push(0); // stub 0 ( 8bit value) + rb.Push(task_service_status); // stub 1 ( 8bit value) this is not taskstatus rb.PushMappedBuffer(buffer); LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}", size); @@ -374,23 +599,46 @@ void Module::Interface::StartTask(Kernel::HLERequestContext& ctx) { const u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + if (size > TASK_ID_SIZE) { + LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8"); + } else { + std::string task_id(size, 0); + buffer.Read(task_id.data(), 0, size); + if (!util.task_id_list.contains(task_id)) { + LOG_WARNING(Service_BOSS, "Task Id {} not found", task_id); + } else { + util.task_id_list[task_id].times_checked = 0; + if (const auto* url_prop = util.task_id_list[task_id].props.contains(PropertyID::Url) + ? std::get_if>( + &util.task_id_list[task_id].props[PropertyID::Url]) + : nullptr; + url_prop && url_prop->size() == URL_SIZE) { + const char* url_pointer = reinterpret_cast(url_prop->data()); + std::string_view url(url_pointer, strnlen(url_pointer, URL_SIZE)); + std::string_view file_name(util.task_id_list.find(task_id)->first.c_str(), + strnlen(task_id.c_str(), TASK_ID_SIZE)); + util.task_id_list[task_id].download_task = + std::async(std::launch::async, DownloadBossDataFromURL, url, file_name, + util.program_id, util.extdata_id); + } else { + LOG_ERROR(Service_BOSS, "URL property is invalid"); + } + } + LOG_DEBUG(Service_BOSS, "Read task id {}", task_id); + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}", size); + LOG_DEBUG(Service_BOSS, "size={:#010X}", size); } void Module::Interface::StartTaskImmediate(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - const u32 size = rp.Pop(); - auto& buffer = rp.PopMappedBuffer(); - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(RESULT_SUCCESS); - rb.PushMappedBuffer(buffer); - - LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}", size); + LOG_WARNING(Service_BOSS, "StartTaskImmediate called"); + // StartTask and StartTaskImmediate do much the same thing + StartTask(ctx); + LOG_DEBUG(Service_BOSS, "called"); } void Module::Interface::CancelTask(Kernel::HLERequestContext& ctx) { @@ -398,6 +646,14 @@ void Module::Interface::CancelTask(Kernel::HLERequestContext& ctx) { const u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + if (size > TASK_ID_SIZE) { + LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8"); + } else { + std::string task_id(size, 0); + buffer.Read(task_id.data(), 0, size); + LOG_DEBUG(Service_BOSS, "Read task id {}", task_id); + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); rb.PushMappedBuffer(buffer); @@ -418,17 +674,30 @@ void Module::Interface::GetTaskFinishHandle(Kernel::HLERequestContext& ctx) { void Module::Interface::GetTaskState(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); const u32 size = rp.Pop(); - const u8 state = rp.Pop(); + const s8 state = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + TaskStatus task_status = TaskStatus::Failed; + u32 duration; + + if (size > TASK_ID_SIZE) { + LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8"); + duration = 0; + } else { + std::string task_id(size, 0); + buffer.Read(task_id.data(), 0, size); + LOG_DEBUG(Service_BOSS, "Read task id {}", task_id); + std::tie(task_status, duration) = util.GetTaskStatusAndDuration(task_id, false); + } + IPC::RequestBuilder rb = rp.MakeBuilder(4, 2); rb.Push(RESULT_SUCCESS); - rb.Push(0); /// TaskStatus - rb.Push(0); /// Current state value for task PropertyID 0x4 - rb.Push(0); /// unknown, usually 0 + rb.Push(static_cast(task_status)); /// TaskStatus + rb.Push(duration); /// Current state value for task PropertyID 0x4 + rb.Push(0); /// unknown, usually 0 rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}, state={:#06X}", size, state); + LOG_DEBUG(Service_BOSS, "size={:#010X}, state={:#06X}", size, state); } void Module::Interface::GetTaskResult(Kernel::HLERequestContext& ctx) { @@ -436,14 +705,30 @@ void Module::Interface::GetTaskResult(Kernel::HLERequestContext& ctx) { const u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + TaskStatus task_status = TaskStatus::Failed; + u32 duration; + + if (size > TASK_ID_SIZE) { + LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8"); + duration = 0; + } else { + std::string task_id(size, 0); + buffer.Read(task_id.data(), 0, size); + LOG_DEBUG(Service_BOSS, "Read task id {}", task_id); + std::tie(task_status, duration) = util.GetTaskStatusAndDuration(task_id, true); + } + IPC::RequestBuilder rb = rp.MakeBuilder(4, 2); rb.Push(RESULT_SUCCESS); - rb.Push(0); // stub 0 (8 bit value) - rb.Push(0); // stub 0 (32 bit value) - rb.Push(0); // stub 0 (8 bit value) + // This might be task_status; however it is considered a failure if + // anything other than 0 is returned, apps won't call this method + // unless they have previously determined the task has ended + rb.Push(static_cast(task_status)); + rb.Push(duration); // return duration (number of times to check) + rb.Push(0); // stub 0 (8 bit value) rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}", size); + LOG_DEBUG(Service_BOSS, "size={:#010X}", size); } void Module::Interface::GetTaskCommErrorCode(Kernel::HLERequestContext& ctx) { @@ -451,6 +736,17 @@ void Module::Interface::GetTaskCommErrorCode(Kernel::HLERequestContext& ctx) { const u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + if (size > TASK_ID_SIZE) { + LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8"); + } else { + std::string task_id(size, 0); + buffer.Read(task_id.data(), 0, size); + LOG_DEBUG(Service_BOSS, "Read task id {}", task_id); + if (!util.task_id_list.contains(task_id)) { + LOG_WARNING(Service_BOSS, "Could not find task_id in list"); + } + } + IPC::RequestBuilder rb = rp.MakeBuilder(4, 2); rb.Push(RESULT_SUCCESS); rb.Push(0); // stub 0 (32 bit value) @@ -468,13 +764,25 @@ void Module::Interface::GetTaskStatus(Kernel::HLERequestContext& ctx) { const u8 unk_param3 = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + TaskStatus task_status; + + if (size > TASK_ID_SIZE) { + LOG_WARNING(Service_BOSS, "Task Id cannot be longer than 8"); + task_status = TaskStatus::Failed; + } else { + std::string task_id(size, 0); + buffer.Read(task_id.data(), 0, size); + LOG_DEBUG(Service_BOSS, "Read task id {}", task_id); + task_status = util.GetTaskStatusAndDuration(task_id, false).first; + } + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); rb.Push(RESULT_SUCCESS); - rb.Push(0); // stub 0 (8 bit value) + rb.Push(static_cast(task_status)); // return current task status rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}, unk_param2={:#04X}, unk_param3={:#04X}", - size, unk_param2, unk_param3); + LOG_DEBUG(Service_BOSS, "size={:#010X}, unk_param2={:#04X}, unk_param3={:#04X}", size, + unk_param2, unk_param3); } void Module::Interface::GetTaskError(Kernel::HLERequestContext& ctx) { @@ -515,18 +823,105 @@ void Module::Interface::DeleteNsData(Kernel::HLERequestContext& ctx) { } void Module::Interface::GetNsDataHeaderInfo(Kernel::HLERequestContext& ctx) { + NsDataHeaderInfo info{}; + IPC::RequestParser rp(ctx); - const u32 ns_data_id = rp.Pop(); - const u8 type = rp.Pop(); + info.ns_data_id = rp.Pop(); + const NsDataHeaderInfoType type = static_cast(rp.Pop()); const u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + ResultCode result = RESULT_FAILED; + u32 zero = 0; + std::optional entry = util.GetNsDataEntryFromID(info.ns_data_id); + if (entry.has_value()) { + info.program_id = entry->header.program_id; + info.datatype = entry->header.datatype; + info.payload_size = entry->header.payload_size; + info.version = entry->header.version; + + switch (type) { + case NsDataHeaderInfoType::ProgramId: + if (size != sizeof(info.program_id)) { + LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type); + break; + } + buffer.Write(&info.program_id, 0, size); + result = RESULT_SUCCESS; + LOG_DEBUG(Service_BOSS, "Wrote out program id {}", info.program_id); + break; + case NsDataHeaderInfoType::Unknown: + if (size != sizeof(u32)) { + LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type); + break; + } + buffer.Write(&zero, 0, size); + result = RESULT_SUCCESS; + LOG_DEBUG(Service_BOSS, "Wrote out unknown as zero"); + break; + case NsDataHeaderInfoType::Datatype: + if (size != sizeof(info.datatype)) { + LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type); + break; + } + buffer.Write(&info.datatype, 0, size); + result = RESULT_SUCCESS; + LOG_DEBUG(Service_BOSS, "Wrote out content datatype {}", info.datatype); + break; + case NsDataHeaderInfoType::PayloadSize: + if (size != sizeof(info.payload_size)) { + LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type); + break; + } + buffer.Write(&info.payload_size, 0, size); + result = RESULT_SUCCESS; + LOG_DEBUG(Service_BOSS, "Wrote out payload size {}", info.payload_size); + break; + case NsDataHeaderInfoType::NsDataId: + if (size != sizeof(info.ns_data_id)) { + LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type); + break; + } + buffer.Write(&info.ns_data_id, 0, size); + result = RESULT_SUCCESS; + LOG_DEBUG(Service_BOSS, "Wrote out NsDataID {}", info.ns_data_id); + break; + case NsDataHeaderInfoType::Version: + if (size != sizeof(info.version)) { + LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type); + break; + } + buffer.Write(&info.version, 0, size); + result = RESULT_SUCCESS; + LOG_DEBUG(Service_BOSS, "Wrote out version {}", info.version); + break; + case NsDataHeaderInfoType::Everything: + if (size != sizeof(info)) { + LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type); + break; + } + buffer.Write(&info, 0, size); + result = RESULT_SUCCESS; + LOG_DEBUG(Service_BOSS, + "Wrote out unknown with program id {:#018X}, unknown zero, " + "datatype {:#010X}, " + "payload size {:#010X}, NsDataID {:#010X}, version " + "{:#010X} and unknown zero", + info.program_id, info.datatype, info.payload_size, info.ns_data_id, + info.version); + break; + default: + LOG_WARNING(Service_BOSS, "Unknown header info type {}", type); + result = RESULT_FAILED; + } + } + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(RESULT_SUCCESS); + rb.Push(result); rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010X}, type={:#04X}, size={:#010X}", - ns_data_id, type, size); + LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}, type={:#04X}, size={:#010X}", info.ns_data_id, + type, size); } void Module::Interface::ReadNsData(Kernel::HLERequestContext& ctx) { @@ -536,14 +931,73 @@ void Module::Interface::ReadNsData(Kernel::HLERequestContext& ctx) { const u32 size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + // This is the error code for NsDataID not found + ResultCode result = RESULT_FAILED; + u32 read_size = 0; + FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory( + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), false, true); + const FileSys::Path boss_path{GetBossDataDir(util.extdata_id)}; + auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); - rb.Push(RESULT_SUCCESS); - rb.Push(size); /// Should be actual read size - rb.Push(0); /// unknown + + std::optional entry = util.GetNsDataEntryFromID(ns_data_id); + if (!archive_result.Succeeded() || !entry.has_value()) { + LOG_WARNING(Service_BOSS, "Opening Spotpass Extdata failed."); + rb.Push(result); + rb.Push(read_size); + rb.Push(0); + rb.PushMappedBuffer(buffer); + LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}, offset={:#018X}, size={:#010X}", ns_data_id, + offset, size); + return; + } + LOG_DEBUG(Service_BOSS, "Spotpass Extdata opened successfully!"); + auto boss_archive = std::move(archive_result).Unwrap().get(); + FileSys::Path file_path = fmt::format("/{}", entry->filename); + FileSys::Mode mode{}; + mode.read_flag.Assign(1); + auto file_result = boss_archive->OpenFile(file_path, mode); + + if (!file_result.Succeeded()) { + LOG_WARNING(Service_BOSS, "Opening Spotpass file failed."); + rb.Push(result); + rb.Push(read_size); + rb.Push(0); + rb.PushMappedBuffer(buffer); + LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}, offset={:#018X}, size={:#010X}", ns_data_id, + offset, size); + return; + } + auto file = std::move(file_result).Unwrap(); + LOG_DEBUG(Service_BOSS, "Opening Spotpass file succeeded!"); + if (entry->header.payload_size < size + offset) { + LOG_WARNING(Service_BOSS, + "Request to read {:#010X} bytes at offset {:#010X}, payload " + "length is {:#010X}", + size, offset, u32(entry->header.payload_size)); + rb.Push(result); + rb.Push(read_size); + rb.Push(0); + rb.PushMappedBuffer(buffer); + LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}, offset={:#018X}, size={:#010X}", ns_data_id, + offset, size); + return; + } + std::vector ns_data_array(size); + file->Read(BOSS_HEADER_LENGTH + offset, size, ns_data_array.data()); + buffer.Write(ns_data_array.data(), 0, size); + result = RESULT_SUCCESS; + read_size = size; + LOG_DEBUG(Service_BOSS, "Read {:#010X} bytes from file {}", read_size, entry->filename); + + rb.Push(result); + rb.Push(read_size); /// Should be actual read size + rb.Push(0); /// unknown rb.PushMappedBuffer(buffer); - LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010X}, offset={:#018X}, size={:#010X}", - ns_data_id, offset, size); + LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}, offset={:#018X}, size={:#010X}", ns_data_id, + offset, size); } void Module::Interface::SetNsDataAdditionalInfo(Kernel::HLERequestContext& ctx) { @@ -571,38 +1025,46 @@ void Module::Interface::GetNsDataAdditionalInfo(Kernel::HLERequestContext& ctx) void Module::Interface::SetNsDataNewFlag(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const u32 unk_param1 = rp.Pop(); + const u32 ns_data_id = rp.Pop(); ns_data_new_flag = rp.Pop(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_BOSS, "(STUBBED) unk_param1={:#010X}, ns_data_new_flag={:#04X}", unk_param1, + LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010X}, ns_data_new_flag={:#04X}", ns_data_id, ns_data_new_flag); } void Module::Interface::GetNsDataNewFlag(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const u32 unk_param1 = rp.Pop(); + const u32 ns_data_id = rp.Pop(); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); rb.Push(ns_data_new_flag); - LOG_WARNING(Service_BOSS, "(STUBBED) unk_param1={:#010X}, ns_data_new_flag={:#04X}", unk_param1, + LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010X}, ns_data_new_flag={:#04X}", ns_data_id, ns_data_new_flag); } void Module::Interface::GetNsDataLastUpdate(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const u32 unk_param1 = rp.Pop(); + const u32 ns_data_id = rp.Pop(); + + u32 last_update = 0; + + std::optional entry = util.GetNsDataEntryFromID(ns_data_id); + if (entry.has_value()) { + last_update = entry->header.download_date; + LOG_DEBUG(Service_BOSS, "Last update: {}", last_update); + } IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); rb.Push(RESULT_SUCCESS); - rb.Push(0); // stub 0 (32bit value) - rb.Push(0); // stub 0 (32bit value) + rb.Push(0); + rb.Push(last_update); // return the download date from the ns data - LOG_WARNING(Service_BOSS, "(STUBBED) unk_param1={:#010X}", unk_param1); + LOG_DEBUG(Service_BOSS, "ns_data_id={:#010X}", ns_data_id); } void Module::Interface::GetErrorCode(Kernel::HLERequestContext& ctx) { @@ -699,18 +1161,8 @@ void Module::Interface::GetTaskProperty0(Kernel::HLERequestContext& ctx) { } void Module::Interface::RegisterImmediateTask(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - const u32 size = rp.Pop(); - const u8 unk_param2 = rp.Pop(); - const u8 unk_param3 = rp.Pop(); - auto& buffer = rp.PopMappedBuffer(); - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(RESULT_SUCCESS); - rb.PushMappedBuffer(buffer); - - LOG_WARNING(Service_BOSS, "(STUBBED) size={:#010X}, unk_param2={:#04X}, unk_param3={:#04X}", - size, unk_param2, unk_param3); + RegisterTask(ctx); + LOG_DEBUG(Service_BOSS, "called"); } void Module::Interface::SetTaskQuery(Kernel::HLERequestContext& ctx) { @@ -879,10 +1331,10 @@ void Module::Interface::SetNsDataNewFlagPrivileged(Kernel::HLERequestContext& ct IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING( - Service_BOSS, - "(STUBBED) programID={:#018X}, unk_param1={:#010X}, ns_data_new_flag_privileged={:#04X}", - programID, unk_param1, ns_data_new_flag_privileged); + LOG_WARNING(Service_BOSS, + "(STUBBED) programID={:#018X}, unk_param1={:#010X}, " + "ns_data_new_flag_privileged={:#04X}", + programID, unk_param1, ns_data_new_flag_privileged); } void Module::Interface::GetNsDataNewFlagPrivileged(Kernel::HLERequestContext& ctx) { @@ -894,16 +1346,20 @@ void Module::Interface::GetNsDataNewFlagPrivileged(Kernel::HLERequestContext& ct rb.Push(RESULT_SUCCESS); rb.Push(ns_data_new_flag_privileged); - LOG_WARNING( - Service_BOSS, - "(STUBBED) programID={:#018X}, unk_param1={:#010X}, ns_data_new_flag_privileged={:#04X}", - programID, unk_param1, ns_data_new_flag_privileged); + LOG_WARNING(Service_BOSS, + "(STUBBED) programID={:#018X}, unk_param1={:#010X}, " + "ns_data_new_flag_privileged={:#04X}", + programID, unk_param1, ns_data_new_flag_privileged); } Module::Interface::Interface(std::shared_ptr boss, const char* name, u32 max_session) - : ServiceFramework(name, max_session), boss(std::move(boss)) {} + : ServiceFramework(name, max_session), boss(std::move(boss)) { + util = Util(); + this->boss->loader.ReadProgramId(util.program_id); + this->boss->loader.ReadExtdataId(util.extdata_id); +} -Module::Module(Core::System& system) { +Module::Module(Core::System& system) : loader(system.GetAppLoader()) { using namespace Kernel; // TODO: verify ResetType task_finish_event = diff --git a/src/core/hle/service/boss/boss.h b/src/core/hle/service/boss/boss.h index e4000e851..0fdf89ac4 100644 --- a/src/core/hle/service/boss/boss.h +++ b/src/core/hle/service/boss/boss.h @@ -4,10 +4,16 @@ #pragma once +#include #include +#include #include +#include +#include "core/file_sys/archive_backend.h" +#include "core/file_sys/directory_backend.h" #include "core/global.h" #include "core/hle/kernel/event.h" +#include "core/hle/service/boss/spotpass_utils.h" #include "core/hle/service/service.h" namespace Core { @@ -15,10 +21,10 @@ class System; } namespace Service::BOSS { - class Module final { public: explicit Module(Core::System& system); + Loader::AppLoader& loader; ~Module() = default; class Interface : public ServiceFramework { @@ -963,6 +969,8 @@ public: u8 ns_data_new_flag_privileged; u8 output_flag; + Util util; + template void serialize(Archive& ar, const unsigned int) { ar& new_arrival_flag; diff --git a/src/core/hle/service/boss/spotpass_utils.cpp b/src/core/hle/service/boss/spotpass_utils.cpp new file mode 100644 index 000000000..5f86dd44a --- /dev/null +++ b/src/core/hle/service/boss/spotpass_utils.cpp @@ -0,0 +1,556 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef ENABLE_WEB_SERVICE +#if defined(__ANDROID__) +#include +#endif +#include +#ifdef WIN32 +// Needed to prevent conflicts with system macros when httplib is included on windows +#undef CreateEvent +#undef CreateFile +#undef ERROR_NOT_FOUND +#undef ERROR_FILE_NOT_FOUND +#undef ERROR_PATH_NOT_FOUND +#undef ERROR_ALREADY_EXISTS +#endif +#endif +#include +#include +#include +#include +#include +#include "common/string_util.h" +#include "core/core.h" +#include "core/file_sys/archive_extsavedata.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/file_backend.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/boss/boss.h" +#include "core/hw/aes/key.h" + +namespace Service::BOSS { + +FileSys::Path GetBossDataDir(u64 extdata_id) { + + const u32 high = static_cast(extdata_id >> 32); + const u32 low = static_cast(extdata_id & 0xFFFFFFFF); + + return FileSys::ConstructExtDataBinaryPath(1, high, low); +} + +bool WriteBossFile(u64 extdata_id, FileSys::Path file_path, u64 size, const u8* buffer, + std::string_view description) { + FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory( + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), false, true); + + const FileSys::Path boss_path{GetBossDataDir(extdata_id)}; + + auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0); + if (!archive_result.Succeeded()) { + LOG_WARNING(Service_BOSS, "Extdata opening failed"); + return false; + } + LOG_DEBUG(Service_BOSS, "Spotpass Extdata opened successfully!"); + auto boss_archive = std::move(archive_result).Unwrap().get(); + + auto file_create_result = boss_archive->CreateFile(file_path, size); + if (file_create_result.is_error) { + LOG_WARNING(Service_BOSS, "{} could not be created, it may already exist", description); + } + FileSys::Mode file_open_mode = {}; + file_open_mode.write_flag.Assign(1); + auto file_result = boss_archive->OpenFile(file_path, file_open_mode); + if (!file_result.Succeeded()) { + LOG_WARNING(Service_BOSS, "Could not open {} for writing", description); + return false; + } + auto file = std::move(file_result).Unwrap(); + file->Write(0, size, true, buffer); + file->Close(); + return true; +} + +bool SendNewsMessage(std::vector decrypted_data, const u32 payload_size, u64 extdata_id, + std::string_view file_name_str) { + // TODO: Actually add a notification to the news service + // Looks like it has some sort of header(only 0x60 bytes, datetime and unknown at 0x20 + // missing?), https://www.3dbrew.org/wiki/NEWSS:AddNotification#Header_structure , then + // the message, then the image + LOG_INFO(Service_BOSS, "News message was received"); + constexpr u32 news_header_size = 0x60; + constexpr u32 news_title_offset = 0x20; + constexpr u32 news_title_size = news_header_size - news_title_offset; + constexpr u32 news_message_size = 0x1780; + constexpr u32 news_total_size = news_header_size + news_message_size; + + struct NewsMessage { + INSERT_PADDING_BYTES(news_title_offset); + std::array news_title; + std::array news_message; + }; + static_assert(sizeof(NewsMessage) == news_total_size, "NewsMessage has incorrect size"); + + if (payload_size < news_total_size) { + LOG_ERROR(Service_BOSS, "Payload is too short to contain news message"); + return false; + } + + NewsMessage* message = + reinterpret_cast(decrypted_data.data() + BOSS_ENTIRE_HEADER_LENGTH); + + std::u16string_view news_title_string(reinterpret_cast(message->news_title.data()), + news_title_size / 2); + std::u16string_view news_message_string( + reinterpret_cast(message->news_message.data()), news_message_size / 2); + + LOG_INFO(Service_BOSS, "News title is: {}", Common::UTF16ToUTF8(news_title_string)); + LOG_INFO(Service_BOSS, "News message is:\n{}", Common::UTF16ToUTF8(news_message_string)); + + if (payload_size > news_total_size) { + LOG_INFO(Service_BOSS, "Image is present in news, dumping..."); + if (!WriteBossFile(extdata_id, fmt::format("{}_news_image.jpg", file_name_str), + payload_size - news_total_size, + decrypted_data.data() + BOSS_ENTIRE_HEADER_LENGTH + news_total_size, + "image file")) { + LOG_WARNING(Service_BOSS, "Could not write image file"); + } + } + return true; +} + +bool DecryptBossData(const BossPayloadHeader* payload_header, const u32 data_size, + const u8* encrypted_data, u8* decrypted_data) { + // AES details here: https://www.3dbrew.org/wiki/SpotPass#Content_Container + // IV is data in payload + 32 bit Big Endian 1 + const u32_be one = 1; + std::vector iv(sizeof(payload_header->iv_start) + sizeof(one)); + std::memcpy(iv.data(), payload_header->iv_start.data(), sizeof(payload_header->iv_start)); + std::memcpy(iv.data() + sizeof(payload_header->iv_start), &one, sizeof(one)); + LOG_DEBUG(Service_BOSS, "IV is {:#018X}{:16X}", + static_cast(*reinterpret_cast(iv.data())), + static_cast(*reinterpret_cast(iv.data() + sizeof(u64_be)))); + + CryptoPP::CTR_Mode::Decryption aes; + HW::AES::AESKey key = HW::AES::GetNormalKey(0x38); + if (key == HW::AES::AESKey{}) { + LOG_WARNING(Service_BOSS, "AES Key 0x38 not found"); + return false; + } + + aes.SetKeyWithIV(key.data(), CryptoPP::AES::BLOCKSIZE, iv.data()); + aes.ProcessData(decrypted_data, encrypted_data, data_size); + return true; +} + +bool DownloadBossDataFromURL(std::string_view url, std::string_view file_name, u64 program_id, + u64 extdata_id) { +#ifdef ENABLE_WEB_SERVICE + const auto url_parse_result = boost::urls::parse_uri(url); + const std::string file_name_str = fmt::format("/{}", file_name); + if (url_parse_result.has_error()) { + LOG_ERROR(Service_BOSS, "Invalid URL {}", url); + return false; + } + const auto url_parsed = url_parse_result.value(); + const std::string scheme = url_parsed.scheme(); + const std::string host = url_parsed.host(); + const std::string path = url_parsed.path(); + LOG_DEBUG(Service_BOSS, "Scheme is {}, host is {}, path is {}", scheme, host, path); + const std::unique_ptr client = + std::make_unique(fmt::format("{}://{}", scheme, host)); + httplib::Request request{ + .method = "GET", + .path = path, + // Needed when httplib is included on android + .matches = httplib::Match(), + }; + client->set_follow_location(true); + client->enable_server_certificate_verification(false); + + const auto result = client->send(request); + if (!result) { + LOG_ERROR(Service_BOSS, "GET to {}://{}{} returned error {}", scheme, host, path, + httplib::to_string(result.error())); + return false; + } + const auto& response = result.value(); + if (response.status >= 400) { + LOG_ERROR(Service_BOSS, "GET to {}://{}{} returned error status code: {}", scheme, host, + path, response.status); + return false; + } + if (!response.headers.contains("content-type")) { + LOG_ERROR(Service_BOSS, "GET to {}://{}{} returned no content", scheme, host, path); + return false; + } + + if (response.body.size() < BOSS_PAYLOAD_HEADER_LENGTH) { + LOG_WARNING(Service_BOSS, "Payload size of {} too short for boss payload", + response.body.size()); + return false; + } + const BossPayloadHeader* payload_header = + reinterpret_cast(response.body.data()); + + if (BOSS_MAGIC != payload_header->boss) { + LOG_WARNING(Service_BOSS, "Start of file is not '{}', it's '{}'", BOSS_MAGIC, + static_cast(payload_header->boss)); + return false; + } + + if (payload_header->magic != BOSS_PAYLOAD_MAGIC) { + LOG_WARNING(Service_BOSS, "Magic number mismatch, expecting {}, found {}", + BOSS_PAYLOAD_MAGIC, static_cast(payload_header->magic)); + return false; + } + + if (payload_header->filesize != response.body.size()) { + LOG_WARNING(Service_BOSS, "Expecting response to be size {}, actual size is {}", + static_cast(payload_header->filesize), response.body.size()); + return false; + } + + // Temporarily also write payload (maybe for re-implementing spotpass when it goes down in the + // future?) + + if (!WriteBossFile(extdata_id, fmt::format("{}_payload", file_name_str), response.body.size(), + reinterpret_cast(response.body.data()), "payload file")) { + LOG_WARNING(Service_BOSS, "Could not write payload file"); + } + + // end payload block + const u32 data_size = payload_header->filesize - BOSS_PAYLOAD_HEADER_LENGTH; + + std::vector decrypted_data(data_size); + + if (!DecryptBossData( + payload_header, data_size, + reinterpret_cast(response.body.data() + BOSS_PAYLOAD_HEADER_LENGTH), + decrypted_data.data())) { + LOG_ERROR(Service_BOSS, "Could not decrypt payload"); + return false; + } + + // Temporarily also write raw data + + if (!WriteBossFile(extdata_id, fmt::format("{}_raw_data", file_name_str), decrypted_data.size(), + decrypted_data.data(), "raw data file")) { + LOG_WARNING(Service_BOSS, "Could not write raw data file"); + } + + // end raw data block + + if (decrypted_data.size() < BOSS_ENTIRE_HEADER_LENGTH) { + LOG_WARNING(Service_BOSS, "Payload size to small to be boss data: {}", + decrypted_data.size()); + return false; + } + + BossHeader header{}; + std::memcpy(&header.program_id, decrypted_data.data() + BOSS_CONTENT_HEADER_LENGTH, + BOSS_HEADER_LENGTH - BOSS_EXTDATA_HEADER_LENGTH); + + const u32 payload_size = static_cast(decrypted_data.size() - BOSS_ENTIRE_HEADER_LENGTH); + if (header.payload_size != payload_size) { + LOG_WARNING(Service_BOSS, "Payload has incorrect size, was expecting {}, found {}", + static_cast(header.payload_size), payload_size); + return false; + } + + if (program_id != header.program_id) { + LOG_WARNING(Service_BOSS, "Mismatched program id, was expecting {:#018X}, found {:#018X}", + program_id, u64(header.program_id)); + if (header.program_id == NEWS_PROG_ID) { + SendNewsMessage(decrypted_data, payload_size, extdata_id, file_name_str); + } + return false; + } + + std::vector data_file(BOSS_HEADER_LENGTH + payload_size); + header.header_length = BOSS_EXTDATA_HEADER_LENGTH; + std::memcpy(data_file.data(), &header, BOSS_HEADER_LENGTH); + std::memcpy(data_file.data() + BOSS_HEADER_LENGTH, + decrypted_data.data() + BOSS_ENTIRE_HEADER_LENGTH, payload_size); + + if (!WriteBossFile(extdata_id, file_name_str, data_file.size(), data_file.data(), + "spotpass file")) { + LOG_WARNING(Service_BOSS, "Could not write spotpass file"); + } + return true; +#else + LOG_ERROR(Service_BOSS, "Cannot download data as web services are not enabled"); + return false; +#endif +} + +std::vector Util::GetNsDataEntries() { + std::vector ns_data; + std::vector boss_files = GetBossExtDataFiles(); + FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory( + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), false, true); + const FileSys::Path boss_path{GetBossDataDir(extdata_id)}; + auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0); + + if (!archive_result.Succeeded()) { + LOG_ERROR(Service_BOSS, "Extdata opening failed"); + return ns_data; + } + LOG_DEBUG(Service_BOSS, "Spotpass Extdata opened successfully!"); + auto boss_archive = std::move(archive_result).Unwrap().get(); + + for (const auto& cur_file : boss_files) { + if (cur_file.is_directory || cur_file.file_size < BOSS_HEADER_LENGTH) { + LOG_WARNING(Service_BOSS, "Spotpass extdata contains directory or file is too short"); + continue; + } + + NsDataEntry entry{}; + entry.filename = Common::UTF16ToUTF8(cur_file.filename); + const FileSys::Path file_path = fmt::format("/{}", entry.filename); + LOG_DEBUG(Service_BOSS, "Spotpass filename={}", entry.filename); + + FileSys::Mode mode{}; + mode.read_flag.Assign(1); + + auto file_result = boss_archive->OpenFile(file_path, mode); + + if (!file_result.Succeeded()) { + LOG_WARNING(Service_BOSS, "Opening Spotpass file failed."); + continue; + } + auto file = std::move(file_result).Unwrap(); + LOG_DEBUG(Service_BOSS, "Opening Spotpass file succeeded!"); + file->Read(0, BOSS_HEADER_LENGTH, reinterpret_cast(&entry.header)); + // Extdata header should have size 0x18: + // https://www.3dbrew.org/wiki/SpotPass#Payload_Content_Header + if (entry.header.header_length != BOSS_EXTDATA_HEADER_LENGTH) { + LOG_WARNING(Service_BOSS, + "Incorrect header length or non-spotpass file; expected {:#010X}, " + "found {:#010X}", + BOSS_EXTDATA_HEADER_LENGTH, entry.header.header_length); + continue; + } + if (entry.header.program_id != program_id) { + LOG_WARNING(Service_BOSS, + "Mismatched program ID in spotpass data. Was expecting " + "{:#018X}, found {:#018X}", + program_id, u64(entry.header.program_id)); + continue; + } + // Check the payload size is correct, excluding header + if (entry.header.payload_size != cur_file.file_size - BOSS_HEADER_LENGTH) { + LOG_WARNING(Service_BOSS, + "Mismatched file size, was expecting {:#010X}, found {:#010X}", + u32(entry.header.payload_size), cur_file.file_size - BOSS_HEADER_LENGTH); + continue; + } + LOG_DEBUG( + Service_BOSS, "Datatype is {:#010X}, Payload size is {:#010X}, NsDataID is {:#010X}", + static_cast(entry.header.datatype), static_cast(entry.header.payload_size), + static_cast(entry.header.ns_data_id)); + + ns_data.push_back(entry); + } + return ns_data; +} + +std::vector Util::GetBossExtDataFiles() { + + std::vector boss_files; + + FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory( + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), false, true); + const FileSys::Path boss_path{GetBossDataDir(extdata_id)}; + + auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0); + if (!archive_result.Succeeded()) { + LOG_WARNING(Service_BOSS, "Extdata opening failed"); + return boss_files; + } + LOG_DEBUG(Service_BOSS, "Spotpass Extdata opened successfully!"); + auto boss_archive = std::move(archive_result).Unwrap().get(); + + auto dir_result = boss_archive->OpenDirectory(DIR_SEP); + if (!dir_result.Succeeded()) { + LOG_WARNING(Service_BOSS, "Extdata directory opening failed"); + return boss_files; + } + LOG_DEBUG(Service_BOSS, "Spotpass Extdata directory opened successfully!"); + const auto dir = std::move(dir_result).Unwrap(); + // Keep reading the directory 32 files at a time until all files have been checked + constexpr u32 files_to_read = 32; + u32 entry_count = 0; + size_t i = 0; + do { + boss_files.resize(boss_files.size() + files_to_read); + entry_count = dir->Read(files_to_read, boss_files.data() + (i * files_to_read)); + } while (files_to_read <= entry_count && ++i); + LOG_DEBUG(Service_BOSS, "Spotpass Extdata directory contains {} files", + i * files_to_read + entry_count); + boss_files.resize(i * files_to_read + entry_count); + return boss_files; +} + +u16 Util::GetOutputEntries(u32 filter, u32 max_entries, Kernel::MappedBuffer& buffer) { + std::vector ns_data = GetNsDataEntries(); + std::vector output_entries; + for (const auto& cur_entry : ns_data) { + const u16 datatype_high = + static_cast(static_cast(cur_entry.header.datatype) >> 16); + const u16 datatype_low = + static_cast(static_cast(cur_entry.header.datatype) & 0xFFFF); + const u16 filter_high = static_cast(filter >> 16); + const u16 filter_low = static_cast(filter & 0xFFFF); + if (filter != 0xFFFFFFFF && + (filter_high != datatype_high || (filter_low & datatype_low) == 0)) { + LOG_DEBUG( + Service_BOSS, + "Filtered out NsDataID {:#010X}; failed filter {:#010X} with datatype {:#010X}", + static_cast(cur_entry.header.ns_data_id), filter, + static_cast(cur_entry.header.datatype)); + continue; + } + if (output_entries.size() >= max_entries) { + LOG_WARNING(Service_BOSS, "Reached maximum number of entries"); + break; + } + output_entries.push_back(cur_entry.header.ns_data_id); + } + buffer.Write(output_entries.data(), 0, sizeof(u32) * output_entries.size()); + LOG_DEBUG(Service_BOSS, "{} usable entries returned", output_entries.size()); + return static_cast(output_entries.size()); +} + +std::pair Util::GetTaskStatusAndDuration(std::string task_id, + bool wait_on_result) { + // Default duration is zero -> means no more runs of the task are allowed + u32 duration = 0; + + if (!task_id_list.contains(task_id)) { + LOG_WARNING(Service_BOSS, "Could not find task_id in list"); + return {TaskStatus::Failed, duration}; + } + LOG_DEBUG(Service_BOSS, "Found currently running task id"); + task_id_list[task_id].times_checked++; + + // Get the duration from the task if available + if (const auto* dur_prop = + task_id_list[task_id].props.contains(PropertyID::Duration) + ? std::get_if(&task_id_list[task_id].props[PropertyID::Duration]) + : nullptr; + dur_prop) { + duration = *dur_prop; + } + + if (task_id_list[task_id].download_task.valid()) { + LOG_DEBUG(Service_BOSS, "Task is still running"); + auto status = task_id_list[task_id].download_task.wait_for(std::chrono::microseconds(0)); + if (status == std::future_status::ready || wait_on_result) { + LOG_DEBUG(Service_BOSS, + wait_on_result ? "Waiting for result..." : "Task just finished"); + + task_id_list[task_id].task_result = task_id_list[task_id].download_task.get(); + if (task_id_list[task_id].task_result) { + LOG_DEBUG(Service_BOSS, "Task ran successfully"); + return {TaskStatus::Success, duration}; + } + + LOG_WARNING(Service_BOSS, "Task failed"); + return {TaskStatus::Failed, duration}; + } + + LOG_DEBUG(Service_BOSS, "Task is still running"); + return {TaskStatus::Running, duration}; + } + + LOG_DEBUG(Service_BOSS, "Task has finished running or is invalid"); + + if (task_id_list[task_id].task_result) { + LOG_DEBUG(Service_BOSS, "Task ran successfully"); + return {TaskStatus::Success, duration}; + } + + LOG_WARNING(Service_BOSS, "Task failed"); + return {TaskStatus::Failed, duration}; +} + +std::optional Util::GetNsDataEntryFromID(u32 ns_data_id) { + std::vector ns_data = GetNsDataEntries(); + const auto entry_iter = std::find_if(ns_data.begin(), ns_data.end(), [ns_data_id](auto entry) { + return entry.header.ns_data_id == ns_data_id; + }); + if (entry_iter == ns_data.end()) { + LOG_WARNING(Service_BOSS, "Could not find NsData with ID {:#010X}", ns_data_id); + return std::nullopt; + } + return *entry_iter; +} + +bool Util::ReadWriteProperties(bool write, PropertyID property_id, u32 size, + Kernel::MappedBuffer& buffer) { + if (!cur_props.props.contains(property_id)) { + LOG_ERROR(Service_BOSS, "Unknown property with id {:#06X}", property_id); + return false; + } + + auto& prop = cur_props.props[property_id]; + + auto readwrite_pod = [&](T& cur_prop) { + static_assert(std::is_trivial::value, + "Only trivial types are allowed for readwrite_pod"); + if (size != sizeof(cur_prop)) { + LOG_ERROR(Service_BOSS, "Unexpected size of property {:#06x}, was expecting {}, got {}", + property_id, sizeof(cur_prop), size); + } + if (write) { + buffer.Write(&cur_prop, 0, size); + } else { + T new_prop = 0; + buffer.Read(&new_prop, 0, size); + prop = new_prop; + LOG_DEBUG(Service_BOSS, "Read property {:#06X}, value {:#010X}", property_id, new_prop); + } + }; + + auto readwrite_vector = [&](std::vector& cur_prop) { + if (size != cur_prop.size() * sizeof(T)) { + LOG_ERROR(Service_BOSS, "Unexpected size of property {:#06x}, was expecting {}, got {}", + property_id, cur_prop.size(), size); + } + LOG_DEBUG(Service_BOSS, + "Vector property contains {} elements of size {} for a total size of {}", + cur_prop.size(), sizeof(T), size); + if (write) { + buffer.Write(cur_prop.data(), 0, size); + } else { + std::vector new_prop(cur_prop.size()); + buffer.Read(new_prop.data(), 0, size); + prop = new_prop; + if (sizeof(T) == sizeof(u8)) { + LOG_DEBUG(Service_BOSS, "Read property {:#06X}, value {}", property_id, + std::string_view(reinterpret_cast(new_prop.data()), size)); + } else if (sizeof(T) == sizeof(u32) && new_prop.size() == CERTIDLIST_SIZE) { + LOG_DEBUG(Service_BOSS, "Read property {:#06X}, values {:#010X},{:#010X},{:#010X}", + property_id, new_prop[0], new_prop[1], new_prop[2]); + } + } + }; + + if (const auto char_prop = std::get_if(&prop)) + readwrite_pod(*char_prop); + else if (const auto short_prop = std::get_if(&prop)) + readwrite_pod(*short_prop); + else if (const auto int_prop = std::get_if(&prop)) + readwrite_pod(*int_prop); + else if (const auto charvec_prop = std::get_if>(&prop)) + readwrite_vector(*charvec_prop); + else if (const auto intvec_prop = std::get_if>(&prop)) + readwrite_vector(*intvec_prop); + + return true; +} + +} // namespace Service::BOSS \ No newline at end of file diff --git a/src/core/hle/service/boss/spotpass_utils.h b/src/core/hle/service/boss/spotpass_utils.h new file mode 100644 index 000000000..dba1a3e30 --- /dev/null +++ b/src/core/hle/service/boss/spotpass_utils.h @@ -0,0 +1,212 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include "core/file_sys/archive_backend.h" +#include "core/file_sys/directory_backend.h" +#include "core/global.h" +#include "core/hle/kernel/event.h" +#include "core/hle/service/service.h" + +namespace Service::BOSS { + +// File header info from +// https://www.3dbrew.org/wiki/SpotPass#Payload_Content_Header +// So the total header is only 52 bytes long + +constexpr u32 BOSS_HEADER_LENGTH = 0x34; +// 52 bytes doesn't align nicely into 8-byte words +#pragma pack(push, 4) +struct BossHeader { + u8 header_length; + INSERT_PADDING_BYTES(11); + u32_be unknown; + u32_be download_date; + INSERT_PADDING_BYTES(4); + u64_be program_id; + INSERT_PADDING_BYTES(4); + u32_be datatype; + u32_be payload_size; + u32_be ns_data_id; + u32_be version; +}; +#pragma pack(pop) + +static_assert(sizeof(BossHeader) == 0x34, "BossHeader has incorrect size"); + +// Payload header info from +// https://www.3dbrew.org/wiki/SpotPass#Content_Container +// So the total header is only 40 bytes long + +constexpr u32 BOSS_PAYLOAD_HEADER_LENGTH = 0x28; +constexpr u32 BOSS_MAGIC = Loader::MakeMagic('b', 'o', 's', 's'); +constexpr u32 BOSS_PAYLOAD_MAGIC = 0x10001; +constexpr u64 NEWS_PROG_ID = 0x0004013000003502; +// 40 bytes doesn't align nicely into 8-byte words either +#pragma pack(push, 4) +struct BossPayloadHeader { + u32_le boss; + u32_be magic; + u32_be filesize; + u64_be release_date; + u16_be one; + INSERT_PADDING_BYTES(2); + u16_be hash_type; + u16_be rsa_size; + std::array iv_start; +}; +#pragma pack(pop) + +static_assert(sizeof(BossPayloadHeader) == 0x28, "BossPayloadHeader has incorrect size"); + +constexpr u32 BOSS_CONTENT_HEADER_LENGTH = 0x132; +constexpr u32 BOSS_HEADER_WITH_HASH_LENGTH = 0x13C; +constexpr u32 BOSS_ENTIRE_HEADER_LENGTH = BOSS_CONTENT_HEADER_LENGTH + BOSS_HEADER_WITH_HASH_LENGTH; +constexpr u32 BOSS_EXTDATA_HEADER_LENGTH = 0x18; +constexpr u32 BOSS_A_ENTRY_SIZE = 0x800; +constexpr u32 BOSS_S_ENTRY_SIZE = 0xC00; +constexpr u32 BOSS_SAVE_HEADER_SIZE = 4; +constexpr u32 BOSS_S_PROG_ID_OFFSET = 0x10; +constexpr u32 BOSS_S_TASK_ID_OFFSET = 0x18; +constexpr u32 BOSS_S_URL_OFFSET = 0x21C; + +struct NsDataEntry { + std::string filename; + BossHeader header; +}; + +constexpr ResultCode RESULT_FAILED(1); +constexpr u8 TASK_ID_SIZE = 8; + +enum class NsDataHeaderInfoType : u8 { + ProgramId, + Unknown, + Datatype, + PayloadSize, + NsDataId, + Version, + Everything, +}; + +struct NsDataHeaderInfo { + u64 program_id; + INSERT_PADDING_BYTES(4); + u32 datatype; + u32 payload_size; + u32 ns_data_id; + u32 version; + INSERT_PADDING_BYTES(4); +}; + +static_assert(sizeof(NsDataHeaderInfo) == 0x20, "NsDataHeaderInfo has incorrect size"); + +enum class TaskStatus : u8 { + Success = 0, + Running = 2, + NotStarted = 5, + Failed = 7, +}; + +enum class PropertyID : u16 { + Interval = 0x03, + Duration = 0x04, + Url = 0x07, + Headers = 0x0D, + CertId = 0x0E, + CertIdList = 0x0F, + LoadCert = 0x10, + LoadRootCert = 0x11, + TotalTasks = 0x35, + TaskIdList = 0x36, +}; + +constexpr size_t URL_SIZE = 0x200; +constexpr size_t HEADERS_SIZE = 0x360; +constexpr size_t CERTIDLIST_SIZE = 3; +constexpr size_t TASKIDLIST_SIZE = 0x400; + +struct BossTaskProperties { + std::future download_task; + bool task_result; + u32 times_checked; + std::map, std::vector>> props{ + {static_cast(0x00), u8()}, + {static_cast(0x01), u8()}, + {static_cast(0x02), u32()}, + // interval + {PropertyID::Interval, u32()}, + // duration + {PropertyID::Duration, u32()}, + {static_cast(0x05), u8()}, + {static_cast(0x06), u8()}, + // url + {PropertyID::Url, std::vector(URL_SIZE)}, + {static_cast(0x08), u32()}, + {static_cast(0x09), u8()}, + {static_cast(0x0A), std::vector(0x100)}, + {static_cast(0x0B), std::vector(0x200)}, + {static_cast(0x0C), u32()}, + // headers + {PropertyID::Headers, std::vector(HEADERS_SIZE)}, + // certid + {PropertyID::CertId, u32()}, + // certidlist + {PropertyID::CertIdList, std::vector(CERTIDLIST_SIZE)}, + // loadcert (bool) + {PropertyID::LoadCert, u8()}, + // loadrootcert (bool) + {PropertyID::LoadRootCert, u8()}, + {static_cast(0x12), u8()}, + {static_cast(0x13), u32()}, + {static_cast(0x14), u32()}, + {static_cast(0x15), std::vector(0x40)}, + {static_cast(0x16), u32()}, + {static_cast(0x18), u8()}, + {static_cast(0x19), u8()}, + {static_cast(0x1A), u8()}, + {static_cast(0x1B), u32()}, + {static_cast(0x1C), u32()}, + // totaltasks + {PropertyID::TotalTasks, u16()}, + // taskidlist + {PropertyID::TaskIdList, std::vector(TASKIDLIST_SIZE)}, + {static_cast(0x3B), u32()}, + {static_cast(0x3E), std::vector(0x200)}, + {static_cast(0x3F), u8()}, + }; +}; + +constexpr std::array BOSS_SYSTEM_SAVEDATA_ID{ + 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x01, 0x00, +}; + +constexpr std::array BOSS_SYSTEM_SAVEDATA_HEADER{0x00, 0x80, 0x34, 0x12}; + +FileSys::Path GetBossDataDir(u64 extdata_id); +bool DownloadBossDataFromURL(std::string_view url, std::string_view file_name, u64 program_id, + u64 extdata_id); + +class Util { +public: + std::vector GetNsDataEntries(); + std::vector GetBossExtDataFiles(); + u16 GetOutputEntries(u32 filter, u32 max_entries, Kernel::MappedBuffer& buffer); + bool ReadWriteProperties(bool write, PropertyID property_id, u32 size, + Kernel::MappedBuffer& buffer); + std::optional GetNsDataEntryFromID(u32 ns_data_id); + std::pair GetTaskStatusAndDuration(std::string task_id, bool wait_on_result); + +public: + u64 program_id; + u64 extdata_id; + std::map task_id_list; + BossTaskProperties cur_props; +}; +} // namespace Service::BOSS \ No newline at end of file diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index ba98ce045..77e539258 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -353,6 +353,10 @@ void ArchiveManager::RegisterArchiveTypes() { std::make_unique(sdmc_directory, false); RegisterArchiveType(std::move(extsavedata_factory), ArchiveIdCode::ExtSaveData); + auto bossextsavedata_factory = + std::make_unique(sdmc_directory, false, true); + RegisterArchiveType(std::move(bossextsavedata_factory), ArchiveIdCode::BossExtSaveData); + auto sharedextsavedata_factory = std::make_unique(nand_directory, true); RegisterArchiveType(std::move(sharedextsavedata_factory), ArchiveIdCode::SharedExtSaveData); diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index f4604803b..c9e8011ec 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -37,6 +37,7 @@ enum class ArchiveIdCode : u32 { SaveData = 0x00000004, ExtSaveData = 0x00000006, SharedExtSaveData = 0x00000007, + BossExtSaveData = 0x12345678, SystemSaveData = 0x00000008, SDMC = 0x00000009, SDMCWriteOnly = 0x0000000A,