int cdd_load()

in cores/genesis/core/cd_hw/cdd.c [309:1160]


int cdd_load(char *filename, char *header)
{
  char fname[256+10];
  char line[128];
  char *ptr, *lptr;
  cdStream *fd;
  
  /* assume CD image file by default */
  int isCDfile = 1;

  /* first unmount any loaded disc */
  cdd_unload();

  /* open file */
  fd = cdStreamOpen(filename);
  if (!fd)
    return (-1);

#if defined(USE_LIBCHDR)
  if (!memcmp(".chd", &filename[strlen(filename) - 4], 4) || !memcmp(".CHD", &filename[strlen(filename) - 4], 4))
  {
    int sectors = 0;
    char metadata[256];
    const chd_header *head;

    /* open CHD file */
    if (chd_open_file(fd, CHD_OPEN_READ, NULL, &cdd.chd.file) != CHDERR_NONE)
    {
      chd_close(cdd.chd.file);
      cdStreamClose(fd);
      return -1;
    }

    /* retrieve CHD header */
    head = chd_get_header(cdd.chd.file);
 
    /* detect invalid hunk size */
    if ((head->hunkbytes == 0) || (head->hunkbytes % CD_FRAME_SIZE))
    {
      chd_close(cdd.chd.file);
      cdStreamClose(fd);
      return -1;
    }

    /* allocate hunk buffer */
    cdd.chd.hunk = (uint8 *)malloc(head->hunkbytes);
    if (!cdd.chd.hunk)
    {
      chd_close(cdd.chd.file);
      cdStreamClose(fd);
      return -1;
    }

    /* initialize hunk size (usually fixed to 8 sectors) */
    cdd.chd.hunkbytes = head->hunkbytes;

    /* initialize buffered hunk index */
    cdd.chd.hunknum = -1;

    /* retrieve tracks informations */
    for (cdd.toc.last = 0; cdd.toc.last < 99; cdd.toc.last++)
    {
      int tracknum = 0, frames = 0, pregap = 0, postgap = 0;
      char type[16], subtype[16], pgtype[16], pgsub[16];
      type[0] = subtype[0] = pgtype[0] = pgsub[0] = 0;

      /* attempt fetch either complete or partial metadata for current track */
      if (chd_get_metadata(cdd.chd.file, CDROM_TRACK_METADATA2_TAG, cdd.toc.last, metadata, 256, 0, 0, 0) == CHDERR_NONE)
      {
        if (sscanf(metadata, CDROM_TRACK_METADATA2_FORMAT, &tracknum, &type[0], &subtype[0], &frames, &pregap, &pgtype[0], &pgsub[0], &postgap) != 8)
          break;
      }
      else if (chd_get_metadata(cdd.chd.file, CDROM_TRACK_METADATA_TAG, cdd.toc.last, metadata, 256, 0, 0, 0) == CHDERR_NONE)
      {
        if (sscanf(metadata, CDROM_TRACK_METADATA_FORMAT, &tracknum, &type[0], &subtype[0], &frames) != 4)
          break;
      }

      /* no more track */
      else break;

      /* detect out of order track number or invalid parameter */
      if ((tracknum != (cdd.toc.last + 1)) || (frames < 0) || (pregap < 0) || (postgap < 0))
        break;

      /* detect track type  */
      if (cdd.toc.last)
      {
        /* CD-ROM track supported only for first track */
        if (strcmp(type, "AUDIO"))
          break;

        /* Audio track start LBA (adjusted with pregap length) */
        cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end + pregap;
      }
      else
      {
        /* COOKED format (2048 bytes data blocks) */
        if (!strcmp(type, "MODE1"))
          cdd.sectorSize = 2048;

        /* RAW format (2352 bytes data blocks) */
        else if (!strcmp(type, "MODE1_RAW"))
          cdd.sectorSize = 2352;

        /* unsupported track format */
        else if (strcmp(type, "AUDIO"))
          break;
        
        /* Data track start LBA (2s pause assumed by default) */
        cdd.toc.tracks[0].start = 0;
      }

      /* detect pregap type */
      if (pgtype[0] != 'V')
      {
        /* clear pause length for further calculations (not included in CHD file) */
        pregap = 0;
      }

      /* track end LBA (remove included pause from CHD track length) */
      cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + frames - pregap;
   
      /* CHD file offset for current track */
      cdd.toc.tracks[cdd.toc.last].offset = (sectors + pregap - cdd.toc.tracks[cdd.toc.last].start) * CD_FRAME_SIZE;

      /* update TOC end with postgap length */
      cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end + postgap;

      /* update CHD file sector count (adjusted with end of the track padding) */
      sectors += (((frames + CD_TRACK_PADDING - 1) / CD_TRACK_PADDING) * CD_TRACK_PADDING);

      /* indicate valid track file */
      cdd.toc.tracks[cdd.toc.last].fd = fd;
    }

    /* valid CD-ROM image file ? */
    if (cdd.sectorSize)
    {
      /* read first chunk of data */
      cdd.chd.hunknum = cdd.toc.tracks[0].offset / cdd.chd.hunkbytes;
      chd_read(cdd.chd.file, cdd.chd.hunknum, cdd.chd.hunk);

      /* copy CD image header + security code */
      memcpy(header, cdd.chd.hunk + (cdd.toc.tracks[0].offset % cdd.chd.hunkbytes) + 0x10, 0x210);

      /* there is a valid DATA track */
      cdd.toc.tracks[0].type = TYPE_CDROM;
    }

    /* valid CD image ? */
    if (cdd.toc.last && (cdd.toc.end < (100*60*75)))
    {
      /* Lead-out */
      cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end;

      /* CD mounted */
      cdd.loaded = 1;
      return 1;
    }

    /* invalid CHD file */
    chd_close(cdd.chd.file);
    cdStreamClose(fd);
    return -1;
  }
#endif

  /* save a copy of base filename */
  strncpy(fname, filename, 256);

  /* check loaded file extension */
  if (memcmp(".cue", &filename[strlen(filename) - 4], 4) && memcmp(".CUE", &filename[strlen(filename) - 4], 4))
  {
    int len;

    /* read first 16 bytes */
    cdStreamRead(header, 0x10, 1, fd);

    /* look for valid CD image identifier */
    if (!memcmp("SEGADISCSYSTEM", header, 14))
    {    
      /* COOKED format (2048 bytes data blocks) */
      cdd.sectorSize = 2048;
    }
    else
    {    
      /* read next 16 bytes */
      cdStreamRead(header, 0x10, 1, fd);

      /* look for valid CD image identifier */
      if (!memcmp("SEGADISCSYSTEM", header, 14))
      {
        /* RAW format (2352 bytes data blocks) */
        cdd.sectorSize = 2352;
      }
    }

    /* valid CD image file ? */
    if (cdd.sectorSize)
    {
      /* read CD image header + security code */
      cdStreamRead(header + 0x10, 0x200, 1, fd);

      /* initialize first track file descriptor */
      cdd.toc.tracks[0].fd = fd;

      /* this is a valid DATA track */
      cdd.toc.tracks[0].type = TYPE_CDROM;

      /* DATA track end LBA (based on DATA file length) */
      cdStreamSeek(fd, 0, SEEK_END);
      cdd.toc.tracks[0].end = cdStreamTell(fd) / cdd.sectorSize;

      /* DATA track length should be at least 2s (BIOS requirement) */
      if (cdd.toc.tracks[0].end < 150)
      {
        cdd.toc.tracks[0].end = 150;
      }
        
      /* DATA track start LBA (logical block 0) */
      cdStreamSeek(fd, 0, SEEK_SET);
      cdd.toc.tracks[0].start = 0;

      /* initialize TOC */
      cdd.toc.end = cdd.toc.tracks[0].end;
      cdd.toc.last = 1;
    }
    else
    {
      /* this is not a CD image file */
      isCDfile = 0;

      /* close file */
      cdStreamClose(fd);
    }

    /* automatically try to mount CD associated CUE file */
    len = strlen(fname);
    while ((len && (fname[len] != '.')) || (len > 251)) len--;
    strcpy(&fname[len], ".cue");
    fd = cdStreamOpen(fname);
  }

  /* parse CUE file */
  if (fd)
  {
    int mm, ss, bb, pregap = 0;

    /* DATA track already loaded ? */
    if (cdd.toc.last)
    {
      /* skip first track */
      while (cdStreamGets(line, 128, fd))
      {
        if (strstr(line, "INDEX 01") && !strstr(line, "INDEX 1"))
          break;
      }
    }

    /* read lines until end of file */
    while (cdStreamGets(line, 128, fd))
    {
      /* skip any SPACE characters */
      lptr = line;
      while (*lptr == 0x20) lptr++;

      /* decode FILE commands */
      if (!(memcmp(lptr, "FILE", 4)))
      {
        /* retrieve current path */
        ptr = fname + strlen(fname) - 1;
        while ((ptr - fname) && (*ptr != '/') && (*ptr != '\\')) ptr--;
        if (ptr - fname) ptr++;

        /* skip "FILE" attribute */
        lptr += 4;

        /* skip SPACE characters */
        while (*lptr == 0x20) lptr++;

        /* retrieve full filename */
        if (*lptr == '\"')
        {
          /* skip first DOUBLE QUOTE character */
          lptr++;
          while ((*lptr != '\"') && (lptr <= (line + 128)) && (ptr < (fname + 255)))
            *ptr++ = *lptr++;
        }
        else
        {
          /* no DOUBLE QUOTE used */
          while ((*lptr != 0x20) && (lptr <= (line + 128)) && (ptr < (fname + 255)))
            *ptr++ = *lptr++;
        }
        *ptr = 0;

        /* open current track file descriptor */
        cdd.toc.tracks[cdd.toc.last].fd = cdStreamOpen(fname);
        if (!cdd.toc.tracks[cdd.toc.last].fd)
        {
          /* error opening file */
          break;
        }

        /* reset current file PREGAP length */
        pregap = 0;

        /* reset current track file read offset */
        cdd.toc.tracks[cdd.toc.last].offset = 0;

        /* check supported audio file types */
        if (!strstr(lptr,"BINARY") && !strstr(lptr,"MOTOROLA"))
        {
          /* read file header */
          unsigned char head[28];
          cdStreamSeek(cdd.toc.tracks[cdd.toc.last].fd, 8, SEEK_SET);
          cdStreamRead(head, 28, 1, cdd.toc.tracks[cdd.toc.last].fd);
          cdStreamSeek(cdd.toc.tracks[cdd.toc.last].fd, 0, SEEK_SET);
      
          /* autodetect WAVE file header (44.1KHz 16-bit stereo format only) */
          if (!memcmp(head, waveHeader, 28))
          {
            /* look for 'data' chunk id */
            int dataOffset = 0;
            cdStreamSeek(cdd.toc.tracks[cdd.toc.last].fd, 36, SEEK_SET);
            while (cdStreamRead(head, 4, 1, cdd.toc.tracks[cdd.toc.last].fd))
            {
              if (!memcmp(head, "data", 4))
              {
                dataOffset = cdStreamTell(cdd.toc.tracks[cdd.toc.last].fd) + 4;
                cdStreamSeek(cdd.toc.tracks[cdd.toc.last].fd, 0, SEEK_SET);
                break;
              }
              cdStreamSeek(cdd.toc.tracks[cdd.toc.last].fd, -2, SEEK_CUR);
            }

            /* check if 'data' chunk has not been found */
            if (!dataOffset)
            {
              /* invalid WAVE file */
              cdStreamClose(cdd.toc.tracks[cdd.toc.last].fd);
              cdd.toc.tracks[cdd.toc.last].fd = 0;
              break;
            }

            /* adjust current track file read offset with WAVE header length */
            cdd.toc.tracks[cdd.toc.last].offset -= dataOffset;
          }
#if defined(USE_LIBTREMOR) || defined(USE_LIBVORBIS)
          else if (!ov_open_callbacks(cdd.toc.tracks[cdd.toc.last].fd,&cdd.toc.tracks[cdd.toc.last].vf,0,0,cb))
          {
            /* retrieve stream infos */
            vorbis_info *info = ov_info(&cdd.toc.tracks[cdd.toc.last].vf,-1);
            if (!info || (info->rate != 44100) || (info->channels != 2))
            {
              /* unsupported VORBIS file format (stereo @44.1kHz only) */
              ov_clear(&cdd.toc.tracks[cdd.toc.last].vf);
              cdd.toc.tracks[cdd.toc.last].fd = 0;
              break;
            }
          }
#endif
          else
          {
            /* unsupported audio file */
            cdStreamClose(cdd.toc.tracks[cdd.toc.last].fd);
            cdd.toc.tracks[cdd.toc.last].fd = 0;
            break;
          }
        }
      }

      /* decode TRACK commands */
      else if ((sscanf(lptr, "TRACK %02d %*s", &bb)) || (sscanf(lptr, "TRACK %d %*s", &bb)))
      {
        /* check track number */
        if (bb != (cdd.toc.last + 1))
        {
          /* close any opened file */
          if (cdd.toc.tracks[cdd.toc.last].fd)
          {
            cdStreamClose(cdd.toc.tracks[cdd.toc.last].fd);
            cdd.toc.tracks[cdd.toc.last].fd = 0;
          }

          /* missing tracks */
          break;
        }

        /* autodetect DATA track (first track only) */
        if (!cdd.toc.last)
        {
          /* CD-ROM Mode 1 support only */
          if (strstr(lptr,"MODE1/2048"))
          {
            /* COOKED format (2048 bytes / block) */
            cdd.sectorSize = 2048;
          }
          else if (strstr(lptr,"MODE1/2352"))
          {
            /* RAW format (2352 bytes / block) */
            cdd.sectorSize = 2352;

            /* skip 16-byte header */
            cdStreamSeek(cdd.toc.tracks[0].fd, 0x10, SEEK_SET);
          }

          if (cdd.sectorSize)
          {
            /* this is a valid DATA track */
            cdd.toc.tracks[0].type = TYPE_CDROM;

            /* read CD image header + security code */
            cdStreamRead(header, 0x210, 1, cdd.toc.tracks[0].fd);
            cdStreamSeek(cdd.toc.tracks[0].fd, 0, SEEK_SET);
          }
        }
        else
        {
          /* check if same file is used for consecutive tracks */
          if (!cdd.toc.tracks[cdd.toc.last].fd)
          {
            /* clear previous track end time */
            cdd.toc.tracks[cdd.toc.last - 1].end = 0;
          }
        }
      }

      /* decode PREGAP commands */
      else if (sscanf(lptr, "PREGAP %02d:%02d:%02d", &mm, &ss, &bb) == 3)
      {
        /* increment current file PREGAP length */
        pregap += bb + ss*75 + mm*60*75;
      }

      /* decode INDEX commands */
      else if ((sscanf(lptr, "INDEX 00 %02d:%02d:%02d", &mm, &ss, &bb) == 3) ||
               (sscanf(lptr, "INDEX 0 %02d:%02d:%02d", &mm, &ss, &bb) == 3))
      {
        /* check if previous track end time needs to be set */
        if (cdd.toc.last && !cdd.toc.tracks[cdd.toc.last - 1].end)
        {
          /* set previous track end time (current file absolute time + PREGAP length) */
          cdd.toc.tracks[cdd.toc.last - 1].end = bb + ss*75 + mm*60*75 + pregap;
        }
      }
      else if ((sscanf(lptr, "INDEX 01 %02d:%02d:%02d", &mm, &ss, &bb) == 3) ||
               (sscanf(lptr, "INDEX 1 %02d:%02d:%02d", &mm, &ss, &bb) == 3))
      {
        /* adjust current track file read offset with current file PREGAP length (only used for AUDIO track) */
        cdd.toc.tracks[cdd.toc.last].offset += pregap * 2352;

        /* check if a single file is used for consecutive tracks */
        if (!cdd.toc.tracks[cdd.toc.last].fd)
        {
          /* use common file descriptor */
          cdd.toc.tracks[cdd.toc.last].fd = cdd.toc.tracks[0].fd;

          /* current track start time (based on current file absolute time + PREGAP length) */
          cdd.toc.tracks[cdd.toc.last].start = bb + ss*75 + mm*60*75 + pregap;

          /* check if previous track end time needs to be set */
          if (cdd.toc.last && !cdd.toc.tracks[cdd.toc.last - 1].end)
          {
            /* set previous track end time (based on current track start time, ignoring any "PREGAP"-type pause if no INDEX00) */
            cdd.toc.tracks[cdd.toc.last - 1].end = cdd.toc.tracks[cdd.toc.last].start;
          }
        }
        else
        {
          /* current file start time (based on previous track end time + PREGAP length) */
          cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end + pregap;

          /* adjust current track file read offset with previous track end time (only used for AUDIO track) */
          cdd.toc.tracks[cdd.toc.last].offset += cdd.toc.end * 2352;

#if defined(USE_LIBTREMOR) || defined(USE_LIBVORBIS)
          if (cdd.toc.tracks[cdd.toc.last].vf.datasource)
          { 
            /* convert read offset to PCM sample offset */
            cdd.toc.tracks[cdd.toc.last].offset = cdd.toc.tracks[cdd.toc.last].offset / 4;

            /* current track end time */
            cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + ov_pcm_total(&cdd.toc.tracks[cdd.toc.last].vf,-1)/588;
            if (cdd.toc.tracks[cdd.toc.last].end <= cdd.toc.tracks[cdd.toc.last].start)
            {
              /* invalid length */
              ov_clear(&cdd.toc.tracks[cdd.toc.last].vf);
              cdd.toc.tracks[cdd.toc.last].fd = 0;
              cdd.toc.tracks[cdd.toc.last].end = 0;
              cdd.toc.tracks[cdd.toc.last].start = 0;
              cdd.toc.tracks[cdd.toc.last].offset = 0;
              break;
            }

#ifdef DISABLE_MANY_OGG_OPEN_FILES
            /* close VORBIS file structure to save memory */
            ogg_free(cdd.toc.last);
#endif
          }
          else
#endif
          {
            /* current track end time */
            cdStreamSeek(cdd.toc.tracks[cdd.toc.last].fd, 0, SEEK_END);
            if (cdd.toc.tracks[cdd.toc.last].type)
            {
              /* DATA track length */
              cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + ((cdStreamTell(cdd.toc.tracks[cdd.toc.last].fd) + cdd.sectorSize - 1) / cdd.sectorSize);
            }
            else
            {
              /* AUDIO track length */
              cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + ((cdStreamTell(cdd.toc.tracks[cdd.toc.last].fd) + 2351) / 2352);
            }
            cdStreamSeek(cdd.toc.tracks[cdd.toc.last].fd, 0, SEEK_SET);
          }

          /* adjust track start time (based on current file start time + index absolute time) */
          cdd.toc.tracks[cdd.toc.last].start += (bb + ss*75 + mm*60*75);

          /* update TOC end */
          cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end;
        }

        /* increment track number */
        cdd.toc.last++;
        
        /* max. 99 tracks */
        if (cdd.toc.last == 99) break;
      }
    }

    /* check if last track end time needs to be set */
    if (cdd.toc.last && !cdd.toc.tracks[cdd.toc.last - 1].end)
    {
      /* adjust TOC end with current file PREGAP length */
      cdd.toc.end += pregap;

      /* last track end time */
      cdd.toc.tracks[cdd.toc.last - 1].end = cdd.toc.end;
    }

    /* close any incomplete track file */
#if defined(USE_LIBTREMOR) || defined(USE_LIBVORBIS)
    if (cdd.toc.tracks[cdd.toc.last].vf.datasource)
    {
      ov_clear(&cdd.toc.tracks[cdd.toc.last].vf);
    }
    else
#endif
    if (cdd.toc.tracks[cdd.toc.last].fd)
    {
      cdStreamClose(cdd.toc.tracks[cdd.toc.last].fd);
    }

    /* close CUE file */
    cdStreamClose(fd);
  }
  else
  {
    int i, offset = 1;

    /* set pointer at the end of filename */
    ptr = fname + strlen(fname) - 4; 

    /* autodetect audio track file extensions */
    for (i=0; i<SUPPORTED_EXT; i++)
    {
      /* auto-detect wrong initial track index */
      sprintf(ptr, extensions[i], cdd.toc.last);
      fd = cdStreamOpen(fname);
      if (fd)
      {
        offset = 0;
        break;
      }

      sprintf(ptr, extensions[i], cdd.toc.last + 1);
      fd = cdStreamOpen(fname);
      if (fd) break;
    }

    /* repeat until no more valid track files can be found */
    while (fd)
    {
      /* read file HEADER */
      unsigned char head[28];
      cdStreamSeek(fd, 8, SEEK_SET);
      cdStreamRead(head, 28, 1, fd);
      cdStreamSeek(fd, 0, SEEK_SET);
      
      /* check if this is a valid WAVE file (44.1KHz 16-bit stereo format only) */
      if (!memcmp(head, waveHeader, 28))
      {
        /* look for 'data' chunk id */
        int dataOffset = 0;
        cdStreamSeek(fd, 36, SEEK_SET);
        while (cdStreamRead(head, 4, 1, fd))
        {
          if (!memcmp(head, "data", 4))
          {
            dataOffset = cdStreamTell(fd) + 4;
            break;
          }
          cdStreamSeek(fd, -2, SEEK_CUR);
        }

        /* check if 'data' chunk has not been found */
        if (!dataOffset)
        {
          /* invalid WAVE file */
          cdStreamClose(fd);
          break;
        }

        /* initialize current track file descriptor */
        cdd.toc.tracks[cdd.toc.last].fd = fd;

        /* initialize current track start time (based on previous track end time) */
        cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end;

        /* add default 2s PAUSE between tracks */
        cdd.toc.tracks[cdd.toc.last].start += 150;

        /* current track end time */
        cdStreamSeek(fd, 0, SEEK_END);
        cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + ((cdStreamTell(fd) - dataOffset + 2351) / 2352);

        /* initialize file read offset for current track */
        cdd.toc.tracks[cdd.toc.last].offset = cdd.toc.tracks[cdd.toc.last].start * 2352;

        /* auto-detect PAUSE within audio files */
        cdStreamSeek(fd, 100 * 2352, SEEK_SET);
        cdStreamRead(head, 4, 1, fd);
        cdStreamSeek(fd, 0, SEEK_SET);
        if (*(int32 *)head == 0)
        {
          /* assume 2s PAUSE is included at the beginning of the file */
          cdd.toc.tracks[cdd.toc.last].offset -= 150 * 2352;
          cdd.toc.tracks[cdd.toc.last].end -= 150;
        }

        /* update TOC end */
        cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end;

        /* adjust file read offset for current track with WAVE header length */
        cdd.toc.tracks[cdd.toc.last].offset -= dataOffset;

        /* increment track number */
        cdd.toc.last++;
      }
#if defined(USE_LIBTREMOR) || defined(USE_LIBVORBIS)
      else if (!ov_open_callbacks(fd,&cdd.toc.tracks[cdd.toc.last].vf,0,0,cb))
      {
        /* retrieve stream infos */
        vorbis_info *info = ov_info(&cdd.toc.tracks[cdd.toc.last].vf,-1);
        if (!info || (info->rate != 44100) || (info->channels != 2))
        {
          /* unsupported OGG file */
          ov_clear(&cdd.toc.tracks[cdd.toc.last].vf);
          break;
        }

        /* initialize current track file descriptor */
        cdd.toc.tracks[cdd.toc.last].fd = fd;

        /* initialize current track start time (based on previous track end time) */
        cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end;

        /* add default 2s PAUSE between tracks */
        cdd.toc.tracks[cdd.toc.last].start += 150;

        /* current track end time */
        cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + ((ov_pcm_total(&cdd.toc.tracks[cdd.toc.last].vf,-1) + 587) / 588);
        if (cdd.toc.tracks[cdd.toc.last].end <= cdd.toc.tracks[cdd.toc.last].start)
        {
          /* invalid file length */
          ov_clear(&cdd.toc.tracks[cdd.toc.last].vf);
          cdd.toc.tracks[cdd.toc.last].fd = 0;
          cdd.toc.tracks[cdd.toc.last].end = 0;
          cdd.toc.tracks[cdd.toc.last].start = 0;
          break;
        }

        /* initialize file read offset for current track */
        cdd.toc.tracks[cdd.toc.last].offset = cdd.toc.tracks[cdd.toc.last].start * 588;

        /* auto-detect PAUSE within audio files */
        ov_pcm_seek(&cdd.toc.tracks[cdd.toc.last].vf, 100 * 588);
#if defined(USE_LIBVORBIS)
        ov_read(&cdd.toc.tracks[cdd.toc.last].vf, (char *)head, 32, 0, 2, 1, 0);
#else
        ov_read(&cdd.toc.tracks[cdd.toc.last].vf, (char *)head, 32, 0);
#endif
        ov_pcm_seek(&cdd.toc.tracks[cdd.toc.last].vf, 0);
        if (*(int32 *)head == 0)
        {
          /* assume 2s PAUSE is included at the beginning of the file */
          cdd.toc.tracks[cdd.toc.last].offset -= 150 * 588;
          cdd.toc.tracks[cdd.toc.last].end -= 150;
        }

#ifdef DISABLE_MANY_OGG_OPEN_FILES
        /* close VORBIS file structure to save memory */
        ogg_free(cdd.toc.last);
#endif

        /* update TOC end */
        cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end;

        /* increment track number */
        cdd.toc.last++;
      }
#endif
      else
      {
        /* unsupported audio file format */
        cdStreamClose(fd);
        break;
      }

      /* max. 99 tracks */
      if (cdd.toc.last == 99)  break;

      /* try to open next audio track file */
      sprintf(ptr, extensions[i], cdd.toc.last + offset);
      fd = cdStreamOpen(fname);
    }
  }

  /* CD tracks found ? */
  if (cdd.toc.last)
  {
    /* Lead-out */
    cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end;

    /* CD mounted */
    cdd.loaded = 1;

    /* Valid DATA track found ? */
    if (cdd.toc.tracks[0].type)
    {
      /* simulate audio tracks if none found */
      if (cdd.toc.last == 1)
      {
        /* some games require exact TOC infos */
        if (strstr(header + 0x180,"T-95035") != NULL)
        {
          /* Snatcher */
          cdd.toc.last = cdd.toc.end = 0;
          do
          {
            cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end;
            cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + toc_snatcher[cdd.toc.last];
            cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end;
            cdd.toc.last++;
          }
          while (cdd.toc.last < 21);
        }
        else if (strstr(header + 0x180,"T-127015") != NULL)
        {
          /* Lunar - The Silver Star */
          cdd.toc.last = cdd.toc.end = 0;
          do
          {
            cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end;
            cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + toc_lunar[cdd.toc.last];
            cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end;
            cdd.toc.last++;
          }
          while (cdd.toc.last < 52);
        }
        else if (strstr(header + 0x180,"T-113045") != NULL)
        {
          /* Shadow of the Beast II */
          cdd.toc.last = cdd.toc.end = 0;
          do
          {
            cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end;
            cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + toc_shadow[cdd.toc.last];
            cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end;
            cdd.toc.last++;
          }
          while (cdd.toc.last < 15);
        }
        else if (strstr(header + 0x180,"T-143025") != NULL)
        {
          /* Dungeon Explorer */
          cdd.toc.last = cdd.toc.end = 0;
          do
          {
            cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end;
            cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + toc_dungeon[cdd.toc.last];
            cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end;
            cdd.toc.last++;
          }
          while (cdd.toc.last < 13);
        }
        else if (strstr(header + 0x180,"MK-4410") != NULL)
        {
          /* Final Fight CD (USA, Europe) */
          cdd.toc.last = cdd.toc.end = 0;
          do
          {
            cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end;
            cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + toc_ffight[cdd.toc.last];
            cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end;
            cdd.toc.last++;
          }
          while (cdd.toc.last < 26);
        }
        else if (strstr(header + 0x180,"G-6013") != NULL)
        {
          /* Final Fight CD (Japan) */
          cdd.toc.last = cdd.toc.end = 0;
          do
          {
            cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end;
            cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + toc_ffightj[cdd.toc.last];
            cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end;
            cdd.toc.last++;
          }
          while (cdd.toc.last < 29);
        }
        else
        {
          /* default TOC (99 tracks & 2s per audio tracks) */
          do
          {
            cdd.toc.tracks[cdd.toc.last].start = cdd.toc.end + 2*75;
            cdd.toc.tracks[cdd.toc.last].end = cdd.toc.tracks[cdd.toc.last].start + 2*75;
            cdd.toc.end = cdd.toc.tracks[cdd.toc.last].end;
            cdd.toc.last++;
          }
          while ((cdd.toc.last < 99) && (cdd.toc.end < 56*60*75));
        }
      }
    }

    /* Automatically try to open associated subcode data file */
    strncpy(&fname[strlen(fname) - 4], ".sub", 4);
    cdd.toc.sub = cdStreamOpen(fname);

    /* return 1 if loaded file is CD image file */
    return (isCDfile);
  }

  /* no CD image file loaded */
  return 0;
}