dep/rcheevos: Update to 0181d02

This commit is contained in:
Connor McLaughlin 2022-07-18 22:46:12 +10:00
parent af91fcf195
commit 3fb61865e5
19 changed files with 3717 additions and 24 deletions

View file

@ -1,4 +1,9 @@
add_library(rcheevos
include/rc_api_editor.h
include/rc_api_info.h
include/rc_api_request.h
include/rc_api_runtime.h
include/rc_api_user.h
include/rcheevos.h
include/rc_consoles.h
include/rc_error.h
@ -6,6 +11,12 @@ add_library(rcheevos
include/rc_runtime.h
include/rc_runtime_types.h
include/rc_url.h
src/rapi/rc_api_common.c
src/rapi/rc_api_common.h
src/rapi/rc_api_editor.c
src/rapi/rc_api_info.c
src/rapi/rc_api_runtime.c
src/rapi/rc_api_user.c
src/rcheevos/alloc.c
src/rcheevos/compat.c
src/rcheevos/condition.c

View file

@ -0,0 +1,247 @@
#ifndef RC_API_EDITOR_H
#define RC_API_EDITOR_H
#include "rc_api_request.h"
#ifdef __cplusplus
extern "C" {
#endif
/* --- Fetch Code Notes --- */
/**
* API parameters for a fetch code notes request.
*/
typedef struct rc_api_fetch_code_notes_request_t {
/* The unique identifier of the game */
unsigned game_id;
}
rc_api_fetch_code_notes_request_t;
/* A code note definiton */
typedef struct rc_api_code_note_t {
/* The address the note is associated to */
unsigned address;
/* The name of the use who last updated the note */
const char* author;
/* The contents of the note */
const char* note;
} rc_api_code_note_t;
/**
* Response data for a fetch code notes request.
*/
typedef struct rc_api_fetch_code_notes_response_t {
/* An array of code notes for the game */
rc_api_code_note_t* notes;
/* The number of items in the notes array */
unsigned num_notes;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_code_notes_response_t;
int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params);
int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response);
void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response);
/* --- Update Code Note --- */
/**
* API parameters for an update code note request.
*/
typedef struct rc_api_update_code_note_request_t {
/* The username of the developer */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the game */
unsigned game_id;
/* The address the note is associated to */
unsigned address;
/* The contents of the note (NULL or empty to delete a note) */
const char* note;
}
rc_api_update_code_note_request_t;
/**
* Response data for an update code note request.
*/
typedef struct rc_api_update_code_note_response_t {
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_update_code_note_response_t;
int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params);
int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response);
void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response);
/* --- Update Achievement --- */
/**
* API parameters for an update achievement request.
*/
typedef struct rc_api_update_achievement_request_t {
/* The username of the developer */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the achievement (0 to create a new achievement) */
unsigned achievement_id;
/* The unique identifier of the game */
unsigned game_id;
/* The name of the achievement */
const char* title;
/* The description of the achievement */
const char* description;
/* The badge name for the achievement */
const char* badge;
/* The serialized trigger for the achievement */
const char* trigger;
/* The number of points the achievement is worth */
unsigned points;
/* The category of the achievement */
unsigned category;
}
rc_api_update_achievement_request_t;
/**
* Response data for an update achievement request.
*/
typedef struct rc_api_update_achievement_response_t {
/* The unique identifier of the achievement */
unsigned achievement_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_update_achievement_response_t;
int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params);
int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response);
void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response);
/* --- Update Leaderboard --- */
/**
* API parameters for an update leaderboard request.
*/
typedef struct rc_api_update_leaderboard_request_t {
/* The username of the developer */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the leaderboard (0 to create a new leaderboard) */
unsigned leaderboard_id;
/* The unique identifier of the game */
unsigned game_id;
/* The name of the leaderboard */
const char* title;
/* The description of the leaderboard */
const char* description;
/* The start trigger for the leaderboard */
const char* start_trigger;
/* The submit trigger for the leaderboard */
const char* submit_trigger;
/* The cancel trigger for the leaderboard */
const char* cancel_trigger;
/* The value definition for the leaderboard */
const char* value_definition;
/* The format of leaderboard values */
const char* format;
/* Whether or not lower scores are better for the leaderboard */
int lower_is_better;
}
rc_api_update_leaderboard_request_t;
/**
* Response data for an update leaderboard request.
*/
typedef struct rc_api_update_leaderboard_response_t {
/* The unique identifier of the leaderboard */
unsigned leaderboard_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_update_leaderboard_response_t;
int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params);
int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response);
void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response);
/* --- Fetch Badge Range --- */
/**
* API parameters for a fetch badge range request.
*/
typedef struct rc_api_fetch_badge_range_request_t {
/* Unused */
unsigned unused;
}
rc_api_fetch_badge_range_request_t;
/**
* Response data for a fetch badge range request.
*/
typedef struct rc_api_fetch_badge_range_response_t {
/* The numeric identifier of the first valid badge ID */
unsigned first_badge_id;
/* The numeric identifier of the first unassigned badge ID */
unsigned next_badge_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_badge_range_response_t;
int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params);
int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response);
void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response);
/* --- Add Game Hash --- */
/**
* API parameters for an add game hash request.
*/
typedef struct rc_api_add_game_hash_request_t {
/* The username of the developer */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the game (0 to create a new game entry) */
unsigned game_id;
/* The unique identifier of the console for the game */
unsigned console_id;
/* The title of the game */
const char* title;
/* The hash being added */
const char* hash;
/* A description of the hash being added (usually the normalized ROM name) */
const char* hash_description;
}
rc_api_add_game_hash_request_t;
/**
* Response data for an update code note request.
*/
typedef struct rc_api_add_game_hash_response_t {
/* The unique identifier of the game */
unsigned game_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_add_game_hash_response_t;
int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params);
int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response);
void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response);
#ifdef __cplusplus
}
#endif
#endif /* RC_EDITOR_H */

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

@ -0,0 +1,63 @@
#ifndef RC_API_REQUEST_H
#define RC_API_REQUEST_H
#include "rc_error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* A block of memory for variable length data (like strings and arrays).
*/
typedef struct rc_api_buffer_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 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. */
char data[256];
}
rc_api_buffer_t;
/**
* A constructed request to send to the retroachievements server.
*/
typedef struct rc_api_request_t {
/* The URL to send the request to (contains protocol, host, path, and query args) */
const char* url;
/* Additional query args that should be sent via a POST command. If null, GET may be used */
const char* post_data;
/* Storage for the url and post_data */
rc_api_buffer_t buffer;
}
rc_api_request_t;
/**
* Common attributes for all server responses.
*/
typedef struct rc_api_response_t {
/* Server-provided success indicator (non-zero on failure) */
int succeeded;
/* Server-provided message associated to the failure */
const char* error_message;
/* Storage for the response data */
rc_api_buffer_t buffer;
}
rc_api_response_t;
void rc_api_destroy_request(rc_api_request_t* request);
void rc_api_set_host(const char* hostname);
void rc_api_set_image_host(const char* hostname);
#ifdef __cplusplus
}
#endif
#endif /* RC_API_REQUEST_H */

View file

