2014-11-04 00:26:53 +00:00
|
|
|
/*
|
|
|
|
* CiderPress
|
|
|
|
* Copyright (C) 2009 by CiderPress authors. All Rights Reserved.
|
|
|
|
* See the file LICENSE for distribution terms.
|
|
|
|
*/
|
|
|
|
/*
|
2015-01-10 06:25:14 +00:00
|
|
|
* Implementation of Gutenberg disk format (used by the Gutenberg and
|
|
|
|
* Gutenberg Jr. word processors).
|
2014-11-04 00:26:53 +00:00
|
|
|
*/
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "DiskImgPriv.h"
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ===========================================================================
|
|
|
|
* DiskFSGutenberg
|
|
|
|
* ===========================================================================
|
|
|
|
*/
|
|
|
|
|
2015-01-10 06:25:14 +00:00
|
|
|
/*
|
|
|
|
The Gutenberg disk format embeds the file structure meta-data into the disk
|
|
|
|
sectors themselves, rather than having a separate track/sector list. The
|
|
|
|
first six bytes in every sector are:
|
|
|
|
|
|
|
|
+00 previous track in this file (+$80 indicates link not valid?)
|
|
|
|
+01 previous sector
|
|
|
|
+02 current track (+$80 indicates start of file)
|
|
|
|
+03 current sector
|
|
|
|
+04 next track (+$80 indicates end of file)
|
|
|
|
+05 next sector
|
|
|
|
|
|
|
|
The files are circular -- the "next" and "previous" links will wrap around --
|
|
|
|
so you have to test the high bit to see if you've reached an end.
|
|
|
|
|
|
|
|
The disk catalog works the same way, and is present as the first file on
|
|
|
|
the disk (called "DIR"). (It's not quite the same -- the "previous" pointer
|
|
|
|
in the first sector just points to the first sector.) The catalog begins
|
|
|
|
at track 17 sector 7, and skips around the disk.
|
|
|
|
|
|
|
|
The boot area is not represented by a file, and does not include the
|
|
|
|
embedded T/S links.
|
|
|
|
|
|
|
|
Each directory entry is 16 bytes, and lays out rather nicely in the sector
|
|
|
|
editor:
|
|
|
|
|
|
|
|
00: 11 07 11 87 15 0e c7 c2 af cd c1 d3 d4 c5 d2 8d ......GB/MASTER.
|
|
|
|
10: c4 c9 d2 a0 a0 a0 a0 a0 a0 a0 a0 a0 11 07 cc 8d DIR ..L.
|
|
|
|
20: c3 cf d0 d9 a0 a0 a0 a0 a0 a0 a0 a0 10 40 d0 8d COPY .@P.
|
|
|
|
30: c3 cf d0 d9 c1 cc cc a0 a0 a0 a0 a0 10 04 d0 8d COPYALL ..P.
|
|
|
|
|
|
|
|
This shows the six T/S bytes, followed by a nine-character volume name.
|
|
|
|
The fact that each entry ends in 0x8d is likely a deliberate attempt to
|
|
|
|
make the file readable as high-ASCII text, with one entry per line.
|
|
|
|
|
|
|
|
The regular directory entries start at 0x10. The file name is 12 bytes,
|
|
|
|
followed by the track and sector of the start of the file. Note "DIR"
|
|
|
|
starts at track 17 sector 7, as expected. The next entry, COPY, has 0x40 for
|
|
|
|
its sector number, indicating that it has been deleted. (Some entries on
|
|
|
|
Gutenberg Jr. disks use 0x40 for the track number instead?)
|
|
|
|
|
|
|
|
The next value is one of 'L', 'P', 'M', or ' ' (0xcc, 0xd0, 0xcd, 0xa0).
|
|
|
|
Some files have text, some have fonts, some have executable code (a short
|
|
|
|
header followed by 6502 instructions). There's no apparent link between
|
|
|
|
the value and the type of data in the file.
|
|
|
|
*/
|
|
|
|
|
2014-11-04 00:26:53 +00:00
|
|
|
const int kMaxSectors = 32;
|
|
|
|
const int kMaxVolNameLen = 9;
|
|
|
|
const int kSctSize = 256;
|
|
|
|
const int kVTOCTrack = 17;
|
|
|
|
const int kVTOCSector = 7;
|
2015-01-10 02:19:14 +00:00
|
|
|
const int kCatalogEntryOffset = 0x10; // first entry in cat sect starts here
|
2014-11-04 00:26:53 +00:00
|
|
|
const int kCatalogEntrySize = 16; // length in bytes of catalog entries
|
|
|
|
const int kCatalogEntriesPerSect = 15; // #of entries per catalog sector
|
|
|
|
const int kEntryDeleted = 0x40; // this is used to designate deleted files
|
|
|
|
const int kEntryUnused = 0x00; // this is track# in never-used entries
|
|
|
|
const int kMaxTSPairs = 0x7a; // 122 entries for 256-byte sectors
|
|
|
|
const int kTSOffset = 0x0c; // first T/S entry in a T/S list
|
|
|
|
|
|
|
|
const int kMaxTSIterations = 32;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get a pointer to the Nth entry in a catalog sector.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
static inline uint8_t* GetCatalogEntryPtr(uint8_t* basePtr, int entryNum)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
assert(entryNum >= 0 && entryNum < kCatalogEntriesPerSect);
|
|
|
|
return basePtr + kCatalogEntryOffset + entryNum * kCatalogEntrySize;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Test this image for Gutenberg-ness.
|
|
|
|
*
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder,
|
|
|
|
int* pGoodCount)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
DIError dierr = kDIErrNone;
|
2014-11-24 20:56:19 +00:00
|
|
|
uint8_t sctBuf[kSctSize];
|
2014-11-04 00:26:53 +00:00
|
|
|
int catTrack = kVTOCTrack;
|
|
|
|
int catSect = kVTOCSector;
|
|
|
|
int foundGood = 0;
|
|
|
|
int iterations = 0;
|
|
|
|
|
|
|
|
*pGoodCount = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Walk through the catalog track to try to figure out ordering.
|
|
|
|
*/
|
|
|
|
while (iterations < DiskFSGutenberg::kMaxCatalogSectors)
|
|
|
|
{
|
|
|
|
dierr = pImg->ReadTrackSectorSwapped(catTrack, catSect, sctBuf,
|
|
|
|
imageOrder, DiskImg::kSectorOrderDOS);
|
|
|
|
if (dierr != kDIErrNone) {
|
|
|
|
dierr = kDIErrNone;
|
|
|
|
break; /* allow it if earlier stuff was okay */
|
|
|
|
}
|
2015-01-10 06:28:41 +00:00
|
|
|
if (catTrack == (sctBuf[2] & 0x7f) && catSect == (sctBuf[3] & 0x7f)) {
|
2015-01-10 06:25:14 +00:00
|
|
|
// current-sector values matched, check for the end-of-entry bits
|
2014-12-13 05:39:35 +00:00
|
|
|
foundGood++;
|
|
|
|
if (sctBuf[0x0f] == 0x8d && sctBuf[0x1f] == 0x8d &&
|
|
|
|
sctBuf[0x2f] == 0x8d && sctBuf[0x3f] == 0x8d &&
|
|
|
|
sctBuf[0x4f] == 0x8d && sctBuf[0x5f] == 0x8d &&
|
|
|
|
sctBuf[0x6f] == 0x8d && sctBuf[0x7f] == 0x8d &&
|
|
|
|
sctBuf[0x8f] == 0x8d && sctBuf[0x9f] == 0x8d)
|
|
|
|
{
|
|
|
|
foundGood++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catTrack = sctBuf[0x04];
|
|
|
|
catSect = sctBuf[0x05];
|
|
|
|
if ((catTrack & 0x80) != 0) {
|
|
|
|
// full circle
|
|
|
|
break;
|
|
|
|
}
|
2014-11-04 00:26:53 +00:00
|
|
|
iterations++; // watch for infinite loops
|
|
|
|
}
|
|
|
|
if (iterations >= DiskFSGutenberg::kMaxCatalogSectors) {
|
|
|
|
/* possible cause: LF->CR conversion screws up link to sector $0a */
|
|
|
|
dierr = kDIErrDirectoryLoop;
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI(" Gutenberg directory links cause a loop (order=%d)", imageOrder);
|
2014-11-04 00:26:53 +00:00
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI(" Gutenberg foundGood=%d order=%d", foundGood, imageOrder);
|
2014-11-04 00:26:53 +00:00
|
|
|
*pGoodCount = foundGood;
|
|
|
|
|
|
|
|
bail:
|
|
|
|
return dierr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Test to see if the image is a Gutenberg word processor data disk.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
/*static*/ DIError DiskFSGutenberg::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
2014-11-04 00:26:53 +00:00
|
|
|
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
|
|
|
{
|
|
|
|
if (pImg->GetNumTracks() > kMaxInterestingTracks)
|
|
|
|
return kDIErrFilesystemNotFound;
|
|
|
|
|
|
|
|
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
|
|
|
|
|
|
|
|
DiskImg::GetSectorOrderArray(ordering, *pOrder);
|
|
|
|
|
|
|
|
DiskImg::SectorOrder bestOrder = DiskImg::kSectorOrderUnknown;
|
|
|
|
int bestCount = 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
|
|
|
|
int goodCount = 0;
|
|
|
|
|
|
|
|
if (ordering[i] == DiskImg::kSectorOrderUnknown)
|
|
|
|
continue;
|
|
|
|
if (TestImage(pImg, ordering[i], &goodCount) == kDIErrNone) {
|
|
|
|
if (goodCount > bestCount) {
|
|
|
|
bestCount = goodCount;
|
|
|
|
bestOrder = ordering[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bestCount >= 2 ||
|
|
|
|
(leniency == kLeniencyVery && bestCount >= 1))
|
|
|
|
{
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI(" Gutenberg test: bestCount=%d for order=%d", bestCount, bestOrder);
|
2014-11-04 00:26:53 +00:00
|
|
|
assert(bestOrder != DiskImg::kSectorOrderUnknown);
|
|
|
|
*pOrder = bestOrder;
|
|
|
|
*pFormat = DiskImg::kFormatGutenberg;
|
|
|
|
return kDIErrNone;
|
|
|
|
}
|
|
|
|
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI(" Gutenberg didn't find a valid filesystem.");
|
2014-11-04 00:26:53 +00:00
|
|
|
return kDIErrFilesystemNotFound;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get things rolling.
|
|
|
|
*
|
|
|
|
* Since we're assured that this is a valid disk, errors encountered from here
|
|
|
|
* on out must be handled somehow, possibly by claiming that the disk is
|
|
|
|
* completely full and has no files on it.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError DiskFSGutenberg::Initialize(InitMode initMode)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
DIError dierr = kDIErrNone;
|
|
|
|
|
|
|
|
fVolumeUsage.Create(fpImg->GetNumTracks(), fpImg->GetNumSectPerTrack());
|
|
|
|
|
|
|
|
/* read the contents of the catalog, creating our A2File list */
|
|
|
|
dierr = ReadCatalog();
|
|
|
|
if (dierr != kDIErrNone)
|
|
|
|
goto bail;
|
|
|
|
|
|
|
|
/* run through and get file lengths and data offsets */
|
|
|
|
dierr = GetFileLengths();
|
|
|
|
if (dierr != kDIErrNone)
|
|
|
|
goto bail;
|
|
|
|
|
2014-12-12 00:04:35 +00:00
|
|
|
sprintf(fDiskVolumeID, "Gutenberg: %s", fDiskVolumeName);
|
2014-11-04 00:26:53 +00:00
|
|
|
|
|
|
|
fDiskIsGood = CheckDiskIsGood();
|
|
|
|
|
|
|
|
fVolumeUsage.Dump();
|
|
|
|
|
|
|
|
bail:
|
|
|
|
return dierr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the amount of free space remaining.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError DiskFSGutenberg::GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits,
|
2014-11-04 00:26:53 +00:00
|
|
|
int* pUnitSize) const
|
|
|
|
{
|
|
|
|
*pTotalUnits = fpImg->GetNumTracks() * fpImg->GetNumSectPerTrack();
|
|
|
|
*pFreeUnits = 0;
|
|
|
|
*pUnitSize = kSectorSize;
|
|
|
|
return kDIErrNone;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the disk's catalog.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError DiskFSGutenberg::ReadCatalog(void)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
DIError dierr = kDIErrNone;
|
2014-11-24 20:56:19 +00:00
|
|
|
uint8_t sctBuf[kSctSize];
|
2014-11-04 00:26:53 +00:00
|
|
|
int catTrack, catSect;
|
|
|
|
int iterations;
|
|
|
|
|
2015-01-10 06:25:14 +00:00
|
|
|
catTrack = kVTOCTrack;
|
|
|
|
catSect = kVTOCSector;
|
2014-11-04 00:26:53 +00:00
|
|
|
iterations = 0;
|
|
|
|
|
|
|
|
memset(fCatalogSectors, 0, sizeof(fCatalogSectors));
|
|
|
|
|
|
|
|
while (catTrack < 35 && catSect < 16 && iterations < kMaxCatalogSectors)
|
|
|
|
{
|
2015-01-10 06:25:14 +00:00
|
|
|
LOGD(" Gutenberg reading catalog sector T=%d S=%d", catTrack, catSect);
|
2014-11-04 00:26:53 +00:00
|
|
|
dierr = fpImg->ReadTrackSector(catTrack, catSect, sctBuf);
|
|
|
|
if (dierr != kDIErrNone)
|
|
|
|
goto bail;
|
|
|
|
memcpy(fDiskVolumeName, &sctBuf[6], kMaxVolNameLen); // Copy out the volume name; it should be the same on all catalog sectors.
|
|
|
|
fDiskVolumeName[kMaxVolNameLen] = 0x00;
|
2014-11-24 20:56:19 +00:00
|
|
|
DiskFSGutenberg::LowerASCII((uint8_t*)fDiskVolumeName, kMaxVolNameLen);
|
2014-11-04 00:26:53 +00:00
|
|
|
A2FileGutenberg::TrimTrailingSpaces(fDiskVolumeName);
|
|
|
|
|
|
|
|
dierr = ProcessCatalogSector(catTrack, catSect, sctBuf);
|
|
|
|
if (dierr != kDIErrNone)
|
|
|
|
goto bail;
|
|
|
|
|
|
|
|
fCatalogSectors[iterations].track = catTrack;
|
|
|
|
fCatalogSectors[iterations].sector = catSect;
|
|
|
|
|
|
|
|
catTrack = sctBuf[0x04];
|
|
|
|
catSect = sctBuf[0x05];
|
|
|
|
|
|
|
|
iterations++; // watch for infinite loops
|
|
|
|
|
|
|
|
}
|
|
|
|
if (iterations >= kMaxCatalogSectors) {
|
|
|
|
dierr = kDIErrDirectoryLoop;
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
|
|
|
bail:
|
|
|
|
return dierr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Process the list of files in one sector of the catalog.
|
|
|
|
*
|
|
|
|
* Pass in the track, sector, and the contents of that track and sector.
|
|
|
|
* (We only use "catTrack" and "catSect" to fill out some fields.)
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError DiskFSGutenberg::ProcessCatalogSector(int catTrack, int catSect,
|
|
|
|
const uint8_t* sctBuf)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
A2FileGutenberg* pFile;
|
2014-11-24 20:56:19 +00:00
|
|
|
const uint8_t* pEntry;
|
2014-11-04 00:26:53 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
pEntry = &sctBuf[kCatalogEntryOffset];
|
|
|
|
|
|
|
|
for (i = 0; i < kCatalogEntriesPerSect; i++) {
|
2015-01-10 06:25:14 +00:00
|
|
|
if (pEntry[0x0c] != kEntryDeleted && pEntry[0x0d] != kEntryDeleted &&
|
|
|
|
pEntry[0x00] != 0xa0 && pEntry[0x00] != 0x00)
|
|
|
|
{
|
2014-11-04 00:26:53 +00:00
|
|
|
pFile = new A2FileGutenberg(this);
|
|
|
|
|
|
|
|
pFile->SetQuality(A2File::kQualityGood);
|
|
|
|
|
|
|
|
pFile->fTrack = pEntry[0x0c];
|
|
|
|
pFile->fSector = pEntry[0x0d];
|
|
|
|
|
|
|
|
memcpy(pFile->fFileName, &pEntry[0x00], A2FileGutenberg::kMaxFileName);
|
|
|
|
pFile->fFileName[A2FileGutenberg::kMaxFileName] = '\0';
|
|
|
|
pFile->FixFilename();
|
|
|
|
|
|
|
|
//pFile->fCatTS.track = catTrack;
|
|
|
|
//pFile->fCatTS.sector = catSect;
|
|
|
|
pFile->fCatEntryNum = i;
|
|
|
|
|
|
|
|
/* can't do these yet, so just set to defaults */
|
|
|
|
pFile->fLength = 0;
|
|
|
|
pFile->fSparseLength = 0;
|
|
|
|
pFile->fDataOffset = 0;
|
|
|
|
pFile->fLengthInSectors = 0;
|
|
|
|
pFile->fLengthInSectors = 0;
|
|
|
|
|
|
|
|
AddFileToList(pFile);
|
|
|
|
}
|
|
|
|
//if (pEntry[0x00] == 0xa0)
|
|
|
|
// break;
|
|
|
|
pEntry += kCatalogEntrySize;
|
|
|
|
}
|
|
|
|
|
|
|
|
return kDIErrNone;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Perform consistency checks on the filesystem.
|
|
|
|
*
|
|
|
|
* Returns "true" if disk appears to be perfect, "false" otherwise.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
bool DiskFSGutenberg::CheckDiskIsGood(void)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
bool result = true;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Run through our list of files, computing the lengths and marking file
|
|
|
|
* usage in the VolumeUsage object.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError DiskFSGutenberg::GetFileLengths(void)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
A2FileGutenberg* pFile;
|
2014-11-24 20:56:19 +00:00
|
|
|
uint8_t sctBuf[kSctSize];
|
2014-11-04 00:26:53 +00:00
|
|
|
int tsCount = 0;
|
2014-11-24 20:56:19 +00:00
|
|
|
uint16_t currentTrack, currentSector;
|
2014-11-04 00:26:53 +00:00
|
|
|
|
2014-11-18 05:13:13 +00:00
|
|
|
pFile = (A2FileGutenberg*) GetNextFile(NULL);
|
|
|
|
while (pFile != NULL) {
|
2014-11-04 00:26:53 +00:00
|
|
|
DIError dierr;
|
|
|
|
tsCount = 0;
|
|
|
|
currentTrack = pFile->fTrack;
|
|
|
|
currentSector = pFile->fSector;
|
|
|
|
|
|
|
|
while (currentTrack < 0x80) {
|
|
|
|
tsCount ++;
|
|
|
|
dierr = fpImg->ReadTrackSector(currentTrack, currentSector, sctBuf);
|
|
|
|
if (dierr != kDIErrNone) {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("Gutenberg failed loading track/sector for '%s'",
|
2014-11-04 00:26:53 +00:00
|
|
|
pFile->GetPathName());
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
currentTrack = sctBuf[0x04];
|
|
|
|
currentSector = sctBuf[0x05];
|
|
|
|
}
|
|
|
|
pFile->fLengthInSectors = tsCount;
|
|
|
|
pFile->fLength = tsCount * 250; // First six bytes of sector are t/s pointers
|
|
|
|
|
|
|
|
pFile = (A2FileGutenberg*) GetNextFile(pFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
bail:
|
|
|
|
return kDIErrNone;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert high ASCII to low ASCII.
|
|
|
|
*
|
|
|
|
* Some people put inverse and flashing text into filenames, not to mention
|
|
|
|
* control characters, so we have to cope with those too.
|
|
|
|
*
|
|
|
|
* We modify the first "len" bytes of "buf" in place.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
/*static*/ void DiskFSGutenberg::LowerASCII(uint8_t* buf, long len)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
while (len--) {
|
|
|
|
if (*buf & 0x80) {
|
|
|
|
if (*buf >= 0xa0)
|
|
|
|
*buf &= 0x7f;
|
|
|
|
else
|
|
|
|
*buf = (*buf & 0x7f) + 0x20;
|
|
|
|
} else
|
|
|
|
*buf = ((*buf & 0x3f) ^ 0x20) + 0x20;
|
|
|
|
|
|
|
|
buf++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ===========================================================================
|
|
|
|
* A2FileGutenberg
|
|
|
|
* ===========================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Constructor.
|
|
|
|
*/
|
|
|
|
A2FileGutenberg::A2FileGutenberg(DiskFS* pDiskFS) : A2File(pDiskFS)
|
|
|
|
{
|
|
|
|
fTrack = -1;
|
|
|
|
fSector = -1;
|
|
|
|
fLengthInSectors = 0;
|
|
|
|
fLocked = true;
|
|
|
|
fFileName[0] = '\0';
|
|
|
|
fFileType = kTypeText;
|
|
|
|
|
|
|
|
fCatTS.track = fCatTS.sector = 0;
|
|
|
|
fCatEntryNum = -1;
|
|
|
|
|
|
|
|
fAuxType = 0;
|
|
|
|
fDataOffset = 0;
|
|
|
|
fLength = -1;
|
|
|
|
fSparseLength = -1;
|
|
|
|
|
2014-11-18 05:13:13 +00:00
|
|
|
fpOpenFile = NULL;
|
2014-11-04 00:26:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Destructor. Make sure an "open" file gets "closed".
|
|
|
|
*/
|
|
|
|
A2FileGutenberg::~A2FileGutenberg(void)
|
|
|
|
{
|
|
|
|
delete fpOpenFile;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert the filetype enum to a ProDOS type.
|
|
|
|
*
|
|
|
|
*/
|
2015-01-05 02:20:59 +00:00
|
|
|
uint32_t A2FileGutenberg::GetFileType(void) const
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
return 0x04; // TXT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "Fix" a filename. Convert DOS-ASCII to normal ASCII, and strip
|
|
|
|
* trailing spaces.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
void A2FileGutenberg::FixFilename(void)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
2014-11-24 20:56:19 +00:00
|
|
|
DiskFSGutenberg::LowerASCII((uint8_t*)fFileName, kMaxFileName);
|
2014-11-04 00:26:53 +00:00
|
|
|
TrimTrailingSpaces(fFileName);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Trim the spaces off the end of a filename.
|
|
|
|
*
|
|
|
|
* Assumes the filename has already been converted to low ASCII.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
/*static*/ void A2FileGutenberg::TrimTrailingSpaces(char* filename)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
char* lastspc = filename + strlen(filename);
|
|
|
|
|
|
|
|
assert(*lastspc == '\0');
|
|
|
|
|
|
|
|
while (--lastspc) {
|
|
|
|
if (*lastspc != ' ')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*(lastspc+1) = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Encode a filename into high ASCII, padded out with spaces to
|
|
|
|
* kMaxFileName chars. Lower case is converted to upper case. This
|
|
|
|
* does not filter out control characters or other chunk.
|
|
|
|
*
|
|
|
|
* "buf" must be able to hold kMaxFileName+1 chars.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
/*static*/ void A2FileGutenberg::MakeDOSName(char* buf, const char* name)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
for (int i = 0; i < kMaxFileName; i++) {
|
|
|
|
if (*name == '\0')
|
|
|
|
*buf++ = (char) 0xa0;
|
|
|
|
else
|
|
|
|
*buf++ = toupper(*name++) | 0x80;
|
|
|
|
}
|
|
|
|
*buf = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up state for this file.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError A2FileGutenberg::Open(A2FileDescr** ppOpenFile, bool readOnly,
|
2014-11-04 00:26:53 +00:00
|
|
|
bool rsrcFork /*=false*/)
|
|
|
|
{
|
|
|
|
DIError dierr = kDIErrNone;
|
2014-11-18 05:13:13 +00:00
|
|
|
A2FDGutenberg* pOpenFile = NULL;
|
2014-11-04 00:26:53 +00:00
|
|
|
|
|
|
|
if (!readOnly) {
|
|
|
|
if (fpDiskFS->GetDiskImg()->GetReadOnly())
|
|
|
|
return kDIErrAccessDenied;
|
|
|
|
if (fpDiskFS->GetFSDamaged())
|
|
|
|
return kDIErrBadDiskImage;
|
|
|
|
}
|
|
|
|
|
2014-11-18 05:13:13 +00:00
|
|
|
if (fpOpenFile != NULL) {
|
2014-11-04 00:26:53 +00:00
|
|
|
dierr = kDIErrAlreadyOpen;
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rsrcFork)
|
|
|
|
return kDIErrForkNotFound;
|
|
|
|
|
|
|
|
pOpenFile = new A2FDGutenberg(this);
|
|
|
|
|
|
|
|
pOpenFile->fOffset = 0;
|
|
|
|
pOpenFile->fOpenEOF = fLength;
|
|
|
|
pOpenFile->fOpenSectorsUsed = fLengthInSectors;
|
|
|
|
|
|
|
|
fpOpenFile = pOpenFile; // add it to our single-member "open file set"
|
|
|
|
*ppOpenFile = pOpenFile;
|
2014-11-18 05:13:13 +00:00
|
|
|
pOpenFile = NULL;
|
2014-11-04 00:26:53 +00:00
|
|
|
|
|
|
|
bail:
|
|
|
|
delete pOpenFile;
|
|
|
|
return dierr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Dump the contents of an A2FileGutenberg.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
void A2FileGutenberg::Dump(void) const
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI("A2FileGutenberg '%s'", fFileName);
|
|
|
|
LOGI(" TS T=%-2d S=%-2d", fTrack, fSector);
|
|
|
|
LOGI(" Cat T=%-2d S=%-2d", fCatTS.track, fCatTS.sector);
|
|
|
|
LOGI(" type=%d lck=%d slen=%d", fFileType, fLocked, fLengthInSectors);
|
|
|
|
LOGI(" auxtype=0x%04x length=%ld",
|
2014-11-04 00:26:53 +00:00
|
|
|
fAuxType, (long) fLength);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ===========================================================================
|
|
|
|
* A2FDGutenberg
|
|
|
|
* ===========================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read data from the current offset.
|
|
|
|
*
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError A2FDGutenberg::Read(void* buf, size_t len, size_t* pActual)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
2014-12-12 01:34:25 +00:00
|
|
|
LOGD(" Gutenberg reading %lu bytes from '%s' (offset=%ld)",
|
|
|
|
(unsigned long) len, fpFile->GetPathName(), (long) fOffset);
|
2014-11-04 00:26:53 +00:00
|
|
|
|
|
|
|
A2FileGutenberg* pFile = (A2FileGutenberg*) fpFile;
|
|
|
|
|
|
|
|
DIError dierr = kDIErrNone;
|
2014-11-24 20:56:19 +00:00
|
|
|
uint8_t sctBuf[kSctSize];
|
2014-11-04 00:26:53 +00:00
|
|
|
short currentTrack, currentSector;
|
2014-12-12 00:04:35 +00:00
|
|
|
//di_off_t actualOffset = fOffset + pFile->fDataOffset; // adjust for embedded len
|
2014-11-04 00:26:53 +00:00
|
|
|
int bufOffset = 6;
|
|
|
|
size_t thisCount;
|
|
|
|
|
|
|
|
if (len == 0)
|
|
|
|
return kDIErrNone;
|
|
|
|
assert(fOpenEOF != 0);
|
|
|
|
currentTrack = pFile->fTrack;
|
|
|
|
currentSector = pFile->fSector;
|
|
|
|
/* could be more clever in here and avoid double-buffering */
|
|
|
|
while (len) {
|
|
|
|
dierr = pFile->GetDiskFS()->GetDiskImg()->ReadTrackSector(
|
|
|
|
currentTrack,
|
|
|
|
currentSector,
|
|
|
|
sctBuf);
|
|
|
|
if (dierr != kDIErrNone) {
|
2014-11-18 21:05:15 +00:00
|
|
|
LOGI(" Gutenberg error reading file '%s'", pFile->GetPathName());
|
2014-11-04 00:26:53 +00:00
|
|
|
return dierr;
|
|
|
|
}
|
|
|
|
thisCount = kSctSize - bufOffset;
|
|
|
|
if (thisCount > len)
|
|
|
|
thisCount = len;
|
|
|
|
memcpy(buf, sctBuf + bufOffset, thisCount);
|
|
|
|
len -= thisCount;
|
|
|
|
buf = (char*)buf + thisCount;
|
|
|
|
currentTrack = sctBuf[0x04];
|
|
|
|
currentSector = sctBuf[0x05];
|
|
|
|
}
|
|
|
|
|
|
|
|
return dierr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Writing Gutenberg files isn't supported.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError A2FDGutenberg::Write(const void* buf, size_t len, size_t* pActual)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
return kDIErrNotSupported;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Seek to the specified offset.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError A2FDGutenberg::Seek(di_off_t offset, DIWhence whence)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
return kDIErrNotSupported;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return current offset.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
di_off_t A2FDGutenberg::Tell(void)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
return kDIErrNotSupported;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Release file state.
|
|
|
|
*
|
|
|
|
* If the file was modified, we need to update the sector usage count in
|
|
|
|
* the catalog track, and possibly a length word in the first sector of
|
|
|
|
* the file (for A/I/B).
|
|
|
|
*
|
|
|
|
* Given the current "write all at once" implementation of Write, we could
|
|
|
|
* have handled the length word back when initially writing the data, but
|
|
|
|
* someday we may fix that and I don't want to have to rewrite this part.
|
|
|
|
*
|
|
|
|
* Most applications don't check the value of "Close", or call it from a
|
|
|
|
* destructor, so we call CloseDescr whether we succeed or not.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError A2FDGutenberg::Close(void)
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
DIError dierr = kDIErrNone;
|
|
|
|
|
|
|
|
fpFile->CloseDescr(this);
|
|
|
|
return dierr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return the #of sectors/blocks in the file.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
long A2FDGutenberg::GetSectorCount(void) const
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
return fTSCount;
|
|
|
|
}
|
2014-11-24 20:56:19 +00:00
|
|
|
|
|
|
|
long A2FDGutenberg::GetBlockCount(void) const
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
return (fTSCount+1)/2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return the Nth track/sector in this file.
|
|
|
|
*
|
|
|
|
* Returns (0,0) for a sparse sector.
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError A2FDGutenberg::GetStorage(long sectorIdx, long* pTrack, long* pSector) const
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
return kDIErrInvalidIndex;
|
|
|
|
}
|
2014-11-24 20:56:19 +00:00
|
|
|
|
2014-11-04 00:26:53 +00:00
|
|
|
/*
|
|
|
|
* Unimplemented
|
|
|
|
*/
|
2014-11-24 20:56:19 +00:00
|
|
|
DIError A2FDGutenberg::GetStorage(long blockIdx, long* pBlock) const
|
2014-11-04 00:26:53 +00:00
|
|
|
{
|
|
|
|
return kDIErrInvalidIndex;
|
|
|
|
}
|