/***************************************************************************

  machine/stvcd.c - Sega Saturn and ST-V CD-ROM handling

  Rewritten Dec. 2005 by R. Belmont, based on the abandoned "Sim"
  Saturn/ST-V emulator by RB & The_Author.

  Status: this is sufficient for the Saturn BIOS to recognize and
  boot discs but actual games demand a lot more than this
  implementation has to give at the moment.  In particular,
  buffer handling is a sick hack and filters need to be supported
  (fixes for both are in progress).

  Information sources:
  - Tyranid's document
  - A commented disassembly I made of the Saturn BIOS's CD code

  More info is welcome!  Please private message RB on mame.net,
  mameworld.net, or bannister.org's message boards if you can
  supply it.

  ------------------------------------------------------------

  Stuff we know about the CD protocol will collect here.

  Top byte of CR1 is always status.
  This status is the same for all commands.

  See CD_STAT_ defines below for definitions.

  Address is mostly in terms of FAD (Frame ADdress).
  FAD is absolute number of frames from the start of the disc.
  In other words, FAD = LBA + 150; FAD is the same units as
  LBA except it counts starting at absolute zero instead of
  the first sector (00:02:00 in MSF format).

  Commands executed on the way to booting a disk:

CD: Initialize CD system (PC=4232)
CD: Set CD Device Connection (PC=4232)
CD: Get Session Info (PC=4232)
CD: Play Disk (PC=4232)
CD: Get Buffer Size (PC=4232)
CD: Get and delete sector data (PC=4232)
CD: End data transfer (PC=4232)
CD: Change Directory (PC=4232)
CD: Get file system scope(PC=4232)
CD: Read File (PC=4232)
CD: Get Status (PC=4232)
CD: Get File Info (PC=4232)
CD: End data transfer (PC=4232)
CD: Get Buffer Size (PC=4232)
CD: Get and delete sector data (PC=4232)
CD: End data transfer (PC=4232)

***************************************************************************/

#include "driver.h"
#ifdef MESS
#include "devices/chd_cd.h"
#endif
#include "cdrom.h"
#include "stvcd.h"

static cdrom_file *cdrom = (cdrom_file *)NULL;

static void cd_readTOC(void);
static void cd_readblock(unsigned long fad, unsigned char *dat);
static void cd_playdata(void);

typedef struct
{
	unsigned char flags;		// iso9660 flags
	unsigned long length;		// length of file
	unsigned long firstfad;		// first sector of file
	unsigned char name[128];
} direntryT;

typedef struct
{
   UINT8 mode;
   UINT8 chan;
   UINT8 smmask;
   UINT8 cimask;
   UINT8 fid;
   UINT8 smval;
   UINT8 cival;
   UINT8 condtrue;
   UINT8 condfalse;
   UINT32 fad;
   UINT32 range;
} filterT;

#define MAX_FILTERS	(24)

// local variables
static UINT16 cr1, cr2, cr3, cr4;
static UINT16 hirqmask, hirqreg;
static int state;
static UINT16 cd_stat;
static unsigned int cd_curfad = 0;
static UINT32 in_buffer = 0;	// amount of data in the buffer
static int oddframe = 0;
static UINT32 wordsread = 0;
static UINT32 fadstoplay = 0;
static int buffull, sectorstore, freeblocks;
static filterT filters[MAX_FILTERS];
static filterT *cddevice;
static int cddevicenum;

// data transfer buffer
static UINT8 databuf[(512*1024)];	// saturn has a 512k CD data buffer
static UINT32 dbufptr;
static UINT32 dbuflen;

// iso9660 utilities
static void read_new_dir(unsigned long fileno);
static void make_dir_current(unsigned long fad);

static direntryT curroot;		// root entry of current filesystem
static direntryT *curdir;		// current directory
static int numfiles;			// # of entries in current directory
static int firstfile;			// first non-directory file

enum
{
	STATE_UNK = 0,
	STATE_HWINFO
};

// HIRQ definitions
#define CMOK 0x0001 // command ok / ready for new command
#define DRDY 0x0002 // drive ready
#define CSCT 0x0004 //
#define BFUL 0x0008 // buffer full
#define PEND 0x0010 // command pending
#define DCHG 0x0020 // disc change / tray open
#define ESEL 0x0040 // soft reset, end of blah
#define EHST 0x0080 //
#define ECPY 0x0100 //
#define EFLS 0x0200 // stop execution of cd block filesystem
#define SCDQ 0x0400 // subcode Q renewal complete
#define MPED 0x0800 // MPEG
#define MPCM 0x1000 // MPEG
#define MPST 0x2000 // MPEG