@ -0,0 +1,291 @@
#ifndef RC_API_RUNTIME_H
#define RC_API_RUNTIME_H
#include "rc_api_request.h"
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/* --- Fetch Image --- */
/**
* API parameters for a fetch image request.
* NOTE: fetch image server response is the raw image data. There is no rc_api_process_fetch_image_response function.
*/
typedef struct rc_api_fetch_image_request_t {
/* The name of the image to fetch */
const char* image_name;
/* The type of image to fetch */
int image_type;
}
rc_api_fetch_image_request_t;
#define RC_IMAGE_TYPE_GAME 1
#define RC_IMAGE_TYPE_ACHIEVEMENT 2
#define RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED 3
#define RC_IMAGE_TYPE_USER 4
int rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params);
/* --- Resolve Hash --- */
/**
* API parameters for a resolve hash request.
*/
typedef struct rc_api_resolve_hash_request_t {
/* Unused - hash lookup does not require credentials */
const char* username;
/* Unused - hash lookup does not require credentials */
const char* api_token;
/* The generated hash of the game to be identified */
const char* game_hash;
}
rc_api_resolve_hash_request_t;
/**
* Response data for a resolve hash request.
*/
typedef struct rc_api_resolve_hash_response_t {
/* The unique identifier of the game, 0 if no match was found */
unsigned game_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_resolve_hash_response_t;
int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params);
int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response);
void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response);
/* --- Fetch Game Data --- */
/**
* API parameters for a fetch game data request.
*/
typedef struct rc_api_fetch_game_data_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 game */
unsigned game_id;
}
rc_api_fetch_game_data_request_t;
/* A leaderboard definition */
typedef struct rc_api_leaderboard_definition_t {
/* The unique identifier of the leaderboard */
unsigned id;
/* The format to pass to rc_format_value to format the leaderboard value */
int format;
/* 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;
/* Non-zero if lower values are better for this leaderboard */
int lower_is_better;
/* Non-zero if the leaderboard should not be displayed in a list of leaderboards */
int hidden;
}
rc_api_leaderboard_definition_t;
/* An achievement definition */
typedef struct rc_api_achievement_definition_t {
/* The unique identifier of the achievement */
unsigned id;
/* The number of points the achievement is worth */
unsigned points;
/* The achievement category (core, unofficial) */
unsigned category;
/* The title of the achievement */
const char* title;
/* The dscription of the achievement */
const char* description;
/* The definition of the achievement to be passed to rc_runtime_activate_achievement */
const char* definition;
/* The author of the achievment */
const char* author;
/* The image name for the achievement badge */
const char* badge_name;
/* When the achievement was first uploaded to the server */
time_t created;
/* When the achievement was last modified on the server */
time_t updated;
}
rc_api_achievement_definition_t;
#define RC_ACHIEVEMENT_CATEGORY_CORE 3
#define RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL 5
/**
* Response data for a fetch game data request.
*/
typedef struct rc_api_fetch_game_data_response_t {
/* The unique identifier of the game */
unsigned id;
/* The console associated to the game */
unsigned console_id;
/* The title of the game */
const char* title;
/* The image name for the game badge */
const char* image_name;
/* The rich presence script for the game to be passed to rc_runtime_activate_richpresence */
const char* rich_presence_script;
/* An array of achievements for the game */
rc_api_achievement_definition_t* achievements;
/* The number of items in the achievements array */
unsigned num_achievements;
/* An array of leaderboards for the game */
rc_api_leaderboard_definition_t* leaderboards;
/* The number of items in the leaderboards array */
unsigned num_leaderboards;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_game_data_response_t;
int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params);
int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response);
void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response);
/* --- Ping --- */
/**
* API parameters for a ping request.
*/
typedef struct rc_api_ping_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 game */
unsigned game_id;
/* (optional) The current rich presence evaluation for the user */
const char* rich_presence;
}
rc_api_ping_request_t;
/**
* Response data for a ping request.
*/
typedef struct rc_api_ping_response_t {
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_ping_response_t;
int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params);
int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response);
void rc_api_destroy_ping_response(rc_api_ping_response_t* response);
/* --- Award Achievement --- */
/**
* API parameters for an award achievement request.
*/
typedef struct rc_api_award_achievement_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;
/* Non-zero if the achievement was earned in hardcore */
int hardcore;
/* The hash associated to the game being played */
const char* game_hash;
}
rc_api_award_achievement_request_t;
/**
* Response data for an award achievement request.
*/
typedef struct rc_api_award_achievement_response_t {
/* The unique identifier of the achievement that was awarded */
unsigned awarded_achievement_id;
/* The updated player score */
unsigned new_player_score;
/* The number of achievements the user has not yet unlocked for this game
* (in hardcore/non-hardcore per hardcore flag in request) */
unsigned achievements_remaining;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_award_achievement_response_t;
int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params);
int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response);
void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response);
/* --- Submit Leaderboard Entry --- */
/**
* API parameters for a submit lboard entry request.
*/
typedef struct rc_api_submit_lboard_entry_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 leaderboard */
unsigned leaderboard_id;
/* The value being submitted */
int score;
/* The hash associated to the game being played */
const char* game_hash;
}
rc_api_submit_lboard_entry_request_t;
/* A leaderboard entry */
typedef struct rc_api_lboard_entry_t {
/* The user associated to the entry */
const char* username;
/* The rank of the entry */
unsigned rank;
/* The value of the entry */
int score;
}
rc_api_lboard_entry_t;
/**
* Response data for a submit lboard entry request.
*/
typedef struct rc_api_submit_lboard_entry_response_t {
/* The value that was submitted */
int submitted_score;
/* The player's best submitted value */
int best_score;
/* The player's new rank within the leaderboard */
unsigned new_rank;
/* The total number of entries in the leaderboard */
unsigned num_entries;
/* An array of the top entries for the leaderboard */
rc_api_lboard_entry_t* top_entries;
/* The number of items in the top_entries array */
unsigned num_top_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_submit_lboard_entry_response_t;
int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params);
int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response);
void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response);
#ifdef __cplusplus
}
#endif
#endif /* RC_API_RUNTIME_H */

View file

@ -0,0 +1,117 @@
#ifndef RC_API_USER_H
#define RC_API_USER_H
#include "rc_api_request.h"
#ifdef __cplusplus
extern "C" {
#endif
/* --- Login --- */
/**
* API parameters for a login request.
* If both password and api_token are provided, api_token will be ignored.
*/
typedef struct rc_api_login_request_t {
/* The username of the player being logged in */
const char* username;
/* The API token from a previous login */
const char* api_token;
/* The player's password */
const char* password;
}
rc_api_login_request_t;
/**
* Response data for a login request.
*/
typedef struct rc_api_login_response_t {
/* The case-corrected username of the player */
const char* username;
/* The API token to use for all future requests */
const char* api_token;
/* The current score of the player */
unsigned score;
/* The number of unread messages waiting for the player on the web site */
unsigned num_unread_messages;
/* The preferred name to display for the player */
const char* display_name;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_login_response_t;
int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params);
int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response);
void rc_api_destroy_login_response(rc_api_login_response_t* response);
/* --- Start Session --- */
/**
* API parameters for a start session request.
*/
typedef struct rc_api_start_session_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 game */
unsigned game_id;
}
rc_api_start_session_request_t;
/**
* Response data for a start session request.
*/
typedef struct rc_api_start_session_response_t {
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_start_session_response_t;
int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params);
int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response);
void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response);
/* --- Fetch User Unlocks --- */
/**
* API parameters for a fetch user unlocks request.
*/
typedef struct rc_api_fetch_user_unlocks_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 game */
unsigned game_id;
/* Non-zero to fetch hardcore unlocks, 0 to fetch non-hardcore unlocks */
int hardcore;
}
rc_api_fetch_user_unlocks_request_t;
/**
* Response data for a fetch user unlocks request.
*/
typedef struct rc_api_fetch_user_unlocks_response_t {
/* An array of achievement IDs previously unlocked by the user */
unsigned* achievement_ids;
/* The number of items in the achievement_ids array */
unsigned num_achievement_ids;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_user_unlocks_response_t;
int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params);
int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response);
void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response);
#ifdef __cplusplus
}
#endif
#endif /* RC_API_H */

