(cheevos) upgrade to rcheevos 11.2 (#16408)

* provide more information during achievement load process

* update rcheevos version

* do disconnected processing even when no game is loaded

* make loading widget unique

* only show loading indicator with verbose messages on
This commit is contained in:
Jamiras 2024-04-05 08:39:38 -06:00 committed by GitHub
parent 5426ec8d90
commit a6beba6376
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 688 additions and 197 deletions

View file

@ -2115,7 +2115,7 @@ ifeq ($(HAVE_NETWORKING), 1)
# RetroAchievements
ifeq ($(HAVE_CHEEVOS), 1)
DEFINES += -DHAVE_CHEEVOS
DEFINES += -DHAVE_CHEEVOS -DRC_CLIENT_SUPPORTS_HASH
INCLUDE_DIRS += -Ideps/rcheevos/include
ifneq ($(HAVE_THREADS), 1)

View file

@ -107,7 +107,7 @@ static rcheevos_locals_t rcheevos_locals =
0, /* menuitem_count */
#endif
#ifdef HAVE_RC_CLIENT
true,/* hardcore_allowed */
true, /* hardcore_allowed */
#else
#ifdef HAVE_GFX_WIDGETS
0, /* active_lboard_trackers */
@ -1221,6 +1221,7 @@ bool rcheevos_unload(void)
#ifdef HAVE_GFX_WIDGETS
rcheevos_hide_widgets(gfx_widgets_ready());
gfx_widget_set_cheevos_set_loading(false);
#endif
#ifdef HAVE_RC_CLIENT
@ -2441,6 +2442,10 @@ static void rcheevos_client_load_game_callback(int result,
const rc_client_game_t* game = rc_client_get_game_info(client);
char msg[256];
#if defined(HAVE_GFX_WIDGETS)
gfx_widget_set_cheevos_set_loading(false);
#endif
if (result != RC_OK || !game)
{
if (result == RC_NO_GAME_LOADED)
@ -3257,6 +3262,11 @@ bool rcheevos_load(const void *data)
/* provide hooks for reading files */
rc_hash_reset_cdreader_hooks();
#if defined(HAVE_GFX_WIDGETS)
if (settings->bools.cheevos_verbose_enable)
gfx_widget_set_cheevos_set_loading(true);
#endif
rc_client_begin_identify_and_load_game(rcheevos_locals.client, RC_CONSOLE_UNKNOWN,
info->path, info->data, info->size, rcheevos_client_load_game_callback, NULL);

View file

@ -338,6 +338,12 @@ static void rcheevos_client_http_task_callback(retro_task_t* task,
if (!http_data)
{
CHEEVOS_LOG(RCHEEVOS_TAG "http_task returned null");
callback_data->callback(&server_response, callback_data->callback_data);
}
else if (http_data->status < 0)
{
CHEEVOS_LOG(RCHEEVOS_TAG "http_task returned %d", http_data->status);
callback_data->callback(&server_response, callback_data->callback_data);
}
else

View file

@ -447,29 +447,71 @@ void rcheevos_menu_populate(void* data)
{
/* no achievements found */
if (!rcheevos_locals->core_supports)
{
menu_entries_append(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, NULL);
else if (!rc_client_get_game_info(rcheevos_locals->client))
}
else if (!game)
{
int state = rc_client_get_load_game_state(rcheevos_locals->client);
enum msg_hash_enums msg = MENU_ENUM_LABEL_VALUE_UNKNOWN_GAME;
switch (state)
{
case RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME:
msg = MENU_ENUM_LABEL_VALUE_CHEEVOS_IDENTIFYING_GAME;
break;
case RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN:
msg = MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN;
break;
case RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA:
msg = MENU_ENUM_LABEL_VALUE_CHEEVOS_FETCHING_GAME_DATA;
break;
case RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION:
msg = MENU_ENUM_LABEL_VALUE_CHEEVOS_STARTING_SESSION;
break;
case RC_CLIENT_LOAD_GAME_STATE_NONE:
if (!rc_client_get_user_info(rcheevos_locals->client))
msg = MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN;
break;
}
menu_entries_append(info->list,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNKNOWN_GAME),
msg_hash_to_str(MENU_ENUM_LABEL_UNKNOWN_GAME),
MENU_ENUM_LABEL_UNKNOWN_GAME,
msg_hash_to_str(msg),
msg_hash_to_str(MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY),
MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY,
FILE_TYPE_NONE, 0, 0, NULL);
}
else if (!game->id)
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "%s (%s)",
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_UNKNOWN_GAME), game->hash);
menu_entries_append(info->list,
buffer,
msg_hash_to_str(MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY),
MENU_ENUM_LABEL_NO_ACHIEVEMENTS_TO_DISPLAY,
FILE_TYPE_NONE, 0, 0, NULL);
}
else if (!rc_client_get_user_info(rcheevos_locals->client))
{
menu_entries_append(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, NULL);
}
else
{
menu_entries_append(info->list,
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, NULL);
}
}
}

View file

@ -1,3 +1,15 @@
# v11.2.0
* add alternate methods for state serialization/deserialization that accept a buffer_size parameter
* add RC_CLIENT_SUPPORTS_HASH compile flag
- allows rc_client code to build without the rhash files (except md5.c)
- must be explicitly defined to use rc_client_begin_identify_and_load_game
* add rc_client_get_load_game_state
* add rc_client_raintegration_set_get_game_name_function
* add RC_MEMSIZE_DOUBLE32 and RC_MEMSIZE_DOUBLE32_BE
* exclude directory records from ZIP hash algorithm
* fix media host when explicitly setting host to production server
* fix potential out-of-bounds read looking for error message in non-JSON response
# v11.1.0
* add rc_client_get_user_agent_clause to generate substring to include in client User-Agents
* add rc_client_can_pause function to control pause spam

View file

@ -6,9 +6,7 @@
#include <stdint.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
RC_BEGIN_C_DECLS
/* --- Fetch Achievement Info --- */
@ -63,10 +61,11 @@ typedef struct rc_api_fetch_achievement_info_response_t {
}
rc_api_fetch_achievement_info_response_t;
int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params);
int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response);
int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response);
RC_EXPORT int RC_CCONV rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params);
/* [deprecated] use rc_api_process_fetch_achievement_info_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response);
/* --- Fetch Leaderboard Info --- */
@ -135,10 +134,11 @@ typedef struct rc_api_fetch_leaderboard_info_response_t {
}
rc_api_fetch_leaderboard_info_response_t;
int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params);
int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response);
int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response);
RC_EXPORT int RC_CCONV rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params);
/* [deprecated] use rc_api_process_fetch_leaderboard_info_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response);
/* --- Fetch Games List --- */
@ -174,13 +174,12 @@ typedef struct rc_api_fetch_games_list_response_t {
}
rc_api_fetch_games_list_response_t;
int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params);
int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response);
int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response);
RC_EXPORT int RC_CCONV rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params);
/* [deprecated] use rc_api_process_fetch_games_list_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response);
#ifdef __cplusplus
}
#endif
RC_END_C_DECLS
#endif /* RC_API_INFO_H */

View file

