/*  CD.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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *  PCSX2 members can be contacted through their website at www.pcsx2.net.
 */

#include <windows.h>
#include <ddk/ntddcdrm.h> // IOCTL_CDROM...
#include <sys/types.h> // off64_t
#define CDVDdefs
#include "PS2Edefs.h"

#include "logfile.h"
#include "../convert.h" // MSFtoLBA(), HEXTOBCD()
#include "actualfile.h"
#include "device.h" // tocbuffer[], FinishCommand()
#include "CD.h"

int actualcdmode; // -1=ReadFile, 0=YellowMode2, 1=XAForm2, 2=CDDA
DWORD cdblocksize; // 2048 to 2352
int cdoffset; // 0, 8, 16, or 24
int cdmode;

void InitCDInfo()
{
	actualcdmode = -1;
	cdblocksize = 2048;
	cdmode = -1;
} // END InitCDInfo()

s32 CDreadTrackPass(u32 lsn, int mode, u8 *buffer)
{
	RAW_READ_INFO rawinfo;
	BOOL boolresult;
	DWORD byteswritten;
	DWORD errcode;
	LARGE_INTEGER targetpos;
	int i;

	if ((actualcdmode < -1) || (actualcdmode > 2))  return(-1);

#ifdef VERBOSE_FUNCTION_DEVICE
	PrintLog("CDVD CD: CDreadTrack(%llu, %i)", lsn, actualcdmode);
#endif /* VERBOSE_FUNCTION_DEVICE */

	if (actualcdmode >= 0)
	{
		rawinfo.DiskOffset.QuadPart = lsn * 2048; // Yes, 2048.
		rawinfo.SectorCount = 1;
		rawinfo.TrackMode = actualcdmode;
		boolresult = DeviceIoControl(devicehandle,
		                             IOCTL_CDROM_RAW_READ,
		                             &rawinfo,
		                             sizeof(RAW_READ_INFO),
		                             buffer,
		                             2352,
		                             &byteswritten,
		                             &waitevent);
		errcode = FinishCommand(boolresult);

		if (errcode != 0)
		{
#ifdef VERBOSE_WARNING_DEVICE
			PrintLog("CDVDiso CD:   Couldn't read a sector raw!");
			PrintError("CDVDiso CD", errcode);
#endif /* VERBOSE_WARNING_DEVICE */
			return(-1);
		} // ENDIF- Couldn't read a raw sector? Maybe not a CD.

	}
	else
	{
		targetpos.QuadPart = lsn * 2048;
		waitevent.Offset = targetpos.LowPart;
		waitevent.OffsetHigh = targetpos.HighPart;
		boolresult = ReadFile(devicehandle,
		                      buffer + 24,
		                      2048,
		                      &byteswritten,
		                      &waitevent);
		errcode = FinishCommand(boolresult);

		if (errcode != 0)
		{
#ifdef VERBOSE_WARNING_DEVICE
			PrintLog("CDVDiso CD:   Couldn't read a cooked sector!");
			PrintError("CDVDiso CD", errcode);
#endif /* VERBOSE_WARNING_DEVICE */
			return(-1);
		} // ENDIF- Trouble with seek? Report it.

		for (i = 0; i < 24; i++)  *(buffer + i) = 0;
		for (i = 24 + 2048; i < 2352; i++) *(buffer + i) = 0;
	} // ENDIF- Could we read a raw sector? Read another one!

	if (boolresult == FALSE)
	{
		boolresult = GetOverlappedResult(devicehandle,
		                                 &waitevent,
		                                 &byteswritten,
		                                 FALSE);
	} // ENDIF- Did the initial call not complete? Get byteswritten for
	//   the completed call.

	if (byteswritten < 2048)
	{
#ifdef VERBOSE_WARNING_DEVICE
		errcode = GetLastError();
		PrintLog("CDVDiso CD:   Short block! only got %u out of %u bytes",
		         byteswritten, cdblocksize);
		PrintError("CDVDiso CD", errcode);
#endif /* VERBOSE_WARNING_DEVICE */
		return(-1);
	} // ENDIF- Couldn't read a raw sector? Maybe not a CD.

	cdmode = mode;
	cdblocksize = byteswritten;
	return(0);
} // END CDreadTrackPass()