// CD status (hi byte of CR1) definitions:
// (these defines are shifted up 8)
#define CD_STAT_BUSY     0x0000		// status change in progress
#define CD_STAT_PAUSE    0x0100		// CD block paused (temporary stop)
#define CD_STAT_STANDBY  0x0200		// CD drive stopped
#define CD_STAT_PLAY     0x0300		// CD play in progress
#define CD_STAT_SEEK     0x0400		// drive seeking
#define CD_STAT_SCAN     0x0500		// drive scanning
#define CD_STAT_OPEN     0x0600		// tray is open
#define CD_STAT_NODISC   0x0700		// no disc present
#define CD_STAT_RETRY    0x0800		// read retry in progress
#define CD_STAT_ERROR    0x0900		// read data error occured
#define CD_STAT_FATAL    0x0a00		// fatal error (hard reset required)
#define CD_STAT_PERI     0x2000		// periodic response if set, else command response
#define CD_STAT_TRANS    0x4000		// data transfer request if set
#define CD_STAT_WAIT     0x8000		// waiting for command if set, else executed immediately
#define CD_STAT_REJECT   0xff00		// ultra-fatal error.

// global functions
void stvcd_reset(void)
{
	state = STATE_UNK;
	hirqmask = 0xffff;
	hirqreg = 0;
	cr1 = 'C';
	cr2 = ('D'<<8) | 'B';
	cr3 = ('L'<<8) | 'O';
	cr4 = ('C'<<8) | 'K';
	cd_stat = 0;

	curdir = (direntryT *)NULL;		// no directory yet

	// reset flag vars
	buffull = sectorstore = 0;

	freeblocks = 200;

	// open device
	if (cdrom)
	{
		cdrom_close(cdrom);
		cdrom = (cdrom_file *)NULL;
	}

	#ifdef MESS
	cdrom = mess_cd_get_cdrom_file_by_number(0);
	#else
	cdrom = cdrom_open(get_disk_handle(0));
	#endif

	memset(databuf, 0, (512*1024));

	dbufptr = 0;
	dbuflen = 0;
	wordsread = 0;

	if (cdrom)
	{
		logerror("Opened CD-ROM successfully, reading root directory\n");
		read_new_dir(0xffffff);	// read root directory
	}
}

static UINT16 cd_readWord(UINT32 addr)
{
	unsigned short rv;

	switch (addr & 0xffff)
	{
		case 0x0008:	// read HIRQ register
			hirqreg &= ~DCHG;	// always clear bit 6 (tray open)
//          logerror("CD: R HIRR (%04x) (PC=%x)\n", hirqreg,   activecpu_get_pc());
			rv = hirqreg;

			if (buffull) rv |= BFUL;
			if (sectorstore) rv |= CSCT;

			return rv;
			break;

		case 0x000C:
//          logerror("CD:\tRW HIRM (%04x)  (PC=%x)", hirqmask,   activecpu_get_pc());
			return hirqmask;
			break;

		case 0x0018:
			rv = cd_stat;
			if (!(cd_stat & CD_STAT_PERI) && (cd_stat & CD_STAT_SEEK))
			{
			 	cd_stat &= ~CD_STAT_SEEK;
				cd_stat |= CD_STAT_PAUSE;
			}
			cd_stat |= CD_STAT_PERI;	// always revert to periodic responses after a command
			if (cr1 == 'C')		// first read on cold boot?
			{
				rv = cr1;
				cr1 = 0;
				cd_stat = CD_STAT_PAUSE;
			}
//          logerror("CD: R CR1 (%04x)  (PC=%x HIRR=%04x)\n", rv, activecpu_get_pc(), hirqreg);
			return rv;
			break;

		case 0x001c:
//          logerror("CD: R CR2 (%04x)  (PC=%x HIRR=%04x)\n", cr2,   activecpu_get_pc(), hirqreg);
			return cr2;
			break;

		case 0x0020:
//          logerror("CD: R CR3 (%04x)  (PC=%x HIRR=%04x)\n", cr3,   activecpu_get_pc(), hirqreg);
#if 0
			if (cd_stat & CD_STAT_PERI)	   	// for periodic responses, return
			{								// the disc position
				cr3 = (cd_curfad>>16)&0xff;
				cr4 = (cd_curfad & 0xffff);
			}
#endif
			return cr3;
			break;

		case 0x0024:
//          logerror("CD: R CR4 (%04x)  (PC=%x HIRR=%04x)\n", cr4,   activecpu_get_pc(), hirqreg);
			rv = cr4;
			if (state == STATE_HWINFO)
			{
				hirqreg |= DRDY;
				cd_stat = CD_STAT_PERI|CD_STAT_PAUSE;	// switch to status readout after a read - bios wants state A
				cr1 = 0;
				cr2 = 0x4101;
				cr3 = 0x100;
				cr4 = 0;
				state = STATE_UNK;
			}
			return rv;
			break;

		case 0x8000:
			wordsread++;
			rv = databuf[dbufptr]<<8 | databuf[dbufptr+1];
//          logerror( "CD: RW %04x DATA (PC=%x)\n", rv,   activecpu_get_pc());
			if (dbufptr < (512*1024))
			{
				dbufptr += 2;
			}
			else
			{
				logerror(  "CD: attempt to read past data buffer end!");
			}
			return rv;
			break;

		default:
			logerror( "CD: RW %08x (PC=%x)\n", addr,   activecpu_get_pc());
			return 0xffff;
			break;
	}

	return 0xffff;
}

