#define NAME         "exe2arc"
#define DISTRIBUTION "(Freeware) "
#define REVISION     "5"
#define DATE	     "24.04.2002"

/* Programmheader

	Name:		exe2arc
	Author:		SDI
	Distribution:	Freeware
	Description:	strips executable header from exe files
	Compileropts:	-
	Linkeropts:	-gsi -l amiga

 1.0   02.11.99 : first version
 1.1   06.11.99 : now also supports zip exes with wrong offsets
 1.2   29.05.00 : renamed from exe2zip, added Ace, Rar, Cab, Arj, LhA
 1.3   07.07.00 : added TYPE keyword to specify archive type
 1.4   15.09.01 : supports directories as destination as well
 1.5   24.04.02 : fixed Enforcer hit
*/

#include <proto/exec.h>
#include <proto/dos.h>
#include <exec/memory.h>
#include "SDI_version.h"
#define SDI_TO_ANSI
#include "SDI_ASM_STD_protos.h"

struct DosLibrary *	 DOSBase = 0;
struct ExecBase *	 SysBase  = 0;

#define PARAM	"FROM/A,TO,TYPE/K"

struct Args {
  STRPTR   from;
  STRPTR   to;
  STRPTR   type;
};

#define BUFSIZE 102400

#define EndConvI32(a)	(((a)>>24)|(((a)>>8)&0xFF00)|(((a)<<8)&0xFF0000)|((a)<<24))
#define EndConvI16(a)	((UWORD)(((a)>>8)|((a)<<8)))

typedef BOOL (* SCANFUNC)(BPTR infh, STRPTR buffer, ULONG filesize, ULONG buffersize);
typedef ULONG (* EXTRACTFUNC)(BPTR infh, BPTR outfh, STRPTR buffer, ULONG filesize, ULONG buffersize);

struct ScanData {
  SCANFUNC	ScanFunc;
  EXTRACTFUNC	ExtractFunc;
  STRPTR	Extension; /* and type specifier */
  STRPTR	Name; /* and type specifier */
};

/* a) It is not the very best method to do scan loop again and again, but easy
      to implement :-)
   b) The buffer can be used to pass data from scanner to extractor.
*/

struct ScanData ScanFuncs[]; /* declaration, real field see file end */

