/*************************************************************************/
/*                                                                       */
/*                     LZW.C Compression de fichiers                     */
/*              Méthode Lempel-Ziv Welch variable à 15 bits              */
/*  Compresseur de fichiers V1.00 CopyRight Christian BRUNON 15-09-1993  */
/*                                                                       */
/*             +------------------------------------------+              */
/*             | Logiciel placé dans le domaine SHAREWARE |              */
/*             +------------------------------------------+              */
/* Conformément à la législation concernant les logiciels placés dans le */
/* domaine SHAREWARE, la diffusion de ce logiciel est libre. L'auteur ne */
/*  demande, en cas d'utilisation, qu'une modique contribution de 50 FF  */
/* ou 90 FF en monnaie étrangère ( dollars, marks, franc suisses, ... )  */
/*                            à adresser à :                             */
/*                                                                       */
/*                           Christian BRUNON                            */
/*                        30 Rue Georges Brassens                        */
/*                      43140 LA SEAUVE SUR SEMENE                       */
/*                                FRANCE                                 */
/*                                                                       */
/*            Fichier source Lattice C AmigaDOS Version 5.04             */
/*                                                                       */
/*************************************************************************/

#define strlen __builtin_strlen
#define printf __builtin_printf
#define strcpy __builtin_strcpy
extern int printf(char *,...);
extern char *strcat(char *, char *);
extern int strlen(char *);
extern char *strcpy(char *, char *);

extern struct DosLibrary *DOSBase;
extern struct ExecBase   *SysBase;

/* Fichiers INCLUDE */
/*#include "CPRG:LZ.H"*/

/* Définitions de pseudo-types */
#define BPTR  long
#define BCPL  long
#define UBYTE unsigned char
#define LONG  long
#define ULONG unsigned long
/* ATTENTION une variable de type "int" est codée sur 4 octets !!! */
/* Une variable de type "short" est codée sur 2 octets.            */
#define UWORD unsigned short

/* Pointeur sur une structure FileInfoBlock */
#define PtrFIB (struct FileInfoBlock *)

#define NULL 0L

/* Drapeaux de compression */
#define COMP_LZW     0X8000
/*
#define COMP_HUF     0X4000
*/
#define COMP_LZW_HUF 0XC000

/* VARIABLES GLOBALES */
BPTR FIn,  /* Fichier à compresser */
     FOut; /* Fichier compressé    */

UBYTE NomFicIn [60], /* Nom du fichier à compresser */
      NomFicOut[60], /* Nom du fichier compressé    */
      CodeErreur=0;  /* Code d'erreur               */

/* Structure utilisée par la structure FileInfoBlock */
struct DateStamp
  {
   LONG ds_Days,
        ds_Minute,
        ds_Tick;
  };

/* Structure utilisée pour Examine(), doit étre alignée sur une adresse divisible par 4 */
struct FileInfoBlock
  {
   LONG   fib_DiskKey;
   LONG   fib_DirEntryType;  /* Type of Directory. If < 0, then a plain file. If > 0 a directory */
   char   fib_FileName[108]; /* Null terminated. Max 30 chars used for now */
   LONG   fib_Protection;    /* bit mask of protection, rwed are 3-0.      */
   LONG   fib_EntryType;
   LONG   fib_Size;          /* Number of bytes in file */
   LONG   fib_NumBlocks;     /* Number of blocks in file */
   struct DateStamp fib_Date;/* Date file last changed */
   char   fib_Comment[80];   /* Null terminated comment associated with file */
   char   fib_Reserved[36];
  };

ULONG LongFicOut=0L, LongFicIn;

/* CONSTANTES DE CODES D'ERREURS */
#define SyntaxError        1
#define FileOpenReadError  2
#define CreateError        3
#define FileBufError       4
#define WriteError         5
#define ReadError          6
#define CompBufError       7
#define IOError            8
#define CompressError      9
#define BreakError        10

