mirror of https://github.com/PCSX2/pcsx2.git
3rdparty/rcheevos: Bump to d54cf8f
This commit is contained in:
parent
46f37f3b45
commit
9225fa9efd
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
response->succeeded = 0;
|
||||
return RC_INVALID_JSON;
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
while (*end && *end != '\n' && end - json < 200)
|
||||
++end;
|
||||
/* title not found, or did not start with an error code, return the first line of the response */
|
||||
iterator.json = server_response->body;
|
||||
|
||||
if (end > json && end[-1] == '\r')
|
||||
--end;
|
||||
while (iterator.json < iterator.end && *iterator.json != '\n' &&
|
||||
iterator.json - server_response->body < 200) {
|
||||
++iterator.json;
|
||||
}
|
||||
|
||||
if (end > json)
|
||||
response->error_message = rc_buffer_strncpy(&response->buffer, json, end - json);
|
||||
if (iterator.json > server_response->body && iterator.json[-1] == '\r')
|
||||
--iterator.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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,9 +237,15 @@ 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;
|
||||
|
||||
last_author = achievement->author;
|
||||
last_author_field = achievement_fields[6].value_start;
|
||||
last_author_len = len;
|
||||
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"))
|
||||
|
|
|
@ -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) {
|
||||
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);
|
||||
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);
|
||||
#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,13 +1947,13 @@ 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;
|
||||
for (; scan; scan = scan->next) {
|
||||
if (scan->public_.title == load_state->game->public_.title) {
|
||||
scan->public_.title = core_subset_title;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subset->public_.title = rc_buffer_strcpy(&load_state->game->buffer, fetch_game_data_response.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 */
|
||||
|
||||
load_state->game->public_.title = "Unknown Game";
|
||||
load_state->game->public_.badge_name = "";
|
||||
client->game = load_state->game;
|
||||
load_state->game = NULL;
|
||||
if (load_state->hash->game_id == 0) {
|
||||
rc_client_subset_info_t* subset;
|
||||
|
||||
rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game");
|
||||
return;
|
||||
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 rc_client_begin_change_media_internal(client, game, game_hash, callback, callback_userdata);
|
||||
}
|
||||
|
||||
#endif /* RC_CLIENT_SUPPORTS_HASH */
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
pending_media->hash = strdup(hash);
|
||||
pending_media->callback = callback;
|
||||
pending_media->callback_userdata = callback_userdata;
|
||||
|
||||
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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,7 +151,8 @@ 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) {
|
||||
rc_client_set_host(client, host_url);
|
||||
if (strcmp(host_url, "https://retroachievements.org") != 0)
|
||||
rc_client_set_host(client, host_url);
|
||||
}
|
||||
else if (client->state.raintegration->init_client_offline) {
|
||||
init_func = 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,12 +355,14 @@ 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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 == '*');
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
@ -957,8 +1008,7 @@ 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;
|
||||
|
||||
|
|
|
@ -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 */
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -89,12 +89,13 @@ typedef struct {
|
|||
void* peek_userdata;
|
||||
lua_State* L;
|
||||
|
||||
rc_typed_value_t measured_value; /* Measured */
|
||||
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 */
|
||||
uint8_t measured_from_hits; /* true if the measured_value came from a condition's hit count */
|
||||
uint8_t was_cond_reset; /* ResetNextIf triggered */
|
||||
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 */
|
||||
uint8_t measured_from_hits; /* true if the measured_value came from a condition's hit count */
|
||||
uint8_t was_cond_reset; /* ResetNextIf triggered */
|
||||
}
|
||||
rc_eval_state_t;
|
||||
|
||||
|
@ -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);
|
||||
|
|
|
@ -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,48 +393,67 @@ int rc_validate_condset_internal(const rc_condset_t* condset, char result[], con
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* if either side is a memref, or there's a running add source chain, check for impossible comparisons */
|
||||
if (is_memref1 || is_memref2 || add_source_max) {
|
||||
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 */
|
||||
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) {
|
||||
snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true");
|
||||
return 0;
|
||||
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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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");
|
||||
#else
|
||||
|
||||
#if defined(__STDC_WANT_SECURE_LIB__)
|
||||
/* 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
|
||||
#endif
|
||||
|
||||
free(wpath);
|
||||
return fp;
|
||||
#elif defined(__STDC_WANT_SECURE_LIB__)
|
||||
FILE* fp;
|
||||
fopen_s(&fp, path, "rb");
|
||||
return fp;
|
||||
#else
|
||||
return fopen(path, "rb");
|
||||
#endif
|
||||
}
|
||||
#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;
|
||||
#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;
|
||||
|
|
Loading…
Reference in New Issue