upgrade to rcheevos 10.2

This commit is contained in:
Brian Weiss 2021-08-28 13:58:33 -06:00
parent 861cb2953a
commit 30db7d8cd3
28 changed files with 1302 additions and 259 deletions

View File

@ -1,3 +1,24 @@
# v10.2.0
* add RC_MEMSIZE_16_BITS_BE, RC_MEMSIZE_24_BITS_BE, and RC_MEMSIZE_32_BITS_BE
* add secondary flag for RC_CONDITION_MEASURED that tells the UI when to show progress as raw vs. as a percentage
* add rapi calls for fetch_leaderboard_info, fetch_achievement_info and fetch_game_list
* add hash support for RC_CONSOLE_PSP
* add RCHEEVOS_URL_SSL compile flag to use https in rurl functions
* add space to "PC Engine" label
* update RC_CONSOLE_INTELLIVISION memory map to acknowledge non-8-bit addresses
* standardize to z64 format when hashing RC_CONSOLE_N64
* prevent generating hash for PSX disc when requesting RC_CONSOLE_PLAYSTATION2
* fix wrong error message being returned when a leaderboard was only slightly malformed
# v10.1.0
* add RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED
* add rc_runtime_validate_addresses
* add external memory to memory map for Magnavox Odyssey 2
* fix memory map base address for NeoGeo Pocket
* fix bitcount always returning 0 when used in rich presence
# v10.0.0
* add rapi sublibrary for communicating with server (eliminates need for client-side JSON parsing; client must still

182
deps/rcheevos/include/rc_api_info.h vendored Normal file
View File

@ -0,0 +1,182 @@
#ifndef RC_API_INFO_H
#define RC_API_INFO_H
#include "rc_api_request.h"
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/* --- Fetch Achievement Info --- */
/**
* API parameters for a fetch achievement info request.
*/
typedef struct rc_api_fetch_achievement_info_request_t {
/* The username of the player */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the achievement */
unsigned achievement_id;
/* The 1-based index of the first entry to retrieve */
unsigned first_entry;
/* The number of entries to retrieve */
unsigned count;
/* Non-zero to only return unlocks earned by the user's friends */
unsigned friends_only;
}
rc_api_fetch_achievement_info_request_t;
/* An achievement awarded entry */
typedef struct rc_api_achievement_awarded_entry_t {
/* The user associated to the entry */
const char* username;
/* When the achievement was awarded */
time_t awarded;
}
rc_api_achievement_awarded_entry_t;
/**
* Response data for a fetch achievement info request.
*/
typedef struct rc_api_fetch_achievement_info_response_t {
/* The unique identifier of the achievement */
unsigned id;
/* The unique identifier of the game to which the leaderboard is associated */
unsigned game_id;
/* The number of times the achievement has been awarded */
unsigned num_awarded;
/* The number of players that have earned at least one achievement for the game */
unsigned num_players;
/* An array of recently rewarded entries */
rc_api_achievement_awarded_entry_t* recently_awarded;
/* The number of items in the recently_awarded array */
unsigned num_recently_awarded;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_achievement_info_response_t;
int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params);
int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response);
void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response);
/* --- Fetch Leaderboard Info --- */
/**
* API parameters for a fetch leaderboard info request.
*/
typedef struct rc_api_fetch_leaderboard_info_request_t {
/* The unique identifier of the leaderboard */
unsigned leaderboard_id;
/* The number of entries to retrieve */
unsigned count;
/* The 1-based index of the first entry to retrieve */
unsigned first_entry;
/* The username of the player around whom the entries should be returned */
const char* username;
}
rc_api_fetch_leaderboard_info_request_t;
/* A leaderboard info entry */
typedef struct rc_api_lboard_info_entry_t {
/* The user associated to the entry */
const char* username;
/* The rank of the entry */
unsigned rank;
/* The index of the entry */
unsigned index;
/* The value of the entry */
int score;
/* When the entry was submitted */
time_t submitted;
}
rc_api_lboard_info_entry_t;
/**
* Response data for a fetch leaderboard info request.
*/
typedef struct rc_api_fetch_leaderboard_info_response_t {
/* The unique identifier of the leaderboard */
unsigned id;
/* The format to pass to rc_format_value to format the leaderboard value */
int format;
/* If non-zero, indicates that lower scores appear first */
int lower_is_better;
/* The title of the leaderboard */
const char* title;
/* The description of the leaderboard */
const char* description;
/* The definition of the leaderboard to be passed to rc_runtime_activate_lboard */
const char* definition;
/* The unique identifier of the game to which the leaderboard is associated */
unsigned game_id;
/* The author of the leaderboard */
const char* author;
/* When the leaderboard was first uploaded to the server */
time_t created;
/* When the leaderboard was last modified on the server */
time_t updated;
/* An array of requested entries */
rc_api_lboard_info_entry_t* entries;
/* The number of items in the entries array */
unsigned num_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_leaderboard_info_response_t;
int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params);
int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response);
void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response);
/* --- Fetch Games List --- */
/**
* API parameters for a fetch games list request.
*/
typedef struct rc_api_fetch_games_list_request_t {
/* The unique identifier of the console to query */
unsigned console_id;
}
rc_api_fetch_games_list_request_t;
/* A game list entry */
typedef struct rc_api_game_list_entry_t {
/* The unique identifier of the game */
unsigned id;
/* The name of the game */
const char* name;
}
rc_api_game_list_entry_t;
/**
* Response data for a fetch games list request.
*/
typedef struct rc_api_fetch_games_list_response_t {
/* An array of requested entries */
rc_api_game_list_entry_t* entries;
/* The number of items in the entries array */
unsigned num_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_games_list_response_t;
int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params);
int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response);
void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response);
#ifdef __cplusplus
}
#endif
#endif /* RC_API_INFO_H */

View File

@ -36,9 +36,9 @@ int rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetc
* API parameters for a resolve hash request.
*/
typedef struct rc_api_resolve_hash_request_t {
/* The username of the player */
/* Unused - hash lookup does not require credentials */
const char* username;
/* The API token from the login request */
/* Unused - hash lookup does not require credentials */
const char* api_token;
/* The generated hash of the game to be identified */
const char* game_hash;

View File

@ -16,7 +16,7 @@ extern "C" {
/* generates a hash from a block of memory.
* returns non-zero on success, or zero on failure.
*/
int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer, size_t buffer_size);
int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* buffer, size_t buffer_size);
/* generates a hash from a file.
* returns non-zero on success, or zero on failure.

View File

@ -7,6 +7,8 @@ extern "C" {
#include "rc_error.h"
#include <stddef.h>
/*****************************************************************************\
| Forward Declarations (defined in rc_runtime_types.h) |
\*****************************************************************************/
@ -94,6 +96,7 @@ int rc_runtime_activate_achievement(rc_runtime_t* runtime, unsigned id, const ch
void rc_runtime_deactivate_achievement(rc_runtime_t* runtime, unsigned id);
rc_trigger_t* rc_runtime_get_achievement(const rc_runtime_t* runtime, unsigned id);
int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id, unsigned* measured_value, unsigned* measured_target);
int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, unsigned id, char *buffer, size_t buffer_size);
int rc_runtime_activate_lboard(rc_runtime_t* runtime, unsigned id, const char* memaddr, lua_State* L, int funcs_idx);
void rc_runtime_deactivate_lboard(rc_runtime_t* runtime, unsigned id);

View File

@ -51,6 +51,9 @@ enum {
RC_MEMSIZE_BIT_6,
RC_MEMSIZE_BIT_7,
RC_MEMSIZE_BITCOUNT,
RC_MEMSIZE_16_BITS_BE,
RC_MEMSIZE_24_BITS_BE,
RC_MEMSIZE_32_BITS_BE,
RC_MEMSIZE_VARIABLE
};
@ -255,6 +258,9 @@ struct rc_trigger_t {
/* True if at least one condition has a non-zero required hit count */
char has_required_hits;
/* True if the measured value should be displayed as a percentage */
char measured_as_percent;
};
int rc_trigger_size(const char* memaddr);

View File

@ -11,6 +11,9 @@ int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const
int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value);
int rc_url_get_lboard_entries(char* buffer, size_t size, unsigned lboard_id, unsigned first_index, unsigned count);
int rc_url_get_lboard_entries_near_user(char* buffer, size_t size, unsigned lboard_id, const char* user_name, unsigned count);
int rc_url_get_gameid(char* buffer, size_t size, const char* hash);
int rc_url_get_patch(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);

View File

