/*
 *   files.c
 *
 *   This file is part of Emu48
 *
 *   Copyright (C) 1995 Sebastien Carlier
 *
 */
#include "pch.h"
#include "Emu48.h"
#include "kml.h"

char    szEmu48Directory[260];
char    szCurrentDirectory[260];
char    szCurrentKml[260];
char    szBackupKml[260];
char    szCurrentFilename[260];
char    szBackupFilename[260];
char    szBufferFilename[260];
char    szPort2Filename[260];

LPBYTE  pbyRom = NULL;
static HANDLE  hRomFile = NULL;
static HANDLE  hRomMap = NULL;
DWORD   dwRomSize = 0;
char    cCurrentRomType = 0;

static HANDLE  hPort2File = NULL;
static HANDLE  hPort2Map = NULL;
LPBYTE  pbyPort2 = NULL;
BOOL    bPort2Writeable = FALSE;
BOOL    bPort2IsShared = FALSE;
DWORD   dwPort2Mask = 0;
UINT    nPort2Bank = 0;

static BYTE pbySignatureE[16] = "Emu48 Document\xFE";
static BYTE pbySignatureW[16] = "Win48 Document\xFE";
static HANDLE  hCurrentFile = NULL;

static CHIPSET BackupChipset;
BOOL    bBackup = FALSE;



//################
//#
//#    ROM
//#
//################

BOOL MapRom(LPCSTR szFilename)
{
	DWORD   dwFileSizeHigh;

	if (pbyRom != NULL)
	{
		return FALSE;
	}
	SetCurrentDirectory(szEmu48Directory);
	hRomFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	SetCurrentDirectory(szCurrentDirectory);
	if (hRomFile == INVALID_HANDLE_VALUE)
	{
		hRomFile = NULL;
		return FALSE;
	}
	dwRomSize = GetFileSize(hRomFile, &dwFileSizeHigh);
	if (dwFileSizeHigh != 0)
	{ // file is too large.
		CloseHandle(hRomFile);
		hRomFile = NULL;
		dwRomSize = 0;
		return FALSE;
	}
	hRomMap = CreateFileMapping(hRomFile, NULL, PAGE_WRITECOPY, 0, dwRomSize, NULL);
	if (hRomMap == NULL)
	{
		CloseHandle(hRomFile);
		hRomFile = NULL;
		dwRomSize = 0;
		return FALSE;
	}
	if (GetLastError() == ERROR_ALREADY_EXISTS)
	{
		AbortMessage("Sharing file mapping handle.");
	} 
	pbyRom = MapViewOfFile(hRomMap, FILE_MAP_COPY, 0, 0, dwRomSize);
	if (pbyRom == NULL)
	{
		CloseHandle(hRomMap);
		CloseHandle(hRomFile);
		hRomMap = NULL;
		hRomFile = NULL;
		dwRomSize = 0;
		return FALSE;
	}
	return TRUE;
}

VOID UnmapRom()
{
	if (pbyRom==NULL) return;
	UnmapViewOfFile(pbyRom);
	CloseHandle(hRomMap);
	CloseHandle(hRomFile);
	pbyRom = NULL;
	hRomMap = NULL;
	hRomFile = NULL;
	dwRomSize = 0;
	return;
}



//################
//#
//#    Port2
//#
//################

