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