/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/loader.c 48    11/08/06 9:21p Racini $
 ************************************************************************/
/************************************************************************\
  MITS Altair Emulator
  File loader code

  Copyright (c) 2000-2006 Richard A. Cini

  Intel HEX read/write code Copyright Paul Stoffregen, paul@ece.orst.edu
	Sample code from Simtel.net modified to work with Altair32. See
	hexfile.c.


Change Log:
  2000/07/02  RAC -- Initial compilation
  2000/08/26  RAC -- Got hex importer code working
  2000/09/07  RAC -- Merged global variables with altair.c
  2000/09/10  RAC -- Fixed external references for Mem and fFileType
  2000/09/11  RAC -- Deleted Mem and fFileType. Mem externed in i8080.h
						and fFileType externed in punch.h
  2000/10/03  RAC -- Changed scoping, again
  2000/10/16  RAC -- Initial checkin after mods from Theo. Changes
						included def of DlgProc's to DLGPROC and
						removing extra indirection on szBuffer.
  2000/10/17  RAC -- Added LED update after loading images and corrected
						DlgBox problem.
  2000/10/19  RAC -- Added WS_TABSTOP style to edit control CreateWindow
  2000/10/20  RAC -- Made major changes to address dlg processes
  2000/10/26  RAC -- Tampered with dialog focus again; added conditionals
						on loaded/saved messages.
  2000/10/31  RAC -- Fixed Mem array indexing problem in file|save code and
						totally rewrote load and save to be able to reuse
						routines.
  2000/11/02  RAC -- Enhanced ReadBinaryFile to allow reuse in ROM routine.
  2000/11/07  RAC -- Added conditionals in RBF for the ROM loader
  2000/11/10  RAC -- Major rework of RBF to work better with the static program
						loader.
  2001/02/20  RAC -- Return parens; removed zero terminator on API calls
  2001/05/24  RAC -- Fixed error where save range dialog appears after canceling
						save.
  2001/08/19  RAC -- RELEASE MARKER -- v2.0
  2001/08/28  RAC -- Allowed user to hit ENTER on load address dialog without
						specifying address; assumes 0 start
  2001/11/22  RAC -- Changed calculation for load address and save address
						(cleaner).
  2001/12/14  RAC -- RELEASE MARKER -- v2.1
  2002/01/02  RAC -- Changes for new master include file.
  2002/01/31  RAC -- RELEASE MARKER -- v2.2
  2002/07/07  RAC -- RELEASE MARKER -- v2.3
  2002/08/24  RAC -- Minor changes to fix ReadBinFile/WriteBinFile (Rodger
  						Smedley)
  2002/08/23  RAC -- RELEASE MARKER -- v2.30.10
  2002/09/03  RAC -- Began merging 680b changes into Altair32 tree.
  2002/09/19  RAC -- Modified code to support syscfg.c. Re-wrote TapeFileLength
  						to use its own file handle to check the file lentgh,
  						making it reusable outside loader.c.
  2002/09/24  RAC -- Changes to ReadBinaryFile to remove special ROM handling
  						since what defines a "ROM" now is the range into which
  						a file is loaded, not the file itself (although
  						mentally you may be loading a ROM).
  2002/11/15  RAC -- RELEASE MARKER -- v2.40.2100
  2003/04/01  RAC -- Minor changes to array sizes
  2003/04/26  RAC -- RELEASE MARKER -- v2.50.2045
  2003/08/11  RAC -- Fixed one-off error in calculation of end_addr in
  						lpAskSaveAddr.
  2004/01/20  RAC -- Diff'ed changes from FJS (Fred J. Scipione)
  2004/03/09  RAC -- Diff'ed more changes from FJS (Fred J. Scipione)
  2004/07/30  RAC -- RELEASE MARKER -- v3.00.0135
  2006/05/01  RAC -- RELEASE MARKER -- v3.10.0200
  2006/11/15  RAC -- RELEASE MARKER -- v3.20.0400
\************************************************************************/
#include <windows.h>
#include <commdlg.h>
#include <commctrl.h>
#include <string.h>
#include <stdio.h>		// C-lib - I/O 
#include "altair32.h"	// includes "resource.h"
#include "comcthlp.h"	// common control macro file
#include "hexfile.h"	// hex file manipulation stuff


