/*
 *   files.c
 *
 *   This file is part of Emu48
 *
 *   Copyright (C) 2004 Christoph Gießelink
 *
 */
#include "pch.h"
#include "Emu48.h"
#include "ops.h"
#include "io.h"								// I/O register definitions
#include "kml.h"
#include "i28f160.h"						// flash support

TCHAR  szEmuDirectory[MAX_PATH];
TCHAR  szDocDirectory[MAX_PATH];
TCHAR  szCurrentKml[MAX_PATH];
TCHAR  szBackupKml[MAX_PATH];
TCHAR  szCurrentFilename[MAX_PATH];
TCHAR  szBackupFilename[MAX_PATH];
TCHAR  szBufferFilename[MAX_PATH];
TCHAR  szPort2Filename[MAX_PATH];

LPBYTE pbyRom = NULL;
static HANDLE  hRomFile = NULL;
static HANDLE  hRomMap = NULL;
DWORD  dwRomSize = 0;
BYTE   cCurrentRomType = 0;					// Model -> hardware
UINT   nCurrentClass = 0;					// Class -> derivate
BOOL   bRomWriteable = TRUE;				// flag if ROM writeable

static HANDLE  hPort2File = NULL;
static HANDLE  hPort2Map = NULL;
LPBYTE pbyPort2 = NULL;
BOOL   bPort2Writeable = FALSE;
BOOL   bPort2IsShared = FALSE;
DWORD  dwPort2Size = 0;						// size of mapped port2
DWORD  dwPort2Mask = 0;

// document signatures
static BYTE pbySignatureA[16] = "Emu38 Document\xFE";
static BYTE pbySignatureB[16] = "Emu39 Document\xFE";
static BYTE pbySignatureE[16] = "Emu48 Document\xFE";
static BYTE pbySignatureW[16] = "Win48 Document\xFE";
static BYTE pbySignatureV[16] = "Emu49 Document\xFE";
static HANDLE hCurrentFile = NULL;

static CHIPSET BackupChipset;

BOOL   bBackup = FALSE;

//################
//#
//#    Directory Helper Tool
//#
//################

LPCTSTR FullFilename(LPCTSTR lpszFilename)
{
	static TCHAR szFullname[MAX_PATH];

	INT nPos;

	// copy directory path
	_tcsncpy(szFullname,szEmuDirectory,ARRAYSIZEOF(szFullname));
	nPos = lstrlen(szFullname);				// end of path name

	if (nPos > 0 && nPos < ARRAYSIZEOF(szFullname) && szFullname[nPos-1] != _T('\\'))
	{
		szFullname[nPos++] = _T('\\');
	}

	_tcsncpy(&szFullname[nPos],lpszFilename,ARRAYSIZEOF(szFullname)-nPos);
	return szFullname;
}



//################
//#
//#    Patch
//#
//################

static __inline BYTE Asc2Nib(BYTE c)
{
	if (c<'0') return 0;
	if (c<='9') return c-'0';
	if (c<'A') return 0;
	if (c<='F') return c-'A'+10;
	if (c<'a') return 0;
	if (c<='f') return c-'a'+10;
	return 0;
}

// functions to restore ROM patches
typedef struct tnode
{
	BOOL   bPatch;							// TRUE = ROM address patched
	DWORD  dwAddress;						// patch address
	BYTE   byROM;							// original ROM value
	BYTE   byPatch;							// patched ROM value
	struct tnode *next;						// next node
} TREENODE;

static TREENODE *nodePatch = NULL;

static BOOL PatchNibble(DWORD dwAddress, BYTE byPatch)
{
	TREENODE *p;

	_ASSERT(pbyRom);						// ROM defined
	if((p = HeapAlloc(hHeap,0,sizeof(TREENODE))) == NULL)
		return TRUE;

	p->bPatch = TRUE;						// address patched
	p->dwAddress = dwAddress;				// save current values
	p->byROM = pbyRom[dwAddress];
	p->byPatch = byPatch;
	p->next = nodePatch;					// save node
	nodePatch = p;

	pbyRom[dwAddress] = byPatch;			// patch ROM
	return FALSE;
}

static VOID RestorePatches(VOID)
{
	TREENODE *p;

	_ASSERT(pbyRom);						// ROM defined
	while (nodePatch != NULL)
	{
		// restore original data
		pbyRom[nodePatch->dwAddress] = nodePatch->byROM;

		p = nodePatch->next;				// save pointer to next node
		HeapFree(hHeap,0,nodePatch);		// free node
		nodePatch = p;						// new node
	}
	return;
}

VOID UpdatePatches(BOOL bPatch)
{
	TREENODE *p = nodePatch;

	_ASSERT(pbyRom);						// ROM defined
	while (p != NULL)
	{
		if (bPatch)							// patch ROM
		{
			if (!p->bPatch)					// patch only if not patched
			{
				// use original data for patch restore
				p->byROM = pbyRom[p->dwAddress];

				// restore patch data
				pbyRom[p->dwAddress] = p->byPatch;
				p->bPatch = TRUE;			// address patched
			}
			else
			{
				_ASSERT(FALSE);				// call ROM patch on a patched ROM
			}
		}
		else								// restore ROM
		{
			// restore original data
			pbyRom[p->dwAddress] = p->byROM;
			p->bPatch = FALSE;				// address not patched
		}

		p = p->next;						// next node
	}
	return;
}

BOOL PatchRom(LPCTSTR szFilename)
{
	HANDLE hFile = NULL;
	DWORD  dwFileSizeLow = 0;
	DWORD  dwFileSizeHigh = 0;
	DWORD  lBytesRead = 0;
	PSZ    lpStop,lpBuf = NULL;
	DWORD  dwAddress = 0;
	UINT   nPos = 0;

	hFile = CreateFile(FullFilename(szFilename),
					   GENERIC_READ,
					   FILE_SHARE_READ,
					   NULL,
					   OPEN_EXISTING,
					   FILE_ATTRIBUTE_NORMAL,
					   NULL);
	if (hFile == INVALID_HANDLE_VALUE) return FALSE;
	dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh);
	if (dwFileSizeLow <= 5)
	{ // file is too small.
		CloseHandle(hFile);
		return FALSE;
	}
	if (dwFileSizeHigh != 0)
	{ // file is too large.
		CloseHandle(hFile);
		return FALSE;
	}
	lpBuf = HeapAlloc(hHeap,0,dwFileSizeLow+1);
	if (lpBuf == NULL)
	{
		CloseHandle(hFile);
		return FALSE;
	}
	ReadFile(hFile, lpBuf, dwFileSizeLow, &lBytesRead, NULL);
	CloseHandle(hFile);
	lpBuf[dwFileSizeLow] = 0;
	nPos = 0;
	while (lpBuf[nPos])
	{
		do // remove blank space
		{
			if (  (lpBuf[nPos]!=' ')
				&&(lpBuf[nPos]!='\n')
				&&(lpBuf[nPos]!='\r')
				&&(lpBuf[nPos]!='\t')) break;
			nPos++;
		} while (lpBuf[nPos]);
		if (lpBuf[nPos]==';') // comment ?
		{
			do
			{
				nPos++;
				if (lpBuf[nPos]=='\n')
				{
					nPos++;
					break;
				}
			} while (lpBuf[nPos]);
			continue;
		}
		dwAddress = strtoul(&lpBuf[nPos], &lpStop, 16);
		nPos += (UINT) (lpStop - &lpBuf[nPos]) + 1;
		if (*lpStop != ':' || *lpStop == 0)
			continue;
		while (lpBuf[nPos])
		{
			if (isxdigit(lpBuf[nPos]) == FALSE) break;
			// patch ROM and save original nibble
			PatchNibble(dwAddress, Asc2Nib(lpBuf[nPos]));
			dwAddress = (dwAddress+1)&(dwRomSize-1);
			nPos++;
		}
	}
	HeapFree(hHeap,0,lpBuf);
	return TRUE;
}



