;/* How to compile
sc parms=register nostackcheck commentnest opttime PackDev
;sc parms=register nostackcheck commentnest PackDev
slink PackDev.o regobj:CountBits.o to PackDev lib lib:sc.lib lib:amiga.lib sc sd
quit 0
*/
#include <clib/alib_protos.h>

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/libraries.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <libraries/xpk.h>
#include <devices/trackdisk.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <ctype.h>       /* For toupper */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>       /* For sprintf */

/*
Version of source code
$VER: PackDev.c V1.9 (20-Mar-97)
*/

/*
Encoded version (high order 4 bits = version, low order 4 bits = revision)
*/

#define PKD_VERSION  1
#define PKD_REVISION 9
#define PKD_FILEVERSION  1		/* Last version with changed file format */
#define PKD_FILEREVISION 3

/* If set, some debug info will be written to Output() */

#define DEBUG 0

/* If set, a hex dump of each read sector will be written to Output() */

#define DEBUGReadSectors 0

/* length of a BSTR */
#define bstrlen(bstr) (*(UBYTE *)BADDR(bstr))

/* Address of the data of a BSTR, maybe not null terminated !! */
#define bstrptr(bstr) ((BYTE *)(1+(LONG)BADDR(bstr)))

/* Obvious */
#define min(a,b) ((a)<(b) ? (a):(b))
#define max(a,b) ((a)<(b) ? (b):(a))

/*
Returns TRUE if BAM bit not set or block is a reserved one.
Input: B = Block number minus device start minus number of reserved b
*/

#define BlockUsed(B) ((B)<0 || !BAMPresent || !(((ULONG *)BAM)[(B)>>5] & (1<<((B) & 31))))

VOID cleanup(BYTE *);
BYTE GetDevice(BYTE *,ULONG,struct IORequest **,ULONG,ULONG);
VOID FreeDevice(struct IORequest *);
ULONG GetDiskChangeCount(struct IOExtTD *);
VOID ReadSectors(struct IOExtTD *,APTR,APTR,ULONG,ULONG,ULONG,LONG,LONG);
VOID WriteSectors(struct IOExtTD *,APTR,APTR,ULONG,ULONG,ULONG,LONG,LONG,BOOL,APTR,APTR);
VOID FormatTrack(struct IOExtTD *,ULONG,LONG,APTR,APTR,LONG,LONG,LONG,APTR,APTR,BOOL);
VOID HandleError(struct IOExtTD *,BYTE *);
BOOL Do(struct IOExtTD *,LONG,USHORT);
LONG XWrite(BPTR,APTR,LONG);
LONG XFlush(BPTR);
LONG XRead(BPTR,APTR,LONG);
ULONG __asm CountSetBits(register __a0 ULONG *,register __d0 ULONG);
LONG ReadFilesystem(VOID);
LONG OpenXPKLibs(BOOL);
LONG HandleParameters(VOID);
LONG InitStuff(VOID);
BPTR OpenForWriteSafely(BYTE *);

BYTE version[]="$VER: PackDev V1.9 (20-Mar-97), © 1994-1997 by Christian Wasner.";

/* Minimum PackDev version needed to unpack archives created by this version */
BYTE PkdID[]={'P','K','D',(PKD_FILEVERSION<<4)+PKD_FILEREVISION};
BYTE xpkname[]="xpkmaster.library";
BYTE xpksublib[]="libs:compressors/xpkXXXX.library";

struct ExecBase *SysBase;
struct DosLibrary *DOSBase;
struct Library *XpkBase;
struct Library *XpkSublibBase;
struct FileSysStartupMsg *fssm;
struct RDArgs *RDArgs;
struct IOExtTD *DiskIO;
struct DosEnvec *DosEnvec;
struct DosList *DosList;
struct Process *OwnProc;
BPTR File,StdIn,StdOut;
BOOL Inhibited,DosListlocked;

#define FILESYSNUM 6

BYTE *filesys[]={
	"OFS (DOS\\0)",
	"FFS (DOS\\1)",
	"OFS International (DOS\\2)",
	"FFS International (DOS\\3)",
	"OFS Directory Cache (DOS\\4)",
	"FFS Directory Cache (DOS\\5)",
	"None, all blocks must be read",
	"Not supported, all blocks must be read"};

#define READ 0
#define WRITE 1
#define VIEWFILESYS 2
#define VIEWFILE 3
#define TESTFILE 4

struct {
	BYTE *From;          /* Source (Raw read: xxx.device) */
	BYTE *To;            /* Destination (Raw write: xxx.device) */
	BYTE *Pack;          /* Type of packer ("xxxx.yyy" or unset) */
	LONG *XpkBufSize;    /* Size of xpk buffer in Kbytes (def. 64) */
	BYTE *MemType;       /* Type of memory (default: any memory) */
	LONG ClrUnused;      /* If set, unused blocks will be overwritten with zeroes */
	LONG ETDFormat;      /* Tracks will be formatted (use with floppies only) */
	LONG TDFormat;       /* Tracks will be formatted (use with non-floppies only) */
	LONG TDFLabel;       /* Same as TDFormat, but with writing of label buffers */
	LONG NoVerify;       /* Disable Verify */
	LONG ReadAll;        /* Read all blocks, ignore BAM */
	LONG NoVerbose;      /* Only important data is printed */
	LONG Quiet;          /* Absolutely no input and output */
	LONG NoConfirm;      /* Absolutely no input */
	BYTE *ViewFile;      /* View file */
	BYTE *ViewFilesys;   /* View file system */
	BYTE *TestFile;      /* Test file */
	BYTE *BlockList;     /* Output blocklist */
	BYTE *Password;      /* Password */
	LONG TrueName;       /* Use file name exactly as specified */
	LONG Force;          /* Overwrite existing file without asking/failing */
	BYTE *RawUnit;       /* Raw device IO: Device unit */
	BYTE *RawStart;      /* Raw device IO: Start block */
	BYTE *RawStop;       /* Raw device IO: Stop block */
	BYTE *RawNum;        /* Raw device IO: Block number */
} Args;

SHORT Action;

BYTE noargs[]="Invalid parameters. Read the docs or check parameters.\n";
BYTE nomem[]="Not enough memory\n";
BYTE aborted[]="Aborted\n";
BYTE cantopen[]="Cannot open file\n";
BYTE cantread[]="Cannot read from file\n";
BYTE cantwrite[]="Cannot write to file\n";
BYTE noheader[]="Corrupt file header\n";
BYTE noxpklib[]="Cannot open xpkmaster.library\n";
BYTE tryvalidate[]="Disk bitmap is not valid. Try to validate the disk\n";
BYTE ctrlc[]="Aborted by user\n";

BYTE *xwriteerr,*xreaderr;
BYTE xpkmsgheader[]="XPK ERROR: ";
BYTE xpkmsg[XPKERRMSGSIZE+13]; /* 11 bytes for "XPK ERROR: ", 1 for \n, 1 for \0 */
BYTE *Filesysname,*Filename;
BYTE areyousure[]="Are you sure to abort (yN) ? ";
BYTE readbam[]="Reading block allocation map...\n";

LONG DiskChanges,len;

BYTE FSnocolon[31];
BYTE *BAM;

ULONG BAMSize,BufSize,MemType;

ULONG *BlkBuf;
UBYTE *TrkBuf;
UBYTE *VerTrk;

SHORT XpkMode;

BYTE  VersRev;
BYTE  XpkPackName[25];
BYTE *PBuf,*UBuf;
BYTE fname[256],fileext[]=".pkd";
LONG PLen,ULen,TotalSize;
LONG XpkPos,XpkLen;
WORD DosDisk;
XPINFO XInfo;

/*
LONG XpkBufSize=65536;
*/
LONG XpkBufSize;

