(cheevos) upgrade to rcheevos 12.0 (#18117)

This commit is contained in:
Jamiras 2025-07-22 20:04:15 -06:00 committed by GitHub
parent a86c031410
commit 3829672dc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 9664 additions and 5740 deletions

View File

@ -2385,6 +2385,10 @@ ifeq ($(HAVE_NETWORKING), 1)
deps/rcheevos/src/rhash/aes.o \
deps/rcheevos/src/rhash/cdreader.o \
deps/rcheevos/src/rhash/hash.o \
deps/rcheevos/src/rhash/hash_disc.o \
deps/rcheevos/src/rhash/hash_encrypted.o \
deps/rcheevos/src/rhash/hash_rom.o \
deps/rcheevos/src/rhash/hash_zip.o \
deps/rcheevos/src/rapi/rc_api_common.o \
deps/rcheevos/src/rapi/rc_api_info.o \
deps/rcheevos/src/rapi/rc_api_runtime.o \

View File

@ -96,6 +96,7 @@ static rcheevos_locals_t rcheevos_locals =
0, /* menuitem_count */
#endif
true, /* hardcore_allowed */
false,/* hardcore_requires_reload */
false,/* hardcore_being_enabled */
true /* core_supports */
};
@ -953,6 +954,7 @@ void rcheevos_validate_config_settings(void)
CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", buffer);
_len = snprintf(buffer, sizeof(buffer),
msg_hash_to_str(MSG_CHEEVOS_HARDCORE_PAUSED_SETTING_NOT_ALLOWED), key, val);
rcheevos_locals.hardcore_requires_reload = true;
rcheevos_pause_hardcore();
runloop_msg_queue_push(buffer, _len, 0, 4 * 60, false, NULL,
@ -1587,6 +1589,7 @@ bool rcheevos_load(const void *data)
rc_client_set_spectator_mode_enabled(rcheevos_locals.client, !rcheevos_is_player_active());
rc_client_set_read_memory_function(rcheevos_locals.client, rcheevos_client_read_memory_uninitialized);
rcheevos_locals.hardcore_requires_reload = false;
rcheevos_validate_config_settings();
CHEEVOS_LOG(RCHEEVOS_TAG "Load started, hardcore %sactive\n", rcheevos_hardcore_active() ? "" : "not ");
@ -1652,7 +1655,7 @@ void rcheevos_change_disc(const char* new_disc_path, bool initial_disc)
{
if (rcheevos_locals.client)
{
rc_client_begin_change_media(rcheevos_locals.client, new_disc_path,
rc_client_begin_identify_and_change_media(rcheevos_locals.client, new_disc_path,
NULL, 0, rcheevos_client_change_media_callback, NULL);
}
}

View File

@ -420,7 +420,7 @@ static void rcheevos_client_download_task_callback(retro_task_t* task,
free(callback_data);
}
static bool rcheevos_client_download_badge(rc_client_download_queue_t* queue,
bool rcheevos_client_download_badge(rc_client_download_queue_t* queue,
const char* url, const char* badge_name)
{
rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals();
@ -472,11 +472,11 @@ void rcheevos_client_download_badge_from_url(const char* url, const char* badge_
static void rcheevos_client_fetch_next_badge(rc_client_download_queue_t* queue)
{
rc_client_achievement_bucket_t* bucket;
rc_client_achievement_t* achievement;
const rc_client_achievement_bucket_t* bucket;
const rc_client_achievement_t* achievement;
const char* next_badge;
char badge_name[32];
char url[256];
const char* url = NULL;
bool done = false;
do
@ -515,9 +515,7 @@ static void rcheevos_client_fetch_next_badge(rc_client_download_queue_t* queue)
if (queue->pass == 0)
{
/* first pass - get all unlocked badges */
if (rc_client_achievement_get_image_url(achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED, url, sizeof(url)) != RC_OK)
continue;
url = achievement->badge_url;
next_badge = achievement->badge_name;
}
else if (achievement->unlock_time)
@ -528,8 +526,7 @@ static void rcheevos_client_fetch_next_badge(rc_client_download_queue_t* queue)
else
{
/* second pass - get locked badge */
if (rc_client_achievement_get_image_url(achievement, RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE, url, sizeof(url)) != RC_OK)
continue;
url = achievement->badge_locked_url;
snprintf(badge_name, sizeof(badge_name), "%s_lock", achievement->badge_name);
next_badge = badge_name;
@ -618,26 +615,5 @@ void rcheevos_client_download_achievement_badges(rc_client_t* client)
#undef RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS
void rcheevos_client_download_achievement_badge(const char* badge_name, bool locked)
{
rc_api_fetch_image_request_t image_request;
rc_api_request_t request;
char locked_badge_name[32];
memset(&image_request, 0, sizeof(image_request));
image_request.image_type = locked ? RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED : RC_IMAGE_TYPE_ACHIEVEMENT;
image_request.image_name = badge_name;
if (locked)
{
snprintf(locked_badge_name, sizeof(locked_badge_name), "%s_lock", badge_name);
badge_name = locked_badge_name;
}
if (rc_api_init_fetch_image_request(&request, &image_request) == RC_OK)
rcheevos_client_download_badge(NULL, request.url, badge_name);
rc_api_destroy_request(&request);
}

View File

@ -23,7 +23,6 @@ RETRO_BEGIN_DECLS
void rcheevos_client_download_placeholder_badge(void);
void rcheevos_client_download_game_badge(const rc_client_game_t* game);
void rcheevos_client_download_achievement_badges(rc_client_t* client);
void rcheevos_client_download_achievement_badge(const char* badge_name, bool locked);
void rcheevos_client_download_badge_from_url(const char* url, const char* badge_name);
void rcheevos_client_server_call(const rc_api_request_t* request,

View File

@ -70,7 +70,7 @@ enum rcheevos_summary_notif
typedef struct rcheevos_menuitem_t
{
rc_client_achievement_t* achievement;
const rc_client_achievement_t* achievement;
uintptr_t menu_badge_texture;
uint32_t subset_id;
uint8_t menu_badge_grayscale;
@ -98,6 +98,7 @@ typedef struct rcheevos_locals_t
#endif
bool hardcore_allowed; /* prevents enabling hardcore if illegal settings detected */
bool hardcore_requires_reload; /* prevents enabling hardcore until the core is reloaded */
bool hardcore_being_enabled; /* allows callers to detect hardcore mode while it's being enabled */
bool core_supports; /* false if core explicitly disables achievements */

View File

@ -264,6 +264,14 @@ void rcheevos_menu_populate_hardcore_pause_submenu(void* data, bool cheevos_hard
MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE,
MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS, 0, 0, NULL);
}
else if (rcheevos_locals->hardcore_requires_reload)
{
menu_entries_append(info->list,
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME_REQUIRES_RELOAD),
msg_hash_to_str(MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_REQUIRES_RELOAD),
MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_REQUIRES_RELOAD,
MENU_SETTING_ACTION_CLOSE, 0, 0, NULL);
}
else
{
menu_entries_append(info->list,
@ -510,6 +518,47 @@ void rcheevos_menu_populate(void* data, bool cheevos_enable,
#endif /* HAVE_MENU */
static void rcheevos_client_download_achievement_badge(const char* badge_name, bool locked)
{
/* have to find the achievement associated to badge_name, then fetch either badge_url
* or badge_locked_url based on the locked parameter */
rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals();
rc_client_achievement_list_t* list = rc_client_create_achievement_list(rcheevos_locals->client,
RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL,
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS);
if (list)
{
const char* url = NULL;
uint32_t i, j;
for (i = 0; i < list->num_buckets && !url; i++)
{
for (j = 0; j < list->buckets[i].num_achievements; j++)
{
const rc_client_achievement_t* achievement = list->buckets[i].achievements[j];
if (achievement && strcmp(achievement->badge_name, badge_name) == 0)
{
url = locked ? achievement->badge_locked_url : achievement->badge_url;
break;
}
}
}
if (url)
{
char locked_badge_name[32];
if (locked)
{
snprintf(locked_badge_name, sizeof(locked_badge_name), "%s_lock", badge_name);
badge_name = locked_badge_name;
}
rcheevos_client_download_badge_from_url(url, badge_name);
}
rc_client_destroy_achievement_list(list);
}
}
uintptr_t rcheevos_get_badge_texture(const char* badge, bool locked, bool download_if_missing)
{
size_t _len;
@ -542,21 +591,13 @@ uintptr_t rcheevos_get_badge_texture(const char* badge, bool locked, bool downlo
{
if (download_if_missing)
{
const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals();
if (badge[0] == 'i')
{
/* rcheevos_client_download_game_badge expects a rc_client_game_t, not the badge name.
* call rc_api_init_fetch_image_request directly */
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 = RC_IMAGE_TYPE_GAME;
image_request.image_name = &badge[1];
result = rc_api_init_fetch_image_request(&request, &image_request);
if (result == RC_OK)
rcheevos_client_download_badge_from_url(request.url, badge);
const rc_client_game_t* game = rc_client_get_game_info(rcheevos_locals->client);
if (game && strcmp(game->badge_name, &badge[1]) == 0)
rcheevos_client_download_badge_from_url(game->badge_url, badge);
}
else
{

View File

@ -1,3 +1,61 @@
# v12.0.0
* rc_client changes
* add RC_CLIENT_EVENT_SUBSET_COMPLETED event
* add 'avatar_url' to rc_client_user_t
* add 'badge_url' to rc_client_game_t
* add 'badge_url' and 'badge_locked_url' to rc_client_achievement_t
* add rc_client_set_hash_callbacks
* add rc_client_set_allow_background_memory_reads
* renamed rc_client_begin_change_media to rc_client_begin_identify_and_change_media
* renamed rc_client_begin_change_media_from_hash to rc_client_begin_change_media
* use slim read/write locks for rc_mutex on Windows Vista+
* use critical sections for rc_mutex on older versions of Windows
* add translation layer in rc_client_external for cross-version support
* rhash changes
* rc_hash_init_verbose_message_callback and rc_hash_init_error_message_callback have been deprecated.
- set values in iterator.callbacks instead.
* rc_hash_init_custom_filereader and rc_hash_init_custom_cdreader have been deprecated.
- set values in iterator.callbacks instead.
* rc_hash_generate_from_file and rc_hash_generate_from_buffer have been deprecated.
- use rc_hash_initialize_iterator and rc_hash_generate instead.
* hash.c has been split into several smaller files which can be conditionally excluded from the build.
- hash_disc.c can be excluded by defining RC_HASH_NO_DISC
- hash_encrypted.c can be excluded by defining RC_HASH_NO_ENCRYPTED
- hash_rom.c can be excluded by defining RC_HASH_NO_ROM
- hash_zip.c can be excluded by defining RC_HASH_NO_ZIP
* add hash method for RC_CONSOLE_WII
* add hash method for Arduboy FX games (RC_CONSOLE_ARDUBOY)
* fix hash for PCE homebrew games that aren't multiples of 128KB
* rapi changes
* add _hosted variations of rc_api_init functions for interacting with custom hosts
- rc_api_set_host and rc_api_set_image_host have been deprecated
* add rc_api_fetch_game_sets
* add rc_api_fetch_followed_users
* add rc_api_fetch_hash_library
* add rc_api_fetch_all_user_progress
* add 'avatar_url' to login response, achievement_info response, and leaderboard_info responses
* add 'badge_url' to game_data response
* rurl has been removed (deprecated in v10.0.0)
* trigger parsing/processing code has been largely modified to improve processing performance
* AddAddress/AddSource chains can now be shared across triggers to avoid recalculating them multiple times
* triggers are preprocessed to allocate conditions as array instead of linked list.
- array is sorted such that similar conditions (pause/reset/hittarget/etc) can be processed together
without having to scan the linked list multiple times
- the linked list still exists for backwards compatibility and to maintain the original ordering
* each condition type has its own handler, eliminating many switches and branches when processing triggers
* memrefs are now allocated in arrays (eliminating the linked list pointer from each to reduce memory footprint)
* commas are now inserted into all numeric formats except SCORE. a new format type UNFORMATTED (@Unformatted) has
been added to display a number without commas.
- To forcibly exclude commas in a backwards-compatible way, use the @Unformatted macro and define Unformatted as
a VALUE formatter. Legacy clients will use the VALUE formatted and new clients will use the @Unformatted macro.
* add RC_CONSOLE_FAMICOM_DISK_SYSTEM (split off of RC_CONSOLE_NINTENDO)
* update RC_CONSOLE_SNES memory map to support SA-1 I-RAM and BW-RAM
* update RC_CONSOLE_SEGACD memory map to supprot WORD RAM
* update RC_CONSOLE_N64 memory map to place RDRAM at $80000000 and expansion pak RAM at $80400000
* remove HAVE_LUA define and support (implementation was never finished)
* report error when RP contains address starting with `0x0x`.
* add .natvis files to improve debugging in Visual Studio
# v11.6.0
* backdate retried unlocks in rc_client
* add memory map and hash method for RC_CONSOLE_ZX_SPECTRUM

View File

@ -6,23 +6,11 @@ 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)
```
**NOTE**: development occurs on the _develop_ branch, which is set as the default branch in GitHub so newly opened PRs will request to be merged into the _develop_ branch. When integrating **rcheevos** into your project, we recommend using the _master_ branch, which corresponds to the last official release, and minimizes the risk of encountering a bug that has been introduced since the last official release.
## 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)
@ -41,13 +29,7 @@ Platforms supported by RetroAchievements are enumerated in `rc_consoles.h`. Note
## Runtime support
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.
A set of functions for managing an active game is provided by the library. If you are considering adding achievement support to your emulator, you should look at the `rc_client_t` functions which will prepare the API calls and other implement other common functionality (like managing the user information, identifying/loading a game, and building the active/inactive achievements list for the UI). It has several callback functions which allow the client to implement dependent functionality (UI and HTTP calls). Please see [the wiki](https://github.com/RetroAchievements/rcheevos/wiki/rc_client-integration) for details on using the `rc_client_t` functions.
## Server Communication
@ -55,11 +37,9 @@ The `rc_client_t` functions wrap a `rc_runtime_t` and manage the API calls and o
**rapi** does *not* make HTTP requests.
NOTE: **rapi** is a replacement for **rurl**. **rurl** has been deprecated.
NOTE: `rc_client` is the preferred way to have a client interact with the server.
These are in `rc_api_user.h`, `rc_api_runtime.h` and `rc_api_common.h`.
**rapi** headers are `rc_api_user.h`, `rc_api_runtime.h` and `rc_api_common.h`.
The basic process of making an **rapi** call is to initialize a params object, call a function to convert it to a URL, send that to the server, then pass the response to a function to convert it into a response object, and handle the response values.
@ -76,6 +56,12 @@ 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);
```
### Custom file handling
**rhash** (and by extension **rc_client**) support custom handlers for opening/reading files. This allows the client to redirect file reads to support custom file formats (like ZIP or CHD).

View File

@ -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,9 +229,50 @@ 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);
/* --- Fetch Game Hashes --- */
/**
* API parameters for a fetch games list request.
*/
typedef struct rc_api_fetch_hash_library_request_t {
/**
* The unique identifier of the console to query.
* Passing RC_CONSOLE_UNKNOWN will return hashes for all consoles.
*/
uint32_t console_id;
} rc_api_fetch_hash_library_request_t;
/* A hash library entry */
typedef struct rc_api_hash_library_entry_t {
/* The hash for the game */
const char* hash;
/* The unique identifier of the game */
uint32_t game_id;
} rc_api_hash_library_entry_t;
/**
* Response data for a fetch hash library request.
*/
typedef struct rc_api_fetch_hash_library_response_t {
/* An array of entries, one per game */
rc_api_hash_library_entry_t* entries;
/* The number of items in the entries array */
uint32_t num_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_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);
RC_END_C_DECLS
#endif /* RC_API_INFO_H */

View File

@ -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 {

View File

@ -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,6 +128,10 @@ 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;
@ -146,6 +155,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;
@ -165,10 +176,96 @@ typedef struct rc_api_fetch_game_data_response_t {
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);
/* --- Fetch Game Sets --- */
/**
* API parameters for a fetch game data request.
*/
typedef struct rc_api_fetch_game_sets_request_t {
/* The username of the player */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the game */
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_sets_request_t;
#define RC_ACHIEVEMENT_SET_TYPE_CORE 0
#define RC_ACHIEVEMENT_SET_TYPE_BONUS 1
#define RC_ACHIEVEMENT_SET_TYPE_SPECIALTY 2
#define RC_ACHIEVEMENT_SET_TYPE_EXCLUSIVE 3
/* A game subset definition */
typedef struct rc_api_achievement_set_definition_t {
/* The unique identifier of the achievement set */
uint32_t id;
/* The legacy game_id of the achievement set (used for editor API calls) */
uint32_t game_id;
/* The title of the achievement set */
const char* title;
/* The image name for the achievement set badge */
const char* image_name;
/* The URL for the achievement set badge */
const char* image_url;
/* An array of achievements for the achievement set */
rc_api_achievement_definition_t* achievements;
/* The number of items in the achievements array */
uint32_t num_achievements;
/* An array of leaderboards for the achievement set */
rc_api_leaderboard_definition_t* leaderboards;
/* The number of items in the leaderboards array */
uint32_t num_leaderboards;
/* The type of the achievement set */
uint8_t type;
}
rc_api_achievement_set_definition_t;
/**
* Response data for a fetch game sets request.
*/
typedef struct rc_api_fetch_game_sets_response_t {
/* The unique identifier of the game */
uint32_t id;
/* The console associated to the game */
uint32_t console_id;
/* The title of the game */
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;
/* The unique identifier of the game to use for session requests (startsession/ping/etc) */
uint32_t session_game_id;
/* An array of sets for the game */
rc_api_achievement_set_definition_t* sets;
/* The number of items in the sets array */
uint32_t num_sets;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_game_sets_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_game_sets_request(rc_api_request_t* request, const rc_api_fetch_game_sets_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_init_fetch_game_sets_request_hosted(rc_api_request_t* request, const rc_api_fetch_game_sets_request_t* api_params, const rc_api_host_t* host);
RC_EXPORT int RC_CCONV rc_api_process_fetch_game_sets_server_response(rc_api_fetch_game_sets_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_game_sets_response(rc_api_fetch_game_sets_response_t* response);
/* --- Ping --- */
/**
@ -200,6 +297,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 +344,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 +406,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);

View File

@ -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,117 @@ 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 --- */
/**
* API parameters for a fetch all user progress request.
*/
typedef struct rc_api_fetch_all_user_progress_request_t {
/* The username of the player */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the console to query */
uint32_t console_id;
} rc_api_fetch_all_user_progress_request_t;
/* An all-user-progress entry */
typedef struct rc_api_all_user_progress_entry_t {
/* The unique identifier of the game */
uint32_t game_id;
/* The total number of achievements for this game */
uint32_t num_achievements;
/* The total number of unlocked achievements for this game in softcore mode */
uint32_t num_unlocked_achievements;
/* The total number of unlocked achievements for this game in hardcore mode */
uint32_t num_unlocked_achievements_hardcore;
} rc_api_all_user_progress_entry_t;
/**
* Response data for a fetch all user progress request.
*/
typedef struct rc_api_fetch_all_user_progress_response_t {
/* An array of entries, one per game */
rc_api_all_user_progress_entry_t* entries;
/* The number of items in the entries array */
uint32_t num_entries;
/* Common server-provided response information */
rc_api_response_t response;
} rc_api_fetch_all_user_progress_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_all_user_progress_request(rc_api_request_t* request, const rc_api_fetch_all_user_progress_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_init_fetch_all_user_progress_request_hosted(rc_api_request_t* request, const rc_api_fetch_all_user_progress_request_t* api_params, const rc_api_host_t* host);
RC_EXPORT int RC_CCONV rc_api_process_fetch_all_user_progress_server_response(rc_api_fetch_all_user_progress_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_all_user_progress_response(rc_api_fetch_all_user_progress_response_t* response);
RC_END_C_DECLS
#endif /* RC_API_H */

View File

@ -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);
@ -188,6 +188,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;
/**
@ -217,6 +219,39 @@ typedef struct rc_client_user_game_summary_t {
*/
RC_EXPORT void RC_CCONV rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary);
typedef struct rc_client_all_user_progress_entry_t {
uint32_t game_id;
uint32_t num_achievements;
uint32_t num_unlocked_achievements;
uint32_t num_unlocked_achievements_hardcore;
} rc_client_all_user_progress_entry_t;
typedef struct rc_client_all_user_progress_t {
rc_client_all_user_progress_entry_t* entries;
uint32_t num_entries;
} rc_client_all_user_progress_t;
/**
* Callback that is fired when an all progress query completes. list may be null if the query failed.
*/
typedef void(RC_CCONV* rc_client_fetch_all_user_progress_callback_t)(int result, const char* error_message,
rc_client_all_user_progress_t* list,
rc_client_t* client, void* callback_userdata);
/**
* Starts an asynchronous request for all progress for the given console.
* This query returns the total number of achievements for all games tracked by this console, as well as
* the user's achievement unlock count for both softcore and hardcore modes.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV
rc_client_begin_fetch_all_user_progress(rc_client_t* client, uint32_t console_id,
rc_client_fetch_all_user_progress_callback_t callback, void* callback_userdata);
/**
* Destroys a previously-allocated result from the rc_client_begin_fetch_all_progress_list() callback.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_all_user_progress(rc_client_all_user_progress_t* list);
/*****************************************************************************\
| Game |
\*****************************************************************************/
@ -229,6 +264,12 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_g
uint32_t console_id, const char* file_path,
const uint8_t* data, size_t data_size,
rc_client_callback_t callback, void* callback_userdata);
struct rc_hash_callbacks;
/**
* Provide callback functions for interacting with the file system and processing disc-based files when generating hashes.
*/
RC_EXPORT void rc_client_set_hash_callbacks(rc_client_t* client, const struct rc_hash_callbacks* callbacks);
#endif
/**
@ -243,9 +284,9 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client
RC_EXPORT int RC_CCONV rc_client_get_load_game_state(const rc_client_t* client);
enum {
RC_CLIENT_LOAD_GAME_STATE_NONE,
RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME,
RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN,
RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA,
RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME,
RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, /* [deprecated] - game data is now returned by identify call */
RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION,
RC_CLIENT_LOAD_GAME_STATE_DONE,
RC_CLIENT_LOAD_GAME_STATE_ABORTED
@ -267,6 +308,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;
/**
@ -285,15 +328,17 @@ RC_EXPORT int RC_CCONV rc_client_game_get_image_url(const rc_client_game_t* game
/**
* Changes the active disc in a multi-disc game.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media(rc_client_t* client, const char* file_path,
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_change_media(rc_client_t* client, const char* file_path,
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
#endif
/**
* Changes the active disc in a multi-disc game.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash,
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media(rc_client_t* client, const char* hash,
rc_client_callback_t callback, void* callback_userdata);
/* this function was renamed in rcheevos 12.0 */
#define rc_client_begin_change_media_from_hash rc_client_begin_change_media
/*****************************************************************************\
| Subsets |
@ -306,10 +351,47 @@ 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);
/*****************************************************************************\
| Fetch Game Hashes |
\*****************************************************************************/
typedef struct rc_client_hash_library_entry_t {
char hash[33];
uint32_t game_id;
} rc_client_hash_library_entry_t;
typedef struct rc_client_hash_library_t {
rc_client_hash_library_entry_t* entries;
uint32_t num_entries;
} rc_client_hash_library_t;
/**
* Callback that is fired when a hash library request completes. list may be null if the query failed.
*/
typedef void(RC_CCONV* rc_client_fetch_hash_library_callback_t)(int result, const char* error_message,
rc_client_hash_library_t* list, rc_client_t* client,
void* callback_userdata);
/**
* Starts an asynchronous request for all hashes for the given console.
* This request returns a mapping from hashes to the game's unique identifier. A single game may have multiple
* hashes in the case of multi-disc games, or variants that are still compatible with the same achievement set.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_hash_library(
rc_client_t* client, uint32_t console_id, rc_client_fetch_hash_library_callback_t callback, void* callback_userdata);
/**
* Destroys a previously-allocated result from the rc_client_destroy_hash_library() callback.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_hash_library(rc_client_hash_library_t* list);
/*****************************************************************************\
| Achievements |
\*****************************************************************************/
@ -373,6 +455,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;
/**
@ -387,7 +472,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;
@ -396,7 +481,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;
@ -463,7 +548,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;
@ -472,7 +557,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;
@ -619,7 +704,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 {
@ -637,6 +723,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;
@ -655,6 +742,11 @@ RC_EXPORT void RC_CCONV rc_client_set_event_handler(rc_client_t* client, rc_clie
*/
RC_EXPORT void RC_CCONV rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler);
/**
* Specifies whether rc_client is allowed to read memory outside of rc_client_do_frame/rc_client_idle.
*/
RC_EXPORT void RC_CCONV rc_client_set_allow_background_memory_reads(rc_client_t* client, int allowed);
/**
* Determines if there are any active achievements/leaderboards/rich presence that need processing.
*/

View File

@ -93,6 +93,7 @@ enum {
RC_CONSOLE_NINTENDO_DSI = 78,
RC_CONSOLE_TI83 = 79,
RC_CONSOLE_UZEBOX = 80,
RC_CONSOLE_FAMICOM_DISK_SYSTEM = 81,
RC_CONSOLE_HUBS = 100,
RC_CONSOLE_EVENTS = 101,

View File

@ -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);

View File

@ -9,54 +9,19 @@
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);
struct rc_hash_iterator;
/* ===================================================== */
/* 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);
/* ===================================================== */
typedef void (RC_CCONV *rc_hash_message_callback_deprecated)(const char*);
/* specifies a function to call when an error occurs to display the error message */
typedef void (RC_CCONV *rc_hash_message_callback)(const char*);
RC_EXPORT void RC_CCONV rc_hash_init_error_message_callback(rc_hash_message_callback callback);
/* [deprecated] set callbacks in rc_hash_iterator_t */
RC_EXPORT void RC_CCONV rc_hash_init_error_message_callback(rc_hash_message_callback_deprecated callback);
/* specifies a function to call for verbose logging */
RC_EXPORT void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback);
/* [deprecated] set callbacks in rc_hash_iterator_t */
RC_EXPORT void rc_hash_init_verbose_message_callback(rc_hash_message_callback_deprecated callback);
/* ===================================================== */
@ -77,19 +42,22 @@ 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);
/* ===================================================== */
#ifndef RC_HASH_NO_DISC
#define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1) /* the first data track (skip audio tracks) */
#define RC_HASH_CDTRACK_LAST ((uint32_t)-2) /* the last data/audio track */
#define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) /* the largest data/audio track */
@ -99,6 +67,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_iterator_handler)(const char* path, uint32_t track, const struct rc_hash_iterator* iterator);
/* 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,24 +80,32 @@ 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_iterator_handler open_track_iterator;
} 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);
#endif /* RC_HASH_NO_DISC */
#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.
@ -143,9 +120,79 @@ RC_BEGIN_C_DECLS
*/
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 /* RC_HASH_NO_ENCRYPTED */
/* ===================================================== */
typedef void (RC_CCONV* rc_hash_message_callback_func)(const char*, const struct rc_hash_iterator* iterator);
typedef struct rc_hash_callbacks {
rc_hash_message_callback_func verbose_message;
rc_hash_message_callback_func error_message;
rc_hash_filereader_t filereader;
#ifndef RC_HASH_NO_DISC
rc_hash_cdreader_t cdreader;
#endif
#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

View File

@ -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;
@ -46,7 +44,6 @@ typedef struct rc_runtime_trigger_t {
rc_memref_t* invalid_memref;
uint8_t md5[16];
int32_t serialized_size;
uint8_t owns_memrefs;
}
rc_runtime_trigger_t;
@ -58,16 +55,13 @@ typedef struct rc_runtime_lboard_t {
rc_memref_t* invalid_memref;
uint8_t md5[16];
uint32_t serialized_size;
uint8_t owns_memrefs;
}
rc_runtime_lboard_t;
typedef struct rc_runtime_richpresence_t {
rc_richpresence_t* richpresence;
void* buffer;
struct rc_runtime_richpresence_t* previous;
uint8_t md5[16];
uint8_t owns_memrefs;
}
rc_runtime_richpresence_t;
@ -82,11 +76,7 @@ typedef struct rc_runtime_t {
rc_runtime_richpresence_t* richpresence;
rc_memref_t* memrefs;
rc_memref_t** next_memref;
rc_value_t* variables;
rc_value_t** next_variable;
struct rc_memrefs_t* memrefs;
uint8_t owns_self;
}
@ -96,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);
enum {
RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED, /* from WAITING, PAUSED, or PRIMED to ACTIVE */
@ -136,22 +126,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

View File

@ -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;
@ -29,7 +27,7 @@ typedef struct rc_value_t rc_value_t;
* num_bytes is greater than 1, the value is read in little-endian from
* memory.
*/
typedef uint32_t(RC_CCONV *rc_peek_t)(uint32_t address, uint32_t num_bytes, void* ud);
typedef uint32_t(RC_CCONV* rc_peek_t)(uint32_t address, uint32_t num_bytes, void* ud);
/*****************************************************************************\
| Memory References |
@ -70,15 +68,14 @@ typedef struct rc_memref_value_t {
/* The last differing value of this memory reference. */
uint32_t prior;
/* The size of the value. */
/* The size of the value. (RC_MEMSIZE_*) */
uint8_t size;
/* True if the value changed this frame. */
uint8_t changed;
/* The value type of the value (for variables) */
/* The value type of the value. (RC_VALUE_TYPE_*) */
uint8_t type;
/* True if the reference will be used in indirection.
* NOTE: This is actually a property of the rc_memref_t, but we put it here to save space */
uint8_t is_indirect;
/* The type of memref (RC_MEMREF_TYPE_*) */
uint8_t memref_type;
}
rc_memref_value_t;
@ -88,9 +85,6 @@ struct rc_memref_t {
/* The memory address of this variable. */
uint32_t address;
/* The next memory reference in the chain. */
rc_memref_t* next;
};
/*****************************************************************************\
@ -103,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. */
@ -120,16 +114,19 @@ 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 */
/* specifies which member of the value union is being used (RC_OPERAND_*) */
uint8_t type;
/* the actual RC_MEMSIZE of the operand - memref.size may differ */
/* the RC_MEMSIZE of the operand specified in the condition definition - memref.size may differ */
uint8_t size;
/* specifies how to read the memref for some types (RC_OPERAND_*) */
uint8_t memref_access_type;
/* if set, this operand is combining the current condition with the previous one */
uint8_t is_combining;
}
rc_operand_t;
@ -141,23 +138,16 @@ RC_EXPORT int RC_CCONV rc_operand_is_memref(const rc_operand_t* operand);
/* types */
enum {
/* NOTE: this enum is ordered to optimize the switch statements in rc_test_condset_internal. the values may change between releases */
/* non-combining conditions (third switch) */
RC_CONDITION_STANDARD, /* this should always be 0 */
RC_CONDITION_PAUSE_IF,
RC_CONDITION_RESET_IF,
RC_CONDITION_MEASURED_IF,
RC_CONDITION_TRIGGER,
RC_CONDITION_MEASURED, /* measured also appears in the first switch, so place it at the border between them */
/* modifiers (first switch) */
RC_CONDITION_ADD_SOURCE, /* everything from this point on affects the condition after it */
RC_CONDITION_MEASURED,
RC_CONDITION_ADD_SOURCE,
RC_CONDITION_SUB_SOURCE,
RC_CONDITION_ADD_ADDRESS,
RC_CONDITION_REMEMBER,
/* logic flags (second switch) */
RC_CONDITION_ADD_HITS,
RC_CONDITION_SUB_HITS,
RC_CONDITION_RESET_NEXT_IF,
@ -180,7 +170,10 @@ enum {
RC_OPERATOR_XOR,
RC_OPERATOR_MOD,
RC_OPERATOR_ADD,
RC_OPERATOR_SUB
RC_OPERATOR_SUB,
RC_OPERATOR_SUB_PARENT, /* internal use */
RC_OPERATOR_INDIRECT_READ /* internal use */
};
typedef struct rc_condition_t rc_condition_t;
@ -204,10 +197,15 @@ struct rc_condition_t {
/* The comparison operator to use. (RC_OPERATOR_*) */
uint8_t oper; /* operator is a reserved word in C++. */
/* Set if the condition needs to processed as part of the "check if paused" pass. (bool) */
uint8_t pause;
/* 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_*) */
@ -224,17 +222,32 @@ struct rc_condset_t {
/* The next condition set in the chain. */
rc_condset_t* next;
/* The list of conditions in this condition set. */
/* The first condition in this condition set. Then follow ->next chain. */
rc_condition_t* conditions;
/* True if any condition in the set is a pause condition. */
uint8_t has_pause;
/* The number of pause conditions in this condition set. */
/* The first pause condition is at "this + RC_ALIGN(sizeof(this)). */
uint16_t num_pause_conditions;
/* The number of reset conditions in this condition set. */
uint16_t num_reset_conditions;
/* The number of hittarget conditions in this condition set. */
uint16_t num_hittarget_conditions;
/* The number of non-hittarget measured conditions in this condition set. */
uint16_t num_measured_conditions;
/* The number of other conditions in this condition set. */
uint16_t num_other_conditions;
/* The number of indirect conditions in this condition set. */
uint16_t num_indirect_conditions;
/* True if any condition in the set is a pause condition. */
uint8_t has_pause; /* DEPRECATED - just check num_pause_conditions != 0 */
/* True if the set is currently paused. */
uint8_t is_paused;
/* True if the set has indirect memory references. */
uint8_t has_indirect_memrefs;
};
/*****************************************************************************\
@ -259,9 +272,6 @@ struct rc_trigger_t {
/* The list of sub condition sets in this test. */
rc_condset_t* alternative;
/* The memory references required by the trigger. */
rc_memref_t* memrefs;
/* The current state of the MEASURED condition. */
uint32_t measured_value;
@ -274,17 +284,17 @@ struct rc_trigger_t {
/* True if at least one condition has a non-zero hit count */
uint8_t has_hits;
/* True if at least one condition has a non-zero required hit count */
uint8_t has_required_hits;
/* True if the measured value should be displayed as a percentage */
uint8_t measured_as_percent;
/* True if the trigger has its own rc_memrefs_t */
uint8_t has_memrefs;
};
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);
/*****************************************************************************\
@ -297,11 +307,11 @@ struct rc_value_t {
/* The current value of the variable. */
rc_memref_value_t value;
/* The list of conditions to evaluate. */
rc_condset_t* conditions;
/* True if the value has its own rc_memrefs_t */
uint8_t has_memrefs;
/* The memory references required by the variable. */
rc_memref_t* memrefs;
/* The list of possible values (traverse next chain, pick max). */
rc_condset_t* conditions;
/* The name of the variable. */
const char* name;
@ -311,8 +321,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 |
@ -335,14 +345,14 @@ struct rc_lboard_t {
rc_trigger_t cancel;
rc_value_t value;
rc_value_t* progress;
rc_memref_t* memrefs;
uint8_t state;
uint8_t has_memrefs;
};
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);
/*****************************************************************************\
@ -370,7 +380,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);
@ -406,7 +417,7 @@ struct rc_richpresence_display_part_t {
rc_richpresence_display_part_t* next;
const char* text;
rc_richpresence_lookup_t* lookup;
rc_memref_value_t *value;
rc_operand_t value;
uint8_t display_type;
};
@ -416,21 +427,22 @@ struct rc_richpresence_display_t {
rc_trigger_t trigger;
rc_richpresence_display_t* next;
rc_richpresence_display_part_t* display;
uint8_t has_required_hits;
};
struct rc_richpresence_t {
rc_richpresence_display_t* first_display;
rc_richpresence_lookup_t* first_lookup;
rc_memref_t* memrefs;
rc_value_t* variables;
rc_value_t* values;
uint8_t has_memrefs;
};
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_END_C_DECLS

View File

@ -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 --- */
@ -318,6 +317,18 @@ static int rc_json_convert_error_code(const char* server_error_code)
case 'i':
if (strcmp(server_error_code, "invalid_credentials") == 0)
return RC_INVALID_CREDENTIALS;
if (strcmp(server_error_code, "invalid_parameter") == 0)
return RC_INVALID_STATE;
break;
case 'm':
if (strcmp(server_error_code, "missing_parameter") == 0)
return RC_INVALID_STATE;
break;
case 'n':
if (strcmp(server_error_code, "not_found") == 0)
return RC_NOT_FOUND;
break;
default:
@ -696,6 +707,57 @@ int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_fiel
return 1;
}
int rc_json_field_string_matches(const rc_json_field_t* field, const char* text) {
int is_quoted = 0;
const char* ptr = field->value_start;
if (!ptr)
return 0;
if (*ptr == '"') {
is_quoted = 1;
++ptr;
}
while (ptr < field->value_end) {
if (*ptr != *text) {
if (*ptr != '\\') {
if (*ptr == '"' && is_quoted && (*text == '\0')) {
is_quoted = 0;
++ptr;
continue;
}
return 0;
}
++ptr;
switch (*ptr) {
case 'n':
if (*text != '\n')
return 0;
break;
case 'r':
if (*text != '\r')
return 0;
break;
case 't':
if (*text != '\t')
return 0;
break;
default:
if (*text != *ptr)
return 0;
break;
}
}
++text;
++ptr;
}
return !is_quoted && (*text == '\0');
}
void rc_json_get_optional_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name, const char* default_value) {
if (!rc_json_get_string(out, &response->buffer, field, field_name))
*out = default_value;
@ -1128,21 +1190,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 +1231,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 +1262,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 +1284,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 +1361,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* image_name) {
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 = image_name;
result = rc_api_init_fetch_image_request(&request, &image_request);
if (result == RC_OK)
return rc_buffer_strcpy(buffer, request.url);
return NULL;
}

View File

@ -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 {
@ -66,6 +68,7 @@ int rc_json_get_required_array(uint32_t* num_entries, rc_json_field_t* array_fie
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator);
int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field);
int rc_json_get_object_string_length(const char* json);
int rc_json_field_string_matches(const rc_json_field_t* field, const char* text);
void rc_json_extract_filename(rc_json_field_t* field);
@ -74,9 +77,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* image_name);
RC_END_C_DECLS
#endif /* RC_API_COMMON_H */

View File

@ -1,5 +1,6 @@
#include "rc_api_info.h"
#include "rc_api_common.h"
#include "rc_api_runtime.h"
#include "rc_runtime_types.h"
@ -11,9 +12,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;
@ -73,7 +80,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));
@ -116,6 +124,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;
}
}
@ -130,9 +142,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;
@ -191,13 +209,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[] = {
@ -205,7 +216,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));
@ -281,6 +293,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;
}
}
@ -295,9 +311,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;
@ -380,11 +402,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;
@ -464,3 +492,91 @@ int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_re
void rc_api_destroy_fetch_game_titles_response(rc_api_fetch_game_titles_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Fetch Game Hashes --- */
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, host);
/* note: unauthenticated request */
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "hashlibrary");
if (api_params->console_id != 0)
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_fetch_hash_library_server_response(rc_api_fetch_hash_library_response_t* response,
const rc_api_server_response_t* server_response)
{
rc_api_hash_library_entry_t* entry;
rc_json_iterator_t iterator;
rc_json_field_t field;
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("MD5List"),
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result =
rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
if (!fields[2].value_start) {
/* call rc_json_get_required_object to generate the error message */
rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "MD5List");
return RC_MISSING_VALUE;
}
response->num_entries = fields[2].array_size;
if (response->num_entries > 0) {
rc_buffer_reserve(&response->response.buffer, response->num_entries * (33 + sizeof(rc_api_hash_library_entry_t)));
response->entries = (rc_api_hash_library_entry_t*)rc_buffer_alloc(
&response->response.buffer, response->num_entries * sizeof(rc_api_hash_library_entry_t));
if (!response->entries)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = fields[2].value_start;
iterator.end = fields[2].value_end;
entry = response->entries;
while (rc_json_get_next_object_field(&iterator, &field)) {
entry->hash = rc_buffer_strncpy(&response->response.buffer, field.name, field.name_len);
field.name = "";
if (!rc_json_get_unum(&entry->game_id, &field, ""))
return RC_MISSING_VALUE;
++entry;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_hash_library_response(rc_api_fetch_hash_library_response_t* response)
{
rc_buffer_destroy(&response->response.buffer);
}

View File

@ -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,48 +108,13 @@ 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)
{
if (strcmp(type, "missable") == 0)
return RC_ACHIEVEMENT_TYPE_MISSABLE;
if (strcmp(type, "win_condition") == 0)
return RC_ACHIEVEMENT_TYPE_WIN;
if (strcmp(type, "progression") == 0)
return RC_ACHIEVEMENT_TYPE_PROGRESSION;
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_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;
rc_json_field_t achievement_fields[] = {
RC_JSON_NEW_FIELD("ID"),
@ -148,9 +129,107 @@ 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, &achievement_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->title, response, &achievement_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->description, response, &achievement_fields[2], "Description"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&achievement->category, response, &achievement_fields[3], "Flags"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&achievement->points, response, &achievement_fields[4], "Points"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->definition, response, &achievement_fields[5], "MemAddr"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->badge_name, response, &achievement_fields[7], "BadgeName"))
return RC_MISSING_VALUE;
rc_json_get_optional_string(&achievement->badge_url, response, &achievement_fields[13], "BadgeURL", "");
if (!achievement->badge_url[0])
achievement->badge_url = rc_api_build_avatar_url(&response->buffer, RC_IMAGE_TYPE_ACHIEVEMENT, achievement->badge_name);
rc_json_get_optional_string(&achievement->badge_locked_url, response, &achievement_fields[14], "BadgeLockedURL", "");
if (!achievement->badge_locked_url[0])
achievement->badge_locked_url = rc_api_build_avatar_url(&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, &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, &achievement_fields[8], "Created"))
return RC_MISSING_VALUE;
achievement->created = (time_t)timet;
if (!rc_json_get_required_unum(&timet, response, &achievement_fields[9], "Modified"))
return RC_MISSING_VALUE;
achievement->updated = (time_t)timet;
if (rc_json_field_string_matches(&achievement_fields[10], ""))
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
else if (rc_json_field_string_matches(&achievement_fields[10], "progression"))
achievement->type = RC_ACHIEVEMENT_TYPE_PROGRESSION;
else if (rc_json_field_string_matches(&achievement_fields[10], "missable"))
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
else if (rc_json_field_string_matches(&achievement_fields[10], "win_condition"))
achievement->type = RC_ACHIEVEMENT_TYPE_WIN;
else
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
/* 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_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 +240,65 @@ 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, &leaderboard_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->title, response, &leaderboard_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->description, response, &leaderboard_fields[2], "Description"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->definition, 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;
}
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 */
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
@ -168,7 +306,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 +319,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 +349,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, 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 +362,9 @@ 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;
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;
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_leaderboards(&response->response, response->leaderboards, &array_field);
if (result != RC_OK)
return result;
}
return RC_OK;
@ -338,12 +374,205 @@ void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t*
rc_buffer_destroy(&response->response.buffer);
}
/* --- Fetch Game Sets --- */
int rc_api_init_fetch_game_sets_request(rc_api_request_t* request, const rc_api_fetch_game_sets_request_t* api_params) {
return rc_api_init_fetch_game_sets_request_hosted(request, api_params, &g_host);
}
int rc_api_init_fetch_game_sets_request_hosted(rc_api_request_t* request,
const rc_api_fetch_game_sets_request_t* api_params,
const rc_api_host_t* host) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request, host);
if (!api_params->game_id && (!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, "achievementsets", api_params->username, api_params->api_token)) {
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;
}
return builder.result;
}
static int rc_api_process_fetch_game_sets_achievement_sets(rc_api_fetch_game_sets_response_t* response,
rc_api_achievement_set_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("AchievementSetId"),
RC_JSON_NEW_FIELD("GameId"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("Type"),
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], "AchievementSetId"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&subset->game_id, &response->response, &subset_fields[1], "GameId"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&subset->title, &response->response, &subset_fields[2], "Title"))
return RC_MISSING_VALUE;
if (!subset->title || !subset->title[0])
subset->title = response->title;
if (rc_json_field_string_matches(&subset_fields[3], "core"))
subset->type = RC_ACHIEVEMENT_SET_TYPE_CORE;
else if (rc_json_field_string_matches(&subset_fields[3], "bonus"))
subset->type = RC_ACHIEVEMENT_SET_TYPE_BONUS;
else if (rc_json_field_string_matches(&subset_fields[3], "specialty"))
subset->type = RC_ACHIEVEMENT_SET_TYPE_SPECIALTY;
else if (rc_json_field_string_matches(&subset_fields[3], "exclusive"))
subset->type = RC_ACHIEVEMENT_SET_TYPE_EXCLUSIVE;
else
subset->type = RC_ACHIEVEMENT_SET_TYPE_BONUS;
if (rc_json_field_string_matches(&subset_fields[4], response->image_url)) {
subset->image_url = response->image_url;
subset->image_name = response->image_name;
}
else {
if (!rc_json_get_required_string(&subset->image_url, &response->response, &subset_fields[4], "ImageIconUrl"))
return RC_MISSING_VALUE;
rc_json_extract_filename(&subset_fields[4]);
rc_json_get_optional_string(&subset->image_name, &response->response, &subset_fields[4], "ImageIconUrl", "");
}
/* 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[5].value_end - subset_fields[5].value_start) - /* achievements */
subset_fields[5].array_size * (80 - sizeof(rc_api_achievement_definition_t));
len += (subset_fields[6].value_end - subset_fields[6].value_start) - /* leaderboards */
subset_fields[6].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[5], "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->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[6], "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->response, subset->leaderboards, &array_field);
if (result != RC_OK)
return result;
}
++subset;
}
return RC_OK;
}
int rc_api_process_fetch_game_sets_server_response(rc_api_fetch_game_sets_response_t* response, const rc_api_server_response_t* server_response) {
rc_json_field_t array_field;
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Code"),
RC_JSON_NEW_FIELD("GameId"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("ConsoleId"),
RC_JSON_NEW_FIELD("ImageIconUrl"),
RC_JSON_NEW_FIELD("RichPresenceGameId"),
RC_JSON_NEW_FIELD("RichPresencePatch"),
RC_JSON_NEW_FIELD("Sets") /* array */
};
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_unum(&response->id, &response->response, &fields[3], "GameId"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->title, &response->response, &fields[4], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->console_id, &response->response, &fields[5], "ConsoleId"))
return RC_MISSING_VALUE;
rc_json_get_required_string(&response->image_url, &response->response, &fields[6], "ImageIconUrl");
rc_json_extract_filename(&fields[6]);
rc_json_get_required_string(&response->image_name, &response->response, &fields[6], "ImageIconUrl");
rc_json_get_optional_unum(&response->session_game_id, &fields[7], "RichPresenceGameId", response->id);
rc_json_get_optional_string(&response->rich_presence_script, &response->response, &fields[8], "RichPresencePatch", "");
if (!response->rich_presence_script)
response->rich_presence_script = "";
rc_json_get_optional_array(&response->num_sets, &array_field, &fields[9], "Sets");
if (response->num_sets) {
response->sets = (rc_api_achievement_set_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_sets * sizeof(rc_api_achievement_set_definition_t));
if (!response->sets)
return RC_OUT_OF_MEMORY;
result = rc_api_process_fetch_game_sets_achievement_sets(response, response->sets, &array_field);
if (result != RC_OK)
return result;
}
return RC_OK;
}
void rc_api_destroy_fetch_game_sets_response(rc_api_fetch_game_sets_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- 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 +625,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 +732,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;

View File

@ -1,16 +1,25 @@
#include "rc_api_user.h"
#include "rc_api_common.h"
#include "rc_api_runtime.h"
#include "rc_consoles.h"
#include "../rc_version.h"
#include <stdlib.h>
#include <string.h>
/* --- 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;
@ -53,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));
@ -72,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;
}
@ -84,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;
@ -201,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)) {
@ -252,3 +280,204 @@ int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_
void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response) {
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_user_progress_request(rc_api_request_t* request,
const rc_api_fetch_all_user_progress_request_t* api_params)
{
return rc_api_init_fetch_all_user_progress_request_hosted(request, api_params, &g_host);
}
int rc_api_init_fetch_all_user_progress_request_hosted(rc_api_request_t* request,
const rc_api_fetch_all_user_progress_request_t* api_params,
const rc_api_host_t* host)
{
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request, host);
if (api_params->console_id == RC_CONSOLE_UNKNOWN)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "allprogress", api_params->username, api_params->api_token))
{
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_fetch_all_user_progress_server_response(rc_api_fetch_all_user_progress_response_t* response,
const rc_api_server_response_t* server_response)
{
rc_api_all_user_progress_entry_t* entry;
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Response"),
};
rc_json_field_t entry_fields[] = {
RC_JSON_NEW_FIELD("Achievements"),
RC_JSON_NEW_FIELD("Unlocked"),
RC_JSON_NEW_FIELD("UnlockedHardcore"),
};
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 (!fields[2].value_start) {
/* call rc_json_get_required_object to generate the error message */
rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response");
return RC_MISSING_VALUE;
}
response->num_entries = fields[2].array_size;
if (response->num_entries > 0) {
rc_json_iterator_t iterator;
rc_json_field_t field;
char* end;
rc_buffer_reserve(&response->response.buffer, response->num_entries * sizeof(rc_api_all_user_progress_entry_t));
response->entries = (rc_api_all_user_progress_entry_t*)rc_buffer_alloc(
&response->response.buffer, response->num_entries * sizeof(rc_api_all_user_progress_entry_t));
if (!response->entries)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = fields[2].value_start;
iterator.end = fields[2].value_end;
entry = response->entries;
while (rc_json_get_next_object_field(&iterator, &field))
{
entry->game_id = strtol(field.name, &end, 10);
field.name = "";
if (!rc_json_get_required_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]),
&response->response, &field, ""))
{
return RC_MISSING_VALUE;
}
rc_json_get_optional_unum(&entry->num_achievements, &entry_fields[0], "Achievements", 0);
rc_json_get_optional_unum(&entry->num_unlocked_achievements, &entry_fields[1], "Unlocked", 0);
rc_json_get_optional_unum(&entry->num_unlocked_achievements_hardcore, &entry_fields[2], "UnlockedHardcore", 0);
++entry;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_all_user_progress_response(rc_api_fetch_all_user_progress_response_t* response)
{
rc_buffer_destroy(&response->response.buffer);
}

