// testDeviceIOWin32.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <stdio.h>
#include <tchar.h>
#include <iostream>

#include <windows.h>
#include <winioctl.h>
#include <ntddcdrm.h>

/*
	CD
	Every byte == 14bit (EFM) ??
	Small Frame == 588bit == 24 byte <== this is the unit of read. Can't read smaller portions.
	+-----------+------------+--------------+-----------+--------------+-----------+
	| Sync code | Subchannel | Main Channel | CIRC code | Main Channel | CIRC code |
	|           | byte       | data         |           | data         |           |
bit | 24+3      | 14+3       | 12x(14+3)    | 4x(14+3)  | 12x(14+3)    | 4x(14+3)  |
	+-----------+------------+--------------+-----------+--------------+-----------+

	Frame == 98 Small Frames == 2352 bytes <== this is reachable form soft.
	+98 byte of subchannel ( 2 sync bytes + 96 bytes of data )
	The first 2 small frames containe sync bytes and then the rest of small frames
	containe data bytes.


	Session == Lead-in + Program Area + Lead-out
	Lead-in == TOC in Q-Subchannel
	Program Area == Logical Tracks (at least containes one logical track)


	Address format is MSF (Minute/Second/Frame) M=60*S S=75*F
	1 Audio Sec = 4(Stereo 16bit)*44100(Hz)=176400 byte = 75 * 2352 byte = 75 Frame
	0<= F <= 74
	0<= S <= 59
	0<= M <= 99


*/

enum {
	ADDR_SEC2FRAME = 75,
	ADDR_MIN2SEC = 60,

	CTRL_COPYPROT_MASK = 0x02,	//	0010
	CTRL_TYPE_MASK = 0x0C,		//	1100
	CTRL_AUDIO2CH_TRACK = 0x00, //
	CTRL_DATA_TRACK =	  0x04,	//
	CTRL_AUDIO4CH_TRACK = 0x06, //
	CTRL_RESERVED_TRACK = 0x0C,	//
	CTRL_DATA_INCREMENTAL = 0x01, //
	CTRL_AUDIO_50_15 = 0x01, //

	FRAME_DATA = 2352,

};

#define MAXIMUM_NUMBER_TRACKS		100
#define NSECTORS					13
#define UNDERSAMPLING				1
#define CB_CDDASECTOR				2368
#define CB_QSUBCHANNEL				16
#define CB_CDROMSECTOR				2048
#define CB_AUDIO					(CB_CDDASECTOR-CB_QSUBCHANNEL)

/* The code of interest is in the subroutine GetDriveGeometry. The 
   code in main shows how to interpret the results of the call. */

class device
{
protected:

	//	Handle to the drive to be examined.
	HANDLE hDevice;

	DISK_GEOMETRY dg;
	CDROM_TOC toc;

	bool m_bLocked;

public:

	device()
	{
		hDevice = INVALID_HANDLE_VALUE;
		m_bLocked = false;
	}

	~device()
	{
		if ( hDevice != INVALID_HANDLE_VALUE )
		{
			if ( m_bLocked )
				storageMediaUnlock();
			close();
		}
	}

	bool create( const TCHAR* pszDevice )
	{
		hDevice = ::CreateFile(pszDevice,  // drive to open
						GENERIC_READ |
						GENERIC_WRITE ,     // no access to the drive
						FILE_SHARE_READ | // share mode
						FILE_SHARE_WRITE, 
						NULL,             // default security attributes
						OPEN_EXISTING,    // disposition
						0,                // file attributes
						NULL);            // do not copy file attributes
		if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
		{
			printErr("CreateFile");
			return false;
		}

		return true;
	}

	bool close()
	{
		if ( hDevice != INVALID_HANDLE_VALUE )
		{
			::CloseHandle(hDevice);
			hDevice = INVALID_HANDLE_VALUE;
		}

		return true;
	}

	static void printErr( char* szMsg = "" )
	{
		printf ("%s Error %ld. ", szMsg, GetLastError ());

		char szMsgBfr[ 1024 ];
		::FormatMessageA(
				FORMAT_MESSAGE_FROM_SYSTEM | 
				FORMAT_MESSAGE_IGNORE_INSERTS,
				NULL,
				GetLastError (),
				0, // Default language
				szMsgBfr,
				sizeof( szMsgBfr ),
				NULL );
		szMsgBfr[sizeof( szMsgBfr )-1] = 0;

		puts ( szMsgBfr );
	}