@ -20,7 +20,7 @@ static char* g_imagehost = NULL;
/* --- rc_json --- */
static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count);
static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen);
static int rc_json_parse_array(const char** json_ptr, rc_json_field_t* field);
static int rc_json_parse_field(const char** json_ptr, rc_json_field_t* field) {
@ -68,7 +68,7 @@ static int rc_json_parse_field(const char** json_ptr, rc_json_field_t* field) {
break;
case '{': /* object */
result = rc_json_parse_object(json_ptr, NULL, 0);
result = rc_json_parse_object(json_ptr, NULL, 0, &field->array_size);
if (result != RC_OK)
return result;
@ -127,13 +127,54 @@ static int rc_json_parse_array(const char** json_ptr, rc_json_field_t* field) {
return RC_OK;
}
static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count) {
rc_json_field_t non_matching_field;
rc_json_field_t* field;
static int rc_json_get_next_field(rc_json_object_field_iterator_t* iterator) {
const char* json = iterator->json;
while (isspace((unsigned char)*json))
++json;
if (*json != '"')
return RC_INVALID_JSON;
iterator->field.name = ++json;
while (*json != '"') {
if (!*json)
return RC_INVALID_JSON;
++json;
}
iterator->name_len = json - iterator->field.name;
++json;
while (isspace((unsigned char)*json))
++json;
if (*json != ':')
return RC_INVALID_JSON;
++json;
while (isspace((unsigned char)*json))
++json;
if (rc_json_parse_field(&json, &iterator->field) < 0)
return RC_INVALID_JSON;
while (isspace((unsigned char)*json))
++json;
iterator->json = json;
return RC_OK;
}
static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen) {
rc_json_object_field_iterator_t iterator;
const char* json = *json_ptr;
const char* key_start;
size_t key_len;
size_t i;
unsigned num_fields = 0;
int result;
if (fields_seen)
*fields_seen = 0;
for (i = 0; i < field_count; ++i)
fields[i].value_start = fields[i].value_end = NULL;
@ -147,61 +188,50 @@ static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields,
return RC_OK;
}
memset(&iterator, 0, sizeof(iterator));
iterator.json = json;
do
{
while (isspace((unsigned char)*json))
++json;
result = rc_json_get_next_field(&iterator);
if (result != RC_OK)
return result;
if (*json != '"')
return RC_INVALID_JSON;
key_start = ++json;
while (*json != '"') {
if (!*json)
return RC_INVALID_JSON;
++json;
}
key_len = json - key_start;
++json;
while (isspace((unsigned char)*json))
++json;
if (*json != ':')
return RC_INVALID_JSON;
++json;
while (isspace((unsigned char)*json))
++json;
field = &non_matching_field;
for (i = 0; i < field_count; ++i) {
if (!fields[i].value_start && strncmp(fields[i].name, key_start, key_len) == 0 && fields[i].name[key_len] == '\0') {
field = &fields[i];
if (!fields[i].value_start && strncmp(fields[i].name, iterator.field.name, iterator.name_len) == 0 &&
fields[i].name[iterator.name_len] == '\0') {
fields[i].value_start = iterator.field.value_start;
fields[i].value_end = iterator.field.value_end;
fields[i].array_size = iterator.field.array_size;
break;
}
}
if (rc_json_parse_field(&json, field) < 0)
return RC_INVALID_JSON;
while (isspace((unsigned char)*json))
++json;
if (*json != ',')
++num_fields;
if (*iterator.json != ',')
break;
++json;
++iterator.json;
} while (1);
if (*json != '}')
if (*iterator.json != '}')
return RC_INVALID_JSON;
*json_ptr = ++json;
if (fields_seen)
*fields_seen = num_fields;
*json_ptr = ++iterator.json;
return RC_OK;
}
int rc_json_get_next_object_field(rc_json_object_field_iterator_t* iterator) {
if (*iterator->json != ',' && *iterator->json != '{')
return 0;
++iterator->json;
return (rc_json_get_next_field(iterator) == RC_OK);
}
int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_json_field_t* fields, size_t field_count) {
#ifndef NDEBUG
if (field_count < 2)
@ -213,7 +243,7 @@ int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_jso
#endif
if (*json == '{') {
int result = rc_json_parse_object(&json, fields, field_count);
int result = rc_json_parse_object(&json, fields, field_count, NULL);
rc_json_get_optional_string(&response->error_message, response, &fields[1], "Error", NULL);
rc_json_get_optional_bool(&response->succeeded, &fields[0], "Success", 1);
@ -266,11 +296,15 @@ static int rc_json_missing_field(rc_api_response_t* response, const rc_json_fiel
int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name) {
const char* json = field->value_start;
#ifndef NDEBUG
if (strcmp(field->name, field_name) != 0)
return 0;
#endif
if (!json)
return rc_json_missing_field(response, field);
return (rc_json_parse_object(&json, fields, field_count) == RC_OK);
return (rc_json_parse_object(&json, fields, field_count, &field->array_size) == RC_OK);
}
static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_field_t* iterator) {
@ -322,6 +356,11 @@ int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, r
}
int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) {
#ifndef NDEBUG
if (strcmp(field->name, field_name) != 0)
return 0;
#endif
if (!field->value_start || *field->value_start != '[') {
*num_entries = 0;
return rc_json_missing_field(response, field);
@ -341,7 +380,7 @@ int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count,
while (isspace((unsigned char)*iterator->value_start))
++iterator->value_start;
rc_json_parse_object(&iterator->value_start, fields, field_count);
rc_json_parse_object(&iterator->value_start, fields, field_count, NULL);
while (isspace((unsigned char)*iterator->value_start))
++iterator->value_start;
@ -358,7 +397,7 @@ static unsigned rc_json_decode_hex4(const char* input) {
memcpy(hex, input, 4);
hex[4] = '\0';
return strtol(hex, NULL, 16);
return (unsigned)strtoul(hex, NULL, 16);
}
static int rc_json_ucs32_to_utf8(unsigned char* dst, unsigned ucs32_char) {
@ -604,6 +643,48 @@ int rc_json_get_required_unum(unsigned* out, rc_api_response_t* response, const
return rc_json_missing_field(response, field);
}
int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* field_name) {
struct tm tm;
#ifndef NDEBUG
if (strcmp(field->name, field_name) != 0)
return 0;
#endif
if (*field->value_start == '\"') {
memset(&tm, 0, sizeof(tm));
if (sscanf(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 */
/* mktime converts a struct tm to a time_t using the local timezone.
* the input string is UTC. since timegm is not universally cross-platform,
* figure out the offset between UTC and local time by applying the
* 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;
*out = local_timet - tz_offset;
}
return 1;
}
}
*out = 0;
return 0;
}
int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) {
if (rc_json_get_datetime(out, field, field_name))
return 1;
return rc_json_missing_field(response, field);
}
int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_name) {
const char* src = field->value_start;

View File

@ -4,6 +4,7 @@
#include "rc_api_request.h"
#include <stddef.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
@ -30,11 +31,19 @@ typedef struct rc_json_field_t {
}
rc_json_field_t;
typedef struct rc_json_object_field_iterator_t {
rc_json_field_t field;
const char* json;
size_t name_len;
}
rc_json_object_field_iterator_t;
int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_json_field_t* fields, size_t field_count);
int rc_json_get_string(const char** out, rc_api_buffer_t* buffer, const rc_json_field_t* field, const char* field_name);
int rc_json_get_num(int* out, const rc_json_field_t* field, const char* field_name);
int rc_json_get_unum(unsigned* out, const rc_json_field_t* field, const char* field_name);
int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_name);
int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* field_name);
void rc_json_get_optional_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name, const char* default_value);
void rc_json_get_optional_num(int* out, const rc_json_field_t* field, const char* field_name, int default_value);
void rc_json_get_optional_unum(unsigned* out, const rc_json_field_t* field, const char* field_name, int default_value);
@ -43,10 +52,12 @@ int rc_json_get_required_string(const char** out, rc_api_response_t* response, c
int rc_json_get_required_num(int* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_unum(unsigned* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name);
int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_field_t* iterator);
int rc_json_get_next_object_field(rc_json_object_field_iterator_t* iterator);
void rc_buf_init(rc_api_buffer_t* buffer);
void rc_buf_destroy(rc_api_buffer_t* buffer);

328
deps/rcheevos/src/rapi/rc_api_info.c vendored Normal file
View File

@ -0,0 +1,328 @@
#include "rc_api_info.h"
#include "rc_api_common.h"
#include "rc_runtime_types.h"
#include <stdlib.h>
#include <string.h>
/* --- Fetch Achievement Info --- */
int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->achievement_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "achievementwondata", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
if (api_params->friends_only)
rc_url_builder_append_unum_param(&builder, "f", 1);
if (api_params->first_entry > 1)
rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */
rc_url_builder_append_unum_param(&builder, "c", api_params->count);
request->post_data = rc_url_builder_finalize(&builder);
}
return builder.result;
}
int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response) {
rc_api_achievement_awarded_entry_t* entry;
rc_json_field_t iterator;
unsigned timet;
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"AchievementID"},
{"Response"}
/* unused fields
{"Offset"},
{"Count"},
{"FriendsOnly"},
* unused fields */
};
rc_json_field_t response_fields[] = {
{"NumEarned"},
{"TotalPlayers"},
{"GameID"},
{"RecentWinner"} /* array */
};
rc_json_field_t entry_fields[] = {
{"User"},
{"DateAwarded"}
};
memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer);
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
if (!rc_json_get_required_unum(&response->id, &response->response, &fields[2], "AchievementID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[3], "Response"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->num_awarded, &response->response, &response_fields[0], "NumEarned"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->num_players, &response->response, &response_fields[1], "TotalPlayers"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[2], "GameID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_array(&response->num_recently_awarded, &iterator, &response->response, &response_fields[3], "RecentWinner"))
return RC_MISSING_VALUE;
if (response->num_recently_awarded) {
response->recently_awarded = (rc_api_achievement_awarded_entry_t*)rc_buf_alloc(&response->response.buffer, response->num_recently_awarded * sizeof(rc_api_achievement_awarded_entry_t));
if (!response->recently_awarded)
return RC_OUT_OF_MEMORY;
entry = response->recently_awarded;
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[1], "DateAwarded"))
return RC_MISSING_VALUE;
entry->awarded = (time_t)timet;
++entry;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Fetch Leaderboard Info --- */
int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->leaderboard_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "lbinfo");
rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
if (api_params->username)
rc_url_builder_append_str_param(&builder, "u", api_params->username);
else if (api_params->first_entry > 1)
rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */
rc_url_builder_append_unum_param(&builder, "c", api_params->count);
request->post_data = rc_url_builder_finalize(&builder);
return builder.result;
}
int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response) {
rc_api_lboard_info_entry_t* entry;
rc_json_field_t iterator;
unsigned timet;
int result;
size_t len;
char format[16];
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"LeaderboardData"}
};
rc_json_field_t leaderboarddata_fields[] = {
{"LBID"},
{"LBFormat"},
{"LowerIsBetter"},
{"LBTitle"},
{"LBDesc"},
{"LBMem"},
{"GameID"},
{"LBAuthor"},
{"LBCreated"},
{"LBUpdated"},
{"Entries"} /* array */
/* unused fields
{"GameTitle"},
{"ConsoleID"},
{"ConsoleName"},
{"ForumTopicID"},
{"GameIcon"},
* unused fields */
};
rc_json_field_t entry_fields[] = {
{"User"},
{"Rank"},
{"Index"},
{"Score"},
{"DateSubmitted"}
};
memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer);
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
if (!rc_json_get_required_object(leaderboarddata_fields, sizeof(leaderboarddata_fields) / sizeof(leaderboarddata_fields[0]), &response->response, &fields[2], "LeaderboardData"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->id, &response->response, &leaderboarddata_fields[0], "LBID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&response->lower_is_better, &response->response, &leaderboarddata_fields[2], "LowerIsBetter"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->title, &response->response, &leaderboarddata_fields[3], "LBTitle"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->description, &response->response, &leaderboarddata_fields[4], "LBDesc"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->definition, &response->response, &leaderboarddata_fields[5], "LBMem"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->game_id, &response->response, &leaderboarddata_fields[6], "GameID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->author, &response->response, &leaderboarddata_fields[7], "LBAuthor"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_datetime(&response->created, &response->response, &leaderboarddata_fields[8], "LBCreated"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_datetime(&response->updated, &response->response, &leaderboarddata_fields[9], "LBUpdated"))
return RC_MISSING_VALUE;
if (!leaderboarddata_fields[1].value_end)
return RC_MISSING_VALUE;
len = leaderboarddata_fields[1].value_end - leaderboarddata_fields[1].value_start - 2;
if (len < sizeof(format) - 1) {
memcpy(format, leaderboarddata_fields[1].value_start + 1, len);
format[len] = '\0';
response->format = rc_parse_format(format);
}
else {
response->format = RC_FORMAT_VALUE;
}
if (!rc_json_get_required_array(&response->num_entries, &iterator, &response->response, &leaderboarddata_fields[10], "Entries"))
return RC_MISSING_VALUE;
if (response->num_entries) {
response->entries = (rc_api_lboard_info_entry_t*)rc_buf_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_lboard_info_entry_t));
if (!response->entries)
return RC_OUT_OF_MEMORY;
entry = response->entries;
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&entry->rank, &response->response, &entry_fields[1], "Rank"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&entry->index, &response->response, &entry_fields[2], "Index"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&entry->score, &response->response, &entry_fields[3], "Score"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[4], "DateSubmitted"))
return RC_MISSING_VALUE;
entry->submitted = (time_t)timet;
++entry;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Fetch Games List --- */
int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->console_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "gameslist");
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
request->post_data = rc_url_builder_finalize(&builder);
return builder.result;
}
int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response) {
rc_api_game_list_entry_t* entry;
rc_json_object_field_iterator_t iterator;
int result;
char* end;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"Response"}
};
memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer);
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
if (!fields[2].value_start) {
/* call rc_json_get_required_object to generate the error message */
rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response");
return RC_MISSING_VALUE;
}
response->num_entries = fields[2].array_size;
rc_buf_reserve(&response->response.buffer, response->num_entries * (32 + sizeof(rc_api_game_list_entry_t)));
response->entries = (rc_api_game_list_entry_t*)rc_buf_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_list_entry_t));
if (!response->entries)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = fields[2].value_start;
entry = response->entries;
while (rc_json_get_next_object_field(&iterator)) {
entry->id = strtol(iterator.field.name, &end, 10);
iterator.field.name = "";
if (!rc_json_get_string(&entry->name, &response->response.buffer, &iterator.field, ""))
return RC_MISSING_VALUE;
++entry;
}
return RC_OK;
}
void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}

