update to rcheevos 11.6 (#17088)

This commit is contained in:
Jamiras 2024-10-12 12:59:08 -06:00 committed by GitHub
parent 228426880b
commit ec314b9e43
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 240 additions and 73 deletions

View File

@ -1,3 +1,14 @@
# v11.6.0
* backdate retried unlocks in rc_client
* add memory map and hash method for RC_CONSOLE_ZX_SPECTRUM
* add RC_CONSOLE_GAMECUBE to supported consoles for iso file extension
* add DTCM to RC_CONSOLE_NINTENDO_DS and RC_CONSOLE_NINTENDO_DSI memory maps
* don't report conflict if last conditions of OrNext chain conflict
* don't report conflict if last conditions of AddSource chain conflict
* fix hits not being reset on leaderboard value when start and submit are both true in a single frame
* fix crash when multiple items in a CSV lookup overlap
* fix crash if game is unloaded while activating achievements
# v11.5.0
* add total_entries to rc_api_fetch_leaderboard_info_response_t
* add RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED event

View File

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

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

View File

@ -1434,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) {
@ -1455,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;
@ -1475,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);
@ -1488,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 */
@ -2001,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();
@ -2012,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 */
@ -2615,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;
@ -2643,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);
@ -3528,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;
@ -3679,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));
@ -3745,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;
@ -4179,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;
@ -4334,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));
@ -4377,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);

View File

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

View File

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

View File

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

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

View File

@ -1,29 +0,0 @@
#ifndef RC_VERSION_H
#define RC_VERSION_H
#ifdef __cplusplus
extern "C" {
#endif
#define RCHEEVOS_VERSION_MAJOR 10
#define RCHEEVOS_VERSION_MINOR 7
#define RCHEEVOS_VERSION_PATCH 0
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)
#define RCHEEVOS_VERSION RCHEEVOS_MAKE_VERSION(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR, RCHEEVOS_VERSION_PATCH)
#define RCHEEVOS_MAKE_STRING(num) #num
#define RCHEEVOS_MAKE_VERSION_STRING(major, minor, patch) RCHEEVOS_MAKE_STRING(major) "." RCHEEVOS_MAKE_STRING(minor) "." RCHEEVOS_MAKE_STRING(patch)
#define RCHEEVOS_MAKE_VERSION_STRING_SHORT(major, minor) RCHEEVOS_MAKE_STRING(major) "." RCHEEVOS_MAKE_STRING(minor)
#if RCHEEVOS_VERSION_PATCH > 0
#define RCHEEVOS_VERSION_STRING RCHEEVOS_MAKE_VERSION_STRING(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR, RCHEEVOS_VERSION_PATCH)
#else
#define RCHEEVOS_VERSION_STRING RCHEEVOS_MAKE_VERSION_STRING_SHORT(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR)
#endif
#ifdef __cplusplus
}
#endif
#endif /* RC_VERSION_H */

View File

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

View File

@ -316,7 +316,7 @@ int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* mema
rc_lboard_t* lboard;
rc_parse_state_t parse;
rc_runtime_lboard_t* runtime_lboard;
int32_t size;
int size;
uint32_t i;
if (memaddr == 0)
@ -834,7 +834,7 @@ void rc_runtime_invalidate_address(rc_runtime_t* self, uint32_t address) {
}
}
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
rc_runtime_validate_address_t validate_handler) {
rc_memref_t** last_memref = &self->memrefs;
rc_memref_t* memref = self->memrefs;

View File

@ -3129,6 +3129,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:
@ -3427,6 +3428,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);
@ -3592,6 +3594,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);
}
@ -3744,6 +3747,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':
@ -3827,6 +3834,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;
@ -3928,6 +3936,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':
@ -3966,11 +3978,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"))
@ -3981,6 +3998,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':