/**  Typedefs & Defines  **************************************/
#define MAXHEXLINE 24	// the max number of bytes to put in one hex line
#define	MAXROM  (( 64 * 1024 ) - 256 ) 	// 64k-256b=65,280
#define MAXPGS	( MAXROM >> 256 ) 		// 255 pages


/**  Module Globals *******************************************/
static char szBuffer[_MAX_FNAME + _MAX_EXT];	// gereral "file" buffer
static char szFileDir[_MAX_PATH];
static char szFilter[] =   "Intel Hex Files (.HEX)\0*.hex\0" \
						   "Binary Files (.BIN)\0*.bin\0\0";
static int  start_addr = 0;
static int  end_addr = 0;
static int  image_len = 0;
static int  iImageType = 0;	// 0=RAM; 1 = ROM
static int  end_addr_typ = IDC_RADIO_END;
static OPENFILENAME l_ofn;
static OPENFILENAME s_ofn;


/**  Forward prototypes - private *****************************/
static BOOL ReadHexFile( HWND, PSTR, PSTR );
static BOOL WriteHexFile( HWND, PSTR, PSTR, int, int );
static BOOL WriteBinaryFile( HWND, PSTR, PSTR, int, int );
uint32 hextoi( char * );		// hex-int conversion
// static BOOL load_file( char *filename );


/**************************************************************\
*   Public/Private Routines   *********************************|
\**************************************************************/
/*--  TapeFileLength  -----------------------------------------
	Get the length of a file.

	Params:		char *filename

	Uses:		locals only

	Returns:	(long)file length. Length of 0 is error.

	Comments:	TFL uses its own file handle, making it standalone.
-------------------------------------------------------------*/
long TapeFileLength(char *filename)
{
	FILE *t_file = NULL;
	long pos = 0l;
	long len = 0l;

	if (NULL == (t_file = fopen(filename, "rb"))){
		return 0l;
	}

	pos = ftell (t_file);
	fseek(t_file, 0, SEEK_END);
	len = ftell (t_file);
	fseek(t_file, pos, SEEK_SET);
	fclose(t_file);
	return len;
}


/*--  hextoi  -------------------------------------------------
	Converts an ASCII hexadecimal string into an integer.

	Params:		*char_buf

	Uses:		nothing

	Returns:	integer representation of string

	Comments:
-------------------------------------------------------------*/
unsigned int hextoi(char* cptr)
{

	unsigned int i, j = 0;

    // isxdigit returns type int
    while (cptr && *cptr && isxdigit(*cptr))
    {
    	i = *cptr++ - '0';
        if (9 < i)
        	i -= 7;
        j <<= 4;
        j |= (i & 0x0f);
    }
    return(j);
}


// FJS -
#if 0
/*--  load_file  ----------------------------------------------
	Loads an Intel HEX file into memory. Used by debugger.

	Params:		*filename

	Uses:		Mem[]

	Returns:	Load success/failure

	Comments:	SOLace compatibiity routine
-------------------------------------------------------------*/
static BOOL load_file(char *filename)

{
	char line[1000];
	FILE *fin = NULL;
	int  addr = 0;
	int  n = 0;
	int  status = 0;
	int  bytes[256];
	int  i = 0;
	int  total = 0;
	int  lineno = 1;
	int  minaddr = 65536;
	int  maxaddr = 0;


    // open the file for reading
    if (NULL == (fin = fopen (filename, "r"))){
		return FALSE;
	}

	while (!feof(fin) && !ferror(fin)) {
		line[0] = '\0';
		fgets(line, 1000, fin);
		if (line[strlen(line)-1] == '\n') line[strlen(line)-1] = '\0';
		if (line[strlen(line)-1] == '\r') line[strlen(line)-1] = '\0';
		if (parse_hex_line(line, bytes, &addr, &n, &status)) {
			if (status == 0) {  /* data */
				for(i=0; i<=(n-1); i++) {
					Mem[addr] = bytes[i] & 255;
					total++;
					if (addr < minaddr) minaddr = addr;
					if (addr > maxaddr) maxaddr = addr;
					addr++;
				}
			}
			if (status == 1) {  /* end of file */
				fclose(fin);
				break; // return TRUE; // FJS
			}
			if (status == 2);  /* begin of file */
		}
		lineno++;
	}
	return TRUE; // FJS
}
#endif