ULONG start(void)
{
  ULONG ret = RETURN_FAIL;
  struct DosLibrary *dosbase;

  SysBase = (*((struct ExecBase **) 4));
  { /* test for WB and reply startup-message */
    struct Process *task;
    if(!(task = (struct Process *) FindTask(0))->pr_CLI)
    {
      WaitPort(&task->pr_MsgPort);
      Forbid();
      ReplyMsg(GetMsg(&task->pr_MsgPort));
      return RETURN_FAIL;
    }
  }

  if((dosbase = (struct DosLibrary *) OpenLibrary("dos.library", 37)))
  {
    struct Args args;
    struct RDArgs *rda;

    DOSBase = dosbase;

    args.to = args.type = 0;
    if((rda = ReadArgs(PARAM, (LONG *) &args, 0)))
    {
      BPTR infh;

      if((infh = Open(args.from, MODE_OLDFILE)))
      {
        struct FileInfoBlock *fib;

        if((fib = (struct FileInfoBlock *) AllocDosObject(DOS_FIB, 0)))
        {
          if(ExamineFH(infh, fib))
          {
            STRPTR buf, name = args.to;

            if((buf = AllocMem(BUFSIZE+1024, MEMF_ANY)))
            {
	      ULONG i, j;
	      LONG stop = 0, found = 0;

	      for(i = 0; !stop && ScanFuncs[i].ScanFunc; ++i)
	      {
		if(args.type && stricmp(ScanFuncs[i].Extension, args.type) && stricmp(ScanFuncs[i].Name, args.type))
		  ; /* skip the entry */
                else if(Seek(infh, 0, OFFSET_BEGINNING) >= 0)
                {
		  if(args.type)
		    found = 1;
                  Printf("Scanning for %s-Archive.", ScanFuncs[i].Name);
                  Flush(Output());
                  if(ScanFuncs[i].ScanFunc(infh, buf, fib->fib_Size, BUFSIZE))
                  {
                    BPTR outfh;
                    STRPTR tmp = buf+BUFSIZE;
                    ULONG k;

                    if(!args.to) name = tmp;
                    for(j = 0; args.from[j]; ++j)
                      tmp[j] = args.from[j];
                    k = j;

		    do
		    {
		      if(tmp[--j] == '.')
		        k = j;
		    } while(j && tmp[j] != '.' && tmp[j] != '/' && tmp[j] != ':');
		    tmp[k++] = '.';
		    for(j = 0; ScanFuncs[i].Extension[j]; ++j)
		      tmp[k++] = ScanFuncs[i].Extension[j];
		    tmp[k] = 0;
                    stop = 1;
		    Printf(" Found.\n");
                    if(!(outfh = Open(name, MODE_NEWFILE)) && args.to)
                    {
                      name = buf+BUFSIZE+512;
                      *name = 0;
                      AddPart(name, args.to, 512);
                      tmp = buf+BUFSIZE; k = 0;
                      for(j = 0; tmp[j]; ++j)
                      {
                        if(tmp[j] == '/' || tmp[j] == ':')
                          k = j+1;
                      }
                      AddPart(name, tmp+k, 512);
                      outfh = Open(name, MODE_NEWFILE);
                    }
                    if(outfh)
                    {
                      if((j = ScanFuncs[i].ExtractFunc(infh, outfh, buf, fib->fib_Size, BUFSIZE)))
                      {
                        Printf("Saved %ld byte to %s\nBe careful and test the file for correctness.\n", j, name);
                        ret = 0;
                      }
                      Close(outfh);
                      if(ret)
                        DeleteFile(name);
                    }
                    else
                      Printf("\nFailed to open output file.\n");
                  }
                  else
                  {
                    Printf("\r\033[K");
                    Flush(Output());
                  }
                }
                else
                {
                  stop = 1;
                  Printf("Failed to seek to file start.\n");
                }
              }
              if(!stop)
              {
                if(args.type)
                {
                  Printf("\r\033[KDid not find archive of type '%s'.\n", args.type);
                  if(found)
                    Printf("Maybe it is an archive of another type?\n");
                  Printf("Valid types are: ");
                  for(i = 0; !stop && ScanFuncs[i].ScanFunc; ++i)
                    Printf("%s (%s)%s", ScanFuncs[i].Extension, ScanFuncs[i].Name, ScanFuncs[i+1].ScanFunc ? ", " : "\n");
                }
                else
                  Printf("\r\033[KDid not find archive data.\n");
              }
              FreeMem(buf, BUFSIZE+1024);
            }
            else
              Printf("Failed to open temporary buffer.\n");
          }
          else
            Printf("Failed to examine file.\n");
          FreeDosObject(DOS_FIB, 0);
        }
        else
          Printf("Failed to open file information object.\n");
        Close(infh);
      }
      else
        Printf("Failed to open input file.\n");
      FreeArgs(rda);
    }
    else
      PrintFault(IoErr(), 0);
    CloseLibrary((struct Library *) dosbase);
  } /* OpenLibrary dos */
  return ret;
}

ULONG DoCopy(BPTR infh, BPTR outfh, STRPTR buf, ULONG size, ULONG buffersize)
{
  ULONG s, err = 0;
  
  while(size && !err)
  {
    s = size;
    if(s > buffersize)
      s = buffersize;
    if(Read(infh, buf, s) != s)
      err = RETURN_FAIL;
    else if(Write(outfh, buf, s) != s)
      err = RETURN_FAIL;
    size -= s;
  }
  
  return err;
}

/******** exe2zip *********/

