// BINMIX ver 1.1 - the program for preparing EPROM contents, when data 
// and/or address lines in the microprocessor system are mixed.
// Written and (c) by Wojciech M. Zabolotny (wzab@ise.pw.edu.pl) 30 Aug 1999
//
// Binmix 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 1, or (at your option)
// any later version.
// (see http://www.gnu.org/copyleft/gpl.html for details)
 
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
typedef unsigned char uchar;
typedef unsigned int uint;

char * InFileName = NULL;
char * OutFileName = NULL;
char * CfgFileName = NULL;
uchar FileBuf[10000];
short int Address[16],Data[8];
uint ConvAdr[16],ConvData[8];

//The function below reads the old configuration files (from DOS version)
void ReadOldCfg(char * CfgFileName)
 {
 int File=open(CfgFileName,O_RDONLY);
 if(File == -1) 
   {
     fprintf(stderr,"I can't read configuration from %s\n",CfgFileName);
     exit(1);
   }
 read(File,Address,sizeof(Address));
 read(File,Data,sizeof(Data));
 close(File);
 }

//The function below processes the config token
void ProcessToken(char * Token)
{
  int i,j;
  if(sscanf(Token,"%*[aAdD]%d:%d",&i,&j) != 2)
    {
      fprintf(stderr,
	      "Erroneous config: %s\n",Token);
      exit(1);
    }
  switch(Token[0])
    {
    case 'a':
    case 'A':
      if((i<0) || (i>15) || (j<0) || (j>15))
	{
	  fprintf(stderr,
		  "Wrong address line number in token %s\n",
		  Token);
	  exit(1);
	}
      Address[i]=j;
      break;
    case 'd':
    case 'D':
      if((i<0) || (i>8) || (j<0) || (j>8))
	{
	  fprintf(stderr,
		  "Wrong data line number in token %s\n",
		  Token);
	  exit(1);
	}
      Data[i]=j;
      break;
    }
}

//The function below reads the new configuration file (text format)
//The file contains the lines with format Axx:yy and Dxx:yy, defining
//the connection between the uP and Eprom lines
void ReadCfg(char * CfgFileName)
{
  uchar Line[256]; 
  FILE * CfgFile = NULL;
  int i,j;
  CfgFile = fopen(CfgFileName,"rt");
  if(!CfgFile) 
    {
      fprintf(stderr,"I can't read configuration form %s\n",CfgFileName);
      exit(1);
    }
  for(;;)
    {
      if(fscanf(CfgFile,"%255s",Line)!=1)
	{
	  if(feof(CfgFile))
	    {
	      break;
	    }
	  else 
	    {
	      fprintf(stderr,"Error reading the configuration file %s\n",CfgFileName);
	      exit(1);
	    }
	}
      ProcessToken(Line);
    }
}

//Function calculating the data and the address
void Conv(uint * addr,uchar * data)
{
  uint i;
  uint naddr=0;
  uchar ndata=0;
  for (i=0;i<16;i++)
    {
      if((*addr) & (1<<i)) naddr |= ConvAdr[i];
    }
  for (i=0;i<8;i++)
    {
      if((*data) & (1<<i)) ndata |= ConvData[i];
    }
  *addr=naddr;
  *data=ndata;
}

//This function performs the real processing of the data
int Process(void)
{
  int i;
  uint j;
  uint Test[16];
  FILE * FileIn=NULL;
  FILE * FileOut=NULL;
  uchar * BufOut;
  int LastByte=-1; //Last byte marker
  //First we generate the data and address conversion tables
  //Generate the address conversion table
  for (i=0;i<16;i++) Test[i]=0;
  for (i=0;i<16;i++)
    {
      j=Address[i];
      if(j<0 || j>15)
	{
	  fprintf(stderr,"Wrong address line number: %d !\n",j);
	  exit(1);
	}
      if(Test[j])
	{
	  fprintf(stderr,"Two address lines shorted together!\n");
	  exit(1);
	}
      Test[j]=1;
      ConvAdr[i]=(1 << j);
    }
  //Generate the data conversion table
  for (i=0;i<16;i++) Test[i]=0;
  for (i=0;i<8;i++)
    {
      j=Data[i];
      if(j<0 || j>7)
	{
	  fprintf(stderr,"Wrong address line number: %d !\n",j);
	  exit(1);
	}
      if(Test[j])
	{
	  fprintf(stderr,"Two data lines shorted together !\n");
	  exit(1);
	}
      Test[j]=1;
      ConvData[i]=(1 << j);
    }
  //We open the input file, or use the stdin
  if(InFileName)
    {
      FileIn=fopen(InFileName,"rt");
      if(!FileIn)
	{
	  fprintf(stderr,"I can't open the input file: %s\n",InFileName);
	  exit(1);
	} 
      setvbuf(FileIn,FileBuf,_IOFBF,sizeof(FileBuf));
    }
  else
    FileIn = stdin;
  //We allocate the output data buffer
  BufOut = (uchar *) malloc(65536);
  if(!BufOut)
    {
      fprintf(stderr,"I can't allocate the data buffer!\n");
      exit(1);
    }
  //We fill the output buffer with 0xff
  for(i=0;i<65536;i++) BufOut[i] = 0xff;
  //We set the last byte marker
  LastByte=-1;
  //And now the real conversion:
  for(i=0;;i++)
    {
      uchar cbuf;
      int address;
      if(1!=fread(&cbuf,1,1,FileIn))
	{
	  if(feof(FileIn))
	    {
	      break;
	    }
	  else
	    {
	      fprintf(stderr,"Error reading the %d-th byte from %s\n",i,InFileName);
	      exit(1);
	    }
	}
      address=i;
      Conv(&address,&cbuf);
      BufOut[address]=cbuf;
      if(LastByte < address) LastByte=address;
    }
  if(InFileName) fclose(FileIn); //Do not close stdin
  if(OutFileName)
    {
      FileOut=fopen(OutFileName,"wt");
      if(!FileOut)
	{
	  fprintf(stderr,"I can't open the output file: %s\n",OutFileName);
	  exit(1);
	}
    }
  else
    FileOut = stdout;
  //We change the number of the lest byte into the amount of bytes
  LastByte++;
  //We adjust the amount of bytes to n*128
  LastByte=128*((LastByte+127)/128);
  if(LastByte!=fwrite(BufOut,1,LastByte,FileOut))
    {
      fprintf(stderr,"Error writing the output file: %s\n",OutFileName);
      exit(1);
    }
  free(BufOut);
  if(OutFileName) fclose(FileOut); //Do not close stdout
  return 0; 
}

