/* p2bin.c */
/*****************************************************************************/
/* AS-Portierung                                                             */
/*                                                                           */
/* Umwandlung von AS-Codefiles in Binaerfiles                                */
/*                                                                           */
/* Historie:  3. 6.1996 Grundsteinlegung                                     */
/*           30. 5.1999 0x statt $ erlaubt                                   */
/*                                                                           */
/*****************************************************************************/

#include "stdinc.h"
#include <string.h>
#include <ctype.h>

#include "version.h"
#include "endian.h"
#include "bpemu.h"
#include "strutil.h"
#include "hex.h"
#include "nls.h"
#include "nlmessages.h"
#include "p2bin.rsc"
#include "ioerrs.h"
#include "chunks.h"
#include "cmdarg.h"
#include "toolutils.h"

#define BinSuffix ".bin"


typedef void (*ProcessProc)(
#ifdef __PROTOS__
char *FileName, LongWord Offset
#endif
);


static CMDProcessed ParProcessed;

static FILE *TargFile;
static String SrcName,TargName;

static LongWord StartAdr,StopAdr,EntryAdr,RealFileLen;
static LongWord MaxGran,Dummy;
static Boolean StartAuto,StopAuto,AutoErase,EntryAdrPresent;

static Byte FillVal;
static Boolean DoCheckSum;

static Byte SizeDiv;
static LongInt ANDMask,ANDEq;
static ShortInt StartHeader;

static ChunkList UsedList;


#ifdef DEBUG
#define ChkIO(s) ChkIO_L(s,__LINE__)

	static void ChkIO_L(char *s, int line)
BEGIN
   if (errno!=0)
    BEGIN
     fprintf(stderr,"%s %d\n",s,line); exit(3);
    END
END
#endif

        static void ParamError(Boolean InEnv, char *Arg)
BEGIN
   printf("%s%s\n%s\n",getmessage((InEnv)?Num_ErrMsgInvEnvParam:Num_ErrMsgInvParam),Arg,getmessage(Num_ErrMsgProgTerm));
   exit(1);
END

#define BufferSize 4096
static Byte Buffer[BufferSize];

        static void OpenTarget(void)
BEGIN
   LongWord Rest,Trans;

   TargFile=fopen(TargName,OPENWRMODE);
   if (TargFile==Nil) ChkIO(TargName); 
   RealFileLen=((StopAdr-StartAdr+1)*MaxGran)/SizeDiv;

   if (StartHeader!=0)
    BEGIN
     memset(Buffer,0,abs(StartHeader));
     if (fwrite(Buffer,1,abs(StartHeader),TargFile)!=abs(StartHeader)) ChkIO(TargName);
    END

   memset(Buffer,FillVal,BufferSize);

   Rest=RealFileLen;
   while (Rest!=0)
    BEGIN
     Trans=min(Rest,BufferSize);
     if (fwrite(Buffer,1,Trans,TargFile)!=Trans) ChkIO(TargName);
     Rest-=Trans;
    END
END

	static void CloseTarget(void)
