dep/rcheevos: Update to 3d01191

This commit is contained in:
Stenzek 2024-02-24 14:52:57 +10:00
parent 272c43d139
commit 94657ae4ab
No known key found for this signature in database
20 changed files with 586 additions and 89 deletions

View file

@ -12,6 +12,7 @@ add_library(rcheevos
include/rc_runtime.h
include/rc_runtime_types.h
include/rc_url.h
include/rc_util.h
src/rapi/rc_api_common.c
src/rapi/rc_api_common.h
src/rapi/rc_api_editor.c
@ -39,7 +40,6 @@ add_library(rcheevos
src/rc_compat.c
src/rc_compat.h
src/rc_util.c
src/rc_util.h
src/rc_version.h
src/rhash/cdreader.c
src/rhash/hash.c

View file

@ -2,7 +2,7 @@
#define RC_API_REQUEST_H
#include "rc_error.h"
#include "../src/rc_util.h"
#include "rc_util.h"
#include <stddef.h>

View file

@ -136,6 +136,11 @@ RC_EXPORT void RC_CCONV rc_client_set_get_time_millisecs_function(rc_client_t* c
*/
RC_EXPORT void RC_CCONV rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle);
/**
* Gets a clause that can be added to the User-Agent to identify the version of rcheevos being used.
*/
RC_EXPORT size_t RC_CCONV rc_client_get_user_agent_clause(rc_client_t* client, char buffer[], size_t buffer_size);
/*****************************************************************************\
| Logging |
\*****************************************************************************/
@ -230,6 +235,20 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_g
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client_t* client, const char* hash,
rc_client_callback_t callback, void* callback_userdata);
/**
* Gets the current progress of the asynchronous load game process.
*/
RC_EXPORT int RC_CCONV rc_client_get_load_game_state(const rc_client_t* client);
enum {
RC_CLIENT_LOAD_GAME_STATE_NONE,
RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME,
RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN,
RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA,
RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION,
RC_CLIENT_LOAD_GAME_STATE_DONE,
RC_CLIENT_LOAD_GAME_STATE_ABORTED
};
/**
* Unloads the current game.
*/

View file

@ -46,6 +46,8 @@ typedef void (RC_CCONV *rc_client_raintegration_event_handler_t)(const rc_client
typedef void (RC_CCONV *rc_client_raintegration_write_memory_func_t)(uint32_t address, uint8_t* buffer,
uint32_t num_bytes, rc_client_t* client);
typedef void (RC_CCONV* rc_client_raintegration_get_game_name_func_t)(char* buffer, uint32_t buffer_size, rc_client_t* client);
/* types needed to integrate raintegration */
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
@ -74,6 +76,8 @@ RC_EXPORT void RC_CCONV rc_client_raintegration_update_menu_item(const rc_client
RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler);
RC_EXPORT int RC_CCONV rc_client_raintegration_has_modifications(const rc_client_t* client);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_event_handler(rc_client_t* client,
rc_client_raintegration_event_handler_t handler);

View file

@ -43,12 +43,12 @@
<ClInclude Include="include\rc_runtime.h" />
<ClInclude Include="include\rc_runtime_types.h" />
<ClInclude Include="include\rc_url.h" />
<ClInclude Include="include\rc_util.h" />
<ClInclude Include="src\rapi\rc_api_common.h" />
<ClInclude Include="src\rcheevos\rc_internal.h" />
<ClInclude Include="src\rcheevos\rc_validate.h" />
<ClInclude Include="src\rc_client_internal.h" />
<ClInclude Include="src\rc_compat.h" />
<ClInclude Include="src\rc_util.h" />
<ClInclude Include="src\rc_version.h" />
<ClInclude Include="src\rhash\md5.h" />
</ItemGroup>

View file

@ -144,8 +144,10 @@
<Filter>rcheevos</Filter>
</ClInclude>
<ClInclude Include="src\rc_compat.h" />
<ClInclude Include="src\rc_util.h" />
<ClInclude Include="src\rc_version.h" />
<ClInclude Include="src\rc_client_internal.h" />
<ClInclude Include="include\rc_util.h">
<Filter>include</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -1150,6 +1150,9 @@ static void rc_api_update_host(char** host, const char* hostname) {
}
void rc_api_set_host(const char* hostname) {
if (hostname && strcmp(hostname, RETROACHIEVEMENTS_HOST) == 0)
hostname = NULL;
rc_api_update_host(&g_host, hostname);
if (!hostname) {

View file

@ -5,6 +5,7 @@
#include "rc_api_user.h"
#include "rc_consoles.h"
#include "rc_hash.h"
#include "rc_version.h"
#include "rapi/rc_api_common.h"
@ -611,7 +612,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp
if (login_callback_data->callback)
login_callback_data->callback(result, error_message, client, login_callback_data->callback_userdata);
if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN)
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
rc_client_begin_fetch_game_data(load_state);
}
else {
@ -634,7 +635,7 @@ static void rc_client_login_callback(const rc_api_server_response_t* server_resp
RC_CLIENT_LOG_INFO_FORMATTED(client, "%s logged in successfully", login_response.display_name);
if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN)
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
rc_client_begin_fetch_game_data(load_state);
if (login_callback_data->callback)
@ -795,7 +796,7 @@ void rc_client_logout(rc_client_t* client)
rc_client_unload_game(client);
if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN)
if (load_state && load_state->progress == RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN)
rc_client_load_error(load_state, RC_ABORTED, "Login aborted");
}
@ -936,9 +937,9 @@ static int rc_client_end_load_state(rc_client_load_state_t* load_state)
* the outstanding_requests count will reach zero and the memory will be free'd then. */
if (remaining_requests == 0) {
/* if one of the callbacks called rc_client_load_error, progress will be set to
* RC_CLIENT_LOAD_STATE_UNKNOWN. There's no need to call the callback with RC_ABORTED
* RC_CLIENT_LOAD_STATE_ABORTED. There's no need to call the callback with RC_ABORTED
* in that case, as it will have already been called with something more appropriate. */
if (load_state->progress != RC_CLIENT_LOAD_STATE_UNKNOWN_GAME && load_state->callback)
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED && load_state->callback)
load_state->callback(RC_ABORTED, "The requested game is no longer active", load_state->client, load_state->callback_userdata);
rc_client_free_load_state(load_state);
@ -956,7 +957,7 @@ static void rc_client_load_error(rc_client_load_state_t* load_state, int result,
rc_mutex_lock(&load_state->client->state.mutex);
load_state->progress = RC_CLIENT_LOAD_STATE_UNKNOWN_GAME;
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED;
if (load_state->client->state.load == load_state)
load_state->client->state.load = NULL;
@ -1020,6 +1021,8 @@ static void rc_client_invalidate_memref_leaderboards(rc_client_game_info_t* game
for (; leaderboard < stop; ++leaderboard) {
if (leaderboard->public_.state == RC_CLIENT_LEADERBOARD_STATE_DISABLED)
continue;
if (!leaderboard->lboard)
continue;
if (rc_trigger_contains_memref(&leaderboard->lboard->start, memref))
leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED;
@ -1032,8 +1035,7 @@ static void rc_client_invalidate_memref_leaderboards(rc_client_game_info_t* game
else
continue;
if (leaderboard->lboard)
leaderboard->lboard->state = RC_LBOARD_STATE_DISABLED;
leaderboard->lboard->state = RC_LBOARD_STATE_DISABLED;
RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabled leaderboard %u. Invalid address %06X", leaderboard->public_.id, memref->address);
}
@ -1301,6 +1303,8 @@ static void rc_client_activate_leaderboards(rc_client_game_info_t* game, rc_clie
{
rc_client_leaderboard_info_t* leaderboard;
rc_client_leaderboard_info_t* stop;
const uint8_t leaderboards_allowed =
client->state.hardcore || client->state.allow_leaderboards_in_softcore;
uint32_t active_count = 0;
rc_client_subset_info_t* subset = game->subsets;
@ -1317,7 +1321,7 @@ static void rc_client_activate_leaderboards(rc_client_game_info_t* game, rc_clie
continue;
case RC_CLIENT_LEADERBOARD_STATE_INACTIVE:
if (client->state.hardcore) {
if (leaderboards_allowed) {
rc_reset_lboard(leaderboard->lboard);
leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE;
++active_count;
@ -1325,7 +1329,7 @@ static void rc_client_activate_leaderboards(rc_client_game_info_t* game, rc_clie
break;
default:
if (client->state.hardcore)
if (leaderboards_allowed)
++active_count;
else
leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_INACTIVE;
@ -1403,11 +1407,11 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
rc_mutex_lock(&client->state.mutex);
load_state->progress = (client->state.load == load_state) ?
RC_CLIENT_LOAD_STATE_DONE : RC_CLIENT_LOAD_STATE_UNKNOWN_GAME;
RC_CLIENT_LOAD_GAME_STATE_DONE : RC_CLIENT_LOAD_GAME_STATE_ABORTED;
client->state.load = NULL;
rc_mutex_unlock(&client->state.mutex);
if (load_state->progress != RC_CLIENT_LOAD_STATE_DONE) {
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_DONE) {
/* previous load state was aborted */
if (load_state->callback)
load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata);
@ -1557,7 +1561,7 @@ static void rc_client_begin_start_session(rc_client_load_state_t* load_state)
rc_client_load_error(load_state, result, rc_error_str(result));
}
else {
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, 1);
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Starting session for game %u", start_session_params.game_id);
rc_client_begin_async(client, &load_state->async_handle);
client->callbacks.server_call(&start_session_request, rc_client_start_session_callback, load_state, client);
@ -1872,7 +1876,7 @@ static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* s
}
/* kick off the start session request while we process the game data */
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_STARTING_SESSION, 1);
if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) {
/* we can't unlock achievements without a session, lock spectator mode for the game */
load_state->client->state.spectator_mode = RC_CLIENT_SPECTATOR_MODE_LOCKED;
@ -2006,18 +2010,30 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
/* only a single hash was tried, capture it */
load_state->game->public_.console_id = load_state->hash_console_id;
load_state->game->public_.hash = load_state->hash->hash;
if (client->callbacks.identify_unknown_hash) {
load_state->hash->game_id = client->callbacks.identify_unknown_hash(
load_state->hash_console_id, load_state->hash->hash, client, load_state->callback_userdata);
if (load_state->hash->game_id != 0) {
RC_CLIENT_LOG_INFO_FORMATTED(load_state->client, "Client says to load game %u for unidentified hash %s",
load_state->hash->game_id, load_state->hash->hash);
}
}
}
load_state->game->public_.title = "Unknown Game";
load_state->game->public_.badge_name = "";
client->game = load_state->game;
load_state->game = NULL;
if (load_state->hash->game_id == 0) {
load_state->game->public_.title = "Unknown Game";
load_state->game->public_.badge_name = "";
client->game = load_state->game;
load_state->game = NULL;
rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game");
return;
rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game");
return;
}
}
if (load_state->hash->hash[0] != '[') {
if (load_state->hash->hash[0] != '[') { /* not [NO HASH] or [SUBSETxx] */
load_state->game->public_.id = load_state->hash->game_id;
load_state->game->public_.hash = load_state->hash->hash;
}
@ -2028,7 +2044,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
rc_mutex_lock(&client->state.mutex);
result = client->state.user;
if (result == RC_CLIENT_USER_STATE_LOGIN_REQUESTED)
load_state->progress = RC_CLIENT_LOAD_STATE_AWAIT_LOGIN;
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_AWAIT_LOGIN;
rc_mutex_unlock(&client->state.mutex);
switch (result) {
@ -2055,7 +2071,7 @@ static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state)
return;
}
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_FETCHING_GAME_DATA, 1);
RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Fetching data for game %u", fetch_game_data_request.game_id);
rc_client_begin_async(client, &load_state->async_handle);
@ -2197,7 +2213,7 @@ static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* loa
return NULL;
}
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME, 1);
rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_GAME_STATE_IDENTIFYING_GAME, 1);
rc_client_begin_async(client, &load_state->async_handle);
client->callbacks.server_call(&request, rc_client_identify_game_callback, load_state, client);
@ -2336,6 +2352,20 @@ rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* cl
return rc_client_load_game(load_state, hash, file_path);
}
int rc_client_get_load_game_state(const rc_client_t* client)
{
int state = RC_CLIENT_LOAD_GAME_STATE_NONE;
if (client) {
const rc_client_load_state_t* load_state = client->state.load;
if (load_state)
state = load_state->progress;
else if (client->game)
state = RC_CLIENT_LOAD_GAME_STATE_DONE;
}
return state;
}
static void rc_client_game_mark_ui_to_be_hidden(rc_client_t* client, rc_client_game_info_t* game)
{
rc_client_achievement_info_t* achievement;
@ -3749,7 +3779,7 @@ int rc_client_has_leaderboards(rc_client_t* client)
return result;
}
static void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard)
void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard)
{
rc_client_leaderboard_tracker_info_t* tracker;
rc_client_leaderboard_tracker_info_t* available_tracker = NULL;
@ -4007,6 +4037,11 @@ static void rc_client_submit_leaderboard_entry(rc_client_t* client, rc_client_le
{
rc_client_submit_leaderboard_entry_callback_data_t* callback_data;
if (!client->state.hardcore) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Leaderboard %u entry submission not allowed in softcore", leaderboard->public_.id);
return;
}
if (client->callbacks.can_submit_leaderboard_entry &&
!client->callbacks.can_submit_leaderboard_entry(leaderboard->public_.id, client)) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Leaderboard %u entry submission blocked by client", leaderboard->public_.id);
@ -4545,6 +4580,11 @@ static void rc_client_do_frame_process_achievements(rc_client_t* client, rc_clie
old_state = trigger->state;
new_state = rc_evaluate_trigger(trigger, client->state.legacy_peek, client, NULL);
/* trigger->state doesn't actually change to RESET - RESET just serves as a notification.
* we don't care about that particular notification, so look at the actual state. */
if (new_state == RC_TRIGGER_STATE_RESET)
new_state = trigger->state;
/* if the measured value changed and the achievement hasn't triggered, show a progress indicator */
if (trigger->measured_value != old_measured_value && old_measured_value != RC_MEASURED_UNKNOWN &&
trigger->measured_value <= trigger->measured_target &&
@ -4940,7 +4980,7 @@ void rc_client_do_frame(rc_client_t* client)
if (client->game->pending_events & RC_CLIENT_GAME_PENDING_EVENT_PROGRESS_TRACKER)
rc_client_do_frame_update_progress_tracker(client, client->game);
if (client->state.hardcore) {
if (client->state.hardcore || client->state.allow_leaderboards_in_softcore) {
for (subset = client->game->subsets; subset; subset = subset->next) {
if (subset->active)
rc_client_do_frame_process_leaderboards(client, subset);
@ -5406,7 +5446,9 @@ static void rc_client_disable_hardcore(rc_client_t* client)
if (client->game) {
rc_client_toggle_hardcore_achievements(client->game, client, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE);
rc_client_deactivate_leaderboards(client->game, client);
if (!client->state.allow_leaderboards_in_softcore)
rc_client_deactivate_leaderboards(client->game, client);
}
}
@ -5592,3 +5634,28 @@ void rc_client_set_host(const rc_client_t* client, const char* hostname)
client->state.external_client->set_host(hostname);
#endif
}
size_t rc_client_get_user_agent_clause(rc_client_t* client, char buffer[], size_t buffer_size)
{
size_t result;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client && client->state.external_client && client->state.external_client->get_user_agent_clause) {
result = client->state.external_client->get_user_agent_clause(buffer, buffer_size);
if (result > 0) {
result += snprintf(buffer + result, buffer_size - result, " rc_client/" RCHEEVOS_VERSION_STRING);
buffer[buffer_size - 1] = '\0';
return result;
}
}
#else
(void)client;
#endif
result = snprintf(buffer, buffer_size, "rcheevos/" RCHEEVOS_VERSION_STRING);
/* some implementations of snprintf will fill the buffer without null terminating.
* make sure the buffer is null terminated */
buffer[buffer_size - 1] = '\0';
return result;
}

View file

@ -73,6 +73,7 @@ typedef struct rc_client_external_t
rc_client_external_set_read_memory_func_t set_read_memory;
rc_client_external_set_get_time_millisecs_func_t set_get_time_millisecs;
rc_client_external_set_string_func_t set_host;
rc_client_external_copy_string_func_t get_user_agent_clause;
rc_client_external_set_int_func_t set_hardcore_enabled;
rc_client_external_get_int_func_t get_hardcore_enabled;

View file

@ -26,6 +26,8 @@ typedef void (RC_CCONV *rc_client_post_process_game_data_response_t)(const rc_ap
typedef int (RC_CCONV *rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client);
typedef int (RC_CCONV *rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client);
typedef int (RC_CCONV *rc_client_rich_presence_override_t)(rc_client_t* client, char buffer[], size_t buffersize);
typedef uint32_t (RC_CCONV* rc_client_identify_hash_func_t)(uint32_t console_id, const char* hash,
rc_client_t* client, void* callback_userdata);
typedef struct rc_client_callbacks_t {
rc_client_read_memory_func_t read_memory;
@ -33,6 +35,7 @@ typedef struct rc_client_callbacks_t {
rc_client_server_call_t server_call;
rc_client_message_callback_t log_call;
rc_get_time_millisecs_func_t get_time_millisecs;
rc_client_identify_hash_func_t identify_unknown_hash;
rc_client_post_process_game_data_response_t post_process_game_data_response;
rc_client_can_submit_achievement_unlock_t can_submit_achievement_unlock;
rc_client_can_submit_leaderboard_entry_t can_submit_leaderboard_entry;
@ -265,16 +268,6 @@ void rc_client_update_active_leaderboards(rc_client_game_info_t* game);
| Client |
\*****************************************************************************/
enum {
RC_CLIENT_LOAD_STATE_NONE,
RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME,
RC_CLIENT_LOAD_STATE_AWAIT_LOGIN,
RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA,
RC_CLIENT_LOAD_STATE_STARTING_SESSION,
RC_CLIENT_LOAD_STATE_DONE,
RC_CLIENT_LOAD_STATE_UNKNOWN_GAME
};
enum {
RC_CLIENT_USER_STATE_NONE,
RC_CLIENT_USER_STATE_LOGIN_REQUESTED,
@ -325,6 +318,7 @@ typedef struct rc_client_state_t {
uint8_t log_level;
uint8_t user;
uint8_t disconnect;
uint8_t allow_leaderboards_in_softcore;
struct rc_client_load_state_t* load;
struct rc_client_async_handle_t* async_handles[4];
@ -386,6 +380,7 @@ enum {
void rc_client_set_legacy_peek(rc_client_t* client, int method);
void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard);
void rc_client_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard);
RC_END_C_DECLS

View file

@ -77,7 +77,9 @@ static void rc_client_raintegration_load_dll(rc_client_t* client,
raintegration->get_menu = (rc_client_raintegration_get_menu_func_t)GetProcAddress(hDLL, "_Rcheevos_RAIntegrationGetMenu");
raintegration->activate_menu_item = (rc_client_raintegration_activate_menuitem_func_t)GetProcAddress(hDLL, "_Rcheevos_ActivateRAIntegrationMenuItem");
raintegration->set_write_memory_function = (rc_client_raintegration_set_write_memory_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationWriteMemoryFunction");
raintegration->set_get_game_name_function = (rc_client_raintegration_set_get_game_name_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationGetGameNameFunction");
raintegration->set_event_handler = (rc_client_raintegration_set_event_handler_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationEventHandler");
raintegration->has_modifications = (rc_client_raintegration_get_int_func_t)GetProcAddress(hDLL, "_Rcheevos_HasModifications");
if (!raintegration->get_version ||
!raintegration->init_client ||
@ -147,7 +149,8 @@ static void rc_client_init_raintegration(rc_client_t* client,
const char* host_url = client->state.raintegration->get_host_url();
if (host_url) {
if (strcmp(host_url, "OFFLINE") != 0) {
rc_client_set_host(client, host_url);
if (strcmp(host_url, "https://retroachievements.org") != 0)
rc_client_set_host(client, host_url);
}
else if (client->state.raintegration->init_client_offline) {
init_func = client->state.raintegration->init_client_offline;
@ -363,6 +366,12 @@ void rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_c
client->state.raintegration->set_write_memory_function(client, handler);
}
void rc_client_raintegration_set_get_game_name_function(rc_client_t* client, rc_client_raintegration_get_game_name_func_t handler)
{
if (client && client->state.raintegration && client->state.raintegration->set_get_game_name_function)
client->state.raintegration->set_get_game_name_function(client, handler);
}
void rc_client_raintegration_set_event_handler(rc_client_t* client,
rc_client_raintegration_event_handler_t handler)
{
@ -382,6 +391,18 @@ const rc_client_raintegration_menu_t* rc_client_raintegration_get_menu(const rc_
return client->state.raintegration->get_menu();
}
int rc_client_raintegration_has_modifications(const rc_client_t* client)
{
if (!client || !client->state.raintegration ||
!client->state.raintegration->bIsInited ||
!client->state.raintegration->has_modifications)
{
return 0;
}
return client->state.raintegration->has_modifications();
}
void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
{
HMENU hPopupMenu = NULL;

View file

@ -20,7 +20,9 @@ typedef void (RC_CCONV* rc_client_raintegration_hwnd_action_func_t)(HWND hWnd);
typedef const rc_client_raintegration_menu_t* (RC_CCONV* rc_client_raintegration_get_menu_func_t)(void);
typedef int (RC_CCONV* rc_client_raintegration_activate_menuitem_func_t)(uint32_t nMenuItemId);
typedef void (RC_CCONV* rc_client_raintegration_set_write_memory_func_t)(rc_client_t* pClient, rc_client_raintegration_write_memory_func_t handler);
typedef void (RC_CCONV* rc_client_raintegration_set_get_game_name_func_t)(rc_client_t* pClient, rc_client_raintegration_get_game_name_func_t handler);
typedef void (RC_CCONV* rc_client_raintegration_set_event_handler_func_t)(rc_client_t* pClient, rc_client_raintegration_event_handler_t handler);
typedef int (RC_CCONV* rc_client_raintegration_get_int_func_t)(void);
typedef struct rc_client_raintegration_t
{
@ -37,9 +39,11 @@ typedef struct rc_client_raintegration_t
rc_client_raintegration_hwnd_action_func_t update_main_window_handle;
rc_client_raintegration_set_write_memory_func_t set_write_memory_function;
rc_client_raintegration_set_get_game_name_func_t set_get_game_name_function;
rc_client_raintegration_set_event_handler_func_t set_event_handler;
rc_client_raintegration_get_menu_func_t get_menu;
rc_client_raintegration_activate_menuitem_func_t activate_menu_item;
rc_client_raintegration_get_int_func_t has_modifications;
rc_client_raintegration_get_external_client_func_t get_external_client;

View file

@ -85,7 +85,8 @@ struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer)
#endif
#ifndef RC_NO_THREADS
#ifdef _WIN32
#if defined(_WIN32)
/* https://gist.github.com/roxlu/1c1af99f92bafff9d8d9 */
@ -113,6 +114,30 @@ void rc_mutex_unlock(rc_mutex_t* mutex)
ReleaseMutex(mutex->handle);
}
#elif defined(GEKKO)
/* https://github.com/libretro/RetroArch/pull/16116 */
void rc_mutex_init(rc_mutex_t* mutex)
{
LWP_MutexInit(mutex, NULL);
}
void rc_mutex_destroy(rc_mutex_t* mutex)
{
LWP_MutexDestroy(mutex);
}
void rc_mutex_lock(rc_mutex_t* mutex)
{
LWP_MutexLock(mutex);
}
void rc_mutex_unlock(rc_mutex_t* mutex)
{
LWP_MutexUnlock(mutex);
}
#else
void rc_mutex_init(rc_mutex_t* mutex)

View file

@ -51,6 +51,11 @@ static const rc_disallowed_setting_t _rc_disallowed_dolphin_settings[] = {
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_dosbox_pure_settings[] = {
{ "dosbox_pure_strict_mode", "false" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_duckstation_settings[] = {
{ "duckstation_CDROM.LoadImagePatches", "true" },
{ NULL, NULL }
@ -149,6 +154,7 @@ static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
{ "bsnes-mercury", _rc_disallowed_bsnes_settings },
{ "cap32", _rc_disallowed_cap32_settings },
{ "dolphin-emu", _rc_disallowed_dolphin_settings },
{ "DOSBox-pure", _rc_disallowed_dosbox_pure_settings },
{ "DuckStation", _rc_disallowed_duckstation_settings },
{ "ecwolf", _rc_disallowed_ecwolf_settings },
{ "FCEUmm", _rc_disallowed_fceumm_settings },
@ -324,27 +330,38 @@ uint8_t* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, ui
uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address,
uint8_t* buffer, uint32_t num_bytes) {
uint32_t i;
uint32_t bytes_read = 0;
uint32_t avail;
uint32_t i;
for (i = 0; i < regions->count; ++i) {
const size_t size = regions->size[i];
if (address < size) {
if (regions->data[i] == NULL)
break;
avail = (unsigned)(size - address);
if (avail < num_bytes)
return avail;
memcpy(buffer, &regions->data[i][address], num_bytes);
return num_bytes;
if (address >= size) {
/* address is not in this block, adjust and look at next block */
address -= (unsigned)size;
continue;
}
address -= (unsigned)size;
if (regions->data[i] == NULL) /* no memory associated to this block. abort */
break;
avail = (unsigned)(size - address);
if (avail >= num_bytes) {
/* requested memory is fully within this block, copy and return it */
memcpy(buffer, &regions->data[i][address], num_bytes);
bytes_read += num_bytes;
return bytes_read;
}
/* copy whatever is available in this block, and adjust for the next block */
memcpy(buffer, &regions->data[i][address], avail);
buffer += avail;
bytes_read += avail;
num_bytes -= avail;
address = 0;
}
return 0;
return bytes_read;
}
void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback) {
@ -671,8 +688,7 @@ void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
return;
file_handle = rc_file_open(m3u_path);
if (!file_handle)
{
if (!file_handle) {
rc_hash_error("Could not open playlist");
return;
}
@ -682,8 +698,7 @@ void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
rc_file_seek(file_handle, 0, SEEK_SET);
m3u_contents = (char*)malloc((size_t)file_len + 1);
if (m3u_contents)
{
if (m3u_contents) {
rc_file_read(file_handle, m3u_contents, (int)file_len);
m3u_contents[file_len] = '\0';
@ -696,23 +711,19 @@ void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
while (isspace((int)*ptr))
++ptr;
if (*ptr == '#')
{
if (*ptr == '#') {
/* ignore comment unless it's the special SAVEDISK extension */
if (memcmp(ptr, "#SAVEDISK:", 10) == 0)
{
if (memcmp(ptr, "#SAVEDISK:", 10) == 0) {
/* get the path to the save disk from the frontend, assign it a bogus hash so
* it doesn't get hashed later */
if (get_image_path(index, image_path, sizeof(image_path)))
{
if (get_image_path(index, image_path, sizeof(image_path))) {
const char save_disk_hash[33] = "[SAVE DISK]";
rc_libretro_hash_set_add(hash_set, image_path, -1, save_disk_hash);
++index;
}
}
}
else
{
else {
/* non-empty line, tally a file */
++index;
}
@ -726,8 +737,7 @@ void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
free(m3u_contents);
}
if (hash_set->entries_count > 0)
{
if (hash_set->entries_count > 0) {
/* at least one save disk was found. make sure the core supports the #SAVEDISK: extension by
* asking for the last expected disk. if it's not found, assume no #SAVEDISK: support */
if (!get_image_path(index - 1, image_path, sizeof(image_path)))
@ -759,13 +769,10 @@ void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
struct rc_libretro_hash_entry_t* scan;
struct rc_libretro_hash_entry_t* stop = hash_set->entries + hash_set->entries_count;;
if (path_djb2)
{
if (path_djb2) {
/* attempt to match the path */
for (scan = hash_set->entries; scan < stop; ++scan)
{
if (scan->path_djb2 == path_djb2)
{
for (scan = hash_set->entries; scan < stop; ++scan) {
if (scan->path_djb2 == path_djb2) {
entry = scan;
break;
}
@ -775,19 +782,20 @@ void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
if (!entry)
{
/* entry not found, allocate a new one */
if (hash_set->entries_size == 0)
{
if (hash_set->entries_size == 0) {
hash_set->entries_size = 4;
hash_set->entries = (struct rc_libretro_hash_entry_t*)
malloc(hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
}
else if (hash_set->entries_count == hash_set->entries_size)
{
else if (hash_set->entries_count == hash_set->entries_size) {
hash_set->entries_size += 4;
hash_set->entries = (struct rc_libretro_hash_entry_t*)realloc(hash_set->entries,
hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
}
if (hash_set->entries == NULL) /* unexpected, but better than crashing */
return;
entry = hash_set->entries + hash_set->entries_count++;
}
@ -802,8 +810,7 @@ const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* h
const uint32_t path_djb2 = rc_libretro_djb2(path);
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
for (; scan < stop; ++scan)
{
for (; scan < stop; ++scan) {
if (scan->path_djb2 == path_djb2)
return scan->hash;
}
@ -815,8 +822,7 @@ int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_s
{
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
for (; scan < stop; ++scan)
{
for (; scan < stop; ++scan) {
if (memcmp(scan->hash, hash, sizeof(scan->hash)) == 0)
return scan->game_id;
}

View file

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

View file

@ -567,6 +567,29 @@ static const rc_memory_region_t _rc_memory_regions_msx[] = {
};
static const rc_memory_regions_t rc_memory_regions_msx = { _rc_memory_regions_msx, 1 };
/* ===== MS DOS ===== */
static const rc_memory_region_t _rc_memory_regions_ms_dos[] = {
/* DOS emulators split the 640 KB conventional memory into two regions.
* First the part of the conventional memory given to the running game at $000000.
* The part of the conventional memory containing DOS and BIOS controlled memory
* is at $100000. The length of these can vary depending on the hardware
* and DOS version (or emulated DOS shell).
* These first two regions will only ever total to 640 KB but the regions map
* to 1 MB bounds to make resulting memory addresses more readable.
* When emulating a game not under DOS (so called 'PC Booter' games), the entirety
* of the 640 KB conventional memory block will be at $000000.
*/
{ 0x00000000U, 0x0009FFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Game Conventional Memory" },
{ 0x000A0000U, 0x000FFFFFU, 0x000A0000U, RC_MEMORY_TYPE_UNUSED, "Padding to align OS Conventional Memory" },
{ 0x00100000U, 0x0019FFFFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "OS Conventional Memory" },
{ 0x001A0000U, 0x001FFFFFU, 0x001A0000U, RC_MEMORY_TYPE_UNUSED, "Padding to align Expanded Memory" },
/* Last is all the expanded memory which for now we map up to 64 MB which should be
* enough for the games we want to cover. An emulator might emulate more than that.
*/
{ 0x00200000U, 0x041FFFFFU, 0x00200000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Expanded Memory" }
};
static const rc_memory_regions_t rc_memory_regions_ms_dos = { _rc_memory_regions_ms_dos, 5 };
/* ===== Neo Geo Pocket ===== */
/* http://neopocott.emuunlim.com/docs/tech-11.txt */
static const rc_memory_region_t _rc_memory_regions_neo_geo_pocket[] = {
@ -972,6 +995,9 @@ const rc_memory_regions_t* rc_console_memory_regions(uint32_t console_id)
case RC_CONSOLE_MSX:
return &rc_memory_regions_msx;
case RC_CONSOLE_MS_DOS:
return &rc_memory_regions_ms_dos;
case RC_CONSOLE_NEOGEO_POCKET:
return &rc_memory_regions_neo_geo_pocket;

View file

@ -2,7 +2,7 @@
#define RC_INTERNAL_H
#include "rc_runtime_types.h"
#include "../rc_util.h"
#include "rc_util.h"
RC_BEGIN_C_DECLS

View file

@ -1,7 +1,7 @@
#include "rc_runtime.h"
#include "rc_internal.h"
#include "../rc_util.h"
#include "rc_util.h"
#include "../rhash/md5.h"
#include <stdlib.h>

View file

@ -16,6 +16,7 @@
#define MAX_BUFFER_SIZE 64 * 1024 * 1024
const char* rc_path_get_filename(const char* path);
static int rc_hash_whole_file(char hash[33], const char* path);
/* ===================================================== */
@ -511,8 +512,8 @@ static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector
verbose_message_callback(message);
}
if (size < (unsigned)num_read)
size = (unsigned)num_read;
if (size < (unsigned)num_read) /* we read a whole sector - only hash the part containing file data */
num_read = (size_t)size;
do
{
@ -679,6 +680,293 @@ static int rc_hash_7800(char hash[33], const uint8_t* buffer, size_t buffer_size
return rc_hash_buffer(hash, buffer, buffer_size);
}
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);
}
static int rc_hash_zip_file(md5_state_t* md5, void* file_handle)
{
uint8_t buf[2048], *alloc_buf, *cdir_start, *cdir_max, *cdir, *hashdata, eocdirhdr_size, cdirhdr_size;
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(file_handle, 0, SEEK_END);
archive_size = rc_file_tell(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_error("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(file_handle, ecdh_ofs, SEEK_SET);
if (rc_file_read(file_handle, buf, n) != (size_t)n)
return rc_hash_error("ZIP read error");
for (i = n - 4; i >= 0; --i)
if (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_error("Failed to find ZIP central directory");
}
/* Read and verify the end of central directory record. */
rc_file_seek(file_handle, ecdh_ofs, SEEK_SET);
if (rc_file_read(file_handle, buf, eocdirhdr_size) != eocdirhdr_size)
return rc_hash_error("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(file_handle, ecdh_ofs - 20, SEEK_SET);
if (rc_file_read(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(file_handle, ecdh64_ofs, SEEK_SET);
if (rc_file_read(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_error("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_error("Could not allocate temporary buffer");
rc_file_seek(file_handle, cdir_ofs, SEEK_SET);
if ((int64_t)rc_file_read(file_handle, alloc_buf, (int)cdir_size) != cdir_size)
{
free(alloc_buf);
return rc_hash_error("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 = 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);
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;
/* 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_error("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_error("Encountered invalid entry in ZIP central directory");
}
/* Write the pointer and length of the data we record about this file */
hashindex->data = hashdata;
hashindex->length = filename_len + 1 + 4 + 8;
hashindex++;
/* Convert and store the file name in the hash data buffer */
for (name = (cdir + cdirhdr_size), name_end = name + filename_len; name != name_end; name++)
{
*(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;
if (verbose_message_callback)
{
char message[1024];
snprintf(message, sizeof(message), "File in ZIP: %.*s (%u bytes, CRC32 = %08X)", filename_len, (const char*)(cdir + cdirhdr_size), (unsigned)decomp_size, crc32);
verbose_message_callback(message);
}
}
if (verbose_message_callback)
{
char message[1024];
snprintf(message, sizeof(message), "Hashing %u files in ZIP archive", (unsigned)(hashindex - hashindices));
verbose_message_callback(message);
}
/* 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_ms_dos(char hash[33], const char* path)
{
md5_state_t md5;
size_t path_len;
int res;
void* file_handle = rc_file_open(path);
if (!file_handle)
return rc_hash_error("Could not open file");
/* hash the main content zip file first */
md5_init(&md5);
res = rc_hash_zip_file(&md5, file_handle);
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);
}
static int rc_hash_arcade(char hash[33], const char* path)
{
/* arcade hash is just the hash of the filename (no extension) - the cores are pretty stringent about having the right ROM data */
@ -2083,6 +2371,7 @@ int rc_hash_generate_from_buffer(char hash[33], uint32_t console_id, const uint8
return rc_hash_snes(hash, buffer, buffer_size);
case RC_CONSOLE_NINTENDO_64:
case RC_CONSOLE_NINTENDO_3DS:
case RC_CONSOLE_NINTENDO_DS:
case RC_CONSOLE_NINTENDO_DSI:
return rc_hash_file_from_buffer(hash, console_id, buffer, buffer_size);
@ -2401,6 +2690,9 @@ int rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* p
case RC_CONSOLE_GAMECUBE:
return rc_hash_gamecube(hash, path);
case RC_CONSOLE_MS_DOS:
return rc_hash_ms_dos(hash, path);
case RC_CONSOLE_NEO_GEO_CD:
return rc_hash_neogeo_cd(hash, path);
@ -2539,6 +2831,14 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
}
break;
case '3':
if (rc_path_compare_extension(ext, "3ds") ||
rc_path_compare_extension(ext, "3dsx"))
{
iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS;
}
break;
case '7':
if (rc_path_compare_extension(ext, "7z"))
{
@ -2562,6 +2862,11 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{
iterator->consoles[0] = RC_CONSOLE_ATARI_7800;
}
else if (rc_path_compare_extension(ext, "app") ||
rc_path_compare_extension(ext, "axf"))
{
iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS;
}
break;
case 'b':
@ -2647,6 +2952,12 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
{
iterator->consoles[0] = RC_CONSOLE_SUPER_CASSETTEVISION;
}
else if (rc_path_compare_extension(ext, "cci") ||
rc_path_compare_extension(ext, "cia") ||
rc_path_compare_extension(ext, "cxi"))
{
iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS;
}
break;
case 'd':
@ -2663,6 +2974,19 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
iterator->consoles[0] = RC_CONSOLE_PC8800;
iterator->consoles[1] = RC_CONSOLE_SHARPX1;
}
else if (rc_path_compare_extension(ext, "dosz"))
{
iterator->consoles[0] = RC_CONSOLE_MS_DOS;
}
break;
case 'e':
if (rc_path_compare_extension(ext, "elf"))
{
/* This should probably apply to more consoles in the future */
/* Although in any case this just hashes the entire file */
iterator->consoles[0] = RC_CONSOLE_NINTENDO_3DS;
}
break;
case 'f':