//################
//#
//#    ROM
//#
//################

static WORD CrcRom(VOID)					// calculate fingerprint of ROM
{
	DWORD dwCount;
	DWORD dwCrc = 0;

	_ASSERT(pbyRom);						// view on ROM
	// use checksum, because it's faster
	for (dwCount = 0;dwCount < dwRomSize; dwCount+=sizeof(dwCrc))
		dwCrc += *((DWORD *) &pbyRom[dwCount]);

	return (WORD) dwCrc;
}

BOOL MapRom(LPCTSTR szFilename)
{
	// open ROM for writing
	BOOL bRomRW = (cCurrentRomType == 'X') ? bRomWriteable : FALSE;

	if (pbyRom != NULL)
	{
		return FALSE;
	}
	if (bRomRW)								// ROM writeable
	{
		hRomFile = CreateFileForMapping(FullFilename(szFilename),
							  GENERIC_READ|GENERIC_WRITE,
							  FILE_SHARE_READ,
							  NULL,
							  OPEN_EXISTING,
							  FILE_ATTRIBUTE_NORMAL,
							  NULL);
		if (hRomFile == INVALID_HANDLE_VALUE)
		{
			bRomRW = FALSE;					// ROM not writeable
			hRomFile = CreateFileForMapping(FullFilename(szFilename),
								  GENERIC_READ,
								  FILE_SHARE_READ|FILE_SHARE_WRITE,
								  NULL,
								  OPEN_EXISTING,
								  FILE_ATTRIBUTE_NORMAL,
								  NULL);
		}
	}
	else									// writing ROM disabled
	{
		hRomFile = CreateFileForMapping(FullFilename(szFilename),
							  GENERIC_READ,
							  FILE_SHARE_READ,
							  NULL,
							  OPEN_EXISTING,
							  FILE_ATTRIBUTE_NORMAL,
							  NULL);
	}
	if (hRomFile == INVALID_HANDLE_VALUE)
	{
		hRomFile = NULL;
		return FALSE;
	}
	dwRomSize = GetFileSize(hRomFile, NULL);
	if (bRomRW)								// ROM is writeable
	{
		hRomMap = CreateFileMapping(hRomFile, NULL, PAGE_READWRITE, 0, 0, NULL);
		if (hRomMap == NULL)
		{
			CloseHandle(hRomFile);
			hRomMap = NULL;
			dwRomSize = 0;
			return FALSE;
		}
		pbyRom = MapViewOfFile(hRomMap, FILE_MAP_WRITE, 0, 0, 0);
		if (pbyRom == NULL)
		{
			CloseHandle(hRomMap);
			CloseHandle(hRomFile);
			hRomMap = NULL;
			hRomFile = NULL;
			dwRomSize = 0;
			return FALSE;
		}
		// check for packed ROM image
		if ((*(DWORD *) pbyRom & 0xF0F0F0F0) != 0)
		{
			UnmapRom();						// free memory
			AbortMessage(_T("Packed ROM image detected!"));
			return FALSE;
		}
	}
	else									// ROM is read only
	{
		DWORD  dwSize,dwFileSize,dwRead;

		if (dwRomSize <= 4)
		{ // file is too small.
			CloseHandle(hRomFile);
			hRomFile = NULL;
			dwRomSize = 0;
			return FALSE;
		}

		// read the first 4 bytes
		ReadFile(hRomFile,&dwSize,sizeof(dwSize),&dwRead,NULL);

		dwFileSize = dwRomSize;				// calculate ROM image buffer size
		if ((dwSize & 0xF0F0F0F0) != 0)		// packed ROM image ->
			dwRomSize *= 2;					// unpacked ROM image has double size

		pbyRom = VirtualAlloc(NULL,dwRomSize,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
		if (pbyRom == NULL)
		{
			CloseHandle(hRomFile);
			hRomFile = NULL;
			dwRomSize = 0;
			return FALSE;
		}

		*(DWORD *) pbyRom = dwSize;			// save first 4 bytes

		// load rest of file content
		ReadFile(hRomFile,&pbyRom[sizeof(dwSize)],dwFileSize - sizeof(dwSize),&dwRead,NULL);
		_ASSERT(dwFileSize - sizeof(dwSize) == dwRead);
		CloseHandle(hRomFile);
		hRomFile = NULL;

		if (dwRomSize != dwFileSize)		// packed ROM image
		{
			dwSize = dwRomSize;				// destination start address
			while (dwFileSize > 0)			// unpack source
			{
				BYTE byValue = pbyRom[--dwFileSize];
				pbyRom[--dwSize] = byValue >> 4;
				pbyRom[--dwSize] = byValue & 0xF;
			}
		}
	}
	return TRUE;
}

VOID UnmapRom(VOID)
{
	if (pbyRom == NULL) return;				// ROM not mapped
	if (hRomMap != NULL)					// memory mapped file
	{
		RestorePatches();					// restore ROM Patches
		UnmapViewOfFile(pbyRom);
		CloseHandle(hRomMap);
		CloseHandle(hRomFile);
		hRomMap = NULL;
		hRomFile = NULL;
	}
	else									// working copy
	{
		VirtualFree(pbyRom,0,MEM_RELEASE);
	}
	pbyRom = NULL;
	dwRomSize = 0;
	return;
}



//################
//#
//#    Port2
//#
//################

WORD CrcPort2(VOID)							// calculate fingerprint of port2
{
	DWORD dwCount;
	DWORD dwFileSize;
	WORD wCrc = 0;

	// port2 CRC isn't available
	if (pbyPort2 == NULL) return wCrc;

	dwFileSize = GetFileSize(hPort2File, &dwCount); // get real filesize
	_ASSERT(dwCount == 0);					// isn't created by MapPort2()

	for (dwCount = 0;dwCount < dwFileSize; ++dwCount)
		wCrc = (wCrc >> 4) ^ (((wCrc ^ ((WORD) pbyPort2[dwCount])) & 0xf) * 0x1081);
	return wCrc;
}

BOOL MapPort2(LPCTSTR szFilename)
{
	DWORD dwFileSizeLo,dwFileSizeHi,dwCount;

	if (pbyPort2 != NULL) return FALSE;
	bPort2Writeable = TRUE;
	dwPort2Size = 0;						// reset size of port2

	hPort2File = CreateFileForMapping(FullFilename(szFilename),
									  GENERIC_READ|GENERIC_WRITE,
									  bPort2IsShared ? FILE_SHARE_READ : 0,
									  NULL,
									  OPEN_EXISTING,
									  FILE_FLAG_RANDOM_ACCESS,
									  NULL);
	if (hPort2File == INVALID_HANDLE_VALUE)
	{
		bPort2Writeable = FALSE;
		hPort2File = CreateFileForMapping(FullFilename(szFilename),
										  GENERIC_READ,
										  bPort2IsShared ? (FILE_SHARE_READ|FILE_SHARE_WRITE) : 0,
										  NULL,
										  OPEN_EXISTING,
										  FILE_FLAG_RANDOM_ACCESS,
										  NULL);
		if (hPort2File == INVALID_HANDLE_VALUE)
		{
			hPort2File = NULL;
			return FALSE;
		}
	}
	dwFileSizeLo = GetFileSize(hPort2File, &dwFileSizeHi);
	if (dwFileSizeHi != 0)
	{ // file is too large.
		CloseHandle(hPort2File);
		hPort2File = NULL;
		dwPort2Mask = 0;
		bPort2Writeable = FALSE;
		return FALSE;
	}

	// count number of set bits
	for (dwCount = 0, dwFileSizeHi = dwFileSizeLo; dwFileSizeHi != 0;dwFileSizeHi >>= 1)
	{
		if ((dwFileSizeHi & 0x1) != 0) ++dwCount;
	}

	// size not 32, 128, 256, 512, 1024, 2048 or 4096 KB
	if (dwCount != 1 || (dwFileSizeLo & 0xFF02FFFF) != 0)
	{
		CloseHandle(hPort2File);
		hPort2File = NULL;
		dwPort2Mask = 0;
		bPort2Writeable = FALSE;
		return FALSE;
	}
	dwPort2Mask = (dwFileSizeLo - 1) >> 18;	// mask for valid address lines of the BS-FF
	hPort2Map = CreateFileMapping(hPort2File, NULL, bPort2Writeable ? PAGE_READWRITE : PAGE_READONLY,
								  0, dwFileSizeLo, NULL);
	if (hPort2Map == NULL)
	{
		CloseHandle(hPort2File);
		hPort2File = NULL;
		dwPort2Mask = 0;
		bPort2Writeable = FALSE;
		return FALSE;
	}
	pbyPort2 = MapViewOfFile(hPort2Map, bPort2Writeable ? FILE_MAP_WRITE : FILE_MAP_READ, 0, 0, dwFileSizeLo);
	if (pbyPort2 == NULL)
	{
		CloseHandle(hPort2Map);
		CloseHandle(hPort2File);
		hPort2Map = NULL;
		hPort2File = NULL;
		dwPort2Mask = 0;
		bPort2Writeable = FALSE;
		return FALSE;
	}
	dwPort2Size = dwFileSizeLo / 2048;		// mapping size of port2
	return TRUE;
}

VOID UnmapPort2(VOID)
{
	if (pbyPort2 == NULL) return;
	UnmapViewOfFile(pbyPort2);
	CloseHandle(hPort2Map);
	CloseHandle(hPort2File);
	pbyPort2 = NULL;
	hPort2Map = NULL;
	hPort2File = NULL;
	dwPort2Size = 0;						// reset size of port2
	dwPort2Mask = 0;
	bPort2Writeable = FALSE;
	return;
}



//################
//#
//#    Documents
//#
//################

VOID ResetDocument(VOID)
{
	if (szCurrentKml[0])
	{
		KillKML();
	}
	if (hCurrentFile)
	{
		CloseHandle(hCurrentFile);
		hCurrentFile = NULL;
	}
	szCurrentKml[0] = 0;
	szCurrentFilename[0]=0;
	if (Chipset.Port0) HeapFree(hHeap,0,Chipset.Port0);
	if (Chipset.Port1) HeapFree(hHeap,0,Chipset.Port1);
	if (Chipset.Port2) HeapFree(hHeap,0,Chipset.Port2); else UnmapPort2();
	ZeroMemory(&Chipset,sizeof(Chipset));
	ZeroMemory(&RMap,sizeof(RMap));			// delete MMU mappings
	ZeroMemory(&WMap,sizeof(WMap));
	return;
}

BOOL NewDocument(VOID)
{
	SaveBackup();
	ResetDocument();

	if (!DisplayChooseKml(0)) goto restore;
	if (!InitKML(szCurrentKml,FALSE)) goto restore;

	Chipset.nPosX = 0;
	Chipset.nPosY = 0;
	Chipset.type = cCurrentRomType;

	if (Chipset.type == '6' || Chipset.type == 'A')	// HP38G
	{
		Chipset.Port0Size = (Chipset.type == 'A') ? 32 : 64;
		Chipset.Port1Size = 0;
		Chipset.Port2Size = 0;

		Chipset.cards_status = 0x0;
	}
	if (Chipset.type == 'E')				// HP39/40G
	{
		Chipset.Port0Size = 128;
		Chipset.Port1Size = 0;
		Chipset.Port2Size = 128;

		Chipset.cards_status = 0xF;

		bPort2Writeable = TRUE;				// port2 is writeable
	}
	if (Chipset.type == 'S')				// HP48SX
	{
		Chipset.Port0Size = 32;
		Chipset.Port1Size = 128;
		Chipset.Port2Size = 0;

		Chipset.cards_status = 0x5;

		// use 2nd command line argument if defined
		MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]);
	}
	if (Chipset.type == 'G')				// HP48GX
	{
		Chipset.Port0Size = 128;
		Chipset.Port1Size = 128;
		Chipset.Port2Size = 0;

		Chipset.cards_status = 0xA;

		// use 2nd command line argument if defined
		MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]);
	}
	if (Chipset.type == 'X')				// HP49G
	{
		Chipset.Port0Size = 256;
		Chipset.Port1Size = 128;
		Chipset.Port2Size = 128;

		Chipset.cards_status = 0xF;
		bPort2Writeable = TRUE;				// port2 is writeable

		FlashInit();						// init flash structure
	}

	Chipset.IORam[LPE] = RST;				// set ReSeT bit at power on reset

	// allocate port memory
	if (Chipset.Port0Size)
	{
		Chipset.Port0 = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,Chipset.Port0Size*2048);
		_ASSERT(Chipset.Port0 != NULL);
	}
	if (Chipset.Port1Size)
	{
		Chipset.Port1 = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,Chipset.Port1Size*2048);
		_ASSERT(Chipset.Port1 != NULL);
	}
	if (Chipset.Port2Size)
	{
		Chipset.Port2 = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,Chipset.Port2Size*2048);
		_ASSERT(Chipset.Port2 != NULL);
	}
	RomSwitch(0);							// boot ROM view of HP49G and map memory
	return TRUE;
