3rdparty/rcheevos: Bump to d54cf8f

This commit is contained in:
Stenzek 2024-06-24 20:45:13 +10:00 committed by Connor McLaughlin
parent 46f37f3b45
commit 9225fa9efd
31 changed files with 1412 additions and 309 deletions

View File

@ -1,3 +1,38 @@
# v11.4.0
* add RC_CONDITION_REMEMBER and RC_OPERAND_RECALL
* add RC_OPERATOR_ADD and RC_OPERATOR_SUB
* add scratch pad memory to PSX memory map
* add Super Game Module memory to Colecovision memory map
* add rapi function fetch_game_titles
* modify progress functions to return RC_NO_GAME_LOADED when "Unknown Game" is loaded
* update subsystem list for arcade hash
* fix exception if server sends null as achievement.author
# v11.3.0
* add RC_OPERATOR_MOD
* add cartridge RAM to Game Gear and Master System memory maps
* add extended cartridge RAM to Gameboy and Gameboy Color memory maps
* add rc_client_is_game_loaded helper function
* add rc_client_raintegration_set_console_id to specify console in case game resolution fails
* add rc_client_raintegration_get_achievement_state to detect local unlocks
* report validation errors on multi-condition logic
* hash whole file for PSP homebrew files (eboot.pbp)
* call DrawMenuBar in rc_client_raintegration_rebuild_submenu if menu changes
* fix file sharing issue using default filereader on Windows
* fix exception calling rc_client_get_game_summary with an unidentified game loaded
# v11.2.0
* add alternate methods for state serialization/deserialization that accept a buffer_size parameter
* add RC_CLIENT_SUPPORTS_HASH compile flag
- allows rc_client code to build without the rhash files (except md5.c)
- must be explicitly defined to use rc_client_begin_identify_and_load_game
* add rc_client_get_load_game_state
* add rc_client_raintegration_set_get_game_name_function
* add RC_MEMSIZE_DOUBLE32 and RC_MEMSIZE_DOUBLE32_BE
* exclude directory records from ZIP hash algorithm
* fix media host when explicitly setting host to production server
* fix potential out-of-bounds read looking for error message in non-JSON response
# v11.1.0
* add rc_client_get_user_agent_clause to generate substring to include in client User-Agents
* add rc_client_can_pause function to control pause spam

View File

@ -43,6 +43,7 @@ typedef struct rc_api_fetch_code_notes_response_t {
rc_api_fetch_code_notes_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params);
/* [deprecated] use rc_api_process_fetch_code_notes_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV 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_EXPORT void RC_CCONV rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response);
@ -76,6 +77,7 @@ typedef struct rc_api_update_code_note_response_t {
rc_api_update_code_note_response_t;
RC_EXPORT int RC_CCONV rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params);
/* [deprecated] use rc_api_process_update_code_note_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response);
@ -124,6 +126,7 @@ typedef struct rc_api_update_achievement_response_t {
rc_api_update_achievement_response_t;
RC_EXPORT int RC_CCONV rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params);
/* [deprecated] use rc_api_process_update_achievement_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response);
@ -174,6 +177,7 @@ typedef struct rc_api_update_leaderboard_response_t {
rc_api_update_leaderboard_response_t;
RC_EXPORT int RC_CCONV rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params);
/* [deprecated] use rc_api_process_update_leaderboard_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response);
@ -204,6 +208,7 @@ typedef struct rc_api_fetch_badge_range_response_t {
rc_api_fetch_badge_range_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params);
/* [deprecated] use rc_api_process_fetch_badge_range_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response);
@ -244,6 +249,7 @@ typedef struct rc_api_add_game_hash_response_t {
rc_api_add_game_hash_response_t;
RC_EXPORT int RC_CCONV rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params);
/* [deprecated] use rc_api_process_add_game_hash_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response);

View File

@ -62,6 +62,7 @@ typedef struct rc_api_fetch_achievement_info_response_t {
rc_api_fetch_achievement_info_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params);
/* [deprecated] use rc_api_process_fetch_achievement_info_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV 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_EXPORT void RC_CCONV rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response);
@ -134,6 +135,7 @@ typedef struct rc_api_fetch_leaderboard_info_response_t {
rc_api_fetch_leaderboard_info_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params);
/* [deprecated] use rc_api_process_fetch_leaderboard_info_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV 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_EXPORT void RC_CCONV rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response);
@ -173,10 +175,53 @@ typedef struct rc_api_fetch_games_list_response_t {
rc_api_fetch_games_list_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params);
/* [deprecated] use rc_api_process_fetch_games_list_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV 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_EXPORT void RC_CCONV rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response);
/* --- Fetch Game Titles --- */
/**
* API parameters for a fetch games list request.
*/
typedef struct rc_api_fetch_game_titles_request_t {
/* An array of game ids to fetch titles for */
const uint32_t* game_ids;
/* The number of items in the game_ids array */
uint32_t num_game_ids;
}
rc_api_fetch_game_titles_request_t;
/* A game title entry */
typedef struct rc_api_game_title_entry_t {
/* The unique identifier of the game */
uint32_t id;
/* The title of the game */
const char* title;
/* The image name for the game badge */
const char* image_name;
}
rc_api_game_title_entry_t;
/**
* Response data for a fetch games title request.
*/
typedef struct rc_api_fetch_game_titles_response_t {
/* An array of requested entries */
rc_api_game_title_entry_t* entries;
/* The number of items in the entries array */
uint32_t num_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_game_titles_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_game_titles_request(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response);
RC_END_C_DECLS
#endif /* RC_API_INFO_H */

View File

@ -47,6 +47,7 @@ typedef struct rc_api_login_response_t {
rc_api_login_response_t;
RC_EXPORT int RC_CCONV rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params);
/* [deprecated] use rc_api_process_login_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_login_response(rc_api_login_response_t* response);
@ -104,6 +105,7 @@ typedef struct rc_api_start_session_response_t {
rc_api_start_session_response_t;
RC_EXPORT int RC_CCONV rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params);
/* [deprecated] use rc_api_process_start_session_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_start_session_response(rc_api_start_session_response_t* response);
@ -140,6 +142,7 @@ typedef struct rc_api_fetch_user_unlocks_response_t {
rc_api_fetch_user_unlocks_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params);
/* [deprecated] use rc_api_process_fetch_user_unlocks_server_response instead */
RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response);

View File

@ -221,6 +221,7 @@ RC_EXPORT void RC_CCONV rc_client_get_user_game_summary(const rc_client_t* clien
| Game |
\*****************************************************************************/
#ifdef RC_CLIENT_SUPPORTS_HASH
/**
* Start loading an unidentified game.
*/
@ -228,6 +229,7 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_g
uint32_t console_id, const char* file_path,
const uint8_t* data, size_t data_size,
rc_client_callback_t callback, void* callback_userdata);
#endif
/**
* Start loading a game.
@ -235,6 +237,25 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_g
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client_t* client, const char* hash,
rc_client_callback_t callback, void* callback_userdata);
/**
* Gets the current progress of the asynchronous load game process.
*/
RC_EXPORT int RC_CCONV rc_client_get_load_game_state(const rc_client_t* client);
enum {
RC_CLIENT_LOAD_GAME_STATE_NONE,
RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME,
RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN,
RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA,
RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION,
RC_CLIENT_LOAD_GAME_STATE_DONE,
RC_CLIENT_LOAD_GAME_STATE_ABORTED
};
/**
* Determines if a game was successfully identified and loaded.
*/
RC_EXPORT int RC_CCONV rc_client_is_game_loaded(const rc_client_t* client);
/**
* Unloads the current game.
*/
@ -250,6 +271,7 @@ typedef struct rc_client_game_t {
/**
* Get information about the current game. Returns NULL if no game is loaded.
* NOTE: returns a dummy game record if an unidentified game is loaded.
*/
RC_EXPORT const rc_client_game_t* RC_CCONV rc_client_get_game_info(const rc_client_t* client);
@ -259,11 +281,19 @@ RC_EXPORT const rc_client_game_t* RC_CCONV rc_client_get_game_info(const rc_clie
*/
RC_EXPORT int RC_CCONV rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size);
#ifdef RC_CLIENT_SUPPORTS_HASH
/**
* Changes the active disc in a multi-disc game.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV 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);
#endif
/**
* Changes the active disc in a multi-disc game.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash,
rc_client_callback_t callback, void* callback_userdata);
/*****************************************************************************\
| Subsets |
@ -663,15 +693,29 @@ RC_EXPORT size_t RC_CCONV rc_client_progress_size(rc_client_t* client);
/**
* Serializes the runtime state into a buffer.
* Returns RC_OK on success, or an error indicator.
* [deprecated] use rc_client_serialize_progress_sized instead
*/
RC_EXPORT int RC_CCONV rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer);
/**
* Deserializes the runtime state from a buffer.
* Serializes the runtime state into a buffer.
* Returns RC_OK on success, or an error indicator.
*/
RC_EXPORT int RC_CCONV rc_client_serialize_progress_sized(rc_client_t* client, uint8_t* buffer, size_t buffer_size);
/**
* Deserializes the runtime state from a buffer.
* Returns RC_OK on success, or an error indicator.
* [deprecated] use rc_client_deserialize_progress_sized instead
*/
RC_EXPORT int RC_CCONV rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized);
/**
* Serializes the runtime state into a buffer.
* Returns RC_OK on success, or an error indicator.
*/
RC_EXPORT int RC_CCONV rc_client_deserialize_progress_sized(rc_client_t* client, const uint8_t* serialized, size_t serialized_size);
RC_END_C_DECLS
#endif /* RC_RUNTIME_H */

View File

@ -27,6 +27,14 @@ typedef struct rc_client_raintegration_menu_t {
uint32_t num_items;
} rc_client_raintegration_menu_t;
enum {
RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_NONE = 0,
RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_PUBLISHED = 1,
RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_LOCAL = 2,
RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_MODIFIED = 3,
RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_INSECURE = 4,
};
enum {
RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE = 0,
RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED = 1, /* [menu_item] checked changed */
@ -46,6 +54,8 @@ typedef void (RC_CCONV *rc_client_raintegration_event_handler_t)(const rc_client
typedef void (RC_CCONV *rc_client_raintegration_write_memory_func_t)(uint32_t address, uint8_t* buffer,
uint32_t num_bytes, rc_client_t* client);
typedef void (RC_CCONV* rc_client_raintegration_get_game_name_func_t)(char* buffer, uint32_t buffer_size, rc_client_t* client);
/* types needed to integrate raintegration */
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
@ -71,13 +81,18 @@ RC_EXPORT const rc_client_raintegration_menu_t* RC_CCONV rc_client_raintegration
RC_EXPORT void RC_CCONV rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu);
RC_EXPORT void RC_CCONV rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menu_item);
RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId);
RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t menu_item_id);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_console_id(rc_client_t* client, uint32_t console_id);
RC_EXPORT int RC_CCONV rc_client_raintegration_has_modifications(const rc_client_t* client);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_event_handler(rc_client_t* client,
rc_client_raintegration_event_handler_t handler);
RC_EXPORT int RC_CCONV rc_client_raintegration_get_achievement_state(const rc_client_t* client, uint32_t achievement_id);
#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */
RC_END_C_DECLS

View File

@ -45,7 +45,10 @@ enum {
RC_NO_RESPONSE = -32,
RC_ACCESS_DENIED = -33,
RC_INVALID_CREDENTIALS = -34,
RC_EXPIRED_TOKEN = -35
RC_EXPIRED_TOKEN = -35,
RC_INSUFFICIENT_BUFFER = -36,
RC_INVALID_VARIABLE_NAME = -37,
RC_UNKNOWN_VARIABLE_NAME = -38
};
RC_EXPORT const char* RC_CCONV rc_error_str(int ret);

View File

@ -143,9 +143,15 @@ typedef int (RC_CCONV *rc_runtime_validate_address_t)(uint32_t address);
RC_EXPORT void RC_CCONV rc_runtime_validate_addresses(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler);
RC_EXPORT void RC_CCONV rc_runtime_invalidate_address(rc_runtime_t* runtime, uint32_t address);
RC_EXPORT int RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L);
RC_EXPORT uint32_t RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L);
/* [deprecated] use rc_runtime_serialize_progress_sized instead */
RC_EXPORT int RC_CCONV rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L);
RC_EXPORT int RC_CCONV rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, lua_State* L);
/* [deprecated] use rc_runtime_deserialize_progress_sized instead */
RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L);
RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, lua_State* L);
RC_END_C_DECLS

View File

