#define NAME	     "CheckX"
#define REVISION     "77"
#define DISTRIBUTION "(Freeware) "
#define DATE	     "05.06.2000"

/*
This program scans for crunched, linked files and archived files. It
decrunches them and saves the result files to another directory-tree.
The scanning routines are recursiv and thus check really all stuff.

The program must be compiled and linked without startup-code. You can set
the pure file protection bit and make it resident, because it is multi-
reentrant (no global variables, except library bases).

Crypted archives are not supported yet (e.g. asking for password).
*/

/* Programmheader

	Name:		CheckX
	Author:		SDI
	Distribution:	Freeware
	Description:	scans, decrunches and dearchives files
	Compileropts:	-
	Linkeropts:	-l xpkmaster xadmaster amiga -gsi

 1.0   14.12.96 : first Version
 1.1   28.12.96 : moved PassRequest into xpkmaster.library
 1.2   12.02.97 : now also decrunches Exe-Files
 1.3   15.06.97 : added length output as test
 1.4   21.11.97 : renamed from Decrunch, got really new program
 1.5   22.11.97 : bug-fixes
 1.6   29.11.97 : added unarchiving feature
 1.7   30.11.97 : bug-fixes
 1.8   06.12.97 : xpkmaster.library now only required with ASKPWD option
 1.9   07.12.97 : Added archive copy for weird archive names, better error
	codes
 1.10  08.12.97 : fixed error codes a bit
 1.11  11.12.97 : disabled DOS requests, added Zip-Archives, added TaskID
	to temporary filenames
 1.12  12.12.97 : added Arc, ZOO and LhASFX archives
 1.13  13.12.97 : fixed Arc recognition
 1.14  19.12.97 : deletes copied arc before scan
 1.15  22.12.97 : crunched archives are unarchived correctly now
 1.16  02.01.98 : opens dos.library itself, no startup-code required
 1.17  23.01.98 : added disk crunchers
 1.18  24.01.98 : some fixes
 1.19  01.02.98 : little bug-fix in argument-option use
 1.20  06.02.98 : better error output, added automount
 1.21  08.02.98 : little bug-fix
 1.22  10.02.98 : fixed archive copy conditions, added PRINTALL
 1.23  12.02.98 : fixed help text, bug fixes with unlinking and FreeMem
 1.24  13.02.98 : added unstripping
 1.25  04.03.98 : added PRINTEXEC
 1.26  13.03.98 : added high density DMS support
 1.27  19.03.98 : added LOUD keyword
 1.28  23.03.98 : RDx no longer depends on archive depth, but on dddepth
 1.29  10.04.98 : bug fixes
 1.30  26.04.98 : bug fixes
 1.31  09.05.98 : now uses no longer adress 4 for SysBase
 1.32  31.05.98 : better output
 1.33  04.06.98 : added HEADER addition for address files
 1.34  08.08.98 : bug fix with SAVE
 1.35  24.09.98 : added xvs.library virus checks
 1.36  18.10.98 : xvs is opened global and only once
 1.37  30.10.98 : renamed from CheckXFD
 1.38  11.11.98 : fixed format drive bug using a delay and an error report
 1.39  14.11.98 : format error with AUTOMOUNT removed
 1.40  16.11.98 : removed HEADER addition stuff
 1.41  18.11.98 : better RDx: access and mount
 1.42  23.11.98 : fixed bug with hunk stripping
 1.43  29.12.98 : now prints an error, when virus detection is turned off,
	added time calculation and output
 1.44  31.12.98 : little bug fix
 1.45  06.02.99 : bug fixes, added xadmaster.library stuff, removed LOUD
	and internal DMS call
 1.46  09.02.99 : removed internal LZX call
 1.47  11.02.99 : now uses assembler startcode allocating a bigger stack
 1.48  14.02.99 : bug-fixes for nocylinder archivers (PackDev)
 1.49  16.02.99 : fixed strip option (don't know, where the error was :-)
 1.50  21.02.99 : removed internal Zoom call and disk-archiver stuff
 1.51  22.02.99 : fixed archiver call for remaining non-XAD archivers, added
	empty file check
 1.52  23.02.99 : fixed StartCode return value
 1.53  24.02.99 : forgot empty check for archived files
 1.54  08.03.99 : old archiver calling did not work, when file was XAD
	unarchived
 1.55  26.03.99 : prints error, when logfile cannot be created
 1.56  30.03.99 : added bootblock scanning for unarchived disks
 1.57  16.05.99 : bug fix with unlinked file save
 1.58  17.07.99 : removed internal LhA Support
 1.59  30.07.99 : bug fix with file name prints
 1.60  03.08.99 : added DEBUG option
 1.61  05.08.99 : added XVS SelfTest and MemoryTest
 1.62  19.08.99 : added error summary, again fixed name problem
 1.63  14.09.99 : solved big memory loss problem (did not free xadArchiveInfo),
	added logfile comment
 1.64  17.10.99 : removed external archiver calls, added QUIET
 1.65  24.11.99 : now sets nice return values
 1.66  26.11.99 : tries reading again before giving read error
 1.67  03.12.99 : bug fix in name printing
 1.68  04.12.99 : tries again opening files.
 1.69  16.12.99 : added NOSILENT Option
 1.70  15.01.00 : files from disc archives are extracted directly now,
	removed double read tries
 1.71  16.01.00 : fixed error messages for disk unarchiving
 1.72  12.02.00 : added support for ADF images
 1.73  13.02.00 : bug fix for disk archives
 1.74  09.03.00 : now detects XADFIF_NOFILENAME and XADFIF_NOUNCRUNCHSIZE
 1.75  05.04.00 : fixed bug with XADFIF_NOFILENAME
 1.76  26.04.00 : added size checkout for XADFIF_NOUNCRUNCHSIZE
 1.77  05.06.00 : added support for multiple filesystems, removed NODOS and
	diskerr errors, always prints FS-type now.
*/

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/xfdmaster.h>
#include <proto/xadmaster.h>
#include <proto/xpkmaster.h>
#include <proto/intuition.h>
#include <proto/xvs.h>
#include <proto/utility.h>
#include <libraries/xfdmaster.h>
#include <dos/dostags.h>
#include <dos/doshunks.h>
#include <dos/filehandler.h>
#include <exec/memory.h>
#include "SDI_version.h" /* make version string */
#include "SDI_compiler.h"
#define SDI_TO_ANSI
#include "SDI_ASM_STD_protos.h"

#define PARAM   "FROM,LOG,SAVE/K,ALL/S,ASKPWD/S,PRINTALL/S,PRINTEXEC/S," \
		"NODECRUNCH/S,NOUNLINK/S,NOUNARCHIVE/S,NOUNTRACK/S,"	 \
		"NOSILENT/S,NOSTRIP/S,NOVIRUS/S,DEBUG/S,QUIET/S"

void RawPutChar(ULONG c);

#ifdef __SASC
  #pragma libcall SysBase RawPutChar 204 001
#elif defined(__GNUC__)
  #define RawPutChar(c) LP1NR(204, RawPutChar, ULONG, c, d0, , SysBase)
#else
  #pragma amicall(SysBase,0x204,RawPutChar(d0))
#endif

void KPutC(ULONG c)
{
  RawPutChar(c);
}

void KPrintf(STRPTR fmt, ...)
{
  RawDoFmt(fmt, &fmt + 1, (void (*)()) KPutC, 0);
}

#ifdef __SASC
  #define XpkBase	xpkbase
  #define ASSIGN_XPK
  #define IntuitionBase	intuitionbase
  #define ASSIGN_INT
  #define UtilityBase	utilitybase
  #define ASSIGN_UTIL
