dep: Bump rcheevos to 3af1e2fc5188d6e932ee379942f4049ea877e648

This commit is contained in:
Stenzek 2023-08-09 19:39:42 +10:00
parent 56ac3d6c32
commit 5d750a8803
41 changed files with 9440 additions and 564 deletions

View file

@ -44,6 +44,7 @@ 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);
int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response);
/* --- Update Code Note --- */
@ -76,6 +77,7 @@ 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);
int rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response);
/* --- Update Achievement --- */
@ -121,6 +123,7 @@ 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);
int rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response);
/* --- Update Leaderboard --- */
@ -170,6 +173,7 @@ 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);
int rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response);
/* --- Fetch Badge Range --- */
@ -199,6 +203,7 @@ 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);
int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response);
/* --- Add Game Hash --- */
@ -238,6 +243,7 @@ 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);
int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response);
#ifdef __cplusplus

View file

@ -64,6 +64,7 @@ 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);
int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response);
/* --- Fetch Leaderboard Info --- */
@ -135,6 +136,7 @@ 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);
int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response);
/* --- Fetch Games List --- */
@ -173,6 +175,7 @@ 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);
int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response);
#ifdef __cplusplus

View file

@ -3,6 +3,8 @@
#include "rc_error.h"
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
@ -10,15 +12,25 @@ extern "C" {
/**
* A block of memory for variable length data (like strings and arrays).
*/
typedef struct rc_api_buffer_t {
typedef struct rc_api_buffer_chunk_t {
/* The current location where data is being written */
char* write;
/* The first byte past the end of data where writing cannot occur */
char* end;
/* The first byte of the data */
char* start;
/* The next block in the allocated memory chain */
struct rc_api_buffer_t* next;
/* The buffer containing the data. The actual size may be larger than 256 bytes for buffers allocated in
* the next chain. The 256 byte size specified is for the initial allocation within the container object. */
struct rc_api_buffer_chunk_t* next;
}
rc_api_buffer_chunk_t;
/**
* A preallocated block of memory for variable length data (like strings and arrays).
*/
typedef struct rc_api_buffer_t {
/* The chunk data (will point at the local data member) */
struct rc_api_buffer_chunk_t chunk;
/* Small chunk of memory pre-allocated for the chunk */
char data[256];
}
rc_api_buffer_t;
@ -31,6 +43,8 @@ typedef struct rc_api_request_t {
const char* url;
/* Additional query args that should be sent via a POST command. If null, GET may be used */
const char* post_data;
/* The HTTP Content-Type of the POST data. */
const char* content_type;
/* Storage for the url and post_data */
rc_api_buffer_t buffer;
@ -41,7 +55,7 @@ rc_api_request_t;
* Common attributes for all server responses.
*/
typedef struct rc_api_response_t {
/* Server-provided success indicator (non-zero on failure) */
/* Server-provided success indicator (non-zero on success, zero on failure) */
int succeeded;
/* Server-provided message associated to the failure */
const char* error_message;
@ -56,6 +70,15 @@ 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);
typedef struct rc_api_server_response_t {
/* Pointer to the data returned from the server */
const char* body;
/* Length of data returned from the server (Content-Length) */
size_t body_length;
/* HTTP status code returned from the server */
int http_status_code;
} rc_api_server_response_t;
#ifdef __cplusplus
}
#endif

View file

@ -59,6 +59,7 @@ 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);
int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response);
/* --- Fetch Game Data --- */
@ -155,6 +156,7 @@ 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);
int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response);
/* --- Ping --- */
@ -185,6 +187,7 @@ 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);
int rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_ping_response(rc_api_ping_response_t* response);
/* --- Award Achievement --- */
@ -214,6 +217,8 @@ typedef struct rc_api_award_achievement_response_t {
unsigned awarded_achievement_id;
/* The updated player score */
unsigned new_player_score;
/* The updated player softcore score */
unsigned new_player_score_softcore;
/* 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;
@ -225,6 +230,7 @@ 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);
int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response);
/* --- Submit Leaderboard Entry --- */
@ -282,6 +288,7 @@ 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);
int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response);
#ifdef __cplusplus

View file

@ -33,6 +33,8 @@ typedef struct rc_api_login_response_t {
const char* api_token;
/* The current score of the player */
unsigned score;
/* The current softcore score of the player */
unsigned score_softcore;
/* 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 */
@ -45,6 +47,7 @@ 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);
int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_login_response(rc_api_login_response_t* response);
/* --- Start Session --- */
@ -73,6 +76,7 @@ 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);
int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response);
/* --- Fetch User Unlocks --- */
@ -108,6 +112,7 @@ 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);
int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response);
void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response);
#ifdef __cplusplus

View file

@ -0,0 +1,584 @@
#ifndef RC_CLIENT_H
#define RC_CLIENT_H
#ifdef __cplusplus
extern "C" {
#endif
#include "rc_api_request.h"
#include "rc_error.h"
#include <stddef.h>
#include <stdint.h>
#include <time.h>
/* implementation abstracted in rc_client_internal.h */
typedef struct rc_client_t rc_client_t;
typedef struct rc_client_async_handle_t rc_client_async_handle_t;
/*****************************************************************************\
| Callbacks |
\*****************************************************************************/
/**
* Callback used to read num_bytes bytes from memory starting at address into buffer.
* Returns the number of bytes read. A return value of 0 indicates the address was invalid.
*/
typedef uint32_t (*rc_client_read_memory_func_t)(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client);
/**
* Internal method passed to rc_client_server_call_t to process the server response.
*/
typedef void (*rc_client_server_callback_t)(const rc_api_server_response_t* server_response, void* callback_data);
/**
* Callback used to issue a request to the server.
*/
typedef void (*rc_client_server_call_t)(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client);
/**
* Generic callback for asynchronous eventing.
*/
typedef void (*rc_client_callback_t)(int result, const char* error_message, rc_client_t* client, void* userdata);
/**
* Callback for logging or displaying a message.
*/
typedef void (*rc_client_message_callback_t)(const char* message, const rc_client_t* client);
/**
* Marks an async process as aborted. The associated callback will not be called.
*/
void rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle);
/*****************************************************************************\
| Runtime |
\*****************************************************************************/
/**
* Creates a new rc_client_t object.
*/
rc_client_t* rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function);
/**
* Releases resources associated to a rc_client_t object.
* Pointer will no longer be valid after making this call.
*/
void rc_client_destroy(rc_client_t* client);
/**
* Sets whether hardcore is enabled (on by default).
* Can be called with a game loaded.
* Enabling hardcore with a game loaded will raise an RC_CLIENT_EVENT_RESET
* event. Processing will be disabled until rc_client_reset is called.
*/
void rc_client_set_hardcore_enabled(rc_client_t* client, int enabled);
/**
* Gets whether hardcore is enabled (on by default).
*/
int rc_client_get_hardcore_enabled(const rc_client_t* client);
/**
* Sets whether encore mode is enabled (off by default).
* Evaluated when loading a game. Has no effect while a game is loaded.
*/
void rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled);
/**
* Gets whether encore mode is enabled (off by default).
*/
int rc_client_get_encore_mode_enabled(const rc_client_t* client);
/**
* Sets whether unofficial achievements should be loaded.
* Evaluated when loading a game. Has no effect while a game is loaded.
*/
void rc_client_set_unofficial_enabled(rc_client_t* client, int enabled);
/**
* Gets whether unofficial achievements should be loaded.
*/
int rc_client_get_unofficial_enabled(const rc_client_t* client);
/**
* Sets whether spectator mode is enabled (off by default).
* If enabled, events for achievement unlocks and leaderboard submissions will be
* raised, but server calls to actually perform the unlock/submit will not occur.
* Can be modified while a game is loaded. Evaluated at unlock/submit time.
* Cannot be modified if disabled before a game is loaded.
*/
void rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled);
/**
* Gets whether spectator mode is enabled (off by default).
*/
int rc_client_get_spectator_mode_enabled(const rc_client_t* client);
/**
* Attaches client-specific data to the runtime.
*/
void rc_client_set_userdata(rc_client_t* client, void* userdata);
/**
* Gets the client-specific data attached to the runtime.
*/
void* rc_client_get_userdata(const rc_client_t* client);
/**
* Sets the name of the server to use.
*/
void rc_client_set_host(const rc_client_t* client, const char* hostname);
/*****************************************************************************\
| Logging |
\*****************************************************************************/
/**
* Sets the logging level and provides a callback to be called to do the logging.
*/
void rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_callback_t callback);
enum
{
RC_CLIENT_LOG_LEVEL_NONE = 0,
RC_CLIENT_LOG_LEVEL_ERROR = 1,
RC_CLIENT_LOG_LEVEL_WARN = 2,
RC_CLIENT_LOG_LEVEL_INFO = 3,
RC_CLIENT_LOG_LEVEL_VERBOSE = 4
};
/*****************************************************************************\
| User |
\*****************************************************************************/
/**
* Attempt to login a user.
*/
rc_client_async_handle_t* rc_client_begin_login_with_password(rc_client_t* client,
const char* username, const char* password,
rc_client_callback_t callback, void* callback_userdata);
/**
* Attempt to login a user.
*/
rc_client_async_handle_t* rc_client_begin_login_with_token(rc_client_t* client,
const char* username, const char* token,
rc_client_callback_t callback, void* callback_userdata);
/**
* Logout the user.
*/
void rc_client_logout(rc_client_t* client);
typedef struct rc_client_user_t {
const char* display_name;
const char* username;
const char* token;
uint32_t score;
uint32_t score_softcore;
uint32_t num_unread_messages;
} rc_client_user_t;
/**
* Gets information about the logged in user. Will return NULL if the user is not logged in.
*/
const rc_client_user_t* rc_client_get_user_info(const rc_client_t* client);
/**
* Gets the URL for the user's profile picture.
* Returns RC_OK on success.
*/
int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size);
typedef struct rc_client_user_game_summary_t
{
uint32_t num_core_achievements;
uint32_t num_unofficial_achievements;
uint32_t num_unlocked_achievements;
uint32_t num_unsupported_achievements;
uint32_t points_core;
uint32_t points_unlocked;
} rc_client_user_game_summary_t;
/**
* Gets a breakdown of the number of achievements in the game, and how many the user has unlocked.
* Used for the "You have unlocked X of Y achievements" message shown when the game starts.
*/
void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary);
/*****************************************************************************\
| Game |
\*****************************************************************************/
/**
* Start loading an unidentified game.
*/
rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* client,
uint32_t console_id, const char* file_path,
const uint8_t* data, size_t data_size,
rc_client_callback_t callback, void* callback_userdata);
/**
* Start loading a game.
*/
rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const char* hash,
rc_client_callback_t callback, void* callback_userdata);
/**
* Unloads the current game.
*/
void rc_client_unload_game(rc_client_t* client);
typedef struct rc_client_game_t {
uint32_t id;
uint32_t console_id;
const char* title;
const char* hash;
const char* badge_name;
} rc_client_game_t;
/**
* Get information about the current game. Returns NULL if no game is loaded.
*/
const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client);
/**
* Gets the URL for the game image.
* Returns RC_OK on success.
*/
int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size);
/**
* Changes the active disc in a multi-disc game.
*/
rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, const char* file_path,
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
/*****************************************************************************\
| Subsets |
\*****************************************************************************/
typedef struct rc_client_subset_t {
uint32_t id;
const char* title;
char badge_name[16];
uint32_t num_achievements;
uint32_t num_leaderboards;
} rc_client_subset_t;
const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id);
/*****************************************************************************\
| Achievements |
\*****************************************************************************/
enum {
RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE = 0, /* unprocessed */
RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE = 1, /* eligible to trigger */
RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED = 2, /* earned by user */
RC_CLIENT_ACHIEVEMENT_STATE_DISABLED = 3 /* not supported by this version of the runtime */
};
enum {
RC_CLIENT_ACHIEVEMENT_CATEGORY_NONE = 0,
RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE = (1 << 0),
RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL = (1 << 1),
RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE | RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL
};
enum {
RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN = 0,
RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED = 1,
RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED = 2,
RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED = 3,
RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL = 4,
RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED = 5,
RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE = 6,
RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7
};
enum {
RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE = 0,
RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE = (1 << 0),
RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE = (1 << 1),
RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH = RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE | RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE
};
typedef struct rc_client_achievement_t {
const char* title;
const char* description;
char badge_name[8];
char measured_progress[24];
float measured_percent;
uint32_t id;
uint32_t points;
time_t unlock_time;
uint8_t state;
uint8_t category;
uint8_t bucket;
uint8_t unlocked;
} rc_client_achievement_t;
/**
* Get information about an achievement. Returns NULL if not found.
*/
const rc_client_achievement_t* rc_client_get_achievement_info(rc_client_t* client, uint32_t id);
/**
* Gets the URL for the achievement image.
* Returns RC_OK on success.
*/
int rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size);
typedef struct rc_client_achievement_bucket_t {
rc_client_achievement_t** achievements;
uint32_t num_achievements;
const char* label;
uint32_t subset_id;
uint8_t bucket_type;
} rc_client_achievement_bucket_t;
typedef struct rc_client_achievement_list_t {
rc_client_achievement_bucket_t* buckets;
uint32_t num_buckets;
} rc_client_achievement_list_t;
enum {
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE = 0,
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS = 1
};
/**
* Creates a list of achievements matching the specified category and grouping.
* Returns an allocated list that must be free'd by calling rc_client_destroy_achievement_list.
*/
rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* client, int category, int grouping);
/**
* Destroys a list allocated by rc_client_get_achievement_list.
*/
void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list);
/*****************************************************************************\
| Leaderboards |
\*****************************************************************************/
enum {
RC_CLIENT_LEADERBOARD_STATE_INACTIVE = 0,
RC_CLIENT_LEADERBOARD_STATE_ACTIVE = 1,
RC_CLIENT_LEADERBOARD_STATE_TRACKING = 2,
RC_CLIENT_LEADERBOARD_STATE_DISABLED = 3
};
typedef struct rc_client_leaderboard_t {
const char* title;
const char* description;
const char* tracker_value;
uint32_t id;
uint8_t state;
uint8_t lower_is_better;
} rc_client_leaderboard_t;
/**
* Get information about a leaderboard. Returns NULL if not found.
*/
const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id);
typedef struct rc_client_leaderboard_tracker_t {
char display[24];
uint32_t id;
} rc_client_leaderboard_tracker_t;
typedef struct rc_client_leaderboard_bucket_t {
rc_client_leaderboard_t** leaderboards;
uint32_t num_leaderboards;
const char* label;
uint32_t subset_id;
uint8_t bucket_type;
} rc_client_leaderboard_bucket_t;
typedef struct rc_client_leaderboard_list_t {
rc_client_leaderboard_bucket_t* buckets;
uint32_t num_buckets;
} rc_client_leaderboard_list_t;
enum {
RC_CLIENT_LEADERBOARD_BUCKET_UNKNOWN = 0,
RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE = 1,
RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE = 2,
RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED = 3,
RC_CLIENT_LEADERBOARD_BUCKET_ALL = 4
};
enum {
RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE = 0,
RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING = 1
};
/**
* Creates a list of leaderboards matching the specified grouping.
* Returns an allocated list that must be free'd by calling rc_client_destroy_leaderboard_list.
*/
rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* client, int grouping);
/**
* Destroys a list allocated by rc_client_get_leaderboard_list.
*/
void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list);
typedef struct rc_client_leaderboard_entry_t {
const char* user;
char display[24];
time_t submitted;
uint32_t rank;
uint32_t index;
} rc_client_leaderboard_entry_t;
typedef struct rc_client_leaderboard_entry_list_t {
rc_client_leaderboard_entry_t* entries;
uint32_t num_entries;
int32_t user_index;
} rc_client_leaderboard_entry_list_t;
typedef void (*rc_client_fetch_leaderboard_entries_callback_t)(int result, const char* error_message,
rc_client_leaderboard_entry_list_t* list, rc_client_t* client, void* callback_userdata);
/**
* Fetches a list of leaderboard entries from the server.
* Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list.
*/
rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id,
uint32_t first_entry, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
/**
* Fetches a list of leaderboard entries from the server containing the logged-in user.
* Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list.
*/
rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries_around_user(rc_client_t* client, uint32_t leaderboard_id,
uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
/**
* Gets the URL for the profile picture of the user associated to a leaderboard entry.
* Returns RC_OK on success.
*/
int rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_entry_t* entry, char buffer[], size_t buffer_size);
/**
* Destroys a list allocated by rc_client_begin_fetch_leaderboard_entries or rc_client_begin_fetch_leaderboard_entries_around_user.
*/
void rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list);
/*****************************************************************************\
| Rich Presence |
\*****************************************************************************/
/**
* Gets the current rich presence message.
* Returns the number of characters written to buffer.
*/
size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size);
/*****************************************************************************\
| Processing |
\*****************************************************************************/
enum {
RC_CLIENT_EVENT_TYPE_NONE = 0,
RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED = 1, /* [achievement] was earned by the player */
RC_CLIENT_EVENT_LEADERBOARD_STARTED = 2, /* [leaderboard] attempt has started */
RC_CLIENT_EVENT_LEADERBOARD_FAILED = 3, /* [leaderboard] attempt failed */
RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED = 4, /* [leaderboard] attempt submitted */
RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW = 5, /* [achievement] challenge indicator should be shown */
RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE = 6, /* [achievement] challenge indicator should be hidden */
RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW = 7, /* progress indicator should be shown for [achievement] */
RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE = 8, /* progress indicator should be hidden */
RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE = 9, /* progress indicator should be updated to reflect new badge/progress for [achievement] */
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW = 10, /* [leaderboard_tracker] should be shown */
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE = 11, /* [leaderboard_tracker] should be hidden */
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE = 12, /* [leaderboard_tracker] updated */
RC_CLIENT_EVENT_RESET = 13, /* emulated system should be reset (as the result of enabling hardcore) */
RC_CLIENT_EVENT_GAME_COMPLETED = 14, /* all achievements for the game have been earned */
RC_CLIENT_EVENT_SERVER_ERROR = 15 /* an API response returned a [server_error] and will not be retried */
};
typedef struct rc_client_server_error_t
{
const char* error_message;
const char* api;
} rc_client_server_error_t;
typedef struct rc_client_event_t
{
uint32_t type;
rc_client_achievement_t* achievement;
rc_client_leaderboard_t* leaderboard;
rc_client_leaderboard_tracker_t* leaderboard_tracker;
rc_client_server_error_t* server_error;
} rc_client_event_t;
/**
* Callback used to notify the client when certain events occur.
*/
typedef void (*rc_client_event_handler_t)(const rc_client_event_t* event, rc_client_t* client);
/**
* Provides a callback for event handling.
*/
void rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler);
/**
* Provides a callback for reading memory.
*/
void rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler);
/**
* Determines if there are any active achievements/leaderboards/rich presence that need processing.
*/
int rc_client_is_processing_required(rc_client_t* client);
/**
* Processes achievements for the current frame.
*/
void rc_client_do_frame(rc_client_t* client);
/**
* Processes the periodic queue.
* Called internally by rc_client_do_frame.
* Should be explicitly called if rc_client_do_frame is not being called because emulation is paused.
*/
void rc_client_idle(rc_client_t* client);
/**
* Informs the runtime that the emulator has been reset. Will reset all achievements and leaderboards
* to their initial state (includes hiding indicators/trackers).
*/
void rc_client_reset(rc_client_t* client);
/**
* Gets the number of bytes needed to serialized the runtime state.
*/
size_t rc_client_progress_size(rc_client_t* client);
/**
* Serializes the runtime state into a buffer.
* Returns RC_OK on success, or an error indicator.
*/
int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer);
/**
* Deserializes the runtime state from a buffer.
* Returns RC_OK on success, or an error indicator.
*/
int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized);
#ifdef __cplusplus
}
#endif
#endif /* RC_RUNTIME_H */

View file