BEGIN
   LongWord Sum,Rest,Trans,Real,z,bpos;

   if ((EntryAdrPresent) AND (StartHeader!=0))
    BEGIN
     rewind(TargFile); 
     bpos=((StartHeader>0) ? 0 : -1-StartHeader)<<3;
     for (z=0; z<abs(StartHeader); z++)
      BEGIN
       Buffer[z]=(EntryAdr >> bpos) & 0xff;
       bpos += (StartHeader>0) ? 8 : -8;
      END
     if (fwrite(Buffer,1,abs(StartHeader),TargFile)!=abs(StartHeader)) ChkIO(TargName);
    END

   if (fclose(TargFile)==EOF) ChkIO(TargName);

   if (DoCheckSum)
    BEGIN
     TargFile=fopen(TargName,OPENUPMODE); if (TargFile==Nil) ChkIO(TargName);
     if (fseek(TargFile,abs(StartHeader),SEEK_SET)==-1) ChkIO(TargName);
     Rest=FileSize(TargFile)-1;
     Sum=0;
     while (Rest!=0)
      BEGIN
       Trans=min(Rest,BufferSize);
       Rest-=Trans;
       Real=fread(Buffer,1,Trans,TargFile);
       if (Real!=Trans) ChkIO(TargName);
       for (z=0; z<Trans; Sum+=Buffer[z++]);
      END
     errno=0; printf("%s%s\n",getmessage(Num_InfoMessChecksum),HexLong(Sum));
     Buffer[0]=0x100-(Sum&0xff); fflush(TargFile);
     if (fwrite(Buffer,1,1,TargFile)!=1) ChkIO(TargName); fflush(TargFile);
     if (fclose(TargFile)==EOF) ChkIO(TargName);
    END

   if (Magic!=0) unlink(TargName);
END

        static void ProcessFile(char *FileName, LongWord Offset)
BEGIN
   FILE *SrcFile;
   Word TestID;
   Byte InpHeader,InpSegment;
   LongWord InpStart,SumLen;
   Word InpLen,TransLen,ResLen;
   Boolean doit;
   LongWord ErgStart,ErgStop,NextPos;
   Word ErgLen=0;
   LongInt z;
   Byte Gran;

   SrcFile=fopen(FileName,OPENRDMODE);
   if (SrcFile==Nil) ChkIO(FileName);

   if (NOT Read2(SrcFile,&TestID)) ChkIO(FileName);
   if (TestID!=FileID) FormatError(FileName,getmessage(Num_FormatInvHeaderMsg));

   errno=0; printf("%s==>>%s",FileName,TargName); ChkIO(OutName);

   SumLen=0;

   do
    BEGIN
     ReadRecordHeader(&InpHeader,&InpSegment,&Gran,FileName,SrcFile);
     if (InpHeader==FileHeaderStartAdr)
      BEGIN
       if (NOT Read4(SrcFile,&ErgStart)) ChkIO(FileName);
       if (NOT EntryAdrPresent)
        BEGIN
         EntryAdr=ErgStart; EntryAdrPresent=True;
        END
      END
     else if (InpHeader!=FileHeaderEnd)
      BEGIN
       if (NOT Read4(SrcFile,&InpStart)) ChkIO(FileName);
       if (NOT Read2(SrcFile,&InpLen)) ChkIO(FileName);

       NextPos=ftell(SrcFile)+InpLen;
       if (NextPos>=FileSize(SrcFile)-1)
        FormatError(FileName,getmessage(Num_FormatInvRecordLenMsg));

       doit=(FilterOK(InpHeader) AND (InpSegment==SegCode));

       if (doit)
        BEGIN
         InpStart+=Offset;
 	 ErgStart=max(StartAdr,InpStart);
 	 ErgStop=min(StopAdr,InpStart+(InpLen/Gran)-1);
 	 doit=(ErgStop>=ErgStart);
         if (doit)
 	  BEGIN
 	   ErgLen=(ErgStop+1-ErgStart)*Gran;
           if (AddChunk(&UsedList,ErgStart,ErgStop-ErgStart+1,True))
            BEGIN
             errno=0; printf(" %s\n",getmessage(Num_ErrMsgOverlap)); ChkIO(OutName);
            END
          END
        END

       if (doit)
        BEGIN
 	 /* an Anfang interessierender Daten */

 	 if (fseek(SrcFile,(ErgStart-InpStart)*Gran,SEEK_CUR)==-1) ChkIO(FileName);

 	 /* in Zieldatei an passende Stelle */

 	 if (fseek(TargFile,(((ErgStart-StartAdr)*Gran)/SizeDiv)+abs(StartHeader),SEEK_SET)==-1) ChkIO(TargName);

 	 /* umkopieren */

 	 while (ErgLen>0)
 	  BEGIN
 	   TransLen=min(BufferSize,ErgLen);
 	   if (fread(Buffer,1,TransLen,SrcFile)!=TransLen) ChkIO(FileName);
 	   if (SizeDiv==1) ResLen=TransLen;
 	   else
 	    BEGIN
 	     ResLen=0;
 	     for (z=0; z<(LongInt)TransLen; z++)
 	      if (((ErgStart*Gran+z)&ANDMask)==ANDEq)
 	       Buffer[ResLen++]=Buffer[z];
 	    END
 	   if (fwrite(Buffer,1,ResLen,TargFile)!=ResLen) ChkIO(TargName);
 	   ErgLen-=TransLen; ErgStart+=TransLen; SumLen+=ResLen;
 	  END
        END
       if (fseek(SrcFile,NextPos,SEEK_SET)==-1) ChkIO(FileName);
      END
    END
   while (InpHeader!=0);

   errno=0; printf("  (%d Byte)\n",SumLen); ChkIO(OutName);

   if (fclose(SrcFile)==EOF) ChkIO(FileName);
