
#include <exec/alerts.h>
#include <exec/memory.h>
#include <exec/execbase.h>

#include <dos/dosextens.h>
#include <dos/rdargs.h>

#include <libraries/xpk.h>

#include <proto/dos.h>
#include <proto/exec.h>

#include <string.h>

#define BUFFERSIZE 16384L

LONG __builtin_min(LONG,LONG);
#define MIN __builtin_min

struct ExecBase *SysBase;
struct DosLibrary *DOSBase;
struct Library *XpkBase;

void PrintF(char *,...);
LONG __regargs PackFile(char *,struct FileInfoBlock *,char *,char *,LONG,char *,LONG);
LONG __regargs UnPackFile(char *,struct FileInfoBlock *,char *);
LONG __asm __saveds ChunkFunc(register __a1 struct XpkProgress *);

struct Hook ChunkHook = {{0L},ChunkFunc};

struct xPackArgs
 {
  char **Files;
  char *Method;
  ULONG *MinSize;
  char *Suffix,*Password;
  LONG All,Force;
 };

char *Template = "FILE/M,METHOD/K,MINSIZE/N/K,SUFFIX/K,PASSWORD/K,ALL/S,FORCE/S";

LONG __saveds main(void)

{
 struct Process *MyProc;
 struct RDArgs *RDArgs;
 char ProgName[32];
 LONG Result;
 struct xPackArgs Args;
 char **Files;
 struct AnchorPath *AnchorPath;
 ULONG MinSize;

 SysBase=*((struct ExecBase **)4L);
 MyProc=(struct Process *)SysBase->ThisTask;

 if (MyProc->pr_CLI==NULL)
  {
   (void)WaitPort(&MyProc->pr_MsgPort);
   Forbid();
   ReplyMsg (GetMsg(&MyProc->pr_MsgPort));

   return 0L;
  }

 if ((DOSBase=(struct DosLibrary *)OpenLibrary("dos.library",37L))==NULL)
  {
   Alert (AT_Recovery|AG_OpenLib|AO_DOSLib);

   return 20L;
  }

 if (!GetProgramName(ProgName,32L)) (void)strcpy(ProgName,"xPack");
 Result=10L;

 if ((XpkBase=OpenLibrary(XPKNAME,2L))==NULL)
  {
   PrintF ("%s: %s V2 or newer required !\n",XPKNAME);

   goto Close1;
  }

 Args.Files=NULL;
 Args.Method=Args.Suffix=Args.Password=NULL;
 Args.MinSize=NULL;
 Args.All=Args.Force=FALSE;
 if ((RDArgs=ReadArgs(Template,(LONG *)&Args,NULL))==NULL)
  {
   PrintFault (IoErr(),ProgName);

   goto Close2;
  }
 if (Args.MinSize) MinSize=*Args.MinSize;
 else MinSize=512L;

 if ((AnchorPath=(struct AnchorPath *)AllocVec(sizeof(struct AnchorPath)+256L,
                                               MEMF_PUBLIC|MEMF_CLEAR))==NULL)
  {
   PrintFault (ERROR_NO_FREE_STORE,ProgName);

   goto Close3;
  }
 AnchorPath->ap_BreakBits=SIGBREAKF_CTRL_C;
 AnchorPath->ap_Strlen=256;

 Files=Args.Files;
 while (*Files)
  {
   LONG RetVal;

   for (RetVal=MatchFirst(*Files,AnchorPath); RetVal==0L; RetVal=MatchNext(AnchorPath))
    {
     if (AnchorPath->ap_Info.fib_DirEntryType>0L)
      {
       if (((AnchorPath->ap_Flags&APF_DIDDIR)==0L)&&Args.All) AnchorPath->ap_Flags|=APF_DODIR;
       AnchorPath->ap_Flags&=~APF_DIDDIR;
      }
     else
      {
       BPTR DirLock;
       LONG Result;

       DirLock=CurrentDir(AnchorPath->ap_Current->an_Lock);
       if (Args.Method==NULL)
        Result=UnPackFile(AnchorPath->ap_Buf,&AnchorPath->ap_Info,Args.Password);
       else
        Result=PackFile(AnchorPath->ap_Buf,&AnchorPath->ap_Info,Args.Method,
                        Args.Suffix,MinSize,Args.Password,Args.Force);
       (void)CurrentDir(DirLock);

       if (!Result)
        {
         PrintF ("*** Aborting\n");

         MatchEnd (AnchorPath);
         goto Close4;
        }
      }
    }
   MatchEnd (AnchorPath);

   if (RetVal!=ERROR_NO_MORE_ENTRIES)
    {
     PrintF ("%s: %s - ",ProgName,*Files);
     PrintFault (RetVal,NULL);
     
     goto Close4;
    }
   else Files++;
  }
 Result=0L;

Close4: /* I hate "goto"s, but it's the only way to keep it short. */
 FreeVec ((APTR)AnchorPath);
Close3:
 FreeArgs (RDArgs);
Close2:
 CloseLibrary (XpkBase);
Close1:
 CloseLibrary (&DOSBase->dl_lib);
 return Result;
}