	bool diskGetDriveGeometry()
	{
		DWORD junk;                   // discard results

		printf("IOCTL_DISK_GET_DRIVE_GEOMETRY\n");
		if ( !DeviceIoControl( hDevice,					// device to be queried
				IOCTL_DISK_GET_DRIVE_GEOMETRY,			// operation to perform
								 NULL, 0,				// no input buffer
								&dg, sizeof(dg),		// output buffer
								&junk,					// # bytes returned
								(LPOVERLAPPED) NULL) )	// synchronous I/O
		{
			printErr("");
			return false;
		}
		else
		{
			printf("MediaType = %d\n", dg.MediaType);
			printf("Cylinders = %I64d\n", dg.Cylinders);
			printf("Tracks/cylinder = %ld\n", (ULONG) dg.TracksPerCylinder);
			printf("Sectors/track = %ld\n", (ULONG) dg.SectorsPerTrack);
			printf("Bytes/sector = %ld\n", (ULONG) dg.BytesPerSector);

			ULONGLONG DiskSize = dg.Cylinders.QuadPart * (ULONG)dg.TracksPerCylinder *
			  (ULONG)dg.SectorsPerTrack * (ULONG)dg.BytesPerSector;
			printf("Disk size = %I64d (Bytes) = %I64d (Gb)\n", DiskSize,
				   DiskSize / (1024 * 1024 * 1024));
		}
		puts("");

		return true;
	}

	bool diskGetCacheInformation()
	{
		DWORD junk;                   // discard results
		DISK_CACHE_INFORMATION dci;

		printf("IOCTL_DISK_GET_CACHE_INFORMATION\n");
		if ( !DeviceIoControl( hDevice,					// device to be queried
				IOCTL_DISK_GET_CACHE_INFORMATION,		// operation to perform
								 NULL, 0,				// no input buffer
								&dci, sizeof(dci),		// output buffer
								&junk,					// # bytes returned
								(LPOVERLAPPED) NULL) )	// synchronous I/O
		{
			printErr("");
			return false;
		}
		else
		{
			printf("ParametersSavable = %d\n", dci.ParametersSavable);
			printf("ReadCacheEnabled = %d\n", dci.ReadCacheEnabled);
			printf("WriteCacheEnabled = %d\n", dci.WriteCacheEnabled);
			printf("ReadRetentionPriority = %d\n", dci.ReadRetentionPriority);
			printf("WriteRetentionPriority = %d\n", dci.WriteRetentionPriority);
			printf("DisablePrefetchTransferLength = %d\n", dci.DisablePrefetchTransferLength);
			if ( dci.PrefetchScalar )
			{
				printf("ScalarPrefetch.Minimum = %d\n", dci.ScalarPrefetch.Minimum);
				printf("ScalarPrefetch.Maximum = %d\n", dci.ScalarPrefetch.Maximum);
				printf("ScalarPrefetch.MaximumBlocks = %d\n", dci.ScalarPrefetch.MaximumBlocks);
			}
			else
			{
				printf("BlockPrefetch.Minimum = %d\n", dci.BlockPrefetch.Minimum);
				printf("BlockPrefetch.Maximum = %d\n", dci.BlockPrefetch.Maximum);
			}
		}
		puts("");

		return true;
	}

	bool diskPerformance()
	{
		DWORD junk;                   // discard results
		DISK_PERFORMANCE dp;

		printf("IOCTL_DISK_PERFORMANCE\n");
		if ( !DeviceIoControl( hDevice,					// device to be queried
				IOCTL_DISK_PERFORMANCE,		// operation to perform
								 NULL, 0,				// no input buffer
								&dp, sizeof(dp),		// output buffer
								&junk,					// # bytes returned
								(LPOVERLAPPED) NULL) )	// synchronous I/O
		{
			printErr("");
			return false;
		}
		else
		{
			printf("BytesRead = %I64d\n", dp.BytesRead);
			printf("BytesWritten = %I64d\n", dp.BytesWritten);
			printf("ReadTime = %I64d\n", dp.ReadTime);
			printf("WriteTime = %I64d\n", dp.WriteTime);
			printf("IdleTime = %I64d\n", dp.IdleTime);
			printf("ReadCount = %d\n", dp.ReadCount);
			printf("WriteCount = %d\n", dp.WriteCount);
			printf("QueueDepth = %d\n", dp.QueueDepth);
			printf("SplitCount = %d\n", dp.SplitCount);
			printf("QueryTime = %I64d\n", dp.QueryTime);
			printf("StorageDeviceNumber = %d\n", dp.StorageDeviceNumber);
			printf("StorageManagerName = %c%c%c%c%c%c%c%c\n", 
				dp.StorageManagerName[0], dp.StorageManagerName[1],
				dp.StorageManagerName[2], dp.StorageManagerName[3],
				dp.StorageManagerName[4], dp.StorageManagerName[5],
				dp.StorageManagerName[6], dp.StorageManagerName[7]);
		}
		puts("");

		//////////////////////////////////////////////////////////////////////////////
		printf("IOCTL_DISK_PERFORMANCE_OFF\n");
		if ( !DeviceIoControl( hDevice,					// device to be queried
				IOCTL_DISK_PERFORMANCE_OFF,		// operation to perform
								 NULL, 0,				// no input buffer
								0, 0,		// output buffer
								&junk,					// # bytes returned
								(LPOVERLAPPED) NULL) )	// synchronous I/O
		{
			printErr("");
			return false;
		}
		puts("");

		return true;
	}

