/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/88disk.c 73    11/08/06 9:21p Racini $
 ************************************************************************/
/************************************************************************
  MITS Altair Emulator
  MITS 88-DSK device driver

  Copyright (c) 1997-2003 Charles E. Owen (core disk interface code)
  Copyright (c) 2000-2006 Richard A. Cini (adaptations for Altair32)

  From file <altair_dsk.c:> in SIMH (http://simh.trailing-edge.com/):
  <Start of Notice>
  "Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of Charles E. Owen shall not
   be used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Charles E. Owen."
  <End of Notice>

  The 88-DSK is a MITS-manufactured two-board floppy controller which 
  could control up to 16 Pertec FD-400 or Shugart SA-800 (8") hard-
  sectored floppy disk drives. The physical disk geometry is 77 tracks
  containing 32 sectors of 137-bytes each (for an unformatted capacity
  of 337,568 bytes). The interface had a drive-to-bus speed of 250kbps.
  MITS used track 70 as the directory track. Each directory sector's
  data begins with the 8th byte (bytes 1-7 are garbage). Allocation
  granularity is 8 (sectors allocated in groups of 8).
	
  In addition to the standard MITS format, CP/M was available and had
  its own geometry, a soft-sectored format of 77 tracks containing 32
  sectors of 128 bytes each (a raw CP/M disk capacity of 315,392 bytes).
  The Altair32 also supports a non-standard disk geometry of 254 tracks
  containing 32 sectors of 137-bytes (an unformatted capacity of
  1,113,536 bytes) for use with CP/M.

  On a real Altair, a CP/M program called MITSCNVT enabled a CP/M user
  to list the conents of and convert the files from the MITS format to
  the CP/M format.

  The controller is interfaced to the CPU by use of 3 consecutive I/O
  addreses 10Q-12Q (08h-0ah).

	Port	Function
	-----	--------
	08	W	Selects and enables Controller and Drive
	08	R	Indicates status of Drive and Controller
	09	W	Controls Disk Function
	09	R	Indicates current sector position of disk
	0a	W	Write data
	0a	R	Read data


Change Log:
  2000/11/09  RAC -- Began integration into emulator
  2000/11/22  RAC -- First clean compile. Much work to do.
  2000/12/06  RAC -- Declared "done" pending test with VDT code
  2000/12/11  RAC -- Added detachment confirmation dialog
  2001/01/10  RAC -- Added shutdown code
  2001/01/11  RAC -- Fixed GPF on disk detach - dialog box won't display
  2001/01/14  RAC -- Fixes completed-stupid pointer assignment error
  2001/02/20  RAC -- "Module global" change; return parens fix.
  2001/08/19  RAC -- RELEASE MARKER -- v2.0
  2001/11/23  RAC -- Reworked dialog code to for different disk mgt process
  2001/12/14  RAC -- RELEASE MARKER -- v2.1
  2002/01/27  RAC -- Minor changes to match simh code. If assigning an
  						image to a slot that already has a disk image,
  						added code to detach the first one before assigning
  						the second one. Other minor changes to attach.
  2002/01/31  RAC -- RELEASE MARKER -- v2.2
  2002/06/05  RAC -- Added code to data register handler to prevent NULL
  						pointer fault when reading register with no disk
  						image attached (and thus no file open). Added
  						code to FlushBuffer to handle exception when
  						writing to data register with no disk image
						attached. Bug reported and fixed by Mike Evenson.  						
  2002/07/07  RAC -- RELEASE MARKER -- v2.3
  2002/08/19  RAC -- Fixed regression errors in disk read routine that
  						caused image corruption after repeated usage. Bug
  						reported by Rodger Smedley.
  2002/08/24  RAC -- Added dirty buffer test to flushbuffer (Rodger)
  2002/08/23  RAC -- RELEASE MARKER -- v2.30.10
  2002/10/16  RAC -- Merged header file into this file or altair32.h
  					 Made minor changes to synchronize code with v.2.9
  						of simh (Peter Schorn's updates).
  2002/10/18  RAC -- Added "large disk" support (supports disks up to
  						1,113,536 (254 tracks))
  2002/11/15  RAC -- RELEASE MARKER -- v2.40.2100
  2002/11/21  RAC -- Added "create blank disk" function; fill with 0xe5
  2003/03/01  JJF -- Modifications to support new disk drive graphics
  						and attachment method.
  2003/04/26  RAC -- RELEASE MARKER -- v2.50.2045
  2004/01/21  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/12  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 <mmsystem.h>
#include <string.h>
#include <stdio.h>
#include "altair32.h"		// includes "resource.h"
#include "comcthlp.h"		// common control macro file


/**  Typedefs  ************************************************/
// Disk unit data structure
typedef struct d_drv {
	char	szFnm[_MAX_FNAME + _MAX_EXT];		// short filename of disk image
	FILE	*fileref;		// file pointer
	void	*filebuf;		// pointer to memory buffer 
//	int 	hwmark;			// high water mark (used for buffering)
//	int		timeout;		// time out
	int		flags;			// flags - set to default
	int		capac;			// capacity
	long	pos;			// file position
} disk_drv;

// Dialog window constants -
#define DRIVE_LEFT   80	// FJS 80 pixels = 40 HU @ 8pt?
#define DRIVE_TOP	 40	// FJS 40 pixels = 20 VU @ 8pt?
#define DISKBMP_W	640	// FJS 724
#define DISKBMP_H	208	// FJS 275

// Disk stuff
#define BOOTROM_SIZE	256
#define BOOTROM_START	0xff00
#define NUM_OF_DSK	  8	// must be power of 2
#define NUM_OF_DSK_MASK (NUM_OF_DSK - 1)
#define NO_DSK 		NUM_OF_DSK 	// FJS could be -1 instead ?

/*  Physical disk geometry
 *  Disk size must be defined based on "large" size for any of the
 *	large CP/M disks to work. LARGE definition doesn't seem to
 *  effect the operation of AltairDOS, which assumes a disk size
 *  of 77 tracks.
 */
#define DSK_SECTSIZE  137	// sector size (in bytes)
#define DSK_SECT       32	// number of sectors per track
#define DSK_TRACSIZE (DSK_SECTSIZE * DSK_SECT)	// track size (in bytes) 4384
#define DSK_TRK_SM     77	// number of tracks
#define DSK_TRK       254	// number of tracks for "large" disk
#define DSK_SIZE (DSK_TRK * DSK_TRACSIZE)	//337568 or 1113536

// Blank diskette sizes
#define DSK_SM    (DSK_TRK_SM * DSK_TRACSIZE)
#define DSK_LG	  (DSK_TRK * DSK_TRACSIZE)

// Drive status flags
#define DSK_ENABLED		0x01		// enabled?
#define DSK_ATTABLE		0x02		// attachable
#define DSK_ATT			0x04		// disk attached
#define DSK_RO			0x08		// read only
#define DSK_BUFABLE		0x10		// bufferable
#define DSK_MUSTBUF		0x20		// must buffer
#define DSK_BUF			0x40		// buffered

// Controller status flags bits 0x08-read. Ignores bits1,3,4
#define DSK_CTL_ENWD     0x01		// 0=write data enable
#define DSK_CTL_MOVABLE  0x02		// 0=movement allowed
#define DSK_CTL_LOADED   0x04		// 0=head is loaded
#define DSK_CTL_TRK0	 0x40		// 0=head at track 0
#define DSK_CTL_DATA	 0x80		// 0=data available

// Controller control register bits 0x08-write
#define DSK_CTL_DEVMASK  0x0f		// drive mask bitfield
#define DSK_CTL_MOVTRUE  0x1a		// head move true
#define DSK_CTL_SELECT   0x80		// disk "selected" bit (inverted 0=sel)

