summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/machine/t10mmc.cpp
blob: bc3d6d0a95c5e0f2d8201f75001b7094f66bf630 (plain) (tree)
1
2
3
4
5
6
7
8
9

                        
                

                   

                      
                                
 




                                        

 















                                                        






                                              

                                        
                           

                                  




                                                
                                              
                                       
                                                





                            
                                
         
                                                                                  

         


                       
                              

                            
                          
                   
                            


                      









                                                                                              











                                                                             


                                     
                                                  








                                                       
                                     






                         






                          
                                                            
                                                                                                                                                                                       
         











                                                                                                                     
 

                             
                                
                                                          







                                                                         
                                                                                                              





























                                                                         


                                                      
                                                           

                      
                                
                                       






                                                                         
                                               

                                                               
                                                                                                                                          









                                                                 
 
                              
 



                                                              
 
                                         
                                       






                                                                         
                                                                                       



                                                                          
 

                                          
                                       






                                                                         
                           
 



                                                        

                              

                                             

                              
                        
                                                                                                   
                                   

                              
 
                                                                              
 



                                                   
 
                              
 





                                                      
                                       






                                                                         
                                               
                                                               
 

                               


                                                                                                                           

                                                                                     

                                            


                                             
                                                                                                 
                 
 
                                                                                                          
 
                                                
 
                                                                               















                                                                                                                   



                                            
                                       
                 
                                                                         


                              
















                                                                                                                          
 

                               

                                                                                     

                                            
                 
 
                                                
 



                                                                                                                                         
                 



                                                                                        
                 













                                                                                                                                 
                 
                                                                            
                                                                         

                                                                                                                   





                                                                                             
 
                      
         

                                               
                                       






                                                                         



                                  

                                            


                                                                                                                                 

                                                                                                                                           
                 
                    
                 

                                                                                       
                                                                                                                                                                            
                                                                  

                                                       
 





                                                                                                                         
 


                                                               
 


                                                                               
 
                                                                                       



                                                                                                     
                                                                                                                     











                                                                                                                           
                      
 
                                     
                            


                                                                        
 
                                                                                                      







                                                      
                                                                 





                                                      
                                                                                                                       











                                                                          
                                       






                                                                         

                                                  
 

                               

                                                                                     

                                            


                                             
                                                                                                 
                 
 
                                                                                                          
 
                                                
 
                                                                               





                                                                                             
                                                                            

                                                                                                                   
 



                                                      
 
                                
                                       






                                                                         

                                                  
 
                                                                                                                                          
 








                                                                 
 
                              
 



                                                              
 
                                     
                                                                                                       



                                                      
 

                                
                                       






                                                                         

                                                  

                                             


                                                                                                                                                                                  






                                                                                                                                      
                 

                                                                       
 


                                                                                                                                    





                                                                                                                           
                                       
                         
 





                                                                                                                                            
 




































































































































































                                                                                                                                                                                    

                 
                                                                                         
                                        
                                                                                                     
 



                                                                                              

                                                      


                      









                                                                               

                                      






                                                                      
                                                      
 
                      
                                 


                             








                                                                              
                                                          


                                      
                                                              
 
                                                      

                                                                 
                                          

                            
                                                    



                                
                                                                                               
                                            

                                              
                         
                                                                                                       
                                 

                                                                                                     
                                 
 
                                                                                                                                
 
                                                                                                           
 



                                                                      
 

                                                   
                                 



                                                             
                         

                      
 














































































                                                                                                                                                                                                                                                                                          
                                                                                












































                                                                                                                                                                                 
                                                                                                                                          









































































































                                                                                                                                            



                                                          
                         
                                             
                                 

                                               
 
                                                                                                                              
 
                                                                   
 
                                              
 





                                                                                                       
                                         
                                                                                      


                                            
                                                                                        
                                         








                                                                                     
                                         



                                                                                                    
 





                                                                                  
                                                                                              
                                                                
 
                                                                    
 



                                                                      
 
                                                                   
 
                                                                                                   
 
                                                
                                         

                                                                                                                  
                                         
 
                                                                    




                                                    
                                 
                                      
                         
                                
                                                                                                                 




                                          



                                                                                        


                                                           
 
                                                                                                        
                                             
                         
                                               
                                 





                                                                                                       

                                                                    
                                                         
                                                                                 







                                                                        
                                         


                                                                            
                                                 

                                                                           

                                                 
                                                                        
                                                 
                                                              
                                                 

                                                                 
                                                                                                     
                                                                     

                                                                 
                                                                                                        


                                                        
                                                                                

                                                 

                                                                               
                                         







                                                              

                                                                    



                                                         
                                                                                   


                                                         
                                                                                      


                                                
                                                                        
                                         
 

                                                                       
                                 


                                      
                                                                                                           
                                      
                         

                      
 

                                      
                                                                                                                                    
                 









                                                                                            

                                                                                                              
                                                        
                                                           
                                                        
                                                           
                                                        
                                                           
                                                        

                                                           
 










                                                                          
 
























                                                                                          


                      
                                            











                                                                                                              


                                                     






                                                                        
                                                       


                             

                                       
                                                                                    
 





                                                                                   
                                                                                                                          
 




                                                             
                                                                                                      

                                      
 
                                                 
                                                            



                                                                                                   


                                                                                                                                                                      

                                                                              







                                                      
 
// license:BSD-3-Clause
// copyright-holders:smf
#include "emu.h"
#include "t10mmc.h"

#include "multibyte.h"

static int to_msf_raw(int frame)
{
	int m = frame / (75 * 60);
	int s = (frame / 75) % 60;
	int f = frame % 75;

	return (m << 16) | (s << 8) | f;
}

static int to_msf(int frame)
{
	int adjusted_frame = frame + 150;
	if (frame <= -151)
		adjusted_frame += 450000;
	return to_msf_raw(adjusted_frame);
}

static int to_lba(int msf)
{
	int lba = cdrom_file::msf_to_lba(msf) - 150;
	if (BIT(msf, 16, 8) >= 90) // 90:00:00 and later
		lba -= 450000;
	return lba;
}

void t10mmc::set_model(std::string model_name)
{
	m_model_name = model_name;
	while(m_model_name.size() < 28)
		m_model_name += ' ';
}

void t10mmc::t10_start(device_t &device)
{
	m_device = &device;
	t10spc::t10_start(device);

	device.save_item(NAME(m_lba));
	device.save_item(NAME(m_blocks));
	device.save_item(NAME(m_last_lba));
	device.save_item(NAME(m_num_subblocks));
	device.save_item(NAME(m_cur_subblock));
	device.save_item(NAME(m_audio_sense));
	device.save_item(NAME(m_sotc));
	device.save_item(NAME(m_read_cd_flags));
}

