dep/rcheevos: Update to 3d01191
This commit is contained in:
parent
272c43d139
commit
94657ae4ab
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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, ®ions->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, ®ions->data[i][address], num_bytes);
|
||||
bytes_read += num_bytes;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
/* copy whatever is available in this block, and adjust for the next block */
|
||||
memcpy(buffer, ®ions->data[i][address], avail);
|
||||
buffer += avail;
|
||||
bytes_read += avail;
|
||||
num_bytes -= avail;
|
||||
address = 0;
|
||||
}
|
||||
|
||||
return 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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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':
|
||||
|
|
Loading…
Reference in New Issue