// BMIO.CPP  Copyright (C) Microsoft Corporation 1995, All Rights reserved.

#include "stdafx.h"

#include <pshpack1.h>

#include "hccom.h"
#include "bmio.h"
#include "cread.h"
#include "skip.h"
#include "zeck.h"
#include "pack.h"

// file mode flags

#define FS_READ_WRITE		0x00	// file (FS) is readwrite
#define FS_OPEN_READ_WRITE	0x00	// file (FS) is opened in read/write mode

#define FS_READ_ONLY		0x01	// file (FS) is readonly
#define FS_OPEN_READ_ONLY	0x02	// file (FS) is opened in readonly mode

#define FS_IS_DIRECTORY 	0x04	// file is really the FS directory
#define FS_DIRTY			0x08	// file (FS) is dirty and needs writing
#define FS_NO_BLOCK 		0x10	// file has no associated block yet
#define FS_CDROM			0x20	// align file optimally for CDROM

// header of a read/write file block

typedef struct {
	LONG lcbBlock; // block size (including header)
	LONG lcbFile;  // file size (not including header)
	BYTE bPerms;   // low byte of file permissions
} FH;

static int STDCALL LcbUncompressHb(PBYTE pbSrc, PBYTE pbDest, int cbSrc);
static RC_TYPE STDCALL RcCopyToTempFile(QRWFO qrwfo);
static BOOL STDCALL FPlungeQfshr(QFSHR qfshr);

static int cbGraphics;

void ClearCbGraphics(void)
{
	cbGraphics = 0;
}

int GetCbGraphics(void)
{
	return cbGraphics;
}

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

	FUNCTION:	HbmhReadHelp30Fid

	PURPOSE:	Read a BMP file in either Windows or OS/2 format

	PARAMETERS:
		pcrFile
		pibmh

	RETURNS:	Handle to BMH structure, RGBQUAD colors, and bitmap bits

	COMMENTS:
		RLE compression is not supported. It should be...

	MODIFICATION DATES:
		13-Feb-1994 [ralphw] -- complete rewrite
		12-Aug-1995 [ralphw] -- moved to hwdll

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

HBMH STDCALL HbmhReadHelp30Fid(CRead* pcrFile, int* pibmh)
{
	int cb = sizeof(BGH) + ((*pibmh + 1) * sizeof(DWORD));
	CMem memBgh(cb);
	BGH* pbgh = (BGH*) memBgh.pb;

	pcrFile->seek(0);
	if (pcrFile->read(pbgh, cb) != cb)
		return hbmhInvalid;	

	ASSERT(*pibmh < pbgh->cbmhMac);

	if (*pibmh == pbgh->cbmhMac - 1) {
		if (*pibmh > 0)	{
#if 1
			cb = pcrFile->seek(0, SK_END) - pbgh->acBmh[*pibmh];
#endif
			// This ASSERT happens, but which is right?
#if 0
			ASSERT(pbgh->acBmh[*pibmh] - pbgh->acBmh[*pibmh - 1] ==
				pcrFile->seek(0, SK_END) - pbgh->acBmh[*pibmh]);
//			cb = pbgh->acBmh[*pibmh] - pbgh->acBmh[*pibmh - 1];
#endif		
		}
		else
			cb = pcrFile->seek(0, SK_END) - pbgh->acBmh[*pibmh];
	}
	else
		cb = pbgh->acBmh[*pibmh + 1] - pbgh->acBmh[*pibmh];

	CMem mem(cb);

	pcrFile->seek(pbgh->acBmh[*pibmh]);
	if (pcrFile->read(mem.pb, cb) != cb)
		return hbmhInvalid;

	if (*pibmh == 0)
		*pibmh = pbgh->cbmhMac;

	return HbmhExpandQv(mem.pb);
}

/***************************************************************************
 *
 -	Name		HbmhExpandQv
 -
 *	Purpose
 *	  This function decompresses the bitmap from its disk format to an
 *	in memory bitmap header, including bitmap and hotspot data.
 *
 *	Arguments
 *	  A pointer to a buffer containing the bitmap data as read off disk.
 *
 *	Returns
 *	  A handle to the bitmap header.  Returns hbmhOOM on out of memory.
 *	Note that this is the same as NULL.  Also returns hbmhInvalid on
 *	invalid format.  This handle is non-discardable, but the code that
 *	deals with qbmi->hbmh can deal with discardable blocks, so after
 *	initialization this can be made discardable.
 *
 *	+++
 *
 *	Notes
 *
 ***************************************************************************/

