// =====================================================
// Проигрывание звуковых компакт-дисков
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =====================================================
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include <memory.h>
  
typedef unsigned char BYTE;
typedef unsigned int  WORD;
typedef unsigned long DWORD;

// Необходимо для обеспечения выравнивания 
// полей структур на границу байта
#pragma pack(1)

// Заголовок запроса для обращения к драйверу
typedef struct _ReqHdr
{
  BYTE bSize;
  BYTE bSubUnit;
  BYTE bCmd;
  WORD wStatus;
  BYTE bReserved[8];    
} ReqHdr;
  
typedef struct _PlayAudio
{
  ReqHdr rh;
  BYTE bMode;
  DWORD dwLoc;
  DWORD dwSectorNum;
} PlayAudio;

// Запрос IOCTL Output
typedef struct _IOCTL_Output
{             
  ReqHdr rh;
  BYTE   bMediaDescriptor;
  DWORD  lpTransferAddress;
  WORD   wDataSize;
  WORD   wStartSector;
  DWORD  lpVolID;
} IOCTL_Output;       

// Запрос на извлечение компакт-диска
typedef struct _EjectDisk
{
  BYTE  bControl;
} EjectDisk;

#pragma pack()

// Прототипы функций
void GetCDLetters(BYTE *bLetters);
void CallCDDriver(void *rh, int nCDUnit);
int PlayAudioTrack(DWORD dwLoc, DWORD dwSectorNum, int nCDUnit);
int StopAudio(int nCDUnit);
int DeviceOpen(int nCDUnit);
int DeviceClose(int nCDUnit);
int EjectCD(int nCDUnit);
DWORD Red2Sierra(DWORD dwRedLoc);

// Регистры для вызова функции int86
union REGS rg;                      

// Количество установленных устройств чтения CD-ROM
int nCDUnits;

// Номер первого устройства чтения CD-ROM
int nCDStartUnit;

// Слово состояния после вызова драйвера CD-ROM
int iStatus;
                               
// Массив номеров установленных устройств CD-ROM
BYTE bLetters[26];

// ---------------------------------------------------
// main
// Точка входа в программу
// ---------------------------------------------------
int main(int argc, char *argv[]) 
{
  int i;
  
  DWORD dwStartTrack;
  
  printf("CDPLAY, (c) A. Frolov, 1997\n\n");
  
  dwStartTrack = 1;
  if(argc == 2)
  {
    dwStartTrack = atol(argv[1]);
    printf("Track Red book: %ld\n", dwStartTrack);
    
  }
  else
  {
    printf("Usage: CDPLAY <Red book sector address>\n");
    return -1;
  }
  
  // Преобразование адреса сектора в формат Sierra
  dwStartTrack = Red2Sierra(dwStartTrack);
  printf("Track Sierra: %ld\n", dwStartTrack);
  
  // Проверяем, установлена ли программа MSCDEX
  rg.x.ax = 0x1500;
  rg.x.bx = 0;
  int86(0x2f, &rg, &rg);

  if(rg.x.bx == 0)
  {
    printf("MSCDEX is not installed\n");
    return -1;
  }
  
  else        
  {
    // Сохраняем общее количество устройств чтения CD-ROM
    nCDUnits = rg.x.bx;
    
    // Сохраняем номер первого такого устройства
    nCDStartUnit = rg.x.cx;

    // Определяем и отображаем вресию MSCDEX
    rg.x.ax = 0x150c;
    int86(0x2f, &rg, &rg);
    printf("MSCDEX version: %d.%d\n", rg.h.bh, rg.h.bl);

    // Отображаем количество найденных устройств чтения
    // CD-ROM и номер первого устройства
    printf("Found %d CD Unit, start unit: %c\n",
      nCDUnits, nCDStartUnit + 'A');
  }

  // Получаем массив номеров устройств чтения CD-ROM
  GetCDLetters(bLetters);
  
  // Отображаем обозначения всех устройств CD-ROM
  printf("CD-ROM letters: ");
  for(i = 0; i < nCDUnits; i++)
  {
    printf("%c ", bLetters[i] + 'A');  
  }
  printf("\n");

  // Открываем устройство
  iStatus = DeviceOpen(bLetters[0]);
  if(iStatus & 0x8000)
  {
    printf("DeviceOpen status: %04.4X\n", iStatus);
    return -1;
  }

  // Запускаем проигрывание
  iStatus = 
    PlayAudioTrack(dwStartTrack, 0xffffffff, bLetters[0]);
  if(iStatus & 0x8000)
  {
    printf("PlayAudioTrack status: %04.4X\n", iStatus);
    return -1;
  }
  printf("Started. Press any key to stop and eject CD\n");
  
  // Ожидаем, пока пользователь не нажмет клавишу
  getch();

  // Останавливаем проигрывание
  iStatus = StopAudio(bLetters[0]);
  if(iStatus & 0x8000)
  {
    printf("StopAudio status: %04.4X\n", iStatus);
    return -1;
  }

  // Извлекаем диск
  iStatus = EjectCD(bLetters[0]);
  if(iStatus & 0x8000)
  {
    printf("EjectCD status: %04.4X\n", iStatus);
    return -1;
  }

  // Закрываем устройство
  iStatus = DeviceClose(bLetters[0]);
  if(iStatus & 0x8000)
  {
    printf("DeviceClose status: %04.4X\n", iStatus);
    return -1;
  }
  return 0;
}

