/*
 * wordole.c
 * Copyright (C) 1998-2004 A.J. van Os; Released under GPL
 *
 * Description:
 * Deal with the OLE internals of a MS Word file
 */

#include <string.h>
#include "antiword.h"

/* Private type for Property Set Storage entries */
typedef struct pps_entry_tag {
	ULONG	ulNext;
	ULONG	ulPrevious;
	ULONG	ulDir;
	ULONG	ulSB;
	ULONG	ulSize;
	int	iLevel;
	char	szName[32];
	UCHAR	ucType;
} pps_entry_type;

/* Show that a PPS number or index should not be used */
#define PPS_NUMBER_INVALID	0xffffffffUL


/* Macro to make sure all such statements will be identical */
#define FREE_ALL()		\
	do {\
		vDestroySmallBlockList();\
		aulRootList = xfree(aulRootList);\
		aulSbdList = xfree(aulSbdList);\
		aulBbdList = xfree(aulBbdList);\
		aulSBD = xfree(aulSBD);\
		aulBBD = xfree(aulBBD);\
	} while(0)


/*
 * ulReadLong - read four bytes from the given file and offset
 */
static ULONG
ulReadLong(FILE *pFile, ULONG ulOffset)
{
	UCHAR	aucBytes[4];

	fail(pFile == NULL);

	if (!bReadBytes(aucBytes, 4, ulOffset, pFile)) {
		werr(1, "Read long 0x%lx not possible", ulOffset);
	}
	return ulGetLong(0, aucBytes);
} /* end of ulReadLong */

/*
 * vName2String - turn the name into a proper string.
 */
static void
vName2String(char *szName, const UCHAR *aucBytes, size_t tNameSize)
{
	char	*pcChar;
	size_t	tIndex;

	fail(aucBytes == NULL || szName == NULL);

	if (tNameSize < 2) {
		szName[0] = '\0';
		return;
	}
	for (tIndex = 0, pcChar = szName;
	     tIndex < 2 * tNameSize;
	     tIndex += 2, pcChar++) {
		*pcChar = (char)aucBytes[tIndex];
	}
	szName[tNameSize - 1] = '\0';
} /* end of vName2String */

/*
 * tReadBlockIndices - read the Big/Small Block Depot indices
 *
 * Returns the number of indices read
 */
static size_t
tReadBlockIndices(FILE *pFile, ULONG *aulBlockDepot,
	size_t tMaxRec, ULONG ulOffset)
{
	size_t	tDone;
	int	iIndex;
	UCHAR	aucBytes[BIG_BLOCK_SIZE];

	fail(pFile == NULL || aulBlockDepot == NULL);
	fail(tMaxRec == 0);

	/* Read a big block with BBD or SBD indices */
	if (!bReadBytes(aucBytes, BIG_BLOCK_SIZE, ulOffset, pFile)) {
		werr(0, "Reading big block from 0x%lx is not possible",
			ulOffset);
		return 0;
	}
	/* Split the big block into indices, an index is four bytes */
	tDone = min(tMaxRec, (size_t)BIG_BLOCK_SIZE / 4);
	for (iIndex = 0; iIndex < (int)tDone; iIndex++) {
		aulBlockDepot[iIndex] = ulGetLong(4 * iIndex, aucBytes);
		NO_DBG_DEC(aulBlockDepot[iIndex]);
	}
	return tDone;
} /* end of tReadBlockIndices */

/*
 * bGetBBD - get the Big Block Depot indices from the index-blocks
 */
static BOOL
bGetBBD(FILE *pFile, const ULONG *aulDepot, size_t tDepotLen,
	ULONG *aulBBD, size_t tBBDLen)
{
	ULONG	ulBegin;
	size_t	tToGo, tDone;
	int	iIndex;

	fail(pFile == NULL || aulDepot == NULL || aulBBD == NULL);

	DBG_MSG("bGetBBD");

	tToGo = tBBDLen;
	for (iIndex = 0; iIndex < (int)tDepotLen && tToGo != 0; iIndex++) {
		ulBegin = (aulDepot[iIndex] + 1) * BIG_BLOCK_SIZE;
		NO_DBG_HEX(ulBegin);
		tDone = tReadBlockIndices(pFile, aulBBD, tToGo, ulBegin);
		fail(tDone > tToGo);
		if (tDone == 0) {
			return FALSE;
		}
		aulBBD += tDone;
		tToGo -= tDone;
	}
	return tToGo == 0;
} /* end of bGetBBD */

