mirror of https://github.com/PCSX2/pcsx2.git
3rdparty/rcheevos: Bump to v11.6.0
This commit is contained in:
parent
ac968f9a6a
commit
981fedfdd1
|
@ -220,6 +220,8 @@ typedef struct rc_api_award_achievement_request_t {
|
|||
uint32_t hardcore;
|
||||
/* The hash associated to the game being played */
|
||||
const char* game_hash;
|
||||
/* The number of seconds since the achievement was unlocked */
|
||||
uint32_t seconds_since_unlock;
|
||||
}
|
||||
rc_api_award_achievement_request_t;
|
||||
|
||||
|
@ -263,6 +265,8 @@ typedef struct rc_api_submit_lboard_entry_request_t {
|
|||
int32_t score;
|
||||
/* The hash associated to the game being played */
|
||||
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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
if (api_params->game_hash && *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. */
|
||||
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));
|
||||
snprintf(buffer, sizeof(buffer), "%d", api_params->hardcore ? 1 : 0);
|
||||
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);
|
||||
rc_format_md5(buffer, digest);
|
||||
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)
|
||||
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. */
|
||||
md5_init(&md5);
|
||||
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));
|
||||
snprintf(buffer, sizeof(buffer), "%d", api_params->score);
|
||||
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);
|
||||
rc_format_md5(buffer, digest);
|
||||
rc_url_builder_append_str_param(&builder, "v", buffer);
|
||||
|
|
|
@ -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;
|
||||
result = rc_api_init_fetch_image_request(&request, &image_request);
|
||||
if (result == RC_OK)
|
||||
{
|
||||
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);
|
||||
}
|
||||
snprintf(buffer, buffer_size, "%s", request.url);
|
||||
|
||||
rc_api_destroy_request(&request);
|
||||
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);
|
||||
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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/* make the loaded game active if another game is not aleady being loaded. */
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
if (client->state.load == NULL)
|
||||
if (client->state.load == load_state)
|
||||
client->game = load_state->game;
|
||||
else
|
||||
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED;
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
if (client->game != load_state->game) {
|
||||
/* 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 (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED) {
|
||||
/* if a change media request is pending, kick it off */
|
||||
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);
|
||||
|
||||
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) {
|
||||
rc_client_begin_change_media_from_hash(client, pending_media->hash,
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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_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_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->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_LOCKED) {
|
||||
/* 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) {
|
||||
rc_client_unload_game(client);
|
||||
client->state.load = load_state;
|
||||
|
||||
if (load_state->game == NULL) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
client->state.load = load_state;
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
}
|
||||
else if (client->state.load != load_state) {
|
||||
/* 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)
|
||||
{
|
||||
rc_client_game_info_t* game;
|
||||
rc_client_scheduled_callback_data_t** last;
|
||||
rc_client_scheduled_callback_data_t* next;
|
||||
|
||||
if (!client)
|
||||
return;
|
||||
|
@ -2649,29 +2682,38 @@ void rc_client_unload_game(rc_client_t* client)
|
|||
if (client->state.load) {
|
||||
/* this mimics rc_client_abort_async without nesting the lock */
|
||||
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;
|
||||
}
|
||||
|
||||
if (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_LOCKED)
|
||||
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);
|
||||
|
||||
last = &client->state.scheduled_callbacks;
|
||||
do {
|
||||
next = *last;
|
||||
if (!next)
|
||||
break;
|
||||
last = &client->state.scheduled_callbacks;
|
||||
do {
|
||||
next = *last;
|
||||
if (!next)
|
||||
break;
|
||||
|
||||
/* remove rich presence ping scheduled event for game */
|
||||
if (next->callback == rc_client_ping && game && next->related_id == game->public_.id) {
|
||||
*last = next->next;
|
||||
continue;
|
||||
}
|
||||
/* remove rich presence ping scheduled event for game */
|
||||
if (next->callback == rc_client_ping && next->related_id == game->public_.id) {
|
||||
*last = next->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
last = &next->next;
|
||||
} while (1);
|
||||
last = &next->next;
|
||||
} while (1);
|
||||
}
|
||||
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
|
@ -3534,7 +3576,7 @@ typedef struct rc_client_award_achievement_callback_data_t
|
|||
uint32_t retry_count;
|
||||
uint8_t hardcore;
|
||||
const char* game_hash;
|
||||
time_t unlock_time;
|
||||
rc_clock_t unlock_time;
|
||||
rc_client_t* client;
|
||||
rc_client_scheduled_callback_data_t* scheduled_callback_data;
|
||||
} 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.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);
|
||||
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));
|
||||
|
@ -3751,7 +3798,8 @@ 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->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) */
|
||||
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;
|
||||
uint32_t retry_count;
|
||||
const char* game_hash;
|
||||
time_t submit_time;
|
||||
rc_clock_t submit_time;
|
||||
rc_client_t* client;
|
||||
rc_client_scheduled_callback_data_t* scheduled_callback_data;
|
||||
} 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.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);
|
||||
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));
|
||||
|
@ -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->score = leaderboard->value;
|
||||
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",
|
||||
leaderboard->public_.tracker_value, leaderboard->value, leaderboard->public_.id, leaderboard->public_.title);
|
||||
|
|
|
@ -36,6 +36,17 @@ typedef struct rc_disallowed_core_settings_t
|
|||
const rc_disallowed_setting_t* disallowed_settings;
|
||||
} 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[] = {
|
||||
{ "bsnes_region", "pal" },
|
||||
{ NULL, NULL }
|
||||
|
@ -80,6 +91,11 @@ static const rc_disallowed_setting_t _rc_disallowed_fceumm_settings[] = {
|
|||
{ 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[] = {
|
||||
{ "genesis_plus_gx_lock_on", ",action replay (pro),game genie" },
|
||||
{ "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[] = {
|
||||
{ "pcsx_rearmed_psxclock", "<55" },
|
||||
{ "pcsx_rearmed_region", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
@ -140,6 +157,11 @@ static const rc_disallowed_setting_t _rc_disallowed_snes9x_settings[] = {
|
|||
{ 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[] = {
|
||||
{ "vice_autostart", "disabled" }, /* autostart dictates initial load and reset from menu */
|
||||
{ "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[] = {
|
||||
{ "Beetle PSX", _rc_disallowed_beetle_psx_settings },
|
||||
{ "Beetle PSX HW", _rc_disallowed_beetle_psx_hw_settings },
|
||||
{ "bsnes-mercury", _rc_disallowed_bsnes_settings },
|
||||
{ "cap32", _rc_disallowed_cap32_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 },
|
||||
{ "FCEUmm", _rc_disallowed_fceumm_settings },
|
||||
{ "FinalBurn Neo", _rc_disallowed_fbneo_settings },
|
||||
{ "Flycast", _rc_disallowed_flycast_settings },
|
||||
{ "Genesis Plus GX", _rc_disallowed_gpgx_settings },
|
||||
{ "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_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 },
|
||||
{ "SMS Plus GX", _rc_disallowed_smsplus_settings },
|
||||
{ "Snes9x", _rc_disallowed_snes9x_settings },
|
||||
{ "SwanStation", _rc_disallowed_swanstation_settings },
|
||||
{ "VICE x64", _rc_disallowed_vice_settings },
|
||||
{ "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings },
|
||||
{ NULL, NULL }
|
||||
|
@ -186,6 +212,12 @@ static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char
|
|||
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) {
|
||||
/* if value starts with a comma, it's a CSV list of potential matches */
|
||||
if (*match == ',') {
|
||||
|
@ -218,6 +250,10 @@ static int rc_libretro_match_value(const char* val, const char* match) {
|
|||
if (*match == '!')
|
||||
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 */
|
||||
return rc_libretro_string_equal_nocase_wildcard(val, match);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
RC_BEGIN_C_DECLS
|
||||
|
||||
#define RCHEEVOS_VERSION_MAJOR 11
|
||||
#define RCHEEVOS_VERSION_MINOR 5
|
||||
#define RCHEEVOS_VERSION_MINOR 6
|
||||
#define RCHEEVOS_VERSION_PATCH 0
|
||||
|
||||
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)
|
||||
|
|
|
@ -721,16 +721,29 @@ static const rc_memory_regions_t rc_memory_regions_n64 = { _rc_memory_regions_n6
|
|||
/* ===== Nintendo DS ===== */
|
||||
/* https://www.akkit.org/info/gbatek.htm#dsmemorymaps */
|
||||
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 ===== */
|
||||
/* https://problemkaputt.de/gbatek.htm#dsiiomap */
|
||||
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 ===== */
|
||||
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 };
|
||||
|
||||
/* ===== 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 ===== */
|
||||
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:
|
||||
return &rc_memory_regions_wonderswan;
|
||||
|
||||
case RC_CONSOLE_ZX_SPECTRUM:
|
||||
return &rc_memory_regions_zx_spectrum;
|
||||
|
||||
default:
|
||||
return &rc_memory_regions_none;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
self->state = RC_LBOARD_STATE_TRIGGERED;
|
||||
}
|
||||
else if (self->start.requirement == 0 && self->start.alternative == 0) {
|
||||
/* start condition is empty - this leaderboard is submit-only with no measured progress */
|
||||
else if (!self->start.requirement && !self->start.alternative) {
|
||||
/* start trigger is empty. assume the leaderboard is in development and ignore */
|
||||
}
|
||||
else {
|
||||
/* start the leaderboard attempt */
|
||||
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;
|
||||
|
||||
|
|
|
@ -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_chain_start;
|
||||
int overlap;
|
||||
int chain_matches;
|
||||
|
||||
/* empty group */
|
||||
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
|
||||
* compare logic. conflicts can only occur if the combinining conditions match. */
|
||||
chain_matches = 1;
|
||||
if (condition_chain_start != condition)
|
||||
{
|
||||
int chain_matches = 1;
|
||||
const rc_condition_t* condition_chain_iter = condition_chain_start;
|
||||
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 &&
|
||||
!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)
|
||||
|
@ -808,17 +806,27 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
|
|||
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;
|
||||
condition_chain_iter = condition_chain_iter->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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))
|
||||
{
|
||||
while (compare_condition->next && rc_validate_is_combining_condition(compare_condition))
|
||||
compare_condition = compare_condition->next;
|
||||
continue;
|
||||
}
|
||||
/* 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))
|
||||
{
|
||||
while (compare_condition->next && rc_validate_is_combining_condition(compare_condition))
|
||||
compare_condition = compare_condition->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (compare_condition->required_hits)
|
||||
|
|
|
@ -360,6 +360,9 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup
|
|||
do
|
||||
{
|
||||
line = nextline;
|
||||
if (line == NULL)
|
||||
break;
|
||||
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
|
||||
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 */
|
||||
rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse);
|
||||
if (parse->offset < 0)
|
||||
break;
|
||||
|
||||
line = endptr + 1;
|
||||
} while (line < endline);
|
||||
|
||||
|
|
|
@ -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_WASM4:
|
||||
case RC_CONSOLE_WONDERSWAN:
|
||||
case RC_CONSOLE_ZX_SPECTRUM:
|
||||
return rc_hash_buffer(hash, buffer, buffer_size);
|
||||
|
||||
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_WASM4:
|
||||
case RC_CONSOLE_WONDERSWAN:
|
||||
case RC_CONSOLE_ZX_SPECTRUM:
|
||||
/* generic whole-file hash - don't buffer */
|
||||
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:
|
||||
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_DSI:
|
||||
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 */
|
||||
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_ZX_SPECTRUM);
|
||||
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;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "csw"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_ZX_SPECTRUM;
|
||||
}
|
||||
break;
|
||||
|
||||
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[2] = RC_CONSOLE_3DO;
|
||||
iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
|
||||
iterator->consoles[4] = RC_CONSOLE_GAMECUBE;
|
||||
need_path = 1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "pzx"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_ZX_SPECTRUM;
|
||||
}
|
||||
break;
|
||||
|
||||
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 */
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "scl"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_ZX_SPECTRUM;
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "trd") ||
|
||||
rc_path_compare_extension(ext, "tzx"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_ZX_SPECTRUM;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
|
|
Loading…
Reference in New Issue