dep/rcheevos: Bump to latest upstream
This commit is contained in:
parent
e41543c38a
commit
a0aac8ef17
|
@ -1,3 +1,14 @@
|
|||
# v11.6.0
|
||||
* backdate retried unlocks in rc_client
|
||||
* add memory map and hash method for RC_CONSOLE_ZX_SPECTRUM
|
||||
* add RC_CONSOLE_GAMECUBE to supported consoles for iso file extension
|
||||
* add DTCM to RC_CONSOLE_NINTENDO_DS and RC_CONSOLE_NINTENDO_DSI memory maps
|
||||
* don't report conflict if last conditions of OrNext chain conflict
|
||||
* don't report conflict if last conditions of AddSource chain conflict
|
||||
* fix hits not being reset on leaderboard value when start and submit are both true in a single frame
|
||||
* fix crash when multiple items in a CSV lookup overlap
|
||||
* fix crash if game is unloaded while activating achievements
|
||||
|
||||
# v11.5.0
|
||||
* add total_entries to rc_api_fetch_leaderboard_info_response_t
|
||||
* add RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED event
|
||||
|
|
|
@ -46,5 +46,5 @@ add_library(rcheevos
|
|||
|
||||
target_include_directories(rcheevos PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_include_directories(rcheevos INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_compile_definitions(rcheevos PRIVATE "RC_DISABLE_LUA=1" "RCHEEVOS_URL_SSL=1" "RC_NO_THREADS=1")
|
||||
target_compile_definitions(rcheevos PRIVATE "RCHEEVOS_URL_SSL=1" "RC_NO_THREADS=1")
|
||||
|
||||
|
|
|
@ -6,23 +6,9 @@ Keep in mind that **rcheevos** does *not* provide HTTP network connections. Clie
|
|||
|
||||
Not all structures defined by **rcheevos** can be created via the public API, but are exposed to allow interactions beyond just creation, destruction, and testing, such as the ones required by UI code that helps to create them.
|
||||
|
||||
## Lua
|
||||
|
||||
RetroAchievements previously considered the use of the [Lua](https://www.lua.org) language to expand the syntax supported for creating achievements.
|
||||
|
||||
To enable Lua support, you must compile with an additional compilation flag: `HAVE_LUA`, as neither the backend nor the UI for editing achievements are currently Lua-enabled. We do not foresee enabling it any time soon, but the code has not yet been completely eliminated as many of the low-level API fuctions have parameters for LUA data.
|
||||
|
||||
> **rcheevos** does *not* create or maintain a Lua state, you have to create your own state and provide it to **rcheevos** to be used when Lua-coded achievements are found. Calls to **rcheevos** may allocate and/or free additional memory as part of the Lua runtime.
|
||||
|
||||
Lua functions used in trigger operands receive two parameters: `peek`, which is used to read from the emulated system's memory, and `userdata`, which must be passed to `peek`. `peek`'s signature is the same as its C counterpart:
|
||||
|
||||
```lua
|
||||
function peek(address, num_bytes, userdata)
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
An understanding about how achievements are developed may be useful, you can read more about it [here](http://docs.retroachievements.org/Developer-docs/).
|
||||
An understanding about how achievements are developed may be useful, you can read more about it [here](https://docs.retroachievements.org/developer-docs/).
|
||||
|
||||
Most of the exposed APIs are documented [here](https://github.com/RetroAchievements/rcheevos/wiki)
|
||||
|
||||
|
@ -43,10 +29,6 @@ Platforms supported by RetroAchievements are enumerated in `rc_consoles.h`. Note
|
|||
|
||||
Provides a set of functions for managing an active game - initializing and processing achievements, leaderboards, and rich presence. When important things occur, events are raised for the caller via a callback.
|
||||
|
||||
These are in `rc_runtime.h`.
|
||||
|
||||
Note: `rc_runtime_t` still requires the client implement all of the logic that calls the APIs to retrieve the data and perform the unlocks.
|
||||
|
||||
The `rc_client_t` functions wrap a `rc_runtime_t` and manage the API calls and other common functionality (like managing the user information, identifying/loading a game, and building the active/inactive achievements list for the UI). Please see [the wiki](https://github.com/RetroAchievements/rcheevos/wiki/rc_client-integration) for details on using the `rc_client_t` functions.
|
||||
|
||||
## Server Communication
|
||||
|
@ -76,6 +58,8 @@ Please see the [wiki](https://github.com/RetroAchievements/rcheevos/wiki) for de
|
|||
These are in `rc_hash.h`.
|
||||
|
||||
```c
|
||||
int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer, size_t buffer_size);
|
||||
int rc_hash_generate_from_file(char hash[33], int console_id, const char* path);
|
||||
void rc_hash_initialize_iterator(rc_hash_iterator_t* iterator, const char* path, const uint8_t* buffer, size_t buffer_size);
|
||||
int rc_hash_generate(char hash[33], uint32_t console_id, const rc_hash_iterator_t* iterator);
|
||||
int rc_hash_iterate(char hash[33], rc_hash_iterator_t* iterator);
|
||||
void rc_hash_destroy_iterator(rc_hash_iterator_t* iterator);
|
||||
```
|
||||
|
|
|
@ -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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_code_notes_request_hosted(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
@ -77,6 +78,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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_update_code_note_request_hosted(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
@ -126,6 +128,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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_update_achievement_request_hosted(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
@ -177,6 +180,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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_update_leaderboard_request_hosted(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
@ -208,6 +212,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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_badge_range_request_hosted(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
@ -249,6 +254,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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_add_game_hash_request_hosted(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
|
|
@ -33,6 +33,8 @@ rc_api_fetch_achievement_info_request_t;
|
|||
typedef struct rc_api_achievement_awarded_entry_t {
|
||||
/* The user associated to the entry */
|
||||
const char* username;
|
||||
/* A URL to the user's avatar image */
|
||||
const char* avatar_url;
|
||||
/* When the achievement was awarded */
|
||||
time_t awarded;
|
||||
}
|
||||
|
@ -62,6 +64,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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_achievement_info_request_hosted(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
@ -88,6 +91,8 @@ rc_api_fetch_leaderboard_info_request_t;
|
|||
typedef struct rc_api_lboard_info_entry_t {
|
||||
/* The user associated to the entry */
|
||||
const char* username;
|
||||
/* A URL to the user's avatar image */
|
||||
const char* avatar_url;
|
||||
/* The rank of the entry */
|
||||
uint32_t rank;
|
||||
/* The index of the entry */
|
||||
|
@ -138,6 +143,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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_leaderboard_info_request_hosted(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
@ -178,6 +184,7 @@ 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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_games_list_request_hosted(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
@ -222,6 +229,7 @@ typedef struct rc_api_fetch_game_titles_response_t {
|
|||
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_init_fetch_game_titles_request_hosted(rc_api_request_t* request, const rc_api_fetch_game_titles_request_t* api_params, const rc_api_host_t* host);
|
||||
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);
|
||||
|
||||
|
@ -258,6 +266,7 @@ typedef struct rc_api_fetch_hash_library_response_t {
|
|||
rc_api_fetch_hash_library_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_hash_library_request(rc_api_request_t* request, const rc_api_fetch_hash_library_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_hash_library_request_hosted(rc_api_request_t* request, const rc_api_fetch_hash_library_request_t* api_params, const rc_api_host_t* host);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_hash_library_server_response(rc_api_fetch_hash_library_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_hash_library_response(rc_api_fetch_hash_library_response_t* response);
|
||||
|
||||
|
|
|
@ -8,6 +8,17 @@
|
|||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/**
|
||||
* Information about the server being connected to.
|
||||
*/
|
||||
typedef struct rc_api_host_t {
|
||||
/* The host name for the API calls (retroachievements.org) */
|
||||
const char* host;
|
||||
/* The host name for media URLs (media.retroachievements.org) */
|
||||
const char* media_host;
|
||||
}
|
||||
rc_api_host_t;
|
||||
|
||||
/**
|
||||
* A constructed request to send to the retroachievements server.
|
||||
*/
|
||||
|
@ -42,7 +53,9 @@ rc_api_response_t;
|
|||
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_request(rc_api_request_t* request);
|
||||
|
||||
/* [deprecated] use rc_api_init_*_hosted instead */
|
||||
RC_EXPORT void RC_CCONV rc_api_set_host(const char* hostname);
|
||||
/* [deprecated] use rc_api_init_*_hosted instead */
|
||||
RC_EXPORT void RC_CCONV rc_api_set_image_host(const char* hostname);
|
||||
|
||||
typedef struct rc_api_server_response_t {
|
||||
|
|
|
@ -28,6 +28,7 @@ rc_api_fetch_image_request_t;
|
|||
#define RC_IMAGE_TYPE_USER 4
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_image_request_hosted(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params, const rc_api_host_t* host);
|
||||
|
||||
/* --- Resolve Hash --- */
|
||||
|
||||
|
@ -57,6 +58,8 @@ typedef struct rc_api_resolve_hash_response_t {
|
|||
rc_api_resolve_hash_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_resolve_hash_request_hosted(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [deprecated] use rc_api_process_resolve_hash_server_response instead */
|
||||
RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response);
|
||||
|
@ -73,6 +76,8 @@ typedef struct rc_api_fetch_game_data_request_t {
|
|||
const char* api_token;
|
||||
/* The unique identifier of the game */
|
||||
uint32_t game_id;
|
||||
/* The generated hash of the game to be identified (ignored if game_id is not 0) */
|
||||
const char* game_hash;
|
||||
}
|
||||
rc_api_fetch_game_data_request_t;
|
||||
|
||||
|
@ -105,7 +110,7 @@ typedef struct rc_api_achievement_definition_t {
|
|||
uint32_t category;
|
||||
/* The title of the achievement */
|
||||
const char* title;
|
||||
/* The dscription of the achievement */
|
||||
/* The description of the achievement */
|
||||
const char* description;
|
||||
/* The definition of the achievement to be passed to rc_runtime_activate_achievement */
|
||||
const char* definition;
|
||||
|
@ -123,9 +128,36 @@ typedef struct rc_api_achievement_definition_t {
|
|||
float rarity;
|
||||
/* The approximate rarity of the achievement in hardcore (X% of users have earned the achievement in hardcore) */
|
||||
float rarity_hardcore;
|
||||
/* The URL for the achievement badge */
|
||||
const char* badge_url;
|
||||
/* The URL for the locked achievement badge */
|
||||
const char* badge_locked_url;
|
||||
}
|
||||
rc_api_achievement_definition_t;
|
||||
|
||||
/* A game subset definition */
|
||||
typedef struct rc_api_subset_definition_t {
|
||||
/* The unique identifier of the subset */
|
||||
uint32_t id;
|
||||
/* The title of the subset */
|
||||
const char* title;
|
||||
/* The image name for the subset badge */
|
||||
const char* image_name;
|
||||
/* The URL for the subset badge */
|
||||
const char* image_url;
|
||||
|
||||
/* An array of achievements for the game */
|
||||
rc_api_achievement_definition_t* achievements;
|
||||
/* The number of items in the achievements array */
|
||||
uint32_t num_achievements;
|
||||
|
||||
/* An array of leaderboards for the game */
|
||||
rc_api_leaderboard_definition_t* leaderboards;
|
||||
/* The number of items in the leaderboards array */
|
||||
uint32_t num_leaderboards;
|
||||
}
|
||||
rc_api_subset_definition_t;
|
||||
|
||||
#define RC_ACHIEVEMENT_CATEGORY_CORE 3
|
||||
#define RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL 5
|
||||
|
||||
|
@ -146,6 +178,8 @@ typedef struct rc_api_fetch_game_data_response_t {
|
|||
const char* title;
|
||||
/* The image name for the game badge */
|
||||
const char* image_name;
|
||||
/* The URL for the game badge */
|
||||
const char* image_url;
|
||||
/* The rich presence script for the game to be passed to rc_runtime_activate_richpresence */
|
||||
const char* rich_presence_script;
|
||||
|
||||
|
@ -159,12 +193,19 @@ typedef struct rc_api_fetch_game_data_response_t {
|
|||
/* The number of items in the leaderboards array */
|
||||
uint32_t num_leaderboards;
|
||||
|
||||
/* An array of subsets for the game */
|
||||
rc_api_subset_definition_t* subsets;
|
||||
/* The number of items in the subsets array */
|
||||
uint32_t num_subsets;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_game_data_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_game_data_request_hosted(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [deprecated] use rc_api_process_fetch_game_data_server_response instead */
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response);
|
||||
|
@ -200,6 +241,8 @@ typedef struct rc_api_ping_response_t {
|
|||
rc_api_ping_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_ping_request_hosted(rc_api_request_t* request, const rc_api_ping_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [deprecated] use rc_api_process_ping_server_response instead */
|
||||
RC_EXPORT int RC_CCONV rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_ping_response(rc_api_ping_response_t* response);
|
||||
|
@ -245,6 +288,8 @@ typedef struct rc_api_award_achievement_response_t {
|
|||
rc_api_award_achievement_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_award_achievement_request_hosted(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [deprecated] use rc_api_process_award_achievement_server_response instead */
|
||||
RC_EXPORT int RC_CCONV rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response);
|
||||
|
@ -305,6 +350,8 @@ typedef struct rc_api_submit_lboard_entry_response_t {
|
|||
rc_api_submit_lboard_entry_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_submit_lboard_entry_request_hosted(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [deprecated] use rc_api_process_submit_lboard_entry_server_response instead */
|
||||
RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response);
|
||||
|
|
|
@ -40,6 +40,8 @@ typedef struct rc_api_login_response_t {
|
|||
uint32_t num_unread_messages;
|
||||
/* The preferred name to display for the player */
|
||||
const char* display_name;
|
||||
/* A URL to the user's avatar image */
|
||||
const char* avatar_url;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
|
@ -47,6 +49,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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_login_request_hosted(rc_api_request_t* request, const rc_api_login_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
@ -105,6 +108,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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_start_session_request_hosted(rc_api_request_t* request, const rc_api_start_session_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
@ -142,11 +146,73 @@ 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);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_user_unlocks_request_hosted(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params, const rc_api_host_t* host);
|
||||
/* [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);
|
||||
|
||||
/* --- Fetch Followed Users --- */
|
||||
|
||||
/**
|
||||
* API parameters for a fetch followed users request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_followed_users_request_t {
|
||||
/* The username of the player */
|
||||
const char* username;
|
||||
/* The API token from the login request */
|
||||
const char* api_token;
|
||||
}
|
||||
rc_api_fetch_followed_users_request_t;
|
||||
|
||||
typedef struct rc_api_followed_user_activity_t {
|
||||
/* The record associated to the activity */
|
||||
const char* context;
|
||||
/* The image of the record associated to the activity */
|
||||
const char* context_image_url;
|
||||
/* The description of the activity */
|
||||
const char* description;
|
||||
/* The time of the activity */
|
||||
time_t when;
|
||||
/* The unique identifier of the record associated to the activity */
|
||||
uint32_t context_id;
|
||||
}
|
||||
rc_api_followed_user_activity_t;
|
||||
|
||||
/**
|
||||
* Response data for a followed user.
|
||||
*/
|
||||
typedef struct rc_api_followed_user_t {
|
||||
/* The preferred name to display for the player */
|
||||
const char* display_name;
|
||||
/* A URL to the user's avatar image */
|
||||
const char* avatar_url;
|
||||
/* The player's last registered activity */
|
||||
rc_api_followed_user_activity_t recent_activity;
|
||||
/* The current score of the player */
|
||||
uint32_t score;
|
||||
}
|
||||
rc_api_followed_user_t;
|
||||
|
||||
/**
|
||||
* Response data for a fetch followed users request.
|
||||
*/
|
||||
typedef struct rc_api_fetch_followed_users_response_t {
|
||||
/* An array of followed user information */
|
||||
rc_api_followed_user_t* users;
|
||||
/* The number of items in the users array */
|
||||
uint32_t num_users;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
rc_api_fetch_followed_users_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_followed_users_request(rc_api_request_t* request, const rc_api_fetch_followed_users_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_followed_users_request_hosted(rc_api_request_t* request, const rc_api_fetch_followed_users_request_t* api_params, const rc_api_host_t* host);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_users_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_followed_users_response(rc_api_fetch_followed_users_response_t* response);
|
||||
|
||||
/* --- Fetch All Progress --- */
|
||||
|
||||
/**
|
||||
|
@ -187,6 +253,7 @@ typedef struct rc_api_fetch_all_progress_response_t {
|
|||
} rc_api_fetch_all_progress_response_t;
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_all_progress_request(rc_api_request_t* request, const rc_api_fetch_all_progress_request_t* api_params);
|
||||
RC_EXPORT int RC_CCONV rc_api_init_fetch_all_progress_request_hosted(rc_api_request_t* request, const rc_api_fetch_all_progress_request_t* api_params, const rc_api_host_t* host);
|
||||
RC_EXPORT int RC_CCONV rc_api_process_fetch_all_progress_server_response(rc_api_fetch_all_progress_response_t* response, const rc_api_server_response_t* server_response);
|
||||
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_all_progress_response(rc_api_fetch_all_progress_response_t* response);
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ RC_EXPORT void* RC_CCONV rc_client_get_userdata(const rc_client_t* client);
|
|||
/**
|
||||
* Sets the name of the server to use.
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_client_set_host(const rc_client_t* client, const char* hostname);
|
||||
RC_EXPORT void RC_CCONV rc_client_set_host(rc_client_t* client, const char* hostname);
|
||||
|
||||
typedef uint64_t rc_clock_t;
|
||||
typedef rc_clock_t (RC_CCONV *rc_get_time_millisecs_func_t)(const rc_client_t* client);
|
||||
|
@ -193,6 +193,8 @@ typedef struct rc_client_user_t {
|
|||
uint32_t score;
|
||||
uint32_t score_softcore;
|
||||
uint32_t num_unread_messages;
|
||||
/* minimum version: 12.0 */
|
||||
const char* avatar_url;
|
||||
} rc_client_user_t;
|
||||
|
||||
/**
|
||||
|
@ -272,6 +274,8 @@ typedef struct rc_client_game_t {
|
|||
const char* title;
|
||||
const char* hash;
|
||||
const char* badge_name;
|
||||
/* minimum version: 12.0 */
|
||||
const char* badge_url;
|
||||
} rc_client_game_t;
|
||||
|
||||
/**
|
||||
|
@ -311,6 +315,9 @@ typedef struct rc_client_subset_t {
|
|||
|
||||
uint32_t num_achievements;
|
||||
uint32_t num_leaderboards;
|
||||
|
||||
/* minimum version: 12.0 */
|
||||
const char* badge_url;
|
||||
} rc_client_subset_t;
|
||||
|
||||
RC_EXPORT const rc_client_subset_t* RC_CCONV rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id);
|
||||
|
@ -460,6 +467,9 @@ typedef struct rc_client_achievement_t {
|
|||
float rarity;
|
||||
float rarity_hardcore;
|
||||
uint8_t type;
|
||||
/* minimum version: 12.0 */
|
||||
const char* badge_url;
|
||||
const char* badge_locked_url;
|
||||
} rc_client_achievement_t;
|
||||
|
||||
/**
|
||||
|
@ -474,7 +484,7 @@ RC_EXPORT const rc_client_achievement_t* RC_CCONV rc_client_get_achievement_info
|
|||
RC_EXPORT int RC_CCONV rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size);
|
||||
|
||||
typedef struct rc_client_achievement_bucket_t {
|
||||
rc_client_achievement_t** achievements;
|
||||
const rc_client_achievement_t** achievements;
|
||||
uint32_t num_achievements;
|
||||
|
||||
const char* label;
|
||||
|
@ -483,7 +493,7 @@ typedef struct rc_client_achievement_bucket_t {
|
|||
} rc_client_achievement_bucket_t;
|
||||
|
||||
typedef struct rc_client_achievement_list_t {
|
||||
rc_client_achievement_bucket_t* buckets;
|
||||
const rc_client_achievement_bucket_t* buckets;
|
||||
uint32_t num_buckets;
|
||||
} rc_client_achievement_list_t;
|
||||
|
||||
|
@ -555,7 +565,7 @@ typedef struct rc_client_leaderboard_tracker_t {
|
|||
} rc_client_leaderboard_tracker_t;
|
||||
|
||||
typedef struct rc_client_leaderboard_bucket_t {
|
||||
rc_client_leaderboard_t** leaderboards;
|
||||
const rc_client_leaderboard_t** leaderboards;
|
||||
uint32_t num_leaderboards;
|
||||
|
||||
const char* label;
|
||||
|
@ -564,7 +574,7 @@ typedef struct rc_client_leaderboard_bucket_t {
|
|||
} rc_client_leaderboard_bucket_t;
|
||||
|
||||
typedef struct rc_client_leaderboard_list_t {
|
||||
rc_client_leaderboard_bucket_t* buckets;
|
||||
const rc_client_leaderboard_bucket_t* buckets;
|
||||
uint32_t num_buckets;
|
||||
} rc_client_leaderboard_list_t;
|
||||
|
||||
|
@ -717,7 +727,8 @@ enum {
|
|||
RC_CLIENT_EVENT_GAME_COMPLETED = 15, /* all achievements for the game have been earned */
|
||||
RC_CLIENT_EVENT_SERVER_ERROR = 16, /* an API response returned a [server_error] and will not be retried */
|
||||
RC_CLIENT_EVENT_DISCONNECTED = 17, /* an unlock request could not be completed and is pending */
|
||||
RC_CLIENT_EVENT_RECONNECTED = 18 /* all pending unlocks have been completed */
|
||||
RC_CLIENT_EVENT_RECONNECTED = 18, /* all pending unlocks have been completed */
|
||||
RC_CLIENT_EVENT_SUBSET_COMPLETED = 19 /* all achievements for the subset have been earned */
|
||||
};
|
||||
|
||||
typedef struct rc_client_server_error_t {
|
||||
|
@ -735,6 +746,7 @@ typedef struct rc_client_event_t {
|
|||
rc_client_leaderboard_tracker_t* leaderboard_tracker;
|
||||
rc_client_leaderboard_scoreboard_t* leaderboard_scoreboard;
|
||||
rc_client_server_error_t* server_error;
|
||||
rc_client_subset_t* subset;
|
||||
|
||||
} rc_client_event_t;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ RC_BEGIN_C_DECLS
|
|||
|
||||
enum {
|
||||
RC_OK = 0,
|
||||
RC_INVALID_LUA_OPERAND = -1,
|
||||
RC_INVALID_FUNC_OPERAND = -1,
|
||||
RC_INVALID_MEMORY_OPERAND = -2,
|
||||
RC_INVALID_CONST_OPERAND = -3,
|
||||
RC_INVALID_FP_OPERAND = -4,
|
||||
|
@ -48,7 +48,8 @@ enum {
|
|||
RC_EXPIRED_TOKEN = -35,
|
||||
RC_INSUFFICIENT_BUFFER = -36,
|
||||
RC_INVALID_VARIABLE_NAME = -37,
|
||||
RC_UNKNOWN_VARIABLE_NAME = -38
|
||||
RC_UNKNOWN_VARIABLE_NAME = -38,
|
||||
RC_NOT_FOUND = -39
|
||||
};
|
||||
|
||||
RC_EXPORT const char* RC_CCONV rc_error_str(int ret);
|
||||
|
|
|
@ -11,51 +11,14 @@ RC_BEGIN_C_DECLS
|
|||
|
||||
/* ===================================================== */
|
||||
|
||||
/* generates a hash from a block of memory.
|
||||
* returns non-zero on success, or zero on failure.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
/* generates a hash from a file.
|
||||
* returns non-zero on success, or zero on failure.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* path);
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
/* data for rc_hash_iterate
|
||||
*/
|
||||
typedef struct rc_hash_iterator
|
||||
{
|
||||
const uint8_t* buffer;
|
||||
size_t buffer_size;
|
||||
uint8_t consoles[12];
|
||||
int index;
|
||||
const char* path;
|
||||
} rc_hash_iterator_t;
|
||||
|
||||
/* initializes a rc_hash_iterator
|
||||
* - path must be provided
|
||||
* - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file)
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
/* releases resources associated to a rc_hash_iterator
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_hash_destroy_iterator(struct rc_hash_iterator* iterator);
|
||||
|
||||
/* generates the next hash for the data in the rc_hash_iterator.
|
||||
* returns non-zero if a hash was generated, or zero if no more hashes can be generated for the data.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_hash_iterate(char hash[33], struct rc_hash_iterator* iterator);
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
/* specifies a function to call when an error occurs to display the error message */
|
||||
typedef void (RC_CCONV *rc_hash_message_callback)(const char*);
|
||||
|
||||
/* [deprecated] set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_error_message_callback(rc_hash_message_callback callback);
|
||||
|
||||
/* specifies a function to call for verbose logging */
|
||||
/* [deprecated] set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback);
|
||||
|
||||
/* ===================================================== */
|
||||
|
@ -77,15 +40,16 @@ RC_BEGIN_C_DECLS
|
|||
/* closes the file */
|
||||
typedef void (RC_CCONV *rc_hash_filereader_close_file_handler)(void* file_handle);
|
||||
|
||||
struct rc_hash_filereader
|
||||
typedef struct rc_hash_filereader
|
||||
{
|
||||
rc_hash_filereader_open_file_handler open;
|
||||
rc_hash_filereader_seek_handler seek;
|
||||
rc_hash_filereader_tell_handler tell;
|
||||
rc_hash_filereader_read_handler read;
|
||||
rc_hash_filereader_close_file_handler close;
|
||||
};
|
||||
} rc_hash_filereader_t;
|
||||
|
||||
/* [deprecated] set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_custom_filereader(struct rc_hash_filereader* reader);
|
||||
|
||||
/* ===================================================== */
|
||||
|
@ -99,6 +63,7 @@ RC_BEGIN_C_DECLS
|
|||
* returns a handle to be passed to the other functions, or NULL if the track could not be opened.
|
||||
*/
|
||||
typedef void* (RC_CCONV *rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track);
|
||||
typedef void* (RC_CCONV* rc_hash_cdreader_open_track_filereader_handler)(const char* path, uint32_t track, const rc_hash_filereader_t* filereader);
|
||||
|
||||
/* attempts to read the specified number of bytes from the file starting at the specified absolute sector.
|
||||
* returns the number of bytes actually read.
|
||||
|
@ -111,19 +76,113 @@ RC_BEGIN_C_DECLS
|
|||
/* gets the absolute sector index for the first sector of a track */
|
||||
typedef uint32_t(RC_CCONV *rc_hash_cdreader_first_track_sector_handler)(void* track_handle);
|
||||
|
||||
struct rc_hash_cdreader
|
||||
typedef struct rc_hash_cdreader
|
||||
{
|
||||
rc_hash_cdreader_open_track_handler open_track;
|
||||
rc_hash_cdreader_read_sector_handler read_sector;
|
||||
rc_hash_cdreader_close_track_handler close_track;
|
||||
rc_hash_cdreader_first_track_sector_handler first_track_sector;
|
||||
};
|
||||
rc_hash_cdreader_open_track_filereader_handler open_track_filereader;
|
||||
} rc_hash_cdreader_t;
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader);
|
||||
/* [deprecated] don't set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_default_cdreader(void);
|
||||
/* [deprecated] set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader);
|
||||
|
||||
/* ===================================================== */
|
||||
#ifndef RC_HASH_NO_ENCRYPTED
|
||||
|
||||
/* specifies a function called to obtain a 3DS CIA decryption normal key.
|
||||
* this key would be derived from slot0x3DKeyX and the common key specified by the passed index.
|
||||
* the normal key should be written in big endian format
|
||||
* returns non-zero on success, or zero on failure.
|
||||
*/
|
||||
typedef int (RC_CCONV *rc_hash_3ds_get_cia_normal_key_func)(uint8_t common_key_index, uint8_t out_normal_key[16]);
|
||||
/* [deprecated] set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_3ds_get_cia_normal_key_func(rc_hash_3ds_get_cia_normal_key_func func);
|
||||
|
||||
/* specifies a function called to obtain 3DS NCCH decryption normal keys.
|
||||
* the primary key will always use slot0x2CKeyX and the passed primary KeyY.
|
||||
* the secondary key will use the KeyX slot passed
|
||||
* the secondary KeyY will be identical to the primary keyY if the passed program id is NULL
|
||||
* if the program id is not null, then the secondary KeyY will be obtained with "seed crypto"
|
||||
* with "seed crypto" the 8 byte program id can be used to obtain a 16 byte "seed" within the seeddb.bin firmware file
|
||||
* the primary KeyY then the seed will then be hashed with SHA256, and the upper 16 bytes of the digest will be the secondary KeyY used
|
||||
* the normal keys should be written in big endian format
|
||||
* returns non-zero on success, or zero on failure.
|
||||
*/
|
||||
typedef int (RC_CCONV *rc_hash_3ds_get_ncch_normal_keys_func)(uint8_t primary_key_y[16], uint8_t secondary_key_x_slot, uint8_t* optional_program_id,
|
||||
uint8_t out_primary_key[16], uint8_t out_secondary_key[16]);
|
||||
/* [deprecated] set callbacks in rc_hash_iterator_t */
|
||||
RC_EXPORT void RC_CCONV rc_hash_init_3ds_get_ncch_normal_keys_func(rc_hash_3ds_get_ncch_normal_keys_func func);
|
||||
|
||||
#endif
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
typedef struct rc_hash_callbacks {
|
||||
rc_hash_message_callback verbose_message;
|
||||
rc_hash_message_callback error_message;
|
||||
|
||||
rc_hash_filereader_t filereader;
|
||||
rc_hash_cdreader_t cdreader;
|
||||
|
||||
#ifndef RC_HASH_NO_ENCRYPTED
|
||||
struct rc_hash_encryption_callbacks {
|
||||
rc_hash_3ds_get_cia_normal_key_func get_3ds_cia_normal_key;
|
||||
rc_hash_3ds_get_ncch_normal_keys_func get_3ds_ncch_normal_keys;
|
||||
} encryption;
|
||||
#endif
|
||||
} rc_hash_callbacks_t;
|
||||
|
||||
/* data for rc_hash_iterate
|
||||
*/
|
||||
typedef struct rc_hash_iterator {
|
||||
const uint8_t* buffer;
|
||||
size_t buffer_size;
|
||||
uint8_t consoles[12];
|
||||
int index;
|
||||
const char* path;
|
||||
|
||||
rc_hash_callbacks_t callbacks;
|
||||
} rc_hash_iterator_t;
|
||||
|
||||
/* initializes a rc_hash_iterator
|
||||
* - path must be provided
|
||||
* - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file)
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_hash_initialize_iterator(rc_hash_iterator_t* iterator, const char* path, const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
/* releases resources associated to a rc_hash_iterator
|
||||
*/
|
||||
RC_EXPORT void RC_CCONV rc_hash_destroy_iterator(rc_hash_iterator_t* iterator);
|
||||
|
||||
/* generates the next hash for the data in the rc_hash_iterator.
|
||||
* returns non-zero if a hash was generated, or zero if no more hashes can be generated for the data.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_hash_iterate(char hash[33], rc_hash_iterator_t* iterator);
|
||||
|
||||
/* generates a hash for the data in the rc_hash_iterator.
|
||||
* returns non-zero if a hash was generated.
|
||||
*/
|
||||
RC_EXPORT int RC_CCONV rc_hash_generate(char hash[33], uint32_t console_id, const rc_hash_iterator_t* iterator);
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
/* generates a hash from a block of memory.
|
||||
* returns non-zero on success, or zero on failure.
|
||||
*/
|
||||
/* [deprecated] use rc_hash_generate instead */
|
||||
RC_EXPORT int RC_CCONV rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
/* generates a hash from a file.
|
||||
* returns non-zero on success, or zero on failure.
|
||||
*/
|
||||
/* [deprecated] use rc_hash_generate instead */
|
||||
RC_EXPORT int RC_CCONV rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* path);
|
||||
|
||||
/* ===================================================== */
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@ RC_BEGIN_C_DECLS
|
|||
|
||||
#ifndef RC_RUNTIME_TYPES_H /* prevents pedantic redefinition error */
|
||||
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
typedef struct rc_trigger_t rc_trigger_t;
|
||||
typedef struct rc_lboard_t rc_lboard_t;
|
||||
typedef struct rc_richpresence_t rc_richpresence_t;
|
||||
|
@ -88,20 +86,20 @@ RC_EXPORT rc_runtime_t* RC_CCONV rc_runtime_alloc(void);
|
|||
RC_EXPORT void RC_CCONV rc_runtime_init(rc_runtime_t* runtime);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_destroy(rc_runtime_t* runtime);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_runtime_activate_achievement(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_activate_achievement(rc_runtime_t* runtime, uint32_t id, const char* memaddr, void* unused_L, int unused_funcs_idx);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_deactivate_achievement(rc_runtime_t* runtime, uint32_t id);
|
||||
RC_EXPORT rc_trigger_t* RC_CCONV rc_runtime_get_achievement(const rc_runtime_t* runtime, uint32_t id);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, uint32_t id, unsigned* measured_value, unsigned* measured_target);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, uint32_t id, char *buffer, size_t buffer_size);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_runtime_activate_lboard(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_activate_lboard(rc_runtime_t* runtime, uint32_t id, const char* memaddr, void* unused_L, int unused_funcs_idx);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_deactivate_lboard(rc_runtime_t* runtime, uint32_t id);
|
||||
RC_EXPORT rc_lboard_t* RC_CCONV rc_runtime_get_lboard(const rc_runtime_t* runtime, uint32_t id);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_format_lboard_value(char* buffer, int size, int32_t value, int format);
|
||||
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_get_richpresence(const rc_runtime_t* runtime, char* buffer, size_t buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, void* unused_L, int unused_funcs_idx);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_get_richpresence(const rc_runtime_t* runtime, char* buffer, size_t buffersize, rc_runtime_peek_t peek, void* peek_ud, void* unused_L);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_get_richpresence_strings(const rc_runtime_t* runtime, const char** buffer, size_t buffersize, size_t* count);
|
||||
|
||||
enum {
|
||||
|
@ -129,22 +127,22 @@ rc_runtime_event_t;
|
|||
|
||||
typedef void (RC_CCONV *rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, void* unused_L);
|
||||
RC_EXPORT void RC_CCONV rc_runtime_reset(rc_runtime_t* runtime);
|
||||
|
||||
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 uint32_t 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, void* unused_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);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, void* unused_L);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, void* unused_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_EXPORT int RC_CCONV rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, void* unused_L);
|
||||
RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, void* unused_L);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
|
|
|
@ -10,8 +10,6 @@ RC_BEGIN_C_DECLS
|
|||
|
||||
#ifndef RC_RUNTIME_H /* prevents pedantic redefiniton error */
|
||||
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
typedef struct rc_trigger_t rc_trigger_t;
|
||||
typedef struct rc_lboard_t rc_lboard_t;
|
||||
typedef struct rc_richpresence_t rc_richpresence_t;
|
||||
|
@ -99,7 +97,7 @@ enum {
|
|||
RC_OPERAND_DELTA, /* The value last known at this address. */
|
||||
RC_OPERAND_CONST, /* A 32-bit unsigned integer. */
|
||||
RC_OPERAND_FP, /* A floating point value. */
|
||||
RC_OPERAND_LUA, /* A Lua function that provides the value. */
|
||||
RC_OPERAND_FUNC, /* A 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. */
|
||||
|
@ -116,9 +114,6 @@ typedef struct rc_operand_t {
|
|||
|
||||
/* A floating point value. */
|
||||
double dbl;
|
||||
|
||||
/* A reference to the Lua function that provides the value. */
|
||||
int luafunc;
|
||||
} value;
|
||||
|
||||
/* specifies which member of the value union is being used (RC_OPERAND_*) */
|
||||
|
@ -199,7 +194,15 @@ struct rc_condition_t {
|
|||
/* The comparison operator to use. (RC_OPERATOR_*) */
|
||||
uint8_t oper; /* operator is a reserved word in C++. */
|
||||
|
||||
/* Whether or not the condition evaluated true on the last check. (bool) */
|
||||
/* Will be non-zero if the condition evaluated true on the last check.
|
||||
* - The lowest bit indicates whether the condition itself was true.
|
||||
* - The second lowest bit will only ever be set on ResetIf conditions.
|
||||
* If set, it indicates that the condition was responsible for resetting the
|
||||
* trigger. A reset clears all hit counts, so the condition may not appear to
|
||||
* be true just from looking at it (in which case the lower bit will be 0).
|
||||
* Also, the condition might have only met its required_hits target though
|
||||
* an AddHits chain which will have also been reset.
|
||||
*/
|
||||
uint8_t is_true;
|
||||
|
||||
/* Unique identifier of optimized comparator to use. (RC_PROCESSING_COMPARE_*) */
|
||||
|
@ -286,9 +289,9 @@ struct rc_trigger_t {
|
|||
};
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_trigger_size(const char* memaddr);
|
||||
RC_EXPORT rc_trigger_t* RC_CCONV rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
RC_EXPORT int RC_CCONV rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
|
||||
RC_EXPORT int RC_CCONV rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
|
||||
RC_EXPORT rc_trigger_t* RC_CCONV rc_parse_trigger(void* buffer, const char* memaddr, void* unused_L, int unused_funcs_idx);
|
||||
RC_EXPORT int RC_CCONV rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, void* unused_L);
|
||||
RC_EXPORT int RC_CCONV rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, void* unused_L);
|
||||
RC_EXPORT void RC_CCONV rc_reset_trigger(rc_trigger_t* self);
|
||||
|
||||
/*****************************************************************************\
|
||||
|
@ -315,8 +318,8 @@ struct rc_value_t {
|
|||
};
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_value_size(const char* memaddr);
|
||||
RC_EXPORT rc_value_t* RC_CCONV rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
RC_EXPORT int32_t RC_CCONV rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L);
|
||||
RC_EXPORT rc_value_t* RC_CCONV rc_parse_value(void* buffer, const char* memaddr, void* unused_L, int unused_funcs_idx);
|
||||
RC_EXPORT int32_t RC_CCONV rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, void* unused_L);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Leaderboards |
|
||||
|
@ -345,8 +348,8 @@ struct rc_lboard_t {
|
|||
};
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_lboard_size(const char* memaddr);
|
||||
RC_EXPORT rc_lboard_t* RC_CCONV rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
RC_EXPORT int RC_CCONV rc_evaluate_lboard(rc_lboard_t* lboard, int32_t* value, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
RC_EXPORT rc_lboard_t* RC_CCONV rc_parse_lboard(void* buffer, const char* memaddr, void* unused_L, int unused_funcs_idx);
|
||||
RC_EXPORT int RC_CCONV rc_evaluate_lboard(rc_lboard_t* lboard, int32_t* value, rc_peek_t peek, void* peek_ud, void* unused_L);
|
||||
RC_EXPORT void RC_CCONV rc_reset_lboard(rc_lboard_t* lboard);
|
||||
|
||||
/*****************************************************************************\
|
||||
|
@ -374,7 +377,8 @@ enum {
|
|||
RC_FORMAT_TENS,
|
||||
RC_FORMAT_HUNDREDS,
|
||||
RC_FORMAT_THOUSANDS,
|
||||
RC_FORMAT_UNSIGNED_VALUE
|
||||
RC_FORMAT_UNSIGNED_VALUE,
|
||||
RC_FORMAT_UNFORMATTED
|
||||
};
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_parse_format(const char* format_str);
|
||||
|
@ -432,10 +436,10 @@ struct rc_richpresence_t {
|
|||
|
||||
RC_EXPORT int RC_CCONV rc_richpresence_size(const char* script);
|
||||
RC_EXPORT int RC_CCONV rc_richpresence_size_lines(const char* script, int* lines_read);
|
||||
RC_EXPORT rc_richpresence_t* RC_CCONV rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx);
|
||||
RC_EXPORT int RC_CCONV rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
RC_EXPORT void RC_CCONV rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
RC_EXPORT int RC_CCONV rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
RC_EXPORT rc_richpresence_t* RC_CCONV rc_parse_richpresence(void* buffer, const char* script, void* unused_L, int unused_funcs_idx);
|
||||
RC_EXPORT int RC_CCONV rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, void* unused_L);
|
||||
RC_EXPORT void RC_CCONV rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, void* unused_L);
|
||||
RC_EXPORT int RC_CCONV rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, void* unused_L);
|
||||
RC_EXPORT void RC_CCONV rc_reset_richpresence(rc_richpresence_t* self);
|
||||
RC_EXPORT int RC_CCONV rc_get_richpresence_strings(rc_richpresence_t* richpresence, const char** buffer, size_t buffersize, size_t* count);
|
||||
|
||||
|
|
|
@ -13,8 +13,7 @@
|
|||
#define RETROACHIEVEMENTS_IMAGE_HOST "https://media.retroachievements.org"
|
||||
#define RETROACHIEVEMENTS_HOST_NONSSL "http://retroachievements.org"
|
||||
#define RETROACHIEVEMENTS_IMAGE_HOST_NONSSL "http://media.retroachievements.org"
|
||||
static char* g_host = NULL;
|
||||
static char* g_imagehost = NULL;
|
||||
rc_api_host_t g_host = { NULL, NULL };
|
||||
|
||||
/* --- rc_json --- */
|
||||
|
||||
|
@ -320,6 +319,11 @@ static int rc_json_convert_error_code(const char* server_error_code)
|
|||
return RC_INVALID_CREDENTIALS;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
if (strcmp(server_error_code, "not_found") == 0)
|
||||
return RC_NOT_FOUND;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1128,21 +1132,25 @@ void rc_url_builder_append_str_param(rc_api_url_builder_t* builder, const char*
|
|||
rc_url_builder_append_encoded_str(builder, value);
|
||||
}
|
||||
|
||||
void rc_api_url_build_dorequest_url(rc_api_request_t* request) {
|
||||
void rc_api_url_build_dorequest_url(rc_api_request_t* request, const rc_api_host_t* host) {
|
||||
#define DOREQUEST_ENDPOINT "/dorequest.php"
|
||||
rc_buffer_init(&request->buffer);
|
||||
|
||||
if (!g_host) {
|
||||
if (!host || !host->host) {
|
||||
request->url = RETROACHIEVEMENTS_HOST DOREQUEST_ENDPOINT;
|
||||
}
|
||||
else {
|
||||
const size_t endpoint_len = sizeof(DOREQUEST_ENDPOINT);
|
||||
const size_t host_len = strlen(g_host);
|
||||
const size_t url_len = host_len + endpoint_len;
|
||||
const size_t host_len = strlen(host->host);
|
||||
const size_t protocol_len = (strstr(host->host, "://")) ? 0 : 7;
|
||||
const size_t url_len = protocol_len + host_len + endpoint_len;
|
||||
uint8_t* url = rc_buffer_reserve(&request->buffer, url_len);
|
||||
|
||||
memcpy(url, g_host, host_len);
|
||||
memcpy(url + host_len, DOREQUEST_ENDPOINT, endpoint_len);
|
||||
if (protocol_len)
|
||||
memcpy(url, "http://", protocol_len);
|
||||
|
||||
memcpy(url + protocol_len, host->host, host_len);
|
||||
memcpy(url + protocol_len + host_len, DOREQUEST_ENDPOINT, endpoint_len);
|
||||
rc_buffer_consume(&request->buffer, url, url + url_len);
|
||||
|
||||
request->url = (char*)url;
|
||||
|
@ -1165,9 +1173,9 @@ int rc_api_url_build_dorequest(rc_api_url_builder_t* builder, const char* api, c
|
|||
|
||||
/* --- Set Host --- */
|
||||
|
||||
static void rc_api_update_host(char** host, const char* hostname) {
|
||||
static void rc_api_update_host(const char** host, const char* hostname) {
|
||||
if (*host != NULL)
|
||||
free(*host);
|
||||
free((void*)*host);
|
||||
|
||||
if (hostname != NULL) {
|
||||
if (strstr(hostname, "://")) {
|
||||
|
@ -1196,11 +1204,15 @@ static void rc_api_update_host(char** host, const char* hostname) {
|
|||
}
|
||||
}
|
||||
|
||||
const char* rc_api_default_host(void) {
|
||||
return RETROACHIEVEMENTS_HOST;
|
||||
}
|
||||
|
||||
void rc_api_set_host(const char* hostname) {
|
||||
if (hostname && strcmp(hostname, RETROACHIEVEMENTS_HOST) == 0)
|
||||
hostname = NULL;
|
||||
|
||||
rc_api_update_host(&g_host, hostname);
|
||||
rc_api_update_host(&g_host.host, hostname);
|
||||
|
||||
if (!hostname) {
|
||||
/* also clear out the image hostname */
|
||||
|
@ -1214,24 +1226,45 @@ void rc_api_set_host(const char* hostname) {
|
|||
}
|
||||
|
||||
void rc_api_set_image_host(const char* hostname) {
|
||||
rc_api_update_host(&g_imagehost, hostname);
|
||||
rc_api_update_host(&g_host.media_host, hostname);
|
||||
}
|
||||
|
||||
/* --- Fetch Image --- */
|
||||
|
||||
int rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params) {
|
||||
return rc_api_init_fetch_image_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_image_request_hosted(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params, const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_buffer_init(&request->buffer);
|
||||
rc_url_builder_init(&builder, &request->buffer, 64);
|
||||
|
||||
if (g_imagehost) {
|
||||
rc_url_builder_append(&builder, g_imagehost, strlen(g_imagehost));
|
||||
if (host && host->media_host) {
|
||||
/* custom media host provided */
|
||||
if (!strstr(host->host, "://"))
|
||||
rc_url_builder_append(&builder, "http://", 7);
|
||||
rc_url_builder_append(&builder, host->media_host, strlen(host->media_host));
|
||||
}
|
||||
else if (g_host) {
|
||||
rc_url_builder_append(&builder, g_host, strlen(g_host));
|
||||
else if (host && host->host) {
|
||||
if (strcmp(host->host, RETROACHIEVEMENTS_HOST_NONSSL) == 0) {
|
||||
/* if host specifically set to non-ssl host, and no media host provided, use non-ssl media host */
|
||||
rc_url_builder_append(&builder, RETROACHIEVEMENTS_IMAGE_HOST_NONSSL, sizeof(RETROACHIEVEMENTS_IMAGE_HOST_NONSSL) - 1);
|
||||
}
|
||||
else if (strcmp(host->host, RETROACHIEVEMENTS_HOST) == 0) {
|
||||
/* if host specifically set to ssl host, and no media host provided, use media host */
|
||||
rc_url_builder_append(&builder, RETROACHIEVEMENTS_IMAGE_HOST, sizeof(RETROACHIEVEMENTS_IMAGE_HOST) - 1);
|
||||
}
|
||||
else {
|
||||
/* custom host and no media host provided. assume custom host is also media host */
|
||||
if (!strstr(host->host, "://"))
|
||||
rc_url_builder_append(&builder, "http://", 7);
|
||||
rc_url_builder_append(&builder, host->host, strlen(host->host));
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* no custom host provided */
|
||||
rc_url_builder_append(&builder, RETROACHIEVEMENTS_IMAGE_HOST, sizeof(RETROACHIEVEMENTS_IMAGE_HOST) - 1);
|
||||
}
|
||||
|
||||
|
@ -1270,3 +1303,19 @@ int rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetc
|
|||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
const char* rc_api_build_avatar_url(rc_buffer_t* buffer, uint32_t image_type, const char* username) {
|
||||
rc_api_fetch_image_request_t image_request;
|
||||
rc_api_request_t request;
|
||||
int result;
|
||||
|
||||
memset(&image_request, 0, sizeof(image_request));
|
||||
image_request.image_type = image_type;
|
||||
image_request.image_name = username;
|
||||
|
||||
result = rc_api_init_fetch_image_request(&request, &image_request);
|
||||
if (result == RC_OK)
|
||||
return rc_buffer_strcpy(buffer, request.url);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ void rc_url_builder_init(rc_api_url_builder_t* builder, rc_buffer_t* buffer, siz
|
|||
void rc_url_builder_append(rc_api_url_builder_t* builder, const char* data, size_t len);
|
||||
const char* rc_url_builder_finalize(rc_api_url_builder_t* builder);
|
||||
|
||||
extern rc_api_host_t g_host;
|
||||
|
||||
#define RC_JSON_NEW_FIELD(n) {NULL,NULL,n,sizeof(n)-1,0}
|
||||
|
||||
typedef struct rc_json_field_t {
|
||||
|
@ -74,9 +76,12 @@ void rc_url_builder_append_num_param(rc_api_url_builder_t* builder, const char*
|
|||
void rc_url_builder_append_unum_param(rc_api_url_builder_t* builder, const char* param, uint32_t value);
|
||||
void rc_url_builder_append_str_param(rc_api_url_builder_t* builder, const char* param, const char* value);
|
||||
|
||||
void rc_api_url_build_dorequest_url(rc_api_request_t* request);
|
||||
const char* rc_api_default_host(void);
|
||||
void rc_api_url_build_dorequest_url(rc_api_request_t* request, const rc_api_host_t* host);
|
||||
int rc_api_url_build_dorequest(rc_api_url_builder_t* builder, const char* api, const char* username, const char* api_token);
|
||||
|
||||
const char* rc_api_build_avatar_url(rc_buffer_t* buffer, uint32_t image_type, const char* username);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_API_COMMON_H */
|
||||
|
|
|
@ -11,9 +11,15 @@
|
|||
/* --- Fetch Code Notes --- */
|
||||
|
||||
int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params) {
|
||||
return rc_api_init_fetch_code_notes_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_code_notes_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_code_notes_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -106,8 +112,14 @@ int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_resp
|
|||
if (!rc_json_get_required_string(¬e->author, &response->response, ¬e_fields[1], "User"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
last_author = note->author;
|
||||
last_author_len = len;
|
||||
if (note->author == NULL) {
|
||||
/* ensure we don't pass NULL out to client */
|
||||
last_author = note->author = "";
|
||||
last_author_len = 0;
|
||||
} else {
|
||||
last_author = note->author;
|
||||
last_author_len = len;
|
||||
}
|
||||
}
|
||||
|
||||
++note;
|
||||
|
@ -124,9 +136,15 @@ void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t
|
|||
/* --- Update Code Note --- */
|
||||
|
||||
int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params) {
|
||||
return rc_api_init_update_code_note_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_update_code_note_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_update_code_note_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -196,12 +214,18 @@ static const char* rc_type_string(uint32_t type) {
|
|||
}
|
||||
|
||||
int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params) {
|
||||
return rc_api_init_update_achievement_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_update_achievement_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_update_achievement_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
char buffer[33];
|
||||
md5_state_t md5;
|
||||
md5_byte_t hash[16];
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->game_id == 0 || api_params->category == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -290,12 +314,18 @@ void rc_api_destroy_update_achievement_response(rc_api_update_achievement_respon
|
|||
/* --- Update Leaderboard --- */
|
||||
|
||||
int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params) {
|
||||
return rc_api_init_update_leaderboard_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_update_leaderboard_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_update_leaderboard_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
char buffer[33];
|
||||
md5_state_t md5;
|
||||
md5_byte_t hash[16];
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -392,9 +422,15 @@ void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_respon
|
|||
/* --- Fetch Badge Range --- */
|
||||
|
||||
int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params) {
|
||||
return rc_api_init_fetch_badge_range_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_badge_range_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_badge_range_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
rc_url_builder_append_str_param(&builder, "r", "badgeiter");
|
||||
|
@ -449,9 +485,15 @@ void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response
|
|||
/* --- Add Game Hash --- */
|
||||
|
||||
int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params) {
|
||||
return rc_api_init_add_game_hash_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_add_game_hash_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_add_game_hash_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->console_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "rc_api_info.h"
|
||||
#include "rc_api_common.h"
|
||||
#include "rc_api_runtime.h"
|
||||
|
||||
#include "rc_consoles.h"
|
||||
#include "rc_runtime_types.h"
|
||||
|
@ -12,9 +13,15 @@
|
|||
/* --- Fetch Achievement Info --- */
|
||||
|
||||
int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params) {
|
||||
return rc_api_init_fetch_achievement_info_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_achievement_info_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_achievement_info_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->achievement_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -74,7 +81,8 @@ int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achieveme
|
|||
|
||||
rc_json_field_t entry_fields[] = {
|
||||
RC_JSON_NEW_FIELD("User"),
|
||||
RC_JSON_NEW_FIELD("DateAwarded")
|
||||
RC_JSON_NEW_FIELD("DateAwarded"),
|
||||
RC_JSON_NEW_FIELD("AvatarUrl")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
|
@ -117,6 +125,10 @@ int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achieveme
|
|||
return RC_MISSING_VALUE;
|
||||
entry->awarded = (time_t)timet;
|
||||
|
||||
rc_json_get_optional_string(&entry->avatar_url, &response->response, &entry_fields[2], "AvatarUrl", NULL);
|
||||
if (!entry->avatar_url)
|
||||
entry->avatar_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_USER, entry->username);
|
||||
|
||||
++entry;
|
||||
}
|
||||
}
|
||||
|
@ -131,9 +143,15 @@ void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_inf
|
|||
/* --- Fetch Leaderboard Info --- */
|
||||
|
||||
int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params) {
|
||||
return rc_api_init_fetch_leaderboard_info_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_leaderboard_info_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_leaderboard_info_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->leaderboard_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -192,13 +210,6 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa
|
|||
RC_JSON_NEW_FIELD("LBUpdated"),
|
||||
RC_JSON_NEW_FIELD("Entries"), /* array */
|
||||
RC_JSON_NEW_FIELD("TotalEntries")
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("GameTitle"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
RC_JSON_NEW_FIELD("ConsoleName"),
|
||||
RC_JSON_NEW_FIELD("ForumTopicID"),
|
||||
RC_JSON_NEW_FIELD("GameIcon")
|
||||
* unused fields */
|
||||
};
|
||||
|
||||
rc_json_field_t entry_fields[] = {
|
||||
|
@ -206,7 +217,8 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa
|
|||
RC_JSON_NEW_FIELD("Rank"),
|
||||
RC_JSON_NEW_FIELD("Index"),
|
||||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("DateSubmitted")
|
||||
RC_JSON_NEW_FIELD("DateSubmitted"),
|
||||
RC_JSON_NEW_FIELD("AvatarUrl")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
|
@ -282,6 +294,10 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa
|
|||
return RC_MISSING_VALUE;
|
||||
entry->submitted = (time_t)timet;
|
||||
|
||||
rc_json_get_optional_string(&entry->avatar_url, &response->response, &entry_fields[5], "AvatarUrl", NULL);
|
||||
if (!entry->avatar_url)
|
||||
entry->avatar_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_USER, entry->username);
|
||||
|
||||
++entry;
|
||||
}
|
||||
}
|
||||
|
@ -296,9 +312,15 @@ void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_inf
|
|||
/* --- Fetch Games List --- */
|
||||
|
||||
int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params) {
|
||||
return rc_api_init_fetch_games_list_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_games_list_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_games_list_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->console_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -381,11 +403,17 @@ void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t
|
|||
/* --- 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) {
|
||||
return rc_api_init_fetch_game_titles_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_game_titles_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_game_titles_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
char num[16];
|
||||
uint32_t i;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->num_game_ids == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -470,9 +498,16 @@ void rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response
|
|||
|
||||
int rc_api_init_fetch_hash_library_request(rc_api_request_t* request,
|
||||
const rc_api_fetch_hash_library_request_t* api_params)
|
||||
{
|
||||
return rc_api_init_fetch_hash_library_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_hash_library_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_hash_library_request_t* api_params,
|
||||
const rc_api_host_t* host)
|
||||
{
|
||||
rc_api_url_builder_t builder;
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->console_id == RC_CONSOLE_UNKNOWN)
|
||||
return RC_INVALID_STATE;
|
||||
|
|
|
@ -13,9 +13,15 @@
|
|||
/* --- Resolve Hash --- */
|
||||
|
||||
int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params) {
|
||||
return rc_api_init_resolve_hash_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_resolve_hash_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_resolve_hash_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (!api_params->game_hash || !*api_params->game_hash)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -65,16 +71,26 @@ void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* respon
|
|||
/* --- Fetch Game Data --- */
|
||||
|
||||
int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params) {
|
||||
return rc_api_init_fetch_game_data_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_game_data_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_game_data_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
if (api_params->game_id == 0 && (!api_params->game_hash || !api_params->game_hash[0]))
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
if (rc_api_url_build_dorequest(&builder, "patch", api_params->username, api_params->api_token)) {
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
if (api_params->game_id)
|
||||
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
|
||||
else
|
||||
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
|
||||
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
@ -92,8 +108,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r
|
|||
return rc_api_process_fetch_game_data_server_response(response, &response_obj);
|
||||
}
|
||||
|
||||
static int rc_parse_achievement_type(const char* type)
|
||||
{
|
||||
static int rc_parse_achievement_type(const char* type) {
|
||||
if (strcmp(type, "missable") == 0)
|
||||
return RC_ACHIEVEMENT_TYPE_MISSABLE;
|
||||
|
||||
|
@ -106,34 +121,14 @@ static int rc_parse_achievement_type(const char* type)
|
|||
return RC_ACHIEVEMENT_TYPE_STANDARD;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_api_achievement_definition_t* achievement;
|
||||
rc_api_leaderboard_definition_t* leaderboard;
|
||||
rc_json_field_t array_field;
|
||||
static int rc_api_process_fetch_game_data_achievements(rc_api_fetch_game_data_response_t* response, rc_api_achievement_definition_t* achievement, rc_json_field_t* array_field) {
|
||||
rc_json_iterator_t iterator;
|
||||
const char* last_author = "";
|
||||
const char* last_author_field = "";
|
||||
size_t last_author_len = 0;
|
||||
size_t len;
|
||||
uint32_t timet;
|
||||
int result;
|
||||
char format[16];
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("PatchData") /* nested object */
|
||||
};
|
||||
|
||||
rc_json_field_t patchdata_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
RC_JSON_NEW_FIELD("ImageIcon"),
|
||||
RC_JSON_NEW_FIELD("RichPresencePatch"),
|
||||
RC_JSON_NEW_FIELD("Achievements"), /* array */
|
||||
RC_JSON_NEW_FIELD("Leaderboards") /* array */
|
||||
};
|
||||
size_t len;
|
||||
char type[16];
|
||||
|
||||
rc_json_field_t achievement_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
|
@ -148,9 +143,106 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
|||
RC_JSON_NEW_FIELD("Modified"),
|
||||
RC_JSON_NEW_FIELD("Type"),
|
||||
RC_JSON_NEW_FIELD("Rarity"),
|
||||
RC_JSON_NEW_FIELD("RarityHardcore")
|
||||
RC_JSON_NEW_FIELD("RarityHardcore"),
|
||||
RC_JSON_NEW_FIELD("BadgeURL"),
|
||||
RC_JSON_NEW_FIELD("BadgeLockedURL")
|
||||
};
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field->value_start;
|
||||
iterator.end = array_field->value_end;
|
||||
|
||||
while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&achievement->id, &response->response, &achievement_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->title, &response->response, &achievement_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->description, &response->response, &achievement_fields[2], "Description"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&achievement->category, &response->response, &achievement_fields[3], "Flags"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&achievement->points, &response->response, &achievement_fields[4], "Points"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->definition, &response->response, &achievement_fields[5], "MemAddr"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
rc_json_get_optional_string(&achievement->badge_url, &response->response, &achievement_fields[13], "BadgeURL", "");
|
||||
if (!achievement->badge_url[0])
|
||||
achievement->badge_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_ACHIEVEMENT, achievement->badge_name);
|
||||
|
||||
rc_json_get_optional_string(&achievement->badge_locked_url, &response->response, &achievement_fields[14], "BadgeLockedURL", "");
|
||||
if (!achievement->badge_locked_url[0])
|
||||
achievement->badge_locked_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, achievement->badge_name);
|
||||
|
||||
len = achievement_fields[6].value_end - achievement_fields[6].value_start;
|
||||
if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) {
|
||||
achievement->author = last_author;
|
||||
}
|
||||
else {
|
||||
if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (achievement->author == NULL) {
|
||||
/* ensure we don't pass NULL out to client */
|
||||
last_author = achievement->author = "";
|
||||
last_author_len = 0;
|
||||
} else {
|
||||
last_author = achievement->author;
|
||||
last_author_field = achievement_fields[6].value_start;
|
||||
last_author_len = len;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created"))
|
||||
return RC_MISSING_VALUE;
|
||||
achievement->created = (time_t)timet;
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[9], "Modified"))
|
||||
return RC_MISSING_VALUE;
|
||||
achievement->updated = (time_t)timet;
|
||||
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
|
||||
if (achievement_fields[10].value_end) {
|
||||
len = achievement_fields[10].value_end - achievement_fields[10].value_start - 2;
|
||||
if (len < sizeof(type) - 1) {
|
||||
memcpy(type, achievement_fields[10].value_start + 1, len);
|
||||
type[len] = '\0';
|
||||
achievement->type = rc_parse_achievement_type(type);
|
||||
}
|
||||
}
|
||||
|
||||
/* legacy support : if title contains[m], change type to missable and remove[m] from title */
|
||||
if (memcmp(achievement->title, "[m]", 3) == 0) {
|
||||
len = 3;
|
||||
while (achievement->title[len] == ' ')
|
||||
++len;
|
||||
achievement->title += len;
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
|
||||
}
|
||||
else if (achievement_fields[1].value_end && memcmp(achievement_fields[1].value_end - 4, "[m]", 3) == 0) {
|
||||
len = strlen(achievement->title) - 3;
|
||||
while (achievement->title[len - 1] == ' ')
|
||||
--len;
|
||||
((char*)achievement->title)[len] = '\0';
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
|
||||
}
|
||||
|
||||
rc_json_get_optional_float(&achievement->rarity, &achievement_fields[11], "Rarity", 100.0);
|
||||
rc_json_get_optional_float(&achievement->rarity_hardcore, &achievement_fields[12], "RarityHardcore", 100.0);
|
||||
|
||||
++achievement;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_api_process_fetch_game_data_leaderboards(rc_api_fetch_game_data_response_t* response, rc_api_leaderboard_definition_t* leaderboard, rc_json_field_t* array_field) {
|
||||
rc_json_iterator_t iterator;
|
||||
size_t len;
|
||||
int result;
|
||||
char format[16];
|
||||
|
||||
rc_json_field_t leaderboard_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
|
@ -161,6 +253,141 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
|||
RC_JSON_NEW_FIELD("Hidden")
|
||||
};
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field->value_start;
|
||||
iterator.end = array_field->value_end;
|
||||
|
||||
while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&leaderboard->id, &response->response, &leaderboard_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->title, &response->response, &leaderboard_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->description, &response->response, &leaderboard_fields[2], "Description"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->definition, &response->response, &leaderboard_fields[3], "Mem"))
|
||||
return RC_MISSING_VALUE;
|
||||
rc_json_get_optional_bool(&result, &leaderboard_fields[5], "LowerIsBetter", 0);
|
||||
leaderboard->lower_is_better = (uint8_t)result;
|
||||
rc_json_get_optional_bool(&result, &leaderboard_fields[6], "Hidden", 0);
|
||||
leaderboard->hidden = (uint8_t)result;
|
||||
|
||||
if (!leaderboard_fields[4].value_end)
|
||||
return RC_MISSING_VALUE;
|
||||
len = leaderboard_fields[4].value_end - leaderboard_fields[4].value_start - 2;
|
||||
if (len < sizeof(format) - 1) {
|
||||
memcpy(format, leaderboard_fields[4].value_start + 1, len);
|
||||
format[len] = '\0';
|
||||
leaderboard->format = rc_parse_format(format);
|
||||
}
|
||||
else {
|
||||
leaderboard->format = RC_FORMAT_VALUE;
|
||||
}
|
||||
|
||||
++leaderboard;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
static int rc_api_process_fetch_game_data_subsets(rc_api_fetch_game_data_response_t* response, rc_api_subset_definition_t* subset, rc_json_field_t* subset_array_field) {
|
||||
rc_json_iterator_t iterator;
|
||||
rc_json_field_t array_field;
|
||||
size_t len;
|
||||
int result;
|
||||
|
||||
rc_json_field_t subset_fields[] = {
|
||||
RC_JSON_NEW_FIELD("GameAchievementSetID"),
|
||||
RC_JSON_NEW_FIELD("SetTitle"),
|
||||
RC_JSON_NEW_FIELD("ImageIcon"),
|
||||
RC_JSON_NEW_FIELD("ImageIconURL"),
|
||||
RC_JSON_NEW_FIELD("Achievements"), /* array */
|
||||
RC_JSON_NEW_FIELD("Leaderboards") /* array */
|
||||
};
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = subset_array_field->value_start;
|
||||
iterator.end = subset_array_field->value_end;
|
||||
|
||||
while (rc_json_get_array_entry_object(subset_fields, sizeof(subset_fields) / sizeof(subset_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&subset->id, &response->response, &subset_fields[0], "GameAchievementSetID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&subset->title, &response->response, &subset_fields[1], "SetTitle"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
|
||||
rc_json_extract_filename(&subset_fields[2]);
|
||||
rc_json_get_optional_string(&subset->image_name, &response->response, &subset_fields[2], "ImageIcon", "");
|
||||
|
||||
if (!rc_json_get_required_string(&subset->image_url, &response->response, &subset_fields[3], "ImageIconURL"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
/* estimate the amount of space necessary to store the achievements, and leaderboards.
|
||||
determine how much space each takes as a string in the JSON, then subtract out the non-data (field names, punctuation)
|
||||
and add space for the structures. */
|
||||
len = (subset_fields[4].value_end - subset_fields[4].value_start) - /* achievements */
|
||||
subset_fields[4].array_size * (80 - sizeof(rc_api_achievement_definition_t));
|
||||
len += (subset_fields[5].value_end - subset_fields[5].value_start) - /* leaderboards */
|
||||
subset_fields[5].array_size * (60 - sizeof(rc_api_leaderboard_definition_t));
|
||||
|
||||
rc_buffer_reserve(&response->response.buffer, len);
|
||||
/* end estimation */
|
||||
|
||||
if (!rc_json_get_required_array(&subset->num_achievements, &array_field, &response->response, &subset_fields[4], "Achievements"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (subset->num_achievements) {
|
||||
subset->achievements = (rc_api_achievement_definition_t*)rc_buffer_alloc(&response->response.buffer, subset->num_achievements * sizeof(rc_api_achievement_definition_t));
|
||||
if (!subset->achievements)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
result = rc_api_process_fetch_game_data_achievements(response, subset->achievements, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_array(&subset->num_leaderboards, &array_field, &response->response, &subset_fields[5], "Leaderboards"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (subset->num_leaderboards) {
|
||||
subset->leaderboards = (rc_api_leaderboard_definition_t*)rc_buffer_alloc(&response->response.buffer, subset->num_leaderboards * sizeof(rc_api_leaderboard_definition_t));
|
||||
if (!subset->leaderboards)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
result = rc_api_process_fetch_game_data_leaderboards(response, subset->leaderboards, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
++subset;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_json_field_t array_field;
|
||||
size_t len;
|
||||
int result;
|
||||
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Code"),
|
||||
RC_JSON_NEW_FIELD("PatchData") /* nested object */
|
||||
};
|
||||
|
||||
rc_json_field_t patchdata_fields[] = {
|
||||
RC_JSON_NEW_FIELD("ID"),
|
||||
RC_JSON_NEW_FIELD("Title"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
RC_JSON_NEW_FIELD("ImageIcon"),
|
||||
RC_JSON_NEW_FIELD("ImageIconURL"),
|
||||
RC_JSON_NEW_FIELD("RichPresencePatch"),
|
||||
RC_JSON_NEW_FIELD("Achievements"), /* array */
|
||||
RC_JSON_NEW_FIELD("Leaderboards"), /* array */
|
||||
RC_JSON_NEW_FIELD("Sets") /* array */
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
rc_buffer_init(&response->response.buffer);
|
||||
|
||||
|
@ -168,7 +395,7 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
|||
if (result != RC_OK || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_object(patchdata_fields, sizeof(patchdata_fields) / sizeof(patchdata_fields[0]), &response->response, &fields[2], "PatchData"))
|
||||
if (!rc_json_get_required_object(patchdata_fields, sizeof(patchdata_fields) / sizeof(patchdata_fields[0]), &response->response, &fields[3], "PatchData"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!rc_json_get_required_unum(&response->id, &response->response, &patchdata_fields[0], "ID"))
|
||||
|
@ -181,26 +408,29 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
|||
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
|
||||
rc_json_extract_filename(&patchdata_fields[3]);
|
||||
rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", "");
|
||||
rc_json_get_optional_string(&response->image_url, &response->response, &patchdata_fields[4], "ImageIconURL", "");
|
||||
if (!response->image_url[0])
|
||||
response->image_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_GAME, response->image_name);
|
||||
|
||||
/* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards.
|
||||
determine how much space each takes as a string in the JSON, then subtract out the non-data (field names, punctuation)
|
||||
and add space for the structures. */
|
||||
len = patchdata_fields[4].value_end - patchdata_fields[4].value_start; /* rich presence */
|
||||
len = patchdata_fields[5].value_end - patchdata_fields[5].value_start; /* rich presence */
|
||||
|
||||
len += (patchdata_fields[5].value_end - patchdata_fields[5].value_start) - /* achievements */
|
||||
patchdata_fields[5].array_size * (130 - sizeof(rc_api_achievement_definition_t));
|
||||
len += (patchdata_fields[6].value_end - patchdata_fields[6].value_start) - /* achievements */
|
||||
patchdata_fields[6].array_size * (80 - sizeof(rc_api_achievement_definition_t));
|
||||
|
||||
len += (patchdata_fields[6].value_end - patchdata_fields[6].value_start) - /* leaderboards */
|
||||
patchdata_fields[6].array_size * (60 - sizeof(rc_api_leaderboard_definition_t));
|
||||
len += (patchdata_fields[7].value_end - patchdata_fields[7].value_start) - /* leaderboards */
|
||||
patchdata_fields[7].array_size * (60 - sizeof(rc_api_leaderboard_definition_t));
|
||||
|
||||
rc_buffer_reserve(&response->response.buffer, len);
|
||||
/* end estimation */
|
||||
|
||||
rc_json_get_optional_string(&response->rich_presence_script, &response->response, &patchdata_fields[4], "RichPresencePatch", "");
|
||||
rc_json_get_optional_string(&response->rich_presence_script, &response->response, &patchdata_fields[5], "RichPresencePatch", "");
|
||||
if (!response->rich_presence_script)
|
||||
response->rich_presence_script = "";
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_achievements, &array_field, &response->response, &patchdata_fields[5], "Achievements"))
|
||||
if (!rc_json_get_required_array(&response->num_achievements, &array_field, &response->response, &patchdata_fields[6], "Achievements"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_achievements) {
|
||||
|
@ -208,87 +438,12 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
|||
if (!response->achievements)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
achievement = response->achievements;
|
||||
while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&achievement->id, &response->response, &achievement_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->title, &response->response, &achievement_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->description, &response->response, &achievement_fields[2], "Description"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&achievement->category, &response->response, &achievement_fields[3], "Flags"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&achievement->points, &response->response, &achievement_fields[4], "Points"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->definition, &response->response, &achievement_fields[5], "MemAddr"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
len = achievement_fields[6].value_end - achievement_fields[6].value_start;
|
||||
if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) {
|
||||
achievement->author = last_author;
|
||||
}
|
||||
else {
|
||||
if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (achievement->author == NULL) {
|
||||
/* ensure we don't pass NULL out to client */
|
||||
last_author = achievement->author = "";
|
||||
last_author_len = 0;
|
||||
} else {
|
||||
last_author = achievement->author;
|
||||
last_author_field = achievement_fields[6].value_start;
|
||||
last_author_len = len;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created"))
|
||||
return RC_MISSING_VALUE;
|
||||
achievement->created = (time_t)timet;
|
||||
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[9], "Modified"))
|
||||
return RC_MISSING_VALUE;
|
||||
achievement->updated = (time_t)timet;
|
||||
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
|
||||
if (achievement_fields[10].value_end) {
|
||||
len = achievement_fields[10].value_end - achievement_fields[10].value_start - 2;
|
||||
if (len < sizeof(format) - 1) {
|
||||
memcpy(format, achievement_fields[10].value_start + 1, len);
|
||||
format[len] = '\0';
|
||||
achievement->type = rc_parse_achievement_type(format);
|
||||
}
|
||||
}
|
||||
|
||||
/* legacy support : if title contains[m], change type to missable and remove[m] from title */
|
||||
if (memcmp(achievement->title, "[m]", 3) == 0) {
|
||||
len = 3;
|
||||
while (achievement->title[len] == ' ')
|
||||
++len;
|
||||
achievement->title += len;
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
|
||||
}
|
||||
else if (achievement_fields[1].value_end && memcmp(achievement_fields[1].value_end - 4, "[m]", 3) == 0) {
|
||||
len = strlen(achievement->title) - 3;
|
||||
while (achievement->title[len - 1] == ' ')
|
||||
--len;
|
||||
((char*)achievement->title)[len] = '\0';
|
||||
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
|
||||
}
|
||||
|
||||
rc_json_get_optional_float(&achievement->rarity, &achievement_fields[11], "Rarity", 100.0);
|
||||
rc_json_get_optional_float(&achievement->rarity_hardcore, &achievement_fields[12], "RarityHardcore", 100.0);
|
||||
|
||||
++achievement;
|
||||
}
|
||||
result = rc_api_process_fetch_game_data_achievements(response, response->achievements, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_leaderboards, &array_field, &response->response, &patchdata_fields[6], "Leaderboards"))
|
||||
if (!rc_json_get_required_array(&response->num_leaderboards, &array_field, &response->response, &patchdata_fields[7], "Leaderboards"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_leaderboards) {
|
||||
|
@ -296,39 +451,20 @@ int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_respon
|
|||
if (!response->leaderboards)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
result = rc_api_process_fetch_game_data_leaderboards(response, response->leaderboards, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
leaderboard = response->leaderboards;
|
||||
while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_unum(&leaderboard->id, &response->response, &leaderboard_fields[0], "ID"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->title, &response->response, &leaderboard_fields[1], "Title"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->description, &response->response, &leaderboard_fields[2], "Description"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&leaderboard->definition, &response->response, &leaderboard_fields[3], "Mem"))
|
||||
return RC_MISSING_VALUE;
|
||||
rc_json_get_optional_bool(&result, &leaderboard_fields[5], "LowerIsBetter", 0);
|
||||
leaderboard->lower_is_better = (uint8_t)result;
|
||||
rc_json_get_optional_bool(&result, &leaderboard_fields[6], "Hidden", 0);
|
||||
leaderboard->hidden = (uint8_t)result;
|
||||
rc_json_get_optional_array(&response->num_subsets, &array_field, &patchdata_fields[8], "Sets");
|
||||
if (response->num_subsets) {
|
||||
response->subsets = (rc_api_subset_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_subsets * sizeof(rc_api_subset_definition_t));
|
||||
if (!response->subsets)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
if (!leaderboard_fields[4].value_end)
|
||||
return RC_MISSING_VALUE;
|
||||
len = leaderboard_fields[4].value_end - leaderboard_fields[4].value_start - 2;
|
||||
if (len < sizeof(format) - 1) {
|
||||
memcpy(format, leaderboard_fields[4].value_start + 1, len);
|
||||
format[len] = '\0';
|
||||
leaderboard->format = rc_parse_format(format);
|
||||
}
|
||||
else {
|
||||
leaderboard->format = RC_FORMAT_VALUE;
|
||||
}
|
||||
|
||||
++leaderboard;
|
||||
}
|
||||
result = rc_api_process_fetch_game_data_subsets(response, response->subsets, &array_field);
|
||||
if (result != RC_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
|
@ -341,9 +477,15 @@ void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t*
|
|||
/* --- Ping --- */
|
||||
|
||||
int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params) {
|
||||
return rc_api_init_ping_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_ping_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_ping_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -396,12 +538,18 @@ void rc_api_destroy_ping_response(rc_api_ping_response_t* response) {
|
|||
/* --- Award Achievement --- */
|
||||
|
||||
int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params) {
|
||||
return rc_api_init_award_achievement_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_award_achievement_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_award_achievement_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
char buffer[33];
|
||||
md5_state_t md5;
|
||||
md5_byte_t digest[16];
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->achievement_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -497,12 +645,18 @@ void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response
|
|||
/* --- Submit Leaderboard Entry --- */
|
||||
|
||||
int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params) {
|
||||
return rc_api_init_submit_lboard_entry_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_submit_lboard_entry_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_submit_lboard_entry_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
char buffer[33];
|
||||
md5_state_t md5;
|
||||
md5_byte_t digest[16];
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->leaderboard_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "rc_api_user.h"
|
||||
#include "rc_api_common.h"
|
||||
#include "rc_api_runtime.h"
|
||||
#include "rc_consoles.h"
|
||||
|
||||
#include "../rc_version.h"
|
||||
|
@ -10,9 +11,15 @@
|
|||
/* --- Login --- */
|
||||
|
||||
int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params) {
|
||||
return rc_api_init_login_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_login_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_login_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (!api_params->username || !*api_params->username)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -55,7 +62,7 @@ int rc_api_process_login_server_response(rc_api_login_response_t* response, cons
|
|||
RC_JSON_NEW_FIELD("Score"),
|
||||
RC_JSON_NEW_FIELD("SoftcoreScore"),
|
||||
RC_JSON_NEW_FIELD("Messages"),
|
||||
RC_JSON_NEW_FIELD("DisplayName")
|
||||
RC_JSON_NEW_FIELD("AvatarUrl")
|
||||
};
|
||||
|
||||
memset(response, 0, sizeof(*response));
|
||||
|
@ -74,7 +81,14 @@ int rc_api_process_login_server_response(rc_api_login_response_t* response, cons
|
|||
rc_json_get_optional_unum(&response->score_softcore, &fields[6], "SoftcoreScore", 0);
|
||||
rc_json_get_optional_unum(&response->num_unread_messages, &fields[7], "Messages", 0);
|
||||
|
||||
rc_json_get_optional_string(&response->display_name, &response->response, &fields[8], "DisplayName", response->username);
|
||||
/* For the highest level of backwards compatibility, we have decided to just send the
|
||||
* display_name back to the client as the "case-corrected username", and allow it to
|
||||
* be used as the Username field for the other APIs. */
|
||||
response->display_name = response->username;
|
||||
|
||||
rc_json_get_optional_string(&response->avatar_url, &response->response, &fields[8], "AvatarUrl", NULL);
|
||||
if (!response->avatar_url)
|
||||
response->avatar_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_USER, response->username);
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
@ -86,9 +100,15 @@ void rc_api_destroy_login_response(rc_api_login_response_t* response) {
|
|||
/* --- Start Session --- */
|
||||
|
||||
int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params) {
|
||||
return rc_api_init_start_session_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_start_session_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_start_session_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->game_id == 0)
|
||||
return RC_INVALID_STATE;
|
||||
|
@ -203,9 +223,15 @@ void rc_api_destroy_start_session_response(rc_api_start_session_response_t* resp
|
|||
/* --- Fetch User Unlocks --- */
|
||||
|
||||
int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params) {
|
||||
return rc_api_init_fetch_user_unlocks_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_user_unlocks_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_user_unlocks_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
if (rc_api_url_build_dorequest(&builder, "unlocks", api_params->username, api_params->api_token)) {
|
||||
|
@ -255,14 +281,114 @@ void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_respon
|
|||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Fetch Followed Users --- */
|
||||
|
||||
int rc_api_init_fetch_followed_users_request(rc_api_request_t* request, const rc_api_fetch_followed_users_request_t* api_params) {
|
||||
return rc_api_init_fetch_followed_users_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_followed_users_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_followed_users_request_t* api_params,
|
||||
const rc_api_host_t* host) {
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
rc_url_builder_init(&builder, &request->buffer, 48);
|
||||
if (rc_api_url_build_dorequest(&builder, "getfriendlist", api_params->username, api_params->api_token)) {
|
||||
request->post_data = rc_url_builder_finalize(&builder);
|
||||
request->content_type = RC_CONTENT_TYPE_URLENCODED;
|
||||
}
|
||||
|
||||
return builder.result;
|
||||
}
|
||||
|
||||
int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_users_response_t* response, const rc_api_server_response_t* server_response) {
|
||||
rc_json_field_t array_field;
|
||||
rc_json_iterator_t iterator;
|
||||
rc_api_followed_user_t* user;
|
||||
uint32_t timet;
|
||||
int result;
|
||||
rc_json_field_t fields[] = {
|
||||
RC_JSON_NEW_FIELD("Success"),
|
||||
RC_JSON_NEW_FIELD("Error"),
|
||||
RC_JSON_NEW_FIELD("Friends")
|
||||
};
|
||||
|
||||
rc_json_field_t followed_user_entry_fields[] = {
|
||||
RC_JSON_NEW_FIELD("Friend"),
|
||||
RC_JSON_NEW_FIELD("AvatarUrl"),
|
||||
RC_JSON_NEW_FIELD("RAPoints"),
|
||||
RC_JSON_NEW_FIELD("LastSeen"),
|
||||
RC_JSON_NEW_FIELD("LastSeenTime"),
|
||||
RC_JSON_NEW_FIELD("LastGameId"),
|
||||
RC_JSON_NEW_FIELD("LastGameTitle"),
|
||||
RC_JSON_NEW_FIELD("LastGameIconUrl"),
|
||||
};
|
||||
|
||||
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 || !response->response.succeeded)
|
||||
return result;
|
||||
|
||||
if (!rc_json_get_required_array(&response->num_users, &array_field, &response->response, &fields[2], "Friends"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (response->num_users) {
|
||||
response->users = (rc_api_followed_user_t*)rc_buffer_alloc(&response->response.buffer, response->num_users * sizeof(rc_api_followed_user_t));
|
||||
if (!response->users)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
memset(&iterator, 0, sizeof(iterator));
|
||||
iterator.json = array_field.value_start;
|
||||
iterator.end = array_field.value_end;
|
||||
|
||||
user = response->users;
|
||||
while (rc_json_get_array_entry_object(followed_user_entry_fields, sizeof(followed_user_entry_fields) / sizeof(followed_user_entry_fields[0]), &iterator)) {
|
||||
if (!rc_json_get_required_string(&user->display_name, &response->response, &followed_user_entry_fields[0], "Friend"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_string(&user->avatar_url, &response->response, &followed_user_entry_fields[1], "AvatarUrl"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&user->score, &response->response, &followed_user_entry_fields[2], "RAPoints"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
rc_json_get_optional_string(&user->recent_activity.description, &response->response, &followed_user_entry_fields[3], "LastSeen", NULL);
|
||||
|
||||
rc_json_get_optional_unum(&timet, &followed_user_entry_fields[4], "LastSeenTime", 0);
|
||||
user->recent_activity.when = (time_t)timet;
|
||||
|
||||
rc_json_get_optional_unum(&user->recent_activity.context_id, &followed_user_entry_fields[5], "LastGameId", 0);
|
||||
rc_json_get_optional_string(&user->recent_activity.context, &response->response, &followed_user_entry_fields[6], "LastGameTitle", NULL);
|
||||
rc_json_get_optional_string(&user->recent_activity.context_image_url, &response->response, &followed_user_entry_fields[7], "LastGameIconUrl", NULL);
|
||||
|
||||
++user;
|
||||
}
|
||||
}
|
||||
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
void rc_api_destroy_fetch_followed_users_response(rc_api_fetch_followed_users_response_t* response) {
|
||||
rc_buffer_destroy(&response->response.buffer);
|
||||
}
|
||||
|
||||
/* --- Fetch All Progress --- */
|
||||
|
||||
int rc_api_init_fetch_all_progress_request(rc_api_request_t* request,
|
||||
const rc_api_fetch_all_progress_request_t* api_params)
|
||||
{
|
||||
return rc_api_init_fetch_all_progress_request_hosted(request, api_params, &g_host);
|
||||
}
|
||||
|
||||
int rc_api_init_fetch_all_progress_request_hosted(rc_api_request_t* request,
|
||||
const rc_api_fetch_all_progress_request_t* api_params,
|
||||
const rc_api_host_t* host)
|
||||
{
|
||||
rc_api_url_builder_t builder;
|
||||
|
||||
rc_api_url_build_dorequest_url(request);
|
||||
rc_api_url_build_dorequest_url(request, host);
|
||||
|
||||
if (api_params->console_id == RC_CONSOLE_UNKNOWN)
|
||||
return RC_INVALID_STATE;
|
||||
|
|
|
@ -641,7 +641,7 @@ static int rc_client_get_image_url(char buffer[], size_t buffer_size, int image_
|
|||
memset(&image_request, 0, sizeof(image_request));
|
||||
image_request.image_type = image_type;
|
||||
image_request.image_name = image_name;
|
||||
result = rc_api_init_fetch_image_request(&request, &image_request);
|
||||
result = rc_api_init_fetch_image_request_hosted(&request, &image_request, NULL);
|
||||
if (result == RC_OK)
|
||||
{
|
||||
const size_t url_length = strlen(request.url);
|
||||
|
@ -708,6 +708,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp
|
|||
else
|
||||
client->user.display_name = rc_buffer_strcpy(&client->state.buffer, login_response.display_name);
|
||||
|
||||
client->user.avatar_url = rc_buffer_strcpy(&client->state.buffer, login_response.avatar_url);
|
||||
client->user.token = rc_buffer_strcpy(&client->state.buffer, login_response.api_token);
|
||||
client->user.score = login_response.score;
|
||||
client->user.score_softcore = login_response.score_softcore;
|
||||
|
@ -736,7 +737,7 @@ static rc_client_async_handle_t* rc_client_begin_login(rc_client_t* client,
|
|||
{
|
||||
rc_client_generic_callback_data_t* callback_data;
|
||||
rc_api_request_t request;
|
||||
int result = rc_api_init_login_request(&request, login_request);
|
||||
int result = rc_api_init_login_request_hosted(&request, login_request, &client->state.host);
|
||||
const char* error_message = rc_error_str(result);
|
||||
|
||||
if (result == RC_OK) {
|
||||
|
@ -891,8 +892,13 @@ const rc_client_user_t* rc_client_get_user_info(const rc_client_t* client)
|
|||
return NULL;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->get_user_info)
|
||||
return client->state.external_client->get_user_info();
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->get_user_info_v3)
|
||||
return client->state.external_client->get_user_info_v3();
|
||||
|
||||
if (client->state.external_client->get_user_info)
|
||||
return rc_client_external_convert_v1_user(client, client->state.external_client->get_user_info());
|
||||
}
|
||||
#endif
|
||||
|
||||
return (client->state.user == RC_CLIENT_USER_STATE_LOGGED_IN) ? &client->user : NULL;
|
||||
|
@ -903,6 +909,11 @@ int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], si
|
|||
if (!user)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
if (user->avatar_url) {
|
||||
snprintf(buffer, buffer_size, "%s", user->avatar_url);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
return rc_client_get_image_url(buffer, buffer_size, RC_IMAGE_TYPE_USER, user->display_name);
|
||||
}
|
||||
|
||||
|
@ -1480,6 +1491,9 @@ static void rc_client_apply_unlocks(rc_client_subset_info_t* subset, rc_api_unlo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subset->next)
|
||||
rc_client_apply_unlocks(subset->next, unlocks, num_unlocks, mode);
|
||||
}
|
||||
|
||||
static void rc_client_free_pending_media(rc_client_pending_media_t* pending_media)
|
||||
|
@ -1692,7 +1706,7 @@ static void rc_client_begin_start_session(rc_client_load_state_t* load_state)
|
|||
start_session_params.game_hash = load_state->hash->hash;
|
||||
start_session_params.hardcore = client->state.hardcore;
|
||||
|
||||
result = rc_api_init_start_session_request(&start_session_request, &start_session_params);
|
||||
result = rc_api_init_start_session_request_hosted(&start_session_request, &start_session_params, &client->state.host);
|
||||
if (result != RC_OK) {
|
||||
rc_client_load_error(load_state, result, rc_error_str(result));
|
||||
}
|
||||
|
@ -1771,11 +1785,13 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state,
|
|||
achievement->public_.rarity = read->rarity;
|
||||
achievement->public_.rarity_hardcore = read->rarity_hardcore;
|
||||
achievement->public_.type = read->type; /* assert: mapping is 1:1 */
|
||||
achievement->public_.badge_url = rc_buffer_strcpy(buffer, read->badge_url);
|
||||
achievement->public_.badge_locked_url = rc_buffer_strcpy(buffer, read->badge_locked_url);
|
||||
|
||||
memaddr = read->definition;
|
||||
rc_runtime_checksum(memaddr, achievement->md5);
|
||||
|
||||
rc_init_preparse_state(&preparse, NULL, 0);
|
||||
rc_init_preparse_state(&preparse);
|
||||
preparse.parse.existing_memrefs = load_state->game->runtime.memrefs;
|
||||
trigger = RC_ALLOC(rc_trigger_t, &preparse.parse);
|
||||
rc_parse_trigger_internal(trigger, &memaddr, &preparse.parse);
|
||||
|
@ -1788,7 +1804,7 @@ static void rc_client_copy_achievements(rc_client_load_state_t* load_state,
|
|||
}
|
||||
else {
|
||||
/* populate the item, using the communal memrefs pool */
|
||||
rc_reset_parse_state(&preparse.parse, rc_buffer_reserve(buffer, trigger_size), NULL, 0);
|
||||
rc_reset_parse_state(&preparse.parse, rc_buffer_reserve(buffer, trigger_size));
|
||||
rc_preparse_reserve_memrefs(&preparse, load_state->game->runtime.memrefs);
|
||||
achievement->trigger = RC_ALLOC(rc_trigger_t, &preparse.parse);
|
||||
memaddr = read->definition;
|
||||
|
@ -1921,7 +1937,7 @@ static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
|
|||
leaderboard->value_djb2 = hash;
|
||||
}
|
||||
|
||||
rc_init_preparse_state(&preparse, NULL, 0);
|
||||
rc_init_preparse_state(&preparse);
|
||||
preparse.parse.existing_memrefs = load_state->game->runtime.memrefs;
|
||||
lboard = RC_ALLOC(rc_lboard_t, &preparse.parse);
|
||||
rc_parse_lboard_internal(lboard, memaddr, &preparse.parse);
|
||||
|
@ -1933,7 +1949,7 @@ static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
|
|||
}
|
||||
else {
|
||||
/* populate the item, using the communal memrefs pool */
|
||||
rc_reset_parse_state(&preparse.parse, rc_buffer_reserve(buffer, lboard_size), NULL, 0);
|
||||
rc_reset_parse_state(&preparse.parse, rc_buffer_reserve(buffer, lboard_size));
|
||||
rc_preparse_reserve_memrefs(&preparse, load_state->game->runtime.memrefs);
|
||||
leaderboard->lboard = RC_ALLOC(rc_lboard_t, &preparse.parse);
|
||||
rc_parse_lboard_internal(leaderboard->lboard, memaddr, &preparse.parse);
|
||||
|
@ -1956,23 +1972,6 @@ static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state,
|
|||
subset->leaderboards = leaderboards;
|
||||
}
|
||||
|
||||
static const char* rc_client_subset_extract_title(rc_client_game_info_t* game, const char* title)
|
||||
{
|
||||
const char* subset_prefix = strstr(title, "[Subset - ");
|
||||
if (subset_prefix) {
|
||||
const char* start = subset_prefix + 10;
|
||||
const char* stop = strstr(start, "]");
|
||||
const size_t len = stop - start;
|
||||
char* result = (char*)rc_buffer_alloc(&game->buffer, len + 1);
|
||||
|
||||
memcpy(result, start, len);
|
||||
result[len] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* server_response, void* callback_data)
|
||||
{
|
||||
rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data;
|
||||
|
@ -1998,21 +1997,38 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
|
|||
|
||||
outstanding_requests = rc_client_end_load_state(load_state);
|
||||
|
||||
if (error_message) {
|
||||
if (error_message && result != RC_NOT_FOUND) {
|
||||
rc_client_load_error(load_state, result, error_message);
|
||||
}
|
||||
else if (outstanding_requests < 0) {
|
||||
/* previous load state was aborted, load_state was free'd */
|
||||
}
|
||||
else if (fetch_game_data_response.id == 0) {
|
||||
load_state->hash->game_id = 0;
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
}
|
||||
else {
|
||||
rc_client_subset_info_t* subset;
|
||||
rc_client_subset_info_t** next_subset;
|
||||
rc_client_subset_info_t* core_subset;
|
||||
uint32_t subset_index;
|
||||
|
||||
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_.id = fetch_game_data_response.id;
|
||||
subset->active = 1;
|
||||
snprintf(subset->public_.badge_name, sizeof(subset->public_.badge_name), "%s", fetch_game_data_response.image_name);
|
||||
load_state->subset = subset;
|
||||
/* hash exists outside the load state - always update it */
|
||||
load_state->hash->game_id = fetch_game_data_response.id;
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(load_state->client, "Identified game: %u \"%s\" (%s)", load_state->hash->game_id, fetch_game_data_response.title, load_state->hash->hash);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
core_subset = (rc_client_subset_info_t*)rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_subset_info_t));
|
||||
memset(core_subset, 0, sizeof(*core_subset));
|
||||
core_subset->public_.id = fetch_game_data_response.id;
|
||||
core_subset->active = 1;
|
||||
snprintf(core_subset->public_.badge_name, sizeof(core_subset->public_.badge_name), "%s", fetch_game_data_response.image_name);
|
||||
core_subset->public_.badge_url = rc_buffer_strcpy(&load_state->game->buffer, fetch_game_data_response.image_url);
|
||||
load_state->subset = core_subset;
|
||||
|
||||
if (load_state->game->public_.console_id != RC_CONSOLE_UNKNOWN &&
|
||||
fetch_game_data_response.console_id != load_state->game->public_.console_id) {
|
||||
|
@ -2031,54 +2047,47 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
|
|||
}
|
||||
|
||||
/* process the game data */
|
||||
rc_client_copy_achievements(load_state, subset,
|
||||
rc_client_copy_achievements(load_state, core_subset,
|
||||
fetch_game_data_response.achievements, fetch_game_data_response.num_achievements);
|
||||
rc_client_copy_leaderboards(load_state, subset,
|
||||
rc_client_copy_leaderboards(load_state, core_subset,
|
||||
fetch_game_data_response.leaderboards, fetch_game_data_response.num_leaderboards);
|
||||
|
||||
if (!load_state->game->subsets) {
|
||||
/* core set */
|
||||
rc_mutex_lock(&load_state->client->state.mutex);
|
||||
load_state->game->public_.title = rc_buffer_strcpy(&load_state->game->buffer, fetch_game_data_response.title);
|
||||
load_state->game->subsets = subset;
|
||||
load_state->game->public_.badge_name = subset->public_.badge_name;
|
||||
load_state->game->public_.console_id = fetch_game_data_response.console_id;
|
||||
rc_mutex_unlock(&load_state->client->state.mutex);
|
||||
/* core set */
|
||||
rc_mutex_lock(&load_state->client->state.mutex);
|
||||
load_state->game->public_.title = rc_buffer_strcpy(&load_state->game->buffer, fetch_game_data_response.title);
|
||||
load_state->game->subsets = core_subset;
|
||||
load_state->game->public_.badge_name = core_subset->public_.badge_name;
|
||||
load_state->game->public_.badge_url = core_subset->public_.badge_url;
|
||||
load_state->game->public_.console_id = fetch_game_data_response.console_id;
|
||||
rc_mutex_unlock(&load_state->client->state.mutex);
|
||||
|
||||
subset->public_.title = load_state->game->public_.title;
|
||||
core_subset->public_.title = load_state->game->public_.title;
|
||||
|
||||
if (fetch_game_data_response.rich_presence_script && fetch_game_data_response.rich_presence_script[0]) {
|
||||
result = rc_runtime_activate_richpresence(&load_state->game->runtime, fetch_game_data_response.rich_presence_script, NULL, 0);
|
||||
if (result != RC_OK) {
|
||||
RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Parse error %d processing rich presence", result);
|
||||
}
|
||||
if (fetch_game_data_response.rich_presence_script && fetch_game_data_response.rich_presence_script[0]) {
|
||||
result = rc_runtime_activate_richpresence(&load_state->game->runtime, fetch_game_data_response.rich_presence_script, NULL, 0);
|
||||
if (result != RC_OK) {
|
||||
RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Parse error %d processing rich presence", result);
|
||||
}
|
||||
}
|
||||
else {
|
||||
rc_client_subset_info_t* scan;
|
||||
|
||||
/* subset - extract subset title */
|
||||
subset->public_.title = rc_client_subset_extract_title(load_state->game, fetch_game_data_response.title);
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
next_subset = &core_subset->next;
|
||||
for (subset_index = 0; subset_index < fetch_game_data_response.num_subsets; ++subset_index) {
|
||||
rc_api_subset_definition_t* api_subset = &fetch_game_data_response.subsets[subset_index];
|
||||
rc_client_subset_info_t* subset;
|
||||
|
||||
subset->public_.title = rc_buffer_strcpy(&load_state->game->buffer, fetch_game_data_response.title);
|
||||
}
|
||||
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_.id = api_subset->id;
|
||||
subset->active = 1;
|
||||
snprintf(subset->public_.badge_name, sizeof(subset->public_.badge_name), "%s", api_subset->image_name);
|
||||
subset->public_.badge_url = rc_buffer_strcpy(&load_state->game->buffer, api_subset->image_url);
|
||||
subset->public_.title = rc_buffer_strcpy(&load_state->game->buffer, api_subset->title);
|
||||
|
||||
/* append to subset list */
|
||||
scan = load_state->game->subsets;
|
||||
while (scan->next)
|
||||
scan = scan->next;
|
||||
scan->next = subset;
|
||||
rc_client_copy_achievements(load_state, subset, api_subset->achievements, api_subset->num_achievements);
|
||||
rc_client_copy_leaderboards(load_state, subset, api_subset->leaderboards, api_subset->num_leaderboards);
|
||||
|
||||
*next_subset = subset;
|
||||
next_subset = &subset->next;
|
||||
}
|
||||
|
||||
if (load_state->client->callbacks.post_process_game_data_response) {
|
||||
|
@ -2178,8 +2187,8 @@ static void rc_client_external_load_state_callback(int result, const char* error
|
|||
}
|
||||
else {
|
||||
/* keep partial game object for media_hash management */
|
||||
if (client->state.external_client && client->state.external_client->get_game_info) {
|
||||
const rc_client_game_t* info = client->state.external_client->get_game_info();
|
||||
if (client->state.external_client) {
|
||||
const rc_client_game_t* info = rc_client_get_game_info(client);
|
||||
load_state->game->public_.console_id = info->console_id;
|
||||
client->game = load_state->game;
|
||||
load_state->game = NULL;
|
||||
|
@ -2194,6 +2203,23 @@ static void rc_client_external_load_state_callback(int result, const char* error
|
|||
|
||||
#endif
|
||||
|
||||
static void rc_client_initialize_unknown_game(rc_client_game_info_t* game)
|
||||
{
|
||||
rc_client_subset_info_t* subset;
|
||||
char buffer[64];
|
||||
|
||||
subset = (rc_client_subset_info_t*)rc_buffer_alloc(&game->buffer, sizeof(rc_client_subset_info_t));
|
||||
memset(subset, 0, sizeof(*subset));
|
||||
subset->public_.title = "";
|
||||
game->subsets = subset;
|
||||
|
||||
game->public_.title = "Unknown Game";
|
||||
game->public_.badge_name = "";
|
||||
|
||||
rc_client_get_image_url(buffer, sizeof(buffer), RC_IMAGE_TYPE_GAME, "000001");
|
||||
game->public_.badge_url = rc_buffer_strcpy(&game->buffer, buffer);
|
||||
}
|
||||
|
||||
static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
|
||||
{
|
||||
rc_client_t* client = load_state->client;
|
||||
|
@ -2254,6 +2280,7 @@ static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
|
|||
load_state->hash_console_id, load_state->hash->hash, client, load_state->callback_userdata);
|
||||
|
||||
if (load_state->hash->game_id != 0) {
|
||||
load_state->hash->is_unknown = 1;
|
||||
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);
|
||||
}
|
||||
|
@ -2276,16 +2303,8 @@ static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
|
|||
}
|
||||
else {
|
||||
#endif
|
||||
/* mimics rc_client_load_unknown_game without allocating a new game object */
|
||||
rc_client_subset_info_t* subset;
|
||||
rc_client_initialize_unknown_game(load_state->game);
|
||||
|
||||
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;
|
||||
|
||||
|
@ -2323,20 +2342,13 @@ static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
|
|||
|
||||
void rc_client_load_unknown_game(rc_client_t* client, const char* tried_hashes)
|
||||
{
|
||||
rc_client_subset_info_t* subset;
|
||||
rc_client_game_info_t* game;
|
||||
|
||||
game = rc_client_allocate_game();
|
||||
if (!game)
|
||||
return;
|
||||
|
||||
subset = (rc_client_subset_info_t*)rc_buffer_alloc(&game->buffer, sizeof(rc_client_subset_info_t));
|
||||
memset(subset, 0, sizeof(*subset));
|
||||
subset->public_.title = "";
|
||||
game->subsets = subset;
|
||||
|
||||
game->public_.title = "Unknown Game";
|
||||
game->public_.badge_name = "";
|
||||
rc_client_initialize_unknown_game(game);
|
||||
game->public_.console_id = RC_CONSOLE_UNKNOWN;
|
||||
|
||||
if (strlen(tried_hashes) == 32) { /* only one hash tried, add it to the list */
|
||||
|
@ -2381,23 +2393,28 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
|||
memset(&fetch_game_data_request, 0, sizeof(fetch_game_data_request));
|
||||
fetch_game_data_request.username = client->user.username;
|
||||
fetch_game_data_request.api_token = client->user.token;
|
||||
fetch_game_data_request.game_id = load_state->hash->game_id;
|
||||
|
||||
result = rc_api_init_fetch_game_data_request(&request, &fetch_game_data_request);
|
||||
if (load_state->hash->is_unknown) /* lookup failed, but client provided a mapping */
|
||||
fetch_game_data_request.game_id = load_state->hash->game_id;
|
||||
else
|
||||
fetch_game_data_request.game_hash = load_state->hash->hash;
|
||||
|
||||
result = rc_api_init_fetch_game_data_request_hosted(&request, &fetch_game_data_request, &client->state.host);
|
||||
if (result != RC_OK) {
|
||||
rc_client_load_error(load_state, result, rc_error_str(result));
|
||||
return;
|
||||
}
|
||||
|
||||
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, 1);
|
||||
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, 1);
|
||||
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Fetching data for hash %s", fetch_game_data_request.game_hash);
|
||||
|
||||
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);
|
||||
client->callbacks.server_call(&request, rc_client_fetch_game_data_callback, load_state, client);
|
||||
|
||||
rc_api_destroy_request(&request);
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
static void rc_client_identify_game_callback(const rc_api_server_response_t* server_response, void* callback_data)
|
||||
{
|
||||
rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data;
|
||||
|
@ -2442,6 +2459,7 @@ static void rc_client_identify_game_callback(const rc_api_server_response_t* ser
|
|||
|
||||
rc_api_destroy_resolve_hash_response(&resolve_hash_response);
|
||||
}
|
||||
#endif
|
||||
|
||||
rc_client_game_hash_t* rc_client_find_game_hash(rc_client_t* client, const char* hash)
|
||||
{
|
||||
|
@ -2507,7 +2525,14 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
|
|||
load_state->game->media_hash->game_hash = load_state->hash;
|
||||
}
|
||||
|
||||
if (load_state->hash->game_id == RC_CLIENT_UNKNOWN_GAME_ID) {
|
||||
if (load_state->hash->game_id == 0) {
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
}
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
else if (load_state->hash->game_id == RC_CLIENT_UNKNOWN_GAME_ID &&
|
||||
client->state.external_client && client->state.external_client->add_game_hash) {
|
||||
/* if an add_game_hash external handler exists, do the identification locally, then
|
||||
* pass the resulting game_id/hash to the external client */
|
||||
rc_api_resolve_hash_request_t resolve_hash_request;
|
||||
rc_api_request_t request;
|
||||
int result;
|
||||
|
@ -2515,7 +2540,7 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
|
|||
memset(&resolve_hash_request, 0, sizeof(resolve_hash_request));
|
||||
resolve_hash_request.game_hash = hash;
|
||||
|
||||
result = rc_api_init_resolve_hash_request(&request, &resolve_hash_request);
|
||||
result = rc_api_init_resolve_hash_request_hosted(&request, &resolve_hash_request, &client->state.host);
|
||||
if (result != RC_OK) {
|
||||
rc_client_load_error(load_state, result, rc_error_str(result));
|
||||
return NULL;
|
||||
|
@ -2528,10 +2553,9 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
|
|||
|
||||
rc_api_destroy_request(&request);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "Identified game: %u (%s)", load_state->hash->game_id, load_state->hash->hash);
|
||||
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
}
|
||||
|
||||
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
|
||||
|
@ -2684,19 +2708,17 @@ int rc_client_get_load_game_state(const rc_client_t* client)
|
|||
|
||||
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
|
||||
if (client->state.external_client) {
|
||||
const rc_client_game_t* game = rc_client_get_game_info(client);
|
||||
return (game && game->id != 0);
|
||||
}
|
||||
#endif
|
||||
game = client->game ? &client->game->public_ : NULL;
|
||||
|
||||
return (game && game->id != 0);
|
||||
return (client->game && client->game->public_.id != 0);
|
||||
}
|
||||
|
||||
static void rc_client_game_mark_ui_to_be_hidden(rc_client_t* client, rc_client_game_info_t* game)
|
||||
|
@ -2885,7 +2907,7 @@ static rc_client_async_handle_t* rc_client_begin_change_media_internal(rc_client
|
|||
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);
|
||||
result = rc_api_init_resolve_hash_request_hosted(&request, &resolve_hash_request, &client->state.host);
|
||||
if (result != RC_OK) {
|
||||
callback(result, rc_error_str(result), client, callback_userdata);
|
||||
return NULL;
|
||||
|
@ -3128,8 +3150,13 @@ const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client)
|
|||
return NULL;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->get_game_info)
|
||||
return client->state.external_client->get_game_info();
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->get_game_info_v3)
|
||||
return client->state.external_client->get_game_info_v3();
|
||||
|
||||
if (client->state.external_client->get_game_info)
|
||||
return rc_client_external_convert_v1_game(client, client->state.external_client->get_game_info());
|
||||
}
|
||||
#endif
|
||||
|
||||
return client->game ? &client->game->public_ : NULL;
|
||||
|
@ -3140,52 +3167,16 @@ int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], si
|
|||
if (!game)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
if (game->badge_url) {
|
||||
snprintf(buffer, buffer_size, "%s", game->badge_url);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
return rc_client_get_image_url(buffer, buffer_size, RC_IMAGE_TYPE_GAME, game->badge_name);
|
||||
}
|
||||
|
||||
/* ===== Subsets ===== */
|
||||
|
||||
rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata)
|
||||
{
|
||||
char buffer[32];
|
||||
rc_client_load_state_t* load_state;
|
||||
|
||||
if (!client) {
|
||||
callback(RC_INVALID_STATE, "client is required", client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->begin_load_subset)
|
||||
return client->state.external_client->begin_load_subset(client, subset_id, callback, callback_userdata);
|
||||
#endif
|
||||
|
||||
if (!rc_client_is_game_loaded(client)) {
|
||||
callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "[SUBSET%lu]", (unsigned long)subset_id);
|
||||
|
||||
load_state = (rc_client_load_state_t*)calloc(1, sizeof(*load_state));
|
||||
if (!load_state) {
|
||||
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
load_state->client = client;
|
||||
load_state->callback = callback;
|
||||
load_state->callback_userdata = callback_userdata;
|
||||
load_state->game = client->game;
|
||||
load_state->hash = rc_client_find_game_hash(client, buffer);
|
||||
load_state->hash->game_id = subset_id;
|
||||
client->state.load = load_state;
|
||||
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
|
||||
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
|
||||
}
|
||||
|
||||
const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id)
|
||||
{
|
||||
rc_client_subset_info_t* subset;
|
||||
|
@ -3194,8 +3185,13 @@ const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_
|
|||
return NULL;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->get_subset_info)
|
||||
return client->state.external_client->get_subset_info(subset_id);
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->get_subset_info_v3)
|
||||
return client->state.external_client->get_subset_info_v3(subset_id);
|
||||
|
||||
if (client->state.external_client->get_subset_info)
|
||||
return rc_client_external_convert_v1_subset(client, client->state.external_client->get_subset_info(subset_id));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!client->game)
|
||||
|
@ -3309,7 +3305,7 @@ rc_client_async_handle_t* rc_client_begin_fetch_hash_library(rc_client_t* client
|
|||
}
|
||||
|
||||
api_params.console_id = console_id;
|
||||
result = rc_api_init_fetch_hash_library_request(&request, &api_params);
|
||||
result = rc_api_init_fetch_hash_library_request_hosted(&request, &api_params, &client->state.host);
|
||||
|
||||
if (result != RC_OK)
|
||||
{
|
||||
|
@ -3441,7 +3437,7 @@ rc_client_async_handle_t* rc_client_begin_fetch_all_progress_list(rc_client_t* c
|
|||
api_params.api_token = client->user.token;
|
||||
api_params.console_id = console_id;
|
||||
|
||||
result = rc_api_init_fetch_all_progress_request(&request, &api_params);
|
||||
result = rc_api_init_fetch_all_progress_request_hosted(&request, &api_params, &client->state.host);
|
||||
|
||||
if (result != RC_OK)
|
||||
{
|
||||
|
@ -3520,8 +3516,11 @@ static void rc_client_update_achievement_display_information(rc_client_t* client
|
|||
achievement->public_.measured_percent = ((float)new_measured_value * 100) / (float)achievement->trigger->measured_target;
|
||||
|
||||
if (!achievement->trigger->measured_as_percent) {
|
||||
snprintf(achievement->public_.measured_progress, sizeof(achievement->public_.measured_progress),
|
||||
"%lu/%lu", (unsigned long)new_measured_value, (unsigned long)achievement->trigger->measured_target);
|
||||
char* ptr = achievement->public_.measured_progress;
|
||||
const int buffer_size = (int)sizeof(achievement->public_.measured_progress);
|
||||
const int chars = rc_format_value(ptr, buffer_size, (int32_t)new_measured_value, RC_FORMAT_UNSIGNED_VALUE);
|
||||
ptr[chars] = '/';
|
||||
rc_format_value(ptr + chars + 1, buffer_size - chars - 1, (int32_t)achievement->trigger->measured_target, RC_FORMAT_UNSIGNED_VALUE);
|
||||
}
|
||||
else if (achievement->public_.measured_percent >= 1.0) {
|
||||
snprintf(achievement->public_.measured_progress, sizeof(achievement->public_.measured_progress),
|
||||
|
@ -3627,8 +3626,8 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
|
|||
{
|
||||
rc_client_achievement_info_t* achievement;
|
||||
rc_client_achievement_info_t* stop;
|
||||
rc_client_achievement_t** bucket_achievements;
|
||||
rc_client_achievement_t** achievement_ptr;
|
||||
const rc_client_achievement_t** bucket_achievements;
|
||||
const rc_client_achievement_t** achievement_ptr;
|
||||
rc_client_achievement_bucket_t* bucket_ptr;
|
||||
rc_client_achievement_list_info_t* list;
|
||||
rc_client_subset_info_t* subset;
|
||||
|
@ -3658,8 +3657,14 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
|
|||
return (rc_client_achievement_list_t*)calloc(1, sizeof(rc_client_achievement_list_info_t));
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->create_achievement_list)
|
||||
return (rc_client_achievement_list_t*)client->state.external_client->create_achievement_list(category, grouping);
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->create_achievement_list_v3)
|
||||
return (rc_client_achievement_list_t*)client->state.external_client->create_achievement_list_v3(category, grouping);
|
||||
|
||||
if (client->state.external_client->create_achievement_list)
|
||||
return rc_client_external_convert_v1_achievement_list(client,
|
||||
(rc_client_achievement_list_t*)client->state.external_client->create_achievement_list(category, grouping));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!client->game)
|
||||
|
@ -3729,8 +3734,8 @@ rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* cli
|
|||
buckets_size = RC_ALIGN(num_buckets * sizeof(rc_client_achievement_bucket_t));
|
||||
|
||||
list = (rc_client_achievement_list_info_t*)malloc(list_size + buckets_size + num_achievements * sizeof(rc_client_achievement_t*));
|
||||
bucket_ptr = list->public_.buckets = (rc_client_achievement_bucket_t*)((uint8_t*)list + list_size);
|
||||
achievement_ptr = (rc_client_achievement_t**)((uint8_t*)bucket_ptr + buckets_size);
|
||||
list->public_.buckets = bucket_ptr = (rc_client_achievement_bucket_t*)((uint8_t*)list + list_size);
|
||||
achievement_ptr = (const rc_client_achievement_t**)((uint8_t*)bucket_ptr + buckets_size);
|
||||
|
||||
if (grouping == RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS) {
|
||||
for (i = 0; i < sizeof(shared_bucket_order) / sizeof(shared_bucket_order[0]); ++i) {
|
||||
|
@ -3885,8 +3890,13 @@ const rc_client_achievement_t* rc_client_get_achievement_info(rc_client_t* clien
|
|||
return NULL;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->get_achievement_info)
|
||||
return client->state.external_client->get_achievement_info(id);
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->get_achievement_info_v3)
|
||||
return client->state.external_client->get_achievement_info_v3(id);
|
||||
|
||||
if (client->state.external_client->get_achievement_info)
|
||||
return rc_client_external_convert_v1_achievement(client, client->state.external_client->get_achievement_info(id));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!client->game)
|
||||
|
@ -3909,6 +3919,16 @@ int rc_client_achievement_get_image_url(const rc_client_achievement_t* achieveme
|
|||
if (!achievement || !achievement->badge_name[0])
|
||||
return rc_client_get_image_url(buffer, buffer_size, image_type, "00000");
|
||||
|
||||
if (image_type == RC_IMAGE_TYPE_ACHIEVEMENT && achievement->badge_url) {
|
||||
snprintf(buffer, buffer_size, "%s", achievement->badge_url);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
if (image_type == RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED && achievement->badge_locked_url) {
|
||||
snprintf(buffer, buffer_size, "%s", achievement->badge_locked_url);
|
||||
return RC_OK;
|
||||
}
|
||||
|
||||
return rc_client_get_image_url(buffer, buffer_size, image_type, achievement->badge_name);
|
||||
}
|
||||
|
||||
|
@ -4053,8 +4073,7 @@ static void rc_client_award_achievement_callback(const rc_api_server_response_t*
|
|||
RC_CLIENT_LOG_INFO_FORMATTED(ach_data->client, "Subset %u %s", ach_data->client->game->public_.id,
|
||||
ach_data->client->state.hardcore ? "mastered" : "completed");
|
||||
|
||||
/* TODO: subset mastery notification */
|
||||
subset->mastery = RC_CLIENT_MASTERY_STATE_SHOWN;
|
||||
subset->mastery = RC_CLIENT_MASTERY_STATE_PENDING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4088,7 +4107,7 @@ static void rc_client_award_achievement_server_call(rc_client_award_achievement_
|
|||
api_params.seconds_since_unlock = (uint32_t)((now - ach_data->unlock_time) / 1000);
|
||||
}
|
||||
|
||||
result = rc_api_init_award_achievement_request(&request, &api_params);
|
||||
result = rc_api_init_award_achievement_request_hosted(&request, &api_params, &ach_data->client->state.host);
|
||||
if (result != RC_OK) {
|
||||
RC_CLIENT_LOG_ERR_FORMATTED(ach_data->client, "Error constructing unlock request for achievement %u: %s", ach_data->id, rc_error_str(result));
|
||||
free(ach_data);
|
||||
|
@ -4286,8 +4305,8 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
|
|||
{
|
||||
rc_client_leaderboard_info_t* leaderboard;
|
||||
rc_client_leaderboard_info_t* stop;
|
||||
rc_client_leaderboard_t** bucket_leaderboards;
|
||||
rc_client_leaderboard_t** leaderboard_ptr;
|
||||
const rc_client_leaderboard_t** bucket_leaderboards;
|
||||
const rc_client_leaderboard_t** leaderboard_ptr;
|
||||
rc_client_leaderboard_bucket_t* bucket_ptr;
|
||||
rc_client_leaderboard_list_info_t* list;
|
||||
rc_client_subset_info_t* subset;
|
||||
|
@ -4382,8 +4401,8 @@ rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* cli
|
|||
buckets_size = RC_ALIGN(num_buckets * sizeof(rc_client_leaderboard_bucket_t));
|
||||
|
||||
list = (rc_client_leaderboard_list_info_t*)malloc(list_size + buckets_size + num_leaderboards * sizeof(rc_client_leaderboard_t*));
|
||||
bucket_ptr = list->public_.buckets = (rc_client_leaderboard_bucket_t*)((uint8_t*)list + list_size);
|
||||
leaderboard_ptr = (rc_client_leaderboard_t**)((uint8_t*)bucket_ptr + buckets_size);
|
||||
list->public_.buckets = bucket_ptr = (rc_client_leaderboard_bucket_t*)((uint8_t*)list + list_size);
|
||||
leaderboard_ptr = (const rc_client_leaderboard_t**)((uint8_t*)bucket_ptr + buckets_size);
|
||||
|
||||
if (grouping == RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING) {
|
||||
for (i = 0; i < sizeof(shared_bucket_order) / sizeof(shared_bucket_order[0]); ++i) {
|
||||
|
@ -4761,7 +4780,7 @@ static void rc_client_submit_leaderboard_entry_server_call(rc_client_submit_lead
|
|||
api_params.seconds_since_completion = (uint32_t)((now - lboard_data->submit_time) / 1000);
|
||||
}
|
||||
|
||||
result = rc_api_init_submit_lboard_entry_request(&request, &api_params);
|
||||
result = rc_api_init_submit_lboard_entry_request_hosted(&request, &api_params, &lboard_data->client->state.host);
|
||||
if (result != RC_OK) {
|
||||
RC_CLIENT_LOG_ERR_FORMATTED(lboard_data->client, "Error constructing submit leaderboard entry for leaderboard %u: %s", lboard_data->id, rc_error_str(result));
|
||||
return;
|
||||
|
@ -4935,7 +4954,7 @@ static rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_info(rc_clien
|
|||
int result;
|
||||
const char* error_message;
|
||||
|
||||
result = rc_api_init_fetch_leaderboard_info_request(&request, lbinfo_request);
|
||||
result = rc_api_init_fetch_leaderboard_info_request_hosted(&request, lbinfo_request, &client->state.host);
|
||||
|
||||
if (result != RC_OK) {
|
||||
error_message = rc_error_str(result);
|
||||
|
@ -5061,7 +5080,7 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r
|
|||
api_params.game_hash = client->game->public_.hash;
|
||||
api_params.hardcore = client->state.hardcore;
|
||||
|
||||
result = rc_api_init_ping_request(&request, &api_params);
|
||||
result = rc_api_init_ping_request_hosted(&request, &api_params, &client->state.host);
|
||||
if (result != RC_OK) {
|
||||
RC_CLIENT_LOG_WARN_FORMATTED(client, "Error generating ping request: %s", rc_error_str(result));
|
||||
}
|
||||
|
@ -5124,7 +5143,7 @@ size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], s
|
|||
int rc_client_get_rich_presence_strings(rc_client_t* client, const char** buffer, size_t buffer_size, size_t* count) {
|
||||
int result;
|
||||
|
||||
if (!client || !buffer)
|
||||
if (!client || !client->game || !buffer)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
|
@ -5324,7 +5343,7 @@ static void rc_client_update_memref_values(rc_client_t* client) {
|
|||
}
|
||||
|
||||
if (client->game->runtime.richpresence && client->game->runtime.richpresence->richpresence)
|
||||
rc_update_values(client->game->runtime.richpresence->richpresence->values, client->state.legacy_peek, client, NULL);
|
||||
rc_update_values(client->game->runtime.richpresence->richpresence->values, client->state.legacy_peek, client);
|
||||
|
||||
if (invalidated_memref)
|
||||
rc_client_update_active_achievements(client->game);
|
||||
|
@ -5521,7 +5540,12 @@ static void rc_client_raise_mastery_event(rc_client_t* client, rc_client_subset_
|
|||
rc_client_event_t client_event;
|
||||
|
||||
memset(&client_event, 0, sizeof(client_event));
|
||||
client_event.type = RC_CLIENT_EVENT_GAME_COMPLETED;
|
||||
client_event.subset = &subset->public_;
|
||||
|
||||
if (subset == client->game->subsets)
|
||||
client_event.type = RC_CLIENT_EVENT_GAME_COMPLETED;
|
||||
else
|
||||
client_event.type = RC_CLIENT_EVENT_SUBSET_COMPLETED;
|
||||
|
||||
subset->mastery = RC_CLIENT_MASTERY_STATE_SHOWN;
|
||||
|
||||
|
@ -6392,23 +6416,28 @@ void* rc_client_get_userdata(const rc_client_t* client)
|
|||
return client ? client->callbacks.client_data : NULL;
|
||||
}
|
||||
|
||||
void rc_client_set_host(const rc_client_t* client, const char* hostname)
|
||||
void rc_client_set_host(rc_client_t* client, const char* hostname)
|
||||
{
|
||||
/* if empty, just pass NULL */
|
||||
if (hostname && !hostname[0])
|
||||
hostname = NULL;
|
||||
if (!client)
|
||||
return;
|
||||
|
||||
/* clear the image host so it'll use the custom host for images too */
|
||||
rc_api_set_image_host(NULL);
|
||||
if (client->state.host.host && hostname && strcmp(hostname, client->state.host.host) == 0)
|
||||
return;
|
||||
|
||||
/* set the custom host */
|
||||
if (hostname && client) {
|
||||
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Using host: %s", hostname);
|
||||
/* clear out any previously specified host information */
|
||||
memset(&client->state.host, 0, sizeof(client->state.host));
|
||||
|
||||
if (hostname && (!hostname[0] || strcmp(hostname, rc_api_default_host()) == 0)) {
|
||||
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Using host: %s", rc_api_default_host());
|
||||
hostname = rc_api_default_host();
|
||||
}
|
||||
else {
|
||||
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Using host: %s", hostname);
|
||||
client->state.host.host = rc_buffer_strcpy(&client->state.buffer, hostname);
|
||||
}
|
||||
rc_api_set_host(hostname);
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client && client->state.external_client && client->state.external_client->set_host)
|
||||
if (client->state.external_client && client->state.external_client->set_host)
|
||||
client->state.external_client->set_host(hostname);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
#include "rc_client_external.h"
|
||||
|
||||
#include "rc_client_external_versions.h"
|
||||
#include "rc_client_internal.h"
|
||||
|
||||
#define RC_CONVERSION_FILL(obj, obj_type, src_type) memset((uint8_t*)obj + sizeof(src_type), 0, sizeof(obj_type) - sizeof(src_type))
|
||||
|
||||
typedef struct rc_client_external_conversions_t {
|
||||
rc_client_user_t user;
|
||||
rc_client_game_t game;
|
||||
rc_client_subset_t subsets[4];
|
||||
rc_client_achievement_t achievements[16];
|
||||
uint32_t next_subset_index;
|
||||
uint32_t next_achievement_index;
|
||||
} rc_client_external_conversions_t;
|
||||
|
||||
static void rc_client_external_conversions_init(const rc_client_t* client)
|
||||
{
|
||||
if (!client->state.external_client_conversions) {
|
||||
rc_client_t* mutable_client = (rc_client_t*)client;
|
||||
rc_client_external_conversions_t* conversions = (rc_client_external_conversions_t*)
|
||||
rc_buffer_alloc(&mutable_client->state.buffer, sizeof(rc_client_external_conversions_t));
|
||||
|
||||
memset(conversions, 0, sizeof(*conversions));
|
||||
|
||||
mutable_client->state.external_client_conversions = conversions;
|
||||
}
|
||||
}
|
||||
|
||||
const rc_client_user_t* rc_client_external_convert_v1_user(const rc_client_t* client, const rc_client_user_t* v1_user)
|
||||
{
|
||||
rc_client_user_t* converted;
|
||||
|
||||
if (!v1_user)
|
||||
return NULL;
|
||||
|
||||
rc_client_external_conversions_init(client);
|
||||
|
||||
converted = &client->state.external_client_conversions->user;
|
||||
memcpy(converted, v1_user, sizeof(v1_rc_client_user_t));
|
||||
RC_CONVERSION_FILL(converted, rc_client_user_t, v1_rc_client_user_t);
|
||||
return converted;
|
||||
}
|
||||
|
||||
const rc_client_game_t* rc_client_external_convert_v1_game(const rc_client_t* client, const rc_client_game_t* v1_game)
|
||||
{
|
||||
rc_client_game_t* converted;
|
||||
|
||||
if (!v1_game)
|
||||
return NULL;
|
||||
|
||||
rc_client_external_conversions_init(client);
|
||||
|
||||
converted = &client->state.external_client_conversions->game;
|
||||
memcpy(converted, v1_game, sizeof(v1_rc_client_game_t));
|
||||
RC_CONVERSION_FILL(converted, rc_client_game_t, v1_rc_client_game_t);
|
||||
return converted;
|
||||
}
|
||||
|
||||
const rc_client_subset_t* rc_client_external_convert_v1_subset(const rc_client_t* client, const rc_client_subset_t* v1_subset)
|
||||
{
|
||||
rc_client_subset_t* converted = NULL;
|
||||
const uint32_t num_subsets = sizeof(client->state.external_client_conversions->subsets) / sizeof(client->state.external_client_conversions->subsets[0]);
|
||||
uint32_t index;
|
||||
|
||||
if (!v1_subset)
|
||||
return NULL;
|
||||
|
||||
rc_client_external_conversions_init(client);
|
||||
|
||||
for (index = 0; index < num_subsets; ++index) {
|
||||
if (client->state.external_client_conversions->subsets[index].id == v1_subset->id) {
|
||||
converted = &client->state.external_client_conversions->subsets[index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!converted) {
|
||||
converted = &client->state.external_client_conversions->subsets[client->state.external_client_conversions->next_subset_index];
|
||||
client->state.external_client_conversions->next_subset_index = (client->state.external_client_conversions->next_subset_index + 1) % num_subsets;
|
||||
}
|
||||
|
||||
memcpy(converted, v1_subset, sizeof(v1_rc_client_subset_t));
|
||||
RC_CONVERSION_FILL(converted, rc_client_subset_t, v1_rc_client_subset_t);
|
||||
return converted;
|
||||
}
|
||||
|
||||
const rc_client_achievement_t* rc_client_external_convert_v1_achievement(const rc_client_t* client, const rc_client_achievement_t* v1_achievement)
|
||||
{
|
||||
rc_client_achievement_t* converted = NULL;
|
||||
const uint32_t num_achievements = sizeof(client->state.external_client_conversions->achievements) / sizeof(client->state.external_client_conversions->achievements[0]);
|
||||
uint32_t index;
|
||||
|
||||
if (!v1_achievement)
|
||||
return NULL;
|
||||
|
||||
rc_client_external_conversions_init(client);
|
||||
|
||||
for (index = 0; index < num_achievements; ++index) {
|
||||
if (client->state.external_client_conversions->achievements[index].id == v1_achievement->id) {
|
||||
converted = &client->state.external_client_conversions->achievements[index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!converted) {
|
||||
converted = &client->state.external_client_conversions->achievements[client->state.external_client_conversions->next_achievement_index];
|
||||
client->state.external_client_conversions->next_achievement_index = (client->state.external_client_conversions->next_achievement_index + 1) % num_achievements;
|
||||
}
|
||||
|
||||
memcpy(converted, v1_achievement, sizeof(v1_rc_client_achievement_t));
|
||||
RC_CONVERSION_FILL(converted, rc_client_achievement_t, v1_rc_client_achievement_t);
|
||||
return converted;
|
||||
}
|
||||
|
||||
typedef struct rc_client_achievement_list_wrapper_t {
|
||||
rc_client_achievement_list_info_t info;
|
||||
rc_client_achievement_list_t* source_list;
|
||||
rc_client_achievement_t* achievements;
|
||||
rc_client_achievement_t** achievements_pointers;
|
||||
} rc_client_achievement_list_wrapper_t;
|
||||
|
||||
static void rc_client_destroy_achievement_list_wrapper(rc_client_achievement_list_info_t* info)
|
||||
{
|
||||
rc_client_achievement_list_wrapper_t* wrapper = (rc_client_achievement_list_wrapper_t*)info;
|
||||
|
||||
if (wrapper->achievements)
|
||||
free(wrapper->achievements);
|
||||
if (wrapper->achievements_pointers)
|
||||
free(wrapper->achievements_pointers);
|
||||
if (wrapper->info.public_.buckets)
|
||||
free((void*)wrapper->info.public_.buckets);
|
||||
|
||||
rc_client_destroy_achievement_list(wrapper->source_list);
|
||||
|
||||
free(wrapper);
|
||||
}
|
||||
|
||||
rc_client_achievement_list_t* rc_client_external_convert_v1_achievement_list(const rc_client_t* client, rc_client_achievement_list_t* v1_achievement_list)
|
||||
{
|
||||
rc_client_achievement_list_wrapper_t* new_list;
|
||||
(void)client;
|
||||
|
||||
if (!v1_achievement_list)
|
||||
return NULL;
|
||||
|
||||
new_list = (rc_client_achievement_list_wrapper_t*)calloc(1, sizeof(*new_list));
|
||||
if (!new_list)
|
||||
return NULL;
|
||||
|
||||
new_list->source_list = v1_achievement_list;
|
||||
new_list->info.destroy_func = rc_client_destroy_achievement_list_wrapper;
|
||||
|
||||
if (v1_achievement_list->num_buckets) {
|
||||
const v1_rc_client_achievement_bucket_t* src_bucket = (const v1_rc_client_achievement_bucket_t*)&v1_achievement_list->buckets[0];
|
||||
const v1_rc_client_achievement_bucket_t* stop_bucket = src_bucket + v1_achievement_list->num_buckets;
|
||||
rc_client_achievement_bucket_t* bucket;
|
||||
uint32_t num_achievements = 0;
|
||||
|
||||
new_list->info.public_.buckets = bucket = (rc_client_achievement_bucket_t*)calloc(v1_achievement_list->num_buckets, sizeof(*new_list->info.public_.buckets));
|
||||
if (!new_list->info.public_.buckets)
|
||||
return (rc_client_achievement_list_t*)new_list;
|
||||
|
||||
for (; src_bucket < stop_bucket; src_bucket++)
|
||||
num_achievements += src_bucket->num_achievements;
|
||||
|
||||
if (num_achievements) {
|
||||
new_list->achievements = (rc_client_achievement_t*)calloc(num_achievements, sizeof(*new_list->achievements));
|
||||
new_list->achievements_pointers = (rc_client_achievement_t**)malloc(num_achievements * sizeof(rc_client_achievement_t*));
|
||||
if (!new_list->achievements || !new_list->achievements_pointers)
|
||||
return (rc_client_achievement_list_t*)new_list;
|
||||
}
|
||||
|
||||
num_achievements = 0;
|
||||
src_bucket = (const v1_rc_client_achievement_bucket_t*)&v1_achievement_list->buckets[0];
|
||||
for (; src_bucket < stop_bucket; src_bucket++, bucket++) {
|
||||
memcpy(bucket, src_bucket, sizeof(*src_bucket));
|
||||
|
||||
if (src_bucket->num_achievements) {
|
||||
const v1_rc_client_achievement_t** src_achievement = (const v1_rc_client_achievement_t**)src_bucket->achievements;
|
||||
const v1_rc_client_achievement_t** stop_achievement = src_achievement + src_bucket->num_achievements;
|
||||
rc_client_achievement_t** achievement = &new_list->achievements_pointers[num_achievements];
|
||||
|
||||
bucket->achievements = (const rc_client_achievement_t**)achievement;
|
||||
|
||||
for (; src_achievement < stop_achievement; ++src_achievement, ++achievement) {
|
||||
*achievement = &new_list->achievements[num_achievements++];
|
||||
memcpy(*achievement, *src_achievement, sizeof(**src_achievement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_list->info.public_.num_buckets = v1_achievement_list->num_buckets;
|
||||
}
|
||||
|
||||
return (rc_client_achievement_list_t*)new_list;
|
||||
}
|
|
@ -95,7 +95,7 @@ typedef struct rc_client_external_t
|
|||
rc_client_external_begin_identify_and_load_game_func_t begin_identify_and_load_game;
|
||||
rc_client_external_begin_load_game_func_t begin_load_game;
|
||||
rc_client_external_get_game_info_func_t get_game_info;
|
||||
rc_client_external_begin_load_subset_t begin_load_subset;
|
||||
rc_client_external_begin_load_subset_t begin_load_subset; /* DEPRECATED */
|
||||
rc_client_external_get_subset_info_func_t get_subset_info;
|
||||
rc_client_external_action_func_t unload_game;
|
||||
rc_client_external_get_user_game_summary_func_t get_user_game_summary;
|
||||
|
@ -129,13 +129,29 @@ typedef struct rc_client_external_t
|
|||
rc_client_external_add_game_hash_func_t add_game_hash;
|
||||
rc_client_external_set_string_func_t load_unknown_game;
|
||||
|
||||
/* VERSION 3 */
|
||||
rc_client_external_get_user_info_func_t get_user_info_v3;
|
||||
rc_client_external_get_game_info_func_t get_game_info_v3;
|
||||
rc_client_external_get_subset_info_func_t get_subset_info_v3;
|
||||
rc_client_external_get_achievement_info_func_t get_achievement_info_v3;
|
||||
rc_client_external_create_achievement_list_func_t create_achievement_list_v3;
|
||||
|
||||
} rc_client_external_t;
|
||||
|
||||
#define RC_CLIENT_EXTERNAL_VERSION 2
|
||||
#define RC_CLIENT_EXTERNAL_VERSION 3
|
||||
|
||||
void rc_client_add_game_hash(rc_client_t* client, const char* hash, uint32_t game_id);
|
||||
void rc_client_load_unknown_game(rc_client_t* client, const char* hash);
|
||||
|
||||
/* conversion support */
|
||||
|
||||
struct rc_client_external_conversions_t;
|
||||
const rc_client_user_t* rc_client_external_convert_v1_user(const rc_client_t* client, const rc_client_user_t* v1_user);
|
||||
const rc_client_game_t* rc_client_external_convert_v1_game(const rc_client_t* client, const rc_client_game_t* v1_game);
|
||||
const rc_client_subset_t* rc_client_external_convert_v1_subset(const rc_client_t* client, const rc_client_subset_t* v1_subset);
|
||||
const rc_client_achievement_t* rc_client_external_convert_v1_achievement(const rc_client_t* client, const rc_client_achievement_t* v1_achievement);
|
||||
rc_client_achievement_list_t* rc_client_external_convert_v1_achievement_list(const rc_client_t* client, rc_client_achievement_list_t* v1_achievement_list);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_CLIENT_EXTERNAL_H */
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
#ifndef RC_CLIENT_EXTERNAL_CONVERSIONS_H
|
||||
#define RC_CLIENT_EXTERNAL_CONVERSIONS_H
|
||||
|
||||
#include "rc_client_internal.h"
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/* user */
|
||||
|
||||
typedef struct v1_rc_client_user_t {
|
||||
const char* display_name;
|
||||
const char* username;
|
||||
const char* token;
|
||||
uint32_t score;
|
||||
uint32_t score_softcore;
|
||||
uint32_t num_unread_messages;
|
||||
} v1_rc_client_user_t;
|
||||
|
||||
typedef struct v3_rc_client_user_t {
|
||||
const char* display_name;
|
||||
const char* username;
|
||||
const char* token;
|
||||
uint32_t score;
|
||||
uint32_t score_softcore;
|
||||
uint32_t num_unread_messages;
|
||||
const char* avatar_url;
|
||||
} v3_rc_client_user_t;
|
||||
|
||||
/* game */
|
||||
|
||||
typedef struct v1_rc_client_game_t {
|
||||
uint32_t id;
|
||||
uint32_t console_id;
|
||||
const char* title;
|
||||
const char* hash;
|
||||
const char* badge_name;
|
||||
} v1_rc_client_game_t;
|
||||
|
||||
typedef struct v3_rc_client_game_t {
|
||||
uint32_t id;
|
||||
uint32_t console_id;
|
||||
const char* title;
|
||||
const char* hash;
|
||||
const char* badge_name;
|
||||
const char* badge_url;
|
||||
} v3_rc_client_game_t;
|
||||
|
||||
/* subset */
|
||||
|
||||
typedef struct v1_rc_client_subset_t {
|
||||
uint32_t id;
|
||||
const char* title;
|
||||
char badge_name[16];
|
||||
uint32_t num_achievements;
|
||||
uint32_t num_leaderboards;
|
||||
} v1_rc_client_subset_t;
|
||||
|
||||
typedef struct v3_rc_client_subset_t {
|
||||
uint32_t id;
|
||||
const char* title;
|
||||
char badge_name[16];
|
||||
uint32_t num_achievements;
|
||||
uint32_t num_leaderboards;
|
||||
const char* badge_url;
|
||||
} v3_rc_client_subset_t;
|
||||
|
||||
/* achievement */
|
||||
|
||||
typedef struct v1_rc_client_achievement_t {
|
||||
const char* title;
|
||||
const char* description;
|
||||
char badge_name[8];
|
||||
char measured_progress[24];
|
||||
float measured_percent;
|
||||
uint32_t id;
|
||||
uint32_t points;
|
||||
time_t unlock_time;
|
||||
uint8_t state;
|
||||
uint8_t category;
|
||||
uint8_t bucket;
|
||||
uint8_t unlocked;
|
||||
float rarity;
|
||||
float rarity_hardcore;
|
||||
uint8_t type;
|
||||
} v1_rc_client_achievement_t;
|
||||
|
||||
typedef struct v3_rc_client_achievement_t {
|
||||
const char* title;
|
||||
const char* description;
|
||||
char badge_name[8];
|
||||
char measured_progress[24];
|
||||
float measured_percent;
|
||||
uint32_t id;
|
||||
uint32_t points;
|
||||
time_t unlock_time;
|
||||
uint8_t state;
|
||||
uint8_t category;
|
||||
uint8_t bucket;
|
||||
uint8_t unlocked;
|
||||
float rarity;
|
||||
float rarity_hardcore;
|
||||
uint8_t type;
|
||||
const char* badge_url;
|
||||
const char* badge_locked_url;
|
||||
} v3_rc_client_achievement_t;
|
||||
|
||||
/* achievement list */
|
||||
|
||||
typedef struct v1_rc_client_achievement_bucket_t {
|
||||
v1_rc_client_achievement_t** achievements;
|
||||
uint32_t num_achievements;
|
||||
|
||||
const char* label;
|
||||
uint32_t subset_id;
|
||||
uint8_t bucket_type;
|
||||
} v1_rc_client_achievement_bucket_t;
|
||||
|
||||
typedef struct v1_rc_client_achievement_list_t {
|
||||
v1_rc_client_achievement_bucket_t* buckets;
|
||||
uint32_t num_buckets;
|
||||
} v1_rc_client_achievement_list_t;
|
||||
|
||||
typedef struct v1_rc_client_achievement_list_info_t {
|
||||
v1_rc_client_achievement_list_t public_;
|
||||
rc_client_destroy_achievement_list_func_t destroy_func;
|
||||
} v1_rc_client_achievement_list_info_t;
|
||||
|
||||
typedef struct v3_rc_client_achievement_bucket_t {
|
||||
const v3_rc_client_achievement_t** achievements;
|
||||
uint32_t num_achievements;
|
||||
|
||||
const char* label;
|
||||
uint32_t subset_id;
|
||||
uint8_t bucket_type;
|
||||
} v3_rc_client_achievement_bucket_t;
|
||||
|
||||
typedef struct v3_rc_client_achievement_list_t {
|
||||
const v3_rc_client_achievement_bucket_t* buckets;
|
||||
uint32_t num_buckets;
|
||||
} v3_rc_client_achievement_list_t;
|
||||
|
||||
typedef struct v3_rc_client_achievement_list_info_t {
|
||||
v3_rc_client_achievement_list_t public_;
|
||||
rc_client_destroy_achievement_list_func_t destroy_func;
|
||||
} v3_rc_client_achievement_list_info_t;
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_CLIENT_EXTERNAL_CONVERSIONS_H */
|
|
@ -215,14 +215,13 @@ typedef struct rc_client_subset_info_t {
|
|||
uint8_t pending_events;
|
||||
} rc_client_subset_info_t;
|
||||
|
||||
rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Game |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_client_game_hash_t {
|
||||
char hash[33];
|
||||
uint8_t is_unknown;
|
||||
uint32_t game_id;
|
||||
struct rc_client_game_hash_t* next;
|
||||
} rc_client_game_hash_t;
|
||||
|
@ -300,9 +299,11 @@ typedef struct rc_client_state_t {
|
|||
rc_buffer_t buffer;
|
||||
|
||||
rc_client_scheduled_callback_data_t* scheduled_callbacks;
|
||||
rc_api_host_t host;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
rc_client_external_t* external_client;
|
||||
struct rc_client_external_conversions_t* external_client_conversions;
|
||||
#endif
|
||||
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
|
||||
rc_client_raintegration_t* raintegration;
|
||||
|
|
|
@ -172,7 +172,7 @@ static void rc_client_init_raintegration(rc_client_t* client,
|
|||
const char* host_url = client->state.raintegration->get_host_url();
|
||||
if (host_url) {
|
||||
if (strcmp(host_url, "OFFLINE") != 0) {
|
||||
if (strcmp(host_url, "https://retroachievements.org") != 0)
|
||||
if (strcmp(host_url, rc_api_default_host()) != 0)
|
||||
rc_client_set_host(client, host_url);
|
||||
}
|
||||
else if (client->state.raintegration->init_client_offline) {
|
||||
|
@ -344,7 +344,7 @@ rc_client_async_handle_t* rc_client_begin_load_raintegration(rc_client_t* client
|
|||
|
||||
if (client->state.raintegration->get_host_url) {
|
||||
const char* host_url = client->state.raintegration->get_host_url();
|
||||
if (host_url && strcmp(host_url, "https://retroachievements.org") != 0 &&
|
||||
if (host_url && strcmp(host_url, rc_api_default_host()) != 0 &&
|
||||
strcmp(host_url, "OFFLINE") != 0) {
|
||||
/* if the DLL specifies a custom host, use it */
|
||||
rc_client_set_host(client, host_url);
|
||||
|
@ -352,7 +352,7 @@ rc_client_async_handle_t* rc_client_begin_load_raintegration(rc_client_t* client
|
|||
}
|
||||
|
||||
memset(&request, 0, sizeof(request));
|
||||
rc_api_url_build_dorequest_url(&request);
|
||||
rc_api_url_build_dorequest_url(&request, &client->state.host);
|
||||
rc_url_builder_init(&builder, &request.buffer, 48);
|
||||
rc_url_builder_append_str_param(&builder, "r", "latestintegration");
|
||||
request.post_data = rc_url_builder_finalize(&builder);
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<Type Name="rc_client_game_t">
|
||||
<DisplayString>{{title={title,s} id={id}}}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_subset_t">
|
||||
<DisplayString>{{title={title,s} id={id}}}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_subset_t">
|
||||
<DisplayString>{{title={title,s} id={id}}}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="__rc_client_achievement_state_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE">{RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE">{RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE}</DisplayString>
|
||||
|
@ -18,71 +18,71 @@
|
|||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_STATE_DISABLED">{RC_CLIENT_ACHIEVEMENT_STATE_DISABLED}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="__rc_client_achievement_category_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_CATEGORY_NONE">{RC_CLIENT_ACHIEVEMENT_CATEGORY_NONE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE">{RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL">{RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL">{RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="__rc_client_achievement_type_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_TYPE_STANDARD">{RC_CLIENT_ACHIEVEMENT_TYPE_STANDARD}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE">{RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION">{RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_TYPE_WIN">{RC_CLIENT_ACHIEVEMENT_TYPE_WIN}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="__rc_client_achievement_bucket_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN">{RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED">{RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED">{RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED">{RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL">{RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED">{RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE">{RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED">{RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED">{RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="__rc_client_achievement_unlocked_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE">{RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE">{RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE">{RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH">{RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_achievement_t">
|
||||
<DisplayString>{{title={title,s} id={id}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="title">title</Item>
|
||||
<Item Name="description">description</Item>
|
||||
<Item Name="points">points</Item>
|
||||
<Item Name="id">id</Item>
|
||||
<Item Name="state">*((__rc_client_achievement_state_enum_t*)&state)</Item>
|
||||
<Item Name="type">*((__rc_client_achievement_type_enum_t*)&state)</Item>
|
||||
<Item Name="category">*((__rc_client_achievement_category_enum_t*)&category)</Item>
|
||||
<Item Name="bucket">*((__rc_client_achievement_state_enum_t*)&bucket)</Item>
|
||||
<Item Name="unlocked">*((__rc_client_achievement_unlocked_enum_t*)&unlocked)</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_achievement_bucket_t">
|
||||
<DisplayString>{{label={label,s} count={num_achievements}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>num_achievements</Size>
|
||||
<ValueNode>achievements[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_achievement_list_t">
|
||||
<DisplayString>{{count={num_buckets}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>num_buckets</Size>
|
||||
<ValueNode>buckets[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="__rc_client_achievement_category_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_CATEGORY_NONE">{RC_CLIENT_ACHIEVEMENT_CATEGORY_NONE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE">{RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL">{RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL">{RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="__rc_client_achievement_type_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_TYPE_STANDARD">{RC_CLIENT_ACHIEVEMENT_TYPE_STANDARD}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE">{RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION">{RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_TYPE_WIN">{RC_CLIENT_ACHIEVEMENT_TYPE_WIN}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="__rc_client_achievement_bucket_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN">{RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED">{RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED">{RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED">{RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL">{RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED">{RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE">{RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED">{RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED">{RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="__rc_client_achievement_unlocked_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE">{RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE">{RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE">{RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH">{RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_achievement_t">
|
||||
<DisplayString>{{title={title,s} id={id}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="title">title</Item>
|
||||
<Item Name="description">description</Item>
|
||||
<Item Name="points">points</Item>
|
||||
<Item Name="id">id</Item>
|
||||
<Item Name="state">*((__rc_client_achievement_state_enum_t*)&state)</Item>
|
||||
<Item Name="type">*((__rc_client_achievement_type_enum_t*)&state)</Item>
|
||||
<Item Name="category">*((__rc_client_achievement_category_enum_t*)&category)</Item>
|
||||
<Item Name="bucket">*((__rc_client_achievement_state_enum_t*)&bucket)</Item>
|
||||
<Item Name="unlocked">*((__rc_client_achievement_unlocked_enum_t*)&unlocked)</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_achievement_bucket_t">
|
||||
<DisplayString>{{label={label,s} count={num_achievements}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>num_achievements</Size>
|
||||
<ValueNode>achievements[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_achievement_list_t">
|
||||
<DisplayString>{{count={num_buckets}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>num_buckets</Size>
|
||||
<ValueNode>buckets[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="__rc_client_leaderboard_state_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_LEADERBOARD_STATE_INACTIVE">{RC_CLIENT_LEADERBOARD_STATE_INACTIVE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_LEADERBOARD_STATE_ACTIVE">{RC_CLIENT_LEADERBOARD_STATE_ACTIVE}</DisplayString>
|
||||
|
@ -96,53 +96,53 @@
|
|||
<DisplayString Condition="value==RC_CLIENT_LEADERBOARD_FORMAT_VALUE">{RC_CLIENT_LEADERBOARD_FORMAT_VALUE}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_t">
|
||||
<DisplayString>{{title={title,s} id={id}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="title">title</Item>
|
||||
<Item Name="description">description</Item>
|
||||
<Item Name="tracker_value">tracker_value</Item>
|
||||
<Item Name="id">id</Item>
|
||||
<Item Name="state">*((__rc_client_leaderboard_state_enum_t*)&state)</Item>
|
||||
<Item Name="format">*((__rc_client_leaderboard_format_enum_t*)&format)</Item>
|
||||
<Item Name="lower_is_better">*((__rc_bool_enum_t*)&lower_is_better)</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_bucket_t">
|
||||
<DisplayString>{{label={label,s} count={num_leaderboards}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>num_leaderboards</Size>
|
||||
<ValueNode>leaderboards[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_list_t">
|
||||
<DisplayString>{{count={num_buckets}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>num_buckets</Size>
|
||||
<ValueNode>buckets[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_scoreboard_entry_t">
|
||||
<DisplayString>{{rank={rank} score={score,s} username={username}}}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_scoreboard_t">
|
||||
<DisplayString>{{leaderboard_id={leaderboard_id} num_entries={num_entries}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="leaderboard_id">leaderboard_id</Item>
|
||||
<Item Name="submitted_score">submitted_score</Item>
|
||||
<Item Name="best_score">best_score</Item>
|
||||
<Item Name="new_rank">new_rank</Item>
|
||||
<Item Name="num_entries">num_entries</Item>
|
||||
<IndexListItems>
|
||||
<Size>num_top_entries</Size>
|
||||
<ValueNode>top_entries[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_t">
|
||||
<DisplayString>{{title={title,s} id={id}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="title">title</Item>
|
||||
<Item Name="description">description</Item>
|
||||
<Item Name="tracker_value">tracker_value</Item>
|
||||
<Item Name="id">id</Item>
|
||||
<Item Name="state">*((__rc_client_leaderboard_state_enum_t*)&state)</Item>
|
||||
<Item Name="format">*((__rc_client_leaderboard_format_enum_t*)&format)</Item>
|
||||
<Item Name="lower_is_better">*((__rc_bool_enum_t*)&lower_is_better)</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_bucket_t">
|
||||
<DisplayString>{{label={label,s} count={num_leaderboards}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>num_leaderboards</Size>
|
||||
<ValueNode>leaderboards[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_list_t">
|
||||
<DisplayString>{{count={num_buckets}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>num_buckets</Size>
|
||||
<ValueNode>buckets[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_scoreboard_entry_t">
|
||||
<DisplayString>{{rank={rank} score={score,s} username={username}}}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_scoreboard_t">
|
||||
<DisplayString>{{leaderboard_id={leaderboard_id} num_entries={num_entries}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="leaderboard_id">leaderboard_id</Item>
|
||||
<Item Name="submitted_score">submitted_score</Item>
|
||||
<Item Name="best_score">best_score</Item>
|
||||
<Item Name="new_rank">new_rank</Item>
|
||||
<Item Name="num_entries">num_entries</Item>
|
||||
<IndexListItems>
|
||||
<Size>num_top_entries</Size>
|
||||
<ValueNode>top_entries[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="__rc_client_event_type_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_EVENT_TYPE_NONE">{RC_CLIENT_EVENT_TYPE_NONE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED">{RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED}</DisplayString>
|
||||
|
@ -163,63 +163,65 @@
|
|||
<DisplayString Condition="value==RC_CLIENT_EVENT_SERVER_ERROR">{RC_CLIENT_EVENT_SERVER_ERROR}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_EVENT_DISCONNECTED">{RC_CLIENT_EVENT_DISCONNECTED}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_EVENT_RECONNECTED">{RC_CLIENT_EVENT_RECONNECTED}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_EVENT_SUBSET_COMPLETED">{RC_CLIENT_EVENT_SUBSET_COMPLETED}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_event_t">
|
||||
<DisplayString>{{type={*((__rc_client_event_type_enum_t*)&type)}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="type">*((__rc_client_event_type_enum_t*)&type)</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_STARTED" Name="leaderboard">*leaderboard</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_FAILED" Name="leaderboard">*leaderboard</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED" Name="leaderboard">*leaderboard</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW" Name="leaderboard_tracker">*leaderboard_tracker</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE" Name="leaderboard_tracker">*leaderboard_tracker</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE" Name="leaderboard_tracker">*leaderboard_tracker</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD" Name="leaderboard_scoreboard">*leaderboard_scoreboard</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD" Name="leaderboard">*leaderboard</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_SERVER_ERROR" Name="server_error">*server_error</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="__rc_client_subset_info_achievements_list_t">
|
||||
<DisplayString>{{count={info.public_.num_achievements}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>info.public_.num_achievements</Size>
|
||||
<ValueNode>info.achievements[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="__rc_client_subset_info_leaderboards_list_t">
|
||||
<DisplayString>{{count={info.public_.num_leaderboards}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>info.public_.num_leaderboards</Size>
|
||||
<ValueNode>info.leaderboards[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_event_t">
|
||||
<DisplayString>{{type={*((__rc_client_event_type_enum_t*)&type)}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="type">*((__rc_client_event_type_enum_t*)&type)</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_STARTED" Name="leaderboard">*leaderboard</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_FAILED" Name="leaderboard">*leaderboard</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED" Name="leaderboard">*leaderboard</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE" Name="achievement">*achievement</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW" Name="leaderboard_tracker">*leaderboard_tracker</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE" Name="leaderboard_tracker">*leaderboard_tracker</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE" Name="leaderboard_tracker">*leaderboard_tracker</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD" Name="leaderboard_scoreboard">*leaderboard_scoreboard</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD" Name="leaderboard">*leaderboard</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_SERVER_ERROR" Name="server_error">*server_error</Item>
|
||||
<Item Condition="type==RC_CLIENT_EVENT_SUBSET_COMPLETED" Name="subset">*subset</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="__rc_client_subset_info_achievements_list_t">
|
||||
<DisplayString>{{count={info.public_.num_achievements}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>info.public_.num_achievements</Size>
|
||||
<ValueNode>info.achievements[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="__rc_client_subset_info_leaderboards_list_t">
|
||||
<DisplayString>{{count={info.public_.num_leaderboards}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>info.public_.num_leaderboards</Size>
|
||||
<ValueNode>info.leaderboards[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="__rc_client_mastery_state_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_MASTERY_STATE_NONE">{RC_CLIENT_MASTERY_STATE_NONE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_MASTERY_STATE_PENDING">{RC_CLIENT_MASTERY_STATE_PENDING}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_MASTERY_STATE_SHOWN">{RC_CLIENT_MASTERY_STATE_SHOWN}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_subset_info_t">
|
||||
<DisplayString>{{title={public_.title,s} id={public_.id}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="public_">public_</Item>
|
||||
<Item Name="active">*((__rc_bool_enum_t*)&active)</Item>
|
||||
<Item Name="mastery">*((__rc_client_mastery_state_enum_t*)&mastery)</Item>
|
||||
<Item Name="achievements">*((__rc_client_subset_info_achievements_list_t*)this)</Item>
|
||||
<Item Name="leaderboards">*((__rc_client_subset_info_leaderboards_list_t*)this)</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_subset_info_t">
|
||||
<DisplayString>{{title={public_.title,s} id={public_.id}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="public_">public_</Item>
|
||||
<Item Name="active">*((__rc_bool_enum_t*)&active)</Item>
|
||||
<Item Name="mastery">*((__rc_client_mastery_state_enum_t*)&mastery)</Item>
|
||||
<Item Name="achievements">*((__rc_client_subset_info_achievements_list_t*)this)</Item>
|
||||
<Item Name="leaderboards">*((__rc_client_subset_info_leaderboards_list_t*)this)</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="__rc_client_leaderboard_tracker_list_t">
|
||||
<DisplayString Condition="first==0">{{NULL}}</DisplayString>
|
||||
<DisplayString>{(void**)&first,na}</DisplayString>
|
||||
|
@ -253,26 +255,26 @@
|
|||
</LinkedListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_achievement_info_t">
|
||||
<DisplayString>{{title={public_.title,s} id={public_.id}}}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_info_t">
|
||||
<DisplayString>{{title={public_.title,s} id={public_.id}}}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_game_info_t">
|
||||
<Type Name="rc_client_achievement_info_t">
|
||||
<DisplayString>{{title={public_.title,s} id={public_.id}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="public_">public_</Item>
|
||||
<Item Name="subsets">*((__rc_client_subset_info_list_t*)&subsets)</Item>
|
||||
<Item Name="media_hash">*((__rc_client_media_hash_list_t*)&media_hash)</Item>
|
||||
<Item Name="leaderboard_trackers">*((__rc_client_leaderboard_tracker_list_t*)&leaderboard_trackers)</Item>
|
||||
<Item Name="progress_tracker">progress_tracker</Item>
|
||||
<Item Name="runtime">runtime</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_game_hash_t">
|
||||
<DisplayString>{{hash={hash,s} game_id={game_id}}}</DisplayString>
|
||||
</Type>
|
||||
</Type>
|
||||
<Type Name="rc_client_leaderboard_info_t">
|
||||
<DisplayString>{{title={public_.title,s} id={public_.id}}}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_game_info_t">
|
||||
<DisplayString>{{title={public_.title,s} id={public_.id}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="public_">public_</Item>
|
||||
<Item Name="subsets">*((__rc_client_subset_info_list_t*)&subsets)</Item>
|
||||
<Item Name="media_hash">*((__rc_client_media_hash_list_t*)&media_hash)</Item>
|
||||
<Item Name="leaderboard_trackers">*((__rc_client_leaderboard_tracker_list_t*)&leaderboard_trackers)</Item>
|
||||
<Item Name="progress_tracker">progress_tracker</Item>
|
||||
<Item Name="runtime">runtime</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_game_hash_t">
|
||||
<DisplayString>{{hash={hash,s} game_id={game_id}}}</DisplayString>
|
||||
</Type>
|
||||
<Type Name="__rc_client_game_hash_list_t">
|
||||
<DisplayString>{client.hashes}</DisplayString>
|
||||
<Expand>
|
||||
|
@ -293,17 +295,17 @@
|
|||
<DisplayString Condition="value==RC_CLIENT_LOAD_GAME_STATE_ABORTED">{RC_CLIENT_LOAD_GAME_STATE_ABORTED}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_load_state_t">
|
||||
<Expand>
|
||||
<Item Name="progress">*((__rc_client_load_game_state_enum_t*)&progress)</Item>
|
||||
<Item Name="game">*game</Item>
|
||||
<Item Name="subset">subset</Item>
|
||||
<Item Name="hash">*hash</Item>
|
||||
<Item Name="pending_media">pending_media</Item>
|
||||
<Item Name="start_session_response">start_session_response</Item>
|
||||
<Item Name="outstanding_requests">(int)outstanding_requests</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_load_state_t">
|
||||
<Expand>
|
||||
<Item Name="progress">*((__rc_client_load_game_state_enum_t*)&progress)</Item>
|
||||
<Item Name="game">*game</Item>
|
||||
<Item Name="subset">subset</Item>
|
||||
<Item Name="hash">*hash</Item>
|
||||
<Item Name="pending_media">pending_media</Item>
|
||||
<Item Name="start_session_response">start_session_response</Item>
|
||||
<Item Name="outstanding_requests">(int)outstanding_requests</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_scheduled_callback_data_t">
|
||||
<DisplayString>{{when={when} callback={callback,na}}}</DisplayString>
|
||||
</Type>
|
||||
|
@ -347,35 +349,36 @@
|
|||
</Type>
|
||||
<Type Name="rc_client_state_t">
|
||||
<Expand>
|
||||
<Item Name="hardcore">*((__rc_bool_enum_t*)&hardcore)</Item>
|
||||
<Item Name="unofficial_enabled">*((__rc_bool_enum_t*)&unofficial_enabled)</Item>
|
||||
<Item Name="encore_mode">*((__rc_bool_enum_t*)&encore_mode)</Item>
|
||||
<Item Name="spectator_mode">*((__rc_client_spectator_mode_enum_t*)&spectator_mode)</Item>
|
||||
<Item Name="disconnect">*((__rc_client_disconnect_enum_t*)&disconnect)</Item>
|
||||
<Item Name="log_level">*((__rc_client_log_level_enum_t*)&log_level)</Item>
|
||||
<Item Name="user">*((__rc_client_user_state_enum_t*)&user)</Item>
|
||||
<Item Name="scheduled_callbacks">*((__rc_client_scheduled_callback_list_t*)this)</Item>
|
||||
<Item Name="load">load</Item>
|
||||
<Item Name="hardcore">*((__rc_bool_enum_t*)&hardcore)</Item>
|
||||
<Item Name="unofficial_enabled">*((__rc_bool_enum_t*)&unofficial_enabled)</Item>
|
||||
<Item Name="encore_mode">*((__rc_bool_enum_t*)&encore_mode)</Item>
|
||||
<Item Name="spectator_mode">*((__rc_client_spectator_mode_enum_t*)&spectator_mode)</Item>
|
||||
<Item Name="disconnect">*((__rc_client_disconnect_enum_t*)&disconnect)</Item>
|
||||
<Item Name="log_level">*((__rc_client_log_level_enum_t*)&log_level)</Item>
|
||||
<Item Name="user">*((__rc_client_user_state_enum_t*)&user)</Item>
|
||||
<Item Name="scheduled_callbacks">*((__rc_client_scheduled_callback_list_t*)this)</Item>
|
||||
<Item Name="host">host</Item>
|
||||
<Item Name="load">load</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_t">
|
||||
<Expand>
|
||||
<Item Name="game">game</Item>
|
||||
<Item Name="hashes">*((__rc_client_game_hash_list_t*)&hashes)</Item>
|
||||
<Item Name="user">user</Item>
|
||||
<Item Name="callbacks">callbacks</Item>
|
||||
<Item Name="state">state</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_raintegration_menu_t">
|
||||
<DisplayString>{{count={num_items}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>num_items</Size>
|
||||
<ValueNode>items[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_t">
|
||||
<Expand>
|
||||
<Item Name="game">game</Item>
|
||||
<Item Name="hashes">*((__rc_client_game_hash_list_t*)&hashes)</Item>
|
||||
<Item Name="user">user</Item>
|
||||
<Item Name="callbacks">callbacks</Item>
|
||||
<Item Name="state">state</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_raintegration_menu_t">
|
||||
<DisplayString>{{count={num_items}}}</DisplayString>
|
||||
<Expand>
|
||||
<IndexListItems>
|
||||
<Size>num_items</Size>
|
||||
<ValueNode>items[$i]</ValueNode>
|
||||
</IndexListItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="__rc_client_raintegration_event_enum_t">
|
||||
<DisplayString Condition="value==RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE">{RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE}</DisplayString>
|
||||
<DisplayString Condition="value==RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED">{RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED}</DisplayString>
|
||||
|
@ -384,11 +387,11 @@
|
|||
<DisplayString Condition="value==RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED">{RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED}</DisplayString>
|
||||
<DisplayString>unknown ({value})</DisplayString>
|
||||
</Type>
|
||||
<Type Name="rc_client_raintegration_event_t">
|
||||
<DisplayString>{{type={*((__rc_client_raintegration_event_enum_t*)&type)}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="type">*((__rc_client_raintegration_event_enum_t*)&type)</Item>
|
||||
<Item Condition="type==RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED" Name="menu_item">menu_item</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="rc_client_raintegration_event_t">
|
||||
<DisplayString>{{type={*((__rc_client_raintegration_event_enum_t*)&type)}}}</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="type">*((__rc_client_raintegration_event_enum_t*)&type)</Item>
|
||||
<Item Condition="type==RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED" Name="menu_item">menu_item</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
</AutoVisualizer>
|
||||
|
|
|
@ -1,901 +0,0 @@
|
|||
/* This file provides a series of functions for integrating RetroAchievements with libretro.
|
||||
* These functions will be called by a libretro frontend to validate certain expected behaviors
|
||||
* and simplify mapping core data to the RAIntegration DLL.
|
||||
*
|
||||
* Originally designed to be shared between RALibretro and RetroArch, but will simplify
|
||||
* integrating with any other frontends.
|
||||
*/
|
||||
|
||||
#include "rc_libretro.h"
|
||||
|
||||
#include "rc_consoles.h"
|
||||
#include "rc_compat.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
/* internal helper functions in hash.c */
|
||||
extern void* rc_file_open(const char* path);
|
||||
extern void rc_file_seek(void* file_handle, int64_t offset, int origin);
|
||||
extern int64_t rc_file_tell(void* file_handle);
|
||||
extern size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes);
|
||||
extern void rc_file_close(void* file_handle);
|
||||
extern int rc_path_compare_extension(const char* path, const char* ext);
|
||||
extern int rc_hash_error(const char* message);
|
||||
|
||||
|
||||
static rc_libretro_message_callback rc_libretro_verbose_message_callback = NULL;
|
||||
|
||||
/* a value that starts with a comma is a CSV.
|
||||
* if it starts with an exclamation point, it's everything but the provided value.
|
||||
* if it starts with an exclamntion point followed by a comma, it's everything but the CSV values.
|
||||
* values are case-insensitive */
|
||||
typedef struct rc_disallowed_core_settings_t
|
||||
{
|
||||
const char* library_name;
|
||||
const rc_disallowed_setting_t* disallowed_settings;
|
||||
} rc_disallowed_core_settings_t;
|
||||
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_beetle_psx_settings[] = {
|
||||
{ "beetle_psx_cpu_freq_scale", "<100" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_beetle_psx_hw_settings[] = {
|
||||
{ "beetle_psx_hw_cpu_freq_scale", "<100" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_bsnes_settings[] = {
|
||||
{ "bsnes_region", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_cap32_settings[] = {
|
||||
{ "cap32_autorun", "disabled" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_dolphin_settings[] = {
|
||||
{ "dolphin_cheats_enabled", "enabled" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_dosbox_pure_settings[] = {
|
||||
{ "dosbox_pure_strict_mode", "false" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_duckstation_settings[] = {
|
||||
{ "duckstation_CDROM.LoadImagePatches", "true" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_ecwolf_settings[] = {
|
||||
{ "ecwolf-invulnerability", "enabled" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = {
|
||||
{ "fbneo-allow-patched-romsets", "enabled" },
|
||||
{ "fbneo-cheat-*", "!,Disabled,0 - Disabled" },
|
||||
{ "fbneo-cpu-speed-adjust", "??%" }, /* disallow speeds under 100% */
|
||||
{ "fbneo-dipswitch-*", "Universe BIOS*" },
|
||||
{ "fbneo-neogeo-mode", "UNIBIOS" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_fceumm_settings[] = {
|
||||
{ "fceumm_region", ",PAL,Dendy" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_flycast_settings[] = {
|
||||
{ "reicast_sh4clock", "<200" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_gpgx_settings[] = {
|
||||
{ "genesis_plus_gx_lock_on", ",action replay (pro),game genie" },
|
||||
{ "genesis_plus_gx_region_detect", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_gpgx_wide_settings[] = {
|
||||
{ "genesis_plus_gx_wide_lock_on", ",action replay (pro),game genie" },
|
||||
{ "genesis_plus_gx_wide_region_detect", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_mesen_settings[] = {
|
||||
{ "mesen_region", ",PAL,Dendy" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_mesen_s_settings[] = {
|
||||
{ "mesen-s_region", "PAL" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_neocd_settings[] = {
|
||||
{ "neocd_bios", "uni-bios*" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = {
|
||||
{ "pcsx_rearmed_psxclock", ",!auto,<55" },
|
||||
{ "pcsx_rearmed_region", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_picodrive_settings[] = {
|
||||
{ "picodrive_region", ",Europe,Japan PAL" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_ppsspp_settings[] = {
|
||||
{ "ppsspp_cheats", "enabled" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_quasi88_settings[] = {
|
||||
{ "q88_cpu_clock", ",1,2" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_smsplus_settings[] = {
|
||||
{ "smsplus_region", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_snes9x_settings[] = {
|
||||
{ "snes9x_gfx_clip", "disabled" },
|
||||
{ "snes9x_gfx_transp", "disabled" },
|
||||
{ "snes9x_layer_*", "disabled" },
|
||||
{ "snes9x_region", "pal" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_swanstation_settings[] = {
|
||||
{ "swanstation_CPU_Overclock", "<100" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_vice_settings[] = {
|
||||
{ "vice_autostart", "disabled" }, /* autostart dictates initial load and reset from menu */
|
||||
{ "vice_reset", "!autostart" }, /* reset dictates behavior when pressing reset button (END) */
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_setting_t _rc_disallowed_virtual_jaguar_settings[] = {
|
||||
{ "virtualjaguar_pal", "enabled" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
|
||||
{ "Beetle PSX", _rc_disallowed_beetle_psx_settings },
|
||||
{ "Beetle PSX HW", _rc_disallowed_beetle_psx_hw_settings },
|
||||
{ "bsnes-mercury", _rc_disallowed_bsnes_settings },
|
||||
{ "cap32", _rc_disallowed_cap32_settings },
|
||||
{ "dolphin-emu", _rc_disallowed_dolphin_settings },
|
||||
{ "DOSBox-pure", _rc_disallowed_dosbox_pure_settings },
|
||||
{ "DuckStation", _rc_disallowed_duckstation_settings },
|
||||
{ "ecwolf", _rc_disallowed_ecwolf_settings },
|
||||
{ "FCEUmm", _rc_disallowed_fceumm_settings },
|
||||
{ "FinalBurn Neo", _rc_disallowed_fbneo_settings },
|
||||
{ "Flycast", _rc_disallowed_flycast_settings },
|
||||
{ "Genesis Plus GX", _rc_disallowed_gpgx_settings },
|
||||
{ "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_settings },
|
||||
{ "Mesen", _rc_disallowed_mesen_settings },
|
||||
{ "Mesen-S", _rc_disallowed_mesen_s_settings },
|
||||
{ "NeoCD", _rc_disallowed_neocd_settings },
|
||||
{ "PPSSPP", _rc_disallowed_ppsspp_settings },
|
||||
{ "PCSX-ReARMed", _rc_disallowed_pcsx_rearmed_settings },
|
||||
{ "PicoDrive", _rc_disallowed_picodrive_settings },
|
||||
{ "QUASI88", _rc_disallowed_quasi88_settings },
|
||||
{ "SMS Plus GX", _rc_disallowed_smsplus_settings },
|
||||
{ "Snes9x", _rc_disallowed_snes9x_settings },
|
||||
{ "SwanStation", _rc_disallowed_swanstation_settings },
|
||||
{ "VICE x64", _rc_disallowed_vice_settings },
|
||||
{ "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char* match) {
|
||||
char c1, c2;
|
||||
while ((c1 = *test++)) {
|
||||
if (tolower(c1) != tolower(c2 = *match++) && c2 != '?')
|
||||
return (c2 == '*');
|
||||
}
|
||||
|
||||
return (*match == '\0');
|
||||
}
|
||||
|
||||
static int rc_libretro_numeric_less_than(const char* test, const char* value) {
|
||||
int test_num = atoi(test);
|
||||
int value_num = atoi(value);
|
||||
return (test_num < value_num);
|
||||
}
|
||||
|
||||
static int rc_libretro_match_token(const char* val, const char* token, size_t size, int* result) {
|
||||
if (*token == '!') {
|
||||
/* !X => if X is a match, it's explicitly allowed. match with result = false */
|
||||
if (rc_libretro_match_token(val, token + 1, size - 1, result)) {
|
||||
*result = 0;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (*token == '<') {
|
||||
/* if val < token, match with result = true */
|
||||
char buffer[128];
|
||||
memcpy(buffer, token + 1, size - 1);
|
||||
buffer[size - 1] = '\0';
|
||||
if (rc_libretro_numeric_less_than(val, buffer)) {
|
||||
*result = 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (memcmp(token, val, size) == 0 && val[size] == 0) {
|
||||
/* exact match, match with result = true */
|
||||
*result = 1;
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
/* check for case insensitive match */
|
||||
char buffer[128];
|
||||
memcpy(buffer, token, size);
|
||||
buffer[size] = '\0';
|
||||
if (rc_libretro_string_equal_nocase_wildcard(val, buffer)) {
|
||||
/* case insensitive match, match with result = true */
|
||||
*result = 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* no match */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rc_libretro_match_value(const char* val, const char* match) {
|
||||
int result = 0;
|
||||
|
||||
/* if value starts with a comma, it's a CSV list of potential matches */
|
||||
if (*match == ',') {
|
||||
do {
|
||||
const char* ptr = ++match;
|
||||
size_t size;
|
||||
|
||||
while (*match && *match != ',')
|
||||
++match;
|
||||
|
||||
size = match - ptr;
|
||||
if (rc_libretro_match_token(val, ptr, size, &result))
|
||||
return result;
|
||||
|
||||
} while (*match == ',');
|
||||
}
|
||||
else {
|
||||
/* a leading exclamation point means the provided value(s) are not forbidden (are allowed) */
|
||||
if (*match == '!')
|
||||
return !rc_libretro_match_value(val, &match[1]);
|
||||
|
||||
/* just a single value, attempt to match it */
|
||||
if (rc_libretro_match_token(val, match, strlen(match), &result))
|
||||
return result;
|
||||
}
|
||||
|
||||
/* value did not match filters, assume it's allowed */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value) {
|
||||
const char* key;
|
||||
size_t key_len;
|
||||
|
||||
for (; disallowed_settings->setting; ++disallowed_settings) {
|
||||
key = disallowed_settings->setting;
|
||||
key_len = strlen(key);
|
||||
|
||||
if (key[key_len - 1] == '*') {
|
||||
if (memcmp(setting, key, key_len - 1) == 0) {
|
||||
if (rc_libretro_match_value(value, disallowed_settings->value))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (memcmp(setting, key, key_len + 1) == 0) {
|
||||
if (rc_libretro_match_value(value, disallowed_settings->value))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name) {
|
||||
const rc_disallowed_core_settings_t* core_filter = rc_disallowed_core_settings;
|
||||
size_t library_name_length;
|
||||
|
||||
if (!library_name || !library_name[0])
|
||||
return NULL;
|
||||
|
||||
library_name_length = strlen(library_name) + 1;
|
||||
while (core_filter->library_name) {
|
||||
if (memcmp(core_filter->library_name, library_name, library_name_length) == 0)
|
||||
return core_filter->disallowed_settings;
|
||||
|
||||
++core_filter;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct rc_disallowed_core_systems_t
|
||||
{
|
||||
const char* library_name;
|
||||
const uint32_t disallowed_consoles[4];
|
||||
} rc_disallowed_core_systems_t;
|
||||
|
||||
static const rc_disallowed_core_systems_t rc_disallowed_core_systems[] = {
|
||||
/* https://github.com/libretro/Mesen-S/issues/8 */
|
||||
{ "Mesen-S", { RC_CONSOLE_GAMEBOY, RC_CONSOLE_GAMEBOY_COLOR, 0 }},
|
||||
{ NULL, { 0 } }
|
||||
};
|
||||
|
||||
int rc_libretro_is_system_allowed(const char* library_name, uint32_t console_id) {
|
||||
const rc_disallowed_core_systems_t* core_filter = rc_disallowed_core_systems;
|
||||
size_t library_name_length;
|
||||
size_t i;
|
||||
|
||||
if (!library_name || !library_name[0])
|
||||
return 1;
|
||||
|
||||
library_name_length = strlen(library_name) + 1;
|
||||
while (core_filter->library_name) {
|
||||
if (memcmp(core_filter->library_name, library_name, library_name_length) == 0) {
|
||||
for (i = 0; i < sizeof(core_filter->disallowed_consoles) / sizeof(core_filter->disallowed_consoles[0]); ++i) {
|
||||
if (core_filter->disallowed_consoles[i] == console_id)
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
++core_filter;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, uint32_t address, uint32_t* avail) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < regions->count; ++i) {
|
||||
const size_t size = regions->size[i];
|
||||
if (address < size) {
|
||||
if (regions->data[i] == NULL)
|
||||
break;
|
||||
|
||||
if (avail)
|
||||
*avail = (uint32_t)(size - address);
|
||||
|
||||
return ®ions->data[i][address];
|
||||
}
|
||||
|
||||
address -= (uint32_t)size;
|
||||
}
|
||||
|
||||
if (avail)
|
||||
*avail = 0;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, uint32_t address) {
|
||||
return rc_libretro_memory_find_avail(regions, address, NULL);
|
||||
}
|
||||
|
||||
uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address,
|
||||
uint8_t* buffer, uint32_t num_bytes) {
|
||||
uint32_t bytes_read = 0;
|
||||
uint32_t avail;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < regions->count; ++i) {
|
||||
const size_t size = regions->size[i];
|
||||
if (address >= size) {
|
||||
/* address is not in this block, adjust and look at next block */
|
||||
address -= (unsigned)size;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (regions->data[i] == NULL) /* no memory associated to this block. abort */
|
||||
break;
|
||||
|
||||
avail = (unsigned)(size - address);
|
||||
if (avail >= num_bytes) {
|
||||
/* requested memory is fully within this block, copy and return it */
|
||||
memcpy(buffer, ®ions->data[i][address], num_bytes);
|
||||
bytes_read += num_bytes;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
/* copy whatever is available in this block, and adjust for the next block */
|
||||
memcpy(buffer, ®ions->data[i][address], avail);
|
||||
buffer += avail;
|
||||
bytes_read += avail;
|
||||
num_bytes -= avail;
|
||||
address = 0;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback) {
|
||||
rc_libretro_verbose_message_callback = callback;
|
||||
}
|
||||
|
||||
static void rc_libretro_verbose(const char* message) {
|
||||
if (rc_libretro_verbose_message_callback)
|
||||
rc_libretro_verbose_message_callback(message);
|
||||
}
|
||||
|
||||
static const char* rc_memory_type_str(int type) {
|
||||
switch (type)
|
||||
{
|
||||
case RC_MEMORY_TYPE_SAVE_RAM:
|
||||
return "SRAM";
|
||||
case RC_MEMORY_TYPE_VIDEO_RAM:
|
||||
return "VRAM";
|
||||
case RC_MEMORY_TYPE_UNUSED:
|
||||
return "UNUSED";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return "SYSTEM RAM";
|
||||
}
|
||||
|
||||
static void rc_libretro_memory_register_region(rc_libretro_memory_regions_t* regions, int type,
|
||||
uint8_t* data, size_t size, const char* description) {
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
if (regions->count == (sizeof(regions->size) / sizeof(regions->size[0]))) {
|
||||
rc_libretro_verbose("Too many memory memory regions to register");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data && regions->count > 0 && !regions->data[regions->count - 1]) {
|
||||
/* extend null region */
|
||||
regions->size[regions->count - 1] += size;
|
||||
}
|
||||
else if (data && regions->count > 0 &&
|
||||
data == (regions->data[regions->count - 1] + regions->size[regions->count - 1])) {
|
||||
/* extend non-null region */
|
||||
regions->size[regions->count - 1] += size;
|
||||
}
|
||||
else {
|
||||
/* create new region */
|
||||
regions->data[regions->count] = data;
|
||||
regions->size[regions->count] = size;
|
||||
++regions->count;
|
||||
}
|
||||
|
||||
regions->total_size += size;
|
||||
|
||||
if (rc_libretro_verbose_message_callback) {
|
||||
char message[128];
|
||||
snprintf(message, sizeof(message), "Registered 0x%04X bytes of %s at $%06X (%s)", (unsigned)size,
|
||||
rc_memory_type_str(type), (unsigned)(regions->total_size - size), description);
|
||||
rc_libretro_verbose_message_callback(message);
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_libretro_memory_init_without_regions(rc_libretro_memory_regions_t* regions,
|
||||
rc_libretro_get_core_memory_info_func get_core_memory_info) {
|
||||
/* no regions specified, assume system RAM followed by save RAM */
|
||||
char description[64];
|
||||
rc_libretro_core_memory_info_t info;
|
||||
|
||||
snprintf(description, sizeof(description), "offset 0x%06x", 0);
|
||||
|
||||
get_core_memory_info(RETRO_MEMORY_SYSTEM_RAM, &info);
|
||||
if (info.size)
|
||||
rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SYSTEM_RAM, info.data, info.size, description);
|
||||
|
||||
get_core_memory_info(RETRO_MEMORY_SAVE_RAM, &info);
|
||||
if (info.size)
|
||||
rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SAVE_RAM, info.data, info.size, description);
|
||||
}
|
||||
|
||||
static const struct retro_memory_descriptor* rc_libretro_memory_get_descriptor(const struct retro_memory_map* mmap, uint32_t real_address, size_t* offset)
|
||||
{
|
||||
const struct retro_memory_descriptor* desc = mmap->descriptors;
|
||||
const struct retro_memory_descriptor* end = desc + mmap->num_descriptors;
|
||||
|
||||
for (; desc < end; desc++) {
|
||||
if (desc->select == 0) {
|
||||
/* if select is 0, attempt to explcitly match the address */
|
||||
if (real_address >= desc->start && real_address < desc->start + desc->len) {
|
||||
*offset = real_address - desc->start;
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* otherwise, attempt to match the address by matching the select bits */
|
||||
/* address is in the block if (addr & select) == (start & select) */
|
||||
if (((desc->start ^ real_address) & desc->select) == 0) {
|
||||
/* get the relative offset of the address from the start of the memory block */
|
||||
uint32_t reduced_address = real_address - (unsigned)desc->start;
|
||||
|
||||
/* remove any bits from the reduced_address that correspond to the bits in the disconnect
|
||||
* mask and collapse the remaining bits. this code was copied from the mmap_reduce function
|
||||
* in RetroArch. i'm not exactly sure how it works, but it does. */
|
||||
uint32_t disconnect_mask = (unsigned)desc->disconnect;
|
||||
while (disconnect_mask) {
|
||||
const uint32_t tmp = (disconnect_mask - 1) & ~disconnect_mask;
|
||||
reduced_address = (reduced_address & tmp) | ((reduced_address >> 1) & ~tmp);
|
||||
disconnect_mask = (disconnect_mask & (disconnect_mask - 1)) >> 1;
|
||||
}
|
||||
|
||||
/* calculate the offset within the descriptor */
|
||||
*offset = reduced_address;
|
||||
|
||||
/* sanity check - make sure the descriptor is large enough to hold the target address */
|
||||
if (reduced_address < desc->len)
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*offset = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void rc_libretro_memory_init_from_memory_map(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
|
||||
const rc_memory_regions_t* console_regions) {
|
||||
char description[64];
|
||||
uint32_t i;
|
||||
uint8_t* region_start;
|
||||
uint8_t* desc_start;
|
||||
size_t desc_size;
|
||||
size_t offset;
|
||||
|
||||
for (i = 0; i < console_regions->num_regions; ++i) {
|
||||
const rc_memory_region_t* console_region = &console_regions->region[i];
|
||||
size_t console_region_size = console_region->end_address - console_region->start_address + 1;
|
||||
uint32_t real_address = console_region->real_address;
|
||||
uint32_t disconnect_size = 0;
|
||||
|
||||
while (console_region_size > 0) {
|
||||
const struct retro_memory_descriptor* desc = rc_libretro_memory_get_descriptor(mmap, real_address, &offset);
|
||||
if (!desc) {
|
||||
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
|
||||
snprintf(description, sizeof(description), "Could not map region starting at $%06X",
|
||||
(unsigned)(real_address - console_region->real_address + console_region->start_address));
|
||||
rc_libretro_verbose(description);
|
||||
}
|
||||
|
||||
if (disconnect_size && console_region_size > disconnect_size) {
|
||||
rc_libretro_memory_register_region(regions, console_region->type, NULL, disconnect_size, "null filler");
|
||||
console_region_size -= disconnect_size;
|
||||
real_address += disconnect_size;
|
||||
disconnect_size = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(description, sizeof(description), "descriptor %u, offset 0x%06X%s",
|
||||
(unsigned)(desc - mmap->descriptors) + 1, (int)offset, desc->ptr ? "" : " [no pointer]");
|
||||
|
||||
if (desc->ptr) {
|
||||
desc_start = (uint8_t*)desc->ptr + desc->offset;
|
||||
region_start = desc_start + offset;
|
||||
}
|
||||
else {
|
||||
region_start = NULL;
|
||||
}
|
||||
|
||||
desc_size = desc->len - offset;
|
||||
if (desc->disconnect && desc_size > desc->disconnect) {
|
||||
/* if we need to extract a disconnect bit, the largest block we can read is up to
|
||||
* the next time that bit flips */
|
||||
/* https://stackoverflow.com/questions/12247186/find-the-lowest-set-bit */
|
||||
disconnect_size = (desc->disconnect & -((int)desc->disconnect));
|
||||
desc_size = disconnect_size - (real_address & (disconnect_size - 1));
|
||||
}
|
||||
|
||||
if (console_region_size > desc_size) {
|
||||
if (desc_size == 0) {
|
||||
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
|
||||
snprintf(description, sizeof(description), "Could not map region starting at $%06X",
|
||||
(unsigned)(real_address - console_region->real_address + console_region->start_address));
|
||||
rc_libretro_verbose(description);
|
||||
}
|
||||
|
||||
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
|
||||
console_region_size = 0;
|
||||
}
|
||||
else {
|
||||
rc_libretro_memory_register_region(regions, console_region->type, region_start, desc_size, description);
|
||||
console_region_size -= desc_size;
|
||||
real_address += (unsigned)desc_size;
|
||||
}
|
||||
}
|
||||
else {
|
||||
rc_libretro_memory_register_region(regions, console_region->type, region_start, console_region_size, description);
|
||||
console_region_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t rc_libretro_memory_console_region_to_ram_type(uint8_t region_type) {
|
||||
switch (region_type)
|
||||
{
|
||||
case RC_MEMORY_TYPE_SAVE_RAM:
|
||||
return RETRO_MEMORY_SAVE_RAM;
|
||||
case RC_MEMORY_TYPE_VIDEO_RAM:
|
||||
return RETRO_MEMORY_VIDEO_RAM;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return RETRO_MEMORY_SYSTEM_RAM;
|
||||
}
|
||||
|
||||
static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regions_t* regions,
|
||||
rc_libretro_get_core_memory_info_func get_core_memory_info, const rc_memory_regions_t* console_regions) {
|
||||
char description[64];
|
||||
uint32_t i, j;
|
||||
rc_libretro_core_memory_info_t info;
|
||||
size_t offset;
|
||||
|
||||
for (i = 0; i < console_regions->num_regions; ++i) {
|
||||
const rc_memory_region_t* console_region = &console_regions->region[i];
|
||||
const size_t console_region_size = console_region->end_address - console_region->start_address + 1;
|
||||
const uint32_t type = rc_libretro_memory_console_region_to_ram_type(console_region->type);
|
||||
uint32_t base_address = 0;
|
||||
|
||||
for (j = 0; j <= i; ++j) {
|
||||
const rc_memory_region_t* console_region2 = &console_regions->region[j];
|
||||
if (rc_libretro_memory_console_region_to_ram_type(console_region2->type) == type) {
|
||||
base_address = console_region2->start_address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
offset = console_region->start_address - base_address;
|
||||
|
||||
get_core_memory_info(type, &info);
|
||||
|
||||
if (offset < info.size) {
|
||||
info.size -= offset;
|
||||
|
||||
if (info.data) {
|
||||
snprintf(description, sizeof(description), "offset 0x%06X", (int)offset);
|
||||
info.data += offset;
|
||||
}
|
||||
else {
|
||||
snprintf(description, sizeof(description), "null filler");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
|
||||
snprintf(description, sizeof(description), "Could not map region starting at $%06X", (unsigned)console_region->start_address);
|
||||
rc_libretro_verbose(description);
|
||||
}
|
||||
|
||||
info.data = NULL;
|
||||
info.size = 0;
|
||||
}
|
||||
|
||||
if (console_region_size > info.size) {
|
||||
/* want more than what is available, take what we can and null fill the rest */
|
||||
rc_libretro_memory_register_region(regions, console_region->type, info.data, info.size, description);
|
||||
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size - info.size, "null filler");
|
||||
}
|
||||
else {
|
||||
/* only take as much as we need */
|
||||
rc_libretro_memory_register_region(regions, console_region->type, info.data, console_region_size, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
|
||||
rc_libretro_get_core_memory_info_func get_core_memory_info, uint32_t console_id) {
|
||||
const rc_memory_regions_t* console_regions = rc_console_memory_regions(console_id);
|
||||
rc_libretro_memory_regions_t new_regions;
|
||||
int has_valid_region = 0;
|
||||
uint32_t i;
|
||||
|
||||
if (!regions)
|
||||
return 0;
|
||||
|
||||
memset(&new_regions, 0, sizeof(new_regions));
|
||||
|
||||
if (console_regions == NULL || console_regions->num_regions == 0)
|
||||
rc_libretro_memory_init_without_regions(&new_regions, get_core_memory_info);
|
||||
else if (mmap && mmap->num_descriptors != 0)
|
||||
rc_libretro_memory_init_from_memory_map(&new_regions, mmap, console_regions);
|
||||
else
|
||||
rc_libretro_memory_init_from_unmapped_memory(&new_regions, get_core_memory_info, console_regions);
|
||||
|
||||
/* determine if any valid regions were found */
|
||||
for (i = 0; i < new_regions.count; i++) {
|
||||
if (new_regions.data[i]) {
|
||||
has_valid_region = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(regions, &new_regions, sizeof(*regions));
|
||||
return has_valid_region;
|
||||
}
|
||||
|
||||
void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions) {
|
||||
memset(regions, 0, sizeof(*regions));
|
||||
}
|
||||
|
||||
void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
|
||||
const char* m3u_path, rc_libretro_get_image_path_func get_image_path) {
|
||||
char image_path[1024];
|
||||
char* m3u_contents;
|
||||
char* ptr;
|
||||
int64_t file_len;
|
||||
void* file_handle;
|
||||
int index = 0;
|
||||
|
||||
memset(hash_set, 0, sizeof(*hash_set));
|
||||
|
||||
if (!rc_path_compare_extension(m3u_path, "m3u"))
|
||||
return;
|
||||
|
||||
file_handle = rc_file_open(m3u_path);
|
||||
if (!file_handle) {
|
||||
rc_hash_error("Could not open playlist");
|
||||
return;
|
||||
}
|
||||
|
||||
rc_file_seek(file_handle, 0, SEEK_END);
|
||||
file_len = rc_file_tell(file_handle);
|
||||
rc_file_seek(file_handle, 0, SEEK_SET);
|
||||
|
||||
m3u_contents = (char*)malloc((size_t)file_len + 1);
|
||||
if (m3u_contents) {
|
||||
rc_file_read(file_handle, m3u_contents, (int)file_len);
|
||||
m3u_contents[file_len] = '\0';
|
||||
|
||||
rc_file_close(file_handle);
|
||||
|
||||
ptr = m3u_contents;
|
||||
do
|
||||
{
|
||||
/* ignore whitespace */
|
||||
while (isspace((int)*ptr))
|
||||
++ptr;
|
||||
|
||||
if (*ptr == '#') {
|
||||
/* ignore comment unless it's the special SAVEDISK extension */
|
||||
if (memcmp(ptr, "#SAVEDISK:", 10) == 0) {
|
||||
/* get the path to the save disk from the frontend, assign it a bogus hash so
|
||||
* it doesn't get hashed later */
|
||||
if (get_image_path(index, image_path, sizeof(image_path))) {
|
||||
const char save_disk_hash[33] = "[SAVE DISK]";
|
||||
rc_libretro_hash_set_add(hash_set, image_path, -1, save_disk_hash);
|
||||
++index;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* non-empty line, tally a file */
|
||||
++index;
|
||||
}
|
||||
|
||||
/* find the end of the line */
|
||||
while (*ptr && *ptr != '\n')
|
||||
++ptr;
|
||||
|
||||
} while (*ptr);
|
||||
|
||||
free(m3u_contents);
|
||||
}
|
||||
|
||||
if (hash_set->entries_count > 0) {
|
||||
/* at least one save disk was found. make sure the core supports the #SAVEDISK: extension by
|
||||
* asking for the last expected disk. if it's not found, assume no #SAVEDISK: support */
|
||||
if (!get_image_path(index - 1, image_path, sizeof(image_path)))
|
||||
hash_set->entries_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set) {
|
||||
if (hash_set->entries)
|
||||
free(hash_set->entries);
|
||||
memset(hash_set, 0, sizeof(*hash_set));
|
||||
}
|
||||
|
||||
static uint32_t rc_libretro_djb2(const char* input)
|
||||
{
|
||||
uint32_t result = 5381;
|
||||
char c;
|
||||
|
||||
while ((c = *input++) != '\0')
|
||||
result = ((result << 5) + result) + c; /* result = result * 33 + c */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
|
||||
const char* path, uint32_t game_id, const char hash[33]) {
|
||||
const uint32_t path_djb2 = (path != NULL) ? rc_libretro_djb2(path) : 0;
|
||||
struct rc_libretro_hash_entry_t* entry = NULL;
|
||||
struct rc_libretro_hash_entry_t* scan;
|
||||
struct rc_libretro_hash_entry_t* stop = hash_set->entries + hash_set->entries_count;;
|
||||
|
||||
if (path_djb2) {
|
||||
/* attempt to match the path */
|
||||
for (scan = hash_set->entries; scan < stop; ++scan) {
|
||||
if (scan->path_djb2 == path_djb2) {
|
||||
entry = scan;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!entry)
|
||||
{
|
||||
/* entry not found, allocate a new one */
|
||||
if (hash_set->entries_size == 0) {
|
||||
hash_set->entries_size = 4;
|
||||
hash_set->entries = (struct rc_libretro_hash_entry_t*)
|
||||
malloc(hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
|
||||
}
|
||||
else if (hash_set->entries_count == hash_set->entries_size) {
|
||||
hash_set->entries_size += 4;
|
||||
hash_set->entries = (struct rc_libretro_hash_entry_t*)realloc(hash_set->entries,
|
||||
hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
|
||||
}
|
||||
|
||||
if (hash_set->entries == NULL) /* unexpected, but better than crashing */
|
||||
return;
|
||||
|
||||
entry = hash_set->entries + hash_set->entries_count++;
|
||||
}
|
||||
|
||||
/* update the entry */
|
||||
entry->path_djb2 = path_djb2;
|
||||
entry->game_id = game_id;
|
||||
memcpy(entry->hash, hash, sizeof(entry->hash));
|
||||
}
|
||||
|
||||
const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path)
|
||||
{
|
||||
const uint32_t path_djb2 = rc_libretro_djb2(path);
|
||||
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
|
||||
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
|
||||
for (; scan < stop; ++scan) {
|
||||
if (scan->path_djb2 == path_djb2)
|
||||
return scan->hash;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash)
|
||||
{
|
||||
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
|
||||
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
|
||||
for (; scan < stop; ++scan) {
|
||||
if (memcmp(scan->hash, hash, sizeof(scan->hash)) == 0)
|
||||
return scan->game_id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
#ifndef RC_LIBRETRO_H
|
||||
#define RC_LIBRETRO_H
|
||||
|
||||
#include "rc_export.h"
|
||||
|
||||
/* this file comes from the libretro repository, which is not an explicit submodule.
|
||||
* the integration must set up paths appropriately to find it. */
|
||||
#include <libretro.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
/*****************************************************************************\
|
||||
| Disallowed Settings |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_disallowed_setting_t
|
||||
{
|
||||
const char* setting;
|
||||
const char* value;
|
||||
} rc_disallowed_setting_t;
|
||||
|
||||
RC_EXPORT const rc_disallowed_setting_t* RC_CCONV rc_libretro_get_disallowed_settings(const char* library_name);
|
||||
RC_EXPORT int RC_CCONV rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value);
|
||||
RC_EXPORT int RC_CCONV rc_libretro_is_system_allowed(const char* library_name, uint32_t console_id);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Memory Mapping |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* specifies a function to call for verbose logging */
|
||||
typedef void (RC_CCONV *rc_libretro_message_callback)(const char*);
|
||||
RC_EXPORT void RC_CCONV rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback);
|
||||
|
||||
#define RC_LIBRETRO_MAX_MEMORY_REGIONS 32
|
||||
typedef struct rc_libretro_memory_regions_t
|
||||
{
|
||||
uint8_t* data[RC_LIBRETRO_MAX_MEMORY_REGIONS];
|
||||
size_t size[RC_LIBRETRO_MAX_MEMORY_REGIONS];
|
||||
size_t total_size;
|
||||
uint32_t count;
|
||||
} rc_libretro_memory_regions_t;
|
||||
|
||||
typedef struct rc_libretro_core_memory_info_t
|
||||
{
|
||||
uint8_t* data;
|
||||
size_t size;
|
||||
} rc_libretro_core_memory_info_t;
|
||||
|
||||
typedef void (RC_CCONV *rc_libretro_get_core_memory_info_func)(uint32_t id, rc_libretro_core_memory_info_t* info);
|
||||
|
||||
RC_EXPORT int RC_CCONV rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
|
||||
rc_libretro_get_core_memory_info_func get_core_memory_info, uint32_t console_id);
|
||||
RC_EXPORT void RC_CCONV rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions);
|
||||
|
||||
RC_EXPORT uint8_t* RC_CCONV rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, uint32_t address);
|
||||
RC_EXPORT uint8_t* RC_CCONV rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, uint32_t address, uint32_t* avail);
|
||||
RC_EXPORT uint32_t RC_CCONV rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address, uint8_t* buffer, uint32_t num_bytes);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Disk Identification |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_libretro_hash_entry_t
|
||||
{
|
||||
uint32_t path_djb2;
|
||||
uint32_t game_id;
|
||||
char hash[33];
|
||||
} rc_libretro_hash_entry_t;
|
||||
|
||||
typedef struct rc_libretro_hash_set_t
|
||||
{
|
||||
struct rc_libretro_hash_entry_t* entries;
|
||||
uint16_t entries_count;
|
||||
uint16_t entries_size;
|
||||
} rc_libretro_hash_set_t;
|
||||
|
||||
typedef int (RC_CCONV *rc_libretro_get_image_path_func)(uint32_t index, char* buffer, size_t buffer_size);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
|
||||
const char* m3u_path, rc_libretro_get_image_path_func get_image_path);
|
||||
RC_EXPORT void RC_CCONV rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set);
|
||||
|
||||
RC_EXPORT void RC_CCONV rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
|
||||
const char* path, uint32_t game_id, const char hash[33]);
|
||||
RC_EXPORT const char* RC_CCONV rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path);
|
||||
RC_EXPORT int RC_CCONV rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_LIBRETRO_H */
|
|
@ -155,7 +155,7 @@ const char* rc_error_str(int ret)
|
|||
{
|
||||
switch (ret) {
|
||||
case RC_OK: return "OK";
|
||||
case RC_INVALID_LUA_OPERAND: return "Invalid Lua operand";
|
||||
case RC_INVALID_FUNC_OPERAND: return "Invalid function operand";
|
||||
case RC_INVALID_MEMORY_OPERAND: return "Invalid memory operand";
|
||||
case RC_INVALID_CONST_OPERAND: return "Invalid constant operand";
|
||||
case RC_INVALID_FP_OPERAND: return "Invalid floating-point operand";
|
||||
|
@ -193,6 +193,7 @@ const char* rc_error_str(int ret)
|
|||
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";
|
||||
case RC_NOT_FOUND: return "Not found";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,9 +94,9 @@ char* rc_alloc_str(rc_parse_state_t* parse, const char* text, size_t length) {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
void rc_init_preparse_state(rc_preparse_state_t* preparse, lua_State* L, int funcs_ndx)
|
||||
void rc_init_preparse_state(rc_preparse_state_t* preparse)
|
||||
{
|
||||
rc_init_parse_state(&preparse->parse, NULL, L, funcs_ndx);
|
||||
rc_init_parse_state(&preparse->parse, NULL);
|
||||
rc_init_parse_state_memrefs(&preparse->parse, &preparse->memrefs);
|
||||
}
|
||||
|
||||
|
@ -275,16 +275,8 @@ void rc_preparse_copy_memrefs(rc_parse_state_t* parse, rc_memrefs_t* memrefs)
|
|||
}
|
||||
}
|
||||
|
||||
void rc_reset_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx)
|
||||
void rc_reset_parse_state(rc_parse_state_t* parse, void* buffer)
|
||||
{
|
||||
#ifndef RC_DISABLE_LUA
|
||||
parse->L = L;
|
||||
parse->funcs_ndx = funcs_ndx;
|
||||
#else
|
||||
(void)L;
|
||||
(void)funcs_ndx;
|
||||
#endif
|
||||
|
||||
parse->buffer = buffer;
|
||||
|
||||
parse->offset = 0;
|
||||
|
@ -300,17 +292,18 @@ void rc_reset_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, i
|
|||
parse->is_value = 0;
|
||||
parse->has_required_hits = 0;
|
||||
parse->measured_as_percent = 0;
|
||||
parse->ignore_non_parse_errors = 0;
|
||||
|
||||
parse->scratch.strings = NULL;
|
||||
}
|
||||
|
||||
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx)
|
||||
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer)
|
||||
{
|
||||
/* could use memset here, but rc_parse_state_t contains a 512 byte buffer that doesn't need to be initialized */
|
||||
rc_buffer_init(&parse->scratch.buffer);
|
||||
memset(&parse->scratch.objs, 0, sizeof(parse->scratch.objs));
|
||||
|
||||
rc_reset_parse_state(parse, buffer, L, funcs_ndx);
|
||||
rc_reset_parse_state(parse, buffer);
|
||||
}
|
||||
|
||||
void rc_destroy_parse_state(rc_parse_state_t* parse)
|
||||
|
|
|
@ -255,7 +255,7 @@ void rc_parse_condition_internal(rc_condition_t* self, const char** memaddr, rc_
|
|||
/* non-modifying statements must have a second operand */
|
||||
if (!can_modify) {
|
||||
/* measured does not require a second operand when used in a value */
|
||||
if (self->type != RC_CONDITION_MEASURED) {
|
||||
if (self->type != RC_CONDITION_MEASURED && !parse->ignore_non_parse_errors) {
|
||||
parse->offset = RC_INVALID_OPERATOR;
|
||||
return;
|
||||
}
|
||||
|
@ -274,14 +274,16 @@ void rc_parse_condition_internal(rc_condition_t* self, const char** memaddr, rc_
|
|||
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;
|
||||
|
||||
default:
|
||||
parse->offset = RC_INVALID_OPERATOR;
|
||||
return;
|
||||
if (!parse->ignore_non_parse_errors) {
|
||||
parse->offset = RC_INVALID_OPERATOR;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ static int rc_classify_condition(const rc_condition_t* cond) {
|
|||
}
|
||||
}
|
||||
|
||||
static int32_t rc_classify_conditions(rc_condset_t* self, const char* memaddr) {
|
||||
static int32_t rc_classify_conditions(rc_condset_t* self, const char* memaddr, const rc_parse_state_t* parent_parse) {
|
||||
rc_parse_state_t parse;
|
||||
rc_memrefs_t memrefs;
|
||||
rc_condition_t condition;
|
||||
|
@ -55,8 +55,9 @@ static int32_t rc_classify_conditions(rc_condset_t* self, const char* memaddr) {
|
|||
uint32_t index = 0;
|
||||
uint32_t chain_length = 1;
|
||||
|
||||
rc_init_parse_state(&parse, NULL, NULL, 0);
|
||||
rc_init_parse_state(&parse, NULL);
|
||||
rc_init_parse_state_memrefs(&parse, &memrefs);
|
||||
parse.ignore_non_parse_errors = parent_parse->ignore_non_parse_errors;
|
||||
|
||||
do {
|
||||
rc_parse_condition_internal(&condition, &memaddr, &parse);
|
||||
|
@ -109,7 +110,7 @@ static int rc_find_next_classification(const char* memaddr) {
|
|||
rc_condition_t condition;
|
||||
int classification;
|
||||
|
||||
rc_init_parse_state(&parse, NULL, NULL, 0);
|
||||
rc_init_parse_state(&parse, NULL);
|
||||
rc_init_parse_state_memrefs(&parse, &memrefs);
|
||||
|
||||
do {
|
||||
|
@ -215,7 +216,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) {
|
|||
}
|
||||
|
||||
memset(&local_condset, 0, sizeof(local_condset));
|
||||
result = rc_classify_conditions(&local_condset, *memaddr);
|
||||
result = rc_classify_conditions(&local_condset, *memaddr, parse);
|
||||
if (result < 0) {
|
||||
parse->offset = result;
|
||||
return NULL;
|
||||
|
@ -260,7 +261,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) {
|
|||
if (parse->offset < 0)
|
||||
return NULL;
|
||||
|
||||
if (condition.oper == RC_OPERATOR_NONE) {
|
||||
if (condition.oper == RC_OPERATOR_NONE && !parse->ignore_non_parse_errors) {
|
||||
switch (condition.type) {
|
||||
case RC_CONDITION_ADD_ADDRESS:
|
||||
case RC_CONDITION_ADD_SOURCE:
|
||||
|
@ -285,8 +286,10 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) {
|
|||
case RC_CONDITION_MEASURED:
|
||||
if (measured_target != 0) {
|
||||
/* multiple Measured flags cannot exist in the same group */
|
||||
parse->offset = RC_MULTIPLE_MEASURED;
|
||||
return NULL;
|
||||
if (!parse->ignore_non_parse_errors) {
|
||||
parse->offset = RC_MULTIPLE_MEASURED;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if (parse->is_value) {
|
||||
measured_target = (uint32_t)-1;
|
||||
|
@ -304,15 +307,17 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) {
|
|||
else if (condition.operand2.type == RC_OPERAND_FP) {
|
||||
measured_target = (unsigned)condition.operand2.value.dbl;
|
||||
}
|
||||
else {
|
||||
else if (!parse->ignore_non_parse_errors) {
|
||||
parse->offset = RC_INVALID_MEASURED_TARGET;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (parse->measured_target && measured_target != parse->measured_target) {
|
||||
/* multiple Measured flags in separate groups must have the same target */
|
||||
parse->offset = RC_MULTIPLE_MEASURED;
|
||||
return NULL;
|
||||
if (!parse->ignore_non_parse_errors) {
|
||||
parse->offset = RC_MULTIPLE_MEASURED;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
parse->measured_target = measured_target;
|
||||
|
@ -321,7 +326,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse) {
|
|||
case RC_CONDITION_STANDARD:
|
||||
case RC_CONDITION_TRIGGER:
|
||||
/* these flags are not allowed in value expressions */
|
||||
if (parse->is_value) {
|
||||
if (parse->is_value && !parse->ignore_non_parse_errors) {
|
||||
parse->offset = RC_INVALID_VALUE_FLAG;
|
||||
return NULL;
|
||||
}
|
||||
|
@ -507,6 +512,10 @@ static void rc_condset_evaluate_reset_if(rc_condition_t* condition, rc_eval_stat
|
|||
const uint8_t cond_valid = rc_condset_evaluate_condition(condition, eval_state);
|
||||
|
||||
if (cond_valid) {
|
||||
/* flag the condition as being responsible for the reset */
|
||||
/* make sure not to modify bit0, as we use bitwise-and operators to combine truthiness */
|
||||
condition->is_true |= 0x02;
|
||||
|
||||
/* set cannot be valid if we've hit a reset condition */
|
||||
eval_state->is_true = eval_state->is_primed = 0;
|
||||
|
||||
|
@ -552,9 +561,11 @@ static void rc_condset_evaluate_measured(rc_condition_t* condition, rc_eval_stat
|
|||
}
|
||||
|
||||
static void rc_condset_evaluate_measured_if(rc_condition_t* condition, rc_eval_state_t* eval_state) {
|
||||
rc_condset_evaluate_standard(condition, eval_state);
|
||||
const uint8_t cond_valid = rc_condset_evaluate_condition(condition, eval_state);
|
||||
|
||||
eval_state->can_measure &= condition->is_true;
|
||||
eval_state->is_true &= cond_valid;
|
||||
eval_state->is_primed &= cond_valid;
|
||||
eval_state->can_measure &= cond_valid;
|
||||
}
|
||||
|
||||
static void rc_condset_evaluate_add_hits(rc_condition_t* condition, rc_eval_state_t* eval_state) {
|
||||
|
@ -699,6 +710,17 @@ int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) {
|
|||
}
|
||||
|
||||
if (self->num_measured_conditions) {
|
||||
/* IMPORTANT: reset hit counts on these conditions before processing them so
|
||||
* the MeasuredIf logic and Measured value are correct.
|
||||
* NOTE: a ResetIf in a later alt group may not have been processed yet.
|
||||
* Accept that as a weird edge case, and just recommend the user
|
||||
* move the ResetIf if it becomes a problem. */
|
||||
if (eval_state->was_reset) {
|
||||
int i;
|
||||
for (i = 0; i < self->num_measured_conditions; ++i)
|
||||
conditions[i].current_hits = 0;
|
||||
}
|
||||
|
||||
/* the measured value must be calculated every frame, even if hit counts will be reset */
|
||||
rc_test_condset_internal(conditions, self->num_measured_conditions, eval_state, 0);
|
||||
conditions += self->num_measured_conditions;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
int rc_parse_format(const char* format_str) {
|
||||
switch (*format_str++) {
|
||||
|
@ -158,6 +159,52 @@ static int rc_format_value_padded(char* buffer, size_t size, const char* format,
|
|||
return snprintf(buffer, size, format, value);
|
||||
}
|
||||
|
||||
static int rc_format_insert_commas(int chars, char* buffer, size_t size)
|
||||
{
|
||||
int to_insert;
|
||||
char* src = buffer;
|
||||
char* ptr;
|
||||
char* dst = &buffer[chars];
|
||||
if (chars == 0)
|
||||
return 0;
|
||||
|
||||
/* ignore leading negative sign */
|
||||
if (*src == '-')
|
||||
src++;
|
||||
|
||||
/* determine how many digits are present in the leading number */
|
||||
ptr = src;
|
||||
while (ptr < dst && isdigit((int)*ptr))
|
||||
++ptr;
|
||||
|
||||
/* determine how many commas are needed */
|
||||
to_insert = (int)((ptr - src - 1) / 3);
|
||||
if (to_insert == 0) /* no commas needed */
|
||||
return chars;
|
||||
|
||||
/* if there's not enough room to insert the commas, leave string as-is, but return wanted space */
|
||||
chars += to_insert;
|
||||
if (chars >= (int)size)
|
||||
return chars;
|
||||
|
||||
/* move the trailing part of the string */
|
||||
memmove(ptr + to_insert, ptr, dst - ptr + 1);
|
||||
|
||||
/* shift blocks of three digits at a time, inserting commas in front of them */
|
||||
src = ptr - 1;
|
||||
dst = src + to_insert;
|
||||
while (to_insert > 0) {
|
||||
*dst-- = *src--;
|
||||
*dst-- = *src--;
|
||||
*dst-- = *src--;
|
||||
*dst-- = ',';
|
||||
|
||||
--to_insert;
|
||||
}
|
||||
|
||||
return chars;
|
||||
}
|
||||
|
||||
int rc_format_typed_value(char* buffer, size_t size, const rc_typed_value_t* value, int format) {
|
||||
int chars;
|
||||
rc_typed_value_t converted_value;
|
||||
|
@ -199,8 +246,7 @@ int rc_format_typed_value(char* buffer, size_t size, const rc_typed_value_t* val
|
|||
|
||||
case RC_FORMAT_SCORE:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
|
||||
chars = snprintf(buffer, size, "%06d", converted_value.value.i32);
|
||||
break;
|
||||
return snprintf(buffer, size, "%06d", converted_value.value.i32);
|
||||
|
||||
case RC_FORMAT_FLOAT1:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
|
||||
|
@ -266,9 +312,13 @@ int rc_format_typed_value(char* buffer, size_t size, const rc_typed_value_t* val
|
|||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
|
||||
chars = snprintf(buffer, size, "%u", converted_value.value.u32);
|
||||
break;
|
||||
|
||||
case RC_FORMAT_UNFORMATTED:
|
||||
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
|
||||
return snprintf(buffer, size, "%u", converted_value.value.u32);
|
||||
}
|
||||
|
||||
return chars;
|
||||
return rc_format_insert_commas(chars, buffer, size);
|
||||
}
|
||||
|
||||
int rc_format_value(char* buffer, int size, int32_t value, int format) {
|
||||
|
|
|
@ -131,7 +131,7 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
|
|||
int rc_lboard_size(const char* memaddr) {
|
||||
rc_lboard_with_memrefs_t* lboard;
|
||||
rc_preparse_state_t preparse;
|
||||
rc_init_preparse_state(&preparse, NULL, 0);
|
||||
rc_init_preparse_state(&preparse);
|
||||
|
||||
lboard = RC_ALLOC(rc_lboard_with_memrefs_t, &preparse.parse);
|
||||
rc_parse_lboard_internal(&lboard->lboard, memaddr, &preparse.parse);
|
||||
|
@ -141,18 +141,21 @@ int rc_lboard_size(const char* memaddr) {
|
|||
return preparse.parse.offset;
|
||||
}
|
||||
|
||||
rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
|
||||
rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, void* unused_L, int unused_funcs_idx) {
|
||||
rc_lboard_with_memrefs_t* lboard;
|
||||
rc_preparse_state_t preparse;
|
||||
|
||||
(void)unused_L;
|
||||
(void)unused_funcs_idx;
|
||||
|
||||
if (!buffer || !memaddr)
|
||||
return 0;
|
||||
|
||||
rc_init_preparse_state(&preparse, L, funcs_ndx);
|
||||
rc_init_preparse_state(&preparse);
|
||||
lboard = RC_ALLOC(rc_lboard_with_memrefs_t, &preparse.parse);
|
||||
rc_parse_lboard_internal(&lboard->lboard, memaddr, &preparse.parse);
|
||||
|
||||
rc_reset_parse_state(&preparse.parse, buffer, L, funcs_ndx);
|
||||
rc_reset_parse_state(&preparse.parse, buffer);
|
||||
lboard = RC_ALLOC(rc_lboard_with_memrefs_t, &preparse.parse);
|
||||
rc_preparse_alloc_memrefs(&lboard->memrefs, &preparse);
|
||||
|
||||
|
@ -170,7 +173,7 @@ static void rc_update_lboard_memrefs(rc_lboard_t* self, rc_peek_t peek, void* ud
|
|||
}
|
||||
}
|
||||
|
||||
int rc_evaluate_lboard(rc_lboard_t* self, int32_t* value, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
int rc_evaluate_lboard(rc_lboard_t* self, int32_t* value, rc_peek_t peek, void* peek_ud, void* unused_L) {
|
||||
int start_ok, cancel_ok, submit_ok;
|
||||
|
||||
rc_update_lboard_memrefs(self, peek, peek_ud);
|
||||
|
@ -179,9 +182,9 @@ int rc_evaluate_lboard(rc_lboard_t* self, int32_t* value, rc_peek_t peek, void*
|
|||
return RC_LBOARD_STATE_INACTIVE;
|
||||
|
||||
/* these are always tested once every frame, to ensure hit counts work properly */
|
||||
start_ok = rc_test_trigger(&self->start, peek, peek_ud, L);
|
||||
cancel_ok = rc_test_trigger(&self->cancel, peek, peek_ud, L);
|
||||
submit_ok = rc_test_trigger(&self->submit, peek, peek_ud, L);
|
||||
start_ok = rc_test_trigger(&self->start, peek, peek_ud, unused_L);
|
||||
cancel_ok = rc_test_trigger(&self->cancel, peek, peek_ud, unused_L);
|
||||
submit_ok = rc_test_trigger(&self->submit, peek, peek_ud, unused_L);
|
||||
|
||||
switch (self->state)
|
||||
{
|
||||
|
@ -238,13 +241,13 @@ int rc_evaluate_lboard(rc_lboard_t* self, int32_t* value, rc_peek_t peek, void*
|
|||
switch (self->state) {
|
||||
case RC_LBOARD_STATE_STARTED:
|
||||
if (self->progress) {
|
||||
*value = rc_evaluate_value(self->progress, peek, peek_ud, L);
|
||||
*value = rc_evaluate_value(self->progress, peek, peek_ud, unused_L);
|
||||
break;
|
||||
}
|
||||
/* fallthrough */ /* to RC_LBOARD_STATE_TRIGGERED */
|
||||
|
||||
case RC_LBOARD_STATE_TRIGGERED:
|
||||
*value = rc_evaluate_value(&self->value, peek, peek_ud, L);
|
||||
*value = rc_evaluate_value(&self->value, peek, peek_ud, unused_L);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
@ -5,62 +5,22 @@
|
|||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) {
|
||||
static int rc_parse_operand_func_call(rc_operand_t* self, const char** memaddr) {
|
||||
const char* aux = *memaddr;
|
||||
#ifndef RC_DISABLE_LUA
|
||||
const char* id;
|
||||
#endif
|
||||
|
||||
if (*aux++ != '@') {
|
||||
return RC_INVALID_LUA_OPERAND;
|
||||
return RC_INVALID_FUNC_OPERAND;
|
||||
}
|
||||
|
||||
if (!isalpha((unsigned char)*aux)) {
|
||||
return RC_INVALID_LUA_OPERAND;
|
||||
return RC_INVALID_FUNC_OPERAND;
|
||||
}
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
id = aux;
|
||||
#endif
|
||||
|
||||
while (isalnum((unsigned char)*aux) || *aux == '_') {
|
||||
aux++;
|
||||
}
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
|
||||
if (parse->L != 0) {
|
||||
if (!lua_istable(parse->L, parse->funcs_ndx)) {
|
||||
return RC_INVALID_LUA_OPERAND;
|
||||
}
|
||||
|
||||
lua_pushlstring(parse->L, id, aux - id);
|
||||
lua_gettable(parse->L, parse->funcs_ndx);
|
||||
|
||||
if (!lua_isfunction(parse->L, -1)) {
|
||||
lua_pop(parse->L, 1);
|
||||
return RC_INVALID_LUA_OPERAND;
|
||||
}
|
||||
|
||||
self->value.luafunc = luaL_ref(parse->L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
|
||||
#else
|
||||
(void)parse;
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
self->type = RC_OPERAND_LUA;
|
||||
self->type = RC_OPERAND_FUNC;
|
||||
self->size = RC_MEMSIZE_32_BITS;
|
||||
self->memref_access_type = RC_OPERAND_ADDRESS;
|
||||
*memaddr = aux;
|
||||
|
@ -322,7 +282,7 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, rc_parse_state_t*
|
|||
break;
|
||||
|
||||
case '@':
|
||||
ret = rc_parse_operand_lua(self, &aux, parse);
|
||||
ret = rc_parse_operand_func_call(self, &aux);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
@ -334,27 +294,6 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, rc_parse_state_t*
|
|||
return RC_OK;
|
||||
}
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
|
||||
typedef struct {
|
||||
rc_peek_t peek;
|
||||
void* ud;
|
||||
}
|
||||
rc_luapeek_t;
|
||||
|
||||
static int rc_luapeek(lua_State* L) {
|
||||
uint32_t address = (uint32_t)luaL_checkinteger(L, 1);
|
||||
uint32_t num_bytes = (uint32_t)luaL_checkinteger(L, 2);
|
||||
rc_luapeek_t* luapeek = (rc_luapeek_t*)lua_touserdata(L, 3);
|
||||
|
||||
uint32_t value = luapeek->peek(address, num_bytes, luapeek->ud);
|
||||
|
||||
lua_pushinteger(L, value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
void rc_operand_set_const(rc_operand_t* self, uint32_t value) {
|
||||
self->size = RC_MEMSIZE_32_BITS;
|
||||
self->type = RC_OPERAND_CONST;
|
||||
|
@ -456,7 +395,7 @@ int rc_operand_type_is_memref(uint8_t type) {
|
|||
switch (type) {
|
||||
case RC_OPERAND_CONST:
|
||||
case RC_OPERAND_FP:
|
||||
case RC_OPERAND_LUA:
|
||||
case RC_OPERAND_FUNC:
|
||||
case RC_OPERAND_RECALL:
|
||||
return 0;
|
||||
|
||||
|
@ -588,51 +527,32 @@ static uint32_t rc_transform_operand_value(uint32_t value, const rc_operand_t* s
|
|||
void rc_operand_addsource(rc_operand_t* self, rc_parse_state_t* parse, uint8_t new_size) {
|
||||
rc_modified_memref_t* modified_memref;
|
||||
|
||||
if (rc_operand_is_memref(&parse->addsource_parent)) {
|
||||
if ((self->type == RC_OPERAND_DELTA || self->type == RC_OPERAND_PRIOR) &&
|
||||
self->type == parse->addsource_parent.type) {
|
||||
/* if adding prev(x) and prev(y), just add x and y and take the prev of that.
|
||||
* same for adding prior(x) and prior(y). */
|
||||
rc_operand_t modifier;
|
||||
memcpy(&modifier, self, sizeof(modifier));
|
||||
modifier.type = parse->addsource_parent.type = RC_OPERAND_ADDRESS;
|
||||
|
||||
if ((self->type == RC_OPERAND_DELTA || self->type == RC_OPERAND_PRIOR) &&
|
||||
self->type == parse->addsource_parent.type) {
|
||||
/* if adding prev(x) and prev(y), just add x and y and take the prev of that */
|
||||
memcpy(&modifier, self, sizeof(modifier));
|
||||
modifier.type = parse->addsource_parent.type = RC_OPERAND_ADDRESS;
|
||||
|
||||
modified_memref = rc_alloc_modified_memref(parse,
|
||||
new_size, &parse->addsource_parent, parse->addsource_oper, &modifier);
|
||||
}
|
||||
else {
|
||||
modified_memref = rc_alloc_modified_memref(parse,
|
||||
new_size, &parse->addsource_parent, parse->addsource_oper, self);
|
||||
}
|
||||
modified_memref = rc_alloc_modified_memref(parse,
|
||||
new_size, &parse->addsource_parent, parse->addsource_oper, &modifier);
|
||||
}
|
||||
else {
|
||||
/* N + A => A + N */
|
||||
/* -N + A => A - N */
|
||||
modified_memref = rc_alloc_modified_memref(parse,
|
||||
new_size, &parse->addsource_parent, parse->addsource_oper, self);
|
||||
new_size, &parse->addsource_parent, parse->addsource_oper, self);
|
||||
|
||||
/* the modified memref will contain the combination of modified values, take the current value from that */
|
||||
self->type = self->memref_access_type = RC_OPERAND_ADDRESS;
|
||||
}
|
||||
|
||||
self->value.memref = (rc_memref_t*)modified_memref;
|
||||
|
||||
if (!rc_operand_is_memref(self)) {
|
||||
/* if adding a constant, change the type to be address (current value) */
|
||||
self->type = self->memref_access_type = RC_OPERAND_ADDRESS;
|
||||
}
|
||||
else if (rc_operand_type_is_transform(self->type)) {
|
||||
/* transform is applied in the modified_memref. change the type to be
|
||||
* address (current value) to avoid applying the transform again */
|
||||
self->type = self->memref_access_type = RC_OPERAND_ADDRESS;
|
||||
}
|
||||
|
||||
/* result of an AddSource operation is always a 32-bit integer (even if parent or modifier is a float) */
|
||||
self->size = RC_MEMSIZE_32_BITS;
|
||||
}
|
||||
|
||||
void rc_evaluate_operand(rc_typed_value_t* result, const rc_operand_t* self, rc_eval_state_t* eval_state) {
|
||||
#ifndef RC_DISABLE_LUA
|
||||
rc_luapeek_t luapeek;
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
/* step 1: read memory */
|
||||
switch (self->type) {
|
||||
case RC_OPERAND_CONST:
|
||||
|
@ -645,34 +565,10 @@ void rc_evaluate_operand(rc_typed_value_t* result, const rc_operand_t* self, rc_
|
|||
result->value.f32 = (float)self->value.dbl;
|
||||
return;
|
||||
|
||||
case RC_OPERAND_LUA:
|
||||
case RC_OPERAND_FUNC:
|
||||
/* this feature was never actualized */
|
||||
result->type = RC_VALUE_TYPE_UNSIGNED;
|
||||
result->value.u32 = 0;
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
if (eval_state->L != 0) {
|
||||
lua_rawgeti(eval_state->L, LUA_REGISTRYINDEX, self->value.luafunc);
|
||||
lua_pushcfunction(eval_state->L, rc_luapeek);
|
||||
|
||||
luapeek.peek = eval_state->peek;
|
||||
luapeek.ud = eval_state->peek_userdata;
|
||||
|
||||
lua_pushlightuserdata(eval_state->L, &luapeek);
|
||||
|
||||
if (lua_pcall(eval_state->L, 2, 1, 0) == LUA_OK) {
|
||||
if (lua_isboolean(eval_state->L, -1)) {
|
||||
result->value.u32 = (uint32_t)lua_toboolean(eval_state->L, -1);
|
||||
}
|
||||
else {
|
||||
result->value.u32 = (uint32_t)lua_tonumber(eval_state->L, -1);
|
||||
}
|
||||
}
|
||||
|
||||
lua_pop(eval_state->L, 1);
|
||||
}
|
||||
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
return;
|
||||
|
||||
case RC_OPERAND_RECALL:
|
||||
|
|
|
@ -211,9 +211,6 @@ typedef struct {
|
|||
/* memory accessors */
|
||||
rc_peek_t peek;
|
||||
void* peek_userdata;
|
||||
#ifndef RC_DISABLE_LUA
|
||||
lua_State* L;
|
||||
#endif
|
||||
|
||||
/* processing state */
|
||||
rc_typed_value_t measured_value; /* captured Measured value */
|
||||
|
@ -241,11 +238,6 @@ rc_eval_state_t;
|
|||
typedef struct {
|
||||
int32_t offset;
|
||||
|
||||
#ifndef RC_DISABLE_LUA
|
||||
lua_State* L;
|
||||
int funcs_ndx;
|
||||
#endif
|
||||
|
||||
void* buffer;
|
||||
rc_scratch_t scratch;
|
||||
|
||||
|
@ -264,6 +256,7 @@ typedef struct {
|
|||
uint8_t is_value;
|
||||
uint8_t has_required_hits;
|
||||
uint8_t measured_as_percent;
|
||||
uint8_t ignore_non_parse_errors;
|
||||
}
|
||||
rc_parse_state_t;
|
||||
|
||||
|
@ -272,17 +265,15 @@ typedef struct rc_preparse_state_t {
|
|||
rc_memrefs_t memrefs;
|
||||
} rc_preparse_state_t;
|
||||
|
||||
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx);
|
||||
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer);
|
||||
void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memrefs_t* memrefs);
|
||||
void rc_reset_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx);
|
||||
void rc_reset_parse_state(rc_parse_state_t* parse, void* buffer);
|
||||
void rc_destroy_parse_state(rc_parse_state_t* parse);
|
||||
void rc_init_preparse_state(rc_preparse_state_t* preparse, lua_State* L, int funcs_ndx);
|
||||
void rc_init_preparse_state(rc_preparse_state_t* preparse);
|
||||
void rc_preparse_alloc_memrefs(rc_memrefs_t* memrefs, rc_preparse_state_t* preparse);
|
||||
void rc_preparse_reserve_memrefs(rc_preparse_state_t* preparse, rc_memrefs_t* memrefs);
|
||||
void rc_preparse_copy_memrefs(rc_parse_state_t* parse, rc_memrefs_t* memrefs);
|
||||
void rc_destroy_preparse_state(rc_preparse_state_t *preparse);
|
||||
void rc_copy_memrefs_into_parse_state(rc_parse_state_t* parse, rc_memref_t* memrefs);
|
||||
void rc_sync_operand(rc_operand_t* operand, rc_parse_state_t* parse, const rc_memref_t* memrefs);
|
||||
|
||||
void* rc_alloc(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset);
|
||||
void* rc_alloc_scratch(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset);
|
||||
|
@ -360,12 +351,12 @@ void rc_operand_set_float_const(rc_operand_t* self, double value);
|
|||
|
||||
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);
|
||||
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud);
|
||||
void rc_reset_value(rc_value_t* self);
|
||||
int rc_value_from_hits(rc_value_t* self);
|
||||
rc_value_t* rc_alloc_variable(const char* memaddr, size_t memaddr_len, rc_parse_state_t* parse);
|
||||
uint32_t rc_count_values(const rc_value_t* values);
|
||||
void rc_update_values(rc_value_t* values, rc_peek_t peek, void* ud, lua_State* L);
|
||||
void rc_update_values(rc_value_t* values, rc_peek_t peek, void* ud);
|
||||
void rc_reset_values(rc_value_t* values);
|
||||
|
||||
void rc_typed_value_convert(rc_typed_value_t* value, char new_type);
|
||||
|
|
|
@ -134,7 +134,7 @@
|
|||
<DisplayString Condition="value==RC_OPERAND_DELTA">{RC_OPERAND_DELTA}</DisplayString>
|
||||
<DisplayString Condition="value==RC_OPERAND_CONST">{RC_OPERAND_CONST}</DisplayString>
|
||||
<DisplayString Condition="value==RC_OPERAND_FP">{RC_OPERAND_FP}</DisplayString>
|
||||
<DisplayString Condition="value==RC_OPERAND_LUA">{RC_OPERAND_LUA}</DisplayString>
|
||||
<DisplayString Condition="value==RC_OPERAND_FUNC">{RC_OPERAND_FUNC}</DisplayString>
|
||||
<DisplayString Condition="value==RC_OPERAND_PRIOR">{RC_OPERAND_PRIOR}</DisplayString>
|
||||
<DisplayString Condition="value==RC_OPERAND_BCD">{RC_OPERAND_BCD}</DisplayString>
|
||||
<DisplayString Condition="value==RC_OPERAND_INVERTED">{RC_OPERAND_INVERTED}</DisplayString>
|
||||
|
@ -153,7 +153,7 @@
|
|||
<DisplayString Condition="type==RC_OPERAND_CONST">{{value {value.num}}}</DisplayString>
|
||||
<DisplayString Condition="type==RC_OPERAND_FP">{{value {value.dbl}}}</DisplayString>
|
||||
<DisplayString Condition="type==RC_OPERAND_RECALL">{{recall}}</DisplayString>
|
||||
<DisplayString Condition="type==RC_OPERAND_LUA">{{lua @{value.luafunc}}}</DisplayString>
|
||||
<DisplayString Condition="type==RC_OPERAND_FUNC">{{func @{value}}}</DisplayString>
|
||||
<DisplayString Condition="type==0xFF">{{none}}</DisplayString>
|
||||
<DisplayString>{{unknown}}</DisplayString>
|
||||
<Expand>
|
||||
|
|
|
@ -364,6 +364,13 @@ static int rc_validate_condset_internal(const rc_condset_t* condset, char result
|
|||
break;
|
||||
|
||||
case RC_CONDITION_RESET_IF:
|
||||
if (in_add_hits) {
|
||||
/* ResetIf at the end of a hit chain does not require a hit target.
|
||||
* It's meant to reset things if some subset of conditions have been true. */
|
||||
in_add_hits = 0;
|
||||
is_combining = 0;
|
||||
break;
|
||||
}
|
||||
if (cond->required_hits == 1) {
|
||||
snprintf(result, result_size, "Condition %d: Hit target of 1 is redundant on ResetIf", index);
|
||||
return 0;
|
||||
|
|
|
@ -23,7 +23,7 @@ static void rc_alloc_helper_variable_memref_value(rc_richpresence_display_part_t
|
|||
part->value.type = RC_OPERAND_NONE;
|
||||
|
||||
/* if the expression can be represented as just a memory reference, do so */
|
||||
rc_init_preparse_state(&preparse, NULL, 0);
|
||||
rc_init_preparse_state(&preparse);
|
||||
preparse.parse.existing_memrefs = parse->memrefs;
|
||||
value = RC_ALLOC(rc_value_t, &preparse.parse);
|
||||
rc_parse_value_internal(value, &test_memaddr, &preparse.parse);
|
||||
|
@ -39,7 +39,7 @@ static void rc_alloc_helper_variable_memref_value(rc_richpresence_display_part_t
|
|||
rc_preparse_copy_memrefs(parse, &preparse.memrefs);
|
||||
|
||||
/* parse the value into the scratch buffer so we can look at it */
|
||||
rc_reset_parse_state(&preparse.parse, rc_buffer_alloc(&preparse.parse.scratch.buffer, (size_t)size), NULL, 0);
|
||||
rc_reset_parse_state(&preparse.parse, rc_buffer_alloc(&preparse.parse.scratch.buffer, (size_t)size));
|
||||
preparse.parse.memrefs = parse->memrefs;
|
||||
preparse.parse.existing_memrefs = parse->existing_memrefs;
|
||||
value = RC_ALLOC(rc_value_t, &preparse.parse);
|
||||
|
@ -230,7 +230,8 @@ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const c
|
|||
{"Fixed1", 6, RC_FORMAT_FIXED1},
|
||||
{"Fixed2", 6, RC_FORMAT_FIXED2},
|
||||
{"Fixed3", 6, RC_FORMAT_FIXED3},
|
||||
{"Unsigned", 8, RC_FORMAT_UNSIGNED_VALUE}
|
||||
{"Unsigned", 8, RC_FORMAT_UNSIGNED_VALUE},
|
||||
{"Unformatted", 11, RC_FORMAT_UNFORMATTED}
|
||||
};
|
||||
size_t i;
|
||||
|
||||
|
@ -652,7 +653,7 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
|||
int rc_richpresence_size_lines(const char* script, int* lines_read) {
|
||||
rc_richpresence_with_memrefs_t* richpresence;
|
||||
rc_preparse_state_t preparse;
|
||||
rc_init_preparse_state(&preparse, NULL, 0);
|
||||
rc_init_preparse_state(&preparse);
|
||||
|
||||
richpresence = RC_ALLOC(rc_richpresence_with_memrefs_t, &preparse.parse);
|
||||
preparse.parse.variables = &richpresence->richpresence.values;
|
||||
|
@ -670,19 +671,22 @@ int rc_richpresence_size(const char* script) {
|
|||
return rc_richpresence_size_lines(script, NULL);
|
||||
}
|
||||
|
||||
rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx) {
|
||||
rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, void* unused_L, int unused_funcs_idx) {
|
||||
rc_richpresence_with_memrefs_t* richpresence;
|
||||
rc_preparse_state_t preparse;
|
||||
|
||||
(void)unused_L;
|
||||
(void)unused_funcs_idx;
|
||||
|
||||
if (!buffer || !script)
|
||||
return NULL;
|
||||
|
||||
rc_init_preparse_state(&preparse, L, funcs_ndx);
|
||||
rc_init_preparse_state(&preparse);
|
||||
richpresence = RC_ALLOC(rc_richpresence_with_memrefs_t, &preparse.parse);
|
||||
preparse.parse.variables = &richpresence->richpresence.values;
|
||||
rc_parse_richpresence_internal(&richpresence->richpresence, script, &preparse.parse);
|
||||
|
||||
rc_reset_parse_state(&preparse.parse, buffer, L, funcs_ndx);
|
||||
rc_reset_parse_state(&preparse.parse, buffer);
|
||||
richpresence = RC_ALLOC(rc_richpresence_with_memrefs_t, &preparse.parse);
|
||||
preparse.parse.variables = &richpresence->richpresence.values;
|
||||
rc_preparse_alloc_memrefs(&richpresence->memrefs, &preparse);
|
||||
|
@ -710,15 +714,15 @@ rc_memrefs_t* rc_richpresence_get_memrefs(rc_richpresence_t* self) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, void* unused_L) {
|
||||
rc_richpresence_display_t* display;
|
||||
|
||||
rc_update_richpresence_memrefs(richpresence, peek, peek_ud);
|
||||
rc_update_values(richpresence->values, peek, peek_ud, L);
|
||||
rc_update_values(richpresence->values, peek, peek_ud);
|
||||
|
||||
for (display = richpresence->first_display; display; display = display->next) {
|
||||
if (display->has_required_hits)
|
||||
rc_test_trigger(&display->trigger, peek, peek_ud, L);
|
||||
rc_test_trigger(&display->trigger, peek, peek_ud, unused_L);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -866,7 +870,7 @@ static int rc_evaluate_richpresence_display(rc_richpresence_display_part_t* part
|
|||
return (int)(ptr - buffer);
|
||||
}
|
||||
|
||||
int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, void* unused_L) {
|
||||
rc_richpresence_display_t* display;
|
||||
|
||||
for (display = richpresence->first_display; display; display = display->next) {
|
||||
|
@ -876,7 +880,7 @@ int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* bu
|
|||
|
||||
/* triggers with required hits will be updated in rc_update_richpresence */
|
||||
if (!display->has_required_hits)
|
||||
rc_test_trigger(&display->trigger, peek, peek_ud, L);
|
||||
rc_test_trigger(&display->trigger, peek, peek_ud, unused_L);
|
||||
|
||||
/* if we've found a valid condition, process it */
|
||||
if (display->trigger.state == RC_TRIGGER_STATE_TRIGGERED)
|
||||
|
@ -887,9 +891,9 @@ int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* bu
|
|||
return 0;
|
||||
}
|
||||
|
||||
int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
rc_update_richpresence(richpresence, peek, peek_ud, L);
|
||||
return rc_get_richpresence_display_string(richpresence, buffer, buffersize, peek, peek_ud, L);
|
||||
int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, void* unused_L) {
|
||||
rc_update_richpresence(richpresence, peek, peek_ud, unused_L);
|
||||
return rc_get_richpresence_display_string(richpresence, buffer, buffersize, peek, peek_ud, unused_L);
|
||||
}
|
||||
|
||||
void rc_reset_richpresence_triggers(rc_richpresence_t* self) {
|
||||
|
|
|
@ -117,7 +117,7 @@ void rc_runtime_deactivate_achievement(rc_runtime_t* self, uint32_t id) {
|
|||
}
|
||||
}
|
||||
|
||||
int rc_runtime_activate_achievement(rc_runtime_t* self, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx) {
|
||||
int rc_runtime_activate_achievement(rc_runtime_t* self, uint32_t id, const char* memaddr, void* unused_L, int unused_funcs_idx) {
|
||||
void* trigger_buffer;
|
||||
rc_trigger_t* trigger;
|
||||
rc_runtime_trigger_t* runtime_trigger;
|
||||
|
@ -127,6 +127,9 @@ int rc_runtime_activate_achievement(rc_runtime_t* self, uint32_t id, const char*
|
|||
int32_t size;
|
||||
uint32_t i;
|
||||
|
||||
(void)unused_L;
|
||||
(void)unused_funcs_idx;
|
||||
|
||||
if (memaddr == NULL)
|
||||
return RC_INVALID_MEMORY_OPERAND;
|
||||
|
||||
|
@ -165,7 +168,7 @@ int rc_runtime_activate_achievement(rc_runtime_t* self, uint32_t id, const char*
|
|||
}
|
||||
|
||||
/* item has not been previously registered, determine how much space we need for it, and allocate it */
|
||||
rc_init_preparse_state(&preparse, NULL, 0);
|
||||
rc_init_preparse_state(&preparse);
|
||||
preparse.parse.existing_memrefs = self->memrefs;
|
||||
trigger = RC_ALLOC(rc_trigger_t, &preparse.parse);
|
||||
rc_parse_trigger_internal(trigger, &preparse_memaddr, &preparse.parse);
|
||||
|
@ -179,7 +182,7 @@ int rc_runtime_activate_achievement(rc_runtime_t* self, uint32_t id, const char*
|
|||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
/* populate the item, using the communal memrefs pool */
|
||||
rc_reset_parse_state(&preparse.parse, trigger_buffer, L, funcs_idx);
|
||||
rc_reset_parse_state(&preparse.parse, trigger_buffer);
|
||||
rc_preparse_reserve_memrefs(&preparse, self->memrefs);
|
||||
trigger = RC_ALLOC(rc_trigger_t, &preparse.parse);
|
||||
rc_parse_trigger_internal(trigger, &memaddr, &preparse.parse);
|
||||
|
@ -298,7 +301,7 @@ void rc_runtime_deactivate_lboard(rc_runtime_t* self, uint32_t id) {
|
|||
}
|
||||
}
|
||||
|
||||
int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx) {
|
||||
int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* memaddr, void* unused_L, int unused_funcs_idx) {
|
||||
void* lboard_buffer;
|
||||
uint8_t md5[16];
|
||||
rc_lboard_t* lboard;
|
||||
|
@ -307,6 +310,9 @@ int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* mema
|
|||
int size;
|
||||
uint32_t i;
|
||||
|
||||
(void)unused_L;
|
||||
(void)unused_funcs_idx;
|
||||
|
||||
if (memaddr == 0)
|
||||
return RC_INVALID_MEMORY_OPERAND;
|
||||
|
||||
|
@ -345,7 +351,7 @@ int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* mema
|
|||
}
|
||||
|
||||
/* item has not been previously registered, determine how much space we need for it, and allocate it */
|
||||
rc_init_preparse_state(&preparse, NULL, 0);
|
||||
rc_init_preparse_state(&preparse);
|
||||
preparse.parse.existing_memrefs = self->memrefs;
|
||||
lboard = RC_ALLOC(rc_lboard_t, &preparse.parse);
|
||||
rc_parse_lboard_internal(lboard, memaddr, &preparse.parse);
|
||||
|
@ -359,7 +365,7 @@ int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* mema
|
|||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
/* populate the item, using the communal memrefs pool */
|
||||
rc_reset_parse_state(&preparse.parse, lboard_buffer, L, funcs_idx);
|
||||
rc_reset_parse_state(&preparse.parse, lboard_buffer);
|
||||
rc_preparse_reserve_memrefs(&preparse, self->memrefs);
|
||||
lboard = RC_ALLOC(rc_lboard_t, &preparse.parse);
|
||||
rc_parse_lboard_internal(lboard, memaddr, &preparse.parse);
|
||||
|
@ -416,12 +422,15 @@ int rc_runtime_format_lboard_value(char* buffer, int size, int32_t value, int fo
|
|||
return rc_format_value(buffer, size, value, format);
|
||||
}
|
||||
|
||||
int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua_State* L, int funcs_idx) {
|
||||
int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, void* unused_L, int unused_funcs_idx) {
|
||||
rc_richpresence_t* richpresence;
|
||||
rc_preparse_state_t preparse;
|
||||
uint8_t md5[16];
|
||||
int size;
|
||||
|
||||
(void)unused_L;
|
||||
(void)unused_funcs_idx;
|
||||
|
||||
if (script == NULL)
|
||||
return RC_MISSING_DISPLAY_STRING;
|
||||
|
||||
|
@ -437,7 +446,7 @@ int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua
|
|||
}
|
||||
|
||||
/* no existing match found, parse script */
|
||||
rc_init_preparse_state(&preparse, NULL, 0);
|
||||
rc_init_preparse_state(&preparse);
|
||||
preparse.parse.existing_memrefs = self->memrefs;
|
||||
richpresence = RC_ALLOC(rc_richpresence_t, &preparse.parse);
|
||||
preparse.parse.variables = &richpresence->values;
|
||||
|
@ -464,7 +473,7 @@ int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua
|
|||
if (!self->richpresence->buffer)
|
||||
return RC_OUT_OF_MEMORY;
|
||||
|
||||
rc_reset_parse_state(&preparse.parse, self->richpresence->buffer, L, funcs_idx);
|
||||
rc_reset_parse_state(&preparse.parse, self->richpresence->buffer);
|
||||
rc_preparse_reserve_memrefs(&preparse, self->memrefs);
|
||||
richpresence = RC_ALLOC(rc_richpresence_t, &preparse.parse);
|
||||
preparse.parse.variables = &richpresence->values;
|
||||
|
@ -491,9 +500,9 @@ int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua
|
|||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, size_t buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, size_t buffersize, rc_runtime_peek_t peek, void* peek_ud, void* unused_L) {
|
||||
if (self->richpresence && self->richpresence->richpresence)
|
||||
return rc_get_richpresence_display_string(self->richpresence->richpresence, buffer, buffersize, peek, peek_ud, L);
|
||||
return rc_get_richpresence_display_string(self->richpresence->richpresence, buffer, buffersize, peek, peek_ud, unused_L);
|
||||
|
||||
*buffer = '\0';
|
||||
return 0;
|
||||
|
@ -510,7 +519,7 @@ int rc_runtime_get_richpresence_strings(const rc_runtime_t* self, const char** b
|
|||
return err;
|
||||
}
|
||||
|
||||
void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L) {
|
||||
void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, void* unused_L) {
|
||||
rc_runtime_event_t runtime_event;
|
||||
int i;
|
||||
|
||||
|
@ -542,7 +551,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
|
|||
|
||||
old_measured_value = trigger->measured_value;
|
||||
old_state = trigger->state;
|
||||
new_state = rc_evaluate_trigger(trigger, peek, ud, L);
|
||||
new_state = rc_evaluate_trigger(trigger, peek, ud, unused_L);
|
||||
|
||||
/* trigger->state doesn't actually change to RESET, RESET just serves as a notification.
|
||||
* handle the notification, then look at the actual state */
|
||||
|
@ -644,7 +653,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
|
|||
}
|
||||
|
||||
lboard_state = lboard->state;
|
||||
switch (rc_evaluate_lboard(lboard, &runtime_event.value, peek, ud, L))
|
||||
switch (rc_evaluate_lboard(lboard, &runtime_event.value, peek, ud, unused_L))
|
||||
{
|
||||
case RC_LBOARD_STATE_STARTED: /* leaderboard is running */
|
||||
if (lboard_state != RC_LBOARD_STATE_STARTED) {
|
||||
|
@ -682,7 +691,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
|
|||
}
|
||||
|
||||
if (self->richpresence && self->richpresence->richpresence)
|
||||
rc_update_richpresence(self->richpresence->richpresence, peek, ud, L);
|
||||
rc_update_richpresence(self->richpresence->richpresence, peek, ud, unused_L);
|
||||
}
|
||||
|
||||
void rc_runtime_reset(rc_runtime_t* self) {
|
||||
|
|
|
@ -28,8 +28,6 @@ typedef struct rc_runtime_progress_t {
|
|||
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))
|
||||
|
@ -40,7 +38,7 @@ typedef struct rc_runtime_progress_t {
|
|||
|
||||
#define RC_VAR_FLAG_HAS_COND_DATA 0x01000000
|
||||
|
||||
#define RC_COND_FLAG_IS_TRUE 0x00000001
|
||||
#define RC_COND_FLAG_IS_TRUE_MASK 0x00000003
|
||||
#define RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF 0x00010000
|
||||
#define RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME 0x00020000
|
||||
#define RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF 0x00100000
|
||||
|
@ -116,11 +114,10 @@ static void rc_runtime_progress_end_chunk(rc_runtime_progress_t* progress)
|
|||
}
|
||||
}
|
||||
|
||||
static void rc_runtime_progress_init(rc_runtime_progress_t* progress, const rc_runtime_t* runtime, lua_State* L)
|
||||
static void rc_runtime_progress_init(rc_runtime_progress_t* progress, const rc_runtime_t* runtime)
|
||||
{
|
||||
memset(progress, 0, sizeof(rc_runtime_progress_t));
|
||||
progress->runtime = runtime;
|
||||
progress->L = L;
|
||||
}
|
||||
|
||||
#define RC_RUNTIME_SERIALIZED_MEMREF_SIZE 16 /* 4x uint: address, flags, value, prior */
|
||||
|
@ -302,7 +299,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:
|
||||
case RC_OPERAND_FUNC:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
|
@ -325,9 +322,7 @@ static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc
|
|||
|
||||
cond = condset->conditions;
|
||||
while (cond) {
|
||||
flags = 0;
|
||||
if (cond->is_true)
|
||||
flags |= RC_COND_FLAG_IS_TRUE;
|
||||
flags = (cond->is_true & RC_COND_FLAG_IS_TRUE_MASK);
|
||||
|
||||
if (rc_runtime_progress_is_indirect_memref(&cond->operand1)) {
|
||||
flags |= RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF;
|
||||
|
@ -381,7 +376,7 @@ static int rc_runtime_progress_read_condset(rc_runtime_progress_t* progress, rc_
|
|||
cond->current_hits = rc_runtime_progress_read_uint(progress);
|
||||
flags = rc_runtime_progress_read_uint(progress);
|
||||
|
||||
cond->is_true = (flags & RC_COND_FLAG_IS_TRUE) ? 1 : 0;
|
||||
cond->is_true = (flags & RC_COND_FLAG_IS_TRUE_MASK);
|
||||
|
||||
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
|
||||
if (!rc_operand_is_memref(&cond->operand1)) /* this should never happen, but better safe than sorry */
|
||||
|
@ -897,12 +892,14 @@ static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progres
|
|||
return RC_OK;
|
||||
}
|
||||
|
||||
uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
|
||||
uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, void* unused_L)
|
||||
{
|
||||
rc_runtime_progress_t progress;
|
||||
int result;
|
||||
|
||||
rc_runtime_progress_init(&progress, runtime, L);
|
||||
(void)unused_L;
|
||||
|
||||
rc_runtime_progress_init(&progress, runtime);
|
||||
progress.buffer_size = 0xFFFFFFFF;
|
||||
|
||||
result = rc_runtime_progress_serialize_internal(&progress);
|
||||
|
@ -912,31 +909,33 @@ uint32_t rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
|
|||
return progress.offset;
|
||||
}
|
||||
|
||||
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L)
|
||||
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, void* unused_L)
|
||||
{
|
||||
return rc_runtime_serialize_progress_sized(buffer, 0xFFFFFFFF, runtime, L);
|
||||
return rc_runtime_serialize_progress_sized(buffer, 0xFFFFFFFF, runtime, unused_L);
|
||||
}
|
||||
|
||||
int rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, lua_State* L)
|
||||
int rc_runtime_serialize_progress_sized(uint8_t* buffer, uint32_t buffer_size, const rc_runtime_t* runtime, void* unused_L)
|
||||
{
|
||||
rc_runtime_progress_t progress;
|
||||
|
||||
(void)unused_L;
|
||||
|
||||
if (!buffer)
|
||||
return RC_INVALID_STATE;
|
||||
|
||||
rc_runtime_progress_init(&progress, runtime, L);
|
||||
rc_runtime_progress_init(&progress, runtime);
|
||||
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)
|
||||
int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, void* unused_L)
|
||||
{
|
||||
return rc_runtime_deserialize_progress_sized(runtime, serialized, 0xFFFFFFFF, L);
|
||||
return rc_runtime_deserialize_progress_sized(runtime, serialized, 0xFFFFFFFF, unused_L);
|
||||
}
|
||||
|
||||
int rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, lua_State* L)
|
||||
int rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t* serialized, uint32_t serialized_size, void* unused_L)
|
||||
{
|
||||
rc_runtime_progress_t progress;
|
||||
md5_state_t state;
|
||||
|
@ -948,12 +947,14 @@ int rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t*
|
|||
int seen_rich_presence = 0;
|
||||
int result = RC_OK;
|
||||
|
||||
(void)unused_L;
|
||||
|
||||
if (!serialized || serialized_size < RC_RUNTIME_MIN_BUFFER_SIZE) {
|
||||
rc_runtime_reset(runtime);
|
||||
return RC_INSUFFICIENT_BUFFER;
|
||||
}
|
||||
|
||||
rc_runtime_progress_init(&progress, runtime, L);
|
||||
rc_runtime_progress_init(&progress, runtime);
|
||||
progress.buffer = (uint8_t*)serialized;
|
||||
|
||||
if (rc_runtime_progress_read_uint(&progress) != RC_RUNTIME_MARKER) {
|
||||
|
|
|
@ -52,7 +52,7 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars
|
|||
int rc_trigger_size(const char* memaddr) {
|
||||
rc_trigger_with_memrefs_t* trigger;
|
||||
rc_preparse_state_t preparse;
|
||||
rc_init_preparse_state(&preparse, NULL, 0);
|
||||
rc_init_preparse_state(&preparse);
|
||||
|
||||
trigger = RC_ALLOC(rc_trigger_with_memrefs_t, &preparse.parse);
|
||||
rc_parse_trigger_internal(&trigger->trigger, &memaddr, &preparse.parse);
|
||||
|
@ -62,21 +62,24 @@ int rc_trigger_size(const char* memaddr) {
|
|||
return preparse.parse.offset;
|
||||
}
|
||||
|
||||
rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
|
||||
rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, void* unused_L, int unused_funcs_idx) {
|
||||
rc_trigger_with_memrefs_t* trigger;
|
||||
rc_preparse_state_t preparse;
|
||||
const char* preparse_memaddr = memaddr;
|
||||
|
||||
(void)unused_L;
|
||||
(void)unused_funcs_idx;
|
||||
|
||||
if (!buffer || !memaddr)
|
||||
return NULL;
|
||||
|
||||
/* first pass : determine how many memrefs are needed */
|
||||
rc_init_preparse_state(&preparse, L, funcs_ndx);
|
||||
rc_init_preparse_state(&preparse);
|
||||
trigger = RC_ALLOC(rc_trigger_with_memrefs_t, &preparse.parse);
|
||||
rc_parse_trigger_internal(&trigger->trigger, &preparse_memaddr, &preparse.parse);
|
||||
|
||||
/* allocate the trigger and memrefs */
|
||||
rc_reset_parse_state(&preparse.parse, buffer, L, funcs_ndx);
|
||||
rc_reset_parse_state(&preparse.parse, buffer);
|
||||
trigger = RC_ALLOC(rc_trigger_with_memrefs_t, &preparse.parse);
|
||||
rc_preparse_alloc_memrefs(&trigger->memrefs, &preparse);
|
||||
|
||||
|
@ -146,7 +149,7 @@ rc_memrefs_t* rc_trigger_get_memrefs(rc_trigger_t* self) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, void* unused_L) {
|
||||
rc_eval_state_t eval_state;
|
||||
rc_condset_t* condset;
|
||||
rc_typed_value_t measured_value;
|
||||
|
@ -155,6 +158,8 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
|
|||
char is_paused;
|
||||
char is_primed;
|
||||
|
||||
(void)unused_L;
|
||||
|
||||
switch (self->state)
|
||||
{
|
||||
case RC_TRIGGER_STATE_TRIGGERED:
|
||||
|
@ -181,11 +186,6 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
|
|||
memset(&eval_state, 0, sizeof(eval_state));
|
||||
eval_state.peek = peek;
|
||||
eval_state.peek_userdata = ud;
|
||||
#ifndef RC_DISABLE_LUA
|
||||
eval_state.L = L;
|
||||
#else
|
||||
(void)L;
|
||||
#endif
|
||||
|
||||
measured_value.type = RC_VALUE_TYPE_NONE;
|
||||
|
||||
|
@ -316,11 +316,11 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
|
|||
return self->state;
|
||||
}
|
||||
|
||||
int rc_test_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
int rc_test_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, void* unused_L) {
|
||||
/* for backwards compatibilty, rc_test_trigger always assumes the achievement is active */
|
||||
self->state = RC_TRIGGER_STATE_ACTIVE;
|
||||
|
||||
return (rc_evaluate_trigger(self, peek, ud, L) == RC_TRIGGER_STATE_TRIGGERED);
|
||||
return (rc_evaluate_trigger(self, peek, ud, unused_L) == RC_TRIGGER_STATE_TRIGGERED);
|
||||
}
|
||||
|
||||
void rc_reset_trigger(rc_trigger_t* self) {
|
||||
|
|
|
@ -244,7 +244,7 @@ void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_st
|
|||
int rc_value_size(const char* memaddr) {
|
||||
rc_value_with_memrefs_t* value;
|
||||
rc_preparse_state_t preparse;
|
||||
rc_init_preparse_state(&preparse, NULL, 0);
|
||||
rc_init_preparse_state(&preparse);
|
||||
|
||||
value = RC_ALLOC(rc_value_with_memrefs_t, &preparse.parse);
|
||||
rc_parse_value_internal(&value->value, &memaddr, &preparse.parse);
|
||||
|
@ -254,19 +254,22 @@ int rc_value_size(const char* memaddr) {
|
|||
return preparse.parse.offset;
|
||||
}
|
||||
|
||||
rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
|
||||
rc_value_t* rc_parse_value(void* buffer, const char* memaddr, void* unused_L, int unused_funcs_idx) {
|
||||
rc_value_with_memrefs_t* value;
|
||||
rc_preparse_state_t preparse;
|
||||
const char* preparse_memaddr = memaddr;
|
||||
|
||||
(void)unused_L;
|
||||
(void)unused_funcs_idx;
|
||||
|
||||
if (!buffer || !memaddr)
|
||||
return NULL;
|
||||
|
||||
rc_init_preparse_state(&preparse, L, funcs_ndx);
|
||||
rc_init_preparse_state(&preparse);
|
||||
value = RC_ALLOC(rc_value_with_memrefs_t, &preparse.parse);
|
||||
rc_parse_value_internal(&value->value, &preparse_memaddr, &preparse.parse);
|
||||
|
||||
rc_reset_parse_state(&preparse.parse, buffer, L, funcs_ndx);
|
||||
rc_reset_parse_state(&preparse.parse, buffer);
|
||||
value = RC_ALLOC(rc_value_with_memrefs_t, &preparse.parse);
|
||||
rc_preparse_alloc_memrefs(&value->memrefs, &preparse);
|
||||
|
||||
|
@ -284,7 +287,7 @@ static void rc_update_value_memrefs(rc_value_t* self, rc_peek_t peek, void* ud)
|
|||
}
|
||||
}
|
||||
|
||||
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud) {
|
||||
rc_eval_state_t eval_state;
|
||||
rc_condset_t* condset;
|
||||
int valid = 0;
|
||||
|
@ -298,11 +301,6 @@ int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t
|
|||
memset(&eval_state, 0, sizeof(eval_state));
|
||||
eval_state.peek = peek;
|
||||
eval_state.peek_userdata = ud;
|
||||
#ifndef RC_DISABLE_LUA
|
||||
eval_state.L = L;
|
||||
#else
|
||||
(void)L;
|
||||
#endif
|
||||
|
||||
rc_test_condset(condset, &eval_state);
|
||||
|
||||
|
@ -335,9 +333,11 @@ int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t
|
|||
return valid;
|
||||
}
|
||||
|
||||
int32_t rc_evaluate_value(rc_value_t* self, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
int32_t rc_evaluate_value(rc_value_t* self, rc_peek_t peek, void* ud, void* unused_L) {
|
||||
rc_typed_value_t result;
|
||||
int valid = rc_evaluate_value_typed(self, &result, peek, ud, L);
|
||||
int valid = rc_evaluate_value_typed(self, &result, peek, ud);
|
||||
|
||||
(void)unused_L;
|
||||
|
||||
if (valid) {
|
||||
/* if not paused, store the value so that it's available when paused. */
|
||||
|
@ -430,12 +430,12 @@ uint32_t rc_count_values(const rc_value_t* values) {
|
|||
return count;
|
||||
}
|
||||
|
||||
void rc_update_values(rc_value_t* values, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
void rc_update_values(rc_value_t* values, rc_peek_t peek, void* ud) {
|
||||
rc_typed_value_t result;
|
||||
|
||||
rc_value_t* value = values;
|
||||
for (; value; value = value->next) {
|
||||
if (rc_evaluate_value_typed(value, &result, peek, ud, L)) {
|
||||
if (rc_evaluate_value_typed(value, &result, peek, ud)) {
|
||||
/* store the raw bytes and type to be restored by rc_typed_value_from_memref_value */
|
||||
rc_update_memref_value(&value->value, result.value.u32);
|
||||
value->value.type = result.type;
|
||||
|
|
|
@ -1,879 +0,0 @@
|
|||
#include "rc_hash.h"
|
||||
|
||||
#include "../rc_compat.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* internal helper functions in hash.c */
|
||||
extern void* rc_file_open(const char* path);
|
||||
extern void rc_file_seek(void* file_handle, int64_t offset, int origin);
|
||||
extern int64_t rc_file_tell(void* file_handle);
|
||||
extern size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes);
|
||||
extern void rc_file_close(void* file_handle);
|
||||
extern int rc_hash_error(const char* message);
|
||||
extern const char* rc_path_get_filename(const char* path);
|
||||
extern int rc_path_compare_extension(const char* path, const char* ext);
|
||||
extern rc_hash_message_callback verbose_message_callback;
|
||||
|
||||
struct cdrom_t
|
||||
{
|
||||
void* file_handle; /* the file handle for reading the track data */
|
||||
int sector_size; /* the size of each sector in the track data */
|
||||
int sector_header_size; /* the offset to the raw data within a sector block */
|
||||
int raw_data_size; /* the amount of raw data within a sector block */
|
||||
int64_t file_track_offset;/* the offset of the track data within the file */
|
||||
int track_first_sector; /* the first absolute sector associated to the track (includes pregap) */
|
||||
int track_pregap_sectors; /* the number of pregap sectors */
|
||||
#ifndef NDEBUG
|
||||
uint32_t track_id; /* the index of the track */
|
||||
#endif
|
||||
};
|
||||
|
||||
static int cdreader_get_sector(uint8_t header[16])
|
||||
{
|
||||
int minutes = (header[12] >> 4) * 10 + (header[12] & 0x0F);
|
||||
int seconds = (header[13] >> 4) * 10 + (header[13] & 0x0F);
|
||||
int frames = (header[14] >> 4) * 10 + (header[14] & 0x0F);
|
||||
|
||||
/* convert the MSF value to a sector index, and subtract 150 (2 seconds) per:
|
||||
* For data and mixed mode media (those conforming to ISO/IEC 10149), logical block address
|
||||
* zero shall be assigned to the block at MSF address 00/02/00 */
|
||||
return ((minutes * 60) + seconds) * 75 + frames - 150;
|
||||
}
|
||||
|
||||
static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
|
||||
{
|
||||
/* Attempt to determine the sector and header sizes. The CUE file may be lying.
|
||||
* Look for the sync pattern using each of the supported sector sizes.
|
||||
* Then check for the presence of "CD001", which is gauranteed to be in either the
|
||||
* boot record or primary volume descriptor, one of which is always at sector 16.
|
||||
*/
|
||||
const uint8_t sync_pattern[] = {
|
||||
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
|
||||
};
|
||||
|
||||
uint8_t header[32];
|
||||
const int64_t toc_sector = 16 + cdrom->track_pregap_sectors;
|
||||
|
||||
cdrom->sector_size = 0;
|
||||
cdrom->sector_header_size = 0;
|
||||
cdrom->raw_data_size = 2048;
|
||||
|
||||
rc_file_seek(cdrom->file_handle, toc_sector * 2352 + cdrom->file_track_offset, SEEK_SET);
|
||||
if (rc_file_read(cdrom->file_handle, header, sizeof(header)) < sizeof(header))
|
||||
return;
|
||||
|
||||
if (memcmp(header, sync_pattern, 12) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2352;
|
||||
|
||||
if (memcmp(&header[25], "CD001", 5) == 0)
|
||||
cdrom->sector_header_size = 24;
|
||||
else
|
||||
cdrom->sector_header_size = 16;
|
||||
|
||||
cdrom->track_first_sector = cdreader_get_sector(header) - (int)toc_sector;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc_file_seek(cdrom->file_handle, toc_sector * 2336 + cdrom->file_track_offset, SEEK_SET);
|
||||
rc_file_read(cdrom->file_handle, header, sizeof(header));
|
||||
|
||||
if (memcmp(header, sync_pattern, 12) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2336;
|
||||
|
||||
if (memcmp(&header[25], "CD001", 5) == 0)
|
||||
cdrom->sector_header_size = 24;
|
||||
else
|
||||
cdrom->sector_header_size = 16;
|
||||
|
||||
cdrom->track_first_sector = cdreader_get_sector(header) - (int)toc_sector;
|
||||
}
|
||||
else
|
||||
{
|
||||
rc_file_seek(cdrom->file_handle, toc_sector * 2048 + cdrom->file_track_offset, SEEK_SET);
|
||||
rc_file_read(cdrom->file_handle, header, sizeof(header));
|
||||
|
||||
if (memcmp(&header[1], "CD001", 5) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2048;
|
||||
cdrom->sector_header_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void* cdreader_open_bin_track(const char* path, uint32_t track)
|
||||
{
|
||||
void* file_handle;
|
||||
struct cdrom_t* cdrom;
|
||||
|
||||
if (track > 1)
|
||||
{
|
||||
if (verbose_message_callback)
|
||||
verbose_message_callback("Cannot locate secondary tracks without a cue sheet");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
file_handle = rc_file_open(path);
|
||||
if (!file_handle)
|
||||
return NULL;
|
||||
|
||||
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
|
||||
if (!cdrom)
|
||||
return NULL;
|
||||
cdrom->file_handle = file_handle;
|
||||
#ifndef NDEBUG
|
||||
cdrom->track_id = track;
|
||||
#endif
|
||||
|
||||
cdreader_determine_sector_size(cdrom);
|
||||
|
||||
if (cdrom->sector_size == 0)
|
||||
{
|
||||
int64_t size;
|
||||
|
||||
rc_file_seek(cdrom->file_handle, 0, SEEK_END);
|
||||
size = rc_file_tell(cdrom->file_handle);
|
||||
|
||||
if ((size % 2352) == 0)
|
||||
{
|
||||
/* raw tracks use all 2352 bytes and have a 24 byte header */
|
||||
cdrom->sector_size = 2352;
|
||||
cdrom->sector_header_size = 24;
|
||||
}
|
||||
else if ((size % 2048) == 0)
|
||||
{
|
||||
/* cooked tracks eliminate all header/footer data */
|
||||
cdrom->sector_size = 2048;
|
||||
cdrom->sector_header_size = 0;
|
||||
}
|
||||
else if ((size % 2336) == 0)
|
||||
{
|
||||
/* MODE 2 format without 16-byte sync data */
|
||||
cdrom->sector_size = 2336;
|
||||
cdrom->sector_header_size = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
free(cdrom);
|
||||
|
||||
if (verbose_message_callback)
|
||||
verbose_message_callback("Could not determine sector size");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return cdrom;
|
||||
}
|
||||
|
||||
static int cdreader_open_bin(struct cdrom_t* cdrom, const char* path, const char* mode)
|
||||
{
|
||||
cdrom->file_handle = rc_file_open(path);
|
||||
if (!cdrom->file_handle)
|
||||
return 0;
|
||||
|
||||
/* determine sector size */
|
||||
cdreader_determine_sector_size(cdrom);
|
||||
|
||||
/* could not determine, which means we'll probably have more issues later
|
||||
* but use the CUE provided information anyway
|
||||
*/
|
||||
if (cdrom->sector_size == 0)
|
||||
{
|
||||
/* All of these modes have 2048 byte payloads. In MODE1/2352 and MODE2/2352
|
||||
* modes, the mode can actually be specified per sector to change the payload
|
||||
* size, but that reduces the ability to recover from errors when the disc
|
||||
* is damaged, so it's seldomly used, and when it is, it's mostly for audio
|
||||
* or video data where a blip or two probably won't be noticed by the user.
|
||||
* So, while we techincally support all of the following modes, we only do
|
||||
* so with 2048 byte payloads.
|
||||
* http://totalsonicmastering.com/cuesheetsyntax.htm
|
||||
* MODE1/2048 ? CDROM Mode1 Data (cooked) [no header, no footer]
|
||||
* MODE1/2352 ? CDROM Mode1 Data (raw) [16 byte header, 288 byte footer]
|
||||
* MODE2/2336 ? CDROM-XA Mode2 Data [8 byte header, 280 byte footer]
|
||||
* MODE2/2352 ? CDROM-XA Mode2 Data [24 byte header, 280 byte footer]
|
||||
*/
|
||||
if (memcmp(mode, "MODE2/2352", 10) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2352;
|
||||
cdrom->sector_header_size = 24;
|
||||
}
|
||||
else if (memcmp(mode, "MODE1/2048", 10) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2048;
|
||||
cdrom->sector_header_size = 0;
|
||||
}
|
||||
else if (memcmp(mode, "MODE2/2336", 10) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2336;
|
||||
cdrom->sector_header_size = 8;
|
||||
}
|
||||
else if (memcmp(mode, "MODE1/2352", 10) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2352;
|
||||
cdrom->sector_header_size = 16;
|
||||
}
|
||||
else if (memcmp(mode, "AUDIO", 5) == 0)
|
||||
{
|
||||
cdrom->sector_size = 2352;
|
||||
cdrom->sector_header_size = 0;
|
||||
cdrom->raw_data_size = 2352; /* no header or footer data on audio tracks */
|
||||
}
|
||||
}
|
||||
|
||||
return (cdrom->sector_size != 0);
|
||||
}
|
||||
|
||||
static char* cdreader_get_bin_path(const char* cue_path, const char* bin_name)
|
||||
{
|
||||
const char* filename = rc_path_get_filename(cue_path);
|
||||
const size_t bin_name_len = strlen(bin_name);
|
||||
const size_t cue_path_len = filename - cue_path;
|
||||
const size_t needed = cue_path_len + bin_name_len + 1;
|
||||
|
||||
char* bin_filename = (char*)malloc(needed);
|
||||
if (!bin_filename)
|
||||
{
|
||||
char buffer[64];
|
||||
snprintf(buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)needed);
|
||||
rc_hash_error((const char*)buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(bin_filename, cue_path, cue_path_len);
|
||||
memcpy(bin_filename + cue_path_len, bin_name, bin_name_len + 1);
|
||||
}
|
||||
|
||||
return bin_filename;
|
||||
}
|
||||
|
||||
static int64_t cdreader_get_bin_size(const char* cue_path, const char* bin_name)
|
||||
{
|
||||
int64_t size = 0;
|
||||
char* bin_filename = cdreader_get_bin_path(cue_path, bin_name);
|
||||
if (bin_filename)
|
||||
{
|
||||
/* disable verbose messaging while getting file size */
|
||||
rc_hash_message_callback old_verbose_message_callback = verbose_message_callback;
|
||||
void* file_handle;
|
||||
verbose_message_callback = NULL;
|
||||
|
||||
file_handle = rc_file_open(bin_filename);
|
||||
if (file_handle)
|
||||
{
|
||||
rc_file_seek(file_handle, 0, SEEK_END);
|
||||
size = rc_file_tell(file_handle);
|
||||
rc_file_close(file_handle);
|
||||
}
|
||||
|
||||
verbose_message_callback = old_verbose_message_callback;
|
||||
free(bin_filename);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
||||
{
|
||||
void* cue_handle;
|
||||
int64_t cue_offset = 0;
|
||||
char buffer[1024];
|
||||
char* bin_filename = NULL;
|
||||
char *ptr, *ptr2, *end;
|
||||
int done = 0;
|
||||
int session = 1;
|
||||
size_t num_read = 0;
|
||||
struct cdrom_t* cdrom = NULL;
|
||||
|
||||
struct track_t
|
||||
{
|
||||
uint32_t id;
|
||||
int sector_size;
|
||||
int sector_count;
|
||||
int first_sector;
|
||||
int pregap_sectors;
|
||||
int is_data;
|
||||
int file_track_offset;
|
||||
int file_first_sector;
|
||||
char mode[16];
|
||||
char filename[256];
|
||||
} current_track, previous_track, largest_track;
|
||||
|
||||
cue_handle = rc_file_open(path);
|
||||
if (!cue_handle)
|
||||
return NULL;
|
||||
|
||||
memset(¤t_track, 0, sizeof(current_track));
|
||||
memset(&previous_track, 0, sizeof(previous_track));
|
||||
memset(&largest_track, 0, sizeof(largest_track));
|
||||
|
||||
do
|
||||
{
|
||||
num_read = rc_file_read(cue_handle, buffer, sizeof(buffer) - 1);
|
||||
if (num_read == 0)
|
||||
break;
|
||||
|
||||
buffer[num_read] = 0;
|
||||
if (num_read == sizeof(buffer) - 1)
|
||||
end = buffer + sizeof(buffer) * 3 / 4;
|
||||
else
|
||||
end = buffer + num_read;
|
||||
|
||||
for (ptr = buffer; ptr < end; ++ptr)
|
||||
{
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
|
||||
if (strncasecmp(ptr, "INDEX ", 6) == 0)
|
||||
{
|
||||
int m = 0, s = 0, f = 0;
|
||||
int index;
|
||||
int sector_offset;
|
||||
|
||||
ptr += 6;
|
||||
index = atoi(ptr);
|
||||
|
||||
while (*ptr != ' ' && *ptr != '\n')
|
||||
++ptr;
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
|
||||
/* convert mm:ss:ff to sector count */
|
||||
sscanf_s(ptr, "%d:%d:%d", &m, &s, &f);
|
||||
sector_offset = ((m * 60) + s) * 75 + f;
|
||||
|
||||
if (current_track.first_sector == -1)
|
||||
{
|
||||
current_track.first_sector = sector_offset;
|
||||
if (strcmp(current_track.filename, previous_track.filename) == 0)
|
||||
{
|
||||
previous_track.sector_count = current_track.first_sector - previous_track.first_sector;
|
||||
current_track.file_track_offset += previous_track.sector_count * previous_track.sector_size;
|
||||
}
|
||||
|
||||
/* if looking for the largest data track, determine previous track size */
|
||||
if (track == RC_HASH_CDTRACK_LARGEST && previous_track.sector_count > largest_track.sector_count &&
|
||||
previous_track.is_data)
|
||||
{
|
||||
memcpy(&largest_track, &previous_track, sizeof(largest_track));
|
||||
}
|
||||
}
|
||||
|
||||
if (index == 1)
|
||||
{
|
||||
current_track.pregap_sectors = (sector_offset - current_track.first_sector);
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
char message[128];
|
||||
char* scan = current_track.mode;
|
||||
while (*scan && !isspace((unsigned char)*scan))
|
||||
++scan;
|
||||
*scan = '\0';
|
||||
|
||||
/* it's undesirable to truncate offset to 32-bits, but %lld isn't defined in c89. */
|
||||
snprintf(message, sizeof(message), "Found %s track %d (first sector %d, sector size %d, %d pregap sectors)",
|
||||
current_track.mode, current_track.id, current_track.first_sector, current_track.sector_size, current_track.pregap_sectors);
|
||||
verbose_message_callback(message);
|
||||
}
|
||||
|
||||
if (current_track.id == track)
|
||||
{
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (track == RC_HASH_CDTRACK_FIRST_DATA && current_track.is_data)
|
||||
{
|
||||
track = current_track.id;
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (track == RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION && session == 2)
|
||||
{
|
||||
track = current_track.id;
|
||||
done = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strncasecmp(ptr, "TRACK ", 6) == 0)
|
||||
{
|
||||
if (current_track.sector_size)
|
||||
memcpy(&previous_track, ¤t_track, sizeof(current_track));
|
||||
|
||||
ptr += 6;
|
||||
current_track.id = atoi(ptr);
|
||||
|
||||
current_track.pregap_sectors = -1;
|
||||
current_track.first_sector = -1;
|
||||
|
||||
while (*ptr != ' ')
|
||||
++ptr;
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
memcpy(current_track.mode, ptr, sizeof(current_track.mode));
|
||||
current_track.is_data = (memcmp(current_track.mode, "MODE", 4) == 0);
|
||||
|
||||
if (current_track.is_data)
|
||||
{
|
||||
current_track.sector_size = atoi(ptr + 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* assume AUDIO */
|
||||
current_track.sector_size = 2352;
|
||||
}
|
||||
}
|
||||
else if (strncasecmp(ptr, "FILE ", 5) == 0)
|
||||
{
|
||||
if (current_track.sector_size)
|
||||
{
|
||||
memcpy(&previous_track, ¤t_track, sizeof(previous_track));
|
||||
|
||||
if (previous_track.sector_count == 0)
|
||||
{
|
||||
const uint32_t file_sector_count = (uint32_t)cdreader_get_bin_size(path, previous_track.filename) / previous_track.sector_size;
|
||||
previous_track.sector_count = file_sector_count - previous_track.first_sector;
|
||||
}
|
||||
|
||||
/* if looking for the largest data track, check to see if this one is larger */
|
||||
if (track == RC_HASH_CDTRACK_LARGEST && previous_track.is_data &&
|
||||
previous_track.sector_count > largest_track.sector_count)
|
||||
{
|
||||
memcpy(&largest_track, &previous_track, sizeof(largest_track));
|
||||
}
|
||||
}
|
||||
|
||||
memset(¤t_track, 0, sizeof(current_track));
|
||||
|
||||
current_track.file_first_sector = previous_track.file_first_sector +
|
||||
previous_track.first_sector + previous_track.sector_count;
|
||||
|
||||
ptr += 5;
|
||||
ptr2 = ptr;
|
||||
if (*ptr == '"')
|
||||
{
|
||||
++ptr;
|
||||
do
|
||||
{
|
||||
++ptr2;
|
||||
} while (*ptr2 && *ptr2 != '\n' && *ptr2 != '"');
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
++ptr2;
|
||||
} while (*ptr2 && *ptr2 != '\n' && *ptr2 != ' ');
|
||||
}
|
||||
|
||||
if (ptr2 - ptr < (int)sizeof(current_track.filename))
|
||||
memcpy(current_track.filename, ptr, ptr2 - ptr);
|
||||
}
|
||||
else if (strncasecmp(ptr, "REM ", 4) == 0)
|
||||
{
|
||||
ptr += 4;
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
|
||||
if (strncasecmp(ptr, "SESSION ", 8) == 0)
|
||||
{
|
||||
ptr += 8;
|
||||
while (*ptr == ' ')
|
||||
++ptr;
|
||||
session = atoi(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
while (*ptr && *ptr != '\n')
|
||||
++ptr;
|
||||
}
|
||||
|
||||
if (done)
|
||||
break;
|
||||
|
||||
cue_offset += (ptr - buffer);
|
||||
rc_file_seek(cue_handle, cue_offset, SEEK_SET);
|
||||
|
||||
} while (1);
|
||||
|
||||
rc_file_close(cue_handle);
|
||||
|
||||
if (track == RC_HASH_CDTRACK_LARGEST)
|
||||
{
|
||||
if (current_track.sector_size && current_track.is_data)
|
||||
{
|
||||
const uint32_t file_sector_count = (uint32_t)cdreader_get_bin_size(path, current_track.filename) / current_track.sector_size;
|
||||
current_track.sector_count = file_sector_count - current_track.first_sector;
|
||||
|
||||
if (largest_track.sector_count > current_track.sector_count)
|
||||
memcpy(¤t_track, &largest_track, sizeof(current_track));
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(¤t_track, &largest_track, sizeof(current_track));
|
||||
}
|
||||
|
||||
track = current_track.id;
|
||||
}
|
||||
else if (track == RC_HASH_CDTRACK_LAST && !done)
|
||||
{
|
||||
track = current_track.id;
|
||||
}
|
||||
|
||||
if (current_track.id == track)
|
||||
{
|
||||
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
|
||||
if (!cdrom)
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
|
||||
rc_hash_error((const char*)buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cdrom->file_track_offset = current_track.file_track_offset;
|
||||
cdrom->track_pregap_sectors = current_track.pregap_sectors;
|
||||
cdrom->track_first_sector = current_track.file_first_sector + current_track.first_sector;
|
||||
#ifndef NDEBUG
|
||||
cdrom->track_id = current_track.id;
|
||||
#endif
|
||||
|
||||
/* verify existance of bin file */
|
||||
bin_filename = cdreader_get_bin_path(path, current_track.filename);
|
||||
if (bin_filename)
|
||||
{
|
||||
if (cdreader_open_bin(cdrom, bin_filename, current_track.mode))
|
||||
{
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
if (cdrom->track_pregap_sectors)
|
||||
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d, %d pregap sectors)",
|
||||
track, cdrom->sector_size, cdrom->track_pregap_sectors);
|
||||
else
|
||||
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d)", track, cdrom->sector_size);
|
||||
|
||||
verbose_message_callback((const char*)buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cdrom->file_handle)
|
||||
{
|
||||
rc_file_close(cdrom->file_handle);
|
||||
snprintf((char*)buffer, sizeof(buffer), "Could not determine sector size for %s track", current_track.mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Could not open %s", bin_filename);
|
||||
}
|
||||
|
||||
rc_hash_error((const char*)buffer);
|
||||
|
||||
free(cdrom);
|
||||
cdrom = NULL;
|
||||
}
|
||||
|
||||
free(bin_filename);
|
||||
}
|
||||
}
|
||||
|
||||
return cdrom;
|
||||
}
|
||||
|
||||
static void* cdreader_open_gdi_track(const char* path, uint32_t track)
|
||||
{
|
||||
void* file_handle;
|
||||
char buffer[1024];
|
||||
char mode[16] = "MODE1/";
|
||||
char sector_size[16];
|
||||
char file[256];
|
||||
int64_t track_size;
|
||||
int track_type;
|
||||
char* bin_path = NULL;
|
||||
uint32_t current_track = 0;
|
||||
char* ptr, *ptr2, *end;
|
||||
int lba = 0;
|
||||
|
||||
uint32_t largest_track = 0;
|
||||
int64_t largest_track_size = 0;
|
||||
char largest_track_file[256];
|
||||
char largest_track_sector_size[16];
|
||||
int largest_track_lba = 0;
|
||||
|
||||
int found = 0;
|
||||
size_t num_read = 0;
|
||||
int64_t file_offset = 0;
|
||||
struct cdrom_t* cdrom = NULL;
|
||||
|
||||
file_handle = rc_file_open(path);
|
||||
if (!file_handle)
|
||||
return NULL;
|
||||
|
||||
file[0] = '\0';
|
||||
do
|
||||
{
|
||||
num_read = rc_file_read(file_handle, buffer, sizeof(buffer) - 1);
|
||||
if (num_read == 0)
|
||||
break;
|
||||
|
||||
buffer[num_read] = 0;
|
||||
if (num_read == sizeof(buffer) - 1)
|
||||
end = buffer + sizeof(buffer) * 3 / 4;
|
||||
else
|
||||
end = buffer + num_read;
|
||||
|
||||
ptr = buffer;
|
||||
|
||||
/* the first line contains the number of tracks, so we can get the last track index from it */
|
||||
if (track == RC_HASH_CDTRACK_LAST)
|
||||
track = atoi(ptr);
|
||||
|
||||
/* first line contains the number of tracks and will be skipped */
|
||||
while (ptr < end)
|
||||
{
|
||||
/* skip until next newline */
|
||||
while (*ptr != '\n' && ptr < end)
|
||||
++ptr;
|
||||
|
||||
/* skip newlines */
|
||||
while ((*ptr == '\n' || *ptr == '\r') && ptr < end)
|
||||
++ptr;
|
||||
|
||||
/* line format: [trackid] [lba] [type] [sectorsize] [file] [?] */
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
current_track = (uint32_t)atoi(ptr);
|
||||
if (track && current_track != track && track != RC_HASH_CDTRACK_FIRST_DATA)
|
||||
continue;
|
||||
|
||||
while (isdigit((unsigned char)*ptr))
|
||||
++ptr;
|
||||
++ptr;
|
||||
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
lba = atoi(ptr);
|
||||
while (isdigit((unsigned char)*ptr))
|
||||
++ptr;
|
||||
++ptr;
|
||||
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
track_type = atoi(ptr);
|
||||
while (isdigit((unsigned char)*ptr))
|
||||
++ptr;
|
||||
++ptr;
|
||||
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
ptr2 = sector_size;
|
||||
while (isdigit((unsigned char)*ptr))
|
||||
*ptr2++ = *ptr++;
|
||||
*ptr2 = '\0';
|
||||
++ptr;
|
||||
|
||||
while (isspace((unsigned char)*ptr))
|
||||
++ptr;
|
||||
|
||||
ptr2 = file;
|
||||
if (*ptr == '\"')
|
||||
{
|
||||
++ptr;
|
||||
while (*ptr != '\"')
|
||||
*ptr2++ = *ptr++;
|
||||
++ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (*ptr != ' ')
|
||||
*ptr2++ = *ptr++;
|
||||
}
|
||||
*ptr2 = '\0';
|
||||
|
||||
if (track == current_track)
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
else if (track == RC_HASH_CDTRACK_FIRST_DATA && track_type == 4)
|
||||
{
|
||||
track = current_track;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
else if (track == RC_HASH_CDTRACK_LARGEST && track_type == 4)
|
||||
{
|
||||
track_size = cdreader_get_bin_size(path, file);
|
||||
if (track_size > largest_track_size)
|
||||
{
|
||||
largest_track_size = track_size;
|
||||
largest_track = current_track;
|
||||
largest_track_lba = lba;
|
||||
strcpy_s(largest_track_file, sizeof(largest_track_file), file);
|
||||
strcpy_s(largest_track_sector_size, sizeof(largest_track_sector_size), sector_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
|
||||
file_offset += (ptr - buffer);
|
||||
rc_file_seek(file_handle, file_offset, SEEK_SET);
|
||||
|
||||
} while (1);
|
||||
|
||||
rc_file_close(file_handle);
|
||||
|
||||
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
|
||||
if (!cdrom)
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
|
||||
rc_hash_error((const char*)buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* if we were tracking the largest track, make it the current track.
|
||||
* otherwise, current_track will be the requested track, or last track. */
|
||||
if (largest_track != 0 && largest_track != current_track)
|
||||
{
|
||||
current_track = largest_track;
|
||||
strcpy_s(file, sizeof(file), largest_track_file);
|
||||
strcpy_s(sector_size, sizeof(sector_size), largest_track_sector_size);
|
||||
lba = largest_track_lba;
|
||||
}
|
||||
|
||||
/* open the bin file for the track - construct mode parameter from sector_size */
|
||||
ptr = &mode[6];
|
||||
ptr2 = sector_size;
|
||||
while (*ptr2 && *ptr2 != '\"')
|
||||
*ptr++ = *ptr2++;
|
||||
*ptr = '\0';
|
||||
|
||||
bin_path = cdreader_get_bin_path(path, file);
|
||||
if (cdreader_open_bin(cdrom, bin_path, mode))
|
||||
{
|
||||
cdrom->track_pregap_sectors = 0;
|
||||
cdrom->track_first_sector = lba;
|
||||
#ifndef NDEBUG
|
||||
cdrom->track_id = current_track;
|
||||
#endif
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d)", current_track, cdrom->sector_size);
|
||||
verbose_message_callback((const char*)buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Could not open %s", bin_path);
|
||||
rc_hash_error((const char*)buffer);
|
||||
|
||||
free(cdrom);
|
||||
cdrom = NULL;
|
||||
}
|
||||
|
||||
free(bin_path);
|
||||
|
||||
return cdrom;
|
||||
}
|
||||
|
||||
static void* cdreader_open_track(const char* path, uint32_t track)
|
||||
{
|
||||
/* backwards compatibility - 0 used to mean largest */
|
||||
if (track == 0)
|
||||
track = RC_HASH_CDTRACK_LARGEST;
|
||||
|
||||
if (rc_path_compare_extension(path, "cue"))
|
||||
return cdreader_open_cue_track(path, track);
|
||||
if (rc_path_compare_extension(path, "gdi"))
|
||||
return cdreader_open_gdi_track(path, track);
|
||||
|
||||
return cdreader_open_bin_track(path, track);
|
||||
}
|
||||
|
||||
static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes)
|
||||
{
|
||||
int64_t sector_start;
|
||||
size_t num_read, total_read = 0;
|
||||
uint8_t* buffer_ptr = (uint8_t*)buffer;
|
||||
|
||||
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
|
||||
if (!cdrom)
|
||||
return 0;
|
||||
|
||||
if (sector < (uint32_t)cdrom->track_first_sector)
|
||||
return 0;
|
||||
|
||||
sector_start = (int64_t)(sector - cdrom->track_first_sector) * cdrom->sector_size +
|
||||
cdrom->sector_header_size + cdrom->file_track_offset;
|
||||
|
||||
while (requested_bytes > (size_t)cdrom->raw_data_size)
|
||||
{
|
||||
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
|
||||
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, cdrom->raw_data_size);
|
||||
total_read += num_read;
|
||||
|
||||
if (num_read < (size_t)cdrom->raw_data_size)
|
||||
return total_read;
|
||||
|
||||
buffer_ptr += cdrom->raw_data_size;
|
||||
sector_start += cdrom->sector_size;
|
||||
requested_bytes -= cdrom->raw_data_size;
|
||||
}
|
||||
|
||||
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
|
||||
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, (int)requested_bytes);
|
||||
total_read += num_read;
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
static void cdreader_close_track(void* track_handle)
|
||||
{
|
||||
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
|
||||
if (cdrom)
|
||||
{
|
||||
if (cdrom->file_handle)
|
||||
rc_file_close(cdrom->file_handle);
|
||||
|
||||
free(track_handle);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t cdreader_first_track_sector(void* track_handle)
|
||||
{
|
||||
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
|
||||
if (cdrom)
|
||||
return cdrom->track_first_sector + cdrom->track_pregap_sectors;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader)
|
||||
{
|
||||
cdreader->open_track = cdreader_open_track;
|
||||
cdreader->read_sector = cdreader_read_sector;
|
||||
cdreader->close_track = cdreader_close_track;
|
||||
cdreader->first_track_sector = cdreader_first_track_sector;
|
||||
}
|
||||
|
||||
void rc_hash_init_default_cdreader(void)
|
||||
{
|
||||
struct rc_hash_cdreader cdreader;
|
||||
rc_hash_get_default_cdreader(&cdreader);
|
||||
rc_hash_init_custom_cdreader(&cdreader);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -4485,7 +4485,7 @@ void Achievements::UpdateProgressDatabase(bool force)
|
|||
std::span<const rc_client_achievement_bucket_t>(achievements->buckets, achievements->num_buckets))
|
||||
{
|
||||
for (const rc_client_achievement_t* achievement :
|
||||
std::span<rc_client_achievement_t*>(bucket.achievements, bucket.num_achievements))
|
||||
std::span<const rc_client_achievement_t*>(bucket.achievements, bucket.num_achievements))
|
||||
{
|
||||
achievements_unlocked += BoolToUInt32((achievement->unlocked & RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE) != 0);
|
||||
achievements_unlocked_hardcore +=
|
||||
|
|
Loading…
Reference in New Issue