1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2017 - Jean-André Santoni
3  *  Copyright (C) 2017-2019 - Andrés Suárez
4  *
5  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
6  *  of the GNU General Public License as published by the Free Software Found-
7  *  ation, either version 3 of the License, or (at your option) any later version.
8  *
9  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11  *  PURPOSE.  See the GNU General Public License for more details.
12  *
13  *  You should have received a copy of the GNU General Public License along with RetroArch.
14  *  If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include <string.h>
18 #include <errno.h>
19 #include <file/nbio.h>
20 #include <formats/image.h>
21 #include <compat/strl.h>
22 #include <retro_assert.h>
23 #include <retro_miscellaneous.h>
24 #include <lists/string_list.h>
25 #include <lrc_hash.h>
26 #include <string/stdstring.h>
27 #include <file/file_path.h>
28 #include <lists/dir_list.h>
29 #include <queues/task_queue.h>
30 
31 #include "task_content.h"
32 #include "tasks_internal.h"
33 #include "../file_path_special.h"
34 #include "../verbosity.h"
35 #include "../configuration.h"
36 #include "../playlist.h"
37 #include "../command.h"
38 #include "../core_info.h"
39 #include "../../retroarch.h"
40 #include "../../menu/menu_driver.h"
41 
42 typedef struct
43 {
44    struct string_list *lpl_list;
45    playlist_config_t playlist_config; /* size_t alignment */
46    char hostname[512];
47    char subsystem_name[512];
48    char content_crc[PATH_MAX_LENGTH];
49    char content_path[PATH_MAX_LENGTH];
50    char core_name[PATH_MAX_LENGTH];
51    char core_path[PATH_MAX_LENGTH];
52    char core_extensions[PATH_MAX_LENGTH];
53    bool found;
54    bool current;
55    bool contentless;
56 } netplay_crc_handle_t;
57 
netplay_crc_scan_callback(retro_task_t * task,void * task_data,void * user_data,const char * error)58 static void netplay_crc_scan_callback(retro_task_t *task,
59       void *task_data,
60       void *user_data, const char *error)
61 {
62    netplay_crc_handle_t *state     = (netplay_crc_handle_t*)task_data;
63    content_ctx_info_t content_info = {0};
64 
65    if (!state)
66       return;
67 
68    fflush(stdout);
69 
70    if (!string_is_empty(state->subsystem_name) && !string_is_equal(state->subsystem_name, "N/A"))
71    {
72       content_ctx_info_t content_info  = {0};
73       struct string_list *game_list = string_split(state->content_path, "|");
74       unsigned i = 0;
75 
76       task_push_load_new_core(state->core_path, NULL,
77             &content_info, CORE_TYPE_PLAIN, NULL, NULL);
78       content_clear_subsystem();
79       if (!content_set_subsystem_by_name(state->subsystem_name))
80          RARCH_LOG("[Lobby]: Subsystem not found in implementation\n");
81 
82       for (i = 0; i < game_list->size; i++)
83          content_add_subsystem(game_list->elems[i].data);
84       task_push_load_subsystem_with_core_from_menu(
85          NULL, &content_info,
86          CORE_TYPE_PLAIN, NULL, NULL);
87       string_list_free(game_list);
88       return;
89    }
90 
91    /* regular core with content file */
92    if (!string_is_empty(state->core_path) && !string_is_empty(state->content_path)
93       && !state->contentless && !state->current)
94    {
95       struct retro_system_info *system = runloop_get_libretro_system_info();
96 
97       RARCH_LOG("[Lobby]: Loading core %s with content file %s\n",
98          state->core_path, state->content_path);
99 
100       command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, state->hostname);
101 
102       if (system && string_is_equal(system->library_name, state->core_name))
103          task_push_load_content_with_core_from_menu(
104                state->content_path, &content_info,
105                CORE_TYPE_PLAIN, NULL, NULL);
106       else
107       {
108          task_push_load_new_core(state->core_path, NULL,
109                &content_info, CORE_TYPE_PLAIN, NULL, NULL);
110          task_push_load_content_with_core_from_menu(
111                state->content_path, &content_info,
112                CORE_TYPE_PLAIN, NULL, NULL);
113       }
114 
115    }
116    else
117 
118    /* contentless core */
119    if (!string_is_empty(state->core_path) && !string_is_empty(state->content_path)
120       && state->contentless)
121    {
122       content_ctx_info_t content_info  = {0};
123       struct retro_system_info *system = runloop_get_libretro_system_info();
124 
125       RARCH_LOG("[Lobby]: Loading contentless core %s\n", state->core_path);
126 
127       command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, state->hostname);
128 
129       if (!string_is_equal(system->library_name, state->core_name))
130          task_push_load_new_core(state->core_path, NULL,
131                &content_info, CORE_TYPE_PLAIN, NULL, NULL);
132 
133       task_push_start_current_core(&content_info);
134    }
135    /* regular core with current content */
136    else if (!string_is_empty(state->core_path) && !string_is_empty(state->content_path)
137       && state->current)
138    {
139       RARCH_LOG("[Lobby]: Loading core %s with current content\n", state->core_path);
140       command_event(CMD_EVENT_NETPLAY_INIT_DIRECT, state->hostname);
141       command_event(CMD_EVENT_RESUME, NULL);
142    }
143    /* no match found */
144    else
145    {
146       RARCH_LOG("[Lobby]: Couldn't find a suitable %s\n",
147          string_is_empty(state->content_path) ? "content file" : "core");
148       runloop_msg_queue_push(
149             msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_LOAD_CONTENT_MANUALLY),
150             1, 480, true,
151             NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
152    }
153 
154    free(state);
155 }
156 
begin_task(retro_task_t * task,const char * title)157 static void begin_task(retro_task_t *task, const char *title)
158 {
159    task_set_progress(task, 0);
160    task_free_title(task);
161    task_set_title(task, strdup(title));
162    task_set_finished(task, false);
163 }
164 
finish_task(retro_task_t * task,const char * title)165 static void finish_task(retro_task_t *task, const char *title)
166 {
167    task_set_progress(task, 100);
168    task_free_title(task);
169    task_set_title(task, strdup(title));
170    task_set_finished(task, true);
171 }
172 
173 #define core_requires_content(state) string_is_not_equal(state->content_path, "N/A")
174 
175 /**
176  * Given a path to a content file, return the base name without the
177  * path or the file extension.
178  *
179  * e.g. /home/user/foo.rom => foo
180  */
get_entry(char * entry,int len,const char * path)181 static void get_entry(char *entry, int len, const char *path)
182 {
183    const char *buf = path_basename(path);
184    entry[0]        = '\0';
185 
186    strlcpy(entry, buf, len);
187    path_remove_extension(entry);
188 }
189 
190 /**
191  * Execute a search for compatible content for netplay.
192  * We prioritize a CRC match, if we have a CRC to match against.
193  * If we don't have a CRC, or if there's no CRC match found, fall
194  * back to a filename match and hope for the best.
195  */
task_netplay_crc_scan_handler(retro_task_t * task)196 static void task_netplay_crc_scan_handler(retro_task_t *task)
197 {
198    size_t i, j, k;
199    char entry[PATH_MAX_LENGTH];
200    bool have_crc               = false;
201    netplay_crc_handle_t *state = (netplay_crc_handle_t*)task->state;
202 
203    begin_task(task, "Looking for compatible content...");
204 
205    /* start by checking cases that don't require a search */
206 
207    /* the core doesn't have any content to match, so fast-succeed */
208    if (!core_requires_content(state))
209    {
210       state->found = true;
211       state->contentless = true;
212       task_set_data(task, state);
213       finish_task(task, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND));
214       return;
215    }
216 
217    /* if this list is null, it means that RA failed to open the playlist directory */
218    if (!state->lpl_list)
219    {
220       finish_task(task, "Playlist directory not found");
221       free(state);
222       return;
223    }
224 
225    /* We opened the playlist directory, but there's nothing there. Nothing to do. */
226    if (state->lpl_list->size == 0 && core_requires_content(state))
227    {
228       string_list_free(state->lpl_list);
229       finish_task(task, "There are no playlists available; cannot execute search");
230       command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, state->hostname);
231       free(state);
232       return;
233    }
234 
235    have_crc = !string_is_equal(state->content_crc, "00000000|crc");
236 
237    /* if content is already loaded and the lobby gave us a CRC, check the loaded content first */
238    if (have_crc && content_get_crc() > 0)
239    {
240       char current[PATH_MAX_LENGTH];
241 
242       RARCH_LOG("[Lobby]: Testing CRC matching for: %s\n", state->content_crc);
243 
244       snprintf(current, sizeof(current), "%X|crc", content_get_crc());
245       RARCH_LOG("[Lobby]: Current content CRC: %s\n", current);
246 
247       if (string_is_equal(current, state->content_crc))
248       {
249          RARCH_LOG("[Lobby]: CRC match %s with currently loaded content\n", current);
250          strcpy_literal(state->content_path, "N/A");
251          state->found   = true;
252          state->current = true;
253          task_set_data(task, state);
254          finish_task(task, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND));
255          string_list_free(state->lpl_list);
256          return;
257       }
258    }
259 
260    /* now let's do the search */
261    if (string_is_empty(state->subsystem_name) || string_is_equal(state->subsystem_name, "N/A"))
262    {
263       for (i = 0; i < state->lpl_list->size; i++)
264       {
265          playlist_t *playlist   = NULL;
266          unsigned playlist_size = 0;
267          const char *lpl_path   = state->lpl_list->elems[i].data;
268 
269          /* skip files without .lpl file extension */
270          if (!string_ends_with_size(lpl_path, ".lpl",
271                   strlen(lpl_path),
272                   STRLEN_CONST(".lpl")))
273             continue;
274 
275          RARCH_LOG("[Lobby]: Searching playlist: %s\n", lpl_path);
276          playlist_config_set_path(&state->playlist_config, lpl_path);
277          playlist      = playlist_init(&state->playlist_config);
278          playlist_size = playlist_get_size(playlist);
279 
280          for (j = 0; j < playlist_size; j++)
281          {
282             const char *playlist_path     = NULL;
283             const char *playlist_crc32    = NULL;
284             const struct playlist_entry *playlist_entry = NULL;
285 
286             playlist_get_index(playlist, j, &playlist_entry);
287 
288             playlist_path = playlist_entry->path;
289             playlist_crc32 = playlist_entry->crc32;
290 
291             if (have_crc && string_is_equal(playlist_crc32, state->content_crc))
292             {
293                RARCH_LOG("[Lobby]: CRC match %s\n", playlist_crc32);
294                strlcpy(state->content_path, playlist_path, sizeof(state->content_path));
295                state->found = true;
296                task_set_data(task, state);
297                finish_task(task, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND));
298                string_list_free(state->lpl_list);
299                playlist_free(playlist);
300                return;
301             }
302 
303             get_entry(entry, sizeof(entry), playlist_path);
304 
305             /* See if the filename is a match. The response depends on whether or not we are doing a CRC
306             * search.
307             * Otherwise, on match we complete the task and mark it as successful immediately.
308             */
309 
310             if (!string_is_empty(entry) &&
311                string_is_equal(entry, state->content_path) &&
312                strstr(state->core_extensions, path_get_extension(playlist_path)))
313             {
314                RARCH_LOG("[Lobby]: Filename match %s\n", playlist_path);
315 
316                strlcpy(state->content_path, playlist_path, sizeof(state->content_path));
317                state->found = true;
318                task_set_data(task, state);
319                finish_task(task, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND));
320                string_list_free(state->lpl_list);
321                playlist_free(playlist);
322                return;
323             }
324 
325             task_set_progress(task, (int)(j / playlist_size * 100.0));
326          }
327 
328          playlist_free(playlist);
329       }
330    }
331    else
332    {
333       bool found[100];
334       struct string_list *game_list = string_split(state->content_path, "|");
335 
336       for (i = 0; i < game_list->size; i++)
337       {
338          found[i] = false;
339 
340          for (j = 0; j < state->lpl_list->size && !found[i]; j++)
341          {
342             playlist_t *playlist   = NULL;
343             unsigned playlist_size = 0;
344             const char *lpl_path   = state->lpl_list->elems[j].data;
345 
346             /* skip files without .lpl file extension */
347             if (!string_ends_with_size(lpl_path, ".lpl",
348                      strlen(lpl_path),
349                      STRLEN_CONST(".lpl")))
350                continue;
351 
352             RARCH_LOG("[Lobby]: Searching content %d/%d (%s) in playlist: %s\n", i + 1, game_list->size, game_list->elems[i].data, lpl_path);
353             playlist_config_set_path(&state->playlist_config, lpl_path);
354             playlist      = playlist_init(&state->playlist_config);
355             playlist_size = playlist_get_size(playlist);
356 
357             for (k = 0; k < playlist_size && !found[i]; k++)
358             {
359                const struct playlist_entry *playlist_entry = NULL;
360 
361                playlist_get_index(playlist, k, &playlist_entry);
362 
363                get_entry(entry, sizeof(entry), playlist_entry->path);
364 
365                if (!string_is_empty(entry) &&
366                   strstr(game_list->elems[i].data, entry) &&
367                   strstr(state->core_extensions, path_get_extension(playlist_entry->path)))
368                {
369                   RARCH_LOG("[Lobby]: Filename match %s\n", playlist_entry->path);
370 
371                   if (i == 0)
372                   {
373                      state->content_path[0] = '\0';
374                      strlcpy(state->content_path, playlist_entry->path, sizeof(state->content_path));
375                   }
376                   else
377                   {
378                      strlcat(state->content_path, "|", sizeof(state->content_path));
379                      strlcat(state->content_path, playlist_entry->path, sizeof(state->content_path));
380                   }
381 
382                   found[i] = true;
383                }
384 
385                task_set_progress(task, (int)(j / playlist_size * 100.0));
386             }
387 
388             playlist_free(playlist);
389          }
390       }
391 
392       for (i = 0; i < game_list->size; i++)
393       {
394          state->found = true;
395          if (!found[i])
396          {
397             state->found = false;
398             break;
399          }
400       }
401 
402       if (state->found)
403       {
404          RARCH_LOG("[Lobby]: Subsystem matching set found %s\n", state->content_path);
405          task_set_data(task, state);
406          finish_task(task, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_COMPAT_CONTENT_FOUND));
407       }
408 
409       string_list_free(state->lpl_list);
410       string_list_free(game_list);
411       return;
412    }
413 
414    /* end of the line. no matches at all. */
415    string_list_free(state->lpl_list);
416    finish_task(task, "Failed to locate matching content by either CRC or filename.");
417    command_event(CMD_EVENT_NETPLAY_INIT_DIRECT_DEFERRED, state->hostname);
418    free(state);
419 }
420 
task_push_netplay_crc_scan(uint32_t crc,char * name,const char * hostname,const char * core_name,const char * subsystem)421 bool task_push_netplay_crc_scan(uint32_t crc, char* name,
422       const char *hostname, const char *core_name, const char *subsystem)
423 {
424    unsigned i;
425    union string_list_elem_attr attr;
426    struct string_list *lpl_list = NULL;
427    core_info_list_t *info       = NULL;
428    settings_t        *settings  = config_get_ptr();
429    retro_task_t          *task  = task_init();
430    netplay_crc_handle_t *state  = (netplay_crc_handle_t*)
431       calloc(1, sizeof(*state));
432 
433    if (!task || !state)
434       goto error;
435 
436    state->playlist_config.capacity            = COLLECTION_SIZE;
437    state->playlist_config.old_format          = settings->bools.playlist_use_old_format;
438    state->playlist_config.compress            = settings->bools.playlist_compression;
439    state->playlist_config.fuzzy_archive_match = settings->bools.playlist_fuzzy_archive_match;
440    playlist_config_set_base_content_directory(&state->playlist_config, settings->bools.playlist_portable_paths ? settings->paths.directory_menu_content : NULL);
441 
442    state->content_crc[0]    = '\0';
443    state->content_path[0]   = '\0';
444    state->hostname[0]       = '\0';
445    state->core_name[0]      = '\0';
446    state->subsystem_name[0] = '\0';
447    attr.i = 0;
448 
449    snprintf(state->content_crc,
450          sizeof(state->content_crc),
451          "%08X|crc", crc);
452 
453    strlcpy(state->content_path,
454          name, sizeof(state->content_path));
455    strlcpy(state->hostname,
456          hostname, sizeof(state->hostname));
457    strlcpy(state->subsystem_name,
458          subsystem, sizeof(state->subsystem_name));
459    strlcpy(state->core_name,
460          core_name, sizeof(state->core_name));
461 
462    lpl_list = dir_list_new(settings->paths.directory_playlist,
463          NULL, true, true, true, false);
464 
465    if (!lpl_list)
466       goto error;
467 
468    state->lpl_list = lpl_list;
469 
470    string_list_append(state->lpl_list,
471          settings->paths.path_content_history, attr);
472    state->found = false;
473 
474    core_info_get_list(&info);
475 
476    for (i = 0; i < info->count; i++)
477    {
478       /* check if the core name matches.
479          TO-DO :we could try to load the core too to check
480          if the version string matches too */
481 #if 0
482       printf("Info: %s State: %s", info->list[i].core_name, state->core_name);
483 #endif
484       if (string_is_equal(info->list[i].core_name, state->core_name))
485       {
486          strlcpy(state->core_path,
487                info->list[i].path, sizeof(state->core_path));
488 
489          if (string_is_not_equal(state->content_path, "N/A") &&
490             !string_is_empty(info->list[i].supported_extensions))
491          {
492             strlcpy(state->core_extensions,
493                   info->list[i].supported_extensions,
494                   sizeof(state->core_extensions));
495          }
496          break;
497       }
498    }
499 
500    /* blocking means no other task can run while this one is running,
501     * which is the default */
502    task->type           = TASK_TYPE_BLOCKING;
503    task->state          = state;
504    task->handler        = task_netplay_crc_scan_handler;
505    task->callback       = netplay_crc_scan_callback;
506    task->title          = strdup("Looking for matching content...");
507 
508    task_queue_push(task);
509 
510    return true;
511 
512 error:
513    if (state)
514       free(state);
515    if (task)
516       free(task);
517 
518    return false;
519 }
520