BOOL ScanZIP(BPTR fh, STRPTR buf, ULONG size, ULONG bufsize)
{
  ULONG start = 0, corr = 0;
  LONG stop = 0, i, j, k;

  if(Seek(fh, 0, OFFSET_END) >= 0)
  {
    while(size > 22 && !stop)
    {
      if(bufsize > size)
        bufsize = size;
      if(Seek(fh, size-bufsize, OFFSET_BEGINNING) < 0)
        ++stop;
      else if(Read(fh, buf, bufsize) != bufsize)
        ++stop;
      for(i = bufsize-22; i >= 0 && !stop; --i)
      {
        if(buf[i] == 'P' && buf[i+1] == 'K' && buf[i+2] == 5 && buf[i+3] == 6)
        {
          j = (((((buf[i+19]<<8)+buf[i+18])<<8)+buf[i+17])<<8)+buf[i+16];
          k = (((((buf[i+15]<<8)+buf[i+14])<<8)+buf[i+13])<<8)+buf[i+12];
          if(j != size-bufsize+i-k)
	  {
	    corr = (size-bufsize+i-k)-j;
	    j += corr;
	  }
          if(Seek(fh, j+4, OFFSET_BEGINNING) >= 0)
          {
            if(Read(fh, buf, 42) == 42)
              start = ((buf[38]) | (buf[39]<<8) | (buf[40]<<16) | (buf[41]<<24)) + corr;
          }
          ++stop;
        }
      }
      size -= (bufsize-21);
    }
  }

  if(start)
    Seek(fh, start, OFFSET_BEGINNING);
  ((ULONG *)buf)[0] = corr; /* store this for extract */
  ((ULONG *)buf)[1] = start;

  return (BOOL) (start ? TRUE : FALSE);
}

ULONG ExtractZIP(BPTR infh, BPTR outfh, STRPTR buf, ULONG filesize, ULONG buffersize)
{
  LONG start, corr, Type = 0, ret = 0, i;

  start = ((ULONG *)buf)[1];
  corr = ((ULONG *)buf)[0]-start;

  while(Type != 0x504B0506 && !ret && !(SetSignal(0L,0L) & SIGBREAKF_CTRL_C))
  {
    if(Read(infh, &Type, 4) == 4)
    {
      ret = RETURN_FAIL;
      switch(Type)
      {
      case 0x504B0304: /* local */
        if(Read(infh, buf+4, 26) == 26)
        {
          buf[0] = 'P'; buf[1] = 'K'; buf[2] = 3; buf[3] = 4;
          if(Write(outfh, buf, 30) == 30)
          {
            ret = DoCopy(infh, outfh, buf, ((buf[18]) | (buf[19]<<8) | (buf[20]<<16) | (buf[21]<<24)) +
            ((buf[26]) | (buf[27]<<8)) + ((buf[28]) | (buf[29]<<8)), buffersize);
          }
        }
        break;
      case 0x504B0102: /* central */
        if(Read(infh, buf+4, 42) == 42)
        {
          buf[0] = 'P'; buf[1] = 'K'; buf[2] = 1; buf[3] = 2;
          i = ((buf[42]) | (buf[43]<<8) | (buf[44]<<16) | (buf[45]<<24)) + corr;
	  buf[42] = i;
	  buf[43] = i>>8;
	  buf[44] = i>>16;
	  buf[45] = i>>24;
          if(Write(outfh, buf, 46) == 46)
          {
            ret = DoCopy(infh, outfh, buf, ((buf[28]) | (buf[29]<<8)) +
            ((buf[30]) | (buf[31]<<8)) + ((buf[32]) | (buf[33]<<8)), buffersize);
          }
        }
        break;
      case 0x504B0506: /* end */
        if(Read(infh, buf+4, 18) == 18)
        {
          buf[0] = 'P'; buf[1] = 'K'; buf[2] = 5; buf[3] = 6;
          i = ((buf[16]) | (buf[17]<<8) | (buf[18]<<16) | (buf[19]<<24)) + corr;
	  buf[16] = i;
	  buf[17] = i>>8;
	  buf[18] = i>>16;
	  buf[19] = i>>24;
          if(Write(outfh, buf, 22) == 22)
            ret = DoCopy(infh, outfh, buf, buf[20] | (buf[21]<<8), buffersize); /* copy comment */
        }
        break;
      default:
	Printf("Unknown or illegal data found.\n");
	break;
      }
    }
    else
      Printf("Unexpected end of data.\n");
  }
  if(SetSignal(0L,0L) & SIGBREAKF_CTRL_C)
    SetIoErr(ERROR_BREAK);

  return ret ? 0 : filesize-start;
}

/******** exe2ace *********/