#else
  struct Library *	XpkBase		= 0;
  struct IntuitionBase *IntuitionBase	= 0;
  struct UtilityBase *  UtilityBase     = 0;
  #define ASSIGN_XPK	XpkBase = xpkbase;
  #define ASSIGN_INT	IntuitionBase = intuitionbase;
  #define ASSIGN_UTIL	UtilityBase = utilitybase;
#endif
struct xfdMasterBase *	xfdMasterBase	= 0;
struct DosLibrary *	DOSBase		= 0;
struct ExecBase *	SysBase		= 0;
struct xvsBase *	xvsBase		= 0;
struct xadMasterBase *	xadMasterBase	= 0;

struct Args {
  STRPTR from;
  STRPTR log;
  STRPTR save;
  ULONG  all;
  ULONG  askpwd;
  ULONG  printall;
  ULONG  printexec;
  ULONG  nodecrunch;
  ULONG  nounlink;
  ULONG  nounarchive;
  ULONG  nountrack;
  ULONG  nosilent;
  ULONG  nostrip;
  ULONG  novirus;
  ULONG  debug;
  ULONG	 quiet;
};

struct CrunchMemList {
  struct CrunchMemList * cml_Next;
  APTR			 cml_MemoryRegion;
  ULONG			 cml_MemorySize;
};

#define CHECKXFLAG_SAVE			(1<< 0)
#define CHECKXFLAG_ALL			(1<< 1)
#define CHECKXFLAG_ASKPWD		(1<< 2)
#define CHECKXFLAG_PRINTALL		(1<< 3)
#define CHECKXFLAG_PRINTEXEC		(1<< 4)
#define CHECKXFLAG_NODECRUNCH		(1<< 5)
#define CHECKXFLAG_NOUNLINK		(1<< 6)
#define CHECKXFLAG_NOUNARCHIVE		(1<< 7)
#define CHECKXFLAG_NOUNTRACK		(1<< 8)
#define CHECKXFLAG_NOSTRIP		(1<< 9)
#define CHECKXFLAG_DEBUG		(1<<10)
#define CHECKXFLAG_QUIET		(1<<11)

#define CHECKXFLAG_XVSLIB		(1<<20)

#define CHKXCALLFLAGS	(CHECKXFLAG_SAVE|CHECKXFLAG_ALL|		\
			 CHECKXFLAG_ASKPWD|CHECKXFLAG_PRINTALL|		\
			 CHECKXFLAG_PRINTEXEC|CHECKXFLAG_NODECRUNCH|	\
			 CHECKXFLAG_NOUNLINK|CHECKXFLAG_NOUNARCHIVE|	\
			 CHECKXFLAG_NOUNTRACK|CHECKXFLAG_NOSTRIP|	\
			 CHECKXFLAG_DEBUG|CHECKXFLAG_QUIET|		\
			 CHECKXFLAG_XVSLIB)

#define CHKXSAVEFLAGS	(CHECKXFLAG_LINKED|CHECKXFLAG_CRUNCHED|		\
			 CHECKXFLAG_STRIPPED)

#define CHECKXFLAG_NAMEPRINTED		(1<<26)
#define CHECKXFLAG_CRUNCHED		(1<<27)
#define CHECKXFLAG_LINKED		(1<<28)
#define CHECKXFLAG_ADDRESS		(1<<29)
#define CHECKXFLAG_NOFREEMEM		(1<<30)
#define CHECKXFLAG_STRIPPED		(1<<31)

#define CHXWARN_OFFSET		30
#define XADERR_OFFSET		0x100
#define XFDERR_OFFSET		0x200

#define CHKXERR_NOMEMORY	1
#define CHKXERR_EXAMINEERR	2
#define CHKXERR_OPENERR		3
#define CHKXERR_READ		4
#define CHKXERR_SCANERR		5
#define CHKXERR_BREAK		6
#define CHKXERR_OPENDIR		7
#define CHKXERR_RESOURCE	8
#define CHKXERR_NOBOOTVIRUS	9
#define CHKXERR_WRITE		10
#define CHKXERR_NOVIRUS		11
#define CHKXERR_EMPTY		12

#define CHKXWARN_NOVIRUS	31
#define CHKXWARN_XVSSELFTEST	32
#define	CHKXWARN_MEMVIRUS	33

struct FileData {
  struct CrunchMemList * fd_MemList;
  STRPTR		 fd_Name;
  ULONG  		 fd_LogFileFH;
  ULONG  		 fd_SaveDirL;
  ULONG			 fd_Flags;
  ULONG			 fd_NumVirus;
  ULONG			 fd_CHKXErrors;
  ULONG			 fd_XFDErrors;
  ULONG			 fd_XADErrors;
  ULONG			 fd_CorruptedArchives;
  BYTE  		 fd_RecurseDepth;
  UBYTE  		 fd_LinkNum;
  UBYTE			 fd_ArchiveDepth;
};

static void SetLogComment(STRPTR);
static LONG DoDirectoryScan(STRPTR, STRPTR, struct FileData *);
static LONG DoFileOpen(struct FileData *);
static LONG DoGetVirus(struct FileData *, APTR, ULONG);
ASM(LONG) OutHookCheckX(REG(a0, struct Hook *hook), REG(a1, struct xadHookParam *hp));
static LONG DoFileUnArchive(struct FileData *, APTR, ULONG);
static LONG DoFileUnLink(struct FileData *, APTR, ULONG);
static LONG DoFileUnCrunch(struct FileData *, APTR, ULONG);
static LONG DoFileStrip(struct FileData *, APTR, ULONG);
static void PrintCHKXFile(struct FileData *);
static void PrintCHKXErr(struct FileData *, LONG);
static void PrintCHKXTxt(struct FileData *, STRPTR, ...);
static LONG AddCrunchMemList(struct FileData *, APTR, ULONG);
static void FreeCrunchMemList(struct FileData *, APTR);
static LONG SaveUncrFile(struct FileData *, APTR, ULONG);
static ULONG OpenParentDir(struct FileData *);
static ULONG OpenNewDir(struct FileData *, STRPTR);

/* All memory regions must be in mem list. All unneeded memory must be freed
   as fast as possible (after unlinking, decrunching), as well as the
   MemoryList structure.
   
   The program has a loop like scan routine system, which is called for
   every file:

   A) Scan files, directories and sub directories and call following for
      every file:
   1) Check for viruses.
   2) Test if it is an archive. When yes decrunch and start for every file
      with point 1.
   3) Test if file is linked. When yes unlink and call point 1 for both
      parts.
   4) Test if file is crunched. When yes, decrunch and start again with
      point 1.
   5) Try stripping useless stuff. When successful start with point 1 again.
   6) Possibly save file (with SAVE option) or end loop here.
*/