s32 CDreadTrack(u32 lsn, int mode, u8 *buffer)
{
	int retval;
	int lastmode;
	int i;
	retval = CDreadTrackPass(lsn, mode, buffer);
	if (retval >= 0)  return(retval);

#ifdef VERBOSE_WARNING_DEVICE
	PrintLog("CDVDiso CD:   Same mode doesn't work. Scanning...");
#endif /* VERBOSE_WARNING_DEVICE */

	lastmode = actualcdmode;
	actualcdmode = 2;
	while (actualcdmode >= -1)
	{
		retval = CDreadTrackPass(lsn, mode, buffer);
		if (retval >= 0)  return(retval);
		actualcdmode--;
	} // ENDWHILE- Searching each mode for a way to read the sector
	actualcdmode = lastmode; // None worked? Go back to first mode.
#ifdef VERBOSE_WARNING_DEVICE
	PrintLog("CDVDiso CD:   No modes work. Failing sector!");
#endif /* VERBOSE_WARNING_DEVICE */
	for (i = 0; i < 2352; i++)  *(buffer + i) = 0;
	return(-1);
} // END CDreadTrack()

s32 CDgetBufferOffset()
{
#ifdef VERBOSE_FUNCTION_DEVICE
	PrintLog("CDVD CD: CDgetBufferOffset()");
#endif /* VERBOSE_FUNCTION_DEVICE */
	// Send a whole CDDA record in?
	if ((actualcdmode == CDDA) && (cdmode == CDVD_MODE_2352)) return(0);

	// Otherwise, send the start of the data block in...
	return(cdoffset);
} // END CDgetBufferOffset()

s32 CDreadSubQ()
{
	return(-1);
} // END CDreadSubQ()

s32 CDgetTN(cdvdTN *cdvdtn)
{
	cdvdtn->strack = BCDTOHEX(tocbuffer[7]);
	cdvdtn->etrack = BCDTOHEX(tocbuffer[17]);
	return(0);
} // END CDgetTN()

s32 CDgetTD(u8 newtrack, cdvdTD *cdvdtd)
{
	u8 actualtrack;
	int pos;
	char temptime[3];

#ifdef VERBOSE_FUNCTION_DEVICE
	PrintLog("CDVDiso CD: CDgetTD()");
#endif /* VERBOSE_FUNCTION_DEVICE */

	actualtrack = newtrack;
	if (actualtrack == 0xaa)  actualtrack = 0;
	if (actualtrack == 0)
	{
		cdvdtd->type = 0;
		temptime[0] = BCDTOHEX(tocbuffer[27]);
		temptime[1] = BCDTOHEX(tocbuffer[28]);
		temptime[2] = BCDTOHEX(tocbuffer[29]);
		cdvdtd->lsn = MSFtoLBA(temptime);
	}
	else
	{
		pos = actualtrack * 10;
		pos += 30;
		cdvdtd->type = tocbuffer[pos];
		temptime[0] = BCDTOHEX(tocbuffer[pos + 7]);
		temptime[1] = BCDTOHEX(tocbuffer[pos + 8]);
		temptime[2] = BCDTOHEX(tocbuffer[pos + 9]);
		cdvdtd->lsn = MSFtoLBA(temptime);
	} // ENDIF- Whole disc? (or single track?)
	return(0);
} // END CDgetTD()