BOOL ScanACE(BPTR fh, STRPTR buf, ULONG size, ULONG bufsize)
{
  ULONG start = 0;
  LONG i, pos = 0, stop = 0;

  while(size > 14 && !stop)
  {
    if(bufsize > size)
      bufsize = size;
    if(Seek(fh, pos, OFFSET_BEGINNING) < 0)
      ++stop;
    else if(Read(fh, buf, bufsize) != bufsize)
      ++stop;
    for(i = 0; i <= bufsize-14 && !stop; ++i)
    {
      if(buf[i+7] == '*' && buf[i+8] == '*' && buf[i+9] == 'A' && buf[i+10] == 'C' &&
      buf[i+11] == 'E' && buf[i+12] == '*' && buf[i+13] == '*')
      {
        if(Seek(fh, -i, OFFSET_CURRENT) >= 0)
          start = pos+i;
        ++stop;
      }
    }
    size -= i;
    pos += i;
  }

  if(start)
    Seek(fh, start, OFFSET_BEGINNING);
  ((ULONG *)buf)[0] = start;

  return (BOOL) (start ? TRUE : FALSE);
}

ULONG ExtractACE(BPTR infh, BPTR outfh, STRPTR buf, ULONG filesize, ULONG buffersize)
{
  LONG ret;

  filesize -= ((ULONG *)buf)[0];
  ret = DoCopy(infh, outfh, buf, filesize, buffersize);
  
  return ret ? 0 : filesize;
}

/******** exe2rar *********/

BOOL ScanRAR(BPTR fh, STRPTR buf, ULONG size, ULONG bufsize)
{
  ULONG start = 0;
  LONG i, pos = 0, stop = 0;

  while(size > 7 && !stop)
  {
    if(bufsize > size)
      bufsize = size;
    if(Seek(fh, pos, OFFSET_BEGINNING) < 0)
      ++stop;
    else if(Read(fh, buf, bufsize) != bufsize)
      ++stop;
    for(i = 0; i <= bufsize-7 && !stop; ++i)
    {
      if(buf[i] == 'R' && buf[i+1] == 'a' && buf[i+2] == 'r' && buf[i+3] == '!' &&
      buf[i+4] == 0x1A && buf[i+5] == 7 && buf[i+6] == 0)
      {
        if(Seek(fh, -i, OFFSET_CURRENT) >= 0)
          start = pos+i;
        ++stop;
      }
    }
    size -= i;
    pos += i;
  }

  if(start)
    Seek(fh, start, OFFSET_BEGINNING);
  ((ULONG *)buf)[0] = start;

  return (BOOL) (start ? TRUE : FALSE);
}

/******** exe2cab *********/

BOOL ScanCAB(BPTR fh, STRPTR buf, ULONG size, ULONG bufsize)
{
  ULONG start = 0, len = 0, foff;
  LONG i, pos = 0, stop = 0;

  while(size > 20 && !stop)
  {
    if(bufsize > size)
      bufsize = size;
    if(Seek(fh, pos, OFFSET_BEGINNING) < 0)
      ++stop;
    else if(Read(fh, buf, bufsize) != bufsize)
      ++stop;
    for(i = 0; i <= bufsize-20 && !stop; ++i)
    {
      if(buf[i] == 'M' && buf[i+1] == 'S' && buf[i+2] == 'C' && buf[i+3] == 'F')
      {
        len = (buf[i+8]) | (buf[i+9]<<8) | (buf[i+10]<<16) | (buf[i+11]<<24);
        foff = (buf[i+16]) | (buf[i+17]<<8) | (buf[i+18]<<16) | (buf[i+19]<<24);
        if(len <= size-i && foff < len)
        {
          if(Seek(fh, -i, OFFSET_CURRENT) >= 0)
            start = pos+i;
          ++stop;
        }
      }
    }
    size -= i;
    pos += i;
  }

  if(start)
    Seek(fh, start, OFFSET_BEGINNING);
  ((ULONG *)buf)[0] = len;

  return (BOOL) (start ? TRUE : FALSE);
}

ULONG ExtractCAB(BPTR infh, BPTR outfh, STRPTR buf, ULONG filesize, ULONG buffersize)
{
  LONG ret;

  ret = DoCopy(infh, outfh, buf, (filesize = ((ULONG *)buf)[0]), buffersize);
  
  return ret ? 0 : filesize;
}

/******** exe2arj *********/

