upgrade to rcheevos 10.7 (#15152)

This commit is contained in:
Jamiras 2023-04-04 08:06:44 -06:00 committed by GitHub
parent 54055558f1
commit 08a5288144
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 665 additions and 224 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
#include "rc_api_user.h"
#include "rc_api_common.h"
#include "../rcheevos/rc_version.h"
#include <string.h>
/* --- 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 */
};

View File

@ -3,6 +3,8 @@
#include <ctype.h>
#include <stdarg.h>
#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

View File

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

View File

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

View File

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

View File

@ -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 <time.h>
extern struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer);
#define gmtime_s rc_gmtime_s
#endif
#ifdef __cplusplus
}
#endif

View File

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

View File

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

View File

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

29
deps/rcheevos/src/rcheevos/rc_version.h vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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