From e510972691e7313b5b8f4c88b5fd0554d038068b Mon Sep 17 00:00:00 2001 From: emoose Date: Fri, 16 Nov 2018 05:50:48 +0000 Subject: [PATCH] [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? --- src/xenia/kernel/xam/xam_user.cc | 63 +++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 03b72ebcd..2e6bb6c1c 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -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 id; + xe::be label_ptr; + xe::be description_ptr; + xe::be unachieved_ptr; + xe::be image_id; + xe::be gamerscore; + xe::be unlock_time; + xe::be 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 = kernel_memory()->SystemHeapAlloc( + ((uint32_t)wcslen(placeholder_val) + 1) * 2); + auto* place_addr = kernel_memory()->TranslateVirtual(placeholder); + + memset(place_addr, 0, (wcslen(placeholder_val) + 1) * 2); + xe::copy_and_swap(place_addr, placeholder_val, wcslen(placeholder_val)); + } + + std::vector 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,