/* RetroArch - A frontend for libretro.
* Copyright (C) 2017 - Jean-André Santoni
* Copyright (C) 2017-2019 - 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
#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"
#ifndef HAVE_DYNAMIC
#include "../retroarch.h"
#include "../frontend/frontend_driver.h"
#endif
#include "task_content.h"
#include "tasks_internal.h"
#ifdef HAVE_NETWORKING
#include "../network/netplay/netplay.h"
enum
{
/* values */
STATE_NONE = 0,
STATE_LOAD,
STATE_LOAD_SUBSYSTEM,
STATE_LOAD_CONTENTLESS,
/* values mask */
STATE_MASK = 0xFF,
/* flags */
STATE_RELOAD = 0x100
};
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 content_path[PATH_MAX_LENGTH];
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];
};
static struct netplay_crc_scan_state scan_state = {0};
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)
{
size_t i, j;
char crc_ident[16];
playlist_t *playlist;
union string_list_elem_attr attr;
bool ret = false;
snprintf(crc_ident, sizeof(crc_ident), "%08lX|crc", (unsigned long)crc);
attr.i = 0;
for (i = 0; i < playlists->size; i++)
{
playlist_config_set_path(playlist_config, playlists->elems[i].data);
if (!(playlist = playlist_init(playlist_config)))
continue;
for (j = 0; j < playlist_get_size(playlist); j++)
{
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;
}
}
playlist_free(playlist);
}
return ret;
}
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)
{
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));
if (!playlist_ptrs)
return false;
has_extensions = extensions && extensions->size > 0;
attr.i = 0;
for (i = 0; i < names->size && !err; i++)
{
bool found = false;
const char *name = names->elems[i].data;
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. */
if (!(playlist = playlist_ptrs[j]))
{
playlist_config_set_path(playlist_config,
playlists->elems[j].data);
if (!(playlist = playlist_init(playlist_config)))
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;
}
if (!with_extension)
fill_pathname(buf, path_basename(entry->path),
"", sizeof(buf));
else
strlcpy(buf, path_basename(entry->path), sizeof(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.
*/
static void task_netplay_crc_scan_handler(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;
const char *title = NULL;
struct string_list content_list = {0};
if (state->state != STATE_NONE)
goto finished; /* We already have what we need. */
/* We really can't do much without the core's path. */
if (string_is_empty(data->core))
{
title =
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_NO_CORE);
goto finished;
}
if (string_is_empty(data->content) ||
string_is_equal_case_insensitive(data->content, "N/A"))
{
title =
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND);
state->state = STATE_LOAD_CONTENTLESS;
if (data->current.core_loaded)
state->state |= STATE_RELOAD;
goto finished;
}
if (data->current.core_loaded && data->crc > 0 && data->current.crc > 0)
{
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);
if (data->current.crc == data->crc)
{
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 | STATE_RELOAD;
goto finished;
}
}
if (string_is_empty(data->subsystem) ||
string_is_equal_case_insensitive(data->subsystem, "N/A"))
{
if (data->current.core_loaded && data->extensions &&
!string_is_empty(data->current.content) &&
!string_is_empty(data->current.extension))
{
if (!data->current.subsystem_content ||
!data->current.subsystem_content->size)
{
if (string_is_equal_case_insensitive(
data->current.content, data->content) &&
string_list_find_elem(
data->extensions, data->current.extension))
{
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 | STATE_RELOAD;
goto finished;
}
}
}
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
{
if (!string_list_initialize(&content_list))
goto finished;
if (!string_split_noalloc(&content_list, data->content, "|"))
goto finished;
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))
{
if (content_list.size == data->current.subsystem_content->size)
{
size_t i;
bool loaded = true;
for (i = 0; i < content_list.size; i++)
{
const char *local_content = path_basename(
data->current.subsystem_content->elems[i].data);
const char *remote_content = content_list.elems[i].data;
if (!string_is_equal_case_insensitive(local_content,
remote_content))
{
loaded = false;
break;
}
}
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_SUBSYSTEM | STATE_RELOAD;
goto finished;
}
}
}
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;
/* 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;
}
}
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_flags(task, RETRO_TASK_FLG_FINISHED, true);
}
#ifndef HAVE_DYNAMIC
static bool static_load(const char *core, const char *subsystem,
const void *content, const char *hostname)
{
#define ARG(arg) (void*)(arg)
netplay_driver_ctl(RARCH_NETPLAY_CTL_CLEAR_FORK_ARGS, NULL);
if (string_is_empty(hostname))
{
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ADD_FORK_ARG, ARG("-H")))
goto failure;
}
else
{
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ADD_FORK_ARG, ARG("-C")) ||
!netplay_driver_ctl(RARCH_NETPLAY_CTL_ADD_FORK_ARG, ARG(hostname)))
goto failure;
}
if (!string_is_empty(subsystem))
{
const struct string_list *subsystem_content =
(const struct string_list*)content;
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ADD_FORK_ARG, ARG("--subsystem")) ||
!netplay_driver_ctl(RARCH_NETPLAY_CTL_ADD_FORK_ARG, ARG(subsystem)))
goto failure;
if (subsystem_content && subsystem_content->size > 0)
{
size_t i;
for (i = 0; i < subsystem_content->size; i++)
{
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ADD_FORK_ARG,
ARG(subsystem_content->elems[i].data)))
goto failure;
}
}
#ifdef HAVE_MENU
else
{
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ADD_FORK_ARG, ARG("--menu")))
goto failure;
}
#endif
}
else
{
if (content)
{
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ADD_FORK_ARG, ARG(content)))
goto failure;
}
#ifdef HAVE_MENU
else
{
if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_ADD_FORK_ARG, ARG("--menu")))
goto failure;
}
#endif
}
if (!frontend_driver_set_fork(FRONTEND_FORK_CORE_WITH_ARGS))
goto failure;
path_set(RARCH_PATH_CORE, core);
retroarch_ctl(RARCH_CTL_SET_SHUTDOWN, NULL);
return true;
failure:
RARCH_ERR("[Lobby] Failed to fork RetroArch for netplay.\n");
netplay_driver_ctl(RARCH_NETPLAY_CTL_CLEAR_FORK_ARGS, NULL);
return false;
#undef ARG
}
#endif
static void task_netplay_crc_scan_callback(retro_task_t *task,
void *task_data, void *user_data, const char *error)
{
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;
switch (state->state & STATE_MASK)
{
case STATE_LOAD:
{
const char *content_path = (state->state & STATE_RELOAD) ?
data->current.content_path : data->content_paths.elems[0].data;
#if IOS
char tmp[PATH_MAX_LENGTH];
fill_pathname_expand_special(tmp, content_path, sizeof(tmp));
content_path = tmp;
#endif
#ifdef HAVE_DYNAMIC
content_ctx_info_t content_info = {0};
if (data->current.core_loaded)
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
RARCH_LOG("[Lobby] Loading core '%s' with content file '%s'.\n",
data->core, content_path);
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
if (string_is_empty(data->hostname))
{
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_SERVER, NULL);
}
else
{
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL);
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_load_content_with_core(content_path,
&content_info, CORE_TYPE_PLAIN, NULL, NULL);
#else
static_load(data->core, NULL, content_path, data->hostname);
#endif
}
break;
case STATE_LOAD_SUBSYSTEM:
{
const char *subsystem;
struct string_list *subsystem_content;
if (state->state & STATE_RELOAD)
{
subsystem = data->current.subsystem;
subsystem_content = data->current.subsystem_content;
}
else
{
subsystem = data->subsystem;
subsystem_content = &data->content_paths;
}
#ifdef HAVE_DYNAMIC
if (data->current.core_loaded)
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
RARCH_LOG("[Lobby] Loading core '%s' with subsystem '%s'.\n",
data->core, subsystem);
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
if (string_is_empty(data->hostname))
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_SERVER, NULL);
else
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL);
task_push_load_new_core(data->core,
NULL, NULL, CORE_TYPE_PLAIN, NULL, NULL);
content_clear_subsystem();
if (content_set_subsystem_by_name(subsystem))
{
size_t i;
content_ctx_info_t content_info = {0};
for (i = 0; i < subsystem_content->size; i++)
content_add_subsystem(subsystem_content->elems[i].data);
if (!string_is_empty(data->hostname))
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);
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
}
#else
static_load(data->core, subsystem, subsystem_content,
data->hostname);
#endif
}
break;
case STATE_LOAD_CONTENTLESS:
{
#ifdef HAVE_DYNAMIC
content_ctx_info_t content_info = {0};
if (data->current.core_loaded)
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
RARCH_LOG("[Lobby] Loading contentless core '%s'.\n", data->core);
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
if (string_is_empty(data->hostname))
{
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_SERVER, NULL);
}
else
{
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL);
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);
#else
static_load(data->core, NULL, NULL, data->hostname);
#endif
}
break;
case STATE_NONE:
{
if (data->current.core_loaded && (state->state & STATE_RELOAD))
{
#ifdef HAVE_DYNAMIC
command_event(CMD_EVENT_UNLOAD_CORE, NULL);
command_event(CMD_EVENT_NETPLAY_DEINIT, NULL);
if (string_is_empty(data->hostname))
{
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_SERVER, NULL);
}
else
{
netplay_driver_ctl(RARCH_NETPLAY_CTL_ENABLE_CLIENT, NULL);
command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED,
data->hostname);
}
task_push_load_new_core(data->core,
NULL, NULL, CORE_TYPE_PLAIN, NULL, NULL);
if (!string_is_empty(data->current.subsystem))
{
content_clear_subsystem();
content_set_subsystem_by_name(data->current.subsystem);
}
runloop_msg_queue_push(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_START_WHEN_LOADED),
1, 480, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
#else
runloop_msg_queue_push(
msg_hash_to_str(MSG_NETPLAY_NEED_CONTENT_LOADED),
1, 480, true, NULL,
MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
#endif
}
else
RARCH_WARN("[Lobby] Nothing to load.\n");
}
break;
default:
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)
{
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 *sysinfo = &runloop_state_get_ptr()->system.info;
/* Do not run more than one CRC scan task at a time. */
if (scan_state.running)
return false;
data = (struct netplay_crc_scan_data*)calloc(1, sizeof(*data));
task = task_init();
if (!data || !task)
{
free(data);
free(task);
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;
}
}
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.content_path, pcontent,
sizeof(data->current.content_path));
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(sysinfo->library_name, core);
scan_state.state = STATE_NONE;
scan_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 = &scan_state;
task->title = strdup(
msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_LOOK));
task_queue_push(task);
return true;
}
bool task_push_netplay_content_reload(const char *hostname)
{
struct netplay_crc_scan_data *data;
retro_task_t *task;
const char *pcore;
uint8_t flags;
/* Do not run more than one CRC scan task at a time. */
if (scan_state.running)
return false;
pcore = path_get(RARCH_PATH_CORE);
if (string_is_empty(pcore) || string_is_equal(pcore, "builtin"))
return false; /* Nothing to reload. */
data = (struct netplay_crc_scan_data*)calloc(1, sizeof(*data));
task = task_init();
if (!data || !task)
{
free(data);
free(task);
return false;
}
scan_state.state = STATE_RELOAD;
strlcpy(data->core, pcore, sizeof(data->core));
/* Hostname being NULL indicates this is a host. */
if (hostname)
strlcpy(data->hostname, hostname, sizeof(data->hostname));
flags = content_get_flags();
if (flags & CONTENT_ST_FLAG_IS_INITED)
{
const char *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());
if (data->current.subsystem_content &&
data->current.subsystem_content->size > 0)
scan_state.state |= STATE_LOAD_SUBSYSTEM;
}
}
else if (!path_is_empty(RARCH_PATH_BASENAME))
{
const char *pcontent = path_get(RARCH_PATH_CONTENT);
if (!string_is_empty(pcontent))
{
strlcpy(data->current.content_path, pcontent,
sizeof(data->current.content_path));
scan_state.state |= STATE_LOAD;
}
}
}
if ((flags & CONTENT_ST_FLAG_CORE_DOES_NOT_NEED_CONTENT) &&
!(scan_state.state & (STATE_LOAD|STATE_LOAD_SUBSYSTEM)))
scan_state.state |= STATE_LOAD_CONTENTLESS;
data->current.core_loaded = true;
scan_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 = &scan_state;
task_queue_push(task);
return true;
}
#else
bool task_push_netplay_crc_scan(uint32_t crc, const char *content,
const char *subsystem, const char *core, const char *hostname)
{
return false;
}
bool task_push_netplay_content_reload(const char *hostname)
{
return false;
}
#endif