File diff suppressed because it is too large Load Diff

261
deps/rcheevos/src/rc_client_external.c vendored Normal file
View File

@ -0,0 +1,261 @@
#include "rc_client_external.h"
#include "rc_client_external_versions.h"
#include "rc_client_internal.h"
#include "rc_api_runtime.h"
#define RC_CONVERSION_FILL(obj, obj_type, src_type) memset((uint8_t*)obj + sizeof(src_type), 0, sizeof(obj_type) - sizeof(src_type))
/* https://media.retroachievements.org/Badge/123456_lock.png is 58 with null terminator */
#define RC_CLIENT_IMAGE_URL_BUFFER_SIZE 64
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];
char user_avatar_url[RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
char game_badge_url[RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
char subset_badge_url[4][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
char achievement_badge_url[16][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
char achievement_badge_locked_url[16][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
uint32_t next_subset_index;
uint32_t next_achievement_index;
} rc_client_external_conversions_t;
static const char* rc_client_external_build_avatar_url(char buffer[], uint32_t image_type, const char* image_name)
{
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 = image_name;
result = rc_api_init_fetch_image_request(&request, &image_request);
if (result != RC_OK)
return NULL;
strcpy_s(buffer, RC_CLIENT_IMAGE_URL_BUFFER_SIZE, request.url);
return buffer;
}
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);
converted->avatar_url = rc_client_external_build_avatar_url(
client->state.external_client_conversions->user_avatar_url, RC_IMAGE_TYPE_USER, v1_user->username);
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);
converted->badge_url = rc_client_external_build_avatar_url(
client->state.external_client_conversions->game_badge_url, RC_IMAGE_TYPE_GAME, v1_game->badge_name);
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;
char* badge_url = 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];
badge_url = client->state.external_client_conversions->subset_badge_url[index];
break;
}
}
if (!converted) {
index = client->state.external_client_conversions->next_subset_index;
converted = &client->state.external_client_conversions->subsets[index];
badge_url = client->state.external_client_conversions->subset_badge_url[client->state.external_client_conversions->next_subset_index];
client->state.external_client_conversions->next_subset_index = (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);
converted->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_GAME, v1_subset->badge_name);
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;
char* badge_url = NULL;
char* badge_locked_url = 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];
badge_url = client->state.external_client_conversions->achievement_badge_url[index];
badge_locked_url = client->state.external_client_conversions->achievement_badge_locked_url[index];
break;
}
}
if (!converted) {
index = client->state.external_client_conversions->next_achievement_index;
converted = &client->state.external_client_conversions->achievements[index];
badge_url = client->state.external_client_conversions->achievement_badge_url[index];
badge_locked_url = client->state.external_client_conversions->achievement_badge_locked_url[index];
client->state.external_client_conversions->next_achievement_index = (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);
converted->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT, v1_achievement->badge_name);
converted->badge_locked_url = rc_client_external_build_avatar_url(badge_locked_url, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, v1_achievement->badge_name);
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;
char* badge_url_buffer;
} 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);
if (wrapper->badge_url_buffer)
free(wrapper->badge_url_buffer);
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;
char* badge_url = NULL;
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*));
new_list->badge_url_buffer = badge_url = (char*)malloc(num_achievements * 2 * RC_CLIENT_IMAGE_URL_BUFFER_SIZE);
if (!new_list->achievements || !new_list->achievements_pointers || !new_list->badge_url_buffer)
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));
(*achievement)->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT, (*achievement)->badge_name);
badge_url += RC_CLIENT_IMAGE_URL_BUFFER_SIZE;
(*achievement)->badge_locked_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, (*achievement)->badge_name);
badge_url += RC_CLIENT_IMAGE_URL_BUFFER_SIZE;
}
}
}
new_list->info.public_.num_buckets = v1_achievement_list->num_buckets;
}
return (rc_client_achievement_list_t*)new_list;
}

View File

@ -95,12 +95,12 @@ 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;
rc_client_external_begin_change_media_func_t begin_change_media;
rc_client_external_begin_load_game_func_t begin_change_media_from_hash;
rc_client_external_begin_change_media_func_t begin_identify_and_change_media;
rc_client_external_begin_load_game_func_t begin_change_media;
rc_client_external_create_achievement_list_func_t create_achievement_list;
rc_client_external_get_int_func_t has_achievements;
@ -129,13 +129,32 @@ 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;
/* VERSION 4 */
rc_client_external_set_int_func_t set_allow_background_memory_reads;
} rc_client_external_t;
#define RC_CLIENT_EXTERNAL_VERSION 2
#define RC_CLIENT_EXTERNAL_VERSION 4
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 */

View File

@ -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 */

View File

@ -9,6 +9,9 @@
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
#include "rc_client_external.h"
#endif
#ifdef RC_CLIENT_SUPPORTS_HASH
#include "rhash/rc_hash_internal.h"
#endif
#include "rc_compat.h"
#include "rc_runtime.h"
@ -20,9 +23,9 @@ RC_BEGIN_C_DECLS
| Callbacks |
\*****************************************************************************/
struct rc_api_fetch_game_data_response_t;
typedef void (RC_CCONV *rc_client_post_process_game_data_response_t)(const rc_api_server_response_t* server_response,
struct rc_api_fetch_game_data_response_t* game_data_response, rc_client_t* client, void* userdata);
struct rc_api_fetch_game_sets_response_t;
typedef void (RC_CCONV *rc_client_post_process_game_sets_response_t)(const rc_api_server_response_t* server_response,
struct rc_api_fetch_game_sets_response_t* game_sets_response, rc_client_t* client, void* userdata);
typedef int (RC_CCONV *rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client);
typedef int (RC_CCONV *rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client);
typedef int (RC_CCONV *rc_client_rich_presence_override_t)(rc_client_t* client, char buffer[], size_t buffersize);
@ -36,11 +39,15 @@ typedef struct rc_client_callbacks_t {
rc_client_message_callback_t log_call;
rc_get_time_millisecs_func_t get_time_millisecs;
rc_client_identify_hash_func_t identify_unknown_hash;
rc_client_post_process_game_data_response_t post_process_game_data_response;
rc_client_post_process_game_sets_response_t post_process_game_sets_response;
rc_client_can_submit_achievement_unlock_t can_submit_achievement_unlock;
rc_client_can_submit_leaderboard_entry_t can_submit_leaderboard_entry;
rc_client_rich_presence_override_t rich_presence_override;
#ifdef RC_CLIENT_SUPPORTS_HASH
rc_hash_callbacks_t hash;
#endif
void* client_data;
} rc_client_callbacks_t;
@ -215,14 +222,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 +306,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;
@ -319,6 +327,7 @@ typedef struct rc_client_state_t {
uint8_t user;
uint8_t disconnect;
uint8_t allow_leaderboards_in_softcore;
uint8_t allow_background_memory_reads;
struct rc_client_load_state_t* load;
struct rc_client_async_handle_t* async_handles[4];

View File

@ -6,6 +6,24 @@
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
/* ===== natvis extensions ===== */
typedef struct __rc_client_raintegration_event_enum_t { uint8_t value; } __rc_client_raintegration_event_enum_t;
static void rc_client_raintegration_natvis_helper(void)
{
struct natvis_extensions {
__rc_client_raintegration_event_enum_t raintegration_event_type;
} natvis;
natvis.raintegration_event_type.value = RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE;
natvis.raintegration_event_type.value = RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED;
natvis.raintegration_event_type.value = RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED;
natvis.raintegration_event_type.value = RC_CLIENT_RAINTEGRATION_EVENT_PAUSE;
natvis.raintegration_event_type.value = RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED;
}
/* ============================= */
static void rc_client_raintegration_load_dll(rc_client_t* client,
const wchar_t* search_directory, rc_client_callback_t callback, void* callback_userdata)
{
@ -89,6 +107,9 @@ static void rc_client_raintegration_load_dll(rc_client_t* client,
FreeLibrary(hDLL);
callback(RC_ABORTED, "One or more required exports was not found in RA_Integration.dll", client, callback_userdata);
/* dummy reference to natvis helper to ensure extensions get compiled in. */
raintegration->shutdown = rc_client_raintegration_natvis_helper;
}
else {
rc_mutex_lock(&client->state.mutex);
@ -151,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) {
@ -323,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);
@ -331,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);

View File

@ -1,5 +1,15 @@
#if !defined(RC_NO_THREADS) && !defined(_WIN32) && !defined(GEKKO) && !defined(_3DS) && (!defined(_XOPEN_SOURCE) || (_XOPEN_SOURCE - 0) < 500)
/* We'll want to use pthread_mutexattr_settype/PTHREAD_MUTEX_RECURSIVE, but glibc only conditionally exposes pthread_mutexattr_settype and PTHREAD_MUTEX_RECURSIVE depending on feature flags
* Defining _XOPEN_SOURCE must be done at the top of the source file, before including any headers
* pthread_mutexattr_settype/PTHREAD_MUTEX_RECURSIVE are specified the Single UNIX Specification (Version 2, 1997), along with POSIX later on (IEEE Standard 1003.1-2008), so should cover practically any pthread implementation
*/
#undef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif
#include "rc_compat.h"
#include <assert.h>
#include <ctype.h>
#include <stdarg.h>
@ -58,7 +68,7 @@ int rc_snprintf(char* buffer, size_t size, const char* format, ...)
va_start(args, format);
#ifdef __STDC_WANT_SECURE_LIB__
#ifdef __STDC_SECURE_LIB__
result = vsprintf_s(buffer, size, format, args);
#else
/* assume buffer is large enough and ignore size */
@ -73,7 +83,7 @@ int rc_snprintf(char* buffer, size_t size, const char* format, ...)
#endif
#ifndef __STDC_WANT_SECURE_LIB__
#ifndef __STDC_SECURE_LIB__
struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer)
{
@ -88,32 +98,79 @@ struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer)
#if defined(_WIN32)
/* https://gist.github.com/roxlu/1c1af99f92bafff9d8d9 */
/* https://learn.microsoft.com/en-us/archive/msdn-magazine/2012/november/windows-with-c-the-evolution-of-synchronization-in-windows-and-c */
/* implementation largely taken from https://github.com/libsdl-org/SDL/blob/0fc3574/src/thread/windows/SDL_sysmutex.c */
#if defined(WINVER) && WINVER >= 0x0600
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
void rc_mutex_init(rc_mutex_t* mutex)
{
/* default security, not owned by calling thread, unnamed */
mutex->handle = CreateMutex(NULL, FALSE, NULL);
InitializeSRWLock(&mutex->srw_lock);
/* https://learn.microsoft.com/en-us/windows/win32/procthread/thread-handles-and-identifiers */
/* thread ids are never 0 */
mutex->owner = 0;
mutex->count = 0;
}
void rc_mutex_destroy(rc_mutex_t* mutex)
{
CloseHandle(mutex->handle);
/* Nothing to do here */
(void)mutex;
}
void rc_mutex_lock(rc_mutex_t* mutex)
{
WaitForSingleObject(mutex->handle, 0xFFFFFFFF);
DWORD current_thread = GetCurrentThreadId();
if (mutex->owner == current_thread) {
++mutex->count;
assert(mutex->count > 0);
}
else {
AcquireSRWLockExclusive(&mutex->srw_lock);
assert(mutex->owner == 0 && mutex->count == 0);
mutex->owner = current_thread;
mutex->count = 1;
}
}
void rc_mutex_unlock(rc_mutex_t* mutex)
{
ReleaseMutex(mutex->handle);
if (mutex->owner == GetCurrentThreadId()) {
assert(mutex->count > 0);
if (--mutex->count == 0) {
mutex->owner = 0;
ReleaseSRWLockExclusive(&mutex->srw_lock);
}
}
else {
assert(!"Tried to unlock unowned mutex");
}
}
#else
void rc_mutex_init(rc_mutex_t* mutex)
{
InitializeCriticalSection(&mutex->critical_section);
}
void rc_mutex_destroy(rc_mutex_t* mutex)
{
DeleteCriticalSection(&mutex->critical_section);
}
void rc_mutex_lock(rc_mutex_t* mutex)
{
EnterCriticalSection(&mutex->critical_section);
}
void rc_mutex_unlock(rc_mutex_t* mutex)
{
LeaveCriticalSection(&mutex->critical_section);
}
#endif
#elif defined(GEKKO)
/* https://github.com/libretro/RetroArch/pull/16116 */
@ -167,7 +224,12 @@ void rc_mutex_unlock(rc_mutex_t* mutex)
void rc_mutex_init(rc_mutex_t* mutex)
{
pthread_mutex_init(mutex, NULL);
/* Define the mutex as recursive, for consistent semantics against other rc_mutex_t implementations */
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(mutex, &attr);
pthread_mutexattr_destroy(&attr);
}
void rc_mutex_destroy(rc_mutex_t* mutex)

View File

@ -1,6 +1,13 @@
#ifndef RC_COMPAT_H
#define RC_COMPAT_H
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif
#include "rc_export.h"
#include <stdio.h>
@ -58,7 +65,7 @@ RC_BEGIN_C_DECLS
#endif /* __STDC_VERSION__ < 199901L */
#ifndef __STDC_WANT_SECURE_LIB__
#ifndef __STDC_SECURE_LIB__
/* _CRT_SECURE_NO_WARNINGS redefinitions */
#define strcpy_s(dest, sz, src) strcpy(dest, src)
#define sscanf_s sscanf
@ -77,10 +84,19 @@ RC_BEGIN_C_DECLS
#define rc_mutex_lock(mutex)
#define rc_mutex_unlock(mutex)
#else
#ifdef _WIN32
typedef struct rc_mutex_t {
void* handle; /* HANDLE is defined as "void*" */
} rc_mutex_t;
#if defined(_WIN32)
typedef struct rc_mutex_t {
#if defined(WINVER) && WINVER >= 0x0600
/* Windows Vista and later can use a slim reader/writer (SRW) lock */
SRWLOCK srw_lock;
/* Current thread owner needs to be tracked (for recursive mutex usage) */
DWORD owner;
DWORD count;
#else
/* Pre-Vista must use a critical section */
CRITICAL_SECTION critical_section;
#endif
} rc_mutex_t;
#elif defined(GEKKO)
#include <ogcsys.h>
typedef struct rc_mutex_t {

View File

@ -10,20 +10,11 @@
#include "rc_consoles.h"
#include "rc_compat.h"
#include "rhash/rc_hash_internal.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.
@ -87,6 +78,7 @@ static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = {
};
static const rc_disallowed_setting_t _rc_disallowed_fceumm_settings[] = {
{ "fceumm_game_genie", "!disabled" },
{ "fceumm_region", ",PAL,Dendy" },
{ NULL, NULL }
};
@ -124,7 +116,7 @@ static const rc_disallowed_setting_t _rc_disallowed_neocd_settings[] = {
};
static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = {
{ "pcsx_rearmed_psxclock", "<55" },
{ "pcsx_rearmed_psxclock", ",!auto,<55" },
{ "pcsx_rearmed_region", "pal" },
{ NULL, NULL }
};
@ -202,14 +194,14 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
{ NULL, NULL }
};
static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char* value) {
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 = *value++) && c2 != '?')
if (tolower(c1) != tolower(c2 = *match++) && c2 != '?')
return (c2 == '*');
}
return (*value == '\0');
return (*match == '\0');
}
static int rc_libretro_numeric_less_than(const char* test, const char* value) {
@ -218,7 +210,50 @@ static int rc_libretro_numeric_less_than(const char* test, const char* 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 {
@ -229,33 +264,23 @@ static int rc_libretro_match_value(const char* val, const char* match) {
++match;
size = match - ptr;
if (val[size] == '\0') {
if (memcmp(ptr, val, size) == 0) {
return 1;
}
else {
char buffer[128];
memcpy(buffer, ptr, size);
buffer[size] = '\0';
if (rc_libretro_string_equal_nocase_wildcard(buffer, val))
return 1;
}
}
} while (*match == ',');
if (rc_libretro_match_token(val, ptr, size, &result))
return result;
return 0;
} 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;
}
/* a leading exclamation point means the provided value(s) are not forbidden (are allowed) */
if (*match == '!')
return !rc_libretro_match_value(val, &match[1]);
/* a leading less tahn means the provided value is the minimum allowed */
if (*match == '<')
return rc_libretro_numeric_less_than(val, &match[1]);
/* just a single value, attempt to match it */
return rc_libretro_string_equal_nocase_wildcard(val, match);
/* 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) {
@ -711,7 +736,8 @@ void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* 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) {
const char* m3u_path, rc_libretro_get_image_path_func get_image_path,
const rc_hash_filereader_t* file_reader) {
char image_path[1024];
char* m3u_contents;
char* ptr;
@ -724,23 +750,24 @@ void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
if (!rc_path_compare_extension(m3u_path, "m3u"))
return;
file_handle = rc_file_open(m3u_path);
file_handle = file_reader->open(m3u_path);
if (!file_handle) {
rc_hash_error("Could not open playlist");
rc_hash_iterator_t iterator;
memset(&iterator, 0, sizeof(iterator));
memcpy(&iterator.callbacks, &hash_set->callbacks, sizeof(hash_set->callbacks));
rc_hash_iterator_error(&iterator, "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);
file_reader->seek(file_handle, 0, SEEK_END);
file_len = file_reader->tell(file_handle);
file_reader->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);
file_reader->read(file_handle, m3u_contents, (int)file_len);
m3u_contents[file_len] = '\0';
rc_file_close(file_handle);
ptr = m3u_contents;
do
{
@ -774,6 +801,9 @@ void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
free(m3u_contents);
}
if (file_reader->close)
file_reader->close(file_handle);
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 */

View File

@ -3,6 +3,8 @@
#include "rc_export.h"
#include "rc_hash.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>
@ -75,12 +77,15 @@ typedef struct rc_libretro_hash_set_t
struct rc_libretro_hash_entry_t* entries;
uint16_t entries_count;
uint16_t entries_size;
rc_hash_callbacks_t callbacks;
} 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);
const char* m3u_path, rc_libretro_get_image_path_func get_image_path,
const rc_hash_filereader_t* file_reader);
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,

View File

@ -38,6 +38,9 @@ void rc_buffer_destroy(rc_buffer_t* buffer)
total += (int)(chunk->end - chunk->start);
wasted += (int)(chunk->end - chunk->write);
++count;
#endif
#ifdef DEBUG_BUFFERS
printf("< free %p.%p\n", (void*)buffer, (void*)chunk);
#endif
free(chunk);
chunk = next;
@ -70,6 +73,10 @@ uint8_t* rc_buffer_reserve(rc_buffer_t* buffer, size_t amount)
if (!chunk->next)
break;
#ifdef DEBUG_BUFFERS
printf("> alloc %p.%p\n", (void*)buffer, (void*)chunk->next);
#endif
chunk->next->start = (uint8_t*)chunk->next + chunk_header_size;
chunk->next->write = chunk->next->start;
chunk->next->end = (uint8_t*)chunk->next + alloc_size;
@ -148,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";
@ -186,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";
}
}

View File

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

View File

