/* Warning! This C source contains extended characters! */

#include <stdio.h>
#include <dir.h>
#include <dos.h>
#include <string.h>
#include <bios.h>
#include <process.h>
#include <conio.h>
#include <time.h>
#include <stdlib.h>
#include <musique.h>
#include <musique.e>

void bellit(void)
/* short bell */
{
  sound(SNDFREQ);
  delay(SNDLENGTH);
  nosound();
}

void myresetmode(PARAMETERTYPE *parameter)
/* Turbo-C doesn't like extended or non-text modes */
/* resets the video mode iff necessary */
/* does not rely on knowing the screen size since no clear screen done */
/* some incomplete BIOSes may force complete reset all the time */
{
  union REGS regs;

  regs.h.ah=(unsigned)'\xF'; /* get current video mode */
  int86(0x10,&regs,&regs);
  if (regs.h.al!=(unsigned char)parameter->videomode) {
    regs.h.ah=(unsigned)'\x0'; /* reset video mode */
    regs.h.al=(unsigned char)parameter->videomode;
    int86(0x10,&regs,&regs);
  }
}

void setborder(int color)
/* sets the border color */
/* color is in high 4 nibbles */
{
  union REGS regs;

  /* CGA EGA VGA */
  regs.h.bl=(unsigned char)(color>>4);
  regs.h.bh=(unsigned)'\x0';
  regs.x.ax=(unsigned)0xB00;
  int86(0x10,&regs,&regs);
}

void highbackground(void)
/* disables blinking/enables high intensity background */
{
  union REGS regs;
  int addr;
  unsigned char data;

  /* MDA CGA */
  addr=peek((unsigned)0x40,(unsigned)0x63)+0x4;
  data=(unsigned)(peekb((unsigned)0x40,(unsigned)0x65) & '\xDF');
  outportb(addr,data);
  /* MCGA EGA VGA */
  regs.h.bl=(unsigned)'\x0';
  regs.x.ax=(unsigned)0x1003;
  int86(0x10,&regs,&regs);
}

void myclrscr(PARAMETERTYPE *parameter, int color)
/* Turbo-C doesn't like extended or non-text modes */
/* clear screen, sets border, and enables high intensity background */
/* color is is high 4 nibbles */
{
  union REGS regs;

  regs.x.ax=(unsigned)0x700; /* scroll whole screen */
  regs.h.bh=(unsigned char)color;
  regs.x.cx=(unsigned)0x0;
  regs.h.dl=(unsigned char)(parameter->maxcolumn-1);
  regs.h.dh=(unsigned char)(parameter->maxline-1);
  int86(0x10,&regs,&regs);
  regs.h.ah=(unsigned)'\x2'; /* cursor in upper-left corner */
  regs.h.bh=(unsigned)'\x0';
  regs.x.dx=(unsigned)0x0;
  int86(0x10,&regs,&regs);
  highbackground();
  setborder(color);
}

void mysetmode(PARAMETERTYPE *parameter)
/* Turbo-C doesn't like extended or non-text modes */
/* sets the video mode and border, enables high intensity background */
/* avoids a complete video mode reset when possible */
/* some incomplete BIOSes may force complete reset all the time */
{
  union REGS regs;

  regs.h.ah=(unsigned)'\xF'; /* get current video mode */
  int86(0x10,&regs,&regs);
  if (regs.h.al!=(unsigned char)parameter->videomode) {
    regs.h.ah=(unsigned)'\x0'; /* reset video mode */
    regs.h.al=(unsigned char)parameter->videomode;
    int86(0x10,&regs,&regs);
  }
  myclrscr(parameter,colorborder); /* always set screen attributes to border */
}

void blankstring(char target[], int targetlength)
/* creates a string full of spaces */
{
  int ptr;

  for (ptr=0 ; ptr<targetlength ; ptr++) target[ptr]=' ';
  target[targetlength]='\0';
}