restore:
	RestoreBackup();
	ResetBackup();

	// HP48SX/GX
	if(Chipset.type == 'S' || Chipset.type == 'G')
	{
		// use 2nd command line argument if defined
		MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]);
	}
	if (pbyRom)
	{
		Map(0x00,0xFF);
	}
	return FALSE;
}

BOOL OpenDocument(LPCTSTR szFilename)
{
	HANDLE  hFile = INVALID_HANDLE_VALUE;
	DWORD   lBytesRead,lSizeofChipset;
	BYTE    pbyFileSignature[16];
	LPBYTE  pbySig;
	UINT    ctBytesCompared;
	UINT    nLength;

	SaveBackup();
	ResetDocument();

	// Open file
	if (lstrcmpi(szBackupFilename, szFilename)==0)
	{
		if (YesNoMessage(_T("Do you want to reload this document ?")) == IDNO)
			goto restore;
	}
	hFile = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		AbortMessage(_T("This file is missing or already loaded in another instance of Emu48."));
		goto restore;
	}

	// Read and Compare signature
	ReadFile(hFile, pbyFileSignature, 16, &lBytesRead, NULL);
	switch (pbyFileSignature[0])
	{
	case 'E':
		pbySig = (pbyFileSignature[3] == '3')
			   ? ((pbyFileSignature[4] == '8') ? pbySignatureA : pbySignatureB)
			   : ((pbyFileSignature[4] == '8') ? pbySignatureE : pbySignatureV);
		for (ctBytesCompared=0; ctBytesCompared<14; ctBytesCompared++)
		{
			if (pbyFileSignature[ctBytesCompared]!=pbySig[ctBytesCompared])
			{
				AbortMessage(_T("This file is not a valid Emu48 document."));
				goto restore;
			}
		}
		break;
	case 'W':
		for (ctBytesCompared=0; ctBytesCompared<14; ctBytesCompared++)
		{
			if (pbyFileSignature[ctBytesCompared]!=pbySignatureW[ctBytesCompared])
			{
				AbortMessage(_T("This file is not a valid Win48 document."));
				goto restore;
			}
		}
		break;
	default:
		AbortMessage(_T("This file is not a valid document."));
		goto restore;
	}

	switch (pbyFileSignature[14])
	{
	case 0xFE: // Win48 2.1 / Emu4x 0.99.x format
		ReadFile(hFile,&nLength,sizeof(nLength),&lBytesRead,NULL);
		#if defined _UNICODE
		{
			LPSTR szTmp = HeapAlloc(hHeap,0,nLength);
			if (szTmp == NULL)
			{
				AbortMessage(_T("Memory Allocation Failure."));
				goto restore;
			}
			ReadFile(hFile, szTmp, nLength, &lBytesRead, NULL);
			MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, szTmp, lBytesRead,
								szCurrentKml, ARRAYSIZEOF(szCurrentKml));
			HeapFree(hHeap,0,szTmp);
		}
		#else
		{
			ReadFile(hFile, szCurrentKml, nLength, &lBytesRead, NULL);
		}
		#endif
		if (nLength != lBytesRead) goto read_err;
		szCurrentKml[nLength] = 0;
		break;
	case 0xFF: // Win48 2.05 format
		break;
	default:
		AbortMessage(_T("This file is for an unknown version of Emu48."));
		goto restore;
	}

	// read chipset size inside file
	ReadFile(hFile, &lSizeofChipset, sizeof(lSizeofChipset), &lBytesRead, NULL);
	if (lBytesRead != sizeof(lSizeofChipset)) goto read_err;
	if (lSizeofChipset <= sizeof(Chipset))	// actual or older chipset version
	{
		// read chipset content
		ZeroMemory(&Chipset,sizeof(Chipset));	// init chipset
		ReadFile(hFile, &Chipset, lSizeofChipset, &lBytesRead, NULL);
	}
	else									// newer chipset version
	{
		// read my used chipset content
		ReadFile(hFile, &Chipset, sizeof(Chipset), &lBytesRead, NULL);

		// skip rest of chipset
		SetFilePointer(hFile, lSizeofChipset-sizeof(Chipset), NULL, FILE_CURRENT);
		lSizeofChipset = sizeof(Chipset);
	}
	Chipset.Port0 = NULL;					// delete invalid port pointers
	Chipset.Port1 = NULL;
	Chipset.Port2 = NULL;
	if (lBytesRead != lSizeofChipset) goto read_err;

	if (!isModelValid(Chipset.type))		// check for valid model in emulator state file
	{
		AbortMessage(_T("Emulator state file with invalid calculator model."));
		goto restore;
	}

	while (TRUE)
	{
		if (szCurrentKml[0])				// KML file name
		{
			BOOL bOK;

			bOK = InitKML(szCurrentKml,FALSE);
			bOK = bOK && (cCurrentRomType == Chipset.type);
			if (bOK) break;

			KillKML();
		}
		if (!DisplayChooseKml(Chipset.type))
			goto restore;
	}
	// reload old button state
	ReloadButtons(Chipset.Keyboard_Row,sizeof(Chipset.Keyboard_Row));

	FlashInit();							// init flash structure

	if (Chipset.Port0Size)
	{
		Chipset.Port0 = HeapAlloc(hHeap,0,Chipset.Port0Size*2048);
		if (Chipset.Port0 == NULL)
		{
			AbortMessage(_T("Memory Allocation Failure."));
			goto restore;
		}

		ReadFile(hFile, Chipset.Port0, Chipset.Port0Size*2048, &lBytesRead, NULL);
		if (lBytesRead != Chipset.Port0Size*2048) goto read_err;
	}

	if (Chipset.Port1Size)
	{
		Chipset.Port1 = HeapAlloc(hHeap,0,Chipset.Port1Size*2048);
		if (Chipset.Port1 == NULL)
		{
			AbortMessage(_T("Memory Allocation Failure."));
			goto restore;
		}

		ReadFile(hFile, Chipset.Port1, Chipset.Port1Size*2048, &lBytesRead, NULL);
		if (lBytesRead != Chipset.Port1Size*2048) goto read_err;
	}

	// HP48SX/GX
	if(cCurrentRomType=='S' || cCurrentRomType=='G')
	{
		MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]);
		// port2 changed and card detection enabled
		if (    Chipset.wPort2Crc != CrcPort2()
			&& (Chipset.IORam[CARDCTL] & ECDT) != 0 && (Chipset.IORam[TIMER2_CTRL] & RUN) != 0
		   )
		{
			Chipset.HST |= MP;				// set Module Pulled
			IOBit(SRQ2,NINT,FALSE);			// set NINT to low
			Chipset.SoftInt = TRUE;			// set interrupt
			bInterrupt = TRUE;
		}
	}
	else									// HP38G, HP39/40G, HP49G
	{
		if (Chipset.Port2Size)
		{
			Chipset.Port2 = HeapAlloc(hHeap,0,Chipset.Port2Size*2048);
			if (Chipset.Port2 == NULL)
			{
				AbortMessage(_T("Memory Allocation Failure."));
				goto restore;
			}

			ReadFile(hFile, Chipset.Port2, Chipset.Port2Size*2048, &lBytesRead, NULL);
			if (lBytesRead != Chipset.Port2Size*2048) goto read_err;

			bPort2Writeable = TRUE;
			Chipset.cards_status = 0xF;
		}
	}

	RomSwitch(Chipset.Bank_FF);				// reload ROM view of HP49G and map memory

	if (Chipset.wRomCrc != CrcRom())		// ROM changed
	{
		CpuReset();
		Chipset.Shutdn = FALSE;				// automatic restart
	}

	lstrcpy(szCurrentFilename, szFilename);
	_ASSERT(hCurrentFile == NULL);
	hCurrentFile = hFile;
	SetWindowTitle(szCurrentFilename);
	return TRUE;

