2006-02-25 20:50:29 +00:00
/*
AppleWin : An Apple //e emulator for Windows
Copyright ( C ) 1994 - 1996 , Michael O ' Brien
Copyright ( C ) 1999 - 2001 , Oliver Schmidt
Copyright ( C ) 2002 - 2005 , Tom Charlesworth
2019-04-16 20:30:54 +01:00
Copyright ( C ) 2006 - 2019 , Tom Charlesworth , Michael Pohoreski , Nick Westgate
2006-02-25 20:50:29 +00:00
AppleWin is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
AppleWin is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with AppleWin ; if not , write to the Free Software
Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
/* Description: Disk
*
* Author : Various
2015-09-13 11:39:58 +01:00
*
2017-05-14 11:33:07 +01:00
* In comments , UTAIIe is an abbreviation for a reference to " Understanding the Apple //e " by James Sather
2006-02-25 20:50:29 +00:00
*/
# include "StdAfx.h"
2014-08-13 21:30:35 +01:00
2015-02-13 22:40:53 +00:00
# include "SaveState_Structs_v1.h"
2018-02-24 15:12:40 +00:00
# include "Applewin.h"
2018-01-14 18:01:22 +00:00
# include "CPU.h"
2014-08-13 21:30:35 +01:00
# include "Disk.h"
2010-01-03 18:43:08 +00:00
# include "DiskImage.h"
2014-08-13 21:30:35 +01:00
# include "Frame.h"
2015-09-13 11:39:58 +01:00
# include "Log.h"
2014-08-13 21:30:35 +01:00
# include "Memory.h"
# include "Registry.h"
# include "Video.h"
2015-12-05 16:50:27 +00:00
# include "YamlHelper.h"
2014-08-13 21:30:35 +01:00
2018-02-24 15:12:40 +00:00
# include "../resource/resource.h"
2007-05-28 11:16:42 +00:00
2019-04-11 22:34:40 +01:00
// About m_enhanceDisk:
// . In general m_enhanceDisk==false is used for authentic disk access speed, whereas m_enhanceDisk==true is for enhanced speed.
// Details:
// . if false: Used by ImageReadTrack() to skew the sectors in a track (for .do, .dsk, .po 5.25" images).
// . if true && m_floppyMotorOn, then this is a condition for full-speed (unthrottled) emulation mode.
// . if false && I/O ReadWrite($C0EC) && drive is spinning, then advance the track buffer's nibble index (to simulate spinning).
// . if I/O ReadWrite($C0EC) && read, then depending on true/false support partial nibble reads for different gaps between consecutive accesses.
// Also m_enhanceDisk is persisted to the save-state, so it's an attribute of the DiskII interface card.
2019-04-14 17:00:15 +01:00
Disk2InterfaceCard : : Disk2InterfaceCard ( void )
2018-02-25 13:38:04 +00:00
{
2019-04-07 15:54:26 +01:00
m_currDrive = 0 ;
m_floppyLatch = 0 ;
m_floppyMotorOn = 0 ;
m_floppyLoadMode = 0 ;
m_floppyWriteMode = 0 ;
m_phases = 0 ;
m_saveDiskImage = true ; // Save the DiskImage name to Registry
m_slot = 0 ;
m_diskLastCycle = 0 ;
m_diskLastReadLatchCycle = 0 ;
m_enhanceDisk = true ;
2018-02-25 15:09:25 +00:00
2019-04-07 15:32:24 +01:00
// Debug:
# if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
2019-04-07 15:54:26 +01:00
m_bLogDisk_NibblesRW = false ;
2019-04-07 15:32:24 +01:00
# endif
# if LOG_DISK_NIBBLES_WRITE
2019-04-07 15:54:26 +01:00
m_uWriteLastCycle = 0 ;
m_uSyncFFCount = 0 ;
2019-04-07 15:32:24 +01:00
# endif
}
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
bool Disk2InterfaceCard : : GetEnhanceDisk ( void ) { return m_enhanceDisk ; }
void Disk2InterfaceCard : : SetEnhanceDisk ( bool bEnhanceDisk ) { m_enhanceDisk = bEnhanceDisk ; }
2018-02-25 15:09:25 +00:00
2019-04-14 17:00:15 +01:00
int Disk2InterfaceCard : : GetCurrentDrive ( void ) { return m_currDrive ; }
int Disk2InterfaceCard : : GetCurrentTrack ( void ) { return m_floppyDrive [ m_currDrive ] . m_track ; }
int Disk2InterfaceCard : : GetCurrentPhase ( void ) { return m_floppyDrive [ m_currDrive ] . m_phase ; }
int Disk2InterfaceCard : : GetCurrentOffset ( void ) { return m_floppyDrive [ m_currDrive ] . m_disk . m_byte ; }
int Disk2InterfaceCard : : GetTrack ( const int drive ) { return m_floppyDrive [ drive ] . m_track ; }
2013-11-02 21:24:45 +00:00
2019-04-14 17:00:15 +01:00
LPCTSTR Disk2InterfaceCard : : GetCurrentState ( void )
2013-11-02 21:24:45 +00:00
{
2019-04-14 16:58:49 +01:00
if ( m_floppyDrive [ m_currDrive ] . m_disk . m_imagehandle = = NULL )
2013-11-02 21:24:45 +00:00
return " Empty " ;
2019-04-07 15:54:26 +01:00
if ( ! m_floppyMotorOn )
2013-11-02 21:24:45 +00:00
{
2019-04-14 16:47:41 +01:00
if ( m_floppyDrive [ m_currDrive ] . m_spinning > 0 )
2013-11-02 21:24:45 +00:00
return " Off (spinning) " ;
else
return " Off " ;
}
2019-04-07 15:54:26 +01:00
else if ( m_floppyWriteMode )
2013-11-02 21:24:45 +00:00
{
2019-04-14 16:58:49 +01:00
if ( m_floppyDrive [ m_currDrive ] . m_disk . m_bWriteProtected )
2013-11-02 21:24:45 +00:00
return " Writing (write protected) " ;
2015-09-13 11:39:58 +01:00
else
return " Writing " ;
2013-11-02 21:24:45 +00:00
}
else
{
2019-04-07 15:54:26 +01:00
/*if (m_floppyLoadMode)
2015-09-13 11:39:58 +01:00
{
2019-04-07 15:54:26 +01:00
if ( m_floppyDrive [ m_currDrive ] . disk . bWriteProtected )
2015-09-13 11:39:58 +01:00
return " Reading write protect state (write protected) " ;
else
return " Reading write protect state (not write protected) " ;
}
else */
return " Reading " ;
2013-11-02 21:24:45 +00:00
}
}
//===========================================================================
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : LoadLastDiskImage ( const int drive )
2008-08-31 04:31:35 +00:00
{
2019-04-08 16:54:11 +01:00
_ASSERT ( drive = = DRIVE_1 | | drive = = DRIVE_2 ) ;
2013-12-06 21:10:41 +00:00
2008-08-31 04:31:35 +00:00
char sFilePath [ MAX_PATH + 1 ] ;
sFilePath [ 0 ] = 0 ;
2019-04-08 16:54:11 +01:00
const char * pRegKey = ( drive = = DRIVE_1 )
2008-08-31 04:31:35 +00:00
? REGVALUE_PREF_LAST_DISK_1
: REGVALUE_PREF_LAST_DISK_2 ;
2009-05-29 21:39:13 +00:00
2015-02-13 22:40:53 +00:00
if ( RegLoadString ( TEXT ( REG_PREFS ) , pRegKey , 1 , sFilePath , MAX_PATH ) )
2008-08-31 04:31:35 +00:00
{
sFilePath [ MAX_PATH ] = 0 ;
2019-04-07 15:54:26 +01:00
m_saveDiskImage = false ;
2009-05-29 21:39:13 +00:00
// Pass in ptr to local copy of filepath, since RemoveDisk() sets DiskPathFilename = ""
2019-04-08 16:54:11 +01:00
InsertDisk ( drive , sFilePath , IMAGE_USE_FILES_WRITE_PROTECT_STATUS , IMAGE_DONT_CREATE ) ;
2019-04-07 15:54:26 +01:00
m_saveDiskImage = true ;
2008-08-31 04:31:35 +00:00
}
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : SaveLastDiskImage ( const int drive )
2008-08-31 04:31:35 +00:00
{
2019-04-08 16:54:11 +01:00
_ASSERT ( drive = = DRIVE_1 | | drive = = DRIVE_2 ) ;
2008-08-31 04:31:35 +00:00
2019-04-07 15:54:26 +01:00
if ( ! m_saveDiskImage )
2013-12-06 21:10:41 +00:00
return ;
2019-04-14 16:58:49 +01:00
const char * pFileName = m_floppyDrive [ drive ] . m_disk . m_fullname ;
2013-12-06 21:10:41 +00:00
2019-04-08 16:54:11 +01:00
if ( drive = = DRIVE_1 )
2013-12-06 21:10:41 +00:00
RegSaveString ( TEXT ( REG_PREFS ) , REGVALUE_PREF_LAST_DISK_1 , TRUE , pFileName ) ;
else
RegSaveString ( TEXT ( REG_PREFS ) , REGVALUE_PREF_LAST_DISK_2 , TRUE , pFileName ) ;
2015-04-11 22:24:54 +01:00
//
char szPathName [ MAX_PATH ] ;
2019-04-08 16:54:11 +01:00
strcpy ( szPathName , DiskGetFullPathName ( drive ) ) ;
2015-04-11 22:24:54 +01:00
if ( _tcsrchr ( szPathName , TEXT ( ' \\ ' ) ) )
{
char * pPathEnd = _tcsrchr ( szPathName , TEXT ( ' \\ ' ) ) + 1 ;
* pPathEnd = 0 ;
RegSaveString ( TEXT ( REG_PREFS ) , TEXT ( REGVALUE_PREF_START_DIR ) , 1 , szPathName ) ;
}
2008-08-31 04:31:35 +00:00
}
2006-02-25 20:50:29 +00:00
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:41:26 +01:00
// Called by ControlMotor() & Enable()
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : CheckSpinning ( const ULONG nExecutedCycles )
2010-01-03 18:43:08 +00:00
{
2019-04-14 16:47:41 +01:00
DWORD modechange = ( m_floppyMotorOn & & ! m_floppyDrive [ m_currDrive ] . m_spinning ) ;
2014-07-26 13:23:57 -07:00
2019-04-07 15:54:26 +01:00
if ( m_floppyMotorOn )
2019-04-14 16:47:41 +01:00
m_floppyDrive [ m_currDrive ] . m_spinning = SPINNING_CYCLES ;
2014-07-26 13:23:57 -07:00
2010-01-03 18:43:08 +00:00
if ( modechange )
2014-07-23 18:08:52 -07:00
FrameDrawDiskLEDS ( ( HDC ) 0 ) ;
2018-01-14 18:01:22 +00:00
if ( modechange )
{
2019-04-07 15:54:26 +01:00
// Set m_diskLastCycle when motor changes: not spinning (ie. off for 1 sec) -> on
2018-03-03 21:27:50 +00:00
CpuCalcCycles ( nExecutedCycles ) ;
2019-04-07 15:54:26 +01:00
m_diskLastCycle = g_nCumulativeCycles ;
2018-01-14 18:01:22 +00:00
}
2006-02-25 20:50:29 +00:00
}
2006-02-28 18:37:47 +00:00
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
Disk_Status_e Disk2InterfaceCard : : GetDriveLightStatus ( const int drive )
2006-02-28 18:37:47 +00:00
{
2019-04-08 16:54:11 +01:00
if ( IsDriveValid ( drive ) )
2006-02-28 18:37:47 +00:00
{
2019-04-14 16:47:41 +01:00
FloppyDrive * pDrive = & m_floppyDrive [ drive ] ;
2006-02-28 18:37:47 +00:00
2019-04-14 16:47:41 +01:00
if ( pDrive - > m_spinning )
2006-02-28 18:37:47 +00:00
{
2019-04-14 16:58:49 +01:00
if ( pDrive - > m_disk . m_bWriteProtected )
2006-02-28 18:37:47 +00:00
return DISK_STATUS_PROT ;
2019-04-14 16:47:41 +01:00
if ( pDrive - > m_writelight )
2006-02-28 18:37:47 +00:00
return DISK_STATUS_WRITE ;
else
return DISK_STATUS_READ ;
}
else
2018-02-25 13:38:04 +00:00
{
2006-02-28 18:37:47 +00:00
return DISK_STATUS_OFF ;
2018-02-25 13:38:04 +00:00
}
2006-02-28 18:37:47 +00:00
}
return DISK_STATUS_OFF ;
}
2006-02-26 02:02:57 +00:00
//===========================================================================
2019-04-14 17:00:15 +01:00
bool Disk2InterfaceCard : : IsDriveValid ( const int drive )
2010-01-03 18:43:08 +00:00
{
2019-04-08 16:54:11 +01:00
return ( drive > = 0 & & drive < NUM_DRIVES ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : AllocTrack ( const int drive )
2006-02-25 20:50:29 +00:00
{
2019-04-14 16:58:49 +01:00
FloppyDisk * pFloppy = & m_floppyDrive [ drive ] . m_disk ;
pFloppy - > m_trackimage = ( LPBYTE ) VirtualAlloc ( NULL , NIBBLES_PER_TRACK , MEM_COMMIT , PAGE_READWRITE ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : ReadTrack ( const int drive )
2006-02-26 02:02:57 +00:00
{
2019-04-08 16:54:11 +01:00
if ( ! IsDriveValid ( drive ) )
2006-02-26 02:02:57 +00:00
return ;
2019-04-14 16:47:41 +01:00
FloppyDrive * pDrive = & m_floppyDrive [ drive ] ;
2019-04-14 16:58:49 +01:00
FloppyDisk * pFloppy = & pDrive - > m_disk ;
2006-02-26 02:02:57 +00:00
2019-04-14 16:58:49 +01:00
if ( pDrive - > m_track > = ImageGetNumTracks ( pFloppy - > m_imagehandle ) )
2006-02-26 02:02:57 +00:00
{
2019-04-14 16:58:49 +01:00
pFloppy - > m_trackimagedata = false ;
2006-02-26 02:02:57 +00:00
return ;
}
2019-04-14 16:58:49 +01:00
if ( ! pFloppy - > m_trackimage )
2019-04-08 16:54:11 +01:00
AllocTrack ( drive ) ;
2006-02-26 02:02:57 +00:00
2019-04-14 16:58:49 +01:00
if ( pFloppy - > m_trackimage & & pFloppy - > m_imagehandle )
2006-02-26 02:02:57 +00:00
{
2015-09-13 11:39:58 +01:00
# if LOG_DISK_TRACKS
2019-04-14 17:45:01 +01:00
LOG_DISK ( " track $%02X%s read \r \n " , pDrive - > m_track , ( pDrive - > m_phase & 1 ) ? " .5 " : " " ) ;
2015-09-13 11:39:58 +01:00
# endif
2006-02-26 02:02:57 +00:00
ImageReadTrack (
2019-04-14 16:58:49 +01:00
pFloppy - > m_imagehandle ,
2019-04-14 16:47:41 +01:00
pDrive - > m_track ,
pDrive - > m_phase ,
2019-04-14 16:58:49 +01:00
pFloppy - > m_trackimage ,
& pFloppy - > m_nibbles ,
2019-04-07 15:54:26 +01:00
m_enhanceDisk ) ;
2006-02-26 02:02:57 +00:00
2019-04-14 16:58:49 +01:00
pFloppy - > m_byte = 0 ;
pFloppy - > m_trackimagedata = ( pFloppy - > m_nibbles ! = 0 ) ;
2006-02-26 02:02:57 +00:00
}
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : RemoveDisk ( const int drive )
2006-02-26 02:02:57 +00:00
{
2019-04-14 16:58:49 +01:00
FloppyDisk * pFloppy = & m_floppyDrive [ drive ] . m_disk ;
2006-02-26 02:02:57 +00:00
2019-04-14 16:58:49 +01:00
if ( pFloppy - > m_imagehandle )
2006-02-26 02:02:57 +00:00
{
2019-04-08 16:54:11 +01:00
FlushCurrentTrack ( drive ) ;
2006-02-26 02:02:57 +00:00
2019-04-14 16:58:49 +01:00
ImageClose ( pFloppy - > m_imagehandle ) ;
pFloppy - > m_imagehandle = NULL ;
2006-02-26 02:02:57 +00:00
}
2019-04-14 16:58:49 +01:00
if ( pFloppy - > m_trackimage )
2006-02-26 02:02:57 +00:00
{
2019-04-14 16:58:49 +01:00
VirtualFree ( pFloppy - > m_trackimage , 0 , MEM_RELEASE ) ;
pFloppy - > m_trackimage = NULL ;
pFloppy - > m_trackimagedata = false ;
2006-02-26 02:02:57 +00:00
}
2019-04-14 16:58:49 +01:00
memset ( pFloppy - > m_imagename , 0 , MAX_DISK_IMAGE_NAME + 1 ) ;
memset ( pFloppy - > m_fullname , 0 , MAX_DISK_FULL_NAME + 1 ) ;
pFloppy - > m_strFilenameInZip = " " ;
2008-08-31 04:31:35 +00:00
2019-04-08 16:54:11 +01:00
SaveLastDiskImage ( drive ) ;
2008-08-31 04:31:35 +00:00
Video_ResetScreenshotCounter ( NULL ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : WriteTrack ( const int drive )
2006-02-26 02:02:57 +00:00
{
2019-04-14 16:47:41 +01:00
FloppyDrive * pDrive = & m_floppyDrive [ drive ] ;
2019-04-14 16:58:49 +01:00
FloppyDisk * pFloppy = & pDrive - > m_disk ;
2006-02-26 02:02:57 +00:00
2019-04-14 16:58:49 +01:00
if ( pDrive - > m_track > = ImageGetNumTracks ( pFloppy - > m_imagehandle ) )
2006-02-26 02:02:57 +00:00
return ;
2019-04-14 16:58:49 +01:00
if ( pFloppy - > m_bWriteProtected )
2006-02-26 02:02:57 +00:00
return ;
2019-04-14 16:58:49 +01:00
if ( pFloppy - > m_trackimage & & pFloppy - > m_imagehandle )
2015-09-13 11:39:58 +01:00
{
# if LOG_DISK_TRACKS
2019-04-14 16:58:49 +01:00
LOG_DISK ( " track $%02X%s write \r \n " , pDrive - > m_track , ( pDrive - > m_phase & 0 ) ? " .5 " : " " ) ; // TODO: hard-coded to whole tracks - see below (nickw)
2015-09-13 11:39:58 +01:00
# endif
2006-02-26 02:02:57 +00:00
ImageWriteTrack (
2019-04-14 16:58:49 +01:00
pFloppy - > m_imagehandle ,
2019-04-14 16:47:41 +01:00
pDrive - > m_track ,
pDrive - > m_phase , // TODO: this should never be used; it's the current phase (half-track), not that of the track to be written (nickw)
2019-04-14 16:58:49 +01:00
pFloppy - > m_trackimage ,
pFloppy - > m_nibbles ) ;
2015-09-13 11:39:58 +01:00
}
2006-02-26 02:02:57 +00:00
2019-04-14 16:58:49 +01:00
pFloppy - > m_trackimagedirty = false ;
2018-04-02 18:21:18 +01:00
}
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : FlushCurrentTrack ( const int drive )
2018-04-02 18:21:18 +01:00
{
2019-04-14 16:58:49 +01:00
FloppyDisk * pFloppy = & m_floppyDrive [ drive ] . m_disk ;
2018-04-02 18:21:18 +01:00
2019-04-14 16:58:49 +01:00
if ( pFloppy - > m_trackimage & & pFloppy - > m_trackimagedirty )
2019-04-08 16:54:11 +01:00
WriteTrack ( drive ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : Boot ( void )
2010-01-03 18:43:08 +00:00
{
// THIS FUNCTION RELOADS A PROGRAM IMAGE IF ONE IS LOADED IN DRIVE ONE.
// IF A DISK IMAGE OR NO IMAGE IS LOADED IN DRIVE ONE, IT DOES NOTHING.
2019-04-14 16:58:49 +01:00
if ( m_floppyDrive [ 0 ] . m_disk . m_imagehandle & & ImageBoot ( m_floppyDrive [ 0 ] . m_disk . m_imagehandle ) )
2019-04-07 15:54:26 +01:00
m_floppyMotorOn = 0 ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void __stdcall Disk2InterfaceCard : : ControlMotor ( WORD , WORD address , BYTE , BYTE , ULONG uExecutedCycles )
2010-01-03 18:43:08 +00:00
{
2018-01-14 18:01:22 +00:00
BOOL newState = address & 1 ;
2019-04-07 15:54:26 +01:00
if ( newState ! = m_floppyMotorOn ) // motor changed state
m_formatTrack . DriveNotWritingTrack ( ) ;
2018-01-14 18:01:22 +00:00
2019-04-07 15:54:26 +01:00
m_floppyMotorOn = newState ;
2018-01-28 12:30:54 +00:00
// NB. Motor off doesn't reset the Command Decoder like reset. (UTAIIe figures 9.7 & 9.8 chip C2)
2019-04-07 15:54:26 +01:00
// - so it doesn't reset this state: m_floppyLoadMode, m_floppyWriteMode, m_phases
2015-09-13 11:39:58 +01:00
# if LOG_DISK_MOTOR
2019-04-07 15:54:26 +01:00
LOG_DISK ( " motor %s \r \n " , ( m_floppyMotorOn ) ? " on " : " off " ) ;
2015-09-13 11:39:58 +01:00
# endif
2018-01-14 18:01:22 +00:00
CheckSpinning ( uExecutedCycles ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void __stdcall Disk2InterfaceCard : : ControlStepper ( WORD , WORD address , BYTE , BYTE , ULONG uExecutedCycles )
2007-03-23 22:26:35 +00:00
{
2019-04-14 16:47:41 +01:00
FloppyDrive * pDrive = & m_floppyDrive [ m_currDrive ] ;
2019-04-14 16:58:49 +01:00
FloppyDisk * pFloppy = & pDrive - > m_disk ;
2018-01-28 12:21:28 +00:00
2019-04-07 15:54:26 +01:00
if ( ! m_floppyMotorOn ) // GH#525
2018-01-27 17:02:33 +00:00
{
2019-04-14 16:47:41 +01:00
if ( ! pDrive - > m_spinning )
2018-01-28 12:21:28 +00:00
{
2018-01-27 17:02:33 +00:00
# if LOG_DISK_PHASES
2018-01-28 12:21:28 +00:00
LOG_DISK ( " stepper accessed whilst motor is off and not spinning \r \n " ) ;
# endif
return ;
}
# if LOG_DISK_PHASES
LOG_DISK ( " stepper accessed whilst motor is off, but still spinning \r \n " ) ;
2018-01-27 17:02:33 +00:00
# endif
}
2015-09-13 11:39:58 +01:00
int phase = ( address > > 1 ) & 3 ;
2010-01-03 18:43:08 +00:00
int phase_bit = ( 1 < < phase ) ;
2007-03-23 22:26:35 +00:00
2015-09-13 11:39:58 +01:00
# if 1
2010-01-03 18:43:08 +00:00
// update the magnet states
if ( address & 1 )
{
// phase on
2019-04-07 15:54:26 +01:00
m_phases | = phase_bit ;
2010-01-03 18:43:08 +00:00
}
else
{
// phase off
2019-04-07 15:54:26 +01:00
m_phases & = ~ phase_bit ;
2010-01-03 18:43:08 +00:00
}
2007-03-23 22:26:35 +00:00
2010-01-03 18:43:08 +00:00
// check for any stepping effect from a magnet
// - move only when the magnet opposite the cog is off
// - move in the direction of an adjacent magnet if one is on
// - do not move if both adjacent magnets are on
// momentum and timing are not accounted for ... maybe one day!
int direction = 0 ;
2019-04-14 16:47:41 +01:00
if ( m_phases & ( 1 < < ( ( pDrive - > m_phase + 1 ) & 3 ) ) )
2010-01-03 18:43:08 +00:00
direction + = 1 ;
2019-04-14 16:47:41 +01:00
if ( m_phases & ( 1 < < ( ( pDrive - > m_phase + 3 ) & 3 ) ) )
2010-01-03 18:43:08 +00:00
direction - = 1 ;
// apply magnet step, if any
if ( direction )
{
2019-04-14 16:47:41 +01:00
pDrive - > m_phase = MAX ( 0 , MIN ( 79 , pDrive - > m_phase + direction ) ) ;
2019-04-14 16:58:49 +01:00
const int nNumTracksInImage = ImageGetNumTracks ( pFloppy - > m_imagehandle ) ;
2010-01-03 18:43:08 +00:00
const int newtrack = ( nNumTracksInImage = = 0 ) ? 0
2019-04-14 16:47:41 +01:00
: MIN ( nNumTracksInImage - 1 , pDrive - > m_phase > > 1 ) ; // (round half tracks down)
if ( newtrack ! = pDrive - > m_track )
2010-01-03 18:43:08 +00:00
{
2019-04-08 10:41:47 +01:00
FlushCurrentTrack ( m_currDrive ) ;
2019-04-14 16:47:41 +01:00
pDrive - > m_track = newtrack ;
2019-04-14 16:58:49 +01:00
pFloppy - > m_trackimagedata = false ;
2018-01-14 18:01:22 +00:00
2019-04-07 15:54:26 +01:00
m_formatTrack . DriveNotWritingTrack ( ) ;
2010-01-03 18:43:08 +00:00
}
2014-07-23 18:08:52 -07:00
// Feature Request #201 Show track status
// https://github.com/AppleWin/AppleWin/issues/201
FrameDrawDiskStatus ( ( HDC ) 0 ) ;
2010-01-03 18:43:08 +00:00
}
2015-09-13 11:39:58 +01:00
# else
// substitute alternate stepping code here to test
# endif
2018-01-14 18:01:22 +00:00
2015-09-13 11:39:58 +01:00
# if LOG_DISK_PHASES
LOG_DISK ( " track $%02X%s phases %d%d%d%d phase %d %s address $%4X \r \n " ,
2019-04-14 17:45:01 +01:00
pDrive - > m_phase > > 1 ,
( pDrive - > m_phase & 1 ) ? " .5 " : " " ,
2019-04-07 15:54:26 +01:00
( m_phases > > 3 ) & 1 ,
( m_phases > > 2 ) & 1 ,
( m_phases > > 1 ) & 1 ,
( m_phases > > 0 ) & 1 ,
2015-09-13 11:39:58 +01:00
phase ,
( address & 1 ) ? " on " : " off " ,
address ) ;
2011-05-10 20:26:47 +00:00
# endif
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : Destroy ( void )
2006-02-26 02:02:57 +00:00
{
2019-04-07 15:54:26 +01:00
m_saveDiskImage = false ;
2010-01-03 18:43:08 +00:00
RemoveDisk ( DRIVE_1 ) ;
2008-08-31 04:31:35 +00:00
2019-04-07 15:54:26 +01:00
m_saveDiskImage = false ;
2010-01-03 18:43:08 +00:00
RemoveDisk ( DRIVE_2 ) ;
2008-08-31 04:31:35 +00:00
2019-04-07 15:54:26 +01:00
m_saveDiskImage = true ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2019-04-14 17:00:15 +01:00
void __stdcall Disk2InterfaceCard : : Enable ( WORD , WORD address , BYTE , BYTE , ULONG uExecutedCycles )
2010-01-03 18:43:08 +00:00
{
2019-04-07 15:54:26 +01:00
m_currDrive = address & 1 ;
2018-01-14 18:01:22 +00:00
# if LOG_DISK_ENABLE_DRIVE
2019-04-07 15:54:26 +01:00
LOG_DISK ( " enable drive: %d \r \n " , m_currDrive ) ;
2018-01-14 18:01:22 +00:00
# endif
2019-04-14 16:47:41 +01:00
m_floppyDrive [ ! m_currDrive ] . m_spinning = 0 ;
m_floppyDrive [ ! m_currDrive ] . m_writelight = 0 ;
2018-01-14 18:01:22 +00:00
CheckSpinning ( uExecutedCycles ) ;
2010-01-03 18:43:08 +00:00
}
2006-02-26 02:02:57 +00:00
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : EjectDisk ( const int drive )
2006-02-26 02:02:57 +00:00
{
2019-04-08 16:54:11 +01:00
if ( IsDriveValid ( drive ) )
2006-02-26 02:02:57 +00:00
{
2019-04-08 16:54:11 +01:00
RemoveDisk ( drive ) ;
2006-02-26 02:02:57 +00:00
}
}
2006-02-25 20:50:29 +00:00
//===========================================================================
2010-01-03 18:43:08 +00:00
// Return the filename
// . Used by Drive Buttons' tooltips
2019-04-14 17:00:15 +01:00
LPCTSTR Disk2InterfaceCard : : GetFullDiskFilename ( const int drive )
2010-01-03 18:43:08 +00:00
{
2019-04-14 16:58:49 +01:00
if ( ! m_floppyDrive [ drive ] . m_disk . m_strFilenameInZip . empty ( ) )
return m_floppyDrive [ drive ] . m_disk . m_strFilenameInZip . c_str ( ) ;
2006-02-28 18:37:47 +00:00
2019-04-08 16:54:11 +01:00
return GetFullName ( drive ) ;
2010-01-03 18:43:08 +00:00
}
2008-06-20 23:47:25 +00:00
2019-04-09 19:29:58 +01:00
// Return the file or zip name
// . Used by Property Sheet Page (Disk)
2019-04-14 17:00:15 +01:00
LPCTSTR Disk2InterfaceCard : : GetFullName ( const int drive )
2015-02-13 22:40:53 +00:00
{
2019-04-14 16:58:49 +01:00
return m_floppyDrive [ drive ] . m_disk . m_fullname ;
2015-02-13 22:40:53 +00:00
}
2010-01-03 18:43:08 +00:00
// Return the imagename
// . Used by Drive Button's icons & Property Sheet Page (Save snapshot)
2019-04-14 17:00:15 +01:00
LPCTSTR Disk2InterfaceCard : : GetBaseName ( const int drive )
2010-01-03 18:43:08 +00:00
{
2019-04-14 16:58:49 +01:00
return m_floppyDrive [ drive ] . m_disk . m_imagename ;
2010-01-03 18:43:08 +00:00
}
2019-04-09 19:29:58 +01:00
2019-04-14 17:00:15 +01:00
LPCTSTR Disk2InterfaceCard : : DiskGetFullPathName ( const int drive )
2019-04-09 19:29:58 +01:00
{
2019-04-14 16:58:49 +01:00
return ImageGetPathname ( m_floppyDrive [ drive ] . m_disk . m_imagehandle ) ;
2019-04-09 19:29:58 +01:00
}
2006-02-25 20:50:29 +00:00
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : GetLightStatus ( Disk_Status_e * pDisk1Status , Disk_Status_e * pDisk2Status )
2006-02-28 18:37:47 +00:00
{
2018-02-25 13:38:04 +00:00
if ( pDisk1Status )
* pDisk1Status = GetDriveLightStatus ( DRIVE_1 ) ;
2014-07-26 13:23:57 -07:00
2018-02-25 13:38:04 +00:00
if ( pDisk2Status )
* pDisk2Status = GetDriveLightStatus ( DRIVE_2 ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2007-05-28 11:16:42 +00:00
2019-04-14 17:00:15 +01:00
ImageError_e Disk2InterfaceCard : : InsertDisk ( const int drive , LPCTSTR pszImageFilename , const bool bForceWriteProtected , const bool bCreateIfNecessary )
2008-06-20 23:47:25 +00:00
{
2019-04-14 16:47:41 +01:00
FloppyDrive * pDrive = & m_floppyDrive [ drive ] ;
2019-04-14 16:58:49 +01:00
FloppyDisk * pFloppy = & pDrive - > m_disk ;
2018-02-25 13:38:04 +00:00
2019-04-14 16:58:49 +01:00
if ( pFloppy - > m_imagehandle )
2019-04-08 16:54:11 +01:00
RemoveDisk ( drive ) ;
2008-06-20 23:47:25 +00:00
2019-04-16 21:11:47 +01:00
// Reset the disk's attributes, but preserve the drive's attributes (GH#138/Platoon, GH#640)
// . Changing the disk (in the drive) doesn't affect the drive's attributes.
2019-04-16 21:13:55 +01:00
pFloppy - > clear ( ) ;
2008-06-20 23:47:25 +00:00
2010-01-03 18:43:08 +00:00
const DWORD dwAttributes = GetFileAttributes ( pszImageFilename ) ;
if ( dwAttributes = = INVALID_FILE_ATTRIBUTES )
2019-04-14 16:58:49 +01:00
pFloppy - > m_bWriteProtected = false ; // Assume this is a new file to create
2010-01-03 18:43:08 +00:00
else
2019-04-14 16:58:49 +01:00
pFloppy - > m_bWriteProtected = bForceWriteProtected ? true : ( dwAttributes & FILE_ATTRIBUTE_READONLY ) ;
2010-01-03 18:43:08 +00:00
2015-02-13 22:40:53 +00:00
// Check if image is being used by the other drive, and if so remove it in order so it can be swapped
{
2019-04-08 16:54:11 +01:00
const char * pszOtherPathname = DiskGetFullPathName ( ! drive ) ;
2015-02-13 22:40:53 +00:00
char szCurrentPathname [ MAX_PATH ] ;
DWORD uNameLen = GetFullPathName ( pszImageFilename , MAX_PATH , szCurrentPathname , NULL ) ;
if ( uNameLen = = 0 | | uNameLen > = MAX_PATH )
strcpy_s ( szCurrentPathname , MAX_PATH , pszImageFilename ) ;
if ( ! strcmp ( pszOtherPathname , szCurrentPathname ) )
{
2019-04-08 16:54:11 +01:00
EjectDisk ( ! drive ) ;
2015-02-13 22:40:53 +00:00
FrameRefreshStatus ( DRAW_LEDS | DRAW_BUTTON_DRIVES ) ;
}
2014-10-01 19:18:46 -04:00
}
2010-01-03 18:43:08 +00:00
ImageError_e Error = ImageOpen ( pszImageFilename ,
2019-04-14 16:58:49 +01:00
& pFloppy - > m_imagehandle ,
& pFloppy - > m_bWriteProtected ,
2010-01-03 18:43:08 +00:00
bCreateIfNecessary ,
2019-04-14 16:58:49 +01:00
pFloppy - > m_strFilenameInZip ) ;
2010-01-03 18:43:08 +00:00
2019-04-14 16:58:49 +01:00
if ( Error = = eIMAGE_ERROR_NONE & & ImageIsMultiFileZip ( pFloppy - > m_imagehandle ) )
2010-01-03 18:43:08 +00:00
{
TCHAR szText [ 100 + MAX_PATH ] ;
2018-01-14 18:01:22 +00:00
szText [ sizeof ( szText ) - 1 ] = 0 ;
2019-04-14 16:58:49 +01:00
_snprintf ( szText , sizeof ( szText ) - 1 , " Only the first file in a multi-file zip is supported \n Use disk image '%s' ? " , pFloppy - > m_strFilenameInZip . c_str ( ) ) ;
2010-01-03 18:43:08 +00:00
int nRes = MessageBox ( g_hFrameWindow , szText , TEXT ( " Multi-Zip Warning " ) , MB_ICONWARNING | MB_YESNO | MB_SETFOREGROUND ) ;
if ( nRes = = IDNO )
{
2019-04-08 16:54:11 +01:00
RemoveDisk ( drive ) ;
2010-01-03 18:43:08 +00:00
Error = eIMAGE_ERROR_REJECTED_MULTI_ZIP ;
}
}
2008-06-20 23:47:25 +00:00
2010-01-03 18:43:08 +00:00
if ( Error = = eIMAGE_ERROR_NONE )
2008-06-20 23:47:25 +00:00
{
2019-04-14 16:58:49 +01:00
GetImageTitle ( pszImageFilename , pFloppy - > m_imagename , pFloppy - > m_fullname ) ;
Video_ResetScreenshotCounter ( pFloppy - > m_imagename ) ;
2008-08-31 04:31:35 +00:00
}
else
{
2015-02-13 22:40:53 +00:00
Video_ResetScreenshotCounter ( NULL ) ;
2008-06-20 23:47:25 +00:00
}
2019-04-08 16:54:11 +01:00
SaveLastDiskImage ( drive ) ;
2008-08-31 04:31:35 +00:00
2010-01-03 18:43:08 +00:00
return Error ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
bool Disk2InterfaceCard : : IsConditionForFullSpeed ( void )
2018-02-27 21:07:16 +00:00
{
2019-04-07 15:54:26 +01:00
return m_floppyMotorOn & & m_enhanceDisk ;
2018-02-27 21:07:16 +00:00
}
2006-02-25 20:50:29 +00:00
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : NotifyInvalidImage ( const int drive , LPCTSTR pszImageFilename , const ImageError_e Error )
2006-02-26 02:02:57 +00:00
{
2010-01-03 18:43:08 +00:00
TCHAR szBuffer [ MAX_PATH + 128 ] ;
2018-01-14 18:01:22 +00:00
szBuffer [ sizeof ( szBuffer ) - 1 ] = 0 ;
2006-02-26 02:02:57 +00:00
2010-01-03 18:43:08 +00:00
switch ( Error )
2006-06-27 02:34:46 +00:00
{
2010-01-03 18:43:08 +00:00
case eIMAGE_ERROR_UNABLE_TO_OPEN :
case eIMAGE_ERROR_UNABLE_TO_OPEN_GZ :
case eIMAGE_ERROR_UNABLE_TO_OPEN_ZIP :
2018-01-14 18:01:22 +00:00
_snprintf (
2010-01-03 18:43:08 +00:00
szBuffer ,
2018-01-14 18:01:22 +00:00
sizeof ( szBuffer ) - 1 ,
2006-06-27 02:34:46 +00:00
TEXT ( " Unable to open the file %s. " ) ,
2010-01-03 18:43:08 +00:00
pszImageFilename ) ;
break ;
case eIMAGE_ERROR_BAD_SIZE :
2018-01-14 18:01:22 +00:00
_snprintf (
2010-01-03 18:43:08 +00:00
szBuffer ,
2018-01-14 18:01:22 +00:00
sizeof ( szBuffer ) - 1 ,
2010-01-03 18:43:08 +00:00
TEXT ( " Unable to use the file %s \n because the " )
TEXT ( " disk image is an unsupported size. " ) ,
pszImageFilename ) ;
break ;
case eIMAGE_ERROR_BAD_FILE :
2018-01-14 18:01:22 +00:00
_snprintf (
2010-01-03 18:43:08 +00:00
szBuffer ,
2018-01-14 18:01:22 +00:00
sizeof ( szBuffer ) - 1 ,
2010-01-03 18:43:08 +00:00
TEXT ( " Unable to use the file %s \n because the " )
TEXT ( " OS can't access it. " ) ,
pszImageFilename ) ;
2006-06-27 02:34:46 +00:00
break ;
2010-01-03 18:43:08 +00:00
case eIMAGE_ERROR_UNSUPPORTED :
2018-01-14 18:01:22 +00:00
_snprintf (
2010-01-03 18:43:08 +00:00
szBuffer ,
2018-01-14 18:01:22 +00:00
sizeof ( szBuffer ) - 1 ,
2006-06-27 02:34:46 +00:00
TEXT ( " Unable to use the file %s \n because the " )
TEXT ( " disk image format is not recognized. " ) ,
2010-01-03 18:43:08 +00:00
pszImageFilename ) ;
break ;
case eIMAGE_ERROR_UNSUPPORTED_HDV :
2018-01-14 18:01:22 +00:00
_snprintf (
2010-01-03 18:43:08 +00:00
szBuffer ,
2018-01-14 18:01:22 +00:00
sizeof ( szBuffer ) - 1 ,
2010-01-03 18:43:08 +00:00
TEXT ( " Unable to use the file %s \n " )
TEXT ( " because this UniDisk 3.5/Apple IIGS/hard-disk image is not supported. \n " )
TEXT ( " Try inserting as a hard-disk image instead. " ) ,
pszImageFilename ) ;
break ;
case eIMAGE_ERROR_UNSUPPORTED_MULTI_ZIP :
2018-01-14 18:01:22 +00:00
_snprintf (
2010-01-03 18:43:08 +00:00
szBuffer ,
2018-01-14 18:01:22 +00:00
sizeof ( szBuffer ) - 1 ,
2010-01-03 18:43:08 +00:00
TEXT ( " Unable to use the file %s \n because the " )
TEXT ( " first file (%s) in this multi-zip archive is not recognized. \n " )
TEXT ( " Try unzipping and using the disk images directly. \n " ) ,
pszImageFilename ,
2019-04-14 16:58:49 +01:00
m_floppyDrive [ drive ] . m_disk . m_strFilenameInZip . c_str ( ) ) ;
2010-01-03 18:43:08 +00:00
break ;
case eIMAGE_ERROR_GZ :
case eIMAGE_ERROR_ZIP :
2018-01-14 18:01:22 +00:00
_snprintf (
2010-01-03 18:43:08 +00:00
szBuffer ,
2018-01-14 18:01:22 +00:00
sizeof ( szBuffer ) - 1 ,
2010-01-03 18:43:08 +00:00
TEXT ( " Unable to use the compressed file %s \n because the " )
TEXT ( " compressed disk image is corrupt/unsupported. " ) ,
pszImageFilename ) ;
2006-06-27 02:34:46 +00:00
break ;
2015-02-13 22:40:53 +00:00
case eIMAGE_ERROR_FAILED_TO_GET_PATHNAME :
2018-01-14 18:01:22 +00:00
_snprintf (
2015-02-13 22:40:53 +00:00
szBuffer ,
2018-01-14 18:01:22 +00:00
sizeof ( szBuffer ) - 1 ,
2015-02-13 22:40:53 +00:00
TEXT ( " Unable to GetFullPathName() for the file: %s. " ) ,
pszImageFilename ) ;
break ;
2018-01-14 18:01:22 +00:00
case eIMAGE_ERROR_ZEROLENGTH_WRITEPROTECTED :
_snprintf (
szBuffer ,
sizeof ( szBuffer ) - 1 ,
TEXT ( " Unsupported zero-length write-protected file: %s. " ) ,
pszImageFilename ) ;
break ;
case eIMAGE_ERROR_FAILED_TO_INIT_ZEROLENGTH :
_snprintf (
szBuffer ,
sizeof ( szBuffer ) - 1 ,
TEXT ( " Failed to resize the zero-length file: %s. " ) ,
pszImageFilename ) ;
break ;
2006-06-27 02:34:46 +00:00
default :
// IGNORE OTHER ERRORS SILENTLY
return ;
}
2006-02-25 20:50:29 +00:00
2006-06-27 02:34:46 +00:00
MessageBox (
g_hFrameWindow ,
2010-01-03 18:43:08 +00:00
szBuffer ,
2006-06-27 02:34:46 +00:00
g_pAppTitle ,
MB_ICONEXCLAMATION | MB_SETFOREGROUND ) ;
2006-02-25 20:50:29 +00:00
}
2006-02-26 02:02:57 +00:00
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
bool Disk2InterfaceCard : : GetProtect ( const int drive )
2006-03-07 18:14:09 +00:00
{
2019-04-08 16:54:11 +01:00
if ( IsDriveValid ( drive ) )
2006-03-07 18:14:09 +00:00
{
2019-04-14 16:58:49 +01:00
if ( m_floppyDrive [ drive ] . m_disk . m_bWriteProtected )
2006-03-07 18:14:09 +00:00
return true ;
}
2018-02-25 13:38:04 +00:00
2006-03-07 18:14:09 +00:00
return false ;
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : SetProtect ( const int drive , const bool bWriteProtect )
2006-02-26 02:02:57 +00:00
{
2019-04-08 16:54:11 +01:00
if ( IsDriveValid ( drive ) )
2006-02-26 02:02:57 +00:00
{
2019-04-14 16:58:49 +01:00
m_floppyDrive [ drive ] . m_disk . m_bWriteProtected = bWriteProtect ;
2006-02-26 02:02:57 +00:00
}
}
2006-02-25 20:50:29 +00:00
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
bool Disk2InterfaceCard : : IsDiskImageWriteProtected ( const int drive )
2010-01-03 18:43:08 +00:00
{
2019-04-08 16:54:11 +01:00
if ( ! IsDriveValid ( drive ) )
2010-01-03 18:43:08 +00:00
return true ;
2019-04-14 16:58:49 +01:00
return ImageIsWriteProtected ( m_floppyDrive [ drive ] . m_disk . m_imagehandle ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2007-05-28 11:16:42 +00:00
2019-04-14 17:00:15 +01:00
bool Disk2InterfaceCard : : IsDriveEmpty ( const int drive )
2007-05-28 11:16:42 +00:00
{
2019-04-08 16:54:11 +01:00
if ( ! IsDriveValid ( drive ) )
2010-01-03 18:43:08 +00:00
return true ;
2019-04-14 16:58:49 +01:00
return m_floppyDrive [ drive ] . m_disk . m_imagehandle = = NULL ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2018-01-14 18:01:22 +00:00
# if LOG_DISK_NIBBLES_WRITE
2019-04-14 17:00:15 +01:00
bool Disk2InterfaceCard : : LogWriteCheckSyncFF ( ULONG & uCycleDelta )
2018-01-14 18:01:22 +00:00
{
bool bIsSyncFF = false ;
2019-04-07 15:54:26 +01:00
if ( m_uWriteLastCycle = = 0 ) // Reset to 0 when write mode is enabled
2018-01-14 18:01:22 +00:00
{
uCycleDelta = 0 ;
2019-04-07 15:54:26 +01:00
if ( m_floppyLatch = = 0xFF )
2018-01-14 18:01:22 +00:00
{
2019-04-07 15:54:26 +01:00
m_uSyncFFCount = 0 ;
2018-01-14 18:01:22 +00:00
bIsSyncFF = true ;
}
}
else
{
2019-04-07 15:54:26 +01:00
uCycleDelta = ( ULONG ) ( g_nCumulativeCycles - m_uWriteLastCycle ) ;
if ( m_floppyLatch = = 0xFF & & uCycleDelta > 32 )
2018-01-14 18:01:22 +00:00
{
2019-04-07 15:54:26 +01:00
m_uSyncFFCount + + ;
2018-01-14 18:01:22 +00:00
bIsSyncFF = true ;
}
}
2019-04-07 15:54:26 +01:00
m_uWriteLastCycle = g_nCumulativeCycles ;
2018-01-14 18:01:22 +00:00
return bIsSyncFF ;
}
# endif
//===========================================================================
2019-04-14 17:00:15 +01:00
void __stdcall Disk2InterfaceCard : : ReadWrite ( WORD pc , WORD addr , BYTE bWrite , BYTE d , ULONG nExecutedCycles )
2006-02-25 20:50:29 +00:00
{
2019-04-07 15:54:26 +01:00
/* m_floppyLoadMode = 0; */
2019-04-14 16:47:41 +01:00
FloppyDrive * pDrive = & m_floppyDrive [ m_currDrive ] ;
2019-04-14 16:58:49 +01:00
FloppyDisk * pFloppy = & pDrive - > m_disk ;
2010-01-03 18:43:08 +00:00
2019-04-14 16:58:49 +01:00
if ( ! pFloppy - > m_trackimagedata & & pFloppy - > m_imagehandle )
2019-04-07 15:54:26 +01:00
ReadTrack ( m_currDrive ) ;
2010-01-03 18:43:08 +00:00
2019-04-14 16:58:49 +01:00
if ( ! pFloppy - > m_trackimagedata )
2010-01-03 18:43:08 +00:00
{
2019-04-07 15:54:26 +01:00
m_floppyLatch = 0xFF ;
2015-09-13 11:39:58 +01:00
return ;
2010-01-03 18:43:08 +00:00
}
2006-02-25 20:50:29 +00:00
2018-01-14 18:01:22 +00:00
// Improve precision of "authentic" drive mode - GH#125
UINT uSpinNibbleCount = 0 ;
2018-03-03 21:27:50 +00:00
CpuCalcCycles ( nExecutedCycles ) ; // g_nCumulativeCycles required for uSpinNibbleCount & LogWriteCheckSyncFF()
2018-01-14 18:01:22 +00:00
2019-04-14 16:47:41 +01:00
if ( ! m_enhanceDisk & & pDrive - > m_spinning )
2018-01-14 18:01:22 +00:00
{
2019-04-07 15:54:26 +01:00
const ULONG nCycleDiff = ( ULONG ) ( g_nCumulativeCycles - m_diskLastCycle ) ;
m_diskLastCycle = g_nCumulativeCycles ;
2018-01-14 18:01:22 +00:00
if ( nCycleDiff > 40 )
{
// 40 cycles for a write of a 10-bit 0xFF sync byte
uSpinNibbleCount = nCycleDiff > > 5 ; // ...but divide by 32 (not 40)
2019-04-14 16:58:49 +01:00
ULONG uWrapOffset = uSpinNibbleCount % pFloppy - > m_nibbles ;
pFloppy - > m_byte + = uWrapOffset ;
if ( pFloppy - > m_byte > = pFloppy - > m_nibbles )
pFloppy - > m_byte - = pFloppy - > m_nibbles ;
2018-01-14 18:01:22 +00:00
# if LOG_DISK_NIBBLES_SPIN
2019-04-14 16:58:49 +01:00
UINT uCompleteRevolutions = uSpinNibbleCount / pFloppy - > m_nibbles ;
2018-01-14 18:01:22 +00:00
LOG_DISK ( " spin: revs=%d, nibbles=%d \r \n " , uCompleteRevolutions , uWrapOffset ) ;
# endif
}
}
2019-04-07 15:54:26 +01:00
if ( ! m_floppyWriteMode )
2015-09-13 11:39:58 +01:00
{
2018-12-03 17:38:52 +00:00
// Don't change latch if drive off after 1 second drive-off delay (UTAIIe page 9-13)
// "DRIVES OFF forces the data register to hold its present state." (UTAIIe page 9-12)
// Note: Sherwood Forest sets shift mode and reads with the drive off.
2019-04-14 16:47:41 +01:00
if ( ! pDrive - > m_spinning ) // GH#599
2018-12-03 17:38:52 +00:00
return ;
2019-04-07 15:54:26 +01:00
const ULONG nReadCycleDiff = ( ULONG ) ( g_nCumulativeCycles - m_diskLastReadLatchCycle ) ;
2018-10-02 22:08:54 +01:00
// Support partial nibble read if disk reads are very close: (GH#582)
// . 6 cycles (1st->2nd read) for DOS 3.3 / $BD34: "read with delays to see if disk is spinning." (Beneath Apple DOS)
// . 6 cycles (1st->2nd read) for Curse of the Azure Bonds (loop to see if disk is spinning)
// . 31 cycles is the max for a partial 8-bit nibble
2019-04-07 15:54:26 +01:00
const ULONG kReadAccessThreshold = m_enhanceDisk ? 6 : 31 ;
2018-10-02 22:08:54 +01:00
if ( nReadCycleDiff < = kReadAccessThreshold )
{
UINT invalidBits = 8 - ( nReadCycleDiff / 4 ) ; // 4 cycles per bit-cell
2019-04-14 16:58:49 +01:00
m_floppyLatch = * ( pFloppy - > m_trackimage + pFloppy - > m_byte ) > > invalidBits ;
2019-04-07 15:54:26 +01:00
return ; // Early return so don't update: m_diskLastReadLatchCycle & pFloppy->byte
2018-10-02 22:08:54 +01:00
}
2019-04-14 16:58:49 +01:00
m_floppyLatch = * ( pFloppy - > m_trackimage + pFloppy - > m_byte ) ;
2019-04-07 15:54:26 +01:00
m_diskLastReadLatchCycle = g_nCumulativeCycles ;
2018-01-14 18:01:22 +00:00
# if LOG_DISK_NIBBLES_READ
# if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
2019-04-07 15:54:26 +01:00
if ( m_bLogDisk_NibblesRW )
2018-01-14 18:01:22 +00:00
# endif
{
2019-04-14 16:58:49 +01:00
LOG_DISK ( " read %04X = %02X \r \n " , pFloppy - > m_byte , m_floppyLatch ) ;
2018-01-14 18:01:22 +00:00
}
2019-04-07 15:54:26 +01:00
m_formatTrack . DecodeLatchNibbleRead ( m_floppyLatch ) ;
2015-09-13 11:39:58 +01:00
# endif
}
2019-04-14 16:58:49 +01:00
else if ( ! pFloppy - > m_bWriteProtected ) // && m_floppyWriteMode
2015-09-13 11:39:58 +01:00
{
2019-04-14 16:58:49 +01:00
* ( pFloppy - > m_trackimage + pFloppy - > m_byte ) = m_floppyLatch ;
pFloppy - > m_trackimagedirty = true ;
2018-01-14 18:01:22 +00:00
2018-02-15 21:54:15 +00:00
bool bIsSyncFF = false ;
# if LOG_DISK_NIBBLES_WRITE
ULONG uCycleDelta = 0 ;
2018-02-24 13:18:26 +00:00
bIsSyncFF = LogWriteCheckSyncFF ( uCycleDelta ) ;
2018-02-15 21:54:15 +00:00
# endif
2019-04-07 15:54:26 +01:00
m_formatTrack . DecodeLatchNibbleWrite ( m_floppyLatch , uSpinNibbleCount , pFloppy , bIsSyncFF ) ; // GH#125
2018-01-14 18:01:22 +00:00
# if LOG_DISK_NIBBLES_WRITE
# if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
2019-04-07 15:54:26 +01:00
if ( m_bLogDisk_NibblesRW )
2018-01-14 18:01:22 +00:00
# endif
{
2018-02-15 21:54:15 +00:00
if ( ! bIsSyncFF )
2019-04-14 16:58:49 +01:00
LOG_DISK ( " write %04X = %02X (cy=+%d) \r \n " , pFloppy - > m_byte , m_floppyLatch , uCycleDelta ) ;
2018-01-14 18:01:22 +00:00
else
2019-04-14 16:58:49 +01:00
LOG_DISK ( " write %04X = %02X (cy=+%d) sync #%d \r \n " , pFloppy - > m_byte , m_floppyLatch , uCycleDelta , m_uSyncFFCount ) ;
2018-01-14 18:01:22 +00:00
}
# endif
2015-09-13 11:39:58 +01:00
}
2006-02-25 20:50:29 +00:00
2019-04-14 16:58:49 +01:00
if ( + + pFloppy - > m_byte > = pFloppy - > m_nibbles )
pFloppy - > m_byte = 0 ;
2006-02-25 20:50:29 +00:00
2018-12-03 17:38:52 +00:00
// Show track status (GH#201) - NB. Prevent flooding of forcing UI to redraw!!!
2019-04-14 16:58:49 +01:00
if ( ( pFloppy - > m_byte & 0xFF ) = = 0 )
2017-08-11 21:45:07 +01:00
FrameDrawDiskStatus ( ( HDC ) 0 ) ;
2010-01-03 18:43:08 +00:00
}
//===========================================================================
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : Reset ( const bool bIsPowerCycle /*=false*/ )
2010-01-03 18:43:08 +00:00
{
2017-05-14 11:33:07 +01:00
// RESET forces all switches off (UTAIIe Table 9.1)
2019-04-07 15:54:26 +01:00
m_currDrive = 0 ;
m_floppyMotorOn = 0 ;
m_floppyLoadMode = 0 ;
m_floppyWriteMode = 0 ;
m_phases = 0 ;
2017-08-11 21:45:07 +01:00
2019-04-07 15:54:26 +01:00
m_formatTrack . Reset ( ) ;
2018-01-14 18:01:22 +00:00
2017-08-12 11:50:31 +01:00
if ( bIsPowerCycle ) // GH#460
2017-08-11 21:45:07 +01:00
{
2018-10-02 22:08:54 +01:00
// NB. This doesn't affect the drive head (ie. drive's track position)
// . The initial machine start-up state is track=0, but after a power-cycle the track could be any value.
// . (For DiskII firmware, this results in a subtle extra latch read in this latter case, for the track!=0 case)
2019-04-14 16:47:41 +01:00
m_floppyDrive [ DRIVE_1 ] . m_spinning = 0 ;
m_floppyDrive [ DRIVE_1 ] . m_writelight = 0 ;
m_floppyDrive [ DRIVE_2 ] . m_spinning = 0 ;
m_floppyDrive [ DRIVE_2 ] . m_writelight = 0 ;
2017-08-11 21:45:07 +01:00
2018-01-14 18:01:22 +00:00
FrameRefreshStatus ( DRAW_LEDS , false ) ;
2017-08-11 21:45:07 +01:00
}
2010-01-03 18:43:08 +00:00
}
//===========================================================================
2019-04-14 17:00:15 +01:00
bool Disk2InterfaceCard : : UserSelectNewDiskImage ( const int drive , LPCSTR pszFilename /*=""*/ )
2010-01-03 18:43:08 +00:00
{
TCHAR directory [ MAX_PATH ] = TEXT ( " " ) ;
2010-01-17 18:43:06 +00:00
TCHAR filename [ MAX_PATH ] = TEXT ( " " ) ;
2010-01-03 18:43:08 +00:00
TCHAR title [ 40 ] ;
strcpy ( filename , pszFilename ) ;
2010-01-17 18:43:06 +00:00
RegLoadString ( TEXT ( REG_PREFS ) , REGVALUE_PREF_START_DIR , 1 , directory , MAX_PATH ) ;
2010-01-03 18:43:08 +00:00
_tcscpy ( title , TEXT ( " Select Disk Image For Drive " ) ) ;
2019-04-08 16:54:11 +01:00
_tcscat ( title , drive ? TEXT ( " 2 " ) : TEXT ( " 1 " ) ) ;
2010-01-03 18:43:08 +00:00
2010-12-30 20:10:48 +00:00
_ASSERT ( sizeof ( OPENFILENAME ) = = sizeof ( OPENFILENAME_NT4 ) ) ; // Required for Win98/ME support (selected by _WIN32_WINNT=0x0400 in stdafx.h)
2010-01-03 18:43:08 +00:00
OPENFILENAME ofn ;
ZeroMemory ( & ofn , sizeof ( OPENFILENAME ) ) ;
ofn . lStructSize = sizeof ( OPENFILENAME ) ;
ofn . hwndOwner = g_hFrameWindow ;
ofn . hInstance = g_hInstance ;
2010-01-17 18:43:06 +00:00
ofn . lpstrFilter = TEXT ( " All Images \0 *.bin;*.do;*.dsk;*.nib;*.po;*.gz;*.zip;*.2mg;*.2img;*.iie;*.apl \0 " )
TEXT ( " Disk Images (*.bin,*.do,*.dsk,*.nib,*.po,*.gz,*.zip,*.2mg,*.2img,*.iie) \0 *.bin;*.do;*.dsk;*.nib;*.po;*.gz;*.zip;*.2mg;*.2img;*.iie \0 " )
TEXT ( " All Files \0 *.* \0 " ) ;
2010-01-03 18:43:08 +00:00
ofn . lpstrFile = filename ;
ofn . nMaxFile = MAX_PATH ;
ofn . lpstrInitialDir = directory ;
ofn . Flags = OFN_PATHMUSTEXIST ;
ofn . lpstrTitle = title ;
2006-02-25 20:50:29 +00:00
2015-02-13 22:40:53 +00:00
bool bRes = false ;
2008-08-31 04:31:35 +00:00
if ( GetOpenFileName ( & ofn ) )
2006-02-25 20:50:29 +00:00
{
2008-08-31 04:31:35 +00:00
if ( ( ! ofn . nFileExtension ) | | ! filename [ ofn . nFileExtension ] )
2010-01-17 18:43:06 +00:00
_tcscat ( filename , TEXT ( " .dsk " ) ) ;
2008-08-31 04:31:35 +00:00
2019-04-08 16:54:11 +01:00
ImageError_e Error = InsertDisk ( drive , filename , ofn . Flags & OFN_READONLY , IMAGE_CREATE ) ;
2010-01-03 18:43:08 +00:00
if ( Error = = eIMAGE_ERROR_NONE )
2008-08-31 04:31:35 +00:00
{
2015-02-13 22:40:53 +00:00
bRes = true ;
2008-08-31 04:31:35 +00:00
}
else
{
2019-04-08 16:54:11 +01:00
NotifyInvalidImage ( drive , filename , Error ) ;
2008-08-31 04:31:35 +00:00
}
2006-02-25 20:50:29 +00:00
}
2015-02-13 22:40:53 +00:00
return bRes ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void __stdcall Disk2InterfaceCard : : LoadWriteProtect ( WORD , WORD , BYTE write , BYTE value , ULONG )
2018-02-11 14:52:21 +00:00
{
2019-04-07 15:54:26 +01:00
/* m_floppyLoadMode = 1; */
2018-12-03 17:38:52 +00:00
// Don't change latch if drive off after 1 second drive-off delay (UTAIIe page 9-13)
// "DRIVES OFF forces the data register to hold its present state." (UTAIIe page 9-12)
// Note: Gemstone Warrior sets load mode with the drive off.
2019-04-14 16:47:41 +01:00
if ( ! m_floppyDrive [ m_currDrive ] . m_spinning ) // GH#599
2018-12-03 17:38:52 +00:00
return ;
2015-09-13 11:39:58 +01:00
if ( ! write )
{
2018-02-11 14:52:21 +00:00
// Notes:
// . Phase 1 on also forces write protect in the Disk II drive (UTAIIe page 9-7) but we don't implement that
// . write mode doesn't prevent reading write protect (GH#537):
// "If for some reason the above write protect check were entered with the READ/WRITE switch in WRITE,
// the write protect switch would still be read correctly" (UTAIIe page 9-21)
2019-04-14 16:58:49 +01:00
if ( m_floppyDrive [ m_currDrive ] . m_disk . m_bWriteProtected )
2019-04-07 15:54:26 +01:00
m_floppyLatch | = 0x80 ;
2018-02-11 14:52:21 +00:00
else
2019-04-07 15:54:26 +01:00
m_floppyLatch & = 0x7F ;
2015-09-13 11:39:58 +01:00
}
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void __stdcall Disk2InterfaceCard : : SetReadMode ( WORD , WORD , BYTE , BYTE , ULONG )
2010-01-03 18:43:08 +00:00
{
2019-04-07 15:54:26 +01:00
m_floppyWriteMode = 0 ;
2018-01-14 18:01:22 +00:00
2019-04-14 16:47:41 +01:00
m_formatTrack . DriveSwitchedToReadMode ( & m_floppyDrive [ m_currDrive ] . m_disk ) ;
2018-01-14 18:01:22 +00:00
# if LOG_DISK_RW_MODE
LOG_DISK ( " rw mode: read \r \n " ) ;
# endif
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void __stdcall Disk2InterfaceCard : : SetWriteMode ( WORD , WORD , BYTE , BYTE , ULONG uExecutedCycles )
2010-01-03 18:43:08 +00:00
{
2019-04-07 15:54:26 +01:00
m_floppyWriteMode = 1 ;
2018-01-14 18:01:22 +00:00
2019-04-14 16:58:49 +01:00
m_formatTrack . DriveSwitchedToWriteMode ( m_floppyDrive [ m_currDrive ] . m_disk . m_byte ) ;
2018-01-14 18:01:22 +00:00
2019-04-14 16:47:41 +01:00
BOOL modechange = ! m_floppyDrive [ m_currDrive ] . m_writelight ;
2018-01-14 18:01:22 +00:00
# if LOG_DISK_RW_MODE
LOG_DISK ( " rw mode: write (mode changed=%d) \r \n " , modechange ? 1 : 0 ) ;
# endif
# if LOG_DISK_NIBBLES_WRITE
2019-04-07 15:54:26 +01:00
m_uWriteLastCycle = 0 ;
2018-01-14 18:01:22 +00:00
# endif
2019-04-14 16:47:41 +01:00
m_floppyDrive [ m_currDrive ] . m_writelight = WRITELIGHT_CYCLES ;
2018-01-14 18:01:22 +00:00
2010-01-03 18:43:08 +00:00
if ( modechange )
2014-07-23 18:08:52 -07:00
FrameDrawDiskLEDS ( ( HDC ) 0 ) ;
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2010-01-03 18:43:08 +00:00
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : UpdateDriveState ( DWORD cycles )
2010-01-03 18:43:08 +00:00
{
int loop = NUM_DRIVES ;
while ( loop - - )
{
2019-04-14 16:47:41 +01:00
FloppyDrive * pDrive = & m_floppyDrive [ loop ] ;
2010-01-03 18:43:08 +00:00
2019-04-14 16:47:41 +01:00
if ( pDrive - > m_spinning & & ! m_floppyMotorOn )
2018-01-14 18:01:22 +00:00
{
2019-04-14 16:47:41 +01:00
if ( ! ( pDrive - > m_spinning - = MIN ( pDrive - > m_spinning , cycles ) ) )
2014-07-23 18:08:52 -07:00
{
FrameDrawDiskLEDS ( ( HDC ) 0 ) ;
FrameDrawDiskStatus ( ( HDC ) 0 ) ;
}
2010-01-03 18:43:08 +00:00
}
2019-04-14 16:47:41 +01:00
if ( m_floppyWriteMode & & ( m_currDrive = = loop ) & & pDrive - > m_spinning )
2010-01-03 18:43:08 +00:00
{
2019-04-14 16:47:41 +01:00
pDrive - > m_writelight = WRITELIGHT_CYCLES ;
2010-01-03 18:43:08 +00:00
}
2019-04-14 16:47:41 +01:00
else if ( pDrive - > m_writelight )
2010-01-03 18:43:08 +00:00
{
2019-04-14 16:47:41 +01:00
if ( ! ( pDrive - > m_writelight - = MIN ( pDrive - > m_writelight , cycles ) ) )
2014-07-23 18:08:52 -07:00
{
FrameDrawDiskLEDS ( ( HDC ) 0 ) ;
FrameDrawDiskStatus ( ( HDC ) 0 ) ;
}
2010-01-03 18:43:08 +00:00
}
}
2006-02-25 20:50:29 +00:00
}
//===========================================================================
2019-04-14 17:00:15 +01:00
bool Disk2InterfaceCard : : DriveSwap ( void )
2006-02-25 20:50:29 +00:00
{
// Refuse to swap if either Disk][ is active
2017-05-11 08:34:57 -07:00
// TODO: if Shift-Click then FORCE drive swap to bypass message
2019-04-14 16:47:41 +01:00
if ( m_floppyDrive [ DRIVE_1 ] . m_spinning | | m_floppyDrive [ DRIVE_2 ] . m_spinning )
2017-05-11 08:34:57 -07:00
{
// 1.26.2.4 Prompt when trying to swap disks while drive is on instead of silently failing
int status = MessageBox (
g_hFrameWindow ,
" WARNING: \n "
" \n "
" \t Attempting to swap a disk while a drive is on \n "
" \t \t --> is NOT recommended <-- \n "
" \t as this will most likely read/write incorrect data! \n "
" \n "
" If the other drive is empty then swapping is harmless. The "
2017-05-22 22:04:34 +01:00
" computer will appear to 'hang' trying to read non-existent data but "
2017-05-11 08:34:57 -07:00
" you can safely swap disks once more to restore the original disk. \n "
" \n "
" Do you still wish to swap disks? " ,
" Trying to swap a disk while a drive is on ... " ,
MB_ICONWARNING | MB_YESNOCANCEL
) ;
switch ( status )
{
case IDNO :
case IDCANCEL :
return false ;
default :
break ; // User is OK with swapping disks so let them proceed at their own risk
}
}
2006-02-25 20:50:29 +00:00
2019-04-08 10:41:47 +01:00
FlushCurrentTrack ( DRIVE_1 ) ;
FlushCurrentTrack ( DRIVE_2 ) ;
2018-04-02 18:21:18 +01:00
2006-02-25 20:50:29 +00:00
// Swap disks between drives
2013-12-06 21:10:41 +00:00
// . NB. We swap trackimage ptrs (so don't need to swap the buffers' data)
2019-04-14 16:47:41 +01:00
std : : swap ( m_floppyDrive [ DRIVE_1 ] . m_disk , m_floppyDrive [ DRIVE_2 ] . m_disk ) ;
2018-04-02 18:21:18 +01:00
// Invalidate the trackimage so that a read latch will re-read the track for the new floppy (GH#543)
2019-04-14 16:58:49 +01:00
m_floppyDrive [ DRIVE_1 ] . m_disk . m_trackimagedata = false ;
m_floppyDrive [ DRIVE_2 ] . m_disk . m_trackimagedata = false ;
2006-02-25 20:50:29 +00:00
2019-04-08 10:41:47 +01:00
SaveLastDiskImage ( DRIVE_1 ) ;
SaveLastDiskImage ( DRIVE_2 ) ;
2006-02-25 20:50:29 +00:00
2018-01-14 18:01:22 +00:00
FrameRefreshStatus ( DRAW_LEDS | DRAW_BUTTON_DRIVES , false ) ;
2006-02-25 20:50:29 +00:00
return true ;
}
//===========================================================================
2008-09-04 16:42:14 +00:00
// TODO: LoadRom_Disk_Floppy()
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : Initialize ( LPBYTE pCxRomPeripheral , UINT uSlot )
2007-05-28 11:16:42 +00:00
{
2008-09-04 16:42:14 +00:00
const UINT DISK2_FW_SIZE = APPLE_SLOT_SIZE ;
2007-05-28 11:16:42 +00:00
HRSRC hResInfo = FindResource ( NULL , MAKEINTRESOURCE ( IDR_DISK2_FW ) , " FIRMWARE " ) ;
if ( hResInfo = = NULL )
return ;
DWORD dwResSize = SizeofResource ( NULL , hResInfo ) ;
if ( dwResSize ! = DISK2_FW_SIZE )
return ;
HGLOBAL hResData = LoadResource ( NULL , hResInfo ) ;
if ( hResData = = NULL )
return ;
BYTE * pData = ( BYTE * ) LockResource ( hResData ) ; // NB. Don't need to unlock resource
if ( pData = = NULL )
return ;
2009-01-17 15:10:00 +00:00
memcpy ( pCxRomPeripheral + uSlot * APPLE_SLOT_SIZE , pData , DISK2_FW_SIZE ) ;
2007-05-28 11:16:42 +00:00
2015-09-13 11:39:58 +01:00
// Note: We used to disable the track stepping delay in the Disk II controller firmware by
2009-02-01 10:33:37 +00:00
// patching $C64C with $A9,$00,$EA. Now not doing this since:
// . Authentic Speed should be authentic
// . Enhanced Speed runs emulation unthrottled, so removing the delay has negligible effect
2009-02-03 02:24:50 +00:00
// . Patching the firmware breaks the ADC checksum used by "The CIA Files" (Tricky Dick)
2015-09-13 11:39:58 +01:00
// . In this case we can patch to compensate for an ADC or EOR checksum but not both (nickw)
2007-05-28 11:16:42 +00:00
2019-04-14 17:00:15 +01:00
RegisterIoHandler ( uSlot , & Disk2InterfaceCard : : IORead , & Disk2InterfaceCard : : IOWrite , NULL , NULL , this , NULL ) ;
2015-02-13 22:40:53 +00:00
2019-04-07 15:54:26 +01:00
m_slot = uSlot ;
2007-05-28 11:16:42 +00:00
}
//===========================================================================
2019-04-14 17:00:15 +01:00
BYTE __stdcall Disk2InterfaceCard : : IORead ( WORD pc , WORD addr , BYTE bWrite , BYTE d , ULONG nExecutedCycles )
2007-05-28 11:16:42 +00:00
{
2019-04-07 14:22:05 +01:00
UINT uSlot = ( ( addr & 0xff ) > > 4 ) - 8 ;
2019-04-14 17:00:15 +01:00
Disk2InterfaceCard * pCard = ( Disk2InterfaceCard * ) MemGetSlotParameters ( uSlot ) ;
2019-04-07 14:22:05 +01:00
2015-09-13 11:39:58 +01:00
switch ( addr & 0xF )
2007-05-28 11:16:42 +00:00
{
2019-04-08 10:41:47 +01:00
case 0x0 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x1 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x2 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x3 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x4 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x5 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x6 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x7 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x8 : pCard - > ControlMotor ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x9 : pCard - > ControlMotor ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xA : pCard - > Enable ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xB : pCard - > Enable ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xC : pCard - > ReadWrite ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xD : pCard - > LoadWriteProtect ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xE : pCard - > SetReadMode ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xF : pCard - > SetWriteMode ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
2007-05-28 11:16:42 +00:00
}
2017-05-14 11:33:07 +01:00
// only even addresses return the latch (UTAIIe Table 9.1)
2015-09-13 11:39:58 +01:00
if ( ! ( addr & 1 ) )
2019-04-07 15:54:26 +01:00
return pCard - > m_floppyLatch ;
2015-09-13 11:39:58 +01:00
else
2018-03-03 21:27:50 +00:00
return MemReadFloatingBus ( nExecutedCycles ) ;
2007-05-28 11:16:42 +00:00
}
2019-04-14 17:00:15 +01:00
BYTE __stdcall Disk2InterfaceCard : : IOWrite ( WORD pc , WORD addr , BYTE bWrite , BYTE d , ULONG nExecutedCycles )
2007-05-28 11:16:42 +00:00
{
2019-04-07 14:22:05 +01:00
UINT uSlot = ( ( addr & 0xff ) > > 4 ) - 8 ;
2019-04-14 17:00:15 +01:00
Disk2InterfaceCard * pCard = ( Disk2InterfaceCard * ) MemGetSlotParameters ( uSlot ) ;
2019-04-07 14:22:05 +01:00
2015-09-13 11:39:58 +01:00
switch ( addr & 0xF )
2007-05-28 11:16:42 +00:00
{
2019-04-08 10:41:47 +01:00
case 0x0 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x1 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x2 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x3 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x4 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x5 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x6 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x7 : pCard - > ControlStepper ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x8 : pCard - > ControlMotor ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0x9 : pCard - > ControlMotor ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xA : pCard - > Enable ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xB : pCard - > Enable ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xC : pCard - > ReadWrite ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xD : pCard - > LoadWriteProtect ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xE : pCard - > SetReadMode ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
case 0xF : pCard - > SetWriteMode ( pc , addr , bWrite , d , nExecutedCycles ) ; break ;
2007-05-28 11:16:42 +00:00
}
2015-09-13 11:39:58 +01:00
// any address writes the latch via sequencer LD command (74LS323 datasheet)
2019-04-07 15:54:26 +01:00
if ( pCard - > m_floppyWriteMode /* && m_floppyLoadMode */ )
2015-09-13 11:39:58 +01:00
{
2019-04-07 15:54:26 +01:00
pCard - > m_floppyLatch = d ;
2015-09-13 11:39:58 +01:00
}
2007-05-28 11:16:42 +00:00
return 0 ;
}
//===========================================================================
2019-02-02 15:51:27 +00:00
// Unit version history:
2018-01-14 18:01:22 +00:00
// 2: Added: Format Track state & DiskLastCycle
2018-10-02 22:08:54 +01:00
// 3: Added: DiskLastReadLatchCycle
static const UINT kUNIT_VERSION = 3 ;
2018-01-14 18:01:22 +00:00
2015-12-05 16:50:27 +00:00
# define SS_YAML_VALUE_CARD_DISK2 "Disk]["
# define SS_YAML_KEY_PHASES "Phases"
# define SS_YAML_KEY_CURRENT_DRIVE "Current Drive"
# define SS_YAML_KEY_DISK_ACCESSED "Disk Accessed"
# define SS_YAML_KEY_ENHANCE_DISK "Enhance Disk"
# define SS_YAML_KEY_FLOPPY_LATCH "Floppy Latch"
# define SS_YAML_KEY_FLOPPY_MOTOR_ON "Floppy Motor On"
# define SS_YAML_KEY_FLOPPY_WRITE_MODE "Floppy Write Mode"
2018-01-14 18:01:22 +00:00
# define SS_YAML_KEY_LAST_CYCLE "Last Cycle"
2018-10-02 22:08:54 +01:00
# define SS_YAML_KEY_LAST_READ_LATCH_CYCLE "Last Read Latch Cycle"
2015-12-05 16:50:27 +00:00
2016-02-15 22:33:38 +00:00
# define SS_YAML_KEY_DISK2UNIT "Unit"
2015-12-05 16:50:27 +00:00
# define SS_YAML_KEY_FILENAME "Filename"
# define SS_YAML_KEY_TRACK "Track"
# define SS_YAML_KEY_PHASE "Phase"
# define SS_YAML_KEY_BYTE "Byte"
# define SS_YAML_KEY_WRITE_PROTECTED "Write Protected"
# define SS_YAML_KEY_SPINNING "Spinning"
# define SS_YAML_KEY_WRITE_LIGHT "Write Light"
# define SS_YAML_KEY_NIBBLES "Nibbles"
# define SS_YAML_KEY_TRACK_IMAGE_DATA "Track Image Data"
# define SS_YAML_KEY_TRACK_IMAGE_DIRTY "Track Image Dirty"
# define SS_YAML_KEY_TRACK_IMAGE "Track Image"
2019-04-14 17:00:15 +01:00
std : : string Disk2InterfaceCard : : GetSnapshotCardName ( void )
2015-12-05 16:50:27 +00:00
{
static const std : : string name ( SS_YAML_VALUE_CARD_DISK2 ) ;
return name ;
}
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : SaveSnapshotDisk2Unit ( YamlSaveHelper & yamlSaveHelper , UINT unit )
2015-12-05 16:50:27 +00:00
{
YamlSaveHelper : : Label label ( yamlSaveHelper , " %s%d: \n " , SS_YAML_KEY_DISK2UNIT , unit ) ;
2019-04-14 16:58:49 +01:00
yamlSaveHelper . SaveString ( SS_YAML_KEY_FILENAME , m_floppyDrive [ unit ] . m_disk . m_fullname ) ;
2019-04-14 16:47:41 +01:00
yamlSaveHelper . SaveUint ( SS_YAML_KEY_TRACK , m_floppyDrive [ unit ] . m_track ) ;
yamlSaveHelper . SaveUint ( SS_YAML_KEY_PHASE , m_floppyDrive [ unit ] . m_phase ) ;
2019-04-14 16:58:49 +01:00
yamlSaveHelper . SaveHexUint16 ( SS_YAML_KEY_BYTE , m_floppyDrive [ unit ] . m_disk . m_byte ) ;
yamlSaveHelper . SaveBool ( SS_YAML_KEY_WRITE_PROTECTED , m_floppyDrive [ unit ] . m_disk . m_bWriteProtected ) ;
2019-04-14 16:47:41 +01:00
yamlSaveHelper . SaveUint ( SS_YAML_KEY_SPINNING , m_floppyDrive [ unit ] . m_spinning ) ;
yamlSaveHelper . SaveUint ( SS_YAML_KEY_WRITE_LIGHT , m_floppyDrive [ unit ] . m_writelight ) ;
2019-04-14 16:58:49 +01:00
yamlSaveHelper . SaveHexUint16 ( SS_YAML_KEY_NIBBLES , m_floppyDrive [ unit ] . m_disk . m_nibbles ) ;
yamlSaveHelper . SaveUint ( SS_YAML_KEY_TRACK_IMAGE_DATA , m_floppyDrive [ unit ] . m_disk . m_trackimagedata ) ;
yamlSaveHelper . SaveUint ( SS_YAML_KEY_TRACK_IMAGE_DIRTY , m_floppyDrive [ unit ] . m_disk . m_trackimagedirty ) ;
2019-04-14 16:47:41 +01:00
2019-04-14 16:58:49 +01:00
if ( m_floppyDrive [ unit ] . m_disk . m_trackimage )
2015-12-05 16:50:27 +00:00
{
YamlSaveHelper : : Label image ( yamlSaveHelper , " %s: \n " , SS_YAML_KEY_TRACK_IMAGE ) ;
2019-04-14 16:58:49 +01:00
yamlSaveHelper . SaveMemory ( m_floppyDrive [ unit ] . m_disk . m_trackimage , NIBBLES_PER_TRACK ) ;
2015-12-05 16:50:27 +00:00
}
}
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : SaveSnapshot ( class YamlSaveHelper & yamlSaveHelper )
2015-12-05 16:50:27 +00:00
{
2019-04-08 10:41:47 +01:00
YamlSaveHelper : : Slot slot ( yamlSaveHelper , GetSnapshotCardName ( ) , m_slot , kUNIT_VERSION ) ;
2015-12-05 16:50:27 +00:00
YamlSaveHelper : : Label state ( yamlSaveHelper , " %s: \n " , SS_YAML_KEY_STATE ) ;
2019-04-07 15:54:26 +01:00
yamlSaveHelper . SaveHexUint4 ( SS_YAML_KEY_PHASES , m_phases ) ;
yamlSaveHelper . SaveUint ( SS_YAML_KEY_CURRENT_DRIVE , m_currDrive ) ;
2018-01-14 18:01:22 +00:00
yamlSaveHelper . SaveBool ( SS_YAML_KEY_DISK_ACCESSED , false ) ; // deprecated
2019-04-07 15:54:26 +01:00
yamlSaveHelper . SaveBool ( SS_YAML_KEY_ENHANCE_DISK , m_enhanceDisk ) ;
yamlSaveHelper . SaveHexUint8 ( SS_YAML_KEY_FLOPPY_LATCH , m_floppyLatch ) ;
yamlSaveHelper . SaveBool ( SS_YAML_KEY_FLOPPY_MOTOR_ON , m_floppyMotorOn = = TRUE ) ;
yamlSaveHelper . SaveBool ( SS_YAML_KEY_FLOPPY_WRITE_MODE , m_floppyWriteMode = = TRUE ) ;
yamlSaveHelper . SaveHexUint64 ( SS_YAML_KEY_LAST_CYCLE , m_diskLastCycle ) ; // v2
yamlSaveHelper . SaveHexUint64 ( SS_YAML_KEY_LAST_READ_LATCH_CYCLE , m_diskLastReadLatchCycle ) ; // v3
m_formatTrack . SaveSnapshot ( yamlSaveHelper ) ; // v2
2015-12-05 16:50:27 +00:00
2019-04-08 10:41:47 +01:00
SaveSnapshotDisk2Unit ( yamlSaveHelper , DRIVE_1 ) ;
SaveSnapshotDisk2Unit ( yamlSaveHelper , DRIVE_2 ) ;
2015-12-05 16:50:27 +00:00
}
2019-04-14 17:00:15 +01:00
void Disk2InterfaceCard : : LoadSnapshotDriveUnit ( YamlLoadHelper & yamlLoadHelper , UINT unit )
2015-12-05 16:50:27 +00:00
{
std : : string disk2UnitName = std : : string ( SS_YAML_KEY_DISK2UNIT ) + ( unit = = DRIVE_1 ? std : : string ( " 0 " ) : std : : string ( " 1 " ) ) ;
if ( ! yamlLoadHelper . GetSubMap ( disk2UnitName ) )
throw std : : string ( " Card: Expected key: " ) + disk2UnitName ;
bool bImageError = false ;
2019-04-14 16:58:49 +01:00
m_floppyDrive [ unit ] . m_disk . m_fullname [ 0 ] = 0 ;
m_floppyDrive [ unit ] . m_disk . m_imagename [ 0 ] = 0 ;
m_floppyDrive [ unit ] . m_disk . m_bWriteProtected = false ; // Default to false (until image is successfully loaded below)
2016-02-24 21:51:20 +00:00
2016-02-24 22:38:59 +00:00
std : : string filename = yamlLoadHelper . LoadString ( SS_YAML_KEY_FILENAME ) ;
2015-12-05 16:50:27 +00:00
if ( ! filename . empty ( ) )
{
DWORD dwAttributes = GetFileAttributes ( filename . c_str ( ) ) ;
if ( dwAttributes = = INVALID_FILE_ATTRIBUTES )
{
// Get user to browse for file
2019-04-08 10:41:47 +01:00
UserSelectNewDiskImage ( unit , filename . c_str ( ) ) ;
2015-12-05 16:50:27 +00:00
dwAttributes = GetFileAttributes ( filename . c_str ( ) ) ;
}
bImageError = ( dwAttributes = = INVALID_FILE_ATTRIBUTES ) ;
if ( ! bImageError )
{
2019-04-08 10:41:47 +01:00
if ( InsertDisk ( unit , filename . c_str ( ) , dwAttributes & FILE_ATTRIBUTE_READONLY , IMAGE_DONT_CREATE ) ! = eIMAGE_ERROR_NONE )
2015-12-05 16:50:27 +00:00
bImageError = true ;
2019-04-07 15:54:26 +01:00
// DiskInsert() zeros m_floppyDrive[unit], then sets up:
2015-12-05 16:50:27 +00:00
// . imagename
// . fullname
// . writeprotected
}
}
2019-04-14 16:47:41 +01:00
m_floppyDrive [ unit ] . m_track = yamlLoadHelper . LoadUint ( SS_YAML_KEY_TRACK ) ;
m_floppyDrive [ unit ] . m_phase = yamlLoadHelper . LoadUint ( SS_YAML_KEY_PHASE ) ;
2019-04-14 16:58:49 +01:00
m_floppyDrive [ unit ] . m_disk . m_byte = yamlLoadHelper . LoadUint ( SS_YAML_KEY_BYTE ) ;
2016-03-01 22:31:17 +00:00
yamlLoadHelper . LoadBool ( SS_YAML_KEY_WRITE_PROTECTED ) ; // Consume
2019-04-14 16:47:41 +01:00
m_floppyDrive [ unit ] . m_spinning = yamlLoadHelper . LoadUint ( SS_YAML_KEY_SPINNING ) ;
m_floppyDrive [ unit ] . m_writelight = yamlLoadHelper . LoadUint ( SS_YAML_KEY_WRITE_LIGHT ) ;
2019-04-14 16:58:49 +01:00
m_floppyDrive [ unit ] . m_disk . m_nibbles = yamlLoadHelper . LoadUint ( SS_YAML_KEY_NIBBLES ) ;
m_floppyDrive [ unit ] . m_disk . m_trackimagedata = yamlLoadHelper . LoadUint ( SS_YAML_KEY_TRACK_IMAGE_DATA ) ? true : false ;
m_floppyDrive [ unit ] . m_disk . m_trackimagedirty = yamlLoadHelper . LoadUint ( SS_YAML_KEY_TRACK_IMAGE_DIRTY ) ? true : false ;
2015-12-05 16:50:27 +00:00
2018-02-24 15:12:40 +00:00
std : : vector < BYTE > track ( NIBBLES_PER_TRACK ) ;
2016-02-14 16:01:30 +00:00
if ( yamlLoadHelper . GetSubMap ( SS_YAML_KEY_TRACK_IMAGE ) )
{
2017-12-17 19:35:54 +00:00
yamlLoadHelper . LoadMemory ( & track [ 0 ] , NIBBLES_PER_TRACK ) ;
2016-02-14 16:01:30 +00:00
yamlLoadHelper . PopMap ( ) ;
}
2015-12-05 16:50:27 +00:00
yamlLoadHelper . PopMap ( ) ;
//
if ( ! filename . empty ( ) & & ! bImageError )
{
2019-04-14 16:58:49 +01:00
if ( ( m_floppyDrive [ unit ] . m_disk . m_trackimage = = NULL ) & & m_floppyDrive [ unit ] . m_disk . m_nibbles )
2015-12-05 16:50:27 +00:00
AllocTrack ( unit ) ;
2019-04-14 16:58:49 +01:00
if ( m_floppyDrive [ unit ] . m_disk . m_trackimage = = NULL )
2015-12-05 16:50:27 +00:00
bImageError = true ;
else
2019-04-14 16:58:49 +01:00
memcpy ( m_floppyDrive [ unit ] . m_disk . m_trackimage , & track [ 0 ] , NIBBLES_PER_TRACK ) ;
2015-12-05 16:50:27 +00:00
}
if ( bImageError )
{
2019-04-14 16:58:49 +01:00
m_floppyDrive [ unit ] . m_disk . m_trackimagedata = false ;
m_floppyDrive [ unit ] . m_disk . m_trackimagedirty = false ;
m_floppyDrive [ unit ] . m_disk . m_nibbles = 0 ;
2015-12-05 16:50:27 +00:00
}
}
2019-04-14 17:00:15 +01:00
bool Disk2InterfaceCard : : LoadSnapshot ( class YamlLoadHelper & yamlLoadHelper , UINT slot , UINT version )
2015-12-05 16:50:27 +00:00
{
if ( slot ! = 6 ) // fixme
throw std : : string ( " Card: wrong slot " ) ;
2018-01-14 18:01:22 +00:00
if ( version < 1 | | version > kUNIT_VERSION )
2015-12-05 16:50:27 +00:00
throw std : : string ( " Card: wrong version " ) ;
2019-04-08 10:41:47 +01:00
m_phases = yamlLoadHelper . LoadUint ( SS_YAML_KEY_PHASES ) ;
m_currDrive = yamlLoadHelper . LoadUint ( SS_YAML_KEY_CURRENT_DRIVE ) ;
( void ) yamlLoadHelper . LoadBool ( SS_YAML_KEY_DISK_ACCESSED ) ; // deprecated - but retrieve the value to avoid the "State: Unknown key (Disk Accessed)" warning
2019-04-07 15:54:26 +01:00
m_enhanceDisk = yamlLoadHelper . LoadBool ( SS_YAML_KEY_ENHANCE_DISK ) ;
m_floppyLatch = yamlLoadHelper . LoadUint ( SS_YAML_KEY_FLOPPY_LATCH ) ;
2019-04-08 10:41:47 +01:00
m_floppyMotorOn = yamlLoadHelper . LoadBool ( SS_YAML_KEY_FLOPPY_MOTOR_ON ) ;
2019-04-07 15:54:26 +01:00
m_floppyWriteMode = yamlLoadHelper . LoadBool ( SS_YAML_KEY_FLOPPY_WRITE_MODE ) ;
2015-12-05 16:50:27 +00:00
2018-01-14 18:01:22 +00:00
if ( version > = 2 )
{
2019-04-07 15:54:26 +01:00
m_diskLastCycle = yamlLoadHelper . LoadUint64 ( SS_YAML_KEY_LAST_CYCLE ) ;
m_formatTrack . LoadSnapshot ( yamlLoadHelper ) ;
2018-01-14 18:01:22 +00:00
}
2018-10-02 22:08:54 +01:00
if ( version > = 3 )
{
2019-04-07 15:54:26 +01:00
m_diskLastReadLatchCycle = yamlLoadHelper . LoadUint64 ( SS_YAML_KEY_LAST_READ_LATCH_CYCLE ) ;
2018-10-02 22:08:54 +01:00
}
2015-12-05 16:50:27 +00:00
// Eject all disks first in case Drive-2 contains disk to be inserted into Drive-1
2019-04-08 10:41:47 +01:00
for ( UINT i = 0 ; i < NUM_DRIVES ; i + + )
2015-12-05 16:50:27 +00:00
{
2019-04-08 10:41:47 +01:00
EjectDisk ( i ) ; // Remove any disk & update Registry to reflect empty drive
2019-04-07 15:54:26 +01:00
m_floppyDrive [ i ] . clear ( ) ;
2015-12-05 16:50:27 +00:00
}
2019-04-08 10:41:47 +01:00
LoadSnapshotDriveUnit ( yamlLoadHelper , DRIVE_1 ) ;
LoadSnapshotDriveUnit ( yamlLoadHelper , DRIVE_2 ) ;
2015-12-05 16:50:27 +00:00
FrameRefreshStatus ( DRAW_LEDS | DRAW_BUTTON_DRIVES ) ;
return true ;
}