/* main routine, do argument parsing */
LONG start(void)
{
  LONG error = RETURN_FAIL;
  struct DosLibrary *dosbase;
  struct Process *task;

  SysBase = (*((struct ExecBase **) 4));

  /* test for WB and reply startup-message */
  if(!(task = (struct Process *) FindTask(0))->pr_CLI)
  {
    WaitPort(&task->pr_MsgPort);
    Forbid();
    ReplyMsg(GetMsg(&task->pr_MsgPort));
    return RETURN_FAIL;
  }

  if((dosbase = (struct DosLibrary *) OpenLibrary("dos.library", 37)))
  {
    struct xfdMasterBase *xfdmasterbase;

    DOSBase = dosbase;
    if((xfdmasterbase = (struct xfdMasterBase *) OpenLibrary("xfdmaster.library", 37)))
    {
      struct xadMasterBase *xadmasterbase;

      xfdMasterBase = xfdmasterbase;
      if((xadmasterbase = (struct xadMasterBase *) OpenLibrary("xadmaster.library", 7)))
      {
        struct Args Args;
        struct RDArgs *rda;

	xadMasterBase = xadmasterbase;
        memset(&Args, 0, sizeof(struct Args));

        if((rda = (struct RDArgs *) AllocDosObject(DOS_RDARGS, 0)))
        {
          rda->RDA_ExtHelp =
          "FROM        source file or directory - may contain patterns\n"
          "LOG         log file name\n"
          "SAVE        directory, where decrunched files are saved\n"
          "ALL         scan deep into directories\n"
          "ASKPWD      ask for password when needed (needs xpkmaster.library)\n"
	  "PRINTALL    print all filenames\n"
          "PRINTEXEC   print names of all executable files\n"
          "NODECRUNCH  do not decrunch files with xfdmaster\n"
          "NOUNLINK    do not unlink files with xfdmaster\n"
          "NOUNARCHIVE do not unarchive file archives\n"
          "NOUNTRACK   do not unarchive track archives\n"
          "NOSILENT    do not disable dos requests\n"
          "NOSTRIP     do not strip useless hunks\n"
          "NOVIRUS     do not scan with xvs.library for viruses\n"
          "DEBUG       also output texts to serial debug engine\n"
          "QUIET       do not output texts to console\n";

          if(ReadArgs(PARAM, (LONG *) &Args, rda))
          {
            ULONG flags = 0, log = 0;
            struct xvsBase *xvsbase = 0;

            if(!Args.from)		Args.from = "#?";
            if(Args.all)		flags |= CHECKXFLAG_ALL;
            if(Args.save)		flags |= CHECKXFLAG_SAVE;
            if(Args.askpwd)		flags |= CHECKXFLAG_ASKPWD;
	    if(Args.printall)		flags |= CHECKXFLAG_PRINTALL;
	    if(Args.printexec)		flags |= CHECKXFLAG_PRINTEXEC;
            if(Args.nodecrunch)		flags |= CHECKXFLAG_NODECRUNCH;
            if(Args.nounlink) 		flags |= CHECKXFLAG_NOUNLINK;
            if(Args.nounarchive)	flags |= CHECKXFLAG_NOUNARCHIVE;
            if(Args.nountrack)		flags |= CHECKXFLAG_NOUNTRACK;
            if(Args.nostrip)		flags |= CHECKXFLAG_NOSTRIP;
            if(Args.debug)		flags |= CHECKXFLAG_DEBUG;
            if(Args.quiet)		flags |= CHECKXFLAG_QUIET;

            if(!Args.novirus)
            {
              if((xvsbase = (struct xvsBase *) OpenLibrary("xvs.library", 33)))
              {
                flags |= CHECKXFLAG_XVSLIB;
                xvsBase = xvsbase;
              }
	    }

            if(!Args.log || (log = Open(Args.log, MODE_READWRITE)))
            {
	      struct FileData fd;
	      APTR win;
	      ULONG s1 = 0, s2 = 0, msecs;
	      struct IntuitionBase *intuitionbase;

	      win = task->pr_WindowPtr;
	      if(!Args.nosilent)
	        task->pr_WindowPtr = (APTR) -1;
	      /* prevent dos requests */

	      if(log)
	      {
	        SetFileSize(log, 0, OFFSET_BEGINNING);
	        SetProtection(Args.log, FIBF_EXECUTE);
	        SetLogComment(Args.log);
	      }

	      memset(&fd, 0, sizeof(struct FileData));
    	      fd.fd_Flags = flags | CHECKXFLAG_NAMEPRINTED; /* for possible warnings display */
    	      fd.fd_LogFileFH = log;

	      if(!xvsbase)
	        PrintCHKXErr(&fd, CHKXWARN_NOVIRUS);
	      else
	      {
                struct xvsMemoryInfo *mi;

	        if(!xvsSelfTest())
	          PrintCHKXErr(&fd, CHKXWARN_XVSSELFTEST);

      	        if((mi = (struct xvsMemoryInfo *) xvsAllocObject(XVSOBJ_MEMORYINFO)))
      	        { 
      	          if(xvsSurveyMemory(mi))
	            PrintCHKXErr(&fd, CHKXWARN_MEMVIRUS);

      	          xvsFreeObject(mi);
      	        }
	      }

	      if((intuitionbase = (struct IntuitionBase *) OpenLibrary("intuition.library", 37)))
	      {
	        ASSIGN_INT
	        CurrentTime(&s1, &msecs);
	      }	

	      ++fd.fd_RecurseDepth;
	      error = DoDirectoryScan(Args.from, Args.save, &fd) ? RETURN_FAIL : RETURN_OK;
	      --fd.fd_RecurseDepth;
	      fd.fd_Flags |= CHECKXFLAG_NAMEPRINTED; /* for result texts */

	      task->pr_WindowPtr = win;

	      if(intuitionbase)
	      {
	        CurrentTime(&s2, &msecs);
	        s2 -= s1;
	        s1 = s2 / 60;
	        s2 %= 60;
	        msecs = s1 / 60;
	        s1 %= 60;
	        PrintCHKXTxt(&fd, "\nTime needed for check: %2ld:%02ld:%02ld", msecs, s1, s2);
	        CloseLibrary((struct Library *) intuitionbase);
	      }
	      if(fd.fd_NumVirus)
	        PrintCHKXTxt(&fd, "The scan detected %ld virus%s.", fd.fd_NumVirus, fd.fd_NumVirus > 1 ? "es" : "");
	      if(fd.fd_CHKXErrors || fd.fd_XADErrors || fd.fd_XFDErrors || fd.fd_CorruptedArchives)
	        PrintCHKXTxt(&fd, "There were errors (CheckX/XFD/XAD/corrupted archives): %ld/%ld/%ld/%ld.",
	        fd.fd_CHKXErrors, fd.fd_XFDErrors, fd.fd_XADErrors, fd.fd_CorruptedArchives);
	      if(!error && (fd.fd_NumVirus || !xvsBase))
	      {
	        SetIoErr(0);
	        error = RETURN_WARN;
	      }

              if(log)
                Close(log);
            }
            else if(Args.log && !Args.quiet)
              Printf("Could not create logfile.\n");

	    if(xvsbase)
	      CloseLibrary((struct Library *) xvsbase);
            FreeArgs(rda);
          }
          FreeDosObject(DOS_RDARGS, rda);
        }
	CloseLibrary((struct Library *) xadmasterbase);
      }
      CloseLibrary((struct Library *) xfdmasterbase);
    }
    CloseLibrary((struct Library *) dosbase);
  }

  return error;
}

static void SetLogComment(STRPTR name)
{
  UBYTE com[30];
  struct MsgPort *TimerMP;

  if((TimerMP = CreateMsgPort()))
  {
    struct timerequest *TimerIO;

    if((TimerIO = (struct timerequest *) CreateIORequest(TimerMP,
    sizeof(struct timerequest))))
    {
      if(!OpenDevice("timer.device",UNIT_VBLANK,
      (struct IORequest *)TimerIO,0))
      {
        struct UtilityBase *utilitybase;

        TimerIO->tr_node.io_Command = TR_GETSYSTIME;
        DoIO((struct IORequest *) TimerIO);
        if((utilitybase = (struct UtilityBase *) OpenLibrary("utility.library",37)))
        {
          struct ClockData dat;

	  ASSIGN_UTIL
          Amiga2Date(TimerIO->tr_time.tv_secs, &dat);
          sprintf(com, "CheckX " VERSION "." REVISION " - %02ld.%02ld.%ld", dat.mday, dat.month, dat.year);
	  CloseLibrary((struct Library *) utilitybase);
	  SetComment(name, com);
        }
        CloseDevice((struct IORequest *) TimerIO);
      }
      DeleteIORequest(TimerIO);
    }
    DeleteMsgPort(TimerMP);
  }
}

