ciderpress/app/Tools.cpp
Andy McFadden 51b5f00f5c Large set of changes to restore CiderPress build.
CiderPress and MDC now compile, and execute far enough to open
their respective "about" boxes, but I doubt they'll do much
more than that.

* Switch from MBCS to UNICODE APIs

Microsoft switched to UTF-16 (by way of UCS-2) a long time ago,
and the support for MBCS seems to be getting phased out.  So it's
time to switch to wide strings.

This is a bit awkward for CiderPress because it works with disk
and file archives with 8-bit filenames, and I want NufxLib and
DiskImgLib to continue to work on Linux (which has largely taken
the UTF-8 approach to Unicode).  The libraries will continue to
work with 8-bit filenames, with CiderPress/MDC doing the
conversion at the appropriate point.

There were a couple of places where strings from a structure
handed back by one of the libraries were used directly in the UI,
or vice-versa, which is a problem because we have nowhere to
store the result of the conversion.  These currently have fixed
place-holder "xyzzy" strings.

All UI strings are now wide.

Various format strings now use "%ls" and "%hs" to explicitly
specify wide and narrow.  This doesn't play well with gcc, so
only the Windows-specific parts use those.

* Various updates to vcxproj files

The project-file conversion had some cruft that is now largely
gone.  The build now has a common output directory for the EXEs
and libraries, avoiding the old post-build copy steps.

* Added zlib 1.2.8 and nufxlib 2.2.2 source snapshots

The old "prebuilts" directory is now gone.  The libraries are now
built as part of building the apps.

