mirror of
https://github.com/fadden/ciderpress.git
synced 2024-10-13 07:23:48 +00:00
1687 lines
69 KiB
C++
1687 lines
69 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 <sys/types.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 5
|
|
#define kDiskImgVersionMinor 0
|
|
#define kDiskImgVersionBug 1
|
|
|
|
|
|
/*
|
|
* 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 */
|
|
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(int32_t* pMajor, int32_t* pMinor, int32_t* 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?
|
|
*/
|
|
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?)
|
|
|
|
uint8_t addrProlog[kNibbleAddrPrologLen];
|
|
uint8_t addrEpilog[kNibbleAddrEpilogLen];
|
|
uint8_t addrChecksumSeed;
|
|
bool addrVerifyChecksum;
|
|
bool addrVerifyTrack;
|
|
int addrEpilogVerifyCount;
|
|
|
|
uint8_t dataProlog[kNibbleDataPrologLen];
|
|
uint8_t dataEpilog[kNibbleDataEpilogLen];
|
|
uint8_t 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, uint8_t* buf,
|
|
long* pTrackLen);
|
|
// write a track; trackLen must be <= those in image
|
|
virtual DIError WriteNibbleTrack(long track, const uint8_t* 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 */
|
|
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 uint8_t* nibbleBuf,
|
|
long nibbleLen, uint8_t* 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 uint8_t MacToASCII(uint8_t 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
|
|
|
|
uint8_t* 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 uint16_t ConvFrom44(uint8_t val1, uint8_t val2) {
|
|
return ((val1 << 1) | 0x01) & val2;
|
|
}
|
|
DIError DecodeNibbleData(const CircularBufferAccess& buffer, int idx,
|
|
uint8_t* sctBuf, const NibbleDescr* pNibbleDescr);
|
|
void EncodeNibbleData(const CircularBufferAccess& buffer, int idx,
|
|
const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const;
|
|
DIError DecodeNibble62(const CircularBufferAccess& buffer, int idx,
|
|
uint8_t* sctBuf, const NibbleDescr* pNibbleDescr);
|
|
void EncodeNibble62(const CircularBufferAccess& buffer, int idx,
|
|
const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const;
|
|
DIError DecodeNibble53(const CircularBufferAccess& buffer, int idx,
|
|
uint8_t* sctBuf, const NibbleDescr* pNibbleDescr);
|
|
void EncodeNibble53(const CircularBufferAccess& buffer, int idx,
|
|
const uint8_t* sctBuf, const NibbleDescr* pNibbleDescr) const;
|
|
int TestNibbleTrack(int track, const NibbleDescr* pNibbleDescr, int* pVol);
|
|
DIError AnalyzeNibbleData(void);
|
|
inline uint8_t Conv44(uint16_t val, bool first) const {
|
|
if (first)
|
|
return (val >> 1) | 0xaa;
|
|
else
|
|
return val | 0xaa;
|
|
}
|
|
DIError FormatNibbles(GenericFD* pGFD) const;
|
|
|
|
static const uint8_t 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, uint8_t* sectorBuf, uint8_t* readChecksum,
|
|
uint8_t* calcChecksum);
|
|
static bool UnpackChecksum35(const CircularBufferAccess& buffer,
|
|
int offset, uint8_t* checksumBuf);
|
|
static void EncodeNibbleSector35(const uint8_t* sectorData,
|
|
uint8_t* outBuf);
|
|
|
|
/* static data tables */
|
|
static uint8_t kDiskBytes53[32];
|
|
static uint8_t kDiskBytes62[64];
|
|
static uint8_t kInvDiskBytes53[256];
|
|
static uint8_t 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;
|
|
uint8_t* 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.
|
|
*
|
|
* Generally speaking, the normalization process involves separating
|
|
* the pathname into its components, modifying each filename as needed
|
|
* to make it legal on the current filesystem, and then reassembling
|
|
* the components.
|
|
*
|
|
* The input and output strings are Mac OS Roman.
|
|
*/
|
|
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
|
|
uint32_t fileType;
|
|
uint32_t auxType;
|
|
uint32_t 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, uint32_t fileType,
|
|
uint32_t auxType, uint32_t 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 filename returned is defined to be null-terminated Mac OS Roman.
|
|
* For most filesystems this is automatic, as filenames are restricted
|
|
* to ASCII, but for DOS 3.3 it requires stripping high bits. It also
|
|
* means that embedded nulls will be lost.
|
|
*
|
|
* The original unmodified filename is available through GetRawFileName,
|
|
* which can be optionally passed a size_t pointer to get the size
|
|
* of the raw file name, in case it has embedded nulls.
|
|
*
|
|
* 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 const char* GetRawFileName(size_t* size = NULL) const; // get unmodified file name
|
|
virtual char GetFssep(void) const = 0; // '\0' if none
|
|
virtual uint32_t GetFileType(void) const = 0;
|
|
virtual uint32_t GetAuxType(void) const = 0;
|
|
virtual uint32_t 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),
|
|
fProgressUpdateMax(0),
|
|
fProgressUpdateState(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*/
|