/*
   cole - A free C OLE library.
   Copyright 1998, 1999  Roberto Arturo Tena Sanchez
   Copyright 2002, 2007  Jaroslav Fojtik

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
   by the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program; if not, see http://www.gnu.org/copyleft/lesser.html
   or write to the Free Software Foundation, Inc., 59 Temple Place, 
   Suite 330, Boston, MA  02111-1307  USA
 
   Modified by: Arturo Tena <arturo@directmail.org> - cole library
	        Jaroslav Fojtik <fojtik@penguin.cz> - cole part of wp2latex
 */
#ifdef FINAL
 #ifndef NDEBUG
  #define NDEBUG
 #endif
#endif

#include <stdlib.h>
#include <string.h>
#include "cole.h"
#include <assert.h>



#define ENTRYCHUNK 20	/* number of entries in root_list and sbd_list
			   will be added each time. must be at least 1 */
#define MIN(a,b) ((a)<(b) ? (a) : (b))


typedef struct
  {
  FILE *input;
  U8 *Block;
  U8 *Blockx;
  U8 *BDepot, *SDepot, *Root;
  pps_entry *pps_list;
  U32 num_of_pps;
  FILE *sbfile;
  /* sbfilename is stored in *_sbfilename instead -- cole 2.0.0 */
  /* static char sbfilename[L_tmpnam]; */
  U32 *sbd_list;
  U32 *root_list;

  U32 *last_next_link_visited;
  } ole_internal;


/* reorder pps tree, from tree structure to a linear one,
   and write the level numbers, returns zero if OLE format fails,
   returns no zero if success */
/* reorder pps tree and write levels */
/* not sure if it is safe declare last_next_link_visited
   inside reorder_pps_tree function */

static int reorder_pps_tree (pps_entry * node, U16 level,ole_internal *o)
{
  /* NOTE: in next, previous and dir link,
     0xffffffff means point to nowhere (NULL) */

  node->level = level;

  /* reorder subtrees, if there's any */
  if (node->dir != 0xffffffffUL)
  {
    if (node->dir > o->num_of_pps || !o->pps_list[node->dir].name[0])
      return 0;
    else if (!reorder_pps_tree (&o->pps_list[node->dir], (U16)(level + 1),o))
      return 0;
  }

  /* reorder next-link subtree, saving the most next link visited */
  if (node->next != 0xffffffffUL)
    {
      if (node->next > o->num_of_pps || !o->pps_list[node->next].name[0])
	return 0;
      else if (!reorder_pps_tree (&o->pps_list[node->next], level,o))
	return 0;
    }
  else
    o->last_next_link_visited = &node->next;

  /* move the prev child to the next link and reorder it, if there's any */
  if (node->previous != 0xffffffffUL)
  {
    if (node->previous > o->num_of_pps || !o->pps_list[node->previous].name[0])
		return 0;
    else
      {
	*(o->last_next_link_visited) = node->previous;
	node->previous = 0xffffffffUL;
	if (!reorder_pps_tree (&o->pps_list[*(o->last_next_link_visited)], level,o))
		return 0;
      }
  }
  return 1;
}


/* free memory used (except the pps tree) */
#define freeNoNULL(x) { if ((x) != NULL) free (x); }
static void ends(ole_internal *o)
{
  /* if (input != NULL) and next lines --- commented out so conservate input
     file open --- cole 2.0.0 */
  /*
  if (input != NULL)
    fclose (input);
  */
  freeNoNULL (o->Block);
  freeNoNULL (o->Blockx);
  /* freeNoNULL (BDepot) and next line --- commented out so conservate
     depots --- cole 2.0.0 */
  /*
    freeNoNULL (BDepot);
    freeNoNULL (SDepot);
  */
  freeNoNULL (o->Root);
  freeNoNULL (o->sbd_list);
  freeNoNULL (o->root_list);
  /* if (sbfile != NULL) and next lines --- commented out so conservate sbfile
     open --- cole 2.0.0 */
  /* 
  if (sbfile != NULL)
    {
      fclose (sbfile);
      if (!remove (sbfilename)) ;
    }
   */
}


