mirror of
https://github.com/stenzek/duckstation.git
synced 2024-11-23 13:59:49 +00:00
dep/rcheevos: Bump to v11.5.0 + local changes
https://github.com/stenzek/rcheevos
This commit is contained in:
parent
ec5d8cb1d6
commit
59a1cca858
@ -1,3 +1,70 @@
|
||||
# v11.5.0
|
||||
* add total_entries to rc_api_fetch_leaderboard_info_response_t
|
||||
* add RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED event
|
||||
* modify rc_client_begin_identify_and_load_game and rc_client_begin_change_media to use locally
|
||||
registered filereader/cdreader for hash resolution when using rc_client_raintegration
|
||||
* add support for ISO-8601 timestamps in JSON responses
|
||||
* update RC_CONSOLE_MS_DOS hash logic to support parent archives
|
||||
* fix infinite loop that sometimes occurs when resetting while progress tracker is onscreen
|
||||
|
||||
# v11.4.0
|
||||
* add RC_CONDITION_REMEMBER and RC_OPERAND_RECALL
|
||||
* add RC_OPERATOR_ADD and RC_OPERATOR_SUB
|
||||
* add scratch pad memory to PSX memory map
|
||||
* add Super Game Module memory to Colecovision memory map
|
||||
* add rapi function fetch_game_titles
|
||||
* modify progress functions to return RC_NO_GAME_LOADED when "Unknown Game" is loaded
|
||||
* update subsystem list for arcade hash
|
||||
* fix exception if server sends null as achievement.author
|
||||
|
||||
# v11.3.0
|
||||
* add RC_OPERATOR_MOD
|
||||
* add cartridge RAM to Game Gear and Master System memory maps
|
||||
* add extended cartridge RAM to Gameboy and Gameboy Color memory maps
|
||||
* add rc_client_is_game_loaded helper function
|
||||
* add rc_client_raintegration_set_console_id to specify console in case game resolution fails
|
||||
* add rc_client_raintegration_get_achievement_state to detect local unlocks
|
||||
* report validation errors on multi-condition logic
|
||||
* hash whole file for PSP homebrew files (eboot.pbp)
|
||||
* call DrawMenuBar in rc_client_raintegration_rebuild_submenu if menu changes
|
||||
* fix file sharing issue using default filereader on Windows
|
||||
* fix exception calling rc_client_get_game_summary with an unidentified game loaded
|
||||
|
||||
# v11.2.0
|
||||
* add alternate methods for state serialization/deserialization that accept a buffer_size parameter
|
||||
* add RC_CLIENT_SUPPORTS_HASH compile flag
|
||||
- allows rc_client code to build without the rhash files (except md5.c)
|
||||
- must be explicitly defined to use rc_client_begin_identify_and_load_game
|
||||
* add rc_client_get_load_game_state
|
||||
* add rc_client_raintegration_set_get_game_name_function
|
||||
* add RC_MEMSIZE_DOUBLE32 and RC_MEMSIZE_DOUBLE32_BE
|
||||
* exclude directory records from ZIP hash algorithm
|
||||
* fix media host when explicitly setting host to production server
|
||||
* fix potential out-of-bounds read looking for error message in non-JSON response
|
||||
|
||||
# v11.1.0
|
||||
* add rc_client_get_user_agent_clause to generate substring to include in client User-Agents
|
||||
* add rc_client_can_pause function to control pause spam
|
||||
* add achievement type and rarity to rc_api_fetch_game_data_response_t and rc_client_achievement_t
|
||||
* add RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED for achievements that have been unlocked locally but not synced to the server
|
||||
* add RC_CONSOLE_NEO_GEO_CD to supported consoles for chd file extension
|
||||
* add hash logic for RC_CONSOLE_NINTENDO_3DS (note: added new file rhash/aes.c to support this)
|
||||
* add hash logic for RC_CONSOLE_MS_DOS
|
||||
* add game_hash and hardcore fields to rc_api_start_session_request_t and rc_api_ping_request_t
|
||||
* add RC_FORMAT_FIXED1/2/3, RC_FORMAT_TENS, RC_FORMAT_HUNDREDS, RC_FORMAT_THOUSANDS, and RC_FORMAT_UNSIGNED_VALUE
|
||||
* add RC_CONSOLE_STANDALONE
|
||||
* add extern "C" and __cdecl attributes to public functions
|
||||
* add __declspec(dllexport/dllimport) attributes to public functions via #define enablement
|
||||
* add rc_version and rc_version_string functions for accessing version from external linkage
|
||||
* add unicode path support to default filereader (Windows builds)
|
||||
* add rc_mutex support for GEKKO (libogc)
|
||||
* fix async_handle being returned when rc_client_begin_login is aborted synchronously
|
||||
* fix logic error hashing CD files smaller than one sector
|
||||
* fix read across region boundary in rc_libretro_memory_read
|
||||
* fix RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW event not being raised if achievement is reset in the same frame that it's primed
|
||||
* moved rc_util.h from src/ to include/
|
||||
* initial (incomplete) support for rc_client_external_t and rc_client_raintegration_t
|
||||
|
||||
# v11.0.0
|
||||
* add rc_client_t and related functions
|
||||
* add RC_MEMSIZE_FLOAT_BE
|
||||
|
@ -129,6 +129,9 @@ typedef struct rc_api_fetch_leaderboard_info_response_t {
|
||||
/* The number of items in the entries array */
|
||||
uint32_t num_entries;
|
||||
|
||||
/* The total number of entries on the server */
|
||||
uint32_t total_entries;
|
||||
|
||||
/* Common server-provided response information */
|
||||
rc_api_response_t response;
|
||||
}
|
||||
|
@ -517,6 +517,7 @@ typedef struct rc_client_leaderboard_entry_t {
|
||||
typedef struct rc_client_leaderboard_entry_list_t {
|
||||
rc_client_leaderboard_entry_t* entries;
|
||||
uint32_t num_entries;
|
||||
uint32_t total_entries;
|
||||
int32_t user_index;
|
||||
} rc_client_leaderboard_entry_list_t;
|
||||
|
||||
|
@ -39,7 +39,8 @@ enum {
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE = 0,
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED = 1, /* [menu_item] checked changed */
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED = 2, /* hardcore was enabled or disabled */
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_PAUSE = 3 /* emulated system should be paused */
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_PAUSE = 3, /* emulated system should be paused */
|
||||
RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED = 4 /* one or more items were added/removed from the menu and it should be rebuilt */
|
||||
};
|
||||
|
||||
typedef struct rc_client_raintegration_event_t {
|
||||
|
@ -864,7 +864,10 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char*
|
||||
|
||||
if (*field->value_start == '\"') {
|
||||
memset(&tm, 0, sizeof(tm));
|
||||
if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d",
|
||||
if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d", /* DB format "2013-10-20 22:12:21" */
|
||||
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6 ||
|
||||
/* NOTE: relies on sscanf stopping when it sees a non-digit after the seconds. could be 'Z', '.', '+', or '-' */
|
||||
sscanf_s(field->value_start + 1, "%d-%d-%dT%d:%d:%d", /* ISO format "2013-10-20T22:12:21.000000Z */
|
||||
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) {
|
||||
tm.tm_mon--; /* 0-based */
|
||||
tm.tm_year -= 1900; /* 1900 based */
|
||||
|
@ -189,7 +189,8 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa
|
||||
RC_JSON_NEW_FIELD("LBAuthor"),
|
||||
RC_JSON_NEW_FIELD("LBCreated"),
|
||||
RC_JSON_NEW_FIELD("LBUpdated"),
|
||||
RC_JSON_NEW_FIELD("Entries") /* array */
|
||||
RC_JSON_NEW_FIELD("Entries"), /* array */
|
||||
RC_JSON_NEW_FIELD("TotalEntries")
|
||||
/* unused fields
|
||||
RC_JSON_NEW_FIELD("GameTitle"),
|
||||
RC_JSON_NEW_FIELD("ConsoleID"),
|
||||
@ -235,6 +236,8 @@ int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboa
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_datetime(&response->updated, &response->response, &leaderboarddata_fields[9], "LBUpdated"))
|
||||
return RC_MISSING_VALUE;
|
||||
if (!rc_json_get_required_unum(&response->total_entries, &response->response, &leaderboarddata_fields[11], "TotalEntries"))
|
||||
return RC_MISSING_VALUE;
|
||||
|
||||
if (!leaderboarddata_fields[1].value_end)
|
||||
return RC_MISSING_VALUE;
|
||||
|
@ -78,6 +78,7 @@ typedef struct rc_client_load_state_t
|
||||
#endif
|
||||
} rc_client_load_state_t;
|
||||
|
||||
static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state);
|
||||
static void rc_client_begin_fetch_game_data(rc_client_load_state_t* callback_data);
|
||||
static void rc_client_hide_progress_tracker(rc_client_t* client, rc_client_game_info_t* game);
|
||||
static void rc_client_load_error(rc_client_load_state_t* load_state, int result, const char* error_message);
|
||||
@ -1984,12 +1985,101 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
|
||||
rc_api_destroy_fetch_game_data_response(&fetch_game_data_response);
|
||||
}
|
||||
|
||||
static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
static rc_client_game_info_t* rc_client_allocate_game(void)
|
||||
{
|
||||
rc_client_game_info_t* game = (rc_client_game_info_t*)calloc(1, sizeof(*game));
|
||||
if (!game)
|
||||
return NULL;
|
||||
|
||||
rc_buffer_init(&game->buffer);
|
||||
rc_runtime_init(&game->runtime);
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
static int rc_client_attach_load_state(rc_client_t* client, rc_client_load_state_t* load_state)
|
||||
{
|
||||
if (client->state.load == NULL) {
|
||||
rc_client_unload_game(client);
|
||||
client->state.load = load_state;
|
||||
|
||||
if (load_state->game == NULL) {
|
||||
load_state->game = rc_client_allocate_game();
|
||||
if (!load_state->game) {
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, load_state->callback_userdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (client->state.load != load_state) {
|
||||
/* previous load was aborted */
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
|
||||
static void rc_client_external_load_state_callback(int result, const char* error_message, rc_client_t* client, void* userdata)
|
||||
{
|
||||
rc_client_load_state_t* load_state = (rc_client_load_state_t*)userdata;
|
||||
int async_aborted;
|
||||
|
||||
client = load_state->client;
|
||||
async_aborted = rc_client_end_async(client, &load_state->async_handle);
|
||||
if (async_aborted) {
|
||||
if (async_aborted != RC_CLIENT_ASYNC_DESTROYED) {
|
||||
RC_CLIENT_LOG_VERBOSE(client, "Load aborted during external loading");
|
||||
}
|
||||
|
||||
rc_client_unload_game(client); /* unload the game from the external client */
|
||||
rc_client_free_load_state(load_state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != RC_OK) {
|
||||
rc_client_load_error(load_state, result, error_message);
|
||||
return;
|
||||
}
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
load_state->progress = (client->state.load == load_state) ?
|
||||
RC_CLIENT_LOAD_GAME_STATE_DONE : RC_CLIENT_LOAD_GAME_STATE_ABORTED;
|
||||
client->state.load = NULL;
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_DONE) {
|
||||
/* previous load state was aborted */
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata);
|
||||
}
|
||||
else {
|
||||
/* keep partial game object for media_hash management */
|
||||
if (client->state.external_client && client->state.external_client->get_game_info) {
|
||||
const rc_client_game_t* info = client->state.external_client->get_game_info();
|
||||
load_state->game->public_.console_id = info->console_id;
|
||||
client->game = load_state->game;
|
||||
load_state->game = NULL;
|
||||
}
|
||||
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_OK, NULL, client, load_state->callback_userdata);
|
||||
}
|
||||
|
||||
rc_client_free_load_state(load_state);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void rc_client_process_resolved_hash(rc_client_load_state_t* load_state)
|
||||
{
|
||||
rc_api_fetch_game_data_request_t fetch_game_data_request;
|
||||
rc_client_t* client = load_state->client;
|
||||
rc_api_request_t request;
|
||||
int result;
|
||||
|
||||
if (load_state->hash->game_id == 0) {
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
@ -2058,6 +2148,18 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
#endif /* RC_CLIENT_SUPPORTS_HASH */
|
||||
|
||||
if (load_state->hash->game_id == 0) {
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->load_unknown_game) {
|
||||
client->state.external_client->load_unknown_game(load_state->game->public_.hash);
|
||||
rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game");
|
||||
return;
|
||||
}
|
||||
/* no external method specifically for unknown game, just pass the hash through to begin_load_game below */
|
||||
}
|
||||
else {
|
||||
#endif
|
||||
/* mimics rc_client_load_unknown_game without allocating a new game object */
|
||||
rc_client_subset_info_t* subset;
|
||||
|
||||
subset = (rc_client_subset_info_t*)rc_buffer_alloc(&load_state->game->buffer, sizeof(rc_client_subset_info_t));
|
||||
@ -2072,6 +2174,9 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
|
||||
rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game");
|
||||
return;
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -2083,6 +2188,60 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
/* done with the hashing code, release the global pointer */
|
||||
g_hash_client = NULL;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->add_game_hash)
|
||||
client->state.external_client->add_game_hash(load_state->hash->hash, load_state->hash->game_id);
|
||||
|
||||
if (client->state.external_client->begin_load_game) {
|
||||
rc_client_begin_async(client, &load_state->async_handle);
|
||||
client->state.external_client->begin_load_game(client, load_state->hash->hash, rc_client_external_load_state_callback, load_state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
}
|
||||
|
||||
void rc_client_load_unknown_game(rc_client_t* client, const char* tried_hashes)
|
||||
{
|
||||
rc_client_subset_info_t* subset;
|
||||
rc_client_game_info_t* game;
|
||||
|
||||
game = rc_client_allocate_game();
|
||||
if (!game)
|
||||
return;
|
||||
|
||||
subset = (rc_client_subset_info_t*)rc_buffer_alloc(&game->buffer, sizeof(rc_client_subset_info_t));
|
||||
memset(subset, 0, sizeof(*subset));
|
||||
subset->public_.title = "";
|
||||
game->subsets = subset;
|
||||
|
||||
game->public_.title = "Unknown Game";
|
||||
game->public_.badge_name = "";
|
||||
game->public_.console_id = RC_CONSOLE_UNKNOWN;
|
||||
|
||||
if (strlen(tried_hashes) == 32) { /* only one hash tried, add it to the list */
|
||||
rc_client_game_hash_t* game_hash = rc_client_find_game_hash(client, tried_hashes);
|
||||
game_hash->game_id = 0;
|
||||
game->public_.hash = game_hash->hash;
|
||||
}
|
||||
else {
|
||||
game->public_.hash = rc_buffer_strcpy(&game->buffer, tried_hashes);
|
||||
}
|
||||
|
||||
rc_client_unload_game(client);
|
||||
client->game = game;
|
||||
}
|
||||
|
||||
static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
|
||||
{
|
||||
rc_api_fetch_game_data_request_t fetch_game_data_request;
|
||||
rc_client_t* client = load_state->client;
|
||||
rc_api_request_t request;
|
||||
int result;
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
result = client->state.user;
|
||||
if (result == RC_CLIENT_USER_STATE_LOGIN_REQUESTED)
|
||||
@ -2160,7 +2319,7 @@ static void rc_client_identify_game_callback(const rc_api_server_response_t* ser
|
||||
/* previous load state was aborted, load_state was free'd */
|
||||
}
|
||||
else {
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2193,35 +2352,25 @@ rc_client_game_hash_t* rc_client_find_game_hash(rc_client_t* client, const char*
|
||||
return game_hash;
|
||||
}
|
||||
|
||||
void rc_client_add_game_hash(rc_client_t* client, const char* hash, uint32_t game_id)
|
||||
{
|
||||
/* store locally, even if passing to external client */
|
||||
rc_client_game_hash_t* game_hash = rc_client_find_game_hash(client, hash);
|
||||
game_hash->game_id = game_id;
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->add_game_hash)
|
||||
client->state.external_client->add_game_hash(hash, game_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* load_state,
|
||||
const char* hash, const char* file_path)
|
||||
{
|
||||
rc_client_t* client = load_state->client;
|
||||
rc_client_game_hash_t* old_hash;
|
||||
|
||||
if (client->state.load == NULL) {
|
||||
rc_client_unload_game(client);
|
||||
client->state.load = load_state;
|
||||
|
||||
if (load_state->game == NULL) {
|
||||
load_state->game = (rc_client_game_info_t*)calloc(1, sizeof(*load_state->game));
|
||||
if (!load_state->game) {
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, load_state->callback_userdata);
|
||||
|
||||
rc_client_free_load_state(load_state);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc_buffer_init(&load_state->game->buffer);
|
||||
rc_runtime_init(&load_state->game->runtime);
|
||||
}
|
||||
}
|
||||
else if (client->state.load != load_state) {
|
||||
/* previous load was aborted */
|
||||
if (load_state->callback)
|
||||
load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata);
|
||||
|
||||
if (!rc_client_attach_load_state(client, load_state)) {
|
||||
rc_client_free_load_state(load_state);
|
||||
return NULL;
|
||||
}
|
||||
@ -2265,7 +2414,7 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
|
||||
else {
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "Identified game: %u (%s)", load_state->hash->game_id, load_state->hash->hash);
|
||||
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
}
|
||||
|
||||
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
|
||||
@ -2327,8 +2476,12 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->begin_identify_and_load_game)
|
||||
/* if a add_game_hash handler exists, do the identification locally, then pass the
|
||||
* resulting game_id/hash to the external client */
|
||||
if (client->state.external_client && !client->state.external_client->add_game_hash) {
|
||||
if (client->state.external_client->begin_identify_and_load_game)
|
||||
return client->state.external_client->begin_identify_and_load_game(client, console_id, file_path, data, data_size, callback, callback_userdata);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (data) {
|
||||
@ -2471,6 +2624,13 @@ void rc_client_unload_game(rc_client_t* client)
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->unload_game) {
|
||||
client->state.external_client->unload_game();
|
||||
|
||||
/* a game object may have been allocated to manage hashes */
|
||||
game = client->game;
|
||||
client->game = NULL;
|
||||
if (game != NULL)
|
||||
rc_client_free_game(game);
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@ -2629,15 +2789,77 @@ static rc_client_async_handle_t* rc_client_begin_change_media_internal(rc_client
|
||||
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
|
||||
}
|
||||
|
||||
static rc_client_game_info_t* rc_client_check_pending_media(rc_client_t* client, const rc_client_pending_media_t* media)
|
||||
{
|
||||
rc_client_game_info_t* game;
|
||||
rc_client_pending_media_t* pending_media = NULL;
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
if (client->state.load) {
|
||||
game = client->state.load->game;
|
||||
if (!game || game->public_.console_id == 0) {
|
||||
/* still waiting for game data */
|
||||
pending_media = client->state.load->pending_media;
|
||||
if (pending_media)
|
||||
rc_client_free_pending_media(pending_media);
|
||||
|
||||
pending_media = (rc_client_pending_media_t*)malloc(sizeof(*pending_media));
|
||||
if (!pending_media) {
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
media->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, media->callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(pending_media, media, sizeof(*pending_media));
|
||||
if (media->hash)
|
||||
pending_media->hash = strdup(media->hash);
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
if (media->file_path)
|
||||
pending_media->file_path = strdup(media->file_path);
|
||||
|
||||
if (media->data && media->data_size) {
|
||||
pending_media->data = (uint8_t*)malloc(media->data_size);
|
||||
if (!pending_media->data) {
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
media->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, media->callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(pending_media->data, media->data, media->data_size);
|
||||
} else {
|
||||
pending_media->data = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
client->state.load->pending_media = pending_media;
|
||||
}
|
||||
}
|
||||
else {
|
||||
game = client->game;
|
||||
}
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
if (!game) {
|
||||
media->callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, media->callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* still waiting for game data - don't call callback - it's queued */
|
||||
if (pending_media)
|
||||
return NULL;
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_HASH
|
||||
|
||||
rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, const char* file_path,
|
||||
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata)
|
||||
{
|
||||
rc_client_pending_media_t media;
|
||||
rc_client_game_hash_t* game_hash = NULL;
|
||||
rc_client_media_hash_t* media_hash;
|
||||
rc_client_game_info_t* game;
|
||||
rc_client_pending_media_t* pending_media = NULL;
|
||||
rc_client_media_hash_t* media_hash;
|
||||
uint32_t path_djb2;
|
||||
|
||||
if (!client) {
|
||||
@ -2651,55 +2873,21 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->begin_change_media)
|
||||
if (client->state.external_client && !client->state.external_client->begin_change_media_from_hash) {
|
||||
if (client->state.external_client->begin_change_media)
|
||||
return client->state.external_client->begin_change_media(client, file_path, data, data_size, callback, callback_userdata);
|
||||
}
|
||||
#endif
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
if (client->state.load) {
|
||||
game = client->state.load->game;
|
||||
if (game->public_.console_id == 0) {
|
||||
/* still waiting for game data */
|
||||
pending_media = client->state.load->pending_media;
|
||||
if (pending_media)
|
||||
rc_client_free_pending_media(pending_media);
|
||||
memset(&media, 0, sizeof(media));
|
||||
media.file_path = file_path;
|
||||
media.data = (uint8_t*)data;
|
||||
media.data_size = data_size;
|
||||
media.callback = callback;
|
||||
media.callback_userdata = callback_userdata;
|
||||
|
||||
pending_media = (rc_client_pending_media_t*)calloc(1, sizeof(*pending_media));
|
||||
if (!pending_media) {
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pending_media->file_path = strdup(file_path);
|
||||
pending_media->callback = callback;
|
||||
pending_media->callback_userdata = callback_userdata;
|
||||
if (data && data_size) {
|
||||
pending_media->data_size = data_size;
|
||||
pending_media->data = (uint8_t*)malloc(data_size);
|
||||
if (!pending_media->data) {
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(pending_media->data, data, data_size);
|
||||
}
|
||||
|
||||
client->state.load->pending_media = pending_media;
|
||||
}
|
||||
}
|
||||
else {
|
||||
game = client->game;
|
||||
}
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
if (!game) {
|
||||
callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* still waiting for game data */
|
||||
if (pending_media)
|
||||
game = rc_client_check_pending_media(client, &media);
|
||||
if (game == NULL)
|
||||
return NULL;
|
||||
|
||||
/* check to see if we've already hashed this file */
|
||||
@ -2749,11 +2937,25 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
if (!result) {
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client && client->state.external_client->begin_change_media_from_hash)
|
||||
return client->state.external_client->begin_change_media_from_hash(client, game_hash->hash, callback, callback_userdata);
|
||||
#endif
|
||||
|
||||
rc_client_change_media_internal(client, game_hash, callback, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
|
||||
if (client->state.external_client) {
|
||||
if (client->state.external_client->add_game_hash)
|
||||
client->state.external_client->add_game_hash(game_hash->hash, game_hash->game_id);
|
||||
if (client->state.external_client->begin_change_media_from_hash)
|
||||
return client->state.external_client->begin_change_media_from_hash(client, game_hash->hash, callback, callback_userdata);
|
||||
}
|
||||
#endif
|
||||
|
||||
return rc_client_begin_change_media_internal(client, game, game_hash, callback, callback_userdata);
|
||||
}
|
||||
|
||||
@ -2762,9 +2964,9 @@ rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, cons
|
||||
rc_client_async_handle_t* rc_client_begin_change_media_from_hash(rc_client_t* client, const char* hash,
|
||||
rc_client_callback_t callback, void* callback_userdata)
|
||||
{
|
||||
rc_client_pending_media_t media;
|
||||
rc_client_game_hash_t* game_hash;
|
||||
rc_client_game_info_t* game;
|
||||
rc_client_pending_media_t* pending_media = NULL;
|
||||
|
||||
if (!client) {
|
||||
callback(RC_INVALID_STATE, "client is required", client, callback_userdata);
|
||||
@ -2782,40 +2984,13 @@ rc_client_async_handle_t* rc_client_begin_change_media_from_hash(rc_client_t* cl
|
||||
}
|
||||
#endif
|
||||
|
||||
rc_mutex_lock(&client->state.mutex);
|
||||
if (client->state.load) {
|
||||
game = client->state.load->game;
|
||||
if (game->public_.console_id == 0) {
|
||||
/* still waiting for game data */
|
||||
pending_media = client->state.load->pending_media;
|
||||
if (pending_media)
|
||||
rc_client_free_pending_media(pending_media);
|
||||
memset(&media, 0, sizeof(media));
|
||||
media.hash = hash;
|
||||
media.callback = callback;
|
||||
media.callback_userdata = callback_userdata;
|
||||
|
||||
pending_media = (rc_client_pending_media_t*)calloc(1, sizeof(*pending_media));
|
||||
if (!pending_media) {
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pending_media->hash = strdup(hash);
|
||||
pending_media->callback = callback;
|
||||
pending_media->callback_userdata = callback_userdata;
|
||||
|
||||
client->state.load->pending_media = pending_media;
|
||||
}
|
||||
} else {
|
||||
game = client->game;
|
||||
}
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
|
||||
if (!game) {
|
||||
callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* still waiting for game data */
|
||||
if (pending_media)
|
||||
game = rc_client_check_pending_media(client, &media);
|
||||
if (game == NULL)
|
||||
return NULL;
|
||||
|
||||
/* check to see if we've already hashed this file. */
|
||||
@ -2882,7 +3057,7 @@ rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint3
|
||||
load_state->hash->game_id = subset_id;
|
||||
client->state.load = load_state;
|
||||
|
||||
rc_client_begin_fetch_game_data(load_state);
|
||||
rc_client_process_resolved_hash(load_state);
|
||||
|
||||
return (client->state.load == load_state) ? &load_state->async_handle : NULL;
|
||||
}
|
||||
@ -3570,9 +3745,11 @@ static void rc_client_award_achievement(rc_client_t* client, rc_client_achieveme
|
||||
callback_data->client = client;
|
||||
callback_data->id = achievement->public_.id;
|
||||
callback_data->hardcore = client->state.hardcore;
|
||||
callback_data->game_hash = client->game->public_.hash;
|
||||
callback_data->unlock_time = achievement->public_.unlock_time;
|
||||
|
||||
if (client->game) /* may be NULL if this gets called while unloading the game (from another thread - events are raised outside the lock) */
|
||||
callback_data->game_hash = client->game->public_.hash;
|
||||
|
||||
RC_CLIENT_LOG_INFO_FORMATTED(client, "Awarding achievement %u: %s", achievement->public_.id, achievement->public_.title);
|
||||
rc_client_award_achievement_server_call(callback_data);
|
||||
}
|
||||
@ -4311,6 +4488,7 @@ static void rc_client_fetch_leaderboard_entries_callback(const rc_api_server_res
|
||||
}
|
||||
|
||||
list->num_entries = lbinfo_response.num_entries;
|
||||
list->total_entries = lbinfo_response.total_entries;
|
||||
|
||||
lbinfo_callback_data->callback(RC_OK, NULL, list, client, lbinfo_callback_data->callback_userdata);
|
||||
}
|
||||
@ -4797,6 +4975,8 @@ static void rc_client_progress_tracker_timer_elapsed(rc_client_scheduled_callbac
|
||||
|
||||
static void rc_client_do_frame_update_progress_tracker(rc_client_t* client, rc_client_game_info_t* game)
|
||||
{
|
||||
/* ASSERT: this should only be called if the mutex is held */
|
||||
|
||||
if (!game->progress_tracker.hide_callback) {
|
||||
game->progress_tracker.hide_callback = (rc_client_scheduled_callback_data_t*)
|
||||
rc_buffer_alloc(&game->buffer, sizeof(rc_client_scheduled_callback_data_t));
|
||||
@ -5181,6 +5361,7 @@ void rc_client_idle(rc_client_t* client)
|
||||
else {
|
||||
/* remove the callback from the queue while we process it. callback can requeue if desired */
|
||||
client->state.scheduled_callbacks = scheduled_callback->next;
|
||||
scheduled_callback->next = NULL;
|
||||
}
|
||||
}
|
||||
rc_mutex_unlock(&client->state.mutex);
|
||||
@ -5256,7 +5437,7 @@ static void rc_client_reschedule_callback(rc_client_t* client,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!next || when < next->when) {
|
||||
if (!next || (when < next->when && when != 0)) {
|
||||
/* insert here */
|
||||
callback->next = next;
|
||||
*last = callback;
|
||||
|
@ -38,6 +38,7 @@ typedef const rc_client_subset_t* (RC_CCONV *rc_client_external_get_subset_info_
|
||||
typedef void (RC_CCONV *rc_client_external_get_user_game_summary_func_t)(rc_client_user_game_summary_t* summary);
|
||||
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_change_media_func_t)(rc_client_t* client, const char* file_path,
|
||||
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
|
||||
typedef void (RC_CCONV* rc_client_external_add_game_hash_func_t)(const char* hash, uint32_t game_id);
|
||||
|
||||
/* NOTE: rc_client_external_create_achievement_list_func_t returns an internal wrapper structure which contains the public list
|
||||
* and a destructor function. */
|
||||
@ -124,9 +125,16 @@ typedef struct rc_client_external_t
|
||||
rc_client_external_serialize_progress_func_t serialize_progress;
|
||||
rc_client_external_deserialize_progress_func_t deserialize_progress;
|
||||
|
||||
/* VERSION 2 */
|
||||
rc_client_external_add_game_hash_func_t add_game_hash;
|
||||
rc_client_external_set_string_func_t load_unknown_game;
|
||||
|
||||
} rc_client_external_t;
|
||||
|
||||
#define RC_CLIENT_EXTERNAL_VERSION 1
|
||||
#define RC_CLIENT_EXTERNAL_VERSION 2
|
||||
|
||||
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);
|
||||
|
||||
RC_END_C_DECLS
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
RC_BEGIN_C_DECLS
|
||||
|
||||
#define RCHEEVOS_VERSION_MAJOR 11
|
||||
#define RCHEEVOS_VERSION_MINOR 4
|
||||
#define RCHEEVOS_VERSION_MINOR 5
|
||||
#define RCHEEVOS_VERSION_PATCH 0
|
||||
|
||||
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)
|
||||
|
@ -700,6 +700,12 @@ struct rc_hash_zip_idx
|
||||
uint8_t* data;
|
||||
};
|
||||
|
||||
struct rc_hash_ms_dos_dosz_state
|
||||
{
|
||||
const char* path;
|
||||
const struct rc_hash_ms_dos_dosz_state* child;
|
||||
};
|
||||
|
||||
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;
|
||||
@ -707,9 +713,12 @@ static int rc_hash_zip_idx_sort(const void* a, const void* b)
|
||||
return memcmp(A->data, B->data, len);
|
||||
}
|
||||
|
||||
static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
static int rc_hash_ms_dos_parent(md5_state_t* md5, const struct rc_hash_ms_dos_dosz_state *child, const char* parentname, uint32_t parentname_len);
|
||||
static int rc_hash_ms_dos_dosc(md5_state_t* md5, const struct rc_hash_ms_dos_dosz_state *dosz);
|
||||
|
||||
static int rc_hash_zip_file(md5_state_t* md5, void* file_handle, const struct rc_hash_ms_dos_dosz_state* dosz)
|
||||
{
|
||||
uint8_t buf[2048], *alloc_buf, *cdir_start, *cdir_max, *cdir, *hashdata, eocdirhdr_size, cdirhdr_size;
|
||||
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;
|
||||
@ -821,7 +830,7 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
hashindex = hashindices;
|
||||
|
||||
/* Now process the central directory file records */
|
||||
for (i_file = 0, cdir = cdir_start; i_file < total_files && cdir >= cdir_start && cdir <= cdir_max; i_file++, cdir += cdir_entry_len)
|
||||
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);
|
||||
@ -891,6 +900,27 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
return rc_hash_error("Encountered invalid entry in ZIP central directory");
|
||||
}
|
||||
|
||||
/* A DOSZ file can contain a special empty <base>.dosz.parent file in its root which means a parent dosz file is used */
|
||||
if (dosz && decomp_size == 0 && filename_len > 7 && !strncasecmp((const char*)name + filename_len - 7, ".parent", 7) && !memchr(name, '/', filename_len) && !memchr(name, '\\', filename_len))
|
||||
{
|
||||
/* A DOSZ file can only have one parent file */
|
||||
if (nparents++)
|
||||
{
|
||||
free(alloc_buf);
|
||||
return rc_hash_error("Invalid DOSZ file with multiple parents");
|
||||
}
|
||||
|
||||
/* If there is an error with the parent DOSZ, abort now */
|
||||
if (!rc_hash_ms_dos_parent(md5, dosz, (const char*)name, (filename_len - 7)))
|
||||
{
|
||||
free(alloc_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We don't hash this meta file so a user is free to rename it and the parent file */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Write the pointer and length of the data we record about this file */
|
||||
hashindex->data = hashdata;
|
||||
hashindex->length = filename_len + 1 + 4 + 8;
|
||||
@ -935,6 +965,11 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
md5_append(md5, hashindices->data, (int)hashindices->length);
|
||||
|
||||
free(alloc_buf);
|
||||
|
||||
/* If this is a .dosz file, check if an associated .dosc file exists */
|
||||
if (dosz && !rc_hash_ms_dos_dosc(md5, dosz))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
|
||||
#undef RC_ZIP_READ_LE16
|
||||
@ -944,10 +979,86 @@ static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
|
||||
#undef RC_ZIP_WRITE_LE64
|
||||
}
|
||||
|
||||
static int rc_hash_ms_dos_parent(md5_state_t* md5, 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_error("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_error("Invalid DOSZ file with recursive parents");
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to open the parent DOSZ file */
|
||||
parent_handle = rc_file_open(parent_path);
|
||||
if (!parent_handle)
|
||||
{
|
||||
char message[1024];
|
||||
snprintf(message, sizeof(message), "DOSZ parent file '%s' does not exist", parent_path);
|
||||
free(parent_path);
|
||||
return rc_hash_error(message);
|
||||
}
|
||||
|
||||
/* Fully hash the parent DOSZ ahead of the child */
|
||||
parent.path = parent_path;
|
||||
parent.child = child;
|
||||
parent_res = rc_hash_zip_file(md5, parent_handle, &parent);
|
||||
rc_file_close(parent_handle);
|
||||
free(parent_path);
|
||||
return parent_res;
|
||||
}
|
||||
|
||||
static int rc_hash_ms_dos_dosc(md5_state_t* md5, 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_error("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(dosc_path);
|
||||
free(dosc_path);
|
||||
|
||||
if (file_handle)
|
||||
{
|
||||
/* Hash the DOSC as a plain zip file (pass NULL as dosz state) */
|
||||
int res = rc_hash_zip_file(md5, file_handle, NULL);
|
||||
rc_file_close(file_handle);
|
||||
if (!res)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int rc_hash_ms_dos(char hash[33], const char* path)
|
||||
{
|
||||
struct rc_hash_ms_dos_dosz_state dosz;
|
||||
md5_state_t md5;
|
||||
size_t path_len;
|
||||
int res;
|
||||
|
||||
void* file_handle = rc_file_open(path);
|
||||
@ -956,34 +1067,14 @@ static int rc_hash_ms_dos(char hash[33], const char* path)
|
||||
|
||||
/* hash the main content zip file first */
|
||||
md5_init(&md5);
|
||||
res = rc_hash_zip_file(&md5, file_handle);
|
||||
dosz.path = path;
|
||||
dosz.child = NULL;
|
||||
res = rc_hash_zip_file(&md5, file_handle, &dosz);
|
||||
rc_file_close(file_handle);
|
||||
|
||||
if (!res)
|
||||
return 0;
|
||||
|
||||
/* if this is a .dosz file, check if an associated .dosc file exists */
|
||||
path_len = strlen(path);
|
||||
if (path[path_len-1] == 'z' || path[path_len-1] == 'Z')
|
||||
{
|
||||
char *dosc_path = strdup(path);
|
||||
if (!dosc_path)
|
||||
return rc_hash_error("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] = (path[path_len-1] == 'z' ? 'c' : 'C');
|
||||
file_handle = rc_file_open(dosc_path);
|
||||
free((void*)dosc_path);
|
||||
if (file_handle)
|
||||
{
|
||||
res = rc_hash_zip_file(&md5, file_handle);
|
||||
rc_file_close(file_handle);
|
||||
|
||||
if (!res)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return rc_hash_finalize(&md5, hash);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user