BOOL MapPort2(LPCSTR szFilename)
{
	DWORD dwFileSizeLo;
	DWORD dwFileSizeHi;

	if (pbyPort2 != NULL)
	{
		return FALSE;
	}
	bPort2Writeable = TRUE;

	SetCurrentDirectory(szEmu48Directory);
	if (bPort2IsShared)
	{
		hPort2File = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
		SetCurrentDirectory(szCurrentDirectory);
		if (hPort2File == INVALID_HANDLE_VALUE)
		{
			bPort2Writeable = FALSE;
			SetCurrentDirectory(szEmu48Directory);
			hPort2File = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
			SetCurrentDirectory(szCurrentDirectory);
			if (hPort2File == INVALID_HANDLE_VALUE)
			{
				hPort2File = NULL;
				return FALSE;
			}
		}
	}
	else
	{
		hPort2File = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
		SetCurrentDirectory(szCurrentDirectory);
		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;
	}
	if (dwFileSizeLo & 0x3FFFF)
	{ // file size is wrong
		CloseHandle(hPort2File);
		hPort2File = NULL;
		dwPort2Mask = 0;
		bPort2Writeable = FALSE;
		return FALSE;
	}
	dwPort2Mask = 0;
	dwFileSizeHi = dwFileSizeLo >> 18;
	while ((dwFileSizeHi&1)==0)
	{
		dwPort2Mask = (dwPort2Mask<<1)+1;
		dwFileSizeHi>>=1;
	}
	dwPort2Mask = (dwPort2Mask<<1)+1;
	if (bPort2Writeable)
		hPort2Map = CreateFileMapping(hPort2File, NULL, PAGE_READWRITE, 0, dwFileSizeLo, NULL);
	else
		hPort2Map = CreateFileMapping(hPort2File, NULL, PAGE_READONLY, 0, dwFileSizeLo, NULL);
	if (hPort2Map == NULL)
	{
		CloseHandle(hPort2File);
		hPort2File = NULL;
		dwPort2Mask = 0;
		bPort2Writeable = FALSE;
		return FALSE;
	}
	if (bPort2Writeable)
		pbyPort2 = MapViewOfFile(hPort2Map, FILE_MAP_WRITE, 0, 0, dwFileSizeLo);
	else
		pbyPort2 = MapViewOfFile(hPort2Map, FILE_MAP_READ, 0, 0, dwFileSizeLo);
	if (pbyPort2 == NULL)
	{
		CloseHandle(hPort2Map);
		CloseHandle(hPort2File);
		hPort2Map = NULL;
		hPort2File = NULL;
		dwPort2Mask = 0;
		bPort2Writeable = FALSE;
		return FALSE;
	}
	return TRUE;
}

VOID UnmapPort2()
{
	if (pbyPort2==NULL) return;
	UnmapViewOfFile(pbyPort2);
	CloseHandle(hPort2Map);
	CloseHandle(hPort2File);
	pbyPort2 = NULL;
	hPort2Map = NULL;
	hPort2File = NULL;
	dwPort2Mask = 0;
	bPort2Writeable = FALSE;
	return;
}



//################
//#
//#    Patch
//#
//################

static BYTE Asc2Nib(char 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;
}

static DWORD Asc2Nib5(LPSTR lpBuf)
{
	return (
		 ((DWORD)Asc2Nib(lpBuf[0])<<16)
		|((DWORD)Asc2Nib(lpBuf[1])<<12)
		|((DWORD)Asc2Nib(lpBuf[2])<<8)
		|((DWORD)Asc2Nib(lpBuf[3])<<4)
		|((DWORD)Asc2Nib(lpBuf[4])));
}