int cpp_OLEdecode (char *OLEfilename, COLEFS *ole, U16 max_level)
{
  ole_internal o;
  int c;
  U32 num_bbd_blocks;
  U32 num_xbbd_blocks;
  U32 bl;
  U32 i, j, len;
  U8 *s, *p, *t;
  long FilePos;
  /* FilePos is long, not U32, because second argument of fseek is long */

  /* initialize static variables */
  o.input = o.sbfile = NULL;
  o.Block = o.Blockx = o.BDepot = o.SDepot = o.Root = NULL;
  o.pps_list = NULL;
  o.num_of_pps = 0;
/* sbfilename is stored in *_sbfilename instead -- cole 2.0.0 */
/*  sbfilename[0] = 0; */
  o.root_list = o.sbd_list = NULL;
  /* initalize return parameters */
  ole->tree = NULL;

  /* open input file */
  verbose ("open input file");
  if(OLEfilename!=NULL)
      {
      o.input = fopen (OLEfilename, "rb");
      ole->file = o.input;
      }
  else o.input=ole->file;
  test_exitf (o.input != NULL, 4, ends(&o));

  /* fast check type of file */
  verbose ("fast testing type of file");
  test_exitf ((c = getc (o.input)) != EOF, 5, ends(&o));  
  test_exitf (ungetc (c, o.input) != EOF, 5, ends(&o));
  test_exitf (c == 0xD0, 9, ends(&o));

  /* read header block */
  verbose ("read header block");
  o.Block = (U8 *) malloc (0x0200);
  test_exitf (o.Block != NULL, 10, ends(&o));
  fread (o.Block, 0x0200, 1, o.input);
  test_exitf (!ferror (o.input), 5, ends(&o));

  /* really check type of file */
  rewind (o.input);
  verbose ("testing type of file");
  test_exitf (fil_sreadU32 (o.Block) != 0xd0cf11e0UL, 9, ends(&o));
  test_exitf (fil_sreadU32 (o.Block + 0x04) != 0xa1b11ae1UL, 9, ends(&o));


  /* read big block depot */
  verbose ("read big block depot (bbd)");
  num_bbd_blocks = fil_sreadU32 (o.Block + 0x2c);
  num_xbbd_blocks = fil_sreadU32 (o.Block + 0x48);
  verboseU32 (num_bbd_blocks);
  verboseU32 (num_xbbd_blocks);
  o.BDepot = (U8*)malloc (0x0200 * (num_bbd_blocks + num_xbbd_blocks));
  test_exitf (o.BDepot != NULL, 10, ends(&o));
  ole->BDepot = o.BDepot;
  s = o.BDepot;
  assert (num_bbd_blocks <=  (0x0200 / 4 - 1) * num_xbbd_blocks +
			     (0x0200 / 4) - 19);
  /* the first 19 U32 in header does not belong to bbd_list */
  for (i = 0; i < MIN (num_bbd_blocks, 0x0200 / 4 - 19); i++)
    {
      /* note: next line may be needed to be cast to long in right side */
      FilePos = 0x0200 * (1 + fil_sreadU32 (o.Block + 0x4c + (i * 4)));
      assert (FilePos >= 0);
      test_exitf (!fseek (o.input, FilePos, SEEK_SET), 5, ends(&o));
      fread (s, 0x0200, 1, o.input);
      test_exitf (!ferror (o.input), 5, ends(&o));
      s += 0x0200;
    }

  o.Blockx = (U8 *) malloc (0x0200);
  test_exitf (o.Blockx != NULL, 10, ends(&o));
  bl = fil_sreadU32 (o.Block + 0x44);
  for (i = 0; i < num_xbbd_blocks; i++)
  {
      FilePos = 0x0200 * (1 + bl);
      assert (FilePos >= 0);
      test_exitf (!fseek (o.input, FilePos, SEEK_SET), 5, ends(&o));
      fread (o.Blockx, 0x0200, 1, o.input);
      test_exitf (!ferror (o.input), 5, ends(&o));

    for (j=0; j < 0x0200 / 4 - 1;j++)
                             /* last U32 is for the next bl */
    {
      if (fil_sreadU32 (o.Blockx + (j * 4)) == 0xfffffffeUL ||
	  fil_sreadU32 (o.Blockx + (j * 4)) == 0xfffffffdUL ||
	  fil_sreadU32 (o.Blockx + (j * 4)) == 0xffffffffUL)
	break;
      /* note: next line may be needed to be cast to long in right side */
      FilePos = 0x0200 * (1 + fil_sreadU32 (o.Blockx + (j * 4)));
      assert (FilePos >= 0);
      test_exitf (!fseek (o.input, FilePos, SEEK_SET), 5, ends(&o));
      fread (s, 0x0200, 1, o.input);
      test_exitf (!ferror (o.input), 5, ends(&o));
      s += 0x0200;
    }

    bl = fil_sreadU32 (o.Blockx + 0x0200 - 4);
  }
  verboseU8Array (BDepot, (num_bbd_blocks+num_xbbd_blocks), 0x0200);


  /* extract the sbd block list */
  verbose ("extract small block depot (sbd) block list");
  o.sbd_list = (U32 *)malloc (ENTRYCHUNK * 4);
  test_exitf (o.sbd_list != NULL, 10, ends(&o));
  o.sbd_list[0] = fil_sreadU32 (o.Block + 0x3c);
  /* -2 signed long int == 0xfffffffe unsinged long int */
  for (len = 1; o.sbd_list[len - 1] != 0xfffffffeUL; len++)
    {
      test_exitf (len != 0, 5, ends(&o));	/* means file is too big */
      /* if memory allocated in sbd_list is all used, allocate more memory */
      if (!(len % ENTRYCHUNK))
	{
	  U32 *newspace;
	  newspace = (U32 *)realloc (o.sbd_list,
			      (1 + len/ENTRYCHUNK) * ENTRYCHUNK * 4);
	  test_exitf (newspace != NULL, 10, ends(&o));
	  o.sbd_list = newspace;
	}
      o.sbd_list[len] = fil_sreadU32 (o.BDepot + (o.sbd_list[len - 1] * 4));
      /*verboseU32 (len);*/
      /*verboseU32 (sbd_list[0]);*/
      /*verboseU32 (sbd_list[1]);*/
      if (o.sbd_list[len] != 0xfffffffeUL)
	test_exitf (o.sbd_list[len] <= num_bbd_blocks * 0x0200 - 4, 5, ends(&o));
      test_exitf (o.sbd_list[len] != 0xfffffffdUL &&
		  o.sbd_list[len] != 0xffffffffUL,
		  5, ends(&o));
    }
  len--;
  verboseU32Array (sbd_list, len+1);
  /* read in small block depot, if there's any small block */
  if (len == 0)
    {
      o.SDepot = NULL;
      verbose ("not read small block depot (sbd): there's no small blocks");
    }
  else
    {
      verbose ("read small block depot (sbd)");
      o.SDepot = (U8 *)malloc (0x0200*len);
      test_exitf (o.SDepot != NULL, 10, ends(&o));
      s = o.SDepot;
      for (i = 0; i < len; i++)
	{
	  FilePos = 0x0200 * (1 + o.sbd_list[i]);
	  assert (FilePos >= 0);
	  test_exitf (!fseek (o.input, FilePos, SEEK_SET), 5, ends(&o));
	  fread (s, 0x0200, 1, o.input);
	  test_exitf (!ferror (o.input), 5, ends(&o));
	  s += 0x200;
	}
      verboseU8Array (SDepot, len, 0x0200);
    }
  ole->SDepot = o.SDepot;


  /* extract the root block list */
  verbose ("extract root block depot (root) block list");
  o.root_list = (U32 *)malloc(ENTRYCHUNK*4);
  test_exitf (o.root_list != NULL, 10, ends(&o));
  o.root_list[0] = fil_sreadU32 (o.Block + 0x30);
  for (len = 1; o.root_list[len - 1] != 0xfffffffeUL; len++)
    {
      test_exitf (len != 0, 5, ends(&o));	/* means file is too long */
      /* if memory allocated in root_list is all used, allocate more memory */
      if (!(len % ENTRYCHUNK))
	{
	  U32 *newspace;
	  newspace = (U32 *)realloc (o.root_list,
			      (1 + len / ENTRYCHUNK) * ENTRYCHUNK * 4);
	  test_exitf (newspace != NULL, 10, ends(&o));
	  o.root_list = newspace;
	}
      o.root_list[len] = fil_sreadU32 (o.BDepot + (o.root_list[len - 1] * 4));
      test_exitf (o.root_list[len] != 0xfffffffdUL && o.root_list[len] !=
		  0xffffffffUL, 5, ends(&o));
    }
  len--;
  verboseU32Array (root_list, len+1);
  /* read in root block depot */
  verbose ("read in root block depot (Root)");
  o.Root = (U8 *)malloc (0x0200 * len);
  test_exitf (o.Root != NULL, 10, ends(&o));
  s = o.Root;
  for (i = 0; i < len; i++)
    {
      FilePos = 0x0200 * (o.root_list[i] + 1);
      assert (FilePos >= 0);
      test_exitf (!fseek (o.input, FilePos, SEEK_SET), 5, ends(&o));
      fread (s, 0x0200, 1, o.input);
      test_exitf (!ferror (o.input), 5, ends(&o));
      s += 0x200;
    }
  verboseU8Array (Root, len, 0x0200);


  /* assign space for pps list */
  verbose ("read pps list");
  o.num_of_pps = len * 4;		/* each sbd block have 4 pps */
  ole->tree = o.pps_list = (pps_entry *)malloc(o.num_of_pps * sizeof (pps_entry));
  test_exitf (o.pps_list != NULL, 10, ends(&o));
  /* read pss entry details and look out for "Root Entry" */
  verbose ("read pps entry details");
  for (i = 0; i < o.num_of_pps; i++)
    {
      U16 size_of_name;

      s = o.Root + (i * 0x80);

      /* read the number */
      o.pps_list[i].ppsnumber = i;

      /* read the name */
      size_of_name = (U16)MIN (0x40, fil_sreadU16 (s + 0x40));
      o.pps_list[i].name[0] = 0;
      if (size_of_name == 0)
	continue;
      for (p = (U8 *) o.pps_list[i].name, t = s;
	   t < s + size_of_name; t++)
	*p++ = *t++;
      /* makes visible the non printable first character */
      /* if (!isprint (pps_list[i].name[0]) && pps_list[i].name[0])
	pps_list[i].name[0] += 'a'; */

      /* read the pps type */
      o.pps_list[i].type = *(s + 0x42);
      if (o.pps_list[i].type == 5)
	{
	  assert (i == 0);
	  ole->root = i;		/* this pps is the root */
	}

      /* read the others fields */
      o.pps_list[i].previous = fil_sreadU32 (s + 0x44);
      o.pps_list[i].next = fil_sreadU32 (s + 0x48);
      o.pps_list[i].dir = fil_sreadU32 (s + 0x4c);
      o.pps_list[i].start = fil_sreadU32 (s + 0x74);
      o.pps_list[i].size = fil_sreadU32 (s + 0x78);
      o.pps_list[i].seconds1 = fil_sreadU32 (s + 0x64);
      o.pps_list[i].seconds2 = fil_sreadU32 (s + 0x6c);
      o.pps_list[i].days1 = fil_sreadU32 (s + 0x68);
      o.pps_list[i].days2 = fil_sreadU32 (s + 0x70);
    }

  /* NEXT IS VERBOSE verbose */
#ifdef VERBOSE
  {
    U32 i;
    printf ("before reorder pps tree\n");
    printf ("pps    type    prev     next      dir start   level size     name\n");
    for (i = 0; i < num_of_pps; i++)
      {
	if (!pps_list[i].name[0])
	{
	  printf (" -\n");
	  continue;
	}
	printf ("%08lx ", pps_list[i].ppsnumber);
	printf ("%d ", pps_list[i].type);
	printf ("%08lx ", pps_list[i].previous);
	printf ("%08lx ", pps_list[i].next);
	printf ("%08lx ", pps_list[i].dir);
	printf ("%08lx ", pps_list[i].start);
	printf ("%04x ", pps_list[i].level);
	printf ("%08lx ", pps_list[i].size);
	printf ("'%c", !isprint (pps_list[i].name[0]) ? ' ' : pps_list[i].name[0]);
	printf ("%s'\n", pps_list[i].name+1);
      }
  }
#endif

  /* go through the tree made with pps entries, and reorder it so only the
     next link is used (move the previous-link-children to the last visited
     next-link-children) */
  test_exitf (reorder_pps_tree (&o.pps_list[ole->root], 0, &o), 9, ends(&o));

  /* NEXT IS VERBOSE verbose */
#ifdef VERBOSE
  {
    U32 i;
    printf ("after reorder pps tree\n");
    printf ("pps    type    prev     next      dir start   level size     name\n");
    for (i = 0; i < num_of_pps; i++)
      {
        if (!pps_list[i].name[0])
        {
          printf (" -\n");
          continue;
        }
	printf ("%08lx ", pps_list[i].ppsnumber);
	printf ("%d ", pps_list[i].type);
	printf ("%08lx ", pps_list[i].previous);
	printf ("%08lx ", pps_list[i].next);
	printf ("%08lx ", pps_list[i].dir);
	printf ("%08lx ", pps_list[i].start);
	printf ("%04x ", pps_list[i].level);
	printf ("%08lx ", pps_list[i].size);
	printf ("'%c", !isprint (pps_list[i].name[0]) ? ' ' : pps_list[i].name[0]);
	printf ("%s\n", pps_list[i].name+1);
      }
  }
#endif
#ifdef VERBOSE
  /* NEXT IS VERBOSE verbose */
  verbosePPSTree (pps_list, *root, 0);
#endif

  /* generates pps real files */
  /* NOTE: by this moment, the pps tree,
     wich is made with pps_list entries, is reordered */
  verbose ("create pps files");
  {
    U8 *Depot;
    FILE *OLEfile, *infile;
    U16 BlockSize, Offset;
    size_t bytes_to_read;
    U32 pps_size, pps_start;

    assert (o.num_of_pps >= 1);
    /* i < 1 --- before i < num_of_pps --- changed so not to generate the
       real files by now --- cole 2.0.0 */
    /* may be later we can rewrite this code in order to only extract the
       sbfile, may be using __cole_extract_file call to avoid duplicated
       code --- cole 2.0.0 */
    for (i = 0; i < 1; i++)
      {
	o.pps_list[i].filename[0] = 0;

	/* storage pps and non-valid-pps (except root) does not need files */
	/* because FlashPix file format have a root of type 5 but with no name,
	   we must to check if the non-valid-pps is root */
	if (o.pps_list[i].type == 1
	    || (!o.pps_list[i].name[0] && o.pps_list[i].type != 5))
	    continue;
	/* pps that have level > max_level will not be extracted */
	if (max_level != 0 && o.pps_list[i].level > max_level)
	    continue;

	pps_size = o.pps_list[i].size;
	pps_start = o.pps_list[i].start;
/* FIXME MARK 2 */
	/* How we get sure pps_start doesn't point to a block bigger than the
	   real file (input of sbfile) have? */

	/* create the new file */
	if (o.pps_list[i].type == 5)
	  /* root entry, sbfile must be generated */
	  {
	    if (o.SDepot == NULL) {
		/* if there are not small blocks, not generate sbfile */
		ole->sbfilename = NULL;
		ole->sbfile = NULL;
		break;
	    }
	    assert (i == ole->root);
	    assert (i == 0);
	    /* tmpnam (sbfile) and next calls --- commented out so not to
	       generate the real files by now --- sbfilename is stored in
	       *_sbfilename instead --- cole 2.0.0
	     */
	    /*
	      tmpnam (sbfilename);
	      test_exitf (sbfilename[0], 7, ends(&o));
	      sbfile = OLEfile = fopen (sbfilename, "wb+");
	      test_exitf (OLEfile != NULL, 7, ends(&o));
	      verboseS (sbfilename);
	     */
	    ole->sbfilename = (char *)malloc(L_tmpnam);
	    test_exitf (ole->sbfilename != NULL, 10, ends(&o));
	    tmpnam (ole->sbfilename);
	    test_exitf (ole->sbfilename[0], 7, ends(&o));
	    o.sbfile = OLEfile = fopen (ole->sbfilename, "wb+");
	    ole->sbfile = o.sbfile;
	    test_exitf (OLEfile != NULL, 7, ends(&o));
	    verboseS (*_sbfilename);
	  }
	else
	  /* other entry, save in a file */
	  {
	    /* this branch is never executed now */
	    tmpnam (o.pps_list[i].filename);
	    test_exitf (o.pps_list[i].filename[0], 7, ends(&o));
	    verbose(pps_list[i].name + (!isprint(pps_list[i].name[0]) ? 1 : 0));
	    OLEfile = fopen (o.pps_list[i].filename, "wb");
	    test_exitf (OLEfile != NULL, 7, ends(&o));
	    verbose (pps_list[i].filename);
	  }

	if (pps_size >= 0x1000 /*is in bbd */  ||
	    OLEfile == o.sbfile /*is root */ )
	  {
	    /* read from big block depot */
	    Offset = 1;
	    BlockSize = 0x0200;
	    assert (o.input != NULL);
	    assert (o.BDepot != NULL);
	    infile = o.input;
	    Depot = o.BDepot;
	  }
	else
	  {
	    /* read from small block file */
	    Offset = 0;
	    BlockSize = 0x40;
	    assert (o.sbfile != NULL);
	    assert (o.SDepot != NULL);
	    infile = o.sbfile;
	    Depot = o.SDepot;
	  }

	/* -2 signed long int == 0xfffffffe unsinged long int */
	while (pps_start != 0xfffffffeUL)
	  {
#ifdef VERBOSE
	    printf ("reading pps %08lx block %08lx from %s\n",
		    pps_list[i].ppsnumber, pps_start,
		 Depot == BDepot ? "big block depot" : "small block depot");
#endif
	    FilePos = (pps_start + Offset) * BlockSize;
	    assert (FilePos >= 0);
	    bytes_to_read = MIN ((U32)BlockSize, pps_size);
	    fseek (infile, FilePos, SEEK_SET);
	    fread (o.Block, bytes_to_read, 1, infile);
	    test_exitf (!ferror (infile), 5, ends(&o));		// TODO: Resource leak OleFile.
	    fwrite (o.Block, bytes_to_read, 1, OLEfile);
	    test_exitf (!ferror (infile), 5, ends(&o));
	    pps_start = fil_sreadU32 (Depot + (pps_start * 4));
	    pps_size -= MIN ((U32)BlockSize, pps_size);
	    if (pps_size == 0)
	      pps_start = 0xfffffffeUL;
	  }
	if (OLEfile == o.sbfile)
	  /* if small block file generated */
	  rewind (OLEfile);	/* rewind because we will read it later */
	/*else if (!fclose (OLEfile))*/	/* close the pps file */
	  /* don't know what to do here */
	  /*;*/
      }
    /* if (sbfile != NULL) --- commented out so conservate sbfile
       open --- cole 2.0.0 */
    /* 
    if (sbfile != NULL)
      {
	fclose (sbfile);
	  if (!remove (sbfilename)) ;
	sbfile = NULL;
      }
     */
  }
  ends(&o);
  return 0;
}