void ReadFileName(char * Line, char * * FileName)
{
  if (*FileName) free(*FileName);
  *FileName = malloc(strlen(Line)+1);
  if(!(*FileName)) 
    {
      fprintf(stderr,"I can't allocate the filename buffer\n");
      exit(1);
    }
  sscanf(Line,"%s",*FileName);
}

void ExplainUsage()
{
  fprintf(stdout,
	  "BINMIX 1.1 (c) Wojciech Zabolotny (wzab@ise.pw.edu.pl) 30.07.1999\n"
	  "Free software, available under the GPL license\n"
	  "\n"
	  "Typical usage: binmix -i input_file -o output_file -c config_file\n"
	  "alternative form: binmix -c config_file < input_file > output_file\n"
	  "\n"
	  "Command line options:\n"
	  "-i input_file or -iinput_file\n"
	  "  This option specifies the file from which the binary data are read.\n"
	  "  If this option is omitted, stdin is used.\n"
	  "\n"
	  "-o output_file or -ooutput_file\n"
	  "  This option specifies the file where the converted binary data are stored.\n"
	  "  If this option is omitted, stdout is used.\n"
	  "\n"
	  "-c conf_file or -cconf_file\n"
	  "  This option specifies the configuration file, from which the A/D lines\n"
	  "  configuration is read.\n"
	  "\n"
	  "-f conf_file or -fconf_file\n"
	  "  This option specifies the old format configuration file, from which\n"
	  "  the A/D lines configuration is read.\n"
	  "  (only for compatibility with the old DOS version).\n"
	  "\n"
	  "Instead of using the configuration file, the A/D lines configuration\n"
	  "may be specified with the -a and -d options:\n"
	  "-aX:Y (where 0<=X<=15 and 0<=Y<=15) informs, that X-th adress line\n"
	  "of processor is connected to the Y-th address line of EPROM.\n"
	  "-dX:Y (where 0<=X<=7 and 0<=Y<=7) informs, that X-th data line\n"
	  "of processor is connected to the Y-th data line of EPROM.\n"
	  "\n"
	  "Format of the configuration file:\n"
	  "The configuration file contains lines with following format:\n"
	  "aX:Y\n"
	  "dX:Y\n"
	  "The meaning and limits of X and Y is the same as in \"-a\" and \"-d\"\n"
	  "options, described above.\n"
	  "The processor lines not specified in the configuration file/options\n"
	  "are assumed to be connected to the same lines of EPROM.\n"
	  "\n"
	  "All options are case insensitive.\n"
	  );
  exit(1);
}

void ReadCommandLine(int argc, char * argv[])
{
  int i;
  if(argc==1) ExplainUsage();
  for(i=1;i<argc;i++)
    {
      char option;
      char * arg=NULL;
      if (argv[i][0] != '-')
	{
	  ExplainUsage();
	}
      option=argv[i][1];
      switch(option)
	{
	  //The filenames options are grouped together, because the
	  //preprocessing is similar
	case 'i': case 'I': case 'o': case 'O':
	case 'f': case 'F': case 'c': case 'C':
	  //Check if the filename is separated from the option
	  if(!argv[i][2])
	    {
	      //The filename is in the next argument
	      i++;
	      arg = argv[i];
	    }
	  else
	    arg=&(argv[i][2]);
	  switch(option)
	    {
	    case 'i': case 'I':
	      ReadFileName(arg,&InFileName);
	      break;
	    case 'o': case 'O':
	      ReadFileName(arg,&OutFileName);
	      break;
	    case 'c': case 'C':  
	      ReadFileName(arg,&CfgFileName);
	      ReadCfg(CfgFileName);
	      break;
	    case 'f': case 'F':  
	      ReadFileName(arg,&CfgFileName);
	      ReadOldCfg(CfgFileName);
	      break;
	    }
	  break;
	case 'a': case 'A': case 'd': case 'D':
	  ProcessToken(&(argv[i][1]));
	  break;
	}
    }
}

void main(int argc, char * argv[])
{
  //First we fill the data and address tables with the default values:
  int i;
  for (i=0;i<8;i++) Data[i]=i;
  for (i=0;i<16;i++) Address[i]=i;
  //Then we analyze the command line arguments
  ReadCommandLine(argc,argv);
  //Now let's process the data
  Process();
  //Now a small cleanup
  if(InFileName) free(InFileName);
  if(OutFileName) free(OutFileName);
  if(CfgFileName) free(CfgFileName);
}