View file

@ -81,6 +81,7 @@ enum {
RC_CONSOLE_MEGADUCK = 69,
RC_CONSOLE_ZEEBO = 70,
RC_CONSOLE_ARDUBOY = 71,
RC_CONSOLE_WASM4 = 72,
RC_CONSOLE_HUBS = 100,
RC_CONSOLE_EVENTS = 101

View file

@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\msvc\vsprops\Configurations.props" />
<ItemGroup>
<ClCompile Include="src\rapi\rc_api_common.c" />
<ClCompile Include="src\rapi\rc_api_editor.c" />
<ClCompile Include="src\rapi\rc_api_info.c" />
<ClCompile Include="src\rapi\rc_api_runtime.c" />
<ClCompile Include="src\rapi\rc_api_user.c" />
<ClCompile Include="src\rcheevos\alloc.c" />
<ClCompile Include="src\rcheevos\compat.c" />
<ClCompile Include="src\rcheevos\condition.c" />
@ -24,23 +28,26 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\rcheevos.h" />
<ClInclude Include="include\rc_api_editor.h" />
<ClInclude Include="include\rc_api_info.h" />
<ClInclude Include="include\rc_api_request.h" />
<ClInclude Include="include\rc_api_runtime.h" />
<ClInclude Include="include\rc_api_user.h" />
<ClInclude Include="include\rc_consoles.h" />
<ClInclude Include="include\rc_error.h" />
<ClInclude Include="include\rc_hash.h" />
<ClInclude Include="include\rc_runtime.h" />
<ClInclude Include="include\rc_runtime_types.h" />
<ClInclude Include="include\rc_url.h" />
<ClInclude Include="src\rapi\rc_api_common.h" />
<ClInclude Include="src\rcheevos\rc_compat.h" />
<ClInclude Include="src\rcheevos\rc_internal.h" />
<ClInclude Include="src\rhash\md5.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{4BA0A6D4-3AE1-42B2-9347-096FD023FF64}</ProjectGuid>
</PropertyGroup>
<Import Project="..\msvc\vsprops\StaticLibrary.props" />
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
@ -48,6 +55,5 @@
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="..\msvc\vsprops\Targets.props" />
</Project>

View file

@ -13,6 +13,9 @@
<Filter Include="include">
<UniqueIdentifier>{01fc10b0-c122-461b-b75a-f97c8b89d627}</UniqueIdentifier>
</Filter>
<Filter Include="rapi">
<UniqueIdentifier>{92c73497-6936-4a1c-9534-4f2ffb64cba2}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\rurl\url.c">
@ -69,6 +72,21 @@
<ClCompile Include="src\rhash\hash.c">
<Filter>rhash</Filter>
</ClCompile>
<ClCompile Include="src\rapi\rc_api_editor.c">
<Filter>rapi</Filter>
</ClCompile>
<ClCompile Include="src\rapi\rc_api_info.c">
<Filter>rapi</Filter>
</ClCompile>
<ClCompile Include="src\rapi\rc_api_runtime.c">
<Filter>rapi</Filter>
</ClCompile>
<ClCompile Include="src\rapi\rc_api_user.c">
<Filter>rapi</Filter>
</ClCompile>
<ClCompile Include="src\rapi\rc_api_common.c">
<Filter>rapi</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\rc_consoles.h">
@ -101,5 +119,23 @@
<ClInclude Include="src\rhash\md5.h">
<Filter>rhash</Filter>
</ClInclude>
<ClInclude Include="include\rc_api_info.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include\rc_api_request.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include\rc_api_runtime.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include\rc_api_user.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="include\rc_api_editor.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="src\rapi\rc_api_common.h">
<Filter>rapi</Filter>
</ClInclude>
</ItemGroup>
</Project>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,81 @@
#ifndef RC_API_COMMON_H
#define RC_API_COMMON_H
#include "rc_api_request.h"
#include <stddef.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct rc_api_url_builder_t {
char* write;
char* start;
char* end;
rc_api_buffer_t* buffer;
int result;
}
rc_api_url_builder_t;
void rc_url_builder_init(rc_api_url_builder_t* builder, rc_api_buffer_t* buffer, size_t estimated_size);
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);
typedef struct rc_json_field_t {
const char* name;
const char* value_start;
const char* value_end;
unsigned array_size;
}
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, unsigned default_value);
void rc_json_get_optional_bool(int* out, const rc_json_field_t* field, const char* field_name, int default_value);
int rc_json_get_required_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
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);
char* rc_buf_reserve(rc_api_buffer_t* buffer, size_t amount);
void rc_buf_consume(rc_api_buffer_t* buffer, const char* start, char* end);
void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount);
void rc_url_builder_append_encoded_str(rc_api_url_builder_t* builder, const char* str);
void rc_url_builder_append_num_param(rc_api_url_builder_t* builder, const char* param, int value);
void rc_url_builder_append_unum_param(rc_api_url_builder_t* builder, const char* param, unsigned value);
void rc_url_builder_append_str_param(rc_api_url_builder_t* builder, const char* param, const char* value);
void rc_api_url_build_dorequest_url(rc_api_request_t* request);
int rc_api_url_build_dorequest(rc_api_url_builder_t* builder, const char* api, const char* username, const char* api_token);
void rc_api_format_md5(char checksum[33], const unsigned char digest[16]);
#ifdef __cplusplus
}
#endif
#endif /* RC_API_COMMON_H */

View file