read_err:
	AbortMessage(_T("This file must be truncated, and cannot be loaded."));
restore:
	if (INVALID_HANDLE_VALUE != hFile)		// close if valid handle
		CloseHandle(hFile);
	RestoreBackup();
	ResetBackup();

	// HP48SX/GX
	if(cCurrentRomType=='S' || cCurrentRomType=='G')
	{
		// use 2nd command line argument if defined
		MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]);
	}
	return FALSE;
}

BOOL SaveDocument(VOID)
{
	DWORD           lBytesWritten;
	DWORD           lSizeofChipset;
	LPBYTE          pbySig;
	UINT            nLength;

	if (hCurrentFile == NULL) return FALSE;

	_ASSERT(hWnd);							// window open
	SetFilePointer(hCurrentFile,0,NULL,FILE_BEGIN);

	// get document signature
	pbySig = (Chipset.type=='6' || Chipset.type=='A')
		   ? pbySignatureA : (Chipset.type=='E' ? pbySignatureB : ((Chipset.type!='X') ? pbySignatureE : pbySignatureV));

	if (!WriteFile(hCurrentFile, pbySig, 16, &lBytesWritten, NULL))
	{
		AbortMessage(_T("Could not write into file !"));
		return FALSE;
	}

	Chipset.wRomCrc = CrcRom();				// save fingerprint of ROM
	Chipset.wPort2Crc = CrcPort2();			// save fingerprint of port2

	nLength = lstrlen(szCurrentKml);
	WriteFile(hCurrentFile, &nLength, sizeof(nLength), &lBytesWritten, NULL);
	#if defined _UNICODE
	{
		LPSTR szTmp = HeapAlloc(hHeap,0,nLength);
		if (szTmp != NULL)
		{
			WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK,
								szCurrentKml, nLength,
								szTmp, nLength, NULL, NULL);
			WriteFile(hCurrentFile, szTmp, nLength, &lBytesWritten, NULL);
			HeapFree(hHeap,0,szTmp);
		}
	}
	#else
	{
		WriteFile(hCurrentFile, szCurrentKml, nLength, &lBytesWritten, NULL);
	}
	#endif
	lSizeofChipset = sizeof(CHIPSET);
	WriteFile(hCurrentFile, &lSizeofChipset, sizeof(lSizeofChipset), &lBytesWritten, NULL);
	WriteFile(hCurrentFile, &Chipset, lSizeofChipset, &lBytesWritten, NULL);
	if (Chipset.Port0Size) WriteFile(hCurrentFile, Chipset.Port0, Chipset.Port0Size*2048, &lBytesWritten, NULL);
	if (Chipset.Port1Size) WriteFile(hCurrentFile, Chipset.Port1, Chipset.Port1Size*2048, &lBytesWritten, NULL);
	if (Chipset.Port2Size) WriteFile(hCurrentFile, Chipset.Port2, Chipset.Port2Size*2048, &lBytesWritten, NULL);
	SetEndOfFile(hCurrentFile);				// cut the rest
	return TRUE;
}