void initialize(SONGSTYPE *songs, PARAMETERTYPE *parameter, char menuname[])
{
  language=FRENCH;
  coloroutline=DEFAULTOUTLINE;
  colortitle=DEFAULTTITLE;
  coloritem=DEFAULTITEM;
  colorcurrent=DEFAULTCURRENT;
  colorpicked=DEFAULTPICKED;
  colorcursor=DEFAULTCURSOR;
  colorborder=DEFAULTBORDER;
  songs->songnumber=0;
  songs->songcorner=songs->songcurrent=1;
  songs->songpath[0]='\0';
  songs->songpathoffset=1;
  /* used for overwriting items to erase them on the screen */
  blankstring(songs->song[0].diskname,DISKNAMELENGTH);
  blankstring(songs->song[0].score,SCORELENGTH);
  blankstring(songs->song[0].DOSname,DOSNAMELENGTH);
  blankstring(songs->song[0].songname,SONGNAMELENGTH);
  songs->song[0].picked=0;
  songs->song[0].DOSpath=songs->songpath;
  strcpy(menuname,DEFAULTMENU);
  strcpy(parameter->speed,DEFAULTSPEED);
  parameter->tempdrive=ODDDRIVE;
  parameter->ports[0]=ODDPORTS;
  parameter->videomode=ODDMODE;
  parameter->A=parameter->B=parameter->C=FALSE;
  parameter->verbose=FALSE;
  parameter->DOS=FALSE;
  parameter->pause=FALSE;
  parameter->mouse=FALSE;
  parameter->rawvideo=TRUE;
  switch (peek((unsigned)0x40,(unsigned)0x63)) {
    case 0x3B4:parameter->videosegment=0xB000;
               break;
    case 0x3D4:parameter->videosegment=0xB800;
               break;
    default:error(34);
  }
}

void busyfloppy(void)
/* waits until floppy shuts off */
{
while ((0xF & (int)peekb((unsigned)0x40,(unsigned)0x3F))!=0) ;
}

void cuttail(char text[])
/* cuts the tail of spaces */
{
  int scanner;

  scanner=strlen(text);
  while ((text[--scanner]==' ') && (scanner>=0)) text[scanner]='\0';
}

int locateasong(SONGTYPE *song, PARAMETERTYPE *parameter)
/* checks that a module exists and maybe asks for the correct floppy */
/* looks for .MOD first, then for .ZIP (iff PKUNZIP.EXE located) */
/* returns -1 if not found, 0-1-2 for .MOD, and 3-4-5 for .ZIP */
/* looks in: temporary drive-path supplied(if not .)-current drive */
/* uses normal, read-only, hidden, system, archive bits for looking */
{
  struct ffblk fff;
  char filename[LINELENGTH+LINELENGTH]; /* path could be long */
  int notfound;
  char currenttext[3];

  strcpy(currenttext,"A:");
  currenttext[0]+=(char)parameter->currentdrive;
  /* look on temporary drive for .MOD */
  strcpy(filename,(*song).DOSname);
  cuttail(filename);
  strcat(filename,MPENDING);
  notfound=findfirst(filename,&fff,39); 
  if (notfound) {
    if (parameter->pkunzipfound) {
      /* look on temporary drive for .ZIP */
      strcpy(filename,(*song).DOSname);
      cuttail(filename);
      strcat(filename,PKENDING);
      notfound=findfirst(filename,&fff,39); 
    }
    if (notfound) {
      if (strcmp((*song).DOSpath,ODDPATH)!=0) {
        /* look on menu path for .MOD */
        strcpy(filename,(*song).DOSpath);
        strcat(filename,(*song).DOSname);
        cuttail(filename);
        strcat(filename,MPENDING);
        notfound=findfirst(filename,&fff,39); 
      }
      if (notfound) {
        if (parameter->pkunzipfound) 
          if (strcmp((*song).DOSpath,ODDPATH)!=0) {
            /* look on menu path for .ZIP */
            strcpy(filename,(*song).DOSpath);
            strcat(filename,(*song).DOSname);
            cuttail(filename);
            strcat(filename,PKENDING);
            notfound=findfirst(filename,&fff,39); 
        }
        if (notfound)
          do {
            /* look on current drive for .MOD */
            strcpy(filename,currenttext);
            strcat(filename,(*song).DOSname);
            cuttail(filename);
            strcat(filename,MPENDING);
            notfound=findfirst(filename,&fff,39); 
            if (notfound) {
              if (parameter->pkunzipfound) {
                /* look on current drive for .ZIP */
                strcpy(filename,currenttext);
                strcat(filename,(*song).DOSname);
                cuttail(filename);
                strcat(filename,PKENDING);
                notfound=findfirst(filename,&fff,39); 
              }
              if (notfound)
                if (parameter->currentdrive<=1) {
                  /* song on floppy; ask user */
                  bellit();
                  message(23);
                  printf("%s\n",(*song).diskname);
                  message(22);
                  switch (bioskey(0)) {
                    case KEYESC:return(-1); /* aborted */
                    default:break;
                  }
                } 
                else {
                  /* song not on floppy; give up */
                  printf("<%s>\n",(*song).songname);
                  message(24);
                  if (parameter->pause) {
                    message(25);
                    bellit();
                    bioskey(0);
                  }
                  else {
                    bellit();
                    delay(NOTFOUNDDELAY);
                    bellit();
                    delay(NOTFOUNDDELAY);
                    bellit();
                    delay(NOTFOUNDDELAY);
                    bellit();
                    delay(NOTFOUNDDELAY);
                    bellit();
                  }
                  return(-1); /* not found */
                }
              else {
                message(35);
                return(5); /* .ZIP under current drive */
              }
            }
            else {
              message(32);
              return(2); /* .MOD under current drive */
            }
          } while (TRUE);
        else {
          message(34);
          return(4); /* .ZIP under path */
        }
      }
      else {
        message(31);
        return(1); /* .MOD under path */
      }
    }
    else {
      message(33);
      return(3); /* .ZIP under temporary drive */
    }
  }
  else {
    message(36);
    return(0); /* .MOD under temporary drive */
  }
}

