2023-06-18 12:24:47 +00:00
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-2.0 OR GPL-3.0 OR CC-BY-NC-ND-4.0)
// Derived from Duckstation's RetroAchievements implementation by stenzek as can be seen
// above, relicensed to GPL 2.0.
2023-06-15 20:17:27 +00:00
# include <algorithm>
# include <atomic>
# include <cstdarg>
# include <cstdlib>
# include <ctime>
# include <functional>
2023-07-10 08:39:44 +00:00
# include <set>
2023-06-15 20:17:27 +00:00
# include <string>
# include <vector>
2023-06-16 08:31:16 +00:00
# include <mutex>
2023-06-15 20:17:27 +00:00
2023-06-15 11:40:37 +00:00
# include "ext/rcheevos/include/rcheevos.h"
2023-06-27 21:31:15 +00:00
# include "ext/rcheevos/include/rc_client.h"
2023-06-15 14:40:30 +00:00
# include "ext/rcheevos/include/rc_api_user.h"
2023-06-15 20:17:27 +00:00
# include "ext/rcheevos/include/rc_api_info.h"
# include "ext/rcheevos/include/rc_api_request.h"
# include "ext/rcheevos/include/rc_api_runtime.h"
# include "ext/rcheevos/include/rc_api_user.h"
# include "ext/rcheevos/include/rc_url.h"
2023-06-16 23:52:28 +00:00
# include "ext/rcheevos/include/rc_hash.h"
# include "ext/rcheevos/src/rhash/md5.h"
2023-06-15 11:40:37 +00:00
2023-06-15 20:17:27 +00:00
# include "ext/rapidjson/include/rapidjson/document.h"
# include "Common/Log.h"
# include "Common/File/Path.h"
2023-06-16 08:31:16 +00:00
# include "Common/File/FileUtil.h"
2023-07-11 23:11:09 +00:00
# include "Core/FileLoaders/LocalFileLoader.h"
# include "Core/FileSystems/BlockDevices.h"
2023-06-15 20:17:27 +00:00
# include "Common/Net/HTTPClient.h"
2023-06-30 15:15:49 +00:00
# include "Common/System/OSD.h"
# include "Common/System/System.h"
2023-06-26 08:01:20 +00:00
# include "Common/System/NativeApp.h"
2023-06-15 21:27:38 +00:00
# include "Common/TimeUtil.h"
# include "Common/Data/Text/I18n.h"
# include "Common/Serialize/Serializer.h"
2023-06-27 21:31:15 +00:00
# include "Common/Serialize/SerializeFuncs.h"
2023-06-27 16:00:50 +00:00
# include "Common/StringUtils.h"
2023-06-16 22:00:57 +00:00
# include "Common/Crypto/md5.h"
2023-06-18 12:43:38 +00:00
# include "Common/UI/IconCache.h"
2023-06-15 21:27:38 +00:00
# include "Core/MemMap.h"
# include "Core/Config.h"
2023-06-16 08:31:16 +00:00
# include "Core/CoreParameter.h"
# include "Core/ELF/ParamSFO.h"
# include "Core/System.h"
2023-06-16 22:00:57 +00:00
# include "Core/FileSystems/MetaFileSystem.h"
2023-07-02 10:00:13 +00:00
# include "Core/RetroAchievements.h"
2023-06-16 08:31:16 +00:00
2023-07-10 17:26:41 +00:00
static inline const char * DeNull ( const char * ptr ) {
return ptr ? ptr : " " ;
}
2023-06-16 14:29:44 +00:00
void OnAchievementsLoginStateChange ( ) {
2023-09-30 09:21:22 +00:00
System_PostUIMessage ( UIMessage : : ACHIEVEMENT_LOGIN_STATE_CHANGE ) ;
2023-06-16 14:29:44 +00:00
}
2023-06-15 21:27:38 +00:00
2023-06-15 20:17:27 +00:00
namespace Achievements {
2023-06-26 08:01:20 +00:00
// It's the name of the secret, not a secret name - the value is not secret :)
2023-06-26 15:21:39 +00:00
static const char * RA_TOKEN_SECRET_NAME = " retroachievements " ;
2023-06-26 08:01:20 +00:00
2023-06-27 07:47:35 +00:00
static Achievements : : Statistics g_stats ;
2023-06-20 21:16:12 +00:00
2023-06-18 12:43:38 +00:00
const std : : string g_gameIconCachePrefix = " game: " ;
const std : : string g_iconCachePrefix = " badge: " ;
2023-06-27 21:31:15 +00:00
Path s_game_path ;
std : : string s_game_hash ;
2023-06-27 07:47:35 +00:00
2023-07-10 08:39:44 +00:00
std : : set < uint32_t > g_activeChallenges ;
2023-07-03 07:18:25 +00:00
bool g_isIdentifying = false ;
2023-07-13 10:10:20 +00:00
bool g_isLoggingIn = false ;
2023-07-21 23:30:20 +00:00
int g_loginResult ;
2023-07-03 07:18:25 +00:00
2023-08-28 10:15:24 +00:00
double g_lastLoginAttemptTime ;
2023-06-27 21:31:15 +00:00
// rc_client implementation
static rc_client_t * g_rcClient ;
2023-08-25 14:36:29 +00:00
static const std : : string g_RAImageID = " I_RETROACHIEVEMENTS_LOGO " ;
2023-08-28 10:15:24 +00:00
constexpr double LOGIN_ATTEMPT_INTERVAL_S = 10.0 ;
2023-06-15 11:40:37 +00:00
2023-09-12 12:02:12 +00:00
struct FileContext {
BlockDevice * bd ;
int64_t seekPos ;
} ;
static BlockDevice * g_blockDevice ;
2023-06-27 21:31:15 +00:00
# define PSP_MEMORY_OFFSET 0x08000000
2023-06-15 20:17:27 +00:00
2023-08-28 10:15:24 +00:00
static void TryLoginByToken ( bool isInitialAttempt ) ;
2023-06-27 21:31:15 +00:00
rc_client_t * GetClient ( ) {
return g_rcClient ;
2023-06-15 14:40:30 +00:00
}
2023-06-27 21:31:15 +00:00
bool IsLoggedIn ( ) {
2023-07-24 10:00:16 +00:00
return rc_client_get_user_info ( g_rcClient ) ! = nullptr & & ! g_isLoggingIn ;
2023-06-27 21:31:15 +00:00
}
2023-06-15 20:17:27 +00:00
2023-12-03 15:21:31 +00:00
// This is the RetroAchievements game ID, rather than the PSP game ID.
static u32 GetGameID ( ) {
if ( ! g_rcClient ) {
return 0 ;
}
const rc_client_game_t * info = rc_client_get_game_info ( g_rcClient ) ;
if ( ! info ) {
return 0 ;
}
return info - > id ; // 0 if not identified
}
2023-07-02 22:14:23 +00:00
bool EncoreModeActive ( ) {
if ( ! g_rcClient ) {
return false ;
}
return rc_client_get_encore_mode_enabled ( g_rcClient ) ;
}
2023-07-03 20:17:07 +00:00
bool UnofficialEnabled ( ) {
if ( ! g_rcClient ) {
return false ;
}
return rc_client_get_unofficial_enabled ( g_rcClient ) ;
}
2023-07-02 22:47:54 +00:00
bool ChallengeModeActive ( ) {
if ( ! g_rcClient ) {
return false ;
}
2023-12-03 15:21:31 +00:00
return IsLoggedIn ( ) & & rc_client_get_hardcore_enabled ( g_rcClient ) & & GetGameID ( ) ;
2023-07-02 22:47:54 +00:00
}
2023-11-16 19:13:47 +00:00
bool WarnUserIfChallengeModeActive ( bool isSaveStateAction , const char * message ) {
if ( ! ChallengeModeActive ( ) | | ( isSaveStateAction & & g_Config . bAchievementsSaveStateInChallengeMode ) ) {
2023-07-02 22:47:54 +00:00
return false ;
}
const char * showMessage = message ;
if ( ! message ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-11-30 16:33:14 +00:00
showMessage = ac - > T ( " This feature is not available in Hardcore Mode " ) ;
2023-07-02 22:47:54 +00:00
}
2023-08-25 14:36:29 +00:00
g_OSD . Show ( OSDType : : MESSAGE_WARNING , showMessage , " " , g_RAImageID , 3.0f ) ;
2023-07-02 22:47:54 +00:00
return true ;
2023-06-27 12:58:39 +00:00
}
2023-07-03 07:18:25 +00:00
bool IsBlockingExecution ( ) {
2023-07-24 10:00:16 +00:00
if ( g_isLoggingIn | | g_isIdentifying ) {
// Useful for debugging race conditions.
// INFO_LOG(ACHIEVEMENTS, "isLoggingIn: %d isIdentifying: %d", (int)g_isLoggingIn, (int)g_isIdentifying);
}
return g_isLoggingIn | | g_isIdentifying ;
2023-07-03 07:18:25 +00:00
}
2023-07-02 22:47:54 +00:00
bool IsActive ( ) {
return GetGameID ( ) ! = 0 ;
}
2023-06-27 21:31:15 +00:00
static uint32_t read_memory_callback ( uint32_t address , uint8_t * buffer , uint32_t num_bytes , rc_client_t * client ) {
// Achievements are traditionally defined relative to the base of main memory of the emulated console.
// This is some kind of RetroArch-related legacy. In the PSP's case, this is simply a straight offset of 0x08000000.
2023-07-10 14:11:02 +00:00
uint32_t orig_address = address ;
2023-06-27 21:31:15 +00:00
address + = PSP_MEMORY_OFFSET ;
2023-06-15 20:17:27 +00:00
2023-07-16 07:08:31 +00:00
if ( ! Memory : : ValidSize ( address , num_bytes ) ) {
2023-06-27 21:31:15 +00:00
// Some achievement packs are really, really spammy.
// So we'll just count the bad accesses.
Achievements : : g_stats . badMemoryAccessCount + + ;
if ( g_Config . bAchievementsLogBadMemReads ) {
2023-07-10 14:11:02 +00:00
WARN_LOG ( G3D , " RetroAchievements PeekMemory: Bad address %08x (%d bytes) (%08x was passed in) " , address , num_bytes , orig_address ) ;
2023-06-15 20:17:27 +00:00
}
2023-07-10 14:11:02 +00:00
2023-07-16 15:05:08 +00:00
// This tells rcheevos that the access was bad, which should now be handled properly.
2023-08-09 14:16:39 +00:00
return 0 ;
2023-06-15 20:17:27 +00:00
}
2023-07-16 07:08:31 +00:00
Memory : : MemcpyUnchecked ( buffer , address , num_bytes ) ;
return num_bytes ;
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
// This is the HTTP request dispatcher that is provided to the rc_client. Whenever the client
// needs to talk to the server, it will call this function.
static void server_call_callback ( const rc_api_request_t * request ,
rc_client_server_callback_t callback , void * callback_data , rc_client_t * client )
{
// If post data is provided, we need to make a POST request, otherwise, a GET request will suffice.
2023-07-18 13:52:14 +00:00
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-06-27 21:31:15 +00:00
if ( request - > post_data ) {
2023-07-21 20:04:05 +00:00
std : : shared_ptr < http : : Request > download = g_DownloadManager . AsyncPostWithCallback ( std : : string ( request - > url ) , std : : string ( request - > post_data ) , " application/x-www-form-urlencoded " , http : : ProgressBarMode : : DELAYED , [ = ] ( http : : Request & download ) {
2023-06-27 21:31:15 +00:00
std : : string buffer ;
download . buffer ( ) . TakeAll ( & buffer ) ;
rc_api_server_response_t response { } ;
response . body = buffer . c_str ( ) ;
response . body_length = buffer . size ( ) ;
response . http_status_code = download . ResultCode ( ) ;
callback ( & response , callback_data ) ;
2023-07-18 13:52:14 +00:00
} , ac - > T ( " Contacting RetroAchievements server... " ) ) ;
2023-06-27 21:31:15 +00:00
} else {
2023-07-21 20:04:05 +00:00
std : : shared_ptr < http : : Request > download = g_DownloadManager . StartDownloadWithCallback ( std : : string ( request - > url ) , Path ( ) , http : : ProgressBarMode : : DELAYED , [ = ] ( http : : Request & download ) {
2023-06-27 21:31:15 +00:00
std : : string buffer ;
download . buffer ( ) . TakeAll ( & buffer ) ;
rc_api_server_response_t response { } ;
response . body = buffer . c_str ( ) ;
response . body_length = buffer . size ( ) ;
response . http_status_code = download . ResultCode ( ) ;
callback ( & response , callback_data ) ;
2023-07-18 13:52:14 +00:00
} , ac - > T ( " Contacting RetroAchievements server... " ) ) ;
2023-06-27 21:31:15 +00:00
}
}
static void log_message_callback ( const char * message , const rc_client_t * client ) {
2023-07-13 09:24:37 +00:00
INFO_LOG ( ACHIEVEMENTS , " RetroAchievements: %s " , message ) ;
2023-06-27 21:31:15 +00:00
}
// For detailed documentation, see https://github.com/RetroAchievements/rcheevos/wiki/rc_client_set_event_handler.
static void event_handler_callback ( const rc_client_event_t * event , rc_client_t * client ) {
2023-07-03 12:39:49 +00:00
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-06-27 21:31:15 +00:00
switch ( event - > type ) {
case RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED :
// An achievement was earned by the player. The handler should notify the player that the achievement was earned.
2023-07-02 22:14:23 +00:00
g_OSD . ShowAchievementUnlocked ( event - > achievement - > id ) ;
2023-09-30 09:21:22 +00:00
System_PostUIMessage ( UIMessage : : REQUEST_PLAY_SOUND , " achievement_unlocked " ) ;
2023-07-03 12:39:49 +00:00
INFO_LOG ( ACHIEVEMENTS , " Achievement unlocked: '%s' (%d) " , event - > achievement - > title , event - > achievement - > id ) ;
2023-06-27 21:31:15 +00:00
break ;
2023-07-12 17:18:56 +00:00
2023-06-27 21:31:15 +00:00
case RC_CLIENT_EVENT_GAME_COMPLETED :
2023-07-03 12:39:49 +00:00
{
2023-07-12 17:18:56 +00:00
// TODO: Do some zany fireworks!
2023-11-30 16:33:14 +00:00
// All achievements for the game have been earned. The handler should notify the player that the game was completed or mastered, depending on mode, hardcore or not.
2023-07-03 12:39:49 +00:00
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
const rc_client_game_t * gameInfo = rc_client_get_game_info ( g_rcClient ) ;
// TODO: Translation?
2023-07-16 14:16:47 +00:00
std : : string title = ApplySafeSubstitutions ( ac - > T ( " Mastered %1 " ) , gameInfo - > title ) ;
2023-11-27 00:10:52 +00:00
2023-07-03 12:39:49 +00:00
rc_client_user_game_summary_t summary ;
rc_client_get_user_game_summary ( g_rcClient , & summary ) ;
2023-10-08 21:22:28 +00:00
std : : string message = ApplySafeSubstitutions ( ac - > T ( " %1 achievements, %2 points " ) , summary . num_unlocked_achievements , summary . points_unlocked ) ;
2023-07-03 12:39:49 +00:00
2023-07-10 17:26:41 +00:00
g_OSD . Show ( OSDType : : MESSAGE_INFO , title , message , DeNull ( gameInfo - > badge_name ) , 10.0f ) ;
2023-07-03 12:39:49 +00:00
2023-09-30 09:21:22 +00:00
System_PostUIMessage ( UIMessage : : REQUEST_PLAY_SOUND , " achievement_unlocked " ) ;
2023-07-12 17:18:56 +00:00
2023-07-03 12:39:49 +00:00
INFO_LOG ( ACHIEVEMENTS , " %s " , message . c_str ( ) ) ;
2023-06-27 21:31:15 +00:00
break ;
2023-07-03 12:39:49 +00:00
}
2023-06-27 21:31:15 +00:00
case RC_CLIENT_EVENT_LEADERBOARD_STARTED :
case RC_CLIENT_EVENT_LEADERBOARD_FAILED :
2023-08-01 09:57:28 +00:00
{
bool started = event - > type = = RC_CLIENT_EVENT_LEADERBOARD_STARTED ;
// A leaderboard attempt has started. The handler may show a message with the leaderboard title and /or description indicating the attempt started.
const char * title = " " ;
const char * description = " " ;
// Hack around some problematic events in Burnout Legends. Hopefully this can be fixed in the backend.
if ( strlen ( event - > leaderboard - > title ) > 0 ) {
title = event - > leaderboard - > title ;
description = event - > leaderboard - > description ;
} else {
title = event - > leaderboard - > description ;
}
INFO_LOG ( ACHIEVEMENTS , " Attempt %s: %s " , started ? " started " : " failed " , title ) ;
g_OSD . ShowLeaderboardStartEnd ( ApplySafeSubstitutions ( ac - > T ( started ? " %1: Attempt started " : " %1: Attempt failed " ) , title ) , description , started ) ;
2023-06-27 21:31:15 +00:00
break ;
2023-08-01 09:57:28 +00:00
}
2023-06-27 21:31:15 +00:00
case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED :
2023-08-01 09:57:28 +00:00
{
2023-07-30 08:55:55 +00:00
INFO_LOG ( ACHIEVEMENTS , " Leaderboard result submitted: %s " , event - > leaderboard - > title ) ;
2023-08-01 09:57:28 +00:00
const char * title = " " ;
// Hack around some problematic events in Burnout Legends. Hopefully this can be fixed in the backend.
if ( strlen ( event - > leaderboard - > title ) > 0 ) {
title = event - > leaderboard - > title ;
} else {
title = event - > leaderboard - > description ;
}
g_OSD . ShowLeaderboardSubmitted ( ApplySafeSubstitutions ( ac - > T ( " Submitted %1 for %2 " ) , DeNull ( event - > leaderboard - > tracker_value ) , title ) , " " ) ;
2023-09-30 09:21:22 +00:00
System_PostUIMessage ( UIMessage : : REQUEST_PLAY_SOUND , " leaderboard_submitted " ) ;
2023-06-27 21:31:15 +00:00
break ;
2023-08-01 09:57:28 +00:00
}
2023-06-27 21:31:15 +00:00
case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW :
2023-07-30 08:55:55 +00:00
INFO_LOG ( ACHIEVEMENTS , " Challenge indicator show: %s " , event - > achievement - > title ) ;
2023-07-03 12:39:49 +00:00
g_OSD . ShowChallengeIndicator ( event - > achievement - > id , true ) ;
2023-07-10 08:39:44 +00:00
g_activeChallenges . insert ( event - > achievement - > id ) ;
2023-06-27 21:31:15 +00:00
// A challenge achievement has become active. The handler should show a small version of the achievement icon
// to indicate the challenge is active.
break ;
case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE :
2023-07-30 08:55:55 +00:00
INFO_LOG ( ACHIEVEMENTS , " Challenge indicator hide: %s " , event - > achievement - > title ) ;
2023-07-03 12:39:49 +00:00
g_OSD . ShowChallengeIndicator ( event - > achievement - > id , false ) ;
2023-07-10 08:39:44 +00:00
g_activeChallenges . erase ( event - > achievement - > id ) ;
2023-06-27 21:31:15 +00:00
// The handler should hide the small version of the achievement icon that was shown by the corresponding RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW event.
break ;
case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW :
2023-07-30 08:55:55 +00:00
INFO_LOG ( ACHIEVEMENTS , " Progress indicator show: %s, progress: '%s' (%f) " , event - > achievement - > title , event - > achievement - > measured_progress , event - > achievement - > measured_percent ) ;
2023-06-27 21:31:15 +00:00
// An achievement that tracks progress has changed the amount of progress that has been made.
// The handler should show a small version of the achievement icon along with the achievement->measured_progress text (for two seconds).
// Only one progress indicator should be shown at a time.
// If a progress indicator is already visible, it should be updated with the new icon and text, and the two second timer should be restarted.
2023-07-30 08:55:55 +00:00
g_OSD . ShowAchievementProgress ( event - > achievement - > id , true ) ;
break ;
case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE :
INFO_LOG ( ACHIEVEMENTS , " Progress indicator update: %s, progress: '%s' (%f) " , event - > achievement - > title , event - > achievement - > measured_progress , event - > achievement - > measured_percent ) ;
g_OSD . ShowAchievementProgress ( event - > achievement - > id , true ) ;
break ;
case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE :
INFO_LOG ( ACHIEVEMENTS , " Progress indicator hide " ) ;
// An achievement that tracks progress has changed the amount of progress that has been made.
// The handler should show a small version of the achievement icon along with the achievement->measured_progress text (for two seconds).
// Only one progress indicator should be shown at a time.
// If a progress indicator is already visible, it should be updated with the new icon and text, and the two second timer should be restarted.
g_OSD . ShowAchievementProgress ( 0 , false ) ;
2023-06-27 21:31:15 +00:00
break ;
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW :
2023-07-30 08:55:55 +00:00
INFO_LOG ( ACHIEVEMENTS , " Leaderboard tracker show: '%s' (id %d) " , event - > leaderboard_tracker - > display , event - > leaderboard_tracker - > id ) ;
2023-06-27 21:31:15 +00:00
// A leaderboard_tracker has become active. The handler should show the tracker text on screen.
// Multiple active leaderboards may share a single tracker if they have the same definition and value.
// As such, the leaderboard tracker IDs are unique amongst the leaderboard trackers, and have no correlation to the active leaderboard(s).
// Use event->leaderboard_tracker->id for uniqueness checks, and display event->leaderboard_tracker->display (string)
2023-07-03 12:39:49 +00:00
g_OSD . ShowLeaderboardTracker ( event - > leaderboard_tracker - > id , event - > leaderboard_tracker - > display , true ) ;
2023-06-27 21:31:15 +00:00
break ;
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE :
2023-07-03 12:39:49 +00:00
// A leaderboard_tracker has become inactive. The handler should hide the tracker text from the screen.
2023-07-30 08:55:55 +00:00
INFO_LOG ( ACHIEVEMENTS , " Leaderboard tracker hide: '%s' (id %d) " , event - > leaderboard_tracker - > display , event - > leaderboard_tracker - > id ) ;
2023-07-03 12:39:49 +00:00
g_OSD . ShowLeaderboardTracker ( event - > leaderboard_tracker - > id , nullptr , false ) ;
2023-06-27 21:31:15 +00:00
break ;
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE :
// A leaderboard_tracker value has been updated. The handler should update the tracker text on the screen.
2023-07-30 08:55:55 +00:00
INFO_LOG ( ACHIEVEMENTS , " Leaderboard tracker update: '%s' (id %d) " , event - > leaderboard_tracker - > display , event - > leaderboard_tracker - > id ) ;
2023-07-02 22:47:54 +00:00
g_OSD . ShowLeaderboardTracker ( event - > leaderboard_tracker - > id , event - > leaderboard_tracker - > display , true ) ;
2023-06-27 21:31:15 +00:00
break ;
case RC_CLIENT_EVENT_RESET :
2023-07-03 12:39:49 +00:00
WARN_LOG ( ACHIEVEMENTS , " Resetting game due to achievement setting change! " ) ;
2023-11-30 16:33:14 +00:00
// Hardcore mode was enabled, or something else that forces a game reset.
2023-09-30 09:21:22 +00:00
System_PostUIMessage ( UIMessage : : REQUEST_GAME_RESET ) ;
2023-06-27 21:31:15 +00:00
break ;
case RC_CLIENT_EVENT_SERVER_ERROR :
ERROR_LOG ( ACHIEVEMENTS , " Server error: %s: %s " , event - > server_error - > api , event - > server_error - > error_message ) ;
2023-08-25 14:36:29 +00:00
g_OSD . Show ( OSDType : : MESSAGE_ERROR , " Server error " , " " , g_RAImageID ) ;
2023-06-27 21:31:15 +00:00
break ;
default :
WARN_LOG ( ACHIEVEMENTS , " Unhandled rc_client event %d, ignoring " , event - > type ) ;
break ;
}
2023-06-15 20:17:27 +00:00
}
2023-07-13 10:10:20 +00:00
static void login_token_callback ( int result , const char * error_message , rc_client_t * client , void * userdata ) {
2023-08-28 10:15:24 +00:00
bool isInitialAttempt = userdata ! = nullptr ;
2023-07-13 10:10:20 +00:00
switch ( result ) {
case RC_OK :
2023-08-28 10:15:24 +00:00
{
2023-07-24 10:00:16 +00:00
INFO_LOG ( ACHIEVEMENTS , " Successful login by token. " ) ;
2023-07-13 10:10:20 +00:00
OnAchievementsLoginStateChange ( ) ;
2023-08-28 10:15:24 +00:00
if ( ! isInitialAttempt ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
g_OSD . Show ( OSDType : : MESSAGE_SUCCESS , ac - > T ( " Reconnected to RetroAchievements. " ) , " " , g_RAImageID ) ;
}
2023-07-13 10:10:20 +00:00
break ;
2023-08-28 10:15:24 +00:00
}
2023-07-13 10:10:20 +00:00
case RC_NO_RESPONSE :
{
2023-08-28 10:15:24 +00:00
if ( isInitialAttempt ) {
auto di = GetI18NCategory ( I18NCat : : DIALOG ) ;
g_OSD . Show ( OSDType : : MESSAGE_WARNING , di - > T ( " Failed to connect to server, check your internet connection. " ) , " " , g_RAImageID ) ;
}
2023-07-13 10:10:20 +00:00
break ;
}
2023-10-03 12:57:27 +00:00
case RC_ACCESS_DENIED :
case RC_INVALID_CREDENTIALS :
case RC_EXPIRED_TOKEN :
2023-07-13 10:10:20 +00:00
case RC_API_FAILURE :
2023-07-21 23:30:20 +00:00
case RC_INVALID_STATE :
2023-07-13 10:10:20 +00:00
case RC_MISSING_VALUE :
case RC_INVALID_JSON :
default :
2023-07-21 23:30:20 +00:00
{
2023-07-24 10:00:16 +00:00
ERROR_LOG ( ACHIEVEMENTS , " Callback: Failure logging in via token: %d, %s " , result , error_message ) ;
2023-08-28 10:15:24 +00:00
if ( isInitialAttempt ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
g_OSD . Show ( OSDType : : MESSAGE_WARNING , ac - > T ( " Failed logging in to RetroAchievements " ) , " " , g_RAImageID ) ;
}
2023-07-13 10:10:20 +00:00
OnAchievementsLoginStateChange ( ) ;
break ;
}
2023-07-21 23:30:20 +00:00
}
g_loginResult = result ;
2023-07-13 10:10:20 +00:00
g_isLoggingIn = false ;
}
2023-06-27 21:31:15 +00:00
void Initialize ( ) {
2023-07-01 10:31:46 +00:00
if ( ! g_Config . bAchievementsEnable ) {
_dbg_assert_ ( ! g_rcClient ) ;
INFO_LOG ( ACHIEVEMENTS , " Achievements are disabled, not initializing. " ) ;
return ;
}
2023-07-11 08:16:58 +00:00
_assert_msg_ ( ! g_rcClient , " Achievements already initialized " ) ;
2023-06-15 20:17:27 +00:00
2023-06-27 21:31:15 +00:00
g_rcClient = rc_client_create ( read_memory_callback , server_call_callback ) ;
2023-07-11 08:16:58 +00:00
if ( ! g_rcClient ) {
// Shouldn't happen really.
return ;
}
2023-06-27 21:31:15 +00:00
// Provide a logging function to simplify debugging
rc_client_enable_logging ( g_rcClient , RC_CLIENT_LOG_LEVEL_VERBOSE , log_message_callback ) ;
2023-06-15 20:17:27 +00:00
2023-07-21 08:49:01 +00:00
if ( ! System_GetPropertyBool ( SYSPROP_SUPPORTS_HTTPS ) ) {
// Disable SSL if not supported by our platform implementation.
rc_client_set_host ( g_rcClient , " http://retroachievements.org " ) ;
}
2023-06-15 20:17:27 +00:00
2023-06-27 21:31:15 +00:00
rc_client_set_event_handler ( g_rcClient , event_handler_callback ) ;
2023-06-15 20:17:27 +00:00
2023-09-12 12:02:12 +00:00
rc_hash_filereader rc_filereader ;
rc_filereader . open = [ ] ( const char * utf8Path ) - > void * {
if ( ! g_blockDevice ) {
ERROR_LOG ( ACHIEVEMENTS , " No block device " ) ;
return nullptr ;
}
return ( void * ) new FileContext { g_blockDevice , 0 } ;
} ;
rc_filereader . seek = [ ] ( void * file_handle , int64_t offset , int origin ) {
FileContext * ctx = ( FileContext * ) file_handle ;
switch ( origin ) {
case SEEK_SET : ctx - > seekPos = offset ; break ;
case SEEK_END : ctx - > seekPos = ctx - > bd - > GetBlockSize ( ) * ctx - > bd - > GetNumBlocks ( ) + offset ; break ;
case SEEK_CUR : ctx - > seekPos + = offset ; break ;
default : break ;
}
} ;
rc_filereader . tell = [ ] ( void * file_handle ) - > int64_t {
return ( ( FileContext * ) file_handle ) - > seekPos ;
} ;
rc_filereader . read = [ ] ( void * file_handle , void * buffer , size_t requested_bytes ) - > size_t {
FileContext * ctx = ( FileContext * ) file_handle ;
int blockSize = ctx - > bd - > GetBlockSize ( ) ;
int64_t offset = ctx - > seekPos ;
int64_t endOffset = ctx - > seekPos + requested_bytes ;
int firstBlock = offset / blockSize ;
int afterLastBlock = ( endOffset + blockSize - 1 ) / blockSize ;
int numBlocks = afterLastBlock - firstBlock ;
// This is suboptimal, but good enough since we're not doing a lot of accesses.
uint8_t * buf = new uint8_t [ numBlocks * blockSize ] ;
bool success = ctx - > bd - > ReadBlocks ( firstBlock , numBlocks , ( u8 * ) buf ) ;
if ( success ) {
int64_t firstOffset = firstBlock * blockSize ;
memcpy ( buffer , buf + ( offset - firstOffset ) , requested_bytes ) ;
ctx - > seekPos + = requested_bytes ;
delete [ ] buf ;
return requested_bytes ;
} else {
delete [ ] buf ;
ERROR_LOG ( ACHIEVEMENTS , " Block device load fail " ) ;
return 0 ;
}
} ;
rc_filereader . close = [ ] ( void * file_handle ) {
FileContext * ctx = ( FileContext * ) file_handle ;
delete ctx - > bd ;
delete ctx ;
} ;
rc_hash_init_custom_filereader ( & rc_filereader ) ;
rc_hash_init_default_cdreader ( ) ;
2023-08-28 10:15:24 +00:00
TryLoginByToken ( true ) ;
2023-07-21 23:30:20 +00:00
}
bool HasToken ( ) {
return ! NativeLoadSecret ( RA_TOKEN_SECRET_NAME ) . empty ( ) ;
}
bool LoginProblems ( std : : string * errorString ) {
// TODO: Set error string.
return g_loginResult ! = RC_OK ;
}
2023-08-28 10:15:24 +00:00
static void TryLoginByToken ( bool isInitialAttempt ) {
2023-09-06 09:19:13 +00:00
if ( g_Config . sAchievementsUserName . empty ( ) ) {
// Don't even look for a token - without a username we can't login.
return ;
}
2023-06-27 21:31:15 +00:00
std : : string api_token = NativeLoadSecret ( RA_TOKEN_SECRET_NAME ) ;
if ( ! api_token . empty ( ) ) {
2023-07-13 10:10:20 +00:00
g_isLoggingIn = true ;
2023-08-28 10:15:24 +00:00
rc_client_begin_login_with_token ( g_rcClient , g_Config . sAchievementsUserName . c_str ( ) , api_token . c_str ( ) , & login_token_callback , ( void * ) isInitialAttempt ) ;
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
}
2023-06-15 20:17:27 +00:00
2023-06-27 21:31:15 +00:00
static void login_password_callback ( int result , const char * error_message , rc_client_t * client , void * userdata ) {
2023-07-12 17:21:08 +00:00
auto di = GetI18NCategory ( I18NCat : : DIALOG ) ;
2023-06-27 21:31:15 +00:00
switch ( result ) {
case RC_OK :
2023-06-15 20:17:27 +00:00
{
2023-06-27 21:31:15 +00:00
// Get the token and store it.
const rc_client_user_t * user = rc_client_get_user_info ( client ) ;
g_Config . sAchievementsUserName = user - > username ;
NativeSaveSecret ( RA_TOKEN_SECRET_NAME , std : : string ( user - > token ) ) ;
2023-07-02 22:14:23 +00:00
OnAchievementsLoginStateChange ( ) ;
2023-08-25 14:36:29 +00:00
g_OSD . Show ( OSDType : : MESSAGE_SUCCESS , di - > T ( " Logged in! " ) , " " , g_RAImageID ) ;
2023-06-27 21:31:15 +00:00
break ;
2023-06-15 20:17:27 +00:00
}
2023-07-13 09:24:37 +00:00
case RC_NO_RESPONSE :
{
auto di = GetI18NCategory ( I18NCat : : DIALOG ) ;
2023-08-25 14:36:29 +00:00
g_OSD . Show ( OSDType : : MESSAGE_WARNING , di - > T ( " Failed to connect to server, check your internet connection. " ) , " " , g_RAImageID ) ;
2023-07-13 09:24:37 +00:00
break ;
}
2023-06-27 21:31:15 +00:00
case RC_INVALID_STATE :
case RC_API_FAILURE :
case RC_MISSING_VALUE :
case RC_INVALID_JSON :
2023-10-03 12:57:27 +00:00
case RC_ACCESS_DENIED :
case RC_INVALID_CREDENTIALS :
case RC_EXPIRED_TOKEN :
2023-07-13 09:24:37 +00:00
default :
2023-07-12 17:21:08 +00:00
{
ERROR_LOG ( ACHIEVEMENTS , " Failure logging in via password: %d, %s " , result , error_message ) ;
2023-08-25 14:36:29 +00:00
g_OSD . Show ( OSDType : : MESSAGE_WARNING , di - > T ( " Failed to log in, check your username and password. " ) , " " , g_RAImageID ) ;
2023-07-02 22:14:23 +00:00
OnAchievementsLoginStateChange ( ) ;
2023-06-27 21:31:15 +00:00
break ;
2023-06-15 20:17:27 +00:00
}
2023-07-12 17:21:08 +00:00
}
2023-06-15 21:27:38 +00:00
2023-07-18 13:13:44 +00:00
g_OSD . RemoveProgressBar ( " cheevos_async_login " , true , 0.1f ) ;
2023-07-21 23:30:20 +00:00
g_loginResult = RC_OK ; // For these, we don't want the "permanence" of the login-by-token failure, this prevents LoginProblems from returning true.
2023-07-13 10:10:20 +00:00
g_isLoggingIn = false ;
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
bool LoginAsync ( const char * username , const char * password ) {
2023-07-13 09:24:37 +00:00
auto di = GetI18NCategory ( I18NCat : : DIALOG ) ;
2023-06-27 21:31:15 +00:00
if ( IsLoggedIn ( ) | | std : : strlen ( username ) = = 0 | | std : : strlen ( password ) = = 0 | | IsUsingRAIntegration ( ) )
2023-06-15 20:17:27 +00:00
return false ;
2023-07-18 13:13:44 +00:00
g_OSD . SetProgressBar ( " cheevos_async_login " , di - > T ( " Logging in... " ) , 0 , 0 , 0 , 0.0f ) ;
2023-07-13 09:24:37 +00:00
2023-07-13 10:10:20 +00:00
g_isLoggingIn = true ;
2023-06-27 21:31:15 +00:00
rc_client_begin_login_with_password ( g_rcClient , username , password , & login_password_callback , nullptr ) ;
2023-06-15 20:17:27 +00:00
return true ;
}
2023-06-27 21:31:15 +00:00
void Logout ( ) {
rc_client_logout ( g_rcClient ) ;
// remove from config
g_Config . sAchievementsUserName . clear ( ) ;
NativeSaveSecret ( RA_TOKEN_SECRET_NAME , " " ) ;
g_Config . Save ( " Achievements logout " ) ;
2023-07-10 08:39:44 +00:00
g_activeChallenges . clear ( ) ;
2023-07-21 23:30:20 +00:00
g_loginResult = RC_OK ; // Allow trying again
2023-07-11 08:05:55 +00:00
OnAchievementsLoginStateChange ( ) ;
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
void UpdateSettings ( ) {
2023-07-11 08:16:58 +00:00
if ( g_rcClient & & ! g_Config . bAchievementsEnable ) {
2023-06-27 21:31:15 +00:00
// we're done here
Shutdown ( ) ;
2023-06-15 20:17:27 +00:00
return ;
}
2023-07-11 08:16:58 +00:00
if ( ! g_rcClient & & g_Config . bAchievementsEnable ) {
2023-07-02 15:12:46 +00:00
// we just got enabled.
2023-06-27 21:31:15 +00:00
Initialize ( ) ;
2023-06-15 20:17:27 +00:00
}
}
2023-06-27 21:31:15 +00:00
bool Shutdown ( ) {
2023-07-10 08:39:44 +00:00
g_activeChallenges . clear ( ) ;
2023-06-27 21:31:15 +00:00
rc_client_destroy ( g_rcClient ) ;
g_rcClient = nullptr ;
2023-07-11 08:16:58 +00:00
INFO_LOG ( ACHIEVEMENTS , " Achievements shut down. " ) ;
2023-06-15 20:17:27 +00:00
return true ;
}
2023-06-27 21:31:15 +00:00
void ResetRuntime ( ) {
2023-06-23 20:08:52 +00:00
INFO_LOG ( ACHIEVEMENTS , " Resetting rcheevos state... " ) ;
2023-06-27 21:31:15 +00:00
rc_client_reset ( g_rcClient ) ;
2023-07-10 08:39:44 +00:00
g_activeChallenges . clear ( ) ;
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
void FrameUpdate ( ) {
if ( ! g_rcClient )
2023-06-15 20:17:27 +00:00
return ;
2023-06-27 21:31:15 +00:00
rc_client_do_frame ( g_rcClient ) ;
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
void Idle ( ) {
rc_client_idle ( g_rcClient ) ;
2023-08-28 10:15:24 +00:00
double now = time_now_d ( ) ;
// If failed to log in, occasionally try again while the user is at the menu.
// Do not try if if in-game, that could get confusing.
2023-08-28 12:38:32 +00:00
if ( g_Config . bAchievementsEnable & & GetUIState ( ) = = UISTATE_MENU & & now > g_lastLoginAttemptTime + LOGIN_ATTEMPT_INTERVAL_S ) {
2023-08-28 10:15:24 +00:00
g_lastLoginAttemptTime = now ;
if ( g_rcClient & & IsLoggedIn ( ) ) {
return ; // All good.
}
2023-09-06 08:34:32 +00:00
if ( g_Config . sAchievementsUserName . empty ( ) | | g_isLoggingIn | | ! HasToken ( ) ) {
// Didn't try to login yet or is in the process of logging in. Also OK.
2023-08-28 10:15:24 +00:00
return ;
}
// In this situation, there's a token, but we're not logged in. Probably disrupted internet connection
// during startup.
// Let's make an attempt.
INFO_LOG ( ACHIEVEMENTS , " Retrying login.. " ) ;
TryLoginByToken ( false ) ;
}
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
void DoState ( PointerWrap & p ) {
2023-07-01 10:31:46 +00:00
auto sw = p . Section ( " Achievements " , 0 , 1 ) ;
2023-06-15 21:27:38 +00:00
if ( ! sw ) {
// Save state is missing the section.
// Reset the runtime.
2023-07-01 10:31:46 +00:00
if ( HasAchievementsOrLeaderboards ( ) ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-08-25 14:36:29 +00:00
g_OSD . Show ( OSDType : : MESSAGE_WARNING , ac - > T ( " Save state loaded without achievement data " ) , " " , g_RAImageID , 5.0f , " " ) ;
2023-07-01 10:31:46 +00:00
}
rc_client_reset ( g_rcClient ) ;
2023-06-15 21:27:38 +00:00
return ;
}
2023-06-27 21:31:15 +00:00
uint32_t data_size = 0 ;
2023-07-01 10:31:46 +00:00
2023-07-02 22:14:23 +00:00
if ( ! IsActive ( ) ) {
2023-07-01 10:31:46 +00:00
Do ( p , data_size ) ;
2023-07-02 22:14:23 +00:00
if ( p . mode = = PointerWrap : : MODE_READ ) {
WARN_LOG ( ACHIEVEMENTS , " Save state contained achievement data, but achievements are not active. Ignore. " ) ;
}
2023-07-01 10:31:46 +00:00
p . SkipBytes ( data_size ) ;
return ;
}
2023-06-27 21:31:15 +00:00
if ( p . mode = = PointerWrap : : MODE_MEASURE | | p . mode = = PointerWrap : : MODE_WRITE | | p . mode = = PointerWrap : : MODE_VERIFY | | p . mode = = PointerWrap : : MODE_NOOP ) {
data_size = ( uint32_t ) ( g_rcClient ? rc_client_progress_size ( g_rcClient ) : 0 ) ;
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
Do ( p , data_size ) ;
2023-06-15 20:17:27 +00:00
2023-06-27 21:31:15 +00:00
if ( data_size > 0 ) {
uint8_t * buffer = new uint8_t [ data_size ] ;
switch ( p . mode ) {
case PointerWrap : : MODE_NOOP :
case PointerWrap : : MODE_MEASURE :
case PointerWrap : : MODE_WRITE :
case PointerWrap : : MODE_VERIFY :
2023-07-01 10:31:46 +00:00
{
int retval = rc_client_serialize_progress ( g_rcClient , buffer ) ;
if ( retval ! = RC_OK ) {
ERROR_LOG ( ACHIEVEMENTS , " Error %d serializing achievement data. Ignoring. " , retval ) ;
}
2023-06-27 21:31:15 +00:00
break ;
2023-06-15 20:17:27 +00:00
}
2023-07-11 23:11:09 +00:00
default :
break ;
2023-07-01 10:31:46 +00:00
}
2023-06-15 20:17:27 +00:00
2023-06-27 21:31:15 +00:00
DoArray ( p , buffer , data_size ) ;
2023-06-15 20:17:27 +00:00
2023-06-27 21:31:15 +00:00
switch ( p . mode ) {
case PointerWrap : : MODE_READ :
2023-07-01 10:31:46 +00:00
{
int retval = rc_client_deserialize_progress ( g_rcClient , buffer ) ;
if ( retval ! = RC_OK ) {
// TODO: What should we really do here?
ERROR_LOG ( ACHIEVEMENTS , " Error %d deserializing achievement data. Ignoring. " , retval ) ;
}
2023-06-27 21:31:15 +00:00
break ;
2023-06-15 20:17:27 +00:00
}
2023-07-11 23:11:09 +00:00
default :
break ;
2023-07-01 10:31:46 +00:00
}
2023-06-27 21:31:15 +00:00
delete [ ] buffer ;
2023-07-01 10:31:46 +00:00
} else {
2023-07-02 22:14:23 +00:00
if ( IsActive ( ) ) {
2023-07-01 10:31:46 +00:00
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-08-25 14:36:29 +00:00
g_OSD . Show ( OSDType : : MESSAGE_WARNING , ac - > T ( " Save state loaded without achievement data " ) , " " , g_RAImageID , 5.0f ) ;
2023-07-01 10:31:46 +00:00
}
rc_client_reset ( g_rcClient ) ;
2023-06-15 20:17:27 +00:00
}
}
2023-06-27 21:31:15 +00:00
bool HasAchievementsOrLeaderboards ( ) {
if ( ! g_rcClient ) {
return false ;
}
2023-07-02 22:14:23 +00:00
return IsActive ( ) ;
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
void DownloadImageIfMissing ( const std : : string & cache_key , std : : string & & url ) {
if ( g_iconCache . MarkPending ( cache_key ) ) {
INFO_LOG ( ACHIEVEMENTS , " Downloading image: %s (%s) " , url . c_str ( ) , cache_key . c_str ( ) ) ;
2023-07-21 20:04:05 +00:00
g_DownloadManager . StartDownloadWithCallback ( url , Path ( ) , http : : ProgressBarMode : : NONE , [ cache_key ] ( http : : Request & download ) {
2023-07-02 22:21:17 +00:00
if ( download . ResultCode ( ) ! = 200 )
return ;
std : : string data ;
download . buffer ( ) . TakeAll ( & data ) ;
g_iconCache . InsertIcon ( cache_key , IconFormat : : PNG , std : : move ( data ) ) ;
} ) ;
2023-06-27 21:31:15 +00:00
}
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
Statistics GetStatistics ( ) {
return g_stats ;
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
std : : string GetGameAchievementSummary ( ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-06-15 20:17:27 +00:00
2023-06-27 21:31:15 +00:00
rc_client_user_game_summary_t summary ;
rc_client_get_user_game_summary ( g_rcClient , & summary ) ;
2023-07-14 13:24:58 +00:00
std : : string summaryString ;
if ( summary . num_core_achievements + summary . num_unofficial_achievements = = 0 ) {
summaryString = ac - > T ( " This game has no achievements " ) ;
} else {
2023-10-04 12:36:42 +00:00
summaryString = ApplySafeSubstitutions ( ac - > T ( " Earned " , " You have unlocked %1 of %2 achievements, earning %3 of %4 points " ) ,
2023-07-14 13:24:58 +00:00
summary . num_unlocked_achievements , summary . num_core_achievements + summary . num_unofficial_achievements ,
summary . points_unlocked , summary . points_core ) ;
if ( ChallengeModeActive ( ) ) {
summaryString . append ( " \n " ) ;
2023-11-30 16:33:14 +00:00
summaryString . append ( ac - > T ( " Hardcore Mode " ) ) ;
2023-07-14 13:24:58 +00:00
}
if ( EncoreModeActive ( ) ) {
summaryString . append ( " \n " ) ;
summaryString . append ( ac - > T ( " Encore Mode " ) ) ;
}
if ( UnofficialEnabled ( ) ) {
summaryString . append ( " \n " ) ;
summaryString . append ( ac - > T ( " Unofficial achievements " ) ) ;
}
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
return summaryString ;
}
2023-06-15 20:17:27 +00:00
2023-08-28 10:15:24 +00:00
// Can happen two ways.
void ShowNotLoggedInMessage ( ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
g_OSD . Show ( OSDType : : MESSAGE_ERROR , ac - > T ( " Failed to connect to RetroAchievements. Achievements will not unlock. " ) , " " , g_RAImageID , 6.0f ) ;
}
2023-06-27 21:31:15 +00:00
void identify_and_load_callback ( int result , const char * error_message , rc_client_t * client , void * userdata ) {
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-06-15 20:17:27 +00:00
2023-06-27 21:31:15 +00:00
NOTICE_LOG ( ACHIEVEMENTS , " Load callback: %d (%s) " , result , error_message ) ;
2023-06-15 20:17:27 +00:00
2023-06-27 21:31:15 +00:00
switch ( result ) {
case RC_OK :
{
// Successful! Show a message that we're active.
const rc_client_game_t * gameInfo = rc_client_get_game_info ( client ) ;
2023-06-15 20:17:27 +00:00
2023-07-02 22:14:23 +00:00
char cacheId [ 128 ] ;
snprintf ( cacheId , sizeof ( cacheId ) , " gi:%s " , gameInfo - > badge_name ) ;
2023-06-27 21:31:15 +00:00
char temp [ 512 ] ;
if ( RC_OK = = rc_client_game_get_image_url ( gameInfo , temp , sizeof ( temp ) ) ) {
2023-08-18 10:48:57 +00:00
Achievements : : DownloadImageIfMissing ( cacheId , std : : string ( temp ) ) ;
2023-06-27 21:31:15 +00:00
}
2023-07-03 12:39:49 +00:00
g_OSD . Show ( OSDType : : MESSAGE_INFO , std : : string ( gameInfo - > title ) , GetGameAchievementSummary ( ) , cacheId , 5.0f ) ;
2023-06-27 21:31:15 +00:00
break ;
}
case RC_NO_GAME_LOADED :
// The current game does not support achievements.
2023-08-25 14:36:29 +00:00
g_OSD . Show ( OSDType : : MESSAGE_INFO , ac - > T ( " RetroAchievements are not available for this game " ) , " " , g_RAImageID , 3.0f ) ;
2023-06-27 21:31:15 +00:00
break ;
2023-08-28 10:15:24 +00:00
case RC_NO_RESPONSE :
// We lost the internet connection at some point and can't report achievements.
ShowNotLoggedInMessage ( ) ;
break ;
2023-06-27 21:31:15 +00:00
default :
// Other various errors.
ERROR_LOG ( ACHIEVEMENTS , " Failed to identify/load game: %d (%s) " , result , error_message ) ;
2023-08-28 10:15:24 +00:00
g_OSD . Show ( OSDType : : MESSAGE_ERROR , ac - > T ( " Failed to identify game. Achievements will not unlock. " ) , " " , g_RAImageID , 6.0f ) ;
2023-06-27 21:31:15 +00:00
break ;
}
2023-07-03 07:18:25 +00:00
g_isIdentifying = false ;
2023-06-15 20:17:27 +00:00
}
2023-07-23 21:40:53 +00:00
bool IsReadyToStart ( ) {
return ! g_isLoggingIn ;
}
2023-09-06 08:56:51 +00:00
void SetGame ( const Path & path , IdentifiedFileType fileType , FileLoader * fileLoader ) {
switch ( fileType ) {
case IdentifiedFileType : : PSP_ISO :
case IdentifiedFileType : : PSP_ISO_NP :
// These file types are OK.
break ;
default :
// Other file types are not yet supported.
// TODO: Should we show an OSD popup here?
WARN_LOG ( ACHIEVEMENTS , " File type of '%s' is not yet compatible with RetroAchievements " , path . c_str ( ) ) ;
return ;
}
2023-07-13 10:10:20 +00:00
if ( g_isLoggingIn ) {
2023-08-28 10:15:24 +00:00
// IsReadyToStart should have been checked the same frame, so we shouldn't be here.
// Maybe there's a race condition possible, but don't think so.
2023-07-23 21:40:53 +00:00
ERROR_LOG ( ACHIEVEMENTS , " Still logging in during SetGame - shouldn't happen " ) ;
2023-07-13 10:10:20 +00:00
}
2023-06-27 21:31:15 +00:00
if ( ! g_rcClient | | ! IsLoggedIn ( ) ) {
2023-08-28 12:38:32 +00:00
if ( g_Config . bAchievementsEnable & & HasToken ( ) ) {
2023-08-28 10:15:24 +00:00
ShowNotLoggedInMessage ( ) ;
2023-08-25 13:55:31 +00:00
}
2023-06-27 21:31:15 +00:00
// Nothing to do.
return ;
2023-06-15 14:40:30 +00:00
}
2023-06-15 11:40:37 +00:00
2023-07-12 13:37:28 +00:00
_dbg_assert_ ( ! g_blockDevice ) ;
2023-07-11 23:11:09 +00:00
// TODO: Fish the block device out of the loading process somewhere else. Though, probably easier to just do it here.
g_blockDevice = constructBlockDevice ( fileLoader ) ;
if ( ! g_blockDevice ) {
ERROR_LOG ( ACHIEVEMENTS , " Failed to construct block device for '%s' - can't identify " , path . c_str ( ) ) ;
return ;
}
2023-07-03 07:18:25 +00:00
// The caller should hold off on executing game code until this turns false, checking with IsBlockingExecution()
g_isIdentifying = true ;
2023-07-02 22:14:23 +00:00
// Apply pre-load settings.
2023-07-02 22:47:54 +00:00
rc_client_set_hardcore_enabled ( g_rcClient , g_Config . bAchievementsChallengeMode ? 1 : 0 ) ;
2023-07-02 22:14:23 +00:00
rc_client_set_encore_mode_enabled ( g_rcClient , g_Config . bAchievementsEncoreMode ? 1 : 0 ) ;
2023-07-03 20:17:07 +00:00
rc_client_set_unofficial_enabled ( g_rcClient , g_Config . bAchievementsUnofficial ? 1 : 0 ) ;
2023-07-02 22:14:23 +00:00
2023-06-27 21:31:15 +00:00
rc_client_begin_identify_and_load_game ( g_rcClient , RC_CONSOLE_PSP , path . c_str ( ) , nullptr , 0 , & identify_and_load_callback , nullptr ) ;
2023-07-11 23:11:09 +00:00
// fclose above will have deleted it.
g_blockDevice = nullptr ;
2023-06-15 20:17:27 +00:00
}
2023-06-27 21:31:15 +00:00
void UnloadGame ( ) {
if ( g_rcClient ) {
rc_client_unload_game ( g_rcClient ) ;
2023-06-15 20:17:27 +00:00
}
}
2023-06-27 21:31:15 +00:00
void change_media_callback ( int result , const char * error_message , rc_client_t * client , void * userdata ) {
2023-09-12 12:17:50 +00:00
auto ac = GetI18NCategory ( I18NCat : : ACHIEVEMENTS ) ;
2023-06-27 21:31:15 +00:00
NOTICE_LOG ( ACHIEVEMENTS , " Change media callback: %d (%s) " , result , error_message ) ;
2023-07-03 13:12:30 +00:00
g_isIdentifying = false ;
2023-09-12 12:17:50 +00:00
switch ( result ) {
case RC_OK :
{
// Successful! Later, show a message that we succeeded.
break ;
}
case RC_NO_GAME_LOADED :
// The current game does not support achievements.
g_OSD . Show ( OSDType : : MESSAGE_INFO , ac - > T ( " RetroAchievements are not available for this game " ) , " " , g_RAImageID , 3.0f ) ;
break ;
case RC_NO_RESPONSE :
// We lost the internet connection at some point and can't report achievements.
ShowNotLoggedInMessage ( ) ;
break ;
default :
// Other various errors.
ERROR_LOG ( ACHIEVEMENTS , " Failed to identify/load game: %d (%s) " , result , error_message ) ;
g_OSD . Show ( OSDType : : MESSAGE_ERROR , ac - > T ( " Failed to identify game. Achievements will not unlock. " ) , " " , g_RAImageID , 6.0f ) ;
break ;
}
2023-06-15 20:17:27 +00:00
}
2023-09-12 12:17:50 +00:00
void ChangeUMD ( const Path & path , FileLoader * fileLoader ) {
2023-07-03 13:12:30 +00:00
if ( ! IsActive ( ) ) {
// Nothing to do.
return ;
}
2023-06-15 20:17:27 +00:00
2023-09-12 12:17:50 +00:00
g_blockDevice = constructBlockDevice ( fileLoader ) ;
if ( ! g_blockDevice ) {
ERROR_LOG ( ACHIEVEMENTS , " Failed to construct block device for '%s' - can't identify " , path . c_str ( ) ) ;
return ;
}
g_isIdentifying = true ;
rc_client_begin_change_media ( g_rcClient ,
2023-06-27 21:31:15 +00:00
path . c_str ( ) ,
nullptr ,
0 ,
& change_media_callback ,
nullptr
) ;
2023-07-03 13:12:30 +00:00
2023-09-12 12:17:50 +00:00
// fclose above will have deleted it.
g_blockDevice = nullptr ;
2023-06-15 20:17:27 +00:00
}
2023-07-10 08:39:44 +00:00
std : : set < uint32_t > GetActiveChallengeIDs ( ) {
return g_activeChallenges ;
}
2023-06-27 21:31:15 +00:00
} // namespace Achievements