@ -35,11 +35,11 @@ void* rc_alloc(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment
if (pointer != 0) {
/* valid buffer, grab the next chunk */
ptr = (void*)((char*)pointer + *offset);
ptr = (void*)((uint8_t*)pointer + *offset);
}
else if (scratch != 0 && scratch_object_pointer_offset < sizeof(scratch->objs)) {
/* only allocate one instance of each object type (indentified by scratch_object_pointer_offset) */
void** scratch_object_pointer = (void**)((char*)&scratch->objs + scratch_object_pointer_offset);
void** scratch_object_pointer = (void**)((uint8_t*)&scratch->objs + scratch_object_pointer_offset);
ptr = *scratch_object_pointer;
if (!ptr) {
int32_t used;
@ -94,22 +94,216 @@ char* rc_alloc_str(rc_parse_state_t* parse, const char* text, size_t length) {
return ptr;
}
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx)
void rc_init_preparse_state(rc_preparse_state_t* preparse)
{
rc_init_parse_state(&preparse->parse, NULL);
rc_init_parse_state_memrefs(&preparse->parse, &preparse->memrefs);
}
void rc_destroy_preparse_state(rc_preparse_state_t* preparse)
{
rc_destroy_parse_state(&preparse->parse);
}
void rc_preparse_alloc_memrefs(rc_memrefs_t* memrefs, rc_preparse_state_t* preparse)
{
const uint32_t num_memrefs = rc_memrefs_count_memrefs(&preparse->memrefs);
const uint32_t num_modified_memrefs = rc_memrefs_count_modified_memrefs(&preparse->memrefs);
if (preparse->parse.offset < 0)
return;
if (memrefs) {
memset(memrefs, 0, sizeof(*memrefs));
preparse->parse.memrefs = memrefs;
}
if (num_memrefs) {
rc_memref_t* memref_items = RC_ALLOC_ARRAY(rc_memref_t, num_memrefs, &preparse->parse);
if (memrefs) {
memrefs->memrefs.capacity = num_memrefs;
memrefs->memrefs.items = memref_items;
}
}
if (num_modified_memrefs) {
rc_modified_memref_t* modified_memref_items =
RC_ALLOC_ARRAY(rc_modified_memref_t, num_modified_memrefs, &preparse->parse);
if (memrefs) {
memrefs->modified_memrefs.capacity = num_modified_memrefs;
memrefs->modified_memrefs.items = modified_memref_items;
}
}
/* when preparsing, this structure will be allocated at the end. when it's allocated earlier
* in the buffer, it could be followed by something aligned at 8 bytes. force the offset to
* an 8-byte boundary */
if (!memrefs) {
rc_alloc(preparse->parse.buffer, &preparse->parse.offset, 0, 8, &preparse->parse.scratch, 0);
}
}
static uint32_t rc_preparse_array_size(uint32_t needed, uint32_t minimum)
{
while (minimum < needed)
minimum <<= 1;
return minimum;
}
void rc_preparse_reserve_memrefs(rc_preparse_state_t* preparse, rc_memrefs_t* memrefs)
{
uint32_t num_memrefs = rc_memrefs_count_memrefs(&preparse->memrefs);
uint32_t num_modified_memrefs = rc_memrefs_count_modified_memrefs(&preparse->memrefs);
uint32_t available;
if (preparse->parse.offset < 0)
return;
if (num_memrefs) {
rc_memref_list_t* memref_list = &memrefs->memrefs;
while (memref_list->count == memref_list->capacity) {
if (!memref_list->next)
break;
memref_list = memref_list->next;
}
available = memref_list->capacity - memref_list->count;
if (available < num_memrefs) {
rc_memref_list_t* new_memref_list = (rc_memref_list_t*)calloc(1, sizeof(rc_memref_list_t));
if (!new_memref_list)
return;
new_memref_list->capacity = rc_preparse_array_size(num_memrefs - available, 16);
new_memref_list->items = (rc_memref_t*)malloc(new_memref_list->capacity * sizeof(rc_memref_t));
new_memref_list->allocated = 1;
memref_list->next = new_memref_list;
}
}
if (num_modified_memrefs) {
rc_modified_memref_list_t* modified_memref_list = &memrefs->modified_memrefs;
while (modified_memref_list->count == modified_memref_list->capacity) {
if (!modified_memref_list->next)
break;
modified_memref_list = modified_memref_list->next;
}
available = modified_memref_list->capacity - modified_memref_list->count;
if (available < num_modified_memrefs) {
rc_modified_memref_list_t* new_modified_memref_list = (rc_modified_memref_list_t*)calloc(1, sizeof(rc_modified_memref_list_t));
if (!new_modified_memref_list)
return;
new_modified_memref_list->capacity = rc_preparse_array_size(num_modified_memrefs - available, 8);
new_modified_memref_list->items = (rc_modified_memref_t*)malloc(new_modified_memref_list->capacity * sizeof(rc_modified_memref_t));
new_modified_memref_list->allocated = 1;
modified_memref_list->next = new_modified_memref_list;
}
}
preparse->parse.memrefs = memrefs;
}
static void rc_preparse_sync_operand(rc_operand_t* operand, rc_parse_state_t* parse, const rc_memrefs_t* memrefs)
{
if (rc_operand_is_memref(operand) || rc_operand_is_recall(operand)) {
const rc_memref_t* src_memref = operand->value.memref;
if (src_memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) {
const rc_modified_memref_list_t* modified_memref_list = &memrefs->modified_memrefs;
for (; modified_memref_list; modified_memref_list = modified_memref_list->next) {
const rc_modified_memref_t* modified_memref = modified_memref_list->items;
const rc_modified_memref_t* modified_memref_end = modified_memref + modified_memref_list->count;
for (; modified_memref < modified_memref_end; ++modified_memref) {
if ((const rc_modified_memref_t*)src_memref == modified_memref) {
rc_modified_memref_t* dst_modified_memref = rc_alloc_modified_memref(parse, modified_memref->memref.value.size,
&modified_memref->parent, modified_memref->modifier_type, &modified_memref->modifier);
operand->value.memref = &dst_modified_memref->memref;
return;
}
}
}
}
else {
const rc_memref_list_t* memref_list = &memrefs->memrefs;
for (; memref_list; memref_list = memref_list->next) {
const rc_memref_t* memref = memref_list->items;
const rc_memref_t* memref_end = memref + memref_list->count;
for (; memref < memref_end; ++memref) {
if (src_memref == memref) {
operand->value.memref = rc_alloc_memref(parse, memref->address, memref->value.size);
return;
}
}
}
}
}
}
void rc_preparse_copy_memrefs(rc_parse_state_t* parse, rc_memrefs_t* memrefs)
{
const rc_memref_list_t* memref_list = &memrefs->memrefs;
const rc_modified_memref_list_t* modified_memref_list = &memrefs->modified_memrefs;
for (; memref_list; memref_list = memref_list->next) {
const rc_memref_t* memref = memref_list->items;
const rc_memref_t* memref_end = memref + memref_list->count;
for (; memref < memref_end; ++memref)
rc_alloc_memref(parse, memref->address, memref->value.size);
}
for (; modified_memref_list; modified_memref_list = modified_memref_list->next) {
rc_modified_memref_t* modified_memref = modified_memref_list->items;
const rc_modified_memref_t* modified_memref_end = modified_memref + modified_memref_list->count;
for (; modified_memref < modified_memref_end; ++modified_memref) {
rc_preparse_sync_operand(&modified_memref->parent, parse, memrefs);
rc_preparse_sync_operand(&modified_memref->modifier, parse, memrefs);
rc_alloc_modified_memref(parse, modified_memref->memref.value.size,
&modified_memref->parent, modified_memref->modifier_type, &modified_memref->modifier);
}
}
}
void rc_reset_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 */
parse->offset = 0;
parse->L = L;
parse->funcs_ndx = funcs_ndx;
parse->buffer = buffer;
parse->scratch.strings = NULL;
rc_buffer_init(&parse->scratch.buffer);
memset(&parse->scratch.objs, 0, sizeof(parse->scratch.objs));
parse->first_memref = 0;
parse->variables = 0;
parse->offset = 0;
parse->memrefs = NULL;
parse->existing_memrefs = NULL;
parse->variables = NULL;
parse->measured_target = 0;
parse->lines_read = 0;
parse->addsource_oper = RC_OPERATOR_NONE;
parse->addsource_parent.type = RC_OPERAND_NONE;
parse->indirect_parent.type = RC_OPERAND_NONE;
parse->remember.type = RC_OPERAND_NONE;
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)
{
/* 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);
}
void rc_destroy_parse_state(rc_parse_state_t* parse)

View File

@ -1,6 +1,7 @@
#include "rc_internal.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
static int rc_test_condition_compare(uint32_t value1, uint32_t value2, uint8_t oper) {
@ -15,7 +16,7 @@ static int rc_test_condition_compare(uint32_t value1, uint32_t value2, uint8_t o
}
}
static char rc_condition_determine_comparator(const rc_condition_t* self) {
static uint8_t rc_condition_determine_comparator(const rc_condition_t* self) {
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_NE:
@ -31,7 +32,8 @@ static char rc_condition_determine_comparator(const rc_condition_t* self) {
}
if ((self->operand1.type == RC_OPERAND_ADDRESS || self->operand1.type == RC_OPERAND_DELTA) &&
!self->operand1.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand1)) {
/* TODO: allow modified memref comparisons */
self->operand1.value.memref->value.memref_type == RC_MEMREF_TYPE_MEMREF && !rc_operand_is_float(&self->operand1)) {
/* left side is an integer memory reference */
int needs_translate = (self->operand1.size != self->operand1.value.memref->value.size);
@ -43,7 +45,7 @@ static char rc_condition_determine_comparator(const rc_condition_t* self) {
return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_CONST;
}
else if ((self->operand2.type == RC_OPERAND_ADDRESS || self->operand2.type == RC_OPERAND_DELTA) &&
!self->operand2.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand2)) {
self->operand2.value.memref->value.memref_type == RC_MEMREF_TYPE_MEMREF && !rc_operand_is_float(&self->operand2)) {
/* right side is an integer memory reference */
const int is_same_memref = (self->operand1.value.memref == self->operand2.value.memref);
needs_translate |= (self->operand2.size != self->operand2.value.memref->value.size);
@ -161,17 +163,45 @@ static int rc_parse_operator(const char** memaddr) {
}
}
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect) {
rc_condition_t* self;
void rc_condition_convert_to_operand(const rc_condition_t* condition, rc_operand_t* operand, rc_parse_state_t* parse) {
if (condition->oper == RC_OPERATOR_NONE) {
if (operand != &condition->operand1)
memcpy(operand, &condition->operand1, sizeof(*operand));
}
else {
uint8_t new_size = RC_MEMSIZE_32_BITS;
if (rc_operand_is_float(&condition->operand1) || rc_operand_is_float(&condition->operand2))
new_size = RC_MEMSIZE_FLOAT;
/* NOTE: this makes the operand include the modification, but we have to also
* leave the modification in the condition so the condition reflects the actual
* definition. This doesn't affect the evaluation logic since this method is only
* called for combining conditions and Measured, and the Measured handling function
* ignores the operator assuming it's been handled by a modified memref chain */
operand->value.memref = (rc_memref_t*)rc_alloc_modified_memref(parse,
new_size, &condition->operand1, condition->oper, &condition->operand2);
/* not actually an address, just a non-delta memref read */
operand->type = operand->memref_access_type = RC_OPERAND_ADDRESS;
operand->size = new_size;
}
}
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse) {
rc_condition_t * self = RC_ALLOC(rc_condition_t, parse);
rc_parse_condition_internal(self, memaddr, parse);
return (parse->offset < 0) ? NULL : self;
}
void rc_parse_condition_internal(rc_condition_t* self, const char** memaddr, rc_parse_state_t* parse) {
const char* aux;
int result;
int can_modify = 0;
aux = *memaddr;
self = RC_ALLOC(rc_condition_t, parse);
self->current_hits = 0;
self->is_true = 0;
self->pause = 0;
self->optimized_comparator = RC_PROCESSING_COMPARE_DEFAULT;
if (*aux != 0 && aux[1] == ':') {
@ -194,8 +224,11 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
parse->measured_as_percent = 1;
self->type = RC_CONDITION_MEASURED;
break;
/* e f h j l s u v w x y */
default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0;
default:
parse->offset = RC_INVALID_CONDITION_TYPE;
return;
}
aux += 2;
@ -204,79 +237,65 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
self->type = RC_CONDITION_STANDARD;
}
result = rc_parse_operand(&self->operand1, &aux, is_indirect, parse);
result = rc_parse_operand(&self->operand1, &aux, parse);
if (result < 0) {
parse->offset = result;
return 0;
return;
}
result = rc_parse_operator(&aux);
if (result < 0) {
parse->offset = result;
return 0;
return;
}
self->oper = (char)result;
switch (self->oper) {
case RC_OPERATOR_NONE:
/* 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) {
parse->offset = RC_INVALID_OPERATOR;
return 0;
}
self->oper = (uint8_t)result;
if (self->oper == RC_OPERATOR_NONE) {
/* 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 && !parse->ignore_non_parse_errors) {
parse->offset = RC_INVALID_OPERATOR;
return;
}
}
/* provide dummy operand of '1' and no required hits */
self->operand2.type = RC_OPERAND_CONST;
self->operand2.value.num = 1;
self->required_hits = 0;
*memaddr = aux;
return self;
/* provide dummy operand of '1' and no required hits */
rc_operand_set_const(&self->operand2, 1);
self->required_hits = 0;
*memaddr = aux;
return;
}
case RC_OPERATOR_MULT:
case RC_OPERATOR_DIV:
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_MOD:
case RC_OPERATOR_ADD:
case RC_OPERATOR_SUB:
/* modifying operators are only valid on modifying statements */
if (can_modify)
if (can_modify && !rc_operator_is_modifying(self->oper)) {
/* comparison operators are not valid on modifying statements */
switch (self->type) {
case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_SUB_SOURCE:
case RC_CONDITION_ADD_ADDRESS:
/* prevent parse errors on legacy achievements where a condition was present before changing the type */
self->oper = RC_OPERATOR_NONE;
break;
/* fallthrough */
default:
/* comparison operators are not valid on modifying statements */
if (can_modify) {
switch (self->type) {
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 0;
default:
if (!parse->ignore_non_parse_errors) {
parse->offset = RC_INVALID_OPERATOR;
return;
}
}
break;
break;
}
}
result = rc_parse_operand(&self->operand2, &aux, is_indirect, parse);
result = rc_parse_operand(&self->operand2, &aux, parse);
if (result < 0) {
parse->offset = result;
return 0;
return;
}
if (self->oper == RC_OPERATOR_NONE) {
/* if operator is none, explicitly clear out the right side */
self->operand2.type = RC_OPERAND_CONST;
self->operand2.value.num = 0;
rc_operand_set_const(&self->operand2, 0);
}
if (*aux == '(') {
@ -285,7 +304,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
if (end == aux || *end != ')') {
parse->offset = RC_INVALID_REQUIRED_HITS;
return 0;
return;
}
/* if operator is none, explicitly clear out the required hits */
@ -302,7 +321,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
if (end == aux || *end != '.') {
parse->offset = RC_INVALID_REQUIRED_HITS;
return 0;
return;
}
/* if operator is none, explicitly clear out the required hits */
@ -321,7 +340,185 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
self->optimized_comparator = rc_condition_determine_comparator(self);
*memaddr = aux;
return self;
}
void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t* parse) {
/* type of values in the chain are determined by the parent.
* the last element of a chain is determined by the operand
*
* 1 + 1.5 + 1.75 + 1.0 => (int)1 + (int)1 + (int)1 + (float)1 = (float)4.0
* 1.0 + 1.5 + 1.75 + 1.0 => (float)1.0 + (float)1.5 + (float)1.75 + (float)1.0 = (float)5.25
* 1.0 + 1.5 + 1.75 + 1 => (float)1.0 + (float)1.5 + (float)1.75 + (int)1 = (int)5
*/
switch (condition->type) {
case RC_CONDITION_ADD_ADDRESS:
if (condition->oper != RC_OPERAND_NONE)
rc_condition_convert_to_operand(condition, &parse->indirect_parent, parse);
else
memcpy(&parse->indirect_parent, &condition->operand1, sizeof(parse->indirect_parent));
break;
case RC_CONDITION_ADD_SOURCE:
if (parse->addsource_parent.type == RC_OPERAND_NONE) {
rc_condition_convert_to_operand(condition, &parse->addsource_parent, parse);
}
else {
rc_operand_t cond_operand;
/* type determined by parent */
const uint8_t new_size = rc_operand_is_float(&parse->addsource_parent) ? RC_MEMSIZE_FLOAT : RC_MEMSIZE_32_BITS;
rc_condition_convert_to_operand(condition, &cond_operand, parse);
rc_operand_addsource(&cond_operand, parse, new_size);
memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand));
}
parse->addsource_oper = RC_OPERATOR_ADD;
parse->indirect_parent.type = RC_OPERAND_NONE;
break;
case RC_CONDITION_SUB_SOURCE:
if (parse->addsource_parent.type == RC_OPERAND_NONE) {
rc_condition_convert_to_operand(condition, &parse->addsource_parent, parse);
parse->addsource_oper = RC_OPERATOR_SUB_PARENT;
}
else {
rc_operand_t cond_operand;
/* type determined by parent */
const uint8_t new_size = rc_operand_is_float(&parse->addsource_parent) ? RC_MEMSIZE_FLOAT : RC_MEMSIZE_32_BITS;
if (parse->addsource_oper == RC_OPERATOR_ADD && !rc_operand_is_memref(&parse->addsource_parent)) {
/* if the previous element was a constant we have to turn it into a memref by adding zero */
rc_modified_memref_t* memref;
rc_operand_t zero;
rc_operand_set_const(&zero, 0);
memref = rc_alloc_modified_memref(parse,
parse->addsource_parent.size, &parse->addsource_parent, RC_OPERATOR_ADD, &zero);
parse->addsource_parent.value.memref = (rc_memref_t*)memref;
parse->addsource_parent.type = RC_OPERAND_ADDRESS;
}
else if (parse->addsource_oper == RC_OPERATOR_SUB_PARENT) {
/* if the previous element was also a SubSource, we have to insert a 0 and start subtracting from there */
rc_modified_memref_t* negate;
rc_operand_t zero;
if (rc_operand_is_float(&parse->addsource_parent))
rc_operand_set_float_const(&zero, 0.0);
else
rc_operand_set_const(&zero, 0);
negate = rc_alloc_modified_memref(parse, new_size, &parse->addsource_parent, RC_OPERATOR_SUB_PARENT, &zero);
parse->addsource_parent.value.memref = (rc_memref_t*)negate;
parse->addsource_parent.size = zero.size;
}
/* subtract the condition from the chain */
parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB : RC_OPERATOR_SUB_PARENT;
rc_condition_convert_to_operand(condition, &cond_operand, parse);
rc_operand_addsource(&cond_operand, parse, new_size);
memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand));
/* indicate the next value can be added to the chain */
parse->addsource_oper = RC_OPERATOR_ADD;
}
parse->indirect_parent.type = RC_OPERAND_NONE;
break;
case RC_CONDITION_REMEMBER:
rc_condition_convert_to_operand(condition, &condition->operand1, parse);
if (parse->addsource_parent.type != RC_OPERAND_NONE) {
/* type determined by leaf */
rc_operand_addsource(&condition->operand1, parse, condition->operand1.size);
condition->operand1.is_combining = 1;
}
memcpy(&parse->remember, &condition->operand1, sizeof(parse->remember));
parse->addsource_parent.type = RC_OPERAND_NONE;
parse->indirect_parent.type = RC_OPERAND_NONE;
break;
case RC_CONDITION_MEASURED:
/* Measured condition can have modifiers in values */
if (parse->is_value) {
switch (condition->oper) {
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_DIV:
case RC_OPERATOR_MULT:
case RC_OPERATOR_MOD:
case RC_OPERATOR_ADD:
case RC_OPERATOR_SUB:
rc_condition_convert_to_operand(condition, &condition->operand1, parse);
break;
default:
break;
}
}
/* fallthrough */ /* to default */
default:
if (parse->addsource_parent.type != RC_OPERAND_NONE) {
/* type determined by leaf */
rc_operand_addsource(&condition->operand1, parse, condition->operand1.size);
condition->operand1.is_combining = 1;
if (parse->buffer)
condition->optimized_comparator = rc_condition_determine_comparator(condition);
}
parse->addsource_parent.type = RC_OPERAND_NONE;
parse->indirect_parent.type = RC_OPERAND_NONE;
break;
}
}
static const rc_modified_memref_t* rc_operand_get_modified_memref(const rc_operand_t* operand) {
if (!rc_operand_is_memref(operand))
return NULL;
if (operand->value.memref->value.memref_type != RC_MEMREF_TYPE_MODIFIED_MEMREF)
return NULL;
return (rc_modified_memref_t*)operand->value.memref;
}
/* rc_condition_update_parse_state will mutate the operand1 to point at the modified memref
* containing the accumulated result up until that point. this function returns the original
* unmodified operand1 from parsing the definition.
*/
const rc_operand_t* rc_condition_get_real_operand1(const rc_condition_t* self) {
const rc_operand_t* operand = &self->operand1;
const rc_modified_memref_t* modified_memref;
if (operand->is_combining) {
/* operand = "previous + current" - extract current */
const rc_modified_memref_t* combining_modified_memref = rc_operand_get_modified_memref(operand);
if (combining_modified_memref)
operand = &combining_modified_memref->modifier;
}
/* modifying operators are merged into an rc_modified_memref_t
* if operand1 is a modified memref, assume it's been merged with the right side and
* extract the parent which is the actual operand1. */
modified_memref = rc_operand_get_modified_memref(operand);
if (modified_memref) {
if (modified_memref->modifier_type == RC_OPERATOR_INDIRECT_READ) {
/* if the modified memref is an indirect read, the parent is the indirect
* address and the modifier is the offset. the actual size and address are
* stored in the modified memref - use it */
} else if (rc_operator_is_modifying(self->oper) && self->oper != RC_OPERATOR_NONE) {
/* operand = "parent*modifier" - extract parent.modifier will already be in operand2 */
operand = &modified_memref->parent;
}
}
return operand;
}
int rc_condition_is_combining(const rc_condition_t* self) {
@ -500,41 +697,35 @@ static int rc_test_condition_compare_delta_to_memref_transformed(rc_condition_t*
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) {
rc_typed_value_t value1, value2;
if (eval_state->add_value.type != RC_VALUE_TYPE_NONE) {
/* if there's an accumulator, we can't use the optimized comparators */
rc_evaluate_operand(&value1, &self->operand1, eval_state);
rc_typed_value_add(&value1, &eval_state->add_value);
} else {
/* use an optimized comparator whenever possible */
switch (self->optimized_comparator) {
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST:
return rc_test_condition_compare_memref_to_const(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA:
return rc_test_condition_compare_memref_to_delta(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF:
return rc_test_condition_compare_memref_to_memref(self);
case RC_PROCESSING_COMPARE_DELTA_TO_CONST:
return rc_test_condition_compare_delta_to_const(self);
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF:
return rc_test_condition_compare_delta_to_memref(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED:
return rc_test_condition_compare_memref_to_const_transformed(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED:
return rc_test_condition_compare_memref_to_delta_transformed(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED:
return rc_test_condition_compare_memref_to_memref_transformed(self);
case RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED:
return rc_test_condition_compare_delta_to_const_transformed(self);
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED:
return rc_test_condition_compare_delta_to_memref_transformed(self);
case RC_PROCESSING_COMPARE_ALWAYS_TRUE:
return 1;
case RC_PROCESSING_COMPARE_ALWAYS_FALSE:
return 0;
default:
rc_evaluate_operand(&value1, &self->operand1, eval_state);
break;
}
/* use an optimized comparator whenever possible */
switch (self->optimized_comparator) {
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST:
return rc_test_condition_compare_memref_to_const(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA:
return rc_test_condition_compare_memref_to_delta(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF:
return rc_test_condition_compare_memref_to_memref(self);
case RC_PROCESSING_COMPARE_DELTA_TO_CONST:
return rc_test_condition_compare_delta_to_const(self);
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF:
return rc_test_condition_compare_delta_to_memref(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED:
return rc_test_condition_compare_memref_to_const_transformed(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED:
return rc_test_condition_compare_memref_to_delta_transformed(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED:
return rc_test_condition_compare_memref_to_memref_transformed(self);
case RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED:
return rc_test_condition_compare_delta_to_const_transformed(self);
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED:
return rc_test_condition_compare_delta_to_memref_transformed(self);
case RC_PROCESSING_COMPARE_ALWAYS_TRUE:
return 1;
case RC_PROCESSING_COMPARE_ALWAYS_FALSE:
return 0;
default:
rc_evaluate_operand(&value1, &self->operand1, eval_state);
break;
}
rc_evaluate_operand(&value2, &self->operand2, eval_state);
@ -548,38 +739,5 @@ void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self,
rc_evaluate_operand(value, &self->operand1, eval_state);
rc_evaluate_operand(&amount, &self->operand2, eval_state);
switch (self->oper) {
case RC_OPERATOR_MULT:
rc_typed_value_multiply(value, &amount);
break;
case RC_OPERATOR_DIV:
rc_typed_value_divide(value, &amount);
break;
case RC_OPERATOR_AND:
rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED);
rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED);
value->value.u32 &= amount.value.u32;
break;
case RC_OPERATOR_XOR:
rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED);
rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED);
value->value.u32 ^= amount.value.u32;
break;
case RC_OPERATOR_MOD:
rc_typed_value_modulus(value, &amount);
break;
case RC_OPERATOR_ADD:
rc_typed_value_add(value, &amount);
break;
case RC_OPERATOR_SUB:
rc_typed_value_negate(&amount);
rc_typed_value_add(value, &amount);
break;
}
rc_typed_value_combine(value, &amount, self->oper);
}

File diff suppressed because it is too large Load Diff

View File

@ -72,6 +72,9 @@ const char* rc_console_name(uint32_t console_id)
case RC_CONSOLE_FAIRCHILD_CHANNEL_F:
return "Fairchild Channel F";
case RC_CONSOLE_FAMICOM_DISK_SYSTEM:
return "Famicom Disk System";
case RC_CONSOLE_FM_TOWNS:
return "FM Towns";
@ -380,14 +383,18 @@ static const rc_memory_regions_t rc_memory_regions_colecovision = { _rc_memory_r
/* ===== Commodore 64 ===== */
/* https://www.c64-wiki.com/wiki/Memory_Map */
/* https://sta.c64.org/cbm64mem.html */
/* NOTE: Several blocks of C64 memory can be bank-switched for ROM data (see https://www.c64-wiki.com/wiki/Bank_Switching).
* Achievement triggers rely on values changing, so we don't really need to look at the ROM data.
* The achievement logic assumes the RAM data is always present in the bankable blocks. As such,
* clients providing memory to achievements should always return the RAM values at the queried address. */
static const rc_memory_region_t _rc_memory_regions_c64[] = {
{ 0x000000U, 0x0003FFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" },
{ 0x000400U, 0x0007FFU, 0x000400U, RC_MEMORY_TYPE_VIDEO_RAM, "Screen RAM" },
{ 0x000800U, 0x009FFFU, 0x000800U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* BASIC Program Storage Area */
{ 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Machine Language Storage Area / BASIC ROM Area */
{ 0x00C000U, 0x00CFFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Machine Language Storage Area */
{ 0x00D000U, 0x00DFFFU, 0x00D000U, RC_MEMORY_TYPE_SYSTEM_RAM, "I/O Area" }, /* also Character ROM */
{ 0x00E000U, 0x00FFFFU, 0x00E000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Machine Language Storage Area / Kernal ROM */
{ 0x000800U, 0x009FFFU, 0x000800U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* BASIC area. $8000-$9FFF can bank to cartridge ROM */
{ 0x00A000U, 0x00BFFFU, 0x00A000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* can bank to BASIC ROM or cartridge ROM */
{ 0x00C000U, 0x00CFFFU, 0x00C000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x00D000U, 0x00DFFFU, 0x00D000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* can bank to I/O Area or character ROM */
{ 0x00E000U, 0x00FFFFU, 0x00E000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* can bank to kernel ROM */
};
static const rc_memory_regions_t rc_memory_regions_c64 = { _rc_memory_regions_c64, 7 };
@ -423,6 +430,24 @@ static const rc_memory_region_t _rc_memory_regions_fairchild_channel_f[] = {
};
static const rc_memory_regions_t rc_memory_regions_fairchild_channel_f = { _rc_memory_regions_fairchild_channel_f, 4 };
/* ===== Famicon Disk System ===== */
/* https://fms.komkon.org/EMUL8/NES.html */
static const rc_memory_region_t _rc_memory_regions_famicom_disk_system[] = {
{ 0x0000U, 0x07FFU, 0x0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x0800U, 0x0FFFU, 0x0000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirror RAM" }, /* duplicates memory from $0000-$07FF */
{ 0x1000U, 0x17FFU, 0x0000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirror RAM" }, /* duplicates memory from $0000-$07FF */
{ 0x1800U, 0x1FFFU, 0x0000U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirror RAM" }, /* duplicates memory from $0000-$07FF */
{ 0x2000U, 0x2007U, 0x2000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "PPU Register" },
{ 0x2008U, 0x3FFFU, 0x2008U, RC_MEMORY_TYPE_VIRTUAL_RAM, "Mirrored PPU Register" }, /* repeats every 8 bytes */
{ 0x4000U, 0x4017U, 0x4000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "APU and I/O register" },
{ 0x4018U, 0x401FU, 0x4018U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "APU and I/O test register" },
{ 0x4020U, 0x40FFU, 0x4020U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "FDS I/O registers"},
{ 0x4100U, 0x5FFFU, 0x4100U, RC_MEMORY_TYPE_READONLY, "Cartridge data"}, /* varies by mapper */
{ 0x6000U, 0xDFFFU, 0x6000U, RC_MEMORY_TYPE_SYSTEM_RAM, "FDS RAM"},
{ 0xE000U, 0xFFFFU, 0xE000U, RC_MEMORY_TYPE_READONLY, "FDS BIOS ROM"},
};
static const rc_memory_regions_t rc_memory_regions_famicom_disk_system = { _rc_memory_regions_famicom_disk_system, 12 };
/* ===== GameBoy / MegaDuck ===== */
static const rc_memory_region_t _rc_memory_regions_gameboy[] = {
{ 0x000000U, 0x0000FFU, 0x000000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Interrupt vector" },
@ -694,27 +719,16 @@ static const rc_memory_region_t _rc_memory_regions_nes[] = {
{ 0x4020U, 0x5FFFU, 0x4020U, RC_MEMORY_TYPE_READONLY, "Cartridge data"}, /* varies by mapper */
{ 0x6000U, 0x7FFFU, 0x6000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM"},
{ 0x8000U, 0xFFFFU, 0x8000U, RC_MEMORY_TYPE_READONLY, "Cartridge ROM"},
/* NOTE: these are the correct mappings for FDS: https://fms.komkon.org/EMUL8/NES.html
* 0x6000-0xDFFF is RAM on the FDS system and 0xE000-0xFFFF is FDS BIOS.
* If the core implements a memory map, we should still be able to translate the addresses
* correctly as we only use the classifications when a memory map is not provided
{ 0x4020U, 0x40FFU, 0x4020U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "FDS I/O registers"},
{ 0x4100U, 0x5FFFU, 0x4100U, RC_MEMORY_TYPE_READONLY, "Cartridge data"}, // varies by mapper
{ 0x6000U, 0xDFFFU, 0x6000U, RC_MEMORY_TYPE_SYSTEM_RAM, "FDS RAM"},
{ 0xE000U, 0xFFFFU, 0xE000U, RC_MEMORY_TYPE_READONLY, "FDS BIOS ROM"},
*/
};
static const rc_memory_regions_t rc_memory_regions_nes = { _rc_memory_regions_nes, 11 };
/* ===== Nintendo 64 ===== */
/* https://raw.githubusercontent.com/mikeryan/n64dev/master/docs/n64ops/n64ops%23h.txt */
/* https://n64brew.dev/wiki/Memory_map#Virtual_Memory_Map */
static const rc_memory_region_t _rc_memory_regions_n64[] = {
{ 0x000000U, 0x1FFFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 1 */
{ 0x200000U, 0x3FFFFFU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 2 */
{ 0x400000U, 0x7FFFFFU, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } /* expansion pak - cannot find any details for real address */
{ 0x000000U, 0x1FFFFFU, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 1 */
{ 0x200000U, 0x3FFFFFU, 0x80200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* RDRAM 2 */
{ 0x400000U, 0x7FFFFFU, 0x80400000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } /* expansion pak */
};
static const rc_memory_regions_t rc_memory_regions_n64 = { _rc_memory_regions_n64, 3 };
@ -820,12 +834,13 @@ static const rc_memory_region_t _rc_memory_regions_pokemini[] = {
static const rc_memory_regions_t rc_memory_regions_pokemini = { _rc_memory_regions_pokemini, 2 };
/* ===== Sega CD ===== */
/* https://en.wikibooks.org/wiki/Genesis_Programming#MegaCD_Changes */
/* https://en.wikibooks.org/wiki/Genesis_Programming/68K_Memory_map/ */
static const rc_memory_region_t _rc_memory_regions_segacd[] = {
{ 0x000000U, 0x00FFFFU, 0x00FF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "68000 RAM" },
{ 0x010000U, 0x08FFFFU, 0x80020000U, RC_MEMORY_TYPE_SAVE_RAM, "CD PRG RAM" } /* normally banked into $020000-$03FFFF */
{ 0x010000U, 0x08FFFFU, 0x80020000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD PRG RAM" }, /* normally banked into $020000-$03FFFF */
{ 0x090000U, 0x0AFFFFU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD WORD RAM" }
};
static const rc_memory_regions_t rc_memory_regions_segacd = { _rc_memory_regions_segacd, 2 };
static const rc_memory_regions_t rc_memory_regions_segacd = { _rc_memory_regions_segacd, 3 };
/* ===== Sega Saturn ===== */
/* https://segaretro.org/Sega_Saturn_hardware_notes_(2004-04-27) */
@ -868,10 +883,18 @@ static const rc_memory_regions_t rc_memory_regions_scv = { _rc_memory_regions_sc
/* ===== Super Nintendo ===== */
/* https://en.wikibooks.org/wiki/Super_NES_Programming/SNES_memory_map#LoROM */
static const rc_memory_region_t _rc_memory_regions_snes[] = {
{ 0x000000U, 0x01FFFFU, 0x7E0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
{ 0x020000U, 0x03FFFFU, 0xFE0000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" }
{ 0x000000U, 0x01FFFFU, 0x07E0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
/* Cartridge RAM here could be in a variety of places in SNES memory, depending on the ROM type.
* Due to this, we place Cartridge RAM outside of the possible native addressing space.
* Note that this also covers SA-1 BW-RAM (which is exposed as RETRO_MEMORY_SAVE_RAM for libretro).
*/
{ 0x020000U, 0x09FFFFU, 0x1000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" },
/* I-RAM on the SA-1 is normally at 0x003000. However, this address typically just has a mirror of System RAM for other ROM types.
* To avoid conflicts, don't use 0x003000, instead map it outside of the possible native addressing space.
*/
{ 0x0A0000U, 0x0A07FFU, 0x1080000U, RC_MEMORY_TYPE_SYSTEM_RAM, "I-RAM (SA-1)" }
};
static const rc_memory_regions_t rc_memory_regions_snes = { _rc_memory_regions_snes, 2 };
static const rc_memory_regions_t rc_memory_regions_snes = { _rc_memory_regions_snes, 3 };
/* ===== Thomson TO8 ===== */
/* https://github.com/mamedev/mame/blob/master/src/mame/drivers/thomson.cpp#L1617 */
@ -1046,7 +1069,10 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id)
case RC_CONSOLE_FAIRCHILD_CHANNEL_F:
return &rc_memory_regions_fairchild_channel_f;
case RC_CONSOLE_FAMICOM_DISK_SYSTEM:
return &rc_memory_regions_famicom_disk_system;
case RC_CONSOLE_GAMEBOY:
return &rc_memory_regions_gameboy;

View File

@ -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) {

View File

@ -29,7 +29,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
if (*memaddr && *memaddr != ':') {
found |= RC_LBOARD_START;
rc_parse_trigger_internal(&self->start, &memaddr, parse);
self->start.memrefs = 0;
}
}
else if ((memaddr[0] == 'c' || memaddr[0] == 'C') &&
@ -44,7 +43,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
if (*memaddr && *memaddr != ':') {
found |= RC_LBOARD_CANCEL;
rc_parse_trigger_internal(&self->cancel, &memaddr, parse);
self->cancel.memrefs = 0;
}
}
else if ((memaddr[0] == 's' || memaddr[0] == 'S') &&
@ -59,7 +57,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
if (*memaddr && *memaddr != ':') {
found |= RC_LBOARD_SUBMIT;
rc_parse_trigger_internal(&self->submit, &memaddr, parse);
self->submit.memrefs = 0;
}
}
else if ((memaddr[0] == 'v' || memaddr[0] == 'V') &&
@ -74,7 +71,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
if (*memaddr && *memaddr != ':') {
found |= RC_LBOARD_VALUE;
rc_parse_value_internal(&self->value, &memaddr, parse);
self->value.memrefs = 0;
}
}
else if ((memaddr[0] == 'p' || memaddr[0] == 'P') &&
@ -91,7 +87,6 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
self->progress = RC_ALLOC(rc_value_t, parse);
rc_parse_value_internal(self->progress, &memaddr, parse);
self->progress->memrefs = 0;
}
}
@ -130,52 +125,66 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
}
self->state = RC_LBOARD_STATE_WAITING;
self->has_memrefs = 0;
}
int rc_lboard_size(const char* memaddr) {
rc_lboard_t* self;
rc_parse_state_t parse;
rc_memref_t* first_memref;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &first_memref);
rc_lboard_with_memrefs_t* lboard;
rc_preparse_state_t preparse;
rc_init_preparse_state(&preparse);
self = RC_ALLOC(rc_lboard_t, &parse);
rc_parse_lboard_internal(self, memaddr, &parse);
lboard = RC_ALLOC(rc_lboard_with_memrefs_t, &preparse.parse);
rc_parse_lboard_internal(&lboard->lboard, memaddr, &preparse.parse);
rc_preparse_alloc_memrefs(NULL, &preparse);
rc_destroy_parse_state(&parse);
return parse.offset;
rc_destroy_preparse_state(&preparse);
return preparse.parse.offset;
}
rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
rc_lboard_t* self;
rc_parse_state_t parse;
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_parse_state(&parse, buffer, 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);
self = RC_ALLOC(rc_lboard_t, &parse);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_reset_parse_state(&preparse.parse, buffer);
lboard = RC_ALLOC(rc_lboard_with_memrefs_t, &preparse.parse);
rc_preparse_alloc_memrefs(&lboard->memrefs, &preparse);
rc_parse_lboard_internal(self, memaddr, &parse);
rc_parse_lboard_internal(&lboard->lboard, memaddr, &preparse.parse);
lboard->lboard.has_memrefs = 1;
rc_destroy_parse_state(&parse);
return (parse.offset >= 0) ? self : 0;
rc_destroy_preparse_state(&preparse);
return (preparse.parse.offset >= 0) ? &lboard->lboard : NULL;
}
int rc_evaluate_lboard(rc_lboard_t* self, int32_t* value, rc_peek_t peek, void* peek_ud, lua_State* L) {
static void rc_update_lboard_memrefs(rc_lboard_t* self, rc_peek_t peek, void* ud) {
if (self->has_memrefs) {
rc_lboard_with_memrefs_t* lboard = (rc_lboard_with_memrefs_t*)self;
rc_update_memref_values(&lboard->memrefs, peek, ud);
}
}
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_memref_values(self->memrefs, peek, peek_ud);
rc_update_lboard_memrefs(self, peek, peek_ud);
if (self->state == RC_LBOARD_STATE_INACTIVE || self->state == RC_LBOARD_STATE_DISABLED)
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)
{
@ -232,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:

View File

@ -6,41 +6,232 @@
#define MEMREF_PLACEHOLDER_ADDRESS 0xFFFFFFFF
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size, uint8_t is_indirect) {
rc_memref_t** next_memref;
rc_memref_t* memref;
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size) {
rc_memref_list_t* memref_list = NULL;
rc_memref_t* memref = NULL;
int i;
if (!is_indirect) {
/* attempt to find an existing memref that can be shared */
next_memref = parse->first_memref;
while (*next_memref) {
memref = *next_memref;
if (!memref->value.is_indirect && memref->address == address && memref->value.size == size)
return memref;
for (i = 0; i < 2; i++) {
if (i == 0) {
if (!parse->existing_memrefs)
continue;
next_memref = &memref->next;
memref_list = &parse->existing_memrefs->memrefs;
}
else {
memref_list = &parse->memrefs->memrefs;
}
/* no match found, create a new entry */
memref = RC_ALLOC_SCRATCH(rc_memref_t, parse);
*next_memref = memref;
do
{
const rc_memref_t* memref_stop;
memref = memref_list->items;
memref_stop = memref + memref_list->count;
for (; memref < memref_stop; ++memref) {
if (memref->address == address && memref->value.size == size)
return memref;
}
if (!memref_list->next)
break;
memref_list = memref_list->next;
} while (1);
}
else {
/* indirect references always create a new entry because we can't guarantee that the
* indirection amount will be the same between references. because they aren't shared,
* don't bother putting them in the chain.
*/
memref = RC_ALLOC(rc_memref_t, parse);
/* no match found, find a place to put the new entry */
memref_list = &parse->memrefs->memrefs;
while (memref_list->count == memref_list->capacity && memref_list->next)
memref_list = memref_list->next;
/* create a new entry */
if (memref_list->count < memref_list->capacity) {
memref = &memref_list->items[memref_list->count++];
} else {
const int32_t old_offset = parse->offset;
if (memref_list->capacity != 0) {
memref_list = memref_list->next = RC_ALLOC_SCRATCH(rc_memref_list_t, parse);
memref_list->next = NULL;
}
memref_list->items = RC_ALLOC_ARRAY_SCRATCH(rc_memref_t, 8, parse);
memref_list->count = 1;
memref_list->capacity = 8;
memref_list->allocated = 0;
memref = memref_list->items;
/* in preparse mode, don't count this memory, we'll do a single allocation once we have
* the final total */
if (!parse->buffer)
parse->offset = old_offset;
}
memset(memref, 0, sizeof(*memref));
memref->address = address;
memref->value.memref_type = RC_MEMREF_TYPE_MEMREF;
memref->value.type = RC_VALUE_TYPE_UNSIGNED;
memref->value.size = size;
memref->value.is_indirect = is_indirect;
memref->address = address;
return memref;
}
rc_modified_memref_t* rc_alloc_modified_memref(rc_parse_state_t* parse, uint8_t size, const rc_operand_t* parent,
uint8_t modifier_type, const rc_operand_t* modifier) {
rc_modified_memref_list_t* modified_memref_list = NULL;
rc_modified_memref_t* modified_memref = NULL;
int i = 0;
for (i = 0; i < 2; i++) {
if (i == 0) {
if (!parse->existing_memrefs)
continue;
modified_memref_list = &parse->existing_memrefs->modified_memrefs;
}
else {
modified_memref_list = &parse->memrefs->modified_memrefs;
}
do {
const rc_modified_memref_t* memref_stop;
modified_memref = modified_memref_list->items;
memref_stop = modified_memref + modified_memref_list->count;
for (; modified_memref < memref_stop; ++modified_memref) {
if (modified_memref->memref.value.size == size &&
modified_memref->modifier_type == modifier_type &&
rc_operands_are_equal(&modified_memref->parent, parent) &&
rc_operands_are_equal(&modified_memref->modifier, modifier)) {
return modified_memref;
}
}
if (!modified_memref_list->next)
break;
modified_memref_list = modified_memref_list->next;
} while (1);
}
/* no match found, find a place to put the new entry */
modified_memref_list = &parse->memrefs->modified_memrefs;
while (modified_memref_list->count == modified_memref_list->capacity && modified_memref_list->next)
modified_memref_list = modified_memref_list->next;
/* create a new entry */
if (modified_memref_list->count < modified_memref_list->capacity) {
modified_memref = &modified_memref_list->items[modified_memref_list->count++];
} else {
const int32_t old_offset = parse->offset;
if (modified_memref_list->capacity != 0) {
modified_memref_list = modified_memref_list->next = RC_ALLOC_SCRATCH(rc_modified_memref_list_t, parse);
modified_memref_list->next = NULL;
}
modified_memref_list->items = RC_ALLOC_ARRAY_SCRATCH(rc_modified_memref_t, 8, parse);
modified_memref_list->count = 1;
modified_memref_list->capacity = 8;
modified_memref_list->allocated = 0;
modified_memref = modified_memref_list->items;
/* in preparse mode, don't count this memory, we'll do a single allocation once we have
* the final total */
if (!parse->buffer)
parse->offset = old_offset;
}
memset(modified_memref, 0, sizeof(*modified_memref));
modified_memref->memref.value.memref_type = RC_MEMREF_TYPE_MODIFIED_MEMREF;
modified_memref->memref.value.size = size;
modified_memref->memref.value.type = rc_memsize_is_float(size) ? RC_VALUE_TYPE_FLOAT : RC_VALUE_TYPE_UNSIGNED;
memcpy(&modified_memref->parent, parent, sizeof(modified_memref->parent));
memcpy(&modified_memref->modifier, modifier, sizeof(modified_memref->modifier));
modified_memref->modifier_type = modifier_type;
modified_memref->memref.address = rc_operand_is_memref(modifier) ? modifier->value.memref->address : modifier->value.num;
return modified_memref;
}
void rc_memrefs_init(rc_memrefs_t* memrefs)
{
memset(memrefs, 0, sizeof(*memrefs));
memrefs->memrefs.capacity = 32;
memrefs->memrefs.items =
(rc_memref_t*)malloc(memrefs->memrefs.capacity * sizeof(rc_memref_t));
memrefs->memrefs.allocated = 1;
memrefs->modified_memrefs.capacity = 16;
memrefs->modified_memrefs.items =
(rc_modified_memref_t*)malloc(memrefs->modified_memrefs.capacity * sizeof(rc_modified_memref_t));
memrefs->modified_memrefs.allocated = 1;
}
void rc_memrefs_destroy(rc_memrefs_t* memrefs)
{
rc_memref_list_t* memref_list = &memrefs->memrefs;
rc_modified_memref_list_t* modified_memref_list = &memrefs->modified_memrefs;
do {
rc_memref_list_t* current_memref_list = memref_list;
memref_list = memref_list->next;
if (current_memref_list->allocated) {
if (current_memref_list->items)
free(current_memref_list->items);
if (current_memref_list != &memrefs->memrefs)
free(current_memref_list);
}
} while (memref_list);
do {
rc_modified_memref_list_t* current_modified_memref_list = modified_memref_list;
modified_memref_list = modified_memref_list->next;
if (current_modified_memref_list->allocated) {
if (current_modified_memref_list->items)
free(current_modified_memref_list->items);
if (current_modified_memref_list != &memrefs->modified_memrefs)
free(current_modified_memref_list);
}
} while (modified_memref_list);
free(memrefs);
}
uint32_t rc_memrefs_count_memrefs(const rc_memrefs_t* memrefs)
{
uint32_t count = 0;
const rc_memref_list_t* memref_list = &memrefs->memrefs;
while (memref_list) {
count += memref_list->count;
memref_list = memref_list->next;
}
return count;
}
uint32_t rc_memrefs_count_modified_memrefs(const rc_memrefs_t* memrefs)
{
uint32_t count = 0;
const rc_modified_memref_list_t* modified_memref_list = &memrefs->modified_memrefs;
while (modified_memref_list) {
count += modified_memref_list->count;
modified_memref_list = modified_memref_list->next;
}
return count;
}
int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address) {
const char* aux = *memaddr;
char* end;
@ -77,7 +268,12 @@ int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address) {
/* case 'y': case 'Y': 64 bit? */
/* case 'z': case 'Z': 128 bit? */
case '0': case '1': case '2': case '3': case '4':
case '0':
if (*aux == 'x') /* user mistyped an extra 0x: 0x0xabcd */
return RC_INVALID_MEMORY_OPERAND;
/* fallthrough */
case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
@ -485,19 +681,12 @@ void rc_update_memref_value(rc_memref_value_t* memref, uint32_t new_value) {
}
}
void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud) {
while (memref) {
/* indirect memory references are not shared and will be updated in rc_get_memref_value */
if (!memref->value.is_indirect)
rc_update_memref_value(&memref->value, rc_peek_value(memref->address, memref->value.size, peek, ud));
void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memrefs_t* memrefs)
{
if (memrefs)
memset(memrefs, 0, sizeof(*memrefs));
memref = memref->next;
}
}
void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs) {
parse->first_memref = memrefs;
*memrefs = 0;
parse->memrefs = memrefs;
}
static uint32_t rc_get_memref_value_value(const rc_memref_value_t* memref, int operand_type) {
@ -520,12 +709,68 @@ static uint32_t rc_get_memref_value_value(const rc_memref_value_t* memref, int o
}
}
uint32_t rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state) {
/* if this is an indirect reference, handle the indirection. */
if (memref->value.is_indirect) {
const uint32_t new_address = memref->address + eval_state->add_address;
rc_update_memref_value(&memref->value, rc_peek_value(new_address, memref->value.size, eval_state->peek, eval_state->peek_userdata));
void rc_get_memref_value(rc_typed_value_t* value, rc_memref_t* memref, int operand_type) {
value->type = memref->value.type;
value->value.u32 = rc_get_memref_value_value(&memref->value, operand_type);
}
uint32_t rc_get_modified_memref_value(const rc_modified_memref_t* memref, rc_peek_t peek, void* ud) {
rc_typed_value_t value, modifier;
rc_evaluate_operand(&value, &memref->parent, NULL);
rc_evaluate_operand(&modifier, &memref->modifier, NULL);
switch (memref->modifier_type) {
case RC_OPERATOR_INDIRECT_READ:
rc_typed_value_add(&value, &modifier);
rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
value.value.u32 = rc_peek_value(value.value.u32, memref->memref.value.size, peek, ud);
value.type = memref->memref.value.type;
break;
case RC_OPERATOR_SUB_PARENT:
rc_typed_value_negate(&value);
rc_typed_value_add(&value, &modifier);
rc_typed_value_convert(&value, memref->memref.value.type);
break;
default:
rc_typed_value_combine(&value, &modifier, memref->modifier_type);
rc_typed_value_convert(&value, memref->memref.value.type);
break;
}
return rc_get_memref_value_value(&memref->value, operand_type);
return value.value.u32;
}
void rc_update_memref_values(rc_memrefs_t* memrefs, rc_peek_t peek, void* ud) {
rc_memref_list_t* memref_list;
rc_modified_memref_list_t* modified_memref_list;
memref_list = &memrefs->memrefs;
do
{
rc_memref_t* memref = memref_list->items;
const rc_memref_t* memref_stop = memref + memref_list->count;
for (; memref < memref_stop; ++memref) {
if (memref->value.type != RC_VALUE_TYPE_NONE)
rc_update_memref_value(&memref->value, rc_peek_value(memref->address, memref->value.size, peek, ud));
}
memref_list = memref_list->next;
} while (memref_list);
modified_memref_list = &memrefs->modified_memrefs;
if (modified_memref_list->count) {
do {
rc_modified_memref_t* modified_memref = modified_memref_list->items;
const rc_modified_memref_t* modified_memref_stop = modified_memref + modified_memref_list->count;
for (; modified_memref < modified_memref_stop; ++modified_memref)
rc_update_memref_value(&modified_memref->memref.value, rc_get_modified_memref_value(modified_memref, peek, ud));
modified_memref_list = modified_memref_list->next;
} while (modified_memref_list);
}
}

View File

@ -5,67 +5,29 @@
#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;
return RC_OK;
}
static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr) {
static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) {
const char* aux = *memaddr;
size_t i;
char varName[RC_VALUE_MAX_NAME_LENGTH + 1] = { 0 };
@ -86,6 +48,16 @@ static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr) {
++aux;
if (strcmp(varName, "recall") == 0) {
if (parse->remember.type == RC_OPERAND_NONE) {
self->value.memref = NULL;
self->size = RC_MEMSIZE_32_BITS;
self->memref_access_type = RC_OPERAND_ADDRESS;
}
else {
memcpy(self, &parse->remember, sizeof(*self));
self->is_combining = 0;
self->memref_access_type = self->type;
}
self->type = RC_OPERAND_RECALL;
}
else { /* process named variable when feature is available.*/
@ -96,7 +68,7 @@ static int rc_parse_operand_variable(rc_operand_t* self, const char** memaddr) {
return RC_OK;
}
static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect) {
static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) {
const char* aux = *memaddr;
uint32_t address;
uint8_t size;
@ -128,6 +100,8 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_
break;
}
self->memref_access_type = self->type;
ret = rc_parse_memref(&aux, &self->size, &address);
if (ret != RC_OK)
return ret;
@ -137,13 +111,23 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_
/* if the shared size differs from the requested size and it's a prior operation, we
* have to check to make sure both sizes use the same mask, or the prior value may be
* updated when bits outside the mask are modified, which would make it look like the
* current value once the mask is applied. if the mask differs, create a new
* current value once the mask is applied. if the mask differs, create a new
* non-shared record for tracking the prior data. */
if (rc_memref_mask(size) != rc_memref_mask(self->size))
size = self->size;
}
self->value.memref = rc_alloc_memref(parse, address, size, is_indirect);
if (parse->indirect_parent.type != RC_OPERAND_NONE) {
rc_operand_t offset;
rc_operand_set_const(&offset, address);
self->value.memref = (rc_memref_t*)rc_alloc_modified_memref(parse,
size, &parse->indirect_parent, RC_OPERATOR_INDIRECT_READ, &offset);
}
else {
self->value.memref = rc_alloc_memref(parse, address, size);
}
if (parse->offset < 0)
return parse->offset;
@ -151,7 +135,7 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_
return RC_OK;
}
int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indirect, rc_parse_state_t* parse) {
int rc_parse_operand(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) {
const char* aux = *memaddr;
char* end;
int ret;
@ -159,7 +143,7 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
int negative;
int allow_decimal = 0;
self->size = RC_MEMSIZE_32_BITS;
self->is_combining = 0;
switch (*aux) {
case 'h': case 'H': /* hex constant */
@ -175,15 +159,14 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
if (value > 0xffffffffU)
value = 0xffffffffU;
self->type = RC_OPERAND_CONST;
self->value.num = (unsigned)value;
rc_operand_set_const(self, (unsigned)value);
aux = end;
break;
case 'f': case 'F': /* floating point constant */
if (isalpha((unsigned char)aux[1])) {
ret = rc_parse_operand_memory(self, &aux, parse, is_indirect);
ret = rc_parse_operand_memory(self, &aux, parse);
if (ret < 0)
return ret;
@ -211,6 +194,7 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
/* custom parser for decimal values to ignore locale */
unsigned long shift = 1;
unsigned long fraction = 0;
double dbl_val;
aux = end + 1;
if (*aux < '0' || *aux > '9')
@ -231,19 +215,19 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
/* non-zero fractional part, convert to double and merge in integer portion */
const double dbl_fraction = ((double)fraction) / ((double)shift);
if (negative)
self->value.dbl = ((double)(-((long)value))) - dbl_fraction;
dbl_val = ((double)(-((long)value))) - dbl_fraction;
else
self->value.dbl = (double)value + dbl_fraction;
dbl_val = (double)value + dbl_fraction;
}
else {
/* fractional part is 0, just convert the integer portion */
if (negative)
self->value.dbl = (double)(-((long)value));
dbl_val = (double)(-((long)value));
else
self->value.dbl = (double)value;
dbl_val = (double)value;
}
self->type = RC_OPERAND_FP;
rc_operand_set_float_const(self, dbl_val);
}
else {
/* not a floating point value, make sure something was read and advance the read pointer */
@ -255,17 +239,15 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
if (value > 0x7fffffffU)
value = 0x7fffffffU;
self->type = RC_OPERAND_CONST;
if (negative)
self->value.num = (unsigned)(-((long)value));
rc_operand_set_const(self, (unsigned)(-((long)value)));
else
self->value.num = (unsigned)value;
rc_operand_set_const(self, (unsigned)value);
}
break;
case '{': /* variable */
++aux;
ret = rc_parse_operand_variable(self, &aux);
ret = rc_parse_operand_variable(self, &aux, parse);
if (ret < 0)
return ret;
@ -275,7 +257,7 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
if (aux[1] == 'x' || aux[1] == 'X') { /* hex integer constant */
/* fallthrough */ /* to default */
default:
ret = rc_parse_operand_memory(self, &aux, parse, is_indirect);
ret = rc_parse_operand_memory(self, &aux, parse);
if (ret < 0)
return ret;
@ -292,14 +274,13 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
if (value > 0xffffffffU)
value = 0xffffffffU;
self->type = RC_OPERAND_CONST;
self->value.num = (unsigned)value;
rc_operand_set_const(self, (unsigned)value);
aux = end;
break;
case '@':
ret = rc_parse_operand_lua(self, &aux, parse);
ret = rc_parse_operand_func_call(self, &aux);
if (ret < 0)
return ret;
@ -311,29 +292,77 @@ int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indire
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;
void rc_operand_set_const(rc_operand_t* self, uint32_t value) {
self->size = RC_MEMSIZE_32_BITS;
self->type = RC_OPERAND_CONST;
self->memref_access_type = RC_OPERAND_NONE;
self->value.num = value;
}
#endif /* RC_DISABLE_LUA */
void rc_operand_set_float_const(rc_operand_t* self, double value) {
self->size = RC_MEMSIZE_FLOAT;
self->type = RC_OPERAND_FP;
self->memref_access_type = RC_OPERAND_NONE;
self->value.dbl = value;
}
int rc_operand_is_float_memref(const rc_operand_t* self) {
switch (self->size) {
int rc_operands_are_equal(const rc_operand_t* left, const rc_operand_t* right) {
if (left == right)
return 1;
if (left->type != right->type)
return 0;
switch (left->type) {
case RC_OPERAND_CONST:
return (left->value.num == right->value.num);
case RC_OPERAND_FP:
return (left->value.dbl == right->value.dbl);
case RC_OPERAND_RECALL:
return (left->value.memref == right->value.memref);
default:
break;
}
/* comparing two memrefs - look for exact matches on type and size */
if (left->size != right->size || left->value.memref->value.memref_type != right->value.memref->value.memref_type)
return 0;
switch (left->value.memref->value.memref_type) {
case RC_MEMREF_TYPE_MODIFIED_MEMREF:
{
const rc_modified_memref_t* left_memref = (const rc_modified_memref_t*)left->value.memref;
const rc_modified_memref_t* right_memref = (const rc_modified_memref_t*)right->value.memref;
return (left_memref->modifier_type == right_memref->modifier_type &&
rc_operands_are_equal(&left_memref->parent, &right_memref->parent) &&
rc_operands_are_equal(&left_memref->modifier, &right_memref->modifier));
}
default:
return (left->value.memref->address == right->value.memref->address &&
left->value.memref->value.size == right->value.memref->value.size);
}
}
int rc_operator_is_modifying(int oper) {
switch (oper) {
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_DIV:
case RC_OPERATOR_MULT:
case RC_OPERATOR_MOD:
case RC_OPERATOR_ADD:
case RC_OPERATOR_SUB:
case RC_OPERATOR_NONE: /* NONE operator implies "* 1" */
return 1;
default:
return 0;
}
}
int rc_memsize_is_float(uint8_t size) {
switch (size) {
case RC_MEMSIZE_FLOAT:
case RC_MEMSIZE_FLOAT_BE:
case RC_MEMSIZE_DOUBLE32:
@ -347,11 +376,24 @@ int rc_operand_is_float_memref(const rc_operand_t* self) {
}
}
int rc_operand_is_memref(const rc_operand_t* self) {
switch (self->type) {
int rc_operand_is_float_memref(const rc_operand_t* self) {
if (!rc_operand_is_memref(self))
return 0;
if (self->value.memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) {
const rc_modified_memref_t* memref = (const rc_modified_memref_t*)self->value.memref;
if (memref->modifier_type != RC_OPERATOR_INDIRECT_READ)
return rc_memsize_is_float(self->value.memref->value.size);
}
return rc_memsize_is_float(self->size);
}
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;
@ -360,6 +402,21 @@ int rc_operand_is_memref(const rc_operand_t* self) {
}
}
int rc_operand_type_is_transform(uint8_t type) {
switch (type) {
case RC_OPERAND_BCD:
case RC_OPERAND_INVERTED:
return 1;
default:
return 0;
}
}
int rc_operand_is_memref(const rc_operand_t* self) {
return rc_operand_type_is_memref(self->type);
}
int rc_operand_is_recall(const rc_operand_t* self) {
switch (self->type) {
case RC_OPERAND_RECALL:
@ -374,10 +431,13 @@ int rc_operand_is_float(const rc_operand_t* self) {
if (self->type == RC_OPERAND_FP)
return 1;
if (self->type == RC_OPERAND_RECALL)
return rc_memsize_is_float(self->size);
return rc_operand_is_float_memref(self);
}
uint32_t rc_transform_operand_value(uint32_t value, const rc_operand_t* self) {
static uint32_t rc_transform_operand_value(uint32_t value, const rc_operand_t* self) {
switch (self->type)
{
case RC_OPERAND_BCD:
@ -465,11 +525,35 @@ uint32_t rc_transform_operand_value(uint32_t value, const rc_operand_t* self) {
return value;
}
void rc_evaluate_operand(rc_typed_value_t* result, rc_operand_t* self, rc_eval_state_t* eval_state) {
#ifndef RC_DISABLE_LUA
rc_luapeek_t luapeek;
#endif /* RC_DISABLE_LUA */
void rc_operand_addsource(rc_operand_t* self, rc_parse_state_t* parse, uint8_t new_size) {
rc_modified_memref_t* modified_memref;
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;
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);
/* 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;
/* 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) {
/* step 1: read memory */
switch (self->type) {
case RC_OPERAND_CONST:
@ -482,44 +566,32 @@ void rc_evaluate_operand(rc_typed_value_t* result, rc_operand_t* self, rc_eval_s
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 */
break;
case RC_OPERAND_RECALL:
result->type = eval_state->recall_value.type;
result->value = eval_state->recall_value.value;
return;
case RC_OPERAND_RECALL:
if (!rc_operand_type_is_memref(self->memref_access_type)) {
rc_operand_t recall;
memcpy(&recall, self, sizeof(recall));
recall.type = self->memref_access_type;
rc_evaluate_operand(result, &recall, eval_state);
return;
}
if (!self->value.memref) {
result->type = RC_VALUE_TYPE_UNSIGNED;
result->value.u32 = 0;
return;
}
rc_get_memref_value(result, self->value.memref, self->memref_access_type);
break;
default:
result->type = RC_VALUE_TYPE_UNSIGNED;
result->value.u32 = rc_get_memref_value(self->value.memref, self->type, eval_state);
rc_get_memref_value(result, self->value.memref, self->type);
break;
}

View File

@ -13,27 +13,115 @@ typedef struct rc_scratch_string {
}
rc_scratch_string_t;
#define RC_ALLOW_ALIGN(T) struct __align_ ## T { char ch; T t; };
typedef struct rc_modified_memref_t {
rc_memref_t memref; /* for compatibility with rc_operand_t.value.memref */
rc_operand_t parent; /* The parent memref this memref is derived from (type will always be a memref type) */
rc_operand_t modifier; /* The modifier to apply to the parent. */
uint8_t modifier_type; /* How to apply the modifier to the parent. (RC_OPERATOR_*) */
}
rc_modified_memref_t;
typedef struct rc_memref_list_t {
rc_memref_t* items;
struct rc_memref_list_t* next;
uint16_t count;
uint16_t capacity;
uint8_t allocated;
} rc_memref_list_t;
typedef struct rc_modified_memref_list_t {
rc_modified_memref_t* items;
struct rc_modified_memref_list_t* next;
uint16_t count;
uint16_t capacity;
uint8_t allocated;
} rc_modified_memref_list_t;
typedef struct rc_memrefs_t {
rc_memref_list_t memrefs;
rc_modified_memref_list_t modified_memrefs;
} rc_memrefs_t;
typedef struct rc_trigger_with_memrefs_t {
rc_trigger_t trigger;
rc_memrefs_t memrefs;
} rc_trigger_with_memrefs_t;
typedef struct rc_lboard_with_memrefs_t {
rc_lboard_t lboard;
rc_memrefs_t memrefs;
} rc_lboard_with_memrefs_t;
typedef struct rc_richpresence_with_memrefs_t {
rc_richpresence_t richpresence;
rc_memrefs_t memrefs;
} rc_richpresence_with_memrefs_t;
typedef struct rc_value_with_memrefs_t {
rc_value_t value;
rc_memrefs_t memrefs;
} rc_value_with_memrefs_t;
/* enum helpers for natvis expansion. Have to use a struct to define the mapping,
* and a single field to allow the conditional logic to switch on the value */
typedef struct __rc_bool_enum_t { uint8_t value; } __rc_bool_enum_t;
typedef struct __rc_memsize_enum_t { uint8_t value; } __rc_memsize_enum_t;
typedef struct __rc_memsize_enum_func_t { uint8_t value; } __rc_memsize_enum_func_t;
typedef struct __rc_operand_enum_t { uint8_t value; } __rc_operand_enum_t;
typedef struct __rc_value_type_enum_t { uint8_t value; } __rc_value_type_enum_t;
typedef struct __rc_memref_type_enum_t { uint8_t value; } __rc_memref_type_enum_t;
typedef struct __rc_condition_enum_t { uint8_t value; } __rc_condition_enum_t;
typedef struct __rc_condition_enum_str_t { uint8_t value; } __rc_condition_enum_str_t;
typedef struct __rc_condset_list_t { rc_condset_t* first_condset; } __rc_condset_list_t;
typedef struct __rc_operator_enum_t { uint8_t value; } __rc_operator_enum_t;
typedef struct __rc_operator_enum_str_t { uint8_t value; } __rc_operator_enum_str_t;
typedef struct __rc_operand_memref_t { rc_operand_t operand; } __rc_operand_memref_t; /* requires &rc_operand_t to be the same as &rc_operand_t.value.memref */
typedef struct __rc_value_list_t { rc_value_t* first_value; } __rc_value_list_t;
typedef struct __rc_trigger_state_enum_t { uint8_t value; } __rc_trigger_state_enum_t;
typedef struct __rc_lboard_state_enum_t { uint8_t value; } __rc_lboard_state_enum_t;
typedef struct __rc_richpresence_display_list_t { rc_richpresence_display_t* first_display; } __rc_richpresence_display_list_t;
typedef struct __rc_richpresence_display_part_list_t { rc_richpresence_display_part_t* display; } __rc_richpresence_display_part_list_t;
typedef struct __rc_richpresence_lookup_list_t { rc_richpresence_lookup_t* first_lookup; } __rc_richpresence_lookup_list_t;
typedef struct __rc_format_enum_t { uint8_t value; } __rc_format_enum_t;
#define RC_ALLOW_ALIGN(T) struct __align_ ## T { uint8_t ch; T t; };
RC_ALLOW_ALIGN(rc_condition_t)
RC_ALLOW_ALIGN(rc_condset_t)
RC_ALLOW_ALIGN(rc_modified_memref_t)
RC_ALLOW_ALIGN(rc_lboard_t)
RC_ALLOW_ALIGN(rc_lboard_with_memrefs_t)
RC_ALLOW_ALIGN(rc_memref_t)
RC_ALLOW_ALIGN(rc_memref_list_t)
RC_ALLOW_ALIGN(rc_memrefs_t)
RC_ALLOW_ALIGN(rc_modified_memref_list_t)
RC_ALLOW_ALIGN(rc_operand_t)
RC_ALLOW_ALIGN(rc_richpresence_t)
RC_ALLOW_ALIGN(rc_richpresence_display_t)
RC_ALLOW_ALIGN(rc_richpresence_display_part_t)
RC_ALLOW_ALIGN(rc_richpresence_lookup_t)
RC_ALLOW_ALIGN(rc_richpresence_lookup_item_t)
RC_ALLOW_ALIGN(rc_richpresence_with_memrefs_t)
RC_ALLOW_ALIGN(rc_scratch_string_t)
RC_ALLOW_ALIGN(rc_trigger_t)
RC_ALLOW_ALIGN(rc_trigger_with_memrefs_t)
RC_ALLOW_ALIGN(rc_value_t)
RC_ALLOW_ALIGN(rc_value_with_memrefs_t)
RC_ALLOW_ALIGN(char)
#define RC_ALIGNOF(T) (sizeof(struct __align_ ## T) - sizeof(T))
#define RC_OFFSETOF(o, t) (int)((char*)&(o.t) - (char*)&(o))
#define RC_OFFSETOF(o, t) (int)((uint8_t*)&(o.t) - (uint8_t*)&(o))
#define RC_ALLOC(t, p) ((t*)rc_alloc((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
#define RC_ALLOC_SCRATCH(t, p) ((t*)rc_alloc_scratch((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
#define RC_ALLOC_ARRAY(t, n, p) ((t*)rc_alloc((p)->buffer, &(p)->offset, (n) * sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
#define RC_ALLOC_ARRAY_SCRATCH(t, n, p) ((t*)rc_alloc_scratch((p)->buffer, &(p)->offset, (n) * sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
#define RC_ALLOC_WITH_TRAILING(container_type, trailing_type, trailing_field, trailing_count, parse) ((container_type*)rc_alloc(\
(parse)->buffer, &(parse)->offset, \
RC_OFFSETOF((*(container_type*)NULL),trailing_field) + trailing_count * sizeof(trailing_type), \
RC_ALIGNOF(container_type), &(parse)->scratch, 0))
#define RC_GET_TRAILING(container_pointer, container_type, trailing_type, trailing_field) (trailing_type*)(&((container_type*)(container_pointer))->trailing_field)
/* force alignment to 4 bytes on 32-bit systems, or 8 bytes on 64-bit systems */
#define RC_ALIGN(n) (((n) + (sizeof(void*)-1)) & ~(sizeof(void*)-1))
@ -45,17 +133,49 @@ typedef struct {
struct objs {
rc_condition_t* __rc_condition_t;
rc_condset_t* __rc_condset_t;
rc_modified_memref_t* __rc_modified_memref_t;
rc_lboard_t* __rc_lboard_t;
rc_lboard_with_memrefs_t* __rc_lboard_with_memrefs_t;
rc_memref_t* __rc_memref_t;
rc_memref_list_t* __rc_memref_list_t;
rc_memrefs_t* __rc_memrefs_t;
rc_modified_memref_list_t* __rc_modified_memref_list_t;
rc_operand_t* __rc_operand_t;
rc_richpresence_t* __rc_richpresence_t;
rc_richpresence_display_t* __rc_richpresence_display_t;
rc_richpresence_display_part_t* __rc_richpresence_display_part_t;
rc_richpresence_lookup_t* __rc_richpresence_lookup_t;
rc_richpresence_lookup_item_t* __rc_richpresence_lookup_item_t;
rc_richpresence_with_memrefs_t* __rc_richpresence_with_memrefs_t;
rc_scratch_string_t __rc_scratch_string_t;
rc_trigger_t* __rc_trigger_t;
rc_trigger_with_memrefs_t* __rc_trigger_with_memrefs_t;
rc_value_t* __rc_value_t;
rc_value_with_memrefs_t* __rc_value_with_memrefs_t;
/* these fields aren't actually used by the code, but they force the
* virtual enum wrapper types to exist so natvis can use them */
union {
__rc_bool_enum_t boolean;
__rc_memsize_enum_t memsize;
__rc_memsize_enum_func_t memsize_func;
__rc_operand_enum_t operand;
__rc_value_type_enum_t value_type;
__rc_memref_type_enum_t memref_type;
__rc_condition_enum_t condition;
__rc_condition_enum_str_t condition_str;
__rc_condset_list_t condset_list;
__rc_operator_enum_t oper;
__rc_operator_enum_str_t oper_str;
__rc_operand_memref_t operand_memref;
__rc_value_list_t value_list;
__rc_trigger_state_enum_t trigger_state;
__rc_lboard_state_enum_t lboard_state;
__rc_richpresence_display_list_t richpresence_display_list;
__rc_richpresence_display_part_list_t richpresence_display_part_list;
__rc_richpresence_lookup_list_t richpresence_lookup_list;
__rc_format_enum_t format;
} natvis_extension;
} objs;
}
rc_scratch_t;
@ -78,72 +198,119 @@ typedef struct {
}
rc_typed_value_t;
enum {
RC_MEMREF_TYPE_MEMREF, /* rc_memref_t */
RC_MEMREF_TYPE_MODIFIED_MEMREF, /* rc_modified_memref_t */
RC_MEMREF_TYPE_VALUE /* rc_value_t */
};
#define RC_MEASURED_UNKNOWN 0xFFFFFFFF
#define RC_OPERAND_NONE 0xFF
typedef struct {
rc_typed_value_t add_value;/* AddSource/SubSource */
int32_t add_hits; /* AddHits */
uint32_t add_address; /* AddAddress */
/* memory accessors */
rc_peek_t peek;
void* peek_userdata;
lua_State* L;
rc_typed_value_t measured_value; /* Measured */
rc_typed_value_t recall_value; /* Set by RC_CONDITION_REMEMBER */
uint8_t was_reset; /* ResetIf triggered */
uint8_t has_hits; /* one of more hit counts is non-zero */
uint8_t primed; /* true if all non-Trigger conditions are true */
/* processing state */
rc_typed_value_t measured_value; /* captured Measured value */
int32_t add_hits; /* AddHits/SubHits accumulator */
uint8_t is_true; /* true if all conditions are true */
uint8_t is_primed; /* true if all non-Trigger conditions are true */
uint8_t is_paused; /* true if one or more PauseIf conditions is true */
uint8_t can_measure; /* false if the measured value should be ignored */
uint8_t measured_from_hits; /* true if the measured_value came from a condition's hit count */
uint8_t was_cond_reset; /* ResetNextIf triggered */
uint8_t and_next; /* true if the previous condition was AndNext true */
uint8_t or_next; /* true if the previous condition was OrNext true */
uint8_t reset_next; /* true if the previous condition was ResetNextIf true */
uint8_t stop_processing; /* true to abort the processing loop */
/* result state */
uint8_t has_hits; /* true if one of more hit counts is non-zero */
uint8_t was_reset; /* true if one or more ResetIf conditions is true */
uint8_t was_cond_reset; /* true if one or more ResetNextIf conditions is true */
/* control settings */
uint8_t can_short_curcuit; /* allows logic processing to stop as soon as a false condition is encountered */
}
rc_eval_state_t;
typedef struct {
int32_t offset;
lua_State* L;
int funcs_ndx;
void* buffer;
rc_scratch_t scratch;
rc_memref_t** first_memref;
rc_memrefs_t* memrefs;
rc_memrefs_t* existing_memrefs;
rc_value_t** variables;
uint32_t measured_target;
int lines_read;
rc_operand_t addsource_parent;
rc_operand_t indirect_parent;
rc_operand_t remember;
uint8_t addsource_oper;
uint8_t is_value;
uint8_t has_required_hits;
uint8_t measured_as_percent;
uint8_t ignore_non_parse_errors;
}
rc_parse_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_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs);
void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables);
typedef struct rc_preparse_state_t {
rc_parse_state_t parse;
rc_memrefs_t memrefs;
} rc_preparse_state_t;
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);
void rc_destroy_parse_state(rc_parse_state_t* parse);
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_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);
char* rc_alloc_str(rc_parse_state_t* parse, const char* text, size_t length);
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size, uint8_t is_indirect);
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size);
rc_modified_memref_t* rc_alloc_modified_memref(rc_parse_state_t* parse, uint8_t size, const rc_operand_t* parent,
uint8_t modifier_type, const rc_operand_t* modifier);
int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* address);
void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud);
void rc_update_memref_values(rc_memrefs_t* memrefs, rc_peek_t peek, void* ud);
void rc_update_memref_value(rc_memref_value_t* memref, uint32_t value);
uint32_t rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state);
void rc_get_memref_value(rc_typed_value_t* value, rc_memref_t* memref, int operand_type);
uint32_t rc_get_modified_memref_value(const rc_modified_memref_t* memref, rc_peek_t peek, void* ud);
uint8_t rc_memref_shared_size(uint8_t size);
uint32_t rc_memref_mask(uint8_t size);
void rc_transform_memref_value(rc_typed_value_t* value, uint8_t size);
uint32_t rc_peek_value(uint32_t address, uint8_t size, rc_peek_t peek, void* ud);
void rc_memrefs_init(rc_memrefs_t* memrefs);
void rc_memrefs_destroy(rc_memrefs_t* memrefs);
uint32_t rc_memrefs_count_memrefs(const rc_memrefs_t* memrefs);
uint32_t rc_memrefs_count_modified_memrefs(const rc_memrefs_t* memrefs);
void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse);
int rc_trigger_state_active(int state);
rc_memrefs_t* rc_trigger_get_memrefs(rc_trigger_t* self);
rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, int is_value);
typedef struct rc_condset_with_trailing_conditions_t {
rc_condset_t condset;
rc_condition_t conditions[2];
} rc_condset_with_trailing_conditions_t;
RC_ALLOW_ALIGN(rc_condset_with_trailing_conditions_t)
rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse);
int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state);
void rc_reset_condset(rc_condset_t* self);
rc_condition_t* rc_condset_get_conditions(rc_condset_t* self);
enum {
RC_PROCESSING_COMPARE_DEFAULT = 0,
@ -161,24 +328,38 @@ enum {
RC_PROCESSING_COMPARE_ALWAYS_FALSE
};
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, uint8_t is_indirect);
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse);
void rc_parse_condition_internal(rc_condition_t* self, const char** memaddr, rc_parse_state_t* parse);
void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t* parse);
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state);
void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state);
int rc_condition_is_combining(const rc_condition_t* self);
void rc_condition_convert_to_operand(const rc_condition_t* condition, rc_operand_t* operand, rc_parse_state_t* parse);
const rc_operand_t* rc_condition_get_real_operand1(const rc_condition_t* self);
int rc_parse_operand(rc_operand_t* self, const char** memaddr, uint8_t is_indirect, rc_parse_state_t* parse);
void rc_evaluate_operand(rc_typed_value_t* value, rc_operand_t* self, rc_eval_state_t* eval_state);
int rc_parse_operand(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse);
void rc_evaluate_operand(rc_typed_value_t* value, const rc_operand_t* self, rc_eval_state_t* eval_state);
int rc_operator_is_modifying(int oper);
int rc_memsize_is_float(uint8_t size);
int rc_operand_is_float_memref(const rc_operand_t* self);
int rc_operand_is_float(const rc_operand_t* self);
int rc_operand_is_recall(const rc_operand_t* self);
int rc_operand_type_is_memref(uint8_t type);
int rc_operand_type_is_transform(uint8_t type);
int rc_operands_are_equal(const rc_operand_t* left, const rc_operand_t* right);
void rc_operand_addsource(rc_operand_t* self, rc_parse_state_t* parse, uint8_t new_size);
void rc_operand_set_const(rc_operand_t* self, uint32_t value);
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_helper_variable(const char* memaddr, size_t memaddr_len, rc_parse_state_t* parse);
void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_State* L);
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);
void rc_reset_values(rc_value_t* values);
void rc_typed_value_convert(rc_typed_value_t* value, char new_type);
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount);
@ -187,6 +368,7 @@ void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amou
void rc_typed_value_modulus(rc_typed_value_t* value, const rc_typed_value_t* amount);
void rc_typed_value_negate(rc_typed_value_t* value);
int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper);
void rc_typed_value_combine(rc_typed_value_t* value, rc_typed_value_t* amount, uint8_t oper);
void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref);
int rc_format_typed_value(char* buffer, size_t size, const rc_typed_value_t* value, int format);
@ -195,6 +377,11 @@ void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_s
int rc_lboard_state_active(int state);
void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, rc_parse_state_t* parse);
rc_memrefs_t* rc_richpresence_get_memrefs(rc_richpresence_t* self);
void rc_reset_richpresence_triggers(rc_richpresence_t* self);
int rc_validate_memrefs(const rc_memrefs_t* memrefs, char result[], const size_t result_size, uint32_t max_address);
int rc_validate_memrefs_for_console(const rc_memrefs_t* memrefs, char result[], const size_t result_size, uint32_t console_id);
RC_END_C_DECLS

View File

@ -13,29 +13,67 @@ enum {
RC_FORMAT_UNICODECHAR = 105
};
static rc_memref_value_t* rc_alloc_helper_variable_memref_value(const char* memaddr, int memaddr_len, rc_parse_state_t* parse) {
const char* end;
rc_value_t* variable;
uint32_t address;
uint8_t size;
static void rc_alloc_helper_variable_memref_value(rc_richpresence_display_part_t* part, const char* memaddr, int memaddr_len, rc_parse_state_t* parse) {
rc_preparse_state_t preparse;
const char* test_memaddr = memaddr;
rc_condset_t* condset;
rc_value_t* value;
int32_t size;
/* single memory reference lookups without a modifier flag can be handled without a variable */
end = memaddr;
if (rc_parse_memref(&end, &size, &address) == RC_OK) {
/* make sure the entire memaddr was consumed. if not, there's an operator and it's a comparison, not a memory reference */
if (end == &memaddr[memaddr_len]) {
/* if it's not a derived size, we can reference the memref directly */
if (rc_memref_shared_size(size) == size)
return &rc_alloc_memref(parse, address, size, 0)->value;
part->value.type = RC_OPERAND_NONE;
/* if the expression can be represented as just a memory reference, do so */
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);
size = preparse.parse.offset;
if (size < 0) {
parse->offset = size;
rc_destroy_preparse_state(&preparse);
return;
}
/* ensure new needed memrefs are allocated in the primary buffer */
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));
preparse.parse.memrefs = parse->memrefs;
preparse.parse.existing_memrefs = parse->existing_memrefs;
value = RC_ALLOC(rc_value_t, &preparse.parse);
test_memaddr = memaddr;
rc_parse_value_internal(value, &test_memaddr, &preparse.parse);
condset = value->conditions;
if (condset && !condset->next) {
/* single value - if it's only "measured" and "indirect" conditions, we can simplify to a memref */
if (condset->num_measured_conditions &&
!condset->num_pause_conditions && !condset->num_reset_conditions &&
!condset->num_other_conditions && !condset->num_hittarget_conditions) {
rc_condition_t* condition = condset->conditions;
for (; condition; condition = condition->next) {
if (condition->type == RC_CONDITION_MEASURED && condition->required_hits == 0) {
memcpy(&part->value, &condition->operand1, sizeof(condition->operand1));
break;
}
}
}
}
/* not a simple memory reference, need to create a variable */
variable = rc_alloc_helper_variable(memaddr, memaddr_len, parse);
if (!variable)
return NULL;
rc_destroy_preparse_state(&preparse);
return &variable->value;
/* could not express value with just a memory reference, create a helper variable */
if (part->value.type == RC_OPERAND_NONE) {
value = rc_alloc_variable(memaddr, memaddr_len, parse);
if (value) {
part->value.value.memref = (rc_memref_t*)&value->value;
part->value.type = RC_OPERAND_ADDRESS;
part->value.size = RC_MEMSIZE_32_BITS;
part->value.memref_access_type = RC_OPERAND_ADDRESS;
}
}
}
static const char* rc_parse_line(const char* line, const char** end, rc_parse_state_t* parse) {
@ -192,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;
@ -220,7 +259,7 @@ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const c
part->text = rc_alloc_str(parse, in, (int)(ptr - in));
}
else if (part->display_type != RC_FORMAT_UNKNOWN_MACRO) {
part->value = rc_alloc_helper_variable_memref_value(line, (int)(ptr - line), parse);
rc_alloc_helper_variable_memref_value(part, line, (int)(ptr - line), parse);
if (parse->offset < 0)
return 0;
@ -468,6 +507,8 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
int display_line = 0;
int chars;
self->values = NULL;
/* special case for empty script to return 1 line read */
if (!*script) {
parse->lines_read = 1;
@ -496,6 +537,13 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
} else if (strncmp(line, "Format:", 7) == 0) {
line += 7;
if (endline - line == 11 && memcmp(line, "Unformatted", 11) == 0) {
/* for backwards compatibility with the comma rollout, we allow old scripts
* to define an Unformatted type mapped to VALUE, and new versions will ignore
* the definition and use the built-in macro. skip the next line (FormatType=) */
line = rc_parse_line(nextline, &endline, parse);
continue;
}
lookup = RC_ALLOC_SCRATCH(rc_richpresence_lookup_t, parse);
lookup->name = rc_alloc_str(parse, line, (int)(endline - line));
@ -558,11 +606,20 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
*nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup);
if (parse->offset < 0)
return;
trigger = &((*nextdisplay)->trigger);
rc_parse_trigger_internal(trigger, &line, parse);
trigger->memrefs = 0;
if (parse->offset < 0)
return;
if (line != ptr) {
/* incomplete read */
parse->offset = RC_INVALID_OPERATOR;
return;
}
(*nextdisplay)->has_required_hits = parse->has_required_hits;
if (parse->buffer)
nextdisplay = &((*nextdisplay)->next);
}
@ -593,6 +650,7 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
/* finalize */
*nextdisplay = 0;
self->has_memrefs = 0;
if (!hasdisplay && parse->offset > 0) {
parse->offset = RC_MISSING_DISPLAY_STRING;
@ -600,56 +658,78 @@ 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_t* self;
rc_parse_state_t parse;
rc_memref_t* first_memref;
rc_value_t* variables;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &first_memref);
rc_init_parse_state_variables(&parse, &variables);
rc_richpresence_with_memrefs_t* richpresence;
rc_preparse_state_t preparse;
rc_init_preparse_state(&preparse);
self = RC_ALLOC(rc_richpresence_t, &parse);
rc_parse_richpresence_internal(self, script, &parse);
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_preparse_alloc_memrefs(NULL, &preparse);
if (lines_read)
*lines_read = parse.lines_read;
*lines_read = preparse.parse.lines_read;
rc_destroy_parse_state(&parse);
return parse.offset;
rc_destroy_preparse_state(&preparse);
return preparse.parse.offset;
}
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* self;
rc_parse_state_t parse;
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_parse_state(&parse, buffer, 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);
self = RC_ALLOC(rc_richpresence_t, &parse);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_init_parse_state_variables(&parse, &self->variables);
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);
rc_parse_richpresence_internal(self, script, &parse);
rc_parse_richpresence_internal(&richpresence->richpresence, script, &preparse.parse);
richpresence->richpresence.has_memrefs = 1;
rc_destroy_parse_state(&parse);
return (parse.offset >= 0) ? self : NULL;
rc_destroy_preparse_state(&preparse);
return (preparse.parse.offset >= 0) ? &richpresence->richpresence : NULL;
}
void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L) {
static void rc_update_richpresence_memrefs(rc_richpresence_t* self, rc_peek_t peek, void* ud) {
if (self->has_memrefs) {
rc_richpresence_with_memrefs_t* richpresence = (rc_richpresence_with_memrefs_t*)self;
rc_update_memref_values(&richpresence->memrefs, peek, ud);
}
}
rc_memrefs_t* rc_richpresence_get_memrefs(rc_richpresence_t* self) {
if (self->has_memrefs) {
rc_richpresence_with_memrefs_t* richpresence = (rc_richpresence_with_memrefs_t*)self;
return &richpresence->memrefs;
}
return NULL;
}
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_memref_values(richpresence->memrefs, peek, peek_ud);
rc_update_variables(richpresence->variables, peek, peek_ud, L);
rc_update_richpresence_memrefs(richpresence, peek, peek_ud);
rc_update_values(richpresence->values, peek, peek_ud);
for (display = richpresence->first_display; display; display = display->next) {
if (display->trigger.has_required_hits)
rc_test_trigger(&display->trigger, peek, peek_ud, L);
if (display->has_required_hits)
rc_test_trigger(&display->trigger, peek, peek_ud, unused_L);
}
}
@ -671,7 +751,7 @@ static int rc_evaluate_richpresence_display(rc_richpresence_display_part_t* part
break;
case RC_FORMAT_LOOKUP:
rc_typed_value_from_memref_value(&value, part->value);
rc_evaluate_operand(&value, &part->value, NULL);
rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
text = part->lookup->default_label;
@ -698,7 +778,7 @@ static int rc_evaluate_richpresence_display(rc_richpresence_display_part_t* part
value.type = RC_VALUE_TYPE_UNSIGNED;
do {
value.value.u32 = part->value->value;
rc_evaluate_operand(&value, &part->value, NULL);
if (value.value.u32 == 0) {
/* null terminator - skip over remaining character macros */
while (part->next && part->next->display_type == RC_FORMAT_ASCIICHAR)
@ -725,7 +805,7 @@ static int rc_evaluate_richpresence_display(rc_richpresence_display_part_t* part
value.type = RC_VALUE_TYPE_UNSIGNED;
do {
value.value.u32 = part->value->value;
rc_evaluate_operand(&value, &part->value, NULL);
if (value.value.u32 == 0) {
/* null terminator - skip over remaining character macros */
while (part->next && part->next->display_type == RC_FORMAT_UNICODECHAR)
@ -770,7 +850,7 @@ static int rc_evaluate_richpresence_display(rc_richpresence_display_part_t* part
break;
default:
rc_typed_value_from_memref_value(&value, part->value);
rc_evaluate_operand(&value, &part->value, NULL);
chars = rc_format_typed_value(tmp, sizeof(tmp), &value, part->display_type);
text = tmp;
break;
@ -797,7 +877,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) {
@ -806,8 +886,8 @@ int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* bu
return rc_evaluate_richpresence_display(display->display, buffer, buffersize);
/* triggers with required hits will be updated in rc_update_richpresence */
if (!display->trigger.has_required_hits)
rc_test_trigger(&display->trigger, peek, peek_ud, L);
if (!display->has_required_hits)
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)
@ -818,18 +898,19 @@ 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(rc_richpresence_t* self) {
void rc_reset_richpresence_triggers(rc_richpresence_t* self) {
rc_richpresence_display_t* display;
rc_value_t* variable;
for (display = self->first_display; display; display = display->next)
rc_reset_trigger(&display->trigger);
for (variable = self->variables; variable; variable = variable->next)
rc_reset_value(variable);
}
void rc_reset_richpresence(rc_richpresence_t* self) {
rc_reset_richpresence_triggers(self);
rc_reset_values(self->values);
}

View File

@ -9,8 +9,34 @@
#define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256
/* ===== natvis extensions ===== */
typedef struct __rc_runtime_trigger_list_t { rc_runtime_t runtime; } __rc_runtime_trigger_list_t;
typedef struct __rc_runtime_lboard_list_t { rc_runtime_t runtime; } __rc_runtime_lboard_list_t;
static void rc_runtime_natvis_helper(const rc_runtime_event_t* runtime_event)
{
struct natvis_extensions {
__rc_runtime_trigger_list_t trigger_list;
__rc_runtime_lboard_list_t lboard_list;
} natvis;
memset(&natvis, 0, sizeof(natvis));
(void)runtime_event;
natvis.lboard_list.runtime.lboard_count = 0;
}
/* ============================= */
rc_runtime_t* rc_runtime_alloc(void) {
rc_runtime_t* self = malloc(sizeof(rc_runtime_t));
rc_runtime_t* self;
/* create a reference to rc_runtime_natvis_helper so the extensions get compiled in. */
rc_runtime_event_handler_t unused = &rc_runtime_natvis_helper;
(void)unused;
self = malloc(sizeof(rc_runtime_t));
if (self) {
rc_runtime_init(self);
@ -22,16 +48,19 @@ rc_runtime_t* rc_runtime_alloc(void) {
void rc_runtime_init(rc_runtime_t* self) {
memset(self, 0, sizeof(rc_runtime_t));
self->next_memref = &self->memrefs;
self->next_variable = &self->variables;
self->memrefs = (rc_memrefs_t*)malloc(sizeof(*self->memrefs));
rc_memrefs_init(self->memrefs);
}
void rc_runtime_destroy(rc_runtime_t* self) {
uint32_t i;
if (self->triggers) {
for (i = 0; i < self->trigger_count; ++i)
free(self->triggers[i].buffer);
for (i = 0; i < self->trigger_count; ++i) {
if (self->triggers[i].buffer)
free(self->triggers[i].buffer);
}
free(self->triggers);
self->triggers = NULL;
@ -40,8 +69,10 @@ void rc_runtime_destroy(rc_runtime_t* self) {
}
if (self->lboards) {
for (i = 0; i < self->lboard_count; ++i)
free(self->lboards[i].buffer);
for (i = 0; i < self->lboard_count; ++i) {
if (self->lboards[i].buffer)
free(self->lboards[i].buffer);
}
free(self->lboards);
self->lboards = NULL;
@ -49,20 +80,17 @@ void rc_runtime_destroy(rc_runtime_t* self) {
self->lboard_count = self->lboard_capacity = 0;
}
while (self->richpresence) {
rc_runtime_richpresence_t* previous = self->richpresence->previous;
free(self->richpresence->buffer);
if (self->richpresence) {
if (self->richpresence->buffer)
free(self->richpresence->buffer);
free(self->richpresence);
self->richpresence = previous;
}
self->next_memref = 0;
self->memrefs = 0;
if (self->memrefs)
rc_memrefs_destroy(self->memrefs);
if (self->owns_self) {
if (self->owns_self)
free(self);
}
}
void rc_runtime_checksum(const char* memaddr, uint8_t* md5) {
@ -72,45 +100,12 @@ void rc_runtime_checksum(const char* memaddr, uint8_t* md5) {
md5_finish(&state, md5);
}
static char rc_runtime_allocated_memrefs(rc_runtime_t* self) {
char owns_memref = 0;
/* if at least one memref was allocated within the object, we can't free the buffer when the object is deactivated */
if (*self->next_memref != NULL) {
owns_memref = 1;
/* advance through the new memrefs so we're ready for the next allocation */
do {
self->next_memref = &(*self->next_memref)->next;
} while (*self->next_memref != NULL);
}
/* if at least one variable was allocated within the object, we can't free the buffer when the object is deactivated */
if (*self->next_variable != NULL) {
owns_memref = 1;
/* advance through the new variables so we're ready for the next allocation */
do {
self->next_variable = &(*self->next_variable)->next;
} while (*self->next_variable != NULL);
}
return owns_memref;
}
static void rc_runtime_deactivate_trigger_by_index(rc_runtime_t* self, uint32_t index) {
if (self->triggers[index].owns_memrefs) {
/* if the trigger has one or more memrefs in its buffer, we can't free the buffer.
* just null out the trigger so the runtime processor will skip it
*/
rc_reset_trigger(self->triggers[index].trigger);
self->triggers[index].trigger = NULL;
}
else {
/* trigger doesn't own any memrefs, go ahead and free it, then replace it with the last trigger */
free(self->triggers[index].buffer);
/* free the trigger, then replace it with the last trigger */
free(self->triggers[index].buffer);
if (--self->trigger_count > index)
memcpy(&self->triggers[index], &self->triggers[self->trigger_count], sizeof(rc_runtime_trigger_t));
}
if (--self->trigger_count > index)
memcpy(&self->triggers[index], &self->triggers[self->trigger_count], sizeof(rc_runtime_trigger_t));
}
void rc_runtime_deactivate_achievement(rc_runtime_t* self, uint32_t id) {
@ -122,15 +117,19 @@ 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;
rc_parse_state_t parse;
rc_preparse_state_t preparse;
const char* preparse_memaddr = memaddr;
uint8_t md5[16];
int32_t size;
uint32_t i;
(void)unused_L;
(void)unused_funcs_idx;
if (memaddr == NULL)
return RC_INVALID_MEMORY_OPERAND;
@ -169,7 +168,12 @@ 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 */
size = rc_trigger_size(memaddr);
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);
size = preparse.parse.offset;
if (size < 0)
return size;
@ -178,16 +182,15 @@ 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_init_parse_state(&parse, trigger_buffer, L, funcs_idx);
parse.first_memref = &self->memrefs;
trigger = RC_ALLOC(rc_trigger_t, &parse);
rc_parse_trigger_internal(trigger, &memaddr, &parse);
rc_destroy_parse_state(&parse);
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);
rc_destroy_preparse_state(&preparse);
if (parse.offset < 0) {
if (preparse.parse.offset < 0) {
free(trigger_buffer);
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
return parse.offset;
return preparse.parse.offset;
}
/* grow the trigger buffer if necessary */
@ -200,7 +203,6 @@ int rc_runtime_activate_achievement(rc_runtime_t* self, uint32_t id, const char*
if (!self->triggers) {
free(trigger_buffer);
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
return RC_OUT_OF_MEMORY;
}
}
@ -213,11 +215,9 @@ int rc_runtime_activate_achievement(rc_runtime_t* self, uint32_t id, const char*
runtime_trigger->invalid_memref = NULL;
memcpy(runtime_trigger->md5, md5, 16);
runtime_trigger->serialized_size = 0;
runtime_trigger->owns_memrefs = rc_runtime_allocated_memrefs(self);
++self->trigger_count;
/* reset it, and return it */
trigger->memrefs = NULL;
rc_reset_trigger(trigger);
return RC_OK;
}
@ -285,20 +285,11 @@ int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, uint32_t
}
static void rc_runtime_deactivate_lboard_by_index(rc_runtime_t* self, uint32_t index) {
if (self->lboards[index].owns_memrefs) {
/* if the lboard has one or more memrefs in its buffer, we can't free the buffer.
* just null out the lboard so the runtime processor will skip it
*/
rc_reset_lboard(self->lboards[index].lboard);
self->lboards[index].lboard = NULL;
}
else {
/* lboard doesn't own any memrefs, go ahead and free it, then replace it with the last lboard */
free(self->lboards[index].buffer);
/* free the lboard, then replace it with the last lboard */
free(self->lboards[index].buffer);
if (--self->lboard_count > index)
memcpy(&self->lboards[index], &self->lboards[self->lboard_count], sizeof(rc_runtime_lboard_t));
}
if (--self->lboard_count > index)
memcpy(&self->lboards[index], &self->lboards[self->lboard_count], sizeof(rc_runtime_lboard_t));
}
void rc_runtime_deactivate_lboard(rc_runtime_t* self, uint32_t id) {
@ -310,15 +301,18 @@ 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;
rc_parse_state_t parse;
rc_preparse_state_t preparse;
rc_runtime_lboard_t* runtime_lboard;
int32_t size;
int size;
uint32_t i;
(void)unused_L;
(void)unused_funcs_idx;
if (memaddr == 0)
return RC_INVALID_MEMORY_OPERAND;
@ -357,7 +351,12 @@ 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 */
size = rc_lboard_size(memaddr);
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);
size = preparse.parse.offset;
if (size < 0)
return size;
@ -366,16 +365,15 @@ 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_init_parse_state(&parse, lboard_buffer, L, funcs_idx);
lboard = RC_ALLOC(rc_lboard_t, &parse);
parse.first_memref = &self->memrefs;
rc_parse_lboard_internal(lboard, memaddr, &parse);
rc_destroy_parse_state(&parse);
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);
rc_destroy_preparse_state(&preparse);
if (parse.offset < 0) {
if (preparse.parse.offset < 0) {
free(lboard_buffer);
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
return parse.offset;
return preparse.parse.offset;
}
/* grow the lboard buffer if necessary */
@ -388,7 +386,6 @@ int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* mema
if (!self->lboards) {
free(lboard_buffer);
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
return RC_OUT_OF_MEMORY;
}
}
@ -402,10 +399,8 @@ int rc_runtime_activate_lboard(rc_runtime_t* self, uint32_t id, const char* mema
runtime_lboard->invalid_memref = NULL;
memcpy(runtime_lboard->md5, md5, 16);
runtime_lboard->serialized_size = 0;
runtime_lboard->owns_memrefs = rc_runtime_allocated_memrefs(self);
/* reset it, and return it */
lboard->memrefs = NULL;
rc_reset_lboard(lboard);
return RC_OK;
}
@ -427,61 +422,44 @@ 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_runtime_richpresence_t* previous;
rc_runtime_richpresence_t** previous_ptr;
rc_parse_state_t parse;
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;
rc_runtime_checksum(script, md5);
/* look for existing match */
previous_ptr = NULL;
previous = self->richpresence;
while (previous) {
if (previous && self->richpresence->richpresence && memcmp(self->richpresence->md5, md5, 16) == 0) {
/* unchanged. reset all of the conditions */
rc_reset_richpresence(self->richpresence->richpresence);
if (self->richpresence && self->richpresence->richpresence && memcmp(self->richpresence->md5, md5, 16) == 0) {
/* unchanged. reset all of the conditions */
rc_reset_richpresence(self->richpresence->richpresence);
/* move to front of linked list*/
if (previous_ptr) {
*previous_ptr = previous->previous;
if (!self->richpresence->owns_memrefs) {
free(self->richpresence->buffer);
previous->previous = self->richpresence->previous;
}
else {
previous->previous = self->richpresence;
}
self->richpresence = previous;
}
/* return success*/
return RC_OK;
}
previous_ptr = &previous->previous;
previous = previous->previous;
/* return success*/
return RC_OK;
}
/* no existing match found, parse script */
size = rc_richpresence_size(script);
rc_init_preparse_state(&preparse);
preparse.parse.existing_memrefs = self->memrefs;
richpresence = RC_ALLOC(rc_richpresence_t, &preparse.parse);
preparse.parse.variables = &richpresence->values;
rc_parse_richpresence_internal(richpresence, script, &preparse.parse);
size = preparse.parse.offset;
if (size < 0)
return size;
/* if the previous script doesn't have any memrefs, free it */
previous = self->richpresence;
if (previous) {
if (!previous->owns_memrefs) {
free(previous->buffer);
previous = previous->previous;
}
/* if there's a previous script, free it */
if (self->richpresence) {
free(self->richpresence->buffer);
free(self->richpresence);
}
/* allocate and process the new script */
@ -489,34 +467,26 @@ int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua
if (!self->richpresence)
return RC_OUT_OF_MEMORY;
self->richpresence->previous = previous;
self->richpresence->owns_memrefs = 0;
memcpy(self->richpresence->md5, md5, sizeof(md5));
self->richpresence->buffer = malloc(size);
self->richpresence->buffer = malloc(size);
if (!self->richpresence->buffer)
return RC_OUT_OF_MEMORY;
rc_init_parse_state(&parse, self->richpresence->buffer, L, funcs_idx);
self->richpresence->richpresence = richpresence = RC_ALLOC(rc_richpresence_t, &parse);
parse.first_memref = &self->memrefs;
parse.variables = &self->variables;
rc_parse_richpresence_internal(richpresence, script, &parse);
rc_destroy_parse_state(&parse);
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;
rc_parse_richpresence_internal(richpresence, script, &preparse.parse);
rc_destroy_preparse_state(&preparse);
if (parse.offset < 0) {
if (preparse.parse.offset < 0) {
free(self->richpresence->buffer);
free(self->richpresence);
self->richpresence = previous;
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
return parse.offset;
self->richpresence = NULL;
return preparse.parse.offset;
}
self->richpresence->owns_memrefs = rc_runtime_allocated_memrefs(self);
richpresence->memrefs = NULL;
richpresence->variables = NULL;
if (!richpresence->first_display || !richpresence->first_display->display) {
/* non-existant rich presence */
self->richpresence->richpresence = NULL;
@ -524,27 +494,27 @@ int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua
else {
/* reset all of the conditions */
rc_reset_richpresence(richpresence);
self->richpresence->richpresence = richpresence;
}
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;
}
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;
runtime_event.value = 0;
rc_update_memref_values(self->memrefs, peek, ud);
rc_update_variables(self->variables, peek, ud, L);
for (i = self->trigger_count - 1; i >= 0; --i) {
rc_trigger_t* trigger = self->triggers[i].trigger;
@ -570,7 +540,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 */
@ -672,7 +642,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) {
@ -710,11 +680,10 @@ 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) {
rc_value_t* variable;
uint32_t i;
for (i = 0; i < self->trigger_count; ++i) {
@ -729,9 +698,6 @@ void rc_runtime_reset(rc_runtime_t* self) {
if (self->richpresence && self->richpresence->richpresence)
rc_reset_richpresence(self->richpresence->richpresence);
for (variable = self->variables; variable; variable = variable->next)
rc_reset_value(variable);
}
static int rc_condset_contains_memref(const rc_condset_t* condset, const rc_memref_t* memref) {
@ -815,47 +781,41 @@ static void rc_runtime_invalidate_memref(rc_runtime_t* self, rc_memref_t* memref
}
void rc_runtime_invalidate_address(rc_runtime_t* self, uint32_t address) {
rc_memref_t** last_memref = &self->memrefs;
rc_memref_t* memref = self->memrefs;
rc_memref_list_t* memref_list = &self->memrefs->memrefs;
do {
rc_memref_t* memref = memref_list->items;
const rc_memref_t* memref_stop = memref + memref_list->count;
while (memref) {
if (memref->address == address && !memref->value.is_indirect) {
/* remove the invalid memref from the chain so we don't try to evaluate it in the future.
* it's still there, so anything referencing it will continue to fetch 0.
*/
*last_memref = memref->next;
rc_runtime_invalidate_memref(self, memref);
break;
for (; memref < memref_stop; ++memref) {
if (memref->address == address) {
memref->value.type = RC_VALUE_TYPE_NONE;
rc_runtime_invalidate_memref(self, memref);
}
}
last_memref = &memref->next;
memref = *last_memref;
}
memref_list = memref_list->next;
} while (memref_list);
}
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
rc_runtime_validate_address_t validate_handler) {
rc_memref_t** last_memref = &self->memrefs;
rc_memref_t* memref = self->memrefs;
int num_invalid = 0;
rc_memref_list_t* memref_list = &self->memrefs->memrefs;
do {
rc_memref_t* memref = memref_list->items;
const rc_memref_t* memref_stop = memref + memref_list->count;
while (memref) {
if (!memref->value.is_indirect && !validate_handler(memref->address)) {
/* remove the invalid memref from the chain so we don't try to evaluate it in the future.
* it's still there, so anything referencing it will continue to fetch 0.
*/
*last_memref = memref->next;
for (; memref < memref_stop; ++memref) {
if (!validate_handler(memref->address)) {
memref->value.type = RC_VALUE_TYPE_NONE;
rc_runtime_invalidate_memref(self, memref);
rc_runtime_invalidate_memref(self, memref);
++num_invalid;
}
else {
last_memref = &memref->next;
++num_invalid;
}
}
memref = *last_memref;
}
memref_list = memref_list->next;
} while (memref_list);
if (num_invalid) {
rc_runtime_event_t runtime_event;

View File

@ -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,22 +114,17 @@ 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 */
static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
{
rc_memref_t* memref;
uint32_t count = 0;
for (memref = progress->runtime->memrefs; memref; memref = memref->next)
++count;
uint32_t count = rc_memrefs_count_memrefs(progress->runtime->memrefs);
if (count == 0)
return RC_OK;
@ -145,15 +138,24 @@ static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
}
else {
uint32_t flags = 0;
for (memref = progress->runtime->memrefs; memref; memref = memref->next) {
flags = memref->value.size;
if (memref->value.changed)
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
const rc_memref_list_t* memref_list = &progress->runtime->memrefs->memrefs;
const rc_memref_t* memref;
rc_runtime_progress_write_uint(progress, memref->address);
rc_runtime_progress_write_uint(progress, flags);
rc_runtime_progress_write_uint(progress, memref->value.value);
rc_runtime_progress_write_uint(progress, memref->value.prior);
for (; memref_list; memref_list = memref_list->next) {
const rc_memref_t* memref_end;
memref = memref_list->items;
memref_end = memref + memref_list->count;
for (; memref < memref_end; ++memref) {
flags = memref->value.size;
if (memref->value.changed)
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
rc_runtime_progress_write_uint(progress, memref->address);
rc_runtime_progress_write_uint(progress, flags);
rc_runtime_progress_write_uint(progress, memref->value.value);
rc_runtime_progress_write_uint(progress, memref->value.prior);
}
}
}
@ -162,13 +164,77 @@ static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
return RC_OK;
}
static void rc_runtime_progress_update_modified_memrefs(rc_runtime_progress_t* progress)
{
rc_typed_value_t value, prior_value, modifier, prior_modifier;
rc_modified_memref_list_t* modified_memref_list;
rc_modified_memref_t* modified_memref;
rc_operand_t prior_parent_operand, prior_modifier_operand;
rc_memref_t prior_parent_memref, prior_modifier_memref;
modified_memref_list = &progress->runtime->memrefs->modified_memrefs;
for (; modified_memref_list; modified_memref_list = modified_memref_list->next) {
const rc_modified_memref_t* modified_memref_end;
modified_memref = modified_memref_list->items;
modified_memref_end = modified_memref + modified_memref_list->count;
for (; modified_memref < modified_memref_end; ++modified_memref) {
modified_memref->memref.value.changed = 0;
/* indirect memref values are stored in conditions */
if (modified_memref->modifier_type == RC_OPERATOR_INDIRECT_READ)
continue;
/* non-indirect memref values can be reconstructed from the parents */
memcpy(&prior_parent_operand, &modified_memref->parent, sizeof(prior_parent_operand));
if (rc_operand_is_memref(&prior_parent_operand)) {
memcpy(&prior_parent_memref, modified_memref->parent.value.memref, sizeof(prior_parent_memref));
prior_parent_memref.value.value = prior_parent_memref.value.prior;
modified_memref->memref.value.changed |= prior_parent_memref.value.changed;
prior_parent_operand.value.memref = &prior_parent_memref;
}
memcpy(&prior_modifier_operand, &modified_memref->modifier, sizeof(prior_modifier_operand));
if (rc_operand_is_memref(&prior_modifier_operand)) {
memcpy(&prior_modifier_memref, modified_memref->modifier.value.memref, sizeof(prior_modifier_memref));
prior_modifier_memref.value.value = prior_modifier_memref.value.prior;
modified_memref->memref.value.changed |= prior_modifier_memref.value.changed;
prior_modifier_operand.value.memref = &prior_modifier_memref;
}
rc_evaluate_operand(&value, &modified_memref->parent, NULL);
rc_evaluate_operand(&modifier, &modified_memref->modifier, NULL);
rc_evaluate_operand(&prior_value, &prior_parent_operand, NULL);
rc_evaluate_operand(&prior_modifier, &prior_modifier_operand, NULL);
if (modified_memref->modifier_type == RC_OPERATOR_SUB_PARENT) {
rc_typed_value_negate(&value);
rc_typed_value_add(&value, &modifier);
rc_typed_value_negate(&prior_value);
rc_typed_value_add(&prior_value, &prior_modifier);
}
else {
rc_typed_value_combine(&value, &modifier, modified_memref->modifier_type);
rc_typed_value_combine(&prior_value, &prior_modifier, modified_memref->modifier_type);
}
rc_typed_value_convert(&value, modified_memref->memref.value.type);
modified_memref->memref.value.value = value.value.u32;
rc_typed_value_convert(&prior_value, modified_memref->memref.value.type);
modified_memref->memref.value.prior = prior_value.value.u32;
}
}
}
static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress)
{
uint32_t entries;
uint32_t address, flags, value, prior;
uint8_t size;
rc_memref_list_t* unmatched_memref_list = &progress->runtime->memrefs->memrefs;
rc_memref_t* first_unmatched_memref = unmatched_memref_list->items;
rc_memref_t* memref;
rc_memref_t* first_unmatched_memref = progress->runtime->memrefs;
/* re-read the chunk size to determine how many memrefs are present */
progress->offset -= 4;
@ -183,24 +249,46 @@ static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress)
size = flags & 0xFF;
memref = first_unmatched_memref;
while (memref) {
if (memref->address == address && memref->value.size == size) {
memref->value.value = value;
memref->value.changed = (flags & RC_MEMREF_FLAG_CHANGED_THIS_FRAME) ? 1 : 0;
memref->value.prior = prior;
if (memref->address == address && memref->value.size == size) {
memref->value.value = value;
memref->value.changed = (flags & RC_MEMREF_FLAG_CHANGED_THIS_FRAME) ? 1 : 0;
memref->value.prior = prior;
if (memref == first_unmatched_memref)
first_unmatched_memref = memref->next;
break;
first_unmatched_memref++;
if (first_unmatched_memref >= unmatched_memref_list->items + unmatched_memref_list->count) {
unmatched_memref_list = unmatched_memref_list->next;
if (!unmatched_memref_list)
break;
first_unmatched_memref = unmatched_memref_list->items;
}
}
else {
rc_memref_list_t* memref_list = unmatched_memref_list;
do {
++memref;
if (memref >= memref_list->items + memref_list->count) {
memref_list = memref_list->next;
if (!memref_list)
break;
memref = memref->next;
memref = memref_list->items;
}
if (memref->address == address && memref->value.size == size) {
memref->value.value = value;
memref->value.changed = (flags & RC_MEMREF_FLAG_CHANGED_THIS_FRAME) ? 1 : 0;
memref->value.prior = prior;
break;
}
} while (1);
}
--entries;
}
rc_runtime_progress_update_modified_memrefs(progress);
return RC_OK;
}
@ -211,11 +299,14 @@ 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:
return oper->value.memref->value.is_indirect;
if (oper->value.memref->value.memref_type != RC_MEMREF_TYPE_MODIFIED_MEMREF)
return 0;
return ((const rc_modified_memref_t*)oper->value.memref)->modifier_type == RC_OPERATOR_INDIRECT_READ;
}
}
@ -231,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;
@ -287,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 */
@ -317,8 +406,8 @@ static uint32_t rc_runtime_progress_should_serialize_variable_condset(const rc_c
{
const rc_condition_t* condition;
/* predetermined presence of pause flag or indirect memrefs - must serialize */
if (conditions->has_pause || conditions->has_indirect_memrefs)
/* predetermined presence of pause flag - must serialize */
if (conditions->has_pause)
return RC_VAR_FLAG_HAS_COND_DATA;
/* if any conditions has required hits, must serialize */
@ -358,12 +447,15 @@ static int rc_runtime_progress_write_variable(rc_runtime_progress_t* progress, c
static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress)
{
uint32_t count = 0;
const rc_value_t* variable;
uint32_t count;
const rc_value_t* value;
int result;
for (variable = progress->runtime->variables; variable; variable = variable->next)
++count;
if (!progress->runtime->richpresence || !progress->runtime->richpresence->richpresence)
return RC_OK;
value = progress->runtime->richpresence->richpresence->values;
count = rc_count_values(value);
if (count == 0)
return RC_OK;
@ -374,14 +466,14 @@ static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress)
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_VARIABLES);
rc_runtime_progress_write_uint(progress, count);
for (variable = progress->runtime->variables; variable; variable = variable->next) {
uint32_t djb2 = rc_djb2(variable->name);
for (; value; value = value->next) {
const uint32_t djb2 = rc_djb2(value->name);
if (progress->offset + 16 > progress->buffer_size)
return RC_INSUFFICIENT_BUFFER;
rc_runtime_progress_write_uint(progress, djb2);
result = rc_runtime_progress_write_variable(progress, variable);
result = rc_runtime_progress_write_variable(progress, value);
if (result != RC_OK)
return result;
}
@ -418,19 +510,20 @@ static int rc_runtime_progress_read_variables(rc_runtime_progress_t* progress)
};
struct rc_pending_value_t local_pending_variables[32];
struct rc_pending_value_t* pending_variables;
rc_value_t* variable;
rc_value_t* value;
uint32_t count, serialized_count;
int result;
uint32_t i;
int32_t i;
serialized_count = rc_runtime_progress_read_uint(progress);
if (serialized_count == 0)
return RC_OK;
count = 0;
for (variable = progress->runtime->variables; variable; variable = variable->next)
++count;
if (!progress->runtime->richpresence || !progress->runtime->richpresence->richpresence)
return RC_OK;
value = progress->runtime->richpresence->richpresence->values;
count = rc_count_values(value);
if (count == 0)
return RC_OK;
@ -443,22 +536,22 @@ static int rc_runtime_progress_read_variables(rc_runtime_progress_t* progress)
return RC_OUT_OF_MEMORY;
}
count = 0;
for (variable = progress->runtime->variables; variable; variable = variable->next) {
pending_variables[count].variable = variable;
pending_variables[count].djb2 = rc_djb2(variable->name);
++count;
i = (int32_t)count;
for (; value; value = value->next) {
--i;
pending_variables[i].variable = value;
pending_variables[i].djb2 = rc_djb2(value->name);
}
result = RC_OK;
for (; serialized_count > 0 && result == RC_OK; --serialized_count) {
uint32_t djb2 = rc_runtime_progress_read_uint(progress);
for (i = 0; i < count; ++i) {
for (i = (int32_t)count - 1; i >= 0; --i) {
if (pending_variables[i].djb2 == djb2) {
variable = pending_variables[i].variable;
result = rc_runtime_progress_read_variable(progress, variable);
value = pending_variables[i].variable;
result = rc_runtime_progress_read_variable(progress, value);
if (result == RC_OK) {
if (i < count - 1)
if (i < (int32_t)count - 1)
memcpy(&pending_variables[i], &pending_variables[count - 1], sizeof(struct rc_pending_value_t));
count--;
}
@ -467,8 +560,17 @@ static int rc_runtime_progress_read_variables(rc_runtime_progress_t* progress)
}
}
/* VS raises a C6385 warning here because it thinks count can exceed the size of the local_pending_variables array.
* When count is larger, pending_variables points to allocated memory, so the warning is wrong. */
#if defined (_MSC_VER)
#pragma warning(push)
#pragma warning(disable:6385)
#endif
while (count > 0)
rc_reset_value(pending_variables[--count].variable);
#if defined (_MSC_VER)
#pragma warning(pop)
#endif
if (pending_variables != local_pending_variables)
free(pending_variables);
@ -742,7 +844,7 @@ static int rc_runtime_progress_read_rich_presence(rc_runtime_progress_t* progres
return RC_OK;
if (!rc_runtime_progress_match_md5(progress, progress->runtime->richpresence->md5)) {
rc_reset_richpresence(progress->runtime->richpresence->richpresence);
rc_reset_richpresence_triggers(progress->runtime->richpresence->richpresence);
return RC_OK;
}
@ -799,12 +901,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);
@ -814,31 +918,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;
@ -850,12 +956,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) {
@ -958,7 +1066,7 @@ int rc_runtime_deserialize_progress_sized(rc_runtime_t* runtime, const uint8_t*
}
if (!seen_rich_presence && runtime->richpresence && runtime->richpresence->richpresence)
rc_reset_richpresence(runtime->richpresence->richpresence);
rc_reset_richpresence_triggers(runtime->richpresence->richpresence);
}
return result;

View File

@ -16,21 +16,20 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars
parse->measured_as_percent = 0;
if (*aux == 's' || *aux == 'S') {
self->requirement = 0;
self->requirement = NULL;
}
else {
self->requirement = rc_parse_condset(&aux, parse, 0);
self->requirement = rc_parse_condset(&aux, parse);
if (parse->offset < 0) {
if (parse->offset < 0)
return;
}
self->requirement->next = 0;
self->requirement->next = NULL;
}
while (*aux == 's' || *aux == 'S') {
aux++;
*next = rc_parse_condset(&aux, parse, 0);
*next = rc_parse_condset(&aux, parse);
if (parse->offset < 0) {
return;
@ -39,7 +38,7 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars
next = &(*next)->next;
}
*next = 0;
*next = NULL;
*memaddr = aux;
self->measured_target = parse->measured_target;
@ -47,39 +46,49 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars
self->measured_as_percent = parse->measured_as_percent;
self->state = RC_TRIGGER_STATE_WAITING;
self->has_hits = 0;
self->has_required_hits = parse->has_required_hits;
self->has_memrefs = 0;
}
int rc_trigger_size(const char* memaddr) {
rc_trigger_t* self;
rc_parse_state_t parse;
rc_memref_t* memrefs;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &memrefs);
rc_trigger_with_memrefs_t* trigger;
rc_preparse_state_t preparse;
rc_init_preparse_state(&preparse);
self = RC_ALLOC(rc_trigger_t, &parse);
rc_parse_trigger_internal(self, &memaddr, &parse);
trigger = RC_ALLOC(rc_trigger_with_memrefs_t, &preparse.parse);
rc_parse_trigger_internal(&trigger->trigger, &memaddr, &preparse.parse);
rc_preparse_alloc_memrefs(NULL, &preparse);
rc_destroy_parse_state(&parse);
return parse.offset;
rc_destroy_preparse_state(&preparse);
return preparse.parse.offset;
}
rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
rc_trigger_t* self;
rc_parse_state_t parse;
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;
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
/* first pass : determine how many memrefs are needed */
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);
self = RC_ALLOC(rc_trigger_t, &parse);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
/* allocate the trigger and memrefs */
rc_reset_parse_state(&preparse.parse, buffer);
trigger = RC_ALLOC(rc_trigger_with_memrefs_t, &preparse.parse);
rc_preparse_alloc_memrefs(&trigger->memrefs, &preparse);
rc_parse_trigger_internal(self, &memaddr, &parse);
/* parse the trigger */
rc_parse_trigger_internal(&trigger->trigger, &memaddr, &preparse.parse);
trigger->trigger.has_memrefs = 1;
rc_destroy_parse_state(&parse);
return (parse.offset >= 0) ? self : NULL;
rc_destroy_preparse_state(&preparse);
return (preparse.parse.offset >= 0) ? &trigger->trigger : NULL;
}
int rc_trigger_state_active(int state)
@ -124,13 +133,33 @@ static void rc_reset_trigger_hitcounts(rc_trigger_t* self) {
}
}
int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) {
static void rc_update_trigger_memrefs(rc_trigger_t* self, rc_peek_t peek, void* ud) {
if (self->has_memrefs) {
rc_trigger_with_memrefs_t* trigger = (rc_trigger_with_memrefs_t*)self;
rc_update_memref_values(&trigger->memrefs, peek, ud);
}
}
rc_memrefs_t* rc_trigger_get_memrefs(rc_trigger_t* self) {
if (self->has_memrefs) {
rc_trigger_with_memrefs_t* trigger = (rc_trigger_with_memrefs_t*)self;
return &trigger->memrefs;
}
return NULL;
}
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;
int measured_from_hits = 0;
int ret;
char is_paused;
char is_primed;
(void)unused_L;
switch (self->state)
{
case RC_TRIGGER_STATE_TRIGGERED:
@ -143,7 +172,7 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
case RC_TRIGGER_STATE_INACTIVE:
/* not yet active. update the memrefs so deltas are correct when it becomes active, then return INACTIVE */
rc_update_memref_values(self->memrefs, peek, ud);
rc_update_trigger_memrefs(self, peek, ud);
return RC_TRIGGER_STATE_INACTIVE;
default:
@ -151,18 +180,24 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
}
/* update the memory references */
rc_update_memref_values(self->memrefs, peek, ud);
rc_update_trigger_memrefs(self, peek, ud);
/* process the trigger */
memset(&eval_state, 0, sizeof(eval_state));
eval_state.peek = peek;
eval_state.peek_userdata = ud;
eval_state.L = L;
measured_value.type = RC_VALUE_TYPE_NONE;
if (self->requirement != NULL) {
ret = rc_test_condset(self->requirement, &eval_state);
is_paused = self->requirement->is_paused;
is_primed = eval_state.primed;
is_paused = eval_state.is_paused;
is_primed = eval_state.is_primed;
if (eval_state.measured_value.type != RC_VALUE_TYPE_NONE) {
memcpy(&measured_value, &eval_state.measured_value, sizeof(measured_value));
measured_from_hits = eval_state.measured_from_hits;
}
} else {
ret = 1;
is_paused = 0;
@ -177,8 +212,17 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
do {
sub |= rc_test_condset(condset, &eval_state);
sub_paused &= condset->is_paused;
sub_primed |= eval_state.primed;
sub_paused &= eval_state.is_paused;
sub_primed |= eval_state.is_primed;
if (eval_state.measured_value.type != RC_VALUE_TYPE_NONE) {
/* if no previous Measured value was captured, or the new one is greater, keep the new one */
if (measured_value.type == RC_VALUE_TYPE_NONE ||
rc_typed_value_compare(&eval_state.measured_value, &measured_value, RC_OPERATOR_GT)) {
memcpy(&measured_value, &eval_state.measured_value, sizeof(measured_value));
measured_from_hits = eval_state.measured_from_hits;
}
}
condset = condset->next;
} while (condset);
@ -193,15 +237,15 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
/* if paused, the measured value may not be captured, keep the old value */
if (!is_paused) {
rc_typed_value_convert(&eval_state.measured_value, RC_VALUE_TYPE_UNSIGNED);
self->measured_value = eval_state.measured_value.value.u32;
rc_typed_value_convert(&measured_value, RC_VALUE_TYPE_UNSIGNED);
self->measured_value = measured_value.value.u32;
}
/* if any ResetIf condition was true, reset the hit counts */
if (eval_state.was_reset) {
/* if the measured value came from a hit count, reset it. do this before calling
* rc_reset_trigger_hitcounts in case we need to call rc_condset_is_measured_from_hitcount */
if (eval_state.measured_from_hits) {
if (measured_from_hits) {
self->measured_value = 0;
}
else if (is_paused && self->measured_value) {
@ -272,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) {

View File

@ -5,8 +5,6 @@
#include <float.h> /* FLT_EPSILON */
#include <math.h> /* fmod */
int rc_is_valid_variable_character(char ch, int is_first) {
if (is_first) {
if (!isalpha((unsigned char)ch))
@ -27,7 +25,7 @@ static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse
do
{
parse->measured_target = 0; /* passing is_value=1 should prevent any conflicts, but clear it out anyway */
*next_clause = rc_parse_condset(memaddr, parse, 1);
*next_clause = rc_parse_condset(memaddr, parse);
if (parse->offset < 0) {
return;
}
@ -52,182 +50,249 @@ static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse
(*next_clause)->next = 0;
}
void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
rc_condset_with_trailing_conditions_t* condset_with_conditions;
rc_condition_t** next;
rc_condset_t** next_clause;
rc_condset_t* condset;
rc_condition_t local_cond;
rc_condition_t* cond;
uint32_t num_measured_conditions;
char buffer[64] = "A:";
const char* buffer_ptr;
char* ptr;
char c;
/* convert legacy format into condset */
self->conditions = RC_ALLOC(rc_condset_t, parse);
memset(self->conditions, 0, sizeof(rc_condset_t));
next_clause = &self->conditions;
do {
num_measured_conditions = 0;
next = &self->conditions->conditions;
next_clause = &self->conditions->next;
for (;; ++(*memaddr)) {
buffer[0] = 'A'; /* reset to AddSource */
ptr = &buffer[2];
/* extract the next clause */
for (;; ++(*memaddr)) {
switch (**memaddr) {
case '_': /* add next */
case '$': /* maximum of */
case '\0': /* end of string */
case ':': /* end of leaderboard clause */
case ')': /* end of rich presence macro */
*ptr = '\0';
break;
case '*':
*ptr++ = '*';
buffer_ptr = *memaddr + 1;
if (*buffer_ptr == '-') {
buffer[0] = 'B'; /* change to SubSource */
++(*memaddr); /* don't copy sign */
++buffer_ptr; /* ignore sign when doing floating point check */
}
else if (*buffer_ptr == '+') {
++buffer_ptr; /* ignore sign when doing floating point check */
}
/* if it looks like a floating point number, add the 'f' prefix */
while (isdigit((unsigned char)*buffer_ptr))
++buffer_ptr;
if (*buffer_ptr == '.')
*ptr++ = 'f';
continue;
default:
*ptr++ = **memaddr;
continue;
/* count the number of joiners and add one to determine the number of clauses. */
num_measured_conditions = 1;
buffer_ptr = *memaddr;
while ((c = *buffer_ptr++) && c != '$') {
if (c == '_') {
++num_measured_conditions;
buffer[0] = 'A'; /* reset to AddSource */
}
else if (c == '*' && *buffer_ptr == '-') {
/* multiplication by a negative number will convert to SubSource */
++buffer_ptr;
buffer[0] = 'B';
}
break;
}
/* process the clause */
buffer_ptr = buffer;
cond = rc_parse_condition(&buffer_ptr, parse, 0);
/* if last condition is SubSource, we'll need to add a dummy condition for the Measured */
if (buffer[0] == 'B')
++num_measured_conditions;
condset_with_conditions = RC_ALLOC_WITH_TRAILING(rc_condset_with_trailing_conditions_t,
rc_condition_t, conditions, num_measured_conditions, parse);
if (parse->offset < 0)
return;
if (*buffer_ptr) {
/* whatever we copied as a single condition was not fully consumed */
parse->offset = RC_INVALID_COMPARISON;
return;
}
condset = (rc_condset_t*)condset_with_conditions;
memset(condset, 0, sizeof(*condset));
condset->num_measured_conditions = num_measured_conditions;
cond = &condset_with_conditions->conditions[0];
next = &condset->conditions;
for (;; ++(*memaddr)) {
buffer[0] = 'A'; /* reset to AddSource */
ptr = &buffer[2];
/* extract the next clause */
for (;; ++(*memaddr)) {
switch (**memaddr) {
case '_': /* add next */
case '$': /* maximum of */
case '\0': /* end of string */
case ':': /* end of leaderboard clause */
case ')': /* end of rich presence macro */
*ptr = '\0';
break;
case '*':
*ptr++ = '*';
buffer_ptr = *memaddr + 1;
if (*buffer_ptr == '-') {
buffer[0] = 'B'; /* change to SubSource */
++(*memaddr); /* don't copy sign */
++buffer_ptr; /* ignore sign when doing floating point check */
}
else if (*buffer_ptr == '+') {
++buffer_ptr; /* ignore sign when doing floating point check */
}
/* if it looks like a floating point number, add the 'f' prefix */
while (isdigit((unsigned char)*buffer_ptr))
++buffer_ptr;
if (*buffer_ptr == '.')
*ptr++ = 'f';
continue;
default:
*ptr++ = **memaddr;
continue;
}
switch (cond->oper) {
case RC_OPERATOR_MULT:
case RC_OPERATOR_DIV:
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_MOD:
case RC_OPERATOR_ADD:
case RC_OPERATOR_SUB:
case RC_OPERATOR_NONE:
break;
}
default:
/* process the clause */
if (!parse->buffer)
cond = &local_cond;
buffer_ptr = buffer;
rc_parse_condition_internal(cond, &buffer_ptr, parse);
if (parse->offset < 0)
return;
if (*buffer_ptr) {
/* whatever we copied as a single condition was not fully consumed */
parse->offset = RC_INVALID_COMPARISON;
return;
}
if (!rc_operator_is_modifying(cond->oper)) {
parse->offset = RC_INVALID_OPERATOR;
return;
}
}
*next = cond;
if (**memaddr == '_') {
/* add next */
*next = cond;
next = &cond->next;
continue;
if (**memaddr != '_') /* add next */
break;
rc_condition_update_parse_state(cond, parse);
++cond;
}
/* end of clause */
if (cond->type == RC_CONDITION_SUB_SOURCE) {
/* cannot change SubSource to Measured. add a dummy condition */
next = &cond->next;
rc_condition_update_parse_state(cond, parse);
if (parse->buffer)
++cond;
buffer_ptr = "A:0";
cond = rc_parse_condition(&buffer_ptr, parse, 0);
rc_parse_condition_internal(cond, &buffer_ptr, parse);
*next = cond;
next = &cond->next;
}
/* convert final AddSource condition to Measured */
cond->type = RC_CONDITION_MEASURED;
cond->next = 0;
cond->next = NULL;
rc_condition_update_parse_state(cond, parse);
/* finalize clause */
*next_clause = condset;
next_clause = &condset->next;
if (**memaddr != '$') {
/* end of valid string */
*next_clause = 0;
*next_clause = NULL;
break;
}
/* max of ($), start a new clause */
*next_clause = RC_ALLOC(rc_condset_t, parse);
if (parse->buffer) /* don't clear in sizing mode or pointer will break */
memset(*next_clause, 0, sizeof(rc_condset_t));
next = &(*next_clause)->conditions;
next_clause = &(*next_clause)->next;
}
++(*memaddr);
} while (1);
}
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
const uint8_t was_value = parse->is_value;
const rc_condition_t* condition;
parse->is_value = 1;
/* if it starts with a condition flag (M: A: B: C:), parse the conditions */
if ((*memaddr)[1] == ':') {
if ((*memaddr)[1] == ':')
rc_parse_cond_value(self, memaddr, parse);
}
else {
else
rc_parse_legacy_value(self, memaddr, parse);
if (parse->offset >= 0 && parse->buffer) {
self->name = "(unnamed)";
self->value.value = self->value.prior = 0;
self->value.memref_type = RC_MEMREF_TYPE_VALUE;
self->value.changed = 0;
self->has_memrefs = 0;
for (condition = self->conditions->conditions; condition; condition = condition->next) {
if (condition->type == RC_CONDITION_MEASURED) {
if (rc_operand_is_float(&condition->operand1)) {
self->value.size = RC_MEMSIZE_FLOAT;
self->value.type = RC_VALUE_TYPE_FLOAT;
}
else {
self->value.size = RC_MEMSIZE_32_BITS;
self->value.type = RC_VALUE_TYPE_UNSIGNED;
}
break;
}
}
}
self->name = "(unnamed)";
self->value.value = self->value.prior = 0;
self->value.changed = 0;
self->next = 0;
parse->is_value = was_value;
}
int rc_value_size(const char* memaddr) {
rc_value_t* self;
rc_parse_state_t parse;
rc_memref_t* first_memref;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &first_memref);
rc_value_with_memrefs_t* value;
rc_preparse_state_t preparse;
rc_init_preparse_state(&preparse);
self = RC_ALLOC(rc_value_t, &parse);
rc_parse_value_internal(self, &memaddr, &parse);
value = RC_ALLOC(rc_value_with_memrefs_t, &preparse.parse);
rc_parse_value_internal(&value->value, &memaddr, &preparse.parse);
rc_preparse_alloc_memrefs(NULL, &preparse);
rc_destroy_parse_state(&parse);
return parse.offset;
rc_destroy_preparse_state(&preparse);
return preparse.parse.offset;
}
rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
rc_value_t* self;
rc_parse_state_t parse;
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_parse_state(&parse, buffer, 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);
self = RC_ALLOC(rc_value_t, &parse);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_reset_parse_state(&preparse.parse, buffer);
value = RC_ALLOC(rc_value_with_memrefs_t, &preparse.parse);
rc_preparse_alloc_memrefs(&value->memrefs, &preparse);
rc_parse_value_internal(self, &memaddr, &parse);
rc_parse_value_internal(&value->value, &memaddr, &preparse.parse);
value->value.has_memrefs = 1;
rc_destroy_parse_state(&parse);
return (parse.offset >= 0) ? self : NULL;
rc_destroy_preparse_state(&preparse);
return (preparse.parse.offset >= 0) ? &value->value : NULL;
}
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L) {
static void rc_update_value_memrefs(rc_value_t* self, rc_peek_t peek, void* ud) {
if (self->has_memrefs) {
rc_value_with_memrefs_t* value = (rc_value_with_memrefs_t*)self;
rc_update_memref_values(&value->memrefs, peek, ud);
}
}
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;
rc_update_memref_values(self->memrefs, peek, ud);
rc_update_value_memrefs(self, peek, ud);
value->value.i32 = 0;
value->type = RC_VALUE_TYPE_SIGNED;
@ -236,7 +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;
eval_state.L = L;
rc_test_condset(condset, &eval_state);
@ -248,34 +312,32 @@ int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t
* NOTE: ResetIf only affects the current condset when used in values!
*/
rc_reset_condset(condset);
/* if the measured value came from a hit count, reset it too */
if (eval_state.measured_from_hits) {
eval_state.measured_value.value.u32 = 0;
eval_state.measured_value.type = RC_VALUE_TYPE_UNSIGNED;
}
}
if (!valid) {
/* capture the first valid measurement */
memcpy(value, &eval_state.measured_value, sizeof(*value));
valid = 1;
}
else {
/* multiple condsets are currently only used for the MAX_OF operation.
* only keep the condset's value if it's higher than the current highest value.
*/
if (rc_typed_value_compare(&eval_state.measured_value, value, RC_OPERATOR_GT))
if (eval_state.measured_value.type != RC_VALUE_TYPE_NONE) {
if (!valid) {
/* capture the first valid measurement, which may be negative */
memcpy(value, &eval_state.measured_value, sizeof(*value));
valid = 1;
}
else {
/* multiple condsets are currently only used for the MAX_OF operation.
* only keep the condset's value if it's higher than the current highest value.
*/
if (rc_typed_value_compare(&eval_state.measured_value, value, RC_OPERATOR_GT))
memcpy(value, &eval_state.measured_value, sizeof(*value));
}
}
}
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. */
@ -317,84 +379,81 @@ int rc_value_from_hits(rc_value_t* self)
return 0;
}
void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables) {
parse->variables = variables;
*variables = 0;
}
rc_value_t* rc_alloc_helper_variable(const char* memaddr, size_t memaddr_len, rc_parse_state_t* parse)
{
rc_value_t** variables = parse->variables;
rc_value_t* rc_alloc_variable(const char* memaddr, size_t memaddr_len, rc_parse_state_t* parse) {
rc_value_t** value_ptr = parse->variables;
rc_value_t* value;
const char* name;
uint32_t measured_target;
while ((value = *variables) != NULL) {
if (!value_ptr)
return NULL;
while (*value_ptr) {
value = *value_ptr;
if (strncmp(value->name, memaddr, memaddr_len) == 0 && value->name[memaddr_len] == 0)
return value;
variables = &value->next;
value_ptr = &value->next;
}
value = RC_ALLOC_SCRATCH(rc_value_t, parse);
memset(&value->value, 0, sizeof(value->value));
value->value.size = RC_MEMSIZE_VARIABLE;
value->memrefs = NULL;
/* capture name before calling parse as parse will update memaddr pointer */
name = rc_alloc_str(parse, memaddr, memaddr_len);
if (!name)
return NULL;
/* no match found, create a new entry */
value = RC_ALLOC_SCRATCH(rc_value_t, parse);
memset(value, 0, sizeof(value->value));
value->value.size = RC_MEMSIZE_VARIABLE;
value->next = NULL;
/* the helper variable likely has a Measured condition. capture the current measured_target so we can restore it
* after generating the variable so the variable's Measured target doesn't conflict with the rest of the trigger. */
measured_target = parse->measured_target;
/* disable variable resolution when defining a variable to prevent infinite recursion */
variables = parse->variables;
parse->variables = NULL;
rc_parse_value_internal(value, &memaddr, parse);
parse->variables = variables;
/* restore the measured target */
parse->measured_target = measured_target;
/* store name after calling parse as parse will set name to (unnamed) */
value->name = name;
/* append the new variable to the end of the list (have to re-evaluate in case any others were added) */
while (*variables != NULL)
variables = &(*variables)->next;
*variables = value;
*value_ptr = value;
return value;
}
void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_State* L) {
uint32_t rc_count_values(const rc_value_t* values) {
uint32_t count = 0;
while (values) {
++count;
values = values->next;
}
return count;
}
void rc_update_values(rc_value_t* values, rc_peek_t peek, void* ud) {
rc_typed_value_t result;
while (variable) {
if (rc_evaluate_value_typed(variable, &result, peek, ud, L)) {
rc_value_t* value = values;
for (; value; value = value->next) {
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(&variable->value, result.value.u32);
variable->value.type = result.type;
rc_update_memref_value(&value->value, result.value.u32);
value->value.type = result.type;
}
variable = variable->next;
}
}
void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref) {
value->value.u32 = memref->value;
void rc_reset_values(rc_value_t* values) {
rc_value_t* value = values;
if (memref->size == RC_MEMSIZE_VARIABLE) {
/* a variable can be any of the supported types, but the raw data was copied into u32 */
value->type = memref->type;
}
else {
/* not a variable, only u32 is supported */
value->type = RC_VALUE_TYPE_UNSIGNED;
}
for (; value; value = value->next)
rc_reset_value(value);
}
void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref) {
/* raw value is always u32, type can mark it as something else */
value->value.u32 = memref->value;
value->type = memref->type;
}
void rc_typed_value_convert(rc_typed_value_t* value, char new_type) {
@ -483,8 +542,12 @@ void rc_typed_value_negate(rc_typed_value_t* value) {
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount) {
rc_typed_value_t converted;
if (amount->type != value->type && value->type != RC_VALUE_TYPE_NONE)
amount = rc_typed_value_convert_into(&converted, amount, value->type);
if (amount->type != value->type && value->type != RC_VALUE_TYPE_NONE) {
if (amount->type == RC_VALUE_TYPE_FLOAT)
rc_typed_value_convert(value, RC_VALUE_TYPE_FLOAT);
else
amount = rc_typed_value_convert_into(&converted, amount, value->type);
}
switch (value->type)
{
@ -651,56 +714,56 @@ void rc_typed_value_modulus(rc_typed_value_t* value, const rc_typed_value_t* amo
switch (amount->type)
{
case RC_VALUE_TYPE_UNSIGNED:
if (amount->value.u32 == 0) { /* divide by zero */
value->type = RC_VALUE_TYPE_NONE;
return;
}
case RC_VALUE_TYPE_UNSIGNED:
if (amount->value.u32 == 0) { /* divide by zero */
value->type = RC_VALUE_TYPE_NONE;
return;
}
switch (value->type) {
case RC_VALUE_TYPE_UNSIGNED: /* integer math */
value->value.u32 %= amount->value.u32;
return;
case RC_VALUE_TYPE_SIGNED: /* integer math */
value->value.i32 %= (int)amount->value.u32;
return;
case RC_VALUE_TYPE_FLOAT:
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
switch (value->type) {
case RC_VALUE_TYPE_UNSIGNED: /* integer math */
value->value.u32 %= amount->value.u32;
return;
case RC_VALUE_TYPE_SIGNED: /* integer math */
value->value.i32 %= (int)amount->value.u32;
return;
case RC_VALUE_TYPE_FLOAT:
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
break;
case RC_VALUE_TYPE_SIGNED:
if (amount->value.i32 == 0) { /* divide by zero */
value->type = RC_VALUE_TYPE_NONE;
return;
}
switch (value->type) {
case RC_VALUE_TYPE_SIGNED: /* integer math */
value->value.i32 %= amount->value.i32;
return;
case RC_VALUE_TYPE_UNSIGNED: /* integer math */
value->value.u32 %= (unsigned)amount->value.i32;
return;
case RC_VALUE_TYPE_FLOAT:
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
break;
case RC_VALUE_TYPE_FLOAT:
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
break;
case RC_VALUE_TYPE_SIGNED:
if (amount->value.i32 == 0) { /* divide by zero */
value->type = RC_VALUE_TYPE_NONE;
return;
}
switch (value->type) {
case RC_VALUE_TYPE_SIGNED: /* integer math */
value->value.i32 %= amount->value.i32;
return;
case RC_VALUE_TYPE_UNSIGNED: /* integer math */
value->value.u32 %= (unsigned)amount->value.i32;
return;
case RC_VALUE_TYPE_FLOAT:
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
break;
case RC_VALUE_TYPE_FLOAT:
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
if (amount->value.f32 == 0.0) { /* divide by zero */
@ -712,6 +775,44 @@ void rc_typed_value_modulus(rc_typed_value_t* value, const rc_typed_value_t* amo
value->value.f32 = (float)fmod(value->value.f32, amount->value.f32);
}
void rc_typed_value_combine(rc_typed_value_t* value, rc_typed_value_t* amount, uint8_t oper) {
switch (oper) {
case RC_OPERATOR_MULT:
rc_typed_value_multiply(value, amount);
break;
case RC_OPERATOR_DIV:
rc_typed_value_divide(value, amount);
break;
case RC_OPERATOR_AND:
rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED);
rc_typed_value_convert(amount, RC_VALUE_TYPE_UNSIGNED);
value->value.u32 &= amount->value.u32;
break;
case RC_OPERATOR_XOR:
rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED);
rc_typed_value_convert(amount, RC_VALUE_TYPE_UNSIGNED);
value->value.u32 ^= amount->value.u32;
break;
case RC_OPERATOR_MOD:
rc_typed_value_modulus(value, amount);
break;
case RC_OPERATOR_ADD:
rc_typed_value_add(value, amount);
break;
case RC_OPERATOR_SUB:
rc_typed_value_negate(amount);
rc_typed_value_add(value, amount);
break;
}
}
static int rc_typed_value_compare_floats(float f1, float f2, char oper) {
if (f1 == f2) {
/* exactly equal */

View File

@ -1,4 +1,4 @@
#include "rc_hash.h"
#include "rc_hash_internal.h"
#include "../rc_compat.h"
@ -6,31 +6,6 @@
#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);
@ -43,7 +18,7 @@ static int cdreader_get_sector(uint8_t header[16])
return ((minutes * 60) + seconds) * 75 + frames - 150;
}
static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
static void cdreader_determine_sector_size(rc_hash_cdrom_track_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.
@ -61,8 +36,8 @@ static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
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))
cdrom->file_reader->seek(cdrom->file_handle, toc_sector * 2352 + cdrom->file_track_offset, SEEK_SET);
if (cdrom->file_reader->read(cdrom->file_handle, header, sizeof(header)) < sizeof(header))
return;
if (memcmp(header, sync_pattern, 12) == 0)
@ -78,8 +53,8 @@ static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
}
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));
cdrom->file_reader->seek(cdrom->file_handle, toc_sector * 2336 + cdrom->file_track_offset, SEEK_SET);
cdrom->file_reader->read(cdrom->file_handle, header, sizeof(header));
if (memcmp(header, sync_pattern, 12) == 0)
{
@ -94,8 +69,8 @@ static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
}
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));
cdrom->file_reader->seek(cdrom->file_handle, toc_sector * 2048 + cdrom->file_track_offset, SEEK_SET);
cdrom->file_reader->read(cdrom->file_handle, header, sizeof(header));
if (memcmp(&header[1], "CD001", 5) == 0)
{
@ -106,26 +81,24 @@ static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
}
}
static void* cdreader_open_bin_track(const char* path, uint32_t track)
static void* cdreader_open_bin_track(const char* path, uint32_t track, const rc_hash_iterator_t* iterator)
{
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");
rc_hash_cdrom_track_t* cdrom;
if (track > 1) {
rc_hash_iterator_verbose(iterator, "Cannot locate secondary tracks without a cue sheet");
return NULL;
}
file_handle = rc_file_open(path);
file_handle = iterator->callbacks.filereader.open(path);
if (!file_handle)
return NULL;
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
cdrom = (rc_hash_cdrom_track_t*)calloc(1, sizeof(*cdrom));
if (!cdrom)
return NULL;
cdrom->file_reader = &iterator->callbacks.filereader;
cdrom->file_handle = file_handle;
#ifndef NDEBUG
cdrom->track_id = track;
@ -137,8 +110,8 @@ static void* cdreader_open_bin_track(const char* path, uint32_t track)
{
int64_t size;
rc_file_seek(cdrom->file_handle, 0, SEEK_END);
size = rc_file_tell(cdrom->file_handle);
iterator->callbacks.filereader.seek(file_handle, 0, SEEK_END);
size = iterator->callbacks.filereader.tell(file_handle);
if ((size % 2352) == 0)
{
@ -160,10 +133,11 @@ static void* cdreader_open_bin_track(const char* path, uint32_t track)
}
else
{
if (iterator->callbacks.filereader.close)
iterator->callbacks.filereader.close(file_handle);
free(cdrom);
if (verbose_message_callback)
verbose_message_callback("Could not determine sector size");
rc_hash_iterator_verbose(iterator, "Could not determine sector size");
return NULL;
}
@ -172,9 +146,9 @@ static void* cdreader_open_bin_track(const char* path, uint32_t track)
return cdrom;
}
static int cdreader_open_bin(struct cdrom_t* cdrom, const char* path, const char* mode)
static int cdreader_open_bin(rc_hash_cdrom_track_t* cdrom, const char* path, const char* mode)
{
cdrom->file_handle = rc_file_open(path);
cdrom->file_handle = cdrom->file_reader->open(path);
if (!cdrom->file_handle)
return 0;
@ -230,7 +204,7 @@ static int cdreader_open_bin(struct cdrom_t* cdrom, const char* path, const char
return (cdrom->sector_size != 0);
}
static char* cdreader_get_bin_path(const char* cue_path, const char* bin_name)
static char* cdreader_get_bin_path(const char* cue_path, const char* bin_name, const rc_hash_iterator_t* iterator)
{
const char* filename = rc_path_get_filename(cue_path);
const size_t bin_name_len = strlen(bin_name);
@ -240,9 +214,7 @@ static char* cdreader_get_bin_path(const char* cue_path, const char* bin_name)
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);
rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", (unsigned)needed);
}
else
{
@ -253,33 +225,29 @@ static char* cdreader_get_bin_path(const char* cue_path, const char* bin_name)
return bin_filename;
}
static int64_t cdreader_get_bin_size(const char* cue_path, const char* bin_name)
static int64_t cdreader_get_bin_size(const char* cue_path, const char* bin_name, const rc_hash_iterator_t* iterator)
{
int64_t size = 0;
char* bin_filename = cdreader_get_bin_path(cue_path, bin_name);
char* bin_filename = cdreader_get_bin_path(cue_path, bin_name, iterator);
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)
void* handle = iterator->callbacks.filereader.open(bin_filename);
if (handle)
{
rc_file_seek(file_handle, 0, SEEK_END);
size = rc_file_tell(file_handle);
rc_file_close(file_handle);
iterator->callbacks.filereader.seek(handle, 0, SEEK_END);
size = iterator->callbacks.filereader.tell(handle);
if (iterator->callbacks.filereader.close)
iterator->callbacks.filereader.close(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)
static void* cdreader_open_cue_track(const char* path, uint32_t track, const rc_hash_iterator_t* iterator)
{
void* cue_handle;
int64_t cue_offset = 0;
@ -289,7 +257,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
int done = 0;
int session = 1;
size_t num_read = 0;
struct cdrom_t* cdrom = NULL;
rc_hash_cdrom_track_t* cdrom = NULL;
struct track_t
{
@ -305,7 +273,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
char filename[256];
} current_track, previous_track, largest_track;
cue_handle = rc_file_open(path);
cue_handle = iterator->callbacks.filereader.open(path);
if (!cue_handle)
return NULL;
@ -315,7 +283,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
do
{
num_read = rc_file_read(cue_handle, buffer, sizeof(buffer) - 1);
num_read = iterator->callbacks.filereader.read(cue_handle, buffer, sizeof(buffer) - 1);
if (num_read == 0)
break;
@ -369,18 +337,15 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
{
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)",
rc_hash_iterator_verbose_formatted(iterator, "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)
@ -440,7 +405,8 @@ static void* cdreader_open_cue_track(const char* path, uint32_t 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;
const int64_t bin_size = cdreader_get_bin_size(path, previous_track.filename, iterator);
const uint32_t file_sector_count = (uint32_t)bin_size / previous_track.sector_size;
previous_track.sector_count = file_sector_count - previous_track.first_sector;
}
@ -501,17 +467,19 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
break;
cue_offset += (ptr - buffer);
rc_file_seek(cue_handle, cue_offset, SEEK_SET);
iterator->callbacks.filereader.seek(cue_handle, cue_offset, SEEK_SET);
} while (1);
rc_file_close(cue_handle);
if (iterator->callbacks.filereader.close)
iterator->callbacks.filereader.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;
const int64_t bin_size = cdreader_get_bin_size(path, current_track.filename, iterator);
const uint32_t file_sector_count = (uint32_t)bin_size / current_track.sector_size;
current_track.sector_count = file_sector_count - current_track.first_sector;
if (largest_track.sector_count > current_track.sector_count)
@ -531,14 +499,14 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
if (current_track.id == track)
{
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
cdrom = (rc_hash_cdrom_track_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);
rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
return NULL;
}
cdrom->file_reader = &iterator->callbacks.filereader;
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;
@ -547,36 +515,29 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
#endif
/* verify existance of bin file */
bin_filename = cdreader_get_bin_path(path, current_track.filename);
bin_filename = cdreader_get_bin_path(path, current_track.filename, iterator);
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);
}
if (cdrom->track_pregap_sectors)
rc_hash_iterator_verbose_formatted(iterator, "Opened track %d (sector size %d, %d pregap sectors)",
track, cdrom->sector_size, cdrom->track_pregap_sectors);
else
rc_hash_iterator_verbose_formatted(iterator, "Opened track %d (sector size %d)", track, cdrom->sector_size);
}
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);
cdrom->file_reader->close(cdrom->file_handle);
rc_hash_iterator_error_formatted(iterator, "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_iterator_error_formatted(iterator, "Could not open %s", bin_filename);
}
rc_hash_error((const char*)buffer);
free(cdrom);
cdrom = NULL;
}
@ -588,7 +549,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
return cdrom;
}
static void* cdreader_open_gdi_track(const char* path, uint32_t track)
static void* cdreader_open_gdi_track(const char* path, uint32_t track, const rc_hash_iterator_t* iterator)
{
void* file_handle;
char buffer[1024];
@ -611,16 +572,16 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
int found = 0;
size_t num_read = 0;
int64_t file_offset = 0;
struct cdrom_t* cdrom = NULL;
rc_hash_cdrom_track_t* cdrom = NULL;
file_handle = rc_file_open(path);
file_handle = iterator->callbacks.filereader.open(path);
if (!file_handle)
return NULL;
file[0] = '\0';
do
{
num_read = rc_file_read(file_handle, buffer, sizeof(buffer) - 1);
num_read = iterator->callbacks.filereader.read(file_handle, buffer, sizeof(buffer) - 1);
if (num_read == 0)
break;
@ -715,7 +676,7 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
}
else if (track == RC_HASH_CDTRACK_LARGEST && track_type == 4)
{
track_size = cdreader_get_bin_size(path, file);
track_size = cdreader_get_bin_size(path, file, iterator);
if (track_size > largest_track_size)
{
largest_track_size = track_size;
@ -731,20 +692,22 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
break;
file_offset += (ptr - buffer);
rc_file_seek(file_handle, file_offset, SEEK_SET);
iterator->callbacks.filereader.seek(file_handle, file_offset, SEEK_SET);
} while (1);
rc_file_close(file_handle);
if (iterator->callbacks.filereader.close)
iterator->callbacks.filereader.close(file_handle);
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
cdrom = (rc_hash_cdrom_track_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);
rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
return NULL;
}
cdrom->file_reader = &iterator->callbacks.filereader;
/* 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)
@ -762,7 +725,7 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
*ptr++ = *ptr2++;
*ptr = '\0';
bin_path = cdreader_get_bin_path(path, file);
bin_path = cdreader_get_bin_path(path, file, iterator);
if (cdreader_open_bin(cdrom, bin_path, mode))
{
cdrom->track_pregap_sectors = 0;
@ -771,16 +734,11 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
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);
}
rc_hash_iterator_verbose_formatted(iterator, "Opened track %d (sector size %d)", current_track, cdrom->sector_size);
}
else
{
snprintf((char*)buffer, sizeof(buffer), "Could not open %s", bin_path);
rc_hash_error((const char*)buffer);
rc_hash_iterator_error_formatted(iterator, "Could not open %s", bin_path);
free(cdrom);
cdrom = NULL;
@ -791,18 +749,18 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
return cdrom;
}
static void* cdreader_open_track(const char* path, uint32_t track)
static void* cdreader_open_track_iterator(const char* path, uint32_t track, const rc_hash_iterator_t* iterator)
{
/* 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);
return cdreader_open_cue_track(path, track, iterator);
if (rc_path_compare_extension(path, "gdi"))
return cdreader_open_gdi_track(path, track);
return cdreader_open_gdi_track(path, track, iterator);
return cdreader_open_bin_track(path, track);
return cdreader_open_bin_track(path, track, iterator);
}
static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes)
@ -811,7 +769,7 @@ static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* bu
size_t num_read, total_read = 0;
uint8_t* buffer_ptr = (uint8_t*)buffer;
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
rc_hash_cdrom_track_t* cdrom = (rc_hash_cdrom_track_t*)track_handle;
if (!cdrom)
return 0;
@ -823,8 +781,8 @@ static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* bu
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);
cdrom->file_reader->seek(cdrom->file_handle, sector_start, SEEK_SET);
num_read = cdrom->file_reader->read(cdrom->file_handle, buffer_ptr, cdrom->raw_data_size);
total_read += num_read;
if (num_read < (size_t)cdrom->raw_data_size)
@ -835,8 +793,8 @@ static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* bu
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);
cdrom->file_reader->seek(cdrom->file_handle, sector_start, SEEK_SET);
num_read = cdrom->file_reader->read(cdrom->file_handle, buffer_ptr, (int)requested_bytes);
total_read += num_read;
return total_read;
@ -844,11 +802,11 @@ static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* bu
static void cdreader_close_track(void* track_handle)
{
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
rc_hash_cdrom_track_t* cdrom = (rc_hash_cdrom_track_t*)track_handle;
if (cdrom)
{
if (cdrom->file_handle)
rc_file_close(cdrom->file_handle);
if (cdrom->file_handle && cdrom->file_reader->close)
cdrom->file_reader->close(cdrom->file_handle);
free(track_handle);
}
@ -856,7 +814,7 @@ static void cdreader_close_track(void* track_handle)
static uint32_t cdreader_first_track_sector(void* track_handle)
{
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
rc_hash_cdrom_track_t* cdrom = (rc_hash_cdrom_track_t*)track_handle;
if (cdrom)
return cdrom->track_first_sector + cdrom->track_pregap_sectors;
@ -865,10 +823,11 @@ static uint32_t cdreader_first_track_sector(void* track_handle)
void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader)
{
cdreader->open_track = cdreader_open_track;
cdreader->open_track = NULL;
cdreader->read_sector = cdreader_read_sector;
cdreader->close_track = cdreader_close_track;
cdreader->first_track_sector = cdreader_first_track_sector;
cdreader->open_track_iterator = cdreader_open_track_iterator;
}
void rc_hash_init_default_cdreader(void)

File diff suppressed because it is too large Load Diff

1340
deps/rcheevos/src/rhash/hash_disc.c vendored Normal file

File diff suppressed because it is too large Load Diff

566
deps/rcheevos/src/rhash/hash_encrypted.c vendored Normal file
View File

@ -0,0 +1,566 @@
#include "rc_hash_internal.h"
#include "../rc_compat.h"
#include "aes.h"
/* ===================================================== */
static rc_hash_3ds_get_cia_normal_key_func _3ds_get_cia_normal_key_func = NULL;
static rc_hash_3ds_get_ncch_normal_keys_func _3ds_get_ncch_normal_keys_func = NULL;
void rc_hash_reset_iterator_encrypted(rc_hash_iterator_t* iterator)
{
iterator->callbacks.encryption.get_3ds_cia_normal_key = _3ds_get_cia_normal_key_func;
iterator->callbacks.encryption.get_3ds_ncch_normal_keys = _3ds_get_ncch_normal_keys_func;
}
void rc_hash_init_3ds_get_cia_normal_key_func(rc_hash_3ds_get_cia_normal_key_func func)
{
_3ds_get_cia_normal_key_func = func;
}
void rc_hash_init_3ds_get_ncch_normal_keys_func(rc_hash_3ds_get_ncch_normal_keys_func func)
{
_3ds_get_ncch_normal_keys_func = func;
}
/* ===================================================== */
static int rc_hash_nintendo_3ds_ncch(md5_state_t* md5, void* file_handle, uint8_t header[0x200],
struct AES_ctx* cia_aes, const rc_hash_iterator_t* iterator)
{
struct AES_ctx ncch_aes;
uint8_t* hash_buffer;
uint64_t exefs_offset, exefs_real_size;
uint32_t exefs_buffer_size;
uint8_t primary_key[AES_KEYLEN], secondary_key[AES_KEYLEN];
uint8_t fixed_key_flag, no_crypto_flag, seed_crypto_flag;
uint8_t crypto_method, secondary_key_x_slot;
uint16_t ncch_version;
uint32_t i;
uint8_t primary_key_y[AES_KEYLEN], program_id[sizeof(uint64_t)];
uint8_t iv[AES_BLOCKLEN], cia_iv[AES_BLOCKLEN];
uint8_t exefs_section_name[8];
uint64_t exefs_section_offset, exefs_section_size;
exefs_offset = ((uint32_t)header[0x1A3] << 24) | (header[0x1A2] << 16) | (header[0x1A1] << 8) | header[0x1A0];
exefs_real_size = ((uint32_t)header[0x1A7] << 24) | (header[0x1A6] << 16) | (header[0x1A5] << 8) | header[0x1A4];
/* Offset and size are in "media units" (1 media unit = 0x200 bytes) */
exefs_offset *= 0x200;
exefs_real_size *= 0x200;
if (exefs_real_size > MAX_BUFFER_SIZE)
exefs_buffer_size = MAX_BUFFER_SIZE;
else
exefs_buffer_size = (uint32_t)exefs_real_size;
/* This region is technically optional, but it should always be present for executable content (i.e. games) */
if (exefs_offset == 0 || exefs_real_size == 0)
return rc_hash_iterator_error(iterator, "ExeFS was not available");
/* NCCH flag 7 is a bitfield of various crypto related flags */
fixed_key_flag = header[0x188 + 7] & 0x01;
no_crypto_flag = header[0x188 + 7] & 0x04;
seed_crypto_flag = header[0x188 + 7] & 0x20;
ncch_version = (header[0x113] << 8) | header[0x112];
if (no_crypto_flag == 0) {
rc_hash_iterator_verbose(iterator, "Encrypted NCCH detected");
if (fixed_key_flag != 0) {
/* Fixed crypto key means all 0s for both keys */
memset(primary_key, 0, sizeof(primary_key));
memset(secondary_key, 0, sizeof(secondary_key));
rc_hash_iterator_verbose(iterator, "Using fixed key crypto");
}
else {
if (iterator->callbacks.encryption.get_3ds_ncch_normal_keys == NULL)
return rc_hash_iterator_error(iterator, "An encrypted NCCH was detected, but the NCCH normal keys callback was not set");
/* Primary key y is just the first 16 bytes of the header */
memcpy(primary_key_y, header, sizeof(primary_key_y));
/* NCCH flag 3 indicates which secondary key x slot is used */
crypto_method = header[0x188 + 3];
switch (crypto_method) {
case 0x00:
rc_hash_iterator_verbose(iterator, "Using NCCH crypto method v1");
secondary_key_x_slot = 0x2C;
break;
case 0x01:
rc_hash_iterator_verbose(iterator, "Using NCCH crypto method v2");
secondary_key_x_slot = 0x25;
break;
case 0x0A:
rc_hash_iterator_verbose(iterator, "Using NCCH crypto method v3");
secondary_key_x_slot = 0x18;
break;
case 0x0B:
rc_hash_iterator_verbose(iterator, "Using NCCH crypto method v4");
secondary_key_x_slot = 0x1B;
break;
default:
return rc_hash_iterator_error_formatted(iterator, "Invalid crypto method %02X", (unsigned)crypto_method);
}
/* We only need the program id if we're doing seed crypto */
if (seed_crypto_flag != 0) {
rc_hash_iterator_verbose(iterator, "Using seed crypto");
memcpy(program_id, &header[0x118], sizeof(program_id));
}
if (iterator->callbacks.encryption.get_3ds_ncch_normal_keys(primary_key_y, secondary_key_x_slot, seed_crypto_flag != 0 ? program_id : NULL, primary_key, secondary_key) == 0)
return rc_hash_iterator_error(iterator, "Could not obtain NCCH normal keys");
}
switch (ncch_version) {
case 0:
case 2:
rc_hash_iterator_verbose(iterator, "Detected NCCH version 0/2");
for (i = 0; i < 8; i++) {
/* First 8 bytes is the partition id in reverse byte order */
iv[7 - i] = header[0x108 + i];
}
/* Magic number for ExeFS */
iv[8] = 2;
/* Rest of the bytes are 0 */
memset(&iv[9], 0, sizeof(iv) - 9);
break;
case 1:
rc_hash_iterator_verbose(iterator, "Detected NCCH version 1");
for (i = 0; i < 8; i++) {
/* First 8 bytes is the partition id in normal byte order */
iv[i] = header[0x108 + i];
}
/* Next 4 bytes are 0 */
memset(&iv[8], 0, 4);
/* Last 4 bytes is the ExeFS byte offset in big endian */
iv[12] = (exefs_offset >> 24) & 0xFF;
iv[13] = (exefs_offset >> 16) & 0xFF;
iv[14] = (exefs_offset >> 8) & 0xFF;
iv[15] = exefs_offset & 0xFF;
break;
default:
return rc_hash_iterator_error_formatted(iterator, "Invalid NCCH version %04X", (unsigned)ncch_version);
}
}
/* ASSERT: file position must be +0x200 from start of NCCH (i.e. end of header) */
exefs_offset -= 0x200;
if (cia_aes) {
/* CBC decryption works by setting the IV to the encrypted previous block.
* Normally this means we would need to decrypt the data between the header and the ExeFS so the CIA AES state is correct.
* However, we can abuse how CBC decryption works and just set the IV to last block we would otherwise decrypt.
* We don't care about the data betweeen the header and ExeFS, so this works fine. */
rc_file_seek(iterator, file_handle, (int64_t)exefs_offset - AES_BLOCKLEN, SEEK_CUR);
if (rc_file_read(iterator, file_handle, cia_iv, AES_BLOCKLEN) != AES_BLOCKLEN)
return rc_hash_iterator_error(iterator, "Could not read NCCH data");
AES_ctx_set_iv(cia_aes, cia_iv);
}
else {
/* No encryption present, just skip over the in-between data */
rc_file_seek(iterator, file_handle, (int64_t)exefs_offset, SEEK_CUR);
}
hash_buffer = (uint8_t*)malloc(exefs_buffer_size);
if (!hash_buffer)
return rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", (unsigned)exefs_buffer_size);
/* Clear out crypto flags to ensure we get the same hash for decrypted and encrypted ROMs */
memset(&header[0x114], 0, 4);
header[0x188 + 3] = 0;
header[0x188 + 7] &= ~(0x20 | 0x04 | 0x01);
rc_hash_iterator_verbose(iterator, "Hashing 512 byte NCCH header");
md5_append(md5, header, 0x200);
rc_hash_iterator_verbose_formatted(iterator, "Hashing %u bytes for ExeFS (at NCCH offset %08X%08X)",
(unsigned)exefs_buffer_size, (unsigned)(exefs_offset >> 32), (unsigned)exefs_offset);
if (rc_file_read(iterator, file_handle, hash_buffer, exefs_buffer_size) != exefs_buffer_size) {
free(hash_buffer);
return rc_hash_iterator_error(iterator, "Could not read ExeFS data");
}
if (cia_aes) {
rc_hash_iterator_verbose(iterator, "Performing CIA decryption for ExeFS");
AES_CBC_decrypt_buffer(cia_aes, hash_buffer, exefs_buffer_size);
}
if (no_crypto_flag == 0) {
rc_hash_iterator_verbose(iterator, "Performing NCCH decryption for ExeFS");
AES_init_ctx_iv(&ncch_aes, primary_key, iv);
AES_CTR_xcrypt_buffer(&ncch_aes, hash_buffer, 0x200);
for (i = 0; i < 8; i++) {
memcpy(exefs_section_name, &hash_buffer[i * 16], sizeof(exefs_section_name));
exefs_section_offset = ((uint32_t)hash_buffer[i * 16 + 11] << 24) | (hash_buffer[i * 16 + 10] << 16) | (hash_buffer[i * 16 + 9] << 8) | hash_buffer[i * 16 + 8];
exefs_section_size = ((uint32_t)hash_buffer[i * 16 + 15] << 24) | (hash_buffer[i * 16 + 14] << 16) | (hash_buffer[i * 16 + 13] << 8) | hash_buffer[i * 16 + 12];
/* 0 size indicates an unused section */
if (exefs_section_size == 0)
continue;
/* Offsets must be aligned by a media unit */
if (exefs_section_offset & 0x1FF)
return rc_hash_iterator_error(iterator, "ExeFS section offset is misaligned");
/* Offset is relative to the end of the header */
exefs_section_offset += 0x200;
/* Check against malformed sections */
if (exefs_section_offset + ((exefs_section_size + 0x1FF) & ~(uint64_t)0x1FF) > (uint64_t)exefs_real_size)
return rc_hash_iterator_error(iterator, "ExeFS section would overflow");
if (memcmp(exefs_section_name, "icon", 4) == 0 ||
memcmp(exefs_section_name, "banner", 6) == 0) {
/* Align size up by a media unit */
exefs_section_size = (exefs_section_size + 0x1FF) & ~(uint64_t)0x1FF;
AES_init_ctx(&ncch_aes, primary_key);
}
else {
/* We don't align size up here, as the padding bytes will use the primary key rather than the secondary key */
AES_init_ctx(&ncch_aes, secondary_key);
}
/* In theory, the section offset + size could be greater than the buffer size */
/* In practice, this likely never occurs, but just in case it does, ignore the section or constrict the size */
if (exefs_section_offset + exefs_section_size > exefs_buffer_size) {
if (exefs_section_offset >= exefs_buffer_size)
continue;
exefs_section_size = exefs_buffer_size - exefs_section_offset;
}
exefs_section_name[7] = '\0';
rc_hash_iterator_verbose_formatted(iterator, "Decrypting ExeFS file %s at ExeFS offset %08X with size %08X",
(const char*)exefs_section_name, (unsigned)exefs_section_offset, (unsigned)exefs_section_size);
AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], exefs_section_size & ~(uint64_t)0xF);
if (exefs_section_size & 0x1FF) {
/* Handle padding bytes, these always use the primary key */
exefs_section_offset += exefs_section_size;
exefs_section_size = 0x200 - (exefs_section_size & 0x1FF);
rc_hash_iterator_verbose_formatted(iterator, "Decrypting ExeFS padding at ExeFS offset %08X with size %08X",
(unsigned)exefs_section_offset, (unsigned)exefs_section_size);
/* Align our decryption start to an AES block boundary */
if (exefs_section_size & 0xF) {
/* We're a little evil here re-using the IV like this, but this seems to be the best way to deal with this... */
memcpy(iv, ncch_aes.Iv, sizeof(iv));
exefs_section_offset &= ~(uint64_t)0xF;
/* First decrypt these last bytes using the secondary key */
AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], 0x10 - (exefs_section_size & 0xF));
/* Now re-encrypt these bytes using the primary key */
AES_init_ctx_iv(&ncch_aes, primary_key, iv);
AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], 0x10 - (exefs_section_size & 0xF));
/* All of the padding can now be decrypted using the primary key */
AES_ctx_set_iv(&ncch_aes, iv);
exefs_section_size += 0x10 - (exefs_section_size & 0xF);
}
AES_init_ctx(&ncch_aes, primary_key);
AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], (size_t)exefs_section_size);
}
}
}
md5_append(md5, hash_buffer, exefs_buffer_size);
free(hash_buffer);
return 1;
}
static uint32_t rc_hash_nintendo_3ds_cia_signature_size(uint8_t header[0x200], const rc_hash_iterator_t* iterator)
{
uint32_t signature_type;
signature_type = ((uint32_t)header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3];
switch (signature_type) {
case 0x010000:
case 0x010003:
return 0x200 + 0x3C;
case 0x010001:
case 0x010004:
return 0x100 + 0x3C;
case 0x010002:
case 0x010005:
return 0x3C + 0x40;
default:
return rc_hash_iterator_error_formatted(iterator, "Invalid signature type %08X", (unsigned)signature_type);
}
}
static int rc_hash_nintendo_3ds_cia(md5_state_t* md5, void* file_handle, uint8_t header[0x200],
const rc_hash_iterator_t* iterator)
{
const uint32_t CIA_HEADER_SIZE = 0x2020; /* Yes, this is larger than the header[0x200], but we only use the beginning of the header */
const uint64_t CIA_ALIGNMENT_MASK = 64 - 1; /* sizes are aligned by 64 bytes */
struct AES_ctx aes;
uint8_t iv[AES_BLOCKLEN], normal_key[AES_KEYLEN], title_key[AES_KEYLEN], title_id[sizeof(uint64_t)];
uint32_t cert_size, tik_size, tmd_size;
int64_t cert_offset, tik_offset, tmd_offset, content_offset;
uint32_t signature_size, i;
uint16_t content_count;
uint8_t common_key_index;
cert_size = ((uint32_t)header[0x0B] << 24) | (header[0x0A] << 16) | (header[0x09] << 8) | header[0x08];
tik_size = ((uint32_t)header[0x0F] << 24) | (header[0x0E] << 16) | (header[0x0D] << 8) | header[0x0C];
tmd_size = ((uint32_t)header[0x13] << 24) | (header[0x12] << 16) | (header[0x11] << 8) | header[0x10];
cert_offset = (CIA_HEADER_SIZE + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK;
tik_offset = (cert_offset + cert_size + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK;
tmd_offset = (tik_offset + tik_size + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK;
content_offset = (tmd_offset + tmd_size + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK;
/* Check if this CIA is encrypted, if it isn't, we can hash it right away */
rc_file_seek(iterator, file_handle, tmd_offset, SEEK_SET);
if (rc_file_read(iterator, file_handle, header, 4) != 4)
return rc_hash_iterator_error(iterator, "Could not read TMD signature type");
signature_size = rc_hash_nintendo_3ds_cia_signature_size(header, iterator);
if (signature_size == 0)
return 0; /* rc_hash_nintendo_3ds_cia_signature_size will call rc_hash_error, so we don't need to do so here */
rc_file_seek(iterator, file_handle, signature_size + 0x9E, SEEK_CUR);
if (rc_file_read(iterator, file_handle, header, 2) != 2)
return rc_hash_iterator_error(iterator, "Could not read TMD content count");
content_count = (header[0] << 8) | header[1];
rc_file_seek(iterator, file_handle, 0x9C4 - 0x9E - 2, SEEK_CUR);
for (i = 0; i < content_count; i++) {
if (rc_file_read(iterator, file_handle, header, 0x30) != 0x30)
return rc_hash_iterator_error(iterator, "Could not read TMD content chunk");
/* Content index 0 is the main content (i.e. the 3DS executable) */
if (((header[4] << 8) | header[5]) == 0)
break;
content_offset += ((uint32_t)header[0xC] << 24) | (header[0xD] << 16) | (header[0xE] << 8) | header[0xF];
}
if (i == content_count)
return rc_hash_iterator_error(iterator, "Could not find main content chunk in TMD");
if ((header[7] & 1) == 0) {
/* Not encrypted, we can hash the NCCH immediately */
rc_file_seek(iterator, file_handle, content_offset, SEEK_SET);
if (rc_file_read(iterator, file_handle, header, 0x200) != 0x200)
return rc_hash_iterator_error(iterator, "Could not read NCCH header");
if (memcmp(&header[0x100], "NCCH", 4) != 0)
return rc_hash_iterator_error_formatted(iterator, "NCCH header was not at %08X%08X", (unsigned)(content_offset >> 32), (unsigned)content_offset);
return rc_hash_nintendo_3ds_ncch(md5, file_handle, header, NULL, iterator);
}
if (iterator->callbacks.encryption.get_3ds_cia_normal_key == NULL)
return rc_hash_iterator_error(iterator, "An encrypted CIA was detected, but the CIA normal key callback was not set");
/* Acquire the encrypted title key, title id, and common key index from the ticket */
/* These will be needed to decrypt the title key, and that will be needed to decrypt the CIA */
rc_file_seek(iterator, file_handle, tik_offset, SEEK_SET);
if (rc_file_read(iterator, file_handle, header, 4) != 4)
return rc_hash_iterator_error(iterator, "Could not read ticket signature type");
signature_size = rc_hash_nintendo_3ds_cia_signature_size(header, iterator);
if (signature_size == 0)
return 0;
rc_file_seek(iterator, file_handle, signature_size, SEEK_CUR);
if (rc_file_read(iterator, file_handle, header, 0xB2) != 0xB2)
return rc_hash_iterator_error(iterator, "Could not read ticket data");
memcpy(title_key, &header[0x7F], sizeof(title_key));
memcpy(title_id, &header[0x9C], sizeof(title_id));
common_key_index = header[0xB1];
if (common_key_index > 5)
return rc_hash_iterator_error_formatted(iterator, "Invalid common key index %02X", (unsigned)common_key_index);
if (iterator->callbacks.encryption.get_3ds_cia_normal_key(common_key_index, normal_key) == 0)
return rc_hash_iterator_error_formatted(iterator, "Could not obtain common key %02X", (unsigned)common_key_index);
memset(iv, 0, sizeof(iv));
memcpy(iv, title_id, sizeof(title_id));
AES_init_ctx_iv(&aes, normal_key, iv);
/* Finally, decrypt the title key */
AES_CBC_decrypt_buffer(&aes, title_key, sizeof(title_key));
/* Now we can hash the NCCH */
rc_file_seek(iterator, file_handle, content_offset, SEEK_SET);
if (rc_file_read(iterator, file_handle, header, 0x200) != 0x200)
return rc_hash_iterator_error(iterator, "Could not read NCCH header");
memset(iv, 0, sizeof(iv)); /* Content index is iv (which is always 0 for main content) */
AES_init_ctx_iv(&aes, title_key, iv);
AES_CBC_decrypt_buffer(&aes, header, 0x200);
if (memcmp(&header[0x100], "NCCH", 4) != 0)
return rc_hash_iterator_error_formatted(iterator, "NCCH header was not at %08X%08X", (unsigned)(content_offset >> 32), (unsigned)content_offset);
return rc_hash_nintendo_3ds_ncch(md5, file_handle, header, &aes, iterator);
}
static int rc_hash_nintendo_3ds_3dsx(md5_state_t* md5, void* file_handle, uint8_t header[0x200], const rc_hash_iterator_t* iterator)
{
uint8_t* hash_buffer;
uint32_t header_size, reloc_header_size, code_size;
int64_t code_offset;
header_size = (header[5] << 8) | header[4];
reloc_header_size = (header[7] << 8) | header[6];
code_size = ((uint32_t)header[0x13] << 24) | (header[0x12] << 16) | (header[0x11] << 8) | header[0x10];
/* 3 relocation headers are in-between the 3DSX header and code segment */
code_offset = header_size + reloc_header_size * 3;
if (code_size > MAX_BUFFER_SIZE)
code_size = MAX_BUFFER_SIZE;
hash_buffer = (uint8_t*)malloc(code_size);
if (!hash_buffer)
return rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", (unsigned)code_size);
rc_file_seek(iterator, file_handle, code_offset, SEEK_SET);
rc_hash_iterator_verbose_formatted(iterator, "Hashing %u bytes for 3DSX (at %08X)", (unsigned)code_size, (unsigned)code_offset);
if (rc_file_read(iterator, file_handle, hash_buffer, code_size) != code_size) {
free(hash_buffer);
return rc_hash_iterator_error(iterator, "Could not read 3DSX code segment");
}
md5_append(md5, hash_buffer, code_size);
free(hash_buffer);
return 1;
}
int rc_hash_nintendo_3ds(char hash[33], const rc_hash_iterator_t* iterator)
{
md5_state_t md5;
void* file_handle;
uint8_t header[0x200]; /* NCCH and NCSD headers are both 0x200 bytes */
int64_t header_offset;
file_handle = rc_file_open(iterator, iterator->path);
if (!file_handle)
return rc_hash_iterator_error(iterator, "Could not open file");
rc_file_seek(iterator, file_handle, 0, SEEK_SET);
/* If we don't have a full header, this is probably not a 3DS ROM */
if (rc_file_read(iterator, file_handle, header, sizeof(header)) != sizeof(header)) {
rc_file_close(iterator, file_handle);
return rc_hash_iterator_error(iterator, "Could not read 3DS ROM header");
}
md5_init(&md5);
if (memcmp(&header[0x100], "NCSD", 4) == 0) {
/* A NCSD container contains 1-8 NCCH partitions */
/* The first partition (index 0) is reserved for executable content */
header_offset = ((uint32_t)header[0x123] << 24) | (header[0x122] << 16) | (header[0x121] << 8) | header[0x120];
/* Offset is in "media units" (1 media unit = 0x200 bytes) */
header_offset *= 0x200;
/* We include the NCSD header in the hash, as that will ensure different versions of a game result in a different hash
* This is due to some revisions / languages only ever changing other NCCH paritions (e.g. the game manual)
*/
rc_hash_iterator_verbose(iterator, "Hashing 512 byte NCSD header");
md5_append(&md5, header, sizeof(header));
rc_hash_iterator_verbose_formatted(iterator,
"Detected NCSD header, seeking to NCCH partition at %08X%08X",
(unsigned)(header_offset >> 32), (unsigned)header_offset);
rc_file_seek(iterator, file_handle, header_offset, SEEK_SET);
if (rc_file_read(iterator, file_handle, header, sizeof(header)) != sizeof(header)) {
rc_file_close(iterator, file_handle);
return rc_hash_iterator_error(iterator, "Could not read 3DS NCCH header");
}
if (memcmp(&header[0x100], "NCCH", 4) != 0) {
rc_file_close(iterator, file_handle);
return rc_hash_iterator_error_formatted(iterator, "3DS NCCH header was not at %08X%08X", (unsigned)(header_offset >> 32), (unsigned)header_offset);
}
}
if (memcmp(&header[0x100], "NCCH", 4) == 0) {
if (rc_hash_nintendo_3ds_ncch(&md5, file_handle, header, NULL, iterator)) {
rc_file_close(iterator, file_handle);
return rc_hash_finalize(iterator, &md5, hash);
}
rc_file_close(iterator, file_handle);
return rc_hash_iterator_error(iterator, "Failed to hash 3DS NCCH container");
}
/* Couldn't identify either an NCSD or NCCH */
/* Try to identify this as a CIA */
if (header[0] == 0x20 && header[1] == 0x20 && header[2] == 0x00 && header[3] == 0x00) {
rc_hash_iterator_verbose(iterator, "Detected CIA, attempting to find executable NCCH");
if (rc_hash_nintendo_3ds_cia(&md5, file_handle, header, iterator)) {
rc_file_close(iterator, file_handle);
return rc_hash_finalize(iterator, &md5, hash);
}
rc_file_close(iterator, file_handle);
return rc_hash_iterator_error(iterator, "Failed to hash 3DS CIA container");
}
/* This might be a homebrew game, try to detect that */
if (memcmp(&header[0], "3DSX", 4) == 0) {
rc_hash_iterator_verbose(iterator, "Detected 3DSX");
if (rc_hash_nintendo_3ds_3dsx(&md5, file_handle, header, iterator)) {
rc_file_close(iterator, file_handle);
return rc_hash_finalize(iterator, &md5, hash);
}
rc_file_close(iterator, file_handle);
return rc_hash_iterator_error(iterator, "Failed to hash 3DS 3DSX container");
}
/* Raw ELF marker (AXF/ELF files) */
if (memcmp(&header[0], "\x7f\x45\x4c\x46", 4) == 0) {
rc_hash_iterator_verbose(iterator, "Detected AXF/ELF file, hashing entire file");
/* Don't bother doing anything fancy here, just hash entire file */
rc_file_close(iterator, file_handle);
return rc_hash_whole_file(hash, iterator);
}
rc_file_close(iterator, file_handle);
return rc_hash_iterator_error(iterator, "Not a 3DS ROM");
}

426
deps/rcheevos/src/rhash/hash_rom.c vendored Normal file
View File

@ -0,0 +1,426 @@
#include "rc_hash_internal.h"
#include "../rc_compat.h"
#include <ctype.h>
/* ===================================================== */
static int rc_hash_unheadered_iterator_buffer(char hash[33], const rc_hash_iterator_t* iterator, size_t header_size)
{
return rc_hash_buffer(hash, iterator->buffer + header_size, iterator->buffer_size - header_size, iterator);
}
static int rc_hash_iterator_buffer(char hash[33], const rc_hash_iterator_t* iterator)
{
return rc_hash_buffer(hash, iterator->buffer, iterator->buffer_size, iterator);
}
/* ===================================================== */
int rc_hash_7800(char hash[33], const rc_hash_iterator_t* iterator)
{
/* if the file contains a header, ignore it */
if (memcmp(&iterator->buffer[1], "ATARI7800", 9) == 0) {
rc_hash_iterator_verbose(iterator, "Ignoring 7800 header");
return rc_hash_unheadered_iterator_buffer(hash, iterator, 128);
}
return rc_hash_iterator_buffer(hash, iterator);
}
int rc_hash_arcade(char hash[33], const rc_hash_iterator_t* iterator)
{
/* arcade hash is just the hash of the filename (no extension) - the cores are pretty stringent about having the right ROM data */
const char* filename = rc_path_get_filename(iterator->path);
const char* ext = rc_path_get_extension(filename);
char buffer[128]; /* realistically, this should never need more than ~32 characters */
size_t filename_length = ext - filename - 1;
/* fbneo supports loading subsystems by using specific folder names.
* if one is found, include it in the hash.
* https://github.com/libretro/FBNeo/blob/master/src/burner/libretro/README.md#emulating-consoles-and-computers
*/
if (filename > iterator->path + 1) {
int include_folder = 0;
const char* folder = filename - 1;
size_t parent_folder_length = 0;
do {
if (folder[-1] == '/' || folder[-1] == '\\')
break;
--folder;
} while (folder > iterator->path);
parent_folder_length = filename - folder - 1;
if (parent_folder_length < 16) {
char* ptr = buffer;
while (folder < filename - 1)
*ptr++ = tolower(*folder++);
*ptr = '\0';
folder = buffer;
}
switch (parent_folder_length) {
case 3:
if (memcmp(folder, "nes", 3) == 0 || /* NES */
memcmp(folder, "fds", 3) == 0 || /* FDS */
memcmp(folder, "sms", 3) == 0 || /* Master System */
memcmp(folder, "msx", 3) == 0 || /* MSX */
memcmp(folder, "ngp", 3) == 0 || /* NeoGeo Pocket */
memcmp(folder, "pce", 3) == 0 || /* PCEngine */
memcmp(folder, "chf", 3) == 0 || /* ChannelF */
memcmp(folder, "sgx", 3) == 0) /* SuperGrafX */
include_folder = 1;
break;
case 4:
if (memcmp(folder, "tg16", 4) == 0 || /* TurboGrafx-16 */
memcmp(folder, "msx1", 4) == 0) /* MSX */
include_folder = 1;
break;
case 5:
if (memcmp(folder, "neocd", 5) == 0) /* NeoGeo CD */
include_folder = 1;
break;
case 6:
if (memcmp(folder, "coleco", 6) == 0 || /* Colecovision */
memcmp(folder, "sg1000", 6) == 0) /* SG-1000 */
include_folder = 1;
break;
case 7:
if (memcmp(folder, "genesis", 7) == 0) /* Megadrive (Genesis) */
include_folder = 1;
break;
case 8:
if (memcmp(folder, "gamegear", 8) == 0 || /* Game Gear */
memcmp(folder, "megadriv", 8) == 0 || /* Megadrive */
memcmp(folder, "pcengine", 8) == 0 || /* PCEngine */
memcmp(folder, "channelf", 8) == 0 || /* ChannelF */
memcmp(folder, "spectrum", 8) == 0) /* ZX Spectrum */
include_folder = 1;
break;
case 9:
if (memcmp(folder, "megadrive", 9) == 0) /* Megadrive */
include_folder = 1;
break;
case 10:
if (memcmp(folder, "supergrafx", 10) == 0 || /* SuperGrafX */
memcmp(folder, "zxspectrum", 10) == 0) /* ZX Spectrum */
include_folder = 1;
break;
case 12:
if (memcmp(folder, "mastersystem", 12) == 0 || /* Master System */
memcmp(folder, "colecovision", 12) == 0) /* Colecovision */
include_folder = 1;
break;
default:
break;
}
if (include_folder) {
if (parent_folder_length + filename_length + 1 < sizeof(buffer)) {
buffer[parent_folder_length] = '_';
memcpy(&buffer[parent_folder_length + 1], filename, filename_length);
return rc_hash_buffer(hash, (uint8_t*)&buffer[0], parent_folder_length + filename_length + 1, iterator);
}
}
}
return rc_hash_buffer(hash, (uint8_t*)filename, filename_length, iterator);
}
static int rc_hash_text(char hash[33], const rc_hash_iterator_t* iterator)
{
md5_state_t md5;
const uint8_t* scan = iterator->buffer;
const uint8_t* stop = iterator->buffer + iterator->buffer_size;
md5_init(&md5);
do {
const uint8_t* line = scan;
/* find end of line */
while (scan < stop && *scan != '\r' && *scan != '\n')
++scan;
md5_append(&md5, line, (int)(scan - line));
/* include a normalized line ending */
/* NOTE: this causes a line ending to be hashed at the end of the file, even if one was not present */
md5_append(&md5, (const uint8_t*)"\n", 1);
/* skip newline */
if (scan < stop && *scan == '\r')
++scan;
if (scan < stop && *scan == '\n')
++scan;
} while (scan < stop);
return rc_hash_finalize(iterator, &md5, hash);
}
int rc_hash_arduboy(char hash[33], const rc_hash_iterator_t* iterator)
{
if (iterator->path && rc_path_compare_extension(iterator->path, "arduboy")) {
#ifndef RC_HASH_NO_ZIP
return rc_hash_arduboyfx(hash, iterator);
#else
rc_hash_iterator_verbose(iterator, ".arduboy file processing not enabled");
return 0;
#endif
}
if (!iterator->buffer)
return rc_hash_buffered_file(hash, RC_CONSOLE_ARDUBOY, iterator);
/* https://en.wikipedia.org/wiki/Intel_HEX */
return rc_hash_text(hash, iterator);
}
int rc_hash_lynx(char hash[33], const rc_hash_iterator_t* iterator)
{
/* if the file contains a header, ignore it */
if (memcmp(&iterator->buffer[0], "LYNX", 5) == 0) {
rc_hash_iterator_verbose(iterator, "Ignoring LYNX header");
return rc_hash_unheadered_iterator_buffer(hash, iterator, 64);
}
return rc_hash_iterator_buffer(hash, iterator);
}
int rc_hash_nes(char hash[33], const rc_hash_iterator_t* iterator)
{
/* if the file contains a header, ignore it */
if (memcmp(&iterator->buffer[0], "NES\x1a", 4) == 0) {
rc_hash_iterator_verbose(iterator, "Ignoring NES header");
return rc_hash_unheadered_iterator_buffer(hash, iterator, 16);
}
if (memcmp(&iterator->buffer[0], "FDS\x1a", 4) == 0) {
rc_hash_iterator_verbose(iterator, "Ignoring FDS header");
return rc_hash_unheadered_iterator_buffer(hash, iterator, 16);
}
return rc_hash_iterator_buffer(hash, iterator);
}
int rc_hash_n64(char hash[33], const rc_hash_iterator_t* iterator)
{
uint8_t* buffer;
uint8_t* stop;
const size_t buffer_size = 65536;
md5_state_t md5;
size_t remaining;
void* file_handle;
int is_v64 = 0;
int is_n64 = 0;
file_handle = rc_file_open(iterator, iterator->path);
if (!file_handle)
return rc_hash_iterator_error(iterator, "Could not open file");
buffer = (uint8_t*)malloc(buffer_size);
if (!buffer) {
rc_file_close(iterator, file_handle);
return rc_hash_iterator_error(iterator, "Could not allocate temporary buffer");
}
stop = buffer + buffer_size;
/* read first byte so we can detect endianness */
rc_file_seek(iterator, file_handle, 0, SEEK_SET);
rc_file_read(iterator, file_handle, buffer, 1);
if (buffer[0] == 0x80) { /* z64 format (big endian [native]) */
}
else if (buffer[0] == 0x37) { /* v64 format (byteswapped) */
rc_hash_iterator_verbose(iterator, "converting v64 to z64");
is_v64 = 1;
}
else if (buffer[0] == 0x40) { /* n64 format (little endian) */
rc_hash_iterator_verbose(iterator, "converting n64 to z64");
is_n64 = 1;
}
else if (buffer[0] == 0xE8 || buffer[0] == 0x22) { /* ndd format (don't byteswap) */
}
else {
free(buffer);
rc_file_close(iterator, file_handle);
rc_hash_iterator_verbose(iterator, "Not a Nintendo 64 ROM");
return 0;
}
/* calculate total file size */
rc_file_seek(iterator, file_handle, 0, SEEK_END);
remaining = (size_t)rc_file_tell(iterator, file_handle);
if (remaining > MAX_BUFFER_SIZE)
remaining = MAX_BUFFER_SIZE;
rc_hash_iterator_verbose_formatted(iterator, "Hashing %u bytes", (unsigned)remaining);
/* begin hashing */
md5_init(&md5);
rc_file_seek(iterator, file_handle, 0, SEEK_SET);
while (remaining >= buffer_size) {
rc_file_read(iterator, file_handle, buffer, (int)buffer_size);
if (is_v64)
rc_hash_byteswap16(buffer, stop);
else if (is_n64)
rc_hash_byteswap32(buffer, stop);
md5_append(&md5, buffer, (int)buffer_size);
remaining -= buffer_size;
}
if (remaining > 0) {
rc_file_read(iterator, file_handle, buffer, (int)remaining);
stop = buffer + remaining;
if (is_v64)
rc_hash_byteswap16(buffer, stop);
else if (is_n64)
rc_hash_byteswap32(buffer, stop);
md5_append(&md5, buffer, (int)remaining);
}
/* cleanup */
rc_file_close(iterator, file_handle);
free(buffer);
return rc_hash_finalize(iterator, &md5, hash);
}
int rc_hash_nintendo_ds(char hash[33], const rc_hash_iterator_t* iterator)
{
uint8_t header[512];
uint8_t* hash_buffer;
uint32_t hash_size, arm9_size, arm9_addr, arm7_size, arm7_addr, icon_addr;
size_t num_read;
int64_t offset = 0;
md5_state_t md5;
void* file_handle;
file_handle = rc_file_open(iterator, iterator->path);
if (!file_handle)
return rc_hash_iterator_error(iterator, "Could not open file");
rc_file_seek(iterator, file_handle, 0, SEEK_SET);
if (rc_file_read(iterator, file_handle, header, sizeof(header)) != 512)
return rc_hash_iterator_error(iterator, "Failed to read header");
if (header[0] == 0x2E && header[1] == 0x00 && header[2] == 0x00 && header[3] == 0xEA &&
header[0xB0] == 0x44 && header[0xB1] == 0x46 && header[0xB2] == 0x96 && header[0xB3] == 0) {
/* SuperCard header detected, ignore it */
rc_hash_iterator_verbose(iterator, "Ignoring SuperCard header");
offset = 512;
rc_file_seek(iterator, file_handle, offset, SEEK_SET);
rc_file_read(iterator, file_handle, header, sizeof(header));
}
arm9_addr = header[0x20] | (header[0x21] << 8) | (header[0x22] << 16) | (header[0x23] << 24);
arm9_size = header[0x2C] | (header[0x2D] << 8) | (header[0x2E] << 16) | (header[0x2F] << 24);
arm7_addr = header[0x30] | (header[0x31] << 8) | (header[0x32] << 16) | (header[0x33] << 24);
arm7_size = header[0x3C] | (header[0x3D] << 8) | (header[0x3E] << 16) | (header[0x3F] << 24);
icon_addr = header[0x68] | (header[0x69] << 8) | (header[0x6A] << 16) | (header[0x6B] << 24);
if (arm9_size + arm7_size > 16 * 1024 * 1024) {
/* sanity check - code blocks are typically less than 1MB each - assume not a DS ROM */
return rc_hash_iterator_error_formatted(iterator, "arm9 code size (%u) + arm7 code size (%u) exceeds 16MB", arm9_size, arm7_size);
}
hash_size = 0xA00;
if (arm9_size > hash_size)
hash_size = arm9_size;
if (arm7_size > hash_size)
hash_size = arm7_size;
hash_buffer = (uint8_t*)malloc(hash_size);
if (!hash_buffer) {
rc_file_close(iterator, file_handle);
return rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", hash_size);
}
md5_init(&md5);
rc_hash_iterator_verbose(iterator, "Hashing 352 byte header");
md5_append(&md5, header, 0x160);
rc_hash_iterator_verbose_formatted(iterator, "Hashing %u byte arm9 code (at %08X)", arm9_size, arm9_addr);
rc_file_seek(iterator, file_handle, arm9_addr + offset, SEEK_SET);
rc_file_read(iterator, file_handle, hash_buffer, arm9_size);
md5_append(&md5, hash_buffer, arm9_size);
rc_hash_iterator_verbose_formatted(iterator, "Hashing %u byte arm7 code (at %08X)", arm7_size, arm7_addr);
rc_file_seek(iterator, file_handle, arm7_addr + offset, SEEK_SET);
rc_file_read(iterator, file_handle, hash_buffer, arm7_size);
md5_append(&md5, hash_buffer, arm7_size);
rc_hash_iterator_verbose_formatted(iterator, "Hashing 2560 byte icon and labels data (at %08X)", icon_addr);
rc_file_seek(iterator, file_handle, icon_addr + offset, SEEK_SET);
num_read = rc_file_read(iterator, file_handle, hash_buffer, 0xA00);
if (num_read < 0xA00) {
/* some homebrew games don't provide a full icon block, and no data after the icon block.
* if we didn't get a full icon block, fill the remaining portion with 0s
*/
rc_hash_iterator_verbose_formatted(iterator,
"Warning: only got %u bytes for icon and labels data, 0-padding to 2560 bytes", (unsigned)num_read);
memset(&hash_buffer[num_read], 0, 0xA00 - num_read);
}
md5_append(&md5, hash_buffer, 0xA00);
free(hash_buffer);
rc_file_close(iterator, file_handle);
return rc_hash_finalize(iterator, &md5, hash);
}
int rc_hash_pce(char hash[33], const rc_hash_iterator_t* iterator)
{
/* The PCE header doesn't bear any distinguishable marks, so we have to detect
* it by looking at the file size. The core looks for anything that's 512 bytes
* more than a multiple of 8KB, so we'll do that too.
* https://github.com/libretro/beetle-pce-libretro/blob/af28fb0385d00e0292c4703b3aa7e72762b564d2/mednafen/pce/huc.cpp#L196-L202
*/
if (iterator->buffer_size & 512) {
rc_hash_iterator_verbose(iterator, "Ignoring PCE header");
return rc_hash_unheadered_iterator_buffer(hash, iterator, 512);
}
return rc_hash_iterator_buffer(hash, iterator);
}
int rc_hash_scv(char hash[33], const rc_hash_iterator_t* iterator)
{
/* if the file contains a header, ignore it */
/* https://gitlab.com/MaaaX-EmuSCV/libretro-emuscv/-/blob/master/readme.txt#L211 */
if (memcmp(iterator->buffer, "EmuSCV", 6) == 0) {
rc_hash_iterator_verbose(iterator, "Ignoring SCV header");
return rc_hash_unheadered_iterator_buffer(hash, iterator, 32);
}
return rc_hash_iterator_buffer(hash, iterator);
}
int rc_hash_snes(char hash[33], const rc_hash_iterator_t* iterator)
{
/* if the file contains a header, ignore it */
uint32_t calc_size = ((uint32_t)iterator->buffer_size / 0x2000) * 0x2000;
if (iterator->buffer_size - calc_size == 512) {
rc_hash_iterator_verbose(iterator, "Ignoring SNES header");
return rc_hash_unheadered_iterator_buffer(hash, iterator, 512);
}
return rc_hash_iterator_buffer(hash, iterator);
}

460
deps/rcheevos/src/rhash/hash_zip.c vendored Normal file
View File

@ -0,0 +1,460 @@
#include "rc_hash_internal.h"
#include "../rc_compat.h"
struct rc_hash_zip_idx
{
size_t length;
uint8_t* data;
};
static int rc_hash_zip_idx_sort(const void* a, const void* b)
{
struct rc_hash_zip_idx* A = (struct rc_hash_zip_idx*)a, * B = (struct rc_hash_zip_idx*)b;
size_t len = (A->length < B->length ? A->length : B->length);
return memcmp(A->data, B->data, len);
}
typedef int (RC_CCONV* rc_hash_zip_filter_t)(const char* filename, uint32_t filename_len, uint64_t decomp_size, void* userdata);
static int rc_hash_zip_file(md5_state_t* md5, void* file_handle,
const rc_hash_iterator_t* iterator,
rc_hash_zip_filter_t filter_func, void* filter_userdata)
{
uint8_t buf[2048], *alloc_buf, *cdir_start, *cdir_max, *cdir, *hashdata, eocdirhdr_size, cdirhdr_size, nparents;
uint32_t cdir_entry_len;
size_t sizeof_idx, indices_offset, alloc_size;
int64_t i_file, archive_size, ecdh_ofs, total_files, cdir_size, cdir_ofs;
struct rc_hash_zip_idx* hashindices, *hashindex;
rc_file_seek(iterator, file_handle, 0, SEEK_END);
archive_size = rc_file_tell(iterator, file_handle);
/* Basic sanity checks - reject files which are too small */
eocdirhdr_size = 22; /* the 'end of central directory header' is 22 bytes */
if (archive_size < eocdirhdr_size)
return rc_hash_iterator_error(iterator, "ZIP is too small");
/* Macros used for reading ZIP and writing to a buffer for hashing (undefined again at the end of the function) */
#define RC_ZIP_READ_LE16(p) ((uint16_t)(((const uint8_t*)(p))[0]) | ((uint16_t)(((const uint8_t*)(p))[1]) << 8U))
#define RC_ZIP_READ_LE32(p) ((uint32_t)(((const uint8_t*)(p))[0]) | ((uint32_t)(((const uint8_t*)(p))[1]) << 8U) | ((uint32_t)(((const uint8_t*)(p))[2]) << 16U) | ((uint32_t)(((const uint8_t*)(p))[3]) << 24U))
#define RC_ZIP_READ_LE64(p) ((uint64_t)(((const uint8_t*)(p))[0]) | ((uint64_t)(((const uint8_t*)(p))[1]) << 8U) | ((uint64_t)(((const uint8_t*)(p))[2]) << 16U) | ((uint64_t)(((const uint8_t*)(p))[3]) << 24U) | ((uint64_t)(((const uint8_t*)(p))[4]) << 32U) | ((uint64_t)(((const uint8_t*)(p))[5]) << 40U) | ((uint64_t)(((const uint8_t*)(p))[6]) << 48U) | ((uint64_t)(((const uint8_t*)(p))[7]) << 56U))
#define RC_ZIP_WRITE_LE32(p,v) { ((uint8_t*)(p))[0] = (uint8_t)((uint32_t)(v) & 0xFF); ((uint8_t*)(p))[1] = (uint8_t)(((uint32_t)(v) >> 8) & 0xFF); ((uint8_t*)(p))[2] = (uint8_t)(((uint32_t)(v) >> 16) & 0xFF); ((uint8_t*)(p))[3] = (uint8_t)((uint32_t)(v) >> 24); }
#define RC_ZIP_WRITE_LE64(p,v) { ((uint8_t*)(p))[0] = (uint8_t)((uint64_t)(v) & 0xFF); ((uint8_t*)(p))[1] = (uint8_t)(((uint64_t)(v) >> 8) & 0xFF); ((uint8_t*)(p))[2] = (uint8_t)(((uint64_t)(v) >> 16) & 0xFF); ((uint8_t*)(p))[3] = (uint8_t)(((uint64_t)(v) >> 24) & 0xFF); ((uint8_t*)(p))[4] = (uint8_t)(((uint64_t)(v) >> 32) & 0xFF); ((uint8_t*)(p))[5] = (uint8_t)(((uint64_t)(v) >> 40) & 0xFF); ((uint8_t*)(p))[6] = (uint8_t)(((uint64_t)(v) >> 48) & 0xFF); ((uint8_t*)(p))[7] = (uint8_t)((uint64_t)(v) >> 56); }
/* Find the end of central directory record by scanning the file from the end towards the beginning */
for (ecdh_ofs = archive_size - sizeof(buf); ; ecdh_ofs -= (sizeof(buf) - 3)) {
int i, n = sizeof(buf);
if (ecdh_ofs < 0)
ecdh_ofs = 0;
if (n > archive_size)
n = (int)archive_size;
rc_file_seek(iterator, file_handle, ecdh_ofs, SEEK_SET);
if (rc_file_read(iterator, file_handle, buf, n) != (size_t)n)
return rc_hash_iterator_error(iterator, "ZIP read error");
for (i = n - 4; i >= 0; --i) {
if (buf[i] == 'P' && RC_ZIP_READ_LE32(buf + i) == 0x06054b50) /* end of central directory header signature */
break;
}
if (i >= 0) {
ecdh_ofs += i;
break;
}
if (!ecdh_ofs || (archive_size - ecdh_ofs) >= (0xFFFF + eocdirhdr_size))
return rc_hash_iterator_error(iterator, "Failed to find ZIP central directory");
}
/* Read and verify the end of central directory record. */
rc_file_seek(iterator, file_handle, ecdh_ofs, SEEK_SET);
if (rc_file_read(iterator, file_handle, buf, eocdirhdr_size) != eocdirhdr_size)
return rc_hash_iterator_error(iterator, "Failed to read ZIP central directory");
/* Read central dir information from end of central directory header */
total_files = RC_ZIP_READ_LE16(buf + 0x0A);
cdir_size = RC_ZIP_READ_LE32(buf + 0x0C);
cdir_ofs = RC_ZIP_READ_LE32(buf + 0x10);
/* Check if this is a Zip64 file. In the block of code below:
* - 20 is the size of the ZIP64 end of central directory locator
* - 56 is the size of the ZIP64 end of central directory header
*/
if ((cdir_ofs == 0xFFFFFFFF || cdir_size == 0xFFFFFFFF || total_files == 0xFFFF) && ecdh_ofs >= (20 + 56)) {
/* Read the ZIP64 end of central directory locator if it actually exists */
rc_file_seek(iterator, file_handle, ecdh_ofs - 20, SEEK_SET);
if (rc_file_read(iterator, file_handle, buf, 20) == 20 && RC_ZIP_READ_LE32(buf) == 0x07064b50) { /* locator signature */
/* Found the locator, now read the actual ZIP64 end of central directory header */
int64_t ecdh64_ofs = (int64_t)RC_ZIP_READ_LE64(buf + 0x08);
if (ecdh64_ofs <= (archive_size - 56)) {
rc_file_seek(iterator, file_handle, ecdh64_ofs, SEEK_SET);
if (rc_file_read(iterator, file_handle, buf, 56) == 56 && RC_ZIP_READ_LE32(buf) == 0x06064b50) { /* header signature */
total_files = RC_ZIP_READ_LE64(buf + 0x20);
cdir_size = RC_ZIP_READ_LE64(buf + 0x28);
cdir_ofs = RC_ZIP_READ_LE64(buf + 0x30);
}
}
}
}
/* Basic verificaton of central directory (limit to a 256MB content directory) */
cdirhdr_size = 46; /* the 'central directory header' is 46 bytes */
if ((cdir_size >= 0x10000000) || (cdir_size < total_files * cdirhdr_size) || ((cdir_ofs + cdir_size) > archive_size))
return rc_hash_iterator_error(iterator, "Central directory of ZIP file is invalid");
/* Allocate once for both directory and our temporary sort index (memory aligned to sizeof(rc_hash_zip_idx)) */
sizeof_idx = sizeof(struct rc_hash_zip_idx);
indices_offset = (size_t)((cdir_size + sizeof_idx - 1) / sizeof_idx * sizeof_idx);
alloc_size = (size_t)(indices_offset + total_files * sizeof_idx);
alloc_buf = (uint8_t*)malloc(alloc_size);
/* Read entire central directory to a buffer */
if (!alloc_buf)
return rc_hash_iterator_error(iterator, "Could not allocate temporary buffer");
rc_file_seek(iterator, file_handle, cdir_ofs, SEEK_SET);
if ((int64_t)rc_file_read(iterator, file_handle, alloc_buf, (int)cdir_size) != cdir_size) {
free(alloc_buf);
return rc_hash_iterator_error(iterator, "Failed to read central directory of ZIP file");
}
cdir_start = alloc_buf;
cdir_max = cdir_start + cdir_size - cdirhdr_size;
cdir = cdir_start;
/* Write our temporary hash data to the same buffer we read the central directory from.
* We can do that because the amount of data we keep for each file is guaranteed to be less than the file record.
*/
hashdata = alloc_buf;
hashindices = (struct rc_hash_zip_idx*)(alloc_buf + indices_offset);
hashindex = hashindices;
/* Now process the central directory file records */
for (i_file = nparents = 0, cdir = cdir_start; i_file < total_files && cdir >= cdir_start && cdir <= cdir_max; i_file++, cdir += cdir_entry_len) {
const uint8_t* name, * name_end;
uint32_t signature = RC_ZIP_READ_LE32(cdir + 0x00);
uint32_t method = RC_ZIP_READ_LE16(cdir + 0x0A);
uint32_t crc32 = RC_ZIP_READ_LE32(cdir + 0x10);
uint64_t comp_size = RC_ZIP_READ_LE32(cdir + 0x14);
uint64_t decomp_size = RC_ZIP_READ_LE32(cdir + 0x18);
uint32_t filename_len = RC_ZIP_READ_LE16(cdir + 0x1C);
int32_t extra_len = RC_ZIP_READ_LE16(cdir + 0x1E);
int32_t comment_len = RC_ZIP_READ_LE16(cdir + 0x20);
int32_t external_attr = RC_ZIP_READ_LE16(cdir + 0x26);
uint64_t local_hdr_ofs = RC_ZIP_READ_LE32(cdir + 0x2A);
cdir_entry_len = cdirhdr_size + filename_len + extra_len + comment_len;
if (signature != 0x02014b50) /* expected central directory entry signature */
break;
/* Ignore records describing a directory (we only hash file records) */
name = (cdir + cdirhdr_size);
if (name[filename_len - 1] == '/' || name[filename_len - 1] == '\\' || (external_attr & 0x10))
continue;
/* Handle Zip64 fields */
if (decomp_size == 0xFFFFFFFF || comp_size == 0xFFFFFFFF || local_hdr_ofs == 0xFFFFFFFF) {
int invalid = 0;
const uint8_t* x = cdir + cdirhdr_size + filename_len, * xEnd, * field, * fieldEnd;
for (xEnd = x + extra_len; (x + (sizeof(uint16_t) * 2)) < xEnd; x = fieldEnd) {
field = x + (sizeof(uint16_t) * 2);
fieldEnd = field + RC_ZIP_READ_LE16(x + 2);
if (RC_ZIP_READ_LE16(x) != 0x0001 || fieldEnd > xEnd)
continue; /* Not the Zip64 extended information extra field */
if (decomp_size == 0xFFFFFFFF) {
if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) {
invalid = 1;
break;
}
decomp_size = RC_ZIP_READ_LE64(field);
field += sizeof(uint64_t);
}
if (comp_size == 0xFFFFFFFF) {
if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) {
invalid = 1;
break;
}
comp_size = RC_ZIP_READ_LE64(field);
field += sizeof(uint64_t);
}
if (local_hdr_ofs == 0xFFFFFFFF) {
if ((unsigned)(fieldEnd - field) < sizeof(uint64_t)) {
invalid = 1;
break;
}
local_hdr_ofs = RC_ZIP_READ_LE64(field);
field += sizeof(uint64_t);
}
break;
}
if (invalid) {
free(alloc_buf);
return rc_hash_iterator_error(iterator, "Encountered invalid Zip64 file");
}
}
/* Basic sanity check on file record */
/* 30 is the length of the local directory header preceeding the compressed data */
if ((!method && decomp_size != comp_size) || (decomp_size && !comp_size) ||
((local_hdr_ofs + 30 + comp_size) > (uint64_t)archive_size)) {
free(alloc_buf);
return rc_hash_iterator_error(iterator, "Encountered invalid entry in ZIP central directory");
}
if (filter_func) {
int filtered = filter_func((const char*)name, filename_len, decomp_size, filter_userdata);
if (filtered < 0) {
free(alloc_buf);
return 0;
}
if (filtered) /* this file shouldn't be hashed */
continue;
}
/* Write the pointer and length of the data we record about this file */
hashindex->data = hashdata;
hashindex->length = filename_len + 1 + 4 + 8;
hashindex++;
rc_hash_iterator_verbose_formatted(iterator, "File in ZIP: %.*s (%u bytes, CRC32 = %08X)", filename_len, (const char*)name, (unsigned)decomp_size, crc32);
/* Convert and store the file name in the hash data buffer */
for (name_end = name + filename_len; name != name_end; name++) {
*(hashdata++) =
(*name == '\\' ? '/' : /* convert back-slashes to regular slashes */
(*name >= 'A' && *name <= 'Z') ? (*name | 0x20) : /* convert upper case letters to lower case */
*name); /* else use the byte as-is */
}
/* Add zero terminator, CRC32 and decompressed size to the hash data buffer */
*(hashdata++) = '\0';
RC_ZIP_WRITE_LE32(hashdata, crc32);
hashdata += 4;
RC_ZIP_WRITE_LE64(hashdata, decomp_size);
hashdata += 8;
}
rc_hash_iterator_verbose_formatted(iterator, "Hashing %u files in ZIP archive", (unsigned)(hashindex - hashindices));
/* Sort the file list indices */
qsort(hashindices, (hashindex - hashindices), sizeof(struct rc_hash_zip_idx), rc_hash_zip_idx_sort);
/* Hash the data in the order of the now sorted indices */
for (; hashindices != hashindex; hashindices++)
md5_append(md5, hashindices->data, (int)hashindices->length);
free(alloc_buf);
return 1;
#undef RC_ZIP_READ_LE16
#undef RC_ZIP_READ_LE32
#undef RC_ZIP_READ_LE64
#undef RC_ZIP_WRITE_LE32
#undef RC_ZIP_WRITE_LE64
}
/* ===================================================== */
static int rc_hash_arduboyfx_filter(const char* filename, uint32_t filename_len, uint64_t decomp_size, void* userdata)
{
(void)decomp_size;
(void)userdata;
/* An .arduboy file is a zip file containing an info.json pointing at one or more bin
* and hex files. It can also contain a bunch of screenshots, but we don't care about
* those. As they're also referenced in the info.json, we have to ignore that too.
* Instead of ignoring the info.json and all image files, only process any bin/hex files */
if (filename_len > 4) {
const char* ext = &filename[filename_len - 4];
if (strncasecmp(ext, ".hex", 4) == 0 || strncasecmp(ext, ".bin", 4) == 0)
return 0; /* keep hex and bin */
}
return 1; /* filter everything else */
}
int rc_hash_arduboyfx(char hash[33], const rc_hash_iterator_t* iterator)
{
md5_state_t md5;
int res;
void* file_handle = rc_file_open(iterator, iterator->path);
if (!file_handle)
return rc_hash_iterator_error(iterator, "Could not open file");
md5_init(&md5);
res = rc_hash_zip_file(&md5, file_handle, iterator, rc_hash_arduboyfx_filter, NULL);
rc_file_close(iterator, file_handle);
if (!res)
return 0;
return rc_hash_finalize(iterator, &md5, hash);
}
/* ===================================================== */
struct rc_hash_ms_dos_dosz_state {
const char* path;
const struct rc_hash_ms_dos_dosz_state* child;
md5_state_t* md5;
const rc_hash_iterator_t* iterator;
void* file_handle;
uint32_t nparents;
};
static int rc_hash_dosz(struct rc_hash_ms_dos_dosz_state* dosz);
static int rc_hash_ms_dos_parent(const struct rc_hash_ms_dos_dosz_state* child,
const char* parentname, uint32_t parentname_len)
{
const char* lastfslash = strrchr(child->path, '/');
const char* lastbslash = strrchr(child->path, '\\');
const char* lastslash = (lastbslash > lastfslash ? lastbslash : lastfslash);
size_t dir_len = (lastslash ? (lastslash + 1 - child->path) : 0);
char* parent_path = (char*)malloc(dir_len + parentname_len + 1);
struct rc_hash_ms_dos_dosz_state parent;
const struct rc_hash_ms_dos_dosz_state* check;
void* parent_handle;
int parent_res;
/* Build the path of the parent by combining the directory of the current file with the name */
if (!parent_path)
return rc_hash_iterator_error(child->iterator, "Could not allocate temporary buffer");
memcpy(parent_path, child->path, dir_len);
memcpy(parent_path + dir_len, parentname, parentname_len);
parent_path[dir_len + parentname_len] = '\0';
/* Make sure there is no recursion where a parent DOSZ is an already seen child DOSZ */
for (check = child->child; check; check = check->child) {
if (!strcmp(check->path, parent_path)) {
free(parent_path);
return rc_hash_iterator_error(child->iterator, "Invalid DOSZ file with recursive parents");
}
}
/* Try to open the parent DOSZ file */
parent_handle = rc_file_open(child->iterator, parent_path);
if (!parent_handle) {
rc_hash_iterator_error_formatted(child->iterator, "DOSZ parent file '%s' does not exist", parent_path);
free(parent_path);
return 0;
}
/* Fully hash the parent DOSZ ahead of the child */
memcpy(&parent, child, sizeof(parent));
parent.path = parent_path;
parent.child = child;
parent.file_handle = parent_handle;
parent_res = rc_hash_dosz(&parent);
rc_file_close(child->iterator, parent_handle);
free(parent_path);
return parent_res;
}
static int rc_hash_ms_dos_dosc(const struct rc_hash_ms_dos_dosz_state* dosz)
{
size_t path_len = strlen(dosz->path);
if (dosz->path[path_len - 1] == 'z' || dosz->path[path_len - 1] == 'Z') {
void* file_handle;
char* dosc_path = strdup(dosz->path);
if (!dosc_path)
return rc_hash_iterator_error(dosz->iterator, "Could not allocate temporary buffer");
/* Swap the z to c and use the same capitalization, hash the file if it exists */
dosc_path[path_len - 1] = (dosz->path[path_len - 1] == 'z' ? 'c' : 'C');
file_handle = rc_file_open(dosz->iterator, dosc_path);
free(dosc_path);
if (file_handle) {
/* Hash the entire contents of the DOSC file */
int res = rc_hash_zip_file(dosz->md5, file_handle, dosz->iterator, NULL, NULL);
rc_file_close(dosz->iterator, file_handle);
if (!res)
return 0;
}
}
return 1;
}
static int rc_hash_dosz_filter(const char* filename, uint32_t filename_len, uint64_t decomp_size, void* userdata)
{
struct rc_hash_ms_dos_dosz_state* dosz = (struct rc_hash_ms_dos_dosz_state*)userdata;
/* A DOSZ file can contain a special empty <base>.dosz.parent file in its root which means a parent dosz file is used */
if (decomp_size == 0 && filename_len > 7 &&
strncasecmp(&filename[filename_len - 7], ".parent", 7) == 0 &&
!memchr(filename, '/', filename_len) &&
!memchr(filename, '\\', filename_len))
{
/* A DOSZ file can only have one parent file */
if (dosz->nparents++)
return -1;
/* process the parent. if it fails, stop */
if (!rc_hash_ms_dos_parent(dosz, filename, (filename_len - 7)))
return -1;
/* We don't hash this meta file so a user is free to rename it and the parent file */
return 1;
}
return 0;
}
static int rc_hash_dosz(struct rc_hash_ms_dos_dosz_state* dosz)
{
if (!rc_hash_zip_file(dosz->md5, dosz->file_handle, dosz->iterator, rc_hash_dosz_filter, dosz))
return 0;
/* A DOSZ file can only have one parent file */
if (dosz->nparents > 1)
return rc_hash_iterator_error(dosz->iterator, "Invalid DOSZ file with multiple parents");
/* Check if an associated .dosc file exists */
if (!rc_hash_ms_dos_dosc(dosz))
return 0;
return 1;
}
int rc_hash_ms_dos(char hash[33], const rc_hash_iterator_t* iterator)
{
struct rc_hash_ms_dos_dosz_state dosz;
md5_state_t md5;
int res;
void* file_handle = rc_file_open(iterator, iterator->path);
if (!file_handle)
return rc_hash_iterator_error(iterator, "Could not open file");
memset(&dosz, 0, sizeof(dosz));
dosz.path = iterator->path;
dosz.file_handle = file_handle;
dosz.iterator = iterator;
dosz.md5 = &md5;
md5_init(&md5);
res = rc_hash_dosz(&dosz);
rc_file_close(iterator, file_handle);
if (!res)
return 0;
return rc_hash_finalize(iterator, &md5, hash);
}

View File

@ -0,0 +1,116 @@
#ifndef RC_HASH_INTERNAL_H
#define RC_HASH_INTERNAL_H
#include "rc_hash.h"
#include "md5.h"
RC_BEGIN_C_DECLS
/* hash.c */
void* rc_file_open(const rc_hash_iterator_t* iterator, const char* path);
void rc_file_seek(const rc_hash_iterator_t* iterator, void* file_handle, int64_t offset, int origin);
int64_t rc_file_tell(const rc_hash_iterator_t* iterator, void* file_handle);
size_t rc_file_read(const rc_hash_iterator_t* iterator, void* file_handle, void* buffer, int requested_bytes);
void rc_file_close(const rc_hash_iterator_t* iterator, void* file_handle);
int64_t rc_file_size(const rc_hash_iterator_t* iterator, const char* path);
void rc_hash_iterator_verbose(const rc_hash_iterator_t* iterator, const char* message);
void rc_hash_iterator_verbose_formatted(const rc_hash_iterator_t* iterator, const char* format, ...);
int rc_hash_iterator_error(const rc_hash_iterator_t* iterator, const char* message);
int rc_hash_iterator_error_formatted(const rc_hash_iterator_t* iterator, const char* format, ...);
/* arbitrary limit to prevent allocating and hashing large files */
#define MAX_BUFFER_SIZE 64 * 1024 * 1024
void rc_hash_merge_callbacks(rc_hash_iterator_t* iterator, const rc_hash_callbacks_t* callbacks);
int rc_hash_finalize(const rc_hash_iterator_t* iterator, md5_state_t* md5, char hash[33]);
int rc_hash_buffer(char hash[33], const uint8_t* buffer, size_t buffer_size, const rc_hash_iterator_t* iterator);
void rc_hash_byteswap16(uint8_t* buffer, const uint8_t* stop);
void rc_hash_byteswap32(uint8_t* buffer, const uint8_t* stop);
const char* rc_path_get_filename(const char* path);
const char* rc_path_get_extension(const char* path);
int rc_path_compare_extension(const char* path, const char* ext);
typedef void (RC_CCONV* rc_hash_iterator_ext_handler_t)(rc_hash_iterator_t* iterator, int data);
typedef struct rc_hash_iterator_ext_handler_entry_t {
char ext[8];
rc_hash_iterator_ext_handler_t handler;
int data;
} rc_hash_iterator_ext_handler_entry_t;
const rc_hash_iterator_ext_handler_entry_t* rc_hash_get_iterator_ext_handlers(size_t* num_handlers);
typedef struct rc_hash_cdrom_track_t {
void* file_handle; /* the file handle for reading the track data */
const rc_hash_filereader_t* file_reader; /* functions to perform raw file I/O */
int64_t file_track_offset;/* the offset of the track data within the file */
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 */
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
} rc_hash_cdrom_track_t;
int rc_hash_whole_file(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_buffered_file(char hash[33], uint32_t console_id, const rc_hash_iterator_t* iterator);
#ifndef RC_HASH_NO_ROM
/* hash_rom.c */
int rc_hash_7800(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_arcade(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_arduboy(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_lynx(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_nes(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_n64(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_nintendo_ds(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_pce(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_scv(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_snes(char hash[33], const rc_hash_iterator_t* iterator);
#endif
#ifndef RC_HASH_NO_DISC
/* hash_disc.c */
void rc_hash_reset_iterator_disc(rc_hash_iterator_t* iterator);
int rc_hash_3do(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_dreamcast(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_gamecube(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_jaguar_cd(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_neogeo_cd(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_pce_cd(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_pcfx_cd(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_psx(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_ps2(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_psp(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_sega_cd(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_wii(char hash[33], const rc_hash_iterator_t* iterator);
#endif
#ifndef RC_HASH_NO_ENCRYPTED
/* hash_encrypted.c */
void rc_hash_reset_iterator_encrypted(rc_hash_iterator_t* iterator);
int rc_hash_nintendo_3ds(char hash[33], const rc_hash_iterator_t* iterator);
#endif
#ifndef RC_HASH_NO_ZIP
/* hash_zip.c */
int rc_hash_ms_dos(char hash[33], const rc_hash_iterator_t* iterator);
int rc_hash_arduboyfx(char hash[33], const rc_hash_iterator_t* iterator);
#endif
RC_END_C_DECLS
#endif /* RC_HASH_INTERNAL_H */

View File

@ -50,7 +50,7 @@ struct progress_tracker_info
retro_time_t show_until;
};
#define CHEEVO_LBOARD_FIRST_FIXED_CHAR 0x2D /* -./0123456789: */
#define CHEEVO_LBOARD_FIRST_FIXED_CHAR 0x2C /* ,-./0123456789: */
#define CHEEVO_LBOARD_LAST_FIXED_CHAR 0x3A
/* TODO: rename; this file handles all achievement tracker information, not just leaderboards */

View File

@ -226,6 +226,10 @@ ACHIEVEMENTS
#include "../deps/rcheevos/src/rhash/aes.c"
#include "../deps/rcheevos/src/rhash/cdreader.c"
#include "../deps/rcheevos/src/rhash/hash.c"
#include "../deps/rcheevos/src/rhash/hash_rom.c"
#include "../deps/rcheevos/src/rhash/hash_disc.c"
#include "../deps/rcheevos/src/rhash/hash_zip.c"
#include "../deps/rcheevos/src/rhash/hash_encrypted.c"
#endif

View File

@ -10302,6 +10302,14 @@ MSG_HASH(
MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME_CANCEL,
"Leave achievements hardcore mode disabled for the current session"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_RESUME_REQUIRES_RELOAD,
"Resume Achievements Hardcore Mode Disabled"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME_REQUIRES_RELOAD,
"You must reload the core to resume Achievements Hardcore Mode"
)
MSG_HASH(
MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_PAUSE,
"Pause Achievements Hardcore Mode"

View File

@ -9164,6 +9164,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs,
{MENU_ENUM_LABEL_ACHIEVEMENT_PAUSE_CANCEL, action_ok_close_submenu},
{MENU_ENUM_LABEL_ACHIEVEMENT_RESUME, action_ok_cheevos_toggle_hardcore_mode},
{MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_CANCEL, action_ok_close_submenu },
{MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_REQUIRES_RELOAD, action_ok_close_submenu },
{MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST, action_ok_push_manual_content_scan_list},
{MENU_ENUM_LABEL_AUDIO_OUTPUT_SETTINGS, action_ok_push_audio_output_settings_list},
#ifdef HAVE_MICROPHONE

View File

@ -309,6 +309,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_information_list_list, MENU_
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_list, MENU_ENUM_SUBLABEL_ACHIEVEMENT_LIST)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_pause_cancel, MENU_ENUM_SUBLABEL_ACHIEVEMENT_PAUSE_CANCEL)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_resume_cancel, MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME_CANCEL)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_resume_requires_reload, MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME_REQUIRES_RELOAD)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_server_unreachable,MENU_ENUM_SUBLABEL_ACHIEVEMENT_SERVER_UNREACHABLE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_enable, MENU_ENUM_SUBLABEL_CHEEVOS_ENABLE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_test_unofficial, MENU_ENUM_SUBLABEL_CHEEVOS_TEST_UNOFFICIAL)
@ -4835,6 +4836,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
case MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_CANCEL:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_achievement_resume_cancel);
break;
case MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_REQUIRES_RELOAD:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_achievement_resume_requires_reload);
break;
case MENU_ENUM_LABEL_ACHIEVEMENT_SERVER_UNREACHABLE:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_achievement_server_unreachable);
break;

View File

@ -3092,6 +3092,7 @@ enum msg_hash_enums
MENU_LABEL(ACHIEVEMENT_PAUSE_MENU),
MENU_LABEL(ACHIEVEMENT_PAUSE_CANCEL),
MENU_LABEL(ACHIEVEMENT_RESUME_CANCEL),
MENU_LABEL(ACHIEVEMENT_RESUME_REQUIRES_RELOAD),
MENU_LABEL(ACHIEVEMENT_PAUSE),
MENU_LABEL(ACHIEVEMENT_RESUME),
MENU_LABEL(ACHIEVEMENT_SERVER_UNREACHABLE),