mirror of
https://github.com/fadden/ciderpress.git
synced 2024-05-28 11:41:32 +00:00
edb7c13120
The OpenImage method had an overload that took void*. This turns out to be a bad idea, because void* matches any pointer type that didn't match something else. So the WCHAR* filenames were going to the "open from buffer" method rather than the "open from file" variant. A less important issue is whether open-from-buffer should take a const or non-const pointer. If the "readOnly" boolean flag is not set, then the contents can be altered and const is inappropriate. The best course seems to be to drop the boolean flag as an argument, and just have two different methods.
1667 lines
68 KiB
C++
1667 lines
68 KiB
C++
/*
|
|
* CiderPress
|
|
* Copyright (C) 2009 by CiderPress authors. All Rights Reserved.
|
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
|
* See the file LICENSE for distribution terms.
|
|
*/
|
|
/*
|
|
* Public declarations for the DiskImg library.
|
|
*
|
|
* Everything is wrapped in the "DiskImgLib" namespace. Either prefix
|
|
* all references with "DiskImgLib::", or add "using namespace DiskImgLib"
|
|
* to all C++ source files that make use of it.
|
|
*
|
|
* Under Linux, this should be compiled with -D_FILE_OFFSET_BITS=64.
|
|
*
|
|
* These classes are not thread-safe with respect to access to a single
|
|
* disk image. Writing to the same disk image from multiple threads
|
|
* simultaneously is bound to end in disaster. Simultaneous access to
|
|
* different objects will work, though modifying the same disk image
|
|
* file from multiple objects will lead to unpredictable results.
|
|
*/
|
|
#ifndef DISKIMG_DISKIMG_H
|
|
#define DISKIMG_DISKIMG_H
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
|
|
//#define EXCISE_GPL_CODE
|
|
|
|
/* Windows DLL stuff */
|
|
#ifdef _WIN32
|
|
# ifdef DISKIMG_EXPORTS
|
|
# define DISKIMG_API __declspec(dllexport)
|
|
# else
|
|
# define DISKIMG_API __declspec(dllimport)
|
|
# endif
|
|
#else
|
|
# define DISKIMG_API
|
|
#endif
|
|
|
|
namespace DiskImgLib {
|
|
|
|
/* compiled-against versions; call DiskImg::GetVersion for linked-against */
|
|
#define kDiskImgVersionMajor 4
|
|
#define kDiskImgVersionMinor 6
|
|
#define kDiskImgVersionBug 0
|
|
|
|
|
|
/*
|
|
* Errors from the various disk image classes.
|
|
*/
|
|
typedef enum DIError {
|
|
kDIErrNone = 0,
|
|
|
|
/* I/O request errors (should renumber/rename to match GS/OS errors?) */
|
|
kDIErrAccessDenied = -10,
|
|
kDIErrVWAccessForbidden = -11, // write access to volume forbidden
|
|
kDIErrSharingViolation = -12, // file is in use and not shareable
|
|
kDIErrNoExclusiveAccess = -13, // couldn't get exclusive access
|
|
kDIErrWriteProtected = -14, // disk is write protected
|
|
kDIErrCDROMNotSupported = -15, // access to CD-ROM drives not supptd
|
|
kDIErrASPIFailure = -16, // generic ASPI failure result
|
|
kDIErrSPTIFailure = -17, // generic SPTI failure result
|
|
kDIErrSCSIFailure = -18, // generic SCSI failure result
|
|
kDIErrDeviceNotReady = -19, // floppy or CD-ROM drive has no media
|
|
|
|
kDIErrFileNotFound = -20,
|
|
kDIErrForkNotFound = -21, // requested fork does not exist
|
|
kDIErrAlreadyOpen = -22, // already open, can't open a 2nd time
|
|
kDIErrFileOpen = -23, // file is open, can't delete it
|
|
kDIErrNotReady = -24,
|
|
kDIErrFileExists = -25, // file already exists
|
|
kDIErrDirectoryExists = -26, // directory already exists
|
|
|
|
kDIErrEOF = -30, // end-of-file reached
|
|
kDIErrReadFailed = -31,
|
|
kDIErrWriteFailed = -32,
|
|
kDIErrDataUnderrun = -33, // tried to read off end of the image
|
|
kDIErrDataOverrun = -34, // tried to write off end of the image
|
|
kDIErrGenericIO = -35, // generic I/O error
|
|
|
|
kDIErrOddLength = -40, // image size not multiple of sectors
|
|
kDIErrUnrecognizedFileFmt = -41, // file format just not recognized
|
|
kDIErrBadFileFormat = -42, // filename ext doesn't match contents
|
|
kDIErrUnsupportedFileFmt = -43, // recognized but not supported
|
|
kDIErrUnsupportedPhysicalFmt = -44, // (same)
|
|
kDIErrUnsupportedFSFmt = -45, // (and again)
|
|
kDIErrBadOrdering = -46, // requested sector ordering no good
|
|
kDIErrFilesystemNotFound = -47, // requested filesystem isn't there
|
|
kDIErrUnsupportedAccess = -48, // e.g. read sectors from blocks-only
|
|
kDIErrUnsupportedImageFeature = -49, // e.g. FDI image w/Amiga sectors
|
|
|
|
kDIErrInvalidTrack = -50, // request for invalid track number
|
|
kDIErrInvalidSector = -51, // request for invalid sector number
|
|
kDIErrInvalidBlock = -52, // request for invalid block number
|
|
kDIErrInvalidIndex = -53, // request with an invalid index
|
|
|
|
kDIErrDirectoryLoop = -60, // directory chain points into itself
|
|
kDIErrFileLoop = -61, // file sector or block alloc loops
|
|
kDIErrBadDiskImage = -62, // the FS on the disk image is damaged
|
|
kDIErrBadFile = -63, // bad file on disk image
|
|
kDIErrBadDirectory = -64, // bad dir on disk image
|
|
kDIErrBadPartition = -65, // bad partition on multi-part format
|
|
|
|
kDIErrFileArchive = -70, // file archive, not disk archive
|
|
kDIErrUnsupportedCompression = -71, // compression method is not supported
|
|
kDIErrBadChecksum = -72, // image file's checksum is bad
|
|
kDIErrBadCompressedData = -73, // data can't even be unpacked
|
|
kDIErrBadArchiveStruct = -74, // bad archive structure
|
|
|
|
kDIErrBadNibbleSectors = -80, // can't read sectors from this image
|
|
kDIErrSectorUnreadable = -81, // requested sector not readable
|
|
kDIErrInvalidDiskByte = -82, // invalid byte for encoding type
|
|
kDIErrBadRawData = -83, // couldn't get correct nibbles
|
|
|
|
kDIErrInvalidFileName = -90, // tried to create file with bad name
|
|
kDIErrDiskFull = -91, // no space left on disk
|
|
kDIErrVolumeDirFull = -92, // no more entries in volume dir
|
|
kDIErrInvalidCreateReq = -93, // CreateImage request was flawed
|
|
kDIErrTooBig = -94, // larger than we want to handle
|
|
|
|
/* higher-level errors */
|
|
kDIErrGeneric = -101,
|
|
kDIErrInternal = -102,
|
|
kDIErrMalloc = -103,
|
|
kDIErrInvalidArg = -104,
|
|
kDIErrNotSupported = -105, // feature not currently supported
|
|
kDIErrCancelled = -106, // an operation was cancelled by user
|
|
|
|
kDIErrNufxLibInitFailed = -110,
|
|
} DIError;
|
|
|
|
/* return a string describing the error */
|
|
DISKIMG_API const char* DIStrError(DIError dierr);
|
|
|
|
|
|
/* exact definition of off_t varies, so just define our own */
|
|
#ifdef _ULONGLONG_
|
|
typedef LONGLONG di_off_t;
|
|
#else
|
|
typedef off_t di_off_t;
|
|
#endif
|
|
|
|
/* common definition of "whence" for seeks */
|
|
typedef enum DIWhence {
|
|
kSeekSet = SEEK_SET,
|
|
kSeekCur = SEEK_CUR,
|
|
kSeekEnd = SEEK_END
|
|
};
|
|
|
|
/* try to load ASPI under Win2K; if successful, SPTI should be disabled */
|
|
const bool kAlwaysTryASPI = false;
|
|
/* ASPI device "filenames" look like "ASPI:x:y:z\" */
|
|
DISKIMG_API extern const char* kASPIDev;
|
|
|
|
/* some nibble-encoding constants */
|
|
const int kTrackLenNib525 = 6656;
|
|
const int kTrackLenNb2525 = 6384;
|
|
const int kTrackLenTrackStar525 = 6525; // max len of data in TS image
|
|
const int kTrackAllocSize = 6656; // max 5.25 nibble track len; for buffers
|
|
const int kTrackCount525 = 35; // expected #of tracks on 5.25 img
|
|
const int kMaxNibbleTracks525 = 40; // max #of tracks on 5.25 nibble img
|
|
const int kDefaultNibbleVolumeNum = 254;
|
|
const int kBlockSize = 512; // block size for DiskImg interfaces
|
|
const int kSectorSize = 256; // sector size (1/2 block)
|
|
const int kD13Length = 256 * 13 * 35; // length of a .d13 image
|
|
|
|
/* largest expanse we allow access to on a volume (8GB in 512-byte blocks) */
|
|
const long kVolumeMaxBlocks = 8*1024*(1024*1024 / kBlockSize);
|
|
|
|
/* largest .gz file we'll open (uncompressed size) */
|
|
const long kGzipMax = 32*1024*1024;
|
|
|
|
/* forward and external class definitions */
|
|
class DiskFS;
|
|
class A2File;
|
|
class A2FileDescr;
|
|
class GenericFD;
|
|
class OuterWrapper;
|
|
class ImageWrapper;
|
|
class CircularBufferAccess;
|
|
class ASPI;
|
|
class LinearBitmap;
|
|
|
|
|
|
/*
|
|
* Library-global data functions.
|
|
*
|
|
* This class is just a namespace clumper. Do not instantiate.
|
|
*/
|
|
class DISKIMG_API Global {
|
|
public:
|
|
// one-time DLL initialization; use SetDebugMsgHandler first
|
|
static DIError AppInit(void);
|
|
// one-time DLL cleanup
|
|
static DIError AppCleanup(void);
|
|
|
|
// return the DiskImg version number
|
|
static void GetVersion(long* pMajor, long* pMinor, long* pBug);
|
|
|
|
static bool GetAppInitCalled(void) { return fAppInitCalled; }
|
|
static bool GetHasSPTI(void);
|
|
static bool GetHasASPI(void);
|
|
|
|
// return a pointer to our global ASPI instance, or NULL
|
|
static ASPI* GetASPI(void) { return fpASPI; }
|
|
// shortcut for fpASPI->GetVersion()
|
|
static unsigned long GetASPIVersion(void);
|
|
|
|
// pointer to the debug message handler
|
|
typedef void (*DebugMsgHandler)(const char* file, int line, const char* msg);
|
|
static DebugMsgHandler gDebugMsgHandler;
|
|
|
|
static DebugMsgHandler SetDebugMsgHandler(DebugMsgHandler handler);
|
|
static void PrintDebugMsg(const char* file, int line, const char* fmt, ...)
|
|
#if defined(__GNUC__)
|
|
__attribute__ ((format(printf, 3, 4)))
|
|
#endif
|
|
;
|
|
|
|
private:
|
|
// no instantiation allowed
|
|
Global(void) {}
|
|
~Global(void) {}
|
|
|
|
// make sure app calls AppInit
|
|
static bool fAppInitCalled;
|
|
|
|
static ASPI* fpASPI;
|
|
};
|
|
|
|
extern bool gAllowWritePhys0; // ugh -- see Win32BlockIO.cpp
|
|
|
|
|
|
/*
|
|
* Disk I/O class, roughly equivalent to a GS/OS disk device driver.
|
|
*
|
|
* Abstracts away the file's source (file on disk, file in memory) and
|
|
* storage format (DOS order, ProDOS order, nibble). Will also cope
|
|
* with common disk compression and wrapper formats (Mac DiskCopy, 2MG,
|
|
* ShrinkIt, etc) if handed a file on disk.
|
|
*
|
|
* Images may be embedded within other images, e.g. UNIDOS and storage
|
|
* type $04 pascal volumes.
|
|
*
|
|
* THOUGHT: we need a list(?) of pointers from here back to the DiskFS
|
|
* so that any modifications here will "wake" the DiskFS and sub-volumes.
|
|
* We also need a "dirty" flag so things like CloseNufx can know not to
|
|
* re-do work when Closing after a Flush. Also DiskFS can alert us to
|
|
* any locally cached stuff, and we can tell them to flush everything.
|
|
* (Possibly useful when doing disk updates, so stuff can be trivially
|
|
* un-done. Currently CiderPress checks the filename manually after
|
|
* each write, but that's generally less reliable than having the knowledge
|
|
* contained in the DiskImg.)
|
|
*
|
|
* THOUGHT: need a ReadRawTrack that gets raw nibblized data. For a
|
|
* nibblized image it returns the data, for a sector image it generates
|
|
* the raw data.
|
|
*
|
|
* THOUGHT: we could reduce the risk of problems and increase performance
|
|
* for physical media with a "copy on write" scheme. We'd create a shadow
|
|
* array of modified blocks, and write them at Flush time. This would
|
|
* provide an instantaneous "revert" feature, and prevent formats like
|
|
* DiskCopy42 (which has a CRC in its header) from being inconsistent for
|
|
* long stretches.
|
|
*/
|
|
class DISKIMG_API DiskImg {
|
|
public:
|
|
// create DiskImg object
|
|
DiskImg(void);
|
|
virtual ~DiskImg(void);
|
|
|
|
/*
|
|
* Types describing an image file.
|
|
*
|
|
* The file itself is described by an external parameter ("file source")
|
|
* that is either the name of the file, a memory buffer, or an EFD
|
|
* (EmbeddedFileDescriptor).
|
|
*/
|
|
typedef enum { // format of the "wrapper wrapper"
|
|
kOuterFormatUnknown = 0,
|
|
kOuterFormatNone = 1, // (plain)
|
|
kOuterFormatCompress = 2, // .xx.Z
|
|
kOuterFormatGzip = 3, // .xx.gz
|
|
kOuterFormatBzip2 = 4, // .xx.bz2
|
|
kOuterFormatZip = 10, // .zip
|
|
} OuterFormat;
|
|
typedef enum { // format of the image "wrapper"
|
|
kFileFormatUnknown = 0,
|
|
kFileFormatUnadorned = 1, // .po, .do, ,nib, .raw, .d13
|
|
kFileFormat2MG = 2, // .2mg, .2img, $e0/0130
|
|
kFileFormatDiskCopy42 = 3, // .dsk/.disk, maybe .dc
|
|
kFileFormatDiskCopy60 = 4, // .dc6 (often just raw format)
|
|
kFileFormatDavex = 5, // $e0/8004
|
|
kFileFormatSim2eHDV = 6, // .hdv
|
|
kFileFormatTrackStar = 7, // .app (40-track or 80-track)
|
|
kFileFormatFDI = 8, // .fdi (5.25" or 3.5")
|
|
kFileFormatNuFX = 20, // .shk, .sdk, .bxy
|
|
kFileFormatDDD = 21, // .ddd
|
|
kFileFormatDDDDeluxe = 22, // $DD, .ddd
|
|
} FileFormat;
|
|
typedef enum { // format of the image data stream
|
|
kPhysicalFormatUnknown = 0,
|
|
kPhysicalFormatSectors = 1, // sequential 256-byte sectors (13/16/32)
|
|
kPhysicalFormatNib525_6656 = 2, // 5.25" disk ".nib" (6656 bytes/track)
|
|
kPhysicalFormatNib525_6384 = 3, // 5.25" disk ".nb2" (6384 bytes/track)
|
|
kPhysicalFormatNib525_Var = 4, // 5.25" disk (variable len, e.g. ".app")
|
|
} PhysicalFormat;
|
|
typedef enum { // sector ordering for "sector" format images
|
|
kSectorOrderUnknown = 0,
|
|
kSectorOrderProDOS = 1, // written as series of ProDOS blocks
|
|
kSectorOrderDOS = 2, // written as series of DOS sectors
|
|
kSectorOrderCPM = 3, // written as series of 1K CP/M blocks
|
|
kSectorOrderPhysical = 4, // written as un-interleaved sectors
|
|
kSectorOrderMax, // (used for array sizing)
|
|
} SectorOrder;
|
|
typedef enum { // main filesystem format (based on NuFX enum)
|
|
kFormatUnknown = 0,
|
|
kFormatProDOS = 1,
|
|
kFormatDOS33 = 2,
|
|
kFormatDOS32 = 3,
|
|
kFormatPascal = 4,
|
|
kFormatMacHFS = 5,
|
|
kFormatMacMFS = 6,
|
|
kFormatLisa = 7,
|
|
kFormatCPM = 8,
|
|
//kFormatCharFST
|
|
kFormatMSDOS = 10, // any FAT filesystem
|
|
//kFormatHighSierra
|
|
kFormatISO9660 = 12,
|
|
//kFormatAppleShare
|
|
kFormatRDOS33 = 20, // 16-sector RDOS disk
|
|
kFormatRDOS32 = 21, // 13-sector RDOS disk
|
|
kFormatRDOS3 = 22, // 13-sector RDOS disk converted to 16
|
|
// "generic" formats *must* be in their own "decade"
|
|
kFormatGenericPhysicalOrd = 30, // unknown, but physical-sector-ordered
|
|
kFormatGenericProDOSOrd = 31, // unknown, but ProDOS-block-ordered
|
|
kFormatGenericDOSOrd = 32, // unknown, but DOS-sector-ordered
|
|
kFormatGenericCPMOrd = 33, // unknown, but CP/M-block-ordered
|
|
kFormatUNIDOS = 40, // two 400K DOS 3.3 volumes
|
|
kFormatOzDOS = 41, // two 400K DOS 3.3 volumes, weird order
|
|
kFormatCFFA4 = 42, // CFFA image with 4 or 6 partitions
|
|
kFormatCFFA8 = 43, // CFFA image with 8 partitions
|
|
kFormatMacPart = 44, // Macintosh-style partitioned disk
|
|
kFormatMicroDrive = 45, // ///SHH Systeme's MicroDrive format
|
|
kFormatFocusDrive = 46, // Parsons Engineering FocusDrive format
|
|
kFormatGutenberg = 47, // Gutenberg word processor format
|
|
|
|
// try to keep this in an unsigned char, e.g. for CP clipboard
|
|
} FSFormat;
|
|
|
|
/*
|
|
* Nibble encode/decode description. Use no pointers here, so we
|
|
* store as an array and resize at will.
|
|
*
|
|
* Should we define an enum to describe whether address and data
|
|
* headers are standard or some wacky variant?
|
|
*/
|
|
typedef enum {
|
|
kNibbleAddrPrologLen = 3, // d5 aa 96
|
|
kNibbleAddrEpilogLen = 3, // de aa eb
|
|
kNibbleDataPrologLen = 3, // d5 aa ad
|
|
kNibbleDataEpilogLen = 3, // de aa eb
|
|
};
|
|
typedef enum {
|
|
kNibbleEncUnknown = 0,
|
|
kNibbleEnc44,
|
|
kNibbleEnc53,
|
|
kNibbleEnc62,
|
|
} NibbleEnc;
|
|
typedef enum {
|
|
kNibbleSpecialNone = 0,
|
|
kNibbleSpecialMuse, // doubled sector numbers on tracks > 2
|
|
kNibbleSpecialSkipFirstAddrByte,
|
|
} NibbleSpecial;
|
|
typedef struct {
|
|
char description[32];
|
|
short numSectors; // 13 or 16 (or 18?)
|
|
|
|
unsigned char addrProlog[kNibbleAddrPrologLen];
|
|
unsigned char addrEpilog[kNibbleAddrEpilogLen];
|
|
unsigned char addrChecksumSeed;
|
|
bool addrVerifyChecksum;
|
|
bool addrVerifyTrack;
|
|
int addrEpilogVerifyCount;
|
|
|
|
unsigned char dataProlog[kNibbleDataPrologLen];
|
|
unsigned char dataEpilog[kNibbleDataEpilogLen];
|
|
unsigned char dataChecksumSeed;
|
|
bool dataVerifyChecksum;
|
|
int dataEpilogVerifyCount;
|
|
|
|
NibbleEnc encoding;
|
|
NibbleSpecial special;
|
|
} NibbleDescr;
|
|
|
|
|
|
static inline bool IsSectorFormat(PhysicalFormat fmt) {
|
|
return (fmt == kPhysicalFormatSectors);
|
|
}
|
|
static inline bool IsNibbleFormat(PhysicalFormat fmt) {
|
|
return (fmt == kPhysicalFormatNib525_6656 ||
|
|
fmt == kPhysicalFormatNib525_6384 ||
|
|
fmt == kPhysicalFormatNib525_Var);
|
|
}
|
|
|
|
// file is on disk; stuff like 2MG headers will be identified and stripped
|
|
DIError OpenImage(const char* filename, char fssep, bool readOnly);
|
|
// file is in memory; provide a pointer to the data start and buffer size
|
|
DIError OpenImageFromBufferRO(const uint8_t* buffer, long length);
|
|
// file is in memory; provide a pointer to the data start and buffer size
|
|
DIError OpenImageFromBufferRW(uint8_t* buffer, long length);
|
|
// file is a range of blocks on an open block-oriented disk image
|
|
DIError OpenImage(DiskImg* pParent, long firstBlock, long numBlocks);
|
|
// file is a range of tracks/sectors on an open sector-oriented disk image
|
|
DIError OpenImage(DiskImg* pParent, long firstTrack, long firstSector,
|
|
long numSectors);
|
|
|
|
// create a new, blank image file
|
|
DIError CreateImage(const char* pathName, const char* storageName,
|
|
OuterFormat outerFormat, FileFormat fileFormat,
|
|
PhysicalFormat physical, const NibbleDescr* pNibbleDescr,
|
|
SectorOrder order, FSFormat format,
|
|
long numBlocks, bool skipFormat);
|
|
DIError CreateImage(const char* pathName, const char* storageName,
|
|
OuterFormat outerFormat, FileFormat fileFormat,
|
|
PhysicalFormat physical, const NibbleDescr* pNibbleDescr,
|
|
SectorOrder order, FSFormat format,
|
|
long numTracks, long numSectPerTrack, bool skipFormat);
|
|
|
|
// flush any changes to disk; slow recompress only for "kFlushAll"
|
|
typedef enum { kFlushUnknown=0, kFlushFastOnly=1, kFlushAll=2 } FlushMode;
|
|
DIError FlushImage(FlushMode mode);
|
|
// close the image, freeing up any resources in use
|
|
DIError CloseImage(void);
|
|
// raise/lower refCnt (may want to track pointers someday)
|
|
void AddDiskFS(DiskFS* pDiskFS) { fDiskFSRefCnt++; }
|
|
void RemoveDiskFS(DiskFS* pDiskFS) {
|
|
assert(fDiskFSRefCnt > 0);
|
|
fDiskFSRefCnt--;
|
|
}
|
|
|
|
// (re-)format this image in the specified FS format
|
|
DIError FormatImage(FSFormat format, const char* volName);
|
|
// reset all blocks/sectors to zeroes
|
|
DIError ZeroImage(void);
|
|
|
|
// configure for paired sectors (OzDOS)
|
|
void SetPairedSectors(bool enable, int idx);
|
|
|
|
// identify sector ordering and disk format
|
|
// (may want a version that takes "hints" for special disks?)
|
|
DIError AnalyzeImage(void);
|
|
// figure out what FS and sector ordering is on the disk image
|
|
void AnalyzeImageFS(void);
|
|
bool ShowAsBlocks(void) const;
|
|
// overrule the analyzer (generally not recommended) -- does not
|
|
// override FileFormat, which is very reliable
|
|
DIError OverrideFormat(PhysicalFormat physical, FSFormat format,
|
|
SectorOrder order);
|
|
|
|
// Create a DiskFS that matches this DiskImg. Must be called after
|
|
// AnalayzeImage, or you will always get a DiskFSUnknown. The DiskFS
|
|
// must be freed with "delete" when no longer needed.
|
|
DiskFS* OpenAppropriateDiskFS(bool allowUnknown = false);
|
|
|
|
// Add information or a warning to the list of notes. Use linefeeds to
|
|
// indicate line breaks. This is currently append-only.
|
|
typedef enum { kNoteInfo, kNoteWarning } NoteType;
|
|
void AddNote(NoteType type, const char* fmt, ...)
|
|
#if defined(__GNUC__)
|
|
__attribute__ ((format(printf, 3, 4)))
|
|
#endif
|
|
;
|
|
const char* GetNotes(void) const;
|
|
|
|
// simple accessors
|
|
OuterFormat GetOuterFormat(void) const { return fOuterFormat; }
|
|
FileFormat GetFileFormat(void) const { return fFileFormat; }
|
|
PhysicalFormat GetPhysicalFormat(void) const { return fPhysical; }
|
|
SectorOrder GetSectorOrder(void) const { return fOrder; }
|
|
FSFormat GetFSFormat(void) const { return fFormat; }
|
|
long GetNumTracks(void) const { return fNumTracks; }
|
|
int GetNumSectPerTrack(void) const { return fNumSectPerTrack; }
|
|
long GetNumBlocks(void) const { return fNumBlocks; }
|
|
bool GetReadOnly(void) const { return fReadOnly; }
|
|
bool GetDirtyFlag(void) const { return fDirty; }
|
|
|
|
// set read-only flag; don't use this (open with correct setting;
|
|
// this was added as safety hack for the volume copier)
|
|
void SetReadOnly(bool val) { fReadOnly = val; }
|
|
|
|
// read a 256-byte sector
|
|
// NOTE to self: this function should not be available for odd-sized
|
|
// volumes, e.g. a ProDOS /RAM or /RAM5 stored with Davex. Need some way
|
|
// to communicate that to disk editor so it knows to grey-out the
|
|
// selection checkbox and/or not use "show as sectors" as default.
|
|
virtual DIError ReadTrackSector(long track, int sector, void* buf) {
|
|
return ReadTrackSectorSwapped(track, sector, buf, fOrder,
|
|
fFileSysOrder);
|
|
}
|
|
DIError ReadTrackSectorSwapped(long track, int sector,
|
|
void* buf, SectorOrder imageOrder, SectorOrder fsOrder);
|
|
// write a 256-byte sector
|
|
virtual DIError WriteTrackSector(long track, int sector, const void* buf);
|
|
|
|
// read a 512-byte block
|
|
virtual DIError ReadBlock(long block, void* buf) {
|
|
return ReadBlockSwapped(block, buf, fOrder, fFileSysOrder);
|
|
}
|
|
DIError ReadBlockSwapped(long block, void* buf, SectorOrder imageOrder,
|
|
SectorOrder fsOrder);
|
|
// read multiple blocks
|
|
virtual DIError ReadBlocks(long startBlock, int numBlocks, void* buf);
|
|
// check our virtual bad block map
|
|
bool CheckForBadBlocks(long startBlock, int numBlocks);
|
|
// write a 512-byte block
|
|
virtual DIError WriteBlock(long block, const void* buf);
|
|
// write multiple blocks
|
|
virtual DIError WriteBlocks(long startBlock, int numBlocks, const void* buf);
|
|
|
|
// read an entire nibblized track
|
|
virtual DIError ReadNibbleTrack(long track, unsigned char* buf,
|
|
long* pTrackLen);
|
|
// write a track; trackLen must be <= those in image
|
|
virtual DIError WriteNibbleTrack(long track, const unsigned char* buf,
|
|
long trackLen);
|
|
|
|
// save the current image as a 2MG file
|
|
//DIError Write2MG(const char* filename);
|
|
|
|
// need to treat the DOS volume number as meta-data for some disks
|
|
short GetDOSVolumeNum(void) const { return fDOSVolumeNum; }
|
|
void SetDOSVolumeNum(short val) { fDOSVolumeNum = val; }
|
|
enum { kVolumeNumNotSet = -1 };
|
|
|
|
// some simple getters
|
|
bool GetHasSectors(void) const { return fHasSectors; }
|
|
bool GetHasBlocks(void) const { return fHasBlocks; }
|
|
bool GetHasNibbles(void) const { return fHasNibbles; }
|
|
bool GetIsEmbedded(void) const { return fpParentImg != NULL; }
|
|
|
|
// return the current NibbleDescr
|
|
const NibbleDescr* GetNibbleDescr(void) const { return fpNibbleDescr; }
|
|
// set the NibbleDescr; we do this by copying the entry into our table
|
|
// (could improve by doing memcmp on available entries?)
|
|
void SetNibbleDescr(int idx);
|
|
void SetCustomNibbleDescr(const NibbleDescr* pDescr);
|
|
const NibbleDescr* GetNibbleDescrTable(int* pCount) const {
|
|
*pCount = fNumNibbleDescrEntries;
|
|
return fpNibbleDescrTable;
|
|
}
|
|
|
|
// set the NuFX compression type, used when compressing or re-compressing;
|
|
// must be set before image is opened or created
|
|
void SetNuFXCompressionType(int val) { fNuFXCompressType = val; }
|
|
|
|
/*
|
|
* Set up a progress callback to use when scanning a disk volume. Pass
|
|
* NULL for "func" to disable.
|
|
*
|
|
* The callback function is expected to return "true" if all is well.
|
|
* If it returns false, kDIErrCancelled will eventually come back.
|
|
*/
|
|
typedef bool (*ScanProgressCallback)(void* cookie, const char* str,
|
|
int count);
|
|
void SetScanProgressCallback(ScanProgressCallback func, void* cookie);
|
|
/* update status dialog during disk scan; called from DiskFS code */
|
|
bool UpdateScanProgress(const char* newStr);
|
|
|
|
/*
|
|
* Static utility functions.
|
|
*/
|
|
// returns "true" if the files on this image have DOS structure, i.e.
|
|
// simple file types and high-ASCII text files
|
|
static bool UsesDOSFileStructure(FSFormat format) {
|
|
return (format == kFormatDOS33 ||
|
|
format == kFormatDOS32 ||
|
|
format == kFormatGutenberg ||
|
|
format == kFormatUNIDOS ||
|
|
format == kFormatOzDOS ||
|
|
format == kFormatRDOS33 ||
|
|
format == kFormatRDOS32 ||
|
|
format == kFormatRDOS3);
|
|
}
|
|
// returns "true" if we can open files on the specified filesystem
|
|
static bool CanOpenFiles(FSFormat format) {
|
|
return (format == kFormatProDOS ||
|
|
format == kFormatDOS33 ||
|
|
format == kFormatDOS32 ||
|
|
format == kFormatPascal ||
|
|
format == kFormatCPM ||
|
|
format == kFormatRDOS33 ||
|
|
format == kFormatRDOS32 ||
|
|
format == kFormatRDOS3);
|
|
}
|
|
// returns "true" if we can create subdirectories on this filesystem
|
|
static bool IsHierarchical(FSFormat format) {
|
|
return (format == kFormatProDOS ||
|
|
format == kFormatMacHFS ||
|
|
format == kFormatMSDOS);
|
|
}
|
|
// returns "true" if we can create resource forks on this filesystem
|
|
static bool HasResourceForks(FSFormat format) {
|
|
return (format == kFormatProDOS ||
|
|
format == kFormatMacHFS);
|
|
}
|
|
// returns "true" if the format is one of the "generics"
|
|
static bool IsGenericFormat(FSFormat format) {
|
|
return (format / 10 == DiskImg::kFormatGenericDOSOrd / 10);
|
|
}
|
|
|
|
/* this must match DiskImg::kStdNibbleDescrs table */
|
|
typedef enum StdNibbleDescr {
|
|
kNibbleDescrDOS33Std = 0,
|
|
kNibbleDescrDOS33Patched,
|
|
kNibbleDescrDOS33IgnoreChecksum,
|
|
kNibbleDescrDOS32Std,
|
|
kNibbleDescrDOS32Patched,
|
|
kNibbleDescrMuse32,
|
|
kNibbleDescrRDOS33,
|
|
kNibbleDescrRDOS32,
|
|
kNibbleDescrCustom, // slot for custom entry
|
|
|
|
kNibbleDescrMAX // must be last
|
|
};
|
|
static const NibbleDescr* GetStdNibbleDescr(StdNibbleDescr idx);
|
|
// call this once, at DLL initialization time
|
|
static void CalcNibbleInvTables(void);
|
|
// calculate block number from cyl/head/sect on 3.5" disk
|
|
static int CylHeadSect35ToBlock(int cyl, int head, int sect);
|
|
// unpack nibble data from a 3.5" disk track
|
|
static DIError UnpackNibbleTrack35(const unsigned char* nibbleBuf,
|
|
long nibbleLen, unsigned char* outputBuf, int cyl, int head,
|
|
LinearBitmap* pBadBlockMap);
|
|
// compute the #of sectors per track for cylinder N (0-79)
|
|
static int SectorsPerTrack35(int cylinder);
|
|
|
|
// get the order in which we test for sector ordering
|
|
static void GetSectorOrderArray(SectorOrder* orderArray, SectorOrder first);
|
|
|
|
// utility function used by HFS filename normalizer; available to apps
|
|
static inline unsigned char MacToASCII(unsigned char uch) {
|
|
if (uch < 0x20)
|
|
return '?';
|
|
else if (uch < 0x80)
|
|
return uch;
|
|
else
|
|
return kMacHighASCII[uch - 0x80];
|
|
}
|
|
|
|
// Allow write access to physical disk 0. This is usually the boot disk,
|
|
// but with some BIOS the first IDE drive is always physical 0 even if
|
|
// you're booting from SATA. This only has meaning under Win32.
|
|
static void SetAllowWritePhys0(bool val);
|
|
|
|
/*
|
|
* Get string constants for enumerated values.
|
|
*/
|
|
typedef struct { int format; const char* str; } ToStringLookup;
|
|
static const char* ToStringCommon(int format, const ToStringLookup* pTable,
|
|
int tableSize);
|
|
static const char* ToString(OuterFormat format);
|
|
static const char* ToString(FileFormat format);
|
|
static const char* ToString(PhysicalFormat format);
|
|
static const char* ToString(SectorOrder format);
|
|
static const char* ToString(FSFormat format);
|
|
|
|
private:
|
|
/*
|
|
* Fundamental disk image identification.
|
|
*/
|
|
OuterFormat fOuterFormat; // e.g. gzip
|
|
FileFormat fFileFormat;
|
|
PhysicalFormat fPhysical;
|
|
const NibbleDescr* fpNibbleDescr; // only used for "nibble" images
|
|
SectorOrder fOrder; // only used for "sector" images
|
|
FSFormat fFormat;
|
|
|
|
/*
|
|
* This affects how the DiskImg responds to requests for reading
|
|
* a track or sector.
|
|
*
|
|
* "fFileSysOrder", together with with "fOrder", determines how
|
|
* sector numbers are translated. It describes the order that the
|
|
* DiskFS filesystem expects things to be in. If the image isn't
|
|
* sector-addressable, then it is assumed to be in linear block order.
|
|
*
|
|
* If "fSectorPairing" is set, the DiskImg treats the disk as if
|
|
* it were in OzDOS format, with one sector from two different
|
|
* volumes in a single 512-byte block.
|
|
*/
|
|
SectorOrder fFileSysOrder;
|
|
bool fSectorPairing;
|
|
int fSectorPairOffset; // which image (should be 0 or 1)
|
|
|
|
/*
|
|
* Internal state.
|
|
*/
|
|
GenericFD* fpOuterGFD; // outer wrapper, if any (.gz only)
|
|
GenericFD* fpWrapperGFD; // Apple II image file
|
|
GenericFD* fpDataGFD; // raw Apple II data
|
|
OuterWrapper* fpOuterWrapper; // needed for outer .gz wrapper
|
|
ImageWrapper* fpImageWrapper; // disk image wrapper (2MG, SHK, etc)
|
|
DiskImg* fpParentImg; // set for embedded volumes
|
|
short fDOSVolumeNum; // specified by some wrapper formats
|
|
di_off_t fOuterLength; // total len of file
|
|
di_off_t fWrappedLength; // len of file after Outer wrapper removed
|
|
di_off_t fLength; // len of disk image (w/o wrappers)
|
|
bool fExpandable; // ProDOS .hdv can expand
|
|
bool fReadOnly; // allow writes to this image?
|
|
bool fDirty; // have we modified this image?
|
|
//bool fIsEmbedded; // is this image embedded in another?
|
|
|
|
bool fHasSectors; // image is sector-addressable
|
|
bool fHasBlocks; // image is block-addressable
|
|
bool fHasNibbles; // image is nibble-addressable
|
|
|
|
long fNumTracks; // for sector-addressable images
|
|
int fNumSectPerTrack; // (ditto)
|
|
long fNumBlocks; // for 512-byte block-addressable images
|
|
|
|
unsigned char* fNibbleTrackBuf; // allocated on heap
|
|
int fNibbleTrackLoaded; // track currently in buffer
|
|
|
|
int fNuFXCompressType; // used when compressing a NuFX image
|
|
|
|
char* fNotes; // warnings and FYIs about DiskImg/DiskFS
|
|
|
|
LinearBitmap* fpBadBlockMap; // used for 3.5" nibble images
|
|
|
|
int fDiskFSRefCnt; // #of DiskFS objects pointing at us
|
|
|
|
/*
|
|
* NibbleDescr entries. There are several standard ones, and we want
|
|
* to allow applications to define additional ones.
|
|
*/
|
|
NibbleDescr* fpNibbleDescrTable;
|
|
int fNumNibbleDescrEntries;
|
|
|
|
/* static table of default values */
|
|
static const NibbleDescr kStdNibbleDescrs[];
|
|
|
|
DIError OpenImageFromBuffer(uint8_t* buffer, long length, bool readOnly);
|
|
DIError CreateImageCommon(const char* pathName, const char* storageName,
|
|
bool skipFormat);
|
|
DIError ValidateCreateFormat(void) const;
|
|
DIError FormatSectors(GenericFD* pGFD, bool quickFormat) const;
|
|
//DIError FormatBlocks(GenericFD* pGFD) const;
|
|
|
|
DIError CopyBytesOut(void* buf, di_off_t offset, int size) const;
|
|
DIError CopyBytesIn(const void* buf, di_off_t offset, int size);
|
|
DIError AnalyzeImageFile(const char* pathName, char fssep);
|
|
// Figure out the sector ordering for this filesystem, so we can decide
|
|
// how the sectors need to be re-arranged when we're reading them.
|
|
SectorOrder CalcFSSectorOrder(void) const;
|
|
// Handle sector order calculations.
|
|
DIError CalcSectorAndOffset(long track, int sector, SectorOrder ImageOrder,
|
|
SectorOrder fsOrder, di_off_t* pOffset, int* pNewSector);
|
|
inline bool IsLinearBlocks(SectorOrder imageOrder, SectorOrder fsOrder);
|
|
|
|
/*
|
|
* Progress update during the filesystem scan. This only exists in the
|
|
* topmost DiskImg. (This is arguably more appropriate in DiskFS, but
|
|
* DiskFS objects don't have a notion of "parent" and are somewhat more
|
|
* ephemeral.)
|
|
*/
|
|
ScanProgressCallback fpScanProgressCallback;
|
|
void* fScanProgressCookie;
|
|
int fScanCount;
|
|
char fScanMsg[128];
|
|
time_t fScanLastMsgWhen;
|
|
|
|
/*
|
|
* 5.25" nibble image access.
|
|
*/
|
|
enum {
|
|
kDataSize62 = 343, // 342 bytes + checksum byte
|
|
kChunkSize62 = 86, // (0x56)
|
|
|
|
kDataSize53 = 411, // 410 bytes + checksum byte
|
|
kChunkSize53 = 51, // (0x33)
|
|
kThreeSize = (kChunkSize53 * 3) + 1, // same as 410 - 256
|
|
};
|
|
DIError ReadNibbleSector(long track, int sector, void* buf,
|
|
const NibbleDescr* pNibbleDescr);
|
|
DIError WriteNibbleSector(long track, int sector, const void* buf,
|
|
const NibbleDescr* pNibbleDescr);
|
|
void DumpNibbleDescr(const NibbleDescr* pNibDescr) const;
|
|
int GetNibbleTrackLength(long track) const;
|
|
int GetNibbleTrackOffset(long track) const;
|
|
int GetNibbleTrackFormatLength(void) const {
|
|
/* return length to use when formatting for 16 sectors */
|
|
if (fPhysical == kPhysicalFormatNib525_6656)
|
|
return kTrackLenNib525;
|
|
else if (fPhysical == kPhysicalFormatNib525_6384)
|
|
return kTrackLenNb2525;
|
|
else if (fPhysical == kPhysicalFormatNib525_Var) {
|
|
if (fFileFormat == kFileFormatTrackStar ||
|
|
fFileFormat == kFileFormatFDI)
|
|
{
|
|
return kTrackLenNb2525; // use minimum possible
|
|
}
|
|
}
|
|
assert(false);
|
|
return -1;
|
|
}
|
|
int GetNibbleTrackAllocLength(void) const {
|
|
/* return length to allocate when creating an image */
|
|
if (fPhysical == kPhysicalFormatNib525_Var &&
|
|
(fFileFormat == kFileFormatTrackStar ||
|
|
fFileFormat == kFileFormatFDI))
|
|
{
|
|
// use maximum possible
|
|
return kTrackLenTrackStar525;
|
|
}
|
|
return GetNibbleTrackFormatLength();
|
|
}
|
|
DIError LoadNibbleTrack(long track, long* pTrackLen);
|
|
DIError SaveNibbleTrack(void);
|
|
int FindNibbleSectorStart(const CircularBufferAccess& buffer,
|
|
int track, int sector, const NibbleDescr* pNibbleDescr, int* pVol);
|
|
void DecodeAddr(const CircularBufferAccess& buffer, int offset,
|
|
short* pVol, short* pTrack, short* pSector, short* pChksum);
|
|
inline unsigned int ConvFrom44(unsigned char val1, unsigned char val2) {
|
|
return ((val1 << 1) | 0x01) & val2;
|
|
}
|
|
DIError DecodeNibbleData(const CircularBufferAccess& buffer, int idx,
|
|
unsigned char* sctBuf, const NibbleDescr* pNibbleDescr);
|
|
void EncodeNibbleData(const CircularBufferAccess& buffer, int idx,
|
|
const unsigned char* sctBuf, const NibbleDescr* pNibbleDescr) const;
|
|
DIError DecodeNibble62(const CircularBufferAccess& buffer, int idx,
|
|
unsigned char* sctBuf, const NibbleDescr* pNibbleDescr);
|
|
void EncodeNibble62(const CircularBufferAccess& buffer, int idx,
|
|
const unsigned char* sctBuf, const NibbleDescr* pNibbleDescr) const;
|
|
DIError DecodeNibble53(const CircularBufferAccess& buffer, int idx,
|
|
unsigned char* sctBuf, const NibbleDescr* pNibbleDescr);
|
|
void EncodeNibble53(const CircularBufferAccess& buffer, int idx,
|
|
const unsigned char* sctBuf, const NibbleDescr* pNibbleDescr) const;
|
|
int TestNibbleTrack(int track, const NibbleDescr* pNibbleDescr, int* pVol);
|
|
DIError AnalyzeNibbleData(void);
|
|
inline unsigned char Conv44(unsigned short val, bool first) const {
|
|
if (first)
|
|
return (val >> 1) | 0xaa;
|
|
else
|
|
return val | 0xaa;
|
|
}
|
|
DIError FormatNibbles(GenericFD* pGFD) const;
|
|
|
|
static const unsigned char kMacHighASCII[];
|
|
|
|
/*
|
|
* 3.5" nibble access
|
|
*/
|
|
static int FindNextSector35(const CircularBufferAccess& buffer, int start,
|
|
int cyl, int head, int* pSector);
|
|
static bool DecodeNibbleSector35(const CircularBufferAccess& buffer,
|
|
int start, unsigned char* sectorBuf, unsigned char* readChecksum,
|
|
unsigned char* calcChecksum);
|
|
static bool UnpackChecksum35(const CircularBufferAccess& buffer,
|
|
int offset, unsigned char* checksumBuf);
|
|
static void EncodeNibbleSector35(const unsigned char* sectorData,
|
|
unsigned char* outBuf);
|
|
|
|
/* static data tables */
|
|
static unsigned char kDiskBytes53[32];
|
|
static unsigned char kDiskBytes62[64];
|
|
static unsigned char kInvDiskBytes53[256];
|
|
static unsigned char kInvDiskBytes62[256];
|
|
enum { kInvInvalidValue = 0xff };
|
|
|
|
private: // some C++ stuff to block behavior we don't support
|
|
DiskImg& operator=(const DiskImg&);
|
|
DiskImg(const DiskImg&);
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
* Disk filesystem class, roughly equivalent to a GS/OS FST. This is an
|
|
* abstract base class.
|
|
*
|
|
* Static functions know how to access a DiskImg and figure out what kind
|
|
* of image we have. Once known, the appropriate sub-class can be
|
|
* instantiated.
|
|
*
|
|
* We maintain a linear list of files to make it easy for applications to
|
|
* traverse the full set of files. Sometimes this makes it hard for us to
|
|
* update internally (especially HFS). With some minor cleverness it
|
|
* should be possible to switch to a tree structure while retaining the
|
|
* linear "get next" API. This would be a big help for ProDOS and HFS.
|
|
*
|
|
* NEED: some notification mechanism for changes to files and/or block
|
|
* editing of the disk (especially with regard to open sub-volumes). If
|
|
* a disk volume open for file-by-file viewing is modified with the disk
|
|
* editor, we should close the file when the disk editor exits.
|
|
*
|
|
* NEED? disk utilities, such as "disk crunch", for Pascal volumes. Could
|
|
* be written externally, but might as well keep fs knowledge embedded.
|
|
*
|
|
* MISSING: there is no way to override the image analyzer when working
|
|
* with sub-volumes. Actually, there is; it just has to happen *after*
|
|
* the DiskFS has been created. We should provide an approach that either
|
|
* stifles the DiskFS creation, or allows us to override and replace the
|
|
* internal DiskFS so we can pop up a sub-volume list, show what we *think*
|
|
* is there, and then let the user pick a volume and pick overrides (mainly
|
|
* for use in the disk editor). Not sure if we want the changes to "stick";
|
|
* we probably do. Q: does the "scan for sub-volumes" attribute propagate
|
|
* recursively to each sub-sub-volume? Probably.
|
|
*
|
|
* NOTE to self: should make "test" functions more lax when called
|
|
* from here, on the assumption that the caller is knowledgeable. Perhaps
|
|
* an independent "strictness" variable that can be cranked down through
|
|
* multiple calls to AnalyzeImage??
|
|
*/
|
|
class DISKIMG_API DiskFS {
|
|
public:
|
|
/*
|
|
* Information about volume usage.
|
|
*
|
|
* Each "chunk" is a track/sector on a DOS disk or a block on a ProDOS
|
|
* or Pascal disk. CP/M really ought to use 1K blocks, but for
|
|
* convenience we're just using 512-byte blocks (it's up to the CP/M
|
|
* code to set two "chunks" per block).
|
|
*
|
|
* NOTE: the current DOS/ProDOS/Pascal code is sloppy when it comes to
|
|
* keeping this structure up to date. HFS doesn't use it at all. This
|
|
* has always been a low-priority feature.
|
|
*/
|
|
class DISKIMG_API VolumeUsage {
|
|
public:
|
|
VolumeUsage(void) {
|
|
fByBlocks = false;
|
|
fTotalChunks = -1;
|
|
fNumSectors = -1;
|
|
//fFreeChunks = -1;
|
|
fList = NULL;
|
|
fListSize = -1;
|
|
}
|
|
~VolumeUsage(void) {
|
|
delete[] fList;
|
|
}
|
|
|
|
/*
|
|
* These values MUST fit in five bits.
|
|
*
|
|
* Suggested disk map colors:
|
|
* 0 = unknown (color-key pink)
|
|
* 1 = conflict (medium-red)
|
|
* 2 = boot loader (dark-blue)
|
|
* 3 = volume dir (light-blue)
|
|
* 4 = subdir (medium-blue)
|
|
* 5 = user data (medium-green)
|
|
* 6 = user index blocks (light-green)
|
|
* 7 = embedded filesys (yellow)
|
|
*
|
|
* THOUGHT: Add flag for I/O error (nibble images) -- requires
|
|
* automatic disk verify pass. (Or maybe could be done manually
|
|
* on request?)
|
|
*
|
|
* unused --> black
|
|
* marked-used-but-not-used --> dark-red
|
|
* used-but-not-marked-used --> orange
|
|
*/
|
|
typedef enum {
|
|
kChunkPurposeUnknown = 0,
|
|
kChunkPurposeConflict = 1, // two or more different things
|
|
kChunkPurposeSystem = 2, // boot blocks, volume bitmap
|
|
kChunkPurposeVolumeDir = 3, // volume dir (or only dir)
|
|
kChunkPurposeSubdir = 4, // ProDOS sub-directory
|
|
kChunkPurposeUserData = 5, // file on this filesystem
|
|
kChunkPurposeFileStruct = 6, // index blocks, T/S lists
|
|
kChunkPurposeEmbedded = 7, // embedded filesystem
|
|
// how about: outside range claimed by disk, e.g. fTotalBlocks on
|
|
// 800K ProDOS disk in a 32MB CFFA volume?
|
|
} ChunkPurpose;
|
|
|
|
typedef struct ChunkState {
|
|
bool isUsed;
|
|
bool isMarkedUsed;
|
|
ChunkPurpose purpose; // only valid if isUsed is set
|
|
} ChunkState;
|
|
|
|
// initialize, configuring for either blocks or sectors
|
|
DIError Create(long numBlocks);
|
|
DIError Create(long numTracks, long numSectors);
|
|
bool GetInitialized(void) const { return (fList != NULL); }
|
|
|
|
// return the number of chunks on this disk
|
|
long GetNumChunks(void) const { return fTotalChunks; }
|
|
|
|
// return the number of unallocated chunks, taking into account
|
|
// both the free-chunk bitmap (if any) and the actual usage
|
|
long GetActualFreeChunks(void) const;
|
|
|
|
// return the state of the specified chunk
|
|
DIError GetChunkState(long block, ChunkState* pState) const;
|
|
DIError GetChunkState(long track, long sector,
|
|
ChunkState* pState) const;
|
|
|
|
// set the state of a particular chunk (should only be done by
|
|
// the DiskFS sub-classes)
|
|
DIError SetChunkState(long block, const ChunkState* pState);
|
|
DIError SetChunkState(long track, long sector,
|
|
const ChunkState* pState);
|
|
|
|
void Dump(void) const; // debugging
|
|
|
|
private:
|
|
DIError GetChunkStateIdx(int idx, ChunkState* pState) const;
|
|
DIError SetChunkStateIdx(int idx, const ChunkState* pState);
|
|
inline char StateToChar(ChunkState* pState) const;
|
|
|
|
/*
|
|
* Chunk state is stored as a set of bits in one byte:
|
|
*
|
|
* 0-4: how is block used (only has meaning if bit 6 is set)
|
|
* 5: for nibble images, indicates the block or sector is unreadable
|
|
* 6: is block used by something (0=no, 1=yes)
|
|
* 7: is block marked "in use" by system map (0=no, 1=yes)
|
|
*
|
|
* [ Consider reducing "purpose" to 0-3 and adding bad block bit for
|
|
* nibble images and physical media.]
|
|
*/
|
|
enum {
|
|
kChunkPurposeMask = 0x1f, // ChunkPurpose enum
|
|
kChunkDamagedFlag = 0x20,
|
|
kChunkUsedFlag = 0x40,
|
|
kChunkMarkedUsedFlag = 0x80,
|
|
};
|
|
|
|
bool fByBlocks;
|
|
long fTotalChunks;
|
|
long fNumSectors; // only valid if !fByBlocks
|
|
//long fFreeChunks;
|
|
unsigned char* fList;
|
|
int fListSize;
|
|
}; // end of VolumeUsage class
|
|
|
|
/*
|
|
* List of sub-volumes. The SubVolume owns the DiskImg and DiskFS
|
|
* that are handed to it, so they can be deleted when the SubVolume
|
|
* is deleted as part of destroying the parent.
|
|
*/
|
|
class SubVolume {
|
|
public:
|
|
SubVolume(void) : fpDiskImg(NULL), fpDiskFS(NULL),
|
|
fpPrev(NULL), fpNext(NULL) {}
|
|
~SubVolume(void) {
|
|
delete fpDiskFS; // must close first; may need flush to DiskImg
|
|
delete fpDiskImg;
|
|
}
|
|
|
|
void Create(DiskImg* pDiskImg, DiskFS* pDiskFS) {
|
|
assert(pDiskImg != NULL);
|
|
assert(pDiskFS != NULL);
|
|
fpDiskImg = pDiskImg;
|
|
fpDiskFS = pDiskFS;
|
|
}
|
|
|
|
DiskImg* GetDiskImg(void) const { return fpDiskImg; }
|
|
DiskFS* GetDiskFS(void) const { return fpDiskFS; }
|
|
|
|
SubVolume* GetPrev(void) const { return fpPrev; }
|
|
void SetPrev(SubVolume* pSubVol) { fpPrev = pSubVol; }
|
|
SubVolume* GetNext(void) const { return fpNext; }
|
|
void SetNext(SubVolume* pSubVol) { fpNext = pSubVol; }
|
|
|
|
private:
|
|
DiskImg* fpDiskImg;
|
|
DiskFS* fpDiskFS;
|
|
|
|
SubVolume* fpPrev;
|
|
SubVolume* fpNext;
|
|
}; // end of SubVolume class
|
|
|
|
|
|
|
|
/*
|
|
* Start of DiskFS declarations.
|
|
*/
|
|
public:
|
|
typedef enum SubScanMode {
|
|
kScanSubUnknown = 0,
|
|
kScanSubDisabled,
|
|
kScanSubEnabled,
|
|
kScanSubContainerOnly,
|
|
} SubScanMode;
|
|
|
|
|
|
DiskFS(void) {
|
|
fpA2Head = fpA2Tail = NULL;
|
|
fpSubVolumeHead = fpSubVolumeTail = NULL;
|
|
fpImg = NULL;
|
|
fScanForSubVolumes = kScanSubDisabled;
|
|
|
|
fParmTable[kParm_CreateUnique] = 0;
|
|
fParmTable[kParmProDOS_AllowLowerCase] = 1;
|
|
fParmTable[kParmProDOS_AllocSparse] = 1;
|
|
}
|
|
virtual ~DiskFS(void) {
|
|
DeleteSubVolumeList();
|
|
DeleteFileList();
|
|
SetDiskImg(NULL);
|
|
}
|
|
|
|
/*
|
|
* Static FSFormat-analysis functions, called by the DiskImg AnalyzeImage
|
|
* function. Capable of determining with a high degree of accuracy
|
|
* what format the disk is in, yet remaining flexible enough to
|
|
* function correctly with variations (like DOS3.3 disks with
|
|
* truncated TOCs and ProDOS images from hard drives).
|
|
*
|
|
* The "pOrder" and "pFormat" arguments are in/out. Set them to the
|
|
* appropriate "unknown" enum value on entry. If something is known
|
|
* of the order or format, put that in instead; in some cases this will
|
|
* bias the proceedings, which is useful for efficiency and for making
|
|
* overrides work correctly.
|
|
*
|
|
* On success, these return kDIErrNone and set "*pOrder". On failure,
|
|
* they return nonzero and leave "*pOrder" unmodified.
|
|
*
|
|
* Each DiskFS sub-class should declare a TestFS function. It's not
|
|
* virtual void here because, since it's called before the DiskFS is
|
|
* created, it must be static.
|
|
*/
|
|
typedef enum FSLeniency { kLeniencyNot, kLeniencyVery } FSLeniency;
|
|
//static DIError TestFS(const DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
|
// DiskImg::FSFormat* pFormat, FSLeniency leniency);
|
|
|
|
/*
|
|
* Load the disk contents and (if enabled) scan for sub-volumes.
|
|
*
|
|
* If "headerOnly" is set, we just do a quick scan of the volume header
|
|
* to get basic information. The deep file scan is skipped (but can
|
|
* be done later). (Sub-classes can choose to ignore the flag and
|
|
* always do the full scan; this is an optimization.) Guaranteed to
|
|
* set the volume name and volume block/sector count.
|
|
*
|
|
* If a progress callback is set up, this can return with a "cancelled"
|
|
* result, which should not be treated as a failure.
|
|
*/
|
|
typedef enum { kInitUnknown = 0, kInitHeaderOnly, kInitFull } InitMode;
|
|
virtual DIError Initialize(DiskImg* pImg, InitMode initMode) = 0;
|
|
|
|
/*
|
|
* Format the disk with the appropriate filesystem, creating all filesystem
|
|
* structures and (when appropriate) boot blocks.
|
|
*/
|
|
virtual DIError Format(DiskImg* pDiskImg, const char* volName)
|
|
{ return kDIErrNotSupported; }
|
|
|
|
/*
|
|
* Pass in a full path to normalize, and a buffer to copy the output
|
|
* into. On entry "pNormalizedBufLen" holds the length of the buffer.
|
|
* On exit, it holds the size of the buffer required to hold the
|
|
* normalized string. If the buffer is NULL or isn't big enough, no part
|
|
* of the normalized path will be copied into the buffer, and a specific
|
|
* error (kDIErrDataOverrun) will be returned.
|
|
*/
|
|
virtual DIError NormalizePath(const char* path, char fssep,
|
|
char* normalizedBuf, int* pNormalizedBufLen)
|
|
{ return kDIErrNotSupported; }
|
|
|
|
|
|
/*
|
|
* Create a file. The CreateParms struct specifies the full set of file
|
|
* details. To remain FS-agnostic, use the NufxLib constants
|
|
* (kNuStorageDirectory, kNuAccessUnlocked, etc). They match up with
|
|
* their ProDOS equivalents, and I promise to make them work right.
|
|
*
|
|
* On success, the file exists as a fully-formed, zero-length file. A
|
|
* pointer to the relevant A2File structure is returned.
|
|
*/
|
|
enum {
|
|
/* valid values for CreateParms; must match ProDOS enum */
|
|
kStorageSeedling = 1,
|
|
kStorageExtended = 5,
|
|
kStorageDirectory = 13,
|
|
};
|
|
typedef struct CreateParms {
|
|
const char* pathName; // full pathname for file on disk image
|
|
char fssep;
|
|
int storageType; // determines normal, subdir, or forked
|
|
long fileType;
|
|
long auxType;
|
|
int access;
|
|
time_t createWhen;
|
|
time_t modWhen;
|
|
} CreateParms;
|
|
virtual DIError CreateFile(const CreateParms* pParms, A2File** ppNewFile)
|
|
{ return kDIErrNotSupported; }
|
|
|
|
/*
|
|
* Delete a file from the disk.
|
|
*/
|
|
virtual DIError DeleteFile(A2File* pFile)
|
|
{ return kDIErrNotSupported; }
|
|
|
|
/*
|
|
* Rename a file.
|
|
*/
|
|
virtual DIError RenameFile(A2File* pFile, const char* newName)
|
|
{ return kDIErrNotSupported; }
|
|
|
|
/*
|
|
* Alter file attributes.
|
|
*/
|
|
virtual DIError SetFileInfo(A2File* pFile, long fileType, long auxType,
|
|
long accessFlags)
|
|
{ return kDIErrNotSupported; }
|
|
|
|
/*
|
|
* Rename a volume. Also works for changing the disk volume number.
|
|
*/
|
|
virtual DIError RenameVolume(const char* newName)
|
|
{ return kDIErrNotSupported; }
|
|
|
|
|
|
// Accessor
|
|
DiskImg* GetDiskImg(void) const { return fpImg; }
|
|
|
|
// Test file and volume names (and volume numbers)
|
|
// [these need to be static functions for some things... hmm]
|
|
//virtual bool IsValidFileName(const char* name) const { return false; }
|
|
//virtual bool IsValidVolumeName(const char* name) const { return false; }
|
|
|
|
// Return the disk volume name, or NULL if there isn't one.
|
|
virtual const char* GetVolumeName(void) const = 0;
|
|
|
|
// Return a printable string identifying the FS type and volume
|
|
virtual const char* GetVolumeID(void) const = 0;
|
|
|
|
// Return the "bare" volume name. For formats where the volume name
|
|
// is actually a number (e.g. DOS 3.3), this returns just the number.
|
|
// For formats without a volume name or number (e.g. CP/M), this returns
|
|
// NULL, indicating that any attempt to change the volume name will fail.
|
|
virtual const char* GetBareVolumeName(void) const = 0;
|
|
|
|
// Returns "false" if we only support read-only access to this FS type
|
|
virtual bool GetReadWriteSupported(void) const = 0;
|
|
|
|
// Returns "true" if the filesystem shows evidence of damage.
|
|
virtual bool GetFSDamaged(void) const = 0;
|
|
|
|
// Returns number of blocks recognized by the filesystem, or -1 if the
|
|
// FS isn't block-oriented (e.g. DOS 3.2/3.3)
|
|
virtual long GetFSNumBlocks(void) const { return -1; }
|
|
|
|
// Get the next file in the list. Start by passing in NULL to get the
|
|
// head of the list. Returns NULL when the end of the list is reached.
|
|
A2File* GetNextFile(A2File* pCurrent) const;
|
|
|
|
// Get a count of the files and directories on this disk.
|
|
long GetFileCount(void) const;
|
|
|
|
/*
|
|
* Find a file by case-insensitive pathname. Assumes fssep=':'. The
|
|
* compare function can be overridden for systems like HFS, where "case
|
|
* insensitive" has a different meaning because of the native
|
|
* character set.
|
|
*
|
|
* The A2File* returned should not be deleted.
|
|
*/
|
|
typedef int (*StringCompareFunc)(const char* str1, const char* str2);
|
|
A2File* GetFileByName(const char* pathName, StringCompareFunc func = NULL);
|
|
|
|
// This controls scanning for sub-volumes; must be set before Initialize().
|
|
SubScanMode GetScanForSubVolumes(void) const { return fScanForSubVolumes; }
|
|
void SetScanForSubVolumes(SubScanMode val) { fScanForSubVolumes = val; }
|
|
|
|
// some constants for non-ProDOS filesystems to use
|
|
enum { kFileAccessLocked = 0x01, kFileAccessUnlocked = 0xc3 };
|
|
|
|
|
|
/*
|
|
* We use this as a filename separator character (i.e. between pathname
|
|
* components) in all filenames. It's useful to standardize on this
|
|
* so that behavior is consistent across all disk and archive formats.
|
|
*
|
|
* The choice of ':' is good because it's invalid in filenames on
|
|
* Windows, Mac OS, GS/OS, and pretty much anywhere else we could be
|
|
* running except for UNIX. It's valid under DOS 3.3, but since you
|
|
* can't have subdirectories under DOS there's no risk of confusion.
|
|
*/
|
|
enum { kDIFssep = ':' };
|
|
|
|
|
|
/*
|
|
* Return the volume use map. This is a non-const function because
|
|
* it might need to do a "just-in-time" usage map update. It returns
|
|
* const to keep non-DiskFS classes from altering the map.
|
|
*/
|
|
const VolumeUsage* GetVolumeUsageMap(void) {
|
|
if (fVolumeUsage.GetInitialized())
|
|
return &fVolumeUsage;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Return the total space and free space, in either blocks or sectors
|
|
* as appropriate. "*pUnitSize" will be kBlockSize or kSectorSize.
|
|
*/
|
|
virtual DIError GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits,
|
|
int* pUnitSize) const = 0;
|
|
|
|
|
|
/*
|
|
* Get the next volume in the list. Start by passing in NULL to get the
|
|
* head of the list. Returns NULL when the end of the list is reached.
|
|
*/
|
|
SubVolume* GetNextSubVolume(const SubVolume* pCurrent) const;
|
|
|
|
/*
|
|
* Set some parameters to tell the DiskFS how to operate. Some of
|
|
* these are FS-specific, some may be general.
|
|
*
|
|
* Parameters are set in the current object and all sub-volume objects.
|
|
*
|
|
* The enum is part of the interface and must be rigidly defined, but
|
|
* it is also used to size an array so it can't be too sparse.
|
|
*/
|
|
typedef enum DiskFSParameter {
|
|
kParmUnknown = 0,
|
|
|
|
kParm_CreateUnique = 1, // make new filenames unique
|
|
|
|
kParmProDOS_AllowLowerCase = 10, // allow lower case and spaces
|
|
kParmProDOS_AllocSparse = 11, // don't store empty blocks
|
|
|
|
kParmMax // must be last entry
|
|
} DiskFSParameter;
|
|
long GetParameter(DiskFSParameter parm);
|
|
void SetParameter(DiskFSParameter parm, long val);
|
|
|
|
/*
|
|
* Flush changed data.
|
|
*
|
|
* The individual filesystems shouldn't generally do any caching; if
|
|
* they do, we would want a virtual "FlushFS()" that gets called by
|
|
* Flush. The better answer is to cache in DiskImg, which works for
|
|
* everything, and knows if the underlying storage is already in RAM.
|
|
*
|
|
* For the most part this just needs to recursively flush the DiskImg
|
|
* objects in all of the sub-volumes and then the current volume. This
|
|
* is a no-op in most cases, but if the archive is compressed this will
|
|
* cause a new, compressed archive to be created.
|
|
*
|
|
* The "mode" value determines whether or not we do "heavy" flushes. It's
|
|
* very handy to be able to do "slow" flushes for anything that is being
|
|
* written directly to disk (as opposed to being run through Deflate),
|
|
* so that the UI doesn't indicate that they're partially written when
|
|
* in fact they're fully written.
|
|
*/
|
|
DIError Flush(DiskImg::FlushMode mode);
|
|
|
|
/*
|
|
* Set the read-only flag on our DiskImg and those of our subvolumes.
|
|
* Used to ensure that a DiskFS with un-flushed data can be deleted
|
|
* without corrupting the volume.
|
|
*/
|
|
void SetAllReadOnly(bool val);
|
|
|
|
// debug dump
|
|
void DumpFileList(void);
|
|
|
|
protected:
|
|
/*
|
|
* Set the DiskImg pointer. Updates the reference count in DiskImg.
|
|
*/
|
|
void SetDiskImg(DiskImg* pImg);
|
|
|
|
// once added, we own the pDiskImg and the pDiskFS (DO NOT pass the
|
|
// same DiskImg or DiskFS in more than once!). Note this copies the
|
|
// fParmTable and other stuff (fScanForSubVolumes) from parent to child.
|
|
void AddSubVolumeToList(DiskImg* pDiskImg, DiskFS* pDiskFS);
|
|
// add files to fpA2Head/fpA2Tail
|
|
void AddFileToList(A2File* pFile);
|
|
// only need for hierarchical filesystems; insert file after pPrev
|
|
void InsertFileInList(A2File* pFile, A2File* pPrev);
|
|
// delete an entry
|
|
void DeleteFileFromList(A2File* pFile);
|
|
|
|
// scan for damaged or suspicious files
|
|
void ScanForDamagedFiles(bool* pDamaged, bool* pSuspicious);
|
|
|
|
// pointer to the DiskImg structure underlying this filesystem
|
|
DiskImg* fpImg;
|
|
|
|
VolumeUsage fVolumeUsage;
|
|
SubScanMode fScanForSubVolumes;
|
|
|
|
|
|
private:
|
|
A2File* SkipSubdir(A2File* pSubdir);
|
|
void CopyInheritables(DiskFS* pNewFS);
|
|
void DeleteFileList(void);
|
|
void DeleteSubVolumeList(void);
|
|
|
|
long fParmTable[kParmMax]; // for DiskFSParameter
|
|
|
|
A2File* fpA2Head;
|
|
A2File* fpA2Tail;
|
|
SubVolume* fpSubVolumeHead;
|
|
SubVolume* fpSubVolumeTail;
|
|
|
|
private:
|
|
DiskFS& operator=(const DiskFS&);
|
|
DiskFS(const DiskFS&);
|
|
};
|
|
|
|
|
|
/*
|
|
* Apple II file class, representing a file on an Apple II volume. This is an
|
|
* abstract base class.
|
|
*
|
|
* There is a different sub-class for each filesystem type. The A2File object
|
|
* encapsulates all of the knowledge required to read a file from a disk
|
|
* image.
|
|
*
|
|
* The prev/next pointers, used to maintain a linked list of files, are only
|
|
* accessible from DiskFS functions. At some point we may want to rearrange
|
|
* the way this is handled, e.g. by not maintaining a list at all, so it's
|
|
* important that everything go through DiskFS requests.
|
|
*
|
|
* The FSFormat is made an explicit member, because sub-classes may not
|
|
* understand exactly where the file came from (e.g. was it DOS3.2 or
|
|
* DOS 3.3). Somebody might care.
|
|
*
|
|
*
|
|
* NOTE TO SELF: open files need to be generalized. Right now the internal
|
|
* implementations only allow a single open, which is okay for our purposes
|
|
* but bad for a general FS implementation. As it stands, you can't even
|
|
* open both forks at the same time on ProDOS/HFS.
|
|
*
|
|
* UMMM: The handling of "damaged" files could be more consistent.
|
|
*/
|
|
class DISKIMG_API A2File {
|
|
public:
|
|
friend class DiskFS;
|
|
|
|
A2File(DiskFS* pDiskFS) : fpDiskFS(pDiskFS) {
|
|
fpPrev = fpNext = NULL;
|
|
fFileQuality = kQualityGood;
|
|
}
|
|
virtual ~A2File(void) {}
|
|
|
|
/*
|
|
* All Apple II files have certain characteristics, of which ProDOS
|
|
* is roughly a superset. (Yes, you can have HFS on a IIgs, but
|
|
* all that fancy creator type stuff is decidedly Mac-centric. Still,
|
|
* we want to assume 4-byte file and aux types.)
|
|
*
|
|
* NEED: something distinguishing between files and disk images?
|
|
*
|
|
* NOTE: there is no guarantee that GetPathName will return unique values
|
|
* (duplicates are possible). We don't guarantee that you won't get an
|
|
* empty string back (it's valid to have an empty filename in the dir in
|
|
* DOS 3.3, and it's possible for other filesystems to be damaged). The
|
|
* pathname may receive some minor sanitizing, e.g. removal or conversion
|
|
* of high ASCII and control characters, but some filesystems (like HFS)
|
|
* make use of them.
|
|
*
|
|
* We do guarantee that the contents of subdirectories are grouped
|
|
* together. This makes it much easier to construct a hierarchy out of
|
|
* the linear list. This becomes important when dynamically adding
|
|
* files to a disk.
|
|
*/
|
|
virtual const char* GetFileName(void) const = 0; // name of this file
|
|
virtual const char* GetPathName(void) const = 0; // full path
|
|
virtual char GetFssep(void) const = 0; // '\0' if none
|
|
virtual long GetFileType(void) const = 0;
|
|
virtual long GetAuxType(void) const = 0;
|
|
virtual long GetAccess(void) const = 0; // ProDOS-style perms
|
|
virtual time_t GetCreateWhen(void) const = 0;
|
|
virtual time_t GetModWhen(void) const = 0;
|
|
virtual di_off_t GetDataLength(void) const = 0; // len of data fork
|
|
virtual di_off_t GetDataSparseLength(void) const = 0; // len w/o sparse areas
|
|
virtual di_off_t GetRsrcLength(void) const = 0; // len or -1 if no rsrc
|
|
virtual di_off_t GetRsrcSparseLength(void) const = 0; // len or -1 if no rsrc
|
|
virtual DiskImg::FSFormat GetFSFormat(void) const {
|
|
return fpDiskFS->GetDiskImg()->GetFSFormat();
|
|
}
|
|
virtual bool IsDirectory(void) const { return false; }
|
|
virtual bool IsVolumeDirectory(void) const { return false; }
|
|
|
|
/*
|
|
* Open a file. Treat the A2FileDescr like an fd.
|
|
*/
|
|
virtual DIError Open(A2FileDescr** ppOpenFile, bool readOnly,
|
|
bool rsrcFork = false) = 0;
|
|
|
|
/*
|
|
* This is called by the A2FileDescr object when somebody invokes Close().
|
|
* The A2File object should remove the A2FileDescr from its list of open
|
|
* files and delete the storage. The implementation must not call the
|
|
* A2FileDescr's Close function, since that would cause a recursive loop.
|
|
*
|
|
* This should not be called by an application.
|
|
*/
|
|
virtual void CloseDescr(A2FileDescr* pOpenFile) = 0;
|
|
|
|
/*
|
|
* This is only useful for hierarchical filesystems like ProDOS,
|
|
* where the order of items in the linear list is significant. It
|
|
* allows an unambiguous determination of which subdir a file resides
|
|
* in, even if somebody has sector-edited the filesystem so that two
|
|
* subdirs have the same name. (It's also a bit speedier to compare
|
|
* than pathname substrings would be.)
|
|
*/
|
|
virtual A2File* GetParent(void) const { return NULL; }
|
|
|
|
/*
|
|
* Returns "true" if either fork of the file is open, "false" if not.
|
|
*/
|
|
virtual bool IsFileOpen(void) const = 0;
|
|
|
|
virtual void Dump(void) const = 0; // debugging
|
|
|
|
typedef enum FileQuality {
|
|
kQualityUnknown = 0,
|
|
kQualityGood,
|
|
kQualitySuspicious,
|
|
kQualityDamaged,
|
|
} FileQuality;
|
|
virtual FileQuality GetQuality(void) const { return fFileQuality; }
|
|
virtual void SetQuality(FileQuality quality);
|
|
virtual void ResetQuality(void);
|
|
|
|
DiskFS* GetDiskFS(void) const { return fpDiskFS; }
|
|
|
|
protected:
|
|
DiskFS* fpDiskFS;
|
|
virtual void SetParent(A2File* pParent) { /* do nothing */ }
|
|
|
|
private:
|
|
A2File* GetPrev(void) const { return fpPrev; }
|
|
void SetPrev(A2File* pFile) { fpPrev = pFile; }
|
|
A2File* GetNext(void) const { return fpNext; }
|
|
void SetNext(A2File* pFile) { fpNext = pFile; }
|
|
|
|
// Set when file structure is damaged and application should not try
|
|
// to open the file.
|
|
FileQuality fFileQuality;
|
|
|
|
A2File* fpPrev;
|
|
A2File* fpNext;
|
|
|
|
|
|
private:
|
|
A2File& operator=(const A2File&);
|
|
A2File(const A2File&);
|
|
};
|
|
|
|
|
|
/*
|
|
* Abstract representation of an open file. This contains all active state
|
|
* and all information required to read and write a file.
|
|
*
|
|
* Do not delete these objects; instead, invoke the Close method, so that they
|
|
* can be removed from the parents' list of open files.
|
|
* TODO: consider making the destructor "protected"
|
|
*/
|
|
class DISKIMG_API A2FileDescr {
|
|
public:
|
|
A2FileDescr(A2File* pFile) : fpFile(pFile), fProgressUpdateFunc(NULL) {}
|
|
virtual ~A2FileDescr(void) { fpFile = NULL; /*paranoia*/ }
|
|
|
|
virtual DIError Read(void* buf, size_t len, size_t* pActual = NULL) = 0;
|
|
virtual DIError Write(const void* buf, size_t len, size_t* pActual = NULL) = 0;
|
|
virtual DIError Seek(di_off_t offset, DIWhence whence) = 0;
|
|
virtual di_off_t Tell(void) = 0;
|
|
virtual DIError Close(void) = 0;
|
|
|
|
virtual DIError GetStorage(long sectorIdx, long* pTrack, long* pSector)
|
|
const = 0;
|
|
virtual DIError GetStorage(long blockIdx, long* pBlock)
|
|
const = 0;
|
|
virtual long GetSectorCount(void) const = 0;
|
|
virtual long GetBlockCount(void) const = 0;
|
|
|
|
virtual DIError Rewind(void) { return Seek(0, kSeekSet); }
|
|
|
|
A2File* GetFile(void) const { return fpFile; }
|
|
|
|
/*
|
|
* Progress update callback mechanism. Pass in the length or (for writes)
|
|
* expected length of the file. This invokes the callback with the
|
|
* lengths and some pointers.
|
|
*
|
|
* If the progress callback returns "true", progress continues. If it
|
|
* returns "false", the read or write function will return with
|
|
* kDIErrCancelled.
|
|
*/
|
|
typedef bool (*ProgressUpdater)(A2FileDescr* pFile, di_off_t max,
|
|
di_off_t current, void* vState);
|
|
void SetProgressUpdater(ProgressUpdater func, di_off_t max, void* state) {
|
|
fProgressUpdateFunc = func;
|
|
fProgressUpdateMax = max;
|
|
fProgressUpdateState = state;
|
|
}
|
|
void ClearProgressUpdater(void) {
|
|
fProgressUpdateFunc = NULL;
|
|
}
|
|
|
|
protected:
|
|
A2File* fpFile;
|
|
|
|
/*
|
|
* Internal utility functions for mapping blocks to sectors and vice-versa.
|
|
*/
|
|
virtual void TrackSectorToBlock(long track, long sector, long* pBlock,
|
|
bool* pSecondHalf) const
|
|
{
|
|
int numSectPerTrack = fpFile->GetDiskFS()->GetDiskImg()->GetNumSectPerTrack();
|
|
assert(track < fpFile->GetDiskFS()->GetDiskImg()->GetNumTracks());
|
|
assert(sector < numSectPerTrack);
|
|
long dblBlock = track * numSectPerTrack + sector;
|
|
*pBlock = dblBlock / 2;
|
|
*pSecondHalf = (dblBlock & 0x01) != 0;
|
|
}
|
|
virtual void BlockToTrackSector(long block, bool secondHalf, long* pTrack,
|
|
long* pSector) const
|
|
{
|
|
assert(block < fpFile->GetDiskFS()->GetDiskImg()->GetNumBlocks());
|
|
int numSectPerTrack = fpFile->GetDiskFS()->GetDiskImg()->GetNumSectPerTrack();
|
|
int dblBlock = block * 2;
|
|
if (secondHalf)
|
|
dblBlock++;
|
|
*pTrack = dblBlock / numSectPerTrack;
|
|
*pSector = dblBlock % numSectPerTrack;
|
|
}
|
|
|
|
/*
|
|
* Call this from FS-specific read/write functions on successful
|
|
* completion (and perhaps more often for filesystems with potentially
|
|
* large files, e.g. ProDOS/HFS).
|
|
*
|
|
* Test the return value; if "false", user wishes to cancel the op, and
|
|
* long read or write calls should return immediately.
|
|
*/
|
|
inline bool UpdateProgress(di_off_t current) {
|
|
if (fProgressUpdateFunc != NULL) {
|
|
return (*fProgressUpdateFunc)(this, fProgressUpdateMax, current,
|
|
fProgressUpdateState);
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private:
|
|
A2FileDescr& operator=(const A2FileDescr&);
|
|
A2FileDescr(const A2FileDescr&);
|
|
|
|
/* storage for progress update goodies */
|
|
ProgressUpdater fProgressUpdateFunc;
|
|
di_off_t fProgressUpdateMax;
|
|
void* fProgressUpdateState;
|
|
};
|
|
|
|
}; // namespace DiskImgLib
|
|
|
|
#endif /*DISKIMG_DISKIMG_H*/
|