/*
 *  sys_windows.cpp - System dependent routines, Windows implementation
 *
 *  Basilisk II (C) 1997-2008 Christian Bauer
 *
 *  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
 */

#include "sysdeps.h"

#include <winioctl.h>

#include <string>
typedef std::basic_string<TCHAR> tstring;

#include <algorithm>
using std::min;

#include "main.h"
#include "util_windows.h"
#include "macos_util.h"
#include "prefs.h"
#include "user_strings.h"
#include "sys.h"

#include "cd_defs.h"
#include "cdenable/ntcd.h"
#include "cdenable/cache.h"
#include "cdenable/eject_nt.h"

#if defined(BINCUE)
#include "bincue.h"
#endif

#define DEBUG 0
#include "debug.h"


// File handles are pointers to these structures
struct file_handle {
	TCHAR *name;		// Copy of device/file name
	HANDLE fh;
	bool is_file;		// Flag: plain file or physical device?
	bool is_floppy;		// Flag: floppy device
	bool is_cdrom;		// Flag: CD-ROM device
	bool read_only;		// Copy of Sys_open() flag
	loff_t start_byte;	// Size of file header (if any)
	loff_t file_size;	// Size of file data (only valid if is_file is true)
	cachetype cache;
	bool is_media_present;
	bool is_tray_locked;
	HANDLE storage_ejection_handle;  // Handle used for storage ejection prevention

#if defined(BINCUE)
	bool is_bincue;		// Flag: BIN CUE file
	void *bincue_fd;
#endif
	file_handle() {
		// Since our PreventRemovalOfVolume implementaion on Windows increments a lock counter,
		// let's have our own safeguard to prevent incrementing it more than once.
		is_tray_locked = false;
		storage_ejection_handle = NULL;
#if defined(BINCUE)
		is_bincue = false;	// default bincue false
#endif
	}

};

// Open file handles
struct open_file_handle {
	file_handle *fh;
	open_file_handle *next;
};
static open_file_handle *open_file_handles = NULL;

// File handle of first floppy drive (for SysMountFirstFloppy())
static file_handle *first_floppy = NULL;

// CD-ROM variables
static const int CD_READ_AHEAD_SECTORS = 16;
static char *sector_buffer = NULL;

// Prototypes
static bool is_cdrom_readable(file_handle *fh);
static DWORD file_offset_read(HANDLE fh, loff_t offset, int count, char *buf);

/*
 *  Initialization
 */