END

static ProcessProc CurrProcessor;
static LongWord CurrOffset;

	static void Callback(char *Name)
BEGIN
   CurrProcessor(Name,CurrOffset);
END

	static void ProcessGroup(char *GroupName_O, ProcessProc Processor)
BEGIN
   String Ext,GroupName;

   CurrProcessor=Processor;
   strmaxcpy(GroupName,GroupName_O,255); strmaxcpy(Ext,GroupName,255);
   if (NOT RemoveOffset(GroupName,&CurrOffset)) ParamError(False,Ext);
   AddSuffix(GroupName,getmessage(Num_Suffix));

   if (NOT DirScan(GroupName,Callback))
    fprintf(stderr,"%s%s%s\n",getmessage(Num_ErrMsgNullMaskA),GroupName,getmessage(Num_ErrMsgNullMaskB));
END

        static void MeasureFile(char *FileName, LongWord Offset)
BEGIN
   FILE *f;
   Byte Header,Gran,Segment;
   Word Length,TestID;
   LongWord Adr,EndAdr,NextPos;

   f=fopen(FileName,OPENRDMODE);
   if (f==Nil) ChkIO(FileName);

   if (NOT Read2(f,&TestID)) ChkIO(FileName); 
   if (TestID!=FileMagic) FormatError(FileName,getmessage(Num_FormatInvHeaderMsg));

   do
    BEGIN 
     ReadRecordHeader(&Header,&Segment,&Gran,FileName,f);

     if (Header==FileHeaderStartAdr)
      BEGIN
       if (fseek(f,sizeof(LongWord),SEEK_CUR)==-1) ChkIO(FileName);
      END
     else if (Header!=FileHeaderEnd)
      BEGIN
       if (NOT Read4(f,&Adr)) ChkIO(FileName);
       if (NOT Read2(f,&Length)) ChkIO(FileName);
       NextPos=ftell(f)+Length;
       if (NextPos>FileSize(f))
        FormatError(FileName,getmessage(Num_FormatInvRecordLenMsg));

       if (FilterOK(Header) AND (Segment==SegCode))
        BEGIN
         Adr+=Offset;
 	 EndAdr=Adr+(Length/Gran)-1;
         if (Gran>MaxGran) MaxGran=Gran;
         if (StartAuto) if (StartAdr>Adr) StartAdr=Adr;
 	 if (StopAuto) if (EndAdr>StopAdr) StopAdr=EndAdr;
        END

       fseek(f,NextPos,SEEK_SET);
      END
    END
   while(Header!=0);

   if (fclose(f)==EOF) ChkIO(FileName);
END

	static CMDResult CMD_AdrRange(Boolean Negate, char *Arg)