LONG __saveds main(VOID)
{
	struct DateStamp Start,Stop;
	LONG  n,z,BlocksPerCyl,BAMBlocks,zmax,bcnt,Ticks,k,b,bmin,bmax;
	ULONG BlocksPerDisk,RootBlock,AbsAdd,BlkSize,BlkLW,Filesysnum,extbam,Free,
	      Reserved,Blk,BlocksPerTrack,BytesPerTrack,SByte,DByte,LSByte,LDByte,
	      bbcnt,Used,XErr=0,XNameLen;
	ULONG *RootBuf,*LabBuf;
	WORD  BAMPresent;
	BYTE  buf[256],*ptr;
	BYTE  *TrkLab,*VerLab;

	if ((n=InitStuff())!=-1
	  || (n=HandleParameters())!=-1
	  || (Args.Pack && (n=OpenXPKLibs(TRUE))!=-1)
	  || (Action!=VIEWFILE && Action!=TESTFILE && (n=ReadFilesystem())!=-1)
		)
		return n;

	if (Action==VIEWFILE || Action==WRITE || Action==TESTFILE)
	{ /* Read-from-file and eventually write-to-disk operations */
		switch (Action)
		{
			case WRITE:
				strncpy(fname,Filename,251);
				break;
			case VIEWFILE:
				strncpy(fname,Args.ViewFile,251);
				break;
			case TESTFILE:
				strncpy(fname,Args.TestFile,251);
				break;
		}

		fname[251]=0;
		if (!Args.TrueName)
		{
			n=strlen(fname);
			if (n<4 || stricmp(fileext,&fname[n-4]))
				strcpy(&fname[n],fileext);
			else
				n=-1; /* n=-1: Filename specified with extension --> Keep it as it is */
		}
		else
			n=-1; /* Keep filename as it is. */

		if (!(File=Open(fname,MODE_OLDFILE)))     /* Try foobar.pkd... */
		{
			if (n==-1)  /* Filename kept the same: No second try */
			{
				cleanup(cantopen);
				return 20;
			}
			else
				fname[n]=0;
			if (!(File=Open(fname,MODE_OLDFILE)))  /* Failed: Try foobar */
			{
				cleanup(cantopen);
				return 20;
			}
		}
		if (Read(File,XpkPackName,4)!=4)
		{
			cleanup(cantread);
			return 20;
		}
		if (strncmp(XpkPackName,"PKD",3))
		{
			cleanup("File is not a pkd archive\n");
			return 20;
		}
		if ((VersRev=XpkPackName[3])>(PKD_VERSION<<4)+PKD_REVISION)
		{
			if (!Args.Quiet)
				Printf("You need PackDev V%ld.%ld for this file, "
				  "this is PackDev V%ld.%ld.\n",
				  ((ULONG)VersRev)>>4,VersRev & 15,PKD_VERSION,PKD_REVISION);
			cleanup(NULL);
			return 20;
		}
		if (VersRev<0x12)
			XNameLen=4;
		else
			XNameLen=24;
		if (Read(File,&BlocksPerDisk,4)!=4
		  || Read(File,&BlkSize,4)!=4
		  || Read(File,&Reserved,4)!=4
		  || Read(File,&BytesPerTrack,4)!=4
		  || Read(File,&XpkBufSize,4)!=4
		  || Read(File,XpkPackName,XNameLen)!=XNameLen
		  || Read(File,&XpkMode,2)!=2
		  || Read(File,&BAMPresent,2)!=2)
		{
			cleanup(cantread);
			return 20;
		}
		TotalSize=XNameLen+28;
		if ((!XpkPackName[0] && XpkMode) || XpkMode<0 || XpkMode>100
		  || BlocksPerDisk<=0 || BlkSize<=0 || Reserved<0 || BytesPerTrack<BlkSize)
		{
			cleanup(noheader);
			return 20;
		}

		BlocksPerTrack=BytesPerTrack/BlkSize;

		if (!Args.Quiet)
			Printf("XPK packer information:\n");

		if (XpkPackName[0])
		{
/* XpkBase was only opened if Args.Pack was set. Args.Pack must not be
   set when unpacking. */
			if (OpenXPKLibs(FALSE)!=-1)
			{
				if (!Args.Quiet)
					Printf(noxpklib);
				XErr=1;
			}
			else if (XErr=XpkQueryTags(XPK_PackMethod,XpkPackName,
			                           XPK_PackerQuery,&XInfo,
			                           XPK_GetError,xpkmsg,TAG_DONE)
			          && !Args.Quiet)
				Printf("XPK ERROR: %s\n",xpkmsg);

			if (!Args.Quiet)
				Printf("%s (%s)\n%s\nBuffer: %ld bytes, Mode:   %ld\n\n",
				  XInfo.Name,XInfo.LongName,XInfo.Description,XpkBufSize,XpkMode);
		}
		else if (!Args.Quiet)
			Printf("File unpacked.\n\n");

		if (!Args.Quiet)
			Printf("File information:\n"
			  "Version needed to unpack: V%ld.%ld\n"
			  "Disk size:                %ld blocks\n"
			  "Block size:               %ld bytes\n"
			  "Reserved blocks:          %ld\n"
			  "Known file system exists: %s\n\n",
			  ((ULONG)VersRev)>>4,VersRev & 15,BlocksPerDisk,BlkSize,
			  Reserved,BAMPresent ? "Yes":"No");

		if (Action==VIEWFILE || XErr)
		{
			cleanup(NULL);
			return 0;
		}

		if (strlen(XpkPackName) && !(UBuf=AllocMem(ULen=XpkBufSize+XPK_MARGIN,0)))
		{
			cleanup(nomem);
			return 20;
		}
		XpkPos=XpkLen=XpkBufSize;

		BAMSize=((BlocksPerDisk-Reserved+31)>>5)<<2;
		if (BAMPresent && !(BAM=AllocMem(BAMSize,0)))
		{
			cleanup(nomem);
			return 20;
		}

		if (Action!=TESTFILE)
		{
			BlocksPerCyl=DosEnvec->de_Surfaces*DosEnvec->de_BlocksPerTrack;

			if (BlocksPerDisk!=(DosEnvec->de_HighCyl-DosEnvec->de_LowCyl+1)*BlocksPerCyl)
			{
				cleanup("The target device has a different number of blocks\n");
				return 20;
			}

			if (BlkSize!=DosEnvec->de_SizeBlock<<2)
			{
				cleanup("The target device has a different block size\n");
				return 20;
			}

			if ((Args.ETDFormat || Args.TDFormat || Args.TDFLabel)
			  && BlocksPerTrack!=DosEnvec->de_BlocksPerTrack)
			{
				cleanup("The target's and the original device track size must be\n"
				        "the same (yet) when formatting.\n");
				return 20;
			}

			AbsAdd=DosEnvec->de_LowCyl*BlocksPerCyl;

			if (GetDevice(bstrptr(fssm->fssm_Device),fssm->fssm_Unit,
			  (struct IORequest **)&DiskIO,fssm->fssm_Flags,sizeof(struct IOExtTD)))
			{
				if (!Args.Quiet)
					Printf("Cannot open %s, unit %d.\n",bstrptr(fssm->fssm_Device),
					  fssm->fssm_Unit);
				cleanup(NULL);
				return 20;
			}
		}

		if (!Args.Quiet)
			Printf("Block allocation map: Reading%s...\n\n",
			  XpkPackName[0] ? " and unpacking":"");

		if (BAM && XRead(File,BAM,BAMSize)!=BAMSize)
		{
			cleanup(xreaderr);
			return 20;
		}

		if (Action!=TESTFILE && !Args.Quiet)
			Printf("Device information (CONTENTS WILL BE DESTROYED):\n"
			  "Device:     %s\n"
			  "Unit:       %ld\n"
			  "LowCyl:     %ld\n"
			  "HighCyl:    %ld\n"
			  "Surfaces:   %ld\n"
			  "Blocks:     %ld\n"
			  "Reserved:   %ld blocks\n"
			  "BlkSize:    %ld bytes\n"
			  "Total size: %ld bytes\n"
	        "Free/used:  ",
			  bstrptr(fssm->fssm_Device),fssm->fssm_Unit,
			  DosEnvec->de_LowCyl,DosEnvec->de_HighCyl,DosEnvec->de_Surfaces,
			  DosEnvec->de_BlocksPerTrack,DosEnvec->de_Reserved,BlkSize,
			  BlocksPerDisk*BlkSize);

		if (BAM)
		{                   /* BAM is always even because of AllocMem() */
			Free=CountSetBits((ULONG *)BAM,BlocksPerDisk-Reserved);
			if (Action!=TESTFILE && !Args.Quiet)
				Printf("%ld/%ld blocks",Free,BlocksPerDisk-Reserved-Free);
			Used=BlocksPerDisk-Free;
		}
		else
		{
			Used=BlocksPerDisk;
			if (Action!=TESTFILE && !Args.Quiet)
				Printf("Unknown");
		}

		if (!Args.Quiet)
		{
			if (Action!=TESTFILE)
				Printf(" after writing\n\n");
			if (Action!=WRITE)
			{
				if (XpkPackName[0])
					if (Args.ETDFormat || Args.TDFormat || Args.TDFLabel)
						ptr="Unpacking and formatting";
					else if (Action==WRITE)
						ptr="Unpacking and writing";
					else
						ptr="Unpacking and testing";
				else
					if (Args.ETDFormat || Args.TDFormat || Args.TDFLabel)
						ptr="Formatting";
					else if (Action==WRITE)
						ptr="Writing";
					else
						ptr="Testing";
				Printf("Action:     %s%s%s\n\n",ptr,
				  Args.NoVerify ? ", no verify":" with verify",
				  Args.TDFormat ? ", losing label buffers":"");
			}
			if (!Args.NoConfirm)
			{
				Printf("Continue (yN) ? ");
				Flush(StdOut);
				FGets(StdIn,buf,255);
				if (buf[1]!='\n' || (buf[0]!='y' && buf[0]!='Y'))
				{
					cleanup(aborted);
					return 0;
				}
			}
			if (!Args.NoVerbose)
				Printf("\033[0 p");
		}

		if (Action!=TESTFILE && (DiskChanges=GetDiskChangeCount(DiskIO))==-1)
		{
			cleanup(aborted);
			return 20;
		}

		if (CheckSignal(SIGBREAKF_CTRL_C))
		{
			cleanup(ctrlc);
			return 0;
		}

		DateStamp(&Start);

		BufSize=BlocksPerTrack*(BlkSize+TD_LABELSIZE);

		if (!(TrkBuf=AllocMem(BufSize,MemType|MEMF_CLEAR)))
		{
			cleanup(nomem);
			return 20;
		}

		TrkLab=(UBYTE *)(BytesPerTrack+(long)TrkBuf);

		if (!(VerTrk=AllocMem(BufSize,MemType|MEMF_CLEAR)))
		{
			cleanup(nomem);
			return 20;
		}

		VerLab=(UBYTE *)(BytesPerTrack+(long)VerTrk);
/*
Printf("TrkBuf=$%08lx,TrkLab=$%08lx,VerTrk=$%08lx,VerLab=$%08lx,BufSize=%ld\n",
  TrkBuf,TrkLab,VerTrk,VerLab,BufSize);
*/
		if (!Args.Quiet && Args.NoVerbose)
			Printf("Writing data...\n");

		for (Blk=0,z=0;Blk<BlocksPerDisk;Blk+=BlocksPerTrack)
		{
			if (CheckSignal(SIGBREAKF_CTRL_C))
			{
				if (!Args.Quiet && !Args.NoConfirm)
				{
					Printf(areyousure);
					Flush(StdOut);
					FGets(StdIn,buf,255);
					if (buf[1]=='\n' && (buf[0]=='y' || buf[0]=='Y'))
					{
						cleanup(ctrlc);
						return 0;
					}
				}
				else
				{
					cleanup(ctrlc);
					return 0;
				}
			}

/* Count number of used blocks on current track */
			for (bmax=(b=Blk-Reserved)+BlocksPerTrack,k=0;b<bmax;b++)
				if (BlockUsed(b))
					k++;

/* Blocks present: Read them and write them to device */
			if (k || Args.ClrUnused || Args.ETDFormat || Args.TDFormat || Args.TDFLabel)
			{
/*
Printf("XRead($%08lx,%08lx,%ld)\n",File,TrkBuf,k*BlkSize);
Printf("XRead($%08lx,%08lx,%ld)\n",File,TrkLab,k*TD_LABELSIZE);
*/

/* Read data */
				if ( k && (XRead(File,TrkBuf,k*BlkSize)!=k*BlkSize
				  || XRead(File,TrkLab,k*TD_LABELSIZE)!=k*TD_LABELSIZE))
				{
					cleanup(xreaderr);
					return 20;
				}

/* Copy blocks to the correct positions */
				for (b=(bmin=Blk-Reserved)+BlocksPerTrack-1,n=0,
				  SByte=BlkSize*(k-1),DByte=BlkSize*(BlocksPerTrack-1),
				  LSByte=TD_LABELSIZE*(k-1),LDByte=TD_LABELSIZE*(BlocksPerTrack-1);
				  b>=bmin;b--,DByte-=BlkSize,LDByte-=TD_LABELSIZE)
					if (BlockUsed(b))
					{
						n++;
						if (SByte!=DByte)
						{
/*
Printf("CopyMem($%08lx,$%08lx,%ld)\n",&TrkBuf[SByte],&TrkBuf[DByte],BlkSize);
Printf("CopyMem($%08lx,$%08lx,%ld)\n",&TrkLab[LSByte],&TrkLab[LDByte],TD_LABELSIZE);
*/
							CopyMem(&TrkBuf[SByte],&TrkBuf[DByte],BlkSize);
							CopyMem(&TrkLab[LSByte],&TrkLab[LDByte],TD_LABELSIZE);
						}
						SByte-=BlkSize;
						LSByte-=TD_LABELSIZE;
					}
					else
					{
/*
Printf("ClrMem($%08lx,$%08lx,%ld)\n",&TrkBuf[SByte],&TrkBuf[DByte],BlkSize);
Printf("ClrMem($%08lx,$%08lx,%ld)\n",&TrkLab[LSByte],&TrkLab[LDByte],TD_LABELSIZE);
*/
						for (k=0;k<BlkSize;k++)
							TrkBuf[DByte+k]=0;
						for (k=0;k<TD_LABELSIZE;k++)
							TrkLab[LDByte+k]=0;
					}
				z+=n;
#if DEBUG
				Printf("\n");
#endif
				if (!Args.Quiet && !Args.NoVerbose)
					Printf("\033[1F"
					  "Blocks %7ld - %7ld: %4ld of %4ld, %6ld bytes, %3ld %% done...      \n",
					  Blk,Blk+BlocksPerTrack-1,n,BlocksPerTrack,n*BlkSize,
					  (100*z)/Used);

/*
Printf("WriteSectors($%08lx,$%08lx,$%08lx,%ld,%ld,%ld,%ld,%ld,%ld,$%08lx,$%08lx\n",
  DiskIO,TrkBuf,TrkLab,Blk+AbsAdd,BlocksPerTrack,BlkSize,
  DiskChanges,Blk,0,VerTrk,VerLab);
*/
				if (Action!=TESTFILE)
				{
					if (Args.ETDFormat || Args.TDFormat || Args.TDFLabel)
						FormatTrack(DiskIO,(Blk+AbsAdd)/BlocksPerTrack,BytesPerTrack,
						  TrkBuf,Args.TDFormat ? NULL:TrkLab,BlkSize,DiskChanges,Blk,
						  Args.NoVerify ? NULL:VerTrk,VerLab,Args.ETDFormat);
					else
						WriteSectors(DiskIO,TrkBuf,TrkLab,Blk+AbsAdd,BlocksPerTrack,BlkSize,
						  DiskChanges,Blk,0,Args.NoVerify ? NULL:VerTrk,VerLab);
					if (DiskIO->iotd_Req.io_Error)
					{
						cleanup(aborted);
						return 20;
					}
				}
			}
		}

		DateStamp(&Stop);

		if (!Args.Quiet)
		{
			Printf("%ld bytes, %ld blocks %s%s\n",z*BlkSize,z,
			  XpkPackName[0] ? "unpacked and ":"",
			  Action==WRITE ? "written to disk":"tested");
			if ((Ticks=Stop.ds_Tick-Start.ds_Tick+3000*(Stop.ds_Minute-Start.ds_Minute
			  +1440*(Stop.ds_Days-Start.ds_Days)))>0)
			{
				if (Ticks>=180000)
					sprintf(buf,"%ldh %ldm %ld.%02lds",
					  Ticks/180000,(Ticks%180000)/3000,(Ticks%3000)/50,2*(Ticks%50));
				else if (Ticks>=3000)
					sprintf(buf,"%ldm %ld.%02lds",
					  Ticks/3000,(Ticks%3000)/50,2*(Ticks%50));
				else
					sprintf(buf,"%ld.%02lds",Ticks/50,2*(Ticks%50));
				Printf("Elapsed time: %s, %ld bytes --> %ld bytes, %ld bytes/s\n",
				  buf,TotalSize,z*BlkSize,z*BlkSize/Ticks*50);
			}
			else
				Printf("System clock changed or nonfunctional, no time and speed info possible\n");
		}
	}
	else
	{ /* Read-from-disk and eventually write-to-file operations */
		BlkSize=DosEnvec->de_SizeBlock<<2;
		BlocksPerTrack=DosEnvec->de_BlocksPerTrack;
		BytesPerTrack=BlocksPerTrack*BlkSize;
		BlocksPerCyl=DosEnvec->de_Surfaces*BlocksPerTrack;
		BlocksPerDisk=(DosEnvec->de_HighCyl-DosEnvec->de_LowCyl+1)
		  *BlocksPerCyl;
		AbsAdd=DosEnvec->de_LowCyl*BlocksPerCyl;
		BlkLW=BlkSize>>2;
		Reserved=DosEnvec->de_Reserved;
		RootBlock=(BlocksPerDisk-1+Reserved)>>1;

		if (GetDevice(bstrptr(fssm->fssm_Device),fssm->fssm_Unit,
		  (struct IORequest **)&DiskIO,fssm->fssm_Flags,sizeof(struct IOExtTD)))
		{
			cleanup("Cannot open device\n");
			return 20;
		}

		if (!(BlkBuf=AllocMem(BlkSize+BlkSize+TD_LABELSIZE,MemType)))
		{
			cleanup(nomem);
			return 20;
		}
		RootBuf=(APTR)(BlkSize+(long)BlkBuf);
		LabBuf=(APTR)(BlkSize+(long)RootBuf);

		if ((DiskChanges=GetDiskChangeCount(DiskIO))==-1)
		{
			cleanup(aborted);
			return 20;
		}

		if (CheckSignal(SIGBREAKF_CTRL_C))
		{
			if (!Args.Quiet && !Args.NoConfirm)
			{
				Printf(areyousure);
				Flush(StdOut);
				FGets(StdIn,buf,255);
				if (buf[1]=='\n' && (buf[0]=='y' || buf[0]=='Y'))
				{
					cleanup(ctrlc);
					return 0;
				}
			}
			else
			{
				cleanup(ctrlc);
				return 0;
			}
		}

		if (DosDisk)  // Means that there is a filesystem (which may not be supported, however)
		{
			if (!Args.Quiet)
				Printf("Valid DOS disk, reading block 0...\n");
			ReadSectors(DiskIO,RootBuf,LabBuf,AbsAdd,1,BlkSize,DiskChanges,
			  RootBlock);
/* The dosnode always contains DOS\0 for floppies... */
			if ((Filesysnum=(*(LONG *)RootBuf)-0x444f5300)<0
			  || Filesysnum>=FILESYSNUM)
				Filesysnum=FILESYSNUM+1; /* Disk format not supported, e.g. PC disk */
		}
		else
			Filesysnum=FILESYSNUM; /* Disk format unknown for DOS */
		if (!Args.Quiet)
			Printf("Reading root block...\n");
		ReadSectors(DiskIO,RootBuf,LabBuf,RootBlock+AbsAdd,1,BlkSize,DiskChanges,
		  RootBlock);
		if (DiskIO->iotd_Req.io_Error)
		{
			cleanup(aborted);
			return 20;
		}

		if (Filesysnum<FILESYSNUM)
		{
			if (!((ULONG *)RootBuf)[BlkLW-50])
			{
				cleanup(tryvalidate);
				return 20;
			}

			BAMSize=((BlocksPerDisk-Reserved+31)>>5)<<2;
			BAMBlocks=(BAMSize+BlkSize-5)/(BlkSize-4);

			if ((Filesysnum==0 || Filesysnum==2 || Filesysnum==4)
			  && BAMBlocks>208*BlkSize)
			{
				cleanup("An OFS partition with this block size cannot have that many blocks\n");
				return 0;
			}
			if (!((ULONG *)RootBuf)[BlkLW-50])
			{
				return 20;
			}
#if DEBUG
			Printf("BAMSize=%ld,BAMBlocks=%ld,BlkSize=%ld,BlkLW=%ld\n",
			  BAMSize,BAMBlocks,BlkSize,BlkLW);
#endif
/* Mem type not affected by MEMTYPE, no device buffer. There may be DOS disks
   that aren't supported by PackDev, e.g. PeeCee disks */
			if (Filesysnum<FILESYSNUM && !(BAM=AllocMem(BAMSize,0)))
			{
				cleanup(nomem);
				return 20;
			}

			if (Filesysnum==0 || Filesysnum==2 || Filesysnum==4)
			{
				if (!Args.Quiet)
					Printf(readbam);
/* Read OFS BAM */
				zmax=BlkLW-49+min(BAMBlocks,26);
#if DEBUG
				Printf("OFS: zmax=%ld\n",zmax);
#endif
				for (z=BlkLW-49,bcnt=0;z<zmax;z++,bcnt+=BlkSize-4)
				{
					ReadSectors(DiskIO,BlkBuf,LabBuf,RootBuf[z]+AbsAdd,1,BlkSize,DiskChanges,
					  RootBuf[z]);
					if (DiskIO->iotd_Req.io_Error)
					{
						cleanup(aborted);
						return 20;
					}
#if DEBUG
					Printf("CopyMem: Size=%8ld, bcnt=%8ld,bits: %ld\n",
					  min(BlkSize-4,BAMSize-bcnt),bcnt,min((BlkSize-4)<<3,
					  BlocksPerDisk-Reserved-(bcnt<<3)));
#endif
					CopyMem(&BlkBuf[1],&BAM[bcnt],min(BlkSize-4,BAMSize-bcnt));
				}
				BAMPresent=!Args.ReadAll;
			}
			else if (Filesysnum==1 || Filesysnum==3 || Filesysnum==5)
			{
				if (!Args.Quiet)
					Printf(readbam);
/* Mem type not affected by MEMTYPE, no device buffer */
/* Read FFS BAM */
				zmax=BlkLW-49+min(BAMBlocks,25);
#if DEBUG
				Printf("FFS: zmax=%ld\n",zmax);
#endif
				for (z=BlkLW-49,bcnt=0;z<zmax;z++,bcnt+=BlkSize-4)
				{
					ReadSectors(DiskIO,BlkBuf,LabBuf,RootBuf[z]+AbsAdd,1,BlkSize,DiskChanges,
					  RootBuf[z]);
					if (DiskIO->iotd_Req.io_Error)
					{
						cleanup(aborted);
						return 20;
					}
#if DEBUG
					Printf("CopyMem: Size=%8ld, bcnt=%8ld,bits: %ld\n",min(BlkSize-4,
					  BAMSize-bcnt),bcnt,min((BlkSize-4)<<3,BlocksPerDisk-Reserved-(bcnt<<3)));
#endif
					CopyMem(&BlkBuf[1],&BAM[bcnt],min(BlkSize-4,BAMSize-bcnt));
				}
				if (BAMBlocks>24)
				{
					for (bbcnt=25,extbam=BlkLW-24;bbcnt<BAMBlocks;bbcnt+=(extbam=BlkLW-1))
					{
/* WARNING:
   Partitions with so much blocks that the bitmap doesn't fit on the
   blocks are unclear with respect to the location of further bitmap
   keys. With a block size of 512 Bytes the maximum partition size
   is 306 MByte if there are no further bitmap blocks possible. It's
   assumed that the last longword of the block that contains further
   numbers of blocks with bitmaps, contains the number of another block
   of this type etc. */
#if DEBUG
						Printf("New BAM keys:Blk=%ld,DOSBlk=%ld\n",
						  RootBuf[extbam]+AbsAdd,RootBuf[extbam]);
#endif
						ReadSectors(DiskIO,RootBuf,LabBuf,RootBuf[extbam]+AbsAdd,1,BlkSize,
						  DiskChanges,RootBuf[extbam]);
						if (DiskIO->iotd_Req.io_Error)
						{
							cleanup(aborted);
							return 20;
						}
						zmax=min(BAMBlocks-bbcnt,BlkLW-2);
#if DEBUG
						Printf("zmax=%ld\n",zmax);
#endif
						for (z=0;z<zmax;z++,bcnt+=BlkSize-4)
						{
							ReadSectors(DiskIO,BlkBuf,LabBuf,RootBuf[z]+AbsAdd,1,BlkSize,
							  DiskChanges,RootBuf[z]);
							if (DiskIO->iotd_Req.io_Error)
							{
								cleanup(aborted);
								return 20;
							}
#if DEBUG
							Printf("CopyMem: Size=%8ld, bcnt=%8ld,bits: %ld\n",
							  min(BlkSize-4,BAMSize-bcnt),bcnt,
							  min((BlkSize-4)<<3,BlocksPerDisk-Reserved-(bcnt<<3)));
#endif
							CopyMem(&BlkBuf[1],&BAM[bcnt],min(BlkSize-4,BAMSize-bcnt));
						}
					}
				}
				BAMPresent=!Args.ReadAll;
			}
			else
				BAMPresent=0;
		}
		else
			BAMPresent=0;

		if (Args.Pack)
		{
			n=strlen(Args.Pack);
			if (!(ptr=strchr(Args.Pack,'.')))
			{
				XpkMode=-1;
				z=n;
			}
			else
			{
				z=(LONG)(ptr-Args.Pack);
				if (!Args.Pack[z+1])
					XpkMode=-1;
				else if ((XpkMode=atoi(&Args.Pack[z+1]))<0 || XpkMode>100)
				{
					cleanup("Pack mode must be a number from 0 to 100\n");
					return 20;
				}
			}

			strncpy(XpkPackName,Args.Pack,z);
			XpkPackName[z]=0;

			if (XErr=XpkQueryTags(XPK_PackMethod,XpkPackName,
			  XPK_PackerQuery,&XInfo,
			  XPK_GetError,xpkmsg,TAG_DONE))
			{
				if (!Args.Quiet)
					Printf("XPK ERROR: %s\n",xpkmsg);
				cleanup(NULL);
				return 20;
			}
			if (Args.XpkBufSize)
			{
				if (*Args.XpkBufSize*BlkSize>XInfo.MaxChunk)
				{
					if (!Args.Quiet)
						Printf("Maximum buffer size is %ld blocks.\n",XInfo.MaxChunk/BlkSize);
					cleanup(NULL);
					return 20;
				}
				XpkBufSize=*Args.XpkBufSize*BlkSize;
			}
			else
			{
/* Buffer size must be a multiple of BlkSize */
				XpkBufSize=XInfo.DefChunk+BlkSize-1;
				XpkBufSize=XpkBufSize-(XpkBufSize%BlkSize);
/* Some packers don't work with their minimum buffer size. */
				XpkBufSize=XpkBufSize+XpkBufSize;
			}
			if (XpkMode<0)
				XpkMode=XInfo.DefMode;
			if (!(UBuf=AllocMem(ULen=XpkBufSize,0))
			  || !(PBuf=AllocMem(PLen=XpkBufSize+(XpkBufSize>>5)
			  +XPK_MARGIN+XPK_MARGIN,0)))
			{
				cleanup(nomem);
				return 20;
			}
			XpkLen=XpkBufSize;
			if (!Args.Quiet)
				Printf("\nXPK packer information:\n"
				  "%s (%s)\n%s\nBuffer: %ld bytes, Mode:   %ld\n\n",
				  XInfo.Name,XInfo.LongName,XInfo.Description,XpkLen,XpkMode);
		}

		if (CheckSignal(SIGBREAKF_CTRL_C))
		{
			if (!Args.Quiet && !Args.NoConfirm)
			{
				Printf(areyousure);
				Flush(StdOut);
				FGets(StdIn,buf,255);
				if (buf[1]=='\n' && (buf[0]=='y' || buf[0]=='Y'))
				{
					cleanup(ctrlc);
					return 0;
				}
			}
			else
			{
				cleanup(ctrlc);
				return 0;
			}
		}

		if (BAM)
			Free=CountSetBits((ULONG *)BAM,BlocksPerDisk-Reserved);

		if (!Args.Quiet)
			Printf("Device information:\n"
			  "%s, Unit %ld:\n"
			  "LowCyl:     %ld\n"
			  "HighCyl:    %ld\n"
			  "Surfaces:   %ld\n"
			  "Blocks:     %ld\n"
			  "Reserved:   %ld blocks\n"
			  "BlkSize:    %ld bytes\n"
			  "Total size: %ld bytes\n"
			  "Filesystem: %s\n"
			  "Free/used:  ",
			  bstrptr(fssm->fssm_Device),fssm->fssm_Unit,
			  DosEnvec->de_LowCyl,DosEnvec->de_HighCyl,DosEnvec->de_Surfaces,
			  BlocksPerTrack,Reserved,BlkSize,
			  BlocksPerDisk*BlkSize,filesys[Filesysnum]);
		if (BAM)
		{
			if (!Args.Quiet)
				Printf("%ld/%ld blocks\n\n",Free,BlocksPerDisk-Reserved-Free);
			Used=BlocksPerDisk-Free;
		}
		else
		{
			Used=BlocksPerDisk;
			if (!Args.Quiet)
				Printf("Unknown\n\n");
		}

		if (Action==READ && !Args.Quiet)
		{
			if (Args.Pack)
				ptr="Reading and packing";
			else
				ptr="Reading";
			Printf("Action:     %s%s\n\n",ptr,Args.ReadAll ? " all":"");
		}

		if (Args.ReadAll)
			Used=BlocksPerDisk;

		if (Action==VIEWFILESYS)
		{
			cleanup(NULL);
			return 0;
		}

		if (!Args.Quiet && !Args.NoConfirm)
		{
			Printf("Continue (yN) ? ");
			Flush(StdOut);
			FGets(StdIn,buf,255);
			if (buf[1]!='\n' || (buf[0]!='y' && buf[0]!='Y'))
			{
				cleanup(aborted);
				return 0;
			}
		}

		strncpy(fname,Filename,251);
		fname[251]=0;                 /* len=251: fname not 0-terminated !! */
		if (!Args.TrueName)
		{
			n=strlen(Filename);
			if (n<4 || stricmp(fileext,&fname[n-4]))
				strcpy(&fname[n],fileext);
		}

		if (!(File=OpenForWriteSafely(fname)))
			return 20;							/* cleanup() already called */

		if (!Args.Quiet && !Args.NoVerbose)
			Printf("\033[0 p");
		DateStamp(&Start);

		BufSize=BlocksPerTrack*(BlkSize+TD_LABELSIZE);

		if (!(TrkBuf=AllocMem(BufSize,MemType|MEMF_CLEAR)))
		{
			cleanup(nomem);
			return 20;
		}

		TrkLab=(UBYTE *)(BytesPerTrack+(long)TrkBuf);

		if (!Args.Quiet)
			Printf("Writing header...\n");
		if (Write(File,PkdID,4)!=4 || Write(File,&BlocksPerDisk,4)!=4
		  || Write(File,&BlkSize,4)!=4 || Write(File,&Reserved,4)!=4
		  || Write(File,&BytesPerTrack,4)!=4 || Write(File,&XpkBufSize,4)!=4
		  || Write(File,XpkPackName,24)!=24 || Write(File,&XpkMode,2)!=2
		  || Write(File,&BAMPresent,2)!=2)
		{
			cleanup(cantwrite);
			return 20;
		}
		TotalSize=52;

		if (BAMPresent)
		{
			if (CheckSignal(SIGBREAKF_CTRL_C))
			{
				if (!Args.Quiet && !Args.NoConfirm)
				{
					Printf(areyousure);
					Flush(StdOut);
					FGets(StdIn,buf,255);
					if (buf[1]=='\n' && (buf[0]=='y' || buf[0]=='Y'))
					{
						cleanup(ctrlc);
						return 0;
					}
				}
				else
				{
					cleanup(ctrlc);
					return 0;
				}
			}
#if DEBUG
			Printf("\n");
#endif
			if (!Args.Quiet)
				Printf("\033[1F\033[1KBlock allocation map: %storing...\n",
				  Args.Pack ? "Packing and s":"S");
			if (XWrite(File,BAM,BAMSize)!=BAMSize)
			{
				cleanup(xwriteerr);
				return 20;
			}
		}
		if (!Args.Quiet && Args.NoVerbose)
			Printf("Reading data...\n");

		for (Blk=0,z=0;Blk<BlocksPerDisk;Blk+=BlocksPerTrack)
		{
			if (CheckSignal(SIGBREAKF_CTRL_C))
			{
				if (!Args.Quiet && !Args.NoConfirm)
				{
					Printf(areyousure);
					Flush(StdOut);
					FGets(StdIn,buf,255);
					if (buf[1]=='\n' && (buf[0]=='y' || buf[0]=='Y'))
					{
						cleanup(ctrlc);
						return 0;
					}
				}
				else
				{
					cleanup(ctrlc);
					return 0;
				}
			}
			for (bmax=(b=Blk-Reserved)+BlocksPerTrack;b<bmax;b++)
				if (BlockUsed(b))
					break;
			if (b<bmax)
			{
				ReadSectors(DiskIO,TrkBuf,TrkLab,Blk+AbsAdd,BlocksPerTrack,BlkSize,
				  DiskChanges,Blk);
				if (DiskIO->iotd_Req.io_Error)
				{
					cleanup(aborted);
					return 20;
				}

				for (bmax=(b=Blk-Reserved)+BlocksPerTrack,n=SByte=DByte=LSByte=LDByte=0;
				  b<bmax;b++,SByte+=BlkSize,LSByte+=TD_LABELSIZE)
					if (BlockUsed(b))
					{
						n++;
						if (SByte!=DByte)
						{
							CopyMem(&TrkBuf[SByte],&TrkBuf[DByte],BlkSize);
							CopyMem(&TrkLab[LSByte],&TrkLab[LDByte],TD_LABELSIZE);
						}
						DByte+=BlkSize;
						LDByte+=TD_LABELSIZE;
					}
				z+=n;
#if DEBUG
				Printf("\n");
#endif
				if (!Args.Quiet && !Args.NoVerbose)
					Printf("\033[1F"
					  "Blocks %7ld - %7ld: %4ld of %4ld, %6ld bytes, %3ld %% done...      \n",
					  Blk,Blk+BlocksPerTrack-1,n,BlocksPerTrack,DByte,
					  (100*z)/Used);
				if (XWrite(File,TrkBuf,DByte)!=DByte
				  || XWrite(File,TrkLab,LDByte)!=LDByte)
				{
					cleanup(xwriteerr);
					return 20;
				}
			}
		}

		if (UBuf)
			XFlush(File);

		DateStamp(&Stop);
		if (!Args.Quiet)
		{
			Printf("%ld bytes, %ld blocks read%s\n",z*BlkSize,z,
			  Args.Pack ? " and packed to file":"");
			if ((Ticks=Stop.ds_Tick-Start.ds_Tick+3000*(Stop.ds_Minute-Start.ds_Minute
			  +1440*(Stop.ds_Days-Start.ds_Days)))>0)
			{
				if (Ticks>=180000)
					sprintf(buf,"%ldh %ldm %ld.%02lds",
					  Ticks/180000,(Ticks%180000)/3000,(Ticks%3000)/50,2*(Ticks%50));
				else if (Ticks>=3000)
					sprintf(buf,"%ldm %ld.%02lds",
					  Ticks/3000,(Ticks%3000)/50,2*(Ticks%50));
				else
					sprintf(buf,"%ld.%02lds",Ticks/50,2*(Ticks%50));
				Printf("Elapsed time: %s, %ld bytes --> %ld bytes, %ld bytes/s\n",
				  buf,z*BlkSize,TotalSize,z*BlkSize/Ticks*50);
			}
			else
				Printf("Internal clock not properly set, no time and speed info possible\n");
		}
	}

	if (File)
	{
		Close(File);
		File=NULL;
	}

	if (Args.BlockList && !Args.Quiet)
	{
		Printf("Creating block list file \"%s\"\n",Args.BlockList);
		if (File=OpenForWriteSafely(Args.BlockList))
		{
			for (n=BlocksPerDisk,z=1;n;n/=10)
				z++;
			sprintf(buf,"  %%%ldld   %%08lx\n",z);
			Printf("\n");
			for (Blk=0;Blk<BlocksPerDisk;Blk++)
				if (BlockUsed(Blk-Reserved))
					FPrintf(File,buf,Blk,Blk);
			Close(File);
			File=NULL;
		}
		else
			return 20;
	}

	cleanup(NULL);
	return 0;
}