void SysInit(void)
{
	// Initialize CD-ROM driver
	sector_buffer = (char *)VirtualAlloc(NULL, 8192, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
	CdenableSysInstallStart();
}


/*
 *  Deinitialization
 */

void SysExit(void)
{
	if (sector_buffer) {
		VirtualFree(sector_buffer, 0, MEM_RELEASE );
		sector_buffer = NULL;
	}
}


/*
 *  Manage open file handles
 */

static void sys_add_file_handle(file_handle *fh)
{
	open_file_handle *p = new open_file_handle;
	p->fh = fh;
	p->next = open_file_handles;
	open_file_handles = p;
}

static void sys_remove_file_handle(file_handle *fh)
{
	open_file_handle *p = open_file_handles;
	open_file_handle *q = NULL;

	while (p) {
		if (p->fh == fh) {
			if (q)
				q->next = p->next;
			else
				open_file_handles = p->next;
			delete p;
			break;
		}
		q = p;
		p = p->next;
	}
}


/*
 *  Mount removable media now
 */

void mount_removable_media(int media)
{
	for (open_file_handle *p = open_file_handles; p != NULL; p = p->next) {
		file_handle * const fh = p->fh;

		if (fh->is_cdrom && (media & MEDIA_CD)) {
			cache_clear(&fh->cache);
			fh->start_byte = 0;

			if (fh->fh && fh->fh != INVALID_HANDLE_VALUE)
				CloseHandle(fh->fh);

			// Re-open device
			TCHAR device_name[MAX_PATH];
			_sntprintf(device_name, lengthof(device_name), TEXT("\\\\.\\%c:"), fh->name[0]);
			fh->fh = CreateFile(
				device_name,
				GENERIC_READ,
				FILE_SHARE_READ | FILE_SHARE_WRITE,
				NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

			if (fh->fh != INVALID_HANDLE_VALUE) {
				fh->is_media_present = is_cdrom_readable(fh);
				if (fh->is_media_present)
					MountVolume(fh);
			} else {
				fh->is_media_present = false;
			}
		}
	}
}


/*
 *  Account for media that has just arrived
 */

void SysMediaArrived(void)
{
	mount_removable_media(MEDIA_REMOVABLE);
}


/*
 *  Account for media that has just been removed
 */

void SysMediaRemoved(void)
{
}


/*
 *  Mount first floppy disk
 */

void SysMountFirstFloppy(void)
{
	if (first_floppy)
		MountVolume(first_floppy);
}


/*
 *  This gets called when no "floppy" prefs items are found
 *  It scans for available floppy drives and adds appropriate prefs items
 */

void SysAddFloppyPrefs(void)
{
}


/*
 *  This gets called when no "disk" prefs items are found
 *  It scans for available HFS volumes and adds appropriate prefs items
 */

void SysAddDiskPrefs(void)
{
}


/*
 *  This gets called when no "cdrom" prefs items are found
 *  It scans for available CD-ROM drives and adds appropriate prefs items
 */

void SysAddCDROMPrefs(void)
{
	// Don't scan for drives if nocdrom option given
	if (PrefsFindBool("nocdrom"))
		return;

	char rootdir[] = "C:\\";
	for (; rootdir[0] <= 'Z'; rootdir[0]++) {
		if (GetDriveTypeA(rootdir) == DRIVE_CDROM)
			PrefsAddString("cdrom", rootdir);
	}
}


/*
 *  Add default serial prefs (must be added, even if no ports present)
 */

void SysAddSerialPrefs(void)
{
	PrefsAddString("seriala", "COM1");
	PrefsAddString("serialb", "COM2");
}


/*
 *  Read CD-ROM
 *  Must give cd some time to settle
 *  Can't give too much however, would be annoying, this is difficult..
 */

static inline int cd_read_with_retry(file_handle *fh, ULONG offset, int count, char *buf )
{
	if (!fh || !fh->fh)
		return 0;

	DWORD bytes_read = CdenableSysReadCdBytes(fh->fh, offset, count, buf);

	if (bytes_read == 0) {
		// fall back to logical volume handle read in the case where there's no cdenable
		bytes_read = file_offset_read(fh->fh, offset, count, buf);
	}

	return bytes_read;
}

/*
 * Generic offset read function for a file or a device that behaves like one
 */
 
static DWORD file_offset_read(HANDLE fh, loff_t offset, int count, char *buf)
{
	// Seek to position
	LONG lo = (LONG)offset;
	LONG hi = (LONG)(offset >> 32);
	DWORD r = SetFilePointer(fh, lo, &hi, FILE_BEGIN);
	if (r == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) {
		return 0;
	}

	DWORD bytes_read;

	// Read data
	if (ReadFile(fh, buf, count, &bytes_read, NULL) == 0)
		bytes_read = 0;

	return bytes_read;
}

static int cd_read(file_handle *fh, cachetype *cptr, ULONG LBA, int count, char *buf)
{
	ULONG l1, l2, cc;
	int i, c_count, got_bytes = 0, nblocks, s_inx, ss, first_block;
	int ok_bytes = 0;
	char *ptr, *ttptr = 0, *tmpbuf;

	if (count <= 0)
		return 0;

	if (!fh || !fh->fh)
		return 0;

	ss = 2048;
	l1 = (LBA / ss) * ss;
	l2 = ((LBA + count - 1 + ss) / ss) * ss;
	cc = l2 - l1;
	nblocks = cc / ss;
	first_block = LBA / ss;

	ptr = buf;
	s_inx = LBA - l1;
	c_count = ss - s_inx;
	if (c_count > count)
		c_count = count;

	for (i = 0; i < nblocks; i++) {
		if (!cache_get(cptr, first_block + i, sector_buffer))
			break;

		memcpy(ptr, sector_buffer + s_inx, c_count);
		ok_bytes += c_count;
		ptr += c_count;
		s_inx = 0;
		c_count = ss;
		if (c_count > count - ok_bytes)
			c_count = count - ok_bytes;
	}

	if (i != nblocks && count != ok_bytes) {
		int bytes_left = count - ok_bytes;
		int blocks_left = nblocks - i;
		int alignedleft;

		// NEW read ahead code:
		int ahead = CD_READ_AHEAD_SECTORS;
		if (blocks_left < ahead) {
			nblocks += (ahead - blocks_left);
			blocks_left = ahead;
		}

		alignedleft = blocks_left*ss;

		tmpbuf = (char *)VirtualAlloc(
			NULL, alignedleft,
			MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
		if (tmpbuf) {
			got_bytes = cd_read_with_retry(fh, (first_block + i) * ss, alignedleft, tmpbuf);
			if (got_bytes != alignedleft) {
				// should never happen
				// Yes it does ...
				if (got_bytes < 0)
					got_bytes = 0;
				if (c_count > got_bytes)
					c_count = got_bytes;
				if (c_count > 0) {
					ttptr = tmpbuf;
					memcpy(ptr, ttptr + s_inx, c_count);
					ok_bytes += c_count;
				}
				VirtualFree(tmpbuf, 0, MEM_RELEASE );
				return ok_bytes;
			}
			ttptr = tmpbuf;
			for ( ; i < nblocks; i++) {
				if (c_count > 0) {
					memcpy(ptr, ttptr + s_inx, c_count);
					ok_bytes += c_count;
					ptr += c_count;
				}
				s_inx = 0;
				c_count = ss;
				if (c_count > count - ok_bytes)
					c_count = count - ok_bytes;
				cache_put(cptr, first_block + i, ttptr, ss);
				ttptr += ss;
			}
			VirtualFree(tmpbuf, 0, MEM_RELEASE );
		}
	}

	return ok_bytes;
}


/*
 *  Check if file handle FH represents a readable CD-ROM
 */

static bool is_cdrom_readable(file_handle *fh)
{
	if (!fh || !fh->fh)
		return false;

	cache_clear(&fh->cache);

	DWORD dummy;
	bool result = (0 != DeviceIoControl(
		fh->fh,
		IOCTL_STORAGE_CHECK_VERIFY,
		NULL, 0,
		NULL, 0,
		&dummy,
		NULL));
	if (!result) {
		const size_t n_bytes = 2048;
		char *buffer = (char *)VirtualAlloc(NULL, n_bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
		if (buffer) {
			result = (cd_read_with_retry(fh, 0, n_bytes, buffer) == n_bytes);
			VirtualFree(buffer, 0, MEM_RELEASE);
		}
	}

	return result;
}


/*
 *  Check if NAME represents a read-only file
 */

static bool is_read_only_path(const TCHAR *name)
{
	DWORD attrib = GetFileAttributes(name);
	return (attrib != INVALID_FILE_ATTRIBUTES && ((attrib & FILE_ATTRIBUTE_READONLY) != 0));
}


/*
 *  Open file/device, create new file handle (returns NULL on error)
 */

void *Sys_open(const char *path_name, bool read_only, bool is_cdrom)
{
	file_handle * fh = NULL;

	// Parse path name and options
	TCHAR name[MAX_PATH];
	tcslcpy(name, path_name, lengthof(name));

	// Normalize floppy / cd path
	int name_len = _tcslen(name);
	if (name_len == 1 && _istalpha(name[0]))
		_tcscat(name, TEXT(":\\"));
	if (name_len > 0 && name[name_len - 1] == TEXT(':'))
		_tcscat(name, TEXT("\\"));
	name_len = _tcslen(name);

	D(bug(TEXT("Sys_open(%s, %s)\n"), name, read_only ? TEXT("read-only") : TEXT("read/write")));
	if (name_len > 0 && name[name_len - 1] == TEXT('\\')) {
		int type = GetDriveType(name);

		if (type == DRIVE_CDROM) {
			read_only = true;
			TCHAR device_name[MAX_PATH];
			_sntprintf(device_name, lengthof(device_name), TEXT("\\\\.\\%c:"), name[0]);

			// Open device
			HANDLE h = CreateFile(
				device_name,
				GENERIC_READ,
				0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

			if (h != INVALID_HANDLE_VALUE) {
				fh = new file_handle;
				fh->name = _tcsdup(name);
				fh->fh = h;
				fh->is_file = false;
				fh->read_only = read_only;
				fh->start_byte = 0;
				fh->is_floppy = false;
				fh->is_cdrom = true;
				memset(&fh->cache, 0, sizeof(cachetype));
				cache_init(&fh->cache);
				cache_clear(&fh->cache);
				if (!PrefsFindBool("nocdrom"))
					fh->is_media_present = is_cdrom_readable(fh);
			}
		}
	}

	else { // Hard file

		// Check if write access is allowed, set read-only flag if not
		if (!read_only && is_read_only_path(name))
			read_only = true;

		// Open file

#if defined(BINCUE)
		void *binfd = open_bincue(name); // check if bincue
#endif

		HANDLE h = CreateFile(
			name,
			read_only ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
			0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

		if (h == INVALID_HANDLE_VALUE && !read_only) {
			// Read-write failed, try read-only
			read_only = true;
			h = CreateFile(
				name,
				GENERIC_READ,
				0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		}

		if (h != INVALID_HANDLE_VALUE) {
			fh = new file_handle;
			fh->name = _tcsdup(name);
			fh->fh = h;
			fh->is_file = true;
			fh->read_only = read_only;
			fh->start_byte = 0;
			fh->is_floppy = false;
			fh->is_cdrom = false;

#if defined(BINCUE)
			if (binfd) {
				fh->bincue_fd = binfd;
				fh->is_bincue = true;
				fh->is_media_present = true;
				sys_add_file_handle(fh);
				return fh;
			}
#endif

			// Detect disk image file layout
			loff_t size = GetFileSize(h, NULL);
			DWORD bytes_read;
			uint8 data[256];
			ReadFile(h, data, sizeof(data), &bytes_read, NULL);
			FileDiskLayout(size, data, fh->start_byte, fh->file_size);
		}
	}

	if (fh) {
		if (fh->is_floppy && first_floppy == NULL)
			first_floppy = fh;
		sys_add_file_handle(fh);
	}

	return fh;
}


/*
 *  Close file/device, delete file handle
 */

void Sys_close(void *arg)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return;

	sys_remove_file_handle(fh);

#if defined(BINCUE)
	if (fh->is_bincue)
		close_bincue(fh->bincue_fd);
#endif

	if (fh->is_cdrom) {
		cache_final(&fh->cache);
		SysAllowRemoval((void *)fh);
	}
	if (fh->storage_ejection_handle != NULL) {
		CloseHandle(fh->storage_ejection_handle);
		fh->storage_ejection_handle = NULL;
	}
	if (fh->fh != NULL) {
		CloseHandle(fh->fh);
		fh->fh = NULL;
	}
	if (fh->name)
		free(fh->name);

	delete fh;
}


/*
 *  Read "length" bytes from file/device, starting at "offset", to "buffer",
 *  returns number of bytes read (or 0)
 */

size_t Sys_read(void *arg, void *buffer, loff_t offset, size_t length)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return 0;

#if defined(BINCUE)
	if (fh->is_bincue)
		return read_bincue(fh->bincue_fd, buffer, offset, length);
#endif

	DWORD bytes_read = 0;

	if (fh->is_file) {
		bytes_read = file_offset_read(fh->fh, offset, length, (char *)buffer);
	}
	else if (fh->is_cdrom) {
		int bytes_left, try_bytes, got_bytes;
		char *b = (char *)buffer;
		bytes_left = length;
		while (bytes_left) {
			try_bytes = min(bytes_left, 32768);
			if (fh->is_cdrom) {
				got_bytes = cd_read(fh, &fh->cache, (DWORD)offset, try_bytes, b);
				if (got_bytes != try_bytes && !PrefsFindBool("nocdrom"))
					fh->is_media_present = is_cdrom_readable(fh);
			}
			b += got_bytes;
			offset += got_bytes;
			bytes_read += got_bytes;
			bytes_left -= got_bytes;
			if (got_bytes != try_bytes)
				bytes_left = 0;
		}
	}
	// TODO: other media

	return bytes_read;
}


/*
 *  Write "length" bytes from "buffer" to file/device, starting at "offset",
 *  returns number of bytes written (or 0)
 */

size_t Sys_write(void *arg, void *buffer, loff_t offset, size_t length)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return 0;

	DWORD bytes_written = 0;

	if (fh->is_file) {
		// Seek to position
		LONG lo = (LONG)offset;
		LONG hi = (LONG)(offset >> 32);
		DWORD r = SetFilePointer(fh->fh, lo, &hi, FILE_BEGIN);
		if (r == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
			return 0;

		// Write data
		if (WriteFile(fh->fh, buffer, length, &bytes_written, NULL) == 0)
			bytes_written = 0;
	}
	// TODO: other media

	return bytes_written;
}


/*
 *  Return size of file/device (minus header)
 */

loff_t SysGetFileSize(void *arg)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return true;

#if defined(BINCUE)
	if (fh->is_bincue)
		return size_bincue(fh->bincue_fd);
#endif 

	if (fh->is_file)
		return fh->file_size;
	else if (fh->is_cdrom)
		return 0x28A00000; // FIXME: get real CD-ROM size
	else {
		// TODO: other media
		return 0;
	}
}

static void PreventRemovalCommon(file_handle * fh, bool val) {
	D(bug("PreventRemovalCommon %p %d\n", fh, val));
	if (!fh) return;
	if (!fh->is_cdrom) return;
	if (!fh->name) return;
	D(bug(" seems ok to do\n"));

	if (fh->storage_ejection_handle == NULL) {
		// we need a device handle with just FILE_READ_ATTRIBUTES
		TCHAR device_name[MAX_PATH];
		_sntprintf(device_name, lengthof(device_name), TEXT("\\\\.\\%c:"), fh->name[0]);

		// Open device
		HANDLE h = CreateFile(
			device_name,
			FILE_READ_ATTRIBUTES,
			0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

		if (h == INVALID_HANDLE_VALUE) {
			D(bug(" failed to get suitable handle\n"));
			return;
		}

		fh->storage_ejection_handle = h;
	}

	PreventRemovalOfVolume(fh->storage_ejection_handle, val);
}


/*
 *  Eject volume (if applicable)
 */

void SysEject(void *arg)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return;

#if defined(BINCUE)
	if (fh->is_bincue) {
		fh->is_media_present = false;
		return;
	}
#endif

	if (fh->is_cdrom && fh->fh) {
		fh->is_media_present = false;
		// Commented out because there was some problems, but can't remember
		// exactly ... need to find out
		// EjectVolume(toupper(*fh->name),false);

		D(bug("SysEject disabling PreventRemoval\n"));
		PreventRemovalCommon(fh, false);
		fh->is_tray_locked = false;

		if (!PrefsFindBool("nocdrom")) {
			DWORD dummy;
			DeviceIoControl(
				fh->fh,
				IOCTL_STORAGE_EJECT_MEDIA,
				NULL, 0,
				NULL, 0,
				&dummy,
				NULL
				);
		}
		cache_clear(&fh->cache);
		fh->start_byte = 0;
	}
	// TODO: handle floppies
}


/*
 *  Format volume (if applicable)
 */

bool SysFormat(void *arg)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return false;

	//!!
	return true;
}


/*
 *  Check if file/device is read-only (this includes the read-only flag on Sys_open())
 */

bool SysIsReadOnly(void *arg)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return true;

	return fh->read_only;
}


/*
 *  Check if the given file handle refers to a fixed or a removable disk
 */

bool SysIsFixedDisk(void *arg)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return true;

	if (fh->is_file)
		return true;
	else if (fh->is_floppy || fh->is_cdrom)
		return false;
	else
		return true;
}


/*
 *  Check if a disk is inserted in the drive (always true for files)
 */

bool SysIsDiskInserted(void *arg)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return false;

	if (fh->is_file)
		return true;
	else if (fh->is_cdrom && !PrefsFindBool("nocdrom")) {
		if (PrefsFindBool("pollmedia"))
			fh->is_media_present = is_cdrom_readable(fh);
		return fh->is_media_present;
	}
	else {
		// TODO: other media
	}

	return false;
}