static UBYTE *ListeErreurs[] =
     {
      "\n",
#ifdef TINY_LZW
      "Compresseur économique Lempel-Ziv Welch variable à 15 bits\nUtilisation : LZWT [-DEST] Fichier1 [-DEST] [Fichier2] [...]\n",
#else
      "Compresseur Lempel-Ziv Welch variable à 15 bits\nUtilisation : LZW [-DEST] Fichier1 [-DEST] [Fichier2] [...]\n",
#endif
      "\aFichier inexistant ou illisible\n",
      "\aErreur en création\n",
      "\aPas assez de mémoire pour les buffers de fichiers\n",
      "\aErreur en écriture\n",
      "\aErreur en lecture\n",
      "\aPas assez de mémoire pour les buffers de compression\n",
      "\aErreur DOS ( IoErr() )\n",
      "\aCompression inefficace\n",
      "\a*** BREAK\n"
     };

/* Déclarations pour les zones mémoires allouées de taille BUFSIZE */
/*#define BUFSIZE 5003L*/
#ifdef TINY_LZW
#define BUFSIZE 16384L
#else
#define BUFSIZE 32767L
#endif
/* La valeur de BUFSIZE doit être inférieure à 32768.
 * La longueur du buffer compressé est écrite sur 2 octets
 * avant d'écrire le buffer compressé.
 * Comme on écrit le flag de compression ( buffer de données compressé par
 * LZ, LZ et HUFFMAN ou non compressé),
 * dans les deux bits de poids le plus fort, on ne peut utiliser une longueur
 * de données supérieure à 32767.
 * La valeur de BUFSIZE doit être supérieure à 259 puisque on utilise le
 * buffer du fichier à compresser pour stocker le contenu d'une structure
 * FileInfoBlock d'une longueur de 260 octets.
 */
#if BUFSIZE > 32767
Erreur : La valeur de BUFSIZE doit être inférieure à 32768
#endif
#if BUFSIZE < 260
Erreur : La valeur de BUFSIZE doit être supérieure à 259
#endif

#define BufIn  Buffer[0]
#define BufOut Buffer[1]

/* Deux zones mémoires pour les fichiers */
static UBYTE *Buffer[2] = {
                           NULL, /* Buffer en lecture  */
                           NULL  /* Buffer en écriture */
                          };

extern int (*_ONBREAK)();

/**************************************************************************/

int CXBRK()
{
 CodeErreur=BreakError;

 return(0);
}

/**************************************************************************/

void LibererMemoire()
{
 /* Déclarations et fonctions de Exec.Library */
 extern void FreeMem(void *,long);
 #pragma syscall FreeMem d2 902

 register UBYTE i;

 for(i=0;i<2;i++) if(Buffer[i]) FreeMem(Buffer[i],BUFSIZE);
}

/**************************************************************************/

UBYTE ReserverMemoire()
{
 /* Déclarations et fonctions de Exec.Library
  * Type de mémoires : l'un des 4 premiers paramètres est à combiner
  * éventuellement avec l'un des 2 derniers paramètres.
  * MEMF_ANY     0L
  * MEMF_PUBLIC  1L<<0
  * MEMF_CHIP    1L<<1
  * MEMF_FAST    1L<<2
  * MEMF_CLEAR   1L<<16
  * MEMF_LARGEST 1L<<17
  */

 /* Mémoire quelconque la première portion suffisamment grande */
 #define MEMF_ANY 0L
 extern void *AllocMem(long,long);
 #pragma syscall AllocMem c6 1002

 register UBYTE i;

 for(i=0;i<2;i++) Buffer[i]=(UBYTE *)AllocMem(BUFSIZE,0L);

 return((UBYTE)(BufIn && BufOut));
}

/**************************************************************************/

UBYTE EcrireBuffer(UBYTE *Buffer,UWORD EnTeteBuffer)
{
 extern long Write(BPTR,UBYTE *,long);
 #pragma libcall DOSBase Write 30 32103

 /* Méthode plus explicite pour écrire la valeur EnTeteBuffer de type UWORD */
 /* UBYTE *AdrEnTeteBuffer=(UBYTE *)&EnTeteBuffer;                          */

 if(Write(FOut,(UBYTE *)(&EnTeteBuffer),sizeof(UWORD))!=sizeof(UWORD)) return(WriteError);

 /* Ecriture d'un octet avec la méthode plus explicite */
 /* if(Write(FOut,AdrEnTeteBuffer,sizeof(UWORD))!=sizeof(UWORD)) return(WriteError);*/

 EnTeteBuffer&=32767; /* Extraction de la longueur du buffer écrit */

 if(Write(FOut,Buffer,(long)EnTeteBuffer)!=(long)EnTeteBuffer) return(WriteError);
 else LongFicOut+=2L+(long)EnTeteBuffer;

 return(0);
}