VOID cleanup(BYTE *str)
{

#if DEBUG
	BYTE buf[256];
#endif

	if (UBuf)
		FreeMem(UBuf,ULen);
	if (PBuf)
		FreeMem(PBuf,PLen);
	if (TrkBuf)
		FreeMem(TrkBuf,BufSize);
	if (VerTrk)
		FreeMem(VerTrk,BufSize);
	if (XpkSublibBase)
		CloseLibrary(XpkSublibBase);
	if (XpkBase)
		CloseLibrary(XpkBase);
	if (File)
	{
		Close(File);
		if (Action==READ)
			DeleteFile(fname);
	}
	if (BAM)
		FreeMem(BAM,BAMSize);
	if (DiskIO)
		FreeDevice((struct IORequest *)DiskIO);
	if (BlkBuf)
		FreeMem(BlkBuf,(DosEnvec->de_SizeBlock<<3)+TD_LABELSIZE);
	if (Inhibited)
	{
		Inhibit(Filesysname,DOSFALSE);
#if DEBUG
		Delay(50);
		Printf("\nINFO %s\n",Filesysname);
		strcpy(buf,"INFO ");
		strcpy(&buf[5],Filesysname);
		Execute(buf,NULL,NULL);
#endif
	}
	if (DosListlocked)
		UnLockDosList(LDF_VOLUMES|LDF_READ);
	if (RDArgs)
		FreeArgs(RDArgs);
	if (DOSBase)
	{
		if (!Args.Quiet)
		{
			if (!Args.NoVerbose)
				Write(StdOut,"\033[1 p",5);
			if (str)
				Write(StdOut,str,strlen(str));
		}
		CloseLibrary((struct Library *)DOSBase);
	}
}

