diff --git a/src/core/hle/service/boss/boss.cpp b/src/core/hle/service/boss/boss.cpp index 2fa6489373..6850094242 100644 --- a/src/core/hle/service/boss/boss.cpp +++ b/src/core/hle/service/boss/boss.cpp @@ -631,34 +631,51 @@ void Module::Interface::DeleteNsData(Kernel::HLERequestContext& ctx) { void Module::Interface::GetNsDataHeaderInfo(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const u32 ns_data_id = rp.Pop(); - const u8 type = rp.Pop(); - const u32 size = rp.Pop(); + const auto ns_data_id = rp.Pop(); + const auto type = rp.PopEnum(); + const auto size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); + const auto online_service = GetSessionService(ctx); + if (online_service == nullptr) { + return; + } + const auto result = online_service->GetNsDataHeaderInfo(ns_data_id, type, size, buffer); + 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, "called, ns_data_id={:#010x}, type={:#04x}, size={:#010x}", ns_data_id, + type, size); } void Module::Interface::ReadNsData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const u32 ns_data_id = rp.Pop(); - const u64 offset = rp.Pop(); - const u32 size = rp.Pop(); + const auto ns_data_id = rp.Pop(); + const auto offset = rp.Pop(); + const auto size = rp.Pop(); auto& buffer = rp.PopMappedBuffer(); - IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); - rb.Push(RESULT_SUCCESS); - rb.Push(size); /// Should be actual read size - rb.Push(0); /// unknown - rb.PushMappedBuffer(buffer); + const auto online_service = GetSessionService(ctx); + if (online_service == nullptr) { + return; + } + const auto result = online_service->ReadNsData(ns_data_id, offset, size, buffer); - LOG_WARNING(Service_BOSS, "(STUBBED) ns_data_id={:#010x}, offset={:#018x}, size={:#010x}", - ns_data_id, offset, size); + if (result.Succeeded()) { + IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); + rb.Push(result.Code()); + rb.Push(static_cast(result.Unwrap())); + rb.Push(0); /// unknown + rb.PushMappedBuffer(buffer); + } else { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(result.Code()); + } + + LOG_DEBUG(Service_BOSS, "called, ns_data_id={:#010x}, offset={:#018x}, size={:#010x}", + ns_data_id, offset, size); } void Module::Interface::SetNsDataAdditionalInfo(Kernel::HLERequestContext& ctx) { @@ -710,14 +727,27 @@ void Module::Interface::GetNsDataNewFlag(Kernel::HLERequestContext& ctx) { void Module::Interface::GetNsDataLastUpdate(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); - const u32 unk_param1 = rp.Pop(); + const u32 ns_data_id = rp.Pop(); + + const auto online_service = GetSessionService(ctx); + if (online_service == nullptr) { + return; + } + + const auto entry = online_service->GetNsDataEntryFromId(ns_data_id); + if (!entry.has_value()) { + // TODO: Proper error code. + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_UNKNOWN); + return; + } 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(entry->header.download_date); // return the download date from the ns data - LOG_WARNING(Service_BOSS, "(STUBBED) unk_param1={:#010x}", unk_param1); + LOG_DEBUG(Service_BOSS, "called, ns_data_id={:#010X}", ns_data_id); } void Module::Interface::GetErrorCode(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/boss/online_service.cpp b/src/core/hle/service/boss/online_service.cpp index 6a14975211..10f1ba058f 100644 --- a/src/core/hle/service/boss/online_service.cpp +++ b/src/core/hle/service/boss/online_service.cpp @@ -73,12 +73,14 @@ ResultCode OnlineService::InitializeSession(u64 init_program_id) { auto create_archive_result = systemsavedata_factory.Open(archive_path, 0); if (!create_archive_result.Succeeded()) { LOG_ERROR(Service_BOSS, "Could not open BOSS savedata"); - return ResultCode(1); + // TODO: Proper error code. + return RESULT_UNKNOWN; } boss_system_save_data_archive = std::move(create_archive_result).Unwrap(); } else { LOG_ERROR(Service_BOSS, "Could not open BOSS savedata"); - return ResultCode(1); + // TODO: Proper error code. + return RESULT_UNKNOWN; } FileSys::Mode open_mode = {}; @@ -151,14 +153,16 @@ void OnlineService::RegisterTask(const u32 size, Kernel::MappedBuffer& buffer) { ResultCode OnlineService::UnregisterTask(const u32 size, Kernel::MappedBuffer& buffer) { if (size > TASK_ID_SIZE) { LOG_WARNING(Service_BOSS, "TaskId cannot be longer than 8"); - return ResultCode(1); + // TODO: Proper error code. + return RESULT_UNKNOWN; } std::string task_id(size, 0); buffer.Read(task_id.data(), 0, size); if (task_id_list.erase(task_id) == 0) { LOG_WARNING(Service_BOSS, "TaskId not in list"); - return ResultCode(1); + // TODO: Proper error code. + return RESULT_UNKNOWN; } return RESULT_SUCCESS; @@ -187,20 +191,32 @@ void OnlineService::GetTaskIdList() { } } -std::vector OnlineService::GetBossExtDataFiles() { +FileSys::Path OnlineService::GetBossDataDir() { + const u32 high = static_cast(extdata_id >> 32); + const u32 low = static_cast(extdata_id & 0xFFFFFFFF); + return FileSys::ConstructExtDataBinaryPath(1, high, low); +} + +std::unique_ptr OnlineService::OpenBossExtData() { FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory( FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), FileSys::ExtSaveDataType::Boss); const FileSys::Path boss_path{GetBossDataDir()}; auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0); if (!archive_result.Succeeded()) { - LOG_WARNING(Service_BOSS, "Extdata opening failed"); - return {}; + LOG_WARNING(Service_BOSS, "Failed to open SpotPass ext data archive with ID '{:#010x}'.", + extdata_id); + return nullptr; } + return std::move(archive_result).Unwrap(); +} - auto boss_archive = std::move(archive_result).Unwrap(); +std::vector OnlineService::GetBossExtDataFiles( + FileSys::ArchiveBackend* boss_archive) { auto dir_result = boss_archive->OpenDirectory("/"); if (!dir_result.Succeeded()) { - LOG_WARNING(Service_BOSS, "Extdata directory opening failed"); + LOG_WARNING(Service_BOSS, + "Failed to open root directory of SpotPass ext data with ID '{:#010x}'.", + extdata_id); return {}; } auto dir = std::move(dir_result).Unwrap(); @@ -219,29 +235,20 @@ std::vector OnlineService::GetBossExtDataFiles() { return boss_files; } -FileSys::Path OnlineService::GetBossDataDir() { - const u32 high = static_cast(extdata_id >> 32); - const u32 low = static_cast(extdata_id & 0xFFFFFFFF); - return FileSys::ConstructExtDataBinaryPath(1, high, low); -} - std::vector OnlineService::GetNsDataEntries() { - FileSys::ArchiveFactory_ExtSaveData boss_extdata_archive_factory( - FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), FileSys::ExtSaveDataType::Boss); - const FileSys::Path boss_path{GetBossDataDir()}; - auto archive_result = boss_extdata_archive_factory.Open(boss_path, 0); - if (!archive_result.Succeeded()) { - LOG_WARNING(Service_BOSS, "Extdata opening failed"); + auto boss_archive = OpenBossExtData(); + if (!boss_archive) { return {}; } - auto boss_archive = std::move(archive_result).Unwrap().get(); std::vector ns_data; - std::vector boss_files = GetBossExtDataFiles(); + const auto boss_files = GetBossExtDataFiles(boss_archive.get()); for (const auto& current_file : boss_files) { constexpr u32 boss_header_length = 0x34; if (current_file.is_directory || current_file.file_size < boss_header_length) { - LOG_WARNING(Service_BOSS, "SpotPass extdata contains directory or file is too short"); + LOG_WARNING(Service_BOSS, + "SpotPass extdata contains directory or file is too short: '{}'", + Common::UTF16ToUTF8(current_file.filename)); continue; } @@ -315,6 +322,129 @@ u16 OnlineService::GetNsDataIdList(const u32 filter, const u32 max_entries, return static_cast(output_entries.size()); } +std::optional OnlineService::GetNsDataEntryFromId(const 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; +} + +ResultCode OnlineService::GetNsDataHeaderInfo(const u32 ns_data_id, const NsDataHeaderInfoType type, + const u32 size, Kernel::MappedBuffer& buffer) { + const auto entry = GetNsDataEntryFromId(ns_data_id); + if (!entry.has_value()) { + LOG_WARNING(Service_BOSS, "Failed to find NsData entry for ID {:#010X}", ns_data_id); + // TODO: Proper error code. + return RESULT_UNKNOWN; + } + + static constexpr std::array EXPECTED_NS_DATA_HEADER_INFO_SIZES = { + sizeof(u64), // Program ID + sizeof(u32), // Unknown + sizeof(u32), // Data Type + sizeof(u32), // Payload Size + sizeof(u32), // NsData ID + sizeof(u32), // Version + sizeof(NsDataHeaderInfo), // Everything + }; + if (size != EXPECTED_NS_DATA_HEADER_INFO_SIZES[static_cast(type)]) { + LOG_WARNING(Service_BOSS, "Invalid size {} for type {}", size, type); + // TODO: Proper error code. + return RESULT_UNKNOWN; + } + + switch (type) { + case NsDataHeaderInfoType::ProgramId: + buffer.Write(&entry->header.program_id, 0, size); + return RESULT_SUCCESS; + case NsDataHeaderInfoType::Unknown: { + // TODO: Figure out what this is. Stubbed to zero for now. + const u32 zero = 0; + buffer.Write(&zero, 0, size); + return RESULT_SUCCESS; + } + case NsDataHeaderInfoType::Datatype: + buffer.Write(&entry->header.datatype, 0, size); + return RESULT_SUCCESS; + case NsDataHeaderInfoType::PayloadSize: + buffer.Write(&entry->header.payload_size, 0, size); + return RESULT_SUCCESS; + case NsDataHeaderInfoType::NsDataId: + buffer.Write(&entry->header.ns_data_id, 0, size); + return RESULT_SUCCESS; + case NsDataHeaderInfoType::Version: + buffer.Write(&entry->header.version, 0, size); + return RESULT_SUCCESS; + case NsDataHeaderInfoType::Everything: { + const NsDataHeaderInfo info = { + .program_id = entry->header.program_id, + .datatype = entry->header.datatype, + .payload_size = entry->header.payload_size, + .ns_data_id = entry->header.ns_data_id, + .version = entry->header.version, + }; + buffer.Write(&info, 0, size); + return RESULT_SUCCESS; + } + default: + LOG_WARNING(Service_BOSS, "Unknown header info type {}", type); + // TODO: Proper error code. + return RESULT_UNKNOWN; + } +} + +ResultVal OnlineService::ReadNsData(const u32 ns_data_id, const u64 offset, const u32 size, + Kernel::MappedBuffer& buffer) { + std::optional entry = GetNsDataEntryFromId(ns_data_id); + if (!entry.has_value()) { + LOG_WARNING(Service_BOSS, "Failed to find NsData entry for ID {:#010X}", ns_data_id); + // TODO: Proper error code. + return RESULT_UNKNOWN; + } + + if (entry->header.payload_size < size + offset) { + LOG_WARNING(Service_BOSS, + "Invalid request to read {:#010X} bytes at offset {:#010X}, payload " + "length is {:#010X}", + size, offset, static_cast(entry->header.payload_size)); + // TODO: Proper error code. + return RESULT_UNKNOWN; + } + + auto boss_archive = OpenBossExtData(); + if (!boss_archive) { + // TODO: Proper error code. + return RESULT_UNKNOWN; + } + + 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, "Failed to open SpotPass extdata file '{}'.", entry->filename); + // TODO: Proper error code. + return RESULT_UNKNOWN; + } + + auto file = std::move(file_result).Unwrap(); + std::vector ns_data_array(size); + auto read_result = file->Read(sizeof(BossHeader) + offset, size, ns_data_array.data()); + if (!read_result.Succeeded()) { + LOG_WARNING(Service_BOSS, "Failed to read SpotPass extdata file '{}'.", entry->filename); + // TODO: Proper error code. + return RESULT_UNKNOWN; + } + + buffer.Write(ns_data_array.data(), 0, size); + return read_result; +} + template struct overload : Ts... { using Ts::operator()...; @@ -325,8 +455,9 @@ overload(Ts...) -> overload; ResultCode OnlineService::SendProperty(const u16 id, const u32 size, Kernel::MappedBuffer& buffer) { const auto property_id = static_cast(id); if (!current_props.properties.contains(property_id)) { - LOG_ERROR(Service_BOSS, "Unknown property with id {:#06x}", property_id); - return ResultCode(1); + LOG_ERROR(Service_BOSS, "Unknown property with ID {:#06x} and size {}", property_id, size); + // TODO: Proper error code. + return RESULT_UNKNOWN; } auto& prop = current_props.properties[property_id]; @@ -365,8 +496,9 @@ ResultCode OnlineService::ReceiveProperty(const u16 id, const u32 size, Kernel::MappedBuffer& buffer) { const auto property_id = static_cast(id); if (!current_props.properties.contains(property_id)) { - LOG_ERROR(Service_BOSS, "Unknown property with id {:#06x}", property_id); - return ResultCode(1); + LOG_ERROR(Service_BOSS, "Unknown property with ID {:#06x} and size {}", property_id, size); + // TODO: Proper error code. + return RESULT_UNKNOWN; } auto write_pod = [&](T& cur_prop) { diff --git a/src/core/hle/service/boss/online_service.h b/src/core/hle/service/boss/online_service.h index b059060cf7..ec2b18a797 100644 --- a/src/core/hle/service/boss/online_service.h +++ b/src/core/hle/service/boss/online_service.h @@ -17,6 +17,7 @@ class MappedBuffer; } namespace FileSys { +class ArchiveBackend; struct Entry; class Path; } // namespace FileSys @@ -67,6 +68,27 @@ struct NsDataEntry { BossHeader header; }; +enum class NsDataHeaderInfoType : u8 { + ProgramId = 0, + Unknown = 1, + Datatype = 2, + PayloadSize = 3, + NsDataId = 4, + Version = 5, + Everything = 6, +}; + +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 PropertyID : u16 { Interval = 0x03, Duration = 0x04, @@ -144,11 +166,17 @@ public: ResultCode UnregisterTask(const u32 size, Kernel::MappedBuffer& buffer); void GetTaskIdList(); u16 GetNsDataIdList(const u32 filter, const u32 max_entries, Kernel::MappedBuffer& buffer); + std::optional GetNsDataEntryFromId(const u32 ns_data_id); + ResultCode GetNsDataHeaderInfo(const u32 ns_data_id, const NsDataHeaderInfoType type, + const u32 size, Kernel::MappedBuffer& buffer); + ResultVal ReadNsData(const u32 ns_data_id, const u64 offset, const u32 size, + Kernel::MappedBuffer& buffer); ResultCode SendProperty(const u16 id, const u32 size, Kernel::MappedBuffer& buffer); ResultCode ReceiveProperty(const u16 id, const u32 size, Kernel::MappedBuffer& buffer); private: - std::vector GetBossExtDataFiles(); + std::unique_ptr OpenBossExtData(); + std::vector GetBossExtDataFiles(FileSys::ArchiveBackend* boss_archive); FileSys::Path GetBossDataDir(); std::vector GetNsDataEntries();