I added a minimal set of files for zlib, and a full set for nufxlib.
The Linux-specific nufxlib goodies are included for the benefit of
the Linux utilities, which are currently broken (don't build).

* Replace symbols used for include guards

Symbols with a leading "__" are reserved.
2014-11-16 21:01:53 -08:00

2510 lines
82 KiB
C++

/*
* 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 <io.h> // need chsize() for TwoImgProps
/*
* Put up the ImageFormatDialog and apply changes to "pImg".
*
* "*pDisplayFormat" gets the result of user changes to the display format.
* If "pDisplayFormat" is nil, the "query image format" feature will be
* disabled.
*
* Returns IDCANCEL if the user cancelled out of the dialog, IDOK otherwise.
* On error, "*pErrMsg" will be non-empty.
*/
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 == nil)
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;
}
WMSG2(" On entry, sectord=%d format=%d\n",
imf.fSectorOrder, imf.fFSFormat);
if (imf.DoModal() != IDOK) {
WMSG0(" User bailed on IMF dialog\n");
return IDCANCEL;
}
WMSG2(" On exit, sectord=%d format=%d\n",
imf.fSectorOrder, imf.fFSFormat);
if (pDisplayFormat != nil)
*pDisplayFormat = imf.fDisplayFormat;
if (imf.fSectorOrder != pImg->GetSectorOrder() ||
imf.fFSFormat != pImg->GetFSFormat())
{
WMSG0("Initial values overridden, forcing img format\n");
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
* ==========================================================================
*/
/*
* User wants to edit a disk.
*/
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();
failed.LoadString(IDS_FAILED);
diskEditOpen.fArchiveOpen = false;
if (fpOpenArchive != nil &&
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
loadName = fpOpenArchive->GetPathName();
readOnly = fpOpenArchive->IsReadOnly();
} else {
WMSG1("GLITCH: unexpected fOpenWhat %d\n", diskEditOpen.fOpenWhat);
ASSERT(false);
goto bail;
}
WMSG3("Disk editor what=%d name='%ls' ro=%d\n",
diskEditOpen.fOpenWhat, (LPCWSTR) loadName, readOnly);
#if 1
{
CWaitCursor waitc;
/* open the image file and analyze it */
dierr = img.OpenImage(loadName, PathProposal::kLocalFssep, true);
}
#else
/* quick test of memory-buffer-based interface */
FILE* tmpfp;
char* phatbuf;
long length;
tmpfp = fopen(loadName, "rb");
ASSERT(tmpfp != nil);
fseek(tmpfp, 0, SEEK_END);
length = ftell(tmpfp);
rewind(tmpfp);
WMSG1(" PHATBUF %d\n", length);
phatbuf = new char[length];
if (fread(phatbuf, length, 1, tmpfp) != 1)
WMSG1("FREAD FAILED %d\n", 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 = &sectorEdit;
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 == nil) {
WMSG0("HEY: OpenAppropriateDiskFS failed!\n");
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
* ==========================================================================
*/
/*
* Convert a disk image from one format to another.
*/
void
MainWindow::OnToolsDiskConv(void)
{
DIError dierr;
CString openFilters, errMsg;
CString loadName, saveName, saveFolder;
DiskImg srcImg, dstImg;
DiskConvertDialog convDlg(this);
CString storageName;
CStringA saveNameA, storageNameA;
/* 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 */
dierr = srcImg.OpenImage(loadName, 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,
nil, 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();
}
delete pDiskFS;
} else {
/* use filename as storageName (exception for DiskCopy42 later) */
storageName = PathName::FilenameOnly(loadName, '\\');
}
WMSG1(" Using '%ls' as storageName\n", (LPCWSTR) storageName);
/* transfer the DOS volume num, if one was set */
dstImg.SetDOSVolumeNum(srcImg.GetDOSVolumeNum());
WMSG1("DOS volume number set to %d\n", 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) {
WMSG0(" User bailed out of convert dialog\n");
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, &sectorOrder) != 0)
{
goto bail;
}
const DiskImg::NibbleDescr* pNibbleDescr;
pNibbleDescr = srcImg.GetNibbleDescr();
if (pNibbleDescr == nil && 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);
}
}
WMSG2(" NibbleDescr is 0x%08lx (%hs)\n", (long) pNibbleDescr,
pNibbleDescr != nil ? pNibbleDescr->description : "---");
if (srcImg.GetFileFormat() == DiskImg::kFileFormatTrackStar &&
fileFormat != DiskImg::kFileFormatTrackStar)
{
/* converting from TrackStar to anything else */
CString msg, appName;
msg.LoadString(IDS_TRACKSTAR_TO_OTHER_WARNING);
appName.LoadString(IDS_MB_APP_NAME);
if (MessageBox(msg, appName, MB_OKCANCEL | MB_ICONWARNING) != IDOK) {
WMSG0(" User bailed after trackstar-to-other warning\n");
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;
msg.LoadString(IDS_FDI_TO_OTHER_WARNING);
appName.LoadString(IDS_MB_APP_NAME);
if (MessageBox(msg, appName, MB_OKCANCEL | MB_ICONWARNING) != IDOK) {
WMSG0(" User bailed after fdi-to-other warning\n");
goto bail;
}
} else if (srcImg.GetHasNibbles() && DiskImg::IsSectorFormat(physicalFormat))
{
/* converting from nibble to non-nibble format */
CString msg, appName;
msg.LoadString(IDS_NIBBLE_TO_SECTOR_WARNING);
appName.LoadString(IDS_MB_APP_NAME);
if (MessageBox(msg, appName, MB_OKCANCEL | MB_ICONWARNING) != IDOK) {
WMSG0(" User bailed after nibble-to-sector warning\n");
goto bail;
}
} else if (srcImg.GetHasNibbles() &&
DiskImg::IsNibbleFormat(physicalFormat) &&
srcImg.GetPhysicalFormat() != physicalFormat)
{
/* converting between differing nibble formats */
CString msg, appName;
msg.LoadString(IDS_DIFFERENT_NIBBLE_WARNING);
appName.LoadString(IDS_MB_APP_NAME);
if (MessageBox(msg, appName, MB_OKCANCEL | MB_ICONWARNING) != IDOK) {
WMSG0(" User bailed after differing-nibbles warning\n");
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)
{
WMSG0(" Switching to DOS sector ordering for UNIDOS/DiskCopy42");
sectorOrder = DiskImg::kSectorOrderDOS;
}
if (origFSFormat != DiskImg::kFormatProDOS &&
fileFormat == DiskImg::kFileFormatDiskCopy42)
{
WMSG0(" 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) {
WMSG0(" User bailed out of image save dialog\n");
goto bail;
}
saveFolder = saveDlg.m_ofn.lpstrFile;
saveFolder = saveFolder.Left(saveDlg.m_ofn.nFileOffset);
fPreferences.SetPrefString(kPrConvertArchiveFolder, saveFolder);
saveName = saveDlg.GetPathName();
}
WMSG1("File will be saved to '%ls'\n", (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, nil);
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) {
WMSG1(" At user request, opening '%ls'\n", (LPCWSTR) saveName);
DoOpenArchive(saveName, convDlg.fExtension,
kFilterIndexDiskImage, false);
}
}
bail:
return;
}
/*
* Determine the settings we need to pass into DiskImgLib to create the
* desired disk image format.
*
* Returns 0 on success, -1 on failure.
*/
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);
WMSG1(" WHOA: invalid conv type %d\n", convertIdx);
return -1;
}
return 0;
}
static inline int MIN(int val1, int val2)
{
return (val1 < val2) ? val1 : val2;
}
/*
* Do a block copy or track copy from one disk image to another.
*
* If "bulk" is set, warning dialogs are suppressed. If "partial" is set,
* copies between volumes of different sizes are allowed.
*
* This originally just did a block copy. Nibble track copies were added
* later, and sector copies were added even later.
*/
DIError
MainWindow::CopyDiskImage(DiskImg* pDstImg, DiskImg* pSrcImg, bool bulk,
bool partial, ProgressCancelDialog* pPCDialog)
{
DIError dierr = kDIErrNone;
CString errMsg;
unsigned char* dataBuf = nil;
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 == nil) {
dierr = kDIErrMalloc;
goto bail;
}
numTracks = MIN(pSrcImg->GetNumTracks(), pDstImg->GetNumTracks());
WMSG1("Nibble track copy (%d tracks)\n", 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 == nil) {
dierr = kDIErrMalloc;
goto bail;
}
numTracks = MIN(pSrcImg->GetNumTracks(), pDstImg->GetNumTracks());
numSectPerTrack = MIN(pSrcImg->GetNumSectPerTrack(),
pDstImg->GetNumSectPerTrack());
WMSG2("Sector copy (%d tracks / %d sectors)\n",
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) {
WMSG2("Bad sector T=%d S=%d\n", 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;
appName.LoadString(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 == nil) {
dierr = kDIErrMalloc;
goto bail;
}
WMSG2("--- BLOCK COPY (%ld blocks, %d per)\n",
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.
*/
WMSG2(" Bad sector encountered at %ld(%ld), slowing\n",
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 != nil && (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;
appName.LoadString(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 != nil);
pWnd->SetWindowText(fileName);
}
bool fAbortOperation;
private:
void OnOK(void) {
WMSG0("Ignoring BulkConvCancelDialog OnOK\n");
}
MainWindow* GetMainWindow(void) const {
return (MainWindow*)::AfxGetMainWnd();
}
};
/*
* Handle a request for a bulk disk conversion.
*/
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 != nil) {
CString pathName;
pathName = dlg.GetNextPathName(posn);
nameCount++;
}
WMSG1("BulkConv got nameCount=%d\n", 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) {
WMSG0(" User bailed out of convert dialog\n");
goto bail;
}
/* initialize cancel dialog, and disable main window */
EnableWindow(FALSE);
if (pCancelDialog->Create(this) == FALSE) {
WMSG0("Cancel dialog init failed?!\n");
ASSERT(false);
goto bail;
}
/*
* Loop through all selected files and convert them one at a time.
*/
posn = dlg.GetStartPosition();
while (posn != nil) {
CString pathName;
pathName = dlg.GetNextPathName(posn);
WMSG1(" BulkConv: source path='%ls'\n", (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;
failed.LoadString(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 != nil)
pCancelDialog->DestroyWindow();
delete[] dlg.m_ofn.lpstrFile;
return;
}
/*
* Convert one image during a bulk conversion.
*
* [Much of this is copy & pasted from OnToolsDiskConv(). This needs to get
* refactored.]
*
* On failure, the reason for failure is stuffed into "*pErrMsg".
*/
void
MainWindow::BulkConvertImage(const WCHAR* pathName, const WCHAR* targetDir,
const DiskConvertDialog& convDlg, CString* pErrMsg)
{
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 */
dierr = srcImg.OpenImage(pathName, 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,
nil, 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());
WMSG1("DOS volume number set to %d\n", 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, &sectorOrder) != 0)
{
*pErrMsg = L"Odd: couldn't configure image settings";
goto bail;
}
const DiskImg::NibbleDescr* pNibbleDescr;
pNibbleDescr = srcImg.GetNibbleDescr();
if (pNibbleDescr == nil && 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);
}
}
WMSG2(" NibbleDescr is 0x%08lx (%hs)\n", (long) pNibbleDescr,
pNibbleDescr != nil ? 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;
WMSG2(" Bulk converting '%ls' to '%ls'\n", 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();
}
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
}
WMSG1(" Using '%ls' as storageName\n", (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)
{
WMSG0(" 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, nil);
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;
/*
* Merge two SST images into a single NIB image.
*/
void
MainWindow::OnToolsSSTMerge(void)
{
const int kBadCountThreshold = 3072;
DiskImg srcImg0, srcImg1;
CString appName, saveName, saveFolder, errMsg;
BYTE* trackBuf = nil;
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);
appName.LoadString(IDS_MB_APP_NAME);
trackBuf = new BYTE[kSSTNumTracks * kSSTTrackLen];
if (trackBuf == nil)
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;
WMSG1("FOUND %ld bad bytes in part 0\n", badCount);
if (badCount > kBadCountThreshold) {
errMsg.LoadString(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;
WMSG1("FOUND %ld bad bytes in part 1\n", badCount);
if (badCount > kBadCountThreshold) {
errMsg.LoadString(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) {
WMSG0(" User bailed out of image save dialog\n");
goto bail;
}
saveFolder = saveDlg.m_ofn.lpstrFile;
saveFolder = saveFolder.Left(saveDlg.m_ofn.nFileOffset);
fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder);
saveName = saveDlg.GetPathName();
WMSG1("File will be saved to '%ls'\n", (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 == nil) {
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) {
WMSG1(" At user request, opening '%ls'\n", (LPCWSTR) saveName);
DoOpenArchive(saveName, L"nib", kFilterIndexDiskImage, false);
}
}
bail:
delete[] trackBuf;
return;
}
/*
* Open one of the SST images.
*
* Configures "pDiskImg" appropriately.
*
* Returns 0 on success, nonzero on failure.
*/
int
MainWindow::SSTOpenImage(int seqNum, DiskImg* pDiskImg)
{
DIError dierr;
int result = -1;
CString openFilters, errMsg;
CString loadName, saveFolder;
/*
* 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 */
dierr = pDiskImg->OpenImage(loadName, 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, nil, 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;
}
/*
* Copy 17.5 tracks of data from the SST image to a .NIB image.
*
* Data is stored in all 16 sectors of track 0, followed by the first
* 12 sectors of track 1, then on to track 2. Total of $1a00 bytes.
*
* Returns 0 on success, -1 on failure.
*/
int
MainWindow::SSTLoadData(int seqNum, DiskImg* pDiskImg, BYTE* trackBuf,
long* pBadCount)
{
DIError dierr;
BYTE sctBuf[256];
int track, sector;
long bufOffset;
for (track = 0; track < kSSTNumTracks; track++) {
int virtualTrack = track + (seqNum * kSSTNumTracks);
bufOffset = SSTGetBufOffset(virtualTrack);
//WMSG3("USING offset=%ld (track=%d / %d)\n",
// 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) {
WMSG2("ERROR: on track=%d sector=%d\n",
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) {
WMSG2("ERROR: on track=%d sector=%d\n",
track, sector);
return -1;
}
*pBadCount += SSTCountBadBytes(sctBuf, 256);
memcpy(trackBuf + bufOffset, sctBuf, 256);
bufOffset += 256;
}
}
}
return 0;
}
/*
* Compute the destination file offset for a particular source track. The
* track number ranges from 0 to 69 inclusive. Sectors from two adjacent
* "cooked" tracks are combined into a single "raw nibbilized" track.
*
* The data is ordered like this:
* track 1 sector 15 --> track 1 sector 4 (12 sectors)
* track 0 sector 13 --> track 0 sector 0 (14 sectors)
*
* Total of 26 sectors, or $1a00 bytes.
*/
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;
}
/*
* Count the number of "bad" bytes in the sector.
*
* Strictly speaking, a "bad" byte is anything that doesn't appear in the
* 6&2 decoding table, 5&3 decoding table, special list (D5, AA), and
* can't be used as a 4+4 encoding value.
*
* We just use $80 - $92, which qualify for all of the above.
*/
long
MainWindow::SSTCountBadBytes(const unsigned char* sctBuf, int count)
{
long badCount = 0;
unsigned char uch;
while (count--) {
uch = (*sctBuf) | 0x80;
if (uch >= 0x80 && uch <= 0x92)
badCount++;
sctBuf++;
}
return badCount;
}
/*
* Run through the data, adding 0x80 everywhere and re-aligning the
* tracks so that the big clump of sync bytes is at the end.
*/
void
MainWindow::SSTProcessTrackData(unsigned char* trackBuf)
{
unsigned char* trackPtr;
int track;
for (track = 0, trackPtr = trackBuf; track < kSSTNumTracks;
track++, trackPtr += kSSTTrackLen)
{
bool inRun;
int start, longestStart;
int count7f, 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) {
WMSG1("HEY: couldn't find any 0x7f in track %d\n",
track);
} else {
WMSG3("Found run of %d at %d in track %d\n",
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);
}
/*
* Select a volume and then invoke the volcopy dialog.
*/
void
MainWindow::VolumeCopier(bool openFile)
{
VolumeCopyDialog copyDlg(this);
DiskImg srcImg;
//DiskFS* pDiskFS = nil;
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();
failed.LoadString(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);
dierr = srcImg.OpenImage(deviceName, '\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,
nil, 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
* ==========================================================================
*/
/*
* Create a new disk image.
*/
void
MainWindow::OnToolsDiskImageCreator(void)
{
CreateImageDialog createDlg(this);
DiskArchive* pNewArchive = nil;
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:
WMSG1("Invalid fDiskFormatIdx %d from CreateImageDialog\n",
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) {
WMSG0(" User cancelled xfer from image create dialog\n");
return;
}
saveFolder = saveDlg.m_ofn.lpstrFile;
saveFolder = saveFolder.Left(saveDlg.m_ofn.nFileOffset);
fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder);
filename = saveDlg.GetPathName();
WMSG2(" Will xfer to file '%ls' (filterIndex=%d)\n",
(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 {
WMSG0("Disk image created successfully\n");
#if 0
SuccessBeep();
/* give them the opportunity to open the new disk image */
DoneOpenDialog doneOpen(this);
if (doneOpen.DoModal() == IDOK) {
WMSG1(" At user request, opening '%ls'\n", filename);
DoOpenArchive(filename, "dsk", kFilterIndexDiskImage, false);
}
#else
if (createDlg.fDiskFormatIdx != CreateImageDialog::kFmtBlank)
DoOpenArchive(filename, L"dsk", kFilterIndexDiskImage, false);
#endif
}
}
/*
* ==========================================================================
* EOL scanner
* ==========================================================================
*/
/*
* Scan and report on the end-of-line markers found in a file.
*
* Useful for identifying files that have been mangled by ASCII conversions.
*/
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);
WMSG1("Scanning '%ls'\n", (LPCWSTR) fileName);
FILE* fp = _wfopen(fileName, L"rb");
if (fp == nil) {
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);
WMSG4("Got CR=%ld LF=%ld CRLF=%ld (numChars=%ld)\n",
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
* ==========================================================================
*/
/*
* Edit the properties (but not the disk image inside) a .2MG disk image.
*/
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();
}
}
/*
* Edit the properties of a 2MG file.
*
* Returns "true" if the file was modified, "false" if not.
*/
bool
MainWindow::EditTwoImgProps(const WCHAR* fileName)
{
TwoImgPropsDialog dialog;
TwoImgHeader header;
FILE* fp = nil;
bool dirty = false;
CString errMsg;
long totalLength;
bool readOnly = false;
WMSG1("EditTwoImgProps '%ls'\n", fileName);
fp = _wfopen(fileName, L"r+b");
if (fp == nil) {
int firstError = errno;
fp = _wfopen(fileName, L"rb");
if (fp == nil) {
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;
}
}
WMSG0("2MG success!\n");
}
bail:
if (fp != nil)
fclose(fp);
if (!errMsg.IsEmpty()) {
ShowFailureMsg(this, errMsg, IDS_FAILED);
}
return dirty;
}