/* This scans a directory and calls DoFileOpen for every file. It
   automatically creates SAVE destination directories when necessary. */
static LONG DoDirectoryScan(STRPTR name, STRPTR sav, struct FileData *fd)
{
  struct AnchorPath *APath;
  LONG error = CHKXERR_SCANERR;
  ULONG retval;

  if(!(fd->fd_Flags & CHECKXFLAG_SAVE) || !sav ||
  (fd->fd_SaveDirL = Lock(sav, SHARED_LOCK)))
  {
    if((APath = (struct AnchorPath *) AllocMem(sizeof(struct AnchorPath) +
    512, MEMF_PUBLIC|MEMF_CLEAR)))
    {
      fd->fd_Name = APath->ap_Buf;
      APath->ap_Strlen = 256;
      for(retval = MatchFirst(name, APath); !retval;
      retval = MatchNext(APath))
      {
        if(APath->ap_Flags & APF_DIDDIR)
        {
          OpenParentDir(fd);
          APath->ap_Flags &= ~APF_DIDDIR; /* clear flag */
        }
        else if(APath->ap_Info.fib_DirEntryType > 0)
        {
          if(fd->fd_Flags & CHECKXFLAG_ALL)
          {
            OpenNewDir(fd, APath->ap_Info.fib_FileName);
            APath->ap_Flags |= APF_DODIR;
          }
        }
        else
        {
	  fd->fd_Flags &= CHKXCALLFLAGS;
          fd->fd_LinkNum = 0;
          PrintCHKXErr(fd, DoFileOpen(fd));

          while(fd->fd_MemList)
            FreeCrunchMemList(fd, fd->fd_MemList->cml_MemoryRegion);
        }
        if((fd->fd_Flags & CHECKXFLAG_SAVE) && !fd->fd_SaveDirL)
        {
          error = CHKXERR_OPENDIR; break;
        }
        if(SetSignal(0L,0L) & SIGBREAKF_CTRL_C)
        {
          error = CHKXERR_BREAK; break;
        }
      }
      MatchEnd(APath);

      if(retval == ERROR_NO_MORE_ENTRIES)
        error = 0;

      FreeMem(APath, sizeof(struct AnchorPath) + 512);
    }
    else
      error = CHKXERR_NOMEMORY;

    if(sav && fd->fd_SaveDirL)
      UnLock(fd->fd_SaveDirL);
  }
  else
    error = CHKXERR_OPENDIR;

  if(error)
    PrintCHKXErr(fd, error);

  return error;
}

/* Open a file and call DoGetVirus to scan */
static LONG DoFileOpen(struct FileData *fd)
{
  struct FileInfoBlock *fib;
  LONG ret = 0;

  if((fib = (struct FileInfoBlock *) AllocDosObject(DOS_FIB, 0)))
  {
    ULONG fh;

    if((fh = Open(fd->fd_Name, MODE_OLDFILE)))
    {
      if(ExamineFH(fh, fib))
      {
        APTR mem;

	if(!fib->fib_Size)
          ret = CHKXERR_EMPTY;
        else if((mem = AllocMem(fib->fib_Size, MEMF_ANY)))
        {
          if(Read(fh, mem, fib->fib_Size) != fib->fib_Size)
            ret = CHKXERR_READ;

          if(!ret && !(ret = AddCrunchMemList(fd, mem, fib->fib_Size)))
          {
            if((fd->fd_Flags & CHECKXFLAG_PRINTALL) ||
            ((fd->fd_Flags & CHECKXFLAG_PRINTEXEC) && fib->fib_Size > 4
            && *((ULONG *)mem) == HUNK_HEADER))
              PrintCHKXFile(fd);
            ret = DoGetVirus(fd, mem, fib->fib_Size);
          }
          else
            FreeMem(mem, fib->fib_Size);
        }
        else
          ret = CHKXERR_NOMEMORY;
      }
      else
        ret = CHKXERR_EXAMINEERR;
      Close(fh);
    }
    else
      ret = CHKXERR_OPENERR;
  
    FreeDosObject(DOS_FIB, fib);
  }
  else
    ret = CHKXERR_NOMEMORY;

  return ret;
}

/* for SAVE option: open parent directory and try to delete the directory
   we leave. This only succeeds, when the directory is empty. */
static ULONG OpenParentDir(struct FileData *fd)
{
  ULONG g;
  UBYTE name[300];

  if((g = fd->fd_SaveDirL))
  {
    NameFromLock(g, name, 300);
    fd->fd_SaveDirL = ParentDir(g);
    UnLock(g);

    DeleteFile(name);
  }
  else
    return -1;

  return fd->fd_SaveDirL;
}

/* Open a new subdirectory for SAVE option */
static ULONG OpenNewDir(struct FileData *fd, STRPTR name)
{
  ULONG g;

  if(fd->fd_SaveDirL)
  {
    g = CurrentDir(fd->fd_SaveDirL);
    if(!(fd->fd_SaveDirL = Lock(name, SHARED_LOCK)))
      if((fd->fd_SaveDirL = CreateDir(name)))
        ChangeMode(CHANGE_LOCK, fd->fd_SaveDirL, SHARED_LOCK);
    UnLock(CurrentDir(g));
  }
  else
    return -1;

  return fd->fd_SaveDirL;
}

static LONG DoGetVirus(struct FileData *fd, APTR buffer, ULONG buflength)
{
  if(!buflength) /* restarted with empty file */
    return CHKXERR_EMPTY;

  if(fd->fd_Flags & CHECKXFLAG_XVSLIB)
  {
    APTR mem;
      
    if((mem = AllocMem(buflength, MEMF_ANY)))
    {
      struct xvsFileInfo *fi;
      if((fi = (struct xvsFileInfo *) xvsAllocObject(XVSOBJ_FILEINFO)))
      {
        ULONG i;
	/* xvs may modify the buffer! */
        CopyMem(buffer, mem, buflength);
        fi->xvsfi_File = mem;
        fi->xvsfi_FileLen = buflength;
        i = xvsCheckFile(fi);
        if(i == XVSFT_DATAVIRUS)
        {
 	  PrintCHKXTxt(fd, "Data-Virus '%s'", fi->xvsfi_Name);
 	  ++fd->fd_NumVirus;
 	}
        else if(i == XVSFT_FILEVIRUS)
        {
 	  PrintCHKXTxt(fd, "File-Virus '%s'", fi->xvsfi_Name);
 	  ++fd->fd_NumVirus;
 	}
        else if(i == XVSFT_LINKVIRUS)
        {
 	  PrintCHKXTxt(fd, "Link-Virus '%s'", fi->xvsfi_Name);
 	  ++fd->fd_NumVirus;
 	}
        xvsFreeObject(fi);
      }
      else
        PrintCHKXErr(fd, CHKXERR_NOVIRUS);
      FreeMem(mem, buflength);
    }
    else
      PrintCHKXErr(fd, CHKXERR_NOVIRUS);
  }

  return DoFileUnArchive(fd, buffer, buflength);
}

