// MP3 to WAV converter (c) '97 N. Beisert (nbeisert@physik.tu-muenchen.de)
// you are free to do with this stuff whatever you like. it's freeware.

#include <conio.h>
#include <dos.h>
#include <direct.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>

const int buflen=16384;
static char buf[buflen];

struct
{
  char riff[4];
  long rifflen;
  char wave[4];
  char fmt[4];
  long fmtlen;
  short format;
  short chan;
  long freq;
  long rate;
  short align;
  short bits;
  short extra;
  short dunno1;
  short dunno2;
  short dunno3;
  short blklen;
  short dunno4;
  short dunno5;
  char data[4];
  long datalen;
} wavehdr;

int getheader(unsigned long hdr, int &layer, int &ver, int &freq, int &stereo, int &rate)
{
  if ((hdr&0xF0FF)!=0xF0FF)
    return 0;
  ver=(hdr>>11)&1;
  layer=4-((hdr>>9)&3);
  if (layer==4)
    return 0;
  int rateidx=(hdr>>20)&15;
  int frqidx=(hdr>>18)&3;
  stereo="\x02\x02\x02\x01"[(hdr>>30)&3];
  switch (frqidx)
  {
  case 0: freq=44100; break;
  case 1: freq=48000; break;
  case 2: freq=32000; break;
  default: return 0;
  }
  if (ver)
    switch (layer)
    {
    case 1: rate="\x00\x04\x08\x0C\x10\x14\x18\x1C\x20\x24\x28\x2C\x30\x34\x38\x00"[rateidx]*1000; break;
    case 2: rate="\x00\x04\x06\x07\x08\x0A\x0C\x0E\x10\x14\x18\x1C\x20\x28\x30\x00"[rateidx]*1000; break;
    case 3: rate="\x00\x04\x05\x06\x07\x08\x0A\x0C\x0E\x10\x14\x18\x1C\x20\x28\x00"[rateidx]*1000; break;
    default: return 0;
    }
  else
    switch (layer)
    {
    case 1: rate="\x00\x04\x06\x07\x08\x0A\x0C\x0E\x10\x12\x14\x16\x18\x1C\x20\x00"[rateidx]*1000; break;
    case 2: rate="\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0A\x0C\x0E\x10\x12\x14\x00"[rateidx]*1000; break;
    case 3: rate="\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0A\x0C\x0E\x10\x12\x14\x00"[rateidx]*1000; break;
    default: return 0;
    }

  if (!ver)
    freq>>=1;
  if (!rate)
    return 0;
  return 1;
}

int conv(const char *name, int kill, int ext, int excl)
{
  static char outname[300];

  strcpy(outname, name);
  if (!ext&&!stricmp(outname+strlen(outname)-4, ".mp3"))
    outname[strlen(outname)-4]=0;
  strcat(outname, ".wav");

  char tag[128];
  int fi=open(name, O_RDONLY|O_BINARY);
  if (fi<0)
    return 1;
  long len=filelength(fi);
  lseek(fi, -128, SEEK_END);
  *tag=0;
  read(fi, tag, 128);
  if (!memcmp(tag, "TAG", 3))
    len-=128;
  lseek(fi, 0, SEEK_SET);
  unsigned long hdr;
  read(fi, &hdr, 4);
  lseek(fi, 0, SEEK_SET);

  int chan=2;
  int layer,freq,rate,ver;
  if (!getheader(hdr, layer, ver, freq, chan, rate))
    return 1;
  if (layer!=3)
    return 1;
  int blklen=(288<<ver)*chan*rate/freq;

  memcpy(wavehdr.riff, "RIFF", 4);
  wavehdr.rifflen=sizeof(wavehdr)-8+(memcmp(tag, "TAG", 3)?0:136)+len;
  memcpy(wavehdr.wave, "WAVE", 4);
  memcpy(wavehdr.fmt, "fmt ", 4);
  wavehdr.fmtlen=30;
  wavehdr.format=0x55;
  wavehdr.bits=0;
  wavehdr.chan=chan;
  wavehdr.freq=freq;
  wavehdr.rate=rate;
  wavehdr.align=(layer==1)?4:1;
  wavehdr.extra=12;
  wavehdr.dunno1=1;
  wavehdr.dunno2=2;
  wavehdr.dunno3=0;
  wavehdr.blklen=blklen;
  wavehdr.dunno4=1;
  wavehdr.dunno5=0x571;
  memcpy(wavehdr.data, "data", 4);
  wavehdr.datalen=len;

  int fo=open(outname, O_WRONLY|O_BINARY|O_TRUNC|O_CREAT|(excl?O_EXCL:0), S_IREAD|S_IWRITE);
  if (fo<0)
    return 0;
  write(fo, &wavehdr, sizeof(wavehdr));
  cputs(name);

  long pos=0;
  while (1)
  {
    long l=len-pos;
    if (l>buflen)
      l=buflen;
    if (!l)
      break;
    read(fi, buf, l);
    write(fo, buf, l);
    pos+=l;
  }
  if (!memcmp(tag, "TAG", 3))
  {
    write(fo, "$TAG", 4);
    write(fo, "\x80\0\0\0", 4);
    write(fo, tag, 128);
  }
  len=filelength(fo)-8;
  lseek(fo, 4, SEEK_SET);
  write(fo, &len, 4);
  close(fi);
  close(fo);
  if (kill)
    unlink(name);
  cputs("\n");
  return 0;
}

