/* LHwarp 1.11, written by Jonathan Forbes */
/* Copyright © Xenomiga Technology, 1990   */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/io.h>
#include <devices/trackdisk.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <stdlib.h>
#include <stdio.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include "LHcrc.h"


/* LHwarp version 1.11 */
#define MAJOR_VERSION 1
#define MINOR_VERSION 1
#define DECIMAL_VERSION 1


/* Trackdisk information */
#define NUM_HEADS 2
#define TRACK_SIZE   (TD_SECTOR * NUMSECS * NUM_HEADS)
#define LABEL_LENGTH (16 * NUMSECS * NUM_HEADS)
#define NUM_CYLS     80
#define BLOCK_SIZE   TD_SECTOR
#define NUM_BLOCKS   (NUM_CYLS * NUM_HEADS * NUMSECS)
#define ROOT_BLOCK   (NUM_BLOCKS / 2)
#define BITMAP_INDEX 79
#define NUM_LONGS    (NUM_BLOCKS / 32)


/* File i/o information */
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2


/* LHwarp commands */
#define WARP_READ 1
#define WARP_WRITE 2


/* For buffered i/o */
#define IN_BUFFER_SIZE 16384
#define OUT_BUFFER_SIZE 16384


/* Only LZHUF is supported at the moment */
#define COMPTYPE_LHARC 0
#define COMPTYPE_NONE 1


/* Track data types */
#define DATATYPE_NORMAL 0
#define DATATYPE_RAW 1
#define DATATYPE_NORMAL_BLANK 2


/* CRC-32 macro */
#define UPDC32(b, c) (cr3tab[((int)c ^ b) & 0xff] ^ ((c >> 8) & 0x00FFFFFF))


struct WarpHeader
   {
      UBYTE  MajorVersion;
      UBYTE  MinorVersion;
      UBYTE  DecimalVersion;
      UBYTE  Reserved;
      USHORT StartTrack;
      USHORT EndTrack;
      ULONG  TextLength;
      ULONG  CompressedTextLength;
   };


struct CompHeader101
   {
      UBYTE  CompressionType;
      UBYTE  DataType;
      UBYTE  TrackNumber;
      UBYTE  Unused;
      ULONG  FullLength;
      ULONG  CompressedLength;
   };


struct CompHeader102
   {
      UBYTE  CompressionType;
      UBYTE  DataType;
      UBYTE  TrackNumber;
      UBYTE  Unused;
      ULONG  FullLength;
      ULONG  CompressedLength;
      ULONG  CheckSum;
   };


struct CompHeader110
   {
      UBYTE  CompressionType;
      UBYTE  DataType;
      UBYTE  TrackNumber;
      UBYTE  Unused;
      ULONG  SectorMask;
      ULONG  FullLength;
      ULONG  CompressedLength;
      ULONG  CRC;
   };


UBYTE          *iotd_SecLabel = NULL;


struct WarpHeader WHeader;
struct IOExtTD   *ReadMsg   = NULL;
struct IOExtTD   *WriteMsg  = NULL;
struct MsgPort   *ReadPort  = NULL;
struct MsgPort   *WritePort = NULL;
long              GlobalTrack;
long              GlobalSector;
ULONG             GlobalSectorMap;


extern FILE      *infile;            /* Input file handle  */
extern FILE      *outfile;           /* Output file handle */
extern UBYTE     *FilePosition;      /* For memory getc() and putc() functions */
extern UBYTE     *EndOfFilePosition; /* For memory getc() and putc() functions */
extern UBYTE     *PutFilePosition;   /* For memory getc() and putc() functions */
extern long       cr3tab[];          /* External CRC-32 table */


/* LZHUF defines and externs */
#define N		4096	/* Size of string buffer */
#define F		60	   /* Size of look-ahead buffer */
#define THRESHOLD	2
#define NIL		N	   /* End of tree's node  */


extern unsigned char *text_buf;
extern short         *lson, *rson, *dad;


struct CompHeader101 CHeader101; /* Version 1.01      */
struct CompHeader102 CHeader102; /* Version 1.02/1.03 */
struct CompHeader110 CHeader110; /* Version 1.10/1.11 */


char                 FileName[256];
char                 TextFileName[256];
UBYTE                Command;
UBYTE                AppendTextFile;

USHORT               Version;

UBYTE                UseBitMap;
BPTR                 FHandle;
ULONG                StartTrack;
ULONG                EndTrack;
ULONG                Unit;
APTR                 DataBlock = NULL;
APTR                 NewBlock = NULL;
APTR                 TrackdiskBlock = NULL;
UBYTE                BitMapAllocation[NUM_CYLS][NUMSECS * NUM_HEADS];


/* __regargs ... Isn't Lattice C amazing? */
long  __regargs GetFileLength(char *FileToOpen);   /* Find file length    */
ULONG __regargs DoSum(UBYTE *Block, ULONG Length); /* Checksum-32 routine */
ULONG __regargs DoCRC(UBYTE *Block, ULONG Length); /* Fast CRC-32 routine */
void  __regargs Decode(ULONG Size);                /* Uncompress data     */
UBYTE           ReadCHeader();                     /* Read compression header */


void main(int argc, char **argv)
{
   /* Make sure the user supplied the correct number of arguments */
   if (argc != 2 && argc != 4 && argc != 6 && argc != 7)
      {
         Title();
         exit (1);
      }

   BuildFileName(argv);

   if (!stricmp(argv[1], "READ"))
      {
         DoReadInit();
         UseBitMap = 1;
      }
   else if (!stricmp(argv[1], "WRITE"))
      {
         Command = WARP_WRITE;
      }
   else if (!stricmp(argv[1], "NOMAP"))
      {
         DoReadInit();
         UseBitMap = 0;
      }
   else
      {
         puts("\nUnknown command\n");
         exit(1);
      }

   if (argc < 6 && (Command == WARP_READ))
      {
         Title();
         exit (1);
      }

   if (argc == 7)
      {
         strcpy(TextFileName, argv[6]);
         AppendTextFile = 1;
      }
   else
      {
         AppendTextFile = 0;
      }

   /* Get the unit number */
   Unit       = atoi(argv[2]);

   /* Get the start and end tracks */
   if (argc > 5)
      {
         StartTrack = atoi(argv[4]);
         EndTrack   = atoi(argv[5]);
      }

   if (StartTrack > EndTrack)
      {
         puts("Start track is greater than end track\n");
         exit (1);
      }

   Init();

   if (Command == WARP_WRITE)
      {
         WriteData();
      }
   else
      {
         ReadData();
      }
}


Init()
{
   text_buf = NULL;
   lson     = NULL;
   rson     = NULL;
   dad      = NULL;

   if (!(ReadPort = CreatePort(0L, 0L)))
      {
         puts("Couldn't create read port");
         exit (1);
      }

   if (!(WritePort = CreatePort(0L, 0L)))
      {
         puts("Couldn't create write port");
         CleanUp();
         exit (1);
      }

   if (!(ReadMsg = (struct IOExtTD *) CreateExtIO(ReadPort, (long) sizeof(struct IOExtTD))))
      {
         puts("Couldn't create i/o read request");
         CleanUp();
         exit (1);
      }

   if (!(WriteMsg = (struct IOExtTD *) CreateExtIO(WritePort, (long) sizeof(struct IOExtTD))))
      {
         puts("Couldn't create i/o write request");
         CleanUp();
         exit (1);
      }

   ReadMsg->iotd_Req.io_Command = ETD_READ;
   WriteMsg->iotd_Req.io_Command = ETD_WRITE;

   if (OpenDevice(TD_NAME, Unit, (struct IORequest *) ReadMsg, 0L))
      {
         puts("Couldn't open trackdisk.device for reading");
         CleanUp();
         exit (1);
      }

   if (OpenDevice(TD_NAME, Unit, (struct IORequest *) WriteMsg, 0L))
      {
         puts("Couldn't open trackdisk.device for writing");
         CleanUp();
         exit (1);
      }

   if (!(DataBlock = AllocMem((ULONG) TRACK_SIZE + (ULONG) LABEL_LENGTH, MEMF_PUBLIC)))
      {
         puts("Couldn't allocate trackdisk memory buffer");
         CleanUp();
         exit (1);
      }

   if (!(NewBlock = AllocMem((ULONG) TRACK_SIZE + (ULONG) LABEL_LENGTH, MEMF_PUBLIC)))
      {
         puts("Couldn't allocate trackdisk memory buffer");
         CleanUp();
         exit (1);
      }

   if (!(TrackdiskBlock = AllocMem((ULONG) TRACK_SIZE + (ULONG) LABEL_LENGTH, MEMF_CHIP)))
      {
         puts("Couldn't allocate trackdisk memory buffer");
         CleanUp();
         exit (1);
      }

   if (!(iotd_SecLabel = AllocMem((ULONG) LABEL_LENGTH, MEMF_CHIP)))
      {
         puts("Couldn't allocate trackdisk extended memory buffer");
         CleanUp();
         exit (1);
      }

   if (!(text_buf = AllocMem((ULONG) (N + F - 1), MEMF_PUBLIC)))
      {
         puts("Out of memory");
         CleanUp();
         exit (1);
      }

   if (!(lson = AllocMem((ULONG) (N + 1) * (ULONG) (sizeof(short)), MEMF_PUBLIC)))
      {
         puts("Out of memory");
         CleanUp();
         exit (1);
      }

   if (!(rson = AllocMem((ULONG) (N + 257) * (ULONG) (sizeof(short)), MEMF_PUBLIC)))
      {
         puts("Out of memory");
         CleanUp();
         exit (1);
      }

   if (!(dad = AllocMem((ULONG) (N + 1) * (ULONG) (sizeof(short)), MEMF_PUBLIC)))
      {
         puts("Out of memory");
         CleanUp();
         exit (1);
      }
}


Archive(USHORT Track, ULONG DataSize)
{
   register UBYTE   *OutBuffer;
   UBYTE             CanSetBuffer;
   ULONG             CurrentPosition;
   ULONG             AfterEncodeLength;
   ULONG             TotalEncodeLength;
   register BPTR     outfp;

   /* Allocate a file buffer if we can */
   if (OutBuffer = AllocMem((ULONG) OUT_BUFFER_SIZE, MEMF_PUBLIC))
      {
         CanSetBuffer = 1;
      }
   else
      {
         CanSetBuffer = 0;
      }

   CurrentPosition = GetFileLength(FileName);

   /* Lhwarp to a file; this way, if the output is larger than the input */
   /* we won't crash the machine */
   if (!(outfile = fopen(FileName, "a+b")))
      {
         printf("Couldn't open temporary file for output\n");

         if (CanSetBuffer)
            {
               FreeMem(OutBuffer, (ULONG) OUT_BUFFER_SIZE);
            }

         CleanUp();
         exit (1);
      }

   if (CanSetBuffer)
      {
         setvbuf(outfile, OutBuffer, _IOFBF, (long) OUT_BUFFER_SIZE);
      }

   /* Write out a dummy file compression header */
   fwrite(&CHeader110, sizeof(struct CompHeader110), 1, outfile);

   GlobalTrack  = Track;
   GlobalSector = 0;

   Encode((ULONG) (DataSize));

   fclose(outfile);

   if (CanSetBuffer)
      {
         FreeMem(OutBuffer, (ULONG) OUT_BUFFER_SIZE);
      }

   AfterEncodeLength = GetFileLength(FileName);
   TotalEncodeLength = AfterEncodeLength - CurrentPosition - sizeof(struct CompHeader110);

   if (!(outfp = (BPTR) Open(FileName, (long) MODE_READWRITE)))
      {
         printf("Couldn't open temporary file for output\n\n");

         if (CanSetBuffer)
            {
               FreeMem(OutBuffer, (ULONG) OUT_BUFFER_SIZE);
            }

         CleanUp();
         exit (1);
      }

   /* Seek back to the CompHeader section */
   Seek((BPTR) outfp, (long) CurrentPosition, (long) OFFSET_BEGINNING);

   CHeader110.CompressedLength = TotalEncodeLength;
   CHeader110.FullLength       = DataSize;
   CHeader110.TrackNumber      = (UBYTE) Track;
   CHeader110.CompressionType  = COMPTYPE_LHARC;
   CHeader110.DataType         = DATATYPE_NORMAL;

   /* Checksum the block */
   CHeader110.CRC = (ULONG) DoCRC(NewBlock, (ULONG) CHeader110.FullLength);

   /* Write it to disk */
   Write((BPTR) outfp, &CHeader110, (long) sizeof(struct CompHeader110));

   /* Close the file */
   Close((BPTR) outfp);
}


WriteData()
{
   ULONG           j;
   ULONG           Track;
   UBYTE          *TextMemory;
   UBYTE          *TextCompMemory;
   UBYTE          *ExtendedDataBlock;
   ULONG           CheckSum;
   ULONG           TracksWritten = 0L;
   long            Error;
   long            z;
   register UBYTE *WriteLocation;
   register UBYTE *TrackdiskBlockLoc;

   printf("\nLHWARP %d.%d%d - Amiga disk tracker - Written by Jonathan Forbes @ 1:250/642\n", (UBYTE) MAJOR_VERSION, (UBYTE) MINOR_VERSION, (UBYTE) DECIMAL_VERSION);
   puts("Copyright © Xenomiga Technology, 1990.\n");

   printf("Insert destination disk in drive %ld and press RETURN when ready", Unit);
   fflush(stdout);

   while (getc(stdin) != 10)
      {
      }

   puts("");

   printf("Writing to disk in drive %ld from file %s\n", Unit, FileName);

   /* Set up the write request */
   WriteMsg->iotd_Req.io_Command = ETD_FORMAT;

   if (!(infile = fopen(FileName, "r")))
      {
         printf("Cannot open %s for input\n", FileName);
         CleanUp();
         exit (1);
      }

   /* Read the Warp header */
   fread(&WHeader, sizeof(struct WarpHeader), 1, infile);

   /* Get the version number */
   Version = (WHeader.MajorVersion * 100) + (WHeader.MinorVersion * 10) + WHeader.DecimalVersion;

   /* What version of Lhwarp was used to compress the disk? */
   if (WHeader.MajorVersion == 1 && WHeader.MinorVersion == 0 && WHeader.DecimalVersion == 1)
      {
         printf("Disk was compressed using Lhwarp 1.01\n\n");
      }
   else
      {
         printf("Disk was compressed using Lhwarp %d.%d%d\n\n", WHeader.MajorVersion, WHeader.MinorVersion, WHeader.DecimalVersion);
      }

   if ((WHeader.MajorVersion > MAJOR_VERSION) ||
       (WHeader.MajorVersion == MAJOR_VERSION && WHeader.MinorVersion > MINOR_VERSION))
      {
         printf("Sorry, this file was Lhwarp'd with Lhwarp V%d.%d%d\n",
            WHeader.MajorVersion, WHeader.MinorVersion, WHeader.DecimalVersion);

         printf("You are using Lhwarp V%d.%d%d\n",
            MAJOR_VERSION, MINOR_VERSION, DECIMAL_VERSION);

         CleanUp();
         exit(1);
      }

   if (WHeader.TextLength)
      {
         if (!(TextMemory = AllocMem((ULONG) WHeader.TextLength+1, MEMF_PUBLIC)))
            {
               printf("Insufficient memory (%ld bytes required) to read compressed text\n", (ULONG) WHeader.TextLength+1);
               CleanUp();
               exit(1);
            }

         if (!(TextCompMemory = AllocMem((ULONG) WHeader.CompressedTextLength, MEMF_PUBLIC)))
            {
               printf("Insufficient memory (%ld bytes required) to decompress text\n", (ULONG) WHeader.CompressedTextLength);
               FreeMem(TextMemory, (ULONG) WHeader.TextLength+1);
               CleanUp();
               exit(1);
            }

         fread(TextCompMemory, (ULONG) WHeader.CompressedTextLength, 1, infile);

         FilePosition = (UBYTE *) TextCompMemory;

         /* Arbitrary number; just make it greater than TextLength */
         EndOfFilePosition = (UBYTE *) ( ((UBYTE *) TextCompMemory) + (WHeader.CompressedTextLength));

         PutFilePosition = (UBYTE *) TextMemory;

         /* Don't display '.' and 'o' when decompressing text */
         GlobalTrack = 0;

         Decode((ULONG) WHeader.TextLength);

         /* Make sure the text has a null terminator */
         TextMemory[WHeader.TextLength] = 0;

         puts("\n----\n");
         puts(TextMemory);
         puts("----\n");

         FreeMem(TextMemory, (ULONG) WHeader.TextLength+1);
         FreeMem(TextCompMemory, (ULONG) WHeader.CompressedTextLength);
      }

   /* Start writing tracks */
   for (;;)
      {
         /* If there are no more tracks to be written, exit */
         if (ReadCHeader())
            {
               break;
            }

         FilePosition = (UBYTE *) DataBlock;
         PutFilePosition = (UBYTE *) NewBlock;

         if (Version == 101)
            {
               EndOfFilePosition = (UBYTE *) ( ((UBYTE *) DataBlock) + CHeader101.CompressedLength);
               fread(DataBlock, (long) CHeader101.CompressedLength, 1, infile);

               /* Version 1.01 didn't support individual sectors */
               GlobalTrack = 0;
               Decode((ULONG) CHeader101.FullLength);

               Track = (ULONG) CHeader101.TrackNumber;

               /* Copy data into CHIP RAM */
               CopyMem(NewBlock, TrackdiskBlock, (ULONG) TRACK_SIZE + (ULONG) LABEL_LENGTH);

               ExtendedDataBlock = (UBYTE *) ( ((UBYTE *) NewBlock) + ((ULONG) TRACK_SIZE));
               CopyMem(ExtendedDataBlock, (UBYTE *) &iotd_SecLabel[0], (ULONG) LABEL_LENGTH);

               printf("Writing track %2ld\r", (ULONG) (Track));
               fflush(stdout);
            }
         else if ((Version == 110 || Version == 111) && CHeader110.DataType == DATATYPE_NORMAL_BLANK)
            {
               Track = (ULONG) CHeader110.TrackNumber;
               ClearMem(TrackdiskBlock, (ULONG) TRACK_SIZE);
               ClearMem(&iotd_SecLabel[0], (ULONG) LABEL_LENGTH);

               WriteMsg->iotd_Req.io_Offset = (long) (TRACK_SIZE * Track);
               WriteMsg->iotd_Req.io_Data   = (APTR) TrackdiskBlock;
               WriteMsg->iotd_Req.io_Length = (ULONG) TRACK_SIZE;
               WriteMsg->iotd_SecLabel      = (ULONG) &iotd_SecLabel[0];
               WriteMsg->iotd_Count         = 0xFFFFFFFF;

               printf("Writing track %2ld: ______________________\r", (ULONG) (Track));
               fflush(stdout);

               if (Error = DoIO((struct IORequest *) WriteMsg))
                  {
                     fclose(infile);
                     printf("Fatal error %ld writing to disk in unit %ld -terminating\n\n", Error, Unit);
                     MotorOff();
                     CleanUp();
                     exit (1);
                  }
            }
         else if (Version == 102 || Version == 103)
            {
               EndOfFilePosition = (UBYTE *) ( ((UBYTE *) DataBlock) + CHeader102.CompressedLength);
               fread(DataBlock, (long) CHeader102.CompressedLength, 1, infile);

               /* Versions 1.02 and 1.03 didn't support individual sectors */
               GlobalTrack = 0;
               Decode((ULONG) CHeader102.FullLength);

               Track = (ULONG) CHeader102.TrackNumber;

               CheckSum = (ULONG) DoSum(NewBlock, (ULONG) CHeader102.FullLength);

               if (CheckSum != CHeader102.CheckSum)
                  {
                     printf("Warning: Track %ld fails checksum\n", Track);
                  }

               /* Copy data into CHIP RAM */
               CopyMem(NewBlock, TrackdiskBlock, (ULONG) TRACK_SIZE + (ULONG) LABEL_LENGTH);

               ExtendedDataBlock = (UBYTE *) ( ((UBYTE *) NewBlock) + ((ULONG) TRACK_SIZE));
               CopyMem(ExtendedDataBlock, (UBYTE *) &iotd_SecLabel[0], (ULONG) LABEL_LENGTH);

               printf("Writing track %2ld", (ULONG) (Track));
               fflush(stdout);
            }
         else if (Version == 110 || Version == 111)
            {
               Track = (ULONG) CHeader110.TrackNumber;

               if (Track)
                  {
                     printf("Writing track %2ld: ", (ULONG) (Track));

                     /* Display the sectors which are to be decompressed */
                     for (z = 0; z < (NUMSECS * NUM_HEADS); z++)
                        {
                           putc((UBYTE) Bit(&CHeader110.SectorMask, (UBYTE) z) ? '.' : '_', stdout);
                        }

                     printf("[22D");
                     fflush(stdout);
                  }

               EndOfFilePosition = (UBYTE *) ( ((UBYTE *) DataBlock) + CHeader110.CompressedLength);
               fread(DataBlock, (long) CHeader110.CompressedLength, 1, infile);

               /* Tell Decode() which sectors are to be decompressed */
               /* so it can display '.' and 'o' properly */
               GlobalTrack     = CHeader110.TrackNumber;
               GlobalSectorMap = CHeader110.SectorMask;
               GlobalSector    = 0;
               Decode((ULONG) CHeader110.FullLength);

               if (DoCRC(NewBlock, (ULONG) CHeader110.FullLength) != CHeader110.CRC)
                  {
                     printf("Warning: Track %ld fails CRC\n", Track);
                  }

               WriteLocation     = (UBYTE *) NewBlock;
               TrackdiskBlockLoc = (UBYTE *) TrackdiskBlock;

               if (!Track)
                  {
                     puts("Disk contains the following bootblock:\n");
                     DisplayBootBlock(NewBlock);
                  }

               for (j=0; j < (NUMSECS * NUM_HEADS); j++)
                  {
                     if (Bit(&CHeader110.SectorMask, (UBYTE) j))
                        {
                           CopyMem(WriteLocation, TrackdiskBlockLoc, (ULONG) (TD_SECTOR));
                           CopyMem((long) WriteLocation + (long) TD_SECTOR, &iotd_SecLabel[j * 16], (long) 16);
                           WriteLocation = WriteLocation + (TD_SECTOR + 16);
                           TrackdiskBlockLoc = TrackdiskBlockLoc + (long) TD_SECTOR;
                         }
                     else
                        {
                           ClearMem(TrackdiskBlockLoc, (ULONG) (TD_SECTOR));
                           ClearMem(&iotd_SecLabel[j * 16], (long) 16);
                           TrackdiskBlockLoc = TrackdiskBlockLoc + (long) TD_SECTOR;
                        }
                  }

               WriteMsg->iotd_Req.io_Offset = (long) (TRACK_SIZE * Track);
               WriteMsg->iotd_Req.io_Data   = (APTR) TrackdiskBlock;
               WriteMsg->iotd_Req.io_Length = (ULONG) TRACK_SIZE;
               WriteMsg->iotd_SecLabel      = (ULONG) &iotd_SecLabel[0];
               WriteMsg->iotd_Count         = 0xFFFFFFFF;

               if (Error = DoIO((struct IORequest *) WriteMsg))
                  {
                     fclose(infile);
                     printf("Fatal error %ld writing to disk in unit %ld -terminating\n\n", Error, Unit);
                     MotorOff();
                     CleanUp();
                     exit (1);
                  }
            }

         if (Version == 101 || Version == 102 || Version == 103)
            {
               if (!Track)
                  {
                     puts("Disk contains the following bootblock:\n");
                     DisplayBootBlock(NewBlock);
                  }

               WriteMsg->iotd_Req.io_Offset = (long) TRACK_SIZE * Track;
               WriteMsg->iotd_Req.io_Data   = (APTR) TrackdiskBlock;
               WriteMsg->iotd_Req.io_Length = (long) TRACK_SIZE;
               WriteMsg->iotd_SecLabel      = (ULONG) &iotd_SecLabel[0];
               WriteMsg->iotd_Count         = 0xFFFFFFFF;

               printf("Writing track %2ld", (ULONG) (Track));
               fflush(stdout);

               if (DoIO((struct IORequest *) WriteMsg))
                  {
                     fclose(infile);
                     printf("Fatal error writing to disk in unit %ld -terminating\n\n", Unit);
                     MotorOff();
                     CleanUp();
                     exit (1);
                  }
            }

         printf("\r");
         fflush(stdout);

         TracksWritten++;
      }

   /* Push all trackdisk buffers onto the disk */
   WriteMsg->iotd_Req.io_Length  = 0L;
   WriteMsg->iotd_Req.io_Command = CMD_UPDATE;
   DoIO((struct IORequest *) WriteMsg);

   MotorOff();

   WriteMsg->iotd_Req.io_Length  = 0L;
   WriteMsg->iotd_Req.io_Command = CMD_FLUSH;
   DoIO((struct IORequest *) WriteMsg);

   fclose(infile);

   printf("\n\nFinished writing %ld tracks\n\n", TracksWritten);
   printf("Please remove and re-insert destination disk, to use\n\n");

   CleanUp();
}


MotorOff()
{
   WriteMsg->iotd_Req.io_Length  = 0L;
   WriteMsg->iotd_Req.io_Command = TD_MOTOR;
   DoIO((struct IORequest *) WriteMsg);
}


DisplayBootBlock(UBYTE *Data)
{
   char           Line[76];
   UBYTE          i;
   register UBYTE j;
   register UBYTE c;

   /* End of the line to be displayed */
   Line[73] = 0;

   for (i=0; i<14; i++)
      {
         for (j=0; j<73; j++)
            {
               c = *((UBYTE *) ( ((UBYTE *) Data) + (ULONG) (i * 64) + j));

               if (c < 32 || c > 126)
                  {
                     c = '.';
                  }

               Line[j] = c;
            }

         puts(Line);
      }

   puts("");
}


ReadSector(ULONG Sector)
{
   ULONG Error;

   ReadMsg->iotd_Req.io_Command = CMD_READ;
   ReadMsg->iotd_Req.io_Offset  = (long) BLOCK_SIZE * Sector;
   ReadMsg->iotd_Req.io_Data    = (APTR) TrackdiskBlock;
   ReadMsg->iotd_Req.io_Length  = (long) BLOCK_SIZE;

   if (Error = DoIO((struct IORequest *) ReadMsg))
      {
         printf("Error %d reading from trackdisk.device\n", Error);
         CleanUp();
         exit (1);
      }
}


GetBitMap()
{
   int   BitMapSector;
   ULONG i;
   ULONG j;
   ULONG Mask;
   ULONG BlockNumber;
   ULONG TrackNumber;
   ULONG SectorNumber;

   for (i=1; i<NUM_CYLS; i++)
      {
         for (j=0; j<(NUMSECS * NUM_HEADS); j++)
            {
               BitMapAllocation[i][j] = 0;
            }
      }

   /* Track zero and the root track must always be sent */
   BitMapAllocation[0][0] = 1;
   BitMapAllocation[0][1] = 1;

   BitMapAllocation[(ROOT_BLOCK / 22)][0] = 1;

   ReadSector((ULONG) ROOT_BLOCK);
   BitMapSector = (int) *((ULONG *) TrackdiskBlock + BITMAP_INDEX);

   ReadSector((long) BitMapSector);

   for (i = 1; i <= NUM_LONGS; i++)
      {
         Mask = (ULONG) TrackdiskBlock[i];

         for (j = 0; j < 32; j++)
            {
               if (i < NUM_LONGS || j < 30)
                  {
                     if (~Mask & 1)
                        {
                           BlockNumber  = ((i-1) * 32) + j + 2;
                           TrackNumber  = (BlockNumber / 22);
                           SectorNumber = (BlockNumber % 22);

                           BitMapAllocation[TrackNumber][SectorNumber] = 1;
                        }

                     Mask >>= 1;
                  }
            }
      }
}


ReadData()
{
   ULONG  AfterEncodeLength;
   ULONG  TotalEncodeLength;
   ULONG  TextLength;
   ULONG  i;
   ULONG  j;
   ULONG  SecsRead;
   ULONG  FileLength;
   ULONG  OriginalLength = 0L;
   BPTR   OutputFile;
   BPTR   outfp;
   ULONG  Error;
   ULONG  DataSize;
   FILE  *TextInFile;
   UBYTE *TextMemory;
   UBYTE *ReadLocation;

   printf("\nLHWARP %d.%d%d - Amiga disk tracker - Written by Jonathan Forbes @ 1:250/642\n", (UBYTE) MAJOR_VERSION, (UBYTE) MINOR_VERSION, (UBYTE) DECIMAL_VERSION);
   puts("Copyright © Xenomiga Technology, 1990.\n");

   printf("Insert source disk in drive %ld and press RETURN when ready", Unit);
   fflush(stdout);

   while (getc(stdin) != 10)
      {
      }

   puts("");

   if (UseBitMap)
      {
         GetBitMap();
         printf("Using disk bitmap to skip unused sectors\n\n");
      }

   printf("Reading from disk in drive %ld to file %s\n", Unit, FileName);

   /* Set up the read request */
   ReadMsg->iotd_Req.io_Command = ETD_READ;
   ReadMsg->iotd_Count          = 0xFFFFFFFF;

   /* If we start at track zero, check the boot block */
   if (!StartTrack)
      {
         ReadMsg->iotd_Req.io_Offset = (long) 0L;
         ReadMsg->iotd_Req.io_Data   = (APTR) TrackdiskBlock;

         /* Only get the first two sectors */
         ReadMsg->iotd_Req.io_Length = (long) 1024L;

         if (Error = DoIO((struct IORequest *) ReadMsg))
            {
               printf("Error %d reading from trackdisk.device\n", Error);
               CleanUp();
               exit (1);
            }

         printf("\nBoot block of floppy disk in drive %d:\n\n", Unit);
         DisplayBootBlock(TrackdiskBlock);
      }

   if (AppendTextFile)
      {
         TextLength = GetFileLength(TextFileName);

         if (!TextLength)
            {
               puts("Text file missing or zero length\n");
               CleanUp();
               exit (1);
            }

         WHeader.TextLength = TextLength;
      }
   else
      {
         WHeader.TextLength = 0L;
      }

   /* Build the Warp header */
   WHeader.StartTrack = StartTrack;
   WHeader.EndTrack   = EndTrack;

   /* Version 1.11 */
   WHeader.MajorVersion = (UBYTE) MAJOR_VERSION;
   WHeader.MinorVersion = (UBYTE) MINOR_VERSION;
   WHeader.DecimalVersion = (UBYTE) DECIMAL_VERSION;

   /* Write out the Warp header */
   if (!(OutputFile = Open(FileName, MODE_NEWFILE)))
      {
         printf("Couldn't open output file\n");
         CleanUp();
         exit (1);
      }

   Write(OutputFile, &WHeader, (long) sizeof(struct WarpHeader));
   Close(OutputFile);

   if (AppendTextFile)
      {
         printf("Compressing text ... ");
         fflush(stdout);

         if (!(TextInFile = fopen(TextFileName, "r")))
            {
               printf("Can't open %s for input\n", TextInFile);
               CleanUp();
               exit(1);
            }

         if (!(TextMemory = AllocMem((ULONG) TextLength, MEMF_PUBLIC)))
            {
               puts("Couldn't allocate text memory buffer");
               CleanUp();
               exit(1);
            }

         fread(TextMemory, (ULONG) TextLength, 1, TextInFile);
         fclose(TextInFile);

         if (!(outfile = fopen(FileName, "a")))
            {
               printf("Can't open %s for append\n", FileName);
               FreeMem(TextMemory, (ULONG) TextLength);
               CleanUp();
               exit(1);
            }

         FilePosition = (UBYTE *) TextMemory;
         EndOfFilePosition = (UBYTE *) ( ((UBYTE *) TextMemory) + (TextLength));

         GlobalTrack = -1;

         Encode((ULONG) (TextLength));
         fclose(outfile);

         FreeMem(TextMemory, (ULONG) TextLength);

         AfterEncodeLength = GetFileLength(FileName);
         TotalEncodeLength = AfterEncodeLength - (ULONG) sizeof(struct WarpHeader);

         WHeader.CompressedTextLength = TotalEncodeLength;

         if (!(outfile = fopen(FileName, "r+w")))
            {
               printf("Can't open %s for append\n", FileName);
               CleanUp();
               exit(1);
            }

         fwrite(&WHeader, sizeof(struct WarpHeader), 1, outfile);
         fclose(outfile);

         printf("done\n\n");
      }

   if (StartTrack)
      {
         puts("");
      }

   /* Start reading tracks */
   for (i = StartTrack; i <= EndTrack; i++)
      {
         for (j = 0; j < NUMSECS * NUM_HEADS; j++)
            {
               if (BitMapAllocation[i][j])
                  {
                     break;
                  }
            }

         if (UseBitMap && j >= (NUMSECS * NUM_HEADS))
            {
               printf("Reading track %2ld: ______________________\r", (ULONG) i);
               fflush(stdout);

               CHeader110.CompressedLength = 0L;
               CHeader110.FullLength       = 0L;
               CHeader110.TrackNumber      = (UBYTE) i;
               CHeader110.CompressionType  = COMPTYPE_NONE;
               CHeader110.DataType         = DATATYPE_NORMAL_BLANK;
               CHeader110.CRC              = 0L;
               CHeader110.SectorMask       = 0L;

               /* Write it to disk */
               if (!(outfp = (BPTR) Open(FileName, (long) MODE_READWRITE)))
                  {
                     printf("Couldn't open %s for output\n\n", FileName);
                     CleanUp();
                     exit(1);
                   }

               Seek((BPTR) outfp, (long) 0L, (long) OFFSET_END);
               Write((BPTR) outfp, &CHeader110, (long) sizeof(struct CompHeader110));
               Close((BPTR) outfp);
            }
         else
            {
               DataSize          = 0L;
               ReadLocation      = (UBYTE *) NewBlock;

               printf("Reading track %2ld:                          [25D", (ULONG) (i));
               fflush(stdout);

               SecsRead = 0;

               for (j=0; j<(NUMSECS * NUM_HEADS); j++)
                  {
                     if ((!UseBitMap) || BitMapAllocation[i][j])
                        {
                           putc('.', stdout);

                           ReadMsg->iotd_Req.io_Offset = (long) (TRACK_SIZE * i) + (j * TD_SECTOR);
                           ReadMsg->iotd_Req.io_Data   = (APTR) TrackdiskBlock;
                           ReadMsg->iotd_Req.io_Length = (long) TD_SECTOR;
                           ReadMsg->iotd_SecLabel      = (UBYTE *) TrackdiskBlock + TD_SECTOR;
                           ReadMsg->iotd_Count         = 0xFFFFFFFF;

                           DoIO((struct IORequest *) ReadMsg);

                           CopyMem(TrackdiskBlock, ReadLocation, (ULONG) (TD_SECTOR + 16));
                           ReadLocation = ReadLocation + (TD_SECTOR + 16);

                           SecsRead++;
                           DataSize = DataSize + (TD_SECTOR + 16);

                           SetBit(&CHeader110.SectorMask, (UBYTE) j, (UBYTE) 1);
                        }
                     else
                        {
                           putc('_', stdout);

                           SetBit(&CHeader110.SectorMask, (UBYTE) j, (UBYTE) 0);
                        }
                  }

               printf("[22D");
               fflush(stdout);

               FilePosition = (UBYTE *) NewBlock;
               EndOfFilePosition = (UBYTE *) ( ((UBYTE *) NewBlock) + (DataSize));

               Archive(i, DataSize);

               putc('\r', stdout);
               fflush(stdout);

               OriginalLength += (DataSize);
            }
      }

   ReadMsg->iotd_Req.io_Length  = 0;
   ReadMsg->iotd_Req.io_Command = TD_MOTOR;
   DoIO((struct IORequest *) ReadMsg);

   FileLength = GetFileLength(FileName);

   printf("\n\nFinished reading tracks %d to %d (%d total)\n", WHeader.StartTrack, WHeader.EndTrack, WHeader.EndTrack - WHeader.StartTrack + 1);
   printf(" Input file: %ld bytes\n", OriginalLength);
   printf("Output file: %ld bytes\n\n", FileLength);

   CleanUp();
}


CleanUp()
{
   if (ReadMsg)
      {
         CloseDevice((struct IORequest *) ReadMsg);
         DeleteExtIO((struct IORequest *) ReadMsg, (long) sizeof(struct IOExtTD));
      }

   if (WriteMsg)
      {
         CloseDevice((struct IORequest *) WriteMsg);
         DeleteExtIO((struct IORequest *) WriteMsg, (long) sizeof(struct IOExtTD));
      }

   if (ReadPort)
      {
         DeletePort(ReadPort);
      }

   if (WritePort)
      {
         DeletePort(WritePort);
      }

   if (DataBlock)
      {
         FreeMem(DataBlock, (ULONG) TRACK_SIZE + (ULONG) LABEL_LENGTH);
      }

   if (NewBlock)
      {
         FreeMem(NewBlock, (ULONG) TRACK_SIZE + (ULONG) LABEL_LENGTH);
      }

   if (TrackdiskBlock)
      {
         FreeMem(TrackdiskBlock, (ULONG) TRACK_SIZE + (ULONG) LABEL_LENGTH);
      }

   if (iotd_SecLabel)
      {
         FreeMem(iotd_SecLabel, (ULONG) LABEL_LENGTH);
      }

   if (text_buf)
      {
         FreeMem(text_buf, (ULONG) (N + F - 1));
      }

   if (lson)
      {
         FreeMem(lson, (ULONG) (N + 1) * (ULONG) (sizeof(short)));
      }

   if (rson)
      {
         FreeMem(rson, (ULONG) (N + 257) * (ULONG) (sizeof(short)));
      }

   if (dad)
      {
         FreeMem(dad, (ULONG) (N + 1) * (ULONG) (sizeof(short)));
      }
}