/*
 *  Prevent medium removal (if applicable)
 */

void SysPreventRemoval(void *arg)
{
	D(bug("SysPreventRemoval %p\n", arg));
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return;

	if (fh->is_cdrom && fh->fh && !fh->is_tray_locked) {
		D(bug("  doing prevent removal\n"));
		PreventRemovalCommon(fh, true);
		fh->is_tray_locked = true;
	}
}


/*
 *  Allow medium removal (if applicable)
 */

void SysAllowRemoval(void *arg)
{
	D(bug("SysAllowRemoval %p\n", arg));
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return;

	if (fh->is_cdrom && fh->fh) {
		D(bug("  doing allow removal\n"));
		PreventRemovalCommon(fh, false);
		fh->is_tray_locked = false;
	}
}


/*
 *  Read CD-ROM TOC (binary MSF format, 804 bytes max.)
 */

bool SysCDReadTOC(void *arg, uint8 *toc)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return false;

#if defined(BINCUE)
	if (fh->is_bincue)
		return readtoc_bincue(fh->bincue_fd, toc);
#endif

	if (fh->is_cdrom) {

		DWORD dummy;
		return DeviceIoControl(fh->fh,
							   IOCTL_CDROM_READ_TOC,
							   NULL, 0,
							   toc, min((int)sizeof(CDROM_TOC), 804),
							   &dummy,
							   NULL) != FALSE;

	} else
		return false;
}