HBMH STDCALL HbmhExpandQv(void* qv)
{
	PBMH qbmh;
	BMH bmh;
	int cbBits, cbUncompressedBits;
	PBYTE pDst;

	PBYTE pBase = (PBYTE) qv;
	bmh.bmFormat = *((PBYTE) qv);
	qv = QFromQCb(qv, sizeof(BYTE));
	bmh.fCompressed = *((PBYTE) qv);
	qv = QFromQCb(qv, sizeof(BYTE));

	switch(bmh.bmFormat) {
		case bmWbitmap:
		case bmDIB:
			qv = QVSkipQGB(qv, (&bmh.w.dib.biXPelsPerMeter));
			qv = QVSkipQGB(qv, (&bmh.w.dib.biYPelsPerMeter));
			qv = QVSkipQGA(qv, (&bmh.w.dib.biPlanes));
			qv = QVSkipQGA(qv, (&bmh.w.dib.biBitCount));

			qv = QVSkipQGB(qv, (&bmh.w.dib.biWidth));
			qv = QVSkipQGB(qv, (&bmh.w.dib.biHeight));
			qv = QVSkipQGB(qv, (&bmh.w.dib.biClrUsed));
			qv = QVSkipQGB(qv, (&bmh.w.dib.biClrImportant));

			qv = QVSkipQGB(qv, (&bmh.cbSizeBits));
			qv = QVSkipQGB(qv, (&bmh.cbSizeExtra));

			bmh.cbOffsetBits = *((DWORD *) qv);
			qv = (PBYTE) qv + sizeof(DWORD);
			bmh.cbOffsetExtra = *((DWORD *) qv);
			qv = (PBYTE) qv + sizeof(DWORD);

			// Fix up constant fields in DIB structure:

			bmh.w.dib.biSize = sizeof(BITMAPINFOHEADER);
			bmh.w.dib.biCompression = 0L;
			bmh.w.dib.biSizeImage = 0L;

			// Determine size of bitmap header plus all data

			if (bmh.fCompressed)
			  cbBits = LAlignLong (bmh.w.dib.biWidth * bmh.w.dib.biBitCount) *
						bmh.w.dib.biHeight;
			else
			  cbBits = bmh.cbSizeBits;

			qbmh = (BMH*) lcCalloc(LcbSizeQbmh(&bmh) + cbBits
							+ bmh.cbSizeExtra);

			// Copy header and color table:

			memmove(qbmh, &bmh, sizeof(BMH) - 2 * sizeof(RGBQUAD));
			memmove(qbmh->rgrgb, qv, sizeof(RGBQUAD) * (int) bmh.w.dib.biClrUsed);

			// Copy bits, decompressing if necessary:

			qbmh->cbOffsetBits = LcbSizeQbmh(qbmh);
			if (bmh.fCompressed == BMH_COMPRESS_30) {
			  qbmh->cbSizeBits = LcbUncompressHb(pBase + bmh.cbOffsetBits,
				(PBYTE) QFromQCb(qbmh, qbmh->cbOffsetBits), bmh.cbSizeBits);
			  ASSERT(qbmh->cbSizeBits <= cbBits);
			  }
			else if (bmh.fCompressed == BMH_COMPRESS_ZECK) {
			  qbmh->cbSizeBits = LcbUncompressZeck(
				pBase + bmh.cbOffsetBits,
				(PBYTE) qbmh + qbmh->cbOffsetBits, bmh.cbSizeBits);
			  ASSERT(qbmh->cbSizeBits <= cbBits);
			  }
			else
			  memmove(QFromQCb(qbmh, qbmh->cbOffsetBits),
					  pBase + bmh.cbOffsetBits,
					  bmh.cbSizeBits);
			qbmh->fCompressed = BMH_COMPRESS_NONE;	// bits are no longer compressed

			// Copy extra info:

			qbmh->cbOffsetExtra = qbmh->cbOffsetBits + qbmh->cbSizeBits;
			if (bmh.cbSizeExtra)
				memmove((PBYTE) qbmh + qbmh->cbOffsetExtra,
					pBase + bmh.cbOffsetExtra, bmh.cbSizeExtra);
			break;

		case bmWmetafile:
		  qv = QVSkipQGA(qv, (&bmh.w.mf.mm));
		  bmh.w.mf.xExt = *(INT16 *) qv;
		  qv = (PBYTE) qv + sizeof(INT16);
		  bmh.w.mf.yExt = *(INT16 *) qv;
		  qv = (PBYTE) qv + sizeof(INT16);

		  qv = QVSkipQGB(qv, &cbUncompressedBits);
		  qv = QVSkipQGB(qv, (&bmh.cbSizeBits));
		  qv = QVSkipQGB(qv, (&bmh.cbSizeExtra));

		  bmh.cbOffsetBits = *((DWORD *) qv);
		  qv = (PBYTE) qv + sizeof(DWORD);
		  bmh.cbOffsetExtra = *((DWORD *) qv);
		  qv = (PBYTE) qv + sizeof(DWORD);

		  qbmh = (BMH*) lcCalloc(sizeof(BMH) + bmh.cbSizeExtra);

		  *qbmh = bmh;
		  qbmh->cbOffsetExtra = sizeof(BMH);
		  memmove((PBYTE) qbmh + qbmh->cbOffsetExtra,
				pBase + bmh.cbOffsetExtra, bmh.cbSizeExtra);

		  qbmh->w.mf.hMF =
			(HMETAFILE) lcMalloc(cbUncompressedBits);
		  if (qbmh->w.mf.hMF == NULL) {
			lcFree(qbmh);
			return hbmhOOM;
		  }

		  // REVIEW: 18-Sep-1993 [ralphw] -- Don't lock!

		  pDst = (PBYTE) qbmh->w.mf.hMF;
		  switch (bmh.fCompressed) {
			case BMH_COMPRESS_NONE:
			  memmove(pDst, pBase + bmh.cbOffsetBits, bmh.cbSizeBits);
			  break;
			case BMH_COMPRESS_30:
			  LcbUncompressHb(pBase + bmh.cbOffsetBits, pDst, bmh.cbSizeBits);
			  break;
			case BMH_COMPRESS_ZECK:
			  LcbUncompressZeck(pBase + bmh.cbOffsetBits,
				  pDst, bmh.cbSizeBits);
			  break;
			default:
			  ASSERT(FALSE);
		  }

		  // Invalidate this field, as the bits are in a separate handle:

		  qbmh->cbOffsetBits = 0L;

		  qbmh->cbSizeBits = cbUncompressedBits;
		  qbmh->fCompressed = BMH_COMPRESS_NONE;

		  break;

		default:
		  return hbmhInvalid;
	}

	return (HBMH) qbmh;
}

