dmolony-DiskBrowser/src/com/bytezone/diskbrowser/prodos/FileEntry.java
2016-02-05 11:23:53 +11:00

570 lines
19 KiB
Java
Executable File

package com.bytezone.diskbrowser.prodos;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
import com.bytezone.diskbrowser.HexFormatter;
import com.bytezone.diskbrowser.applefile.*;
import com.bytezone.diskbrowser.appleworks.AppleworksADBFile;
import com.bytezone.diskbrowser.appleworks.AppleworksSSFile;
import com.bytezone.diskbrowser.appleworks.AppleworksWPFile;
import com.bytezone.diskbrowser.disk.DiskAddress;
import com.bytezone.diskbrowser.gui.DataSource;
// - Set sector types for each used sector
// - Populate dataBlocks, indexBlocks, catalogBlock and masterIndexBlock
// - Provide getDataSource ()
class FileEntry extends CatalogEntry implements ProdosConstants
{
private final int fileType;
final int keyPtr;
private final int blocksUsed;
private final int endOfFile;
private final int auxType;
private final GregorianCalendar modified;
private final int headerPointer;
private DataSource file;
private final DiskAddress catalogBlock;
private DiskAddress masterIndexBlock;
private final List<DiskAddress> indexBlocks = new ArrayList<DiskAddress> ();
private boolean invalid;
public FileEntry (ProdosDisk fDisk, byte[] entryBuffer, DirectoryHeader parent,
int parentBlock)
{
super (fDisk, entryBuffer);
this.parentDirectory = parent;
this.catalogBlock = this.disk.getDiskAddress (parentBlock);
fileType = entryBuffer[16] & 0xFF;
keyPtr = HexFormatter.intValue (entryBuffer[17], entryBuffer[18]);
blocksUsed = HexFormatter.intValue (entryBuffer[19], entryBuffer[20]);
endOfFile = HexFormatter.intValue (entryBuffer[21], entryBuffer[22], entryBuffer[23]);
auxType = HexFormatter.intValue (entryBuffer[31], entryBuffer[32]);
modified = HexFormatter.getAppleDate (entryBuffer, 33);
headerPointer = HexFormatter.intValue (entryBuffer[37], entryBuffer[38]);
switch (storageType)
{
case TYPE_SEEDLING:
parentDisk.setSectorType (keyPtr, fDisk.dataSector);
DiskAddress da = disk.getDiskAddress (keyPtr);
if (da != null)
dataBlocks.add (da);
else
invalid = true;
break;
case TYPE_SAPLING:
if (isGEOSFile ())
traverseGEOSIndex (keyPtr);
else
traverseIndex (keyPtr);
break;
case TYPE_TREE:
parentDisk.setSectorType (keyPtr, fDisk.masterIndexSector);
masterIndexBlock = disk.getDiskAddress (keyPtr);
if (isGEOSFile ())
traverseGEOSMasterIndex (keyPtr);
else
traverseMasterIndex (keyPtr);
break;
case TYPE_GSOS_EXTENDED_FILE:
parentDisk.setSectorType (keyPtr, fDisk.extendedKeySector);
indexBlocks.add (disk.getDiskAddress (keyPtr));
byte[] buffer2 = disk.readSector (keyPtr);
// data fork and resource fork
for (int i = 0; i < 512; i += 256)
{
int storageType = buffer2[i] & 0x0F;
int keyBlock = HexFormatter.intValue (buffer2[i + 1], buffer2[i + 2]);
switch (storageType)
{
case ProdosConstants.TYPE_SEEDLING:
parentDisk.setSectorType (keyBlock, fDisk.dataSector);
dataBlocks.add (disk.getDiskAddress (keyBlock));
break;
case ProdosConstants.TYPE_SAPLING:
traverseIndex (keyBlock);
break;
default:
System.out.println ("fork not a sapling or seedling!!!");
}
}
break;
case TYPE_SUBDIRECTORY:
int block = keyPtr;
do
{
dataBlocks.add (disk.getDiskAddress (block));
byte[] buffer = disk.readSector (block);
block = HexFormatter.intValue (buffer[2], buffer[3]);
} while (block > 0);
break;
default:
System.out.println ("Unknown storage type: " + storageType);
}
}
private boolean isGEOSFile ()
{
return ((fileType & 0xF0) == 0x80);
}
private void removeEmptyBlocks ()
{
while (dataBlocks.size () > 0)
{
DiskAddress da = dataBlocks.get (dataBlocks.size () - 1);
if (da.getBlock () == 0)
dataBlocks.remove (dataBlocks.size () - 1);
else
break;
}
}
private void traverseMasterIndex (int keyPtr)
{
byte[] buffer = disk.readSector (keyPtr); // master index
// find the last used index block
// get the file size from the catalog and only check those blocks
int highestBlock = 0;
// A master index block can never be more than half full
for (int i = 127; i >= 0; i--)
{
int block = HexFormatter.intValue (buffer[i], buffer[i + 256]);
if (block > 0)
{
highestBlock = i;
break;
}
}
for (int i = 0; i <= highestBlock; i++)
{
int block = HexFormatter.intValue (buffer[i], buffer[i + 256]); // index
if (block != 0)
traverseIndex (block);
else
// add 256 empty data blocks
{
DiskAddress da = disk.getDiskAddress (0);
for (int j = 0; j < 256; j++)
dataBlocks.add (da);
}
}
removeEmptyBlocks ();
}
private void traverseIndex (int keyBlock)
{
parentDisk.setSectorType (keyBlock, ((ProdosDisk) parentDisk).indexSector);
indexBlocks.add (disk.getDiskAddress (keyBlock));
byte[] buffer = disk.readSector (keyBlock);
for (int i = 0; i < 256; i++)
{
int block = HexFormatter.intValue (buffer[i], buffer[i + 256]);
if (!disk.isValidAddress (block))
{
System.out.println ("Invalid block in " + name + " : " + block);
invalid = true;
break;
}
// System.out.printf ("%4d %02X %02X%n", block, fileType, auxType);
// should we break if block == 0 and it's not a text file?
// if (block == 0 && !(fileType == ProdosConstants.FILE_TYPE_TEXT && auxType > 0))
// if (block == 0 && fileType != 4)
// break;
if (block != 0)
{
parentDisk.setSectorType (block, ((ProdosDisk) parentDisk).dataSector);
dataBlocks.add (disk.getDiskAddress (block));
}
}
}
private void traverseGEOSMasterIndex (int keyPtr)
{
byte[] buffer = disk.readSector (keyPtr); // master index
// int length = HexFormatter.intValue (buffer[0xFF], buffer[0x1FF]);
for (int i = 0; i < 0x80; i++)
{
int block = HexFormatter.intValue (buffer[i], buffer[i + 256]);
if (block == 0)
break;
if (block == 0xFFFF)
continue;
traverseGEOSIndex (block);
}
}
private void traverseGEOSIndex (int keyPtr)
{
parentDisk.setSectorType (keyPtr, ((ProdosDisk) parentDisk).indexSector);
indexBlocks.add (disk.getDiskAddress (keyPtr));
byte[] buffer = disk.readSector (keyPtr);
// int length = HexFormatter.intValue (buffer[0xFF], buffer[0x1FF]);
for (int i = 0; i < 0x80; i++)
{
int block = HexFormatter.intValue (buffer[i], buffer[i + 256]);
if (block == 0)
break;
if (block == 0xFFFF)
continue;
parentDisk.setSectorType (block, ((ProdosDisk) parentDisk).dataSector);
dataBlocks.add (disk.getDiskAddress (block));
}
}
@Override
public DataSource getDataSource ()
{
if (file != null)
return file;
if (invalid)
{
file = new DefaultAppleFile (name, null);
return file;
}
/*
* Text files with reclen > 0 are random access, possibly with gaps between
* records, so they need to be handled separately.
*/
if (fileType == FILE_TYPE_TEXT && auxType > 0)
{
switch (storageType)
{
case TYPE_TREE:
return getTreeTextFile ();
case TYPE_SAPLING:
return getSaplingTextFile ();
case TYPE_SEEDLING:
return getSeedlingTextFile ();
}
}
byte[] buffer = isGEOSFile () ? getGEOSBuffer () : getBuffer ();
byte[] exactBuffer = getExactBuffer (buffer);
try
{
switch (fileType)
{
case FILE_TYPE_BINARY:
case FILE_TYPE_RELOCATABLE:
case FILE_TYPE_SYS:
if (ShapeTable.isShapeTable (exactBuffer))
file = new ShapeTable (name, exactBuffer);
else if (SimpleText.isHTML (exactBuffer))
file = new SimpleText (name, exactBuffer);
else if (HiResImage.isGif (exactBuffer))
file = new HiResImage (name, exactBuffer);
else if ((endOfFile == 0x1FF8 || endOfFile == 0x1FFF || endOfFile == 0x2000
|| endOfFile == 0x4000)
&& (auxType == 0x1FFF || auxType == 0x2000 || auxType == 0x4000))
file = new HiResImage (name, exactBuffer);
else if (endOfFile == 38400 && name.startsWith ("LVL."))
file = new LodeRunner (name, exactBuffer);
else
{
file = new AssemblerProgram (name, exactBuffer, auxType);
if (exactBuffer.length < buffer.length)
((AssemblerProgram) file)
.setExtraBuffer (buffer, exactBuffer.length,
buffer.length - exactBuffer.length);
}
break;
case FILE_TYPE_TEXT:
assert auxType == 0; // auxType > 0 handled above
if (name.endsWith (".S"))
file = new MerlinSource (name, exactBuffer, auxType, endOfFile);
else
file = new TextFile (name, exactBuffer, auxType, endOfFile);
break;
case FILE_TYPE_APPLESOFT_BASIC:
file = new BasicProgram (name, exactBuffer);
break;
case FILE_TYPE_INTEGER_BASIC:
file = new IntegerBasicProgram (name, exactBuffer);
break;
case FILE_TYPE_DIRECTORY:
VolumeDirectoryHeader vdh = ((ProdosDisk) parentDisk).vdh;
file = new ProdosDirectory (parentDisk, name, buffer, vdh.totalBlocks,
vdh.freeBlocks, vdh.usedBlocks);
break;
case FILE_TYPE_APPLESOFT_BASIC_VARS:
if (endOfFile == 0)
{
System.out.println ("Stored Variables EOF = 0");
file = new StoredVariables (name, buffer);
}
else
file = new StoredVariables (name, exactBuffer);
break;
case FILE_TYPE_APPLETALK:
file = new DefaultAppleFile (name + " (Appletalk file)", buffer);
break;
case FILE_TYPE_AWP:
file = new AppleworksWPFile (name + " (Appleworks Word Processor)", buffer);
break;
case FILE_TYPE_ADB:
file = new AppleworksADBFile (name + " (Appleworks Database File)", buffer);
break;
case FILE_TYPE_ASP:
file = new AppleworksSSFile (name + " (Appleworks Spreadsheet File)", buffer);
break;
case FILE_TYPE_ASM_SOURCE:
file = new SimpleText (name, exactBuffer);
break;
case FILE_TYPE_ICN:
file = new IconFile (name, exactBuffer);
break;
case FILE_TYPE_PNT:
file = new HiResImage (name, exactBuffer, fileType, auxType);
break;
case FILE_TYPE_PIC:
file = new HiResImage (name, exactBuffer, fileType, auxType);
break;
default:
System.out.format ("Unknown file type : %02X%n", fileType);
if (fileType == 0xB3)
file = new DefaultAppleFile (name, exactBuffer,
"S16 Apple IIgs Application Program");
else
file = new DefaultAppleFile (name, exactBuffer);
}
}
catch (Exception e)
{
file = new ErrorMessageFile (name, buffer, e);
e.printStackTrace ();
}
return file;
}
private byte[] getExactBuffer (byte[] buffer)
{
byte[] exactBuffer;
if (buffer.length < endOfFile)
{
exactBuffer = new byte[buffer.length];
System.arraycopy (buffer, 0, exactBuffer, 0, buffer.length);
}
else if (buffer.length == endOfFile)
exactBuffer = buffer;
else
{
exactBuffer = new byte[endOfFile];
System.arraycopy (buffer, 0, exactBuffer, 0, endOfFile);
}
return exactBuffer;
}
private DataSource getTreeTextFile ()
{
List<TextBuffer> buffers = new ArrayList<TextBuffer> ();
List<DiskAddress> addresses = new ArrayList<DiskAddress> ();
int logicalBlock = 0;
byte[] mainIndexBuffer = disk.readSector (keyPtr);
for (int i = 0; i < 256; i++)
{
int indexBlock =
HexFormatter.intValue (mainIndexBuffer[i], mainIndexBuffer[i + 256]);
if (indexBlock > 0)
logicalBlock = readIndexBlock (indexBlock, addresses, buffers, logicalBlock);
else
{
if (addresses.size () > 0)
{
byte[] tempBuffer = disk.readSectors (addresses);
buffers.add (new TextBuffer (tempBuffer, auxType,
logicalBlock - addresses.size ()));
addresses.clear ();
}
logicalBlock += 256;
}
}
if (buffers.size () == 1 && name.endsWith (".S"))
return new MerlinSource (name, buffers.get (0).buffer, auxType, endOfFile);
return new TextFile (name, buffers, auxType, endOfFile);
}
private DataSource getSaplingTextFile ()
{
List<TextBuffer> buffers = new ArrayList<TextBuffer> ();
List<DiskAddress> addresses = new ArrayList<DiskAddress> ();
readIndexBlock (keyPtr, addresses, buffers, 0);
if (buffers.size () == 1 && name.endsWith (".S"))
return new MerlinSource (name, buffers.get (0).buffer, auxType, endOfFile);
return new TextFile (name, buffers, auxType, endOfFile);
}
private DataSource getSeedlingTextFile ()
{
byte[] buffer = getBuffer ();
if (endOfFile < buffer.length)
{
byte[] exactBuffer = new byte[endOfFile];
System.arraycopy (buffer, 0, exactBuffer, 0, endOfFile);
buffer = exactBuffer;
}
if (name.endsWith (".S"))
return new MerlinSource (name, buffer, auxType, endOfFile);
return new TextFile (name, buffer, auxType, endOfFile);
}
private byte[] getBuffer ()
{
switch (storageType)
{
case TYPE_SEEDLING:
case TYPE_SAPLING:
case TYPE_TREE:
return disk.readSectors (dataBlocks);
case TYPE_GSOS_EXTENDED_FILE:
// this will return the data fork and the resource fork concatenated
return disk.readSectors (dataBlocks);
case TYPE_SUBDIRECTORY:
byte[] fullBuffer = new byte[dataBlocks.size () * BLOCK_ENTRY_SIZE]; // 39 * 13 = 507
int offset = 0;
for (DiskAddress da : dataBlocks)
{
byte[] buffer = disk.readSector (da);
System.arraycopy (buffer, 4, fullBuffer, offset, BLOCK_ENTRY_SIZE);
offset += BLOCK_ENTRY_SIZE;
}
return fullBuffer;
default:
System.out.println ("Unknown storage type in getBuffer : " + storageType);
return new byte[512];
}
}
private byte[] getGEOSBuffer ()
{
switch (storageType)
{
case TYPE_SEEDLING:
System.out.println ("Seedling GEOS file : " + name); // not sure if this is possible
return disk.readSectors (dataBlocks);
case TYPE_SAPLING:
return getIndexFile (keyPtr);
case TYPE_TREE:
return getMasterIndexFile (keyPtr);
default:
System.out.println ("Unknown storage type for GEOS file : " + storageType);
return new byte[512];
}
}
private byte[] getMasterIndexFile (int keyPtr)
{
byte[] buffer = disk.readSector (keyPtr);
int length = HexFormatter.intValue (buffer[0xFF], buffer[0x1FF]);
byte[] fileBuffer = new byte[length];
int ptr = 0;
for (int i = 0; i < 0x80; i++)
{
int block = HexFormatter.intValue (buffer[i], buffer[i + 256]);
if (block == 0)
break;
if (block == 0xFFFF) // should this insert 131,072 zeroes?
continue;
byte[] temp = getIndexFile (block);
System.arraycopy (temp, 0, fileBuffer, ptr, temp.length);
ptr += temp.length;
}
return fileBuffer;
}
private byte[] getIndexFile (int keyPtr)
{
byte[] buffer = disk.readSector (keyPtr);
int length = HexFormatter.intValue (buffer[0xFF], buffer[0x1FF]);
byte[] fileBuffer = new byte[length];
for (int i = 0; i < 0x80; i++)
{
int block = HexFormatter.intValue (buffer[i], buffer[i + 256]);
if (block == 0)
break;
if (block == 0xFFFF) // should this insert 512 zeroes?
continue;
byte[] temp = disk.readSector (block);
System.arraycopy (temp, 0, fileBuffer, i * 512, length > 512 ? 512 : length);
length -= 512;
}
return fileBuffer;
}
private int readIndexBlock (int indexBlock, List<DiskAddress> addresses,
List<TextBuffer> buffers, int logicalBlock)
{
byte[] indexBuffer = disk.readSector (indexBlock);
for (int j = 0; j < 256; j++)
{
int block = HexFormatter.intValue (indexBuffer[j], indexBuffer[j + 256]);
if (block > 0)
addresses.add (disk.getDiskAddress (block));
else if (addresses.size () > 0)
{
byte[] tempBuffer = disk.readSectors (addresses);
buffers
.add (new TextBuffer (tempBuffer, auxType, logicalBlock - addresses.size ()));
addresses.clear ();
}
logicalBlock++;
}
return logicalBlock;
}
@Override
public List<DiskAddress> getSectors ()
{
List<DiskAddress> sectors = new ArrayList<DiskAddress> ();
sectors.add (catalogBlock);
if (masterIndexBlock != null)
sectors.add (masterIndexBlock);
sectors.addAll (indexBlocks);
sectors.addAll (dataBlocks);
return sectors;
}
public boolean contains (DiskAddress da)
{
if (da.equals (masterIndexBlock))
return true;
for (DiskAddress block : indexBlocks)
if (block.compareTo (da) == 0)
return true;
for (DiskAddress block : dataBlocks)
if (block.compareTo (da) == 0)
return true;
return false;
}
@Override
public String toString ()
{
if (ProdosConstants.fileTypes[fileType].equals ("DIR"))
return name;
// String locked = (access == 0x01) ? "*" : " ";
String locked = (access == 0x00) ? "*" : " ";
if (true)
return String.format ("%s %03d %s", ProdosConstants.fileTypes[fileType],
blocksUsed, locked)
+ name;
String timeC = created == null ? "" : ProdosDisk.df.format (created.getTime ());
String timeF = modified == null ? "" : ProdosDisk.df.format (modified.getTime ());
return String.format ("%s %s%-30s %3d %,10d %15s %15s",
ProdosConstants.fileTypes[fileType], locked,
parentDirectory.name + "/" + name, blocksUsed, endOfFile, timeC,
timeF);
}
}