(cheevos) update to rcheevos 11.5 (#16827)

* update rcheevos

* revalidate config settings after game is loaded
This commit is contained in:
Jamiras 2024-07-30 18:33:07 -06:00 committed by GitHub
parent 13dbb31f62
commit 9f7361c1bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 870 additions and 234 deletions

View File

@ -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

View File

@ -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

View File

@ -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 */

View File

@ -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;

View File

@ -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 {

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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);

View File

@ -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);
}

View File

@ -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"))

View File

@ -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);

View File

@ -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

View File

@ -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 == '*');
}

View File

@ -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";
}
}

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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/ */

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);