void convdir(const char *base, const char *sub, const char *mask, int recurse, int kill, int ext, int excl)
{
  char *pathbuf=new char [400];
  strcpy(pathbuf, base);
  strcat(pathbuf, sub);
  strcat(pathbuf, mask);

  const int buflen=4096;
  static char buffer[buflen];
  find_t fnd;
  int found;
  for (found=!_dos_findfirst(pathbuf, _A_NORMAL, &fnd); found; found=!_dos_findnext(&fnd))
  {
    strcpy(pathbuf, base);
    strcat(pathbuf, sub);
    strcat(pathbuf, fnd.name);
    conv(pathbuf, kill, ext, excl);
  }
  if (!recurse)
    return;
  strcpy(pathbuf, base);
  strcat(pathbuf, sub);
  strcat(pathbuf, "*");
  for (found=!_dos_findfirst(pathbuf, _A_SUBDIR, &fnd); found; found=!_dos_findnext(&fnd))
  {
    if (!(fnd.attrib&_A_SUBDIR)||!strcmp(fnd.name, ".")||!strcmp(fnd.name, ".."))
      continue;
    strcpy(pathbuf, sub);
    strcat(pathbuf, fnd.name);
    strcat(pathbuf, "\\");

    convdir(base, pathbuf, mask, 1, kill, ext, excl);
  }
  delete pathbuf;
}

int isdir(const char *x)
{
  find_t fnd;
  if (strchr(x, '?')||strchr(x, '*')||_dos_findfirst(x, _A_SUBDIR, &fnd))
    return 0;
  return (fnd.attrib&_A_SUBDIR)?1:0;
}


int main(int argn, char **argv)
{
  cputs("mp3 to mp3-wav converter (c) '97 Niklas Beisert\n\n");

  int excl=1;
  int help=1;
  int kill=0;
  int recurse=0;
  int ext=0;
  int i;
  for (i=1; i<argn; i++)
  {
    if (!stricmp(argv[i], "/k"))
      kill=1;
    if (!stricmp(argv[i], "/r"))
      recurse=1;
    if (!stricmp(argv[i], "/e"))
      ext=1;
    if (!stricmp(argv[i], "/o"))
      excl=0;
  }
  for (i=1; i<argn; i++)
  {
    if (argv[i][0]=='/')
      continue;

    char path[300];
    strcpy(path, argv[i]);
    if (path[0]&&(path[1]==':')&&(path[2]!='\\'))
    {
      memmove(path+4, path+2, strlen(path)-1);
      path[2]='.';
      path[3]='\\';
    }
    char mask[300];
    if (isdir(path))
      strcat(path, "\\");
    char *p=strrchr(path, '\\')?(strrchr(path, '\\')+1):path;
    strcpy(mask, p);
    *p=0;
    if (!*path)
      strcpy(path, ".\\");
    if (!*mask)
      strcpy(mask, "*");

    convdir(path, "", mask, recurse, kill, ext, excl);
    help=0;
  }

  if (help)
  {
    cputs("syntax: mp3wav [/r] [/k] [/o] [/e] files\n");
    cputs("  /r: recurse subdirectories\n");
    cputs("  /o: overwrite existing files\n");
    cputs("  /k: kill originals\n");
    cputs("  /e: always add .wav extension (otherwise replace .mp3 by .wav)\n");
  }
  return 0;
}