static int cole_extract_file (COLEFILE *OleFile)
{
	/* FIXME rewrite this cleanner */
FILE *ret;
U16 BlockSize, Offset;
U8 *Depot;
FILE *infile;
long FilePos;
size_t bytes_to_copy;
U8 Block[0x0200];
U32 size;
U32 pps_start;

  size=OleFile->fs->tree[OleFile->entry].size;
  pps_start=OleFile->fs->tree[OleFile->entry].start;

  if(OleFile->filename == NULL)
      {
      OleFile->filename = (char *)malloc (L_tmpnam); /* It must be L_tmpnam+1? */
      if(OleFile->filename == NULL)
	  return 1;
      if (tmpnam (OleFile->filename) == NULL)
	  {
	  free (OleFile->filename);
	  return 2;
	  }
      }
  ret = fopen (OleFile->filename, "w+b");
  OleFile->file = ret;
  if (ret == NULL)
	{
	free (OleFile->filename);
	return 3;
	}
  if (size >= 0x1000) {
		/* read from big block depot */
		Offset = 1;
		BlockSize = 0x0200;
		infile = OleFile->fs->file;
		Depot = OleFile->fs->BDepot;
	} else {
		/* read from small block file */
		Offset = 0;
		BlockSize = 0x40;
		infile = OleFile->fs->sbfile;
		Depot = OleFile->fs->SDepot;
	}
  while (pps_start != 0xfffffffeUL /*&& pps_start != 0xffffffffUL &&
		pps_start != 0xfffffffdUL*/) {
		FilePos = (long)((pps_start + Offset) * BlockSize);
		if (FilePos < 0) {
			fclose (ret);
			remove (OleFile->filename);
			free (OleFile->filename);
			return 4;
		}
		bytes_to_copy = MIN ((U32)BlockSize, size);
		if (fseek (infile, FilePos, SEEK_SET)) {
			fclose (ret);
			remove (OleFile->filename);
			free (OleFile->filename);
			return 4;
		}
		fread (Block, bytes_to_copy, 1, infile);
		if (ferror (infile)) {
			fclose (ret);
			remove (OleFile->filename);
			free (OleFile->filename);
			return 5;
		}
		fwrite (Block, bytes_to_copy, 1, ret);
		if (ferror (ret)) {
			fclose (ret);
			remove (OleFile->filename);
			free (OleFile->filename);
			return 6;
		}
		pps_start = fil_sreadU32 (Depot + (pps_start * 4));
		size -= MIN ((U32)BlockSize, size);
		if (size == 0)
			break;
	}

return 0;
}


