/*----------------------------------------------------------------------

  PRG2WAV - a program which converts .PRG, .P00 or .T64 files
  into .WAV samples of Turbo Tape 64 files
  by Fabrizio Gennari, (C) 1999, 2000
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

  --------------------------------------------------------------------*/
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <math.h>

#ifndef MSDOS
#include <linux/soundcard.h>
#if !defined (SOUNDDEV)
#define SOUNDDEV "/dev/dsp"
#endif /*SOUNDDEV*/
#endif /*MSDOS*/

int inverted_waveform=0,stand_alone=0,kernel_load=0;
struct entry_element{
  int entry;
  struct entry_element* next;
};
struct entry_element* files_to_convert=0;
typedef enum {not_a_valid_file,t64,p00,prg} filetype;

void help(){
  printf("Usage: prg2wav -h|-v\n");
  printf("       prg2wav -l file [file...]\n");
#ifdef MSDOS
  printf("       prg2wav [-i] [-s] -o output_file_name file [file...]\n");
#else
  printf("       prg2wav [-i] [-s] [-o output_file_name] file [file...]\n");
#endif
  printf("Options:\n");
  printf("       -h: show this help message and exit successfully\n");
  printf("       -i: generate an inverted waveform\n");
  printf("       -k: create slow-loading files, loadable with kernel loader\n");
  printf("       -l: list files' contents\n");
  printf("       -o output_file_name[.wav]: use output_file_name.wav as output\n");
  printf("       -s: create stand-alone files\n");
  printf("       -v: show version and copyright info and exit successfully\n");
}

void version(){
  printf("PRG2WAV version 1.3, (C) by Fabrizio Gennari, 1999-2001\n");
  printf("This program is distributed under the GNU General Public License\n");
  printf("Read the file LICENSE for details\n");
}

int file_size(int descriptor){
  struct stat file_attributes;
  fstat(descriptor,&file_attributes);
  return file_attributes.st_size;
}

int ist64(ingresso){
  char* signature1="C64 tape image file";
  char* signature2="C64S tape image file";
  char* signature3="C64S tape file";
  char read_from_file[64];
  int byteletti;
  lseek(ingresso,0,SEEK_SET);
  if((byteletti=read(ingresso,read_from_file,64))<64||(
(strncmp(read_from_file,signature1,19))&&
(strncmp(read_from_file,signature2,20))&&
(strncmp(read_from_file,signature3,14))
)) return 0;
  return 1;
}

int isp00(ingresso){
  char* signature1="C64File\0";
  char read_from_file[28];
  unsigned char byte;
  int byteletti,start_address,end_address;
  lseek(ingresso,0,SEEK_SET);
  if(((byteletti=read(ingresso,read_from_file,28))<28)||
(strncmp(read_from_file,signature1,8))) return 0;
  lseek(ingresso,26,SEEK_SET);
  read(ingresso,&byte,1);
  start_address=byte;
  read(ingresso,&byte,1);
  start_address=byte*256+start_address;
  if((end_address=file_size(ingresso)+start_address-28)>65536) return 0;
  return 1;
}

int isprg(ingresso){
  int byteletti;
  int start_address;
  int end_address;
  unsigned char byte;
  lseek(ingresso,0,SEEK_SET);
  if((byteletti=read(ingresso,&byte,1))<1) return 0;
  start_address=byte;
  if((byteletti=read(ingresso,&byte,1))<1) return 0;
  start_address=byte*256+start_address;
  if((end_address=file_size(ingresso)+start_address-2)>65536) return 0;
  return 1;
}

filetype detect_type(int ingresso){
  if (ist64(ingresso)) return t64;
  if (isp00(ingresso)) return p00;
  if (isprg(ingresso)) return prg;
  return not_a_valid_file;
}

int get_total_entries(int ingresso){
  unsigned char byte;
  int entries;
  lseek(ingresso,34,SEEK_SET);
  read(ingresso,&byte,1);
  entries=byte;
  read(ingresso,&byte,1);
  entries=byte*256+entries;
  return entries;
}

