/*  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()