char *VersionString = "$VER: xPack 1.0 ("__DATE__")";

void PrintF(char *FormatString,...)

{
 (void)VPrintf(FormatString,(LONG *)&FormatString+1L);
 (void)Flush(Output());
}

LONG XpkExamineTags(struct XpkFib *XpkFib,ULONG Tag1,...)

{
 return XpkExamine(XpkFib,(struct TagItem *)&Tag1);
}

LONG XpkPackTags(ULONG Tag1,...)

{
 return XpkPack((struct TagItem *)&Tag1);
}

LONG XpkUnpackTags(ULONG Tag1,...)

{
 return XpkUnpack((struct TagItem *)&Tag1);
}

void __regargs TempName(char *Name)

{
 UWORD Index;
 ULONG Addr;

 for (Index=0, Addr=(ULONG)SysBase->ThisTask; Index<8; Index++, Addr>>=4)
  *Name++='A'+(char)(Addr&15);
 (void)strcpy(Name,".zop");
}

LONG __asm __saveds ChunkFunc(register __a1 struct XpkProgress *Prog)

{
 if (Prog->Type==XPKPROG_START ) PrintF ("\x1B[0 p");
 PrintF ("\r%-9s %-12s (%6ld bytes, %3ld%% done, %2ld%% CF) ",
         Prog->Activity,Prog->FileName,Prog->ULen,Prog->Done,Prog->CF);
 if (Prog->Type==XPKPROG_END) PrintF ("\n\x1B[1 p");

 return (LONG)SetSignal(0L,SIGBREAKF_CTRL_C)&SIGBREAKF_CTRL_C;
}

LONG __regargs PackFile(char *PathName,struct FileInfoBlock *FIB,char *Method,
                        char *Suffix,LONG MinSize,char *Password,LONG Force)