/*--  ReadHexFile  --------------------------------------------
	Reads Intel HEX file from disk to memory.

	Params:		HWND, *fqual_path, *fname

	Uses:		Mem[]

	Returns:	Success/failure

	Comments:
-------------------------------------------------------------*/
static BOOL ReadHexFile(HWND hWnd, PSTR pstrFileName, PSTR pstrTitleName)
{

	char line[1000];
	FILE *pFile = NULL;
	int  addr = 0;
	int  n = 0;
	int  status = 0;
	int  bytes[256];
	int  i = 0;
	int  total = 0;
	int  lineno = 1;
	int  minaddr = 65536;
	int  maxaddr = 0;

    // open the file for reading
    if (NULL == (pFile = fopen(pstrFileName, "r"))){
		HOST_ThrowErrMsg("ReadHexFile: Unable to open image file!");
		return FALSE;
	}

	while (!feof(pFile) && !ferror(pFile)) {
		line[0] = '\0';
		fgets(line, 1000, pFile);
		if (line[strlen(line)-1] == '\n') line[strlen(line)-1] = '\0';
		if (line[strlen(line)-1] == '\r') line[strlen(line)-1] = '\0';
		if (parse_hex_line(line, bytes, &addr, &n, &status)) {
			if (status == 0) {  /* data */
				for(i=0; i<=(n-1); i++) {
					Mem[addr] = bytes[i] & 255;
					total++;
					if (addr < minaddr) minaddr = addr;
					if (addr > maxaddr) maxaddr = addr;
					addr++;
				}
			}
			if (status == 1) {  /* end of file */
				fclose(pFile);
				wsprintf(szBuffer, "Loaded 0x%04X bytes between 0x%04X to 0x%04X\n", total, minaddr, maxaddr);
				Status_SetText(hwndStatusBar, STATBAR_READY, 0, szBuffer);
				return TRUE;
			}
			if (status == 2);  /* begin of file */
		} else {
			wsprintf(szBuffer, "Error: '%s', line: %d\n", pstrFileName, lineno);
			HOST_ThrowErrMsg(szBuffer);
		}
		lineno++;
	}
	return TRUE;
}


/*--  ReadBinaryFile  -----------------------------------------
	Reads a binary file from disk to memory. Used to load ROMs
	as well as conventional RAM programs

	Params:		HWND, *fqual_path, *fname, load_addr

	Uses:		Mem[]

	Returns:	operation pass/fail

	Comments:
-------------------------------------------------------------*/
//uint32 ReadBinaryFile (HWND hWnd, PSTR pstrFileName, PSTR pstrTitleName, int iLoadAddr)
BOOL ReadBinaryFile (HWND hWnd, PSTR pstrFileName, PSTR pstrTitleName, int iLoadAddr)
{

	FILE *pFileIn = NULL;		// ptr for file opening
	long iLength = 0l;			// length of read buffer


    // open the image file for reading; leave error handling to caller
    if (NULL == (pFileIn = fopen(pstrFileName, "rb")))
		return FALSE;
		//return RBF_ERR_CANNOT_OPEN_IMAGE
	

	// Get raw file size. iLength is a (long) 1-based count.
	iLength = TapeFileLength(pstrFileName);

	// Make sure that ending address doesn't exceed address space.
	if (((long)iLoadAddr + iLength) > MEMSIZE) {
		HOST_ThrowErrMsg("ReadBinaryFile: Program ending location exceeds processor address space!");
	    fclose(pFileIn);
		return FALSE;
		//return RBF_ERR_IMAGE_TOO_LARGE
	}

	// Read the file into memory
	fread((char *) (Mem + iLoadAddr), 1, (int)iLength, pFileIn);

	if (ferror(pFileIn)) {
		HOST_ThrowErrMsg("ReadBinaryFile: Error loading file into memory!");
		wsprintf(szBuffer, "Error loading file %s", pstrTitleName);
		Status_SetText(hwndStatusBar, STATBAR_READY, 0, szBuffer);
		fclose(pFileIn);
		return FALSE;
		//return RBF_ERR_IMAGE_LOAD_ERROR
	}

	wsprintf(szBuffer, "Loaded 0x%04X bytes to 0x%04X\n\r", (int)iLength, iLoadAddr);
	Status_SetText(hwndStatusBar, STATBAR_READY, 0, szBuffer);
	fclose(pFileIn);
	return TRUE;
	//return RBF_ERR_SUCCESS
}