void t10mmc::t10_reset()
{
	t10spc::t10_reset();

	if( !m_image->exists() )
	{
		m_device->logerror( "T10MMC %s: no CD found!\n", m_image->tag() );
	}

	m_lba = 0;
	m_blocks = 0;
	m_last_lba = 0;
	m_sector_bytes = 2048;
	m_num_subblocks = 1;
	m_cur_subblock = 0;
	m_audio_sense = 0;
	m_sotc = 0;
	m_read_cd_flags = 0;
}

// scsicd_exec_command

void t10mmc::abort_audio()
{
	if (m_cdda->audio_active())
	{
		m_cdda->stop_audio();
		m_audio_sense = SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_STOPPED_DUE_TO_ERROR;
	}
}

t10mmc::toc_format_t t10mmc::toc_format()
{
	int mmc_format = command[2] & 0xf;
	if (mmc_format != 0)
	{
		return (toc_format_t) mmc_format;
	}

	/// SFF8020 legacy format field (see T10/1836-D Revision 2g page 643)
	return (toc_format_t) ((command[9] >> 6) & 3);
}

int t10mmc::toc_tracks()
{
	int start_track = command[6];
	int end_track = m_image->get_last_track();

	if (start_track == 0)
	{
		return end_track + 1;
	}
	else if (start_track <= end_track)
	{
		return ( end_track - start_track ) + 2;
	}
	else if (start_track <= 0xaa)
	{
		return 1;
	}

	return 0;
}

//
// Execute a SCSI command.

void t10mmc::ExecCommand()
{
	int trk;

	// keep updating the sense data while playing audio.
	if (command[0] == T10SPC_CMD_REQUEST_SENSE && m_audio_sense != SCSI_SENSE_ASC_ASCQ_NO_SENSE && m_sense_key == SCSI_SENSE_KEY_NO_SENSE && m_sense_asc == 0 && m_sense_ascq == 0)
	{
		if (m_audio_sense == SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_IN_PROGRESS && !m_cdda->audio_active())
		{
			m_audio_sense = SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_SUCCESSFULLY_COMPLETED;
		}

		set_sense(SCSI_SENSE_KEY_NO_SENSE, (sense_asc_ascq_t) m_audio_sense);

		if (m_audio_sense != SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_IN_PROGRESS)
		{
			m_audio_sense = SCSI_SENSE_ASC_ASCQ_NO_SENSE;
		}
	}

	switch ( command[0] )
	{
	case T10SPC_CMD_INQUIRY:
		//m_device->logerror("T10MMC: INQUIRY\n");
		m_phase = SCSI_PHASE_DATAIN;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = SCSILengthFromUINT8( &command[ 4 ] );
		if (m_transfer_length > 36)
			m_transfer_length = 36;
		break;

	case T10SPC_CMD_MODE_SELECT_6:
		//m_device->logerror("T10MMC: MODE SELECT(6) length %x control %x\n", command[4], command[5]);
		m_phase = SCSI_PHASE_DATAOUT;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = SCSILengthFromUINT8( &command[ 4 ] );
		break;

	case T10SPC_CMD_MODE_SENSE_6:
		m_phase = SCSI_PHASE_DATAIN;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = SCSILengthFromUINT8( &command[ 4 ] );
		break;

	case T10SPC_CMD_START_STOP_UNIT:
		abort_audio();
		m_phase = SCSI_PHASE_STATUS;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = 0;
		break;

	case T10SPC_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
		m_phase = SCSI_PHASE_STATUS;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = 0;
		break;

	case T10SBC_CMD_READ_CAPACITY:
		m_phase = SCSI_PHASE_DATAIN;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = 8;
		break;

	case T10MMC_CMD_READ_DISC_STRUCTURE:
		m_phase = SCSI_PHASE_DATAIN;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = get_u16be(&command[8]);
		break;

	case T10SBC_CMD_READ_10:
		if (!m_image->exists())
		{
			m_phase = SCSI_PHASE_STATUS;
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			m_transfer_length = 0;
			break;
		}

		m_lba = get_u32be(&command[2]);
		m_blocks = SCSILengthFromUINT16( &command[7] );

		//m_device->logerror("T10MMC: READ(10) at LBA %x for %d blocks (%d bytes)\n", m_lba, m_blocks, m_blocks * m_sector_bytes);

		if (m_num_subblocks > 1)
		{
			m_cur_subblock = m_lba % m_num_subblocks;
			m_lba /= m_num_subblocks;
		}
		else
		{
			m_cur_subblock = 0;
		}

		abort_audio();

		m_phase = SCSI_PHASE_DATAIN;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = m_blocks * m_sector_bytes;
		break;

	case T10MMC_CMD_READ_SUB_CHANNEL:
		if (!m_image->exists())
		{
			m_phase = SCSI_PHASE_STATUS;
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			m_transfer_length = 0;
			break;
		}

		//m_device->logerror("T10MMC: READ SUB-CHANNEL type %d\n", command[3]);
		m_phase = SCSI_PHASE_DATAIN;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = SCSILengthFromUINT16( &command[ 7 ] );
		break;

	case T10MMC_CMD_READ_TOC_PMA_ATIP:
	{
		if (!m_image->exists())
		{
			m_phase = SCSI_PHASE_STATUS;
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			m_transfer_length = 0;
			break;
		}

		int length;

		switch (toc_format())
		{
		case TOC_FORMAT_TRACKS:
			length = 4 + (8 * toc_tracks());
			break;

		case TOC_FORMAT_SESSIONS:
			length = 4 + (8 * 1);
			break;

		default:
			m_device->logerror("T10MMC: Unhandled READ TOC format %d\n", toc_format());
			length = 0;
			break;
		}

		int allocation_length = SCSILengthFromUINT16( &command[ 7 ] );

		if( length > allocation_length )
		{
			length = allocation_length;
		}

		abort_audio();

		m_phase = SCSI_PHASE_DATAIN;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = length;
		break;
	}
	case T10MMC_CMD_PLAY_AUDIO_10:
		if (!m_image->exists())
		{
			m_phase = SCSI_PHASE_STATUS;
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			m_transfer_length = 0;
			break;
		}

		m_lba = get_u32be(&command[2]);
		m_blocks = SCSILengthFromUINT16( &command[7] );

		if (m_lba == 0)
		{
			// A request for LBA 0 will return something different depending on the type of media being played.
			// For data and mixed media, LBA 0 is assigned to MSF 00:02:00 (= LBA 150).
			// For audio media, LBA 0 is assigned to the actual starting address of track 1.
			if (m_image->get_track_type(0) == cdrom_file::CD_TRACK_AUDIO)
				m_lba = m_image->get_track_start(0);
			else
				m_lba = 150;
		}
		else if (m_lba == 0xffffffff)
		{
			m_device->logerror("T10MMC: play audio from current not implemented!\n");
		}

		//m_device->logerror("T10MMC: PLAY AUDIO(10) at LBA %x for %x blocks\n", m_lba, m_blocks);

		trk = m_image->get_track(m_lba);

		if (m_image->get_track_type(trk) == cdrom_file::CD_TRACK_AUDIO)
		{
			m_cdda->start_audio(m_lba, m_blocks);
			m_audio_sense = SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_IN_PROGRESS;
		}
		else
		{
			m_device->logerror("T10MMC: track is NOT audio!\n");
			set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_ILLEGAL_MODE_FOR_THIS_TRACK);
		}

		m_phase = SCSI_PHASE_STATUS;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = 0;
		break;

	case T10MMC_CMD_PLAY_AUDIO_MSF:
	{
		m_phase = SCSI_PHASE_STATUS;
		m_transfer_length = 0;

		if (!m_image->exists())
		{
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			break;
		}

		const uint32_t msf_start = get_u24be(&command[3]);
		const uint32_t msf_end = get_u24be(&command[6]);

		int32_t lba_start = to_lba(msf_start);
		int32_t lba_end = to_lba(msf_end);

		// LBA valid range is technically -45150 to 404849 but negatives are not handled anywhere
		if (lba_start < 0 || lba_end < 0)
		{
			m_device->logerror("T10MMC: tried playing audio from lba %d to %d\n", lba_start, lba_end);
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE);
			break;
		}

		m_lba = lba_start;
		m_blocks = lba_end - lba_start;

		if (m_lba == 0)
		{
			if (m_image->get_track_type(0) == cdrom_file::CD_TRACK_AUDIO)
				m_lba = m_image->get_track_start(0);
			else
				m_lba = 150;
		}

		trk = m_image->get_track(m_lba);

		//m_device->logerror("T10MMC: PLAY AUDIO MSF at LBA %x (track %d) for %x blocks (MSF %02d:%02d:%02d - %02d:%02d:%02d)\n",
			//m_lba, trk + 1, m_blocks, command[3], command[4], command[5], command[6], command[7], command[8]);

		if (msf_start == msf_end)
		{
			// audio start operation does not happen but also isn't an error
			m_device->logerror("T10MMC: track is not played\n");
			m_status_code = SCSI_STATUS_CODE_GOOD;
			m_audio_sense = SCSI_SENSE_ASC_ASCQ_NO_SENSE;
		}
		else if (msf_start == 0xffffff)
		{
			// when start MSF is set to all FFs, the starting address becomes the current optical head location
			m_device->logerror("T10MMC: play audio from current not implemented!\n");
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_STOPPED_DUE_TO_ERROR);
		}
		else if (msf_start > msf_end)
		{
			m_device->logerror("T10MMC: track starts after requested end time!\n");
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_ILLEGAL_FIELD_IN_CDB);
		}
		else if (m_image->get_track_type(trk) != cdrom_file::CD_TRACK_AUDIO)
		{
			m_device->logerror("T10MMC: track is NOT audio!\n");
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_ILLEGAL_MODE_FOR_THIS_TRACK);
		}
		else
		{
			m_cdda->start_audio(m_lba, m_blocks);
			m_status_code = SCSI_STATUS_CODE_GOOD;
			m_audio_sense = SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_IN_PROGRESS;
		}

		break;
	}

	case T10MMC_CMD_PLAY_AUDIO_TRACK_INDEX:
		if (!m_image->exists())
		{
			m_phase = SCSI_PHASE_STATUS;
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			m_transfer_length = 0;
			break;
		}

		// [4] track start
		// [5] index start
		// [7] track end
		// [8] index end
		if (command[4] > command[7])
		{
			// TODO: check error
			set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_STOPPED_DUE_TO_ERROR);
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;

			m_device->logerror("Error: start TNO (%d,%d) > end TNO (%d,%d)\n", command[4], command[5], command[7], command[8]);
		}
		else
		{
			// be careful: tracks here are zero-based, but the SCSI command
			// uses the real CD track number which is 1-based!
			//m_device->logerror("T10MMC: PLAY AUDIO T/I: strk %d idx %d etrk %d idx %d frames %d\n", command[4], command[5], command[7], command[8], m_blocks);
			int end_track = m_image->get_last_track();
			if (end_track > command[7])
				end_track = command[7];

			// konamigv lacrazyc just sends same track start/end
			if (command[4] != command[7] && command[5] != command[8])
			{
				// HACK: assume index 0 & 1 means beginning of track and anything else means end of track
				if (command[8] <= 1)
					end_track--;

				if (m_sotc)
					end_track = command[4];
			}

			m_lba = m_image->get_track_start(command[4] - 1);
			m_blocks = m_image->get_track_start(end_track) - m_lba;
			trk = m_image->get_track(m_lba);

			if (m_image->get_track_type(trk) == cdrom_file::CD_TRACK_AUDIO)
			{
				m_cdda->start_audio(m_lba, m_blocks);
				m_audio_sense = SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_IN_PROGRESS;
				m_status_code = SCSI_STATUS_CODE_GOOD;
				m_device->logerror("Starting audio TNO %d LBA %d blocks %d\n", trk, m_lba, m_blocks);
			}
			else
			{
				m_device->logerror("T10MMC: track is NOT audio!\n");
				// TODO: check error
				set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_ILLEGAL_MODE_FOR_THIS_TRACK);
				m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			}

			m_phase = SCSI_PHASE_STATUS;
			m_transfer_length = 0;
		}
		break;

	case T10MMC_CMD_PAUSE_RESUME:
		if (m_image)
		{
			m_cdda->pause_audio((command[8] & 0x01) ^ 0x01);
		}

		//m_device->logerror("T10MMC: PAUSE/RESUME: %s\n", command[8]&1 ? "RESUME" : "PAUSE");
		m_phase = SCSI_PHASE_STATUS;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = 0;
		break;

	case T10MMC_CMD_STOP_PLAY_SCAN:
		abort_audio();

		//m_device->logerror("T10MMC: STOP_PLAY_SCAN\n");
		m_phase = SCSI_PHASE_STATUS;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = 0;
		break;

	case T10SPC_CMD_MODE_SELECT_10:
		//m_device->logerror("T10MMC: MODE SELECT length %x control %x\n", get_u16be(&command[7]), command[1]);
		m_phase = SCSI_PHASE_DATAOUT;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = SCSILengthFromUINT16( &command[ 7 ] );
		break;

	case T10SPC_CMD_MODE_SENSE_10:
		m_phase = SCSI_PHASE_DATAIN;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = SCSILengthFromUINT16( &command[ 7 ] );
		break;

	case T10MMC_CMD_PLAY_AUDIO_12:
		if (!m_image->exists())
		{
			m_phase = SCSI_PHASE_STATUS;
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			m_transfer_length = 0;
			break;
		}

		m_lba = get_u32be(&command[2]);
		m_blocks = get_u32be(&command[6]);

		if (m_lba == 0)
		{
			if (m_image->get_track_type(0) == cdrom_file::CD_TRACK_AUDIO)
				m_lba = m_image->get_track_start(0);
			else
				m_lba = 150;
		}
		else if (m_lba == 0xffffffff)
		{
			m_device->logerror("T10MMC: play audio from current not implemented!\n");
		}

		//m_device->logerror("T10MMC: PLAY AUDIO(12) at LBA %x for %x blocks\n", m_lba, m_blocks);

		trk = m_image->get_track(m_lba);

		if (m_image->get_track_type(trk) == cdrom_file::CD_TRACK_AUDIO)
		{
			m_cdda->start_audio(m_lba, m_blocks);
			m_audio_sense = SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_IN_PROGRESS;
		}
		else
		{
			m_device->logerror("T10MMC: track is NOT audio!\n");
			set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_ILLEGAL_MODE_FOR_THIS_TRACK);
		}

		m_phase = SCSI_PHASE_STATUS;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = 0;
		break;

	case T10SBC_CMD_READ_12:
		if (!m_image->exists())
		{
			m_phase = SCSI_PHASE_STATUS;
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			m_transfer_length = 0;
			break;
		}

		m_lba = get_u32be(&command[2]);
		m_blocks = get_u24be(&command[7]);

		//m_device->logerror("T10MMC: READ(12) at LBA %x for %x blocks (%x bytes)\n", m_lba, m_blocks, m_blocks * m_sector_bytes);

		if (m_num_subblocks > 1)
		{
			m_cur_subblock = m_lba % m_num_subblocks;
			m_lba /= m_num_subblocks;
		}
		else
		{
			m_cur_subblock = 0;
		}

		abort_audio();

		m_phase = SCSI_PHASE_DATAIN;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = m_blocks * m_sector_bytes;
		break;

	case T10MMC_CMD_SET_CD_SPEED:
		m_device->logerror("T10MMC: SET CD SPEED to %d kbytes/sec.\n", get_u16be(&command[2]));
		m_phase = SCSI_PHASE_STATUS;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = 0;
		break;

	case T10MMC_CMD_READ_CD:
	{
		if (!m_image->exists())
		{
			m_phase = SCSI_PHASE_STATUS;
			m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
			m_transfer_length = 0;
			break;
		}

		m_lba = get_u32be(&command[2]);
		m_blocks = get_u24be(&command[6]);
		m_read_cd_flags = command[9];
		m_transfer_length = 0;

		// m_device->logerror("T10MMC: READ CD start_lba[%08x] block_len[%06x] %02x %02x %02x %02x\n", m_lba, m_blocks, command[1], command[9], command[10], command[11]);

		if (command[10] != 0)
			m_device->logerror("T10MMC: READ CD requested sub-channel data which is not implemented %02x\n", command[10]);

		const int expected_sector_type = BIT(command[1], 2, 3);
		int last_track_type = -1;
		uint8_t last_read_cd_flags = 0;
		for (int lba = m_lba; lba < m_lba + m_blocks; lba++)
		{
			auto trk = m_image->get_track(lba);
			auto track_type = m_image->get_track_type(trk);

			// If there's a transition between CD data and CD audio anywhere in the requested range then return an error
			if ((last_track_type == cdrom_file::CD_TRACK_AUDIO && track_type != cdrom_file::CD_TRACK_AUDIO)
			|| (last_track_type != cdrom_file::CD_TRACK_AUDIO && track_type == cdrom_file::CD_TRACK_AUDIO))
			{
				set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_ILLEGAL_MODE_FOR_THIS_TRACK);

				m_phase = SCSI_PHASE_STATUS;
				m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
				m_transfer_length = 0;
				return;
			}

			// Must read the subheader to figure out what sector type it is exactly when dealing with these specific track types
			int mode2_form = 0;
			if (track_type == cdrom_file::CD_TRACK_MODE2_RAW || track_type == cdrom_file::CD_TRACK_MODE2_FORM_MIX)
			{
				uint8_t tmp_buffer[2352];
				const int submode_offset = track_type == cdrom_file::CD_TRACK_MODE2_FORM_MIX ? 2 : 0x12;

				if (!m_image->read_data(lba, tmp_buffer, cdrom_file::CD_TRACK_RAW_DONTCARE))
				{
					m_device->logerror("T10MMC: CD read error! (%08x)\n", lba);
					return;
				}

				mode2_form = BIT(tmp_buffer[submode_offset], 5) + 1;
			}

			// If the expected sector type field is set then all tracks within the specified range must be the same
			if ((expected_sector_type == T10MMC_READ_CD_SECTOR_TYPE_CDDA && track_type != cdrom_file::CD_TRACK_AUDIO)
			|| (expected_sector_type == T10MMC_READ_CD_SECTOR_TYPE_MODE1 && track_type != cdrom_file::CD_TRACK_MODE1 && track_type != cdrom_file::CD_TRACK_MODE1_RAW)
			|| (expected_sector_type == T10MMC_READ_CD_SECTOR_TYPE_MODE2 && track_type != cdrom_file::CD_TRACK_MODE2)
			|| (expected_sector_type == T10MMC_READ_CD_SECTOR_TYPE_MODE2_FORM1 && track_type != cdrom_file::CD_TRACK_MODE2_FORM1 && mode2_form != 1)
			|| (expected_sector_type == T10MMC_READ_CD_SECTOR_TYPE_MODE2_FORM2 && track_type != cdrom_file::CD_TRACK_MODE2_FORM2 && mode2_form != 2))
			{
				set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_ILLEGAL_MODE_FOR_THIS_TRACK);

				m_phase = SCSI_PHASE_STATUS;
				m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
				m_transfer_length = 0;
				return;
			}

			// No fields selected is a valid request but none of the rest of the verification code is required in that case
			if (m_read_cd_flags == 0)
			{
				last_track_type = track_type;
				continue;
			}

			// Check for illegal combinations
			// t10 mmc spec gives a table which shows illegal combinations and combinations that get demoted
			auto read_cd_flags = m_read_cd_flags & 0xf8;

			// CDDA tracks can only ever be user data or no data (0), requesting other fields is not illegal but will be demoted to just user data
			if (track_type == cdrom_file::CD_TRACK_AUDIO)
				read_cd_flags = read_cd_flags ? T10MMC_READ_CD_FIELD_USER_DATA : 0;

			// All of these combinations will be illegal for all tracks besides CDDA tracks
			bool is_illegal_combo = (read_cd_flags == (T10MMC_READ_CD_FIELD_HEADER | T10MMC_READ_CD_FIELD_ECC))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SUBHEADER | T10MMC_READ_CD_FIELD_ECC))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_HEADER | T10MMC_READ_CD_FIELD_SUBHEADER | T10MMC_READ_CD_FIELD_ECC))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_ECC))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_USER_DATA))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_USER_DATA | T10MMC_READ_CD_FIELD_ECC))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_HEADER | T10MMC_READ_CD_FIELD_ECC))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_SUBHEADER))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_SUBHEADER | T10MMC_READ_CD_FIELD_ECC))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_SUBHEADER | T10MMC_READ_CD_FIELD_USER_DATA))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_SUBHEADER | T10MMC_READ_CD_FIELD_USER_DATA | T10MMC_READ_CD_FIELD_ECC))
				|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_HEADER | T10MMC_READ_CD_FIELD_SUBHEADER | T10MMC_READ_CD_FIELD_ECC));

			// Mode 2 form 1/2 sectors have additional restrictions that CDDA, mode 1, and mode 2 formless tracks don't
			if (!is_illegal_combo && (track_type == cdrom_file::CD_TRACK_MODE2_FORM1 || track_type == cdrom_file::CD_TRACK_MODE2_FORM2 || mode2_form > 0))
			{
				is_illegal_combo = (read_cd_flags == (T10MMC_READ_CD_FIELD_HEADER | T10MMC_READ_CD_FIELD_USER_DATA))
					|| (read_cd_flags == (T10MMC_READ_CD_FIELD_HEADER | T10MMC_READ_CD_FIELD_USER_DATA | T10MMC_READ_CD_FIELD_ECC))
					|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_HEADER | T10MMC_READ_CD_FIELD_USER_DATA))
					|| (read_cd_flags == (T10MMC_READ_CD_FIELD_SYNC | T10MMC_READ_CD_FIELD_HEADER | T10MMC_READ_CD_FIELD_USER_DATA | T10MMC_READ_CD_FIELD_ECC));
			}

			// Mask out flags that can't be used for specific track types
			if (track_type == cdrom_file::CD_TRACK_MODE1 || track_type == cdrom_file::CD_TRACK_MODE1_RAW)
			{
				// Sub header only is valid but will return 0 bytes, otherwise subheader is always demoted
				if (read_cd_flags != T10MMC_READ_CD_FIELD_SUBHEADER)
					read_cd_flags &= ~T10MMC_READ_CD_FIELD_SUBHEADER;
			}
			else if (track_type == cdrom_file::CD_TRACK_MODE2)
			{
				// Mode 2 formless
				// No EDC/ECC data
				read_cd_flags &= ~T10MMC_READ_CD_FIELD_ECC;

				// Sub header only is valid but will return 0 bytes, otherwise subheader is always demoted
				if (read_cd_flags != T10MMC_READ_CD_FIELD_SUBHEADER)
					read_cd_flags &= ~T10MMC_READ_CD_FIELD_SUBHEADER;
			}
			else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM2 || mode2_form == 2)
			{
				// No EDC/ECC data
				read_cd_flags &= ~T10MMC_READ_CD_FIELD_ECC;
			}

			// Requested fields must be valid for all tracks within the selected range
			if (is_illegal_combo || (last_track_type != -1 && read_cd_flags != last_read_cd_flags))
			{
				m_device->logerror("T10MMC: READ CD called with invalid data request for given track type %d %02x\n", track_type, command[9]);
				set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_ILLEGAL_FIELD_IN_CDB);
				m_phase = SCSI_PHASE_STATUS;
				m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION;
				m_transfer_length = 0;
				return;
			}

			// The actual transfer size must be calculated for every possible valid requested data
			const int c2_error_codes = BIT(m_read_cd_flags, 1, 2);
			const bool requested_c2 = c2_error_codes == T10MMC_READ_CD_C2_ONLY;
			const bool requested_c2_error_block = c2_error_codes == T10MMC_READ_CD_C2_BLOCK;

			const bool requested_edc_ecc = (read_cd_flags & T10MMC_READ_CD_FIELD_ECC) != 0;
			const bool requested_user_data = (read_cd_flags & T10MMC_READ_CD_FIELD_USER_DATA) != 0;
			const bool requested_header = (read_cd_flags & T10MMC_READ_CD_FIELD_HEADER) != 0;
			const bool requested_subheader = (read_cd_flags & T10MMC_READ_CD_FIELD_SUBHEADER) != 0;
			const bool requested_sync = (read_cd_flags & T10MMC_READ_CD_FIELD_SYNC) != 0;

			if (requested_c2 || requested_c2_error_block)
				m_transfer_length += 294;

			if (requested_c2_error_block)
				m_transfer_length += 2;

			if (track_type == cdrom_file::CD_TRACK_AUDIO)
			{
				if (requested_user_data)
					m_transfer_length += 2352;
			}
			else if (track_type == cdrom_file::CD_TRACK_MODE1 || track_type == cdrom_file::CD_TRACK_MODE1_RAW)
			{
				if (requested_sync)
					m_transfer_length += 12;
				if (requested_header)
					m_transfer_length += 4;
				if (requested_user_data)
					m_transfer_length += 2048;
				if (requested_edc_ecc)
					m_transfer_length += 288;
			}
			else if (track_type == cdrom_file::CD_TRACK_MODE2)
			{
				if (requested_sync)
					m_transfer_length += 12;
				if (requested_header)
					m_transfer_length += 4;
				if (requested_user_data)
					m_transfer_length += 2336;
			}
			else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM1 || mode2_form == 1)
			{
				if (requested_sync)
					m_transfer_length += 12;
				if (requested_header)
					m_transfer_length += 4;
				if (requested_subheader)
					m_transfer_length += 8;
				if (requested_user_data)
					m_transfer_length += 2048;
				if (requested_edc_ecc)
					m_transfer_length += 280;
			}
			else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM2 || mode2_form == 2)
			{
				if (requested_sync)
					m_transfer_length += 12;
				if (requested_header)
					m_transfer_length += 4;
				if (requested_subheader)
					m_transfer_length += 8;
				if (requested_user_data)
					m_transfer_length += 2328;
			}

			last_track_type = track_type;
			last_read_cd_flags = read_cd_flags;
		}

		// Only worry about SGI block extension stuff if it ever becomes an issue
		if (m_num_subblocks > 1)
			m_device->logerror("T10MMC: READ CD does not handle sub blocks currently\n");

		// All fields were matched between all tracks, so store the simplified version
		m_read_cd_flags = last_read_cd_flags | (m_read_cd_flags & ~0xf8);

		m_cur_subblock = 0;
		m_phase = SCSI_PHASE_DATAIN;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		break;
	}

	case T10SBC_CMD_SEEK_10:
		m_lba = get_u32be(&command[2]);

		m_device->logerror("T10SBC: SEEK EXTENDED to LBA %x\n", m_lba);

		m_phase = SCSI_PHASE_STATUS;
		m_status_code = SCSI_STATUS_CODE_GOOD;
		m_transfer_length = 0;
		break;

	default:
		t10spc::ExecCommand();
	}
}

// scsicd_read_data
//
// Read data from the device resulting from the execution of a command

void t10mmc::ReadData( uint8_t *data, int dataLength )
{
	uint32_t temp;
	uint8_t tmp_buffer[2352];

	switch ( command[0] )
	{
	case T10SPC_CMD_INQUIRY:
		data[0] = 0x05; // device is present, device is CD/DVD (MMC-3)
		data[1] = 0x80; // media is removable
		data[2] = 0x05; // device complies with SPC-3 standard
		data[3] = 0x02; // response data format = SPC-3 standard
		data[4] = 0x1f;
		data[5] = 0;
		data[6] = 0;
		data[7] = 0;
		memcpy(&data[8], m_model_name.data(), 28);
		break;

	case T10SBC_CMD_READ_CAPACITY:
		m_device->logerror("T10MMC: READ CAPACITY\n");

		temp = m_image->get_track_start(0xaa);
		temp--; // return the last used block on the disc

		put_u32be(&data[0], temp);
		data[4] = 0;
		data[5] = 0;
		put_u16be(&data[6], m_sector_bytes);
		break;

	case T10SBC_CMD_READ_10:
	case T10SBC_CMD_READ_12:
		//m_device->logerror("T10MMC: read %x dataLength lba=%x\n", dataLength, m_lba);
		if ((m_image) && (m_blocks))
		{
			while (dataLength > 0)
			{
				if (!m_image->read_data(m_lba, tmp_buffer, cdrom_file::CD_TRACK_MODE1))
				{
					m_device->logerror("T10MMC: CD read error! (%08x)\n", m_lba);
					return;
				}

				//m_device->logerror("True LBA: %d, buffer half: %d\n", m_lba, m_cur_subblock * m_sector_bytes);

				memcpy(data, &tmp_buffer[m_cur_subblock * m_sector_bytes], m_sector_bytes);

				m_cur_subblock++;
				if (m_cur_subblock >= m_num_subblocks)
				{
					m_cur_subblock = 0;

					m_lba++;
					m_blocks--;
				}

				m_last_lba = m_lba;
				dataLength -= m_sector_bytes;
				data += m_sector_bytes;
			}
		}
		break;

	case T10MMC_CMD_READ_CD:
		//m_device->logerror("T10MMC: read CD %x dataLength lba=%x\n", dataLength, m_lba);
		if ((m_image) && (m_blocks))
		{
			const int c2_error_codes = BIT(m_read_cd_flags, 1, 2);
			const bool requested_c2 = c2_error_codes == T10MMC_READ_CD_C2_ONLY;
			const bool requested_c2_error_block = c2_error_codes == T10MMC_READ_CD_C2_BLOCK;

			const bool requested_edc_ecc = (m_read_cd_flags & T10MMC_READ_CD_FIELD_ECC) != 0;
			const bool requested_user_data = (m_read_cd_flags & T10MMC_READ_CD_FIELD_USER_DATA) != 0;
			const bool requested_header = (m_read_cd_flags & T10MMC_READ_CD_FIELD_HEADER) != 0;
			const bool requested_subheader = (m_read_cd_flags & T10MMC_READ_CD_FIELD_SUBHEADER) != 0;
			const bool requested_sync = (m_read_cd_flags & T10MMC_READ_CD_FIELD_SYNC) != 0;

			// m_device->logerror("T10MMC: read CD flags c2[%d] c2block[%d] edc/ecc[%d] user[%d] header[%d] subheader[%d] sync[%d]\n", requested_c2, requested_c2_error_block, requested_edc_ecc, requested_user_data, requested_header, requested_subheader, requested_sync);

			if (m_read_cd_flags == 0)
			{
				// No data is supposed to be returned
				if (dataLength > 0)
					memset(data, 0, dataLength);

				data += dataLength;
				dataLength = 0;
			}

			while (dataLength > 0)
			{
				int data_idx = 0;
				auto trk = m_image->get_track(m_lba);
				auto track_type = m_image->get_track_type(trk);

				// Some CHDs don't have the required data so just log the error and return zeros as required
				// CD_TRACK_MODE1: Only has user data
				// CD_TRACK_MODE1_RAW: Has all fields required
				// CD_TRACK_MODE2: Only has user data
				// CD_TRACK_MODE2_FORM1: ?
				// CD_TRACK_MODE2_FORM2: ?
				// CD_TRACK_MODE2_FORM_MIX: Subheader data + user data + EDC/ECC data
				// CD_TRACK_MODE2_RAW: Has all fields required
				// CD_TRACK_AUDIO: Only returns user data

				auto read_track_type = track_type;
				if (track_type == cdrom_file::CD_TRACK_MODE1)
					read_track_type = cdrom_file::CD_TRACK_MODE1_RAW; // mode1 has code for partial promotion to mode1_raw so make use of it

				if (!m_image->read_data(m_lba, tmp_buffer, read_track_type))
				{
					m_device->logerror("T10MMC: CD read error! (%08x)\n", m_lba);
					return;
				}

				int mode2_form = 0;
				if (track_type == cdrom_file::CD_TRACK_MODE2_RAW)
					mode2_form = BIT(tmp_buffer[0x12], 5) + 1;
				else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM_MIX)
					mode2_form = BIT(tmp_buffer[2], 5) + 1;

				if (requested_sync)
				{
					if (track_type == cdrom_file::CD_TRACK_MODE2 || track_type == cdrom_file::CD_TRACK_MODE2_FORM1 || track_type == cdrom_file::CD_TRACK_MODE2_FORM2 || track_type == cdrom_file::CD_TRACK_MODE2_FORM_MIX)
					{
						m_device->logerror("T10MMC: sync data is not available for track type %d, inserting fake sync data\n", track_type);
						constexpr uint8_t sync_field[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 };
						memcpy(data + data_idx, sync_field, std::size(sync_field));
					}
					else
						memcpy(data + data_idx, tmp_buffer, 12);

					data_idx += 12;
				}

				if (requested_header)
				{
					if (track_type == cdrom_file::CD_TRACK_MODE2 || track_type == cdrom_file::CD_TRACK_MODE2_FORM1 || track_type == cdrom_file::CD_TRACK_MODE2_FORM2 || track_type == cdrom_file::CD_TRACK_MODE2_FORM_MIX)
					{
						m_device->logerror("T10MMC: header data is not available for track type %d, inserting fake header data\n", track_type);

						uint32_t msf = to_msf(m_lba);
						put_u24be(&data[data_idx], msf);
						data[data_idx+3] = 2; // mode 2
					}
					else
						memcpy(data + data_idx, tmp_buffer + 12, 4);

					data_idx += 4;
				}

				if (requested_subheader)
				{
					if (track_type == cdrom_file::CD_TRACK_MODE2_RAW)
					{
						memcpy(data + data_idx, tmp_buffer + 16, 8);
					}
					else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM_MIX)
					{
						// Only been able to verify 1 CHD for form mix, appears to have the 8 subheader bytes at the top of the sector
						memcpy(data + data_idx, tmp_buffer, 8);
					}
					else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM1 || track_type == cdrom_file::CD_TRACK_MODE2_FORM2)
					{
						// It's not possible to generate a fake subheader for mode 2 tracks because the submode byte contains detailed info
						// about what actual data is inside the block
						m_device->logerror("T10MMC: subheader data is not available for track type %d\n", track_type);
						memset(data + data_idx, 0, 8);

						if (track_type == cdrom_file::CD_TRACK_MODE2_FORM2)
							data[data_idx+2] = data[data_idx+6] = 1 << 5; // The form 2 flag can at least be set accurately
					}

					// Mode 1 and mode 2 formless return 0 bytes
					if (track_type != cdrom_file::CD_TRACK_MODE1 && track_type != cdrom_file::CD_TRACK_MODE1_RAW && track_type != cdrom_file::CD_TRACK_MODE2)
						data_idx += 8;
				}

				if (requested_user_data)
				{
					int buffer_offset = 0;
					int data_len = 0;

					if (track_type == cdrom_file::CD_TRACK_AUDIO)
					{
						buffer_offset = 0;
						data_len = 2352;
					}
					else if (track_type == cdrom_file::CD_TRACK_MODE1 || track_type == cdrom_file::CD_TRACK_MODE1_RAW)
					{
						buffer_offset = 16;
						data_len = 2048;
					}
					else if ((track_type == cdrom_file::CD_TRACK_MODE2_RAW && mode2_form == 1))
					{
						buffer_offset = 24;
						data_len = 2048;
					}
					else if ((track_type == cdrom_file::CD_TRACK_MODE2_RAW && mode2_form == 2))
					{
						buffer_offset = 24;
						data_len = 2328;
					}
					else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM_MIX && mode2_form == 1)
					{
						buffer_offset = 8;
						data_len = 2048;
					}
					else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM_MIX && mode2_form == 2)
					{
						buffer_offset = 8;
						data_len = 2328;
					}
					else if (track_type == cdrom_file::CD_TRACK_MODE2)
					{
						// Untested
						buffer_offset = 0;
						data_len = 2336;
					}
					else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM1)
					{
						// Untested
						m_device->logerror("T10MMC: reading user data from untested mode 2 form 1 track\n");
						buffer_offset = 0;
						data_len = 2048;
					}
					else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM2)
					{
						// Untested
						m_device->logerror("T10MMC: reading user data from untested mode 2 form 2 track\n");
						buffer_offset = 0;
						data_len = 2324;
					}

					memcpy(data + data_idx, tmp_buffer + buffer_offset, data_len);
					data_idx += data_len;

					if (track_type == cdrom_file::CD_TRACK_MODE2_FORM2)
					{
						// Untested, but the sector size of 2324 as noted in cdrom.h
						// implies the lack of the last 4 bytes for the (optional) CRC
						memset(data + data_idx, 0, 4);
						data_idx += 4;
					}
				}

				if (requested_edc_ecc)
				{
					if (track_type == cdrom_file::CD_TRACK_MODE1_RAW)
					{
						// Includes the 8 bytes of padding
						memcpy(data + data_idx, tmp_buffer + 16 + 2048, 288);
					}
					else if (track_type == cdrom_file::CD_TRACK_MODE2_RAW && mode2_form == 1)
					{
						memcpy(data + data_idx, tmp_buffer + 24 + 2048, 280);
					}
					else if (track_type == cdrom_file::CD_TRACK_MODE2_FORM_MIX && mode2_form == 1)
					{
						memcpy(data + data_idx, tmp_buffer + 8 + 2048, 280);
					}
					else
					{
						m_device->logerror("T10MMC: EDC/ECC data is not available for track type %d\n", track_type);
						memset(data + data_idx, 0, 280);
					}

					data_idx += 280;
				}

				if (requested_c2 || requested_c2_error_block)
				{
					m_device->logerror("T10MMC: C2 field is not supported, returning zero data\n");
					memset(data + data_idx, 0, 294);
					data_idx += 294;
				}

				if (requested_c2_error_block)
				{
					m_device->logerror("T10MMC: error block field is not supported, returning zero data\n");
					memset(data + data_idx, 0, 2);
					data_idx += 2;
				}

				m_lba++;
				m_blocks--;

				m_last_lba = m_lba;
				dataLength -= data_idx;
				data += data_idx;
				data_idx = 0;
			}
		}
		break;

	case T10MMC_CMD_READ_SUB_CHANNEL:
		switch (command[3])
		{
			case 1: // return current position
			{
				if (!m_image)
				{
					return;
				}

				m_device->logerror("T10MMC: READ SUB-CHANNEL Time = %x, SUBQ = %x\n", command[1], command[2]);

				bool msf = (command[1] & 0x2) != 0;

				data[0]= 0x00;

				int audio_active = m_cdda->audio_active();
				if (audio_active)
				{
					// if audio is playing, get the latest LBA from the CDROM layer
					m_last_lba = m_cdda->get_audio_lba();
					if (m_cdda->audio_paused())
					{
						data[1] = 0x12;     // audio is paused
					}
					else
					{
						data[1] = 0x11;     // audio in progress
					}
				}
				else
				{
					m_last_lba = 0;
					if (m_cdda->audio_ended())
					{
						data[1] = 0x13; // ended successfully
					}
					else
					{
//                          data[1] = 0x14;    // stopped due to error
						data[1] = 0x15; // No current audio status to return
					}
				}

				if (command[2] & 0x40)
				{
					data[2] = 0;
					data[3] = 12;       // data length
					data[4] = 0x01; // sub-channel format code
					data[5] = 0x10 | (audio_active ? 0 : 4);
					data[6] = m_image->get_track(m_last_lba) + 1; // track
					data[7] = 0;    // index

					uint32_t frame = m_last_lba;

					if (msf)
					{
						frame = to_msf(frame);
					}

					put_u32be(&data[8], frame);

					frame = m_last_lba - m_image->get_track_start(data[6] - 1);

					if (msf)
					{
						// this is relative so don't adjust the LBA when converting to MSF
						frame = to_msf_raw(frame);
					}

					put_u32be(&data[12], frame);
				}
				else
				{
					data[2] = 0;
					data[3] = 0;
				}
				break;
			}
			default:
				m_device->logerror("T10MMC: Unknown subchannel type %d requested\n", command[3]);
		}
		break;

	case T10MMC_CMD_READ_TOC_PMA_ATIP:
		/*
		    Track numbers are problematic here: 0 = lead-in, 0xaa = lead-out.
		    That makes sense in terms of how real-world CDs are referred to, but
		    our internal routines for tracks use "0" as track 1.  That probably
		    should be fixed...
		*/
		{
			bool msf = (command[1] & 0x2) != 0;

			m_device->logerror("T10MMC: READ TOC, format = %d time=%d\n", toc_format(),msf);
			switch (toc_format())
			{
			case TOC_FORMAT_TRACKS:
				{
					int tracks = toc_tracks();
					int len = 2 + (tracks * 8);

					// the returned TOC DATA LENGTH must be the full amount,
					// regardless of how much we're able to pass back due to in_len
					int dptr = 0;
					put_u16be(&data[dptr], len);
					dptr += 2;
					data[dptr++] = 1;
					data[dptr++] = m_image->get_last_track();

					int first_track = command[6];
					if (first_track == 0)
					{
						first_track = 1;
					}

					for (int i = 0; i < tracks; i++)
					{
						int track = first_track + i;
						int cdrom_track = track - 1;
						if( i == tracks - 1 )
						{
							track = 0xaa;
							cdrom_track = 0xaa;
						}

						if( dptr >= dataLength )
						{
							break;
						}

						data[dptr++] = 0;
						data[dptr++] = m_image->get_adr_control(cdrom_track);
						data[dptr++] = track;
						data[dptr++] = 0;

						uint32_t tstart = m_image->get_track_start(cdrom_track);

						if (msf)
						{
							tstart = to_msf(tstart);
						}

						put_u32be(&data[dptr], tstart);
						dptr += 4;
					}
				}
				break;

			case TOC_FORMAT_SESSIONS:
				{
					int len = 2 + (8 * 1);

					int dptr = 0;
					put_u16be(&data[dptr], len);
					dptr += 2;
					data[dptr++] = 1;
					data[dptr++] = 1;

					data[dptr++] = 0;
					data[dptr++] = m_image->get_adr_control(0);
					data[dptr++] = 1;
					data[dptr++] = 0;

					uint32_t tstart = m_image->get_track_start(0);

					if (msf)
					{
						tstart = to_msf(tstart);
					}

					put_u32be(&data[dptr], tstart);
					dptr += 4;
				}
				break;

			default:
				m_device->logerror("T10MMC: Unhandled READ TOC format %d\n", toc_format());
				break;
			}
		}
		break;

	case T10SPC_CMD_MODE_SENSE_6:
	case T10SPC_CMD_MODE_SENSE_10:
		//      m_device->logerror("T10MMC: MODE SENSE page code = %x, PC = %x\n", command[2] & 0x3f, (command[2]&0xc0)>>6);
		{
			memset(data, 0, SCSILengthFromUINT16( &command[ 7 ] ));

			const uint8_t page = command[2] & 0x3f;
			int ptr = 0;

			if ((page == 0xe) || (page == 0x3f))
			{ // CD Audio control page
					data[ptr++] = 0x8e; // page E, parameter is savable
					data[ptr++] = 0x0e; // page length
					data[ptr++] = (1 << 2) | (m_sotc << 1); // IMMED = 1
					ptr += 5;   // skip reserved bytes
					// connect each audio channel to 1 output port and indicate max volume
					data[ptr++] = 1;
					data[ptr++] = 0xff;
					data[ptr++] = 2;
					data[ptr++] = 0xff;
					data[ptr++] = 4;
					data[ptr++] = 0xff;
					data[ptr++] = 8;
					data[ptr++] = 0xff;
			}

			if ((page == 0x0d) || (page == 0x3f))
			{ // CD page
					data[ptr++] = 0x0d;
					data[ptr++] = 6;    // page length
					data[ptr++] = 0;
					data[ptr++] = 0;
					data[ptr++] = 0;
					data[ptr++] = 60;
					data[ptr++] = 0;
					data[ptr++] = 75;
			}

			if ((page == 0x2a) || (page == 0x3f))
			{ // Page capabilities
					data[ptr++] = 0x2a;
					data[ptr++] = 0x14; // page length
					data[ptr++] = 0x00;
					data[ptr++] = 0x00; // CD-R only
					data[ptr++] = 0x01; // can play audio
					data[ptr++] = 0;
					data[ptr++] = 0;
					data[ptr++] = 0;
					data[ptr++] = 0x02;
					data[ptr++] = 0xc0; // 4x speed
					data[ptr++] = 0x01;
					data[ptr++] = 0x00; // 256 volume levels supported
					data[ptr++] = 0x00;
					data[ptr++] = 0x00; // buffer
					data[ptr++] = 0x02;
					data[ptr++] = 0xc0; // 4x read speed
					data[ptr++] = 0;
					data[ptr++] = 0;
					data[ptr++] = 0;
					data[ptr++] = 0;
					data[ptr++] = 0;
					data[ptr++] = 0;
			}
		}
		break;

	case T10MMC_CMD_READ_DISC_STRUCTURE:
		m_device->logerror("T10MMC: READ DISC STRUCTURE, data\n");
		data[0] = data[1] = 0;
		data[2] = data[3] = 0;

		if((command[1] & 0x0f) == 0 && command[7] == 0x04) // DVD / DVD disc manufacturing information
		{
			data[1] = 0xe;
			for(int i=4; i != 0xe; i++)
				data[i] = 0;
		}
		break;

	default:
		t10spc::ReadData( data, dataLength );
		break;
	}
}

// scsicd_write_data
//
// Write data to the CD-ROM device as part of the execution of a command

void t10mmc::WriteData( uint8_t *data, int dataLength )
{
	switch (command[ 0 ])
	{
	case T10SPC_CMD_MODE_SELECT_6:
	case T10SPC_CMD_MODE_SELECT_10:
		m_device->logerror("T10MMC: MODE SELECT page %x\n", data[0] & 0x3f);

		switch (data[0] & 0x3f)
		{
			case 0x0:   // vendor-specific
				// check for SGI extension to force 512-byte blocks
				if ((data[3] == 8) && (data[10] == 2))
				{
					m_device->logerror("T10MMC: Experimental SGI 512-byte block extension enabled\n");

					m_sector_bytes = 512;
					m_num_subblocks = 4;
				}
				else
				{
					m_device->logerror("T10MMC: Unknown vendor-specific page!\n");
				}
				break;

			case 0xe:   // audio page
				m_sotc = (data[2] >> 1) & 1;
				m_device->logerror("Ch 0 route: %x vol: %x\n", data[8], data[9]);
				m_device->logerror("Ch 1 route: %x vol: %x\n", data[10], data[11]);
				m_device->logerror("Ch 2 route: %x vol: %x\n", data[12], data[13]);
				m_device->logerror("Ch 3 route: %x vol: %x\n", data[14], data[15]);

				// TODO: CDDA audio channels and output port should be separate
				// The actual audio channel that gets sent to the output port is configurable and more than one audio channel can go to an output port
				m_cdda->set_output_gain(0, data[9] / 255.0f);
				m_cdda->set_output_gain(1, data[11] / 255.0f);
				break;
		}
		break;

	default:
		t10spc::WriteData( data, dataLength );
		break;
}
}