/***************************************************************************
 *
 -	Name		LcbUncompressHb
 -
 *	Purpose
 *	  Decompresses the bits in pbSrc.
 *
 *	Arguments
 *	  pbSrc:	 Huge pointer to compressed bits.
 *	  pbDest:	 Buffer to copy decompressed bits to.
 *	  cbSrc:	Number of bytes in pbSrc.
 *
 *	Returns
 *	  Number of bytes copied to pbDest.  This can only be used for
 *	real mode error checking, as the maximum size of pbDest must
 *	be determined before decompression.
 *
 *	+++
 *
 *	Notes
 *
 ***************************************************************************/

#define fRLEBit  0x80
const int MAX_RUN = 127;

static int STDCALL LcbUncompressHb(PBYTE pbSrc, PBYTE pbDest, int cbSrc)
{
	PBYTE pbStart;
	WORD cbRun;
	BYTE ch;

	pbStart = pbDest;

	while (cbSrc-- > 0) {
		cbRun = *pbSrc++;
		if (cbRun & fRLEBit) {
			cbRun -= fRLEBit;
			cbSrc -= cbRun;
			while (cbRun-- > 0)
				*pbDest++ = *pbSrc++;
		}
		else {
			ch = *pbSrc++;
			while (cbRun-- > 0)
				*pbDest++ = ch;
			cbSrc--;
		}
	}

	return (int) (pbDest - pbStart);
}