/*
 * bGetSBD - get the Small Block Depot indices from the index-blocks
 */
static BOOL
bGetSBD(FILE *pFile, const ULONG *aulDepot, size_t tDepotLen,
	ULONG *aulSBD, size_t tSBDLen)
{
	ULONG	ulBegin;
	size_t	tToGo, tDone;
	int	iIndex;

	fail(pFile == NULL || aulDepot == NULL || aulSBD == NULL);

	DBG_MSG("bGetSBD");

	tToGo = tSBDLen;
	for (iIndex = 0; iIndex < (int)tDepotLen && tToGo != 0; iIndex++) {
		fail(aulDepot[iIndex] >= ULONG_MAX / BIG_BLOCK_SIZE);
		ulBegin = (aulDepot[iIndex] + 1) * BIG_BLOCK_SIZE;
		NO_DBG_HEX(ulBegin);
		tDone = tReadBlockIndices(pFile, aulSBD, tToGo, ulBegin);
		fail(tDone > tToGo);
		if (tDone == 0) {
			return FALSE;
		}
		aulSBD += tDone;
		tToGo -= tDone;
	}
	return tToGo == 0;
} /* end of bGetSBD */

/*
 * vComputePPSlevels - compute the levels of the Property Set Storage entries
 */
static void
vComputePPSlevels(pps_entry_type *atPPSlist, pps_entry_type *pNode,
			int iLevel, int iRecursionLevel)
{
	fail(atPPSlist == NULL || pNode == NULL);
	fail(iLevel < 0 || iRecursionLevel < 0);

	if (iRecursionLevel > 25) {
		/* This removes the possibility of an infinite recursion */
		DBG_DEC(iRecursionLevel);
		return;
	}
	if (pNode->iLevel <= iLevel) {
		/* Avoid entering a loop */
		DBG_DEC(iLevel);
		DBG_DEC(pNode->iLevel);
		return;
	}

	pNode->iLevel = iLevel;

	if (pNode->ulDir != PPS_NUMBER_INVALID) {
		vComputePPSlevels(atPPSlist,
				&atPPSlist[pNode->ulDir],
				iLevel + 1,
				iRecursionLevel + 1);
	}
	if (pNode->ulNext != PPS_NUMBER_INVALID) {
		vComputePPSlevels(atPPSlist,
				&atPPSlist[pNode->ulNext],
				iLevel,
				iRecursionLevel + 1);
	}
	if (pNode->ulPrevious != PPS_NUMBER_INVALID) {
		vComputePPSlevels(atPPSlist,
				&atPPSlist[pNode->ulPrevious],
				iLevel,
				iRecursionLevel + 1);
	}
} /* end of vComputePPSlevels */

/*
 * bGetPPS - search the Property Set Storage for three sets
 *
 * Return TRUE if the WordDocument PPS is found
 */