/*--  WriteHexFile  -------------------------------------------
	Writes an Intel HEX formatted file to disk.

	Params:		HWND, *fqual_path, *fname, start, end

	Uses:		Mem[]

	Returns:	Success/failure

	Comments:
-------------------------------------------------------------*/
static BOOL WriteHexFile(HWND hWnd,
						  PSTR pstrFileName,
						  PSTR pstrTitleName,
						  int  iSaveStart,
						  int  iSaveEnd)
{
	FILE *pFileOut = NULL;		// ptr for file opening
	int	 iAddr = 0;			// loop variable


	// Ensure that start and end are 0<=x<=ffff
	iSaveStart &= 0xffff;
	iSaveEnd &= 0xffff;

	if (iSaveStart > iSaveEnd) {
		HOST_ThrowErrMsg("WriteHexFile: Begin address must be less than the ending address!");
		return FALSE;
	}

	// Open the file for writing
	if (NULL == (pFileOut = fopen(pstrFileName, "w"))) {
		HOST_ThrowErrMsg("WriteHexFile: Unable to open the image file for writing!");
		return FALSE;
	}

	// Write loop. Call once for each byte to write.
	for (iAddr=iSaveStart; iAddr<=iSaveEnd; iAddr++ ){
		//		*file,    byte_to_write,    addr_of_byte, flag
		hexout(pFileOut, (int) Mem[iAddr], iAddr, 0);
	}

	// Flush buffers and close the file
	hexout(pFileOut, 0, 0, 1);
	fclose(pFileOut);

	wsprintf(szBuffer, "Memory 0x%04X to 0x%04X written to '%s'\n", iSaveStart, iSaveEnd, pstrFileName);
	Status_SetText(hwndStatusBar, STATBAR_READY, 0, szBuffer);
	return TRUE;
}


/*--  WriteBinaryFile  ----------------------------------------
	Write a binary file to disk.

	Params:		HWND, *fqual_path, *fname, start, end

	Uses:    	Mem[]

	Returns:	operation pass/fail

	Comments:	Generally called from dialog box handler, but
				params are general enough to be used elsewhere.
-------------------------------------------------------------*/
static BOOL WriteBinaryFile(HWND hWnd,
							 PSTR pstrFileName,
							 PSTR pstrTitleName,
							 int  iSaveStart,
							 int  iSaveEnd)
{
	FILE *pFileOut = NULL;		// ptr for file opening


	if (iSaveStart > iSaveEnd) {
		HOST_ThrowErrMsg("WriteBinaryFile: Begin address must be lower than the ending address!");
		return FALSE;
	}

	// Open the file for writing
	if (NULL == (pFileOut = fopen(pstrFileName, "wb"))) {
		HOST_ThrowErrMsg("WriteBinaryFile: Unable to open the image file for writing!");
		return FALSE;
	}

	// Do the actual writing.
	fwrite ((char *)(Mem + iSaveStart), sizeof(byte), (iSaveEnd - iSaveStart), pFileOut);
	if (ferror(pFileOut)) {
		HOST_ThrowErrMsg("WriteBinaryFile: Error writing memory to file");
		wsprintf(szBuffer, "Error writing file %s", pstrTitleName);
		Status_SetText(hwndStatusBar, STATBAR_READY, 0, szBuffer);
		fclose(pFileOut);
		return FALSE;
	}

	fclose(pFileOut);
	wsprintf(szBuffer, "Memory 0x%04X to 0x%04X written to '%s'\n", iSaveStart, iSaveEnd, pstrTitleName);
	Status_SetText(hwndStatusBar, STATBAR_READY, 0, szBuffer);
	return TRUE;
}


/**************************************************************\
*   Windows Callback Routines  ********************************|
\**************************************************************/