@ -0,0 +1,443 @@
#include "rc_api_editor.h"
#include "rc_api_common.h"
#include "../rcheevos/rc_compat.h"
#include "../rhash/md5.h"
#include <stdlib.h>
#include <string.h>
/* --- Fetch Code Notes --- */
int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "codenotes2");
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
request->post_data = rc_url_builder_finalize(&builder);
return builder.result;
}
int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response) {
rc_json_field_t iterator;
rc_api_code_note_t* note;
const char* address_str;
const char* last_author = "";
size_t last_author_len = 0;
size_t len;
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"CodeNotes"}
};
rc_json_field_t note_fields[] = {
{"Address"},
{"User"},
{"Note"}
};
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 || !response->response.succeeded)
return result;
if (!rc_json_get_required_array(&response->num_notes, &iterator, &response->response, &fields[2], "CodeNotes"))
return RC_MISSING_VALUE;
if (response->num_notes) {
response->notes = (rc_api_code_note_t*)rc_buf_alloc(&response->response.buffer, response->num_notes * sizeof(rc_api_code_note_t));
if (!response->notes)
return RC_OUT_OF_MEMORY;
note = response->notes;
while (rc_json_get_array_entry_object(note_fields, sizeof(note_fields) / sizeof(note_fields[0]), &iterator)) {
/* an empty note represents a record that was deleted on the server */
/* a note set to '' also represents a deleted note (remnant of a bug) */
/* NOTE: len will include the quotes */
len = note_fields[2].value_end - note_fields[2].value_start;
if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) {
--response->num_notes;
continue;
}
if (!rc_json_get_required_string(&address_str, &response->response, &note_fields[0], "Address"))
return RC_MISSING_VALUE;
note->address = (unsigned)strtol(address_str, NULL, 16);
if (!rc_json_get_required_string(&note->note, &response->response, &note_fields[2], "Note"))
return RC_MISSING_VALUE;
len = note_fields[1].value_end - note_fields[1].value_start;
if (len == last_author_len && memcmp(note_fields[1].value_start, last_author, len) == 0) {
note->author = last_author;
}
else {
if (!rc_json_get_required_string(&note->author, &response->response, &note_fields[1], "User"))
return RC_MISSING_VALUE;
last_author = note->author;
last_author_len = len;
}
++note;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Update Code Note --- */
int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 128);
if (!rc_api_url_build_dorequest(&builder, "submitcodenote", api_params->username, api_params->api_token))
return builder.result;
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
rc_url_builder_append_unum_param(&builder, "m", api_params->address);
if (api_params->note && *api_params->note)
rc_url_builder_append_str_param(&builder, "n", api_params->note);
request->post_data = rc_url_builder_finalize(&builder);
return builder.result;
}
int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"}
/* unused fields
{"GameID"},
{"Address"},
{"Note"}
*/
};
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 || !response->response.succeeded)
return result;
return RC_OK;
}
void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Update Achievement --- */
int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params) {
rc_api_url_builder_t builder;
char buffer[33];
md5_state_t md5;
md5_byte_t hash[16];
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0 || api_params->category == 0)
return RC_INVALID_STATE;
if (!api_params->title || !*api_params->title)
return RC_INVALID_STATE;
if (!api_params->description || !*api_params->description)
return RC_INVALID_STATE;
if (!api_params->trigger || !*api_params->trigger)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 128);
if (!rc_api_url_build_dorequest(&builder, "uploadachievement", api_params->username, api_params->api_token))
return builder.result;
if (api_params->achievement_id)
rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
rc_url_builder_append_str_param(&builder, "n", api_params->title);
rc_url_builder_append_str_param(&builder, "d", api_params->description);
rc_url_builder_append_str_param(&builder, "m", api_params->trigger);
rc_url_builder_append_unum_param(&builder, "z", api_params->points);
rc_url_builder_append_unum_param(&builder, "f", api_params->category);
if (api_params->badge)
rc_url_builder_append_str_param(&builder, "b", api_params->badge);
/* Evaluate the signature. */
md5_init(&md5);
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
md5_append(&md5, (md5_byte_t*)"SECRET", 6);
snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)"SEC", 3);
md5_append(&md5, (md5_byte_t*)api_params->trigger, (int)strlen(api_params->trigger));
snprintf(buffer, sizeof(buffer), "%u", api_params->points);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)"RE2", 3);
snprintf(buffer, sizeof(buffer), "%u", api_params->points * 3);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_finish(&md5, hash);
rc_api_format_md5(buffer, hash);
rc_url_builder_append_str_param(&builder, "h", buffer);
request->post_data = rc_url_builder_finalize(&builder);
return builder.result;
}
int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"AchievementID"}
};
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 || !response->response.succeeded)
return result;
if (!rc_json_get_required_unum(&response->achievement_id, &response->response, &fields[2], "AchievementID"))
return RC_MISSING_VALUE;
return RC_OK;
}
void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Update Leaderboard --- */
int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params) {
rc_api_url_builder_t builder;
char buffer[33];
md5_state_t md5;
md5_byte_t hash[16];
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
if (!api_params->title || !*api_params->title)
return RC_INVALID_STATE;
if (!api_params->description)
return RC_INVALID_STATE;
if (!api_params->start_trigger || !*api_params->start_trigger)
return RC_INVALID_STATE;
if (!api_params->submit_trigger || !*api_params->submit_trigger)
return RC_INVALID_STATE;
if (!api_params->cancel_trigger || !*api_params->cancel_trigger)
return RC_INVALID_STATE;
if (!api_params->value_definition || !*api_params->value_definition)
return RC_INVALID_STATE;
if (!api_params->format || !*api_params->format)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 128);
if (!rc_api_url_build_dorequest(&builder, "uploadleaderboard", api_params->username, api_params->api_token))
return builder.result;
if (api_params->leaderboard_id)
rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
rc_url_builder_append_str_param(&builder, "n", api_params->title);
rc_url_builder_append_str_param(&builder, "d", api_params->description);
rc_url_builder_append_str_param(&builder, "s", api_params->start_trigger);
rc_url_builder_append_str_param(&builder, "b", api_params->submit_trigger);
rc_url_builder_append_str_param(&builder, "c", api_params->cancel_trigger);
rc_url_builder_append_str_param(&builder, "l", api_params->value_definition);
rc_url_builder_append_num_param(&builder, "w", api_params->lower_is_better);
rc_url_builder_append_str_param(&builder, "f", api_params->format);
/* Evaluate the signature. */
md5_init(&md5);
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
md5_append(&md5, (md5_byte_t*)"SECRET", 6);
snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)"SEC", 3);
md5_append(&md5, (md5_byte_t*)api_params->start_trigger, (int)strlen(api_params->start_trigger));
md5_append(&md5, (md5_byte_t*)api_params->submit_trigger, (int)strlen(api_params->submit_trigger));
md5_append(&md5, (md5_byte_t*)api_params->cancel_trigger, (int)strlen(api_params->cancel_trigger));
md5_append(&md5, (md5_byte_t*)api_params->value_definition, (int)strlen(api_params->value_definition));
md5_append(&md5, (md5_byte_t*)"RE2", 3);
md5_append(&md5, (md5_byte_t*)api_params->format, (int)strlen(api_params->format));
md5_finish(&md5, hash);
rc_api_format_md5(buffer, hash);
rc_url_builder_append_str_param(&builder, "h", buffer);
request->post_data = rc_url_builder_finalize(&builder);
return builder.result;
}
int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"LeaderboardID"}
};
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 || !response->response.succeeded)
return result;
if (!rc_json_get_required_unum(&response->leaderboard_id, &response->response, &fields[2], "LeaderboardID"))
return RC_MISSING_VALUE;
return RC_OK;
}
void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Fetch Badge Range --- */
int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "badgeiter");
request->post_data = rc_url_builder_finalize(&builder);
return builder.result;
}
int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"FirstBadge"},
{"NextBadge"}
};
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 || !response->response.succeeded)
return result;
if (!rc_json_get_required_unum(&response->first_badge_id, &response->response, &fields[2], "FirstBadge"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->next_badge_id, &response->response, &fields[3], "NextBadge"))
return RC_MISSING_VALUE;
return RC_OK;
}
void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Add Game Hash --- */
int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_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;
if (!api_params->hash || !*api_params->hash)
return RC_INVALID_STATE;
if (api_params->game_id == 0 && (!api_params->title || !*api_params->title))
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 128);
if (!rc_api_url_build_dorequest(&builder, "submitgametitle", api_params->username, api_params->api_token))
return builder.result;
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
rc_url_builder_append_str_param(&builder, "m", api_params->hash);
if (api_params->title)
rc_url_builder_append_str_param(&builder, "i", api_params->title);
if (api_params->game_id)
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
if (api_params->hash_description && *api_params->hash_description)
rc_url_builder_append_str_param(&builder, "d", api_params->hash_description);
request->post_data = rc_url_builder_finalize(&builder);
return builder.result;
}
int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"Response"}
};
rc_json_field_t response_fields[] = {
{"GameID"}
/* unused fields
{"MD5"},
{"ConsoleID"},
{"GameTitle"},
{"Success"}
*/
};
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 || !response->response.succeeded)
return result;
if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[0], "GameID"))
return RC_MISSING_VALUE;
return RC_OK;
}
void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}

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

