3rdparty/rcheevos: Bump to v11.6.0

This commit is contained in:
JordanTheToaster 2024-12-05 05:44:24 +00:00 committed by Ty
parent ac968f9a6a
commit 981fedfdd1
10 changed files with 252 additions and 62 deletions

View File

@ -220,6 +220,8 @@ typedef struct rc_api_award_achievement_request_t {
uint32_t hardcore; uint32_t hardcore;
/* The hash associated to the game being played */ /* The hash associated to the game being played */
const char* game_hash; const char* game_hash;
/* The number of seconds since the achievement was unlocked */
uint32_t seconds_since_unlock;
} }
rc_api_award_achievement_request_t; rc_api_award_achievement_request_t;
@ -263,6 +265,8 @@ typedef struct rc_api_submit_lboard_entry_request_t {
int32_t score; int32_t score;
/* The hash associated to the game being played */ /* The hash associated to the game being played */
const char* game_hash; const char* game_hash;
/* The number of seconds since the leaderboard attempt was completed */
uint32_t seconds_since_completion;
} }
rc_api_submit_lboard_entry_request_t; rc_api_submit_lboard_entry_request_t;

View File

@ -412,6 +412,8 @@ int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_ap
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0); rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
if (api_params->game_hash && *api_params->game_hash) if (api_params->game_hash && *api_params->game_hash)
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash); rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
if (api_params->seconds_since_unlock)
rc_url_builder_append_unum_param(&builder, "o", api_params->seconds_since_unlock);
/* Evaluate the signature. */ /* Evaluate the signature. */
md5_init(&md5); md5_init(&md5);
@ -420,6 +422,14 @@ int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_ap
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username)); md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
snprintf(buffer, sizeof(buffer), "%d", api_params->hardcore ? 1 : 0); snprintf(buffer, sizeof(buffer), "%d", api_params->hardcore ? 1 : 0);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer)); md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
if (api_params->seconds_since_unlock) {
/* second achievement id is needed by delegated unlock. including it here allows overloading
* the hash generating code on the server */
snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
snprintf(buffer, sizeof(buffer), "%u", api_params->seconds_since_unlock);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
}
md5_finish(&md5, digest); md5_finish(&md5, digest);
rc_format_md5(buffer, digest); rc_format_md5(buffer, digest);
rc_url_builder_append_str_param(&builder, "v", buffer); rc_url_builder_append_str_param(&builder, "v", buffer);
@ -505,6 +515,9 @@ int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_
if (api_params->game_hash && *api_params->game_hash) if (api_params->game_hash && *api_params->game_hash)
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash); rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
if (api_params->seconds_since_completion)
rc_url_builder_append_unum_param(&builder, "o", api_params->seconds_since_completion);
/* Evaluate the signature. */ /* Evaluate the signature. */
md5_init(&md5); md5_init(&md5);
snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id); snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id);
@ -512,6 +525,10 @@ int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username)); md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
snprintf(buffer, sizeof(buffer), "%d", api_params->score); snprintf(buffer, sizeof(buffer), "%d", api_params->score);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer)); md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
if (api_params->seconds_since_completion) {
snprintf(buffer, sizeof(buffer), "%u", api_params->seconds_since_completion);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
}
md5_finish(&md5, digest); md5_finish(&md5, digest);
rc_format_md5(buffer, digest); rc_format_md5(buffer, digest);
rc_url_builder_append_str_param(&builder, "v", buffer); rc_url_builder_append_str_param(&builder, "v", buffer);

View File