// Controller commands   0x09-read
#define DSK_CTL_SECTRUE  0x01		// 1=sector true (sector in position)
#define DSK_CTL_SECTMASK 0x3e		// sector mask bitfield
#define DSK_CTL_11UNUSED 0xc0

// Controller commands   0x09-write. EI, DI, and low current are ignored.
#define DSK_CMD_STEPIN	 0x01		// step head in
#define DSK_CMD_STEPOUT  0x02		// step head out
#define DSK_CMD_LOAD	 0x04		// load head
#define DSK_CMD_UNLOAD	 0x08		// unload head
#define DSK_CMD_EI		 0x10		// enable drive interrupt processing
#define DSK_CMD_DI		 0x20		// disable drive interrupt processing
#define DSK_CMD_LOWCUR	 0x40		// enable low-current writing
#define DSK_CMD_WRTSTRT  0x80		// write sequence start


/**  Module Globals - Public  *********************************/
int  iAttachCount = 0;			// count of files attached
HWND hwndDISK = NULL;			// disk window handle


/**  Module Globals - Private *********************************/
static BOOL fBuildDisk = FALSE ;
static char szBuffer[64 + _MAX_FNAME + _MAX_EXT];
static char szFileDir[_MAX_PATH + _MAX_FNAME + _MAX_EXT];
static char szNewFilePath[_MAX_PATH];
static char szNewFileName[_MAX_FNAME + _MAX_EXT];
static char buf2[80];		// for profile strings
static HBITMAP HBMPopen, HBMPclosed;
static HDC HDCopen, HDCclosed;
static int d = 0;	// scratch variable (persistant)
static int iEditID[] = {IDC_DISK1, IDC_DISK2, IDC_DISK3, IDC_DISK4,
						IDC_DISK5, IDC_DISK6, IDC_DISK7, IDC_DISK8};
static int iDskSize = IDC_DSKSIZE77;	// default size radio button

