AppleWin/source/Disk.cpp
Brett Vickers 9e5e21b8c9 Prevent uninitialized value bugs and improve string safety.
This change does two things:

1. Updates the registry APIs to reduce the likelihood of uninitialized
variables.

The code wasn't always checking the return value of registry load operations.
In some cases, this led to uninitialized memory being used, and crashes could
result. For example, LoadConfiguration in Applewin.cpp was using an
uninitialized value for the computer type if no registry variable for the
"Apple 2 type" was set.

New registry reading methods and macros have also been introduced, allowing
default value fallbacks for the cases where a registry variable is not found.
This makes registry access simpler and safer when a default value is known in
advance.

The registry code's style has also been updated to conform with the rest of
the code base (tabs instead of spaces, naming conventions, etc.)

2. Introduces string safety improvements.

A number of code paths have been modified to use safe-string functions instead
of their unsafe counterparts (e.g., strcpy, sprintf).  In the process, some
strings were converted from "char" to "TCHAR". This was done mostly for
consistency with the rest of the code-base.
2019-08-09 13:38:50 -07:00

2015 lines
61 KiB
C++

/*
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
Copyright (C) 2006-2019, Tom Charlesworth, Michael Pohoreski, Nick Westgate
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
*
* In comments, UTAIIe is an abbreviation for a reference to "Understanding the Apple //e" by James Sather
*/
#include "StdAfx.h"
#include "SaveState_Structs_v1.h"
#include "Applewin.h"
#include "CPU.h"
#include "Disk.h"
#include "DiskImage.h"
#include "Frame.h"
#include "Log.h"
#include "Memory.h"
#include "Registry.h"
#include "Video.h"
#include "YamlHelper.h"
#include "../resource/resource.h"
// 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.
Disk2InterfaceCard::Disk2InterfaceCard(void)
{
ResetSwitches();
m_floppyLatch = 0;
m_saveDiskImage = true; // Save the DiskImage name to Registry
m_slot = 0;
m_diskLastCycle = 0;
m_diskLastReadLatchCycle = 0;
m_enhanceDisk = true;
ResetLogicStateSequencer();
// Debug:
#if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
m_bLogDisk_NibblesRW = false;
#endif
#if LOG_DISK_NIBBLES_WRITE
m_uWriteLastCycle = 0;
m_uSyncFFCount = 0;
#endif
}
bool Disk2InterfaceCard::GetEnhanceDisk(void) { return m_enhanceDisk; }
void Disk2InterfaceCard::SetEnhanceDisk(bool bEnhanceDisk) { m_enhanceDisk = bEnhanceDisk; }
int Disk2InterfaceCard::GetCurrentDrive(void) { return m_currDrive; }
int Disk2InterfaceCard::GetCurrentTrack(void) { return ImagePhaseToTrack(m_floppyDrive[m_currDrive].m_disk.m_imagehandle, m_floppyDrive[m_currDrive].m_phasePrecise, false); }
float Disk2InterfaceCard::GetCurrentPhase(void) { return m_floppyDrive[m_currDrive].m_phasePrecise; }
int Disk2InterfaceCard::GetCurrentOffset(void) { return m_floppyDrive[m_currDrive].m_disk.m_byte; }
BYTE Disk2InterfaceCard::GetCurrentLSSBitMask(void) { return m_floppyDrive[m_currDrive].m_disk.m_bitMask; }
double Disk2InterfaceCard::GetCurrentExtraCycles(void) { return m_floppyDrive[m_currDrive].m_disk.m_extraCycles; }
int Disk2InterfaceCard::GetTrack(const int drive) { return ImagePhaseToTrack(m_floppyDrive[drive].m_disk.m_imagehandle, m_floppyDrive[drive].m_phasePrecise, false); }
std::string Disk2InterfaceCard::GetCurrentTrackString(void)
{
const UINT trackInt = (UINT)(m_floppyDrive[m_currDrive].m_phasePrecise / 2);
const float trackFrac = (m_floppyDrive[m_currDrive].m_phasePrecise / 2) - (float)trackInt;
char szInt[8] = "";
sprintf(szInt, "%02X", trackInt); // "$NN"
char szFrac[8] = "";
sprintf(szFrac, "%.02f", trackFrac); // "0.nn"
return std::string(szInt) + std::string(szFrac+1);
}
std::string Disk2InterfaceCard::GetCurrentPhaseString(void)
{
const UINT phaseInt = (UINT)(m_floppyDrive[m_currDrive].m_phasePrecise);
const float phaseFrac = m_floppyDrive[m_currDrive].m_phasePrecise - (float)phaseInt;
char szInt[8] = "";
sprintf(szInt, "%02X", phaseInt); // "$NN"
char szFrac[8] = "";
sprintf(szFrac, "%.02f", phaseFrac); // "0.nn"
return std::string(szInt) + std::string(szFrac+1);
}
LPCTSTR Disk2InterfaceCard::GetCurrentState(void)
{
if (m_floppyDrive[m_currDrive].m_disk.m_imagehandle == NULL)
return "Empty";
if (!m_floppyMotorOn)
{
if (m_floppyDrive[m_currDrive].m_spinning > 0)
return "Off (spinning)";
else
return "Off";
}
else if (m_floppyWriteMode)
{
if (m_floppyDrive[m_currDrive].m_disk.m_bWriteProtected)
return "Writing (write protected)";
else
return "Writing";
}
else
{
/*if (m_floppyLoadMode)
{
if (m_floppyDrive[m_currDrive].disk.bWriteProtected)
return "Reading write protect state (write protected)";
else
return "Reading write protect state (not write protected)";
}
else*/
return "Reading";
}
}
//===========================================================================
void Disk2InterfaceCard::LoadLastDiskImage(const int drive)
{
_ASSERT(drive == DRIVE_1 || drive == DRIVE_2);
const TCHAR *pRegKey = (drive == DRIVE_1)
? TEXT(REGVALUE_PREF_LAST_DISK_1)
: TEXT(REGVALUE_PREF_LAST_DISK_2);
TCHAR sFilePath[MAX_PATH];
if (RegLoadString(TEXT(REG_PREFS), pRegKey, 1, sFilePath, MAX_PATH, TEXT("")))
{
m_saveDiskImage = false;
// Pass in ptr to local copy of filepath, since RemoveDisk() sets DiskPathFilename = ""
InsertDisk(drive, sFilePath, IMAGE_USE_FILES_WRITE_PROTECT_STATUS, IMAGE_DONT_CREATE);
m_saveDiskImage = true;
}
}
//===========================================================================
void Disk2InterfaceCard::SaveLastDiskImage(const int drive)
{
_ASSERT(drive == DRIVE_1 || drive == DRIVE_2);
if (!m_saveDiskImage)
return;
const TCHAR *pFileName = m_floppyDrive[drive].m_disk.m_fullname;
if (drive == DRIVE_1)
RegSaveString(TEXT(REG_PREFS), TEXT(REGVALUE_PREF_LAST_DISK_1), TRUE, pFileName);
else
RegSaveString(TEXT(REG_PREFS), TEXT(REGVALUE_PREF_LAST_DISK_2), TRUE, pFileName);
//
TCHAR szPathName[MAX_PATH];
StringCbCopy(szPathName, MAX_PATH, DiskGetFullPathName(drive));
TCHAR* slash = _tcsrchr(szPathName, TEXT('\\'));
if (slash != NULL)
{
slash[1] = '\0';
RegSaveString(TEXT(REG_PREFS), TEXT(REGVALUE_PREF_START_DIR), 1, szPathName);
}
}
//===========================================================================
// Called by ControlMotor() & Enable()
void Disk2InterfaceCard::CheckSpinning(const ULONG uExecutedCycles)
{
DWORD modechange = (m_floppyMotorOn && !m_floppyDrive[m_currDrive].m_spinning);
if (m_floppyMotorOn)
m_floppyDrive[m_currDrive].m_spinning = SPINNING_CYCLES;
if (modechange)
FrameDrawDiskLEDS( (HDC)0 );
if (modechange)
{
// Set m_diskLastCycle when motor changes: not spinning (ie. off for 1 sec) -> on
CpuCalcCycles(uExecutedCycles);
m_diskLastCycle = g_nCumulativeCycles;
}
}
//===========================================================================
Disk_Status_e Disk2InterfaceCard::GetDriveLightStatus(const int drive)
{
if (IsDriveValid( drive ))
{
FloppyDrive* pDrive = &m_floppyDrive[ drive ];
if (pDrive->m_spinning)
{
if (pDrive->m_disk.m_bWriteProtected)
return DISK_STATUS_PROT;
if (pDrive->m_writelight)
return DISK_STATUS_WRITE;
else
return DISK_STATUS_READ;
}
else
{
return DISK_STATUS_OFF;
}
}
return DISK_STATUS_OFF;
}
//===========================================================================
bool Disk2InterfaceCard::IsDriveValid(const int drive)
{
return (drive >= 0 && drive < NUM_DRIVES);
}
//===========================================================================
void Disk2InterfaceCard::AllocTrack(const int drive)
{
FloppyDisk* pFloppy = &m_floppyDrive[drive].m_disk;
pFloppy->m_trackimage = (LPBYTE)VirtualAlloc(NULL, NIBBLES_PER_TRACK, MEM_COMMIT, PAGE_READWRITE);
}
//===========================================================================
void Disk2InterfaceCard::ReadTrack(const int drive, ULONG uExecutedCycles)
{
if (! IsDriveValid( drive ))
return;
FloppyDrive* pDrive = &m_floppyDrive[ drive ];
FloppyDisk* pFloppy = &pDrive->m_disk;
if (ImagePhaseToTrack(pFloppy->m_imagehandle, pDrive->m_phasePrecise, false) >= ImageGetNumTracks(pFloppy->m_imagehandle))
{
_ASSERT(0); // What can cause this? Add a comment to replace this assert.
pFloppy->m_trackimagedata = false;
return;
}
if (!pFloppy->m_trackimage)
AllocTrack( drive );
if (pFloppy->m_trackimage && pFloppy->m_imagehandle)
{
#if LOG_DISK_TRACKS
CpuCalcCycles(uExecutedCycles);
const ULONG cycleDelta = (ULONG)(g_nCumulativeCycles - pDrive->m_lastStepperCycle);
LOG_DISK("track $%s read (time since last stepper %.3fms)\r\n", GetCurrentTrackString().c_str(), ((float)cycleDelta) / (CLK_6502_NTSC / 1000.0));
#endif
const UINT32 currentPosition = pFloppy->m_byte;
const UINT32 currentTrackLength = pFloppy->m_nibbles;
ImageReadTrack(
pFloppy->m_imagehandle,
pDrive->m_phasePrecise,
pFloppy->m_trackimage,
&pFloppy->m_nibbles,
&pFloppy->m_bitCount,
m_enhanceDisk);
if (!ImageIsWOZ(pFloppy->m_imagehandle) || (currentTrackLength == 0))
{
pFloppy->m_byte = 0;
}
else
{
_ASSERT(pFloppy->m_nibbles && pFloppy->m_bitCount);
if (pFloppy->m_nibbles == 0 || pFloppy->m_bitCount == 0)
{
pFloppy->m_nibbles = 1;
pFloppy->m_bitCount = 8;
}
pFloppy->m_byte = (currentPosition * pFloppy->m_nibbles) / currentTrackLength; // Ref: WOZ-1.01
if (pFloppy->m_byte == (pFloppy->m_nibbles-1)) // Last nibble may not be complete, so advance by 1 nibble
pFloppy->m_byte = 0;
pFloppy->m_bitOffset = pFloppy->m_byte*8;
pFloppy->m_bitMask = 1 << 7;
pFloppy->m_extraCycles = 0.0;
pDrive->m_headWindow = 0;
}
pFloppy->m_trackimagedata = (pFloppy->m_nibbles != 0);
}
}
//===========================================================================
void Disk2InterfaceCard::RemoveDisk(const int drive)
{
FloppyDisk* pFloppy = &m_floppyDrive[drive].m_disk;
if (pFloppy->m_imagehandle)
{
FlushCurrentTrack(drive);
ImageClose(pFloppy->m_imagehandle);
pFloppy->m_imagehandle = NULL;
}
if (pFloppy->m_trackimage)
{
VirtualFree(pFloppy->m_trackimage, 0, MEM_RELEASE);
pFloppy->m_trackimage = NULL;
pFloppy->m_trackimagedata = false;
}
memset( pFloppy->m_imagename, 0, MAX_DISK_IMAGE_NAME+1 );
memset( pFloppy->m_fullname , 0, MAX_DISK_FULL_NAME +1 );
pFloppy->m_strFilenameInZip = "";
SaveLastDiskImage( drive );
Video_ResetScreenshotCounter( NULL );
}
//===========================================================================
void Disk2InterfaceCard::WriteTrack(const int drive)
{
FloppyDrive* pDrive = &m_floppyDrive[ drive ];
FloppyDisk* pFloppy = &pDrive->m_disk;
if (ImagePhaseToTrack(pFloppy->m_imagehandle, pDrive->m_phasePrecise, false) >= ImageGetNumTracks(pFloppy->m_imagehandle))
{
_ASSERT(0); // What can cause this? Add a comment to replace this assert.
return;
}
if (pFloppy->m_bWriteProtected)
return;
if (pFloppy->m_trackimage && pFloppy->m_imagehandle)
{
#if LOG_DISK_TRACKS
LOG_DISK("track $%s write\r\n", GetCurrentTrackString().c_str());
#endif
ImageWriteTrack(
pFloppy->m_imagehandle,
pDrive->m_phasePrecise,
pFloppy->m_trackimage,
pFloppy->m_nibbles);
}
pFloppy->m_trackimagedirty = false;
}
void Disk2InterfaceCard::FlushCurrentTrack(const int drive)
{
FloppyDisk* pFloppy = &m_floppyDrive[drive].m_disk;
if (pFloppy->m_trackimage && pFloppy->m_trackimagedirty)
WriteTrack(drive);
}
//===========================================================================
void Disk2InterfaceCard::Boot(void)
{
// 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.
if (m_floppyDrive[0].m_disk.m_imagehandle && ImageBoot(m_floppyDrive[0].m_disk.m_imagehandle))
m_floppyMotorOn = 0;
}
//===========================================================================
void __stdcall Disk2InterfaceCard::ControlMotor(WORD, WORD address, BYTE, BYTE, ULONG uExecutedCycles)
{
BOOL newState = address & 1;
if (newState != m_floppyMotorOn) // motor changed state
m_formatTrack.DriveNotWritingTrack();
m_floppyMotorOn = newState;
// NB. Motor off doesn't reset the Command Decoder like reset. (UTAIIe figures 9.7 & 9.8 chip C2)
// - so it doesn't reset this state: m_floppyLoadMode, m_floppyWriteMode, m_magnetStates
#if LOG_DISK_MOTOR
LOG_DISK("motor %s\r\n", (m_floppyMotorOn) ? "on" : "off");
#endif
CheckSpinning(uExecutedCycles);
}
//===========================================================================
void __stdcall Disk2InterfaceCard::ControlStepper(WORD, WORD address, BYTE, BYTE, ULONG uExecutedCycles)
{
FloppyDrive* pDrive = &m_floppyDrive[m_currDrive];
FloppyDisk* pFloppy = &pDrive->m_disk;
if (!m_floppyMotorOn) // GH#525
{
if (!pDrive->m_spinning)
{
#if LOG_DISK_PHASES
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");
#endif
}
// update phases (magnet states)
{
const int phase = (address >> 1) & 3;
const int phase_bit = (1 << phase);
// update the magnet states
if (address & 1)
m_magnetStates |= phase_bit; // phase on
else
m_magnetStates &= ~phase_bit; // phase off
}
CpuCalcCycles(uExecutedCycles);
#if LOG_DISK_PHASES
const ULONG cycleDelta = (ULONG)(g_nCumulativeCycles - pDrive->m_lastStepperCycle);
#endif
pDrive->m_lastStepperCycle = g_nCumulativeCycles;
// 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 (ie. quarter track)
// momentum and timing are not accounted for ... maybe one day!
int direction = 0;
if (m_magnetStates & (1 << ((pDrive->m_phase + 1) & 3)))
direction += 1;
if (m_magnetStates & (1 << ((pDrive->m_phase + 3) & 3)))
direction -= 1;
// Only calculate quarterDirection for WOZ, as NIB/DSK don't support half phases.
int quarterDirection = 0;
if (ImageIsWOZ(pFloppy->m_imagehandle))
{
if ((m_magnetStates == 0xC || // 1100
m_magnetStates == 0x6 || // 0110
m_magnetStates == 0x3 || // 0011
m_magnetStates == 0x9)) // 1001
{
quarterDirection = direction;
direction = 0;
}
}
pDrive->m_phase = MAX(0, MIN(79, pDrive->m_phase + direction));
float newPhasePrecise = (float)(pDrive->m_phase) + (float)quarterDirection * 0.5f;
if (newPhasePrecise < 0)
newPhasePrecise = 0;
// apply magnet step, if any
if (newPhasePrecise != pDrive->m_phasePrecise)
{
FlushCurrentTrack(m_currDrive);
pDrive->m_phasePrecise = newPhasePrecise;
pFloppy->m_trackimagedata = false;
m_formatTrack.DriveNotWritingTrack();
FrameDrawDiskStatus((HDC)0); // Show track status (GH#201)
}
#if LOG_DISK_PHASES
LOG_DISK("track $%s magnet-states %d%d%d%d phase %d %s address $%4X last-stepper %.3fms\r\n",
GetCurrentTrackString().c_str(),
(m_magnetStates >> 3) & 1,
(m_magnetStates >> 2) & 1,
(m_magnetStates >> 1) & 1,
(m_magnetStates >> 0) & 1,
(address >> 1) & 3, // phase
(address & 1) ? "on " : "off",
address,
((float)cycleDelta)/(CLK_6502_NTSC/1000.0));
#endif
}
//===========================================================================
void Disk2InterfaceCard::Destroy(void)
{
m_saveDiskImage = false;
RemoveDisk(DRIVE_1);
m_saveDiskImage = false;
RemoveDisk(DRIVE_2);
m_saveDiskImage = true;
}
//===========================================================================
void __stdcall Disk2InterfaceCard::Enable(WORD, WORD address, BYTE, BYTE, ULONG uExecutedCycles)
{
m_currDrive = address & 1;
#if LOG_DISK_ENABLE_DRIVE
LOG_DISK("enable drive: %d\r\n", m_currDrive);
#endif
m_floppyDrive[!m_currDrive].m_spinning = 0;
m_floppyDrive[!m_currDrive].m_writelight = 0;
CheckSpinning(uExecutedCycles);
}
//===========================================================================
void Disk2InterfaceCard::EjectDisk(const int drive)
{
if (IsDriveValid(drive))
{
RemoveDisk(drive);
}
}
//===========================================================================
// Return the filename
// . Used by Drive Buttons' tooltips
LPCTSTR Disk2InterfaceCard::GetFullDiskFilename(const int drive)
{
if (!m_floppyDrive[drive].m_disk.m_strFilenameInZip.empty())
return m_floppyDrive[drive].m_disk.m_strFilenameInZip.c_str();
return GetFullName(drive);
}
// Return the file or zip name
// . Used by Property Sheet Page (Disk)
LPCTSTR Disk2InterfaceCard::GetFullName(const int drive)
{
return m_floppyDrive[drive].m_disk.m_fullname;
}
// Return the imagename
// . Used by Drive Button's icons & Property Sheet Page (Save snapshot)
LPCTSTR Disk2InterfaceCard::GetBaseName(const int drive)
{
return m_floppyDrive[drive].m_disk.m_imagename;
}
LPCTSTR Disk2InterfaceCard::DiskGetFullPathName(const int drive)
{
return ImageGetPathname(m_floppyDrive[drive].m_disk.m_imagehandle);
}
//===========================================================================
void Disk2InterfaceCard::GetLightStatus(Disk_Status_e *pDisk1Status, Disk_Status_e *pDisk2Status)
{
if (pDisk1Status)
*pDisk1Status = GetDriveLightStatus(DRIVE_1);
if (pDisk2Status)
*pDisk2Status = GetDriveLightStatus(DRIVE_2);
}
//===========================================================================
ImageError_e Disk2InterfaceCard::InsertDisk(const int drive, LPCTSTR pszImageFilename, const bool bForceWriteProtected, const bool bCreateIfNecessary)
{
FloppyDrive* pDrive = &m_floppyDrive[drive];
FloppyDisk* pFloppy = &pDrive->m_disk;
if (pFloppy->m_imagehandle)
RemoveDisk(drive);
// 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.
pFloppy->clear();
const DWORD dwAttributes = GetFileAttributes(pszImageFilename);
if(dwAttributes == INVALID_FILE_ATTRIBUTES)
pFloppy->m_bWriteProtected = false; // Assume this is a new file to create
else
pFloppy->m_bWriteProtected = bForceWriteProtected ? true : (dwAttributes & FILE_ATTRIBUTE_READONLY);
// Check if image is being used by the other drive, and if so remove it in order so it can be swapped
{
const char* pszOtherPathname = DiskGetFullPathName(!drive);
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))
{
EjectDisk(!drive);
FrameRefreshStatus(DRAW_LEDS | DRAW_BUTTON_DRIVES);
}
}
ImageError_e Error = ImageOpen(pszImageFilename,
&pFloppy->m_imagehandle,
&pFloppy->m_bWriteProtected,
bCreateIfNecessary,
pFloppy->m_strFilenameInZip);
if (Error == eIMAGE_ERROR_NONE && ImageIsMultiFileZip(pFloppy->m_imagehandle))
{
TCHAR szText[100+MAX_PATH];
szText[sizeof(szText)-1] = 0;
_snprintf(szText, sizeof(szText)-1, "Only the first file in a multi-file zip is supported\nUse disk image '%s' ?", pFloppy->m_strFilenameInZip.c_str());
int nRes = MessageBox(g_hFrameWindow, szText, TEXT("Multi-Zip Warning"), MB_ICONWARNING | MB_YESNO | MB_SETFOREGROUND);
if (nRes == IDNO)
{
RemoveDisk(drive);
Error = eIMAGE_ERROR_REJECTED_MULTI_ZIP;
}
}
if (Error == eIMAGE_ERROR_NONE)
{
GetImageTitle(pszImageFilename, pFloppy->m_imagename, pFloppy->m_fullname);
Video_ResetScreenshotCounter(pFloppy->m_imagename);
}
else
{
Video_ResetScreenshotCounter(NULL);
}
SaveLastDiskImage(drive);
return Error;
}
//===========================================================================
bool Disk2InterfaceCard::IsConditionForFullSpeed(void)
{
return m_floppyMotorOn && m_enhanceDisk;
}
//===========================================================================
void Disk2InterfaceCard::NotifyInvalidImage(const int drive, LPCTSTR pszImageFilename, const ImageError_e Error)
{
TCHAR szBuffer[MAX_PATH + 128];
switch (Error)
{
case eIMAGE_ERROR_UNABLE_TO_OPEN:
case eIMAGE_ERROR_UNABLE_TO_OPEN_GZ:
case eIMAGE_ERROR_UNABLE_TO_OPEN_ZIP:
StringCbPrintf(
szBuffer,
MAX_PATH + 128,
TEXT("Unable to open the file %s."),
pszImageFilename);
break;
case eIMAGE_ERROR_BAD_SIZE:
StringCbPrintf(
szBuffer,
MAX_PATH + 128,
TEXT("Unable to use the file %s\nbecause the ")
TEXT("disk image is an unsupported size."),
pszImageFilename);
break;
case eIMAGE_ERROR_BAD_FILE:
StringCbPrintf(
szBuffer,
MAX_PATH + 128,
TEXT("Unable to use the file %s\nbecause the ")
TEXT("OS can't access it."),
pszImageFilename);
break;
case eIMAGE_ERROR_UNSUPPORTED:
StringCbPrintf(
szBuffer,
MAX_PATH + 128,
TEXT("Unable to use the file %s\nbecause the ")
TEXT("disk image format is not recognized."),
pszImageFilename);
break;
case eIMAGE_ERROR_UNSUPPORTED_HDV:
StringCbPrintf(
szBuffer,
MAX_PATH + 128,
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:
StringCbPrintf(
szBuffer,
MAX_PATH + 128,
TEXT("Unable to use the file %s\nbecause 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,
m_floppyDrive[drive].m_disk.m_strFilenameInZip.c_str());
break;
case eIMAGE_ERROR_GZ:
case eIMAGE_ERROR_ZIP:
StringCbPrintf(
szBuffer,
MAX_PATH + 128,
TEXT("Unable to use the compressed file %s\nbecause the ")
TEXT("compressed disk image is corrupt/unsupported."),
pszImageFilename);
break;
case eIMAGE_ERROR_FAILED_TO_GET_PATHNAME:
StringCbPrintf(
szBuffer,
MAX_PATH + 128,
TEXT("Unable to GetFullPathName() for the file: %s."),
pszImageFilename);
break;
case eIMAGE_ERROR_ZEROLENGTH_WRITEPROTECTED:
StringCbPrintf(
szBuffer,
MAX_PATH + 128,
TEXT("Unsupported zero-length write-protected file: %s."),
pszImageFilename);
break;
case eIMAGE_ERROR_FAILED_TO_INIT_ZEROLENGTH:
StringCbPrintf(
szBuffer,
MAX_PATH + 128,
TEXT("Failed to resize the zero-length file: %s."),
pszImageFilename);
break;
default:
// IGNORE OTHER ERRORS SILENTLY
return;
}
MessageBox(
g_hFrameWindow,
szBuffer,
g_pAppTitle,
MB_ICONEXCLAMATION | MB_SETFOREGROUND);
}
//===========================================================================
bool Disk2InterfaceCard::GetProtect(const int drive)
{
if (IsDriveValid(drive))
{
if (m_floppyDrive[drive].m_disk.m_bWriteProtected)
return true;
}
return false;
}
//===========================================================================
void Disk2InterfaceCard::SetProtect(const int drive, const bool bWriteProtect)
{
if (IsDriveValid( drive ))
{
m_floppyDrive[drive].m_disk.m_bWriteProtected = bWriteProtect;
}
}
//===========================================================================
bool Disk2InterfaceCard::IsDiskImageWriteProtected(const int drive)
{
if (!IsDriveValid(drive))
return true;
return ImageIsWriteProtected(m_floppyDrive[drive].m_disk.m_imagehandle);
}
//===========================================================================
bool Disk2InterfaceCard::IsDriveEmpty(const int drive)
{
if (!IsDriveValid(drive))
return true;
return m_floppyDrive[drive].m_disk.m_imagehandle == NULL;
}
//===========================================================================
#if LOG_DISK_NIBBLES_WRITE
bool Disk2InterfaceCard::LogWriteCheckSyncFF(ULONG& uCycleDelta)
{
bool bIsSyncFF = false;
if (m_uWriteLastCycle == 0) // Reset to 0 when write mode is enabled
{
uCycleDelta = 0;
if (m_floppyLatch == 0xFF)
{
m_uSyncFFCount = 0;
bIsSyncFF = true;
}
}
else
{
uCycleDelta = (ULONG) (g_nCumulativeCycles - m_uWriteLastCycle);
if (m_floppyLatch == 0xFF && uCycleDelta > 32)
{
m_uSyncFFCount++;
bIsSyncFF = true;
}
}
m_uWriteLastCycle = g_nCumulativeCycles;
return bIsSyncFF;
}
#endif
//===========================================================================
void __stdcall Disk2InterfaceCard::ReadWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles)
{
/* m_floppyLoadMode = 0; */
FloppyDrive* pDrive = &m_floppyDrive[m_currDrive];
FloppyDisk* pFloppy = &pDrive->m_disk;
if (!pFloppy->m_trackimagedata && pFloppy->m_imagehandle)
ReadTrack(m_currDrive, uExecutedCycles);
if (!pFloppy->m_trackimagedata)
{
m_floppyLatch = 0xFF;
return;
}
// Improve precision of "authentic" drive mode - GH#125
UINT uSpinNibbleCount = 0;
CpuCalcCycles(uExecutedCycles); // g_nCumulativeCycles required for uSpinNibbleCount & LogWriteCheckSyncFF()
if (!m_enhanceDisk && pDrive->m_spinning)
{
const ULONG nCycleDiff = (ULONG) (g_nCumulativeCycles - m_diskLastCycle);
m_diskLastCycle = g_nCumulativeCycles;
if (nCycleDiff > 40)
{
// 40 cycles for a write of a 10-bit 0xFF sync byte
uSpinNibbleCount = nCycleDiff >> 5; // ...but divide by 32 (not 40)
ULONG uWrapOffset = uSpinNibbleCount % pFloppy->m_nibbles;
pFloppy->m_byte += uWrapOffset;
if (pFloppy->m_byte >= pFloppy->m_nibbles)
pFloppy->m_byte -= pFloppy->m_nibbles;
#if LOG_DISK_NIBBLES_SPIN
UINT uCompleteRevolutions = uSpinNibbleCount / pFloppy->m_nibbles;
LOG_DISK("spin: revs=%d, nibbles=%d\r\n", uCompleteRevolutions, uWrapOffset);
#endif
}
}
if (!m_floppyWriteMode)
{
// 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.
if (!pDrive->m_spinning) // GH#599
return;
const ULONG nReadCycleDiff = (ULONG) (g_nCumulativeCycles - m_diskLastReadLatchCycle);
// 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
const ULONG kReadAccessThreshold = m_enhanceDisk ? 6 : 31;
if (nReadCycleDiff <= kReadAccessThreshold)
{
UINT invalidBits = 8 - (nReadCycleDiff / 4); // 4 cycles per bit-cell
m_floppyLatch = *(pFloppy->m_trackimage + pFloppy->m_byte) >> invalidBits;
return; // Early return so don't update: m_diskLastReadLatchCycle & pFloppy->byte
}
m_floppyLatch = *(pFloppy->m_trackimage + pFloppy->m_byte);
m_diskLastReadLatchCycle = g_nCumulativeCycles;
#if LOG_DISK_NIBBLES_READ
#if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
if (m_bLogDisk_NibblesRW)
#endif
{
LOG_DISK("read %04X = %02X\r\n", pFloppy->m_byte, m_floppyLatch);
}
m_formatTrack.DecodeLatchNibbleRead(m_floppyLatch);
#endif
}
else if (!pFloppy->m_bWriteProtected) // && m_floppyWriteMode
{
if (!pDrive->m_spinning)
return; // If not spinning then only 1 bit-cell gets written?
*(pFloppy->m_trackimage + pFloppy->m_byte) = m_floppyLatch;
pFloppy->m_trackimagedirty = true;
bool bIsSyncFF = false;
#if LOG_DISK_NIBBLES_WRITE
ULONG uCycleDelta = 0;
bIsSyncFF = LogWriteCheckSyncFF(uCycleDelta);
#endif
m_formatTrack.DecodeLatchNibbleWrite(m_floppyLatch, uSpinNibbleCount, pFloppy, bIsSyncFF); // GH#125
#if LOG_DISK_NIBBLES_WRITE
#if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
if (m_bLogDisk_NibblesRW)
#endif
{
if (!bIsSyncFF)
LOG_DISK("write %04X = %02X (cy=+%d)\r\n", pFloppy->m_byte, m_floppyLatch, uCycleDelta);
else
LOG_DISK("write %04X = %02X (cy=+%d) sync #%d\r\n", pFloppy->m_byte, m_floppyLatch, uCycleDelta, m_uSyncFFCount);
}
#endif
}
if (++pFloppy->m_byte >= pFloppy->m_nibbles)
pFloppy->m_byte = 0;
// Show track status (GH#201) - NB. Prevent flooding of forcing UI to redraw!!!
if ((pFloppy->m_byte & 0xFF) == 0)
FrameDrawDiskStatus( (HDC)0 );
}
//===========================================================================
void Disk2InterfaceCard::ResetLogicStateSequencer(void)
{
m_shiftReg = 0;
m_latchDelay = 0;
m_resetSequencer = true;
m_dbgLatchDelayedCnt = 0;
}
void Disk2InterfaceCard::UpdateBitStreamPositionAndDiskCycle(const ULONG uExecutedCycles)
{
FloppyDisk& floppy = m_floppyDrive[m_currDrive].m_disk;
CpuCalcCycles(uExecutedCycles);
const UINT bitCellDelta = GetBitCellDelta(ImageGetOptimalBitTiming(floppy.m_imagehandle));
UpdateBitStreamPosition(floppy, bitCellDelta);
m_diskLastCycle = g_nCumulativeCycles;
}
UINT Disk2InterfaceCard::GetBitCellDelta(const BYTE optimalBitTiming)
{
FloppyDisk& floppy = m_floppyDrive[m_currDrive].m_disk;
// NB. m_extraCycles is needed to retain accuracy:
// . Read latch #1: 0-> 9: cycleDelta= 9, bitCellDelta=2, extraCycles=1
// . Read latch #2: 9->20: cycleDelta=11, bitCellDelta=2, extraCycles=3
// . Overall: 0->20: cycleDelta=20, bitCellDelta=5, extraCycles=0
UINT bitCellDelta;
#if 0
if (optimalBitTiming == 32)
{
const ULONG cycleDelta = (ULONG)(g_nCumulativeCycles - m_diskLastCycle) + (BYTE) m_extraCycles;
bitCellDelta = cycleDelta / 4; // DIV 4 for 4us per bit-cell
m_extraCycles = cycleDelta & 3; // MOD 4 : remainder carried forward for next time
}
else
#endif
{
const double cycleDelta = (double)(g_nCumulativeCycles - m_diskLastCycle) + floppy.m_extraCycles;
const double bitTime = 0.125 * (double)optimalBitTiming; // 125ns units
bitCellDelta = (UINT) floor( cycleDelta / bitTime );
floppy.m_extraCycles = (double)cycleDelta - ((double)bitCellDelta * bitTime);
}
return bitCellDelta;
}
void Disk2InterfaceCard::UpdateBitStreamPosition(FloppyDisk& floppy, const ULONG bitCellDelta)
{
_ASSERT(floppy.m_bitCount); // Should never happen - ReadTrack() will handle this
if (floppy.m_bitCount == 0)
return;
floppy.m_bitOffset += bitCellDelta;
if (floppy.m_bitOffset >= floppy.m_bitCount)
floppy.m_bitOffset %= floppy.m_bitCount;
UpdateBitStreamOffsets(floppy);
}
void Disk2InterfaceCard::UpdateBitStreamOffsets(FloppyDisk& floppy)
{
floppy.m_byte = floppy.m_bitOffset / 8;
const UINT remainder = 7 - (floppy.m_bitOffset & 7);
floppy.m_bitMask = 1 << remainder;
}
void __stdcall Disk2InterfaceCard::DataLatchReadWriteWOZ(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles)
{
/* m_floppyLoadMode = 0; */
FloppyDrive& drive = m_floppyDrive[m_currDrive];
FloppyDisk& floppy = drive.m_disk;
if (!floppy.m_trackimagedata && floppy.m_imagehandle)
ReadTrack(m_currDrive, uExecutedCycles);
if (!floppy.m_trackimagedata)
{
_ASSERT(0); // Can't happen for WOZ - ReadTrack() should return an empty track
m_floppyLatch = 0xFF;
return;
}
// 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.
// TODO: And same for a write?
if (!drive.m_spinning) // GH#599
return;
CpuCalcCycles(uExecutedCycles);
// Skipping forward a large amount of bitcells means the bitstream will very likely be out-of-sync.
// The first 1-bit will produce a latch nibble, and this 1-bit is unlikely to be the nibble's high bit.
// So we need to ensure we run enough bits through the sequencer to re-sync.
// NB. For Planetfall 13 bitcells(NG) / 14 bitcells(OK)
const UINT significantBitCells = 50; // 5x 10-bit sync FF nibbles
UINT bitCellDelta = GetBitCellDelta(ImageGetOptimalBitTiming(floppy.m_imagehandle));
UINT bitCellRemainder;
if (bitCellDelta <= significantBitCells)
{
bitCellRemainder = bitCellDelta;
}
else
{
bitCellRemainder = significantBitCells;
bitCellDelta -= significantBitCells;
UpdateBitStreamPosition(floppy, bitCellDelta);
m_latchDelay = 0;
}
m_diskLastCycle = g_nCumulativeCycles;
if (!bWrite)
DataLatchReadWOZ(pc, addr, bitCellRemainder);
else
DataLatchWriteWOZ(pc, addr, d, bitCellRemainder);
// Show track status (GH#201) - NB. Prevent flooding of forcing UI to redraw!!!
if ((floppy.m_byte & 0xFF) == 0)
FrameDrawDiskStatus((HDC)0);
}
void Disk2InterfaceCard::DataLatchReadWOZ(WORD pc, WORD addr, UINT bitCellRemainder)
{
// m_diskLastReadLatchCycle = g_nCumulativeCycles; // Not used by WOZ (only by NIB)
#if LOG_DISK_NIBBLES_READ
bool newLatchData = false;
#endif
FloppyDrive& drive = m_floppyDrive[m_currDrive];
FloppyDisk& floppy = drive.m_disk;
#if _DEBUG
static int dbgWOZ = 0;
if (dbgWOZ)
{
DumpSectorWOZ(floppy);
//DumpTrackWOZ(floppy); // Enable as necessary
}
#endif
for (UINT i = 0; i < bitCellRemainder; i++)
{
BYTE n = floppy.m_trackimage[floppy.m_byte];
drive.m_headWindow <<= 1;
drive.m_headWindow |= (n & floppy.m_bitMask) ? 1 : 0;
BYTE outputBit = (drive.m_headWindow & 0xf) ? (drive.m_headWindow >> 1) & 1
: rand() & 1;
floppy.m_bitMask >>= 1;
if (!floppy.m_bitMask)
{
floppy.m_bitMask = 1 << 7;
floppy.m_byte++;
}
floppy.m_bitOffset++;
if (floppy.m_bitOffset == floppy.m_bitCount)
{
floppy.m_bitMask = 1 << 7;
floppy.m_bitOffset = 0;
floppy.m_byte = 0;
}
if (m_resetSequencer)
{
m_resetSequencer = false; // LSS takes some cycles to reset (ref?)
continue;
}
//
m_shiftReg <<= 1;
m_shiftReg |= outputBit;
if (m_latchDelay)
{
m_latchDelay -= 4;
if (m_latchDelay < 0)
m_latchDelay = 0;
if (m_shiftReg)
{
m_dbgLatchDelayedCnt = 0;
}
else // m_shiftReg==0
{
m_latchDelay += 4; // extend by 4us (so 7us again) - GH#662
m_dbgLatchDelayedCnt++;
#if LOG_DISK_NIBBLES_READ
if (m_dbgLatchDelayedCnt >= 3)
{
LOG_DISK("read: latch held due to 0: PC=%04X, cnt=%02X\r\n", regs.pc, m_dbgLatchDelayedCnt);
}
#endif
}
}
if (!m_latchDelay)
{
#if LOG_DISK_NIBBLES_READ
if (newLatchData)
{
LOG_DISK("read skipped latch data: %04X = %02X\r\n", floppy.m_byte, m_floppyLatch);
newLatchData = false;
}
#endif
m_floppyLatch = m_shiftReg;
if (m_shiftReg & 0x80)
{
m_latchDelay = 7;
m_shiftReg = 0;
#if LOG_DISK_NIBBLES_READ
// May not actually be read by 6502 (eg. Prologue's CHKSUM 4&4 nibble pair), but still pass to the log's nibble reader
m_formatTrack.DecodeLatchNibbleRead(m_floppyLatch);
newLatchData = true;
#endif
}
}
}
#if LOG_DISK_NIBBLES_READ
if (m_floppyLatch & 0x80)
{
#if LOG_DISK_NIBBLES_USE_RUNTIME_VAR
if (m_bLogDisk_NibblesRW)
#endif
{
LOG_DISK("read %04X = %02X\r\n", floppy.m_byte, m_floppyLatch);
}
}
#endif
}
void Disk2InterfaceCard::DataLatchWriteWOZ(WORD pc, WORD addr, BYTE d, UINT bitCellRemainder)
{
_ASSERT(m_floppyWriteMode);
FloppyDrive& drive = m_floppyDrive[m_currDrive];
FloppyDisk& floppy = drive.m_disk;
if (!floppy.m_bWriteProtected)
{
//TODO
}
}
//===========================================================================
#ifdef _DEBUG
// Dump nibbles from current position until 0xDEAA (ie. data epilogue)
void Disk2InterfaceCard::DumpSectorWOZ(FloppyDisk floppy) // pass a copy of m_floppy
{
BYTE shiftReg = 0;
UINT32 lastNibbles = 0;
UINT zeroCount = 0;
UINT nibbleCount = 0;
while (1)
{
BYTE n = floppy.m_trackimage[floppy.m_byte];
BYTE outputBit = (n & floppy.m_bitMask) ? 1 : 0;
floppy.m_bitMask >>= 1;
if (!floppy.m_bitMask)
{
floppy.m_bitMask = 1 << 7;
floppy.m_byte++;
}
floppy.m_bitOffset++;
if (floppy.m_bitOffset == floppy.m_bitCount)
{
floppy.m_bitMask = 1 << 7;
floppy.m_bitOffset = 0;
floppy.m_byte = 0;
}
if (shiftReg == 0 && outputBit == 0)
{
zeroCount++;
continue;
}
shiftReg <<= 1;
shiftReg |= outputBit;
if ((shiftReg & 0x80) == 0)
continue;
nibbleCount++;
char str[10];
sprintf(str, "%02X ", shiftReg);
OutputDebugString(str);
if ((nibbleCount & 0xf) == 0)
OutputDebugString("\n");
lastNibbles <<= 8;
lastNibbles |= shiftReg;
if ((lastNibbles & 0xffff) == 0xDEAA)
break;
shiftReg = 0;
zeroCount = 0;
}
}
// Dump nibbles from current position bitstream wraps to same position
void Disk2InterfaceCard::DumpTrackWOZ(FloppyDisk floppy) // pass a copy of m_floppy
{
#ifdef LOG_DISK_NIBBLES_READ
FormatTrack formatTrack;
#endif
BYTE shiftReg = 0;
UINT nibbleCount = 0;
floppy.m_bitMask = 1 << 7;
floppy.m_bitOffset = 0;
floppy.m_byte = 0;
const UINT startBitOffset = floppy.m_bitOffset;
while (1)
{
BYTE n = floppy.m_trackimage[floppy.m_byte];
BYTE outputBit = (n & floppy.m_bitMask) ? 1 : 0;
floppy.m_bitMask >>= 1;
if (!floppy.m_bitMask)
{
floppy.m_bitMask = 1 << 7;
floppy.m_byte++;
}
floppy.m_bitOffset++;
if (floppy.m_bitOffset == floppy.m_bitCount)
{
floppy.m_bitMask = 1 << 7;
floppy.m_bitOffset = 0;
floppy.m_byte = 0;
}
if (startBitOffset == floppy.m_bitOffset)
break;
if (shiftReg == 0 && outputBit == 0)
continue;
shiftReg <<= 1;
shiftReg |= outputBit;
if ((shiftReg & 0x80) == 0)
continue;
nibbleCount++;
TCHAR str[10];
StringCbPrintf(str, 10, "%02X ", shiftReg);
OutputDebugString(str);
if ((nibbleCount % 32) == 0)
OutputDebugString("\n");
#ifdef LOG_DISK_NIBBLES_READ
formatTrack.DecodeLatchNibbleRead(shiftReg);
#endif
shiftReg = 0;
}
}
#endif
//===========================================================================
void Disk2InterfaceCard::Reset(const bool bIsPowerCycle/*=false*/)
{
// RESET forces all switches off (UTAIIe Table 9.1)
ResetSwitches();
m_formatTrack.Reset();
ResetLogicStateSequencer();
if (bIsPowerCycle) // GH#460
{
// 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)
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;
FrameRefreshStatus(DRAW_LEDS, false);
}
}
void Disk2InterfaceCard::ResetSwitches(void)
{
m_currDrive = 0;
m_floppyMotorOn = 0;
m_floppyLoadMode = 0;
m_floppyWriteMode = 0;
m_magnetStates = 0;
}
//===========================================================================
bool Disk2InterfaceCard::UserSelectNewDiskImage(const int drive, LPCSTR pszFilename/*=""*/)
{
TCHAR directory[MAX_PATH];
TCHAR filename[MAX_PATH];
TCHAR title[40];
StringCbCopy(filename, MAX_PATH, pszFilename);
RegLoadString(TEXT(REG_PREFS), REGVALUE_PREF_START_DIR, 1, directory, MAX_PATH, TEXT(""));
StringCbPrintf(title, 40, TEXT("Select Disk Image For Drive %d"), drive + 1);
_ASSERT(sizeof(OPENFILENAME) == sizeof(OPENFILENAME_NT4)); // Required for Win98/ME support (selected by _WIN32_WINNT=0x0400 in stdafx.h)
OPENFILENAME ofn;
ZeroMemory(&ofn,sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = g_hFrameWindow;
ofn.hInstance = g_hInstance;
ofn.lpstrFilter = TEXT("All Images\0*.bin;*.do;*.dsk;*.nib;*.po;*.gz;*.woz;*.zip;*.2mg;*.2img;*.iie;*.apl\0")
TEXT("Disk Images (*.bin,*.do,*.dsk,*.nib,*.po,*.gz,*.woz,*.zip,*.2mg,*.2img,*.iie)\0*.bin;*.do;*.dsk;*.nib;*.po;*.gz;*.woz;*.zip;*.2mg;*.2img;*.iie\0")
TEXT("All Files\0*.*\0");
ofn.lpstrFile = filename;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrInitialDir = directory;
ofn.Flags = OFN_PATHMUSTEXIST;
ofn.lpstrTitle = title;
bool bRes = false;
if (GetOpenFileName(&ofn))
{
if ((!ofn.nFileExtension) || !filename[ofn.nFileExtension])
StringCbCat(filename, MAX_PATH, TEXT(".dsk"));
ImageError_e Error = InsertDisk(drive, filename, ofn.Flags & OFN_READONLY, IMAGE_CREATE);
if (Error == eIMAGE_ERROR_NONE)
{
bRes = true;
}
else
{
NotifyInvalidImage(drive, filename, Error);
}
}
return bRes;
}
//===========================================================================
void __stdcall Disk2InterfaceCard::LoadWriteProtect(WORD, WORD, BYTE write, BYTE value, ULONG uExecutedCycles)
{
/* m_floppyLoadMode = 1; */
// 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.
if (!m_floppyDrive[m_currDrive].m_spinning) // GH#599
return;
if (!write)
{
// 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)
// . Sequencer "SR" (Shift Right) command only loads QA (bit7) of data register (UTAIIe page 9-21)
if (m_floppyDrive[m_currDrive].m_disk.m_bWriteProtected)
m_floppyLatch |= 0x80;
else
m_floppyLatch &= 0x7F;
}
if (ImageIsWOZ(m_floppyDrive[m_currDrive].m_disk.m_imagehandle))
{
#if LOG_DISK_NIBBLES_READ
LOG_DISK("reset LSS: ~PC=%04X\r\n", regs.pc);
#endif
ResetLogicStateSequencer(); // reset sequencer (Ref: WOZ-1.01)
// m_latchDelay = 7; // TODO: Treat like a regular $C0EC latch load?
UpdateBitStreamPositionAndDiskCycle(uExecutedCycles); // Fix E7-copy protection
}
}
//===========================================================================
void __stdcall Disk2InterfaceCard::SetReadMode(WORD, WORD, BYTE, BYTE, ULONG)
{
m_floppyWriteMode = 0;
m_formatTrack.DriveSwitchedToReadMode(&m_floppyDrive[m_currDrive].m_disk);
#if LOG_DISK_RW_MODE
LOG_DISK("rw mode: read\r\n");
#endif
}
//===========================================================================
void __stdcall Disk2InterfaceCard::SetWriteMode(WORD, WORD, BYTE, BYTE, ULONG uExecutedCycles)
{
m_floppyWriteMode = 1;
m_formatTrack.DriveSwitchedToWriteMode(m_floppyDrive[m_currDrive].m_disk.m_byte);
BOOL modechange = !m_floppyDrive[m_currDrive].m_writelight;
#if LOG_DISK_RW_MODE
LOG_DISK("rw mode: write (mode changed=%d)\r\n", modechange ? 1 : 0);
#endif
#if LOG_DISK_NIBBLES_WRITE
m_uWriteLastCycle = 0;
#endif
m_floppyDrive[m_currDrive].m_writelight = WRITELIGHT_CYCLES;
if (modechange)
FrameDrawDiskLEDS( (HDC)0 );
}
//===========================================================================
void Disk2InterfaceCard::UpdateDriveState(DWORD cycles)
{
int loop = NUM_DRIVES;
while (loop--)
{
FloppyDrive* pDrive = &m_floppyDrive[loop];
if (pDrive->m_spinning && !m_floppyMotorOn)
{
if (!(pDrive->m_spinning -= MIN(pDrive->m_spinning, cycles)))
{
FrameDrawDiskLEDS( (HDC)0 );
FrameDrawDiskStatus( (HDC)0 );
}
}
if (m_floppyWriteMode && (m_currDrive == loop) && pDrive->m_spinning)
{
pDrive->m_writelight = WRITELIGHT_CYCLES;
}
else if (pDrive->m_writelight)
{
if (!(pDrive->m_writelight -= MIN(pDrive->m_writelight, cycles)))
{
FrameDrawDiskLEDS( (HDC)0 );
FrameDrawDiskStatus( (HDC)0 );
}
}
}
}
//===========================================================================
bool Disk2InterfaceCard::DriveSwap(void)
{
// Refuse to swap if either Disk][ is active
// TODO: if Shift-Click then FORCE drive swap to bypass message
if (m_floppyDrive[DRIVE_1].m_spinning || m_floppyDrive[DRIVE_2].m_spinning)
{
// 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"
"\tAttempting to swap a disk while a drive is on\n"
"\t\t--> is NOT recommended <--\n"
"\tas this will most likely read/write incorrect data!\n"
"\n"
"If the other drive is empty then swapping is harmless. The"
" computer will appear to 'hang' trying to read non-existent data but"
" 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
}
}
FlushCurrentTrack(DRIVE_1);
FlushCurrentTrack(DRIVE_2);
// Swap disks between drives
// . NB. We swap trackimage ptrs (so don't need to swap the buffers' data)
std::swap(m_floppyDrive[DRIVE_1].m_disk, m_floppyDrive[DRIVE_2].m_disk);
// Invalidate the trackimage so that a read latch will re-read the track for the new floppy (GH#543)
m_floppyDrive[DRIVE_1].m_disk.m_trackimagedata = false;
m_floppyDrive[DRIVE_2].m_disk.m_trackimagedata = false;
SaveLastDiskImage(DRIVE_1);
SaveLastDiskImage(DRIVE_2);
FrameRefreshStatus(DRAW_LEDS | DRAW_BUTTON_DRIVES, false);
return true;
}
//===========================================================================
// TODO: LoadRom_Disk_Floppy()
void Disk2InterfaceCard::Initialize(LPBYTE pCxRomPeripheral, UINT uSlot)
{
const UINT DISK2_FW_SIZE = APPLE_SLOT_SIZE;
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;
memcpy(pCxRomPeripheral + uSlot*APPLE_SLOT_SIZE, pData, DISK2_FW_SIZE);
// Note: We used to disable the track stepping delay in the Disk II controller firmware by
// 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
// . Patching the firmware breaks the ADC checksum used by "The CIA Files" (Tricky Dick)
// . In this case we can patch to compensate for an ADC or EOR checksum but not both (nickw)
RegisterIoHandler(uSlot, &Disk2InterfaceCard::IORead, &Disk2InterfaceCard::IOWrite, NULL, NULL, this, NULL);
m_slot = uSlot;
}
//===========================================================================
BYTE __stdcall Disk2InterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
{
UINT uSlot = ((addr & 0xff) >> 4) - 8;
Disk2InterfaceCard* pCard = (Disk2InterfaceCard*) MemGetSlotParameters(uSlot);
ImageInfo* pImage = pCard->m_floppyDrive[pCard->m_currDrive].m_disk.m_imagehandle;
bool isWOZ = ImageIsWOZ(pImage);
switch (addr & 0xF)
{
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: if (!isWOZ) 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;
}
// only even addresses return the latch (UTAIIe Table 9.1)
if (!(addr & 1))
{
if (isWOZ)
pCard->DataLatchReadWriteWOZ(pc, addr, bWrite, d, nExecutedCycles);
return pCard->m_floppyLatch;
}
return MemReadFloatingBus(nExecutedCycles);
}
BYTE __stdcall Disk2InterfaceCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
{
UINT uSlot = ((addr & 0xff) >> 4) - 8;
Disk2InterfaceCard* pCard = (Disk2InterfaceCard*) MemGetSlotParameters(uSlot);
ImageInfo* pImage = pCard->m_floppyDrive[pCard->m_currDrive].m_disk.m_imagehandle;
bool isWOZ = ImageIsWOZ(pImage);
switch (addr & 0xF)
{
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: if (!isWOZ) 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;
}
// any address writes the latch via sequencer LD command (74LS323 datasheet)
if (pCard->m_floppyWriteMode /* && m_floppyLoadMode */)
{
pCard->m_floppyLatch = d;
if (isWOZ)
pCard->DataLatchReadWriteWOZ(pc, addr, bWrite, d, nExecutedCycles);
}
return 0;
}
//===========================================================================
// Unit version history:
// 2: Added: Format Track state & DiskLastCycle
// 3: Added: DiskLastReadLatchCycle
// 4: Added: WOZ state
// Split up 'Unit' putting some state into a new 'Floppy'
static const UINT kUNIT_VERSION = 4;
#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"
#define SS_YAML_KEY_LAST_CYCLE "Last Cycle"
#define SS_YAML_KEY_LAST_READ_LATCH_CYCLE "Last Read Latch Cycle"
#define SS_YAML_KEY_LSS_SHIFT_REG "LSS Shift Reg"
#define SS_YAML_KEY_LSS_LATCH_DELAY "LSS Latch Delay"
#define SS_YAML_KEY_LSS_RESET_SEQUENCER "LSS Reset Sequencer"
#define SS_YAML_KEY_DISK2UNIT "Unit"
#define SS_YAML_KEY_FILENAME "Filename"
#define SS_YAML_KEY_PHASE "Phase"
#define SS_YAML_KEY_PHASE_PRECISE "Phase (precise)"
#define SS_YAML_KEY_TRACK "Track" // deprecated at v4
#define SS_YAML_KEY_HEAD_WINDOW "Head Window"
#define SS_YAML_KEY_LAST_STEPPER_CYCLE "Last Stepper Cycle"
#define SS_YAML_KEY_FLOPPY "Floppy"
#define SS_YAML_KEY_BYTE "Byte"
#define SS_YAML_KEY_NIBBLES "Nibbles"
#define SS_YAML_KEY_BIT_OFFSET "Bit Offset"
#define SS_YAML_KEY_BIT_COUNT "Bit Count"
#define SS_YAML_KEY_EXTRA_CYCLES "Extra Cycles"
#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_TRACK_IMAGE_DATA "Track Image Data"
#define SS_YAML_KEY_TRACK_IMAGE_DIRTY "Track Image Dirty"
#define SS_YAML_KEY_TRACK_IMAGE "Track Image"
std::string Disk2InterfaceCard::GetSnapshotCardName(void)
{
static const std::string name(SS_YAML_VALUE_CARD_DISK2);
return name;
}
void Disk2InterfaceCard::SaveSnapshotFloppy(YamlSaveHelper& yamlSaveHelper, UINT unit)
{
YamlSaveHelper::Label label(yamlSaveHelper, "%s:\n", SS_YAML_KEY_FLOPPY);
yamlSaveHelper.SaveString(SS_YAML_KEY_FILENAME, m_floppyDrive[unit].m_disk.m_fullname);
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_BYTE, m_floppyDrive[unit].m_disk.m_byte);
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_NIBBLES, m_floppyDrive[unit].m_disk.m_nibbles);
yamlSaveHelper.SaveHexUint32(SS_YAML_KEY_BIT_OFFSET, m_floppyDrive[unit].m_disk.m_bitOffset); // v4
yamlSaveHelper.SaveHexUint32(SS_YAML_KEY_BIT_COUNT, m_floppyDrive[unit].m_disk.m_bitCount); // v4
yamlSaveHelper.SaveDouble(SS_YAML_KEY_EXTRA_CYCLES, m_floppyDrive[unit].m_disk.m_extraCycles); // v4
yamlSaveHelper.SaveBool(SS_YAML_KEY_WRITE_PROTECTED, m_floppyDrive[unit].m_disk.m_bWriteProtected);
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);
if (m_floppyDrive[unit].m_disk.m_trackimage)
{
YamlSaveHelper::Label image(yamlSaveHelper, "%s:\n", SS_YAML_KEY_TRACK_IMAGE);
yamlSaveHelper.SaveMemory(m_floppyDrive[unit].m_disk.m_trackimage, NIBBLES_PER_TRACK);
}
}
void Disk2InterfaceCard::SaveSnapshotDriveUnit(YamlSaveHelper& yamlSaveHelper, UINT unit)
{
YamlSaveHelper::Label label(yamlSaveHelper, "%s%d:\n", SS_YAML_KEY_DISK2UNIT, unit);
yamlSaveHelper.SaveUint(SS_YAML_KEY_PHASE, m_floppyDrive[unit].m_phase);
yamlSaveHelper.SaveFloat(SS_YAML_KEY_PHASE_PRECISE, m_floppyDrive[unit].m_phasePrecise); // v4
yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_HEAD_WINDOW, m_floppyDrive[unit].m_headWindow); // v4
yamlSaveHelper.SaveHexUint64(SS_YAML_KEY_LAST_STEPPER_CYCLE, m_floppyDrive[unit].m_lastStepperCycle); // v4
yamlSaveHelper.SaveUint(SS_YAML_KEY_SPINNING, m_floppyDrive[unit].m_spinning);
yamlSaveHelper.SaveUint(SS_YAML_KEY_WRITE_LIGHT, m_floppyDrive[unit].m_writelight);
SaveSnapshotFloppy(yamlSaveHelper, unit);
}
void Disk2InterfaceCard::SaveSnapshot(class YamlSaveHelper& yamlSaveHelper)
{
YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_VERSION);
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE);
yamlSaveHelper.SaveUint(SS_YAML_KEY_CURRENT_DRIVE, m_currDrive);
yamlSaveHelper.SaveHexUint4(SS_YAML_KEY_PHASES, m_magnetStates);
yamlSaveHelper.SaveBool(SS_YAML_KEY_DISK_ACCESSED, false); // deprecated
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
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_LSS_SHIFT_REG, m_shiftReg); // v4
yamlSaveHelper.SaveInt(SS_YAML_KEY_LSS_LATCH_DELAY, m_latchDelay); // v4
yamlSaveHelper.SaveBool(SS_YAML_KEY_LSS_RESET_SEQUENCER, m_resetSequencer); // v4
m_formatTrack.SaveSnapshot(yamlSaveHelper); // v2
SaveSnapshotDriveUnit(yamlSaveHelper, DRIVE_1);
SaveSnapshotDriveUnit(yamlSaveHelper, DRIVE_2);
}
bool Disk2InterfaceCard::LoadSnapshotFloppy(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector<BYTE>& track)
{
std::string filename = yamlLoadHelper.LoadString(SS_YAML_KEY_FILENAME);
bool bImageError = filename.empty();
if (!bImageError)
{
DWORD dwAttributes = GetFileAttributes(filename.c_str());
if (dwAttributes == INVALID_FILE_ATTRIBUTES)
{
// Get user to browse for file
UserSelectNewDiskImage(unit, filename.c_str());
dwAttributes = GetFileAttributes(filename.c_str());
}
bImageError = (dwAttributes == INVALID_FILE_ATTRIBUTES);
if (!bImageError)
{
if (InsertDisk(unit, filename.c_str(), dwAttributes & FILE_ATTRIBUTE_READONLY, IMAGE_DONT_CREATE) != eIMAGE_ERROR_NONE)
bImageError = true;
// DiskInsert() zeros m_floppyDrive[unit], then sets up:
// . m_imagename
// . m_fullname
// . m_bWriteProtected
}
}
yamlLoadHelper.LoadBool(SS_YAML_KEY_WRITE_PROTECTED); // Consume
m_floppyDrive[unit].m_disk.m_byte = yamlLoadHelper.LoadUint(SS_YAML_KEY_BYTE);
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;
if (version >= 4)
{
m_floppyDrive[unit].m_disk.m_bitOffset = yamlLoadHelper.LoadUint(SS_YAML_KEY_BIT_OFFSET);
m_floppyDrive[unit].m_disk.m_bitCount = yamlLoadHelper.LoadUint(SS_YAML_KEY_BIT_COUNT);
m_floppyDrive[unit].m_disk.m_extraCycles = yamlLoadHelper.LoadDouble(SS_YAML_KEY_EXTRA_CYCLES);
if (m_floppyDrive[unit].m_disk.m_bitCount && (m_floppyDrive[unit].m_disk.m_bitOffset >= m_floppyDrive[unit].m_disk.m_bitCount))
throw std::string("Disk image: bitOffset >= bitCount");
if (ImageIsWOZ(m_floppyDrive[unit].m_disk.m_imagehandle))
UpdateBitStreamOffsets(m_floppyDrive[unit].m_disk); // overwrites m_byte, inits m_bitMask
}
if (yamlLoadHelper.GetSubMap(SS_YAML_KEY_TRACK_IMAGE))
{
yamlLoadHelper.LoadMemory(&track[0], NIBBLES_PER_TRACK);
yamlLoadHelper.PopMap();
}
return bImageError;
}
bool Disk2InterfaceCard::LoadSnapshotDriveUnitv3(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector<BYTE>& track)
{
_ASSERT(version <= 3);
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 = LoadSnapshotFloppy(yamlLoadHelper, unit, version, track);
yamlLoadHelper.LoadUint(SS_YAML_KEY_TRACK); // consume
m_floppyDrive[unit].m_phase = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASE);
m_floppyDrive[unit].m_phasePrecise = (float) m_floppyDrive[unit].m_phase;
m_floppyDrive[unit].m_spinning = yamlLoadHelper.LoadUint(SS_YAML_KEY_SPINNING);
m_floppyDrive[unit].m_writelight = yamlLoadHelper.LoadUint(SS_YAML_KEY_WRITE_LIGHT);
yamlLoadHelper.PopMap();
return bImageError;
}
bool Disk2InterfaceCard::LoadSnapshotDriveUnitv4(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector<BYTE>& track)
{
_ASSERT(version >= 4);
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;
if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_FLOPPY))
throw std::string("Card: Expected key: ") + SS_YAML_KEY_FLOPPY;
bool bImageError = LoadSnapshotFloppy(yamlLoadHelper, unit, version, track);
yamlLoadHelper.PopMap();
//
m_floppyDrive[unit].m_phase = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASE);
m_floppyDrive[unit].m_phasePrecise = yamlLoadHelper.LoadFloat(SS_YAML_KEY_PHASE_PRECISE);
m_floppyDrive[unit].m_headWindow = yamlLoadHelper.LoadUint(SS_YAML_KEY_HEAD_WINDOW) & 0xf;
m_floppyDrive[unit].m_lastStepperCycle = yamlLoadHelper.LoadUint64(SS_YAML_KEY_LAST_STEPPER_CYCLE);
m_floppyDrive[unit].m_spinning = yamlLoadHelper.LoadUint(SS_YAML_KEY_SPINNING);
m_floppyDrive[unit].m_writelight = yamlLoadHelper.LoadUint(SS_YAML_KEY_WRITE_LIGHT);
yamlLoadHelper.PopMap();
return bImageError;
}
void Disk2InterfaceCard::LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version)
{
bool bImageError = false;
std::vector<BYTE> track(NIBBLES_PER_TRACK);
if (version <= 3)
bImageError = LoadSnapshotDriveUnitv3(yamlLoadHelper, unit, version, track);
else
bImageError = LoadSnapshotDriveUnitv4(yamlLoadHelper, unit, version, track);
if (!bImageError)
{
if ((m_floppyDrive[unit].m_disk.m_trackimage == NULL) && m_floppyDrive[unit].m_disk.m_nibbles)
AllocTrack(unit);
if (m_floppyDrive[unit].m_disk.m_trackimage == NULL)
bImageError = true;
else
memcpy(m_floppyDrive[unit].m_disk.m_trackimage, &track[0], NIBBLES_PER_TRACK);
}
if (bImageError)
{
m_floppyDrive[unit].m_disk.m_trackimagedata = false;
m_floppyDrive[unit].m_disk.m_trackimagedirty = false;
m_floppyDrive[unit].m_disk.m_nibbles = 0;
}
}
bool Disk2InterfaceCard::LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT slot, UINT version)
{
if (slot != 6) // fixme
throw std::string("Card: wrong slot");
if (version < 1 || version > kUNIT_VERSION)
throw std::string("Card: wrong version");
m_currDrive = yamlLoadHelper.LoadUint(SS_YAML_KEY_CURRENT_DRIVE);
m_magnetStates = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASES);
(void) yamlLoadHelper.LoadBool(SS_YAML_KEY_DISK_ACCESSED); // deprecated - but retrieve the value to avoid the "State: Unknown key (Disk Accessed)" warning
m_enhanceDisk = yamlLoadHelper.LoadBool(SS_YAML_KEY_ENHANCE_DISK);
m_floppyLatch = yamlLoadHelper.LoadUint(SS_YAML_KEY_FLOPPY_LATCH);
m_floppyMotorOn = yamlLoadHelper.LoadBool(SS_YAML_KEY_FLOPPY_MOTOR_ON);
m_floppyWriteMode = yamlLoadHelper.LoadBool(SS_YAML_KEY_FLOPPY_WRITE_MODE);
if (version >= 2)
{
m_diskLastCycle = yamlLoadHelper.LoadUint64(SS_YAML_KEY_LAST_CYCLE);
m_formatTrack.LoadSnapshot(yamlLoadHelper);
}
if (version >= 3)
{
m_diskLastReadLatchCycle = yamlLoadHelper.LoadUint64(SS_YAML_KEY_LAST_READ_LATCH_CYCLE);
}
if (version >= 4)
{
m_shiftReg = yamlLoadHelper.LoadUint(SS_YAML_KEY_LSS_SHIFT_REG) & 0xff;
m_latchDelay = yamlLoadHelper.LoadInt(SS_YAML_KEY_LSS_LATCH_DELAY);
m_resetSequencer = yamlLoadHelper.LoadBool(SS_YAML_KEY_LSS_RESET_SEQUENCER);
}
// Eject all disks first in case Drive-2 contains disk to be inserted into Drive-1
for (UINT i=0; i<NUM_DRIVES; i++)
{
EjectDisk(i); // Remove any disk & update Registry to reflect empty drive
m_floppyDrive[i].clear();
}
LoadSnapshotDriveUnit(yamlLoadHelper, DRIVE_1, version);
LoadSnapshotDriveUnit(yamlLoadHelper, DRIVE_2, version);
FrameRefreshStatus(DRAW_LEDS | DRAW_BUTTON_DRIVES);
return true;
}