int get_used_entries(int ingresso){
  int total_entries=get_total_entries(ingresso);
  int used_entries=0;
  int count,used,start_address,end_address,offset;
  char entry_name[17];
  for(count=1;count<=total_entries;count++)
    if(used=get_entry(count,ingresso,&start_address,&end_address,
		      &offset,entry_name))
      used_entries++;
  return used_entries;
}

void get_tape_name(char* tape_name,int ingresso){
  lseek(ingresso,40,SEEK_SET);
  read(ingresso,tape_name,24);
  tape_name[24]=0;
}

/* This fuction is false if an entry of a .T64 file is unused or
   out of range. It is always true for .PRG and .P00. */

int get_entry(int count,int ingresso,int* start_address,
	      int* end_address,int* offset,char* entry_name){
  unsigned char byte;
  int power;
  filetype type;
  switch (type=detect_type(ingresso)){
  case t64:
    if((count<1)||(count>get_total_entries(ingresso))) return 0;
    lseek(ingresso,32+32*count,SEEK_SET);
    read(ingresso,&byte,1);
    if (byte!=1)return 0;
    read(ingresso,&byte,1);
    if (byte==0)return 0;
    read(ingresso,&byte,1);
    *start_address=byte;
    read(ingresso,&byte,1);
    *start_address=byte*256+*start_address;
    read(ingresso,&byte,1);
    *end_address=byte;
    read(ingresso,&byte,1);
    *end_address=byte*256+*end_address;
    read(ingresso,&byte,1);
    read(ingresso,&byte,1);
    *offset=0;
    for(power=0;power<=3;power++){
      read(ingresso,&byte,1);
      *offset=*offset+(byte<<8*power);
    }
    read(ingresso,&byte,1);
    read(ingresso,&byte,1);
    read(ingresso,&byte,1);
    read(ingresso,&byte,1);
    read(ingresso,entry_name,16);
    entry_name[16]=0;
    return 1;
  case p00:
    *offset=28;
    lseek(ingresso,8,SEEK_SET);
    read(ingresso,entry_name,16);
    entry_name[16]=0;
    lseek(ingresso,26,SEEK_SET);
    read(ingresso,&byte,1);
    *start_address=byte;
    read(ingresso,&byte,1);
    *start_address=byte*256+*start_address;
    *end_address=file_size(ingresso)+*start_address-28;
    return 1;
  case prg:
    *offset=2;
    strcpy(entry_name,"                ");
    lseek(ingresso,0,SEEK_SET);
    read(ingresso,&byte,1);
    *start_address=byte;
    read(ingresso,&byte,1);
    *start_address=byte*256+*start_address;
    *end_address=file_size(ingresso)+*start_address-2;
    return 1;
  }
}
    
void list_contents(int ingresso){
  filetype type;
  int total_entries,used;
  int used_entries;
  char tape_name[25];
  int start_address,end_address,count,offset;
  char entry_name[17];
  switch (type=detect_type(ingresso)){
  case t64:
    total_entries=get_total_entries(ingresso);
    used_entries=get_used_entries(ingresso);
    get_tape_name(tape_name,ingresso);
    printf("%d total entr",total_entries);
    if (total_entries==1) printf("y"); else printf("ies");
    printf(", %d used entr",used_entries);
    if (used_entries==1) printf("y"); else printf("ies");
    printf(", tape name: %s\n",tape_name);
    printf("                      Start address    End address\n");
    printf("  No. Name              dec   hex       dec   hex\n");
    printf("--------------------------------------------------\n");
    for(count=1;count<=total_entries;count++)
      if(used=get_entry(count,ingresso,&start_address,&end_address,
			&offset,entry_name))
	printf("%5d %s %5d  %04x     %5d  %04x\n",count,entry_name,
	       start_address,start_address,end_address,end_address);
    return;
  case p00:
    get_entry(count,ingresso,&start_address,&end_address,
	      &offset,entry_name);
    printf("Start address: %d (hex %04x), end address: %d (hex %04x), name: %s\n"
	   ,start_address,start_address,end_address,end_address,entry_name);
    return;
  case prg:
    get_entry(count,ingresso,&start_address,&end_address,
	      &offset,entry_name);
    printf("Start address: %d (hex %04x), end address: %d (hex %04x)\n"
	   ,start_address,start_address,end_address,end_address);
    return;
  }	 
}	 