/* For bootblocks */
ASM(LONG) OutHookCheckX(REG(a0, struct Hook *hook), REG(a1, struct xadHookParam *hp))
{
  ULONG i;

  switch(hp->xhp_Command)
  {
  case XADHC_WRITE:
    if((i = 1024-hp->xhp_DataPos) > hp->xhp_BufferSize)
      i = hp->xhp_BufferSize;
    CopyMem(hp->xhp_BufferPtr, ((STRPTR)(hook->h_Data))+hp->xhp_DataPos, i);
    hp->xhp_DataPos += i;
    if(hp->xhp_DataPos == 1024)
      return 10000; /* CheckX own error code for error detection */
  case XADHC_INIT:
  case XADHC_FREE:
  case XADHC_ABORT:
    break;
  default: return XADERR_NOTSUPPORTED;
  }

  return 0;
}

/* To get real file size */
ASM(LONG) OutHookCheckXSize(REG(a0, struct Hook *hook), REG(a1, struct xadHookParam *hp))
{
  switch(hp->xhp_Command)
  {
  case XADHC_WRITE:
    hp->xhp_DataPos += hp->xhp_BufferSize;
    hook->h_Data = (APTR) hp->xhp_DataPos;
  case XADHC_INIT:
  case XADHC_FREE:
  case XADHC_ABORT:
    break;
  default: return XADERR_NOTSUPPORTED;
  }

  return 0;
}

static void CheckArcFiles(struct FileData *fd, struct xadArchiveInfo *ai, ULONG disk)
{
  struct xadFileInfo *fi;
  struct TagItem ti[4];

  ti[0].ti_Tag = XAD_OUTMEMORY;
  ti[1].ti_Tag = XAD_OUTSIZE;
  ti[2].ti_Tag = XAD_ENTRYNUMBER;
  ti[3].ti_Tag = TAG_DONE;

  fi = ai->xai_FileInfo;
  while(!(SetSignal(0L,0L) & SIGBREAKF_CTRL_C) && fi)
  {
    APTR dest;

    if(!(fi->xfi_Flags & (XADFIF_DIRECTORY|XADFIF_LINK)))
    {
      LONG err = 0;
      ULONG flags, size;

      flags = fd->fd_Flags;
      if(!(fi->xfi_Flags & XADFIF_NOFILENAME))
        fd->fd_Flags &= ~CHECKXFLAG_NAMEPRINTED;
      fd->fd_Name = fi->xfi_FileName;
      fd->fd_RecurseDepth++;
      fd->fd_ArchiveDepth++;

      size = fi->xfi_Size;
      if(fi->xfi_Flags & XADFIF_NOUNCRUNCHSIZE)
      {
	struct Hook hook = {{0,0},(ULONG (*)()) OutHookCheckXSize, 0, 0};
  	struct TagItem ti[3];

	ti[0].ti_Tag = XAD_OUTHOOK;
	ti[0].ti_Data = (ULONG) &hook;
  	ti[1].ti_Tag = XAD_ENTRYNUMBER;
	ti[1].ti_Data = fi->xfi_EntryNumber;
  	ti[2].ti_Tag = TAG_DONE;

        if(!(err = (disk ? xadDiskFileUnArcA(ai, ti) : xadFileUnArcA(ai, ti))))
	  size = (ULONG) hook.h_Data;
      }

      if(!size)
      {
        if(!err)
          err = CHKXERR_EMPTY;
      }
      else if((dest = AllocMem(size, MEMF_PUBLIC)))
      {
        if(!(err = AddCrunchMemList(fd, dest, size)))
        {
	  ti[0].ti_Data = (ULONG) dest;
	  ti[1].ti_Data = size;
	  ti[2].ti_Data = fi->xfi_EntryNumber;

          if((err = disk ? xadDiskFileUnArcA(ai, ti) : xadFileUnArcA(ai, ti)))
	  {
	    if(disk)
	      err = CHKXERR_READ;
	    else
              err += XADERR_OFFSET;
          }
          else
          {
            if((fd->fd_Flags & CHECKXFLAG_PRINTALL) ||
	    ((fd->fd_Flags & CHECKXFLAG_PRINTEXEC) && size > 4
	    && *((ULONG *)dest) == HUNK_HEADER))
              PrintCHKXFile(fd);
            err = DoGetVirus(fd, dest, size);
          }
          FreeCrunchMemList(fd, dest);
        }
        else
          FreeMem(dest, size);
      }
      else
        err = CHKXERR_NOMEMORY;

      if(err)
      {
        if(fi->xfi_Flags & XADFIF_NOFILENAME)
          fd->fd_Flags &= ~CHECKXFLAG_NAMEPRINTED;
        PrintCHKXErr(fd, err);
      }

      --fd->fd_RecurseDepth;
      --fd->fd_ArchiveDepth;
      fd->fd_Flags = flags;
    }
    fi = fi->xfi_Next;
  }
}