@ -578,13 +578,7 @@ static int rc_client_get_image_url(char buffer[], size_t buffer_size, int image_
image_request.image_name = image_name; image_request.image_name = image_name;
result = rc_api_init_fetch_image_request(&request, &image_request); result = rc_api_init_fetch_image_request(&request, &image_request);
if (result == RC_OK) if (result == RC_OK)
{ snprintf(buffer, buffer_size, "%s", request.url);
const size_t url_length = strlen(request.url);
if (url_length >= buffer_size)
result = RC_INSUFFICIENT_BUFFER;
else
memcpy(buffer, request.url, url_length + 1);
}
rc_api_destroy_request(&request); rc_api_destroy_request(&request);
return result; return result;
@ -1440,7 +1434,6 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
rc_mutex_lock(&client->state.mutex); rc_mutex_lock(&client->state.mutex);
load_state->progress = (client->state.load == load_state) ? load_state->progress = (client->state.load == load_state) ?
RC_CLIENT_LOAD_GAME_STATE_DONE : RC_CLIENT_LOAD_GAME_STATE_ABORTED; RC_CLIENT_LOAD_GAME_STATE_DONE : RC_CLIENT_LOAD_GAME_STATE_ABORTED;
client->state.load = NULL;
rc_mutex_unlock(&client->state.mutex); rc_mutex_unlock(&client->state.mutex);
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_DONE) { if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_DONE) {
@ -1461,17 +1454,15 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
start_session_response->num_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE); start_session_response->num_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE);
} }
/* make the loaded game active if another game is not aleady being loaded. */
rc_mutex_lock(&client->state.mutex); rc_mutex_lock(&client->state.mutex);
if (client->state.load == NULL) if (client->state.load == load_state)
client->game = load_state->game; client->game = load_state->game;
else
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED;
rc_mutex_unlock(&client->state.mutex); rc_mutex_unlock(&client->state.mutex);
if (client->game != load_state->game) { if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED) {
/* 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 {
/* if a change media request is pending, kick it off */ /* if a change media request is pending, kick it off */
rc_client_pending_media_t* pending_media; rc_client_pending_media_t* pending_media;
@ -1481,6 +1472,9 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
rc_mutex_unlock(&load_state->client->state.mutex); rc_mutex_unlock(&load_state->client->state.mutex);
if (pending_media) { if (pending_media) {
/* rc_client_check_pending_media will fail if it can't find the game in client->game or
* client->state.load->game. since we've detached the load_state, this has to occur after
* we've made the game active. */
if (pending_media->hash) { if (pending_media->hash) {
rc_client_begin_change_media_from_hash(client, pending_media->hash, rc_client_begin_change_media_from_hash(client, pending_media->hash,
pending_media->callback, pending_media->callback_userdata); pending_media->callback, pending_media->callback_userdata);
@ -1494,12 +1488,50 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
rc_client_free_pending_media(pending_media); rc_client_free_pending_media(pending_media);
} }
/* client->game must be set before calling this function so it can query the console_id */ rc_mutex_lock(&client->state.mutex);
if (client->state.load != load_state)
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED;
rc_mutex_unlock(&client->state.mutex);
}
/* if the game is still being loaded, make sure all the required memory addresses are accessible
* so we can mark achievements as unsupported before loading them into the runtime. */
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED) {
/* TODO: it is desirable to not do memory reads from a background thread. Some emulators (like Dolphin) don't
* allow it. Dolphin's solution is to use a dummy read function that says all addresses are valid and
* switches to the actual read function after the callback is called. latter invalid reads will
* mark achievements as unsupported. */
/* ASSERT: client->game must be set before calling this function so the read_memory callback can query the console_id */
rc_client_validate_addresses(load_state->game, client); rc_client_validate_addresses(load_state->game, client);
rc_mutex_lock(&client->state.mutex);
if (client->state.load != load_state)
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED;
rc_mutex_unlock(&client->state.mutex);
}
/* if the game is still being loaded, load any active acheivements/leaderboards into the runtime */
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED) {
rc_client_activate_achievements(load_state->game, client); rc_client_activate_achievements(load_state->game, client);
rc_client_activate_leaderboards(load_state->game, client); rc_client_activate_leaderboards(load_state->game, client);
/* detach the load state to indicate that loading is fully complete */
rc_mutex_lock(&client->state.mutex);
if (client->state.load == load_state)
client->state.load = NULL;
else
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED;
rc_mutex_unlock(&client->state.mutex);
}
/* one last sanity check to make sure the game is still being loaded. */
if (load_state->progress == RC_CLIENT_LOAD_GAME_STATE_ABORTED) {
/* game has been unloaded, or another game is being loaded over the top of this game */
if (load_state->callback)
load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata);
}
else {
if (load_state->hash->hash[0] != '[') { if (load_state->hash->hash[0] != '[') {
if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_LOCKED) { if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_LOCKED) {
/* schedule the periodic ping */ /* schedule the periodic ping */
@ -2007,7 +2039,6 @@ static int rc_client_attach_load_state(rc_client_t* client, rc_client_load_state
{ {
if (client->state.load == NULL) { if (client->state.load == NULL) {
rc_client_unload_game(client); rc_client_unload_game(client);
client->state.load = load_state;
if (load_state->game == NULL) { if (load_state->game == NULL) {
load_state->game = rc_client_allocate_game(); load_state->game = rc_client_allocate_game();
@ -2018,6 +2049,10 @@ static int rc_client_attach_load_state(rc_client_t* client, rc_client_load_state
return 0; return 0;
} }
} }
rc_mutex_lock(&client->state.mutex);
client->state.load = load_state;
rc_mutex_unlock(&client->state.mutex);
} }
else if (client->state.load != load_state) { else if (client->state.load != load_state) {
/* previous load was aborted */ /* previous load was aborted */
@ -2621,8 +2656,6 @@ static void rc_client_game_mark_ui_to_be_hidden(rc_client_t* client, rc_client_g
void rc_client_unload_game(rc_client_t* client) void rc_client_unload_game(rc_client_t* client)
{ {
rc_client_game_info_t* game; rc_client_game_info_t* game;
rc_client_scheduled_callback_data_t** last;
rc_client_scheduled_callback_data_t* next;
if (!client) if (!client)
return; return;
@ -2649,29 +2682,38 @@ void rc_client_unload_game(rc_client_t* client)
if (client->state.load) { if (client->state.load) {
/* this mimics rc_client_abort_async without nesting the lock */ /* this mimics rc_client_abort_async without nesting the lock */
client->state.load->async_handle.aborted = RC_CLIENT_ASYNC_ABORTED; client->state.load->async_handle.aborted = RC_CLIENT_ASYNC_ABORTED;
/* if the game is still being loaded, let the load process clean it up */
if (client->state.load->game == game)
game = NULL;
client->state.load = NULL; client->state.load = NULL;
} }
if (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_LOCKED) if (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_LOCKED)
client->state.spectator_mode = RC_CLIENT_SPECTATOR_MODE_ON; client->state.spectator_mode = RC_CLIENT_SPECTATOR_MODE_ON;
if (game != NULL) if (game != NULL) {
rc_client_scheduled_callback_data_t** last;
rc_client_scheduled_callback_data_t* next;
rc_client_game_mark_ui_to_be_hidden(client, game); rc_client_game_mark_ui_to_be_hidden(client, game);
last = &client->state.scheduled_callbacks; last = &client->state.scheduled_callbacks;
do { do {
next = *last; next = *last;
if (!next) if (!next)
break; break;
/* remove rich presence ping scheduled event for game */ /* remove rich presence ping scheduled event for game */
if (next->callback == rc_client_ping && game && next->related_id == game->public_.id) { if (next->callback == rc_client_ping && next->related_id == game->public_.id) {
*last = next->next; *last = next->next;
continue; continue;
} }
last = &next->next; last = &next->next;
} while (1); } while (1);
}
rc_mutex_unlock(&client->state.mutex); rc_mutex_unlock(&client->state.mutex);
@ -3534,7 +3576,7 @@ typedef struct rc_client_award_achievement_callback_data_t
uint32_t retry_count; uint32_t retry_count;
uint8_t hardcore; uint8_t hardcore;
const char* game_hash; const char* game_hash;
time_t unlock_time; rc_clock_t unlock_time;
rc_client_t* client; rc_client_t* client;
rc_client_scheduled_callback_data_t* scheduled_callback_data; rc_client_scheduled_callback_data_t* scheduled_callback_data;
} rc_client_award_achievement_callback_data_t; } rc_client_award_achievement_callback_data_t;
@ -3685,6 +3727,11 @@ static void rc_client_award_achievement_server_call(rc_client_award_achievement_
api_params.hardcore = ach_data->hardcore; api_params.hardcore = ach_data->hardcore;
api_params.game_hash = ach_data->game_hash; api_params.game_hash = ach_data->game_hash;
if (ach_data->retry_count) {
const rc_clock_t now = ach_data->client->callbacks.get_time_millisecs(ach_data->client);
api_params.seconds_since_unlock = (uint32_t)((now - ach_data->unlock_time) / 1000);
}
result = rc_api_init_award_achievement_request(&request, &api_params); result = rc_api_init_award_achievement_request(&request, &api_params);
if (result != RC_OK) { if (result != RC_OK) {
RC_CLIENT_LOG_ERR_FORMATTED(ach_data->client, "Error constructing unlock request for achievement %u: %s", ach_data->id, rc_error_str(result)); RC_CLIENT_LOG_ERR_FORMATTED(ach_data->client, "Error constructing unlock request for achievement %u: %s", ach_data->id, rc_error_str(result));
@ -3751,7 +3798,8 @@ static void rc_client_award_achievement(rc_client_t* client, rc_client_achieveme
callback_data->client = client; callback_data->client = client;
callback_data->id = achievement->public_.id; callback_data->id = achievement->public_.id;
callback_data->hardcore = client->state.hardcore; callback_data->hardcore = client->state.hardcore;
callback_data->unlock_time = achievement->public_.unlock_time; callback_data->game_hash = client->game->public_.hash;
callback_data->unlock_time = client->callbacks.get_time_millisecs(client);
if (client->game) /* may be NULL if this gets called while unloading the game (from another thread - events are raised outside the lock) */ 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; callback_data->game_hash = client->game->public_.hash;
@ -4185,7 +4233,7 @@ typedef struct rc_client_submit_leaderboard_entry_callback_data_t
int32_t score; int32_t score;
uint32_t retry_count; uint32_t retry_count;
const char* game_hash; const char* game_hash;
time_t submit_time; rc_clock_t submit_time;
rc_client_t* client; rc_client_t* client;
rc_client_scheduled_callback_data_t* scheduled_callback_data; rc_client_scheduled_callback_data_t* scheduled_callback_data;
} rc_client_submit_leaderboard_entry_callback_data_t; } rc_client_submit_leaderboard_entry_callback_data_t;
@ -4340,6 +4388,11 @@ static void rc_client_submit_leaderboard_entry_server_call(rc_client_submit_lead
api_params.score = lboard_data->score; api_params.score = lboard_data->score;
api_params.game_hash = lboard_data->game_hash; api_params.game_hash = lboard_data->game_hash;
if (lboard_data->retry_count) {
const rc_clock_t now = lboard_data->client->callbacks.get_time_millisecs(lboard_data->client);
api_params.seconds_since_completion = (uint32_t)((now - lboard_data->submit_time) / 1000);
}
result = rc_api_init_submit_lboard_entry_request(&request, &api_params); result = rc_api_init_submit_lboard_entry_request(&request, &api_params);
if (result != RC_OK) { if (result != RC_OK) {
RC_CLIENT_LOG_ERR_FORMATTED(lboard_data->client, "Error constructing submit leaderboard entry for leaderboard %u: %s", lboard_data->id, rc_error_str(result)); RC_CLIENT_LOG_ERR_FORMATTED(lboard_data->client, "Error constructing submit leaderboard entry for leaderboard %u: %s", lboard_data->id, rc_error_str(result));
@ -4383,7 +4436,7 @@ static void rc_client_submit_leaderboard_entry(rc_client_t* client, rc_client_le
callback_data->id = leaderboard->public_.id; callback_data->id = leaderboard->public_.id;
callback_data->score = leaderboard->value; callback_data->score = leaderboard->value;
callback_data->game_hash = client->game->public_.hash; callback_data->game_hash = client->game->public_.hash;
callback_data->submit_time = time(NULL); callback_data->submit_time = client->callbacks.get_time_millisecs(client);
RC_CLIENT_LOG_INFO_FORMATTED(client, "Submitting %s (%d) for leaderboard %u: %s", RC_CLIENT_LOG_INFO_FORMATTED(client, "Submitting %s (%d) for leaderboard %u: %s",
leaderboard->public_.tracker_value, leaderboard->value, leaderboard->public_.id, leaderboard->public_.title); leaderboard->public_.tracker_value, leaderboard->value, leaderboard->public_.id, leaderboard->public_.title);

View File

@ -36,6 +36,17 @@ typedef struct rc_disallowed_core_settings_t
const rc_disallowed_setting_t* disallowed_settings; const rc_disallowed_setting_t* disallowed_settings;
} rc_disallowed_core_settings_t; } rc_disallowed_core_settings_t;
static const rc_disallowed_setting_t _rc_disallowed_beetle_psx_settings[] = {
{ "beetle_psx_cpu_freq_scale", "<100" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_beetle_psx_hw_settings[] = {
{ "beetle_psx_hw_cpu_freq_scale", "<100" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_bsnes_settings[] = { static const rc_disallowed_setting_t _rc_disallowed_bsnes_settings[] = {
{ "bsnes_region", "pal" }, { "bsnes_region", "pal" },
{ NULL, NULL } { NULL, NULL }
@ -80,6 +91,11 @@ static const rc_disallowed_setting_t _rc_disallowed_fceumm_settings[] = {
{ NULL, NULL } { NULL, NULL }
}; };
static const rc_disallowed_setting_t _rc_disallowed_flycast_settings[] = {
{ "reicast_sh4clock", "<200" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_gpgx_settings[] = { static const rc_disallowed_setting_t _rc_disallowed_gpgx_settings[] = {
{ "genesis_plus_gx_lock_on", ",action replay (pro),game genie" }, { "genesis_plus_gx_lock_on", ",action replay (pro),game genie" },
{ "genesis_plus_gx_region_detect", "pal" }, { "genesis_plus_gx_region_detect", "pal" },
@ -108,6 +124,7 @@ static const rc_disallowed_setting_t _rc_disallowed_neocd_settings[] = {
}; };
static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = { static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = {
{ "pcsx_rearmed_psxclock", "<55" },
{ "pcsx_rearmed_region", "pal" }, { "pcsx_rearmed_region", "pal" },
{ NULL, NULL } { NULL, NULL }
}; };
@ -140,6 +157,11 @@ static const rc_disallowed_setting_t _rc_disallowed_snes9x_settings[] = {
{ NULL, NULL } { NULL, NULL }
}; };
static const rc_disallowed_setting_t _rc_disallowed_swanstation_settings[] = {
{ "swanstation_CPU_Overclock", "<100" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_vice_settings[] = { static const rc_disallowed_setting_t _rc_disallowed_vice_settings[] = {
{ "vice_autostart", "disabled" }, /* autostart dictates initial load and reset from menu */ { "vice_autostart", "disabled" }, /* autostart dictates initial load and reset from menu */
{ "vice_reset", "!autostart" }, /* reset dictates behavior when pressing reset button (END) */ { "vice_reset", "!autostart" }, /* reset dictates behavior when pressing reset button (END) */
@ -152,6 +174,8 @@ static const rc_disallowed_setting_t _rc_disallowed_virtual_jaguar_settings[] =
}; };
static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = { static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
{ "Beetle PSX", _rc_disallowed_beetle_psx_settings },
{ "Beetle PSX HW", _rc_disallowed_beetle_psx_hw_settings },
{ "bsnes-mercury", _rc_disallowed_bsnes_settings }, { "bsnes-mercury", _rc_disallowed_bsnes_settings },
{ "cap32", _rc_disallowed_cap32_settings }, { "cap32", _rc_disallowed_cap32_settings },
{ "dolphin-emu", _rc_disallowed_dolphin_settings }, { "dolphin-emu", _rc_disallowed_dolphin_settings },
@ -160,6 +184,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
{ "ecwolf", _rc_disallowed_ecwolf_settings }, { "ecwolf", _rc_disallowed_ecwolf_settings },
{ "FCEUmm", _rc_disallowed_fceumm_settings }, { "FCEUmm", _rc_disallowed_fceumm_settings },
{ "FinalBurn Neo", _rc_disallowed_fbneo_settings }, { "FinalBurn Neo", _rc_disallowed_fbneo_settings },
{ "Flycast", _rc_disallowed_flycast_settings },
{ "Genesis Plus GX", _rc_disallowed_gpgx_settings }, { "Genesis Plus GX", _rc_disallowed_gpgx_settings },
{ "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_settings }, { "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_settings },
{ "Mesen", _rc_disallowed_mesen_settings }, { "Mesen", _rc_disallowed_mesen_settings },
@ -171,6 +196,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
{ "QUASI88", _rc_disallowed_quasi88_settings }, { "QUASI88", _rc_disallowed_quasi88_settings },
{ "SMS Plus GX", _rc_disallowed_smsplus_settings }, { "SMS Plus GX", _rc_disallowed_smsplus_settings },
{ "Snes9x", _rc_disallowed_snes9x_settings }, { "Snes9x", _rc_disallowed_snes9x_settings },
{ "SwanStation", _rc_disallowed_swanstation_settings },
{ "VICE x64", _rc_disallowed_vice_settings }, { "VICE x64", _rc_disallowed_vice_settings },
{ "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings }, { "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings },
{ NULL, NULL } { NULL, NULL }
@ -186,6 +212,12 @@ static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char
return (*value == '\0'); return (*value == '\0');
} }
static int rc_libretro_numeric_less_than(const char* test, const char* value) {
int test_num = atoi(test);
int value_num = atoi(value);
return (test_num < value_num);
}
static int rc_libretro_match_value(const char* val, const char* match) { static int rc_libretro_match_value(const char* val, const char* match) {
/* if value starts with a comma, it's a CSV list of potential matches */ /* if value starts with a comma, it's a CSV list of potential matches */
if (*match == ',') { if (*match == ',') {
@ -218,6 +250,10 @@ static int rc_libretro_match_value(const char* val, const char* match) {
if (*match == '!') if (*match == '!')
return !rc_libretro_match_value(val, &match[1]); return !rc_libretro_match_value(val, &match[1]);
/* a leading less tahn means the provided value is the minimum allowed */
if (*match == '<')
return rc_libretro_numeric_less_than(val, &match[1]);
/* just a single value, attempt to match it */ /* just a single value, attempt to match it */
return rc_libretro_string_equal_nocase_wildcard(val, match); return rc_libretro_string_equal_nocase_wildcard(val, match);
} }

View File

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

View File

@ -721,16 +721,29 @@ static const rc_memory_regions_t rc_memory_regions_n64 = { _rc_memory_regions_n6
/* ===== Nintendo DS ===== */ /* ===== Nintendo DS ===== */
/* https://www.akkit.org/info/gbatek.htm#dsmemorymaps */ /* https://www.akkit.org/info/gbatek.htm#dsmemorymaps */
static const rc_memory_region_t _rc_memory_regions_nintendo_ds[] = { static const rc_memory_region_t _rc_memory_regions_nintendo_ds[] = {
{ 0x000000U, 0x3FFFFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } { 0x0000000U, 0x03FFFFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
/* To keep DS/DSi memory maps aligned, padding is set here for the DSi's extra RAM */
{ 0x0400000U, 0x0FFFFFFU, 0x02400000U, RC_MEMORY_TYPE_UNUSED, "Unused (DSi exclusive)" },
/* The DS/DSi have "tightly coupled memory": very fast memory directly connected to the CPU.
* This memory has an instruction variant (ITCM) and a data variant (DTCM).
* For achievement purposes it is useful to be able to access the data variant.
* This memory does not have a fixed address on console, being able to be moved to any $0xxxx000 region.
* While normally this kind of memory is addressed outside of the possible native addressing space, this is simply not possible,
* as the DS/DSi's address space covers all possible uint32_t values.
* $0E000000 is used here as a "pseudo-end," as this is nearly the end of all the memory actually mapped to addresses
* This means that (with the exception of $FFFF0000 onwards, which has the ARM9 BIOS mapped) $0E000000 onwards has nothing mapped to it
*/
{ 0x1000000U, 0x1003FFFU, 0x0E000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Data TCM" }
}; };
static const rc_memory_regions_t rc_memory_regions_nintendo_ds = { _rc_memory_regions_nintendo_ds, 1 }; static const rc_memory_regions_t rc_memory_regions_nintendo_ds = { _rc_memory_regions_nintendo_ds, 3 };
/* ===== Nintendo DSi ===== */ /* ===== Nintendo DSi ===== */
/* https://problemkaputt.de/gbatek.htm#dsiiomap */ /* https://problemkaputt.de/gbatek.htm#dsiiomap */
static const rc_memory_region_t _rc_memory_regions_nintendo_dsi[] = { static const rc_memory_region_t _rc_memory_regions_nintendo_dsi[] = {
{ 0x000000U, 0xFFFFFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } { 0x0000000U, 0x0FFFFFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x1000000U, 0x1003FFFU, 0x0E000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Data TCM" }
}; };
static const rc_memory_regions_t rc_memory_regions_nintendo_dsi = { _rc_memory_regions_nintendo_dsi, 1 }; static const rc_memory_regions_t rc_memory_regions_nintendo_dsi = { _rc_memory_regions_nintendo_dsi, 2 };
/* ===== Oric ===== */ /* ===== Oric ===== */
static const rc_memory_region_t _rc_memory_regions_oric[] = { static const rc_memory_region_t _rc_memory_regions_oric[] = {
@ -956,6 +969,31 @@ static const rc_memory_region_t _rc_memory_regions_wonderswan[] = {
}; };
static const rc_memory_regions_t rc_memory_regions_wonderswan = { _rc_memory_regions_wonderswan, 2 }; static const rc_memory_regions_t rc_memory_regions_wonderswan = { _rc_memory_regions_wonderswan, 2 };
/* ===== ZX Spectrum ===== */
/* https://github.com/TASEmulators/BizHawk/blob/3a3b22c/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs
* https://github.com/TASEmulators/BizHawk/blob/3a3b22c/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs
* https://worldofspectrum.org/faq/reference/128kreference.htm */
static const rc_memory_region_t _rc_memory_regions_zx_spectrum[] = {
/* ZX Spectrum is complicated as multiple models exist with varying amounts of memory.
* In practice, this can be reduced to two categories: 16K/48K units, and 128K units.
* 16K/48K units have RAM starting at $4000 onwards, 16K ending at $7FFF, 48K ending at $FFFF.
* 128K units have banked memory, with $4000-$7FFF normally having RAM bank 5, and $8000-$BFFF normally having RAM bank 2.
* $C000-$FFFF is normally reserved for banked RAM, having any of banks 0-7.
* For the purposes of the RAM map, $C000-$FFFF is assumed to be bank 0, and $10000 onwards has the other banks in order (1, 3, 4, 6, 7)
* Doing it this way always for 16K/48K games to have the same memory map on the 128K, and thus avoid issues due to the model selected.
* Later 128K units also have a special banking mode that changes up banking completely, but for 16K/48K compatibility purposes this doesn't matter, and so is irrelevant.
*/
{ 0x00000U, 0x03FFFU, 0x04000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Screen RAM" }, /* RAM bank 5 on 128K units */
{ 0x04000U, 0x07FFFU, 0x08000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RAM bank 2 on 128K units */
{ 0x08000U, 0x0BFFFU, 0x0C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RAM bank 0-7 on 128K units, assumed to be bank 0 here */
{ 0x0C000U, 0x0FFFFU, 0x10000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RAM bank 1 on 128K units */
{ 0x10000U, 0x13FFFU, 0x14000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RAM bank 3 on 128K units */
{ 0x14000U, 0x17FFFU, 0x18000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RAM bank 4 on 128K units */
{ 0x18000U, 0x1BFFFU, 0x1C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RAM bank 6 on 128K units */
{ 0x1C000U, 0x1FFFFU, 0x20000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Screen RAM" } /* RAM bank 7 on 128K units */
};
static const rc_memory_regions_t rc_memory_regions_zx_spectrum = { _rc_memory_regions_zx_spectrum, 8 };
/* ===== default ===== */ /* ===== default ===== */
static const rc_memory_regions_t rc_memory_regions_none = { 0, 0 }; static const rc_memory_regions_t rc_memory_regions_none = { 0, 0 };
@ -1141,6 +1179,9 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id)
case RC_CONSOLE_WONDERSWAN: case RC_CONSOLE_WONDERSWAN:
return &rc_memory_regions_wonderswan; return &rc_memory_regions_wonderswan;
case RC_CONSOLE_ZX_SPECTRUM:
return &rc_memory_regions_zx_spectrum;
default: default:
return &rc_memory_regions_none; return &rc_memory_regions_none;
} }

View File

@ -199,19 +199,19 @@ int rc_evaluate_lboard(rc_lboard_t* self, int32_t* value, rc_peek_t peek, void*
/* start and submit are both true in the same frame, just submit without announcing the leaderboard is available */ /* start and submit are both true in the same frame, just submit without announcing the leaderboard is available */
self->state = RC_LBOARD_STATE_TRIGGERED; self->state = RC_LBOARD_STATE_TRIGGERED;
} }
else if (self->start.requirement == 0 && self->start.alternative == 0) { else if (!self->start.requirement && !self->start.alternative) {
/* start condition is empty - this leaderboard is submit-only with no measured progress */ /* start trigger is empty. assume the leaderboard is in development and ignore */
} }
else { else {
/* start the leaderboard attempt */ /* start the leaderboard attempt */
self->state = RC_LBOARD_STATE_STARTED; self->state = RC_LBOARD_STATE_STARTED;
/* reset any hit counts in the value */
if (self->progress)
rc_reset_value(self->progress);
rc_reset_value(&self->value);
} }
/* reset any hit counts in the value */
if (self->progress)
rc_reset_value(self->progress);
rc_reset_value(&self->value);
} }
break; break;

View File

@ -726,6 +726,7 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
const rc_condition_t* condition; const rc_condition_t* condition;
const rc_condition_t* condition_chain_start; const rc_condition_t* condition_chain_start;
int overlap; int overlap;
int chain_matches;
/* empty group */ /* empty group */
if (conditions == NULL || compare_conditions == NULL) if (conditions == NULL || compare_conditions == NULL)
@ -777,9 +778,9 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
/* if combining conditions exist, make sure the same combining conditions exist in the /* if combining conditions exist, make sure the same combining conditions exist in the
* compare logic. conflicts can only occur if the combinining conditions match. */ * compare logic. conflicts can only occur if the combinining conditions match. */
chain_matches = 1;
if (condition_chain_start != condition) if (condition_chain_start != condition)
{ {
int chain_matches = 1;
const rc_condition_t* condition_chain_iter = condition_chain_start; const rc_condition_t* condition_chain_iter = condition_chain_start;
while (condition_chain_iter != condition) while (condition_chain_iter != condition)
{ {
@ -795,11 +796,8 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
if (compare_condition->oper != RC_OPERATOR_NONE && if (compare_condition->oper != RC_OPERATOR_NONE &&
!rc_validate_are_operands_equal(&compare_condition->operand2, &condition_chain_iter->operand2)) !rc_validate_are_operands_equal(&compare_condition->operand2, &condition_chain_iter->operand2))
{ {
if (compare_condition->operand2.type != condition_chain_iter->operand2.type) chain_matches = 0;
{ break;
chain_matches = 0;
break;
}
} }
if (!compare_condition->next) if (!compare_condition->next)
@ -808,17 +806,27 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
break; break;
} }
if (compare_condition->type != RC_CONDITION_ADD_ADDRESS &&
compare_condition->type != RC_CONDITION_ADD_SOURCE &&
compare_condition->type != RC_CONDITION_SUB_SOURCE &&
compare_condition->type != RC_CONDITION_AND_NEXT)
{
/* things like AddHits and OrNext are hard to definitively detect conflicts. ignore them. */
chain_matches = 0;
break;
}
compare_condition = compare_condition->next; compare_condition = compare_condition->next;
condition_chain_iter = condition_chain_iter->next; condition_chain_iter = condition_chain_iter->next;
} }
}
/* combining field didn't match, or there's more unmatched combining fields. ignore this condition */ /* combining field didn't match, or there's more unmatched combining fields. ignore this condition */
if (!chain_matches || rc_validate_is_combining_condition(compare_condition)) if (!chain_matches || rc_validate_is_combining_condition(compare_condition))
{ {
while (compare_condition->next && rc_validate_is_combining_condition(compare_condition)) while (compare_condition->next && rc_validate_is_combining_condition(compare_condition))
compare_condition = compare_condition->next; compare_condition = compare_condition->next;
continue; continue;
}
} }
if (compare_condition->required_hits) if (compare_condition->required_hits)

View File

@ -360,6 +360,9 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup
do do
{ {
line = nextline; line = nextline;
if (line == NULL)
break;
nextline = rc_parse_line(line, &endline, parse); nextline = rc_parse_line(line, &endline, parse);
if (endline - line < 2) { if (endline - line < 2) {
@ -438,6 +441,9 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup
/* insert the current item and continue scanning the next one */ /* insert the current item and continue scanning the next one */
rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse); rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse);
if (parse->offset < 0)
break;
line = endptr + 1; line = endptr + 1;
} while (line < endline); } while (line < endline);

View File

@ -3113,6 +3113,7 @@ int rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8
case RC_CONSOLE_VIRTUAL_BOY: case RC_CONSOLE_VIRTUAL_BOY:
case RC_CONSOLE_WASM4: case RC_CONSOLE_WASM4:
case RC_CONSOLE_WONDERSWAN: case RC_CONSOLE_WONDERSWAN:
case RC_CONSOLE_ZX_SPECTRUM:
return rc_hash_buffer(hash, buffer, buffer_size); return rc_hash_buffer(hash, buffer, buffer_size);
case RC_CONSOLE_ARDUBOY: case RC_CONSOLE_ARDUBOY:
@ -3411,6 +3412,7 @@ int rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* p
case RC_CONSOLE_VIRTUAL_BOY: case RC_CONSOLE_VIRTUAL_BOY:
case RC_CONSOLE_WASM4: case RC_CONSOLE_WASM4:
case RC_CONSOLE_WONDERSWAN: case RC_CONSOLE_WONDERSWAN:
case RC_CONSOLE_ZX_SPECTRUM:
/* generic whole-file hash - don't buffer */ /* generic whole-file hash - don't buffer */
return rc_hash_whole_file(hash, path); return rc_hash_whole_file(hash, path);
@ -3466,6 +3468,9 @@ int rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* p
case RC_CONSOLE_NINTENDO_64: case RC_CONSOLE_NINTENDO_64:
return rc_hash_n64(hash, path); return rc_hash_n64(hash, path);
case RC_CONSOLE_NINTENDO_3DS:
return rc_hash_nintendo_3ds(hash, path);
case RC_CONSOLE_NINTENDO_DS: case RC_CONSOLE_NINTENDO_DS:
case RC_CONSOLE_NINTENDO_DSI: case RC_CONSOLE_NINTENDO_DSI:
return rc_hash_nintendo_ds(hash, path); return rc_hash_nintendo_ds(hash, path);
@ -3573,6 +3578,7 @@ static void rc_hash_initialize_dsk_iterator(struct rc_hash_iterator* iterator, c
/* check MSX first, as Apple II isn't supported by RetroArch, and RAppleWin won't use the iterator */ /* check MSX first, as Apple II isn't supported by RetroArch, and RAppleWin won't use the iterator */
rc_hash_iterator_append_console(iterator, RC_CONSOLE_MSX); rc_hash_iterator_append_console(iterator, RC_CONSOLE_MSX);
rc_hash_iterator_append_console(iterator, RC_CONSOLE_AMSTRAD_PC); rc_hash_iterator_append_console(iterator, RC_CONSOLE_AMSTRAD_PC);
rc_hash_iterator_append_console(iterator, RC_CONSOLE_ZX_SPECTRUM);
rc_hash_iterator_append_console(iterator, RC_CONSOLE_APPLE_II); rc_hash_iterator_append_console(iterator, RC_CONSOLE_APPLE_II);
} }
@ -3725,6 +3731,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{ {
iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS; iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS;
} }
else if (rc_path_compare_extension(ext, "csw"))
{
iterator->consoles[0] = RC_CONSOLE_ZX_SPECTRUM;
}
break; break;
case 'd': case 'd':
@ -3808,6 +3818,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
iterator->consoles[1] = RC_CONSOLE_PSP; iterator->consoles[1] = RC_CONSOLE_PSP;
iterator->consoles[2] = RC_CONSOLE_3DO; iterator->consoles[2] = RC_CONSOLE_3DO;
iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */ iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
iterator->consoles[4] = RC_CONSOLE_GAMECUBE;
need_path = 1; need_path = 1;
} }
break; break;
@ -3909,6 +3920,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{ {
iterator->consoles[0] = RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER; iterator->consoles[0] = RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER;
} }
else if (rc_path_compare_extension(ext, "pzx"))
{
iterator->consoles[0] = RC_CONSOLE_ZX_SPECTRUM;
}
break; break;
case 'r': case 'r':
@ -3947,11 +3962,16 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{ {
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */ iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */
} }
else if (rc_path_compare_extension(ext, "scl"))
{
iterator->consoles[0] = RC_CONSOLE_ZX_SPECTRUM;
}
break; break;
case 't': case 't':
if (rc_path_compare_extension(ext, "tap")) if (rc_path_compare_extension(ext, "tap"))
{ {
/* also Commodore 64 and ZX Spectrum, but all are full file hashes */
iterator->consoles[0] = RC_CONSOLE_ORIC; iterator->consoles[0] = RC_CONSOLE_ORIC;
} }
else if (rc_path_compare_extension(ext, "tic")) else if (rc_path_compare_extension(ext, "tic"))
@ -3962,6 +3982,11 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{ {
iterator->consoles[0] = RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER; iterator->consoles[0] = RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER;
} }
else if (rc_path_compare_extension(ext, "trd") ||
rc_path_compare_extension(ext, "tzx"))
{
iterator->consoles[0] = RC_CONSOLE_ZX_SPECTRUM;
}
break; break;
case 'u': case 'u':