/* RetroArch - A frontend for libretro. * Copyright (C) 2017 - Jean-André Santoni * Copyright (C) 2017 - Andrés Suárez * * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #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 { struct string_list *lpl_list; char content_crc[PATH_MAX_LENGTH]; char content_path[PATH_MAX_LENGTH]; char hostname[512]; 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(void *task_data, void *user_data, const char *error) { netplay_crc_handle_t *state = (netplay_crc_handle_t*)task_data; content_ctx_info_t content_info = {0}; rarch_system_info_t *info = NULL; if (!state) return; fflush(stdout); menu_driver_ctl(RARCH_MENU_CTL_SYSTEM_INFO_GET, &info); #ifdef HAVE_MENU /* regular core with content file */ if (!string_is_empty(state->core_path) && !string_is_empty(state->content_path) && !state->contentless && !state->current) { 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 (string_is_equal(info->info.library_name, state->core_name)) task_push_load_content_with_core_from_menu( state->content_path, &content_info, CORE_TYPE_PLAIN, NULL, NULL); else task_push_load_content_with_new_core_from_menu( state->core_path, state->content_path, &content_info, CORE_TYPE_PLAIN, NULL, NULL); } else #endif /* contentless core */ if (!string_is_empty(state->core_path) && !string_is_empty(state->content_path) && state->contentless) { RARCH_LOG("[lobby] loading contentless core %s\n", state->core_path); content_ctx_info_t content_info = {0}; command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, state->hostname); if (!string_is_equal(info->info.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); } /* no match found */ else { RARCH_LOG("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); } free(state); } static void task_netplay_crc_scan_handler(retro_task_t *task) { size_t i, j; netplay_crc_handle_t *state = (netplay_crc_handle_t*)task->state; char current[PATH_MAX_LENGTH]; task_set_progress(task, 0); task_free_title(task); task_set_title(task, strdup("Looking for compatible content...")); task_set_finished(task, false); if (!state->lpl_list) { task_set_progress(task, 100); task_free_title(task); task_set_title(task, strdup("Playlist directory not found")); task_set_finished(task, true); free(state); return; } if (state->lpl_list->size == 0 && string_is_not_equal_fast(state->content_path, "N/A", 3)) goto no_playlists; /* Core requires content */ if (string_is_not_equal_fast(state->content_path, "N/A", 3)) { /* CRC matching */ if (!string_is_equal(state->content_crc, "00000000|crc")) { RARCH_LOG("[lobby] testing CRC matching for: %s\n", state->content_crc); snprintf(current, sizeof(current), "%X|crc", content_get_crc()); RARCH_LOG("[lobby] current content crc: %s\n", current); if (string_is_equal(current, state->content_crc)) { RARCH_LOG("[lobby] CRC match %s with currently loaded content\n", current); strlcpy(state->content_path, "N/A", sizeof(state->content_path)); state->found = true; state->current = true; task_set_data(task, state); task_set_progress(task, 100); task_free_title(task); task_set_title(task, strdup(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND))); task_set_finished(task, true); string_list_free(state->lpl_list); return; } for (i = 0; i < state->lpl_list->size; i++) { playlist_t *playlist = NULL; const char *lpl_path = state->lpl_list->elems[i].data; if (!strstr(lpl_path, file_path_str(FILE_PATH_LPL_EXTENSION))) continue; playlist = playlist_init(lpl_path, 99999); for (j = 0; j < playlist->size; j++) { #if 0 RARCH_LOG("[lobby] playlist CRC: %s\n", playlist->entries[j].crc32); #endif if (string_is_equal(playlist->entries[j].crc32, state->content_crc)) { RARCH_LOG("[lobby] CRC match %s\n", playlist->entries[j].crc32); strlcpy(state->content_path, playlist->entries[j].path, sizeof(state->content_path)); state->found = true; task_set_data(task, state); task_set_progress(task, 100); task_free_title(task); task_set_title(task, strdup(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND))); task_set_finished(task, true); string_list_free(state->lpl_list); free(playlist); return; } task_set_progress(task, (int)(j/playlist->size*100.0)); } free(playlist); } /* CRC matching failed, goto filename matching */ if (!state->found) { RARCH_LOG("[lobby] CRC matching for: %s failed\n", state->content_crc); goto filename_matching; } } /* filename matching*/ else { filename_matching: RARCH_LOG("[lobby] testing filename matching for: %s\n", state->content_path); for (i = 0; i < state->lpl_list->size; i++) { playlist_t *playlist = NULL; const char *lpl_path = state->lpl_list->elems[i].data; if (!strstr(lpl_path, file_path_str(FILE_PATH_LPL_EXTENSION))) continue; playlist = playlist_init(lpl_path, 99999); for (j = 0; j < playlist->size; j++) { char entry[PATH_MAX_LENGTH]; const char* buf = path_basename(playlist->entries[j].path); entry[0] = '\0'; strlcpy(entry, buf, sizeof(entry)); path_remove_extension(entry); #if 0 RARCH_LOG("[lobby] playlist filename: %s\n", playlist->entries[j].path); #endif if ( !string_is_empty(entry) && string_is_equal(entry, state->content_path) && strstr(state->core_extensions, path_get_extension(playlist->entries[j].path))) { RARCH_LOG("[lobby] filename match %s\n", playlist->entries[j].path); strlcpy(state->content_path, playlist->entries[j].path, sizeof(state->content_path)); state->found = true; task_set_data(task, state); task_set_progress(task, 100); task_free_title(task); task_set_title(task, strdup(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND))); task_set_finished(task, true); string_list_free(state->lpl_list); free(playlist); return; } task_set_progress(task, (int)(j/playlist->size*100.0)); } free(playlist); } /* filename matching failed */ if (!state->found) RARCH_LOG("[lobby] filename matching for: %s failed\n", state->content_path); } } /* Lobby reports core doesn't need content */ else { state->found = true; state->contentless = true; task_set_data(task, state); task_set_progress(task, 100); task_free_title(task); task_set_title(task, strdup(msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND))); task_set_finished(task, true); return; } no_playlists: string_list_free(state->lpl_list); task_set_progress(task, 100); task_free_title(task); task_set_title(task, strdup("Content not found, try manual load or disconnect from host")); task_set_finished(task, true); command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, state->hostname); free(state); return; } bool task_push_netplay_crc_scan(uint32_t crc, char* name, const char *hostname, const char *core_name) { unsigned i; union string_list_elem_attr attr; core_info_list_t *info = NULL; settings_t *settings = config_get_ptr(); retro_task_t *task = (retro_task_t *)calloc(1, sizeof(*task)); netplay_crc_handle_t *state = (netplay_crc_handle_t*)calloc(1, sizeof(*state)); core_info_get_list(&info); if (!task || !state) goto error; state->content_crc[0] = '\0'; state->content_path[0] = '\0'; state->hostname[0] = '\0'; state->core_name[0] = '\0'; snprintf(state->content_crc, sizeof(state->content_crc), "%08X|crc", crc); strlcpy(state->content_path, name, sizeof(state->content_path)); strlcpy(state->hostname, hostname, sizeof(state->hostname)); strlcpy(state->core_name, core_name, sizeof(state->core_name)); state->lpl_list = dir_list_new(settings->paths.directory_playlist, NULL, true, true, true, false); attr.i = 0; string_list_append(state->lpl_list, settings->paths.path_content_history, attr); state->found = false; for (i=0; i < info->count; i++) { /* 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_fast(state->content_path, "N/A", 3) && !string_is_empty(info->list[i].supported_extensions)) { strlcpy(state->core_extensions, info->list[i].supported_extensions, sizeof(state->core_extensions)); } 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..."); task_queue_push(task); return true; error: if (state) free(state); if (task) free(task); return false; }