/*
 *  Copyright (C) 2006 Mike Melanson (mike at multimedia.cx)
 *
 *  This program 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 Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * Compact Disc Detection Experiment
 *
 * compile with:
 *   cc -Wall cdexp.c -o cdexp
 */


#define THIS_IS_LINUX
//#define THIS_IS_SUN
//#define THIS_IS_FREEBSD

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define CD_SECONDS_PER_MINUTE 60
#define CD_FRAMES_PER_SECOND 75
#define CD_RAW_FRAME_SIZE 2352
#define LEADOUT_TRACK 0xAA

typedef struct _cdrom_toc_entry {
  int track_mode;
  int first_frame;
  int first_frame_minute;
  int first_frame_second;
  int first_frame_frame;
} cdrom_toc_entry;

typedef struct _cdrom_toc {
  int first_track;
  int last_track;
  int total_tracks;

  cdrom_toc_entry *toc_entries;
  cdrom_toc_entry leadout_track;
} cdrom_toc;


#ifdef THIS_IS_LINUX

#include <linux/cdrom.h>

void read_cdrom_toc(int fd, cdrom_toc *toc) {

  struct cdrom_tochdr tochdr;
  struct cdrom_tocentry tocentry;
  int i;

  /* fetch the table of contents */
  if (ioctl(fd, CDROMREADTOCHDR, &tochdr) == -1) {
    perror("CDROMREADTOCHDR");
    return;
  }

  toc->first_track = tochdr.cdth_trk0;
  toc->last_track = tochdr.cdth_trk1;
  toc->total_tracks = toc->last_track - toc->first_track + 1;

  /* allocate space for the toc entries */
  toc->toc_entries = 
    (cdrom_toc_entry *)malloc(toc->total_tracks * sizeof(cdrom_toc_entry));
  if (!toc->toc_entries) {
    perror("malloc");
    return;
  }

  /* fetch each toc entry */
  for (i = toc->first_track; i <= toc->last_track; i++) {

    memset(&tocentry, 0, sizeof(tocentry));

    tocentry.cdte_track = i;
    tocentry.cdte_format = CDROM_MSF;
    if (ioctl(fd, CDROMREADTOCENTRY, &tocentry) == -1) {
      perror("CDROMREADTOCENTRY");
      return;
    }

    toc->toc_entries[i-1].track_mode = (tocentry.cdte_ctrl & 0x04) ? 1 : 0;
    toc->toc_entries[i-1].first_frame_minute = tocentry.cdte_addr.msf.minute;
    toc->toc_entries[i-1].first_frame_second = tocentry.cdte_addr.msf.second;
    toc->toc_entries[i-1].first_frame_frame = tocentry.cdte_addr.msf.frame;
    toc->toc_entries[i-1].first_frame = 
      (tocentry.cdte_addr.msf.minute * CD_SECONDS_PER_MINUTE * CD_FRAMES_PER_SECOND) +
      (tocentry.cdte_addr.msf.second * CD_FRAMES_PER_SECOND) +
       tocentry.cdte_addr.msf.frame;
  }

  /* fetch the leadout as well */
  memset(&tocentry, 0, sizeof(tocentry));

  tocentry.cdte_track = LEADOUT_TRACK;
  tocentry.cdte_format = CDROM_MSF;
  if (ioctl(fd, CDROMREADTOCENTRY, &tocentry) == -1) {
    perror("CDROMREADTOCENTRY");
    return;
  }

  toc->leadout_track.track_mode = (tocentry.cdte_ctrl & 0x04) ? 1 : 0;
  toc->leadout_track.first_frame_minute = tocentry.cdte_addr.msf.minute;
  toc->leadout_track.first_frame_second = tocentry.cdte_addr.msf.second;
  toc->leadout_track.first_frame_frame = tocentry.cdte_addr.msf.frame;
  toc->leadout_track.first_frame = 
    (tocentry.cdte_addr.msf.minute * CD_SECONDS_PER_MINUTE * CD_FRAMES_PER_SECOND) +
    (tocentry.cdte_addr.msf.second * CD_FRAMES_PER_SECOND) +
     tocentry.cdte_addr.msf.frame;
}

void read_cdrom_frame(int fd, int frame, 
  unsigned char data[CD_RAW_FRAME_SIZE]) {

  struct cdrom_msf msf;

  /* read from starting frame... */
  msf.cdmsf_min0 = frame / CD_SECONDS_PER_MINUTE / CD_FRAMES_PER_SECOND;
  msf.cdmsf_sec0 = (frame / CD_FRAMES_PER_SECOND) % CD_SECONDS_PER_MINUTE;
  msf.cdmsf_frame0 = frame % CD_FRAMES_PER_SECOND;

  /* read until ending track (starting frame + 1)... */
  msf.cdmsf_min1 = (frame + 1) / CD_SECONDS_PER_MINUTE / CD_FRAMES_PER_SECOND;
  msf.cdmsf_sec1 = ((frame + 1) / CD_FRAMES_PER_SECOND) % CD_SECONDS_PER_MINUTE;
  msf.cdmsf_frame1 = (frame + 1) % CD_FRAMES_PER_SECOND;

  /* MSF structure is the input to the ioctl */
  memcpy(data, &msf, sizeof(msf));

  /* read a frame */
  if(ioctl(fd, CDROMREADRAW, data, data) < 0) {
    perror("CDROMREADRAW");
    return;
  }
}

#endif


#ifdef THIS_IS_SUN

#include <sys/dkio.h>
#include <sys/scsi/generic/commands.h>
#include <sys/scsi/impl/uscsi.h>