BOOL SaveDocumentAs(LPCTSTR szFilename)
{
	HANDLE hFile;

	if (hCurrentFile)						// already file in use
	{
		CloseHandle(hCurrentFile);			// close it, even it's same, so data always will be saved
		hCurrentFile = NULL;
	}
	hFile = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE)		// error, couldn't create a new file
	{
		AbortMessage(_T("This file must be currently used by another instance of Emu48."));
		return FALSE;
	}
	lstrcpy(szCurrentFilename, szFilename);	// save new file name
	hCurrentFile = hFile;					// and the corresponding handle
	SetWindowTitle(szCurrentFilename);		// update window title line
	return SaveDocument();					// save current content
}



//################
//#
//#    Backup
//#
//################

BOOL SaveBackup(VOID)
{
	if (pbyRom == NULL) return FALSE;

	lstrcpy(szBackupFilename, szCurrentFilename);
	lstrcpy(szBackupKml, szCurrentKml);
	if (BackupChipset.Port0) HeapFree(hHeap,0,BackupChipset.Port0);
	if (BackupChipset.Port1) HeapFree(hHeap,0,BackupChipset.Port1);
	if (BackupChipset.Port2) HeapFree(hHeap,0,BackupChipset.Port2);
	CopyMemory(&BackupChipset, &Chipset, sizeof(Chipset));
	BackupChipset.Port0 = HeapAlloc(hHeap,0,Chipset.Port0Size*2048);
	CopyMemory(BackupChipset.Port0,Chipset.Port0,Chipset.Port0Size*2048);
	BackupChipset.Port1 = HeapAlloc(hHeap,0,Chipset.Port1Size*2048);
	CopyMemory(BackupChipset.Port1,Chipset.Port1,Chipset.Port1Size*2048);
	BackupChipset.Port2 = NULL;
	if (Chipset.Port2Size)					// internal port2
	{
		BackupChipset.Port2 = HeapAlloc(hHeap,0,Chipset.Port2Size*2048);
		CopyMemory(BackupChipset.Port2,Chipset.Port2,Chipset.Port2Size*2048);
	}
	bBackup = TRUE;
	return TRUE;
}

BOOL RestoreBackup(VOID)
{
	if (!bBackup) return FALSE;
	ResetDocument();
	// need chipset for contrast setting in InitKML()
	Chipset.contrast = BackupChipset.contrast;
	if (!InitKML(szBackupKml,TRUE))
	{
		InitKML(szCurrentKml,TRUE);
		return FALSE;
	}
	lstrcpy(szCurrentKml, szBackupKml);
	lstrcpy(szCurrentFilename, szBackupFilename);
	if (szCurrentFilename[0])
	{
		hCurrentFile = CreateFile(szCurrentFilename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		if (hCurrentFile == INVALID_HANDLE_VALUE)
		{
			hCurrentFile = NULL;
			szCurrentFilename[0] = 0;
		}
	}
	CopyMemory(&Chipset, &BackupChipset, sizeof(Chipset));
	Chipset.Port0 = HeapAlloc(hHeap,0,Chipset.Port0Size*2048);
	CopyMemory(Chipset.Port0,BackupChipset.Port0,Chipset.Port0Size*2048);
	Chipset.Port1 = HeapAlloc(hHeap,0,Chipset.Port1Size*2048);
	CopyMemory(Chipset.Port1,BackupChipset.Port1,Chipset.Port1Size*2048);
	if (Chipset.Port2Size)					// internal port2
	{
		Chipset.Port2 = HeapAlloc(hHeap,0,Chipset.Port2Size*2048);
		CopyMemory(Chipset.Port2,BackupChipset.Port2,Chipset.Port2Size*2048);
	}
	// map port2
	else
	{
		if(cCurrentRomType=='S' || cCurrentRomType=='G') // HP48SX/GX
		{
			// use 2nd command line argument if defined
			MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]);
		}
	}
	SetWindowTitle(szCurrentFilename);
	Map(0x00,0xFF);
	return TRUE;
}

BOOL ResetBackup(VOID)
{
	if (!bBackup) return FALSE;
	szBackupFilename[0] = 0;
	szBackupKml[0] = 0;
	if (BackupChipset.Port0) HeapFree(hHeap,0,BackupChipset.Port0);
	if (BackupChipset.Port1) HeapFree(hHeap,0,BackupChipset.Port1);
	if (BackupChipset.Port2) HeapFree(hHeap,0,BackupChipset.Port2);
	ZeroMemory(&BackupChipset,sizeof(BackupChipset));
	bBackup = FALSE;
	return TRUE;
}



//################
//#
//#    Open File Common Dialog Boxes
//#
//################

static VOID InitializeOFN(LPOPENFILENAME ofn)
{
	SHFullScreen(hWnd, SHFS_SHOWTASKBAR | SHFS_SHOWSIPBUTTON | SHFS_SHOWSTARTICON);

	ZeroMemory((LPVOID)ofn, sizeof(OPENFILENAME));
	ofn->lStructSize = sizeof(OPENFILENAME);
	ofn->hwndOwner = hWnd;
//	ofn->lpstrInitialDir = szDocDirectory;	// bug at saving in PPC 2002
	ofn->Flags = OFN_EXPLORER|OFN_HIDEREADONLY;
	return;
}