/* Write error message */

VOID HandleError(struct IOExtTD *DiskIO,BYTE *buffer)
{
	BYTE err;
	static char *DiskErr[21]={ /* Error messages */
		"Error while opening",
		"Command aborted",
		"Invalid command",
		"Bad data length",
		"Unknown error",
		"No sector header",
		"Bad sector preamble",
		"Bad sector ID",
		"Checksum error in header",
		"Checksum error in sector",
		"Too few sectors on track",
		"Sector header unreadable",
		"Disk is write protected",
		"Disk has changed or no disk present",
		"Error while verifying seek position",
		"Not enough memory",
		"Bad unit number",
		"Bad drive type",
		"Drive in use",
		"Ctrl-Amiga-Amiga was hit",
		"No error reported"};

	err=DiskIO->iotd_Req.io_Error;
	if (err<=35 && err>=20)
		strcpy(buffer,DiskErr[err-16]);
	else if (!err)
		strcpy(buffer,DiskErr[20]);
	else
		sprintf(buffer,"Error # %ld",err);
}

/*
Perform a trackdisk IO command and eventually ask "Retry/Cancel"
if an error occurs.

Inputs:  DiskIO       Pointer to IOExtTD
         BlockNo      If !=-1 and an error occurs, this number is printed
         Flags        Specifies what should be done if an error occurs
                      (see below)

Result:  TRUE if successful, FALSE if not

Comment: If Flags contains an illegal value, a retry is asked for
*/

/* Values for Flags */

#define ASKRETRY    0 /* A retry is asked for if an error occurs */
#define NEVERRETRY  1 /* Fail if an error occurs, write message */
#define IGNOREERROR 2 /* No output if an error occurs and fail */

BOOL Do(struct IOExtTD *DiskIO,LONG BlockNo,USHORT Flags)
{
	BYTE err[50];
	UWORD OldCmd;
//	struct IOExtTD IOBuf;

	while (1)
	{
		DiskIO->iotd_Req.io_Error=0;
		DoIO((struct IORequest *)DiskIO);
		if (DiskIO->iotd_Req.io_Error)
		{
			if (Flags==IGNOREERROR)
				return FALSE;
			HandleError(DiskIO,err);
			if (!Args.Quiet)
				if (BlockNo!=-1)
					Printf("Device error at block %ld: %s\n\n",BlockNo,err);
				else
					Printf("Device error: %s\n\n",err);
			if (Flags==NEVERRETRY || Args.Quiet || Args.NoConfirm)
				return FALSE;
/* We are very pedantic with inputs here. */
			while (1)
			{
				Printf("\033[1F\033[1K(R)etry or (C)ancel ? ");
				Flush(StdOut);
				FGets(StdIn,err,49);
				if (err[1]=='\n')
				{
					if (err[0]=='C' || err[0]=='c')
						return FALSE;
					else if (err[0]=='R' || err[0]=='r')
					{
//						IOBuf=*DiskIO;
						OldCmd=DiskIO->iotd_Req.io_Command;
						DiskChanges=GetDiskChangeCount(DiskIO);
						DiskIO->iotd_Req.io_Command=OldCmd;
//						*DiskIO=IOBuf;
						break;
					}
				}
			}
		}
		else
			return TRUE;
	}
}

/*
Initialize IORequest

Input:   size - Size of IORequest

Return:  Pointer to allocated IORequest incl. ReplyPort

Comment: This routine is called by GetDevice, it is not necessary to call it
         if GetDevice is used.
*/

struct IORequest *GetDevBlock(ULONG size)
{
	struct MsgPort *DevPort;
	struct IORequest *IOReq=NULL;

	if ((DevPort=(struct MsgPort *)CreateMsgPort())
	  && !(IOReq=CreateIORequest(DevPort,size)))
		DeleteMsgPort(DevPort);
	return IOReq;
}
	
/*
Release IORequest

Input:   Pointer to IORequest

Comment: This routine is called by FreeDevice, it is not necessary to call it
         if FreeDevice is used.
*/

VOID FreeDevBlock(struct IORequest *IOReq)
{
	if (IOReq->io_Message.mn_ReplyPort)
		DeleteMsgPort(IOReq->io_Message.mn_ReplyPort);
	DeleteIORequest(IOReq);
}

/*
Open Device incl. allocation und initialization of the IORequest

Inputs:  name     Device name
         unit     Unit of the Device (e.g. 1 for trackdisk.device for DF1)
         dev_req  Pointer to IORequest pointer
         flags    Flags to specify for OpenDevice()
         size     Size ofIORequest

Return:  0, if sucessful, 1 if not

Comment: Functions with all kickstart versions.
         If unsuccessful, all used resources are freed
*/

BYTE GetDevice(BYTE *name,ULONG unit,struct IORequest **dev_req,ULONG flags,
  ULONG size)
{
	if(*dev_req=GetDevBlock(size))
	{
		if (!OpenDevice((UBYTE *)name,unit,*dev_req,flags))
			return 0;
		else
		{
			FreeDevBlock(*dev_req);
			*dev_req=NULL;
			return 1;
		}
	}
	else
		return 1;
}

/*
Close Device incl. deallokation of the IORequest

Input: IOReq  Pointer to IORequest

Comment: Functions with all Kickstart versions
*/

VOID FreeDevice(struct IORequest *IOReq)
{
	if (IOReq->io_Message.mn_ReplyPort)
		DeletePort(IOReq->io_Message.mn_ReplyPort);
	CloseDevice(IOReq);
	FreeMem(IOReq,IOReq->io_Message.mn_Length);
}

/*
Determine number of disk changes

Input:  DiskIO  Pointer to IOExtTD

Return: Total number of disk changes
*/

ULONG GetDiskChangeCount(struct IOExtTD *DiskIO)
{
	DiskIO->iotd_Req.io_Command=(UWORD)TD_CHANGENUM;
	if (!Do(DiskIO,-1,NEVERRETRY))
		return -1;
	else
		return DiskIO->iotd_Req.io_Actual;
}