@ -0,0 +1,526 @@
#include "rc_api_runtime.h"
#include "rc_api_common.h"
#include "rc_runtime.h"
#include "rc_runtime_types.h"
#include "../rcheevos/rc_compat.h"
#include "../rhash/md5.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* --- Resolve Hash --- */
int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (!api_params->game_hash || !*api_params->game_hash)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
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;
}
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"},
};
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;
rc_json_get_required_unum(&response->game_id, &response->response, &fields[2], "GameID");
return RC_OK;
}
void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Fetch Game Data --- */
int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "patch", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
request->post_data = rc_url_builder_finalize(&builder);
}
return builder.result;
}
int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response) {
rc_api_achievement_definition_t* achievement;
rc_api_leaderboard_definition_t* leaderboard;
rc_json_field_t iterator;
const char* str;
const char* last_author = "";
size_t last_author_len = 0;
size_t len;
unsigned timet;
int result;
char format[16];
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"PatchData"} /* nested object */
};
rc_json_field_t patchdata_fields[] = {
{"ID"},
{"Title"},
{"ConsoleID"},
{"ImageIcon"},
{"RichPresencePatch"},
{"Achievements"}, /* array */
{"Leaderboards"} /* array */
/* unused fields
{"ForumTopicID"},
{"Flags"},
* unused fields */
};
rc_json_field_t achievement_fields[] = {
{"ID"},
{"Title"},
{"Description"},
{"Flags"},
{"Points"},
{"MemAddr"},
{"Author"},
{"BadgeName"},
{"Created"},
{"Modified"}
};
rc_json_field_t leaderboard_fields[] = {
{"ID"},
{"Title"},
{"Description"},
{"Mem"},
{"Format"},
{"LowerIsBetter"},
{"Hidden"}
};
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 || !response->response.succeeded)
return result;
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"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->title, &response->response, &patchdata_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->console_id, &response->response, &patchdata_fields[2], "ConsoleID"))
return RC_MISSING_VALUE;
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
if (patchdata_fields[3].value_end) {
str = patchdata_fields[3].value_end - 5;
if (memcmp(str, ".png\"", 5) == 0) {
patchdata_fields[3].value_end -= 5;
while (str > patchdata_fields[3].value_start && str[-1] != '/')
--str;
patchdata_fields[3].value_start = str;
}
}
rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", "");
/* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards.
determine how much space each takes as a string in the JSON, then subtract out the non-data (field names, punctuation)
and add space for the structures. */
len = patchdata_fields[4].value_end - patchdata_fields[4].value_start; /* rich presence */
len += (patchdata_fields[5].value_end - patchdata_fields[5].value_start) - /* achievements */
patchdata_fields[5].array_size * (130 - sizeof(rc_api_achievement_definition_t));
len += (patchdata_fields[6].value_end - patchdata_fields[6].value_start) - /* leaderboards */
patchdata_fields[6].array_size * (60 - sizeof(rc_api_leaderboard_definition_t));
rc_buf_reserve(&response->response.buffer, len);
/* end estimation */
rc_json_get_optional_string(&response->rich_presence_script, &response->response, &patchdata_fields[4], "RichPresencePatch", "");
if (!response->rich_presence_script)
response->rich_presence_script = "";
if (!rc_json_get_required_array(&response->num_achievements, &iterator, &response->response, &patchdata_fields[5], "Achievements"))
return RC_MISSING_VALUE;
if (response->num_achievements) {
response->achievements = (rc_api_achievement_definition_t*)rc_buf_alloc(&response->response.buffer, response->num_achievements * sizeof(rc_api_achievement_definition_t));
if (!response->achievements)
return RC_OUT_OF_MEMORY;
achievement = response->achievements;
while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) {
if (!rc_json_get_required_unum(&achievement->id, &response->response, &achievement_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->title, &response->response, &achievement_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->description, &response->response, &achievement_fields[2], "Description"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&achievement->category, &response->response, &achievement_fields[3], "Flags"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&achievement->points, &response->response, &achievement_fields[4], "Points"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->definition, &response->response, &achievement_fields[5], "MemAddr"))
return RC_MISSING_VALUE;
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) {
achievement->author = last_author;
}
else {
if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author"))
return RC_MISSING_VALUE;
last_author = achievement->author;
last_author_len = len;
}
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created"))
return RC_MISSING_VALUE;
achievement->created = (time_t)timet;
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[9], "Modified"))
return RC_MISSING_VALUE;
achievement->updated = (time_t)timet;
++achievement;
}
}
if (!rc_json_get_required_array(&response->num_leaderboards, &iterator, &response->response, &patchdata_fields[6], "Leaderboards"))
return RC_MISSING_VALUE;
if (response->num_leaderboards) {
response->leaderboards = (rc_api_leaderboard_definition_t*)rc_buf_alloc(&response->response.buffer, response->num_leaderboards * sizeof(rc_api_leaderboard_definition_t));
if (!response->leaderboards)
return RC_OUT_OF_MEMORY;
leaderboard = response->leaderboards;
while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) {
if (!rc_json_get_required_unum(&leaderboard->id, &response->response, &leaderboard_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->title, &response->response, &leaderboard_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->description, &response->response, &leaderboard_fields[2], "Description"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->definition, &response->response, &leaderboard_fields[3], "Mem"))
return RC_MISSING_VALUE;
rc_json_get_optional_bool(&leaderboard->lower_is_better, &leaderboard_fields[5], "LowerIsBetter", 0);
rc_json_get_optional_bool(&leaderboard->hidden, &leaderboard_fields[6], "Hidden", 0);
if (!leaderboard_fields[4].value_end)
return RC_MISSING_VALUE;
len = leaderboard_fields[4].value_end - leaderboard_fields[4].value_start - 2;
if (len < sizeof(format) - 1) {
memcpy(format, leaderboard_fields[4].value_start + 1, len);
format[len] = '\0';
leaderboard->format = rc_parse_format(format);
}
else {
leaderboard->format = RC_FORMAT_VALUE;
}
++leaderboard;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Ping --- */
int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "ping", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
if (api_params->rich_presence && *api_params->rich_presence)
rc_url_builder_append_str_param(&builder, "m", api_params->rich_presence);
request->post_data = rc_url_builder_finalize(&builder);
}
return builder.result;
}
int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response) {
rc_json_field_t fields[] = {
{"Success"},
{"Error"}
};
memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer);
return rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
}
void rc_api_destroy_ping_response(rc_api_ping_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Award Achievement --- */
int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params) {
rc_api_url_builder_t builder;
char buffer[33];
md5_state_t md5;
md5_byte_t digest[16];
rc_api_url_build_dorequest_url(request);
if (api_params->achievement_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 96);
if (rc_api_url_build_dorequest(&builder, "awardachievement", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
if (api_params->game_hash && *api_params->game_hash)
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
/* Evaluate the signature. */
md5_init(&md5);
snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
snprintf(buffer, sizeof(buffer), "%d", api_params->hardcore ? 1 : 0);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_finish(&md5, digest);
rc_api_format_md5(buffer, digest);
rc_url_builder_append_str_param(&builder, "v", buffer);
request->post_data = rc_url_builder_finalize(&builder);
}
return builder.result;
}
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"}
};
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 (!response->response.succeeded) {
if (response->response.error_message &&
memcmp(response->response.error_message, "User already has", 16) == 0) {
/* not really an error, the achievement is unlocked, just not by the current call.
* hardcore: User already has hardcore and regular achievements awarded.
* non-hardcore: User already has this achievement awarded.
*/
response->response.succeeded = 1;
} else {
return result;
}
}
rc_json_get_optional_unum(&response->new_player_score, &fields[2], "Score", 0);
rc_json_get_optional_unum(&response->awarded_achievement_id, &fields[3], "AchievementID", 0);
rc_json_get_optional_unum(&response->achievements_remaining, &fields[4], "AchievementsRemaining", (unsigned)-1);
return RC_OK;
}
void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Submit Leaderboard Entry --- */
int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params) {
rc_api_url_builder_t builder;
char buffer[33];
md5_state_t md5;
md5_byte_t digest[16];
rc_api_url_build_dorequest_url(request);
if (api_params->leaderboard_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 96);
if (rc_api_url_build_dorequest(&builder, "submitlbentry", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
rc_url_builder_append_num_param(&builder, "s", api_params->score);
if (api_params->game_hash && *api_params->game_hash)
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
/* Evaluate the signature. */
md5_init(&md5);
snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
snprintf(buffer, sizeof(buffer), "%d", api_params->score);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_finish(&md5, digest);
rc_api_format_md5(buffer, digest);
rc_url_builder_append_str_param(&builder, "v", buffer);
request->post_data = rc_url_builder_finalize(&builder);
}
return builder.result;
}
int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response) {
rc_api_lboard_entry_t* entry;
rc_json_field_t iterator;
const char* str;
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"Response"} /* nested object */
};
rc_json_field_t response_fields[] = {
{"Score"},
{"BestScore"},
{"RankInfo"}, /* nested object */
{"TopEntries"} /* array */
/* unused fields
{"LBData"}, / * array * /
{"ScoreFormatted"},
{"TopEntriesFriends"}, / * array * /
* unused fields */
};
/* unused fields
rc_json_field_t lbdata_fields[] = {
{"Format"},
{"LeaderboardID"},
{"GameID"},
{"Title"},
{"LowerIsBetter"}
};
* unused fields */
rc_json_field_t entry_fields[] = {
{"User"},
{"Rank"},
{"Score"}
/* unused fields
{"DateSubmitted"},
* unused fields */
};
rc_json_field_t rank_info_fields[] = {
{"Rank"},
{"NumEntries"}
/* unused fields
{"LowerIsBetter"},
{"UserRank"},
* unused fields */
};
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 || !response->response.succeeded)
return result;
if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&response->submitted_score, &response->response, &response_fields[0], "Score"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&response->best_score, &response->response, &response_fields[1], "BestScore"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_object(rank_info_fields, sizeof(rank_info_fields) / sizeof(rank_info_fields[0]), &response->response, &response_fields[2], "RankInfo"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->new_rank, &response->response, &rank_info_fields[0], "Rank"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&str, &response->response, &rank_info_fields[1], "NumEntries"))
return RC_MISSING_VALUE;
response->num_entries = (unsigned)atoi(str);
if (!rc_json_get_required_array(&response->num_top_entries, &iterator, &response->response, &response_fields[3], "TopEntries"))
return RC_MISSING_VALUE;
if (response->num_top_entries) {
response->top_entries = (rc_api_lboard_entry_t*)rc_buf_alloc(&response->response.buffer, response->num_top_entries * sizeof(rc_api_lboard_entry_t));
if (!response->top_entries)
return RC_OUT_OF_MEMORY;
entry = response->top_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_num(&entry->score, &response->response, &entry_fields[2], "Score"))
return RC_MISSING_VALUE;
++entry;
}
}
return RC_OK;
}
void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}