@ -10,6 +10,7 @@ extern "C" {
\*****************************************************************************/
enum {
RC_CONSOLE_UNKNOWN = 0,
RC_CONSOLE_MEGA_DRIVE = 1,
RC_CONSOLE_NINTENDO_64 = 2,
RC_CONSOLE_SUPER_NINTENDO = 3,
@ -85,6 +86,11 @@ enum {
RC_CONSOLE_ARCADIA_2001 = 73,
RC_CONSOLE_INTERTON_VC_4000 = 74,
RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER = 75,
RC_CONSOLE_PC_ENGINE_CD = 76,
RC_CONSOLE_ATARI_JAGUAR_CD = 77,
RC_CONSOLE_NINTENDO_DSI = 78,
RC_CONSOLE_TI83 = 79,
RC_CONSOLE_UZEBOX = 80,
RC_CONSOLE_HUBS = 100,
RC_CONSOLE_EVENTS = 101

View file

@ -36,7 +36,13 @@ enum {
RC_INVALID_MEASURED_TARGET = -23,
RC_INVALID_COMPARISON = -24,
RC_INVALID_STATE = -25,
RC_INVALID_JSON = -26
RC_INVALID_JSON = -26,
RC_API_FAILURE = -27,
RC_LOGIN_REQUIRED = -28,
RC_NO_GAME_LOADED = -29,
RC_HARDCORE_DISABLED = -30,
RC_ABORTED = -31,
RC_NO_RESPONSE = -32
};
const char* rc_error_str(int ret);

View file

@ -27,20 +27,20 @@ extern "C" {
/* data for rc_hash_iterate
*/
struct rc_hash_iterator
typedef struct rc_hash_iterator
{
uint8_t* buffer;
const uint8_t* buffer;
size_t buffer_size;
uint8_t consoles[12];
int index;
const char* path;
};
} rc_hash_iterator_t;
/* initializes a rc_hash_iterator
* - path must be provided
* - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file)
*/
void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, uint8_t* buffer, size_t buffer_size);
void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size);
/* releases resources associated to a rc_hash_iterator
*/
@ -92,11 +92,12 @@ extern "C" {
/* ===================================================== */
#define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1)
#define RC_HASH_CDTRACK_LAST ((uint32_t)-2)
#define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3)
#define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1) /* the first data track (skip audio tracks) */
#define RC_HASH_CDTRACK_LAST ((uint32_t)-2) /* the last data/audio track */
#define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) /* the largest data/audio track */
#define RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION ((uint32_t)-4) /* the first data/audio track of the second session */
/* opens a track from the specified file. track 0 indicates the largest data track should be opened.
/* opens a track from the specified file. see the RC_HASH_CDTRACK_ defines for special tracks.
* returns a handle to be passed to the other functions, or NULL if the track could not be opened.
*/
typedef void* (*rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track);

View file

@ -88,9 +88,12 @@ typedef struct rc_runtime_t {
rc_value_t* variables;
rc_value_t** next_variable;
char owns_self;
}
rc_runtime_t;
rc_runtime_t* rc_runtime_alloc(void);
void rc_runtime_init(rc_runtime_t* runtime);
void rc_runtime_destroy(rc_runtime_t* runtime);
@ -121,7 +124,8 @@ enum {
RC_RUNTIME_EVENT_LBOARD_TRIGGERED,
RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED,
RC_RUNTIME_EVENT_LBOARD_DISABLED,
RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED
RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED,
RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED
};
typedef struct rc_runtime_event_t {

View file

@ -56,6 +56,7 @@ enum {
RC_MEMSIZE_32_BITS_BE,
RC_MEMSIZE_FLOAT,
RC_MEMSIZE_MBF32,
RC_MEMSIZE_MBF32_LE,
RC_MEMSIZE_VARIABLE
};
@ -169,7 +170,8 @@ enum {
RC_OPERATOR_NONE,
RC_OPERATOR_MULT,
RC_OPERATOR_DIV,
RC_OPERATOR_AND
RC_OPERATOR_AND,
RC_OPERATOR_XOR
};
typedef struct rc_condition_t rc_condition_t;
@ -198,6 +200,9 @@ struct rc_condition_t {
/* Whether or not the condition evaluated true on the last check */
char is_true;
/* Unique identifier of optimized comparator to use */
char optimized_comparator;
};
/*****************************************************************************\

View file

@ -10,7 +10,9 @@
#include <string.h>
#define RETROACHIEVEMENTS_HOST "https://retroachievements.org"
#define RETROACHIEVEMENTS_IMAGE_HOST "http://i.retroachievements.org"
#define RETROACHIEVEMENTS_IMAGE_HOST "https://media.retroachievements.org"
#define RETROACHIEVEMENTS_HOST_NONSSL "http://retroachievements.org"
#define RETROACHIEVEMENTS_IMAGE_HOST_NONSSL "http://media.retroachievements.org"
static char* g_host = NULL;
static char* g_imagehost = NULL;
@ -18,157 +20,174 @@ static char* g_imagehost = NULL;
/* --- rc_json --- */
static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen);
static int rc_json_parse_array(const char** json_ptr, rc_json_field_t* field);
static int rc_json_parse_object(rc_json_iterator_t* iterator, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen);
static int rc_json_parse_array(rc_json_iterator_t* iterator, rc_json_field_t* field);
static int rc_json_parse_field(const char** json_ptr, rc_json_field_t* field) {
static int rc_json_match_char(rc_json_iterator_t* iterator, char c)
{
if (iterator->json < iterator->end && *iterator->json == c) {
++iterator->json;
return 1;
}
return 0;
}
static void rc_json_skip_whitespace(rc_json_iterator_t* iterator)
{
while (iterator->json < iterator->end && isspace((unsigned char)*iterator->json))
++iterator->json;
}
static int rc_json_find_closing_quote(rc_json_iterator_t* iterator)
{
while (iterator->json < iterator->end) {
if (*iterator->json == '"')
return 1;
if (*iterator->json == '\\') {
++iterator->json;
if (iterator->json == iterator->end)
return 0;
}
if (*iterator->json == '\0')
return 0;
++iterator->json;
}
return 0;
}
static int rc_json_parse_field(rc_json_iterator_t* iterator, rc_json_field_t* field) {
int result;
field->value_start = *json_ptr;
if (iterator->json >= iterator->end)
return RC_INVALID_JSON;
switch (**json_ptr)
field->value_start = iterator->json;
switch (*iterator->json)
{
case '"': /* quoted string */
++(*json_ptr);
while (**json_ptr != '"') {
if (**json_ptr == '\\')
++(*json_ptr);
if (**json_ptr == '\0')
return RC_INVALID_JSON;
++(*json_ptr);
}
++(*json_ptr);
++iterator->json;
if (!rc_json_find_closing_quote(iterator))
return RC_INVALID_JSON;
++iterator->json;
break;
case '-':
case '+': /* signed number */
++(*json_ptr);
++iterator->json;
/* fallthrough to number */
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': /* number */
do {
++(*json_ptr);
} while (**json_ptr >= '0' && **json_ptr <= '9');
if (**json_ptr == '.') {
do {
++(*json_ptr);
} while (**json_ptr >= '0' && **json_ptr <= '9');
while (iterator->json < iterator->end && *iterator->json >= '0' && *iterator->json <= '9')
++iterator->json;
if (rc_json_match_char(iterator, '.')) {
while (iterator->json < iterator->end && *iterator->json >= '0' && *iterator->json <= '9')
++iterator->json;
}
break;
case '[': /* array */
result = rc_json_parse_array(json_ptr, field);
result = rc_json_parse_array(iterator, field);
if (result != RC_OK)
return result;
return result;
break;
case '{': /* object */
result = rc_json_parse_object(json_ptr, NULL, 0, &field->array_size);
result = rc_json_parse_object(iterator, NULL, 0, &field->array_size);
if (result != RC_OK)
return result;
break;
default: /* non-quoted text [true,false,null] */
if (!isalpha((unsigned char)**json_ptr))
if (!isalpha((unsigned char)*iterator->json))
return RC_INVALID_JSON;
do {
++(*json_ptr);
} while (isalnum((unsigned char)**json_ptr));
while (iterator->json < iterator->end && isalnum((unsigned char)*iterator->json))
++iterator->json;
break;
}
field->value_end = *json_ptr;
field->value_end = iterator->json;
return RC_OK;
}
static int rc_json_parse_array(const char** json_ptr, rc_json_field_t* field) {
static int rc_json_parse_array(rc_json_iterator_t* iterator, rc_json_field_t* field) {
rc_json_field_t unused_field;
const char* json = *json_ptr;
int result;
if (*json != '[')
if (!rc_json_match_char(iterator, '['))
return RC_INVALID_JSON;
++json;
field->array_size = 0;
if (*json != ']') {
do
{
while (isspace((unsigned char)*json))
++json;
result = rc_json_parse_field(&json, &unused_field);
if (result != RC_OK)
return result;
if (rc_json_match_char(iterator, ']')) /* empty array */
return RC_OK;
++field->array_size;
do
{
rc_json_skip_whitespace(iterator);
while (isspace((unsigned char)*json))
++json;
result = rc_json_parse_field(iterator, &unused_field);
if (result != RC_OK)
return result;
if (*json != ',')
break;
++field->array_size;
++json;
} while (1);
rc_json_skip_whitespace(iterator);
} while (rc_json_match_char(iterator, ','));
if (*json != ']')
return RC_INVALID_JSON;
}
if (!rc_json_match_char(iterator, ']'))
return RC_INVALID_JSON;
*json_ptr = ++json;
return RC_OK;
}
static int rc_json_get_next_field(rc_json_object_field_iterator_t* iterator) {
const char* json = iterator->json;
static int rc_json_get_next_field(rc_json_iterator_t* iterator, rc_json_field_t* field) {
rc_json_skip_whitespace(iterator);
while (isspace((unsigned char)*json))
++json;
if (*json != '"')
if (!rc_json_match_char(iterator, '"'))
return RC_INVALID_JSON;
iterator->field.name = ++json;
while (*json != '"') {
if (!*json)
field->name = iterator->json;
while (iterator->json < iterator->end && *iterator->json != '"') {
if (!*iterator->json)
return RC_INVALID_JSON;
++json;
++iterator->json;
}
iterator->name_len = json - iterator->field.name;
++json;
while (isspace((unsigned char)*json))
++json;
if (*json != ':')
if (iterator->json == iterator->end)
return RC_INVALID_JSON;
++json;
field->name_len = iterator->json - field->name;
++iterator->json;
while (isspace((unsigned char)*json))
++json;
rc_json_skip_whitespace(iterator);
if (rc_json_parse_field(&json, &iterator->field) < 0)
if (!rc_json_match_char(iterator, ':'))
return RC_INVALID_JSON;
while (isspace((unsigned char)*json))
++json;
rc_json_skip_whitespace(iterator);
if (rc_json_parse_field(iterator, field) < 0)
return RC_INVALID_JSON;
rc_json_skip_whitespace(iterator);
iterator->json = json;
return RC_OK;
}
static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen) {
rc_json_object_field_iterator_t iterator;
const char* json = *json_ptr;
static int rc_json_parse_object(rc_json_iterator_t* iterator, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen) {
size_t i;
unsigned num_fields = 0;
rc_json_field_t field;
int result;
if (fields_seen)
@ -177,60 +196,105 @@ static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields,
for (i = 0; i < field_count; ++i)
fields[i].value_start = fields[i].value_end = NULL;
if (*json != '{')
if (!rc_json_match_char(iterator, '{'))
return RC_INVALID_JSON;
++json;
if (*json == '}') {
*json_ptr = ++json;
if (rc_json_match_char(iterator, '}')) /* empty object */
return RC_OK;
}
memset(&iterator, 0, sizeof(iterator));
iterator.json = json;
do
{
result = rc_json_get_next_field(&iterator);
result = rc_json_get_next_field(iterator, &field);
if (result != RC_OK)
return result;
for (i = 0; i < field_count; ++i) {
if (!fields[i].value_start && strncmp(fields[i].name, iterator.field.name, iterator.name_len) == 0 &&
fields[i].name[iterator.name_len] == '\0') {
fields[i].value_start = iterator.field.value_start;
fields[i].value_end = iterator.field.value_end;
fields[i].array_size = iterator.field.array_size;
if (!fields[i].value_start && fields[i].name_len == field.name_len &&
memcmp(fields[i].name, field.name, field.name_len) == 0) {
fields[i].value_start = field.value_start;
fields[i].value_end = field.value_end;
fields[i].array_size = field.array_size;
break;
}
}
++num_fields;
if (*iterator.json != ',')
break;
++iterator.json;
} while (1);
} while (rc_json_match_char(iterator, ','));
if (*iterator.json != '}')
if (!rc_json_match_char(iterator, '}'))
return RC_INVALID_JSON;
if (fields_seen)
*fields_seen = num_fields;
*json_ptr = ++iterator.json;
return RC_OK;
}
int rc_json_get_next_object_field(rc_json_object_field_iterator_t* iterator) {
if (*iterator->json != ',' && *iterator->json != '{')
int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field) {
if (!rc_json_match_char(iterator, ',') && !rc_json_match_char(iterator, '{'))
return 0;
++iterator->json;
return (rc_json_get_next_field(iterator) == RC_OK);
return (rc_json_get_next_field(iterator, field) == RC_OK);
}
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_object_string_length(const char* json) {
const char* json_start = json;
rc_json_iterator_t iterator;
memset(&iterator, 0, sizeof(iterator));
iterator.json = json;
iterator.end = json + (1024 * 1024 * 1024); /* arbitrary 1GB limit on JSON response */
rc_json_parse_object(&iterator, NULL, 0, NULL);
return (int)(iterator.json - json_start);
}
static int rc_json_extract_html_error(rc_api_response_t* response, const rc_api_server_response_t* server_response) {
const char* json = server_response->body;
const char* end = json;
const char* title_start = strstr(json, "<title>");
if (title_start) {
title_start += 7;
if (isdigit((int)*title_start)) {
const char* title_end = strstr(title_start + 7, "</title>");
if (title_end) {
char* dst = rc_buf_reserve(&response->buffer, (title_end - title_start) + 1);
response->error_message = dst;
memcpy(dst, title_start, title_end - title_start);
dst += (title_end - title_start);
*dst++ = '\0';
rc_buf_consume(&response->buffer, response->error_message, dst);
response->succeeded = 0;
return RC_INVALID_JSON;
}
}
}
while (*end && *end != '\n' && end - json < 200)
++end;
if (end > json && end[-1] == '\r')
--end;
if (end > json) {
char* dst = rc_buf_reserve(&response->buffer, (end - json) + 1);
response->error_message = dst;
memcpy(dst, json, end - json);
dst += (end - json);
*dst++ = '\0';
rc_buf_consume(&response->buffer, response->error_message, dst);
}
response->succeeded = 0;
return RC_INVALID_JSON;
}
int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_server_response_t* server_response, rc_json_field_t* fields, size_t field_count) {
int result;
#ifndef NDEBUG
if (field_count < 2)
return RC_INVALID_STATE;
@ -240,37 +304,28 @@ int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_jso
return RC_INVALID_STATE;
#endif
if (*json == '{') {
int result = rc_json_parse_object(&json, fields, field_count, NULL);
response->error_message = NULL;
if (!server_response || !server_response->body || !*server_response->body) {
response->succeeded = 0;
return RC_NO_RESPONSE;
}
if (*server_response->body != '{') {
result = rc_json_extract_html_error(response, server_response);
}
else {
rc_json_iterator_t iterator;
memset(&iterator, 0, sizeof(iterator));
iterator.json = server_response->body;
iterator.end = server_response->body + server_response->body_length;
result = rc_json_parse_object(&iterator, fields, field_count, NULL);
rc_json_get_optional_string(&response->error_message, response, &fields[1], "Error", NULL);
rc_json_get_optional_bool(&response->succeeded, &fields[0], "Success", 1);
return result;
}
response->error_message = NULL;
if (*json) {
const char* end = json;
while (*end && *end != '\n' && end - json < 200)
++end;
if (end > json && end[-1] == '\r')
--end;
if (end > json) {
char* dst = rc_buf_reserve(&response->buffer, (end - json) + 1);
response->error_message = dst;
memcpy(dst, json, end - json);
dst += (end - json);
*dst++ = '\0';
rc_buf_consume(&response->buffer, response->error_message, dst);
}
}
response->succeeded = 0;
return RC_INVALID_JSON;
return result;
}
static int rc_json_missing_field(rc_api_response_t* response, const rc_json_field_t* field) {
@ -293,42 +348,48 @@ static int rc_json_missing_field(rc_api_response_t* response, const rc_json_fiel
}
int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name) {
const char* json = field->value_start;
rc_json_iterator_t iterator;
#ifndef NDEBUG
if (strcmp(field->name, field_name) != 0)
return 0;
#else
(void)field_name;
#endif
if (!json)
if (!field->value_start)
return rc_json_missing_field(response, field);
return (rc_json_parse_object(&json, fields, field_count, &field->array_size) == RC_OK);
memset(&iterator, 0, sizeof(iterator));
iterator.json = field->value_start;
iterator.end = field->value_end;
return (rc_json_parse_object(&iterator, fields, field_count, &field->array_size) == RC_OK);
}
static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_field_t* iterator) {
if (!iterator->array_size)
static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_iterator_t* iterator) {
rc_json_skip_whitespace(iterator);
if (iterator->json >= iterator->end)
return 0;
while (isspace((unsigned char)*iterator->value_start))
++iterator->value_start;
if (rc_json_parse_field(iterator, field) != RC_OK)
return 0;
rc_json_parse_field(&iterator->value_start, field);
rc_json_skip_whitespace(iterator);
while (isspace((unsigned char)*iterator->value_start))
++iterator->value_start;
if (!rc_json_match_char(iterator, ','))
rc_json_match_char(iterator, ']');
++iterator->value_start; /* skip , or ] */
--iterator->array_size;
return 1;
}
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) {
rc_json_field_t iterator;
rc_json_iterator_t iterator;
rc_json_field_t array;
rc_json_field_t value;
unsigned* entry;
if (!rc_json_get_required_array(num_entries, &iterator, response, field, field_name))
if (!rc_json_get_required_array(num_entries, &array, response, field, field_name))
return RC_MISSING_VALUE;
if (*num_entries) {
@ -338,6 +399,10 @@ int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, r
value.name = field_name;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array.value_start;
iterator.end = array.value_end;
entry = *entries;
while (rc_json_get_array_entry_value(&value, &iterator)) {
if (!rc_json_get_unum(entry, &value, field_name))
@ -353,10 +418,12 @@ int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, r
return RC_OK;
}
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_required_array(unsigned* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) {
#ifndef NDEBUG
if (strcmp(field->name, field_name) != 0)
return 0;
#else
(void)field_name;
#endif
if (!field->value_start || *field->value_start != '[') {
@ -364,28 +431,27 @@ int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator,
return rc_json_missing_field(response, field);
}
memcpy(iterator, field, sizeof(*iterator));
++iterator->value_start; /* skip [ */
memcpy(array_field, field, sizeof(*array_field));
++array_field->value_start; /* skip [ */
*num_entries = field->array_size;
return 1;
}
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_field_t* iterator) {
if (!iterator->array_size)
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator) {
rc_json_skip_whitespace(iterator);
if (iterator->json >= iterator->end)
return 0;
while (isspace((unsigned char)*iterator->value_start))
++iterator->value_start;
if (rc_json_parse_object(iterator, fields, field_count, NULL) != RC_OK)
return 0;
rc_json_parse_object(&iterator->value_start, fields, field_count, NULL);
rc_json_skip_whitespace(iterator);
while (isspace((unsigned char)*iterator->value_start))
++iterator->value_start;
if (!rc_json_match_char(iterator, ','))
rc_json_match_char(iterator, ']');
++iterator->value_start; /* skip , or ] */
--iterator->array_size;
return 1;
}
@ -451,6 +517,8 @@ int rc_json_get_string(const char** out, rc_api_buffer_t* buffer, const rc_json_
#ifndef NDEBUG
if (strcmp(field->name, field_name) != 0)
return 0;
#else
(void)field_name;
#endif
if (!src) {
@ -562,6 +630,8 @@ int rc_json_get_num(int* out, const rc_json_field_t* field, const char* field_na
#ifndef NDEBUG
if (strcmp(field->name, field_name) != 0)
return 0;
#else
(void)field_name;
#endif
if (!src) {
@ -613,6 +683,8 @@ int rc_json_get_unum(unsigned* out, const rc_json_field_t* field, const char* fi
#ifndef NDEBUG
if (strcmp(field->name, field_name) != 0)
return 0;
#else
(void)field_name;
#endif
if (!src) {
@ -654,11 +726,13 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char*
#ifndef NDEBUG
if (strcmp(field->name, field_name) != 0)
return 0;
#else
(void)field_name;
#endif
if (*field->value_start == '\"') {
memset(&tm, 0, sizeof(tm));
if (sscanf(field->value_start + 1, "%d-%d-%d %d:%d:%d",
if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) {
tm.tm_mon--; /* 0-based */
tm.tm_year -= 1900; /* 1900 based */
@ -669,9 +743,11 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char*
* timezone conversion twice and manually removing the difference */
{
time_t local_timet = mktime(&tm);
struct tm* gmt_tm = gmtime(&local_timet);
time_t skewed_timet = mktime(gmt_tm); /* applies local time adjustment second time */
time_t tz_offset = skewed_timet - local_timet;
time_t skewed_timet, tz_offset;
struct tm gmt_tm;
gmtime_s(&gmt_tm, &local_timet);
skewed_timet = mktime(&gmt_tm); /* applies local time adjustment second time */
tz_offset = skewed_timet - local_timet;
*out = local_timet - tz_offset;
}
@ -696,6 +772,8 @@ int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_n
#ifndef NDEBUG
if (strcmp(field->name, field_name) != 0)
return 0;
#else
(void)field_name;
#endif
if (src) {
@ -731,31 +809,32 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js
/* --- rc_buf --- */
void rc_buf_init(rc_api_buffer_t* buffer) {
buffer->write = &buffer->data[0];
buffer->end = &buffer->data[sizeof(buffer->data)];
buffer->next = NULL;
buffer->chunk.write = buffer->chunk.start = &buffer->data[0];
buffer->chunk.end = &buffer->data[sizeof(buffer->data)];
buffer->chunk.next = NULL;
}
void rc_buf_destroy(rc_api_buffer_t* buffer) {
rc_api_buffer_chunk_t *chunk;
#ifdef DEBUG_BUFFERS
int count = 0;
int wasted = 0;
int total = 0;
#endif
/* first buffer is not allocated */
buffer = buffer->next;
/* first chunk is not allocated. skip it. */
chunk = buffer->chunk.next;
/* deallocate any additional buffers */
while (buffer) {
rc_api_buffer_t* next = buffer->next;
while (chunk) {
rc_api_buffer_chunk_t* next = chunk->next;
#ifdef DEBUG_BUFFERS
total += (int)(buffer->end - buffer->data);
wasted += (int)(buffer->end - buffer->write);
total += (int)(chunk->end - chunk->data);
wasted += (int)(chunk->end - chunk->write);
++count;
#endif
free(buffer);
buffer = next;
free(chunk);
chunk = next;
}
#ifdef DEBUG_BUFFERS
@ -765,47 +844,50 @@ void rc_buf_destroy(rc_api_buffer_t* buffer) {
}
char* rc_buf_reserve(rc_api_buffer_t* buffer, size_t amount) {
rc_api_buffer_chunk_t* chunk = &buffer->chunk;
size_t remaining;
while (buffer) {
remaining = buffer->end - buffer->write;
while (chunk) {
remaining = chunk->end - chunk->write;
if (remaining >= amount)
return buffer->write;
return chunk->write;
if (!buffer->next) {
/* allocate a chunk of memory that is a multiple of 256-bytes. casting it to an rc_api_buffer_t will
* effectively unbound the data field, so use write and end pointers to track how data is being used.
if (!chunk->next) {
/* allocate a chunk of memory that is a multiple of 256-bytes. the first 32 bytes will be associated
* to the chunk header, and the remaining will be used for data.
*/
const size_t buffer_prefix_size = sizeof(rc_api_buffer_t) - sizeof(buffer->data);
const size_t alloc_size = (amount + buffer_prefix_size + 0xFF) & ~0xFF;
buffer->next = (rc_api_buffer_t*)malloc(alloc_size);
if (!buffer->next)
const size_t chunk_header_size = sizeof(rc_api_buffer_chunk_t);
const size_t alloc_size = (chunk_header_size + amount + 0xFF) & ~0xFF;
chunk->next = (rc_api_buffer_chunk_t*)malloc(alloc_size);
if (!chunk->next)
break;
buffer->next->write = buffer->next->data;
buffer->next->end = buffer->next->write + (alloc_size - buffer_prefix_size);
buffer->next->next = NULL;
chunk->next->start = (char*)chunk->next + chunk_header_size;
chunk->next->write = chunk->next->start;
chunk->next->end = (char*)chunk->next + alloc_size;
chunk->next->next = NULL;
}
buffer = buffer->next;
chunk = chunk->next;
}
return NULL;
}
void rc_buf_consume(rc_api_buffer_t* buffer, const char* start, char* end) {
rc_api_buffer_chunk_t* chunk = &buffer->chunk;
do {
if (buffer->write == start) {
size_t offset = (end - buffer->data);
if (chunk->write == start) {
size_t offset = (end - chunk->start);
offset = (offset + 7) & ~7;
buffer->write = &buffer->data[offset];
chunk->write = &chunk->start[offset];
if (buffer->write > buffer->end)
buffer->write = buffer->end;
if (chunk->write > chunk->end)
chunk->write = chunk->end;
break;
}
buffer = buffer->next;
} while (buffer);
chunk = chunk->next;
} while (chunk);
}
void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount) {
@ -814,6 +896,18 @@ void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount) {
return (void*)ptr;
}
char* rc_buf_strncpy(rc_api_buffer_t* buffer, const char* src, size_t len) {
char* dst = rc_buf_reserve(buffer, len + 1);
memcpy(dst, src, len);
dst[len] = '\0';
rc_buf_consume(buffer, dst, dst + len + 2);
return dst;
}
char* rc_buf_strcpy(rc_api_buffer_t* buffer, const char* src) {
return rc_buf_strncpy(buffer, src, strlen(src));
}
void rc_api_destroy_request(rc_api_request_t* request) {
rc_buf_destroy(&request->buffer);
}
@ -828,13 +922,13 @@ void rc_api_format_md5(char checksum[33], const unsigned char digest[16]) {
/* --- rc_url_builder --- */
void rc_url_builder_init(rc_api_url_builder_t* builder, rc_api_buffer_t* buffer, size_t estimated_size) {
rc_api_buffer_t* used_buffer;
rc_api_buffer_chunk_t* used_buffer;
memset(builder, 0, sizeof(*builder));
builder->buffer = buffer;
builder->write = builder->start = rc_buf_reserve(buffer, estimated_size);
used_buffer = buffer;
used_buffer = &buffer->chunk;
while (used_buffer && used_buffer->write != builder->write)
used_buffer = used_buffer->next;
@ -857,7 +951,7 @@ static int rc_url_builder_reserve(rc_api_url_builder_t* builder, size_t amount)
if (remaining < amount) {
const size_t used = builder->write - builder->start;
const size_t current_size = builder->end - builder->start;
const size_t buffer_prefix_size = sizeof(rc_api_buffer_t) - sizeof(builder->buffer->data);
const size_t buffer_prefix_size = sizeof(rc_api_buffer_chunk_t);
char* new_start;
size_t new_size = (current_size < 256) ? 256 : current_size * 2;
do {
@ -1054,6 +1148,16 @@ static void rc_api_update_host(char** host, const char* hostname) {
void rc_api_set_host(const char* hostname) {
rc_api_update_host(&g_host, hostname);
if (!hostname) {
/* also clear out the image hostname */
rc_api_set_image_host(NULL);
}
else if (strcmp(hostname, RETROACHIEVEMENTS_HOST_NONSSL) == 0) {
/* if just pointing at the non-HTTPS host, explicitly use the default image host
* so it doesn't try to use the web host directly */
rc_api_set_image_host(RETROACHIEVEMENTS_IMAGE_HOST_NONSSL);
}
}
void rc_api_set_image_host(const char* hostname) {

View file

@ -10,10 +10,13 @@
extern "C" {
#endif
#define RC_CONTENT_TYPE_URLENCODED "application/x-www-form-urlencoded"
typedef struct rc_api_url_builder_t {
char* write;
char* start;
char* end;
/* pointer to a preallocated rc_api_buffer_t */
rc_api_buffer_t* buffer;
int result;
}
@ -23,22 +26,24 @@ void rc_url_builder_init(rc_api_url_builder_t* builder, rc_api_buffer_t* buffer,
void rc_url_builder_append(rc_api_url_builder_t* builder, const char* data, size_t len);
const char* rc_url_builder_finalize(rc_api_url_builder_t* builder);
#define RC_JSON_NEW_FIELD(n) {NULL,NULL,n,sizeof(n)-1,0}
typedef struct rc_json_field_t {
const char* name;
const char* value_start;
const char* value_end;
const char* name;
size_t name_len;
unsigned array_size;
}
rc_json_field_t;
typedef struct rc_json_object_field_iterator_t {
rc_json_field_t field;
typedef struct rc_json_iterator_t {
const char* json;
size_t name_len;
const char* end;
}
rc_json_object_field_iterator_t;
rc_json_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_parse_server_response(rc_api_response_t* response, const rc_api_server_response_t* server_response, 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);
@ -55,15 +60,18 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js
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);
int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* array_field, 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_iterator_t* iterator);
int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field);
int rc_json_get_object_string_length(const char* json);
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);
char* rc_buf_strcpy(rc_api_buffer_t* buffer, const char* src);
char* rc_buf_strncpy(rc_api_buffer_t* buffer, const char* src, size_t len);
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);

View file

@ -22,12 +22,24 @@ int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
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_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_code_notes_server_response(response, &response_obj);
}
int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response) {
rc_json_field_t array_field;
rc_json_iterator_t iterator;
rc_api_code_note_t* note;
const char* address_str;
const char* last_author = "";
@ -36,25 +48,25 @@ int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t*
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"CodeNotes"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("CodeNotes")
};
rc_json_field_t note_fields[] = {
{"Address"},
{"User"},
{"Note"}
RC_JSON_NEW_FIELD("Address"),
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("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]));
result = rc_json_parse_server_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"))
if (!rc_json_get_required_array(&response->num_notes, &array_field, &response->response, &fields[2], "CodeNotes"))
return RC_MISSING_VALUE;
if (response->num_notes) {
@ -62,15 +74,21 @@ int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t*
if (!response->notes)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
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 (note_fields[2].value_start) {
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"))
@ -123,27 +141,38 @@ int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api
rc_url_builder_append_str_param(&builder, "n", api_params->note);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_update_code_note_server_response(response, &response_obj);
}
int rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error")
/* unused fields
{"GameID"},
{"Address"},
{"Note"}
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("Address"),
RC_JSON_NEW_FIELD("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]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
@ -206,23 +235,34 @@ int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_a
rc_url_builder_append_str_param(&builder, "h", buffer);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_update_achievement_server_response(response, &response_obj);
}
int rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"AchievementID"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("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]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
@ -297,23 +337,34 @@ int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_a
rc_url_builder_append_str_param(&builder, "h", buffer);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_update_leaderboard_server_response(response, &response_obj);
}
int rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"LeaderboardID"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("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]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
@ -338,24 +389,37 @@ int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_ap
rc_url_builder_append_str_param(&builder, "r", "badgeiter");
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
(void)api_params;
return builder.result;
}
int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_badge_range_server_response(response, &response_obj);
}
int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"FirstBadge"},
{"NextBadge"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("FirstBadge"),
RC_JSON_NEW_FIELD("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]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
@ -399,33 +463,44 @@ int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_ad
rc_url_builder_append_str_param(&builder, "d", api_params->hash_description);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_add_game_hash_server_response(response, &response_obj);
}
int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"Response"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Response")
};
rc_json_field_t response_fields[] = {
{"GameID"}
RC_JSON_NEW_FIELD("GameID")
/* unused fields
{"MD5"},
{"ConsoleID"},
{"GameTitle"},
{"Success"}
RC_JSON_NEW_FIELD("MD5"),
RC_JSON_NEW_FIELD("ConsoleID"),
RC_JSON_NEW_FIELD("GameTitle"),
RC_JSON_NEW_FIELD("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]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;

View file

@ -27,45 +27,57 @@ int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const
rc_url_builder_append_unum_param(&builder, "c", api_params->count);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
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_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_achievement_info_server_response(response, &response_obj);
}
int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_achievement_awarded_entry_t* entry;
rc_json_field_t iterator;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
unsigned timet;
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"AchievementID"},
{"Response"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("AchievementID"),
RC_JSON_NEW_FIELD("Response")
/* unused fields
{"Offset"},
{"Count"},
{"FriendsOnly"},
RC_JSON_NEW_FIELD("Offset"),
RC_JSON_NEW_FIELD("Count"),
RC_JSON_NEW_FIELD("FriendsOnly")
* unused fields */
};
rc_json_field_t response_fields[] = {
{"NumEarned"},
{"TotalPlayers"},
{"GameID"},
{"RecentWinner"} /* array */
RC_JSON_NEW_FIELD("NumEarned"),
RC_JSON_NEW_FIELD("TotalPlayers"),
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("RecentWinner") /* array */
};
rc_json_field_t entry_fields[] = {
{"User"},
{"DateAwarded"}
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("DateAwarded")
};
memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer);
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
@ -81,7 +93,7 @@ int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info
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"))
if (!rc_json_get_required_array(&response->num_recently_awarded, &array_field, &response->response, &response_fields[3], "RecentWinner"))
return RC_MISSING_VALUE;
if (response->num_recently_awarded) {
@ -89,6 +101,10 @@ int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info
if (!response->recently_awarded)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
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"))
@ -130,57 +146,69 @@ int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const
rc_url_builder_append_unum_param(&builder, "c", api_params->count);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
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_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_leaderboard_info_server_response(response, &response_obj);
}
int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_lboard_info_entry_t* entry;
rc_json_field_t iterator;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
unsigned timet;
int result;
size_t len;
char format[16];
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"LeaderboardData"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("LeaderboardData")
};
rc_json_field_t leaderboarddata_fields[] = {
{"LBID"},
{"LBFormat"},
{"LowerIsBetter"},
{"LBTitle"},
{"LBDesc"},
{"LBMem"},
{"GameID"},
{"LBAuthor"},
{"LBCreated"},
{"LBUpdated"},
{"Entries"} /* array */
RC_JSON_NEW_FIELD("LBID"),
RC_JSON_NEW_FIELD("LBFormat"),
RC_JSON_NEW_FIELD("LowerIsBetter"),
RC_JSON_NEW_FIELD("LBTitle"),
RC_JSON_NEW_FIELD("LBDesc"),
RC_JSON_NEW_FIELD("LBMem"),
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("LBAuthor"),
RC_JSON_NEW_FIELD("LBCreated"),
RC_JSON_NEW_FIELD("LBUpdated"),
RC_JSON_NEW_FIELD("Entries") /* array */
/* unused fields
{"GameTitle"},
{"ConsoleID"},
{"ConsoleName"},
{"ForumTopicID"},
{"GameIcon"},
RC_JSON_NEW_FIELD("GameTitle"),
RC_JSON_NEW_FIELD("ConsoleID"),
RC_JSON_NEW_FIELD("ConsoleName"),
RC_JSON_NEW_FIELD("ForumTopicID"),
RC_JSON_NEW_FIELD("GameIcon")
* unused fields */
};
rc_json_field_t entry_fields[] = {
{"User"},
{"Rank"},
{"Index"},
{"Score"},
{"DateSubmitted"}
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("Rank"),
RC_JSON_NEW_FIELD("Index"),
RC_JSON_NEW_FIELD("Score"),
RC_JSON_NEW_FIELD("DateSubmitted")
};
memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer);
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
@ -218,7 +246,7 @@ int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info
response->format = RC_FORMAT_VALUE;
}
if (!rc_json_get_required_array(&response->num_entries, &iterator, &response->response, &leaderboarddata_fields[10], "Entries"))
if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &leaderboarddata_fields[10], "Entries"))
return RC_MISSING_VALUE;
if (response->num_entries) {
@ -226,6 +254,10 @@ int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info
if (!response->entries)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
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"))
@ -270,26 +302,38 @@ int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
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_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_games_list_server_response(response, &response_obj);
}
int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_game_list_entry_t* entry;
rc_json_object_field_iterator_t iterator;
rc_json_iterator_t iterator;
rc_json_field_t field;
int result;
char* end;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"Response"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Response")
};
memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer);
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
@ -308,13 +352,14 @@ int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t*
memset(&iterator, 0, sizeof(iterator));
iterator.json = fields[2].value_start;
iterator.end = fields[2].value_end;
entry = response->entries;
while (rc_json_get_next_object_field(&iterator)) {
entry->id = strtol(iterator.field.name, &end, 10);
while (rc_json_get_next_object_field(&iterator, &field)) {
entry->id = strtol(field.name, &end, 10);
iterator.field.name = "";
if (!rc_json_get_string(&entry->name, &response->response.buffer, &iterator.field, ""))
field.name = "";
if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, ""))
return RC_MISSING_VALUE;
++entry;

View file

@ -24,22 +24,33 @@ int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_res
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);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_resolve_hash_server_response(response, &response_obj);
}
int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"GameID"},
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("GameID")
};
memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer);
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
@ -65,17 +76,30 @@ int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_
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);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
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_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_game_data_server_response(response, &response_obj);
}
int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_achievement_definition_t* achievement;
rc_api_leaderboard_definition_t* leaderboard;
rc_json_field_t iterator;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
const char* str;
const char* last_author = "";
const char* last_author_field = "";
size_t last_author_len = 0;
size_t len;
unsigned timet;
@ -83,52 +107,52 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
char format[16];
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"PatchData"} /* nested object */
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("PatchData") /* nested object */
};
rc_json_field_t patchdata_fields[] = {
{"ID"},
{"Title"},
{"ConsoleID"},
{"ImageIcon"},
{"RichPresencePatch"},
{"Achievements"}, /* array */
{"Leaderboards"} /* array */
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("ConsoleID"),
RC_JSON_NEW_FIELD("ImageIcon"),
RC_JSON_NEW_FIELD("RichPresencePatch"),
RC_JSON_NEW_FIELD("Achievements"), /* array */
RC_JSON_NEW_FIELD("Leaderboards") /* array */
/* unused fields
{"ForumTopicID"},
{"Flags"},
RC_JSON_NEW_FIELD("ForumTopicID"),
RC_JSON_NEW_FIELD("Flags")
* unused fields */
};
rc_json_field_t achievement_fields[] = {
{"ID"},
{"Title"},
{"Description"},
{"Flags"},
{"Points"},
{"MemAddr"},
{"Author"},
{"BadgeName"},
{"Created"},
{"Modified"}
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("Description"),
RC_JSON_NEW_FIELD("Flags"),
RC_JSON_NEW_FIELD("Points"),
RC_JSON_NEW_FIELD("MemAddr"),
RC_JSON_NEW_FIELD("Author"),
RC_JSON_NEW_FIELD("BadgeName"),
RC_JSON_NEW_FIELD("Created"),
RC_JSON_NEW_FIELD("Modified")
};
rc_json_field_t leaderboard_fields[] = {
{"ID"},
{"Title"},
{"Description"},
{"Mem"},
{"Format"},
{"LowerIsBetter"},
{"Hidden"}
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("Description"),
RC_JSON_NEW_FIELD("Mem"),
RC_JSON_NEW_FIELD("Format"),
RC_JSON_NEW_FIELD("LowerIsBetter"),
RC_JSON_NEW_FIELD("Hidden")
};
memset(response, 0, sizeof(*response));
rc_buf_init(&response->response.buffer);
result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
@ -174,7 +198,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
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"))
if (!rc_json_get_required_array(&response->num_achievements, &array_field, &response->response, &patchdata_fields[5], "Achievements"))
return RC_MISSING_VALUE;
if (response->num_achievements) {
@ -182,6 +206,10 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
if (!response->achievements)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
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"))
@ -199,8 +227,8 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName"))
return RC_MISSING_VALUE;
len = achievement_fields[7].value_end - achievement_fields[7].value_start;
if (len == last_author_len && memcmp(achievement_fields[7].value_start, last_author, len) == 0) {
len = achievement_fields[6].value_end - achievement_fields[6].value_start;
if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) {
achievement->author = last_author;
}
else {
@ -208,6 +236,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
return RC_MISSING_VALUE;
last_author = achievement->author;
last_author_field = achievement_fields[6].value_start;
last_author_len = len;
}
@ -222,7 +251,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
}
}
if (!rc_json_get_required_array(&response->num_leaderboards, &iterator, &response->response, &patchdata_fields[6], "Leaderboards"))
if (!rc_json_get_required_array(&response->num_leaderboards, &array_field, &response->response, &patchdata_fields[6], "Leaderboards"))
return RC_MISSING_VALUE;
if (response->num_leaderboards) {
@ -230,6 +259,10 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
if (!response->leaderboards)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
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"))
@ -284,21 +317,32 @@ int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_reques
rc_url_builder_append_str_param(&builder, "m", api_params->rich_presence);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_ping_server_response(response, &response_obj);
}
int rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response) {
rc_json_field_t fields[] = {
{"Success"},
{"Error"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("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]));
return rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
}
void rc_api_destroy_ping_response(rc_api_ping_response_t* response) {
@ -337,25 +381,37 @@ int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_ap
rc_url_builder_append_str_param(&builder, "v", buffer);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_award_achievement_server_response(response, &response_obj);
}
int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"Score"},
{"AchievementID"},
{"AchievementsRemaining"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Score"),
RC_JSON_NEW_FIELD("SoftcoreScore"),
RC_JSON_NEW_FIELD("AchievementID"),
RC_JSON_NEW_FIELD("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]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
@ -373,8 +429,9 @@ int rc_api_process_award_achievement_response(rc_api_award_achievement_response_
}
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);
rc_json_get_optional_unum(&response->new_player_score_softcore, &fields[3], "SoftcoreScore", 0);
rc_json_get_optional_unum(&response->awarded_achievement_id, &fields[4], "AchievementID", 0);
rc_json_get_optional_unum(&response->achievements_remaining, &fields[5], "AchievementsRemaining", (unsigned)-1);
return RC_OK;
}
@ -416,67 +473,79 @@ int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_
rc_url_builder_append_str_param(&builder, "v", buffer);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
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_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_submit_lboard_entry_server_response(response, &response_obj);
}
int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_lboard_entry_t* entry;
rc_json_field_t iterator;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
const char* str;
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"Response"} /* nested object */
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Response") /* nested object */
};
rc_json_field_t response_fields[] = {
{"Score"},
{"BestScore"},
{"RankInfo"}, /* nested object */
{"TopEntries"} /* array */
RC_JSON_NEW_FIELD("Score"),
RC_JSON_NEW_FIELD("BestScore"),
RC_JSON_NEW_FIELD("RankInfo"), /* nested object */
RC_JSON_NEW_FIELD("TopEntries") /* array */
/* unused fields
{"LBData"}, / * array * /
{"ScoreFormatted"},
{"TopEntriesFriends"}, / * array * /
RC_JSON_NEW_FIELD("LBData"), / * array * /
RC_JSON_NEW_FIELD("ScoreFormatted"),
RC_JSON_NEW_FIELD("TopEntriesFriends") / * array * /
* unused fields */
};
/* unused fields
rc_json_field_t lbdata_fields[] = {
{"Format"},
{"LeaderboardID"},
{"GameID"},
{"Title"},
{"LowerIsBetter"}
RC_JSON_NEW_FIELD("Format"),
RC_JSON_NEW_FIELD("LeaderboardID"),
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("LowerIsBetter")
};
* unused fields */
rc_json_field_t entry_fields[] = {
{"User"},
{"Rank"},
{"Score"}
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("Rank"),
RC_JSON_NEW_FIELD("Score")
/* unused fields
{"DateSubmitted"},
RC_JSON_NEW_FIELD("DateSubmitted")
* unused fields */
};
rc_json_field_t rank_info_fields[] = {
{"Rank"},
{"NumEntries"}
RC_JSON_NEW_FIELD("Rank"),
RC_JSON_NEW_FIELD("NumEntries")
/* unused fields
{"LowerIsBetter"},
{"UserRank"},
RC_JSON_NEW_FIELD("LowerIsBetter"),
RC_JSON_NEW_FIELD("UserRank")
* unused fields */
};
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]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
@ -495,7 +564,7 @@ int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_respo
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"))
if (!rc_json_get_required_array(&response->num_top_entries, &array_field, &response->response, &response_fields[3], "TopEntries"))
return RC_MISSING_VALUE;
if (response->num_top_entries) {
@ -503,6 +572,10 @@ int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_respo
if (!response->top_entries)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
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"))

View file

@ -1,6 +1,8 @@
#include "rc_api_user.h"
#include "rc_api_common.h"
#include "../rcheevos/rc_version.h"
#include <string.h>
/* --- Login --- */
@ -25,26 +27,38 @@ int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_requ
return RC_INVALID_STATE;
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_login_server_response(response, &response_obj);
}
int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"User"},
{"Token"},
{"Score"},
{"Messages"},
{"DisplayName"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("Token"),
RC_JSON_NEW_FIELD("Score"),
RC_JSON_NEW_FIELD("SoftcoreScore"),
RC_JSON_NEW_FIELD("Messages"),
RC_JSON_NEW_FIELD("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]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
@ -54,9 +68,10 @@ int rc_api_process_login_response(rc_api_login_response_t* response, const char*
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_unum(&response->score_softcore, &fields[5], "SoftcoreScore", 0);
rc_json_get_optional_unum(&response->num_unread_messages, &fields[6], "Messages", 0);
rc_json_get_optional_string(&response->display_name, &response->response, &fields[6], "DisplayName", response->username);
rc_json_get_optional_string(&response->display_name, &response->response, &fields[7], "DisplayName", response->username);
return RC_OK;
}
@ -86,22 +101,34 @@ int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_st
*/
rc_url_builder_append_unum_param(&builder, "a", 3);
rc_url_builder_append_unum_param(&builder, "m", api_params->game_id);
rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_start_session_server_response(response, &response_obj);
}
int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response) {
rc_json_field_t fields[] = {
{"Success"},
{"Error"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("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]));
return rc_json_parse_server_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) {
@ -120,27 +147,38 @@ int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_a
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);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_user_unlocks_server_response(response, &response_obj);
}
int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
{"Success"},
{"Error"},
{"UserUnlocks"}
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("UserUnlocks")
/* unused fields
{ "GameID" },
{ "HardcoreMode" }
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("HardcoreMode")
* unused fields */
};
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]));
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;