/***************************************************************************\
*
*   FUNCTION: lpAskLoadAddr(HWND, UINT, WPARAM, LPARAM)
*
*   PURPOSE:  Processes messages for "address" dialog box. Initially, code
*		assumes decimal address entered. Future enhancements to
*		include radio button array to allow the selection of
*		hex and octal variants.
*
*   MESSAGES:
*
*		WM_INITDIALOG - initialize dialog box
*		WM_COMMAND    - Input received
*
*   COMMENTS:
*
*		Application modal.
*
\***************************************************************************/
BOOL CALLBACK lpAskLoadAddr(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	BOOL fRet;
	char szHexBufr[10];
	int iRangeEnd = 0;


// In a DlgBox procedure, in general return TRUE if the message was processed
// and FALSE if not. WM_INITDIALOG must return TRUE if Windows will set the
// default focus to the control specified in wParam; otherwise return FALSE.

	switch (iMsg) {
		case WM_INITDIALOG:
			// gray out start address and ROM if file_fmt is Hex (ROM
			// support is limited to binary files).
			EnableWindow(GetDlgItem(hDlg, IDC_EDIT_STARTA), FALSE);

			strcpy(szFileDir, winstate.szFilesDir);
			SetFocus(GetDlgItem (hDlg,IDC_BROWSEA));
		    return FALSE;			// don't let Windows set default focus

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDC_BROWSE:
					// Get the filename
					l_ofn.lStructSize       = sizeof(OPENFILENAME);
					l_ofn.hwndOwner         = hDlg;
					l_ofn.hInstance         = (HINSTANCE)NULL;
					l_ofn.lpstrFilter       = szFilter;
					l_ofn.lpstrCustomFilter = NULL;
					l_ofn.nMaxCustFilter    = 0;
					l_ofn.nFilterIndex      = 0;
					l_ofn.lpstrFile         = winstate.szFileName;
					l_ofn.nMaxFile          = _MAX_PATH;
					l_ofn.lpstrFileTitle    = winstate.szTitleName;
					l_ofn.nMaxFileTitle     = _MAX_FNAME + _MAX_EXT;
				 	l_ofn.lpstrInitialDir   = szFileDir;		// module global
					l_ofn.lpstrTitle        = "Load File Image";
					l_ofn.Flags             = OFN_HIDEREADONLY | OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT;
					l_ofn.nFileOffset       = 0;
					l_ofn.nFileExtension    = 0;
					l_ofn.lpstrDefExt       = "hex";
					l_ofn.lCustData         = 0L;
					l_ofn.lpfnHook          = NULL;
					l_ofn.lpTemplateName    = NULL;
					l_ofn.lpstrFile         = winstate.szFileName; // full path
					l_ofn.lpstrFileTitle    = winstate.szTitleName;// short name

					// GOFN returns zero on cancel. nFilterIndex is the 1-based
					// index number of the selected file filter. If nFilterIndex
					// is 0 and lpstrCustomFilter is NULL, the system sets the
					// filter index to the first filter.
					if (!GetOpenFileName(&l_ofn)) return TRUE;

					// put filename in edit control
					SetDlgItemText(hDlg, IDC_EDIT_FNMA, winstate.szTitleName);

					if (l_ofn.nFilterIndex == 1){	//hex
						EnableWindow(GetDlgItem(hDlg, IDC_EDIT_STARTA), FALSE);
						SetDlgItemText(hDlg, IDC_EDIT_TYPEA, "Intel Hex");
						SetFocus(GetDlgItem (hDlg,IDOKA));
					}
					else {
						EnableWindow(GetDlgItem(hDlg, IDC_EDIT_STARTA), TRUE);
						SetDlgItemText(hDlg, IDC_EDIT_TYPEA, "Binary");
						SetFocus(GetDlgItem (hDlg,IDC_EDIT_STARTA));
					}
					return TRUE;

				case IDOK:

					// if filename is blank, throw error
					if (winstate.szTitleName[0] == 0){
						HOST_ThrowErrMsg("No filename specified!");
						return TRUE;
					}

					// We have a filename, so do final checks and processing
 					if (l_ofn.nFilterIndex == 1){
 						// only need filename for Hex format; cannot load HEX ROMs.
						fRet = ReadHexFile(hDlg, winstate.szFileName, winstate.szTitleName);

 					}
 					else {
						// make sure we have a valid starting address
 						GetDlgItemText(hDlg, IDC_EDIT_STARTA, szHexBufr, 5);
 						if (szHexBufr[0]==0){
							HOST_ThrowErrMsg("No starting address specified!");
							return TRUE;
 						}

						start_addr = (hextoi(szHexBufr) & 0xffff);
						image_len = TapeFileLength(winstate.szFileName);
						iRangeEnd = start_addr + image_len - 1; // image_len is 1-based

						// See which range the starting address is, flag type and
						// do some checks:
 						if ((start_addr >= romstart) && (start_addr <= romend)){
							// must be a ROM
							iImageType = 1;
							if ((image_len > (int)(romend - romstart)) || (iRangeEnd > (int)romend)){
								HOST_ThrowErrMsg("There is a problem with the ROM address range. Please reconfigure.");
								return TRUE;
							}
						}
						else if ((start_addr >= ramstart) && (start_addr <= ramend)){
							iImageType = 0;	//RAM
							if ((image_len > (ramend - ramstart)) || (iRangeEnd > (int)ramend)){
								HOST_ThrowErrMsg("There is a problem with the RAM address range. Please reconfigure.");
								return TRUE;
							}
						}
						else {
							HOST_ThrowErrMsg("Attempting to load a file into an undefined memory range! Please reconfigure.");
							return TRUE;
						}

						// Passed the gauntlet of tests, so load file
						fRet = ReadBinaryFile(hDlg, winstate.szFileName, winstate.szTitleName, start_addr);
 					}

					if (!fRet){	// FALSE = error - don't close dialog on error.
						HOST_ThrowErrMsg("An error occurred while reading file image!");
						return TRUE;
					}

					// Success -- we're done.
					// Fall through to CANCEL

				case IDCANCEL:
					winstate.szFileName[0] = 0;
					winstate.szTitleName[0] = 0;
					szFileDir[0] = 0;
					EndDialog(hDlg, wParam);
					return TRUE;

		    }	// WM_COMMAND switch
	}	// message case end; don't call DefDlgProc (recursion)
    return (LRESULT) FALSE;
}