/* Tests if a file is an archive. When yes, the archive is unarchived and
   DoDirectoryScan is called for the result files. Else DoFileUnLink is
   called.
*/
static LONG DoFileUnArchive(struct FileData *fd, APTR buffer, ULONG buflength)
{
  LONG err = 0;
  struct xadArchiveInfo *ai;

  if((ai = (struct xadArchiveInfo *) xadAllocObject(XADOBJ_ARCHIVEINFO, 0)))
  {
    STRPTR arcname;

    arcname = fd->fd_Name;
    if(!xadGetInfo(ai, XAD_INMEMORY, buffer, XAD_INSIZE, buflength,
    TAG_DONE))
    {
      if(ai->xai_Flags & XADAIF_FILECORRUPT)
      {
        ++fd->fd_CorruptedArchives;
        PrintCHKXTxt(fd, "%s (ARCHIVE, CORRUPTED)", ai->xai_Client->xc_ArchiverName);
      }
      else
        PrintCHKXTxt(fd, "%s (ARCHIVE)", ai->xai_Client->xc_ArchiverName);
        
      if(!(!(fd->fd_Flags & CHECKXFLAG_NOUNARCHIVE) && ai->xai_FileInfo ||
      !(fd->fd_Flags & CHECKXFLAG_NOUNTRACK) && ai->xai_DiskInfo))
        err = DoFileUnLink(fd, buffer, buflength);
      else if(!OpenNewDir(fd, FilePart(fd->fd_Name)))
        err = CHKXERR_OPENERR;
      else
      {
	if(!(fd->fd_Flags & CHECKXFLAG_NOUNARCHIVE) && ai->xai_FileInfo)
	  CheckArcFiles(fd, ai, 0);
	if(!(fd->fd_Flags & CHECKXFLAG_NOUNTRACK) && ai->xai_DiskInfo)
	{
	  STRPTR buf;

	  /* reduce stacksize, as we have a recursive program */
	  if((buf = (STRPTR) AllocMem(1024, MEMF_PUBLIC)))
	  {
            ULONG flags;
	    struct xadDiskInfo *di;
	    struct xadArchiveInfo *aid;
	    di = ai->xai_DiskInfo;

            flags = fd->fd_Flags;
	    while(!(SetSignal(0L,0L) & SIGBREAKF_CTRL_C) && di)
	    {
	      if(di->xdi_SectorSize != 512 || (di->xdi_TrackSectors != 11 &&
	      di->xdi_TrackSectors != 22) || (!(di->xdi_Flags & XADDIF_NOHEADS) &&
	      di->xdi_Heads != 2) || (!(di->xdi_Flags & XADDIF_NOCYLINDERS) &&
	      di->xdi_Cylinders != 80))
	        sprintf(buf, "-disk image %ld", di->xdi_EntryNumber);
	      else
	      {
	        if(!(di->xdi_Flags & (XADDIF_NOHIGHCYL|XADDIF_NOLOWCYL)) &&
	 	(di->xdi_LowCyl || di->xdi_HighCyl != 79))
            	  sprintf(buf, "-disk image %ld (%s, %ld to %ld)",
            	  di->xdi_EntryNumber, di->xdi_TrackSectors == 22 ?
            	  "HD" : "DD", di->xdi_LowCyl, di->xdi_HighCyl);
                else
            	  sprintf(buf, "-disk image %ld (%s)",
            	  di->xdi_EntryNumber, di->xdi_TrackSectors == 22 ? "HD" : "DD");
              }
      
              fd->fd_Flags &= ~CHECKXFLAG_NAMEPRINTED;
	      fd->fd_Name = buf;
              PrintCHKXFile(fd);

	      if(di->xdi_TextInfo)
	      {
                struct xadTextInfo *ti;
                ULONG flags, i = 1;

	        for(ti = di->xdi_TextInfo; ti; ti = ti->xti_Next)
	        {
	          if(ti->xti_Size && ti->xti_Text)
	          {
                    flags = fd->fd_Flags;
                    fd->fd_Flags &= ~CHECKXFLAG_NAMEPRINTED;
                    fd->fd_Flags |= CHECKXFLAG_NOFREEMEM;
                    fd->fd_RecurseDepth++;
                    sprintf(buf, "--infotext %ld (size %ld)", i, ti->xti_Size);
                    if((fd->fd_Flags & CHECKXFLAG_PRINTALL) ||
                    ((fd->fd_Flags & CHECKXFLAG_PRINTEXEC) && ti->xti_Size > 4
                    && *((ULONG *)(ti->xti_Text)) == HUNK_HEADER))
                      PrintCHKXFile(fd);
                    PrintCHKXErr(fd, DoGetVirus(fd, ti->xti_Text, ti->xti_Size));
                    --fd->fd_RecurseDepth;
                    fd->fd_Flags = flags;
                  }
                  ++i;
                }
              }

	      /* check bootblock here - special outhook for bootblock extraction */
	      if(!di->xdi_LowCyl) /* boot check only, when first block! */
	      {
	        struct Hook hook = {{0,0},(ULONG (*)()) OutHookCheckX, 0};

	        hook.h_Data = buf;
	        if(xadDiskUnArc(ai, XAD_ENTRYNUMBER, di->xdi_EntryNumber,
	        XAD_OUTHOOK, &hook, TAG_DONE) == 10000) /* special hook return code! */
	        {
	          struct xvsBootInfo *bi;

	          if((bi = (struct xvsBootInfo *) xvsAllocObject(XVSOBJ_BOOTINFO)))
      		  {
        	    bi->xvsbi_Bootblock = buf;
	            if(xvsCheckBootblock(bi) == XVSBT_VIRUS)
	            {
	              PrintCHKXTxt(fd, "Boot-Virus '%s'", bi->xvsbi_Name);
	              ++fd->fd_NumVirus;
	            }
	            xvsFreeObject(bi);
      		  }
      		  else
      		    PrintCHKXErr(fd, CHKXERR_NOBOOTVIRUS);
      		}
      		else
      		  PrintCHKXErr(fd, CHKXERR_NOBOOTVIRUS);
	      }
	
	      if((aid = (struct xadArchiveInfo *) xadAllocObject(XADOBJ_ARCHIVEINFO, 0)))
	      {
	        struct TagItem ti[4];
	        struct xadClient *cl;
	  
	  	ti[0].ti_Tag = XAD_INMEMORY;
	  	ti[0].ti_Data = (ULONG) buffer;
	  	ti[1].ti_Tag = XAD_INSIZE;
	  	ti[1].ti_Data = buflength;
	  	ti[2].ti_Tag = XAD_ENTRYNUMBER;
	  	ti[2].ti_Data = di->xdi_EntryNumber;
	  	ti[3].ti_Tag = TAG_DONE;
	
	  	if(!(err = xadGetDiskInfo(aid, XAD_INDISKARCHIVE, ti, TAG_DONE)))
	  	{
	  	  if(aid->xai_Flags & XADAIF_FILECORRUPT)
	  	  {
                    ++fd->fd_CorruptedArchives;
                    PrintCHKXTxt(fd, "%s (FILESYSTEM, CORRUPTED)", aid->xai_Client->xc_ArchiverName);
	  	  }
                  else
                    PrintCHKXTxt(fd, "%s (FILESYSTEM)", aid->xai_Client->xc_ArchiverName);
		  CheckArcFiles(fd, aid, 1);
		  cl = aid->xai_Client->xc_Next;
		  xadFreeInfo(aid);
		  while(cl) /* multiple filesystems */
		  {
	  	    if(!xadGetDiskInfo(aid, XAD_INDISKARCHIVE, ti, XAD_STARTCLIENT, cl, TAG_DONE))
	  	    {
	  	      if(aid->xai_Flags & XADAIF_FILECORRUPT)
	  	      {
                        ++fd->fd_CorruptedArchives;
                        PrintCHKXTxt(fd, "%s (FILESYSTEM, CORRUPTED)", aid->xai_Client->xc_ArchiverName);
	  	      }
                      else
                        PrintCHKXTxt(fd, "%s (FILESYSTEM)", aid->xai_Client->xc_ArchiverName);
		      CheckArcFiles(fd, aid, 1);
		      cl = aid->xai_Client->xc_Next;
		      xadFreeInfo(aid);
		    }
		    else
		      cl = 0;
		  }
	  	}
	  	else
	  	{
	  	  if(err != XADERR_FILESYSTEM)
	  	    PrintCHKXErr(fd, XADERR_OFFSET + err);
	  	  err = 0;
	  	}
	  	xadFreeObjectA(aid, 0);
	      }

              di = di->xdi_Next;
            } /* while */
            fd->fd_Flags = flags;
            FreeMem(buf, 1024);
          } /* AllocMem buffer */
          else
            err = CHKXERR_NOMEMORY;
        } /* is there a disk entry? */
        if(!OpenParentDir(fd))
          err = CHKXERR_OPENDIR;
      }

      xadFreeInfo(ai);
    } /* xadGetInfo */
    else if(!xadGetDiskInfo(ai, XAD_INMEMORY, buffer, XAD_INSIZE, buflength, TAG_DONE))
    {
      struct xadClient *cl;
      ULONG isvalid = 1;

      if(ai->xai_Flags & XADAIF_FILECORRUPT)
      {
        ++fd->fd_CorruptedArchives;
        PrintCHKXTxt(fd, "%s (FILESYSTEM, CORRUPTED)", ai->xai_Client->xc_ArchiverName);
      }
      else
        PrintCHKXTxt(fd, "%s (FILESYSTEM)", ai->xai_Client->xc_ArchiverName);
        
      if((fd->fd_Flags & CHECKXFLAG_NOUNTRACK) || !ai->xai_FileInfo)
        err = DoFileUnLink(fd, buffer, buflength);
      else if(!OpenNewDir(fd, FilePart(fd->fd_Name)))
	err = CHKXERR_OPENERR;
      else
      {
	CheckArcFiles(fd, ai, 1);
	while(isvalid && (cl = ai->xai_Client->xc_Next))
	{
          xadFreeInfo(ai);
          if(!xadGetDiskInfo(ai, XAD_INMEMORY, buffer, XAD_INSIZE, buflength, XAD_STARTCLIENT, cl, TAG_DONE))
          {
            if(ai->xai_Flags & XADAIF_FILECORRUPT)
            {
              ++fd->fd_CorruptedArchives;
              PrintCHKXTxt(fd, "%s (FILESYSTEM, CORRUPTED)", ai->xai_Client->xc_ArchiverName);
            }
            else
              PrintCHKXTxt(fd, "%s (FILESYSTEM)", ai->xai_Client->xc_ArchiverName);
	    CheckArcFiles(fd, ai, 1);
          }
          else
            isvalid = 0;
        } 
        if(!OpenParentDir(fd))
          err = CHKXERR_OPENDIR;
      }
      if(isvalid)
        xadFreeInfo(ai);
    }
    else
      err = DoFileUnLink(fd, buffer, buflength);

    fd->fd_Name = arcname;
    xadFreeObjectA(ai, 0);
  }
  else
    err = CHKXERR_NOMEMORY;

  return err;
}

