[Kernel] Update XamUserCreateAchievementEnumerator to work with GPDs

Seems to work fine, tested with Tetris TGM ACE which uses achievements to track progression & unlock things in the game, and unlocking achievements in the GPDs does unlock things in game as intended.
Haven't actually tested unlocking the achievements in-game and then checking though (my tetris skills are awful :P), but I think it should work fine.

Strings aren't copied into it yet since I didn't want to alloc guest memory for every CreateEnumerator call...
Not many games actually have in-game achievement lists though, but I've added a placeholder string for any that might.

Like said in the comment, maybe we should alloc those strings in guest mem when the game GPD/SPA is first loaded in?
This commit is contained in:
emoose 2018-11-16 05:50:48 +00:00
parent 8368e8ce06
commit e510972691
No known key found for this signature in database
GPG Key ID: 3735C67912F5FF97
1 changed files with 61 additions and 2 deletions

View File

@ -448,6 +448,20 @@ dword_result_t XamShowSigninUI(dword_t unk, dword_t unk_mask) {
}
DECLARE_XAM_EXPORT1(XamShowSigninUI, kUserProfiles, kStub);
#pragma pack(push, 1)
struct X_XACHIEVEMENT_DETAILS {
xe::be<uint32_t> id;
xe::be<uint32_t> label_ptr;
xe::be<uint32_t> description_ptr;
xe::be<uint32_t> unachieved_ptr;
xe::be<uint32_t> image_id;
xe::be<uint32_t> gamerscore;
xe::be<uint64_t> unlock_time;
xe::be<uint32_t> flags;
};
static_assert_size(X_XACHIEVEMENT_DETAILS, 36);
#pragma pack(pop)
dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id,
dword_t user_index,
dword_t xuid, dword_t flags,
@ -455,14 +469,59 @@ dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id,
lpdword_t buffer_size_ptr,
lpdword_t handle_ptr) {
if (buffer_size_ptr) {
*buffer_size_ptr = 500 * count;
*buffer_size_ptr = sizeof(X_XACHIEVEMENT_DETAILS) * count;
}
auto e = new XStaticEnumerator(kernel_state(), count, 500);
auto e = new XStaticEnumerator(kernel_state(), count,
sizeof(X_XACHIEVEMENT_DETAILS));
e->Initialize();
*handle_ptr = e->handle();
// Copy achievements into the enumerator if game GPD is loaded
auto* game_gpd = kernel_state()->user_profile()->GetTitleGpd();
if (!game_gpd) {
XELOGE(
"XamUserCreateAchievementEnumerator called without GPD being loaded!");
return X_ERROR_SUCCESS;
}
static uint32_t placeholder = 0;
if (!placeholder) {
wchar_t* placeholder_val = L"<placeholder>";
placeholder = kernel_memory()->SystemHeapAlloc(
((uint32_t)wcslen(placeholder_val) + 1) * 2);
auto* place_addr = kernel_memory()->TranslateVirtual<wchar_t*>(placeholder);
memset(place_addr, 0, (wcslen(placeholder_val) + 1) * 2);
xe::copy_and_swap(place_addr, placeholder_val, wcslen(placeholder_val));
}
std::vector<util::XdbfAchievement> achievements;
game_gpd->GetAchievements(&achievements);
for (auto ach : achievements) {
auto* details = (X_XACHIEVEMENT_DETAILS*)e->AppendItem();
details->id = ach.id;
details->image_id = ach.image_id;
details->gamerscore = ach.gamerscore;
details->unlock_time = ach.unlock_time;
details->flags = ach.flags;
// TODO: these, allocating guest mem for them every CreateEnum call would be
// very bad...
// maybe we could alloc these in guest when the title GPD is first loaded?
details->label_ptr = placeholder;
details->description_ptr = placeholder;
details->unachieved_ptr = placeholder;
}
XELOGD("XamUserCreateAchievementEnumerator: added %d items to enumerator",
e->item_count());
return X_ERROR_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamUserCreateAchievementEnumerator, kUserProfiles,