View File

@ -20,10 +20,9 @@ int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_res
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "gameid", api_params->username, api_params->api_token)) {
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
request->post_data = rc_url_builder_finalize(&builder);
}
rc_url_builder_append_str_param(&builder, "r", "gameid");
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
request->post_data = rc_url_builder_finalize(&builder);
return builder.result;
}
@ -130,7 +129,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
if (result != RC_OK)
return result;
if (!rc_json_get_required_object(patchdata_fields, sizeof(patchdata_fields) / sizeof(patchdata_fields[0]), &response->response, &fields[2], "Response"))
if (!rc_json_get_required_object(patchdata_fields, sizeof(patchdata_fields) / sizeof(patchdata_fields[0]), &response->response, &fields[2], "PatchData"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->id, &response->response, &patchdata_fields[0], "ID"))
@ -440,8 +439,8 @@ int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_respo
{"Rank"},
{"Score"}
/* unused fields
{ "DateSumitted" },
* unused fields */
{"DateSubmitted"},
* unused fields */
};
rc_json_field_t rank_info_fields[] = {

View File

@ -144,6 +144,7 @@ void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, in
parse->measured_target = 0;
parse->lines_read = 0;
parse->has_required_hits = 0;
parse->measured_as_percent = 0;
}
void rc_destroy_parse_state(rc_parse_state_t* parse)

View File

@ -55,6 +55,7 @@ int rc_snprintf(char* buffer, size_t size, const char* format, ...)
va_start(args, format);
/* assume buffer is large enough and ignore size */
(void)size;
result = vsprintf(buffer, format, args);
va_end(args);

View File