	bool diskVerify()
	{
		DWORD junk;                   // discard results
		VERIFY_INFORMATION vi;

		printf("IOCTL_DISK_VERIFY\n");
		vi.StartingOffset.QuadPart = 0;
		vi.Length = 2048;
		if ( !DeviceIoControl( hDevice,					// device to be queried
				IOCTL_DISK_VERIFY,		// operation to perform
								 &vi, sizeof(vi),				// no input buffer
								0, 0,		// output buffer
								&junk,					// # bytes returned
								(LPOVERLAPPED) NULL) )	// synchronous I/O
		{
			printErr("");
			return false;
		}
		puts("");

		return true;
	}

	bool storageMediaLock()
	{
		DWORD junk;                   // discard results
		PREVENT_MEDIA_REMOVAL pmr;
		pmr.PreventMediaRemoval = 1;
		
		printf("IOCTL_STORAGE_MEDIA_REMOVAL lock\n");
		if ( !::DeviceIoControl( hDevice,
				IOCTL_STORAGE_MEDIA_REMOVAL, 
								&pmr, sizeof( pmr ),
								0, 0,
								&junk,
								(LPOVERLAPPED)0 ) )
		{
			printErr("");
			return false;
		}

		m_bLocked = true;
		return true;
	}

	bool storageMediaUnlock()
	{
		DWORD junk;                   // discard results
		PREVENT_MEDIA_REMOVAL pmr;
		pmr.PreventMediaRemoval = 0;
		
		printf("IOCTL_STORAGE_MEDIA_REMOVAL unlock\n");
		if ( !::DeviceIoControl( hDevice,
				IOCTL_STORAGE_MEDIA_REMOVAL, 
								&pmr, sizeof( pmr ),
								0, 0,
								&junk,
								(LPOVERLAPPED)0 ) )
		{
			printErr("");
			return false;
		}

		m_bLocked = false;
		return true;
	}

	bool storageLoadMedia()
	{
		DWORD junk;                   // discard results
		
		printf("IOCTL_STORAGE_LOAD_MEDIA\n");
		if ( !::DeviceIoControl( hDevice,
				IOCTL_STORAGE_LOAD_MEDIA, 
								0, 0,
								0, 0,
								&junk,
								(LPOVERLAPPED)0 ) )
		{
			printErr("");
			return false;
		}
		else
		{
//			printf("");
		}

		return true;
	}

	bool storageCheckVerify()
	{
		DWORD junk;                   // discard results
		
		printf("IOCTL_STORAGE_CHECK_VERIFY\n");
		if ( !::DeviceIoControl( hDevice,
				IOCTL_STORAGE_CHECK_VERIFY, 
								0, 0,
								0, 0,
								&junk,
								(LPOVERLAPPED)0 ) )
		{
			printErr("");
			return false;
		}
		else
		{
//			printf("");
		}

		return true;
	}

	bool storageEjectMedia()
	{
		DWORD junk;                   // discard results
		
		printf("IOCTL_STORAGE_EJECT_MEDIA\n");
		if ( !::DeviceIoControl( hDevice,
				IOCTL_STORAGE_EJECT_MEDIA, 
								0, 0,
								0, 0,
								&junk,
								(LPOVERLAPPED)0 ) )
		{
			printErr("");
			return false;
		}
		else
		{
//			printf("");
		}

		return true;
	}

	bool storageReadCapacity()
	{
//	2003
#if 0
		DWORD junk;                   // discard results

		STORAGE_READ_CAPACITY src;
		printf("IOCTL_STORAGE_READ_CAPACITY\n");
		if ( !DeviceIoControl( hDevice,					// device to be queried
				IOCTL_STORAGE_READ_CAPACITY,			// operation to perform
								 NULL, 0,				// no input buffer
								&src, sizeof(src),		// output buffer
								&junk,					// # bytes returned
								(LPOVERLAPPED) NULL) )	// synchronous I/O
		{
			printErr("");
			return false;
		}
		else
		{
  			printf("Version = %d\n", src.Version);
			printf("Size = %d\n", src.Size);
			printf("BlockLength = %d\n", src.BlockLength);
			printf("NumberOfBlocks = %I64d\n", dg.NumberOfBlocks);
			printf("DiskLength = %I64d\n", dg.DiskLength);
		}
		puts("");
#endif
		return true;
	}