VOID SaveLastDocumentFolder(LPCTSTR lpszFilename)
{
	LPTSTR lpszChar;

	if (lpszFilename[0] != 0)				// not empty
	{
		_ASSERT(lpszFilename[0] == _T('\\'));

		// search for end of "My Documents" folder
		lpszChar = _tcschr(&lpszFilename[1],_T('\\'));

		// copy filename without "My Documents" to destination
		_tcsncpy(szDocDirectory,&lpszChar[1],ARRAYSIZEOF(szDocDirectory));

		// search for begin of filename
		lpszChar = _tcsrchr(szDocDirectory,_T('\\'));

		// cut filename
		if (lpszChar) *lpszChar = 0;
	}
	return;
}

BOOL GetOpenFilename(VOID)
{
	TCHAR szBuffer[ARRAYSIZEOF(szBufferFilename)];
	OPENFILENAME ofn;

	InitializeOFN(&ofn);
	ofn.lpstrFilter =
		_T("Emu38 Document (*.E38)\0*.E38\0")
		_T("Emu39 Document (*.E39)\0*.E39\0")
		_T("Emu48 Document (*.E48)\0*.E48\0")
		_T("Emu49 Document (*.E49)\0*.E49\0")
		_T("Win48 Document (*.W48)\0*.W48\0")
		_T("\0\0");
	ofn.lpstrDefExt = _T("E48");			// HP48SX/GX
	ofn.nFilterIndex = 3;
	if (cCurrentRomType=='6' || cCurrentRomType=='A') // HP38G
	{
		ofn.lpstrDefExt = _T("E38");
		ofn.nFilterIndex = 1;
	}
	if (cCurrentRomType=='E')				// HP39/40G
	{
		ofn.lpstrDefExt = _T("E39");
		ofn.nFilterIndex = 2;
	}
	if (cCurrentRomType=='X')				// HP49G
	{
		ofn.lpstrDefExt = _T("E49");
		ofn.nFilterIndex = 4;
	}
	ofn.lpstrFile = szBuffer;
	ofn.lpstrFile[0] = 0;
	ofn.nMaxFile = ARRAYSIZEOF(szBuffer);
	ofn.lpstrInitialDir = szDocDirectory;
	ofn.Flags |= OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST;
	if (GetOpenFileName(&ofn) == FALSE) return FALSE;
	SaveLastDocumentFolder(ofn.lpstrFile);
	_ASSERT(ARRAYSIZEOF(szBufferFilename) == ofn.nMaxFile);
	lstrcpy(szBufferFilename, ofn.lpstrFile);
	return TRUE;
}

BOOL GetSaveAsFilename(VOID)
{
	TCHAR szBuffer[ARRAYSIZEOF(szBufferFilename)];
	OPENFILENAME ofn;

	InitializeOFN(&ofn);
	ofn.lpstrFilter =
		_T("Emu38 Document (*.E38)\0*.E38\0")
		_T("Emu39 Document (*.E39)\0*.E39\0")
		_T("Emu48 Document (*.E48)\0*.E48\0")
		_T("Emu49 Document (*.E49)\0*.E49\0")
		_T("Win48 Document (*.W48)\0*.W48\0")
		_T("\0\0");
	ofn.lpstrDefExt = _T("E48");			// HP48SX/GX
	ofn.nFilterIndex = 3;
	if (cCurrentRomType=='6' || cCurrentRomType=='A') // HP38G
	{
		ofn.lpstrDefExt = _T("E38");
		ofn.nFilterIndex = 1;
	}
	if (cCurrentRomType=='E')				// HP39/40G
	{
		ofn.lpstrDefExt = _T("E39");
		ofn.nFilterIndex = 2;
	}
	if (cCurrentRomType=='X')				// HP49G
	{
		ofn.lpstrDefExt = _T("E49");
		ofn.nFilterIndex = 4;
	}
	ofn.lpstrFile = szBuffer;;
	ofn.lpstrFile[0] = 0;
	ofn.nMaxFile = ARRAYSIZEOF(szBuffer);
	ofn.Flags |= OFN_CREATEPROMPT|OFN_OVERWRITEPROMPT;
	if (GetSaveFileName(&ofn) == FALSE) return FALSE;
	SaveLastDocumentFolder(ofn.lpstrFile);
	_ASSERT(ARRAYSIZEOF(szBufferFilename) == ofn.nMaxFile);
	lstrcpy(szBufferFilename, ofn.lpstrFile);
	return TRUE;
}

BOOL GetLoadObjectFilename(VOID)
{
	TCHAR szBuffer[ARRAYSIZEOF(szBufferFilename)];
	OPENFILENAME ofn;

	InitializeOFN(&ofn);
	ofn.lpstrFilter = _T("All Files (*.*)\0*.*\0") _T("\0\0");
	ofn.nFilterIndex = 1;
	ofn.lpstrFile = szBuffer;
	ofn.lpstrFile[0] = 0;
	ofn.nMaxFile = ARRAYSIZEOF(szBuffer);
	ofn.lpstrInitialDir = szDocDirectory;
	ofn.Flags |= OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST;
	if (GetOpenFileName(&ofn) == FALSE) return FALSE;
	SaveLastDocumentFolder(ofn.lpstrFile);
	_ASSERT(ARRAYSIZEOF(szBufferFilename) == ofn.nMaxFile);
	lstrcpy(szBufferFilename, ofn.lpstrFile);
	return TRUE;
}

BOOL GetSaveObjectFilename(VOID)
{
	TCHAR szBuffer[ARRAYSIZEOF(szBufferFilename)];
	OPENFILENAME ofn;

	InitializeOFN(&ofn);
	ofn.lpstrFilter = _T("All Files (*.*)\0*.*\0") _T("\0\0");
	ofn.nFilterIndex = 1;
	ofn.lpstrFile = szBuffer;
	ofn.lpstrFile[0] = 0;
	ofn.nMaxFile = ARRAYSIZEOF(szBuffer);
	ofn.Flags |= OFN_CREATEPROMPT|OFN_OVERWRITEPROMPT;
	if (GetSaveFileName(&ofn) == FALSE) return FALSE;
	SaveLastDocumentFolder(ofn.lpstrFile);
	_ASSERT(ARRAYSIZEOF(szBufferFilename) == ofn.nMaxFile);
	lstrcpy(szBufferFilename, ofn.lpstrFile);
	return TRUE;
}



//################
//#
//#    Load and Save HP48 Objects
//#
//################