BEGIN
   char *p,Save;
   Boolean err;

   if (Negate)
    BEGIN
     StartAdr=0; StopAdr=0x7fff;
     return CMDOK;
    END
   else
    BEGIN
     p=strchr(Arg,'-'); if (p==Nil) return CMDErr;

     Save=(*p); *p='\0'; 
     if ((StartAuto = AddressWildcard(Arg))) err = True;
     else StartAdr = ConstLongInt(Arg, &err);
     *p = Save;
     if (NOT err) return CMDErr;

     if ((StopAuto = AddressWildcard(p + 1))) err = True;
     else StopAdr = ConstLongInt(p+1, &err);
     if (NOT err) return CMDErr;

     if ((NOT StartAuto) AND (NOT StopAuto) AND (StartAdr>StopAdr)) return CMDErr;

     return CMDArg;
    END
END

	static CMDResult CMD_ByteMode(Boolean Negate, char *Arg)
BEGIN
#define ByteModeCnt 9
   static char *ByteModeStrings[ByteModeCnt]={"ALL","EVEN","ODD","BYTE0","BYTE1","BYTE2","BYTE3","WORD0","WORD1"};
   static Byte ByteModeDivs[ByteModeCnt]={1,2,2,4,4,4,4,2,2};
   static Byte ByteModeMasks[ByteModeCnt]={0,1,1,3,3,3,3,2,2};
   static Byte ByteModeEqs[ByteModeCnt]={0,0,1,0,1,2,3,0,2};

   int z;

   if (*Arg=='\0')
    BEGIN
     SizeDiv=1; ANDEq=0; ANDMask=0;
     return CMDOK;
    END
   else
    BEGIN
     for (z=0; z<strlen(Arg); z++) Arg[z]=toupper(Arg[z]);
     ANDEq=0xff;
     for (z=0; z<ByteModeCnt; z++)
      if (strcmp(Arg,ByteModeStrings[z])==0)
       BEGIN
        SizeDiv=ByteModeDivs[z];
        ANDMask=ByteModeMasks[z];
        ANDEq  =ByteModeEqs[z];
       END
      if (ANDEq==0xff) return CMDErr; else return CMDArg;
    END
END

	static CMDResult CMD_StartHeader(Boolean Negate, char *Arg)
BEGIN
   Boolean err;
   ShortInt Sgn;
   
   if (Negate)
    BEGIN
     StartHeader=0; return CMDOK;
    END
   else
    BEGIN
     Sgn=1; if (*Arg=='\0') return CMDErr;
     switch (toupper(*Arg))
      BEGIN
       case 'B': Sgn=(-1);
       case 'L': Arg++;
      END
     StartHeader=ConstLongInt(Arg,&err);
     if ((NOT err) OR (StartHeader>4)) return CMDErr;
     StartHeader*=Sgn;
     return CMDArg;
    END
END	

	static CMDResult CMD_EntryAdr(Boolean Negate, char *Arg)
BEGIN
   Boolean err;
   
   if (Negate)
    BEGIN
     EntryAdrPresent=False; return CMDOK;
    END
   else
    BEGIN
     EntryAdr=ConstLongInt(Arg,&err);
     return (err) ? CMDArg : CMDErr;
    END
END	

	static CMDResult CMD_FillVal(Boolean Negate, char *Arg)
BEGIN
   Boolean err;

   FillVal=ConstLongInt(Arg,&err);
   if (NOT err) return CMDErr; else return CMDArg;
END

	static CMDResult CMD_CheckSum(Boolean Negate, char *Arg)
BEGIN
   DoCheckSum=NOT Negate;
   return CMDOK;
END

	static CMDResult CMD_AutoErase(Boolean Negate, char *Arg)
BEGIN
   AutoErase=NOT Negate;
   return CMDOK;
END

#define P2BINParamCnt 8
static CMDRec P2BINParams[P2BINParamCnt]=
	       {{"f", CMD_FilterList},
		{"r", CMD_AdrRange},
		{"s", CMD_CheckSum},
		{"m", CMD_ByteMode},
		{"l", CMD_FillVal},
		{"e", CMD_EntryAdr},
		{"S", CMD_StartHeader},
		{"k", CMD_AutoErase}};

	int main(int argc, char **argv)	
