2018-10-01 13:02:20 +00:00
/* RetroArch - A frontend for libretro.
* Copyright ( C ) 2015 - 2016 - Andre Leiradella
*
* RetroArch is free software : you can redistribute it and / or modify it under the terms
* of the GNU General Public License as published by the Free Software Found -
* ation , either version 3 of the License , or ( at your option ) any later version .
*
* RetroArch is distributed in the hope that it will be useful , but WITHOUT ANY WARRANTY ;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE . See the GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License along with RetroArch .
* If not , see < http : //www.gnu.org/licenses/>.
*/
# include <string.h>
# include <ctype.h>
# include <file/file_path.h>
# include <string/stdstring.h>
# include <streams/interface_stream.h>
# include <streams/file_stream.h>
# include <features/features_cpu.h>
2019-08-31 01:38:58 +00:00
# include <formats/cdfs.h>
2020-05-29 16:44:31 +00:00
# include <formats/m3u_file.h>
2018-10-01 13:02:20 +00:00
# include <compat/strl.h>
# include <retro_miscellaneous.h>
# include <retro_math.h>
# include <net/net_http.h>
# include <libretro.h>
# ifdef HAVE_CONFIG_H
# include "../config.h"
# endif
# ifdef HAVE_MENU
# include "../menu/menu_driver.h"
# include "../menu/menu_entries.h"
2020-02-17 20:47:09 +00:00
# endif
2020-02-17 20:28:42 +00:00
# ifdef HAVE_GFX_WIDGETS
2020-02-17 00:54:07 +00:00
# include "../gfx/gfx_widgets.h"
2019-04-11 14:46:29 +00:00
# endif
2018-10-01 13:02:20 +00:00
# ifdef HAVE_THREADS
# include <rthreads/rthreads.h>
# endif
2020-01-05 23:53:59 +00:00
# ifdef HAVE_DISCORD
2020-05-20 13:27:27 +00:00
# include "../network/discord.h"
2020-01-05 23:53:59 +00:00
# endif
2021-01-12 03:34:13 +00:00
# ifdef HAVE_CHEATS
# include "../cheat_manager.h"
# endif
2019-05-29 17:26:22 +00:00
# include "badges.h"
2018-10-01 13:02:20 +00:00
# include "cheevos.h"
2020-08-12 14:13:37 +00:00
# include "cheevos_memory.h"
# include "cheevos_parser.h"
2018-10-01 13:02:20 +00:00
# include "util.h"
# include "../file_path_special.h"
# include "../paths.h"
# include "../command.h"
# include "../dynamic.h"
# include "../configuration.h"
# include "../performance_counters.h"
# include "../msg_hash.h"
# include "../retroarch.h"
# include "../core.h"
2020-10-20 16:37:05 +00:00
# include "../core_option_manager.h"
2019-09-30 03:09:08 +00:00
# include "../version.h"
2018-10-01 13:02:20 +00:00
2019-09-30 03:09:08 +00:00
# include "../frontend/frontend_driver.h"
2018-10-01 13:02:20 +00:00
# include "../network/net_http_special.h"
# include "../tasks/tasks_internal.h"
2019-04-20 23:39:10 +00:00
# include "../deps/rcheevos/include/rcheevos.h"
# include "../deps/rcheevos/include/rurl.h"
2020-12-28 18:55:54 +00:00
# include "../deps/rcheevos/include/rc_hash.h"
2018-10-01 13:02:20 +00:00
/* Define this macro to prevent cheevos from being deactivated. */
# undef CHEEVOS_DONT_DEACTIVATE
/* Define this macro to load a JSON file from disk instead of downloading
* from retroachievements . org . */
# undef CHEEVOS_JSON_OVERRIDE
/* Define this macro with a string to save the JSON file to disk with
* that name . */
# undef CHEEVOS_SAVE_JSON
2020-05-04 15:11:16 +00:00
/* Define this macro to log URLs. */
2020-02-22 03:00:20 +00:00
# undef CHEEVOS_LOG_URLS
2018-10-01 13:02:20 +00:00
/* Define this macro to have the password and token logged. THIS WILL DISCLOSE
* THE USER ' S PASSWORD , TAKE CARE ! */
# undef CHEEVOS_LOG_PASSWORD
/* Define this macro to log downloaded badge images. */
# undef CHEEVOS_LOG_BADGES
2020-06-07 16:41:03 +00:00
/* Define this macro to capture how long it takes to generate a hash */
# undef CHEEVOS_TIME_HASH
2020-01-05 23:53:59 +00:00
/* Number of usecs to wait between posting rich presence to the site. */
/* Keep consistent with SERVER_PING_FREQUENCY from RAIntegration. */
# define CHEEVOS_PING_FREQUENCY 2 * 60 * 1000000
2020-06-07 19:18:07 +00:00
enum rcheevos_async_io_type
{
2020-08-23 13:10:20 +00:00
CHEEVOS_ASYNC_RICHPRESENCE = 0 ,
2020-06-07 19:18:07 +00:00
CHEEVOS_ASYNC_AWARD_ACHIEVEMENT ,
CHEEVOS_ASYNC_SUBMIT_LBOARD
} ;
2020-02-22 03:00:20 +00:00
typedef struct rcheevos_async_io_request
{
2020-08-23 13:10:20 +00:00
const char * success_message ;
const char * failure_message ;
2020-02-22 03:00:20 +00:00
int id ;
int value ;
int attempt_count ;
2020-08-23 13:10:20 +00:00
char user_agent [ 256 ] ;
2020-02-22 03:00:20 +00:00
char type ;
char hardcore ;
} rcheevos_async_io_request ;
2018-10-01 13:02:20 +00:00
typedef struct
{
2020-08-26 18:38:36 +00:00
rc_runtime_t runtime ;
rcheevos_rapatchdata_t patchdata ; /* ptr alignment */
rcheevos_memory_regions_t memory ; /* ptr alignment */
2018-10-01 13:02:20 +00:00
retro_task_t * task ;
# ifdef HAVE_THREADS
slock_t * task_lock ;
# endif
char token [ 32 ] ;
2020-02-01 01:37:33 +00:00
char hash [ 33 ] ;
2020-08-22 17:37:59 +00:00
char user_agent_prefix [ 128 ] ;
2020-08-23 13:10:20 +00:00
bool hardcore_active ;
bool loaded ;
bool core_supports ;
2020-09-25 20:01:07 +00:00
bool leaderboards_enabled ;
bool leaderboard_notifications ;
bool leaderboard_trackers ;
2019-03-20 01:36:54 +00:00
} rcheevos_locals_t ;
2018-10-01 13:02:20 +00:00
2019-03-20 01:36:54 +00:00
static rcheevos_locals_t rcheevos_locals =
2018-10-01 13:02:20 +00:00
{
2020-08-26 18:38:36 +00:00
{ 0 } , /* runtime */
{ 0 } , /* patchdata */
{ { 0 } } , /* memory */
2018-10-01 13:02:20 +00:00
NULL , /* task */
# ifdef HAVE_THREADS
NULL , /* task_lock */
# endif
{ 0 } , /* token */
2020-02-01 01:37:33 +00:00
" N/A " , /* hash */
2020-08-22 17:37:59 +00:00
" " , /* user_agent_prefix */
2020-08-23 13:10:20 +00:00
false , /* hardcore_active */
false , /* loaded */
true , /* core_supports */
2020-09-25 20:01:07 +00:00
false , /* leaderboards_enabled */
false , /* leaderboard_notifications */
false /* leaderboard_trackers */
2018-10-01 13:02:20 +00:00
} ;
# ifdef HAVE_THREADS
# define CHEEVOS_LOCK(l) do { slock_lock(l); } while (0)
# define CHEEVOS_UNLOCK(l) do { slock_unlock(l); } while (0)
# else
# define CHEEVOS_LOCK(l)
# define CHEEVOS_UNLOCK(l)
# endif
# define CHEEVOS_MB(x) ((x) * 1024 * 1024)
2020-08-23 19:07:15 +00:00
/* Forward declaration */
static void rcheevos_async_task_callback (
retro_task_t * task , void * task_data , void * user_data , const char * error ) ;
static void rcheevos_async_submit_lboard ( rcheevos_locals_t * locals ,
rcheevos_async_io_request * request ) ;
2018-10-01 13:02:20 +00:00
/*****************************************************************************
Supporting functions .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# ifndef CHEEVOS_VERBOSE
2019-03-20 01:36:54 +00:00
void rcheevos_log ( const char * fmt , . . . )
2018-10-01 13:02:20 +00:00
{
( void ) fmt ;
}
# endif
2020-08-23 19:07:15 +00:00
static void rcheevos_get_user_agent (
rcheevos_locals_t * locals ,
char * buffer , size_t len )
2019-09-30 03:09:08 +00:00
{
struct retro_system_info * system = runloop_get_libretro_system_info ( ) ;
const char * scan ;
char * ptr ;
2020-08-23 19:07:15 +00:00
if ( ! locals - > user_agent_prefix [ 0 ] )
2019-09-30 03:09:08 +00:00
{
const frontend_ctx_driver_t * frontend = frontend_get_ptr ( ) ;
int major , minor ;
char tmp [ 64 ] ;
2020-08-23 19:07:15 +00:00
ptr = locals - > user_agent_prefix + snprintf ( locals - > user_agent_prefix , sizeof ( locals - > user_agent_prefix ) , " RetroArch/%s " , PACKAGE_VERSION ) ;
2019-09-30 03:09:08 +00:00
if ( frontend & & frontend - > get_os )
{
frontend - > get_os ( tmp , sizeof ( tmp ) , & major , & minor ) ;
ptr + = sprintf ( ptr , " (%s %d.%d) " , tmp , major , minor ) ;
}
}
2020-08-23 19:07:15 +00:00
ptr = buffer + snprintf ( buffer , len , " %s " , locals - > user_agent_prefix ) ;
2019-09-30 03:09:08 +00:00
if ( system & & ! string_is_empty ( system - > library_name ) )
{
const char * path = path_get ( RARCH_PATH_CORE ) ;
if ( ! string_is_empty ( path ) )
{
sprintf ( ptr , " %s " , path_basename ( path ) ) ;
path_remove_extension ( ptr ) ;
ptr + = strlen ( ptr ) ;
}
else
{
* ptr + + = ' ' ;
scan = system - > library_name ;
while ( * scan )
{
if ( * scan = = ' ' )
{
* ptr + + = ' _ ' ;
+ + scan ;
}
else
* ptr + + = * scan + + ;
}
}
if ( system - > library_version )
{
* ptr + + = ' / ' ;
scan = system - > library_version ;
while ( * scan )
{
if ( * scan = = ' ' )
{
* ptr + + = ' _ ' ;
+ + scan ;
}
else
* ptr + + = * scan + + ;
}
}
}
* ptr = ' \0 ' ;
}
2018-10-01 13:02:20 +00:00
# ifdef CHEEVOS_LOG_URLS
2020-05-04 15:11:16 +00:00
static void rcheevos_filter_url_param ( char * url , char * param )
{
2020-06-07 19:48:36 +00:00
char * next ;
2020-05-04 15:11:16 +00:00
size_t param_len = strlen ( param ) ;
2020-06-07 19:48:36 +00:00
char * start = strchr ( url , ' ? ' ) ;
2020-05-04 15:11:16 +00:00
if ( ! start )
start = url ;
else
+ + start ;
2018-10-01 13:02:20 +00:00
2020-05-04 15:11:16 +00:00
do
2018-10-01 13:02:20 +00:00
{
2020-05-04 15:11:16 +00:00
next = strchr ( start , ' & ' ) ;
2018-10-01 13:02:20 +00:00
2020-05-04 15:11:16 +00:00
if ( start [ param_len ] = = ' = ' & & memcmp ( start , param , param_len ) = = 0 )
2018-10-01 13:02:20 +00:00
{
2020-05-04 15:11:16 +00:00
if ( next )
strcpy ( start , next + 1 ) ;
else if ( start > url )
start [ - 1 ] = ' \0 ' ;
else
* start = ' \0 ' ;
return ;
2018-10-01 13:02:20 +00:00
}
2020-05-04 15:11:16 +00:00
if ( ! next )
return ;
start = next + 1 ;
} while ( 1 ) ;
}
# endif
2018-10-01 13:02:20 +00:00
2020-05-04 15:11:16 +00:00
static void rcheevos_log_url ( const char * api , const char * url )
{
# ifdef CHEEVOS_LOG_URLS
2020-08-23 13:15:34 +00:00
# ifdef CHEEVOS_LOG_PASSWORD
2020-05-04 15:11:16 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " %s: %s \n " , api , url ) ;
2020-08-23 13:15:34 +00:00
# else
2020-05-04 15:11:16 +00:00
char copy [ 256 ] ;
strlcpy ( copy , url , sizeof ( copy ) ) ;
rcheevos_filter_url_param ( copy , " p " ) ;
rcheevos_filter_url_param ( copy , " t " ) ;
CHEEVOS_LOG ( RCHEEVOS_TAG " %s: %s \n " , api , copy ) ;
2020-08-23 13:15:34 +00:00
# endif
2020-05-04 15:11:16 +00:00
# else
( void ) api ;
( void ) url ;
# endif
}
2018-10-01 13:02:20 +00:00
2020-06-07 19:48:36 +00:00
static void rcheevos_log_post_url (
const char * api ,
const char * url ,
const char * post )
2020-05-04 15:11:16 +00:00
{
# ifdef CHEEVOS_LOG_URLS
# ifdef CHEEVOS_LOG_PASSWORD
if ( post & & post [ 0 ] )
CHEEVOS_LOG ( RCHEEVOS_TAG " %s: %s&%s \n " , api , url , post ) ;
else
CHEEVOS_LOG ( RCHEEVOS_TAG " %s: %s \n " , api , url ) ;
# else
if ( post & & post [ 0 ] )
2018-10-01 13:02:20 +00:00
{
2020-05-04 15:11:16 +00:00
char post_copy [ 2048 ] ;
strlcpy ( post_copy , post , sizeof ( post_copy ) ) ;
rcheevos_filter_url_param ( post_copy , " p " ) ;
rcheevos_filter_url_param ( post_copy , " t " ) ;
2018-10-01 13:02:20 +00:00
2020-05-04 15:11:16 +00:00
if ( post_copy [ 0 ] )
CHEEVOS_LOG ( RCHEEVOS_TAG " %s: %s&%s \n " , api , url , post_copy ) ;
2018-10-01 13:02:20 +00:00
else
2020-05-04 15:11:16 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " %s: %s \n " , api , url ) ;
2018-10-01 13:02:20 +00:00
}
2020-05-04 15:11:16 +00:00
else
{
CHEEVOS_LOG ( RCHEEVOS_TAG " %s: %s \n " , api , url ) ;
}
# endif
2018-10-01 13:02:20 +00:00
# else
2020-05-04 15:11:16 +00:00
( void ) api ;
2018-10-01 13:02:20 +00:00
( void ) url ;
2020-05-04 15:11:16 +00:00
( void ) post ;
2018-10-01 13:02:20 +00:00
# endif
}
2020-08-26 18:38:36 +00:00
static bool rcheevos_condset_contains_memref ( const rc_condset_t * condset , const rc_memref_value_t * memref )
{
2021-01-16 20:19:59 +00:00
if ( condset )
2020-08-26 18:38:36 +00:00
{
2021-01-16 20:19:59 +00:00
rc_condition_t * cond = NULL ;
for ( cond = condset - > conditions ; cond ; cond = cond - > next )
{
if ( cond - > operand1 . value . memref = = memref
| | cond - > operand2 . value . memref = = memref )
return true ;
}
2020-08-26 18:38:36 +00:00
}
return false ;
}
static bool rcheevos_trigger_contains_memref ( const rc_trigger_t * trigger , const rc_memref_value_t * memref )
{
rc_condset_t * condset ;
if ( ! trigger )
return false ;
if ( rcheevos_condset_contains_memref ( trigger - > requirement , memref ) )
return true ;
for ( condset = trigger - > alternative ; condset ; condset = condset - > next )
{
if ( rcheevos_condset_contains_memref ( condset , memref ) )
return true ;
}
return false ;
}
static void rcheevos_invalidate_address ( unsigned address )
{
unsigned i , count ;
2021-01-16 20:19:59 +00:00
rcheevos_racheevo_t * cheevo = NULL ;
rcheevos_ralboard_t * lboard = NULL ;
2020-08-26 18:38:36 +00:00
/* remove the invalid memref from the chain so we don't try to evaluate it in the future.
* it ' s still there , so anything referencing it will continue to fetch 0.
*/
2021-01-16 20:19:59 +00:00
rc_memref_value_t * * last_memref = & rcheevos_locals . runtime . memrefs ;
rc_memref_value_t * memref = * last_memref ;
2020-08-26 18:38:36 +00:00
do
{
if ( memref - > memref . address = = address & & ! memref - > memref . is_indirect )
{
* last_memref = memref - > next ;
break ;
}
last_memref = & memref - > next ;
2021-01-16 20:19:59 +00:00
memref = * last_memref ;
2020-08-26 18:38:36 +00:00
} while ( memref ) ;
2021-01-16 20:19:59 +00:00
/* If the address is only used indirectly,
* don ' t disable anything dependent on it */
2020-08-26 18:38:36 +00:00
if ( ! memref )
return ;
2021-01-16 20:19:59 +00:00
/* Disable any achievements dependent on the address */
2020-08-26 18:38:36 +00:00
for ( i = 0 ; i < 2 ; + + i )
{
if ( i = = 0 )
{
cheevo = rcheevos_locals . patchdata . core ;
2021-01-16 20:19:59 +00:00
count = rcheevos_locals . patchdata . core_count ;
2020-08-26 18:38:36 +00:00
}
else
{
cheevo = rcheevos_locals . patchdata . unofficial ;
2021-01-16 20:19:59 +00:00
count = rcheevos_locals . patchdata . unofficial_count ;
2020-08-26 18:38:36 +00:00
}
while ( count - - )
{
if ( cheevo - > memaddr )
{
const rc_trigger_t * trigger = rc_runtime_get_achievement ( & rcheevos_locals . runtime , cheevo - > id ) ;
if ( trigger & & rcheevos_trigger_contains_memref ( trigger , memref ) )
{
CHEEVOS_ERR ( RCHEEVOS_TAG " Achievement disabled (invalid address %06X): %s \n " , address , cheevo - > title ) ;
rc_runtime_deactivate_achievement ( & rcheevos_locals . runtime , cheevo - > id ) ;
CHEEVOS_FREE ( cheevo - > memaddr ) ;
cheevo - > memaddr = NULL ;
}
}
+ + cheevo ;
}
}
/* disable any leaderboards dependent on the address */
lboard = rcheevos_locals . patchdata . lboards ;
for ( i = 0 ; i < rcheevos_locals . patchdata . lboard_count ; + + i , + + lboard )
{
if ( lboard - > mem )
{
const rc_lboard_t * rc_lboard = rc_runtime_get_lboard ( & rcheevos_locals . runtime , lboard - > id ) ;
if ( rc_lboard & &
( rcheevos_trigger_contains_memref ( & rc_lboard - > start , memref ) | |
rcheevos_trigger_contains_memref ( & rc_lboard - > cancel , memref ) | |
rcheevos_trigger_contains_memref ( & rc_lboard - > submit , memref ) | |
rcheevos_condset_contains_memref ( rc_lboard - > value . conditions , memref ) ) )
{
CHEEVOS_ERR ( RCHEEVOS_TAG " Leaderboard disabled (invalid address %06X): %s \n " , address , lboard - > title ) ;
rc_runtime_deactivate_lboard ( & rcheevos_locals . runtime , lboard - > id ) ;
CHEEVOS_FREE ( lboard - > mem ) ;
lboard - > mem = NULL ;
}
}
}
}
2020-07-16 19:35:34 +00:00
uint8_t * rcheevos_patch_address ( unsigned address )
2020-06-07 19:18:07 +00:00
{
2020-07-16 19:35:34 +00:00
return rcheevos_memory_find ( & rcheevos_locals . memory , address ) ;
}
2020-06-07 19:18:07 +00:00
2020-07-16 19:35:34 +00:00
static unsigned rcheevos_peek ( unsigned address , unsigned num_bytes , void * ud )
{
uint8_t * data = rcheevos_memory_find ( & rcheevos_locals . memory , address ) ;
2020-08-23 19:07:15 +00:00
if ( data )
2020-06-07 19:18:07 +00:00
{
switch ( num_bytes )
{
2020-08-23 19:07:15 +00:00
case 4 :
return ( data [ 3 ] < < 24 ) | ( data [ 2 ] < < 16 ) |
( data [ 1 ] < < 8 ) | ( data [ 0 ] ) ;
case 3 :
return ( data [ 2 ] < < 16 ) | ( data [ 1 ] < < 8 ) | ( data [ 0 ] ) ;
case 2 :
return ( data [ 1 ] < < 8 ) | ( data [ 0 ] ) ;
case 1 :
return data [ 0 ] ;
2020-06-07 19:18:07 +00:00
}
}
2020-08-26 18:38:36 +00:00
rcheevos_invalidate_address ( address ) ;
2020-07-16 19:35:34 +00:00
return 0 ;
2020-06-07 19:18:07 +00:00
}
2020-08-23 19:07:15 +00:00
static void rcheevos_async_award_achievement (
rcheevos_locals_t * locals ,
rcheevos_async_io_request * request )
{
char buffer [ 256 ] ;
settings_t * settings = config_get_ptr ( ) ;
int ret = rc_url_award_cheevo ( buffer , sizeof ( buffer ) , settings - > arrays . cheevos_username , locals - > token , request - > id , request - > hardcore , locals - > hash ) ;
if ( ret ! = 0 )
{
CHEEVOS_ERR ( RCHEEVOS_TAG " Buffer too small to create URL \n " ) ;
free ( request ) ;
return ;
}
rcheevos_log_url ( " rc_url_award_cheevo " , buffer ) ;
task_push_http_transfer_with_user_agent ( buffer , true , NULL , request - > user_agent , rcheevos_async_task_callback , request ) ;
# ifdef HAVE_AUDIOMIXER
if ( settings - > bools . cheevos_unlock_sound_enable )
audio_driver_mixer_play_menu_sound ( AUDIO_MIXER_SYSTEM_SLOT_ACHIEVEMENT_UNLOCK ) ;
# endif
}
2020-02-22 03:00:20 +00:00
2020-06-07 19:48:36 +00:00
static retro_time_t rcheevos_async_send_rich_presence (
2020-08-23 19:07:15 +00:00
rcheevos_locals_t * locals ,
2020-06-07 19:48:36 +00:00
rcheevos_async_io_request * request )
2020-06-07 19:18:07 +00:00
{
2021-01-16 20:19:59 +00:00
char url [ 256 ] , post_data [ 1024 ] ;
2020-06-07 19:18:07 +00:00
settings_t * settings = config_get_ptr ( ) ;
const char * cheevos_username = settings - > arrays . cheevos_username ;
bool cheevos_richpresence_enable = settings - > bools . cheevos_richpresence_enable ;
2021-01-16 20:19:59 +00:00
int ret = rc_url_ping (
url , sizeof ( url ) , post_data , sizeof ( post_data ) ,
2020-08-23 19:07:15 +00:00
cheevos_username , locals - > token , locals - > patchdata . game_id ,
2021-01-16 20:19:59 +00:00
cheevos_richpresence_enable
? rc_runtime_get_richpresence ( & locals - > runtime )
: " " ) ;
2020-06-07 19:18:07 +00:00
2021-01-16 20:19:59 +00:00
if ( ret < 0 )
{
CHEEVOS_ERR ( RCHEEVOS_TAG " buffer too small to create URL \n " ) ;
}
else
{
rcheevos_log_post_url ( " rc_url_ping " , url , post_data ) ;
2020-06-07 19:18:07 +00:00
2021-01-16 20:19:59 +00:00
rcheevos_get_user_agent ( locals ,
request - > user_agent , sizeof ( request - > user_agent ) ) ;
task_push_http_post_transfer_with_user_agent ( url , post_data , true , " POST " , request - > user_agent , NULL , NULL ) ;
2020-06-07 19:18:07 +00:00
}
# ifdef HAVE_DISCORD
2020-08-26 18:38:36 +00:00
if ( locals - > runtime . richpresence_display_buffer )
2020-06-07 19:18:07 +00:00
{
if ( settings - > bools . discord_enable
& & discord_is_ready ( ) )
2020-06-26 14:39:03 +00:00
discord_update ( DISCORD_PRESENCE_RETROACHIEVEMENTS ) ;
2020-06-07 19:18:07 +00:00
}
# endif
/* Update rich presence every two minutes */
2020-08-26 18:38:36 +00:00
if ( cheevos_richpresence_enable )
2020-06-07 19:18:07 +00:00
return cpu_features_get_time_usec ( ) + CHEEVOS_PING_FREQUENCY ;
/* Send ping every four minutes */
return cpu_features_get_time_usec ( ) + CHEEVOS_PING_FREQUENCY * 2 ;
}
2020-02-22 03:00:20 +00:00
static void rcheevos_async_task_handler ( retro_task_t * task )
{
2020-06-07 19:48:36 +00:00
rcheevos_async_io_request * request = ( rcheevos_async_io_request * )
task - > user_data ;
2020-02-22 03:00:20 +00:00
switch ( request - > type )
{
case CHEEVOS_ASYNC_RICHPRESENCE :
2020-06-07 19:18:07 +00:00
/* update the task to fire again in two minutes */
2020-02-22 03:00:20 +00:00
if ( request - > id = = ( int ) rcheevos_locals . patchdata . game_id )
2020-08-23 19:07:15 +00:00
task - > when = rcheevos_async_send_rich_presence ( & rcheevos_locals ,
request ) ;
2020-02-22 03:00:20 +00:00
else
{
2020-06-07 19:18:07 +00:00
/* game changed; stop the recurring task - a new one will
* be scheduled for the next game */
2020-02-22 03:00:20 +00:00
task_set_finished ( task , 1 ) ;
free ( request ) ;
}
break ;
case CHEEVOS_ASYNC_AWARD_ACHIEVEMENT :
2020-08-23 19:07:15 +00:00
rcheevos_async_award_achievement ( & rcheevos_locals , request ) ;
2020-02-22 03:00:20 +00:00
task_set_finished ( task , 1 ) ;
break ;
2020-02-29 15:42:17 +00:00
case CHEEVOS_ASYNC_SUBMIT_LBOARD :
2020-08-23 19:07:15 +00:00
rcheevos_async_submit_lboard ( & rcheevos_locals , request ) ;
2020-02-29 15:42:17 +00:00
task_set_finished ( task , 1 ) ;
break ;
2020-02-22 03:00:20 +00:00
}
}
2020-06-07 19:48:36 +00:00
static void rcheevos_async_schedule (
rcheevos_async_io_request * request , retro_time_t delay )
2020-02-22 03:00:20 +00:00
{
retro_task_t * task = task_init ( ) ;
2020-06-07 19:48:36 +00:00
task - > when = cpu_features_get_time_usec ( ) + delay ;
task - > handler = rcheevos_async_task_handler ;
task - > user_data = request ;
task - > progress = - 1 ;
2020-02-22 03:00:20 +00:00
task_queue_push ( task ) ;
}
2020-06-07 19:48:36 +00:00
static void rcheevos_async_task_callback (
retro_task_t * task , void * task_data , void * user_data , const char * error )
2020-02-29 15:42:17 +00:00
{
rcheevos_async_io_request * request = ( rcheevos_async_io_request * ) user_data ;
if ( ! error )
{
2020-06-03 01:55:44 +00:00
char buffer [ 224 ] ;
const http_transfer_data_t * data = ( http_transfer_data_t * ) task - > task_data ;
if ( rcheevos_get_json_error ( data - > data , buffer , sizeof ( buffer ) ) = = RC_OK )
{
char errbuf [ 256 ] ;
snprintf ( errbuf , sizeof ( errbuf ) , " %s %u: %s " , request - > failure_message , request - > id , buffer ) ;
CHEEVOS_LOG ( RCHEEVOS_TAG " %s \n " , errbuf ) ;
switch ( request - > type )
{
case CHEEVOS_ASYNC_RICHPRESENCE :
/* don't bother informing user when rich presence update fails */
break ;
case CHEEVOS_ASYNC_AWARD_ACHIEVEMENT :
/* ignore already unlocked */
2020-06-25 10:51:04 +00:00
if ( string_starts_with_size ( buffer , " User already has " ,
STRLEN_CONST ( " User already has " ) ) )
2020-06-03 01:55:44 +00:00
break ;
/* fallthrough to default */
default :
runloop_msg_queue_push ( errbuf , 0 , 5 * 60 , false , NULL ,
MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_ERROR ) ;
break ;
}
}
else
{
CHEEVOS_LOG ( RCHEEVOS_TAG " %s %u \n " , request - > success_message , request - > id ) ;
}
2020-02-29 15:42:17 +00:00
free ( request ) ;
}
else
{
/* double the wait between each attempt until we hit a maximum delay of two minutes
* 250 ms - > 500 ms - > 1 s - > 2 s - > 4 s - > 8 s - > 16 s - > 32 s - > 64 s - > 120 s - > 120 s . . . */
retro_time_t retry_delay = ( request - > attempt_count > 8 ) ? ( 120 * 1000 * 1000 ) : ( ( 250 * 1000 ) < < request - > attempt_count ) ;
request - > attempt_count + + ;
rcheevos_async_schedule ( request , retry_delay ) ;
CHEEVOS_ERR ( RCHEEVOS_TAG " %s %u: %s \n " , request - > failure_message , request - > id , error ) ;
}
}
2020-09-21 23:48:14 +00:00
static void rcheevos_activate_achievements ( rcheevos_locals_t * locals ,
rcheevos_racheevo_t * cheevo , unsigned count , unsigned flags )
{
settings_t * settings = config_get_ptr ( ) ;
char buffer [ 256 ] ;
unsigned i ;
int res ;
for ( i = 0 ; i < count ; i + + , cheevo + + )
{
res = rc_runtime_activate_achievement ( & locals - > runtime , cheevo - > id , cheevo - > memaddr , NULL , 0 ) ;
if ( res < 0 )
{
snprintf ( buffer , sizeof ( buffer ) ,
" Could not activate achievement %d \" %s \" : %s " ,
cheevo - > id , cheevo - > title , rc_error_str ( res ) ) ;
if ( settings - > bools . cheevos_verbose_enable )
runloop_msg_queue_push ( buffer , 0 , 4 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
CHEEVOS_ERR ( RCHEEVOS_TAG " %s: mem %s \n " , buffer , cheevo - > memaddr ) ;
CHEEVOS_FREE ( cheevo - > memaddr ) ;
cheevo - > memaddr = NULL ;
continue ;
}
cheevo - > active = RCHEEVOS_ACTIVE_SOFTCORE | RCHEEVOS_ACTIVE_HARDCORE | flags ;
}
}
2021-01-16 20:19:59 +00:00
static int rcheevos_parse ( rcheevos_locals_t * locals , const char * json )
2018-10-01 13:02:20 +00:00
{
2019-05-03 05:19:13 +00:00
char buffer [ 256 ] ;
2020-09-22 02:52:31 +00:00
settings_t * settings = config_get_ptr ( ) ;
unsigned j = 0 ;
unsigned count = 0 ;
2020-08-26 18:38:36 +00:00
rcheevos_ralboard_t * lboard = NULL ;
2020-09-22 02:52:31 +00:00
int res = rcheevos_get_patchdata (
2020-08-23 19:07:15 +00:00
json , & locals - > patchdata ) ;
2018-10-01 13:02:20 +00:00
if ( res ! = 0 )
{
2020-02-23 03:58:11 +00:00
char * ptr = buffer + snprintf ( buffer , sizeof ( buffer ) , " Error retrieving achievement data: " ) ;
/* extract the Error field from the JSON. if not found, remove the colon from the message */
if ( rcheevos_get_json_error ( json , ptr , sizeof ( buffer ) - ( ptr - buffer ) ) = = - 1 )
2021-01-16 20:19:59 +00:00
{
ptr [ - 2 ] = ' \0 ' ; /* TODO/FIXME - writing 1 byte into a region of size 0 [-Wstringop-overflow=] at offset -2 to object 'buffer' with size 256 declared here */
}
2020-02-23 03:58:11 +00:00
runloop_msg_queue_push ( buffer , 0 , 5 * 60 , false , NULL ,
MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_WARNING ) ;
RARCH_ERR ( RCHEEVOS_TAG " %s " , buffer ) ;
2018-10-01 13:02:20 +00:00
return - 1 ;
}
2020-08-23 19:07:15 +00:00
if ( locals - > patchdata . core_count = = 0
& & locals - > patchdata . unofficial_count = = 0
& & locals - > patchdata . lboard_count = = 0 )
2018-10-01 13:02:20 +00:00
{
2020-08-23 19:07:15 +00:00
rcheevos_free_patchdata ( & locals - > patchdata ) ;
2018-10-01 13:02:20 +00:00
return 0 ;
}
2020-08-23 19:07:15 +00:00
if ( ! rcheevos_memory_init ( & locals - > memory , locals - > patchdata . console_id ) )
2019-12-30 03:49:01 +00:00
{
2020-08-23 19:07:15 +00:00
/* some cores (like Mupen64-Plus) don't expose the
* memory until the first call to retro_run .
* in that case , there will be a total_size of
* memory reported by the core , but init will return
2020-07-16 19:35:34 +00:00
* false , as all of the pointers were null .
*/
2020-08-23 19:07:15 +00:00
/* reset the memory count and we'll re-evaluate in rcheevos_test() */
if ( locals - > memory . total_size ! = 0 )
locals - > memory . count = 0 ;
2020-02-06 01:16:34 +00:00
else
2020-01-29 02:48:56 +00:00
{
2020-08-23 19:07:15 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " No memory exposed by core. \n " ) ;
2020-11-17 04:29:29 +00:00
rcheevos_locals . core_supports = false ;
2019-12-30 03:49:01 +00:00
2020-01-29 02:48:56 +00:00
if ( settings - > bools . cheevos_verbose_enable )
2020-11-17 04:29:29 +00:00
{
runloop_msg_queue_push ( msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE ) ,
0 , 4 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_WARNING ) ;
}
2019-12-30 03:49:01 +00:00
2020-01-29 02:48:56 +00:00
goto error ;
}
2019-12-30 03:49:01 +00:00
}
2018-10-01 13:02:20 +00:00
/* Initialize. */
2020-09-21 23:48:14 +00:00
rcheevos_activate_achievements ( locals , locals - > patchdata . core , locals - > patchdata . core_count , 0 ) ;
if ( settings - > bools . cheevos_test_unofficial )
2018-10-01 13:02:20 +00:00
{
2020-09-21 23:48:14 +00:00
rcheevos_activate_achievements ( locals , locals - > patchdata . unofficial ,
locals - > patchdata . unofficial_count , RCHEEVOS_ACTIVE_UNOFFICIAL ) ;
}
2020-09-25 20:01:07 +00:00
if ( locals - > hardcore_active & & locals - > leaderboards_enabled )
2020-09-21 23:48:14 +00:00
{
lboard = locals - > patchdata . lboards ;
count = locals - > patchdata . lboard_count ;
2018-10-01 13:02:20 +00:00
2020-09-21 23:48:14 +00:00
for ( j = 0 ; j < count ; j + + , lboard + + )
2018-10-01 13:02:20 +00:00
{
2020-09-21 23:48:14 +00:00
res = rc_runtime_activate_lboard ( & locals - > runtime , lboard - > id , lboard - > mem , NULL , 0 ) ;
2018-10-01 13:02:20 +00:00
if ( res < 0 )
{
2020-06-07 19:48:36 +00:00
snprintf ( buffer , sizeof ( buffer ) ,
2020-09-21 23:48:14 +00:00
" Could not activate leaderboard %d \" %s \" : %s " ,
lboard - > id , lboard - > title , rc_error_str ( res ) ) ;
2019-11-11 02:42:00 +00:00
if ( settings - > bools . cheevos_verbose_enable )
runloop_msg_queue_push ( buffer , 0 , 4 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2020-09-21 23:48:14 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " %s mem: %s \n " , buffer , lboard - > mem ) ;
CHEEVOS_FREE ( lboard - > mem ) ;
lboard - > mem = NULL ;
2019-11-11 02:42:00 +00:00
continue ;
2018-10-01 13:02:20 +00:00
}
}
}
2020-08-26 18:38:36 +00:00
res = RC_MISSING_DISPLAY_STRING ;
2020-08-23 19:07:15 +00:00
if ( locals - > patchdata . richpresence_script
& & * locals - > patchdata . richpresence_script )
2020-01-05 23:53:59 +00:00
{
2020-08-26 18:38:36 +00:00
res = rc_runtime_activate_richpresence ( & locals - > runtime , locals - > patchdata . richpresence_script , NULL , 0 ) ;
2020-08-23 19:07:15 +00:00
2020-08-26 18:38:36 +00:00
if ( res < 0 )
2020-01-05 23:53:59 +00:00
{
2020-06-07 19:48:36 +00:00
snprintf ( buffer , sizeof ( buffer ) ,
2020-08-26 18:38:36 +00:00
" Could not activate rich presence: %s " ,
rc_error_str ( res ) ) ;
2020-01-05 23:53:59 +00:00
2020-02-15 03:12:49 +00:00
if ( settings - > bools . cheevos_verbose_enable )
runloop_msg_queue_push ( buffer , 0 , 4 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2020-01-05 23:53:59 +00:00
2020-02-15 03:12:49 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " %s \n " , buffer ) ;
2020-01-05 23:53:59 +00:00
}
2020-08-26 18:38:36 +00:00
}
if ( res < 0 & & locals - > patchdata . title )
{
size_t len = strlen ( locals - > patchdata . title ) ;
if ( ! locals - > runtime . richpresence_display_buffer )
locals - > runtime . richpresence_display_buffer = ( char * ) malloc ( len + 9 ) ;
if ( locals - > runtime . richpresence_display_buffer )
2020-01-05 23:53:59 +00:00
{
2020-08-26 18:38:36 +00:00
memcpy ( locals - > runtime . richpresence_display_buffer , " Playing " , 8 ) ;
memcpy ( & locals - > runtime . richpresence_display_buffer [ 8 ] , locals - > patchdata . title , len + 1 ) ;
2020-01-05 23:53:59 +00:00
}
2020-02-15 03:12:49 +00:00
}
2020-02-22 03:00:20 +00:00
/* schedule the first rich presence call in 30 seconds */
{
2020-06-07 19:48:36 +00:00
rcheevos_async_io_request * request = ( rcheevos_async_io_request * )
calloc ( 1 , sizeof ( rcheevos_async_io_request ) ) ;
2020-08-23 19:07:15 +00:00
request - > id = locals - > patchdata . game_id ;
2020-06-07 19:48:36 +00:00
request - > type = CHEEVOS_ASYNC_RICHPRESENCE ;
2020-02-22 03:00:20 +00:00
rcheevos_async_schedule ( request , CHEEVOS_PING_FREQUENCY / 4 ) ;
}
2018-10-01 13:02:20 +00:00
return 0 ;
error :
2020-08-23 19:07:15 +00:00
rcheevos_free_patchdata ( & locals - > patchdata ) ;
rcheevos_memory_destroy ( & locals - > memory ) ;
2018-10-01 13:02:20 +00:00
return - 1 ;
}
2020-08-26 18:38:36 +00:00
static rcheevos_racheevo_t * rcheevos_find_cheevo ( unsigned id )
{
unsigned i ;
rcheevos_racheevo_t * cheevo ;
cheevo = rcheevos_locals . patchdata . core ;
for ( i = 0 ; i < rcheevos_locals . patchdata . core_count ; i + + , cheevo + + )
{
if ( cheevo - > id = = id )
return cheevo ;
}
cheevo = rcheevos_locals . patchdata . unofficial ;
for ( i = 0 ; i < rcheevos_locals . patchdata . unofficial_count ; i + + , cheevo + + )
{
if ( cheevo - > id = = id )
return cheevo ;
}
return NULL ;
}
2020-07-11 16:56:09 +00:00
2020-08-26 18:38:36 +00:00
static void rcheevos_award_achievement ( rcheevos_locals_t * locals , rcheevos_racheevo_t * cheevo )
2018-10-01 13:02:20 +00:00
{
2020-08-26 18:38:36 +00:00
char buffer [ 256 ] = " " ;
if ( ! cheevo )
return ;
2018-10-01 13:02:20 +00:00
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Awarding achievement %u: %s (%s) \n " ,
cheevo - > id , cheevo - > title , cheevo - > description ) ;
2018-10-01 13:02:20 +00:00
/* Deactivates the cheevo. */
2020-08-26 18:38:36 +00:00
rc_runtime_deactivate_achievement ( & locals - > runtime , cheevo - > id ) ;
2018-10-01 13:02:20 +00:00
2020-08-26 18:38:36 +00:00
cheevo - > active & = ~ RCHEEVOS_ACTIVE_SOFTCORE ;
if ( locals - > hardcore_active )
cheevo - > active & = ~ RCHEEVOS_ACTIVE_HARDCORE ;
2018-10-01 13:02:20 +00:00
/* Show the OSD message. */
2019-04-11 14:46:29 +00:00
{
2020-02-17 20:28:42 +00:00
# if defined(HAVE_GFX_WIDGETS)
2020-02-17 00:43:40 +00:00
bool widgets_ready = gfx_widgets_ready ( ) ;
2019-08-15 14:09:18 +00:00
if ( widgets_ready )
2020-08-26 18:38:36 +00:00
gfx_widgets_push_achievement ( cheevo - > title , cheevo - > badge ) ;
2019-08-15 14:09:18 +00:00
else
2019-08-15 13:48:08 +00:00
# endif
2019-08-15 14:09:18 +00:00
{
2020-08-26 18:38:36 +00:00
snprintf ( buffer , sizeof ( buffer ) , " Achievement Unlocked: %s " , cheevo - > title ) ;
2019-08-15 14:09:18 +00:00
runloop_msg_queue_push ( buffer , 0 , 2 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2020-08-26 18:38:36 +00:00
runloop_msg_queue_push ( cheevo - > description , 0 , 3 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2019-08-15 14:09:18 +00:00
}
2019-04-11 14:46:29 +00:00
}
2018-10-01 13:02:20 +00:00
2020-08-26 18:38:36 +00:00
/* Start the award task (unofficial achievement unlocks are not submitted). */
if ( ! ( cheevo - > active & RCHEEVOS_ACTIVE_UNOFFICIAL ) )
2020-02-22 03:00:20 +00:00
{
2020-06-07 19:48:36 +00:00
rcheevos_async_io_request * request = ( rcheevos_async_io_request * ) calloc ( 1 , sizeof ( rcheevos_async_io_request ) ) ;
request - > type = CHEEVOS_ASYNC_AWARD_ACHIEVEMENT ;
2020-08-26 18:38:36 +00:00
request - > id = cheevo - > id ;
request - > hardcore = locals - > hardcore_active ? 1 : 0 ;
2020-02-29 15:42:17 +00:00
request - > success_message = " Awarded achievement " ;
request - > failure_message = " Error awarding achievement " ;
2020-08-23 19:07:15 +00:00
rcheevos_get_user_agent ( locals ,
request - > user_agent , sizeof ( request - > user_agent ) ) ;
rcheevos_async_award_achievement ( locals , request ) ;
2020-02-22 03:00:20 +00:00
}
2018-10-01 13:02:20 +00:00
2020-07-01 15:47:13 +00:00
# ifdef HAVE_SCREENSHOTS
2018-10-01 13:02:20 +00:00
{
2020-07-01 15:47:13 +00:00
settings_t * settings = config_get_ptr ( ) ;
/* Take a screenshot of the achievement. */
if ( settings & & settings - > bools . cheevos_auto_screenshot )
{
char shotname [ 8192 ] ;
snprintf ( shotname , sizeof ( shotname ) , " %s/%s-cheevo-%u " ,
settings - > paths . directory_screenshot ,
path_basename ( path_get ( RARCH_PATH_BASENAME ) ) ,
2020-08-26 18:38:36 +00:00
cheevo - > id ) ;
2020-07-01 15:47:13 +00:00
shotname [ sizeof ( shotname ) - 1 ] = ' \0 ' ;
if ( take_screenshot ( settings - > paths . directory_screenshot ,
shotname , true ,
video_driver_cached_frame_has_valid_framebuffer ( ) , false , true ) )
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Captured screenshot for achievement %u \n " , cheevo - > id ) ;
2020-07-01 15:47:13 +00:00
else
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Failed to capture screenshot for achievement %u \n " , cheevo - > id ) ;
2020-07-01 15:47:13 +00:00
}
2018-10-01 13:02:20 +00:00
}
2020-07-01 15:47:13 +00:00
# endif
2018-10-01 13:02:20 +00:00
}
2020-08-23 19:07:15 +00:00
static void rcheevos_async_submit_lboard ( rcheevos_locals_t * locals ,
rcheevos_async_io_request * request )
2018-10-01 13:02:20 +00:00
{
char buffer [ 256 ] ;
2020-02-29 15:42:17 +00:00
settings_t * settings = config_get_ptr ( ) ;
2020-08-23 19:07:15 +00:00
int ret = rc_url_submit_lboard ( buffer , sizeof ( buffer ) ,
settings - > arrays . cheevos_username ,
locals - > token , request - > id , request - > value ) ;
2018-10-01 13:02:20 +00:00
if ( ret ! = 0 )
{
2020-02-29 15:42:17 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " Buffer too small to create URL \n " ) ;
free ( request ) ;
2018-10-01 13:02:20 +00:00
return ;
}
2020-05-04 15:11:16 +00:00
rcheevos_log_url ( " rc_url_submit_lboard " , buffer ) ;
2020-02-29 15:42:17 +00:00
task_push_http_transfer_with_user_agent ( buffer , true , NULL , request - > user_agent , rcheevos_async_task_callback , request ) ;
2018-10-01 13:02:20 +00:00
}
2020-08-26 18:38:36 +00:00
static rcheevos_ralboard_t * rcheevos_find_lboard ( unsigned id )
{
rcheevos_ralboard_t * lboard = rcheevos_locals . patchdata . lboards ;
unsigned i ;
for ( i = 0 ; i < rcheevos_locals . patchdata . lboard_count ; + + i , + + lboard )
{
if ( lboard - > id = = id )
return lboard ;
}
return NULL ;
}
2020-08-23 19:07:15 +00:00
static void rcheevos_lboard_submit ( rcheevos_locals_t * locals ,
2020-08-26 18:38:36 +00:00
rcheevos_ralboard_t * lboard , int value )
2018-10-01 13:02:20 +00:00
{
char buffer [ 256 ] ;
2020-08-26 18:38:36 +00:00
char formatted_value [ 16 ] ;
2018-10-01 13:02:20 +00:00
2020-09-25 20:01:07 +00:00
/* Show the OSD message (regardless of notifications setting). */
2020-08-26 18:38:36 +00:00
rc_format_value ( formatted_value , sizeof ( formatted_value ) , value , lboard - > format ) ;
2018-10-01 13:02:20 +00:00
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Submitting %s for leaderboard %u \n " , formatted_value , lboard - > id ) ;
2018-10-01 13:02:20 +00:00
snprintf ( buffer , sizeof ( buffer ) , " Submitted %s for %s " ,
2020-08-26 18:38:36 +00:00
formatted_value , lboard - > title ) ;
2018-11-22 14:45:52 +00:00
runloop_msg_queue_push ( buffer , 0 , 2 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2018-10-01 13:02:20 +00:00
2020-09-24 15:25:56 +00:00
# if defined(HAVE_GFX_WIDGETS)
/* Hide the tracker */
if ( gfx_widgets_ready ( ) )
gfx_widgets_set_leaderboard_display ( lboard - > id , NULL ) ;
# endif
2018-10-01 13:02:20 +00:00
/* Start the submit task. */
2020-02-29 15:42:17 +00:00
{
rcheevos_async_io_request * request = ( rcheevos_async_io_request * ) calloc ( 1 , sizeof ( rcheevos_async_io_request ) ) ;
request - > type = CHEEVOS_ASYNC_SUBMIT_LBOARD ;
2020-08-26 18:38:36 +00:00
request - > id = lboard - > id ;
request - > value = value ;
2020-02-29 15:42:17 +00:00
request - > success_message = " Submitted leaderboard " ;
request - > failure_message = " Error submitting leaderboard " ;
2020-08-23 19:07:15 +00:00
rcheevos_get_user_agent ( locals ,
request - > user_agent , sizeof ( request - > user_agent ) ) ;
rcheevos_async_submit_lboard ( locals , request ) ;
2020-02-29 15:42:17 +00:00
}
2018-10-01 13:02:20 +00:00
}
2020-08-26 18:38:36 +00:00
static void rcheevos_lboard_canceled ( rcheevos_ralboard_t * lboard )
2018-10-01 13:02:20 +00:00
{
2020-08-26 18:38:36 +00:00
char buffer [ 256 ] ;
if ( ! lboard )
return ;
2019-04-12 00:50:24 +00:00
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Leaderboard %u canceled: %s \n " , lboard - > id , lboard - > title ) ;
2018-10-01 13:02:20 +00:00
2020-09-24 15:25:56 +00:00
# if defined(HAVE_GFX_WIDGETS)
if ( gfx_widgets_ready ( ) )
gfx_widgets_set_leaderboard_display ( lboard - > id , NULL ) ;
# endif
2020-09-25 20:01:07 +00:00
if ( rcheevos_locals . leaderboard_notifications )
{
snprintf ( buffer , sizeof ( buffer ) , " Leaderboard attempt failed: %s " , lboard - > title ) ;
runloop_msg_queue_push ( buffer , 0 , 2 * 60 , false , NULL ,
MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
}
2020-08-26 18:38:36 +00:00
}
2018-10-01 13:02:20 +00:00
2020-09-24 15:25:56 +00:00
static void rcheevos_lboard_started ( rcheevos_ralboard_t * lboard , int value )
2020-08-26 18:38:36 +00:00
{
char buffer [ 256 ] ;
if ( ! lboard )
return ;
2019-12-11 03:42:27 +00:00
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Leaderboard %u started: %s \n " , lboard - > id , lboard - > title ) ;
2019-12-26 16:53:05 +00:00
2020-09-24 15:25:56 +00:00
# if defined(HAVE_GFX_WIDGETS)
2020-09-25 20:01:07 +00:00
if ( gfx_widgets_ready ( ) & & rcheevos_locals . leaderboard_trackers )
2020-09-24 15:25:56 +00:00
{
rc_format_value ( buffer , sizeof ( buffer ) , value , lboard - > format ) ;
gfx_widgets_set_leaderboard_display ( lboard - > id , buffer ) ;
}
# endif
2020-09-25 20:01:07 +00:00
if ( rcheevos_locals . leaderboard_notifications )
{
if ( lboard - > description & & * lboard - > description )
snprintf ( buffer , sizeof ( buffer ) , " Leaderboard attempt started: %s - %s " , lboard - > title , lboard - > description ) ;
else
snprintf ( buffer , sizeof ( buffer ) , " Leaderboard attempt started: %s " , lboard - > title ) ;
2019-12-26 16:53:05 +00:00
2020-09-25 20:01:07 +00:00
runloop_msg_queue_push ( buffer , 0 , 2 * 60 , false , NULL ,
MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
}
2018-10-01 13:02:20 +00:00
}
2020-09-24 15:25:56 +00:00
# if defined(HAVE_GFX_WIDGETS)
static void rcheevos_lboard_updated ( rcheevos_ralboard_t * lboard , int value )
{
if ( ! lboard )
return ;
2020-09-25 20:01:07 +00:00
if ( gfx_widgets_ready ( ) & & rcheevos_locals . leaderboard_trackers )
2020-09-24 15:25:56 +00:00
{
char buffer [ 32 ] ;
rc_format_value ( buffer , sizeof ( buffer ) , value , lboard - > format ) ;
gfx_widgets_set_leaderboard_display ( lboard - > id , buffer ) ;
}
}
# endif
2020-01-05 23:53:59 +00:00
const char * rcheevos_get_richpresence ( void )
{
2020-08-26 18:38:36 +00:00
return rc_runtime_get_richpresence ( & rcheevos_locals . runtime ) ;
2020-01-05 23:53:59 +00:00
}
2019-03-17 01:13:57 +00:00
void rcheevos_reset_game ( void )
2018-10-01 13:02:20 +00:00
{
2020-09-24 15:25:56 +00:00
# if defined(HAVE_GFX_WIDGETS)
/* Hide any visible trackers */
if ( gfx_widgets_ready ( ) )
{
rcheevos_ralboard_t * lboard = rcheevos_locals . patchdata . lboards ;
unsigned i ;
for ( i = 0 ; i < rcheevos_locals . patchdata . lboard_count ; + + i , + + lboard )
gfx_widgets_set_leaderboard_display ( lboard - > id , NULL ) ;
}
# endif
2020-08-26 18:38:36 +00:00
rc_runtime_reset ( & rcheevos_locals . runtime ) ;
2020-07-16 19:35:34 +00:00
/* some cores reallocate memory on reset, make sure we update our pointers */
if ( rcheevos_locals . memory . total_size > 0 )
rcheevos_memory_init ( & rcheevos_locals . memory , rcheevos_locals . patchdata . console_id ) ;
2018-10-01 13:02:20 +00:00
}
2019-11-11 02:42:00 +00:00
# ifdef HAVE_MENU
2020-08-23 19:07:15 +00:00
void rcheevos_get_achievement_state ( unsigned index ,
char * buffer , size_t len )
2019-11-11 02:42:00 +00:00
{
2020-08-26 18:38:36 +00:00
rcheevos_racheevo_t * cheevo ;
2020-02-24 14:15:37 +00:00
enum msg_hash_enums enum_idx ;
bool check_measured = false ;
2019-11-11 02:42:00 +00:00
2020-02-24 14:15:37 +00:00
if ( index < rcheevos_locals . patchdata . core_count )
{
enum_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_LOCKED_ENTRY ;
2020-08-26 18:38:36 +00:00
cheevo = rcheevos_locals . patchdata . core ? & rcheevos_locals . patchdata . core [ index ] : NULL ;
2020-02-24 14:15:37 +00:00
}
else
{
enum_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNOFFICIAL_ENTRY ;
2020-08-26 18:38:36 +00:00
cheevo = rcheevos_locals . patchdata . unofficial ? & rcheevos_locals . patchdata . unofficial [ index - rcheevos_locals . patchdata . core_count ] : NULL ;
2020-02-24 14:15:37 +00:00
}
2020-08-26 18:38:36 +00:00
if ( ! cheevo | | ! cheevo - > memaddr )
2020-02-24 14:15:37 +00:00
enum_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNSUPPORTED_ENTRY ;
2020-06-22 22:29:55 +00:00
else if ( ! ( cheevo - > active & RCHEEVOS_ACTIVE_HARDCORE ) )
enum_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNLOCKED_ENTRY_HARDCORE ;
else if ( ! ( cheevo - > active & RCHEEVOS_ACTIVE_SOFTCORE ) )
{
/* if in hardcore mode, track progress towards hardcore unlock */
2020-08-22 17:37:59 +00:00
check_measured = rcheevos_locals . hardcore_active ;
2020-06-22 22:29:55 +00:00
enum_idx = MENU_ENUM_LABEL_VALUE_CHEEVOS_UNLOCKED_ENTRY ;
}
2020-08-23 19:07:15 +00:00
/* Use either "Locked" for core or "Unofficial" for unofficial as set above and track progress */
2019-11-11 02:42:00 +00:00
else
2020-06-22 22:29:55 +00:00
check_measured = true ;
2020-02-24 14:15:37 +00:00
2020-08-23 19:07:15 +00:00
strlcpy ( buffer , msg_hash_to_str ( enum_idx ) , len ) ;
2020-02-24 14:15:37 +00:00
if ( check_measured )
{
2020-08-26 18:38:36 +00:00
const rc_trigger_t * trigger = rc_runtime_get_achievement ( & rcheevos_locals . runtime , cheevo - > id ) ;
const unsigned int target = trigger - > measured_target ;
if ( target > 0 & & trigger - > measured_value > 0 )
2020-02-24 14:15:37 +00:00
{
char measured_buffer [ 12 ] ;
2020-08-26 18:38:36 +00:00
const unsigned int value = MIN ( trigger - > measured_value , target ) ;
2020-06-07 19:18:07 +00:00
const int percent = ( int ) ( ( ( unsigned long ) value ) * 100 / target ) ;
2020-02-24 14:15:37 +00:00
snprintf ( measured_buffer , sizeof ( measured_buffer ) , " - %d%% " , percent ) ;
2020-08-23 19:07:15 +00:00
strlcat ( buffer , measured_buffer , len ) ;
2020-02-24 14:15:37 +00:00
}
2019-11-11 02:42:00 +00:00
}
2020-02-24 14:15:37 +00:00
}
2020-06-07 19:18:07 +00:00
static void rcheevos_append_menu_achievement (
2020-08-26 18:38:36 +00:00
menu_displaylist_info_t * info , size_t idx , rcheevos_racheevo_t * cheevo )
2020-02-24 14:15:37 +00:00
{
bool badge_grayscale ;
2019-11-11 02:42:00 +00:00
2020-08-26 18:38:36 +00:00
menu_entries_append_enum ( info - > list , cheevo - > title ,
cheevo - > description , MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY ,
2019-11-11 02:42:00 +00:00
MENU_SETTINGS_CHEEVOS_START + idx , 0 , 0 ) ;
2020-08-26 18:38:36 +00:00
if ( ! cheevo - > memaddr )
2020-08-23 19:07:15 +00:00
badge_grayscale = true ; /* unsupported */
2020-06-07 19:18:07 +00:00
else if ( ! ( cheevo - > active & RCHEEVOS_ACTIVE_HARDCORE ) | |
! ( cheevo - > active & RCHEEVOS_ACTIVE_SOFTCORE ) )
badge_grayscale = false ; /* unlocked */
2020-02-24 14:15:37 +00:00
else
2020-06-07 19:18:07 +00:00
badge_grayscale = true ; /* locked */
2020-02-24 14:15:37 +00:00
2020-08-26 18:38:36 +00:00
cheevos_set_menu_badge ( idx , cheevo - > badge , badge_grayscale ) ;
2019-11-11 02:42:00 +00:00
}
# endif
2020-12-26 18:12:09 +00:00
void rcheevos_populate_hardcore_pause_menu ( void * data )
{
# ifdef HAVE_MENU
menu_displaylist_info_t * info = ( menu_displaylist_info_t * ) data ;
settings_t * settings = config_get_ptr ( ) ;
bool cheevos_hardcore_mode_enable = settings - > bools . cheevos_hardcore_mode_enable ;
if ( cheevos_hardcore_mode_enable & & rcheevos_locals . loaded )
{
if ( rcheevos_locals . hardcore_active )
{
menu_entries_append_enum ( info - > list ,
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE_CANCEL ) ,
msg_hash_to_str ( MENU_ENUM_SUBLABEL_ACHIEVEMENT_PAUSE_CANCEL ) ,
MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_CANCEL ,
MENU_SETTING_ACTION_CLOSE , 0 , 0 ) ;
menu_entries_append_enum ( info - > list ,
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE ) ,
msg_hash_to_str ( MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE ) ,
MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE ,
MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS , 0 , 0 ) ;
}
else
{
menu_entries_append_enum ( info - > list ,
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME_CANCEL ) ,
msg_hash_to_str ( MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME_CANCEL ) ,
MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_CANCEL ,
MENU_SETTING_ACTION_CLOSE , 0 , 0 ) ;
menu_entries_append_enum ( info - > list ,
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME ) ,
msg_hash_to_str ( MENU_ENUM_LABEL_ACHIEVEMENT_RESUME ) ,
MENU_ENUM_LABEL_ACHIEVEMENT_RESUME ,
MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS , 0 , 0 ) ;
}
}
# endif
}
2019-03-17 01:13:57 +00:00
void rcheevos_populate_menu ( void * data )
2018-10-01 13:02:20 +00:00
{
# ifdef HAVE_MENU
2020-02-19 21:20:24 +00:00
int i = 0 ;
int count = 0 ;
2020-08-26 18:38:36 +00:00
rcheevos_racheevo_t * cheevo = NULL ;
2020-02-19 21:20:24 +00:00
menu_displaylist_info_t * info = ( menu_displaylist_info_t * ) data ;
settings_t * settings = config_get_ptr ( ) ;
bool cheevos_enable = settings - > bools . cheevos_enable ;
bool cheevos_hardcore_mode_enable = settings - > bools . cheevos_hardcore_mode_enable ;
bool cheevos_test_unofficial = settings - > bools . cheevos_test_unofficial ;
if ( cheevos_enable
& & cheevos_hardcore_mode_enable
2020-08-22 17:37:59 +00:00
& & rcheevos_locals . loaded )
2018-10-01 13:02:20 +00:00
{
2020-08-22 17:37:59 +00:00
if ( rcheevos_locals . hardcore_active )
2018-10-01 13:02:20 +00:00
menu_entries_append_enum ( info - > list ,
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE ) ,
2020-12-26 18:12:09 +00:00
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE_MENU ) ,
MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU ,
2018-10-01 13:02:20 +00:00
MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS , 0 , 0 ) ;
else
menu_entries_append_enum ( info - > list ,
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME ) ,
2020-12-26 18:12:09 +00:00
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE_MENU ) ,
MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_MENU ,
2018-10-01 13:02:20 +00:00
MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS , 0 , 0 ) ;
}
2020-08-26 18:38:36 +00:00
cheevo = rcheevos_locals . patchdata . core ;
2019-11-11 02:42:00 +00:00
for ( count = rcheevos_locals . patchdata . core_count ; count > 0 ; count - - )
2020-02-24 14:15:37 +00:00
rcheevos_append_menu_achievement ( info , i + + , cheevo + + ) ;
2018-10-01 13:02:20 +00:00
2020-02-19 21:20:24 +00:00
if ( cheevos_test_unofficial )
2018-10-01 13:02:20 +00:00
{
2020-08-26 18:38:36 +00:00
cheevo = rcheevos_locals . patchdata . unofficial ;
2019-11-11 02:42:00 +00:00
for ( count = rcheevos_locals . patchdata . unofficial_count ; count > 0 ; count - - )
2020-02-24 14:15:37 +00:00
rcheevos_append_menu_achievement ( info , i + + , cheevo + + ) ;
2018-10-01 13:02:20 +00:00
}
2019-11-11 02:42:00 +00:00
if ( i = = 0 )
2018-10-01 13:02:20 +00:00
{
2020-11-17 04:29:29 +00:00
if ( ! rcheevos_locals . core_supports )
{
menu_entries_append_enum ( info - > list ,
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE ) ,
msg_hash_to_str ( MENU_ENUM_LABEL_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE ) ,
MENU_ENUM_LABEL_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE ,
FILE_TYPE_NONE , 0 , 0 ) ;
}
else if ( ! settings - > arrays . cheevos_token [ 0 ] )
2020-11-10 03:18:31 +00:00
{
menu_entries_append_enum ( info - > list ,
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN ) ,
msg_hash_to_str ( MENU_ENUM_LABEL_NOT_LOGGED_IN ) ,
MENU_ENUM_LABEL_NOT_LOGGED_IN ,
FILE_TYPE_NONE , 0 , 0 ) ;
}
else
{
menu_entries_append_enum ( info - > list ,
2018-10-01 13:02:20 +00:00
msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_NO_ACHIEVEMENTS_TO_DISPLAY ) ,
msg_hash_to_str ( MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY ) ,
MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY ,
FILE_TYPE_NONE , 0 , 0 ) ;
2020-11-10 03:18:31 +00:00
}
2018-10-01 13:02:20 +00:00
}
# endif
}
2019-03-17 01:13:57 +00:00
bool rcheevos_get_description ( rcheevos_ctx_desc_t * desc )
2018-10-01 13:02:20 +00:00
{
unsigned idx ;
2020-08-26 18:38:36 +00:00
const rcheevos_racheevo_t * cheevo ;
2018-10-01 13:02:20 +00:00
if ( ! desc )
return false ;
* desc - > s = 0 ;
2020-08-22 17:37:59 +00:00
if ( rcheevos_locals . loaded )
2018-10-01 13:02:20 +00:00
{
idx = desc - > idx ;
2019-03-20 01:36:54 +00:00
if ( idx < rcheevos_locals . patchdata . core_count )
2020-08-26 18:38:36 +00:00
cheevo = rcheevos_locals . patchdata . core + idx ;
2018-10-01 13:02:20 +00:00
else
{
2019-03-20 01:36:54 +00:00
idx - = rcheevos_locals . patchdata . core_count ;
2018-10-01 13:02:20 +00:00
2019-03-20 01:36:54 +00:00
if ( idx < rcheevos_locals . patchdata . unofficial_count )
2020-08-26 18:38:36 +00:00
cheevo = rcheevos_locals . patchdata . unofficial + idx ;
2018-10-01 13:02:20 +00:00
else
return true ;
}
2020-08-26 18:38:36 +00:00
strlcpy ( desc - > s , cheevo - > description , desc - > len ) ;
2018-10-01 13:02:20 +00:00
}
return true ;
}
2020-08-22 17:37:59 +00:00
bool rcheevos_hardcore_active ( void )
{
return rcheevos_locals . hardcore_active ;
}
2020-06-07 19:18:07 +00:00
void rcheevos_pause_hardcore ( void )
2018-10-01 13:02:20 +00:00
{
2020-08-22 17:37:59 +00:00
if ( rcheevos_locals . hardcore_active )
rcheevos_toggle_hardcore_paused ( ) ;
2018-10-01 13:02:20 +00:00
}
2019-03-17 01:13:57 +00:00
bool rcheevos_unload ( void )
2018-10-01 13:02:20 +00:00
{
2020-06-07 19:18:07 +00:00
bool running = false ;
settings_t * settings = config_get_ptr ( ) ;
2018-10-01 13:02:20 +00:00
2019-03-20 01:36:54 +00:00
CHEEVOS_LOCK ( rcheevos_locals . task_lock ) ;
2020-09-15 17:38:07 +00:00
running = rcheevos_locals . task ! = NULL ;
2019-03-20 01:36:54 +00:00
CHEEVOS_UNLOCK ( rcheevos_locals . task_lock ) ;
2018-10-01 13:02:20 +00:00
if ( running )
{
2019-03-20 01:36:54 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Asked the load thread to terminate \n " ) ;
task_queue_cancel_task ( rcheevos_locals . task ) ;
2018-10-01 13:02:20 +00:00
# ifdef HAVE_THREADS
do
{
2019-03-20 01:36:54 +00:00
CHEEVOS_LOCK ( rcheevos_locals . task_lock ) ;
running = rcheevos_locals . task ! = NULL ;
CHEEVOS_UNLOCK ( rcheevos_locals . task_lock ) ;
2021-01-16 20:19:59 +00:00
} while ( running ) ;
2018-10-01 13:02:20 +00:00
# endif
}
2020-08-22 17:37:59 +00:00
if ( rcheevos_locals . loaded )
2018-10-01 13:02:20 +00:00
{
2019-03-20 01:36:54 +00:00
rcheevos_free_patchdata ( & rcheevos_locals . patchdata ) ;
2020-07-16 19:35:34 +00:00
rcheevos_memory_destroy ( & rcheevos_locals . memory ) ;
2020-08-13 03:48:26 +00:00
# ifdef HAVE_MENU
cheevos_reset_menu_badges ( ) ;
# endif
2018-10-01 13:02:20 +00:00
2020-08-22 17:37:59 +00:00
rcheevos_locals . loaded = false ;
rcheevos_locals . hardcore_active = false ;
2018-10-01 13:02:20 +00:00
}
2020-08-26 18:38:36 +00:00
rc_runtime_destroy ( & rcheevos_locals . runtime ) ;
2020-02-23 03:58:11 +00:00
/* if the config-level token has been cleared, we need to re-login on loading the next game */
if ( ! settings - > arrays . cheevos_token [ 0 ] )
2020-06-07 19:18:07 +00:00
rcheevos_locals . token [ 0 ] = ' \0 ' ;
2020-02-23 03:58:11 +00:00
2018-10-01 13:02:20 +00:00
return true ;
}
2020-09-21 23:48:14 +00:00
static void rcheevos_toggle_hardcore_achievements ( rcheevos_locals_t * locals ,
rcheevos_racheevo_t * cheevo , unsigned count )
{
const unsigned active_mask = RCHEEVOS_ACTIVE_SOFTCORE | RCHEEVOS_ACTIVE_HARDCORE ;
while ( count - - )
{
if ( cheevo - > memaddr & & ( cheevo - > active & active_mask ) = = RCHEEVOS_ACTIVE_HARDCORE )
{
/* player has unlocked achievement in non-hardcore but has not unlocked in hardcore; toggle state */
if ( locals - > hardcore_active )
{
rc_runtime_activate_achievement ( & locals - > runtime , cheevo - > id , cheevo - > memaddr , NULL , 0 ) ;
CHEEVOS_LOG ( RCHEEVOS_TAG " Achievement %u activated: %s \n " , cheevo - > id , cheevo - > title ) ;
}
else
{
rc_runtime_deactivate_achievement ( & locals - > runtime , cheevo - > id ) ;
CHEEVOS_LOG ( RCHEEVOS_TAG " Achievement %u deactivated: %s \n " , cheevo - > id , cheevo - > title ) ;
}
}
+ + cheevo ;
}
}
2020-09-24 15:25:56 +00:00
static void rcheevos_activate_leaderboards ( rcheevos_locals_t * locals )
{
rcheevos_ralboard_t * lboard = locals - > patchdata . lboards ;
unsigned i ;
for ( i = 0 ; i < locals - > patchdata . lboard_count ; + + i , + + lboard )
{
if ( lboard - > mem )
rc_runtime_activate_lboard ( & locals - > runtime , lboard - > id , lboard - > mem , NULL , 0 ) ;
}
}
static void rcheevos_deactivate_leaderboards ( rcheevos_locals_t * locals )
{
rcheevos_ralboard_t * lboard = locals - > patchdata . lboards ;
unsigned i ;
for ( i = 0 ; i < locals - > patchdata . lboard_count ; + + i , + + lboard )
{
if ( lboard - > mem )
{
rc_runtime_deactivate_lboard ( & locals - > runtime , lboard - > id ) ;
# if defined(HAVE_GFX_WIDGETS)
/* Hide any visible trackers */
gfx_widgets_set_leaderboard_display ( lboard - > id , NULL ) ;
# endif
}
}
}
void rcheevos_leaderboards_enabled_changed ( void )
{
2020-09-25 20:01:07 +00:00
const settings_t * settings = config_get_ptr ( ) ;
const bool leaderboards_enabled = rcheevos_locals . leaderboards_enabled ;
const bool leaderboard_trackers = rcheevos_locals . leaderboard_trackers ;
rcheevos_locals . leaderboards_enabled = rcheevos_hardcore_active ( ) ;
if ( string_is_equal ( settings - > arrays . cheevos_leaderboards_enable , " true " ) )
{
rcheevos_locals . leaderboard_notifications = true ;
rcheevos_locals . leaderboard_trackers = true ;
}
# if defined(HAVE_GFX_WIDGETS)
else if ( string_is_equal ( settings - > arrays . cheevos_leaderboards_enable , " trackers " ) )
{
rcheevos_locals . leaderboard_notifications = false ;
rcheevos_locals . leaderboard_trackers = true ;
}
else if ( string_is_equal ( settings - > arrays . cheevos_leaderboards_enable , " notifications " ) )
{
rcheevos_locals . leaderboard_notifications = true ;
rcheevos_locals . leaderboard_trackers = false ;
}
# endif
else
{
rcheevos_locals . leaderboards_enabled = false ;
rcheevos_locals . leaderboard_notifications = false ;
rcheevos_locals . leaderboard_trackers = false ;
}
2020-09-24 15:25:56 +00:00
if ( rcheevos_locals . loaded )
{
2020-09-25 20:01:07 +00:00
if ( leaderboards_enabled ! = rcheevos_locals . leaderboards_enabled )
{
if ( rcheevos_locals . leaderboards_enabled )
rcheevos_activate_leaderboards ( & rcheevos_locals ) ;
else
rcheevos_deactivate_leaderboards ( & rcheevos_locals ) ;
}
2020-09-24 15:25:56 +00:00
2020-09-25 20:01:07 +00:00
# if defined(HAVE_GFX_WIDGETS)
if ( ! rcheevos_locals . leaderboard_trackers & & leaderboard_trackers )
{
/* Hide any visible trackers */
rcheevos_ralboard_t * lboard = rcheevos_locals . patchdata . lboards ;
unsigned i ;
for ( i = 0 ; i < rcheevos_locals . patchdata . lboard_count ; + + i , + + lboard )
{
if ( lboard - > mem )
gfx_widgets_set_leaderboard_display ( lboard - > id , NULL ) ;
}
}
# endif
2020-09-24 15:25:56 +00:00
}
}
static void rcheevos_toggle_hardcore_active ( rcheevos_locals_t * locals )
2018-10-01 13:02:20 +00:00
{
2020-08-22 17:37:59 +00:00
settings_t * settings = config_get_ptr ( ) ;
bool rewind_enable = settings - > bools . rewind_enable ;
2018-10-01 13:02:20 +00:00
2020-08-23 19:07:15 +00:00
if ( ! locals - > hardcore_active )
2018-10-01 13:02:20 +00:00
{
2020-08-22 17:37:59 +00:00
/* activate hardcore */
2020-08-23 19:07:15 +00:00
locals - > hardcore_active = true ;
2018-10-01 13:02:20 +00:00
2020-10-20 16:37:05 +00:00
/* if one or more invalid settings is enabled, abort*/
rcheevos_validate_config_settings ( ) ;
if ( ! locals - > hardcore_active )
return ;
2021-01-12 03:34:13 +00:00
# ifdef HAVE_CHEATS
/* if one or more emulator managed cheats is active, abort */
cheat_manager_apply_cheats ( ) ;
if ( ! locals - > hardcore_active )
return ;
# endif
2020-08-23 19:07:15 +00:00
if ( locals - > loaded )
2020-08-22 17:37:59 +00:00
{
const char * msg = msg_hash_to_str ( MSG_CHEEVOS_HARDCORE_MODE_ENABLE ) ;
CHEEVOS_LOG ( " %s \n " , msg ) ;
runloop_msg_queue_push ( msg , 0 , 3 * 60 , true , NULL ,
MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2018-10-01 13:02:20 +00:00
2020-08-26 18:38:36 +00:00
/* reactivate leaderboards */
2020-09-25 20:01:07 +00:00
if ( locals - > leaderboards_enabled )
2020-09-24 15:25:56 +00:00
rcheevos_activate_leaderboards ( locals ) ;
2020-08-26 18:38:36 +00:00
2020-08-22 17:37:59 +00:00
/* reset the game */
command_event ( CMD_EVENT_RESET , NULL ) ;
}
2018-10-01 13:02:20 +00:00
2020-08-22 17:37:59 +00:00
/* deinit rewind */
2020-02-19 21:20:24 +00:00
if ( rewind_enable )
2018-10-01 13:02:20 +00:00
command_event ( CMD_EVENT_REWIND_DEINIT , NULL ) ;
}
else
{
2020-08-22 17:37:59 +00:00
/* pause hardcore */
2020-08-23 19:07:15 +00:00
locals - > hardcore_active = false ;
2020-08-22 17:37:59 +00:00
2020-08-23 19:07:15 +00:00
if ( locals - > loaded )
2020-08-22 17:37:59 +00:00
{
CHEEVOS_LOG ( RCHEEVOS_TAG " Hardcore paused \n " ) ;
2020-08-26 18:38:36 +00:00
/* deactivate leaderboards */
2020-09-24 15:25:56 +00:00
rcheevos_deactivate_leaderboards ( locals ) ;
2020-08-22 17:37:59 +00:00
}
/* re-init rewind */
2020-02-19 21:20:24 +00:00
if ( rewind_enable )
2018-10-01 13:02:20 +00:00
command_event ( CMD_EVENT_REWIND_INIT , NULL ) ;
}
2020-08-26 18:38:36 +00:00
if ( locals - > loaded )
{
2020-09-21 23:48:14 +00:00
rcheevos_toggle_hardcore_achievements ( locals , locals - > patchdata . core , locals - > patchdata . core_count ) ;
if ( settings - > bools . cheevos_test_unofficial )
2020-08-26 18:38:36 +00:00
{
2020-09-21 23:48:14 +00:00
rcheevos_toggle_hardcore_achievements ( locals ,
locals - > patchdata . unofficial , locals - > patchdata . unofficial_count ) ;
2020-08-26 18:38:36 +00:00
}
}
2020-08-22 17:37:59 +00:00
}
2018-10-01 13:02:20 +00:00
2020-08-22 17:37:59 +00:00
void rcheevos_toggle_hardcore_paused ( void )
{
settings_t * settings = config_get_ptr ( ) ;
/* if hardcore mode is not enabled, we can't toggle it */
if ( settings - > bools . cheevos_hardcore_mode_enable )
2020-08-23 19:07:15 +00:00
rcheevos_toggle_hardcore_active ( & rcheevos_locals ) ;
2020-08-22 17:37:59 +00:00
}
void rcheevos_hardcore_enabled_changed ( void )
{
const settings_t * settings = config_get_ptr ( ) ;
const bool enabled = settings & & settings - > bools . cheevos_enable & & settings - > bools . cheevos_hardcore_mode_enable ;
if ( enabled ! = rcheevos_locals . hardcore_active )
2020-09-25 20:01:07 +00:00
{
2020-08-23 19:07:15 +00:00
rcheevos_toggle_hardcore_active ( & rcheevos_locals ) ;
2020-09-25 20:01:07 +00:00
/* update leaderboard state flags */
rcheevos_leaderboards_enabled_changed ( ) ;
}
2018-10-01 13:02:20 +00:00
}
2020-10-20 16:37:05 +00:00
typedef struct rc_disallowed_setting_t
{
const char * setting ;
const char * value ;
} rc_disallowed_setting_t ;
typedef struct rc_disallowed_core_settings_t
{
const char * library_name ;
const rc_disallowed_setting_t * disallowed_settings ;
} rc_disallowed_core_settings_t ;
static const rc_disallowed_setting_t _rc_disallowed_dolphin_settings [ ] = {
{ " dolphin_cheats_enabled " , " enabled " } ,
{ NULL , NULL }
} ;
static const rc_disallowed_setting_t _rc_disallowed_ecwolf_settings [ ] = {
{ " ecwolf-invulnerability " , " enabled " } ,
{ NULL , NULL }
} ;
static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings [ ] = {
{ " fbneo-allow-patched-romsets " , " enabled " } ,
2020-10-21 02:58:34 +00:00
{ " fbneo-cheat-* " , " !,Disabled,0 - Disabled " } ,
2020-10-20 16:37:05 +00:00
{ NULL , NULL }
} ;
static const rc_disallowed_setting_t _rc_disallowed_gpgx_settings [ ] = {
2020-10-21 02:58:34 +00:00
{ " genesis_plus_gx_lock_on " , " ,action replay (pro),game genie " } ,
2020-10-20 16:37:05 +00:00
{ NULL , NULL }
} ;
static const rc_disallowed_setting_t _rc_disallowed_ppsspp_settings [ ] = {
{ " ppsspp_cheats " , " enabled " } ,
{ NULL , NULL }
} ;
static const rc_disallowed_core_settings_t rc_disallowed_core_settings [ ] = {
{ " dolphin-emu " , _rc_disallowed_dolphin_settings } ,
{ " ecwolf " , _rc_disallowed_ecwolf_settings } ,
{ " FinalBurn Neo " , _rc_disallowed_fbneo_settings } ,
{ " Genesis Plus GX " , _rc_disallowed_gpgx_settings } ,
{ " PPSSPP " , _rc_disallowed_ppsspp_settings } ,
{ NULL , NULL }
} ;
static int rcheevos_match_value ( const char * val , const char * match )
{
2020-10-21 02:58:34 +00:00
/* if value starts with a comma, it's a CSV list of potential matches */
if ( * match = = ' , ' )
{
do
{
int size ;
2021-01-16 20:19:59 +00:00
const char * ptr = + + match ;
2020-10-21 02:58:34 +00:00
while ( * match & & * match ! = ' , ' )
+ + match ;
size = match - ptr ;
if ( val [ size ] = = ' \0 ' )
{
2021-01-16 20:19:59 +00:00
char buffer [ 128 ] ;
2020-10-21 02:58:34 +00:00
if ( string_is_equal_fast ( ptr , val , size ) )
return true ;
2021-01-16 20:19:59 +00:00
memcpy ( buffer , ptr , size ) ;
buffer [ size ] = ' \0 ' ;
if ( string_is_equal_case_insensitive ( buffer , val ) )
return true ;
2020-10-21 02:58:34 +00:00
}
2021-01-16 20:19:59 +00:00
} while ( * match = = ' , ' ) ;
2020-10-21 02:58:34 +00:00
return false ;
}
/* a leading exclamation point means the provided value(s) are not forbidden (are allowed) */
2020-10-20 16:37:05 +00:00
if ( * match = = ' ! ' )
2020-10-21 02:58:34 +00:00
return ! rcheevos_match_value ( val , & match [ 1 ] ) ;
2020-10-20 16:37:05 +00:00
2020-10-21 02:58:34 +00:00
/* just a single value, attempt to match it */
2020-10-20 16:37:05 +00:00
return string_is_equal_case_insensitive ( val , match ) ;
}
void rcheevos_validate_config_settings ( void )
{
2020-11-04 02:37:42 +00:00
const rc_disallowed_core_settings_t
* core_filter = rc_disallowed_core_settings ;
2020-10-20 16:37:05 +00:00
struct retro_system_info * system = runloop_get_libretro_system_info ( ) ;
if ( ! system - > library_name | | ! rcheevos_hardcore_active ( ) )
return ;
while ( core_filter - > library_name )
{
if ( string_is_equal ( core_filter - > library_name , system - > library_name ) )
{
core_option_manager_t * coreopts = NULL ;
if ( rarch_ctl ( RARCH_CTL_CORE_OPTIONS_LIST_GET , & coreopts ) )
{
int i ;
2020-11-04 02:37:42 +00:00
const char * key = NULL ;
const char * val = NULL ;
const rc_disallowed_setting_t
* disallowed_setting = core_filter - > disallowed_settings ;
int allowed = 1 ;
2020-10-20 16:37:05 +00:00
for ( ; disallowed_setting - > setting ; + + disallowed_setting )
{
2020-11-07 02:42:54 +00:00
size_t key_len ;
2020-11-04 02:37:42 +00:00
key = disallowed_setting - > setting ;
2020-11-07 02:42:54 +00:00
key_len = strlen ( key ) ;
2020-11-04 02:37:42 +00:00
2020-10-20 16:37:05 +00:00
if ( key [ key_len - 1 ] = = ' * ' )
{
for ( i = 0 ; i < coreopts - > size ; i + + )
{
2020-11-04 02:37:42 +00:00
if ( string_starts_with_size (
coreopts - > opts [ i ] . key , key , key_len - 1 ) )
2020-10-20 16:37:05 +00:00
{
2020-11-04 02:37:42 +00:00
const char * val = core_option_manager_get_val ( coreopts , i ) ;
if ( val )
2020-10-20 16:37:05 +00:00
{
2020-11-04 02:37:42 +00:00
if ( rcheevos_match_value (
val , disallowed_setting - > value ) )
{
key = coreopts - > opts [ i ] . key ;
allowed = 0 ;
break ;
}
2020-10-20 16:37:05 +00:00
}
}
}
}
else
{
for ( i = 0 ; i < coreopts - > size ; i + + )
{
if ( string_is_equal ( coreopts - > opts [ i ] . key , key ) )
{
val = core_option_manager_get_val ( coreopts , i ) ;
if ( rcheevos_match_value ( val , disallowed_setting - > value ) )
{
allowed = 0 ;
break ;
}
}
}
}
2020-10-21 02:58:34 +00:00
if ( ! allowed )
{
char buffer [ 256 ] ;
snprintf ( buffer , sizeof ( buffer ) , " Hardcore paused. Setting not allowed: %s=%s " , key , val ) ;
CHEEVOS_LOG ( RCHEEVOS_TAG " %s \n " , buffer ) ;
rcheevos_pause_hardcore ( ) ;
runloop_msg_queue_push ( buffer , 0 , 4 * 60 , false , NULL ,
MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_WARNING ) ;
2020-10-20 16:37:05 +00:00
2020-10-21 02:58:34 +00:00
break ;
}
2020-10-20 16:37:05 +00:00
}
}
break ;
}
+ + core_filter ;
}
}
2020-08-26 18:38:36 +00:00
static void rcheevos_runtime_event_handler ( const rc_runtime_event_t * runtime_event )
{
switch ( runtime_event - > type )
{
case RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED :
rcheevos_award_achievement ( & rcheevos_locals , rcheevos_find_cheevo ( runtime_event - > id ) ) ;
break ;
case RC_RUNTIME_EVENT_LBOARD_STARTED :
2020-09-24 15:25:56 +00:00
rcheevos_lboard_started ( rcheevos_find_lboard ( runtime_event - > id ) , runtime_event - > value ) ;
2020-08-26 18:38:36 +00:00
break ;
2020-09-24 15:25:56 +00:00
# if defined(HAVE_GFX_WIDGETS)
case RC_RUNTIME_EVENT_LBOARD_UPDATED :
rcheevos_lboard_updated ( rcheevos_find_lboard ( runtime_event - > id ) , runtime_event - > value ) ;
break ;
# endif
2020-08-26 18:38:36 +00:00
case RC_RUNTIME_EVENT_LBOARD_CANCELED :
rcheevos_lboard_canceled ( rcheevos_find_lboard ( runtime_event - > id ) ) ;
break ;
case RC_RUNTIME_EVENT_LBOARD_TRIGGERED :
rcheevos_lboard_submit ( & rcheevos_locals , rcheevos_find_lboard ( runtime_event - > id ) , runtime_event - > value ) ;
break ;
default :
break ;
}
}
2020-06-07 16:41:03 +00:00
/*****************************************************************************
Test all the achievements ( call once per frame ) .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-03-17 01:13:57 +00:00
void rcheevos_test ( void )
2018-10-01 13:02:20 +00:00
{
2020-08-22 17:37:59 +00:00
settings_t * settings ;
if ( ! rcheevos_locals . loaded )
return ;
settings = config_get_ptr ( ) ;
2018-10-01 13:02:20 +00:00
2020-07-16 19:35:34 +00:00
if ( rcheevos_locals . memory . count = = 0 )
{
/* we were unable to initialize memory earlier, try now */
if ( ! rcheevos_memory_init ( & rcheevos_locals . memory , rcheevos_locals . patchdata . console_id ) )
{
CHEEVOS_ERR ( RCHEEVOS_TAG " No memory exposed by core \n " ) ;
2020-11-17 04:29:29 +00:00
rcheevos_locals . core_supports = false ;
2020-07-16 19:35:34 +00:00
2020-08-22 17:37:59 +00:00
if ( settings & & settings - > bools . cheevos_verbose_enable )
2020-11-17 04:29:29 +00:00
{
runloop_msg_queue_push ( msg_hash_to_str ( MENU_ENUM_LABEL_VALUE_CANNOT_ACTIVATE_ACHIEVEMENTS_WITH_THIS_CORE ) ,
0 , 4 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_WARNING ) ;
}
2020-07-16 19:35:34 +00:00
2020-11-17 04:29:29 +00:00
rcheevos_unload ( ) ;
2020-08-22 17:37:59 +00:00
rcheevos_pause_hardcore ( ) ;
2020-07-16 19:35:34 +00:00
return ;
}
}
2020-08-26 18:38:36 +00:00
rc_runtime_do_frame ( & rcheevos_locals . runtime , & rcheevos_runtime_event_handler , rcheevos_peek , NULL , 0 ) ;
2018-10-01 13:02:20 +00:00
}
2019-03-17 01:13:57 +00:00
void rcheevos_set_support_cheevos ( bool state )
2018-10-01 13:02:20 +00:00
{
2019-03-20 01:36:54 +00:00
rcheevos_locals . core_supports = state ;
2018-10-01 13:02:20 +00:00
}
2019-03-17 01:13:57 +00:00
bool rcheevos_get_support_cheevos ( void )
2018-10-01 13:02:20 +00:00
{
2020-06-07 19:48:36 +00:00
return rcheevos_locals . core_supports ;
2018-10-01 13:02:20 +00:00
}
2020-02-01 01:37:33 +00:00
const char * rcheevos_get_hash ( void )
{
return rcheevos_locals . hash ;
}
2019-03-20 01:36:54 +00:00
static void rcheevos_unlock_cb ( unsigned id , void * userdata )
2018-10-01 13:02:20 +00:00
{
2020-08-26 18:38:36 +00:00
rcheevos_racheevo_t * cheevo = rcheevos_find_cheevo ( id ) ;
if ( cheevo )
2018-10-01 13:02:20 +00:00
{
2020-08-26 18:38:36 +00:00
unsigned mode = * ( unsigned * ) userdata ;
2018-10-01 13:02:20 +00:00
# ifndef CHEEVOS_DONT_DEACTIVATE
2020-08-26 18:38:36 +00:00
cheevo - > active & = ~ mode ;
2018-10-01 13:02:20 +00:00
# endif
2020-08-26 18:38:36 +00:00
if ( ( rcheevos_locals . hardcore_active & & mode = = RCHEEVOS_ACTIVE_HARDCORE ) | |
( ! rcheevos_locals . hardcore_active & & mode = = RCHEEVOS_ACTIVE_SOFTCORE ) )
{
rc_runtime_deactivate_achievement ( & rcheevos_locals . runtime , cheevo - > id ) ;
CHEEVOS_LOG ( RCHEEVOS_TAG " Achievement %u deactivated: %s \n " , id , cheevo - > title ) ;
2018-10-01 13:02:20 +00:00
}
}
}
# include "coro.h"
2019-03-20 01:36:54 +00:00
/* Uncomment the following two lines to debug rcheevos_iterate, this will
2018-10-01 13:02:20 +00:00
* disable the coroutine yielding .
*
* The code is very easy to understand . It ' s meant to be like BASIC :
* CORO_GOTO will jump execution to another label , CORO_GOSUB will
* call another label , and CORO_RET will return from a CORO_GOSUB .
*
* This coroutine code is inspired in a very old pure C implementation
* that runs everywhere :
*
* https : //www.chiark.greenend.org.uk/~sgtatham/coroutines.html
*/
/*#undef CORO_YIELD
# define CORO_YIELD()* /
typedef struct
{
/* variables used in the co-routine */
char badge_name [ 16 ] ;
char url [ 256 ] ;
char badge_basepath [ PATH_MAX_LENGTH ] ;
char badge_fullpath [ PATH_MAX_LENGTH ] ;
2020-06-07 16:41:03 +00:00
char hash [ 33 ] ;
2018-10-01 13:02:20 +00:00
unsigned gameid ;
unsigned i ;
unsigned j ;
unsigned k ;
size_t len ;
retro_time_t t0 ;
void * data ;
char * json ;
const char * path ;
2020-08-26 18:38:36 +00:00
rcheevos_racheevo_t * cheevo ;
const rcheevos_racheevo_t * cheevo_end ;
2018-10-01 13:02:20 +00:00
settings_t * settings ;
struct http_connection_t * conn ;
struct http_t * http ;
2020-06-07 16:41:03 +00:00
struct rc_hash_iterator iterator ;
2018-10-01 13:02:20 +00:00
/* co-routine required fields */
CORO_FIELDS
2019-03-20 01:36:54 +00:00
} rcheevos_coro_t ;
2018-10-01 13:02:20 +00:00
enum
{
/* Negative values because CORO_SUB generates positive values */
2020-06-07 16:41:03 +00:00
RCHEEVOS_GET_GAMEID = - 1 ,
RCHEEVOS_GET_CHEEVOS = - 2 ,
RCHEEVOS_GET_BADGES = - 3 ,
RCHEEVOS_LOGIN = - 4 ,
RCHEEVOS_HTTP_GET = - 5 ,
RCHEEVOS_DEACTIVATE = - 6 ,
RCHEEVOS_PLAYING = - 7 ,
RCHEEVOS_DELAY = - 8
2018-10-01 13:02:20 +00:00
} ;
2019-03-20 01:36:54 +00:00
static int rcheevos_iterate ( rcheevos_coro_t * coro )
2018-10-01 13:02:20 +00:00
{
2020-02-19 21:20:24 +00:00
char buffer [ 2048 ] ;
2020-06-07 16:41:03 +00:00
# ifdef CHEEVOS_TIME_HASH
retro_time_t start ;
# endif
2018-10-01 13:02:20 +00:00
CORO_ENTER ( ) ;
coro - > settings = config_get_ptr ( ) ;
/* Bail out if cheevos are disabled.
* But set the above anyways ,
* command_read_ram needs it . */
if ( ! coro - > settings - > bools . cheevos_enable )
CORO_STOP ( ) ;
2020-06-07 16:41:03 +00:00
/* iterate over the possible hashes for the file being loaded */
2020-06-22 22:29:55 +00:00
rc_hash_initialize_iterator ( & coro - > iterator , coro - > path , ( uint8_t * ) coro - > data , coro - > len ) ;
2020-06-07 16:41:03 +00:00
# ifdef CHEEVOS_TIME_HASH
start = cpu_features_get_time_usec ( ) ;
# endif
while ( rc_hash_iterate ( coro - > hash , & coro - > iterator ) )
2018-10-01 13:02:20 +00:00
{
2020-06-07 16:41:03 +00:00
# ifdef CHEEVOS_TIME_HASH
CHEEVOS_LOG ( RCHEEVOS_TAG " hash generated in %ums \n " , ( cpu_features_get_time_usec ( ) - start ) / 1000 ) ;
# endif
CORO_GOSUB ( RCHEEVOS_GET_GAMEID ) ;
if ( coro - > gameid ! = 0 )
break ;
2018-10-01 13:02:20 +00:00
2020-06-07 16:41:03 +00:00
# ifdef CHEEVOS_TIME_HASH
start = cpu_features_get_time_usec ( ) ;
# endif
2018-10-01 13:02:20 +00:00
}
2020-06-07 16:41:03 +00:00
rc_hash_destroy_iterator ( & coro - > iterator ) ;
2018-10-01 13:02:20 +00:00
2020-06-07 16:41:03 +00:00
/* if no match was found, bail */
if ( coro - > gameid = = 0 )
2018-10-01 13:02:20 +00:00
{
2020-06-07 16:41:03 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " this game doesn't feature achievements \n " ) ;
2020-08-23 13:15:34 +00:00
strlcpy ( rcheevos_locals . hash , " N/A " , sizeof ( rcheevos_locals . hash ) ) ;
2020-08-22 17:37:59 +00:00
rcheevos_pause_hardcore ( ) ;
2020-06-07 16:41:03 +00:00
CORO_STOP ( ) ;
2018-10-01 13:02:20 +00:00
}
# ifdef CHEEVOS_JSON_OVERRIDE
{
size_t size = 0 ;
FILE * file = fopen ( CHEEVOS_JSON_OVERRIDE , " rb " ) ;
fseek ( file , 0 , SEEK_END ) ;
size = ftell ( file ) ;
fseek ( file , 0 , SEEK_SET ) ;
coro - > json = ( char * ) malloc ( size + 1 ) ;
fread ( ( void * ) coro - > json , 1 , size , file ) ;
fclose ( file ) ;
coro - > json [ size ] = 0 ;
}
# else
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_GET_CHEEVOS ) ;
2018-10-01 13:02:20 +00:00
if ( ! coro - > json )
{
2018-11-22 14:45:52 +00:00
runloop_msg_queue_push ( " Error loading achievements. " , 0 , 5 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2019-03-20 01:36:54 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " error loading achievements \n " ) ;
2018-10-01 13:02:20 +00:00
CORO_STOP ( ) ;
}
# endif
# ifdef CHEEVOS_SAVE_JSON
{
FILE * file = fopen ( CHEEVOS_SAVE_JSON , " w " ) ;
fwrite ( ( void * ) coro - > json , 1 , strlen ( coro - > json ) , file ) ;
fclose ( file ) ;
}
# endif
2020-08-23 19:07:15 +00:00
if ( rcheevos_parse ( & rcheevos_locals , coro - > json ) )
2018-10-01 13:02:20 +00:00
{
CHEEVOS_FREE ( coro - > json ) ;
CORO_STOP ( ) ;
}
CHEEVOS_FREE ( coro - > json ) ;
2019-03-20 01:36:54 +00:00
if ( rcheevos_locals . patchdata . core_count = = 0
& & rcheevos_locals . patchdata . unofficial_count = = 0
& & rcheevos_locals . patchdata . lboard_count = = 0 )
2018-10-01 13:02:20 +00:00
{
runloop_msg_queue_push (
" This game has no achievements. " ,
2019-02-09 23:13:36 +00:00
0 , 5 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2018-10-01 13:02:20 +00:00
2020-08-22 17:37:59 +00:00
rcheevos_pause_hardcore ( ) ;
2018-10-01 13:02:20 +00:00
CORO_STOP ( ) ;
}
2020-08-22 17:37:59 +00:00
rcheevos_locals . loaded = true ;
2018-10-01 13:02:20 +00:00
/*
* Inputs : CHEEVOS_VAR_GAMEID
* Outputs :
*/
2020-05-15 14:00:48 +00:00
if ( ! coro - > settings - > bools . cheevos_start_active )
CORO_GOSUB ( RCHEEVOS_DEACTIVATE ) ;
2018-10-01 13:02:20 +00:00
/*
* Inputs : CHEEVOS_VAR_GAMEID
* Outputs :
*/
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_PLAYING ) ;
2018-10-01 13:02:20 +00:00
2019-03-20 01:36:54 +00:00
if ( coro - > settings - > bools . cheevos_verbose_enable & & rcheevos_locals . patchdata . core_count > 0 )
2018-10-01 13:02:20 +00:00
{
char msg [ 256 ] ;
2020-06-07 19:48:36 +00:00
int mode = RCHEEVOS_ACTIVE_SOFTCORE ;
2020-08-26 18:38:36 +00:00
const rcheevos_racheevo_t * cheevo = rcheevos_locals . patchdata . core ;
const rcheevos_racheevo_t * end = cheevo + rcheevos_locals . patchdata . core_count ;
2020-06-07 19:48:36 +00:00
int number_of_unlocked = rcheevos_locals . patchdata . core_count ;
int number_of_unsupported = 0 ;
2018-10-01 13:02:20 +00:00
2020-08-22 17:37:59 +00:00
if ( rcheevos_locals . hardcore_active )
2019-03-17 01:13:57 +00:00
mode = RCHEEVOS_ACTIVE_HARDCORE ;
2018-10-01 13:02:20 +00:00
for ( ; cheevo < end ; cheevo + + )
2019-12-30 03:49:01 +00:00
{
2020-08-26 18:38:36 +00:00
if ( ! cheevo - > memaddr )
2019-12-30 03:49:01 +00:00
number_of_unsupported + + ;
else if ( cheevo - > active & mode )
2018-10-01 13:02:20 +00:00
number_of_unlocked - - ;
2019-12-30 03:49:01 +00:00
}
2018-10-01 13:02:20 +00:00
2019-12-30 03:49:01 +00:00
if ( ! number_of_unsupported )
{
2020-06-07 19:18:07 +00:00
if ( coro - > settings - > bools . cheevos_start_active )
2020-08-26 18:38:36 +00:00
{
2020-05-15 14:00:48 +00:00
snprintf ( msg , sizeof ( msg ) ,
" All %d achievements activated for this session. " ,
rcheevos_locals . patchdata . core_count ) ;
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " %s \n " , msg ) ;
}
2020-05-15 14:00:48 +00:00
else
{
snprintf ( msg , sizeof ( msg ) ,
" You have %d of %d achievements unlocked. " ,
number_of_unlocked , rcheevos_locals . patchdata . core_count ) ;
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " %s \n " , & msg [ 9 ] ) ;
2020-05-15 14:00:48 +00:00
}
2019-12-30 03:49:01 +00:00
}
else
{
2020-06-07 19:18:07 +00:00
if ( coro - > settings - > bools . cheevos_start_active )
2020-08-26 18:38:36 +00:00
{
2020-05-15 14:00:48 +00:00
snprintf ( msg , sizeof ( msg ) ,
" All %d achievements activated for this session (%d unsupported). " ,
rcheevos_locals . patchdata . core_count ,
number_of_unsupported ) ;
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " %s \n " , msg ) ;
}
2020-06-07 19:18:07 +00:00
else
{
2020-05-15 14:00:48 +00:00
snprintf ( msg , sizeof ( msg ) ,
2020-06-07 19:18:07 +00:00
" You have %d of %d achievements unlocked (%d unsupported). " ,
number_of_unlocked - number_of_unsupported ,
rcheevos_locals . patchdata . core_count ,
number_of_unsupported ) ;
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " %s \n " , & msg [ 9 ] ) ;
2020-05-15 14:00:48 +00:00
}
2019-12-30 03:49:01 +00:00
}
2018-10-01 13:02:20 +00:00
msg [ sizeof ( msg ) - 1 ] = 0 ;
2020-08-26 18:38:36 +00:00
runloop_msg_queue_push ( msg , 0 , 3 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2018-10-01 13:02:20 +00:00
}
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_GET_BADGES ) ;
2018-10-01 13:02:20 +00:00
CORO_STOP ( ) ;
2019-09-14 03:38:35 +00:00
/**************************************************************************
* Info Gets the achievements from Retro Achievements
* Inputs coro - > hash
2019-11-18 05:47:01 +00:00
* Outputs coro - > gameid
2019-09-14 03:38:35 +00:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CORO_SUB ( RCHEEVOS_GET_GAMEID )
2018-10-01 13:02:20 +00:00
{
2019-09-14 03:38:35 +00:00
int size ;
2018-10-01 13:02:20 +00:00
2020-06-07 16:41:03 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " checking %s \n " , coro - > hash ) ;
memcpy ( rcheevos_locals . hash , coro - > hash , sizeof ( coro - > hash ) ) ;
2020-05-04 15:11:16 +00:00
size = rc_url_get_gameid ( coro - > url , sizeof ( coro - > url ) , rcheevos_locals . hash ) ;
if ( size < 0 )
{
CHEEVOS_ERR ( RCHEEVOS_TAG " buffer too small to create URL \n " ) ;
CORO_RET ( ) ;
}
rcheevos_log_url ( " rc_url_get_gameid " , coro - > url ) ;
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_HTTP_GET ) ;
2018-10-01 13:02:20 +00:00
if ( ! coro - > json )
CORO_RET ( ) ;
coro - > gameid = chevos_get_gameid ( coro - > json ) ;
CHEEVOS_FREE ( coro - > json ) ;
2019-03-20 01:36:54 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " got game id %u \n " , coro - > gameid ) ;
2018-10-01 13:02:20 +00:00
CORO_RET ( ) ;
}
2019-09-14 03:38:35 +00:00
/**************************************************************************
* Info Gets the achievements from Retro Achievements
* Inputs CHEEVOS_VAR_GAMEID
* Outputs CHEEVOS_VAR_JSON
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-03-20 01:36:54 +00:00
CORO_SUB ( RCHEEVOS_GET_CHEEVOS )
2018-10-01 13:02:20 +00:00
{
int ret ;
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_LOGIN ) ;
2018-10-01 13:02:20 +00:00
2019-03-20 01:36:54 +00:00
ret = rc_url_get_patch ( coro - > url , sizeof ( coro - > url ) , coro - > settings - > arrays . cheevos_username , rcheevos_locals . token , coro - > gameid ) ;
2018-10-01 13:02:20 +00:00
if ( ret < 0 )
{
2019-03-20 01:36:54 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " buffer too small to create URL \n " ) ;
2018-10-01 13:02:20 +00:00
CORO_STOP ( ) ;
}
2020-05-04 15:11:16 +00:00
rcheevos_log_url ( " rc_url_get_patch " , coro - > url ) ;
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_HTTP_GET ) ;
2018-10-01 13:02:20 +00:00
if ( ! coro - > json )
{
2019-03-20 01:36:54 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " error getting achievements for game id %u \n " , coro - > gameid ) ;
2018-10-01 13:02:20 +00:00
CORO_STOP ( ) ;
}
2019-03-20 01:36:54 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " got achievements for game id %u \n " , coro - > gameid ) ;
2018-10-01 13:02:20 +00:00
CORO_RET ( ) ;
}
2019-09-14 03:38:35 +00:00
/**************************************************************************
* Info Gets the achievements from Retro Achievements
* Inputs CHEEVOS_VAR_GAMEID
* Outputs CHEEVOS_VAR_JSON
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-03-20 01:36:54 +00:00
CORO_SUB ( RCHEEVOS_GET_BADGES )
2018-10-01 13:02:20 +00:00
2020-03-13 02:56:51 +00:00
/* we always want badges if display widgets are enabled */
2020-03-04 19:53:23 +00:00
# if !defined(HAVE_GFX_WIDGETS)
{
settings_t * settings = config_get_ptr ( ) ;
if ( ! (
2019-02-03 23:49:35 +00:00
string_is_equal ( settings - > arrays . menu_driver , " xmb " ) | |
2018-11-19 09:23:55 +00:00
string_is_equal ( settings - > arrays . menu_driver , " ozone " )
2020-03-04 19:53:23 +00:00
) | |
! settings - > bools . cheevos_badges_enable )
CORO_RET ( ) ;
}
# endif
2018-10-01 13:02:20 +00:00
2020-02-23 17:38:27 +00:00
# ifdef HAVE_MENU
cheevos_reset_menu_badges ( ) ;
# endif
2018-10-01 13:02:20 +00:00
for ( coro - > i = 0 ; coro - > i < 2 ; coro - > i + + )
{
if ( coro - > i = = 0 )
{
2020-08-26 18:38:36 +00:00
coro - > cheevo = rcheevos_locals . patchdata . core ;
2019-03-20 01:36:54 +00:00
coro - > cheevo_end = coro - > cheevo + rcheevos_locals . patchdata . core_count ;
2018-10-01 13:02:20 +00:00
}
else
{
2020-08-26 18:38:36 +00:00
coro - > cheevo = rcheevos_locals . patchdata . unofficial ;
2019-03-20 01:36:54 +00:00
coro - > cheevo_end = coro - > cheevo + rcheevos_locals . patchdata . unofficial_count ;
2018-10-01 13:02:20 +00:00
}
for ( ; coro - > cheevo < coro - > cheevo_end ; coro - > cheevo + + )
{
2020-08-26 18:38:36 +00:00
if ( ! coro - > cheevo - > badge [ 0 ] )
2020-06-07 16:41:03 +00:00
continue ;
2018-10-01 13:02:20 +00:00
for ( coro - > j = 0 ; coro - > j < 2 ; coro - > j + + )
{
coro - > badge_fullpath [ 0 ] = ' \0 ' ;
fill_pathname_application_special (
coro - > badge_fullpath ,
sizeof ( coro - > badge_fullpath ) ,
APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES ) ;
if ( ! path_is_directory ( coro - > badge_fullpath ) )
path_mkdir ( coro - > badge_fullpath ) ;
CORO_YIELD ( ) ;
2020-02-15 03:12:49 +00:00
2020-08-26 18:38:36 +00:00
if ( ! coro - > cheevo - > badge | | ! coro - > cheevo - > badge [ 0 ] )
2020-02-15 03:12:49 +00:00
continue ;
2018-10-01 13:02:20 +00:00
if ( coro - > j = = 0 )
snprintf ( coro - > badge_name ,
sizeof ( coro - > badge_name ) ,
2020-09-01 19:51:11 +00:00
" %s " FILE_PATH_PNG_EXTENSION ,
2020-08-26 18:38:36 +00:00
coro - > cheevo - > badge ) ;
2018-10-01 13:02:20 +00:00
else
snprintf ( coro - > badge_name ,
sizeof ( coro - > badge_name ) ,
2020-09-01 19:51:11 +00:00
" %s_lock " FILE_PATH_PNG_EXTENSION ,
2020-08-26 18:38:36 +00:00
coro - > cheevo - > badge ) ;
2018-10-01 13:02:20 +00:00
fill_pathname_join (
coro - > badge_fullpath ,
coro - > badge_fullpath ,
coro - > badge_name ,
sizeof ( coro - > badge_fullpath ) ) ;
2020-02-23 17:38:27 +00:00
if ( ! path_is_valid ( coro - > badge_fullpath ) )
2018-10-01 13:02:20 +00:00
{
# ifdef CHEEVOS_LOG_BADGES
CHEEVOS_LOG (
2019-03-20 01:36:54 +00:00
RCHEEVOS_TAG " downloading badge %s \n " ,
2018-10-01 13:02:20 +00:00
coro - > badge_fullpath ) ;
# endif
snprintf ( coro - > url ,
sizeof ( coro - > url ) ,
2020-09-01 19:51:11 +00:00
FILE_PATH_RETROACHIEVEMENTS_URL " /Badge/%s " ,
2018-10-01 13:02:20 +00:00
coro - > badge_name ) ;
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_HTTP_GET ) ;
2018-10-01 13:02:20 +00:00
if ( coro - > json )
{
if ( ! filestream_write_file ( coro - > badge_fullpath ,
coro - > json , coro - > k ) )
2020-09-01 19:51:11 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " Error writing badge %s \n " , coro - > badge_fullpath ) ;
2018-10-01 13:02:20 +00:00
else
2019-07-04 19:08:01 +00:00
{
2018-10-01 13:02:20 +00:00
CHEEVOS_FREE ( coro - > json ) ;
2019-07-04 19:08:01 +00:00
coro - > json = NULL ;
}
2018-10-01 13:02:20 +00:00
}
}
}
}
}
CORO_RET ( ) ;
2019-09-14 03:38:35 +00:00
/**************************************************************************
* Info Logs in the user at Retro Achievements
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-03-20 01:36:54 +00:00
CORO_SUB ( RCHEEVOS_LOGIN )
2018-10-01 13:02:20 +00:00
{
int ret ;
char tok [ 256 ] ;
2019-04-20 14:50:51 +00:00
2019-03-20 01:36:54 +00:00
if ( rcheevos_locals . token [ 0 ] )
2018-10-01 13:02:20 +00:00
CORO_RET ( ) ;
2020-02-19 22:50:26 +00:00
if ( string_is_empty ( coro - > settings - > arrays . cheevos_username ) )
2018-10-01 13:02:20 +00:00
{
runloop_msg_queue_push (
" Missing RetroAchievements account information. " ,
2019-02-09 23:13:36 +00:00
0 , 5 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2018-10-01 13:02:20 +00:00
runloop_msg_queue_push (
" Please fill in your account information in Settings. " ,
2019-02-09 23:13:36 +00:00
0 , 5 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2019-03-20 01:36:54 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " login info not informed \n " ) ;
2018-10-01 13:02:20 +00:00
CORO_STOP ( ) ;
}
2020-02-19 22:50:26 +00:00
if ( string_is_empty ( coro - > settings - > arrays . cheevos_token ) )
2020-05-04 15:11:16 +00:00
{
2018-10-01 13:02:20 +00:00
ret = rc_url_login_with_password ( coro - > url , sizeof ( coro - > url ) ,
2020-02-19 22:50:26 +00:00
coro - > settings - > arrays . cheevos_username ,
coro - > settings - > arrays . cheevos_password ) ;
2020-05-04 15:11:16 +00:00
if ( ret = = RC_OK )
{
CHEEVOS_LOG ( RCHEEVOS_TAG " attempting to login %s (with password) \n " , coro - > settings - > arrays . cheevos_username ) ;
rcheevos_log_url ( " rc_url_login_with_password " , coro - > url ) ;
}
}
2018-10-01 13:02:20 +00:00
else
2020-05-04 15:11:16 +00:00
{
2018-10-01 13:02:20 +00:00
ret = rc_url_login_with_token ( coro - > url , sizeof ( coro - > url ) ,
2020-02-19 22:50:26 +00:00
coro - > settings - > arrays . cheevos_username ,
coro - > settings - > arrays . cheevos_token ) ;
2018-10-01 13:02:20 +00:00
2020-05-04 15:11:16 +00:00
if ( ret = = RC_OK )
{
CHEEVOS_LOG ( RCHEEVOS_TAG " attempting to login %s (with token) \n " , coro - > settings - > arrays . cheevos_username ) ;
rcheevos_log_url ( " rc_url_login_with_token " , coro - > url ) ;
}
}
2018-10-01 13:02:20 +00:00
if ( ret < 0 )
{
2019-03-20 01:36:54 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " buffer too small to create URL \n " ) ;
2018-10-01 13:02:20 +00:00
CORO_STOP ( ) ;
}
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_HTTP_GET ) ;
2018-10-01 13:02:20 +00:00
if ( ! coro - > json )
{
2018-11-22 14:45:52 +00:00
runloop_msg_queue_push ( " RetroAchievements: Error contacting server. " , 0 , 5 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2019-03-20 01:36:54 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " error getting user token \n " ) ;
2018-10-01 13:02:20 +00:00
CORO_STOP ( ) ;
}
2019-03-20 01:36:54 +00:00
ret = rcheevos_get_token ( coro - > json , tok , sizeof ( tok ) ) ;
2018-10-01 13:02:20 +00:00
if ( ret ! = 0 )
{
2019-02-23 10:20:28 +00:00
char msg [ 512 ] ;
2018-10-01 13:02:20 +00:00
snprintf ( msg , sizeof ( msg ) ,
" RetroAchievements: %s " ,
tok ) ;
2018-11-22 14:45:52 +00:00
runloop_msg_queue_push ( msg , 0 , 5 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2018-10-01 13:02:20 +00:00
* coro - > settings - > arrays . cheevos_token = 0 ;
2020-05-04 15:11:16 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " login error: %s \n " , tok ) ;
2018-10-01 13:02:20 +00:00
CHEEVOS_FREE ( coro - > json ) ;
CORO_STOP ( ) ;
}
CHEEVOS_FREE ( coro - > json ) ;
if ( coro - > settings - > bools . cheevos_verbose_enable )
{
char msg [ 256 ] ;
snprintf ( msg , sizeof ( msg ) ,
" RetroAchievements: Logged in as \" %s \" . " ,
coro - > settings - > arrays . cheevos_username ) ;
msg [ sizeof ( msg ) - 1 ] = 0 ;
2020-08-26 18:38:36 +00:00
runloop_msg_queue_push ( msg , 0 , 2 * 60 , false , NULL , MESSAGE_QUEUE_ICON_DEFAULT , MESSAGE_QUEUE_CATEGORY_INFO ) ;
2018-10-01 13:02:20 +00:00
}
2020-05-04 15:11:16 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " logged in successfully \n " ) ;
2019-03-20 01:36:54 +00:00
strlcpy ( rcheevos_locals . token , tok ,
sizeof ( rcheevos_locals . token ) ) ;
2018-10-01 13:02:20 +00:00
/* Save token to config and clear pass on success */
strlcpy ( coro - > settings - > arrays . cheevos_token , tok ,
sizeof ( coro - > settings - > arrays . cheevos_token ) ) ;
* coro - > settings - > arrays . cheevos_password = 0 ;
CORO_RET ( ) ;
}
2019-09-14 03:38:35 +00:00
/**************************************************************************
* Info Pauses execution for five seconds
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-03-20 01:36:54 +00:00
CORO_SUB ( RCHEEVOS_DELAY )
2018-10-01 13:02:20 +00:00
{
retro_time_t t1 ;
coro - > t0 = cpu_features_get_time_usec ( ) ;
do
{
CORO_YIELD ( ) ;
t1 = cpu_features_get_time_usec ( ) ;
2021-01-16 20:19:59 +00:00
} while ( ( t1 - coro - > t0 ) < 3000000 ) ;
2018-10-01 13:02:20 +00:00
}
CORO_RET ( ) ;
2019-09-14 03:38:35 +00:00
/**************************************************************************
* Info Makes a HTTP GET request
* Inputs CHEEVOS_VAR_URL
* Outputs CHEEVOS_VAR_JSON
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-03-20 01:36:54 +00:00
CORO_SUB ( RCHEEVOS_HTTP_GET )
2018-10-01 13:02:20 +00:00
for ( coro - > k = 0 ; coro - > k < 5 ; coro - > k + + )
{
if ( coro - > k ! = 0 )
2019-03-20 01:36:54 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Retrying HTTP request: %u of 5 \n " , coro - > k + 1 ) ;
2018-10-01 13:02:20 +00:00
coro - > json = NULL ;
coro - > conn = net_http_connection_new (
coro - > url , " GET " , NULL ) ;
if ( ! coro - > conn )
{
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_DELAY ) ;
2018-10-01 13:02:20 +00:00
continue ;
}
/* Don't bother with timeouts here, it's just a string scan. */
while ( ! net_http_connection_iterate ( coro - > conn ) ) { }
/* Error finishing the connection descriptor. */
if ( ! net_http_connection_done ( coro - > conn ) )
{
net_http_connection_free ( coro - > conn ) ;
continue ;
}
2020-08-23 19:07:15 +00:00
rcheevos_get_user_agent ( & rcheevos_locals ,
buffer , sizeof ( buffer ) ) ;
2019-09-30 03:09:08 +00:00
net_http_connection_set_user_agent ( coro - > conn , buffer ) ;
2018-10-01 13:02:20 +00:00
coro - > http = net_http_new ( coro - > conn ) ;
/* Error connecting to the endpoint. */
if ( ! coro - > http )
{
net_http_connection_free ( coro - > conn ) ;
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_DELAY ) ;
2018-10-01 13:02:20 +00:00
continue ;
}
while ( ! net_http_update ( coro - > http , NULL , NULL ) )
CORO_YIELD ( ) ;
{
size_t length ;
uint8_t * data = net_http_data ( coro - > http ,
& length , false ) ;
if ( data )
{
coro - > json = ( char * ) malloc ( length + 1 ) ;
if ( coro - > json )
{
memcpy ( ( void * ) coro - > json , ( void * ) data , length ) ;
CHEEVOS_FREE ( data ) ;
coro - > json [ length ] = 0 ;
}
coro - > k = ( unsigned ) length ;
net_http_delete ( coro - > http ) ;
net_http_connection_free ( coro - > conn ) ;
CORO_RET ( ) ;
}
}
net_http_delete ( coro - > http ) ;
net_http_connection_free ( coro - > conn ) ;
}
2019-03-20 01:36:54 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Couldn't connect to server after 5 tries \n " ) ;
2018-10-01 13:02:20 +00:00
CORO_RET ( ) ;
2019-09-14 03:38:35 +00:00
/**************************************************************************
* Info Deactivates the achievements already awarded
* Inputs CHEEVOS_VAR_GAMEID
* Outputs
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-03-20 01:36:54 +00:00
CORO_SUB ( RCHEEVOS_DEACTIVATE )
2018-10-01 13:02:20 +00:00
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_LOGIN ) ;
2018-10-01 13:02:20 +00:00
{
2019-02-23 10:20:28 +00:00
int ret ;
2018-10-01 13:02:20 +00:00
unsigned mode ;
2019-09-14 03:38:35 +00:00
/* Two calls - one for softcore and one for hardcore */
2019-02-23 10:20:28 +00:00
for ( coro - > i = 0 ; coro - > i < 2 ; coro - > i + + )
2018-10-01 13:02:20 +00:00
{
2020-02-19 21:20:24 +00:00
ret = rc_url_get_unlock_list ( coro - > url , sizeof ( coro - > url ) ,
coro - > settings - > arrays . cheevos_username ,
rcheevos_locals . token , coro - > gameid , coro - > i ) ;
2018-10-01 13:02:20 +00:00
if ( ret < 0 )
{
2019-03-20 01:36:54 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " buffer too small to create URL \n " ) ;
2018-10-01 13:02:20 +00:00
CORO_STOP ( ) ;
}
2020-05-04 15:11:16 +00:00
rcheevos_log_url ( " rc_url_get_unlock_list " , coro - > url ) ;
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_HTTP_GET ) ;
2018-10-01 13:02:20 +00:00
if ( coro - > json )
{
2019-03-17 01:13:57 +00:00
mode = coro - > i = = 0 ? RCHEEVOS_ACTIVE_SOFTCORE : RCHEEVOS_ACTIVE_HARDCORE ;
2019-03-20 01:36:54 +00:00
rcheevos_deactivate_unlocks ( coro - > json , rcheevos_unlock_cb , & mode ) ;
2018-10-01 13:02:20 +00:00
CHEEVOS_FREE ( coro - > json ) ;
}
else
2019-03-20 01:36:54 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " error retrieving list of unlocked achievements in softcore mode \n " ) ;
2018-10-01 13:02:20 +00:00
}
}
CORO_RET ( ) ;
2019-09-14 03:38:35 +00:00
/**************************************************************************
* Info Posts the " playing " activity to Retro Achievements
* Inputs CHEEVOS_VAR_GAMEID
* Outputs
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-03-20 01:36:54 +00:00
CORO_SUB ( RCHEEVOS_PLAYING )
2018-10-01 13:02:20 +00:00
2020-05-04 15:11:16 +00:00
{
int ret = rc_url_post_playing ( coro - > url , sizeof ( coro - > url ) ,
2018-10-01 13:02:20 +00:00
coro - > settings - > arrays . cheevos_username ,
2020-05-04 15:11:16 +00:00
rcheevos_locals . token , coro - > gameid ) ;
if ( ret < 0 )
{
CHEEVOS_ERR ( RCHEEVOS_TAG " buffer too small to create URL \n " ) ;
CORO_STOP ( ) ;
}
}
2018-10-01 13:02:20 +00:00
2020-05-04 15:11:16 +00:00
rcheevos_log_url ( " rc_url_post_playing " , coro - > url ) ;
2018-10-01 13:02:20 +00:00
2019-03-20 01:36:54 +00:00
CORO_GOSUB ( RCHEEVOS_HTTP_GET ) ;
2018-10-01 13:02:20 +00:00
if ( coro - > json )
{
2020-08-26 18:38:36 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Posted playing activity \n " ) ;
2018-10-01 13:02:20 +00:00
CHEEVOS_FREE ( coro - > json ) ;
}
else
2019-03-20 01:36:54 +00:00
CHEEVOS_ERR ( RCHEEVOS_TAG " error posting playing activity \n " ) ;
2018-10-01 13:02:20 +00:00
CORO_RET ( ) ;
CORO_LEAVE ( ) ;
}
2019-03-20 01:36:54 +00:00
static void rcheevos_task_handler ( retro_task_t * task )
2018-10-01 13:02:20 +00:00
{
2019-03-20 01:36:54 +00:00
rcheevos_coro_t * coro = ( rcheevos_coro_t * ) task - > state ;
2018-10-01 13:02:20 +00:00
if ( ! coro )
return ;
2019-03-20 01:36:54 +00:00
if ( ! rcheevos_iterate ( coro ) | | task_get_cancelled ( task ) )
2018-10-01 13:02:20 +00:00
{
task_set_finished ( task , true ) ;
2019-03-20 01:36:54 +00:00
CHEEVOS_LOCK ( rcheevos_locals . task_lock ) ;
rcheevos_locals . task = NULL ;
CHEEVOS_UNLOCK ( rcheevos_locals . task_lock ) ;
2018-10-01 13:02:20 +00:00
if ( task_get_cancelled ( task ) )
{
2019-03-20 01:36:54 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Load task cancelled \n " ) ;
2018-10-01 13:02:20 +00:00
}
else
{
2019-03-20 01:36:54 +00:00
CHEEVOS_LOG ( RCHEEVOS_TAG " Load task finished \n " ) ;
2018-10-01 13:02:20 +00:00
}
CHEEVOS_FREE ( coro - > data ) ;
CHEEVOS_FREE ( coro - > path ) ;
CHEEVOS_FREE ( coro ) ;
}
}
2020-12-28 18:55:54 +00:00
/* hooks for rc_hash library */
2020-06-07 16:41:03 +00:00
static void * rc_hash_handle_file_open ( const char * path )
{
return intfstream_open_file ( path , RETRO_VFS_FILE_ACCESS_READ , RETRO_VFS_FILE_ACCESS_HINT_NONE ) ;
}
static void rc_hash_handle_file_seek ( void * file_handle , size_t offset , int origin )
{
intfstream_seek ( ( intfstream_t * ) file_handle , offset , origin ) ;
}
static size_t rc_hash_handle_file_tell ( void * file_handle )
{
return intfstream_tell ( ( intfstream_t * ) file_handle ) ;
}
static size_t rc_hash_handle_file_read ( void * file_handle , void * buffer , size_t requested_bytes )
{
return intfstream_read ( ( intfstream_t * ) file_handle , buffer , requested_bytes ) ;
}
static void rc_hash_handle_file_close ( void * file_handle )
{
intfstream_close ( ( intfstream_t * ) file_handle ) ;
CHEEVOS_FREE ( file_handle ) ;
}
static void * rc_hash_handle_cd_open_track ( const char * path , uint32_t track )
{
cdfs_track_t * cdfs_track ;
if ( track = = 0 )
cdfs_track = cdfs_open_data_track ( path ) ;
else
cdfs_track = cdfs_open_track ( path , track ) ;
if ( cdfs_track )
{
cdfs_file_t * file = ( cdfs_file_t * ) malloc ( sizeof ( cdfs_file_t ) ) ;
if ( cdfs_open_file ( file , cdfs_track , NULL ) )
return file ;
CHEEVOS_FREE ( file ) ;
}
cdfs_close_track ( cdfs_track ) ; /* ASSERT: this free()s cdfs_track */
return NULL ;
}
static size_t rc_hash_handle_cd_read_sector ( void * track_handle , uint32_t sector , void * buffer , size_t requested_bytes )
{
cdfs_file_t * file = ( cdfs_file_t * ) track_handle ;
cdfs_seek_sector ( file , sector ) ;
return cdfs_read_file ( file , buffer , requested_bytes ) ;
}
static void rc_hash_handle_cd_close_track ( void * track_handle )
{
cdfs_file_t * file = ( cdfs_file_t * ) track_handle ;
if ( file )
{
cdfs_close_track ( file - > track ) ;
cdfs_close_file ( file ) ; /* ASSERT: this does not free() file */
CHEEVOS_FREE ( file ) ;
}
}
static void rc_hash_handle_log_message ( const char * message )
{
CHEEVOS_LOG ( RCHEEVOS_TAG " %s \n " , message ) ;
}
/* end hooks */
2019-03-17 01:13:57 +00:00
bool rcheevos_load ( const void * data )
2018-10-01 13:02:20 +00:00
{
2020-02-19 21:20:24 +00:00
retro_task_t * task = NULL ;
2018-10-01 13:02:20 +00:00
const struct retro_game_info * info = NULL ;
2019-09-14 03:38:35 +00:00
rcheevos_coro_t * coro = NULL ;
2020-05-15 12:33:05 +00:00
settings_t * settings = config_get_ptr ( ) ;
bool cheevos_enable = settings & & settings - > bools . cheevos_enable ;
2020-06-07 16:41:03 +00:00
struct rc_hash_filereader filereader ;
struct rc_hash_cdreader cdreader ;
2020-05-29 16:44:31 +00:00
2020-08-22 17:37:59 +00:00
rcheevos_locals . loaded = false ;
2020-08-26 18:38:36 +00:00
rc_runtime_init ( & rcheevos_locals . runtime ) ;
2018-10-01 13:02:20 +00:00
2020-05-15 12:33:05 +00:00
if ( ! cheevos_enable | | ! rcheevos_locals . core_supports | | ! data )
2019-11-27 14:38:41 +00:00
{
2020-08-22 17:37:59 +00:00
rcheevos_pause_hardcore ( ) ;
2018-10-01 13:02:20 +00:00
return false ;
2019-11-27 14:38:41 +00:00
}
2018-10-01 13:02:20 +00:00
2020-09-25 20:01:07 +00:00
/* reset hardcore mode and leaderboard settings based on configs */
2020-08-22 17:37:59 +00:00
rcheevos_hardcore_enabled_changed ( ) ;
2020-10-20 16:37:05 +00:00
rcheevos_validate_config_settings ( ) ;
2020-09-25 20:01:07 +00:00
rcheevos_leaderboards_enabled_changed ( ) ;
2020-08-22 17:37:59 +00:00
2019-03-20 01:36:54 +00:00
coro = ( rcheevos_coro_t * ) calloc ( 1 , sizeof ( * coro ) ) ;
2018-10-01 13:02:20 +00:00
if ( ! coro )
return false ;
2020-06-07 16:41:03 +00:00
/* provide hooks for reading files */
filereader . open = rc_hash_handle_file_open ;
filereader . seek = rc_hash_handle_file_seek ;
filereader . tell = rc_hash_handle_file_tell ;
filereader . read = rc_hash_handle_file_read ;
filereader . close = rc_hash_handle_file_close ;
rc_hash_init_custom_filereader ( & filereader ) ;
cdreader . open_track = rc_hash_handle_cd_open_track ;
cdreader . read_sector = rc_hash_handle_cd_read_sector ;
cdreader . close_track = rc_hash_handle_cd_close_track ;
rc_hash_init_custom_cdreader ( & cdreader ) ;
rc_hash_init_error_message_callback ( rc_hash_handle_log_message ) ;
# ifndef DEBUG /* in DEBUG mode, always initialize the verbose message handler */
if ( settings - > bools . cheevos_verbose_enable )
# endif
{
rc_hash_init_verbose_message_callback ( rc_hash_handle_log_message ) ;
}
2018-10-01 13:02:20 +00:00
2020-06-07 16:41:03 +00:00
task = task_init ( ) ;
2018-10-01 13:02:20 +00:00
if ( ! task )
{
CHEEVOS_FREE ( coro ) ;
return false ;
}
CORO_SETUP ( ) ;
info = ( const struct retro_game_info * ) data ;
2020-06-07 16:41:03 +00:00
coro - > path = strdup ( info - > path ) ;
2018-10-01 13:02:20 +00:00
if ( info - > data )
{
coro - > len = info - > size ;
/* size limit */
if ( coro - > len > CHEEVOS_MB ( 64 ) )
coro - > len = CHEEVOS_MB ( 64 ) ;
coro - > data = malloc ( coro - > len ) ;
if ( ! coro - > data )
{
CHEEVOS_FREE ( task ) ;
CHEEVOS_FREE ( coro ) ;
return false ;
}
memcpy ( coro - > data , info - > data , coro - > len ) ;
}
else
{
coro - > data = NULL ;
}
2019-03-20 01:36:54 +00:00
task - > handler = rcheevos_task_handler ;
2018-10-01 13:02:20 +00:00
task - > state = ( void * ) coro ;
task - > mute = true ;
task - > callback = NULL ;
task - > user_data = NULL ;
task - > progress = 0 ;
task - > title = NULL ;
# ifdef HAVE_THREADS
2020-02-19 21:20:24 +00:00
if ( ! rcheevos_locals . task_lock )
2019-03-20 01:36:54 +00:00
rcheevos_locals . task_lock = slock_new ( ) ;
2018-10-01 13:02:20 +00:00
# endif
2019-03-20 01:36:54 +00:00
CHEEVOS_LOCK ( rcheevos_locals . task_lock ) ;
rcheevos_locals . task = task ;
CHEEVOS_UNLOCK ( rcheevos_locals . task_lock ) ;
2018-10-01 13:02:20 +00:00
task_queue_push ( task ) ;
return true ;
}