/*  DVD.c
 *  Copyright (C) 2002-2005  PCSX2 Team
 *
 *  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
 *
 *  PCSX2 members can be contacted through their website at www.pcsx2.net.
 */

#include <errno.h> // errno
#include <stddef.h> // NULL
#include <stdio.h> // sprintf()
#include <string.h> // strerror(), memset(), memcpy()
#include <fcntl.h> // open()
#include <sys/ioctl.h> // ioctl()
#include <sys/stat.h> // open()
#include <sys/types.h> // lseek(), open()
#include <unistd.h> // close(), lseek(), (sleep())

#include <linux/cdrom.h> // CD/DVD based ioctl() and defines.

#include "logfile.h"
#include "device.h"
#include "DVD.h"

#include "../PS2Etypes.h" // u8, u32


// Constants
u8 *playstationname = "PLAYSTATION\0";

// DVD storage structures (see linux/cdrom.h for details)
dvd_struct dvdphysical;
dvd_struct dvdcopyright[DVD_LAYERS];
dvd_struct dvdbca;
dvd_struct dvdmanufact[DVD_LAYERS];

u32 dvdlastlsn;
u8 dvdtempbuffer[2064];


// Internal Functions

void InitDVDSectorInfo() {
  dvdlastlsn = 0xffffffff;
} // END InitSectorInfo();

void HexDump(u8 *strptr, u8 count) {
  int i;
  u8 ch[2];
  char hexline[81];
  int hexlinepos;

  ch[1] = 0;

  if(count == 0)  count = 16;
  if((count < 1) || (count > 16))  return;

  hexlinepos = 0;
  hexlinepos += sprintf(&hexline[hexlinepos], "CDVD driver: ");

  for(i = 0; i < count; i++) {
    hexlinepos += sprintf(&hexline[hexlinepos], "%.2x ", (*(strptr + i)) * 1);
  } // NEXT i- printing each new Hex number

  for(i = 0; i < count; i++) {
    if((*(strptr + i) < 32) || (*(strptr + i) > 127)) {
      hexlinepos += sprintf(&hexline[hexlinepos], ".");
    } else {
      ch[0] = *(strptr + i);
      hexlinepos += sprintf(&hexline[hexlinepos], "%s", ch);
    } // ENDIF- Is this an unprintable character?
  } // NEXT i- printing each new character
  PrintLog(hexline);
} // ENDIF HexDump()


//// DVD Structure Functions

