#define NAME	 "xPK"
#define REVISION "3"

/* Programmheader

	Name:		xPK
	Author:		SDI (before 1.2 Urban Dominik Müller)
	Distribution:	PD
	Description:	General XPK file-to-file packer/unpacker
	Compileropts:	-
	Linkeropts:	-l xpkmaster

 1.0	: first public release
 1.1	: docs written, version string added
 1.2   19.10.96 : fixed an recursion error
 1.3   29.11.96 : recompiled
*/

#include "SDI_defines.h"
#define SDI_TO_ANSI
#include "SDI_ASM_STD_protos.h"
#include <pragma/exec_lib.h>
#include <pragma/dos_lib.h>
#include <pragma/xpkmaster_lib.h>
#include <exec/memory.h>

#ifdef __MAXON__
  #define __asm
  #define __saveds
#endif

#define lines[1000]

ULONG __asm __saveds chunkfunc(register __a1 struct XpkProgress *);
STRPTR tempname(STRPTR);
STRPTR basename(STRPTR);
void   doarg(STRPTR);
STRPTR dofile(STRPTR, struct FileInfoBlock *);
void   end(STRPTR);
LONG   isexecutable(STRPTR);

struct Hook 		chunkhook	= {{0}, (ULONG (*) ()) chunkfunc};
struct Library 		*XpkBase	= 0;
UBYTE			errbuf[300],
			*err		= 0,
			namebuf[200],
			PrevName[100],
			strbuf[200];
struct FileInfoBlock 	*fib		= 0;

UBYTE usage[] =
"Usage: XPK [-efrsux] [-m method] [-p password] files\n"
"       -e = extract files (same as -u)\n"
"       -f = force packing of already packed files\n"
"       -m = four letter packing method name\n"
"       -p = encrypt/decrypt using password\n"
"       -r = recursively (un)pack files in dir\n"
"       -s = add suffix and don't delete original\n"
"       -x = pack executables only\n";