static BOOL
bGetPPS(FILE *pFile,
	const ULONG *aulRootList, size_t tRootListLen, pps_info_type *pPPS)
{
	pps_entry_type	*atPPSlist;
	ULONG	ulBegin, ulOffset, ulTmp;
	size_t	tNbrOfPPS, tNameSize;
	int	iIndex, iStartBlock, iRootIndex;
	BOOL	bWord, bExcel;
	UCHAR	aucBytes[PROPERTY_SET_STORAGE_SIZE];

	fail(pFile == NULL || aulRootList == NULL || pPPS == NULL);

	DBG_MSG("bGetPPS");

	NO_DBG_DEC(tRootListLen);

	bWord = FALSE;
	bExcel = FALSE;
	(void)memset(pPPS, 0, sizeof(*pPPS));

	/* Read and store all the Property Set Storage entries */

	tNbrOfPPS = tRootListLen * BIG_BLOCK_SIZE / PROPERTY_SET_STORAGE_SIZE;
	atPPSlist = xcalloc(tNbrOfPPS, sizeof(pps_entry_type));
	iRootIndex = 0;

	for (iIndex = 0; iIndex < (int)tNbrOfPPS; iIndex++) {
		ulTmp = (ULONG)iIndex * PROPERTY_SET_STORAGE_SIZE;
		iStartBlock = (int)(ulTmp / BIG_BLOCK_SIZE);
		ulOffset = ulTmp % BIG_BLOCK_SIZE;
		ulBegin = (aulRootList[iStartBlock] + 1) * BIG_BLOCK_SIZE +
				ulOffset;
		NO_DBG_HEX(ulBegin);
		if (!bReadBytes(aucBytes, PROPERTY_SET_STORAGE_SIZE,
							ulBegin, pFile)) {
			werr(0, "Reading PPS %d is not possible", iIndex);
			atPPSlist = xfree(atPPSlist);
			return FALSE;
		}
		tNameSize = (size_t)usGetWord(0x40, aucBytes);
		tNameSize = (tNameSize + 1) / 2;
		vName2String(atPPSlist[iIndex].szName, aucBytes, tNameSize);
		atPPSlist[iIndex].ucType = ucGetByte(0x42, aucBytes);
		if (atPPSlist[iIndex].ucType == 5) {
			iRootIndex = iIndex;
		}
		atPPSlist[iIndex].ulPrevious = ulGetLong(0x44, aucBytes);
		atPPSlist[iIndex].ulNext = ulGetLong(0x48, aucBytes);
		atPPSlist[iIndex].ulDir = ulGetLong(0x4c, aucBytes);
		atPPSlist[iIndex].ulSB = ulGetLong(0x74, aucBytes);
		atPPSlist[iIndex].ulSize = ulGetLong(0x78, aucBytes);
		atPPSlist[iIndex].iLevel = INT_MAX;
		if ((atPPSlist[iIndex].ulPrevious >= (ULONG)tNbrOfPPS &&
		     atPPSlist[iIndex].ulPrevious != PPS_NUMBER_INVALID) ||
		    (atPPSlist[iIndex].ulNext >= (ULONG)tNbrOfPPS &&
		     atPPSlist[iIndex].ulNext != PPS_NUMBER_INVALID) ||
		    (atPPSlist[iIndex].ulDir >= (ULONG)tNbrOfPPS &&
		     atPPSlist[iIndex].ulDir != PPS_NUMBER_INVALID)) {
			DBG_DEC(iIndex);
			DBG_DEC(atPPSlist[iIndex].ulPrevious);
			DBG_DEC(atPPSlist[iIndex].ulNext);
			DBG_DEC(atPPSlist[iIndex].ulDir);
			DBG_DEC(tNbrOfPPS);
			werr(0, "The Property Set Storage is damaged");
			atPPSlist = xfree(atPPSlist);
			return FALSE;
		}
	}

#if 0 /* defined(DEBUG) */
	DBG_MSG("Before");
	for (iIndex = 0; iIndex < (int)tNbrOfPPS; iIndex++) {
		DBG_MSG(atPPSlist[iIndex].szName);
		DBG_HEX(atPPSlist[iIndex].ulDir);
		DBG_HEX(atPPSlist[iIndex].ulPrevious);
		DBG_HEX(atPPSlist[iIndex].ulNext);
		DBG_DEC(atPPSlist[iIndex].ulSB);
		DBG_HEX(atPPSlist[iIndex].ulSize);
		DBG_DEC(atPPSlist[iIndex].iLevel);
	}
#endif /* DEBUG */

	/* Add level information to each entry */
	vComputePPSlevels(atPPSlist, &atPPSlist[iRootIndex], 0, 0);

	/* Check the entries on level 1 for the required information */
	NO_DBG_MSG("After");
	for (iIndex = 0; iIndex < (int)tNbrOfPPS; iIndex++) {
#if 0 /* defined(DEBUG) */
		DBG_MSG(atPPSlist[iIndex].szName);
		DBG_HEX(atPPSlist[iIndex].ulDir);
		DBG_HEX(atPPSlist[iIndex].ulPrevious);
		DBG_HEX(atPPSlist[iIndex].ulNext);
		DBG_DEC(atPPSlist[iIndex].ulSB);
		DBG_HEX(atPPSlist[iIndex].ulSize);
		DBG_DEC(atPPSlist[iIndex].iLevel);
#endif /* DEBUG */
		if (atPPSlist[iIndex].iLevel != 1 ||
		    atPPSlist[iIndex].ucType != 2 ||
		    atPPSlist[iIndex].szName[0] == '\0' ||
		    atPPSlist[iIndex].ulSize == 0) {
			/* This entry can be ignored */
			continue;
		}
		if (pPPS->tWordDocument.ulSize == 0 &&
		    STREQ(atPPSlist[iIndex].szName, "WordDocument")) {
			pPPS->tWordDocument.ulSB = atPPSlist[iIndex].ulSB;
			pPPS->tWordDocument.ulSize = atPPSlist[iIndex].ulSize;
			bWord = TRUE;
		} else if (pPPS->tData.ulSize == 0 &&
			   STREQ(atPPSlist[iIndex].szName, "Data")) {
			pPPS->tData.ulSB = atPPSlist[iIndex].ulSB;
			pPPS->tData.ulSize = atPPSlist[iIndex].ulSize;
		} else if (pPPS->t0Table.ulSize == 0 &&
			   STREQ(atPPSlist[iIndex].szName, "0Table")) {
			pPPS->t0Table.ulSB = atPPSlist[iIndex].ulSB;
			pPPS->t0Table.ulSize = atPPSlist[iIndex].ulSize;
		} else if (pPPS->t1Table.ulSize == 0 &&
			   STREQ(atPPSlist[iIndex].szName, "1Table")) {
			pPPS->t1Table.ulSB = atPPSlist[iIndex].ulSB;
			pPPS->t1Table.ulSize = atPPSlist[iIndex].ulSize;
		} else if (pPPS->tSummaryInfo.ulSize == 0 &&
			   STREQ(atPPSlist[iIndex].szName,
						"\005SummaryInformation")) {
			pPPS->tSummaryInfo.ulSB = atPPSlist[iIndex].ulSB;
			pPPS->tSummaryInfo.ulSize = atPPSlist[iIndex].ulSize;
		} else if (pPPS->tDocSummaryInfo.ulSize == 0 &&
			   STREQ(atPPSlist[iIndex].szName,
					"\005DocumentSummaryInformation")) {
			pPPS->tDocSummaryInfo.ulSB = atPPSlist[iIndex].ulSB;
			pPPS->tDocSummaryInfo.ulSize = atPPSlist[iIndex].ulSize;
		} else if (STREQ(atPPSlist[iIndex].szName, "Book") ||
			   STREQ(atPPSlist[iIndex].szName, "Workbook")) {
			bExcel = TRUE;
		}
	}

	/* Free the space for the Property Set Storage entries */
	atPPSlist = xfree(atPPSlist);

	/* Draw your conclusions */
	if (bWord) {
		return TRUE;
	}

	if (bExcel) {
		werr(0, "Sorry, but this is an Excel spreadsheet");
	} else {
		werr(0, "This OLE file does not contain a Word document");
	}
	return FALSE;
} /* end of bGetPPS */

