/*
    PAKSORT.C	Sort contents of .PAK archive.

	Copyright 1988, Michael J. Housky
	Released to the Public Domain ... *no* rights reserved.
*/

/*
    Update log:
******************************************************************************
	Version 1.2, 10 September 1988: Released to public domain.
	Also relocated stderr fprintf's to stdout so error messages
	can be captured with command-line redirection.
	P.S. I only observe about 2% improvement with read/write over
	fread/fwrite, but it *is* smaller. (mjh)
******************************************************************************
	Version 1.1, 9 September 1988: used read, write, tell, lseek, open,
	close instead of fread, fwrite, ftell, etc. This caused a 12% or so
	speedup in the operation. jjn
******************************************************************************
	Version 1.0, 25 August 1988: First version.
******************************************************************************
*/

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>


#include "dirfn.h"
#include "pakdefn.h"

#define BUF_SIZE	4096	/* buffer size for sort/copy step */

typedef struct			/* stored directory format: */
{
    long	floc;		/* byte location of header in file */
    long	slen;		/* stored (packed) length of file */
    long	olen;		/* original (unpacked) length of file */
    unsigned	ddate;		/* DOS date of update */
    unsigned	dtime;		/* DOS time of update */
    char	fname[13];	/* filename */
} pak_dir;

/* ------------	Global data: */


FILE_TYPE	*file_list = NULL;	/* list of files (malloc'ed) */
unsigned char	pak_buf[BUF_SIZE];	/* buffer for reading pakfile data */
pak_hdr		fhdr;			/* buffer for reading pakfile header */
long		pakloc;			/* location in input file */
long		pakloc,paklen;		/* length of input file */
char		order[33];		/* sort order from argument */
char		fpath[128];		/* full path\name of file */
char		*fnptr;			/* location of filename in fpath */

char		pak_mark[2] = {PAK_MARK,0};

/*
	read_pakhdr:	Read header of .PAK member file

Reads next header sequentially from file.

Returns 0 if good header, and global fhdr has header.
Returns +1 if end of file marker found.
Returns -1 if bad header or read error.

*/

int read_pakhdr( int );

int read_pakhdr( fp )
    int		fp;
{
    int i;
    register int j;

    i = 0;
    j = read(fp, (char *)&i, 1);	/* get file marker */
    if ( i != PAK_MARK || j == -1)	/* error if not correct */
	return -1;

    j = read(fp, (char*)&fhdr, PAK_HDRLEN);
					/* read header, follows mark */

    if ( j>=1 && fhdr.type==0 )		/* type 0 header is an EOF mark */
	return 1;			/* end of file mark seen */

    if ( j!=PAK_HDRLEN || fhdr.slen<0L || fhdr.olen<fhdr.slen )
	return -1;			/* invalid header seen */

    pakloc = tell(fp);			/* note start of data and test */
    if ( pakloc+fhdr.slen > paklen )	/*  for all data present */
	return -1;			/* no: presume truncated file */
    return 0;

} /* read_pakhdr */


/*
	pak_getdir:	Build directory of .PAK file headers.

Sequentially read and validate all headers within pakfile. Build malloc-ed
table of headers found.

Returns:
	 0 = success, *pdirp = pointer to malloc-ed directory.
	 1 = out of memory;
	-1 = read/seek error or bad header.
*/

int pak_getdir( int, pak_dir** );