/**************************************************************************/

UBYTE CompresserFichier()
{
 /* Fonctions de LZBuffer.O */
 extern long __regargs CompLZBuf(unsigned char *,long,unsigned char *,long);
 /* LongueurBufferCompressé=CompLZBuf(From,SectSize,To,MaxSize) */
 extern void           CompLZBufExit(void);
 extern UBYTE          CompLZBufInit(void);

 /* Déclarations et fonctions de Dos.Library */
 #define MODE_OLDFILE 1005L
 #define MODE_NEWFILE 1006L
 /* #define MODE_READWRITE 1004L INUTILE ICI */
 extern BPTR Open(char *,long);
 extern long Read(BPTR,unsigned char *,long);
 extern long IoErr(void);
 #pragma libcall DOSBase Open 1e 2102
 #pragma libcall DOSBase Read 2a 32103
 #pragma libcall DOSBase IoErr 84 0

 register UWORD LongIn,LongOut;

 LongFicIn=LongFicOut=0L;

 if(!(FIn =Open(NomFicIn ,MODE_OLDFILE))) return(FileOpenReadError);
 if(!(FOut=Open(NomFicOut,MODE_NEWFILE))) return(CreateError);

 if(!CompLZBufInit()) return(CompBufError);

 while(LongIn=(UWORD)Read(FIn,BufIn,BUFSIZE))
      {
       LongFicIn+=LongIn;

       LongOut=(UWORD)CompLZBuf(BufIn,LongIn,BufOut,LongIn);

       if(LongOut==0)
         {
          CodeErreur=EcrireBuffer(BufIn,LongIn);
          LongOut=LongIn;
         }
       else CodeErreur=EcrireBuffer(BufOut,(UWORD)(LongOut | COMP_LZW));

       printf("\r %7lu ( %5lu ) -> %7lu ( %5lu )",LongFicIn,LongIn,LongFicOut,LongOut);

       if(CodeErreur) break;
      }

 CompLZBufExit();

 if(!CodeErreur && IoErr()) CodeErreur=IOError;

 if(LongFicOut>=LongFicIn) CodeErreur=CompressError;

 if(!CodeErreur && LongFicIn && LongFicOut)
 printf(" => %3u %%",(5+1000*(LongFicIn-LongFicOut)/(LongFicIn))/10);
/* else printf("\n");*/

 return(CodeErreur);
}

/**************************************************************************/