/* Tries to unlink a file. When the file was linked, we call DoGetVirus
   for the two parts to check if they may be archives, else we call
   DoFileUnCrunch.
*/
static LONG DoFileUnLink(struct FileData *fd, APTR buffer, ULONG buflength)
{
  LONG ret = CHKXERR_NOMEMORY;
  struct xfdLinkerInfo *xli;

  if((xli = (struct xfdLinkerInfo *) xfdAllocObject(XFDOBJ_LINKERINFO)))
  {
    xli->xfdli_Buffer = buffer;
    xli->xfdli_BufLen = buflength;
    if(xfdRecogLinker(xli))
    {
      PrintCHKXTxt(fd, xli->xfdli_LinkerName);
      if(fd->fd_Flags & CHECKXFLAG_NOUNLINK)
        ret = DoFileUnCrunch(fd, buffer, buflength);
      else if(xfdUnlink(xli))
      {
        ULONG flags;
	fd->fd_Flags |= CHECKXFLAG_LINKED;
        ++fd->fd_RecurseDepth;
        ++fd->fd_LinkNum;
	flags = fd->fd_Flags;
	fd->fd_Flags |= CHECKXFLAG_NOFREEMEM;
        PrintCHKXErr(fd, DoGetVirus(fd, xli->xfdli_Save1,
        xli->xfdli_SaveLen1));
        fd->fd_Flags = flags; /* CHECKXFLAG_NOFREEMEM is cleared */
        PrintCHKXErr(fd, DoGetVirus(fd, xli->xfdli_Save2,
        xli->xfdli_SaveLen2));
        ret = 0;
        --fd->fd_RecurseDepth;
      }
      else
        ret = XFDERR_OFFSET + xli->xfdli_Error;
    }
    else
      ret = DoFileUnCrunch(fd, buffer, buflength);

    xfdFreeObject(xli);
  }
  return ret;
}

/* Tries to decrunch a file. When it is crunched, we decrunch it and call
   DoGetVirus to start the loop again. Else we call unstripping.
*/
static LONG DoFileUnCrunch(struct FileData *fd, APTR buffer, ULONG buflength)
{
  LONG ret = CHKXERR_NOMEMORY;
  struct xfdBufferInfo *xbi;

  if((xbi = (struct xfdBufferInfo *) xfdAllocObject(XFDOBJ_BUFFERINFO)))
  {
    xbi->xfdbi_SourceBuffer = buffer;
    xbi->xfdbi_SourceBufLen = buflength;
    xbi->xfdbi_Flags = XFDFF_RECOGEXTERN;
    if(xfdRecogBuffer(xbi))
    {
      struct Library *xpkbase;
      STRPTR buf = 0;
      ULONG buflen = 0;

      PrintCHKXTxt(fd, xbi->xfdbi_PackerFlags & XFDPFF_ADDR ? "%s (ADDRESS)" :
      "%s", xbi->xfdbi_PackerName);

      if(fd->fd_Flags & CHECKXFLAG_ASKPWD && (xpkbase =
      OpenLibrary(XPKNAME, 4)))
      {
	ASSIGN_XPK
        if(xbi->xfdbi_PackerFlags & XFDPFF_PASSWORD)
        {
          buflen = (xbi->xfdbi_MaxSpecialLen == 0xFFFF) ? 256 :
	    xbi->xfdbi_MaxSpecialLen;
          if((buf = (STRPTR) AllocMem(buflen, MEMF_ANY|MEMF_CLEAR)))
	  {
            if(!XpkPassRequestTags(XPK_PasswordBuf, buf,
            XPK_PassBufSize, buflen, TAG_DONE))
	      xbi->xfdbi_Special = buf;
	  }
        }
        else if(xbi->xfdbi_PackerFlags & XFDPFF_KEY16)
        {
	  if(!XpkPassRequestTags(XPK_Key16BitPtr, &buflen, TAG_DONE))
	    xbi->xfdbi_Special = &buflen;
        }
        else if(xbi->xfdbi_PackerFlags & XFDPFF_KEY32)
        {
	  if(!XpkPassRequestTags(XPK_Key32BitPtr, &buflen, TAG_DONE))
            xbi->xfdbi_Special = &buflen;
        }
	CloseLibrary(xpkbase);
      }
      if(fd->fd_Flags & CHECKXFLAG_NODECRUNCH)
        ret = DoFileStrip(fd, buffer, buflength);
      else if(xfdDecrunchBuffer(xbi))
      {
        if((xbi->xfdbi_PackerFlags & XFDPFF_ADDR) &&
        !(fd->fd_Flags & CHECKXFLAG_ADDRESS))
        {
	  fd->fd_Flags |= CHECKXFLAG_ADDRESS;
          PrintCHKXErr(fd, SaveUncrFile(fd, buffer, buflength));
        }
	fd->fd_Flags |= CHECKXFLAG_CRUNCHED;
        FreeCrunchMemList(fd, buffer);
        ++fd->fd_RecurseDepth;
        if(!(ret = AddCrunchMemList(fd, xbi->xfdbi_TargetBuffer,
        xbi->xfdbi_TargetBufLen)))
        {
          PrintCHKXErr(fd, DoGetVirus(fd, xbi->xfdbi_TargetBuffer,
            xbi->xfdbi_TargetBufSaveLen));
        }
        else
          FreeMem(xbi->xfdbi_TargetBuffer, xbi->xfdbi_TargetBufLen);
        --fd->fd_RecurseDepth;
      }
      else
        ret = XFDERR_OFFSET + xbi->xfdbi_Error;

      if(buf)
        FreeMem(buf, buflen);
    }
    else
      ret = DoFileStrip(fd, buffer, buflength);

    xfdFreeObject(xbi);
  }
  return ret;
}

/* Tries to strip useless hunks in a file. When there are some, we remove
   them and call DoGetVirus to start the loop again. Else we finish.
   When either unlinking or uncrunching happend in before loops, we may
   save the file when there was SAVE option.
*/
static LONG DoFileStrip(struct FileData *fd, APTR buffer, ULONG buflength)
{
  LONG ret = 0;
  ULONG reslength = buflength;

  if(*((ULONG *) buffer) == 0x000003F3 && !(fd->fd_Flags &
  CHECKXFLAG_NOSTRIP))
    xfdStripHunks(buffer, buflength, &reslength,
    XFDSHF_NAME|XFDSHF_SYMBOL|XFDSHF_DEBUG);
    /* errors are not interpreted */

  if(buflength > reslength)
  {
    fd->fd_Flags |= CHECKXFLAG_STRIPPED;
    PrintCHKXTxt(fd, "%ld bytes stripped", buflength-reslength);
    ret = DoGetVirus(fd, buffer, reslength);
  }
  else if(!(fd->fd_Flags & CHECKXFLAG_ADDRESS))
    ret = SaveUncrFile(fd, buffer, buflength);

  FreeCrunchMemList(fd, buffer);

  return ret;
}