int pak_getdir( fp, pdirp )
    int		fp;
    pak_dir	**pdirp;
{
    register	int i;
    int		n,k;
    long	l,hdrloc;
    pak_dir	*pd;

/* Read the first header: */

    hdrloc = tell(fp);

    i = read_pakhdr(fp);
    if ( i )
    {
	printf("Invalid header seen.\n");
	return -1;
    }

    pd = (pak_dir*) malloc( (k=32)*sizeof(pak_dir) );
    if ( pd==NULL )
    {
	printf("Out of memory.\n");
	return -1;			/* out of memory error */
    }
    n = 0;
    
    do			/* loop once for each new entry found, with */
    {			/* n=#headers found, k=directory size */

	if ( n >= k )			/* test for current table full */
	{
	    /* add 10 more entries to directory: */
	    pd = (pak_dir*) realloc( (char*)pd, (k+=16)*sizeof(pak_dir) );
	    if ( pd==NULL )
	    {
		printf("Out of memory.\n");
		return -1;		/* out of memory error */
	    }
	}

	pd[n].floc  = hdrloc;		/* store header location */
	pd[n].slen  = fhdr.slen;	/* copy stored length */
	pd[n].olen  = fhdr.olen;	/* copy original length */
	pd[n].ddate = fhdr.ddate;	/* copy DOS date */
	pd[n].dtime = fhdr.dtime;	/* copy DOS time */
	strcpy(pd[n].fname,fhdr.fname);	/* copy file name */
	++n;				/* and increment file count */

	hdrloc += fhdr.slen+PAK_HDRLEN+1;
	l = lseek( fp, hdrloc, SEEK_SET );
	if ( l == -1L )
	{
	    printf("Seek error.\n");
	    free( (char*)pd );
	    return -1;
	}

	i = read_pakhdr(fp);

    } while ( i==0 );

    if ( i<0 )				/* test for error */
    {
	printf("Invalid header seen.\n");
	free( (char*)pd );
	return -1;
    }
    *pdirp = pd;
    return n;

} /* pak_getdir */


/*
	dir_cmp:	Directory entry compare function for sort.

Input:
	lp:	pointer to "left" entry
	rp:	pointer to "right" entry
	order:	sort order
Return:
	0 if entries are equal
	positive if left "greater than" right
	negative if left "less than" right
*/

int dir_cmp( pak_dir*, pak_dir*, char* );

int dir_cmp( lp, rp, order )
    pak_dir	*lp,*rp;
    char	*order;
{
    register int i,j;
    int		lc,rc;
    char	*lcp,*rcp;

    for ( i=j=0; j==0; i+=2 )
    {
	switch( order[i] )
	{
	    case 'F':		/* compare full fileid */
		j = strcmp( lp->fname, rp->fname );
		break;

	    case 'N':		/* compare name only */
		lc = *(lcp = lp->fname);
		rc = *(rcp = rp->fname);
	        while ( (j=lc-rc)==0 && lc!=0 )
		{
		    lc = *++lcp;
		    if ( lc=='.' )
			lc = 0;
		    rc = *++rcp;
		    if ( rc=='.' )
			rc = 0;
		}
		break;

	    case 'X':		/* compare .ext only */
		/* find start of left extension: */
		for ( lc=*(lcp = lp->fname); lc; )
		{
		    ++lcp;
		    lc = ( lc=='.' ) ? 0 : *lcp;
		}
		/* find start of right extension: */
		for ( rc=*(rcp = rp->fname); rc; )
		{
		    ++rcp;
		    rc = ( rc=='.' ) ? 0 : *rcp;
		}
		/* return regular string compare of remainder of names: */
		j = strcmp( lcp, rcp );
		break;

	    case 'D':		/* compare unsigned date: */
		j = (lp->ddate > rp->ddate) - (lp->ddate < rp->ddate);
		break;

	    case 'T':		/* compare unsigned time: */
		j = (lp->dtime > rp->dtime) - (lp->dtime < rp->dtime);
		break;

	    case 'S':		/* compare (long) size: */
		j = (lp->olen > rp->olen) - (lp->olen < rp->olen);
		break;

	    case '\0':
		return 0;
	}
	if ( order[i+1] == '-' )
	    j = -j;
    }
    return j;

} /* dir_cmp */


/*
	pak_sort:	Sort one .PAK file.

A simple (slow, but small and sure) routine is used here to
sort the directory without the overhead of qsort and recursion.

If this sort is replaced with an unstable sort (like quicksort) then
the dir_cmp function should be changed to return compare of original
file location if all fields in order[] compare equal. This will preserve
sort stability ... i.e. input order is preserved on equal entries.

Return value is 0 if all files were originally in order,
 or 1 if output order is different from input order.

*/

int pak_sort( pak_dir*, int, char* );