BOOL PatchRom(LPCSTR szFilename)
{
	HANDLE hFile = NULL;
	DWORD  dwFileSizeLow = 0;
	DWORD  dwFileSizeHigh = 0;
	DWORD  lBytesRead = 0;
	LPBYTE lpBuf = NULL;
	DWORD  dwAddress = 0;
	UINT   nPos = 0;

	if (pbyRom == NULL) return FALSE;
	SetCurrentDirectory(szEmu48Directory);
	hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
	SetCurrentDirectory(szCurrentDirectory);
	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 = (LPBYTE)LocalAlloc(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])
	{
		if (lpBuf[nPos]==';') // comment ?
		{
			do 
			{
				if (lpBuf[nPos]=='\n') break;
				nPos++;
			} while (lpBuf[nPos]);
			if (lpBuf[nPos]==0)
			{
				LocalFree(lpBuf);
				return TRUE;
			}
		}
		do // remove blank space
		{
			if (  (lpBuf[nPos]!=' ')
				&&(lpBuf[nPos]!='\n')
				&&(lpBuf[nPos]!='\r')
				&&(lpBuf[nPos]!='\t')) break;
			nPos++;
		} while (lpBuf[nPos]);
		if (lpBuf[nPos] == 0) break;
		if (lpBuf[nPos+5]!=':')
		{
			nPos++;
			continue;
		}
		dwAddress = Asc2Nib5(lpBuf+nPos);
		nPos+=6;
		while (lpBuf[nPos])
		{
			if (lpBuf[nPos]<'0') break;
			if (lpBuf[nPos]>'9')
			{
				if (lpBuf[nPos]<'A') break;
				if (lpBuf[nPos]>'F') break;
				pbyRom[dwAddress] = lpBuf[nPos]-'A'+10;
			}
			else
			{
				pbyRom[dwAddress] = lpBuf[nPos]-'0';
			}
			dwAddress = (dwAddress+1)&0xFFFFF;
			nPos++;
		}
	}
	LocalFree(lpBuf);
	return TRUE;
}



//################
//#
//#    Documents
//#
//################

VOID ResetDocument()
{
	if (szCurrentKml[0])
	{
		KillKML();
	}
	if (hCurrentFile)
	{
		CloseHandle(hCurrentFile);
		hCurrentFile = NULL;
	}
	szCurrentKml[0] = 0;
	szCurrentFilename[0]=0;
	if (Chipset.Port0) LocalFree(Chipset.Port0);
	if (Chipset.Port1) LocalFree(Chipset.Port1);
	FillMemory(&Chipset,sizeof(Chipset),0);
	UpdateWindowStatus();
	return;
}