@ -47,6 +47,7 @@ typedef struct rc_api_login_response_t {
rc_api_login_response_t;
RC_EXPORT int RC_CCONV rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params);
/* [deprecated] use rc_api_process_login_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_login_response(rc_api_login_response_t* response);
@ -104,6 +105,7 @@ typedef struct rc_api_start_session_response_t {
rc_api_start_session_response_t;
RC_EXPORT int RC_CCONV rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params);
/* [deprecated] use rc_api_process_start_session_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_start_session_response(rc_api_start_session_response_t* response);
@ -140,6 +142,7 @@ typedef struct rc_api_fetch_user_unlocks_response_t {
rc_api_fetch_user_unlocks_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params);
/* [deprecated] use rc_api_process_fetch_user_unlocks_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response);

View file

@ -221,6 +221,7 @@ RC_EXPORT void RC_CCONV rc_client_get_user_game_summary(const rc_client_t* clien
| Game |
\*****************************************************************************/
#ifdef RC_CLIENT_SUPPORTS_HASH
/**
* Start loading an unidentified game.
*/
@ -228,6 +229,7 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_g
uint32_t console_id, const char* file_path,
const uint8_t* data, size_t data_size,
rc_client_callback_t callback, void* callback_userdata);
#endif
/**
* Start loading a game.
@ -235,6 +237,20 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_g
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client_t* client, const char* hash,
rc_client_callback_t callback, void* callback_userdata);
/**
* Gets the current progress of the asynchronous load game process.
*/
RC_EXPORT int RC_CCONV rc_client_get_load_game_state(const rc_client_t* client);
enum {
RC_CLIENT_LOAD_GAME_STATE_NONE,
RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME,
RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN,
RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA,
RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION,
RC_CLIENT_LOAD_GAME_STATE_DONE,
RC_CLIENT_LOAD_GAME_STATE_ABORTED
};
/**
* Unloads the current game.
*/
@ -259,11 +275,19 @@ RC_EXPORT const rc_client_game_t* RC_CCONV rc_client_get_game_info(const rc_clie
*/
RC_EXPORT int RC_CCONV rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size);
#ifdef RC_CLIENT_SUPPORTS_HASH
/**
* Changes the active disc in a multi-disc game.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media(rc_client_t* client, const char* file_path,
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
#endif
/**
* Changes the active disc in a multi-disc game.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash,
rc_client_callback_t callback, void* callback_userdata);
/*****************************************************************************\
| Subsets |
@ -663,15 +687,29 @@ RC_EXPORT size_t RC_CCONV rc_client_progress_size(rc_client_t* client);
/**
* Serializes the runtime state into a buffer.
* Returns RC_OK on success, or an error indicator.
* [deprecated] use rc_client_serialize_progress_sized instead
*/
RC_EXPORT int RC_CCONV rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer);
/**
* Deserializes the runtime state from a buffer.
* Serializes the runtime state into a buffer.
* Returns RC_OK on success, or an error indicator.
*/
RC_EXPORT int RC_CCONV rc_client_serialize_progress_sized(rc_client_t* client, uint8_t* buffer, size_t buffer_size);
/**
* Deserializes the runtime state from a buffer.
* Returns RC_OK on success, or an error indicator.
* [deprecated] use rc_client_deserialize_progress_sized instead
*/
RC_EXPORT int RC_CCONV rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized);
/**
* Serializes the runtime state into a buffer.
* Returns RC_OK on success, or an error indicator.
*/
RC_EXPORT int RC_CCONV rc_client_deserialize_progress_sized(rc_client_t* client, const uint8_t* serialized, size_t serialized_size);
RC_END_C_DECLS
#endif /* RC_RUNTIME_H */

View file

@ -46,6 +46,8 @@ typedef void (RC_CCONV *rc_client_raintegration_event_handler_t)(const rc_client
typedef void (RC_CCONV *rc_client_raintegration_write_memory_func_t)(uint32_t address, uint8_t* buffer,
uint32_t num_bytes, rc_client_t* client);
typedef void (RC_CCONV* rc_client_raintegration_get_game_name_func_t)(char* buffer, uint32_t buffer_size, rc_client_t* client);
/* types needed to integrate raintegration */
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
@ -71,9 +73,11 @@ RC_EXPORT const rc_client_raintegration_menu_t* RC_CCONV rc_client_raintegration
RC_EXPORT void RC_CCONV rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu);
RC_EXPORT void RC_CCONV rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menu_item);
RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId);
RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t menu_item_id);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler);
RC_EXPORT int RC_CCONV rc_client_raintegration_has_modifications(const rc_client_t* client);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_event_handler(rc_client_t* client,
rc_client_raintegration_event_handler_t handler);

View file

@ -45,7 +45,8 @@ enum {
RC_NO_RESPONSE = -32,
RC_ACCESS_DENIED = -33,
RC_INVALID_CREDENTIALS = -34,
RC_EXPIRED_TOKEN = -35
RC_EXPIRED_TOKEN = -35,
RC_INSUFFICIENT_BUFFER = -36
};
RC_EXPORT const char* RC_CCONV rc_error_str(int ret);

View file

@ -143,9 +143,15 @@ typedef int (RC_CCONV *rc_runtime_validate_address_t)(uint32_t address);
RC_EXPORT void RC_CCONV rc_runtime_validate_addresses(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler);
RC_EXPORT void RC_CCONV rc_runtime_invalidate_address(rc_runtime_t* runtime, uint32_t address);
RC_EXPORT int RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L);
RC_EXPORT uint32_t RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L);
/* [deprecated] use rc_runtime_serialize_progress_sized instead */
RC_EXPORT int RC_CCONV rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L);
RC_EXPORT int RC_CCONV rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, lua_State* L);
/* [deprecated] use rc_runtime_deserialize_progress_sized instead */
RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L);
RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, lua_State* L);
RC_END_C_DECLS

View file

@ -59,6 +59,8 @@ enum {
RC_MEMSIZE_MBF32,
RC_MEMSIZE_MBF32_LE,
RC_MEMSIZE_FLOAT_BE,
RC_MEMSIZE_DOUBLE32,
RC_MEMSIZE_DOUBLE32_BE,
RC_MEMSIZE_VARIABLE
};

View file