int pak_sort( pdir, n, order )
    pak_dir	*pdir;
    int		n;
    char	*order;
{
    register	int i,j;
    int		i0,j0,k,r;
    pak_dir	tdir;

    r = 0;
    for ( j0=1; j0<n; ++j0 )
    {
	i = i0 = j0-1;			/* starting entry # to compare */
    	for ( j=j0; j<n; ++j )
    	{
	    k = dir_cmp( pdir+i, pdir+j, order );
	    if ( k>0 )
	    {				/* out of order */
		i = j;			/* will swap at end */
	    }
    	}
	if ( i != i0 )			/* if out of order */
	{
	    tdir = pdir[i];		/* swap smallest entry */
	    pdir[i] = pdir[i0];		/* (at most n-1 times) */
	    pdir[i0] = tdir;
	    r = 1;			/* tell caller that input was */
	    				/* not in order */
	}
    }
    return r;

} /* pak_sort */


/*
	pak_write:	Copy packed source files to dest in sorted order.
*/

int pak_write( int, int, pak_dir*, int );

int pak_write( src, dst, dirp, n )
    int		src;		/* opened source file */
    int		dst;		/* opened dest file */
    pak_dir	*dirp;		/* sorted directory of source */
    int		n;		/* number of entries in source directory */
{
    register int i,j;
    int		k;
    long	len,start,end;
    long	l;

    for ( i=0; i<n; ++i )
    {				/* once for each member file */

	printf("  Copying: %-13s - loc=%-6ld  len=%-6ld\n",
		dirp[i].fname,
		dirp[i].floc,
		dirp[i].slen+1+PAK_HDRLEN );

	/* point to start of member, test for error: */
	l = lseek(src, dirp[i].floc, SEEK_SET);
	if ( l == -1L )
	{
	    printf("Seek error.\n");
	    return -1;
	}

	/* compute length and copy member to destination: */
	len = dirp[i].slen+1+PAK_HDRLEN;
	for ( end=0; end<len; )
	{
 	    start = end;
	    end = start+(k=BUF_SIZE);
	    if (end>len)
		k = (int)((end=len)-start);
	    j = read(src,pak_buf,k);
	    if ( j != k )
	    {
		printf("Read error.\n");
		return -1;
	    }
	    j = write(dst,pak_buf,k);
	    if ( j != k )
	    {
		printf("Write error.\n");
		return -1;
	    }
	}
    }

    /* Add EOF mark to (end of) destination: */
    j = write(dst, pak_mark, sizeof(pak_mark));
    if ( j != sizeof(pak_mark) )
    {
	printf("Write error.\n");
	return -1;
    }

    return 0;

} /* pak_write */



/*
	pak_dosort:	Read/sort/rewrite one .PAK file.

Returns 0 if successful, or errorlevel for DOS on error:
   1 = Can't open/read source. (missing or invalid pakfile)
   2 = Can't create/write dest (maybe disk or directory full)
   3 = Can't delete old source file. (read-only)
   4 = Can't rename temp file to source name. (???)
*/

int pak_dosort( char* );

