diff --git a/rcheevos b/rcheevos index c6887b29..64fcb9ea 160000 --- a/rcheevos +++ b/rcheevos @@ -1 +1 @@ -Subproject commit c6887b295657a742b686dfdc2f14023a4cd07773 +Subproject commit 64fcb9ea3cf990e65343057ace9271ff3b77428e diff --git a/src/api/impl/ConnectedServer.cpp b/src/api/impl/ConnectedServer.cpp index ab8eccba..3e56133b 100644 --- a/src/api/impl/ConnectedServer.cpp +++ b/src/api/impl/ConnectedServer.cpp @@ -346,7 +346,7 @@ static bool DoRequest(const rc_api_request_t& api_request, const char* sApiName, if (!sParams.empty()) sParams.push_back('&'); - sParams.append(api_request.post_data, ptr - api_request.post_data - 1); + sParams.append(api_request.post_data, ptr - api_request.post_data); } while (*ptr && ptr[0] != '&') diff --git a/src/data/context/GameContext.cpp b/src/data/context/GameContext.cpp index a89b6aa0..b53bd4f6 100644 --- a/src/data/context/GameContext.cpp +++ b/src/data/context/GameContext.cpp @@ -145,9 +145,6 @@ void GameContext::LoadGame(unsigned int nGameId, const std::string& sGameHash, M } } - // enable spectator mode to prevent unlocks when testing compatibility - rc_client_set_spectator_mode_enabled(pRuntime.GetClient(), nMode == Mode::CompatibilityTest); - const bool bWasPaused = pRuntime.IsPaused(); pRuntime.SetPaused(true); @@ -322,6 +319,15 @@ void GameContext::InitializeFromAchievementRuntime(const std::mappublic_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE; + + if (pAchievementData->trigger) + pAchievementData->trigger->state = RC_TRIGGER_STATE_WAITING; + } break; case RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL: diff --git a/src/data/models/CodeNotesModel.cpp b/src/data/models/CodeNotesModel.cpp index 905e7c5c..22a8f4c1 100644 --- a/src/data/models/CodeNotesModel.cpp +++ b/src/data/models/CodeNotesModel.cpp @@ -110,7 +110,12 @@ void CodeNotesModel::ExtractSize(CodeNote& pNote) const wchar_t c = (nIndex == nLength) ? 0 : pNote.Note.at(nIndex); // find the next word - if (isalpha(c)) + if (c > 255) + { + // ignore unicode characters - isalpha with the default locale would return false, + // but also likes to pop up asserts when in a debug build. + } + else if (isalpha(c)) { if (sWord.empty()) { @@ -252,7 +257,7 @@ void CodeNotesModel::ExtractSize(CodeNote& pNote) bLastWordIsSize = bWordIsSize; bLastWordIsNumber = bWordIsNumber; - if (isalnum(c)) + if (c < 256 && isalnum(c)) { std::swap(sPreviousWord, sWord); sWord.clear(); diff --git a/src/services/AchievementRuntime.cpp b/src/services/AchievementRuntime.cpp index 38ad3ec5..11c1ae62 100644 --- a/src/services/AchievementRuntime.cpp +++ b/src/services/AchievementRuntime.cpp @@ -20,6 +20,7 @@ #include "services\IHttpRequester.hh" #include "services\ILocalStorage.hh" #include "services\ServiceLocator.hh" +#include "services\impl\JsonFileConfiguration.hh" #include "ui\viewmodels\MessageBoxViewModel.hh" #include "ui\viewmodels\OverlayManager.hh" @@ -987,6 +988,24 @@ rc_client_async_handle_t* AchievementRuntime::BeginLoadGame(const char* sHash, u return rc_client_begin_load_game(client, sHash, AchievementRuntime::LoadGameCallback, pCallbackWrapper); } +rc_client_async_handle_t* AchievementRuntime::BeginIdentifyAndLoadGame(uint32_t console_id, const char* file_path, + const uint8_t* data, size_t data_size, + CallbackWrapper* pCallbackWrapper) noexcept +{ + auto* client = GetClient(); + + // unload the game and free any additional memory we allocated for local changes + rc_client_unload_game(client); + m_pClientSynchronizer.reset(); + s_mAchievementPopups.clear(); + + // start the load process + client->callbacks.post_process_game_data_response = PostProcessGameDataResponse; + + return rc_client_begin_identify_and_load_game(client, console_id, file_path, data, data_size, + AchievementRuntime::LoadGameCallback, pCallbackWrapper); +} + GSL_SUPPRESS_CON3 void AchievementRuntime::LoadGameCallback(int nResult, const char* sErrorMessage, rc_client_t*, void* pUserdata) { @@ -1009,6 +1028,28 @@ void AchievementRuntime::LoadGameCallback(int nResult, const char* sErrorMessage delete wrapper; } +rc_client_async_handle_t* AchievementRuntime::BeginChangeMedia(const char* file_path, + const uint8_t* data, size_t data_size, CallbackWrapper* pCallbackWrapper) noexcept +{ + auto* client = GetClient(); + return rc_client_begin_change_media(client, file_path, data, data_size, AchievementRuntime::ChangeMediaCallback, pCallbackWrapper); +} + +GSL_SUPPRESS_CON3 +void AchievementRuntime::ChangeMediaCallback(int nResult, const char* sErrorMessage, rc_client_t*, void* pUserdata) +{ + auto* wrapper = static_cast(pUserdata); + Expects(wrapper != nullptr); + + if (nResult == RC_HARDCORE_DISABLED) + ra::services::ServiceLocator::GetMutable().DisableHardcoreMode(); + + wrapper->DoCallback(nResult, sErrorMessage); + + delete wrapper; +} + + /* ---- DoFrame ----- */ static void PrepareForPauseOnChangeEvents(rc_client_t* pClient, @@ -2284,15 +2325,127 @@ int AchievementRuntime::SaveProgressToBuffer(uint8_t* pBuffer, int nBufferSize) /* ---- Exports ----- */ -#ifdef RC_CLIENT_SUPPORTS_EXTERNAL +#ifdef RC_CLIENT_EXPORTS_EXTERNAL class AchievementRuntimeExports : private AchievementRuntime { public: + static void destroy() noexcept + { + memset(&s_callbacks, 0, sizeof(s_callbacks)); + } + + static void enable_logging(rc_client_t* client, int level, rc_client_message_callback_t callback) + { + s_callbacks.log_callback = callback; + s_callbacks.log_client = client; + + auto& pClient = ra::services::ServiceLocator::GetMutable(); + rc_client_enable_logging(pClient.GetClient(), level, AchievementRuntimeExports::LogMessageExternal); + } + + static void set_event_handler(rc_client_t* client, rc_client_event_handler_t handler) + { + s_callbacks.event_handler = handler; + s_callbacks.event_client = client; + + auto& pClient = ra::services::ServiceLocator::GetMutable(); + rc_client_set_event_handler(pClient.GetClient(), AchievementRuntimeExports::EventHandlerExternal); + } + + static void set_read_memory(rc_client_t* client, rc_client_read_memory_func_t handler) + { + s_callbacks.read_memory_handler = handler; + s_callbacks.read_memory_client = client; + + auto& pClient = ra::services::ServiceLocator::GetMutable(); + rc_client_set_read_memory_function(pClient.GetClient(), AchievementRuntimeExports::ReadMemoryExternal); + + auto& pEmulatorContext = ra::services::ServiceLocator::GetMutable(); + pEmulatorContext.ClearMemoryBlocks(); + pEmulatorContext.AddMemoryBlock(0, 0xFFFFFFFF, nullptr, nullptr); + pEmulatorContext.AddMemoryBlockReader(0, AchievementRuntimeExports::ReadMemoryBlock); + } + + static void set_get_time_millisecs(rc_client_t* client, rc_get_time_millisecs_func_t handler) + { + s_callbacks.get_time_millisecs_handler = handler; + s_callbacks.get_time_millisecs_client = client; + + auto& pClient = ra::services::ServiceLocator::GetMutable(); + rc_client_set_get_time_millisecs_function(pClient.GetClient(), AchievementRuntimeExports::GetTimeMillisecsExternal); + } + + static void set_host(const char* value) + { + auto* pConfiguration = dynamic_cast( + &ra::services::ServiceLocator::GetMutable()); + if (pConfiguration != nullptr) + pConfiguration->SetHost(value); + } + + static void set_hardcore_enabled(int value) + { + auto& pEmulatorContext = ra::services::ServiceLocator::GetMutable(); + if (value) + pEmulatorContext.EnableHardcoreMode(false); + else + pEmulatorContext.DisableHardcoreMode(); + } + + static int get_hardcore_enabled() + { + const auto& pConfiguration = ra::services::ServiceLocator::Get(); + return pConfiguration.IsFeatureEnabled(ra::services::Feature::Hardcore); + } + + static void set_unofficial_enabled(int value) + { + auto& pClient = ra::services::ServiceLocator::GetMutable(); + rc_client_set_unofficial_enabled(pClient.GetClient(), value); + } + + static int get_unofficial_enabled() + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_get_unofficial_enabled(pClient.GetClient()); + } + + static void set_encore_mode_enabled(int value) + { + auto& pClient = ra::services::ServiceLocator::GetMutable(); + rc_client_set_encore_mode_enabled(pClient.GetClient(), value); + } + + static int get_encore_mode_enabled() + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_get_encore_mode_enabled(pClient.GetClient()); + } + + static void set_spectator_mode_enabled(int value) + { + auto& pClient = ra::services::ServiceLocator::GetMutable(); + rc_client_set_spectator_mode_enabled(pClient.GetClient(), value); + } + + static int get_spectator_mode_enabled() + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_get_spectator_mode_enabled(pClient.GetClient()); + } + + static void abort_async(rc_client_async_handle_t* handle) + { + const auto& pClient = ra::services::ServiceLocator::Get(); + rc_client_abort_async(pClient.GetClient(), handle); + } + static rc_client_async_handle_t* begin_login_with_password(rc_client_t* client, const char* username, const char* password, rc_client_callback_t callback, void* callback_userdata) { + GSL_SUPPRESS_R3 auto* pCallbackData = new CallbackWrapper(client, callback, callback_userdata); auto& pClient = ra::services::ServiceLocator::GetMutable(); @@ -2303,10 +2456,11 @@ class AchievementRuntimeExports : private AchievementRuntime const char* token, rc_client_callback_t callback, void* callback_userdata) { - auto* pCallbackData = new CallbackWrapper(client, callback, callback_userdata); + GSL_SUPPRESS_R3 + auto* pCallbackWrapper = new LoadGameCallbackWrapper(client, callback, callback_userdata); auto& pClient = ra::services::ServiceLocator::GetMutable(); - return pClient.BeginLoginWithToken(username, token, pCallbackData); + return pClient.BeginLoginWithToken(username, token, pCallbackWrapper); } static const rc_client_user_t* get_user_info() @@ -2320,9 +2474,312 @@ class AchievementRuntimeExports : private AchievementRuntime auto& pClient = ra::services::ServiceLocator::GetMutable(); return rc_client_logout(pClient.GetClient()); } + + static rc_client_async_handle_t* begin_identify_and_load_game(rc_client_t* client, uint32_t console_id, + const char* file_path, const uint8_t* data, + size_t data_size, rc_client_callback_t callback, + void* callback_userdata) + { + GSL_SUPPRESS_R3 + auto* pCallbackWrapper = new LoadGameCallbackWrapper(client, callback, callback_userdata); + + auto& pClient = ra::services::ServiceLocator::GetMutable(); + return pClient.BeginIdentifyAndLoadGame(console_id, file_path, data, data_size, pCallbackWrapper); + } + + static rc_client_async_handle_t* begin_load_game(rc_client_t* client, const char* hash, + rc_client_callback_t callback, void* callback_userdata) + { + GSL_SUPPRESS_R3 + auto* pCallbackData = new CallbackWrapper(client, callback, callback_userdata); + + auto& pClient = ra::services::ServiceLocator::GetMutable(); + return pClient.BeginLoadGame(hash, 0, pCallbackData); + } + + static void unload_game() + { + auto& pClient = ra::services::ServiceLocator::GetMutable(); + return rc_client_unload_game(pClient.GetClient()); + } + + static const rc_client_game_t* get_game_info() + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_get_game_info(pClient.GetClient()); + } + + static const rc_client_subset_t* get_subset_info(uint32_t subset_id) + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_get_subset_info(pClient.GetClient(), subset_id); + } + + static void get_user_game_summary(rc_client_user_game_summary_t* summary) + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_get_user_game_summary(pClient.GetClient(), summary); + } + + static rc_client_async_handle_t* begin_change_media(rc_client_t* client, const char* file_path, + const uint8_t* data, size_t data_size, + rc_client_callback_t callback, void* callback_userdata) + { + GSL_SUPPRESS_R3 + auto* pCallbackData = new CallbackWrapper(client, callback, callback_userdata); + + auto& pClient = ra::services::ServiceLocator::GetMutable(); + return pClient.BeginChangeMedia(file_path, data, data_size, pCallbackData); + } + + static rc_client_achievement_list_info_t* create_achievement_list(int category, int grouping) + { + auto& pClient = ra::services::ServiceLocator::GetMutable(); + GSL_SUPPRESS_TYPE1 + auto* list = reinterpret_cast( + rc_client_create_achievement_list(pClient.GetClient(), category, grouping)); + list->destroy_func = destroy_achievement_list; + return list; + } + + static int has_achievements() + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_has_achievements(pClient.GetClient()); + } + + static const rc_client_achievement_t* get_achievement_info(uint32_t id) + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_get_achievement_info(pClient.GetClient(), id); + } + + static rc_client_leaderboard_list_info_t* create_leaderboard_list(int grouping) + { + auto& pClient = ra::services::ServiceLocator::GetMutable(); + GSL_SUPPRESS_TYPE1 + auto* list = reinterpret_cast( + rc_client_create_leaderboard_list(pClient.GetClient(), grouping)); + list->destroy_func = destroy_leaderboard_list; + return list; + } + + static int has_leaderboards() + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_has_leaderboards(pClient.GetClient()); + } + + static const rc_client_leaderboard_t* get_leaderboard_info(uint32_t id) + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_get_leaderboard_info(pClient.GetClient(), id); + } + +private: + class LeaderboardEntriesListCallbackWrapper + { + public: + LeaderboardEntriesListCallbackWrapper(rc_client_t* client, + rc_client_fetch_leaderboard_entries_callback_t callback, + void* callback_userdata) noexcept : + m_pClient(client), m_fCallback(callback), m_pCallbackUserdata(callback_userdata) + {} + + void DoCallback(int nResult, rc_client_leaderboard_entry_list_t* pList, const char* sErrorMessage) noexcept + { + m_fCallback(nResult, sErrorMessage, pList, m_pClient, m_pCallbackUserdata); + } + + static void Dispatch(int nResult, const char* sErrorMessage, rc_client_leaderboard_entry_list_t* pList, + rc_client_t*, void* pUserdata) + { + auto* wrapper = static_cast(pUserdata); + Expects(wrapper != nullptr); + + if (pList) + { + GSL_SUPPRESS_TYPE1 + reinterpret_cast(pList)->destroy_func = destroy_leaderboard_entry_list; + } + + wrapper->DoCallback(nResult, pList, sErrorMessage); + + delete wrapper; + } + + private: + rc_client_t* m_pClient; + rc_client_fetch_leaderboard_entries_callback_t m_fCallback; + void* m_pCallbackUserdata; + }; + +public: + static rc_client_async_handle_t* begin_fetch_leaderboard_entries(rc_client_t* client, + uint32_t leaderboard_id, uint32_t first_entry, uint32_t count, + rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata) + { + GSL_SUPPRESS_R3 + auto* pCallbackData = new LeaderboardEntriesListCallbackWrapper(client, callback, callback_userdata); + + auto& pClient = ra::services::ServiceLocator::GetMutable(); + return rc_client_begin_fetch_leaderboard_entries(pClient.GetClient(), leaderboard_id, first_entry, count, + LeaderboardEntriesListCallbackWrapper::Dispatch, pCallbackData); + } + + static rc_client_async_handle_t* begin_fetch_leaderboard_entries_around_user(rc_client_t* client, + uint32_t leaderboard_id, uint32_t count, + rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata) + { + GSL_SUPPRESS_R3 + auto* pCallbackData = new LeaderboardEntriesListCallbackWrapper(client, callback, callback_userdata); + + auto& pClient = ra::services::ServiceLocator::GetMutable(); + return rc_client_begin_fetch_leaderboard_entries_around_user(pClient.GetClient(), leaderboard_id, count, + LeaderboardEntriesListCallbackWrapper::Dispatch, pCallbackData); + } + + static size_t get_rich_presence_message(char buffer[], size_t buffer_size) + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_get_rich_presence_message(pClient.GetClient(), buffer, buffer_size); + } + + static int has_rich_presence() + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_has_rich_presence(pClient.GetClient()); + } + + static void do_frame() noexcept + { + _RA_DoAchievementsFrame(); + } + + static void idle() + { + auto& pClient = ra::services::ServiceLocator::GetMutable(); + return rc_client_idle(pClient.GetClient()); + } + + static int is_processing_required() + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_is_processing_required(pClient.GetClient()); + } + + static int can_pause(uint32_t* frames_remaining) + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_can_pause(pClient.GetClient(), frames_remaining); + } + + static void reset() noexcept + { +#ifndef RA_UTEST + _RA_OnReset(); +#endif + } + + static size_t progress_size() + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_progress_size(pClient.GetClient()); + } + + static int serialize_progress(uint8_t* buffer) + { + const auto& pClient = ra::services::ServiceLocator::Get(); + return rc_client_serialize_progress(pClient.GetClient(), buffer); + } + + static int deserialize_progress(const uint8_t* buffer) + { + auto& pClient = ra::services::ServiceLocator::GetMutable(); + return rc_client_deserialize_progress(pClient.GetClient(), buffer); + } + +private: + typedef struct ExternalClientCallbacks + { + rc_client_message_callback_t log_callback; + rc_client_t* log_client; + + rc_client_event_handler_t event_handler; + rc_client_t* event_client; + + rc_client_read_memory_func_t read_memory_handler; + rc_client_t* read_memory_client; + + rc_get_time_millisecs_func_t get_time_millisecs_handler; + rc_client_t* get_time_millisecs_client; + + } ExternalClientCallbacks; + + static ExternalClientCallbacks s_callbacks; + + static void LogMessageExternal(const char* sMessage, const rc_client_t*) + { + const auto& pLogger = ra::services::ServiceLocator::Get(); + if (pLogger.IsEnabled(ra::services::LogLevel::Info)) + pLogger.LogMessage(ra::services::LogLevel::Info, sMessage); + + if (s_callbacks.log_callback) + s_callbacks.log_callback(sMessage, s_callbacks.log_client); + } + + static void EventHandlerExternal(const rc_client_event_t* event, rc_client_t*) noexcept(false) + { + if (s_callbacks.event_handler) + s_callbacks.event_handler(event, s_callbacks.event_client); + } + + static uint32_t ReadMemoryBlock(uint32_t address, uint8_t* buffer, uint32_t num_bytes) noexcept(false) + { + if (s_callbacks.read_memory_handler) + return s_callbacks.read_memory_handler(address, buffer, num_bytes, s_callbacks.read_memory_client); + + return 0; + } + + static uint32_t ReadMemoryExternal(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t*) noexcept(false) + { + if (s_callbacks.read_memory_handler) + return s_callbacks.read_memory_handler(address, buffer, num_bytes, s_callbacks.read_memory_client); + + return 0; + } + + static rc_clock_t GetTimeMillisecsExternal(const rc_client_t*) noexcept(false) + { + if (s_callbacks.get_time_millisecs_handler) + return s_callbacks.get_time_millisecs_handler(s_callbacks.get_time_millisecs_client); + + return 0; + } + + static void destroy_achievement_list(rc_client_achievement_list_info_t* list) noexcept + { + if (list) + free(list); + } + + static void destroy_leaderboard_list(rc_client_leaderboard_list_info_t* list) noexcept + { + if (list) + free(list); + } + + static void destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_info_t* list) noexcept + { + if (list) + free(list); + } }; -#endif RC_CLIENT_SUPPORTS_EXTERNAL +AchievementRuntimeExports::ExternalClientCallbacks AchievementRuntimeExports::s_callbacks{}; + +#endif RC_CLIENT_EXPORTS_EXTERNAL } // namespace services } // namespace ra @@ -2343,16 +2800,75 @@ extern "C" unsigned int rc_peek_callback(unsigned int nAddress, unsigned int nBy } } -#ifdef RC_CLIENT_SUPPORTS_EXTERNAL +#ifdef RC_CLIENT_EXPORTS_EXTERNAL #include "Exports.hh" -#include "rcheevos/src/rcheevos/rc_client_external.h" +#include "rcheevos/src/rc_client_external.h" #ifdef __cplusplus extern "C" { #endif -API int CCONV _Rcheevos_GetExternalClient(rc_client_external_t* pClient, int nVersion) +static void GetExternalClientV1(rc_client_external_t* pClientExternal) +{ + pClientExternal->destroy = ra::services::AchievementRuntimeExports::destroy; + + pClientExternal->enable_logging = ra::services::AchievementRuntimeExports::enable_logging; + pClientExternal->set_event_handler = ra::services::AchievementRuntimeExports::set_event_handler; + pClientExternal->set_read_memory = ra::services::AchievementRuntimeExports::set_read_memory; + pClientExternal->set_get_time_millisecs = ra::services::AchievementRuntimeExports::set_get_time_millisecs; + pClientExternal->set_host = ra::services::AchievementRuntimeExports::set_host; + + pClientExternal->set_hardcore_enabled = ra::services::AchievementRuntimeExports::set_hardcore_enabled; + pClientExternal->get_hardcore_enabled = ra::services::AchievementRuntimeExports::get_hardcore_enabled; + pClientExternal->set_unofficial_enabled = ra::services::AchievementRuntimeExports::set_unofficial_enabled; + pClientExternal->get_unofficial_enabled = ra::services::AchievementRuntimeExports::get_unofficial_enabled; + pClientExternal->set_encore_mode_enabled = ra::services::AchievementRuntimeExports::set_encore_mode_enabled; + pClientExternal->get_encore_mode_enabled = ra::services::AchievementRuntimeExports::get_encore_mode_enabled; + pClientExternal->set_spectator_mode_enabled = ra::services::AchievementRuntimeExports::set_spectator_mode_enabled; + pClientExternal->get_spectator_mode_enabled = ra::services::AchievementRuntimeExports::get_spectator_mode_enabled; + + pClientExternal->abort_async = ra::services::AchievementRuntimeExports::abort_async; + + pClientExternal->begin_login_with_password = ra::services::AchievementRuntimeExports::begin_login_with_password; + pClientExternal->begin_login_with_token = ra::services::AchievementRuntimeExports::begin_login_with_token; + pClientExternal->logout = ra::services::AchievementRuntimeExports::logout; + pClientExternal->get_user_info = ra::services::AchievementRuntimeExports::get_user_info; + + pClientExternal->begin_identify_and_load_game = ra::services::AchievementRuntimeExports::begin_identify_and_load_game; + pClientExternal->begin_load_game = ra::services::AchievementRuntimeExports::begin_load_game; + pClientExternal->get_game_info = ra::services::AchievementRuntimeExports::get_game_info; + pClientExternal->get_subset_info = ra::services::AchievementRuntimeExports::get_subset_info; + pClientExternal->unload_game = ra::services::AchievementRuntimeExports::unload_game; + pClientExternal->get_user_game_summary = ra::services::AchievementRuntimeExports::get_user_game_summary; + pClientExternal->begin_change_media = ra::services::AchievementRuntimeExports::begin_change_media; + + pClientExternal->create_achievement_list = ra::services::AchievementRuntimeExports::create_achievement_list; + pClientExternal->has_achievements = ra::services::AchievementRuntimeExports::has_achievements; + pClientExternal->get_achievement_info = ra::services::AchievementRuntimeExports::get_achievement_info; + + pClientExternal->create_leaderboard_list = ra::services::AchievementRuntimeExports::create_leaderboard_list; + pClientExternal->has_leaderboards = ra::services::AchievementRuntimeExports::has_leaderboards; + pClientExternal->get_leaderboard_info = ra::services::AchievementRuntimeExports::get_leaderboard_info; + pClientExternal->begin_fetch_leaderboard_entries = ra::services::AchievementRuntimeExports::begin_fetch_leaderboard_entries; + pClientExternal->begin_fetch_leaderboard_entries_around_user = + ra::services::AchievementRuntimeExports::begin_fetch_leaderboard_entries_around_user; + + pClientExternal->get_rich_presence_message = ra::services::AchievementRuntimeExports::get_rich_presence_message; + pClientExternal->has_rich_presence = ra::services::AchievementRuntimeExports::has_rich_presence; + + pClientExternal->do_frame = ra::services::AchievementRuntimeExports::do_frame; + pClientExternal->idle = ra::services::AchievementRuntimeExports::idle; + pClientExternal->is_processing_required = ra::services::AchievementRuntimeExports::is_processing_required; + pClientExternal->can_pause = ra::services::AchievementRuntimeExports::can_pause; + pClientExternal->reset = ra::services::AchievementRuntimeExports::reset; + + pClientExternal->progress_size = ra::services::AchievementRuntimeExports::progress_size; + pClientExternal->serialize_progress = ra::services::AchievementRuntimeExports::serialize_progress; + pClientExternal->deserialize_progress = ra::services::AchievementRuntimeExports::deserialize_progress; +} + +API int CCONV _Rcheevos_GetExternalClient(rc_client_external_t* pClientExternal, int nVersion) { switch (nVersion) { @@ -2361,10 +2877,7 @@ API int CCONV _Rcheevos_GetExternalClient(rc_client_external_t* pClient, int nVe __fallthrough; case 1: - pClient->begin_login_with_password = ra::services::AchievementRuntimeExports::begin_login_with_password; - pClient->begin_login_with_token = ra::services::AchievementRuntimeExports::begin_login_with_token; - pClient->logout = ra::services::AchievementRuntimeExports::logout; - pClient->get_user_info = ra::services::AchievementRuntimeExports::get_user_info; + GetExternalClientV1(pClientExternal); break; } @@ -2375,4 +2888,4 @@ API int CCONV _Rcheevos_GetExternalClient(rc_client_external_t* pClient, int nVe } // extern "C" #endif -#endif /* RC_CLIENT_SUPPORTS_EXTERNAL */ +#endif /* RC_CLIENT_EXPORTS_EXTERNAL */ diff --git a/src/services/AchievementRuntime.hh b/src/services/AchievementRuntime.hh index 392912f6..5c801c85 100644 --- a/src/services/AchievementRuntime.hh +++ b/src/services/AchievementRuntime.hh @@ -13,6 +13,8 @@ #include +#define RC_CLIENT_EXPORTS_EXTERNAL + #include struct rc_api_fetch_game_data_response_t; @@ -260,6 +262,14 @@ private: rc_client_async_handle_t* BeginLoadGame(const char* sHash, unsigned id, CallbackWrapper* pCallbackWrapper) noexcept; static void LoadGameCallback(int nResult, const char* sErrorMessage, rc_client_t* pClient, void* pUserdata); + rc_client_async_handle_t* BeginIdentifyAndLoadGame(uint32_t console_id, const char* file_path, + const uint8_t* data, size_t data_size, + CallbackWrapper* pCallbackWrapper) noexcept; + + rc_client_async_handle_t* BeginChangeMedia(const char* file_path, const uint8_t* data, size_t data_size, + CallbackWrapper* pCallbackWrapper) noexcept; + static void ChangeMediaCallback(int nResult, const char* sErrorMessage, rc_client_t*, void* pUserdata); + static void PostProcessGameDataResponse(const rc_api_server_response_t* server_response, struct rc_api_fetch_game_data_response_t* game_data_response, rc_client_t* client, void* pUserdata); diff --git a/src/services/impl/JsonFileConfiguration.cpp b/src/services/impl/JsonFileConfiguration.cpp index 797eb869..7575dc00 100644 --- a/src/services/impl/JsonFileConfiguration.cpp +++ b/src/services/impl/JsonFileConfiguration.cpp @@ -301,15 +301,21 @@ ra::ui::Size JsonFileConfiguration::GetWindowSize(const std::string & sPositionK return ra::ui::Size{ INT32_MIN, INT32_MIN }; } -void JsonFileConfiguration::SetWindowSize(const std::string & sPositionKey, const ra::ui::Size & oSize) +void JsonFileConfiguration::SetWindowSize(const std::string& sPositionKey, const ra::ui::Size& oSize) { m_mWindowPositions[sPositionKey].oSize = oSize; } +void JsonFileConfiguration::SetHost(const std::string& sHost) +{ + m_sHostName = sHost; + UpdateHost(); +} + const std::string& JsonFileConfiguration::GetHostName() const { if (m_sHostName.empty()) - GSL_SUPPRESS_TYPE3 const_cast(this)->UpdateHost(); + GSL_SUPPRESS_TYPE3 const_cast(this)->ReadHostFile(); return m_sHostName; } @@ -317,7 +323,7 @@ const std::string& JsonFileConfiguration::GetHostName() const const std::string& JsonFileConfiguration::GetHostUrl() const { if (m_sHostUrl.empty()) - GSL_SUPPRESS_TYPE3 const_cast(this)->UpdateHost(); + GSL_SUPPRESS_TYPE3 const_cast(this)->ReadHostFile(); return m_sHostUrl; } @@ -325,12 +331,12 @@ const std::string& JsonFileConfiguration::GetHostUrl() const const std::string& JsonFileConfiguration::GetImageHostUrl() const { if (m_sImageHostUrl.empty()) - GSL_SUPPRESS_TYPE3 const_cast(this)->UpdateHost(); + GSL_SUPPRESS_TYPE3 const_cast(this)->ReadHostFile(); return m_sImageHostUrl; } -void JsonFileConfiguration::UpdateHost() +void JsonFileConfiguration::ReadHostFile() { const auto& pFileSystem = ra::services::ServiceLocator::Get(); if (pFileSystem.GetFileSize(L"host.txt") > 0) @@ -340,8 +346,14 @@ void JsonFileConfiguration::UpdateHost() pFile->GetLine(m_sHostName); } + UpdateHost(); +} + +void JsonFileConfiguration::UpdateHost() +{ if (m_sHostName.empty()) { + m_bCustomHost = false; m_sHostName = "retroachievements.org"; m_sHostUrl = "https://retroachievements.org"; m_sImageHostUrl = "http://i.retroachievements.org"; diff --git a/src/services/impl/JsonFileConfiguration.hh b/src/services/impl/JsonFileConfiguration.hh index ff375972..271cb1ed 100644 --- a/src/services/impl/JsonFileConfiguration.hh +++ b/src/services/impl/JsonFileConfiguration.hh @@ -38,6 +38,7 @@ public: ra::ui::Size GetWindowSize(const std::string& sPositionKey) const override; void SetWindowSize(const std::string& sPositionKey, const ra::ui::Size& oSize) override; + void SetHost(const std::string& sHost); bool IsCustomHost() const noexcept override { return m_bCustomHost; } const std::string& GetHostName() const override; const std::string& GetHostUrl() const override; @@ -46,6 +47,7 @@ public: void Save() const override; private: + void ReadHostFile(); void UpdateHost(); std::string m_sUsername; diff --git a/src/services/impl/WindowsHttpRequester.cpp b/src/services/impl/WindowsHttpRequester.cpp index ba2544ed..25950d71 100644 --- a/src/services/impl/WindowsHttpRequester.cpp +++ b/src/services/impl/WindowsHttpRequester.cpp @@ -157,6 +157,7 @@ unsigned int WindowsHttpRequester::Request(const Http::Request& pRequest, TextWr // open the connection auto sPathWide = ra::Widen(sPath); + GSL_SUPPRESS_ES47 HINTERNET hRequest = WinHttpOpenRequest(hConnect, sPostData.empty() ? L"GET" : L"POST", sPathWide.c_str(), @@ -185,6 +186,7 @@ unsigned int WindowsHttpRequester::Request(const Http::Request& pRequest, TextWr // send the request if (sPostData.empty()) { + GSL_SUPPRESS_ES47 bResults = WinHttpSendRequest(hRequest, sHeaders.c_str(), gsl::narrow_cast(sHeaders.length()), WINHTTP_NO_REQUEST_DATA, @@ -195,7 +197,7 @@ unsigned int WindowsHttpRequester::Request(const Http::Request& pRequest, TextWr { bResults = WinHttpSendRequest(hRequest, sHeaders.c_str(), gsl::narrow_cast(sHeaders.length()), - static_cast(sPostData.data()), + sPostData.data(), gsl::narrow_cast(sPostData.length()), gsl::narrow_cast(sPostData.length()), 0); } @@ -319,7 +321,7 @@ std::string WindowsHttpRequester::GetStatusCodeText(unsigned int nStatusCode) co const DWORD nResult = ::FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, ::GetModuleHandle(TEXT("winhttp.dll")), nStatusCode, 0, (LPTSTR)szMessageBuffer, - sizeof(szMessageBuffer)/sizeof(szMessageBuffer[0]), NULL); + sizeof(szMessageBuffer)/sizeof(szMessageBuffer[0]), nullptr); if (nResult > 0) { diff --git a/src/ui/drawing/gdi/GDISurface.cpp b/src/ui/drawing/gdi/GDISurface.cpp index 3c45cab1..f0285bb6 100644 --- a/src/ui/drawing/gdi/GDISurface.cpp +++ b/src/ui/drawing/gdi/GDISurface.cpp @@ -125,7 +125,7 @@ void GDISurface::DrawSurface(int nX, int nY, const ISurface& pSurface) if (pGDISurface != nullptr) { ::BitBlt(m_hDC, nX, nY, - static_cast(pSurface.GetWidth()), static_cast(pSurface.GetHeight()), + gsl::narrow_cast(pSurface.GetWidth()), gsl::narrow_cast(pSurface.GetHeight()), pGDISurface->m_hDC, 0, 0, SRCCOPY); } } diff --git a/src/ui/drawing/gdi/ImageRepository.cpp b/src/ui/drawing/gdi/ImageRepository.cpp index 7b4f7f10..5051e414 100644 --- a/src/ui/drawing/gdi/ImageRepository.cpp +++ b/src/ui/drawing/gdi/ImageRepository.cpp @@ -39,6 +39,7 @@ bool ImageRepository::Initialize() if (SUCCEEDED(hr) || hr == RPC_E_CHANGED_MODE) { + GSL_SUPPRESS_TYPE1 hr = CoCreateInstance( #if defined (__cplusplus) diff --git a/tests/mocks/MockAchievementRuntime.cpp b/tests/mocks/MockAchievementRuntime.cpp index dd5a6a07..fcab935e 100644 --- a/tests/mocks/MockAchievementRuntime.cpp +++ b/tests/mocks/MockAchievementRuntime.cpp @@ -35,10 +35,11 @@ void MockAchievementRuntime::MockUser(const std::string& sUsername, const std::s { m_sUsername = sUsername; m_sApiToken = sApiToken; - GetClient()->user.username = m_sUsername.c_str(); - GetClient()->user.display_name = m_sUsername.c_str(); - GetClient()->user.token = m_sApiToken.c_str(); - GetClient()->state.user = RC_CLIENT_USER_STATE_LOGGED_IN; + rc_client_t* pClient = GetClient(); + pClient->user.username = m_sUsername.c_str(); + pClient->user.display_name = m_sUsername.c_str(); + pClient->user.token = m_sApiToken.c_str(); + pClient->state.user = RC_CLIENT_USER_STATE_LOGGED_IN; } void MockAchievementRuntime::MockGame() diff --git a/tests/services/AchievementRuntime_Tests.cpp b/tests/services/AchievementRuntime_Tests.cpp index f72a1bd8..1767a6c9 100644 --- a/tests/services/AchievementRuntime_Tests.cpp +++ b/tests/services/AchievementRuntime_Tests.cpp @@ -24,6 +24,12 @@ #include #include +#ifdef RC_CLIENT_EXPORTS_EXTERNAL +#include +#include "Exports.hh" +extern "C" API int CCONV _Rcheevos_GetExternalClient(rc_client_external_t* pClientExternal, int nVersion); +#endif + using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace ra { @@ -3105,6 +3111,77 @@ TEST_CLASS(AchievementRuntime_Tests) runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, false); Assert::AreEqual(std::string("Developing Achievements"), runtime.GetRichPresenceOverride()); } + +#ifdef RC_CLIENT_EXPORTS_EXTERNAL + static void AssertV1Exports(rc_client_external_t& pClient) + { + Assert::IsNotNull((void*)pClient.destroy, L"destory not set"); + + Assert::IsNotNull((void*)pClient.enable_logging, L"enable_logging not set"); + Assert::IsNotNull((void*)pClient.set_event_handler, L"set_event_handler not set"); + Assert::IsNotNull((void*)pClient.set_read_memory, L"set_read_memory not set"); + Assert::IsNotNull((void*)pClient.set_get_time_millisecs, L"set_get_time_millisecs not set"); + Assert::IsNotNull((void*)pClient.set_host, L"set_host not set"); + + Assert::IsNotNull((void*)pClient.set_hardcore_enabled, L"set_hardcore_enabled not set"); + Assert::IsNotNull((void*)pClient.get_hardcore_enabled, L"get_hardcore_enabled not set"); + Assert::IsNotNull((void*)pClient.set_unofficial_enabled, L"set_unofficial_enabled not set"); + Assert::IsNotNull((void*)pClient.get_unofficial_enabled, L"get_unofficial_enabled not set"); + Assert::IsNotNull((void*)pClient.set_encore_mode_enabled, L"set_encore_mode_enabled not set"); + Assert::IsNotNull((void*)pClient.get_encore_mode_enabled, L"get_encore_mode_enabled not set"); + Assert::IsNotNull((void*)pClient.set_spectator_mode_enabled, L"set_spectator_mode_enabled not set"); + Assert::IsNotNull((void*)pClient.get_spectator_mode_enabled, L"get_spectator_mode_enabled not set"); + + Assert::IsNotNull((void*)pClient.abort_async, L"abort_async not set"); + + Assert::IsNotNull((void*)pClient.begin_login_with_password, L"begin_login_with_password not set"); + Assert::IsNotNull((void*)pClient.begin_login_with_token, L"begin_login_with_token not set"); + Assert::IsNotNull((void*)pClient.logout, L"logout not set"); + Assert::IsNotNull((void*)pClient.get_user_info, L"get_user_info not set"); + + Assert::IsNotNull((void*)pClient.begin_identify_and_load_game, L"begin_identify_and_load_game not set"); + Assert::IsNotNull((void*)pClient.begin_load_game, L"begin_load_game not set"); + Assert::IsNotNull((void*)pClient.get_game_info, L"get_game_info not set"); + Assert::IsNull((void*)pClient.begin_load_subset, L"begin_load_subset set"); + Assert::IsNotNull((void*)pClient.get_subset_info, L"get_subset_info not set"); + Assert::IsNotNull((void*)pClient.unload_game, L"unload_game not set"); + Assert::IsNotNull((void*)pClient.get_user_game_summary, L"get_user_game_summary not set"); + Assert::IsNotNull((void*)pClient.begin_change_media, L"begin_change_media not set"); + + Assert::IsNotNull((void*)pClient.create_achievement_list, L"create_achievement_list not set"); + Assert::IsNotNull((void*)pClient.has_achievements, L"has_achievements not set"); + Assert::IsNotNull((void*)pClient.get_achievement_info, L"get_achievement_info not set"); + + Assert::IsNotNull((void*)pClient.create_leaderboard_list, L"create_leaderboard_list not set"); + Assert::IsNotNull((void*)pClient.has_leaderboards, L"has_leaderboards not set"); + Assert::IsNotNull((void*)pClient.get_leaderboard_info, L"get_leaderboard_info not set"); + Assert::IsNotNull((void*)pClient.begin_fetch_leaderboard_entries, L"begin_fetch_leaderboard_entries not set"); + Assert::IsNotNull((void*)pClient.begin_fetch_leaderboard_entries_around_user, L"begin_fetch_leaderboard_entries_around_user not set"); + + Assert::IsNotNull((void*)pClient.get_rich_presence_message, L"get_rich_presence_message not set"); + Assert::IsNotNull((void*)pClient.has_rich_presence, L"has_rich_presence not set"); + + Assert::IsNotNull((void*)pClient.do_frame, L"do_frame not set"); + Assert::IsNotNull((void*)pClient.idle, L"idle not set"); + Assert::IsNotNull((void*)pClient.is_processing_required, L"is_processing_required not set"); + Assert::IsNotNull((void*)pClient.can_pause, L"can_pause not set"); + Assert::IsNotNull((void*)pClient.reset, L"reset not set"); + + Assert::IsNotNull((void*)pClient.progress_size, L"progress_size not set"); + Assert::IsNotNull((void*)pClient.serialize_progress, L"serialize_progress not set"); + Assert::IsNotNull((void*)pClient.deserialize_progress, L"deserialize_progress not set"); + } + + TEST_METHOD(TestGetExternalClientV1) + { + rc_client_external_t pClient; + memset(&pClient, 0, sizeof(pClient)); + + _Rcheevos_GetExternalClient(&pClient, 1); + + AssertV1Exports(pClient); + } +#endif }; } // namespace tests diff --git a/tests/services/JsonFileConfiguration_Tests.cpp b/tests/services/JsonFileConfiguration_Tests.cpp index 3e0440b6..9c09cb3e 100644 --- a/tests/services/JsonFileConfiguration_Tests.cpp +++ b/tests/services/JsonFileConfiguration_Tests.cpp @@ -279,6 +279,17 @@ TEST_CLASS(JsonFileConfiguration_Tests) Assert::AreEqual(std::string("https://stage.retroachievements.org"), config.GetHostUrl()); Assert::AreEqual(std::string("https://stage.retroachievements.org"), config.GetImageHostUrl()); } + + TEST_METHOD(TestHostNameFromRcClient) + { + MockFileSystem mockFileSystem; + mockFileSystem.MockFile(L"host.txt", "stage.retroachievements.org"); + JsonFileConfiguration config; + config.SetHost("localhost"); + Assert::AreEqual(std::string("localhost"), config.GetHostName()); + Assert::AreEqual(std::string("http://localhost"), config.GetHostUrl()); + Assert::AreEqual(std::string("http://localhost"), config.GetImageHostUrl()); + } }; } // namespace tests