WORD WriteStack(UINT nStkLevel,LPBYTE lpBuf,DWORD dwSize)	// separated from LoadObject()
{
	BOOL  bBinary;
	DWORD dwAddress, i;

	bBinary =  ((lpBuf[dwSize+0]=='H')
	        &&  (lpBuf[dwSize+1]=='P')
	        &&  (lpBuf[dwSize+2]=='H')
	        &&  (lpBuf[dwSize+3]=='P')
	        &&  (lpBuf[dwSize+4]=='4')
	        &&  (lpBuf[dwSize+5]==((cCurrentRomType!='X') ? '8' : '9'))
	        &&  (lpBuf[dwSize+6]=='-'));

	for (i = 0; i < dwSize; i++)
	{
		BYTE byTwoNibs = lpBuf[i+dwSize];
		lpBuf[i*2  ] = (BYTE)(byTwoNibs&0xF);
		lpBuf[i*2+1] = (BYTE)(byTwoNibs>>4);
	}

	if (bBinary == TRUE)
	{ // load as binary
		dwSize    = RPL_ObjectSize(lpBuf+16);
		dwAddress = RPL_CreateTemp(dwSize);
		if (dwAddress == 0) return S_ERR_BINARY;

		Nwrite(lpBuf+16,dwAddress,dwSize);
	}
	else
	{ // load as string
		dwSize *= 2;
		dwAddress = RPL_CreateTemp(dwSize+10);
		if (dwAddress == 0) return S_ERR_ASCII;
		Write5(dwAddress,0x02A2C);			// String
		Write5(dwAddress+5,dwSize+5);		// length of String
		Nwrite(lpBuf,dwAddress+10,dwSize);	// data
	}
	RPL_Push(nStkLevel,dwAddress);
	return S_ERR_NO;
}

BOOL LoadObject(LPCTSTR szFilename)			// separated stack writing part
{
	HANDLE hFile;
	DWORD  dwFileSizeLow;
	DWORD  dwFileSizeHigh;
	LPBYTE lpBuf;
	WORD   wError;

	hFile = CreateFile(szFilename,
					   GENERIC_READ,
					   FILE_SHARE_READ,
					   NULL,
					   OPEN_EXISTING,
					   FILE_ATTRIBUTE_NORMAL,
					   NULL);
	if (hFile == INVALID_HANDLE_VALUE) return FALSE;
	dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh);
	if (dwFileSizeHigh != 0)
	{ // file is too large.
		CloseHandle(hFile);
		return FALSE;
	}
	lpBuf = HeapAlloc(hHeap,0,dwFileSizeLow*2);
	if (lpBuf == NULL)
	{
		CloseHandle(hFile);
		return FALSE;
	}
	ReadFile(hFile, lpBuf+dwFileSizeLow, dwFileSizeLow, &dwFileSizeHigh, NULL);
	CloseHandle(hFile);

	wError = WriteStack(1,lpBuf,dwFileSizeLow);

	if (wError == S_ERR_BINARY)
		AbortMessage(_T("The calculator does not have enough\nfree memory to load this binary file."));

	if (wError == S_ERR_ASCII)
		AbortMessage(_T("The calculator does not have enough\nfree memory to load this text file."));

	HeapFree(hHeap,0,lpBuf);
	return (wError == S_ERR_NO);
}

BOOL SaveObject(LPCTSTR szFilename)			// separated stack reading part
{
	HANDLE	hFile;
	LPBYTE  pbyHeader;
	DWORD	lBytesWritten;
	DWORD   dwAddress;
	DWORD   dwLength;

	dwAddress = RPL_Pick(1);
	if (dwAddress == 0)
	{
		AbortMessage(_T("Too Few Arguments."));
		return FALSE;
	}
	dwLength = (RPL_SkipOb(dwAddress) - dwAddress + 1) / 2;

	hFile = CreateFile(szFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		AbortMessage(_T("Cannot open file."));
		return FALSE;
	}

	pbyHeader = (Chipset.type != 'X') ? BINARYHEADER48 : BINARYHEADER49;
	WriteFile(hFile, pbyHeader, 8, &lBytesWritten, NULL);

	while (dwLength--)
	{
		BYTE byByte = Read2(dwAddress);
		WriteFile(hFile, &byByte, 1, &lBytesWritten, NULL);
		dwAddress += 2;
	}
	CloseHandle(hFile);
	return TRUE;
}



//################
//#
//#    Load Bitmap
//#
//################

static __inline UINT DibNumColors(BITMAPINFOHEADER CONST *lpbi)
{
	if (lpbi->biClrUsed != 0) return (UINT)lpbi->biClrUsed;

	/* a 24 bitcount DIB has no color table */
	return (lpbi->biBitCount <= 8) ? (1 << lpbi->biBitCount) : 0;
}

static HPALETTE CreateBIPalette(BITMAPINFOHEADER CONST *lpbi)
{
	LOGPALETTE *pPal;
	HPALETTE   hpal = NULL;
	UINT       nNumColors;
	BYTE       red;
	BYTE       green;
	BYTE       blue;
	UINT       i;
	RGBQUAD    *pRgb;

	if (!lpbi)
		return NULL;

	if (lpbi->biSize != sizeof(BITMAPINFOHEADER))
		return NULL;

	// Get a pointer to the color table and the number of colors in it
	pRgb = (RGBQUAD *)((LPBYTE)lpbi + (WORD)lpbi->biSize);
	nNumColors = DibNumColors(lpbi);

	if (nNumColors)
	{
		// Allocate for the logical palette structure
		pPal = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(LOGPALETTE) + nNumColors * sizeof(PALETTEENTRY));
		if (!pPal)
			return NULL;

		pPal->palNumEntries = nNumColors;
		pPal->palVersion    = 0x300;

		// Fill in the palette entries from the DIB color table and
		// create a logical color palette.
		for (i = 0; i < nNumColors; i++)
		{
			pPal->palPalEntry[i].peRed   = pRgb[i].rgbRed;
			pPal->palPalEntry[i].peGreen = pRgb[i].rgbGreen;
			pPal->palPalEntry[i].peBlue  = pRgb[i].rgbBlue;
			pPal->palPalEntry[i].peFlags = (BYTE)0;
		}
		hpal = CreatePalette(pPal);
		HeapFree(hHeap,0,(HANDLE)pPal);
	}
	else
	{
		// create halftone palette for 16, 24 and 32 bitcount bitmaps

		// 16, 24 and 32 bitcount DIB's have no color table entries so, set the
		// number of to the maximum value (256).
		nNumColors = 256;
		pPal = HeapAlloc(hHeap,0,sizeof(LOGPALETTE) + nNumColors * sizeof(PALETTEENTRY));
		if (!pPal) return NULL;

		pPal->palNumEntries = nNumColors;
		pPal->palVersion    = 0x300;

		red = green = blue = 0;

		// Generate 256 (= 8*8*4) RGB combinations to fill the palette
		// entries.
		for (i = 0; i < pPal->palNumEntries; i++)
		{
			pPal->palPalEntry[i].peRed   = red;
			pPal->palPalEntry[i].peGreen = green;
			pPal->palPalEntry[i].peBlue  = blue;
			pPal->palPalEntry[i].peFlags = 0;

			if (!(red += 32))
				if (!(green += 32))
					blue += 64;
		}
		hpal = CreatePalette(pPal);
		HeapFree(hHeap,0,pPal);
	}
	return hpal;
}