@ -86,6 +86,11 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; can_modify = 1; break;
case 't': case 'T': self->type = RC_CONDITION_TRIGGER; break;
case 'z': case 'Z': self->type = RC_CONDITION_RESET_NEXT_IF; break;
case 'g': case 'G':
parse->measured_as_percent = 1;
self->type = RC_CONDITION_MEASURED;
break;
/* e f h j k l s u v w x y */
default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0;
}
@ -95,7 +100,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
self->type = RC_CONDITION_STANDARD;
}
result = rc_parse_operand(&self->operand1, &aux, 1, is_indirect, parse);
result = rc_parse_operand(&self->operand1, &aux, is_indirect, parse);
if (result < 0) {
parse->offset = result;
return 0;
@ -158,7 +163,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
break;
}
result = rc_parse_operand(&self->operand2, &aux, 1, is_indirect, parse);
result = rc_parse_operand(&self->operand2, &aux, is_indirect, parse);
if (result < 0) {
parse->offset = result;
return 0;

View File

@ -18,7 +18,7 @@ static void rc_update_condition_pause(rc_condition_t* condition, int* in_pause)
case RC_CONDITION_OR_NEXT:
case RC_CONDITION_ADD_ADDRESS:
case RC_CONDITION_RESET_NEXT_IF:
condition->pause = *in_pause;
condition->pause = (char)*in_pause;
break;
default:
@ -140,7 +140,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
return self;
}
static void rc_condset_update_indirect_memrefs(rc_condset_t* self, rc_condition_t* condition, int processing_pause, rc_eval_state_t* eval_state) {
static void rc_condset_update_indirect_memrefs(rc_condition_t* condition, int processing_pause, rc_eval_state_t* eval_state) {
for (; condition != 0; condition = condition->next) {
if (condition->pause != processing_pause)
continue;
@ -210,7 +210,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc
}
/* STEP 2: evaluate the current condition */
condition->is_true = rc_test_condition(condition, eval_state);
condition->is_true = (char)rc_test_condition(condition, eval_state);
eval_state->add_value = 0;
eval_state->add_address = 0;
@ -311,10 +311,10 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc
* if the set has any indirect memrefs, manually update them now so the deltas are correct */
if (self->has_indirect_memrefs) {
/* first, update any indirect memrefs in the remaining part of the pause subset */
rc_condset_update_indirect_memrefs(self, condition->next, 1, eval_state);
rc_condset_update_indirect_memrefs(condition->next, 1, eval_state);
/* then, update all indirect memrefs in the non-pause subset */
rc_condset_update_indirect_memrefs(self, self->conditions, 0, eval_state);
rc_condset_update_indirect_memrefs(self->conditions, 0, eval_state);
}
return 1;
@ -371,7 +371,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc
/* if not suppressed, update the measured value */
if (measured_value > eval_state->measured_value && can_measure) {
eval_state->measured_value = measured_value;
eval_state->measured_from_hits = measured_from_hits;
eval_state->measured_from_hits = (char)measured_from_hits;
}
return set_valid;
@ -384,8 +384,9 @@ int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) {
}
if (self->has_pause) {
if ((self->is_paused = rc_test_condset_internal(self, 1, eval_state))) {
/* one or more Pause conditions exists, if any of them are true, stop processing this group */
/* one or more Pause conditions exists, if any of them are true, stop processing this group */
self->is_paused = (char)rc_test_condset_internal(self, 1, eval_state);
if (self->is_paused) {
eval_state->primed = 0;
return 0;
}

View File

@ -136,7 +136,7 @@ const char* rc_console_name(int console_id)
return "PC-FX";
case RC_CONSOLE_PC_ENGINE:
return "PCEngine";
return "PC Engine";
case RC_CONSOLE_PLAYSTATION:
return "PlayStation";
@ -323,19 +323,45 @@ static const rc_memory_region_t _rc_memory_regions_game_gear[] = {
static const rc_memory_regions_t rc_memory_regions_game_gear = { _rc_memory_regions_game_gear, 1 };
/* ===== Intellivision ===== */
/* http://wiki.intellivision.us/index.php%3Ftitle%3DMemory_Map */
/* http://wiki.intellivision.us/index.php/Memory_Map */
/* NOTE: Intellivision memory addresses point at 16-bit values. FreeIntv exposes them as little-endian
* 32-bit values. As such, the addresses are off by a factor of 4 _and_ the data is only where we
* expect it on little-endian systems.
*/
static const rc_memory_region_t _rc_memory_regions_intellivision[] = {
{ 0x000000U, 0x00007FU, 0x000000U, RC_MEMORY_TYPE_VIDEO_RAM, "STIC Registers" },
{ 0x000080U, 0x0000FFU, 0x000080U, RC_MEMORY_TYPE_UNUSED, "" },
{ 0x000100U, 0x00035FU, 0x000100U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x000360U, 0x0003FFU, 0x000360U, RC_MEMORY_TYPE_UNUSED, "" },
{ 0x000400U, 0x000FFFU, 0x000400U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" },
{ 0x001000U, 0x001FFFU, 0x001000U, RC_MEMORY_TYPE_UNUSED, "" },
{ 0x002000U, 0x002FFFU, 0x002000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" },
{ 0x003000U, 0x003FFFU, 0x003000U, RC_MEMORY_TYPE_VIDEO_RAM, "Video RAM" },
{ 0x004000U, 0x00FFFFU, 0x004000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" },
/* For backwards compatibility, register a 128-byte chunk of video RAM so the system memory
* will start at $0080. $0000-$007F previously tried to map to the STIC video registers as
* RETRO_MEMORY_VIDEO_RAM, and FreeIntv didn't expose any RETRO_MEMORY_VIDEO_RAM, so the first
* byte of RETRO_MEMORY_SYSTEM_RAM was registered at $0080. The data at $0080 is actually the
* STIC registers (4 bytes each), so we need to provide an arbitrary 128-byte padding that
* claims to be video RAM to ensure the system RAM ends up at the right address.
*/
{ 0x000000U, 0x00007FU, 0xFFFFFFU, RC_MEMORY_TYPE_VIDEO_RAM, "" },
/* RetroAchievements address = real address x4 + 0x80.
* These all have to map to RETRO_MEMORY_SYSTEM_RAM (even the video-related fields) as the
* entire block is exposed as a single entity by FreeIntv */
/* $0000-$007F: STIC registers, $0040-$007F are readonly */
{ 0x000080U, 0x00027FU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "STIC Registers" },
/* $0080-$00FF: unused */
{ 0x000280U, 0x00047FU, 0x000080U, RC_MEMORY_TYPE_UNUSED, "" },
/* $0100-$035F: system RAM, $0100-$01EF is scratch memory and only 8-bits per address */
{ 0x000480U, 0x000DFFU, 0x000100U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
/* $0360-$03FF: unused */
{ 0x000E00U, 0x00107FU, 0x000360U, RC_MEMORY_TYPE_UNUSED, "" },
/* $0400-$0FFF: cartridge RAM */
{ 0x001080U, 0x00407FU, 0x000400U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" },
/* $1000-$1FFF: unused */
{ 0x004080U, 0x00807FU, 0x001000U, RC_MEMORY_TYPE_UNUSED, "" },
/* $2000-$2FFF: cartridge RAM */
{ 0x008080U, 0x00C07FU, 0x002000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" },
/* $3000-$3FFF: video RAM */
{ 0x00C080U, 0x01007FU, 0x003000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Video RAM" },
/* $4000-$FFFF: cartridge RAM */
{ 0x010080U, 0x04007FU, 0x004000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" },
};
static const rc_memory_regions_t rc_memory_regions_intellivision = { _rc_memory_regions_intellivision, 9 };
static const rc_memory_regions_t rc_memory_regions_intellivision = { _rc_memory_regions_intellivision, 10 };
/* ===== Magnavox Odyssey 2 ===== */
/* https://sudonull.com/post/76885-Architecture-and-programming-Philips-Videopac-Magnavox-Odyssey-2 */
@ -447,13 +473,13 @@ static const rc_memory_regions_t rc_memory_regions_pc8800 = { _rc_memory_regions
/* ===== PC Engine ===== */
/* http://www.archaicpixels.com/Memory_Map */
static const rc_memory_region_t _rc_memory_regions_pcengine[] = {
static const rc_memory_region_t _rc_memory_regions_pc_engine[] = {
{ 0x000000U, 0x001FFFU, 0x1F0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x002000U, 0x011FFFU, 0x100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD RAM" },
{ 0x012000U, 0x041FFFU, 0x0D0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Super System Card RAM" },
{ 0x042000U, 0x0427FFU, 0x1EE000U, RC_MEMORY_TYPE_SAVE_RAM, "CD Battery-backed RAM" }
};
static const rc_memory_regions_t rc_memory_regions_pcengine = { _rc_memory_regions_pcengine, 4 };
static const rc_memory_regions_t rc_memory_regions_pc_engine = { _rc_memory_regions_pc_engine, 4 };
/* ===== PC-FX ===== */
/* http://daifukkat.su/pcfx/data/memmap.html */
@ -679,7 +705,7 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
return &rc_memory_regions_pc8800;
case RC_CONSOLE_PC_ENGINE:
return &rc_memory_regions_pcengine;
return &rc_memory_regions_pc_engine;
case RC_CONSOLE_PCFX:
return &rc_memory_regions_pcfx;

View File

@ -29,10 +29,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
memaddr += 4;
rc_parse_trigger_internal(&self->start, &memaddr, parse);
self->start.memrefs = 0;
if (parse->offset < 0) {
return;
}
}
else if ((memaddr[0] == 'c' || memaddr[0] == 'C') &&
(memaddr[1] == 'a' || memaddr[1] == 'A') &&
@ -46,10 +42,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
memaddr += 4;
rc_parse_trigger_internal(&self->cancel, &memaddr, parse);
self->cancel.memrefs = 0;
if (parse->offset < 0) {
return;
}
}
else if ((memaddr[0] == 's' || memaddr[0] == 'S') &&
(memaddr[1] == 'u' || memaddr[1] == 'U') &&
@ -63,10 +55,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
memaddr += 4;
rc_parse_trigger_internal(&self->submit, &memaddr, parse);
self->submit.memrefs = 0;
if (parse->offset < 0) {
return;
}
}
else if ((memaddr[0] == 'v' || memaddr[0] == 'V') &&
(memaddr[1] == 'a' || memaddr[1] == 'A') &&
@ -80,10 +68,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
memaddr += 4;
rc_parse_value_internal(&self->value, &memaddr, parse);
self->value.memrefs = 0;
if (parse->offset < 0) {
return;
}
}
else if ((memaddr[0] == 'p' || memaddr[0] == 'P') &&
(memaddr[1] == 'r' || memaddr[1] == 'R') &&
@ -99,20 +83,22 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
self->progress = RC_ALLOC(rc_value_t, parse);
rc_parse_value_internal(self->progress, &memaddr, parse);
self->progress->memrefs = 0;
if (parse->offset < 0) {
return;
}
}
else {
/* encountered an error parsing one of the parts */
if (parse->offset < 0)
return;
/* end of string, or end of quoted string - stop processing */
if (memaddr[0] == '\0' || memaddr[0] == '\"')
break;
/* expect two colons between fields */
if (memaddr[0] != ':' || memaddr[1] != ':') {
parse->offset = RC_INVALID_LBOARD_FIELD;
return;
}
if (memaddr[0] != ':' || memaddr[1] != ':') {
break;
}
memaddr += 2;
}

View File

@ -53,6 +53,11 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) {
aux++;
switch (*aux++) {
/* ordered by estimated frequency in case compiler doesn't build a jump table */
case 'h': case 'H': *size = RC_MEMSIZE_8_BITS; break;
case ' ': *size = RC_MEMSIZE_16_BITS; break;
case 'x': case 'X': *size = RC_MEMSIZE_32_BITS; break;
case 'm': case 'M': *size = RC_MEMSIZE_BIT_0; break;
case 'n': case 'N': *size = RC_MEMSIZE_BIT_1; break;
case 'o': case 'O': *size = RC_MEMSIZE_BIT_2; break;
@ -64,17 +69,21 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) {
case 'l': case 'L': *size = RC_MEMSIZE_LOW; break;
case 'u': case 'U': *size = RC_MEMSIZE_HIGH; break;
case 'k': case 'K': *size = RC_MEMSIZE_BITCOUNT; break;
case 'h': case 'H': *size = RC_MEMSIZE_8_BITS; break;
case 'w': case 'W': *size = RC_MEMSIZE_24_BITS; break;
case 'x': case 'X': *size = RC_MEMSIZE_32_BITS; break;
case 'g': case 'G': *size = RC_MEMSIZE_32_BITS_BE; break;
case 'i': case 'I': *size = RC_MEMSIZE_16_BITS_BE; break;
case 'j': case 'J': *size = RC_MEMSIZE_24_BITS_BE; break;
/* case 'v': case 'V': */
/* case 'y': case 'Y': 64 bit? */
/* case 'z': case 'Z': 128 bit? */
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
/* legacy support - addresses without a size prefix are assumed to be 16-bit */
aux--;
/* fallthrough */
case ' ':
*size = RC_MEMSIZE_16_BITS;
break;
@ -97,10 +106,24 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) {
static const unsigned char rc_bits_set[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 };
unsigned rc_transform_memref_value(unsigned value, char size)
{
unsigned rc_transform_memref_value(unsigned value, char size) {
switch (size)
{
case RC_MEMSIZE_8_BITS:
value = (value & 0x000000ff);
break;
case RC_MEMSIZE_16_BITS:
value = (value & 0x0000ffff);
break;
case RC_MEMSIZE_24_BITS:
value = (value & 0x00ffffff);
break;
case RC_MEMSIZE_32_BITS:
break;
case RC_MEMSIZE_BIT_0:
value = (value >> 0) & 1;
break;
@ -146,6 +169,24 @@ unsigned rc_transform_memref_value(unsigned value, char size)
+ rc_bits_set[((value >> 4) & 0x0F)];
break;
case RC_MEMSIZE_16_BITS_BE:
value = ((value & 0xFF00) >> 8) |
((value & 0x00FF) << 8);
break;
case RC_MEMSIZE_24_BITS_BE:
value = ((value & 0xFF0000) >> 16) |
(value & 0x00FF00) |
((value & 0x0000FF) << 16);
break;
case RC_MEMSIZE_32_BITS_BE:
value = ((value & 0xFF000000) >> 24) |
((value & 0x00FF0000) >> 8) |
((value & 0x0000FF00) << 8) |
((value & 0x000000FF) << 24);
break;
default:
break;
}
@ -153,35 +194,48 @@ unsigned rc_transform_memref_value(unsigned value, char size)
return value;
}
char rc_memref_shared_size(char size)
{
switch (size) {
case RC_MEMSIZE_BIT_0:
case RC_MEMSIZE_BIT_1:
case RC_MEMSIZE_BIT_2:
case RC_MEMSIZE_BIT_3:
case RC_MEMSIZE_BIT_4:
case RC_MEMSIZE_BIT_5:
case RC_MEMSIZE_BIT_6:
case RC_MEMSIZE_BIT_7:
case RC_MEMSIZE_LOW:
case RC_MEMSIZE_HIGH:
case RC_MEMSIZE_BITCOUNT:
/* these can all share an 8-bit memref and just mask off the appropriate data in rc_transform_memref_value */
return RC_MEMSIZE_8_BITS;
/* all sizes less than 8-bits (1 byte) are mapped to 8-bits. 24-bit is mapped to 32-bit
* as we don't expect the client to understand a request for 3 bytes. all other reads are
* mapped to the little-endian read of the same size. */
static const char rc_memref_shared_sizes[] = {
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_8_BITS */
RC_MEMSIZE_16_BITS, /* RC_MEMSIZE_16_BITS */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_24_BITS */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_LOW */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_HIGH */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_0 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_1 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_2 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_3 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_4 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_5 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_6 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_7 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BITCOUNT */
RC_MEMSIZE_16_BITS, /* RC_MEMSIZE_16_BITS_BE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_24_BITS_BE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS_BE */
RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */
};
default:
return size;
}
char rc_memref_shared_size(char size) {
const size_t index = (size_t)size;
if (index >= sizeof(rc_memref_shared_sizes) / sizeof(rc_memref_shared_sizes[0]))
return size;
return rc_memref_shared_sizes[index];
}
static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) {
unsigned value;
char shared_size;
if (!peek)
return 0;
switch (size)
shared_size = rc_memref_shared_size(size);
switch (shared_size)
{
case RC_MEMSIZE_8_BITS:
value = peek(address, 1, ud);
@ -191,28 +245,17 @@ static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void*
value = peek(address, 2, ud);
break;
case RC_MEMSIZE_24_BITS:
/* peek 4 bytes - don't expect the caller to understand 24-bit numbers */
value = peek(address, 4, ud) & 0x00FFFFFF;
break;
case RC_MEMSIZE_32_BITS:
value = peek(address, 4, ud);
break;
default:
if (rc_memref_shared_size(size) == RC_MEMSIZE_8_BITS)
{
value = peek(address, 1, ud);
value = rc_transform_memref_value(value, size);
}
else
{
value = 0;
}
break;
return 0;
}
if (shared_size != size)
value = rc_transform_memref_value(value, size);
return value;
}
@ -242,17 +285,7 @@ void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs)
*memrefs = 0;
}
unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state) {
/* if this is an indirect reference, handle the indirection. */
if (memref->value.is_indirect) {
const unsigned new_address = memref->address + eval_state->add_address;
rc_update_memref_value(&memref->value, rc_peek_value(new_address, memref->value.size, eval_state->peek, eval_state->peek_userdata));
}
return rc_get_memref_value_value(&memref->value, operand_type);
}
unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type) {
static unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type) {
switch (operand_type)
{
/* most common case explicitly first, even though it could be handled by default case.
@ -271,3 +304,13 @@ unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type)
return memref->prior;
}
}
unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state) {
/* if this is an indirect reference, handle the indirection. */
if (memref->value.is_indirect) {
const unsigned new_address = memref->address + eval_state->add_address;
rc_update_memref_value(&memref->value, rc_peek_value(new_address, memref->value.size, eval_state->peek, eval_state->peek_userdata));
}
return rc_get_memref_value_value(&memref->value, operand_type);
}

View File

@ -103,7 +103,7 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_
return ret;
size = rc_memref_shared_size(self->size);
self->value.memref = rc_alloc_memref(parse, address, size, is_indirect);
self->value.memref = rc_alloc_memref(parse, address, size, (char)is_indirect);
if (parse->offset < 0)
return parse->offset;
@ -111,12 +111,13 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_
return RC_OK;
}
int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, int is_indirect, rc_parse_state_t* parse) {
int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse) {
const char* aux = *memaddr;
char* end;
int ret;
unsigned long value;
int negative;
int allow_decimal = 0;
self->size = RC_MEMSIZE_32_BITS;
@ -128,14 +129,11 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i
}
value = strtoul(++aux, &end, 16);
if (end == aux) {
if (end == aux)
return RC_INVALID_CONST_OPERAND;
}
if (value > 0xffffffffU) {
if (value > 0xffffffffU)
value = 0xffffffffU;
}
self->type = RC_OPERAND_CONST;
self->value.num = (unsigned)value;
@ -144,47 +142,61 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i
break;
case 'f': case 'F': /* floating point constant */
self->value.dbl = strtod(++aux, &end);
if (end == aux) {
return RC_INVALID_FP_OPERAND;
}
if (floor(self->value.dbl) == self->value.dbl) {
self->type = RC_OPERAND_CONST;
self->value.num = (unsigned)floor(self->value.dbl);
}
else {
self->type = RC_OPERAND_FP;
}
aux = end;
break;
allow_decimal = 1;
/* fall through */
case 'v': case 'V': /* signed integer constant */
++aux;
/* fallthrough */
/* fall through */
case '+': case '-': /* signed integer constant */
negative = 0;
if (*aux == '-')
{
if (*aux == '-') {
negative = 1;
++aux;
}
else if (*aux == '+')
{
else if (*aux == '+') {
++aux;
}
value = strtoul(aux, &end, 10);
if (end == aux) {
return RC_INVALID_CONST_OPERAND;
if (*end == '.' && allow_decimal) {
/* custom parser for decimal values to ignore locale */
unsigned long shift = 1;
unsigned long fraction = 0;
aux = end + 1;
if (*aux < '0' || *aux > '9')
return RC_INVALID_FP_OPERAND;
do {
fraction *= 10;
fraction += (*aux - '0');
shift *= 10;
++aux;
} while (*aux >= '0' && *aux <= '9');
/* if fractional part is 0, convert to an integer constant */
if (fraction != 0) {
/* non-zero fractional part, convert to double and merge in integer portion */
const double dbl_fraction = ((double)fraction) / ((double)shift);
if (negative)
self->value.dbl = ((double)(-((long)value))) - dbl_fraction;
else
self->value.dbl = (double)value + dbl_fraction;
self->type = RC_OPERAND_FP;
break;
}
} else {
/* not a floating point value, make sure something was read and advance the read pointer */
if (end == aux)
return allow_decimal ? RC_INVALID_FP_OPERAND : RC_INVALID_CONST_OPERAND;
aux = end;
}
if (value > 0x7fffffffU) {
if (value > 0x7fffffffU)
value = 0x7fffffffU;
}
self->type = RC_OPERAND_CONST;
@ -193,7 +205,6 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i
else
self->value.num = (unsigned)value;
aux = end;
break;
case '0':
@ -202,9 +213,8 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i
default:
ret = rc_parse_operand_memory(self, &aux, parse, is_indirect);
if (ret < 0) {
if (ret < 0)
return ret;
}
break;
}
@ -213,14 +223,11 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i
case '1': case '2': case '3': case '4': case '5': /* unsigned integer constant */
case '6': case '7': case '8': case '9':
value = strtoul(aux, &end, 10);
if (end == aux) {
if (end == aux)
return RC_INVALID_CONST_OPERAND;
}
if (value > 0xffffffffU) {
if (value > 0xffffffffU)
value = 0xffffffffU;
}
self->type = RC_OPERAND_CONST;
self->value.num = (unsigned)value;
@ -231,9 +238,8 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, i
case '@':
ret = rc_parse_operand_lua(self, &aux, parse);
if (ret < 0) {
if (ret < 0)
return ret;
}
break;
}
@ -340,6 +346,7 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) {
break;
case RC_MEMSIZE_16_BITS:
case RC_MEMSIZE_16_BITS_BE:
value = ((value >> 12) & 0x0f) * 1000
+ ((value >> 8) & 0x0f) * 100
+ ((value >> 4) & 0x0f) * 10
@ -347,6 +354,7 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) {
break;
case RC_MEMSIZE_24_BITS:
case RC_MEMSIZE_24_BITS_BE:
value = ((value >> 20) & 0x0f) * 100000
+ ((value >> 16) & 0x0f) * 10000
+ ((value >> 12) & 0x0f) * 1000
@ -356,6 +364,7 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) {
break;
case RC_MEMSIZE_32_BITS:
case RC_MEMSIZE_32_BITS_BE:
case RC_MEMSIZE_VARIABLE:
value = ((value >> 28) & 0x0f) * 10000000
+ ((value >> 24) & 0x0f) * 1000000
@ -385,14 +394,17 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) {
break;
case RC_MEMSIZE_16_BITS:
case RC_MEMSIZE_16_BITS_BE:
value ^= 0xffff;
break;
case RC_MEMSIZE_24_BITS:
case RC_MEMSIZE_24_BITS_BE:
value ^= 0xffffff;
break;
case RC_MEMSIZE_32_BITS:
case RC_MEMSIZE_32_BITS_BE:
case RC_MEMSIZE_VARIABLE:
value ^= 0xffffffff;
break;

View File

@ -99,6 +99,7 @@ typedef struct {
int lines_read;
char has_required_hits;
char measured_as_percent;
}
rc_parse_state_t;
@ -116,7 +117,6 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address);
void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud);
void rc_update_memref_value(rc_memref_value_t* memref, unsigned value);
unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state);
unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type);
char rc_memref_shared_size(char size);
unsigned rc_transform_memref_value(unsigned value, char size);
@ -131,7 +131,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state);
int rc_evaluate_condition_value(rc_condition_t* self, rc_eval_state_t* eval_state);
int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_trigger, int is_indirect, rc_parse_state_t* parse);
int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse);
unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state);
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse);

View File

@ -89,6 +89,11 @@ static const rc_disallowed_setting_t _rc_disallowed_ppsspp_settings[] = {
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_smsplus_settings[] = {
{ "smsplus_region", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_snes9x_settings[] = {
{ "snes9x_region", "pal" },
{ NULL, NULL }
@ -112,6 +117,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
{ "PPSSPP", _rc_disallowed_ppsspp_settings },
{ "PCSX-ReARMed", _rc_disallowed_pcsx_rearmed_settings },
{ "PicoDrive", _rc_disallowed_picodrive_settings },
{ "SMS Plus GX", _rc_disallowed_smsplus_settings },
{ "Snes9x", _rc_disallowed_snes9x_settings },
{ "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings },
{ NULL, NULL }

View File

@ -383,7 +383,7 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup
base = 10;
}
first = strtoul(line, &endptr, base);
first = (unsigned)strtoul(line, &endptr, base);
/* check for a range */
if (*endptr != '-') {
@ -401,7 +401,7 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup
base = 10;
}
last = strtoul(line, &endptr, base);
last = (unsigned)strtoul(line, &endptr, base);
}
/* ignore spaces after the number - was previously ignored as string was split on equals */
@ -493,7 +493,7 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
memcpy(format, line, chars);
format[chars] = '\0';
lookup->format = rc_parse_format(format);
lookup->format = (unsigned short)rc_parse_format(format);
} else {
lookup->format = RC_FORMAT_VALUE;
}

View File

@ -1,5 +1,6 @@
#include "rc_runtime.h"
#include "rc_internal.h"
#include "rc_compat.h"
#include "../rhash/md5.h"
@ -241,6 +242,33 @@ int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id
return 1;
}
int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, unsigned id, char* buffer, size_t buffer_size)
{
const rc_trigger_t* trigger = rc_runtime_get_achievement(runtime, id);
unsigned value;
if (!buffer || !buffer_size)
return 0;
if (!trigger || /* no trigger */
trigger->measured_target == 0 || /* not measured */
!rc_trigger_state_active(trigger->state)) { /* don't report measured value for inactive triggers */
*buffer = '\0';
return 0;
}
/* cap the value at the target so we can count past the target: "107 >= 100" */
value = trigger->measured_value;
if (value > trigger->measured_target)
value = trigger->measured_target;
if (trigger->measured_as_percent) {
unsigned percent = (unsigned)(((unsigned long long)value * 100) / trigger->measured_target);
return snprintf(buffer, buffer_size, "%u%%", percent);
}
return snprintf(buffer, buffer_size, "%u/%u", value, trigger->measured_target);
}
static void rc_runtime_deactivate_lboard_by_index(rc_runtime_t* self, unsigned index) {
if (self->lboards[index].owns_memrefs) {
/* if the lboard has one or more memrefs in its buffer, we can't free the buffer.
@ -507,7 +535,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
if (new_state == old_state)
continue;
/* raise an UNPRIMED event when changing from UNPRIMED to anything else */
/* raise an UNPRIMED event when changing from PRIMED to anything else */
if (old_state == RC_TRIGGER_STATE_PRIMED) {
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED;
runtime_event.id = self->triggers[i].id;

View File

@ -247,7 +247,7 @@ static int rc_runtime_progress_read_condset(rc_runtime_progress_t* progress, rc_
rc_condition_t* cond;
unsigned flags;
condset->is_paused = rc_runtime_progress_read_uint(progress);
condset->is_paused = (char)rc_runtime_progress_read_uint(progress);
cond = condset->conditions;
while (cond) {
@ -257,12 +257,18 @@ static int rc_runtime_progress_read_condset(rc_runtime_progress_t* progress, rc_
cond->is_true = (flags & RC_COND_FLAG_IS_TRUE) ? 1 : 0;
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
if (!rc_operand_is_memref(&cond->operand1)) /* this should never happen, but better safe than sorry */
return RC_INVALID_STATE;
cond->operand1.value.memref->value.value = rc_runtime_progress_read_uint(progress);
cond->operand1.value.memref->value.prior = rc_runtime_progress_read_uint(progress);
cond->operand1.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0;
}
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
if (!rc_operand_is_memref(&cond->operand2)) /* this should never happen, but better safe than sorry */
return RC_INVALID_STATE;
cond->operand2.value.memref->value.value = rc_runtime_progress_read_uint(progress);
cond->operand2.value.memref->value.prior = rc_runtime_progress_read_uint(progress);
cond->operand2.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0;
@ -305,7 +311,7 @@ static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_
rc_condset_t* condset;
int result;
trigger->state = rc_runtime_progress_read_uint(progress);
trigger->state = (char)rc_runtime_progress_read_uint(progress);
trigger->measured_value = rc_runtime_progress_read_uint(progress);
if (trigger->requirement) {

View File

@ -13,6 +13,7 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars
/* reset in case multiple triggers are parsed by the same parse_state */
parse->measured_target = 0;
parse->has_required_hits = 0;
parse->measured_as_percent = 0;
if (*aux == 's' || *aux == 'S') {
self->requirement = 0;
@ -43,6 +44,7 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars
self->measured_value = 0;
self->measured_target = parse->measured_target;
self->measured_as_percent = parse->measured_as_percent;
self->state = RC_TRIGGER_STATE_WAITING;
self->has_hits = 0;
self->has_required_hits = parse->has_required_hits;

View File

@ -72,17 +72,14 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat
*ptr++ = '*';
buffer_ptr = *memaddr + 1;
if (*buffer_ptr == '-') {
/* negative value automatically needs prefix, 'f' handles both float and digits, so use it */
if (*buffer_ptr == '-' || *buffer_ptr == '+')
++buffer_ptr; /* ignore sign */
/* if it looks like a floating point number, add the 'f' prefix */
while (isdigit((unsigned char)*buffer_ptr))
++buffer_ptr;
if (*buffer_ptr == '.')
*ptr++ = 'f';
}
else {
/* if it looks like a floating point number, add the 'f' prefix */
while (isdigit((unsigned char)*buffer_ptr))
++buffer_ptr;
if (*buffer_ptr == '.')
*ptr++ = 'f';
}
break;
default:
@ -95,7 +92,12 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat
buffer_ptr = buffer;
cond = rc_parse_condition(&buffer_ptr, parse, 0);
if (parse->offset < 0) {
if (parse->offset < 0)
return;
if (*buffer_ptr) {
/* whatever we copied as a single condition was not fully consumed */
parse->offset = RC_INVALID_COMPARISON;
return;
}

View File

@ -118,7 +118,11 @@ void* rc_file_open(const char* path)
void* handle;
if (!filereader)
{
rc_hash_init_custom_filereader(NULL);
if (!filereader)
return NULL;
}
handle = filereader->open(path);
if (handle && verbose_message_callback)
@ -360,7 +364,7 @@ static int rc_hash_finalize(md5_state_t* md5, char hash[33])
return 1;
}
static int rc_hash_buffer(char hash[33], uint8_t* buffer, size_t buffer_size)
static int rc_hash_buffer(char hash[33], const uint8_t* buffer, size_t buffer_size)
{
md5_state_t md5;
md5_init(&md5);
@ -410,9 +414,9 @@ static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector
{
md5_append(md5, buffer, (int)num_read);
size -= (unsigned)num_read;
if (size == 0)
if (size <= (unsigned)num_read)
break;
size -= (unsigned)num_read;
++sector;
if (size >= sizeof(buffer))
@ -494,7 +498,7 @@ static int rc_hash_3do(char hash[33], const char* path)
block_location *= block_size;
/* the file size is at offset 0x10 (assume 0x10 is always 0) */
size = buffer[offset + 0x11] * 65536 + buffer[offset + 0x12] * 256 + buffer[offset + 0x13];
size = (size_t)buffer[offset + 0x11] * 65536 + buffer[offset + 0x12] * 256 + buffer[offset + 0x13];
if (verbose_message_callback)
{
@ -557,7 +561,7 @@ static int rc_hash_3do(char hash[33], const char* path)
return rc_hash_finalize(&md5, hash);
}
static int rc_hash_7800(char hash[33], uint8_t* buffer, size_t buffer_size)
static int rc_hash_7800(char hash[33], const uint8_t* buffer, size_t buffer_size)
{
/* if the file contains a header, ignore it */
if (memcmp(&buffer[1], "ATARI7800", 9) == 0)
@ -644,7 +648,7 @@ static int rc_hash_arcade(char hash[33], const char* path)
return rc_hash_buffer(hash, (uint8_t*)filename, filename_length);
}
static int rc_hash_lynx(char hash[33], uint8_t* buffer, size_t buffer_size)
static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size)
{
/* if the file contains a header, ignore it */
if (buffer[0] == 'L' && buffer[1] == 'Y' && buffer[2] == 'N' && buffer[3] == 'X' && buffer[4] == 0)
@ -658,7 +662,7 @@ static int rc_hash_lynx(char hash[33], uint8_t* buffer, size_t buffer_size)
return rc_hash_buffer(hash, buffer, buffer_size);
}
static int rc_hash_nes(char hash[33], uint8_t* buffer, size_t buffer_size)
static int rc_hash_nes(char hash[33], const uint8_t* buffer, size_t buffer_size)
{
/* if the file contains a header, ignore it */
if (buffer[0] == 'N' && buffer[1] == 'E' && buffer[2] == 'S' && buffer[3] == 0x1A)
@ -679,6 +683,212 @@ static int rc_hash_nes(char hash[33], uint8_t* buffer, size_t buffer_size)
return rc_hash_buffer(hash, buffer, buffer_size);
}
static void rc_hash_v64_to_z64(uint8_t* buffer, const uint8_t* stop)
{
uint32_t* ptr = (uint32_t*)buffer;
const uint32_t* stop32 = (const uint32_t*)stop;
while (ptr < stop32)
{
uint32_t temp = *ptr;
temp = (temp & 0xFF00FF00) >> 8 |
(temp & 0x00FF00FF) << 8;
*ptr++ = temp;
}
}
static void rc_hash_n64_to_z64(uint8_t* buffer, const uint8_t* stop)
{
uint32_t* ptr = (uint32_t*)buffer;
const uint32_t* stop32 = (const uint32_t*)stop;
while (ptr < stop32)
{
uint32_t temp = *ptr;
temp = (temp & 0xFF000000) >> 24 |
(temp & 0x00FF0000) >> 8 |
(temp & 0x0000FF00) << 8 |
(temp & 0x000000FF) << 24;
*ptr++ = temp;
}
}
static int rc_hash_n64(char hash[33], const uint8_t* buffer, size_t buffer_size)
{
uint8_t* swapbuffer;
uint8_t* stop;
const size_t swapbuffer_size = 65536;
md5_state_t md5;
size_t remaining;
int is_v64;
if (buffer[0] == 0x80) /* z64 format (big endian [native]) */
{
return rc_hash_buffer(hash, buffer, buffer_size);
}
else if (buffer[0] == 0x37) /* v64 format (byteswapped) */
{
rc_hash_verbose("converting v64 to z64");
is_v64 = 1;
}
else if (buffer[0] == 0x40) /* n64 format (little endian) */
{
rc_hash_verbose("converting n64 to z64");
is_v64 = 0;
}
else
{
rc_hash_verbose("Not a Nintendo 64 ROM");
return 0;
}
swapbuffer = (uint8_t*)malloc(swapbuffer_size);
if (!swapbuffer)
return rc_hash_error("Could not allocate temporary buffer");
stop = swapbuffer + swapbuffer_size;
md5_init(&md5);
if (buffer_size > MAX_BUFFER_SIZE)
remaining = MAX_BUFFER_SIZE;
else
remaining = (size_t)buffer_size;
if (verbose_message_callback)
{
char message[64];
snprintf(message, sizeof(message), "Hashing %u bytes", (unsigned)remaining);
verbose_message_callback(message);
}
while (remaining >= swapbuffer_size)
{
memcpy(swapbuffer, buffer, swapbuffer_size);
if (is_v64)
rc_hash_v64_to_z64(swapbuffer, stop);
else
rc_hash_n64_to_z64(swapbuffer, stop);
md5_append(&md5, swapbuffer, (int)swapbuffer_size);
buffer += swapbuffer_size;
remaining -= swapbuffer_size;
}
if (remaining > 0)
{
memcpy(swapbuffer, buffer, remaining);
stop = swapbuffer + remaining;
if (is_v64)
rc_hash_v64_to_z64(swapbuffer, stop);
else
rc_hash_n64_to_z64(swapbuffer, stop);
md5_append(&md5, swapbuffer, (int)remaining);
}
free(swapbuffer);
return rc_hash_finalize(&md5, hash);
}
static int rc_hash_n64_file(char hash[33], const char* path)
{
uint8_t* buffer;
uint8_t* stop;
const size_t buffer_size = 65536;
md5_state_t md5;
size_t remaining;
void* file_handle;
int is_v64 = 0;
int is_n64 = 0;
file_handle = rc_file_open(path);
if (!file_handle)
return rc_hash_error("Could not open file");
buffer = (uint8_t*)malloc(buffer_size);
if (!buffer)
{
rc_file_close(file_handle);
return rc_hash_error("Could not allocate temporary buffer");
}
stop = buffer + buffer_size;
/* read first byte so we can detect endianness */
rc_file_seek(file_handle, 0, SEEK_SET);
rc_file_read(file_handle, buffer, 1);
if (buffer[0] == 0x80) /* z64 format (big endian [native]) */
{
}
else if (buffer[0] == 0x37) /* v64 format (byteswapped) */
{
rc_hash_verbose("converting v64 to z64");
is_v64 = 1;
}
else if (buffer[0] == 0x40) /* n64 format (little endian) */
{
rc_hash_verbose("converting n64 to z64");
is_n64 = 1;
}
else
{
free(buffer);
rc_file_close(file_handle);
rc_hash_verbose("Not a Nintendo 64 ROM");
return 0;
}
/* calculate total file size */
rc_file_seek(file_handle, 0, SEEK_END);
remaining = (size_t)rc_file_tell(file_handle);
if (remaining > MAX_BUFFER_SIZE)
remaining = MAX_BUFFER_SIZE;
if (verbose_message_callback)
{
char message[64];
snprintf(message, sizeof(message), "Hashing %u bytes", (unsigned)remaining);
verbose_message_callback(message);
}
/* begin hashing */
md5_init(&md5);
rc_file_seek(file_handle, 0, SEEK_SET);
while (remaining >= buffer_size)
{
rc_file_read(file_handle, buffer, (int)buffer_size);
if (is_v64)
rc_hash_v64_to_z64(buffer, stop);
else if (is_n64)
rc_hash_n64_to_z64(buffer, stop);
md5_append(&md5, buffer, (int)buffer_size);
remaining -= buffer_size;
}
if (remaining > 0)
{
rc_file_read(file_handle, buffer, (int)remaining);
stop = buffer + remaining;
if (is_v64)
rc_hash_v64_to_z64(buffer, stop);
else if (is_n64)
rc_hash_n64_to_z64(buffer, stop);
md5_append(&md5, buffer, (int)remaining);
}
/* cleanup */
rc_file_close(file_handle);
free(buffer);
return rc_hash_finalize(&md5, hash);
}
static int rc_hash_nintendo_ds(char hash[33], const char* path)
{
uint8_t header[512];
@ -790,7 +1000,7 @@ static int rc_hash_nintendo_ds(char hash[33], const char* path)
return rc_hash_finalize(&md5, hash);
}
static int rc_hash_pce(char hash[33], uint8_t* buffer, size_t buffer_size)
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) */
uint32_t calc_size = ((uint32_t)buffer_size / 0x20000) * 0x20000;
@ -1095,6 +1305,7 @@ static int rc_hash_find_playstation_executable(void* track_handle, const char* b
size = (unsigned)rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer) - 1);
buffer[size] = '\0';
sector = 0;
for (ptr = (char*)buffer; *ptr; ++ptr)
{
if (strncmp(ptr, boot_key, boot_key_len) == 0)
@ -1170,7 +1381,7 @@ static int rc_hash_psx(char hash[33], const char* path)
{
rc_hash_error("Could not locate primary executable");
}
else if ((rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer))) < sizeof(buffer))
else if (rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)) < sizeof(buffer))
{
rc_hash_error("Could not read primary executable");
}
@ -1227,7 +1438,7 @@ static int rc_hash_ps2(char hash[33], const char* path)
{
rc_hash_error("Could not locate primary executable");
}
else if ((rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer))) < sizeof(buffer))
else if (rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)) < sizeof(buffer))
{
rc_hash_error("Could not read primary executable");
}
@ -1258,6 +1469,40 @@ static int rc_hash_ps2(char hash[33], const char* path)
return result;
}
static int rc_hash_psp(char hash[33], const char* path)
{
void* track_handle;
uint32_t sector;
unsigned size;
md5_state_t md5;
track_handle = rc_cd_open_track(path, 1);
if (!track_handle)
return rc_hash_error("Could not open track");
/* http://www.romhacking.net/forum/index.php?topic=30899.0
* PSP_GAME/PARAM.SFO contains key/value pairs identifying the game for the system (i.e. serial number,
* name, version). PSP_GAME/SYSDIR/EBOOT.BIN is the encrypted primary executable.
*/
sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\PARAM.SFO", &size);
if (!sector)
return rc_hash_error("Not a PSP game disc");
md5_init(&md5);
if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\PARAM.SFO"))
return 0;
sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\SYSDIR\\EBOOT.BIN", &size);
if (!sector)
return rc_hash_error("Could not find primary executable");
if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\SYSDIR\\EBOOT.BIN"))
return 0;
rc_cd_close_track(track_handle);
return rc_hash_finalize(&md5, hash);
}
static int rc_hash_sega_cd(char hash[33], const char* path)
{
uint8_t buffer[512];
@ -1287,7 +1532,7 @@ static int rc_hash_sega_cd(char hash[33], const char* path)
return rc_hash_buffer(hash, buffer, sizeof(buffer));
}
static int rc_hash_snes(char hash[33], uint8_t* buffer, size_t buffer_size)
static int rc_hash_snes(char hash[33], const uint8_t* buffer, size_t buffer_size)
{
/* if the file contains a header, ignore it */
uint32_t calc_size = ((uint32_t)buffer_size / 0x2000) * 0x2000;
@ -1302,7 +1547,7 @@ static int rc_hash_snes(char hash[33], uint8_t* buffer, size_t buffer_size)
return rc_hash_buffer(hash, buffer, buffer_size);
}
int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer, size_t buffer_size)
int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* buffer, size_t buffer_size)
{
switch (console_id)
{
@ -1327,7 +1572,6 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer,
case RC_CONSOLE_MEGA_DRIVE:
case RC_CONSOLE_MSX:
case RC_CONSOLE_NEOGEO_POCKET:
case RC_CONSOLE_NINTENDO_64:
case RC_CONSOLE_ORIC:
case RC_CONSOLE_PC8800:
case RC_CONSOLE_POKEMON_MINI:
@ -1349,6 +1593,9 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer,
case RC_CONSOLE_NINTENDO:
return rc_hash_nes(hash, buffer, buffer_size);
case RC_CONSOLE_NINTENDO_64:
return rc_hash_n64(hash, buffer, buffer_size);
case RC_CONSOLE_PC_ENGINE: /* NOTE: does not support PCEngine CD */
return rc_hash_pce(hash, buffer, buffer_size);
@ -1357,7 +1604,7 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer,
}
}
static int rc_hash_whole_file(char hash[33], int console_id, const char* path)
static int rc_hash_whole_file(char hash[33], const char* path)
{
md5_state_t md5;
uint8_t* buffer;
@ -1608,7 +1855,6 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
case RC_CONSOLE_MASTER_SYSTEM:
case RC_CONSOLE_MEGA_DRIVE:
case RC_CONSOLE_NEOGEO_POCKET:
case RC_CONSOLE_NINTENDO_64:
case RC_CONSOLE_ORIC:
case RC_CONSOLE_POKEMON_MINI:
case RC_CONSOLE_SEGA_32X:
@ -1619,7 +1865,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
case RC_CONSOLE_VIRTUAL_BOY:
case RC_CONSOLE_WONDERSWAN:
/* generic whole-file hash - don't buffer */
return rc_hash_whole_file(hash, console_id, path);
return rc_hash_whole_file(hash, path);
case RC_CONSOLE_MSX:
case RC_CONSOLE_PC8800:
@ -1627,7 +1873,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
if (rc_path_compare_extension(path, "m3u"))
return rc_hash_generate_from_playlist(hash, console_id, path);
return rc_hash_whole_file(hash, console_id, path);
return rc_hash_whole_file(hash, path);
case RC_CONSOLE_ATARI_7800:
case RC_CONSOLE_ATARI_LYNX:
@ -1645,6 +1891,9 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
case RC_CONSOLE_ARCADE:
return rc_hash_arcade(hash, path);
case RC_CONSOLE_NINTENDO_64:
return rc_hash_n64_file(hash, path);
case RC_CONSOLE_NINTENDO_DS:
return rc_hash_nintendo_ds(hash, path);
@ -1675,6 +1924,9 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
return rc_hash_ps2(hash, path);
case RC_CONSOLE_PSP:
return rc_hash_psp(hash, path);
case RC_CONSOLE_DREAMCAST:
if (rc_path_compare_extension(path, "m3u"))
return rc_hash_generate_from_playlist(hash, console_id, path);
@ -1690,7 +1942,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
}
}
static void rc_hash_iterator_append_console(struct rc_hash_iterator* iterator, int console_id)
static void rc_hash_iterator_append_console(struct rc_hash_iterator* iterator, uint8_t console_id)
{
int i = 0;
while (iterator->consoles[i] != 0)
@ -1916,8 +2168,9 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
if (rc_path_compare_extension(ext, "iso"))
{
iterator->consoles[0] = RC_CONSOLE_PLAYSTATION_2;
iterator->consoles[1] = RC_CONSOLE_3DO;
iterator->consoles[2] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
iterator->consoles[1] = RC_CONSOLE_PSP;
iterator->consoles[2] = RC_CONSOLE_3DO;
iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
need_path = 1;
}
break;
@ -2061,6 +2314,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{
iterator->consoles[0] = RC_CONSOLE_VIRTUAL_BOY;
}
else if (rc_path_compare_extension(ext, "v64"))
{
iterator->consoles[0] = RC_CONSOLE_NINTENDO_64;
}
break;
case 'w':
@ -2081,6 +2338,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
iterator->consoles[0] = RC_CONSOLE_ARCADE;
need_path = 1;
}
else if (rc_path_compare_extension(ext, "z64"))
{
iterator->consoles[0] = RC_CONSOLE_NINTENDO_64;
}
break;
}