/***************************************************************************\
*
*   FUNCTION:	lpAskSaveAddr (HWND, UINT, WPARAM, LPARAM)
*
*   PURPOSE:	WndProc for "save memory" dialog box.
*
*   MESSAGES:
*
*		WM_INITDIALOG - initialize dialog box
*		WM_COMMAND    - Input received
*
*   COMMENTS:
*
*		Application modal.
*
\***************************************************************************/
BOOL CALLBACK lpAskSaveAddr(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	BOOL fRet = 0;
	char szHexBufr[10];


	switch (iMsg) {
		case WM_INITDIALOG:
			// set initial radio buttons
			//				  hwnd  first_item      last_item        ID_to_select
			CheckRadioButton(hDlg, IDC_RADIO_END, IDC_RADIO_SIZE, end_addr_typ);

			strcpy(szFileDir, winstate.szFilesDir);
			SetFocus(GetDlgItem(hDlg, IDC_BROWSE));
			return FALSE;				// don't let Windows set default focus

		case WM_COMMAND:
			switch (LOWORD(wParam)){
				case IDC_BROWSE:

					// Get the filename
					s_ofn.lStructSize       = sizeof (OPENFILENAME);
					s_ofn.hwndOwner         = hDlg;
					s_ofn.hInstance         = (HINSTANCE)NULL;
					s_ofn.lpstrFilter       = szFilter;
					s_ofn.lpstrCustomFilter = NULL;
					s_ofn.nMaxCustFilter    = 0;
					s_ofn.nFilterIndex      = 0;
					s_ofn.lpstrFile         = winstate.szFileName;
					s_ofn.nMaxFile          = _MAX_PATH;
					s_ofn.lpstrFileTitle    = winstate.szTitleName;
					s_ofn.nMaxFileTitle     = _MAX_FNAME + _MAX_EXT;
				 	s_ofn.lpstrInitialDir   = szFileDir;		// module global
					s_ofn.lpstrTitle        = "Save File Image";
					s_ofn.Flags             = OFN_HIDEREADONLY | OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT;
					s_ofn.nFileOffset       = 0;
					s_ofn.nFileExtension    = 0;
					s_ofn.lpstrDefExt       = NULL;
					s_ofn.lCustData         = 0L;
					s_ofn.lpfnHook          = NULL;
					s_ofn.lpTemplateName    = NULL;
					s_ofn.lpstrFile         = winstate.szFileName; // full path
					s_ofn.lpstrFileTitle    = winstate.szTitleName;// short name

					// GSFN returns zero on canceling save
					if (!GetSaveFileName(&s_ofn)) return TRUE;

					// put filename in edit control
					SetDlgItemText(hDlg, IDC_EDIT_FNM, winstate.szTitleName);
					if (s_ofn.nFilterIndex == 1)	//hex
						SetDlgItemText(hDlg, IDC_EDIT_TYPE, "Intel Hex");
					else
						SetDlgItemText(hDlg, IDC_EDIT_TYPE, "Binary");
					return TRUE;

				case IDC_RADIO_END:
				case IDC_RADIO_SIZE:
					end_addr_typ = LOWORD(wParam);	// copy button ID
					CheckRadioButton(hDlg, IDC_RADIO_END, IDC_RADIO_SIZE, end_addr_typ);

					if (end_addr_typ == IDC_RADIO_END){
						EnableWindow(GetDlgItem(hDlg, IDC_EDIT_END), TRUE);
						EnableWindow(GetDlgItem(hDlg, IDC_EDIT_SIZE), FALSE);
					} else {
						EnableWindow(GetDlgItem(hDlg, IDC_EDIT_END), FALSE);
						EnableWindow(GetDlgItem(hDlg, IDC_EDIT_SIZE), TRUE);
					}
					return TRUE;

				case IDOK:
					// if filename is blank, throw error
					if (winstate.szTitleName[0] == 0){
						HOST_ThrowErrMsg("No filename specified!");
						return TRUE;
					}

					// Get starting address first...
					// value is in hex; GDIT returns number of TCHARS read
					//if (IDC_EDIT_START) is blank, throw error
					GetDlgItemText(hDlg, IDC_EDIT_START, szHexBufr, 5);
					if (szHexBufr[0]==0){
						HOST_ThrowErrMsg("No starting address specified!");
						return TRUE;
					}
					start_addr = (hextoi(szHexBufr) & 0xffff);

					// get end address *OR* length based on setting of radio buttons
					if (end_addr_typ == IDC_RADIO_END){
						GetDlgItemText(hDlg, IDC_EDIT_END, szHexBufr, 5);
						if (szHexBufr[0]==0){
							HOST_ThrowErrMsg("No ending address specified!");
							return TRUE;
						}
						end_addr = (hextoi(szHexBufr) & 0xffff);
					} else {
						GetDlgItemText(hDlg, IDC_EDIT_SIZE, szHexBufr, 5);
						if (szHexBufr[0]==0){
							HOST_ThrowErrMsg("No region length specified!");
							return TRUE;
						}
						end_addr = (start_addr + ((hextoi(szHexBufr) - 1) & 0xffff)) & 0xffff;
					}

					if (end_addr < start_addr) {
						HOST_ThrowErrMsg("Invalid region length specified!");
						return TRUE;
					}

					// call appropriate type handler.
					if (s_ofn.nFilterIndex == 1)
						fRet = WriteHexFile(hDlg, winstate.szFileName, winstate.szTitleName, start_addr, end_addr);
					else
						fRet = WriteBinaryFile(hDlg, winstate.szFileName, winstate.szTitleName, start_addr, end_addr);

					if (!fRet){	// FALSE = error - don't close dialog on error.
						HOST_ThrowErrMsg("An error occurred while writing file image!");
						return TRUE;
					}

					// Success -- we're done.
					// Fall through to CANCEL

				case IDCANCEL:
					winstate.szFileName[0] = 0;
					winstate.szTitleName[0] = 0;
					szFileDir[0] = 0;
					EndDialog(hDlg, wParam);
					return TRUE;

		    }	// WM_COMMAND switch
	}	// message case end
    return (LRESULT)FALSE;
}
/* end of file: loader.c */
