From 4bc75093b8571a56adaa6028cdeb4adc706e184a Mon Sep 17 00:00:00 2001 From: TomCh Date: Fri, 5 Jul 2019 23:01:19 +0100 Subject: [PATCH] Support (read-only) WOZ1/WOZ2 images (#544) (PR #653) Supports: - all "woz test images" v1.3 (WOZ1, WOZ2) are working, except 3.5" - additionally: Frogger (spiradisc), Choplifter (not Enhanced //e!), Lode Runner, Marble Madness, Skyfox. - woz images can be .gz or .zip compressed (ie. same as other supported images) - save-state Limitations: - read-only, so WOZ images are forced to be write-protected . as a result, games that need r/w images won't work (Stickybear Town Builder, Wizardry) - 5.25" only (not 3.5") --- source/Applewin.cpp | 4 +- source/Debugger/Debug.cpp | 15 +- source/Disk.cpp | 674 +++++++++++++++++++++++++++------- source/Disk.h | 69 +++- source/DiskDefs.h | 4 +- source/DiskFormatTrack.cpp | 5 +- source/DiskImage.cpp | 63 +++- source/DiskImage.h | 9 +- source/DiskImageHelper.cpp | 380 +++++++++++++++---- source/DiskImageHelper.h | 120 +++++- source/Keyboard.cpp | 2 +- source/Memory.cpp | 2 + source/SaveState_Structs_v1.h | 2 +- source/YamlHelper.cpp | 40 +- source/YamlHelper.h | 6 +- 15 files changed, 1133 insertions(+), 262 deletions(-) diff --git a/source/Applewin.cpp b/source/Applewin.cpp index 17270177..9b9cd088 100644 --- a/source/Applewin.cpp +++ b/source/Applewin.cpp @@ -138,6 +138,7 @@ void LogFileTimeUntilFirstKeyReadReset(void) // Log the time from emulation restart/reboot until the first key read: BIT $C000 // . AZTEC.DSK (DOS 3.3) does prior LDY $C000 reads, but the BIT $C000 is at the "Press any key" message // . Phasor1.dsk / ProDOS 1.1.1: PC=E797: B1 50: LDA ($50),Y / "Select an Option:" message +// . Rescue Raiders v1.3,v1.5: PC=895: LDA $C000 / boot to intro void LogFileTimeUntilFirstKeyRead(void) { if (!g_fh || bLogKeyReadDone) @@ -145,6 +146,7 @@ void LogFileTimeUntilFirstKeyRead(void) if ( (mem[regs.pc-3] != 0x2C) // AZTEC: bit $c000 && !((regs.pc-2) == 0xE797 && mem[regs.pc-2] == 0xB1 && mem[regs.pc-1] == 0x50) // Phasor1: lda ($50),y + && !((regs.pc-3) == 0x0895 && mem[regs.pc-3] == 0xAD) // Rescue Raiders v1.3,v1.5: lda $c000 ) return; @@ -1638,7 +1640,7 @@ int APIENTRY WinMain(HINSTANCE passinstance, HINSTANCE, LPSTR lpCmdLine, int) } // Need to test if it's safe to call ResetMachineState(). In the meantime, just call DiskReset(): - sg_Disk2Card.Reset(); // Switch from a booting A][+ to a non-autostart A][, so need to turn off floppy motor + sg_Disk2Card.Reset(true); // Switch from a booting A][+ to a non-autostart A][, so need to turn off floppy motor LogFileOutput("Main: DiskReset()\n"); HD_Reset(); // GH#515 LogFileOutput("Main: HDDReset()\n"); diff --git a/source/Debugger/Debug.cpp b/source/Debugger/Debug.cpp index c29e3236..c9ff3689 100644 --- a/source/Debugger/Debug.cpp +++ b/source/Debugger/Debug.cpp @@ -3727,15 +3727,16 @@ Update_t CmdDisk ( int nArgs) if (nArgs > 2) goto _Help; - int drive = sg_Disk2Card.GetCurrentDrive() + 1; char buffer[200] = ""; - ConsoleBufferPushFormat(buffer, "D%d at T$%X (%d), phase $%X, offset $%X, %s", - drive, - sg_Disk2Card.GetCurrentTrack(), - sg_Disk2Card.GetCurrentTrack(), - sg_Disk2Card.GetCurrentPhase(), + ConsoleBufferPushFormat(buffer, "D%d at T$%s, phase $%s, offset $%X, mask $%02X, extraCycles %.2f, %s", + sg_Disk2Card.GetCurrentDrive() + 1, + sg_Disk2Card.GetCurrentTrackString().c_str(), + sg_Disk2Card.GetCurrentPhaseString().c_str(), sg_Disk2Card.GetCurrentOffset(), - sg_Disk2Card.GetCurrentState()); + sg_Disk2Card.GetCurrentLSSBitMask(), + sg_Disk2Card.GetCurrentExtraCycles(), + sg_Disk2Card.GetCurrentState() + ); return ConsoleUpdate(); } diff --git a/source/Disk.cpp b/source/Disk.cpp index c7ea7073..33b0355d 100644 --- a/source/Disk.cpp +++ b/source/Disk.cpp @@ -56,18 +56,17 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Disk2InterfaceCard::Disk2InterfaceCard(void) { - m_currDrive = 0; + ResetSwitches(); + 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; + ResetLogicStateSequencer(); + // Debug: #if LOG_DISK_NIBBLES_USE_RUNTIME_VAR m_bLogDisk_NibblesRW = false; @@ -82,11 +81,40 @@ 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 m_floppyDrive[m_currDrive].m_track; } -int Disk2InterfaceCard::GetCurrentPhase(void) { return m_floppyDrive[m_currDrive].m_phase; } +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; } -int Disk2InterfaceCard::GetTrack(const int drive) { return m_floppyDrive[drive].m_track; } +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[m_currDrive].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) @@ -175,7 +203,7 @@ void Disk2InterfaceCard::SaveLastDiskImage(const int drive) //=========================================================================== // Called by ControlMotor() & Enable() -void Disk2InterfaceCard::CheckSpinning(const ULONG nExecutedCycles) +void Disk2InterfaceCard::CheckSpinning(const ULONG uExecutedCycles) { DWORD modechange = (m_floppyMotorOn && !m_floppyDrive[m_currDrive].m_spinning); @@ -188,7 +216,7 @@ void Disk2InterfaceCard::CheckSpinning(const ULONG nExecutedCycles) if (modechange) { // Set m_diskLastCycle when motor changes: not spinning (ie. off for 1 sec) -> on - CpuCalcCycles(nExecutedCycles); + CpuCalcCycles(uExecutedCycles); m_diskLastCycle = g_nCumulativeCycles; } } @@ -237,7 +265,7 @@ void Disk2InterfaceCard::AllocTrack(const int drive) //=========================================================================== -void Disk2InterfaceCard::ReadTrack(const int drive) +void Disk2InterfaceCard::ReadTrack(const int drive, ULONG uExecutedCycles) { if (! IsDriveValid( drive )) return; @@ -245,8 +273,9 @@ void Disk2InterfaceCard::ReadTrack(const int drive) FloppyDrive* pDrive = &m_floppyDrive[ drive ]; FloppyDisk* pFloppy = &pDrive->m_disk; - if (pDrive->m_track >= ImageGetNumTracks(pFloppy->m_imagehandle)) + 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; } @@ -257,17 +286,45 @@ void Disk2InterfaceCard::ReadTrack(const int drive) if (pFloppy->m_trackimage && pFloppy->m_imagehandle) { #if LOG_DISK_TRACKS - LOG_DISK("track $%02X%s read\r\n", pDrive->m_track, (pDrive->m_phase & 1) ? ".5" : " "); + 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 / 1000.0)); #endif + const UINT32 currentPosition = pFloppy->m_byte; + const UINT32 currentTrackLength = pFloppy->m_nibbles; + ImageReadTrack( pFloppy->m_imagehandle, - pDrive->m_track, - pDrive->m_phase, + pDrive->m_phasePrecise, pFloppy->m_trackimage, &pFloppy->m_nibbles, + &pFloppy->m_bitCount, m_enhanceDisk); - pFloppy->m_byte = 0; + 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); } } @@ -308,8 +365,11 @@ void Disk2InterfaceCard::WriteTrack(const int drive) FloppyDrive* pDrive = &m_floppyDrive[ drive ]; FloppyDisk* pFloppy = &pDrive->m_disk; - if (pDrive->m_track >= ImageGetNumTracks(pFloppy->m_imagehandle)) + 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; @@ -317,12 +377,11 @@ void Disk2InterfaceCard::WriteTrack(const int drive) if (pFloppy->m_trackimage && pFloppy->m_imagehandle) { #if LOG_DISK_TRACKS - 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) + LOG_DISK("track $%s write\r\n", GetCurrentTrackString().c_str()); #endif ImageWriteTrack( pFloppy->m_imagehandle, - 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) + pDrive->m_phasePrecise, pFloppy->m_trackimage, pFloppy->m_nibbles); } @@ -359,7 +418,7 @@ void __stdcall Disk2InterfaceCard::ControlMotor(WORD, WORD address, BYTE, BYTE, 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_phases + // - 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 @@ -388,68 +447,75 @@ void __stdcall Disk2InterfaceCard::ControlStepper(WORD, WORD address, BYTE, BYTE #endif } - int phase = (address >> 1) & 3; - int phase_bit = (1 << phase); + // update phases (magnet states) + { + const int phase = (address >> 1) & 3; + const int phase_bit = (1 << phase); -#if 1 - // update the magnet states - if (address & 1) - { - // phase on - m_phases |= phase_bit; - } - else - { - // phase off - m_phases &= ~phase_bit; + // 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 + // - 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_phases & (1 << ((pDrive->m_phase + 1) & 3))) + if (m_magnetStates & (1 << ((pDrive->m_phase + 1) & 3))) direction += 1; - if (m_phases & (1 << ((pDrive->m_phase + 3) & 3))) + if (m_magnetStates & (1 << ((pDrive->m_phase + 3) & 3))) direction -= 1; - // apply magnet step, if any - if (direction) + // Only calculate quarterDirection for WOZ, as NIB/DSK don't support half phases. + int quarterDirection = 0; + if (ImageIsWOZ(pFloppy->m_imagehandle)) { - pDrive->m_phase = MAX(0, MIN(79, pDrive->m_phase + direction)); - const int nNumTracksInImage = ImageGetNumTracks(pFloppy->m_imagehandle); - const int newtrack = (nNumTracksInImage == 0) ? 0 - : MIN(nNumTracksInImage-1, pDrive->m_phase >> 1); // (round half tracks down) - if (newtrack != pDrive->m_track) + if ((m_magnetStates == 0xC || // 1100 + m_magnetStates == 0x6 || // 0110 + m_magnetStates == 0x3 || // 0011 + m_magnetStates == 0x9)) // 1001 { - FlushCurrentTrack(m_currDrive); - pDrive->m_track = newtrack; - pFloppy->m_trackimagedata = false; - - m_formatTrack.DriveNotWritingTrack(); + quarterDirection = direction; + direction = 0; } - - // Feature Request #201 Show track status - // https://github.com/AppleWin/AppleWin/issues/201 - FrameDrawDiskStatus( (HDC)0 ); } -#else - // substitute alternate stepping code here to test -#endif + + 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 $%02X%s phases %d%d%d%d phase %d %s address $%4X\r\n", - pDrive->m_phase >> 1, - (pDrive->m_phase & 1) ? ".5" : " ", - (m_phases >> 3) & 1, - (m_phases >> 2) & 1, - (m_phases >> 1) & 1, - (m_phases >> 0) & 1, - phase, + 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, + m_magnetStates, (address & 1) ? "on " : "off", - address); + address, + ((float)cycleDelta)/(CLK_6502/1000.0)); #endif } @@ -797,14 +863,14 @@ bool Disk2InterfaceCard::LogWriteCheckSyncFF(ULONG& uCycleDelta) //=========================================================================== -void __stdcall Disk2InterfaceCard::ReadWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles) +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); + ReadTrack(m_currDrive, uExecutedCycles); if (!pFloppy->m_trackimagedata) { @@ -814,7 +880,7 @@ void __stdcall Disk2InterfaceCard::ReadWrite(WORD pc, WORD addr, BYTE bWrite, BY // Improve precision of "authentic" drive mode - GH#125 UINT uSpinNibbleCount = 0; - CpuCalcCycles(nExecutedCycles); // g_nCumulativeCycles required for uSpinNibbleCount & LogWriteCheckSyncFF() + CpuCalcCycles(uExecutedCycles); // g_nCumulativeCycles required for uSpinNibbleCount & LogWriteCheckSyncFF() if (!m_enhanceDisk && pDrive->m_spinning) { @@ -877,6 +943,9 @@ void __stdcall Disk2InterfaceCard::ReadWrite(WORD pc, WORD addr, BYTE bWrite, BY } 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; @@ -911,17 +980,247 @@ void __stdcall Disk2InterfaceCard::ReadWrite(WORD pc, WORD addr, BYTE bWrite, BY //=========================================================================== +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: 11->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::ReadWriteWOZ(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 (!m_floppyWriteMode) + { + // m_diskLastReadLatchCycle = g_nCumulativeCycles; // Not used by WOZ (only by NIB) +#if LOG_DISK_NIBBLES_READ + bool newLatchData = false; +#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 + { + if (m_latchDelay == 0) + m_latchDelay = 4; // extend for another 4us + + 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 + } + else if (!floppy.m_bWriteProtected) // && m_floppyWriteMode + { + //TODO + } + + // Show track status (GH#201) - NB. Prevent flooding of forcing UI to redraw!!! + if ((floppy.m_byte & 0xFF) == 0) + FrameDrawDiskStatus( (HDC)0 ); +} + +//=========================================================================== + void Disk2InterfaceCard::Reset(const bool bIsPowerCycle/*=false*/) { // RESET forces all switches off (UTAIIe Table 9.1) - m_currDrive = 0; - m_floppyMotorOn = 0; - m_floppyLoadMode = 0; - m_floppyWriteMode = 0; - m_phases = 0; + ResetSwitches(); m_formatTrack.Reset(); + ResetLogicStateSequencer(); + if (bIsPowerCycle) // GH#460 { // NB. This doesn't affect the drive head (ie. drive's track position) @@ -937,6 +1236,15 @@ void Disk2InterfaceCard::Reset(const bool bIsPowerCycle/*=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/*=""*/) @@ -958,8 +1266,8 @@ bool Disk2InterfaceCard::UserSelectNewDiskImage(const int drive, LPCSTR pszFilen ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = g_hFrameWindow; ofn.hInstance = g_hInstance; - 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") + 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; @@ -990,7 +1298,7 @@ bool Disk2InterfaceCard::UserSelectNewDiskImage(const int drive, LPCSTR pszFilen //=========================================================================== -void __stdcall Disk2InterfaceCard::LoadWriteProtect(WORD, WORD, BYTE write, BYTE value, ULONG) +void __stdcall Disk2InterfaceCard::LoadWriteProtect(WORD, WORD, BYTE write, BYTE value, ULONG uExecutedCycles) { /* m_floppyLoadMode = 1; */ @@ -1007,11 +1315,22 @@ void __stdcall Disk2InterfaceCard::LoadWriteProtect(WORD, WORD, BYTE write, BYTE // . 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 + } } //=========================================================================== @@ -1181,6 +1500,9 @@ BYTE __stdcall Disk2InterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE 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; @@ -1195,7 +1517,9 @@ BYTE __stdcall Disk2InterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE 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 0xC: if (!isWOZ) pCard->ReadWrite(pc, addr, bWrite, d, nExecutedCycles); + else pCard->ReadWriteWOZ(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; @@ -1213,6 +1537,9 @@ BYTE __stdcall Disk2InterfaceCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE 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; @@ -1227,7 +1554,9 @@ BYTE __stdcall Disk2InterfaceCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE 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 0xC: if (!isWOZ) pCard->ReadWrite(pc, addr, bWrite, d, nExecutedCycles); + else pCard->ReadWriteWOZ(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; @@ -1246,7 +1575,9 @@ BYTE __stdcall Disk2InterfaceCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE // Unit version history: // 2: Added: Format Track state & DiskLastCycle // 3: Added: DiskLastReadLatchCycle -static const UINT kUNIT_VERSION = 3; +// 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][" @@ -1259,16 +1590,27 @@ static const UINT kUNIT_VERSION = 3; #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_TRACK "Track" #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_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" @@ -1279,17 +1621,16 @@ std::string Disk2InterfaceCard::GetSnapshotCardName(void) return name; } -void Disk2InterfaceCard::SaveSnapshotDisk2Unit(YamlSaveHelper& yamlSaveHelper, UINT unit) +void Disk2InterfaceCard::SaveSnapshotFloppy(YamlSaveHelper& yamlSaveHelper, UINT unit) { - YamlSaveHelper::Label label(yamlSaveHelper, "%s%d:\n", SS_YAML_KEY_DISK2UNIT, 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.SaveUint(SS_YAML_KEY_TRACK, m_floppyDrive[unit].m_track); - yamlSaveHelper.SaveUint(SS_YAML_KEY_PHASE, m_floppyDrive[unit].m_phase); 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); - yamlSaveHelper.SaveUint(SS_YAML_KEY_SPINNING, m_floppyDrive[unit].m_spinning); - yamlSaveHelper.SaveUint(SS_YAML_KEY_WRITE_LIGHT, m_floppyDrive[unit].m_writelight); 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); @@ -1300,13 +1641,26 @@ void Disk2InterfaceCard::SaveSnapshotDisk2Unit(YamlSaveHelper& yamlSaveHelper, U } } +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.SaveHexUint4(SS_YAML_KEY_PHASES, m_phases); 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); @@ -1314,29 +1668,24 @@ void Disk2InterfaceCard::SaveSnapshot(class YamlSaveHelper& yamlSaveHelper) 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 - SaveSnapshotDisk2Unit(yamlSaveHelper, DRIVE_1); - SaveSnapshotDisk2Unit(yamlSaveHelper, DRIVE_2); + SaveSnapshotDriveUnit(yamlSaveHelper, DRIVE_1); + SaveSnapshotDriveUnit(yamlSaveHelper, DRIVE_2); } -void Disk2InterfaceCard::LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, UINT unit) +bool Disk2InterfaceCard::LoadSnapshotFloppy(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track) { - 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; - - 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) - std::string filename = yamlLoadHelper.LoadString(SS_YAML_KEY_FILENAME); - if (!filename.empty()) + bool bImageError = filename.empty(); + + if (!bImageError) { DWORD dwAttributes = GetFileAttributes(filename.c_str()); - if(dwAttributes == INVALID_FILE_ATTRIBUTES) + if (dwAttributes == INVALID_FILE_ATTRIBUTES) { // Get user to browse for file UserSelectNewDiskImage(unit, filename.c_str()); @@ -1347,38 +1696,106 @@ void Disk2InterfaceCard::LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, U bImageError = (dwAttributes == INVALID_FILE_ATTRIBUTES); if (!bImageError) { - if(InsertDisk(unit, filename.c_str(), dwAttributes & FILE_ATTRIBUTE_READONLY, IMAGE_DONT_CREATE) != eIMAGE_ERROR_NONE) + 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: - // . imagename - // . fullname - // . writeprotected + // . m_imagename + // . m_fullname + // . m_bWriteProtected } } - m_floppyDrive[unit].m_track = yamlLoadHelper.LoadUint(SS_YAML_KEY_TRACK); - m_floppyDrive[unit].m_phase = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASE); - m_floppyDrive[unit].m_disk.m_byte = yamlLoadHelper.LoadUint(SS_YAML_KEY_BYTE); yamlLoadHelper.LoadBool(SS_YAML_KEY_WRITE_PROTECTED); // Consume - m_floppyDrive[unit].m_spinning = yamlLoadHelper.LoadUint(SS_YAML_KEY_SPINNING); - m_floppyDrive[unit].m_writelight = yamlLoadHelper.LoadUint(SS_YAML_KEY_WRITE_LIGHT); - 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; + 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 + } - std::vector track(NIBBLES_PER_TRACK); 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& 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& 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(); // - if (!filename.empty() && !bImageError) + 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 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); @@ -1391,9 +1808,9 @@ void Disk2InterfaceCard::LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, U 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; + m_floppyDrive[unit].m_disk.m_trackimagedata = false; + m_floppyDrive[unit].m_disk.m_trackimagedirty = false; + m_floppyDrive[unit].m_disk.m_nibbles = 0; } } @@ -1405,8 +1822,8 @@ bool Disk2InterfaceCard::LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT if (version < 1 || version > kUNIT_VERSION) throw std::string("Card: wrong version"); - m_phases = yamlLoadHelper.LoadUint(SS_YAML_KEY_PHASES); - m_currDrive = yamlLoadHelper.LoadUint(SS_YAML_KEY_CURRENT_DRIVE); + 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); @@ -1424,6 +1841,13 @@ bool Disk2InterfaceCard::LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT 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 ImageInfo* m_imagehandle; // Init'd by InsertDisk() -> ImageOpen() bool m_bWriteProtected; - int m_byte; - int m_nibbles; // Init'd by ReadTrack() -> ImageReadTrack() + int m_byte; // byte offset + int m_nibbles; // # nibbles in track / Init'd by ReadTrack() -> ImageReadTrack() + UINT m_bitOffset; // bit offset + UINT m_bitCount; // # bits in track + BYTE m_bitMask; + double m_extraCycles; LPBYTE m_trackimage; bool m_trackimagedata; bool m_trackimagedirty; @@ -91,16 +99,20 @@ public: void clear() { + m_phasePrecise = 0; m_phase = 0; - m_track = 0; + m_lastStepperCycle = 0; + m_headWindow = 0; m_spinning = 0; m_writelight = 0; m_disk.clear(); } public: - int m_phase; - int m_track; + float m_phasePrecise; // Phase precise to half a phase (aka quarter track) + int m_phase; // Integral phase number + unsigned __int64 m_lastStepperCycle; + BYTE m_headWindow; DWORD m_spinning; DWORD m_writelight; FloppyDisk m_disk; @@ -132,10 +144,14 @@ public: bool GetProtect(const int drive); void SetProtect(const int drive, const bool bWriteProtect); int GetCurrentDrive(void); - int GetCurrentTrack(); - int GetTrack(const int drive); - int GetCurrentPhase(void); + int GetCurrentTrack(void); + float GetCurrentPhase(void); int GetCurrentOffset(void); + BYTE GetCurrentLSSBitMask(void); + double GetCurrentExtraCycles(void); + int GetTrack(const int drive); + std::string GetCurrentTrackString(void); + std::string GetCurrentPhaseString(void); LPCTSTR GetCurrentState(void); bool UserSelectNewDiskImage(const int drive, LPCSTR pszFilename=""); void UpdateDriveState(DWORD cycles); @@ -158,21 +174,32 @@ public: static BYTE __stdcall IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles); private: - void CheckSpinning(const ULONG nExecutedCycles); + void ResetSwitches(void); + void CheckSpinning(const ULONG uExecutedCycles); Disk_Status_e GetDriveLightStatus(const int drive); bool IsDriveValid(const int drive); void AllocTrack(const int drive); - void ReadTrack(const int drive); + void ReadTrack(const int drive, ULONG uExecutedCycles); void RemoveDisk(const int drive); void WriteTrack(const int drive); LPCTSTR DiskGetFullPathName(const int drive); - void SaveSnapshotDisk2Unit(YamlSaveHelper& yamlSaveHelper, UINT unit); - void LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, UINT unit); + void ResetLogicStateSequencer(void); + void UpdateBitStreamPositionAndDiskCycle(const ULONG uExecutedCycles); + UINT GetBitCellDelta(const BYTE optimalBitTiming); + void UpdateBitStreamPosition(FloppyDisk& floppy, const ULONG bitCellDelta); + void UpdateBitStreamOffsets(FloppyDisk& floppy); + void SaveSnapshotFloppy(YamlSaveHelper& yamlSaveHelper, UINT unit); + void SaveSnapshotDriveUnit(YamlSaveHelper& yamlSaveHelper, UINT unit); + bool LoadSnapshotFloppy(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track); + bool LoadSnapshotDriveUnitv3(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track); + bool LoadSnapshotDriveUnitv4(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version, std::vector& track); + void LoadSnapshotDriveUnit(YamlLoadHelper& yamlLoadHelper, UINT unit, UINT version); void __stdcall ControlStepper(WORD, WORD address, BYTE, BYTE, ULONG uExecutedCycles); void __stdcall ControlMotor(WORD, WORD address, BYTE, BYTE, ULONG uExecutedCycles); void __stdcall Enable(WORD, WORD address, BYTE, BYTE, ULONG uExecutedCycles); - void __stdcall ReadWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles); + void __stdcall ReadWrite(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles); + void __stdcall ReadWriteWOZ(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles); void __stdcall LoadWriteProtect(WORD, WORD, BYTE write, BYTE value, ULONG); void __stdcall SetReadMode(WORD, WORD, BYTE, BYTE, ULONG); void __stdcall SetWriteMode(WORD, WORD, BYTE, BYTE, ULONG uExecutedCycles); @@ -189,7 +216,11 @@ private: BOOL m_floppyMotorOn; BOOL m_floppyLoadMode; // for efficiency this is not used; it's extremely unlikely to affect emulation (nickw) BOOL m_floppyWriteMode; - WORD m_phases; // state bits for stepper magnet phases 0 - 3 + + // Although the magnets are a property of the drive, their state is a property of the controller card, + // since the magnets will only be on for whichever of the 2 drives is currently selected. + WORD m_magnetStates; // state bits for stepper motor magnet states (phases 0 - 3) + bool m_saveDiskImage; UINT m_slot; unsigned __int64 m_diskLastCycle; @@ -197,8 +228,14 @@ private: FormatTrack m_formatTrack; bool m_enhanceDisk; - static const UINT SPINNING_CYCLES = 20000*64; // 1280000 cycles = 1.25s - static const UINT WRITELIGHT_CYCLES = 20000*64; // 1280000 cycles = 1.25s + static const UINT SPINNING_CYCLES = 1000*1000; // 1M cycles = ~1.000s + static const UINT WRITELIGHT_CYCLES = 1000*1000; // 1M cycles = ~1.000s + + // Logic State Sequencer (for WOZ): + BYTE m_shiftReg; + int m_latchDelay; + bool m_resetSequencer; + UINT m_dbgLatchDelayedCnt; // Debug: #if LOG_DISK_NIBBLES_USE_RUNTIME_VAR diff --git a/source/DiskDefs.h b/source/DiskDefs.h index 90a2e17b..1b809218 100644 --- a/source/DiskDefs.h +++ b/source/DiskDefs.h @@ -1,5 +1,7 @@ #pragma once -#define NIBBLES_PER_TRACK 0x1A00 +#define NIBBLES_PER_TRACK_NIB 0x1A00 +#define NIBBLES_PER_TRACK_WOZ2 0x1A18 // 6680 +#define NIBBLES_PER_TRACK NIBBLES_PER_TRACK_WOZ2 // MAX(NIBBLES_PER_TRACK_NIB, NIBBLES_PER_TRACK_WOZ2) const UINT NUM_SECTORS = 16; diff --git a/source/DiskFormatTrack.cpp b/source/DiskFormatTrack.cpp index 201ac392..229fedee 100644 --- a/source/DiskFormatTrack.cpp +++ b/source/DiskFormatTrack.cpp @@ -264,17 +264,18 @@ void FormatTrack::DecodeLatchNibble(BYTE floppylatch, bool bIsWrite, bool bIsSyn m_VolTrkSecChk[i] = ((m_VolTrkSecChk4and4[i*2] & 0x55) << 1) | (m_VolTrkSecChk4and4[i*2+1] & 0x55); #if LOG_DISK_NIBBLES_READ + const bool chk = (m_VolTrkSecChk[0] ^ m_VolTrkSecChk[1] ^ m_VolTrkSecChk[2] ^ m_VolTrkSecChk[3]) == 0; if (!bIsWrite) { BYTE addrPrologue = m_bAddressPrologueIsDOS3_2 ? (BYTE)kADDR_PROLOGUE_DOS3_2 : (BYTE)kADDR_PROLOGUE_DOS3_3; - LOG_DISK("read D5AA%02X detected - Vol:%02X Trk:%02X Sec:%02X Chk:%02X\r\n", addrPrologue, m_VolTrkSecChk[0], m_VolTrkSecChk[1], m_VolTrkSecChk[2], m_VolTrkSecChk[3]); + LOG_DISK("read D5AA%02X detected - Vol:%02X Trk:%02X Sec:%02X Chk:%02X %s\r\n", addrPrologue, m_VolTrkSecChk[0], m_VolTrkSecChk[1], m_VolTrkSecChk[2], m_VolTrkSecChk[3], chk?"":"(bad)"); } #endif #if LOG_DISK_NIBBLES_WRITE if (bIsWrite) { BYTE addrPrologue = m_bAddressPrologueIsDOS3_2 ? (BYTE)kADDR_PROLOGUE_DOS3_2 : (BYTE)kADDR_PROLOGUE_DOS3_3; - LOG_DISK("write D5AA%02X detected - Vol:%02X Trk:%02X Sec:%02X Chk:%02X\r\n", addrPrologue, m_VolTrkSecChk[0], m_VolTrkSecChk[1], m_VolTrkSecChk[2], m_VolTrkSecChk[3]); + LOG_DISK("write D5AA%02X detected - Vol:%02X Trk:%02X Sec:%02X Chk:%02X %s\r\n", addrPrologue, m_VolTrkSecChk[0], m_VolTrkSecChk[1], m_VolTrkSecChk[2], m_VolTrkSecChk[3], chk?"":"(bad)"); } #endif diff --git a/source/DiskImage.cpp b/source/DiskImage.cpp index 0d2d300e..8fb3beac 100644 --- a/source/DiskImage.cpp +++ b/source/DiskImage.cpp @@ -28,6 +28,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "StdAfx.h" +#include "Common.h" #include "DiskImage.h" #include "DiskImageHelper.h" @@ -152,19 +153,21 @@ void ImageInitialize(void) //=========================================================================== void ImageReadTrack( ImageInfo* const pImageInfo, - const int nTrack, - const int nQuarterTrack, + float phase, // phase [0..79] +/- 0.5 LPBYTE pTrackImageBuffer, int* pNibbles, + UINT* pBitCount, bool enhanceDisk) { - _ASSERT(nTrack >= 0); - if (nTrack < 0) - return; + _ASSERT(phase >= 0); + if (phase < 0) + phase = 0; - if (pImageInfo->pImageType->AllowRW() && pImageInfo->ValidTrack[nTrack]) + const UINT track = pImageInfo->pImageType->PhaseToTrack(phase); + + if (pImageInfo->pImageType->AllowRW() && pImageInfo->ValidTrack[track]) { - pImageInfo->pImageType->Read(pImageInfo, nTrack, nQuarterTrack, pTrackImageBuffer, pNibbles, enhanceDisk); + pImageInfo->pImageType->Read(pImageInfo, phase, pTrackImageBuffer, pNibbles, pBitCount, enhanceDisk); } else { @@ -176,19 +179,20 @@ void ImageReadTrack( ImageInfo* const pImageInfo, //=========================================================================== void ImageWriteTrack( ImageInfo* const pImageInfo, - const int nTrack, - const int nQuarterTrack, - LPBYTE pTrackImage, + float phase, // phase [0..79] +/- 0.5 + LPBYTE pTrackImageBuffer, const int nNibbles) { - _ASSERT(nTrack >= 0); - if (nTrack < 0) - return; + _ASSERT(phase >= 0); + if (phase < 0) + phase = 0; + + const UINT track = pImageInfo->pImageType->PhaseToTrack(phase); if (pImageInfo->pImageType->AllowRW() && !pImageInfo->bWriteProtected) { - pImageInfo->pImageType->Write(pImageInfo, nTrack, nQuarterTrack, pTrackImage, nNibbles); - pImageInfo->ValidTrack[nTrack] = 1; + pImageInfo->pImageType->Write(pImageInfo, phase, pTrackImageBuffer, nNibbles); + pImageInfo->ValidTrack[track] = 1; } } @@ -220,7 +224,7 @@ bool ImageWriteBlock( ImageInfo* const pImageInfo, //=========================================================================== -int ImageGetNumTracks(ImageInfo* const pImageInfo) +UINT ImageGetNumTracks(ImageInfo* const pImageInfo) { return pImageInfo ? pImageInfo->uNumTracks : 0; } @@ -246,6 +250,33 @@ UINT ImageGetImageSize(ImageInfo* const pImageInfo) return pImageInfo ? pImageInfo->uImageSize : 0; } +bool ImageIsWOZ(ImageInfo* const pImageInfo) +{ + return pImageInfo ? (pImageInfo->pImageType->GetType() == eImageWOZ1 || pImageInfo->pImageType->GetType() == eImageWOZ2) : false; +} + +BYTE ImageGetOptimalBitTiming(ImageInfo* const pImageInfo) +{ + return pImageInfo ? pImageInfo->optimalBitTiming : 32; +} + +UINT ImagePhaseToTrack(ImageInfo* const pImageInfo, const float phase, const bool limit/*=true*/) +{ + if (!pImageInfo) + return 0; + + UINT track = pImageInfo->pImageType->PhaseToTrack(phase); + + if (limit) + { + const UINT numTracksInImage = ImageGetNumTracks(pImageInfo); + track = (numTracksInImage == 0) ? 0 + : MIN(numTracksInImage - 1, track); + } + + return track; +} + void GetImageTitle(LPCTSTR pPathname, TCHAR* pImageName, TCHAR* pFullName) { TCHAR imagetitle[ MAX_DISK_FULL_NAME+1 ]; diff --git a/source/DiskImage.h b/source/DiskImage.h index 7a8277c4..14489677 100644 --- a/source/DiskImage.h +++ b/source/DiskImage.h @@ -71,15 +71,18 @@ BOOL ImageBoot(ImageInfo* const pImageInfo); void ImageDestroy(void); void ImageInitialize(void); -void ImageReadTrack(ImageInfo* const pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk); -void ImageWriteTrack(ImageInfo* const pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles); +void ImageReadTrack(ImageInfo* const pImageInfo, float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk); +void ImageWriteTrack(ImageInfo* const pImageInfo, float phase, LPBYTE pTrackImageBuffer, int nNibbles); bool ImageReadBlock(ImageInfo* const pImageInfo, UINT nBlock, LPBYTE pBlockBuffer); bool ImageWriteBlock(ImageInfo* const pImageInfo, UINT nBlock, LPBYTE pBlockBuffer); -int ImageGetNumTracks(ImageInfo* const pImageInfo); +UINT ImageGetNumTracks(ImageInfo* const pImageInfo); bool ImageIsWriteProtected(ImageInfo* const pImageInfo); bool ImageIsMultiFileZip(ImageInfo* const pImageInfo); const char* ImageGetPathname(ImageInfo* const pImageInfo); UINT ImageGetImageSize(ImageInfo* const pImageInfo); +bool ImageIsWOZ(ImageInfo* const pImageInfo); +BYTE ImageGetOptimalBitTiming(ImageInfo* const pImageInfo); +UINT ImagePhaseToTrack(ImageInfo* const pImageInfo, const float phase, const bool limit=true); void GetImageTitle(LPCTSTR pPathname, TCHAR* pImageName, TCHAR* pFullName); diff --git a/source/DiskImageHelper.cpp b/source/DiskImageHelper.cpp index e84b2faf..fefc440a 100644 --- a/source/DiskImageHelper.cpp +++ b/source/DiskImageHelper.cpp @@ -70,8 +70,8 @@ LPBYTE CImageBase::ms_pWorkBuffer = NULL; bool CImageBase::ReadTrack(ImageInfo* pImageInfo, const int nTrack, LPBYTE pTrackBuffer, const UINT uTrackSize) { - const long Offset = pImageInfo->uOffset + nTrack * uTrackSize; - memcpy(pTrackBuffer, &pImageInfo->pImageBuffer[Offset], uTrackSize); + const long offset = pImageInfo->uOffset + nTrack * uTrackSize; + memcpy(pTrackBuffer, &pImageInfo->pImageBuffer[offset], uTrackSize); return true; } @@ -630,18 +630,20 @@ public: return ePossibleMatch; } - virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk) + virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk) { - ReadTrack(pImageInfo, nTrack, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE); - *pNibbles = NibblizeTrack(pTrackImageBuffer, eDOSOrder, nTrack); + const UINT track = PhaseToTrack(phase); + ReadTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE); + *pNibbles = NibblizeTrack(pTrackImageBuffer, eDOSOrder, track); if (!enhanceDisk) - SkewTrack(nTrack, *pNibbles, pTrackImageBuffer); + SkewTrack(track, *pNibbles, pTrackImageBuffer); } - virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles) + virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) { - DenibblizeTrack(pTrackImage, eDOSOrder, nNibbles); - WriteTrack(pImageInfo, nTrack, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE); + const UINT track = PhaseToTrack(phase); + DenibblizeTrack(pTrackImageBuffer, eDOSOrder, nNibbles); + WriteTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE); } virtual bool AllowCreate(void) { return true; } @@ -696,23 +698,25 @@ public: return ePossibleMatch; } - virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk) + virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk) { - ReadTrack(pImageInfo, nTrack, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE); - *pNibbles = NibblizeTrack(pTrackImageBuffer, eProDOSOrder, nTrack); + const UINT track = PhaseToTrack(phase); + ReadTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE); + *pNibbles = NibblizeTrack(pTrackImageBuffer, eProDOSOrder, track); if (!enhanceDisk) - SkewTrack(nTrack, *pNibbles, pTrackImageBuffer); + SkewTrack(track, *pNibbles, pTrackImageBuffer); } - virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles) + virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) { - DenibblizeTrack(pTrackImage, eProDOSOrder, nNibbles); - WriteTrack(pImageInfo, nTrack, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE); + const UINT track = PhaseToTrack(phase); + DenibblizeTrack(pTrackImageBuffer, eProDOSOrder, nNibbles); + WriteTrack(pImageInfo, track, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE); } virtual eImageType GetType(void) { return eImagePO; } virtual const char* GetCreateExtensions(void) { return ".po"; } - virtual const char* GetRejectExtensions(void) { return ".do;.iie;.nib;.prg"; } + virtual const char* GetRejectExtensions(void) { return ".do;.iie;.nib;.prg;.woz"; } }; //------------------------------------- @@ -724,7 +728,7 @@ public: CNib1Image(void) {} virtual ~CNib1Image(void) {} - static const UINT NIB1_TRACK_SIZE = NIBBLES_PER_TRACK; + static const UINT NIB1_TRACK_SIZE = NIBBLES_PER_TRACK_NIB; virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt) { @@ -735,16 +739,18 @@ public: return eMatch; } - virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk) + virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk) { - ReadTrack(pImageInfo, nTrack, pTrackImageBuffer, NIB1_TRACK_SIZE); + const UINT track = PhaseToTrack(phase); + ReadTrack(pImageInfo, track, pTrackImageBuffer, NIB1_TRACK_SIZE); *pNibbles = NIB1_TRACK_SIZE; } - virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles) + virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) { _ASSERT(nNibbles == NIB1_TRACK_SIZE); // Must be true - as nNibbles gets init'd by ImageReadTrace() - WriteTrack(pImageInfo, nTrack, pTrackImage, nNibbles); + const UINT track = PhaseToTrack(phase); + WriteTrack(pImageInfo, track, pTrackImageBuffer, nNibbles); } virtual bool AllowCreate(void) { return true; } @@ -752,7 +758,7 @@ public: virtual eImageType GetType(void) { return eImageNIB1; } virtual const char* GetCreateExtensions(void) { return ".nib"; } - virtual const char* GetRejectExtensions(void) { return ".do;.iie;.po;.prg"; } + virtual const char* GetRejectExtensions(void) { return ".do;.iie;.po;.prg;.woz"; } }; //------------------------------------- @@ -775,21 +781,23 @@ public: return eMatch; } - virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk) + virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk) { - ReadTrack(pImageInfo, nTrack, pTrackImageBuffer, NIB2_TRACK_SIZE); + const UINT track = PhaseToTrack(phase); + ReadTrack(pImageInfo, track, pTrackImageBuffer, NIB2_TRACK_SIZE); *pNibbles = NIB2_TRACK_SIZE; } - virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles) + virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) { _ASSERT(nNibbles == NIB2_TRACK_SIZE); // Must be true - as nNibbles gets init'd by ImageReadTrace() - WriteTrack(pImageInfo, nTrack, pTrackImage, nNibbles); + const UINT track = PhaseToTrack(phase); + WriteTrack(pImageInfo, track, pTrackImageBuffer, nNibbles); } virtual eImageType GetType(void) { return eImageNIB2; } virtual const char* GetCreateExtensions(void) { return ".nb2"; } - virtual const char* GetRejectExtensions(void) { return ".do;.iie;.po;.prg;.2mg;.2img"; } + virtual const char* GetRejectExtensions(void) { return ".do;.iie;.po;.prg;.woz;.2mg;.2img"; } }; //------------------------------------- @@ -851,8 +859,10 @@ public: return eMatch; } - virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk) + virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk) { + UINT track = PhaseToTrack(phase); + // IF WE HAVEN'T ALREADY DONE SO, READ THE IMAGE FILE HEADER if (!m_pHeader) { @@ -872,19 +882,19 @@ public: if (*(m_pHeader+13) <= 2) { ConvertSectorOrder(m_pHeader+14); - SetFilePointer(pImageInfo->hFile, nTrack*TRACK_DENIBBLIZED_SIZE+30, NULL, FILE_BEGIN); + SetFilePointer(pImageInfo->hFile, track*TRACK_DENIBBLIZED_SIZE+30, NULL, FILE_BEGIN); ZeroMemory(ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE); DWORD bytesread; ReadFile(pImageInfo->hFile, ms_pWorkBuffer, TRACK_DENIBBLIZED_SIZE, &bytesread, NULL); - *pNibbles = NibblizeTrack(pTrackImageBuffer, eSIMSYSTEMOrder, nTrack); + *pNibbles = NibblizeTrack(pTrackImageBuffer, eSIMSYSTEMOrder, track); } // OTHERWISE, IF THIS IMAGE CONTAINS NIBBLE INFORMATION, READ IT DIRECTLY INTO THE TRACK BUFFER else { - *pNibbles = *(LPWORD)(m_pHeader+nTrack*2+14); + *pNibbles = *(LPWORD)(m_pHeader+track*2+14); LONG Offset = 88; - while (nTrack--) - Offset += *(LPWORD)(m_pHeader+nTrack*2+14); + while (track--) + Offset += *(LPWORD)(m_pHeader+track*2+14); SetFilePointer(pImageInfo->hFile, Offset, NULL,FILE_BEGIN); ZeroMemory(pTrackImageBuffer, *pNibbles); DWORD dwBytesRead; @@ -892,14 +902,14 @@ public: } } - virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles) + virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) { // note: unimplemented } virtual eImageType GetType(void) { return eImageIIE; } virtual const char* GetCreateExtensions(void) { return ".iie"; } - virtual const char* GetRejectExtensions(void) { return ".do.;.nib;.po;.prg;.2mg;.2img"; } + virtual const char* GetRejectExtensions(void) { return ".do.;.nib;.po;.prg;.woz;.2mg;.2img"; } private: void ConvertSectorOrder(LPBYTE sourceorder) @@ -973,7 +983,7 @@ public: virtual eImageType GetType(void) { return eImageAPL; } virtual const char* GetCreateExtensions(void) { return ".apl"; } - virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.iie;.nib;.po;.2mg;.2img"; } + virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.iie;.nib;.po;.woz;.2mg;.2img"; } }; //------------------------------------- @@ -1024,7 +1034,154 @@ public: virtual eImageType GetType(void) { return eImagePRG; } virtual const char* GetCreateExtensions(void) { return ".prg"; } - virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.iie;.nib;.po;.2mg;.2img"; } + virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.iie;.nib;.po;.woz;.2mg;.2img"; } +}; + +//------------------------------------- + +class CWOZEmptyTrack +{ +public: + CWOZEmptyTrack(void) + { + m_pWOZEmptyTrack = new BYTE[CWOZHelper::EMPTY_TRACK_SIZE]; + + srand(1); // Use a fixed seed for determinism + for (UINT i = 0; i < CWOZHelper::EMPTY_TRACK_SIZE; i++) + { + BYTE n = 0; + for (UINT j = 0; j < 8; j++) + { + if (rand() < ((RAND_MAX * 3) / 10)) // ~30% of buffer are 1 bits + n |= 1 << j; + } + m_pWOZEmptyTrack[i] = n; + } + } + virtual ~CWOZEmptyTrack(void) { delete m_pWOZEmptyTrack; } + + void ReadEmptyTrack(LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount) + { + memcpy(pTrackImageBuffer, m_pWOZEmptyTrack, CWOZHelper::EMPTY_TRACK_SIZE); + *pNibbles = CWOZHelper::EMPTY_TRACK_SIZE; + *pBitCount = CWOZHelper::EMPTY_TRACK_SIZE * 8; + return; + } + +private: + BYTE* m_pWOZEmptyTrack; +}; + +//------------------------------------- + +class CWOZ1Image : public CImageBase, private CWOZEmptyTrack +{ +public: + CWOZ1Image(void) {} + virtual ~CWOZ1Image(void) {} + + virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt) + { + CWOZHelper::WOZHeader* pWozHdr = (CWOZHelper::WOZHeader*) pImage; + + if (pWozHdr->id1 != CWOZHelper::ID1_WOZ1 || pWozHdr->id2 != CWOZHelper::ID2) + return eMismatch; + + if (pWozHdr->crc32) + { + // TODO: check crc + } + + m_uNumTracksInImage = CWOZHelper::MAX_TRACKS_5_25; + return eMatch; + } + + virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk) + { + BYTE*& pTrackMap = pImageInfo->pTrackMap; + + const int trackFromTMAP = pTrackMap[(UINT)(phase * 2)]; + if (trackFromTMAP == 0xFF) + return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount); + + ReadTrack(pImageInfo, trackFromTMAP, pTrackImageBuffer, CWOZHelper::WOZ1_TRACK_SIZE); + CWOZHelper::TRKv1* pTRK = (CWOZHelper::TRKv1*) &pTrackImageBuffer[CWOZHelper::WOZ1_TRK_OFFSET]; + *pNibbles = pTRK->bytesUsed; + *pBitCount = pTRK->bitCount; + } + + virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) + { + // TODO + _ASSERT(0); + } + + // TODO: Uncomment and fix-up if we want to allow .woz image creation (eg. for INIT or FORMAT) +// virtual bool AllowCreate(void) { return true; } +// virtual UINT GetImageSizeForCreate(void) { return 0; }//TODO + + virtual eImageType GetType(void) { return eImageWOZ1; } + virtual const char* GetCreateExtensions(void) { return ".woz"; } + virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.nib;.iie;.po;.prg"; } +}; + +//------------------------------------- + +class CWOZ2Image : public CImageBase, private CWOZEmptyTrack +{ +public: + CWOZ2Image(void) {} + virtual ~CWOZ2Image(void) {} + + virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt) + { + CWOZHelper::WOZHeader* pWozHdr = (CWOZHelper::WOZHeader*) pImage; + + if (pWozHdr->id1 != CWOZHelper::ID1_WOZ2 || pWozHdr->id2 != CWOZHelper::ID2) + return eMismatch; + + if (pWozHdr->crc32) + { + // TODO: check crc + } + + m_uNumTracksInImage = CWOZHelper::MAX_TRACKS_5_25; + return eMatch; + } + + virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk) + { + BYTE*& pTrackMap = pImageInfo->pTrackMap; + + const int trackFromTMAP = pTrackMap[(UINT)(phase * 2)]; + if (trackFromTMAP == 0xFF) + return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount); + + CWOZHelper::TRKv2* pTRKS = (CWOZHelper::TRKv2*) &pImageInfo->pImageBuffer[pImageInfo->uOffset]; + CWOZHelper::TRKv2* pTRK = &pTRKS[trackFromTMAP]; + *pBitCount = pTRK->bitCount; + *pNibbles = (pTRK->bitCount+7) / 8; + + _ASSERT(*pNibbles <= NIBBLES_PER_TRACK_WOZ2); + if (*pNibbles > NIBBLES_PER_TRACK_WOZ2) + return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount); // TODO: Enlarge track buffer, but for now just return an empty track + + memcpy(pTrackImageBuffer, &pImageInfo->pImageBuffer[pTRK->startBlock*512], *pNibbles); + } + + virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) + { + // TODO + _ASSERT(0); + } + + // TODO: Uncomment and fix-up if we want to allow .woz image creation (eg. for INIT or FORMAT) + // virtual bool AllowCreate(void) { return true; } + // virtual UINT GetImageSizeForCreate(void) { return 0; }//TODO + + virtual eImageType GetType(void) { return eImageWOZ2; } + virtual const char* GetCreateExtensions(void) { return ".woz"; } + virtual const char* GetRejectExtensions(void) { return ".do;.dsk;.nib;.iie;.po;.prg"; } }; //----------------------------------------------------------------------------- @@ -1048,6 +1205,8 @@ eDetectResult CMacBinaryHelper::DetectHdr(LPBYTE& pImage, DWORD& dwImageSize, DW return eMismatch; } +//----------------------------------------------------------------------------- + eDetectResult C2IMGHelper::DetectHdr(LPBYTE& pImage, DWORD& dwImageSize, DWORD& dwOffset) { Header2IMG* pHdr = (Header2IMG*) pImage; @@ -1099,7 +1258,7 @@ eDetectResult C2IMGHelper::DetectHdr(LPBYTE& pImage, DWORD& dwImageSize, DWORD& break; case e2IMGFormatNIBData: { - if (pHdr->DiskDataLength != TRACKS_STANDARD*NIBBLES_PER_TRACK) + if (pHdr->DiskDataLength != TRACKS_STANDARD*NIBBLES_PER_TRACK_NIB) return eMismatch; } break; @@ -1126,11 +1285,59 @@ bool C2IMGHelper::IsLocked(void) //----------------------------------------------------------------------------- +// Pre: already matched the WOZ header +eDetectResult CWOZHelper::ProcessChunks(const LPBYTE pImage, const DWORD dwImageSize, DWORD& dwOffset, BYTE*& pTrackMap) +{ + UINT32* pImage32 = (uint32_t*) (pImage + sizeof(WOZHeader)); + UINT32 imageSizeRemaining = dwImageSize - sizeof(WOZHeader); + + while(imageSizeRemaining > 8) + { + UINT32 chunkId = *pImage32++; + UINT32 chunkSize = *pImage32++; + imageSizeRemaining -= 8; + + switch(chunkId) + { + case INFO_CHUNK_ID: + m_pInfo = (InfoChunkv2*)(pImage32-2); + if (m_pInfo->v1.version > InfoChunk::maxSupportedVersion) + return eMismatch; + if (m_pInfo->v1.diskType != InfoChunk::diskType5_25) + return eMismatch; + break; + case TMAP_CHUNK_ID: + pTrackMap = (uint8_t*)pImage32; + break; + case TRKS_CHUNK_ID: + dwOffset = dwImageSize - imageSizeRemaining; // offset into image of track data + break; + case WRIT_CHUNK_ID: // WOZ v2 (optional) + break; + case META_CHUNK_ID: // (optional) + break; + default: // no idea what this chunk is, so skip it + _ASSERT(0); + break; + } + + pImage32 = (UINT32*) ((BYTE*)pImage32 + chunkSize); + imageSizeRemaining -= chunkSize; + _ASSERT(imageSizeRemaining >= 0); + if (imageSizeRemaining < 0) + return eMismatch; + } + + return eMatch; +} + +//----------------------------------------------------------------------------- + // NB. Of the 6 cases (floppy/harddisk x gzip/zip/normal) only harddisk-normal isn't read entirely to memory // - harddisk-normal-create also doesn't create a max size image-buffer // DETERMINE THE FILE'S EXTENSION AND CONVERT IT TO LOWERCASE -void GetCharLowerExt(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize) +void CImageHelperBase::GetCharLowerExt(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize) { LPCTSTR pImageFileExt = pszImageFilename; @@ -1146,7 +1353,7 @@ void GetCharLowerExt(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSiz CharLowerBuff(pszExt, _tcslen(pszExt)); } -void GetCharLowerExt2(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize) +void CImageHelperBase::GetCharLowerExt2(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize) { TCHAR szFilename[MAX_PATH]; _tcsncpy(szFilename, pszImageFilename, MAX_PATH); @@ -1187,7 +1394,7 @@ ImageError_e CImageHelperBase::CheckGZipFile(LPCTSTR pszImageFilename, ImageInfo DWORD dwSize = nLen; DWORD dwOffset = 0; - CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, &pImageInfo->bWriteProtected); + CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, pImageInfo->bWriteProtected, pImageInfo->pTrackMap, pImageInfo->optimalBitTiming); if (!pImageType) return eIMAGE_ERROR_UNSUPPORTED; @@ -1196,11 +1403,7 @@ ImageError_e CImageHelperBase::CheckGZipFile(LPCTSTR pszImageFilename, ImageInfo if (Type == eImageAPL || Type == eImageIIE || Type == eImagePRG) return eIMAGE_ERROR_UNSUPPORTED; - pImageInfo->FileType = eFileGZip; - pImageInfo->uOffset = dwOffset; - pImageInfo->pImageType = pImageType; - pImageInfo->uImageSize = dwSize; - + SetImageInfo(pImageInfo, eFileGZip, dwOffset, pImageType, dwSize); return eIMAGE_ERROR_NONE; } @@ -1283,7 +1486,7 @@ ImageError_e CImageHelperBase::CheckZipFile(LPCTSTR pszImageFilename, ImageInfo* DWORD dwSize = nLen; DWORD dwOffset = 0; - CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, &pImageInfo->bWriteProtected); + CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, pImageInfo->bWriteProtected, pImageInfo->pTrackMap, pImageInfo->optimalBitTiming); if (!pImageType) { @@ -1300,11 +1503,7 @@ ImageError_e CImageHelperBase::CheckZipFile(LPCTSTR pszImageFilename, ImageInfo* if (global_info.number_entry > 1) pImageInfo->bWriteProtected = 1; // Zip archives with multiple files are read-only (for now) - pImageInfo->FileType = eFileZip; - pImageInfo->uOffset = dwOffset; - pImageInfo->pImageType = pImageType; - pImageInfo->uImageSize = dwSize; - + SetImageInfo(pImageInfo, eFileZip, dwOffset, pImageType, dwSize); return eIMAGE_ERROR_NONE; } @@ -1384,7 +1583,7 @@ ImageError_e CImageHelperBase::CheckNormalFile(LPCTSTR pszImageFilename, ImageIn return eIMAGE_ERROR_BAD_SIZE; } - pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, &pImageInfo->bWriteProtected); + pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, pImageInfo->bWriteProtected, pImageInfo->pTrackMap, pImageInfo->optimalBitTiming); if (bTempDetectBuffer) { delete [] pImageInfo->pImageBuffer; @@ -1437,12 +1636,18 @@ ImageError_e CImageHelperBase::CheckNormalFile(LPCTSTR pszImageFilename, ImageIn return eIMAGE_ERROR_UNSUPPORTED; } - pImageInfo->FileType = eFileNormal; + SetImageInfo(pImageInfo, eFileNormal, dwOffset, pImageType, dwSize); + return eIMAGE_ERROR_NONE; +} + +//------------------------------------- + +void CImageHelperBase::SetImageInfo(ImageInfo* pImageInfo, FileType_e eFileGZip, DWORD dwOffset, CImageBase* pImageType, DWORD dwSize) +{ + pImageInfo->FileType = eFileGZip; pImageInfo->uOffset = dwOffset; pImageInfo->pImageType = pImageType; pImageInfo->uImageSize = dwSize; - - return eIMAGE_ERROR_NONE; } //------------------------------------- @@ -1517,54 +1722,68 @@ CDiskImageHelper::CDiskImageHelper(void) : m_vecImageTypes.push_back( new CIIeImage ); m_vecImageTypes.push_back( new CAplImage ); m_vecImageTypes.push_back( new CPrgImage ); + m_vecImageTypes.push_back( new CWOZ1Image ); + m_vecImageTypes.push_back( new CWOZ2Image ); } -CImageBase* CDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool* pWriteProtected_) +CImageBase* CDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool& writeProtected, BYTE*& pTrackMap, BYTE& optimalBitTiming) { dwOffset = 0; m_MacBinaryHelper.DetectHdr(pImage, dwSize, dwOffset); m_Result2IMG = m_2IMGHelper.DetectHdr(pImage, dwSize, dwOffset); // CALL THE DETECTION FUNCTIONS IN ORDER, LOOKING FOR A MATCH - eImageType ImageType = eImageUNKNOWN; - eImageType PossibleType = eImageUNKNOWN; + eImageType imageType = eImageUNKNOWN; + eImageType possibleType = eImageUNKNOWN; if (m_Result2IMG == eMatch) { if (m_2IMGHelper.IsImageFormatDOS33()) - ImageType = eImageDO; + imageType = eImageDO; else if (m_2IMGHelper.IsImageFormatProDOS()) - ImageType = eImagePO; + imageType = eImagePO; - if (ImageType != eImageUNKNOWN) + if (imageType != eImageUNKNOWN) { - CImageBase* pImageType = GetImage(ImageType); + CImageBase* pImageType = GetImage(imageType); if (!pImageType || !pImageType->IsValidImageSize(dwSize)) - ImageType = eImageUNKNOWN; + imageType = eImageUNKNOWN; } } - if (ImageType == eImageUNKNOWN) + if (imageType == eImageUNKNOWN) { - for (UINT uLoop=0; uLoop < GetNumImages() && ImageType == eImageUNKNOWN; uLoop++) + for (UINT uLoop=0; uLoop < GetNumImages() && imageType == eImageUNKNOWN; uLoop++) { if (*pszExt && _tcsstr(GetImage(uLoop)->GetRejectExtensions(), pszExt)) continue; eDetectResult Result = GetImage(uLoop)->Detect(pImage, dwSize, pszExt); if (Result == eMatch) - ImageType = GetImage(uLoop)->GetType(); - else if ((Result == ePossibleMatch) && (PossibleType == eImageUNKNOWN)) - PossibleType = GetImage(uLoop)->GetType(); + imageType = GetImage(uLoop)->GetType(); + else if ((Result == ePossibleMatch) && (possibleType == eImageUNKNOWN)) + possibleType = GetImage(uLoop)->GetType(); } } - if (ImageType == eImageUNKNOWN) - ImageType = PossibleType; + if (imageType == eImageUNKNOWN) + imageType = possibleType; - CImageBase* pImageType = GetImage(ImageType); + CImageBase* pImageType = GetImage(imageType); + if (!pImageType) + return NULL; - if (pImageType) + if (imageType == eImageWOZ1 || imageType == eImageWOZ2) + { + if (m_WOZHelper.ProcessChunks(pImage, dwSize, dwOffset, pTrackMap) != eMatch) + return NULL; + +// if (m_WOZHelper.IsWriteProtected() && !writeProtected) // Force write-protected until writing is supported + writeProtected = true; + + optimalBitTiming = m_WOZHelper.GetOptimalBitTiming(); + } + else { if (pImageType->AllowRW()) { @@ -1578,8 +1797,8 @@ CImageBase* CDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* p { pImageType->SetVolumeNumber( m_2IMGHelper.GetVolumeNumber() ); - if (m_2IMGHelper.IsLocked() && !*pWriteProtected_) - *pWriteProtected_ = 1; + if (m_2IMGHelper.IsLocked() && !writeProtected) + writeProtected = true; } else { @@ -1634,7 +1853,7 @@ CHardDiskImageHelper::CHardDiskImageHelper(void) : m_vecImageTypes.push_back( new CHDVImage ); } -CImageBase* CHardDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool* pWriteProtected_) +CImageBase* CHardDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool& writeProtected, BYTE*& pTrackMap, BYTE& optimalBitTiming) { dwOffset = 0; m_Result2IMG = m_2IMGHelper.DetectHdr(pImage, dwSize, dwOffset); @@ -1659,11 +1878,14 @@ CImageBase* CHardDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHA { if (m_Result2IMG == eMatch) { - if (m_2IMGHelper.IsLocked() && !*pWriteProtected_) - *pWriteProtected_ = 1; + if (m_2IMGHelper.IsLocked() && !writeProtected) + writeProtected = true; } } + pTrackMap = 0; // TODO: WOZ + optimalBitTiming = 0; // TODO: WOZ + return pImageType; } diff --git a/source/DiskImageHelper.h b/source/DiskImageHelper.h index a6e8b773..cf64bdac 100644 --- a/source/DiskImageHelper.h +++ b/source/DiskImageHelper.h @@ -10,7 +10,7 @@ #define ZIP_SUFFIX_LEN (sizeof(ZIP_SUFFIX)-1) -enum eImageType {eImageUNKNOWN, eImageDO, eImagePO, eImageNIB1, eImageNIB2, eImageHDV, eImageIIE, eImageAPL, eImagePRG}; +enum eImageType {eImageUNKNOWN, eImageDO, eImagePO, eImageNIB1, eImageNIB2, eImageHDV, eImageIIE, eImageAPL, eImagePRG, eImageWOZ1, eImageWOZ2}; enum eDetectResult {eMismatch, ePossibleMatch, eMatch}; class CImageBase; @@ -35,6 +35,8 @@ struct ImageInfo BYTE ValidTrack[TRACKS_MAX]; UINT uNumTracks; BYTE* pImageBuffer; + BYTE* pTrackMap; // WOZ only + BYTE optimalBitTiming; // WOZ only }; //------------------------------------- @@ -54,9 +56,9 @@ public: virtual bool Boot(ImageInfo* pImageInfo) { return false; } virtual eDetectResult Detect(const LPBYTE pImage, const DWORD dwImageSize, const TCHAR* pszExt) = 0; - virtual void Read(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImageBuffer, int* pNibbles, bool enhanceDisk) { } + virtual void Read(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount, bool enhanceDisk) { } virtual bool Read(ImageInfo* pImageInfo, UINT nBlock, LPBYTE pBlockBuffer) { return false; } - virtual void Write(ImageInfo* pImageInfo, int nTrack, int nQuarterTrack, LPBYTE pTrackImage, int nNibbles) { } + virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) { } virtual bool Write(ImageInfo* pImageInfo, UINT nBlock, LPBYTE pBlockBuffer) { return false; } virtual bool AllowBoot(void) { return false; } // Only: APL and PRG @@ -71,6 +73,11 @@ public: void SetVolumeNumber(const BYTE uVolumeNumber) { m_uVolumeNumber = uVolumeNumber; } bool IsValidImageSize(const DWORD uImageSize); + // To accurately convert a half phase (quarter track) back to a track (round half tracks down), use: ceil(phase)/2, eg: + // . phase=4,+1 half phase = phase 4.5 => ceil(4.5)/2 = track 2 (OK) + // . phase=4,-1 half phase = phase 3.5 => ceil(3.5)/2 = track 2 (OK) + UINT PhaseToTrack(const float phase) { return ((UINT)ceil(phase)) >> 1; } + enum SectorOrder_e {eProDOSOrder, eDOSOrder, eSIMSYSTEMOrder, NUM_SECTOR_ORDERS}; protected: @@ -122,7 +129,7 @@ private: // http://apple2.org.za/gswv/a2zine/Docs/DiskImage_2MG_Info.txt #pragma pack(push) -#pragma pack(1) // Ensure Header2IMG is packed +#pragma pack(1) // Ensure Header2IMG & WOZ structs are packed class C2IMGHelper : public CHdrHelper { @@ -181,6 +188,98 @@ private: bool m_bIsFloppy; }; +class CWOZHelper : public CHdrHelper +{ +public: + CWOZHelper() : + m_pInfo(NULL) + {} + virtual ~CWOZHelper(void) {} + virtual eDetectResult DetectHdr(LPBYTE& pImage, DWORD& dwImageSize, DWORD& dwOffset) { _ASSERT(0); return eMismatch; } + virtual UINT GetMaxHdrSize(void) { return sizeof(WOZHeader); } + eDetectResult ProcessChunks(const LPBYTE pImage, const DWORD dwImageSize, DWORD& dwOffset, BYTE*& pTrackMap); + bool IsWriteProtected(void) { return m_pInfo->v1.writeProtected == 1; } + BYTE GetOptimalBitTiming(void) { return (m_pInfo->v1.version == 1) ? CWOZHelper::InfoChunkv2::optimalBitTiming5_25 : m_pInfo->optimalBitTiming; } + + static const UINT32 ID1_WOZ1 = '1ZOW'; // 'WOZ1' + static const UINT32 ID1_WOZ2 = '2ZOW'; // 'WOZ2' + static const UINT32 ID2 = 0x0A0D0AFF; + + struct WOZHeader + { + UINT32 id1; // 'WOZ1' or 'WOZ2' + UINT32 id2; + UINT32 crc32; + }; + + static const UINT32 MAX_TRACKS_5_25 = 40; + static const UINT32 WOZ1_TRACK_SIZE = 6656; // 0x1A00 + static const UINT32 WOZ1_TRK_OFFSET = 6646; + static const UINT32 EMPTY_TRACK_SIZE = 6400; + + struct TRKv1 + { + UINT16 bytesUsed; + UINT16 bitCount; + UINT16 splicePoint; + BYTE spliceNibble; + BYTE spliceBitCount; + UINT16 reserved; + }; + + struct TRKv2 + { + UINT16 startBlock; // relative to start of file + UINT16 blockCount; // number of blocks for this BITS data + UINT32 bitCount; + }; + +private: + static const UINT32 INFO_CHUNK_ID = 'OFNI'; // 'INFO' + static const UINT32 TMAP_CHUNK_ID = 'PAMT'; // 'TMAP' + static const UINT32 TRKS_CHUNK_ID = 'SKRT'; // 'TRKS' + static const UINT32 WRIT_CHUNK_ID = 'TIRW'; // 'WRIT' - WOZv2 + static const UINT32 META_CHUNK_ID = 'ATEM'; // 'META' + + struct InfoChunk + { + UINT32 id; + UINT32 size; + BYTE version; + BYTE diskType; + BYTE writeProtected; // 1 = Floppy is write protected + BYTE synchronized; // 1 = Cross track sync was used during imaging + BYTE cleaned; // 1 = MC3470 fake bits have been removed + BYTE creator[32]; // Name of software that created the WOZ file. + // String in UTF-8. No BOM. Padded to 32 bytes + // using space character (0x20). + + static const BYTE maxSupportedVersion = 2; + static const BYTE diskType5_25 = 1; + static const BYTE diskType3_5 = 2; + }; + + struct InfoChunkv2 + { + InfoChunk v1; + BYTE diskSides; // 5.25 will always be 1; 3.5 can be 1 or 2 + BYTE bootSectorFormat; + BYTE optimalBitTiming; // in 125ns increments (And a standard bit rate for 5.25 disk would be 32 (4us)) + UINT16 compatibleHardware; + UINT16 requiredRAM; // in K (1024 bytes) + UINT16 largestTrack; // in blocks (512 bytes) + + static const BYTE bootUnknown = 0; + static const BYTE bootSector16 = 1; + static const BYTE bootSector13 = 2; + static const BYTE bootSectorBoth = 3; + + static const BYTE optimalBitTiming5_25 = 32; + }; + + InfoChunkv2* m_pInfo; +}; + #pragma pack(pop) //------------------------------------- @@ -190,7 +289,8 @@ class CImageHelperBase public: CImageHelperBase(const bool bIsFloppy) : m_2IMGHelper(bIsFloppy), - m_Result2IMG(eMismatch) + m_Result2IMG(eMismatch), + m_WOZHelper() { } virtual ~CImageHelperBase(void) @@ -202,7 +302,7 @@ public: ImageError_e Open(LPCTSTR pszImageFilename, ImageInfo* pImageInfo, const bool bCreateIfNecessary, std::string& strFilenameInZip); void Close(ImageInfo* pImageInfo, const bool bDeleteFile); - virtual CImageBase* Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool* pWriteProtected_) = 0; + virtual CImageBase* Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, bool& writeProtected, BYTE*& pTrackMap, BYTE& optimalBitTiming) = 0; virtual CImageBase* GetImageForCreation(const TCHAR* pszExt, DWORD* pCreateImageSize) = 0; virtual UINT GetMaxImageSize(void) = 0; virtual UINT GetMinDetectSize(const UINT uImageSize, bool* pTempDetectBuffer) = 0; @@ -211,6 +311,9 @@ protected: ImageError_e CheckGZipFile(LPCTSTR pszImageFilename, ImageInfo* pImageInfo); ImageError_e CheckZipFile(LPCTSTR pszImageFilename, ImageInfo* pImageInfo, std::string& strFilenameInZip); ImageError_e CheckNormalFile(LPCTSTR pszImageFilename, ImageInfo* pImageInfo, const bool bCreateIfNecessary); + void GetCharLowerExt(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize); + void GetCharLowerExt2(TCHAR* pszExt, LPCTSTR pszImageFilename, const UINT uExtSize); + void SetImageInfo(ImageInfo* pImageInfo, FileType_e eFileGZip, DWORD dwOffset, CImageBase* pImageType, DWORD dwSize); UINT GetNumImages(void) { return m_vecImageTypes.size(); }; CImageBase* GetImage(UINT uIndex) { _ASSERT(uIndex= 2) keywaiting = (BOOL) yamlLoadHelper.LoadBool(SS_YAML_KEY_KEYWAITING); yamlLoadHelper.PopMap(); diff --git a/source/Memory.cpp b/source/Memory.cpp index 8c547203..da593286 100644 --- a/source/Memory.cpp +++ b/source/Memory.cpp @@ -1745,6 +1745,8 @@ void MemReset() g_eExpansionRomType = eExpRomNull; g_uPeripheralRomSlot = 0; + ZeroMemory(memdirty, 0x100); + // int iByte; diff --git a/source/SaveState_Structs_v1.h b/source/SaveState_Structs_v1.h index ce171c84..3a48aba5 100644 --- a/source/SaveState_Structs_v1.h +++ b/source/SaveState_Structs_v1.h @@ -92,7 +92,7 @@ struct DISK2_Unit DWORD spinning; DWORD writelight; int nibbles; - BYTE nTrack[NIBBLES_PER_TRACK]; + BYTE nTrack[NIBBLES_PER_TRACK_NIB]; }; struct SS_CARD_DISK2 diff --git a/source/YamlHelper.cpp b/source/YamlHelper.cpp index 0e9f96cb..dbcaddcd 100644 --- a/source/YamlHelper.cpp +++ b/source/YamlHelper.cpp @@ -342,6 +342,34 @@ std::string YamlLoadHelper::LoadString(const std::string& key) return value; } +float YamlLoadHelper::LoadFloat(const std::string key) +{ + bool bFound; + std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound); + if (value == "") + { + m_bDoGetMapRemainder = false; + throw std::string(m_currentMapName + ": Missing: " + key); + } +#if (_MSC_VER >= 1900) + return strtof(value.c_str(), NULL); // MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015 version 14.0) +#else + return (float) strtod(value.c_str(), NULL); // NB. strtof() requires VS2015 +#endif +} + +double YamlLoadHelper::LoadDouble(const std::string key) +{ + bool bFound; + std::string value = m_yamlHelper.GetMapValue(*m_pMapYaml, key, bFound); + if (value == "") + { + m_bDoGetMapRemainder = false; + throw std::string(m_currentMapName + ": Missing: " + key); + } + return strtod(value.c_str(), NULL); +} + void YamlLoadHelper::LoadMemory(const LPBYTE pMemBase, const size_t size) { m_yamlHelper.LoadMemory(*m_pMapYaml, pMemBase, size); @@ -371,7 +399,7 @@ void YamlSaveHelper::SaveUint(const char* key, UINT value) void YamlSaveHelper::SaveHexUint4(const char* key, UINT value) { - Save("%s: 0x%01X\n", key, value); + Save("%s: 0x%01X\n", key, value & 0xf); } void YamlSaveHelper::SaveHexUint8(const char* key, UINT value) @@ -414,6 +442,16 @@ void YamlSaveHelper::SaveString(const char* key, const char* value) Save("%s: %s\n", key, (value[0] != 0) ? value : "\"\""); } +void YamlSaveHelper::SaveFloat(const char* key, float value) +{ + Save("%s: %f\n", key, value); +} + +void YamlSaveHelper::SaveDouble(const char* key, double value) +{ + Save("%s: %f\n", key, value); +} + // Pre: uMemSize must be multiple of 8 void YamlSaveHelper::SaveMemory(const LPBYTE pMemBase, const UINT uMemSize) { diff --git a/source/YamlHelper.h b/source/YamlHelper.h index c96cca2c..48f0999a 100644 --- a/source/YamlHelper.h +++ b/source/YamlHelper.h @@ -97,6 +97,8 @@ public: bool LoadBool(const std::string key); std::string LoadString_NoThrow(const std::string& key, bool& bFound); std::string LoadString(const std::string& key); + float LoadFloat(const std::string key); + double LoadDouble(const std::string key); void LoadMemory(const LPBYTE pMemBase, const size_t size); bool GetSubMap(const std::string key) @@ -213,7 +215,9 @@ public: void SaveHexUint32(const char* key, UINT value); void SaveHexUint64(const char* key, UINT64 value); void SaveBool(const char* key, bool value); - void SaveString(const char* key, const char* value); + void SaveString(const char* key, const char* value); + void SaveFloat(const char* key, float value); + void SaveDouble(const char* key, double value); void SaveMemory(const LPBYTE pMemBase, const UINT uMemSize); class Label