#define NAME         "xadUnDisk"
#define DISTRIBUTION "(Freeware) "
#define REVISION     "7"
#define DATE	     "19.03.2000"

/* Programmheader

	Name:		xadUnDisk
	Author:		SDI
	Distribution:	Freeware
	Description:	dearchives disk archives
	Compileropts:	-
	Linkeropts:	-gsi -l amiga

 1.0   18.11.98 : first version
 1.1   23.12.98 : added support for FILEDESCRIPT
 1.2   04.02.99 : added file corrupt message, new InfoText system and SAVETEXTS
 1.3   16.02.99 : bug fix in report hook
 1.4   28.02.99 : little bug fix in chunk hook
 1.5   15.09.99 : added insert disk question
 1.6   20.02.00 : added FORMAT keyword and ADF to disk
 1.7   19.03.00 : bug fix
*/

#include <proto/xadmaster.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/utility.h>
#include <exec/memory.h>
#include <devices/trackdisk.h>
#include <dos/dosasl.h>
#include <dos/filehandler.h>
#include <utility/hooks.h>
#include "SDI_defines.h"
#include "SDI_compiler.h"
#define SDI_TO_ANSI
#include "SDI_ASM_STD_protos.h"

struct xadMasterBase *   xadMasterBase = 0;
struct DosLibrary *	 DOSBase = 0;
struct ExecBase *	 SysBase  = 0;

#define PARAM	"FROM/A,TO,LOWCYL/N,HIGHCYL/N,ENTRY/N,PASSWORD,SAVETEXTS/K," \
		"NE=NOEXTERN/S,INFO=LIST/S,SHOWTEXTS/S,OW=OVERWRITE/S,"	\
		"IG=IGNOREGEOMETRY/S,FORMAT/S,DIMG=DISKIMAGE/S"

struct Args {
  STRPTR from;
  STRPTR to;
  LONG * lowcyl;
  LONG * highcyl;
  LONG * entry;
  STRPTR password;
  STRPTR savetexts;
  ULONG  noextern;
  ULONG  info;
  ULONG  showtexts;
  ULONG  overwrite;
  ULONG  ignoregeometry;
  ULONG  format;
  ULONG  diskimage;
};

ASM(ULONG) SAVEDS progrhook(REG(a0, struct Hook *),
  REG(a1, struct xadProgressInfo *));
struct Hook prhook = {{0,0},(ULONG (*)()) progrhook, 0, 0};
void ShowTexts(struct xadTextInfo *ti);
void SaveTexts(struct xadTextInfo *ti, STRPTR name);
LONG WriteDisk(struct Args *);

