/*
 * DVD Media Info utility by Andy Polyakov <appro@fy.chalmers.se>.
 *
 * This code is in public domain.
 */

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include "transport.hxx"

int main(int argc,char *argv[])
{ Scsi_Command	cmd;
  unsigned char	header[32],inq[36],*page2A=NULL;
  int		i,j,ntracks,err,dvd_minus=0;
  unsigned short profile;

    if (argc<2)
	fprintf (stderr,"usage: %s /dev/cdrom\n",argv[0]),
	exit (1);

    if (!cmd.associate(argv[1]))
	fprintf (stderr,"%s: unable to open: ",argv[1]),
	perror (NULL),
	exit (1);

    if (argc>1)
    {	cmd[0] = 0x12;	// INQUIRY
	cmd[4] = sizeof(inq);
	cmd[5] = 0;
	if ((err=cmd.transport(READ,inq,sizeof(inq))))
	    fprintf (stderr,"- [unable to INQUIRY(%X)]: ",err),
	    perror (NULL),
	    exit (1);

	printf ("INQUIRY:                [%.8s][%.16s][%.4s]\n",
		inq+8,inq+16,inq+32);
    }

    if (argc>2) do
    { unsigned char *pages;
      int len,n;
      static int pagecode=0x3F;

	printf ("MODE SENSE[#%02Xh]:\n",pagecode);

	cmd[0] = 0x5A;		// MODE SENSE
	cmd[1] = 0x08;		// "Disable Block Descriptors"
	cmd[2] = pagecode;	// initially "All Pages"
	cmd[8] = 12;
	cmd[9] = 0;
	if ((err=cmd.transport(READ,header,12)))
	    fprintf (stderr,"- [unable to MODE SENSE#%02X(%X)]: ",pagecode,err),
	    perror (NULL),
	    exit(1);

	if (header[6]<<8|header[7])
	    fprintf (stderr,"- [\"DBD\" is not respected]\n"),
	    exit(1);

	len=(header[0]<<8|header[1])+2;

	if (len < (8+2+header[9]))
	{   if (pagecode == 0x3F)
	    {	pagecode = 0x5;
		continue;
	    }
	    len = 8+2+header[9];
	}

	if (!(pages=(unsigned char *)malloc(len))) exit(1);

	cmd[0] = 0x5A;
	cmd[1] = 0x08;
	cmd[2] = pagecode;
	cmd[7] = len>>8;
	cmd[8] = len;
	cmd[9] = 0;
	cmd.transport(READ,pages,len);

	for(i=8+(pages[6]<<8|pages[7]);i<len;)
	{   printf (" %02X:",pages[i++]);
	    n=pages[i++];
	    for (j=0;j<n;j++)
	    {	if (j && j%16==0) printf("\n");
		printf ("%c%02x",(j%16)?' ':'\t',pages[i++]);
	    }
	    printf("\n");
	}

	free(pages);

	break;

    } while (1);

    { int len,n;

	page2A=header, len=sizeof(header);

	while (1)
	{   cmd[0] = 0x5A;	// MODE SENSE(10)
	    cmd[1] = 0x08;	// "Disable Block Descriptors"
	    cmd[2] = 0x2A;	// "Capabilities and Mechanical Status"
	    cmd[7] = len>>8;
	    cmd[8] = len;
	    cmd[9] = 0;
	    if ((err=cmd.transport(READ,page2A,len)))
		fprintf (stderr,"- [unable to MODE SENSE#2A(%X)]: ",err),
		exit (1);

	    if (page2A[6]<<8|page2A[7])
		fprintf (stderr,"- [\"DBD\" is not respected]\n");
	    else if (len<((page2A[0]<<8|page2A[1])+2) || len<(8+2+page2A[9]))
	    {	len=(page2A[0]<<8|page2A[1])+2;
		if (len < (8+2+page2A[9]))
		    len = 8+2+page2A[9];
		page2A=(unsigned char *)malloc(len);
		continue;
	    }

	    if (!(page2A[8+(page2A[6]<<8|page2A[7])+2]&8))
		fprintf (stderr,"- [not a DVD unit]\n"),
		exit (1);

	    if (page2A==header) page2A=NULL;
	    else if (argc>2)
	    {	printf("MODE SENSE[#2A]:\n");
		for(i=8+(page2A[6]<<8|page2A[7]);i<len;)
		{   printf (" %02X:",page2A[i++]);
		    n=page2A[i++];
		    for (j=0;j<n;j++)
		    {	if (j && j%16==0) printf("\n");
			printf ("%c%02x",(j%16)?' ':'\t',page2A[i++]);
		    }
		    printf("\n");
		}
	    }

	    break;
	}
    }

    cmd[0] = 0x46;	// GET CONFIGURATION
    cmd[1] = 1;
    cmd[8] = 8;
    cmd[9] = 0;
    if ((err=cmd.transport(READ,header,8)))
    {	if (err!=0x52000)	// "IVALID COMMAND OPERATION CODE"
	    fprintf (stderr,"- [unable to GET CONFIGURATION(%X)]: ",err),
	    perror (NULL),
	    exit(1);
	profile=0x10;	// non MMC3 DVD-ROM unit?
	goto legacy;
    }

    profile=header[6]<<8|header[7];

    printf ("GET [CURRENT] CONFIGURATION:\n");

    if (argc>2)
    { unsigned char *profiles;
      int len,n;

	len=header[2]<<8|header[3];
	len+=4;

	if (!(profiles=(unsigned char *)malloc(len))) exit(1);

	cmd[0] = 0x46;
	cmd[1] = 1;
	cmd[6] = len>>16;
	cmd[7] = len>>8;
	cmd[8] = len;
	cmd[9] = 0;
	cmd.transport(READ,profiles,len);

	for(i=8;i<len;)
	{   printf (" %04X%c",profiles[i]<<8|profiles[i+1],':');
	    n=profiles[i+3]; i+=4;
	    for (j=0;j<n;j++)
	    {	if (j && j%16==0) printf("\n");
		printf ("%c%02x",(j%16)?' ':'\t',profiles[i++]);
	    }
	    printf("\n");
	}

	free(profiles);
    }

    if (profile==0)
	fprintf (stderr,"- [no media mounted, exiting...]\n"),
	exit(1);
    if ((profile&0xF0) != 0x10)
	fprintf (stderr,"- [non-DVD media mounted, exiting...]\n"),
	exit (1);

    { const char *str;

	switch (profile)
	{   case 0x10:	str="-ROM";			break;
	    case 0x11:	str="-R Sequential";		break;
	    case 0x12:	str="-RAM";			break;
	    case 0x13:	str="-RW Restricted Overwrite";	break;
	    case 0x14:	str="-RW Sequential";		break;
	    case 0x1A:	str="+RW";			break;
	    case 0x1B:	str="+R";			break;
	    default:	str="unknown";			break;
	}
	printf (" Mounted Media:         %Xh, DVD%s\n",profile,str);
	if (str[0]=='-' && str[3]!='M') dvd_minus=0x10;
    }

    while (dvd_minus||1)
    { unsigned char dvd_e[4+40];
      unsigned int len;

	cmd[0] = 0xAD;
	cmd[7] = 0xE;
	cmd[9] = 4;
	cmd[11] = 0;
	if ((err=cmd.transport(READ,header,4)))
	{   if (argc>2)
		fprintf (stderr,"- [unable to READ DVD STRUCTURE#E (%X)]\n",err);
	    break;
	}

	len = (header[0]<<8|header[1]) + 2;
	if (len>sizeof(dvd_e)) len=sizeof(dvd_e);

	cmd[0] = 0xAD;
	cmd[7] = 0xE;
	cmd[8] = len>>8;
	cmd[9] = len;
	cmd[11] = 0;
	if ((err=cmd.transport(READ,dvd_e,len)))
	    break;

	printf ("READ DVD STRUCTURE[#E]:");
	if (argc>2)
	    for (j=0,i=len;j<i;j++) printf("%c%02x",j?' ':'\t',dvd_e[j]);
	printf("\n");
#if 0
	if (dvd_e[4+0]==1)
	printf (" Data Recordable Area:  %u\n",
		dvd_e[4+3]<<16|dvd_e[4+4]<<8|dvd_e[4+5]);
#endif
	if (dvd_e[4+16]==3 && dvd_e[4+24]==4)
	printf (" MediaID:               %6.6s%-6.6s\n",dvd_e+4+17,dvd_e+4+25);

	break;
    }

    while (page2A)
    { int len,hlen,v,n=0;
      unsigned char *p;

	len  = (page2A[0]<<8|page2A[1])+2;
	hlen = 8+(page2A[6]<<8|page2A[7]);
	if (len<(hlen+30)) break;	// no "Current Write Speed"

        p = page2A+hlen;

	v = p[28]<<8|p[29];
	// Check Write Speed descriptors for sanity. Some DVD+units
	// return CD-R descriptors here, which has no meaning in the
	// context of interest.
	if (v<1385)	break;
	if (len>=(hlen+32))
	{   n = (p[30]<<8|p[31])*4;
	    for (i=0;i<n;i+=4)
		if ((p[32+i+2]<<8|p[32+i+3]) < 1385) break;
	    if (i<n) break;
	}

	printf (" Current Write Speed:   %.1fx1385=%dKB/s\n",v/1385.0,v);

	if (len<(hlen+32)) break;	// no "Write Speed Descriptors"

	for (i=0;i<n;i+=4)
	v = p[32+i+2]<<8|p[32+i+3],
	printf (" Write Speed #%d:        %.1fx1385=%dKB/s\n",i/4,v/1385.0,v);

	break;
    }

legacy:

    header[4]=0;
    cmd[0] = 0xAD;	// READ DVD STRUCTURE
    cmd[7] = dvd_minus;
    cmd[9] = 20;
    cmd[11] = 0;
    if ((err=cmd.transport(READ,header,20)))
    {   if (dvd_minus) { dvd_minus=0; goto legacy; }
	fprintf (stderr,"- [unable to READ DVD STRUCTURE#%X (%X)]\n",
			dvd_minus,err);
    }
    else do
    { unsigned char book=header[4];
      const char *brand;
      unsigned int phys_start,phys_end;

	printf("READ DVD STRUCTURE[#%Xh]:",dvd_minus<0?0:dvd_minus);
	if (argc>2)
	    for (j=0;j<20-4;j++) printf("%c%02x",j?' ':'\t',header[4+j]);
	printf("\n");
	switch(book&0xF0)
	{   case 0x00:	brand="-ROM";	break;
	    case 0x10:	brand="-RAM";	break;
	    case 0x20:	brand="-R";	break;
	    case 0x30:	brand="-RW";	break;
	    case 0x90:	brand="+RW";	break;
	    case 0xA0:	brand="+R";	break;
	    default:	brand=NULL;	break;
	}
	printf (" Media Book Type:       %02Xh, ",book);
	if (brand)  printf ("DVD%s book [revision %d]\n",
			    brand,book&0xF);
	else	    printf ("unrecognized value\n");

	phys_start  = header[4+5]<<16;
	phys_start |= header[4+6]<<8;
	phys_start |= header[4+7];
	phys_end    = header[4+9]<<16;
	phys_end   |= header[4+10]<<8;
	phys_end   |= header[4+11];
	if (phys_end>0)	phys_end-=phys_start;
	if (phys_end>0) phys_end += 1;

	printf (" %s    %u*2KB=%llu\n",
		dvd_minus>=0?"Legacy lead-out at:":"Last border-out at:",
		phys_end,phys_end*2048LL);

	if (dvd_minus<=0) break;

	cmd[0] = 0xAD;
	cmd[7] = 0; dvd_minus=-1;
	cmd[9] = 20;
	cmd[11] = 0;
	if ((err=cmd.transport(READ,header,20)))
	{   fprintf (stderr,"- [unable to READ DVD-R STRUCTURE (%X)]: ",err),
	    perror (NULL);
	    break;
	}
    } while (1);

    if (profile==0x10 && (header[4]&0xF0==0))
	printf ("DVD-ROM media detected, exiting...\n"),
	exit(0);

    cmd[0] = 0x51;	// READ DISC INFORMATION
    cmd[8] = 32;
    cmd[9] = 0;
    if ((err=cmd.transport(READ,header,32)))
	fprintf (stderr,"- [unable to READ DISC INFORMATION(%X)]: ",err),
	perror (NULL),
	exit (1);

    { static const char
		   *session_state[]={	"empty",	"incomplete",
					"reserved/damaged","complete"	},
		   *disc_status[]={	"blank",	"appendable",
					"complete",	"other"		},
		   *bg_format[]={	"blank",	"suspended",
					"in progress",	"complete"	};
					
	printf ("READ DISC INFORMATION:");
	if (argc>2)
	    for (j=0;j<16;j++) printf("%c%02x",j?' ':'\t',header[j]);
	printf ("\n");
	printf (" Disc status:           %s\n",disc_status[header[2]&3]);
	printf (" Number of Sessions:    %d\n",header[9]<<8|header[4]);
	printf (" State of Last Session: %s\n",session_state[(header[2]>>2)&3]);
	if (header[2]&3!=2)
	printf (" \"Next\" Track:          %d\n",header[10]<<8|header[5]);
	printf (" Number of Tracks:      %d",ntracks=header[11]<<8|header[6]);
	if (header[7]&3)
	printf ("\n BG Format Status:      %s",bg_format[header[7]&3]);
	if ((header[7]&3) == 2)
	{ unsigned char sense[18];

	    cmd[0] = 0x03;	// REQUEST SENSE
	    cmd[4] = sizeof(sense);
	    cmd[5] = 0;
	    if (!cmd.transport (READ,sense,sizeof(sense)) && sense[15]&0x80)
		printf (", %.1f%% complete",(sense[16]<<8|sense[17])/655.36);
	}
	printf ("\n");

	while (header[2]&0x10 && argc>2)
	{ unsigned char formats[260];
	  int len;
	  unsigned int capacity,blocksize;
	  static const char *type[] = {	"reserved",	"unformatted",
					"formatted",	"no media"	};

	    printf ("READ FORMAT CAPACITIES:\n");

	    cmd[0] = 0x23;		// READ FORMAT CAPACITIES
	    cmd[8] = 12;
	    cmd[9] = 0;
	    if ((err=cmd.transport(READ,formats,12)))
	    {	fprintf (stderr,"- [unable to READ FORMAT CAPACITIES(%X)]: ",
				err),
		perror (NULL);
		break;
	    }

	    len = formats[3];
	    if (len&7 || len<16)
	    {	fprintf (stderr,"- [allocation length isn't sane %d]\n",len);
		break;
	    }

	    cmd[0] = 0x23;		// READ FORMAT CAPACITIES
	    cmd[7] = (4+len)>>8;	// now with real length...
	    cmd[8] = (4+len)&0xFF;
	    cmd[9] = 0;
	    if ((err=cmd.transport(READ,formats,4+len)))
	    {	fprintf (stderr,"- [unable to READ FORMAT CAPACITIES(%X)]: ",
				err),
		perror (NULL);
		break;
	    }

	    if (len != formats[3])
	    {	fprintf (stderr,"- [parameter length inconsistency]\n");
		break;
	    }

	    printf(" %s:\t\t%u*%u=",type[formats[8]&3],
	        capacity=formats[4]<<24|formats[5]<<16|formats[6]<<8|formats[7],
		blocksize=formats[9]<<16|formats[10]<<8|formats[11]);
	    printf("%llu\n",(unsigned long long)capacity*blocksize);

	    for(i=12;i<len;i+=8)
	    {	printf(" %02Xh(%x):\t\t%u*%u=",formats[i+4]>>2,
			formats[i+5]<<16|formats[i+6]<<8|formats[i+7],
	    		capacity=formats[i]<<24|formats[i+1]<<16|formats[i+2]<<8|formats[i+3],
			blocksize);
		printf("%llu\n",(unsigned long long)capacity*blocksize);
	    }
	    break;
	}
    }

    for (i=1;i<=ntracks;i++)
    { static const char *track_state[]={
				"complete",	"complete incremental",
				"blank",	"invisible incremental",
				"partial",	"partial incremental",
				"reserved",	"reserved incremental"};

	cmd[0] = 0x52;	// READ TRACK INFORMATION
	cmd[1] = 1;
	cmd[2] = i>>24;
	cmd[3] = i>>16;
	cmd[4] = i>>8;
	cmd[5] = i;
	cmd[8] = 32;
	cmd[9] = 0;
	if ((err=cmd.transport(READ,header,32)))
	    fprintf (stderr,"- [unable to READ TRACK INFORMATION(%X)]: ",err),
	    perror (NULL),
	    exit (1);

	printf ("READ TRACK INFORMATION[#%d]:",i);
	if (argc>2)
	    for (j=0;j<16;j++) printf("%c%02x",j?' ':'\t',header[j]);
	printf ("\n");
	printf (" Track State:           %s%s",
					 ((header[6]>>5)==1 && (header[7]&1))?"in":"",
					 track_state[header[6]>>5]);
	if (header[5]&0x20)	printf(",damaged");
	printf ("\n");
	printf (" Track Start Address:   %u*2KB\n",
		header[8]<<24|header[9]<<16|header[10]<<8|header[11]);
	if (header[7]&1)
	printf (" Next Writable Address: %u*2KB\n",
		header[12]<<24|header[13]<<16|header[14]<<8|header[15]);
	printf (" Free Blocks:           %u*2KB\n",
		header[16]<<24|header[17]<<16|header[18]<<8|header[19]);
	if (header[6]&0x10)
	printf (" Fixed Packet Size:     %u*2KB\n",
		header[20]<<24|header[21]<<16|header[22]<<8|header[23]);
	printf (" Track Size:            %u*2KB\n",
		header[24]<<24|header[25]<<16|header[26]<<8|header[27]);
	if (header[7]&2)
	printf (" Last Recorded Address: %u*2KB\n",
		header[28]<<24|header[29]<<16|header[30]<<8|header[31]);
    }

    do
    {	unsigned char *toc,*p;
	int   len,sony;

	cmd[0] = 0x43;	// READ TOC
	cmd[6] = 1;
	cmd[8] = 12;
	cmd[9] = 0;
	if ((err=cmd.transport (READ,header,12)))
	{   if (argc>2)
		fprintf (stderr,"- [unable to READ TOC (%X)]\n",err);
	    break;
	}

	len = (header[0]<<8|header[1])+2;
	toc = (unsigned char *)malloc (len);

	printf ("FABRICATED TOC:");
	if (argc>2)
	    printf ("\t\t%u %x %x",header[0]<<8|header[1],header[2],header[3]);
	printf ("\n");

	cmd[0] = 0x43;		// READ TOC
	cmd[6] = header[2];	// "First Track Number"
	cmd[7] = len>>8;
	cmd[8] = len;
	cmd[9] = 0;
	if ((err=cmd.transport (READ,toc,len)))
	{   fprintf (stderr,"- [unable to READ TOC (%X)]\n",err);
	    break;
	}

	for (p=toc+4,i=4;i<len-8;i+=8,p+=8)
	printf (" Track#%-3u:             %02x@%u\n",
		p[2],p[1],p[4]<<24|p[5]<<16|p[6]<<8|p[7]);
	printf (" Track#%-2X :             %02x@%u\n",
		p[2],p[1],p[4]<<24|p[5]<<16|p[6]<<8|p[7]);

	cmd[0] = 0x43;	// READ TOC
	cmd[2] = 1;	// "Session info"
	cmd[8] = 12;
	cmd[9] = 0;
	if ((err=cmd.transport (READ,header,12)))
	{   if (argc>2)
		fprintf (stderr,"- [unable to READ TOC#1 (%X)]\n",err);
	    break;
	}

	len = header[4+4]<<24|header[4+5]<<16|header[4+6]<<8|header[4+7];
	printf (" Multi-session Info:    #%u@%u\n",header[4+2],len);

	cmd[0] = 0x43;	// READ TOC
	cmd[8] = 12;
	cmd[9] = 0x40;	// "SONY Vendor bit"
	if ((err=cmd.transport (READ,header,12)))
	{   if (argc>2)
		fprintf (stderr,"- [unable to READ SONY SESS (%X)]\n",err);
	    break;
	}

	sony = header[4+4]<<24|header[4+5]<<16|header[4+6]<<8|header[4+7];
	if (len != sony)
	printf (" SONY Session Info:     #%u@%u\n",header[4+2],sony);

    } while (0);
}