/* Print file name */
static void PrintCHKXFile(struct FileData *fd)
{
  STRPTR name = fd->fd_Name;
  UBYTE i;

  if(fd->fd_Flags & CHECKXFLAG_DEBUG)
  {
    for(i = 0; i < fd->fd_ArchiveDepth; ++i)
      KPutC('*');
    KPrintf("%s\n", name);
  }

  if(fd->fd_LogFileFH)
  {
    for(i = 0; i < fd->fd_ArchiveDepth; ++i)
      FPutC(fd->fd_LogFileFH, '*');
    FPrintf(fd->fd_LogFileFH, "%s\n", name);
  }
  if(!(fd->fd_Flags & CHECKXFLAG_QUIET))
  {
    for(i = 0; i < fd->fd_ArchiveDepth; ++i)
      FPutC(Output(), '*');
    Printf("%s\n", name);
  }
  fd->fd_Flags |= CHECKXFLAG_NAMEPRINTED;
}

static void PrintCHKXErr(struct FileData *fd, LONG err)
{
  if(err)
  {
    STRPTR txt = 0, txt2;

    if(err > XFDERR_OFFSET)
    {
      ++fd->fd_XFDErrors;
      txt2 = "XFD-Error %ld: %s";
      err -= XFDERR_OFFSET;
      txt = xfdGetErrorText(err);
    }
    else if(err > XADERR_OFFSET)
    {
      ++fd->fd_XADErrors;
      txt2 = "XAD-Error %ld: %s";
      err -= XADERR_OFFSET;
      txt = xadGetErrorText(err);
    }
    else if(err > CHXWARN_OFFSET)
    {
      txt2 = "CheckX-Warning %ld: %s";
      switch(err)
      {
      case CHKXWARN_NOVIRUS:	txt = "Virus-Checking disabled!";						break;
      case CHKXWARN_XVSSELFTEST:txt = "The xvs.library is modified, maybe the system is virus infected!";	break;
      case CHKXWARN_MEMVIRUS:	txt = "Your system memory was virus infected!";					break;
      }
      err -= CHXWARN_OFFSET;
    }
    else
    {
      ++fd->fd_CHKXErrors;
      txt2 = "CheckX-Error %ld: %s";
      switch(err)
      {
      case CHKXERR_NOMEMORY:	txt = "not enough memory";			break;
      case CHKXERR_EXAMINEERR:	txt = "examining failed";			break;
      case CHKXERR_OPENERR:	txt = "opening file failed";			break;
      case CHKXERR_READ:	txt = "reading failed";				break;
      case CHKXERR_SCANERR:	txt = "directory scan failed";			break;
      case CHKXERR_BREAK:	txt = "user break";				break;
      case CHKXERR_OPENDIR:	txt = "opening directory failed";		break;
      case CHKXERR_RESOURCE:	txt = "needed resource not available";		break;
      case CHKXERR_NOBOOTVIRUS:	txt = "could not check for bootblock virus";	break;
      case CHKXERR_WRITE:	txt = "writing failed";				break;
      case CHKXERR_NOVIRUS:	txt = "could not check for virus";		break;
      case CHKXERR_EMPTY:	txt = "file is empty";				break;
      }
    }

    PrintCHKXTxt(fd, txt2, err, txt);
  }
}

/* Print type text */
static void PrintCHKXTxt(struct FileData *fd, STRPTR txt, ...)
{
  UBYTE i;

  if(!(fd->fd_Flags & CHECKXFLAG_NAMEPRINTED))
    PrintCHKXFile(fd);

  if(fd->fd_Flags & CHECKXFLAG_DEBUG)
  {
    for(i = 0; i < fd->fd_RecurseDepth; ++i)
      KPutC(' ');
    RawDoFmt(txt, &txt+1, (void (*)()) KPutC, 0);
    KPutC('\n');
  }

  if(fd->fd_LogFileFH)
  {
    for(i = 0; i < fd->fd_RecurseDepth; ++i)
      FPutC(fd->fd_LogFileFH, ' ');
    VFPrintf(fd->fd_LogFileFH, txt, &txt+1);
    FPutC(fd->fd_LogFileFH, '\n');
    Flush(fd->fd_LogFileFH);
  }

  if(!(fd->fd_Flags & CHECKXFLAG_QUIET))
  {
    for(i = 0; i < fd->fd_RecurseDepth; ++i)
      FPutC(Output(), ' ');
    VPrintf(txt, &txt+1);
    FPutC(Output(), '\n');
  }
}

/* Add memory to the memory list. */
static LONG AddCrunchMemList(struct FileData *fd, APTR reg, ULONG size)
{
  struct CrunchMemList *ml;

  if((ml = (struct CrunchMemList *) AllocMem(sizeof(struct CrunchMemList),
  MEMF_ANY)))
  {
    ml->cml_Next = fd->fd_MemList;
    ml->cml_MemoryRegion = reg;
    ml->cml_MemorySize = size;
    fd->fd_MemList = ml;
    return 0;
  }

  return CHKXERR_NOMEMORY;
}

/* Free memory from the memory list. */
static void FreeCrunchMemList(struct FileData *fd, APTR reg)
{
  struct CrunchMemList mc, *ml = &mc;

  if(fd->fd_Flags & CHECKXFLAG_NOFREEMEM)
    return;

  for(mc.cml_Next = fd->fd_MemList; ml; ml = ml->cml_Next)
  {
    if(ml->cml_Next->cml_MemoryRegion == reg)
    {
      struct CrunchMemList *m;
      m = ml->cml_Next;
      ml->cml_Next = m->cml_Next;
      FreeMem(m->cml_MemoryRegion, m->cml_MemorySize);
      FreeMem(m, sizeof(struct CrunchMemList));
    }
  }

  fd->fd_MemList = mc.cml_Next;
}

/* Save file, when SAVE option and file was crunched or linked. */
static LONG SaveUncrFile(struct FileData *fd, APTR buf, ULONG size)
{
  BPTR filefh, cd;
  LONG ret = 0, i = 0;
  UBYTE name[50];
  UBYTE nbuf[256];

  if(!(fd->fd_SaveDirL && (fd->fd_Flags & CHKXSAVEFLAGS)))
    return 0;

  sprintf(name, (fd->fd_LinkNum ? "%s.%ld" : "%s"), FilePart(fd->fd_Name),
  fd->fd_LinkNum++);

  cd = CurrentDir(fd->fd_SaveDirL);

  while(!ret && name[i])
  {
    for(;name[i] && name[i] != '/'; ++i)
      nbuf[i] = name[i];
    if(name[i] == '/')
    {
      nbuf[i] = 0;
      if((filefh = Lock(nbuf, SHARED_LOCK)))
        UnLock(filefh);
      else
      {
        if((filefh = CreateDir(nbuf)))
          UnLock(filefh);
        else
          ret = CHKXERR_OPENERR;
      }
      nbuf[i] = name[i];
      ++i;
    }
  }

  if(!ret)
  {
    if((filefh = Open(name, MODE_NEWFILE)))
    {
      if(Write(filefh, buf, size) != size)
        ret = CHKXERR_WRITE;
      Close(filefh);
    }
    else
      ret = CHKXERR_OPENERR;
  }

  CurrentDir(cd);

  return ret;
}