BOOL NewDocument()
{
	SaveBackup();
	ResetDocument();

	if (!DisplayChooseKml(0)) goto restore;
	if (!InitKML(szCurrentKml,FALSE)) goto restore;
	Chipset.type = cCurrentRomType;
	if (Chipset.type == 'S')
	{
		Chipset.Port0Size = 32;
		Chipset.Port1Size = 128;
		Chipset.Port1_Writeable = TRUE;
		Chipset.Port0 = (LPBYTE)LocalAlloc(0,Chipset.Port0Size*2048);
		_ASSERT(Chipset.Port0 != NULL);
		FillMemory(Chipset.Port0, Chipset.Port0Size*2048, 0);
		Chipset.Port1 = (LPBYTE)LocalAlloc(0,Chipset.Port1Size*2048);
		_ASSERT(Chipset.Port1 != NULL);
		FillMemory(Chipset.Port1, Chipset.Port1Size*2048, 0);
		Chipset.cards_status = 0x5;
	}
	else
	{
		Chipset.Port0Size = 128;
		Chipset.Port1Size = 128;
		Chipset.Port1_Writeable = TRUE;
		Chipset.Port0 = (LPBYTE)LocalAlloc(0,Chipset.Port0Size*2048);
		_ASSERT(Chipset.Port0 != NULL);
		FillMemory(Chipset.Port0, Chipset.Port0Size*2048, 0);
		Chipset.Port1 = (LPBYTE)LocalAlloc(0,Chipset.Port1Size*2048);
		_ASSERT(Chipset.Port1 != NULL);
		FillMemory(Chipset.Port1, Chipset.Port1Size*2048, 0);
		Chipset.cards_status = 0xA;
	}
	Map(0x00,0xFF);
	return TRUE;
restore:
	RestoreBackup();
	ResetBackup();
	if (pbyRom)
	{
		SetWindowPos(hWnd,NULL,(int)Chipset.wPosX,(int)Chipset.wPosY,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
		Map(0x00,0xFF);
	}
	return FALSE;
}

BOOL OpenDocument(LPCSTR szFilename)
{
	HANDLE  hFile = INVALID_HANDLE_VALUE;
	DWORD   lBytesRead;
	DWORD   lSizeofChipset;
	BYTE    pbyFileSignature[16];
	UINT    ctBytesCompared;
	UINT    nLength;

	SaveBackup();
	ResetDocument();

	// Open file
	if (lstrcmpi(szBackupFilename, szFilename)==0)
	{
		if (YesNoMessage("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("This file is missing or already loaded in another instance of Emu48.\n");
		goto restore;
	}

	// Read and Compare signature
	ReadFile(hFile, pbyFileSignature, 16, &lBytesRead, NULL);
	switch (pbyFileSignature[0])
	{
	case 'E':
		for (ctBytesCompared=0; ctBytesCompared<14; ctBytesCompared++)
		{
			if (pbyFileSignature[ctBytesCompared]!=pbySignatureE[ctBytesCompared])
			{
				AbortMessage("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("This file is not a valid Win48 document.");
				goto restore;
			}
		}
		break;
	default:
		AbortMessage("This file is not a valid document.");
		goto restore;
	}

	switch (pbyFileSignature[14])
	{
	case 0xFE: // Win48 2.1 / Emu48 0.99.x format
		ReadFile(hFile, &nLength, sizeof(nLength), &lBytesRead, NULL);
		ReadFile(hFile, szCurrentKml, nLength, &lBytesRead, NULL);
		if (nLength != lBytesRead) goto read_err;
		szCurrentKml[nLength] = 0;
		break;
	case 0xFF: // Win48 2.05 format
		break;
	default:
		AbortMessage("This file is for an unknown version of Emu48.\n"
			"Please contact the author (see Emu48.txt).");
		goto restore;
	}

	ReadFile(hFile, &lSizeofChipset, sizeof(lSizeofChipset), &lBytesRead, NULL);
	if (lBytesRead != sizeof(lSizeofChipset)) goto read_err;
	if (lSizeofChipset!=sizeof(CHIPSET))
	{
		AbortMessage("This file is probably corrupted, and cannot be loaded.");
		goto restore;
	}

	ReadFile(hFile, &Chipset, lSizeofChipset, &lBytesRead, NULL);
	if (lBytesRead != lSizeofChipset) goto read_err;
	Chipset.Port0 = NULL;
	Chipset.Port1 = NULL;

	SetWindowPos(hWnd,NULL,(int)Chipset.wPosX,(int)Chipset.wPosY,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);

	if (szCurrentKml == NULL)
	{
		if (!DisplayChooseKml(Chipset.type))
		{
			CloseHandle(hFile);
			goto restore;
		}
	}
	while (!InitKML(szCurrentKml,FALSE))
	{
		if (!DisplayChooseKml(Chipset.type))
		{
			CloseHandle(hFile);
			goto restore;
		}
	}

	if (Chipset.Port0Size)
	{
		Chipset.Port0 = (LPBYTE)LocalAlloc(0,Chipset.Port0Size*2048);
		if (Chipset.Port0 == NULL)
		{
			AbortMessage("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 = (LPBYTE)LocalAlloc(0,Chipset.Port1Size*2048);
		if (Chipset.Port1 == NULL)
		{
			AbortMessage("Memory Allocation Failure.");
			goto restore;
		}

		ReadFile(hFile, Chipset.Port1, Chipset.Port1Size*2048, &lBytesRead, NULL);
		if (lBytesRead != Chipset.Port1Size*2048) goto read_err;
	}

	lstrcpy(szCurrentFilename, szFilename);
	hCurrentFile = hFile;
	Map(0x00,0xFF);
	SetWindowTitle(szCurrentFilename);
	UpdateWindowStatus();
	return TRUE;

read_err:
	AbortMessage("This file must be truncated, and cannot be loaded.");
restore:
	if (INVALID_HANDLE_VALUE == hFile)
		CloseHandle(hFile);
	RestoreBackup();
	ResetBackup();
	return FALSE;
}

BOOL SaveDocument()
{
	DWORD	lBytesWritten;
	DWORD	lSizeofChipset;
	UINT    nLength;
	RECT    Rect;

	if (hCurrentFile == NULL) return FALSE;

	GetWindowRect(hWnd, &Rect);
	Chipset.wPosX = (WORD)Rect.left;
	Chipset.wPosY = (WORD)Rect.top;

	SetFilePointer(hCurrentFile,0,0,FILE_BEGIN);
	if (!WriteFile(hCurrentFile, pbySignatureE, 16, &lBytesWritten, NULL))
	{
		AbortMessage("Could not write into file !");
		return FALSE;
	}

	nLength = strlen(szCurrentKml);
	WriteFile(hCurrentFile, &nLength, sizeof(nLength), &lBytesWritten, NULL);
	WriteFile(hCurrentFile, szCurrentKml, nLength, &lBytesWritten, NULL);
	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);

	return TRUE;
}

BOOL SaveDocumentAs(LPCTSTR szFilename)
{
	HANDLE hFile = INVALID_HANDLE_VALUE;

	if (hCurrentFile)
	{
		if (lstrcmpi(szFilename, szCurrentFilename)==0)
		{
			hFile = hCurrentFile;
		}
	}
	if (hFile == INVALID_HANDLE_VALUE)
	{
		hFile = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
	}
	if (hFile == INVALID_HANDLE_VALUE)
	{
		AbortMessage("This file must be currently used by another instance of Emu48.");
		return FALSE;
	}
	lstrcpy(szCurrentFilename, szFilename);
	hCurrentFile = hFile;
	SetWindowTitle(szCurrentFilename);
	UpdateWindowStatus();
	return SaveDocument();
}



//################
//#
//#    Backup
//#
//################

BOOL SaveBackup()
{
	if (pbyRom == NULL) return FALSE;
	lstrcpy(szBackupFilename, szCurrentFilename);
	lstrcpy(szBackupKml, szCurrentKml);
	if (BackupChipset.Port0) LocalFree(BackupChipset.Port0);
	if (BackupChipset.Port1) LocalFree(BackupChipset.Port1);
	CopyMemory(&BackupChipset, &Chipset, sizeof(Chipset));
	BackupChipset.Port0 = (LPBYTE)LocalAlloc(0,Chipset.Port0Size*2048);
	CopyMemory(BackupChipset.Port0, Chipset.Port0, Chipset.Port0Size*2048);
	BackupChipset.Port1 = (LPBYTE)LocalAlloc(0,Chipset.Port1Size*2048);
	CopyMemory(BackupChipset.Port1, Chipset.Port1, Chipset.Port1Size*2048);
	bBackup = TRUE;
	UpdateWindowStatus();
	return TRUE;
}

BOOL RestoreBackup()
{
	if (!bBackup) return FALSE;
	ResetDocument();
	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 = (LPBYTE)LocalAlloc(0,Chipset.Port0Size*2048);
	CopyMemory(Chipset.Port0, BackupChipset.Port0, Chipset.Port0Size*2048);
	Chipset.Port1 = (LPBYTE)LocalAlloc(0,Chipset.Port1Size*2048);
	CopyMemory(Chipset.Port1, BackupChipset.Port1, Chipset.Port1Size*2048);
	SetWindowPos(hWnd,NULL,(int)Chipset.wPosX,(int)Chipset.wPosY,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
	UpdateWindowStatus();
	Map(0x00,0xFF);
	return TRUE;
}

BOOL ResetBackup()
{
	if (!bBackup) return FALSE;
	szBackupFilename[0] = 0;
	szBackupKml[0] = 0;
	if (BackupChipset.Port0) LocalFree(BackupChipset.Port0);
	if (BackupChipset.Port1) LocalFree(BackupChipset.Port1);
	FillMemory(&BackupChipset,sizeof(BackupChipset),0);
	bBackup = FALSE;
	UpdateWindowStatus();
	return TRUE;
}



//################
//#
//#    Open File Common Dialog Boxes
//#
//################

static VOID InitializeOFN(LPOPENFILENAME ofn)
{
	FillMemory((LPVOID)ofn, sizeof(OPENFILENAME), 0);
	ofn->lStructSize = sizeof(OPENFILENAME);
	ofn->hwndOwner = hWnd;
	ofn->Flags = OFN_EXPLORER|OFN_HIDEREADONLY;
	return;
}

BOOL GetOpenFilename()
{
	OPENFILENAME ofn;

	InitializeOFN(&ofn);
	ofn.lpstrFilter = 
		"Emu48 Document (*.E48)\0*.E48\0"
		"Win48 Document (*.W48)\0*.W48\0"
		"\0\0";
	ofn.lpstrDefExt = "E48";
	ofn.nFilterIndex = 1;
	ofn.lpstrFile = LocalAlloc(0,512);
	ofn.lpstrFile[0] = 0;
	ofn.nMaxFile = 512;
	ofn.Flags |= OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST;
	if (GetOpenFileName(&ofn) == FALSE)
	{
		LocalFree(ofn.lpstrFile);
		return FALSE;
	}
	lstrcpy(szBufferFilename, ofn.lpstrFile);
	LocalFree(ofn.lpstrFile);
	return TRUE;
}

BOOL GetSaveAsFilename()
{
	OPENFILENAME ofn;

	InitializeOFN(&ofn);
	ofn.lpstrFilter = 
		"Emu48 Document (*.E48)\0*.E48\0"
		"Win48 Document (*.W48)\0*.W48\0"
		"\0\0";
	ofn.lpstrDefExt = "E48";
	ofn.nFilterIndex = 1;
	ofn.lpstrFile = LocalAlloc(0,512);
	ofn.lpstrFile[0] = 0;
	ofn.nMaxFile = 512;
	ofn.Flags |= OFN_CREATEPROMPT|OFN_OVERWRITEPROMPT;
	if (GetSaveFileName(&ofn) == FALSE)
	{
		LocalFree(ofn.lpstrFile);
		return FALSE;
	}
	lstrcpy(szBufferFilename, ofn.lpstrFile);
	LocalFree(ofn.lpstrFile);
	return TRUE;
}

BOOL GetLoadObjectFilename()
{
	OPENFILENAME ofn;

	InitializeOFN(&ofn);
	ofn.lpstrFilter = "All Files (*.*)\0*.*\0" "\0\0";
	ofn.nFilterIndex = 1;
	ofn.lpstrFile = LocalAlloc(0,512);
	ofn.lpstrFile[0] = 0;
	ofn.nMaxFile = 512;
	ofn.Flags |= OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST;
	if (GetOpenFileName(&ofn) == FALSE)
	{
		LocalFree(ofn.lpstrFile);
		return FALSE;
	}
	lstrcpy(szBufferFilename, ofn.lpstrFile);
	LocalFree(ofn.lpstrFile);
	return TRUE;
}

BOOL GetSaveObjectFilename()
{
	OPENFILENAME ofn;

	InitializeOFN(&ofn);
	ofn.lpstrFilter = "All Files (*.*)\0*.*\0" "\0\0";
	ofn.nFilterIndex = 1;
	ofn.lpstrFile = LocalAlloc(0,512);
	ofn.lpstrFile[0] = 0;
	ofn.nMaxFile = 512;
	ofn.Flags |= OFN_CREATEPROMPT|OFN_OVERWRITEPROMPT;
	if (GetSaveFileName(&ofn) == FALSE)
	{
		LocalFree(ofn.lpstrFile);
		return FALSE;
	}
	lstrcpy(szBufferFilename, ofn.lpstrFile);
	LocalFree(ofn.lpstrFile);
	return TRUE;
}



//################
//#
//#    Load and Save HP48 Objects
//#
//################

BOOL LoadObject(LPCSTR szFilename)
{
	HANDLE hFile;
	DWORD  dwFileSizeLow;
	DWORD  dwFileSizeHigh;
	DWORD  lBytesRead;
	LPBYTE lpBuf;
	BOOL   bKermit = FALSE;
	DWORD  dwLength, dwAddress;
	DWORD  i;

	hFile = CreateFile(szFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
	if (hFile == INVALID_HANDLE_VALUE) return FALSE;
	dwFileSizeLow = GetFileSize(hFile, &dwFileSizeHigh);
	if (dwFileSizeHigh != 0)
	{ // file is too large.
		CloseHandle(hFile);
		return FALSE;
	}
	lpBuf = (LPBYTE)LocalAlloc(0,dwFileSizeLow*2);
	if (lpBuf == NULL)
	{
		CloseHandle(hFile);
		return FALSE;
	}
	ReadFile(hFile, lpBuf+dwFileSizeLow, dwFileSizeLow, &lBytesRead, NULL);
	CloseHandle(hFile);

	if ((lpBuf[dwFileSizeLow+0]=='H')
	  &&(lpBuf[dwFileSizeLow+1]=='P')
	  &&(lpBuf[dwFileSizeLow+2]=='H')
	  &&(lpBuf[dwFileSizeLow+3]=='P')
	  &&(lpBuf[dwFileSizeLow+4]=='4')
	  &&(lpBuf[dwFileSizeLow+5]=='8')
	  &&(lpBuf[dwFileSizeLow+6]=='-')) bKermit = TRUE;

	for (i=0; i<dwFileSizeLow; i++)
	{
		BYTE byTwoNibs = lpBuf[i+dwFileSizeLow];
		lpBuf[i*2  ] = (BYTE)(byTwoNibs&0xF);
		lpBuf[i*2+1] = (BYTE)(byTwoNibs>>4);
	}

	if (bKermit == TRUE)
	{ // load as binary
		dwLength  = RPL_ObjectSize(lpBuf+16);
		dwAddress = RPL_CreateTemp(dwLength);
		if (dwAddress == 0)
		{
			LocalFree(lpBuf);
			AbortMessage("The HP48 has not enough free memory left to load this binary file.");
			return FALSE;
		}
		for (i=0; i<dwLength; i++)
		{
			Nwrite(lpBuf+i+16, dwAddress+i, 1);
		}
	}
	else
	{ // load as string
		BYTE lpHead[5];
		dwLength = dwFileSizeLow*2;
		dwAddress = RPL_CreateTemp(dwLength+10);
		if (dwAddress == 0)
		{
			LocalFree(lpBuf);
			AbortMessage("The HP48 has not enough free memory left to load this text file.");
			return FALSE;
		}
		Nunpack(lpHead,0x02A2C,5);
		Nwrite(lpHead,dwAddress,5);
		Nunpack(lpHead,dwLength+5,5);
		Nwrite(lpHead,dwAddress+5,5);
		for (i=0; i<dwLength; i++)
		{
			Nwrite(lpBuf+i, dwAddress+10+i, 1);
		}
	}
	RPL_Push(dwAddress);
	LocalFree(lpBuf);
	return TRUE;
}


BOOL SaveObject(LPCSTR szFilename)
{
	HANDLE	hFile;
	DWORD	lBytesWritten;
	DWORD   dwAddress;
	DWORD   dwLength;

	dwAddress = RPL_Pick(1);
	if (dwAddress == 0)
	{
		AbortMessage("Too Few Arguments.");
		return FALSE;
	}
	dwLength = (RPL_SkipOb(dwAddress) - dwAddress + 1) / 2;

	hFile = CreateFile(szFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		AbortMessage("Cannot open file.");
		return FALSE;
	}

	WriteFile(hFile, "HPHP48-W", 8, &lBytesWritten, NULL);
	while (dwLength--)
	{
		BYTE byByte = Read2(dwAddress);
		WriteFile(hFile, &byByte, 1, &lBytesWritten, NULL);
		dwAddress += 2;
	}
	CloseHandle(hFile);
	return TRUE;
}



//################
//#
//#    Load Bitmap
//#
//################

static UINT DibNumColors (LPBITMAPINFOHEADER lpbi)
{
	UINT bits;

	if (lpbi->biClrUsed != 0) return (UINT)lpbi->biClrUsed;
	bits = lpbi->biBitCount;

	switch (bits)
	{
	case 1:
		return 2;
	case 4:
		return 16;
	case 8:
		return 256;
	default:
		/* A 24 bitcount DIB has no color table */
		return 0;
	}
}

static HPALETTE CreateBIPalette(LPBITMAPINFOHEADER 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 FAR *)((LPSTR)lpbi + (WORD)lpbi->biSize);
	nNumColors = DibNumColors(lpbi);

	if (nNumColors)
	{
		// Allocate for the logical palette structure
		pPal = (LOGPALETTE*)LocalAlloc(LPTR,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);
		LocalFree((HANDLE)pPal);
	}
	else
	{
		if (lpbi->biBitCount == 24)
		{
			// A 24 bitcount DIB has no color table entries so, set the
			// number of to the maximum value (256).
			nNumColors = 256;
			pPal = (LOGPALETTE*)LocalAlloc(LPTR,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 = (BYTE)0;
				
				if (!(red += 32))
					if (!(green += 32))
					blue += 64;
			}
			hpal = CreatePalette(pPal);
			LocalFree((HANDLE)pPal);
		}
	}
	return hpal;
}

HBITMAP LoadBitmapFile(LPCSTR szFilename)
{
	HANDLE  hFile;
	HANDLE  hMap;
	LPBYTE  pbyFile;
	HBITMAP hBitmap;
	LPBITMAPFILEHEADER pBmfh;
	LPBITMAPINFO pBmi;
	
	SetCurrentDirectory(szEmu48Directory);
	hFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	SetCurrentDirectory(szCurrentDirectory);
	if (hRomFile == INVALID_HANDLE_VALUE) return NULL;
	hMap = CreateFileMapping(hFile, NULL, PAGE_WRITECOPY, 0, 0, NULL);
	if (hMap == NULL)
	{
		CloseHandle(hFile);
		return NULL;
	}
	pbyFile = MapViewOfFile(hMap, FILE_MAP_COPY, 0, 0, 0);	
	if (pbyFile == NULL)
	{
		CloseHandle(hMap);
		CloseHandle(hFile);
		return NULL;
	}
	
	hBitmap = NULL;
	pBmfh = (LPBITMAPFILEHEADER)pbyFile;
	if (pBmfh->bfType != 0x4D42) goto quit; // "BM"
	pBmi = (LPBITMAPINFO)(pbyFile+sizeof(BITMAPFILEHEADER));
	
	hPalette = CreateBIPalette(&pBmi->bmiHeader);
	SelectPalette(hWindowDC, hPalette, FALSE);
	RealizePalette(hWindowDC);

	if (FALSE&&(pBmi->bmiHeader.biBitCount <= 8))
	{
		UINT i;
		LPDWORD pdwTable = ((LPDWORD)&(pBmi->bmiColors[0]));
		if (pBmi->bmiHeader.biClrUsed)
			for (i=0; i<(UINT)pBmi->bmiHeader.biClrUsed; i++) pdwTable[i]=i;
		else
			for (i=0; i<256; i++) pdwTable[i]=i;
		hBitmap = CreateDIBitmap(
			hWindowDC,
			&pBmi->bmiHeader,
			CBM_INIT,
			pbyFile+pBmfh->bfOffBits,
			pBmi, DIB_PAL_COLORS);
    }
	else
	{
		hBitmap = CreateDIBitmap(
			hWindowDC,
			&pBmi->bmiHeader,
			CBM_INIT,
			pbyFile+pBmfh->bfOffBits,
			pBmi, DIB_RGB_COLORS);
	}

quit:
	UnmapViewOfFile(pbyFile);
	CloseHandle(hMap);
	CloseHandle(hFile);
	return hBitmap;
}