int pak_dosort( fn )
    char	*fn;
{
    register int i,j;
    int		n;
    pak_dir	*pdir;		/* pointer to (malloced) directory */
				/*  built by pak_getdir */
    int		src;		/* source file */
    int		dst;		/* dest file */
    char	srcname[81];	/* save source filename */

    strcpy( fnptr, fn );		/* make full path to file */
    printf("\nSearching:  %s\n",fpath);	/* and identify */
    src = open( fpath, O_RDONLY | O_BINARY );	/* and open file */
    if ( src == -1 )
    {
	printf("Can't open.\n", fpath);
	return 1;
    }
    paklen = filelength(src);		/* get file length in global */

    pdir = NULL;			/* clear directory pointer */
    n = pak_getdir( src, &pdir );	/* get directory */
    if ( n<1 )
    {
	printf("Invalid file format, not changed.\n");
	close(src);
	return 1;
    }

    i = pak_sort( pdir, n, order );
    if ( i==0 )
    {
	printf("Already in order, not changed.\n");
	free( (char*)pdir );
	return 0;
    }

/* pakfile is not in order, create sorted copy & rename: */

    strcpy( srcname, fpath );
    strcpy( fnptr, "PAKSRTMP.$$$" );
    dst = open( fpath,
		O_CREAT | O_TRUNC | O_WRONLY | O_BINARY,
		S_IREAD | S_IWRITE);
    if ( dst==-1 )
    {
	printf("Can't create: %s\n", fpath );
	free( (char*)pdir );
	return 1;
    }

    j = pak_write( src, dst, pdir, n );	/* copy source to dest tempfile */
    close(src);				/* unconditionally close source */
    free( (char*)pdir );		/*  and release directory mem */
    i = close(dst);			/* close dest file */

    if ( j || (i == -1) )		/* if error on write or close */
    {
	    if ( !j )			/* identify close error */
		printf("Can't close: %s\n",fpath);
	    unlink(fpath);
	    return 2;
    }

    j = unlink(srcname);		/* delete source */
    if ( j )
    {					/* can't delete, read-only? */
	printf( "Can't delete original.\n");
	unlink(fpath);			/* delete sorted temp copy */
	return 3;			/* return error */
    }
    j = rename( fpath, srcname );
    if ( j )
    {
	printf("Rename error: %d, original unsorted data lost!\n");
	printf("Sorted data retained in temp output file for recovery:\n");
	printf("%s\n",fpath);
	return 4;
    }

    return 0;

} /* pak_dosort */


/*
	PKASORT.C main program
*/


int main(int,char **);
int main( argc, argv )
    int		argc;
    char	**argv;
{
    register int
		i,j;
    int		k;
    long	len;
    int		addext;
    int		num_files;

/* First, identify self and examine paramter: */

    printf("\nPAKSORT v1.2\n");
    if ( argc<2 )
    {
	printf("Usage: paksort <fname> [<order>]\n");
	printf("<order> is one or more of: \n");
	printf("  F+ or F-: sort by full filename.ext,\n");
	printf("  N+ or N-: sort by filename,\n");
	printf("  X+ or X-: sort by .ext,\n");
	printf("  D+ or D-: sort by date,\n");
	printf("  T+ or T-: sort by time,\n");
	printf("  S+ or S-: sort by (unpacked) file size,\n");
	printf("  [Note: + gives ascending order, - gives descending order.]\n");
	printf("The default order is D+T+F+\n");

	return 0;
    }

/* Parameter valid, find path and decide about default extension */

    strcpy( order, "D+T+F+" );		/* set default order */
    if ( argc > 2 )
    {
	k = strlen(argv[2]);
	if ( (k&1) || (k>=sizeof(order)) )
	{
	    printf( "Invalid sort order\n" );
	    return 1;
	}
	strcpy( order, argv[2] );
	strupr( order );
	for ( i=0; i<k; i+=2 )
	{
	    switch( order[i] )
	    {
		default:
		    printf("Invalid sort order\n");
		    return 1;
		case 'N': case 'F': case 'X':
		case 'D': case 'T': case 'S':
		    /* break */;
	    }
	    switch( order[i+1] )
	    {
		default:
		    printf("Invalid sort order\n");
		    return 1;
		case '+': case '-':
		    /* break */;
	    }

	}
    }
    strcpy( fpath, argv[1] );		/* extract path, find end */
    i = strlen(fpath);			/* index of last character */
    addext = 1;				/* presume no extension */
    while ( --i >= 0 )			/* search backward thru argument */
    {
	j = fpath[i];
	if ( j=='\\' || j==':' )
	    break;			/* colon or backslash ends search */
	if ( j=='.' )
	    addext = 0;			/* period found, no default */
    }
    fnptr = fpath+i+1;
    strupr(fpath);			/* uppercase for display */
    if ( addext )
	strcat(fpath,".PAK");		/* add default extension */

    num_files = get_files(fpath,0,&file_list);
    if (num_files < 0)
    {
	printf("Too many files: out of memory.\n");
	return(1);
    }

    if (num_files < 1)
    {
	printf("Cannot find file(s): %s\n",argv[1]);
	return(1);
    }

/* Now, list contents of each file: */

    for (i = 0; i < num_files; ++i)
    {
	j = pak_dosort( file_list[i].filename ); 
	if ( j )
	    return j;
    }
    return 0;

} /* main */