/*
 *  Read CD-ROM position data (Sub-Q Channel, 16 bytes, see SCSI standard)
 */

bool SysCDGetPosition(void *arg, uint8 *pos)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return false;

#if defined(BINCUE)
	if (fh->is_bincue)
		return GetPosition_bincue(fh->bincue_fd, pos);
#endif

	if (fh->is_cdrom) {
		SUB_Q_CHANNEL_DATA q_data;

		CDROM_SUB_Q_DATA_FORMAT q_format;
		q_format.Format = IOCTL_CDROM_CURRENT_POSITION;
		q_format.Track = 0; // used only by ISRC reads

		DWORD dwBytesReturned = 0;
		bool ok = DeviceIoControl(fh->fh,
								  IOCTL_CDROM_READ_Q_CHANNEL,
								  &q_format, sizeof(CDROM_SUB_Q_DATA_FORMAT),
								  &q_data, sizeof(SUB_Q_CHANNEL_DATA),
								  &dwBytesReturned,
								  NULL) != FALSE;
		if (ok)
			memcpy(pos, &q_data.CurrentPosition, sizeof(SUB_Q_CURRENT_POSITION));

		return ok;
	} else
		return false;
}


/*
 *  Play CD audio
 */

bool SysCDPlay(void *arg, uint8 start_m, uint8 start_s, uint8 start_f, uint8 end_m, uint8 end_s, uint8 end_f)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return false;

