1 /*  RetroArch - A frontend for libretro.
2  *  Copyright (C) 2011-2017 - Daniel De Matteis
3  *  Copyright (C) 2014-2017 - Jean-André Santoni
4  *  Copyright (C) 2016-2019 - Brad Parker
5  *
6  *  RetroArch is free software: you can redistribute it and/or modify it under the terms
7  *  of the GNU General Public License as published by the Free Software Found-
8  *  ation, either version 3 of the License, or (at your option) any later version.
9  *
10  *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12  *  PURPOSE.  See the GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License along with RetroArch.
15  *  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <errno.h>
19 #include <ctype.h>
20 #include <string.h>
21 
22 #include <retro_miscellaneous.h>
23 #include <retro_endianness.h>
24 #include <compat/strcasestr.h>
25 #include <compat/strl.h>
26 #include <file/file_path.h>
27 #include <retro_endianness.h>
28 #include <streams/file_stream.h>
29 #include <streams/interface_stream.h>
30 #include <string/stdstring.h>
31 
32 #ifdef HAVE_CONFIG_H
33 #include "../config.h"
34 #endif
35 
36 #include "../database_info.h"
37 
38 #include "tasks_internal.h"
39 
40 #include "../list_special.h"
41 #include "../msg_hash.h"
42 #include "../verbosity.h"
43 
44 #define MAGIC_LEN       17
45 #define MAX_TOKEN_LEN   255
46 
47 #ifdef MSB_FIRST
48 #define MODETEST_VAL    0x00ffffff
49 #else
50 #define MODETEST_VAL    0xffffff00
51 #endif
52 
53 /* TODO/FIXME - reorder this according to CODING-GUIDELINES
54  * and make sure LUT table below conforms */
55 struct magic_entry
56 {
57    int32_t offset;
58    const char *system_name;
59    const char *magic;
60 };
61 
62 static struct magic_entry MAGIC_NUMBERS[] = {
63    { 0,        "ps1",    "\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x02\x00\x02\x00"},
64    { 0x838840, "pcecd",  "\x82\xb1\x82\xcc\x83\x76\x83\x8d\x83\x4f\x83\x89\x83\x80\x82\xcc\x92"},
65    { 0,        "scd",    "\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x02\x00\x01\x53"},
66    { 0x000400, "gc",     "\x00\x01\xC7\x04\x80\x28\x00\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
67    { 0,        NULL,     NULL}
68 };
69 
get_token(intfstream_t * fd,char * token,uint64_t max_len)70 static int64_t get_token(intfstream_t *fd, char *token, uint64_t max_len)
71 {
72    char *c       = token;
73    int64_t len   = 0;
74    int in_string = 0;
75 
76    for (;;)
77    {
78       int64_t rv = (int64_t)intfstream_read(fd, c, 1);
79       if (rv == 0)
80          return 0;
81 
82       if (rv < 1)
83       {
84          switch (errno)
85          {
86             case EINTR:
87             case EAGAIN:
88                continue;
89             default:
90                return -errno;
91          }
92       }
93 
94       switch (*c)
95       {
96          case ' ':
97          case '\t':
98          case '\r':
99          case '\n':
100             if (c == token)
101                continue;
102 
103             if (!in_string)
104             {
105                *c = '\0';
106                return len;
107             }
108             break;
109          case '\"':
110             if (c == token)
111             {
112                in_string = 1;
113                continue;
114             }
115 
116             *c = '\0';
117             return len;
118       }
119 
120       len++;
121       c++;
122       if (len == (int64_t)max_len)
123       {
124          *c = '\0';
125          return len;
126       }
127    }
128 }
129 
detect_ps1_game_sub(intfstream_t * fp,char * game_id,int sub_channel_mixed)130 static int detect_ps1_game_sub(intfstream_t *fp,
131       char *game_id, int sub_channel_mixed)
132 {
133    uint8_t* tmp;
134    uint8_t* boot_file;
135    int skip, frame_size, cd_sector;
136    uint8_t buffer[2048 * 2];
137    int is_mode1 = 0;
138 
139    buffer[0]    = '\0';
140 
141    if (intfstream_seek(fp, 0, SEEK_END) == -1)
142       return 0;
143 
144    if (!sub_channel_mixed)
145    {
146       if (!(intfstream_tell(fp) & 0x7FF))
147       {
148          unsigned int mode_test = 0;
149 
150          if (intfstream_seek(fp, 0, SEEK_SET) == -1)
151             return 0;
152 
153          intfstream_read(fp, &mode_test, 4);
154          if (mode_test != MODETEST_VAL)
155             is_mode1 = 1;
156       }
157    }
158 
159    skip       = is_mode1? 0: 24;
160    frame_size = sub_channel_mixed? 2448: is_mode1? 2048: 2352;
161 
162    if (intfstream_seek(fp, 156 + skip + 16 * frame_size, SEEK_SET) == -1)
163       return 0;
164 
165    intfstream_read(fp, buffer, 6);
166 
167    cd_sector = buffer[2] | (buffer[3] << 8) | (buffer[4] << 16);
168 
169    if (intfstream_seek(fp, skip + cd_sector * frame_size, SEEK_SET) == -1)
170       return 0;
171    intfstream_read(fp, buffer, 2048 * 2);
172 
173    tmp = buffer;
174    while (tmp < (buffer + 2048 * 2))
175    {
176       if (!*tmp)
177          return 0;
178 
179       if (!strncasecmp((const char*)(tmp + 33), "SYSTEM.CNF;1", 12))
180          break;
181 
182       tmp += *tmp;
183    }
184 
185    if (tmp >= (buffer + 2048 * 2))
186       return 0;
187 
188    cd_sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16);
189    if (intfstream_seek(fp, skip + cd_sector * frame_size, SEEK_SET) == -1)
190       return 0;
191 
192    intfstream_read(fp, buffer, 256);
193    buffer[256] = '\0';
194 
195    tmp = buffer;
196    while (*tmp && strncasecmp((const char*)tmp, "boot", 4))
197       tmp++;
198 
199    if (!*tmp)
200       return 0;
201 
202    boot_file = tmp;
203    while (*tmp && *tmp != '\n')
204    {
205       if ((*tmp == '\\') || (*tmp == ':'))
206          boot_file = tmp + 1;
207 
208       tmp++;
209    }
210 
211    tmp = boot_file;
212    *game_id++ = toupper(*tmp++);
213    *game_id++ = toupper(*tmp++);
214    *game_id++ = toupper(*tmp++);
215    *game_id++ = toupper(*tmp++);
216    *game_id++ = '-';
217 
218    if (!ISALNUM(*tmp))
219       tmp++;
220 
221    while (ISALNUM(*tmp))
222    {
223       *game_id++ = *tmp++;
224       if (*tmp == '.')
225          tmp++;
226    }
227 
228    *game_id = 0;
229 
230    return 1;
231 }
232 
detect_ps1_game(intfstream_t * fd,char * game_id)233 int detect_ps1_game(intfstream_t *fd, char *game_id)
234 {
235    if (detect_ps1_game_sub(fd, game_id, 0))
236       return 1;
237 
238    return detect_ps1_game_sub(fd, game_id, 1);
239 }
240 
detect_psp_game(intfstream_t * fd,char * game_id)241 int detect_psp_game(intfstream_t *fd, char *game_id)
242 {
243    unsigned pos;
244    bool rv   = false;
245 
246    for (pos = 0; pos < 100000; pos++)
247    {
248       intfstream_seek(fd, pos, SEEK_SET);
249 
250       if (intfstream_read(fd, game_id, 5) > 0)
251       {
252          bool found = false;
253          game_id[5] = '\0';
254 
255          if (string_starts_with_size(game_id, "UL", STRLEN_CONST("UL")))
256          {
257             if (
258                    (string_is_equal(game_id, "ULES-"))
259                 || (string_is_equal(game_id, "ULUS-"))
260                 || (string_is_equal(game_id, "ULJS-"))
261                 || (string_is_equal(game_id, "ULET-"))
262                 || (string_is_equal(game_id, "ULUX-"))
263                 || (string_is_equal(game_id, "ULJP-"))
264                 || (string_is_equal(game_id, "ULED-"))
265                 || (string_is_equal(game_id, "ULEM-"))
266                 || (string_is_equal(game_id, "ULUM-"))
267                 || (string_is_equal(game_id, "ULJM-"))
268                 || (string_is_equal(game_id, "ULKS-"))
269                 || (string_is_equal(game_id, "ULAS-"))
270                )
271                found = true;
272          }
273          if (!found && string_starts_with_size(game_id, "UC", STRLEN_CONST("UC")))
274          {
275             if (
276                    (string_is_equal(game_id, "UCES-"))
277                 || (string_is_equal(game_id, "UCED-"))
278                 || (string_is_equal(game_id, "UCET-"))
279                 || (string_is_equal(game_id, "UCJB-"))
280                 || (string_is_equal(game_id, "UCJM-"))
281                 || (string_is_equal(game_id, "UCJP-"))
282                 || (string_is_equal(game_id, "UCJX-"))
283                 || (string_is_equal(game_id, "UCJP-"))
284                 || (string_is_equal(game_id, "UCKM-"))
285                 || (string_is_equal(game_id, "UCUS-"))
286                 || (string_is_equal(game_id, "UCAM-"))
287                 || (string_is_equal(game_id, "UCJS-"))
288                 || (string_is_equal(game_id, "UCAS-"))
289                 || (string_is_equal(game_id, "UCKS-"))
290                )
291                found = true;
292          }
293 
294          if (!found && string_starts_with_size(game_id, "NP", STRLEN_CONST("NP")))
295          {
296             if (
297                      (string_is_equal(game_id, "NPEH-"))
298                   || (string_is_equal(game_id, "NPUH-"))
299                   || (string_is_equal(game_id, "NPJH-"))
300 
301                   || (string_is_equal(game_id, "NPEG-"))
302                   || (string_is_equal(game_id, "NPUG-"))
303                   || (string_is_equal(game_id, "NPJG-"))
304                   || (string_is_equal(game_id, "NPHG-"))
305                   || (string_is_equal(game_id, "NPEX-"))
306                   || (string_is_equal(game_id, "NPHH-"))
307                   || (string_is_equal(game_id, "NPHZ-"))
308                   || (string_is_equal(game_id, "NPJJ-"))
309                   || (string_is_equal(game_id, "NPUF-"))
310                   || (string_is_equal(game_id, "NPUX-"))
311 
312                   || (string_is_equal(game_id, "NPEZ-"))
313                   || (string_is_equal(game_id, "NPUZ-"))
314                   || (string_is_equal(game_id, "NPJZ-"))
315                )
316                found = true;
317          }
318 
319          if (found)
320          {
321             intfstream_seek(fd, pos, SEEK_SET);
322             if (intfstream_read(fd, game_id, 10) > 0)
323             {
324 #if 0
325                game_id[4] = '-';
326                game_id[8] = game_id[9];
327                game_id[9] = game_id[10];
328 #endif
329                game_id[10] = '\0';
330                rv = true;
331             }
332             break;
333          }
334       }
335       else
336          break;
337    }
338 
339    return rv;
340 }
341 
detect_gc_game(intfstream_t * fd,char * game_id)342 int detect_gc_game(intfstream_t *fd, char *game_id)
343 {
344    bool rv   = false;
345 
346    intfstream_seek(fd, 0, SEEK_SET);
347 
348    if (intfstream_read(fd, game_id, 6) > 0)
349    {
350       game_id[6] = '\0';
351       rv = true;
352    }
353 
354    return rv;
355 }
356 
357 /**
358  * Check for an ASCII serial in the first few bits of the ISO (Wii).
359  */
detect_serial_ascii_game(intfstream_t * fd,char * game_id)360 int detect_serial_ascii_game(intfstream_t *fd, char *game_id)
361 {
362    unsigned pos;
363    int number_of_ascii = 0;
364    bool rv             = false;
365 
366    for (pos = 0; pos < 10000; pos++)
367    {
368       intfstream_seek(fd, pos, SEEK_SET);
369       if (intfstream_read(fd, game_id, 15) > 0)
370       {
371          unsigned i;
372          game_id[15]     = '\0';
373          number_of_ascii = 0;
374 
375          /* When scanning WBFS files, "WBFS" is discovered as the first serial. Ignore it. */
376          if (string_is_equal(game_id, "WBFS"))
377             continue;
378 
379          /* Loop through until we run out of ASCII characters. */
380          for (i = 0; i < 15; i++)
381          {
382             /* Is the given character ASCII? A-Z, 0-9, - */
383             if (  (game_id[i] == 45) ||
384                   (game_id[i] >= 48 && game_id[i] <= 57) ||
385                   (game_id[i] >= 65 && game_id[i] <= 90))
386                number_of_ascii++;
387             else
388                break;
389          }
390 
391          /* If the length of the text is between 3 and 9 characters,
392           * it could be a serial. */
393          if (number_of_ascii > 3 && number_of_ascii < 9)
394          {
395             /* Cut the string off, and return it as a valid serial. */
396             game_id[number_of_ascii] = '\0';
397             rv                       = true;
398             break;
399          }
400       }
401    }
402 
403    return rv;
404 }
405 
detect_system(intfstream_t * fd,const char ** system_name)406 int detect_system(intfstream_t *fd, const char **system_name)
407 {
408    int i;
409    int rv;
410    int64_t read;
411    char magic[MAGIC_LEN];
412 
413    for (i = 0; MAGIC_NUMBERS[i].system_name != NULL; i++)
414    {
415       intfstream_seek(fd, MAGIC_NUMBERS[i].offset, SEEK_SET);
416 
417       read = intfstream_read(fd, magic, MAGIC_LEN);
418 
419       if (read < 0)
420       {
421          RARCH_LOG("Could not read data at offset %d: %s\n",
422                MAGIC_NUMBERS[i].offset, strerror(errno));
423          rv = -errno;
424          goto clean;
425       }
426 
427       if (read < MAGIC_LEN)
428          continue;
429 
430       if (memcmp(MAGIC_NUMBERS[i].magic, magic, MAGIC_LEN) == 0)
431       {
432          *system_name = MAGIC_NUMBERS[i].system_name;
433          rv = 0;
434          goto clean;
435       }
436    }
437 
438    intfstream_seek(fd, 0x8008, SEEK_SET);
439    if (intfstream_read(fd, magic, 8) > 0)
440    {
441       magic[8] = '\0';
442       if (!string_is_empty(magic) &&
443             string_is_equal(magic, "PSP GAME"))
444       {
445          *system_name = "psp\0";
446          rv = 0;
447          goto clean;
448       }
449    }
450 
451    RARCH_LOG("%s\n", msg_hash_to_str(MSG_COULD_NOT_FIND_COMPATIBLE_SYSTEM));
452    rv = -EINVAL;
453 
454 clean:
455    return rv;
456 }
457 
intfstream_get_file_size(const char * path)458 static int64_t intfstream_get_file_size(const char *path)
459 {
460    int64_t rv;
461    intfstream_t *fd = intfstream_open_file(path,
462          RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
463    if (!fd)
464       return -1;
465    rv = intfstream_get_size(fd);
466    intfstream_close(fd);
467    free(fd);
468    return rv;
469 }
470 
update_cand(int64_t * cand_index,int64_t * last_index,uint64_t * largest,char * last_file,uint64_t * offset,uint64_t * size,char * track_path,uint64_t max_len)471 static bool update_cand(int64_t *cand_index, int64_t *last_index,
472       uint64_t *largest, char *last_file, uint64_t *offset,
473       uint64_t *size, char *track_path, uint64_t max_len)
474 {
475    if (*cand_index != -1)
476    {
477       if ((uint64_t)(*last_index - *cand_index) > *largest)
478       {
479          *largest    = *last_index - *cand_index;
480          strlcpy(track_path, last_file, (size_t)max_len);
481          *offset     = *cand_index;
482          *size       = *largest;
483          *cand_index = -1;
484          return true;
485       }
486       *cand_index    = -1;
487    }
488    return false;
489 }
490 
cue_find_track(const char * cue_path,bool first,uint64_t * offset,uint64_t * size,char * track_path,uint64_t max_len)491 int cue_find_track(const char *cue_path, bool first,
492       uint64_t *offset, uint64_t *size, char *track_path, uint64_t max_len)
493 {
494    int rv;
495    intfstream_info_t info;
496    char tmp_token[MAX_TOKEN_LEN];
497    char last_file[PATH_MAX_LENGTH];
498    char cue_dir[PATH_MAX_LENGTH];
499    intfstream_t *fd           = NULL;
500    int64_t last_index         = -1;
501    int64_t cand_index         = -1;
502    int32_t cand_track         = -1;
503    int32_t track              = 0;
504    uint64_t largest             = 0;
505    int64_t volatile file_size = -1;
506    bool is_data               = false;
507    cue_dir[0] = last_file[0]  = '\0';
508 
509    fill_pathname_basedir(cue_dir, cue_path, sizeof(cue_dir));
510 
511    info.type                  = INTFSTREAM_FILE;
512    fd                         = (intfstream_t*)intfstream_init(&info);
513 
514    if (!fd)
515       goto error;
516 
517    if (!intfstream_open(fd, cue_path,
518             RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE))
519    {
520       RARCH_LOG("Could not open CUE file '%s': %s\n", cue_path,
521             strerror(errno));
522       goto error;
523    }
524 
525    RARCH_LOG("Parsing CUE file '%s'...\n", cue_path);
526 
527    tmp_token[0] = '\0';
528 
529    rv = -EINVAL;
530 
531    while (get_token(fd, tmp_token, sizeof(tmp_token)) > 0)
532    {
533       if (string_is_equal_noncase(tmp_token, "FILE"))
534       {
535          /* Set last index to last EOF */
536          if (file_size != -1)
537             last_index = file_size;
538 
539          /* We're changing files since the candidate, update it */
540          if (update_cand(&cand_index, &last_index,
541                   &largest, last_file, offset,
542                   size, track_path, max_len))
543          {
544             rv = 0;
545             if (first)
546                goto clean;
547          }
548 
549          get_token(fd, tmp_token, sizeof(tmp_token));
550          fill_pathname_join(last_file, cue_dir,
551                tmp_token, sizeof(last_file));
552 
553          file_size = intfstream_get_file_size(last_file);
554 
555          get_token(fd, tmp_token, sizeof(tmp_token));
556 
557       }
558       else if (string_is_equal_noncase(tmp_token, "TRACK"))
559       {
560          get_token(fd, tmp_token, sizeof(tmp_token));
561          get_token(fd, tmp_token, sizeof(tmp_token));
562          is_data = !string_is_equal_noncase(tmp_token, "AUDIO");
563          ++track;
564       }
565       else if (string_is_equal_noncase(tmp_token, "INDEX"))
566       {
567          int m, s, f;
568          get_token(fd, tmp_token, sizeof(tmp_token));
569          get_token(fd, tmp_token, sizeof(tmp_token));
570 
571          if (sscanf(tmp_token, "%02d:%02d:%02d", &m, &s, &f) < 3)
572          {
573             RARCH_LOG("Error parsing time stamp '%s'\n", tmp_token);
574             goto error;
575          }
576 
577          last_index = (size_t) (((m * 60 + s) * 75) + f) * 2352;
578 
579          /* If we've changed tracks since the candidate, update it */
580          if (cand_track != -1 && track != cand_track &&
581              update_cand(&cand_index, &last_index, &largest,
582                 last_file, offset,
583                 size, track_path, max_len))
584          {
585             rv = 0;
586             if (first)
587                goto clean;
588          }
589 
590          if (!is_data)
591             continue;
592 
593          if (cand_index == -1)
594          {
595             cand_index = last_index;
596             cand_track = track;
597          }
598       }
599    }
600 
601    if (file_size != -1)
602       last_index = file_size;
603 
604    if (update_cand(&cand_index, &last_index,
605             &largest, last_file, offset,
606             size, track_path, max_len))
607       rv = 0;
608 
609 clean:
610    intfstream_close(fd);
611    free(fd);
612    return rv;
613 
614 error:
615    if (fd)
616    {
617       intfstream_close(fd);
618       free(fd);
619    }
620    return -errno;
621 }
622 
cue_next_file(intfstream_t * fd,const char * cue_path,char * path,uint64_t max_len)623 bool cue_next_file(intfstream_t *fd,
624       const char *cue_path, char *path, uint64_t max_len)
625 {
626    char tmp_token[MAX_TOKEN_LEN];
627    char cue_dir[PATH_MAX_LENGTH];
628    bool rv                    = false;
629    cue_dir[0]                 = '\0';
630 
631    fill_pathname_basedir(cue_dir, cue_path, sizeof(cue_dir));
632 
633    tmp_token[0] = '\0';
634 
635    while (get_token(fd, tmp_token, sizeof(tmp_token)) > 0)
636    {
637       if (string_is_equal_noncase(tmp_token, "FILE"))
638       {
639          get_token(fd, tmp_token, sizeof(tmp_token));
640          fill_pathname_join(path, cue_dir, tmp_token, (size_t)max_len);
641          rv = true;
642          break;
643       }
644    }
645 
646    return rv;
647 }
648 
gdi_find_track(const char * gdi_path,bool first,char * track_path,uint64_t max_len)649 int gdi_find_track(const char *gdi_path, bool first,
650       char *track_path, uint64_t max_len)
651 {
652    int rv;
653    intfstream_info_t info;
654    char tmp_token[MAX_TOKEN_LEN];
655    intfstream_t *fd  = NULL;
656    uint64_t largest  = 0;
657    int size          = -1;
658    int mode          = -1;
659    int64_t file_size = -1;
660 
661    info.type         = INTFSTREAM_FILE;
662 
663    fd                = (intfstream_t*)intfstream_init(&info);
664 
665    if (!fd)
666       goto error;
667 
668    if (!intfstream_open(fd, gdi_path,
669             RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE))
670    {
671       RARCH_LOG("Could not open GDI file '%s': %s\n", gdi_path,
672             strerror(errno));
673       goto error;
674    }
675 
676    RARCH_LOG("Parsing GDI file '%s'...\n", gdi_path);
677 
678    tmp_token[0] = '\0';
679 
680    rv = -EINVAL;
681 
682    /* Skip track count */
683    get_token(fd, tmp_token, sizeof(tmp_token));
684 
685    /* Track number */
686    while (get_token(fd, tmp_token, sizeof(tmp_token)) > 0)
687    {
688       /* Offset */
689       if (get_token(fd, tmp_token, sizeof(tmp_token)) <= 0)
690       {
691          errno = EINVAL;
692          goto error;
693       }
694 
695       /* Mode */
696       if (get_token(fd, tmp_token, sizeof(tmp_token)) <= 0)
697       {
698          errno = EINVAL;
699          goto error;
700       }
701 
702       mode = atoi(tmp_token);
703 
704       /* Sector size */
705       if (get_token(fd, tmp_token, sizeof(tmp_token)) <= 0)
706       {
707          errno = EINVAL;
708          goto error;
709       }
710 
711       size = atoi(tmp_token);
712 
713       /* File name */
714       if (get_token(fd, tmp_token, sizeof(tmp_token)) <= 0)
715       {
716          errno = EINVAL;
717          goto error;
718       }
719 
720       /* Check for data track */
721       if (!(mode == 0 && size == 2352))
722       {
723          char last_file[PATH_MAX_LENGTH];
724          char gdi_dir[PATH_MAX_LENGTH];
725 
726          gdi_dir[0]        = last_file[0] = '\0';
727 
728          fill_pathname_basedir(gdi_dir, gdi_path, sizeof(gdi_dir));
729 
730          fill_pathname_join(last_file,
731                gdi_dir, tmp_token, sizeof(last_file));
732          file_size = intfstream_get_file_size(last_file);
733 
734          if (file_size < 0)
735             goto error;
736 
737          if ((uint64_t)file_size > largest)
738          {
739             strlcpy(track_path, last_file, (size_t)max_len);
740 
741             rv      = 0;
742             largest = file_size;
743 
744             if (first)
745                goto clean;
746          }
747       }
748 
749       /* Disc offset (not used?) */
750       if (get_token(fd, tmp_token, sizeof(tmp_token)) <= 0)
751       {
752          errno = EINVAL;
753          goto error;
754       }
755    }
756 
757 clean:
758    intfstream_close(fd);
759    free(fd);
760    return rv;
761 
762 error:
763    if (fd)
764    {
765       intfstream_close(fd);
766       free(fd);
767    }
768    return -errno;
769 }
770 
gdi_next_file(intfstream_t * fd,const char * gdi_path,char * path,uint64_t max_len)771 bool gdi_next_file(intfstream_t *fd, const char *gdi_path,
772       char *path, uint64_t max_len)
773 {
774    char tmp_token[MAX_TOKEN_LEN];
775    bool rv         = false;
776 
777    tmp_token[0]    = '\0';
778 
779    /* Skip initial track count */
780    if (intfstream_tell(fd) == 0)
781       get_token(fd, tmp_token, sizeof(tmp_token));
782 
783    get_token(fd, tmp_token, sizeof(tmp_token)); /* Track number */
784    get_token(fd, tmp_token, sizeof(tmp_token)); /* Offset       */
785    get_token(fd, tmp_token, sizeof(tmp_token)); /* Mode         */
786    get_token(fd, tmp_token, sizeof(tmp_token)); /* Sector size  */
787 
788    /* File name */
789    if (get_token(fd, tmp_token, sizeof(tmp_token)) > 0)
790    {
791       char gdi_dir[PATH_MAX_LENGTH];
792 
793       gdi_dir[0]      = '\0';
794 
795       fill_pathname_basedir(gdi_dir, gdi_path, sizeof(gdi_dir));
796 
797       fill_pathname_join(path, gdi_dir, tmp_token, (size_t)max_len);
798 
799       rv              = true;
800 
801       /* Disc offset */
802       get_token(fd, tmp_token, sizeof(tmp_token));
803    }
804 
805    return rv;
806 }
807