diff --git a/dep/rcheevos/include/rc_runtime_types.h b/dep/rcheevos/include/rc_runtime_types.h
index a2ff02ea3..28f456477 100644
--- a/dep/rcheevos/include/rc_runtime_types.h
+++ b/dep/rcheevos/include/rc_runtime_types.h
@@ -124,6 +124,9 @@ typedef struct rc_operand_t {
/* specifies how to read the memref for some types (RC_OPERAND_*) */
uint8_t memref_access_type;
+
+ /* if set, this operand is combining the current condition with the previous one */
+ uint8_t is_combining;
}
rc_operand_t;
diff --git a/dep/rcheevos/src/rapi/rc_api_common.c b/dep/rcheevos/src/rapi/rc_api_common.c
index 3eeb79cf9..4caef42a2 100644
--- a/dep/rcheevos/src/rapi/rc_api_common.c
+++ b/dep/rcheevos/src/rapi/rc_api_common.c
@@ -1304,14 +1304,14 @@ int rc_api_init_fetch_image_request_hosted(rc_api_request_t* request, const rc_a
return builder.result;
}
-const char* rc_api_build_avatar_url(rc_buffer_t* buffer, uint32_t image_type, const char* username) {
+const char* rc_api_build_avatar_url(rc_buffer_t* buffer, uint32_t image_type, const char* image_name) {
rc_api_fetch_image_request_t image_request;
rc_api_request_t request;
int result;
memset(&image_request, 0, sizeof(image_request));
image_request.image_type = image_type;
- image_request.image_name = username;
+ image_request.image_name = image_name;
result = rc_api_init_fetch_image_request(&request, &image_request);
if (result == RC_OK)
diff --git a/dep/rcheevos/src/rapi/rc_api_common.h b/dep/rcheevos/src/rapi/rc_api_common.h
index 3cefc2fd6..d44260aa7 100644
--- a/dep/rcheevos/src/rapi/rc_api_common.h
+++ b/dep/rcheevos/src/rapi/rc_api_common.h
@@ -80,7 +80,7 @@ const char* rc_api_default_host(void);
void rc_api_url_build_dorequest_url(rc_api_request_t* request, const rc_api_host_t* host);
int rc_api_url_build_dorequest(rc_api_url_builder_t* builder, const char* api, const char* username, const char* api_token);
-const char* rc_api_build_avatar_url(rc_buffer_t* buffer, uint32_t image_type, const char* username);
+const char* rc_api_build_avatar_url(rc_buffer_t* buffer, uint32_t image_type, const char* image_name);
RC_END_C_DECLS
diff --git a/dep/rcheevos/src/rc_client.c b/dep/rcheevos/src/rc_client.c
index 5c758c677..d45b45c28 100644
--- a/dep/rcheevos/src/rc_client.c
+++ b/dep/rcheevos/src/rc_client.c
@@ -64,6 +64,7 @@ typedef struct rc_client_load_state_t
#ifdef RC_CLIENT_SUPPORTS_HASH
rc_hash_iterator_t hash_iterator;
+ rc_client_game_hash_t* tried_hashes[4];
#endif
rc_client_pending_media_t* pending_media;
@@ -2235,40 +2236,31 @@ static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
return;
}
- if (load_state->game->media_hash &&
- load_state->game->media_hash->game_hash &&
- load_state->game->media_hash->game_hash->next) {
+ if (load_state->tried_hashes[1]) {
/* multiple hashes were tried, create a CSV */
- struct rc_client_game_hash_t* game_hash = load_state->game->media_hash->game_hash;
- int count = 1;
+ size_t i;
+ size_t count = 0;
char* ptr;
- size_t size;
+ size_t size = 0;
- size = strlen(game_hash->hash) + 1;
- while (game_hash->next) {
- game_hash = game_hash->next;
- size += strlen(game_hash->hash) + 1;
+ for (i = 0; i < sizeof(load_state->tried_hashes) / sizeof(load_state->tried_hashes[0]); ++i) {
+ if (!load_state->tried_hashes[i])
+ break;
+
+ size += strlen(load_state->tried_hashes[i]->hash) + 1;
count++;
}
ptr = (char*)rc_buffer_alloc(&load_state->game->buffer, size);
- ptr += size - 1;
- *ptr = '\0';
- game_hash = load_state->game->media_hash->game_hash;
- do {
- const size_t hash_len = strlen(game_hash->hash);
- ptr -= hash_len;
- memcpy(ptr, game_hash->hash, hash_len);
-
- game_hash = game_hash->next;
- if (!game_hash)
- break;
-
- ptr--;
- *ptr = ',';
- } while (1);
-
load_state->game->public_.hash = ptr;
+ for (i = 0; i < count; i++) {
+ const size_t hash_len = strlen(load_state->tried_hashes[i]->hash);
+ memcpy(ptr, load_state->tried_hashes[i]->hash, hash_len);
+ ptr += hash_len;
+ *ptr++ = ',';
+ }
+ *(ptr - 1) = '\0';
+
load_state->game->public_.console_id = RC_CONSOLE_UNKNOWN;
} else {
/* only a single hash was tried, capture it */
@@ -2293,6 +2285,12 @@ static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
if (load_state->hash->game_id == 0) {
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
+ #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
+ if (client->state.raintegration && client->state.raintegration->set_console_id) {
+ if (load_state->game->public_.console_id != RC_CONSOLE_UNKNOWN)
+ client->state.raintegration->set_console_id(load_state->game->public_.console_id);
+ }
+ #endif
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);
@@ -2504,6 +2502,9 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
{
rc_client_t* client = load_state->client;
rc_client_game_hash_t* old_hash;
+#ifdef RC_CLIENT_SUPPORTS_HASH
+ size_t i;
+#endif
if (!rc_client_attach_load_state(client, load_state)) {
rc_client_free_load_state(load_state);
@@ -2513,6 +2514,24 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
old_hash = load_state->hash;
load_state->hash = rc_client_find_game_hash(client, hash);
+#ifdef RC_CLIENT_SUPPORTS_HASH
+ i = 0;
+ do {
+ if (!load_state->tried_hashes[i]) {
+ load_state->tried_hashes[i] = load_state->hash;
+ break;
+ }
+
+ if (load_state->tried_hashes[i] == load_state->hash)
+ break;
+
+ if (++i == sizeof(load_state->tried_hashes) / sizeof(load_state->tried_hashes[0])) {
+ RC_CLIENT_LOG_VERBOSE(client, "tried_hashes buffer is full");
+ break;
+ }
+ } while (1);
+#endif
+
if (file_path) {
rc_client_media_hash_t* media_hash =
(rc_client_media_hash_t*)rc_buffer_alloc(&load_state->game->buffer, sizeof(*media_hash));
@@ -2553,6 +2572,11 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
rc_api_destroy_request(&request);
}
+ else if (load_state->hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID &&
+ client->state.external_client && 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);
+ }
#endif
else {
rc_client_begin_fetch_game_data(load_state);
@@ -2561,6 +2585,26 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
}
+static void rc_client_abort_load_in_progress(rc_client_t* client)
+{
+ rc_client_load_state_t* load_state;
+
+ rc_mutex_lock(&client->state.mutex);
+
+ load_state = client->state.load;
+ if (load_state) {
+ /* this mimics rc_client_abort_async without nesting the lock */
+ load_state->async_handle.aborted = RC_CLIENT_ASYNC_ABORTED;
+
+ client->state.load = NULL;
+ }
+
+ rc_mutex_unlock(&client->state.mutex);
+
+ if (load_state && load_state->callback)
+ load_state->callback(RC_ABORTED, "The requested game is no longer active", load_state->client, load_state->callback_userdata);
+}
+
rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const char* hash, rc_client_callback_t callback, void* callback_userdata)
{
rc_client_load_state_t* load_state;
@@ -2575,6 +2619,8 @@ rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const c
return NULL;
}
+ rc_client_abort_load_in_progress(client);
+
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_load_game)
return client->state.external_client->begin_load_game(client, hash, callback, callback_userdata);
@@ -2616,6 +2662,8 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
return NULL;
}
+ rc_client_abort_load_in_progress(client);
+
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
/* if a add_game_hash handler exists, do the identification locally, then pass the
* resulting game_id/hash to the external client */
@@ -5991,6 +6039,9 @@ void rc_client_reset(rc_client_t* client)
int rc_client_can_pause(rc_client_t* client, uint32_t* frames_remaining)
{
+ if (!client)
+ return 1;
+
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->can_pause)
return client->state.external_client->can_pause(frames_remaining);
diff --git a/dep/rcheevos/src/rc_client_external.c b/dep/rcheevos/src/rc_client_external.c
index acfcf1740..fd8f76cbd 100644
--- a/dep/rcheevos/src/rc_client_external.c
+++ b/dep/rcheevos/src/rc_client_external.c
@@ -3,17 +3,45 @@
#include "rc_client_external_versions.h"
#include "rc_client_internal.h"
+#include "rc_api_runtime.h"
+
#define RC_CONVERSION_FILL(obj, obj_type, src_type) memset((uint8_t*)obj + sizeof(src_type), 0, sizeof(obj_type) - sizeof(src_type))
+/* https://media.retroachievements.org/Badge/123456_lock.png is 58 with null terminator */
+#define RC_CLIENT_IMAGE_URL_BUFFER_SIZE 64
+
typedef struct rc_client_external_conversions_t {
rc_client_user_t user;
rc_client_game_t game;
rc_client_subset_t subsets[4];
rc_client_achievement_t achievements[16];
+ char user_avatar_url[RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
+ char game_badge_url[RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
+ char subset_badge_url[4][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
+ char achievement_badge_url[16][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
+ char achievement_badge_locked_url[16][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
uint32_t next_subset_index;
uint32_t next_achievement_index;
} rc_client_external_conversions_t;
+static const char* rc_client_external_build_avatar_url(char buffer[], uint32_t image_type, const char* image_name)
+{
+ rc_api_fetch_image_request_t image_request;
+ rc_api_request_t request;
+ int result;
+
+ memset(&image_request, 0, sizeof(image_request));
+ image_request.image_type = image_type;
+ image_request.image_name = image_name;
+
+ result = rc_api_init_fetch_image_request(&request, &image_request);
+ if (result != RC_OK)
+ return NULL;
+
+ strcpy_s(buffer, RC_CLIENT_IMAGE_URL_BUFFER_SIZE, request.url);
+ return buffer;
+}
+
static void rc_client_external_conversions_init(const rc_client_t* client)
{
if (!client->state.external_client_conversions) {
@@ -39,6 +67,10 @@ const rc_client_user_t* rc_client_external_convert_v1_user(const rc_client_t* cl
converted = &client->state.external_client_conversions->user;
memcpy(converted, v1_user, sizeof(v1_rc_client_user_t));
RC_CONVERSION_FILL(converted, rc_client_user_t, v1_rc_client_user_t);
+
+ converted->avatar_url = rc_client_external_build_avatar_url(
+ client->state.external_client_conversions->user_avatar_url, RC_IMAGE_TYPE_USER, v1_user->username);
+
return converted;
}
@@ -54,12 +86,17 @@ const rc_client_game_t* rc_client_external_convert_v1_game(const rc_client_t* cl
converted = &client->state.external_client_conversions->game;
memcpy(converted, v1_game, sizeof(v1_rc_client_game_t));
RC_CONVERSION_FILL(converted, rc_client_game_t, v1_rc_client_game_t);
+
+ converted->badge_url = rc_client_external_build_avatar_url(
+ client->state.external_client_conversions->game_badge_url, RC_IMAGE_TYPE_GAME, v1_game->badge_name);
+
return converted;
}
const rc_client_subset_t* rc_client_external_convert_v1_subset(const rc_client_t* client, const rc_client_subset_t* v1_subset)
{
rc_client_subset_t* converted = NULL;
+ char* badge_url = NULL;
const uint32_t num_subsets = sizeof(client->state.external_client_conversions->subsets) / sizeof(client->state.external_client_conversions->subsets[0]);
uint32_t index;
@@ -71,23 +108,31 @@ const rc_client_subset_t* rc_client_external_convert_v1_subset(const rc_client_t
for (index = 0; index < num_subsets; ++index) {
if (client->state.external_client_conversions->subsets[index].id == v1_subset->id) {
converted = &client->state.external_client_conversions->subsets[index];
+ badge_url = client->state.external_client_conversions->subset_badge_url[index];
break;
}
}
if (!converted) {
- converted = &client->state.external_client_conversions->subsets[client->state.external_client_conversions->next_subset_index];
- client->state.external_client_conversions->next_subset_index = (client->state.external_client_conversions->next_subset_index + 1) % num_subsets;
+ index = client->state.external_client_conversions->next_subset_index;
+ converted = &client->state.external_client_conversions->subsets[index];
+ badge_url = client->state.external_client_conversions->subset_badge_url[client->state.external_client_conversions->next_subset_index];
+ client->state.external_client_conversions->next_subset_index = (index + 1) % num_subsets;
}
memcpy(converted, v1_subset, sizeof(v1_rc_client_subset_t));
RC_CONVERSION_FILL(converted, rc_client_subset_t, v1_rc_client_subset_t);
+
+ converted->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_GAME, v1_subset->badge_name);
+
return converted;
}
const rc_client_achievement_t* rc_client_external_convert_v1_achievement(const rc_client_t* client, const rc_client_achievement_t* v1_achievement)
{
rc_client_achievement_t* converted = NULL;
+ char* badge_url = NULL;
+ char* badge_locked_url = NULL;
const uint32_t num_achievements = sizeof(client->state.external_client_conversions->achievements) / sizeof(client->state.external_client_conversions->achievements[0]);
uint32_t index;
@@ -99,17 +144,26 @@ const rc_client_achievement_t* rc_client_external_convert_v1_achievement(const r
for (index = 0; index < num_achievements; ++index) {
if (client->state.external_client_conversions->achievements[index].id == v1_achievement->id) {
converted = &client->state.external_client_conversions->achievements[index];
+ badge_url = client->state.external_client_conversions->achievement_badge_url[index];
+ badge_locked_url = client->state.external_client_conversions->achievement_badge_locked_url[index];
break;
}
}
if (!converted) {
- converted = &client->state.external_client_conversions->achievements[client->state.external_client_conversions->next_achievement_index];
- client->state.external_client_conversions->next_achievement_index = (client->state.external_client_conversions->next_achievement_index + 1) % num_achievements;
+ index = client->state.external_client_conversions->next_achievement_index;
+ converted = &client->state.external_client_conversions->achievements[index];
+ badge_url = client->state.external_client_conversions->achievement_badge_url[index];
+ badge_locked_url = client->state.external_client_conversions->achievement_badge_locked_url[index];
+ client->state.external_client_conversions->next_achievement_index = (index + 1) % num_achievements;
}
memcpy(converted, v1_achievement, sizeof(v1_rc_client_achievement_t));
RC_CONVERSION_FILL(converted, rc_client_achievement_t, v1_rc_client_achievement_t);
+
+ converted->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT, v1_achievement->badge_name);
+ converted->badge_locked_url = rc_client_external_build_avatar_url(badge_locked_url, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, v1_achievement->badge_name);
+
return converted;
}
@@ -118,6 +172,7 @@ typedef struct rc_client_achievement_list_wrapper_t {
rc_client_achievement_list_t* source_list;
rc_client_achievement_t* achievements;
rc_client_achievement_t** achievements_pointers;
+ char* badge_url_buffer;
} rc_client_achievement_list_wrapper_t;
static void rc_client_destroy_achievement_list_wrapper(rc_client_achievement_list_info_t* info)
@@ -130,6 +185,8 @@ static void rc_client_destroy_achievement_list_wrapper(rc_client_achievement_lis
free(wrapper->achievements_pointers);
if (wrapper->info.public_.buckets)
free((void*)wrapper->info.public_.buckets);
+ if (wrapper->badge_url_buffer)
+ free(wrapper->badge_url_buffer);
rc_client_destroy_achievement_list(wrapper->source_list);
@@ -156,6 +213,7 @@ rc_client_achievement_list_t* rc_client_external_convert_v1_achievement_list(con
const v1_rc_client_achievement_bucket_t* stop_bucket = src_bucket + v1_achievement_list->num_buckets;
rc_client_achievement_bucket_t* bucket;
uint32_t num_achievements = 0;
+ char* badge_url = NULL;
new_list->info.public_.buckets = bucket = (rc_client_achievement_bucket_t*)calloc(v1_achievement_list->num_buckets, sizeof(*new_list->info.public_.buckets));
if (!new_list->info.public_.buckets)
@@ -167,7 +225,8 @@ rc_client_achievement_list_t* rc_client_external_convert_v1_achievement_list(con
if (num_achievements) {
new_list->achievements = (rc_client_achievement_t*)calloc(num_achievements, sizeof(*new_list->achievements));
new_list->achievements_pointers = (rc_client_achievement_t**)malloc(num_achievements * sizeof(rc_client_achievement_t*));
- if (!new_list->achievements || !new_list->achievements_pointers)
+ new_list->badge_url_buffer = badge_url = (char*)malloc(num_achievements * 2 * RC_CLIENT_IMAGE_URL_BUFFER_SIZE);
+ if (!new_list->achievements || !new_list->achievements_pointers || !new_list->badge_url_buffer)
return (rc_client_achievement_list_t*)new_list;
}
@@ -186,6 +245,11 @@ rc_client_achievement_list_t* rc_client_external_convert_v1_achievement_list(con
for (; src_achievement < stop_achievement; ++src_achievement, ++achievement) {
*achievement = &new_list->achievements[num_achievements++];
memcpy(*achievement, *src_achievement, sizeof(**src_achievement));
+
+ (*achievement)->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT, (*achievement)->badge_name);
+ badge_url += RC_CLIENT_IMAGE_URL_BUFFER_SIZE;
+ (*achievement)->badge_locked_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, (*achievement)->badge_name);
+ badge_url += RC_CLIENT_IMAGE_URL_BUFFER_SIZE;
}
}
}
diff --git a/dep/rcheevos/src/rc_client_types.natvis b/dep/rcheevos/src/rc_client_types.natvis
index 01b06026a..ddb71817f 100644
--- a/dep/rcheevos/src/rc_client_types.natvis
+++ b/dep/rcheevos/src/rc_client_types.natvis
@@ -364,7 +364,7 @@
- game
- - *((__rc_client_game_hash_list_t*)&hashes)
+ - *((__rc_client_game_hash_list_t*)this)
- user
- callbacks
- state
diff --git a/dep/rcheevos/src/rcheevos/condition.c b/dep/rcheevos/src/rcheevos/condition.c
index df8cb5049..320ca0a62 100644
--- a/dep/rcheevos/src/rcheevos/condition.c
+++ b/dep/rcheevos/src/rcheevos/condition.c
@@ -432,6 +432,7 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
if (parse->addsource_parent.type != RC_OPERAND_NONE) {
/* type determined by leaf */
rc_operand_addsource(&condition->operand1, parse, condition->operand1.size);
+ condition->operand1.is_combining = 1;
}
memcpy(&parse->remember, &condition->operand1, sizeof(parse->remember));
@@ -465,6 +466,7 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
if (parse->addsource_parent.type != RC_OPERAND_NONE) {
/* type determined by leaf */
rc_operand_addsource(&condition->operand1, parse, condition->operand1.size);
+ condition->operand1.is_combining = 1;
if (parse->buffer)
condition->optimized_comparator = rc_condition_determine_comparator(condition);
@@ -476,6 +478,49 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
}
}
+static const rc_modified_memref_t* rc_operand_get_modified_memref(const rc_operand_t* operand) {
+ if (!rc_operand_is_memref(operand))
+ return NULL;
+
+ if (operand->value.memref->value.memref_type != RC_MEMREF_TYPE_MODIFIED_MEMREF)
+ return NULL;
+
+ return (rc_modified_memref_t*)operand->value.memref;
+}
+
+/* rc_condition_update_parse_state will mutate the operand1 to point at the modified memref
+ * containing the accumulated result up until that point. this function returns the original
+ * unmodified operand1 from parsing the definition.
+ */
+const rc_operand_t* rc_condition_get_real_operand1(const rc_condition_t* self) {
+ const rc_operand_t* operand = &self->operand1;
+ const rc_modified_memref_t* modified_memref;
+
+ if (operand->is_combining) {
+ /* operand = "previous + current" - extract current */
+ const rc_modified_memref_t* combining_modified_memref = rc_operand_get_modified_memref(operand);
+ if (combining_modified_memref)
+ operand = &combining_modified_memref->modifier;
+ }
+
+ /* modifying operators are merged into an rc_modified_memref_t
+ * if operand1 is a modified memref, assume it's been merged with the right side and
+ * extract the parent which is the actual operand1. */
+ modified_memref = rc_operand_get_modified_memref(operand);
+ if (modified_memref) {
+ if (modified_memref->modifier_type == RC_OPERATOR_INDIRECT_READ) {
+ /* if the modified memref is an indirect read, the parent is the indirect
+ * address and the modifier is the offset. the actual size and address are
+ * stored in the modified memref - use it */
+ } else if (rc_operator_is_modifying(self->oper) && self->oper != RC_OPERATOR_NONE) {
+ /* operand = "parent*modifier" - extract parent.modifier will already be in operand2 */
+ operand = &modified_memref->parent;
+ }
+ }
+
+ return operand;
+}
+
int rc_condition_is_combining(const rc_condition_t* self) {
switch (self->type) {
case RC_CONDITION_STANDARD:
diff --git a/dep/rcheevos/src/rcheevos/consoleinfo.c b/dep/rcheevos/src/rcheevos/consoleinfo.c
index 0fa9f14db..e25d74c5e 100644
--- a/dep/rcheevos/src/rcheevos/consoleinfo.c
+++ b/dep/rcheevos/src/rcheevos/consoleinfo.c
@@ -833,12 +833,13 @@ static const rc_memory_region_t _rc_memory_regions_pokemini[] = {
static const rc_memory_regions_t rc_memory_regions_pokemini = { _rc_memory_regions_pokemini, 2 };
/* ===== Sega CD ===== */
-/* https://en.wikibooks.org/wiki/Genesis_Programming#MegaCD_Changes */
+/* https://en.wikibooks.org/wiki/Genesis_Programming/68K_Memory_map/ */
static const rc_memory_region_t _rc_memory_regions_segacd[] = {
{ 0x000000U, 0x00FFFFU, 0x00FF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "68000 RAM" },
- { 0x010000U, 0x08FFFFU, 0x80020000U, RC_MEMORY_TYPE_SAVE_RAM, "CD PRG RAM" } /* normally banked into $020000-$03FFFF */
+ { 0x010000U, 0x08FFFFU, 0x80020000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD PRG RAM" }, /* normally banked into $020000-$03FFFF */
+ { 0x090000U, 0x0AFFFFU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD WORD RAM" }
};
-static const rc_memory_regions_t rc_memory_regions_segacd = { _rc_memory_regions_segacd, 2 };
+static const rc_memory_regions_t rc_memory_regions_segacd = { _rc_memory_regions_segacd, 3 };
/* ===== Sega Saturn ===== */
/* https://segaretro.org/Sega_Saturn_hardware_notes_(2004-04-27) */
diff --git a/dep/rcheevos/src/rcheevos/memref.c b/dep/rcheevos/src/rcheevos/memref.c
index ca94077ad..5b5d80cf8 100644
--- a/dep/rcheevos/src/rcheevos/memref.c
+++ b/dep/rcheevos/src/rcheevos/memref.c
@@ -150,7 +150,7 @@ rc_modified_memref_t* rc_alloc_modified_memref(rc_parse_state_t* parse, uint8_t
memset(modified_memref, 0, sizeof(*modified_memref));
modified_memref->memref.value.memref_type = RC_MEMREF_TYPE_MODIFIED_MEMREF;
modified_memref->memref.value.size = size;
- modified_memref->memref.value.type = (size == RC_MEMSIZE_FLOAT) ? RC_VALUE_TYPE_FLOAT : RC_VALUE_TYPE_UNSIGNED;
+ modified_memref->memref.value.type = rc_memsize_is_float(size) ? RC_VALUE_TYPE_FLOAT : RC_VALUE_TYPE_UNSIGNED;
memcpy(&modified_memref->parent, parent, sizeof(modified_memref->parent));
memcpy(&modified_memref->modifier, modifier, sizeof(modified_memref->modifier));
modified_memref->modifier_type = modifier_type;
diff --git a/dep/rcheevos/src/rcheevos/operand.c b/dep/rcheevos/src/rcheevos/operand.c
index 84e620bd6..e79050fa9 100644
--- a/dep/rcheevos/src/rcheevos/operand.c
+++ b/dep/rcheevos/src/rcheevos/operand.c
@@ -55,6 +55,7 @@ static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr, r
}
else {
memcpy(self, &parse->remember, sizeof(*self));
+ self->is_combining = 0;
self->memref_access_type = self->type;
}
self->type = RC_OPERAND_RECALL;
@@ -117,16 +118,11 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_
}
if (parse->indirect_parent.type != RC_OPERAND_NONE) {
- if (parse->indirect_parent.type == RC_OPERAND_CONST) {
- self->value.memref = rc_alloc_memref(parse, address + parse->indirect_parent.value.num, size);
- }
- else {
- rc_operand_t offset;
- rc_operand_set_const(&offset, address);
+ rc_operand_t offset;
+ rc_operand_set_const(&offset, address);
- self->value.memref = (rc_memref_t*)rc_alloc_modified_memref(parse,
- size, &parse->indirect_parent, RC_OPERATOR_INDIRECT_READ, &offset);
- }
+ self->value.memref = (rc_memref_t*)rc_alloc_modified_memref(parse,
+ size, &parse->indirect_parent, RC_OPERATOR_INDIRECT_READ, &offset);
}
else {
self->value.memref = rc_alloc_memref(parse, address, size);
@@ -147,6 +143,8 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, rc_parse_state_t*
int negative;
int allow_decimal = 0;
+ self->is_combining = 0;
+
switch (*aux) {
case 'h': case 'H': /* hex constant */
if (aux[2] == 'x' || aux[2] == 'X') {
@@ -318,7 +316,7 @@ int rc_operands_are_equal(const rc_operand_t* left, const rc_operand_t* right) {
case RC_OPERAND_FP:
return (left->value.dbl == right->value.dbl);
case RC_OPERAND_RECALL:
- return 1;
+ return (left->value.memref == right->value.memref);
default:
break;
}
@@ -360,7 +358,7 @@ int rc_operator_is_modifying(int oper) {
}
}
-static int rc_memsize_is_float(uint8_t size) {
+int rc_memsize_is_float(uint8_t size) {
switch (size) {
case RC_MEMSIZE_FLOAT:
case RC_MEMSIZE_FLOAT_BE:
diff --git a/dep/rcheevos/src/rcheevos/rc_internal.h b/dep/rcheevos/src/rcheevos/rc_internal.h
index f63d90b90..7c013d9dc 100644
--- a/dep/rcheevos/src/rcheevos/rc_internal.h
+++ b/dep/rcheevos/src/rcheevos/rc_internal.h
@@ -335,10 +335,12 @@ int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state);
void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state);
int rc_condition_is_combining(const rc_condition_t* self);
void rc_condition_convert_to_operand(const rc_condition_t* condition, rc_operand_t* operand, rc_parse_state_t* parse);
+const rc_operand_t* rc_condition_get_real_operand1(const rc_condition_t* self);
int rc_parse_operand(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse);
void rc_evaluate_operand(rc_typed_value_t* value, const rc_operand_t* self, rc_eval_state_t* eval_state);
int rc_operator_is_modifying(int oper);
+int rc_memsize_is_float(uint8_t size);
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);
diff --git a/dep/rcheevos/src/rcheevos/rc_runtime_types.natvis b/dep/rcheevos/src/rcheevos/rc_runtime_types.natvis
index e086e9ad2..45e31921d 100644
--- a/dep/rcheevos/src/rcheevos/rc_runtime_types.natvis
+++ b/dep/rcheevos/src/rcheevos/rc_runtime_types.natvis
@@ -120,7 +120,7 @@
- ... {*((__rc_memsize_enum_func_t*)&operand.size)} {operand.value.memref->address,x}
+ ... {*((__rc_memsize_enum_func_t*)&operand.size)} {((rc_modified_memref_t*)operand.value.memref)->parent,na}
value {((rc_value_t*)operand.value.memref)->name,s}
{*((__rc_memsize_enum_func_t*)&operand.size)} {operand.value.memref->address,x}
diff --git a/dep/rcheevos/src/rcheevos/rc_validate.c b/dep/rcheevos/src/rcheevos/rc_validate.c
index a01e362ee..6a5c658a7 100644
--- a/dep/rcheevos/src/rcheevos/rc_validate.c
+++ b/dep/rcheevos/src/rcheevos/rc_validate.c
@@ -199,8 +199,10 @@ static uint32_t rc_max_chain_value(const rc_operand_t* operand)
{
if (rc_operand_is_memref(operand) && operand->value.memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) {
const rc_modified_memref_t* modified_memref = (const rc_modified_memref_t*)operand->value.memref;
- const uint32_t op_max = rc_max_chain_value(&modified_memref->parent);
- return rc_scale_value(op_max, modified_memref->modifier_type, &modified_memref->modifier);
+ if (modified_memref->modifier_type != RC_OPERATOR_INDIRECT_READ) {
+ const uint32_t op_max = rc_max_chain_value(&modified_memref->parent);
+ return rc_scale_value(op_max, modified_memref->modifier_type, &modified_memref->modifier);
+ }
}
return rc_max_value(operand);
@@ -304,11 +306,13 @@ static int rc_validate_condset_internal(const rc_condset_t* condset, char result
}
for (cond = condset->conditions; cond; cond = cond->next, ++index) {
- const int is_memref1 = rc_operand_is_memref(&cond->operand1);
+ /* validate the original operands first */
+ const rc_operand_t* operand1 = rc_condition_get_real_operand1(cond);
+ int is_memref1 = rc_operand_is_memref(operand1);
const int is_memref2 = rc_operand_is_memref(&cond->operand2);
if (!in_add_address) {
- if (is_memref1 && !rc_validate_memref(cond->operand1.value.memref, buffer, sizeof(buffer), console_id, max_address)) {
+ if (is_memref1 && !rc_validate_memref(operand1->value.memref, buffer, sizeof(buffer), console_id, max_address)) {
snprintf(result, result_size, "Condition %d: %s", index, buffer);
return 0;
}
@@ -321,8 +325,8 @@ static int rc_validate_condset_internal(const rc_condset_t* condset, char result
in_add_address = 0;
}
- if (rc_operand_is_recall(&cond->operand1)) {
- if (rc_operand_type_is_memref(cond->operand1.memref_access_type) && !cond->operand1.value.memref) {
+ if (rc_operand_is_recall(operand1)) {
+ if (rc_operand_type_is_memref(operand1->memref_access_type) && !operand1->value.memref) {
snprintf(result, result_size, "Condition %d: Recall used before Remember", index);
return 0;
}
@@ -343,10 +347,18 @@ static int rc_validate_condset_internal(const rc_condset_t* condset, char result
continue;
case RC_CONDITION_ADD_ADDRESS:
- if (cond->operand1.type == RC_OPERAND_DELTA || cond->operand1.type == RC_OPERAND_PRIOR) {
+ if (operand1->type == RC_OPERAND_DELTA || operand1->type == RC_OPERAND_PRIOR) {
snprintf(result, result_size, "Condition %d: Using pointer from previous frame", index);
return 0;
}
+ if (rc_operand_is_float(&cond->operand1) || rc_operand_is_float(&cond->operand2)) {
+ snprintf(result, result_size, "Condition %d: Using non-integer value in AddAddress calcuation", index);
+ return 0;
+ }
+ if (rc_operand_type_is_transform(cond->operand1.type)) {
+ snprintf(result, result_size, "Condition %d: Using transformed value in AddAddress calcuation", index);
+ return 0;
+ }
in_add_address = 1;
is_combining = 1;
continue;
@@ -390,22 +402,26 @@ static int rc_validate_condset_internal(const rc_condset_t* condset, char result
break;
}
+ /* original operands are valid. now switch to the derived operands for logic
+ * combining/comparing them */
+ operand1 = &cond->operand1;
+ is_memref1 = rc_operand_is_memref(operand1);
+
/* check for comparing two differently sized memrefs */
if (is_memref1 && is_memref2 &&
- cond->operand1.value.memref->value.memref_type == RC_MEMREF_TYPE_MEMREF &&
+ operand1->value.memref->value.memref_type == RC_MEMREF_TYPE_MEMREF &&
cond->operand2.value.memref->value.memref_type == RC_MEMREF_TYPE_MEMREF &&
- rc_max_value(&cond->operand1) != rc_max_value(&cond->operand2)) {
+ rc_max_value(operand1) != rc_max_value(&cond->operand2)) {
snprintf(result, result_size, "Condition %d: Comparing different memory sizes", index);
return 0;
}
- if (is_memref1 && rc_operand_is_float(&cond->operand1)) {
+ if (is_memref1 && rc_operand_is_float(operand1)) {
/* if left side is a float, right side will be converted to a float, so don't do range validation */
}
else if (is_memref1 || is_memref2) {
/* if either side is a memref, check for impossible comparisons */
const size_t prefix_length = snprintf(result, result_size, "Condition %d: ", index);
- const rc_operand_t* operand1 = &cond->operand1;
const rc_operand_t* operand2 = &cond->operand2;
uint8_t oper = cond->oper;
uint32_t max = rc_max_chain_value(operand1);
@@ -414,8 +430,8 @@ static int rc_validate_condset_internal(const rc_condset_t* condset, char result
if (!is_memref1) {
/* pretend constant was on right side */
+ operand2 = operand1;
operand1 = &cond->operand2;
- operand2 = &cond->operand1;
max_val = max;
max = rc_max_value(&cond->operand2);