@ -59,6 +59,8 @@ enum {
RC_MEMSIZE_MBF32,
RC_MEMSIZE_MBF32_LE,
RC_MEMSIZE_FLOAT_BE,
RC_MEMSIZE_DOUBLE32,
RC_MEMSIZE_DOUBLE32_BE,
RC_MEMSIZE_VARIABLE
};
@ -104,7 +106,8 @@ enum {
RC_OPERAND_LUA, /* A Lua function that provides the value. */
RC_OPERAND_PRIOR, /* The last differing value at this address. */
RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM. */
RC_OPERAND_INVERTED /* The twos-complement value of a live address in RAM. */
RC_OPERAND_INVERTED, /* The twos-complement value of a live address in RAM. */
RC_OPERAND_RECALL /* The value captured by the last RC_CONDITION_REMEMBER condition */
};
typedef struct rc_operand_t {
@ -152,6 +155,7 @@ enum {
RC_CONDITION_ADD_SOURCE, /* everything from this point on affects the condition after it */
RC_CONDITION_SUB_SOURCE,
RC_CONDITION_ADD_ADDRESS,
RC_CONDITION_REMEMBER,
/* logic flags (second switch) */
RC_CONDITION_ADD_HITS,
@ -173,7 +177,10 @@ enum {
RC_OPERATOR_MULT,
RC_OPERATOR_DIV,
RC_OPERATOR_AND,
RC_OPERATOR_XOR
RC_OPERATOR_XOR,
RC_OPERATOR_MOD,
RC_OPERATOR_ADD,
RC_OPERATOR_SUB
};
typedef struct rc_condition_t rc_condition_t;
@ -284,6 +291,8 @@ RC_EXPORT void RC_CCONV rc_reset_trigger(rc_trigger_t* self);
| Values |
\*****************************************************************************/
#define RC_VALUE_MAX_NAME_LENGTH 15
struct rc_value_t {
/* The current value of the variable. */
rc_memref_value_t value;

View File

@ -37,6 +37,24 @@ static void rc_json_skip_whitespace(rc_json_iterator_t* iterator)
++iterator->json;
}
static int rc_json_find_substring(rc_json_iterator_t* iterator, const char* substring)
{
const char first = *substring;
const size_t substring_len = strlen(substring);
const char* end = iterator->end - substring_len;
while (iterator->json <= end) {
if (*iterator->json == first) {
if (memcmp(iterator->json, substring, substring_len) == 0)
return 1;
}
++iterator->json;
}
return 0;
}
static int rc_json_find_closing_quote(rc_json_iterator_t* iterator)
{
while (iterator->json < iterator->end) {
@ -237,8 +255,6 @@ int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t*
}
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;
@ -246,34 +262,41 @@ int rc_json_get_object_string_length(const char* json) {
rc_json_parse_object(&iterator, NULL, 0, NULL);
return (int)(iterator.json - json_start);
if (iterator.json == json) /* not JSON */
return (int)strlen(json);
return (int)(iterator.json - json);
}
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;
rc_json_iterator_t iterator;
memset(&iterator, 0, sizeof(iterator));
iterator.json = server_response->body;
iterator.end = server_response->body + server_response->body_length;
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) {
response->error_message = rc_buffer_strncpy(&response->buffer, title_start, title_end - title_start);
/* if the title contains an HTTP status code(i.e "404 Not Found"), return the title */
if (rc_json_find_substring(&iterator, "<title>")) {
const char* title_start = iterator.json + 7;
if (isdigit((int)*title_start) && rc_json_find_substring(&iterator, "</title>")) {
response->error_message = rc_buffer_strncpy(&response->buffer, title_start, iterator.json - title_start);
response->succeeded = 0;
return RC_INVALID_JSON;
}
}
/* title not found, or did not start with an error code, return the first line of the response */
iterator.json = server_response->body;
while (iterator.json < iterator.end && *iterator.json != '\n' &&
iterator.json - server_response->body < 200) {
++iterator.json;
}
while (*end && *end != '\n' && end - json < 200)
++end;
if (iterator.json > server_response->body && iterator.json[-1] == '\r')
--iterator.json;
if (end > json && end[-1] == '\r')
--end;
if (end > json)
response->error_message = rc_buffer_strncpy(&response->buffer, json, end - json);
if (iterator.json > server_response->body)
response->error_message = rc_buffer_strncpy(&response->buffer, server_response->body, iterator.json - server_response->body);
response->succeeded = 0;
return RC_INVALID_JSON;
@ -915,6 +938,27 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js
return rc_json_missing_field(response, field);
}
void rc_json_extract_filename(rc_json_field_t* field) {
if (field->value_end) {
const char* str = field->value_end;
/* remove the extension */
while (str > field->value_start && str[-1] != '/') {
--str;
if (*str == '.') {
field->value_end = str;
break;
}
}
/* find the path separator */
while (str > field->value_start && str[-1] != '/')
--str;
field->value_start = str;
}
}
/* --- rc_api_request --- */
void rc_api_destroy_request(rc_api_request_t* request)
@ -1150,6 +1194,9 @@ static void rc_api_update_host(char** host, const char* hostname) {
}
void rc_api_set_host(const char* hostname) {
if (hostname && strcmp(hostname, RETROACHIEVEMENTS_HOST) == 0)
hostname = NULL;
rc_api_update_host(&g_host, hostname);
if (!hostname) {

View File

@ -67,6 +67,8 @@ int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count,
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_json_extract_filename(rc_json_field_t* field);
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, int32_t value);
void rc_url_builder_append_unum_param(rc_api_url_builder_t* builder, const char* param, uint32_t value);

View File

@ -3,6 +3,8 @@
#include "rc_runtime_types.h"
#include "../rc_compat.h"
#include <stdlib.h>
#include <string.h>
@ -371,3 +373,91 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp
void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Fetch Game Titles --- */
int rc_api_init_fetch_game_titles_request(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params) {
rc_api_url_builder_t builder;
char num[16];
uint32_t i;
rc_api_url_build_dorequest_url(request);
if (api_params->num_game_ids == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "gameinfolist");
rc_url_builder_append_unum_param(&builder, "g", api_params->game_ids[0]);
for (i = 1; i < api_params->num_game_ids; i++) {
int chars = snprintf(num, sizeof(num), "%u", api_params->game_ids[i]);
rc_url_builder_append(&builder, ",", 1);
rc_url_builder_append(&builder, num, chars);
}
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_game_title_entry_t* entry;
rc_json_iterator_t iterator;
rc_json_field_t array_field;
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Response")
};
rc_json_field_t entry_fields[] = {
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("ImageIcon")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &fields[2], "Response"))
return RC_MISSING_VALUE;
if (response->num_entries) {
response->entries = (rc_api_game_title_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_title_entry_t));
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_unum(&entry->id, &response->response, &entry_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&entry->title, &response->response, &entry_fields[1], "Title"))
return RC_MISSING_VALUE;
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
rc_json_extract_filename(&entry_fields[2]);
if (!rc_json_get_required_string(&entry->image_name, &response->response, &entry_fields[2], "ImageIcon"))
return RC_MISSING_VALUE;
++entry;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}

View File

@ -111,7 +111,6 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
rc_api_leaderboard_definition_t* leaderboard;
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;
@ -180,17 +179,7 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
return RC_MISSING_VALUE;
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
if (patchdata_fields[3].value_end) {
str = patchdata_fields[3].value_end - 5;
if (memcmp(str, ".png\"", 5) == 0) {
patchdata_fields[3].value_end -= 5;
while (str > patchdata_fields[3].value_start && str[-1] != '/')
--str;
patchdata_fields[3].value_start = str;
}
}
rc_json_extract_filename(&patchdata_fields[3]);
rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", "");
/* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards.
@ -248,10 +237,16 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author"))
return RC_MISSING_VALUE;
if (achievement->author == NULL) {
/* ensure we don't pass NULL out to client */
last_author = achievement->author = "";
last_author_len = 0;
} else {
last_author = achievement->author;
last_author_field = achievement_fields[6].value_start;
last_author_len = len;
}
}
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created"))
return RC_MISSING_VALUE;

View File