/*
 * vGetBbdList - make a list of the places to find big blocks
 */
static void
vGetBbdList(FILE *pFile, int iNbr, ULONG *aulBbdList, ULONG ulOffset)
{
	int	iIndex;

	fail(pFile == NULL);
	fail(iNbr > 127);
	fail(aulBbdList == NULL);

	NO_DBG_DEC(iNbr);
	for (iIndex = 0; iIndex < iNbr; iIndex++) {
                aulBbdList[iIndex] =
                        ulReadLong(pFile, ulOffset + 4 * (ULONG)iIndex);
		NO_DBG_DEC(iIndex);
                NO_DBG_HEX(aulBbdList[iIndex]);
        }
} /* end of vGetBbdList */

/*
 * bGetDocumentText - make a list of the text blocks of a Word document
 *
 * Return TRUE when succesful, otherwise FALSE
 */
static BOOL
bGetDocumentText(FILE *pFile, const pps_info_type *pPPS,
	const ULONG *aulBBD, size_t tBBDLen,
	const ULONG *aulSBD, size_t tSBDLen,
	const UCHAR *aucHeader, int iWordVersion)
{
	ULONG	ulBeginOfText;
	ULONG	ulTextLen, ulFootnoteLen, ulEndnoteLen;
	ULONG	ulHdrFtrLen, ulMacroLen, ulAnnotationLen;
	ULONG	ulTextBoxLen, ulHdrTextBoxLen;
	UINT	uiQuickSaves;
	BOOL	bFarEastWord, bTemplate, bFastSaved, bEncrypted, bSuccess;
	USHORT	usIdent, usDocStatus;

	fail(pFile == NULL || pPPS == NULL);
	fail(aulBBD == NULL);
	fail(aulSBD == NULL);

	DBG_MSG("bGetDocumentText");

	/* Get the "magic number" from the header */
	usIdent = usGetWord(0x00, aucHeader);
	DBG_HEX(usIdent);
	bFarEastWord = usIdent == 0x8098 || usIdent == 0x8099 ||
			usIdent == 0xa697 || usIdent == 0xa699;
	/* Get the status flags from the header */
	usDocStatus = usGetWord(0x0a, aucHeader);
	DBG_HEX(usDocStatus);
	bTemplate = (usDocStatus & BIT(0)) != 0;
	DBG_MSG_C(bTemplate, "This document is a Template");
	bFastSaved = (usDocStatus & BIT(2)) != 0;
	uiQuickSaves = (UINT)(usDocStatus & 0x00f0) >> 4;
	DBG_MSG_C(bFastSaved, "This document is Fast Saved");
	DBG_DEC_C(bFastSaved, uiQuickSaves);
	bEncrypted = (usDocStatus & BIT(8)) != 0;
	if (bEncrypted) {
		werr(0, "Encrypted documents are not supported");
		return FALSE;
	}

	/* Get length information */
	ulBeginOfText = ulGetLong(0x18, aucHeader);
	DBG_HEX(ulBeginOfText);
	switch (iWordVersion) {
	case 6:
	case 7:
		ulTextLen = ulGetLong(0x34, aucHeader);
		ulFootnoteLen = ulGetLong(0x38, aucHeader);
		ulHdrFtrLen = ulGetLong(0x3c, aucHeader);
		ulMacroLen = ulGetLong(0x40, aucHeader);
		ulAnnotationLen = ulGetLong(0x44, aucHeader);
		ulEndnoteLen = ulGetLong(0x48, aucHeader);
		ulTextBoxLen = ulGetLong(0x4c, aucHeader);
		ulHdrTextBoxLen = ulGetLong(0x50, aucHeader);
		break;
	case 8:
		ulTextLen = ulGetLong(0x4c, aucHeader);
		ulFootnoteLen = ulGetLong(0x50, aucHeader);
		ulHdrFtrLen = ulGetLong(0x54, aucHeader);
		ulMacroLen = ulGetLong(0x58, aucHeader);
		ulAnnotationLen = ulGetLong(0x5c, aucHeader);
		ulEndnoteLen = ulGetLong(0x60, aucHeader);
		ulTextBoxLen = ulGetLong(0x64, aucHeader);
		ulHdrTextBoxLen = ulGetLong(0x68, aucHeader);
		break;
	default:
		werr(0, "This version of Word is not supported");
		return FALSE;
	}
	DBG_DEC(ulTextLen);
	DBG_DEC(ulFootnoteLen);
	DBG_DEC(ulHdrFtrLen);
	DBG_DEC(ulMacroLen);
	DBG_DEC(ulAnnotationLen);
	DBG_DEC(ulEndnoteLen);
	DBG_DEC(ulTextBoxLen);
	DBG_DEC(ulHdrTextBoxLen);

	/* Make a list of the text blocks */
	switch (iWordVersion) {
	case 6:
	case 7:
		if (bFastSaved) {
			bSuccess = bGet6DocumentText(pFile,
					bFarEastWord,
					pPPS->tWordDocument.ulSB,
					aulBBD, tBBDLen,
					aucHeader);
		} else {
		  	bSuccess = bAddTextBlocks(ulBeginOfText,
				ulTextLen +
				ulFootnoteLen +
				ulHdrFtrLen +
				ulMacroLen + ulAnnotationLen +
				ulEndnoteLen +
				ulTextBoxLen + ulHdrTextBoxLen,
				bFarEastWord,
				IGNORE_PROPMOD,
				pPPS->tWordDocument.ulSB,
				aulBBD, tBBDLen);
		}
		break;
	case 8:
		bSuccess = bGet8DocumentText(pFile,
				pPPS,
				aulBBD, tBBDLen, aulSBD, tSBDLen,
				aucHeader);
		break;
	default:
		werr(0, "This version of Word is not supported");
		bSuccess = FALSE;
		break;
	}

	if (bSuccess) {
		vSplitBlockList(pFile,
				ulTextLen,
				ulFootnoteLen,
				ulHdrFtrLen,
				ulMacroLen,
				ulAnnotationLen,
				ulEndnoteLen,
				ulTextBoxLen,
				ulHdrTextBoxLen,
				!bFastSaved && iWordVersion == 8);
	} else {
		vDestroyTextBlockList();
		werr(0, "I can't find the text of this document");
	}
	return bSuccess;
} /* end of bGetDocumentText */