View file

@ -159,6 +159,17 @@ void rc_destroy_parse_state(rc_parse_state_t* parse)
}
}
unsigned rc_djb2(const char* input)
{
unsigned result = 5381;
char c;
while ((c = *input++) != '\0')
result = ((result << 5) + result) + c; /* result = result * 33 + c */
return result;
}
const char* rc_error_str(int ret)
{
switch (ret) {
@ -189,7 +200,12 @@ const char* rc_error_str(int ret)
case RC_INVALID_COMPARISON: return "Invalid comparison";
case RC_INVALID_STATE: return "Invalid state";
case RC_INVALID_JSON: return "Invalid JSON";
case RC_API_FAILURE: return "API call failed";
case RC_LOGIN_REQUIRED: return "Login required";
case RC_NO_GAME_LOADED: return "No game loaded";
case RC_HARDCORE_DISABLED: return "Hardcore disabled";
case RC_ABORTED: return "Aborted";
case RC_NO_RESPONSE: return "No response";
default: return "Unknown error";
}
}

View file

@ -3,6 +3,8 @@
#include <ctype.h>
#include <stdarg.h>
#ifdef RC_C89_HELPERS
int rc_strncasecmp(const char* left, const char* right, size_t length)
{
while (length)
@ -44,20 +46,94 @@ char* rc_strdup(const char* str)
{
const size_t length = strlen(str);
char* buffer = (char*)malloc(length + 1);
memcpy(buffer, str, length + 1);
if (buffer)
memcpy(buffer, str, length + 1);
return buffer;
}
int rc_snprintf(char* buffer, size_t size, const char* format, ...)
{
int result;
va_list args;
int result;
va_list args;
va_start(args, format);
/* assume buffer is large enough and ignore size */
(void)size;
result = vsprintf(buffer, format, args);
va_end(args);
va_start(args, format);
return result;
#ifdef __STDC_WANT_SECURE_LIB__
result = vsprintf_s(buffer, size, format, args);
#else
/* assume buffer is large enough and ignore size */
(void)size;
result = vsprintf(buffer, format, args);
#endif
va_end(args);
return result;
}
#endif
#ifndef __STDC_WANT_SECURE_LIB__
struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer)
{
struct tm* tm = gmtime(timer);
memcpy(buf, tm, sizeof(*tm));
return buf;
}
#endif
#ifndef RC_NO_THREADS
#ifdef _WIN32
/* https://gist.github.com/roxlu/1c1af99f92bafff9d8d9 */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
void rc_mutex_init(rc_mutex_t* mutex)
{
/* default security, not owned by calling thread, unnamed */
mutex->handle = CreateMutex(NULL, FALSE, NULL);
}
void rc_mutex_destroy(rc_mutex_t* mutex)
{
CloseHandle(mutex->handle);
}
void rc_mutex_lock(rc_mutex_t* mutex)
{
WaitForSingleObject(mutex->handle, 0xFFFFFFFF);
}
void rc_mutex_unlock(rc_mutex_t* mutex)
{
ReleaseMutex(mutex->handle);
}
#else
void rc_mutex_init(rc_mutex_t* mutex)
{
pthread_mutex_init(mutex, NULL);
}
void rc_mutex_destroy(rc_mutex_t* mutex)
{
pthread_mutex_destroy(mutex);
}
void rc_mutex_lock(rc_mutex_t* mutex)
{
pthread_mutex_lock(mutex);
}
void rc_mutex_unlock(rc_mutex_t* mutex)
{
pthread_mutex_unlock(mutex);
}
#endif
#endif /* RC_NO_THREADS */

View file