void checkasong(char DOSname[])
/* checks that a module exists */
{
  struct ffblk fff;
  char filename[LINELENGTH];

  strcpy(filename,DOSname);
  cuttail(filename);
  strcat(filename,MPENDING);
  /* normal, read-only, hidden, system, archive */
  if (findfirst(filename,&fff,39)) error(18);
}

void deleteasong(char DOSname[])
/* deletes a song */
{
  char filename[LINELENGTH];
  int errorint;

  message(40);
  strcpy(filename,DOSname);
  cuttail(filename);
  strcat(filename,MPENDING);
  errorint=unlink(filename);
  if (errorint==-1) error(19);
}

void shutup(int port)
/* turns off gradually a parallel port */
{
  int value;

  value=(int)inportb(port);
  while (value>0) {
    value--;
    outportb(port,(unsigned char)value);
    delay(SHUTUPDELAY);
  }  
}

void movemodule(SONGTYPE *song, int found, PARAMETERTYPE *parameter)
/* moves a module onto the temporary drive */
/* because the module must be in the current directory for ModPlay */
/* this is simpler than to look at DOSpath and to be clever with logged drive */
/* found=1 is from path, found=2 is from current drive */
{
  char buffer[BUFFERSIZE];
  char filename[LINELENGTH+LINELENGTH]; /* path could be long */
  FILE *source,*target;
  int errorint,tomove,moved;
  char temporarytext[3];
  char currenttext[3];

  message(38);
  strcpy(temporarytext,"A:");
  strcpy(currenttext,"A:");
  temporarytext[0]+=(char)parameter->tempdrive;
  currenttext[0]+=(char)parameter->currentdrive;
  if (found==1) strcpy(filename,(*song).DOSpath);
  else strcpy(filename,currenttext);
  strcat(filename,(*song).DOSname);
  cuttail(filename);
  strcat(filename,MPENDING);
  source=fopen(filename,"rb");
  if (source==NULL) error(25);
  strcpy(filename,temporarytext);
  strcat(filename,(*song).DOSname);
  cuttail(filename);
  strcat(filename,MPENDING);
  target=fopen(filename,"wb");
  if (target==NULL) error(26);
  tomove=(int)fread(buffer,(unsigned)1,(unsigned)BUFFERSIZE,source);
  while (tomove!=0) {
    moved=(int)fwrite(buffer,(unsigned)1,(unsigned)tomove,target);
    if (moved!=tomove) error(27);
    tomove=(int)fread(buffer,(unsigned)1,(unsigned)BUFFERSIZE,source);
  }
  errorint=fclose(source);
  if (errorint==EOF) error(28);
  errorint=fclose(target);
  if (errorint==EOF) error(29);
}

void playasong(SONGTYPE *song, PARAMETERTYPE *parameter)
/*finds, maybe decompresses, waits, plays, shuts off, and maybe deletes a song*/
{
  char command[LINELENGTH+LINELENGTH]; /* path could be long */
  char currenttext[3];
  int found,errorint;

  strcpy(currenttext,"A:");
  currenttext[0]+=(char)parameter->currentdrive;
  /* look for MOD or ZIP under temporary drive, menu path, and current drive */
  found=locateasong(song,parameter);
  if (found>=0) {
    /* song found */
    if (found>=3) {
      /* must be decompressed on the temporary drive known clear */
      message(37);
      /* known on the temporary drive */
      strcpy(command,PKUNZIP0);
      strcat(command," ");
      if (found==4) strcat(command,(*song).DOSpath);
      else
        if (found==5) strcat(command,currenttext);
      strcat(command,(*song).DOSname);
      cuttail(command);
      message(43);
      printf("%s\n",command);
      if (parameter->pause) {
        bellit();
        message(22);
        switch (bioskey(0)) {
          case KEYESC:error(9); /* aborted */
          default:break;
        }
      }
      errorint=system(command);
      printf("\n");
      if (errorint==-1) error(20);
      checkasong((*song).DOSname);
    }
    else 
      /* if temporary drive is clear, move module there */
      if (found>=1) movemodule(song,found,parameter);
    message(39);
    /* known on the temporary drive */
    strcpy(command,MODPLAY0);
    strcat(command," ");
    strcat(command,parameter->ports);
    strcat(command," ");
    strcat(command,parameter->speed);
    strcat(command," ");
    strcat(command,(*song).DOSname);
    cuttail(command);
    message(43);
    printf("%s\n",command);
    if (parameter->pause) {
      bellit();
      message(22);
      switch (bioskey(0)) {
        case KEYESC:error(9); /* aborted */
        default:break;
      }
    }
    busyfloppy();
    errorint=system(command);
    if (errorint==-1) error(20);
    if (parameter->A) {
      message(29);
      shutup(PORT1);
    }
    if (parameter->B) {
      message(29);
      shutup(PORT2);
    }
    if (parameter->C) {
      message(29);
      shutup(PORT3);
    }
    /* if .MOD was copied/created, it must be deleted since not there before */
    if (found>=1) deleteasong((*song).DOSname);
  }
}

