#ifndef XPKMASTER_OPEN_C
#define XPKMASTER_OPEN_C

/* Routinesheader

	Name:		open.c
	Main:		xpkmaster
	Versionstring:	$VER: open.c 1.1 (28.10.96)
	Author:		SDI
	Distribution:	PD
	Description:	Opening and initialisation routines for XPK files

 1.0   06.10.96 : first real version
 1.1   28.10.96 : reincluded A4 support
*/

#include <exec/types.h>
#include <dos/dos.h>
#include <pragma/exec_lib.h>
#include <pragma/dos_lib.h>
#include "xpkmaster.h"
#include "xpk_strings.h"

static LONG getinlen(struct XpkBuffer *xbuf);

#define ROW_OF_MINUS	0x2d2d2d2d	/* '----' */
#define PP_COOKIE	0x50503230	/* 'PP20' */

LONG __asm xpkopen(register __a0 struct XpkBuffer **xbufp,
register __a1 struct TagItem *tags, register __d2 ULONG examine A4PROTO)
{
  struct Library	 *XpkSubBase;
  struct XpkBuffer	 *xbuf;
  struct XpkStreamHeader *globhdr;
  struct XpkFib		 *fib;
  LONG res;
  LONG bytesread;	/* used to be able to reset after a short read */

#if defined(DEBUG) && defined(SUPPORT_A4)
  DebugRunTime("xpkopen: A4 = %ld", a4);
#elif defined (DEBUG)
  DebugRunTime("xpkopen");
#endif

  *xbufp = 0;
  if(!(xbuf = initxbuf()))
  {
    parseerrortags(tags);
    return XPKERR_NOMEM;
  }

#ifdef SUPPORT_A4
  xbuf->xb_regA4 = a4;
#endif

  globhdr = &xbuf->xb_Headers.h_Glob;
  fib = &xbuf->xb_Fib;

  if(parsebuftags(xbuf, tags, 0))
    goto Abort;

  *xbufp = xbuf;
  if(xbuf->xb_Flags & XMF_PACKING)
    return xpkopenwrite(xbufp, tags);

  if(!hookread(xbuf, XIO_READ, globhdr, 4))
  {	/* Read first longword only */
    bytesread = xbuf->xb_InLen = xbuf->xb_RMsg.xmm_Size;
    if(xbuf->xb_Result == XPKERR_TRUNCATED)
      goto Uncompressed;
    else
      goto Abort;
  }
  else
    bytesread = 4;

  /**************************** Standard XPK file *********************/
  if(globhdr->xsh_Pack == XPK_COOKIE)
  {	/* Standard XPK packed files */
    UWORD exthlen = 0;		/* size of extended header if present */

    xbuf->xb_Format = XPKMODE_UPSTD;

    /* Read rest of the global header */
    if(!hookread(xbuf, XIO_READ, (STRPTR) globhdr + 4, sizeof(struct XpkStreamHeader) - 4))
      goto Abort;

    if(hchecksum((STRPTR) globhdr, sizeof(struct XpkStreamHeader)))
    {
      xbuf->xb_Result = XPKERR_CHECKSUM;
      goto Abort;
    }

    if(!examine && globhdr->xsh_Flags & XPKSTREAMF_PASSWORD &&
    !xbuf->xb_Password)
    {
      xbuf->xb_Result = XPKERR_NEEDPASSWD;
      goto Abort;
    }

    if(globhdr->xsh_Flags & XPKSTREAMF_LONGHEADERS)
      xbuf->xb_Headers.h_LocSize = sizeof(struct XpkChunkHdrLong);
    else
      xbuf->xb_Headers.h_LocSize = sizeof(struct XpkChunkHdrWord);

    if(globhdr->xsh_Flags & XPKSTREAMF_EXTHEADER)
    {
      if(!hookread(xbuf, XIO_READ, &exthlen, sizeof(UWORD)))
	goto Abort;
      if(!hookread(xbuf, XIO_READ, NULL, exthlen))
	goto Abort;
      exthlen += sizeof(UWORD);	/* for unwinding while XpkExamine */
    }

    if(!hookread(xbuf, XIO_READ, &xbuf->xb_Headers.h_Loc,
    xbuf->xb_Headers.h_LocSize))	/* first lochdr */
      goto Abort;

    xbuf->xb_Fib.xf_CCur = sizeof(struct XpkStreamHeader);
    updatefib(xbuf);

    if(!(xbuf->xb_SubBase = XpkSubBase = opensub(xbuf, globhdr->xsh_Type)))
      goto Abort;

    if(globhdr->xsh_SubVrs > xbuf->xb_SubInfo->xi_LibVersion)
    {
      xbuf->xb_Result = XPKERR_OLDSUBLIB;
      goto Abort;
    }

    xbuf->xb_ULen = globhdr->xsh_ULen;
    xbuf->xb_Prog.xp_Activity = xbuf->xb_SubInfo->xi_UnpackMsg ?
      xbuf->xb_SubInfo->xi_UnpackMsg : strings[TXT_UNPACKING_UPPER];
    xbuf->xb_Prog.xp_PackerName = xbuf->xb_SubInfo->xi_Name;
    xbuf->xb_LastMsg = xbuf->xb_SubInfo->xi_UnpackedMsg ?
      xbuf->xb_SubInfo->xi_UnpackedMsg : strings[TXT_UNPACKED];

    if(globhdr->xsh_Flags & XPKSTREAMF_PASSWORD)
      xbuf->xb_Fib.xf_Flags |= XPKFLAGS_PASSWORD;

    if(examine && !hookread(xbuf, XIO_SEEK, 0,
    -(sizeof(struct XpkStreamHeader) + xbuf->xb_Headers.h_LocSize+exthlen)))
      goto Abort;

    goto Exit;
  }

  if(xbuf->xb_InLen == -1)
  {
    if((res = getinlen(xbuf)))
    {
      xbuf->xb_Result = res;
      goto Abort;
    }
  }

#ifdef USE_POWERPACKER
  /***************************** PowerPacker file ***********************/
  if(globhdr->xsh_Pack == PP_COOKIE)
  {
    struct Library *PPBase;
    LONG outsize;

    xbuf->xb_Format = XPKMODE_UPPP;

    if(!hookread(xbuf, XIO_SEEK, 0, xbuf->xb_InLen - 8))
      goto Abort;					/* 4 Bytes before EOF */
    if(!hookread(xbuf, XIO_READ, &outsize, 4))
      goto Abort;
    if(!hookread(xbuf, XIO_SEEK, 0, - (xbuf->xb_InLen - 4 + (examine << 2))))
      goto Abort;

    outsize >>= 8;

    fib->xf_Type = XPKTYPE_PACKED;
    fib->xf_CLen = xbuf->xb_InLen;
    fib->xf_ULen = outsize;
    fib->xf_NLen = outsize + XPK_MARGIN;
    fib->xf_ID = PP_COOKIE;
    percentages (fib);

    if(examine)
      goto Exit;

    xbuf->xb_Prog.xp_Activity = strings[TXT_UNPACKING_UPPER];
    xbuf->xb_Prog.xp_PackerName = "PowerPacker";
    xbuf->xb_LastMsg = strings[TXT_UNPACKED];
    xbuf->xb_ULen = outsize;

    if(!(PPBase = OpenLibrary ("powerpacker.library", 0)))
    {
      xbuf->xb_Result = XPKERR_MISSINGLIB;
      goto Abort;
    }
    xbuf->xb_SubBase = PPBase;

    goto Exit;
  }
#endif /* USE_POWERPACKER */

/* Future Code
  if((WORD) globhdr->xsh_Pack == 0x1F9D)
  {	// UNIX compress
  }
*/

  /**************************** Uncompressed file *************************/
Uncompressed:
  if(examine || xbuf->xb_Flags & XMF_PASSTHRU)		/* Unpacked */
  {
    xbuf->xb_Format = XPKMODE_UPUP;

    fib->xf_Type = XPKTYPE_UNPACKED;
    fib->xf_CLen = xbuf->xb_InLen;
    fib->xf_ULen = xbuf->xb_InLen;
    fib->xf_NLen = Min(CHUNKSIZE, xbuf->xb_InLen) + XPK_MARGIN;
    fib->xf_ID = ROW_OF_MINUS;

    xbuf->xb_Prog.xp_Activity = strings[TXT_READING];
    xbuf->xb_Prog.xp_PackerName = "MASTER";
    xbuf->xb_LastMsg = strings[TXT_READ];
    xbuf->xb_ULen = xbuf->xb_InLen;

    xbuf->xb_Result = XPKERR_OK;	/* if != 0 is was XPKERR_TRUNCATED */
    if(!hookread(xbuf, XIO_SEEK, 0, -bytesread))
      goto Abort;

    goto Exit;
  }

  xbuf->xb_Result = XPKERR_NOTPACKED;	/* Can't unpack, can't passthru */

Abort:
  *xbufp = 0;
  return XpkClose(xbuf);

Exit:
  *xbufp = xbuf;
  return XPKERR_OK;
}