{
 BPTR Handle;
 struct XpkFib XpkFib;
 char ErrorBuffer[XPKERRMSGSIZE],TargetName[108];
 LONG Size;

 if (FIB->fib_Protection&FIBF_DELETE)
  {
   PrintF ("Skipping %s (delete protected)\n",PathName);
   return TRUE;
  }
 if (FIB->fib_Size<=MinSize)
  {
   PrintF ("Skipping %s (too small)\n",PathName);
   return TRUE;
  }
 if ((Handle=Open(FIB->fib_FileName,MODE_OLDFILE))==NULL)
  {
   PrintF ("Can't open %s\n",PathName);
   return TRUE;
  }
 if (!Force)
  {
   if (XpkExamineTags(&XpkFib,XPK_GetError,ErrorBuffer,
                     XPK_InFH,Handle,TAG_DONE)<0L)
    {
     Close (Handle);

     PrintF ("%s\n",ErrorBuffer);
     return TRUE;
    }
   if (XpkFib.Type!=XPKTYPE_UNPACKED)
    {
     Close (Handle);

     PrintF ("Skipping %s (already crunched)\n",PathName);
     return TRUE;
    }
  }

 if (Suffix)
  {
   ULONG Length;

   Length=strlen(strcpy(TargetName,FIB->fib_FileName));
   (void)strcpy(&TargetName[Length],Suffix);
  }
 else TempName (TargetName);
 Size=0L;
 if (XpkPackTags(XPK_GetError,ErrorBuffer,
                 XPK_Password,Password,XPK_FindMethod,Method,
                 XPK_ChunkHook,&ChunkHook,XPK_FileName,(ULONG)FIB->fib_FileName,
                 XPK_InFH,Handle,
                 XPK_OutName,TargetName,XPK_GetOutLen,&Size,TAG_DONE)<0L)
  {
   Close (Handle);

   PrintF ("%s\n",ErrorBuffer);
   return TRUE;
  }
 Close (Handle);

 if ((Size>FIB->fib_Size)&&(!Force))
  {
   (void)DeleteFile(TargetName);

   PrintF ("Skipping %s (unable to reduce size)\n",PathName);
   return TRUE;
  }

 if (FIB->fib_Comment[0]) (void)SetComment(TargetName,FIB->fib_Comment);
 if (FIB->fib_Protection) (void)SetProtection(TargetName,FIB->fib_Protection);
 (void)SetFileDate(TargetName,&FIB->fib_Date);

 if (Suffix) return TRUE;

 if (!DeleteFile(FIB->fib_FileName))
  {
   (void)DeleteFile(TargetName);

   PrintF ("Unable to delete %s\n",PathName);
   return TRUE;
  }
 if (!Rename(TargetName,FIB->fib_FileName))
  {
   PrintF ("Unable to rename %s to %s\n",TargetName,FIB->fib_FileName);
   return FALSE;
  }

 return TRUE;
}

LONG __regargs UnPackFile(char *PathName,struct FileInfoBlock *FIB,char *Password)

{
 BPTR Handle;
 struct XpkFib XpkFib;
 char ErrorBuffer[XPKERRMSGSIZE],TargetName[14];

 if (FIB->fib_Protection&FIBF_DELETE)
  {
   PrintF ("Skipping %s (delete protected)\n",PathName);
   return TRUE;
  }
 if ((Handle=Open(FIB->fib_FileName,MODE_OLDFILE))==NULL)
  {
   PrintF ("Can't open %s\n",PathName);
   return TRUE;
  }
 if (XpkExamineTags(&XpkFib,XPK_GetError,ErrorBuffer,
                    XPK_InFH,Handle,TAG_DONE)<0L)
  {
   Close (Handle);

   PrintF ("%s\n",ErrorBuffer);
   return TRUE;
  }
 if (XpkFib.Type!=XPKTYPE_PACKED)
  {
   Close (Handle);

   PrintF ("Skipping %s (not crunched)\n",PathName);
   return TRUE;
  }

 TempName (TargetName);
 if (XpkUnpackTags(XPK_GetError,ErrorBuffer,
                   XPK_Password,Password,
                   XPK_ChunkHook,&ChunkHook,XPK_FileName,(ULONG)FIB->fib_FileName,
                   XPK_InFH,Handle,
                   XPK_OutName,TargetName,TAG_DONE)<0L)
  {
   Close (Handle);

   PrintF ("%s\n",ErrorBuffer);
   return TRUE;
  }
 Close (Handle);

 if (FIB->fib_Comment[0]) (void)SetComment(TargetName,FIB->fib_Comment);
 if (FIB->fib_Protection) (void)SetProtection(TargetName,FIB->fib_Protection);
 (void)SetFileDate(TargetName,&FIB->fib_Date);

 if (!DeleteFile(FIB->fib_FileName))
  {
   (void)DeleteFile(TargetName);

   PrintF ("Unable to delete %s\n",PathName);
   return TRUE;
  }
 if (!Rename(TargetName,FIB->fib_FileName))
  {
   PrintF ("Unable to rename %s to %s\n",TargetName,FIB->fib_FileName);
   return FALSE;
  }

 return TRUE;
}