/* ---------------FILE support functions ---------------*/

/**
 * cole_fclose:
 * @colefile: file to be closed.
 * @colerrno: error value (COLE_ECLOSEFILE, CLOSE_EREMOVE).
 *
 * Closes the file @colefile.
 *
 * Returns: zero in sucess, no zero in other case.
 */
int cole_fclose (COLEFILE *colefile, COLERRNO *colerrno)
{
int ret;

  if(colefile==NULL) return(2);
  ret = 0;
  if (fclose (colefile->file) && !ret)
	{
	if (colerrno != NULL) *colerrno = COLE_ECLOSEFILE;
	ret = 1;
	}
  if(remove (colefile->filename) && !ret)
	{
	if (colerrno != NULL) *colerrno = COLE_EREMOVE;
	ret = 1;
	}
  free(colefile->filename);
  free(colefile);

return ret;
}


/** cole_fopen_direntry:
 * @coledirentry: directory entry to be opened as file.
 * @colerrno: error value (COLE_EISNOTFILE, COLE_EMEMORY, COLE_ETMPNAM,
 * 			   COLE_EOPENFILE, COLE_EINVALIDFILESYSTEM, COLE_EREAD,
 * 			   COLE_EWRITE, COLE_EUNKNOWN).
 *
 * Opens a directory entry as file.
 *
 * Returns: a file in success, or NULL in other case. */