/*
 * vGetDocumentData - make a list of the data blocks of a Word document
 */
static void
vGetDocumentData(FILE *pFile, const pps_info_type *pPPS,
	const ULONG *aulBBD, size_t tBBDLen,
	const UCHAR *aucHeader, int iWordVersion)
{
	options_type	tOptions;
	ULONG	ulBeginOfText;
	BOOL	bFastSaved, bHasImages, bSuccess;
	USHORT	usDocStatus;

	fail(pFile == NULL);
	fail(pPPS == NULL);
	fail(aulBBD == NULL);

	/* Get the options */
	vGetOptions(&tOptions);

	/* Get the status flags from the header */
	usDocStatus = usGetWord(0x0a, aucHeader);
	DBG_HEX(usDocStatus);
	bFastSaved = (usDocStatus & BIT(2)) != 0;
	bHasImages = (usDocStatus & BIT(3)) != 0;

	if (!bHasImages ||
	    tOptions.eConversionType == conversion_text ||
	    tOptions.eConversionType == conversion_fmt_text ||
#if CR3_ANTIWORD_PATCH!=1
        tOptions.eConversionType == conversion_xml ||
#endif
	    tOptions.eImageLevel == level_no_images) {
		/*
		 * No images in the document or text-only output or
		 * no images wanted, so no data blocks will be needed
		 */
		vDestroyDataBlockList();
		return;
	}

	/* Get length information */
	ulBeginOfText = ulGetLong(0x18, aucHeader);
	DBG_HEX(ulBeginOfText);

	/* Make a list of the data blocks */
	switch (iWordVersion) {
	case 6:
	case 7:
		/*
		 * The data blocks are in the text stream. The text stream
		 * is in "fast saved" format or "normal saved" format
		 */
		if (bFastSaved) {
			bSuccess = bGet6DocumentData(pFile,
					pPPS->tWordDocument.ulSB,
					aulBBD, tBBDLen,
					aucHeader);
		} else {
		  	bSuccess = bAddDataBlocks(ulBeginOfText,
					(ULONG)LONG_MAX,
					pPPS->tWordDocument.ulSB,
					aulBBD, tBBDLen);
		}
		break;
	case 8:
		/*
		 * The data blocks are in the data stream. The data stream
		 * is always in "normal saved" format
		 */
		bSuccess = bAddDataBlocks(0, (ULONG)LONG_MAX,
				pPPS->tData.ulSB, aulBBD, tBBDLen);
		break;
	default:
		werr(0, "This version of Word is not supported");
		bSuccess = FALSE;
		break;
	}

	if (!bSuccess) {
		vDestroyDataBlockList();
		werr(0, "I can't find the data of this document");
	}
} /* end of vGetDocumentData */

