Merge pull request #12700 from Jamiras/rapi_client

(cheevos) move client code into separate file
This commit is contained in:
Autechre 2021-07-30 13:11:08 +02:00 committed by GitHub
commit 742e195172
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 796 additions and 523 deletions

View File

@ -2119,6 +2119,7 @@ ifeq ($(HAVE_NETWORKING), 1)
INCLUDE_DIRS += -Ideps/rcheevos/include
OBJ += cheevos/cheevos.o \
cheevos/cheevos_client.o \
cheevos/cheevos_menu.o \
cheevos/cheevos_parser.o \
$(LIBRETRO_COMM_DIR)/formats/cdfs/cdfs.o \
@ -2138,6 +2139,9 @@ ifeq ($(HAVE_NETWORKING), 1)
deps/rcheevos/src/rcheevos/trigger.o \
deps/rcheevos/src/rcheevos/value.o \
deps/rcheevos/src/rhash/hash.o \
deps/rcheevos/src/rapi/rc_api_common.o \
deps/rcheevos/src/rapi/rc_api_runtime.o \
deps/rcheevos/src/rapi/rc_api_user.o \
deps/rcheevos/src/rurl/url.o
ifeq ($(HAVE_LUA), 1)

View File

@ -41,10 +41,6 @@
#include <rthreads/rthreads.h>
#endif
#ifdef HAVE_DISCORD
#include "../network/discord.h"
#endif
#ifdef HAVE_CHEATS
#include "../cheat_manager.h"
#endif
@ -54,23 +50,20 @@
#endif
#include "cheevos.h"
#include "cheevos_client.h"
#include "cheevos_locals.h"
#include "cheevos_parser.h"
#include "../file_path_special.h"
#include "../paths.h"
#include "../command.h"
#include "../dynamic.h"
#include "../configuration.h"
#include "../performance_counters.h"
#include "../msg_hash.h"
#include "../retroarch.h"
#include "../core.h"
#include "../core_option_manager.h"
#include "../version.h"
#include "../frontend/frontend_driver.h"
#include "../network/net_http_special.h"
#include "../tasks/tasks_internal.h"
#include "../deps/rcheevos/include/rc_runtime.h"
@ -89,42 +82,12 @@
* that name. */
#undef CHEEVOS_SAVE_JSON
/* Define this macro to log URLs. */
#undef CHEEVOS_LOG_URLS
/* Define this macro to have the password and token logged. THIS WILL DISCLOSE
* THE USER'S PASSWORD, TAKE CARE! */
#undef CHEEVOS_LOG_PASSWORD
/* Define this macro to log downloaded badge images. */
#undef CHEEVOS_LOG_BADGES
/* Define this macro to capture how long it takes to generate a hash */
#undef CHEEVOS_TIME_HASH
/* Number of usecs to wait between posting rich presence to the site. */
/* Keep consistent with SERVER_PING_FREQUENCY from RAIntegration. */
#define CHEEVOS_PING_FREQUENCY 2 * 60 * 1000000
enum rcheevos_async_io_type
{
CHEEVOS_ASYNC_RICHPRESENCE = 0,
CHEEVOS_ASYNC_AWARD_ACHIEVEMENT,
CHEEVOS_ASYNC_SUBMIT_LBOARD
};
typedef struct rcheevos_async_io_request
{
const char* success_message;
const char* failure_message;
int id;
int value;
int attempt_count;
char user_agent[256];
char type;
char hardcore;
} rcheevos_async_io_request;
static rcheevos_locals_t rcheevos_locals =
{
{0}, /* runtime */
@ -135,9 +98,11 @@ static rcheevos_locals_t rcheevos_locals =
NULL, /* task_lock */
CMD_EVENT_NONE, /* queued_command */
#endif
{0}, /* token */
"", /* username */
"", /* token */
"N/A",/* hash */
"", /* user_agent_prefix */
"", /* user_agent_core */
#ifdef HAVE_MENU
NULL, /* menuitems */
0, /* menuitem_capacity */
@ -167,10 +132,6 @@ rcheevos_locals_t* get_rcheevos_locals(void)
#define CHEEVOS_MB(x) ((x) * 1024 * 1024)
/* Forward declaration */
static void rcheevos_async_task_callback(
retro_task_t* task, void* task_data, void* user_data, const char* error);
static void rcheevos_async_submit_lboard(rcheevos_locals_t *locals,
rcheevos_async_io_request* request);
static void rcheevos_validate_memrefs(rcheevos_locals_t* locals);
/*****************************************************************************
@ -184,173 +145,6 @@ void rcheevos_log(const char *fmt, ...)
}
#endif
static int append_no_spaces(char* buffer, char* stop, const char* text)
{
char* ptr = buffer;
while (ptr < stop && *text)
{
if (*text == ' ')
{
*ptr++ = '_';
++text;
}
else
{
*ptr++ = *text++;
}
}
*ptr = '\0';
return (ptr - buffer);
}
static void rcheevos_get_user_agent(
rcheevos_locals_t *locals,
char *buffer, size_t len)
{
struct retro_system_info *system = runloop_get_libretro_system_info();
char* ptr;
/* if we haven't calculated the non-changing portion yet, do so now [retroarch version + os version] */
if (!locals->user_agent_prefix[0])
{
const frontend_ctx_driver_t *frontend = frontend_get_ptr();
int major, minor;
char tmp[64];
if (frontend && frontend->get_os)
{
frontend->get_os(tmp, sizeof(tmp), &major, &minor);
snprintf(locals->user_agent_prefix, sizeof(locals->user_agent_prefix),
"RetroArch/%s (%s %d.%d)", PACKAGE_VERSION, tmp, major, minor);
}
else
{
snprintf(locals->user_agent_prefix, sizeof(locals->user_agent_prefix),
"RetroArch/%s", PACKAGE_VERSION);
}
}
/* append the non-changing portion */
ptr = buffer + strlcpy(buffer, locals->user_agent_prefix, len);
/* if a core is loaded, append its information */
if (system && !string_is_empty(system->library_name))
{
char* stop = buffer + len - 1;
const char* path = path_get(RARCH_PATH_CORE);
*ptr++ = ' ';
if (!string_is_empty(path))
{
append_no_spaces(ptr, stop, path_basename(path));
path_remove_extension(ptr);
ptr += strlen(ptr);
}
else
{
ptr += append_no_spaces(ptr, stop, system->library_name);
}
if (system->library_version)
{
*ptr++ = '/';
ptr += append_no_spaces(ptr, stop, system->library_version);
}
}
*ptr = '\0';
}
#ifdef CHEEVOS_LOG_URLS
static void rcheevos_filter_url_param(char* url, char* param)
{
char *next;
size_t param_len = strlen(param);
char *start = strchr(url, '?');
if (!start)
start = url;
else
++start;
do
{
next = strchr(start, '&');
if (start[param_len] == '=' && memcmp(start, param, param_len) == 0)
{
if (next)
strcpy_literal(start, next + 1);
else if (start > url)
start[-1] = '\0';
else
*start = '\0';
return;
}
if (!next)
return;
start = next + 1;
} while (1);
}
#endif
static void rcheevos_log_url(const char* api, const char* url)
{
#ifdef CHEEVOS_LOG_URLS
#ifdef CHEEVOS_LOG_PASSWORD
CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url);
#else
char copy[256];
strlcpy(copy, url, sizeof(copy));
rcheevos_filter_url_param(copy, "p");
rcheevos_filter_url_param(copy, "t");
CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, copy);
#endif
#else
(void)api;
(void)url;
#endif
}
static void rcheevos_log_post_url(
const char* api,
const char* url,
const char* post)
{
#ifdef CHEEVOS_LOG_URLS
#ifdef CHEEVOS_LOG_PASSWORD
if (post && post[0])
CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s&%s\n", api, url, post);
else
CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url);
#else
if (post && post[0])
{
char post_copy[2048];
strlcpy(post_copy, post, sizeof(post_copy));
rcheevos_filter_url_param(post_copy, "p");
rcheevos_filter_url_param(post_copy, "t");
if (post_copy[0])
CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s&%s\n", api, url, post_copy);
else
CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url);
}
else
{
CHEEVOS_LOG(RCHEEVOS_TAG "%s: %s\n", api, url);
}
#endif
#else
(void)api;
(void)url;
(void)post;
#endif
}
static void rcheevos_achievement_disabled(rcheevos_racheevo_t* cheevo, unsigned address)
{
@ -453,208 +247,6 @@ static unsigned rcheevos_peek(unsigned address, unsigned num_bytes, void* ud)
return 0;
}
static void rcheevos_async_award_achievement(
rcheevos_locals_t *locals,
rcheevos_async_io_request* request)
{
char buffer[256];
settings_t *settings = config_get_ptr();
int ret = rc_url_award_cheevo(buffer, sizeof(buffer),
settings->arrays.cheevos_username,
locals->token,
request->id,
request->hardcore,
locals->hash);
if (ret != 0)
{
CHEEVOS_ERR(RCHEEVOS_TAG "Buffer too small to create URL\n");
free(request);
return;
}
rcheevos_log_url("rc_url_award_cheevo", buffer);
task_push_http_transfer_with_user_agent(buffer, true, NULL,
request->user_agent, rcheevos_async_task_callback, request);
#ifdef HAVE_AUDIOMIXER
if (settings->bools.cheevos_unlock_sound_enable)
audio_driver_mixer_play_menu_sound(
AUDIO_MIXER_SYSTEM_SLOT_ACHIEVEMENT_UNLOCK);
#endif
}
static retro_time_t rcheevos_async_send_rich_presence(
rcheevos_locals_t *locals,
rcheevos_async_io_request* request)
{
char url[256], post_data[1024];
char buffer[256] = "";
const settings_t *settings = config_get_ptr();
const char *cheevos_username = settings->arrays.cheevos_username;
const bool cheevos_richpresence_enable = settings->bools.cheevos_richpresence_enable;
int ret;
if (cheevos_richpresence_enable)
rcheevos_get_richpresence(buffer, sizeof(buffer));
ret = rc_url_ping(url, sizeof(url), post_data, sizeof(post_data),
cheevos_username, locals->token, locals->patchdata.game_id, buffer);
if (ret < 0)
{
CHEEVOS_ERR(RCHEEVOS_TAG "buffer too small to create URL\n");
}
else
{
rcheevos_log_post_url("rc_url_ping", url, post_data);
rcheevos_get_user_agent(locals,
request->user_agent, sizeof(request->user_agent));
task_push_http_post_transfer_with_user_agent(url, post_data, true, "POST", request->user_agent, NULL, NULL);
}
#ifdef HAVE_DISCORD
if (settings->bools.discord_enable && discord_is_ready())
discord_update(DISCORD_PRESENCE_RETROACHIEVEMENTS);
#endif
/* Update rich presence every two minutes */
if (cheevos_richpresence_enable)
return cpu_features_get_time_usec() + CHEEVOS_PING_FREQUENCY;
/* Send ping every four minutes */
return cpu_features_get_time_usec() + CHEEVOS_PING_FREQUENCY * 2;
}
static void rcheevos_async_task_handler(retro_task_t* task)
{
rcheevos_async_io_request* request = (rcheevos_async_io_request*)
task->user_data;
switch (request->type)
{
case CHEEVOS_ASYNC_RICHPRESENCE:
/* update the task to fire again in two minutes */
if (request->id == (int)rcheevos_locals.patchdata.game_id)
task->when = rcheevos_async_send_rich_presence(&rcheevos_locals,
request);
else
{
/* game changed; stop the recurring task - a new one will
* be scheduled for the next game */
task_set_finished(task, 1);
free(request);
}
break;
case CHEEVOS_ASYNC_AWARD_ACHIEVEMENT:
rcheevos_async_award_achievement(&rcheevos_locals, request);
task_set_finished(task, 1);
break;
case CHEEVOS_ASYNC_SUBMIT_LBOARD:
rcheevos_async_submit_lboard(&rcheevos_locals, request);
task_set_finished(task, 1);
break;
}
}
static void rcheevos_async_schedule(
rcheevos_async_io_request* request, retro_time_t delay)
{
retro_task_t* task = task_init();
task->when = cpu_features_get_time_usec() + delay;
task->handler = rcheevos_async_task_handler;
task->user_data = request;
task->progress = -1;
task_queue_push(task);
}
static void rcheevos_async_task_callback(
retro_task_t* task, void* task_data, void* user_data, const char* error)
{
rcheevos_async_io_request *request = (rcheevos_async_io_request*)user_data;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
if (!error)
{
char buffer[224] = "";
/* Server did not return HTTP headers */
if (!data)
snprintf(buffer, sizeof(buffer), "Server communication error");
else if (data->status != 200)
{
/* Server returned an error via status code.
* Check to see if it also returned a JSON error */
if (!data->data || rcheevos_get_json_error(data->data, buffer, sizeof(buffer)) != RC_OK)
snprintf(buffer, sizeof(buffer), "HTTP error code: %d",
data->status);
}
else if (!data->data || !data->len)
{
/* Server sent an empty response without an error status code */
snprintf(buffer, sizeof(buffer), "No response from server");
}
else
{
/* Server sent a message - assume it's JSON
* and check for a JSON error */
rcheevos_get_json_error(data->data, buffer, sizeof(buffer));
}
if (buffer[0])
{
char errbuf[256];
snprintf(errbuf, sizeof(errbuf), "%s %u: %s",
request->failure_message, request->id, buffer);
CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", errbuf);
switch (request->type)
{
case CHEEVOS_ASYNC_RICHPRESENCE:
/* Don't bother informing user when
* rich presence update fails */
break;
case CHEEVOS_ASYNC_AWARD_ACHIEVEMENT:
/* ignore already unlocked */
if (string_starts_with_size(buffer, "User already has ",
STRLEN_CONST("User already has ")))
break;
/* fallthrough to default */
default:
runloop_msg_queue_push(errbuf, 0, 5 * 60, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
break;
}
}
else
{
CHEEVOS_LOG(RCHEEVOS_TAG "%s %u\n", request->success_message, request->id);
}
free(request);
}
else
{
/* Double the wait between each attempt until we hit
* a maximum delay of two minutes.
* 250ms -> 500ms -> 1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s -> 120s -> 120s... */
retro_time_t retry_delay =
(request->attempt_count > 8)
? (120 * 1000 * 1000)
: ((250 * 1000) << request->attempt_count);
request->attempt_count++;
rcheevos_async_schedule(request, retry_delay);
CHEEVOS_ERR(RCHEEVOS_TAG "%s %u: %s\n", request->failure_message,
request->id, error);
}
}
static void rcheevos_activate_achievements(rcheevos_locals_t *locals,
rcheevos_racheevo_t* cheevo, unsigned count, unsigned flags)
{
@ -815,14 +407,7 @@ static int rcheevos_parse(rcheevos_locals_t *locals, const char* json)
}
}
/* schedule the first rich presence call in 30 seconds */
{
rcheevos_async_io_request* request = (rcheevos_async_io_request*)
calloc(1, sizeof(rcheevos_async_io_request));
request->id = locals->patchdata.game_id;
request->type = CHEEVOS_ASYNC_RICHPRESENCE;
rcheevos_async_schedule(request, CHEEVOS_PING_FREQUENCY / 4);
}
rcheevos_client_start_session(locals->patchdata.game_id);
/* validate the memrefs */
if (rcheevos_locals.memory.count != 0)
@ -858,10 +443,10 @@ static rcheevos_racheevo_t* rcheevos_find_cheevo(unsigned id)
return NULL;
}
static void rcheevos_award_achievement(rcheevos_locals_t *locals,
void rcheevos_award_achievement(rcheevos_locals_t* locals,
rcheevos_racheevo_t* cheevo, bool widgets_ready)
{
char buffer[256] = "";
const settings_t *settings = config_get_ptr();
if (!cheevo)
return;
@ -869,7 +454,7 @@ static void rcheevos_award_achievement(rcheevos_locals_t *locals,
CHEEVOS_LOG(RCHEEVOS_TAG "Awarding achievement %u: %s (%s)\n",
cheevo->id, cheevo->title, cheevo->description);
/* Deactivates the cheevo. */
/* Deactivates the acheivement. */
rc_runtime_deactivate_achievement(&locals->runtime, cheevo->id);
cheevo->active &= ~RCHEEVOS_ACTIVE_SOFTCORE;
@ -878,49 +463,44 @@ static void rcheevos_award_achievement(rcheevos_locals_t *locals,
cheevo->unlock_time = cpu_features_get_time_usec();
/* Show the OSD message. */
{
/* Show the on screen message. */
#if defined(HAVE_GFX_WIDGETS)
if (widgets_ready)
gfx_widgets_push_achievement(cheevo->title, cheevo->badge);
else
if (widgets_ready)
{
gfx_widgets_push_achievement(cheevo->title, cheevo->badge);
}
else
#endif
{
snprintf(buffer, sizeof(buffer),
"Achievement Unlocked: %s", cheevo->title);
runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
runloop_msg_queue_push(cheevo->description, 0, 3 * 60, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
{
char buffer[256];
snprintf(buffer, sizeof(buffer), "%s: %s",
msg_hash_to_str(MSG_ACHIEVEMENT_UNLOCKED), cheevo->title);
runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
runloop_msg_queue_push(cheevo->description, 0, 3 * 60, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
/* Start the award task (unofficial achievement unlocks are not submitted). */
if (!(cheevo->active & RCHEEVOS_ACTIVE_UNOFFICIAL))
{
rcheevos_async_io_request *request = (rcheevos_async_io_request*)calloc(1, sizeof(rcheevos_async_io_request));
request->type = CHEEVOS_ASYNC_AWARD_ACHIEVEMENT;
request->id = cheevo->id;
request->hardcore = locals->hardcore_active ? 1 : 0;
request->success_message = "Awarded achievement";
request->failure_message = "Error awarding achievement";
rcheevos_get_user_agent(locals,
request->user_agent, sizeof(request->user_agent));
rcheevos_async_award_achievement(locals, request);
}
rcheevos_client_award_achievement(cheevo->id);
/* play the unlock sound */
#ifdef HAVE_AUDIOMIXER
if (settings->bools.cheevos_unlock_sound_enable)
audio_driver_mixer_play_menu_sound(
AUDIO_MIXER_SYSTEM_SLOT_ACHIEVEMENT_UNLOCK);
#endif
/* Take a screenshot of the achievement. */
#ifdef HAVE_SCREENSHOTS
if (settings->bools.cheevos_auto_screenshot)
{
settings_t *settings = config_get_ptr();
/* Take a screenshot of the achievement. */
if (settings && settings->bools.cheevos_auto_screenshot)
size_t shotname_len = sizeof(char) * 8192;
char *shotname = (char*)malloc(shotname_len);
if (shotname)
{
size_t shotname_len = sizeof(char) * 8192;
char *shotname = (char*)malloc(shotname_len);
if (!shotname)
return;
snprintf(shotname, shotname_len, "%s/%s-cheevo-%u",
settings->paths.directory_screenshot,
path_basename(path_get(RARCH_PATH_BASENAME)),
@ -931,40 +511,18 @@ static void rcheevos_award_achievement(rcheevos_locals_t *locals,
shotname, true,
video_driver_cached_frame_has_valid_framebuffer(),
false, true))
CHEEVOS_LOG(
RCHEEVOS_TAG "Captured screenshot for achievement %u\n",
CHEEVOS_LOG(RCHEEVOS_TAG "Captured screenshot for achievement %u\n",
cheevo->id);
else
CHEEVOS_LOG(
RCHEEVOS_TAG "Failed to capture screenshot for achievement %u\n",
CHEEVOS_LOG(RCHEEVOS_TAG "Failed to capture screenshot for achievement %u\n",
cheevo->id);
free(shotname);
}
}
#endif
}
static void rcheevos_async_submit_lboard(rcheevos_locals_t *locals,
rcheevos_async_io_request* request)
{
char buffer[256];
settings_t *settings = config_get_ptr();
int ret = rc_url_submit_lboard(buffer, sizeof(buffer),
settings->arrays.cheevos_username,
locals->token, request->id, request->value);
if (ret != 0)
{
CHEEVOS_ERR(RCHEEVOS_TAG "Buffer too small to create URL\n");
free(request);
return;
}
rcheevos_log_url("rc_url_submit_lboard", buffer);
task_push_http_transfer_with_user_agent(buffer, true, NULL,
request->user_agent, rcheevos_async_task_callback, request);
}
static rcheevos_ralboard_t* rcheevos_find_lboard(unsigned id)
{
rcheevos_ralboard_t* lboard = rcheevos_locals.patchdata.lboards;
@ -979,44 +537,31 @@ static rcheevos_ralboard_t* rcheevos_find_lboard(unsigned id)
return NULL;
}
static void rcheevos_lboard_submit(rcheevos_locals_t *locals,
static void rcheevos_lboard_submit(rcheevos_locals_t* locals,
rcheevos_ralboard_t* lboard, int value, bool widgets_ready)
{
char buffer[256];
char formatted_value[16];
/* Show the OSD message (regardless of notifications setting). */
rc_runtime_format_lboard_value(formatted_value, sizeof(formatted_value),
value, lboard->format);
CHEEVOS_LOG(RCHEEVOS_TAG "Submitting %s for leaderboard %u\n",
formatted_value, lboard->id);
/* Show the on-screen message (regardless of notifications setting). */
snprintf(buffer, sizeof(buffer), "Submitted %s for %s",
formatted_value, lboard->title);
formatted_value, lboard->title);
runloop_msg_queue_push(buffer, 0, 2 * 60, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
#if defined(HAVE_GFX_WIDGETS)
/* Hide the tracker */
if (widgets_ready)
if (gfx_widgets_ready())
gfx_widgets_set_leaderboard_display(lboard->id, NULL);
#endif
/* Start the submit task. */
{
rcheevos_async_io_request
*request = (rcheevos_async_io_request*)
calloc(1, sizeof(rcheevos_async_io_request));
request->type = CHEEVOS_ASYNC_SUBMIT_LBOARD;
request->id = lboard->id;
request->value = value;
request->success_message = "Submitted leaderboard";
request->failure_message = "Error submitting leaderboard";
rcheevos_get_user_agent(locals,
request->user_agent, sizeof(request->user_agent));
rcheevos_async_submit_lboard(locals, request);
}
/* Start the submit task */
rcheevos_client_submit_lboard_entry(lboard->id, value);
}
static void rcheevos_lboard_canceled(rcheevos_ralboard_t * lboard,
@ -1988,7 +1533,7 @@ static int rcheevos_iterate(rcheevos_coro_t* coro)
CORO_GOSUB(RCHEEVOS_LOGIN);
ret = rc_url_get_patch(coro->url, sizeof(coro->url), coro->settings->arrays.cheevos_username, rcheevos_locals.token, coro->gameid);
ret = rc_url_get_patch(coro->url, sizeof(coro->url), rcheevos_locals.username, rcheevos_locals.token, coro->gameid);
if (ret < 0)
{
@ -2206,7 +1751,9 @@ static int rcheevos_iterate(rcheevos_coro_t* coro)
CORO_STOP();
}
ret = rcheevos_get_token(coro->json, tok, sizeof(tok));
ret = rcheevos_get_token(coro->json,
rcheevos_locals.username, sizeof(rcheevos_locals.username),
tok, sizeof(tok));
if (ret != 0)
{
@ -2229,12 +1776,12 @@ static int rcheevos_iterate(rcheevos_coro_t* coro)
char msg[256];
snprintf(msg, sizeof(msg),
"RetroAchievements: Logged in as \"%s\".",
coro->settings->arrays.cheevos_username);
rcheevos_locals.username);
msg[sizeof(msg) - 1] = 0;
runloop_msg_queue_push(msg, 0, 2 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
CHEEVOS_LOG(RCHEEVOS_TAG "logged in successfully\n");
CHEEVOS_LOG(RCHEEVOS_TAG "%s logged in successfully\n", rcheevos_locals.username);
strlcpy(rcheevos_locals.token, tok,
sizeof(rcheevos_locals.token));
@ -2362,7 +1909,7 @@ static int rcheevos_iterate(rcheevos_coro_t* coro)
for (coro->i = 0; coro->i < 2; coro->i++)
{
ret = rc_url_get_unlock_list(coro->url, sizeof(coro->url),
coro->settings->arrays.cheevos_username,
rcheevos_locals.username,
rcheevos_locals.token, coro->gameid, coro->i);
if (ret < 0)
@ -2397,7 +1944,7 @@ static int rcheevos_iterate(rcheevos_coro_t* coro)
{
int ret = rc_url_post_playing(coro->url, sizeof(coro->url),
coro->settings->arrays.cheevos_username,
rcheevos_locals.username,
rcheevos_locals.token, coro->gameid);
if (ret < 0)

661
cheevos/cheevos_client.c Normal file
View File

@ -0,0 +1,661 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2019-2021 - Brian Weiss
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "cheevos_client.h"
#include "cheevos.h"
#include "../configuration.h"
#include "../paths.h"
#include "../version.h"
#include <string/stdstring.h>
#include <features/features_cpu.h>
#include "../frontend/frontend_driver.h"
#include "../network/net_http_special.h"
#include "../tasks/tasks_internal.h"
#ifdef HAVE_DISCORD
#include "../network/discord.h"
#endif
#include "../deps/rcheevos/include/rc_api_runtime.h"
/* Define this macro to log URLs. */
#undef CHEEVOS_LOG_URLS
/* Define this macro to have the password and token logged.
* THIS WILL DISCLOSE THE USER'S PASSWORD, TAKE CARE! */
#undef CHEEVOS_LOG_PASSWORD
/* Number of usecs to wait between posting rich presence to the site. */
/* Keep consistent with SERVER_PING_FREQUENCY from RAIntegration. */
#define CHEEVOS_PING_FREQUENCY 2 * 60 * 1000000
/****************************
* data types *
****************************/
enum rcheevos_async_io_type
{
CHEEVOS_ASYNC_RICHPRESENCE = 0,
CHEEVOS_ASYNC_AWARD_ACHIEVEMENT,
CHEEVOS_ASYNC_SUBMIT_LBOARD
};
typedef void (*rcheevos_async_handler)(int id,
http_transfer_data_t *data, char buffer[], size_t buffer_size);
typedef struct rcheevos_async_io_request
{
rc_api_request_t request;
rcheevos_async_handler handler;
int id;
int attempt_count;
const char* success_message;
const char* failure_message;
const char* user_agent;
char type;
} rcheevos_async_io_request;
/****************************
* forward declarations *
****************************/
static retro_time_t rcheevos_client_prepare_ping(rcheevos_async_io_request* request);
static void rcheevos_async_http_task_callback(
retro_task_t* task, void* task_data, void* user_data, const char* error);
/****************************
* user agent construction *
****************************/
static int append_no_spaces(char* buffer, char* stop, const char* text)
{
char* ptr = buffer;
while (ptr < stop && *text)
{
if (*text == ' ')
{
*ptr++ = '_';
++text;
}
else
{
*ptr++ = *text++;
}
}
*ptr = '\0';
return (ptr - buffer);
}
void rcheevos_get_user_agent(rcheevos_locals_t *locals,
char *buffer, size_t len)
{
struct retro_system_info *system = runloop_get_libretro_system_info();
char* ptr;
/* if we haven't calculated the non-changing portion yet, do so now
* [retroarch version + os version] */
if (!locals->user_agent_prefix[0])
{
const frontend_ctx_driver_t *frontend = frontend_get_ptr();
int major, minor;
char tmp[64];
if (frontend && frontend->get_os)
{
frontend->get_os(tmp, sizeof(tmp), &major, &minor);
snprintf(locals->user_agent_prefix, sizeof(locals->user_agent_prefix),
"RetroArch/%s (%s %d.%d)", PACKAGE_VERSION, tmp, major, minor);
}
else
{
snprintf(locals->user_agent_prefix, sizeof(locals->user_agent_prefix),
"RetroArch/%s", PACKAGE_VERSION);
}
}
/* append the non-changing portion */
ptr = buffer + strlcpy(buffer, locals->user_agent_prefix, len);
/* if a core is loaded, append its information */
if (system && !string_is_empty(system->library_name))
{
char* stop = buffer + len - 1;
const char* path = path_get(RARCH_PATH_CORE);
*ptr++ = ' ';
if (!string_is_empty(path))
{
append_no_spaces(ptr, stop, path_basename(path));
path_remove_extension(ptr);
ptr += strlen(ptr);
}
else
{
ptr += append_no_spaces(ptr, stop, system->library_name);
}
if (system->library_version)
{
*ptr++ = '/';
ptr += append_no_spaces(ptr, stop, system->library_version);
}
}
*ptr = '\0';
}
#ifdef CHEEVOS_LOG_URLS
#ifndef CHEEVOS_LOG_PASSWORD
static void rcheevos_filter_url_param(char* url, char* param)
{
char *next;
size_t param_len = strlen(param);
char *start = strchr(url, '?');
if (!start)
start = url;
else
++start;
do
{
next = strchr(start, '&');
if (start[param_len] == '=' && memcmp(start, param, param_len) == 0)
{
if (next)
strcpy_literal(start, next + 1);
else if (start > url)
start[-1] = '\0';
else
*start = '\0';
return;
}
if (!next)
return;
start = next + 1;
} while (1);
}
#endif
#endif
void rcheevos_log_url(const char* api, const char* url)
{
#ifdef CHEEVOS_LOG_URLS
#ifdef CHEEVOS_LOG_PASSWORD
CHEEVOS_LOG(RCHEEVOS_TAG "GET %s\n", url);
#else
char copy[256];
strlcpy(copy, url, sizeof(copy));
rcheevos_filter_url_param(copy, "p");
rcheevos_filter_url_param(copy, "t");
CHEEVOS_LOG(RCHEEVOS_TAG "GET %s\n", copy);
#endif
#else
(void)api;
(void)url;
#endif
}
static void rcheevos_log_post_url(const char* url, const char* post)
{
#ifdef CHEEVOS_LOG_URLS
#ifdef CHEEVOS_LOG_PASSWORD
if (post && post[0])
CHEEVOS_LOG(RCHEEVOS_TAG "POST %s %s\n", url, post);
else
CHEEVOS_LOG(RCHEEVOS_TAG "POST %s\n", url);
#else
if (post && post[0])
{
char post_copy[2048];
strlcpy(post_copy, post, sizeof(post_copy));
rcheevos_filter_url_param(post_copy, "p");
rcheevos_filter_url_param(post_copy, "t");
if (post_copy[0])
CHEEVOS_LOG(RCHEEVOS_TAG "POST %s %s\n", url, post_copy);
else
CHEEVOS_LOG(RCHEEVOS_TAG "POST %s\n", url);
}
else
{
CHEEVOS_LOG(RCHEEVOS_TAG "POST %s\n", url);
}
#endif
#else
(void)url;
(void)post;
#endif
}
/****************************
* dispatch *
****************************/
static void rcheevos_async_retry_request(retro_task_t* task)
{
rcheevos_async_io_request* request = (rcheevos_async_io_request*)
task->user_data;
/* the timer task has done its job. let it dispose itself */
task_set_finished(task, 1);
/* start a new task for the HTTP call */
task_push_http_post_transfer_with_user_agent(request->request.url,
request->request.post_data, true, "POST", request->user_agent,
rcheevos_async_http_task_callback, request);
}
static void rcheevos_async_retry_request_after_delay(rcheevos_async_io_request* request, const char* error)
{
retro_task_t* task = task_init();
/* Double the wait between each attempt until we hit
* a maximum delay of two minutes.
* 250ms -> 500ms -> 1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s -> 120s -> 120s... */
retro_time_t retry_delay = (request->attempt_count > 8)
? (120 * 1000 * 1000)
: ((250 * 1000) << request->attempt_count);
CHEEVOS_ERR(RCHEEVOS_TAG "%s %u: %s (automatic retry in %dms)\n", request->failure_message,
request->id, error, (int)retry_delay / 1000);
task->when = cpu_features_get_time_usec() + retry_delay;
task->handler = rcheevos_async_retry_request;
task->user_data = request;
task->progress = -1;
++request->attempt_count;
task_queue_push(task);
}
static void rcheevos_async_http_task_callback(
retro_task_t* task, void* task_data, void* user_data, const char* error)
{
rcheevos_async_io_request *request = (rcheevos_async_io_request*)user_data;
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
char buffer[224];
if (error)
{
/* there was a communication error */
if (request->type == CHEEVOS_ASYNC_RICHPRESENCE && request->attempt_count > 0)
{
/* only retry the ping once (in case of network hiccup), otherwise let
* the timer handle it after the normal ping period has elapsed */
CHEEVOS_ERR(RCHEEVOS_TAG "%s %u: %s\n", request->failure_message,
request->id, error);
}
else
{
/* automatically retry the request */
rcheevos_async_retry_request_after_delay(request, error);
}
return;
}
if (!data)
{
/* Server did not return HTTP headers */
strlcpy(buffer, "Server communication error", sizeof(buffer));
}
else if (!data->data || !data->len)
{
if (data->status != 200)
{
/* Server returned an error via status code. */
snprintf(buffer, sizeof(buffer), "HTTP error code %d", data->status);
}
else
{
/* Server sent an empty response without an error status code */
strlcpy(buffer, "No response from server", sizeof(buffer));
}
}
else
{
buffer[0] = '\0'; /* indicate success unless handler provides error */
/* Call appropriate handler to process the response */
if (request->handler)
{
/* NOTE: data->data is not null-terminated. Most handlers assume the
* response is properly formatted or will encounter a parse failure
* before reading past the end of the data */
request->handler(request->id, data, buffer, sizeof(buffer));
}
}
if (!buffer[0])
{
/* success */
if (request->success_message)
{
CHEEVOS_LOG(RCHEEVOS_TAG "%s %u\n", request->success_message, request->id);
}
}
else
{
/* encountered an error */
char errbuf[256];
snprintf(errbuf, sizeof(errbuf), "%s %u: %s",
request->failure_message, request->id, buffer);
CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", errbuf);
switch (request->type)
{
case CHEEVOS_ASYNC_RICHPRESENCE:
/* Don't bother informing user when
* rich presence update fails */
break;
default:
runloop_msg_queue_push(errbuf, 0, 5 * 60, false, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
break;
}
}
rc_api_destroy_request(&request->request);
/* rich presence request will be reused on next ping - reset the attempt
* counter. for all other request types, free the request object */
if (request->type == CHEEVOS_ASYNC_RICHPRESENCE)
request->attempt_count = 0;
else
free(request);
}
static void rcheevos_async_begin_request(rcheevos_async_io_request* request,
rcheevos_async_handler handler, char type, int id,
const char* success_message, const char* failure_message)
{
request->handler = handler;
request->type = type;
request->id = id;
request->success_message = success_message;
request->failure_message = failure_message;
request->attempt_count = 0;
if (!request->user_agent)
request->user_agent = get_rcheevos_locals()->user_agent_core;
rcheevos_log_post_url(request->request.url, request->request.post_data);
task_push_http_post_transfer_with_user_agent(request->request.url,
request->request.post_data, true, "POST", request->user_agent,
rcheevos_async_http_task_callback, request);
}
static bool rcheevos_async_succeeded(int result,
const rc_api_response_t* response, char buffer[], size_t buffer_size)
{
if (result != RC_OK)
{
strlcpy(buffer, rc_error_str(result), buffer_size);
return false;
}
if (!response->succeeded)
{
strlcpy(buffer, response->error_message, buffer_size);
return false;
}
return true;
}
/****************************
* ping *
****************************/
static retro_time_t rcheevos_client_prepare_ping(rcheevos_async_io_request* request)
{
const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals();
const settings_t *settings = config_get_ptr();
const bool cheevos_richpresence_enable =
settings->bools.cheevos_richpresence_enable;
rc_api_ping_request_t api_params;
char buffer[256] = "";
memset(&api_params, 0, sizeof(api_params));
api_params.username = rcheevos_locals->username;
api_params.api_token = rcheevos_locals->token;
api_params.game_id = request->id;
if (cheevos_richpresence_enable)
{
rcheevos_get_richpresence(buffer, sizeof(buffer));
api_params.rich_presence = buffer;
}
rc_api_init_ping_request(&request->request, &api_params);
rcheevos_log_post_url(request->request.url, request->request.post_data);
#ifdef HAVE_DISCORD
if (settings->bools.discord_enable && discord_is_ready())
discord_update(DISCORD_PRESENCE_RETROACHIEVEMENTS);
#endif
/* Update rich presence every two minutes */
if (cheevos_richpresence_enable)
return cpu_features_get_time_usec() + CHEEVOS_PING_FREQUENCY;
/* Send ping every four minutes */
return cpu_features_get_time_usec() + CHEEVOS_PING_FREQUENCY * 2;
}
static void rcheevos_async_ping_handler(retro_task_t* task)
{
rcheevos_async_io_request* request = (rcheevos_async_io_request*)
task->user_data;
const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals();
if (request->id != (int)rcheevos_locals->patchdata.game_id)
{
/* game changed; stop the recurring task - a new one will
* be scheduled if a new game is loaded */
task_set_finished(task, 1);
/* request->request was destroyed in rcheevos_async_http_task_callback */
free(request);
return;
}
/* update the request and set the task to fire again in
* two minutes */
task->when = rcheevos_client_prepare_ping(request);
/* start the HTTP request */
task_push_http_post_transfer_with_user_agent(request->request.url,
request->request.post_data, true, "POST", request->user_agent,
rcheevos_async_http_task_callback, request);
}
/****************************
* start session *
****************************/
void rcheevos_client_start_session(unsigned game_id)
{
rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals();
/* the core won't change while a session is active, so only
* calculate the user agent once */
rcheevos_get_user_agent(rcheevos_locals,
rcheevos_locals->user_agent_core,
sizeof(rcheevos_locals->user_agent_core));
/* force non-HTTPS until everything uses RAPI */
rc_api_set_host("http://retroachievements.org");
/* schedule the first rich presence call in 30 seconds */
{
rcheevos_async_io_request *request = (rcheevos_async_io_request*)
calloc(1, sizeof(rcheevos_async_io_request));
if (!request)
{
CHEEVOS_LOG(RCHEEVOS_TAG "Failed to allocate rich presence request\n");
}
else
{
retro_task_t* task = task_init();
request->id = game_id;
request->type = CHEEVOS_ASYNC_RICHPRESENCE;
request->user_agent = rcheevos_locals->user_agent_core;
request->failure_message = "Error sending ping";
task->handler = rcheevos_async_ping_handler;
task->user_data = request;
task->progress = -1;
task->when = cpu_features_get_time_usec() +
CHEEVOS_PING_FREQUENCY / 4;
task_queue_push(task);
}
}
}
/****************************
* award achievement *
****************************/
static void rcheevos_async_award_achievement_callback(int id,
http_transfer_data_t *data, char buffer[], size_t buffer_size)
{
rc_api_award_achievement_response_t api_response;
int result = rc_api_process_award_achievement_response(&api_response, data->data);
if (rcheevos_async_succeeded(result, &api_response.response, buffer, buffer_size))
{
if (api_response.awarded_achievement_id != id)
{
snprintf(buffer, buffer_size, "Achievement %u awarded instead",
api_response.awarded_achievement_id);
}
else if (api_response.response.error_message)
{
/* previously unlocked achievements are returned as a "successful" error */
CHEEVOS_LOG(RCHEEVOS_TAG "Achievement %u: %s\n",
id, api_response.response.error_message);
}
}
rc_api_destroy_award_achievement_response(&api_response);
}
void rcheevos_client_award_achievement(unsigned achievement_id)
{
rcheevos_async_io_request *request = (rcheevos_async_io_request*)
calloc(1, sizeof(rcheevos_async_io_request));
if (!request)
{
CHEEVOS_LOG(RCHEEVOS_TAG "Failed to allocate unlock request for achievement %u\n",
achievement_id);
}
else
{
const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals();
rc_api_award_achievement_request_t api_params;
memset(&api_params, 0, sizeof(api_params));
api_params.username = rcheevos_locals->username;
api_params.api_token = rcheevos_locals->token;
api_params.achievement_id = achievement_id;
api_params.hardcore = rcheevos_locals->hardcore_active ? 1 : 0;
api_params.game_hash = rcheevos_locals->hash;
rc_api_init_award_achievement_request(&request->request, &api_params);
rcheevos_async_begin_request(request,
rcheevos_async_award_achievement_callback,
CHEEVOS_ASYNC_AWARD_ACHIEVEMENT, achievement_id,
"Awarded achievement",
"Error awarding achievement");
}
}
/****************************
* submit leaderboard *
****************************/
static void rcheevos_async_submit_lboard_entry_callback(int id,
http_transfer_data_t* data, char buffer[], size_t buffer_size)
{
rc_api_submit_lboard_entry_response_t api_response;
int result = rc_api_process_submit_lboard_entry_response(&api_response, data->data);
if (rcheevos_async_succeeded(result, &api_response.response, buffer, buffer_size))
{
/* not currently doing anything with the response */
}
rc_api_destroy_submit_lboard_entry_response(&api_response);
}
void rcheevos_client_submit_lboard_entry(unsigned leaderboard_id, int value)
{
rcheevos_async_io_request *request = (rcheevos_async_io_request*)
calloc(1, sizeof(rcheevos_async_io_request));
if (!request)
{
CHEEVOS_LOG(RCHEEVOS_TAG "Failed to allocate request for lboard %u submit\n",
leaderboard_id);
}
else
{
const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals();
rc_api_submit_lboard_entry_request_t api_params;
memset(&api_params, 0, sizeof(api_params));
api_params.username = rcheevos_locals->username;
api_params.api_token = rcheevos_locals->token;
api_params.leaderboard_id = leaderboard_id;
api_params.score = value;
api_params.game_hash = rcheevos_locals->hash;
rc_api_init_submit_lboard_entry_request(&request->request, &api_params);
rcheevos_async_begin_request(request,
rcheevos_async_submit_lboard_entry_callback,
CHEEVOS_ASYNC_SUBMIT_LBOARD, leaderboard_id,
"Submitted leaderboard",
"Error submitting leaderboard");
}
}

32
cheevos/cheevos_client.h Normal file
View File

@ -0,0 +1,32 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2019-2021 - Brian Weiss
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __RARCH_CHEEVOS_CLIENT_H
#define __RARCH_CHEEVOS_CLIENT_H
#include "cheevos_locals.h"
RETRO_BEGIN_DECLS
void rcheevos_client_start_session(unsigned game_id);
void rcheevos_client_award_achievement(unsigned achievement_id);
void rcheevos_client_submit_lboard_entry(unsigned leaderboard_id, int value);
void rcheevos_log_url(const char* api, const char* url);
void rcheevos_get_user_agent(rcheevos_locals_t *locals, char *buffer, size_t len);
RETRO_END_DECLS
#endif /* __RARCH_CHEEVOS_MENU_H */

View File

@ -133,9 +133,11 @@ typedef struct rcheevos_locals_t
enum event_command queued_command; /* action queued by background thread to be run on main thread */
#endif
char username[32]; /* case-corrected username */
char token[32]; /* user's session token */
char hash[33]; /* retroachievements hash for current content */
char user_agent_prefix[128]; /* RetroArch/OS version information */
char user_agent_core[256]; /* RetroArch/OS/Core version information */
#ifdef HAVE_MENU
rcheevos_menuitem_t* menuitems; /* array of items for the achievements quick menu */

View File

@ -23,6 +23,7 @@
#define CHEEVOS_JSON_KEY_BADGENAME 0x887685d9U
#define CHEEVOS_JSON_KEY_CONSOLE_ID 0x071656e5U
#define CHEEVOS_JSON_KEY_TOKEN 0x0e2dbd26U
#define CHEEVOS_JSON_KEY_USERNAME 0x7c8da264U
#define CHEEVOS_JSON_KEY_FLAGS 0x0d2e96b2U
#define CHEEVOS_JSON_KEY_LEADERBOARDS 0xf1247d2dU
#define CHEEVOS_JSON_KEY_RICHPRESENCE 0xf18dd230U
@ -149,14 +150,16 @@ static int rcheevos_get_value(const char* json, unsigned key_hash,
Returns the token or the error message
*****************************************************************************/
int rcheevos_get_token(const char* json, char* token, size_t length)
int rcheevos_get_token(const char* json, char* username, size_t username_length,
char* token, size_t length)
{
rcheevos_get_value(json, CHEEVOS_JSON_KEY_ERROR, token, length);
if (!string_is_empty(token))
return -1;
return rcheevos_get_value(json, CHEEVOS_JSON_KEY_TOKEN, token, length);
return rcheevos_get_value(json, CHEEVOS_JSON_KEY_TOKEN, token, length) +
rcheevos_get_value(json, CHEEVOS_JSON_KEY_USERNAME, username, username_length);
}
int rcheevos_get_json_error(const char* json, char* token, size_t length)

View File

@ -23,7 +23,9 @@ RETRO_BEGIN_DECLS
typedef void (*rcheevos_unlock_cb_t)(unsigned id, void* userdata);
int rcheevos_get_json_error(const char* json, char* token, size_t length);
int rcheevos_get_token(const char* json, char* token, size_t length);
int rcheevos_get_token(const char* json, char* username, size_t username_length,
char* token, size_t length);
int rcheevos_get_patchdata(const char* json, rcheevos_rapatchdata_t* patchdata);
void rcheevos_free_patchdata(rcheevos_rapatchdata_t* patchdata);

View File

@ -191,9 +191,13 @@ ACHIEVEMENTS
#include "../network/net_http_special.c"
#include "../cheevos/cheevos.c"
#include "../cheevos/cheevos_client.c"
#include "../cheevos/cheevos_menu.c"
#include "../cheevos/cheevos_parser.c"
#include "../deps/rcheevos/src/rapi/rc_api_common.c"
#include "../deps/rcheevos/src/rapi/rc_api_runtime.c"
#include "../deps/rcheevos/src/rapi/rc_api_user.c"
#include "../deps/rcheevos/src/rcheevos/alloc.c"
#include "../deps/rcheevos/src/rcheevos/compat.c"
#include "../deps/rcheevos/src/rcheevos/condition.c"

View File

@ -46,6 +46,8 @@ void net_http_connection_set_user_agent(struct http_connection_t* conn, const ch
const char *net_http_connection_url(struct http_connection_t *conn);
const char* net_http_connection_method(struct http_connection_t* conn);
struct http_t *net_http_new(struct http_connection_t *conn);
/* You can use this to call net_http_update

View File

@ -682,6 +682,11 @@ const char *net_http_connection_url(struct http_connection_t *conn)
return conn->urlcopy;
}
const char* net_http_connection_method(struct http_connection_t* conn)
{
return conn->methodcopy;
}
struct http_t *net_http_new(struct http_connection_t *conn)
{
bool error = false;

View File

@ -280,25 +280,36 @@ static void* task_push_http_transfer_generic(
const char *url, bool mute, const char *type,
retro_task_callback_t cb, void *user_data)
{
task_finder_data_t find_data;
retro_task_t *t = NULL;
http_handle_t *http = NULL;
find_data.func = task_http_finder;
find_data.userdata = (void*)url;
/* Concurrent download of the same file is not allowed */
if (task_queue_find(&find_data))
{
if (conn)
net_http_connection_free(conn);
return NULL;
}
const char *method = NULL;
if (!conn)
return NULL;
method = net_http_connection_method(conn);
if (method && (method[0] == 'P' || method[0] == 'p'))
{
/* POST requests usually mutate the server, so assume multiple calls are
* intended, even if they're duplicated. Additionally, they may differ
* only by the POST data, and task_http_finder doesn't look at that, so
* unique requests could be misclassified as duplicates.
*/
}
else
{
task_finder_data_t find_data;
find_data.func = task_http_finder;
find_data.userdata = (void*)url;
/* Concurrent download of the same file is not allowed */
if (task_queue_find(&find_data))
{
net_http_connection_free(conn);
return NULL;
}
}
http = (http_handle_t*)malloc(sizeof(*http));
if (!http)