@ -42,9 +42,12 @@ typedef struct rc_client_generic_callback_data_t {
typedef struct rc_client_pending_media_t
{
#ifdef RC_CLIENT_SUPPORTS_HASH
const char* file_path;
uint8_t* data;
size_t data_size;
#endif
const char* hash;
rc_client_callback_t callback;
void* callback_userdata;
} rc_client_pending_media_t;
@ -59,7 +62,9 @@ typedef struct rc_client_load_state_t
rc_client_subset_info_t* subset;
rc_client_game_hash_t* hash;
#ifdef RC_CLIENT_SUPPORTS_HASH
rc_hash_iterator_t hash_iterator;
#endif
rc_client_pending_media_t* pending_media;
rc_api_start_session_response_t *start_session_response;
@ -68,7 +73,9 @@ typedef struct rc_client_load_state_t
uint8_t progress;
uint8_t outstanding_requests;
#ifdef RC_CLIENT_SUPPORTS_HASH
uint8_t hash_console_id;
#endif
} rc_client_load_state_t;
static void rc_client_begin_fetch_game_data(rc_client_load_state_t* callback_data);
@ -135,6 +142,10 @@ void rc_client_destroy(rc_client_t* client)
rc_client_unload_game(client);
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
rc_client_unload_raintegration(client);
#endif
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->destroy)
client->state.external_client->destroy();
@ -151,9 +162,11 @@ void rc_client_destroy(rc_client_t* client)
static rc_client_t* g_hash_client = NULL;
#ifdef RC_CLIENT_SUPPORTS_HASH
static void rc_client_log_hash_message(const char* message) {
rc_client_log_message(g_hash_client, message);
}
#endif
void rc_client_log_message(const rc_client_t* client, const char* message)
{
@ -612,7 +625,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp
if (login_callback_data->callback)
login_callback_data->callback(result, error_message, client, login_callback_data->callback_userdata);
if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN)
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
rc_client_begin_fetch_game_data(load_state);
}
else {
@ -635,7 +648,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp
RC_CLIENT_LOG_INFO_FORMATTED(client, "%s logged in successfully", login_response.display_name);
if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN)
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
rc_client_begin_fetch_game_data(load_state);
if (login_callback_data->callback)
@ -796,7 +809,7 @@ void rc_client_logout(rc_client_t* client)
rc_client_unload_game(client);
if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN)
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
rc_client_load_error(load_state, RC_ABORTED, "Login aborted");
}
@ -871,7 +884,7 @@ void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_g
}
#endif
if (!client->game)
if (!rc_client_is_game_loaded(client))
return;
rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */
@ -937,9 +950,9 @@ static int rc_client_end_load_state(rc_client_load_state_t* load_state)
* the outstanding_requests count will reach zero and the memory will be free'd then. */
if (remaining_requests == 0) {
/* if one of the callbacks called rc_client_load_error, progress will be set to
* RC_CLIENT_LOAD_STATE_UNKNOWN. There's no need to call the callback with RC_ABORTED
* RC_CLIENT_LOAD_STATE_ABORTED. There's no need to call the callback with RC_ABORTED
* in that case, as it will have already been called with something more appropriate. */
if (load_state->progress != RC_CLIENT_LOAD_STATE_UNKNOWN_GAME && load_state->callback)
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED && load_state->callback)
load_state->callback(RC_ABORTED, "The requested game is no longer active", load_state->client, load_state->callback_userdata);
rc_client_free_load_state(load_state);
@ -957,7 +970,7 @@ static void rc_client_load_error(rc_client_load_state_t* load_state, int result,
rc_mutex_lock(&load_state->client->state.mutex);
load_state->progress = RC_CLIENT_LOAD_STATE_UNKNOWN_GAME;
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED;
if (load_state->client->state.load == load_state)
load_state->client->state.load = NULL;
@ -1401,17 +1414,29 @@ static void rc_client_apply_unlocks(rc_client_subset_info_t* subset, rc_api_unlo
}
}
static void rc_client_free_pending_media(rc_client_pending_media_t* pending_media)
{
if (pending_media->hash)
free((void*)pending_media->hash);
#ifdef RC_CLIENT_SUPPORTS_HASH
if (pending_media->data)
free(pending_media->data);
free((void*)pending_media->file_path);
#endif
free(pending_media);
}
static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_start_session_response_t *start_session_response)
{
rc_client_t* client = load_state->client;
rc_mutex_lock(&client->state.mutex);
load_state->progress = (client->state.load == load_state) ?
RC_CLIENT_LOAD_STATE_DONE : RC_CLIENT_LOAD_STATE_UNKNOWN_GAME;
RC_CLIENT_LOAD_GAME_STATE_DONE : RC_CLIENT_LOAD_GAME_STATE_ABORTED;
client->state.load = NULL;
rc_mutex_unlock(&client->state.mutex);
if (load_state->progress != RC_CLIENT_LOAD_STATE_DONE) {
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_DONE) {
/* previous load state was aborted */
if (load_state->callback)
load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata);
@ -1449,12 +1474,17 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
rc_mutex_unlock(&load_state->client->state.mutex);
if (pending_media) {
if (pending_media->hash) {
rc_client_begin_change_media_from_hash(client, pending_media->hash,
pending_media->callback, pending_media->callback_userdata);
} else {
#ifdef RC_CLIENT_SUPPORTS_HASH
rc_client_begin_change_media(client, pending_media->file_path,
pending_media->data, pending_media->data_size, pending_media->callback, pending_media->callback_userdata);
if (pending_media->data)
free(pending_media->data);
free((void*)pending_media->file_path);
free(pending_media);
pending_media->data, pending_media->data_size,
pending_media->callback, pending_media->callback_userdata);
#endif
}
rc_client_free_pending_media(pending_media);
}
/* client->game must be set before calling this function so it can query the console_id */
@ -1561,7 +1591,7 @@ static void rc_client_begin_start_session(rc_client_load_state_t* load_state)
rc_client_load_error(load_state, result, rc_error_str(result));
}
else {
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, 1);
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Starting session for game %u", start_session_params.game_id);
rc_client_begin_async(client, &load_state->async_handle);
client->callbacks.server_call(&start_session_request, rc_client_start_session_callback, load_state, client);
@ -1876,7 +1906,7 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
}
/* kick off the start session request while we process the game data */
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, 1);
if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) {
/* we can't unlock achievements without a session, lock spectator mode for the game */
load_state->client->state.spectator_mode = RC_CLIENT_SPECTATOR_MODE_LOCKED;
@ -1917,7 +1947,7 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
if (!subset->public_.title) {
const char* core_subset_title = rc_client_subset_extract_title(load_state->game, load_state->game->public_.title);
if (core_subset_title) {
rc_client_subset_info_t* scan = load_state->game->subsets;
scan = load_state->game->subsets;
for (; scan; scan = scan->next) {
if (scan->public_.title == load_state->game->public_.title) {
scan->public_.title = core_subset_title;
@ -1962,6 +1992,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
int result;
if (load_state->hash->game_id == 0) {
#ifdef RC_CLIENT_SUPPORTS_HASH
char hash[33];
if (rc_hash_iterate(hash, &load_state->hash_iterator)) {
@ -2010,18 +2041,41 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
/* only a single hash was tried, capture it */
load_state->game->public_.console_id = load_state->hash_console_id;
load_state->game->public_.hash = load_state->hash->hash;
if (client->callbacks.identify_unknown_hash) {
load_state->hash->game_id = client->callbacks.identify_unknown_hash(
load_state->hash_console_id, load_state->hash->hash, client, load_state->callback_userdata);
if (load_state->hash->game_id != 0) {
RC_CLIENT_LOG_INFO_FORMATTED(load_state->client, "Client says to load game %u for unidentified hash %s",
load_state->hash->game_id, load_state->hash->hash);
}
}
}
#else
load_state->game->public_.console_id = RC_CONSOLE_UNKNOWN;
load_state->game->public_.hash = load_state->hash->hash;
#endif /* RC_CLIENT_SUPPORTS_HASH */
if (load_state->hash->game_id == 0) {
rc_client_subset_info_t* subset;
subset = (rc_client_subset_info_t*)rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_subset_info_t));
memset(subset, 0, sizeof(*subset));
subset->public_.title = "";
load_state->game->public_.title = "Unknown Game";
load_state->game->public_.badge_name = "";
load_state->game->subsets = subset;
client->game = load_state->game;
load_state->game = NULL;
rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game");
return;
}
}
if (load_state->hash->hash[0] != '[') {
if (load_state->hash->hash[0] != '[') { /* not [NO HASH] or [SUBSETxx] */
load_state->game->public_.id = load_state->hash->game_id;
load_state->game->public_.hash = load_state->hash->hash;
}
@ -2032,7 +2086,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
rc_mutex_lock(&client->state.mutex);
result = client->state.user;
if (result == RC_CLIENT_USER_STATE_LOGIN_REQUESTED)
load_state->progress = RC_CLIENT_LOAD_STATE_AWAIT_LOGIN;
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN;
rc_mutex_unlock(&client->state.mutex);
switch (result) {
@ -2059,7 +2113,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
return;
}
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, 1);
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Fetching data for game %u", fetch_game_data_request.game_id);
rc_client_begin_async(client, &load_state->async_handle);
@ -2201,7 +2255,7 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
return NULL;
}
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, 1);
rc_client_begin_async(client, &load_state->async_handle);
client->callbacks.server_call(&request, rc_client_identify_game_callback, load_state, client);
@ -2217,14 +2271,6 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
}
rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client)
{
if (client && client->state.load)
return &client->state.load->hash_iterator;
return NULL;
}
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)
{
rc_client_load_state_t* load_state;
@ -2257,6 +2303,16 @@ rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const c
return rc_client_load_game(load_state, hash, NULL);
}
#ifdef RC_CLIENT_SUPPORTS_HASH
rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client)
{
if (client && client->state.load)
return &client->state.load->hash_iterator;
return NULL;
}
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,
@ -2340,6 +2396,39 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
return rc_client_load_game(load_state, hash, file_path);
}
#endif /* RC_CLIENT_SUPPORTS_HASH */
int rc_client_get_load_game_state(const rc_client_t* client)
{
int state = RC_CLIENT_LOAD_GAME_STATE_NONE;
if (client) {
const rc_client_load_state_t* load_state = client->state.load;
if (load_state)
state = load_state->progress;
else if (client->game)
state = RC_CLIENT_LOAD_GAME_STATE_DONE;
}
return state;
}
int rc_client_is_game_loaded(const rc_client_t* client)
{
const rc_client_game_t* game;
if (!client)
return 0;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->get_game_info)
game = client->state.external_client->get_game_info();
else
#endif
game = client->game ? &client->game->public_ : NULL;
return (game && game->id != 0);
}
static void rc_client_game_mark_ui_to_be_hidden(rc_client_t* client, rc_client_game_info_t* game)
{
rc_client_achievement_info_t* achievement;
@ -2428,8 +2517,10 @@ void rc_client_unload_game(rc_client_t* client)
}
}
static void rc_client_change_media(rc_client_t* client, const rc_client_game_hash_t* game_hash, rc_client_callback_t callback, void* callback_userdata)
static void rc_client_change_media_internal(rc_client_t* client, const rc_client_game_hash_t* game_hash, rc_client_callback_t callback, void* callback_userdata)
{
client->game->public_.hash = game_hash->hash;
if (game_hash->game_id == client->game->public_.id) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to valid media for game %u: %s", game_hash->game_id, game_hash->hash);
}
@ -2437,13 +2528,19 @@ static void rc_client_change_media(rc_client_t* client, const rc_client_game_has
RC_CLIENT_LOG_INFO(client, "Switching to unknown media");
}
else if (game_hash->game_id == 0) {
if (client->state.hardcore) {
RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabling hardcore for unidentified media: %s", game_hash->hash);
rc_client_set_hardcore_enabled(client, 0);
callback(RC_HARDCORE_DISABLED, "Hardcore disabled. Unidentified media inserted.", client, callback_userdata);
return;
}
RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to unrecognized media: %s", game_hash->hash);
}
else {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to known media for game %u: %s", game_hash->game_id, game_hash->hash);
}
client->game->public_.hash = game_hash->hash;
callback(RC_OK, NULL, client, callback_userdata);
}
@ -2475,22 +2572,65 @@ static void rc_client_identify_changed_media_callback(const rc_api_server_respon
else {
load_state->hash->game_id = resolve_hash_response.game_id;
if (resolve_hash_response.game_id == 0 && client->state.hardcore) {
RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabling hardcore for unidentified media: %s", load_state->hash->hash);
rc_client_set_hardcore_enabled(client, 0);
client->game->public_.hash = load_state->hash->hash; /* do still update the loaded hash */
load_state->callback(RC_HARDCORE_DISABLED, "Hardcore disabled. Unidentified media inserted.", client, load_state->callback_userdata);
}
else {
if (resolve_hash_response.game_id != 0) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Identified game: %u (%s)", load_state->hash->game_id, load_state->hash->hash);
rc_client_change_media(client, load_state->hash, load_state->callback, load_state->callback_userdata);
}
rc_client_change_media_internal(client, load_state->hash, load_state->callback, load_state->callback_userdata);
}
free(load_state);
rc_api_destroy_resolve_hash_response(&resolve_hash_response);
}
static rc_client_async_handle_t* rc_client_begin_change_media_internal(rc_client_t* client,
rc_client_game_info_t* game, rc_client_game_hash_t* game_hash, rc_client_callback_t callback, void* callback_userdata)
{
rc_client_load_state_t* callback_data;
rc_client_async_handle_t* async_handle;
rc_api_resolve_hash_request_t resolve_hash_request;
rc_api_request_t request;
int result;
if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID) {
rc_client_change_media_internal(client, game_hash, callback, callback_userdata);
return NULL;
}
/* call the server to make sure the hash is valid for the loaded game */
memset(&resolve_hash_request, 0, sizeof(resolve_hash_request));
resolve_hash_request.game_hash = game_hash->hash;
result = rc_api_init_resolve_hash_request(&request, &resolve_hash_request);
if (result != RC_OK) {
callback(result, rc_error_str(result), client, callback_userdata);
return NULL;
}
callback_data = (rc_client_load_state_t*)calloc(1, sizeof(rc_client_load_state_t));
if (!callback_data) {
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
return NULL;
}
callback_data->callback = callback;
callback_data->callback_userdata = callback_userdata;
callback_data->client = client;
callback_data->hash = game_hash;
callback_data->game = game;
async_handle = &callback_data->async_handle;
rc_client_begin_async(client, async_handle);
client->callbacks.server_call(&request, rc_client_identify_changed_media_callback, callback_data, client);
rc_api_destroy_request(&request);
/* if handle is no longer valid, the async operation completed synchronously */
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
}
#ifdef RC_CLIENT_SUPPORTS_HASH
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)
{
@ -2521,12 +2661,8 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
if (game->public_.console_id == 0) {
/* still waiting for game data */
pending_media = client->state.load->pending_media;
if (pending_media) {
if (pending_media->data)
free(pending_media->data);
free((void*)pending_media->file_path);
free(pending_media);
}
if (pending_media)
rc_client_free_pending_media(pending_media);
pending_media = (rc_client_pending_media_t*)calloc(1, sizeof(*pending_media));
if (!pending_media) {
@ -2613,53 +2749,78 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
rc_mutex_unlock(&client->state.mutex);
if (!result) {
rc_client_change_media(client, game_hash, callback, callback_userdata);
rc_client_change_media_internal(client, game_hash, callback, callback_userdata);
return NULL;
}
}
if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID) {
rc_client_change_media(client, game_hash, callback, callback_userdata);
return NULL;
return rc_client_begin_change_media_internal(client, game, game_hash, callback, callback_userdata);
}
else {
/* call the server to make sure the hash is valid for the loaded game */
rc_client_load_state_t* callback_data;
rc_client_async_handle_t* async_handle;
rc_api_resolve_hash_request_t resolve_hash_request;
rc_api_request_t request;
int result;
memset(&resolve_hash_request, 0, sizeof(resolve_hash_request));
resolve_hash_request.game_hash = game_hash->hash;
#endif /* RC_CLIENT_SUPPORTS_HASH */
result = rc_api_init_resolve_hash_request(&request, &resolve_hash_request);
if (result != RC_OK) {
callback(result, rc_error_str(result), client, callback_userdata);
rc_client_async_handle_t* rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash,
rc_client_callback_t callback, void* callback_userdata)
{
rc_client_game_hash_t* game_hash;
rc_client_game_info_t* game;
rc_client_pending_media_t* pending_media = NULL;
if (!client) {
callback(RC_INVALID_STATE, "client is required", client, callback_userdata);
return NULL;
}
callback_data = (rc_client_load_state_t*)calloc(1, sizeof(rc_client_load_state_t));
if (!callback_data) {
if (!hash || !hash[0]) {
callback(RC_INVALID_STATE, "hash is required", client, callback_userdata);
return NULL;
}
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->begin_change_media_from_hash) {
return client->state.external_client->begin_change_media_from_hash(client, hash, callback, callback_userdata);
}
#endif
rc_mutex_lock(&client->state.mutex);
if (client->state.load) {
game = client->state.load->game;
if (game->public_.console_id == 0) {
/* still waiting for game data */
pending_media = client->state.load->pending_media;
if (pending_media)
rc_client_free_pending_media(pending_media);
pending_media = (rc_client_pending_media_t*)calloc(1, sizeof(*pending_media));
if (!pending_media) {
rc_mutex_unlock(&client->state.mutex);
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
return NULL;
}
callback_data->callback = callback;
callback_data->callback_userdata = callback_userdata;
callback_data->client = client;
callback_data->hash = game_hash;
callback_data->game = game;
pending_media->hash = strdup(hash);
pending_media->callback = callback;
pending_media->callback_userdata = callback_userdata;
async_handle = &callback_data->async_handle;
rc_client_begin_async(client, async_handle);
client->callbacks.server_call(&request, rc_client_identify_changed_media_callback, callback_data, client);
rc_api_destroy_request(&request);
/* if handle is no longer valid, the async operation completed synchronously */
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
client->state.load->pending_media = pending_media;
}
} else {
game = client->game;
}
rc_mutex_unlock(&client->state.mutex);
if (!game) {
callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata);
return NULL;
}
/* still waiting for game data */
if (pending_media)
return NULL;
/* check to see if we've already hashed this file. */
game_hash = rc_client_find_game_hash(client, hash);
return rc_client_begin_change_media_internal(client, game, game_hash, callback, callback_userdata);
}
const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client)
@ -2700,7 +2861,7 @@ rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint3
return client->state.external_client->begin_load_subset(client, subset_id, callback, callback_userdata);
#endif
if (!client->game) {
if (!rc_client_is_game_loaded(client)) {
callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata);
return NULL;
}
@ -3753,7 +3914,7 @@ int rc_client_has_leaderboards(rc_client_t* client)
return result;
}
static void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard)
void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard)
{
rc_client_leaderboard_tracker_info_t* tracker;
rc_client_leaderboard_tracker_info_t* available_tracker = NULL;
@ -5223,7 +5384,7 @@ size_t rc_client_progress_size(rc_client_t* client)
return client->state.external_client->progress_size();
#endif
if (!client->game)
if (!rc_client_is_game_loaded(client))
return 0;
rc_mutex_lock(&client->state.mutex);
@ -5234,6 +5395,11 @@ size_t rc_client_progress_size(rc_client_t* client)
}
int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer)
{
return rc_client_serialize_progress_sized(client, buffer, 0xFFFFFFFF);
}
int rc_client_serialize_progress_sized(rc_client_t* client, uint8_t* buffer, size_t buffer_size)
{
int result;
@ -5242,17 +5408,17 @@ int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer)
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->serialize_progress)
return client->state.external_client->serialize_progress(buffer);
return client->state.external_client->serialize_progress(buffer, buffer_size);
#endif
if (!client->game)
if (!rc_client_is_game_loaded(client))
return RC_NO_GAME_LOADED;
if (!buffer)
return RC_INVALID_STATE;
rc_mutex_lock(&client->state.mutex);
result = rc_runtime_serialize_progress(buffer, &client->game->runtime, NULL);
result = rc_runtime_serialize_progress_sized(buffer, (uint32_t)buffer_size, &client->game->runtime, NULL);
rc_mutex_unlock(&client->state.mutex);
return result;
@ -5352,6 +5518,11 @@ static void rc_client_subset_after_deserialize_progress(rc_client_game_info_t* g
}
int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized)
{
return rc_client_deserialize_progress_sized(client, serialized, 0xFFFFFFFF);
}
int rc_client_deserialize_progress_sized(rc_client_t* client, const uint8_t* serialized, size_t serialized_size)
{
rc_client_subset_info_t* subset;
int result;
@ -5361,10 +5532,10 @@ int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialize
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->deserialize_progress)
return client->state.external_client->deserialize_progress(serialized);
return client->state.external_client->deserialize_progress(serialized, serialized_size);
#endif
if (!client->game)
if (!rc_client_is_game_loaded(client))
return RC_NO_GAME_LOADED;
rc_mutex_lock(&client->state.mutex);
@ -5381,7 +5552,7 @@ int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialize
result = RC_OK;
}
else {
result = rc_runtime_deserialize_progress(&client->game->runtime, serialized, NULL);
result = rc_runtime_deserialize_progress_sized(&client->game->runtime, serialized, (uint32_t)serialized_size, NULL);
}
for (subset = client->game->subsets; subset; subset = subset->next)