	bool cdromDiskInfo()
	{
#if 0
		DWORD junk;                   // discard results
		printf("IOCTL_CDROM_DISC_INFO\n");
		CDROM_DISCINFO cdi;
		if ( !DeviceIoControl( hDevice,					// device to be queried
				IOCTL_CDROM_DISC_INFO,		// operation to perform
								 0, 0,				// no input buffer
								&cdi, sizeof( cdi ),		// output buffer
								&junk,					// # bytes returned
								(LPOVERLAPPED) NULL) )	// synchronous I/O
		{
			printErr("");
		}
		else
		{
			printf("Reserved = %I64d\n", cdi.Reserved );
			printf("FirstTrack = %I64d\n", cdi.FirstTrack );
			printf("LastTrack = %I64d\n", cdi.LastTrack );
			printf("LastTrack = %I64d\n", cdi.LeadOutTrackAddr );
			printf("FirstSession = %I64d\n", cdi.FirstSession );
			printf("LastSession = %I64d\n", cdi.LastSession );
			printf("ReqSession = %I64d\n", cdi.ReqSession );
			printf("RetSession = %I64d\n", cdi.RetSession );
			printf("LogicStartAddr = %I64d\n", cdi.LogicStartAddr );
		}
		puts("");
#endif
	}

	
	bool cdromReadTOC()
	{
		DWORD junk;                   // discard results
		printf("IOCTL_CDROM_READ_TOC\n");

		if ( !::DeviceIoControl( hDevice,
				IOCTL_CDROM_READ_TOC, 
								0, 0,
								&toc, sizeof(toc),
								&junk,
								(LPOVERLAPPED)0 ) )
		{
			printErr("");
			return false;
		}
		else
		{
			std::cout << "Length = " << (USHORT&)toc.Length << std::endl; 
			std::cout << "FirstTrack = " << int(toc.FirstTrack) << std::endl; 
			std::cout << "LastTrack = " << int(toc.LastTrack) << std::endl; 
			for ( int i = 0; i < (toc.LastTrack - toc.FirstTrack +1); ++i )
			{
				std::cout << "track " << i << " {" << std::endl;
				
				if ( toc.TrackData[i].Control & CTRL_COPYPROT_MASK )
					std::cout << "  No copy protection" << std::endl;
				else
					std::cout << "  Copy protected" << std::endl;
				
				int n = toc.TrackData[i].Control & CTRL_TYPE_MASK;
				if ( n == CTRL_AUDIO2CH_TRACK )
					std::cout << "  2 channel audio track" << std::endl;
				else if ( n == CTRL_AUDIO4CH_TRACK )
					std::cout << "  4 channel audio track" << std::endl;
				else if ( n == CTRL_DATA_TRACK )
					std::cout << "  Data track" << std::endl;
				else
					std::cout << "  Unknown type of track" << std::endl;

				if ( n == CTRL_DATA_TRACK )
				{
					if ( toc.TrackData[i].Control & CTRL_DATA_INCREMENTAL )
						std::cout << "  Incremental record" << std::endl;
					else
						std::cout << "  Continuous record" << std::endl;
				}
				else
				{
					if ( toc.TrackData[i].Control & CTRL_AUDIO_50_15 )
						std::cout << "  Predistortion 50/15 mks" << std::endl;
					else
						std::cout << "  No predistortion" << std::endl;
				}

				std::cout << "  Adr = " << int(toc.TrackData[i].Adr) << std::endl; 
				std::cout << "  TrackNumber = " << int(toc.TrackData[i].TrackNumber) << std::endl; 
				std::cout << "  Address[0] = " << int(toc.TrackData[i].Address[0]) << std::endl; 
				std::cout << "  Minute = " << int(toc.TrackData[i].Address[1]) << std::endl; 
				std::cout << "  Second = " << int(toc.TrackData[i].Address[2]) << std::endl; 
				std::cout << "  Frame = " << int(toc.TrackData[i].Address[3]) << std::endl; 
				std::cout << "  Start Sector = " << toc.TrackData[i].Address[1] * ADDR_MIN2SEC * ADDR_SEC2FRAME
												+ toc.TrackData[i].Address[2] * ADDR_SEC2FRAME
												+ toc.TrackData[i].Address[3] << std::endl; 
				std::cout << "}" << std::endl; 

			}
		}

		return true;
	}

