Catakig/Source/LibAppleII/A2DiskDrive.m

461 lines
11 KiB
Objective-C

/* class A2DiskDrive
*/
#import "LibAppleII-Priv.h"
#import "A2DiskDrive.h"
#import "A2DiskImages.h"
@implementation A2DiskDrive
//---------------------------------------------------------------------------
enum
{
kSizeNIBTrack = 0x1A00, // 6656
kSizeNB2Track = 0x18F0, // 6384
kSizePhysTrack = kSizeNIBTrack, // should be 6350-6480 bytes
kGap3 = 16, // should be 16-24 bytes
kGap2 = 6, // should be 5-10 bytes
kSizePhysSector = kGap3 + (3+8+3) + kGap2 + (3+86+256+1+3),
// kNumTracks = 35, // 35-40??
kDefaultVolumeNum = 254,
kOpenRW = O_NONBLOCK | O_RDWR | O_EXLOCK,
kOpenR = O_NONBLOCK | O_RDONLY,
};
static struct
{
regex_t rexDigits,
rexSuffixes5,
rexSuffixes3;
} g;
//---------------------------------------------------------------------------
+ (void)initialize
{
enum { kFlags = REG_EXTENDED | REG_NOSUB | REG_ICASE };
if (self != [A2DiskDrive class])
return; // ensures this routine executes no more than once
regcomp(&g.rexDigits, "[0-9]+", kFlags);
regcomp(&g.rexSuffixes5, "2i?mg|dsk|[dp]o|n[iy]b", kFlags);
regcomp(&g.rexSuffixes3, "2i?mg|img|hdv", kFlags);
}
//---------------------------------------------------------------------------
- (void)_WriteWorkingDiskToFile:(fd_t)fout:(BOOL)as2IMG
{
REWIND(fout);
ftruncate(fout, 0);
if (as2IMG)
{
A2Header2IMG hdr =
{
.m2IMG = "2IMG",
.mCreator = "CTKG",
.mHeaderLength = NSSwapHostShortToLittle(64),
.mVersion = NSSwapHostShortToLittle(1),
.mFormat = 2, // NIB
// .mPad1 = {0},
.mVolNumber = 0,
.mGotVolume = 0,
// .mPad2 = 0,
.mLocked = 0,
.mNumBlocks = 0,
.mDataPos = NSSwapHostLongToLittle(sizeof(A2Header2IMG)),
.mDataLen = NSSwapHostLongToLittle(35L * 0x2000),
.mCommentPos = 0,
.mCommentLen = 0,
.mAppDataPos = 0,
.mAppDataLen = 0,
// .mPad3 = {0},
};
write(fout, &hdr, sizeof(hdr));
}
for (int t = 0; t < 35; ++t)
{
[self SeekTrack:t];
write(fout, mTrackBase, kSizeNIBTrack);
}
}
//---------------------------------------------------------------------------
static void EnnybSector(uint8_t* dest, //[kSizePhysSector]
uint8_t src[256+2], int volume, int track, int sector)
{/*
Make a physical, nybblized sector out of a logical one. Input is
taken from array _src_; output is deposited into array _dest_.
*/
#define ENCODE_44(BYTE) \
dest[0] = 0xAA | (x=(BYTE))>>1; \
dest[1] = 0xAA | x; dest += 2
#define TRIPLET(B0,B2) \
dest[0] = B0; dest[1] = 0xAA; dest[2] = B2; dest += 3
static const uint8_t pbyte[] =
{ // physical encoding of 6-bit logical values
0x96,0x97,0x9A,0x9B,0x9D,0x9E,0x9F,0xA6,
0xA7,0xAB,0xAC,0xAD,0xAE,0xAF,0xB2,0xB3,
0xB4,0xB5,0xB6,0xB7,0xB9,0xBA,0xBB,0xBC,
0xBD,0xBE,0xBF,0xCB,0xCD,0xCE,0xCF,0xD3,
0xD6,0xD7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,
0xDF,0xE5,0xE6,0xE7,0xE9,0xEA,0xEB,0xEC,
0xED,0xEE,0xEF,0xF2,0xF3,0xF4,0xF5,0xF6,
0xF7,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF
};
uint8_t x, ox;
dest += kGap3;
TRIPLET(0xD5, 0x96);
ENCODE_44(volume);
ENCODE_44(track);
ENCODE_44(sector);
ENCODE_44(volume ^ track ^ sector);
TRIPLET(0xDE, 0xEB);
dest += kGap2;
TRIPLET(0xD5, 0xAD);
for (int i = ox = 0; i < 86; ++i, ox = x)
{
x = "\0\x02\x01\x03"[ src [i] & 3] |
"\0\x08\x04\x0C"[(src+ 86)[i] & 3] |
"\0\x20\x10\x30"[(src+172)[i] & 3];
dest[i] = pbyte[ox ^ x];
}
dest += 86;
for (int i = 0; i < 256; ++i, ox = x)
dest[i] = pbyte[ox ^ (x = src[i] >> 2)];
dest += 256;
*dest++ = pbyte[x];
TRIPLET(0xDE, 0xEB);
#undef ENCODE_44
#undef TRIPLET
}
//---------------------------------------------------------------------------
- (void)_ImportDisk140K:(int)format:(int)volume
{
const uint8_t sectorMaps[][16] =
{
{0,7,14,6,13,5,12,4,11,3,10,2,9,1,8,15}, // DO
{0,8,1,9,2,10,3,11,4,12,5,13,6,14,7,15}, // PO
// {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, // identical
// {0,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}, // reversed
};
enum { kGap1 = 0x2000 - 16*kSizePhysSector };
const uint8_t* map;
long base = TELL(mOrigFD);
uint8_t lsec[256+2], // logical sector
psec[kSizePhysSector]; // physical sector
lsec[256] = lsec[257] = 0;
memset(psec, 0xFF, sizeof(psec));
map = sectorMaps[format == kFmtPO or format == kFmt2IMG_PO];
for (int t = 0; t < 35; ++t) // 35 tracks of ...
{
for (int s = 0; s < 16; ++s) // 16 physical sectors
{
pread(mOrigFD, lsec, 256, base + 256L*(t*16 + map[s]));
EnnybSector(psec, lsec, volume, t, s);
write(mWorkFD, psec, kSizePhysSector);
}
A2WriteFiller(mWorkFD, 0xFF, kGap1);
}
ftruncate(mWorkFD, 35*0x2000);
}
//---------------------------------------------------------------------------
- (void)_ImportDiskNIB:(uint32_t)rdTrackSize
{
enum { wrTrackSize = 0x2000 };
uint8_t buf[2][wrTrackSize];
memset(buf[0], 0xFF, wrTrackSize);
for (int t = 35; --t >= 0;)
{
read(mOrigFD, buf[0], rdTrackSize);
#if 0
memcpy(buf[1], buf[0], wrTrackSize);
#endif
write(mWorkFD, buf[0], wrTrackSize);
}
}
//---------------------------------------------------------------------------
- (BOOL)SeekTrack:(unsigned)reqTrack
{/*
Moves this drive's R/W head to the requested track, identified by a
non-negative index. Returns whether successful. Fails if the index is
outside the range of available tracks.
*/
if (reqTrack > mTrackMax) // then requested track out of range
return NO;
// if (mTrackIndex != reqTrack)
A2MemoryMap(mTrackBase, 0x2000, mWorkFD, reqTrack*0x2000);
// mTheta = 0; // always rewind??
mTrackIndex = reqTrack;
return YES;
}
//---------------------------------------------------------------------------
- (void)Unload
{/*
Empties this disk drive of any disk.
*/
if (mDiskModified and IS_OPEN(mOrigFD))
{
[self _WriteWorkingDiskToFile:mOrigFD:NO];
[mFilePath SetFileCreator:'Ctkg' andType:'A2D5'];
if ([mFilePath ExtensionMatches:&g.rexSuffixes5])
[mFilePath SetFileExtension:@"nyb"];
}
[self SeekTrack:0];
mContent = kA2DiskNone;
mOrigFD = CLOSE(mOrigFD);
mFilePath = [mFilePath Release];
mTrackSize = 0x1A00;
mTrackMax = 0;
mTheta = 0;
mDiskModified = NO;
memset(mTrackBase, 0xFF, mTrackSize);
}
//---------------------------------------------------------------------------
- (id)InitUsingBuffer:(uint8_t [/*0x2000*/])buffer
{
if (nil == (self = [super init]))
return nil;
mTrackBase = buffer;
mOrigFD = kBadFD;
mWorkFD = A2OpenTempFile(35L * 0x2000);
if (not IS_OPEN(mWorkFD))
return [self Release];
[self Unload];
return self;
}
//---------------------------------------------------------------------------
- (void)dealloc
{
[self Unload];
close(mWorkFD);
[super dealloc];
}
//---------------------------------------------------------------------------
- (BOOL)_OpenDiskImageFile:(NSString*)fpath
{
const char* cfpath = [fpath fileSystemRepresentation];
unsigned volumeNum = kDefaultVolumeNum,
format = kFmtUnknown;
struct stat st;
long n;
union {
A2Header2IMG _2IMG;
A2HeaderDiskCopy4 DC4;
char bytes[512];
} header;
/*-------------------------------------------------------
Try opening the file for reading & writing -- if that
fails, then just for reading -- and if that fails,
give up.
*/
if (IS_OPEN(mOrigFD = open(cfpath, kOpenRW)))
mContent = kA2DiskReadWrite;
else if (IS_OPEN(mOrigFD = open(cfpath, kOpenR)))
mContent = kA2DiskReadOnly;
else // we can't open in any useful mode
return NO;
if (fstat(mOrigFD, &st) != 0 or
read(mOrigFD, &header, sizeof(header)) < sizeof(header) )
return NO;
/*------------------------------------------------------
Infer the file's format from it's size or header.
*/
n = st.st_size / 35;
if (n == 0x1000) // then DO or PO
format = kFmtDO + [[fpath lowercaseString] hasSuffix:@".po"];
else if (n == kSizeNIBTrack)
format = kFmtNIB;
else if (n == kSizeNB2Track)
format = kFmtNB2;
else // try lexing the header bytes to infer format
format = A2GleanFileFormat(&header, sizeof(header));
if (format == kFmtUnknown // still? then test for HDV format
and (st.st_size & 0x1FF) == 0
and (st.st_size >> 9) >= 280
and (st.st_size >> 9) <= 0xFFFF
) format = kFmtHDV;
mTrackMax = 35 - 1;
mTrackSize = kSizePhysTrack;
mFilePath = [fpath retain];
REWIND(mOrigFD);
REWIND(mWorkFD);
// NSLog(@"disk file format = %d", format); //!!
switch (format)
{
default: return NO;
case kFmtDO: // fall thru
case kFmtPO: [self _ImportDisk140K:format:volumeNum]; break;
case kFmtNB2: // fall thru
case kFmtNIB: [self _ImportDiskNIB:(st.st_size / 35)]; break;
case kFmt2IMG_PO: case kFmt2IMG_DO: case kFmt2IMG_NIB:
if (header._2IMG.mLocked & 0x80)
mContent = kA2DiskReadOnly;
if (header._2IMG.mGotVolume & 1)
volumeNum = header._2IMG.mVolNumber;
lseek(mOrigFD,
NSSwapLittleLongToHost(header._2IMG.mDataPos),
SEEK_SET);
if (format == kFmt2IMG_NIB)
[self _ImportDiskNIB:kSizeNIBTrack];
else
[self _ImportDisk140K:format:volumeNum];
break;
#if 0
case kFmtDiskCopy4:
lseek(mOrigFD, 84, SEEK_SET);
mTrackMax = 80 - 1;
break;
case kFmtHDV: case kFmt2IMG_HDV: //??
break;
#endif
}
return YES;
}
//---------------------------------------------------------------------------
- (BOOL)Load:(NSString*)fpath //!!
{/*
Attempts to load the specified disk image file into this disk drive.
Returns whether successful.
Any disk currently loaded is unloaded first. Passing nil has the
effect of unloading only, and is automatically successful.
*/
[self Unload];
if (fpath == nil)
return YES;
// errno = 0;
// fpath = [fpath stringByStandardizingPath]; //??
if (not [self _OpenDiskImageFile:fpath])
{
[self Unload];
return NO;
}
return YES;
}
//---------------------------------------------------------------------------
- (NSString*)Label
{/*
Returns a name for the currently loaded disk that's appropriate to
display in the user interface. Common file extensions are omitted.
Returns an empty string if the drive is empty.
*/
if (mFilePath == nil)
return @"";
NSString* name = [mFilePath lastPathComponent];
return [[name pathExtension] Matches:&g.rexDigits]? name :
[name stringByDeletingPathExtension];
}
//---------------------------------------------------------------------------
+ (BOOL)ShouldShowFilename:(NSString*)path
{
#if 0
NSString *app, *type;
[[NSWorkspace sharedWorkspace] getInfoForFile:path
application:&app type:&type];
NSLog(@"file type = '%@'", type);
#endif
struct stat st;
if (not [path Stat:&st]) // can't stat? then reject
return NO;
/*
if it's a bundle/package
return NO
else if it's a directory
return YES
else if it's not a readable file
return NO
else if we like the extension
return YES
else return whether we like the file size
*/
if (st.st_mode & S_IFDIR) // pass non-bundle directories
return YES; //return [NSBundle bundleWithPath:path] == nil;
if (not [path IsReadableFile]) // reject unreadable files
return NO;
if ([path ExtensionMatches:&g.rexSuffixes5])
return YES;
off_t tsz = st.st_size / 35;
if (tsz == 0x1000)
return YES;
return tsz >= kSizeNB2Track and tsz <= kSizeNIBTrack;
}
//---------------------------------------------------------------------------
- (unsigned)Content
{ return mContent; }
// Returns this drive's current content (kA2DiskNone, etc.)
//---------------------------------------------------------------------------
@end