View file

@ -0,0 +1,153 @@
#include "rc_api_user.h"
#include "rc_api_common.h"
#include <string.h>
/* --- Login --- */
int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (!api_params->username || !*api_params->username)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "login");
rc_url_builder_append_str_param(&builder, "u", api_params->username);
if (api_params->password && api_params->password[0])
rc_url_builder_append_str_param(&builder, "p", api_params->password);
else if (api_params->api_token && api_params->api_token[0])
rc_url_builder_append_str_param(&builder, "t", api_params->api_token);
else
return RC_INVALID_STATE;
request->post_data = rc_url_builder_finalize(&builder);
return builder.result;
}
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"}
};
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 || !response->response.succeeded)
return result;
if (!rc_json_get_required_string(&response->username, &response->response, &fields[2], "User"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->api_token, &response->response, &fields[3], "Token"))
return RC_MISSING_VALUE;
rc_json_get_optional_unum(&response->score, &fields[4], "Score", 0);
rc_json_get_optional_unum(&response->num_unread_messages, &fields[5], "Messages", 0);
rc_json_get_optional_string(&response->display_name, &response->response, &fields[6], "DisplayName", response->username);
return RC_OK;
}
void rc_api_destroy_login_response(rc_api_login_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Start Session --- */
int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "postactivity", api_params->username, api_params->api_token)) {
/* activity type enum (only 3 is used )
* 1 = earned achievement - handled by awardachievement
* 2 = logged in - handled by login
* 3 = started playing
* 4 = uploaded achievement - handled by uploadachievement
* 5 = modified achievement - handled by uploadachievement
*/
rc_url_builder_append_unum_param(&builder, "a", 3);
rc_url_builder_append_unum_param(&builder, "m", api_params->game_id);
request->post_data = rc_url_builder_finalize(&builder);
}
return builder.result;
}
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"}
};
memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer);
return rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
}
void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}
/* --- Fetch User Unlocks --- */
int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "unlocks", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
request->post_data = rc_url_builder_finalize(&builder);
}
return builder.result;
}
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"}
/* unused fields
{ "GameID" },
{ "HardcoreMode" }
* unused fields */
};
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 || !response->response.succeeded)
return result;
result = rc_json_get_required_unum_array(&response->achievement_ids, &response->num_achievement_ids, &response->response, &fields[2], "UserUnlocks");
return result;
}
void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response) {
rc_buf_destroy(&response->response.buffer);
}

View file