@ -1,6 +1,91 @@
#include "rc_internal.h"
#include <stdlib.h>
#include <assert.h>
static int rc_test_condition_compare(unsigned value1, unsigned value2, char oper) {
switch (oper) {
case RC_OPERATOR_EQ: return value1 == value2;
case RC_OPERATOR_NE: return value1 != value2;
case RC_OPERATOR_LT: return value1 < value2;
case RC_OPERATOR_LE: return value1 <= value2;
case RC_OPERATOR_GT: return value1 > value2;
case RC_OPERATOR_GE: return value1 >= value2;
default: return 1;
}
}
static char rc_condition_determine_comparator(const rc_condition_t* self) {
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_NE:
case RC_OPERATOR_LT:
case RC_OPERATOR_LE:
case RC_OPERATOR_GT:
case RC_OPERATOR_GE:
break;
default:
/* not a comparison. should not be getting compared. but if it is, legacy behavior was to return 1 */
return RC_PROCESSING_COMPARE_ALWAYS_TRUE;
}
if ((self->operand1.type == RC_OPERAND_ADDRESS || self->operand1.type == RC_OPERAND_DELTA) &&
!self->operand1.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand1)) {
/* left side is an integer memory reference */
int needs_translate = (self->operand1.size != self->operand1.value.memref->value.size);
if (self->operand2.type == RC_OPERAND_CONST) {
/* right side is a constant */
if (self->operand1.type == RC_OPERAND_ADDRESS)
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_CONST;
return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_CONST;
}
else if ((self->operand2.type == RC_OPERAND_ADDRESS || self->operand2.type == RC_OPERAND_DELTA) &&
!self->operand2.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand2)) {
/* right side is an integer memory reference */
const int is_same_memref = (self->operand1.value.memref == self->operand2.value.memref);
needs_translate |= (self->operand2.size != self->operand2.value.memref->value.size);
if (self->operand1.type == RC_OPERAND_ADDRESS) {
if (self->operand2.type == RC_OPERAND_ADDRESS) {
if (is_same_memref && !needs_translate) {
/* comparing a memref to itself, will evaluate to a constant */
return rc_test_condition_compare(0, 0, self->oper) ? RC_PROCESSING_COMPARE_ALWAYS_TRUE : RC_PROCESSING_COMPARE_ALWAYS_FALSE;
}
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF;
}
assert(self->operand2.type == RC_OPERAND_DELTA);
if (is_same_memref) {
/* delta comparison is optimized to compare with itself (for detecting change) */
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_DELTA;
}
}
else {
assert(self->operand1.type == RC_OPERAND_DELTA);
if (self->operand2.type == RC_OPERAND_ADDRESS) {
if (is_same_memref) {
/* delta comparison is optimized to compare with itself (for detecting change) */
return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_MEMREF;
}
}
}
}
}
if (self->operand1.type == RC_OPERAND_CONST && self->operand2.type == RC_OPERAND_CONST) {
/* comparing constants will always generate a constant result */
return rc_test_condition_compare(self->operand1.value.num, self->operand2.value.num, self->oper) ?
RC_PROCESSING_COMPARE_ALWAYS_TRUE : RC_PROCESSING_COMPARE_ALWAYS_FALSE;
}
return RC_PROCESSING_COMPARE_DEFAULT;
}
static int rc_parse_operator(const char** memaddr) {
const char* oper = *memaddr;
@ -50,6 +135,10 @@ static int rc_parse_operator(const char** memaddr) {
++(*memaddr);
return RC_OPERATOR_AND;
case '^':
++(*memaddr);
return RC_OPERATOR_XOR;
case '\0':/* end of string */
case '_': /* next condition */
case 'S': /* next condset */
@ -71,6 +160,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
self->current_hits = 0;
self->is_true = 0;
self->pause = 0;
self->optimized_comparator = RC_PROCESSING_COMPARE_DEFAULT;
if (*aux != 0 && aux[1] == ':') {
switch (*aux) {
@ -135,6 +225,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
case RC_OPERATOR_MULT:
case RC_OPERATOR_DIV:
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
/* modifying operators are only valid on modifying statements */
if (can_modify)
break;
@ -209,6 +300,9 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
self->required_hits = 0;
}
if (parse->buffer != 0)
self->optimized_comparator = rc_condition_determine_comparator(self);
*memaddr = aux;
return self;
}
@ -228,12 +322,203 @@ int rc_condition_is_combining(const rc_condition_t* self) {
}
}
static int rc_test_condition_compare_memref_to_const(rc_condition_t* self) {
const unsigned value1 = self->operand1.value.memref->value.value;
const unsigned value2 = self->operand2.value.num;
assert(self->operand1.size == self->operand1.value.memref->value.size);
return rc_test_condition_compare(value1, value2, self->oper);
}
static int rc_test_condition_compare_delta_to_const(rc_condition_t* self) {
const rc_memref_value_t* memref1 = &self->operand1.value.memref->value;
const unsigned value1 = (memref1->changed) ? memref1->prior : memref1->value;
const unsigned value2 = self->operand2.value.num;
assert(self->operand1.size == self->operand1.value.memref->value.size);
return rc_test_condition_compare(value1, value2, self->oper);
}
static int rc_test_condition_compare_memref_to_memref(rc_condition_t* self) {
const unsigned value1 = self->operand1.value.memref->value.value;
const unsigned value2 = self->operand2.value.memref->value.value;
assert(self->operand1.size == self->operand1.value.memref->value.size);
assert(self->operand2.size == self->operand2.value.memref->value.size);
return rc_test_condition_compare(value1, value2, self->oper);
}
static int rc_test_condition_compare_memref_to_delta(rc_condition_t* self) {
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
assert(self->operand1.value.memref == self->operand2.value.memref);
assert(self->operand1.size == self->operand1.value.memref->value.size);
assert(self->operand2.size == self->operand2.value.memref->value.size);
if (memref->changed)
return rc_test_condition_compare(memref->value, memref->prior, self->oper);
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_GE:
case RC_OPERATOR_LE:
return 1;
default:
return 0;
}
}
static int rc_test_condition_compare_delta_to_memref(rc_condition_t* self) {
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
assert(self->operand1.value.memref == self->operand2.value.memref);
assert(self->operand1.size == self->operand1.value.memref->value.size);
assert(self->operand2.size == self->operand2.value.memref->value.size);
if (memref->changed)
return rc_test_condition_compare(memref->prior, memref->value, self->oper);
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_GE:
case RC_OPERATOR_LE:
return 1;
default:
return 0;
}
}
static int rc_test_condition_compare_memref_to_const_transformed(rc_condition_t* self) {
rc_typed_value_t value1;
const unsigned value2 = self->operand2.value.num;
value1.type = RC_VALUE_TYPE_UNSIGNED;
value1.value.u32 = self->operand1.value.memref->value.value;
rc_transform_memref_value(&value1, self->operand1.size);
return rc_test_condition_compare(value1.value.u32, value2, self->oper);
}
static int rc_test_condition_compare_delta_to_const_transformed(rc_condition_t* self) {
rc_typed_value_t value1;
const rc_memref_value_t* memref1 = &self->operand1.value.memref->value;
const unsigned value2 = self->operand2.value.num;
value1.type = RC_VALUE_TYPE_UNSIGNED;
value1.value.u32 = (memref1->changed) ? memref1->prior : memref1->value;
rc_transform_memref_value(&value1, self->operand1.size);
return rc_test_condition_compare(value1.value.u32, value2, self->oper);
}
static int rc_test_condition_compare_memref_to_memref_transformed(rc_condition_t* self) {
rc_typed_value_t value1, value2;
value1.type = RC_VALUE_TYPE_UNSIGNED;
value1.value.u32 = self->operand1.value.memref->value.value;
rc_transform_memref_value(&value1, self->operand1.size);
value2.type = RC_VALUE_TYPE_UNSIGNED;
value2.value.u32 = self->operand2.value.memref->value.value;
rc_transform_memref_value(&value2, self->operand2.size);
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
}
static int rc_test_condition_compare_memref_to_delta_transformed(rc_condition_t* self) {
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
assert(self->operand1.value.memref == self->operand2.value.memref);
if (memref->changed) {
rc_typed_value_t value1, value2;
value1.type = RC_VALUE_TYPE_UNSIGNED;
value1.value.u32 = memref->value;
rc_transform_memref_value(&value1, self->operand1.size);
value2.type = RC_VALUE_TYPE_UNSIGNED;
value2.value.u32 = memref->prior;
rc_transform_memref_value(&value2, self->operand2.size);
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
}
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_GE:
case RC_OPERATOR_LE:
return 1;
default:
return 0;
}
}
static int rc_test_condition_compare_delta_to_memref_transformed(rc_condition_t* self) {
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
assert(self->operand1.value.memref == self->operand2.value.memref);
if (memref->changed) {
rc_typed_value_t value1, value2;
value1.type = RC_VALUE_TYPE_UNSIGNED;
value1.value.u32 = memref->prior;
rc_transform_memref_value(&value1, self->operand1.size);
value2.type = RC_VALUE_TYPE_UNSIGNED;
value2.value.u32 = memref->value;
rc_transform_memref_value(&value2, self->operand2.size);
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
}
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_GE:
case RC_OPERATOR_LE:
return 1;
default:
return 0;
}
}
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) {
rc_typed_value_t value1, value2;
rc_evaluate_operand(&value1, &self->operand1, eval_state);
if (eval_state->add_value.type != RC_VALUE_TYPE_NONE)
if (eval_state->add_value.type != RC_VALUE_TYPE_NONE) {
/* if there's an accumulator, we can't use the optimized comparators */
rc_evaluate_operand(&value1, &self->operand1, eval_state);
rc_typed_value_add(&value1, &eval_state->add_value);
} else {
/* use an optimized comparator whenever possible */
switch (self->optimized_comparator) {
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST:
return rc_test_condition_compare_memref_to_const(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA:
return rc_test_condition_compare_memref_to_delta(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF:
return rc_test_condition_compare_memref_to_memref(self);
case RC_PROCESSING_COMPARE_DELTA_TO_CONST:
return rc_test_condition_compare_delta_to_const(self);
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF:
return rc_test_condition_compare_delta_to_memref(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED:
return rc_test_condition_compare_memref_to_const_transformed(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED:
return rc_test_condition_compare_memref_to_delta_transformed(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED:
return rc_test_condition_compare_memref_to_memref_transformed(self);
case RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED:
return rc_test_condition_compare_delta_to_const_transformed(self);
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED:
return rc_test_condition_compare_delta_to_memref_transformed(self);
case RC_PROCESSING_COMPARE_ALWAYS_TRUE:
return 1;
case RC_PROCESSING_COMPARE_ALWAYS_FALSE:
return 0;
default:
rc_evaluate_operand(&value1, &self->operand1, eval_state);
break;
}
}
rc_evaluate_operand(&value2, &self->operand2, eval_state);
@ -260,5 +545,11 @@ void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self,
rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED);
value->value.u32 &= amount.value.u32;
break;
case RC_OPERATOR_XOR:
rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED);
rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED);
value->value.u32 ^= amount.value.u32;
break;
}
}

View file

@ -84,6 +84,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
switch ((*next)->oper)
{
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_DIV:
case RC_OPERATOR_MULT:
case RC_OPERATOR_NONE:
@ -209,8 +210,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc
case RC_CONDITION_SUB_SOURCE:
rc_evaluate_condition_value(&value, condition, eval_state);
rc_typed_value_convert(&value, RC_VALUE_TYPE_SIGNED);
value.value.i32 = -value.value.i32;
rc_typed_value_negate(&value);
rc_typed_value_add(&eval_state->add_value, &value);
eval_state->add_address = 0;
continue;

View file

@ -39,6 +39,9 @@ const char* rc_console_name(int console_id)
case RC_CONSOLE_ATARI_JAGUAR:
return "Atari Jaguar";
case RC_CONSOLE_ATARI_JAGUAR_CD:
return "Atari Jaguar CD";
case RC_CONSOLE_ATARI_LYNX:
return "Atari Lynx";
@ -132,6 +135,9 @@ const char* rc_console_name(int console_id)
case RC_CONSOLE_NINTENDO_DS:
return "Nintendo DS";
case RC_CONSOLE_NINTENDO_DSI:
return "Nintendo DSi";
case RC_CONSOLE_NINTENDO_3DS:
return "Nintendo 3DS";
@ -156,6 +162,9 @@ const char* rc_console_name(int console_id)
case RC_CONSOLE_PC_ENGINE:
return "PC Engine";
case RC_CONSOLE_PC_ENGINE_CD:
return "PC Engine CD";
case RC_CONSOLE_PLAYSTATION:
return "PlayStation";
@ -198,9 +207,15 @@ const char* rc_console_name(int console_id)
case RC_CONSOLE_THOMSONTO8:
return "Thomson TO8";
case RC_CONSOLE_TI83:
return "TI-83";
case RC_CONSOLE_TIC80:
return "TIC-80";
case RC_CONSOLE_UZEBOX:
return "Uzebox";
case RC_CONSOLE_VECTREX:
return "Vectrex";
@ -435,6 +450,13 @@ static const rc_memory_region_t _rc_memory_regions_gameboy_advance[] = {
};
static const rc_memory_regions_t rc_memory_regions_gameboy_advance = { _rc_memory_regions_gameboy_advance, 2 };
/* ===== GameCube ===== */
/* https://wiibrew.org/wiki/Memory_map */
static const rc_memory_region_t _rc_memory_regions_gamecube[] = {
{ 0x00000000U, 0x017FFFFF, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_gamecube = { _rc_memory_regions_gamecube, 1 };
/* ===== Game Gear ===== */
/* http://www.smspower.org/Development/MemoryMap */
static const rc_memory_region_t _rc_memory_regions_game_gear[] = {
@ -518,6 +540,15 @@ static const rc_memory_region_t _rc_memory_regions_megadrive[] = {
};
static const rc_memory_regions_t rc_memory_regions_megadrive = { _rc_memory_regions_megadrive, 2 };
/* ===== MegaDrive 32X (Genesis 32X) ===== */
/* http://devster.monkeeh.com/sega/32xguide1.txt */
static const rc_memory_region_t _rc_memory_regions_megadrive_32x[] = {
{ 0x000000U, 0x00FFFFU, 0x00FF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Main MegaDrive RAM */
{ 0x010000U, 0x04FFFFU, 0x06000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "32X RAM"}, /* Additional 32X RAM */
{ 0x050000U, 0x05FFFFU, 0x00000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }
};
static const rc_memory_regions_t rc_memory_regions_megadrive_32x = { _rc_memory_regions_megadrive_32x, 3 };
/* ===== MSX ===== */
/* https://www.msx.org/wiki/The_Memory */
/* MSX only has 64KB of addressable RAM, of which 32KB is reserved for the system/BIOS.
@ -540,6 +571,35 @@ static const rc_memory_region_t _rc_memory_regions_neo_geo_pocket[] = {
};
static const rc_memory_regions_t rc_memory_regions_neo_geo_pocket = { _rc_memory_regions_neo_geo_pocket, 1 };
/* ===== Neo Geo CD ===== */
/* https://wiki.neogeodev.org/index.php?title=68k_memory_map */
/* NeoCD exposes $000000-$1FFFFF as System RAM, but it seems like only the WORKRAM section is used.
* This is consistent with http://www.hardmvs.fr/manuals/NeoGeoProgrammersGuide.pdf (page25), which says:
*
* Furthermore, the NEO-GEO provides addresses 100000H-10FFFFH as a work area, out of which the
* addresses 10F300H-10FFFFH are reserved exclusively for use by the system program. Therefore,
* every game is to use addresses 100000H-10F2FFH.
*
* Also note that PRG files (game ROM) can be loaded anywhere else in the $000000-$1FFFFF range.
* AoF3 illustrates this pretty clearly: https://wiki.neogeodev.org/index.php?title=IPL_file
*
* PROG_CD.PRG,0,0
* PROG_CDX.PRG,0,058000
* CNV_NM.PRG,0,0C0000
* FIX_DATA.PRG,0,0FD000
* OBJACTLK.PRG,0,130000
* SSEL_CNV.PRG,0,15A000
* SSEL_BAK.PRG,0,16F000
* HITMSG.PRG,0,170000
* SSEL_SPR.PRG,0,19D000
*/
static const rc_memory_region_t _rc_memory_regions_neo_geo_cd[] = {
{ 0x000000U, 0x00F2FFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
/* NOTE: some BIOS settings are exposed through the reserved RAM: https://wiki.neogeodev.org/index.php?title=68k_ASM_defines */
{ 0x00F300U, 0x00FFFFU, 0x0010F300U, RC_MEMORY_TYPE_SYSTEM_RAM, "Reserved RAM" },
};
static const rc_memory_regions_t rc_memory_regions_neo_geo_cd = { _rc_memory_regions_neo_geo_cd, 2 };
/* ===== Nintendo Entertainment System ===== */
/* https://wiki.nesdev.com/w/index.php/CPU_memory_map */
static const rc_memory_region_t _rc_memory_regions_nes[] = {
@ -587,6 +647,13 @@ static const rc_memory_region_t _rc_memory_regions_nintendo_ds[] = {
};
static const rc_memory_regions_t rc_memory_regions_nintendo_ds = { _rc_memory_regions_nintendo_ds, 1 };
/* ===== Nintendo DSi ===== */
/* https://problemkaputt.de/gbatek.htm#dsiiomap */
static const rc_memory_region_t _rc_memory_regions_nintendo_dsi[] = {
{ 0x000000U, 0xFFFFFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_nintendo_dsi = { _rc_memory_regions_nintendo_dsi, 1 };
/* ===== Oric ===== */
static const rc_memory_region_t _rc_memory_regions_oric[] = {
/* actual size depends on machine type - up to 64KB */
@ -605,11 +672,18 @@ static const rc_memory_regions_t rc_memory_regions_pc8800 = { _rc_memory_regions
/* http://www.archaicpixels.com/Memory_Map */
static const rc_memory_region_t _rc_memory_regions_pc_engine[] = {
{ 0x000000U, 0x001FFFU, 0x1F0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
};
static const rc_memory_regions_t rc_memory_regions_pc_engine = { _rc_memory_regions_pc_engine, 1 };
/* ===== PC Engine CD===== */
/* http://www.archaicpixels.com/Memory_Map */
static const rc_memory_region_t _rc_memory_regions_pc_engine_cd[] = {
{ 0x000000U, 0x001FFFU, 0x1F0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x002000U, 0x011FFFU, 0x100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD RAM" },
{ 0x012000U, 0x041FFFU, 0x0D0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Super System Card RAM" },
{ 0x042000U, 0x0427FFU, 0x1EE000U, RC_MEMORY_TYPE_SAVE_RAM, "CD Battery-backed RAM" }
};
static const rc_memory_regions_t rc_memory_regions_pc_engine = { _rc_memory_regions_pc_engine, 4 };
static const rc_memory_regions_t rc_memory_regions_pc_engine_cd = { _rc_memory_regions_pc_engine_cd, 4 };
/* ===== PC-FX ===== */
/* http://daifukkat.su/pcfx/data/memmap.html */
@ -711,6 +785,13 @@ static const rc_memory_region_t _rc_memory_regions_thomson_to8[] = {
};
static const rc_memory_regions_t rc_memory_regions_thomson_to8 = { _rc_memory_regions_thomson_to8, 1 };
/* ===== TI-83 ===== */
/* https://tutorials.eeems.ca/ASMin28Days/lesson/day03.html#mem */
static const rc_memory_region_t _rc_memory_regions_ti83[] = {
{ 0x000000U, 0x007FFFU, 0x008000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
};
static const rc_memory_regions_t rc_memory_regions_ti83 = { _rc_memory_regions_ti83, 1 };
/* ===== TIC-80 ===== */
/* https://github.com/nesbox/TIC-80/wiki/RAM */
static const rc_memory_region_t _rc_memory_regions_tic80[] = {
@ -727,6 +808,13 @@ static const rc_memory_region_t _rc_memory_regions_tic80[] = {
};
static const rc_memory_regions_t rc_memory_regions_tic80 = { _rc_memory_regions_tic80, 10 };
/* ===== Uzebox ===== */
/* https://uzebox.org/index.php */
static const rc_memory_region_t _rc_memory_regions_uzebox[] = {
{ 0x000000U, 0x000FFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_uzebox = { _rc_memory_regions_uzebox, 1 };
/* ===== Vectrex ===== */
/* https://roadsidethoughts.com/vectrex/vectrex-memory-map.htm */
static const rc_memory_region_t _rc_memory_regions_vectrex[] = {
@ -761,6 +849,14 @@ static const rc_memory_region_t _rc_memory_regions_wasm4[] = {
};
static const rc_memory_regions_t rc_memory_regions_wasm4 = { _rc_memory_regions_wasm4, 1 };
/* ===== Wii ===== */
/* https://wiibrew.org/wiki/Memory_map */
static const rc_memory_region_t _rc_memory_regions_wii[] = {
{ 0x00000000U, 0x017FFFFF, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x01800000U, 0x057FFFFF, 0x90000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
};
static const rc_memory_regions_t rc_memory_regions_wii = { _rc_memory_regions_wii, 2 };
/* ===== WonderSwan ===== */
/* http://daifukkat.su/docs/wsman/#ovr_memmap */
static const rc_memory_region_t _rc_memory_regions_wonderswan[] = {
@ -810,6 +906,7 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
return &rc_memory_regions_atari7800;
case RC_CONSOLE_ATARI_JAGUAR:
case RC_CONSOLE_ATARI_JAGUAR_CD:
return &rc_memory_regions_atari_jaguar;
case RC_CONSOLE_ATARI_LYNX:
@ -840,6 +937,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
case RC_CONSOLE_GAMEBOY_ADVANCE:
return &rc_memory_regions_gameboy_advance;
case RC_CONSOLE_GAMECUBE:
return &rc_memory_regions_gamecube;
case RC_CONSOLE_GAME_GEAR:
return &rc_memory_regions_game_gear;
@ -856,17 +956,20 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
return &rc_memory_regions_master_system;
case RC_CONSOLE_MEGA_DRIVE:
case RC_CONSOLE_SEGA_32X:
/* NOTE: 32x adds an extra 512KB of memory (256KB RAM + 256KB VRAM) to the
* Genesis, but we currently don't support it. */
return &rc_memory_regions_megadrive;
case RC_CONSOLE_SEGA_32X:
return &rc_memory_regions_megadrive_32x;
case RC_CONSOLE_MSX:
return &rc_memory_regions_msx;
case RC_CONSOLE_NEOGEO_POCKET:
return &rc_memory_regions_neo_geo_pocket;
case RC_CONSOLE_NEO_GEO_CD:
return &rc_memory_regions_neo_geo_cd;
case RC_CONSOLE_NINTENDO:
return &rc_memory_regions_nes;
@ -876,6 +979,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
case RC_CONSOLE_NINTENDO_DS:
return &rc_memory_regions_nintendo_ds;
case RC_CONSOLE_NINTENDO_DSI:
return &rc_memory_regions_nintendo_dsi;
case RC_CONSOLE_ORIC:
return &rc_memory_regions_oric;
@ -885,6 +991,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
case RC_CONSOLE_PC_ENGINE:
return &rc_memory_regions_pc_engine;
case RC_CONSOLE_PC_ENGINE_CD:
return &rc_memory_regions_pc_engine_cd;
case RC_CONSOLE_PCFX:
return &rc_memory_regions_pcfx;
@ -921,9 +1030,15 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
case RC_CONSOLE_THOMSONTO8:
return &rc_memory_regions_thomson_to8;
case RC_CONSOLE_TI83:
return &rc_memory_regions_ti83;
case RC_CONSOLE_TIC80:
return &rc_memory_regions_tic80;
case RC_CONSOLE_UZEBOX:
return &rc_memory_regions_uzebox;
case RC_CONSOLE_VECTREX:
return &rc_memory_regions_vectrex;
@ -933,6 +1048,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
case RC_CONSOLE_WASM4:
return &rc_memory_regions_wasm4;
case RC_CONSOLE_WII:
return &rc_memory_regions_wii;
case RC_CONSOLE_WONDERSWAN:
return &rc_memory_regions_wonderswan;

View file

@ -95,6 +95,7 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) {
switch (*aux++) {
case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break;
case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break;
case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break;
default:
return RC_INVALID_FP_OPERAND;
@ -201,6 +202,21 @@ static void rc_transform_memref_mbf32(rc_typed_value_t* value) {
value->type = RC_VALUE_TYPE_FLOAT;
}
static void rc_transform_memref_mbf32_le(rc_typed_value_t* value) {
/* decodes a Microsoft Binary Format float */
/* Locomotive BASIC (CPC) uses MBF40, but in little endian format */
const unsigned mantissa = value->value.u32 & 0x007FFFFF;
const int exponent = (int)(value->value.u32 >> 24) - 129;
const int sign = (value->value.u32 & 0x00800000);
if (mantissa == 0 && exponent == -129)
value->value.f32 = (sign) ? -0.0f : 0.0f;
else
value->value.f32 = rc_build_float(mantissa, exponent, sign);
value->type = RC_VALUE_TYPE_FLOAT;
}
static const unsigned char rc_bits_set[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 };
void rc_transform_memref_value(rc_typed_value_t* value, char size) {
@ -293,6 +309,10 @@ void rc_transform_memref_value(rc_typed_value_t* value, char size) {
rc_transform_memref_mbf32(value);
break;
case RC_MEMSIZE_MBF32_LE:
rc_transform_memref_mbf32_le(value);
break;
default:
break;
}
@ -319,6 +339,7 @@ static const unsigned rc_memref_masks[] = {
0xffffffff, /* RC_MEMSIZE_32_BITS_BE */
0xffffffff, /* RC_MEMSIZE_FLOAT */
0xffffffff, /* RC_MEMSIZE_MBF32 */
0xffffffff, /* RC_MEMSIZE_MBF32_LE */
0xffffffff /* RC_MEMSIZE_VARIABLE */
};
@ -354,6 +375,7 @@ static const char rc_memref_shared_sizes[] = {
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS_BE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */
RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */
};
@ -365,7 +387,7 @@ char rc_memref_shared_size(char size) {
return rc_memref_shared_sizes[index];
}
static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) {
unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) {
if (!peek)
return 0;
@ -422,7 +444,7 @@ void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs)
*memrefs = 0;
}
static unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type) {
static unsigned rc_get_memref_value_value(const rc_memref_value_t* memref, int operand_type) {
switch (operand_type)
{
/* most common case explicitly first, even though it could be handled by default case.

View file

@ -59,6 +59,8 @@ static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_par
self->value.luafunc = luaL_ref(parse->L, LUA_REGISTRYINDEX);
}
#else
(void)parse;
#endif /* RC_DISABLE_LUA */
self->type = RC_OPERAND_LUA;
@ -300,6 +302,7 @@ int rc_operand_is_float_memref(const rc_operand_t* self) {
switch (self->size) {
case RC_MEMSIZE_FLOAT:
case RC_MEMSIZE_MBF32:
case RC_MEMSIZE_MBF32_LE:
return 1;
default:
@ -319,6 +322,13 @@ int rc_operand_is_memref(const rc_operand_t* self) {
}
}
int rc_operand_is_float(const rc_operand_t* self) {
if (self->type == RC_OPERAND_FP)
return 1;
return rc_operand_is_float_memref(self);
}
unsigned rc_transform_operand_value(unsigned value, const rc_operand_t* self) {
switch (self->type)
{

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,290 @@
#ifndef RC_CLIENT_INTERNAL_H
#define RC_CLIENT_INTERNAL_H
#ifdef __cplusplus
extern "C" {
#endif
#include "rc_client.h"
#include "rc_compat.h"
#include "rc_runtime.h"
#include "rc_runtime_types.h"
typedef struct rc_client_callbacks_t {
rc_client_read_memory_func_t read_memory;
rc_client_event_handler_t event_handler;
rc_client_server_call_t server_call;
rc_client_message_callback_t log_call;
void* client_data;
} rc_client_callbacks_t;
struct rc_client_scheduled_callback_data_t;
typedef void (*rc_client_scheduled_callback_t)(struct rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now);
typedef struct rc_client_scheduled_callback_data_t
{
clock_t when;
unsigned related_id;
rc_client_scheduled_callback_t callback;
void* data;
struct rc_client_scheduled_callback_data_t* next;
} rc_client_scheduled_callback_data_t;
void rc_client_schedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* scheduled_callback);
enum {
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_NONE = 0,
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_TRIGGERED = (1 << 1),
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_SHOW = (1 << 2),
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE = (1 << 3),
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_UPDATE = (1 << 4) /* not a real event, just triggers update */
};
typedef struct rc_client_achievement_info_t {
rc_client_achievement_t public_;
rc_trigger_t* trigger;
uint8_t md5[16];
time_t unlock_time_hardcore;
time_t unlock_time_softcore;
uint8_t pending_events;
const char* author;
time_t created_time;
time_t updated_time;
} rc_client_achievement_info_t;
enum {
RC_CLIENT_PROGRESS_TRACKER_ACTION_NONE,
RC_CLIENT_PROGRESS_TRACKER_ACTION_SHOW,
RC_CLIENT_PROGRESS_TRACKER_ACTION_UPDATE,
RC_CLIENT_PROGRESS_TRACKER_ACTION_HIDE
};
typedef struct rc_client_progress_tracker_t {
rc_client_achievement_info_t* achievement;
float progress;
rc_client_scheduled_callback_data_t* hide_callback;
uint8_t action;
} rc_client_progress_tracker_t;
enum {
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_NONE = 0,
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_UPDATE = (1 << 1),
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_SHOW = (1 << 2),
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_HIDE = (1 << 3)
};
typedef struct rc_client_leaderboard_tracker_info_t {
rc_client_leaderboard_tracker_t public_;
struct rc_client_leaderboard_tracker_info_t* next;
int raw_value;
uint32_t value_djb2;
uint8_t format;
uint8_t pending_events;
uint8_t reference_count;
uint8_t value_from_hits;
} rc_client_leaderboard_tracker_info_t;
enum {
RC_CLIENT_LEADERBOARD_PENDING_EVENT_NONE = 0,
RC_CLIENT_LEADERBOARD_PENDING_EVENT_STARTED = (1 << 1),
RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED = (1 << 2),
RC_CLIENT_LEADERBOARD_PENDING_EVENT_SUBMITTED = (1 << 3)
};
typedef struct rc_client_leaderboard_info_t {
rc_client_leaderboard_t public_;
rc_lboard_t* lboard;
uint8_t md5[16];
rc_client_leaderboard_tracker_info_t* tracker;
uint32_t value_djb2;
int value;
uint8_t format;
uint8_t pending_events;
uint8_t bucket;
uint8_t hidden;
} rc_client_leaderboard_info_t;
enum {
RC_CLIENT_SUBSET_PENDING_EVENT_NONE = 0,
RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT = (1 << 1),
RC_CLIENT_SUBSET_PENDING_EVENT_LEADERBOARD = (1 << 2)
};
enum {
RC_CLIENT_GAME_PENDING_EVENT_NONE = 0,
RC_CLIENT_GAME_PENDING_EVENT_LEADERBOARD_TRACKER = (1 << 1),
RC_CLIENT_GAME_PENDING_EVENT_UPDATE_ACTIVE_ACHIEVEMENTS = (1 << 2),
RC_CLIENT_GAME_PENDING_EVENT_PROGRESS_TRACKER = (1 << 3)
};
typedef struct rc_client_subset_info_t {
rc_client_subset_t public_;
rc_client_achievement_info_t* achievements;
rc_client_leaderboard_info_t* leaderboards;
struct rc_client_subset_info_t* next;
const char* all_label;
const char* inactive_label;
const char* locked_label;
const char* unlocked_label;
const char* unofficial_label;
const char* unsupported_label;
uint8_t active;
uint8_t mastery;
uint8_t pending_events;
} rc_client_subset_info_t;
typedef struct rc_client_game_hash_t {
char hash[33];
uint32_t game_id;
struct rc_client_game_hash_t* next;
} rc_client_game_hash_t;
rc_client_game_hash_t* rc_client_find_game_hash(rc_client_t* client, const char* hash);
typedef struct rc_client_media_hash_t {
rc_client_game_hash_t* game_hash;
struct rc_client_media_hash_t* next;
uint32_t path_djb2;
} rc_client_media_hash_t;
typedef struct rc_client_game_info_t {
rc_client_game_t public_;
rc_client_leaderboard_tracker_info_t* leaderboard_trackers;
rc_client_progress_tracker_t progress_tracker;
rc_client_subset_info_t* subsets;
rc_client_media_hash_t* media_hash;
rc_runtime_t runtime;
uint32_t max_valid_address;
uint8_t waiting_for_reset;
uint8_t pending_events;
rc_api_buffer_t buffer;
} rc_client_game_info_t;
enum {
RC_CLIENT_LOAD_STATE_NONE,
RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME,
RC_CLIENT_LOAD_STATE_AWAIT_LOGIN,
RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA,
RC_CLIENT_LOAD_STATE_STARTING_SESSION,
RC_CLIENT_LOAD_STATE_DONE,
RC_CLIENT_LOAD_STATE_UNKNOWN_GAME
};
enum {
RC_CLIENT_USER_STATE_NONE,
RC_CLIENT_USER_STATE_LOGIN_REQUESTED,
RC_CLIENT_USER_STATE_LOGGED_IN
};
enum {
RC_CLIENT_MASTERY_STATE_NONE,
RC_CLIENT_MASTERY_STATE_PENDING,
RC_CLIENT_MASTERY_STATE_SHOWN
};
enum {
RC_CLIENT_SPECTATOR_MODE_OFF,
RC_CLIENT_SPECTATOR_MODE_ON,
RC_CLIENT_SPECTATOR_MODE_LOCKED
};
struct rc_client_load_state_t;
typedef struct rc_client_state_t {
rc_mutex_t mutex;
rc_api_buffer_t buffer;
rc_client_scheduled_callback_data_t* scheduled_callbacks;
uint8_t hardcore;
uint8_t encore_mode;
uint8_t spectator_mode;
uint8_t unofficial_enabled;
uint8_t log_level;
uint8_t user;
struct rc_client_load_state_t* load;
rc_memref_t* processing_memref;
rc_peek_t legacy_peek;
} rc_client_state_t;
struct rc_client_t {
rc_client_game_info_t* game;
rc_client_game_hash_t* hashes;
rc_client_user_t user;
rc_client_callbacks_t callbacks;
rc_client_state_t state;
};
#ifdef RC_NO_VARIADIC_MACROS
void RC_CLIENT_LOG_ERR_FORMATTED(const rc_client_t* client, const char* format, ...);
void RC_CLIENT_LOG_WARN_FORMATTED(const rc_client_t* client, const char* format, ...);
void RC_CLIENT_LOG_INFO_FORMATTED(const rc_client_t* client, const char* format, ...);
void RC_CLIENT_LOG_VERBOSE_FORMATTED(const rc_client_t* client, const char* format, ...);
#else
void rc_client_log_message_formatted(const rc_client_t* client, const char* format, ...);
#define RC_CLIENT_LOG_ERR_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
#define RC_CLIENT_LOG_WARN_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_WARN) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
#define RC_CLIENT_LOG_INFO_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
#define RC_CLIENT_LOG_VERBOSE_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_VERBOSE) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
#endif
void rc_client_log_message(const rc_client_t* client, const char* message);
#define RC_CLIENT_LOG_ERR(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR) rc_client_log_message(client, message); }
#define RC_CLIENT_LOG_WARN(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_WARN) rc_client_log_message(client, message); }
#define RC_CLIENT_LOG_INFO(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) rc_client_log_message(client, message); }
#define RC_CLIENT_LOG_VERBOSE(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_VERBOSE) rc_client_log_message(client, message); }
/* internals pulled from runtime.c */
void rc_runtime_checksum(const char* memaddr, unsigned char* md5);
int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref);
int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref);
/* end runtime.c internals */
/* helper functions for unit tests */
struct rc_hash_iterator;
struct rc_hash_iterator* rc_client_get_load_state_hash_iterator(rc_client_t* client);
/* end helper functions for unit tests */
enum {
RC_CLIENT_LEGACY_PEEK_AUTO,
RC_CLIENT_LEGACY_PEEK_CONSTRUCTED,
RC_CLIENT_LEGACY_PEEK_LITTLE_ENDIAN_READS
};
void rc_client_set_legacy_peek(rc_client_t* client, int method);
void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
#ifdef __cplusplus
}
#endif
#endif /* RC_CLIENT_INTERNAL_H */

View file

@ -13,6 +13,8 @@ extern "C" {
/* MinGW redefinitions */
#define RC_NO_VARIADIC_MACROS 1
#elif defined(_MSC_VER)
/* Visual Studio redefinitions */
@ -30,6 +32,9 @@ extern "C" {
#elif __STDC_VERSION__ < 199901L
/* C89 redefinitions */
#define RC_C89_HELPERS 1
#define RC_NO_VARIADIC_MACROS 1
#ifndef snprintf
extern int rc_snprintf(char* buffer, size_t size, const char* format, ...);
@ -53,6 +58,40 @@ extern "C" {
#endif /* __STDC_VERSION__ < 199901L */
#ifndef __STDC_WANT_SECURE_LIB__
/* _CRT_SECURE_NO_WARNINGS redefinitions */
#define strcpy_s(dest, sz, src) strcpy(dest, src)
#define sscanf_s sscanf
/* NOTE: Microsoft secure gmtime_s parameter order differs from C11 standard */
#include <time.h>
extern struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer);
#define gmtime_s rc_gmtime_s
#endif
#ifdef RC_NO_THREADS
typedef int rc_mutex_t;
#define rc_mutex_init(mutex)
#define rc_mutex_destroy(mutex)
#define rc_mutex_lock(mutex)
#define rc_mutex_unlock(mutex)
#else
#ifdef _WIN32
typedef struct rc_mutex_t {
void* handle; /* HANDLE is defined as "void*" */
} rc_mutex_t;
#else
#include <pthread.h>
typedef pthread_mutex_t rc_mutex_t;
#endif
void rc_mutex_init(rc_mutex_t* mutex);
void rc_mutex_destroy(rc_mutex_t* mutex);
void rc_mutex_lock(rc_mutex_t* mutex);
void rc_mutex_unlock(rc_mutex_t* mutex);
#endif
#ifdef __cplusplus
}
#endif

View file

@ -36,6 +36,9 @@ RC_ALLOW_ALIGN(char)
#define RC_ALLOC(t, p) ((t*)rc_alloc((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
#define RC_ALLOC_SCRATCH(t, p) ((t*)rc_alloc_scratch((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
/* force alignment to 4 bytes on 32-bit systems, or 8 bytes on 64-bit systems */
#define RC_ALIGN(n) (((n) + (sizeof(void*)-1)) & ~(sizeof(void*)-1))
typedef struct rc_scratch_buffer {
struct rc_scratch_buffer* next;
int offset;
@ -83,6 +86,8 @@ typedef struct {
}
rc_typed_value_t;
#define RC_MEASURED_UNKNOWN 0xFFFFFFFF
typedef struct {
rc_typed_value_t add_value;/* AddSource/SubSource */
int add_hits; /* AddHits */
@ -130,6 +135,8 @@ void* rc_alloc(void* pointer, int* offset, int size, int alignment, rc_scratch_t
void* rc_alloc_scratch(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset);
char* rc_alloc_str(rc_parse_state_t* parse, const char* text, int length);
unsigned rc_djb2(const char* input);
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, unsigned address, char size, char is_indirect);
int rc_parse_memref(const char** memaddr, char* size, unsigned* address);
void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud);
@ -138,6 +145,7 @@ unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_stat
char rc_memref_shared_size(char size);
unsigned rc_memref_mask(char size);
void rc_transform_memref_value(rc_typed_value_t* value, char size);
unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud);
void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse);
int rc_trigger_state_active(int state);
@ -146,6 +154,22 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state);
void rc_reset_condset(rc_condset_t* self);
enum {
RC_PROCESSING_COMPARE_DEFAULT = 0,
RC_PROCESSING_COMPARE_MEMREF_TO_CONST,
RC_PROCESSING_COMPARE_MEMREF_TO_DELTA,
RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF,
RC_PROCESSING_COMPARE_DELTA_TO_MEMREF,
RC_PROCESSING_COMPARE_DELTA_TO_CONST,
RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED,
RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED,
RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED,
RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED,
RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED,
RC_PROCESSING_COMPARE_ALWAYS_TRUE,
RC_PROCESSING_COMPARE_ALWAYS_FALSE
};
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, int is_indirect);
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state);
void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state);
@ -154,10 +178,12 @@ int rc_condition_is_combining(const rc_condition_t* self);
int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse);
void rc_evaluate_operand(rc_typed_value_t* value, rc_operand_t* self, rc_eval_state_t* eval_state);
int rc_operand_is_float_memref(const rc_operand_t* self);
int rc_operand_is_float(const rc_operand_t* self);
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse);
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L);
void rc_reset_value(rc_value_t* self);
int rc_value_from_hits(rc_value_t* self);
rc_value_t* rc_alloc_helper_variable(const char* memaddr, int memaddr_len, rc_parse_state_t* parse);
void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_State* L);
@ -165,6 +191,7 @@ void rc_typed_value_convert(rc_typed_value_t* value, char new_type);
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount);
void rc_typed_value_multiply(rc_typed_value_t* value, const rc_typed_value_t* amount);
void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amount);
void rc_typed_value_negate(rc_typed_value_t* value);
int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper);
void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref);

View file

@ -0,0 +1,816 @@
/* This file provides a series of functions for integrating RetroAchievements with libretro.
* These functions will be called by a libretro frontend to validate certain expected behaviors
* and simplify mapping core data to the RAIntegration DLL.
*
* Originally designed to be shared between RALibretro and RetroArch, but will simplify
* integrating with any other frontends.
*/
#include "rc_libretro.h"
#include "rc_consoles.h"
#include "rc_compat.h"
#include <ctype.h>
#include <string.h>
/* internal helper functions in hash.c */
extern void* rc_file_open(const char* path);
extern void rc_file_seek(void* file_handle, int64_t offset, int origin);
extern int64_t rc_file_tell(void* file_handle);
extern size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes);
extern void rc_file_close(void* file_handle);
extern int rc_path_compare_extension(const char* path, const char* ext);
extern int rc_hash_error(const char* message);
static rc_libretro_message_callback rc_libretro_verbose_message_callback = NULL;
/* a value that starts with a comma is a CSV.
* if it starts with an exclamation point, it's everything but the provided value.
* if it starts with an exclamntion point followed by a comma, it's everything but the CSV values.
* values are case-insensitive */
typedef struct rc_disallowed_core_settings_t
{
const char* library_name;
const rc_disallowed_setting_t* disallowed_settings;
} rc_disallowed_core_settings_t;
static const rc_disallowed_setting_t _rc_disallowed_bsnes_settings[] = {
{ "bsnes_region", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_cap32_settings[] = {
{ "cap32_autorun", "disabled" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_dolphin_settings[] = {
{ "dolphin_cheats_enabled", "enabled" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_duckstation_settings[] = {
{ "duckstation_CDROM.LoadImagePatches", "true" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_ecwolf_settings[] = {
{ "ecwolf-invulnerability", "enabled" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = {
{ "fbneo-allow-patched-romsets", "enabled" },
{ "fbneo-cheat-*", "!,Disabled,0 - Disabled" },
{ "fbneo-dipswitch-*", "Universe BIOS*" },
{ "fbneo-neogeo-mode", "UNIBIOS" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_fceumm_settings[] = {
{ "fceumm_region", ",PAL,Dendy" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_gpgx_settings[] = {
{ "genesis_plus_gx_lock_on", ",action replay (pro),game genie" },
{ "genesis_plus_gx_region_detect", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_gpgx_wide_settings[] = {
{ "genesis_plus_gx_wide_lock_on", ",action replay (pro),game genie" },
{ "genesis_plus_gx_wide_region_detect", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_mesen_settings[] = {
{ "mesen_region", ",PAL,Dendy" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_mesen_s_settings[] = {
{ "mesen-s_region", "PAL" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = {
{ "pcsx_rearmed_region", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_picodrive_settings[] = {
{ "picodrive_region", ",Europe,Japan PAL" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_ppsspp_settings[] = {
{ "ppsspp_cheats", "enabled" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_quasi88_settings[] = {
{ "q88_cpu_clock", ",1,2" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_smsplus_settings[] = {
{ "smsplus_region", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_snes9x_settings[] = {
{ "snes9x_gfx_clip", "disabled" },
{ "snes9x_gfx_transp", "disabled" },
{ "snes9x_layer_*", "disabled" },
{ "snes9x_region", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_vice_settings[] = {
{ "vice_autostart", "disabled" }, /* autostart dictates initial load and reset from menu */
{ "vice_reset", "!autostart" }, /* reset dictates behavior when pressing reset button (END) */
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_virtual_jaguar_settings[] = {
{ "virtualjaguar_pal", "enabled" },
{ NULL, NULL }
};
static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
{ "bsnes-mercury", _rc_disallowed_bsnes_settings },
{ "cap32", _rc_disallowed_cap32_settings },
{ "dolphin-emu", _rc_disallowed_dolphin_settings },
{ "DuckStation", _rc_disallowed_duckstation_settings },
{ "ecwolf", _rc_disallowed_ecwolf_settings },
{ "FCEUmm", _rc_disallowed_fceumm_settings },
{ "FinalBurn Neo", _rc_disallowed_fbneo_settings },
{ "Genesis Plus GX", _rc_disallowed_gpgx_settings },
{ "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_settings },
{ "Mesen", _rc_disallowed_mesen_settings },
{ "Mesen-S", _rc_disallowed_mesen_s_settings },
{ "PPSSPP", _rc_disallowed_ppsspp_settings },
{ "PCSX-ReARMed", _rc_disallowed_pcsx_rearmed_settings },
{ "PicoDrive", _rc_disallowed_picodrive_settings },
{ "QUASI88", _rc_disallowed_quasi88_settings },
{ "SMS Plus GX", _rc_disallowed_smsplus_settings },
{ "Snes9x", _rc_disallowed_snes9x_settings },
{ "VICE x64", _rc_disallowed_vice_settings },
{ "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings },
{ NULL, NULL }
};
static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char* value) {
char c1, c2;
while ((c1 = *test++)) {
if (tolower(c1) != tolower(c2 = *value++))
return (c2 == '*');
}
return (*value == '\0');
}
static int rc_libretro_match_value(const char* val, const char* match) {
/* if value starts with a comma, it's a CSV list of potential matches */
if (*match == ',') {
do {
const char* ptr = ++match;
size_t size;
while (*match && *match != ',')
++match;
size = match - ptr;
if (val[size] == '\0') {
if (memcmp(ptr, val, size) == 0) {
return 1;
}
else {
char buffer[128];
memcpy(buffer, ptr, size);
buffer[size] = '\0';
if (rc_libretro_string_equal_nocase_wildcard(buffer, val))
return 1;
}
}
} while (*match == ',');
return 0;
}
/* a leading exclamation point means the provided value(s) are not forbidden (are allowed) */
if (*match == '!')
return !rc_libretro_match_value(val, &match[1]);
/* just a single value, attempt to match it */
return rc_libretro_string_equal_nocase_wildcard(val, match);
}
int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value) {
const char* key;
size_t key_len;
for (; disallowed_settings->setting; ++disallowed_settings) {
key = disallowed_settings->setting;
key_len = strlen(key);
if (key[key_len - 1] == '*') {
if (memcmp(setting, key, key_len - 1) == 0) {
if (rc_libretro_match_value(value, disallowed_settings->value))
return 0;
}
}
else {
if (memcmp(setting, key, key_len + 1) == 0) {
if (rc_libretro_match_value(value, disallowed_settings->value))
return 0;
}
}
}
return 1;
}
const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name) {
const rc_disallowed_core_settings_t* core_filter = rc_disallowed_core_settings;
size_t library_name_length;
if (!library_name || !library_name[0])
return NULL;
library_name_length = strlen(library_name) + 1;
while (core_filter->library_name) {
if (memcmp(core_filter->library_name, library_name, library_name_length) == 0)
return core_filter->disallowed_settings;
++core_filter;
}
return NULL;
}
typedef struct rc_disallowed_core_systems_t
{
const char* library_name;
const int disallowed_consoles[4];
} rc_disallowed_core_systems_t;
static const rc_disallowed_core_systems_t rc_disallowed_core_systems[] = {
/* https://github.com/libretro/Mesen-S/issues/8 */
{ "Mesen-S", { RC_CONSOLE_GAMEBOY, RC_CONSOLE_GAMEBOY_COLOR, 0 }},
{ NULL, { 0 } }
};
int rc_libretro_is_system_allowed(const char* library_name, int console_id) {
const rc_disallowed_core_systems_t* core_filter = rc_disallowed_core_systems;
size_t library_name_length;
size_t i;
if (!library_name || !library_name[0])
return 1;
library_name_length = strlen(library_name) + 1;
while (core_filter->library_name) {
if (memcmp(core_filter->library_name, library_name, library_name_length) == 0) {
for (i = 0; i < sizeof(core_filter->disallowed_consoles) / sizeof(core_filter->disallowed_consoles[0]); ++i) {
if (core_filter->disallowed_consoles[i] == console_id)
return 0;
}
break;
}
++core_filter;
}
return 1;
}
unsigned char* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, unsigned address, unsigned* avail) {
unsigned i;
for (i = 0; i < regions->count; ++i) {
const size_t size = regions->size[i];
if (address < size) {
if (regions->data[i] == NULL)
break;
if (avail)
*avail = (unsigned)(size - address);
return &regions->data[i][address];
}
address -= (unsigned)size;
}
if (avail)
*avail = 0;
return NULL;
}
unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address) {
return rc_libretro_memory_find_avail(regions, address, NULL);
}
uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, unsigned address,
uint8_t* buffer, uint32_t num_bytes) {
unsigned i;
uint32_t avail;
for (i = 0; i < regions->count; ++i) {
const size_t size = regions->size[i];
if (address < size) {
if (regions->data[i] == NULL)
break;
avail = (unsigned)(size - address);
if (avail < num_bytes)
return avail;
memcpy(buffer, &regions->data[i][address], num_bytes);
return num_bytes;
}
address -= (unsigned)size;
}
return 0;
}
void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback) {
rc_libretro_verbose_message_callback = callback;
}
static void rc_libretro_verbose(const char* message) {
if (rc_libretro_verbose_message_callback)
rc_libretro_verbose_message_callback(message);
}
static const char* rc_memory_type_str(int type) {
switch (type)
{
case RC_MEMORY_TYPE_SAVE_RAM:
return "SRAM";
case RC_MEMORY_TYPE_VIDEO_RAM:
return "VRAM";
case RC_MEMORY_TYPE_UNUSED:
return "UNUSED";
default:
break;
}
return "SYSTEM RAM";
}
static void rc_libretro_memory_register_region(rc_libretro_memory_regions_t* regions, int type,
unsigned char* data, size_t size, const char* description) {
if (size == 0)
return;
if (regions->count == (sizeof(regions->size) / sizeof(regions->size[0]))) {
rc_libretro_verbose("Too many memory memory regions to register");
return;
}
if (!data && regions->count > 0 && !regions->data[regions->count - 1]) {
/* extend null region */
regions->size[regions->count - 1] += size;
}
else if (data && regions->count > 0 &&
data == (regions->data[regions->count - 1] + regions->size[regions->count - 1])) {
/* extend non-null region */
regions->size[regions->count - 1] += size;
}
else {
/* create new region */
regions->data[regions->count] = data;
regions->size[regions->count] = size;
++regions->count;
}
regions->total_size += size;
if (rc_libretro_verbose_message_callback) {
char message[128];
snprintf(message, sizeof(message), "Registered 0x%04X bytes of %s at $%06X (%s)", (unsigned)size,
rc_memory_type_str(type), (unsigned)(regions->total_size - size), description);
rc_libretro_verbose_message_callback(message);
}
}
static void rc_libretro_memory_init_without_regions(rc_libretro_memory_regions_t* regions,
rc_libretro_get_core_memory_info_func get_core_memory_info) {
/* no regions specified, assume system RAM followed by save RAM */
char description[64];
rc_libretro_core_memory_info_t info;
snprintf(description, sizeof(description), "offset 0x%06x", 0);
get_core_memory_info(RETRO_MEMORY_SYSTEM_RAM, &info);
if (info.size)
rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SYSTEM_RAM, info.data, info.size, description);
get_core_memory_info(RETRO_MEMORY_SAVE_RAM, &info);
if (info.size)
rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SAVE_RAM, info.data, info.size, description);
}
static const struct retro_memory_descriptor* rc_libretro_memory_get_descriptor(const struct retro_memory_map* mmap, unsigned real_address, size_t* offset)
{
const struct retro_memory_descriptor* desc = mmap->descriptors;
const struct retro_memory_descriptor* end = desc + mmap->num_descriptors;
for (; desc < end; desc++) {
if (desc->select == 0) {
/* if select is 0, attempt to explcitly match the address */
if (real_address >= desc->start && real_address < desc->start + desc->len) {
*offset = real_address - desc->start;
return desc;
}
}
else {
/* otherwise, attempt to match the address by matching the select bits */
/* address is in the block if (addr & select) == (start & select) */
if (((desc->start ^ real_address) & desc->select) == 0) {
/* get the relative offset of the address from the start of the memory block */
unsigned reduced_address = real_address - (unsigned)desc->start;
/* remove any bits from the reduced_address that correspond to the bits in the disconnect
* mask and collapse the remaining bits. this code was copied from the mmap_reduce function
* in RetroArch. i'm not exactly sure how it works, but it does. */
unsigned disconnect_mask = (unsigned)desc->disconnect;
while (disconnect_mask) {
const unsigned tmp = (disconnect_mask - 1) & ~disconnect_mask;
reduced_address = (reduced_address & tmp) | ((reduced_address >> 1) & ~tmp);
disconnect_mask = (disconnect_mask & (disconnect_mask - 1)) >> 1;
}
/* calculate the offset within the descriptor */
*offset = reduced_address;
/* sanity check - make sure the descriptor is large enough to hold the target address */
if (reduced_address < desc->len)
return desc;
}
}
}
*offset = 0;
return NULL;
}
static void rc_libretro_memory_init_from_memory_map(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
const rc_memory_regions_t* console_regions) {
char description[64];
unsigned i;
unsigned char* region_start;
unsigned char* desc_start;
size_t desc_size;
size_t offset;
for (i = 0; i < console_regions->num_regions; ++i) {
const rc_memory_region_t* console_region = &console_regions->region[i];
size_t console_region_size = console_region->end_address - console_region->start_address + 1;
unsigned real_address = console_region->real_address;
unsigned disconnect_size = 0;
while (console_region_size > 0) {
const struct retro_memory_descriptor* desc = rc_libretro_memory_get_descriptor(mmap, real_address, &offset);
if (!desc) {
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
snprintf(description, sizeof(description), "Could not map region starting at $%06X",
real_address - console_region->real_address + console_region->start_address);
rc_libretro_verbose(description);
}
if (disconnect_size && console_region_size > disconnect_size) {
rc_libretro_memory_register_region(regions, console_region->type, NULL, disconnect_size, "null filler");
console_region_size -= disconnect_size;
real_address += disconnect_size;
disconnect_size = 0;
continue;
}
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
break;
}
snprintf(description, sizeof(description), "descriptor %u, offset 0x%06X%s",
(unsigned)(desc - mmap->descriptors) + 1, (int)offset, desc->ptr ? "" : " [no pointer]");
if (desc->ptr) {
desc_start = (uint8_t*)desc->ptr + desc->offset;
region_start = desc_start + offset;
}
else {
region_start = NULL;
}
desc_size = desc->len - offset;
if (desc->disconnect && desc_size > desc->disconnect) {
/* if we need to extract a disconnect bit, the largest block we can read is up to
* the next time that bit flips */
/* https://stackoverflow.com/questions/12247186/find-the-lowest-set-bit */
disconnect_size = (desc->disconnect & -((int)desc->disconnect));
desc_size = disconnect_size - (real_address & (disconnect_size - 1));
}
if (console_region_size > desc_size) {
if (desc_size == 0) {
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
snprintf(description, sizeof(description), "Could not map region starting at $%06X",
real_address - console_region->real_address + console_region->start_address);
rc_libretro_verbose(description);
}
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
console_region_size = 0;
}
else {
rc_libretro_memory_register_region(regions, console_region->type, region_start, desc_size, description);
console_region_size -= desc_size;
real_address += (unsigned)desc_size;
}
}
else {
rc_libretro_memory_register_region(regions, console_region->type, region_start, console_region_size, description);
console_region_size = 0;
}
}
}
}
static unsigned rc_libretro_memory_console_region_to_ram_type(int region_type) {
switch (region_type)
{
case RC_MEMORY_TYPE_SAVE_RAM:
return RETRO_MEMORY_SAVE_RAM;
case RC_MEMORY_TYPE_VIDEO_RAM:
return RETRO_MEMORY_VIDEO_RAM;
default:
break;
}
return RETRO_MEMORY_SYSTEM_RAM;
}
static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regions_t* regions,
rc_libretro_get_core_memory_info_func get_core_memory_info, const rc_memory_regions_t* console_regions) {
char description[64];
unsigned i, j;
rc_libretro_core_memory_info_t info;
size_t offset;
for (i = 0; i < console_regions->num_regions; ++i) {
const rc_memory_region_t* console_region = &console_regions->region[i];
const size_t console_region_size = console_region->end_address - console_region->start_address + 1;
const unsigned type = rc_libretro_memory_console_region_to_ram_type(console_region->type);
unsigned base_address = 0;
for (j = 0; j <= i; ++j) {
const rc_memory_region_t* console_region2 = &console_regions->region[j];
if (rc_libretro_memory_console_region_to_ram_type(console_region2->type) == type) {
base_address = console_region2->start_address;
break;
}
}
offset = console_region->start_address - base_address;
get_core_memory_info(type, &info);
if (offset < info.size) {
info.size -= offset;
if (info.data) {
snprintf(description, sizeof(description), "offset 0x%06X", (int)offset);
info.data += offset;
}
else {
snprintf(description, sizeof(description), "null filler");
}
}
else {
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
snprintf(description, sizeof(description), "Could not map region starting at $%06X", console_region->start_address);
rc_libretro_verbose(description);
}
info.data = NULL;
info.size = 0;
}
if (console_region_size > info.size) {
/* want more than what is available, take what we can and null fill the rest */
rc_libretro_memory_register_region(regions, console_region->type, info.data, info.size, description);
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size - info.size, "null filler");
}
else {
/* only take as much as we need */
rc_libretro_memory_register_region(regions, console_region->type, info.data, console_region_size, description);
}
}
}
int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
rc_libretro_get_core_memory_info_func get_core_memory_info, int console_id) {
const rc_memory_regions_t* console_regions = rc_console_memory_regions(console_id);
rc_libretro_memory_regions_t new_regions;
int has_valid_region = 0;
unsigned i;
if (!regions)
return 0;
memset(&new_regions, 0, sizeof(new_regions));
if (console_regions == NULL || console_regions->num_regions == 0)
rc_libretro_memory_init_without_regions(&new_regions, get_core_memory_info);
else if (mmap && mmap->num_descriptors != 0)
rc_libretro_memory_init_from_memory_map(&new_regions, mmap, console_regions);
else
rc_libretro_memory_init_from_unmapped_memory(&new_regions, get_core_memory_info, console_regions);
/* determine if any valid regions were found */
for (i = 0; i < new_regions.count; i++) {
if (new_regions.data[i]) {
has_valid_region = 1;
break;
}
}
memcpy(regions, &new_regions, sizeof(*regions));
return has_valid_region;
}
void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions) {
memset(regions, 0, sizeof(*regions));
}
void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
const char* m3u_path, rc_libretro_get_image_path_func get_image_path) {
char image_path[1024];
char* m3u_contents;
char* ptr;
int64_t file_len;
void* file_handle;
int index = 0;
memset(hash_set, 0, sizeof(*hash_set));
if (!rc_path_compare_extension(m3u_path, "m3u"))
return;
file_handle = rc_file_open(m3u_path);
if (!file_handle)
{
rc_hash_error("Could not open playlist");
return;
}
rc_file_seek(file_handle, 0, SEEK_END);
file_len = rc_file_tell(file_handle);
rc_file_seek(file_handle, 0, SEEK_SET);
m3u_contents = (char*)malloc((size_t)file_len + 1);
rc_file_read(file_handle, m3u_contents, (int)file_len);
m3u_contents[file_len] = '\0';
rc_file_close(file_handle);
ptr = m3u_contents;
do
{
/* ignore whitespace */
while (isspace((int)*ptr))
++ptr;
if (*ptr == '#')
{
/* ignore comment unless it's the special SAVEDISK extension */
if (memcmp(ptr, "#SAVEDISK:", 10) == 0)
{
/* get the path to the save disk from the frontend, assign it a bogus hash so
* it doesn't get hashed later */
if (get_image_path(index, image_path, sizeof(image_path)))
{
const char save_disk_hash[33] = "[SAVE DISK]";
rc_libretro_hash_set_add(hash_set, image_path, -1, save_disk_hash);
++index;
}
}
}
else
{
/* non-empty line, tally a file */
++index;
}
/* find the end of the line */
while (*ptr && *ptr != '\n')
++ptr;
} while (*ptr);
free(m3u_contents);
if (hash_set->entries_count > 0)
{
/* at least one save disk was found. make sure the core supports the #SAVEDISK: extension by
* asking for the last expected disk. if it's not found, assume no #SAVEDISK: support */
if (!get_image_path(index - 1, image_path, sizeof(image_path)))
hash_set->entries_count = 0;
}
}
void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set) {
if (hash_set->entries)
free(hash_set->entries);
memset(hash_set, 0, sizeof(*hash_set));
}
static unsigned rc_libretro_djb2(const char* input)
{
unsigned result = 5381;
char c;
while ((c = *input++) != '\0')
result = ((result << 5) + result) + c; /* result = result * 33 + c */
return result;
}
void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
const char* path, int game_id, const char hash[33]) {
const unsigned path_djb2 = (path != NULL) ? rc_libretro_djb2(path) : 0;
struct rc_libretro_hash_entry_t* entry = NULL;
struct rc_libretro_hash_entry_t* scan;
struct rc_libretro_hash_entry_t* stop = hash_set->entries + hash_set->entries_count;;
if (path_djb2)
{
/* attempt to match the path */
for (scan = hash_set->entries; scan < stop; ++scan)
{
if (scan->path_djb2 == path_djb2)
{
entry = scan;
break;
}
}
}
if (!entry)
{
/* entry not found, allocate a new one */
if (hash_set->entries_size == 0)
{
hash_set->entries_size = 4;
hash_set->entries = (struct rc_libretro_hash_entry_t*)
malloc(hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
}
else if (hash_set->entries_count == hash_set->entries_size)
{
hash_set->entries_size += 4;
hash_set->entries = (struct rc_libretro_hash_entry_t*)realloc(hash_set->entries,
hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
}
entry = hash_set->entries + hash_set->entries_count++;
}
/* update the entry */
entry->path_djb2 = path_djb2;
entry->game_id = game_id;
memcpy(entry->hash, hash, sizeof(entry->hash));
}
const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path)
{
const unsigned path_djb2 = rc_libretro_djb2(path);
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
for (; scan < stop; ++scan)
{
if (scan->path_djb2 == path_djb2)
return scan->hash;
}
return NULL;
}
int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash)
{
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
for (; scan < stop; ++scan)
{
if (memcmp(scan->hash, hash, sizeof(scan->hash)) == 0)
return scan->game_id;
}
return 0;
}

View file

@ -0,0 +1,92 @@
#ifndef RC_LIBRETRO_H
#define RC_LIBRETRO_H
/* this file comes from the libretro repository, which is not an explicit submodule.
* the integration must set up paths appropriately to find it. */
#include <libretro.h>
#ifdef __cplusplus
extern "C" {
#endif
/*****************************************************************************\
| Disallowed Settings |
\*****************************************************************************/
typedef struct rc_disallowed_setting_t
{
const char* setting;
const char* value;
} rc_disallowed_setting_t;
const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name);
int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value);
int rc_libretro_is_system_allowed(const char* library_name, int console_id);
/*****************************************************************************\
| Memory Mapping |
\*****************************************************************************/
/* specifies a function to call for verbose logging */
typedef void (*rc_libretro_message_callback)(const char*);
void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback);
#define RC_LIBRETRO_MAX_MEMORY_REGIONS 32
typedef struct rc_libretro_memory_regions_t
{
unsigned char* data[RC_LIBRETRO_MAX_MEMORY_REGIONS];
size_t size[RC_LIBRETRO_MAX_MEMORY_REGIONS];
size_t total_size;
unsigned count;
} rc_libretro_memory_regions_t;
typedef struct rc_libretro_core_memory_info_t
{
unsigned char* data;
size_t size;
} rc_libretro_core_memory_info_t;
typedef void (*rc_libretro_get_core_memory_info_func)(unsigned id, rc_libretro_core_memory_info_t* info);
int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
rc_libretro_get_core_memory_info_func get_core_memory_info, int console_id);
void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions);
unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address);
unsigned char* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, unsigned address, unsigned* avail);
uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, unsigned address, uint8_t* buffer, uint32_t num_bytes);
/*****************************************************************************\
| Disk Identification |
\*****************************************************************************/
typedef struct rc_libretro_hash_entry_t
{
uint32_t path_djb2;
int game_id;
char hash[33];
} rc_libretro_hash_entry_t;
typedef struct rc_libretro_hash_set_t
{
struct rc_libretro_hash_entry_t* entries;
uint16_t entries_count;
uint16_t entries_size;
} rc_libretro_hash_set_t;
typedef int (*rc_libretro_get_image_path_func)(unsigned index, char* buffer, size_t buffer_size);
void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
const char* m3u_path, rc_libretro_get_image_path_func get_image_path);
void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set);
void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
const char* path, int game_id, const char hash[33]);
const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path);
int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash);
#ifdef __cplusplus
}
#endif
#endif /* RC_LIBRETRO_H */

View file

@ -0,0 +1,847 @@
#include "rc_validate.h"
#include "rc_compat.h"
#include "rc_consoles.h"
#include "rc_internal.h"
#include <stddef.h>
#include <stdlib.h>
static int rc_validate_memref(const rc_memref_t* memref, char result[], const size_t result_size, int console_id, unsigned max_address)
{
if (memref->address > max_address) {
snprintf(result, result_size, "Address %04X out of range (max %04X)", memref->address, max_address);
return 0;
}
switch (console_id) {
case RC_CONSOLE_NINTENDO:
if (memref->address >= 0x0800 && memref->address <= 0x1FFF) {
snprintf(result, result_size, "Mirror RAM may not be exposed by emulator (address %04X)", memref->address);
return 0;
}
break;
case RC_CONSOLE_GAMEBOY:
case RC_CONSOLE_GAMEBOY_COLOR:
if (memref->address >= 0xE000 && memref->address <= 0xFDFF) {
snprintf(result, result_size, "Echo RAM may not be exposed by emulator (address %04X)", memref->address);
return 0;
}
break;
case RC_CONSOLE_PLAYSTATION:
if (memref->address <= 0xFFFF) {
snprintf(result, result_size, "Kernel RAM may not be initialized without real BIOS (address %04X)", memref->address);
return 0;
}
break;
}
return 1;
}
int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, unsigned max_address)
{
while (memref) {
if (!rc_validate_memref(memref, result, result_size, 0, max_address))
return 0;
memref = memref->next;
}
return 1;
}
static unsigned rc_console_max_address(int console_id)
{
const rc_memory_regions_t* memory_regions;
memory_regions = rc_console_memory_regions(console_id);
if (memory_regions && memory_regions->num_regions > 0)
return memory_regions->region[memory_regions->num_regions - 1].end_address;
return 0xFFFFFFFF;
}
int rc_validate_memrefs_for_console(const rc_memref_t* memref, char result[], const size_t result_size, int console_id)
{
const unsigned max_address = rc_console_max_address(console_id);
while (memref) {
if (!rc_validate_memref(memref, result, result_size, console_id, max_address))
return 0;
memref = memref->next;
}
return 1;
}
static unsigned rc_max_value(const rc_operand_t* operand)
{
if (operand->type == RC_OPERAND_CONST)
return operand->value.num;
if (!rc_operand_is_memref(operand))
return 0xFFFFFFFF;
switch (operand->size) {
case RC_MEMSIZE_BIT_0:
case RC_MEMSIZE_BIT_1:
case RC_MEMSIZE_BIT_2:
case RC_MEMSIZE_BIT_3:
case RC_MEMSIZE_BIT_4:
case RC_MEMSIZE_BIT_5:
case RC_MEMSIZE_BIT_6:
case RC_MEMSIZE_BIT_7:
return 1;
case RC_MEMSIZE_LOW:
case RC_MEMSIZE_HIGH:
return 0xF;
case RC_MEMSIZE_BITCOUNT:
return 8;
case RC_MEMSIZE_8_BITS:
return (operand->type == RC_OPERAND_BCD) ? 165 : 0xFF;
case RC_MEMSIZE_16_BITS:
case RC_MEMSIZE_16_BITS_BE:
return (operand->type == RC_OPERAND_BCD) ? 16665 : 0xFFFF;
case RC_MEMSIZE_24_BITS:
case RC_MEMSIZE_24_BITS_BE:
return (operand->type == RC_OPERAND_BCD) ? 1666665 : 0xFFFFFF;
default:
return (operand->type == RC_OPERAND_BCD) ? 166666665 : 0xFFFFFFFF;
}
}
static unsigned rc_scale_value(unsigned value, char oper, const rc_operand_t* operand)
{
switch (oper) {
case RC_OPERATOR_MULT:
{
unsigned long long scaled = ((unsigned long long)value) * rc_max_value(operand);
if (scaled > 0xFFFFFFFF)
return 0xFFFFFFFF;
return (unsigned)scaled;
}
case RC_OPERATOR_DIV:
{
const unsigned min_val = (operand->type == RC_OPERAND_CONST) ? operand->value.num : 1;
return value / min_val;
}
case RC_OPERATOR_AND:
return rc_max_value(operand);
case RC_OPERATOR_XOR:
return value | rc_max_value(operand);
default:
return value;
}
}
static int rc_validate_get_condition_index(const rc_condset_t* condset, const rc_condition_t* condition)
{
int index = 1;
const rc_condition_t* scan;
for (scan = condset->conditions; scan != NULL; scan = scan->next)
{
if (scan == condition)
return index;
++index;
}
return 0;
}
static int rc_validate_range(unsigned min_val, unsigned max_val, char oper, unsigned max, char result[], const size_t result_size)
{
switch (oper) {
case RC_OPERATOR_AND:
if (min_val > max) {
snprintf(result, result_size, "Mask has more bits than source");
return 0;
}
else if (min_val == 0 && max_val == 0) {
snprintf(result, result_size, "Result of mask always 0");
return 0;
}
break;
case RC_OPERATOR_EQ:
if (min_val > max) {
snprintf(result, result_size, "Comparison is never true");
return 0;
}
break;
case RC_OPERATOR_NE:
if (min_val > max) {
snprintf(result, result_size, "Comparison is always true");
return 0;
}
break;
case RC_OPERATOR_GE:
if (min_val > max) {
snprintf(result, result_size, "Comparison is never true");
return 0;
}
if (max_val == 0) {
snprintf(result, result_size, "Comparison is always true");
return 0;
}
break;
case RC_OPERATOR_GT:
if (min_val >= max) {
snprintf(result, result_size, "Comparison is never true");
return 0;
}
break;
case RC_OPERATOR_LE:
if (min_val >= max) {
snprintf(result, result_size, "Comparison is always true");
return 0;
}
break;
case RC_OPERATOR_LT:
if (min_val > max) {
snprintf(result, result_size, "Comparison is always true");
return 0;
}
if (max_val == 0) {
snprintf(result, result_size, "Comparison is never true");
return 0;
}
break;
}
return 1;
}
int rc_validate_condset_internal(const rc_condset_t* condset, char result[], const size_t result_size, int console_id, unsigned max_address)
{
const rc_condition_t* cond;
char buffer[128];
unsigned max_val;
int index = 1;
unsigned long long add_source_max = 0;
int in_add_hits = 0;
int in_add_address = 0;
int is_combining = 0;
if (!condset) {
*result = '\0';
return 1;
}
for (cond = condset->conditions; cond; cond = cond->next, ++index) {
unsigned max = rc_max_value(&cond->operand1);
const int is_memref1 = rc_operand_is_memref(&cond->operand1);
const int is_memref2 = rc_operand_is_memref(&cond->operand2);
if (!in_add_address) {
if (is_memref1 && !rc_validate_memref(cond->operand1.value.memref, buffer, sizeof(buffer), console_id, max_address)) {
snprintf(result, result_size, "Condition %d: %s", index, buffer);
return 0;
}
if (is_memref2 && !rc_validate_memref(cond->operand2.value.memref, buffer, sizeof(buffer), console_id, max_address)) {
snprintf(result, result_size, "Condition %d: %s", index, buffer);
return 0;
}
}
else {
in_add_address = 0;
}
switch (cond->type) {
case RC_CONDITION_ADD_SOURCE:
max = rc_scale_value(max, cond->oper, &cond->operand2);
add_source_max += max;
is_combining = 1;
continue;
case RC_CONDITION_SUB_SOURCE:
max = rc_scale_value(max, cond->oper, &cond->operand2);
if (add_source_max < max) /* potential underflow - may be expected */
add_source_max = 0xFFFFFFFF;
is_combining = 1;
continue;
case RC_CONDITION_ADD_ADDRESS:
if (cond->operand1.type == RC_OPERAND_DELTA || cond->operand1.type == RC_OPERAND_PRIOR) {
snprintf(result, result_size, "Condition %d: Using pointer from previous frame", index);
return 0;
}
in_add_address = 1;
is_combining = 1;
continue;
case RC_CONDITION_ADD_HITS:
case RC_CONDITION_SUB_HITS:
in_add_hits = 1;
is_combining = 1;
break;
case RC_CONDITION_AND_NEXT:
case RC_CONDITION_OR_NEXT:
case RC_CONDITION_RESET_NEXT_IF:
is_combining = 1;
break;
default:
if (in_add_hits) {
if (cond->required_hits == 0) {
snprintf(result, result_size, "Condition %d: Final condition in AddHits chain must have a hit target", index);
return 0;
}
in_add_hits = 0;
}
is_combining = 0;
break;
}
/* if we're in an add source chain, check for overflow */
if (add_source_max) {
const unsigned long long overflow = add_source_max + max;
if (overflow > 0xFFFFFFFFUL)
max = 0xFFFFFFFF;
else
max += (unsigned)add_source_max;
}
/* check for comparing two differently sized memrefs */
max_val = rc_max_value(&cond->operand2);
if (max_val != max && add_source_max == 0 && is_memref1 && is_memref2) {
snprintf(result, result_size, "Condition %d: Comparing different memory sizes", index);
return 0;
}
/* if either side is a memref, or there's a running add source chain, check for impossible comparisons */
if (is_memref1 || is_memref2 || add_source_max) {
const size_t prefix_length = snprintf(result, result_size, "Condition %d: ", index);
unsigned min_val;
switch (cond->operand2.type) {
case RC_OPERAND_CONST:
min_val = cond->operand2.value.num;
break;
case RC_OPERAND_FP:
min_val = (int)cond->operand2.value.dbl;
/* cannot compare an integer memory reference to a non-integral floating point value */
/* assert: is_memref1 (because operand2==FP means !is_memref2) */
if (!add_source_max && !rc_operand_is_float_memref(&cond->operand1) &&
(float)min_val != cond->operand2.value.dbl) {
snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true");
return 0;
}
break;
default:
min_val = 0;
/* cannot compare an integer memory reference to a non-integral floating point value */
/* assert: is_memref2 (because operand1==FP means !is_memref1) */
if (cond->operand1.type == RC_OPERAND_FP && !add_source_max && !rc_operand_is_float_memref(&cond->operand2) &&
(float)((int)cond->operand1.value.dbl) != cond->operand1.value.dbl) {
snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true");
return 0;
}
break;
}
if (rc_operand_is_float(&cond->operand2) && rc_operand_is_float(&cond->operand1)) {
/* both sides are floats, don't validate range*/
} else if (!rc_validate_range(min_val, max_val, cond->oper, max, result + prefix_length, result_size - prefix_length)) {
return 0;
}
}
add_source_max = 0;
}
if (is_combining) {
snprintf(result, result_size, "Final condition type expects another condition to follow");
return 0;
}
*result = '\0';
return 1;
}
int rc_validate_condset(const rc_condset_t* condset, char result[], const size_t result_size, unsigned max_address)
{
return rc_validate_condset_internal(condset, result, result_size, 0, max_address);
}
int rc_validate_condset_for_console(const rc_condset_t* condset, char result[], const size_t result_size, int console_id)
{
const unsigned max_address = rc_console_max_address(console_id);
return rc_validate_condset_internal(condset, result, result_size, console_id, max_address);
}
static int rc_validate_is_combining_condition(const rc_condition_t* condition)
{
switch (condition->type)
{
case RC_CONDITION_ADD_ADDRESS:
case RC_CONDITION_ADD_HITS:
case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_AND_NEXT:
case RC_CONDITION_OR_NEXT:
case RC_CONDITION_RESET_NEXT_IF:
case RC_CONDITION_SUB_HITS:
case RC_CONDITION_SUB_SOURCE:
return 1;
default:
return 0;
}
}
static const rc_condition_t* rc_validate_next_non_combining_condition(const rc_condition_t* condition)
{
int is_combining = rc_validate_is_combining_condition(condition);
for (condition = condition->next; condition != NULL; condition = condition->next)
{
if (rc_validate_is_combining_condition(condition))
is_combining = 1;
else if (is_combining)
is_combining = 0;
else
return condition;
}
return NULL;
}
static int rc_validate_get_opposite_comparison(int oper)
{
switch (oper)
{
case RC_OPERATOR_EQ: return RC_OPERATOR_NE;
case RC_OPERATOR_NE: return RC_OPERATOR_EQ;
case RC_OPERATOR_LT: return RC_OPERATOR_GE;
case RC_OPERATOR_LE: return RC_OPERATOR_GT;
case RC_OPERATOR_GT: return RC_OPERATOR_LE;
case RC_OPERATOR_GE: return RC_OPERATOR_LT;
default: return oper;
}
}
static const rc_operand_t* rc_validate_get_comparison(const rc_condition_t* condition, int* comparison, unsigned* value)
{
if (rc_operand_is_memref(&condition->operand1))
{
if (condition->operand2.type != RC_OPERAND_CONST)
return NULL;
*comparison = condition->oper;
*value = condition->operand2.value.num;
return &condition->operand1;
}
if (condition->operand1.type != RC_OPERAND_CONST)
return NULL;
if (!rc_operand_is_memref(&condition->operand2))
return NULL;
*comparison = rc_validate_get_opposite_comparison(condition->oper);
*value = condition->operand1.value.num;
return &condition->operand2;
}
enum {
RC_OVERLAP_NONE = 0,
RC_OVERLAP_CONFLICTING,
RC_OVERLAP_REDUNDANT,
RC_OVERLAP_DEFER
};
static int rc_validate_comparison_overlap(int comparison1, unsigned value1, int comparison2, unsigned value2)
{
/* NOTE: this only cares if comp2 conflicts with comp1.
* If comp1 conflicts with comp2, we'll catch that later (return RC_OVERLAP_NONE for now) */
switch (comparison2)
{
case RC_OPERATOR_EQ:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a == 1 | a == 1 && a == 2 | a == 2 && a == 1 */
/* redundant conflict conflict */
return (value1 == value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
case RC_OPERATOR_LE: /* a <= 1 && a == 1 | a <= 1 && a == 2 | a <= 2 && a == 1 */
/* defer conflict defer */
return (value1 < value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
case RC_OPERATOR_GE: /* a >= 1 && a == 1 | a >= 1 && a == 2 | a >= 2 && a == 1 */
/* defer defer conflict */
return (value1 > value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
case RC_OPERATOR_NE: /* a != 1 && a == 1 | a != 1 && a == 2 | a != 2 && a == 1 */
/* conflict defer defer */
return (value1 == value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
case RC_OPERATOR_LT: /* a < 1 && a == 1 | a < 1 && a == 2 | a < 2 && a == 1 */
/* conflict conflict defer */
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
case RC_OPERATOR_GT: /* a > 1 && a == 1 | a > 1 && a == 2 | a > 2 && a == 1 */
/* conflict defer conflict */
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
}
break;
case RC_OPERATOR_NE:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a != 1 | a == 1 && a != 2 | a == 2 && a != 1 */
/* conflict redundant redundant */
return (value1 == value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_REDUNDANT;
case RC_OPERATOR_LE: /* a <= 1 && a != 1 | a <= 1 && a != 2 | a <= 2 && a != 1 */
/* none redundant none */
return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
case RC_OPERATOR_GE: /* a >= 1 && a != 1 | a >= 1 && a != 2 | a >= 2 && a != 1 */
/* none none redundant */
return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
case RC_OPERATOR_NE: /* a != 1 && a != 1 | a != 1 && a != 2 | a != 2 && a != 1 */
/* redundant none none */
return (value1 == value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
case RC_OPERATOR_LT: /* a < 1 && a != 1 | a < 1 && a != 2 | a < 2 && a != 1 */
/* redundant redundant none */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
case RC_OPERATOR_GT: /* a > 1 && a != 1 | a > 1 && a != 2 | a > 2 && a != 1 */
/* redundant none redundant */
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
}
break;
case RC_OPERATOR_LT:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a < 1 | a == 1 && a < 2 | a == 2 && a < 1 */
/* conflict redundant conflict */
return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
case RC_OPERATOR_LE: /* a <= 1 && a < 1 | a <= 1 && a < 2 | a <= 2 && a < 1 */
/* defer redundant defer */
return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_GE: /* a >= 1 && a < 1 | a >= 1 && a < 2 | a >= 2 && a < 1 */
/* conflict none conflict */
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
case RC_OPERATOR_NE: /* a != 1 && a < 1 | a != 1 && a < 2 | a != 2 && a < 1 */
/* defer none defer */
return (value1 >= value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
case RC_OPERATOR_LT: /* a < 1 && a < 1 | a < 1 && a < 2 | a < 2 && a < 1 */
/* redundant redundant defer */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_GT: /* a > 1 && a < 1 | a > 1 && a < 2 | a > 2 && a < 1 */
/* conflict none conflict */
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
}
break;
case RC_OPERATOR_LE:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a <= 1 | a == 1 && a <= 2 | a == 2 && a <= 1 */
/* redundant redundant conflict */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
case RC_OPERATOR_LE: /* a <= 1 && a <= 1 | a <= 1 && a <= 2 | a <= 2 && a <= 1 */
/* redundant redundant defer */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_GE: /* a >= 1 && a <= 1 | a >= 1 && a <= 2 | a >= 2 && a <= 1 */
/* none none conflict */
return (value1 > value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
case RC_OPERATOR_NE: /* a != 1 && a <= 1 | a != 1 && a <= 2 | a != 2 && a <= 1 */
/* none none defer */
return (value1 > value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
case RC_OPERATOR_LT: /* a < 1 && a <= 1 | a < 1 && a <= 2 | a < 2 && a <= 1 */
/* redundant redundant defer */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_GT: /* a > 1 && a <= 1 | a > 1 && a <= 2 | a > 2 && a <= 1 */
/* conflict none conflict */
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
}
break;
case RC_OPERATOR_GT:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a > 1 | a == 1 && a > 2 | a == 2 && a > 1 */
/* conflict conflict redundant */
return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
case RC_OPERATOR_LE: /* a <= 1 && a > 1 | a <= 1 && a > 2 | a <= 2 && a > 1 */
/* conflict conflict defer */
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
case RC_OPERATOR_GE: /* a >= 1 && a > 1 | a >= 1 && a > 2 | a >= 2 && a > 1 */
/* defer defer redundant */
return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_NE: /* a != 1 && a > 1 | a != 1 && a > 2 | a != 2 && a > 1 */
/* defer defer none */
return (value1 <= value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
case RC_OPERATOR_LT: /* a < 1 && a > 1 | a < 1 && a > 2 | a < 2 && a > 1 */
/* conflict conflict none */
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
case RC_OPERATOR_GT: /* a > 1 && a > 1 | a > 1 && a > 2 | a > 2 && a > 1 */
/* redundant defer redundant */
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
}
break;
case RC_OPERATOR_GE:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a >= 1 | a == 1 && a >= 2 | a == 2 && a >= 1 */
/* redundant conflict redundant */
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
case RC_OPERATOR_LE: /* a <= 1 && a >= 1 | a <= 1 && a >= 2 | a <= 2 && a >= 1 */
/* none conflict none */
return (value1 < value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
case RC_OPERATOR_GE: /* a >= 1 && a >= 1 | a >= 1 && a >= 2 | a >= 2 && a >= 1 */
/* redundant redundant defer */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_NE: /* a != 1 && a >= 1 | a != 1 && a >= 2 | a != 2 && a >= 1 */
/* none defer none */
return (value1 < value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
case RC_OPERATOR_LT: /* a < 1 && a >= 1 | a < 1 && a >= 2 | a < 2 && a >= 1 */
/* conflict conflict none */
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
case RC_OPERATOR_GT: /* a > 1 && a >= 1 | a > 1 && a >= 2 | a > 2 && a >= 1 */
/* redundant defer redundant */
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
}
break;
}
return RC_OVERLAP_NONE;
}
static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, const rc_condset_t* compare_conditions,
const char* prefix, const char* compare_prefix, char result[], const size_t result_size)
{
int comparison1, comparison2;
unsigned value1, value2;
const rc_operand_t* operand1;
const rc_operand_t* operand2;
const rc_condition_t* compare_condition;
const rc_condition_t* condition;
int overlap;
/* empty group */
if (conditions == NULL || compare_conditions == NULL)
return 1;
/* outer loop is the source conditions */
for (condition = conditions->conditions; condition != NULL;
condition = rc_validate_next_non_combining_condition(condition))
{
/* hits can be captured at any time, so any potential conflict will not be conflicting at another time */
if (condition->required_hits)
continue;
operand1 = rc_validate_get_comparison(condition, &comparison1, &value1);
if (!operand1)
continue;
switch (condition->type)
{
case RC_CONDITION_PAUSE_IF:
if (conditions != compare_conditions)
break;
/* fallthrough */
case RC_CONDITION_RESET_IF:
comparison1 = rc_validate_get_opposite_comparison(comparison1);
break;
default:
if (rc_validate_is_combining_condition(condition))
continue;
break;
}
/* inner loop is the potentially conflicting conditions */
for (compare_condition = compare_conditions->conditions; compare_condition != NULL;
compare_condition = rc_validate_next_non_combining_condition(compare_condition))
{
if (compare_condition == condition)
continue;
if (compare_condition->required_hits)
continue;
operand2 = rc_validate_get_comparison(compare_condition, &comparison2, &value2);
if (!operand2 || operand2->value.memref->address != operand1->value.memref->address ||
operand2->size != operand1->size || operand2->type != operand1->type)
continue;
switch (compare_condition->type)
{
case RC_CONDITION_PAUSE_IF:
if (conditions != compare_conditions)
break;
/* fallthrough */
case RC_CONDITION_RESET_IF:
comparison2 = rc_validate_get_opposite_comparison(comparison2);
break;
default:
if (rc_validate_is_combining_condition(compare_condition))
continue;
break;
}
overlap = rc_validate_comparison_overlap(comparison1, value1, comparison2, value2);
switch (overlap)
{
case RC_OVERLAP_CONFLICTING:
if (compare_condition->type == RC_CONDITION_PAUSE_IF || condition->type == RC_CONDITION_PAUSE_IF)
{
/* ignore PauseIf conflicts between groups, unless both conditions are PauseIfs */
if (conditions != compare_conditions && compare_condition->type != condition->type)
continue;
}
break;
case RC_OVERLAP_REDUNDANT:
if (prefix != compare_prefix && strcmp(compare_prefix, "Core") == 0)
{
/* if the alt condition is more restrictive than the core condition, ignore it */
if (rc_validate_comparison_overlap(comparison2, value2, comparison1, value1) != RC_OVERLAP_REDUNDANT)
continue;
}
if (compare_condition->type == RC_CONDITION_PAUSE_IF || condition->type == RC_CONDITION_PAUSE_IF)
{
/* ignore PauseIf redundancies between groups */
if (conditions != compare_conditions)
continue;
/* if the PauseIf is less restrictive than the other condition, it's just a guard. ignore it */
if (rc_validate_comparison_overlap(comparison2, value2, comparison1, value1) != RC_OVERLAP_REDUNDANT)
continue;
/* PauseIf redundant with ResetIf is a conflict (both are inverted comparisons) */
if (compare_condition->type == RC_CONDITION_RESET_IF || condition->type == RC_CONDITION_RESET_IF)
overlap = RC_OVERLAP_CONFLICTING;
}
else if (compare_condition->type == RC_CONDITION_RESET_IF && condition->type != RC_CONDITION_RESET_IF)
{
/* only ever report the redundancy on the non-ResetIf condition. The ResetIf is allowed to
* fire when the non-ResetIf condition is not true. */
continue;
}
else if (compare_condition->type == RC_CONDITION_MEASURED_IF || condition->type == RC_CONDITION_MEASURED_IF)
{
/* ignore MeasuredIf redundancies between groups */
if (conditions != compare_conditions)
continue;
if (compare_condition->type == RC_CONDITION_MEASURED_IF && condition->type != RC_CONDITION_MEASURED_IF)
{
/* only ever report the redundancy on the non-MeasuredIf condition. The MeasuredIf provides
* additional functionality. */
continue;
}
}
else if (compare_condition->type == RC_CONDITION_TRIGGER || condition->type == RC_CONDITION_TRIGGER)
{
/* Trigger is allowed to be redundant with non-trigger conditions as there may be limits that start a
* challenge that are furhter reduced for the completion of the challenge */
if (compare_condition->type != condition->type)
continue;
}
break;
default:
continue;
}
if (compare_prefix && *compare_prefix)
{
snprintf(result, result_size, "%s Condition %d: %s with %s Condition %d",
compare_prefix, rc_validate_get_condition_index(compare_conditions, compare_condition),
(overlap == RC_OVERLAP_REDUNDANT) ? "Redundant" : "Conflicts",
prefix, rc_validate_get_condition_index(conditions, condition));
}
else
{
snprintf(result, result_size, "Condition %d: %s with Condition %d",
rc_validate_get_condition_index(compare_conditions, compare_condition),
(overlap == RC_OVERLAP_REDUNDANT) ? "Redundant" : "Conflicts",
rc_validate_get_condition_index(conditions, condition));
}
return 0;
}
}
return 1;
}
static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result[], const size_t result_size, int console_id, unsigned max_address)
{
const rc_condset_t* alt;
int index;
if (!trigger->alternative)
{
if (!rc_validate_condset_internal(trigger->requirement, result, result_size, console_id, max_address))
return 0;
return rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "", "", result, result_size);
}
snprintf(result, result_size, "Core ");
if (!rc_validate_condset_internal(trigger->requirement, result + 5, result_size - 5, console_id, max_address))
return 0;
/* compare core to itself */
if (!rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "Core", "Core", result, result_size))
return 0;
index = 1;
for (alt = trigger->alternative; alt; alt = alt->next, ++index) {
char altname[16];
const size_t prefix_length = snprintf(result, result_size, "Alt%d ", index);
if (!rc_validate_condset_internal(alt, result + prefix_length, result_size - prefix_length, console_id, max_address))
return 0;
/* compare alt to itself */
snprintf(altname, sizeof(altname), "Alt%d", index);
if (!rc_validate_conflicting_conditions(alt, alt, altname, altname, result, result_size))
return 0;
/* compare alt to core */
if (!rc_validate_conflicting_conditions(trigger->requirement, alt, "Core", altname, result, result_size))
return 0;
/* compare core to alt */
if (!rc_validate_conflicting_conditions(alt, trigger->requirement, altname, "Core", result, result_size))
return 0;
}
*result = '\0';
return 1;
}
int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, unsigned max_address)
{
return rc_validate_trigger_internal(trigger, result, result_size, 0, max_address);
}
int rc_validate_trigger_for_console(const rc_trigger_t* trigger, char result[], const size_t result_size, int console_id)
{
const unsigned max_address = rc_console_max_address(console_id);
return rc_validate_trigger_internal(trigger, result, result_size, console_id, max_address);
}

View file

@ -0,0 +1,26 @@
#ifndef RC_VALIDATE_H
#define RC_VALIDATE_H
#include "rc_runtime_types.h"
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, unsigned max_address);
int rc_validate_condset(const rc_condset_t* condset, char result[], const size_t result_size, unsigned max_address);
int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, unsigned max_address);
int rc_validate_memrefs_for_console(const rc_memref_t* memref, char result[], const size_t result_size, int console_id);
int rc_validate_condset_for_console(const rc_condset_t* condset, char result[], const size_t result_size, int console_id);
int rc_validate_trigger_for_console(const rc_trigger_t* trigger, char result[], const size_t result_size, int console_id);
#ifdef __cplusplus
}
#endif
#endif /* RC_VALIDATE_H */

View file

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

View file

@ -280,7 +280,6 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo
{
rc_richpresence_lookup_item_t** items;
rc_scratch_buffer_t* buffer;
const int alignment = sizeof(rc_richpresence_lookup_item_t*);
int index;
int size;
@ -293,7 +292,7 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo
size = count * sizeof(rc_richpresence_lookup_item_t*);
buffer = &parse->scratch.buffer;
do {
const int aligned_offset = (buffer->offset + alignment - 1) & ~(alignment - 1);
const int aligned_offset = RC_ALIGN(buffer->offset);
const int remaining = sizeof(buffer->buffer) - aligned_offset;
if (remaining >= size) {
@ -534,10 +533,11 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
display = nextline;
display_line = parse->lines_read;
/* scan as long as we find conditional lines or full line comments */
do {
line = nextline;
nextline = rc_parse_line(line, &endline, parse);
} while (*line == '?');
} while (*line == '?' || (line[0] == '/' && line[1] == '/'));
}
line = nextline;
@ -557,38 +557,48 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
nextline = rc_parse_line(line, &endline, parse);
while (*line == '?') {
/* conditional display: ?trigger?string */
ptr = ++line;
while (ptr < endline && *ptr != '?')
++ptr;
do {
if (line[0] == '?') {
/* conditional display: ?trigger?string */
ptr = ++line;
while (ptr < endline && *ptr != '?')
++ptr;
if (ptr < endline) {
*nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup);
if (parse->offset < 0)
return;
trigger = &((*nextdisplay)->trigger);
rc_parse_trigger_internal(trigger, &line, parse);
trigger->memrefs = 0;
if (parse->offset < 0)
return;
if (parse->buffer)
nextdisplay = &((*nextdisplay)->next);
if (ptr < endline) {
*nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup);
if (parse->offset < 0)
return;
trigger = &((*nextdisplay)->trigger);
rc_parse_trigger_internal(trigger, &line, parse);
trigger->memrefs = 0;
if (parse->offset < 0)
return;
if (parse->buffer)
nextdisplay = &((*nextdisplay)->next);
}
}
else if (line[0] != '/' || line[1] != '/') {
break;
}
line = nextline;
nextline = rc_parse_line(line, &endline, parse);
}
} while (1);
/* non-conditional display: string */
*nextdisplay = rc_parse_richpresence_display_internal(line, endline, parse, firstlookup);
if (*nextdisplay) {
hasdisplay = 1;
nextdisplay = &((*nextdisplay)->next);
}
/* restore the parser state */
parse->lines_read = lines_read;
/* restore the parser state */
parse->lines_read = lines_read;
}
else {
/* this should only happen if the line is blank.
* expect parse->offset to be RC_MISSING_DISPLAY_STRING and leave parse->lines_read
* on the current line for error tracking. */
}
}
/* finalize */

View file

@ -9,6 +9,17 @@
#define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256
rc_runtime_t* rc_runtime_alloc(void) {
rc_runtime_t* self = malloc(sizeof(rc_runtime_t));
if (self) {
rc_runtime_init(self);
self->owns_self = 1;
}
return self;
}
void rc_runtime_init(rc_runtime_t* self) {
memset(self, 0, sizeof(rc_runtime_t));
self->next_memref = &self->memrefs;
@ -48,9 +59,13 @@ void rc_runtime_destroy(rc_runtime_t* self) {
self->next_memref = 0;
self->memrefs = 0;
if (self->owns_self) {
free(self);
}
}
static void rc_runtime_checksum(const char* memaddr, unsigned char* md5) {
void rc_runtime_checksum(const char* memaddr, unsigned char* md5) {
md5_state_t state;
md5_init(&state);
md5_append(&state, (unsigned char*)memaddr, (int)strlen(memaddr));
@ -231,7 +246,7 @@ int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id
}
if (rc_trigger_state_active(trigger->state)) {
*measured_value = trigger->measured_value;
*measured_value = (trigger->measured_value == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value;
*measured_target = trigger->measured_target;
}
else {
@ -257,7 +272,7 @@ int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, unsigned
}
/* cap the value at the target so we can count past the target: "107 >= 100" */
value = trigger->measured_value;
value = (trigger->measured_value == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value;
if (value > trigger->measured_target)
value = trigger->measured_target;
@ -534,6 +549,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
for (i = self->trigger_count - 1; i >= 0; --i) {
rc_trigger_t* trigger = self->triggers[i].trigger;
int old_state, new_state;
unsigned old_measured_value;
if (!trigger)
continue;
@ -552,13 +568,13 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
continue;
}
old_measured_value = trigger->measured_value;
old_state = trigger->state;
new_state = rc_evaluate_trigger(trigger, peek, ud, L);
/* the trigger state doesn't actually change to RESET, RESET just serves as a notification.
/* trigger->state doesn't actually change to RESET, RESET just serves as a notification.
* handle the notification, then look at the actual state */
if (new_state == RC_TRIGGER_STATE_RESET)
{
if (new_state == RC_TRIGGER_STATE_RESET) {
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET;
runtime_event.id = self->triggers[i].id;
event_handler(&runtime_event);
@ -566,6 +582,32 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
new_state = trigger->state;
}
/* if the measured value changed and the achievement hasn't triggered, send a notification */
if (trigger->measured_value != old_measured_value && old_measured_value != RC_MEASURED_UNKNOWN &&
trigger->measured_target != 0 && trigger->measured_value <= trigger->measured_target &&
new_state != RC_TRIGGER_STATE_TRIGGERED &&
new_state != RC_TRIGGER_STATE_INACTIVE && new_state != RC_TRIGGER_STATE_WAITING) {
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED;
runtime_event.id = self->triggers[i].id;
if (trigger->measured_as_percent) {
/* if reporting measured value as a percentage, only send the notification if the percentage changes */
unsigned old_percent = (unsigned)(((unsigned long long)old_measured_value * 100) / trigger->measured_target);
unsigned new_percent = (unsigned)(((unsigned long long)trigger->measured_value * 100) / trigger->measured_target);
if (old_percent != new_percent) {
runtime_event.value = new_percent;
event_handler(&runtime_event);
}
}
else {
runtime_event.value = trigger->measured_value;
event_handler(&runtime_event);
}
runtime_event.value = 0; /* achievement loop expects this to stay at 0 */
}
/* if the state hasn't changed, there won't be any events raised */
if (new_state == old_state)
continue;
@ -685,13 +727,8 @@ void rc_runtime_reset(rc_runtime_t* self) {
rc_reset_lboard(self->lboards[i].lboard);
}
if (self->richpresence && self->richpresence->richpresence) {
rc_richpresence_display_t* display = self->richpresence->richpresence->first_display;
while (display != 0) {
rc_reset_trigger(&display->trigger);
display = display->next;
}
}
if (self->richpresence && self->richpresence->richpresence)
rc_reset_richpresence(self->richpresence->richpresence);
for (variable = self->variables; variable; variable = variable->next)
rc_reset_value(variable);
@ -712,7 +749,7 @@ static int rc_condset_contains_memref(const rc_condset_t* condset, const rc_memr
return 0;
}
static int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref) {
int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref) {
rc_condset_t* condset;
if (!value)
return 0;
@ -725,7 +762,7 @@ static int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t*
return 0;
}
static int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref) {
int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref) {
rc_condset_t* condset;
if (!trigger)
return 0;

View file

@ -17,7 +17,7 @@
#define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */
typedef struct rc_runtime_progress_t {
rc_runtime_t* runtime;
const rc_runtime_t* runtime;
int offset;
unsigned char* buffer;
@ -81,17 +81,6 @@ static int rc_runtime_progress_match_md5(rc_runtime_progress_t* progress, unsign
return result;
}
static unsigned rc_runtime_progress_djb2(const char* input)
{
unsigned result = 5381;
char c;
while ((c = *input++) != '\0')
result = ((result << 5) + result) + c; /* result = result * 33 + c */
return result;
}
static void rc_runtime_progress_start_chunk(rc_runtime_progress_t* progress, unsigned chunk_id)
{
rc_runtime_progress_write_uint(progress, chunk_id);
@ -120,7 +109,7 @@ static void rc_runtime_progress_end_chunk(rc_runtime_progress_t* progress)
}
}
static void rc_runtime_progress_init(rc_runtime_progress_t* progress, rc_runtime_t* runtime, lua_State* L)
static void rc_runtime_progress_init(rc_runtime_progress_t* progress, const rc_runtime_t* runtime, lua_State* L)
{
memset(progress, 0, sizeof(rc_runtime_progress_t));
progress->runtime = runtime;
@ -352,7 +341,7 @@ static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress)
for (variable = progress->runtime->variables; variable; variable = variable->next)
{
unsigned djb2 = rc_runtime_progress_djb2(variable->name);
unsigned djb2 = rc_djb2(variable->name);
rc_runtime_progress_write_uint(progress, djb2);
rc_runtime_progress_write_variable(progress, variable);
@ -418,7 +407,7 @@ static int rc_runtime_progress_read_variables(rc_runtime_progress_t* progress)
count = 0;
for (variable = progress->runtime->variables; variable; variable = variable->next) {
pending_variables[count].variable = variable;
pending_variables[count].djb2 = rc_runtime_progress_djb2(variable->name);
pending_variables[count].djb2 = rc_djb2(variable->name);
++count;
}
@ -751,7 +740,7 @@ int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
rc_runtime_progress_t progress;
int result;
rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L);
rc_runtime_progress_init(&progress, runtime, L);
result = rc_runtime_progress_serialize_internal(&progress);
if (result != RC_OK)
@ -764,7 +753,10 @@ int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua
{
rc_runtime_progress_t progress;
rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L);
if (!buffer)
return RC_INVALID_STATE;
rc_runtime_progress_init(&progress, runtime, L);
progress.buffer = (unsigned char*)buffer;
return rc_runtime_progress_serialize_internal(&progress);
@ -782,6 +774,11 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char*
int seen_rich_presence = 0;
int result = RC_OK;
if (!serialized) {
rc_runtime_reset(runtime);
return RC_INVALID_STATE;
}
rc_runtime_progress_init(&progress, runtime, L);
progress.buffer = (unsigned char*)serialized;

View file

@ -42,8 +42,8 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars
*next = 0;
*memaddr = aux;
self->measured_value = 0;
self->measured_target = parse->measured_target;
self->measured_value = parse->measured_target ? RC_MEASURED_UNKNOWN : 0;
self->measured_as_percent = parse->measured_as_percent;
self->state = RC_TRIGGER_STATE_WAITING;
self->has_hits = 0;
@ -197,14 +197,6 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
self->measured_value = eval_state.measured_value.value.u32;
}
/* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */
/* otherwise, if the state is WAITING, proceed to activating the trigger */
if (self->state == RC_TRIGGER_STATE_WAITING && ret) {
rc_reset_trigger(self);
self->has_hits = 0;
return RC_TRIGGER_STATE_WAITING;
}
/* if any ResetIf condition was true, reset the hit counts */
if (eval_state.was_reset) {
/* if the measured value came from a hit count, reset it. do this before calling
@ -247,6 +239,13 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
is_primed = 0;
}
else if (ret) {
/* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */
if (self->state == RC_TRIGGER_STATE_WAITING) {
rc_reset_trigger(self);
self->has_hits = 0;
return RC_TRIGGER_STATE_WAITING;
}
/* trigger was triggered */
self->state = RC_TRIGGER_STATE_TRIGGERED;
return RC_TRIGGER_STATE_TRIGGERED;
@ -284,6 +283,9 @@ void rc_reset_trigger(rc_trigger_t* self) {
rc_reset_trigger_hitcounts(self);
self->state = RC_TRIGGER_STATE_WAITING;
self->measured_value = 0;
if (self->measured_target)
self->measured_value = RC_MEASURED_UNKNOWN;
self->has_hits = 0;
}

View file

@ -111,6 +111,7 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat
case RC_OPERATOR_MULT:
case RC_OPERATOR_DIV:
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_NONE:
break;
@ -284,6 +285,20 @@ void rc_reset_value(rc_value_t* self) {
self->value.changed = 0;
}
int rc_value_from_hits(rc_value_t* self)
{
rc_condset_t* condset = self->conditions;
for (; condset != NULL; condset = condset->next) {
rc_condition_t* condition = condset->conditions;
for (; condition != NULL; condition = condition->next) {
if (condition->type == RC_CONDITION_MEASURED)
return (condition->required_hits != 0);
}
}
return 0;
}
void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables) {
parse->variables = variables;
*variables = 0;
@ -427,6 +442,26 @@ static rc_typed_value_t* rc_typed_value_convert_into(rc_typed_value_t* dest, con
return dest;
}
void rc_typed_value_negate(rc_typed_value_t* value) {
switch (value->type)
{
case RC_VALUE_TYPE_UNSIGNED:
rc_typed_value_convert(value, RC_VALUE_TYPE_SIGNED);
/* fallthrough to RC_VALUE_TYPE_SIGNED */
case RC_VALUE_TYPE_SIGNED:
value->value.i32 = -(value->value.i32);
break;
case RC_VALUE_TYPE_FLOAT:
value->value.f32 = -(value->value.f32);
break;
default:
break;
}
}
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount) {
rc_typed_value_t converted;

View file

@ -22,6 +22,7 @@ struct cdrom_t
void* file_handle; /* the file handle for reading the track data */
int sector_size; /* the size of each sector in the track data */
int sector_header_size; /* the offset to the raw data within a sector block */
int raw_data_size; /* the amount of raw data within a sector block */
int64_t file_track_offset;/* the offset of the track data within the file */
int track_first_sector; /* the first absolute sector associated to the track (includes pregap) */
int track_pregap_sectors; /* the number of pregap sectors */
@ -58,6 +59,7 @@ static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
cdrom->sector_size = 0;
cdrom->sector_header_size = 0;
cdrom->raw_data_size = 2048;
rc_file_seek(cdrom->file_handle, toc_sector * 2352 + cdrom->file_track_offset, SEEK_SET);
if (rc_file_read(cdrom->file_handle, header, sizeof(header)) < sizeof(header))
@ -122,6 +124,8 @@ static void* cdreader_open_bin_track(const char* path, uint32_t track)
return NULL;
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
if (!cdrom)
return NULL;
cdrom->file_handle = file_handle;
#ifndef NDEBUG
cdrom->track_id = track;
@ -219,6 +223,7 @@ static int cdreader_open_bin(struct cdrom_t* cdrom, const char* path, const char
{
cdrom->sector_size = 2352;
cdrom->sector_header_size = 0;
cdrom->raw_data_size = 2352; /* no header or footer data on audio tracks */
}
}
@ -282,6 +287,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
char* bin_filename = NULL;
char *ptr, *ptr2, *end;
int done = 0;
int session = 1;
size_t num_read = 0;
struct cdrom_t* cdrom = NULL;
@ -339,7 +345,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
++ptr;
/* convert mm:ss:ff to sector count */
sscanf(ptr, "%d:%d:%d", &m, &s, &f);
sscanf_s(ptr, "%d:%d:%d", &m, &s, &f);
sector_offset = ((m * 60) + s) * 75 + f;
if (current_track.first_sector == -1)
@ -389,6 +395,13 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
done = 1;
break;
}
if (track == RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION && session == 2)
{
track = current_track.id;
done = 1;
break;
}
}
}
else if (strncasecmp(ptr, "TRACK ", 6) == 0)
@ -465,6 +478,20 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
if (ptr2 - ptr < (int)sizeof(current_track.filename))
memcpy(current_track.filename, ptr, ptr2 - ptr);
}
else if (strncasecmp(ptr, "REM ", 4) == 0)
{
ptr += 4;
while (*ptr == ' ')
++ptr;
if (strncasecmp(ptr, "SESSION ", 8) == 0)
{
ptr += 8;
while (*ptr == ' ')
++ptr;
session = atoi(ptr);
}
}
while (*ptr && *ptr != '\n')
++ptr;
@ -694,8 +721,8 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
largest_track_size = track_size;
largest_track = current_track;
largest_track_lba = lba;
strcpy(largest_track_file, file);
strcpy(largest_track_sector_size, sector_size);
strcpy_s(largest_track_file, sizeof(largest_track_file), file);
strcpy_s(largest_track_sector_size, sizeof(largest_track_sector_size), sector_size);
}
}
}
@ -723,8 +750,8 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
if (largest_track != 0 && largest_track != current_track)
{
current_track = largest_track;
strcpy(file, largest_track_file);
strcpy(sector_size, largest_track_sector_size);
strcpy_s(file, sizeof(file), largest_track_file);
strcpy_s(sector_size, sizeof(sector_size), largest_track_sector_size);
lba = largest_track_lba;
}
@ -794,18 +821,18 @@ static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* bu
sector_start = (int64_t)(sector - cdrom->track_first_sector) * cdrom->sector_size +
cdrom->sector_header_size + cdrom->file_track_offset;
while (requested_bytes > 2048)
while (requested_bytes > (size_t)cdrom->raw_data_size)
{
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, 2048);
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, cdrom->raw_data_size);
total_read += num_read;
if (num_read < 2048)
if (num_read < (size_t)cdrom->raw_data_size)
return total_read;
buffer_ptr += 2048;
buffer_ptr += cdrom->raw_data_size;
sector_start += cdrom->sector_size;
requested_bytes -= 2048;
requested_bytes -= cdrom->raw_data_size;
}
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
@ -844,7 +871,7 @@ void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader)
cdreader->first_track_sector = cdreader_first_track_sector;
}
void rc_hash_init_default_cdreader()
void rc_hash_init_default_cdreader(void)
{
struct rc_hash_cdreader cdreader;
rc_hash_get_default_cdreader(&cdreader);

View file

@ -48,7 +48,13 @@ static struct rc_hash_filereader* filereader = NULL;
static void* filereader_open(const char* path)
{
#if defined(__STDC_WANT_SECURE_LIB__)
FILE* fp;
fopen_s(&fp, path, "rb");
return fp;
#else
return fopen(path, "rb");
#endif
}
static void filereader_seek(void* file_handle, int64_t offset, int origin)
@ -223,12 +229,17 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
{
uint8_t buffer[2048], *tmp;
int sector;
unsigned num_sectors = 0;
size_t filename_length;
const char* slash;
if (!track_handle)
return 0;
/* we start at the root. don't need to explicitly find it */
if (*path == '\\')
++path;
filename_length = strlen(path);
slash = strrchr(path, '\\');
if (slash)
@ -247,6 +258,8 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
}
else
{
unsigned logical_block_size;
/* find the cd information */
if (!rc_cd_read_sector(track_handle, rc_cd_first_track_sector(track_handle) + 16, buffer, 256))
return 0;
@ -255,6 +268,15 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
* https://www.cdroller.com/htm/readdata.html
*/
sector = buffer[156 + 2] | (buffer[156 + 3] << 8) | (buffer[156 + 4] << 16);
/* if the table of contents spans more than one sector, it's length of section will exceed it's logical block size */
logical_block_size = (buffer[128] | (buffer[128 + 1] << 8)); /* logical block size */
if (logical_block_size == 0) {
num_sectors = 1;
} else {
num_sectors = (buffer[156 + 10] | (buffer[156 + 11] << 8) | (buffer[156 + 12] << 16) | (buffer[156 + 13] << 24)); /* length of section */
num_sectors /= logical_block_size;
}
}
/* fetch and process the directory record */
@ -262,21 +284,34 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
return 0;
tmp = buffer;
while (tmp < buffer + sizeof(buffer))
do
{
if (!*tmp)
return 0;
if (tmp >= buffer + sizeof(buffer) || !*tmp)
{
/* end of this path table block. if the path table spans multiple sectors, keep scanning */
if (num_sectors > 1)
{
--num_sectors;
if (rc_cd_read_sector(track_handle, ++sector, buffer, sizeof(buffer)))
{
tmp = buffer;
continue;
}
}
break;
}
/* filename is 33 bytes into the record and the format is "FILENAME;version" or "DIRECTORY" */
if ((tmp[33 + filename_length] == ';' || tmp[33 + filename_length] == '\0') &&
if ((tmp[32] == filename_length || tmp[33 + filename_length] == ';') &&
strncasecmp((const char*)(tmp + 33), path, filename_length) == 0)
{
sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16);
if (verbose_message_callback)
{
snprintf((char*)buffer, sizeof(buffer), "Found %s at sector %d", path, sector);
verbose_message_callback((const char*)buffer);
char message[128];
snprintf(message, sizeof(message), "Found %s at sector %d", path, sector);
verbose_message_callback(message);
}
if (size)
@ -287,7 +322,7 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns
/* the first byte of the record is the length of the record */
tmp += *tmp;
}
} while (1);
return 0;
}
@ -347,6 +382,34 @@ int rc_path_compare_extension(const char* path, const char* ext)
/* ===================================================== */
static void rc_hash_byteswap16(uint8_t* buffer, const uint8_t* stop)
{
uint32_t* ptr = (uint32_t*)buffer;
const uint32_t* stop32 = (const uint32_t*)stop;
while (ptr < stop32)
{
uint32_t temp = *ptr;
temp = (temp & 0xFF00FF00) >> 8 |
(temp & 0x00FF00FF) << 8;
*ptr++ = temp;
}
}
static void rc_hash_byteswap32(uint8_t* buffer, const uint8_t* stop)
{
uint32_t* ptr = (uint32_t*)buffer;
const uint32_t* stop32 = (const uint32_t*)stop;
while (ptr < stop32)
{
uint32_t temp = *ptr;
temp = (temp & 0xFF000000) >> 24 |
(temp & 0x00FF0000) >> 8 |
(temp & 0x0000FF00) << 8 |
(temp & 0x000000FF) << 24;
*ptr++ = temp;
}
}
static int rc_hash_finalize(md5_state_t* md5, char hash[33])
{
md5_byte_t digest[16];
@ -415,6 +478,9 @@ static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector
verbose_message_callback(message);
}
if (size < (unsigned)num_read)
size = (unsigned)num_read;
do
{
md5_append(md5, buffer, (int)num_read);
@ -684,6 +750,142 @@ static int rc_hash_text(char hash[33], const uint8_t* buffer, size_t buffer_size
return rc_hash_finalize(&md5, hash);
}
/* helper variable only used for testing */
const char* _rc_hash_jaguar_cd_homebrew_hash = NULL;
static int rc_hash_jaguar_cd(char hash[33], const char* path)
{
uint8_t buffer[2352];
char message[128];
void* track_handle;
md5_state_t md5;
int byteswapped = 0;
unsigned size = 0;
unsigned offset = 0;
unsigned sector = 0;
unsigned remaining;
unsigned i;
/* Jaguar CD header is in the first sector of the first data track OF THE SECOND SESSION.
* The first track must be an audio track, but may be a warning message or actual game audio */
track_handle = rc_cd_open_track(path, RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION);
if (!track_handle)
return rc_hash_error("Could not open track");
/* The header is an unspecified distance into the first sector, but usually two bytes in.
* It consists of 64 bytes of "TAIR" or "ATRI" repeating, depending on whether or not the data
* is byteswapped. Then another 32 byte that reads "ATARI APPROVED DATA HEADER ATRI "
* (possibly byteswapped). Then a big-endian 32-bit value for the address where the boot code
* should be loaded, and a second big-endian 32-bit value for the size of the boot code. */
sector = rc_cd_first_track_sector(track_handle);
rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer));
for (i = 64; i < sizeof(buffer) - 32 - 4 * 3; i++)
{
if (memcmp(&buffer[i], "TARA IPARPVODED TA AEHDAREA RT I", 32) == 0)
{
byteswapped = 1;
offset = i + 32 + 4;
size = (buffer[offset] << 16) | (buffer[offset + 1] << 24) | (buffer[offset + 2]) | (buffer[offset + 3] << 8);
break;
}
else if (memcmp(&buffer[i], "ATARI APPROVED DATA HEADER ATRI ", 32) == 0)
{
byteswapped = 0;
offset = i + 32 + 4;
size = (buffer[offset] << 24) | (buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | (buffer[offset + 3]);
break;
}
}
if (size == 0) /* did not see ATARI APPROVED DATA HEADER */
{
rc_cd_close_track(track_handle);
return rc_hash_error("Not a Jaguar CD");
}
i = 0; /* only loop once */
do
{
md5_init(&md5);
offset += 4;
if (verbose_message_callback)
{
snprintf(message, sizeof(message), "Hashing boot executable (%u bytes starting at %u bytes into sector %u)", size, offset, sector);
rc_hash_verbose(message);
}
if (size > MAX_BUFFER_SIZE)
size = MAX_BUFFER_SIZE;
do
{
if (byteswapped)
rc_hash_byteswap16(buffer, &buffer[sizeof(buffer)]);
remaining = sizeof(buffer) - offset;
if (remaining >= size)
{
md5_append(&md5, &buffer[offset], size);
size = 0;
break;
}
md5_append(&md5, &buffer[offset], remaining);
size -= remaining;
offset = 0;
} while (rc_cd_read_sector(track_handle, ++sector, buffer, sizeof(buffer)) == sizeof(buffer));
rc_cd_close_track(track_handle);
if (size > 0)
return rc_hash_error("Not enough data");
rc_hash_finalize(&md5, hash);
/* homebrew games all seem to have the same boot executable and store the actual game code in track 2.
* if we generated something other than the homebrew hash, return it. assume all homebrews are byteswapped. */
if (strcmp(hash, "254487b59ab21bc005338e85cbf9fd2f") != 0 || !byteswapped) {
if (_rc_hash_jaguar_cd_homebrew_hash == NULL || strcmp(hash, _rc_hash_jaguar_cd_homebrew_hash) != 0)
return 1;
}
/* if we've already been through the loop a second time, just return the hash */
if (i == 1)
return 1;
++i;
if (verbose_message_callback)
{
snprintf(message, sizeof(message), "Potential homebrew at sector %u, checking for KART data in track 2", sector);
rc_hash_verbose(message);
}
track_handle = rc_cd_open_track(path, 2);
if (!track_handle)
return rc_hash_error("Could not open track");
/* track 2 of the homebrew code has the 64 bytes or ATRI followed by 32 bytes of "ATARI APPROVED DATA HEADER ATRI!",
* then 64 bytes of KART repeating. */
sector = rc_cd_first_track_sector(track_handle);
rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer));
if (memcmp(&buffer[0x5E], "RT!IRTKA", 8) != 0)
return rc_hash_error("Homebrew executable not found in track 2");
/* found KART data*/
if (verbose_message_callback)
{
snprintf(message, sizeof(message), "Found KART data in track 2");
rc_hash_verbose(message);
}
offset = 0xA6;
size = (buffer[offset] << 16) | (buffer[offset + 1] << 24) | (buffer[offset + 2]) | (buffer[offset + 3] << 8);
} while (1);
}
static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size)
{
/* if the file contains a header, ignore it */
@ -698,6 +900,70 @@ static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size
return rc_hash_buffer(hash, buffer, buffer_size);
}
static int rc_hash_neogeo_cd(char hash[33], const char* path)
{
char buffer[1024], *ptr;
void* track_handle;
uint32_t sector;
unsigned size;
md5_state_t md5;
track_handle = rc_cd_open_track(path, 1);
if (!track_handle)
return rc_hash_error("Could not open track");
/* https://wiki.neogeodev.org/index.php?title=IPL_file, https://wiki.neogeodev.org/index.php?title=PRG_file
* IPL file specifies data to be loaded before the game starts. PRG files are the executable code
*/
sector = rc_cd_find_file_sector(track_handle, "IPL.TXT", &size);
if (!sector)
{
rc_cd_close_track(track_handle);
return rc_hash_error("Not a NeoGeo CD game disc");
}
if (rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)) == 0)
{
rc_cd_close_track(track_handle);
return 0;
}
md5_init(&md5);
buffer[sizeof(buffer) - 1] = '\0';
ptr = &buffer[0];
do
{
char* start = ptr;
while (*ptr && *ptr != '.')
++ptr;
if (strncasecmp(ptr, ".PRG", 4) == 0)
{
ptr += 4;
*ptr++ = '\0';
sector = rc_cd_find_file_sector(track_handle, start, &size);
if (!sector || !rc_hash_cd_file(&md5, track_handle, sector, NULL, size, start))
{
char error[128];
rc_cd_close_track(track_handle);
snprintf(error, sizeof(error), "Could not read %.16s", start);
return rc_hash_error(error);
}
}
while (*ptr && *ptr != '\n')
++ptr;
if (*ptr != '\n')
break;
++ptr;
} while (*ptr != '\0' && *ptr != '\x1a');
rc_cd_close_track(track_handle);
return rc_hash_finalize(&md5, hash);
}
static int rc_hash_nes(char hash[33], const uint8_t* buffer, size_t buffer_size)
{
/* if the file contains a header, ignore it */
@ -719,34 +985,6 @@ static int rc_hash_nes(char hash[33], const uint8_t* buffer, size_t buffer_size)
return rc_hash_buffer(hash, buffer, buffer_size);
}
static void rc_hash_v64_to_z64(uint8_t* buffer, const uint8_t* stop)
{
uint32_t* ptr = (uint32_t*)buffer;
const uint32_t* stop32 = (const uint32_t*)stop;
while (ptr < stop32)
{
uint32_t temp = *ptr;
temp = (temp & 0xFF00FF00) >> 8 |
(temp & 0x00FF00FF) << 8;
*ptr++ = temp;
}
}
static void rc_hash_n64_to_z64(uint8_t* buffer, const uint8_t* stop)
{
uint32_t* ptr = (uint32_t*)buffer;
const uint32_t* stop32 = (const uint32_t*)stop;
while (ptr < stop32)
{
uint32_t temp = *ptr;
temp = (temp & 0xFF000000) >> 24 |
(temp & 0x00FF0000) >> 8 |
(temp & 0x0000FF00) << 8 |
(temp & 0x000000FF) << 24;
*ptr++ = temp;
}
}
static int rc_hash_n64(char hash[33], const char* path)
{
uint8_t* buffer;
@ -787,6 +1025,9 @@ static int rc_hash_n64(char hash[33], const char* path)
rc_hash_verbose("converting n64 to z64");
is_n64 = 1;
}
else if (buffer[0] == 0xE8 || buffer[0] == 0x22) /* ndd format (don't byteswap) */
{
}
else
{
free(buffer);
@ -818,9 +1059,9 @@ static int rc_hash_n64(char hash[33], const char* path)
rc_file_read(file_handle, buffer, (int)buffer_size);
if (is_v64)
rc_hash_v64_to_z64(buffer, stop);
rc_hash_byteswap16(buffer, stop);
else if (is_n64)
rc_hash_n64_to_z64(buffer, stop);
rc_hash_byteswap32(buffer, stop);
md5_append(&md5, buffer, (int)buffer_size);
remaining -= buffer_size;
@ -832,9 +1073,9 @@ static int rc_hash_n64(char hash[33], const char* path)
stop = buffer + remaining;
if (is_v64)
rc_hash_v64_to_z64(buffer, stop);
rc_hash_byteswap16(buffer, stop);
else if (is_n64)
rc_hash_n64_to_z64(buffer, stop);
rc_hash_byteswap32(buffer, stop);
md5_append(&md5, buffer, (int)remaining);
}
@ -957,6 +1198,121 @@ static int rc_hash_nintendo_ds(char hash[33], const char* path)
return rc_hash_finalize(&md5, hash);
}
static int rc_hash_gamecube(char hash[33], const char* path)
{
md5_state_t md5;
void* file_handle;
const uint32_t BASE_HEADER_SIZE = 0x2440;
const uint32_t MAX_HEADER_SIZE = 1024 * 1024;
uint32_t apploader_header_size, apploader_body_size, apploader_trailer_size, header_size;
uint8_t quad_buffer[4];
uint8_t addr_buffer[0xD8];
uint8_t* buffer;
uint32_t dol_offset;
uint32_t dol_offsets[18];
uint32_t dol_sizes[18];
uint32_t dol_buf_size = 0;
uint32_t ix;
file_handle = rc_file_open(path);
if (!file_handle)
return rc_hash_error("Could not open file");
/* Verify Gamecube */
rc_file_seek(file_handle, 0x1c, SEEK_SET);
rc_file_read(file_handle, quad_buffer, 4);
if (quad_buffer[0] != 0xC2|| quad_buffer[1] != 0x33 || quad_buffer[2] != 0x9F || quad_buffer[3] != 0x3D)
{
rc_file_close(file_handle);
return rc_hash_error("Not a Gamecube disc");
}
/* GetApploaderSize */
rc_file_seek(file_handle, BASE_HEADER_SIZE + 0x14, SEEK_SET);
apploader_header_size = 0x20;
rc_file_read(file_handle, quad_buffer, 4);
apploader_body_size =
(quad_buffer[0] << 24) | (quad_buffer[1] << 16) | (quad_buffer[2] << 8) | quad_buffer[3];
rc_file_read(file_handle, quad_buffer, 4);
apploader_trailer_size =
(quad_buffer[0] << 24) | (quad_buffer[1] << 16) | (quad_buffer[2] << 8) | quad_buffer[3];
header_size = BASE_HEADER_SIZE + apploader_header_size + apploader_body_size + apploader_trailer_size;
if (header_size > MAX_HEADER_SIZE) header_size = MAX_HEADER_SIZE;
/* Hash headers */
buffer = (uint8_t*)malloc(header_size);
if (!buffer)
{
rc_file_close(file_handle);
return rc_hash_error("Could not allocate temporary buffer");
}
rc_file_seek(file_handle, 0, SEEK_SET);
rc_file_read(file_handle, buffer, header_size);
md5_init(&md5);
if (verbose_message_callback)
{
char message[128];
snprintf(message, sizeof(message), "Hashing %u byte header", header_size);
verbose_message_callback(message);
}
md5_append(&md5, buffer, header_size);
/* GetBootDOLOffset
* Base header size is guaranteed larger than 0x423 therefore buffer contains dol_offset right now
*/
dol_offset = (buffer[0x420] << 24) | (buffer[0x421] << 16) | (buffer[0x422] << 8) | buffer[0x423];
free(buffer);
/* Find offsetsand sizes for the 7 main.dol code segments and 11 main.dol data segments */
rc_file_seek(file_handle, dol_offset, SEEK_SET);
rc_file_read(file_handle, addr_buffer, 0xD8);
for (ix = 0; ix < 18; ix++)
{
dol_offsets[ix] =
(addr_buffer[0x0 + ix * 4] << 24) |
(addr_buffer[0x1 + ix * 4] << 16) |
(addr_buffer[0x2 + ix * 4] << 8) |
addr_buffer[0x3 + ix * 4];
dol_sizes[ix] =
(addr_buffer[0x90 + ix * 4] << 24) |
(addr_buffer[0x91 + ix * 4] << 16) |
(addr_buffer[0x92 + ix * 4] << 8) |
addr_buffer[0x93 + ix * 4];
dol_buf_size = (dol_sizes[ix] > dol_buf_size) ? dol_sizes[ix] : dol_buf_size;
}
/* Iterate through the 18 main.dol segments and hash each */
buffer = (uint8_t*)malloc(dol_buf_size);
if (!buffer)
{
rc_file_close(file_handle);
return rc_hash_error("Could not allocate temporary buffer");
}
for (ix = 0; ix < 18; ix++)
{
if (dol_sizes[ix] == 0)
continue;
rc_file_seek(file_handle, dol_offsets[ix], SEEK_SET);
rc_file_read(file_handle, buffer, dol_sizes[ix]);
if (verbose_message_callback)
{
char message[128];
if (ix < 7)
snprintf(message, sizeof(message), "Hashing %u byte main.dol code segment %u", dol_sizes[ix], ix);
else
snprintf(message, sizeof(message), "Hashing %u byte main.dol data segment %u", dol_sizes[ix], ix - 7);
verbose_message_callback(message);
}
md5_append(&md5, buffer, dol_sizes[ix]);
}
/* Finalize */
rc_file_close(file_handle);
free(buffer);
return rc_hash_finalize(&md5, hash);
}
static int rc_hash_pce(char hash[33], const uint8_t* buffer, size_t buffer_size)
{
/* if the file contains a header, ignore it (expect ROM data to be multiple of 128KB) */
@ -1290,7 +1646,7 @@ static int rc_hash_find_playstation_executable(void* track_handle, const char* b
if (strncmp(ptr, cdrom_prefix, cdrom_prefix_len) == 0)
ptr += cdrom_prefix_len;
if (*ptr == '\\')
while (*ptr == '\\')
++ptr;
start = ptr;
@ -1454,18 +1810,30 @@ static int rc_hash_psp(char hash[33], const char* path)
*/
sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\PARAM.SFO", &size);
if (!sector)
{
rc_cd_close_track(track_handle);
return rc_hash_error("Not a PSP game disc");
}
md5_init(&md5);
if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\PARAM.SFO"))
{
rc_cd_close_track(track_handle);
return 0;
}
sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\SYSDIR\\EBOOT.BIN", &size);
if (!sector)
{
rc_cd_close_track(track_handle);
return rc_hash_error("Could not find primary executable");
}
if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\SYSDIR\\EBOOT.BIN"))
{
rc_cd_close_track(track_handle);
return 0;
}
rc_cd_close_track(track_handle);
return rc_hash_finalize(&md5, hash);
@ -1527,7 +1895,11 @@ static struct rc_buffered_file rc_buffered_file;
static void* rc_file_open_buffered_file(const char* path)
{
struct rc_buffered_file* handle = (struct rc_buffered_file*)malloc(sizeof(struct rc_buffered_file));
memcpy(handle, &rc_buffered_file, sizeof(rc_buffered_file));
(void)path;
if (handle)
memcpy(handle, &rc_buffered_file, sizeof(rc_buffered_file));
return handle;
}
@ -1631,7 +2003,9 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b
case RC_CONSOLE_SEGA_32X:
case RC_CONSOLE_SG1000:
case RC_CONSOLE_SUPERVISION:
case RC_CONSOLE_TI83:
case RC_CONSOLE_TIC80:
case RC_CONSOLE_UZEBOX:
case RC_CONSOLE_VECTREX:
case RC_CONSOLE_VIRTUAL_BOY:
case RC_CONSOLE_WASM4:
@ -1659,6 +2033,7 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b
case RC_CONSOLE_NINTENDO_64:
case RC_CONSOLE_NINTENDO_DS:
case RC_CONSOLE_NINTENDO_DSI:
return rc_hash_file_from_buffer(hash, console_id, buffer, buffer_size);
}
}
@ -1922,7 +2297,9 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
case RC_CONSOLE_SEGA_32X:
case RC_CONSOLE_SG1000:
case RC_CONSOLE_SUPERVISION:
case RC_CONSOLE_TI83:
case RC_CONSOLE_TIC80:
case RC_CONSOLE_UZEBOX:
case RC_CONSOLE_VECTREX:
case RC_CONSOLE_VIRTUAL_BOY:
case RC_CONSOLE_WASM4:
@ -1946,6 +2323,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
case RC_CONSOLE_ATARI_7800:
case RC_CONSOLE_ATARI_LYNX:
case RC_CONSOLE_NINTENDO:
case RC_CONSOLE_PC_ENGINE:
case RC_CONSOLE_SUPER_NINTENDO:
/* additional logic whole-file hash - buffer then call rc_hash_generate_from_buffer */
return rc_hash_buffered_file(hash, console_id, path);
@ -1959,13 +2337,29 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
case RC_CONSOLE_ARCADE:
return rc_hash_arcade(hash, path);
case RC_CONSOLE_ATARI_JAGUAR_CD:
return rc_hash_jaguar_cd(hash, path);
case RC_CONSOLE_DREAMCAST:
if (rc_path_compare_extension(path, "m3u"))
return rc_hash_generate_from_playlist(hash, console_id, path);
return rc_hash_dreamcast(hash, path);
case RC_CONSOLE_GAMECUBE:
return rc_hash_gamecube(hash, path);
case RC_CONSOLE_NEO_GEO_CD:
return rc_hash_neogeo_cd(hash, path);
case RC_CONSOLE_NINTENDO_64:
return rc_hash_n64(hash, path);
case RC_CONSOLE_NINTENDO_DS:
case RC_CONSOLE_NINTENDO_DSI:
return rc_hash_nintendo_ds(hash, path);
case RC_CONSOLE_PC_ENGINE:
case RC_CONSOLE_PC_ENGINE_CD:
if (rc_path_compare_extension(path, "cue") || rc_path_compare_extension(path, "chd"))
return rc_hash_pce_cd(hash, path);
@ -1995,12 +2389,6 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
case RC_CONSOLE_PSP:
return rc_hash_psp(hash, path);
case RC_CONSOLE_DREAMCAST:
if (rc_path_compare_extension(path, "m3u"))
return rc_hash_generate_from_playlist(hash, console_id, path);
return rc_hash_dreamcast(hash, path);
case RC_CONSOLE_SEGA_CD:
case RC_CONSOLE_SATURN:
if (rc_path_compare_extension(path, "m3u"))
@ -2077,7 +2465,7 @@ static void rc_hash_initialize_dsk_iterator(struct rc_hash_iterator* iterator, c
rc_hash_iterator_append_console(iterator, RC_CONSOLE_APPLE_II);
}
void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, uint8_t* buffer, size_t buffer_size)
void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size)
{
int need_path = !buffer;
@ -2108,6 +2496,15 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
}
break;
case '8':
/* http://tibasicdev.wikidot.com/file-extensions */
if (rc_path_compare_extension(ext, "83g") ||
rc_path_compare_extension(ext, "83p"))
{
iterator->consoles[0] = RC_CONSOLE_TI83;
}
break;
case 'a':
if (rc_path_compare_extension(ext, "a78"))
{
@ -2162,9 +2559,11 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
iterator->consoles[1] = RC_CONSOLE_PLAYSTATION_2;
iterator->consoles[2] = RC_CONSOLE_DREAMCAST;
iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
iterator->consoles[4] = RC_CONSOLE_PC_ENGINE;
iterator->consoles[4] = RC_CONSOLE_PC_ENGINE_CD;
iterator->consoles[5] = RC_CONSOLE_3DO;
iterator->consoles[6] = RC_CONSOLE_PCFX;
iterator->consoles[7] = RC_CONSOLE_NEO_GEO_CD;
iterator->consoles[8] = RC_CONSOLE_ATARI_JAGUAR_CD;
need_path = 1;
}
else if (rc_path_compare_extension(ext, "chd"))
@ -2173,7 +2572,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
iterator->consoles[1] = RC_CONSOLE_PLAYSTATION_2;
iterator->consoles[2] = RC_CONSOLE_DREAMCAST;
iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
iterator->consoles[4] = RC_CONSOLE_PC_ENGINE;
iterator->consoles[4] = RC_CONSOLE_PC_ENGINE_CD;
iterator->consoles[5] = RC_CONSOLE_3DO;
iterator->consoles[6] = RC_CONSOLE_PCFX;
need_path = 1;
@ -2199,7 +2598,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
}
else if (rc_path_compare_extension(ext, "d64"))
{
iterator->consoles[0] = RC_CONSOLE_COMMODORE_64;
iterator->consoles[0] = RC_CONSOLE_COMMODORE_64;
}
else if (rc_path_compare_extension(ext, "d88"))
{
@ -2219,7 +2618,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
}
else if (rc_path_compare_extension(ext, "fd"))
{
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */
}
break;
@ -2330,7 +2729,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
}
else if (rc_path_compare_extension(ext, "nds"))
{
iterator->consoles[0] = RC_CONSOLE_NINTENDO_DS;
iterator->consoles[0] = RC_CONSOLE_NINTENDO_DS; /* ASSERT: handles both DS and DSi */
}
else if (rc_path_compare_extension(ext, "n64") ||
rc_path_compare_extension(ext, "ndd"))
@ -2412,6 +2811,13 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
}
break;
case 'u':
if (rc_path_compare_extension(ext, "uze"))
{
iterator->consoles[0] = RC_CONSOLE_UZEBOX;
}
break;
case 'v':
if (rc_path_compare_extension(ext, "vb"))
{

View file

@ -52,6 +52,7 @@
*/
#include "md5.h"
#include <stddef.h>
#include <string.h>
#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
@ -161,7 +162,7 @@ md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
* On little-endian machines, we can process properly aligned
* data without copying it.
*/
if (!((data - (const md5_byte_t *)0) & 3)) {
if (!((ptrdiff_t)data & 3)) {
/* data are properly aligned */
X = (const md5_word_t *)data;
} else {