View File

@ -61,8 +61,8 @@ typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_lead
typedef size_t (RC_CCONV *rc_client_external_progress_size_func_t)(void);
typedef int (RC_CCONV *rc_client_external_serialize_progress_func_t)(uint8_t* buffer);
typedef int (RC_CCONV *rc_client_external_deserialize_progress_func_t)(const uint8_t* buffer);
typedef int (RC_CCONV *rc_client_external_serialize_progress_func_t)(uint8_t* buffer, size_t buffer_size);
typedef int (RC_CCONV *rc_client_external_deserialize_progress_func_t)(const uint8_t* buffer, size_t buffer_size);
typedef struct rc_client_external_t
{
@ -99,6 +99,7 @@ typedef struct rc_client_external_t
rc_client_external_action_func_t unload_game;
rc_client_external_get_user_game_summary_func_t get_user_game_summary;
rc_client_external_begin_change_media_func_t begin_change_media;
rc_client_external_begin_load_game_func_t begin_change_media_from_hash;
rc_client_external_create_achievement_list_func_t create_achievement_list;
rc_client_external_get_int_func_t has_achievements;

View File

@ -26,6 +26,8 @@ typedef void (RC_CCONV *rc_client_post_process_game_data_response_t)(const rc_ap
typedef int (RC_CCONV *rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client);
typedef int (RC_CCONV *rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client);
typedef int (RC_CCONV *rc_client_rich_presence_override_t)(rc_client_t* client, char buffer[], size_t buffersize);
typedef uint32_t (RC_CCONV* rc_client_identify_hash_func_t)(uint32_t console_id, const char* hash,
rc_client_t* client, void* callback_userdata);
typedef struct rc_client_callbacks_t {
rc_client_read_memory_func_t read_memory;
@ -33,6 +35,7 @@ typedef struct rc_client_callbacks_t {
rc_client_server_call_t server_call;
rc_client_message_callback_t log_call;
rc_get_time_millisecs_func_t get_time_millisecs;
rc_client_identify_hash_func_t identify_unknown_hash;
rc_client_post_process_game_data_response_t post_process_game_data_response;
rc_client_can_submit_achievement_unlock_t can_submit_achievement_unlock;
rc_client_can_submit_leaderboard_entry_t can_submit_leaderboard_entry;
@ -265,16 +268,6 @@ void rc_client_update_active_leaderboards(rc_client_game_info_t* game);
| Client |
\*****************************************************************************/
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,
@ -375,8 +368,10 @@ int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref)
/* end runtime.c internals */
/* helper functions for unit tests */
#ifdef RC_CLIENT_SUPPORTS_HASH
struct rc_hash_iterator;
struct rc_hash_iterator* rc_client_get_load_state_hash_iterator(rc_client_t* client);
#endif
/* end helper functions for unit tests */
enum {
@ -387,6 +382,7 @@ enum {
void rc_client_set_legacy_peek(rc_client_t* client, int method);
void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard);
void rc_client_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard);
RC_END_C_DECLS

View File

@ -69,6 +69,7 @@ static void rc_client_raintegration_load_dll(rc_client_t* client,
raintegration->get_host_url = (rc_client_raintegration_get_string_func_t)GetProcAddress(hDLL, "_RA_HostUrl");
raintegration->init_client = (rc_client_raintegration_init_client_func_t)GetProcAddress(hDLL, "_RA_InitClient");
raintegration->init_client_offline = (rc_client_raintegration_init_client_func_t)GetProcAddress(hDLL, "_RA_InitOffline");
raintegration->set_console_id = (rc_client_raintegration_set_int_func_t)GetProcAddress(hDLL, "_RA_SetConsoleID");
raintegration->shutdown = (rc_client_raintegration_action_func_t)GetProcAddress(hDLL, "_RA_Shutdown");
raintegration->update_main_window_handle = (rc_client_raintegration_hwnd_action_func_t)GetProcAddress(hDLL, "_RA_UpdateHWnd");
@ -77,7 +78,10 @@ static void rc_client_raintegration_load_dll(rc_client_t* client,
raintegration->get_menu = (rc_client_raintegration_get_menu_func_t)GetProcAddress(hDLL, "_Rcheevos_RAIntegrationGetMenu");
raintegration->activate_menu_item = (rc_client_raintegration_activate_menuitem_func_t)GetProcAddress(hDLL, "_Rcheevos_ActivateRAIntegrationMenuItem");
raintegration->set_write_memory_function = (rc_client_raintegration_set_write_memory_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationWriteMemoryFunction");
raintegration->set_get_game_name_function = (rc_client_raintegration_set_get_game_name_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationGetGameNameFunction");
raintegration->set_event_handler = (rc_client_raintegration_set_event_handler_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationEventHandler");
raintegration->has_modifications = (rc_client_raintegration_get_int_func_t)GetProcAddress(hDLL, "_Rcheevos_HasModifications");
raintegration->get_achievement_state = (rc_client_raintegration_get_achievement_state_func_t)GetProcAddress(hDLL, "_Rcheevos_GetAchievementState");
if (!raintegration->get_version ||
!raintegration->init_client ||
@ -147,6 +151,7 @@ static void rc_client_init_raintegration(rc_client_t* client,
const char* host_url = client->state.raintegration->get_host_url();
if (host_url) {
if (strcmp(host_url, "OFFLINE") != 0) {
if (strcmp(host_url, "https://retroachievements.org") != 0)
rc_client_set_host(client, host_url);
}
else if (client->state.raintegration->init_client_offline) {
@ -201,6 +206,7 @@ static void rc_client_init_raintegration(rc_client_t* client,
/* attach the external client and call the callback */
client->state.external_client = external_client;
client->state.raintegration->hMainWindow = version_validation_callback_data->main_window_handle;
client->state.raintegration->bIsInited = 1;
version_validation_callback_data->callback(RC_OK, NULL,
@ -349,13 +355,15 @@ rc_client_async_handle_t* rc_client_begin_load_raintegration(rc_client_t* client
void rc_client_raintegration_update_main_window_handle(rc_client_t* client, HWND main_window_handle)
{
if (client && client->state.raintegration &&
client->state.raintegration->bIsInited &&
client->state.raintegration->update_main_window_handle)
{
if (client && client->state.raintegration) {
client->state.raintegration->hMainWindow = main_window_handle;
if (client->state.raintegration->bIsInited &&
client->state.raintegration->update_main_window_handle) {
client->state.raintegration->update_main_window_handle(main_window_handle);
}
}
}
void rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler)
{
@ -363,6 +371,12 @@ void rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_c
client->state.raintegration->set_write_memory_function(client, handler);
}
void rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler)
{
if (client && client->state.raintegration && client->state.raintegration->set_get_game_name_function)
client->state.raintegration->set_get_game_name_function(client, handler);
}
void rc_client_raintegration_set_event_handler(rc_client_t* client,
rc_client_raintegration_event_handler_t handler)
{
@ -374,14 +388,41 @@ const rc_client_raintegration_menu_t* rc_client_raintegration_get_menu(const rc_
{
if (!client || !client->state.raintegration ||
!client->state.raintegration->bIsInited ||
!client->state.raintegration->get_menu)
{
!client->state.raintegration->get_menu) {
return NULL;
}
return client->state.raintegration->get_menu();
}
void rc_client_raintegration_set_console_id(rc_client_t* client, uint32_t console_id)
{
if (client && client->state.raintegration && client->state.raintegration->set_console_id)
client->state.raintegration->set_console_id(console_id);
}
int rc_client_raintegration_has_modifications(const rc_client_t* client)
{
if (!client || !client->state.raintegration ||
!client->state.raintegration->bIsInited ||
!client->state.raintegration->has_modifications) {
return 0;
}
return client->state.raintegration->has_modifications();
}
int rc_client_raintegration_get_achievement_state(const rc_client_t* client, uint32_t achievement_id)
{
if (!client || !client->state.raintegration ||
!client->state.raintegration->bIsInited ||
!client->state.raintegration->get_achievement_state) {
return RC_CLIENT_RAINTEGRATION_ACHIEVEMENT_STATE_NONE;
}
return client->state.raintegration->get_achievement_state(achievement_id);
}
void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
{
HMENU hPopupMenu = NULL;
@ -413,7 +454,7 @@ void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
if (menuitem->checked)
flags |= MF_CHECKED;
if (!menuitem->enabled)
flags |= MF_DISABLED | MF_GRAYED;
flags |= MF_GRAYED;
AppendMenuA(hPopupMenu, flags, menuitem->id, menuitem->label);
}
@ -428,7 +469,7 @@ void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
UINT flags = MF_POPUP | MF_STRING;
if (!menu || !menu->num_items)
flags |= MF_DISABLED | MF_GRAYED;
flags |= MF_GRAYED;
while (--nIndex >= 0)
{
@ -443,6 +484,9 @@ void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
AppendMenuA(hMenu, flags, (UINT_PTR)hPopupMenu, menuText);
else
ModifyMenuA(hMenu, nIndex, flags | MF_BYPOSITION, (UINT_PTR)hPopupMenu, menuText);
if (client->state.raintegration->hMainWindow && GetMenu(client->state.raintegration->hMainWindow) == hMenu)
DrawMenuBar(client->state.raintegration->hMainWindow);
}
client->state.raintegration->hPopupMenu = hPopupMenu;
@ -457,15 +501,18 @@ void rc_client_raintegration_update_menu_item(const rc_client_t* client, const r
flags |= MF_CHECKED;
CheckMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND);
flags = (menuitem->enabled) ? MF_ENABLED : MF_GRAYED;
EnableMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND);
}
}
int rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId)
int rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t menu_item_id)
{
if (!client || !client->state.raintegration || !client->state.raintegration->activate_menu_item)
return 0;
return client->state.raintegration->activate_menu_item(nMenuItemId);
return client->state.raintegration->activate_menu_item(menu_item_id);
}
void rc_client_unload_raintegration(rc_client_t* client)
@ -477,6 +524,9 @@ void rc_client_unload_raintegration(rc_client_t* client)
RC_CLIENT_LOG_INFO(client, "Unloading RA_Integration")
if (client->state.external_client && client->state.external_client->destroy)
client->state.external_client->destroy();
if (client->state.raintegration->shutdown)
client->state.raintegration->shutdown();

View File

@ -17,14 +17,19 @@ typedef const char* (RC_CCONV* rc_client_raintegration_get_string_func_t)(void);
typedef int (RC_CCONV* rc_client_raintegration_init_client_func_t)(HWND hMainWnd, const char* sClientName, const char* sClientVersion);
typedef int (RC_CCONV* rc_client_raintegration_get_external_client_func_t)(rc_client_external_t* pClient, int nVersion);
typedef void (RC_CCONV* rc_client_raintegration_hwnd_action_func_t)(HWND hWnd);
typedef int (RC_CCONV* rc_client_raintegration_get_achievement_state_func_t)(uint32_t nMenuItemId);
typedef const rc_client_raintegration_menu_t* (RC_CCONV* rc_client_raintegration_get_menu_func_t)(void);
typedef int (RC_CCONV* rc_client_raintegration_activate_menuitem_func_t)(uint32_t nMenuItemId);
typedef void (RC_CCONV* rc_client_raintegration_set_write_memory_func_t)(rc_client_t* pClient, rc_client_raintegration_write_memory_func_t handler);
typedef void (RC_CCONV* rc_client_raintegration_set_get_game_name_func_t)(rc_client_t* pClient, rc_client_raintegration_get_game_name_func_t handler);
typedef void (RC_CCONV* rc_client_raintegration_set_event_handler_func_t)(rc_client_t* pClient, rc_client_raintegration_event_handler_t handler);
typedef void (RC_CCONV* rc_client_raintegration_set_int_func_t)(int);
typedef int (RC_CCONV* rc_client_raintegration_get_int_func_t)(void);
typedef struct rc_client_raintegration_t
{
HINSTANCE hDLL;
HWND hMainWindow;
HMENU hPopupMenu;
uint8_t bIsInited;
@ -32,14 +37,18 @@ typedef struct rc_client_raintegration_t
rc_client_raintegration_get_string_func_t get_host_url;
rc_client_raintegration_init_client_func_t init_client;
rc_client_raintegration_init_client_func_t init_client_offline;
rc_client_raintegration_set_int_func_t set_console_id;
rc_client_raintegration_action_func_t shutdown;
rc_client_raintegration_hwnd_action_func_t update_main_window_handle;
rc_client_raintegration_set_write_memory_func_t set_write_memory_function;
rc_client_raintegration_set_get_game_name_func_t set_get_game_name_function;
rc_client_raintegration_set_event_handler_func_t set_event_handler;
rc_client_raintegration_get_menu_func_t get_menu;
rc_client_raintegration_activate_menuitem_func_t activate_menu_item;
rc_client_raintegration_get_int_func_t has_modifications;
rc_client_raintegration_get_achievement_state_func_t get_achievement_state;
rc_client_raintegration_get_external_client_func_t get_external_client;

View File

@ -69,6 +69,7 @@ static const rc_disallowed_setting_t _rc_disallowed_ecwolf_settings[] = {
static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = {
{ "fbneo-allow-patched-romsets", "enabled" },
{ "fbneo-cheat-*", "!,Disabled,0 - Disabled" },
{ "fbneo-cpu-speed-adjust", "??%" }, /* disallow speeds under 100% */
{ "fbneo-dipswitch-*", "Universe BIOS*" },
{ "fbneo-neogeo-mode", "UNIBIOS" },
{ NULL, NULL }
@ -178,7 +179,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
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++))
if (tolower(c1) != tolower(c2 = *value++) && c2 != '?')
return (c2 == '*');
}

View File

@ -183,6 +183,9 @@ const char* rc_error_str(int ret)
case RC_ACCESS_DENIED: return "Access denied";
case RC_INVALID_CREDENTIALS: return "Invalid credentials";
case RC_EXPIRED_TOKEN: return "Expired token";
case RC_INSUFFICIENT_BUFFER: return "Buffer not large enough";
case RC_INVALID_VARIABLE_NAME: return "Invalid variable name";
case RC_UNKNOWN_VARIABLE_NAME: return "Unknown variable name";
default: return "Unknown error";
}
}

View File

@ -8,7 +8,7 @@
RC_BEGIN_C_DECLS
#define RCHEEVOS_VERSION_MAJOR 11
#define RCHEEVOS_VERSION_MINOR 1
#define RCHEEVOS_VERSION_MINOR 4
#define RCHEEVOS_VERSION_PATCH 0
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)

View File

@ -139,6 +139,18 @@ static int rc_parse_operator(const char** memaddr) {
++(*memaddr);
return RC_OPERATOR_XOR;
case '%':
++(*memaddr);
return RC_OPERATOR_MOD;
case '+':
++(*memaddr);
return RC_OPERATOR_ADD;
case '-':
++(*memaddr);
return RC_OPERATOR_SUB;
case '\0':/* end of string */
case '_': /* next condition */
case 'S': /* next condset */
@ -176,12 +188,13 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
case 'q': case 'Q': self->type = RC_CONDITION_MEASURED_IF; break;
case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; can_modify = 1; break;
case 't': case 'T': self->type = RC_CONDITION_TRIGGER; break;
case 'k': case 'K': self->type = RC_CONDITION_REMEMBER; can_modify = 1; break;
case 'z': case 'Z': self->type = RC_CONDITION_RESET_NEXT_IF; break;
case 'g': case 'G':
parse->measured_as_percent = 1;
self->type = RC_CONDITION_MEASURED;
break;
/* e f h j k l s u v w x y */
/* e f h j l s u v w x y */
default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0;
}
@ -226,6 +239,9 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
case RC_OPERATOR_DIV:
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_MOD:
case RC_OPERATOR_ADD:
case RC_OPERATOR_SUB:
/* modifying operators are only valid on modifying statements */
if (can_modify)
break;
@ -238,6 +254,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_SUB_SOURCE:
case RC_CONDITION_ADD_ADDRESS:
case RC_CONDITION_REMEMBER:
/* prevent parse errors on legacy achievements where a condition was present before changing the type */
self->oper = RC_OPERATOR_NONE;
break;
@ -551,5 +568,18 @@ 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_MOD:
rc_typed_value_modulus(value, &amount);
break;
case RC_OPERATOR_ADD:
rc_typed_value_add(value, &amount);
break;
case RC_OPERATOR_SUB:
rc_typed_value_negate(&amount);
rc_typed_value_add(value, &amount);
break;
}
}

View File

@ -53,6 +53,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
case RC_CONDITION_ADD_ADDRESS:
case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_SUB_SOURCE:
case RC_CONDITION_REMEMBER:
/* these conditions don't require a right hand size (implied *1) */
break;
@ -87,6 +88,9 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
case RC_OPERATOR_XOR:
case RC_OPERATOR_DIV:
case RC_OPERATOR_MULT:
case RC_OPERATOR_MOD:
case RC_OPERATOR_ADD:
case RC_OPERATOR_SUB:
case RC_OPERATOR_NONE:
/* measuring value. leave required_hits at 0 */
break;
@ -221,6 +225,15 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc
eval_state->add_address = value.value.u32;
continue;
case RC_CONDITION_REMEMBER:
rc_evaluate_condition_value(&value, condition, eval_state);
rc_typed_value_add(&value, &eval_state->add_value);
eval_state->recall_value.type = value.type;
eval_state->recall_value.value = value.value;
eval_state->add_value.type = RC_VALUE_TYPE_NONE;
eval_state->add_address = 0;
continue;
case RC_CONDITION_MEASURED:
if (condition->required_hits == 0 && can_measure) {
/* Measured condition without a hit target measures the value of the left operand */
@ -416,6 +429,10 @@ int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) {
return 1;
}
/* initialize recall value so each condition set has a functionally new recall accumulator */
eval_state->recall_value.type = RC_VALUE_TYPE_UNSIGNED;
eval_state->recall_value.value.u32 = 0;
if (self->has_pause) {
/* one or more Pause conditions exists, if any of them are true, stop processing this group */
self->is_paused = (char)rc_test_condset_internal(self, 1, eval_state);

View File

@ -368,9 +368,14 @@ static const rc_memory_regions_t rc_memory_regions_atari_lynx = { _rc_memory_reg
/* ===== ColecoVision ===== */
static const rc_memory_region_t _rc_memory_regions_colecovision[] = {
{ 0x000000U, 0x0003FFU, 0x006000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
/* "System RAM" refers to the main RAM at 0x6000-0x63FF. However, this RAM might not always be visible.
* If the Super Game Module (SGM) is active, then it might overlay its own RAM at 0x0000-0x1FFF and 0x2000-0x7FFF.
* These positions overlap the BIOS and System RAM, therefore we use virtual addresses for these memory spaces. */
{ 0x000000U, 0x0003FFU, 0x006000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x000400U, 0x0023FFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "SGM Low RAM" }, /* Normally situated at 0x0000-0x1FFF, which overlaps the BIOS */
{ 0x002400U, 0x0083FFU, 0x012000U, RC_MEMORY_TYPE_SYSTEM_RAM, "SGM High RAM" } /* Normally situated at 0x2000-0x7FFF, which overlaps System RAM */
};
static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_regions_colecovision, 1 };
static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_regions_colecovision, 3 };
/* ===== Commodore 64 ===== */
/* https://www.c64-wiki.com/wiki/Memory_Map */
@ -418,7 +423,7 @@ static const rc_memory_region_t _rc_memory_regions_fairchild_channel_f[] = {
};
static const rc_memory_regions_t rc_memory_regions_fairchild_channel_f = { _rc_memory_regions_fairchild_channel_f, 4 };
/* ===== GameBoy / GameBoy Color ===== */
/* ===== GameBoy / MegaDuck ===== */
static const rc_memory_region_t _rc_memory_regions_gameboy[] = {
{ 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt vector" },
{ 0x000100U, 0x00014FU, 0x000100U, RC_MEMORY_TYPE_READONLY, "Cartridge header" },
@ -427,8 +432,37 @@ static const rc_memory_region_t _rc_memory_regions_gameboy[] = {
{ 0x008000U, 0x0097FFU, 0x008000U, RC_MEMORY_TYPE_VIDEO_RAM, "Tile RAM" },
{ 0x009800U, 0x009BFFU, 0x009800U, RC_MEMORY_TYPE_VIDEO_RAM, "BG1 map data" },
{ 0x009C00U, 0x009FFFU, 0x009C00U, RC_MEMORY_TYPE_VIDEO_RAM, "BG2 map data" },
{ 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM"},
{ 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM (bank 0)"},
{ 0x00C000U, 0x00CFFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (fixed)" },
{ 0x00D000U, 0x00DFFFU, 0x00D000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (fixed)" },
{ 0x00E000U, 0x00FDFFU, 0x00C000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Echo RAM" },
{ 0x00FE00U, 0x00FE9FU, 0x00FE00U, RC_MEMORY_TYPE_VIDEO_RAM, "Sprite RAM"},
{ 0x00FEA0U, 0x00FEFFU, 0x00FEA0U, RC_MEMORY_TYPE_UNUSED, ""},
{ 0x00FF00U, 0x00FF7FU, 0x00FF00U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Hardware I/O"},
{ 0x00FF80U, 0x00FFFEU, 0x00FF80U, RC_MEMORY_TYPE_SYSTEM_RAM, "Quick RAM"},
{ 0x00FFFFU, 0x00FFFFU, 0x00FFFFU, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt enable"},
/* GameBoy's cartridge RAM may have a total of up to 16 banks that can be paged through $A000-$BFFF.
* It is desirable to always have access to these extra banks. We do this by expecting the extra banks
* to be addressable at addresses not supported by the native system. 0x10000-0x16000 is reserved
* for the extra banks of system memory that are exclusive to the GameBoy Color. */
{ 0x010000U, 0x015FFFU, 0x010000U, RC_MEMORY_TYPE_UNUSED, "Unused (GameBoy Color exclusive)" },
{ 0x016000U, 0x033FFFU, 0x016000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM (banks 1-15)" },
};
static const rc_memory_regions_t rc_memory_regions_megaduck = { _rc_memory_regions_gameboy, 16 };
static const rc_memory_regions_t rc_memory_regions_gameboy = { _rc_memory_regions_gameboy, 18 };
/* ===== GameBoy Color ===== */
static const rc_memory_region_t _rc_memory_regions_gameboy_color[] = {
{ 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt vector" },
{ 0x000100U, 0x00014FU, 0x000100U, RC_MEMORY_TYPE_READONLY, "Cartridge header" },
{ 0x000150U, 0x003FFFU, 0x000150U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM (fixed)" }, /* bank 0 */
{ 0x004000U, 0x007FFFU, 0x004000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM (paged)" }, /* bank 1-XX (switchable) */
{ 0x008000U, 0x0097FFU, 0x008000U, RC_MEMORY_TYPE_VIDEO_RAM, "Tile RAM" },
{ 0x009800U, 0x009BFFU, 0x009800U, RC_MEMORY_TYPE_VIDEO_RAM, "BG1 map data" },
{ 0x009C00U, 0x009FFFU, 0x009C00U, RC_MEMORY_TYPE_VIDEO_RAM, "BG2 map data" },
{ 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM (bank 0)"},
{ 0x00C000U, 0x00CFFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (bank 0)" },
{ 0x00D000U, 0x00DFFFU, 0x00D000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (bank 1)" },
{ 0x00E000U, 0x00FDFFU, 0x00C000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Echo RAM" },
{ 0x00FE00U, 0x00FE9FU, 0x00FE00U, RC_MEMORY_TYPE_VIDEO_RAM, "Sprite RAM"},
@ -437,14 +471,14 @@ static const rc_memory_region_t _rc_memory_regions_gameboy[] = {
{ 0x00FF80U, 0x00FFFEU, 0x00FF80U, RC_MEMORY_TYPE_SYSTEM_RAM, "Quick RAM"},
{ 0x00FFFFU, 0x00FFFFU, 0x00FFFFU, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt enable"},
/* GameBoy Color provides six extra banks of memory that can be paged out through the $DXXX
* memory space, but the timing of that does not correspond with blanks, which is when achievements
* are processed. As such, it is desirable to always have access to these extra banks. We do this
* by expecting the extra banks to be addressable at addresses not supported by the native system. */
{ 0x010000U, 0x015FFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (banks 2-7, GBC only)" }
/* GameBoy Color provides 6 extra banks of system memory that can be paged out through the $D000-$DFFF,
* and the cartridge RAM may have a total of up to 16 banks page through $A000-$BFFF.
* It is desirable to always have access to these extra banks. We do this by expecting the extra banks
* to be addressable at addresses not supported by the native system. */
{ 0x010000U, 0x015FFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM (banks 2-7)" },
{ 0x016000U, 0x033FFFU, 0x016000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM (banks 1-15)" },
};
static const rc_memory_regions_t rc_memory_regions_gameboy = { _rc_memory_regions_gameboy, 16 };
static const rc_memory_regions_t rc_memory_regions_gameboy_color = { _rc_memory_regions_gameboy, 17 };
static const rc_memory_regions_t rc_memory_regions_gameboy_color = { _rc_memory_regions_gameboy_color, 18 };
/* ===== GameBoy Advance ===== */
/* http://problemkaputt.de/gbatek-gba-memory-map.htm */
@ -463,11 +497,19 @@ static const rc_memory_region_t _rc_memory_regions_gamecube[] = {
static const rc_memory_regions_t rc_memory_regions_gamecube = { _rc_memory_regions_gamecube, 1 };
/* ===== Game Gear ===== */
/* http://www.smspower.org/Development/MemoryMap */
/* https://www.smspower.org/Development/MemoryMap */
/* https://www.smspower.org/Development/Mappers */
static const rc_memory_region_t _rc_memory_regions_game_gear[] = {
{ 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
{ 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
/* GG/SMS have various possible mappings for cartridge memory depending on the mapper used.
* However, these ultimately do not map all of their memory at once, typically requiring banking.
* Thus, the "real address" used is just a virtual address mapping all cartridge memory in one contiguous block.
* Note that this may possibly refer to non-battery backed "extended RAM" so this isn't strictly RC_MEMORY_TYPE_SAVE_RAM.
* libretro cores expose "extended RAM" as RETRO_MEMORY_SAVE_RAM regardless however.
*/
{ 0x002000U, 0x009FFFU, 0x010000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }
};
static const rc_memory_regions_t rc_memory_regions_game_gear = { _rc_memory_regions_game_gear, 1 };
static const rc_memory_regions_t rc_memory_regions_game_gear = { _rc_memory_regions_game_gear, 2 };
/* ===== Intellivision ===== */
/* http://wiki.intellivision.us/index.php/Memory_Map */
@ -531,14 +573,22 @@ static const rc_memory_region_t _rc_memory_regions_magnavox_odyssey_2[] = {
static const rc_memory_regions_t rc_memory_regions_magnavox_odyssey_2 = { _rc_memory_regions_magnavox_odyssey_2, 2 };
/* ===== Master System ===== */
/* http://www.smspower.org/Development/MemoryMap */
/* https://www.smspower.org/Development/MemoryMap */
/* https://www.smspower.org/Development/Mappers */
static const rc_memory_region_t _rc_memory_regions_master_system[] = {
{ 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
{ 0x000000U, 0x001FFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
/* GG/SMS have various possible mappings for cartridge memory depending on the mapper used.
* However, these ultimately do not map all of their memory at once, typically requiring banking.
* Thus, the "real address" used is just a virtual address mapping all cartridge memory in one contiguous block.
* Note that this may possibly refer to non-battery backed "extended RAM" so this isn't strictly RC_MEMORY_TYPE_SAVE_RAM.
* libretro cores expose "extended RAM" as RETRO_MEMORY_SAVE_RAM regardless however.
*/
{ 0x002000U, 0x009FFFU, 0x010000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }
};
static const rc_memory_regions_t rc_memory_regions_master_system = { _rc_memory_regions_master_system, 1 };
static const rc_memory_regions_t rc_memory_regions_master_system = { _rc_memory_regions_master_system, 2 };
/* ===== MegaDrive (Genesis) ===== */
/* http://www.smspower.org/Development/MemoryMap */
/* https://www.smspower.org/Development/MemoryMap */
static const rc_memory_region_t _rc_memory_regions_megadrive[] = {
{ 0x000000U, 0x00FFFFU, 0xFF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x010000U, 0x01FFFFU, 0x000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }
@ -725,10 +775,11 @@ static const rc_memory_regions_t rc_memory_regions_pcfx = { _rc_memory_regions_p
/* ===== PlayStation ===== */
/* http://www.raphnet.net/electronique/psx_adaptor/Playstation.txt */
static const rc_memory_region_t _rc_memory_regions_playstation[] = {
{ 0x000000U, 0x00FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" },
{ 0x010000U, 0x1FFFFFU, 0x010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
{ 0x000000U, 0x00FFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" },
{ 0x010000U, 0x1FFFFFU, 0x00010000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x200000U, 0x2003FFU, 0x1F800000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Scratchpad RAM" }
};
static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_regions_playstation, 2 };
static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_regions_playstation, 3 };
/* ===== PlayStation 2 ===== */
/* https://psi-rockin.github.io/ps2tek/ */
@ -772,7 +823,7 @@ static const rc_memory_region_t _rc_memory_regions_saturn[] = {
static const rc_memory_regions_t rc_memory_regions_saturn = { _rc_memory_regions_saturn, 2 };
/* ===== SG-1000 ===== */
/* http://www.smspower.org/Development/MemoryMap */
/* https://www.smspower.org/Development/MemoryMap */
static const rc_memory_region_t _rc_memory_regions_sg1000[] = {
{ 0x000000U, 0x0003FFU, 0xC000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
/* https://github.com/libretro/FBNeo/blob/697801c6262be6ca91615cf905444d3e039bc06f/src/burn/drv/sg1000/d_sg1000.cpp#L210-L237 */
@ -958,7 +1009,6 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id)
case RC_CONSOLE_FAIRCHILD_CHANNEL_F:
return &rc_memory_regions_fairchild_channel_f;
case RC_CONSOLE_MEGADUCK:
case RC_CONSOLE_GAMEBOY:
return &rc_memory_regions_gameboy;
@ -989,6 +1039,9 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id)
case RC_CONSOLE_MEGA_DRIVE:
return &rc_memory_regions_megadrive;
case RC_CONSOLE_MEGADUCK:
return &rc_memory_regions_megaduck;
case RC_CONSOLE_SEGA_32X:
return &rc_memory_regions_megadrive_32x;

View File

@ -95,6 +95,8 @@ int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address) {
switch (*aux++) {
case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break;
case 'b': case 'B': *size = RC_MEMSIZE_FLOAT_BE; break;
case 'h': case 'H': *size = RC_MEMSIZE_DOUBLE32; break;
case 'i': case 'I': *size = RC_MEMSIZE_DOUBLE32_BE; break;
case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break;
case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break;
@ -198,6 +200,29 @@ static void rc_transform_memref_float_be(rc_typed_value_t* value) {
value->type = RC_VALUE_TYPE_FLOAT;
}
static void rc_transform_memref_double32(rc_typed_value_t* value)
{
/* decodes the four most significant bytes of an IEEE 754 double into a float */
const uint32_t mantissa = (value->value.u32 & 0x000FFFFF) << 3;
const int32_t exponent = (int32_t)((value->value.u32 >> 20) & 0x7FF) - 1023;
const int sign = (value->value.u32 & 0x80000000);
value->value.f32 = rc_build_float(mantissa, exponent, sign);
value->type = RC_VALUE_TYPE_FLOAT;
}
static void rc_transform_memref_double32_be(rc_typed_value_t* value)
{
/* decodes the four most significant bytes of an IEEE 754 double in big endian format into a float */
const uint32_t mantissa = (((value->value.u32 & 0xFF000000) >> 24) |
((value->value.u32 & 0x00FF0000) >> 8) |
((value->value.u32 & 0x00000F00) << 8)) << 3;
const int32_t exponent = (int32_t)(((value->value.u32 & 0x0000007F) << 4) |
((value->value.u32 & 0x0000F000) >> 12)) - 1023;
const int sign = (value->value.u32 & 0x00000080);
value->value.f32 = rc_build_float(mantissa, exponent, sign);
value->type = RC_VALUE_TYPE_FLOAT;
}
static void rc_transform_memref_mbf32(rc_typed_value_t* value) {
/* decodes a Microsoft Binary Format float */
/* NOTE: 32-bit MBF is stored in memory as big endian (at least for Apple II) */
@ -322,6 +347,14 @@ void rc_transform_memref_value(rc_typed_value_t* value, uint8_t size) {
rc_transform_memref_float_be(value);
break;
case RC_MEMSIZE_DOUBLE32:
rc_transform_memref_double32(value);
break;
case RC_MEMSIZE_DOUBLE32_BE:
rc_transform_memref_double32_be(value);
break;
case RC_MEMSIZE_MBF32:
rc_transform_memref_mbf32(value);
break;
@ -358,6 +391,8 @@ static const uint32_t rc_memref_masks[] = {
0xffffffff, /* RC_MEMSIZE_MBF32 */
0xffffffff, /* RC_MEMSIZE_MBF32_LE */
0xffffffff, /* RC_MEMSIZE_FLOAT_BE */
0xffffffff, /* RC_MEMSIZE_DOUBLE32 */
0xffffffff, /* RC_MEMSIZE_DOUBLE32_BE*/
0xffffffff /* RC_MEMSIZE_VARIABLE */
};
@ -395,6 +430,8 @@ static const uint8_t rc_memref_shared_sizes[] = {
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT_BE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_DOUBLE32 */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_DOUBLE32_BE*/
RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */
};

View File

@ -3,6 +3,7 @@
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
#ifndef RC_DISABLE_LUA
@ -64,6 +65,37 @@ static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_par
return RC_OK;
}
static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr) {
const char* aux = *memaddr;
size_t i;
char varName[RC_VALUE_MAX_NAME_LENGTH + 1] = { 0 };
for (i = 0; i < RC_VALUE_MAX_NAME_LENGTH && *aux != '}'; i++) {
if (!rc_is_valid_variable_character(*aux, i == 0))
return RC_INVALID_VARIABLE_NAME;
varName[i] = *aux++;
}
if (i == 0)
return RC_INVALID_VARIABLE_NAME;
if (*aux != '}')
return RC_INVALID_VARIABLE_NAME;
++aux;
if (strcmp(varName, "recall") == 0) {
self->type = RC_OPERAND_RECALL;
}
else { /* process named variable when feature is available.*/
return RC_UNKNOWN_VARIABLE_NAME;
}
*memaddr = aux;
return RC_OK;
}
static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect) {
const char* aux = *memaddr;
uint32_t address;
@ -231,6 +263,13 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
self->value.num = (unsigned)value;
}
break;
case '{': /* variable */
++aux;
ret = rc_parse_operand_variable(self, &aux);
if (ret < 0)
return ret;
break;
case '0':
if (aux[1] == 'x' || aux[1] == 'X') { /* hex integer constant */
@ -297,6 +336,8 @@ int rc_operand_is_float_memref(const rc_operand_t* self) {
switch (self->size) {
case RC_MEMSIZE_FLOAT:
case RC_MEMSIZE_FLOAT_BE:
case RC_MEMSIZE_DOUBLE32:
case RC_MEMSIZE_DOUBLE32_BE:
case RC_MEMSIZE_MBF32:
case RC_MEMSIZE_MBF32_LE:
return 1;
@ -311,6 +352,7 @@ int rc_operand_is_memref(const rc_operand_t* self) {
case RC_OPERAND_CONST:
case RC_OPERAND_FP:
case RC_OPERAND_LUA:
case RC_OPERAND_RECALL:
return 0;
default:
@ -318,6 +360,16 @@ int rc_operand_is_memref(const rc_operand_t* self) {
}
}
int rc_operand_is_recall(const rc_operand_t* self) {
switch (self->type) {
case RC_OPERAND_RECALL:
return 1;
default:
return 0;
}
}
int rc_operand_is_float(const rc_operand_t* self) {
if (self->type == RC_OPERAND_FP)
return 1;
@ -460,6 +512,11 @@ void rc_evaluate_operand(rc_typed_value_t* result, rc_operand_t* self, rc_eval_s
break;
case RC_OPERAND_RECALL:
result->type = eval_state->recall_value.type;
result->value = eval_state->recall_value.value;
return;
default:
result->type = RC_VALUE_TYPE_UNSIGNED;
result->value.u32 = rc_get_memref_value(self->value.memref, self->type, eval_state);

View File

@ -90,6 +90,7 @@ typedef struct {
lua_State* L;
rc_typed_value_t measured_value; /* Measured */
rc_typed_value_t recall_value; /* Set by RC_CONDITION_REMEMBER */
uint8_t was_reset; /* ResetIf triggered */
uint8_t has_hits; /* one of more hit counts is non-zero */
uint8_t primed; /* true if all non-Trigger conditions are true */
@ -169,7 +170,9 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
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);
int rc_operand_is_recall(const rc_operand_t* self);
int rc_is_valid_variable_character(char ch, int is_first);
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);
@ -181,6 +184,7 @@ void rc_typed_value_convert(rc_typed_value_t* value, char new_type);
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount);
void rc_typed_value_multiply(rc_typed_value_t* value, const rc_typed_value_t* amount);
void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amount);
void rc_typed_value_modulus(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

@ -143,6 +143,31 @@ static uint32_t rc_scale_value(uint32_t value, uint8_t oper, const rc_operand_t*
case RC_OPERATOR_XOR:
return value | rc_max_value(operand);
case RC_OPERATOR_MOD:
{
const uint32_t divisor = (operand->type == RC_OPERAND_CONST) ? operand->value.num : 1;
return (divisor >= value) ? (divisor - 1) : value;
}
case RC_OPERATOR_ADD:
{
unsigned long scaled = ((unsigned long)value) + rc_max_value(operand);
if (scaled > 0xFFFFFFFF)
return 0xFFFFFFFF;
return (uint32_t)scaled;
}
case RC_OPERATOR_SUB:
{
if (operand->type == RC_OPERAND_CONST)
return value - operand->value.num;
else if (value > rc_max_value(operand))
return value - rc_max_value(operand);
return 0xFFFFFFFF;
}
default:
return value;
}
@ -241,6 +266,8 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con
int in_add_hits = 0;
int in_add_address = 0;
int is_combining = 0;
int remember_used = 0;
int remember_used_in_pause = 0;
if (!condset) {
*result = '\0';
@ -251,6 +278,7 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con
uint32_t 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);
const int uses_recall = rc_operand_is_recall(&cond->operand1) || rc_operand_is_recall(&cond->operand2);
if (!in_add_address) {
if (is_memref1 && !rc_validate_memref(cond->operand1.value.memref, buffer, sizeof(buffer), console_id, max_address)) {
@ -266,6 +294,28 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con
in_add_address = 0;
}
if (!remember_used && uses_recall) {
if (!cond->pause && condset->has_pause) {
/* pause conditions will be processed before non-pause conditions.
* scan forward for any remembers in yet-to-be-processed pause conditions */
const rc_condition_t* cond_rem_pause_check = cond->next;
for (; cond_rem_pause_check; cond_rem_pause_check = cond_rem_pause_check->next) {
if (cond_rem_pause_check->type == RC_CONDITION_REMEMBER && cond_rem_pause_check->pause) {
remember_used = 1; /* do not set remember_used_in_pause here because we don't know at which poing in the pause processing this remember is occurring. */
break;
}
}
}
if (!remember_used) {
snprintf(result, result_size, "Condition %d: Recall used before Remember", index);
return 0;
}
}
else if (cond->pause && uses_recall && !remember_used_in_pause) {
snprintf(result, result_size, "Condition %d: Recall used in Pause processing before Remember was used in Pause processing", index);
return 0;
}
switch (cond->type) {
case RC_CONDITION_ADD_SOURCE:
max = rc_scale_value(max, cond->oper, &cond->operand2);
@ -289,6 +339,12 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con
is_combining = 1;
continue;
case RC_CONDITION_REMEMBER:
is_combining = 1;
remember_used = 1;
remember_used_in_pause += cond->pause;
continue;
case RC_CONDITION_ADD_HITS:
case RC_CONDITION_SUB_HITS:
in_add_hits = 1;
@ -337,49 +393,68 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con
return 0;
}
if (is_memref1 && rc_operand_is_float(&cond->operand1)) {
/* if left side is a float, right side will be converted to a float, so don't do range validation */
}
else if (is_memref1 || is_memref2 || add_source_max) {
/* 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);
const rc_operand_t* operand1 = &cond->operand1;
const rc_operand_t* operand2 = &cond->operand2;
uint8_t oper = cond->oper;
uint32_t min_val;
switch (cond->operand2.type) {
if (!is_memref1 && !add_source_max) {
/* pretend constant was on right side */
operand1 = &cond->operand2;
operand2 = &cond->operand1;
max = max_val;
switch (oper) {
case RC_OPERATOR_LT: oper = RC_OPERATOR_GT; break;
case RC_OPERATOR_LE: oper = RC_OPERATOR_GE; break;
case RC_OPERATOR_GT: oper = RC_OPERATOR_LT; break;
case RC_OPERATOR_GE: oper = RC_OPERATOR_LE; break;
}
}
switch (operand2->type) {
case RC_OPERAND_CONST:
min_val = cond->operand2.value.num;
min_val = operand2->value.num;
break;
case RC_OPERAND_FP:
min_val = (int)cond->operand2.value.dbl;
min_val = (int)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) {
if (!add_source_max && !rc_operand_is_float_memref(operand1) &&
(float)min_val != operand2->value.dbl) {
switch (oper) {
case RC_OPERATOR_EQ:
snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true");
return 0;
case RC_OPERATOR_NE:
snprintf(result + prefix_length, result_size - prefix_length, "Comparison is always true");
return 0;
case RC_OPERATOR_GT: /* value could be greater than floor(float) */
case RC_OPERATOR_LE: /* value could be less than or equal to floor(float) */
break;
case RC_OPERATOR_GE: /* value could be greater than or equal to ceil(float) */
case RC_OPERATOR_LT: /* value could be less than ceil(float) */
++min_val;
break;
}
}
break;
default:
default: /* right side is memref or add source chain */
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)) {
if (!rc_validate_range(min_val, max_val, oper, max, result + prefix_length, result_size - prefix_length))
return 0;
}
}
add_source_max = 0;
}
@ -416,6 +491,7 @@ static int rc_validate_is_combining_condition(const rc_condition_t* condition)
case RC_CONDITION_RESET_NEXT_IF:
case RC_CONDITION_SUB_HITS:
case RC_CONDITION_SUB_SOURCE:
case RC_CONDITION_REMEMBER:
return 1;
default:
@ -423,22 +499,6 @@ static int rc_validate_is_combining_condition(const rc_condition_t* condition)
}
}
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)
@ -637,6 +697,24 @@ static int rc_validate_comparison_overlap(int comparison1, uint32_t value1, int
return RC_OVERLAP_NONE;
}
static int rc_validate_are_operands_equal(const rc_operand_t* oper1, const rc_operand_t* oper2)
{
if (oper1->type != oper2->type)
return 0;
switch (oper1->type)
{
case RC_OPERAND_CONST:
return (oper1->value.num == oper2->value.num);
case RC_OPERAND_FP:
return (oper1->value.dbl == oper2->value.dbl);
case RC_OPERAND_RECALL:
return (oper2->type == RC_OPERAND_RECALL);
default:
return (oper1->value.memref->address == oper2->value.memref->address && oper1->size == oper2->size);
}
}
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)
{
@ -646,6 +724,7 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
const rc_operand_t* operand2;
const rc_condition_t* compare_condition;
const rc_condition_t* condition;
const rc_condition_t* condition_chain_start;
int overlap;
/* empty group */
@ -653,9 +732,14 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
return 1;
/* outer loop is the source conditions */
for (condition = conditions->conditions; condition != NULL;
condition = rc_validate_next_non_combining_condition(condition))
for (condition = conditions->conditions; condition != NULL; condition = condition->next)
{
condition_chain_start = condition;
while (rc_condition_is_combining(condition))
condition = condition->next;
if (!condition)
break;
/* hits can be captured at any time, so any potential conflict will not be conflicting at another time */
if (condition->required_hits)
continue;
@ -680,11 +764,62 @@ static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, co
}
/* 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))
for (compare_condition = compare_conditions->conditions; compare_condition != NULL; compare_condition = compare_condition->next)
{
if (compare_condition == condition)
if (compare_condition == condition_chain_start)
{
/* skip condition we're already looking at */
while (compare_condition != condition)
compare_condition = compare_condition->next;
continue;
}
/* if combining conditions exist, make sure the same combining conditions exist in the
* compare logic. conflicts can only occur if the combinining conditions match. */
if (condition_chain_start != condition)
{
int chain_matches = 1;
const rc_condition_t* condition_chain_iter = condition_chain_start;
while (condition_chain_iter != condition)
{
if (compare_condition->type != condition_chain_iter->type ||
compare_condition->oper != condition_chain_iter->oper ||
compare_condition->required_hits != condition_chain_iter->required_hits ||
!rc_validate_are_operands_equal(&compare_condition->operand1, &condition_chain_iter->operand1))
{
chain_matches = 0;
break;
}
if (compare_condition->oper != RC_OPERATOR_NONE &&
!rc_validate_are_operands_equal(&compare_condition->operand2, &condition_chain_iter->operand2))
{
if (compare_condition->operand2.type != condition_chain_iter->operand2.type)
{
chain_matches = 0;
break;
}
}
if (!compare_condition->next)
{
chain_matches = 0;
break;
}
compare_condition = compare_condition->next;
condition_chain_iter = condition_chain_iter->next;
}
/* combining field didn't match, or there's more unmatched combining fields. ignore this condition */
if (!chain_matches || rc_validate_is_combining_condition(compare_condition))
{
while (compare_condition->next && rc_validate_is_combining_condition(compare_condition))
compare_condition = compare_condition->next;
continue;
}
}
if (compare_condition->required_hits)
continue;
@ -801,8 +936,7 @@ static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result
const rc_condset_t* alt;
int index;
if (!trigger->alternative)
{
if (!trigger->alternative) {
if (!rc_validate_condset_internal(trigger->requirement, result, result_size, console_id, max_address))
return 0;

View File

@ -4,6 +4,7 @@
#include "rc_util.h"
#include "../rhash/md5.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
@ -17,17 +18,22 @@
#define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */
#define RC_RUNTIME_MIN_BUFFER_SIZE 4 + 8 + 16 /* RUNTIME_MARKER, CHUNK_DONE, MD5 */
typedef struct rc_runtime_progress_t {
const rc_runtime_t* runtime;
uint32_t offset;
uint8_t* buffer;
uint32_t buffer_size;
uint32_t chunk_size_offset;
lua_State* L;
} rc_runtime_progress_t;
#define assert_chunk_size(expected_size) assert((uint32_t)(progress->offset - progress->chunk_size_offset - 4) == (uint32_t)(expected_size))
#define RC_TRIGGER_STATE_UNUPDATED 0x7F
#define RC_MEMREF_FLAG_CHANGED_THIS_FRAME 0x00010000
@ -117,21 +123,29 @@ static void rc_runtime_progress_init(rc_runtime_progress_t* progress, const rc_r
progress->L = L;
}
#define RC_RUNTIME_SERIALIZED_MEMREF_SIZE 16 /* 4x uint: address, flags, value, prior */
static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
{
rc_memref_t* memref = progress->runtime->memrefs;
uint32_t flags = 0;
rc_memref_t* memref;
uint32_t count = 0;
for (memref = progress->runtime->memrefs; memref; memref = memref->next)
++count;
if (count == 0)
return RC_OK;
if (progress->offset + 8 + count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_MEMREFS);
if (!progress->buffer) {
while (memref) {
progress->offset += 16;
memref = memref->next;
}
progress->offset += count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE;
}
else {
while (memref) {
uint32_t flags = 0;
for (memref = progress->runtime->memrefs; memref; memref = memref->next) {
flags = memref->value.size;
if (memref->value.changed)
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
@ -140,11 +154,10 @@ static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
rc_runtime_progress_write_uint(progress, flags);
rc_runtime_progress_write_uint(progress, memref->value.value);
rc_runtime_progress_write_uint(progress, memref->value.prior);
memref = memref->next;
}
}
assert_chunk_size(count * RC_RUNTIME_SERIALIZED_MEMREF_SIZE);
rc_runtime_progress_end_chunk(progress);
return RC_OK;
}
@ -159,7 +172,7 @@ static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress)
/* re-read the chunk size to determine how many memrefs are present */
progress->offset -= 4;
entries = rc_runtime_progress_read_uint(progress) / 16;
entries = rc_runtime_progress_read_uint(progress) / RC_RUNTIME_SERIALIZED_MEMREF_SIZE;
while (entries != 0) {
address = rc_runtime_progress_read_uint(progress);
@ -197,6 +210,7 @@ static int rc_runtime_progress_is_indirect_memref(rc_operand_t* oper)
{
case RC_OPERAND_CONST:
case RC_OPERAND_FP:
case RC_OPERAND_RECALL:
case RC_OPERAND_LUA:
return 0;
@ -210,6 +224,9 @@ static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc
rc_condition_t* cond;
uint32_t flags;
if (progress->offset + 4 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, condset->is_paused);
cond = condset->conditions;
@ -230,15 +247,24 @@ static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc
flags |= RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME;
}
if (progress->offset + 8 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, cond->current_hits);
rc_runtime_progress_write_uint(progress, flags);
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
if (progress->offset + 8 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.value);
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.prior);
}
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
if (progress->offset + 8 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.value);
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.prior);
}
@ -310,6 +336,9 @@ static int rc_runtime_progress_write_variable(rc_runtime_progress_t* progress, c
{
uint32_t flags;
if (progress->offset + 12 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
flags = rc_runtime_progress_should_serialize_variable_condset(variable->conditions);
if (variable->value.changed)
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
@ -331,21 +360,30 @@ static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress)
{
uint32_t count = 0;
const rc_value_t* variable;
int result;
for (variable = progress->runtime->variables; variable; variable = variable->next)
++count;
if (count == 0)
return RC_OK;
/* header + count + count(djb2,flags,value,prior,?cond) */
if (progress->offset + 8 + 4 + count * 16 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_VARIABLES);
rc_runtime_progress_write_uint(progress, count);
for (variable = progress->runtime->variables; variable; variable = variable->next)
{
for (variable = progress->runtime->variables; variable; variable = variable->next) {
uint32_t djb2 = rc_djb2(variable->name);
if (progress->offset + 16 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, djb2);
rc_runtime_progress_write_variable(progress, variable);
result = rc_runtime_progress_write_variable(progress, variable);
if (result != RC_OK)
return result;
}
rc_runtime_progress_end_chunk(progress);
@ -493,7 +531,7 @@ static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_
static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progress)
{
uint32_t i;
int offset = 0;
int initial_offset = 0;
int result;
for (i = 0; i < progress->runtime->trigger_count; ++i) {
@ -511,7 +549,10 @@ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progres
continue;
}
offset = progress->offset;
initial_offset = progress->offset;
} else {
if (progress->offset + runtime_trigger->serialized_size > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
}
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_ACHIEVEMENT);
@ -522,10 +563,15 @@ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progres
if (result != RC_OK)
return result;
if (runtime_trigger->serialized_size) {
/* runtime_trigger->serialized_size includes the header */
assert_chunk_size(runtime_trigger->serialized_size - 8);
}
rc_runtime_progress_end_chunk(progress);
if (!progress->buffer)
runtime_trigger->serialized_size = progress->offset - offset;
runtime_trigger->serialized_size = progress->offset - initial_offset;
}
return RC_OK;
@ -556,7 +602,7 @@ static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progres
{
uint32_t i;
uint32_t flags;
int offset = 0;
int initial_offset = 0;
int result;
for (i = 0; i < progress->runtime->lboard_count; ++i) {
@ -574,7 +620,10 @@ static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progres
continue;
}
offset = progress->offset;
initial_offset = progress->offset;
} else {
if (progress->offset + runtime_lboard->serialized_size > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
}
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_LEADERBOARD);
@ -600,10 +649,15 @@ static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progres
if (result != RC_OK)
return result;
if (runtime_lboard->serialized_size) {
/* runtime_lboard->serialized_size includes the header */
assert_chunk_size(runtime_lboard->serialized_size - 8);
}
rc_runtime_progress_end_chunk(progress);
if (!progress->buffer)
runtime_lboard->serialized_size = progress->offset - offset;
runtime_lboard->serialized_size = progress->offset - initial_offset;
}
return RC_OK;
@ -663,6 +717,9 @@ static int rc_runtime_progress_write_rich_presence(rc_runtime_progress_t* progre
if (!display->next)
return RC_OK;
if (progress->offset + 8 + 16 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_RICHPRESENCE);
rc_runtime_progress_write_md5(progress, progress->runtime->richpresence->md5);
@ -705,6 +762,9 @@ static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progres
uint8_t md5[16];
int result;
if (progress->buffer_size < RC_RUNTIME_MIN_BUFFER_SIZE)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, RC_RUNTIME_MARKER);
if ((result = rc_runtime_progress_write_memrefs(progress)) != RC_OK)
@ -722,6 +782,9 @@ static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progres
if ((result = rc_runtime_progress_write_rich_presence(progress)) != RC_OK)
return result;
if (progress->offset + 8 + 16 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, RC_RUNTIME_CHUNK_DONE);
rc_runtime_progress_write_uint(progress, 16);
@ -736,12 +799,13 @@ static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progres
return RC_OK;
}
int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
{
rc_runtime_progress_t progress;
int result;
rc_runtime_progress_init(&progress, runtime, L);
progress.buffer_size = 0xFFFFFFFF;
result = rc_runtime_progress_serialize_internal(&progress);
if (result != RC_OK)
@ -751,6 +815,11 @@ int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
}
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L)
{
return rc_runtime_serialize_progress_sized(buffer, 0xFFFFFFFF, runtime, L);
}
int rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, lua_State* L)
{
rc_runtime_progress_t progress;
@ -759,11 +828,17 @@ int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua
rc_runtime_progress_init(&progress, runtime, L);
progress.buffer = (uint8_t*)buffer;
progress.buffer_size = buffer_size;
return rc_runtime_progress_serialize_internal(&progress);
}
int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L)
{
return rc_runtime_deserialize_progress_sized(runtime, serialized, 0xFFFFFFFF, L);
}
int rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, lua_State* L)
{
rc_runtime_progress_t progress;
md5_state_t state;
@ -775,9 +850,9 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serial
int seen_rich_presence = 0;
int result = RC_OK;
if (!serialized) {
if (!serialized || serialized_size < RC_RUNTIME_MIN_BUFFER_SIZE) {
rc_runtime_reset(runtime);
return RC_INVALID_STATE;
return RC_INSUFFICIENT_BUFFER;
}
rc_runtime_progress_init(&progress, runtime, L);
@ -813,12 +888,21 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serial
}
do {
if (progress.offset + 8 >= serialized_size) {
result = RC_INSUFFICIENT_BUFFER;
break;
}
chunk_id = rc_runtime_progress_read_uint(&progress);
chunk_size = rc_runtime_progress_read_uint(&progress);
next_chunk_offset = progress.offset + chunk_size;
switch (chunk_id)
{
if (next_chunk_offset > serialized_size) {
result = RC_INSUFFICIENT_BUFFER;
break;
}
switch (chunk_id) {
case RC_RUNTIME_CHUNK_MEMREFS:
result = rc_runtime_progress_read_memrefs(&progress);
break;

View File

@ -3,6 +3,21 @@
#include <string.h> /* memset */
#include <ctype.h> /* isdigit */
#include <float.h> /* FLT_EPSILON */
#include <math.h> /* fmod */
int rc_is_valid_variable_character(char ch, int is_first) {
if (is_first) {
if (!isalpha((unsigned char)ch))
return 0;
}
else {
if (!isalnum((unsigned char)ch))
return 0;
}
return 1;
}
static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
rc_condset_t** next_clause;
@ -112,6 +127,9 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat
case RC_OPERATOR_DIV:
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_MOD:
case RC_OPERATOR_ADD:
case RC_OPERATOR_SUB:
case RC_OPERATOR_NONE:
break;
@ -628,6 +646,72 @@ void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amou
value->value.f32 /= amount->value.f32;
}
void rc_typed_value_modulus(rc_typed_value_t* value, const rc_typed_value_t* amount) {
rc_typed_value_t converted;
switch (amount->type)
{
case RC_VALUE_TYPE_UNSIGNED:
if (amount->value.u32 == 0) { /* divide by zero */
value->type = RC_VALUE_TYPE_NONE;
return;
}
switch (value->type) {
case RC_VALUE_TYPE_UNSIGNED: /* integer math */
value->value.u32 %= amount->value.u32;
return;
case RC_VALUE_TYPE_SIGNED: /* integer math */
value->value.i32 %= (int)amount->value.u32;
return;
case RC_VALUE_TYPE_FLOAT:
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
break;
case RC_VALUE_TYPE_SIGNED:
if (amount->value.i32 == 0) { /* divide by zero */
value->type = RC_VALUE_TYPE_NONE;
return;
}
switch (value->type) {
case RC_VALUE_TYPE_SIGNED: /* integer math */
value->value.i32 %= amount->value.i32;
return;
case RC_VALUE_TYPE_UNSIGNED: /* integer math */
value->value.u32 %= (unsigned)amount->value.i32;
return;
case RC_VALUE_TYPE_FLOAT:
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
break;
case RC_VALUE_TYPE_FLOAT:
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
if (amount->value.f32 == 0.0) { /* divide by zero */
value->type = RC_VALUE_TYPE_NONE;
return;
}
rc_typed_value_convert(value, RC_VALUE_TYPE_FLOAT);
value->value.f32 = (float)fmod(value->value.f32, amount->value.f32);
}
static int rc_typed_value_compare_floats(float f1, float f2, char oper) {
if (f1 == f2) {
/* exactly equal */
@ -683,9 +767,14 @@ static int rc_typed_value_compare_floats(float f1, float f2, char oper) {
}
int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper) {
rc_typed_value_t converted_value2;
if (value2->type != value1->type)
value2 = rc_typed_value_convert_into(&converted_value2, value2, value1->type);
rc_typed_value_t converted_value;
if (value2->type != value1->type) {
/* if either side is a float, convert both sides to float. otherwise, assume the signed-ness of the left side. */
if (value2->type == RC_VALUE_TYPE_FLOAT)
value1 = rc_typed_value_convert_into(&converted_value, value1, value2->type);
else
value2 = rc_typed_value_convert_into(&converted_value, value2, value1->type);
}
switch (value1->type) {
case RC_VALUE_TYPE_UNSIGNED:

View File

@ -10,6 +10,7 @@
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <share.h>
#endif
/* arbitrary limit to prevent allocating and hashing large files */
@ -52,9 +53,9 @@ static void rc_hash_verbose(const char* message)
static struct rc_hash_filereader filereader_funcs;
static struct rc_hash_filereader* filereader = NULL;
#if defined(WINVER) && WINVER >= 0x0500
static void* filereader_open(const char* path)
{
#if defined(WINVER) && WINVER >= 0x0500
/* Windows requires using wchar APIs for Unicode paths */
/* Note that MultiByteToWideChar will only be defined for >= Windows 2000 */
wchar_t* wpath;
@ -75,21 +76,34 @@ static void* filereader_open(const char* path)
free(wpath);
return NULL;
}
#if defined(__STDC_WANT_SECURE_LIB__)
_wfopen_s(&fp, wpath, L"rb");
/* have to use _SH_DENYNO because some cores lock the file while its loaded */
fp = _wfsopen(wpath, L"rb", _SH_DENYNO);
#else
fp = _wfopen(wpath, L"rb");
#endif
free(wpath);
return fp;
#elif defined(__STDC_WANT_SECURE_LIB__)
}
#else /* !WINVER >= 0x0500 */
static void* filereader_open(const char* path)
{
#if defined(__STDC_WANT_SECURE_LIB__)
#if defined(WINVER)
/* have to use _SH_DENYNO because some cores lock the file while its loaded */
return _fsopen(path, "rb", _SH_DENYNO);
#else /* !WINVER */
FILE *fp;
fopen_s(&fp, path, "rb");
return fp;
#else
#endif
#else /* !__STDC_WANT_SECURE_LIB__ */
return fopen(path, "rb");
#endif
}
#endif /* WINVER >= 0x0500 */
static void filereader_seek(void* file_handle, int64_t offset, int origin)
{
@ -818,12 +832,18 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
uint32_t filename_len = RC_ZIP_READ_LE16(cdir + 0x1C);
int32_t extra_len = RC_ZIP_READ_LE16(cdir + 0x1E);
int32_t comment_len = RC_ZIP_READ_LE16(cdir + 0x20);
int32_t external_attr = RC_ZIP_READ_LE16(cdir + 0x26);
uint64_t local_hdr_ofs = RC_ZIP_READ_LE32(cdir + 0x2A);
cdir_entry_len = cdirhdr_size + filename_len + extra_len + comment_len;
if (signature != 0x02014b50) /* expected central directory entry signature */
break;
/* Ignore records describing a directory (we only hash file records) */
name = (cdir + cdirhdr_size);
if (name[filename_len - 1] == '/' || name[filename_len - 1] == '\\' || (external_attr & 0x10))
continue;
/* Handle Zip64 fields */
if (decomp_size == 0xFFFFFFFF || comp_size == 0xFFFFFFFF || local_hdr_ofs == 0xFFFFFFFF)
{
@ -877,7 +897,7 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
hashindex++;
/* Convert and store the file name in the hash data buffer */
for (name = (cdir + cdirhdr_size), name_end = name + filename_len; name != name_end; name++)
for (name_end = name + filename_len; name != name_end; name++)
{
*(hashdata++) =
(*name == '\\' ? '/' : /* convert back-slashes to regular slashes */
@ -972,11 +992,12 @@ static int rc_hash_arcade(char hash[33], const char* path)
/* arcade hash is just the hash of the filename (no extension) - the cores are pretty stringent about having the right ROM data */
const char* filename = rc_path_get_filename(path);
const char* ext = rc_path_get_extension(filename);
char buffer[128]; /* realistically, this should never need more than ~32 characters */
size_t filename_length = ext - filename - 1;
/* fbneo supports loading subsystems by using specific folder names.
* if one is found, include it in the hash.
* https://github.com/libretro/FBNeo/blob/master/src/burner/libretro/README.md#emulating-consoles
* https://github.com/libretro/FBNeo/blob/master/src/burner/libretro/README.md#emulating-consoles-and-computers
*/
if (filename > path + 1)
{
@ -993,31 +1014,67 @@ static int rc_hash_arcade(char hash[33], const char* path)
} while (folder > path);
parent_folder_length = filename - folder - 1;
if (parent_folder_length < 16)
{
char* ptr = buffer;
while (folder < filename - 1)
*ptr++ = tolower(*folder++);
*ptr = '\0';
folder = buffer;
}
switch (parent_folder_length)
{
case 3:
if (memcmp(folder, "nes", 3) == 0 ||
memcmp(folder, "fds", 3) == 0 ||
memcmp(folder, "sms", 3) == 0 ||
memcmp(folder, "msx", 3) == 0 ||
memcmp(folder, "ngp", 3) == 0 ||
memcmp(folder, "pce", 3) == 0 ||
memcmp(folder, "sgx", 3) == 0)
if (memcmp(folder, "nes", 3) == 0 || /* NES */
memcmp(folder, "fds", 3) == 0 || /* FDS */
memcmp(folder, "sms", 3) == 0 || /* Master System */
memcmp(folder, "msx", 3) == 0 || /* MSX */
memcmp(folder, "ngp", 3) == 0 || /* NeoGeo Pocket */
memcmp(folder, "pce", 3) == 0 || /* PCEngine */
memcmp(folder, "chf", 3) == 0 || /* ChannelF */
memcmp(folder, "sgx", 3) == 0) /* SuperGrafX */
include_folder = 1;
break;
case 4:
if (memcmp(folder, "tg16", 4) == 0)
if (memcmp(folder, "tg16", 4) == 0 || /* TurboGrafx-16 */
memcmp(folder, "msx1", 4) == 0) /* MSX */
include_folder = 1;
break;
case 5:
if (memcmp(folder, "neocd", 5) == 0) /* NeoGeo CD */
include_folder = 1;
break;
case 6:
if (memcmp(folder, "coleco", 6) == 0 ||
memcmp(folder, "sg1000", 6) == 0)
if (memcmp(folder, "coleco", 6) == 0 || /* Colecovision */
memcmp(folder, "sg1000", 6) == 0) /* SG-1000 */
include_folder = 1;
break;
case 7:
if (memcmp(folder, "genesis", 7) == 0) /* Megadrive (Genesis) */
include_folder = 1;
break;
case 8:
if (memcmp(folder, "gamegear", 8) == 0 ||
memcmp(folder, "megadriv", 8) == 0 ||
memcmp(folder, "spectrum", 8) == 0)
if (memcmp(folder, "gamegear", 8) == 0 || /* Game Gear */
memcmp(folder, "megadriv", 8) == 0 || /* Megadrive */
memcmp(folder, "pcengine", 8) == 0 || /* PCEngine */
memcmp(folder, "channelf", 8) == 0 || /* ChannelF */
memcmp(folder, "spectrum", 8) == 0) /* ZX Spectrum */
include_folder = 1;
break;
case 9:
if (memcmp(folder, "megadrive", 9) == 0) /* Megadrive */
include_folder = 1;
break;
case 10:
if (memcmp(folder, "supergrafx", 10) == 0 || /* SuperGrafX */
memcmp(folder, "zxspectrum", 10) == 0) /* ZX Spectrum */
include_folder = 1;
break;
case 12:
if (memcmp(folder, "mastersystem", 12) == 0 || /* Master System */
memcmp(folder, "colecovision", 12) == 0) /* Colecovision */
include_folder = 1;
break;
default:
@ -1026,10 +1083,8 @@ static int rc_hash_arcade(char hash[33], const char* path)
if (include_folder)
{
char buffer[128]; /* realistically, this should never need more than ~20 characters */
if (parent_folder_length + filename_length + 1 < sizeof(buffer))
{
memcpy(&buffer[0], folder, parent_folder_length);
buffer[parent_folder_length] = '_';
memcpy(&buffer[parent_folder_length + 1], filename, filename_length);
return rc_hash_buffer(hash, (uint8_t*)&buffer[0], parent_folder_length + filename_length + 1);
@ -2121,6 +2176,14 @@ static int rc_hash_psp(char hash[33], const char* path)
uint32_t size;
md5_state_t md5;
/* https://www.psdevwiki.com/psp/PBP
* A PBP file is an archive containing the PARAM.SFO, primary executable, and a bunch of metadata.
* While we could extract the PARAM.SFO and primary executable to mimic the normal PSP hashing logic,
* it's easier to just hash the entire file. This also helps alleviate issues where the primary
* executable is just a game engine and the only differentiating data would be the metadata. */
if (rc_path_compare_extension(path, "pbp"))
return rc_hash_whole_file(hash, path);
track_handle = rc_cd_open_track(path, 1);
if (!track_handle)
return rc_hash_error("Could not open track");
@ -3134,6 +3197,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{
iterator->consoles[0] = RC_CONSOLE_PC_ENGINE;
}
else if (rc_path_compare_extension(ext, "pbp"))
{
iterator->consoles[0] = RC_CONSOLE_PSP;
}
else if (rc_path_compare_extension(ext, "pgm"))
{
iterator->consoles[0] = RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER;