/*********************************************************************
   ATR/XFD File handling library
   (C) Copyright 1997 Ken Siders    

   This file can be used in any freeware or public domain software
   as long as credit is given.


History

00.000  6/16/97    Initial version
00.001  6/24/97    Fixed AtariFileSize, Added EofAtariFile and
                   ExtractAtariFile functions
00.002  6/26/97    Added SortDirectory Function
00.003  7/14/97    Fixed Double density to treat first 3 sectors as  
                   Single Density.
00.004  7/16/97    Added CreateBootAtr
00.005  8/22/97    Added ExtractExeFromBootAtr
00.006  9/04/97    Fix signature check

*********************************************************************

To do:

1 Clean up warnings + make more portable
2 Allow opening write-protected ATRs
3 Allow more than one reference to an ATR file to be opened so more 
   than one atari file can be opened at once. (Keep a count)
4 More specific error returns
5 Implement XFD handling
6 Create documentation
7 Optimize if necessary
8 Implement DCM images (maybe)


*********************************************************************/

#define wide 1


/* compile with /Zp option */
/* 20070518 bkw: or -fpack-struct on gcc */


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include "atr.h"
#include "atdos.h"
#include "kboot.h"

AtrHeader hdr;

int lastAtariError = 0;

static unsigned char sectorBuffer[256];
static int CompareName( const void *a, const void *b );
static int verbose = 0;

/* 20070518 bkw: min() isn't standard on Linux. If you're porting to
	some other UNIX and get errors about min() already being defined,
	just comment this out. */
static int min(int a, int b) {
	return ((a < b) ? a : b);
}

/* 20070518 bkw: As written, the DOS sources aren't portable to
	big-endian architectures. To fix this, EndianFix() will swap
	the high and low bytes of startSector and sectorCount in a
	dir entry on a big-endian arch (and leave them alone on a little-
	endian arch). Must be called just after reading, and just before
	writing.

	The Intel x86 arch and the Atari's 6502 both happen to be
	little-endian, which is why Ken could ignore the endianness
	issue in the original MS-DOS sources.
 */
#define LITTLE_END 0
#define BIG_END 1

static int InitEndian() {
	/* code based on Wikipedia "Endianness" entry */
	union {
		short s;
		char c[sizeof(short)];
	} un;

	un.s = 0x0102;

	if(sizeof(short) == 2) {
		if(un.c[0] == 1 && un.c[1] == 2)
			return BIG_END;
		else if(un.c[0] == 2 && un.c[1] == 1)
			return LITTLE_END;
	}

	printf("WARNING: Can't determine endianness of this platform!\n");
	printf("Assuming little-endian, hoping for the best. Expect trouble.\n");
	return LITTLE_END;
}

static void EndianFix(AtariDosDirEntryPtr entry) {
	static int endian = -1;
	unsigned short tmp;

	if(endian == -1)
		endian = InitEndian();

	/* don't do anything on little-endian platforms */
	if(endian == LITTLE_END)
		return;

	tmp = entry->startSector;
	entry->startSector = ((tmp & 0xff) << 8) | ((tmp & 0xff00) >> 8);

	tmp = entry->sectorCount;
	entry->sectorCount = ((tmp & 0xff) << 8) | ((tmp & 0xff00) >> 8);
}

/******************************************************************
SetVerbose - Sets verbose flag, returns last value of flag
******************************************************************/

int SetVerbose( int verb )
   {
   int last;

   last = verbose;
   verbose = verb;
   return(last);
   }



/******************************************************************
 OpenAtr - opens ATR file specified for read/write (if writeable).
           Returns Atr Pointer if successful, 0 if not
******************************************************************/

AtrFilePtr OpenAtr(char *file )
   {
   AtrFilePtr atr;
   int bytes;
   unsigned short signature;

   atr = malloc(sizeof(AtrFile));

   atr->dosType = DOS_ATARI; /* assume atari dos disk */


   atr->atrIn = fopen(file, "rb+");
   if ( !atr->atrIn )
      {
      free(atr);
      return 0;
      }
   bytes = fread(&hdr, 1, 16, atr->atrIn);
   if ( !bytes )
      {
      free(atr);
      return 0;
      }
   
   signature = hdr.idLow | hdr.idHigh << 8;
   atr->imageSize = 16L * (hdr.paraLow | hdr.paraHigh * 256L | hdr.paraHigher * 65536L);
   atr->secSize = hdr.secSizeLow | hdr.secSizeHigh << 8; 
   atr->crc = hdr.crc1 | hdr.crc2 * 256L | hdr.crc3 * 65536L | hdr.crc4 *256L * 65536L ;
   atr->sectorCount = atr->imageSize / atr->secSize;
   atr->flags = hdr.flags;
   atr->writeProtect = atr->flags&1;
   atr->authenticated = (atr->flags >> 1) & 1;
   
   if ( atr->sectorCount > 721 ) 
      atr->dosType = DOS_MYDOS;

   if ( signature == 0x296 )
      return atr;
   else
      {
      free(atr);
      return 0;
      }
   return atr;
   }


/******************************************************************
 CloseAtr - closes ATR file specified in an Atr Pointer from 
            and Atr Open.  Returns 0 if successful
******************************************************************/

int CloseAtr( AtrFilePtr atr )
   {
   if ( atr )
      return(fclose(atr->atrIn));
   else
      return 1;
    }


/******************************************************************
 ReadSector - Reads specified sector from the ATR file specified
              info buffer which must be big enough for the sector
              size of teh file.  Returns number of bytes read or
              0 if error.
******************************************************************/

int ReadSector(AtrFilePtr atr, unsigned short sector, char *buffer)
   {
   unsigned long pos;
   size_t bytes;
                                                             
   if ( !atr )                                               
      {                                                      
      lastAtariError = 12;                                   
      return 0;                                              
      }            
/* calculate offset into file */                                          
   if ( atr->secSize > 128 && sector > 3 )
      pos = (unsigned long)(sector-4) * atr->secSize + 400L;
   else
      pos = (unsigned long)(sector-1) * 128L + 16;

/* position file pointer at that offset */
   if ( fseek(atr->atrIn, pos, SEEK_SET) )
      {
      lastAtariError = 13;
      return 0;
      }

/* read the data */
   bytes = fread(buffer, 1, atr->secSize, atr->atrIn);
   if ( bytes & 127 )
      {
      lastAtariError = 14;
      return 0;
      }
   return bytes;
   }

/******************************************************************
 WriteSector - Writes specified sector from the ATR file specified
               from buffer specified.  Returns number of bytes
               written or 0 if error.  Image must be writeable.
******************************************************************/

int WriteSector(AtrFilePtr atr, unsigned short sector, char *buffer)
   {
   unsigned long pos;
   size_t bytes;

   if ( !atr )
      {
      lastAtariError = 12;
      return 0;
      }

/* calculate offset into file */
   if ( atr->secSize > 128 && sector > 3 )
      pos = (unsigned long)(sector-4) * atr->secSize + 400L;
   else
      pos = (unsigned long)(sector-1) * 128L + 16;

/* set file pointer to that position */
   if ( fseek(atr->atrIn, pos, SEEK_SET) )
      {
      lastAtariError = 13;
      return 0;
      }

/* sector # to high? */
   if ( pos + atr->secSize > atr->imageSize )
      {
      lastAtariError = 15;
      return 0;
      }

/* write the data */
   bytes = fwrite(buffer, 1, atr->secSize, atr->atrIn);
   if ( bytes & 127 )
      {
      lastAtariError = 14;
      return 0;
      }
   return bytes;
}

/******************************************************************
 CreateAtr - Creates an ATR file with parameters specified.  Sector
             size must be a multiple of 128 bytes.  Return 0 for
             success
******************************************************************/

int CreateAtr( char *file, unsigned short sectors, 
               unsigned short sectorSize )
   {
   FILE *fp;
   AtrHeader hdr = {0};
   unsigned long imageSize;
   int bytes;

/* sector size must be a multiple of 128 */
   if ( sectorSize & 127 )
      return 1;
/* determine the file size for the image */

   if ( sectorSize > 128 && sectors > 2)
      imageSize = (unsigned long)(sectorSize-3) * sectors + 384;
   else
      imageSize = (unsigned long)sectorSize * sectors;

/* create the file */
   fp = fopen(file, "wb");
   if ( !fp )
      return 1;

/* set up the ATR header */
   hdr.idHigh = 0x02;
   hdr.idLow = 0x96;
   hdr.paraLow = (imageSize >> 4) & 255;
   hdr.paraHigh = (imageSize >> 12) & 255; 
   hdr.paraHigher = imageSize >> 20;
   hdr.secSizeLow = sectorSize & 255;
   hdr.secSizeHigh = sectorSize >> 8;
   bytes = fwrite(&hdr, 1, 16, fp);
   if ( bytes != 16 )
      return 1;

/* seek to last position needed in file - 1 */
   if ( fseek(fp,(unsigned long)sectors * sectorSize - 1 , SEEK_SET) )
      return 1;
/* write one null byte */
   if ( fputc( 0, fp ) == EOF )
      return 1;
   if ( fclose(fp) )
      return 1;
   return 0;
   }
   
/******************************************************************
 GetAtrInfo - returns info for an open ATR image via pointers.
              non 0 returned is error. 
******************************************************************/

int GetAtrInfo( AtrFilePtr atr, unsigned short *sectorSize,
                unsigned short *sectorCount, byte  *protected)
   {
   if ( !atr )
      return 1;
/* duh */
   *sectorSize = atr->secSize;
   *sectorCount = atr->sectorCount;
   *protected = atr->writeProtect;
   return 0;
   }





/*-----------------------------------------------------------------*/
/*   ATARI 8-bit File IO routines                                  */
/*-----------------------------------------------------------------*/

/******************************************************************
 MakeFileName - Creates a filename.ext string from a zero padded
                raw fileName and extender.  Result is stored in
                string pointed to be result.  There is no return
                value.
******************************************************************/

void MakeFileName( char *result, char *fileName, char *extender )
   {
   int i;
   
   for(i=0; i<8; i++)
      {
      if (fileName[i] == ' ' || !fileName[i] )
         break;
      *(result++) = fileName[i];
      }      
   *(result++) = '.';
   for(i=0; i<3; i++)
      {
      if (extender[i] == ' ' || !extender[i] )
         break;
      *(result++) = extender[i];
      }      
   *(result++) = 0;
   }


/******************************************************************
 PatternMatch - Returns 1 if fileName+extender matches pattern in
                pattern. Wildcards are the standard '?' and '*'
                as supported by all Atari Dos's.  Returns 0 if
                it does not match.
******************************************************************/
int PatternMatch( char *pattern, char *fileName, char *extender)
   {
   int i=0;
   char file[13];

	/* 20070518 bkw: Special case: "*" and "*.*" always match anything */
	if(strcmp(pattern, "*.*") == 0 || strcmp(pattern, "*") == 0)
		return 1;

   MakeFileName(file, fileName, extender);

   while (*pattern && file[i] )
      {
      if ( !file[i] && *pattern )
         return 0;
      if ( file[i] && !*pattern )
         return 0;

      if ( *pattern == '*')
         {
         while ( file[i] && file[i] != '.')
            i++;
         while ( *pattern && *pattern != '.')
            pattern++;
         if ( file[i] == '.' && *pattern == '.' )
            {
            pattern++;
            i++;
            continue;
            }
         continue;
         }

      if ( *pattern == '?' && file[i] != '.' )
         {
         i++;
         pattern++;
         continue;
         }

      if ( toupper(*pattern) != file[i] )
         return 0;

      i++; 
      pattern++;
      }

   if ( !*pattern && !file[i] )
      return 1;
   else
      return 0;
   }


/******************************************************************
 AtariFindFirst - Finds first match for pattern and sets struct
                  with file information.  returns 0 for success,
                  -1 if not found, other for error.  This is 
                  similiar to _dosfindfist in the DOS world.
******************************************************************/

int AtariFindFirst( char *atrName, unsigned attrib,
    char *pattern, AtariFileInfoPtr fileInfo )
   {
   char buffer[256];
   AtrFilePtr atr;
   int i,j;
   AtariDosDirEntryPtr dirEntry;
   unsigned short sectorSize, sectorCount;
   byte protected;

/* open the ATR image */
   atr = OpenAtr(atrName);
   if ( atr == NULL )
      return 2;

/* Get some info about the ATR image and save */
   if ( GetAtrInfo( atr, &sectorSize, &sectorCount, &protected) )
      {
      free(atr);
      return 3;
      }

/* look for the file in the directory, if found initilize the fileInfo
   structure with data from the directory sector */

   for( i = firstDirSector, fileInfo->fileNo = 0; i <= lastDirSector; i++ )
      {
      if (! ReadSector(atr, (unsigned short) i, buffer) )
         return 4;
      for( j=0; j< dirEntriesPerSector; j++, fileInfo->fileNo++ )
         {
         dirEntry = (AtariDosDirEntryPtr)(buffer + dirEntrySize * j );
			EndianFix(dirEntry);
         fileInfo->locked = (dirEntry->flag & LOCKED_FLAG) ? 1 : 0;
         if (dirEntry->flag & DELETED_FLAG )
            continue; 
         if ( (/* (dirEntry->flag == DOS25EXT_FLAGS) || */ (dirEntry->flag & INUSE_FLAG)) &&
					PatternMatch(pattern,
             dirEntry->fileName, dirEntry->extender) )
            {
            fileInfo->flag = dirEntry->flag;
            fileInfo->startSector = dirEntry->startSector;
            fileInfo->sectorCount = dirEntry->sectorCount;
            fileInfo->dirSector = i;
            fileInfo->dirEntry = j;
            fileInfo->attrib = attrib;
            fileInfo->pattern = pattern;
            fileInfo->atrName = atrName;
            MakeFileName( fileInfo->fileName, dirEntry->fileName,
                dirEntry->extender);
            if ( CloseAtr(atr) )
                return 5;
            return( 0 ); /* success */
            }
         }
      }
   if ( CloseAtr(atr) )
      return 6;

   return -1;
   }

/******************************************************************
 AtariFindNext - Returns next matching file after previous 
                 AtariFindFirst or AtariFindNext call.  The fileinfo
                 structure passed should not be altered from the
                 previous call.  Also the ATR file name and pattern
                 from the initial AtariFindFirst call must still be
                 in scope. Similiar to _dosfindnext in DOS world.
******************************************************************/

int AtariFindNext( AtariFileInfoPtr fileInfo )
   {
   char buffer[256];
   AtrFilePtr atr;
   int i,j;
   AtariDosDirEntryPtr dirEntry;
   unsigned short sectorSize, sectorCount;
   byte protected;

   atr = OpenAtr(fileInfo->atrName);
   if ( atr == NULL )
      return 1;

   if ( GetAtrInfo( atr, &sectorSize, &sectorCount, &protected) )
      {
      free(atr);
      return 2;
      }
   i = fileInfo->dirSector;
   j = fileInfo->dirEntry;

   j++;
   if ( j >= dirEntriesPerSector )
      {
      j=0;
      i++;
      }

   for( ; i <= lastDirSector; i++ , j = 0)
      {
      if (! ReadSector(atr, (unsigned short) i, buffer) )
         return 3;
      for( ; j< dirEntriesPerSector; j++, fileInfo->fileNo++ )
         {
         dirEntry = (AtariDosDirEntryPtr)(buffer + dirEntrySize * j );
			EndianFix(dirEntry);
         fileInfo->locked = (dirEntry->flag & LOCKED_FLAG) ? 1 : 0;
         if (dirEntry->flag & DELETED_FLAG )
            continue; 
         if ( (dirEntry->flag & INUSE_FLAG) && PatternMatch(fileInfo->pattern,
             dirEntry->fileName, dirEntry->extender) )
            {
            fileInfo->flag = dirEntry->flag;
            fileInfo->startSector = dirEntry->startSector;
            fileInfo->sectorCount = dirEntry->sectorCount;
            fileInfo->dirSector = i;
            fileInfo->dirEntry = j;
            MakeFileName( fileInfo->fileName, dirEntry->fileName,
                dirEntry->extender);
            if ( CloseAtr(atr) )
               return 4;
            return( 0 );
            }
         }
      }
   if ( CloseAtr(atr) )
      return 5;

   return -1;
   }


/******************************************************************
 OpenAtariFile - Opens file in an ATR image in the mode specified
                 (ATARI_OPEN_READ, ATARI_OPEN_WRITE, or
                 ATARI_OPEN_DIR.  Returns pointer to atari file
                 structure or 0 on error. 
******************************************************************/

AtariFilePtr OpenAtariFile( char *atrName, char *fileName, byte mode)
   {
   AtariFilePtr atFile;
   byte protected;
   unsigned short sectorSize;
   unsigned short sectorCount;
   AtariFileInfo fileInfo;

/* bad open mode? */
   if ( mode != ATARI_OPEN_READ && mode != ATARI_OPEN_WRITE &&
        mode != ATARI_OPEN_DIR )
      {
      lastAtariError = 2;
      return NULL;
      }

   atFile = malloc(sizeof(AtariFile));
/* open the atr image */
   atFile->atr = OpenAtr(atrName);
   if ( atFile->atr == NULL )
      {
      free(atFile);
      lastAtariError = 1;
      return NULL;
      }
/* get some info on the ATR file and store */
   if ( GetAtrInfo( atFile->atr, &sectorSize, &sectorCount, &protected) )
      {
      CloseAtr(atFile->atr);
      free(atFile);
      lastAtariError = 3;
      return NULL;
      }

/* set file parameters */
   atFile->sectorSize = sectorSize;
   atFile->openFlag = mode;
   atFile->eofFlag = 0;

/* is ATR write protected? (APE extension?) */
   if ( protected && (mode & ATARI_OPEN_WRITE) )
      {
      CloseAtr(atFile->atr);
      free(atFile);
      lastAtariError = 4;
      return NULL;
      }

/* read directory, find start sector and number of sectors and set
   in atFile.  Initialize current sector to start sector also */

   if ( AtariFindFirst(atrName, 0, fileName, &fileInfo) )
      {
      lastAtariError = 5;
      return NULL;
      }

/* is the file the ATR is lcoated in write protected? */   
   if ( fileInfo.locked && (mode & ATARI_OPEN_WRITE) )
      {
      lastAtariError = 6;
      return NULL;
      }

/* set some file info data in the structure */
   atFile->startSector = atFile->currentSector = fileInfo.startSector;
   atFile->fileNo = fileInfo.fileNo;
   atFile->numberOfSectors = fileInfo.sectorCount;
   atFile->openFlag = mode;
   atFile->currentOffset = 0;
   if (sectorSize == 128)
      atFile->sectorLinkOffset = 125;
   else if (sectorSize == 256 )
      atFile->sectorLinkOffset = 253;
   else
      {
      lastAtariError = 7;
      return NULL;
      }
   return atFile;
   }


/******************************************************************
 ReadAtariFile - reads bytes bytes from the open atari file specified
   in atFile and stores them in buffer.  buffer must be big enough.
   Returns bytes actually read. 
******************************************************************/

long ReadAtariFile( AtariFilePtr atFile, char *buffer, long bytes )
{
	/* int lastSector = 0; */
   long bytesRead = 0;

   if ( !bytes || atFile->eofFlag)
      return 0;
   if ( !(atFile->openFlag & ATARI_OPEN_READ) )
      return 0;
   if ( !atFile->currentOffset )
      {
      /* read sector */
      if (ReadSector(atFile->atr, atFile->currentSector, atFile->sectorBuffer) != atFile->sectorSize )
         {
         if ( !lastAtariError )
            lastAtariError = 19;
         return 0;
         }
      if ( atFile->sectorSize == 128 )
         atFile->bytesData = atFile->sectorBuffer[atFile->sectorLinkOffset+2]
         & 127;
      else
         atFile->bytesData = atFile->sectorBuffer[atFile->sectorLinkOffset+2]; 
      }
   while( bytes )
      {
      while ( atFile->currentOffset < atFile->bytesData && bytes)
         {
         *(buffer++) = atFile->sectorBuffer[atFile->currentOffset++];
         bytes--;
         bytesRead++;
         }

      if ( bytes )
         {
         /* read next sector */
         atFile->currentOffset = 0;
         if (atFile->atr->dosType == DOS_MYDOS)
            atFile->currentSector =
                (atFile->sectorBuffer[atFile->sectorLinkOffset] << 8) |
                atFile->sectorBuffer[atFile->sectorLinkOffset+1];
         else /* assume atari dos */
            atFile->currentSector =
                ((atFile->sectorBuffer[atFile->sectorLinkOffset] & 3) << 8) |
                (atFile->sectorBuffer[atFile->sectorLinkOffset+1]);

         if (!atFile->currentSector )
            {
            atFile->eofFlag = 1;
            return bytesRead;
            }

         ReadSector(atFile->atr, atFile->currentSector, atFile->sectorBuffer);
         if ( atFile->sectorSize == 128 )
            atFile->bytesData = atFile->sectorBuffer[atFile->sectorLinkOffset+2] & 127;
         else
            atFile->bytesData = atFile->sectorBuffer[atFile->sectorLinkOffset+2]; 
         }

      }
   return bytesRead;
   }


/******************************************************************
 CloseAtariFile - Closes Atari File 
******************************************************************/

int CloseAtariFile( AtariFilePtr atFile )
   {
   int stat;
   /* simple enough */
   stat = CloseAtr( atFile->atr );
   free( atFile );
   return stat;
   }


/******************************************************************
 EofAtariFile - Returns 1 if at EOF of atari file, 0 if not 
******************************************************************/

int EofAtariFile( AtariFilePtr atFile )
   {
   /* simple enough */
   return atFile->eofFlag;
   }

/******************************************************************
 AtariDirectory - Displays atari directory of disk image to screen
                  return 0 for success.  atrName is the ATR file
                  name, pattern is mask to use.  use "*.*" for all
                  files.  Wide if non zero displays actual file
                  length instead of just a sector count.
******************************************************************/

int AtariDirectory( char *atrName, char *pattern)
   {
   char buffer[256];
   char fileName[14];
   byte protected;
   unsigned short sectorSize;
   unsigned short sectorCount;
   AtrFilePtr atr;
   int i,j,cnt = 0;
   char locked;
   long fileSize;
   AtariDosDirEntryPtr dirEntry;

   atr = OpenAtr(atrName);
   if ( atr == NULL )
      return 1;

   if ( GetAtrInfo( atr, &sectorSize, &sectorCount, &protected) )
      {
      free(atr);
      return 1;
      }
   
   printf("sector size = %hu    sector count = %hu\n\n", sectorSize,
       sectorCount);


   printf("\nDirectory of '%s':\n\n", atrName);

   if ( !wide )
      {
      printf("no f filename ext  secs  startSec\n");
      printf("-- - -------- ---  ----  --------\n");
      }
   else
      {
      printf("no f filename ext  secs  length  startSec\n");
      printf("-- - -------- ---  ----  ------  --------\n");
      }
   for( i = firstDirSector; i <= lastDirSector; i++ )
      {
		char printbuf[101];
		int done = 0;
      if (! ReadSector(atr, (unsigned short) i, buffer) )
         return 1;
      for( j=0; j< dirEntriesPerSector; j++ )
         {
         dirEntry = (AtariDosDirEntryPtr)(buffer + dirEntrySize * j );
			EndianFix(dirEntry);
         locked = (dirEntry->flag & LOCKED_FLAG) ? '*' : ' ';
         if (dirEntry->flag & DELETED_FLAG )
            locked = 'D'; 
			else if (dirEntry->flag & MYDOSDIR_FLAG ) {
            locked = ':'; 
			} else if(dirEntry->flag == DOS25EXT_FLAGS) {
				locked = '<';
			}
         if ( (dirEntry->flag == DOS25EXT_FLAGS) ||
					((dirEntry->flag & (INUSE_FLAG | MYDOSDIR_FLAG)) &&
					PatternMatch(pattern, dirEntry->fileName, dirEntry->extender)) )
            {
            if ( wide )
               {
               CloseAtr(atr);
               MakeFileName(fileName, dirEntry->fileName, dirEntry->extender);
					fileSize = AtariFileSize(atrName, fileName);
					if(fileSize < 0)
						sprintf(printbuf, "??");
					else
						sprintf(printbuf, "%-6ld", fileSize);

               atr = OpenAtr(atrName);
               if ( !atr )
                  return 1;
               printf("%2u %c %-8.8s %-3.3s  %4.3hu  %-6s  %hu\n", cnt, locked,
                  dirEntry->fileName, dirEntry->extender,
                  dirEntry->sectorCount, printbuf, dirEntry->startSector);
               }
            else
               printf("%2u %c %-8.8s %-3.3s  %4.3hu  %hu\n", cnt, locked,
                  dirEntry->fileName, dirEntry->extender,
                  dirEntry->sectorCount, dirEntry->startSector);
            }  else {
					/* 20070518 bkw: Atari DOS stops at the first "never used"
						dirent, so do the same here.  */
					done = 1;
					break;
				}
         cnt++;
         }
		if(done) break;
      }

   if ( CloseAtr(atr) )
      return 1;

   return 0;
   }


/******************************************************************
 AtariFileSize - Returns size of atari file or -1 on error
******************************************************************/

long AtariFileSize( char *atrFile, char *fileName )
   {
   long count = 0;
   long bytes;
   static char buffer[16];
   AtariFilePtr input;

   /* open the atari file on the ATR image */
   input = OpenAtariFile(atrFile, fileName, ATARI_OPEN_READ);
   if ( input == NULL )
      return -1;  
   /* count how many bytes we can actually read */
   while( (bytes=ReadAtariFile(input, buffer, sizeof(buffer))) > 0 )
      count+= bytes; 
   CloseAtariFile(input);
   return(count);
   }


/******************************************************************
 ExtractAtariFile - returns no. files extracted, -no for error.
                    file is stored with same name in dosPath
                    directory.  (don't add the trailing '\').
                    Wildcards are allowed for atari file. Use NULL
                    for dosPath to extract to current directory
******************************************************************/

int ExtractAtariFile( char *atrFile, char *fileName, char *unixPath )
{
   int count = 0;
   long bytes, bytesOut;
   static char buffer[16];
   AtariFilePtr input;
   char outName[4096];
   FILE *output;
   AtariFileInfo info;
   

   if ( !AtariFindFirst(atrFile, 0, fileName, &info) )
      {
      do {
         if ( unixPath != NULL)
            {
            strcpy(outName, unixPath);
            strcat(outName,"/");
            }
         else
            outName[0] = 0;
         strcat(outName, info.fileName);

			/* 20070518 bkw: Get rid of trailing dots for files with no extension */
			if(outName[strlen(outName)-1] == '.')
				outName[strlen(outName)-1] = '\0';
         
         output = fopen(outName, "wb");
         if (output == NULL )
            {
            lastAtariError = 30;
            return -count-1;
            }
         input = OpenAtariFile(atrFile, info.fileName, ATARI_OPEN_READ);
         if ( input == NULL )
            {
            return -count-1;  
            }
         if ( verbose )
            printf("Extracting '%s'...", outName);
         while( (bytes=ReadAtariFile(input, buffer, sizeof(buffer))) > 0 )
            {
            bytesOut = fwrite(buffer, 1, (int)bytes, output);
            if ( bytes != bytesOut )
               {
               fclose( output );
               CloseAtariFile(input);
               lastAtariError = 31;
               if (verbose )
                  printf("\n");
               return -count-1;
               }
            }
         fclose( output );
         CloseAtariFile(input);
         if ( verbose )
            printf(" done\n");
         count ++;
         } while ( !AtariFindNext(&info) );
      }
   else
      {
      return 0;
      }
   return(count);
}


/******************************************************************
 UpdateAtariFileNo - For atari dos, will fix the file no in each
                     sector within the file.  For use after a
                     directory is sorted.  returns 0 for success
******************************************************************/

int FixAtariFileNo( char *atrName, char *fileName, int fileNo )
{
	/* int cnt=0; */
   AtariFilePtr atFile;

   atFile = OpenAtariFile( atrName, fileName, ATARI_OPEN_READ);
   if ( atFile == NULL )
      return 1;

   if ( atFile->atr->dosType != DOS_ATARI )
      {
      CloseAtariFile(atFile);
      return 0;
      }


   while (atFile->currentSector) 
      {   
      if (ReadSector(atFile->atr, atFile->currentSector, sectorBuffer) != atFile->sectorSize )
         {
         if ( !lastAtariError )
            lastAtariError = 19;
         CloseAtariFile(atFile);
         return 1;
         }

      /* set the file no in the file */
      sectorBuffer[atFile->sectorLinkOffset] &= 3;
      sectorBuffer[atFile->sectorLinkOffset] |= (atFile->fileNo<<2);

      /* write the sector */
      if (WriteSector(atFile->atr, atFile->currentSector, sectorBuffer) != atFile->sectorSize )
         {
         if ( !lastAtariError )
            lastAtariError = 19;
         CloseAtariFile(atFile);
         return 1;
         }

      /* get next sector in link */
      atFile->currentSector =
             ((sectorBuffer[atFile->sectorLinkOffset] & 3) << 8) |
             sectorBuffer[atFile->sectorLinkOffset+1];
      }
   CloseAtariFile(atFile);
   return 0;
   }