/*
Read sectors

Inputs:  DiskExtIO    Pointer to IOExtTD
         SectorBuffer Buffer for sector data
         LabelBuffer  Buffer for LabelBuffer data
         Offset       Number of first block to read
         NumBlocks    Number of blocks
         Block size   Size of a block in bytes
         Diskchanges  Number of disk changes having to stay constant,
                      -1 to ignore it

Return:  None

Comment: Note that an ETD command is not used because some HDs don't support
         them (e.g. oktagon.device). To avoid the disk change problem, the
         number of disk changes is checked by hand.
         It is a good idea to use buffer pointers divisable by 4 because HD
         devices may be much faster with this

         Kickstart 1.3 or lower users:
          - Use chip ram only for floppy support (Floppy MFM data is decoded
            and encoded by the blitter)
          - You should use even addresses or trackdisk.device may go nuts
            (I'm not sure about this, but DMA only funtions with even
            addresses, so be careful).

Bugs:   When in debug mode, The SectorBuffer must be even when running with
        a 68000.

        Never use the variable "_SpcVerify" in any way, it's for internal
        interaction with WriteSector() when verifying.
*/

BOOL _SpcVerify;

VOID ReadSectors(struct IOExtTD *DiskExtIO,APTR SectorBuffer,APTR LabelBuffer,
  ULONG Offset,ULONG NumBlocks,ULONG BlockSize,LONG Diskchanges,LONG Errblk)
{
#if DEBUG
	Printf("ReadSectors: Off    $%08lx | Blks   $%08lx | BlkSize $%08lx\n"
	       "             DskChg $%08lx | Errblk $%08lx\n",
	  Offset,NumBlocks,BlockSize,Diskchanges,Errblk);
#endif
	DiskExtIO->iotd_Req.io_Command=(UWORD) CMD_READ;
	DiskExtIO->iotd_Req.io_Offset=Offset*BlockSize;
	DiskExtIO->iotd_Req.io_Data=SectorBuffer;
	DiskExtIO->iotd_Req.io_Length=NumBlocks*BlockSize;
	DiskExtIO->iotd_SecLabel=(ULONG)LabelBuffer;
	Do(DiskExtIO,Errblk,_SpcVerify ? IGNOREERROR:ASKRETRY);
#if DEBUGReadSectors
{
	USHORT i,imax=(BlockSize-1)>>2;

	for (i=0;i<=imax;i++)
		Printf("%08lx%lc",((ULONG *)SectorBuffer)[i],(i & 7)==7 || i==imax ? '\n':' ');
}
#endif
}

BYTE VerifySectors(struct IOExtTD *DiskExtIO,APTR SectorBuffer,APTR LabelBuffer,
  APTR VerSecBuf,APTR VerLabBuf,ULONG Offset,ULONG NumBlocks,ULONG BlockSize,
  LONG Diskchanges,LONG Errblk)
{
	BYTE err;
	LONG i,NumLWords;

/* In case label buffers aren't supported, but the caller provides a buffer
   for verifying them. Copying will let the verification succeed in this case. */
	if (VerLabBuf)
		CopyMem(LabelBuffer,VerLabBuf,NumBlocks*TD_LABELSIZE);
	_SpcVerify=1;
	ReadSectors(DiskExtIO,VerSecBuf,VerLabBuf,Offset,NumBlocks,BlockSize,
	  Diskchanges,Errblk);
	_SpcVerify=0;
	if (!(err=DiskExtIO->iotd_Req.io_Error))
	{
		for (i=0,NumLWords=(NumBlocks*BlockSize)>>2;i<NumLWords;i++)
			if (((LONG *)SectorBuffer)[i]!=((LONG *)VerSecBuf)[i])
				return 127;
		if (VerLabBuf)
			for (i=0,NumLWords=(NumBlocks*TD_LABELSIZE)>>2;i<NumLWords;i++)
				if (((LONG *)LabelBuffer)[i]!=((LONG *)VerLabBuf)[i])
					return 126;
	}
	return 0;
}


/*
Write sectors

Inputs:  DiskExtIO    Pointer to IOExtTD
         SectorBuffer Buffer for sector data
         LabelBuffer  Buffer for LabelBuffer data
         Offset       Number of first block to write
         NumBlocks    Number of blocks to write
         Block size   Size of a block in bytes
         Diskchanges  Number of disk changes having to stay constant,
                      -1 to ignore it
         Update       Set to nonzero if an update should be done
         VerTrkBuf    If nonzero, a verify will be made using this buffer
         VerLabBuf    If nonzero and VerTrkBuf!=0, a label buffer verify will
                      be made using this buffer

Return:  None

Comment: If the data should be verified, an update is done before, no matter
         what the parameter "Update" contains.

         This routine needs two global variables "StdIn" and "StdOut" that
         must be BPTRs of the input and output filehandles (acquired by
         Input() and Output() respectively).

         Note that an ETD command is not used because some HDs don't support
         them (e.g. oktagon.device). To avoid the disk change problem, the
         number of disk changes is checked by hand.
         It is a good idea to use buffer pointers divisable by 4 because HD
         devices may be much faster with this.

         Kickstart 1.3 or lower users:
          - Use chip ram only for floppy support (Floppy MFM data is decoded
            and encoded by the blitter)
          - You should use even addresses or trackdisk.device may go nuts
            (I'm not sure about this, but DMA only funtions with even
            addresses, so be careful).

         Kickstart 2.0+ users:
          - Chip ram isn't needed, don't use it (floppies are read/written
            from a trackdisk-internal buffer from/to which the data is copied)
*/

VOID WriteSectors(struct IOExtTD *DiskExtIO,APTR SectorBuffer,APTR LabelBuffer,
  ULONG Offset,ULONG NumBlocks,ULONG BlockSize,LONG Diskchanges,LONG Errblk,
  BOOL Update,APTR VerSecBuf,APTR VerLabBuf)
{
	BYTE err,buf[50];

	while (1)
	{
		DiskExtIO->iotd_Req.io_Command=(UWORD) CMD_WRITE;
		DiskExtIO->iotd_Req.io_Offset=Offset*BlockSize;
		DiskExtIO->iotd_Req.io_Data=SectorBuffer;
		DiskExtIO->iotd_Req.io_Length=(ULONG) NumBlocks*BlockSize;
		DiskExtIO->iotd_SecLabel=(ULONG)LabelBuffer;
	
		Do(DiskExtIO,Errblk,ASKRETRY);
		if (DiskIO->iotd_Req.io_Error)
			return;
		if (Update || VerSecBuf)
		{
			DiskExtIO->iotd_Req.io_Command=(UWORD) CMD_UPDATE;
			Do(DiskExtIO,Errblk,ASKRETRY);
			if (DiskIO->iotd_Req.io_Error)
				return;
		}
		if (VerSecBuf)
			if (err=VerifySectors(DiskExtIO,SectorBuffer,LabelBuffer,VerSecBuf,VerLabBuf,
			  Offset,NumBlocks,BlockSize,Diskchanges,Errblk))
			{
				if (Args.Quiet || Args.NoConfirm)
					return;
/* Rethink last parameter if VerifySectors changes return value range */
				Printf("Verify error somewhere at block %ld-%ld% !\n\n",
				  Offset,Offset+NumBlocks-1,err==127 ? "":"'s label buffers");
/* We are very pedantic with inputs here. */
				while (1)
				{
					Printf("\033[1F\033[1K(R)etry or (C)ancel ? ");
					Flush(StdOut);
					FGets(StdIn,buf,49);
					if (buf[1]=='\n')
					{
						if (buf[0]=='C' || buf[0]=='c')
						{
							DiskExtIO->iotd_Req.io_Error=1;
							return;
						}
						else if (buf[0]=='R' || buf[0]=='r')
							break;
					}
				}
			}
			else
				break;
		else
			break;
	}
}

/* Format a track. Set VerTrk to zero for skipping verifying */

VOID FormatTrack(struct IOExtTD *DiskExtIO,ULONG TrackNo,LONG BytesPerTrack,
  APTR FormatData,APTR LabelData,LONG BlkSize,LONG Diskchanges,LONG Errblk,
  APTR VerTrk,APTR VerLab,BOOL UseETD)
{
	BYTE err,buf[50];
	LONG BlocksPerTrack;

	BlocksPerTrack=BytesPerTrack/BlkSize;
	while (1)
	{
		DiskExtIO->iotd_Count=DiskChanges;
		DiskExtIO->iotd_Req.io_Command=(UWORD)(UseETD ? ETD_FORMAT:TD_FORMAT);
		DiskExtIO->iotd_Req.io_Offset=TrackNo*BytesPerTrack;
		DiskExtIO->iotd_Req.io_Data  =FormatData;
		DiskExtIO->iotd_Req.io_Length=BytesPerTrack;
		DiskExtIO->iotd_SecLabel=(ULONG)LabelData;

		Do(DiskExtIO,Errblk,ASKRETRY);
		if (DiskExtIO->iotd_Req.io_Error)
			return;

		if (!UseETD && LabelData)
		{
			WriteSectors(DiskExtIO,FormatData,LabelData,TrackNo*BlocksPerTrack,
			  BlocksPerTrack,BlkSize,Diskchanges,Errblk,1,VerTrk,VerLab);
			return;
		}
		if (VerTrk)
			if (err=VerifySectors(DiskExtIO,FormatData,LabelData,VerTrk,VerLab,
			  TrackNo*BlocksPerTrack,BlocksPerTrack,BlkSize,Diskchanges,Errblk))
			{
				if (Args.Quiet || Args.NoConfirm)
					return;
				Printf("Verify error at Cyl %ld, head %ld !\n\n",
				  TrackNo/DosEnvec->de_Surfaces,TrackNo%DosEnvec->de_Surfaces);
/* We are very pedantic with inputs here. */
				while (1)
				{
					Printf("\033[1F\033[1K(R)etry or (C)ancel ? ");
					Flush(StdOut);
					FGets(StdIn,buf,49);
					if (buf[1]=='\n')
					{
						if (buf[0]=='C' || buf[0]=='c')
						{
							DiskExtIO->iotd_Req.io_Error=1;
							return;
						}
						else if (buf[0]=='R' || buf[0]=='r')
							break;
					}
				}
			}
			else
				break;
		else
			break;
	}
}

/*
Buffered writing to a file with data packed by an xpk packer

If not exactly the same number of bytes to writes is actually written,
you MUST abort the operation.
*/