BOOL ScanARJ(BPTR fh, STRPTR buf, ULONG size, ULONG bufsize)
{
  ULONG start = 0, len, cbuf[256], j, k, l;
  LONG i, pos = 0, stop = 0;
  STRPTR mem;

  while(size > 50 && !stop)
  {
    if(bufsize > size)
      bufsize = size;
    if(Seek(fh, pos, OFFSET_BEGINNING) < 0)
      ++stop;
    else if(Read(fh, buf, bufsize) != bufsize)
      ++stop;
    for(i = 0; i <= bufsize-50 && !stop; ++i)
    {
      if(buf[i] == 0x60 && buf[i+1] == 0xEA)
      {
        len = (buf[i+2]) | (buf[i+3]<<8);
        if(size-i > len+4)
        {
          if(bufsize-i < len+4)
            break;
          else
          {
            for(l = 0; l < 256; ++l)
            {
              k = l;

              for(j = 0; j < 8; ++j)
              {
                if(k & 1)
                  k = (k >> 1) ^ 0xEDB88320;
                else
                  k >>= 1;
              }
              cbuf[l] = k;
            }
            l = ~0;
            k = len;
            mem = buf+i+4;

            while(k--)
              l = cbuf[(l ^ *mem++) & 0xFF] ^ (l >> 8);
            if(~l == ((mem[0]) | (mem[1]<<8) | (mem[2]<<16) | (mem[3]<<24)))
            {
              if(Seek(fh, -i, OFFSET_CURRENT) >= 0)
                start = pos+i;
              ++stop;
            }
          }
        }
      }
    }
    size -= i;
    pos += i;
  }

  if(start)
    Seek(fh, start, OFFSET_BEGINNING);
  ((ULONG *)buf)[0] = start;

  return (BOOL) (start ? TRUE : FALSE);
}

/******** exe2lha *********/

BOOL ScanLHA(BPTR fh, STRPTR buf, ULONG size, ULONG bufsize)
{
  ULONG start = 0;

  if(Read(fh, buf, 100) < 0)
    return 0;
  if(!buf[0] && !buf[1] && buf[2] == 3 && buf[3] == 0xF3 && buf[44] == 'S'
  && buf[45] == 'F' && buf[46] == 'X' && buf[47] == '!')
  {
    start = (buf[55]) | (buf[54]<<8) | (buf[53]<<16) | (buf[52]<<24);
    if(Seek(fh, start, OFFSET_BEGINNING) < 0)
      start = 0;
  }

  if(start)
    Seek(fh, start, OFFSET_BEGINNING);
  ((ULONG *)buf)[0] = start;

  return (BOOL) (start ? TRUE : FALSE);
}

/******** exe2lzh *********/

BOOL ScanLZH(BPTR fh, STRPTR buf, ULONG size, ULONG bufsize)
{
  ULONG start = 0;
  LONG i, pos = 0, stop = 0;

  while(size > 21 && !stop)
  {
    if(bufsize > size)
      bufsize = size;
    if(Seek(fh, pos, OFFSET_BEGINNING) < 0)
      ++stop;
    else if(Read(fh, buf, bufsize) != bufsize)
      ++stop;
    for(i = 0; i <= bufsize-21 && !stop; ++i)
    {
      if(buf[i+2] == '-' && buf[i+3] == 'l' && (buf[i+4] == 'h' || buf[i+4] == 'z') &&
      buf[i+6] == '-' && buf[20] <= 2)
      {
        if(Seek(fh, -i, OFFSET_CURRENT) >= 0)
          start = pos+i;
        ++stop;
      }
    }
    size -= i;
    pos += i;
  }

  if(start)
    Seek(fh, start, OFFSET_BEGINNING);
  ((ULONG *)buf)[0] = start;

  return (BOOL) (start ? TRUE : FALSE);
}

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

struct ScanData ScanFuncs[] = {
{ScanZIP, ExtractZIP, "zip", "Zip"},
{ScanACE, ExtractACE, "ace", "Ace"},
{ScanRAR, ExtractACE, "rar", "Rar"}, /* reuse ExtractACE */
{ScanCAB, ExtractCAB, "cab", "Cabinet"},
{ScanARJ, ExtractACE, "arj", "Arj"}, /* reuse ExtractACE */
{ScanLHA, ExtractACE, "lha", "LhA"}, /* reuse ExtractACE */
{ScanLZH, ExtractACE, "lzh", "Amiga-LhA"}, /* reuse ExtractACE */
{0,0},
};