// ---------------------------------------------------
// PlayAudioTrack
// Запуск проигрывания звукового компакт-диска
// ---------------------------------------------------
int PlayAudioTrack(DWORD dwLoc, DWORD dwSectorNum, int nCDUnit)
{
  PlayAudio cmd;
  
  memset(&cmd, 0, sizeof(PlayAudio));
  
  cmd.rh.bSize    = 22;
  cmd.rh.bSubUnit = 0;
  cmd.rh.bCmd     = 132;
  
  cmd.bMode  = 0;
  cmd.dwLoc  = dwLoc;
  cmd.dwSectorNum  = dwSectorNum;
  
  CallCDDriver(&cmd, nCDUnit);
  return cmd.rh.wStatus;
}  

// ---------------------------------------------------
// StopAudio
// Остановка проигрывания звукового компакт-диска
// ---------------------------------------------------
int StopAudio(int nCDUnit)
{
  ReqHdr cmd;
  
  memset(&cmd, 0, sizeof(ReqHdr));
  
  cmd.bSize    = 13;
  cmd.bSubUnit = 0;
  cmd.bCmd     = 133;
  
  CallCDDriver(&cmd, nCDUnit);
  return (cmd.wStatus);
}  

// ---------------------------------------------------
// DeviceOpen
// Открывание устройства
// ---------------------------------------------------
int DeviceOpen(int nCDUnit)
{
  ReqHdr cmd;
  
  memset(&cmd, 0, sizeof(ReqHdr));
  
  cmd.bSize    = 13;
  cmd.bSubUnit = 0;
  cmd.bCmd     = 13;
  
  CallCDDriver(&cmd, nCDUnit);
  return (cmd.wStatus);
}  


// ---------------------------------------------------
// DeviceClose
// Закрывание устройства
// ---------------------------------------------------
int DeviceClose(int nCDUnit)
{
  ReqHdr cmd;
  
  memset(&cmd, 0, sizeof(ReqHdr));
  
  cmd.bSize    = 13;
  cmd.bSubUnit = 0;
  cmd.bCmd     = 14;
  
  CallCDDriver(&cmd, nCDUnit);
  return (cmd.wStatus);
}  

// ---------------------------------------------------
// EjectCD
// Извлечение компакт-диска
// ---------------------------------------------------
int EjectCD(int nCDUnit)
{
  IOCTL_Output cmd;
  EjectDisk ed;
  
  memset(&cmd, 0, sizeof(IOCTL_Output));
  
  cmd.rh.bSize    = 14;
  cmd.rh.bSubUnit = 0;
  cmd.rh.bCmd     = 12;
  
  cmd.bMediaDescriptor  = 0;
  cmd.lpTransferAddress = (DWORD)(void far *)&ed;
  cmd.wDataSize         = 1;
  cmd.wStartSector      = 0;
  cmd.lpVolID           = (DWORD)(void far *)NULL;
  
  ed.bControl = 0;
  
  CallCDDriver(&cmd, nCDUnit);
  return cmd.rh.wStatus;
}  

// ---------------------------------------------------
// CallCDDriver
// Вызов драйвера компакт-диска
// ---------------------------------------------------
void CallCDDriver(void *rh, int nCDUnit)
{
  static union REGS rg;
  static struct SREGS srg;
  
  segread(&srg);
  rg.x.ax = 0x1510;
  rg.x.cx = nCDUnit;
  rg.x.bx = FP_OFF(rh);  
  
  int86x(0x2f, &rg, &rg, &srg);
}

// ---------------------------------------------------
// GetCDLetters
// Заполнение массива номерами установленных 
// в системе устройств чтения компакт-диска
// ---------------------------------------------------
void GetCDLetters(BYTE *bLetters)
{
  static union REGS rg;
  static struct SREGS srg;
  
  segread(&srg);
  rg.x.ax = 0x150d;
  rg.x.bx = FP_OFF(bLetters);  
  
  int86x(0x2f, &rg, &rg, &srg);
}

// ---------------------------------------------------
// Преобразование адреса дорожки из формата Red book
// в формат Sierra
// ---------------------------------------------------
DWORD Red2Sierra(DWORD dwRedLoc)
{
  BYTE bMin, bSec, bFrame;

  bMin   = (BYTE)((dwRedLoc >> 16) & 0xff);
  bSec   = (BYTE)((dwRedLoc >> 8) & 0xff);
  bFrame = (BYTE)(dwRedLoc & 0xff);
  
  return (DWORD)bMin * 75 * 60 + (DWORD)bSec * 75 + 
    (DWORD)bFrame - 150;
}