ULONG start(void)
{
  ULONG ret = RETURN_FAIL;
  struct DosLibrary *dosbase;

  SysBase = (*((struct ExecBase **) 4));
  { /* test for WB and reply startup-message */
    struct Process *task;
    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)))
  {
    LONG err = 0;
    struct xadMasterBase *xadmasterbase;

    DOSBase = dosbase;
    if((xadmasterbase = (struct xadMasterBase *)
    OpenLibrary("xadmaster.library", 5)))
    {
      LONG def = 1;
      struct Args args;
      struct RDArgs *rda;
      
      memset(&args, 0 , sizeof(struct Args));
      args.entry = &def;

      xadMasterBase = xadmasterbase;
      if((rda = ReadArgs(PARAM, (LONG *) &args, 0)))
      {
	if(args.to || args.info)
	{
	  struct xadArchiveInfo *ai;
	
	  if(args.diskimage)
	  {
	    ret = 0;
	    if(args.to[strlen(args.to)-1] == ':' && stricmp(args.to, "NIL:"))
	      err = WriteDisk(&args);
	    else
	      Printf("Use copy command to copy disk images.\n");
	  }
	  else if((ai = (struct xadArchiveInfo *)
	  xadAllocObjectA(XADOBJ_ARCHIVEINFO, 0)))
	  {
	    if(!(err = xadGetInfo(ai, XAD_INFILENAME, args.from,
	    XAD_NOEXTERN, args.noextern, args.password ? XAD_PASSWORD :
	    TAG_IGNORE, args.password, TAG_DONE)))
	    {
	      if(ai->xai_Flags & XADAIF_FILECORRUPT)
	        Printf("!!! The archive file has some corrupt data. !!!\n");
	      if(args.info)
	      {
	        struct xadDiskInfo *xdi;
	        Printf("ArchiverName:   %s\n", ai->xai_Client->xc_ArchiverName);
#ifdef DEBUG
	        Printf("Password:       %s\n", ai->xai_Password ? ai->xai_Password : "<none>");
	        Printf("InSize:         %ld\n", ai->xai_InSize);
	        Printf("InPos:          %ld\n", ai->xai_InPos);
#endif

	        xdi = ai->xai_DiskInfo;
	        while(xdi)
	        {
		  if(xdi->xdi_EntryNumber != 1 || xdi->xdi_Next)
	            Printf("\nEntry:          %ld\n", xdi->xdi_EntryNumber);
	          Printf("EntryInfo:      %s\n", xdi->xdi_EntryInfo ? xdi->xdi_EntryInfo : "<none>");
#ifdef DEBUG
	          Printf("Flags:          ");
	          if(xdi->xdi_Flags & XADDIF_CRYPTED)
	            Printf("XADDIF_CRYPTED ");
	          Printf(xdi->xdi_Flags ? "\n" : "<none>\n");
#endif
	          Printf("SectorSize:     %ld\n", xdi->xdi_SectorSize);
	          Printf("Sectors:        %ld\n", xdi->xdi_TotalSectors);
	          Printf("Cylinders:      %ld\n", xdi->xdi_Cylinders);
	          Printf("CylSectors:     %ld\n", xdi->xdi_CylSectors);
	          Printf("Heads:          %ld\n", xdi->xdi_Heads);
	          Printf("TrackSectors:   %ld\n", xdi->xdi_TrackSectors);
	          Printf("LowCyl:         %ld\n", xdi->xdi_LowCyl);
	          Printf("HighCyl:        %ld\n", xdi->xdi_HighCyl);
	          if(xdi->xdi_Flags & XADDIF_CRYPTED)
	            Printf("The entry is encrypted\n");
	          if(xdi->xdi_TextInfo)
	          {
	            STRPTR a;
	            struct xadTextInfo *ti;

	            for(ti = xdi->xdi_TextInfo; ti; ti = ti->xti_Next)
	            {
		      a = "TextInfo";
		      if(ti->xti_Flags & XADTIF_BANNER)
		        a = "Banner";
		      else if(ti->xti_Flags & XADTIF_FILEDIZ)
		        a = "DIZ-Text";

		      if(ti->xti_Size && ti->xti_Text)
	                Printf("There is a %s with size %ld.\n", a, ti->xti_Size);
	              else if(ti->xti_Flags & XADTIF_CRYPTED)
	                Printf("There is a crypted %s.\n", a);
	              else
	                Printf("There is an empty %s.\n", a);
	            }
	            if(args.showtexts)
	              ShowTexts(xdi->xdi_TextInfo);
	            if(args.savetexts)
	              SaveTexts(xdi->xdi_TextInfo, args.savetexts);
	          }
	          xdi = xdi->xdi_Next;
	        }
	        ret = 0;
	      }
	      else
	      {
		struct xadDeviceInfo *dvi = 0;

	        if(args.to[strlen(args.to)-1] == ':' && stricmp(args.to, "NIL:"))
	        {
	          if((dvi = (struct xadDeviceInfo *)
	          xadAllocObjectA(XADOBJ_DEVICEINFO, 0)))
	          {
	            args.to[strlen(args.to)-1] = 0; /* strip ':' */
	            dvi->xdi_DOSName = args.to;
	          }
	          else
	            err = XADERR_NOMEMORY;
	        }
	        if(args.showtexts || args.savetexts)
	        {
	          struct xadDiskInfo *xdi = ai->xai_DiskInfo;

	          while(xdi && xdi->xdi_EntryNumber < *args.entry)
	            xdi = xdi->xdi_Next;
	          if(xdi && xdi->xdi_TextInfo)
	          {
	            if(args.showtexts)
	              ShowTexts(xdi->xdi_TextInfo);
	            if(args.savetexts)
	              SaveTexts(xdi->xdi_TextInfo, args.savetexts);
	          }
	        }
		if(dvi)
		{
		  UBYTE r;
		  STRPTR b;
		  
		  for(b = args.to; *b; ++b)
		    *b = toupper(*b);

                  Printf("Insert disk into %s: and press <ENTER> (any other key to abort): ", dvi->xdi_DOSName);
                  Flush(Output());
                  SetMode(Input(), TRUE);
                  r = FGetC(Input());
                  SetMode(Input(), FALSE);
                  if(r != '\r' && r != '\n')
                  {
                    Printf("\n");
                    err = XADERR_BREAK;
                  }
		}
	        if(!err && !(err = xadDiskUnArc(ai, dvi ? XAD_OUTDEVICE :
	        XAD_OUTFILENAME, dvi ? (ULONG) dvi : (ULONG) args.to,
	        XAD_ENTRYNUMBER, *args.entry, args.lowcyl ?
	        XAD_LOWCYLINDER : TAG_IGNORE, args.lowcyl ? *args.lowcyl :
	        0, args.highcyl ? XAD_HIGHCYLINDER : TAG_IGNORE,
	        args.highcyl ? *args.highcyl : 0, XAD_OVERWRITE,
	        args.overwrite, XAD_IGNOREGEOMETRY, args.ignoregeometry,
	        XAD_FORMAT, args.format, XAD_VERIFY, TRUE, XAD_PROGRESSHOOK,
	        &prhook, TAG_DONE)))
	          ret = 0;
	        if(dvi)
	          xadFreeObjectA(dvi, 0);
	      }
	      xadFreeInfo(ai);
	    } /* xadGetInfo */
	    else if(args.to[strlen(args.to)-1] == ':' && stricmp(args.to, "NIL:"))
	    {
	      UBYTE r;

	      ret = 0;
              Printf("Unknown type. Press <I> to write it to disks as image: ");
              Flush(Output());
              SetMode(Input(), TRUE);
              r = FGetC(Input());
              SetMode(Input(), FALSE);
              if(r == 'i' || r == 'I')
	        err = WriteDisk(&args);
	      else
                Printf("\n");
	    }

	    xadFreeObjectA(ai, 0);
          } /* xadAllocObject */
        }
        else
          SetIoErr(ERROR_REQUIRED_ARG_MISSING);

        FreeArgs(rda);
      } /* ReadArgs */

      if(CTRL_C)
        SetIoErr(ERROR_BREAK);

      if(err)
	Printf("An error occured: %s\n", xadGetErrorText(err));
      else if(ret)
        PrintFault(IoErr(), 0);

      CloseLibrary((struct Library *) xadmasterbase);
    } /* OpenLibrary xadmaster */
    else
      Printf("Could not open xadmaster.library\n");
    CloseLibrary((struct Library *) dosbase);
  } /* OpenLibrary dos */
  return ret;
}