static VOID DecodeRleBmp(LPBYTE ppvBits,BYTE CONST *lpbyBitmap,BITMAPINFOHEADER CONST *lpbi)
{
	#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)

	BYTE  byLength,byColorIndex;
	DWORD dwPos,dwRow,dwSizeImage,dwPixelPerLine;
	BOOL  bDecoding;

	_ASSERT(ppvBits != NULL);				// destination
	_ASSERT(lpbyBitmap != NULL);			// source
	_ASSERT(lpbi != NULL);					// BITMAPINFOHEADER

	// valid bit count for RLE bitmaps
	_ASSERT(lpbi->biBitCount == 4 || lpbi->biBitCount == 8);

	// wrong bit count for compressed bitmap or top-down bitmap
	if ((lpbi->biBitCount != 4 && lpbi->biBitCount != 8) || lpbi->biHeight <= 0)
		return;

	bDecoding = TRUE;						// RLE decoder running
	dwPos = dwRow = 0;						// reset absolute position and row counter

	// image size
	dwSizeImage = WIDTHBYTES((DWORD)lpbi->biWidth * lpbi->biBitCount) * lpbi->biHeight;

	ZeroMemory(ppvBits,dwSizeImage);		// clear bitmap
	
	// image size in pixel
	dwSizeImage *= (8 / lpbi->biBitCount);

	// no. of pixels per line
	dwPixelPerLine = dwSizeImage / lpbi->biHeight;

	do
	{
		// length information is WORD aligned
		_ASSERT(((DWORD) lpbyBitmap % sizeof(WORD)) == 0);
		byLength     = *lpbyBitmap++;
		byColorIndex = *lpbyBitmap++;

		if (byLength)						// length information
		{
			// check for buffer overflow
			if (dwPos + byLength > dwSizeImage)
			{
				// write rest of data until buffer full
				byLength = (dwPos > dwSizeImage) ? 0 : (BYTE) (dwSizeImage - dwPos);
				bDecoding = FALSE;			// abort
			}

			if (lpbi->biBitCount == 4)		// RLE4
			{
				BYTE byColor[2];
				UINT s,d;

				// split into upper/lower nibble
				byColor[0] = byColorIndex >> 4;
				byColor[1] = byColorIndex & 0x0F;

				s = 0;						// source nibble selector [0/1]
				d = (~dwPos & 1) * 4;		// destination shift [0/4]

				while (byLength-- > 0)
				{
					// write nibble to memory
					_ASSERT((byColor[s] & 0xF0) == 0);
					ppvBits[dwPos++/2] |= (byColor[s] << d);
					s ^= 1;					// next source nibble
					d ^= 4;					// next destination shift
				}
			}
			else							// RLE8
			{
				while (byLength-- > 0)
					ppvBits[dwPos++] = byColorIndex;
			}
		}
		else								// escape sequence
		{
			switch (byColorIndex)
			{
			case 0: // End of Line
				dwPos = ++dwRow * dwPixelPerLine;
				break;
			case 1: // End of Bitmap
				bDecoding = FALSE;
				break;
			case 2: // Delta
				// column offset
				dwPos += *lpbyBitmap++;
				// row offset
				byColorIndex = *lpbyBitmap++;
				dwRow += byColorIndex;
				dwPos += dwPixelPerLine * byColorIndex;
				break;
			default: // absolute mode
				// check for buffer overflow
				if (dwPos + byColorIndex > dwSizeImage)
				{
					// write rest of data until buffer full
					byColorIndex = (dwPos > dwSizeImage) ? 0 : (BYTE) (dwSizeImage - dwPos);
					bDecoding = FALSE;		// abort
				}

				if (lpbi->biBitCount == 4)	// RLE4
				{
					BYTE byColor,byColorPair;
					UINT s,d;

					d = (~dwPos & 1) * 4;	// destination shift [0/4]

					for (s = 0; s < byColorIndex; ++s)
					{
						if ((s & 1) == 0)	// upper nibble
						{
							// fetch color pair
							byColorPair = *lpbyBitmap++;

							// get upper nibble
							byColor = (byColorPair >> 4);
						}
						else				// lower nibble
						{
							// get lower nibble
							byColor = (byColorPair & 0x0F);
						}

						// write nibble to memory
						_ASSERT((byColor & 0xF0) == 0);
						ppvBits[dwPos++/2] |= (byColor << d);
						d ^= 4;				// next destination shift
					}

					// for odd byte length detection
					byColorIndex = (++byColorIndex) >> 1;
				}
				else						// RLE8
				{
					CopyMemory(ppvBits+dwPos,lpbyBitmap,byColorIndex);
					dwPos += byColorIndex;
					lpbyBitmap += byColorIndex;
				}
				// word align on odd byte length
				if (byColorIndex & 1) ++lpbyBitmap;
				break;
			}
		}
	}
	while (bDecoding);
	return;
	#undef WIDTHBYTES
}

HBITMAP LoadBitmapFile(LPCTSTR szFilename)
{
	#define BI_RLE8	1L
	#define BI_RLE4	2L
	#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)

	HANDLE  hFile;
	BITMAPFILEHEADER Bmfh;
	LPBITMAPINFO pBmi;
	DWORD dwRead;
	DWORD dwCompression;
	LPBYTE ppvBits;
	INT nSize;

	HBITMAP hBitmap = NULL;					// bitmap not loaded

	hFile = CreateFile(FullFilename(szFilename),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
	if (hFile == INVALID_HANDLE_VALUE) return NULL;

	// read BITMAPFILEHEADER
	ReadFile(hFile,&Bmfh,sizeof(Bmfh),&dwRead,NULL);
	_ASSERT(dwRead == sizeof(Bmfh));
	if (Bmfh.bfType != 0x4D42) goto quit;	// "BM"

	// read BITMAPINFO
	nSize = Bmfh.bfSize - sizeof(Bmfh);
	pBmi = HeapAlloc(hHeap,0,nSize);
	if (pBmi == NULL) goto quit;
	ReadFile(hFile,pBmi,nSize,&dwRead,NULL);
	_ASSERT(dwRead == (DWORD) nSize);

	_ASSERT(hPalette == NULL);				// resource free
	hPalette = CreateBIPalette(&pBmi->bmiHeader);
	// save old palette
	hOldPalette = SelectPalette(hWindowDC, hPalette, FALSE);
	RealizePalette(hWindowDC);

	// save compression type of bitmap and generate a normal RGB-DIB
	dwCompression = pBmi->bmiHeader.biCompression;
	if (dwCompression == BI_RLE8 || dwCompression == BI_RLE4)
		pBmi->bmiHeader.biCompression = BI_RGB;

	hBitmap = CreateDIBSection(hWindowDC,pBmi,DIB_RGB_COLORS,&ppvBits,NULL,0);
	if (hBitmap)
	{
		// pointer to bitmap
		LPBYTE lpbyBitmap = (LPBYTE) pBmi + Bmfh.bfOffBits - sizeof(Bmfh);

		switch (dwCompression)
		{
		case BI_RLE4:
		case BI_RLE8:
			DecodeRleBmp(ppvBits,lpbyBitmap,&pBmi->bmiHeader);
			break;
		case BI_RGB:
			// calculate bitmap size
			pBmi->bmiHeader.biSizeImage = WIDTHBYTES((DWORD)pBmi->bmiHeader.biWidth * pBmi->bmiHeader.biBitCount)
										* labs(pBmi->bmiHeader.biHeight);
			// fall into BI_BITFIELDS to copy data
		case BI_BITFIELDS:
			CopyMemory(ppvBits,lpbyBitmap,pBmi->bmiHeader.biSizeImage);
		}
	}
	HeapFree(hHeap,0,pBmi);
	_ASSERT(hBitmap != NULL);

quit:
	CloseHandle(hFile);
	return hBitmap;

	#undef BI_RLE8
	#undef BI_RLE4
	#undef WIDTHBYTES
}

