[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?
This commit is contained in:
emoose 2020-01-13 19:49:28 +00:00 committed by illusion
parent d06678b2b0
commit b0097d16eb
2 changed files with 63 additions and 2 deletions

View File

@ -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;

View File

@ -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<uint8_t> 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!
}