	virtual int cdromRawRead( int sector, int num, char* buf, int buf_size );
	
};

int device::cdromRawRead( int sector, int num, char* buf, int buf_size )
{
	DWORD nBytsRead = 0;

	RAW_READ_INFO rri;
//		rri.TrackMode = CDDA;
	rri.TrackMode = YellowMode2;
	rri.SectorCount = num;
	rri.DiskOffset.QuadPart = sector*CB_CDROMSECTOR;

	memset ( buf, 0, buf_size );

#if 1
//	std::cout << "IOCTL_CDROM_RAW_READ" << std::endl;
	if ( !::DeviceIoControl( hDevice,
			IOCTL_CDROM_RAW_READ, 
							&rri, sizeof(rri),
							buf, buf_size,
							&nBytsRead,
							(LPOVERLAPPED)0 ) )
	{
		int i = 0;
		char tmp[32];

		for ( ; i < buf_size && !buf[i]; ++i );
		if ( i < buf_size  )
			_snprintf( tmp, sizeof( tmp), "Sector %d buf!=0", sector );
		else
			_snprintf( tmp, sizeof( tmp), "Sector %d", sector );

		printErr( tmp );
		return false;
	}
	else
#endif
	{
#if 0
		std::cout << " Read " << nBytsRead << " bytes" << std::endl;
		const int cCol = 16;
		for ( int i = 0; i < int(nBytsRead) ; )
		{
			int k = i;
			for ( int j = 0; j < cCol && k < int(nBytsRead); ++k, ++j )
			{
				std::cout << std::hex << (((unsigned)( buf[k] ) >> 4) & 0xF ) << ((unsigned)( buf[k] ) & 0x0F )  << " ";
			}

			for ( int j = 0; j < cCol && i < int(nBytsRead); ++i, ++j )
			{
				if ( 32 < buf[i] && buf[i] < 255 )
					std::cout << buf[i];
				else
					std::cout << ' ';
			}

			std::cout << std::endl;
		}
#endif
	}

	//	CB_CDROMSECTOR
#if 0
	if (m_bTocValid && ((sector + NumSectors) <= GetEndSector(m_TOC.LastTrack)))
	{
		RAW_READ_INFO rri;
		rri.TrackMode = CDDA;
		rri.SectorCount = (DWORD)NumSectors;
		rri.DiskOffset.QuadPart = sector*CB_CDROMSECTOR;

		DWORD charsRead = 0;
		if (::DeviceIoControl(m_hDrive, IOCTL_CDROM_RAW_READ, &rri, sizeof(rri), Buffer, (DWORD)NumSectors * CB_AUDIO, &charsRead, NULL) != 0)
			return true;
		else
			return false;
	}
	else 
		return false;
#endif
	return nBytsRead;
}


int _tmain(int argc, _TCHAR* argv[])
{
//TCHAR szDevice[] = L"\\\\.\\PhysicalDrive0";
//TCHAR szDevice[] = L"\\\\.\\PhysicalDrive2";
TCHAR szDevice[] = L"\\\\.\\E:";

	FILE* f = fopen( "dump.txt", "wb" );

	device d;
	
	if ( !d.create( szDevice ) )
	{
		std::cerr << "Cannot open device." << std::endl;
		return 1;
	}

	if ( !d.storageCheckVerify() )
	{
		std::cerr << "Media is not accessable." << std::endl;
		return 2;
	}

	if ( !d.storageMediaLock() )
	{
		std::cerr << "Cannot lock the media." << std::endl;
		return 3;
	}

//	d.diskGetDriveGeometry();
//	d.storageReadCapacity();
//	d.diskGetCacheInformation();
//	d.diskPerformance();

//	d.cdromDiskInfo();
	d.cdromReadTOC();

	const int nStart = 000;
	const int nAmount = nStart + 350000;
	int i = nStart;
	char buf[ 2*FRAME_DATA ];
	for ( ; i < nAmount; ++i )
	{
		int n = d.cdromRawRead( i, 1, buf, sizeof( buf ) );
//		fwrite( buf, 1, n, f );
	}

	d.storageMediaUnlock();
	d.storageMediaUnlock();
	d.storageMediaUnlock();
	d.storageMediaUnlock();
	d.storageMediaUnlock();
	d.storageMediaUnlock();

//	d.storageEjectMedia();

	d.close();

	fclose( f );

	return 0;
}



