diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index ac5a648bf6..469f0349a8 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -218,10 +218,11 @@ uint8_t* rcheevos_patch_address(unsigned address) static unsigned rcheevos_peek(unsigned address, unsigned num_bytes, void* ud) { - uint8_t* data = rc_libretro_memory_find( - &rcheevos_locals.memory, address); + unsigned avail; + uint8_t* data = rc_libretro_memory_find_avail( + &rcheevos_locals.memory, address, &avail); - if (data) + if (data && avail >= num_bytes) { switch (num_bytes) { diff --git a/deps/rcheevos/CHANGELOG.md b/deps/rcheevos/CHANGELOG.md index b40205b3be..150f90e12b 100644 --- a/deps/rcheevos/CHANGELOG.md +++ b/deps/rcheevos/CHANGELOG.md @@ -1,3 +1,14 @@ +# v10.7.0 +* add hash method and memory map for Gamecube +* add console enum, hash method, and memory map for DSi +* add console enum, hash method, and memory map for TI-83 +* add console enum, hash method, and memory map for Uzebox +* add constant for rcheevos version; include in start session server API call +* fix SubSource calculations using float values +* fix game identification for homebrew Jaguar CD games +* fix game identification for CD with many files at root directory +* address _CRT_SECURE_NO_WARNINGS warnings + # v10.6.0 * add RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED * use optimized comparators for most common condition logic diff --git a/deps/rcheevos/include/rc_api_request.h b/deps/rcheevos/include/rc_api_request.h index 8ba482a8fe..64f25ec12a 100644 --- a/deps/rcheevos/include/rc_api_request.h +++ b/deps/rcheevos/include/rc_api_request.h @@ -10,15 +10,25 @@ extern "C" { /** * A block of memory for variable length data (like strings and arrays). */ -typedef struct rc_api_buffer_t { +typedef struct rc_api_buffer_chunk_t { /* The current location where data is being written */ char* write; /* The first byte past the end of data where writing cannot occur */ char* end; + /* The first byte of the data */ + char* start; /* The next block in the allocated memory chain */ - struct rc_api_buffer_t* next; - /* The buffer containing the data. The actual size may be larger than 256 bytes for buffers allocated in - * the next chain. The 256 byte size specified is for the initial allocation within the container object. */ + struct rc_api_buffer_chunk_t* next; +} +rc_api_buffer_chunk_t; + +/** + * A preallocated block of memory for variable length data (like strings and arrays). + */ +typedef struct rc_api_buffer_t { + /* The chunk data (will point at the local data member) */ + struct rc_api_buffer_chunk_t chunk; + /* Small chunk of memory pre-allocated for the chunk */ char data[256]; } rc_api_buffer_t; @@ -41,7 +51,7 @@ rc_api_request_t; * Common attributes for all server responses. */ typedef struct rc_api_response_t { - /* Server-provided success indicator (non-zero on failure) */ + /* Server-provided success indicator (non-zero on success, zero on failure) */ int succeeded; /* Server-provided message associated to the failure */ const char* error_message; diff --git a/deps/rcheevos/include/rc_consoles.h b/deps/rcheevos/include/rc_consoles.h index 689b5886fb..71e67764a3 100644 --- a/deps/rcheevos/include/rc_consoles.h +++ b/deps/rcheevos/include/rc_consoles.h @@ -87,6 +87,9 @@ enum { RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER = 75, RC_CONSOLE_PC_ENGINE_CD = 76, RC_CONSOLE_ATARI_JAGUAR_CD = 77, + RC_CONSOLE_NINTENDO_DSI = 78, + RC_CONSOLE_TI83 = 79, + RC_CONSOLE_UZEBOX = 80, RC_CONSOLE_HUBS = 100, RC_CONSOLE_EVENTS = 101 diff --git a/deps/rcheevos/include/rc_runtime.h b/deps/rcheevos/include/rc_runtime.h index 6ad5f0266a..92b4296351 100644 --- a/deps/rcheevos/include/rc_runtime.h +++ b/deps/rcheevos/include/rc_runtime.h @@ -121,7 +121,8 @@ enum { RC_RUNTIME_EVENT_LBOARD_TRIGGERED, RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED, RC_RUNTIME_EVENT_LBOARD_DISABLED, - RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED + RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED, + RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED }; typedef struct rc_runtime_event_t { diff --git a/deps/rcheevos/src/rapi/rc_api_common.c b/deps/rcheevos/src/rapi/rc_api_common.c index f9a13dbfb5..42fb40527e 100644 --- a/deps/rcheevos/src/rapi/rc_api_common.c +++ b/deps/rcheevos/src/rapi/rc_api_common.c @@ -299,6 +299,8 @@ int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_ #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (!json) @@ -359,6 +361,8 @@ int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator, #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (!field->value_start || *field->value_start != '[') { @@ -453,6 +457,8 @@ int rc_json_get_string(const char** out, rc_api_buffer_t* buffer, const rc_json_ #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (!src) { @@ -564,6 +570,8 @@ int rc_json_get_num(int* out, const rc_json_field_t* field, const char* field_na #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (!src) { @@ -615,6 +623,8 @@ int rc_json_get_unum(unsigned* out, const rc_json_field_t* field, const char* fi #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (!src) { @@ -656,11 +666,13 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (*field->value_start == '\"') { memset(&tm, 0, sizeof(tm)); - if (sscanf(field->value_start + 1, "%d-%d-%d %d:%d:%d", + if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) { tm.tm_mon--; /* 0-based */ tm.tm_year -= 1900; /* 1900 based */ @@ -671,9 +683,11 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* * timezone conversion twice and manually removing the difference */ { time_t local_timet = mktime(&tm); - struct tm* gmt_tm = gmtime(&local_timet); - time_t skewed_timet = mktime(gmt_tm); /* applies local time adjustment second time */ - time_t tz_offset = skewed_timet - local_timet; + time_t skewed_timet, tz_offset; + struct tm gmt_tm; + gmtime_s(&gmt_tm, &local_timet); + skewed_timet = mktime(&gmt_tm); /* applies local time adjustment second time */ + tz_offset = skewed_timet - local_timet; *out = local_timet - tz_offset; } @@ -698,6 +712,8 @@ int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_n #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (src) { @@ -733,31 +749,32 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js /* --- rc_buf --- */ void rc_buf_init(rc_api_buffer_t* buffer) { - buffer->write = &buffer->data[0]; - buffer->end = &buffer->data[sizeof(buffer->data)]; - buffer->next = NULL; + buffer->chunk.write = buffer->chunk.start = &buffer->data[0]; + buffer->chunk.end = &buffer->data[sizeof(buffer->data)]; + buffer->chunk.next = NULL; } void rc_buf_destroy(rc_api_buffer_t* buffer) { + rc_api_buffer_chunk_t *chunk; #ifdef DEBUG_BUFFERS int count = 0; int wasted = 0; int total = 0; #endif - /* first buffer is not allocated */ - buffer = buffer->next; + /* first chunk is not allocated. skip it. */ + chunk = buffer->chunk.next; /* deallocate any additional buffers */ - while (buffer) { - rc_api_buffer_t* next = buffer->next; + while (chunk) { + rc_api_buffer_chunk_t* next = chunk->next; #ifdef DEBUG_BUFFERS - total += (int)(buffer->end - buffer->data); - wasted += (int)(buffer->end - buffer->write); + total += (int)(chunk->end - chunk->data); + wasted += (int)(chunk->end - chunk->write); ++count; #endif - free(buffer); - buffer = next; + free(chunk); + chunk = next; } #ifdef DEBUG_BUFFERS @@ -767,47 +784,50 @@ void rc_buf_destroy(rc_api_buffer_t* buffer) { } char* rc_buf_reserve(rc_api_buffer_t* buffer, size_t amount) { + rc_api_buffer_chunk_t* chunk = &buffer->chunk; size_t remaining; - while (buffer) { - remaining = buffer->end - buffer->write; + while (chunk) { + remaining = chunk->end - chunk->write; if (remaining >= amount) - return buffer->write; + return chunk->write; - if (!buffer->next) { - /* allocate a chunk of memory that is a multiple of 256-bytes. casting it to an rc_api_buffer_t will - * effectively unbound the data field, so use write and end pointers to track how data is being used. + if (!chunk->next) { + /* allocate a chunk of memory that is a multiple of 256-bytes. the first 32 bytes will be associated + * to the chunk header, and the remaining will be used for data. */ - const size_t buffer_prefix_size = sizeof(rc_api_buffer_t) - sizeof(buffer->data); - const size_t alloc_size = (amount + buffer_prefix_size + 0xFF) & ~0xFF; - buffer->next = (rc_api_buffer_t*)malloc(alloc_size); - if (!buffer->next) + const size_t chunk_header_size = sizeof(rc_api_buffer_chunk_t); + const size_t alloc_size = (chunk_header_size + amount + 0xFF) & ~0xFF; + chunk->next = (rc_api_buffer_chunk_t*)malloc(alloc_size); + if (!chunk->next) break; - buffer->next->write = buffer->next->data; - buffer->next->end = buffer->next->write + (alloc_size - buffer_prefix_size); - buffer->next->next = NULL; + chunk->next->start = (char*)chunk->next + chunk_header_size; + chunk->next->write = chunk->next->start; + chunk->next->end = (char*)chunk->next + alloc_size; + chunk->next->next = NULL; } - buffer = buffer->next; + chunk = chunk->next; } return NULL; } void rc_buf_consume(rc_api_buffer_t* buffer, const char* start, char* end) { + rc_api_buffer_chunk_t* chunk = &buffer->chunk; do { - if (buffer->write == start) { - size_t offset = (end - buffer->data); + if (chunk->write == start) { + size_t offset = (end - chunk->start); offset = (offset + 7) & ~7; - buffer->write = &buffer->data[offset]; + chunk->write = &chunk->start[offset]; - if (buffer->write > buffer->end) - buffer->write = buffer->end; + if (chunk->write > chunk->end) + chunk->write = chunk->end; break; } - buffer = buffer->next; - } while (buffer); + chunk = chunk->next; + } while (chunk); } void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount) { @@ -830,13 +850,13 @@ void rc_api_format_md5(char checksum[33], const unsigned char digest[16]) { /* --- rc_url_builder --- */ void rc_url_builder_init(rc_api_url_builder_t* builder, rc_api_buffer_t* buffer, size_t estimated_size) { - rc_api_buffer_t* used_buffer; + rc_api_buffer_chunk_t* used_buffer; memset(builder, 0, sizeof(*builder)); builder->buffer = buffer; builder->write = builder->start = rc_buf_reserve(buffer, estimated_size); - used_buffer = buffer; + used_buffer = &buffer->chunk; while (used_buffer && used_buffer->write != builder->write) used_buffer = used_buffer->next; @@ -859,7 +879,7 @@ static int rc_url_builder_reserve(rc_api_url_builder_t* builder, size_t amount) if (remaining < amount) { const size_t used = builder->write - builder->start; const size_t current_size = builder->end - builder->start; - const size_t buffer_prefix_size = sizeof(rc_api_buffer_t) - sizeof(builder->buffer->data); + const size_t buffer_prefix_size = sizeof(rc_api_buffer_chunk_t); char* new_start; size_t new_size = (current_size < 256) ? 256 : current_size * 2; do { diff --git a/deps/rcheevos/src/rapi/rc_api_common.h b/deps/rcheevos/src/rapi/rc_api_common.h index 9e2dc53b08..fa18b7798e 100644 --- a/deps/rcheevos/src/rapi/rc_api_common.h +++ b/deps/rcheevos/src/rapi/rc_api_common.h @@ -14,6 +14,7 @@ typedef struct rc_api_url_builder_t { char* write; char* start; char* end; + /* pointer to a preallocated rc_api_buffer_t */ rc_api_buffer_t* buffer; int result; } @@ -23,6 +24,8 @@ void rc_url_builder_init(rc_api_url_builder_t* builder, rc_api_buffer_t* buffer, void rc_url_builder_append(rc_api_url_builder_t* builder, const char* data, size_t len); const char* rc_url_builder_finalize(rc_api_url_builder_t* builder); +#define RC_JSON_NEW_FIELD(n) {n,0,0,0} + typedef struct rc_json_field_t { const char* name; const char* value_start; diff --git a/deps/rcheevos/src/rapi/rc_api_info.c b/deps/rcheevos/src/rapi/rc_api_info.c index 7dc758a1c2..774220adcc 100644 --- a/deps/rcheevos/src/rapi/rc_api_info.c +++ b/deps/rcheevos/src/rapi/rc_api_info.c @@ -39,27 +39,27 @@ int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"AchievementID"}, - {"Response"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("AchievementID"), + RC_JSON_NEW_FIELD("Response") /* unused fields - {"Offset"}, - {"Count"}, - {"FriendsOnly"}, + RC_JSON_NEW_FIELD("Offset"), + RC_JSON_NEW_FIELD("Count"), + RC_JSON_NEW_FIELD("FriendsOnly") * unused fields */ }; rc_json_field_t response_fields[] = { - {"NumEarned"}, - {"TotalPlayers"}, - {"GameID"}, - {"RecentWinner"} /* array */ + RC_JSON_NEW_FIELD("NumEarned"), + RC_JSON_NEW_FIELD("TotalPlayers"), + RC_JSON_NEW_FIELD("GameID"), + RC_JSON_NEW_FIELD("RecentWinner") /* array */ }; rc_json_field_t entry_fields[] = { - {"User"}, - {"DateAwarded"} + RC_JSON_NEW_FIELD("User"), + RC_JSON_NEW_FIELD("DateAwarded") }; memset(response, 0, sizeof(*response)); @@ -143,38 +143,38 @@ int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info char format[16]; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"LeaderboardData"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("LeaderboardData") }; rc_json_field_t leaderboarddata_fields[] = { - {"LBID"}, - {"LBFormat"}, - {"LowerIsBetter"}, - {"LBTitle"}, - {"LBDesc"}, - {"LBMem"}, - {"GameID"}, - {"LBAuthor"}, - {"LBCreated"}, - {"LBUpdated"}, - {"Entries"} /* array */ + RC_JSON_NEW_FIELD("LBID"), + RC_JSON_NEW_FIELD("LBFormat"), + RC_JSON_NEW_FIELD("LowerIsBetter"), + RC_JSON_NEW_FIELD("LBTitle"), + RC_JSON_NEW_FIELD("LBDesc"), + RC_JSON_NEW_FIELD("LBMem"), + RC_JSON_NEW_FIELD("GameID"), + RC_JSON_NEW_FIELD("LBAuthor"), + RC_JSON_NEW_FIELD("LBCreated"), + RC_JSON_NEW_FIELD("LBUpdated"), + RC_JSON_NEW_FIELD("Entries") /* array */ /* unused fields - {"GameTitle"}, - {"ConsoleID"}, - {"ConsoleName"}, - {"ForumTopicID"}, - {"GameIcon"}, + RC_JSON_NEW_FIELD("GameTitle"), + RC_JSON_NEW_FIELD("ConsoleID"), + RC_JSON_NEW_FIELD("ConsoleName"), + RC_JSON_NEW_FIELD("ForumTopicID"), + RC_JSON_NEW_FIELD("GameIcon") * unused fields */ }; rc_json_field_t entry_fields[] = { - {"User"}, - {"Rank"}, - {"Index"}, - {"Score"}, - {"DateSubmitted"} + RC_JSON_NEW_FIELD("User"), + RC_JSON_NEW_FIELD("Rank"), + RC_JSON_NEW_FIELD("Index"), + RC_JSON_NEW_FIELD("Score"), + RC_JSON_NEW_FIELD("DateSubmitted") }; memset(response, 0, sizeof(*response)); @@ -281,9 +281,9 @@ int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* char* end; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"Response"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Response") }; memset(response, 0, sizeof(*response)); diff --git a/deps/rcheevos/src/rapi/rc_api_runtime.c b/deps/rcheevos/src/rapi/rc_api_runtime.c index e42db3cf16..43e72b7934 100644 --- a/deps/rcheevos/src/rapi/rc_api_runtime.c +++ b/deps/rcheevos/src/rapi/rc_api_runtime.c @@ -31,9 +31,9 @@ int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_res int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"GameID"}, + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("GameID") }; memset(response, 0, sizeof(*response)); @@ -76,6 +76,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r rc_json_field_t iterator; const char* str; const char* last_author = ""; + const char* last_author_field = ""; size_t last_author_len = 0; size_t len; unsigned timet; @@ -83,46 +84,46 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r char format[16]; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"PatchData"} /* nested object */ + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("PatchData") /* nested object */ }; rc_json_field_t patchdata_fields[] = { - {"ID"}, - {"Title"}, - {"ConsoleID"}, - {"ImageIcon"}, - {"RichPresencePatch"}, - {"Achievements"}, /* array */ - {"Leaderboards"} /* array */ + RC_JSON_NEW_FIELD("ID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("ConsoleID"), + RC_JSON_NEW_FIELD("ImageIcon"), + RC_JSON_NEW_FIELD("RichPresencePatch"), + RC_JSON_NEW_FIELD("Achievements"), /* array */ + RC_JSON_NEW_FIELD("Leaderboards") /* array */ /* unused fields - {"ForumTopicID"}, - {"Flags"}, + RC_JSON_NEW_FIELD("ForumTopicID"), + RC_JSON_NEW_FIELD("Flags") * unused fields */ }; rc_json_field_t achievement_fields[] = { - {"ID"}, - {"Title"}, - {"Description"}, - {"Flags"}, - {"Points"}, - {"MemAddr"}, - {"Author"}, - {"BadgeName"}, - {"Created"}, - {"Modified"} + RC_JSON_NEW_FIELD("ID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("Description"), + RC_JSON_NEW_FIELD("Flags"), + RC_JSON_NEW_FIELD("Points"), + RC_JSON_NEW_FIELD("MemAddr"), + RC_JSON_NEW_FIELD("Author"), + RC_JSON_NEW_FIELD("BadgeName"), + RC_JSON_NEW_FIELD("Created"), + RC_JSON_NEW_FIELD("Modified") }; rc_json_field_t leaderboard_fields[] = { - {"ID"}, - {"Title"}, - {"Description"}, - {"Mem"}, - {"Format"}, - {"LowerIsBetter"}, - {"Hidden"} + RC_JSON_NEW_FIELD("ID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("Description"), + RC_JSON_NEW_FIELD("Mem"), + RC_JSON_NEW_FIELD("Format"), + RC_JSON_NEW_FIELD("LowerIsBetter"), + RC_JSON_NEW_FIELD("Hidden") }; memset(response, 0, sizeof(*response)); @@ -199,8 +200,8 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName")) return RC_MISSING_VALUE; - len = achievement_fields[7].value_end - achievement_fields[7].value_start; - if (len == last_author_len && memcmp(achievement_fields[7].value_start, last_author, len) == 0) { + len = achievement_fields[6].value_end - achievement_fields[6].value_start; + if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) { achievement->author = last_author; } else { @@ -208,6 +209,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r return RC_MISSING_VALUE; last_author = achievement->author; + last_author_field = achievement_fields[6].value_start; last_author_len = len; } @@ -291,8 +293,8 @@ int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_reques int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response) { rc_json_field_t fields[] = { - {"Success"}, - {"Error"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error") }; memset(response, 0, sizeof(*response)); @@ -345,11 +347,11 @@ int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_ap int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"Score"}, - {"AchievementID"}, - {"AchievementsRemaining"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Score"), + RC_JSON_NEW_FIELD("AchievementID"), + RC_JSON_NEW_FIELD("AchievementsRemaining") }; memset(response, 0, sizeof(*response)); @@ -428,48 +430,48 @@ int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_respo int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"Response"} /* nested object */ + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Response") /* nested object */ }; rc_json_field_t response_fields[] = { - {"Score"}, - {"BestScore"}, - {"RankInfo"}, /* nested object */ - {"TopEntries"} /* array */ + RC_JSON_NEW_FIELD("Score"), + RC_JSON_NEW_FIELD("BestScore"), + RC_JSON_NEW_FIELD("RankInfo"), /* nested object */ + RC_JSON_NEW_FIELD("TopEntries") /* array */ /* unused fields - {"LBData"}, / * array * / - {"ScoreFormatted"}, - {"TopEntriesFriends"}, / * array * / + RC_JSON_NEW_FIELD("LBData"), / * array * / + RC_JSON_NEW_FIELD("ScoreFormatted"), + RC_JSON_NEW_FIELD("TopEntriesFriends") / * array * / * unused fields */ }; /* unused fields rc_json_field_t lbdata_fields[] = { - {"Format"}, - {"LeaderboardID"}, - {"GameID"}, - {"Title"}, - {"LowerIsBetter"} + RC_JSON_NEW_FIELD("Format"), + RC_JSON_NEW_FIELD("LeaderboardID"), + RC_JSON_NEW_FIELD("GameID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("LowerIsBetter") }; * unused fields */ rc_json_field_t entry_fields[] = { - {"User"}, - {"Rank"}, - {"Score"} + RC_JSON_NEW_FIELD("User"), + RC_JSON_NEW_FIELD("Rank"), + RC_JSON_NEW_FIELD("Score") /* unused fields - {"DateSubmitted"}, + RC_JSON_NEW_FIELD("DateSubmitted") * unused fields */ }; rc_json_field_t rank_info_fields[] = { - {"Rank"}, - {"NumEntries"} + RC_JSON_NEW_FIELD("Rank"), + RC_JSON_NEW_FIELD("NumEntries") /* unused fields - {"LowerIsBetter"}, - {"UserRank"}, + RC_JSON_NEW_FIELD("LowerIsBetter"), + RC_JSON_NEW_FIELD("UserRank") * unused fields */ }; diff --git a/deps/rcheevos/src/rapi/rc_api_user.c b/deps/rcheevos/src/rapi/rc_api_user.c index e5181ba950..546a8a0578 100644 --- a/deps/rcheevos/src/rapi/rc_api_user.c +++ b/deps/rcheevos/src/rapi/rc_api_user.c @@ -1,6 +1,8 @@ #include "rc_api_user.h" #include "rc_api_common.h" +#include "../rcheevos/rc_version.h" + #include /* --- Login --- */ @@ -32,13 +34,13 @@ int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_requ int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"User"}, - {"Token"}, - {"Score"}, - {"Messages"}, - {"DisplayName"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("User"), + RC_JSON_NEW_FIELD("Token"), + RC_JSON_NEW_FIELD("Score"), + RC_JSON_NEW_FIELD("Messages"), + RC_JSON_NEW_FIELD("DisplayName") }; memset(response, 0, sizeof(*response)); @@ -86,6 +88,7 @@ int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_st */ rc_url_builder_append_unum_param(&builder, "a", 3); rc_url_builder_append_unum_param(&builder, "m", api_params->game_id); + rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING); request->post_data = rc_url_builder_finalize(&builder); } @@ -94,8 +97,8 @@ int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_st int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response) { rc_json_field_t fields[] = { - {"Success"}, - {"Error"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error") }; memset(response, 0, sizeof(*response)); @@ -128,12 +131,12 @@ int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_a int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"UserUnlocks"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("UserUnlocks") /* unused fields - { "GameID" }, - { "HardcoreMode" } + RC_JSON_NEW_FIELD("GameID"), + RC_JSON_NEW_FIELD("HardcoreMode") * unused fields */ }; diff --git a/deps/rcheevos/src/rcheevos/compat.c b/deps/rcheevos/src/rcheevos/compat.c index 877c64ddb2..ce635fc1ee 100644 --- a/deps/rcheevos/src/rcheevos/compat.c +++ b/deps/rcheevos/src/rcheevos/compat.c @@ -3,6 +3,8 @@ #include #include +#ifdef RC_C89_HELPERS + int rc_strncasecmp(const char* left, const char* right, size_t length) { while (length) @@ -44,20 +46,40 @@ char* rc_strdup(const char* str) { const size_t length = strlen(str); char* buffer = (char*)malloc(length + 1); - memcpy(buffer, str, length + 1); + if (buffer) + memcpy(buffer, str, length + 1); return buffer; } int rc_snprintf(char* buffer, size_t size, const char* format, ...) { - int result; - va_list args; + int result; + va_list args; - va_start(args, format); - /* assume buffer is large enough and ignore size */ - (void)size; - result = vsprintf(buffer, format, args); - va_end(args); + va_start(args, format); - return result; +#ifdef __STDC_WANT_SECURE_LIB__ + result = vsprintf_s(buffer, size, format, args); +#else + /* assume buffer is large enough and ignore size */ + (void)size; + result = vsprintf(buffer, format, args); +#endif + + va_end(args); + + return result; } + +#endif + +#ifndef __STDC_WANT_SECURE_LIB__ + +struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer) +{ + struct tm* tm = gmtime(timer); + memcpy(buf, tm, sizeof(*tm)); + return buf; +} + +#endif diff --git a/deps/rcheevos/src/rcheevos/condset.c b/deps/rcheevos/src/rcheevos/condset.c index 4f46e29e03..e77a235815 100644 --- a/deps/rcheevos/src/rcheevos/condset.c +++ b/deps/rcheevos/src/rcheevos/condset.c @@ -210,8 +210,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc case RC_CONDITION_SUB_SOURCE: rc_evaluate_condition_value(&value, condition, eval_state); - rc_typed_value_convert(&value, RC_VALUE_TYPE_SIGNED); - value.value.i32 = -value.value.i32; + rc_typed_value_negate(&value); rc_typed_value_add(&eval_state->add_value, &value); eval_state->add_address = 0; continue; diff --git a/deps/rcheevos/src/rcheevos/consoleinfo.c b/deps/rcheevos/src/rcheevos/consoleinfo.c index dde26c7377..10905ad902 100644 --- a/deps/rcheevos/src/rcheevos/consoleinfo.c +++ b/deps/rcheevos/src/rcheevos/consoleinfo.c @@ -135,6 +135,9 @@ const char* rc_console_name(int console_id) case RC_CONSOLE_NINTENDO_DS: return "Nintendo DS"; + case RC_CONSOLE_NINTENDO_DSI: + return "Nintendo DSi"; + case RC_CONSOLE_NINTENDO_3DS: return "Nintendo 3DS"; @@ -204,9 +207,15 @@ const char* rc_console_name(int console_id) case RC_CONSOLE_THOMSONTO8: return "Thomson TO8"; + case RC_CONSOLE_TI83: + return "TI-83"; + case RC_CONSOLE_TIC80: return "TIC-80"; + case RC_CONSOLE_UZEBOX: + return "Uzebox"; + case RC_CONSOLE_VECTREX: return "Vectrex"; @@ -441,6 +450,13 @@ static const rc_memory_region_t _rc_memory_regions_gameboy_advance[] = { }; static const rc_memory_regions_t rc_memory_regions_gameboy_advance = { _rc_memory_regions_gameboy_advance, 2 }; +/* ===== GameCube ===== */ +/* https://wiibrew.org/wiki/Memory_map */ +static const rc_memory_region_t _rc_memory_regions_gamecube[] = { + { 0x00000000U, 0x017FFFFF, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_gamecube = { _rc_memory_regions_gamecube, 1 }; + /* ===== Game Gear ===== */ /* http://www.smspower.org/Development/MemoryMap */ static const rc_memory_region_t _rc_memory_regions_game_gear[] = { @@ -631,6 +647,13 @@ static const rc_memory_region_t _rc_memory_regions_nintendo_ds[] = { }; static const rc_memory_regions_t rc_memory_regions_nintendo_ds = { _rc_memory_regions_nintendo_ds, 1 }; +/* ===== 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" } +}; +static const rc_memory_regions_t rc_memory_regions_nintendo_dsi = { _rc_memory_regions_nintendo_dsi, 1 }; + /* ===== Oric ===== */ static const rc_memory_region_t _rc_memory_regions_oric[] = { /* actual size depends on machine type - up to 64KB */ @@ -762,6 +785,13 @@ static const rc_memory_region_t _rc_memory_regions_thomson_to8[] = { }; static const rc_memory_regions_t rc_memory_regions_thomson_to8 = { _rc_memory_regions_thomson_to8, 1 }; +/* ===== TI-83 ===== */ +/* https://tutorials.eeems.ca/ASMin28Days/lesson/day03.html#mem */ +static const rc_memory_region_t _rc_memory_regions_ti83[] = { + { 0x000000U, 0x007FFFU, 0x008000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, +}; +static const rc_memory_regions_t rc_memory_regions_ti83 = { _rc_memory_regions_ti83, 1 }; + /* ===== TIC-80 ===== */ /* https://github.com/nesbox/TIC-80/wiki/RAM */ static const rc_memory_region_t _rc_memory_regions_tic80[] = { @@ -778,6 +808,13 @@ static const rc_memory_region_t _rc_memory_regions_tic80[] = { }; static const rc_memory_regions_t rc_memory_regions_tic80 = { _rc_memory_regions_tic80, 10 }; +/* ===== Uzebox ===== */ +/* https://uzebox.org/index.php */ +static const rc_memory_region_t _rc_memory_regions_uzebox[] = { + { 0x000000U, 0x000FFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_uzebox = { _rc_memory_regions_uzebox, 1 }; + /* ===== Vectrex ===== */ /* https://roadsidethoughts.com/vectrex/vectrex-memory-map.htm */ static const rc_memory_region_t _rc_memory_regions_vectrex[] = { @@ -812,6 +849,14 @@ static const rc_memory_region_t _rc_memory_regions_wasm4[] = { }; static const rc_memory_regions_t rc_memory_regions_wasm4 = { _rc_memory_regions_wasm4, 1 }; +/* ===== Wii ===== */ +/* https://wiibrew.org/wiki/Memory_map */ +static const rc_memory_region_t _rc_memory_regions_wii[] = { + { 0x00000000U, 0x017FFFFF, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x01800000U, 0x057FFFFF, 0x90000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_wii = { _rc_memory_regions_wii, 2 }; + /* ===== WonderSwan ===== */ /* http://daifukkat.su/docs/wsman/#ovr_memmap */ static const rc_memory_region_t _rc_memory_regions_wonderswan[] = { @@ -892,6 +937,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_GAMEBOY_ADVANCE: return &rc_memory_regions_gameboy_advance; + case RC_CONSOLE_GAMECUBE: + return &rc_memory_regions_gamecube; + case RC_CONSOLE_GAME_GEAR: return &rc_memory_regions_game_gear; @@ -931,6 +979,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_NINTENDO_DS: return &rc_memory_regions_nintendo_ds; + case RC_CONSOLE_NINTENDO_DSI: + return &rc_memory_regions_nintendo_dsi; + case RC_CONSOLE_ORIC: return &rc_memory_regions_oric; @@ -979,9 +1030,15 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_THOMSONTO8: return &rc_memory_regions_thomson_to8; + case RC_CONSOLE_TI83: + return &rc_memory_regions_ti83; + case RC_CONSOLE_TIC80: return &rc_memory_regions_tic80; + case RC_CONSOLE_UZEBOX: + return &rc_memory_regions_uzebox; + case RC_CONSOLE_VECTREX: return &rc_memory_regions_vectrex; @@ -991,6 +1048,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_WASM4: return &rc_memory_regions_wasm4; + case RC_CONSOLE_WII: + return &rc_memory_regions_wii; + case RC_CONSOLE_WONDERSWAN: return &rc_memory_regions_wonderswan; diff --git a/deps/rcheevos/src/rcheevos/operand.c b/deps/rcheevos/src/rcheevos/operand.c index 0cd18d0560..f8abbbd7ba 100644 --- a/deps/rcheevos/src/rcheevos/operand.c +++ b/deps/rcheevos/src/rcheevos/operand.c @@ -59,6 +59,8 @@ static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_par self->value.luafunc = luaL_ref(parse->L, LUA_REGISTRYINDEX); } +#else + (void)parse; #endif /* RC_DISABLE_LUA */ self->type = RC_OPERAND_LUA; diff --git a/deps/rcheevos/src/rcheevos/rc_compat.h b/deps/rcheevos/src/rcheevos/rc_compat.h index 62c362f47e..f396726db5 100644 --- a/deps/rcheevos/src/rcheevos/rc_compat.h +++ b/deps/rcheevos/src/rcheevos/rc_compat.h @@ -30,6 +30,7 @@ extern "C" { #elif __STDC_VERSION__ < 199901L /* C89 redefinitions */ +#define RC_C89_HELPERS 1 #ifndef snprintf extern int rc_snprintf(char* buffer, size_t size, const char* format, ...); @@ -53,6 +54,17 @@ extern "C" { #endif /* __STDC_VERSION__ < 199901L */ +#ifndef __STDC_WANT_SECURE_LIB__ + /* _CRT_SECURE_NO_WARNINGS redefinitions */ + #define strcpy_s(dest, sz, src) strcpy(dest, src) + #define sscanf_s sscanf + + /* NOTE: Microsoft secure gmtime_s parameter order differs from C11 standard */ + #include + extern struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer); + #define gmtime_s rc_gmtime_s +#endif + #ifdef __cplusplus } #endif diff --git a/deps/rcheevos/src/rcheevos/rc_internal.h b/deps/rcheevos/src/rcheevos/rc_internal.h index 66a6aebe6a..7f80d179ab 100644 --- a/deps/rcheevos/src/rcheevos/rc_internal.h +++ b/deps/rcheevos/src/rcheevos/rc_internal.h @@ -83,6 +83,8 @@ typedef struct { } rc_typed_value_t; +#define RC_MEASURED_UNKNOWN 0xFFFFFFFF + typedef struct { rc_typed_value_t add_value;/* AddSource/SubSource */ int add_hits; /* AddHits */ @@ -182,6 +184,7 @@ void rc_typed_value_convert(rc_typed_value_t* value, char new_type); void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount); void rc_typed_value_multiply(rc_typed_value_t* value, const rc_typed_value_t* amount); void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amount); +void rc_typed_value_negate(rc_typed_value_t* value); int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper); void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref); diff --git a/deps/rcheevos/src/rcheevos/rc_libretro.c b/deps/rcheevos/src/rcheevos/rc_libretro.c index 440d63fca4..541f62a4ba 100644 --- a/deps/rcheevos/src/rcheevos/rc_libretro.c +++ b/deps/rcheevos/src/rcheevos/rc_libretro.c @@ -288,7 +288,7 @@ int rc_libretro_is_system_allowed(const char* library_name, int console_id) { return 1; } -unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address) { +unsigned char* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, unsigned address, unsigned* avail) { unsigned i; for (i = 0; i < regions->count; ++i) { @@ -297,15 +297,25 @@ unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regio if (regions->data[i] == NULL) break; + if (avail) + *avail = (unsigned)(size - address); + return ®ions->data[i][address]; } address -= (unsigned)size; } + if (avail) + *avail = 0; + return NULL; } +unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address) { + return rc_libretro_memory_find_avail(regions, address, NULL); +} + void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback) { rc_libretro_verbose_message_callback = callback; } diff --git a/deps/rcheevos/src/rcheevos/rc_libretro.h b/deps/rcheevos/src/rcheevos/rc_libretro.h index 553bcc5b53..642e3cbdc0 100644 --- a/deps/rcheevos/src/rcheevos/rc_libretro.h +++ b/deps/rcheevos/src/rcheevos/rc_libretro.h @@ -53,6 +53,7 @@ int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions); unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address); +unsigned char* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, unsigned address, unsigned* avail); /*****************************************************************************\ | Disk Identification | diff --git a/deps/rcheevos/src/rcheevos/rc_version.h b/deps/rcheevos/src/rcheevos/rc_version.h new file mode 100644 index 0000000000..cdf857d5f5 --- /dev/null +++ b/deps/rcheevos/src/rcheevos/rc_version.h @@ -0,0 +1,29 @@ +#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 */ diff --git a/deps/rcheevos/src/rcheevos/runtime.c b/deps/rcheevos/src/rcheevos/runtime.c index 32f079a5bc..b5b5569270 100644 --- a/deps/rcheevos/src/rcheevos/runtime.c +++ b/deps/rcheevos/src/rcheevos/runtime.c @@ -231,7 +231,7 @@ int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id } if (rc_trigger_state_active(trigger->state)) { - *measured_value = trigger->measured_value; + *measured_value = (trigger->measured_value == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value; *measured_target = trigger->measured_target; } else { @@ -257,7 +257,7 @@ int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, unsigned } /* cap the value at the target so we can count past the target: "107 >= 100" */ - value = trigger->measured_value; + value = (trigger->measured_value == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value; if (value > trigger->measured_target) value = trigger->measured_target; @@ -534,6 +534,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha for (i = self->trigger_count - 1; i >= 0; --i) { rc_trigger_t* trigger = self->triggers[i].trigger; int old_state, new_state; + unsigned old_measured_value; if (!trigger) continue; @@ -552,13 +553,13 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha continue; } + old_measured_value = trigger->measured_value; old_state = trigger->state; new_state = rc_evaluate_trigger(trigger, peek, ud, L); - /* the trigger state doesn't actually change to RESET, RESET just serves as a notification. + /* trigger->state doesn't actually change to RESET, RESET just serves as a notification. * handle the notification, then look at the actual state */ - if (new_state == RC_TRIGGER_STATE_RESET) - { + if (new_state == RC_TRIGGER_STATE_RESET) { runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET; runtime_event.id = self->triggers[i].id; event_handler(&runtime_event); @@ -566,6 +567,32 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha new_state = trigger->state; } + /* if the measured value changed and the achievement hasn't triggered, send a notification */ + if (trigger->measured_value != old_measured_value && old_measured_value != RC_MEASURED_UNKNOWN && + trigger->measured_target != 0 && trigger->measured_value <= trigger->measured_target && + new_state != RC_TRIGGER_STATE_TRIGGERED && + new_state != RC_TRIGGER_STATE_INACTIVE && new_state != RC_TRIGGER_STATE_WAITING) { + + runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED; + runtime_event.id = self->triggers[i].id; + + if (trigger->measured_as_percent) { + /* if reporting measured value as a percentage, only send the notification if the percentage changes */ + unsigned old_percent = (unsigned)(((unsigned long long)old_measured_value * 100) / trigger->measured_target); + unsigned new_percent = (unsigned)(((unsigned long long)trigger->measured_value * 100) / trigger->measured_target); + if (old_percent != new_percent) { + runtime_event.value = new_percent; + event_handler(&runtime_event); + } + } + else { + runtime_event.value = trigger->measured_value; + event_handler(&runtime_event); + } + + runtime_event.value = 0; /* achievement loop expects this to stay at 0 */ + } + /* if the state hasn't changed, there won't be any events raised */ if (new_state == old_state) continue; diff --git a/deps/rcheevos/src/rcheevos/trigger.c b/deps/rcheevos/src/rcheevos/trigger.c index f310d29f3e..6061ab8ee1 100644 --- a/deps/rcheevos/src/rcheevos/trigger.c +++ b/deps/rcheevos/src/rcheevos/trigger.c @@ -42,8 +42,8 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars *next = 0; *memaddr = aux; - self->measured_value = 0; self->measured_target = parse->measured_target; + self->measured_value = parse->measured_target ? RC_MEASURED_UNKNOWN : 0; self->measured_as_percent = parse->measured_as_percent; self->state = RC_TRIGGER_STATE_WAITING; self->has_hits = 0; @@ -283,6 +283,9 @@ void rc_reset_trigger(rc_trigger_t* self) { rc_reset_trigger_hitcounts(self); self->state = RC_TRIGGER_STATE_WAITING; - self->measured_value = 0; + + if (self->measured_target) + self->measured_value = RC_MEASURED_UNKNOWN; + self->has_hits = 0; } diff --git a/deps/rcheevos/src/rcheevos/value.c b/deps/rcheevos/src/rcheevos/value.c index d9078f6e32..9922231dc3 100644 --- a/deps/rcheevos/src/rcheevos/value.c +++ b/deps/rcheevos/src/rcheevos/value.c @@ -428,6 +428,26 @@ static rc_typed_value_t* rc_typed_value_convert_into(rc_typed_value_t* dest, con return dest; } +void rc_typed_value_negate(rc_typed_value_t* value) { + switch (value->type) + { + case RC_VALUE_TYPE_UNSIGNED: + rc_typed_value_convert(value, RC_VALUE_TYPE_SIGNED); + /* fallthrough to RC_VALUE_TYPE_SIGNED */ + + case RC_VALUE_TYPE_SIGNED: + value->value.i32 = -(value->value.i32); + break; + + case RC_VALUE_TYPE_FLOAT: + value->value.f32 = -(value->value.f32); + break; + + default: + break; + } +} + void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount) { rc_typed_value_t converted; diff --git a/deps/rcheevos/src/rhash/cdreader.c b/deps/rcheevos/src/rhash/cdreader.c index 84bdca72e6..c0f5c88a2b 100644 --- a/deps/rcheevos/src/rhash/cdreader.c +++ b/deps/rcheevos/src/rhash/cdreader.c @@ -124,6 +124,8 @@ static void* cdreader_open_bin_track(const char* path, uint32_t track) return NULL; cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom)); + if (!cdrom) + return NULL; cdrom->file_handle = file_handle; #ifndef NDEBUG cdrom->track_id = track; @@ -343,7 +345,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track) ++ptr; /* convert mm:ss:ff to sector count */ - sscanf(ptr, "%d:%d:%d", &m, &s, &f); + sscanf_s(ptr, "%d:%d:%d", &m, &s, &f); sector_offset = ((m * 60) + s) * 75 + f; if (current_track.first_sector == -1) @@ -719,8 +721,8 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track) largest_track_size = track_size; largest_track = current_track; largest_track_lba = lba; - strcpy(largest_track_file, file); - strcpy(largest_track_sector_size, sector_size); + strcpy_s(largest_track_file, sizeof(largest_track_file), file); + strcpy_s(largest_track_sector_size, sizeof(largest_track_sector_size), sector_size); } } } @@ -748,8 +750,8 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track) if (largest_track != 0 && largest_track != current_track) { current_track = largest_track; - strcpy(file, largest_track_file); - strcpy(sector_size, largest_track_sector_size); + strcpy_s(file, sizeof(file), largest_track_file); + strcpy_s(sector_size, sizeof(sector_size), largest_track_sector_size); lba = largest_track_lba; } @@ -869,7 +871,7 @@ void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader) cdreader->first_track_sector = cdreader_first_track_sector; } -void rc_hash_init_default_cdreader() +void rc_hash_init_default_cdreader(void) { struct rc_hash_cdreader cdreader; rc_hash_get_default_cdreader(&cdreader); diff --git a/deps/rcheevos/src/rhash/hash.c b/deps/rcheevos/src/rhash/hash.c index f8ee4fceb2..72d7692b05 100644 --- a/deps/rcheevos/src/rhash/hash.c +++ b/deps/rcheevos/src/rhash/hash.c @@ -48,7 +48,13 @@ static struct rc_hash_filereader* filereader = NULL; static void* filereader_open(const char* path) { +#if defined(__STDC_WANT_SECURE_LIB__) + FILE* fp; + fopen_s(&fp, path, "rb"); + return fp; +#else return fopen(path, "rb"); +#endif } static void filereader_seek(void* file_handle, int64_t offset, int origin) @@ -268,7 +274,7 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns if (logical_block_size == 0) { num_sectors = 1; } else { - num_sectors = (buffer[156 + 10] | (buffer[156 + 11] << 8)); /* length of section */ + num_sectors = (buffer[156 + 10] | (buffer[156 + 11] << 8) | (buffer[156 + 12] << 16) | (buffer[156 + 13] << 24)); /* length of section */ num_sectors /= logical_block_size; } } @@ -278,9 +284,9 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns return 0; tmp = buffer; - while (tmp < buffer + sizeof(buffer)) + do { - if (!*tmp) + if (tmp >= buffer + sizeof(buffer) || !*tmp) { /* end of this path table block. if the path table spans multiple sectors, keep scanning */ if (num_sectors > 1) @@ -316,7 +322,7 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns /* the first byte of the record is the length of the record */ tmp += *tmp; - } + } while (1); return 0; } @@ -744,13 +750,16 @@ static int rc_hash_text(char hash[33], const uint8_t* buffer, size_t buffer_size return rc_hash_finalize(&md5, hash); } +/* helper variable only used for testing */ +const char* _rc_hash_jaguar_cd_homebrew_hash = NULL; + static int rc_hash_jaguar_cd(char hash[33], const char* path) { uint8_t buffer[2352]; char message[128]; void* track_handle; md5_state_t md5; - int byteswapped; + int byteswapped = 0; unsigned size = 0; unsigned offset = 0; unsigned sector = 0; @@ -795,40 +804,86 @@ static int rc_hash_jaguar_cd(char hash[33], const char* path) return rc_hash_error("Not a Jaguar CD"); } - md5_init(&md5); - - offset += 4; - - if (verbose_message_callback) - { - snprintf(message, sizeof(message), "Hashing boot executable (%u bytes starting at %u bytes into sector %u)", size, offset, sector); - rc_hash_verbose(message); - } - + i = 0; /* only loop once */ do { - if (byteswapped) - rc_hash_byteswap16(buffer, &buffer[sizeof(buffer)]); + md5_init(&md5); - remaining = sizeof(buffer) - offset; - if (remaining >= size) + offset += 4; + + if (verbose_message_callback) { - md5_append(&md5, &buffer[offset], size); - size = 0; - break; + snprintf(message, sizeof(message), "Hashing boot executable (%u bytes starting at %u bytes into sector %u)", size, offset, sector); + rc_hash_verbose(message); } - md5_append(&md5, &buffer[offset], remaining); - size -= remaining; - offset = 0; - } while (rc_cd_read_sector(track_handle, ++sector, buffer, sizeof(buffer)) == sizeof(buffer)); + if (size > MAX_BUFFER_SIZE) + size = MAX_BUFFER_SIZE; - rc_cd_close_track(track_handle); + do + { + if (byteswapped) + rc_hash_byteswap16(buffer, &buffer[sizeof(buffer)]); - if (size > 0) - return rc_hash_error("Not enough data"); + remaining = sizeof(buffer) - offset; + if (remaining >= size) + { + md5_append(&md5, &buffer[offset], size); + size = 0; + break; + } - return rc_hash_finalize(&md5, hash); + md5_append(&md5, &buffer[offset], remaining); + size -= remaining; + offset = 0; + } while (rc_cd_read_sector(track_handle, ++sector, buffer, sizeof(buffer)) == sizeof(buffer)); + + rc_cd_close_track(track_handle); + + if (size > 0) + return rc_hash_error("Not enough data"); + + rc_hash_finalize(&md5, hash); + + /* homebrew games all seem to have the same boot executable and store the actual game code in track 2. + * if we generated something other than the homebrew hash, return it. assume all homebrews are byteswapped. */ + if (strcmp(hash, "254487b59ab21bc005338e85cbf9fd2f") != 0 || !byteswapped) { + if (_rc_hash_jaguar_cd_homebrew_hash == NULL || strcmp(hash, _rc_hash_jaguar_cd_homebrew_hash) != 0) + return 1; + } + + /* if we've already been through the loop a second time, just return the hash */ + if (i == 1) + return 1; + ++i; + + if (verbose_message_callback) + { + snprintf(message, sizeof(message), "Potential homebrew at sector %u, checking for KART data in track 2", sector); + rc_hash_verbose(message); + } + + track_handle = rc_cd_open_track(path, 2); + if (!track_handle) + return rc_hash_error("Could not open track"); + + /* track 2 of the homebrew code has the 64 bytes or ATRI followed by 32 bytes of "ATARI APPROVED DATA HEADER ATRI!", + * then 64 bytes of KART repeating. */ + sector = rc_cd_first_track_sector(track_handle); + rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); + if (memcmp(&buffer[0x5E], "RT!IRTKA", 8) != 0) + return rc_hash_error("Homebrew executable not found in track 2"); + + /* found KART data*/ + if (verbose_message_callback) + { + snprintf(message, sizeof(message), "Found KART data in track 2"); + rc_hash_verbose(message); + } + + offset = 0xA6; + size = (buffer[offset] << 16) | (buffer[offset + 1] << 24) | (buffer[offset + 2]) | (buffer[offset + 3] << 8); + } while (1); } static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size) @@ -883,7 +938,7 @@ static int rc_hash_neogeo_cd(char hash[33], const char* path) while (*ptr && *ptr != '.') ++ptr; - if (memcmp(ptr, ".PRG", 4) == 0) + if (strncasecmp(ptr, ".PRG", 4) == 0) { ptr += 4; *ptr++ = '\0'; @@ -1143,6 +1198,119 @@ static int rc_hash_nintendo_ds(char hash[33], const char* path) return rc_hash_finalize(&md5, hash); } +static int rc_hash_gamecube(char hash[33], const char* path) +{ + md5_state_t md5; + void* file_handle; + const uint32_t BASE_HEADER_SIZE = 0x2440; + const uint32_t MAX_HEADER_SIZE = 1024 * 1024; + uint32_t apploader_header_size, apploader_body_size, apploader_trailer_size, header_size; + uint8_t quad_buffer[4]; + uint8_t addr_buffer[0xD8]; + uint8_t* buffer; + uint32_t dol_offset; + uint32_t dol_offsets[18]; + uint32_t dol_sizes[18]; + uint32_t dol_buf_size = 0; + uint32_t ix; + + file_handle = rc_file_open(path); + + /* Verify Gamecube */ + rc_file_seek(file_handle, 0x1c, SEEK_SET); + rc_file_read(file_handle, quad_buffer, 4); + if (quad_buffer[0] != 0xC2|| quad_buffer[1] != 0x33 || quad_buffer[2] != 0x9F || quad_buffer[3] != 0x3D) + { + rc_file_close(file_handle); + return rc_hash_error("Not a Gamecube disc"); + } + + /* GetApploaderSize */ + rc_file_seek(file_handle, BASE_HEADER_SIZE + 0x14, SEEK_SET); + apploader_header_size = 0x20; + rc_file_read(file_handle, quad_buffer, 4); + apploader_body_size = + (quad_buffer[0] << 24) | (quad_buffer[1] << 16) | (quad_buffer[2] << 8) | quad_buffer[3]; + rc_file_read(file_handle, quad_buffer, 4); + apploader_trailer_size = + (quad_buffer[0] << 24) | (quad_buffer[1] << 16) | (quad_buffer[2] << 8) | quad_buffer[3]; + header_size = BASE_HEADER_SIZE + apploader_header_size + apploader_body_size + apploader_trailer_size; + if (header_size > MAX_HEADER_SIZE) header_size = MAX_HEADER_SIZE; + + /* Hash headers */ + buffer = (uint8_t*)malloc(header_size); + if (!buffer) + { + rc_file_close(file_handle); + return rc_hash_error("Could not allocate temporary buffer"); + } + rc_file_seek(file_handle, 0, SEEK_SET); + rc_file_read(file_handle, buffer, header_size); + md5_init(&md5); + if (verbose_message_callback) + { + char message[128]; + snprintf(message, sizeof(message), "Hashing %u byte header", header_size); + verbose_message_callback(message); + } + md5_append(&md5, buffer, header_size); + + /* GetBootDOLOffset + * Base header size is guaranteed larger than 0x423 therefore buffer contains dol_offset right now + */ + dol_offset = (buffer[0x420] << 24) | (buffer[0x421] << 16) | (buffer[0x422] << 8) | buffer[0x423]; + free(buffer); + + /* Find offsetsand sizes for the 7 main.dol code segments and 11 main.dol data segments */ + rc_file_seek(file_handle, dol_offset, SEEK_SET); + rc_file_read(file_handle, addr_buffer, 0xD8); + for (ix = 0; ix < 18; ix++) + { + dol_offsets[ix] = + (addr_buffer[0x0 + ix * 4] << 24) | + (addr_buffer[0x1 + ix * 4] << 16) | + (addr_buffer[0x2 + ix * 4] << 8) | + addr_buffer[0x3 + ix * 4]; + dol_sizes[ix] = + (addr_buffer[0x90 + ix * 4] << 24) | + (addr_buffer[0x91 + ix * 4] << 16) | + (addr_buffer[0x92 + ix * 4] << 8) | + addr_buffer[0x93 + ix * 4]; + dol_buf_size = (dol_sizes[ix] > dol_buf_size) ? dol_sizes[ix] : dol_buf_size; + } + + /* Iterate through the 18 main.dol segments and hash each */ + buffer = (uint8_t*)malloc(dol_buf_size); + if (!buffer) + { + rc_file_close(file_handle); + return rc_hash_error("Could not allocate temporary buffer"); + } + for (ix = 0; ix < 18; ix++) + { + if (dol_sizes[ix] == 0) + continue; + rc_file_seek(file_handle, dol_offsets[ix], SEEK_SET); + rc_file_read(file_handle, buffer, dol_sizes[ix]); + if (verbose_message_callback) + { + char message[128]; + if (ix < 7) + snprintf(message, sizeof(message), "Hashing %u byte main.dol code segment %u", dol_sizes[ix], ix); + else + snprintf(message, sizeof(message), "Hashing %u byte main.dol data segment %u", dol_sizes[ix], ix - 7); + verbose_message_callback(message); + } + md5_append(&md5, buffer, dol_sizes[ix]); + } + + /* Finalize */ + rc_file_close(file_handle); + free(buffer); + + return rc_hash_finalize(&md5, hash); +} + static int rc_hash_pce(char hash[33], const uint8_t* buffer, size_t buffer_size) { /* if the file contains a header, ignore it (expect ROM data to be multiple of 128KB) */ @@ -1725,7 +1893,11 @@ static struct rc_buffered_file rc_buffered_file; static void* rc_file_open_buffered_file(const char* path) { struct rc_buffered_file* handle = (struct rc_buffered_file*)malloc(sizeof(struct rc_buffered_file)); - memcpy(handle, &rc_buffered_file, sizeof(rc_buffered_file)); + (void)path; + + if (handle) + memcpy(handle, &rc_buffered_file, sizeof(rc_buffered_file)); + return handle; } @@ -1829,7 +2001,9 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b case RC_CONSOLE_SEGA_32X: case RC_CONSOLE_SG1000: case RC_CONSOLE_SUPERVISION: + case RC_CONSOLE_TI83: case RC_CONSOLE_TIC80: + case RC_CONSOLE_UZEBOX: case RC_CONSOLE_VECTREX: case RC_CONSOLE_VIRTUAL_BOY: case RC_CONSOLE_WASM4: @@ -1857,6 +2031,7 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b case RC_CONSOLE_NINTENDO_64: case RC_CONSOLE_NINTENDO_DS: + case RC_CONSOLE_NINTENDO_DSI: return rc_hash_file_from_buffer(hash, console_id, buffer, buffer_size); } } @@ -2120,7 +2295,9 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) case RC_CONSOLE_SEGA_32X: case RC_CONSOLE_SG1000: case RC_CONSOLE_SUPERVISION: + case RC_CONSOLE_TI83: case RC_CONSOLE_TIC80: + case RC_CONSOLE_UZEBOX: case RC_CONSOLE_VECTREX: case RC_CONSOLE_VIRTUAL_BOY: case RC_CONSOLE_WASM4: @@ -2167,6 +2344,9 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) return rc_hash_dreamcast(hash, path); + case RC_CONSOLE_GAMECUBE: + return rc_hash_gamecube(hash, path); + case RC_CONSOLE_NEO_GEO_CD: return rc_hash_neogeo_cd(hash, path); @@ -2174,6 +2354,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) return rc_hash_n64(hash, path); case RC_CONSOLE_NINTENDO_DS: + case RC_CONSOLE_NINTENDO_DSI: return rc_hash_nintendo_ds(hash, path); case RC_CONSOLE_PC_ENGINE_CD: @@ -2313,6 +2494,15 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* } break; + case '8': + /* http://tibasicdev.wikidot.com/file-extensions */ + if (rc_path_compare_extension(ext, "83g") || + rc_path_compare_extension(ext, "83p")) + { + iterator->consoles[0] = RC_CONSOLE_TI83; + } + break; + case 'a': if (rc_path_compare_extension(ext, "a78")) { @@ -2537,7 +2727,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* } else if (rc_path_compare_extension(ext, "nds")) { - iterator->consoles[0] = RC_CONSOLE_NINTENDO_DS; + iterator->consoles[0] = RC_CONSOLE_NINTENDO_DS; /* ASSERT: handles both DS and DSi */ } else if (rc_path_compare_extension(ext, "n64") || rc_path_compare_extension(ext, "ndd")) @@ -2619,6 +2809,13 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* } break; + case 'u': + if (rc_path_compare_extension(ext, "uze")) + { + iterator->consoles[0] = RC_CONSOLE_UZEBOX; + } + break; + case 'v': if (rc_path_compare_extension(ext, "vb")) {