ASM(ULONG) SAVEDS progrhook(REG(a0, struct Hook *hook),
REG(a1, struct xadProgressInfo *pi))
{
  ULONG ret = 0;

  switch(pi->xpi_Mode)
  {
  case XADPMODE_ASK:
    {
      UBYTE r;
      if(pi->xpi_Status & XADPIF_OVERWRITE)
      {
        Printf("File already exists, overwrite? (Y|S|\033[1mN\033[0m): ");
        Flush(Output());
        SetMode(Input(), TRUE);
        r = FGetC(Input());
        if(r == 'Y' || r == 'y')
          ret |= XADPIF_OVERWRITE;
        else if(r == 'S' || r == 's')
          ret |= XADPIF_SKIP;
        SetMode(Input(), FALSE);
      }
      if(pi->xpi_Status & XADPIF_IGNOREGEOMETRY)
      {
        Printf("\r\033[KDrive geometry not correct, ignore? (Y|S|\033[1mN\033[0m): ");
        Flush(Output());
        SetMode(Input(), TRUE);
        r = FGetC(Input());
        if(r == 'Y' || r == 'y')
          ret |= XADPIF_IGNOREGEOMETRY;
        else if(r == 'S' || r == 's')
          ret |= XADPIF_SKIP;
        SetMode(Input(), FALSE);
      }
    }
    break;
  case XADPMODE_PROGRESS:
    {
      if(pi->xpi_DiskInfo->xdi_Flags & (XADDIF_NOCYLINDERS|XADDIF_NOCYLSECTORS))
      {
        Printf("\r\033[KWrote %ld of %ld bytes (%ld/%ld sectors)",
        pi->xpi_CurrentSize, pi->xpi_DiskInfo->xdi_TotalSectors*
        pi->xpi_DiskInfo->xdi_SectorSize, pi->xpi_CurrentSize/
        pi->xpi_DiskInfo->xdi_SectorSize, pi->xpi_DiskInfo->xdi_TotalSectors);
      }
      else
      {
        ULONG numcyl, fullsize, curcyl, i;

        i = pi->xpi_DiskInfo->xdi_CylSectors *
            pi->xpi_DiskInfo->xdi_SectorSize;
        numcyl = pi->xpi_HighCyl+1-pi->xpi_LowCyl;
        fullsize = numcyl * i;
        curcyl = pi->xpi_CurrentSize/i;

        Printf("\r\033[KWrote %ld of %ld bytes (%ld/%ld cylinders)",
        pi->xpi_CurrentSize, fullsize, curcyl, numcyl);
      }
      Flush(Output());
    }
    break;
  case XADPMODE_END: 
    if(pi->xpi_DiskInfo->xdi_Flags & (XADDIF_NOCYLINDERS|XADDIF_NOCYLSECTORS))
      Printf("\r\033[KWrote %ld bytes (%ld sectors)\n",
      pi->xpi_CurrentSize, pi->xpi_DiskInfo->xdi_TotalSectors);
    else
      Printf("\r\033[KWrote %ld bytes (%ld cylinders)\n",
      pi->xpi_CurrentSize, pi->xpi_HighCyl+1-pi->xpi_LowCyl);
    break;
  case XADPMODE_ERROR: Printf("\r\033[K");
    break;
  }

  if(!CTRL_C) /* clear ok flag */
    ret |= XADPIF_OK;

  return ret;
}