COLEFILE *cole_fopen_direntry (COLEDIRENT *coledirentry, COLERRNO *colerrno, char *FileName)
{
COLEFILE *ret;

  if (!cole_direntry_isfile (coledirentry))
	{
	if (colerrno != NULL) *colerrno = COLE_EISNOTFILE;
	return NULL;
	}

  ret = (COLEFILE *)malloc(sizeof (COLEFILE));
  if (ret == NULL)
	  {
	  if (colerrno != NULL) *colerrno = COLE_EMEMORY;
	  return NULL;
	  }
  ret->fs = coledirentry->dir->fs;
  ret->entry = coledirentry->entry;
  ret->filename=FileName;
  switch (cole_extract_file(ret) )
	{
	case 0:
		/* success */
		break;
	case 1:
		if (colerrno != NULL) *colerrno = COLE_EMEMORY;
		free (ret);
		return NULL;
	case 2:
		if (colerrno != NULL) *colerrno = COLE_ETMPNAM;
		free (ret);
		return NULL;
	case 3:
		if (colerrno != NULL) *colerrno = COLE_EOPENFILE;
		free (ret);
		return NULL;
	case 4:
		if (colerrno != NULL) *colerrno = COLE_EINVALIDFILESYSTEM;
		free (ret);
		return NULL;
	case 5:
		if (colerrno != NULL) *colerrno = COLE_EREAD;
		free (ret);
		return NULL;
	case 6:
		if (colerrno != NULL) *colerrno = COLE_EWRITE;
		free (ret);
		return NULL;
	default:
		if (colerrno != NULL) *colerrno = COLE_EUNKNOWN;
		free (ret);
		return NULL;
	}
	/* because the original fopen(3) leaves the file pointer
	   in the beginning */
	rewind (ret->file);
	ret->pos = 0;
	ret->filesize = ret->fs->tree[ ret->entry ].size;

	return ret;
}