View File

@ -6,6 +6,12 @@
#include <stdio.h>
#include <string.h>
#if RCHEEVOS_URL_SSL
#define RCHEEVOS_URL_PROTOCOL "https"
#else
#define RCHEEVOS_URL_PROTOCOL "http"
#endif
static int rc_url_encode(char* encoded, size_t len, const char* str) {
for (;;) {
switch (*str) {
@ -67,7 +73,7 @@ int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=awardachievement&u=%s&t=%s&a=%u&h=%d",
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=awardachievement&u=%s&t=%s&a=%u&h=%d",
urle_user_name,
urle_login_token,
cheevo_id,
@ -106,7 +112,7 @@ int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=submitlbentry&u=%s&t=%s&i=%u&s=%d&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=submitlbentry&u=%s&t=%s&i=%u&s=%d&v=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
urle_user_name,
urle_login_token,
lboard_id,
@ -122,7 +128,7 @@ int rc_url_get_gameid(char* buffer, size_t size, const char* hash) {
int written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=gameid&m=%s",
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=gameid&m=%s",
hash
);
@ -145,7 +151,7 @@ int rc_url_get_patch(char* buffer, size_t size, const char* user_name, const cha
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=patch&u=%s&t=%s&g=%u",
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=patch&u=%s&t=%s&g=%u",
urle_user_name,
urle_login_token,
gameid
@ -181,7 +187,7 @@ int rc_url_login_with_password(char* buffer, size_t size, const char* user_name,
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=login&u=%s&p=%s",
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=login&u=%s&p=%s",
urle_user_name,
urle_password
);
@ -205,7 +211,7 @@ int rc_url_login_with_token(char* buffer, size_t size, const char* user_name, co
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=login&u=%s&t=%s",
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=login&u=%s&t=%s",
urle_user_name,
urle_login_token
);
@ -229,7 +235,7 @@ int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, con
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=%d",
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=unlocks&u=%s&t=%s&g=%u&h=%d",
urle_user_name,
urle_login_token,
gameid,
@ -255,7 +261,7 @@ int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const
written = snprintf(
buffer,
size,
"http://retroachievements.org/dorequest.php?r=postactivity&u=%s&t=%s&a=3&m=%u",
RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php?r=postactivity&u=%s&t=%s&a=3&m=%u",
urle_user_name,
urle_login_token,
gameid
@ -300,8 +306,7 @@ static int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_o
char num[16];
int chars = snprintf(num, sizeof(num), "%u", value);
if (chars + written < (int)buffer_size)
{
if (chars + written < (int)buffer_size) {
memcpy(&buffer[written], num, chars + 1);
*buffer_offset = written + chars;
return 0;
@ -314,13 +319,11 @@ static int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_o
static int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, const char* value)
{
int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param);
if (written > 0)
{
if (written > 0) {
buffer += written;
buffer_size -= written;
if (rc_url_encode(buffer, buffer_size, value) == 0)
{
if (rc_url_encode(buffer, buffer_size, value) == 0) {
written += (int)strlen(buffer);
*buffer_offset = written;
return 0;
@ -333,7 +336,7 @@ static int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_of
static int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size_t* buffer_offset,
const char* api, const char* user_name)
{
const char* base_url = "http://retroachievements.org/dorequest.php";
const char* base_url = RCHEEVOS_URL_PROTOCOL"://retroachievements.org/dorequest.php";
size_t written = strlen(base_url);
int failure = 0;
@ -343,7 +346,8 @@ static int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size
url_buffer[written++] = '?';
failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "r", api);
failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "u", user_name);
if (user_name)
failure |= rc_url_append_str(url_buffer, url_buffer_size, &written, "u", user_name);
*buffer_offset += written;
return failure;
@ -371,3 +375,28 @@ int rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, siz
return failure;
}
int rc_url_get_lboard_entries(char* buffer, size_t size, unsigned lboard_id, unsigned first_index, unsigned count)
{
size_t written = 0;
int failure = rc_url_build_dorequest(buffer, size, &written, "lbinfo", NULL);
failure |= rc_url_append_unum(buffer, size, &written, "i", lboard_id);
if (first_index > 1)
failure |= rc_url_append_unum(buffer, size, &written, "o", first_index - 1);
failure |= rc_url_append_unum(buffer, size, &written, "c", count);
return failure;
}
int rc_url_get_lboard_entries_near_user(char* buffer, size_t size, unsigned lboard_id, const char* user_name, unsigned count)
{
size_t written = 0;
int failure = rc_url_build_dorequest(buffer, size, &written, "lbinfo", NULL);
failure |= rc_url_append_unum(buffer, size, &written, "i", lboard_id);
failure |= rc_url_append_str(buffer, size, &written, "u", user_name);
failure |= rc_url_append_unum(buffer, size, &written, "c", count);
return failure;
}
#undef RCHEEVOS_URL_PROTOCOL