void ShowTexts(struct xadTextInfo *ti)
{
  ULONG i = 1, j;
  BPTR fh;
  STRPTR a;

  fh = Output();

  while(!CTRL_C && ti)
  {
    if(ti->xti_Size && ti->xti_Text)
    {
      Printf("»»»» TEXTINFO %ld ««««\n", i);
      a = ti->xti_Text;
      for(j = 0; !CTRL_C && j < ti->xti_Size; ++j)
      {
        if(isprint(*a) || *a == '\n' || *a == '\t' || *a == '\033')
          FPutC(fh, *a);
        else
          FPutC(fh, '.');
        ++a;
      }
      if(*(--a) != '\n')
        FPutC(fh, '\n');
    }
    ti = ti->xti_Next;
    ++i;
  }
}

void SaveTexts(struct xadTextInfo *ti, STRPTR name)
{
  UBYTE namebuf[256];
  ULONG i = 1;
  BPTR fh;
  LONG err = 0;

  while(!CTRL_C && ti && !err)
  {
    if(ti->xti_Size && ti->xti_Text)
    {
      sprintf(namebuf, "%s.%ld", name, i);
      if((fh = Open(namebuf, MODE_NEWFILE)))
      {
        if(Write(fh, ti->xti_Text, ti->xti_Size) != ti->xti_Size)
          ++err;
        Close(fh);
      }
      else
        ++err;
    }
    ti = ti->xti_Next;
    ++i;
  }
  if(err)
    Printf("Failed to save information texts.\n");
}