@ -37,6 +37,24 @@ static void rc_json_skip_whitespace(rc_json_iterator_t* iterator)
++iterator->json;
}
static int rc_json_find_substring(rc_json_iterator_t* iterator, const char* substring)
{
const char first = *substring;
const size_t substring_len = strlen(substring);
const char* end = iterator->end - substring_len;
while (iterator->json <= end) {
if (*iterator->json == first) {
if (memcmp(iterator->json, substring, substring_len) == 0)
return 1;
}
++iterator->json;
}
return 0;
}
static int rc_json_find_closing_quote(rc_json_iterator_t* iterator)
{
while (iterator->json < iterator->end) {
@ -237,8 +255,6 @@ int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t*
}
int rc_json_get_object_string_length(const char* json) {
const char* json_start = json;
rc_json_iterator_t iterator;
memset(&iterator, 0, sizeof(iterator));
iterator.json = json;
@ -246,34 +262,41 @@ int rc_json_get_object_string_length(const char* json) {
rc_json_parse_object(&iterator, NULL, 0, NULL);
return (int)(iterator.json - json_start);
if (iterator.json == json) /* not JSON */
return (int)strlen(json);
return (int)(iterator.json - json);
}
static int rc_json_extract_html_error(rc_api_response_t* response, const rc_api_server_response_t* server_response) {
const char* json = server_response->body;
const char* end = json;
rc_json_iterator_t iterator;
memset(&iterator, 0, sizeof(iterator));
iterator.json = server_response->body;
iterator.end = server_response->body + server_response->body_length;
const char* title_start = strstr(json, "<title>");
if (title_start) {
title_start += 7;
if (isdigit((int)*title_start)) {
const char* title_end = strstr(title_start + 7, "</title>");
if (title_end) {
response->error_message = rc_buffer_strncpy(&response->buffer, title_start, title_end - title_start);
response->succeeded = 0;
return RC_INVALID_JSON;
}
/* if the title contains an HTTP status code(i.e "404 Not Found"), return the title */
if (rc_json_find_substring(&iterator, "<title>")) {
const char* title_start = iterator.json + 7;
if (isdigit((int)*title_start) && rc_json_find_substring(&iterator, "</title>")) {
response->error_message = rc_buffer_strncpy(&response->buffer, title_start, iterator.json - title_start);
response->succeeded = 0;
return RC_INVALID_JSON;
}
}
while (*end && *end != '\n' && end - json < 200)
++end;
/* title not found, or did not start with an error code, return the first line of the response */
iterator.json = server_response->body;
if (end > json && end[-1] == '\r')
--end;
while (iterator.json < iterator.end && *iterator.json != '\n' &&
iterator.json - server_response->body < 200) {
++iterator.json;
}
if (end > json)
response->error_message = rc_buffer_strncpy(&response->buffer, json, end - json);
if (iterator.json > server_response->body && iterator.json[-1] == '\r')
--iterator.json;
if (iterator.json > server_response->body)
response->error_message = rc_buffer_strncpy(&response->buffer, server_response->body, iterator.json - server_response->body);
response->succeeded = 0;
return RC_INVALID_JSON;
@ -1150,6 +1173,9 @@ static void rc_api_update_host(char** host, const char* hostname) {
}
void rc_api_set_host(const char* hostname) {
if (hostname && strcmp(hostname, RETROACHIEVEMENTS_HOST) == 0)
hostname = NULL;
rc_api_update_host(&g_host, hostname);
if (!hostname) {

View file

@ -42,9 +42,12 @@ typedef struct rc_client_generic_callback_data_t {
typedef struct rc_client_pending_media_t
{
#ifdef RC_CLIENT_SUPPORTS_HASH
const char* file_path;
uint8_t* data;
size_t data_size;
#endif
const char* hash;
rc_client_callback_t callback;
void* callback_userdata;
} rc_client_pending_media_t;
@ -59,7 +62,9 @@ typedef struct rc_client_load_state_t
rc_client_subset_info_t* subset;
rc_client_game_hash_t* hash;
#ifdef RC_CLIENT_SUPPORTS_HASH
rc_hash_iterator_t hash_iterator;
#endif
rc_client_pending_media_t* pending_media;
rc_api_start_session_response_t *start_session_response;
@ -68,7 +73,9 @@ typedef struct rc_client_load_state_t
uint8_t progress;
uint8_t outstanding_requests;
#ifdef RC_CLIENT_SUPPORTS_HASH
uint8_t hash_console_id;
#endif
} rc_client_load_state_t;
static void rc_client_begin_fetch_game_data(rc_client_load_state_t* callback_data);
@ -135,6 +142,10 @@ void rc_client_destroy(rc_client_t* client)
rc_client_unload_game(client);
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
rc_client_unload_raintegration(client);
#endif
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->destroy)
client->state.external_client->destroy();
@ -151,9 +162,11 @@ void rc_client_destroy(rc_client_t* client)
static rc_client_t* g_hash_client = NULL;
#ifdef RC_CLIENT_SUPPORTS_HASH
static void rc_client_log_hash_message(const char* message) {
rc_client_log_message(g_hash_client, message);
}
#endif
void rc_client_log_message(const rc_client_t* client, const char* message)
{
@ -612,7 +625,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp
if (login_callback_data->callback)
login_callback_data->callback(result, error_message, client, login_callback_data->callback_userdata);
if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN)
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
rc_client_begin_fetch_game_data(load_state);
}
else {
@ -635,7 +648,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp
RC_CLIENT_LOG_INFO_FORMATTED(client, "%s logged in successfully", login_response.display_name);
if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN)
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
rc_client_begin_fetch_game_data(load_state);
if (login_callback_data->callback)
@ -796,7 +809,7 @@ void rc_client_logout(rc_client_t* client)
rc_client_unload_game(client);
if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN)
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
rc_client_load_error(load_state, RC_ABORTED, "Login aborted");
}
@ -937,9 +950,9 @@ static int rc_client_end_load_state(rc_client_load_state_t* load_state)
* the outstanding_requests count will reach zero and the memory will be free'd then. */
if (remaining_requests == 0) {
/* if one of the callbacks called rc_client_load_error, progress will be set to
* RC_CLIENT_LOAD_STATE_UNKNOWN. There's no need to call the callback with RC_ABORTED
* RC_CLIENT_LOAD_STATE_ABORTED. There's no need to call the callback with RC_ABORTED
* in that case, as it will have already been called with something more appropriate. */
if (load_state->progress != RC_CLIENT_LOAD_STATE_UNKNOWN_GAME && load_state->callback)
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED && load_state->callback)
load_state->callback(RC_ABORTED, "The requested game is no longer active", load_state->client, load_state->callback_userdata);
rc_client_free_load_state(load_state);
@ -957,7 +970,7 @@ static void rc_client_load_error(rc_client_load_state_t* load_state, int result,
rc_mutex_lock(&load_state->client->state.mutex);
load_state->progress = RC_CLIENT_LOAD_STATE_UNKNOWN_GAME;
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED;
if (load_state->client->state.load == load_state)
load_state->client->state.load = NULL;
@ -1401,17 +1414,29 @@ static void rc_client_apply_unlocks(rc_client_subset_info_t* subset, rc_api_unlo
}
}
static void rc_client_free_pending_media(rc_client_pending_media_t* pending_media)
{
if (pending_media->hash)
free((void*)pending_media->hash);
#ifdef RC_CLIENT_SUPPORTS_HASH
if (pending_media->data)
free(pending_media->data);
free((void*)pending_media->file_path);
#endif
free(pending_media);
}
static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_start_session_response_t *start_session_response)
{
rc_client_t* client = load_state->client;
rc_mutex_lock(&client->state.mutex);
load_state->progress = (client->state.load == load_state) ?
RC_CLIENT_LOAD_STATE_DONE : RC_CLIENT_LOAD_STATE_UNKNOWN_GAME;
RC_CLIENT_LOAD_GAME_STATE_DONE : RC_CLIENT_LOAD_GAME_STATE_ABORTED;
client->state.load = NULL;
rc_mutex_unlock(&client->state.mutex);
if (load_state->progress != RC_CLIENT_LOAD_STATE_DONE) {
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_DONE) {
/* previous load state was aborted */
if (load_state->callback)
load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata);
@ -1449,12 +1474,17 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
rc_mutex_unlock(&load_state->client->state.mutex);
if (pending_media) {
rc_client_begin_change_media(client, pending_media->file_path,
pending_media->data, pending_media->data_size, pending_media->callback, pending_media->callback_userdata);
if (pending_media->data)
free(pending_media->data);
free((void*)pending_media->file_path);
free(pending_media);
if (pending_media->hash) {
rc_client_begin_change_media_from_hash(client, pending_media->hash,
pending_media->callback, pending_media->callback_userdata);
} else {
#ifdef RC_CLIENT_SUPPORTS_HASH
rc_client_begin_change_media(client, pending_media->file_path,
pending_media->data, pending_media->data_size,
pending_media->callback, pending_media->callback_userdata);
#endif
}
rc_client_free_pending_media(pending_media);
}
/* client->game must be set before calling this function so it can query the console_id */
@ -1561,7 +1591,7 @@ static void rc_client_begin_start_session(rc_client_load_state_t* load_state)
rc_client_load_error(load_state, result, rc_error_str(result));
}
else {
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, 1);
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Starting session for game %u", start_session_params.game_id);
rc_client_begin_async(client, &load_state->async_handle);
client->callbacks.server_call(&start_session_request, rc_client_start_session_callback, load_state, client);
@ -1876,7 +1906,7 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
}
/* kick off the start session request while we process the game data */
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, 1);
if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) {
/* we can't unlock achievements without a session, lock spectator mode for the game */
load_state->client->state.spectator_mode = RC_CLIENT_SPECTATOR_MODE_LOCKED;
@ -1962,6 +1992,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
int result;
if (load_state->hash->game_id == 0) {
#ifdef RC_CLIENT_SUPPORTS_HASH
char hash[33];
if (rc_hash_iterate(hash, &load_state->hash_iterator)) {
@ -2010,18 +2041,34 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
/* only a single hash was tried, capture it */
load_state->game->public_.console_id = load_state->hash_console_id;
load_state->game->public_.hash = load_state->hash->hash;
if (client->callbacks.identify_unknown_hash) {
load_state->hash->game_id = client->callbacks.identify_unknown_hash(
load_state->hash_console_id, load_state->hash->hash, client, load_state->callback_userdata);
if (load_state->hash->game_id != 0) {
RC_CLIENT_LOG_INFO_FORMATTED(load_state->client, "Client says to load game %u for unidentified hash %s",
load_state->hash->game_id, load_state->hash->hash);
}
}
}
#else
load_state->game->public_.console_id = RC_CONSOLE_UNKNOWN;
load_state->game->public_.hash = load_state->hash->hash;
#endif /* RC_CLIENT_SUPPORTS_HASH */
load_state->game->public_.title = "Unknown Game";
load_state->game->public_.badge_name = "";
client->game = load_state->game;
load_state->game = NULL;
if (load_state->hash->game_id == 0) {
load_state->game->public_.title = "Unknown Game";
load_state->game->public_.badge_name = "";
client->game = load_state->game;
load_state->game = NULL;
rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game");
return;
rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game");
return;
}
}
if (load_state->hash->hash[0] != '[') {
if (load_state->hash->hash[0] != '[') { /* not [NO HASH] or [SUBSETxx] */
load_state->game->public_.id = load_state->hash->game_id;
load_state->game->public_.hash = load_state->hash->hash;
}
@ -2032,7 +2079,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
rc_mutex_lock(&client->state.mutex);
result = client->state.user;
if (result == RC_CLIENT_USER_STATE_LOGIN_REQUESTED)
load_state->progress = RC_CLIENT_LOAD_STATE_AWAIT_LOGIN;
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN;
rc_mutex_unlock(&client->state.mutex);
switch (result) {
@ -2059,7 +2106,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
return;
}
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, 1);
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Fetching data for game %u", fetch_game_data_request.game_id);
rc_client_begin_async(client, &load_state->async_handle);
@ -2201,7 +2248,7 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
return NULL;
}
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, 1);
rc_client_begin_async(client, &load_state->async_handle);
client->callbacks.server_call(&request, rc_client_identify_game_callback, load_state, client);
@ -2217,14 +2264,6 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
}
rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client)
{
if (client && client->state.load)
return &client->state.load->hash_iterator;
return NULL;
}
rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const char* hash, rc_client_callback_t callback, void* callback_userdata)
{
rc_client_load_state_t* load_state;
@ -2257,6 +2296,16 @@ rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const c
return rc_client_load_game(load_state, hash, NULL);
}
#ifdef RC_CLIENT_SUPPORTS_HASH
rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client)
{
if (client && client->state.load)
return &client->state.load->hash_iterator;
return NULL;
}
rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* client,
uint32_t console_id, const char* file_path,
const uint8_t* data, size_t data_size,
@ -2340,6 +2389,22 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
return rc_client_load_game(load_state, hash, file_path);
}
#endif /* RC_CLIENT_SUPPORTS_HASH */
int rc_client_get_load_game_state(const rc_client_t* client)
{
int state = RC_CLIENT_LOAD_GAME_STATE_NONE;
if (client) {
const rc_client_load_state_t* load_state = client->state.load;
if (load_state)
state = load_state->progress;
else if (client->game)
state = RC_CLIENT_LOAD_GAME_STATE_DONE;
}
return state;
}
static void rc_client_game_mark_ui_to_be_hidden(rc_client_t* client, rc_client_game_info_t* game)
{
rc_client_achievement_info_t* achievement;
@ -2428,8 +2493,10 @@ void rc_client_unload_game(rc_client_t* client)
}
}
static void rc_client_change_media(rc_client_t* client, const rc_client_game_hash_t* game_hash, rc_client_callback_t callback, void* callback_userdata)
static void rc_client_change_media_internal(rc_client_t* client, const rc_client_game_hash_t* game_hash, rc_client_callback_t callback, void* callback_userdata)
{
client->game->public_.hash = game_hash->hash;
if (game_hash->game_id == client->game->public_.id) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to valid media for game %u: %s", game_hash->game_id, game_hash->hash);
}
@ -2437,13 +2504,19 @@ static void rc_client_change_media(rc_client_t* client, const rc_client_game_has
RC_CLIENT_LOG_INFO(client, "Switching to unknown media");
}
else if (game_hash->game_id == 0) {
if (client->state.hardcore) {
RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabling hardcore for unidentified media: %s", game_hash->hash);
rc_client_set_hardcore_enabled(client, 0);
callback(RC_HARDCORE_DISABLED, "Hardcore disabled. Unidentified media inserted.", client, callback_userdata);
return;
}
RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to unrecognized media: %s", game_hash->hash);
}
else {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to known media for game %u: %s", game_hash->game_id, game_hash->hash);
}
client->game->public_.hash = game_hash->hash;
callback(RC_OK, NULL, client, callback_userdata);
}
@ -2475,22 +2548,65 @@ static void rc_client_identify_changed_media_callback(const rc_api_server_respon
else {
load_state->hash->game_id = resolve_hash_response.game_id;
if (resolve_hash_response.game_id == 0 && client->state.hardcore) {
RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabling hardcore for unidentified media: %s", load_state->hash->hash);
rc_client_set_hardcore_enabled(client, 0);
client->game->public_.hash = load_state->hash->hash; /* do still update the loaded hash */
load_state->callback(RC_HARDCORE_DISABLED, "Hardcore disabled. Unidentified media inserted.", client, load_state->callback_userdata);
}
else {
if (resolve_hash_response.game_id != 0) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Identified game: %u (%s)", load_state->hash->game_id, load_state->hash->hash);
rc_client_change_media(client, load_state->hash, load_state->callback, load_state->callback_userdata);
}
rc_client_change_media_internal(client, load_state->hash, load_state->callback, load_state->callback_userdata);
}
free(load_state);
rc_api_destroy_resolve_hash_response(&resolve_hash_response);
}
static rc_client_async_handle_t* rc_client_begin_change_media_internal(rc_client_t* client,
rc_client_game_info_t* game, rc_client_game_hash_t* game_hash, rc_client_callback_t callback, void* callback_userdata)
{
rc_client_load_state_t* callback_data;
rc_client_async_handle_t* async_handle;
rc_api_resolve_hash_request_t resolve_hash_request;
rc_api_request_t request;
int result;
if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID) {
rc_client_change_media_internal(client, game_hash, callback, callback_userdata);
return NULL;
}
/* call the server to make sure the hash is valid for the loaded game */
memset(&resolve_hash_request, 0, sizeof(resolve_hash_request));
resolve_hash_request.game_hash = game_hash->hash;
result = rc_api_init_resolve_hash_request(&request, &resolve_hash_request);
if (result != RC_OK) {
callback(result, rc_error_str(result), client, callback_userdata);
return NULL;
}
callback_data = (rc_client_load_state_t*)calloc(1, sizeof(rc_client_load_state_t));
if (!callback_data) {
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
return NULL;
}
callback_data->callback = callback;
callback_data->callback_userdata = callback_userdata;
callback_data->client = client;
callback_data->hash = game_hash;
callback_data->game = game;
async_handle = &callback_data->async_handle;
rc_client_begin_async(client, async_handle);
client->callbacks.server_call(&request, rc_client_identify_changed_media_callback, callback_data, client);
rc_api_destroy_request(&request);
/* if handle is no longer valid, the async operation completed synchronously */
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
}
#ifdef RC_CLIENT_SUPPORTS_HASH
rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, const char* file_path,
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata)
{
@ -2521,12 +2637,8 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
if (game->public_.console_id == 0) {
/* still waiting for game data */
pending_media = client->state.load->pending_media;
if (pending_media) {
if (pending_media->data)
free(pending_media->data);
free((void*)pending_media->file_path);
free(pending_media);
}
if (pending_media)
rc_client_free_pending_media(pending_media);
pending_media = (rc_client_pending_media_t*)calloc(1, sizeof(*pending_media));
if (!pending_media) {
@ -2613,53 +2725,78 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
rc_mutex_unlock(&client->state.mutex);
if (!result) {
rc_client_change_media(client, game_hash, callback, callback_userdata);
rc_client_change_media_internal(client, game_hash, callback, callback_userdata);
return NULL;
}
}
if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID) {
rc_client_change_media(client, game_hash, callback, callback_userdata);
return rc_client_begin_change_media_internal(client, game, game_hash, callback, callback_userdata);
}
#endif /* RC_CLIENT_SUPPORTS_HASH */
rc_client_async_handle_t* rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash,
rc_client_callback_t callback, void* callback_userdata)
{
rc_client_game_hash_t* game_hash;
rc_client_game_info_t* game;
rc_client_pending_media_t* pending_media = NULL;
if (!client) {
callback(RC_INVALID_STATE, "client is required", client, callback_userdata);
return NULL;
}
else {
/* call the server to make sure the hash is valid for the loaded game */
rc_client_load_state_t* callback_data;
rc_client_async_handle_t* async_handle;
rc_api_resolve_hash_request_t resolve_hash_request;
rc_api_request_t request;
int result;
memset(&resolve_hash_request, 0, sizeof(resolve_hash_request));
resolve_hash_request.game_hash = game_hash->hash;
result = rc_api_init_resolve_hash_request(&request, &resolve_hash_request);
if (result != RC_OK) {
callback(result, rc_error_str(result), client, callback_userdata);
return NULL;
}
callback_data = (rc_client_load_state_t*)calloc(1, sizeof(rc_client_load_state_t));
if (!callback_data) {
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
return NULL;
}
callback_data->callback = callback;
callback_data->callback_userdata = callback_userdata;
callback_data->client = client;
callback_data->hash = game_hash;
callback_data->game = game;
async_handle = &callback_data->async_handle;
rc_client_begin_async(client, async_handle);
client->callbacks.server_call(&request, rc_client_identify_changed_media_callback, callback_data, client);
rc_api_destroy_request(&request);
/* if handle is no longer valid, the async operation completed synchronously */
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
if (!hash || !hash[0]) {
callback(RC_INVALID_STATE, "hash is required", client, callback_userdata);
return NULL;
}
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_change_media_from_hash) {
return client->state.external_client->begin_change_media_from_hash(client, hash, callback, callback_userdata);
}
#endif
rc_mutex_lock(&client->state.mutex);
if (client->state.load) {
game = client->state.load->game;
if (game->public_.console_id == 0) {
/* still waiting for game data */
pending_media = client->state.load->pending_media;
if (pending_media)
rc_client_free_pending_media(pending_media);
pending_media = (rc_client_pending_media_t*)calloc(1, sizeof(*pending_media));
if (!pending_media) {
rc_mutex_unlock(&client->state.mutex);
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
return NULL;
}
pending_media->hash = strdup(hash);
pending_media->callback = callback;
pending_media->callback_userdata = callback_userdata;
client->state.load->pending_media = pending_media;
}
} else {
game = client->game;
}
rc_mutex_unlock(&client->state.mutex);
if (!game) {
callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata);
return NULL;
}
/* still waiting for game data */
if (pending_media)
return NULL;
/* check to see if we've already hashed this file. */
game_hash = rc_client_find_game_hash(client, hash);
return rc_client_begin_change_media_internal(client, game, game_hash, callback, callback_userdata);
}
const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client)
@ -3753,7 +3890,7 @@ int rc_client_has_leaderboards(rc_client_t* client)
return result;
}
static void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard)
void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard)
{
rc_client_leaderboard_tracker_info_t* tracker;
rc_client_leaderboard_tracker_info_t* available_tracker = NULL;
@ -5234,6 +5371,11 @@ size_t rc_client_progress_size(rc_client_t* client)
}
int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer)
{
return rc_client_serialize_progress_sized(client, buffer, 0xFFFFFFFF);
}
int rc_client_serialize_progress_sized(rc_client_t* client, uint8_t* buffer, size_t buffer_size)
{
int result;
@ -5242,7 +5384,7 @@ int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer)
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->serialize_progress)
return client->state.external_client->serialize_progress(buffer);
return client->state.external_client->serialize_progress(buffer, buffer_size);
#endif
if (!client->game)
@ -5252,7 +5394,7 @@ int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer)
return RC_INVALID_STATE;
rc_mutex_lock(&client->state.mutex);
result = rc_runtime_serialize_progress(buffer, &client->game->runtime, NULL);
result = rc_runtime_serialize_progress_sized(buffer, (uint32_t)buffer_size, &client->game->runtime, NULL);
rc_mutex_unlock(&client->state.mutex);
return result;
@ -5352,6 +5494,11 @@ static void rc_client_subset_after_deserialize_progress(rc_client_game_info_t* g
}
int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized)
{
return rc_client_deserialize_progress_sized(client, serialized, 0xFFFFFFFF);
}
int rc_client_deserialize_progress_sized(rc_client_t* client, const uint8_t* serialized, size_t serialized_size)
{
rc_client_subset_info_t* subset;
int result;
@ -5361,7 +5508,7 @@ int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialize
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->deserialize_progress)
return client->state.external_client->deserialize_progress(serialized);
return client->state.external_client->deserialize_progress(serialized, serialized_size);
#endif
if (!client->game)
@ -5381,7 +5528,7 @@ int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialize
result = RC_OK;
}
else {
result = rc_runtime_deserialize_progress(&client->game->runtime, serialized, NULL);
result = rc_runtime_deserialize_progress_sized(&client->game->runtime, serialized, (uint32_t)serialized_size, NULL);
}
for (subset = client->game->subsets; subset; subset = subset->next)

