From c1e8925f8799c07628e22a53ad523e4b19176071 Mon Sep 17 00:00:00 2001 From: Cthulhu-throwaway <96153783+Cthulhu-throwaway@users.noreply.github.com> Date: Thu, 23 Jun 2022 09:48:57 -0300 Subject: [PATCH] (Netplay) Find content task refactor (#14089) --- intl/msg_hash_us.h | 16 + menu/cbs/menu_cbs_ok.c | 4 +- msg_hash.h | 4 + network/discord.c | 4 +- tasks/task_netplay_find_content.c | 969 +++++++++++++++++------------- tasks/tasks_internal.h | 4 +- 6 files changed, 589 insertions(+), 412 deletions(-) diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 2c7e2e4692..d98172da5d 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -11019,10 +11019,26 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_NETPLAY_ROOM_NICKNAME, "Nickname: %s" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_LOOK, + "Looking for compatible content..." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NO_CORE, + "No core found" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NO_PLAYLISTS, + "No playlists found" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND, "Compatible content found" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NOT_FOUND, + "Failed to locate matching content by either CRC or filename" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_START_GONG, "Start Gong" diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index 6a6d04cbf1..65a1b25cc4 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -5999,8 +5999,8 @@ static int action_ok_netplay_connect_room(const char *path, const char *label, hostname, room->gamename, room->gamecrc); #endif - task_push_netplay_crc_scan(room->gamecrc, room->gamename, hostname, - room->corename, room->subsystem_name); + task_push_netplay_crc_scan(room->gamecrc, room->gamename, + room->subsystem_name, room->corename, hostname); return 0; } diff --git a/msg_hash.h b/msg_hash.h index 4a5cc858dc..8aae29519a 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -3066,7 +3066,11 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_SCALE, MENU_ENUM_LABEL_VALUE_NETPLAY_START_WHEN_LOADED, MENU_ENUM_LABEL_VALUE_NETPLAY_LOAD_CONTENT_MANUALLY, + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_LOOK, + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NO_CORE, + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NO_PLAYLISTS, MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND, + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NOT_FOUND, MENU_LABEL(NETPLAY_USE_MITM_SERVER), MENU_LABEL(NETPLAY_MITM_SERVER), diff --git a/network/discord.c b/network/discord.c index fde2d36c1a..036f713f17 100644 --- a/network/discord.c +++ b/network/discord.c @@ -193,8 +193,8 @@ static void handle_discord_join_cb(retro_task_t *task, void *task_data, snprintf(hostname, sizeof(hostname), "%s|%d", room->address, room->port); - task_push_netplay_crc_scan(room->gamecrc, room->gamename, hostname, - room->corename, room->subsystem_name); + task_push_netplay_crc_scan(room->gamecrc, room->gamename, + room->subsystem_name, room->corename, hostname); discord_st->connecting = true; if (discord_st->ready) diff --git a/tasks/task_netplay_find_content.c b/tasks/task_netplay_find_content.c index a4d069349a..2343ab93a1 100644 --- a/tasks/task_netplay_find_content.c +++ b/tasks/task_netplay_find_content.c @@ -1,6 +1,7 @@ /* RetroArch - A frontend for libretro. * Copyright (C) 2017 - Jean-André Santoni * Copyright (C) 2017-2019 - Andrés Suárez + * Copyright (C) 2021-2022 - Roberto V. Rampim * * 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- @@ -14,506 +15,662 @@ * If not, see . */ -#include -#include -#include -#include -#include -#include +#include +#include + #include -#include -#include + #include +#include #include #include -#include + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#include "../verbosity.h" +#include "../configuration.h" +#include "../paths.h" +#include "../command.h" +#include "../playlist.h" +#include "../core_info.h" +#include "../content.h" +#include "../runloop.h" #include "task_content.h" #include "tasks_internal.h" -#include "../file_path_special.h" -#include "../verbosity.h" -#include "../configuration.h" -#include "../playlist.h" -#include "../command.h" -#include "../core_info.h" -#include "../../retroarch.h" -#include "../../menu/menu_driver.h" -typedef struct +#ifdef HAVE_NETWORKING + +#include "../network/netplay/netplay.h" + +enum { - struct string_list *lpl_list; - playlist_config_t playlist_config; /* size_t alignment */ + STATE_NONE, + STATE_LOAD, + STATE_LOAD_SUBSYSTEM, + STATE_LOAD_CONTENTLESS, + STATE_LOAD_CURRENT +}; + +struct netplay_crc_scan_state +{ + int state; + bool running; +}; + +struct netplay_crc_scan_data +{ + struct + { + struct string_list *subsystem_content; + uint32_t crc; + char content[NETPLAY_HOST_LONGSTR_LEN]; + char subsystem[NETPLAY_HOST_LONGSTR_LEN]; + char extension[32]; + bool core_loaded; + } current; + struct string_list content_paths; + playlist_config_t playlist_config; + struct string_list *playlists; + struct string_list *extensions; + uint32_t crc; + char content[NETPLAY_HOST_LONGSTR_LEN]; + char subsystem[NETPLAY_HOST_LONGSTR_LEN]; + char core[PATH_MAX_LENGTH]; char hostname[512]; - char subsystem_name[512]; - char content_crc[PATH_MAX_LENGTH]; - char content_path[PATH_MAX_LENGTH]; - char core_name[PATH_MAX_LENGTH]; - char core_path[PATH_MAX_LENGTH]; - char core_extensions[PATH_MAX_LENGTH]; - bool found; - bool current; - bool contentless; -} netplay_crc_handle_t; +}; -static void netplay_crc_scan_callback(retro_task_t *task, - void *task_data, - void *user_data, const char *error) +static bool find_content_by_crc(playlist_config_t *playlist_config, + const struct string_list *playlists, uint32_t crc, + struct string_list *paths, bool first_only) { - netplay_crc_handle_t *state = (netplay_crc_handle_t*)task_data; - content_ctx_info_t content_info = {0}; + size_t i, j; + char crc_ident[16]; + playlist_t *playlist; + union string_list_elem_attr attr; + bool ret = false; - if (!state) - return; + snprintf(crc_ident, sizeof(crc_ident), "%08lX|crc", (unsigned long)crc); + attr.i = 0; - fflush(stdout); - - if (!string_is_empty(state->subsystem_name) && !string_is_equal(state->subsystem_name, "N/A")) + for (i = 0; i < playlists->size; i++) { - content_ctx_info_t content_info = {0}; - struct string_list *game_list = string_split(state->content_path, "|"); - unsigned i = 0; + playlist_config_set_path(playlist_config, playlists->elems[i].data); - task_push_load_new_core(state->core_path, NULL, - &content_info, CORE_TYPE_PLAIN, NULL, NULL); - content_clear_subsystem(); - if (!content_set_subsystem_by_name(state->subsystem_name)) - RARCH_LOG("[Lobby]: Subsystem not found in implementation\n"); + playlist = playlist_init(playlist_config); + if (!playlist) + continue; - for (i = 0; i < game_list->size; i++) - content_add_subsystem(game_list->elems[i].data); - task_push_load_subsystem_with_core( - NULL, &content_info, - CORE_TYPE_PLAIN, NULL, NULL); - string_list_free(game_list); - return; - } - - /* regular core with content file */ - if (!string_is_empty(state->core_path) && !string_is_empty(state->content_path) - && !state->contentless && !state->current) - { - struct retro_system_info *system = &runloop_state_get_ptr()->system.info; - - RARCH_LOG("[Lobby]: Loading core %s with content file %s\n", - state->core_path, state->content_path); - - command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, state->hostname); - - if (system && string_is_equal(system->library_name, state->core_name)) - task_push_load_content_with_core( - state->content_path, &content_info, - CORE_TYPE_PLAIN, NULL, NULL); - else + for (j = 0; j < playlist_get_size(playlist); j++) { - task_push_load_new_core(state->core_path, NULL, - &content_info, CORE_TYPE_PLAIN, NULL, NULL); - task_push_load_content_with_core( - state->content_path, &content_info, - CORE_TYPE_PLAIN, NULL, NULL); + const struct playlist_entry *entry = NULL; + + playlist_get_index(playlist, j, &entry); + if (!entry) + continue; + + if (string_is_equal(entry->crc32, crc_ident) && + !string_is_empty(entry->path)) + { + if (!string_list_append(paths, entry->path, attr)) + { + playlist_free(playlist); + return false; + } + + if (first_only) + { + playlist_free(playlist); + return true; + } + + ret = true; + } } - } - else - - /* contentless core */ - if (!string_is_empty(state->core_path) && !string_is_empty(state->content_path) - && state->contentless) - { - content_ctx_info_t content_info = {0}; - struct retro_system_info *system = &runloop_state_get_ptr()->system.info; - - RARCH_LOG("[Lobby]: Loading contentless core %s\n", state->core_path); - - command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, state->hostname); - - if (!string_is_equal(system->library_name, state->core_name)) - task_push_load_new_core(state->core_path, NULL, - &content_info, CORE_TYPE_PLAIN, NULL, NULL); - - task_push_start_current_core(&content_info); - } - /* regular core with current content */ - else if (!string_is_empty(state->core_path) && !string_is_empty(state->content_path) - && state->current) - { - RARCH_LOG("[Lobby]: Loading core %s with current content\n", state->core_path); - command_event(CMD_EVENT_NETPLAY_INIT_DIRECT, state->hostname); - command_event(CMD_EVENT_RESUME, NULL); - } - /* no match found */ - else - { - RARCH_LOG("[Lobby]: Couldn't find a suitable %s\n", - string_is_empty(state->content_path) ? "content file" : "core"); - runloop_msg_queue_push( - msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_LOAD_CONTENT_MANUALLY), - 1, 480, true, - NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + playlist_free(playlist); } - free(state); + return ret; } -static void begin_task(retro_task_t *task, const char *title) +static bool find_content_by_name(playlist_config_t *playlist_config, + const struct string_list *playlists, const struct string_list *names, + const struct string_list *extensions, struct string_list *paths, + bool with_extension) { - task_set_progress(task, 0); - task_free_title(task); - task_set_title(task, strdup(title)); - task_set_finished(task, false); -} + size_t i, j, k; + char buf[PATH_MAX_LENGTH]; + bool has_extensions; + union string_list_elem_attr attr; + bool err = false; + playlist_t *playlist = NULL; + playlist_t **playlist_ptrs = + (playlist_t**)calloc(playlists->size, sizeof(*playlist_ptrs)); -static void finish_task(retro_task_t *task, const char *title) -{ - task_set_progress(task, 100); - task_free_title(task); - task_set_title(task, strdup(title)); - task_set_finished(task, true); -} + if (!playlist_ptrs) + return false; -#define core_requires_content(state) string_is_not_equal(state->content_path, "N/A") + has_extensions = extensions && extensions->size > 0; + attr.i = 0; -/** - * Given a path to a content file, return the base name without the - * path or the file extension. - * - * e.g. /home/user/foo.rom => foo - */ -static void get_entry(char *entry, int len, const char *path) -{ - const char *buf = path_basename(path); - entry[0] = '\0'; + for (i = 0; i < names->size && !err; i++) + { + bool found = false; + const char *name = names->elems[i].data; - strlcpy(entry, buf, len); - path_remove_extension(entry); + for (j = 0; j < playlists->size; j++) + { + /* We do it like this + because we want names and paths to have the same order; + we also want to read and parse a playlist only the first time. */ + playlist = playlist_ptrs[j]; + if (!playlist) + { + playlist_config_set_path(playlist_config, + playlists->elems[j].data); + + playlist = playlist_init(playlist_config); + if (!playlist) + continue; + playlist_ptrs[j] = playlist; + } + + for (k = 0; k < playlist_get_size(playlist); k++) + { + const struct playlist_entry *entry = NULL; + + playlist_get_index(playlist, k, &entry); + if (!entry) + continue; + + /* If we don't have an extensions list, accept anything, + as long as the name matches. */ + if (has_extensions) + { + const char *extension = path_get_extension(entry->path); + + if (string_is_empty(extension) || !string_list_find_elem( + extensions, extension)) + continue; + } + + strlcpy(buf, path_basename(entry->path), sizeof(buf)); + if (!with_extension) + path_remove_extension(buf); + + if (string_is_equal_case_insensitive(buf, name)) + { + found = true; + + if (!string_list_append(paths, entry->path, attr)) + err = true; + + break; + } + } + + if (found) + break; + } + + if (!found) + break; + } + + for (j = 0; j < playlists->size; j++) + playlist_free(playlist_ptrs[j]); + free(playlist_ptrs); + + return !err && i == names->size; } /** * Execute a search for compatible content for netplay. * We prioritize a CRC match, if we have a CRC to match against. - * If we don't have a CRC, or if there's no CRC match found, fall - * back to a filename match and hope for the best. + * If we don't have a CRC, or if there's no CRC match found, + * fall back to a filename match and hope for the best. */ static void task_netplay_crc_scan_handler(retro_task_t *task) { - size_t i, j, k; - char entry[PATH_MAX_LENGTH]; - bool have_crc = false; - netplay_crc_handle_t *state = (netplay_crc_handle_t*)task->state; + struct netplay_crc_scan_state *state = + (struct netplay_crc_scan_state*)task->state; + struct netplay_crc_scan_data *data = + (struct netplay_crc_scan_data*)task->task_data; + const char *title = NULL; + struct string_list content_list = {0}; - begin_task(task, "Looking for compatible content..."); - - /* start by checking cases that don't require a search */ - - /* the core doesn't have any content to match, so fast-succeed */ - if (!core_requires_content(state)) + /* We really can't do much without the core's path. */ + if (string_is_empty(data->core)) { - state->found = true; - state->contentless = true; - task_set_data(task, state); - finish_task(task, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND)); - return; + title = + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NO_CORE); + goto finished; } - /* if this list is null, it means that RA failed to open the playlist directory */ - if (!state->lpl_list) + if (string_is_empty(data->content) || + string_is_equal_case_insensitive(data->content, "N/A")) { - finish_task(task, "Playlist directory not found"); - free(state); - return; + title = + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND); + state->state = data->current.core_loaded ? STATE_LOAD_CURRENT : + STATE_LOAD_CONTENTLESS; + goto finished; } - /* We opened the playlist directory, but there's nothing there. Nothing to do. */ - if (state->lpl_list->size == 0 && core_requires_content(state)) + if (data->current.core_loaded && data->crc > 0 && data->current.crc > 0) { - string_list_free(state->lpl_list); - finish_task(task, "There are no playlists available; cannot execute search"); - command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, state->hostname); - free(state); - return; - } + RARCH_LOG("[Lobby] Testing CRC matching for: %08lX\n", + (unsigned long)data->crc); + RARCH_LOG("[Lobby] Current content CRC: %08lX\n", + (unsigned long)data->current.crc); - have_crc = !string_is_equal(state->content_crc, "00000000|crc"); - - /* if content is already loaded and the lobby gave us a CRC, check the loaded content first */ - if (have_crc && content_get_crc() > 0) - { - char current[16]; - - RARCH_LOG("[Lobby]: Testing CRC matching for: %s\n", state->content_crc); - - snprintf(current, sizeof(current), "%08lX|crc", (unsigned long)content_get_crc()); - RARCH_LOG("[Lobby]: Current content CRC: %s\n", current); - - if (string_is_equal(current, state->content_crc)) + if (data->current.crc == data->crc) { - RARCH_LOG("[Lobby]: CRC match %s with currently loaded content\n", current); - strcpy_literal(state->content_path, "N/A"); - state->found = true; - state->current = true; - task_set_data(task, state); - finish_task(task, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND)); - string_list_free(state->lpl_list); - return; + RARCH_LOG("[Lobby] CRC match with currently loaded content.\n"); + + title = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND); + state->state = STATE_LOAD_CURRENT; + goto finished; } } - /* now let's do the search */ - if (string_is_empty(state->subsystem_name) || string_is_equal(state->subsystem_name, "N/A")) + if (string_is_empty(data->subsystem) || + string_is_equal_case_insensitive(data->subsystem, "N/A")) { - for (i = 0; i < state->lpl_list->size; i++) + if (data->current.core_loaded && data->extensions && + !string_is_empty(data->current.content) && + !string_is_empty(data->current.extension)) { - playlist_t *playlist = NULL; - unsigned playlist_size = 0; - const char *lpl_path = state->lpl_list->elems[i].data; - - /* skip files without .lpl file extension */ - if (!string_ends_with_size(lpl_path, ".lpl", - strlen(lpl_path), - STRLEN_CONST(".lpl"))) - continue; - - RARCH_LOG("[Lobby]: Searching playlist: %s\n", lpl_path); - playlist_config_set_path(&state->playlist_config, lpl_path); - playlist = playlist_init(&state->playlist_config); - playlist_size = playlist_get_size(playlist); - - for (j = 0; j < playlist_size; j++) + if (!data->current.subsystem_content || + !data->current.subsystem_content->size) { - const char *playlist_path = NULL; - const char *playlist_crc32 = NULL; - const struct playlist_entry *playlist_entry = NULL; - - playlist_get_index(playlist, j, &playlist_entry); - - playlist_path = playlist_entry->path; - playlist_crc32 = playlist_entry->crc32; - - if (have_crc && string_is_equal(playlist_crc32, state->content_crc)) + if (string_is_equal_case_insensitive( + data->current.content, data->content) && + string_list_find_elem( + data->extensions, data->current.extension)) { - RARCH_LOG("[Lobby]: CRC match %s\n", playlist_crc32); - strlcpy(state->content_path, playlist_path, sizeof(state->content_path)); - state->found = true; - task_set_data(task, state); - finish_task(task, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND)); - string_list_free(state->lpl_list); - playlist_free(playlist); - return; + RARCH_LOG("[Lobby] Filename match with currently loaded content.\n"); + + title = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND); + state->state = STATE_LOAD_CURRENT; + goto finished; } - - get_entry(entry, sizeof(entry), playlist_path); - - /* See if the filename is a match. The response depends on whether or not we are doing a CRC - * search. - * Otherwise, on match we complete the task and mark it as successful immediately. - */ - - if (!string_is_empty(entry) && - string_is_equal(entry, state->content_path) && - strstr(state->core_extensions, path_get_extension(playlist_path))) - { - RARCH_LOG("[Lobby]: Filename match %s\n", playlist_path); - - strlcpy(state->content_path, playlist_path, sizeof(state->content_path)); - state->found = true; - task_set_data(task, state); - finish_task(task, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND)); - string_list_free(state->lpl_list); - playlist_free(playlist); - return; - } - - task_set_progress(task, (int)(j / playlist_size * 100.0)); } + } - playlist_free(playlist); + if (!data->playlists || !data->playlists->size) + { + title = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NO_PLAYLISTS); + goto finished; + } + + if (!string_list_initialize(&data->content_paths)) + goto finished; + + /* We try a CRC match first. */ + if (data->crc > 0 && find_content_by_crc(&data->playlist_config, + data->playlists, data->crc, &data->content_paths, true)) + { + RARCH_LOG("[Lobby] Playlist CRC match.\n"); + + title = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND); + state->state = STATE_LOAD; + goto finished; + } + else + { + union string_list_elem_attr attr; + + if (!string_list_initialize(&content_list)) + goto finished; + + attr.i = 0; + if (!string_list_append(&content_list, data->content, attr)) + goto finished; + + /* Now we try a filename match as a last resort. */ + if (find_content_by_name(&data->playlist_config, data->playlists, + &content_list, data->extensions, &data->content_paths, false)) + { + RARCH_LOG("[Lobby] Playlist filename match.\n"); + + title = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND); + state->state = STATE_LOAD; + goto finished; + } } } else { - bool found[100]; - struct string_list *game_list = string_split(state->content_path, "|"); + if (!string_list_initialize(&content_list)) + goto finished; + if (!string_split_noalloc(&content_list, data->content, "|")) + goto finished; - for (i = 0; i < game_list->size; i++) + if (data->current.core_loaded && data->current.subsystem_content && + data->current.subsystem_content->size > 0 && + string_is_equal_case_insensitive(data->current.subsystem, + data->subsystem)) { - found[i] = false; - - for (j = 0; j < state->lpl_list->size && !found[i]; j++) + if (content_list.size == data->current.subsystem_content->size) { - playlist_t *playlist = NULL; - unsigned playlist_size = 0; - const char *lpl_path = state->lpl_list->elems[j].data; + size_t i; + bool loaded = true; - /* skip files without .lpl file extension */ - if (!string_ends_with_size(lpl_path, ".lpl", - strlen(lpl_path), - STRLEN_CONST(".lpl"))) - continue; - - RARCH_LOG("[Lobby]: Searching content %d/%d (%s) in playlist: %s\n", i + 1, game_list->size, game_list->elems[i].data, lpl_path); - playlist_config_set_path(&state->playlist_config, lpl_path); - playlist = playlist_init(&state->playlist_config); - playlist_size = playlist_get_size(playlist); - - for (k = 0; k < playlist_size && !found[i]; k++) + for (i = 0; i < content_list.size; i++) { - const struct playlist_entry *playlist_entry = NULL; + const char *local_content = path_basename( + data->current.subsystem_content->elems[i].data); + const char *remote_content = content_list.elems[i].data; - playlist_get_index(playlist, k, &playlist_entry); - - get_entry(entry, sizeof(entry), playlist_entry->path); - - if (!string_is_empty(entry) && - strstr(game_list->elems[i].data, entry) && - strstr(state->core_extensions, path_get_extension(playlist_entry->path))) + if (!string_is_equal_case_insensitive(local_content, + remote_content)) { - RARCH_LOG("[Lobby]: Filename match %s\n", playlist_entry->path); - - if (i == 0) - { - state->content_path[0] = '\0'; - strlcpy(state->content_path, playlist_entry->path, sizeof(state->content_path)); - } - else - { - strlcat(state->content_path, "|", sizeof(state->content_path)); - strlcat(state->content_path, playlist_entry->path, sizeof(state->content_path)); - } - - found[i] = true; + loaded = false; + break; } - - task_set_progress(task, (int)(j / playlist_size * 100.0)); } - playlist_free(playlist); + if (loaded) + { + RARCH_LOG("[Lobby] Subsystem match with currently loaded content.\n"); + + title = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND); + state->state = STATE_LOAD_CURRENT; + goto finished; + } } } - for (i = 0; i < game_list->size; i++) + if (!data->playlists || !data->playlists->size) { - state->found = true; - if (!found[i]) - { - state->found = false; - break; - } + title = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NO_PLAYLISTS); + goto finished; } - if (state->found) - { - RARCH_LOG("[Lobby]: Subsystem matching set found %s\n", state->content_path); - task_set_data(task, state); - finish_task(task, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND)); - } + if (!string_list_initialize(&data->content_paths)) + goto finished; - string_list_free(state->lpl_list); - string_list_free(game_list); - return; + /* Subsystems won't have a CRC. + Must always match by filename(s). */ + if (find_content_by_name(&data->playlist_config, data->playlists, + &content_list, data->extensions, &data->content_paths, true)) + { + RARCH_LOG("[Lobby] Playlist subsystem match.\n"); + + title = msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND); + state->state = STATE_LOAD_SUBSYSTEM; + goto finished; + } } - /* end of the line. no matches at all. */ - string_list_free(state->lpl_list); - finish_task(task, "Failed to locate matching content by either CRC or filename."); - command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, state->hostname); - free(state); + title = + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NOT_FOUND); + +finished: + string_list_deinitialize(&content_list); + + task_set_progress(task, 100); + + if (title) + { + task_free_title(task); + task_set_title(task, strdup(title)); + } + + task_set_finished(task, true); } -bool task_push_netplay_crc_scan(uint32_t crc, char* name, - const char *hostname, const char *core_name, const char *subsystem) +static void task_netplay_crc_scan_callback(retro_task_t *task, + void *task_data, void *user_data, const char *error) { - unsigned i; - union string_list_elem_attr attr; - struct string_list *lpl_list = NULL; - core_info_list_t *info = NULL; - settings_t *settings = config_get_ptr(); - retro_task_t *task = task_init(); - netplay_crc_handle_t *state = (netplay_crc_handle_t*) - calloc(1, sizeof(*state)); + struct netplay_crc_scan_state *state = + (struct netplay_crc_scan_state*)task->state; + struct netplay_crc_scan_data *data = + (struct netplay_crc_scan_data*)task_data; - if (!task || !state) - goto error; - - state->playlist_config.capacity = COLLECTION_SIZE; - state->playlist_config.old_format = settings->bools.playlist_use_old_format; - state->playlist_config.compress = settings->bools.playlist_compression; - state->playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match; - playlist_config_set_base_content_directory(&state->playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL); - - state->content_crc[0] = '\0'; - state->content_path[0] = '\0'; - state->hostname[0] = '\0'; - state->core_name[0] = '\0'; - state->subsystem_name[0] = '\0'; - attr.i = 0; - - snprintf(state->content_crc, - sizeof(state->content_crc), - "%08lX|crc", (unsigned long)crc); - - strlcpy(state->content_path, - name, sizeof(state->content_path)); - strlcpy(state->hostname, - hostname, sizeof(state->hostname)); - strlcpy(state->subsystem_name, - subsystem, sizeof(state->subsystem_name)); - strlcpy(state->core_name, - core_name, sizeof(state->core_name)); - - lpl_list = dir_list_new(settings->paths.directory_playlist, - NULL, true, true, true, false); - - if (!lpl_list) - goto error; - - state->lpl_list = lpl_list; - - string_list_append(state->lpl_list, - settings->paths.path_content_history, attr); - state->found = false; - - core_info_get_list(&info); - - for (i = 0; i < info->count; i++) + switch (state->state) { - /* check if the core name matches. - TO-DO :we could try to load the core too to check - if the version string matches too */ -#if 0 - printf("Info: %s State: %s", info->list[i].core_name, state->core_name); -#endif - if (string_is_equal(info->list[i].core_name, state->core_name)) - { - strlcpy(state->core_path, - info->list[i].path, sizeof(state->core_path)); - - if (string_is_not_equal(state->content_path, "N/A") && - !string_is_empty(info->list[i].supported_extensions)) + case STATE_LOAD: { - strlcpy(state->core_extensions, - info->list[i].supported_extensions, - sizeof(state->core_extensions)); + const char *content_path = + data->content_paths.elems[0].data; + content_ctx_info_t content_info = {0}; + + RARCH_LOG("[Lobby] Loading core '%s' with content file '%s'.\n", + data->core, content_path); + + if (!data->current.core_loaded) + task_push_load_new_core(data->core, + NULL, NULL, CORE_TYPE_PLAIN, NULL, NULL); + + command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, + data->hostname); + + task_push_load_content_with_core(content_path, + &content_info, CORE_TYPE_PLAIN, NULL, NULL); } break; + + case STATE_LOAD_SUBSYSTEM: + { + RARCH_LOG("[Lobby] Loading core '%s' with subsystem '%s'.\n", + data->core, data->subsystem); + + if (!data->current.core_loaded) + task_push_load_new_core(data->core, + NULL, NULL, CORE_TYPE_PLAIN, NULL, NULL); + + content_clear_subsystem(); + + if (content_set_subsystem_by_name(data->subsystem)) + { + size_t i; + content_ctx_info_t content_info = {0}; + + for (i = 0; i < data->content_paths.size; i++) + content_add_subsystem(data->content_paths.elems[i].data); + + command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, + data->hostname); + + task_push_load_subsystem_with_core(NULL, + &content_info, CORE_TYPE_PLAIN, NULL, NULL); + } + else + { + RARCH_ERR("[Lobby] Subsystem not found.\n"); + + /* Disable netplay if we don't have the subsystem. */ + netplay_driver_ctl(RARCH_NETPLAY_CTL_DISABLE, NULL); + } + } + break; + + case STATE_LOAD_CONTENTLESS: + { + content_ctx_info_t content_info = {0}; + + RARCH_LOG("[Lobby] Loading contentless core '%s'.\n", data->core); + + command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, + data->hostname); + + task_push_load_new_core(data->core, + NULL, NULL, CORE_TYPE_PLAIN, NULL, NULL); + + task_push_start_current_core(&content_info); + } + break; + + case STATE_LOAD_CURRENT: + { + RARCH_LOG("[Lobby] Loading core '%s' with current content.\n", + data->core); + + command_event(CMD_EVENT_NETPLAY_INIT_DIRECT, data->hostname); + command_event(CMD_EVENT_RESUME, NULL); + } + break; + + case STATE_NONE: + RARCH_WARN("[Lobby] Nothing to load.\n"); + /* fallthrough */ + default: + netplay_driver_ctl(RARCH_NETPLAY_CTL_DISABLE, NULL); + break; + } +} + +static void task_netplay_crc_scan_cleanup(retro_task_t *task) +{ + struct netplay_crc_scan_state *state = + (struct netplay_crc_scan_state*)task->state; + struct netplay_crc_scan_data *data = + (struct netplay_crc_scan_data*)task->task_data; + + string_list_deinitialize(&data->content_paths); + + string_list_free(data->current.subsystem_content); + string_list_free(data->playlists); + string_list_free(data->extensions); + + free(data); + + state->running = false; +} + +bool task_push_netplay_crc_scan(uint32_t crc, const char *content, + const char *subsystem, const char *core, const char *hostname) +{ + static struct netplay_crc_scan_state state = {0}; + size_t i; + struct netplay_crc_scan_data *data; + retro_task_t *task; + const char *pbasename, *pcontent, *psubsystem; + core_info_list_t *coreinfos = NULL; + settings_t *settings = config_get_ptr(); + struct retro_system_info *system = &runloop_state_get_ptr()->system.info; + + /* Do not run more than one CRC scan task at a time. */ + if (state.running) + return false; + + data = (struct netplay_crc_scan_data*)calloc(1, sizeof(*data)); + task = task_init(); + + if (!data || !task) + { + free(data); + free(task); + + /* Make sure we disable netplay on failure. */ + netplay_driver_ctl(RARCH_NETPLAY_CTL_DISABLE, NULL); + + return false; + } + + data->crc = crc; + + strlcpy(data->content, content, sizeof(data->content)); + strlcpy(data->subsystem, subsystem, sizeof(data->subsystem)); + strlcpy(data->hostname, hostname, sizeof(data->hostname)); + + core_info_get_list(&coreinfos); + for (i = 0; i < coreinfos->count; i++) + { + core_info_t *coreinfo = &coreinfos->list[i]; + + if (string_is_equal_case_insensitive(coreinfo->core_name, core)) + { + strlcpy(data->core, coreinfo->path, sizeof(data->core)); + + if (coreinfo->supported_extensions_list) + data->extensions = + string_list_clone(coreinfo->supported_extensions_list); + + break; } } - /* blocking means no other task can run while this one is running, - * which is the default */ - task->type = TASK_TYPE_BLOCKING; - task->state = state; - task->handler = task_netplay_crc_scan_handler; - task->callback = netplay_crc_scan_callback; - task->title = strdup("Looking for matching content..."); + data->playlist_config.capacity = COLLECTION_SIZE; + data->playlist_config.old_format = + settings->bools.playlist_use_old_format; + data->playlist_config.compress = + settings->bools.playlist_compression; + data->playlist_config.fuzzy_archive_match = + settings->bools.playlist_fuzzy_archive_match; + playlist_config_set_base_content_directory(&data->playlist_config, + settings->bools.playlist_portable_paths ? + settings->paths.directory_menu_content : NULL); + + data->playlists = dir_list_new(settings->paths.directory_playlist, "lpl", + false, true, false, false); + if (!data->playlists) + data->playlists = string_list_new(); + if (data->playlists) + { + union string_list_elem_attr attr; + + attr.i = RARCH_PLAIN_FILE; + string_list_append(data->playlists, + settings->paths.path_content_history, attr); + } + + data->current.crc = content_get_crc(); + + pbasename = path_get(RARCH_PATH_BASENAME); + if (!string_is_empty(pbasename)) + strlcpy(data->current.content, path_basename(pbasename), + sizeof(data->current.content)); + + pcontent = path_get(RARCH_PATH_CONTENT); + if (!string_is_empty(pcontent)) + strlcpy(data->current.extension, path_get_extension(pcontent), + sizeof(data->current.extension)); + + psubsystem = path_get(RARCH_PATH_SUBSYSTEM); + if (!string_is_empty(psubsystem)) + strlcpy(data->current.subsystem, psubsystem, + sizeof(data->current.subsystem)); + + if (path_get_subsystem_list()) + data->current.subsystem_content = + string_list_clone(path_get_subsystem_list()); + + data->current.core_loaded = + string_is_equal_case_insensitive(system->library_name, core); + + state.state = STATE_NONE; + state.running = true; + + task->handler = task_netplay_crc_scan_handler; + task->callback = task_netplay_crc_scan_callback; + task->cleanup = task_netplay_crc_scan_cleanup; + task->task_data = data; + task->state = &state; + task->title = strdup( + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_LOOK)); task_queue_push(task); return true; - -error: - if (state) - free(state); - if (task) - free(task); - +} +#else +bool task_push_netplay_crc_scan(uint32_t crc, const char *content, + const char *subsystem, const char *core, const char *hostname) +{ return false; } +#endif diff --git a/tasks/tasks_internal.h b/tasks/tasks_internal.h index 2380f070f6..9666df4a59 100644 --- a/tasks/tasks_internal.h +++ b/tasks/tasks_internal.h @@ -89,8 +89,8 @@ bool task_push_wifi_connect(retro_task_callback_t cb, void*); bool task_push_netplay_lan_scan(void (*cb)(const void*), unsigned timeout); -bool task_push_netplay_crc_scan(uint32_t crc, char* name, - const char *hostname, const char *corename, const char* subsystem); +bool task_push_netplay_crc_scan(uint32_t crc, const char *content, + const char *subsystem, const char *core, const char *hostname); bool task_push_netplay_nat_traversal(void *data, uint16_t port); bool task_push_netplay_nat_close(void *data);