void WAV_write(unsigned char byte,int uscita){
  char normal_one[15]={151,193,223,237,233,210,173,128,83,46,23,19,33,63,105};
  char inverted_one[15]={105,63,33,19,23,46,83,128,173,210,233,237,223,193,151};
  char normal_zero[9]={161,211,223,190,128,66,33,45,95};
  char inverted_zero[9]={95,45,33,66,128,190,223,211,161};
  unsigned char count=128;
  do{
    if(byte&count){
      if (inverted_waveform) write(uscita,inverted_one,15);
      else write(uscita,normal_one,15);
    }
    else{
      if (inverted_waveform) write(uscita,inverted_zero,9);
      else write(uscita,normal_zero,9);
    }
  }while(count=count>>1);
}

void sync_impulse(int file_desc){
  const unsigned char normal_long[30]={139,162,183,202,217,228,236,238,236,228,217,
				202,183,162,139,117,94,73,54,39,28,20,18,20,
				28,39,54,73,94,117};
  const unsigned char inverted_long[30]={117,94,73,54,39,28,20,18,20,28,
						39,54,73,94,117,139,162,183,
						202,217,228,236,238,236,228,
						217,202,183,162,139};
  const unsigned char medium[24]={141,166,189,207,220,227,227,220,207,189,166,
				  141,115,90,67,49,36,29,29,36,49,67,90,115};
  const unsigned char inverted_medium[24]={115,90,67,49,36,29,29,36,49,67,90,
					   115,141,166,189,207,220,227,227,220,
					   207,189,166,141};
  if (inverted_waveform){
    write(file_desc,inverted_long,30);
    write(file_desc,inverted_medium,24);
  }
  else{
    write(file_desc,normal_long,30);
    write(file_desc,medium,24);
  }
}

void slow_write(unsigned char byte, int file_desc){
  const unsigned char medium[24]={141,166,189,207,220,227,227,220,207,189,166,
				  141,115,90,67,49,36,29,29,36,49,67,90,115};
  const unsigned char inverted_medium[24]={115,90,67,49,36,29,29,36,49,67,90,
					   115,141,166,189,207,220,227,227,220,
					   207,189,166,141};
  const unsigned char normal_short[16]={146,178,203,216,216,203,178,146,110,78,53,40,
				 40,53,78,110};
  const unsigned char inverted_short[16]={110,78,53,40,40,53,78,110,146,178,
					  203,216,216,203,178,146};
  unsigned char count=1;
  int parity=1;
  do{
    if (byte & count){
      parity^=1;
      if (inverted_waveform){
	write(file_desc,inverted_medium,24);
	write(file_desc,inverted_short,16);
      }
      else{
	write(file_desc,medium,24);
	write(file_desc,normal_short,16);
      }
    }
    else{
      if (inverted_waveform){
	write(file_desc,inverted_short,16);
	write(file_desc,inverted_medium,24);
      }
      else{
	write(file_desc,normal_short,16);
	write(file_desc,medium,24);
      }
    }
  } while (count <<= 1);
  if (parity){
      if (inverted_waveform){
	write(file_desc,inverted_medium,24);
	write(file_desc,inverted_short,16);
      }
      else{
	write(file_desc,medium,24);
	write(file_desc,normal_short,16);
      }
    }
    else{
      if (inverted_waveform){
	write(file_desc,inverted_short,16);
	write(file_desc,inverted_medium,24);
      }
      else{
	write(file_desc,normal_short,16);
	write(file_desc,medium,24);
      }
    }
  sync_impulse(file_desc);
}