// Drive data - drives 0 - 7; 8 is the null drive (0-based count)
static char	cSectorBuffer[DSK_SECTSIZE+1];	// sector data buffer w/ pad
static disk_drv DiskDrives[NUM_OF_DSK];
static disk_drv *drv_ptr;
static int iCurrentDisk = NO_DSK;	// Currently selected drive
static int iCurrentTrack[NUM_OF_DSK]  = {0, 0, 0, 0, 0, 0, 0, 0};	// track
static int iCurrentSector[NUM_OF_DSK] = {0, 0, 0, 0, 0, 0, 0, 0};	// sector
static int iCurrentByte[NUM_OF_DSK]   = {0, 0, 0, 0, 0, 0, 0, 0};	// buffer index
static int iCurrentFlags[NUM_OF_DSK]  = {0, 0, 0, 0, 0, 0, 0, 0};	// flags
static int iBufferDirty = 0 /* NO */;		// 1 when sector buffer has unwritten data
static int32 bootrom[BOOTROM_SIZE] = {
    0041, 0000, 0114, 0021, 0030, 0377, 0016, 0346,	// f000
    0032, 0167, 0023, 0043, 0015, 0302, 0010, 0377,
    0303, 0000, 0114, 0000, 0000, 0000, 0000, 0000,	// f010
    0363, 0061, 0142, 0115, 0257, 0323, 0010, 0076,
    0004, 0323, 0011, 0303, 0031, 0114, 0333, 0010,	// f020
    0346, 0002, 0302, 0016, 0114, 0076, 0002, 0323,
    0011, 0333, 0010, 0346, 0100, 0302, 0016, 0114,	// f030
    0021, 0000, 0000, 0006, 0000, 0333, 0010, 0346,
    0004, 0302, 0045, 0114, 0076, 0020, 0365, 0325,	// f040
    0305, 0325, 0021, 0206, 0200, 0041, 0324, 0114,
    0333, 0011, 0037, 0332, 0070, 0114, 0346, 0037,	// f050
    0270, 0302, 0070, 0114, 0333, 0010, 0267, 0372,
    0104, 0114, 0333, 0012, 0167, 0043, 0035, 0312,	// f060
    0132, 0114, 0035, 0333, 0012, 0167, 0043, 0302,
    0104, 0114, 0341, 0021, 0327, 0114, 0001, 0200,	// f070
    0000, 0032, 0167, 0276, 0302, 0301, 0114, 0200,
    0107, 0023, 0043, 0015, 0302, 0141, 0114, 0032,	// f080
    0376, 0377, 0302, 0170, 0114, 0023, 0032, 0270,
    0301, 0353, 0302, 0265, 0114, 0361, 0361, 0052,	// f090
    0325, 0114, 0325, 0021, 0000, 0377, 0315, 0316,
    0114, 0321, 0332, 0276, 0114, 0315, 0316, 0114,	// f0a0
    0322, 0256, 0114, 0004, 0004, 0170, 0376, 0040,
    0332, 0054, 0114, 0006, 0001, 0312, 0054, 0114,	// f0b0
    0333, 0010, 0346, 0002, 0302, 0240, 0114, 0076,
    0001, 0323, 0011, 0303, 0043, 0114, 0076, 0200,	// f0c0
    0323, 0010, 0303, 0000, 0000, 0321, 0361, 0075,
    0302, 0056, 0114, 0076, 0103, 0001, 0076, 0117,	// f0d0
    0001, 0076, 0115, 0107, 0076, 0200, 0323, 0010,
    0170, 0323, 0001, 0303, 0311, 0114, 0172, 0274,	// f0e0
    0300, 0173, 0275, 0311, 0204, 0000, 0114, 0044,
    0026, 0126, 0026, 0000, 0000, 0000, 0000, 0000,	// f0f0
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

						
/**  Forward prototypes  **************************************/
static BOOL CreateBlankDisk(HWND, uint32, char *);
static void FlushBuffer(void);	// flush "dirty" buffer to file
static BOOL AttachFile(HWND, int);
static void DetachFile(HWND, int);
static void Draw_Dialog(/* HINSTANCE, */ HWND);
static void Update_Leds(HINSTANCE, HWND, int, int, int, int, int);
static void DISK_InstBootrom(void);


/**  Windows callback procedures  *****************************/
LRESULT CALLBACK WndProcDisk(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProcBuildDisk(HWND, UINT, WPARAM, LPARAM);


/**************************************************************\
*   Public Routines  ******************************************|
\**************************************************************/
/*--  InitDiskController  -------------------------------------
	Initializes disk data structures.

	Params:		None

	Uses:		disk_drv

	Returns:	Nothing
	
	Comments:	Called at emulator startup
-------------------------------------------------------------*/
void DISK_InitController(void)
{
	register int i;
	
	// #0-#7 are active, #8 is the null drive
	for (i = 0; i < NUM_OF_DSK; i++) {
		DiskDrives[i].szFnm[0]	= 0;
		DiskDrives[i].fileref	= NULL;	// NULLCHG
		DiskDrives[i].filebuf	= NULL;
//		DiskDrives[i].hwmark	= 0;	// used for buffering
//		DiskDrives[i].timeout	= 0;
		DiskDrives[i].flags		= DSK_ENABLED | DSK_ATTABLE;
		DiskDrives[i].capac		= DSK_SIZE;
		DiskDrives[i].pos		= 0l;
	}
	iAttachCount = 0;
	iCurrentDisk = NO_DSK;			// set to "null" drive
	DISK_InstBootrom();
	return;
}


/*--  OpenDiskWin  ---------------------------------------------
	Called by main UI code to open up a	modeless dialog for
	the diskette device.
	
-------------------------------------------------------------*/
void OpenDiskWin( void )
{

	// CreateDialog returns NULL on failure
	if (!IsWindow(hwndDISK)) {
		if ( (hwndDISK = CreateDialog(winstate.hInst,
								"Disk_Drv",
								winstate.hWnd,
								(DLGPROC) WndProcDisk) ) != NULL)
			ShowWindow(hwndDISK, SW_SHOW);
	}
}


/*--  ShutdownDisks  ------------------------------------------
	Closes all open file handles.

	Params:		None

	Uses:		disk_drv

	Returns:	Nothing
	
	Comments:	Called at emulator shutdown to ensure that all
				open files are flushed and closed.
-------------------------------------------------------------*/
void DISK_Shutdown(void)
{
	register int i;	

	for (i = 0; i < NUM_OF_DSK; i++) {
		if (DiskDrives[i].fileref == NULL)	// If slot is unused, skip it.
			continue;

		// File is assigned to emulated drive.  If it's the current
		// drive, flush it before closing (if needed). 
		if (iCurrentDisk == i) {
			drv_ptr = &DiskDrives[iCurrentDisk];
			FlushBuffer();
		}

		// TBD record fullname in .ini file if user desires.

		// Slot is assigned and data flushed if needed -- close the file.
		fclose(DiskDrives[i].fileref);		

		// clear drive data from drive data structure
		DiskDrives[i].szFnm[0]	= 0 /* EOS */;
		DiskDrives[i].fileref	= NULL;	//NULLCHG
		DiskDrives[i].filebuf	= NULL;		// pointer to buffer if drive is bufferable
//		DiskDrives[i].hwmark	= 0;
//		DiskDrives[i].timeout	= 0;
		DiskDrives[i].flags		= DSK_ENABLED | DSK_ATTABLE;
		DiskDrives[i].capac		= DSK_SIZE;
		DiskDrives[i].pos		= 0l;
	}

	iAttachCount = 0;
	iCurrentDisk = NO_DSK;
	Status_SetText(hwndStatusBar, STATBAR_DISK, 0, "0 disks loaded");

	// If window was open previously, destroy it.
	if ( hwndDISK != NULL ) {		// NULLCHG
		// TBD first, record window settings.
		DestroyWindow(hwndDISK);
		hwndDISK = NULL;
	}
}


/*--  CreateBlankDisk  ----------------------------------------
	Creates blank disk

	Params:		HWND, disk_bytes, char* path

	Uses:		control ID array

	Returns:	BOOL
	
	Comments:	disk_bytes can really be any size up to size of
				UINT32 but is either 337568 (77trk) or 1113536
				(254trk). This will have to change if we
				decide to support other disk formats. Routine
				will create a file of dsk_bytes size regardless
				if it's a validly-sized Altair disk file.
-------------------------------------------------------------*/
static BOOL CreateBlankDisk(HWND hwnd, uint32 dsk_bytes, char *fpath)
{

	FILE   *pFile_c = NULL;
	uint32 i = 0;

	ASSERT(fpath != NULL);

	if((pFile_c = fopen(fpath, "wb")) == NULL) {
		//HOST_ThrowErrMsg("CreateDisk: Unable to create disk image file!");
		return FALSE;
	}

	for (i = 0; i < dsk_bytes; i++){
		fputc((int) 0xe5, pFile_c);
	}

	fflush(pFile_c);
	fclose(pFile_c);
	return TRUE;
}


/**************************************************************\
*   I/O Instruction Handlers  *********************************|
***************************************************************|
*
*	I/O instruction handlers are called from the CPU module
*  	when an IN or OUT instruction is issued.
*
*  	Each function is passed an 'io' flag, where 0 means a read
*  	from the port, and 1 means a write to the port.  On input,
*	the actual input is passed as the return value.  On output,
*	'data' is written to the device.
***************************************************************|
*      THESE ROUTINES WORK -- MODIFY AT YOUR OWN RISK!!!       |
\**************************************************************/
/*--  dsk08h  -------------------------------------------------
	Emulated disk controller status/select register handler

	Params:		io_dir, data

	Uses:		global drive arrays

	Returns:	int status
	
	Comments: 	The status flags read by port 10Q/8h IN instruction are
				hardware INVERTED, so 0 is true and 1 is false. To
				compensate, we keep our own status flags as 0=false,
				1=true and when read returns the complement of the status
				flags. 
	
		Drive Select Out (Port 10Q OUT):
	
		+---+---+---+---+---+---+---+---+
		| C | X | X | X |   Device      |
		+---+---+---+---+---+---+---+---+
	
		C = If this bit is 1, the disk controller selected by 'device'
		 	is cleared.  If the bit is zero, 'device' is selected as the
		    device being controlled by subsequent I/O operations.
		X = not used
		Device = value zero thru 15, selects drive to be controlled.
	
		Drive Status In (Port 10Q IN):
	
		+---+---+---+---+---+---+---+---+
		| R | Z | I | X | X | H | M | W |
		+---+---+---+---+---+---+---+---+
	
		W - When 0, write circuit ready to write another byte.
		M - When 0, head movement is allowed
		H - When 0, indicates head is loaded for read/write
		X - not used (will be 0)
		I - When 0, indicates interrupts enabled (not used)
		Z - When 0, indicates head is on track 0
		R - When 0, indicates that read circuit has new byte to read
-------------------------------------------------------------*/
int dsk08h(int io, int data)
{
	int cur_flag = 0;
	// int temp_drive=0 /* , temp_flagc=0 */;
	

	// IN mode: 0xff for "IN08 on unattached disk", or flags
	if (io == 0) {
		if ((unsigned)iCurrentDisk >= NUM_OF_DSK /* NO_DSK... */ ) {
			return 0xff;	// no drive selected - can't do anything
		}

		return ((~iCurrentFlags[iCurrentDisk]) & 0xff);
	} // else write ctl register
	
	Update_Leds(winstate.hInst,hwndDISK,(data & 15),2,2,2,(data & 128) != 0);

	// OUT: Controller set/reset/enable/disable.  Implies iCurrentDisk < NUM_OF_DSK
    if (iBufferDirty /* == 1 */ ) FlushBuffer();

	if ((data & 15) > NUM_OF_DSK_MASK) {	// emulate no physical drive
		iCurrentDisk = NO_DSK;				// no drive means no disk too!
	}
	else {
		iCurrentDisk = data & NUM_OF_DSK_MASK;		// 0<=iCurrentDisk<NUM_OF_DSK
		cur_flag = DiskDrives[iCurrentDisk].flags;	// get flags from drive
		if ((cur_flag & DSK_ATT) == 0) {	// if selected drive is not attached
			iCurrentDisk = NO_DSK;			// indicate that there is no current disk
		}
		else {								// reset internal counters
			iCurrentSector[iCurrentDisk] = 0xff;
			iCurrentByte[iCurrentDisk] = 0xff;
			iCurrentFlags[iCurrentDisk] = (data & DSK_CTL_SELECT)
					? 0	// disable drive
					: ( (iCurrentTrack[iCurrentDisk] == 0)
						? (DSK_CTL_MOVTRUE|DSK_CTL_TRK0)  // movetrue+trk0 if there
						: DSK_CTL_MOVTRUE );	// movetrue otherwise
	    }
	}
	return 0;	// ignored
}


/*--  dsk09h  -------------------------------------------------
	Emulated disk drive status/select register handler

	Params:		io_dir, data

	Uses:		global drive arrays

	Returns:	int status
	
	Comments:
		Drive Control (Port 11Q OUT):
		+---+---+---+---+---+---+---+---+
		| W | C | D | E | U | H | O | I |
		+---+---+---+---+---+---+---+---+
	
		I - When 1, steps head IN one track
		O - When 1, steps head OUT out track
		H - When 1, loads head to drive surface
		U - When 1, unloads head
		E - Enables interrupts (ignored)
		D - Disables interrupts (ignored)
		C - When 1 lowers head current (ignored)
		W - When 1, starts Write Enable sequence:	W bit on port 10Q
			(see above) will go 1 and data can be written to port 12Q
			until 137 bytes have been given the controller at that
			port.  The W bit will go off then, and the sector data
			will be written to file.  Before you do this, you must have
			stepped the track to the desired number, and waited until
			the right sector number is presented on port 11Q IN, then
			set this bit.
	
		Sector Position (Port 11Q IN):
	
		As the sectors pass by the read head, they are counted and the
		number of the current one is available in this register.
	
		+---+---+---+---+---+---+---+---+
		| X | X |  Sector Number    | T |
		+---+---+---+---+---+---+---+---+
	
		X = Not used
		Sector number = binary of the sector number currently under the head, 0-31.
		T = Sector True, is a 1 when the sector is positioned to read or write.

-------------------------------------------------------------*/
int dsk09h(int io, int data)
{

	if ((unsigned)iCurrentDisk >= NUM_OF_DSK /* NO_DSK */)
		return 0;		// no drive selected - can't do anything

	// assert(buffer is not dirty)
    if (iBufferDirty /* == 1 */ ) FlushBuffer(); // FJS just in case

	if (io == 0) {	// Read sector position
		// FJS flush before status ??
    	// if (iBufferDirty /* == 1 */) FlushBuffer();

		// Is the head loaded?
    	if (iCurrentFlags[iCurrentDisk] & DSK_CTL_LOADED) {		// 04
		register int stat;

			// TBD allow sector advance throttle
			iCurrentSector[iCurrentDisk]++; // advance on every inquery!
            if (iCurrentSector[iCurrentDisk] >= DSK_SECT)	//  was > 31
            	iCurrentSector[iCurrentDisk] = 0;		// wrap to zero

            iCurrentByte[iCurrentDisk] = 0xff;	// assume read series follows (-1)

			stat = iCurrentSector[iCurrentDisk] << 1;	// build status byte
            stat &= DSK_CTL_SECTMASK;	// return 'sector true' bit = 0 (true) (3e)
            stat |= DSK_CTL_11UNUSED;	// set on 'unused' bits (c0)

			// head load sound only if dialog is open and sounds enabled
			if ( hwndDISK != NULL && Disksnd ) // FJS && was if NULLCHG
				PlaySound("head_load", winstate.hInst, SND_RESOURCE | SND_ASYNC | SND_NOWAIT);

            return (stat);
        } // else

        return 0;		// head not loaded
    } // else write to ctl reg. ->

    /* --- Drive functions --- */
	// Step head in
    if (data & DSK_CMD_STEPIN) {			//01
		// FJS step before flush ??
    	iCurrentTrack[iCurrentDisk]++;
		// TBD use current track limit (76 vs 253?)
        if (iCurrentTrack[iCurrentDisk] > (DSK_TRK - 1))	// 76
        	iCurrentTrack[iCurrentDisk] = (DSK_TRK - 1);	// 76

		// if (iBufferDirty == 1) FlushBuffer();

        iCurrentSector[iCurrentDisk] = 0xff;
        iCurrentByte[iCurrentDisk] = 0xff;

		// Only if the Disk dislog is open
		if (hwndDISK != NULL && Disksnd)	 // NULLCHG
			PlaySound("head_step", winstate.hInst, SND_RESOURCE | SND_ASYNC | SND_NOWAIT);
    }

	// Step head out
    if (data & DSK_CMD_STEPOUT) {		// 02
		// FJS step before flush ??
    	iCurrentTrack[iCurrentDisk]--;
        if (iCurrentTrack[iCurrentDisk] <= 0){	// FJS '<=' was '<'
        	iCurrentTrack[iCurrentDisk] = 0;
    		iCurrentFlags[iCurrentDisk] |= DSK_CTL_TRK0;	// track 0 if there 40
        }

		// if (iBufferDirty /* == 1 */) FlushBuffer();

        iCurrentSector[iCurrentDisk] = 0xff;
        iCurrentByte[iCurrentDisk] = 0xff;
        
		if (hwndDISK != NULL && Disksnd)	// NULLCHG
			PlaySound("head_step", winstate.hInst, SND_RESOURCE | SND_ASYNC | SND_NOWAIT);
	}

	// Head load (no simulated delay)
    if (data & DSK_CMD_LOAD){			// 04
    	iCurrentFlags[iCurrentDisk] |= DSK_CTL_LOADED;	// turn on head loaded bit 04
        iCurrentFlags[iCurrentDisk] |= DSK_CTL_DATA;	// turn on read data available 80
		Update_Leds(winstate.hInst,hwndDISK,iCurrentDisk,2,2,1,0);
	}

	// Head unload
    if (data & DSK_CMD_UNLOAD){			// 08
    	iCurrentFlags[iCurrentDisk] &= ~DSK_CTL_LOADED;	// turn off head loaded (fb)
        iCurrentFlags[iCurrentDisk] &= ~DSK_CTL_DATA;	// turn off read data avail (7f)
        iCurrentSector[iCurrentDisk] = 0xff;
        iCurrentByte[iCurrentDisk] = 0xff;
		Update_Leds(winstate.hInst,hwndDISK,iCurrentDisk,2,2,0,0); // Unload 

		if (hwndDISK != NULL && Disksnd)	// NULLCHG
			PlaySound("head_uload", winstate.hInst,	SND_RESOURCE | SND_ASYNC | SND_NOWAIT);
    }

    // Interrupts and head current bits are ignored

	// Write sequence start
    if (data & DSK_CMD_WRTSTRT) {		// 80
        iCurrentByte[iCurrentDisk] = 0; // set-up for following write series
        iCurrentFlags[iCurrentDisk] |= DSK_CTL_ENWD;	// new write data on (01)
    }
	return 0;
}


/*--  dsk0ah  -------------------------------------------------
	Emulated disk controller data register.

	Params:		io_dir, data

	Uses:		Disk drive data structures

	Returns:	on read, data received; nothing on write
	
	Comments:	
-------------------------------------------------------------*/
int dsk0ah(int io, int data)
{

	register /* FJS */ int  i /* = 0 */;
    long pos /* = 0l */;


	if ((unsigned)iCurrentDisk >= NUM_OF_DSK)
		return 0;		// no drive selected - can't do anything

    // If current fileref is invalid, just swallow call
    if (DiskDrives[iCurrentDisk].fileref == NULL) return 0;

	/*	Read from disk  */
	if (io == 0) { // read
		// TBD check that write is not in progress? (DSK_CTL_ENWD is cleared)

		// If we're in the same sector, get the data from the buffer...
		if ((i = iCurrentByte[iCurrentDisk]) < DSK_SECTSIZE) { // FJS was <=
			iCurrentByte[iCurrentDisk]++; // set for next time,
			return (cSectorBuffer[i] & 0xff); // but return current byte
		}

		// ...else, (re)read the designated sector from the disk image file.
		// First, calculate absolute sector within the "disk"
	    pos = DSK_TRACSIZE * iCurrentTrack[iCurrentDisk]; // needs 32 bit ints!
	    pos += DSK_SECTSIZE * iCurrentSector[iCurrentDisk];

		drv_ptr = &DiskDrives[iCurrentDisk];	// make pointer
		fseek(drv_ptr -> fileref, pos, SEEK_SET);		// seek to spot
	    fread(cSectorBuffer, DSK_SECTSIZE, 1, drv_ptr -> fileref);	// fetch data

	    // Seems that this is the only way that the code works:
    	iCurrentByte[iCurrentDisk] = 1; // set for next time,
        return (cSectorBuffer[0] & 0xff); // but return initial byte
	}	// else write...
	
	/*	Write to disk  */
	// If we're at the last slot in the buffer, flush the buffer to disk.
	// Otherwise, write the data to the buffer.

	// TBD check that write is in progress (DSK_CTL_ENWD is set)

	if (iCurrentByte[iCurrentDisk] >= DSK_SECTSIZE)
		FlushBuffer();
	else {
		iBufferDirty = 1;	// guarantees data flushed on next call
		cSectorBuffer[iCurrentByte[iCurrentDisk]++] = data & 0xff;
	}
	return 0;		// ignored since OUT
}


/**************************************************************\
*   Private Routines   ****************************************|
\**************************************************************/
/*--  FlushBuffer  --------------------------------------------
	Flushes track buffer to associated file.

	Params:		None

	Uses:		Disk drive data structures

	Returns:	Nothing
	
	Comments:
-------------------------------------------------------------*/
static void FlushBuffer(void)
{

	register /* FJS */ int  i /* = 0 */;
    long pos /* = 0l */;
    

	// Is there unwritten data in buffer? 1=flush 0=noflush
    // If the buffer is not dirty, do nothing.
	if (!iBufferDirty) return; // FJS

	/*  Check for NULL file handle before flushing buffer to disk to
     *  avoid throwing exception when running random code.  If handle
	 *  is NULL, return without taking any action.
	 */
    if (DiskDrives[iCurrentDisk].fileref == NULL) return;

    i = iCurrentByte[iCurrentDisk];
	// flush of partially filled buffer means a mistake, but just pad & write -
	// nul-fill rest of sector (if any).
    while (i < DSK_SECTSIZE) // FJS was <=
    	cSectorBuffer[i++] = 0; // could be 0xE5 or 0x1A?

	// calculate position within disk image (i.e. absolute sector)
    pos =  DSK_TRACSIZE * iCurrentTrack[iCurrentDisk]; // needs 32 bit ints!
	pos += DSK_SECTSIZE * iCurrentSector[iCurrentDisk];
	
	// seek to position
    fseek(DiskDrives[iCurrentDisk].fileref, pos, SEEK_SET);

	// write the data
	fwrite(cSectorBuffer, DSK_SECTSIZE, 1, DiskDrives[iCurrentDisk].fileref);

	// update drive flags and clear dirty pointer
    iCurrentFlags[iCurrentDisk] &= 0xfe;	// turn off ENableWriteData bit
    iCurrentByte[iCurrentDisk] = 0xff;		// Invalid buffer flag is 0xff
    iBufferDirty = 0;
    return;

}


/*--  AttachFile  ---------------------------------------------
	Insert virtual floppy disk into emulated disk drive.

	Params:		hWnd, drive index

	Uses:		disk_drv

	Returns:	Success/failure
	
	Comments:	Called by dialog routine to actually bind file
-------------------------------------------------------------*/
static BOOL AttachFile(HWND hwnd, int iBtnIndex)
{

	char	szFileName[_MAX_PATH];	// fully-qualified filename buffer
	char	szFileTitle[_MAX_FNAME + _MAX_EXT];	// filename only
	static  char szFilter[] = "Disk Image Files (*.DSK;*.IMG)\0*.dsk;*.img\0" \
                              "All Files (*.*)\0*.*\0\0";
	DWORD   fstat = 0xFFFFFFFF;		// unsigned long
	FILE	*pFile = NULL;
	int		iIndex = 0;
	OPENFILENAME ofn;
	int		fReadonly;


	szFileName[0]	 = 0  /* EOS */;
	szFileTitle[0]	 = 0  /* EOS */;
	szNewFileName[0] = 0  /* EOS */;
	szNewFilePath[0] = 0  /* EOS */;
	strcpy (szFileDir, winstate.szFilesDir);

	// setup OFN structure
	ofn.lStructSize       = sizeof (OPENFILENAME);
	ofn.hwndOwner         = hwnd;
	ofn.hInstance         = (HINSTANCE)NULL;	// NULLCHG
	ofn.lpstrFilter       = szFilter;		// filter string
	ofn.lpstrCustomFilter = NULL;			// used to preserve user-defined filters
	ofn.nMaxCustFilter    = 0;				// size of custom filter buffer
	ofn.nFilterIndex      = 0;				// current filter index
	ofn.lpstrFile         = szFileName;		// contains full path and filename on return
	ofn.nMaxFile          = _MAX_PATH;		// sizeof lpstrFile
	ofn.lpstrFileTitle    = szFileTitle;	// filename and extension only
	ofn.nMaxFileTitle     = _MAX_FNAME + _MAX_EXT;	// sizeof lpstrFileTitle
 	ofn.lpstrInitialDir   = szFileDir;		// initial directory
	ofn.lpstrTitle        = "Open Disk Image";	// title bar string or NULL
	ofn.Flags             = OFN_HIDEREADONLY | OFN_CREATEPROMPT;
	ofn.nFileOffset       = 0;				// offset to filename in lpstrFile
	ofn.nFileExtension    = 0;				// offset in TCHARs to ext. in lpstrFile
	ofn.lpstrDefExt       = "dsk";			// default extension
	ofn.lCustData         = 0L;				// data passed to hook procedure
	ofn.lpfnHook          = NULL;			// pointer to hook procedure
	ofn.lpTemplateName    = NULL;			// dialog box template


	// GOFN returns zero on error
	if (!GetOpenFileName(&ofn)) return FALSE;

	fstat = GetFileAttributes(szFileName);
	if (fstat == 0xFFFFFFFF){
		// User typed new name into dialog, so try to create the disk
		strcpy(szNewFileName, szFileTitle);
		strcpy(szNewFilePath, szFileName);
		DialogBox(winstate.hInst, "Build Empty Disk", winstate.hWnd, (DLGPROC)WndProcBuildDisk);
		if (!fBuildDisk){
			HOST_ThrowErrMsg("New disk image not created.");
			return FALSE;
		}
	}

	/*  Before attaching image file to drive, make various checks...
	 *  If trying to attach an image to a slot that already has an image 
	 *  attached, flush and close the first file before attaching
	 *  the second file.
	 */
	if (DiskDrives[iBtnIndex].flags & DSK_ATT)
		DetachFile(hwnd, iBtnIndex); // TBD free fullname copy
	
	// Now that slot is free, check if file chosen is already assigned elsewhere.
	for (iIndex = 0; iIndex < NUM_OF_DSK; iIndex++) {	// FJS was "iIndex <= 7"
		if (DiskDrives[iIndex].flags & DSK_ATT) {		// slot used??
	
			// check if selected filename is attached to this slot 
			if (!_stricmp(((char *)DiskDrives[iIndex].szFnm), szFileTitle))	{
				// Image file is already assigned to a drive, so emit error
				HOST_ThrowErrMsg("AttachFile: Image file is already in use!");
				return FALSE;
			}
		}
	}		// end for()

	// For selected slot, make sure drive is enabled.
	if (!(DiskDrives[iBtnIndex].flags & DSK_ENABLED)){
		// Drive is not enabled, so emit error. Unlikely condition...
		HOST_ThrowErrMsg("AttachFile: Disk drive is not enabled!");
		return FALSE;
	}

	/*  Drive is available, enabled and file is not already assigned.
	 *  Open the file.
	 */
	// FJS allow read-only file as write-protected disk -
	fReadonly = 0;
	// if ((file_size > floppy_size) || ((pFile = fopen(szFileName, "rb")) == NULL)) {
 	if (	((pFile = fopen(szFileName, "rb+")) == NULL /* BAD_OPEN */)
			&& ((fReadonly = DSK_RO), ((pFile = fopen(szFileName, "rb")) == NULL /* BAD_OPEN */)) ){
		HOST_ThrowErrMsg("AttachFile: Unable to open disk image file!");
		return FALSE;
	}

	// We've successfully opened the image file, so let's assign the image 
	// file to the free disk drive
	strcpy(DiskDrives[iBtnIndex].szFnm, szFileTitle);	// copy string
	DiskDrives[iBtnIndex].fileref   = pFile;		// file pointer
	DiskDrives[iBtnIndex].filebuf   = NULL;			// pointer to buffer if drive is bufferable
//	DiskDrives[iBtnIndex].hwmark    = 0;			// high water mark for buffering
//	DiskDrives[iBtnIndex].timeout   = 0;			// time out
//	TBD allow read-only flag bit -
	DiskDrives[iBtnIndex].flags     |= DSK_ATT | fReadonly; // flags - mark as attached 3 => 7
	DiskDrives[iBtnIndex].capac     = DSK_SIZE;	// capacity (default)
	// DiskDrives[iBtnIndex].rd_only   = fReadonly;	// read-only status
	DiskDrives[iBtnIndex].pos       = 0l;			// file position

	iAttachCount++;
	wsprintf(szBuffer, "AttachFile: Image file %s attached successfully!",
		DiskDrives[iBtnIndex].szFnm);
	Status_SetText(hwndStatusBar, STATBAR_READY, 0, szBuffer);
	wsprintf(szBuffer, "%d disk loaded", iAttachCount);
	Status_SetText(hwndStatusBar, STATBAR_DISK, 0, szBuffer);
	SetDlgItemText(hwnd, iEditID[iBtnIndex], DiskDrives[iBtnIndex].szFnm);
	return TRUE;
}


/*--  DetachFile  ---------------------------------------------
	Detaches a disk image file from an emulated disk drive.

	Params:		hWnd, disk drive

	Uses:		disk_drv

	Returns:	Nothing
	
	Comments:
-------------------------------------------------------------*/
static void DetachFile(HWND hwnd, int iBtnIndex)
{

	// If the slot is empty, just return silently.
	if (DiskDrives[iBtnIndex].fileref == NULL) return;

	// Query user to confirm detachment
	wsprintf(szBuffer, "Do you really want to detach this image file from the emulator?");
	if (MessageBox(hwnd, szBuffer, winstate.szAppName,
			MB_YESNO | MB_SYSTEMMODAL |	MB_DEFBUTTON2 | MB_ICONWARNING)
			== IDNO)
		return;

	// If selected file is in the currently selected "drive" then flush the
	// buffer.  FlushBuffer() returns silently if the buffer is not dirty.
	if (iCurrentDisk == iBtnIndex){
		drv_ptr = &DiskDrives[iCurrentDisk];
		FlushBuffer();
		iCurrentDisk = NO_DSK;		// =8 closing file, so make no drives current
	}

	// Get file pointer and close the related file.  This drive wasn't the
	// current drive, so we shouldn't have to do anything.
	fclose(DiskDrives[iBtnIndex].fileref);

	// free disk drive slot and clear pointers
	DiskDrives[iBtnIndex].szFnm[0]	= 0;
	DiskDrives[iBtnIndex].fileref  = NULL;
	DiskDrives[iBtnIndex].filebuf  = NULL;
//	DiskDrives[iBtnIndex].hwmark   = 0;	// used for buffering
//	DiskDrives[iBtnIndex].timeout  = 0;
	DiskDrives[iBtnIndex].flags    = DSK_ENABLED | DSK_ATTABLE;
	DiskDrives[iBtnIndex].capac    = DSK_SIZE;
	DiskDrives[iBtnIndex].pos      = 0l;

	if (iAttachCount > 0) iAttachCount--; // FJS added "if" for safety
	SetDlgItemText(hwnd, iEditID[iBtnIndex], "");

	// Issue reports in main window status boxes-
	wsprintf(szBuffer, "DetachFile: Image file %s successfully detached.",
		DiskDrives[iBtnIndex].szFnm);
	Status_SetText(hwndStatusBar, STATBAR_READY, 0, szBuffer);
	wsprintf(szBuffer, "%d disk loaded", iAttachCount);
	Status_SetText(hwndStatusBar, STATBAR_DISK, 0, szBuffer);
	return;
}


/*--  DISK_InstBootrom  --------------------------------------
	Routine to copy bootrom code in array to memory.

	Params:		none

	Uses:		locals, memory access routines

	Returns:	Nothing
	
	Comments:
-------------------------------------------------------------*/
static void DISK_InstBootrom( void )
{

	char *f_bootrom = "88dskrom (internal)";
	word i;

	for (i = 0; i < BOOTROM_SIZE; i++){
		Mem[(i + BOOTROM_START)] = bootrom[i];	// stuff value right into memory
		PageAccessRights[i + BOOTROM_START] = 1;
	}
	strcpy(load_rom[0].szROMName, f_bootrom);
	load_rom[0].iROMStart = 0xff00;
	load_rom[0].iROMSize = 0x0100;		// end address is start+length-1
	load_rom[0].iUsed = 1;
	load_rom[0].Read_only = 1;

	//Status_SetText(hwndStatusBar, STATBAR_READY, 0, "Disk Bootrom Loaded");
	return;
}


/**************************************************************\
*   Windows Callback Routines  ********************************|
\**************************************************************/
/* Main disk window callback */
LRESULT CALLBACK WndProcDisk(HWND hDlg, UINT iMsg, WPARAM wParam,
					LPARAM lParam)
{

	BITMAP	bm;
	char *ProfBuf;
	char buf[10];
	int i = 0;
	int x_size = 0;
	int y_size = 0;
	HBITMAP HBMPtempa, HBMPtempb;
	HDC	HDCtempa, HDCtempb, HDCmain;
	RECT rc;
	register x,y;
	

    switch (iMsg) {
		case WM_LBUTTONDOWN: // 38,92
			x = LOWORD( lParam );
			y = HIWORD( lParam );

			// REMOVE Power Off HIT_TEST unless we really want to emulate
			//	turning the drive OFF.
			/*
			if (IN_RECT(x,y,38,38+32,92,92+32)) {
				PlaySound("Click", winstate.hInst,
							SND_RESOURCE | SND_ASYNC | SND_NOWAIT);
				SendMessage(hDlg,WM_COMMAND,IDCANCEL,0);
			}
			*/

			// Door click
			if (IN_RECT(x,y,DRIVE_LEFT+375,DRIVE_LEFT+475,DRIVE_TOP+50,DRIVE_TOP+80)) {
				if (strlen(DiskDrives[d].szFnm) == 0) { // no file name, so load file
						SendMessage(hDlg,WM_COMMAND,IDC_BTN_LOAD,0);
						SendMessage(hDlg,WM_PAINT,0,0);
				}
				else{ // else detach file
					SendMessage(hDlg,WM_COMMAND,IDC_BTN_UNLOAD,0);
					SendMessage(hDlg,WM_PAINT,0,0);					
				}
			}
		break;
		
		case WM_INITDIALOG:
			fBuildDisk = FALSE;
			// create objects for drive open and closed images
			HBMPopen=LoadBitmap(winstate.hInst,"IDB_DRIVE");
			HBMPclosed=LoadBitmap(winstate.hInst,"IDB_DRIVE");

			// temporary objects for pasting together images -
			HBMPtempa=LoadBitmap(winstate.hInst,"IDB_OPEN");
			HBMPtempb=LoadBitmap(winstate.hInst,"IDB_CLOSED");

			GetObject(HBMPtempa, sizeof(BITMAP), &bm); // both doors same size.
			HDCmain=GetDC(hDlg);
			HDCopen=CreateCompatibleDC(HDCmain);
			HDCclosed=CreateCompatibleDC(HDCmain);

			HDCtempa = CreateCompatibleDC(HDCmain); // for temp door open image
			HDCtempb = CreateCompatibleDC(HDCmain); // for temp door closed image
			SelectObject(HDCopen, HBMPopen);		// Drive (w/ door open)
			SelectObject(HDCclosed, HBMPclosed);	// Drive (w/ door closed)
			SelectObject(HDCtempa, HBMPtempa);		// Door open
			SelectObject(HDCtempb, HBMPtempb);		// Door closed

			// paint doors over copies of drives at proper offset -
			BitBlt(HDCopen,  270,38,bm.bmWidth,bm.bmHeight,HDCtempa,0,0,SRCCOPY);
			BitBlt(HDCclosed,270,38,bm.bmWidth,bm.bmHeight,HDCtempb,0,0,SRCCOPY);

			// dispose of un-needed temporary objects -
			DeleteDC(HDCtempa);
			DeleteDC(HDCtempb);
			ReleaseDC(hDlg,HDCmain);
			DeleteObject(HBMPtempa);
			DeleteObject(HBMPtempb);

			Populate_ComboBox(GetDlgItem(hDlg, IDC_DRIVE_NUM),"0,1,2,3,4,5,6,7"); 
			sprintf(buf, "0"); 
			SetDlgItemText(hDlg, IDC_DRIVE_NUM, buf);	// Start with drive 0

			// Move window into position
			GetWindowRect(hDlg, &rc);	// device window
			// x_size = (int)(rc.right - rc.left);	// base size x
			// y_size = (int)(rc.bottom - rc.top);	// base size y
			x_size = DISKBMP_W + DRIVE_LEFT
					+ GetSystemMetrics( SM_CXFRAME );	// FJS
			y_size = DISKBMP_H + DRIVE_TOP
					+ GetSystemMetrics( SM_CYCAPTION )
					+ GetSystemMetrics( SM_CYFRAME ) + 4; // FJS
			ProfBuf = SYSCFG_ReadConfigInfo("windows", "diskdrv");
		    if (sscanf(ProfBuf, "org=%d,%d (size=%d,%d)", &rc.left, &rc.top,
					&rc.right, &rc.bottom) == 4) {
				MoveWindow(hDlg, rc.left, rc.top, x_size, y_size , FALSE);
		    }

			GetDlgItemText(hDlg, IDC_DRIVE_NUM, buf, 2); /* read-back selected drive */
			d = (atoi(buf) & NUM_OF_DSK_MASK);		// & 7

			// paint load and unload buttons -
			SendMessage(GetDlgItem(hDlg,IDC_BTN_LOAD),
					BM_SETIMAGE,
					IMAGE_ICON,
					(long)LoadIcon(winstate.hInst, "INSERTDSK")
				);
			SendMessage(GetDlgItem(hDlg,IDC_BTN_UNLOAD),
					BM_SETIMAGE,
					IMAGE_ICON,
					(long)LoadIcon(winstate.hInst, "RMOVDISK")
				);

			// Force WM_PAINT to update LEDs
			UpdateWindow(hDlg);
		return TRUE;	// let Windows set default focus
			
		case WM_CTLCOLOREDIT:
		case WM_PAINT:
			Draw_Dialog(/* winstate.hInst, */ hDlg);

			GetDlgItemText(hDlg, IDC_DRIVE_NUM, buf, 2); // current drive
			d = (atoi(buf) & NUM_OF_DSK_MASK);	// 7
			// FJS was if (strlen(DiskDrives[d].szFnm) != 0) ... -
			Update_Leds(winstate.hInst,hDlg,d,1,(strlen(DiskDrives[d].szFnm) != 0),2,2);
							
			// Has to be here so bitmap doesn't overwrite text box -
			for (i = 0; i < NUM_OF_DSK; i++) {	// was "<=7"
				if ((DiskDrives[i].flags & DSK_ATT) == DSK_ATT) // show name
					SetDlgItemText(hDlg, iEditID[i], DiskDrives[i].szFnm);
			}
			// GetDlgItemText(hDlg, IDC_DRIVE_NUM, buf, 2);
			// d=(atoi(buf) & 7); 
			// SetDlgItemText(hDlg, IDC_FILENAME, DiskDrives[d].szFnm);
		break;

		case WM_KEYDOWN:
			//  Process key messages. wParam is key code; lParam is message data
			switch (wParam){
				case VK_F4:
					DestroyWindow(hDlg);
					hwndDISK = NULL;
				return TRUE;

				// default: /* nop */ break;
			}
		break;

		case WM_COMMAND:
			GetDlgItemText(hDlg, IDC_DRIVE_NUM, buf, 2); // current drive
			d = (atoi(buf) & NUM_OF_DSK_MASK);  // "&7"

			switch (LOWORD(wParam)) {
				case IDC_BTN_LOAD:
					if (AttachFile(hDlg,d)) {
						// SetDlgItemText(hDlg, IDC_FILENAME, DiskDrives[d].szFnm);
						// d=((d+1) &7);
						// sprintf(buf,"%d",d);
						// SetDlgItemText(hDlg, IDC_DRIVE_NUM, buf);
						if (Disksnd) PlaySound("door_close", winstate.hInst,
							SND_RESOURCE | SND_ASYNC | SND_NOWAIT);
						// auto increment drive number of good load JJF
					}
				break;
								
				case IDC_BTN_UNLOAD: 
					DetachFile(hDlg,d); 
					Draw_Dialog(/* winstate.hInst, */ hDlg);
					// JJF Actually all leds but power should be off on unload
					Update_Leds(winstate.hInst,hDlg,d,1,0,0,0);
					if (Disksnd) PlaySound("door_open", winstate.hInst,
						SND_RESOURCE | SND_ASYNC | SND_NOWAIT);
				break;

				case IDB_BLANK_DISK:
					DialogBox(winstate.hInst, "Build Blank Disk", winstate.hWnd,
						(DLGPROC)WndProcBuildDisk);
					if (!fBuildDisk){
						HOST_ThrowErrMsg("New disk image not created.");
					}
				break;
					
				case IDCANCEL: // not done on shut-down, why?
					DeleteDC(HDCopen);
					DeleteDC(HDCclosed);
					DeleteObject(HBMPopen);
					DeleteObject(HBMPclosed);

					GetWindowRect(hDlg, &rc);
				    sprintf(buf2, "org=%d,%d (size=%d,%d)", rc.left, rc.top,
							rc.right-rc.left, rc.bottom-rc.top);
				    SYSCFG_WriteConfigInfo("windows", "diskdrv", buf2);
					// TBD - save mounted disk configuration if user desires

					DestroyWindow(hDlg);
					hwndDISK = NULL;
					SetForegroundWindow(winstate.hWnd);	// main window
				break;

				case IDC_DSK1: // FJS select from text buttons -
				case IDC_DSK2:
				case IDC_DSK3:
				case IDC_DSK4:
				case IDC_DSK5:
				case IDC_DSK6:
				case IDC_DSK7:
				case IDC_DSK8:
					GetDlgItemText(hDlg, LOWORD (wParam), buf, 2); /* read indicated drive */
					d = (atoi(buf) & NUM_OF_DSK_MASK);		// 7
					GetDlgItemText(hDlg, IDC_DRIVE_NUM, buf, 2); /* read-back selected drive */
					if ((atoi(buf) & NUM_OF_DSK_MASK) != d) {
						sprintf(buf, "%d", d); 
						SetDlgItemText(hDlg, IDC_DRIVE_NUM, buf);	// Select indicated drive
					}
				break; // Select only

				case IDC_DISK1: // FJS select & load/unload from text buttons -
				case IDC_DISK2: // Note: IDC_DISK1..IDC_DISK8 must be consecutive
				case IDC_DISK3:
				case IDC_DISK4:
				case IDC_DISK5:
				case IDC_DISK6:
				case IDC_DISK7:
				case IDC_DISK8:
					// if indicated drive not selected, select new
					// then, if selected drive has disk, unload
					//		else load selected drive
					d = (LOWORD(wParam) - IDC_DISK1) & NUM_OF_DSK_MASK;
					GetDlgItemText(hDlg, IDC_DRIVE_NUM, buf, 2); /* read-back selected drive */
					if ((atoi(buf) & NUM_OF_DSK_MASK) != d) {
						sprintf(buf, "%d", d); 
						SetDlgItemText(hDlg, IDC_DRIVE_NUM, buf);	// Select indicated drive
					}
				// fall through -					

				case IDOK: // FJS attach/detach (currently) selected drive
					if (strlen(DiskDrives[d].szFnm) == 0) { // no file name, so load file
						SendMessage(hDlg,WM_COMMAND,IDC_BTN_LOAD,0);
						SendMessage(hDlg,WM_PAINT,0,0);
					}
					else { // else detach file
						SendMessage(hDlg,WM_COMMAND,IDC_BTN_UNLOAD,0);
						SendMessage(hDlg,WM_PAINT,0,0);					
					}
				// break;

				// default: break;
			}	// end of switch (LOWORD(wParam))
		// break;	// end of WM_COMMAND switch

		// default: /* no-op */ break;
	}	// message case
	return FALSE;
}


/* Window callback for "build a disk" dialog  */
LRESULT CALLBACK WndProcBuildDisk(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
{

	switch (iMsg) {
		case WM_INITDIALOG:
			fBuildDisk = FALSE ;	// always start out failing
			//				  hwnd  first_item     last_item   ID_to_select
			CheckRadioButton(hDlg, IDC_DSKSIZE77, IDC_DSKSIZE254, iDskSize);
			if (strlen(szNewFileName) /* != 0 */)
				SetDlgItemText(hDlg, IDC_EDIT_NEWDISK, szNewFileName);
		    return TRUE;			// let Windows set default focus

		case WM_COMMAND:
			switch (LOWORD (wParam)){
				case IDC_DSKSIZE77:
				case IDC_DSKSIZE254:
					iDskSize = LOWORD(wParam);
					CheckRadioButton(hDlg, IDC_DSKSIZE77, IDC_DSKSIZE254, iDskSize);
					break;

				case IDOK:
					if (iDskSize == IDC_DSKSIZE77)
						fBuildDisk = CreateBlankDisk(hDlg, DSK_SM, szNewFilePath);
					else
						fBuildDisk = CreateBlankDisk(hDlg, DSK_LG, szNewFilePath);

					// Fall through to IDCANCEL

				case IDCANCEL:
					SetForegroundWindow(hwndDISK);	// return focus to parent
					EndDialog(hDlg, TRUE);
			}
	}
	return FALSE;
}


/*--  Draw_Dialog  --------------------------------------------
	Draws drive graphics on empty template window

	Params:		hInst, hWnd

	Uses:		nothing

	Returns:	nothing
	
	Comments:	Called by WM_PAINT in disk drive WndProc
-------------------------------------------------------------*/
static void Draw_Dialog(/* HINSTANCE Instance, */ HWND Hwnd)
{
	if (Hwnd  != NULL) { // Only draw if the dialog is open. NULLCHG
		BITMAP	bm;
		HDC		hDC;
		HICON	hTogDown;

			// draw open/closed drive face in dialog window -
			GetObject(HBMPopen, sizeof(BITMAP), &bm); // open & closed same size
			hDC = GetDC(Hwnd);
			BitBlt(hDC, DRIVE_LEFT, DRIVE_TOP, bm.bmWidth, bm.bmHeight,
					(strlen(DiskDrives[d].szFnm) == 0) ? HDCopen : HDCclosed,
					0, 0, SRCCOPY);
			// draw power switch over drive face -
			hTogDown = LoadIcon(winstate.hInst, "togdown"); // PWR always ON
			DrawIcon( hDC, DRIVE_LEFT+38, DRIVE_TOP+62, hTogDown );
		}
	return;
}


/*--  Update_Leds  --------------------------------------------
	Update disk drive LEDs

	Params:		hInst, hWnd, drive_index, power, drv_enabled,
					head_loaded, drv_active

	Uses:		nothing

	Returns:	nothing
	
	Comments:	Last 4 params convey drive status based on the
				following map:	param=0::turn LED off
								param=1::turn LED on
								param=2::do nothing
-------------------------------------------------------------*/
static void Update_Leds(HINSTANCE Instance, HWND Hwnd, int drive_num,
			int Power, int Enabled, int H_load, int Active)
{
	
	BITMAP	lo, lf;
	BOOL led_x[4]={0,0,0,0};
	HBITMAP	ledon, ledoff;
	HDC		hMemDCo, hMemDCf;
	HDC		hDC;
	int listx[4] = { DRIVE_LEFT+95, DRIVE_LEFT+127,
				DRIVE_LEFT+162, DRIVE_LEFT+190 };
	int index=0;
	static char /* FJS */  led_state[8][4] = {
		{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0},
		{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}
		};
	RECT	rect;
	

	if (Hwnd == NULL) return; // If the dialog isn't open don't do anything.

	if (Power   !=2) led_state[drive_num][0] = Power;
	if (Enabled !=2) led_state[drive_num][1] = Enabled;
	if (H_load  !=2) led_state[drive_num][2] = H_load;
	if (Active  !=2) led_state[drive_num][3] = Active;

	for (index=0;index<=3;index++)
		led_x[index]=led_state[d][index];
		
	ledon=LoadBitmap(Instance,"LED_ON");
	ledoff=LoadBitmap(Instance,"LED_OFF");
	GetObject(ledon,sizeof(BITMAP),&lo);
	GetObject(ledoff,sizeof(BITMAP),&lf);
	hDC=GetDC(Hwnd);
	hMemDCo=CreateCompatibleDC(hDC);
	hMemDCf=CreateCompatibleDC(hDC);
	SelectObject(hMemDCo,ledon);
	SelectObject(hMemDCf,ledoff);
	GetClientRect(Hwnd,&rect);
	
	for (index=0;index<4;index++){
		if (led_x[index])
			BitBlt(hDC, listx[index], DRIVE_TOP+135, lo.bmWidth,
				lo.bmHeight, hMemDCo, 0, 0, SRCCOPY);
		else
			BitBlt(hDC, listx[index], DRIVE_TOP+135, lf.bmWidth,
				lf.bmHeight, hMemDCf, 0, 0, SRCCOPY);
	}
	
	DeleteDC(hMemDCo);
	DeleteDC(hMemDCf);
	ReleaseDC(Hwnd,hDC);
	DeleteObject(ledon);
	DeleteObject(ledoff);
	return;
}

/* end of file: 88disk.c */