static int ClearAtariDirectory( char *file );


/******************************************************************
 SortAtariDir - 
******************************************************************/

int SortAtariDir( char *atrName )
   {
   AtrFilePtr atr;
   char *pos;
   AtariFileInfoPtr files[64];
   AtariFileInfo info;
   AtariDosDirEntryPtr entry;
   int offset;
   unsigned short sector;   

   int i, cnt = 0;

   /* read file info for all files, allocate memory and store in array */

   if ( !AtariFindFirst(atrName, 0, "*.*", &info) )
      {
      do {
         files[cnt] = malloc( sizeof(info) );
         memcpy( files[cnt], &info, sizeof(info) );
         cnt ++;
         } while ( !AtariFindNext(&info) );
      }
   else
      {
      return 1;
      }

   /* sort the files by name */

   qsort( (void *)files, (size_t) cnt, (size_t) sizeof(AtariFileInfoPtr), CompareName );

   /* clear out the directory */
   if ( ClearAtariDirectory(atrName) )
      return 1;

   /* write the entries in sorted order to the directory sectors */

   offset = 0;
   sector = firstDirSector;

   atr = OpenAtr( atrName );

   for( i=0; i<cnt; i++ )
      {
      entry = (AtariDosDirEntryPtr) (sectorBuffer + offset );
      entry->startSector = files[i]->startSector;
      entry->sectorCount = files[i]->sectorCount;
      entry->flag = files[i]->flag;
		EndianFix(entry);
      pos = strchr( files[i]->fileName, '.' );

		/* 20070518 bkw: init to all spaces, else you get filenames
			like "DOS\0\0\0\0\0SYS" (\0 shows up as Atari heart character) */
		memset(entry->fileName, ' ', 8);
		memset(entry->extender, ' ', 3);

      if ( pos != NULL)
         {

         strncpy( entry->fileName, files[i]->fileName, 
                  min(pos-files[i]->fileName,8) );
         strncpy( entry->extender, pos+1, 3 );     
         }
      else
         {
         strncpy( entry->fileName, files[i]->fileName, 8);
         }
      offset += 16;
      if (offset >= 128)
         {
         if ( WriteSector( atr, sector, sectorBuffer) != atr->secSize )
            {
            CloseAtr( atr );
            return 1;
            }
         sector ++;
         offset = 0;
         memset(sectorBuffer, 0, sizeof(sectorBuffer));
         }      
      }
   if ( offset > 0 )
      if ( WriteSector( atr, sector, sectorBuffer) != atr->secSize )
         {
         CloseAtr( atr );
         return 1;
         }

   CloseAtr( atr );


   /* This should have no effect on Mydos extended format disks */
   for( i=0; i<cnt; i++ )
      {
      if ( FixAtariFileNo( atrName, files[i]->fileName, i) )
         return 1;
      free( files[i] );
      }
   return 0;
   }

/* function for above used as arg to qsort */
static int CompareName( const void *a, const void *b )
   {
   AtariFileInfoPtr aa = *(AtariFileInfoPtr *)a,
                    bb = *(AtariFileInfoPtr *)b;

   return strcmp( aa->fileName, bb->fileName );
   }

/*******************************************************************
ClearAtariDirectory - internal routine
*******************************************************************/

static int ClearAtariDirectory( char *file )
   {
   AtrFilePtr atr;
   int i; 
  
   memset(sectorBuffer, 0, sizeof(sectorBuffer) );
   atr = OpenAtr( file );
   
   for( i = firstDirSector; i <= lastDirSector; i++ )
      if ( WriteSector(atr, i, sectorBuffer) != atr->secSize )
         return 1;
   return 0;
   CloseAtr(atr);
   }


/********************************************************************
 CreateBootAtr - creates a minimally sized bootable ATR image from
                 an atari executable.  The executable must not need
                 DOS to run.
********************************************************************/