/***************************************************************************
 *
 -	Name		RcWriteRgrbmh
 -
 *	Purpose
 *	  Writes out a set of bitmap headers into a single bitmap group.
 *	Can write to a DOS file and/or a FS file.
 *
 *	Arguments
 *	  crbmh:	 Number of bitmaps in bitmap array.
 *	  rgrbmh:	 Array of pointers to bitmap headers.
 *	  hf:		 FS file to write to (may be nil).
 *	  fmFile:	 DOS file to write to (may be nil).
 *
 *	Returns
 *	  RC_Success if successful, rc error code otherwise.  RC_Failure
 *	means that it actually succeeded, but that the bitmap could not
 *	be compressed.
 *
 *	+++
 *
 *
 ***************************************************************************/

__inline LONG Tell(HANDLE hf) {
	return _llseek((int) hf, 0, SEEK_CUR);
};

// Special class for RcWriteRgrbmh so we can use the same functions
//	irregardless of whether we are dealing with a fid or hf

class CHfFid
{
public:
	CHfFid(PCSTR pszFileName) {
		hf = _lcreat(pszFileName, 0);
		fhf = FALSE;
		};

	CHfFid(HF hfAlreadyOpened) {
		hf = (HFILE) hfAlreadyOpened;
		fhf = TRUE;
		};

	~CHfFid(void) {
			if (!fhf && hf != HFILE_ERROR)
				_lclose(hf);
			};

	int STDCALL seek(int lPos, int wOrg) {
		return (fhf) ?
			LSeekHf((HF) hf, lPos, wOrg) :
			_llseek(hf, lPos, wOrg); };
	int STDCALL tell(void) {
		return (fhf) ?
			((QRWFO) hf)->lifCurrent :
			Tell((HANDLE) hf); };
	int STDCALL write(void* qv, int lcb) {
		if (fhf) {
			RC_TYPE rc = RcWriteHf((HF) hf, qv, lcb);
			return (rc == RC_Success) ? lcb : -(int)rc;
		}
		else
			return _lwrite(hf, (LPCSTR) qv, lcb);
		};

	RC_TYPE GetRcError(void) { return (RC_TYPE) (GetLastError() & ~SETERROR_MASK); };

	HFILE hf;
protected:
	BOOL fhf;
};

class CFMDirCurrent
{
public:
	FM fm;

	CFMDirCurrent(PCSTR szFileName) {
		fm = FmNewSzDir(szFileName, DIR_CURRENT); };

	~CFMDirCurrent() {
		if (fm)
			lcFree(fm);
		};

	void* Ptr(void) { return fm; };
};