LONG WriteDisk(struct Args *args)
{
  LONG err = XADERR_RESOURCE;
  struct DosList *dol;

  if((dol = LockDosList(LDF_READ|LDF_DEVICES)))
  {
    ULONG i;

    i = strlen(args->to)-1;
    args->to[i] = 0; /* remove ':' */

    if((dol = FindDosEntry(dol, args->to, LDF_DEVICES)))
    {
      struct MsgPort *port;
      
      args->to[i] = ':';
      if((port = CreateMsgPort()))
      {
	struct IOStdReq *ioreq;
	
	if((ioreq = (struct IOStdReq *) CreateIORequest(port, sizeof(struct IOStdReq))))
	{
	  struct FileSysStartupMsg *fsm;

	  fsm = (struct FileSysStartupMsg *) (dol->dol_misc.dol_handler.dol_Startup<<2);

	  if(!OpenDevice((STRPTR)((fsm->fssm_Device<<2)+1), fsm->fssm_Unit, (struct IORequest *) ioreq, 0))
	  {
	    struct DosEnvec *de;
	    STRPTR buf;
	    ULONG size;

	    de = (struct DosEnvec *) (fsm->fssm_Environ<<2);
 	    size = (de->de_SizeBlock<<2)*(de->de_BlocksPerTrack*de->de_Surfaces);
	    if((buf = (STRPTR) AllocVec(size*2, MEMF_ANY)))
	    {
	      BPTR fh;

	      if((fh = Open(args->from, MODE_OLDFILE)))
	      {
		struct FileInfoBlock *fib;
  		ULONG low, num;

		low = de->de_LowCyl*size;
		num = (de->de_HighCyl-de->de_LowCyl+1) * size;
		if((fib = (struct FileInfoBlock *) AllocDosObject(DOS_FIB, 0)))
		{
		  if(ExamineFH(fh, fib))
		  {
		    struct UtilityBase *UtilityBase;

		    if((UtilityBase = (struct UtilityBase *) OpenLibrary("utility.library", 37)))
		    {
		      struct xadProgressInfo *pi;

		      if((pi = (struct xadProgressInfo *) xadAllocObjectA(XADOBJ_PROGRESSINFO, 0)))
		      {
		        struct xadDiskInfo *xdi;
		        err = 0;
		        if(num != fib->fib_Size && !args->ignoregeometry)
		        {
		          pi->xpi_Mode = XADPMODE_ASK;
		          pi->xpi_Status = XADPIF_IGNOREGEOMETRY;
                          if(!((i = CallHookPkt(&prhook, 0, pi)) & XADPIF_OK))
		            err = XADERR_BREAK;
		          else if(i & XADPIF_SKIP)
		            err = XADERR_SKIP;
		          else if(!(i & XADPIF_IGNOREGEOMETRY))
		            err = XADERR_GEOMETRY;
			}
			if(!err)
			{
			  UBYTE r;
			  STRPTR b;
		  
			  for(b = args->to; *b; ++b)
			    *b = toupper(*b);

	                  Printf("\r\033[KInsert disk into %s and press <ENTER> (any other key to abort): ", args->to);
	                  Flush(Output());
	                  SetMode(Input(), TRUE);
	                  r = FGetC(Input());
	                  SetMode(Input(), FALSE);
	                  if(r != '\r' && r != '\n')
	                  {
	                    Printf("\n");
	                    err = XADERR_BREAK;
	                  }
			}
		        if(!err && (xdi = (struct xadDiskInfo *) xadAllocObjectA(XADOBJ_DISKINFO, 0)))
		        {
		          ULONG siz, p;
		          STRPTR b;
		          siz = fib->fib_Size;

		    	  ioreq->io_Flags = 0;
		    	  b = buf+size;
		    	  p = 0;
		    	  pi->xpi_DiskInfo = xdi;
		    	  xdi->xdi_Flags = XADDIF_NOCYLINDERS|XADDIF_NOCYLSECTORS;
        		  xdi->xdi_TotalSectors = (siz+511)>>9; /* divide by 512 */
        		  xdi->xdi_SectorSize = 512;
        		  Inhibit(args->to, DOSTRUE);
			  while(siz && !err)
			  {
			    i = size > siz ? siz : size;
			    if(p+i > num)
			    {
			      if(!(i = num-p)) /* do not exceed borders */
			      {
			        err = XADERR_OUTPUT; break;
			      }
			    }
			    siz -= i;

			    if(Read(fh, buf, i) != i)
			      err = XADERR_INPUT;
			    else
			    {
		    	      ioreq->io_Length  = i;
		    	      ioreq->io_Offset  = low+p;
		    	      ioreq->io_Command = args->format ? TD_FORMAT : CMD_WRITE;
		    	      ioreq->io_Data    = buf;
		    	      p += i;
		    	      if(DoIO(((struct IORequest *) ioreq)))
		    	        err = XADERR_OUTPUT;
		    	      else
		    	      {
		    	        ioreq->io_Command = CMD_READ;
		    	        ioreq->io_Data    = b;
		    	        if(DoIO(((struct IORequest *) ioreq)))
		    	          err = XADERR_OUTPUT;
		    	        else
		    	        {
		                  pi->xpi_Mode = XADPMODE_PROGRESS;
		                  pi->xpi_CurrentSize = p;
                          	  if(!(CallHookPkt(&prhook, 0, pi)) & XADPIF_OK)
		                    err = XADERR_BREAK;
		                  else
		                  {
		    	            while(i--)
		    	            {
		    	              if(buf[i] != b[i])
		    	              {
		    	                err = XADERR_OUTPUT;
		    	                break;
		    	              }
		    	            }
		    	          }
		    	        }
		    	      }
		    	    }
		    	  } /* while */
		    	  Inhibit(args->to, DOSFALSE);
			  pi->xpi_Mode = err ? XADPMODE_ERROR : XADPMODE_END;
		          pi->xpi_CurrentSize = fib->fib_Size;
		          pi->xpi_Status = err;
                          CallHookPkt(&prhook, 0, pi);
		    	  xadFreeObjectA(xdi, 0);
		    	}
		    	xadFreeObjectA(pi, 0);
		      }
		      else
		        err = XADERR_NOMEMORY;
		      CloseLibrary((struct Library *) UtilityBase);
		    }
		  }
		  else
		    err = XADERR_INPUT;
		  FreeDosObject(DOS_FIB, fib);
		}
		else
		  err = XADERR_NOMEMORY;
		Close(fh);
	      }
	      else
	        err = XADERR_INPUT;
	      FreeVec(buf);
	    }
	    else
	      err = XADERR_NOMEMORY;
	    CloseDevice((struct IORequest *) (ioreq));
	  } /* OpenDevice */
	  DeleteIORequest(ioreq);
	} /* CreatIORequest */
	DeleteMsgPort(port);
      } /* CreateMsgPort */
    } /* FindDosEntry */
    UnLockDosList(LDF_READ|LDF_DEVICES);
  } /* LockDosList */

  return err;
}
