diff --git a/src/com/bytezone/diskbrowser/pascal/CatalogEntry.java b/src/com/bytezone/diskbrowser/pascal/CatalogEntry.java index d7814dc..b3ae93f 100644 --- a/src/com/bytezone/diskbrowser/pascal/CatalogEntry.java +++ b/src/com/bytezone/diskbrowser/pascal/CatalogEntry.java @@ -11,7 +11,9 @@ import com.bytezone.diskbrowser.disk.DiskAddress; import com.bytezone.diskbrowser.disk.FormattedDisk; import com.bytezone.diskbrowser.utilities.HexFormatter; +// -----------------------------------------------------------------------------------// abstract class CatalogEntry implements AppleFileSource +// -----------------------------------------------------------------------------------// { protected AbstractFile file; protected final PascalDisk parent; @@ -23,7 +25,9 @@ abstract class CatalogEntry implements AppleFileSource protected int bytesUsedInLastBlock; protected final List blocks = new ArrayList<> (); - public CatalogEntry (PascalDisk parent, byte[] buffer) + // ---------------------------------------------------------------------------------// + CatalogEntry (PascalDisk parent, byte[] buffer) + // ---------------------------------------------------------------------------------// { this.parent = parent; @@ -39,8 +43,10 @@ abstract class CatalogEntry implements AppleFileSource blocks.add (disk.getDiskAddress (i)); } + // ---------------------------------------------------------------------------------// @Override public boolean contains (DiskAddress da) + // ---------------------------------------------------------------------------------// { for (DiskAddress sector : blocks) if (sector.matches (da)) @@ -48,27 +54,35 @@ abstract class CatalogEntry implements AppleFileSource return false; } + // ---------------------------------------------------------------------------------// @Override public List getSectors () + // ---------------------------------------------------------------------------------// { List sectors = new ArrayList<> (blocks); return sectors; } + // ---------------------------------------------------------------------------------// @Override public FormattedDisk getFormattedDisk () + // ---------------------------------------------------------------------------------// { return parent; } + // ---------------------------------------------------------------------------------// @Override public String getUniqueName () + // ---------------------------------------------------------------------------------// { return name; } + // ---------------------------------------------------------------------------------// @Override public String toString () + // ---------------------------------------------------------------------------------// { int size = lastBlock - firstBlock; String fileTypeText = fileType < 0 || fileType >= parent.fileTypes.length ? "????" diff --git a/src/com/bytezone/diskbrowser/pascal/FileEntry.java b/src/com/bytezone/diskbrowser/pascal/FileEntry.java index 7fefa8b..f7d814e 100644 --- a/src/com/bytezone/diskbrowser/pascal/FileEntry.java +++ b/src/com/bytezone/diskbrowser/pascal/FileEntry.java @@ -2,15 +2,26 @@ package com.bytezone.diskbrowser.pascal; import javax.swing.tree.DefaultMutableTreeNode; -import com.bytezone.diskbrowser.applefile.*; +import com.bytezone.diskbrowser.applefile.AbstractFile; +import com.bytezone.diskbrowser.applefile.AssemblerProgram; +import com.bytezone.diskbrowser.applefile.Charset; +import com.bytezone.diskbrowser.applefile.DefaultAppleFile; +import com.bytezone.diskbrowser.applefile.PascalCode; +import com.bytezone.diskbrowser.applefile.PascalInfo; +import com.bytezone.diskbrowser.applefile.PascalSegment; +import com.bytezone.diskbrowser.applefile.PascalText; import com.bytezone.diskbrowser.utilities.FileFormatException; import com.bytezone.diskbrowser.utilities.HexFormatter; +// -----------------------------------------------------------------------------------// public class FileEntry extends CatalogEntry +// -----------------------------------------------------------------------------------// { private DefaultMutableTreeNode node; + // ---------------------------------------------------------------------------------// public FileEntry (PascalDisk parent, byte[] buffer) + // ---------------------------------------------------------------------------------// { super (parent, buffer); @@ -30,18 +41,24 @@ public class FileEntry extends CatalogEntry } } + // ---------------------------------------------------------------------------------// void setNode (DefaultMutableTreeNode node) + // ---------------------------------------------------------------------------------// { this.node = node; } + // ---------------------------------------------------------------------------------// public void setFile (AbstractFile file) + // ---------------------------------------------------------------------------------// { this.file = file; } + // ---------------------------------------------------------------------------------// @Override public AbstractFile getDataSource () + // ---------------------------------------------------------------------------------// { if (file != null) return file; @@ -99,7 +116,9 @@ public class FileEntry extends CatalogEntry return file; } + // ---------------------------------------------------------------------------------// private byte[] getExactBuffer () + // ---------------------------------------------------------------------------------// { byte[] buffer = parent.getDisk ().readSectors (blocks); byte[] exactBuffer; diff --git a/src/com/bytezone/diskbrowser/pascal/PascalCatalogSector.java b/src/com/bytezone/diskbrowser/pascal/PascalCatalogSector.java index e3637c5..73b543c 100644 --- a/src/com/bytezone/diskbrowser/pascal/PascalCatalogSector.java +++ b/src/com/bytezone/diskbrowser/pascal/PascalCatalogSector.java @@ -9,19 +9,25 @@ import com.bytezone.diskbrowser.disk.Disk; import com.bytezone.diskbrowser.disk.DiskAddress; import com.bytezone.diskbrowser.utilities.HexFormatter; +// -----------------------------------------------------------------------------------// class PascalCatalogSector extends AbstractSector +// -----------------------------------------------------------------------------------// { private final DateFormat df = DateFormat.getDateInstance (DateFormat.SHORT); private static String[] fileTypes = { "Volume", "Bad", "Code", "Text", "Info", "Data", "Graf", "Foto", "SecureDir" }; - public PascalCatalogSector (Disk disk, byte[] buffer, List diskAddress) + // ---------------------------------------------------------------------------------// + PascalCatalogSector (Disk disk, byte[] buffer, List diskAddress) + // ---------------------------------------------------------------------------------// { super (disk, buffer); } + // ---------------------------------------------------------------------------------// @Override public String createText () + // ---------------------------------------------------------------------------------// { StringBuilder text = getHeader ("Pascal Catalog Sectors"); diff --git a/src/com/bytezone/diskbrowser/pascal/PascalCodeObject.java b/src/com/bytezone/diskbrowser/pascal/PascalCodeObject.java index 707d54b..c8a84e7 100644 --- a/src/com/bytezone/diskbrowser/pascal/PascalCodeObject.java +++ b/src/com/bytezone/diskbrowser/pascal/PascalCodeObject.java @@ -11,13 +11,17 @@ import com.bytezone.diskbrowser.disk.DiskAddress; import com.bytezone.diskbrowser.disk.FormattedDisk; import com.bytezone.diskbrowser.gui.DataSource; +// -----------------------------------------------------------------------------------// class PascalCodeObject implements AppleFileSource +// -----------------------------------------------------------------------------------// { private final PascalDisk parent; private final AbstractFile segment; private final List blocks = new ArrayList<> (); - public PascalCodeObject (PascalDisk parent, PascalSegment segment, int firstBlock) + // ---------------------------------------------------------------------------------// + PascalCodeObject (PascalDisk parent, PascalSegment segment, int firstBlock) + // ---------------------------------------------------------------------------------// { this.parent = parent; this.segment = segment; @@ -30,26 +34,34 @@ class PascalCodeObject implements AppleFileSource blocks.add (disk.getDiskAddress (i)); } + // ---------------------------------------------------------------------------------// @Override public DataSource getDataSource () + // ---------------------------------------------------------------------------------// { return segment; } + // ---------------------------------------------------------------------------------// @Override public FormattedDisk getFormattedDisk () + // ---------------------------------------------------------------------------------// { return parent; } + // ---------------------------------------------------------------------------------// @Override public List getSectors () + // ---------------------------------------------------------------------------------// { return blocks; } + // ---------------------------------------------------------------------------------// @Override public boolean contains (DiskAddress da) + // ---------------------------------------------------------------------------------// { for (DiskAddress sector : blocks) if (sector.matches (da)) @@ -57,14 +69,18 @@ class PascalCodeObject implements AppleFileSource return false; } + // ---------------------------------------------------------------------------------// @Override public String getUniqueName () + // ---------------------------------------------------------------------------------// { return segment.getName (); // this should be fileName/segmentName } + // ---------------------------------------------------------------------------------// @Override public String toString () + // ---------------------------------------------------------------------------------// { return segment.getName (); } diff --git a/src/com/bytezone/diskbrowser/pascal/PascalDisk.java b/src/com/bytezone/diskbrowser/pascal/PascalDisk.java index 7fc024c..0870136 100755 --- a/src/com/bytezone/diskbrowser/pascal/PascalDisk.java +++ b/src/com/bytezone/diskbrowser/pascal/PascalDisk.java @@ -1,286 +1,310 @@ -package com.bytezone.diskbrowser.pascal; - -import java.awt.Color; -import java.text.DateFormat; -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.List; - -import javax.swing.tree.DefaultMutableTreeNode; - -import com.bytezone.diskbrowser.applefile.AppleFileSource; -import com.bytezone.diskbrowser.applefile.BootSector; -import com.bytezone.diskbrowser.disk.*; -import com.bytezone.diskbrowser.gui.DataSource; -import com.bytezone.diskbrowser.utilities.HexFormatter; -import com.bytezone.diskbrowser.wizardry.Relocator; - -public class PascalDisk extends AbstractFormattedDisk -{ - static final int CATALOG_ENTRY_SIZE = 26; - private final DateFormat df = DateFormat.getDateInstance (DateFormat.SHORT); - private final VolumeEntry volumeEntry; - private final PascalCatalogSector diskCatalogSector; - - protected Relocator relocator; - - final String[] fileTypes = - { "Volume", "Bad ", "Code", "Text", "Info", "Data", "Graf", "Foto", "SecureDir" }; - - SectorType diskBootSector = new SectorType ("Boot", Color.lightGray); - SectorType catalogSector = new SectorType ("Catalog", Color.magenta); - SectorType dataSector = new SectorType ("Data", new Color (0, 200, 0)); // green - SectorType codeSector = new SectorType ("Code", Color.red); - SectorType textSector = new SectorType ("Text", Color.blue); - SectorType infoSector = new SectorType ("Info", Color.orange); - SectorType grafSector = new SectorType ("Graf", Color.cyan); - SectorType fotoSector = new SectorType ("Foto", Color.gray); - SectorType badSector = new SectorType ("Bad", Color.darkGray); - - SectorType[] sectors = { catalogSector, badSector, codeSector, textSector, infoSector, - dataSector, grafSector, fotoSector }; - - public PascalDisk (Disk disk) - { - super (disk); - - sectorTypesList.add (diskBootSector); - sectorTypesList.add (catalogSector); - sectorTypesList.add (dataSector); - sectorTypesList.add (codeSector); - sectorTypesList.add (textSector); - sectorTypesList.add (infoSector); - sectorTypesList.add (grafSector); - sectorTypesList.add (fotoSector); - sectorTypesList.add (badSector); - - List blocks = disk.getDiskAddressList (0, 1); // B0, B1 - this.bootSector = new BootSector (disk, disk.readSectors (blocks), "Pascal"); - - for (int i = 0; i < 2; i++) - if (!disk.isSectorEmpty (i)) - { - sectorTypes[i] = diskBootSector; - freeBlocks.set (i, false); - } - - for (int i = 2; i < disk.getTotalBlocks (); i++) - freeBlocks.set (i, true); - - byte[] buffer = disk.readSector (2); - byte[] data = new byte[CATALOG_ENTRY_SIZE]; - System.arraycopy (buffer, 0, data, 0, CATALOG_ENTRY_SIZE); - volumeEntry = new VolumeEntry (this, data); - - DefaultMutableTreeNode root = getCatalogTreeRoot (); - DefaultMutableTreeNode volumeNode = new DefaultMutableTreeNode (volumeEntry); - root.add (volumeNode); - - List sectors = new ArrayList<> (); - int max = Math.min (volumeEntry.lastBlock, disk.getTotalBlocks ()); - for (int i = 2; i < max; i++) - { - DiskAddress da = disk.getDiskAddress (i); - if (!disk.isSectorEmpty (da)) - sectorTypes[i] = catalogSector; - sectors.add (da); - freeBlocks.set (i, false); - } - - diskCatalogSector = - new PascalCatalogSector (disk, disk.readSectors (sectors), sectors); - - // read the catalog - List addresses = new ArrayList<> (); - for (int i = 2; i < max; i++) - addresses.add (disk.getDiskAddress (i)); - buffer = disk.readSectors (addresses); - - // loop through each catalog entry (what if there are deleted files?) - for (int i = 1; i <= volumeEntry.totalFiles; i++) - { - int ptr = i * CATALOG_ENTRY_SIZE; - data = new byte[CATALOG_ENTRY_SIZE]; - System.arraycopy (buffer, ptr, data, 0, CATALOG_ENTRY_SIZE); - FileEntry fileEntry = new FileEntry (this, data); - - fileEntries.add (fileEntry); - DefaultMutableTreeNode node = new DefaultMutableTreeNode (fileEntry); - fileEntry.setNode (node); - - if (fileEntry.fileType == 2) - { - node.setAllowsChildren (true); - fileEntry.getDataSource (); // build segments - } - else - node.setAllowsChildren (false); - - volumeNode.add (node); - for (int j = fileEntry.firstBlock; j < fileEntry.lastBlock; j++) - freeBlocks.set (j, false); - } - - volumeNode.setUserObject (getCatalog ()); - makeNodeVisible (volumeNode.getFirstLeaf ()); - } - - public static boolean isCorrectFormat (AppleDisk disk, boolean debug) - { - disk.setInterleave (1); // should only ever be Prodos - if (checkFormat (disk, debug)) - return true; - disk.setInterleave (0); // see SANE Disk 2.po - if (checkFormat (disk, debug)) - return true; - return false; - } - - public static boolean checkFormat (AppleDisk disk, boolean debug) - { - byte[] buffer = disk.readSector (2); - int nameLength = buffer[6] & 0xFF; - if (nameLength < 1 || nameLength > 7) - { - if (debug) - System.out.println ("bad name length : " + nameLength); - return false; - } - - if (debug) - { - String name = HexFormatter.getPascalString (buffer, 6); - System.out.println ("Name ok : " + name); - } - - int from = HexFormatter.intValue (buffer[0], buffer[1]); - int to = HexFormatter.intValue (buffer[2], buffer[3]); - if (from != 0 || to != 6) - { - if (debug) - System.out.printf ("from: %d, to: %d%n", from, to); - return false; // will only work for floppies! - } - - int blocks = HexFormatter.intValue (buffer[14], buffer[15]); - if (blocks > 280) - { - if (debug) - System.out.printf ("Blocks > 280: %d%n", blocks); - // return false; - } - - List addresses = new ArrayList<> (); - for (int i = 2; i < to; i++) - addresses.add (disk.getDiskAddress (i)); - buffer = disk.readSectors (addresses); - - int files = HexFormatter.intValue (buffer[16], buffer[17]); - if (files < 0 || files > 77) - { - if (debug) - System.out.printf ("Files: %d%n", files); - return false; - } - - if (debug) - System.out.println ("Files found : " + files); - - for (int i = 1; i <= files; i++) - { - int ptr = i * 26; - int firstBlock = HexFormatter.intValue (buffer[ptr], buffer[ptr + 1]); - int lastBlock = HexFormatter.intValue (buffer[ptr + 2], buffer[ptr + 3]); - int kind = HexFormatter.intValue (buffer[ptr + 4], buffer[ptr + 5]); - if (lastBlock < firstBlock) - return false; - if (kind == 0) - return false; - nameLength = buffer[ptr + 6] & 0xFF; - if (nameLength < 1 || nameLength > 15) - return false; - int lastByte = HexFormatter.intValue (buffer[ptr + 22], buffer[ptr + 23]); - GregorianCalendar date = HexFormatter.getPascalDate (buffer, 24); - if (debug) - System.out.printf ("%4d %4d %d %-15s %d %s%n", firstBlock, lastBlock, kind, - new String (buffer, ptr + 7, nameLength), lastByte, date); - } - - return true; - } - - @Override - public DataSource getFormattedSector (DiskAddress da) - { - SectorType st = sectorTypes[da.getBlock ()]; - if (st == diskBootSector) - return bootSector; - if (st == catalogSector) - return diskCatalogSector; - String name = getSectorFilename (da); - if (name != null) - return new DefaultSector (name, disk, disk.readSector (da), da); - return super.getFormattedSector (da); - } - - @Override - public String getSectorFilename (DiskAddress da) - { - for (AppleFileSource ce : fileEntries) - if (((CatalogEntry) ce).contains (da)) - return ((CatalogEntry) ce).name; - return null; - } - - @Override - public List getFileSectors (int fileNo) - { - if (fileNo < 0 || fileNo >= fileEntries.size ()) - return null; - return fileEntries.get (fileNo).getSectors (); - } - - public DataSource getFile (int fileNo) - { - if (fileNo < 0 || fileNo >= fileEntries.size ()) - return null; - return fileEntries.get (fileNo).getDataSource (); - } - - @Override - public AppleFileSource getCatalog () - { - String newLine = String.format ("%n"); - String newLine2 = newLine + newLine; - String line = "---- --------------- ---- -------- ------- ---- ---- ----" - + newLine; - String date = - volumeEntry.date == null ? "--" : df.format (volumeEntry.date.getTime ()); - StringBuilder text = new StringBuilder (); - text.append ("Disk : " + getDisplayPath () + newLine2); - text.append ("Volume : " + volumeEntry.name + newLine); - text.append ("Date : " + date + newLine2); - text.append ( - "Blks Name Type Date Length Frst Last Blks\n"); - text.append (line); - - int usedBlocks = 6; - for (AppleFileSource fe : fileEntries) - { - FileEntry ce = (FileEntry) fe; - int size = ce.lastBlock - ce.firstBlock; - usedBlocks += size; - date = ce.date == null ? "--" : df.format (ce.date.getTime ()); - int bytes = (size - 1) * 512 + ce.bytesUsedInLastBlock; - String fileType = ce.fileType < 0 || ce.fileType >= fileTypes.length ? "????" - : fileTypes[ce.fileType]; - text.append (String.format ("%4d %-15s %s %8s %,8d $%03X $%03X $%03X%n", - size, ce.name, fileType, date, bytes, ce.firstBlock, ce.lastBlock, size)); - } - text.append (line); - text.append ( - String.format ("Blocks free : %3d Blocks used : %3d Total blocks : %3d%n", - (volumeEntry.totalBlocks - usedBlocks), usedBlocks, volumeEntry.totalBlocks)); - return new DefaultAppleFileSource (volumeEntry.name, text.toString (), this); - } +package com.bytezone.diskbrowser.pascal; + +import java.awt.Color; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; + +import javax.swing.tree.DefaultMutableTreeNode; + +import com.bytezone.diskbrowser.applefile.AppleFileSource; +import com.bytezone.diskbrowser.applefile.BootSector; +import com.bytezone.diskbrowser.disk.AbstractFormattedDisk; +import com.bytezone.diskbrowser.disk.AppleDisk; +import com.bytezone.diskbrowser.disk.DefaultAppleFileSource; +import com.bytezone.diskbrowser.disk.DefaultSector; +import com.bytezone.diskbrowser.disk.Disk; +import com.bytezone.diskbrowser.disk.DiskAddress; +import com.bytezone.diskbrowser.disk.SectorType; +import com.bytezone.diskbrowser.gui.DataSource; +import com.bytezone.diskbrowser.utilities.HexFormatter; +import com.bytezone.diskbrowser.wizardry.Relocator; + +// -----------------------------------------------------------------------------------// +public class PascalDisk extends AbstractFormattedDisk +// -----------------------------------------------------------------------------------// +{ + static final int CATALOG_ENTRY_SIZE = 26; + private final DateFormat df = DateFormat.getDateInstance (DateFormat.SHORT); + private final VolumeEntry volumeEntry; + private final PascalCatalogSector diskCatalogSector; + + protected Relocator relocator; + + final String[] fileTypes = + { "Volume", "Bad ", "Code", "Text", "Info", "Data", "Graf", "Foto", "SecureDir" }; + + SectorType diskBootSector = new SectorType ("Boot", Color.lightGray); + SectorType catalogSector = new SectorType ("Catalog", Color.magenta); + SectorType dataSector = new SectorType ("Data", new Color (0, 200, 0)); // green + SectorType codeSector = new SectorType ("Code", Color.red); + SectorType textSector = new SectorType ("Text", Color.blue); + SectorType infoSector = new SectorType ("Info", Color.orange); + SectorType grafSector = new SectorType ("Graf", Color.cyan); + SectorType fotoSector = new SectorType ("Foto", Color.gray); + SectorType badSector = new SectorType ("Bad", Color.darkGray); + + SectorType[] sectors = { catalogSector, badSector, codeSector, textSector, infoSector, + dataSector, grafSector, fotoSector }; + + // ---------------------------------------------------------------------------------// + public PascalDisk (Disk disk) + // ---------------------------------------------------------------------------------// + { + super (disk); + + sectorTypesList.add (diskBootSector); + sectorTypesList.add (catalogSector); + sectorTypesList.add (dataSector); + sectorTypesList.add (codeSector); + sectorTypesList.add (textSector); + sectorTypesList.add (infoSector); + sectorTypesList.add (grafSector); + sectorTypesList.add (fotoSector); + sectorTypesList.add (badSector); + + List blocks = disk.getDiskAddressList (0, 1); // B0, B1 + this.bootSector = new BootSector (disk, disk.readSectors (blocks), "Pascal"); + + for (int i = 0; i < 2; i++) + if (!disk.isSectorEmpty (i)) + { + sectorTypes[i] = diskBootSector; + freeBlocks.set (i, false); + } + + for (int i = 2; i < disk.getTotalBlocks (); i++) + freeBlocks.set (i, true); + + byte[] buffer = disk.readSector (2); + byte[] data = new byte[CATALOG_ENTRY_SIZE]; + System.arraycopy (buffer, 0, data, 0, CATALOG_ENTRY_SIZE); + volumeEntry = new VolumeEntry (this, data); + + DefaultMutableTreeNode root = getCatalogTreeRoot (); + DefaultMutableTreeNode volumeNode = new DefaultMutableTreeNode (volumeEntry); + root.add (volumeNode); + + List sectors = new ArrayList<> (); + int max = Math.min (volumeEntry.lastBlock, disk.getTotalBlocks ()); + for (int i = 2; i < max; i++) + { + DiskAddress da = disk.getDiskAddress (i); + if (!disk.isSectorEmpty (da)) + sectorTypes[i] = catalogSector; + sectors.add (da); + freeBlocks.set (i, false); + } + + diskCatalogSector = + new PascalCatalogSector (disk, disk.readSectors (sectors), sectors); + + // read the catalog + List addresses = new ArrayList<> (); + for (int i = 2; i < max; i++) + addresses.add (disk.getDiskAddress (i)); + buffer = disk.readSectors (addresses); + + // loop through each catalog entry (what if there are deleted files?) + for (int i = 1; i <= volumeEntry.totalFiles; i++) + { + int ptr = i * CATALOG_ENTRY_SIZE; + data = new byte[CATALOG_ENTRY_SIZE]; + System.arraycopy (buffer, ptr, data, 0, CATALOG_ENTRY_SIZE); + FileEntry fileEntry = new FileEntry (this, data); + + fileEntries.add (fileEntry); + DefaultMutableTreeNode node = new DefaultMutableTreeNode (fileEntry); + fileEntry.setNode (node); + + if (fileEntry.fileType == 2) + { + node.setAllowsChildren (true); + fileEntry.getDataSource (); // build segments + } + else + node.setAllowsChildren (false); + + volumeNode.add (node); + for (int j = fileEntry.firstBlock; j < fileEntry.lastBlock; j++) + freeBlocks.set (j, false); + } + + volumeNode.setUserObject (getCatalog ()); + makeNodeVisible (volumeNode.getFirstLeaf ()); + } + + // ---------------------------------------------------------------------------------// + public static boolean isCorrectFormat (AppleDisk disk, boolean debug) + // ---------------------------------------------------------------------------------// + { + disk.setInterleave (1); // should only ever be Prodos + if (checkFormat (disk, debug)) + return true; + disk.setInterleave (0); // see SANE Disk 2.po + if (checkFormat (disk, debug)) + return true; + return false; + } + + // ---------------------------------------------------------------------------------// + public static boolean checkFormat (AppleDisk disk, boolean debug) + // ---------------------------------------------------------------------------------// + { + byte[] buffer = disk.readSector (2); + int nameLength = buffer[6] & 0xFF; + if (nameLength < 1 || nameLength > 7) + { + if (debug) + System.out.println ("bad name length : " + nameLength); + return false; + } + + if (debug) + { + String name = HexFormatter.getPascalString (buffer, 6); + System.out.println ("Name ok : " + name); + } + + int from = HexFormatter.intValue (buffer[0], buffer[1]); + int to = HexFormatter.intValue (buffer[2], buffer[3]); + if (from != 0 || to != 6) + { + if (debug) + System.out.printf ("from: %d, to: %d%n", from, to); + return false; // will only work for floppies! + } + + int blocks = HexFormatter.intValue (buffer[14], buffer[15]); + if (blocks > 280) + { + if (debug) + System.out.printf ("Blocks > 280: %d%n", blocks); + // return false; + } + + List addresses = new ArrayList<> (); + for (int i = 2; i < to; i++) + addresses.add (disk.getDiskAddress (i)); + buffer = disk.readSectors (addresses); + + int files = HexFormatter.intValue (buffer[16], buffer[17]); + if (files < 0 || files > 77) + { + if (debug) + System.out.printf ("Files: %d%n", files); + return false; + } + + if (debug) + System.out.println ("Files found : " + files); + + for (int i = 1; i <= files; i++) + { + int ptr = i * 26; + int firstBlock = HexFormatter.intValue (buffer[ptr], buffer[ptr + 1]); + int lastBlock = HexFormatter.intValue (buffer[ptr + 2], buffer[ptr + 3]); + int kind = HexFormatter.intValue (buffer[ptr + 4], buffer[ptr + 5]); + if (lastBlock < firstBlock) + return false; + if (kind == 0) + return false; + nameLength = buffer[ptr + 6] & 0xFF; + if (nameLength < 1 || nameLength > 15) + return false; + int lastByte = HexFormatter.intValue (buffer[ptr + 22], buffer[ptr + 23]); + GregorianCalendar date = HexFormatter.getPascalDate (buffer, 24); + if (debug) + System.out.printf ("%4d %4d %d %-15s %d %s%n", firstBlock, lastBlock, kind, + new String (buffer, ptr + 7, nameLength), lastByte, date); + } + + return true; + } + + // ---------------------------------------------------------------------------------// + @Override + public DataSource getFormattedSector (DiskAddress da) + // ---------------------------------------------------------------------------------// + { + SectorType st = sectorTypes[da.getBlock ()]; + if (st == diskBootSector) + return bootSector; + if (st == catalogSector) + return diskCatalogSector; + String name = getSectorFilename (da); + if (name != null) + return new DefaultSector (name, disk, disk.readSector (da), da); + return super.getFormattedSector (da); + } + + // ---------------------------------------------------------------------------------// + @Override + public String getSectorFilename (DiskAddress da) + // ---------------------------------------------------------------------------------// + { + for (AppleFileSource ce : fileEntries) + if (((CatalogEntry) ce).contains (da)) + return ((CatalogEntry) ce).name; + return null; + } + + // ---------------------------------------------------------------------------------// + @Override + public List getFileSectors (int fileNo) + // ---------------------------------------------------------------------------------// + { + if (fileNo < 0 || fileNo >= fileEntries.size ()) + return null; + return fileEntries.get (fileNo).getSectors (); + } + + // ---------------------------------------------------------------------------------// + public DataSource getFile (int fileNo) + // ---------------------------------------------------------------------------------// + { + if (fileNo < 0 || fileNo >= fileEntries.size ()) + return null; + return fileEntries.get (fileNo).getDataSource (); + } + + // ---------------------------------------------------------------------------------// + @Override + public AppleFileSource getCatalog () + // ---------------------------------------------------------------------------------// + { + String newLine = String.format ("%n"); + String newLine2 = newLine + newLine; + String line = "---- --------------- ---- -------- ------- ---- ---- ----" + + newLine; + String date = + volumeEntry.date == null ? "--" : df.format (volumeEntry.date.getTime ()); + StringBuilder text = new StringBuilder (); + text.append ("Disk : " + getDisplayPath () + newLine2); + text.append ("Volume : " + volumeEntry.name + newLine); + text.append ("Date : " + date + newLine2); + text.append ( + "Blks Name Type Date Length Frst Last Blks\n"); + text.append (line); + + int usedBlocks = 6; + for (AppleFileSource fe : fileEntries) + { + FileEntry ce = (FileEntry) fe; + int size = ce.lastBlock - ce.firstBlock; + usedBlocks += size; + date = ce.date == null ? "--" : df.format (ce.date.getTime ()); + int bytes = (size - 1) * 512 + ce.bytesUsedInLastBlock; + String fileType = ce.fileType < 0 || ce.fileType >= fileTypes.length ? "????" + : fileTypes[ce.fileType]; + text.append (String.format ("%4d %-15s %s %8s %,8d $%03X $%03X $%03X%n", + size, ce.name, fileType, date, bytes, ce.firstBlock, ce.lastBlock, size)); + } + text.append (line); + text.append ( + String.format ("Blocks free : %3d Blocks used : %3d Total blocks : %3d%n", + (volumeEntry.totalBlocks - usedBlocks), usedBlocks, volumeEntry.totalBlocks)); + return new DefaultAppleFileSource (volumeEntry.name, text.toString (), this); + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/pascal/VolumeEntry.java b/src/com/bytezone/diskbrowser/pascal/VolumeEntry.java index 4a51ef9..36b2ded 100644 --- a/src/com/bytezone/diskbrowser/pascal/VolumeEntry.java +++ b/src/com/bytezone/diskbrowser/pascal/VolumeEntry.java @@ -4,12 +4,16 @@ import com.bytezone.diskbrowser.applefile.AbstractFile; import com.bytezone.diskbrowser.applefile.DefaultAppleFile; import com.bytezone.diskbrowser.utilities.HexFormatter; +// -----------------------------------------------------------------------------------// class VolumeEntry extends CatalogEntry +// -----------------------------------------------------------------------------------// { final int totalFiles; final int totalBlocks; - public VolumeEntry (PascalDisk parent, byte[] buffer) + // ---------------------------------------------------------------------------------// + VolumeEntry (PascalDisk parent, byte[] buffer) + // ---------------------------------------------------------------------------------// { super (parent, buffer); @@ -18,8 +22,10 @@ class VolumeEntry extends CatalogEntry date = HexFormatter.getPascalDate (buffer, 20); // 2 bytes } + // ---------------------------------------------------------------------------------// @Override public AbstractFile getDataSource () + // ---------------------------------------------------------------------------------// { if (file != null) return file; diff --git a/src/com/bytezone/diskbrowser/prodos/CatalogEntry.java b/src/com/bytezone/diskbrowser/prodos/CatalogEntry.java index 96c18ca..cfe0687 100755 --- a/src/com/bytezone/diskbrowser/prodos/CatalogEntry.java +++ b/src/com/bytezone/diskbrowser/prodos/CatalogEntry.java @@ -1,74 +1,86 @@ -package com.bytezone.diskbrowser.prodos; - -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.List; - -import com.bytezone.diskbrowser.applefile.AppleFileSource; -import com.bytezone.diskbrowser.disk.Disk; -import com.bytezone.diskbrowser.disk.DiskAddress; -import com.bytezone.diskbrowser.disk.FormattedDisk; -import com.bytezone.diskbrowser.utilities.HexFormatter; - -abstract class CatalogEntry implements AppleFileSource -{ - ProdosDisk parentDisk; - DirectoryHeader parentDirectory; - String name; - int storageType; - GregorianCalendar created; - int version; - int minVersion; - int access; - List dataBlocks = new ArrayList<> (); - Disk disk; - - public CatalogEntry (ProdosDisk parentDisk, byte[] entryBuffer) - { - this.parentDisk = parentDisk; - this.disk = parentDisk.getDisk (); - name = HexFormatter.getString (entryBuffer, 1, entryBuffer[0] & 0x0F); - storageType = (entryBuffer[0] & 0xF0) >> 4; - created = HexFormatter.getAppleDate (entryBuffer, 24); - version = entryBuffer[28] & 0xFF; - minVersion = entryBuffer[29] & 0xFF; - access = entryBuffer[30] & 0xFF; - } - - @Override - public String getUniqueName () - { - if (parentDirectory == null) - return name; - return parentDirectory.getUniqueName () + "/" + name; - } - - @Override - public FormattedDisk getFormattedDisk () - { - return parentDisk; - } - - @Override - public boolean contains (DiskAddress da) - { - for (DiskAddress sector : dataBlocks) - if (sector.matches (da)) - return true; - return false; - } - - @Override - public String toString () - { - StringBuilder text = new StringBuilder (); - - text.append (String.format ("Name .......... %s%n", name)); - text.append (String.format ("Storage type... %02X%n", storageType)); - text.append (String.format ("Created ....... %s%n", - created == null ? "" : parentDisk.df.format (created.getTime ()))); - text.append (String.format ("Version ....... %d%n", version)); - - return text.toString (); - } +package com.bytezone.diskbrowser.prodos; + +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; + +import com.bytezone.diskbrowser.applefile.AppleFileSource; +import com.bytezone.diskbrowser.disk.Disk; +import com.bytezone.diskbrowser.disk.DiskAddress; +import com.bytezone.diskbrowser.disk.FormattedDisk; +import com.bytezone.diskbrowser.utilities.HexFormatter; + +// -----------------------------------------------------------------------------------// +abstract class CatalogEntry implements AppleFileSource +// -----------------------------------------------------------------------------------// +{ + ProdosDisk parentDisk; + DirectoryHeader parentDirectory; + String name; + int storageType; + GregorianCalendar created; + int version; + int minVersion; + int access; + List dataBlocks = new ArrayList<> (); + Disk disk; + + // ---------------------------------------------------------------------------------// + CatalogEntry (ProdosDisk parentDisk, byte[] entryBuffer) + // ---------------------------------------------------------------------------------// + { + this.parentDisk = parentDisk; + this.disk = parentDisk.getDisk (); + name = HexFormatter.getString (entryBuffer, 1, entryBuffer[0] & 0x0F); + storageType = (entryBuffer[0] & 0xF0) >> 4; + created = HexFormatter.getAppleDate (entryBuffer, 24); + version = entryBuffer[28] & 0xFF; + minVersion = entryBuffer[29] & 0xFF; + access = entryBuffer[30] & 0xFF; + } + + // ---------------------------------------------------------------------------------// + @Override + public String getUniqueName () + // ---------------------------------------------------------------------------------// + { + if (parentDirectory == null) + return name; + return parentDirectory.getUniqueName () + "/" + name; + } + + // ---------------------------------------------------------------------------------// + @Override + public FormattedDisk getFormattedDisk () + // ---------------------------------------------------------------------------------// + { + return parentDisk; + } + + // ---------------------------------------------------------------------------------// + @Override + public boolean contains (DiskAddress da) + // ---------------------------------------------------------------------------------// + { + for (DiskAddress sector : dataBlocks) + if (sector.matches (da)) + return true; + return false; + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + + text.append (String.format ("Name .......... %s%n", name)); + text.append (String.format ("Storage type... %02X%n", storageType)); + text.append (String.format ("Created ....... %s%n", + created == null ? "" : parentDisk.df.format (created.getTime ()))); + text.append (String.format ("Version ....... %d%n", version)); + + return text.toString (); + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/prodos/DirectoryHeader.java b/src/com/bytezone/diskbrowser/prodos/DirectoryHeader.java index 2057460..21ede70 100755 --- a/src/com/bytezone/diskbrowser/prodos/DirectoryHeader.java +++ b/src/com/bytezone/diskbrowser/prodos/DirectoryHeader.java @@ -1,19 +1,23 @@ -package com.bytezone.diskbrowser.prodos; - -import com.bytezone.diskbrowser.utilities.HexFormatter; - -abstract class DirectoryHeader extends CatalogEntry -{ - final int entryLength; - final int entriesPerBlock; - final int fileCount; - - public DirectoryHeader (ProdosDisk parentDisk, byte[] entryBuffer) - { - super (parentDisk, entryBuffer); - - entryLength = entryBuffer[31] & 0xFF; - entriesPerBlock = entryBuffer[32] & 0xFF; - fileCount = HexFormatter.intValue (entryBuffer[33], entryBuffer[34]); - } +package com.bytezone.diskbrowser.prodos; + +import com.bytezone.diskbrowser.utilities.HexFormatter; + +// -----------------------------------------------------------------------------------// +abstract class DirectoryHeader extends CatalogEntry +// -----------------------------------------------------------------------------------// +{ + final int entryLength; + final int entriesPerBlock; + final int fileCount; + + // ---------------------------------------------------------------------------------// + DirectoryHeader (ProdosDisk parentDisk, byte[] entryBuffer) + // ---------------------------------------------------------------------------------// + { + super (parentDisk, entryBuffer); + + entryLength = entryBuffer[31] & 0xFF; + entriesPerBlock = entryBuffer[32] & 0xFF; + fileCount = HexFormatter.intValue (entryBuffer[33], entryBuffer[34]); + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/prodos/FileEntry.java b/src/com/bytezone/diskbrowser/prodos/FileEntry.java index 1a49adf..41632e2 100755 --- a/src/com/bytezone/diskbrowser/prodos/FileEntry.java +++ b/src/com/bytezone/diskbrowser/prodos/FileEntry.java @@ -1,713 +1,713 @@ -package com.bytezone.diskbrowser.prodos; - -import java.util.ArrayList; -import java.util.GregorianCalendar; -import java.util.List; - -import com.bytezone.diskbrowser.applefile.ApplesoftBasicProgram; -import com.bytezone.diskbrowser.applefile.AssemblerProgram; -import com.bytezone.diskbrowser.applefile.BasicProgramGS; -import com.bytezone.diskbrowser.applefile.CharacterRom; -import com.bytezone.diskbrowser.applefile.DefaultAppleFile; -import com.bytezone.diskbrowser.applefile.DeviceDriver; -import com.bytezone.diskbrowser.applefile.DoubleHiResImage; -import com.bytezone.diskbrowser.applefile.ErrorMessageFile; -import com.bytezone.diskbrowser.applefile.FaddenHiResImage; -import com.bytezone.diskbrowser.applefile.FileSystemTranslator; -import com.bytezone.diskbrowser.applefile.FileTypeDescriptorTable; -import com.bytezone.diskbrowser.applefile.FontFile; -import com.bytezone.diskbrowser.applefile.HiResImage; -import com.bytezone.diskbrowser.applefile.IconFile; -import com.bytezone.diskbrowser.applefile.IntegerBasicProgram; -import com.bytezone.diskbrowser.applefile.LodeRunner; -import com.bytezone.diskbrowser.applefile.MerlinSource; -import com.bytezone.diskbrowser.applefile.OriginalHiResImage; -import com.bytezone.diskbrowser.applefile.QuickDrawFont; -import com.bytezone.diskbrowser.applefile.SHRPictureFile1; -import com.bytezone.diskbrowser.applefile.SHRPictureFile2; -import com.bytezone.diskbrowser.applefile.ShapeTable; -import com.bytezone.diskbrowser.applefile.SimpleText; -import com.bytezone.diskbrowser.applefile.StoredVariables; -import com.bytezone.diskbrowser.applefile.TextBuffer; -import com.bytezone.diskbrowser.applefile.TextFile; -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; -import com.bytezone.diskbrowser.utilities.HexFormatter; - -// - 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 indexBlocks = new ArrayList<> (); - - private boolean invalid; - private FileEntry link; - - // ---------------------------------------------------------------------------------// - public FileEntry (ProdosDisk fDisk, byte[] entryBuffer, DirectoryHeader parent, - int parentBlock) - // ---------------------------------------------------------------------------------// - { - super (fDisk, entryBuffer); - - assert parent != null; - this.parentDirectory = parent; - this.catalogBlock = this.disk.getDiskAddress (parentBlock); - - fileType = entryBuffer[0x10] & 0xFF; - keyPtr = HexFormatter.unsignedShort (entryBuffer, 0x11); - blocksUsed = HexFormatter.unsignedShort (entryBuffer, 0x13); - endOfFile = HexFormatter.intValue (entryBuffer[21], entryBuffer[22], entryBuffer[23]); - - auxType = HexFormatter.unsignedShort (entryBuffer, 0x1F); - modified = HexFormatter.getAppleDate (entryBuffer, 0x21); - headerPointer = HexFormatter.unsignedShort (entryBuffer, 0x25); - - switch (storageType) - { - case SEEDLING: - case SAPLING: - case TREE: - addDataBlocks (storageType, keyPtr); - break; - - case GSOS_EXTENDED_FILE: - readForks (); - break; - - case SUBDIRECTORY: - int block = keyPtr; - do - { - DiskAddress diskAddress = disk.getDiskAddress (block); - if (diskAddress == null) - break; - dataBlocks.add (diskAddress); - byte[] buffer = disk.readSector (block); - block = HexFormatter.unsignedShort (buffer, 2); - } while (block > 0); - break; - - case PASCAL_ON_PROFILE: - indexBlocks.add (disk.getDiskAddress (keyPtr)); - System.out.println ("PASCAL on PROFILE: " + name); - // are these blocks guaranteed to be contiguous? - break; - - default: - System.out.println ("Unknown storage type: " + storageType); - } - } - - // ---------------------------------------------------------------------------------// - private void readForks () - // ---------------------------------------------------------------------------------// - { - parentDisk.setSectorType (keyPtr, parentDisk.extendedKeySector); - indexBlocks.add (disk.getDiskAddress (keyPtr)); - - byte[] buffer2 = disk.readSector (keyPtr); // data fork and resource fork - - // read 2 mini entries (data fork & resource fork) - for (int i = 0; i < 512; i += 256) - { - int storageType = buffer2[i] & 0x0F; - int keyBlock = HexFormatter.unsignedShort (buffer2, i + 1); - int eof = HexFormatter.intValue (buffer2[i + 3], buffer2[i + 4], buffer2[i + 5]); - addDataBlocks (storageType, keyBlock); - } - } - - // ---------------------------------------------------------------------------------// - private void addDataBlocks (int storageType, int keyPtr) - // ---------------------------------------------------------------------------------// - { - DiskAddress emptyDiskAddress = disk.getDiskAddress (0); - List blocks = new ArrayList<> (); - - switch (storageType) - { - case SEEDLING: - if (isValid (keyPtr)) - blocks.add (keyPtr); - break; - - case SAPLING: - if (isValid (keyPtr)) - blocks.addAll (readIndex (keyPtr)); - break; - - case TREE: - if (isValid (keyPtr)) - for (Integer indexBlock : readMasterIndex (keyPtr)) - if (isValid (indexBlock)) - blocks.addAll (readIndex (indexBlock)); - break; - } - - // remove trailing empty blocks - while (blocks.size () > 0 && blocks.get (blocks.size () - 1) == 0) - blocks.remove (blocks.size () - 1); - - for (Integer block : blocks) - { - if (block == 0) - dataBlocks.add (emptyDiskAddress); - else - { - parentDisk.setSectorType (block, parentDisk.dataSector); - dataBlocks.add (disk.getDiskAddress (block)); - } - } - } - - // ---------------------------------------------------------------------------------// - private List readIndex (int blockPtr) - // ---------------------------------------------------------------------------------// - { - List blocks = new ArrayList<> (256); - - if (blockPtr == 0) // master index contains a zero - for (int i = 0; i < 256; i++) - blocks.add (0); - else - { - parentDisk.setSectorType (blockPtr, parentDisk.indexSector); - indexBlocks.add (disk.getDiskAddress (blockPtr)); - - byte[] buffer = disk.readSector (blockPtr); - for (int i = 0; i < 256; i++) - { - int blockNo = (buffer[i] & 0xFF) | ((buffer[i + 0x100] & 0xFF) << 8); - blocks.add (isValid (blockNo) ? blockNo : 0); - } - } - - return blocks; - } - - // ---------------------------------------------------------------------------------// - private List readMasterIndex (int keyPtr) - // ---------------------------------------------------------------------------------// - { - masterIndexBlock = disk.getDiskAddress (keyPtr); - parentDisk.setSectorType (keyPtr, parentDisk.masterIndexSector); - indexBlocks.add (disk.getDiskAddress (keyPtr)); - - byte[] buffer = disk.readSector (keyPtr); // master index - - int highest = 0x80; - while (highest-- > 0) // decrement after test - if (buffer[highest] != 0 || buffer[highest + 0x100] != 0) - break; - - List blocks = new ArrayList<> (highest + 1); - for (int i = 0; i <= highest; i++) - { - int blockNo = (buffer[i] & 0xFF) | ((buffer[i + 256] & 0xFF) << 8); - blocks.add (isValid (blockNo) ? blockNo : 0); - } - - return blocks; - } - - // ---------------------------------------------------------------------------------// - private boolean isValid (int blockNo) - // ---------------------------------------------------------------------------------// - { - if (false) - { - if (!disk.isValidAddress (blockNo)) - System.out.println ("--Invalid Block Address: " + blockNo); - if (parentDisk.isSectorFree (blockNo)) - System.out.println ("--Free block: " + blockNo); - } - return disk.isValidAddress (blockNo) && !parentDisk.isSectorFree (blockNo); - } - - // ---------------------------------------------------------------------------------// - @Override - public DataSource getDataSource () - // ---------------------------------------------------------------------------------// - { - if (file != null) - return file; - - if (invalid) - { - file = new DefaultAppleFile (name, null); - return file; - } - - if (fileType == FILE_TYPE_TEXT && auxType > 0) // random access file - return getRandomAccessTextFile (); - - byte[] buffer = getBuffer (); - byte[] exactBuffer = getExactBuffer (buffer); - - try - { - switch (fileType) - { - case FILE_TYPE_USER_DEFINED_1: // OVL - if (endOfFile == 0x2000 && auxType == 0) - { - file = new OriginalHiResImage (name, exactBuffer, auxType); - break; - } - // drop through - case FILE_TYPE_BINARY: - case FILE_TYPE_RELOCATABLE: - case FILE_TYPE_SYS: - case FILE_TYPE_BAT: - if (SimpleText.isHTML (exactBuffer)) - file = new SimpleText (name, exactBuffer); - else if (HiResImage.isGif (exactBuffer) || HiResImage.isPng (exactBuffer)) - file = new OriginalHiResImage (name, exactBuffer, auxType); - else if (name.endsWith (".BMP") && HiResImage.isBmp (exactBuffer)) - file = new OriginalHiResImage (name, exactBuffer, auxType); - else if (name.endsWith (".3200")) // $C1/02 - file = new SHRPictureFile2 (name, exactBuffer, 0xC1, 0x02, endOfFile); - else if (name.endsWith (".3201") || HiResImage.isAPP (exactBuffer)) // $C0/04 - // I made up aux=99 to test it without stepping on aux==04 - file = new SHRPictureFile2 (name, exactBuffer, 0xC0, 99, endOfFile); - else if (name.endsWith (".FNT") && FontFile.isFont (exactBuffer)) - file = new FontFile (name, exactBuffer, auxType); - else if (name.endsWith (".FONT") && FontFile.isFont (exactBuffer)) - file = new FontFile (name, exactBuffer, auxType); - else if (ShapeTable.isShapeTable (exactBuffer)) - file = new ShapeTable (name, exactBuffer); - else if (link != null) - { - if (name.endsWith (".AUX")) - file = new DoubleHiResImage (name, link.getBuffer (), exactBuffer); - else - file = new DoubleHiResImage (name, exactBuffer, link.getBuffer ()); - } - else if (name.endsWith (".PAC") || name.endsWith (".A2FC") - || (endOfFile == 0x4000 && auxType == 0x2000)) - file = new DoubleHiResImage (name, exactBuffer); - else if (endOfFile == 0x4000 && auxType == 0x4000) - file = new DoubleHiResImage (name, exactBuffer); - else if (oneOf (endOfFile, 0x1FF8, 0x1FFF, 0x2000, 0x4000) - && oneOf (auxType, 0x1FFF, 0x2000, 0x4000, 0x6000)) - file = new OriginalHiResImage (name, exactBuffer, auxType); - else if (endOfFile == 38400 && name.startsWith ("LVL.")) - file = new LodeRunner (name, exactBuffer); - else if (auxType == 0x1000 && CharacterRom.isRom (exactBuffer)) - file = new CharacterRom (name, exactBuffer); - else if (auxType == 0 && endOfFile == 0x8000) - { - // see gs basic disk, one of the four pictures looks ok - file = new SHRPictureFile2 (name, exactBuffer, 0xC1, 0, endOfFile); - } - 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 if (name.endsWith ("PLA")) - file = new SimpleText (name, exactBuffer); - else if (name.endsWith (".GIF") && HiResImage.isGif (exactBuffer)) - file = new OriginalHiResImage (name, exactBuffer, auxType); - else - file = new TextFile (name, exactBuffer, auxType, endOfFile); - break; - - case FILE_TYPE_APPLESOFT_BASIC: - file = new ApplesoftBasicProgram (name, exactBuffer); - break; - - case FILE_TYPE_GS_BASIC: - file = new BasicProgramGS (name, exactBuffer); - break; - - case FILE_TYPE_INTEGER_BASIC: - file = new IntegerBasicProgram (name, exactBuffer); - break; - - case FILE_TYPE_DIRECTORY: - VolumeDirectoryHeader vdh = parentDisk.getVolumeDirectoryHeader (); - 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_GWP: - file = new SimpleText (name, exactBuffer); - 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_IIGS_SOURCE: // I think this has a resource fork - file = new SimpleText (name, exactBuffer); - break; - - case FILE_TYPE_IIGS_APPLICATION: - file = new AssemblerProgram (name, buffer, auxType); - break; - - case FILE_TYPE_IIGS_DEVICE_DRIVER: - file = new DeviceDriver (name, exactBuffer, auxType); - break; - - case FILE_TYPE_ICN: - file = new IconFile (name, exactBuffer); - break; - - case FILE_TYPE_PNT: - if (auxType == 2) - file = new SHRPictureFile1 (name, exactBuffer, fileType, auxType, endOfFile); - else if (endOfFile < 0x222) - file = new DefaultAppleFile (name, exactBuffer); - else - file = new SHRPictureFile2 (name, exactBuffer, fileType, auxType, endOfFile); - break; - - case FILE_TYPE_PIC: - file = new SHRPictureFile2 (name, exactBuffer, fileType, auxType, endOfFile); - break; - - case FILE_TYPE_FOT: - if (auxType == HiResImage.FADDEN_AUX) - file = new FaddenHiResImage (name, exactBuffer, fileType, auxType, endOfFile); - else - { - System.out.println ("Unwritten FOT: " + name); - file = new DefaultAppleFile (name, exactBuffer); - } - break; - - case FILE_TYPE_FNT: - file = new FontFile (name, exactBuffer, auxType); - break; - - case FILE_TYPE_FONT: - file = new QuickDrawFont (name, exactBuffer, fileType, auxType); - break; - - case FILE_TYPE_DESCRIPTOR_TABLE: - file = new FileTypeDescriptorTable (name, exactBuffer); - break; - - case FILE_TYPE_GSOS_FILE_SYSTEM_TRANSLATOR: - file = new FileSystemTranslator (name, exactBuffer); - break; - - case FILE_TYPE_FINDER: - case FILE_TYPE_PASCAL_VOLUME: - case FILE_TYPE_GEO: - case FILE_TYPE_LDF: - case FILE_TYPE_ANI: - case FILE_TYPE_PAL: - case FILE_TYPE_IIGS_OBJECT: - file = new DefaultAppleFile (name, exactBuffer); - break; - - case FILE_TYPE_NON: - if (name.endsWith (".TIFF") && HiResImage.isTiff (exactBuffer)) - file = new OriginalHiResImage (name, exactBuffer, auxType); - else - file = new DefaultAppleFile (name, exactBuffer); - break; - - default: - // System.out.format ("%02X %s %s - Unknown Prodos file type%n", - // fileType, fileTypes[fileType], name); - file = new DefaultAppleFile (name, exactBuffer); - } - } - catch (Exception e) - { - file = new ErrorMessageFile (name, buffer, e); - e.printStackTrace (); - } - return file; - } - - // ---------------------------------------------------------------------------------// - private boolean oneOf (int val, int... values) - // ---------------------------------------------------------------------------------// - { - for (int value : values) - if (val == value) - return true; - return false; - } - - // ---------------------------------------------------------------------------------// - private byte[] getExactBuffer (byte[] buffer) - // ---------------------------------------------------------------------------------// - { - byte[] exactBuffer; - if (buffer.length < endOfFile) - { - exactBuffer = new byte[endOfFile]; - System.arraycopy (buffer, 0, exactBuffer, 0, buffer.length); - } - else if (buffer.length == endOfFile || endOfFile == 512) // 512 seems like crap - exactBuffer = buffer; - else - { - exactBuffer = new byte[endOfFile]; - System.arraycopy (buffer, 0, exactBuffer, 0, endOfFile); - } - return exactBuffer; - } - - // ---------------------------------------------------------------------------------// - private DataSource getRandomAccessTextFile () - // ---------------------------------------------------------------------------------// - { - // Text files with aux (reclen) > 0 are random access, possibly with - // non-contiguous records, so they need to be handled differently - - switch (storageType) - { - case TREE: - return getTreeTextFile (); - case SAPLING: - return getSaplingTextFile (); - case SEEDLING: - return getSeedlingTextFile (); - default: - System.out.println ("Impossible: text file: " + storageType); - return null; - } - } - - // ---------------------------------------------------------------------------------// - private DataSource getTreeTextFile () - // ---------------------------------------------------------------------------------// - { - List buffers = new ArrayList<> (); - List addresses = new ArrayList<> (); - 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 buffers = new ArrayList<> (); - List addresses = new ArrayList<> (); - 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 SEEDLING: - case SAPLING: - case TREE: - return disk.readSectors (dataBlocks); - - case SUBDIRECTORY: - byte[] fullBuffer = new byte[dataBlocks.size () * BLOCK_ENTRY_SIZE]; - 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; - - case GSOS_EXTENDED_FILE: - return disk.readSectors (dataBlocks); // data and resource forks concatenated - - case PASCAL_ON_PROFILE: - return disk.readSectors (dataBlocks); - - default: - System.out.println ("Unknown storage type in getBuffer : " + storageType); - return new byte[512]; - } - } - - // ---------------------------------------------------------------------------------// - private int readIndexBlock (int indexBlock, List addresses, - List 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 getSectors () - // ---------------------------------------------------------------------------------// - { - List sectors = new ArrayList<> (); - sectors.add (catalogBlock); - if (masterIndexBlock != null) - sectors.add (masterIndexBlock); - sectors.addAll (indexBlocks); - sectors.addAll (dataBlocks); - - return sectors; - } - - // ---------------------------------------------------------------------------------// - @Override - public boolean contains (DiskAddress da) - // ---------------------------------------------------------------------------------// - { - if (da == null) - return false; - if (da.equals (masterIndexBlock)) - return true; - for (DiskAddress block : indexBlocks) - if (da.matches (block)) - return true; - for (DiskAddress block : dataBlocks) - if (da.matches (block)) - return true; - - return false; - } - - // called from ProdosDisk.processDirectoryBlock, used to link DoubleHires image files - // ---------------------------------------------------------------------------------// - void link (FileEntry fileEntry) - // ---------------------------------------------------------------------------------// - { - this.link = fileEntry; - } - - // ---------------------------------------------------------------------------------// - @Override - public String toString () - // ---------------------------------------------------------------------------------// - { - if (ProdosConstants.fileTypes[fileType].equals ("DIR")) - return name; - - String locked = (access == 0x00) ? "*" : " "; - - if (true) - return String.format ("%s %03d %s", ProdosConstants.fileTypes[fileType], - blocksUsed, locked) + name; - - String timeC = created == null ? "" : parentDisk.df.format (created.getTime ()); - String timeF = modified == null ? "" : parentDisk.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); - } +package com.bytezone.diskbrowser.prodos; + +import java.util.ArrayList; +import java.util.GregorianCalendar; +import java.util.List; + +import com.bytezone.diskbrowser.applefile.ApplesoftBasicProgram; +import com.bytezone.diskbrowser.applefile.AssemblerProgram; +import com.bytezone.diskbrowser.applefile.BasicProgramGS; +import com.bytezone.diskbrowser.applefile.CharacterRom; +import com.bytezone.diskbrowser.applefile.DefaultAppleFile; +import com.bytezone.diskbrowser.applefile.DeviceDriver; +import com.bytezone.diskbrowser.applefile.DoubleHiResImage; +import com.bytezone.diskbrowser.applefile.ErrorMessageFile; +import com.bytezone.diskbrowser.applefile.FaddenHiResImage; +import com.bytezone.diskbrowser.applefile.FileSystemTranslator; +import com.bytezone.diskbrowser.applefile.FileTypeDescriptorTable; +import com.bytezone.diskbrowser.applefile.FontFile; +import com.bytezone.diskbrowser.applefile.HiResImage; +import com.bytezone.diskbrowser.applefile.IconFile; +import com.bytezone.diskbrowser.applefile.IntegerBasicProgram; +import com.bytezone.diskbrowser.applefile.LodeRunner; +import com.bytezone.diskbrowser.applefile.MerlinSource; +import com.bytezone.diskbrowser.applefile.OriginalHiResImage; +import com.bytezone.diskbrowser.applefile.QuickDrawFont; +import com.bytezone.diskbrowser.applefile.SHRPictureFile1; +import com.bytezone.diskbrowser.applefile.SHRPictureFile2; +import com.bytezone.diskbrowser.applefile.ShapeTable; +import com.bytezone.diskbrowser.applefile.SimpleText; +import com.bytezone.diskbrowser.applefile.StoredVariables; +import com.bytezone.diskbrowser.applefile.TextBuffer; +import com.bytezone.diskbrowser.applefile.TextFile; +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; +import com.bytezone.diskbrowser.utilities.HexFormatter; + +// - 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 indexBlocks = new ArrayList<> (); + + private boolean invalid; + private FileEntry link; + + // ---------------------------------------------------------------------------------// + FileEntry (ProdosDisk fDisk, byte[] entryBuffer, DirectoryHeader parent, + int parentBlock) + // ---------------------------------------------------------------------------------// + { + super (fDisk, entryBuffer); + + assert parent != null; + this.parentDirectory = parent; + this.catalogBlock = this.disk.getDiskAddress (parentBlock); + + fileType = entryBuffer[0x10] & 0xFF; + keyPtr = HexFormatter.unsignedShort (entryBuffer, 0x11); + blocksUsed = HexFormatter.unsignedShort (entryBuffer, 0x13); + endOfFile = HexFormatter.intValue (entryBuffer[21], entryBuffer[22], entryBuffer[23]); + + auxType = HexFormatter.unsignedShort (entryBuffer, 0x1F); + modified = HexFormatter.getAppleDate (entryBuffer, 0x21); + headerPointer = HexFormatter.unsignedShort (entryBuffer, 0x25); + + switch (storageType) + { + case SEEDLING: + case SAPLING: + case TREE: + addDataBlocks (storageType, keyPtr); + break; + + case GSOS_EXTENDED_FILE: + readForks (); + break; + + case SUBDIRECTORY: + int block = keyPtr; + do + { + DiskAddress diskAddress = disk.getDiskAddress (block); + if (diskAddress == null) + break; + dataBlocks.add (diskAddress); + byte[] buffer = disk.readSector (block); + block = HexFormatter.unsignedShort (buffer, 2); + } while (block > 0); + break; + + case PASCAL_ON_PROFILE: + indexBlocks.add (disk.getDiskAddress (keyPtr)); + System.out.println ("PASCAL on PROFILE: " + name); + // are these blocks guaranteed to be contiguous? + break; + + default: + System.out.println ("Unknown storage type: " + storageType); + } + } + + // ---------------------------------------------------------------------------------// + private void readForks () + // ---------------------------------------------------------------------------------// + { + parentDisk.setSectorType (keyPtr, parentDisk.extendedKeySector); + indexBlocks.add (disk.getDiskAddress (keyPtr)); + + byte[] buffer2 = disk.readSector (keyPtr); // data fork and resource fork + + // read 2 mini entries (data fork & resource fork) + for (int i = 0; i < 512; i += 256) + { + int storageType = buffer2[i] & 0x0F; + int keyBlock = HexFormatter.unsignedShort (buffer2, i + 1); + int eof = HexFormatter.intValue (buffer2[i + 3], buffer2[i + 4], buffer2[i + 5]); + addDataBlocks (storageType, keyBlock); + } + } + + // ---------------------------------------------------------------------------------// + private void addDataBlocks (int storageType, int keyPtr) + // ---------------------------------------------------------------------------------// + { + DiskAddress emptyDiskAddress = disk.getDiskAddress (0); + List blocks = new ArrayList<> (); + + switch (storageType) + { + case SEEDLING: + if (isValid (keyPtr)) + blocks.add (keyPtr); + break; + + case SAPLING: + if (isValid (keyPtr)) + blocks.addAll (readIndex (keyPtr)); + break; + + case TREE: + if (isValid (keyPtr)) + for (Integer indexBlock : readMasterIndex (keyPtr)) + if (isValid (indexBlock)) + blocks.addAll (readIndex (indexBlock)); + break; + } + + // remove trailing empty blocks + while (blocks.size () > 0 && blocks.get (blocks.size () - 1) == 0) + blocks.remove (blocks.size () - 1); + + for (Integer block : blocks) + { + if (block == 0) + dataBlocks.add (emptyDiskAddress); + else + { + parentDisk.setSectorType (block, parentDisk.dataSector); + dataBlocks.add (disk.getDiskAddress (block)); + } + } + } + + // ---------------------------------------------------------------------------------// + private List readIndex (int blockPtr) + // ---------------------------------------------------------------------------------// + { + List blocks = new ArrayList<> (256); + + if (blockPtr == 0) // master index contains a zero + for (int i = 0; i < 256; i++) + blocks.add (0); + else + { + parentDisk.setSectorType (blockPtr, parentDisk.indexSector); + indexBlocks.add (disk.getDiskAddress (blockPtr)); + + byte[] buffer = disk.readSector (blockPtr); + for (int i = 0; i < 256; i++) + { + int blockNo = (buffer[i] & 0xFF) | ((buffer[i + 0x100] & 0xFF) << 8); + blocks.add (isValid (blockNo) ? blockNo : 0); + } + } + + return blocks; + } + + // ---------------------------------------------------------------------------------// + private List readMasterIndex (int keyPtr) + // ---------------------------------------------------------------------------------// + { + masterIndexBlock = disk.getDiskAddress (keyPtr); + parentDisk.setSectorType (keyPtr, parentDisk.masterIndexSector); + indexBlocks.add (disk.getDiskAddress (keyPtr)); + + byte[] buffer = disk.readSector (keyPtr); // master index + + int highest = 0x80; + while (highest-- > 0) // decrement after test + if (buffer[highest] != 0 || buffer[highest + 0x100] != 0) + break; + + List blocks = new ArrayList<> (highest + 1); + for (int i = 0; i <= highest; i++) + { + int blockNo = (buffer[i] & 0xFF) | ((buffer[i + 256] & 0xFF) << 8); + blocks.add (isValid (blockNo) ? blockNo : 0); + } + + return blocks; + } + + // ---------------------------------------------------------------------------------// + private boolean isValid (int blockNo) + // ---------------------------------------------------------------------------------// + { + if (false) + { + if (!disk.isValidAddress (blockNo)) + System.out.println ("--Invalid Block Address: " + blockNo); + if (parentDisk.isSectorFree (blockNo)) + System.out.println ("--Free block: " + blockNo); + } + return disk.isValidAddress (blockNo) && !parentDisk.isSectorFree (blockNo); + } + + // ---------------------------------------------------------------------------------// + @Override + public DataSource getDataSource () + // ---------------------------------------------------------------------------------// + { + if (file != null) + return file; + + if (invalid) + { + file = new DefaultAppleFile (name, null); + return file; + } + + if (fileType == FILE_TYPE_TEXT && auxType > 0) // random access file + return getRandomAccessTextFile (); + + byte[] buffer = getBuffer (); + byte[] exactBuffer = getExactBuffer (buffer); + + try + { + switch (fileType) + { + case FILE_TYPE_USER_DEFINED_1: // OVL + if (endOfFile == 0x2000 && auxType == 0) + { + file = new OriginalHiResImage (name, exactBuffer, auxType); + break; + } + // drop through + case FILE_TYPE_BINARY: + case FILE_TYPE_RELOCATABLE: + case FILE_TYPE_SYS: + case FILE_TYPE_BAT: + if (SimpleText.isHTML (exactBuffer)) + file = new SimpleText (name, exactBuffer); + else if (HiResImage.isGif (exactBuffer) || HiResImage.isPng (exactBuffer)) + file = new OriginalHiResImage (name, exactBuffer, auxType); + else if (name.endsWith (".BMP") && HiResImage.isBmp (exactBuffer)) + file = new OriginalHiResImage (name, exactBuffer, auxType); + else if (name.endsWith (".3200")) // $C1/02 + file = new SHRPictureFile2 (name, exactBuffer, 0xC1, 0x02, endOfFile); + else if (name.endsWith (".3201") || HiResImage.isAPP (exactBuffer)) // $C0/04 + // I made up aux=99 to test it without stepping on aux==04 + file = new SHRPictureFile2 (name, exactBuffer, 0xC0, 99, endOfFile); + else if (name.endsWith (".FNT") && FontFile.isFont (exactBuffer)) + file = new FontFile (name, exactBuffer, auxType); + else if (name.endsWith (".FONT") && FontFile.isFont (exactBuffer)) + file = new FontFile (name, exactBuffer, auxType); + else if (ShapeTable.isShapeTable (exactBuffer)) + file = new ShapeTable (name, exactBuffer); + else if (link != null) + { + if (name.endsWith (".AUX")) + file = new DoubleHiResImage (name, link.getBuffer (), exactBuffer); + else + file = new DoubleHiResImage (name, exactBuffer, link.getBuffer ()); + } + else if (name.endsWith (".PAC") || name.endsWith (".A2FC") + || (endOfFile == 0x4000 && auxType == 0x2000)) + file = new DoubleHiResImage (name, exactBuffer); + else if (endOfFile == 0x4000 && auxType == 0x4000) + file = new DoubleHiResImage (name, exactBuffer); + else if (oneOf (endOfFile, 0x1FF8, 0x1FFF, 0x2000, 0x4000) + && oneOf (auxType, 0x1FFF, 0x2000, 0x4000, 0x6000)) + file = new OriginalHiResImage (name, exactBuffer, auxType); + else if (endOfFile == 38400 && name.startsWith ("LVL.")) + file = new LodeRunner (name, exactBuffer); + else if (auxType == 0x1000 && CharacterRom.isRom (exactBuffer)) + file = new CharacterRom (name, exactBuffer); + else if (auxType == 0 && endOfFile == 0x8000) + { + // see gs basic disk, one of the four pictures looks ok + file = new SHRPictureFile2 (name, exactBuffer, 0xC1, 0, endOfFile); + } + 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 if (name.endsWith ("PLA")) + file = new SimpleText (name, exactBuffer); + else if (name.endsWith (".GIF") && HiResImage.isGif (exactBuffer)) + file = new OriginalHiResImage (name, exactBuffer, auxType); + else + file = new TextFile (name, exactBuffer, auxType, endOfFile); + break; + + case FILE_TYPE_APPLESOFT_BASIC: + file = new ApplesoftBasicProgram (name, exactBuffer); + break; + + case FILE_TYPE_GS_BASIC: + file = new BasicProgramGS (name, exactBuffer); + break; + + case FILE_TYPE_INTEGER_BASIC: + file = new IntegerBasicProgram (name, exactBuffer); + break; + + case FILE_TYPE_DIRECTORY: + VolumeDirectoryHeader vdh = parentDisk.getVolumeDirectoryHeader (); + 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_GWP: + file = new SimpleText (name, exactBuffer); + 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_IIGS_SOURCE: // I think this has a resource fork + file = new SimpleText (name, exactBuffer); + break; + + case FILE_TYPE_IIGS_APPLICATION: + file = new AssemblerProgram (name, buffer, auxType); + break; + + case FILE_TYPE_IIGS_DEVICE_DRIVER: + file = new DeviceDriver (name, exactBuffer, auxType); + break; + + case FILE_TYPE_ICN: + file = new IconFile (name, exactBuffer); + break; + + case FILE_TYPE_PNT: + if (auxType == 2) + file = new SHRPictureFile1 (name, exactBuffer, fileType, auxType, endOfFile); + else if (endOfFile < 0x222) + file = new DefaultAppleFile (name, exactBuffer); + else + file = new SHRPictureFile2 (name, exactBuffer, fileType, auxType, endOfFile); + break; + + case FILE_TYPE_PIC: + file = new SHRPictureFile2 (name, exactBuffer, fileType, auxType, endOfFile); + break; + + case FILE_TYPE_FOT: + if (auxType == HiResImage.FADDEN_AUX) + file = new FaddenHiResImage (name, exactBuffer, fileType, auxType, endOfFile); + else + { + System.out.println ("Unwritten FOT: " + name); + file = new DefaultAppleFile (name, exactBuffer); + } + break; + + case FILE_TYPE_FNT: + file = new FontFile (name, exactBuffer, auxType); + break; + + case FILE_TYPE_FONT: + file = new QuickDrawFont (name, exactBuffer, fileType, auxType); + break; + + case FILE_TYPE_DESCRIPTOR_TABLE: + file = new FileTypeDescriptorTable (name, exactBuffer); + break; + + case FILE_TYPE_GSOS_FILE_SYSTEM_TRANSLATOR: + file = new FileSystemTranslator (name, exactBuffer); + break; + + case FILE_TYPE_FINDER: + case FILE_TYPE_PASCAL_VOLUME: + case FILE_TYPE_GEO: + case FILE_TYPE_LDF: + case FILE_TYPE_ANI: + case FILE_TYPE_PAL: + case FILE_TYPE_IIGS_OBJECT: + file = new DefaultAppleFile (name, exactBuffer); + break; + + case FILE_TYPE_NON: + if (name.endsWith (".TIFF") && HiResImage.isTiff (exactBuffer)) + file = new OriginalHiResImage (name, exactBuffer, auxType); + else + file = new DefaultAppleFile (name, exactBuffer); + break; + + default: + // System.out.format ("%02X %s %s - Unknown Prodos file type%n", + // fileType, fileTypes[fileType], name); + file = new DefaultAppleFile (name, exactBuffer); + } + } + catch (Exception e) + { + file = new ErrorMessageFile (name, buffer, e); + e.printStackTrace (); + } + return file; + } + + // ---------------------------------------------------------------------------------// + private boolean oneOf (int val, int... values) + // ---------------------------------------------------------------------------------// + { + for (int value : values) + if (val == value) + return true; + return false; + } + + // ---------------------------------------------------------------------------------// + private byte[] getExactBuffer (byte[] buffer) + // ---------------------------------------------------------------------------------// + { + byte[] exactBuffer; + if (buffer.length < endOfFile) + { + exactBuffer = new byte[endOfFile]; + System.arraycopy (buffer, 0, exactBuffer, 0, buffer.length); + } + else if (buffer.length == endOfFile || endOfFile == 512) // 512 seems like crap + exactBuffer = buffer; + else + { + exactBuffer = new byte[endOfFile]; + System.arraycopy (buffer, 0, exactBuffer, 0, endOfFile); + } + return exactBuffer; + } + + // ---------------------------------------------------------------------------------// + private DataSource getRandomAccessTextFile () + // ---------------------------------------------------------------------------------// + { + // Text files with aux (reclen) > 0 are random access, possibly with + // non-contiguous records, so they need to be handled differently + + switch (storageType) + { + case TREE: + return getTreeTextFile (); + case SAPLING: + return getSaplingTextFile (); + case SEEDLING: + return getSeedlingTextFile (); + default: + System.out.println ("Impossible: text file: " + storageType); + return null; + } + } + + // ---------------------------------------------------------------------------------// + private DataSource getTreeTextFile () + // ---------------------------------------------------------------------------------// + { + List buffers = new ArrayList<> (); + List addresses = new ArrayList<> (); + 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 buffers = new ArrayList<> (); + List addresses = new ArrayList<> (); + 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 SEEDLING: + case SAPLING: + case TREE: + return disk.readSectors (dataBlocks); + + case SUBDIRECTORY: + byte[] fullBuffer = new byte[dataBlocks.size () * BLOCK_ENTRY_SIZE]; + 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; + + case GSOS_EXTENDED_FILE: + return disk.readSectors (dataBlocks); // data and resource forks concatenated + + case PASCAL_ON_PROFILE: + return disk.readSectors (dataBlocks); + + default: + System.out.println ("Unknown storage type in getBuffer : " + storageType); + return new byte[512]; + } + } + + // ---------------------------------------------------------------------------------// + private int readIndexBlock (int indexBlock, List addresses, + List 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 getSectors () + // ---------------------------------------------------------------------------------// + { + List sectors = new ArrayList<> (); + sectors.add (catalogBlock); + if (masterIndexBlock != null) + sectors.add (masterIndexBlock); + sectors.addAll (indexBlocks); + sectors.addAll (dataBlocks); + + return sectors; + } + + // ---------------------------------------------------------------------------------// + @Override + public boolean contains (DiskAddress da) + // ---------------------------------------------------------------------------------// + { + if (da == null) + return false; + if (da.equals (masterIndexBlock)) + return true; + for (DiskAddress block : indexBlocks) + if (da.matches (block)) + return true; + for (DiskAddress block : dataBlocks) + if (da.matches (block)) + return true; + + return false; + } + + // called from ProdosDisk.processDirectoryBlock, used to link DoubleHires image files + // ---------------------------------------------------------------------------------// + void link (FileEntry fileEntry) + // ---------------------------------------------------------------------------------// + { + this.link = fileEntry; + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + if (ProdosConstants.fileTypes[fileType].equals ("DIR")) + return name; + + String locked = (access == 0x00) ? "*" : " "; + + if (true) + return String.format ("%s %03d %s", ProdosConstants.fileTypes[fileType], + blocksUsed, locked) + name; + + String timeC = created == null ? "" : parentDisk.df.format (created.getTime ()); + String timeF = modified == null ? "" : parentDisk.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); + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/prodos/ProdosBitMapSector.java b/src/com/bytezone/diskbrowser/prodos/ProdosBitMapSector.java index a7a33a6..ea55972 100755 --- a/src/com/bytezone/diskbrowser/prodos/ProdosBitMapSector.java +++ b/src/com/bytezone/diskbrowser/prodos/ProdosBitMapSector.java @@ -1,69 +1,75 @@ -package com.bytezone.diskbrowser.prodos; - -import java.awt.Dimension; - -import com.bytezone.diskbrowser.disk.AbstractSector; -import com.bytezone.diskbrowser.disk.Disk; -import com.bytezone.diskbrowser.disk.DiskAddress; - -class ProdosBitMapSector extends AbstractSector -{ - private final ProdosDisk parent; - - ProdosBitMapSector (ProdosDisk parent, Disk disk, byte[] buffer, DiskAddress da) - { - super (disk, buffer, da); - this.parent = parent; - } - - @Override - public String createText () - { - Dimension grid = parent.getGridLayout (); - - // check range of bits for current block - so far I don't have a disk that needs - // more than a single block - int relativeBlock = - diskAddress.getBlock () - parent.getVolumeDirectoryHeader ().bitMapBlock; - int startBit = relativeBlock * 4096; - // int endBit = startBit + 4096; - if (startBit >= grid.width * grid.height) - return "This sector is not used - the physical file size makes it unnecessary"; - - int width = (grid.width - 1) / 8 + 1; // must be 1-4 - - StringBuilder text = getHeader ("Volume Bit Map Block"); - - if (false) - { - int block = 0; - for (int row = 0; row < grid.height; row++) - { - int offset = block / 8; - StringBuilder details = new StringBuilder (); - for (int col = 0; col < grid.width; col++) - details.append (parent.isSectorFree (block++) ? ". " : "X "); - addText (text, buffer, offset, width, details.toString ()); - } - } - else - { - int startRow = relativeBlock * 512 / width; - int endRow = startRow + (512 / width); - int block = startBit; - int byteNo = 0; - // System.out.printf ("Start %d, end %d%n", startRow, endRow); - for (int row = startRow; row < endRow; row++) - { - StringBuilder details = new StringBuilder (); - for (int col = 0; col < grid.width; col++) - details.append (parent.isSectorFree (block++) ? ". " : "X "); - addText (text, buffer, byteNo, width, details.toString ()); - byteNo += width; - } - } - - text.deleteCharAt (text.length () - 1); - return text.toString (); - } +package com.bytezone.diskbrowser.prodos; + +import java.awt.Dimension; + +import com.bytezone.diskbrowser.disk.AbstractSector; +import com.bytezone.diskbrowser.disk.Disk; +import com.bytezone.diskbrowser.disk.DiskAddress; + +// -----------------------------------------------------------------------------------// +class ProdosBitMapSector extends AbstractSector +// -----------------------------------------------------------------------------------// +{ + private final ProdosDisk parent; + + // ---------------------------------------------------------------------------------// + ProdosBitMapSector (ProdosDisk parent, Disk disk, byte[] buffer, DiskAddress da) + // ---------------------------------------------------------------------------------// + { + super (disk, buffer, da); + this.parent = parent; + } + + // ---------------------------------------------------------------------------------// + @Override + public String createText () + // ---------------------------------------------------------------------------------// + { + Dimension grid = parent.getGridLayout (); + + // check range of bits for current block - so far I don't have a disk that needs + // more than a single block + int relativeBlock = + diskAddress.getBlock () - parent.getVolumeDirectoryHeader ().bitMapBlock; + int startBit = relativeBlock * 4096; + // int endBit = startBit + 4096; + if (startBit >= grid.width * grid.height) + return "This sector is not used - the physical file size makes it unnecessary"; + + int width = (grid.width - 1) / 8 + 1; // must be 1-4 + + StringBuilder text = getHeader ("Volume Bit Map Block"); + + if (false) + { + int block = 0; + for (int row = 0; row < grid.height; row++) + { + int offset = block / 8; + StringBuilder details = new StringBuilder (); + for (int col = 0; col < grid.width; col++) + details.append (parent.isSectorFree (block++) ? ". " : "X "); + addText (text, buffer, offset, width, details.toString ()); + } + } + else + { + int startRow = relativeBlock * 512 / width; + int endRow = startRow + (512 / width); + int block = startBit; + int byteNo = 0; + // System.out.printf ("Start %d, end %d%n", startRow, endRow); + for (int row = startRow; row < endRow; row++) + { + StringBuilder details = new StringBuilder (); + for (int col = 0; col < grid.width; col++) + details.append (parent.isSectorFree (block++) ? ". " : "X "); + addText (text, buffer, byteNo, width, details.toString ()); + byteNo += width; + } + } + + text.deleteCharAt (text.length () - 1); + return text.toString (); + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/prodos/ProdosCatalogSector.java b/src/com/bytezone/diskbrowser/prodos/ProdosCatalogSector.java index 2ca708d..ff4f036 100755 --- a/src/com/bytezone/diskbrowser/prodos/ProdosCatalogSector.java +++ b/src/com/bytezone/diskbrowser/prodos/ProdosCatalogSector.java @@ -1,203 +1,232 @@ -package com.bytezone.diskbrowser.prodos; - -import static com.bytezone.diskbrowser.prodos.ProdosConstants.*; - -import java.util.GregorianCalendar; - -import com.bytezone.diskbrowser.disk.AbstractSector; -import com.bytezone.diskbrowser.disk.Disk; -import com.bytezone.diskbrowser.disk.DiskAddress; -import com.bytezone.diskbrowser.utilities.HexFormatter; - -class ProdosCatalogSector extends AbstractSector -{ - private final ProdosDisk parent; - - ProdosCatalogSector (ProdosDisk parent, Disk disk, byte[] buffer, - DiskAddress diskAddress) - { - super (disk, buffer, diskAddress); - this.parent = parent; - } - - @Override - public String createText () - { - StringBuilder text = getHeader ("Volume Directory Block"); - - addTextAndDecimal (text, buffer, 0, 2, "Previous block"); - addTextAndDecimal (text, buffer, 2, 2, "Next block"); - - for (int i = 4, max = buffer.length - ENTRY_SIZE; i <= max; i += ENTRY_SIZE) - { - if (buffer[i] == 0 && buffer[i + 1] == 0) - break; - text.append ("\n"); - - // first byte contains the file type (left nybble) and name length (right nybble) - int fileType = (buffer[i] & 0xF0) >> 4; - int nameLength = buffer[i] & 0x0F; - String hex1 = String.format ("%02X", buffer[i] & 0xF0); - String hex2 = String.format ("%02X", nameLength); - - // deleted files set file type and name length to zero, but the file - // name is still valid - String typeText = hex1 + " = " + getType (buffer[i]); - if (fileType == 0) - addText (text, buffer, i, 1, typeText + " : " + getDeletedName (i + 1)); - else - addText (text, buffer, i, 1, typeText + ", " + hex2 + " = Name length"); - - addText (text, buffer, i + 1, 4, - HexFormatter.getString (buffer, i + 1, nameLength)); - - switch (fileType) - { - case FREE: - case SEEDLING: - case SAPLING: - case TREE: - case PASCAL_ON_PROFILE: - case GSOS_EXTENDED_FILE: - case SUBDIRECTORY: - text.append (doFileDescription (i)); - break; - case SUBDIRECTORY_HEADER: - text.append (doSubdirectoryHeader (i)); - break; - case VOLUME_HEADER: - text.append (doVolumeDirectoryHeader (i)); - break; - default: - text.append ("Unknown\n"); - } - } - if (text.length () > 0) - text.deleteCharAt (text.length () - 1); - return text.toString (); - } - - private String doFileDescription (int offset) - { - StringBuilder text = new StringBuilder (); - int fileType = buffer[offset + 16] & 0xFF; - int auxType = HexFormatter.unsignedShort (buffer, offset + 31); - addText (text, buffer, offset + 16, 1, - "File type (" + ProdosConstants.fileTypes[fileType] + ")"); - addTextAndDecimal (text, buffer, offset + 17, 2, "Key pointer"); - addTextAndDecimal (text, buffer, offset + 19, 2, "Blocks used"); - addTextAndDecimal (text, buffer, offset + 21, 3, "EOF"); - GregorianCalendar created = HexFormatter.getAppleDate (buffer, offset + 24); - String dateC = created == null ? "" : parent.df.format (created.getTime ()); - addText (text, buffer, offset + 24, 4, "Creation date : " + dateC); - addTextAndDecimal (text, buffer, offset + 28, 1, "Version"); - addText (text, buffer, offset + 29, 1, "Minimum version"); - addText (text, buffer, offset + 30, 1, "Access"); - addTextAndDecimal (text, buffer, offset + 31, 2, - "Auxilliary type - " + getAuxilliaryText (fileType, auxType)); - GregorianCalendar modified = HexFormatter.getAppleDate (buffer, offset + 33); - String dateM = modified == null ? "" : parent.df.format (modified.getTime ()); - addText (text, buffer, offset + 33, 4, "Modification date : " + dateM); - addTextAndDecimal (text, buffer, offset + 37, 2, "Header pointer"); - return text.toString (); - } - - private String doVolumeDirectoryHeader (int offset) - { - StringBuilder text = new StringBuilder (); - addText (text, buffer, offset + 16, 4, "Not used"); - text.append (getCommonHeader (offset)); - addTextAndDecimal (text, buffer, offset + 35, 2, "Bit map pointer"); - addTextAndDecimal (text, buffer, offset + 37, 2, "Total blocks"); - return text.toString (); - } - - private String doSubdirectoryHeader (int offset) - { - StringBuilder text = new StringBuilder (); - addText (text, buffer, offset + 16, 1, "Hex $75"); - addText (text, buffer, offset + 17, 3, "Not used"); - text.append (getCommonHeader (offset)); - addTextAndDecimal (text, buffer, offset + 35, 2, "Parent block"); - addTextAndDecimal (text, buffer, offset + 37, 1, "Parent entry number"); - addTextAndDecimal (text, buffer, offset + 38, 1, "Parent entry length"); - return text.toString (); - } - - private String getCommonHeader (int offset) - { - StringBuilder text = new StringBuilder (); - addText (text, buffer, offset + 20, 4, "Not used"); - GregorianCalendar created = HexFormatter.getAppleDate (buffer, offset + 24); - String dateC = created == null ? "" : parent.df.format (created.getTime ()); - addText (text, buffer, offset + 24, 4, "Creation date : " + dateC); - addText (text, buffer, offset + 28, 1, "Prodos version"); - addText (text, buffer, offset + 29, 1, "Minimum version"); - addText (text, buffer, offset + 30, 1, "Access"); - addTextAndDecimal (text, buffer, offset + 31, 1, "Entry length"); - addTextAndDecimal (text, buffer, offset + 32, 1, "Entries per block"); - addTextAndDecimal (text, buffer, offset + 33, 2, "File count"); - - return text.toString (); - } - - private String getAuxilliaryText (int fileType, int auxType) - { - switch (fileType) - { - case 0x04: - return "record length"; - case 0xFD: - return "address of stored variables"; - case 0x06: - case 0xFC: - case 0xFF: - return "load address"; - case 0xB3: - return "forked"; - case 0xC1: - return "PIC"; - case 0xC8: - return auxType == 0 ? "QuickDraw bitmap font" - : auxType == 1 ? "Pointless truetype font" : "??"; - default: - return "???"; - } - } - - private String getType (byte flag) - { - switch ((flag & 0xF0) >> 4) - { - case FREE: - return "Deleted"; - case SEEDLING: - return "Seedling"; - case SAPLING: - return "Sapling"; - case TREE: - return "Tree"; - case PASCAL_ON_PROFILE: - return "Pascal area on a Profile HD"; - case GSOS_EXTENDED_FILE: - return "GS/OS extended file"; - case SUBDIRECTORY: - return "Subdirectory"; - case SUBDIRECTORY_HEADER: - return "Subdirectory Header"; - case VOLUME_HEADER: - return "Volume Directory Header"; - default: - return "???"; - } - } - - // Deleted files leave the name intact, but set the name length to zero - private String getDeletedName (int offset) - { - StringBuilder text = new StringBuilder (); - for (int i = offset, max = offset + 15; i < max && buffer[i] != 0; i++) - text.append ((char) (buffer[i] & 0xFF)); - return text.toString (); - } +package com.bytezone.diskbrowser.prodos; + +import static com.bytezone.diskbrowser.prodos.ProdosConstants.ENTRY_SIZE; +import static com.bytezone.diskbrowser.prodos.ProdosConstants.FREE; +import static com.bytezone.diskbrowser.prodos.ProdosConstants.GSOS_EXTENDED_FILE; +import static com.bytezone.diskbrowser.prodos.ProdosConstants.PASCAL_ON_PROFILE; +import static com.bytezone.diskbrowser.prodos.ProdosConstants.SAPLING; +import static com.bytezone.diskbrowser.prodos.ProdosConstants.SEEDLING; +import static com.bytezone.diskbrowser.prodos.ProdosConstants.SUBDIRECTORY; +import static com.bytezone.diskbrowser.prodos.ProdosConstants.SUBDIRECTORY_HEADER; +import static com.bytezone.diskbrowser.prodos.ProdosConstants.TREE; +import static com.bytezone.diskbrowser.prodos.ProdosConstants.VOLUME_HEADER; + +import java.util.GregorianCalendar; + +import com.bytezone.diskbrowser.disk.AbstractSector; +import com.bytezone.diskbrowser.disk.Disk; +import com.bytezone.diskbrowser.disk.DiskAddress; +import com.bytezone.diskbrowser.utilities.HexFormatter; + +// -----------------------------------------------------------------------------------// +class ProdosCatalogSector extends AbstractSector +// -----------------------------------------------------------------------------------// +{ + private final ProdosDisk parent; + + // ---------------------------------------------------------------------------------// + ProdosCatalogSector (ProdosDisk parent, Disk disk, byte[] buffer, + DiskAddress diskAddress) + // ---------------------------------------------------------------------------------// + { + super (disk, buffer, diskAddress); + this.parent = parent; + } + + // ---------------------------------------------------------------------------------// + @Override + public String createText () + // ---------------------------------------------------------------------------------// + { + StringBuilder text = getHeader ("Volume Directory Block"); + + addTextAndDecimal (text, buffer, 0, 2, "Previous block"); + addTextAndDecimal (text, buffer, 2, 2, "Next block"); + + for (int i = 4, max = buffer.length - ENTRY_SIZE; i <= max; i += ENTRY_SIZE) + { + if (buffer[i] == 0 && buffer[i + 1] == 0) + break; + text.append ("\n"); + + // first byte contains the file type (left nybble) and name length (right nybble) + int fileType = (buffer[i] & 0xF0) >> 4; + int nameLength = buffer[i] & 0x0F; + String hex1 = String.format ("%02X", buffer[i] & 0xF0); + String hex2 = String.format ("%02X", nameLength); + + // deleted files set file type and name length to zero, but the file + // name is still valid + String typeText = hex1 + " = " + getType (buffer[i]); + if (fileType == 0) + addText (text, buffer, i, 1, typeText + " : " + getDeletedName (i + 1)); + else + addText (text, buffer, i, 1, typeText + ", " + hex2 + " = Name length"); + + addText (text, buffer, i + 1, 4, + HexFormatter.getString (buffer, i + 1, nameLength)); + + switch (fileType) + { + case FREE: + case SEEDLING: + case SAPLING: + case TREE: + case PASCAL_ON_PROFILE: + case GSOS_EXTENDED_FILE: + case SUBDIRECTORY: + text.append (doFileDescription (i)); + break; + case SUBDIRECTORY_HEADER: + text.append (doSubdirectoryHeader (i)); + break; + case VOLUME_HEADER: + text.append (doVolumeDirectoryHeader (i)); + break; + default: + text.append ("Unknown\n"); + } + } + if (text.length () > 0) + text.deleteCharAt (text.length () - 1); + return text.toString (); + } + + // ---------------------------------------------------------------------------------// + private String doFileDescription (int offset) + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + int fileType = buffer[offset + 16] & 0xFF; + int auxType = HexFormatter.unsignedShort (buffer, offset + 31); + addText (text, buffer, offset + 16, 1, + "File type (" + ProdosConstants.fileTypes[fileType] + ")"); + addTextAndDecimal (text, buffer, offset + 17, 2, "Key pointer"); + addTextAndDecimal (text, buffer, offset + 19, 2, "Blocks used"); + addTextAndDecimal (text, buffer, offset + 21, 3, "EOF"); + GregorianCalendar created = HexFormatter.getAppleDate (buffer, offset + 24); + String dateC = created == null ? "" : parent.df.format (created.getTime ()); + addText (text, buffer, offset + 24, 4, "Creation date : " + dateC); + addTextAndDecimal (text, buffer, offset + 28, 1, "Version"); + addText (text, buffer, offset + 29, 1, "Minimum version"); + addText (text, buffer, offset + 30, 1, "Access"); + addTextAndDecimal (text, buffer, offset + 31, 2, + "Auxilliary type - " + getAuxilliaryText (fileType, auxType)); + GregorianCalendar modified = HexFormatter.getAppleDate (buffer, offset + 33); + String dateM = modified == null ? "" : parent.df.format (modified.getTime ()); + addText (text, buffer, offset + 33, 4, "Modification date : " + dateM); + addTextAndDecimal (text, buffer, offset + 37, 2, "Header pointer"); + return text.toString (); + } + + // ---------------------------------------------------------------------------------// + private String doVolumeDirectoryHeader (int offset) + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + addText (text, buffer, offset + 16, 4, "Not used"); + text.append (getCommonHeader (offset)); + addTextAndDecimal (text, buffer, offset + 35, 2, "Bit map pointer"); + addTextAndDecimal (text, buffer, offset + 37, 2, "Total blocks"); + return text.toString (); + } + + // ---------------------------------------------------------------------------------// + private String doSubdirectoryHeader (int offset) + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + addText (text, buffer, offset + 16, 1, "Hex $75"); + addText (text, buffer, offset + 17, 3, "Not used"); + text.append (getCommonHeader (offset)); + addTextAndDecimal (text, buffer, offset + 35, 2, "Parent block"); + addTextAndDecimal (text, buffer, offset + 37, 1, "Parent entry number"); + addTextAndDecimal (text, buffer, offset + 38, 1, "Parent entry length"); + return text.toString (); + } + + // ---------------------------------------------------------------------------------// + private String getCommonHeader (int offset) + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + addText (text, buffer, offset + 20, 4, "Not used"); + GregorianCalendar created = HexFormatter.getAppleDate (buffer, offset + 24); + String dateC = created == null ? "" : parent.df.format (created.getTime ()); + addText (text, buffer, offset + 24, 4, "Creation date : " + dateC); + addText (text, buffer, offset + 28, 1, "Prodos version"); + addText (text, buffer, offset + 29, 1, "Minimum version"); + addText (text, buffer, offset + 30, 1, "Access"); + addTextAndDecimal (text, buffer, offset + 31, 1, "Entry length"); + addTextAndDecimal (text, buffer, offset + 32, 1, "Entries per block"); + addTextAndDecimal (text, buffer, offset + 33, 2, "File count"); + + return text.toString (); + } + + // ---------------------------------------------------------------------------------// + private String getAuxilliaryText (int fileType, int auxType) + // ---------------------------------------------------------------------------------// + { + switch (fileType) + { + case 0x04: + return "record length"; + case 0xFD: + return "address of stored variables"; + case 0x06: + case 0xFC: + case 0xFF: + return "load address"; + case 0xB3: + return "forked"; + case 0xC1: + return "PIC"; + case 0xC8: + return auxType == 0 ? "QuickDraw bitmap font" + : auxType == 1 ? "Pointless truetype font" : "??"; + default: + return "???"; + } + } + + // ---------------------------------------------------------------------------------// + private String getType (byte flag) + // ---------------------------------------------------------------------------------// + { + switch ((flag & 0xF0) >> 4) + { + case FREE: + return "Deleted"; + case SEEDLING: + return "Seedling"; + case SAPLING: + return "Sapling"; + case TREE: + return "Tree"; + case PASCAL_ON_PROFILE: + return "Pascal area on a Profile HD"; + case GSOS_EXTENDED_FILE: + return "GS/OS extended file"; + case SUBDIRECTORY: + return "Subdirectory"; + case SUBDIRECTORY_HEADER: + return "Subdirectory Header"; + case VOLUME_HEADER: + return "Volume Directory Header"; + default: + return "???"; + } + } + + // Deleted files leave the name intact, but set the name length to zero + // ---------------------------------------------------------------------------------// + private String getDeletedName (int offset) + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + for (int i = offset, max = offset + 15; i < max && buffer[i] != 0; i++) + text.append ((char) (buffer[i] & 0xFF)); + return text.toString (); + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java b/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java index 760e4d9..d94f48a 100755 --- a/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java +++ b/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java @@ -1,253 +1,255 @@ -package com.bytezone.diskbrowser.prodos; - -public interface ProdosConstants -{ - static int FILE_TYPE_NON = 0x00; - static int FILE_TYPE_PCD = 0x02; - static int FILE_TYPE_TEXT = 0x04; - static int FILE_TYPE_PDA = 0x05; - static int FILE_TYPE_BINARY = 0x06; - static int FILE_TYPE_FNT = 0x07; - static int FILE_TYPE_FOT = 0x08; // was Apple /// FotoFile - static int FILE_TYPE_DIRECTORY = 0x0F; - static int FILE_TYPE_ADB = 0x19; - static int FILE_TYPE_AWP = 0x1A; - static int FILE_TYPE_ASP = 0x1B; - static int FILE_TYPE_DESCRIPTOR_TABLE = 0x42; - static int FILE_TYPE_GWP = 0x50; - static int FILE_TYPE_GEO = 0x82; - static int FILE_TYPE_IIGS_SOURCE = 0xB0; - static int FILE_TYPE_IIGS_OBJECT = 0xB1; - static int FILE_TYPE_IIGS_APPLICATION = 0xB3; - static int FILE_TYPE_IIGS_DEVICE_DRIVER = 0xBB; - static int FILE_TYPE_LDF = 0xBC; - static int FILE_TYPE_GS_BASIC = 0xAB; - static int FILE_TYPE_GSOS_FILE_SYSTEM_TRANSLATOR = 0xBD; - static int FILE_TYPE_PNT = 0xC0; - static int FILE_TYPE_PIC = 0xC1; - static int FILE_TYPE_ANI = 0xC2; - static int FILE_TYPE_PAL = 0xC3; - static int FILE_TYPE_FONT = 0xC8; - static int FILE_TYPE_FINDER = 0xC9; - static int FILE_TYPE_ICN = 0xCA; - static int FILE_TYPE_APPLETALK = 0xE2; - static int FILE_TYPE_PASCAL_VOLUME = 0xEF; - static int FILE_TYPE_USER_DEFINED_1 = 0xF1; // OVL - static int FILE_TYPE_BAT = 0xF5; - static int FILE_TYPE_INTEGER_BASIC = 0xFA; - static int FILE_TYPE_INTEGER_BASIC_VARS = 0xFB; - static int FILE_TYPE_APPLESOFT_BASIC = 0xFC; - static int FILE_TYPE_APPLESOFT_BASIC_VARS = 0xFD; - static int FILE_TYPE_RELOCATABLE = 0xFE; - static int FILE_TYPE_SYS = 0xFF; - - static int VOLUME_HEADER = 15; - static int SUBDIRECTORY_HEADER = 14; - static int SUBDIRECTORY = 13; - static int GSOS_EXTENDED_FILE = 5; // tech note #25 - static int PASCAL_ON_PROFILE = 4; // tech note #25 - static int TREE = 3; - static int SAPLING = 2; - static int SEEDLING = 1; - static int FREE = 0; - - static String[] fileTypes = { // - "NON", "BAD", "PCD", "PTX", "TXT", "PDA", "BIN", "FNT", // - "FOT", "BA3", "DA3", "WPF", "SOS", "$0D", "$0E", "DIR", // - "RPD", "RPI", "AFD", "AFM", "AFR", "SCL", "PFS", "$17", // - "$18", "ADB", "AWP", "ASP", "$1C", "$1D", "$1E", "$1F", // - "TDM", "$21", "$22", "$23", "$24", "$25", "$26", "$27", // - "$28", "$29", "8SC", "8OB", "8IC", "8LD", "P8C", "$2F", // - "$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37", // - "$38", "$39", "$3A", "$3B", "$3C", "$3D", "$3E", "$3F", // - "DIC", "OCR", "FTD", "$43", "$44", "$45", "$46", "$47", // - "$48", "$49", "$4A", "$4B", "$4C", "$4D", "$4E", "$4F", // - "GWP", "GSS", "GDB", "DRW", "GDP", "HMD", "EDU", "STN", // - "HLP", "COM", "CFG", "ANM", "MUM", "ENT", "DVU", "FIN", // - "$60", "$61", "$62", "$63", "$64", "$65", "$66", "$67", // - "$68", "$69", "$6A", "BIO", "$6C", "TDR", "PRE", "HDV", // - "$70", "$71", "$72", "$73", "$74", "$75", "$76", "$77", // - "$78", "$79", "$7A", "$7B", "$7C", "$7D", "$7E", "$7F", // - "GES", "GEA", "GEO", "GED", "GEF", "GEP", "GEI", "GEX", // - "$88", "GEV", "$8A", "GEC", "GEK", "GEW", "$8E", "$8F", // - "$90", "$91", "$92", "$93", "$94", "$95", "$96", "$97", // - "$98", "$99", "$9A", "$9B", "$9C", "$9D", "$9E", "$9F", // - "WP ", "$A1", "$A2", "$A3", "$A4", "$A5", "$A6", "$A7", // - "$A8", "$A9", "$AA", "GSB", "TDF", "BDF", "$AE", "$AF", // - "SRC", "OBJ", "LIB", "S16", "RTL", "EXE", "PIF", "TIF", // - "NDA", "CDA", "TOL", "DVR", "LDF", "FST", "$BE", "DOC", // - "PNT", "PIC", "ANI", "PAL", "$C4", "OOG", "SCR", "CDV", // - "FON", "FND", "ICN", "$CB", "$CC", "$CD", "$CE", "$CF", // - "$D0", "$D1", "$D2", "$D3", "$D4", "MUS", "INS", "MDI", // - "SND", "$D9", "$DA", "DBM", "$DC", "DDD", "$DE", "$DF", // - "LBR", "$E1", "ATK", "$E3", "$E4", "$E5", "$E6", "$E7", // - "$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "R16", "PAR", // - "CMD", "OVL", "UD2", "UD3", "UD4", "BAT", "UD6", "UD7", // - "PRG", "P16", "INT", "IVR", "BAS", "VAR", "REL", "SYS" }; - - static int ENTRY_SIZE = 39; - static int ENTRIES_PER_BLOCK = 13; - static int BLOCK_ENTRY_SIZE = ENTRY_SIZE * ENTRIES_PER_BLOCK; -} - -/* http://www.kreativekorp.com/miscpages/a2info/filetypes.shtml - * - * $00 UNK Unknown - * $01 BAD Bad Block File - * $02 PCD Pascal Code - * $03 PTX Pascal Text - * $04 TXT ASCII Text - * $05 PDA Pascal Data - * $06 BIN Binary File - * $07 FNT Apple III Font - * $08 FOT HiRes/Double HiRes File - * $09 BA3 Apple III BASIC Program - * $0A DA3 Apple III BASIC Data - * $0B WPF Generic Word Processor File - * $0C SOS SOS System File - * $0F DIR ProDOS Directory - * $10 RPD RPS Data - * $11 RPI RPS Index - * $12 AFD AppleFile Discard - * $13 AFM AppleFile Model - * $14 AFR AppleFile Report - * $15 SCL Screen Library - * $16 PFS PFS Document - * $19 ADB AppleWorks Database - * $1A AWP AppleWorks Word Processor - * $1B ASP AppleWorks Spreadsheet - * $20 TDM Desktop Manager File - * $21 IPS Instant Pascal Source - * $22 UPV UCSD Pascal Volume - * $29 3SD SOS Directory - * $2A 8SC Source Code - * $2B 8OB Object Code - * $2C 8IC Interpretted Code - * $2D 8LD Language Data - * $2E P8C ProDOS 8 Code Module - * $41 OCR Optical Character Recognition File - * $50 GWP Apple IIgs Word Processor File - * $51 GSS Apple IIgs Spreadsheet File - * $52 GDB Apple IIgs Database File - * $53 DRW Object Oriented Graphics File - * $54 GDP Apple IIgs Desktop Publishing File - * $55 HMD HyperMedia - * $56 EDU Educational Program Data - * $57 STN Stationery - * $58 HLP Help File - * $59 COM Communications File - * $5A CFG Configuration File - * $5B ANM Animation File - * $5C MUM Multimedia File - * $5D ENT Entertainment Program File - * $5E DVU Development Utility File - * $60 PRE PC Pre-Boot - * $6B BIO PC BIOS - * $6D DVR PC Driver - * $6E PRE PC Pre-Boot - * $6F HDV PC Hard Disk Image - * $77 KES KES Software - * $7B TLB KES Software - * $7F JCP KES Software - * $80 GeOS System File - * $81 GeOS Desk Accessory - * $82 GeOS Application - * $83 GeOS Document - * $84 GeOS Font - * $85 GeOS Printer Driver - * $86 GeOS Input Driver - * $87 GeOS Auxilary Driver - * $8B GeOS Clock Driver - * $8C GeOS Interface Card Driver - * $8D GeOS Formatting Data - * $A0 WP WordPerfect File - * $A6 - * $AB GSB Apple IIgs BASIC Program - * $AC TDF Apple IIgs BASIC TDF - * $AD BDF Apple IIgs BASIC Data - * $B0 SRC Apple IIgs Source Code - * $B1 OBJ Apple IIgs Object Code - * $B2 LIB Apple IIgs Library - * $B3 S16 Apple IIgs Application Program - * $B4 RTL Apple IIgs Runtime Library - * $B5 EXE Apple IIgs Shell - * $B6 PIF Apple IIgs Permanent INIT - * $B7 TIF Apple IIgs Temporary INIT - * $B8 NDA Apple IIgs New Desk Accessory - * $B9 CDA Apple IIgs Classic Desk Accessory - * $BA TOL Apple IIgs Tool - * $BB DRV Apple IIgs Device Driver - * $BC LDF Apple IIgs Generic Load File - * $BD FST Apple IIgs File System Translator - * $BF DOC Apple IIgs Document - * $C0 PNT Apple IIgs Packed Super HiRes - * $C1 PIC Apple IIgs Super HiRes - * $C2 ANI PaintWorks Animation - * $C3 PAL PaintWorks Palette - * $C5 OOG Object-Oriented Graphics - * $C6 SCR Script - * $C7 CDV Apple IIgs Control Panel - * $C8 FON Apple IIgs Font - * $C9 FND Apple IIgs Finder Data - * $CA ICN Apple IIgs Icon File - * $D5 MUS Music File - * $D6 INS Instrument File - * $D7 MDI MIDI File - * $D8 SND Apple IIgs Sound File - * $DB DBM DB Master Document - * $E0 LBR Archive File - * $E2 ATK AppleTalk Data - * $EE R16 EDASM 816 Relocatable Code - * $EF PAR Pascal Area - * $F0 CMD ProDOS Command File - * $F1 OVL User Defined 1 - * $F2 User Defined 2 - * $F3 User Defined 3 - * $F4 User Defined 4 - * $F5 BAT User Defined 5 - * $F6 User Defined 6 - * $F7 User Defined 7 - * $F8 PRG User Defined 8 - * $F9 P16 Apple IIgs System File - * $FA INT Integer BASIC Program - * $FB IVR Integer BASIC Variables - * $FC BAS Applesoft BASIC Program - * $FD VAR Applesoft BASIC Variables - * $FE REL EDASM Relocatable Code - * $FF SYS ProDOS System File - */ - -// See also http://www.kreativekorp.com/miscpages/a2info/filetypes.shtml - -/* - * https://groups.google.com/forum/#!topic/comp.sys.apple2/waoYCIbkJKs - * - * There are a number of disk utilities available that store images of disks that - * utilize file systems that are not ProDOS, at the end of a ProDOS volume. - * There's DOS Master, by Glen Bredon, that stores images of DOS 3.3 disks at the - * end of a ProDOS volume. Similarly, Pro/Part, by Steven Hirsch, stores images - * of CP/M volumes. Also, there's Pascal Partition Manager (PPM) that stores - * images of UCSD Pascal volumes. I've decided to refer to the area used to store - * volume images, by all three of these systems, as a Foreign Volume Area or FVA. - * All three of these systems modify the Block Allocation Map of a ProDOS volume - * to keep ProDOS from assigning blocks used by FVAs for use by files being - * written by ProDOS. Pascal Partition Manager is different from the other two - * in that it has a file type ($EF) and file kind (4) assigned to it by Apple. - * A directory listing of a ProODS volume containing an FVA managed by PPM will - * show a file name of "PASCAL.AREA". A directory listing of a ProDOS volume - * containing an FVA managed by DOS Master or Pro/Part will show absolutely nothing. - * Running a popular utility named "MR.FIXIT", also by Glen Bredon, against a - * ProDOS volume containing an FVA will report an error. Specifically, "MR.FIXIT" - * will complain that all the blocks used by an FVA as allocated but not in use. - * To solve this problem for Pro/Part I wrote a Foreign Volume Area utility - * program that generates a directory entry for the Pro/Part area. That entry has - * file kind 4, file type $EF, file name "PROPART.AREA" and an auxiliary file - * type $4853 (Steven Hirsch's initials). Today I realized that it's likely that - * the same thing could be done for DOS Master. Study of the source code for - * DOS Master will reveal it that's true. If it is, I propose that "DOS33.AREA" - * be used as the file name and $4247 as the auxiliary type (Glen Bredon's initials). - * As I compose the text of this message I realize that another solution is to - * modify "MR.FIXIT" to be aware of FVAs. But doing that would not allow someone - * doing a directory listing of a ProDOS volume containing an FVA to be aware - * that the FVA exists. +package com.bytezone.diskbrowser.prodos; + +// -----------------------------------------------------------------------------------// +public interface ProdosConstants +// -----------------------------------------------------------------------------------// +{ + static int FILE_TYPE_NON = 0x00; + static int FILE_TYPE_PCD = 0x02; + static int FILE_TYPE_TEXT = 0x04; + static int FILE_TYPE_PDA = 0x05; + static int FILE_TYPE_BINARY = 0x06; + static int FILE_TYPE_FNT = 0x07; + static int FILE_TYPE_FOT = 0x08; // was Apple /// FotoFile + static int FILE_TYPE_DIRECTORY = 0x0F; + static int FILE_TYPE_ADB = 0x19; + static int FILE_TYPE_AWP = 0x1A; + static int FILE_TYPE_ASP = 0x1B; + static int FILE_TYPE_DESCRIPTOR_TABLE = 0x42; + static int FILE_TYPE_GWP = 0x50; + static int FILE_TYPE_GEO = 0x82; + static int FILE_TYPE_IIGS_SOURCE = 0xB0; + static int FILE_TYPE_IIGS_OBJECT = 0xB1; + static int FILE_TYPE_IIGS_APPLICATION = 0xB3; + static int FILE_TYPE_IIGS_DEVICE_DRIVER = 0xBB; + static int FILE_TYPE_LDF = 0xBC; + static int FILE_TYPE_GS_BASIC = 0xAB; + static int FILE_TYPE_GSOS_FILE_SYSTEM_TRANSLATOR = 0xBD; + static int FILE_TYPE_PNT = 0xC0; + static int FILE_TYPE_PIC = 0xC1; + static int FILE_TYPE_ANI = 0xC2; + static int FILE_TYPE_PAL = 0xC3; + static int FILE_TYPE_FONT = 0xC8; + static int FILE_TYPE_FINDER = 0xC9; + static int FILE_TYPE_ICN = 0xCA; + static int FILE_TYPE_APPLETALK = 0xE2; + static int FILE_TYPE_PASCAL_VOLUME = 0xEF; + static int FILE_TYPE_USER_DEFINED_1 = 0xF1; // OVL + static int FILE_TYPE_BAT = 0xF5; + static int FILE_TYPE_INTEGER_BASIC = 0xFA; + static int FILE_TYPE_INTEGER_BASIC_VARS = 0xFB; + static int FILE_TYPE_APPLESOFT_BASIC = 0xFC; + static int FILE_TYPE_APPLESOFT_BASIC_VARS = 0xFD; + static int FILE_TYPE_RELOCATABLE = 0xFE; + static int FILE_TYPE_SYS = 0xFF; + + static int VOLUME_HEADER = 15; + static int SUBDIRECTORY_HEADER = 14; + static int SUBDIRECTORY = 13; + static int GSOS_EXTENDED_FILE = 5; // tech note #25 + static int PASCAL_ON_PROFILE = 4; // tech note #25 + static int TREE = 3; + static int SAPLING = 2; + static int SEEDLING = 1; + static int FREE = 0; + + static String[] fileTypes = { // + "NON", "BAD", "PCD", "PTX", "TXT", "PDA", "BIN", "FNT", // + "FOT", "BA3", "DA3", "WPF", "SOS", "$0D", "$0E", "DIR", // + "RPD", "RPI", "AFD", "AFM", "AFR", "SCL", "PFS", "$17", // + "$18", "ADB", "AWP", "ASP", "$1C", "$1D", "$1E", "$1F", // + "TDM", "$21", "$22", "$23", "$24", "$25", "$26", "$27", // + "$28", "$29", "8SC", "8OB", "8IC", "8LD", "P8C", "$2F", // + "$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37", // + "$38", "$39", "$3A", "$3B", "$3C", "$3D", "$3E", "$3F", // + "DIC", "OCR", "FTD", "$43", "$44", "$45", "$46", "$47", // + "$48", "$49", "$4A", "$4B", "$4C", "$4D", "$4E", "$4F", // + "GWP", "GSS", "GDB", "DRW", "GDP", "HMD", "EDU", "STN", // + "HLP", "COM", "CFG", "ANM", "MUM", "ENT", "DVU", "FIN", // + "$60", "$61", "$62", "$63", "$64", "$65", "$66", "$67", // + "$68", "$69", "$6A", "BIO", "$6C", "TDR", "PRE", "HDV", // + "$70", "$71", "$72", "$73", "$74", "$75", "$76", "$77", // + "$78", "$79", "$7A", "$7B", "$7C", "$7D", "$7E", "$7F", // + "GES", "GEA", "GEO", "GED", "GEF", "GEP", "GEI", "GEX", // + "$88", "GEV", "$8A", "GEC", "GEK", "GEW", "$8E", "$8F", // + "$90", "$91", "$92", "$93", "$94", "$95", "$96", "$97", // + "$98", "$99", "$9A", "$9B", "$9C", "$9D", "$9E", "$9F", // + "WP ", "$A1", "$A2", "$A3", "$A4", "$A5", "$A6", "$A7", // + "$A8", "$A9", "$AA", "GSB", "TDF", "BDF", "$AE", "$AF", // + "SRC", "OBJ", "LIB", "S16", "RTL", "EXE", "PIF", "TIF", // + "NDA", "CDA", "TOL", "DVR", "LDF", "FST", "$BE", "DOC", // + "PNT", "PIC", "ANI", "PAL", "$C4", "OOG", "SCR", "CDV", // + "FON", "FND", "ICN", "$CB", "$CC", "$CD", "$CE", "$CF", // + "$D0", "$D1", "$D2", "$D3", "$D4", "MUS", "INS", "MDI", // + "SND", "$D9", "$DA", "DBM", "$DC", "DDD", "$DE", "$DF", // + "LBR", "$E1", "ATK", "$E3", "$E4", "$E5", "$E6", "$E7", // + "$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "R16", "PAR", // + "CMD", "OVL", "UD2", "UD3", "UD4", "BAT", "UD6", "UD7", // + "PRG", "P16", "INT", "IVR", "BAS", "VAR", "REL", "SYS" }; + + static int ENTRY_SIZE = 39; + static int ENTRIES_PER_BLOCK = 13; + static int BLOCK_ENTRY_SIZE = ENTRY_SIZE * ENTRIES_PER_BLOCK; +} + +/* http://www.kreativekorp.com/miscpages/a2info/filetypes.shtml + * + * $00 UNK Unknown + * $01 BAD Bad Block File + * $02 PCD Pascal Code + * $03 PTX Pascal Text + * $04 TXT ASCII Text + * $05 PDA Pascal Data + * $06 BIN Binary File + * $07 FNT Apple III Font + * $08 FOT HiRes/Double HiRes File + * $09 BA3 Apple III BASIC Program + * $0A DA3 Apple III BASIC Data + * $0B WPF Generic Word Processor File + * $0C SOS SOS System File + * $0F DIR ProDOS Directory + * $10 RPD RPS Data + * $11 RPI RPS Index + * $12 AFD AppleFile Discard + * $13 AFM AppleFile Model + * $14 AFR AppleFile Report + * $15 SCL Screen Library + * $16 PFS PFS Document + * $19 ADB AppleWorks Database + * $1A AWP AppleWorks Word Processor + * $1B ASP AppleWorks Spreadsheet + * $20 TDM Desktop Manager File + * $21 IPS Instant Pascal Source + * $22 UPV UCSD Pascal Volume + * $29 3SD SOS Directory + * $2A 8SC Source Code + * $2B 8OB Object Code + * $2C 8IC Interpretted Code + * $2D 8LD Language Data + * $2E P8C ProDOS 8 Code Module + * $41 OCR Optical Character Recognition File + * $50 GWP Apple IIgs Word Processor File + * $51 GSS Apple IIgs Spreadsheet File + * $52 GDB Apple IIgs Database File + * $53 DRW Object Oriented Graphics File + * $54 GDP Apple IIgs Desktop Publishing File + * $55 HMD HyperMedia + * $56 EDU Educational Program Data + * $57 STN Stationery + * $58 HLP Help File + * $59 COM Communications File + * $5A CFG Configuration File + * $5B ANM Animation File + * $5C MUM Multimedia File + * $5D ENT Entertainment Program File + * $5E DVU Development Utility File + * $60 PRE PC Pre-Boot + * $6B BIO PC BIOS + * $6D DVR PC Driver + * $6E PRE PC Pre-Boot + * $6F HDV PC Hard Disk Image + * $77 KES KES Software + * $7B TLB KES Software + * $7F JCP KES Software + * $80 GeOS System File + * $81 GeOS Desk Accessory + * $82 GeOS Application + * $83 GeOS Document + * $84 GeOS Font + * $85 GeOS Printer Driver + * $86 GeOS Input Driver + * $87 GeOS Auxilary Driver + * $8B GeOS Clock Driver + * $8C GeOS Interface Card Driver + * $8D GeOS Formatting Data + * $A0 WP WordPerfect File + * $A6 + * $AB GSB Apple IIgs BASIC Program + * $AC TDF Apple IIgs BASIC TDF + * $AD BDF Apple IIgs BASIC Data + * $B0 SRC Apple IIgs Source Code + * $B1 OBJ Apple IIgs Object Code + * $B2 LIB Apple IIgs Library + * $B3 S16 Apple IIgs Application Program + * $B4 RTL Apple IIgs Runtime Library + * $B5 EXE Apple IIgs Shell + * $B6 PIF Apple IIgs Permanent INIT + * $B7 TIF Apple IIgs Temporary INIT + * $B8 NDA Apple IIgs New Desk Accessory + * $B9 CDA Apple IIgs Classic Desk Accessory + * $BA TOL Apple IIgs Tool + * $BB DRV Apple IIgs Device Driver + * $BC LDF Apple IIgs Generic Load File + * $BD FST Apple IIgs File System Translator + * $BF DOC Apple IIgs Document + * $C0 PNT Apple IIgs Packed Super HiRes + * $C1 PIC Apple IIgs Super HiRes + * $C2 ANI PaintWorks Animation + * $C3 PAL PaintWorks Palette + * $C5 OOG Object-Oriented Graphics + * $C6 SCR Script + * $C7 CDV Apple IIgs Control Panel + * $C8 FON Apple IIgs Font + * $C9 FND Apple IIgs Finder Data + * $CA ICN Apple IIgs Icon File + * $D5 MUS Music File + * $D6 INS Instrument File + * $D7 MDI MIDI File + * $D8 SND Apple IIgs Sound File + * $DB DBM DB Master Document + * $E0 LBR Archive File + * $E2 ATK AppleTalk Data + * $EE R16 EDASM 816 Relocatable Code + * $EF PAR Pascal Area + * $F0 CMD ProDOS Command File + * $F1 OVL User Defined 1 + * $F2 User Defined 2 + * $F3 User Defined 3 + * $F4 User Defined 4 + * $F5 BAT User Defined 5 + * $F6 User Defined 6 + * $F7 User Defined 7 + * $F8 PRG User Defined 8 + * $F9 P16 Apple IIgs System File + * $FA INT Integer BASIC Program + * $FB IVR Integer BASIC Variables + * $FC BAS Applesoft BASIC Program + * $FD VAR Applesoft BASIC Variables + * $FE REL EDASM Relocatable Code + * $FF SYS ProDOS System File + */ + +// See also http://www.kreativekorp.com/miscpages/a2info/filetypes.shtml + +/* + * https://groups.google.com/forum/#!topic/comp.sys.apple2/waoYCIbkJKs + * + * There are a number of disk utilities available that store images of disks that + * utilize file systems that are not ProDOS, at the end of a ProDOS volume. + * There's DOS Master, by Glen Bredon, that stores images of DOS 3.3 disks at the + * end of a ProDOS volume. Similarly, Pro/Part, by Steven Hirsch, stores images + * of CP/M volumes. Also, there's Pascal Partition Manager (PPM) that stores + * images of UCSD Pascal volumes. I've decided to refer to the area used to store + * volume images, by all three of these systems, as a Foreign Volume Area or FVA. + * All three of these systems modify the Block Allocation Map of a ProDOS volume + * to keep ProDOS from assigning blocks used by FVAs for use by files being + * written by ProDOS. Pascal Partition Manager is different from the other two + * in that it has a file type ($EF) and file kind (4) assigned to it by Apple. + * A directory listing of a ProODS volume containing an FVA managed by PPM will + * show a file name of "PASCAL.AREA". A directory listing of a ProDOS volume + * containing an FVA managed by DOS Master or Pro/Part will show absolutely nothing. + * Running a popular utility named "MR.FIXIT", also by Glen Bredon, against a + * ProDOS volume containing an FVA will report an error. Specifically, "MR.FIXIT" + * will complain that all the blocks used by an FVA as allocated but not in use. + * To solve this problem for Pro/Part I wrote a Foreign Volume Area utility + * program that generates a directory entry for the Pro/Part area. That entry has + * file kind 4, file type $EF, file name "PROPART.AREA" and an auxiliary file + * type $4853 (Steven Hirsch's initials). Today I realized that it's likely that + * the same thing could be done for DOS Master. Study of the source code for + * DOS Master will reveal it that's true. If it is, I propose that "DOS33.AREA" + * be used as the file name and $4247 as the auxiliary type (Glen Bredon's initials). + * As I compose the text of this message I realize that another solution is to + * modify "MR.FIXIT" to be aware of FVAs. But doing that would not allow someone + * doing a directory listing of a ProDOS volume containing an FVA to be aware + * that the FVA exists. */ \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/prodos/ProdosDirectory.java b/src/com/bytezone/diskbrowser/prodos/ProdosDirectory.java index 78d8c16..3926455 100755 --- a/src/com/bytezone/diskbrowser/prodos/ProdosDirectory.java +++ b/src/com/bytezone/diskbrowser/prodos/ProdosDirectory.java @@ -1,145 +1,145 @@ -package com.bytezone.diskbrowser.prodos; - -import java.text.SimpleDateFormat; -import java.util.GregorianCalendar; - -import com.bytezone.diskbrowser.applefile.AbstractFile; -import com.bytezone.diskbrowser.disk.FormattedDisk; -import com.bytezone.diskbrowser.utilities.HexFormatter; - -// -----------------------------------------------------------------------------------// -class ProdosDirectory extends AbstractFile implements ProdosConstants -// -----------------------------------------------------------------------------------// -{ - private static final String NO_DATE = ""; - private static final String newLine = String.format ("%n"); - private static final String newLine2 = newLine + newLine; - private static final SimpleDateFormat sdf = new SimpleDateFormat ("d-MMM-yy"); - private static final SimpleDateFormat stf = new SimpleDateFormat ("H:mm"); - - private final ProdosDisk parentFD; - private final int totalBlocks; - private final int freeBlocks; - private final int usedBlocks; - - // ---------------------------------------------------------------------------------// - public ProdosDirectory (FormattedDisk parent, String name, byte[] buffer, - int totalBlocks, int freeBlocks, int usedBlocks) - // ---------------------------------------------------------------------------------// - { - super (name, buffer); - - this.parentFD = (ProdosDisk) parent; - this.totalBlocks = totalBlocks; - this.freeBlocks = freeBlocks; - this.usedBlocks = usedBlocks; - } - - // ---------------------------------------------------------------------------------// - @Override - public String getText () - // ---------------------------------------------------------------------------------// - { - StringBuffer text = new StringBuffer (); - text.append ("Disk : " + parentFD.getDisplayPath () + newLine2); - for (int i = 0; i < buffer.length; i += 39) - { - int storageType = (buffer[i] & 0xF0) >> 4; - if (storageType == 0) - continue; // break?? - - int nameLength = buffer[i] & 0x0F; - String filename = HexFormatter.getString (buffer, i + 1, nameLength); - String subType = ""; - String locked; - - switch (storageType) - { - case ProdosConstants.VOLUME_HEADER: - case ProdosConstants.SUBDIRECTORY_HEADER: - text.append ("/" + filename + newLine2); - text.append (" NAME TYPE BLOCKS " - + "MODIFIED CREATED ENDFILE SUBTYPE" + newLine2); - break; - - case ProdosConstants.FREE: - case ProdosConstants.SEEDLING: - case ProdosConstants.SAPLING: - case ProdosConstants.TREE: - case ProdosConstants.PASCAL_ON_PROFILE: - case ProdosConstants.GSOS_EXTENDED_FILE: - case ProdosConstants.SUBDIRECTORY: - int type = buffer[i + 16] & 0xFF; - int blocks = HexFormatter.intValue (buffer[i + 19], buffer[i + 20]); - - GregorianCalendar created = HexFormatter.getAppleDate (buffer, i + 24); - String dateC = created == null ? NO_DATE - : sdf.format (created.getTime ()).toUpperCase ().replace (".", ""); - String timeC = created == null ? "" : stf.format (created.getTime ()); - GregorianCalendar modified = HexFormatter.getAppleDate (buffer, i + 33); - String dateM = modified == null ? NO_DATE - : sdf.format (modified.getTime ()).toUpperCase ().replace (".", ""); - String timeM = modified == null ? "" : stf.format (modified.getTime ()); - int eof = - HexFormatter.intValue (buffer[i + 21], buffer[i + 22], buffer[i + 23]); - int fileType = buffer[i + 16] & 0xFF; - locked = (buffer[i + 30] & 0xE0) == 0xE0 ? " " : "*"; - - switch (fileType) - { - case FILE_TYPE_TEXT: - int aux = HexFormatter.intValue (buffer[i + 31], buffer[i + 32]); - subType = String.format ("R=%5d", aux); - break; - - case FILE_TYPE_BINARY: - case FILE_TYPE_PNT: - case FILE_TYPE_PIC: - case FILE_TYPE_FOT: - aux = HexFormatter.intValue (buffer[i + 31], buffer[i + 32]); - subType = String.format ("A=$%4X", aux); - break; - - case FILE_TYPE_AWP: - aux = HexFormatter.intValue (buffer[i + 32], buffer[i + 31]); // backwards! - if (aux != 0) - filename = convert (filename, aux); - break; - - default: - subType = ""; - } - - text.append (String.format ("%s%-15s %3s %5d %9s %5s %9s %5s %8d %7s%n", - locked, filename, ProdosConstants.fileTypes[type], blocks, dateM, timeM, - dateC, timeC, eof, subType)); - break; - - default: - text.append (" >>= 1) - { - if ((flags & weight) != 0) - { - if (newName[i] == '.') - newName[i] = ' '; - else if (newName[i] >= 'A' && newName[i] <= 'Z') - newName[i] += 32; - } - } - return new String (newName); - } +package com.bytezone.diskbrowser.prodos; + +import java.text.SimpleDateFormat; +import java.util.GregorianCalendar; + +import com.bytezone.diskbrowser.applefile.AbstractFile; +import com.bytezone.diskbrowser.disk.FormattedDisk; +import com.bytezone.diskbrowser.utilities.HexFormatter; + +// -----------------------------------------------------------------------------------// +class ProdosDirectory extends AbstractFile implements ProdosConstants +// -----------------------------------------------------------------------------------// +{ + private static final String NO_DATE = ""; + private static final String newLine = String.format ("%n"); + private static final String newLine2 = newLine + newLine; + private static final SimpleDateFormat sdf = new SimpleDateFormat ("d-MMM-yy"); + private static final SimpleDateFormat stf = new SimpleDateFormat ("H:mm"); + + private final ProdosDisk parentFD; + private final int totalBlocks; + private final int freeBlocks; + private final int usedBlocks; + + // ---------------------------------------------------------------------------------// + ProdosDirectory (FormattedDisk parent, String name, byte[] buffer, int totalBlocks, + int freeBlocks, int usedBlocks) + // ---------------------------------------------------------------------------------// + { + super (name, buffer); + + this.parentFD = (ProdosDisk) parent; + this.totalBlocks = totalBlocks; + this.freeBlocks = freeBlocks; + this.usedBlocks = usedBlocks; + } + + // ---------------------------------------------------------------------------------// + @Override + public String getText () + // ---------------------------------------------------------------------------------// + { + StringBuffer text = new StringBuffer (); + text.append ("Disk : " + parentFD.getDisplayPath () + newLine2); + for (int i = 0; i < buffer.length; i += 39) + { + int storageType = (buffer[i] & 0xF0) >> 4; + if (storageType == 0) + continue; // break?? + + int nameLength = buffer[i] & 0x0F; + String filename = HexFormatter.getString (buffer, i + 1, nameLength); + String subType = ""; + String locked; + + switch (storageType) + { + case ProdosConstants.VOLUME_HEADER: + case ProdosConstants.SUBDIRECTORY_HEADER: + text.append ("/" + filename + newLine2); + text.append (" NAME TYPE BLOCKS " + + "MODIFIED CREATED ENDFILE SUBTYPE" + newLine2); + break; + + case ProdosConstants.FREE: + case ProdosConstants.SEEDLING: + case ProdosConstants.SAPLING: + case ProdosConstants.TREE: + case ProdosConstants.PASCAL_ON_PROFILE: + case ProdosConstants.GSOS_EXTENDED_FILE: + case ProdosConstants.SUBDIRECTORY: + int type = buffer[i + 16] & 0xFF; + int blocks = HexFormatter.intValue (buffer[i + 19], buffer[i + 20]); + + GregorianCalendar created = HexFormatter.getAppleDate (buffer, i + 24); + String dateC = created == null ? NO_DATE + : sdf.format (created.getTime ()).toUpperCase ().replace (".", ""); + String timeC = created == null ? "" : stf.format (created.getTime ()); + GregorianCalendar modified = HexFormatter.getAppleDate (buffer, i + 33); + String dateM = modified == null ? NO_DATE + : sdf.format (modified.getTime ()).toUpperCase ().replace (".", ""); + String timeM = modified == null ? "" : stf.format (modified.getTime ()); + int eof = + HexFormatter.intValue (buffer[i + 21], buffer[i + 22], buffer[i + 23]); + int fileType = buffer[i + 16] & 0xFF; + locked = (buffer[i + 30] & 0xE0) == 0xE0 ? " " : "*"; + + switch (fileType) + { + case FILE_TYPE_TEXT: + int aux = HexFormatter.intValue (buffer[i + 31], buffer[i + 32]); + subType = String.format ("R=%5d", aux); + break; + + case FILE_TYPE_BINARY: + case FILE_TYPE_PNT: + case FILE_TYPE_PIC: + case FILE_TYPE_FOT: + aux = HexFormatter.intValue (buffer[i + 31], buffer[i + 32]); + subType = String.format ("A=$%4X", aux); + break; + + case FILE_TYPE_AWP: + aux = HexFormatter.intValue (buffer[i + 32], buffer[i + 31]); // backwards! + if (aux != 0) + filename = convert (filename, aux); + break; + + default: + subType = ""; + } + + text.append (String.format ("%s%-15s %3s %5d %9s %5s %9s %5s %8d %7s%n", + locked, filename, ProdosConstants.fileTypes[type], blocks, dateM, timeM, + dateC, timeC, eof, subType)); + break; + + default: + text.append (" >>= 1) + { + if ((flags & weight) != 0) + { + if (newName[i] == '.') + newName[i] = ' '; + else if (newName[i] >= 'A' && newName[i] <= 'Z') + newName[i] += 32; + } + } + return new String (newName); + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/prodos/ProdosExtendedKeySector.java b/src/com/bytezone/diskbrowser/prodos/ProdosExtendedKeySector.java index 1bee6a7..48e1463 100755 --- a/src/com/bytezone/diskbrowser/prodos/ProdosExtendedKeySector.java +++ b/src/com/bytezone/diskbrowser/prodos/ProdosExtendedKeySector.java @@ -1,59 +1,67 @@ -package com.bytezone.diskbrowser.prodos; - -import com.bytezone.diskbrowser.disk.AbstractSector; -import com.bytezone.diskbrowser.disk.Disk; -import com.bytezone.diskbrowser.disk.DiskAddress; - -// see Prodos 8 Tech note #25 -class ProdosExtendedKeySector extends AbstractSector -{ - public ProdosExtendedKeySector (Disk disk, byte[] buffer, DiskAddress diskAddress) - { - super (disk, buffer, diskAddress); - } - - @Override - public String createText () - { - StringBuilder text = getHeader ("Prodos Extended Key Block"); - - for (int i = 0; i < 512; i += 256) - { - String type = i == 0 ? "Data" : "Resource"; - addText (text, buffer, i, 1, - type + " fork storage type (" + getType (buffer[i]) + ")"); - addTextAndDecimal (text, buffer, i + 1, 2, "Key block"); - addTextAndDecimal (text, buffer, i + 3, 2, "Blocks used"); - addTextAndDecimal (text, buffer, i + 5, 3, "EOF"); - text.append ("\n"); - - // check for Finder Info records - if (i == 0 && buffer[8] != 0) - { - for (int j = 0; j <= 18; j += 18) - { - addTextAndDecimal (text, buffer, j + 8, 1, "Size"); - addTextAndDecimal (text, buffer, j + 9, 1, "Type"); - addTextAndDecimal (text, buffer, j + 10, 16, "Finder info"); - } - } - } - - return text.toString (); - } - - private String getType (byte flag) - { - switch ((flag & 0x0F)) - { - case ProdosConstants.SEEDLING: - return "Seedling"; - case ProdosConstants.SAPLING: - return "Sapling"; - case ProdosConstants.TREE: - return "Tree"; - default: - return "???"; - } - } +package com.bytezone.diskbrowser.prodos; + +import com.bytezone.diskbrowser.disk.AbstractSector; +import com.bytezone.diskbrowser.disk.Disk; +import com.bytezone.diskbrowser.disk.DiskAddress; + +// see Prodos 8 Tech note #25 +// -----------------------------------------------------------------------------------// +class ProdosExtendedKeySector extends AbstractSector +// -----------------------------------------------------------------------------------// +{ + // ---------------------------------------------------------------------------------// + ProdosExtendedKeySector (Disk disk, byte[] buffer, DiskAddress diskAddress) + // ---------------------------------------------------------------------------------// + { + super (disk, buffer, diskAddress); + } + + // ---------------------------------------------------------------------------------// + @Override + public String createText () + // ---------------------------------------------------------------------------------// + { + StringBuilder text = getHeader ("Prodos Extended Key Block"); + + for (int i = 0; i < 512; i += 256) + { + String type = i == 0 ? "Data" : "Resource"; + addText (text, buffer, i, 1, + type + " fork storage type (" + getType (buffer[i]) + ")"); + addTextAndDecimal (text, buffer, i + 1, 2, "Key block"); + addTextAndDecimal (text, buffer, i + 3, 2, "Blocks used"); + addTextAndDecimal (text, buffer, i + 5, 3, "EOF"); + text.append ("\n"); + + // check for Finder Info records + if (i == 0 && buffer[8] != 0) + { + for (int j = 0; j <= 18; j += 18) + { + addTextAndDecimal (text, buffer, j + 8, 1, "Size"); + addTextAndDecimal (text, buffer, j + 9, 1, "Type"); + addTextAndDecimal (text, buffer, j + 10, 16, "Finder info"); + } + } + } + + return text.toString (); + } + + // ---------------------------------------------------------------------------------// + private String getType (byte flag) + // ---------------------------------------------------------------------------------// + { + switch ((flag & 0x0F)) + { + case ProdosConstants.SEEDLING: + return "Seedling"; + case ProdosConstants.SAPLING: + return "Sapling"; + case ProdosConstants.TREE: + return "Tree"; + default: + return "???"; + } + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/prodos/ProdosIndexSector.java b/src/com/bytezone/diskbrowser/prodos/ProdosIndexSector.java index 942235e..c9da51a 100755 --- a/src/com/bytezone/diskbrowser/prodos/ProdosIndexSector.java +++ b/src/com/bytezone/diskbrowser/prodos/ProdosIndexSector.java @@ -1,42 +1,48 @@ -package com.bytezone.diskbrowser.prodos; - -import com.bytezone.diskbrowser.disk.AbstractSector; -import com.bytezone.diskbrowser.disk.Disk; -import com.bytezone.diskbrowser.disk.DiskAddress; -import com.bytezone.diskbrowser.utilities.HexFormatter; - -class ProdosIndexSector extends AbstractSector -{ - private final String name; - - ProdosIndexSector (String name, Disk disk, byte[] buffer, DiskAddress diskAddress) - { - super (disk, buffer, diskAddress); - this.name = name; - } - - @Override - public String createText () - { - StringBuilder text = getHeader ("Prodos Index Block : " + name); - - for (int i = 0; i < 256; i++) - { - text.append ( - String.format ("%02X %02X %02X", i, buffer[i], buffer[i + 256])); - if (buffer[i] != 0 || buffer[i + 256] != 0) - { - int blockNo = HexFormatter.intValue (buffer[i], buffer[i + 256]); - String valid = disk.isValidAddress (blockNo) ? "" : " *** invalid ***"; - text.append (String.format (" %s%s%n", "block " + blockNo, valid)); - } - else - text.append ("\n"); - } - - if (text.length () > 0) - text.deleteCharAt (text.length () - 1); - - return text.toString (); - } +package com.bytezone.diskbrowser.prodos; + +import com.bytezone.diskbrowser.disk.AbstractSector; +import com.bytezone.diskbrowser.disk.Disk; +import com.bytezone.diskbrowser.disk.DiskAddress; +import com.bytezone.diskbrowser.utilities.HexFormatter; + +// -----------------------------------------------------------------------------------// +class ProdosIndexSector extends AbstractSector +// -----------------------------------------------------------------------------------// +{ + private final String name; + + // ---------------------------------------------------------------------------------// + ProdosIndexSector (String name, Disk disk, byte[] buffer, DiskAddress diskAddress) + // ---------------------------------------------------------------------------------// + { + super (disk, buffer, diskAddress); + this.name = name; + } + + // ---------------------------------------------------------------------------------// + @Override + public String createText () + // ---------------------------------------------------------------------------------// + { + StringBuilder text = getHeader ("Prodos Index Block : " + name); + + for (int i = 0; i < 256; i++) + { + text.append ( + String.format ("%02X %02X %02X", i, buffer[i], buffer[i + 256])); + if (buffer[i] != 0 || buffer[i + 256] != 0) + { + int blockNo = HexFormatter.intValue (buffer[i], buffer[i + 256]); + String valid = disk.isValidAddress (blockNo) ? "" : " *** invalid ***"; + text.append (String.format (" %s%s%n", "block " + blockNo, valid)); + } + else + text.append ("\n"); + } + + if (text.length () > 0) + text.deleteCharAt (text.length () - 1); + + return text.toString (); + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/prodos/SubDirectoryHeader.java b/src/com/bytezone/diskbrowser/prodos/SubDirectoryHeader.java index 4d10375..3e52462 100755 --- a/src/com/bytezone/diskbrowser/prodos/SubDirectoryHeader.java +++ b/src/com/bytezone/diskbrowser/prodos/SubDirectoryHeader.java @@ -1,48 +1,58 @@ -package com.bytezone.diskbrowser.prodos; - -import java.util.List; - -import com.bytezone.diskbrowser.disk.DiskAddress; -import com.bytezone.diskbrowser.gui.DataSource; -import com.bytezone.diskbrowser.utilities.HexFormatter; - -class SubDirectoryHeader extends DirectoryHeader -{ - private final int parentPointer; - private final int parentSequence; - private final int parentSize; - - public SubDirectoryHeader (ProdosDisk parentDisk, byte[] entryBuffer, FileEntry parent) - { - super (parentDisk, entryBuffer); - this.parentDirectory = parent.parentDirectory; - - parentPointer = HexFormatter.intValue (entryBuffer[35], entryBuffer[36]); - parentSequence = entryBuffer[37] & 0xFF; - parentSize = entryBuffer[38] & 0xFF; - - if (false) - System.out.printf ("", parentPointer, parentSequence, parentSize); - } - - @Override - public DataSource getDataSource () - { - // should this return a directory listing? - return null; - } - - @Override - public List getSectors () - { - return null; - } - - @Override - public String toString () - { - String locked = (access == 0x01) ? "*" : " "; - return String.format (" %s%-40s %15s", locked, "/" + name, - parentDisk.df.format (created.getTime ())); - } +package com.bytezone.diskbrowser.prodos; + +import java.util.List; + +import com.bytezone.diskbrowser.disk.DiskAddress; +import com.bytezone.diskbrowser.gui.DataSource; +import com.bytezone.diskbrowser.utilities.HexFormatter; + +// -----------------------------------------------------------------------------------// +class SubDirectoryHeader extends DirectoryHeader +// -----------------------------------------------------------------------------------// +{ + private final int parentPointer; + private final int parentSequence; + private final int parentSize; + + // ---------------------------------------------------------------------------------// + SubDirectoryHeader (ProdosDisk parentDisk, byte[] entryBuffer, FileEntry parent) + // ---------------------------------------------------------------------------------// + { + super (parentDisk, entryBuffer); + this.parentDirectory = parent.parentDirectory; + + parentPointer = HexFormatter.intValue (entryBuffer[35], entryBuffer[36]); + parentSequence = entryBuffer[37] & 0xFF; + parentSize = entryBuffer[38] & 0xFF; + + if (false) + System.out.printf ("", parentPointer, parentSequence, parentSize); + } + + // ---------------------------------------------------------------------------------// + @Override + public DataSource getDataSource () + // ---------------------------------------------------------------------------------// + { + // should this return a directory listing? + return null; + } + + // ---------------------------------------------------------------------------------// + @Override + public List getSectors () + // ---------------------------------------------------------------------------------// + { + return null; + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + String locked = (access == 0x01) ? "*" : " "; + return String.format (" %s%-40s %15s", locked, "/" + name, + parentDisk.df.format (created.getTime ())); + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/prodos/VolumeDirectoryHeader.java b/src/com/bytezone/diskbrowser/prodos/VolumeDirectoryHeader.java index 25d6d7f..8f912dc 100755 --- a/src/com/bytezone/diskbrowser/prodos/VolumeDirectoryHeader.java +++ b/src/com/bytezone/diskbrowser/prodos/VolumeDirectoryHeader.java @@ -1,133 +1,143 @@ -package com.bytezone.diskbrowser.prodos; - -import java.util.ArrayList; -import java.util.List; - -import com.bytezone.diskbrowser.disk.DiskAddress; -import com.bytezone.diskbrowser.gui.DataSource; -import com.bytezone.diskbrowser.utilities.HexFormatter; - -/* - * There is only one of these - it is always the first entry in the first block. - * Every other entry will be either a SubDirectoryHeader or a FileEntry. - */ -class VolumeDirectoryHeader extends DirectoryHeader -{ - protected final int bitMapBlock; - protected int totalBlocks; - protected int freeBlocks; - protected int usedBlocks; - protected int totalBitMapBlocks; - - public VolumeDirectoryHeader (ProdosDisk parentDisk, byte[] entryBuffer) - { - super (parentDisk, entryBuffer); - - bitMapBlock = HexFormatter.unsignedShort (entryBuffer, 35); - totalBlocks = HexFormatter.unsignedShort (entryBuffer, 37); - - // if (totalBlocks == 0xFFFF || totalBlocks == 0x7FFF) - // totalBlocks = (int) disk.getFile ().length () / 4096 * 8;// ignore extra bytes - - totalBitMapBlocks = (totalBlocks - 1) / 512 + 1; - - int block = 2; - do - { - dataBlocks.add (disk.getDiskAddress (block)); - byte[] buffer = disk.readSector (block); - block = HexFormatter.unsignedShort (buffer, 2); - } while (block > 0); - - // convert the Free Sector Table - // int bitMapBytes = totalBlocks / 8; // one bit per block - int bitMapBytes = (totalBlocks - 1) / 8 + 1; // one bit per block - byte[] buffer = new byte[bitMapBytes]; - int bitMapBlocks = (bitMapBytes - 1) / disk.getSectorsPerTrack () + 1; - int lastBitMapBlock = bitMapBlock + bitMapBlocks - 1; - int ptr = 0; - - for (block = bitMapBlock; block <= lastBitMapBlock; block++) - { - byte[] temp = disk.readSector (block); - int bytesToCopy = buffer.length - ptr; - if (bytesToCopy > temp.length) - bytesToCopy = temp.length; - System.arraycopy (temp, 0, buffer, ptr, bytesToCopy); - ptr += bytesToCopy; - } - - block = 0; - - // nb1 dual-dos disk needs to use totalBlocks obtained from disk - // int max1 = (totalBlocks - 1) / 8 + 1; // bytes required for sector map - // nb2 hard disk may be truncated, so use actual number of blocks - // int max2 = (disk.getTotalBlocks () - 1) / 8 + 1; // bytes required for sector map - - int max = (Math.min (totalBlocks, disk.getTotalBlocks ()) - 1) / 8 + 1; - // System.out.printf ("total blocks %,d%n", totalBlocks); - // System.out.printf ("disk blocks %,d%n", disk.getTotalBlocks ()); - - for (int i = 0; i < max; i++) - { - byte b = buffer[i]; - for (int j = 0; j < 8; j++) - { - if ((b & 0x80) == 0x80) - { - freeBlocks++; - parentDisk.setSectorFree (block++, true); - } - else - { - usedBlocks++; - parentDisk.setSectorFree (block++, false); - } - b <<= 1; - } - } - } - - @Override - public DataSource getDataSource () - { - List blockList = new ArrayList<> (); - int block = 2; - do - { - byte[] buf = disk.readSector (block); - blockList.add (buf); - block = HexFormatter.intValue (buf[2], buf[3]); // next block - } while (block > 0); - - byte[] fullBuffer = new byte[blockList.size () * 507]; - int offset = 0; - for (byte[] bfr : blockList) - { - System.arraycopy (bfr, 4, fullBuffer, offset, 507); - offset += 507; - } - return new ProdosDirectory (parentDisk, name, fullBuffer, totalBlocks, freeBlocks, - usedBlocks); - } - - @Override - public List getSectors () - { - List sectors = new ArrayList<> (); - sectors.addAll (dataBlocks); - return sectors; - } - - @Override - public String toString () - { - if (false) - { - String locked = (access == 0x01) ? "*" : " "; - String timeC = created == null ? "" : parentDisk.df.format (created.getTime ()); - return String.format (" %s%-42s %15s", locked, "/" + name, timeC); - } - return name; - } +package com.bytezone.diskbrowser.prodos; + +import java.util.ArrayList; +import java.util.List; + +import com.bytezone.diskbrowser.disk.DiskAddress; +import com.bytezone.diskbrowser.gui.DataSource; +import com.bytezone.diskbrowser.utilities.HexFormatter; + +/* + * There is only one of these - it is always the first entry in the first block. + * Every other entry will be either a SubDirectoryHeader or a FileEntry. + */ +// -----------------------------------------------------------------------------------// +class VolumeDirectoryHeader extends DirectoryHeader +// -----------------------------------------------------------------------------------// +{ + protected final int bitMapBlock; + protected int totalBlocks; + protected int freeBlocks; + protected int usedBlocks; + protected int totalBitMapBlocks; + + // ---------------------------------------------------------------------------------// + VolumeDirectoryHeader (ProdosDisk parentDisk, byte[] entryBuffer) + // ---------------------------------------------------------------------------------// + { + super (parentDisk, entryBuffer); + + bitMapBlock = HexFormatter.unsignedShort (entryBuffer, 35); + totalBlocks = HexFormatter.unsignedShort (entryBuffer, 37); + + // if (totalBlocks == 0xFFFF || totalBlocks == 0x7FFF) + // totalBlocks = (int) disk.getFile ().length () / 4096 * 8;// ignore extra bytes + + totalBitMapBlocks = (totalBlocks - 1) / 512 + 1; + + int block = 2; + do + { + dataBlocks.add (disk.getDiskAddress (block)); + byte[] buffer = disk.readSector (block); + block = HexFormatter.unsignedShort (buffer, 2); + } while (block > 0); + + // convert the Free Sector Table + // int bitMapBytes = totalBlocks / 8; // one bit per block + int bitMapBytes = (totalBlocks - 1) / 8 + 1; // one bit per block + byte[] buffer = new byte[bitMapBytes]; + int bitMapBlocks = (bitMapBytes - 1) / disk.getSectorsPerTrack () + 1; + int lastBitMapBlock = bitMapBlock + bitMapBlocks - 1; + int ptr = 0; + + for (block = bitMapBlock; block <= lastBitMapBlock; block++) + { + byte[] temp = disk.readSector (block); + int bytesToCopy = buffer.length - ptr; + if (bytesToCopy > temp.length) + bytesToCopy = temp.length; + System.arraycopy (temp, 0, buffer, ptr, bytesToCopy); + ptr += bytesToCopy; + } + + block = 0; + + // nb1 dual-dos disk needs to use totalBlocks obtained from disk + // int max1 = (totalBlocks - 1) / 8 + 1; // bytes required for sector map + // nb2 hard disk may be truncated, so use actual number of blocks + // int max2 = (disk.getTotalBlocks () - 1) / 8 + 1; // bytes required for sector map + + int max = (Math.min (totalBlocks, disk.getTotalBlocks ()) - 1) / 8 + 1; + // System.out.printf ("total blocks %,d%n", totalBlocks); + // System.out.printf ("disk blocks %,d%n", disk.getTotalBlocks ()); + + for (int i = 0; i < max; i++) + { + byte b = buffer[i]; + for (int j = 0; j < 8; j++) + { + if ((b & 0x80) == 0x80) + { + freeBlocks++; + parentDisk.setSectorFree (block++, true); + } + else + { + usedBlocks++; + parentDisk.setSectorFree (block++, false); + } + b <<= 1; + } + } + } + + // ---------------------------------------------------------------------------------// + @Override + public DataSource getDataSource () + // ---------------------------------------------------------------------------------// + { + List blockList = new ArrayList<> (); + int block = 2; + do + { + byte[] buf = disk.readSector (block); + blockList.add (buf); + block = HexFormatter.intValue (buf[2], buf[3]); // next block + } while (block > 0); + + byte[] fullBuffer = new byte[blockList.size () * 507]; + int offset = 0; + for (byte[] bfr : blockList) + { + System.arraycopy (bfr, 4, fullBuffer, offset, 507); + offset += 507; + } + return new ProdosDirectory (parentDisk, name, fullBuffer, totalBlocks, freeBlocks, + usedBlocks); + } + + // ---------------------------------------------------------------------------------// + @Override + public List getSectors () + // ---------------------------------------------------------------------------------// + { + List sectors = new ArrayList<> (); + sectors.addAll (dataBlocks); + return sectors; + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + if (false) + { + String locked = (access == 0x01) ? "*" : " "; + String timeC = created == null ? "" : parentDisk.df.format (created.getTime ()); + return String.format (" %s%-42s %15s", locked, "/" + name, timeC); + } + return name; + } } \ No newline at end of file