RC_TYPE STDCALL RcWriteRgrbmh(int crbmh, PBMH * rgrbmh, HF hf,
	PSTR qchFile, BOOL fTransparent, FM fmSrc, int TypeCompression)
{
	int 	lcb, lcbBits, crgbColorTable, lcbUncompressedBits;
	BMH 	bmh;
	PVOID	pvSrcBits, pvCompressedBits;
	int 	ibmh;
	RC_TYPE rc = RC_Success;
	CMem* pRleBits = NULL;
	CMem* pRleZeckBits = NULL;

	ASSERT(qchFile != NULL || hf != NULL);

	CHfFid* pcfile;

	if (hf)
		pcfile = new CHfFid(hf);
	else {
		CFMDirCurrent cfmFile(qchFile);
		if (!cfmFile.fm)
			return RC_OutOfMemory;

		pcfile = new CHfFid(qchFile);

		if (pcfile->hf == HFILE_ERROR)
			return pcfile->GetRcError();
	}

	UINT lcbBgh = sizeof(BGH) + sizeof(DWORD) * (crbmh - 1);
	CMem bgh(lcbBgh);
	BGH* pbgh = (BGH*) bgh.pb;

	// REVIEW: huh? Why does WinHelp care about this flag? The compression
	// flag should specify what to do, not this general purpose flag

	pbgh->wVersion = (TypeCompression & COMPRESS_BMP_ZECK) ?
		BMP_VERSION3 : BMP_VERSION2;
	pbgh->cbmhMac = crbmh;

	pcfile->seek(lcbBgh, SEEK_SET);

	for (ibmh = 0; ibmh < crbmh; ++ibmh) {

		// Put offset to bitmap in group header

		pbgh->acBmh[ibmh] = pcfile->tell();

		/*
		 * Bitmaps must be uncompressed in memory, and get compressed when
		 * they are translated to disk. Currently, we only support Windows
		 * bitmaps, DIBs, and metafiles.
		 */

		PBMH pbmh = rgrbmh[ibmh];
		ASSERT(pbmh->bmFormat == bmWbitmap || pbmh->bmFormat == bmDIB ||
				 pbmh->bmFormat == bmWmetafile);
		ASSERT(pbmh->fCompressed == BMH_COMPRESS_NONE);

		if (pbmh->bmFormat == bmWmetafile) {
			crgbColorTable = 0;
			if (pbmh->cbOffsetBits == 0)
				pvSrcBits = (void*) pbmh->w.mf.hMF;
			else
				pvSrcBits = (PBYTE) pbmh + pbmh->cbOffsetBits;
		}
		else {

			/*
			 * We must make sure that the number of bits we actually
			 * write out will not overflow the buffer we will allocate at
			 * run time.
			 */

			crgbColorTable = pbmh->w.dib.biClrUsed;
			pbmh->cbSizeBits = min(pbmh->cbSizeBits,
				LAlignLong(pbmh->w.dib.biWidth * pbmh->w.dib.biBitCount) *
				pbmh->w.dib.biHeight);

			pvSrcBits = QFromQCb(pbmh, pbmh->cbOffsetBits);
		}

		/*
		 * We clear out these values because Alchemy creates bogus ones
		 * and because WinHelp 3.1 doesn't handle them correctly. We then
		 * reserve biYPelsPerMeter for use with Zeck+RLE compression.
		 */

		if (pbmh->bmFormat != bmWmetafile) {
			pbmh->w.dib.biXPelsPerMeter = 0;
			pbmh->w.dib.biYPelsPerMeter = 0;
		}
		lcbUncompressedBits = pbmh->cbSizeBits;

		/*
		 * Allocate enough for a Zeck byte every 8 bytes, plus 1 for the
		 * remainder of less than 8 bytes.
		 */

		// REVIEW: Is this sufficient for RLE?

		int cbMem = (DWORD) pbmh->cbSizeBits + (pbmh->cbSizeBits >> 3) + 512;
		CMem bits(cbMem);

		ASSERT(pvSrcBits);

		// REVIEW: BMH_COMPRESS_RLE_ZECK has been added to WinHelp, but
		// we can't support this until we've had a chance to debug the
		// code both here and in WinHelp.

		int cRLE;
		int cZeckRle;

		// Zeck only compression?

		if (TypeCompression & COMPRESS_BMP_ZECK &&
				!(TypeCompression & COMPRESS_BMP_RLE)) {
			lcbBits = LcbCompressZeck((PBYTE) pvSrcBits,
				bits.pb, pbmh->cbSizeBits, cbMem);
			ConfirmOrDie(lcbBits < cbMem);
			if (lcbBits >= pbmh->cbSizeBits) {
				pvCompressedBits = pvSrcBits;
				lcbBits = pbmh->cbSizeBits;
				pbmh->fCompressed = (BYTE) BMH_COMPRESS_NONE;
			}
			else {
				pvCompressedBits = bits.pb;
				pbmh->fCompressed = (BYTE) BMH_COMPRESS_ZECK;
			}
		}

		// RLE only compression?

		else if (TypeCompression & COMPRESS_BMP_RLE &&
				!(TypeCompression & COMPRESS_BMP_ZECK)) {
			lcbBits = RleCompress((PBYTE) pvSrcBits,
				bits.pb, pbmh->cbSizeBits);
			ConfirmOrDie(lcbBits < cbMem);
			if (lcbBits >= pbmh->cbSizeBits) {
				pvCompressedBits = pvSrcBits;
				lcbBits = pbmh->cbSizeBits;
				pbmh->fCompressed = BMH_COMPRESS_NONE;
			}
			else {
				pvCompressedBits = bits.pb;
				pbmh->fCompressed = BMH_COMPRESS_30;
			}
		}

		// Use whatever compression gets the best results

		else {

			// RLE compression

			pRleBits = new CMem(cbMem);
			cRLE = RleCompress((PBYTE) pvSrcBits, pRleBits->pb,
				pbmh->cbSizeBits);
			ConfirmOrDie(cRLE < cbMem);
			pRleBits->resize(cRLE);

			// RLE + Zeck compression

			pRleZeckBits = new CMem(cbMem);
			cZeckRle = LcbCompressZeck(pRleBits->pb, pRleZeckBits->pb, cRLE,
				cbMem);
			ConfirmOrDie(cZeckRle < cbMem);
			pRleZeckBits->resize(cZeckRle);
#ifdef _DEBUG
			lcHeapCheck();
#endif

			// Zeck compression

			lcbBits = LcbCompressZeck((PBYTE) pvSrcBits,
				bits.pb, pbmh->cbSizeBits, cbMem);
			ConfirmOrDie(lcbBits < cbMem);

			/*
			 * At this point we have RLE, RLE+Zeck and Zeck compression.
			 * Now we need to figure what gave us the best compression
			 * and act accordingly.
			 */

			if (cRLE < pbmh->cbSizeBits && cRLE < lcbBits &&
					cRLE < cZeckRle) { // RLE?
				delete pRleZeckBits;
				pRleZeckBits = NULL;
				pvCompressedBits = pRleBits->pb;
				lcbBits = cRLE;
				pbmh->fCompressed = BMH_COMPRESS_30;
			}

			// Can't combine compressions with metafiles because
			// pbmh->w.dib.biYPelsPerMeter doesn't exist in a metafile structure

			else if (pbmh->bmFormat != bmWmetafile &&
					cZeckRle < pbmh->cbSizeBits && cZeckRle < cRLE &&
					cZeckRle < lcbBits) { // RLE + Zeck?
				delete pRleBits;
				pRleBits = NULL;
				pvCompressedBits = pRleZeckBits->pb;
				lcbBits = cZeckRle;
				pbmh->fCompressed = BMH_COMPRESS_RLE_ZECK;

				/*
				 * We store the size of the block needed to decompress
				 * into the RLE block. This lets WinHelp know exactly how
				 * much memory to allocate in order to decompress the
				 * bitmap. We don't allow WinHelp to use these values the
				 * way they were originally intended both because WinHelp
				 * 3.1 didn't deal with them correctly and because Alchemy
				 * puts in bogus values.
				 */

				pbmh->w.dib.biYPelsPerMeter = cRLE;
			}
			else if (lcbBits < pbmh->cbSizeBits && lcbBits < cRLE &&
					lcbBits < cZeckRle) { // Zeck?
				delete pRleBits;
				pRleBits = NULL;
				delete pRleZeckBits;
				pRleZeckBits = NULL;
				pvCompressedBits = bits.pb;
				pbmh->fCompressed = BMH_COMPRESS_ZECK;
			}
			else { // no compression is better
				delete pRleBits;
				pRleBits = NULL;
				delete pRleZeckBits;
				pRleZeckBits = NULL;
				pvCompressedBits = pvSrcBits;
				lcbBits = pbmh->cbSizeBits;
				pbmh->fCompressed = BMH_COMPRESS_NONE;
			}
		}

		// Now, compress the header into the stack.

		bmh.bmFormat = pbmh->bmFormat;
		bmh.fCompressed = pbmh->fCompressed;
		void* pv = PfromPcb(&bmh, sizeof(WORD));

		switch (pbmh->bmFormat) {
			case bmWbitmap:
			case bmDIB:

				/*
				 * Note: These fields must be written in the same order that
				 * they are read in HbmhExpandQv() in bitmap.c
				 */

				pv = PVMakeQGB(pbmh->w.dib.biXPelsPerMeter, pv);
				pv = PVMakeQGB(pbmh->w.dib.biYPelsPerMeter, pv);
				pv = PVMakeQGA(pbmh->w.dib.biPlanes, pv);
				pv = PVMakeQGA(pbmh->w.dib.biBitCount, pv);

				pv = PVMakeQGB(pbmh->w.dib.biWidth, pv);
				pv = PVMakeQGB(pbmh->w.dib.biHeight, pv);
				pv = PVMakeQGB(pbmh->w.dib.biClrUsed, pv);

				if (fTransparent) {
					if (pbmh->w.dib.biBitCount != 1)
						pbmh->w.dib.biClrImportant = 1;
				}

				pv = PVMakeQGB(pbmh->w.dib.biClrImportant, pv);

				ASSERT(pbmh->w.dib.biCompression == 0L);

				break;

			case bmWmetafile:
				pv = PVMakeQGA((UINT) pbmh->w.mf.mm, pv);
				*(INT16 *) pv = (INT16)pbmh->w.mf.xExt;
				pv = PfromPcb(pv, sizeof(INT16));
				*(INT16 *) pv = (INT16)pbmh->w.mf.yExt;
				pv = PfromPcb(pv, sizeof(INT16));

				// Store size of uncompressed bits:

				pv = PVMakeQGB(lcbUncompressedBits, pv);
				break;
		}

		pv = PVMakeQGB(lcbBits, pv);
		pv = PVMakeQGB(pbmh->cbSizeExtra, pv);

		// Calculate and insert offsets.

		lcb = ((PBYTE) pv - (PBYTE) &bmh) + 2 * sizeof(DWORD) +
			sizeof(RGBQUAD) * crgbColorTable;
		*((DWORD *) pv) = lcb;
		pv = PfromPcb(pv, sizeof(DWORD));
		lcb += lcbBits;

		/*
		 * lcbSizeExtra is non-zero if this is a shed bitmap with hotspot
		 * information tacked onto the end.
		 */

		*((DWORD *) pv) =
			(pbmh->cbSizeExtra == 0 ? 0L : lcb);
		pv = PfromPcb(pv, sizeof(DWORD));

		// Write out the header, color table, bits, and extra data

		ASSERT(pvCompressedBits);

		pcfile->write(&bmh, (int) ((PBYTE) pv - (PBYTE) &bmh));
		if (crgbColorTable != 0) {
			pcfile->write(pbmh->rgrgb, crgbColorTable * sizeof(RGBQUAD));
			cbGraphics += crgbColorTable * sizeof(RGBQUAD);
		}
		if (pcfile->write(pvCompressedBits, lcbBits) != lcbBits) {
			rc = RC_DiskFull;
			break;
		}
		cbGraphics += lcbBits;
		if (pbmh->cbSizeExtra != 0) {
			if (pcfile->write(QFromQCb(pbmh, pbmh->cbOffsetExtra),
					 pbmh->cbSizeExtra) != pbmh->cbSizeExtra) {
				rc = RC_DiskFull;
				break;
			}
			cbGraphics += pbmh->cbSizeExtra;
		}
		if (pRleBits) {
			delete pRleBits;
			pRleBits = NULL;
		}
		if (pRleZeckBits) {
			delete pRleZeckBits;
			pRleZeckBits = NULL;
		}
	}

	if (pRleBits) {
		delete pRleBits;
		pRleBits = NULL;
	}
	if (pRleZeckBits) {
		delete pRleZeckBits;
		pRleZeckBits = NULL;
	}

	if (rc == RC_Success) {

		// Write out header with newly calculated offsets

		pcfile->seek(0L, SEEK_SET);
		pcfile->write(pbgh, lcbBgh);
		cbGraphics += lcbBgh;
	}

	delete pcfile;

	return rc;
}