void lead_in_sequence(int number_of_short_impulses,
		      unsigned char start_of_sequence,
		      int file_desc){
  const unsigned char normal_short[16]={146,178,203,216,216,203,178,146,110,78,53,40,
				 40,53,78,110};
  const unsigned char inverted_short[16]={110,78,53,40,40,53,78,110,146,178,
					  203,216,216,203,178,146};
  int count;
  for (count=0;count<number_of_short_impulses;count++)
    if (inverted_waveform) write(file_desc,inverted_short,16);
    else write(file_desc,normal_short,16);
  if (start_of_sequence){
    sync_impulse(file_desc);
    for (count=start_of_sequence;count>start_of_sequence-9;count--)
      slow_write(count,file_desc);
  }
}

void add_silence(int desc){
  char silence[44100];
  memset(silence,128,44100);
  write(desc,silence,44100);
}

void write_loader(int uscita, char* entry_name){
  const unsigned char header_chunk[]={
    0xd7,0x05,0x90,0xf0,0x04,0xa9,0xff,0x85,0x90,0x4c,
    0xa9,0xf5,0x20,0xbf,0x03,0xc9,0x00,0xf0,0xf9,0x85,
    0xab,0x20,0xed,0x03,0x85,0xc3,0x20,0xed,0x03,0x85,
    0xc4,0x20,0xed,0x03,0x85,0xae,0x20,0xed,0x03,0x85,
    0xaf,0xa0,0xbc,0x20,0xed,0x03,0x88,0xd0,0xfa,0xf0,
    0x2f,0x20,0xbf,0x03,0x20,0xed,0x03,0x84,0x93,0x48,
    0xa9,0x04,0x85,0x01,0x68,0x91,0xc3,0x45,0xd7,0x85,
    0xd7,0xa9,0x07,0x85,0x01,0xe6,0xc3,0xd0,0x02,0xe6,
    0xc4,0xa5,0xc3,0xc5,0xae,0xa5,0xc4,0xe5,0xaf,0x90,
    0xdb,0x20,0xed,0x03,0x20,0x02,0x01,0xc8,0x84,0xc0,
    0x58,0x18,0xa9,0x00,0x8d,0xa0,0x02,0x4c,0x93,0xfc,
    0x20,0x17,0xf8,0x20,0x02,0x01,0x84,0xd7,0xa9,0x07,
    0x8d,0x06,0xdd,0xa2,0x01,0x20,0x16,0x01,0x26,0xbd,
    0xa5,0xbd,0xc9,0x02,0xd0,0xf5,0xa0,0x09,0x20,0xed,
    0x03,0xc9,0x02,0xf0,0xf9,0xc4,0xbd,0xd0,0xe8,0x20,
    0xed,0x03,0x88,0xd0,0xf6,0x60,0xa9,0x08,0x85,0xa3,
    0x20,0x16,0x01,0x26,0xbd,0xee,0x20,0xd0,0xc6,0xa3,
    0xd0};
  const unsigned char data_chunk[]={
    0x0b,0x08,0x00,0x00,0x9e,0x32,0x30,0x36,0x31,0x00,
    0x00,0x00,0xa2,0x05,0xbd,0x8c,0x08,0x9d,0x77,0x02,
    0xca,0x10,0xf7,0xa9,0x06,0x85,0xc6,0xa9,0x03,0x8d,
    0x31,0x03,0xa9,0x3c,0x8d,0x30,0x03,0xa2,0x2a,0xbd,
    0x48,0x08,0x9d,0x02,0x01,0xca,0x10,0xf7,0xa2,0x15,
    0xbd,0x72,0x08,0x9d,0x3b,0x03,0xca,0xd0,0xf7,0xa2,
    0x04,0xbd,0x87,0x08,0x9d,0xfb,0x03,0xca,0xd0,0xf7,
    0x60,0xa0,0x00,0x84,0xc0,0xad,0x11,0xd0,0x29,0xef,
    0x8d,0x11,0xd0,0xca,0xd0,0xfd,0x88,0xd0,0xfa,0x78,
    0x60,0xa9,0x10,0x2c,0x0d,0xdc,0xf0,0xfb,0xad,0x0d,
    0xdd,0x8e,0x07,0xdd,0x48,0xa9,0x19,0x8d,0x0f,0xdd,
    0x68,0x4a,0x4a,0x60,0x85,0x90,0x20,0x5d,0x03,0xa5,
    0xab,0xc9,0x02,0xf0,0x04,0xc9,0x01,0xd0,0xf3,0x20,
    0x84,0x03,0xa5,0xbd,0x45,0xf4,0xa5,0xbd,0x60,0x4c,
    0xcf,0x0d,0x52,0xd5,0x0d};
  int count;
  unsigned char checksum;
  lead_in_sequence(10000,137,uscita);
  slow_write(3,uscita);
  slow_write(1,uscita);
  slow_write(8,uscita);
  slow_write(146,uscita);
  slow_write(8,uscita);
  checksum=3^1^8^146^8;
  for(count=0;count<16;count++){
    slow_write(entry_name[count],uscita);
    checksum^=entry_name[count];
  }
  for(count=0;count<171;count++){
    slow_write(header_chunk[count],uscita);
    checksum^=header_chunk[count];
  }
  slow_write(checksum,uscita);
  lead_in_sequence(300,9,uscita);
  slow_write(3,uscita);
  slow_write(1,uscita);
  slow_write(8,uscita);
  slow_write(146,uscita);
  slow_write(8,uscita);
  for(count=0;count<16;count++)
    slow_write(entry_name[count],uscita);
  for(count=0;count<171;count++)
    slow_write(header_chunk[count],uscita);
  slow_write(checksum,uscita);
  lead_in_sequence(5000,137,uscita);
  checksum=0;
  for(count=0;count<145;count++){
    slow_write(data_chunk[count],uscita);
    checksum^=data_chunk[count];
  }
  slow_write(checksum,uscita);
  lead_in_sequence(300,9,uscita);
  for(count=0;count<145;count++)
    slow_write(data_chunk[count],uscita);
  slow_write(checksum,uscita);
  lead_in_sequence(500,0,uscita);
  add_silence(uscita);
}