UBYTE	suffix = 0, force = 0, unpack = 0, recurse = 0, depth = 0, executables = 0;
STRPTR  password = 0, method = 0;

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

  if(!(XpkBase = OpenLibrary(XPKNAME, 0)) ||
  !(fib = (struct FileInfoBlock *) AllocMem(sizeof(struct FileInfoBlock), MEMF_ANY|MEMF_CLEAR)))
    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(; *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':
      case 'u':	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 iprint(STRPTR s)
{
  ULONG out = Output(), i;
  for(i = depth; i; --i)
    Write(out, "  ", 2);
  Write(out, s, strlen(s));
}

void doarg(STRPTR name)
{
  ULONG lock;

  if(*name == 0xFF)
    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)
  {
    ULONG prev;

    sprintf(strbuf, "Directory %s\n", name);
    iprint(strbuf);
    prev = CurrentDir(lock);
    *PrevName = 0xFF;

    while(ExNext(lock, fib) && !err)
    {
      if(SetSignal(0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
	err = " *** Break";
      else
      {
        STRPTR thisname;
	ULONG i = strlen(fib->fib_FileName) + 1;

        if(!(thisname = (STRPTR) AllocMem(i, MEMF_ANY)))
        {
          Write(Output(), "Not enough memory\n", 18);
          break;
        }
	CopyMem(fib->fib_FileName, thisname, i);
	depth++;
	doarg(PrevName);
	depth--;
	strcpy(PrevName, thisname);
	FreeMem(thisname, i);
      }
    }
    depth++;
    doarg(PrevName);
    depth--;
    *PrevName = 0xFF;

    UnLock(CurrentDir(prev));
  }
}

STRPTR dofile(STRPTR filename, struct FileInfoBlock *fib)
{
  struct XpkFib xfib;
  UBYTE buf[100];
  LONG len;

  if(!force || unpack)
  {
#ifdef __MAXON__			/* Maxon has no tagcall pragma now */
      struct TagItem t[] = {		/* and I don't want to use a link */
#else					/* library */
      if(XpkExamineTags(&xfib,
#endif
    XPK_InName, (ULONG) filename,
    TAG_DONE
#ifdef __MAXON__
      , 0 };
      if(XpkExamine(&xfib, t
#endif
    ))
    {
      sprintf(buf, "Error examining %s\n", filename);
      iprint(buf);
      return 0;
    }
  }

  tempname(filename);
  if(!unpack)
  {
    if(!force && xfib.xf_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);

    {
#ifdef __MAXON__			/* Maxon has no tagcall pragma now */
      struct TagItem t[] = {		/* and I don't want to use a link */
#else					/* library */
      if(XpkPackTags(
#endif
        XPK_InName, (ULONG) filename,
	XPK_OutName, (ULONG) namebuf,
	XPK_ChunkHook, (ULONG) &chunkhook,
	XPK_GetError, (ULONG) errbuf,
	XPK_PackMethod, (ULONG) method,
	XPK_Password, (ULONG) password,
	XPK_NoClobber, TRUE,
	TAG_DONE
#ifdef __MAXON__
      , 0 };
      if(XpkPack(t
#endif
      ))
      {
        ULONG i = strlen(errbuf);
        errbuf[i] = '\n'; errbuf[i+1] = '\0';
        return err = errbuf;
      }
    }
  }
  else
  {
    if(xfib.xf_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;
    }

    {
#ifdef __MAXON__			/* Maxon has no tagcall pragma now */
      struct TagItem t[] = {		/* and I don't want to use a link */
#else					/* library */
      if(XpkUnpackTags(
#endif
	XPK_InName, (ULONG) filename,
	XPK_FileName, (ULONG) filename,
	XPK_OutName, (ULONG) namebuf,
	XPK_ChunkHook, (ULONG) &chunkhook,
	XPK_Password, (ULONG) password,
	XPK_GetError, (ULONG) errbuf,
	XPK_NoClobber, TRUE,
	TAG_DONE
#ifdef __MAXON__
      , 0 };
      if(XpkUnpack(t
#endif
      ))
      {
        ULONG i = strlen(errbuf);
        errbuf[i] = '\n'; errbuf[i+1] = '\0';
        return err = errbuf;
      }
    }
  }

  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";
  }
}

ULONG __asm __saveds chunkfunc(register __a1 struct XpkProgress *prog)
{
  ULONG out = Output();
  UBYTE buf[180];

  if(prog->xp_Type == XPKPROG_START)
    Write(out, "\033[0 p", 5);

  if(prog->xp_Type != XPKPROG_END)
    sprintf(buf,
	     "%4s: %-8s (%3ld%% done, %2ld%% CF, %6ld cps) %s\033[K\r",
	     prog->xp_PackerName, prog->xp_Activity, prog->xp_Done,
	     prog->xp_CF, prog->xp_Speed, prog->xp_FileName);
  else
    sprintf(buf,
	     "%4s: %-8s (%3ldK, %2ld%% CF, %6ld cps) %s\033[K\n",
	     prog->xp_PackerName, prog->xp_Activity, prog->xp_ULen / 1024,
	     prog->xp_CF, prog->xp_Speed, prog->xp_FileName);

  iprint(buf);

  if(prog->xp_Type == XPKPROG_END)
    Write(out, "\033[1 p", 5);

  return SetSignal(0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C;
}

STRPTR tempname(STRPTR 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;
}

LONG isexecutable(STRPTR name)
{
  ULONG fh;
  BPTR buf[5];
  UBYTE msg[100];
  LONG 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;
}

STRPTR basename(STRPTR name)
{
  STRPTR ret = name;

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

void end(STRPTR text)
{
  if(text)	Write(Output(), text, strlen(text));
  if(XpkBase)	CloseLibrary(XpkBase);
  if(fib)	FreeMem(fib, sizeof(struct FileInfoBlock));

  exit(text ? 10 : 0);
}