#if defined(BINCUE)
	if (fh->is_bincue)
		return CDPlay_bincue(fh->bincue_fd, start_m, start_s, start_f, end_m, end_s, end_f);
#endif

	if (fh->is_cdrom) {
		CDROM_PLAY_AUDIO_MSF msf;
		msf.StartingM = start_m;
		msf.StartingS = start_s;
		msf.StartingF = start_f;
		msf.EndingM = end_m;
		msf.EndingS = end_s;
		msf.EndingF = end_f;

		DWORD dwBytesReturned = 0;
		return DeviceIoControl(fh->fh,
							   IOCTL_CDROM_PLAY_AUDIO_MSF,
							   &msf, sizeof(CDROM_PLAY_AUDIO_MSF),
							   NULL, 0,
							   &dwBytesReturned,
							   NULL) != FALSE;
	} else
		return false;
}


/*
 *  Pause CD audio
 */

bool SysCDPause(void *arg)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return false;

#if defined(BINCUE)
	if (fh->is_bincue)
		return CDPause_bincue(fh->bincue_fd);
#endif

	if (fh->is_cdrom) {
		DWORD dwBytesReturned = 0;
		return DeviceIoControl(fh->fh,
							   IOCTL_CDROM_PAUSE_AUDIO,
							   NULL, 0,
							   NULL, 0,
							   &dwBytesReturned,
							   NULL) != FALSE;
	} else
		return false;
}