void slow_convert(int ingresso,int uscita,int start_address,int end_address,
	     int offset,char* entry_name){
  unsigned char start_address_low,start_address_high,end_address_low,
    end_address_high,byte,checksum=0;
  int count;

  start_address_high=start_address/256;
  start_address_low=start_address-start_address_high*256;
  end_address_high=end_address/256;
  end_address_low=end_address-end_address_high*256;
  lead_in_sequence(10000,137,uscita);
  slow_write(1,uscita);
  slow_write(start_address_low,uscita);
  slow_write(start_address_high,uscita);
  slow_write(end_address_low,uscita);
  slow_write(end_address_high,uscita);
  checksum=1^32^start_address_high^start_address_low^end_address_high^end_address_low;
  for(count=0;count<16;count++){
    slow_write(entry_name[count],uscita);
    checksum^=entry_name[count];
  }
  for(count=0;count<171;count++){
    slow_write(32,uscita);
  }
  slow_write(checksum,uscita);
  lead_in_sequence(300,9,uscita);
  slow_write(1,uscita);
  slow_write(start_address_low,uscita);
  slow_write(start_address_high,uscita);
  slow_write(end_address_low,uscita);
  slow_write(end_address_high,uscita);
  for(count=0;count<16;count++)
    slow_write(entry_name[count],uscita);
  for(count=0;count<171;count++)
    slow_write(32,uscita);
  slow_write(checksum,uscita);
  lead_in_sequence(5000,137,uscita);
  checksum=0;
  lseek(ingresso,offset,SEEK_SET);
  for(count=0;count<end_address-start_address;count++){
    read(ingresso,&byte,1);
    slow_write(byte,uscita);
    checksum=checksum^byte;
  }
  slow_write(checksum,uscita);
  lead_in_sequence(300,9,uscita);
  lseek(ingresso,offset,SEEK_SET);
  for(count=0;count<end_address-start_address;count++){
    read(ingresso,&byte,1);
    slow_write(byte,uscita);
  }
  slow_write(checksum,uscita);
  lead_in_sequence(500,0,uscita);
  add_silence(uscita);
}
  