@ -201,6 +201,9 @@ const char* rc_console_name(int console_id)
case RC_CONSOLE_VIRTUAL_BOY:
return "Virtual Boy";
case RC_CONSOLE_WASM4:
return "WASM-4";
case RC_CONSOLE_WII:
return "Wii";
@ -242,6 +245,14 @@ static const rc_memory_region_t _rc_memory_regions_3do[] = {
};
static const rc_memory_regions_t rc_memory_regions_3do = { _rc_memory_regions_3do, 1 };
/* ===== Amiga ===== */
/* http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node00D3.html */
static const rc_memory_region_t _rc_memory_regions_amiga[] = {
{ 0x000000U, 0x07FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Main RAM" }, /* 512KB main RAM */
{ 0x080000U, 0x0FFFFFU, 0x080000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Extended RAM" }, /* 512KB extended RAM */
};
static const rc_memory_regions_t rc_memory_regions_amiga = { _rc_memory_regions_amiga, 2 };
/* ===== Amstrad CPC ===== */
/* http://www.cpcalive.com/docs/amstrad_cpc_6128_memory_map.html */
/* https://www.cpcwiki.eu/index.php/File:AWMG_page151.jpg */
@ -324,6 +335,20 @@ static const rc_memory_region_t _rc_memory_regions_colecovision[] = {
};
static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_regions_colecovision, 1 };
/* ===== Commodore 64 ===== */
/* https://www.c64-wiki.com/wiki/Memory_Map */
/* https://sta.c64.org/cbm64mem.html */
static const rc_memory_region_t _rc_memory_regions_c64[] = {
{ 0x000000U, 0x0003FFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" },
{ 0x000400U, 0x0007FFU, 0x000400U, RC_MEMORY_TYPE_VIDEO_RAM, "Screen RAM" },
{ 0x000800U, 0x009FFFU, 0x000800U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* BASIC Program Storage Area */
{ 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Machine Language Storage Area / BASIC ROM Area */
{ 0x00C000U, 0x00CFFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Machine Language Storage Area */
{ 0x00D000U, 0x00DFFFU, 0x00D000U, RC_MEMORY_TYPE_SYSTEM_RAM, "I/O Area" }, /* also Character ROM */
{ 0x00E000U, 0x00FFFFU, 0x00E000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Machine Language Storage Area / Kernal ROM */
};
static const rc_memory_regions_t rc_memory_regions_c64 = { _rc_memory_regions_c64, 7 };
/* ===== Dreamcast ===== */
/* http://archiv.sega-dc.de/munkeechuff/hardware/Memory.html */
static const rc_memory_region_t _rc_memory_regions_dreamcast[] = {
@ -331,6 +356,21 @@ static const rc_memory_region_t _rc_memory_regions_dreamcast[] = {
};
static const rc_memory_regions_t rc_memory_regions_dreamcast = { _rc_memory_regions_dreamcast, 1 };
/* ===== Fairchild Channel F ===== */
static const rc_memory_region_t _rc_memory_regions_fairchild_channel_f[] = {
/* "System RAM" is actually just a bunch of registers internal to CPU so all carts have it.
* "Video RAM" is part of the console so it's always available but it is write-only by the ROMs.
* "Cartridge RAM" is the cart BUS. Most carts only have ROMs on this bus. Exception are
* German Schach and homebrew carts that have 2K of RAM there in addition to ROM.
* "F2102 RAM" is used by Maze for 1K of RAM.
* https://discord.com/channels/310192285306454017/645777658319208448/967001438087708714 */
{ 0x00000000U, 0x0000003FU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x00000040U, 0x0000083FU, 0x00300000U, RC_MEMORY_TYPE_VIDEO_RAM, "Video RAM" },
{ 0x00000840U, 0x0001083FU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Cartridge RAM" },
{ 0x00010840U, 0x00010C3FU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "F2102 RAM" }
};
static const rc_memory_regions_t rc_memory_regions_fairchild_channel_f = { _rc_memory_regions_fairchild_channel_f, 4 };
/* ===== GameBoy / GameBoy Color ===== */
static const rc_memory_region_t _rc_memory_regions_gameboy[] = {
{ 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt vector" },
@ -553,9 +593,10 @@ static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_re
/* https://psi-rockin.github.io/ps2tek/ */
static const rc_memory_region_t _rc_memory_regions_playstation2[] = {
{ 0x00000000U, 0x000FFFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" },
{ 0x00100000U, 0x01FFFFFFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
{ 0x00100000U, 0x01FFFFFFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x02000000U, 0x02003FFFU, 0x70000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Scratchpad RAM" },
};
static const rc_memory_regions_t rc_memory_regions_playstation2 = { _rc_memory_regions_playstation2, 2 };
static const rc_memory_regions_t rc_memory_regions_playstation2 = { _rc_memory_regions_playstation2, 3 };
/* ===== PlayStation Portable ===== */
/* https://github.com/uofw/upspd/wiki/Memory-map */
@ -670,6 +711,17 @@ static const rc_memory_region_t _rc_memory_regions_watara_supervision[] = {
};
static const rc_memory_regions_t rc_memory_regions_watara_supervision = { _rc_memory_regions_watara_supervision, 3 };
/* ===== WASM-4 ===== */
/* fantasy console that runs specifically designed WebAssembly games */
/* https://github.com/aduros/wasm4/blob/main/site/docs/intro.md#hardware-specs */
static const rc_memory_region_t _rc_memory_regions_wasm4[] = {
{ 0x000000U, 0x00FFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
/* Persistent storage is not directly accessible from the game. It has to be loaded into System RAM first
{ 0x010000U, 0x0103FFU, 0x80000000U, RC_MEMORY_TYPE_SAVE_RAM, "Disk Storage"}
*/
};
static const rc_memory_regions_t rc_memory_regions_wasm4 = { _rc_memory_regions_wasm4, 1 };
/* ===== WonderSwan ===== */
/* http://daifukkat.su/docs/wsman/#ovr_memmap */
static const rc_memory_region_t _rc_memory_regions_wonderswan[] = {
@ -697,6 +749,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
case RC_CONSOLE_3DO:
return &rc_memory_regions_3do;
case RC_CONSOLE_AMIGA:
return &rc_memory_regions_amiga;
case RC_CONSOLE_AMSTRAD_PC:
return &rc_memory_regions_amstrad_pc;
@ -721,9 +776,15 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
case RC_CONSOLE_COLECOVISION:
return &rc_memory_regions_colecovision;
case RC_CONSOLE_COMMODORE_64:
return &rc_memory_regions_c64;
case RC_CONSOLE_DREAMCAST:
return &rc_memory_regions_dreamcast;
case RC_CONSOLE_FAIRCHILD_CHANNEL_F:
return &rc_memory_regions_fairchild_channel_f;
case RC_CONSOLE_MEGADUCK:
case RC_CONSOLE_GAMEBOY:
return &rc_memory_regions_gameboy;
@ -821,6 +882,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
case RC_CONSOLE_VIRTUAL_BOY:
return &rc_memory_regions_virtualboy;
case RC_CONSOLE_WASM4:
return &rc_memory_regions_wasm4;
case RC_CONSOLE_WONDERSWAN:
return &rc_memory_regions_wonderswan;

View file

@ -119,8 +119,9 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) {
static float rc_build_float(unsigned mantissa_bits, int exponent, int sign) {
/* 32-bit float has a 23-bit mantissa and 8-bit exponent */
const unsigned mantissa = mantissa_bits | 0x800000;
double dbl = ((double)mantissa) / ((double)0x800000);
const unsigned implied_bit = 1 << 23;
const unsigned mantissa = mantissa_bits | implied_bit;
double dbl = ((double)mantissa) / ((double)implied_bit);
if (exponent > 127) {
/* exponent above 127 is a special number */
@ -151,7 +152,16 @@ static float rc_build_float(unsigned mantissa_bits, int exponent, int sign) {
}
else if (exponent < 0) {
/* exponent from -1 to -127 is a number less than 1 */
if (exponent == -127) {
/* exponent -127 (all exponent bits were zero) is a denormalized value
* (no implied leading bit) with exponent -126 */
dbl = ((double)mantissa_bits) / ((double)implied_bit);
exponent = 126;
} else {
exponent = -exponent;
}
while (exponent > 30) {
dbl /= (double)(1 << 30);
exponent -= 30;
@ -170,12 +180,7 @@ static void rc_transform_memref_float(rc_typed_value_t* value) {
const unsigned mantissa = (value->value.u32 & 0x7FFFFF);
const int exponent = (int)((value->value.u32 >> 23) & 0xFF) - 127;
const int sign = (value->value.u32 & 0x80000000);
if (mantissa == 0 && exponent == -127)
value->value.f32 = (sign) ? -0.0f : 0.0f;
else
value->value.f32 = rc_build_float(mantissa, exponent, sign);
value->type = RC_VALUE_TYPE_FLOAT;
}

View file

@ -429,7 +429,7 @@ int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua
previous_ptr = NULL;
previous = self->richpresence;
while (previous) {
if (previous && memcmp(self->richpresence->md5, md5, 16) == 0) {
if (previous && self->richpresence->richpresence && memcmp(self->richpresence->md5, md5, 16) == 0) {
/* unchanged. reset all of the conditions */
rc_reset_richpresence(self->richpresence->richpresence);
@ -685,7 +685,7 @@ void rc_runtime_reset(rc_runtime_t* self) {
rc_reset_lboard(self->lboards[i].lboard);
}
if (self->richpresence) {
if (self->richpresence && self->richpresence->richpresence) {
rc_richpresence_display_t* display = self->richpresence->richpresence->first_display;
while (display != 0) {
rc_reset_trigger(&display->trigger);

View file

@ -84,7 +84,7 @@ static void filereader_close(void* file_handle)
}
/* for unit tests - normally would call rc_hash_init_custom_filereader(NULL) */
void rc_hash_reset_filereader()
void rc_hash_reset_filereader(void)
{
filereader = NULL;
}
@ -1609,6 +1609,8 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b
case RC_CONSOLE_ATARI_2600:
case RC_CONSOLE_ATARI_JAGUAR:
case RC_CONSOLE_COLECOVISION:
case RC_CONSOLE_COMMODORE_64:
case RC_CONSOLE_FAIRCHILD_CHANNEL_F:
case RC_CONSOLE_GAMEBOY:
case RC_CONSOLE_GAMEBOY_ADVANCE:
case RC_CONSOLE_GAMEBOY_COLOR:
@ -1629,6 +1631,7 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b
case RC_CONSOLE_TIC80:
case RC_CONSOLE_VECTREX:
case RC_CONSOLE_VIRTUAL_BOY:
case RC_CONSOLE_WASM4:
case RC_CONSOLE_WONDERSWAN:
return rc_hash_buffer(hash, buffer, buffer_size);
@ -1898,6 +1901,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
case RC_CONSOLE_ATARI_2600:
case RC_CONSOLE_ATARI_JAGUAR:
case RC_CONSOLE_COLECOVISION:
case RC_CONSOLE_FAIRCHILD_CHANNEL_F:
case RC_CONSOLE_GAMEBOY:
case RC_CONSOLE_GAMEBOY_ADVANCE:
case RC_CONSOLE_GAMEBOY_COLOR:
@ -1905,7 +1909,6 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
case RC_CONSOLE_INTELLIVISION:
case RC_CONSOLE_MAGNAVOX_ODYSSEY2:
case RC_CONSOLE_MASTER_SYSTEM:
case RC_CONSOLE_MEGA_DRIVE:
case RC_CONSOLE_MEGADUCK:
case RC_CONSOLE_NEOGEO_POCKET:
case RC_CONSOLE_ORIC:
@ -1916,12 +1919,15 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
case RC_CONSOLE_TIC80:
case RC_CONSOLE_VECTREX:
case RC_CONSOLE_VIRTUAL_BOY:
case RC_CONSOLE_WASM4:
case RC_CONSOLE_WONDERSWAN:
/* generic whole-file hash - don't buffer */
return rc_hash_whole_file(hash, path);
case RC_CONSOLE_AMSTRAD_PC:
case RC_CONSOLE_APPLE_II:
case RC_CONSOLE_COMMODORE_64:
case RC_CONSOLE_MEGA_DRIVE:
case RC_CONSOLE_MSX:
case RC_CONSOLE_PC8800:
/* generic whole-file hash with m3u support - don't buffer */
@ -2132,7 +2138,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
}
}
/* bin is associated with MegaDrive, Sega32X, Atari 2600, Watara Supervision, and MegaDuck.
/* bin is associated with MegaDrive, Sega32X, Atari 2600, Watara Supervision, MegaDuck, and Fairchild Channel F.
* Since they all use the same hashing algorithm, only specify one of them */
iterator->consoles[0] = RC_CONSOLE_MEGA_DRIVE;
}
@ -2173,6 +2179,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{
iterator->consoles[0] = RC_CONSOLE_MSX;
}
else if (rc_path_compare_extension(ext, "chf"))
{
iterator->consoles[0] = RC_CONSOLE_FAIRCHILD_CHANNEL_F;
}
break;
case 'd':
@ -2180,6 +2190,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{
rc_hash_initialize_dsk_iterator(iterator, path);
}
else if (rc_path_compare_extension(ext, "d64"))
{
iterator->consoles[0] = RC_CONSOLE_COMMODORE_64;
}
else if (rc_path_compare_extension(ext, "d88"))
{
iterator->consoles[0] = RC_CONSOLE_PC8800;
@ -2320,6 +2334,11 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{
iterator->consoles[0] = RC_CONSOLE_NEOGEO_POCKET;
}
else if (rc_path_compare_extension(ext, "nib"))
{
/* also Apple II, but both are full-file hashes */
iterator->consoles[0] = RC_CONSOLE_COMMODORE_64;
}
break;
case 'p':
@ -2332,8 +2351,9 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
case 'r':
if (rc_path_compare_extension(ext, "rom"))
{
/* rom is associated with MSX, Thomson TO-8, and Fairchild Channel F.
* Since they all use the same hashing algorithm, only specify one of them */
iterator->consoles[0] = RC_CONSOLE_MSX;
iterator->consoles[1] = RC_CONSOLE_THOMSONTO8; /* cartridge */
}
if (rc_path_compare_extension(ext, "ri"))
{
@ -2393,6 +2413,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{
iterator->consoles[0] = RC_CONSOLE_WONDERSWAN;
}
else if (rc_path_compare_extension(ext, "wasm"))
{
iterator->consoles[0] = RC_CONSOLE_WASM4;
}
else if (rc_path_compare_extension(ext, "woz"))
{
iterator->consoles[0] = RC_CONSOLE_APPLE_II;