static UINT32 cd_readLong(UINT32 addr)
{
	unsigned long rv;

	switch (addr & 0xffff)
	{
		case 0x8000:
			wordsread++;

			rv = databuf[dbufptr]<<24 | databuf[dbufptr+1]<<16 | databuf[dbufptr+2]<<8 | databuf[dbufptr+3];

//          logerror( "CD: RL %08x DATA (PC=%x)", rv,   activecpu_get_pc());
			if (dbufptr < (512*1024))
			{
				dbufptr += 4;
			}
			else
			{
				logerror(  "CD: attempt to read past data buffer end!\n");
			}
			return rv;
			break;

		default:
			logerror( "CD: RL %08x (PC=%x)\n", addr,   activecpu_get_pc());
			return 0xffff;
			break;
	}
}

static void cd_writeWord(UINT32 addr, UINT16 data)
{
	unsigned long temp;

	switch(addr & 0xffff)
	{
	case 0x0008:
//      logerror("CD: W HIRR %04x (PC=%x)\n", data,   activecpu_get_pc());
		hirqreg |= data;	// never let write to HIRQ clear things (?)
		return;
	case 0x000C:
//      logerror("CD: W HIRM %04x (PC=%x)\n", data,   activecpu_get_pc());
		hirqmask = data;
		return;
	case 0x0018:
//      logerror("CD: W CR1 %04x (PC=%x)\n", data,   activecpu_get_pc());
		cr1 = data;
		cd_stat &= ~CD_STAT_PERI;
		break;
	case 0x001c:
//      logerror("CD: W CR2 %04x (PC=%x)\n", data,   activecpu_get_pc());
		cr2 = data;
		break;
	case 0x0020:
//      logerror("CD: W CR3 %04x (PC=%x)\n", data,   activecpu_get_pc());
		cr3 = data;
		break;
	case 0x0024:
//      logerror("CD: W CR4 %04x (PC=%x)\n", data,   activecpu_get_pc());
		cr4 = data;
		switch (cr1 & 0xff00)
		{
		case 0x0000:
			logerror( "CD: Get Status (PC=%x)\n",   activecpu_get_pc());

			// values taken from a US saturn with a disc in and the lid closed
			hirqreg = 0xfe3;
			cd_stat = CD_STAT_PAUSE;
			cd_stat &= ~CD_STAT_PERI;
			cr2 = 0x4100;
			cr3 = 0x100;
			cr4 = 0;
			break;

		case 0x0100:
			logerror( "CD: Get HW Info (PC=%x)\n",   activecpu_get_pc());
			hirqreg = 0xfe1;
			cd_stat = CD_STAT_PERI|CD_STAT_PAUSE;
			cd_stat = 0;
			cr2 = 0x0002;
			cr3 = 0x0000;
			cr4 = 0x0400;
			state = STATE_HWINFO;
			break;

		case 0x200:	// Get TOC
			logerror( "CD: Get TOC (PC=%x)\n",   activecpu_get_pc());
			cd_readTOC();
			cd_stat = CD_STAT_TRANS|CD_STAT_PAUSE;
			cr2 = dbuflen/2;	// TOC length
			cr3 = 0;
			cr4 = 0;
			hirqreg |= (CMOK|DRDY);
			break;

		case 0x0300:	// get session info (lower byte = session # to get?)
		            	// bios is interested in returns in cr3 and cr4
						// cr3 should be data track #
						// cr4 must be > 1 and < 100 or bios gets angry.
			logerror( "CD: Get Session Info (PC=%x)\n",   activecpu_get_pc());
			switch (cr1 & 0xff)
			{
				case 0:	// get total session info / disc start
					cr2 = 0;
					cr3 = 0x0100;	// total # of sessions in high byte (maybe should be 2?)
					cr4 = 0;
					break;

				case 1:	// get total session info / disc start
					cr2 = 0;
					cr3 = 0x0100;	// this should be 1 for track 1 or something
					cr4 = 0;
					break;

				default:
					fatalerror("CD: Unknown request to Get Session Info %x", cr1 & 0xff);
					break;
			}
			cd_stat = CD_STAT_PAUSE;
			cd_stat &= ~CD_STAT_PERI;
			hirqreg |= (CMOK);
			break;

		case 0x400:	// initialize CD system
				// CR1 & 1 = reset software
				// CR1 & 2 = decode RW subcode
				// CR1 & 4 = don't confirm mode 2 subheader
				// CR1 & 8 = retry reading mode 2 sectors
				// CR1 & 10 = force single-speed
			logerror( "CD: Initialize CD system (PC=%x)\n",   activecpu_get_pc());
			hirqreg |= (CMOK|DRDY|ESEL);
			cd_stat = CD_STAT_PERI|CD_STAT_PAUSE;
			cd_curfad = 150;
			dbufptr = 0;
			in_buffer = 0;
			buffull = 0;
			cr2 = 0x4101;
			cr3 = 0x100 | ((cd_curfad>>16)&0xff);
			cr4 = cd_curfad;
			break;

		case 0x0600:	// end data transfer
						// returns # of bytes transfered (24 bits) in
						// low byte of cr1 (MSB) and cr2 (middle byte, LSB)
			logerror( "CD: End data transfer (PC=%x)\n",   activecpu_get_pc());
			hirqreg |= (CMOK|ESEL|EFLS|SCDQ|DRDY);
			temp = wordsread;	// (should this return the # of words actually read?)
			temp &= 0xffffff;
			cd_stat &= 0xff00;
			cd_stat |= ((temp>>16)&0xff);
			cd_stat &= ~CD_STAT_PERI;
			cr2 = (temp&0xffff);
			dbufptr = 0;  		// reset buffer pointers
			wordsread = 0;		// this command will also reset the read count
			break;

		case 0x1000: // Play Disk.  FAD is in lowest 7 bits of cr1 and all of cr2.
			logerror( "CD: Play Disk (PC=%x)\n",   activecpu_get_pc());
			cd_stat = CD_STAT_PLAY|0x80;	// set "cd-rom" bit
			cd_curfad = ((cr1&0xff)<<16) | cr2;
			fadstoplay = ((cr3&0xff)<<16) | cr4;

			if (cd_curfad & 0x800000)
			{
				if (cd_curfad != 0xffffff)
				{
					// fad mode
					cd_curfad &= 0xfffff;
					fadstoplay &= 0xfffff;
				}
			}
			else
			{
				// track mode
				fatalerror("CD: Play Disk track mode, not yet implemented");
			}

			logerror("CD: Play Disk: start %x length %x\n", cd_curfad, fadstoplay);

			cr2 = 0x4101;	// ctrl/adr in hi byte, track # in low byte
			cr3 = 0x100;	// index of subcode in hi byte, frame address
			cr4 = 0;		// frame address
			hirqreg |= (CMOK|DRDY);
			oddframe = 0;
			dbufptr = 0;  		// reset buffer pointers
			in_buffer = 0;

			// and do the disc I/O
			cd_playdata();
			break;

		case 0x1100: // disk seek
			logerror( "CD: Disk seek (PC=%x)\n",   activecpu_get_pc());
			temp = (cr1&0x7f)<<16;
			temp |= cr2;

			hirqreg |= (CMOK|DRDY);
			hirqreg = 0x1;
			cd_stat = CD_STAT_SEEK;
			cr2 = 0x4101;
			cr3 = (temp>>16)&0xff;
			cr4 = (temp&0xffff);
			break;

		case 0x3000:	// Set CD Device connection
			{
				UINT8 parm;

				logerror( "CD: Set CD Device Connection (PC=%x) op %x\n",   activecpu_get_pc(), cr3>>8);

				// get operation
				parm = cr3>>8;

				cddevicenum = parm;

				if (parm == 0xff)
				{
					cddevice = (filterT *)NULL;
				}
				else
				{
					if (parm < 0x24)
					{
						cddevice = &filters[(cr3>>8)];
					}
				}

	//          cd_stat = CD_STAT_PAUSE;
				hirqreg |= (CMOK|ESEL);
			}
			break;

		case 0x4000:	// Set Filter Range
						// cr1 low + cr2 = FAD0, cr3 low + cr4 = FAD1
						// cr3 hi = filter num.
			logerror( "CD: Set Filter Range (PC=%x)\n",   activecpu_get_pc());
			hirqreg |= (CMOK|ESEL|DRDY);
			cr2 = 0x4101;	// ctrl/adr in hi byte, track # in low byte
			cr3 = 0x0100|((cd_curfad>>16)&0xff);
			cr4 = (cd_curfad & 0xffff);
			break;

		case 0x4200:	// Set Filter Subheader conditions
			{
				UINT8 fnum = (cr3>>8)&0xff;

				logerror( "CD: Set Filter Subheader conditions (PC=%x) %x => chan %x masks %x fid %x vals %x\n", activecpu_get_pc(), fnum, cr1&0xff, cr2, cr3&0xff, cr4);

				filters[fnum].chan = cr1 & 0xff;
				filters[fnum].smmask = (cr2>>8)&0xff;
				filters[fnum].cimask = cr2&0xff;
				filters[fnum].fid = cr3&0xff;
				filters[fnum].smval = (cr4>>8)&0xff;
				filters[fnum].cival = cr4&0xff;

				hirqreg |= (CMOK|ESEL);
				cr2 = 0x4101;	// ctrl/adr in hi byte, track # in low byte
				cr3 = 0x0100|((cd_curfad>>16)&0xff);
				cr4 = (cd_curfad & 0xffff);
			}
			break;

		case 0x4400:	// Set Filter Mode
			{
				UINT8 fnum = (cr3>>8)&0xff;
				UINT8 mode = (cr1 & 0xff);

				// initialize filter?
				if (mode & 0x80)
				{
					memset(&filters[fnum], 0, sizeof(filterT));
				}
				else
				{
					filters[fnum].mode = mode;
				}

				logerror( "CD: Set Filter Mode (PC=%x) filt %x mode %x\n", activecpu_get_pc(), fnum, mode);
				hirqreg |= (CMOK|ESEL|DRDY);
				cr2 = 0x4101;	// ctrl/adr in hi byte, track # in low byte
				cr3 = 0x0100|((cd_curfad>>16)&0xff);
				cr4 = (cd_curfad & 0xffff);
			}
			break;

		case 0x4600:	// Set Filter Connection
			{
				UINT8 fnum = (cr3>>8)&0xff;

				logerror( "CD: Set Filter Connection (PC=%x) %x => mode %x parm %04x\n", activecpu_get_pc(), fnum, cr1 & 0xf, cr2);

				// set true condition?
				if (cr1 & 1)
				{
					filters[fnum].condtrue = (cr2>>8)&0xff;
				}
				else if (cr1 & 2)	// set false condition
				{
					filters[fnum].condfalse = cr2&0xff;
				}

				hirqreg |= (CMOK|ESEL);
				cr2 = 0x4101;	// ctrl/adr in hi byte, track # in low byte
				cr3 = 0x0100|((cd_curfad>>16)&0xff);
				cr4 = (cd_curfad & 0xffff);
			}
			break;

		case 0x4800:	// Reset Selector
			logerror( "CD: Reset Selector (PC=%x)\n",   activecpu_get_pc());
			hirqreg |= (CMOK|ESEL|DRDY);
			cr2 = 0x4101;	// ctrl/adr in hi byte, track # in low byte
			cr3 = 0x0100|((cd_curfad>>16)&0xff);
			cr4 = (cd_curfad & 0xffff);
			break;

		case 0x5000:	// get CD block size
			logerror( "CD: Get CD Block Size (PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;	// zap periodic response
			cr2 = ((512*1024)-in_buffer)/2048;
			cr3 = 0x1800;
			cr4 = 0x00c8;
			hirqreg |= (CMOK|ESEL);
			break;

		case 0x5100:	// get buffer size
			logerror( "CD: Get Buffer Size (PC=%x) => %x\n",   activecpu_get_pc(), in_buffer);
			cd_stat &= ~CD_STAT_PERI;	// zap periodic response
			cr2 = 200;			// free blocks
			cr3 = 0x1800;			// max selectors
			cr4 = in_buffer/2048;		// buffer size in sectors (2048 byte units)
			hirqreg |= (CMOK);
			break;

		case 0x5200:	// calculate acutal size
			logerror( "CD: Calculate actual size (PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			hirqreg |= (CMOK|DRDY);
			cr2 = 0x4101;	// CTRL/track
			cr3 = (cd_curfad>>16)&0xff;
			cr4 = (cd_curfad & 0xffff);
			break;

		case 0x5300:	// get actual block size
			logerror( "CD: Get actual block size (PC=%x)\n", activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			hirqreg |= (CMOK|DRDY);
			temp = in_buffer/2;	// must be in words
			temp &= 0xffffff;
			cd_stat &= 0xff00;
			cd_stat |= ((temp>>16)&0xff);
			cr2 = (temp&0xffff);
			cr3 = cr4 = 0;
			break;

		case 0x6000:	// set sector length
			logerror( "CD: Set sector length (PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			hirqreg |= (CMOK|ESEL|EFLS|SCDQ|DRDY);
			break;

		case 0x6100:	// get sector data
			logerror( "CD: Get sector data (PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			cd_stat |= CD_STAT_TRANS;
			hirqreg |= (CMOK|ESEL|EFLS|SCDQ|DRDY);
			break;

		case 0x6200:	// delete sector data
			logerror( "CD: Delete sector data (PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			cd_stat |= CD_STAT_TRANS;
			hirqreg |= (CMOK|ESEL|EFLS|SCDQ|DRDY);
			in_buffer = 0;	// zap sector data here

			// go ahead and read more.  GFS uses this for Nw mode on
			// multiblock reads.
			fadstoplay = 6;	// go 6 sectors at a time - oughta beat the timeout
			cd_playdata();
//          logerror(  "Got next sector (curFAD=%ld), in_buffer = %ld", cd_curfad, in_buffer);
			break;

		case 0x6300:	// get then delete sector data
			logerror( "CD: Get and delete sector data (PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			cd_stat |= CD_STAT_TRANS;
			hirqreg |= (CMOK|ESEL|EFLS|SCDQ|DRDY);
			break;

		case 0x6700:	// get copy error
			logerror( "CD: Get copy error (PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			hirqreg |= (CMOK|ESEL|EFLS|SCDQ|DRDY);
			break;

		case 0x7000:	// change directory
			logerror( "CD: Change Directory (PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			hirqreg |= (CMOK|DRDY);

			temp = (cr3&0xff)<<16;
			temp |= cr4;
			read_new_dir(temp);
			break;

		case 0x7100:	// Read directory entry
			logerror( "CD: Read Directory Entry (PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			hirqreg |= (CMOK|DRDY);

			temp = (cr3&0xff)<<16;
			temp |= cr4;
			cr2 = 0x4101;	// CTRL/track
			cr3 = (curdir[temp].firstfad>>16)&0xff;
			cr4 = (curdir[temp].firstfad&0xffff);
			break;

		case 0x7200:	// Get file system scope
			logerror( "CD: Get file system scope(PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			hirqreg |= (CMOK|DRDY);
			cr2 = numfiles;	// # of files in directory
			cr3 = 0x0100;	// report directory held
			cr4 = firstfile;	// first file id
			break;

		case 0x7300:	// Get File Info
			logerror( "CD: Get File Info (PC=%x)\n",   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			cd_stat |= CD_STAT_TRANS;
			cd_stat &= 0xff00;		// clear top byte of return value
			hirqreg |= (CMOK|DRDY);

			temp = (cr3&0xff)<<16;
			temp |= cr4;

			if (temp == 0xffffff)	// special
			{
			}
			else
			{
				cr2 = 6;	// 6 words for single file
							// first 4 bytes = FAD address
							// second 4 bytes = length
							// last 4 bytes:
							// - unit size
							// - gap size
							// - file #
							// attributes flags

				// start ptr at last 4k from end
				dbufptr = (508*1024);
				// first 4 bytes = FAD
				databuf[(508*1024)] =   (curdir[temp].firstfad>>24)&0xff;
				databuf[(508*1024)+1] = (curdir[temp].firstfad>>16)&0xff;
				databuf[(508*1024)+2] = (curdir[temp].firstfad>>8)&0xff;
				databuf[(508*1024)+3] = (curdir[temp].firstfad&0xff);
		 		// second 4 bytes = length of file
				databuf[(508*1024)+4] = (curdir[temp].length>>24)&0xff;
				databuf[(508*1024)+5] = (curdir[temp].length>>16)&0xff;
				databuf[(508*1024)+6] = (curdir[temp].length>>8)&0xff;
				databuf[(508*1024)+7] = (curdir[temp].length&0xff);

				databuf[(508*1024)+8] = 0x00;
				databuf[(508*1024)+9] = 0x00;
				databuf[(508*1024)+10] = temp;
				databuf[(508*1024)+11] = curdir[temp].flags;
			}
			wordsread = 0;
			break;

		case 0x7400:	// Read File
			logerror( "CD: Read File (PC=%x)\n",   activecpu_get_pc());
			temp = (cr3&0xff)<<16;
			temp |= cr4;

			cd_stat = CD_STAT_PLAY|0x80;	// set "cd-rom" bit
			cr2 = 0x4101;	// CTRL/track
			cr3 = (curdir[temp].firstfad>>16)&0xff;
			cr4 = (curdir[temp].firstfad&0xffff);

			cd_curfad = curdir[temp].firstfad;
			if (curdir[temp].length / 2048)
			{
				fadstoplay = curdir[temp].length/2048;
				fadstoplay++;
			}
			else
			{
				fadstoplay = curdir[temp].length/2048;
			}

			hirqreg |= (CMOK|DRDY);
			oddframe = 0;
			dbufptr = 0;  		// reset buffer pointers
			in_buffer = 0;

			// and do the disc I/O
			cd_playdata();
			break;

		case 0x7500:
			logerror( "CD: Abort File (PC=%x)\n",   activecpu_get_pc());
			// bios expects "2bc" mask to work against this
			hirqreg |= (CMOK|EFLS|EHST|ESEL|DCHG|PEND|BFUL|CSCT|DRDY);
			cd_stat = CD_STAT_PERI|CD_STAT_PAUSE;	// force to pause
			break;

		case 0xe000:	// appears to be copy protection check.  needs only to return OK.
			logerror( "CD: Verify copy protection (PC=%x)\n",   activecpu_get_pc());
			cd_stat = CD_STAT_PAUSE;
			cr2 = 0x4;
			hirqreg |= (CMOK|DRDY);
			break;

		case 0xe100:	// get disc region
			logerror( "CD: Get disc region (PC=%x)\n",   activecpu_get_pc());
			cd_stat = CD_STAT_PAUSE;
			cr2 = 0x4;		// (must return this value to pass bios checks)
			hirqreg |= (CMOK|DRDY);
			break;

		default:
			logerror( "CD: Unknown command %04x (PC=%x)\n", cr1,   activecpu_get_pc());
			cd_stat &= ~CD_STAT_PERI;
			hirqreg |= (CMOK|DRDY);
			break;
		}
//      scu_cdinterrupt();
		break;
	default:
		logerror("CD: WW %08x %04x (PC=%x)\n", addr, data, activecpu_get_pc());
		break;
	}

}

READ32_HANDLER( stvcd_r )
{
	UINT32 rv = 0;

	offset <<= 2;

	switch (offset)
	{
		case 0x90008:
		case 0x9000c:
		case 0x90018:
		case 0x9001c:
		case 0x90020:
		case 0x90024:
			rv = cd_readWord(offset);
			return rv<<16;
			break;

		case 0x98000:
		case 0x18000:
			if (mem_mask == 0)
			{
				rv = cd_readLong(offset);
			}
			else if (mem_mask == 0x0000ffff)
			{
				rv = cd_readWord(offset)<<16;
			}
			else
			{
				fatalerror("CD: Unknown data buffer read @ mask = %08x", mem_mask);
			}

			break;

		default:
			logerror("Unknown CD read @ %x\n", offset);
			break;
	}

	return rv;
}

WRITE32_HANDLER( stvcd_w )
{
	offset <<= 2;

	switch (offset)
	{
		case 0x90008:
		case 0x9000c:
		case 0x90018:
		case 0x9001c:
		case 0x90020:
		case 0x90024:
			cd_writeWord(offset, data>>16);
			break;

		default:
			logerror("Unknown CD write %x @ %x\n", data, offset);
			break;
	}
}

// iso9660 parsing
static void read_new_dir(unsigned long fileno)
{
	int foundpd, i;
	unsigned long cfad, dirfad;
	unsigned char sect[2048];

	if (fileno == 0xffffff)
	{
		cfad = 166;		// first sector of directory as per iso9660 specs

		foundpd = 0;	// search for primary vol. desc
		while ((!foundpd) && (cfad < 200))
		{
			memset(sect, 0, 2048);
			cd_readblock(cfad++, sect);

			if ((sect[1] == 'C') && (sect[2] == 'D') && (sect[3] == '0') && (sect[4] == '0') && (sect[5] == '1'))
			{
			 	switch (sect[0])
				{
					case 0:	// boot record
						break;

					case 1: // primary vol. desc
						foundpd = 1;
						break;

					case 2: // secondary vol desc
						break;

					case 3: // vol. section descriptor
						break;

					case 0xff:
						cfad = 200;
						break;
				}
			}
		}

		// got primary vol. desc.
		if (foundpd)
		{
			dirfad = sect[140] | (sect[141]<<8) | (sect[142]<<16) | (sect[143]<<24);
			dirfad += 150;

			// parse root entry
			curroot.firstfad = sect[158] | (sect[159]<<8) | (sect[160]<<16) | (sect[161]<<24);
			curroot.firstfad += 150;
			curroot.length = sect[166] | (sect[167]<<8) | (sect[168]<<16) | (sect[169]<<24);
			curroot.flags = sect[181];
			for (i = 0; i < sect[188]; i++)
			{
				curroot.name[i] = sect[189+i];
			}
			curroot.name[i] = '\0';	// terminate

			// easy to fix, but make sure we *need* to first
			if (curroot.length > 14336)
			{
				fatalerror("ERROR: root directory too big (%ld)", curroot.length);
			}

			// done with all that, read the root directory now
			make_dir_current(curroot.firstfad);
		}
	}
	else
	{
		if (curdir[fileno].length > 14336)
		{
			fatalerror("ERROR: new directory too big (%ld)!", curdir[fileno].length);
		}
		make_dir_current(curdir[fileno].firstfad);
	}
}

// makes the directory pointed to by FAD current
static void make_dir_current(unsigned long fad)
{
	int i;
	unsigned long nextent, numentries;
	unsigned char sect[14336];
	direntryT *curentry;

	memset(sect, 0, 14336);
	for (i = 0; i < (curroot.length/2048); i++)
	{
		cd_readblock(fad+i, &sect[2048*i]);
	}

	nextent = 0;
	numentries = 0;
	while (nextent < 14336)
	{
		if (sect[nextent])
		{
			nextent += sect[nextent];
			numentries++;
		}
		else
		{
			nextent = 14336;
		}
	}

	if (curdir != (direntryT *)NULL)
	{
		free((void *)curdir);
	}

	curdir = (direntryT *)malloc(sizeof(direntryT)*numentries);
	curentry = curdir;
	numfiles = numentries;

	nextent = 0;
	while (numentries)
	{
		curentry->firstfad = sect[nextent+2] | (sect[nextent+3]<<8) | (sect[nextent+4]<<16) | (sect[nextent+5]<<24);
		curentry->firstfad += 150;
		curentry->length = sect[nextent+10] | (sect[nextent+11]<<8) | (sect[nextent+12]<<16) | (sect[nextent+13]<<24);
		curentry->flags = sect[nextent+25];
		for (i = 0; i < sect[nextent+32]; i++)
		{
			curentry->name[i] = sect[nextent+33+i];
		}
		curentry->name[i] = '\0';	// terminate

		nextent += sect[nextent];
		curentry++;
		numentries--;
	}

	for (i = 0; i < numfiles; i++)
	{
		if (!(curdir[i].flags & 0x02))
		{
			firstfile = i;
			i = numfiles;
		}
	}
}

static void cd_readTOC(void)
{
	int i, ntrks, toclen, tocptr;

	if (cdrom)
	{
		ntrks = cdrom_get_last_track(cdrom);
	}
	else
	{
		ntrks = 0;
	}

	toclen = (4 * ntrks);	// toclen header entry

	// data format for Saturn TOC:
	// no header.
	// 4 bytes per track
	// top nibble of first byte is CTRL info
	// low nibble is ADR
	// next 3 bytes are FAD address (LBA + 150)
	// there are always 99 track entries (0-98)
	// unused tracks are ffffffff.
	// entries 99-101 are metadata

	tocptr = 0;	// starting point of toc entries

	for (i = 0; i < ntrks; i++)
	{
		int fad;

		if (cdrom)
		{
			databuf[tocptr] = cdrom_get_adr_control(cdrom, i)<<4 | 0x01;
		}

		if (cdrom)
		{
			fad = cdrom_get_track_start(cdrom, i, 0) + 150;
		}
		else
		{
			fad = 150;
		}

		logerror("[%02d]: mode %02x, FAD %d\n", i+1, databuf[tocptr], fad);

		databuf[tocptr+1] = (fad>>16)&0xff;
		databuf[tocptr+2] = (fad>>8)&0xff;
		databuf[tocptr+3] = fad&0xff;

		tocptr += 4;
	}

	// fill in the rest
	for ( ; i < 99; i++)
	{
		databuf[tocptr] = 0xff;
		databuf[tocptr+1] = 0xff;
		databuf[tocptr+2] = 0xff;
		databuf[tocptr+3] = 0xff;

		tocptr += 4;
	}

	// tracks 99-101 are special metadata
	// $$$FIXME: what to do with the address info for these?
	tocptr = 99 * 4;
	databuf[tocptr] = databuf[0];	// get ctrl/adr from first track
	databuf[tocptr+1] = 1;	// first track's track #

	databuf[tocptr+4] = databuf[ntrks*4];	// ditto for last track
	databuf[tocptr+5] = ntrks;	// last track's track #

	// always assume 1 session, give it track 0's starting address
	databuf[tocptr+8] = databuf[0];
	databuf[tocptr+9] = databuf[1];
	databuf[tocptr+10] = databuf[2];
	databuf[tocptr+11] = databuf[3];

	dbuflen = 102*4;	// there are always 102*4 entries
}

// loads in data set up by a CD-block PLAY command
static void cd_playdata(void)
{
	if (1) //(cd_stat & 0x0f00) == CD_STAT_PLAY)
	{
		while (fadstoplay)
		{
//          logerror(  "cd_playdata: Reading FAD %d", cd_curfad);

			if (cdrom)
			{
				cdrom_read_data(cdrom, cd_curfad-150, 1, &databuf[in_buffer], CD_TRACK_MODE1);
			}
			in_buffer += 2048;
			cd_curfad ++;

			// if read past end of buffer, go into pause
			if (in_buffer > (512*1024))
			{
				cd_stat &= ~CD_STAT_PLAY;
				cd_stat |= CD_STAT_PAUSE;
			}

			fadstoplay--;
		}
	}
}

// loads a single sector off the CD, anywhere from FAD 150 on up
static void cd_readblock(unsigned long fad, unsigned char *dat)
{
	if (cdrom)
	{
		cdrom_read_data(cdrom, fad-150, 1, dat, CD_TRACK_MODE1);
	}
}