void convert(int ingresso,int uscita,int start_address,int end_address,
	     int offset,char* entry_name){
  int count;
  unsigned char start_address_low,start_address_high,end_address_low,
    end_address_high,byte;
  unsigned char checksum=0;

  if (kernel_load){
    slow_convert(ingresso, uscita, start_address, end_address, offset, entry_name);
    return;
  }
  if (stand_alone) write_loader(uscita,entry_name);
  start_address_high=start_address/256;
  start_address_low=start_address-start_address_high*256;
  end_address_high=end_address/256;
  end_address_low=end_address-end_address_high*256;
  for(count=1;count<=630;count++)WAV_write(2,uscita);
  for(count=9;count>=1;count--)WAV_write(count,uscita);
  WAV_write(1,uscita);
  WAV_write(start_address_low,uscita);
  WAV_write(start_address_high,uscita);
  WAV_write(end_address_low,uscita);
  WAV_write(end_address_high,uscita);
  WAV_write(0,uscita);
  for(count=0;count<16;count++)WAV_write(entry_name[count],uscita);
  for(count=1;count<=171;count++)WAV_write(32,uscita);
  for(count=1;count<=630;count++)WAV_write(2,uscita);
  for(count=9;count>=1;count--)WAV_write(count,uscita);
  WAV_write(0,uscita);
  lseek(ingresso,offset,SEEK_SET);
  for(count=start_address;count<end_address;count++){
    read(ingresso,&byte,1);
    WAV_write(byte,uscita);
    checksum=checksum^byte;
  }
  WAV_write(checksum,uscita);
  for(count=1;count<=630;count++)WAV_write(0,uscita);
}

void create_WAV(int out){
  char buffer[65536];
  char WAV_header[44]={'R','I','F','F',0,0,0,0,'W','A','V','E','f','m','t',' ',
		       16,0,0,0,1,0,1,0,68,172,0,0,68,172,0,0,1,0,8,0,'d','a',
		       't','a',0,0,0,0};
  int size,size_byte,count;
    lseek(out,0,SEEK_SET);
    size=file_size(out)-8;
    size_byte=size-36;
    for(count=0;count<=3;count++){
      WAV_header[40+count]=size_byte&255;
      WAV_header[4+count]=size&255;
      size_byte>>=8;
      size>>=8;
    };
    write(out,WAV_header,44);
}

/* To determine which files in a .T64 should be converted, the function
   set_range is used. The entries in the range are stored in the list pointed 
   by files_to_convert. It is sorted, and can only contain games which exist
   on the T64. If there are any duplicates in the range, they are not added to
   the list. */

void add_to_list(int count){
  struct entry_element** current=&files_to_convert;
  struct entry_element* new_next;
  while (*current!=0){
    if ((*current)->entry>=count) break;
    current=&((*current)->next);
  }
  if (*current!=0){if ((*current)->entry==count)return;}
  new_next=*current;
  *current=(struct entry_element*)malloc(sizeof(struct entry_element));
  (*current)->entry=count;
  (*current)->next=new_next;
}

void set_range(int lower,int upper,int ingresso){
  int count,start,end,offset;
  char entry_name[16];
  for(count=lower;count<=upper;count++) 
    if (get_entry(count,ingresso,&start,&end,&offset,entry_name))
      add_to_list(count);
    else printf("Entry %d is not used or does not exist\n",count);
}

void empty_list(struct entry_element* files_to_convert){
  if (files_to_convert==0)return;
  if (files_to_convert->next!=0) empty_list(files_to_convert->next);
  free(files_to_convert);
}

/* extract_range scans the string buffer from position *pointerb and copies
   it to the string range until it encounters a comma, an end of string, a
   newline or an invalid character. The above characters are not copied. Thus,
   range only contains digits and '-''s. *pointerb is updated.*/