int main(int argc,char *argv[])
{
 /* Déclarations de variables externes de la DOS Library */
 extern BPTR CreateDir(char *);
 extern long DeleteFile(char *);
 extern long SetProtection(char *, ULONG);
 extern LONG SetComment(char *, char *);
 extern LONG Examine(BPTR, struct FileInfoBlock *);
 extern void Close(BPTR);
 extern BPTR Lock(char *, LONG);
 extern void UnLock(BPTR);
 #pragma libcall DOSBase CreateDir 78 101
 #pragma libcall DOSBase DeleteFile 48 101
 #pragma libcall DOSBase SetProtection ba 2102
 #pragma libcall DOSBase SetComment b4 2102
 #pragma libcall DOSBase Examine 66 2102
 #pragma libcall DOSBase Close 24 101
 #pragma libcall DOSBase Lock 54 2102
 #pragma libcall DOSBase UnLock 5a 101

 /* Type à passer à la fonction Lock() */
 /* #define SHARED_LOCK    -2L  File is readable by others */
    #define ACCESS_READ    -2L  /* Synonym                 */
 /* #define EXCLUSIVE_LOCK -1L  No other access allowed    */
 /* #define ACCESS_WRITE   -1L  Synonym                    */

 #define LastChar(S) S[strlen(S)-1]

 register BCPL Clef; /* Clef renvoyée par Lock() et CreateDir() */

 register UBYTE NuArg,
                FlagNewDir=0,   /* Changer le répertoire de destination ? */
                NewChemin[80],
                Commentaire[80],
                FileName[108];

 register LONG Protection;

 _ONBREAK=CXBRK;

#ifdef TINY_LZW
 printf("V1.00 CopyRight SHAREWARE Christian BRUNON 15-09-1993\n");
#else
 printf("V1.00 CopyRight SHAREWARE Christian BRUNON 15-09-1993\n");
#endif

 /* Pas de paramètre ou paramètre ? */
 if(argc==1 || (argc==2 && argv[1][0]=='?' && !argv[1][1]))
   {
    printf(ListeErreurs[1]); /* Erreur fatale */
    return(1);
   }

 if(!ReserverMemoire()) CodeErreur=FileBufError; /* Erreur fatale */

 if(CodeErreur==0)
 for(NuArg=1 ; NuArg<argc ; NuArg++)
 if(argv[NuArg][0]=='-')
     /* Le paramètre lu est un nouveau chemin */
     {
      FlagNewDir=(UBYTE)1;
      strcpy(NewChemin,&argv[NuArg][1]);

      if(NewChemin[0] && LastChar(NewChemin)!=':' && LastChar(NewChemin)!='/')
        {
         if(Clef=CreateDir(NewChemin)) UnLock(Clef);

         strcat(NewChemin,"/");
        }
     }
 /* Le paramètre lu est un fichier à compresser */
 else{
      CodeErreur=0;

      strcpy(NomFicIn,argv[NuArg]);

      /* On utilitise le Buffer de lecture pour stocker la structure FileInfoBlock
       * Recopie des bits d'attributs de fichier et du commentaire
       * Inutile d'aligner l'adresse de la structure FileInfoBlock,
       * la fonction AllocMem() renvoie toujours une adresse divisible par 4.
       * On utilise directement l'adresse BufIn.
       */
      if(Clef=Lock(NomFicIn,ACCESS_READ))
          {
           /* Test existence et test type = fichier */
           if(Examine(Clef,PtrFIB BufIn) && (PtrFIB BufIn)->fib_DirEntryType<0)
               {
                strcpy(FileName,(PtrFIB BufIn)->fib_FileName);
                LongFicIn=(PtrFIB BufIn)->fib_Size;
                Protection=(PtrFIB BufIn)->fib_Protection;
                strcpy(Commentaire,(PtrFIB BufIn)->fib_Comment);
               }
           else CodeErreur=FileOpenReadError;

           UnLock(Clef);
          }
      else CodeErreur=FileOpenReadError;

      if(CodeErreur){
                     printf("%s %s",NomFicIn,ListeErreurs[CodeErreur]);
                     continue;
                    }

      /* Détermination du nom du fichier compressé */
      if(FlagNewDir)
          {
           strcpy(NomFicOut,NewChemin);
           strcat(NomFicOut,FileName);
          }
      else strcpy(NomFicOut,NomFicIn);

#ifdef TINY_LZW
      /* Rajout de l'extension .LZWT */
      strcat(NomFicOut,".LZWT");
#else
      /* Rajout de l'extension .LZW */
      strcat(NomFicOut,".LZW");
#endif

      /* Affichage des noms des fichiers */
      printf("%s (%lu) -> %s\n      ORIGINAL     -> LEMPEL-ZIV WELCH\n",NomFicIn,LongFicIn,NomFicOut);

      CodeErreur=CompresserFichier();

      if(FOut) Close(FOut);
      if(FIn ) Close(FIn);

      /* Recopie des bits d'attributs de fichier et du commentaire */
      if(CodeErreur==0)
        {
         SetProtection(NomFicOut,Protection);
         SetComment(NomFicOut,Commentaire);
        }

      /* Erreur intervenue pendant que le fichier compressé est ouvert.
       * Le fichier doit étre détruit car certainement incomplet ou trop long.
       */
      if(CodeErreur>4) DeleteFile(NomFicOut);

      printf(ListeErreurs[CodeErreur]);
     }

 LibererMemoire();

 /* Renvoie le code d'erreur du DERNIER fichier compressé */
 return(CodeErreur);
}