/*
 *  Resume paused CD audio
 */

bool SysCDResume(void *arg)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return false;

#if defined(BINCUE)
	if (fh->is_bincue)
		return CDResume_bincue(fh->bincue_fd);
#endif

	if (fh->is_cdrom) {
		DWORD dwBytesReturned = 0;
		return DeviceIoControl(fh->fh,
							   IOCTL_CDROM_RESUME_AUDIO,
							   NULL, 0,
							   NULL, 0,
							   &dwBytesReturned, NULL) != FALSE;
	} else
		return false;
}


/*
 *  Stop CD audio
 */

bool SysCDStop(void *arg, uint8 lead_out_m, uint8 lead_out_s, uint8 lead_out_f)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return false;

#if defined(BINCUE)
	if (fh->is_bincue)
		return CDStop_bincue(fh->bincue_fd);
#endif

	if (fh->is_cdrom) {
		DWORD dwBytesReturned = 0;
		return DeviceIoControl(fh->fh,
							   IOCTL_CDROM_STOP_AUDIO,
							   NULL, 0,
							   NULL, 0,
							   &dwBytesReturned,
							   NULL) != FALSE;
	} else
		return false;
}


/*
 *  Perform CD audio fast-forward/fast-reverse operation starting from specified address
 */

bool SysCDScan(void *arg, uint8 start_m, uint8 start_s, uint8 start_f, bool reverse)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return false;