/**
 * cole_direntry_isdir:
 * @coledirentry: directory entry to be tested.
 *
 * Tests if the directory entry @coledirentry is a directory.
 *
 * Returns: no zero if it is a directory, zero in other case.
 */
int
cole_direntry_isdir (COLEDIRENT *coledirentry)
{
	return coledirentry->dir->fs->tree[ coledirentry->entry ].type ==
	       _COLE_TYPE_DIR;
}


/**
 * cole_direntry_isfile:
 * @coledirentry: directory entry to be tested.
 *
 * Tests if the directory entry @coledirentry is a file.
 *
 * Returns: no zero if it is a directory, zero in other case.
 */
int
cole_direntry_isfile (COLEDIRENT *coledirentry)
{
	return coledirentry->dir->fs->tree[ coledirentry->entry ].type ==
	       _COLE_TYPE_FILE;
}


char *
cole_direntry_getname (COLEDIRENT *coledirentry)
{
	return coledirentry->dir->fs->tree[ coledirentry->entry ].name;
}


size_t
cole_direntry_getsize (COLEDIRENT *coledirentry)
{
	/* FIXME is it cast needed? */
	return coledirentry->dir->fs->tree[ coledirentry->entry ].size;
}


long
cole_direntry_getsec1 (COLEDIRENT *coledirentry)
{
	/* seconds1 is U32, long is at least U32 in all plattforms, isn't it? */
	return coledirentry->dir->fs->tree[ coledirentry->entry ].seconds1;
}


long
cole_direntry_getsec2 (COLEDIRENT *coledirentry)
{
	/* seconds2 is U32, long is at least U32 in all plattforms, isn't it? */
	return coledirentry->dir->fs->tree[ coledirentry->entry ].seconds2;
}


long
cole_direntry_getdays1 (COLEDIRENT *coledirentry)
{
	/* days1 is U32, long is at least U32 in all plattforms, isn't it? */
	return coledirentry->dir->fs->tree[ coledirentry->entry ].days1;
}


long cole_direntry_getdays2 (COLEDIRENT *coledirentry)
{
	/* days2 is U32, long is at least U32 in all plattforms, isn't it? */
	return coledirentry->dir->fs->tree[ coledirentry->entry ].days2;
}



/* ---------------FS support functions ---------------*/