s32 DVDreadPhysical() {
  s32 s32result;
  u8 i;

  errno = 0;

#ifdef VERBOSE_FUNCTION
  PrintLog("CDVD driver: DVDreadPhysical()\n");
#endif /* VERBOSE_FUNCTION */

  memset(&dvdphysical, 0, sizeof(dvd_struct));
  dvdphysical.type = DVD_STRUCT_PHYSICAL;

  i = DVD_LAYERS;
  while(i > 0) {
    i--;
    dvdphysical.physical.layer_num = i;
    errno = 0;
    s32result = ioctl(devicehandle, DVD_READ_STRUCT, &dvdphysical);
  } // ENDWHILE- reading in all physical layers...

  if((s32result == -1) || (errno != 0)) {
    dvdphysical.type = 0xFF;
#ifdef VERBOSE_WARNINGS
    PrintLog("CDVD driver:   Error getting Physical structure: (%i) %i:%s", s32result, errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
    return(-1);
  } // ENDIF- Problem with reading Layer 0 of the physical data? Abort

  i = 3;
  while((i > 0) && (dvdphysical.physical.layer[i].end_sector == 0))  i--;
  dvdphysical.physical.layer_num = i;

#ifdef VERBOSE_DISC_INFO
  PrintLog("CDVD driver: Physical Characteristics");
  PrintLog("CDVD driver:   Number of Layers: %i",
         (s32) dvdphysical.physical.layer_num + 1);
  for(i = 0; i <= dvdphysical.physical.layer_num; i++) {
    PrintLog("CDVD driver:     Layer Number %i", i);
    switch(dvdphysical.physical.layer[i].book_type) {
      case 0:
        PrintLog("CDVD driver:       Book Type: DVD-ROM");
        break;
      case 1:
        PrintLog("CDVD driver:       Book Type: DVD-RAM");
        break;
      case 2:
        PrintLog("CDVD driver:       Book Type: DVD-R");
        break;
      case 3:
        PrintLog("CDVD driver:       Book Type: DVD-RW");
        break;
      case 9:
        PrintLog("CDVD driver:       Book Type: DVD+RW");
        break;
      default:
        PrintLog("CDVD driver:       Book Type: Unknown (%i)", 
                 dvdphysical.physical.layer[i].book_type);
        break;
    } // ENDSWITCH- Displaying the Book Type
    PrintLog("CDVD driver:       Book Version %i", 
             dvdphysical.physical.layer[i].book_version);
    switch(dvdphysical.physical.layer[i].min_rate) {
      case 0:
        PrintLog("CDVD driver:       Use Minimum Rate for: DVD-ROM");
        break;
      case 1:
        PrintLog("CDVD driver:       Use Minimum Rate for: DVD-RAM");
        break;
      case 2:
        PrintLog("CDVD driver:       Use Minimum Rate for: DVD-R");
        break;
      case 3:
        PrintLog("CDVD driver:       Use Minimum Rate for: DVD-RW");
        break;
      case 9:
        PrintLog("CDVD driver:       Use Minimum Rate for: DVD+RW");
        break;
      default:
        PrintLog("CDVD driver:       Use Minimum Rate for: Unknown (%i)", 
                 dvdphysical.physical.layer[i].min_rate);
        break;
    } // ENDSWITCH- Displaying the Minimum (Spin?) Rate
    switch(dvdphysical.physical.layer[i].disc_size) {
      case 0:
        PrintLog("CDVD driver:       Physical Disk Size: 120mm");
        break;
      case 1:
        PrintLog("CDVD driver:       Physical Disk Size: 80mm");
        break;
      default:
        PrintLog("CDVD driver:       Physical Disk Size: Unknown (%i)", 
                 dvdphysical.physical.layer[i].disc_size);
        break;
    } // ENDSWITCH- What's the Disk Size?
    switch(dvdphysical.physical.layer[i].layer_type) {
      case 1:
        PrintLog("CDVD driver:       Layer Type: Read-Only");
        break;
      case 2:
        PrintLog("CDVD driver:       Layer Type: Recordable");
        break;
      case 4:
        PrintLog("CDVD driver:       Layer Type: Rewritable");
        break;
      default:
        PrintLog("CDVD driver:       Layer Type: Unknown (%i)", 
                 dvdphysical.physical.layer[i].layer_type);
        break;
    } // ENDSWITCH- Displaying the Layer Type
    switch(dvdphysical.physical.layer[i].track_path) {
      case 0:
        PrintLog("CDVD driver:       Track Path: PTP");
        break;
      case 1:
        PrintLog("CDVD driver:       Track Path: OTP");
        break;
      default:
        PrintLog("CDVD driver:       Track Path: Unknown (%i)", 
                 dvdphysical.physical.layer[i].track_path);
        break;
    } // ENDSWITCH- What's Track Path Layout?
    // PrintLog("CDVD driver:       Disc Size %i   Layer Type %i   Track Path %i   Nlayers %i",
    //          dvdphysical.physical.layer[i].nlayers);
    switch(dvdphysical.physical.layer[i].track_density) {
      case 0:
        PrintLog("CDVD driver:       Track Density: .74 m/track");
        break;
      case 1:
        PrintLog("CDVD driver:       Track Density: .8 m/track");
        break;
      case 2:
        PrintLog("CDVD driver:       Track Density: .615 m/track");
        break;
      default:
        PrintLog("CDVD driver:       Track Density: Unknown (%i)", 
                 dvdphysical.physical.layer[i].track_density);
        break;
    } // ENDSWITCH- Displaying the Track Density
    switch(dvdphysical.physical.layer[i].linear_density) {
      case 0:
        PrintLog("CDVD driver:       Linear Density: .267 m/bit");
        break;
      case 1:
        PrintLog("CDVD driver:       Linear Density: .293 m/bit");
        break;
      case 2:
        PrintLog("CDVD driver:       Linear Density: .409 to .435 m/bit");
        break;
      case 4:
        PrintLog("CDVD driver:       Linear Density: .280 to .291 m/bit");
        break;
      case 8:
        PrintLog("CDVD driver:       Linear Density: .353 m/bit");
        break;
      default:
        PrintLog("CDVD driver:       Linear Density: Unknown (%i)", 
                 dvdphysical.physical.layer[i].linear_density);
        break;
    } // ENDSWITCH- Displaying the Linear Density
    if(dvdphysical.physical.layer[i].start_sector == 0x30000) {
      PrintLog("CDVD driver:       Starting Sector: %lu (DVD-ROM, DVD-R, DVD-RW)",
               dvdphysical.physical.layer[i].start_sector);
    } else if(dvdphysical.physical.layer[i].start_sector == 0x31000) {
      PrintLog("CDVD driver:       Starting Sector: %lu (DVD-RAM, DVD+RW)",
               dvdphysical.physical.layer[i].start_sector);
    } else {
      PrintLog("CDVD driver:       Starting Sector: %lu", 
               dvdphysical.physical.layer[i].start_sector);
    } // ENDLONGIF- What does the starting sector tell us?
    PrintLog("CDVD driver:       End of Layer 0: %lu", 
             dvdphysical.physical.layer[i].end_sector_l0);
    PrintLog("CDVD driver:       Ending Sector: %lu", 
             dvdphysical.physical.layer[i].end_sector);
    if(dvdphysical.physical.layer[i].bca != 0)
      PrintLog("CDVD driver:       BCA data present");
  } // NEXT i- Work our way through each layer...
#endif /* VERBOSE_DISC_INFO */

  return(0); // Success. Physical data stored for perusal.
} // END DVDreadPhysical()

s32 DVDreadCopyright() {
  s32 s32result;
  u8 i;
  int successflag;

#ifdef VERBOSE_FUNCTION
  PrintLog("CDVD driver: DVDreadCopyright()");
#endif /* VERBOSE_FUNCTION */

  successflag = 0;

  for(i = 0; i <= dvdphysical.physical.layer_num; i++) {
    memset(&dvdcopyright[i], 0, sizeof(dvd_struct));
    dvdcopyright[i].type = DVD_STRUCT_COPYRIGHT;
    dvdcopyright[i].copyright.layer_num = i;
    errno = 0;
    s32result = ioctl(devicehandle, DVD_READ_STRUCT, &dvdcopyright[i]);
    if(s32result == 0) {
      successflag = 1;
    } else {
      dvdcopyright[i].type = 0xFF;
    } // ENDIF-
  } // NEXT i- Getting copyright data for every known layer

  if(successflag == 0) {
#ifdef VERBOSE_WARNINGS
    PrintLog("CDVD driver:   Error getting Copyright info: (%i) %i:%s", s32result, errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
    return(-1);
  } // ENDIF- Problem with read of physical data?

#ifdef VERBOSE_DISC_INFO
  PrintLog("CDVD driver: Copyright Information\n");
  for(i = 0; i <= dvdphysical.physical.layer_num; i++) {
    if(dvdcopyright[i].type != 0xFF) {
      PrintLog("CDVD driver:   Layer Number %i   CPST %i   RMI %i",
               dvdcopyright[i].copyright.layer_num,
               dvdcopyright[i].copyright.cpst,
               dvdcopyright[i].copyright.rmi);
    } // ENDIF- Were we successful reading this one?
  } // NEXT i- Printing out all copyright info found...
#endif /* VERBOSE_DISC_INFO */

  errno = 0;
  return(0); // Success. Copyright data stored for perusal.
} // END DVDreadCopyright()

s32 DVDreadBCA() {
  s32 s32result;
  int i;

  i = 0;
  errno = 0;

#ifdef VERBOSE_FUNCTION
  PrintLog("CDVD driver: DVDreadBCA()");
#endif /* VERBOSE_FUNCTION */

  memset(&dvdbca, 0, sizeof(dvd_struct));
  dvdbca.type = DVD_STRUCT_BCA;
  s32result = ioctl(devicehandle, DVD_READ_STRUCT, &dvdbca);
  if((s32result == -1) || (errno != 0)) {
    dvdbca.type = 0xFF;
#ifdef VERBOSE_WARNINGS
    PrintLog("CDVD driver:   Error getting BCA: (%i) %i:%s", s32result, errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
    return(-1);
  } // ENDIF- Problem with read of physical data?

#ifdef VERBOSE_DISC_INFO
  PrintLog("CDVD driver: BCA   Length %i   Value:",
         dvdbca.bca.len);
  for(i = 0; i < 188-15; i += 16) {
    HexDump(dvdbca.bca.value+i, 16);
  } // NEXT i- dumping whole key data
#endif /* VERBOSE_DISC_INFO */

  return(0); // Success. BCA data stored for perusal.
} // END DVDreadBCA()

s32 DVDreadManufact() {
  s32 s32result;
  u8 i;
  int successflag;
  int j;

#ifdef VERBOSE_FUNCTION
  PrintLog("CDVD driver: DVDreadManufact()");
#endif /* VERBOSE_FUNCTION */

  j = 0;
  successflag = 0;

  for(i = 0; i <= dvdphysical.physical.layer_num; i++) {
    memset(&dvdmanufact[i], 0, sizeof(dvd_struct));
    dvdmanufact[i].type = DVD_STRUCT_MANUFACT;
    dvdmanufact[i].manufact.layer_num = i;
    errno = 0;
    s32result = ioctl(devicehandle, DVD_READ_STRUCT, &dvdmanufact[i]);
    if((s32result != 0) || (errno != 0)) {
      dvdmanufact[i].type = 0xFF;
    } else {
      successflag = 1;
    } // ENDIF- Did we fail to read in some manufacturer data?
  } // NEXT i- Collecting manufacturer data from all layers

  if(successflag == 0) {
#ifdef VERBOSE_WARNINGS
    PrintLog("CDVD driver:   Error getting Manufact: (%i) %i:%s", s32result, errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
    return(-1);
  } // ENDIF- Problem with read of physical data?

#ifdef VERBOSE_DISC_INFO
  PrintLog("CDVD driver: Manufact Data");
  for(i = 0; i <= dvdphysical.physical.layer_num; i++) {
    if(dvdmanufact[i].type != 0xFF) {
      PrintLog("CDVD driver:   Layer %i   Length %i   Value:",
               dvdmanufact[i].manufact.layer_num,
               dvdmanufact[i].manufact.len);
      for(j = 0; j < 128-15; j += 16) {
        HexDump(dvdmanufact[i].manufact.value+j, 16);
      } // NEXT j- dumping whole key data
    } // ENDIF- Do we have data at this layer?
  } // NEXT i- Running through all the layers
#endif /* VERBOSE_DISC_INFO */

  errno = 0;
  return(0); // Success. Manufacturer's data stored for perusal.
} // END DVDreadManufact()


// External Functions

// Function Calls from CDVD.c

void InitDVDInfo() {
  int j;

  dvdphysical.type = 0xFF; // Using for empty=0xff, full!=0xff test
  dvdbca.type = 0xFF;
  for(j = 0; j < DVD_LAYERS; j++) {
    dvdcopyright[j].type = 0xFF;
    dvdmanufact[j].type = 0xFF;
  } // NEXT j- Zeroing each layer of data
  InitDVDSectorInfo();
} // END InitDiscType()

s32 DVDreadTrack(u32 lsn, int mode, u8 *buffer) {
  s32 s32result;
  off64_t offsettarget;
  off64_t offsetresult;

  errno = 0;
  s32result = 0;
  offsetresult = 0;

#ifdef VERBOSE_FUNCTION
  PrintLog("CDVD driver: DVDreadTrack(%i)", lsn);
#endif /* VERBOSE_FUNCTION */

  if(lsn != dvdlastlsn + 1) {
    offsettarget = lsn;
    offsettarget *= 2048;
    errno = 4;
    while(errno == 4) {
      errno = 0;
      offsetresult = lseek64(devicehandle, offsettarget, SEEK_SET);
    } // ENDWHILE- waiting for the system interruptions to cease.
    if((offsetresult < 0) || (errno != 0)) {
#ifdef VERBOSE_WARNINGS
      PrintLog("CDVD driver:   Error on seek: %i:%s", errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
      InitDVDSectorInfo();
      return(-1);
    } // ENDIF- trouble with seek? Reset pointer and abort
  } // ENDIF- Do we have to seek a new position to read?

  errno = 4;
  while(errno == 4) {
    errno = 0;
    s32result = read(devicehandle, buffer, 2048);
  } // ENDWHILE- waiting for the system interruptions to cease.
  if((s32result != 2048) || (errno != 0)) {
#ifdef VERBOSE_WARNINGS
    PrintLog("CDVD driver:   DVD Short Block, Size: %i", s32result);
    PrintLog("CDVD driver:     Error: %i:%s", errno, strerror(errno));
#endif /* VERBOSE_WARNINGS */
    InitDVDSectorInfo();
    return(-1);
  } // ENDIF- Trouble reading the data? Reset pointer and abort

  dvdlastlsn = lsn;
  return(0); // Call accomplished
} // END DVDreadTrack()

s32 DVDgetTN(cdvdTN *cdvdtn) {
#ifdef VERBOSE_FUNCTION
  PrintLog("CDVD driver: CDVDgetTN()");
#endif /* VERBOSE_FUNCTION */

  // Going to treat this as one large track for now.
  if(cdvdtn != NULL) {
    cdvdtn->strack = 1;
    cdvdtn->etrack = 1;
  } // ENDIF- programmer actually WANTS this info?

  return(0); // Call accomplished
} // END DVDgetTN()

s32 DVDgetTD(u8 newtrack, cdvdTD *cdvdtd) {
#ifdef VERBOSE_FUNCTION
  PrintLog("CDVD driver: CDVDgetTD()");
#endif /* VERBOSE_FUNCTION */

  if((newtrack >= 2) && (newtrack != CDROM_LEADOUT))  return(-1);

  if(cdvdtd != NULL) {
    cdvdtd->lsn = dvdphysical.physical.layer[0].end_sector
                - dvdphysical.physical.layer[0].start_sector
                + 1;
    cdvdtd->type = CDVD_MODE_2048;
  } // ENDIF- Does the caller REALLY want this data?

  return(0); // Call accomplished
} // END DVDgetTD()

s32 DVDgetDiskType(s32 ioctldisktype) {
  s32 s32result;
  int i;
  s32 tempdisctype;
  
  errno = 0;
  s32result = 0;
  i = 0;
  tempdisctype = CDVD_TYPE_UNKNOWN;

#ifdef VERBOSE_FUNCTION
  PrintLog("CDVD driver: DVDgetDiskType()");
#endif /* VERBOSE_FUNCTION */

  if((ioctldisktype != CDS_DATA_1) && (ioctldisktype != CDS_MIXED)) {
    return(-1);
  } // ENDIF- Not a data disc we know of? Abort then

  s32result = DVDreadPhysical();
  if((s32result != 0) || (errno != 0)) {
    return(-1);
  } // ENDIF- Error reading the DVD physical structure? Not a DVD after all.

  if(dvdphysical.physical.layer[0].end_sector >= (2048*1024)) {
#ifdef VERBOSE_DISC_TYPE
    PrintLog("CDVD driver: DVD Found (Dual-Sided)");
#endif /* VERBOSE_DISC_TYPE */
    disctype = CDVD_TYPE_DETCTDVDD;
  } else {
#ifdef VERBOSE_DISC_TYPE
    PrintLog("CDVD driver: DVD Found (Single-Sided)");
#endif /* VERBOSE_DISC_TYPE */
    disctype = CDVD_TYPE_DETCTDVDS;
  } // ENDIF- Definitely a DVD. Size Test?

  // Read in the rest of the structures...
  DVDreadCopyright();
  DVDreadBCA();
  DVDreadManufact();

  // Test for "Playstation" header
  s32result = DVDreadTrack(16, CDVD_MODE_2048, dvdtempbuffer);
  if(s32result != 0) {
    return(-1);
  } else {
    i = 0;
    while((*(playstationname + i) != 0) &&
          (*(playstationname + i) == dvdtempbuffer[8 + i])) {
      i++;
    } // ENDWHILE- Checking each letter of PLAYSTATION name for a match
    if(*(playstationname + i) == 0) {
#ifdef VERBOSE_DISC_TYPE
      PrintLog("CDVD driver: Detected Playstation 2 DVD");
#endif /* VERBOSE_DISC_TYPE */
      tempdisctype = CDVD_TYPE_PS2DVD;
    } else {
#ifdef VERBOSE_DISC_TYPE
      PrintLog("CDVD driver: Guessing it's a Video DVD");
#endif /* VERBOSE_DISC_TYPE */
      tempdisctype = CDVD_TYPE_DVDV;
    } // ENDIF- Did we find the Playstation name?
  } // ENDIF- Error reading disc volume information? Invalidate Disc

  if(dvdphysical.physical.layer[0].end_sector >= (2048*1024)) {
    tocbuffer[0] = 0x24; // Dual-Sided DVD
    tocbuffer[4] = 0x41;
    tocbuffer[5] = 0x95;
  } else {
    tocbuffer[0] = 0x04; // Single-Sided DVD
    tocbuffer[4] = 0x86;
    tocbuffer[5] = 0x72;
  } // ENDIF- Are there too many sectors for a single-sided disc?

  tocbuffer[1] = 0x02;
  tocbuffer[2] = 0xF2;
  tocbuffer[3] = 0x00;

  tocbuffer[16] = 0x00;
  tocbuffer[17] = 0x03;
  tocbuffer[18] = 0x00;
  tocbuffer[19] = 0x00;

  disctype = tempdisctype; // Triggers the fact the other info is available
  return(disctype);
} // END DVDgetDiskType()