int CreateBootAtr( char *atrName, char *fileName)
   {
   unsigned long fileSize;
   unsigned long sectorCnt;
   AtrHeader hdr;
   unsigned long paras;
   FILE * atrFile, *inFile;
   size_t padding, bytes, bytes2;
   struct stat fileInfo;
   int status;
	int first = 1;

/* get file's size */

   status = stat(fileName, &fileInfo);
   if ( status )
      return 11;
   fileSize = (unsigned long) fileInfo.st_size;
   if ( !fileSize )
      return 12;

/* determine number of sectors required  */

   sectorCnt = (unsigned short) ((fileSize + 127L) / 128L + 3L);
   paras = sectorCnt * 16;

/* create ATR header */
   memset(&hdr, 0, sizeof(hdr));
   hdr.idLow      = (byte) 0x96;
   hdr.idHigh     = (byte) 0x2;
   hdr.paraLow    = (byte) (paras & 0xFF);
   hdr.paraHigh   = (byte) ((paras >> 8) & 0xFF);
   hdr.paraHigher = (byte) ((paras >> 16) & 0xFF);
   hdr.secSizeLow = (byte) 128;

/* open output file */
   atrFile = fopen(atrName, "wb");
   if ( atrFile == NULL )
      return 1;

/* Write the ATR Header */
   bytes = fwrite(&hdr, 1, sizeof(hdr), atrFile);
   if ( bytes != sizeof(hdr) )
      {
      fclose(atrFile);
      return 2;
      }

/* plug the file size into the boot sectors at offset 9 (4 bytes)*/
   bootData[9]  = (byte)(fileSize & 255);
   bootData[10] = (byte)((fileSize >> 8) & 255);
   bootData[11] = (byte)((fileSize >> 16) & 255);
   bootData[12] = 0;

/* write the three boot sectors */
   bytes = fwrite(bootData, 1, 384, atrFile);
   if ( bytes != 384 )
      {
      fclose(atrFile);
      return 6;
      }

/* open the input file and copy/append the file's data to output file */

   inFile = fopen(fileName, "rb");
   if ( inFile == NULL )
      {
      fclose(atrFile);
      return 13;
      }

   bytes = 384;
   while (bytes == 384)
      {
      bytes = fread(bootData, 1, 384, inFile);
      if ( !bytes )
         break;

		/* 20070518 bkw: Make sure it really is an Atari bin load file */
		if(first) {
			if((bootData[0] != 0xff) && (bootData[1] != 0xff)) {
				fclose(inFile);
				fclose(atrFile);
				return 20;
			}

			first = 0;
		}

      bytes2 = fwrite(bootData, 1, bytes, atrFile);
      if ( bytes != bytes2 )
         {
         fclose(inFile);
         fclose(atrFile);
         return 19;
         }
      }
   if ( !feof(inFile) )
      {
      fclose(inFile);
      fclose(atrFile);
      return 19;
      }

  fclose(inFile); 


/* pad to even sector size (data has no meaning) */
   padding = (size_t) ((sectorCnt-3) * 128 - fileSize ); 
   if ( padding )
      {
      bytes = fwrite(bootData, 1, padding, atrFile);
      if ( bytes != padding )
         {
         fclose(atrFile);
         return 7;
         }
      }  

/* close output */
   fclose(atrFile);

   return 0;
   }

/********************************************************************
ExtractExeFromBootAtr - undoes a CreateBootAtr by extracting the
                        original executable
returns 0 for error, or file length in bytes of file extracted
!!This function needs to have code added to distinguish error types.
********************************************************************/

long ExtractExeFromBootAtr( char *atrName, char *fileName)
   {
   FILE *atrFile, *exeFile;
   unsigned char *buffer;
   AtrHeader hdr = {0};
	/* long fileSize; */
   size_t bytes,bytes2,readCnt;
   unsigned long  fSize, size;

/* get some memory */
   buffer = malloc(384);
   if ( !buffer)
      return 0;

/* open the atr file */
   atrFile = fopen(atrName, "rb");
   if ( !atrFile )
      {
      free(buffer);
      return 0;
      }

/* read Atr header */

   if ( fread(&hdr, 1, 16, atrFile) != 16 )
      {
      free(buffer);
      fclose(atrFile);
      return 0;
      }

/* verify it is an ATR file */

   if ( hdr.idHigh != 0x02 || hdr.idLow != 0x96 )
      {
      free(buffer);
      fclose(atrFile);
      return 0;
      }

/* read first 3 (boot) sectors */

   if ( fread(buffer, 1, 384, atrFile) != 384 )
      {
      free(buffer);
      fclose(atrFile);
      return 0;
      }

/* set the file size in bootData from the file so we can compare*/
   bootData[9]   = buffer[9];
   bootData[10]  = buffer[10];
   bootData[11]  = buffer[11];
   bootData[12]  = buffer[12];

/* check if ATR was created by MakeBootAtr */
   if ( memcmp(buffer, bootData, 384) )
      {
      free(buffer);
      fclose(atrFile);
      return 0;
      }
/* Get size of file to extract */
   fSize = size = ((unsigned long)buffer[9]|(((unsigned long)buffer[10])<<8)|(((unsigned long)buffer[11])<<16));

/* Open output file */
   exeFile = fopen(fileName, "wb");
   if ( !exeFile )
      {
      fclose(atrFile);
      free(buffer);
      return 0;
      }
/* copy 'size' bytes from the Atr file to the exe file */   
   bytes = 384;
   while (bytes == 384 && fSize)
      {
      readCnt = min(384, fSize);
      bytes = fread(buffer, 1, readCnt, atrFile);
      if ( !bytes )
         break;
      bytes2 = fwrite(buffer, 1, bytes, exeFile);
      if ( bytes != readCnt )
         {
         fclose(exeFile);
         fclose(atrFile);
         free(buffer);
         return 0;
         }
      fSize -= bytes;
      }

/* clean up and get out of here */
   fclose(exeFile);
   fclose(atrFile);
   free(buffer);

   return size;
   }