LONG XWrite(BPTR File,APTR Buf,LONG Len)
{
	LONG Pos,Size,i,Checksum,err;

	if (Args.Pack)
	{
		for (Pos=0;Pos<Len;Pos++)
		{
			UBuf[XpkPos++]=((UBYTE *)Buf)[Pos];

			if (XpkPos==XpkLen)
			{
				strcpy(xpkmsg,xpkmsgheader);
				if (Args.Password)
					err=XpkPackTags(XPK_InBuf,UBuf,
					                XPK_InLen,XpkLen,
					                XPK_OutBuf,PBuf,
					                XPK_OutBufLen,PLen,
					                XPK_PackMethod,XpkPackName,
					                XPK_PackMode,XpkMode,
					                XPK_GetOutLen,&Size,
					                XPK_GetError,&xpkmsg[11],
					                XPK_Password,Args.Password,
					                TAG_DONE);
				else
					err=XpkPackTags(XPK_InBuf,UBuf,
					                XPK_InLen,XpkLen,
					                XPK_OutBuf,PBuf,
					                XPK_OutBufLen,PLen,
					                XPK_PackMethod,XpkPackName,
					                XPK_PackMode,XpkMode,
					                XPK_GetOutLen,&Size,
					                XPK_GetError,&xpkmsg[11],
					                TAG_DONE);
				if (err)
				{
					Pos=strlen(xpkmsg);
					xpkmsg[Pos]='\n';
					xpkmsg[Pos+1]=0;
					xwriteerr=xpkmsg;
					return -1;
				}
#if DEBUG
				Printf("Pack:%ld --> %ld, buffer size=%ld, last longword=0x%02.2lx%02.2lx%02.2lx%02.2lx\n\n",XpkLen,Size,ULen,UBuf[XpkLen-4],UBuf[XpkLen-3],UBuf[XpkLen-2],UBuf[XpkLen-1]);
#endif
				for (i=Checksum=0;i<Size;i++)
					Checksum+=(LONG)PBuf[i];

				if (Write(File,&Size,4)!=4
				  || Write(File,PBuf,Size)!=Size
				  || Write(File,&Checksum,4)!=4)
				{
					xwriteerr=cantwrite;
					return -1;
				}
				XpkPos=0;
				TotalSize+=(Size+8);
			}
		}
		return Pos;
	}
	else if ((Size=Write(File,Buf,Len))!=Len)
		xwriteerr=cantwrite;
	TotalSize+=Size;
	return Size;
}

/*
Flushing of write buffer. Use with xpk packer only !!
Further write operations are possible, since XRead supports variable
XPK chunk lengths (even zero)
 */

LONG XFlush(BPTR File)
{
	LONG Size,i,Checksum,err;

	if (XpkPos)
	{
		strcpy(xpkmsg,xpkmsgheader);
		if (Args.Password)
			err=XpkPackTags(XPK_InBuf,UBuf,
			                XPK_InLen,XpkPos,
			                XPK_OutBuf,PBuf,
			                XPK_OutBufLen,PLen,
			                XPK_PackMethod,XpkPackName,
			                XPK_PackMode,XpkMode,
			                XPK_GetOutLen,&Size,
			                XPK_GetError,&xpkmsg[11],
			                XPK_Password,Args.Password,
			                TAG_DONE);
		else
			err=XpkPackTags(XPK_InBuf,UBuf,
			                XPK_InLen,XpkPos,
			                XPK_OutBuf,PBuf,
			                XPK_OutBufLen,PLen,
			                XPK_PackMethod,XpkPackName,
			                XPK_PackMode,XpkMode,
			                XPK_GetOutLen,&Size,
			                XPK_GetError,&xpkmsg[11],
			                TAG_DONE);
		if (err)
		{
			Size=strlen(xpkmsg);
			xpkmsg[Size]='\n';
			xpkmsg[Size+1]=0;
			xwriteerr=xpkmsg;
			return -1;
		}
#if DEBUG
		Printf("Pack:%ld --> %ld, buffer size=%ld, last longword=0x%02.2lx%02.2lx%02.2lx%02.2lx\n\n",XpkPos,Size,ULen,UBuf[XpkPos-4],UBuf[XpkPos-3],UBuf[XpkPos-2],UBuf[XpkPos-1]);
#endif
		for (i=Checksum=0;i<Size;i++)
			Checksum+=(LONG)PBuf[i];
		if (Write(File,&Size,4)!=4
		  ||  Write(File,PBuf,Size)!=Size
		  || Write(File,&Checksum,4)!=4)
		{
			xwriteerr=cantwrite;
			return -1;
		}
		TotalSize+=(Size+8);
		XpkPos=0;
		return 0;
	}
}

/*
Buffered reading from a file with data packed by an xpk packer

If not exactly the same number of bytes to read is actually read or
an error occurs, it is not as fatal as for writing, so you may retry.
*/

LONG XRead(BPTR File,APTR Buf,LONG Len)
{
	LONG Pos,Err,Checksum,i;

	if (UBuf)
	{
		for (Pos=0;Pos<Len;Pos++)
		{
			if (XpkPos==XpkLen) // Buffer empty: Refill
			{
				if (Read(File,&PLen,4)!=4) // Read packblock size
				{
					xreaderr=cantread;
					return -1;
				}
				if (!(PBuf=AllocMem(PLen,0))) // Allocate mem for packblock
				{
					xreaderr=nomem;
					return -1;
				}
				if (Read(File,PBuf,PLen)!=PLen  // Read packblock
				  || (VersRev>=0x13 && Read(File,&Checksum,4)!=4)) // Read checksum if presetn
					i=-1;
				else if (VersRev>=0x13)  // Confirm checksum if present
				{
					for (i=0;i<PLen;i++)        // Checksum = sum of bytes;
						Checksum-=(LONG)PBuf[i];
					if (Checksum)
						i=-2;
					else
						i=0;
				}
				else
					i=0;
				if (i)   // Abort on error
				{
					FreeMem(PBuf,PLen);
					PBuf=NULL;
					xreaderr=(i==-1 ? cantread:"Checksum error.\n");
					return -1;
				}

				TotalSize+=(PLen+4);
				strcpy(xpkmsg,xpkmsgheader);
				if (Args.Password)                       // Unpack data
					Err=XpkUnpackTags(XPK_InBuf,PBuf,
					                  XPK_InLen,PLen,
					                  XPK_OutBuf,UBuf,
					                  XPK_OutBufLen,ULen,
					                  XPK_GetOutLen,&XpkLen,
					                  XPK_GetError,&xpkmsg[11],
					                  XPK_PassThru,TRUE,
					                  XPK_Password,Args.Password,
					                  TAG_DONE);
				else
					Err=XpkUnpackTags(XPK_InBuf,PBuf,
					                  XPK_InLen,PLen,
					                  XPK_OutBuf,UBuf,
					                  XPK_OutBufLen,ULen,
					                  XPK_GetOutLen,&XpkLen,
					                  XPK_GetError,&xpkmsg[11],
					                  XPK_PassThru,TRUE,
					                  TAG_DONE);
				FreeMem(PBuf,PLen);
				PBuf=NULL;
				if (Err)
				{
					Pos=strlen(xpkmsg);
					xpkmsg[Pos]='\n';
					xpkmsg[Pos+1]=0;
					xreaderr=xpkmsg;
					return -1;
				}
#if DEBUG
				Printf("Unpack:%ld --> %ld, buffer size=%ld, last longword=0x%02.2lx%02.2lx%02.2lx%02.2lx\n\n",PLen,XpkLen,ULen,UBuf[XpkLen-4],UBuf[XpkLen-3],UBuf[XpkLen-2],UBuf[XpkLen-1]);
#endif
				XpkPos=0; /* Set XpkPos here to enable a retry after an error */
			}
			((UBYTE *)Buf)[Pos]=UBuf[XpkPos++];
		}
	}
	else
	{
		if ((Pos=Read(File,Buf,Len))!=Len)
			xreaderr=cantread;
		TotalSize+=Pos;
	}
	return Pos;
}

/* Do initializaitons. Success: -1 returned, return code otherwise */

LONG InitStuff(VOID)
{
	struct Message *WbMsg;

/* Get SysBase, open DOS and check version */

	SysBase=*(struct ExecBase **)4;

/* Find own process */
	OwnProc=(struct Process *)FindTask(NULL);

	if (!OwnProc->pr_CLI) /* Must be done before opening dos.library */
		WbMsg=GetMsg(&OwnProc->pr_MsgPort);

	DOSBase=(struct DosLibrary *)OpenLibrary("dos.library",0);

	if (!OwnProc->pr_CLI)
	{
		if (DOSBase)
		{
			if (StdOut=Open("CON:20/20/400/50/PackDev V1.8",MODE_NEWFILE))
			{
				Write(StdOut,"\n You must start this program from a shell (yet).\n"
				               " But don't despair, a GUI is in progress :-)\n\n",96);
				Delay(200);
				Close(StdOut);
			}
			CloseLibrary((struct Library *)DOSBase);
		}
		Forbid();
		ReplyMsg(WbMsg);
		return DOSBase ? 0:100;           /* No Permit() !! */
	}

	if (!DOSBase)
		return 100;

	StdIn=Input();
	StdOut=Output();

	if (DOSBase->dl_lib.lib_Version<36)
	{
		cleanup("You need dos.library V36+\n");
		return 20;
	}

/* Read parameters */

	if (!(RDArgs=ReadArgs("FROM/O,TO/O,P=PACK/K/O,XB=XPKBUFSIZE/K/N/O,"
	  "M=MEMTYPE/K/O,CU=CLRUNUSED/S/O,ETDF=ETDFORMAT/S/O,TDF=TDFORMAT/S/O,"
	  "TDFL=TDFLABEL/S/O,NVF=NOVERIFY/S/O,"
	  "ALL/S/O,NV=NOVERBOSE/S/O,Q=QUIET/S/O,NC=NOCONFIRM/S/O,"
	  "VIEWFILE/K/O,VIEWFILESYS/K/O,TESTFILE/K/O,BLOCKLIST/K/O,PASSWORD/K/O,"
	  "TN=TRUENAME/S/O,F=FORCE/S/O",
	  (LONG*)&Args,NULL)))
	{
		cleanup(noargs);
		return 20;
	}
	return -1;
}

