/* XPK - General XPK file-to-file packer/unpacker */

#include <proto/exec.h>
#include <proto/dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libraries/xpk.h>

struct Library *XpkBase;
char errbuf[300], *err;
#define lines[1000]

int   chkabort(void) { return 0; }                 /* disable SAS ^C handling */
long __asm chunkfunc(register __a1 struct XpkProgress *prog);
char *tempname( char *name );
char *basename( char *name );
void  doarg   ( char *argname  );
char *dofile  ( char *filename, struct FileInfoBlock *fib );
void  end     ( char *text );
int   isexecutable(char *name);

extern struct Hook chunkhook;
char namebuf[200];
char usage[]=
"Usage: XPK [-frsux] [-p password] [-m method] files\n"
"       -m = four letter packing method name\n"
"       -f = force packing of already packed files\n"
"       -s = add suffix and don't delete original\n"
"       -r = recursively (un)pack files in dir\n"
"       -u = unpack files\n"
"       -p = encrypt/decrypt using password\n"
"       -x = pack executables only\n";

char suffix, force, unpack, *password, recurse, *method, depth, executables;

void main(int argc, char *argv[])
{
	char *c;
	int  i=1;

	if(!(XpkBase=OpenLibrary(XPKNAME,0)))
		end("Cannot open "XPKNAME"\n");

	if( stricmp(basename(argv[0]),"XPK"))
		method=basename(argv[0]);
	else if( argc<2 || !strcmp(argv[1],"?"))
		end(usage);

	for( i; *argv[i]=='-'; i++ )
		for( c=argv[i]+1; *c; c++ ) {
			switch(*c) {
			case 'p': password=argv[++i]; break;
			case 'm': method  =argv[++i]; break;
			case 's': suffix=1;           break;
			case 'f': force=1;            break;
			case 'e': unpack=1;           break;
			case 'r': recurse=1;          break;
			case 'x': executables=1;      break;
			default : end(usage);
			}
			if( i>=argc ) end(usage);
		}

	if( !method && !unpack )
		end("Need a packing method, use -m\n");

	if( i==argc )
		end(usage);

	for( ; i<argc && !err; i++ ) 
		doarg(argv[i]);

	end(err);
}


void  
print(char *s) 
{
	Write(Output(),s,strlen(s));
}

void
iprint(char *s)
{
	char buf[200];

	memset(buf,' ',depth*2);
	strcpy(buf+depth*2,s);
	print(buf);
}

char PrevName[100], strbuf[200];
struct FileInfoBlock __aligned fib;

