/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * Some items from the "tools" menu. * * [ There's a lot of cutting and pasting going on in here. Some of this * stuff needs to get refactored. ++ATM 20040729 ] */ #include "StdAfx.h" #include "Main.h" #include "DiskEditDialog.h" #include "ImageFormatDialog.h" #include "DiskConvertDialog.h" #include "ChooseDirDialog.h" #include "DoneOpenDialog.h" #include "OpenVolumeDialog.h" #include "DiskEditOpenDialog.h" #include "VolumeCopyDialog.h" #include "CreateImageDialog.h" #include "DiskArchive.h" #include "EOLScanDialog.h" #include "TwoImgPropsDialog.h" #include // need chsize() for TwoImgProps int MainWindow::TryDiskImgOverride(DiskImg* pImg, const WCHAR* fileSource, DiskImg::FSFormat defaultFormat, int* pDisplayFormat, bool allowUnknown, CString* pErrMsg) { ImageFormatDialog imf; *pErrMsg = ""; imf.InitializeValues(pImg); imf.fFileSource = fileSource; imf.fAllowUnknown = allowUnknown; if (pDisplayFormat == NULL) imf.SetQueryDisplayFormat(false); /* don't show "unknown format" if we have a default value */ if (defaultFormat != DiskImg::kFormatUnknown && imf.fFSFormat == DiskImg::kFormatUnknown) { imf.fFSFormat = defaultFormat; } LOGI(" On entry, sectord=%d format=%d", imf.fSectorOrder, imf.fFSFormat); if (imf.DoModal() != IDOK) { LOGI(" User bailed on IMF dialog"); return IDCANCEL; } LOGI(" On exit, sectord=%d format=%d", imf.fSectorOrder, imf.fFSFormat); if (pDisplayFormat != NULL) *pDisplayFormat = imf.fDisplayFormat; if (imf.fSectorOrder != pImg->GetSectorOrder() || imf.fFSFormat != pImg->GetFSFormat()) { LOGI("Initial values overridden, forcing img format"); DIError dierr; dierr = pImg->OverrideFormat(pImg->GetPhysicalFormat(), imf.fFSFormat, imf.fSectorOrder); if (dierr != kDIErrNone) { pErrMsg->Format(L"Unable to access disk image using selected" L" parameters. Error: %hs.", DiskImgLib::DIStrError(dierr)); // fall through to "return IDOK" } } return IDOK; } /* * ========================================================================== * Disk Editor * ========================================================================== */ void MainWindow::OnToolsDiskEdit(void) { DIError dierr; DiskImg img; CString loadName, saveFolder; CString failed, errMsg; DiskEditOpenDialog diskEditOpen(this); /* create three, show one */ BlockEditDialog blockEdit(this); SectorEditDialog sectorEdit(this); NibbleEditDialog nibbleEdit(this); DiskEditDialog* pEditDialog; int displayFormat; bool readOnly = true; /* flush current archive in case that's what we're planning to edit */ OnFileSave(); CheckedLoadString(&failed, IDS_FAILED); diskEditOpen.fArchiveOpen = false; if (fpOpenArchive != NULL && fpOpenArchive->GetArchiveKind() == GenericArchive::kArchiveDiskImage) { diskEditOpen.fArchiveOpen = true; } if (diskEditOpen.DoModal() != IDOK) goto bail; /* * Choose something to open, based on "fOpenWhat". */ if (diskEditOpen.fOpenWhat == DiskEditOpenDialog::kOpenFile) { CString openFilters, saveFolder; openFilters = kOpenDiskImage; openFilters += kOpenAll; openFilters += kOpenEnd; CFileDialog dlg(TRUE, L"dsk", NULL, OFN_FILEMUSTEXIST, openFilters, this); /* for now, everything is read-only */ dlg.m_ofn.Flags |= OFN_HIDEREADONLY; dlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder); if (dlg.DoModal() != IDOK) goto bail; loadName = dlg.GetPathName(); readOnly = true; // add to file dialog saveFolder = dlg.m_ofn.lpstrFile; saveFolder = saveFolder.Left(dlg.m_ofn.nFileOffset); fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder); } else if (diskEditOpen.fOpenWhat == DiskEditOpenDialog::kOpenVolume) { OpenVolumeDialog dlg(this); int result; result = dlg.DoModal(); if (result != IDOK) goto bail; loadName = dlg.fChosenDrive; readOnly = (dlg.fReadOnly != 0); } else if (diskEditOpen.fOpenWhat == DiskEditOpenDialog::kOpenCurrent) { // get values from currently open archive assert(fpOpenArchive != NULL); loadName = fpOpenArchive->GetPathName(); readOnly = fpOpenArchive->IsReadOnly(); } else { LOGI("GLITCH: unexpected fOpenWhat %d", diskEditOpen.fOpenWhat); ASSERT(false); goto bail; } LOGI("Disk editor what=%d name='%ls' ro=%d", diskEditOpen.fOpenWhat, (LPCWSTR) loadName, readOnly); #if 1 { CWaitCursor waitc; /* open the image file and analyze it */ CStringA loadNameA(loadName); dierr = img.OpenImage(loadNameA, PathProposal::kLocalFssep, true); } #else /* quick test of memory-buffer-based interface */ FILE* tmpfp; char* phatbuf; long length; tmpfp = fopen(loadName, "rb"); ASSERT(tmpfp != NULL); fseek(tmpfp, 0, SEEK_END); length = ftell(tmpfp); rewind(tmpfp); LOGI(" PHATBUF %d", length); phatbuf = new char[length]; if (fread(phatbuf, length, 1, tmpfp) != 1) LOGI("FREAD FAILED %d", errno); fclose(tmpfp); dierr = img.OpenImage(phatbuf, length, true); #endif if (dierr != kDIErrNone) { errMsg.Format(L"Unable to open disk image: %hs.", DiskImgLib::DIStrError(dierr)); MessageBox(errMsg, failed, MB_OK|MB_ICONSTOP); goto bail; } #if 0 { /* * TEST - set custom entry to match Sheila NIB image. We have to * do this here so that the disk routines can analyze the disk * correctly. We need a way to enter these parameters in the * disk editor and then re-analyze the image. (Not to mention a way * to flip in and out of block/sector/nibble mode.) */ DiskImg::NibbleDescr sheilaDescr = { "H.A.L. Labs (Sheila)", 16, { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, 0x00, // checksum seed true, // verify checksum true, // verify track 2, // epilog verify count { 0xd5, 0xaa, 0xda }, { 0xde, 0xaa, 0xeb }, 0x00, // checksum seed true, // verify checksum 2, // epilog verify count DiskImg::kNibbleEnc62, DiskImg::kNibbleSpecialNone, }; /* same thing, but for original 13-sector Zork */ DiskImg::NibbleDescr zork13Descr = { "Zork 13-sector", 13, { 0xd5, 0xaa, 0xb5 }, { 0xde, 0xaa, 0xeb }, 0x00, false, false, 0, { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, 0x1f, true, 0, DiskImg::kNibbleEnc53, DiskImg::kNibbleSpecialNone, }; img.SetCustomNibbleDescr(&zork13Descr); } #endif if (img.AnalyzeImage() != kDIErrNone) { errMsg.Format(L"The file '%ls' doesn't seem to hold a valid disk image.", (LPCWSTR) loadName); MessageBox(errMsg, failed, MB_OK|MB_ICONSTOP); goto bail; } if (img.ShowAsBlocks()) displayFormat = ImageFormatDialog::kShowAsBlocks; else displayFormat = ImageFormatDialog::kShowAsSectors; /* if they can't do anything but view nibbles, don't demand an fs format */ bool allowUnknown; allowUnknown = false; if (!img.GetHasSectors() && !img.GetHasBlocks() && img.GetHasNibbles()) allowUnknown = true; /* * If requested (or necessary), verify the format. */ if (img.GetFSFormat() == DiskImg::kFormatUnknown || img.GetSectorOrder() == DiskImg::kSectorOrderUnknown || fPreferences.GetPrefBool(kPrQueryImageFormat)) { if (TryDiskImgOverride(&img, loadName, DiskImg::kFormatUnknown, &displayFormat, allowUnknown, &errMsg) != IDOK) { goto bail; } if (!errMsg.IsEmpty()) { ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } } /* select edit dialog type, based on blocks vs. sectors */ if (displayFormat == ImageFormatDialog::kShowAsSectors) pEditDialog = §orEdit; else if (displayFormat == ImageFormatDialog::kShowAsBlocks) pEditDialog = &blockEdit; else pEditDialog = &nibbleEdit; /* * Create an appropriate DiskFS object and hand it to the edit dialog. */ DiskFS* pDiskFS; pDiskFS = img.OpenAppropriateDiskFS(true); if (pDiskFS == NULL) { LOGI("HEY: OpenAppropriateDiskFS failed!"); goto bail; } pDiskFS->SetScanForSubVolumes(DiskFS::kScanSubEnabled); { CWaitCursor wait; // big ProDOS volumes can be slow dierr = pDiskFS->Initialize(&img, DiskFS::kInitFull); } if (dierr != kDIErrNone) { errMsg.Format(L"Warning: error during disk scan: %hs.", DiskImgLib::DIStrError(dierr)); MessageBox(errMsg, failed, MB_OK | MB_ICONEXCLAMATION); /* keep going */ } pEditDialog->Setup(pDiskFS, loadName); (void) pEditDialog->DoModal(); delete pDiskFS; /* * FUTURE: if we edited the file we have open in the contentlist, * we need to post a warning and/or close it in the contentlist. * Or maybe just re-open it? Allow that as an option. */ bail: #if 0 delete phatbuf; #endif return; } /* * ========================================================================== * Disk Converter * ========================================================================== */ void MainWindow::OnToolsDiskConv(void) { DIError dierr; CString openFilters, errMsg; CString loadName, saveName, saveFolder; DiskImg srcImg, dstImg; DiskConvertDialog convDlg(this); CString storageName; CStringA saveNameA, storageNameA, loadNameA; /* flush current archive in case that's what we're planning to convert */ OnFileSave(); dstImg.SetNuFXCompressionType(fPreferences.GetPrefLong(kPrCompressionType)); /* * Select the image to convert. */ openFilters = kOpenDiskImage; openFilters += kOpenAll; openFilters += kOpenEnd; CFileDialog dlg(TRUE, L"dsk", NULL, OFN_FILEMUSTEXIST, openFilters, this); /* for now, everything is read-only */ dlg.m_ofn.Flags |= OFN_HIDEREADONLY; dlg.m_ofn.lpstrTitle = L"Select image to convert"; dlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder); if (dlg.DoModal() != IDOK) goto bail; loadName = dlg.GetPathName(); saveFolder = dlg.m_ofn.lpstrFile; saveFolder = saveFolder.Left(dlg.m_ofn.nFileOffset); fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder); /* open the image file and analyze it */ loadNameA = loadName; dierr = srcImg.OpenImage(loadNameA, PathProposal::kLocalFssep, true); if (dierr != kDIErrNone) { errMsg.Format(L"Unable to open disk image: %hs.", DiskImgLib::DIStrError(dierr)); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } if (srcImg.AnalyzeImage() != kDIErrNone) { errMsg.Format(L"The file '%ls' doesn't seem to hold a valid disk image.", (LPCWSTR) loadName); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } /* * If confirm image format is set, or we can't figure out the sector * ordering, prompt the user. */ if (srcImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown || fPreferences.GetPrefBool(kPrQueryImageFormat)) { if (TryDiskImgOverride(&srcImg, loadName, DiskImg::kFormatGenericProDOSOrd, NULL, false, &errMsg) != IDOK) { goto bail; } if (!errMsg.IsEmpty()) { ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } } /* * If this is a ProDOS volume, use the disk volume name as the default * value for "storageName" (which is used for NuFX archives and DC42). */ if (srcImg.GetFSFormat() == DiskImg::kFormatProDOS) { CWaitCursor waitc; DiskFS* pDiskFS = srcImg.OpenAppropriateDiskFS(); // use "headerOnly", which gets the volume name dierr = pDiskFS->Initialize(&srcImg, DiskFS::kInitHeaderOnly); if (dierr == kDIErrNone) { storageName = pDiskFS->GetVolumeName(); // note: ASCII only } delete pDiskFS; } else { /* use filename as storageName (exception for DiskCopy42 later) */ storageName = PathName::FilenameOnly(loadName, '\\'); } LOGI(" Using '%ls' as storageName", (LPCWSTR) storageName); /* transfer the DOS volume num, if one was set */ dstImg.SetDOSVolumeNum(srcImg.GetDOSVolumeNum()); LOGI("DOS volume number set to %d", dstImg.GetDOSVolumeNum()); DiskImg::FSFormat origFSFormat; origFSFormat = srcImg.GetFSFormat(); /* * The converter always tries to read and write images as if they were * ProDOS blocks. This way the only sector ordering changes are caused by * differences in the sector ordering, rather than differences in the * assumed filesystem types (which may not be knowable). */ dierr = srcImg.OverrideFormat(srcImg.GetPhysicalFormat(), DiskImg::kFormatGenericProDOSOrd, srcImg.GetSectorOrder()); if (dierr != kDIErrNone) { errMsg.Format(L"Internal error: couldn't switch to generic ProDOS: %hs.", DiskImgLib::DIStrError(dierr)); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } /* * Put up a dialog to figure out what we want to do with this image. * * We have a fair amount of faith that this will not pick impossible * combinations. If we do, we will fail later on in CreateImage. */ convDlg.Init(&srcImg); if (convDlg.DoModal() != IDOK) { LOGI(" User bailed out of convert dialog"); goto bail; } /* * Examine their choices. */ DiskImg::OuterFormat outerFormat; DiskImg::FileFormat fileFormat; DiskImg::PhysicalFormat physicalFormat; DiskImg::SectorOrder sectorOrder; if (DetermineImageSettings(convDlg.fConvertIdx, (convDlg.fAddGzip != 0), &outerFormat, &fileFormat, &physicalFormat, §orOrder) != 0) { goto bail; } const DiskImg::NibbleDescr* pNibbleDescr; pNibbleDescr = srcImg.GetNibbleDescr(); if (pNibbleDescr == NULL && DiskImg::IsNibbleFormat(physicalFormat)) { /* * We're writing to a nibble format, so we have to decide how the * disk should be formatted. The source doesn't specify it, so we * use generic 13- or 16-sector, defaulting to the latter when in * doubt. */ if (srcImg.GetHasSectors() && srcImg.GetNumSectPerTrack() == 13) { pNibbleDescr = DiskImg::GetStdNibbleDescr( DiskImg::kNibbleDescrDOS32Std); } else { pNibbleDescr = DiskImg::GetStdNibbleDescr( DiskImg::kNibbleDescrDOS33Std); } } LOGI(" NibbleDescr is 0x%08lx (%hs)", (long) pNibbleDescr, pNibbleDescr != NULL ? pNibbleDescr->description : "---"); if (srcImg.GetFileFormat() == DiskImg::kFileFormatTrackStar && fileFormat != DiskImg::kFileFormatTrackStar) { /* converting from TrackStar to anything else */ CString msg, appName; CheckedLoadString(&msg, IDS_TRACKSTAR_TO_OTHER_WARNING); CheckedLoadString(&appName, IDS_MB_APP_NAME); if (MessageBox(msg, appName, MB_OKCANCEL | MB_ICONWARNING) != IDOK) { LOGI(" User bailed after trackstar-to-other warning"); goto bail; } } else if (srcImg.GetFileFormat() == DiskImg::kFileFormatFDI && fileFormat != DiskImg::kFileFormatTrackStar && srcImg.GetNumBlocks() != 1600) { /* converting from 5.25" FDI to anything but TrackStar */ CString msg, appName; CheckedLoadString(&msg, IDS_FDI_TO_OTHER_WARNING); CheckedLoadString(&appName, IDS_MB_APP_NAME); if (MessageBox(msg, appName, MB_OKCANCEL | MB_ICONWARNING) != IDOK) { LOGI(" User bailed after fdi-to-other warning"); goto bail; } } else if (srcImg.GetHasNibbles() && DiskImg::IsSectorFormat(physicalFormat)) { /* converting from nibble to non-nibble format */ CString msg, appName; CheckedLoadString(&msg, IDS_NIBBLE_TO_SECTOR_WARNING); CheckedLoadString(&appName, IDS_MB_APP_NAME); if (MessageBox(msg, appName, MB_OKCANCEL | MB_ICONWARNING) != IDOK) { LOGI(" User bailed after nibble-to-sector warning"); goto bail; } } else if (srcImg.GetHasNibbles() && DiskImg::IsNibbleFormat(physicalFormat) && srcImg.GetPhysicalFormat() != physicalFormat) { /* converting between differing nibble formats */ CString msg, appName; CheckedLoadString(&msg, IDS_DIFFERENT_NIBBLE_WARNING); CheckedLoadString(&appName, IDS_MB_APP_NAME); if (MessageBox(msg, appName, MB_OKCANCEL | MB_ICONWARNING) != IDOK) { LOGI(" User bailed after differing-nibbles warning"); goto bail; } } /* * If the source is a UNIDOS volume and the target format is DiskCopy 4.2, * use DOS sector ordering instead of ProDOS block ordering. For some * reason the disks come out that way. */ if (origFSFormat == DiskImg::kFormatUNIDOS && fileFormat == DiskImg::kFileFormatDiskCopy42) { LOGI(" Switching to DOS sector ordering for UNIDOS/DiskCopy42"); sectorOrder = DiskImg::kSectorOrderDOS; } if (origFSFormat != DiskImg::kFormatProDOS && fileFormat == DiskImg::kFileFormatDiskCopy42) { LOGI(" Nuking storage name for non-ProDOS DiskCopy42 image"); storageName = L""; // want to use "-not a mac disk" for non-ProDOS } /* * Pick file to save into. */ { CFileDialog saveDlg(FALSE, convDlg.fExtension, NULL, OFN_OVERWRITEPROMPT|OFN_NOREADONLYRETURN|OFN_HIDEREADONLY, L"All Files (*.*)|*.*||", this); CString saveFolder; CString title = L"New disk image (."; title += convDlg.fExtension; title += L")"; saveDlg.m_ofn.lpstrTitle = title; saveDlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrConvertArchiveFolder); if (saveDlg.DoModal() != IDOK) { LOGI(" User bailed out of image save dialog"); goto bail; } saveFolder = saveDlg.m_ofn.lpstrFile; saveFolder = saveFolder.Left(saveDlg.m_ofn.nFileOffset); fPreferences.SetPrefString(kPrConvertArchiveFolder, saveFolder); saveName = saveDlg.GetPathName(); } LOGI("File will be saved to '%ls'", (LPCWSTR) saveName); /* DiskImgLib does not like it if file already exists */ errMsg = RemoveFile(saveName); if (!errMsg.IsEmpty()) { ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } /* * Create the image file. Adjust the number of tracks if we're * copying to or from TrackStar or FDI images. */ int dstNumTracks; int dstNumBlocks; bool isPartial; dstNumTracks = srcImg.GetNumTracks(); dstNumBlocks = srcImg.GetNumBlocks(); isPartial = false; if (srcImg.GetFileFormat() == DiskImg::kFileFormatTrackStar && fileFormat != DiskImg::kFileFormatTrackStar && srcImg.GetNumTracks() == 40) { /* from TrackStar to other */ dstNumTracks = 35; dstNumBlocks = 280; isPartial = true; } if (srcImg.GetFileFormat() == DiskImg::kFileFormatFDI && fileFormat != DiskImg::kFileFormatFDI && srcImg.GetNumTracks() != 35 && srcImg.GetNumBlocks() != 1600) { /* from 5.25" FDI to other */ dstNumTracks = 35; dstNumBlocks = 280; isPartial = true; } if (srcImg.GetFileFormat() != DiskImg::kFileFormatTrackStar && fileFormat == DiskImg::kFileFormatTrackStar && dstNumTracks == 35) { /* from other to TrackStar */ isPartial = true; } saveNameA = saveName; storageNameA = storageName; if (srcImg.GetHasNibbles() && DiskImg::IsNibbleFormat(physicalFormat) && physicalFormat == srcImg.GetPhysicalFormat()) { /* * For nibble-to-nibble with the same track format, copy it as * a collection of tracks. */ dierr = dstImg.CreateImage((LPCSTR) saveNameA, (LPCSTR) storageNameA, outerFormat, fileFormat, physicalFormat, pNibbleDescr, sectorOrder, DiskImg::kFormatGenericProDOSOrd, dstNumTracks, srcImg.GetNumSectPerTrack(), false /* must format */); } else if (srcImg.GetHasBlocks()) { /* * For general case, copy as a block image, converting in and out of * nibbles as needed. */ dierr = dstImg.CreateImage((LPCSTR) saveNameA, (LPCSTR) storageNameA, outerFormat, fileFormat, physicalFormat, pNibbleDescr, sectorOrder, DiskImg::kFormatGenericProDOSOrd, dstNumBlocks, false /* only needed for nibble?? */); } else if (srcImg.GetHasSectors()) { /* * We should only get here when converting to/from D13. We have to * special-case this because this was originally written to support * block copying as the lowest common denominator. D13 screwed * everything up. :-) */ dierr = dstImg.CreateImage((LPCSTR) saveNameA, (LPCSTR) storageNameA, outerFormat, fileFormat, physicalFormat, pNibbleDescr, sectorOrder, DiskImg::kFormatGenericProDOSOrd, // needs to match above dstNumTracks, srcImg.GetNumSectPerTrack(), false /* only need for dest=nibble? */); } else { /* * Generally speaking, we don't allow the user to make choices that * would get us here. In particular, the UI should not allow the * user to convert directly between nibble formats when the source * image doesn't have a recognizeable block format. */ ASSERT(false); dierr = kDIErrInternal; } if (dierr != kDIErrNone) { errMsg.Format(L"Couldn't create disk image: %hs.", DiskImgLib::DIStrError(dierr)); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } /* * Do the actual copy, either as blocks or tracks. */ dierr = CopyDiskImage(&dstImg, &srcImg, false, isPartial, NULL); if (dierr != kDIErrNone) { errMsg.Format(L"Copy failed: %hs.", DiskImgLib::DIStrError(dierr)); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } dierr = srcImg.CloseImage(); if (dierr != kDIErrNone) { errMsg.Format(L"ERROR: srcImg close failed (err=%d)\n", dierr); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } dierr = dstImg.CloseImage(); if (dierr != kDIErrNone) { errMsg.Format(L"ERROR: dstImg close failed (err=%d)\n", dierr); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } SuccessBeep(); /* * We're done. Give them the opportunity to open the disk image they * just created. */ { DoneOpenDialog doneOpen(this); if (doneOpen.DoModal() == IDOK) { LOGI(" At user request, opening '%ls'", (LPCWSTR) saveName); DoOpenArchive(saveName, convDlg.fExtension, kFilterIndexDiskImage, false); } } bail: return; } int MainWindow::DetermineImageSettings(int convertIdx, bool addGzip, DiskImg::OuterFormat* pOuterFormat, DiskImg::FileFormat* pFileFormat, DiskImg::PhysicalFormat* pPhysicalFormat, DiskImg::SectorOrder* pSectorOrder) { if (addGzip) *pOuterFormat = DiskImg::kOuterFormatGzip; else *pOuterFormat = DiskImg::kOuterFormatNone; switch (convertIdx) { case DiskConvertDialog::kConvDOSRaw: *pFileFormat = DiskImg::kFileFormatUnadorned; *pPhysicalFormat = DiskImg::kPhysicalFormatSectors; *pSectorOrder = DiskImg::kSectorOrderDOS; break; case DiskConvertDialog::kConvDOS2MG: *pFileFormat = DiskImg::kFileFormat2MG; *pPhysicalFormat = DiskImg::kPhysicalFormatSectors; *pSectorOrder = DiskImg::kSectorOrderDOS; break; case DiskConvertDialog::kConvProDOSRaw: *pFileFormat = DiskImg::kFileFormatUnadorned; *pPhysicalFormat = DiskImg::kPhysicalFormatSectors; *pSectorOrder = DiskImg::kSectorOrderProDOS; break; case DiskConvertDialog::kConvProDOS2MG: *pFileFormat = DiskImg::kFileFormat2MG; *pPhysicalFormat = DiskImg::kPhysicalFormatSectors; *pSectorOrder = DiskImg::kSectorOrderProDOS; break; case DiskConvertDialog::kConvNibbleRaw: *pFileFormat = DiskImg::kFileFormatUnadorned; *pPhysicalFormat = DiskImg::kPhysicalFormatNib525_6656; *pSectorOrder = DiskImg::kSectorOrderPhysical; break; case DiskConvertDialog::kConvNibble2MG: *pFileFormat = DiskImg::kFileFormat2MG; *pPhysicalFormat = DiskImg::kPhysicalFormatNib525_6656; *pSectorOrder = DiskImg::kSectorOrderPhysical; break; case DiskConvertDialog::kConvD13: *pFileFormat = DiskImg::kFileFormatUnadorned; *pPhysicalFormat = DiskImg::kPhysicalFormatSectors; *pSectorOrder = DiskImg::kSectorOrderDOS; break; case DiskConvertDialog::kConvDiskCopy42: *pFileFormat = DiskImg::kFileFormatDiskCopy42; *pPhysicalFormat = DiskImg::kPhysicalFormatSectors; *pSectorOrder = DiskImg::kSectorOrderProDOS; break; case DiskConvertDialog::kConvTrackStar: *pFileFormat = DiskImg::kFileFormatTrackStar; *pPhysicalFormat = DiskImg::kPhysicalFormatNib525_Var; *pSectorOrder = DiskImg::kSectorOrderPhysical; break; case DiskConvertDialog::kConvNuFX: *pFileFormat = DiskImg::kFileFormatNuFX; *pPhysicalFormat = DiskImg::kPhysicalFormatSectors; *pSectorOrder = DiskImg::kSectorOrderProDOS; break; case DiskConvertDialog::kConvSim2eHDV: *pFileFormat = DiskImg::kFileFormatSim2eHDV; *pPhysicalFormat = DiskImg::kPhysicalFormatSectors; *pSectorOrder = DiskImg::kSectorOrderProDOS; break; case DiskConvertDialog::kConvDDD: *pFileFormat = DiskImg::kFileFormatDDD; *pPhysicalFormat = DiskImg::kPhysicalFormatSectors; *pSectorOrder = DiskImg::kSectorOrderDOS; break; default: ASSERT(false); LOGI(" WHOA: invalid conv type %d", convertIdx); return -1; } return 0; } static inline int MIN(int val1, int val2) { return (val1 < val2) ? val1 : val2; } DIError MainWindow::CopyDiskImage(DiskImg* pDstImg, DiskImg* pSrcImg, bool bulk, bool partial, ProgressCancelDialog* pPCDialog) { /* * This originally just did a block copy. Nibble track copies were added * later, and sector copies were added even later. */ DIError dierr = kDIErrNone; CString errMsg; unsigned char* dataBuf = NULL; if (pSrcImg->GetHasNibbles() && pDstImg->GetHasNibbles() && pSrcImg->GetPhysicalFormat() == pDstImg->GetPhysicalFormat()) { /* * Copy as a series of nibble tracks. * * NOTE: we could do better here for 6384 to ".app", but in * practice nobody cares anyway. */ if (!partial) { ASSERT(pSrcImg->GetNumTracks() == pDstImg->GetNumTracks()); } //unsigned char trackBuf[kTrackAllocSize]; long trackLen; int numTracks; dataBuf = new unsigned char[kTrackAllocSize]; if (dataBuf == NULL) { dierr = kDIErrMalloc; goto bail; } numTracks = MIN(pSrcImg->GetNumTracks(), pDstImg->GetNumTracks()); LOGI("Nibble track copy (%d tracks)", numTracks); for (int track = 0; track < numTracks; track++) { dierr = pSrcImg->ReadNibbleTrack(track, dataBuf, &trackLen); if (dierr != kDIErrNone) { errMsg.Format(L"ERROR: read on track %d failed (err=%d)\n", track, dierr); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } dierr = pDstImg->WriteNibbleTrack(track, dataBuf, trackLen); if (dierr != kDIErrNone) { errMsg.Format(L"ERROR: write on track %d failed (err=%d)\n", track, dierr); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } /* these aren't slow enough that we need progress updating */ } } else if (!pSrcImg->GetHasBlocks() || !pDstImg->GetHasBlocks()) { /* * Do a sector copy, for D13 images (which can't be accessed as blocks). */ if (!partial) { ASSERT(pSrcImg->GetNumTracks() == pDstImg->GetNumTracks()); ASSERT(pSrcImg->GetNumSectPerTrack() == pDstImg->GetNumSectPerTrack()); } long numTracks, numSectPerTrack; int numBadSectors = 0; dataBuf = new unsigned char[256]; // one sector if (dataBuf == NULL) { dierr = kDIErrMalloc; goto bail; } numTracks = MIN(pSrcImg->GetNumTracks(), pDstImg->GetNumTracks()); numSectPerTrack = MIN(pSrcImg->GetNumSectPerTrack(), pDstImg->GetNumSectPerTrack()); LOGI("Sector copy (%d tracks / %d sectors)", numTracks, numSectPerTrack); for (int track = 0; track < numTracks; track++) { for (int sector = 0; sector < numSectPerTrack; sector++) { dierr = pSrcImg->ReadTrackSector(track, sector, dataBuf); if (dierr != kDIErrNone) { LOGI("Bad sector T=%d S=%d", track, sector); numBadSectors++; dierr = kDIErrNone; memset(dataBuf, 0, 256); } dierr = pDstImg->WriteTrackSector(track, sector, dataBuf); if (dierr != kDIErrNone) { errMsg.Format(L"ERROR: write of T=%d S=%d failed (err=%d)\n", track, sector, dierr); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } } /* these aren't slow enough that we need progress updating */ } if (!bulk && numBadSectors != 0) { CString appName; CheckedLoadString(&appName, IDS_MB_APP_NAME); errMsg.Format(L"Skipped %ld unreadable sector%ls.", numBadSectors, numBadSectors == 1 ? L"" : L"s"); MessageBox(errMsg, appName, MB_OK | MB_ICONWARNING); } } else { /* * Do a block copy, copying multiple blocks at a time for performance. */ if (!partial) { ASSERT(pSrcImg->GetNumBlocks() == pDstImg->GetNumBlocks()); } //unsigned char blkBuf[512]; long numBadBlocks = 0; long numBlocks; int blocksPerRead; numBlocks = MIN(pSrcImg->GetNumBlocks(), pDstImg->GetNumBlocks()); if (numBlocks <= 2880) blocksPerRead = 9; // better granularity (one floppy track) else blocksPerRead = 64; // 32K per read; max seems to be 64K? dataBuf = new unsigned char[blocksPerRead * 512]; if (dataBuf == NULL) { dierr = kDIErrMalloc; goto bail; } LOGD("--- BLOCK COPY (%ld blocks, %d per)", numBlocks, blocksPerRead); for (long block = 0; block < numBlocks; ) { long blocksThisTime = blocksPerRead; if (block + blocksThisTime > numBlocks) blocksThisTime = numBlocks - block; dierr = pSrcImg->ReadBlocks(block, blocksThisTime, dataBuf); if (dierr != kDIErrNone) { if (blocksThisTime != 1) { /* * Media with errors. Drop to one block per read. */ LOGW(" Bad sector encountered at %ld(%ld), slowing", block, blocksThisTime); blocksThisTime = blocksPerRead = 1; continue; // retry this block } numBadBlocks++; dierr = kDIErrNone; memset(dataBuf, 0, 512); } dierr = pDstImg->WriteBlocks(block, blocksThisTime, dataBuf); if (dierr != kDIErrNone) { if (dierr != kDIErrWriteProtected) { errMsg.Format(L"ERROR: write of block %ld failed: %hs\n", block, DiskImgLib::DIStrError(dierr)); ShowFailureMsg(this, errMsg, IDS_FAILED); } goto bail; } /* if we have a cancel dialog, keep it lively */ if (pPCDialog != NULL && (block % 18) == 0) { int status; PeekAndPump(); LONGLONG bigBlock = block; bigBlock = bigBlock * ProgressCancelDialog::kProgressResolution; status = pPCDialog->SetProgress((int)(bigBlock / numBlocks)); if (status == IDCANCEL) { dierr = kDIErrCancelled; // pretend it came from DiskImg goto bail; } } else if (bulk && (block % 512) == 0) { PeekAndPump(); } block += blocksThisTime; } if (!bulk && numBadBlocks != 0) { CString appName; CheckedLoadString(&appName, IDS_MB_APP_NAME); errMsg.Format(L"Skipped %ld unreadable block%ls.", numBadBlocks, numBadBlocks == 1 ? L"" : L"s"); MessageBox(errMsg, appName, MB_OK | MB_ICONWARNING); } } bail: delete[] dataBuf; return dierr; } /* * ========================================================================== * Bulk disk convert * ========================================================================== */ /* * Sub-class the generic libutil CancelDialog class. */ class BulkConvCancelDialog : public CancelDialog { public: BOOL Create(CWnd* pParentWnd = NULL) { fAbortOperation = false; return CancelDialog::Create(&fAbortOperation, IDD_BULKCONV, pParentWnd); } void SetCurrentFile(const WCHAR* fileName) { CWnd* pWnd = GetDlgItem(IDC_BULKCONV_PATHNAME); ASSERT(pWnd != NULL); pWnd->SetWindowText(fileName); } bool fAbortOperation; private: void OnOK(void) { LOGD("Ignoring BulkConvCancelDialog OnOK"); } MainWindow* GetMainWindow(void) const { return (MainWindow*)::AfxGetMainWnd(); } }; void MainWindow::OnToolsBulkDiskConv(void) { const int kFileNameBufSize = 32768; DiskConvertDialog convDlg(this); ChooseDirDialog chooseDirDlg(this); BulkConvCancelDialog* pCancelDialog = new BulkConvCancelDialog; // on heap CString openFilters, errMsg; CString saveFolder, targetDir; int nameCount; /* flush current archive in case that's what we're planning to convert */ OnFileSave(); /* * Select the set of images to convert. */ openFilters = kOpenDiskImage; openFilters += kOpenAll; openFilters += kOpenEnd; CFileDialog dlg(TRUE, L"dsk", NULL, OFN_FILEMUSTEXIST, openFilters, this); dlg.m_ofn.lpstrFile = new WCHAR[kFileNameBufSize]; dlg.m_ofn.lpstrFile[0] = dlg.m_ofn.lpstrFile[1] = '\0'; dlg.m_ofn.nMaxFile = kFileNameBufSize; dlg.m_ofn.Flags |= OFN_HIDEREADONLY; // open all images as read-only dlg.m_ofn.Flags |= OFN_ALLOWMULTISELECT; dlg.m_ofn.lpstrTitle = L"Select images to convert"; dlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder); if (dlg.DoModal() != IDOK) goto bail; saveFolder = dlg.m_ofn.lpstrFile; saveFolder = saveFolder.Left(dlg.m_ofn.nFileOffset); fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder); /* count up the number of entries */ POSITION posn; posn = dlg.GetStartPosition(); nameCount = 0; while (posn != NULL) { CString pathName; pathName = dlg.GetNextPathName(posn); nameCount++; } LOGD("BulkConv got nameCount=%d", nameCount); /* * Choose the target directory. * * We use the "convert archive" folder by default. */ chooseDirDlg.SetPathName(fPreferences.GetPrefString(kPrConvertArchiveFolder)); if (chooseDirDlg.DoModal() != IDOK) goto bail; targetDir = chooseDirDlg.GetPathName(); fPreferences.SetPrefString(kPrConvertArchiveFolder, targetDir); /* * Put up a dialog to select the target conversion format. * * It is up to the user to select a format that matches the selected * files. If it doesn't (e.g. converting an 800K floppy to DDD format), * the process will fail later on. */ convDlg.Init(nameCount); if (convDlg.DoModal() != IDOK) { LOGI(" User bailed out of convert dialog"); goto bail; } /* initialize cancel dialog, and disable main window */ EnableWindow(FALSE); if (pCancelDialog->Create(this) == FALSE) { LOGE("Cancel dialog init failed?!"); ASSERT(false); goto bail; } /* * Loop through all selected files and convert them one at a time. */ posn = dlg.GetStartPosition(); while (posn != NULL) { CString pathName; pathName = dlg.GetNextPathName(posn); LOGI(" BulkConv: source path='%ls'", (LPCWSTR) pathName); pCancelDialog->SetCurrentFile(PathName::FilenameOnly(pathName, '\\')); PeekAndPump(); if (pCancelDialog->fAbortOperation) break; BulkConvertImage(pathName, targetDir, convDlg, &errMsg); if (!errMsg.IsEmpty()) { /* show error message, do OK/Cancel */ /* do we need to delete the output file on failure? In general we can't, because we could have failed because the file already existed. */ CString failed; int res; CheckedLoadString(&failed, IDS_FAILED); errMsg += "\n\nSource file: "; errMsg += pathName; errMsg += "\n\nClick OK to skip this and continue, or Cancel to " "stop now."; res = pCancelDialog->MessageBox(errMsg, failed, MB_OKCANCEL | MB_ICONERROR); if (res != IDOK) goto bail; } } if (!pCancelDialog->fAbortOperation) SuccessBeep(); bail: // restore the main window to prominence EnableWindow(TRUE); //SetActiveWindow(); if (pCancelDialog != NULL) pCancelDialog->DestroyWindow(); delete[] dlg.m_ofn.lpstrFile; return; } void MainWindow::BulkConvertImage(const WCHAR* pathName, const WCHAR* targetDir, const DiskConvertDialog& convDlg, CString* pErrMsg) { /* * [Much of this is copy & pasted from OnToolsDiskConv(). This needs to get * refactored.] */ DIError dierr; CString saveName; DiskImg srcImg, dstImg; CString storageName; PathName srcPath(pathName); CString fileName, ext; CStringA saveNameA, storageNameA; *pErrMsg = L""; dstImg.SetNuFXCompressionType( fPreferences.GetPrefLong(kPrCompressionType)); /* open the image file and analyze it */ CStringA pathNameA(pathName); dierr = srcImg.OpenImage(pathNameA, PathProposal::kLocalFssep, true); if (dierr != kDIErrNone) { pErrMsg->Format(L"Unable to open disk image: %hs.", DiskImgLib::DIStrError(dierr)); goto bail; } if (srcImg.AnalyzeImage() != kDIErrNone) { pErrMsg->Format(L"The file doesn't seem to hold a valid disk image."); goto bail; } #if 0 // don't feel like posting this UI /* * If we can't figure out the sector ordering, prompt the user. Don't * go into it if they have "confirm format" selected, since that would be * annoying. If they need to confirm it, they can use the one-at-a-time * interface. */ if (srcImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) { if (TryDiskImgOverride(&srcImg, pathName, DiskImg::kFormatGenericProDOSOrd, NULL, pErrMsg) != IDOK) { *pErrMsg = "Image conversion cancelled."; } if (!pErrMsg->IsEmpty()) goto bail; } #else if (srcImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) { *pErrMsg = L"Could not determine the disk image sector ordering. You " L"may need to change the file extension."; goto bail; } #endif /* transfer the DOS volume num, if one was set */ dstImg.SetDOSVolumeNum(srcImg.GetDOSVolumeNum()); LOGI("DOS volume number set to %d", dstImg.GetDOSVolumeNum()); DiskImg::FSFormat origFSFormat; origFSFormat = srcImg.GetFSFormat(); /* * The converter always tries to read and write images as if they were * ProDOS blocks. This way the only sector ordering changes are caused by * differences in the sector ordering, rather than differences in the * assumed filesystem types (which may not be knowable). */ dierr = srcImg.OverrideFormat(srcImg.GetPhysicalFormat(), DiskImg::kFormatGenericProDOSOrd, srcImg.GetSectorOrder()); if (dierr != kDIErrNone) { pErrMsg->Format(L"Internal error: couldn't switch to generic ProDOS: %hs.", DiskImgLib::DIStrError(dierr)); goto bail; } /* * Examine their choices. */ DiskImg::OuterFormat outerFormat; DiskImg::FileFormat fileFormat; DiskImg::PhysicalFormat physicalFormat; DiskImg::SectorOrder sectorOrder; if (DetermineImageSettings(convDlg.fConvertIdx, (convDlg.fAddGzip != 0), &outerFormat, &fileFormat, &physicalFormat, §orOrder) != 0) { *pErrMsg = L"Odd: couldn't configure image settings"; goto bail; } const DiskImg::NibbleDescr* pNibbleDescr; pNibbleDescr = srcImg.GetNibbleDescr(); if (pNibbleDescr == NULL && DiskImg::IsNibbleFormat(physicalFormat)) { /* * We're writing to a nibble format, so we have to decide how the * disk should be formatted. The source doesn't specify it, so we * use generic 13- or 16-sector, defaulting to the latter when in * doubt. */ if (srcImg.GetHasSectors() && srcImg.GetNumSectPerTrack() == 13) { pNibbleDescr = DiskImg::GetStdNibbleDescr( DiskImg::kNibbleDescrDOS32Std); } else { pNibbleDescr = DiskImg::GetStdNibbleDescr( DiskImg::kNibbleDescrDOS33Std); } } LOGI(" NibbleDescr is 0x%08lx (%hs)", (long) pNibbleDescr, pNibbleDescr != NULL ? pNibbleDescr->description : "---"); /* * Create the new filename based on the old filename. */ saveName = targetDir; if (saveName.Right(1) != '\\') saveName += '\\'; fileName = srcPath.GetFileName(); ext = srcPath.GetExtension(); // extension, including '.' if (ext.CompareNoCase(L".gz") == 0) { /* got a .gz, see if there's anything else in front of it */ CString tmpName, ext2; tmpName = srcPath.GetPathName(); tmpName = tmpName.Left(tmpName.GetLength() - ext.GetLength()); PathName tmpPath(tmpName); ext2 = tmpPath.GetExtension(); if (ext2.GetLength() >= 2 && ext2.GetLength() <= 4) ext = ext2 + ext; saveName += fileName.Left(fileName.GetLength() - ext.GetLength()); } else { if (ext.GetLength() < 2 || ext.GetLength() > 4) { /* no meaningful extension */ saveName += fileName; } else { saveName += fileName.Left(fileName.GetLength() - ext.GetLength()); } } storageName = PathName::FilenameOnly(saveName, '\\'); // grab this for SHK name saveName += '.'; saveName += convDlg.fExtension; LOGI(" Bulk converting '%ls' to '%ls'", pathName, (LPCWSTR) saveName); /* * If this is a ProDOS volume, use the disk volume name as the default * value for "storageName" (which is only used for NuFX archives). */ if (srcImg.GetFSFormat() == DiskImg::kFormatProDOS) { CWaitCursor waitc; DiskFS* pDiskFS = srcImg.OpenAppropriateDiskFS(); // set "headerOnly" since we only need the volume name dierr = pDiskFS->Initialize(&srcImg, DiskFS::kInitHeaderOnly); if (dierr == kDIErrNone) { storageName = pDiskFS->GetVolumeName(); // note: ASCII only } delete pDiskFS; } else { /* just use storageName as set earlier, unless target is DiskCopy42 */ if (fileFormat == DiskImg::kFileFormatDiskCopy42) storageName = L""; // want to use "not a mac disk" for non-ProDOS } LOGI(" Using '%ls' as storageName", (LPCWSTR) storageName); /* * If the source is a UNIDOS volume and the target format is DiskCopy 4.2, * use DOS sector ordering instead of ProDOS block ordering. For some * reason the disks come out that way. */ if (origFSFormat == DiskImg::kFormatUNIDOS && fileFormat == DiskImg::kFileFormatDiskCopy42) { LOGI(" Switching to DOS sector ordering for UNIDOS/DiskCopy42"); sectorOrder = DiskImg::kSectorOrderDOS; } /* * Create the image file. Adjust the number of tracks if we're * copying to or from a TrackStar image. */ int dstNumTracks; int dstNumBlocks; bool isPartial; dstNumTracks = srcImg.GetNumTracks(); dstNumBlocks = srcImg.GetNumBlocks(); isPartial = false; if (srcImg.GetFileFormat() == DiskImg::kFileFormatTrackStar && fileFormat != DiskImg::kFileFormatTrackStar && srcImg.GetNumTracks() == 40) { /* from TrackStar to other */ dstNumTracks = 35; dstNumBlocks = 280; isPartial = true; } if (srcImg.GetFileFormat() == DiskImg::kFileFormatFDI && fileFormat != DiskImg::kFileFormatTrackStar && srcImg.GetNumTracks() != 35 && srcImg.GetNumBlocks() != 1600) { /* from 5.25" FDI to other */ dstNumTracks = 35; dstNumBlocks = 280; isPartial = true; } if (srcImg.GetFileFormat() != DiskImg::kFileFormatTrackStar && fileFormat == DiskImg::kFileFormatTrackStar && dstNumTracks == 35) { /* other to TrackStar */ isPartial = true; } saveNameA = saveName; storageNameA = storageName; if (srcImg.GetHasNibbles() && DiskImg::IsNibbleFormat(physicalFormat) && physicalFormat == srcImg.GetPhysicalFormat()) { /* for nibble-to-nibble with the same track format, copy it as collection of tracks */ dierr = dstImg.CreateImage((LPCSTR) saveNameA, (LPCSTR) storageNameA, outerFormat, fileFormat, physicalFormat, pNibbleDescr, sectorOrder, DiskImg::kFormatGenericProDOSOrd, srcImg.GetNumTracks(), srcImg.GetNumSectPerTrack(), false /* must format */); } else if (srcImg.GetHasBlocks()) { /* for general case, create as a block image */ ASSERT(srcImg.GetHasBlocks()); dierr = dstImg.CreateImage((LPCSTR) saveNameA, (LPCSTR) storageNameA, outerFormat, fileFormat, physicalFormat, pNibbleDescr, sectorOrder, DiskImg::kFormatGenericProDOSOrd, srcImg.GetNumBlocks(), false /* only need for nibble? */); } else if (srcImg.GetHasSectors()) { /* * We should only get here when converting to/from D13. We have to * special-case this because this was originally written to support * block copying as the lowest common denominator. D13 screwed * everything up. :-) */ dierr = dstImg.CreateImage((LPCSTR) saveNameA, (LPCSTR) storageNameA, outerFormat, fileFormat, physicalFormat, pNibbleDescr, sectorOrder, DiskImg::kFormatGenericProDOSOrd, // needs to match above dstNumTracks, srcImg.GetNumSectPerTrack(), false /* only need for dest=nibble? */); } else { /* e.g. unrecognizeable nibble to blocks */ *pErrMsg = L"Could not convert to requested format."; goto bail; } if (dierr != kDIErrNone) { if (dierr == kDIErrInvalidCreateReq) *pErrMsg = L"Could not convert to requested format."; else pErrMsg->Format(L"Couldn't construct disk image: %hs.", DiskImgLib::DIStrError(dierr)); goto bail; } /* * Do the actual copy, either as blocks or tracks. */ dierr = CopyDiskImage(&dstImg, &srcImg, true, isPartial, NULL); if (dierr != kDIErrNone) goto bail; dierr = dstImg.CloseImage(); if (dierr != kDIErrNone) { pErrMsg->Format(L"ERROR: dstImg close failed (err=%d)\n", dierr); goto bail; } dierr = srcImg.CloseImage(); if (dierr != kDIErrNone) { pErrMsg->Format(L"ERROR: srcImg close failed (err=%d)\n", dierr); goto bail; } bail: return; } /* * ========================================================================== * SST Merge * ========================================================================== */ const int kSSTNumTracks = 35; const int kSSTNumSectPerTrack = 16; const int kSSTTrackLen = 6656; void MainWindow::OnToolsSSTMerge(void) { const int kBadCountThreshold = 3072; DiskImg srcImg0, srcImg1; CString appName, saveName, saveFolder, errMsg; uint8_t* trackBuf = NULL; long badCount; // no need to flush -- can't really open raw SST images CFileDialog saveDlg(FALSE, L"nib", NULL, OFN_OVERWRITEPROMPT|OFN_NOREADONLYRETURN|OFN_HIDEREADONLY, L"All Files (*.*)|*.*||", this); CheckedLoadString(&appName, IDS_MB_APP_NAME); trackBuf = new uint8_t[kSSTNumTracks * kSSTTrackLen]; if (trackBuf == NULL) goto bail; /* * Open the two images and verify that they are what they seem. */ badCount = 0; if (SSTOpenImage(0, &srcImg0) != 0) goto bail; if (SSTLoadData(0, &srcImg0, trackBuf, &badCount) != 0) goto bail; LOGI("FOUND %ld bad bytes in part 0", badCount); if (badCount > kBadCountThreshold) { CheckedLoadString(&errMsg, IDS_BAD_SST_IMAGE); if (MessageBox(errMsg, appName, MB_OKCANCEL | MB_ICONWARNING) != IDOK) goto bail; } badCount = 0; if (SSTOpenImage(1, &srcImg1) != 0) goto bail; if (SSTLoadData(1, &srcImg1, trackBuf, &badCount) != 0) goto bail; LOGI("FOUND %ld bad bytes in part 1", badCount); if (badCount > kBadCountThreshold) { CheckedLoadString(&errMsg, IDS_BAD_SST_IMAGE); if (MessageBox(errMsg, appName, MB_OKCANCEL | MB_ICONWARNING) != IDOK) goto bail; } /* * Realign the tracks and OR 0x80 to everything. */ SSTProcessTrackData(trackBuf); /* * Pick the output file and write the buffer to it. */ saveDlg.m_ofn.lpstrTitle = L"Save .NIB disk image as..."; saveDlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder); if (saveDlg.DoModal() != IDOK) { LOGI(" User bailed out of image save dialog"); goto bail; } saveFolder = saveDlg.m_ofn.lpstrFile; saveFolder = saveFolder.Left(saveDlg.m_ofn.nFileOffset); fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder); saveName = saveDlg.GetPathName(); LOGI("File will be saved to '%ls'", (LPCWSTR) saveName); /* remove the file if it exists */ errMsg = RemoveFile(saveName); if (!errMsg.IsEmpty()) { ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } FILE* fp; fp = _wfopen(saveName, L"wb"); if (fp == NULL) { errMsg.Format(L"Unable to create '%ls': %hs.", (LPCWSTR) saveName, strerror(errno)); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } if (fwrite(trackBuf, kSSTNumTracks * kSSTTrackLen, 1, fp) != 1) { errMsg.Format(L"Failed while writing to new image file: %hs.", strerror(errno)); ShowFailureMsg(this, errMsg, IDS_FAILED); fclose(fp); goto bail; } fclose(fp); SuccessBeep(); /* * We're done. Give them the opportunity to open the disk image they * just created. */ { DoneOpenDialog doneOpen(this); if (doneOpen.DoModal() == IDOK) { LOGI(" At user request, opening '%ls'", (LPCWSTR) saveName); DoOpenArchive(saveName, L"nib", kFilterIndexDiskImage, false); } } bail: delete[] trackBuf; return; } int MainWindow::SSTOpenImage(int seqNum, DiskImg* pDiskImg) { DIError dierr; int result = -1; CString openFilters, errMsg; CString loadName, saveFolder; CStringA loadNameA; /* * Select the image to convert. */ openFilters = kOpenDiskImage; openFilters += kOpenAll; openFilters += kOpenEnd; CFileDialog dlg(TRUE, L"dsk", NULL, OFN_FILEMUSTEXIST, openFilters, this); dlg.m_ofn.Flags |= OFN_HIDEREADONLY; if (seqNum == 0) dlg.m_ofn.lpstrTitle = L"Select first SST image"; else dlg.m_ofn.lpstrTitle = L"Select second SST image"; dlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder); if (dlg.DoModal() != IDOK) goto bail; loadName = dlg.GetPathName(); saveFolder = dlg.m_ofn.lpstrFile; saveFolder = saveFolder.Left(dlg.m_ofn.nFileOffset); fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder); /* open the image file and analyze it */ loadNameA = loadName; dierr = pDiskImg->OpenImage(loadNameA, PathProposal::kLocalFssep, true); if (dierr != kDIErrNone) { errMsg.Format(L"Unable to open disk image: %hs.", DiskImgLib::DIStrError(dierr)); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } if (pDiskImg->AnalyzeImage() != kDIErrNone) { errMsg.Format(L"The file '%ls' doesn't seem to hold a valid disk image.", (LPCWSTR) loadName); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } /* * If confirm image format is set, or we can't figure out the sector * ordering, prompt the user. */ if (pDiskImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown || fPreferences.GetPrefBool(kPrQueryImageFormat)) { if (TryDiskImgOverride(pDiskImg, loadName, DiskImg::kFormatGenericDOSOrd, NULL, false, &errMsg) != IDOK) { goto bail; } if (!errMsg.IsEmpty()) { ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } } if (pDiskImg->GetFSFormat() != DiskImg::kFormatUnknown && !DiskImg::IsGenericFormat(pDiskImg->GetFSFormat())) { errMsg = L"This disk image appears to have a valid filesystem. SST" L" images are just raw track dumps."; ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } if (pDiskImg->GetNumTracks() != kSSTNumTracks || pDiskImg->GetNumSectPerTrack() != kSSTNumSectPerTrack) { errMsg = L"ERROR: only 5.25\" floppy disk images can be SST inputs."; ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } /* use DOS filesystem sector ordering */ dierr = pDiskImg->OverrideFormat(pDiskImg->GetPhysicalFormat(), DiskImg::kFormatGenericDOSOrd, pDiskImg->GetSectorOrder()); if (dierr != kDIErrNone) { errMsg = L"ERROR: internal failure: format override failed."; ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } result = 0; bail: return result; } int MainWindow::SSTLoadData(int seqNum, DiskImg* pDiskImg, uint8_t* trackBuf, long* pBadCount) { DIError dierr; uint8_t sctBuf[256]; int track, sector; long bufOffset; for (track = 0; track < kSSTNumTracks; track++) { int virtualTrack = track + (seqNum * kSSTNumTracks); bufOffset = SSTGetBufOffset(virtualTrack); //LOGI("USING offset=%ld (track=%d / %d)", // bufOffset, track, virtualTrack); if (virtualTrack & 0x01) { /* odd-numbered track, sectors 15-4 */ for (sector = 15; sector >= 4; sector--) { dierr = pDiskImg->ReadTrackSector(track, sector, sctBuf); if (dierr != kDIErrNone) { LOGI("ERROR: on track=%d sector=%d", track, sector); return -1; } *pBadCount += SSTCountBadBytes(sctBuf, 256); memcpy(trackBuf + bufOffset, sctBuf, 256); bufOffset += 256; } } else { for (sector = 13; sector >= 0; sector--) { dierr = pDiskImg->ReadTrackSector(track, sector, sctBuf); if (dierr != kDIErrNone) { LOGI("ERROR: on track=%d sector=%d", track, sector); return -1; } *pBadCount += SSTCountBadBytes(sctBuf, 256); memcpy(trackBuf + bufOffset, sctBuf, 256); bufOffset += 256; } } } return 0; } long MainWindow::SSTGetBufOffset(int track) { assert(track >= 0 && track < kSSTNumTracks*2); long offset; if (track & 0x01) { /* odd, use start of data */ offset = (track / 2) * kSSTTrackLen; } else { /* even, start of data plus 12 sectors */ offset = (track / 2) * kSSTTrackLen + 12 * 256; } assert(offset >= 0 && offset < kSSTTrackLen * kSSTNumTracks); return offset; } long MainWindow::SSTCountBadBytes(const uint8_t* sctBuf, int count) { long badCount = 0; unsigned char uch; while (count--) { uch = (*sctBuf) | 0x80; if (uch >= 0x80 && uch <= 0x92) badCount++; sctBuf++; } return badCount; } void MainWindow::SSTProcessTrackData(uint8_t* trackBuf) { unsigned char* trackPtr; int track; for (track = 0, trackPtr = trackBuf; track < kSSTNumTracks; track++, trackPtr += kSSTTrackLen) { bool inRun; int start, longestStart; int count7f = 0, longest = -1; int i; inRun = false; for (i = 0; i < kSSTTrackLen; i++) { if (trackPtr[i] == 0x7f) { if (inRun) { count7f++; } else { count7f = 1; start = i; inRun = true; } } else { if (inRun) { if (count7f > longest) { longest = count7f; longestStart = start; } inRun = false; } else { /* do nothing */ } } trackPtr[i] |= 0x80; } if (longest == -1) { LOGI("HEY: couldn't find any 0x7f in track %d", track); } else { LOGI("Found run of %d at %d in track %d", longest, longestStart, track); int bkpt = longestStart + longest; assert(bkpt < kSSTTrackLen); char oneTrack[kSSTTrackLen]; memcpy(oneTrack, trackPtr, kSSTTrackLen); /* copy it back so sync bytes are at end of track */ memcpy(trackPtr, oneTrack + bkpt, kSSTTrackLen - bkpt); memcpy(trackPtr + (kSSTTrackLen - bkpt), oneTrack, bkpt); } } } /* * ========================================================================== * Volume Copier * ========================================================================== */ void MainWindow::OnToolsVolumeCopierVolume(void) { VolumeCopier(false); } void MainWindow::OnToolsVolumeCopierFile(void) { VolumeCopier(true); } void MainWindow::VolumeCopier(bool openFile) { VolumeCopyDialog copyDlg(this); DiskImg srcImg; //DiskFS* pDiskFS = NULL; DIError dierr; CString failed, errMsg, msg; CString deviceName; bool readOnly = false; int result; /* flush current archive in case that's what we're planning to edit */ OnFileSave(); CheckedLoadString(&failed, IDS_FAILED); if (!openFile) { /* * Select the volume to manipulate. */ OpenVolumeDialog openVolDlg(this); //openVolDlg.fReadOnly = false; //openVolDlg.fAllowROChange = true; result = openVolDlg.DoModal(); if (result != IDOK) goto bail; deviceName = openVolDlg.fChosenDrive; readOnly = (openVolDlg.fReadOnly != 0); } else { /* * Open a disk image file instead. */ CString openFilters; openFilters = kOpenDiskImage; openFilters += kOpenAll; openFilters += kOpenEnd; CFileDialog fileDlg(TRUE, L"dsk", NULL, OFN_FILEMUSTEXIST, openFilters, this); //dlg.m_ofn.Flags |= OFN_HIDEREADONLY; fileDlg.m_ofn.Flags &= ~(OFN_READONLY); fileDlg.m_ofn.lpstrTitle = L"Select disk image file"; fileDlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder); if (fileDlg.DoModal() != IDOK) goto bail; deviceName = fileDlg.GetPathName(); readOnly = (fileDlg.GetReadOnlyPref() != 0); } /* * Open the disk image and figure out what it is. */ { CWaitCursor waitc; DiskImg::SetAllowWritePhys0(false); CStringA deviceNameA(deviceName); dierr = srcImg.OpenImage(deviceNameA, '\0', readOnly); if (dierr == kDIErrAccessDenied) { if (openFile) { errMsg.Format(L"Unable to open '%ls': %hs (try opening the file" L" with 'Read Only' checked).", (LPCWSTR) deviceName, DiskImgLib::DIStrError(dierr)); } else if (!IsWin9x() && !openFile) { errMsg.Format(L"Unable to open '%ls': %hs (make sure you have" L" administrator privileges).", (LPCWSTR) deviceName, DiskImgLib::DIStrError(dierr)); } else { errMsg.Format(L"Unable to open '%ls': %hs.", (LPCWSTR) deviceName, DiskImgLib::DIStrError(dierr)); } ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } else if (dierr != kDIErrNone) { errMsg.Format(L"Unable to open '%ls': %hs.", (LPCWSTR) deviceName, DiskImgLib::DIStrError(dierr)); ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } /* analyze it to get #of blocks and determine the FS */ if (srcImg.AnalyzeImage() != kDIErrNone) { errMsg.Format(L"There isn't a valid disk image here?!?"); MessageBox(errMsg, failed, MB_OK|MB_ICONSTOP); goto bail; } } /* * If requested (or necessary), verify the format. */ if (srcImg.GetFSFormat() == DiskImg::kFormatUnknown || srcImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown || fPreferences.GetPrefBool(kPrQueryImageFormat)) { if (TryDiskImgOverride(&srcImg, deviceName, DiskImg::kFormatUnknown, NULL, true, &errMsg) != IDOK) { goto bail; } if (!errMsg.IsEmpty()) { ShowFailureMsg(this, errMsg, IDS_FAILED); goto bail; } } /* * Hand the DiskImg object off to the volume copier dialog. */ copyDlg.fpDiskImg = &srcImg; copyDlg.fPathName = deviceName; (void) copyDlg.DoModal(); /* * The volume copier could have modified our open file. If it has, * we need to close and reopen the archive. */ srcImg.CloseImage(); // could interfere with volume reopen if (fNeedReopen) { PeekAndPump(); // clear out dialog ReopenArchive(); } bail: return; } /* * ========================================================================== * Disk image creator * ========================================================================== */ void MainWindow::OnToolsDiskImageCreator(void) { CreateImageDialog createDlg(this); DiskArchive* pNewArchive = NULL; createDlg.fDiskFormatIdx = fPreferences.GetPrefLong(kPrDiskImageCreateFormat); if (createDlg.fDiskFormatIdx < 0) createDlg.fDiskFormatIdx = CreateImageDialog::kFmtProDOS; /* * Ask the user what sort of disk they'd like to create. */ if (createDlg.DoModal() != IDOK) return; fPreferences.SetPrefLong(kPrDiskImageCreateFormat, createDlg.fDiskFormatIdx); /* * Set up the options struct. We set base.sectorOrder later. */ assert(createDlg.fNumBlocks > 0); DiskArchive::NewOptions options; memset(&options, 0, sizeof(options)); switch (createDlg.fDiskFormatIdx) { case CreateImageDialog::kFmtBlank: options.base.format = DiskImg::kFormatUnknown; options.blank.numBlocks = createDlg.fNumBlocks; break; case CreateImageDialog::kFmtProDOS: options.base.format = DiskImg::kFormatProDOS; options.prodos.numBlocks = createDlg.fNumBlocks; options.prodos.volName = createDlg.fVolName_ProDOS; break; case CreateImageDialog::kFmtPascal: options.base.format = DiskImg::kFormatPascal; options.pascalfs.numBlocks = createDlg.fNumBlocks; options.pascalfs.volName = createDlg.fVolName_Pascal; break; case CreateImageDialog::kFmtHFS: options.base.format = DiskImg::kFormatMacHFS; options.hfs.numBlocks = createDlg.fNumBlocks; options.hfs.volName = createDlg.fVolName_HFS; break; case CreateImageDialog::kFmtDOS32: options.base.format = DiskImg::kFormatDOS32; options.dos.volumeNum = createDlg.fDOSVolumeNum; options.dos.allocDOSTracks = (createDlg.fAllocTracks_DOS != 0); options.dos.numTracks = 35; options.dos.numSectors = 13; break; case CreateImageDialog::kFmtDOS33: options.base.format = DiskImg::kFormatDOS33; options.dos.volumeNum = createDlg.fDOSVolumeNum; options.dos.allocDOSTracks = (createDlg.fAllocTracks_DOS != 0); if (createDlg.fNumBlocks <= 400) { ASSERT(createDlg.fNumBlocks % 8 == 0); options.dos.numTracks = createDlg.fNumBlocks / 8; options.dos.numSectors = 16; } else if (createDlg.fNumBlocks <= 800) { ASSERT(createDlg.fNumBlocks % 16 == 0); options.dos.numTracks = createDlg.fNumBlocks / 16; options.dos.numSectors = 32; options.dos.allocDOSTracks = false; } else { ASSERT(false); return; } break; default: LOGI("Invalid fDiskFormatIdx %d from CreateImageDialog", createDlg.fDiskFormatIdx); ASSERT(false); return; } /* * Select the file to store it in. */ CString filename, saveFolder, errStr; int filterIndex = 1; CString formats; if (createDlg.fDiskFormatIdx == CreateImageDialog::kFmtDOS32) { formats = L"13-sector disk (*.d13)|*.d13|"; } else { formats = L"ProDOS-ordered image (*.po)|*.po|"; if (createDlg.fNumBlocks == 280) { formats += L"DOS-ordered image (*.do)|*.do|"; filterIndex = 2; } } formats += L"|"; CFileDialog saveDlg(FALSE, L"po", NULL, OFN_OVERWRITEPROMPT|OFN_NOREADONLYRETURN|OFN_HIDEREADONLY, formats, this); saveDlg.m_ofn.lpstrTitle = L"New Disk Image"; saveDlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder); saveDlg.m_ofn.nFilterIndex = filterIndex; if (saveDlg.DoModal() != IDOK) { LOGI(" User cancelled xfer from image create dialog"); return; } saveFolder = saveDlg.m_ofn.lpstrFile; saveFolder = saveFolder.Left(saveDlg.m_ofn.nFileOffset); fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder); filename = saveDlg.GetPathName(); LOGI(" Will xfer to file '%ls' (filterIndex=%d)", (LPCWSTR) filename, saveDlg.m_ofn.nFilterIndex); if (createDlg.fDiskFormatIdx == CreateImageDialog::kFmtDOS32) { options.base.sectorOrder = DiskImg::kSectorOrderDOS; } else { if (saveDlg.m_ofn.nFilterIndex == 2) options.base.sectorOrder = DiskImg::kSectorOrderDOS; else options.base.sectorOrder = DiskImg::kSectorOrderProDOS; } /* remove file if it already exists */ CString errMsg; errMsg = RemoveFile(filename); if (!errMsg.IsEmpty()) { ShowFailureMsg(this, errMsg, IDS_FAILED); return; } pNewArchive = new DiskArchive; /* create the new archive, showing a "busy" message */ { ExclusiveModelessDialog* pWaitDlg = new ExclusiveModelessDialog; pWaitDlg->Create(IDD_FORMATTING, this); pWaitDlg->CenterWindow(); PeekAndPump(); // redraw CWaitCursor waitc; errStr = pNewArchive->New(filename, &options); pWaitDlg->DestroyWindow(); //PeekAndPump(); // redraw } delete pNewArchive; // close it, either way if (!errStr.IsEmpty()) { ShowFailureMsg(this, errStr, IDS_FAILED); (void) _wunlink(filename); } else { LOGI("Disk image created successfully"); #if 0 SuccessBeep(); /* give them the opportunity to open the new disk image */ DoneOpenDialog doneOpen(this); if (doneOpen.DoModal() == IDOK) { LOGI(" At user request, opening '%ls'", filename); DoOpenArchive(filename, "dsk", kFilterIndexDiskImage, false); } #else if (createDlg.fDiskFormatIdx != CreateImageDialog::kFmtBlank) DoOpenArchive(filename, L"dsk", kFilterIndexDiskImage, false); #endif } } /* * ========================================================================== * EOL scanner * ========================================================================== */ void MainWindow::OnToolsEOLScanner(void) { CString fileName, saveFolder, errMsg; CString openFilters; openFilters = kOpenAll; openFilters += kOpenEnd; CFileDialog fileDlg(TRUE, L"dsk", NULL, OFN_FILEMUSTEXIST, openFilters, this); fileDlg.m_ofn.Flags |= OFN_HIDEREADONLY; //fileDlg.m_ofn.Flags &= ~(OFN_READONLY); fileDlg.m_ofn.lpstrTitle = L"Select file to scan"; fileDlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder); if (fileDlg.DoModal() != IDOK) return; fileName = fileDlg.GetPathName(); saveFolder = fileDlg.m_ofn.lpstrFile; saveFolder = saveFolder.Left(fileDlg.m_ofn.nFileOffset); fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder); LOGI("Scanning '%ls'", (LPCWSTR) fileName); FILE* fp = _wfopen(fileName, L"rb"); if (fp == NULL) { errMsg.Format(L"Unable to open '%ls': %hs.", (LPCWSTR) fileName, strerror(errno)); ShowFailureMsg(this, errMsg, IDS_FAILED); return; } long numCR, numLF, numCRLF, numHAChars, numChars; bool lastCR; int ic; /* * Plow through the file, counting up characters. */ numCR = numLF = numCRLF = numChars = numHAChars = 0; lastCR = false; while (true) { ic = getc(fp); if (ic == EOF) break; if ((ic & 0x80) != 0) numHAChars++; if (ic == '\r') { lastCR = true; numCR++; } else if (ic == '\n') { if (lastCR) { numCR--; numCRLF++; lastCR = false; } else { numLF++; } } else { lastCR = false; } numChars++; } fclose(fp); LOGD("Got CR=%ld LF=%ld CRLF=%ld (numChars=%ld)", numCR, numLF, numCRLF, numChars); EOLScanDialog output; output.fCountCR = numCR; output.fCountLF = numLF; output.fCountCRLF = numCRLF; output.fCountChars = numChars; output.fCountHighASCII = numHAChars; (void) output.DoModal(); } /* * ========================================================================== * 2MG disk image properties editor * ========================================================================== */ void MainWindow::OnToolsTwoImgProps(void) { CString fileName, saveFolder, errMsg; CString openFilters; /* flush current archive in case that's what we're planning to edit */ OnFileSave(); /* * Select the file to open. */ openFilters = L"2MG Disk Images (.2mg .2img)|*.2mg;*.2img|"; openFilters += kOpenAll; openFilters += kOpenEnd; CFileDialog fileDlg(TRUE, L"2mg", NULL, OFN_FILEMUSTEXIST, openFilters, this); fileDlg.m_ofn.Flags |= OFN_HIDEREADONLY; //fileDlg.m_ofn.Flags &= ~(OFN_READONLY); fileDlg.m_ofn.lpstrTitle = L"Select file to edit"; fileDlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder); if (fileDlg.DoModal() != IDOK) return; fileName = fileDlg.GetPathName(); saveFolder = fileDlg.m_ofn.lpstrFile; saveFolder = saveFolder.Left(fileDlg.m_ofn.nFileOffset); fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder); /* * Open it up. */ bool changed; changed = EditTwoImgProps(fileName); if (changed && IsOpenPathName(fileName)) { PeekAndPump(); // clear out dialog ReopenArchive(); } } bool MainWindow::EditTwoImgProps(const WCHAR* fileName) { TwoImgPropsDialog dialog; TwoImgHeader header; FILE* fp = NULL; bool dirty = false; CString errMsg; long totalLength; bool readOnly = false; LOGI("EditTwoImgProps '%ls'", fileName); fp = _wfopen(fileName, L"r+b"); if (fp == NULL) { int firstError = errno; fp = _wfopen(fileName, L"rb"); if (fp == NULL) { errMsg.Format(L"Unable to open '%ls': %hs.", fileName, strerror(firstError)); goto bail; } else { readOnly = true; } } fseek(fp, 0, SEEK_END); totalLength = ftell(fp); rewind(fp); if (header.ReadHeader(fp, totalLength) != 0) { errMsg.Format(L"Unable to process 2MG header in '%ls'" L" (are you sure this is in 2MG format?).", (LPCWSTR) fileName); goto bail; } dialog.Setup(&header, readOnly); if (dialog.DoModal() == IDOK) { long result; //header.SetCreatorChunk("fubar", 5); header.DumpHeader(); rewind(fp); if (header.WriteHeader(fp) != 0) { errMsg = L"Unable to write 2MG header"; goto bail; } /* * Clip off the footer. They might have had one before but don't * have one now. If they do have one now we'll add it back in a * second. */ result = fseek(fp, header.fDataOffset + header.fDataLen, SEEK_SET); if (result < 0) { errMsg = L"Unable to seek to end of 2MG file"; goto bail; } dirty = true; if (::chsize(fileno(fp), ftell(fp)) != 0) { errMsg = L"Unable to truncate 2MG file before writing footer"; goto bail; } if (header.fCmtLen || header.fCreatorLen) { if (header.WriteFooter(fp) != 0) { errMsg = L"Unable to write 2MG footer"; goto bail; } } LOGI("2MG success!"); } bail: if (fp != NULL) fclose(fp); if (!errMsg.IsEmpty()) { ShowFailureMsg(this, errMsg, IDS_FAILED); } return dirty; }