s32 CDgetDiskType()
{
	CDROM_TOC cdinfo;
	BOOL boolresult;
	int retval;
	DWORD byteswritten;
	DWORD errcode;
	u8 iso9660name[] = "CD001\0";
	u8 playstationname[] = "PLAYSTATION\0";
	u8 ps1name[] = "CD-XA001\0";
	u8 tempbuffer[2448];
	int tempdisctype;
	int i;
	int pos;
	unsigned long volumesize;

#ifdef VERBOSE_FUNCTION_DEVICE
	PrintLog("CDVDiso CD: CDgetDiskType()");
#endif /* VERBOSE_FUNCTION_DEVICE */

	tempdisctype = CDVD_TYPE_UNKNOWN;

	actualcdmode = 2;
	retval = CDreadTrack(16, CDVD_MODE_2048, tempbuffer);
	if (retval < 0)
	{
		disctype = tempdisctype;
		return(-1);
	} // ENDIF- Couldn't read the directory sector? Abort.
	disctype = CDVD_TYPE_DETCTCD;
	tempdisctype = CDVD_TYPE_CDDA;

	cdoffset = 0;
	i = 0;
	while ((cdoffset <= 24) && (iso9660name[i] != 0))
	{
		i = 0;
		while ((iso9660name[i] != 0) &&
		        (iso9660name[i] == tempbuffer[cdoffset + 1 + i]))  i++;
		if (iso9660name[i] != 0)  cdoffset += 8;
	} // ENDWHILE- Trying to find a working offset for a ISO9660 record.
	if (iso9660name[i] != 0)
	{
#ifdef VERBOSE_DISC_TYPE
		PrintLog("Detected a CDDA (Music CD).");
#endif /* VERBOSE_DISC_TYPE */
		tempdisctype = CDVD_TYPE_CDDA;
		tocbuffer[0] = 0x01;

	}
	else
	{
		tocbuffer[0] = 0x41;
		i = 0;
		while ((playstationname[i] != 0) &&
		        (playstationname[i] == tempbuffer[cdoffset + 8 + i]))  i++;
		if (playstationname[i] != 0)
		{
#ifdef VERBOSE_DISC_TYPE
			PrintLog("Detected a non-Playstation CD.");
#endif /* VERBOSE_DISC_TYPE */
			tempdisctype = CDVD_TYPE_UNKNOWN;
		}
		else
		{
			i = 0;
			while ((ps1name[i] != 0) &&
			        (ps1name[i] == tempbuffer[cdoffset + 1024 + i]))  i++;
			if (ps1name[i] != 0)
			{
#ifdef VERBOSE_DISC_TYPE
				PrintLog("Detected a Playstation 2 CD.");
#endif /* VERBOSE_DISC_TYPE */
				tempdisctype = CDVD_TYPE_PS2CD;
			}
			else
			{
#ifdef VERBOSE_DISC_TYPE
				PrintLog("Detected a Playstation CD.");
#endif /* VERBOSE_DISC_TYPE */
				tempdisctype = CDVD_TYPE_PSCD;
			} // ENDIF- Is this not a PlayStation Disc?
		} // ENDIF- Is this not a PlayStation Disc?
	} // ENDIF- Is this not an ISO9660 CD? (a CDDA, in other words?)
	// Build the Fake TOC
	tocbuffer[2] = 0xA0;
	tocbuffer[7] = HEXTOBCD(1); // Starting Track
	tocbuffer[12] = 0xA1;
	tocbuffer[17] = HEXTOBCD(1); // Ending Track
	volumesize = tempbuffer[84]; // Volume size (big endian)
	volumesize *= 256;
	volumesize += tempbuffer[85];
	volumesize *= 256;
	volumesize += tempbuffer[86];
	volumesize *= 256;
	volumesize += tempbuffer[87];
#ifdef VERBOSE_DISC_INFO
	if (tempdisctype != CDVD_TYPE_CDDA)
	{
		PrintLog("CDVDiso CD:   ISO9660 size %llu", volumesize);
	} // ENDIF- Data CD? Print size in blocks.
#endif /* VERBOSE_DISC_INFO */

	LBAtoMSF(volumesize, &tocbuffer[27]);
	tocbuffer[27] = HEXTOBCD(tocbuffer[27]);
	tocbuffer[28] = HEXTOBCD(tocbuffer[28]);
	tocbuffer[29] = HEXTOBCD(tocbuffer[29]);
	tocbuffer[40] = 0x02; // Data Mode
	tocbuffer[42] = 0x01; // Track #
	LBAtoMSF(0, &tocbuffer[47]);
	tocbuffer[47] = HEXTOBCD(tocbuffer[47]);
	tocbuffer[48] = HEXTOBCD(tocbuffer[48]);
	tocbuffer[49] = HEXTOBCD(tocbuffer[49]);

	// Can we get the REAL TOC?
	boolresult = DeviceIoControl(devicehandle,
	                             IOCTL_CDROM_READ_TOC,
	                             NULL,
	                             0,
	                             &cdinfo,
	                             sizeof(CDROM_TOC),
	                             &byteswritten,
	                             NULL);

	if (boolresult == FALSE)
	{
#ifdef VERBOSE_WARNING_DEVICE
		errcode = GetLastError();
		PrintLog("CDVDiso CD:   Can't get TOC!");
		PrintError("CDVDiso CD", errcode);
#endif /* VERBOSE_WARNING_DEVICE */
		disctype = tempdisctype;
		return(disctype);
	} // ENDIF- Can't read the TOC? Accept the fake TOC then.

	// Fill in the pieces of the REAL TOC.
#ifdef VERBOSE_DISC_INFO
	PrintLog("CDVDiso CD:   TOC First Track: %u   Last Track: %u",
	         cdinfo.FirstTrack, cdinfo.LastTrack);
#endif /* VERBOSE_DISC_INFO */
	tocbuffer[7] = HEXTOBCD(cdinfo.FirstTrack);
	tocbuffer[17] = HEXTOBCD(cdinfo.LastTrack);

	// for(i = cdinfo.FirstTrack; i <= cdinfo.LastTrack; i++) {
	for (i = 0; i <= cdinfo.LastTrack - cdinfo.FirstTrack; i++)
	{
#ifdef VERBOSE_DISC_INFO
		PrintLog("CDVDiso CD:   TOC Track %u   Disc Size: %u:%u.%u   Data Mode %u",
		         cdinfo.TrackData[i].TrackNumber,
		         cdinfo.TrackData[i].Address[1] * 1,
		         cdinfo.TrackData[i].Address[2] * 1,
		         cdinfo.TrackData[i].Address[3] * 1,
		         cdinfo.TrackData[i].Control * 1);
#endif /* VERBOSE_DISC_INFO */
		pos = i * 10 + 40;
		tocbuffer[pos] = cdinfo.TrackData[i].Control;
		tocbuffer[pos + 2] = HEXTOBCD(i + 1);
		tocbuffer[pos + 7] = HEXTOBCD(cdinfo.TrackData[i].Address[1]);
		tocbuffer[pos + 8] = HEXTOBCD(cdinfo.TrackData[i].Address[2]);
		tocbuffer[pos + 9] = HEXTOBCD(cdinfo.TrackData[i].Address[3]);
	} // NEXT i- Transferring Track data to the PS2 TOC

#ifdef VERBOSE_DISC_INFO
	PrintLog("CDVDiso CD:   TOC Disc Size: %u:%u.%u",
	         cdinfo.TrackData[i].Address[1] * 1,
	         cdinfo.TrackData[i].Address[2] * 1,
	         cdinfo.TrackData[i].Address[3] * 1);
#endif /* VERBOSE_DISC_INFO */
	tocbuffer[27] = HEXTOBCD(cdinfo.TrackData[i].Address[1]);
	tocbuffer[28] = HEXTOBCD(cdinfo.TrackData[i].Address[2]);
	tocbuffer[29] = HEXTOBCD(cdinfo.TrackData[i].Address[3]);
	disctype = tempdisctype;
	return(disctype);
} // END CDgetDiskType()