void read_cdrom_toc(int fd, cdrom_toc *toc) {
}

void read_cdrom_frame(int fd, int frame, 
  unsigned char data[CD_RAW_FRAME_SIZE]) {
}

#endif

#ifdef THIS_IS_FREEBSD

#include <sys/cdio.h>

void read_cdrom_toc(int fd, cdrom_toc *toc) {

  struct ioc_toc_header tochdr;
  struct ioc_read_toc_entry te;
  struct cd_toc_entry *tocent;
  int i;

  /* read TOC header */
  if ( ioctl(fd, CDIOREADTOCHEADER, &tochdr) == -1 ) {
    printf ("error in ioctl CDROMREADTOCHDR\n");
    return -1;
  }

  toc->first_track = tochdr.starting_track;
  toc->last_track = tochdr.ending_track;
  toc->total_tracks = toc->last_track - toc->first_track + 1;

  /* allocate space for all of the toc entries */
  tocent = (struct cd_toc_entry *)
    malloc(sizeof(struct cd_toc_entry) * toc->total_tracks);

  te.address_format = CD_MSF_FORMAT;
  te.starting_track = 0;
  te.data_len = toc->total_tracks * sizeof(struct cd_toc_entry);
  te.data = this->tocent;

  /* fetch the whole toc */
  if ( ioctl(fd, CDIOREADTOCENTRYS, &te) == -1 ){
    printf ("error in ioctl CDROMREADTOCENTRY\n");
    return;
  }

  /* allocate space for the internal toc entries */
  toc->toc_entries = 
    (cdrom_toc_entry *)malloc(toc->total_tracks * sizeof(cdrom_toc_entry));
  if (!toc->toc_entries) {
    perror("malloc");
    free(tocent);
    return;
  }

  /* copy over the relevant information from each toc entry */
  for (i = 0; i < toc->total_tracks; i++) {

    toc->toc_entries[i-1].track_mode = tocentry.cdte_datamode;
    toc->toc_entries[i-1].first_frame_minute = tocentry.cdte_addr.msf.minute;
    toc->toc_entries[i-1].first_frame_second = tocentry.cdte_addr.msf.second;
    toc->toc_entries[i-1].first_frame_frame = tocentry.cdte_addr.msf.frame;
    toc->toc_entries[i-1].first_frame = 

  }
}

void read_cdrom_frame(int fd, int frame, 
  unsigned char data[CD_RAW_FRAME_SIZE]) {
}

#endif


int main(int argc, char *argv[]) {

  int fd;
  int status;
  cdrom_toc toc;
  unsigned char frame[CD_RAW_FRAME_SIZE];
  int i, j, k;
  int iso_start_index;

  printf ("CD Detection Experiment\n");
  if (argc < 2) {
    printf ("Usage: cdexp </path/to/cdrom/device>\n\n");
    return 0;
  }

  /* open CD-ROM */
  fd = open(argv[1], O_RDONLY | O_NONBLOCK);
  if (fd == -1) {
    perror(argv[1]);
    return 1;
  }

  read_cdrom_toc(fd, &toc);
  printf ("toc:\n  first track = %d\n   last track = %d\n\ntoc entries:\n",
    toc.first_track, toc.last_track);

printf ("leadout track: MSF: %02d:%02d:%02d, first frame = %d\n",
      toc.leadout_track.first_frame_minute,
      toc.leadout_track.first_frame_second,
      toc.leadout_track.first_frame_frame,
      toc.leadout_track.first_frame);

  for (i = toc.first_track; i <= toc.last_track; i++) {

    printf ("track %2d, %s, MSF: %02d:%02d:%02d, first frame = %d\n", 
      i,
      (toc.toc_entries[i-1].track_mode == 0) ? "audio" : " data",
      toc.toc_entries[i-1].first_frame_minute,
      toc.toc_entries[i-1].first_frame_second,
      toc.toc_entries[i-1].first_frame_frame,
      toc.toc_entries[i-1].first_frame);

    /* if this is a data track, get the 16th frame and look for data mode
     * and iso9660 signature ('CD001') */
    if (toc.toc_entries[i-1].track_mode == 1) {
      read_cdrom_frame(fd, toc.toc_entries[i-1].first_frame+16, frame);

/*
for (j = 0; j < 4; j++) {
  for (k = 0; k < 16; k++) {
    printf (" %02X", frame[j * 16 + k]);
  }
  printf ("\n");
}
*/
      if (frame[0x0F] == 1) {
        printf (" mode 1 data\n");
        iso_start_index = 0x10;
      } else if (frame[0x0F]) {
        if (frame[0x12] & 0x20) {
          printf (" mode 2, form 2 data\n");
        } else {
          printf (" mode 2, form 1 data\n");
        }
        iso_start_index = 0x18;
      }

      if ((frame[iso_start_index + 1] == 'C') &&
          (frame[iso_start_index + 2] == 'D') &&
          (frame[iso_start_index + 3] == '0') &&
          (frame[iso_start_index + 4] == '0') &&
          (frame[iso_start_index + 5] == '1')) {

        printf (" iso9660 fs signature found\n");
        printf (" system id = ");
        for (j = 8; j < 40; j++) {
          printf ("%c", frame[iso_start_index + j]);
        }
        printf ("\n");

        printf (" volume id = ");
        for (j = 40; j < 72; j++) {
          printf ("%c", frame[iso_start_index + j]);
        }
        printf ("\n");
      }
    }
  }

  /* close CD-ROM */
  status = close(fd);
  if (status != 0)
    printf ("close() returned status %d\n", status);

  return 0;
}