View file

@ -61,8 +61,8 @@ typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_lead
typedef size_t (RC_CCONV *rc_client_external_progress_size_func_t)(void);
typedef int (RC_CCONV *rc_client_external_serialize_progress_func_t)(uint8_t* buffer);
typedef int (RC_CCONV *rc_client_external_deserialize_progress_func_t)(const uint8_t* buffer);
typedef int (RC_CCONV *rc_client_external_serialize_progress_func_t)(uint8_t* buffer, size_t buffer_size);
typedef int (RC_CCONV *rc_client_external_deserialize_progress_func_t)(const uint8_t* buffer, size_t buffer_size);
typedef struct rc_client_external_t
{
@ -99,6 +99,7 @@ typedef struct rc_client_external_t
rc_client_external_action_func_t unload_game;
rc_client_external_get_user_game_summary_func_t get_user_game_summary;
rc_client_external_begin_change_media_func_t begin_change_media;
rc_client_external_begin_load_game_func_t begin_change_media_from_hash;
rc_client_external_create_achievement_list_func_t create_achievement_list;
rc_client_external_get_int_func_t has_achievements;

View file

@ -26,6 +26,8 @@ typedef void (RC_CCONV *rc_client_post_process_game_data_response_t)(const rc_ap
typedef int (RC_CCONV *rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client);
typedef int (RC_CCONV *rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client);
typedef int (RC_CCONV *rc_client_rich_presence_override_t)(rc_client_t* client, char buffer[], size_t buffersize);
typedef uint32_t (RC_CCONV* rc_client_identify_hash_func_t)(uint32_t console_id, const char* hash,
rc_client_t* client, void* callback_userdata);
typedef struct rc_client_callbacks_t {
rc_client_read_memory_func_t read_memory;
@ -33,6 +35,7 @@ typedef struct rc_client_callbacks_t {
rc_client_server_call_t server_call;
rc_client_message_callback_t log_call;
rc_get_time_millisecs_func_t get_time_millisecs;
rc_client_identify_hash_func_t identify_unknown_hash;
rc_client_post_process_game_data_response_t post_process_game_data_response;
rc_client_can_submit_achievement_unlock_t can_submit_achievement_unlock;
rc_client_can_submit_leaderboard_entry_t can_submit_leaderboard_entry;
@ -265,16 +268,6 @@ void rc_client_update_active_leaderboards(rc_client_game_info_t* game);
| Client |
\*****************************************************************************/
enum {
RC_CLIENT_LOAD_STATE_NONE,
RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME,
RC_CLIENT_LOAD_STATE_AWAIT_LOGIN,
RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA,
RC_CLIENT_LOAD_STATE_STARTING_SESSION,
RC_CLIENT_LOAD_STATE_DONE,
RC_CLIENT_LOAD_STATE_UNKNOWN_GAME
};
enum {
RC_CLIENT_USER_STATE_NONE,
RC_CLIENT_USER_STATE_LOGIN_REQUESTED,
@ -375,8 +368,10 @@ int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref)
/* end runtime.c internals */
/* helper functions for unit tests */
#ifdef RC_CLIENT_SUPPORTS_HASH
struct rc_hash_iterator;
struct rc_hash_iterator* rc_client_get_load_state_hash_iterator(rc_client_t* client);
#endif
/* end helper functions for unit tests */
enum {
@ -387,6 +382,7 @@ enum {
void rc_client_set_legacy_peek(rc_client_t* client, int method);
void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard);
void rc_client_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard);
RC_END_C_DECLS

View file

@ -77,7 +77,9 @@ static void rc_client_raintegration_load_dll(rc_client_t* client,
raintegration->get_menu = (rc_client_raintegration_get_menu_func_t)GetProcAddress(hDLL, "_Rcheevos_RAIntegrationGetMenu");
raintegration->activate_menu_item = (rc_client_raintegration_activate_menuitem_func_t)GetProcAddress(hDLL, "_Rcheevos_ActivateRAIntegrationMenuItem");
raintegration->set_write_memory_function = (rc_client_raintegration_set_write_memory_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationWriteMemoryFunction");
raintegration->set_get_game_name_function = (rc_client_raintegration_set_get_game_name_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationGetGameNameFunction");
raintegration->set_event_handler = (rc_client_raintegration_set_event_handler_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationEventHandler");
raintegration->has_modifications = (rc_client_raintegration_get_int_func_t)GetProcAddress(hDLL, "_Rcheevos_HasModifications");
if (!raintegration->get_version ||
!raintegration->init_client ||
@ -147,7 +149,8 @@ static void rc_client_init_raintegration(rc_client_t* client,
const char* host_url = client->state.raintegration->get_host_url();
if (host_url) {
if (strcmp(host_url, "OFFLINE") != 0) {
rc_client_set_host(client, host_url);
if (strcmp(host_url, "https://retroachievements.org") != 0)
rc_client_set_host(client, host_url);
}
else if (client->state.raintegration->init_client_offline) {
init_func = client->state.raintegration->init_client_offline;
@ -363,6 +366,12 @@ void rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_c
client->state.raintegration->set_write_memory_function(client, handler);
}
void rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler)
{
if (client && client->state.raintegration && client->state.raintegration->set_get_game_name_function)
client->state.raintegration->set_get_game_name_function(client, handler);
}
void rc_client_raintegration_set_event_handler(rc_client_t* client,
rc_client_raintegration_event_handler_t handler)
{
@ -382,6 +391,18 @@ const rc_client_raintegration_menu_t* rc_client_raintegration_get_menu(const rc_
return client->state.raintegration->get_menu();
}
int rc_client_raintegration_has_modifications(const rc_client_t* client)
{
if (!client || !client->state.raintegration ||
!client->state.raintegration->bIsInited ||
!client->state.raintegration->has_modifications)
{
return 0;
}
return client->state.raintegration->has_modifications();
}
void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
{
HMENU hPopupMenu = NULL;
@ -413,7 +434,7 @@ void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
if (menuitem->checked)
flags |= MF_CHECKED;
if (!menuitem->enabled)
flags |= MF_DISABLED | MF_GRAYED;
flags |= MF_GRAYED;
AppendMenuA(hPopupMenu, flags, menuitem->id, menuitem->label);
}
@ -428,7 +449,7 @@ void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
UINT flags = MF_POPUP | MF_STRING;
if (!menu || !menu->num_items)
flags |= MF_DISABLED | MF_GRAYED;
flags |= MF_GRAYED;
while (--nIndex >= 0)
{
@ -457,15 +478,18 @@ void rc_client_raintegration_update_menu_item(const rc_client_t* client, const r
flags |= MF_CHECKED;
CheckMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND);
flags = (menuitem->enabled) ? MF_ENABLED : MF_GRAYED;
EnableMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND);
}
}
int rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId)
int rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t menu_item_id)
{
if (!client || !client->state.raintegration || !client->state.raintegration->activate_menu_item)
return 0;
return client->state.raintegration->activate_menu_item(nMenuItemId);
return client->state.raintegration->activate_menu_item(menu_item_id);
}
void rc_client_unload_raintegration(rc_client_t* client)
@ -477,6 +501,9 @@ void rc_client_unload_raintegration(rc_client_t* client)
RC_CLIENT_LOG_INFO(client, "Unloading RA_Integration")
if (client->state.external_client && client->state.external_client->destroy)
client->state.external_client->destroy();
if (client->state.raintegration->shutdown)
client->state.raintegration->shutdown();

View file

@ -20,7 +20,9 @@ typedef void (RC_CCONV* rc_client_raintegration_hwnd_action_func_t)(HWND hWnd);
typedef const rc_client_raintegration_menu_t* (RC_CCONV* rc_client_raintegration_get_menu_func_t)(void);
typedef int (RC_CCONV* rc_client_raintegration_activate_menuitem_func_t)(uint32_t nMenuItemId);
typedef void (RC_CCONV* rc_client_raintegration_set_write_memory_func_t)(rc_client_t* pClient, rc_client_raintegration_write_memory_func_t handler);
typedef void (RC_CCONV* rc_client_raintegration_set_get_game_name_func_t)(rc_client_t* pClient, rc_client_raintegration_get_game_name_func_t handler);
typedef void (RC_CCONV* rc_client_raintegration_set_event_handler_func_t)(rc_client_t* pClient, rc_client_raintegration_event_handler_t handler);
typedef int (RC_CCONV* rc_client_raintegration_get_int_func_t)(void);
typedef struct rc_client_raintegration_t
{
@ -37,9 +39,11 @@ typedef struct rc_client_raintegration_t
rc_client_raintegration_hwnd_action_func_t update_main_window_handle;
rc_client_raintegration_set_write_memory_func_t set_write_memory_function;
rc_client_raintegration_set_get_game_name_func_t set_get_game_name_function;
rc_client_raintegration_set_event_handler_func_t set_event_handler;
rc_client_raintegration_get_menu_func_t get_menu;
rc_client_raintegration_activate_menuitem_func_t activate_menu_item;
rc_client_raintegration_get_int_func_t has_modifications;
rc_client_raintegration_get_external_client_func_t get_external_client;

View file

@ -183,6 +183,7 @@ const char* rc_error_str(int ret)
case RC_ACCESS_DENIED: return "Access denied";
case RC_INVALID_CREDENTIALS: return "Invalid credentials";
case RC_EXPIRED_TOKEN: return "Expired token";
case RC_INSUFFICIENT_BUFFER: return "Buffer not large enough";
default: return "Unknown error";
}
}

View file

@ -8,7 +8,7 @@
RC_BEGIN_C_DECLS
#define RCHEEVOS_VERSION_MAJOR 11
#define RCHEEVOS_VERSION_MINOR 1
#define RCHEEVOS_VERSION_MINOR 2
#define RCHEEVOS_VERSION_PATCH 0
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)

View file

@ -95,6 +95,8 @@ int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address) {
switch (*aux++) {
case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break;
case 'b': case 'B': *size = RC_MEMSIZE_FLOAT_BE; break;
case 'h': case 'H': *size = RC_MEMSIZE_DOUBLE32; break;
case 'i': case 'I': *size = RC_MEMSIZE_DOUBLE32_BE; break;
case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break;
case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break;
@ -198,6 +200,29 @@ static void rc_transform_memref_float_be(rc_typed_value_t* value) {
value->type = RC_VALUE_TYPE_FLOAT;
}
static void rc_transform_memref_double32(rc_typed_value_t* value)
{
/* decodes the four most significant bytes of an IEEE 754 double into a float */
const uint32_t mantissa = (value->value.u32 & 0x000FFFFF) << 3;
const int32_t exponent = (int32_t)((value->value.u32 >> 20) & 0x7FF) - 1023;
const int sign = (value->value.u32 & 0x80000000);
value->value.f32 = rc_build_float(mantissa, exponent, sign);
value->type = RC_VALUE_TYPE_FLOAT;
}
static void rc_transform_memref_double32_be(rc_typed_value_t* value)
{
/* decodes the four most significant bytes of an IEEE 754 double in big endian format into a float */
const uint32_t mantissa = (((value->value.u32 & 0xFF000000) >> 24) |
((value->value.u32 & 0x00FF0000) >> 8) |
((value->value.u32 & 0x00000F00) << 8)) << 3;
const int32_t exponent = (int32_t)(((value->value.u32 & 0x0000007F) << 4) |
((value->value.u32 & 0x0000F000) >> 12)) - 1023;
const int sign = (value->value.u32 & 0x00000080);
value->value.f32 = rc_build_float(mantissa, exponent, sign);
value->type = RC_VALUE_TYPE_FLOAT;
}
static void rc_transform_memref_mbf32(rc_typed_value_t* value) {
/* decodes a Microsoft Binary Format float */
/* NOTE: 32-bit MBF is stored in memory as big endian (at least for Apple II) */
@ -322,6 +347,14 @@ void rc_transform_memref_value(rc_typed_value_t* value, uint8_t size) {
rc_transform_memref_float_be(value);
break;
case RC_MEMSIZE_DOUBLE32:
rc_transform_memref_double32(value);
break;
case RC_MEMSIZE_DOUBLE32_BE:
rc_transform_memref_double32_be(value);
break;
case RC_MEMSIZE_MBF32:
rc_transform_memref_mbf32(value);
break;
@ -358,6 +391,8 @@ static const uint32_t rc_memref_masks[] = {
0xffffffff, /* RC_MEMSIZE_MBF32 */
0xffffffff, /* RC_MEMSIZE_MBF32_LE */
0xffffffff, /* RC_MEMSIZE_FLOAT_BE */
0xffffffff, /* RC_MEMSIZE_DOUBLE32 */
0xffffffff, /* RC_MEMSIZE_DOUBLE32_BE*/
0xffffffff /* RC_MEMSIZE_VARIABLE */
};
@ -395,6 +430,8 @@ static const uint8_t rc_memref_shared_sizes[] = {
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT_BE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_DOUBLE32 */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_DOUBLE32_BE*/
RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */
};

View file

@ -297,6 +297,8 @@ int rc_operand_is_float_memref(const rc_operand_t* self) {
switch (self->size) {
case RC_MEMSIZE_FLOAT:
case RC_MEMSIZE_FLOAT_BE:
case RC_MEMSIZE_DOUBLE32:
case RC_MEMSIZE_DOUBLE32_BE:
case RC_MEMSIZE_MBF32:
case RC_MEMSIZE_MBF32_LE:
return 1;

View file

@ -4,6 +4,7 @@
#include "rc_util.h"
#include "../rhash/md5.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
@ -17,17 +18,22 @@
#define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */
#define RC_RUNTIME_MIN_BUFFER_SIZE 4 + 8 + 16 /* RUNTIME_MARKER, CHUNK_DONE, MD5 */
typedef struct rc_runtime_progress_t {
const rc_runtime_t* runtime;
uint32_t offset;
uint8_t* buffer;
uint32_t buffer_size;
uint32_t chunk_size_offset;
lua_State* L;
} rc_runtime_progress_t;
#define assert_chunk_size(expected_size) assert((uint32_t)(progress->offset - progress->chunk_size_offset - 4) == (uint32_t)(expected_size))
#define RC_TRIGGER_STATE_UNUPDATED 0x7F
#define RC_MEMREF_FLAG_CHANGED_THIS_FRAME 0x00010000
@ -117,21 +123,29 @@ static void rc_runtime_progress_init(rc_runtime_progress_t* progress, const rc_r
progress->L = L;
}
#define RC_RUNTIME_SERIALIZED_MEMREF_SIZE 16 /* 4x uint: address, flags, value, prior */
static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
{
rc_memref_t* memref = progress->runtime->memrefs;
uint32_t flags = 0;
rc_memref_t* memref;
uint32_t count = 0;
for (memref = progress->runtime->memrefs; memref; memref = memref->next)
++count;
if (count == 0)
return RC_OK;
if (progress->offset + 8 + count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_MEMREFS);
if (!progress->buffer) {
while (memref) {
progress->offset += 16;
memref = memref->next;
}
progress->offset += count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE;
}
else {
while (memref) {
uint32_t flags = 0;
for (memref = progress->runtime->memrefs; memref; memref = memref->next) {
flags = memref->value.size;
if (memref->value.changed)
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
@ -140,11 +154,10 @@ static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
rc_runtime_progress_write_uint(progress, flags);
rc_runtime_progress_write_uint(progress, memref->value.value);
rc_runtime_progress_write_uint(progress, memref->value.prior);
memref = memref->next;
}
}
assert_chunk_size(count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE);
rc_runtime_progress_end_chunk(progress);
return RC_OK;
}
@ -159,7 +172,7 @@ static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress)
/* re-read the chunk size to determine how many memrefs are present */
progress->offset -= 4;
entries = rc_runtime_progress_read_uint(progress) / 16;
entries = rc_runtime_progress_read_uint(progress) / RC_RUNTIME_SERIALIZED_MEMREF_SIZE;
while (entries != 0) {
address = rc_runtime_progress_read_uint(progress);
@ -210,6 +223,9 @@ static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc
rc_condition_t* cond;
uint32_t flags;
if (progress->offset + 4 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, condset->is_paused);
cond = condset->conditions;
@ -230,15 +246,24 @@ static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc
flags |= RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME;
}
if (progress->offset + 8 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, cond->current_hits);
rc_runtime_progress_write_uint(progress, flags);
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
if (progress->offset + 8 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.value);
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.prior);
}
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
if (progress->offset + 8 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.value);
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.prior);
}
@ -310,6 +335,9 @@ static int rc_runtime_progress_write_variable(rc_runtime_progress_t* progress, c
{
uint32_t flags;
if (progress->offset + 12 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
flags = rc_runtime_progress_should_serialize_variable_condset(variable->conditions);
if (variable->value.changed)
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
@ -331,21 +359,30 @@ static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress)
{
uint32_t count = 0;
const rc_value_t* variable;
int result;
for (variable = progress->runtime->variables; variable; variable = variable->next)
++count;
if (count == 0)
return RC_OK;
/* header + count + count(djb2,flags,value,prior,?cond) */
if (progress->offset + 8 + 4 + count * 16 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_VARIABLES);
rc_runtime_progress_write_uint(progress, count);
for (variable = progress->runtime->variables; variable; variable = variable->next)
{
for (variable = progress->runtime->variables; variable; variable = variable->next) {
uint32_t djb2 = rc_djb2(variable->name);
if (progress->offset + 16 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, djb2);
rc_runtime_progress_write_variable(progress, variable);
result = rc_runtime_progress_write_variable(progress, variable);
if (result != RC_OK)
return result;
}
rc_runtime_progress_end_chunk(progress);
@ -493,7 +530,7 @@ static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_
static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progress)
{
uint32_t i;
int offset = 0;
int initial_offset = 0;
int result;
for (i = 0; i < progress->runtime->trigger_count; ++i) {
@ -511,7 +548,10 @@ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progres
continue;
}
offset = progress->offset;
initial_offset = progress->offset;
} else {
if (progress->offset + runtime_trigger->serialized_size > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
}
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_ACHIEVEMENT);
@ -522,10 +562,15 @@ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progres
if (result != RC_OK)
return result;
if (runtime_trigger->serialized_size) {
/* runtime_trigger->serialized_size includes the header */
assert_chunk_size(runtime_trigger->serialized_size - 8);
}
rc_runtime_progress_end_chunk(progress);
if (!progress->buffer)
runtime_trigger->serialized_size = progress->offset - offset;
runtime_trigger->serialized_size = progress->offset - initial_offset;
}
return RC_OK;
@ -556,7 +601,7 @@ static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progres
{
uint32_t i;
uint32_t flags;
int offset = 0;
int initial_offset = 0;
int result;
for (i = 0; i < progress->runtime->lboard_count; ++i) {
@ -574,7 +619,10 @@ static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progres
continue;
}
offset = progress->offset;
initial_offset = progress->offset;
} else {
if (progress->offset + runtime_lboard->serialized_size > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
}
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_LEADERBOARD);
@ -600,10 +648,15 @@ static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progres
if (result != RC_OK)
return result;
if (runtime_lboard->serialized_size) {
/* runtime_lboard->serialized_size includes the header */
assert_chunk_size(runtime_lboard->serialized_size - 8);
}
rc_runtime_progress_end_chunk(progress);
if (!progress->buffer)
runtime_lboard->serialized_size = progress->offset - offset;
runtime_lboard->serialized_size = progress->offset - initial_offset;
}
return RC_OK;
@ -663,6 +716,9 @@ static int rc_runtime_progress_write_rich_presence(rc_runtime_progress_t* progre
if (!display->next)
return RC_OK;
if (progress->offset + 8 + 16 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_RICHPRESENCE);
rc_runtime_progress_write_md5(progress, progress->runtime->richpresence->md5);
@ -705,6 +761,9 @@ static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progres
uint8_t md5[16];
int result;
if (progress->buffer_size < RC_RUNTIME_MIN_BUFFER_SIZE)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, RC_RUNTIME_MARKER);
if ((result = rc_runtime_progress_write_memrefs(progress)) != RC_OK)
@ -722,6 +781,9 @@ static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progres
if ((result = rc_runtime_progress_write_rich_presence(progress)) != RC_OK)
return result;
if (progress->offset + 8 + 16 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, RC_RUNTIME_CHUNK_DONE);
rc_runtime_progress_write_uint(progress, 16);
@ -736,12 +798,13 @@ static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progres
return RC_OK;
}
int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
{
rc_runtime_progress_t progress;
int result;
rc_runtime_progress_init(&progress, runtime, L);
progress.buffer_size = 0xFFFFFFFF;
result = rc_runtime_progress_serialize_internal(&progress);
if (result != RC_OK)
@ -751,6 +814,11 @@ int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
}
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L)
{
return rc_runtime_serialize_progress_sized(buffer, 0xFFFFFFFF, runtime, L);
}
int rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, lua_State* L)
{
rc_runtime_progress_t progress;
@ -759,11 +827,17 @@ int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua
rc_runtime_progress_init(&progress, runtime, L);
progress.buffer = (uint8_t*)buffer;
progress.buffer_size = buffer_size;
return rc_runtime_progress_serialize_internal(&progress);
}
int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L)
{
return rc_runtime_deserialize_progress_sized(runtime, serialized, 0xFFFFFFFF, L);
}
int rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, lua_State* L)
{
rc_runtime_progress_t progress;
md5_state_t state;
@ -775,9 +849,9 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serial
int seen_rich_presence = 0;
int result = RC_OK;
if (!serialized) {
if (!serialized || serialized_size < RC_RUNTIME_MIN_BUFFER_SIZE) {
rc_runtime_reset(runtime);
return RC_INVALID_STATE;
return RC_INSUFFICIENT_BUFFER;
}
rc_runtime_progress_init(&progress, runtime, L);
@ -813,12 +887,21 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serial
}
do {
if (progress.offset + 8 >= serialized_size) {
result = RC_INSUFFICIENT_BUFFER;
break;
}
chunk_id = rc_runtime_progress_read_uint(&progress);
chunk_size = rc_runtime_progress_read_uint(&progress);
next_chunk_offset = progress.offset + chunk_size;
switch (chunk_id)
{
if (next_chunk_offset > serialized_size) {
result = RC_INSUFFICIENT_BUFFER;
break;
}
switch (chunk_id) {
case RC_RUNTIME_CHUNK_MEMREFS:
result = rc_runtime_progress_read_memrefs(&progress);
break;

View file

@ -683,9 +683,14 @@ static int rc_typed_value_compare_floats(float f1, float f2, char oper) {
}
int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper) {
rc_typed_value_t converted_value2;
if (value2->type != value1->type)
value2 = rc_typed_value_convert_into(&converted_value2, value2, value1->type);
rc_typed_value_t converted_value;
if (value2->type != value1->type) {
/* if either side is a float, convert both sides to float. otherwise, assume the signed-ness of the left side. */
if (value2->type == RC_VALUE_TYPE_FLOAT)
value1 = rc_typed_value_convert_into(&converted_value, value1, value2->type);
else
value2 = rc_typed_value_convert_into(&converted_value, value2, value1->type);
}
switch (value1->type) {
case RC_VALUE_TYPE_UNSIGNED:

View file

@ -528,7 +528,7 @@ static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector
verbose_message_callback(message);
}
if (size < (unsigned)num_read)
if (size < (unsigned)num_read) /* we read a whole sector - only hash the part containing file data */
num_read = (size_t)size;
do
@ -834,12 +834,18 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
uint32_t filename_len = RC_ZIP_READ_LE16(cdir + 0x1C);
int32_t extra_len = RC_ZIP_READ_LE16(cdir + 0x1E);
int32_t comment_len = RC_ZIP_READ_LE16(cdir + 0x20);
int32_t external_attr = RC_ZIP_READ_LE16(cdir + 0x26);
uint64_t local_hdr_ofs = RC_ZIP_READ_LE32(cdir + 0x2A);
cdir_entry_len = cdirhdr_size + filename_len + extra_len + comment_len;
if (signature != 0x02014b50) /* expected central directory entry signature */
break;
/* Ignore records describing a directory (we only hash file records) */
name = (cdir + cdirhdr_size);
if (name[filename_len - 1] == '/' || name[filename_len - 1] == '\\' || (external_attr & 0x10))
continue;
/* Handle Zip64 fields */
if (decomp_size == 0xFFFFFFFF || comp_size == 0xFFFFFFFF || local_hdr_ofs == 0xFFFFFFFF)
{
@ -893,7 +899,7 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
hashindex++;
/* Convert and store the file name in the hash data buffer */
for (name = (cdir + cdirhdr_size), name_end = name + filename_len; name != name_end; name++)
for (name_end = name + filename_len; name != name_end; name++)
{
*(hashdata++) =
(*name == '\\' ? '/' : /* convert back-slashes to regular slashes */
@ -1717,7 +1723,7 @@ static int rc_hash_nintendo_3ds_ncch(md5_state_t* md5, void* file_handle, uint8_
}
AES_init_ctx(&ncch_aes, primary_key);
AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], exefs_section_size);
AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], (size_t)exefs_section_size);
}
}
}

View file

@ -391,6 +391,7 @@ void gfx_widgets_set_challenge_display(unsigned id, const char* badge);
void gfx_widgets_clear_challenge_displays(void);
void gfx_widget_set_achievement_progress(const char* badge, const char* progress);
void gfx_widget_set_cheevos_disconnect(bool visible);
void gfx_widget_set_cheevos_set_loading(bool visible);
#endif
/* TODO/FIXME/WARNING: Not thread safe! */

View file

@ -66,6 +66,7 @@ struct gfx_widget_leaderboard_display_state
unsigned challenge_count;
uint16_t char_width[CHEEVO_LBOARD_LAST_FIXED_CHAR - CHEEVO_LBOARD_FIRST_FIXED_CHAR + 1];
uint16_t fixed_char_width;
uint16_t loading;
bool disconnected;
};
@ -115,6 +116,7 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata)
if (state->tracker_count == 0 &&
state->challenge_count == 0 &&
state->progress_tracker.show_until == 0 &&
!state->loading &&
!state->disconnected)
return;
@ -342,9 +344,10 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata)
}
}
if (state->disconnected)
if (state->disconnected || state->loading)
{
const char* disconnected_text = "! RA !";
char loading_buffer[8] = "RA ...";
const char* disconnected_text = state->disconnected ? "! RA !" : loading_buffer;
const unsigned disconnect_widget_width = font_driver_get_message_width(
state->dispwidget_ptr->gfx_widget_fonts.msg_queue.font,
disconnected_text, 0, 1) + CHEEVO_LBOARD_DISPLAY_PADDING * 2;
@ -353,6 +356,13 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata)
x = video_width - disconnect_widget_width - spacing;
y -= disconnect_widget_height + spacing;
if (state->loading) {
const uint16_t loading_shift = 5;
loading_buffer[((state->loading - 1) >> loading_shift) + 3] = '\0';
state->loading &= (1 << (loading_shift + 2)) - 1;
++state->loading;
}
/* Backdrop */
gfx_display_draw_quad(
p_disp,
@ -586,6 +596,12 @@ void gfx_widget_set_cheevos_disconnect(bool value)
state->disconnected = value;
}
void gfx_widget_set_cheevos_set_loading(bool value)
{
gfx_widget_leaderboard_display_state_t* state = &p_w_leaderboard_display_st;
state->loading = value ? 1 : 0;
}
const gfx_widget_t gfx_widget_leaderboard_display = {
&gfx_widget_leaderboard_display_init,

View file

@ -194,6 +194,7 @@ ACHIEVEMENTS
/* Gekko (Wii) and 3DS use custom pthread wrappers (see rthreads.c) */
#define RC_NO_THREADS 1
#endif
#define RC_CLIENT_SUPPORTS_HASH 1
#include "../libretro-common/formats/cdfs/cdfs.c"
#include "../network/net_http_special.c"

View file

@ -10152,6 +10152,18 @@ MSG_HASH(
MENU_ENUM_LABEL_CHEEVOS_SERVER_RECONNECTED,
"All pending requests have succesfully been synced to the RetroAchievements server."
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_CHEEVOS_IDENTIFYING_GAME,
"Identifying game"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_CHEEVOS_FETCHING_GAME_DATA,
"Fetching game data"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_CHEEVOS_STARTING_SESSION,
"Starting session"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN,
"Not logged in"

View file

@ -3062,6 +3062,9 @@ enum msg_hash_enums
MENU_LABEL(ACHIEVEMENT_SERVER_UNREACHABLE),
MENU_LABEL(CHEEVOS_SERVER_DISCONNECTED),
MENU_LABEL(CHEEVOS_SERVER_RECONNECTED),
MENU_LABEL(CHEEVOS_IDENTIFYING_GAME),
MENU_LABEL(CHEEVOS_FETCHING_GAME_DATA),
MENU_LABEL(CHEEVOS_STARTING_SESSION),
MENU_LABEL(CORE_INFORMATION),
MENU_LABEL(DISC_INFORMATION),
MENU_LABEL(CORE_LOCK),

View file

@ -6983,6 +6983,10 @@ int runloop_iterate(void)
netplay_driver_ctl(RARCH_NETPLAY_CTL_PAUSE, NULL);
#endif
#endif
#ifdef HAVE_CHEEVOS
if (cheevos_enable)
rcheevos_idle();
#endif
#ifdef HAVE_MENU
/* Rely on vsync throttling unless VRR is enabled and menu throttle is disabled. */
if (vrr_runloop_enable && !settings->bools.menu_throttle_framerate)
@ -6998,10 +7002,6 @@ int runloop_iterate(void)
: settings->floats.video_refresh_rate));
else
runloop_set_frame_limit(&video_st->av_info, settings->floats.fastforward_ratio);
#endif
#ifdef HAVE_CHEEVOS
if (cheevos_enable)
rcheevos_idle();
#endif
goto end;
case RUNLOOP_STATE_ITERATE: