/********************************************************
*							*
*	PC Engine CD Command 0xD9 - SAPEP		*
*							*
********************************************************/
static void DoNEC_PCE_SAPEP(const uint8 *cdb)
{
 uint32 new_read_sec_end;

 //printf("Set audio end: %02x %02x %02x %02x %02x %02x %02x\n", cdb[9], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], cdb[6]);

 switch (cdb[9] & 0xc0)
 {
  default: SCSIDBG("Unknown SAPEP 9: %02x\n", cdb[9]);

  case 0x00:
   new_read_sec_end = (cdb[3] << 16) | (cdb[4] << 8) | cdb[5];
   break;

  case 0x40:
   new_read_sec_end = BCD_to_U8(cdb[4]) + 75 * (BCD_to_U8(cdb[3]) + 60 * BCD_to_U8(cdb[2]));
   new_read_sec_end -= 150;
   break;

  case 0x80:
   {
    int track = BCD_to_U8(cdb[2]);

    if(!track)
     track = 1;
    else if(track >= toc.last_track + 1)
     track = 100;
    new_read_sec_end = toc.tracks[track].lba;
   }
   break;
 }

 read_sec_end = new_read_sec_end;

 switch(cdb[1])	// PCE CD(TODO: Confirm these, and check the mode mask):
 {
	default:
	case 0x03: cdda.PlayMode = PLAYMODE_NORMAL;
		   cdda.CDDAStatus = CDDASTATUS_PLAYING;
		   break;

	case 0x02: cdda.PlayMode = PLAYMODE_INTERRUPT;
		   cdda.CDDAStatus = CDDASTATUS_PLAYING;
		   break;

	case 0x01: cdda.PlayMode = PLAYMODE_LOOP;
		   cdda.CDDAStatus = CDDASTATUS_PLAYING;
		   break;

	case 0x00: cdda.PlayMode = PLAYMODE_SILENT;
		   cdda.CDDAStatus = CDDASTATUS_STOPPED;
		   break;
 }

 SendStatusAndMessage(STATUS_GOOD, 0x00);
}
/********************************************************
*							*
*	PC Engine CD Command 0xDE - Get Directory Info	*
*							*
********************************************************/
static void DoNEC_PCE_GETDIRINFO(const uint8 *cdb)
{
 // Problems:
 //	Returned data lengths on real PCE are not confirmed.
 //	Mode 0x03 behavior not tested on real PCE

 uint8 data_in[2048];
 uint32 data_in_size = 0;

 memset(data_in, 0, sizeof(data_in));

 switch(cdb[1])
 {
  default: MDFN_DispMessage("Unknown GETDIRINFO Mode: %02x", cdb[1]);
	   printf("Unknown GETDIRINFO Mode: %02x", cdb[1]);
  case 0x0:
   data_in[0] = U8_to_BCD(toc.first_track);
   data_in[1] = U8_to_BCD(toc.last_track);

   data_in_size = 2;
   break;

  case 0x1:
   {
    uint8 m, s, f;

    LBA_to_AMSF(toc.tracks[100].lba, &m, &s, &f);

    data_in[0] = U8_to_BCD(m);
    data_in[1] = U8_to_BCD(s);
    data_in[2] = U8_to_BCD(f);

    data_in_size = 3;
   }
   break;

  case 0x2:
   {
    uint8 m, s, f;
    int track = BCD_to_U8(cdb[2]);

    if(!track)
     track = 1;
    else if(cdb[2] == 0xAA)
    {
     track = 100;
    }
    else if(track > 99)
    {
     CommandCCError(SENSEKEY_ILLEGAL_REQUEST, NSE_INVALID_PARAMETER);
     return;
    }

    LBA_to_AMSF(toc.tracks[track].lba, &m, &s, &f);

    data_in[0] = U8_to_BCD(m);
    data_in[1] = U8_to_BCD(s);
    data_in[2] = U8_to_BCD(f);
    data_in[3] = toc.tracks[track].control;
    data_in_size = 4;
   }
   break;
 }

 DoSimpleDataIn(data_in, data_in_size);
}
/********************************************************
*							*
*	PC Engine CD Command 0xD8 - SAPSP		*
*							*
********************************************************/
static void DoNEC_PCE_SAPSP(const uint8 *cdb)
{
 uint32 new_read_sec_start;

 //printf("Set audio start: %02x %02x %02x %02x %02x %02x %02x\n", cdb[9], cdb[1], cdb[2], cdb[3], cdb[4], cdb[5], cdb[6]);
 switch (cdb[9] & 0xc0)
 {
  default:  SCSIDBG("Unknown SAPSP 9: %02x\n", cdb[9]);
  case 0x00:
   new_read_sec_start = (cdb[3] << 16) | (cdb[4] << 8) | cdb[5];
   break;

  case 0x40:
   new_read_sec_start = AMSF_to_LBA(BCD_to_U8(cdb[2]), BCD_to_U8(cdb[3]), BCD_to_U8(cdb[4]));
   break;

  case 0x80:
   {
    int track = BCD_to_U8(cdb[2]);

    if(!track)
     track = 1;
    else if(track >= toc.last_track + 1)
     track = 100;
    new_read_sec_start = toc.tracks[track].lba;
   }
   break;
 }

 //printf("%lld\n", (long long)(monotonic_timestamp - pce_lastsapsp_timestamp) * 1000 / System_Clock);
 if(cdda.CDDAStatus == CDDASTATUS_PLAYING && new_read_sec_start == read_sec_start && ((int64)(monotonic_timestamp - pce_lastsapsp_timestamp) * 1000 / System_Clock) < 190)
 {
  pce_lastsapsp_timestamp = monotonic_timestamp;

  SendStatusAndMessage(STATUS_GOOD, 0x00);
  CDIRQCallback(PCECD_Drive_IRQ_DATA_TRANSFER_DONE);
  return;
 }

 pce_lastsapsp_timestamp = monotonic_timestamp;

 read_sec = read_sec_start = new_read_sec_start;
 read_sec_end = toc.tracks[100].lba;


 cdda.CDDAReadPos = 588;

 cdda.CDDAStatus = CDDASTATUS_PAUSED;
 cdda.PlayMode = PLAYMODE_SILENT;

 if(cdb[1])
 {
  cdda.PlayMode = PLAYMODE_NORMAL;
  cdda.CDDAStatus = CDDASTATUS_PLAYING;
 }

 if(read_sec < toc.tracks[100].lba)
  Cur_CDIF->HintReadSector(read_sec);

 SendStatusAndMessage(STATUS_GOOD, 0x00);
 CDIRQCallback(PCECD_Drive_IRQ_DATA_TRANSFER_DONE);
}
//
// Checks for Q subchannel mode 1(current time) data that has a correct checksum, but the data is nonsensical or corrupted nonetheless; this is the
// case for some bad rips floating around on the Internet.  Allowing these bad rips to be used will cause all sorts of problems during emulation, so we
// error out here if a bad rip is detected.
//
// This check is not as aggressive or exhaustive as it could be, and will not detect all potential Q subchannel rip errors; as such, it should definitely NOT be
// used in an effort to "repair" a broken rip.
//
void CDAccess_CCD::CheckSubQSanity(void)
{
 size_t checksum_pass_counter = 0;
 int prev_lba = INT_MAX;
 uint8 prev_track = 0;

 for(size_t s = 0; s < img_numsectors; s++)
 {
  union
  {
   uint8 full[96];
   struct
   {
    uint8 pbuf[12];
    uint8 qbuf[12];
   };
  } buf;

  sub_stream->seek(s * 96, SEEK_SET);
  sub_stream->read(buf.full, 96);

  if(subq_check_checksum(buf.qbuf))
  {
   uint8 adr = buf.qbuf[0] & 0xF;

   if(adr == 0x01)
   {
    uint8 track_bcd = buf.qbuf[1];
    uint8 index_bcd = buf.qbuf[2];
    uint8 rm_bcd = buf.qbuf[3];
    uint8 rs_bcd = buf.qbuf[4];
    uint8 rf_bcd = buf.qbuf[5];
    uint8 am_bcd = buf.qbuf[7];
    uint8 as_bcd = buf.qbuf[8];
    uint8 af_bcd = buf.qbuf[9];

    //printf("%2x %2x %2x\n", am_bcd, as_bcd, af_bcd);

    if(!BCD_is_valid(track_bcd) || !BCD_is_valid(index_bcd) || !BCD_is_valid(rm_bcd) || !BCD_is_valid(rs_bcd) || !BCD_is_valid(rf_bcd) ||
	!BCD_is_valid(am_bcd) || !BCD_is_valid(as_bcd) || !BCD_is_valid(af_bcd) ||
	rs_bcd > 0x59 || rf_bcd > 0x74 || as_bcd > 0x59 || af_bcd > 0x74)
    {
     throw MDFN_Error(0, _("Garbage subchannel Q data detected(bad BCD/out of range): %02x:%02x:%02x %02x:%02x:%02x"), rm_bcd, rs_bcd, rf_bcd, am_bcd, as_bcd, af_bcd);
    }
    else
    {
     int lba = ((BCD_to_U8(am_bcd) * 60 + BCD_to_U8(as_bcd)) * 75 + BCD_to_U8(af_bcd)) - 150;
     uint8 track = BCD_to_U8(track_bcd);

     prev_lba = lba;

     if(track < prev_track)
      throw MDFN_Error(0, _("Garbage subchannel Q data detected(bad track number)"));
     //else if(prev_track && track - pre

     prev_track = track;
    }
    checksum_pass_counter++;
   }
  }
 }

 //printf("%u/%u\n", checksum_pass_counter, img_numsectors);
}