diff --git a/rcheevos b/rcheevos index 40d916de..e9ca3694 160000 --- a/rcheevos +++ b/rcheevos @@ -1 +1 @@ -Subproject commit 40d916de00fe757bab40fb4db41a7912193a48e3 +Subproject commit e9ca3694c862b61235595176dac4b22677848c93 diff --git a/src/services/AchievementRuntimeExports.cpp b/src/services/AchievementRuntimeExports.cpp index bb253a19..3331e26b 100644 --- a/src/services/AchievementRuntimeExports.cpp +++ b/src/services/AchievementRuntimeExports.cpp @@ -369,6 +369,22 @@ class AchievementRuntimeExports : private AchievementRuntime return rc_client_get_subset_info(pClient, subset_id); } + static rc_client_subset_list_info_t* create_subset_list() + { + auto* pClient = ra::services::ServiceLocator::Get().GetClient(); + GSL_SUPPRESS_TYPE1 + auto* list = reinterpret_cast( + rc_client_create_subset_list(pClient)); + list->destroy_func = destroy_subset_list; + return list; + } + + static void get_user_subset_summary(uint32_t subset_id, rc_client_user_game_summary_t* summary) + { + const auto* pClient = ra::services::ServiceLocator::Get().GetClient(); + rc_client_get_user_subset_summary(pClient, subset_id, summary); + } + static void get_user_game_summary(rc_client_user_game_summary_t* summary) { const auto* pClient = ra::services::ServiceLocator::Get().GetClient(); @@ -419,6 +435,13 @@ class AchievementRuntimeExports : private AchievementRuntime return rc_client_get_achievement_info(pClient, id); } + static const rc_client_achievement_t* get_next_achievement_info(uint32_t id, int32_t grouping) + { + auto* pClient = ra::services::ServiceLocator::Get().GetClient(); + auto* pAchievement = id ? rc_client_get_achievement_info(pClient, id) : nullptr; + return rc_client_get_next_achievement_info(pClient, pAchievement, grouping); + } + static rc_client_leaderboard_list_info_t* create_leaderboard_list(int grouping) { auto* pClient = ra::services::ServiceLocator::Get().GetClient(); @@ -983,6 +1006,12 @@ class AchievementRuntimeExports : private AchievementRuntime return 0; } + static void destroy_subset_list(rc_client_subset_list_info_t* list) noexcept + { + if (list) + free(list); + } + static void destroy_achievement_list(rc_client_achievement_list_info_t* list) noexcept { if (list) @@ -1245,6 +1274,31 @@ static void GetExternalClientV4(rc_client_external_t* pClientExternal) noexcept ra::services::AchievementRuntimeExports::set_allow_background_memory_reads; } +static void GetExternalClientV5(rc_client_external_t* pClientExternal) noexcept +{ + // ASSERT: the v5 summary structure is a superset of the v1 summary structures with all + // fields at the same offset, so we can pass pointers to a v5 summary structure to + // the client as either a v5 summary structure or v1 summary structure and the client + // will be able to find the data they're looking for. + pClientExternal->get_user_game_summary_v5 = + ra::services::AchievementRuntimeExports::get_user_game_summary; + + pClientExternal->get_user_subset_summary = + ra::services::AchievementRuntimeExports::get_user_subset_summary; +} + +static void GetExternalClientV6(rc_client_external_t* pClientExternal) noexcept +{ + pClientExternal->create_subset_list = + ra::services::AchievementRuntimeExports::create_subset_list; +} + +static void GetExternalClientV7(rc_client_external_t* pClientExternal) noexcept +{ + pClientExternal->get_next_achievement_info = + ra::services::AchievementRuntimeExports::get_next_achievement_info; +} + #ifdef __cplusplus extern "C" { #endif @@ -1257,6 +1311,18 @@ API int CCONV _Rcheevos_GetExternalClient(rc_client_external_t* pClientExternal, RA_LOG_WARN("Unknown rc_client_external interface version: %s", nVersion); __fallthrough; + case 7: + GetExternalClientV7(pClientExternal); + __fallthrough; + + case 6: + GetExternalClientV6(pClientExternal); + __fallthrough; + + case 5: + GetExternalClientV5(pClientExternal); + __fallthrough; + case 4: GetExternalClientV4(pClientExternal); __fallthrough; diff --git a/tests/services/AchievementRuntimeExports_Tests.cpp b/tests/services/AchievementRuntimeExports_Tests.cpp index 8b843a00..820e74b6 100644 --- a/tests/services/AchievementRuntimeExports_Tests.cpp +++ b/tests/services/AchievementRuntimeExports_Tests.cpp @@ -315,14 +315,14 @@ TEST_CLASS(AchievementRuntimeExports_Tests) } GSL_SUPPRESS_TYPE4 - static void AssertV2Exports(const rc_client_external_t & pClient) + static void AssertV2Exports(const rc_client_external_t & pClient) { Assert::IsNotNull((void*)pClient.add_game_hash, L"add_game_hash not set"); Assert::IsNotNull((void*)pClient.load_unknown_game, L"load_unknown_game not set"); } GSL_SUPPRESS_TYPE4 - static void AssertV3Exports(const rc_client_external_t & pClient) + static void AssertV3Exports(const rc_client_external_t & pClient) { Assert::IsNotNull((void*)pClient.get_user_info_v3, L"get_user_info_v3 not set"); Assert::IsNotNull((void*)pClient.get_game_info_v3, L"get_game_info_v3 not set"); @@ -331,6 +331,34 @@ TEST_CLASS(AchievementRuntimeExports_Tests) Assert::IsNotNull((void*)pClient.create_achievement_list_v3, L"create_achievement_list_v3 not set"); } + GSL_SUPPRESS_TYPE4 + static void AssertV4Exports(const rc_client_external_t& pClient) + { + Assert::IsNotNull((void*)pClient.get_user_info_v3, L"get_user_info_v3 not set"); + Assert::IsNotNull((void*)pClient.get_game_info_v3, L"get_game_info_v3 not set"); + Assert::IsNotNull((void*)pClient.get_subset_info_v3, L"get_subset_info_v3 not set"); + Assert::IsNotNull((void*)pClient.get_achievement_info_v3, L"get_achievement_info_v3 not set"); + Assert::IsNotNull((void*)pClient.create_achievement_list_v3, L"create_achievement_list_v3 not set"); + } + + GSL_SUPPRESS_TYPE4 + static void AssertV5Exports(const rc_client_external_t& pClient) + { + Assert::IsNotNull((void*)pClient.get_user_game_summary_v5, L"get_user_game_summary_v5 not set"); + Assert::IsNotNull((void*)pClient.get_user_subset_summary, L"get_user_subset_summary not set"); + } + + GSL_SUPPRESS_TYPE4 + static void AssertV6Exports(const rc_client_external_t& pClient) + { + Assert::IsNotNull((void*)pClient.create_subset_list, L"create_subset_list not set"); + } + + GSL_SUPPRESS_TYPE4 + static void AssertV7Exports(const rc_client_external_t& pClient) + { + Assert::IsNotNull((void*)pClient.get_next_achievement_info, L"get_next_achievement_info not set"); + } public: TEST_METHOD(TestGetExternalClientV1) { @@ -377,6 +405,80 @@ TEST_CLASS(AchievementRuntimeExports_Tests) Assert::IsTrue(IsExternalRcheevosClient()); } + TEST_METHOD(TestGetExternalClientV4) + { + AchievementRuntimeExportsHarness runtime; + + rc_client_external_t pClient; + memset(&pClient, 0, sizeof(pClient)); + + _Rcheevos_GetExternalClient(&pClient, 4); + + AssertV1Exports(pClient); + AssertV2Exports(pClient); + AssertV3Exports(pClient); + AssertV4Exports(pClient); + + Assert::IsTrue(IsExternalRcheevosClient()); + } + + TEST_METHOD(TestGetExternalClientV5) + { + AchievementRuntimeExportsHarness runtime; + + rc_client_external_t pClient; + memset(&pClient, 0, sizeof(pClient)); + + _Rcheevos_GetExternalClient(&pClient, 5); + + AssertV1Exports(pClient); + AssertV2Exports(pClient); + AssertV3Exports(pClient); + AssertV4Exports(pClient); + AssertV5Exports(pClient); + + Assert::IsTrue(IsExternalRcheevosClient()); + } + + TEST_METHOD(TestGetExternalClientV6) + { + AchievementRuntimeExportsHarness runtime; + + rc_client_external_t pClient; + memset(&pClient, 0, sizeof(pClient)); + + _Rcheevos_GetExternalClient(&pClient, 6); + + AssertV1Exports(pClient); + AssertV2Exports(pClient); + AssertV3Exports(pClient); + AssertV4Exports(pClient); + AssertV5Exports(pClient); + AssertV6Exports(pClient); + + Assert::IsTrue(IsExternalRcheevosClient()); + } + + TEST_METHOD(TestGetExternalClientV7) + { + AchievementRuntimeExportsHarness runtime; + + rc_client_external_t pClient; + memset(&pClient, 0, sizeof(pClient)); + + _Rcheevos_GetExternalClient(&pClient, 7); + + AssertV1Exports(pClient); + AssertV2Exports(pClient); + AssertV3Exports(pClient); + AssertV4Exports(pClient); + AssertV5Exports(pClient); + AssertV6Exports(pClient); + AssertV7Exports(pClient); + + Assert::IsTrue(IsExternalRcheevosClient()); + } + TEST_METHOD(TestRAIntegrationGetMenu) { AchievementRuntimeExportsHarness runtime;