Title()
{
   printf("\nLHWARP %d.%d%d - Amiga disk tracker - Written by Jonathan Forbes @ 1:250/642\n", (UBYTE) MAJOR_VERSION, (UBYTE) MINOR_VERSION, (UBYTE) DECIMAL_VERSION);
   puts("Copyright © Xenomiga Technology, 1990.\n");

   puts("Usage: LHWARP <Command> <Unit> <Filename> <StartTrack> <EndTrack> <TextFile>\n");

   puts("<Command>    Read, NoMap, or Write");
   puts("<Unit>       Drive number (0 for internal, 1 ... 3 for external)");
   puts("<Filename>   Output or input filename");
   puts("<StartTrack> Track number (0 ... 79) [valid only in read mode]");
   puts("<EndTrack>   Track number (0 ... 79) [valid only in read mode]");
   puts("<TextFile>   Attach text in <TextFile> to output file (optional)\n");
}


long __regargs GetFileLength(char *FileToOpen)
{
   FILE  *fp;
   long  Length;

   if (!(fp = fopen(FileToOpen, "r")))
      {
         return (NULL);
      }

   fseek(fp, 0L, SEEK_END);
   Length = ftell(fp);
   fclose(fp);

   return (Length);
}


/* If the user presses ^C */
int CXBRK()
{
   if (WriteMsg)
      {
         WriteMsg->iotd_Req.io_Length  = 0L;
         WriteMsg->iotd_Req.io_Command = TD_MOTOR;
         DoIO((struct IORequest *) WriteMsg);
      }

   if (ReadMsg)
      {
         ReadMsg->iotd_Req.io_Length  = 0L;
         ReadMsg->iotd_Req.io_Command = TD_MOTOR;
         DoIO((struct IORequest *) ReadMsg);
      }

   CleanUp();
   return (-1);
}


ULONG __regargs DoSum(UBYTE *Block, ULONG Length)
{
   register ULONG Sum = 0;
   register ULONG i;

   for (i = 0; i < Length; i++)
      {
         Sum = Sum + Block[i];
      }

   return (Sum);
}


ULONG __regargs DoCRC(UBYTE *Block, ULONG Length)
{
   register ULONG CRC = 0;
   register ULONG i;

   for (i = 0; i < Length; i++)
      {
         CRC = UPDC32(Block[i], CRC);
      }

   return (CRC);
}


BuildFileName(char **argv)
{
   /* Get the file name */
   strcpy(FileName, argv[3]);
   strupr(FileName);

   /* Append .LHW if necessary */
   if (strcmp(FileName, ".LHW"))
      {
         if (strlen(FileName) > 4)
            {
               if (strcmp(&FileName[strlen(FileName)-4], ".LHW"))
                  {
                     strcat(FileName, ".LHW");
                  }
            }
         else
            {
               strcat(FileName, ".LHW");
            }
      }
}


Caution()
{
   UBYTE c;

   printf("\nLHWARP %d.%d%d - Amiga disk tracker - Written by Jonathan Forbes @ 1:250/642\n", (UBYTE) MAJOR_VERSION, (UBYTE) MINOR_VERSION, (UBYTE) DECIMAL_VERSION);
   puts("Copyright © Xenomiga Technology, 1990.\n");

   printf("Caution: File %s already exists.  Remove it? (y/n) ", FileName);
   fflush(stdout);

   while (toupper(c = getc(stdin)) != 'Y' && toupper(c) != 'N')
      {
      }

   if (toupper(c) == 'N')
      {
         puts("\n");
         CleanUp();
         exit(1);
      }

   /* Get rid of the 'return' character */
   getc(stdin);
}


DoReadInit()
{
   if (!access(FileName, 0))
      {
         Caution();

         /* Remove the file if it was already there */
         remove(FileName);

         Command = WARP_READ;
      }
}


ClearMem(register UBYTE *source, register ULONG size)
{
   do
      {
         *source = 0;
         source++;
         size--;
      } while (size);
}


Bit(register UBYTE *StartByte, register UBYTE Position)
{
   register UBYTE WhichByte;
   register UBYTE WhichBit;

   WhichByte = Position / 8;
   WhichBit  = Position % 8;

   return (StartByte[WhichByte] & (1 << WhichBit));
}


SetBit(register UBYTE *StartByte, register UBYTE Position, register UBYTE Value)
{
   register UBYTE WhichByte;
   register UBYTE WhichBit;

   WhichByte = Position / 8;
   WhichBit  = Position % 8;

   StartByte[WhichByte] = Value ?
      (StartByte[WhichByte] | (1 << WhichBit)) : (StartByte[WhichByte] & (~(1 << WhichBit)));
}


UBYTE ReadCHeader()
{
   if (Version == 101)
      {
         if (!(fread(&CHeader101, sizeof(struct CompHeader101), 1, infile)))
            {
               return (1);
            }
      }
   else if (Version == 102 || Version == 103)
      {
         if (!(fread(&CHeader102, sizeof(struct CompHeader102), 1, infile)))
            {
               return (1);
            }
      }
   else
      {
         if (!(fread(&CHeader110, sizeof(struct CompHeader110), 1, infile)))
            {
               return (1);
            }
      }

   return (0);
}