void extract_range(char* buffer,char* range,int* pointerb,int* end_of_line,int* invalid_character){
  char lettera; 
  int end_of_range=0;
  int pointer=0;
  *end_of_line=*invalid_character=0;
  do{
     lettera=buffer[*pointerb];
     if (isdigit(lettera)||lettera=='-') range[pointer]=lettera;
     else{
       range[pointer]=0;
       switch(lettera){
       case 0:
       case '\n':
	 *end_of_line=1;
	 break;
       case ',':
	 end_of_range=1;
	 break;
       default:
	 printf("Invalid character: %c\n",lettera);
	 *invalid_character=1;
       }
     }
     pointer++;
     (*pointerb)++;
  }while((!*end_of_line)&&(!*invalid_character)&&(!end_of_range));
}   

/* extract_numbers takes the string range (extracted with extract_range),
   gets the lower and upper bounds of the range from it, and sets the list
   files_to_convert accordingly via the set_range function. If range is empty,
   it does nothing. */

void extract_numbers(char*range,int* invalid_character,int ingresso){
  char lettera;
  int pointer=-1;
  int end_pointer;
  int start=0;
  int end=0;
  int max_entries=get_total_entries(ingresso);
  while (isdigit(lettera=range[++pointer])) start=start*10+lettera-48;
  if(!lettera){ /* range is empty or does not contain '-' signs */
    if(!pointer)return; /* empty range */
    if(start<1)start=1;
    if(start>65536)start=65536;
    set_range(start,start,ingresso); /* range is a single number */
    return;
  } /* range contains a '-' sign' */
  if(!pointer)start=1; /* '-' is the first char */
  end_pointer=pointer+1;
  while (isdigit(lettera=range[++pointer])) end=end*10+lettera-48;
  if(lettera){
    printf("Too many '-' signs in range %s\n",range);
    *invalid_character=1;
    return;
  }
  if (pointer==end_pointer) end=max_entries; /* '-' is the last char */
    if(start<1)start=1;
    if(start>65536)start=65536;
    if(end<1)end=1;
    if(end>65536)end=65536;
    if(end<start){
      printf("The range %s is invalid\n",range);
      *invalid_character=1;
      return;
    }
    set_range(start,end,ingresso);
}

void get_range_from_keyboard(int ingresso){
  char buffer[100],range[100];
  int end_of_line,invalid_character;
  int pointer;
  do{
    pointer=0;
    printf("Enter the numbers of the games you want to convert:");
    fgets(buffer,100,stdin);
    do{
      extract_range(buffer,range,&pointer,&end_of_line,&invalid_character);
      if (invalid_character)break;
      extract_numbers(range,&invalid_character,ingresso);
      if (invalid_character)break;
    }while(!end_of_line);
  }while(invalid_character);
}