/****************************** Open for packing **************************/
LONG xpkopenwrite(struct XpkBuffer **xbufp, struct TagItem *tags)
{
  struct XpkBuffer		*xbuf		= *xbufp;
  struct XpkStreamHeader	*globhdr	= &xbuf->xb_Headers.h_Glob;
  struct Library		*XpkSubBase;
  LONG				 res;

  xbuf->xb_Format = XPKMODE_PKSTD;

  if(getinlen(xbuf))		/* Find InLen */
  {
    xbuf->xb_Result = XPKERR_BADPARAMS;
    goto Abort;
  }

  if(!(XpkSubBase = xbuf->xb_SubBase))	/* Do we know the sublib? */
  {
    xbuf->xb_Result = XPKERR_BADPARAMS;
    goto Abort;
  }

  xbuf->xb_MinChunk = xbuf->xb_SubInfo->xi_MinPkInChunk;

  if(xbuf->xb_Password && !(xbuf->xb_SubInfo->xi_Flags & XPKIF_ENCRYPTION))
  {
    xbuf->xb_Result = XPKERR_NOCRYPT;
    goto Abort;
  }

  if(!xbuf->xb_Password && xbuf->xb_SubInfo->xi_Flags & XPKIF_NEEDPASSWD)
  {
    xbuf->xb_Result = XPKERR_NEEDPASSWD;
    goto Abort;
  }

  if(!(xbuf->xb_Flags & XMF_LOSSYOK) &&
  xbuf->xb_SubInfo->xi_Flags & XPKIF_LOSSY)
  {
    xbuf->xb_Result = XPKERR_LOSSY;
    goto Abort;
  }

  if(xbuf->xb_PackingMode > 100)	/* Is packing mode valid? */
    xbuf->xb_PackingMode = 100;		/* Use max */

  if(!hookwrite(xbuf, XIO_TOTSIZE, 0,
  (xbuf->xb_InLen + xbuf->xb_InLen / 32 & ~3) + 2 * XPK_MARGIN))
    goto Abort;

  /*********************** Find the chunk size *********************/
  if((xbuf->xb_ChunkSize == 0) &&
  ((xbuf->xb_ChunkSize = xbuf->xb_SubInfo->xi_DefPkInChunk) == 0))
    xbuf->xb_ChunkSize = DEFAULTCHUNKSIZE;
  if(xbuf->xb_ChunkSize < xbuf->xb_SubInfo->xi_MinPkInChunk)
    xbuf->xb_ChunkSize = xbuf->xb_SubInfo->xi_MinPkInChunk;
  if((xbuf->xb_SubInfo->xi_MaxPkInChunk) &&
  (xbuf->xb_ChunkSize > xbuf->xb_SubInfo->xi_MaxPkInChunk))
    xbuf->xb_ChunkSize = xbuf->xb_SubInfo->xi_MaxPkInChunk;

  /******************* Prepare global header *********************/
  globhdr->xsh_Pack = 0;		/* Initialize the global header */
  globhdr->xsh_Type = xbuf->xb_SubID;

  if(xbuf->xb_ChunkSize > 65000)
    globhdr->xsh_Flags |= XPKSTREAMF_LONGHEADERS;
  if(xbuf->xb_Password)
    globhdr->xsh_Flags |= XPKSTREAMF_PASSWORD;

  xbuf->xb_Headers.h_LocSize = globhdr->xsh_Flags & XPKSTREAMF_LONGHEADERS
    ? sizeof (struct XpkChunkHdrLong)
    : sizeof (struct XpkChunkHdrWord);

  memset(globhdr->xsh_Initial, 0xff, 16);	/* Read first 16 bytes */

  xbuf->xb_Prog.xp_Activity = xbuf->xb_SubInfo->xi_PackMsg ?
  xbuf->xb_SubInfo->xi_PackMsg : strings[TXT_PACKING_UPPER];
  xbuf->xb_Prog.xp_PackerName = xbuf->xb_SubInfo->xi_Name;
  xbuf->xb_LastMsg = xbuf->xb_SubInfo->xi_PackedMsg ?
  xbuf->xb_SubInfo->xi_PackedMsg : strings[TXT_PACKED];

Abort:
  xbuf->xb_Fib.xf_NLen = Min(xbuf->xb_InLen - xbuf->xb_Fib.xf_UCur,
  xbuf->xb_ChunkSize);

  if((res = xbuf->xb_Result))
    res = XpkClose(xbuf);

  return res;
}

static LONG getinlen(struct XpkBuffer *xbuf)
{
  if(xbuf->xb_InLen == -1)
  {
    if(xbuf->xb_RHook == &fhinhook)
    {	/* For files, find length from here to end */
      LONG opos;

      if((opos = Seek(xbuf->xb_RMsg.xmm_FH, 0, OFFSET_END)) < 0 ||
      (xbuf->xb_InLen=Seek(xbuf->xb_RMsg.xmm_FH, opos,OFFSET_BEGINNING)) <0)
	return XPKERR_IOERRIN;
    }
    else if(xbuf->xb_RHook == &meminhook) /* InLen always required for input from memory */
      return XPKERR_BADPARAMS;
    /* For unpacking from custom hooks, InLen is only needed for PowerPacker */
  }
  return 0;
}

#endif /* XPKMASTER_OPEN_C */
