From b0097d16eb0c7b4845301861b898e49519de24cb Mon Sep 17 00:00:00 2001 From: emoose Date: Mon, 13 Jan 2020 19:49:28 +0000 Subject: [PATCH] [XAM] Load SPA achievement data from DLC content if it exists This'll load in and convert the SPA data from the spa.bin inside DLC (marketplace / 00000002) content, into the profile GPDs. (note that changes from the new SPA won't show in the log-file achievement list until game is restarted!) Also fixed some minor log bugs to do with endianness... The existing SPA code should make sure only additions from the SPA are loaded in, eg. achievements that were added - achievements that might be missing shouldn't make any difference. e.g. it should be able to perform a load like: base game (20 achievements) -> DLC2 (50 achievements) -> DLC1 (30 achievements) and the final GPD will contain the 50 achievements, from the SPA which contained the most. Only additions from new SPAs will be loaded, any achievement changes (like a description text change or something) won't have any effect. Not sure if we should try adding things to support that or not... I'd guess that the X360 would probably support it, but if there isn't anything that takes advantage of it, is it worth us supporting? --- src/xenia/kernel/xam/user_profile.cc | 4 +- src/xenia/kernel/xam/xam_content.cc | 61 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 42afb6c46..ff258acd9 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -552,7 +552,7 @@ xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) { if (gpd != title_gpds_.end()) { auto& title_gpd = (*gpd).second; - XELOGI("Loaded existing GPD for title %X", title_data.title_id); + XELOGD("Updating existing GPD for title %X", (uint32_t)title_data.title_id); bool always_update_title = false; if (!dash_gpd_.GetTitle(title_data.title_id, &title_info)) { @@ -608,7 +608,7 @@ xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) { UpdateGpd(kDashboardID, dash_gpd_); } else { // GPD not found... have to create it! - XELOGI("Creating new GPD for title %X", title_data.title_id); + XELOGI("Creating new GPD for title %X", (uint32_t)title_data.title_id); title_info.title_name = xe::to_wstring(spa_data.GetTitleName()); title_info.title_id = title_data.title_id; diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index e970cfc22..8a1ddf2dd 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -315,6 +315,67 @@ dword_result_t XamContentCreateEx(dword_t user_index, lpstring_t root_name, result = content_manager->OpenContent(root_name.value(), content_data); } + if (!result && content_data.content_type == + (uint32_t)vfs::StfsContentType::kMarketplaceContent) { + // Load up spa.bin from this DLC if it has one: + // TODO: should we do this inside ContentManager instead? + auto spa_entry = kernel_state()->file_system()->ResolvePath( + root_name.value() + ":\\spa.bin"); + if (spa_entry) { + kernel::xam::xdbf::SpaFile spa; + bool spa_result = false; + // If the FS supports mapping, map the file in and load from that. + if (spa_entry->can_map()) { + // Map. + auto mmap = spa_entry->OpenMapped(MappedMemory::Mode::kRead); + if (mmap) { + // Load the SPA + spa_result = spa.Read(mmap->data(), mmap->size()); + } + } else { + std::vector buffer(spa_entry->size()); + + // Open file for reading. + vfs::File* file = nullptr; + auto result2 = spa_entry->Open(vfs::FileAccess::kGenericRead, &file); + if (!result2) { + // Read entire file into memory. + // Ugh. + size_t bytes_read = 0; + result2 = + file->ReadSync(buffer.data(), buffer.size(), 0, &bytes_read); + if (!result2) { + // Load the SPA. + spa_result = spa.Read(buffer.data(), bytes_read); + + // Close the file. + file->Destroy(); + } + } + } + if (spa_result) { + XELOGI("Loaded SPA data from DLC package %s (%S)", + content_data.file_name.c_str(), + content_data.display_name.c_str()); + xdbf::X_XDBF_XTHD_DATA title_data; + if (spa.GetTitleData(&title_data)) { + XELOGI("(SPA version: %d.%d.%d.%d)", + (uint32_t)title_data.title_version_major, + (uint32_t)title_data.title_version_minor, + (uint32_t)title_data.title_version_build, + (uint32_t)title_data.title_version_revision); + } + // Set/update title SPA + for (uint32_t i = 0; i < kernel_state()->num_profiles(); i++) { + auto profile = kernel_state()->user_profile(i); + if (profile) { + profile->SetTitleSpaData(spa); + } + } + } + } + } + if (license_mask_ptr && XSUCCEEDED(result)) { *license_mask_ptr = 0; // Stub! }