RC_TYPE STDCALL RcWriteHf(HF hf, void* pvData, int lcb)
{
	int lcbTotalWrote;

	ASSERT(hf != NULL);
	QRWFO qrwfo = (QRWFO) hf;

	ASSERT(!(qrwfo->bFlags & FS_OPEN_READ_ONLY));

	if (!(qrwfo->bFlags & FS_DIRTY)) {

		ASSERT(!qrwfo->pTmpFile);

		// make sure we have a temp file version
		// FS permission is checked in RcCopyToTempFile()

		if (RcCopyToTempFile(qrwfo) != RC_Success) {
FatalError:
			return (RC_TYPE) (GetLastError() & ~SETERROR_MASK);
		}
	}

	// position file pointer in temp file

	if (qrwfo->pTmpFile->seek(sizeof(FH) + qrwfo->lifCurrent,
			SEEK_SET) != (int) (sizeof(FH) + qrwfo->lifCurrent)) {
		goto FatalError;
	}
	lcbTotalWrote = qrwfo->pTmpFile->write(pvData, lcb);

	if (lcbTotalWrote != lcb) {
		if (!qrwfo->pTmpFile->pszFileName)
			return RC_OutOfMemory;

		return RC_WriteError;
	}

	// update file pointer and file size

	if (lcbTotalWrote > 0) {
		qrwfo->lifCurrent += lcbTotalWrote;
		if (qrwfo->lifCurrent > qrwfo->lcbFile)
			qrwfo->lcbFile = qrwfo->lifCurrent;
	}

	return RC_Success;
}

