diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c index b9ad73a486..2e863d4c26 100644 --- a/cheevos/cheevos.c +++ b/cheevos/cheevos.c @@ -307,7 +307,7 @@ void rcheevos_award_achievement(rcheevos_locals_t* locals, /* Show the on screen message. */ #if defined(HAVE_GFX_WIDGETS) if (widgets_ready) - gfx_widgets_push_achievement(cheevo->title, cheevo->badge); + gfx_widgets_push_achievement(msg_hash_to_str(MSG_ACHIEVEMENT_UNLOCKED), cheevo->title, cheevo->badge); else #endif { @@ -1285,7 +1285,14 @@ static void rcheevos_show_game_placard() CHEEVOS_LOG(RCHEEVOS_TAG "%s\n", msg); if (settings->bools.cheevos_verbose_enable) - runloop_msg_queue_push(msg, 0, 3 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + { +#if defined (HAVE_GFX_WIDGETS) + if (gfx_widgets_ready()) + gfx_widgets_push_achievement(rcheevos_locals.game.title, msg, rcheevos_locals.game.badge_name); + else +#endif + runloop_msg_queue_push(msg, 0, 3 * 60, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } } static void rcheevos_end_load(void) @@ -1306,27 +1313,8 @@ static void rcheevos_fetch_badges(void) rcheevos_client_fetch_badges(rcheevos_fetch_badges_callback, NULL); } -static void rcheevos_start_session(void) +static void rcheevos_start_session_async(retro_task_t* task) { - if (rcheevos_load_aborted()) - { - CHEEVOS_LOG(RCHEEVOS_TAG "Load aborted before starting session\n"); - return; - } - - if ( rcheevos_locals.game.achievement_count == 0 - && rcheevos_locals.game.leaderboard_count == 0) - { - if (!rcheevos_locals.runtime.richpresence) - { - /* nothing for the runtime to process, - * disable hardcore and bail */ - rcheevos_show_game_placard(); - rcheevos_pause_hardcore(); - return; - } - } - rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_STARTING_SESSION); /* activate the achievements and leaderboards @@ -1371,10 +1359,41 @@ static void rcheevos_start_session(void) rcheevos_show_game_placard(); + task_set_finished(task, true); + if (rcheevos_end_load_state() == 0) rcheevos_fetch_badges(); } +static void rcheevos_start_session(void) +{ + retro_task_t* task; + + if (rcheevos_load_aborted()) + { + CHEEVOS_LOG(RCHEEVOS_TAG "Load aborted before starting session\n"); + return; + } + + if (rcheevos_locals.game.achievement_count == 0 + && rcheevos_locals.game.leaderboard_count == 0) + { + if (!rcheevos_locals.runtime.richpresence) + { + /* nothing for the runtime to process, + * disable hardcore and bail */ + rcheevos_show_game_placard(); + rcheevos_pause_hardcore(); + return; + } + } + + /* this is called on the primary thread. use a task to do the initialization on a background thread */ + task = task_init(); + task->handler = rcheevos_start_session_async; + task_queue_push(task); +} + static void rcheevos_initialize_runtime_callback(void* userdata) { rcheevos_start_session(); diff --git a/cheevos/cheevos_client.c b/cheevos/cheevos_client.c index 985cde3e33..f9fb7ea0a3 100644 --- a/cheevos/cheevos_client.c +++ b/cheevos/cheevos_client.c @@ -99,6 +99,32 @@ typedef struct rcheevos_async_io_request char type; } rcheevos_async_io_request; +#ifdef HAVE_THREADS +#define RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS 2 +#else +#define RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS 1 +#endif + +typedef struct rcheevos_fetch_badge_state +{ + unsigned badge_fetch_index; + unsigned locked_badge_fetch_index; + const char* badge_directory; + rcheevos_client_callback callback; + void* callback_data; + char requested_badges[ + RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS][32]; +} rcheevos_fetch_badge_state; + +typedef struct rcheevos_fetch_badge_data +{ + rcheevos_fetch_badge_state* state; + int request_index; + void* data; + size_t data_len; + rcheevos_client_callback callback; +} rcheevos_fetch_badge_data; + /**************************** * forward declarations * @@ -111,6 +137,10 @@ static void rcheevos_async_http_task_callback( static void rcheevos_async_end_request(rcheevos_async_io_request* request); +static void rcheevos_async_fetch_badge_callback( + struct rcheevos_async_io_request* request, + http_transfer_data_t* data, char buffer[], size_t buffer_size); + /**************************** * user agent construction * ****************************/ @@ -893,6 +923,98 @@ static void rcheevos_client_initialize_runtime_callback(void* userdata) free(runtime_data); } +static void rcheevos_client_fetch_game_badge_callback(void* userdata) +{ + rcheevos_fetch_badge_data* data = (rcheevos_fetch_badge_data*)userdata; + rcheevos_async_initialize_runtime_data_t* runtime_data = + (rcheevos_async_initialize_runtime_data_t*)data->state->callback_data; + + free((void*)data->state->badge_directory); + free(data->state); + free(data); + + rcheevos_client_initialize_runtime_callback(runtime_data); +} + +static void rcheevos_client_fetch_game_badge(const char* badge_name, rcheevos_async_initialize_runtime_data_t* runtime_data) +{ +#if defined(HAVE_GFX_WIDGETS) /* don't need game badge unless widgets are enabled */ + char badge_fullpath[PATH_MAX_LENGTH] = ""; + char* badge_fullname = NULL; + size_t badge_fullname_size = 0; + + /* make sure the directory exists */ + fill_pathname_application_special(badge_fullpath, + sizeof(badge_fullpath), + APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES); + + if (!path_is_directory(badge_fullpath)) + { + CHEEVOS_LOG(RCHEEVOS_TAG "Creating %s\n", badge_fullpath); + path_mkdir(badge_fullpath); + } + + fill_pathname_slash(badge_fullpath, sizeof(badge_fullpath)); + badge_fullname = badge_fullpath + strlen(badge_fullpath); + badge_fullname_size = sizeof(badge_fullpath) - + (badge_fullname - badge_fullpath); + + snprintf(badge_fullname, + badge_fullname_size, "i%s" FILE_PATH_PNG_EXTENSION, badge_name); + + /* check if it's already available */ + if (path_is_valid(badge_fullpath)) + return; + +#ifdef CHEEVOS_LOG_BADGES + CHEEVOS_LOG(RCHEEVOS_TAG "Downloading game badge %s\n", badge_name); +#endif + + { + rcheevos_async_io_request* request = (rcheevos_async_io_request*) + calloc(1, sizeof(rcheevos_async_io_request)); + rcheevos_fetch_badge_data* data = (rcheevos_fetch_badge_data*) + calloc(1, sizeof(rcheevos_fetch_badge_data)); + rcheevos_fetch_badge_state* state = (rcheevos_fetch_badge_state*) + calloc(1, sizeof(rcheevos_fetch_badge_state)); + + if (!request || !data || !state) + { + CHEEVOS_LOG(RCHEEVOS_TAG + "Failed to allocate fetch badge request\n"); + } + else + { + rc_api_fetch_image_request_t api_params; + int result; + + memset(&api_params, 0, sizeof(api_params)); + api_params.image_name = badge_name; + api_params.image_type = RC_IMAGE_TYPE_GAME; + + result = rc_api_init_fetch_image_request(&request->request, &api_params); + + strlcpy(state->requested_badges[0], badge_fullname, sizeof(state->requested_badges[0])); + *badge_fullname = '\0'; + state->badge_directory = strdup(badge_fullpath); + state->callback_data = runtime_data; + + data->state = state; + data->request_index = 0; + data->callback = rcheevos_client_fetch_game_badge_callback; + + request->callback_data = data; + + rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_FETCHING_GAME_DATA); + rcheevos_async_begin_request(request, result, + rcheevos_async_fetch_badge_callback, + CHEEVOS_ASYNC_FETCH_BADGE, atoi(badge_name), NULL, + "Error fetching game badge"); + } + } +#endif +} + static void rcheevos_async_fetch_user_unlocks_callback( struct rcheevos_async_io_request* request, http_transfer_data_t* data, char buffer[], size_t buffer_size) @@ -939,6 +1061,10 @@ static void rcheevos_async_fetch_game_data_callback( runtime_data->game_data.title); rcheevos_locals->game.console_id = runtime_data->game_data.console_id; + + snprintf(rcheevos_locals->game.badge_name, sizeof(rcheevos_locals->game.badge_name), + "i%s", runtime_data->game_data.image_name); + rcheevos_client_fetch_game_badge(runtime_data->game_data.image_name, runtime_data); } } @@ -1238,30 +1364,6 @@ void rcheevos_client_start_session(unsigned game_id) * fetch badge * ****************************/ -#ifdef HAVE_THREADS - #define RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS 2 -#else - #define RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS 1 -#endif - -typedef struct rcheevos_fetch_badge_state -{ - unsigned badge_fetch_index; - unsigned locked_badge_fetch_index; - const char* badge_directory; - rcheevos_client_callback callback; - void* callback_data; - char requested_badges[ - RCHEEVOS_CONCURRENT_BADGE_DOWNLOADS][32]; -} rcheevos_fetch_badge_state; - -typedef struct rcheevos_fetch_badge_data -{ - rcheevos_fetch_badge_state* state; - int request_index; -} rcheevos_fetch_badge_data; - - static bool rcheevos_fetch_next_badge(rcheevos_fetch_badge_state* state); static void rcheevos_end_fetch_badges(rcheevos_fetch_badge_state* state) @@ -1285,26 +1387,56 @@ static void rcheevos_async_download_next_badge(void* userdata) free(badge_data); } -static void rcheevos_async_fetch_badge_callback( - struct rcheevos_async_io_request* request, - http_transfer_data_t* data, char buffer[], size_t buffer_size) +static void rcheevos_async_write_badge(retro_task_t* task) { char badge_fullpath[PATH_MAX_LENGTH]; - rcheevos_fetch_badge_data* badge_data = - (rcheevos_fetch_badge_data*)request->callback_data; + rcheevos_fetch_badge_data* badge_data = + (rcheevos_fetch_badge_data*)task->user_data; const rcheevos_locals_t* rcheevos_locals = get_rcheevos_locals(); fill_pathname_join(badge_fullpath, badge_data->state->badge_directory, badge_data->state->requested_badges[badge_data->request_index], sizeof(badge_fullpath)); - if (!filestream_write_file(badge_fullpath, data->data, data->len)) + if (!filestream_write_file(badge_fullpath, badge_data->data, badge_data->data_len)) + { CHEEVOS_ERR(RCHEEVOS_TAG "Error writing badge %s\n", - badge_fullpath); + badge_fullpath); + } + + free(badge_data->data); + badge_data->data = NULL; + badge_data->data_len = 0; CHEEVOS_LOCK(rcheevos_locals->load_info.request_lock); badge_data->state->requested_badges[badge_data->request_index][0] = '\0'; CHEEVOS_UNLOCK(rcheevos_locals->load_info.request_lock); + + task_set_finished(task, true); + + if (badge_data->callback) + badge_data->callback(badge_data); +} + +static void rcheevos_async_fetch_badge_callback( + struct rcheevos_async_io_request* request, + http_transfer_data_t* data, char buffer[], size_t buffer_size) +{ + rcheevos_fetch_badge_data* badge_data = + (rcheevos_fetch_badge_data*)request->callback_data; + retro_task_t* task; + + /* take ownership of the file data */ + badge_data->data = data->data; + badge_data->data_len = data->len; + data->data = NULL; + data->len = 0; + + /* this is called on the primary thread. use a task to write the file from a background thread */ + task = task_init(); + task->handler = rcheevos_async_write_badge; + task->user_data = badge_data; + task_queue_push(task); } static bool rcheevos_client_fetch_badge( @@ -1402,8 +1534,8 @@ static bool rcheevos_client_fetch_badge( data->state = state; data->request_index = request_index; + data->callback = rcheevos_async_download_next_badge; - request->callback = rcheevos_async_download_next_badge; request->callback_data = data; rcheevos_begin_load_state(RCHEEVOS_LOAD_STATE_FETCHING_BADGES); diff --git a/cheevos/cheevos_locals.h b/cheevos/cheevos_locals.h index 15a4d0d59c..bd726afa34 100644 --- a/cheevos/cheevos_locals.h +++ b/cheevos/cheevos_locals.h @@ -125,6 +125,7 @@ typedef struct rcheevos_game_info_t int id; int console_id; char* title; + char badge_name[16]; char hash[33]; rcheevos_racheevo_t* achievements; diff --git a/gfx/gfx_widgets.h b/gfx/gfx_widgets.h index bd409e4b76..1d0d92bf3b 100644 --- a/gfx/gfx_widgets.h +++ b/gfx/gfx_widgets.h @@ -110,12 +110,6 @@ typedef struct gfx_widget_font_data_t msg_queue; } gfx_widget_fonts_t; -typedef struct cheevo_popup -{ - char* title; - uintptr_t badge; -} cheevo_popup; - typedef struct disp_widget_msg { char *msg; @@ -353,7 +347,7 @@ void gfx_widgets_ai_service_overlay_unload(void); #endif #ifdef HAVE_CHEEVOS -void gfx_widgets_push_achievement(const char *title, const char *badge); +void gfx_widgets_push_achievement(const char *title, const char* subtitle, const char *badge); void gfx_widgets_set_leaderboard_display(unsigned id, const char* value); void gfx_widgets_set_challenge_display(unsigned id, const char* badge); #endif diff --git a/gfx/widgets/gfx_widget_achievement_popup.c b/gfx/widgets/gfx_widget_achievement_popup.c index 02d4fe4dfc..160f2cb479 100644 --- a/gfx/widgets/gfx_widget_achievement_popup.c +++ b/gfx/widgets/gfx_widget_achievement_popup.c @@ -32,6 +32,13 @@ #define CHEEVO_QUEUE_SIZE 8 +typedef struct cheevo_popup +{ + char* title; + char* subtitle; + uintptr_t badge; +} cheevo_popup; + struct gfx_widget_achievement_popup_state { #ifdef HAVE_THREADS @@ -222,7 +229,7 @@ static void gfx_widget_achievement_popup_frame(void* data, void* userdata) /* Title */ gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.regular, - msg_hash_to_str(MSG_ACHIEVEMENT_UNLOCKED), + state->queue[state->queue_read_index].title, state->height + p_dispwidget->simple_widget_padding - unfold_offet, state->y + p_dispwidget->gfx_widget_fonts.regular.line_height + p_dispwidget->gfx_widget_fonts.regular.line_ascender, @@ -235,7 +242,7 @@ static void gfx_widget_achievement_popup_frame(void* data, void* userdata) /* TODO: is a ticker necessary ? */ gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.regular, - state->queue[state->queue_read_index].title, + state->queue[state->queue_read_index].subtitle, state->height + p_dispwidget->simple_widget_padding - unfold_offet, state->y + state->height - p_dispwidget->gfx_widget_fonts.regular.line_height @@ -267,6 +274,12 @@ static void gfx_widget_achievement_popup_free_current( state->queue[state->queue_read_index].title = NULL; } + if (state->queue[state->queue_read_index].subtitle) + { + free(state->queue[state->queue_read_index].subtitle); + state->queue[state->queue_read_index].subtitle = NULL; + } + if (state->queue[state->queue_read_index].badge) { video_driver_texture_unload(&state->queue[state->queue_read_index].badge); @@ -284,7 +297,8 @@ static void gfx_widget_achievement_popup_next(void* userdata) if (state->queue_read_index >= 0) { - gfx_widget_achievement_popup_free_current(state); + if (state->queue[state->queue_read_index].title) + gfx_widget_achievement_popup_free_current(state); /* start the next popup (if present) */ if (state->queue[state->queue_read_index].title) @@ -370,10 +384,10 @@ static void gfx_widget_achievement_popup_start( state->width = MAX( font_driver_get_message_width( p_dispwidget->gfx_widget_fonts.regular.font, - msg_hash_to_str(MSG_ACHIEVEMENT_UNLOCKED), 0, 1), + state->queue[state->queue_read_index].title, 0, 1), font_driver_get_message_width( p_dispwidget->gfx_widget_fonts.regular.font, - state->queue[state->queue_read_index].title, 0, 1) + state->queue[state->queue_read_index].subtitle, 0, 1) ); state->width += p_dispwidget->simple_widget_padding * 2; state->y = (float)(-(int)state->height); @@ -391,7 +405,7 @@ static void gfx_widget_achievement_popup_start( gfx_animation_push(&entry); } -void gfx_widgets_push_achievement(const char *title, const char *badge) +void gfx_widgets_push_achievement(const char *title, const char* subtitle, const char *badge) { gfx_widget_achievement_popup_state_t *state = &p_w_achievement_popup_st; int start_notification = 1; @@ -429,6 +443,7 @@ void gfx_widgets_push_achievement(const char *title, const char *badge) state->queue[state->queue_write_index].badge = badge_id; state->queue[state->queue_write_index].title = strdup(title); + state->queue[state->queue_write_index].subtitle = strdup(subtitle); state->queue_write_index = (state->queue_write_index + 1) % ARRAY_SIZE(state->queue);