#if defined(BINCUE)
	if (fh->is_bincue)
		return CDScan_bincue(fh->bincue_fd,start_m,start_s,start_f,reverse);
#endif

	if (fh->is_cdrom) {

		CDROM_SEEK_AUDIO_MSF msf;
		msf.M = start_m;
		msf.S = start_s;
		msf.F = start_f;

		DWORD dwBytesReturned = 0;
		return DeviceIoControl(fh->fh,
							   IOCTL_CDROM_SEEK_AUDIO_MSF,
							   &msf, sizeof(CDROM_SEEK_AUDIO_MSF),
							   NULL, 0,
							   &dwBytesReturned,
							   NULL) != FALSE;
	} else
		return false;
}


/*
 *  Set CD audio volume (0..255 each channel)
 */

void SysCDSetVolume(void *arg, uint8 left, uint8 right)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return;

#if defined(BINCUE)
	if (fh->is_bincue)
		CDSetVol_bincue(fh->bincue_fd,left,right);
#endif

	if (fh->is_cdrom) {

		VOLUME_CONTROL vc;
		vc.PortVolume[0] = left;
		vc.PortVolume[1] = right;
		vc.PortVolume[2] = left;
		vc.PortVolume[3] = right;

		DWORD dwBytesReturned = 0;
		DeviceIoControl(fh->fh,
						IOCTL_CDROM_SET_VOLUME,
						&vc, sizeof(VOLUME_CONTROL),
						NULL, 0,
						&dwBytesReturned,
						NULL);
	}
}


/*
 *  Get CD audio volume (0..255 each channel)
 */

void SysCDGetVolume(void *arg, uint8 &left, uint8 &right)
{
	file_handle *fh = (file_handle *)arg;
	if (!fh)
		return;

	left = right = 0;

#if defined(BINCUE)
	if (fh->is_bincue)
		CDGetVol_bincue(fh->bincue_fd,&left,&right);
#endif
	
	if (fh->is_cdrom) {

		VOLUME_CONTROL vc;
		memset(&vc, 0, sizeof(vc));

		DWORD dwBytesReturned = 0;
		if (DeviceIoControl(fh->fh,
							IOCTL_CDROM_GET_VOLUME,
							NULL, 0,
							&vc, sizeof(VOLUME_CONTROL),
							&dwBytesReturned,
							NULL))
		{
			left = vc.PortVolume[0];
			right = vc.PortVolume[1];
		}
	}
}