LONG HandleParameters(VOID)
{
	LONG flag,l1,l2;

	if (!Args.Quiet)
	{
		Write(StdOut,&version[6],strlen(&version[6]));
		Write(StdOut,"\n",1);
	}

/* Check parameters */

	if (Args.TrueName && !Args.To && !(Args.ViewFile || Args.TestFile))
	{
		cleanup("TRUENAME must be specified along with an action that requires\n"
		        "a file name.\n");
		return 20;
	}

	if ((Args.From!=0) ^ (Args.To!=0)) /* There is no logical eor */
	{
		cleanup("Either both of FROM and TO must be specified or none of them.\n");
		return 20;
	}
	else if (Args.From)
	{
		l1=strlen(Args.From);
		l2=strlen(Args.To);
                              /* There is no logical eor */
		if (l1 && l2 && (flag=(Args.From[l1-1]==':'))+(Args.To[l2-1]==':')==1)
		{
			if (flag)
			{
				Action=READ;
				Filesysname=Args.From;
				Filename=Args.To;
			}
			else
			{
				Action=WRITE;
				Filesysname=Args.To;
				Filename=Args.From;
			}
			if (Args.ViewFile || Args.ViewFilesys || Args.TestFile)
			{
				cleanup("When FROM and TO are set, neither VIEWFILE nor VIEWFILESYS nor\n"
				        "TESTFILE must be set.\n");
				return 20;
			}
		}
		else
		{
			cleanup("One of FROM and TO must be a filesystem name, the other one a file name.\n"
                 "Also note that you cannot read/write from/to NIL:, CON: etc.\n");
			return 20;
		}
	}
	else
	{
		if (Args.ViewFile)
		{
			Action=VIEWFILE;
			Filename=Args.ViewFile;
			flag=1;
		}
		else
			flag=0;
		if (Args.ViewFilesys)
		{
			Action=VIEWFILESYS;
			Filesysname=Args.ViewFilesys;
			flag++;
		}
		if (Args.TestFile)
		{
			Action=TESTFILE;
			Filename=Args.TestFile;
			flag++;
		}
		if (flag>1)
		{
			cleanup("Only one of VIEWFILE, VIEWFILESYS and TESTFILE may be specified.\n");
			return 20;
		}
		else if (!flag)
		{
			cleanup("No action specified.\n");
			return 20;
		}
	}

	if (Args.MemType)
	{
		if (Action==VIEWFILE || Action==TESTFILE)
		{
			cleanup("MEMTYPE may only be specified when handling a filesystem.\n");
			return 20;
		}

		if (!stricmp(Args.MemType,"CHIP"))
			MemType=MEMF_CHIP;
		else if (!stricmp(Args.MemType,"FAST"))
			MemType=MEMF_FAST;
		else if (stricmp(Args.MemType,"ANY"))
		{
			cleanup("MEMTYPE must be set to CHIP, FAST or ANY (default) or be unset\n");
			return 20;
		}
	}

	if (Args.Pack && Action!=READ)
	{
		cleanup("PACK parameter is only allowed when reading from a filesystem\n");
		return 20;
	}

	if (Args.ReadAll && Action!=READ)
	{
		cleanup("ALL paramater is only allowed when reading from a filesystem\n");
		return 20;
	}

	if (Args.XpkBufSize)
	{
		if (Action!=READ || !Args.Pack)
		{
			cleanup("XPKBUFSIZE parameter only allowed when reading from a filesystem\n"
			        "along with the PACK parameter\n");
			return 20;
		}
		if (*Args.XpkBufSize<1)
		{
			cleanup("XPKBUFSIZE must be at least 1 block\n");
			return 20;
		}
	}

	if (Args.ClrUnused && Action!=WRITE)
	{
		cleanup("CLRUNUSED may only be specified along when writing to a filesystem.\n");
		return 20;
	}

	if (Action!=WRITE && (Args.ETDFormat || Args.TDFormat || Args.TDFLabel || Args.NoVerify))
	{
		cleanup("ETDFORMAT, TDFORMAT, TDFLABEL and NOVERIFY may only be specified when\n"
		        "writing to a filesystem.\n");
		return 20;
	}

	l1=(Args.ETDFormat!=0)+(Args.TDFormat!=0)+(Args.TDFLabel!=0);
	if (l1>1)
	{
		cleanup("You may specify only one of ETDFORMAT, TDFORMAT and TDFLABEL.\n");
		return 20;
	}

	if (Args.BlockList && (Action==VIEWFILESYS || Action==VIEWFILE))
	{
		cleanup("BLOCKLIST output not possible along with file/filesystem viewing.\n");
		return 20;
	}
	if (Args.Password && (Action==VIEWFILE || Action==VIEWFILESYS))
	{
		cleanup("A password cannot be specified along with file/filesystem viewing.\n");
		return 20;
	}
	if (Args.Password && Action==READ && !Args.Pack)
	{
		cleanup("A packer must be specified when packing with a password\n");
		return 20;
	}

	if (Args.NoVerify && Action!=WRITE)
	{
		cleanup("Verifying can only be switched off when writing to a volume\n");
		return 20;
	}

	if (Args.Force && Action!=READ)
	{
		cleanup("Overwriting of a file can only be enforced when a file is written to\n");
		return 20;
	}

	return -1;
}

/* Open all needed xpk libraries */

LONG OpenXPKLibs(BOOL DoCleanup)
{
	BYTE *pack;

	if (!(XpkBase=OpenLibrary(xpkname,0)))
	{
		if (DoCleanup)
			cleanup(noxpklib);
		return 20;
	}

/* Open sublib. Maybe disk with LIBS: is to be handled (it could not be
   opened if it isn't in RAM then) */

	if (Args.Pack)
		pack=Args.Pack;
	else if (XpkPackName[0])
		pack=XpkPackName;
	else
		pack=NULL;
#if DEBUG
	Printf("Pack: %08lx\n",pack);
#endif
	if (pack)
	{
		xpksublib[20]=toupper(pack[0]);
		xpksublib[21]=toupper(pack[1]);
		xpksublib[22]=toupper(pack[2]);
		xpksublib[23]=toupper(pack[3]);
		if (!(XpkSublibBase=OpenLibrary(xpksublib,0)))
		{
			if (DoCleanup)
				cleanup("Cannot open XPK sublibrary.\n");
			return 20;
		}
	}
	return -1;
}

/* Get Device data and disable requesters. Returns -1 if successful,
   error to exit with otherwise. */

LONG ReadFilesystem(VOID)
{
	APTR  WindowPtr;
	BPTR  FileLock;
	LONG n;
	BYTE inbuf[256];
/*
	LONG ioerr;
	BYTE errhdr[50];
	BYTE errstr[FAULT_MAX+52];
*/

/* Check if device is a filesystem check not necessary, already done */

	while (1)
{
/* Inhibit dos requesters by setting pr_WindowPtr to -1 */
		WindowPtr=OwnProc->pr_WindowPtr;
		OwnProc->pr_WindowPtr=(APTR)-1;

		FileLock=Lock(Filesysname,ACCESS_READ);

/* Reset pr_WindowPtr */
		OwnProc->pr_WindowPtr=WindowPtr;

		if (FileLock)
		{
			DosDisk=1;
			UnLock(FileLock);
		}
		if (IoErr()!=ERROR_NO_DISK)
			break;
		else
	{

			if (!Args.Quiet)
				Printf("No disk in drive.\n");
			if (Args.Quiet || Args.NoConfirm)
		{
				cleanup(NULL);
				return 20;
		}

			Printf("\n");
			while (1)
		{
				Printf("\033[1F\033[1K(R)etry or (C)ancel ? ");
				Flush(StdOut);
				FGets(StdIn,inbuf,255);
				if (inbuf[1]!='\n')
					continue;
				if (inbuf[0]=='r' || inbuf[0]=='R')
					break;
				if (inbuf[0]=='c' || inbuf[0]=='C')
			{
					cleanup(NULL);
					return 20;
			}
		}
	}
}
/*
	else if (ioerr=IoErr())
	{
		sprintf(errhdr,"Cannot access \"%s\"",Filesysname);
		Fault(ioerr,errhdr,errstr,FAULT_MAX+50);
		sprintf(errhdr," (DOS error %ld)\n",ioerr);
		strcat(errstr,errhdr);
		cleanup(errstr);
		return 20;
	}
*/

	len=strlen(Filesysname);
	strcpy(FSnocolon,Filesysname);
	FSnocolon[len-1]=0; /* Delete ":" */

/* Lock DosList of Devices for reading, abort on failure */

	for (n=0;n<10;n++)
	{
		if (DosList=AttemptLockDosList(LDF_DEVICES|LDF_READ))
			break;
		else if (n)
			Write(StdOut,".",1);
		else
			Write(StdOut,"DosList in use, waiting .",25);
		if (n!=9)
			Delay(50);
	}

	if (n==10)
	{
		cleanup(" try again later\n");
		return 10;
	}
	else if (n)
		Write(StdOut," OK\n",4);
	
	DosListlocked=1;

	if (DosList=FindDosEntry(DosList,FSnocolon,LDF_DEVICES|LDF_READ))
		fssm=(struct FileSysStartupMsg *)
		  BADDR(DosList->dol_misc.dol_handler.dol_Startup);
	else
	{
		cleanup("Cannot find this filesystem\n");
		return 20;
	}

/* Unlock DosList of Devices */
	UnLockDosList(LDF_DEVICES|LDF_READ);
	DosListlocked=0;

/* Inhibit filesystem */
	for (n=0;n<10;n++)
	{
		if (Inhibited=Inhibit(Filesysname,DOSTRUE))
			break;
		else if (n)
			Write(StdOut,".",1);
		else
			Write(StdOut,"Filesystem in use, waiting .",28);
		if (n!=9)
			Delay(50);
	}

	if (n==10)
	{
		cleanup(" try again later\n");
		return 10;
	}
	else if (n)
		Write(StdOut," OK\n",4);

	DosEnvec=(struct DosEnvec *)BADDR(fssm->fssm_Environ);
	if (DosEnvec->de_SectorPerBlock!=1)
	{
		cleanup("Currently only filesystems with one sector per block are supported\n");
		return 10;
	}
	return -1;
}


BPTR OpenForWriteSafely(BYTE *Name)
{
	BPTR file;
	BYTE buf[256];

	if (Args.Force)
	{
		if (!(file=Open(Name,MODE_NEWFILE)))
			cleanup(cantopen);
		return file;
	}
	if (file=Open(Name,MODE_OLDFILE))
	{
		Close(file);
		if (!Args.Quiet)
		{
			Printf("File already exists. ");
			if (Args.NoConfirm)
			{
				Printf("\n");
				cleanup(aborted);
				return NULL;
			}
			else
			{
				Printf("Overwrite (yN) ? ");
				Flush(StdOut);
				FGets(StdIn,buf,255);
				if (buf[1]!='\n' || (buf[0]!='y' && buf[0]!='Y'))
				{
					cleanup(aborted);
					return NULL;
				}
				else if (!(file=Open(Name,MODE_NEWFILE)))
				{
					cleanup(cantopen);
					return NULL;
				}
				else
					return file;
			}
		}
		else
		{
			cleanup(aborted);
			return NULL;
		}
	}
	else if (!(file=Open(Name,MODE_NEWFILE)))
	{
		cleanup(cantopen);
		return NULL;
	}
	else
		return file;
}