COLEFS *cole_mount (char *filename, COLERRNO *colerrno)
{
COLEFS *ret;

  ret = (COLEFS *)malloc (sizeof (COLEFS));
  if (ret == NULL)
	{
	if (colerrno != NULL) *colerrno = COLE_EMEMORY;
	return NULL;
	}

  switch (cpp_OLEdecode (filename, ret, 0))
	{
	case 0:
		/* success */
		break;
	case 10:
		if (colerrno != NULL) *colerrno = COLE_EMEMORY;
		free (ret);
		return NULL;
	case 7:
	case 4:
		if (colerrno != NULL) *colerrno = COLE_EOPENFILE;
		free (ret);
		return NULL;
	case 8:
	case 9:
		if (colerrno != NULL) *colerrno = COLE_ENOFILESYSTEM;
		free (ret);
		return NULL;
	case 5:
		if (colerrno != NULL) *colerrno = COLE_EINVALIDFILESYSTEM;
		free (ret);
		return NULL;
	default:
		if (colerrno != NULL) *colerrno = COLE_EUNKNOWN;
		free (ret);
		return NULL;
	}

	return ret;
}

/**
 * cole_umount:
 * @colefilesystem: filesystem to umount.
 * @colerrno: error value (COLE_ECLOSEFILE, COLE_EREMOVE).
 *
 * Umounts the filesystem @colefilesystem.
 *
 * Returns: zero in success, no zero in other case.
 */
int cole_umount (COLEFS *colefilesystem, COLERRNO *colerrno)
{
int ret;

  ret = 0;
  if(colefilesystem==NULL) return 2;
  free(colefilesystem->BDepot);
  free(colefilesystem->tree);

  if(colefilesystem->file!=NULL)
    if (fclose (colefilesystem->file) && !ret)
	{
	if (colerrno != NULL) *colerrno = COLE_ECLOSEFILE;
	ret = 1;
	}

  if (colefilesystem->SDepot != NULL)
	{
	free (colefilesystem->SDepot);
	/* may no exist SDepot because there are not small files */
	/* assert (colefilesystem->sbfile != NULL); */
	/* assert (colefilesystem->sbfilename != NULL); */
	if (fclose (colefilesystem->sbfile) && !ret)
		{
		if (colerrno != NULL) *colerrno = COLE_ECLOSEFILE;
		ret = 1;
		}
	if (remove (colefilesystem->sbfilename) && !ret)
		{
		if (colerrno != NULL) *colerrno = COLE_EREMOVE;
		ret = 1;
		}
	free (colefilesystem->sbfilename);
	}
  free(colefilesystem);

return ret;
}

/* ---------------DIR support functions ---------------*/

/**
 * cole_opendir_rootdir:
 * @colefilesystem: filesystem of which the root directory will be opened.
 * @colerrno: error value (COLE_EMEMORY).
 *
 * Opens the root directory of the filesystem @colefilesystem as directory.
 *
 * Returns: a directory in success, or NULL in other case.
 */
COLEDIR *cole_opendir_rootdir (COLEFS *colefilesystem, COLERRNO *colerrno)
{
	COLEDIR *ret;

	ret = (COLEDIR*)malloc(sizeof(COLEDIR));
	if (ret == NULL) {
		if (colerrno != NULL) *colerrno = COLE_EMEMORY;
		return NULL;
	}
	ret->fs = colefilesystem;
	ret->entry = ret->fs->root;
	ret->visited_entry.dir = ret;
	ret->visited_entry.entry = ret->fs->tree[ ret->entry ].dir;

	return ret;
}


/**
 * cole_opendir_direntry:
 * @coledirentry: directory entry to be opened as directory.
 * @colerrno: error value (COLE_EISNOTDIR, COLE_EMEMORY).
 *
 * Opens a directory entry as directory.
 *
 * Returns: a directory in success, or NULL in other case.
 */
COLEDIR *cole_opendir_direntry (COLEDIRENT *coledirentry, COLERRNO *colerrno)
{
COLEDIR *ret;

  if (!cole_direntry_isdir (coledirentry))
     {
     if (colerrno != NULL) *colerrno = COLE_EISNOTDIR;
     return NULL;
     }

	ret = (COLEDIR *)malloc (sizeof (COLEDIR));
	if (ret == NULL) {
		if (colerrno != NULL) *colerrno = COLE_EMEMORY;
		return NULL;
	}
	ret->fs = coledirentry->dir->fs;
	ret->entry = coledirentry->entry;
	ret->visited_entry.dir = ret;
	ret->visited_entry.entry = ret->fs->tree[ ret->entry ].dir;

	return ret;
}

/**
 * cole_closedir:
 * @coledir: directory to be closed.
 * @colerrno: error value ().
 *
 * Closes the directory @coledir.
 * Currently this call always success.
 *
 * Returns: zero in success, no zero in other case.
 */
int cole_closedir (COLEDIR *coledir)
{
  free (coledir);
  return 0;
}


COLEDIRENT *cole_visiteddirentry (COLEDIR *coledir)
{
  if(coledir==NULL) return(NULL);
  if (coledir->visited_entry.entry == 0xffffffffUL)
		return NULL;

  return &coledir->visited_entry;
}


COLEDIRENT *cole_nextdirentry (COLEDIR *coledir)
{
  if (coledir->visited_entry.entry == 0xffffffffUL)
		return NULL;

  coledir->visited_entry.entry =
		coledir->fs->tree [ coledir->visited_entry.entry ].next;

  if (coledir->visited_entry.entry == 0xffffffffUL)
		return NULL;

  return &coledir->visited_entry;
}


char *cole_dir_getname (COLEDIR *coledir)
{
	return coledir->fs->tree[ coledir->entry ].name;
}


size_t cole_dir_getsize (COLEDIR *coledir)
{
	/* FIXME is it cast needed? */
  return coledir->fs->tree[ coledir->entry ].size;
}