int STDCALL RleCompress(PBYTE pbSrc, PBYTE pbDest, int cbSrc)
{
	int cbRun, cb;
	BYTE ch;

	PBYTE pbStart = pbDest;

	while (cbSrc > 0) {

		// Find next run of dissimilar bytes:

		cbRun = 0;
		if (cbSrc <= 2)
			cbRun = cbSrc;
		else {
			while (pbSrc[cbRun] != pbSrc[cbRun + 1] ||
					pbSrc[cbRun] != pbSrc[cbRun + 2])
				if (++cbRun >= cbSrc - 2) {
					cbRun = cbSrc;
					break;
				}
		}
		cbSrc -= cbRun;

		// Output run of dissimilar bytes:

		while (cbRun > 0) {
			cb = min(cbRun, MAX_RUN);
			*pbDest++ = ((BYTE) cb) | fRLEBit;
			cbRun -= cb;
			while (cb-- > 0)
				*pbDest++ = *pbSrc++;
		}

		if (cbSrc == 0)
			break;

		// Find next run of identical bytes:

		ch = *pbSrc;
		cbRun = 1;
		while (cbRun < cbSrc && ch == pbSrc[cbRun])
			cbRun++;

		cbSrc -= cbRun;
		pbSrc += cbRun;

		// Output run of identical bytes:

		while (cbRun > 0) {
			cb = min(cbRun, MAX_RUN);
			*pbDest++ = (BYTE) cb;
			*pbDest++ = ch;
			cbRun -= cb;
		}
	}

	return (int) (pbDest - pbStart);
}

