mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-29 20:49:27 +00:00
51b5f00f5c
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.
2510 lines
82 KiB
C++
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 = §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 == 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, §orOrder) != 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, §orOrder) != 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;
|
|
}
|