diff --git a/source/Disk.cpp b/source/Disk.cpp index dd5a0d68..540fdeb2 100644 --- a/source/Disk.cpp +++ b/source/Disk.cpp @@ -282,6 +282,12 @@ void Disk2InterfaceCard::ReadTrack(const int drive, ULONG uExecutedCycles) 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. + // Boot with DOS 3.3 Master in D1 + // Create a blank disk in D2 + // INIT HELLO,D2 + // RUN HELLO + // F2 to reboot DOS 3.3 Master + // RUN HELLO,D2 pFloppy->m_trackimagedata = false; return; } @@ -729,17 +735,6 @@ void Disk2InterfaceCard::NotifyInvalidImage(const int drive, LPCTSTR pszImageFil 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( @@ -984,24 +979,18 @@ void Disk2InterfaceCard::ResetLogicStateSequencer(void) m_shiftReg = 0; m_latchDelay = 0; m_resetSequencer = true; + m_writeStarted = false; m_dbgLatchDelayedCnt = 0; } -void Disk2InterfaceCard::UpdateBitStreamPositionAndDiskCycle(const ULONG uExecutedCycles) +UINT Disk2InterfaceCard::GetBitCellDelta(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; + const BYTE optimalBitTiming = ImageGetOptimalBitTiming(floppy.m_imagehandle); + // 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 @@ -1010,9 +999,9 @@ UINT Disk2InterfaceCard::GetBitCellDelta(const BYTE optimalBitTiming) #if 0 if (optimalBitTiming == 32) { - const ULONG cycleDelta = (ULONG)(g_nCumulativeCycles - m_diskLastCycle) + (BYTE) m_extraCycles; + const ULONG cycleDelta = (ULONG)(g_nCumulativeCycles - m_diskLastCycle) + (BYTE) floppy.m_extraCycles; bitCellDelta = cycleDelta / 4; // DIV 4 for 4us per bit-cell - m_extraCycles = cycleDelta & 3; // MOD 4 : remainder carried forward for next time + floppy.m_extraCycles = cycleDelta & 3; // MOD 4 : remainder carried forward for next time } else #endif @@ -1022,6 +1011,11 @@ UINT Disk2InterfaceCard::GetBitCellDelta(const BYTE optimalBitTiming) bitCellDelta = (UINT) floor( cycleDelta / bitTime ); floppy.m_extraCycles = (double)cycleDelta - ((double)bitCellDelta * bitTime); } + + // NB. actual m_diskLastCycle for the last bitCell is minus floppy.m_extraCycles + // - but don't need this value; and it's correctly accounted for in this function. + m_diskLastCycle = g_nCumulativeCycles; + return bitCellDelta; } @@ -1036,6 +1030,8 @@ void Disk2InterfaceCard::UpdateBitStreamPosition(FloppyDisk& floppy, const ULONG floppy.m_bitOffset %= floppy.m_bitCount; UpdateBitStreamOffsets(floppy); + + m_resetSequencer = false; } void Disk2InterfaceCard::UpdateBitStreamOffsets(FloppyDisk& floppy) @@ -1045,8 +1041,28 @@ void Disk2InterfaceCard::UpdateBitStreamOffsets(FloppyDisk& floppy) floppy.m_bitMask = 1 << remainder; } -void __stdcall Disk2InterfaceCard::DataLatchReadWriteWOZ(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles) +__forceinline void Disk2InterfaceCard::IncBitStream(FloppyDisk& floppy) { + 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; + } +} + +void __stdcall Disk2InterfaceCard::DataLatchReadWriteWOZ(WORD pc, WORD addr, BYTE bWrite, ULONG uExecutedCycles) +{ + _ASSERT(m_seqFunc.function != dataShiftWrite); + FloppyDrive& drive = m_floppyDrive[m_currDrive]; FloppyDisk& floppy = drive.m_disk; @@ -1067,14 +1083,11 @@ void __stdcall Disk2InterfaceCard::DataLatchReadWriteWOZ(WORD pc, WORD addr, BYT 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 bitCellDelta = GetBitCellDelta(uExecutedCycles); UINT bitCellRemainder; if (bitCellDelta <= significantBitCells) @@ -1092,20 +1105,21 @@ void __stdcall Disk2InterfaceCard::DataLatchReadWriteWOZ(WORD pc, WORD addr, BYT drive.m_headWindow = 0; } - // NB. actual m_diskLastCycle for the last bitCell is minus floppy.m_extraCycles - // - but don't need this value; and it's correctly accounted for in GetBitCellDelta() - m_diskLastCycle = g_nCumulativeCycles; - if (!bWrite) { if (m_seqFunc.function != readSequencing) + { + _ASSERT(m_seqFunc.function == checkWriteProtAndInitWrite); + UpdateBitStreamPosition(floppy, bitCellRemainder); return; + } DataLatchReadWOZ(pc, addr, bitCellRemainder); } else { - DataLatchWriteWOZ(pc, addr, d, bitCellRemainder); + _ASSERT(m_seqFunc.function == dataLoadWrite); + DataLoadWriteWOZ(pc, addr, bitCellRemainder); } // Show track status (GH#201) - NB. Prevent flooding of forcing UI to redraw!!! @@ -1147,20 +1161,7 @@ void Disk2InterfaceCard::DataLatchReadWOZ(WORD pc, WORD addr, UINT bitCellRemain BYTE outputBit = (drive.m_headWindow & 0xf) ? (drive.m_headWindow >> 1) & 1 : (rand() < ((RAND_MAX * 3) / 10)) ? 1 : 0; // ~30% chance of a 1 bit (Ref: WOZ-2.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; - } + IncBitStream(floppy); if (m_resetSequencer) { @@ -1238,17 +1239,64 @@ void Disk2InterfaceCard::DataLatchReadWOZ(WORD pc, WORD addr, UINT bitCellRemain #endif } -void Disk2InterfaceCard::DataLatchWriteWOZ(WORD pc, WORD addr, BYTE d, UINT bitCellRemainder) +void Disk2InterfaceCard::DataLoadWriteWOZ(WORD pc, WORD addr, UINT bitCellRemainder) { - _ASSERT(m_seqFunc.writeMode); + _ASSERT(m_seqFunc.function == dataLoadWrite); FloppyDrive& drive = m_floppyDrive[m_currDrive]; FloppyDisk& floppy = drive.m_disk; - if (!floppy.m_bWriteProtected) + if (floppy.m_bWriteProtected) { - //TODO + _ASSERT(0); // Must be a bug in the 6502 code for this to occur! + UpdateBitStreamPosition(floppy, bitCellRemainder); + return; } + + if (!m_writeStarted) + UpdateBitStreamPosition(floppy, bitCellRemainder); // skip over bitCells before switching to write mode + + m_writeStarted = true; +#if LOG_DISK_WOZ_LOADWRITE + LOG_DISK("load shiftReg with %02X (was: %02X)\n", m_floppyLatch, m_shiftReg); +#endif + m_shiftReg = m_floppyLatch; +} + +void Disk2InterfaceCard::DataShiftWriteWOZ(WORD pc, WORD addr, ULONG uExecutedCycles) +{ + _ASSERT(m_seqFunc.function == dataShiftWrite); + + FloppyDrive& drive = m_floppyDrive[m_currDrive]; + FloppyDisk& floppy = drive.m_disk; + + const UINT bitCellRemainder = GetBitCellDelta(uExecutedCycles); + + if (floppy.m_bWriteProtected) + { + _ASSERT(0); // Must be a bug in the 6502 code for this to occur! + UpdateBitStreamPosition(floppy, bitCellRemainder); + return; + } + +#if LOG_DISK_WOZ_SHIFTWRITE + LOG_DISK("T$%02X, bitOffset=%04X: %02X (%d bits)\n", drive.m_phase/2, floppy.m_bitOffset, m_shiftReg, bitCellRemainder); +#endif + + for (UINT i = 0; i < bitCellRemainder; i++) + { + BYTE outputBit = m_shiftReg & 0x80; + m_shiftReg <<= 1; + + BYTE n = floppy.m_trackimage[floppy.m_byte]; + n &= ~floppy.m_bitMask; + if (outputBit) n |= floppy.m_bitMask; + floppy.m_trackimage[floppy.m_byte] = n; + + IncBitStream(floppy); + } + + floppy.m_trackimagedirty = true; } //=========================================================================== @@ -1485,7 +1533,9 @@ bool Disk2InterfaceCard::UserSelectNewDiskImage(const int drive, LPCSTR pszFilen void __stdcall Disk2InterfaceCard::LoadWriteProtect(WORD, WORD, BYTE write, BYTE value, ULONG uExecutedCycles) { - // NB. m_seqFunc.function == checkWriteProtAndInitWrite or shiftWrite (both OK) + // NB. Only reads in LOAD mode can issue the SR (shift write-protect) operation - UTAIIe page 9-20, fig 9.11 + // But STA $C08D,X (no PX) does a read from $C08D+X, followed by the write to $C08D+X + // So just want to ignore: STA $C0ED or eg. STA $BFFF,X (PX, X=$EE) // 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) @@ -1500,20 +1550,27 @@ void __stdcall Disk2InterfaceCard::LoadWriteProtect(WORD, WORD, BYTE write, BYTE // 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) // . A read or write will shift 'write protect' in QA. - if (m_floppyDrive[m_currDrive].m_disk.m_bWriteProtected) + FloppyDisk& floppy = m_floppyDrive[m_currDrive].m_disk; + if (floppy.m_bWriteProtected) m_floppyLatch |= 0x80; else m_floppyLatch &= 0x7F; - if (ImageIsWOZ(m_floppyDrive[m_currDrive].m_disk.m_imagehandle)) + if (m_writeStarted) // Prevent ResetLogicStateSequencer() from resetting m_writeStarted + return; + + if (ImageIsWOZ(floppy.m_imagehandle)) { #if LOG_DISK_NIBBLES_READ CpuCalcCycles(uExecutedCycles); LOG_DISK("%08X: reset LSS: ~PC=%04X\r\n", (UINT32)g_nCumulativeCycles, regs.pc); #endif + + const UINT bitCellDelta = GetBitCellDelta(uExecutedCycles); + UpdateBitStreamPosition(floppy, bitCellDelta); // Fix E7-copy protection + + // UpdateBitStreamPosition() must be done below ResetLSS, as the former clears m_resetSequencer. (Commando.woz is sensitive to this) ResetLogicStateSequencer(); // reset sequencer (UTAIIe page 9-21) -// m_latchDelay = 7; // TODO: Treat like a regular $C0EC latch load? - UpdateBitStreamPositionAndDiskCycle(uExecutedCycles); // Fix E7-copy protection } } @@ -1688,6 +1745,9 @@ void Disk2InterfaceCard::SetSequencerFunction(WORD addr) case 2: m_seqFunc.loadMode = 0; break; // $C08C,X (sequence addr A3 input) case 3: m_seqFunc.loadMode = 1; break; // $C08D,X (sequence addr A3 input) } + + if (!m_seqFunc.writeMode) + m_writeStarted = false; } BYTE __stdcall Disk2InterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles) @@ -1698,6 +1758,9 @@ BYTE __stdcall Disk2InterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE ImageInfo* pImage = pCard->m_floppyDrive[pCard->m_currDrive].m_disk.m_imagehandle; bool isWOZ = ImageIsWOZ(pImage); + if (isWOZ && pCard->m_seqFunc.function == dataShiftWrite) // Occurs at end of sector write ($C0EE) + pCard->DataShiftWriteWOZ(pc, addr, nExecutedCycles); // Finish any previous write + pCard->SetSequencerFunction(addr); switch (addr & 0xF) @@ -1723,8 +1786,8 @@ BYTE __stdcall Disk2InterfaceCard::IORead(WORD pc, WORD addr, BYTE bWrite, BYTE // only even addresses return the latch (UTAIIe Table 9.1) if (!(addr & 1)) { - if (isWOZ) - pCard->DataLatchReadWriteWOZ(pc, addr, bWrite, d, nExecutedCycles); + if (isWOZ && pCard->m_seqFunc.function != dataShiftWrite) + pCard->DataLatchReadWriteWOZ(pc, addr, bWrite, nExecutedCycles); return pCard->m_floppyLatch; } @@ -1740,6 +1803,9 @@ BYTE __stdcall Disk2InterfaceCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE ImageInfo* pImage = pCard->m_floppyDrive[pCard->m_currDrive].m_disk.m_imagehandle; bool isWOZ = ImageIsWOZ(pImage); + if (isWOZ && pCard->m_seqFunc.function == dataShiftWrite) + pCard->DataShiftWriteWOZ(pc, addr, nExecutedCycles); // Finish any previous write + pCard->SetSequencerFunction(addr); switch (addr & 0xF) @@ -1763,13 +1829,12 @@ BYTE __stdcall Disk2InterfaceCard::IOWrite(WORD pc, WORD addr, BYTE bWrite, BYTE } // any address writes the latch via sequencer LD command (74LS323 datasheet) -// if (pCard->m_seqFunc.writeMode /* && m_seqFunc.loadMode */) if (pCard->m_seqFunc.function == dataLoadWrite) { pCard->m_floppyLatch = d; if (isWOZ) - pCard->DataLatchReadWriteWOZ(pc, addr, bWrite, d, nExecutedCycles); + pCard->DataLatchReadWriteWOZ(pc, addr, bWrite, nExecutedCycles); } return 0; diff --git a/source/Disk.h b/source/Disk.h index 48ffa40e..23640b9f 100644 --- a/source/Disk.h +++ b/source/Disk.h @@ -185,12 +185,13 @@ private: void WriteTrack(const int drive); const std::string & DiskGetFullPathName(const int drive); void ResetLogicStateSequencer(void); - void UpdateBitStreamPositionAndDiskCycle(const ULONG uExecutedCycles); - UINT GetBitCellDelta(const BYTE optimalBitTiming); + UINT GetBitCellDelta(const ULONG uExecutedCycles); void UpdateBitStreamPosition(FloppyDisk& floppy, const ULONG bitCellDelta); void UpdateBitStreamOffsets(FloppyDisk& floppy); + __forceinline void IncBitStream(FloppyDisk& floppy); void DataLatchReadWOZ(WORD pc, WORD addr, UINT bitCellRemainder); - void DataLatchWriteWOZ(WORD pc, WORD addr, BYTE d, UINT bitCellRemainder); + void DataLoadWriteWOZ(WORD pc, WORD addr, UINT bitCellRemainder); + void DataShiftWriteWOZ(WORD pc, WORD addr, ULONG uExecutedCycles); void SetSequencerFunction(WORD addr); void DumpSectorWOZ(FloppyDisk floppy); void DumpTrackWOZ(FloppyDisk floppy); @@ -206,7 +207,7 @@ private: 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 uExecutedCycles); - void __stdcall DataLatchReadWriteWOZ(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG uExecutedCycles); + void __stdcall DataLatchReadWriteWOZ(WORD pc, WORD addr, BYTE bWrite, 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); @@ -240,8 +241,9 @@ private: BYTE m_shiftReg; int m_latchDelay; bool m_resetSequencer; + bool m_writeStarted; - enum SEQFUNC {readSequencing=0, checkWriteProtAndInitWrite, dataShiftWrite, dataLoadWrite}; // UTAIIe 9-14 + enum SEQFUNC {readSequencing=0, dataShiftWrite, checkWriteProtAndInitWrite, dataLoadWrite}; // UTAIIe 9-14 union SEQUENCER_FUNCTION { struct diff --git a/source/DiskImage.cpp b/source/DiskImage.cpp index 889819d0..976c9853 100644 --- a/source/DiskImage.cpp +++ b/source/DiskImage.cpp @@ -63,7 +63,7 @@ ImageError_e ImageOpen( const std::string & pszImageFilename, ImageError_e Err = pImageInfo->pImageHelper->Open(pszImageFilename.c_str(), pImageInfo, bCreateIfNecessary, strFilenameInZip); if (Err != eIMAGE_ERROR_NONE) { - ImageClose(*ppImageInfo, true); + ImageClose(*ppImageInfo); *ppImageInfo = NULL; return Err; } @@ -83,9 +83,6 @@ ImageError_e ImageOpen( const std::string & pszImageFilename, pImageInfo->uNumTracks = sg_DiskImageHelper.GetNumTracksInImage(pImageInfo->pImageType); - for (UINT uTrack = 0; uTrack < pImageInfo->uNumTracks; uTrack++) - pImageInfo->ValidTrack[uTrack] = (pImageInfo->uImageSize > 0) ? 1 : 0; - *pWriteProtected = pImageInfo->bWriteProtected; return eIMAGE_ERROR_NONE; @@ -93,25 +90,9 @@ ImageError_e ImageOpen( const std::string & pszImageFilename, //=========================================================================== -void ImageClose(ImageInfo* const pImageInfo, const bool bOpenError /*=false*/) +void ImageClose(ImageInfo* const pImageInfo) { - bool bDeleteFile = false; - - if (!bOpenError) - { - for (UINT uTrack = 0; uTrack < pImageInfo->uNumTracks; uTrack++) - { - if (!pImageInfo->ValidTrack[uTrack]) - { - // TODO: Comment using info from this URL: - // http://groups.google.de/group/comp.emulators.apple2/msg/7a1b9317e7905152 - bDeleteFile = true; - break; - } - } - } - - pImageInfo->pImageHelper->Close(pImageInfo, bDeleteFile); + pImageInfo->pImageHelper->Close(pImageInfo); delete pImageInfo; } @@ -162,7 +143,7 @@ void ImageReadTrack( ImageInfo* const pImageInfo, const UINT track = pImageInfo->pImageType->PhaseToTrack(phase); - if (pImageInfo->pImageType->AllowRW() && pImageInfo->ValidTrack[track]) + if (pImageInfo->pImageType->AllowRW()) { pImageInfo->pImageType->Read(pImageInfo, phase, pTrackImageBuffer, pNibbles, pBitCount, enhanceDisk); } @@ -190,7 +171,14 @@ void ImageWriteTrack( ImageInfo* const pImageInfo, if (pImageInfo->pImageType->AllowRW() && !pImageInfo->bWriteProtected) { pImageInfo->pImageType->Write(pImageInfo, phase, pTrackImageBuffer, nNibbles); - pImageInfo->ValidTrack[track] = 1; + + eImageType imageType = pImageInfo->pImageType->GetType(); + if (imageType == eImageWOZ1 || imageType == eImageWOZ2) + { + DWORD dummy; + bool res = sg_DiskImageHelper.WOZUpdateInfo(pImageInfo, dummy); + _ASSERT(res); + } } } @@ -234,7 +222,7 @@ bool ImageIsWriteProtected(ImageInfo* const pImageInfo) bool ImageIsMultiFileZip(ImageInfo* const pImageInfo) { - return pImageInfo ? (pImageInfo->uNumEntriesInZip > 1) : false; + return pImageInfo ? (pImageInfo->uNumValidImagesInZip > 1) : false; } const std::string & ImageGetPathname(ImageInfo* const pImageInfo) diff --git a/source/DiskImage.h b/source/DiskImage.h index 9374d4a5..af6faf86 100644 --- a/source/DiskImage.h +++ b/source/DiskImage.h @@ -51,7 +51,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA eIMAGE_ERROR_GZ, eIMAGE_ERROR_ZIP, eIMAGE_ERROR_REJECTED_MULTI_ZIP, - eIMAGE_ERROR_UNSUPPORTED_MULTI_ZIP, eIMAGE_ERROR_UNABLE_TO_OPEN, eIMAGE_ERROR_UNABLE_TO_OPEN_GZ, eIMAGE_ERROR_UNABLE_TO_OPEN_ZIP, @@ -66,7 +65,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA struct ImageInfo; ImageError_e ImageOpen(const std::string & pszImageFilename, ImageInfo** ppImageInfo, bool* pWriteProtected, const bool bCreateIfNecessary, std::string& strFilenameInZip, const bool bExpectFloppy=true); -void ImageClose(ImageInfo* const pImageInfo, const bool bOpenError=false); +void ImageClose(ImageInfo* const pImageInfo); BOOL ImageBoot(ImageInfo* const pImageInfo); void ImageDestroy(void); void ImageInitialize(void); diff --git a/source/DiskImageHelper.cpp b/source/DiskImageHelper.cpp index 1f9d0c47..05bac12b 100644 --- a/source/DiskImageHelper.cpp +++ b/source/DiskImageHelper.cpp @@ -36,6 +36,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "CPU.h" #include "DiskImage.h" #include "DiskImageHelper.h" +#include "Log.h" #include "Memory.h" ImageInfo::ImageInfo() @@ -51,10 +52,10 @@ ImageInfo::ImageInfo() uImageSize = 0; ZeroMemory(&zipFileInfo, sizeof(zipFileInfo)); uNumEntriesInZip = 0; - ZeroMemory(&ValidTrack, sizeof(ValidTrack)); + uNumValidImagesInZip = 0; uNumTracks = 0; pImageBuffer = NULL; - pTrackMap = NULL; + pWOZTrackMap = NULL; optimalBitTiming = 0; maxNibblesPerTrack = 0; } @@ -88,6 +89,13 @@ LPBYTE CImageBase::ms_pWorkBuffer = NULL; //----------------------------------------------------------------------------- +bool CImageBase::WriteImageHeader(ImageInfo* pImageInfo, LPBYTE pHdr, const UINT hdrSize) +{ + return WriteImageData(pImageInfo, pHdr, hdrSize, 0); +} + +//----------------------------------------------------------------------------- + bool CImageBase::ReadTrack(ImageInfo* pImageInfo, const int nTrack, LPBYTE pTrackBuffer, const UINT uTrackSize) { const long offset = pImageInfo->uOffset + nTrack * uTrackSize; @@ -100,70 +108,10 @@ bool CImageBase::ReadTrack(ImageInfo* pImageInfo, const int nTrack, LPBYTE pTrac bool CImageBase::WriteTrack(ImageInfo* pImageInfo, const int nTrack, LPBYTE pTrackBuffer, const UINT uTrackSize) { - const long Offset = pImageInfo->uOffset + nTrack * uTrackSize; - memcpy(&pImageInfo->pImageBuffer[Offset], pTrackBuffer, uTrackSize); + const long offset = pImageInfo->uOffset + nTrack * uTrackSize; + memcpy(&pImageInfo->pImageBuffer[offset], pTrackBuffer, uTrackSize); - if (pImageInfo->FileType == eFileNormal) - { - if (pImageInfo->hFile == INVALID_HANDLE_VALUE) - return false; - - SetFilePointer(pImageInfo->hFile, Offset, NULL, FILE_BEGIN); - - DWORD dwBytesWritten; - BOOL bRes = WriteFile(pImageInfo->hFile, pTrackBuffer, uTrackSize, &dwBytesWritten, NULL); - _ASSERT(dwBytesWritten == uTrackSize); - if (!bRes || dwBytesWritten != uTrackSize) - return false; - } - else if (pImageInfo->FileType == eFileGZip) - { - // Write entire compressed image each time (dirty track change or dirty disk removal) - gzFile hGZFile = gzopen(pImageInfo->szFilename.c_str(), "wb"); - if (hGZFile == NULL) - return false; - - int nLen = gzwrite(hGZFile, pImageInfo->pImageBuffer, pImageInfo->uImageSize); - int nRes = gzclose(hGZFile); // close before returning (due to error) to avoid resource leak - hGZFile = NULL; - - if (nLen != pImageInfo->uImageSize) - return false; - - if (nRes != Z_OK) - return false; - } - else if (pImageInfo->FileType == eFileZip) - { - // Write entire compressed image each time (dirty track change or dirty disk removal) - // NB. Only support Zip archives with a single file - zipFile hZipFile = zipOpen(pImageInfo->szFilename.c_str(), APPEND_STATUS_CREATE); - if (hZipFile == NULL) - return false; - - int nRes = zipOpenNewFileInZip(hZipFile, pImageInfo->szFilenameInZip.c_str(), &pImageInfo->zipFileInfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_BEST_SPEED); - if (nRes != ZIP_OK) - return false; - - nRes = zipWriteInFileInZip(hZipFile, pImageInfo->pImageBuffer, pImageInfo->uImageSize); - if (nRes != ZIP_OK) - return false; - - nRes = zipCloseFileInZip(hZipFile); - if (nRes != ZIP_OK) - return false; - - nRes = zipClose(hZipFile, NULL); - if (nRes != ZIP_OK) - return false; - } - else - { - _ASSERT(0); - return false; - } - - return true; + return WriteImageData(pImageInfo, pTrackBuffer, uTrackSize, offset); } //----------------------------------------------------------------------------- @@ -201,15 +149,15 @@ bool CImageBase::ReadBlock(ImageInfo* pImageInfo, const int nBlock, LPBYTE pBloc bool CImageBase::WriteBlock(ImageInfo* pImageInfo, const int nBlock, LPBYTE pBlockBuffer) { - long Offset = pImageInfo->uOffset + nBlock * HD_BLOCK_SIZE; - const bool bGrowImageBuffer = (UINT)Offset+HD_BLOCK_SIZE > pImageInfo->uImageSize; + long offset = pImageInfo->uOffset + nBlock * HD_BLOCK_SIZE; + const bool bGrowImageBuffer = (UINT)offset+HD_BLOCK_SIZE > pImageInfo->uImageSize; if (pImageInfo->FileType == eFileGZip || pImageInfo->FileType == eFileZip) { if (bGrowImageBuffer) { // Horribly inefficient! (Unzip to a normal file if you want better performance!) - const UINT uNewImageSize = Offset+HD_BLOCK_SIZE; + const UINT uNewImageSize = offset+HD_BLOCK_SIZE; BYTE* pNewImageBuffer = new BYTE [uNewImageSize]; memcpy(pNewImageBuffer, pImageInfo->pImageBuffer, pImageInfo->uImageSize); @@ -220,27 +168,48 @@ bool CImageBase::WriteBlock(ImageInfo* pImageInfo, const int nBlock, LPBYTE pBlo pImageInfo->uImageSize = uNewImageSize; } - memcpy(&pImageInfo->pImageBuffer[Offset], pBlockBuffer, HD_BLOCK_SIZE); + memcpy(&pImageInfo->pImageBuffer[offset], pBlockBuffer, HD_BLOCK_SIZE); } + if (!WriteImageData(pImageInfo, pBlockBuffer, HD_BLOCK_SIZE, offset)) + { + _ASSERT(0); + return false; + } + + if (pImageInfo->FileType == eFileNormal) + { + if (bGrowImageBuffer) + pImageInfo->uImageSize += HD_BLOCK_SIZE; + } + + return true; +} + +//----------------------------------------------------------------------------- + +bool CImageBase::WriteImageData(ImageInfo* pImageInfo, LPBYTE pSrcBuffer, const UINT uSrcSize, const long offset) +{ if (pImageInfo->FileType == eFileNormal) { if (pImageInfo->hFile == INVALID_HANDLE_VALUE) return false; - SetFilePointer(pImageInfo->hFile, Offset, NULL, FILE_BEGIN); + if (SetFilePointer(pImageInfo->hFile, offset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER) + { + DWORD err = GetLastError(); + return false; + } DWORD dwBytesWritten; - BOOL bRes = WriteFile(pImageInfo->hFile, pBlockBuffer, HD_BLOCK_SIZE, &dwBytesWritten, NULL); - if (!bRes || dwBytesWritten != HD_BLOCK_SIZE) + BOOL bRes = WriteFile(pImageInfo->hFile, pSrcBuffer, uSrcSize, &dwBytesWritten, NULL); + _ASSERT(dwBytesWritten == uSrcSize); + if (!bRes || dwBytesWritten != uSrcSize) return false; - - if (bGrowImageBuffer) - pImageInfo->uImageSize += HD_BLOCK_SIZE; } else if (pImageInfo->FileType == eFileGZip) { - // Write entire compressed image each time a block is written + // Write entire compressed image each time (dirty track change or dirty disk removal or a HDD block is written) gzFile hGZFile = gzopen(pImageInfo->szFilename.c_str(), "wb"); if (hGZFile == NULL) return false; @@ -257,25 +226,45 @@ bool CImageBase::WriteBlock(ImageInfo* pImageInfo, const int nBlock, LPBYTE pBlo } else if (pImageInfo->FileType == eFileZip) { - // Write entire compressed image each time a block is written + // Write entire compressed image each time (dirty track change or dirty disk removal or a HDD block is written) // NB. Only support Zip archives with a single file + // - there is no delete in a zipfile, so would need to copy files from old to new zip file! + _ASSERT(pImageInfo->uNumEntriesInZip == 1); // Should never occur, since image will be write-protected in CheckZipFile() + if (pImageInfo->uNumEntriesInZip > 1) + return false; + zipFile hZipFile = zipOpen(pImageInfo->szFilename.c_str(), APPEND_STATUS_CREATE); if (hZipFile == NULL) return false; - int nRes = zipOpenNewFileInZip(hZipFile, pImageInfo->szFilenameInZip.c_str(), &pImageInfo->zipFileInfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_BEST_SPEED); - if (nRes != ZIP_OK) - return false; + int nOpenedFileInZip = ZIP_BADZIPFILE; - nRes = zipWriteInFileInZip(hZipFile, pImageInfo->pImageBuffer, pImageInfo->uImageSize); - if (nRes != ZIP_OK) - return false; + try + { + nOpenedFileInZip = zipOpenNewFileInZip(hZipFile, pImageInfo->szFilenameInZip.c_str(), &pImageInfo->zipFileInfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_BEST_SPEED); + if (nOpenedFileInZip != ZIP_OK) + throw false; - nRes = zipCloseFileInZip(hZipFile); - if (nRes != ZIP_OK) - return false; + int nRes = zipWriteInFileInZip(hZipFile, pImageInfo->pImageBuffer, pImageInfo->uImageSize); + if (nRes != ZIP_OK) + throw false; - nRes = zipClose(hZipFile, NULL); + nOpenedFileInZip = ZIP_BADZIPFILE; + nRes = zipCloseFileInZip(hZipFile); + if (nRes != ZIP_OK) + throw false; + } + catch (bool) + { + if (nOpenedFileInZip == ZIP_OK) + zipCloseFileInZip(hZipFile); + + zipClose(hZipFile, NULL); + + return false; + } + + int nRes = zipClose(hZipFile, NULL); if (nRes != ZIP_OK) return false; } @@ -669,7 +658,7 @@ public: } virtual bool AllowCreate(void) { return true; } - virtual UINT GetImageSizeForCreate(void) { return TRACK_DENIBBLIZED_SIZE * TRACKS_STANDARD; } + virtual UINT GetImageSizeForCreate(void) { m_uNumTracksInImage = TRACKS_STANDARD; return TRACK_DENIBBLIZED_SIZE * TRACKS_STANDARD; } virtual eImageType GetType(void) { return eImageDO; } virtual const char* GetCreateExtensions(void) { return ".do;.dsk"; } @@ -776,7 +765,7 @@ public: } virtual bool AllowCreate(void) { return true; } - virtual UINT GetImageSizeForCreate(void) { return NIB1_TRACK_SIZE * TRACKS_STANDARD; } + virtual UINT GetImageSizeForCreate(void) { m_uNumTracksInImage = TRACKS_STANDARD; return NIB1_TRACK_SIZE * TRACKS_STANDARD; } virtual eImageType GetType(void) { return eImageNIB1; } virtual const char* GetCreateExtensions(void) { return ".nib"; } @@ -1061,10 +1050,10 @@ public: //------------------------------------- -class CWOZEmptyTrack +class CWOZImageHelper { public: - CWOZEmptyTrack(void) + CWOZImageHelper(void) { m_pWOZEmptyTrack = new BYTE[CWOZHelper::EMPTY_TRACK_SIZE]; @@ -1080,7 +1069,7 @@ public: m_pWOZEmptyTrack[i] = n; } } - virtual ~CWOZEmptyTrack(void) { delete [] m_pWOZEmptyTrack; } + virtual ~CWOZImageHelper(void) { delete [] m_pWOZEmptyTrack; } void ReadEmptyTrack(LPBYTE pTrackImageBuffer, int* pNibbles, UINT* pBitCount) { @@ -1090,13 +1079,21 @@ public: return; } + bool UpdateWOZHeaderCRC(ImageInfo* pImageInfo, CImageBase* pImageBase, UINT extendedSize) + { + BYTE* pImage = pImageInfo->pImageBuffer; + CWOZHelper::WOZHeader* pWozHdr = (CWOZHelper::WOZHeader*) pImage; + pWozHdr->crc32 = crc32(0, pImage+sizeof(CWOZHelper::WOZHeader), pImageInfo->uImageSize-sizeof(CWOZHelper::WOZHeader)); + return pImageBase->WriteImageHeader(pImageInfo, pImage, sizeof(CWOZHelper::WOZHeader)+extendedSize); + } + private: BYTE* m_pWOZEmptyTrack; }; //------------------------------------- -class CWOZ1Image : public CImageBase, private CWOZEmptyTrack +class CWOZ1Image : public CImageBase, private CWOZImageHelper { public: CWOZ1Image(void) {} @@ -1109,38 +1106,106 @@ public: 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; + BYTE* pTrackMap = ((CWOZHelper::Tmap*)pImageInfo->pWOZTrackMap)->tmap; - const int trackFromTMAP = pTrackMap[(UINT)(phase * 2)]; - if (trackFromTMAP == 0xFF) + const BYTE indexFromTMAP = pTrackMap[(UINT)(phase * 2)]; + if (indexFromTMAP == CWOZHelper::TMAP_TRACK_EMPTY) return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount); - ReadTrack(pImageInfo, trackFromTMAP, pTrackImageBuffer, CWOZHelper::WOZ1_TRACK_SIZE); + ReadTrack(pImageInfo, indexFromTMAP, pTrackImageBuffer, CWOZHelper::WOZ1_TRACK_SIZE); CWOZHelper::TRKv1* pTRK = (CWOZHelper::TRKv1*) &pTrackImageBuffer[CWOZHelper::WOZ1_TRK_OFFSET]; - *pNibbles = pTRK->bytesUsed; *pBitCount = pTRK->bitCount; + *pNibbles = pTRK->bytesUsed; } + // TODO: support writing a bitCount (ie. fractional nibbles) virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) { - // TODO - _ASSERT(0); - } + if (nNibbles > CWOZHelper::WOZ1_TRK_OFFSET) + { + _ASSERT(0); + LogFileOutput("WOZ1 Write Track: failed - track too big (%08X, phase=%f) for file: %s\n", nNibbles, phase, pImageInfo->szFilename.c_str()); + return; + } - // 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 + UINT extendedSize = 0; + BYTE* pTrackMap = ((CWOZHelper::Tmap*)pImageInfo->pWOZTrackMap)->tmap; + + BYTE indexFromTMAP = pTrackMap[(UINT)(phase * 2)]; + if (indexFromTMAP == CWOZHelper::TMAP_TRACK_EMPTY) + { + const BYTE track = (BYTE)(phase*2); + { + int highestIdx = -1; + for (UINT i=0; i highestIdx) + highestIdx = pTrackMap[i]; + } + indexFromTMAP = (highestIdx == -1) ? 0 : highestIdx+1; + } + pTrackMap[track] = indexFromTMAP; + if (track-1 >= 0) pTrackMap[track-1] = indexFromTMAP; // WOZ spec: track is also visible from neighboring quarter tracks + if (track+1 < CWOZHelper::MAX_QUARTER_TRACKS_5_25) pTrackMap[track+1] = indexFromTMAP; + + const UINT trackSizeRoundedUp = CWOZHelper::WOZ1_TRACK_SIZE; + const UINT newImageSize = pImageInfo->uImageSize + trackSizeRoundedUp; + BYTE* pNewImageBuffer = new BYTE[newImageSize]; + memcpy(pNewImageBuffer, pImageInfo->pImageBuffer, pImageInfo->uImageSize); + + // NB. delete old pImageBuffer: pWOZTrackMap updated in WOZUpdateInfo() by parent function + + delete [] pImageInfo->pImageBuffer; + pTrackMap = NULL; // invalidate + pImageInfo->pImageBuffer = pNewImageBuffer; + pImageInfo->uImageSize = newImageSize; + + // NB. pTrackImageBuffer[] is at least WOZ1_TRACK_SIZE in size + memset(&pTrackImageBuffer[nNibbles], 0, CWOZHelper::WOZ1_TRACK_SIZE-nNibbles); + CWOZHelper::TRKv1* pTRK = (CWOZHelper::TRKv1*) &pTrackImageBuffer[CWOZHelper::WOZ1_TRK_OFFSET]; + pTRK->bytesUsed = nNibbles; + pTRK->bitCount = nNibbles * 8; + + CWOZHelper::WOZChunkHdr* pTrksHdr = (CWOZHelper::WOZChunkHdr*) &pImageInfo->pImageBuffer[pImageInfo->uOffset - sizeof(CWOZHelper::WOZChunkHdr)]; + pTrksHdr->size += trackSizeRoundedUp; + + extendedSize = pImageInfo->uOffset - sizeof(CWOZHelper::WOZHeader); + } + + // NB. pTrackImageBuffer[] is at least WOZ1_TRACK_SIZE in size + { + CWOZHelper::TRKv1* pTRK = (CWOZHelper::TRKv1*) &pTrackImageBuffer[CWOZHelper::WOZ1_TRK_OFFSET]; + UINT bitCount = pTRK->bitCount; + UINT trackSize = pTRK->bytesUsed; + _ASSERT(trackSize == nNibbles); + if (trackSize != nNibbles) + { + _ASSERT(0); + LogFileOutput("WOZ1 Write Track: (warning) attempting to write %08X when trackSize is %08X (phase=%f)\n", nNibbles, trackSize, phase); + // NB. just a warning, not a failure (therefore nNibbles < WOZ1_TRK_OFFSET, due to check at start of function) + } + } + + if (!WriteTrack(pImageInfo, indexFromTMAP, pTrackImageBuffer, CWOZHelper::WOZ1_TRACK_SIZE)) + { + _ASSERT(0); + LogFileOutput("WOZ1 Write Track: failed to write track (phase=%f) for file: %s\n", phase, pImageInfo->szFilename.c_str()); + return; + } + + // TODO: zip/gzip: combine the track & hdr writes so that the file is only compressed & written once + if (!UpdateWOZHeaderCRC(pImageInfo, this, extendedSize)) + { + _ASSERT(0); + LogFileOutput("WOZ1 Write Track: failed to write header CRC for file: %s\n", pImageInfo->szFilename.c_str()); + } + } virtual eImageType GetType(void) { return eImageWOZ1; } virtual const char* GetCreateExtensions(void) { return ".woz"; } @@ -1149,7 +1214,7 @@ public: //------------------------------------- -class CWOZ2Image : public CImageBase, private CWOZEmptyTrack +class CWOZ2Image : public CImageBase, private CWOZImageHelper { public: CWOZ2Image(void) {} @@ -1162,45 +1227,131 @@ public: 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; + BYTE* pTrackMap = ((CWOZHelper::Tmap*)pImageInfo->pWOZTrackMap)->tmap; - const int trackFromTMAP = pTrackMap[(UINT)(phase * 2)]; - if (trackFromTMAP == 0xFF) + const BYTE indexFromTMAP = pTrackMap[(BYTE)(phase * 2)]; + if (indexFromTMAP == CWOZHelper::TMAP_TRACK_EMPTY) return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount); CWOZHelper::TRKv2* pTRKS = (CWOZHelper::TRKv2*) &pImageInfo->pImageBuffer[pImageInfo->uOffset]; - CWOZHelper::TRKv2* pTRK = &pTRKS[trackFromTMAP]; + CWOZHelper::TRKv2* pTRK = &pTRKS[indexFromTMAP]; *pBitCount = pTRK->bitCount; *pNibbles = (pTRK->bitCount+7) / 8; const UINT maxNibblesPerTrack = pImageInfo->maxNibblesPerTrack; - _ASSERT(*pNibbles <= (int)maxNibblesPerTrack); if (*pNibbles > (int)maxNibblesPerTrack) + { + _ASSERT(0); + LogFileOutput("WOZ2 Read Track: attempting to read more than max nibbles! (phase=%f)\n", phase); return ReadEmptyTrack(pTrackImageBuffer, pNibbles, pBitCount); // TODO: Enlarge track buffer, but for now just return an empty track + } memcpy(pTrackImageBuffer, &pImageInfo->pImageBuffer[pTRK->startBlock*CWOZHelper::BLOCK_SIZE], *pNibbles); } + // TODO: support writing a bitCount (ie. fractional nibbles) virtual void Write(ImageInfo* pImageInfo, const float phase, LPBYTE pTrackImageBuffer, int nNibbles) { - // TODO - _ASSERT(0); + UINT extendedSize = 0; + BYTE* pTrackMap = ((CWOZHelper::Tmap*)pImageInfo->pWOZTrackMap)->tmap; + + BYTE indexFromTMAP = pTrackMap[(BYTE)(phase * 2)]; + if (indexFromTMAP == CWOZHelper::TMAP_TRACK_EMPTY) + { + const BYTE track = (BYTE)(phase*2); + { + int highestIdx = -1; + for (UINT i=0; i highestIdx) + highestIdx = pTrackMap[i]; + } + indexFromTMAP = (highestIdx == -1) ? 0 : highestIdx+1; + } + pTrackMap[track] = indexFromTMAP; + if (track-1 >= 0) pTrackMap[track-1] = indexFromTMAP; // WOZ spec: track is also visible from neighboring quarter tracks + if (track+1 < CWOZHelper::MAX_QUARTER_TRACKS_5_25) pTrackMap[track+1] = indexFromTMAP; + + const UINT trackSizeRoundedUp = (nNibbles + CWOZHelper::BLOCK_SIZE-1) & ~(CWOZHelper::BLOCK_SIZE-1); + const UINT newImageSize = pImageInfo->uImageSize + trackSizeRoundedUp; + BYTE* pNewImageBuffer = new BYTE[newImageSize]; + + memcpy(pNewImageBuffer, pImageInfo->pImageBuffer, pImageInfo->uImageSize); + memset(pNewImageBuffer+pImageInfo->uImageSize, 0, trackSizeRoundedUp); + + // NB. delete old pImageBuffer: pWOZTrackMap updated in WOZUpdateInfo() by parent function + + delete [] pImageInfo->pImageBuffer; + pTrackMap = NULL; // invalidate + pImageInfo->pImageBuffer = pNewImageBuffer; + pImageInfo->uImageSize = newImageSize; + + CWOZHelper::TRKv2* pTRKS = (CWOZHelper::TRKv2*) &pImageInfo->pImageBuffer[pImageInfo->uOffset]; + CWOZHelper::TRKv2* pTRK = &pTRKS[indexFromTMAP]; + pTRK->blockCount = trackSizeRoundedUp / CWOZHelper::BLOCK_SIZE; + pTRK->startBlock = 3; + for (UINT i=0; istartBlock += pTRKS[i].blockCount; + pTRK->bitCount = nNibbles * 8; + + CWOZHelper::WOZChunkHdr* pTrksHdr = (CWOZHelper::WOZChunkHdr*) (&pImageInfo->pImageBuffer[pImageInfo->uOffset] - sizeof(CWOZHelper::WOZChunkHdr)); + pTrksHdr->size += trackSizeRoundedUp; + + const long offset = pTRK->startBlock * CWOZHelper::BLOCK_SIZE; + memcpy(&pImageInfo->pImageBuffer[offset], pTrackImageBuffer, nNibbles); + + if (!WriteImageData(pImageInfo, &pImageInfo->pImageBuffer[offset], trackSizeRoundedUp, offset)) // write rounded-up track data + { + _ASSERT(0); + LogFileOutput("WOZ2 Write Track: failed to write track (phase=%f) for file: %s\n", phase, pImageInfo->szFilename.c_str()); + return; + } + + extendedSize = ((BYTE*)pTRKS + sizeof(CWOZHelper::Trks) - pNewImageBuffer) - sizeof(CWOZHelper::WOZHeader); + } + else + { + CWOZHelper::TRKv2* pTRKS = (CWOZHelper::TRKv2*) &pImageInfo->pImageBuffer[pImageInfo->uOffset]; + CWOZHelper::TRKv2* pTRK = &pTRKS[indexFromTMAP]; + { + UINT bitCount = pTRK->bitCount; + UINT trackSize = (pTRK->bitCount + 7) / 8; + _ASSERT(trackSize == nNibbles); + if (trackSize != nNibbles) + { + _ASSERT(0); + LogFileOutput("WOZ2 Write Track: attempting to write %08X when trackSize is %08X (phase=%f)\n", nNibbles, trackSize, phase); + return; + } + } + + const long offset = pTRK->startBlock * CWOZHelper::BLOCK_SIZE; + memcpy(&pImageInfo->pImageBuffer[offset], pTrackImageBuffer, nNibbles); + + if (!WriteImageData(pImageInfo, pTrackImageBuffer, nNibbles, offset)) + { + _ASSERT(0); + LogFileOutput("WOZ2 Write Track: failed to write track (phase=%f) for file: %s\n", phase, pImageInfo->szFilename.c_str()); + return; + } + } + + // TODO: zip/gzip: combine the track & hdr writes so that the file is only compressed & written once + if (!UpdateWOZHeaderCRC(pImageInfo, this, extendedSize)) + { + _ASSERT(0); + LogFileOutput("WOZ2 Write Track: failed to write header CRC for file: %s\n", pImageInfo->szFilename.c_str()); + } } - // 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 bool AllowCreate(void) { return true; } + virtual UINT GetImageSizeForCreate(void) { m_uNumTracksInImage = CWOZHelper::MAX_TRACKS_5_25; return sizeof(CWOZHelper::WOZHeader); } virtual eImageType GetType(void) { return eImageWOZ2; } virtual const char* GetCreateExtensions(void) { return ".woz"; } @@ -1309,29 +1460,32 @@ bool C2IMGHelper::IsLocked(void) //----------------------------------------------------------------------------- // Pre: already matched the WOZ header -eDetectResult CWOZHelper::ProcessChunks(const LPBYTE pImage, const DWORD dwImageSize, DWORD& dwOffset, BYTE*& pTrackMap) +eDetectResult CWOZHelper::ProcessChunks(ImageInfo* pImageInfo, DWORD& dwOffset) { - UINT32* pImage32 = (uint32_t*) (pImage + sizeof(WOZHeader)); - UINT32 imageSizeRemaining = dwImageSize - sizeof(WOZHeader); + UINT32* pImage32 = (uint32_t*) (pImageInfo->pImageBuffer + sizeof(WOZHeader)); + int imageSizeRemaining = pImageInfo->uImageSize - sizeof(WOZHeader); + _ASSERT(imageSizeRemaining >= 0); + if (imageSizeRemaining < 0) + return eMismatch; - while(imageSizeRemaining > 8) + while(imageSizeRemaining >= sizeof(WOZChunkHdr)) { UINT32 chunkId = *pImage32++; UINT32 chunkSize = *pImage32++; - imageSizeRemaining -= 8; + imageSizeRemaining -= sizeof(WOZChunkHdr); switch(chunkId) { case INFO_CHUNK_ID: - m_pInfo = (InfoChunkv2*)(pImage32-2); + m_pInfo = (InfoChunkv2*)pImage32; if (m_pInfo->v1.diskType != InfoChunk::diskType5_25) return eMismatch; break; case TMAP_CHUNK_ID: - pTrackMap = (uint8_t*)pImage32; + pImageInfo->pWOZTrackMap = (BYTE*) pImage32; break; case TRKS_CHUNK_ID: - dwOffset = dwImageSize - imageSizeRemaining; // offset into image of track data + dwOffset = pImageInfo->uOffset = pImageInfo->uImageSize - imageSizeRemaining; // offset into image of track data break; case WRIT_CHUNK_ID: // WOZ v2 (optional) break; @@ -1441,90 +1595,133 @@ ImageError_e CImageHelperBase::CheckZipFile(LPCTSTR pszImageFilename, ImageInfo* unz_file_info file_info; char szFilename[MAX_PATH]; memset(szFilename, 0, sizeof(szFilename)); - int nRes = 0, nLen = 0; + BYTE* pImageBuffer = NULL; + ImageInfo* pImageInfo2 = NULL; + CImageBase* pImageType = NULL; + UINT numValidImages = 0; try { - nRes = unzGetGlobalInfo(hZipFile, &global_info); + int nRes = unzGetGlobalInfo(hZipFile, &global_info); if (nRes != UNZ_OK) throw eIMAGE_ERROR_ZIP; - nRes = unzGoToFirstFile(hZipFile); // Only support 1st file in zip archive for now + nRes = unzGoToFirstFile(hZipFile); if (nRes != UNZ_OK) throw eIMAGE_ERROR_ZIP; - nRes = unzGetCurrentFileInfo(hZipFile, &file_info, szFilename, MAX_PATH, NULL, 0, NULL, 0); - if (nRes != UNZ_OK) - throw eIMAGE_ERROR_ZIP; - - const UINT uFileSize = file_info.uncompressed_size; - if (uFileSize > GetMaxImageSize()) - throw eIMAGE_ERROR_BAD_SIZE; - - pImageInfo->pImageBuffer = new BYTE[uFileSize]; - - nRes = unzOpenCurrentFile(hZipFile); - if (nRes != UNZ_OK) - throw eIMAGE_ERROR_ZIP; - - nLen = unzReadCurrentFile(hZipFile, pImageInfo->pImageBuffer, uFileSize); - if (nLen < 0) + for (UINT n=0; n GetMaxImageSize()) + throw eIMAGE_ERROR_BAD_SIZE; + + if (uFileSize == 0) // skip directories or empty files + continue; + + // + + nRes = unzOpenCurrentFile(hZipFile); + if (nRes != UNZ_OK) + throw eIMAGE_ERROR_ZIP; + + BYTE* pImageBuffer = new BYTE[uFileSize]; + int nLen = unzReadCurrentFile(hZipFile, pImageBuffer, uFileSize); + if (nLen < 0) + { + unzCloseCurrentFile(hZipFile); // Must CloseCurrentFile before Close + throw eIMAGE_ERROR_UNSUPPORTED; + } + + nRes = unzCloseCurrentFile(hZipFile); + if (nRes != UNZ_OK) + throw eIMAGE_ERROR_ZIP; + + // Determine the file's extension and convert it to lowercase + TCHAR szExt[_MAX_EXT] = ""; + GetCharLowerExt(szExt, szFilename, _MAX_EXT); + + DWORD dwSize = nLen; + DWORD dwOffset = 0; + + ImageInfo*& pImageInfoForDetect = !pImageInfo2 ? pImageInfo : pImageInfo2; + pImageInfoForDetect->pImageBuffer = pImageBuffer; + CImageBase* pNewImageType = Detect(pImageBuffer, dwSize, szExt, dwOffset, pImageInfoForDetect); + + if (pNewImageType) + { + numValidImages++; + + if (numValidImages == 1) + { + pImageType = pNewImageType; + + pImageInfo->szFilenameInZip = szFilename; + memcpy(&pImageInfo->zipFileInfo.tmz_date, &file_info.tmu_date, sizeof(file_info.tmu_date)); + pImageInfo->zipFileInfo.dosDate = file_info.dosDate; + pImageInfo->zipFileInfo.internal_fa = file_info.internal_fa; + pImageInfo->zipFileInfo.external_fa = file_info.external_fa; + pImageInfo->uNumEntriesInZip = global_info.number_entry; + pImageInfo->pImageBuffer = pImageBuffer; + + pImageBuffer = NULL; + strFilenameInZip = szFilename; + + SetImageInfo(pImageInfo, eFileZip, dwOffset, pImageType, dwSize); + + pImageInfo2 = new ImageInfo(); // use this dummy one, as some members get overwritten during Detect() + } + } + + delete [] pImageBuffer; + pImageBuffer = NULL; + } } catch (ImageError_e error) { if (hZipFile) unzClose(hZipFile); + delete [] pImageBuffer; + delete pImageInfo2; + return error; } - nRes = unzClose(hZipFile); + delete pImageInfo2; + + int nRes = unzClose(hZipFile); hZipFile = NULL; if (nRes != UNZ_OK) return eIMAGE_ERROR_ZIP; - pImageInfo->szFilenameInZip = szFilename; - memcpy(&pImageInfo->zipFileInfo.tmz_date, &file_info.tmu_date, sizeof(file_info.tmu_date)); - pImageInfo->zipFileInfo.dosDate = file_info.dosDate; - pImageInfo->zipFileInfo.internal_fa = file_info.internal_fa; - pImageInfo->zipFileInfo.external_fa = file_info.external_fa; - pImageInfo->uNumEntriesInZip = global_info.number_entry; - strFilenameInZip = szFilename; - // - // Determine the file's extension and convert it to lowercase - TCHAR szExt[_MAX_EXT] = ""; - GetCharLowerExt(szExt, szFilename, _MAX_EXT); - - DWORD dwSize = nLen; - DWORD dwOffset = 0; - CImageBase* pImageType = Detect(pImageInfo->pImageBuffer, dwSize, szExt, dwOffset, pImageInfo); - if (!pImageType) - { - if (global_info.number_entry > 1) - return eIMAGE_ERROR_UNSUPPORTED_MULTI_ZIP; - return eIMAGE_ERROR_UNSUPPORTED; - } const eImageType Type = pImageType->GetType(); if (Type == eImageAPL || Type == eImageIIE || Type == eImagePRG) return eIMAGE_ERROR_UNSUPPORTED; if (global_info.number_entry > 1) - pImageInfo->bWriteProtected = 1; // Zip archives with multiple files are read-only (for now) + pImageInfo->bWriteProtected = 1; // Zip archives with multiple files are read-only (for now) - see WriteImageData() for zipfile + + pImageInfo->uNumValidImagesInZip = numValidImages; - SetImageInfo(pImageInfo, eFileZip, dwOffset, pImageType, dwSize); return eIMAGE_ERROR_NONE; } @@ -1619,19 +1816,29 @@ ImageError_e CImageHelperBase::CheckNormalFile(LPCTSTR pszImageFilename, ImageIn pImageType = GetImageForCreation(szExt, &dwSize); if (pImageType && dwSize) { - pImageInfo->pImageBuffer = new BYTE [dwSize]; - - if (pImageType->GetType() != eImageNIB1) + if (pImageType->GetType() == eImageWOZ2) { - ZeroMemory(pImageInfo->pImageBuffer, dwSize); + pImageInfo->pImageBuffer = m_WOZHelper.CreateEmptyDisk(dwSize); + pImageInfo->uImageSize = dwSize; + bool res = WOZUpdateInfo(pImageInfo, dwOffset); + _ASSERT(res); } else { - // Fill zero-length image buffer with alternating high-bit-set nibbles (GH#196, GH#338) - for (UINT i=0; ipImageBuffer = new BYTE[dwSize]; + + if (pImageType->GetType() == eImageNIB1) { - pImageInfo->pImageBuffer[i+0] = 0x80; // bit7 set, but 0x80 is an invalid nibble - pImageInfo->pImageBuffer[i+1] = 0x81; // bit7 set, but 0x81 is an invalid nibble + // Fill zero-length image buffer with alternating high-bit-set nibbles (GH#196, GH#338) + for (UINT i=0; ipImageBuffer[i+0] = 0x80; // bit7 set, but 0x80 is an invalid nibble + pImageInfo->pImageBuffer[i+1] = 0x81; // bit7 set, but 0x81 is an invalid nibble + } + } + else + { + ZeroMemory(pImageInfo->pImageBuffer, dwSize); } } @@ -1663,9 +1870,9 @@ ImageError_e CImageHelperBase::CheckNormalFile(LPCTSTR pszImageFilename, ImageIn //------------------------------------- -void CImageHelperBase::SetImageInfo(ImageInfo* pImageInfo, FileType_e eFileGZip, DWORD dwOffset, CImageBase* pImageType, DWORD dwSize) +void CImageHelperBase::SetImageInfo(ImageInfo* pImageInfo, FileType_e fileType, DWORD dwOffset, CImageBase* pImageType, DWORD dwSize) { - pImageInfo->FileType = eFileGZip; + pImageInfo->FileType = fileType; pImageInfo->uOffset = dwOffset; pImageInfo->pImageType = pImageType; pImageInfo->uImageSize = dwSize; @@ -1713,7 +1920,7 @@ ImageError_e CImageHelperBase::Open( LPCTSTR pszImageFilename, //------------------------------------- -void CImageHelperBase::Close(ImageInfo* pImageInfo, const bool bDeleteFile) +void CImageHelperBase::Close(ImageInfo* pImageInfo) { if (pImageInfo->hFile != INVALID_HANDLE_VALUE) { @@ -1721,17 +1928,32 @@ void CImageHelperBase::Close(ImageInfo* pImageInfo, const bool bDeleteFile) pImageInfo->hFile = INVALID_HANDLE_VALUE; } - if (bDeleteFile) - { - DeleteFile(pImageInfo->szFilename.c_str()); - } - pImageInfo->szFilename.clear(); delete [] pImageInfo->pImageBuffer; pImageInfo->pImageBuffer = NULL; } +//------------------------------------- + +bool CImageHelperBase::WOZUpdateInfo(ImageInfo* pImageInfo, DWORD& dwOffset) +{ + if (m_WOZHelper.ProcessChunks(pImageInfo, dwOffset) != eMatch) + { + _ASSERT(0); + return false; + } + + if (m_WOZHelper.IsWriteProtected()) + pImageInfo->bWriteProtected = true; + + pImageInfo->optimalBitTiming = m_WOZHelper.GetOptimalBitTiming(); + pImageInfo->maxNibblesPerTrack = m_WOZHelper.GetMaxNibblesPerTrack(); + + m_WOZHelper.InvalidateInfo(); + return true; +} + //----------------------------------------------------------------------------- CDiskImageHelper::CDiskImageHelper(void) : @@ -1799,14 +2021,18 @@ CImageBase* CDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* p if (imageType == eImageWOZ1 || imageType == eImageWOZ2) { - if (m_WOZHelper.ProcessChunks(pImage, dwSize, dwOffset, pImageInfo->pTrackMap) != eMatch) + CWOZHelper::WOZHeader* pWozHdr = (CWOZHelper::WOZHeader*) pImage; + if (pWozHdr->crc32 && // WOZ spec: CRC of 0 should be ignored + pWozHdr->crc32 != crc32(0, pImage+sizeof(CWOZHelper::WOZHeader), dwSize-sizeof(CWOZHelper::WOZHeader))) + { + int res = MessageBox(GetDesktopWindow(), "CRC mismatch\nContinue using image?", "AppleWin: WOZ Header", MB_ICONSTOP | MB_SETFOREGROUND | MB_YESNO); + if (res == IDNO) + return NULL; + } + + pImageInfo->uImageSize = dwSize; + if (!WOZUpdateInfo(pImageInfo, dwOffset)) return NULL; - -// if (m_WOZHelper.IsWriteProtected() && !pImageInfo->writeProtected) // Force write-protected until writing is supported - pImageInfo->bWriteProtected = true; - - pImageInfo->optimalBitTiming = m_WOZHelper.GetOptimalBitTiming(); - pImageInfo->maxNibblesPerTrack = m_WOZHelper.GetMaxNibblesPerTrack(); } else { @@ -1822,7 +2048,7 @@ CImageBase* CDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* p { pImageType->SetVolumeNumber( m_2IMGHelper.GetVolumeNumber() ); - if (m_2IMGHelper.IsLocked() && !pImageInfo->bWriteProtected) + if (m_2IMGHelper.IsLocked()) pImageInfo->bWriteProtected = true; } else @@ -1836,7 +2062,7 @@ CImageBase* CDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* p CImageBase* CDiskImageHelper::GetImageForCreation(const TCHAR* pszExt, DWORD* pCreateImageSize) { - // WE CREATE ONLY DOS ORDER (DO) OR 6656-NIBBLE (NIB) FORMAT FILES + // WE CREATE ONLY DOS ORDER (DO), 6656-NIBBLE (NIB) OR WOZ2 (WOZ) FORMAT FILES for (UINT uLoop = 0; uLoop < GetNumImages(); uLoop++) { if (!GetImage(uLoop)->AllowCreate()) @@ -1845,9 +2071,8 @@ CImageBase* CDiskImageHelper::GetImageForCreation(const TCHAR* pszExt, DWORD* pC if (*pszExt && _tcsstr(GetImage(uLoop)->GetCreateExtensions(), pszExt)) { CImageBase* pImageType = GetImage(uLoop); - SetNumTracksInImage(pImageType, TRACKS_STANDARD); // Assume default # tracks - *pCreateImageSize = pImageType->GetImageSizeForCreate(); + *pCreateImageSize = pImageType->GetImageSizeForCreate(); // Also sets m_uNumTracksInImage if (*pCreateImageSize == (UINT)-1) return NULL; @@ -1903,12 +2128,12 @@ CImageBase* CHardDiskImageHelper::Detect(LPBYTE pImage, DWORD dwSize, const TCHA { if (m_Result2IMG == eMatch) { - if (m_2IMGHelper.IsLocked() && !pImageInfo->bWriteProtected) + if (m_2IMGHelper.IsLocked()) pImageInfo->bWriteProtected = true; } } - pImageInfo->pTrackMap = 0; // TODO: WOZ + pImageInfo->pWOZTrackMap = 0; // TODO: WOZ pImageInfo->optimalBitTiming = 0; // TODO: WOZ pImageInfo->maxNibblesPerTrack = 0; // TODO @@ -1951,3 +2176,102 @@ UINT CHardDiskImageHelper::GetMinDetectSize(const UINT uImageSize, bool* pTempDe *pTempDetectBuffer = true; return m_2IMGHelper.GetMaxHdrSize(); } + +//----------------------------------------------------------------------------- + +#define ASSERT_OFFSET(x, offset) _ASSERT( ((BYTE*)&pWOZ->x - (BYTE*)pWOZ) == offset ) + +extern TCHAR VERSIONSTRING[]; // AppleWin.h + +BYTE* CWOZHelper::CreateEmptyDisk(DWORD& size) +{ + WOZEmptyImage525* pWOZ = new WOZEmptyImage525; + memset(pWOZ, 0, sizeof(WOZEmptyImage525)); + size = sizeof(WOZEmptyImage525); + _ASSERT(size == 3*BLOCK_SIZE); + + pWOZ->hdr.id1 = ID1_WOZ2; + pWOZ->hdr.id2 = ID2; + // hdr.crc32 done at end + + // INFO + ASSERT_OFFSET(infoHdr, 12); + pWOZ->infoHdr.id = INFO_CHUNK_ID; + pWOZ->infoHdr.size = (BYTE*)&pWOZ->tmapHdr - (BYTE*)&pWOZ->info; + _ASSERT(pWOZ->infoHdr.size == INFO_CHUNK_SIZE); + pWOZ->info.v1.version = 2; + pWOZ->info.v1.diskType = InfoChunk::diskType5_25; + pWOZ->info.v1.cleaned = 1; + std::string creator("AppleWin v"); + creator += std::string(VERSIONSTRING); + memset(&pWOZ->info.v1.creator[0], ' ', sizeof(pWOZ->info.v1.creator)); + memcpy(&pWOZ->info.v1.creator[0], creator.c_str(), creator.size()); // don't include null + pWOZ->info.diskSides = 1; + pWOZ->info.bootSectorFormat = InfoChunkv2::bootUnknown; // could be INIT'd to 13 or 16 sector + pWOZ->info.optimalBitTiming = InfoChunkv2::optimalBitTiming5_25; + pWOZ->info.compatibleHardware = 0; // unknown + pWOZ->info.requiredRAM = 0; // unknown + pWOZ->info.largestTrack = TRK_DEFAULT_BLOCK_COUNT_5_25; // unknown - but use default + + // TMAP + ASSERT_OFFSET(tmapHdr, 80); + pWOZ->tmapHdr.id = TMAP_CHUNK_ID; + pWOZ->tmapHdr.size = sizeof(pWOZ->tmap); + memset(&pWOZ->tmap, TMAP_TRACK_EMPTY, sizeof(pWOZ->tmap)); // all tracks empty + + // TRKS + ASSERT_OFFSET(trksHdr, 248); + pWOZ->trksHdr.id = TRKS_CHUNK_ID; + pWOZ->trksHdr.size = sizeof(pWOZ->trks); + for (UINT i = 0; i < sizeof(pWOZ->trks.trks) / sizeof(pWOZ->trks.trks[0]); i++) + { + pWOZ->trks.trks[i].startBlock = 0; // minimum startBlock (at end of file!) + pWOZ->trks.trks[i].blockCount = 0; + pWOZ->trks.trks[i].bitCount = 0; + } + + pWOZ->hdr.crc32 = crc32(0, (BYTE*)&pWOZ->infoHdr, sizeof(WOZEmptyImage525) - sizeof(WOZHeader)); + return (BYTE*) pWOZ; +} + +#if _DEBUG +// Replace the call in CheckNormalFile() to CreateEmptyDiskv1() to generate a WOZv1 empty image-file +BYTE* CWOZHelper::CreateEmptyDiskv1(DWORD& size) +{ + WOZv1EmptyImage525* pWOZ = new WOZv1EmptyImage525; + memset(pWOZ, 0, sizeof(WOZv1EmptyImage525)); + size = sizeof(WOZv1EmptyImage525); + _ASSERT(size == 256); + + pWOZ->hdr.id1 = ID1_WOZ1; + pWOZ->hdr.id2 = ID2; + // hdr.crc32 done at end + + // INFO + ASSERT_OFFSET(infoHdr, 12); + pWOZ->infoHdr.id = INFO_CHUNK_ID; + pWOZ->infoHdr.size = (BYTE*)&pWOZ->tmapHdr - (BYTE*)&pWOZ->info; + _ASSERT(pWOZ->infoHdr.size == INFO_CHUNK_SIZE); + pWOZ->info.version = 1; + pWOZ->info.diskType = InfoChunk::diskType5_25; + pWOZ->info.cleaned = 1; + std::string creator("AppleWin v"); + creator += std::string(VERSIONSTRING); + memset(&pWOZ->info.creator[0], ' ', sizeof(pWOZ->info.creator)); + memcpy(&pWOZ->info.creator[0], creator.c_str(), creator.size()); // don't include null + + // TMAP + ASSERT_OFFSET(tmapHdr, 80); + pWOZ->tmapHdr.id = TMAP_CHUNK_ID; + pWOZ->tmapHdr.size = sizeof(pWOZ->tmap); + memset(&pWOZ->tmap, TMAP_TRACK_EMPTY, sizeof(pWOZ->tmap)); // all tracks empty + + // TRKS + ASSERT_OFFSET(trksHdr, 248); + pWOZ->trksHdr.id = TRKS_CHUNK_ID; + pWOZ->trksHdr.size = 0; + + pWOZ->hdr.crc32 = crc32(0, (BYTE*)&pWOZ->infoHdr, sizeof(WOZv1EmptyImage525) - sizeof(WOZHeader)); + return (BYTE*) pWOZ; +} +#endif diff --git a/source/DiskImageHelper.h b/source/DiskImageHelper.h index 4e2e856e..b467dec2 100644 --- a/source/DiskImageHelper.h +++ b/source/DiskImageHelper.h @@ -31,11 +31,11 @@ struct ImageInfo std::string szFilenameInZip; zip_fileinfo zipFileInfo; UINT uNumEntriesInZip; + UINT uNumValidImagesInZip; // Floppy only - BYTE ValidTrack[TRACKS_MAX]; UINT uNumTracks; BYTE* pImageBuffer; - BYTE* pTrackMap; // WOZ only + BYTE* pWOZTrackMap; // WOZ only (points into pImageBuffer) BYTE optimalBitTiming; // WOZ only UINT maxNibblesPerTrack; @@ -73,6 +73,7 @@ public: virtual const char* GetCreateExtensions(void) = 0; virtual const char* GetRejectExtensions(void) = 0; + bool WriteImageHeader(ImageInfo* pImageInfo, LPBYTE pHdr, const UINT hdrSize); void SetVolumeNumber(const BYTE uVolumeNumber) { m_uVolumeNumber = uVolumeNumber; } bool IsValidImageSize(const DWORD uImageSize); @@ -88,6 +89,7 @@ protected: bool WriteTrack(ImageInfo* pImageInfo, const int nTrack, LPBYTE pTrackBuffer, const UINT uTrackSize); bool ReadBlock(ImageInfo* pImageInfo, const int nBlock, LPBYTE pBlockBuffer); bool WriteBlock(ImageInfo* pImageInfo, const int nBlock, LPBYTE pBlockBuffer); + bool WriteImageData(ImageInfo* pImageInfo, LPBYTE pSrcBuffer, const UINT uSrcSize, const long offset); LPBYTE Code62(int sector); void Decode62(LPBYTE imageptr); @@ -134,6 +136,10 @@ private: #pragma pack(push) #pragma pack(1) // Ensure Header2IMG & WOZ structs are packed +#pragma warning(push) +#pragma warning(disable: 4200) // Allow zero-sized array in struct + + class C2IMGHelper : public CHdrHelper { public: @@ -200,10 +206,15 @@ public: 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); + eDetectResult ProcessChunks(ImageInfo* pImageInfo, DWORD& dwOffset); bool IsWriteProtected(void) { return m_pInfo->v1.writeProtected == 1; } - BYTE GetOptimalBitTiming(void) { return (m_pInfo->v1.version >= 2) ? m_pInfo->optimalBitTiming : CWOZHelper::InfoChunkv2::optimalBitTiming5_25; } - UINT GetMaxNibblesPerTrack(void) { return (m_pInfo->v1.version >= 2) ? m_pInfo->largestTrack*CWOZHelper::BLOCK_SIZE : CWOZHelper::WOZ1_TRACK_SIZE; } + BYTE GetOptimalBitTiming(void) { return (m_pInfo->v1.version >= 2) ? m_pInfo->optimalBitTiming : InfoChunkv2::optimalBitTiming5_25; } + UINT GetMaxNibblesPerTrack(void) { return (m_pInfo->v1.version >= 2) ? m_pInfo->largestTrack*CWOZHelper::BLOCK_SIZE : WOZ1_TRACK_SIZE; } + void InvalidateInfo(void) { m_pInfo = NULL; } + BYTE* CreateEmptyDisk(DWORD& size); +#if _DEBUG + BYTE* CreateEmptyDiskv1(DWORD& size); +#endif static const UINT32 ID1_WOZ1 = '1ZOW'; // 'WOZ1' static const UINT32 ID1_WOZ2 = '2ZOW'; // 'WOZ2' @@ -217,10 +228,24 @@ public: }; static const UINT32 MAX_TRACKS_5_25 = 40; + static const UINT32 MAX_QUARTER_TRACKS_5_25 = MAX_TRACKS_5_25 * 4; static const UINT32 WOZ1_TRACK_SIZE = 6656; // 0x1A00 static const UINT32 WOZ1_TRK_OFFSET = 6646; - static const UINT32 EMPTY_TRACK_SIZE = 6400; + static const UINT32 EMPTY_TRACK_SIZE = 6400; // $C.5 blocks static const UINT32 BLOCK_SIZE = 512; + static const BYTE TMAP_TRACK_EMPTY = 0xFF; + static const UINT16 TRK_DEFAULT_BLOCK_COUNT_5_25 = 13; // $D is default for TRKv2.blockCount + + struct WOZChunkHdr + { + UINT32 id; + UINT32 size; + }; + + struct Tmap + { + BYTE tmap[MAX_QUARTER_TRACKS_5_25]; + }; struct TRKv1 { @@ -239,17 +264,22 @@ public: UINT32 bitCount; }; + struct Trks + { + TRKv2 trks[MAX_QUARTER_TRACKS_5_25]; + BYTE bits[0]; // bits[] starts at offset 3 x BLOCK_SIZE = 1536 + }; + 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' + static const UINT32 INFO_CHUNK_SIZE = 60; // Fixed size for both WOZv1 & WOZv2 struct InfoChunk { - UINT32 id; - UINT32 size; BYTE version; BYTE diskType; BYTE writeProtected; // 1 = Floppy is write protected @@ -281,9 +311,41 @@ private: static const BYTE optimalBitTiming5_25 = 32; }; - InfoChunkv2* m_pInfo; + InfoChunkv2* m_pInfo; // NB. image-specific - only valid during Detect(), which calls InvalidateInfo() when done + + // + + struct WOZEmptyImage525 // 5.25" + { + WOZHeader hdr; + + WOZChunkHdr infoHdr; + InfoChunkv2 info; + BYTE infoPadding[INFO_CHUNK_SIZE-sizeof(InfoChunkv2)]; + + WOZChunkHdr tmapHdr; + Tmap tmap; + + WOZChunkHdr trksHdr; + Trks trks; + }; + + struct WOZv1EmptyImage525 // 5.25" + { + WOZHeader hdr; + + WOZChunkHdr infoHdr; + InfoChunk info; + BYTE infoPadding[INFO_CHUNK_SIZE-sizeof(InfoChunk)]; + + WOZChunkHdr tmapHdr; + Tmap tmap; + + WOZChunkHdr trksHdr; + }; }; +#pragma warning(pop) #pragma pack(pop) //------------------------------------- @@ -304,7 +366,8 @@ public: } ImageError_e Open(LPCTSTR pszImageFilename, ImageInfo* pImageInfo, const bool bCreateIfNecessary, std::string& strFilenameInZip); - void Close(ImageInfo* pImageInfo, const bool bDeleteFile); + void Close(ImageInfo* pImageInfo); + bool WOZUpdateInfo(ImageInfo* pImageInfo, DWORD& dwOffset); virtual CImageBase* Detect(LPBYTE pImage, DWORD dwSize, const TCHAR* pszExt, DWORD& dwOffset, ImageInfo* pImageInfo) = 0; virtual CImageBase* GetImageForCreation(const TCHAR* pszExt, DWORD* pCreateImageSize) = 0; @@ -317,7 +380,7 @@ protected: 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); + void SetImageInfo(ImageInfo* pImageInfo, FileType_e fileType, DWORD dwOffset, CImageBase* pImageType, DWORD dwSize); UINT GetNumImages(void) { return m_vecImageTypes.size(); }; CImageBase* GetImage(UINT uIndex) { _ASSERT(uIndex