mirror of
https://github.com/libretro/RetroArch
synced 2025-03-03 04:14:00 +00:00
(cheevos) update to rcheevos 11.5 (#16827)
* update rcheevos * revalidate config settings after game is loaded
This commit is contained in:
parent
13dbb31f62
commit
9f7361c1bd
@ -1731,7 +1731,7 @@ void rcheevos_validate_config_settings(void)
|
||||
const settings_t* settings = config_get_ptr();
|
||||
unsigned console_id;
|
||||
|
||||
if (!sysinfo->library_name || !rcheevos_hardcore_active())
|
||||
if (!rcheevos_hardcore_active())
|
||||
return;
|
||||
|
||||
/* this adds a sleep to every frame. if the value is high enough that a
|
||||
@ -1793,30 +1793,28 @@ void rcheevos_validate_config_settings(void)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!(disallowed_settings
|
||||
= rc_libretro_get_disallowed_settings(sysinfo->library_name)))
|
||||
if (!sysinfo->library_name)
|
||||
return;
|
||||
|
||||
if (!retroarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts))
|
||||
return;
|
||||
|
||||
for (i = 0; i < (int)coreopts->size; i++)
|
||||
disallowed_settings = rc_libretro_get_disallowed_settings(sysinfo->library_name);
|
||||
if (disallowed_settings && retroarch_ctl(RARCH_CTL_CORE_OPTIONS_LIST_GET, &coreopts))
|
||||
{
|
||||
const char* key = coreopts->opts[i].key;
|
||||
const char* val = core_option_manager_get_val(coreopts, i);
|
||||
if (!rc_libretro_is_setting_allowed(disallowed_settings, key, val))
|
||||
for (i = 0; i < (int)coreopts->size; i++)
|
||||
{
|
||||
char buffer[128];
|
||||
/* TODO/FIXME - localize */
|
||||
snprintf(buffer, sizeof(buffer), "Hardcore paused. Setting not allowed: %s=%s", key, val);
|
||||
CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", buffer);
|
||||
rcheevos_pause_hardcore();
|
||||
const char* key = coreopts->opts[i].key;
|
||||
const char* val = core_option_manager_get_val(coreopts, i);
|
||||
if (!rc_libretro_is_setting_allowed(disallowed_settings, key, val))
|
||||
{
|
||||
char buffer[128];
|
||||
/* TODO/FIXME - localize */
|
||||
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);
|
||||
|
||||
break;
|
||||
runloop_msg_queue_push(buffer, 0, 4 * 60, false, NULL,
|
||||
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2570,6 +2568,7 @@ static void rcheevos_client_load_game_callback(int result,
|
||||
{
|
||||
/* hardcore is active. we're going to start processing
|
||||
* achievements. make sure restrictions are enforced */
|
||||
rcheevos_validate_config_settings();
|
||||
rcheevos_enforce_hardcore_settings();
|
||||
}
|
||||
else
|
||||
|
32
deps/rcheevos/CHANGELOG.md
vendored
32
deps/rcheevos/CHANGELOG.md
vendored
@ -1,3 +1,35 @@
|
||||
# v11.5.0
|
||||
* add total_entries to rc_api_fetch_leaderboard_info_response_t
|
||||
* add RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED event
|
||||
* modify rc_client_begin_identify_and_load_game and rc_client_begin_change_media to use locally
|
||||
registered filereader/cdreader for hash resolution when using rc_client_raintegration
|
||||
* add support for ISO-8601 timestamps in JSON responses
|
||||
* update RC_CONSOLE_MS_DOS hash logic to support parent archives
|
||||
* fix infinite loop that sometimes occurs when resetting while progress tracker is onscreen
|
||||
|
||||
# v11.4.0
|
||||
* add RC_CONDITION_REMEMBER and RC_OPERAND_RECALL
|
||||
* add RC_OPERATOR_ADD and RC_OPERATOR_SUB
|
||||
* add scratch pad memory to PSX memory map
|
||||
* add Super Game Module memory to Colecovision memory map
|
||||
* add rapi function fetch_game_titles
|
||||
* modify progress functions to return RC_NO_GAME_LOADED when "Unknown Game" is loaded
|
||||
* update subsystem list for arcade hash
|
||||
* fix exception if server sends null as achievement.author
|
||||
|
||||
# v11.3.0
|
||||
* add RC_OPERATOR_MOD
|
||||
* add cartridge RAM to Game Gear and Master System memory maps
|
||||
* add extended cartridge RAM to Gameboy and Gameboy Color memory maps
|
||||
* add rc_client_is_game_loaded helper function
|
||||
* add rc_client_raintegration_set_console_id to specify console in case game resolution fails
|
||||
* add rc_client_raintegration_get_achievement_state to detect local unlocks
|
||||
* report validation errors on multi-condition logic
|
||||
* hash whole file for PSP homebrew files (eboot.pbp)
|
||||
* call DrawMenuBar in rc_client_raintegration_rebuild_submenu if menu changes
|
||||
* fix file sharing issue using default filereader on Windows
|
||||
* fix exception calling rc_client_get_game_summary with an unidentified game loaded
|
||||
|
||||
# v11.2.0
|
||||
* add alternate methods for state serialization/deserialization that accept a buffer_size parameter
|
||||
* add RC_CLIENT_SUPPORTS_HASH compile flag
|
||||
|
45
deps/rcheevos/include/rc_api_info.h
vendored
45
deps/rcheevos/include/rc_api_info.h
vendored
@ -129,6 +129,9 @@ typedef struct rc_api_fetch_leaderboard_info_response_t {
|
||||
/* The number of items in the entries array */
|
||||
uint32_t num_entries;
|
||||
|
||||
/* The total number of entries on the server */
|
||||
uint32_t total_entries;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
@ -180,6 +183,48 @@ RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_response(rc_api_fetch_gam
|
||||
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);
|
||||
|
||||
/* --- Fetch Game Titles --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch games list request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_game_titles_request_t {
|
||||
/* An array of game ids to fetch titles for */
|
||||
const uint32_t* game_ids;
|
||||
/* The number of items in the game_ids array */
|
||||
uint32_t num_game_ids;
|
||||
}
|
||||
rc_api_fetch_game_titles_request_t;
|
||||
|
||||
/* A game title entry */
|
||||
typedef struct rc_api_game_title_entry_t {
|
||||
/* The unique identifier of the game */
|
||||
uint32_t id;
|
||||
/* The title of the game */
|
||||
const char* title;
|
||||
/* The image name for the game badge */
|
||||
const char* image_name;
|
||||
}
|
||||
rc_api_game_title_entry_t;
|
||||
|
||||
/**
|
||||
* Response data for a fetch games title request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_game_titles_response_t {
|
||||
/* An array of requested entries */
|
||||
rc_api_game_title_entry_t* entries;
|
||||
/* The number of items in the entries array */
|
||||
uint32_t num_entries;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_game_titles_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_game_titles_request(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_API_INFO_H */
|
||||
|
1
deps/rcheevos/include/rc_client.h
vendored
1
deps/rcheevos/include/rc_client.h
vendored
@ -517,6 +517,7 @@ typedef struct rc_client_leaderboard_entry_t {
|
||||
typedef struct rc_client_leaderboard_entry_list_t {
|
||||
rc_client_leaderboard_entry_t* entries;
|
||||
uint32_t num_entries;
|
||||
uint32_t total_entries;
|
||||
int32_t user_index;
|
||||
} rc_client_leaderboard_entry_list_t;
|
||||
|
||||
|
@ -39,7 +39,8 @@ enum {
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE = 0,
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED = 1, /* [menu_item] checked changed */
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED = 2, /* hardcore was enabled or disabled */
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_PAUSE = 3 /* emulated system should be paused */
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_PAUSE = 3, /* emulated system should be paused */
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED = 4 /* one or more items were added/removed from the menu and it should be rebuilt */
|
||||
};
|
||||
|
||||
typedef struct rc_client_raintegration_event_t {
|
||||
|
4
deps/rcheevos/include/rc_error.h
vendored
4
deps/rcheevos/include/rc_error.h
vendored
@ -46,7 +46,9 @@ enum {
|
||||
RC_ACCESS_DENIED = -33,
|
||||
RC_INVALID_CREDENTIALS = -34,
|
||||
RC_EXPIRED_TOKEN = -35,
|
||||
RC_INSUFFICIENT_BUFFER = -36
|
||||
RC_INSUFFICIENT_BUFFER = -36,
|
||||
RC_INVALID_VARIABLE_NAME = -37,
|
||||
RC_UNKNOWN_VARIABLE_NAME = -38
|
||||
};
|
||||
|
||||
RC_EXPORT const char* RC_CCONV rc_error_str(int ret);
|
||||
|
10
deps/rcheevos/include/rc_runtime_types.h
vendored
10
deps/rcheevos/include/rc_runtime_types.h
vendored
@ -106,7 +106,8 @@ enum {
|
||||
RC_OPERAND_LUA, /* A Lua function that provides the value. */
|
||||
RC_OPERAND_PRIOR, /* The last differing value at this address. */
|
||||
RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM. */
|
||||
RC_OPERAND_INVERTED /* The twos-complement value of a live address in RAM. */
|
||||
RC_OPERAND_INVERTED, /* The twos-complement value of a live address in RAM. */
|
||||
RC_OPERAND_RECALL /* The value captured by the last RC_CONDITION_REMEMBER condition */
|
||||
};
|
||||
|
||||
typedef struct rc_operand_t {
|
||||
@ -154,6 +155,7 @@ enum {
|
||||
RC_CONDITION_ADD_SOURCE, /* everything from this point on affects the condition after it */
|
||||
RC_CONDITION_SUB_SOURCE,
|
||||
RC_CONDITION_ADD_ADDRESS,
|
||||
RC_CONDITION_REMEMBER,
|
||||
|
||||
/* logic flags (second switch) */
|
||||
RC_CONDITION_ADD_HITS,
|
||||
@ -176,7 +178,9 @@ enum {
|
||||
RC_OPERATOR_DIV,
|
||||
RC_OPERATOR_AND,
|
||||
RC_OPERATOR_XOR,
|
||||
RC_OPERATOR_MOD
|
||||
RC_OPERATOR_MOD,
|
||||
RC_OPERATOR_ADD,
|
||||
RC_OPERATOR_SUB
|
||||
};
|
||||
|
||||
typedef struct rc_condition_t rc_condition_t;
|
||||
@ -287,6 +291,8 @@ RC_EXPORT void RC_CCONV rc_reset_trigger(rc_trigger_t* self);
|
||||
| Values |
|
||||
\*****************************************************************************/
|
||||
|
||||
#define RC_VALUE_MAX_NAME_LENGTH 15
|
||||
|
||||
struct rc_value_t {
|
||||
/* The current value of the variable. */
|
||||
rc_memref_value_t value;
|
||||
|
28
deps/rcheevos/src/rapi/rc_api_common.c
vendored
28
deps/rcheevos/src/rapi/rc_api_common.c
vendored
@ -864,8 +864,11 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char*
|
||||
|
||||
if (*field->value_start == '\"') {
|
||||
memset(&tm, 0, sizeof(tm));
|
||||
if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d",
|
||||
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) {
|
||||
if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d", /* DB format "2013-10-20 22:12:21" */
|
||||
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6 ||
|
||||
/* NOTE: relies on sscanf stopping when it sees a non-digit after the seconds. could be 'Z', '.', '+', or '-' */
|
||||
sscanf_s(field->value_start + 1, "%d-%d-%dT%d:%d:%d", /* ISO format "2013-10-20T22:12:21.000000Z */
|
||||
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) {
|
||||
tm.tm_mon--; /* 0-based */
|
||||
tm.tm_year -= 1900; /* 1900 based */
|
||||
|
||||
@ -938,6 +941,27 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js
|
||||
return rc_json_missing_field(response, field);
|
||||
}
|
||||
|
||||
void rc_json_extract_filename(rc_json_field_t* field) {
|
||||
if (field->value_end) {
|
||||
const char* str = field->value_end;
|
||||
|
||||
/* remove the extension */
|
||||
while (str > field->value_start && str[-1] != '/') {
|
||||
--str;
|
||||
if (*str == '.') {
|
||||
field->value_end = str;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* find the path separator */
|
||||
while (str > field->value_start && str[-1] != '/')
|
||||
--str;
|
||||
|
||||
field->value_start = str;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- rc_api_request --- */
|
||||
|
||||
void rc_api_destroy_request(rc_api_request_t* request)
|
||||
|
2
deps/rcheevos/src/rapi/rc_api_common.h
vendored
2
deps/rcheevos/src/rapi/rc_api_common.h
vendored
@ -67,6 +67,8 @@ int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count,
|
||||
int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field);
|
||||
int rc_json_get_object_string_length(const char* json);
|
||||
|
||||
void rc_json_extract_filename(rc_json_field_t* field);
|
||||
|
||||
void rc_url_builder_append_encoded_str(rc_api_url_builder_t* builder, const char* str);
|
||||
void rc_url_builder_append_num_param(rc_api_url_builder_t* builder, const char* param, int32_t value);
|
||||
void rc_url_builder_append_unum_param(rc_api_url_builder_t* builder, const char* param, uint32_t value);
|
||||
|
95
deps/rcheevos/src/rapi/rc_api_info.c
vendored
95
deps/rcheevos/src/rapi/rc_api_info.c
vendored
@ -3,6 +3,8 @@
|
||||
|
||||
#include "rc_runtime_types.h"
|
||||
|
||||
#include "../rc_compat.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@ -187,7 +189,8 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa
|
||||
RC_JSON_NEW_FIELD("LBAuthor"),
|
||||
RC_JSON_NEW_FIELD("LBCreated"),
|
||||
RC_JSON_NEW_FIELD("LBUpdated"),
|
||||
RC_JSON_NEW_FIELD("Entries") /* array */
|
||||
RC_JSON_NEW_FIELD("Entries"), /* array */
|
||||
RC_JSON_NEW_FIELD("TotalEntries")
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("GameTitle"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
@ -233,6 +236,8 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_datetime(&response->updated, &response->response, &leaderboarddata_fields[9], "LBUpdated"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&response->total_entries, &response->response, &leaderboarddata_fields[11], "TotalEntries"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!leaderboarddata_fields[1].value_end)
|
||||
return RC_MISSING_VALUE;
|
||||
@ -371,3 +376,91 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp
|
||||
void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Fetch Game Titles --- */
|
||||
|
||||
int rc_api_init_fetch_game_titles_request(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params) {
|
||||
rc_api_url_builder_t builder;
|
||||
char num[16];
|
||||
uint32_t i;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
|
||||
if (api_params->num_game_ids == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
rc_url_builder_append_str_param(&builder, "r", "gameinfolist");
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_ids[0]);
|
||||
|
||||
for (i = 1; i < api_params->num_game_ids; i++) {
|
||||
int chars = snprintf(num, sizeof(num), "%u", api_params->game_ids[i]);
|
||||
rc_url_builder_append(&builder, ",", 1);
|
||||
rc_url_builder_append(&builder, num, chars);
|
||||
}
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_game_title_entry_t* entry;
|
||||
rc_json_iterator_t iterator;
|
||||
rc_json_field_t array_field;
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Response")
|
||||
};
|
||||
|
||||
rc_json_field_t entry_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("ImageIcon")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &fields[2], "Response"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_entries) {
|
||||
response->entries = (rc_api_game_title_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_title_entry_t));
|
||||
if (!response->entries)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
entry = response->entries;
|
||||
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&entry->id, &response->response, &entry_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&entry->title, &response->response, &entry_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
|
||||
rc_json_extract_filename(&entry_fields[2]);
|
||||
if (!rc_json_get_required_string(&entry->image_name, &response->response, &entry_fields[2], "ImageIcon"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
++entry;
|
||||
}
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
25
deps/rcheevos/src/rapi/rc_api_runtime.c
vendored
25
deps/rcheevos/src/rapi/rc_api_runtime.c
vendored
@ -111,7 +111,6 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
||||
rc_api_leaderboard_definition_t* leaderboard;
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
const char* str;
|
||||
const char* last_author = "";
|
||||
const char* last_author_field = "";
|
||||
size_t last_author_len = 0;
|
||||
@ -180,17 +179,7 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
|
||||
if (patchdata_fields[3].value_end) {
|
||||
str = patchdata_fields[3].value_end - 5;
|
||||
if (memcmp(str, ".png\"", 5) == 0) {
|
||||
patchdata_fields[3].value_end -= 5;
|
||||
|
||||
while (str > patchdata_fields[3].value_start && str[-1] != '/')
|
||||
--str;
|
||||
|
||||
patchdata_fields[3].value_start = str;
|
||||
}
|
||||
}
|
||||
rc_json_extract_filename(&patchdata_fields[3]);
|
||||
rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", "");
|
||||
|
||||
/* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards.
|
||||
@ -248,9 +237,15 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
||||
if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
last_author = achievement->author;
|
||||
last_author_field = achievement_fields[6].value_start;
|
||||
last_author_len = len;
|
||||
if (achievement->author == NULL) {
|
||||
/* ensure we don't pass NULL out to client */
|
||||
last_author = achievement->author = "";
|
||||
last_author_len = 0;
|
||||
} else {
|
||||
last_author = achievement->author;
|
||||
last_author_field = achievement_fields[6].value_start;
|
||||
last_author_len = len;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created"))
|
||||
|
443
deps/rcheevos/src/rc_client.c
vendored
443
deps/rcheevos/src/rc_client.c
vendored
@ -78,6 +78,7 @@ typedef struct rc_client_load_state_t
|
||||
#endif
|
||||
} rc_client_load_state_t;
|
||||
|
||||
static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state);
|
||||
static void rc_client_begin_fetch_game_data(rc_client_load_state_t* callback_data);
|
||||
static void rc_client_hide_progress_tracker(rc_client_t* client, rc_client_game_info_t* game);
|
||||
static void rc_client_load_error(rc_client_load_state_t* load_state, int result, const char* error_message);
|
||||
@ -884,7 +885,7 @@ void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_g
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!client->game)
|
||||
if (!rc_client_is_game_loaded(client))
|
||||
return;
|
||||
|
||||
rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
|
||||
@ -1984,12 +1985,101 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
|
||||
rc_api_destroy_fetch_game_data_response(&fetch_game_data_response);
|
||||
}
|
||||
|
||||
static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
static rc_client_game_info_t* rc_client_allocate_game(void)
|
||||
{
|
||||
rc_client_game_info_t* game = (rc_client_game_info_t*)calloc(1, sizeof(*game));
|
||||
if (!game)
|
||||
return NULL;
|
||||
|
||||
rc_buffer_init(&game->buffer);
|
||||
rc_runtime_init(&game->runtime);
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
static int rc_client_attach_load_state(rc_client_t* client, rc_client_load_state_t* load_state)
|
||||
{
|
||||
if (client->state.load == NULL) {
|
||||
rc_client_unload_game(client);
|
||||
client->state.load = load_state;
|
||||
|
||||
if (load_state->game == NULL) {
|
||||
load_state->game = rc_client_allocate_game();
|
||||
if (!load_state->game) {
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, load_state->callback_userdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (client->state.load != load_state) {
|
||||
/* previous load was aborted */
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
|
||||
static void rc_client_external_load_state_callback(int result, const char* error_message, rc_client_t* client, void* userdata)
|
||||
{
|
||||
rc_client_load_state_t* load_state = (rc_client_load_state_t*)userdata;
|
||||
int async_aborted;
|
||||
|
||||
client = load_state->client;
|
||||
async_aborted = rc_client_end_async(client, &load_state->async_handle);
|
||||
if (async_aborted) {
|
||||
if (async_aborted != RC_CLIENT_ASYNC_DESTROYED) {
|
||||
RC_CLIENT_LOG_VERBOSE(client, "Load aborted during external loading");
|
||||
}
|
||||
|
||||
rc_client_unload_game(client); /* unload the game from the external client */
|
||||
rc_client_free_load_state(load_state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != RC_OK) {
|
||||
rc_client_load_error(load_state, result, error_message);
|
||||
return;
|
||||
}
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
load_state->progress = (client->state.load == load_state) ?
|
||||
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_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);
|
||||
}
|
||||
else {
|
||||
/* keep partial game object for media_hash management */
|
||||
if (client->state.external_client && client->state.external_client->get_game_info) {
|
||||
const rc_client_game_t* info = client->state.external_client->get_game_info();
|
||||
load_state->game->public_.console_id = info->console_id;
|
||||
client->game = load_state->game;
|
||||
load_state->game = NULL;
|
||||
}
|
||||
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_OK, NULL, client, load_state->callback_userdata);
|
||||
}
|
||||
|
||||
rc_client_free_load_state(load_state);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
|
||||
{
|
||||
rc_api_fetch_game_data_request_t fetch_game_data_request;
|
||||
rc_client_t* client = load_state->client;
|
||||
rc_api_request_t request;
|
||||
int result;
|
||||
|
||||
if (load_state->hash->game_id == 0) {
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
@ -2058,20 +2148,35 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
#endif /* RC_CLIENT_SUPPORTS_HASH */
|
||||
|
||||
if (load_state->hash->game_id == 0) {
|
||||
rc_client_subset_info_t* subset;
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->load_unknown_game) {
|
||||
client->state.external_client->load_unknown_game(load_state->game->public_.hash);
|
||||
rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game");
|
||||
return;
|
||||
}
|
||||
/* no external method specifically for unknown game, just pass the hash through to begin_load_game below */
|
||||
}
|
||||
else {
|
||||
#endif
|
||||
/* mimics rc_client_load_unknown_game without allocating a new game object */
|
||||
rc_client_subset_info_t* subset;
|
||||
|
||||
subset = (rc_client_subset_info_t*)rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_subset_info_t));
|
||||
memset(subset, 0, sizeof(*subset));
|
||||
subset->public_.title = "";
|
||||
subset = (rc_client_subset_info_t*)rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_subset_info_t));
|
||||
memset(subset, 0, sizeof(*subset));
|
||||
subset->public_.title = "";
|
||||
|
||||
load_state->game->public_.title = "Unknown Game";
|
||||
load_state->game->public_.badge_name = "";
|
||||
load_state->game->subsets = subset;
|
||||
client->game = load_state->game;
|
||||
load_state->game = NULL;
|
||||
load_state->game->public_.title = "Unknown Game";
|
||||
load_state->game->public_.badge_name = "";
|
||||
load_state->game->subsets = subset;
|
||||
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;
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -2083,6 +2188,60 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
/* done with the hashing code, release the global pointer */
|
||||
g_hash_client = NULL;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->add_game_hash)
|
||||
client->state.external_client->add_game_hash(load_state->hash->hash, load_state->hash->game_id);
|
||||
|
||||
if (client->state.external_client->begin_load_game) {
|
||||
rc_client_begin_async(client, &load_state->async_handle);
|
||||
client->state.external_client->begin_load_game(client, load_state->hash->hash, rc_client_external_load_state_callback, load_state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
}
|
||||
|
||||
void rc_client_load_unknown_game(rc_client_t* client, const char* tried_hashes)
|
||||
{
|
||||
rc_client_subset_info_t* subset;
|
||||
rc_client_game_info_t* game;
|
||||
|
||||
game = rc_client_allocate_game();
|
||||
if (!game)
|
||||
return;
|
||||
|
||||
subset = (rc_client_subset_info_t*)rc_buffer_alloc(&game->buffer, sizeof(rc_client_subset_info_t));
|
||||
memset(subset, 0, sizeof(*subset));
|
||||
subset->public_.title = "";
|
||||
game->subsets = subset;
|
||||
|
||||
game->public_.title = "Unknown Game";
|
||||
game->public_.badge_name = "";
|
||||
game->public_.console_id = RC_CONSOLE_UNKNOWN;
|
||||
|
||||
if (strlen(tried_hashes) == 32) { /* only one hash tried, add it to the list */
|
||||
rc_client_game_hash_t* game_hash = rc_client_find_game_hash(client, tried_hashes);
|
||||
game_hash->game_id = 0;
|
||||
game->public_.hash = game_hash->hash;
|
||||
}
|
||||
else {
|
||||
game->public_.hash = rc_buffer_strcpy(&game->buffer, tried_hashes);
|
||||
}
|
||||
|
||||
rc_client_unload_game(client);
|
||||
client->game = game;
|
||||
}
|
||||
|
||||
static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
{
|
||||
rc_api_fetch_game_data_request_t fetch_game_data_request;
|
||||
rc_client_t* client = load_state->client;
|
||||
rc_api_request_t request;
|
||||
int result;
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
result = client->state.user;
|
||||
if (result == RC_CLIENT_USER_STATE_LOGIN_REQUESTED)
|
||||
@ -2160,7 +2319,7 @@ static void rc_client_identify_game_callback(const rc_api_server_response_t* ser
|
||||
/* previous load state was aborted, load_state was free'd */
|
||||
}
|
||||
else {
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2193,35 +2352,25 @@ rc_client_game_hash_t* rc_client_find_game_hash(rc_client_t* client, const char*
|
||||
return game_hash;
|
||||
}
|
||||
|
||||
void rc_client_add_game_hash(rc_client_t* client, const char* hash, uint32_t game_id)
|
||||
{
|
||||
/* store locally, even if passing to external client */
|
||||
rc_client_game_hash_t* game_hash = rc_client_find_game_hash(client, hash);
|
||||
game_hash->game_id = game_id;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->add_game_hash)
|
||||
client->state.external_client->add_game_hash(hash, game_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* load_state,
|
||||
const char* hash, const char* file_path)
|
||||
{
|
||||
rc_client_t* client = load_state->client;
|
||||
rc_client_game_hash_t* old_hash;
|
||||
|
||||
if (client->state.load == NULL) {
|
||||
rc_client_unload_game(client);
|
||||
client->state.load = load_state;
|
||||
|
||||
if (load_state->game == NULL) {
|
||||
load_state->game = (rc_client_game_info_t*)calloc(1, sizeof(*load_state->game));
|
||||
if (!load_state->game) {
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, load_state->callback_userdata);
|
||||
|
||||
rc_client_free_load_state(load_state);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc_buffer_init(&load_state->game->buffer);
|
||||
rc_runtime_init(&load_state->game->runtime);
|
||||
}
|
||||
}
|
||||
else if (client->state.load != load_state) {
|
||||
/* previous load was aborted */
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata);
|
||||
|
||||
if (!rc_client_attach_load_state(client, load_state)) {
|
||||
rc_client_free_load_state(load_state);
|
||||
return NULL;
|
||||
}
|
||||
@ -2265,7 +2414,7 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
|
||||
else {
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "Identified game: %u (%s)", load_state->hash->game_id, load_state->hash->hash);
|
||||
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
}
|
||||
|
||||
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
|
||||
@ -2327,8 +2476,12 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->begin_identify_and_load_game)
|
||||
return client->state.external_client->begin_identify_and_load_game(client, console_id, file_path, data, data_size, callback, callback_userdata);
|
||||
/* if a add_game_hash handler exists, do the identification locally, then pass the
|
||||
* resulting game_id/hash to the external client */
|
||||
if (client->state.external_client && !client->state.external_client->add_game_hash) {
|
||||
if (client->state.external_client->begin_identify_and_load_game)
|
||||
return client->state.external_client->begin_identify_and_load_game(client, console_id, file_path, data, data_size, callback, callback_userdata);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (data) {
|
||||
@ -2471,6 +2624,13 @@ void rc_client_unload_game(rc_client_t* client)
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->unload_game) {
|
||||
client->state.external_client->unload_game();
|
||||
|
||||
/* a game object may have been allocated to manage hashes */
|
||||
game = client->game;
|
||||
client->game = NULL;
|
||||
if (game != NULL)
|
||||
rc_client_free_game(game);
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@ -2629,15 +2789,77 @@ static rc_client_async_handle_t* rc_client_begin_change_media_internal(rc_client
|
||||
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
|
||||
}
|
||||
|
||||
static rc_client_game_info_t* rc_client_check_pending_media(rc_client_t* client, const rc_client_pending_media_t* media)
|
||||
{
|
||||
rc_client_game_info_t* game;
|
||||
rc_client_pending_media_t* pending_media = NULL;
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
if (client->state.load) {
|
||||
game = client->state.load->game;
|
||||
if (!game || 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*)malloc(sizeof(*pending_media));
|
||||
if (!pending_media) {
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
media->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, media->callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(pending_media, media, sizeof(*pending_media));
|
||||
if (media->hash)
|
||||
pending_media->hash = strdup(media->hash);
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
if (media->file_path)
|
||||
pending_media->file_path = strdup(media->file_path);
|
||||
|
||||
if (media->data && media->data_size) {
|
||||
pending_media->data = (uint8_t*)malloc(media->data_size);
|
||||
if (!pending_media->data) {
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
media->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, media->callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(pending_media->data, media->data, media->data_size);
|
||||
} else {
|
||||
pending_media->data = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
client->state.load->pending_media = pending_media;
|
||||
}
|
||||
}
|
||||
else {
|
||||
game = client->game;
|
||||
}
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
if (!game) {
|
||||
media->callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, media->callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* still waiting for game data - don't call callback - it's queued */
|
||||
if (pending_media)
|
||||
return NULL;
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
#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)
|
||||
{
|
||||
rc_client_pending_media_t media;
|
||||
rc_client_game_hash_t* game_hash = NULL;
|
||||
rc_client_media_hash_t* media_hash;
|
||||
rc_client_game_info_t* game;
|
||||
rc_client_pending_media_t* pending_media = NULL;
|
||||
rc_client_media_hash_t* media_hash;
|
||||
uint32_t path_djb2;
|
||||
|
||||
if (!client) {
|
||||
@ -2651,55 +2873,21 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->begin_change_media)
|
||||
return client->state.external_client->begin_change_media(client, file_path, data, data_size, callback, callback_userdata);
|
||||
if (client->state.external_client && !client->state.external_client->begin_change_media_from_hash) {
|
||||
if (client->state.external_client->begin_change_media)
|
||||
return client->state.external_client->begin_change_media(client, file_path, data, data_size, 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);
|
||||
memset(&media, 0, sizeof(media));
|
||||
media.file_path = file_path;
|
||||
media.data = (uint8_t*)data;
|
||||
media.data_size = data_size;
|
||||
media.callback = callback;
|
||||
media.callback_userdata = callback_userdata;
|
||||
|
||||
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->file_path = strdup(file_path);
|
||||
pending_media->callback = callback;
|
||||
pending_media->callback_userdata = callback_userdata;
|
||||
if (data && data_size) {
|
||||
pending_media->data_size = data_size;
|
||||
pending_media->data = (uint8_t*)malloc(data_size);
|
||||
if (!pending_media->data) {
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(pending_media->data, data, data_size);
|
||||
}
|
||||
|
||||
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)
|
||||
game = rc_client_check_pending_media(client, &media);
|
||||
if (game == NULL)
|
||||
return NULL;
|
||||
|
||||
/* check to see if we've already hashed this file */
|
||||
@ -2749,11 +2937,25 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
if (!result) {
|
||||
#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, game_hash->hash, callback, callback_userdata);
|
||||
#endif
|
||||
|
||||
rc_client_change_media_internal(client, game_hash, callback, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->add_game_hash)
|
||||
client->state.external_client->add_game_hash(game_hash->hash, game_hash->game_id);
|
||||
if (client->state.external_client->begin_change_media_from_hash)
|
||||
return client->state.external_client->begin_change_media_from_hash(client, game_hash->hash, callback, callback_userdata);
|
||||
}
|
||||
#endif
|
||||
|
||||
return rc_client_begin_change_media_internal(client, game, game_hash, callback, callback_userdata);
|
||||
}
|
||||
|
||||
@ -2762,9 +2964,9 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
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_pending_media_t media;
|
||||
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);
|
||||
@ -2782,40 +2984,13 @@ rc_client_async_handle_t* rc_client_begin_change_media_from_hash(rc_client_t* cl
|
||||
}
|
||||
#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);
|
||||
memset(&media, 0, sizeof(media));
|
||||
media.hash = hash;
|
||||
media.callback = callback;
|
||||
media.callback_userdata = callback_userdata;
|
||||
|
||||
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)
|
||||
game = rc_client_check_pending_media(client, &media);
|
||||
if (game == NULL)
|
||||
return NULL;
|
||||
|
||||
/* check to see if we've already hashed this file. */
|
||||
@ -2861,7 +3036,7 @@ rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint3
|
||||
return client->state.external_client->begin_load_subset(client, subset_id, callback, callback_userdata);
|
||||
#endif
|
||||
|
||||
if (!client->game) {
|
||||
if (!rc_client_is_game_loaded(client)) {
|
||||
callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
@ -2882,7 +3057,7 @@ rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint3
|
||||
load_state->hash->game_id = subset_id;
|
||||
client->state.load = load_state;
|
||||
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
|
||||
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
|
||||
}
|
||||
@ -3570,9 +3745,11 @@ static void rc_client_award_achievement(rc_client_t* client, rc_client_achieveme
|
||||
callback_data->client = client;
|
||||
callback_data->id = achievement->public_.id;
|
||||
callback_data->hardcore = client->state.hardcore;
|
||||
callback_data->game_hash = client->game->public_.hash;
|
||||
callback_data->unlock_time = achievement->public_.unlock_time;
|
||||
|
||||
if (client->game) /* may be NULL if this gets called while unloading the game (from another thread - events are raised outside the lock) */
|
||||
callback_data->game_hash = client->game->public_.hash;
|
||||
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "Awarding achievement %u: %s", achievement->public_.id, achievement->public_.title);
|
||||
rc_client_award_achievement_server_call(callback_data);
|
||||
}
|
||||
@ -4311,6 +4488,7 @@ static void rc_client_fetch_leaderboard_entries_callback(const rc_api_server_res
|
||||
}
|
||||
|
||||
list->num_entries = lbinfo_response.num_entries;
|
||||
list->total_entries = lbinfo_response.total_entries;
|
||||
|
||||
lbinfo_callback_data->callback(RC_OK, NULL, list, client, lbinfo_callback_data->callback_userdata);
|
||||
}
|
||||
@ -4797,6 +4975,8 @@ static void rc_client_progress_tracker_timer_elapsed(rc_client_scheduled_callbac
|
||||
|
||||
static void rc_client_do_frame_update_progress_tracker(rc_client_t* client, rc_client_game_info_t* game)
|
||||
{
|
||||
/* ASSERT: this should only be called if the mutex is held */
|
||||
|
||||
if (!game->progress_tracker.hide_callback) {
|
||||
game->progress_tracker.hide_callback = (rc_client_scheduled_callback_data_t*)
|
||||
rc_buffer_alloc(&game->buffer, sizeof(rc_client_scheduled_callback_data_t));
|
||||
@ -5181,6 +5361,7 @@ void rc_client_idle(rc_client_t* client)
|
||||
else {
|
||||
/* remove the callback from the queue while we process it. callback can requeue if desired */
|
||||
client->state.scheduled_callbacks = scheduled_callback->next;
|
||||
scheduled_callback->next = NULL;
|
||||
}
|
||||
}
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
@ -5256,7 +5437,7 @@ static void rc_client_reschedule_callback(rc_client_t* client,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!next || when < next->when) {
|
||||
if (!next || (when < next->when && when != 0)) {
|
||||
/* insert here */
|
||||
callback->next = next;
|
||||
*last = callback;
|
||||
@ -5411,7 +5592,7 @@ int rc_client_serialize_progress_sized(rc_client_t* client, uint8_t* buffer, siz
|
||||
return client->state.external_client->serialize_progress(buffer, buffer_size);
|
||||
#endif
|
||||
|
||||
if (!client->game)
|
||||
if (!rc_client_is_game_loaded(client))
|
||||
return RC_NO_GAME_LOADED;
|
||||
|
||||
if (!buffer)
|
||||
@ -5535,7 +5716,7 @@ int rc_client_deserialize_progress_sized(rc_client_t* client, const uint8_t* ser
|
||||
return client->state.external_client->deserialize_progress(serialized, serialized_size);
|
||||
#endif
|
||||
|
||||
if (!client->game)
|
||||
if (!rc_client_is_game_loaded(client))
|
||||
return RC_NO_GAME_LOADED;
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
|
10
deps/rcheevos/src/rc_client_external.h
vendored
10
deps/rcheevos/src/rc_client_external.h
vendored
@ -38,6 +38,7 @@ typedef const rc_client_subset_t* (RC_CCONV *rc_client_external_get_subset_info_
|
||||
typedef void (RC_CCONV *rc_client_external_get_user_game_summary_func_t)(rc_client_user_game_summary_t* summary);
|
||||
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_change_media_func_t)(rc_client_t* client, const char* file_path,
|
||||
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
|
||||
typedef void (RC_CCONV* rc_client_external_add_game_hash_func_t)(const char* hash, uint32_t game_id);
|
||||
|
||||
/* NOTE: rc_client_external_create_achievement_list_func_t returns an internal wrapper structure which contains the public list
|
||||
* and a destructor function. */
|
||||
@ -124,9 +125,16 @@ typedef struct rc_client_external_t
|
||||
rc_client_external_serialize_progress_func_t serialize_progress;
|
||||
rc_client_external_deserialize_progress_func_t deserialize_progress;
|
||||
|
||||
/* VERSION 2 */
|
||||
rc_client_external_add_game_hash_func_t add_game_hash;
|
||||
rc_client_external_set_string_func_t load_unknown_game;
|
||||
|
||||
} rc_client_external_t;
|
||||
|
||||
#define RC_CLIENT_EXTERNAL_VERSION 1
|
||||
#define RC_CLIENT_EXTERNAL_VERSION 2
|
||||
|
||||
void rc_client_add_game_hash(rc_client_t* client, const char* hash, uint32_t game_id);
|
||||
void rc_client_load_unknown_game(rc_client_t* client, const char* hash);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
|
3
deps/rcheevos/src/rc_libretro.c
vendored
3
deps/rcheevos/src/rc_libretro.c
vendored
@ -69,6 +69,7 @@ static const rc_disallowed_setting_t _rc_disallowed_ecwolf_settings[] = {
|
||||
static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = {
|
||||
{ "fbneo-allow-patched-romsets", "enabled" },
|
||||
{ "fbneo-cheat-*", "!,Disabled,0 - Disabled" },
|
||||
{ "fbneo-cpu-speed-adjust", "??%" }, /* disallow speeds under 100% */
|
||||
{ "fbneo-dipswitch-*", "Universe BIOS*" },
|
||||
{ "fbneo-neogeo-mode", "UNIBIOS" },
|
||||
{ NULL, NULL }
|
||||
@ -178,7 +179,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
|
||||
static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char* value) {
|
||||
char c1, c2;
|
||||
while ((c1 = *test++)) {
|
||||
if (tolower(c1) != tolower(c2 = *value++))
|
||||
if (tolower(c1) != tolower(c2 = *value++) && c2 != '?')
|
||||
return (c2 == '*');
|
||||
}
|
||||
|
||||
|
2
deps/rcheevos/src/rc_util.c
vendored
2
deps/rcheevos/src/rc_util.c
vendored
@ -184,6 +184,8 @@ const char* rc_error_str(int ret)
|
||||
case RC_INVALID_CREDENTIALS: return "Invalid credentials";
|
||||
case RC_EXPIRED_TOKEN: return "Expired token";
|
||||
case RC_INSUFFICIENT_BUFFER: return "Buffer not large enough";
|
||||
case RC_INVALID_VARIABLE_NAME: return "Invalid variable name";
|
||||
case RC_UNKNOWN_VARIABLE_NAME: return "Unknown variable name";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
2
deps/rcheevos/src/rc_version.h
vendored
2
deps/rcheevos/src/rc_version.h
vendored
@ -8,7 +8,7 @@
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
#define RCHEEVOS_VERSION_MAJOR 11
|
||||
#define RCHEEVOS_VERSION_MINOR 2
|
||||
#define RCHEEVOS_VERSION_MINOR 5
|
||||
#define RCHEEVOS_VERSION_PATCH 0
|
||||
|
||||
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)
|
||||
|
23
deps/rcheevos/src/rcheevos/condition.c
vendored
23
deps/rcheevos/src/rcheevos/condition.c
vendored
@ -143,6 +143,14 @@ static int rc_parse_operator(const char** memaddr) {
|
||||
++(*memaddr);
|
||||
return RC_OPERATOR_MOD;
|
||||
|
||||
case '+':
|
||||
++(*memaddr);
|
||||
return RC_OPERATOR_ADD;
|
||||
|
||||
case '-':
|
||||
++(*memaddr);
|
||||
return RC_OPERATOR_SUB;
|
||||
|
||||
case '\0':/* end of string */
|
||||
case '_': /* next condition */
|
||||
case 'S': /* next condset */
|
||||
@ -180,12 +188,13 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
|
||||
case 'q': case 'Q': self->type = RC_CONDITION_MEASURED_IF; break;
|
||||
case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; can_modify = 1; break;
|
||||
case 't': case 'T': self->type = RC_CONDITION_TRIGGER; break;
|
||||
case 'k': case 'K': self->type = RC_CONDITION_REMEMBER; can_modify = 1; break;
|
||||
case 'z': case 'Z': self->type = RC_CONDITION_RESET_NEXT_IF; break;
|
||||
case 'g': case 'G':
|
||||
parse->measured_as_percent = 1;
|
||||
self->type = RC_CONDITION_MEASURED;
|
||||
break;
|
||||
/* e f h j k l s u v w x y */
|
||||
/* e f h j l s u v w x y */
|
||||
default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0;
|
||||
}
|
||||
|
||||
@ -231,6 +240,8 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
|
||||
case RC_OPERATOR_AND:
|
||||
case RC_OPERATOR_XOR:
|
||||
case RC_OPERATOR_MOD:
|
||||
case RC_OPERATOR_ADD:
|
||||
case RC_OPERATOR_SUB:
|
||||
/* modifying operators are only valid on modifying statements */
|
||||
if (can_modify)
|
||||
break;
|
||||
@ -243,6 +254,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
|
||||
case RC_CONDITION_ADD_SOURCE:
|
||||
case RC_CONDITION_SUB_SOURCE:
|
||||
case RC_CONDITION_ADD_ADDRESS:
|
||||
case RC_CONDITION_REMEMBER:
|
||||
/* prevent parse errors on legacy achievements where a condition was present before changing the type */
|
||||
self->oper = RC_OPERATOR_NONE;
|
||||
break;
|
||||
@ -560,5 +572,14 @@ void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self,
|
||||
case RC_OPERATOR_MOD:
|
||||
rc_typed_value_modulus(value, &amount);
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_ADD:
|
||||
rc_typed_value_add(value, &amount);
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_SUB:
|
||||
rc_typed_value_negate(&amount);
|
||||
rc_typed_value_add(value, &amount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
16
deps/rcheevos/src/rcheevos/condset.c
vendored
16
deps/rcheevos/src/rcheevos/condset.c
vendored
@ -53,6 +53,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
|
||||
case RC_CONDITION_ADD_ADDRESS:
|
||||
case RC_CONDITION_ADD_SOURCE:
|
||||
case RC_CONDITION_SUB_SOURCE:
|
||||
case RC_CONDITION_REMEMBER:
|
||||
/* these conditions don't require a right hand size (implied *1) */
|
||||
break;
|
||||
|
||||
@ -88,6 +89,8 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
|
||||
case RC_OPERATOR_DIV:
|
||||
case RC_OPERATOR_MULT:
|
||||
case RC_OPERATOR_MOD:
|
||||
case RC_OPERATOR_ADD:
|
||||
case RC_OPERATOR_SUB:
|
||||
case RC_OPERATOR_NONE:
|
||||
/* measuring value. leave required_hits at 0 */
|
||||
break;
|
||||
@ -222,6 +225,15 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc
|
||||
eval_state->add_address = value.value.u32;
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_REMEMBER:
|
||||
rc_evaluate_condition_value(&value, condition, eval_state);
|
||||
rc_typed_value_add(&value, &eval_state->add_value);
|
||||
eval_state->recall_value.type = value.type;
|
||||
eval_state->recall_value.value = value.value;
|
||||
eval_state->add_value.type = RC_VALUE_TYPE_NONE;
|
||||
eval_state->add_address = 0;
|
||||
continue;
|
||||
|
||||
case RC_CONDITION_MEASURED:
|
||||
if (condition->required_hits == 0 && can_measure) {
|
||||
/* Measured condition without a hit target measures the value of the left operand */
|
||||
@ -417,6 +429,10 @@ int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* initialize recall value so each condition set has a functionally new recall accumulator */
|
||||
eval_state->recall_value.type = RC_VALUE_TYPE_UNSIGNED;
|
||||
eval_state->recall_value.value.u32 = 0;
|
||||
|
||||
if (self->has_pause) {
|
||||
/* one or more Pause conditions exists, if any of them are true, stop processing this group */
|
||||
self->is_paused = (char)rc_test_condset_internal(self, 1, eval_state);
|
||||
|
16
deps/rcheevos/src/rcheevos/consoleinfo.c
vendored
16
deps/rcheevos/src/rcheevos/consoleinfo.c
vendored
@ -368,9 +368,14 @@ static const rc_memory_regions_t rc_memory_regions_atari_lynx = { _rc_memory_reg
|
||||
|
||||
/* ===== ColecoVision ===== */
|
||||
static const rc_memory_region_t _rc_memory_regions_colecovision[] = {
|
||||
{ 0x000000U, 0x0003FFU, 0x006000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
/* "System RAM" refers to the main RAM at 0x6000-0x63FF. However, this RAM might not always be visible.
|
||||
* If the Super Game Module (SGM) is active, then it might overlay its own RAM at 0x0000-0x1FFF and 0x2000-0x7FFF.
|
||||
* These positions overlap the BIOS and System RAM, therefore we use virtual addresses for these memory spaces. */
|
||||
{ 0x000000U, 0x0003FFU, 0x006000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
|
||||
{ 0x000400U, 0x0023FFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "SGM Low RAM" }, /* Normally situated at 0x0000-0x1FFF, which overlaps the BIOS */
|
||||
{ 0x002400U, 0x0083FFU, 0x012000U, RC_MEMORY_TYPE_SYSTEM_RAM, "SGM High RAM" } /* Normally situated at 0x2000-0x7FFF, which overlaps System RAM */
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_regions_colecovision, 1 };
|
||||
static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_regions_colecovision, 3 };
|
||||
|
||||
/* ===== Commodore 64 ===== */
|
||||
/* https://www.c64-wiki.com/wiki/Memory_Map */
|
||||
@ -770,10 +775,11 @@ static const rc_memory_regions_t rc_memory_regions_pcfx = { _rc_memory_regions_p
|
||||
/* ===== PlayStation ===== */
|
||||
/* http://www.raphnet.net/electronique/psx_adaptor/Playstation.txt */
|
||||
static const rc_memory_region_t _rc_memory_regions_playstation[] = {
|
||||
{ 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" },
|
||||
{ 0x010000U, 0x1FFFFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
{ 0x000000U, 0x00FFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" },
|
||||
{ 0x010000U, 0x1FFFFFU, 0x00010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
|
||||
{ 0x200000U, 0x2003FFU, 0x1F800000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Scratchpad RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_regions_playstation, 2 };
|
||||
static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_regions_playstation, 3 };
|
||||
|
||||
/* ===== PlayStation 2 ===== */
|
||||
/* https://psi-rockin.github.io/ps2tek/ */
|
||||
|
55
deps/rcheevos/src/rcheevos/operand.c
vendored
55
deps/rcheevos/src/rcheevos/operand.c
vendored
@ -3,6 +3,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
|
||||
@ -64,6 +65,37 @@ static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_par
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr) {
|
||||
const char* aux = *memaddr;
|
||||
size_t i;
|
||||
char varName[RC_VALUE_MAX_NAME_LENGTH + 1] = { 0 };
|
||||
|
||||
for (i = 0; i < RC_VALUE_MAX_NAME_LENGTH && *aux != '}'; i++) {
|
||||
if (!rc_is_valid_variable_character(*aux, i == 0))
|
||||
return RC_INVALID_VARIABLE_NAME;
|
||||
|
||||
varName[i] = *aux++;
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
return RC_INVALID_VARIABLE_NAME;
|
||||
|
||||
if (*aux != '}')
|
||||
return RC_INVALID_VARIABLE_NAME;
|
||||
|
||||
++aux;
|
||||
|
||||
if (strcmp(varName, "recall") == 0) {
|
||||
self->type = RC_OPERAND_RECALL;
|
||||
}
|
||||
else { /* process named variable when feature is available.*/
|
||||
return RC_UNKNOWN_VARIABLE_NAME;
|
||||
}
|
||||
|
||||
*memaddr = aux;
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect) {
|
||||
const char* aux = *memaddr;
|
||||
uint32_t address;
|
||||
@ -231,6 +263,13 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
|
||||
self->value.num = (unsigned)value;
|
||||
}
|
||||
break;
|
||||
case '{': /* variable */
|
||||
++aux;
|
||||
ret = rc_parse_operand_variable(self, &aux);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
|
||||
case '0':
|
||||
if (aux[1] == 'x' || aux[1] == 'X') { /* hex integer constant */
|
||||
@ -313,6 +352,7 @@ int rc_operand_is_memref(const rc_operand_t* self) {
|
||||
case RC_OPERAND_CONST:
|
||||
case RC_OPERAND_FP:
|
||||
case RC_OPERAND_LUA:
|
||||
case RC_OPERAND_RECALL:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
@ -320,6 +360,16 @@ int rc_operand_is_memref(const rc_operand_t* self) {
|
||||
}
|
||||
}
|
||||
|
||||
int rc_operand_is_recall(const rc_operand_t* self) {
|
||||
switch (self->type) {
|
||||
case RC_OPERAND_RECALL:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int rc_operand_is_float(const rc_operand_t* self) {
|
||||
if (self->type == RC_OPERAND_FP)
|
||||
return 1;
|
||||
@ -462,6 +512,11 @@ void rc_evaluate_operand(rc_typed_value_t* result, rc_operand_t* self, rc_eval_s
|
||||
|
||||
break;
|
||||
|
||||
case RC_OPERAND_RECALL:
|
||||
result->type = eval_state->recall_value.type;
|
||||
result->value = eval_state->recall_value.value;
|
||||
return;
|
||||
|
||||
default:
|
||||
result->type = RC_VALUE_TYPE_UNSIGNED;
|
||||
result->value.u32 = rc_get_memref_value(self->value.memref, self->type, eval_state);
|
||||
|
15
deps/rcheevos/src/rcheevos/rc_internal.h
vendored
15
deps/rcheevos/src/rcheevos/rc_internal.h
vendored
@ -89,12 +89,13 @@ typedef struct {
|
||||
void* peek_userdata;
|
||||
lua_State* L;
|
||||
|
||||
rc_typed_value_t measured_value; /* Measured */
|
||||
uint8_t was_reset; /* ResetIf triggered */
|
||||
uint8_t has_hits; /* one of more hit counts is non-zero */
|
||||
uint8_t primed; /* true if all non-Trigger conditions are true */
|
||||
uint8_t measured_from_hits; /* true if the measured_value came from a condition's hit count */
|
||||
uint8_t was_cond_reset; /* ResetNextIf triggered */
|
||||
rc_typed_value_t measured_value; /* Measured */
|
||||
rc_typed_value_t recall_value; /* Set by RC_CONDITION_REMEMBER */
|
||||
uint8_t was_reset; /* ResetIf triggered */
|
||||
uint8_t has_hits; /* one of more hit counts is non-zero */
|
||||
uint8_t primed; /* true if all non-Trigger conditions are true */
|
||||
uint8_t measured_from_hits; /* true if the measured_value came from a condition's hit count */
|
||||
uint8_t was_cond_reset; /* ResetNextIf triggered */
|
||||
}
|
||||
rc_eval_state_t;
|
||||
|
||||
@ -169,7 +170,9 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
|
||||
void rc_evaluate_operand(rc_typed_value_t* value, rc_operand_t* self, rc_eval_state_t* eval_state);
|
||||
int rc_operand_is_float_memref(const rc_operand_t* self);
|
||||
int rc_operand_is_float(const rc_operand_t* self);
|
||||
int rc_operand_is_recall(const rc_operand_t* self);
|
||||
|
||||
int rc_is_valid_variable_character(char ch, int is_first);
|
||||
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse);
|
||||
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L);
|
||||
void rc_reset_value(rc_value_t* self);
|
||||
|
4
deps/rcheevos/src/rcheevos/runtime.c
vendored
4
deps/rcheevos/src/rcheevos/runtime.c
vendored
@ -10,7 +10,7 @@
|
||||
#define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256
|
||||
|
||||
rc_runtime_t* rc_runtime_alloc(void) {
|
||||
rc_runtime_t* self = (rc_runtime_t*)malloc(sizeof(rc_runtime_t));
|
||||
rc_runtime_t* self = malloc(sizeof(rc_runtime_t));
|
||||
|
||||
if (self) {
|
||||
rc_runtime_init(self);
|
||||
@ -834,7 +834,7 @@ void rc_runtime_invalidate_address(rc_runtime_t* self, uint32_t address) {
|
||||
}
|
||||
}
|
||||
|
||||
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
|
||||
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
|
||||
rc_runtime_validate_address_t validate_handler) {
|
||||
rc_memref_t** last_memref = &self->memrefs;
|
||||
rc_memref_t* memref = self->memrefs;
|
||||
|
@ -210,6 +210,7 @@ static int rc_runtime_progress_is_indirect_memref(rc_operand_t* oper)
|
||||
{
|
||||
case RC_OPERAND_CONST:
|
||||
case RC_OPERAND_FP:
|
||||
case RC_OPERAND_RECALL:
|
||||
case RC_OPERAND_LUA:
|
||||
return 0;
|
||||
|
||||
|
16
deps/rcheevos/src/rcheevos/value.c
vendored
16
deps/rcheevos/src/rcheevos/value.c
vendored
@ -5,6 +5,20 @@
|
||||
#include <float.h> /* FLT_EPSILON */
|
||||
#include <math.h> /* fmod */
|
||||
|
||||
|
||||
|
||||
int rc_is_valid_variable_character(char ch, int is_first) {
|
||||
if (is_first) {
|
||||
if (!isalpha((unsigned char)ch))
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
if (!isalnum((unsigned char)ch))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
|
||||
rc_condset_t** next_clause;
|
||||
|
||||
@ -114,6 +128,8 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat
|
||||
case RC_OPERATOR_AND:
|
||||
case RC_OPERATOR_XOR:
|
||||
case RC_OPERATOR_MOD:
|
||||
case RC_OPERATOR_ADD:
|
||||
case RC_OPERATOR_SUB:
|
||||
case RC_OPERATOR_NONE:
|
||||
break;
|
||||
|
||||
|
214
deps/rcheevos/src/rhash/hash.c
vendored
214
deps/rcheevos/src/rhash/hash.c
vendored
@ -716,6 +716,12 @@ struct rc_hash_zip_idx
|
||||
uint8_t* data;
|
||||
};
|
||||
|
||||
struct rc_hash_ms_dos_dosz_state
|
||||
{
|
||||
const char* path;
|
||||
const struct rc_hash_ms_dos_dosz_state* child;
|
||||
};
|
||||
|
||||
static int rc_hash_zip_idx_sort(const void* a, const void* b)
|
||||
{
|
||||
struct rc_hash_zip_idx *A = (struct rc_hash_zip_idx*)a, *B = (struct rc_hash_zip_idx*)b;
|
||||
@ -723,9 +729,12 @@ static int rc_hash_zip_idx_sort(const void* a, const void* b)
|
||||
return memcmp(A->data, B->data, len);
|
||||
}
|
||||
|
||||
static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
static int rc_hash_ms_dos_parent(md5_state_t* md5, const struct rc_hash_ms_dos_dosz_state *child, const char* parentname, uint32_t parentname_len);
|
||||
static int rc_hash_ms_dos_dosc(md5_state_t* md5, const struct rc_hash_ms_dos_dosz_state *dosz);
|
||||
|
||||
static int rc_hash_zip_file(md5_state_t* md5, void* file_handle, const struct rc_hash_ms_dos_dosz_state* dosz)
|
||||
{
|
||||
uint8_t buf[2048], *alloc_buf, *cdir_start, *cdir_max, *cdir, *hashdata, eocdirhdr_size, cdirhdr_size;
|
||||
uint8_t buf[2048], *alloc_buf, *cdir_start, *cdir_max, *cdir, *hashdata, eocdirhdr_size, cdirhdr_size, nparents;
|
||||
uint32_t cdir_entry_len;
|
||||
size_t sizeof_idx, indices_offset, alloc_size;
|
||||
int64_t i_file, archive_size, ecdh_ofs, total_files, cdir_size, cdir_ofs;
|
||||
@ -789,7 +798,7 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
rc_file_seek(file_handle, ecdh_ofs - 20, SEEK_SET);
|
||||
if (rc_file_read(file_handle, buf, 20) == 20 && RC_ZIP_READ_LE32(buf) == 0x07064b50) /* locator signature */
|
||||
{
|
||||
/* Found the locator, now read the actual ZIP64 end of central directory header */
|
||||
/* Found the locator, now read the actual ZIP64 end of central directory header */
|
||||
int64_t ecdh64_ofs = (int64_t)RC_ZIP_READ_LE64(buf + 0x08);
|
||||
if (ecdh64_ofs <= (archive_size - 56))
|
||||
{
|
||||
@ -837,7 +846,7 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
hashindex = hashindices;
|
||||
|
||||
/* Now process the central directory file records */
|
||||
for (i_file = 0, cdir = cdir_start; i_file < total_files && cdir >= cdir_start && cdir <= cdir_max; i_file++, cdir += cdir_entry_len)
|
||||
for (i_file = nparents = 0, cdir = cdir_start; i_file < total_files && cdir >= cdir_start && cdir <= cdir_max; i_file++, cdir += cdir_entry_len)
|
||||
{
|
||||
const uint8_t *name, *name_end;
|
||||
uint32_t signature = RC_ZIP_READ_LE32(cdir + 0x00);
|
||||
@ -907,6 +916,27 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
return rc_hash_error("Encountered invalid entry in ZIP central directory");
|
||||
}
|
||||
|
||||
/* A DOSZ file can contain a special empty <base>.dosz.parent file in its root which means a parent dosz file is used */
|
||||
if (dosz && decomp_size == 0 && filename_len > 7 && !strncasecmp((const char*)name + filename_len - 7, ".parent", 7) && !memchr(name, '/', filename_len) && !memchr(name, '\\', filename_len))
|
||||
{
|
||||
/* A DOSZ file can only have one parent file */
|
||||
if (nparents++)
|
||||
{
|
||||
free(alloc_buf);
|
||||
return rc_hash_error("Invalid DOSZ file with multiple parents");
|
||||
}
|
||||
|
||||
/* If there is an error with the parent DOSZ, abort now */
|
||||
if (!rc_hash_ms_dos_parent(md5, dosz, (const char*)name, (filename_len - 7)))
|
||||
{
|
||||
free(alloc_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We don't hash this meta file so a user is free to rename it and the parent file */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Write the pointer and length of the data we record about this file */
|
||||
hashindex->data = hashdata;
|
||||
hashindex->length = filename_len + 1 + 4 + 8;
|
||||
@ -951,6 +981,11 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
md5_append(md5, hashindices->data, (int)hashindices->length);
|
||||
|
||||
free(alloc_buf);
|
||||
|
||||
/* If this is a .dosz file, check if an associated .dosc file exists */
|
||||
if (dosz && !rc_hash_ms_dos_dosc(md5, dosz))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
|
||||
#undef RC_ZIP_READ_LE16
|
||||
@ -960,10 +995,86 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
#undef RC_ZIP_WRITE_LE64
|
||||
}
|
||||
|
||||
static int rc_hash_ms_dos_parent(md5_state_t* md5, const struct rc_hash_ms_dos_dosz_state *child, const char* parentname, uint32_t parentname_len)
|
||||
{
|
||||
const char *lastfslash = strrchr(child->path, '/');
|
||||
const char *lastbslash = strrchr(child->path, '\\');
|
||||
const char *lastslash = (lastbslash > lastfslash ? lastbslash : lastfslash);
|
||||
size_t dir_len = (lastslash ? (lastslash + 1 - child->path) : 0);
|
||||
char* parent_path = (char*)malloc(dir_len + parentname_len + 1);
|
||||
struct rc_hash_ms_dos_dosz_state parent;
|
||||
const struct rc_hash_ms_dos_dosz_state *check;
|
||||
void* parent_handle;
|
||||
int parent_res;
|
||||
|
||||
/* Build the path of the parent by combining the directory of the current file with the name */
|
||||
if (!parent_path)
|
||||
return rc_hash_error("Could not allocate temporary buffer");
|
||||
|
||||
memcpy(parent_path, child->path, dir_len);
|
||||
memcpy(parent_path + dir_len, parentname, parentname_len);
|
||||
parent_path[dir_len + parentname_len] = '\0';
|
||||
|
||||
/* Make sure there is no recursion where a parent DOSZ is an already seen child DOSZ */
|
||||
for (check = child->child; check; check = check->child)
|
||||
{
|
||||
if (!strcmp(check->path, parent_path))
|
||||
{
|
||||
free(parent_path);
|
||||
return rc_hash_error("Invalid DOSZ file with recursive parents");
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to open the parent DOSZ file */
|
||||
parent_handle = rc_file_open(parent_path);
|
||||
if (!parent_handle)
|
||||
{
|
||||
char message[1024];
|
||||
snprintf(message, sizeof(message), "DOSZ parent file '%s' does not exist", parent_path);
|
||||
free(parent_path);
|
||||
return rc_hash_error(message);
|
||||
}
|
||||
|
||||
/* Fully hash the parent DOSZ ahead of the child */
|
||||
parent.path = parent_path;
|
||||
parent.child = child;
|
||||
parent_res = rc_hash_zip_file(md5, parent_handle, &parent);
|
||||
rc_file_close(parent_handle);
|
||||
free(parent_path);
|
||||
return parent_res;
|
||||
}
|
||||
|
||||
static int rc_hash_ms_dos_dosc(md5_state_t* md5, const struct rc_hash_ms_dos_dosz_state *dosz)
|
||||
{
|
||||
size_t path_len = strlen(dosz->path);
|
||||
if (dosz->path[path_len-1] == 'z' || dosz->path[path_len-1] == 'Z')
|
||||
{
|
||||
void* file_handle;
|
||||
char *dosc_path = strdup(dosz->path);
|
||||
if (!dosc_path)
|
||||
return rc_hash_error("Could not allocate temporary buffer");
|
||||
|
||||
/* Swap the z to c and use the same capitalization, hash the file if it exists */
|
||||
dosc_path[path_len-1] = (dosz->path[path_len-1] == 'z' ? 'c' : 'C');
|
||||
file_handle = rc_file_open(dosc_path);
|
||||
free(dosc_path);
|
||||
|
||||
if (file_handle)
|
||||
{
|
||||
/* Hash the DOSC as a plain zip file (pass NULL as dosz state) */
|
||||
int res = rc_hash_zip_file(md5, file_handle, NULL);
|
||||
rc_file_close(file_handle);
|
||||
if (!res)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int rc_hash_ms_dos(char hash[33], const char* path)
|
||||
{
|
||||
struct rc_hash_ms_dos_dosz_state dosz;
|
||||
md5_state_t md5;
|
||||
size_t path_len;
|
||||
int res;
|
||||
|
||||
void* file_handle = rc_file_open(path);
|
||||
@ -972,34 +1083,14 @@ static int rc_hash_ms_dos(char hash[33], const char* path)
|
||||
|
||||
/* hash the main content zip file first */
|
||||
md5_init(&md5);
|
||||
res = rc_hash_zip_file(&md5, file_handle);
|
||||
dosz.path = path;
|
||||
dosz.child = NULL;
|
||||
res = rc_hash_zip_file(&md5, file_handle, &dosz);
|
||||
rc_file_close(file_handle);
|
||||
|
||||
if (!res)
|
||||
return 0;
|
||||
|
||||
/* if this is a .dosz file, check if an associated .dosc file exists */
|
||||
path_len = strlen(path);
|
||||
if (path[path_len-1] == 'z' || path[path_len-1] == 'Z')
|
||||
{
|
||||
char *dosc_path = strdup(path);
|
||||
if (!dosc_path)
|
||||
return rc_hash_error("Could not allocate temporary buffer");
|
||||
|
||||
/* swap the z to c and use the same capitalization, hash the file if it exists*/
|
||||
dosc_path[path_len-1] = (path[path_len-1] == 'z' ? 'c' : 'C');
|
||||
file_handle = rc_file_open(dosc_path);
|
||||
free((void*)dosc_path);
|
||||
if (file_handle)
|
||||
{
|
||||
res = rc_hash_zip_file(&md5, file_handle);
|
||||
rc_file_close(file_handle);
|
||||
|
||||
if (!res)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return rc_hash_finalize(&md5, hash);
|
||||
}
|
||||
|
||||
@ -1008,11 +1099,12 @@ static int rc_hash_arcade(char hash[33], const char* path)
|
||||
/* arcade hash is just the hash of the filename (no extension) - the cores are pretty stringent about having the right ROM data */
|
||||
const char* filename = rc_path_get_filename(path);
|
||||
const char* ext = rc_path_get_extension(filename);
|
||||
char buffer[128]; /* realistically, this should never need more than ~32 characters */
|
||||
size_t filename_length = ext - filename - 1;
|
||||
|
||||
/* fbneo supports loading subsystems by using specific folder names.
|
||||
* if one is found, include it in the hash.
|
||||
* https://github.com/libretro/FBNeo/blob/master/src/burner/libretro/README.md#emulating-consoles
|
||||
* https://github.com/libretro/FBNeo/blob/master/src/burner/libretro/README.md#emulating-consoles-and-computers
|
||||
*/
|
||||
if (filename > path + 1)
|
||||
{
|
||||
@ -1029,31 +1121,67 @@ static int rc_hash_arcade(char hash[33], const char* path)
|
||||
} while (folder > path);
|
||||
|
||||
parent_folder_length = filename - folder - 1;
|
||||
if (parent_folder_length < 16)
|
||||
{
|
||||
char* ptr = buffer;
|
||||
while (folder < filename - 1)
|
||||
*ptr++ = tolower(*folder++);
|
||||
*ptr = '\0';
|
||||
|
||||
folder = buffer;
|
||||
}
|
||||
|
||||
switch (parent_folder_length)
|
||||
{
|
||||
case 3:
|
||||
if (memcmp(folder, "nes", 3) == 0 ||
|
||||
memcmp(folder, "fds", 3) == 0 ||
|
||||
memcmp(folder, "sms", 3) == 0 ||
|
||||
memcmp(folder, "msx", 3) == 0 ||
|
||||
memcmp(folder, "ngp", 3) == 0 ||
|
||||
memcmp(folder, "pce", 3) == 0 ||
|
||||
memcmp(folder, "sgx", 3) == 0)
|
||||
if (memcmp(folder, "nes", 3) == 0 || /* NES */
|
||||
memcmp(folder, "fds", 3) == 0 || /* FDS */
|
||||
memcmp(folder, "sms", 3) == 0 || /* Master System */
|
||||
memcmp(folder, "msx", 3) == 0 || /* MSX */
|
||||
memcmp(folder, "ngp", 3) == 0 || /* NeoGeo Pocket */
|
||||
memcmp(folder, "pce", 3) == 0 || /* PCEngine */
|
||||
memcmp(folder, "chf", 3) == 0 || /* ChannelF */
|
||||
memcmp(folder, "sgx", 3) == 0) /* SuperGrafX */
|
||||
include_folder = 1;
|
||||
break;
|
||||
case 4:
|
||||
if (memcmp(folder, "tg16", 4) == 0)
|
||||
if (memcmp(folder, "tg16", 4) == 0 || /* TurboGrafx-16 */
|
||||
memcmp(folder, "msx1", 4) == 0) /* MSX */
|
||||
include_folder = 1;
|
||||
break;
|
||||
case 5:
|
||||
if (memcmp(folder, "neocd", 5) == 0) /* NeoGeo CD */
|
||||
include_folder = 1;
|
||||
break;
|
||||
case 6:
|
||||
if (memcmp(folder, "coleco", 6) == 0 ||
|
||||
memcmp(folder, "sg1000", 6) == 0)
|
||||
if (memcmp(folder, "coleco", 6) == 0 || /* Colecovision */
|
||||
memcmp(folder, "sg1000", 6) == 0) /* SG-1000 */
|
||||
include_folder = 1;
|
||||
break;
|
||||
case 7:
|
||||
if (memcmp(folder, "genesis", 7) == 0) /* Megadrive (Genesis) */
|
||||
include_folder = 1;
|
||||
break;
|
||||
case 8:
|
||||
if (memcmp(folder, "gamegear", 8) == 0 ||
|
||||
memcmp(folder, "megadriv", 8) == 0 ||
|
||||
memcmp(folder, "spectrum", 8) == 0)
|
||||
if (memcmp(folder, "gamegear", 8) == 0 || /* Game Gear */
|
||||
memcmp(folder, "megadriv", 8) == 0 || /* Megadrive */
|
||||
memcmp(folder, "pcengine", 8) == 0 || /* PCEngine */
|
||||
memcmp(folder, "channelf", 8) == 0 || /* ChannelF */
|
||||
memcmp(folder, "spectrum", 8) == 0) /* ZX Spectrum */
|
||||
include_folder = 1;
|
||||
break;
|
||||
case 9:
|
||||
if (memcmp(folder, "megadrive", 9) == 0) /* Megadrive */
|
||||
include_folder = 1;
|
||||
break;
|
||||
case 10:
|
||||
if (memcmp(folder, "supergrafx", 10) == 0 || /* SuperGrafX */
|
||||
memcmp(folder, "zxspectrum", 10) == 0) /* ZX Spectrum */
|
||||
include_folder = 1;
|
||||
break;
|
||||
case 12:
|
||||
if (memcmp(folder, "mastersystem", 12) == 0 || /* Master System */
|
||||
memcmp(folder, "colecovision", 12) == 0) /* Colecovision */
|
||||
include_folder = 1;
|
||||
break;
|
||||
default:
|
||||
@ -1062,10 +1190,8 @@ static int rc_hash_arcade(char hash[33], const char* path)
|
||||
|
||||
if (include_folder)
|
||||
{
|
||||
char buffer[128]; /* realistically, this should never need more than ~20 characters */
|
||||
if (parent_folder_length + filename_length + 1 < sizeof(buffer))
|
||||
{
|
||||
memcpy(&buffer[0], folder, parent_folder_length);
|
||||
buffer[parent_folder_length] = '_';
|
||||
memcpy(&buffer[parent_folder_length + 1], filename, filename_length);
|
||||
return rc_hash_buffer(hash, (uint8_t*)&buffer[0], parent_folder_length + filename_length + 1);
|
||||
|
Loading…
x
Reference in New Issue
Block a user