void cleanup(PARAMETERTYPE *parameter)
/* deletes temporary programs on the temporary drive */
{
  int errorint;

  if (parameter->pkunzipcopied || parameter->modplaycopied) {
    message(6);
    if (parameter->modplaycopied) {
      errorint=unlink(MODPLAY1);
      if (errorint==-1) {
        errorint=unlink(MODPLAY2);
        if (errorint==-1) {
          errorint=unlink(MODPLAY3);
          if (errorint==-1) error(21);
        }
      }
    }
    if (parameter->pkunzipcopied) {
      errorint=unlink(PKUNZIP1);
      if (errorint==-1) error(21);
    }
  }
}

void playbatch(SONGSTYPE *songs, PARAMETERTYPE *parameter)
/* plays the current batch of modules and Escape aborts */
{
  int songptr;
  int priority;
  int songcancel;

  songcancel=FALSE;
  for (priority=1 ; priority<=songs->songpriority ; priority++) 
    if (!songcancel) {
      for (songptr=1 ; songptr<=songs->songnumber ; songptr++)
        if (songs->song[songptr].picked==priority) {  
          songs->song[songptr].picked=0; 
          playasong(&(songs->song[songptr]),parameter);
          break;
        }
      while (bioskey(1)) 
        if (bioskey(0)==KEYESC) songcancel=TRUE;
        else bellit();
    }
  if (songcancel)
    for (songptr=1 ; songptr<=songs->songnumber ; songptr++)
      songs->song[songptr].picked=0; 
}

int main(int argc, char *argv[])
{
  char menuname[LINELENGTH];
  FILE *menufile;
  SONGSTYPE songs;
  PARAMETERTYPE parameter;
  MOUSETYPE mousedata;
  int errorint;
  int songabort; /* TRUE indicates terminate MUSIQUE */

  directvideo=0; /* safety for extended color text modes */ 
  delay(1); /* solves initial delay() bug(?) */
  srand((unsigned)time(NULL));
  initialize(&songs,&parameter,menuname);
  scanarguments(argc,argv,menuname,&parameter);
  if (parameter.videomode!=ODDMODE) myresetmode(&parameter);
  /* Copyright notice so Borland is happy */
  message(1);
  message(2);
  printf("%s\n",menuname);
  menufile=fopen(menuname,"rt");
  if (menufile==NULL) {
    strcat(menuname,MENUENDING);
    message(2);
    printf("%s\n",menuname);
    menufile=fopen(menuname,"rt");
    if (menufile==NULL) error(2);
  }
  message(3);
  readmenu(menufile,&songs);
  message(4);
  printf("%s\n",menuname);
  errorint=fclose(menufile);
  if (errorint==EOF) error(3);
  message(5);
  readparameter(&parameter,&mousedata,songs.songnumber);
  myclrscr(&parameter,colorborder); /* clear screen, set border & high stuff */
  setdisk(parameter.tempdrive);
  if (getdisk()!=parameter.tempdrive) error(32);
  do {
    /* screen already cleared here */
    printoutline(&parameter);
    if (parameter.mouse)
      mouseshow(mousedata.x,mousedata.y,&(mousedata.c),&(mousedata.color));
    printsongs(&songs,&parameter,&mousedata);
    songabort=picksongs(&songs,&parameter,&mousedata);
    if (parameter.mouse)
      mousehide(mousedata.x,mousedata.y,mousedata.c,mousedata.color);
    myclrscr(&parameter,7);
    if (!songabort) {
      playbatch(&songs,&parameter);
      mysetmode(&parameter); /* reset mode/clear screen, border & high stuff */
    }
  } while (!songabort);
  /* screen already cleared here */
  cleanup(&parameter);
  setdisk(parameter.currentdrive);
  if (getdisk()!=parameter.currentdrive) error(33);
  return(0);
}