BEGIN
   int z;
   char *ph1,*ph2;
   String Ver;

   ParamStr=argv; ParamCount=argc-1;

   nls_init(); NLS_Initialize();

   endian_init();
   strutil_init();
   bpemu_init();
   hex_init();
   nlmessages_init("p2bin.msg",*argv,MsgId1,MsgId2); ioerrs_init(*argv);
   chunks_init();
   cmdarg_init(*argv);
   toolutils_init(*argv);

   sprintf(Ver,"P2BIN/C V%s",Version);
   WrCopyRight(Ver);

   InitChunk(&UsedList);

   if (ParamCount==0)
    BEGIN
     errno=0; printf("%s%s%s\n",getmessage(Num_InfoMessHead1),GetEXEName(),getmessage(Num_InfoMessHead2)); ChkIO(OutName);
     for (ph1=getmessage(Num_InfoMessHelp),ph2=strchr(ph1,'\n'); ph2!=Nil; ph1=ph2+1,ph2=strchr(ph1,'\n'))
      BEGIN
       *ph2='\0';
       printf("%s\n",ph1);
       *ph2='\n';
      END
     exit(1);
    END

   StartAdr=0; StopAdr=0x7fff; StartAuto=False; StopAuto=False;
   FillVal=0xff; DoCheckSum=False; SizeDiv=1; ANDEq=0;
   EntryAdr=(-1); EntryAdrPresent=False; AutoErase=False;
   StartHeader=0;
   ProcessCMD(P2BINParams,P2BINParamCnt,ParProcessed,"P2BINCMD",ParamError);

   if (ProcessedEmpty(ParProcessed))
    BEGIN
     errno=0;
     printf("%s\n",getmessage(Num_ErrMsgTargMissing));
     ChkIO(OutName);
     exit(1);
    END

   z=ParamCount;
   while ((z>0) AND (NOT ParProcessed[z])) z--;
   strmaxcpy(TargName,ParamStr[z],255);
   if (NOT RemoveOffset(TargName,&Dummy)) ParamError(False,ParamStr[z]);
   ParProcessed[z]=False;
   if (ProcessedEmpty(ParProcessed))
    BEGIN
     strmaxcpy(SrcName,ParamStr[z],255); DelSuffix(TargName);
    END
   AddSuffix(TargName,BinSuffix);

   MaxGran=1;
   if ((StartAuto) OR (StopAuto))
    BEGIN
#ifdef __STDC__
     if (StartAuto) StartAdr=0xffffffffu;
#else
     if (StartAuto) StartAdr=0xffffffff;
#endif
     if (StopAuto) StopAdr=0;
     if (ProcessedEmpty(ParProcessed)) ProcessGroup(SrcName,MeasureFile);
     else for (z=1; z<=ParamCount; z++)
       if (ParProcessed[z]) ProcessGroup(ParamStr[z],MeasureFile);
     if (StartAdr>StopAdr)
      BEGIN
       errno=0; printf("%s\n",getmessage(Num_ErrMsgAutoFailed)); ChkIO(OutName); exit(1);
      END
    END

   OpenTarget();

   if (ProcessedEmpty(ParProcessed)) ProcessGroup(SrcName,ProcessFile);
   else for (z=1; z<=ParamCount; z++)
    if (ParProcessed[z]) ProcessGroup(ParamStr[z],ProcessFile);

   CloseTarget(); 

   if (AutoErase)
    BEGIN
     if (ProcessedEmpty(ParProcessed)) ProcessGroup(SrcName,EraseFile);
     else for (z=1; z<=ParamCount; z++)
      if (ParProcessed[z]) ProcessGroup(ParamStr[z],EraseFile);
    END

   return 0;
END