int main(int numarg,char** argo){
  int opzione=0;
  int show_help=0;
  int show_version=0;
  int list=0;
  int current_argument;
  int ingresso;
  int uscita;
  char* output_file_name=NULL;
  char* end;
  int append_WAV=0;
  int WAV_descriptor;
  filetype type;
  int count,used,total,used_entries;
  int start_address,end_address,offset;
  char entry_name[17];
  struct entry_element* current_file;
  int first_done=0;
  int dsp_output=0;
  int sample_speed;

  /* Get options */

  while ((opzione=getopt(numarg,argo,"hiklo:sv"))!=EOF){
    switch(opzione){
    case 'h':
      show_help=1;
      break;
    case 'i':
      inverted_waveform=1;
      break;
    case 'k':
      kernel_load=1;
      break;
    case 'l':
      list=1;
      break;
    case 'o':
      output_file_name=(char*)malloc(strlen(optarg)+4);
      strcpy(output_file_name,optarg);
      break;
   case 's':
      stand_alone=1;
      break;
   case 'v':
      show_version=1;
      break;
   default:
      help();
      return 1;
    };
  }
  if (show_help==1){
    help();
    return 0;
  };
  if (show_version==1){
    version();
    return 0;
  };

  if ((kernel_load)&&(stand_alone)){
    printf("Options -s and -k cannot be used together\n");
    return 1;
  };

  current_argument=optind;
  if (current_argument==numarg){
    printf("No input files specified!\n");
    help();
    return 1;
  }
  if (!list){

    if (output_file_name==NULL){ /* -o is not used */
#ifdef MSDOS
       printf("You must specify the output file with the -o option!\n");
       return 1;
    }
#else
       if ((uscita=open(SOUNDDEV,O_WRONLY))==-1){
	 printf("Could not open sound device %s\n",SOUNDDEV);
	 return 3;
       }
       sample_speed = 44100;
       if (ioctl(uscita, SNDCTL_DSP_SPEED, &sample_speed) != 0 || sample_speed != 44100){
	 printf("Could not set playback speed to 44100 Hz in sound device %s\n",SOUNDDEV);
	 return 3;
       }
       dsp_output=1;
    }
#endif
    else{
      if (strlen(output_file_name)<5) append_WAV=1;
      else{
	end=output_file_name+strlen(output_file_name)-4;
	if((strcmp(end,".WAV"))&&(strcmp(end,".wav"))) append_WAV=1;
      };
      if (append_WAV) strcat(output_file_name,".wav");
#ifdef MSDOS
      if((uscita=open(output_file_name,O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))==-1){
#else
      if((uscita=open(output_file_name,O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))==-1){
#endif
	printf("Could not create file %s\n",output_file_name);
	return 5;
      };
      lseek(uscita,44,SEEK_SET);
    }
  }
      
  while (current_argument<numarg){
#ifdef MSDOS
    if ((ingresso=open(argo[current_argument],O_RDONLY|O_BINARY))==-1){
#else
    if ((ingresso=open(argo[current_argument],O_RDONLY))==-1){
#endif
      printf("Could not open file %s\n",argo[current_argument]);
      goto fine;
    };
    switch(type=detect_type(ingresso)){
    case t64:
      printf("%s: T64 file\n",argo[current_argument]);
      break;
    case p00:
      printf("%s: P00 file\n",argo[current_argument]);
      break;
    case prg:
      printf("%s: program file\n",argo[current_argument]);
      break;
    default:
      printf("%s is not a recognized file type\n",argo[current_argument]);
      goto fine;
    };
    if (list){
      list_contents(ingresso);
      goto fine;
    };
    switch(type){
    case t64:
      used_entries=get_used_entries(ingresso);
      total=get_total_entries(ingresso);
      empty_list(files_to_convert);
      files_to_convert=0;
      if (used_entries==1){
	for(count=1;count<=total;count++)
	  if(used=get_entry(count,ingresso,&start_address,&end_address,&offset,
			    entry_name)) set_range(count,count,ingresso);
      }

      /* If there is only one used entry, */
      /* Only the used one will be */
      /* converted */
      /* otherwise ask the user */

      else get_range_from_keyboard(ingresso); 
      current_file=files_to_convert;
      while(current_file!=0){
	count=current_file->entry;
	get_entry(count,ingresso,&start_address,&end_address,&offset,
		  entry_name);
	if (!first_done) first_done=1;
	else add_silence(uscita);
	printf("Converting %d (%s)\n",count,entry_name);
	convert(ingresso,uscita,start_address,end_address,offset,entry_name);
	current_file=current_file->next;
      }
      break;
    case p00:
    case prg:
      if (!first_done) first_done=1;
      else add_silence(uscita);
      printf("Converting %s\n",argo[current_argument]);
      get_entry(count,ingresso,&start_address,&end_address,&offset,
		entry_name);
      convert(ingresso,uscita,start_address,end_address,offset,entry_name);
    };

  fine:   
    ++current_argument;
  };
  if((!list)&&(!dsp_output)){
    if(first_done){
      create_WAV(uscita);
      close(uscita);
    }
    else{
      close(uscita);
      unlink(output_file_name);
    }
  }
}