/*
 * iInitDocumentOLE - initialize an OLE document
 *
 * Returns the version of Word that made the document or -1
 */
int
iInitDocumentOLE(FILE *pFile, long lFilesize)
{
	pps_info_type	PPS_info;
	ULONG	*aulBBD, *aulSBD;
	ULONG	*aulRootList, *aulBbdList, *aulSbdList;
	ULONG	ulBdbListStart, ulAdditionalBBDlist;
	ULONG	ulRootStartblock, ulSbdStartblock, ulSBLstartblock;
	ULONG	ulStart, ulTmp;
	long	lMaxBlock;
	size_t	tBBDLen, tSBDLen, tNumBbdBlocks, tRootListLen;
	int	iWordVersion, iIndex, iToGo;
	BOOL	bSuccess;
	USHORT	usIdent, usDocStatus;
	UCHAR	aucHeader[HEADER_SIZE];

	fail(pFile == NULL);

	lMaxBlock = lFilesize / BIG_BLOCK_SIZE - 2;
	DBG_DEC(lMaxBlock);
	if (lMaxBlock < 1) {
		return -1;
	}
	tBBDLen = (size_t)(lMaxBlock + 1);
	tNumBbdBlocks = (size_t)ulReadLong(pFile, 0x2c);
	DBG_DEC(tNumBbdBlocks);
	ulRootStartblock = ulReadLong(pFile, 0x30);
	DBG_DEC(ulRootStartblock);
	ulSbdStartblock = ulReadLong(pFile, 0x3c);
	DBG_DEC(ulSbdStartblock);
	ulAdditionalBBDlist = ulReadLong(pFile, 0x44);
	DBG_HEX(ulAdditionalBBDlist);
	ulSBLstartblock = ulReadLong(pFile,
			(ulRootStartblock + 1) * BIG_BLOCK_SIZE + 0x74);
	DBG_DEC(ulSBLstartblock);
	tSBDLen = (size_t)(ulReadLong(pFile,
			(ulRootStartblock + 1) * BIG_BLOCK_SIZE + 0x78) /
			SMALL_BLOCK_SIZE);
	/* All to be xcalloc-ed pointers to NULL */
	aulRootList = NULL;
	aulSbdList = NULL;
	aulBbdList = NULL;
	aulSBD = NULL;
	aulBBD = NULL;
/* Big Block Depot */
	aulBbdList = xcalloc(tNumBbdBlocks, sizeof(ULONG));
	aulBBD = xcalloc(tBBDLen, sizeof(ULONG));
	iToGo = (int)tNumBbdBlocks;
	vGetBbdList(pFile, min(iToGo, 109),  aulBbdList, 0x4c);
	ulStart = 109;
	iToGo -= 109;
	while (ulAdditionalBBDlist != END_OF_CHAIN && iToGo > 0) {
		ulBdbListStart = (ulAdditionalBBDlist + 1) * BIG_BLOCK_SIZE;
		vGetBbdList(pFile, min(iToGo, 127),
					aulBbdList + ulStart, ulBdbListStart);
		ulAdditionalBBDlist = ulReadLong(pFile,
					ulBdbListStart + 4 * 127);
		DBG_DEC(ulAdditionalBBDlist);
		DBG_HEX(ulAdditionalBBDlist);
		ulStart += 127;
		iToGo -= 127;
	}
	if (!bGetBBD(pFile, aulBbdList, tNumBbdBlocks, aulBBD, tBBDLen)) {
		FREE_ALL();
		return -1;
	}
	aulBbdList = xfree(aulBbdList);
/* Small Block Depot */
	aulSbdList = xcalloc(tBBDLen, sizeof(ULONG));
	aulSBD = xcalloc(tSBDLen, sizeof(ULONG));
	for (iIndex = 0, ulTmp = ulSbdStartblock;
	     iIndex < (int)tBBDLen && ulTmp != END_OF_CHAIN;
	     iIndex++, ulTmp = aulBBD[ulTmp]) {
		if (ulTmp >= (ULONG)tBBDLen) {
			DBG_DEC(ulTmp);
			DBG_DEC(tBBDLen);
			werr(1, "The Big Block Depot is damaged");
		}
		aulSbdList[iIndex] = ulTmp;
		NO_DBG_HEX(aulSbdList[iIndex]);
	}
	if (!bGetSBD(pFile, aulSbdList, tBBDLen, aulSBD, tSBDLen)) {
		FREE_ALL();
		return -1;
	}
	aulSbdList = xfree(aulSbdList);
/* Root list */
	for (tRootListLen = 0, ulTmp = ulRootStartblock;
	     tRootListLen < tBBDLen && ulTmp != END_OF_CHAIN;
	     tRootListLen++, ulTmp = aulBBD[ulTmp]) {
		if (ulTmp >= (ULONG)tBBDLen) {
			DBG_DEC(ulTmp);
			DBG_DEC(tBBDLen);
			werr(1, "The Big Block Depot is damaged");
		}
	}
	if (tRootListLen == 0) {
		werr(0, "No Rootlist found");
		FREE_ALL();
		return -1;
	}
	aulRootList = xcalloc(tRootListLen, sizeof(ULONG));
	for (iIndex = 0, ulTmp = ulRootStartblock;
	     iIndex < (int)tBBDLen && ulTmp != END_OF_CHAIN;
	     iIndex++, ulTmp = aulBBD[ulTmp]) {
		if (ulTmp >= (ULONG)tBBDLen) {
			DBG_DEC(ulTmp);
			DBG_DEC(tBBDLen);
			werr(1, "The Big Block Depot is damaged");
		}
		aulRootList[iIndex] = ulTmp;
		NO_DBG_DEC(aulRootList[iIndex]);
	}
	fail(tRootListLen != (size_t)iIndex);
	bSuccess = bGetPPS(pFile, aulRootList, tRootListLen, &PPS_info);
	aulRootList = xfree(aulRootList);
	if (!bSuccess) {
		FREE_ALL();
		return -1;
	}
/* Small block list */
	if (!bCreateSmallBlockList(ulSBLstartblock, aulBBD, tBBDLen)) {
		FREE_ALL();
		return -1;
	}

	if (PPS_info.tWordDocument.ulSize < MIN_SIZE_FOR_BBD_USE) {
		DBG_DEC(PPS_info.tWordDocument.ulSize);
		FREE_ALL();
		werr(0, "I'm afraid the text stream of this file "
			"is too small to handle.");
		return -1;
	}
	/* Read the headerblock */
	if (!bReadBuffer(pFile, PPS_info.tWordDocument.ulSB,
			aulBBD, tBBDLen, BIG_BLOCK_SIZE,
			aucHeader, 0, HEADER_SIZE)) {
		FREE_ALL();
		return -1;
	}
	usIdent = usGetWord(0x00, aucHeader);
	DBG_HEX(usIdent);
	fail(usIdent != 0x8098 &&	/* Word 7 for oriental languages */
	     usIdent != 0x8099 &&	/* Word 7 for oriental languages */
	     usIdent != 0xa5dc &&	/* Word 6 & 7 */
	     usIdent != 0xa5ec &&	/* Word 7 & 97 & 98 */
	     usIdent != 0xa697 &&	/* Word 7 for oriental languages */
	     usIdent != 0xa699);	/* Word 7 for oriental languages */
	iWordVersion = iGetVersionNumber(aucHeader);
	if (iWordVersion < 6) {
		FREE_ALL();
		werr(0, "This file is from a version of Word before Word 6.");
		return -1;
	}

	/* Get the status flags from the header */
	usDocStatus = usGetWord(0x0a, aucHeader);
        if (usDocStatus & BIT(9)) {
		PPS_info.tTable = PPS_info.t1Table;
	} else {
		PPS_info.tTable = PPS_info.t0Table;
	}
	/* Clean the entries that should not be used */
	memset(&PPS_info.t0Table, 0, sizeof(PPS_info.t0Table));
	memset(&PPS_info.t1Table, 0, sizeof(PPS_info.t1Table));

	bSuccess = bGetDocumentText(pFile, &PPS_info,
			aulBBD, tBBDLen, aulSBD, tSBDLen,
			aucHeader, iWordVersion);
	if (bSuccess) {
		vGetDocumentData(pFile, &PPS_info,
			aulBBD, tBBDLen, aucHeader, iWordVersion);
		vGetPropertyInfo(pFile, &PPS_info,
			aulBBD, tBBDLen, aulSBD, tSBDLen,
			aucHeader, iWordVersion);
		vSetDefaultTabWidth(pFile, &PPS_info,
			aulBBD, tBBDLen, aulSBD, tSBDLen,
			aucHeader, iWordVersion);
		vGetNotesInfo(pFile, &PPS_info,
			aulBBD, tBBDLen, aulSBD, tSBDLen,
			aucHeader, iWordVersion);
	}
	FREE_ALL();
	return bSuccess ? iWordVersion : -1;
} /* end of iInitDocumentOLE */