static RC_TYPE STDCALL RcCopyToTempFile(QRWFO qrwfo)
{
	QFSHR qfshr = (QFSHR) qrwfo->hfs;

	ConfirmOrDie(!(qfshr->fsh.bFlags & FS_OPEN_READ_ONLY));

	if (!FPlungeQfshr(qfshr))
		return RC_Failure;

	qrwfo->bFlags |= FS_DIRTY;

	qrwfo->pTmpFile = new CTmpFile();

	// copy from FS file into temp file

	if (_llseek(qfshr->fid, qrwfo->lifBase, SEEK_SET) != qrwfo->lifBase)
		return RC_Failure;

	ASSERT(qrwfo->pTmpFile);

	if (qrwfo->pTmpFile->copyfromfile(qfshr->fid, qrwfo->lcbFile + sizeof(FH))
			!= RC_Success) {
		delete qrwfo->pTmpFile;
		qrwfo->pTmpFile = NULL;
		return RC_Failure;
	}
	return RC_Success;
}

static BOOL STDCALL FPlungeQfshr(QFSHR qfshr)
{
	if (qfshr->fid == HFILE_ERROR) {
		qfshr->fid = _lopen(qfshr->fm,
			qfshr->fsh.bFlags & FS_OPEN_READ_ONLY ?
				OF_READ : OF_READWRITE);

		if (qfshr->fid == HFILE_ERROR) {
			return FALSE;
		}

		/*
		 * Check size of file, then reset file pointer. Certain 0-length
		 * files (eg con) give us no end of grief if we try to read from
		 * them, and since a 0-length file could not possibly be a valid FS,
		 * we reject the notion.
		 */

		if (GetFileSize((HANDLE) qfshr->fid, NULL) < sizeof(FSH)) {
			return FALSE;
		}
	}

	return TRUE;
}