long cole_dir_getsec1 (COLEDIR *coledir)
{
	/* seconds1 is U32, long is at least U32 in all plattforms, isn't it? */
  return coledir->fs->tree[ coledir->entry ].seconds1;
}


/* seconds2 is U32, long is at least U32 in all plattforms, isn't it? */
long cole_dir_getsec2 (COLEDIR *coledir)
{
  return coledir->fs->tree[ coledir->entry ].seconds2;
}


/* days1 is U32, long is at least U32 in all plattforms, isn't it? */
long cole_dir_getdays1 (COLEDIR *coledir)
{
  return coledir->fs->tree[ coledir->entry ].days1;
}


/* days2 is U32, long is at least U32 in all plattforms, isn't it? */
long cole_dir_getdays2 (COLEDIR *coledir)
{
  return coledir->fs->tree[ coledir->entry ].days2;
}



/* --------Higher level cole functions------------------*/

/** cole_fopen:
 * @colefilesystem: filesystem in which @filename is in.
 * @filename: name of the file to open.
 * @colerrno: error value (COLE_EFILENOTFOUND, errors from calls
 * 			   cole_opendir_rootdir(), cole_fopen_direntry() and
 *			   cole_locate_filename()).
 *
 * Opens the file with the name @filename in the filesystem @colefilesystem.
 * Currently, @filename must begin with a '/' character, it means @filename is
 * the absolute filename.
 *
 * Returns: a file in success, or NULL in other case. */
COLEFILE *cole_fopen(COLEFS *colefilesystem, const char *filename, char *TempName, COLERRNO *colerrno)
{
COLEFILE *FC;
COLEDIR *Dir;

  Dir = cole_opendir_rootdir(colefilesystem,colerrno);
  FC = cole_fopen_dir(Dir, filename, TempName, colerrno);
  cole_closedir(Dir);
return(FC);
}


COLEFILE *cole_fopen_dir(COLEDIR *Dir, const char *filename, char *TempName, COLERRNO *colerrno)
{
COLEFILE *FC;
COLEDIRENT *cde;

  if(Dir!=NULL)
    {
    for(cde=cole_visiteddirentry(Dir); cde!=NULL; cde=cole_nextdirentry(Dir))
	{
	char *entry_name = cole_direntry_getname(cde);

	if(cole_direntry_isfile(cde))
	  if(entry_name!=NULL)
	    if(!strcmp(entry_name,filename))
	      {
	      FC = cole_fopen_direntry(cde,colerrno,TempName);
	      return(FC);
	      }
       }
    }
  if(colerrno) *colerrno=COLE_EFILENOTFOUND;
  if(TempName) free(TempName);		/*this should be checked*/
  return(NULL);
}


COLEDIR *cole_diropen(COLEFS *colefilesystem, const char *dirname, COLERRNO *colerrno)
{
COLEDIR *FC;
COLEDIR *Dir;
COLEDIRENT *cde;

  Dir = cole_opendir_rootdir(colefilesystem,NULL);
  for(cde=cole_visiteddirentry(Dir); cde!=NULL; cde=cole_nextdirentry(Dir))
       {
       const char *entry_name = cole_direntry_getname(cde);

       if(cole_direntry_isdir(cde))
	 if(entry_name!=NULL)
	   if(!strcmp(entry_name,dirname))
	     {
	     FC=cole_opendir_direntry(cde,colerrno);
	     cole_closedir(Dir);
	     return(FC);
	     }
       }
  if(colerrno) *colerrno=COLE_EFILENOTFOUND;
  cole_closedir(Dir);
  return(NULL);
}


void cole_fprint_tree(COLEDIR *Dir, FILE *output)
{
COLEDIR *Nested;
COLEDIRENT *cde;

  if(Dir==NULL || output==NULL) return;

  for(cde=cole_visiteddirentry(Dir); cde!=NULL; cde=cole_nextdirentry(Dir))
       {
       cole_fprint_direntry(cde, output);

       if(cole_direntry_isdir(cde))
	  {
	  Nested=cole_opendir_direntry(cde,NULL);
	  cole_fprint_tree(Nested,output);
	  cole_closedir(Nested);
	  }
       }
return;
}


void cole_print_tree(COLEFS *colefilesystem, COLERRNO *colerrno)
{
COLEDIR *Dir;
  Dir = cole_opendir_rootdir(colefilesystem,colerrno);
  if(Dir)
    {
    cole_fprint_tree(Dir, stdout);
    cole_closedir(Dir);
    }
}


void cole_fprint_direntry(COLEDIRENT *cde, FILE *output)
{
 char *entry_name;
 if(output==NULL || cde==NULL) return;

 if (cole_direntry_isdir(cde))
		fprintf(output,"DIR ");
 else if (cole_direntry_isfile(cde))
		fprintf(output,"FILE");
 else  fprintf(output,"????");

 fprintf(output," %7u", cole_direntry_getsize(cde));
 fprintf(output," %08lx-%08lx %08lx-%08lx",
		cole_direntry_getdays1(cde), cole_direntry_getsec1(cde),
		cole_direntry_getdays2(cde), cole_direntry_getsec2(cde));

 entry_name = cole_direntry_getname(cde);
 if(entry_name!=NULL)
   {
   if(!isprint ((int)entry_name[0]))
		fprintf(output," '\\x%02x%s'\n", entry_name[0], entry_name+1);
   else
		fprintf(output," '%s'\n", entry_name);
   }
 else fputc('\n',output);
}