void
doarg( char *name )
{
	BPTR lock, prev;
	char thisname[100];

	if( *name==-1 )
		return;

	if( !(lock=Lock( name, ACCESS_READ))) {
		sprintf(err=errbuf,"Error %ld reading %s\n",IoErr(),name);
		return;
	}

	if( !Examine( lock, &fib )) {
		UnLock( lock );
		sprintf(err=errbuf,"Error %ld reading %s\n",IoErr(),name);
		return;
	}

	if( fib.fib_DirEntryType<0 ) {
		UnLock( lock );
		dofile( name, &fib );
	} else if( recurse ) {
		sprintf(strbuf,"Directory %s\n",name);
		iprint(strbuf);
		prev=CurrentDir( lock );
		*PrevName=-1;

		while( ExNext(lock,&fib) && !err) {
			if( SetSignal(0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
				err=" *** Break";
			else {
				strcpy( thisname, fib.fib_FileName );
				depth++;
				doarg( PrevName );
				depth--;
				strcpy( PrevName, thisname );
			}
		}
		depth++;
		doarg( PrevName);
		depth--;
		*PrevName=-1;

		UnLock( CurrentDir(prev));
	}
}



char *
dofile( char *filename, struct FileInfoBlock *fib )
{
	XFIB xfib;
	char buf[100];
	int  len;

	if( !force || unpack ) {
		if( XpkExamineTags(&xfib,XPK_InName,filename,TAG_DONE)) {
			sprintf(buf,"Error examining %s\n",filename);
			iprint(buf);
			return 0;
		}
	}

	tempname(filename); 
	if( !unpack ) {
		if( !force && xfib.Type!=XPKTYPE_UNPACKED ) {
			sprintf(buf,"Skipping (already packed) %s\n",filename);
			iprint(buf);
			return 0;
		}

		if( executables && !isexecutable(filename) )
			return 0;

		if( suffix )
			sprintf( namebuf, "%s.xpk", filename);

		if( XpkPackTags(               
			XPK_InName,      filename,
			XPK_OutName,     namebuf,
			XPK_ChunkHook,   &chunkhook,
			XPK_GetError,    errbuf,
			XPK_FindMethod,  method,
			XPK_Password,    password,
			XPK_NoClobber,   TRUE,
			TAG_DONE
		))
			return err=strcat(errbuf,"\n");

	} else {
		if( xfib.Type!=XPKTYPE_PACKED ) {
			sprintf(buf,"Skipping (already unpacked) %s\n",filename);
			iprint(buf);
			return 0;
		}

		len=strlen(filename);
		suffix=0;
		if( len>4 && !stricmp(filename+len-5,".xpk" )) {
			strcpy(namebuf,filename);
			namebuf[len-5]=0;
			suffix=1;
		}

		if( XpkUnpackTags(               
			XPK_InName,      filename,
			XPK_FileName,    filename,
			XPK_OutName,     namebuf,
			XPK_ChunkHook,   &chunkhook,
			XPK_Password,    password,
			XPK_GetError,    errbuf,
			XPK_NoClobber,   TRUE,
			TAG_DONE
		))
			return err=strcat(errbuf,"\n");
	}

	if( !suffix ) {
		if( !DeleteFile(filename))
			return err="Cannot delete input file\n";
		if( !Rename(namebuf,filename))
			return err="Cannot rename tempfile\n";
		if( *fib->fib_Comment && !SetComment(filename,fib->fib_Comment))
			return err="Cannot set original comment\n";
		if( fib->fib_Protection && !SetProtection(filename,fib->fib_Protection))
			return err="Cannot set original protection bits\n";
	}
}


long __asm 
chunkfunc(register __a1 struct XpkProgress *prog)
{
	char buf[180];

	if( prog->Type==XPKPROG_START )
		print("\033[0 p");

	if( prog->Type!=XPKPROG_END )
		sprintf(buf,
		  "%4s: %-8s (%3ld%% done, %2ld%% CF, %6ld cps) %s\033[K\r",
		   prog->PackerName,  prog->Activity,   prog->Done,
		   prog->CF,          prog->Speed,      prog->FileName);
	else 
		sprintf(buf,
		  "%4s: %-8s (%3ldK, %2ld%% CF, %6ld cps) %s\033[K\n",
		   prog->PackerName,  prog->Activity,   prog->ULen/1024,
		   prog->CF,          prog->Speed,      prog->FileName);

	iprint( buf );

	if( prog->Type==XPKPROG_END )
		print("\033[1 p");

	return (long)SetSignal(0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C;
}
struct Hook chunkhook = {{0},chunkfunc};

char *tempname( char *name )
{
	strcpy( namebuf, name );
	for( name=namebuf+strlen(namebuf); name>namebuf; name-- )
		if( name[-1]=='/' || name[-1]==':' )
			break;
	sprintf(name,"tmp%lx",&name);
	return namebuf;
}


int
isexecutable(char *name)
{
	BPTR fh;
	BPTR buf[5];
	char msg[100];
	int  len;

	if( !(fh=Open(name,MODE_OLDFILE))) {
		sprintf(msg,"Cannot open %s\n",name);
		iprint(msg);
		return 0;
	}
	len=Read(fh,(void*)buf,20);
	Close(fh);

	if( len<20 || buf[0]!=0x3f3 || buf[1]!=0 ) {
		sprintf(msg,"%s not executable\n",name);
		iprint(msg);
		return 0;
	}

	if( buf[3]!=0 || buf[4]+1!=buf[2] ) {
		sprintf(msg,"%s overlayed\n",name);
		iprint(msg);
		return 0;
	}
	return 1;
}


char *basename( char *name )
{
	char *ret=name;

	for( ; *name; name++ )
		if( *name==':' || *name=='/' )
			ret=name+1;
	return ret;
}

void end( char *text )
{
	if( text )    print( text );
	if( XpkBase ) CloseLibrary( XpkBase );
	exit(text ? 10 : 0);
}
