diff --git a/src/com/bytezone/diskbrowser/disk/DiskFactory.java b/src/com/bytezone/diskbrowser/disk/DiskFactory.java index e68a496..bd84d22 100755 --- a/src/com/bytezone/diskbrowser/disk/DiskFactory.java +++ b/src/com/bytezone/diskbrowser/disk/DiskFactory.java @@ -46,10 +46,10 @@ public class DiskFactory } // ---------------------------------------------------------------------------------// - public static FormattedDisk createDisk (String path) + public static FormattedDisk createDisk (String pathName) // ---------------------------------------------------------------------------------// { - FormattedDisk disk = create (path); + FormattedDisk disk = create (pathName); // if (disk.getDisk ().getInterleave () > 0) // { // System.out.println (disk); @@ -146,16 +146,19 @@ public class DiskFactory } } - if (suffix.equals ("sdk")) + if ("sdk".equals (suffix)) // shrinkit disk archive { if (debug) System.out.println (" ** sdk **"); try { NuFX nuFX = new NuFX (file.toPath ()); - File tmp = File.createTempFile ("sdk", null); + int totalDisks = nuFX.getTotalDisks (); + if (totalDisks == 0) + return null; + File tmp = File.createTempFile (suffix, null); FileOutputStream fos = new FileOutputStream (tmp); - fos.write (nuFX.getBuffer ()); + fos.write (nuFX.getDiskBuffer ()); fos.close (); tmp.deleteOnExit (); file = tmp; @@ -172,6 +175,30 @@ public class DiskFactory return null; } } + else if ("shk".equals (suffix)) // shrinkit file archive + { + if (debug) + System.out.println (" ** shk **"); + + try + { + NuFX nuFX = new NuFX (file.toPath ()); + int totalFiles = nuFX.getTotalFiles (); + // System.out.printf ("Total files: %d%n", totalFiles); + if (totalFiles == 0) + return null; + nuFX.getDiskBuffer (); + } + catch (IOException e) + { + e.printStackTrace (); + return null; + } + catch (FileFormatException e) + { + return null; + } + } FormattedDisk disk = null; FormattedDisk disk2 = null; diff --git a/src/com/bytezone/diskbrowser/gui/DiskLayoutSelection.java b/src/com/bytezone/diskbrowser/gui/DiskLayoutSelection.java index 7110c3b..a4e5928 100755 --- a/src/com/bytezone/diskbrowser/gui/DiskLayoutSelection.java +++ b/src/com/bytezone/diskbrowser/gui/DiskLayoutSelection.java @@ -7,6 +7,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import com.bytezone.diskbrowser.disk.AppleDiskAddress; import com.bytezone.diskbrowser.disk.Disk; import com.bytezone.diskbrowser.disk.DiskAddress; import com.bytezone.diskbrowser.disk.FormattedDisk; @@ -167,8 +168,7 @@ class DiskLayoutSelection implements Iterable highlights.clear (); if (list != null) for (DiskAddress da : list) - if (da != null) - // && (da.getBlockNo () > 0 || ((AppleDiskAddress) da).zeroFlag ())) + if (da != null && (da.getBlockNo () > 0 || ((AppleDiskAddress) da).zeroFlag ())) highlights.add (da); } diff --git a/src/com/bytezone/diskbrowser/gui/SaveSectorsAction.java b/src/com/bytezone/diskbrowser/gui/SaveSectorsAction.java index 5291172..45665ce 100644 --- a/src/com/bytezone/diskbrowser/gui/SaveSectorsAction.java +++ b/src/com/bytezone/diskbrowser/gui/SaveSectorsAction.java @@ -40,6 +40,7 @@ class SaveSectorsAction extends DefaultAction implements SectorSelectionListener JFileChooser fileChooser = new JFileChooser (); fileChooser.setDialogTitle ("Save sectors"); fileChooser.setSelectedFile (new File ("saved-" + buffer.length + ".bin")); + if (fileChooser.showSaveDialog (null) == JFileChooser.APPROVE_OPTION) { File file = fileChooser.getSelectedFile (); diff --git a/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java b/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java index 8012473..922a4dd 100755 --- a/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java +++ b/src/com/bytezone/diskbrowser/prodos/ProdosConstants.java @@ -32,6 +32,7 @@ public interface ProdosConstants static int FILE_TYPE_FONT = 0xC8; static int FILE_TYPE_FINDER = 0xC9; static int FILE_TYPE_ICN = 0xCA; + static int FILE_TYPE_LBR = 0xE0; static int FILE_TYPE_APPLETALK = 0xE2; static int FILE_TYPE_PASCAL_VOLUME = 0xEF; static int FILE_TYPE_OVL = 0xF1; @@ -87,8 +88,8 @@ public interface ProdosConstants "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 ENTRY_SIZE = 0x27; + static int ENTRIES_PER_BLOCK = 0x0D; static int BLOCK_ENTRY_SIZE = ENTRY_SIZE * ENTRIES_PER_BLOCK; } diff --git a/src/com/bytezone/diskbrowser/prodos/ProdosDirectory.java b/src/com/bytezone/diskbrowser/prodos/ProdosDirectory.java index a40dd18..e2c763c 100755 --- a/src/com/bytezone/diskbrowser/prodos/ProdosDirectory.java +++ b/src/com/bytezone/diskbrowser/prodos/ProdosDirectory.java @@ -43,7 +43,7 @@ class ProdosDirectory extends AbstractFile implements ProdosConstants { StringBuffer text = new StringBuffer (); text.append ("Disk : " + parentFD.getDisplayPath () + newLine2); - for (int i = 0; i < buffer.length; i += 39) + for (int i = 0; i < buffer.length; i += ENTRY_SIZE) { int storageType = (buffer[i] & 0xF0) >> 4; if (storageType == 0) @@ -56,20 +56,21 @@ class ProdosDirectory extends AbstractFile implements ProdosConstants switch (storageType) { - case ProdosConstants.VOLUME_HEADER: - case ProdosConstants.SUBDIRECTORY_HEADER: - text.append ("/" + filename + newLine2); + case VOLUME_HEADER: + case SUBDIRECTORY_HEADER: + String root = storageType == VOLUME_HEADER ? "/" : ""; + text.append (root + 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: + case FREE: + case SEEDLING: + case SAPLING: + case TREE: + case PASCAL_ON_PROFILE: + case GSOS_EXTENDED_FILE: + case SUBDIRECTORY: int type = buffer[i + 16] & 0xFF; int blocks = Utility.intValue (buffer[i + 19], buffer[i + 20]); diff --git a/src/com/bytezone/diskbrowser/prodos/ProdosDisk.java b/src/com/bytezone/diskbrowser/prodos/ProdosDisk.java index ed95004..df7f552 100755 --- a/src/com/bytezone/diskbrowser/prodos/ProdosDisk.java +++ b/src/com/bytezone/diskbrowser/prodos/ProdosDisk.java @@ -138,7 +138,7 @@ public class ProdosDisk extends AbstractFormattedDisk sectorTypes[block] = currentSectorType; for (int i = 0; i < volumeDirectoryHeader.totalBitMapBlocks; i++) sectorTypes[volumeDirectoryHeader.bitMapBlock + i] = volumeMapSector; - parentNode.setUserObject (volumeDirectoryHeader); // populate the empty volume node + parentNode.setUserObject (volumeDirectoryHeader); // populate the empty volume node localHeader = volumeDirectoryHeader; break; diff --git a/src/com/bytezone/diskbrowser/prodos/write/DirectoryHeader.java b/src/com/bytezone/diskbrowser/prodos/write/DirectoryHeader.java new file mode 100644 index 0000000..f217ebe --- /dev/null +++ b/src/com/bytezone/diskbrowser/prodos/write/DirectoryHeader.java @@ -0,0 +1,98 @@ +package com.bytezone.diskbrowser.prodos.write; + +import static com.bytezone.diskbrowser.prodos.write.ProdosDisk.ENTRY_SIZE; + +import java.time.LocalDateTime; + +// -----------------------------------------------------------------------------------// +public class DirectoryHeader +// -----------------------------------------------------------------------------------// +{ + static final int BLOCK_SIZE = 512; + + ProdosDisk disk; + byte[] buffer; + int ptr; + + String fileName; + byte storageType; + LocalDateTime creationDate; + byte version = 0x00; + byte minVersion = 0x00; + byte access = (byte) 0xE3; + byte entryLength = ENTRY_SIZE; + byte entriesPerBlock = 0x0D; + int fileCount; + + // ---------------------------------------------------------------------------------// + public DirectoryHeader (ProdosDisk disk, byte[] buffer, int ptr) + // ---------------------------------------------------------------------------------// + { + this.disk = disk; + this.buffer = buffer; + this.ptr = ptr; + } + + // ---------------------------------------------------------------------------------// + void read () + // ---------------------------------------------------------------------------------// + { + storageType = (byte) ((buffer[ptr] & 0xF0) >>> 4); + int nameLength = buffer[ptr] & 0x0F; + fileName = new String (buffer, ptr + 1, nameLength); + creationDate = ProdosDisk.getAppleDate (buffer, ptr + 0x18); + version = buffer[ptr + 0x1C]; + minVersion = buffer[ptr + 0x1D]; + access = buffer[ptr + 0x1E]; + entryLength = buffer[ptr + 0x1F]; + entriesPerBlock = buffer[ptr + 0x20]; + fileCount = ProdosDisk.readShort (buffer, ptr + 0x21); + + // System.out.println (this); + } + + // ---------------------------------------------------------------------------------// + void write () + // ---------------------------------------------------------------------------------// + { + buffer[ptr] = (byte) ((storageType << 4) | fileName.length ()); + System.arraycopy (fileName.getBytes (), 0, buffer, ptr + 1, fileName.length ()); + + ProdosDisk.putAppleDate (buffer, ptr + 0x18, creationDate); + buffer[ptr + 0x1C] = version; + buffer[ptr + 0x1D] = minVersion; + buffer[ptr + 0x1E] = access; + buffer[ptr + 0x1F] = entryLength; + buffer[ptr + 0x20] = entriesPerBlock; + ProdosDisk.writeShort (buffer, ptr + 0x21, fileCount); + + // System.out.println (this); + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + + int blockNo = ptr / BLOCK_SIZE; + text.append (String.format ("Block ............ %04X%n", blockNo)); + text.append (String.format ("Entry ............ %02X%n", + (ptr - blockNo * BLOCK_SIZE - 4) / 39)); + + text.append (String.format ("Storage type ..... %02X %s%n", storageType, + ProdosDisk.storageTypes[storageType])); + text.append (String.format ("Name length ...... %02X%n", fileName.length ())); + text.append (String.format ("File name ........ %s%n", fileName)); + text.append (String.format ("Version .......... %02X%n", version)); + text.append (String.format ("Min version ...... %02X%n", minVersion)); + text.append (String.format ("Created .......... %s%n", creationDate)); + text.append (String.format ("Access ........... %02X%n", access)); + text.append (String.format ("Entry length ..... %02X%n", entryLength)); + text.append (String.format ("Entries per blk .. %02X%n", entriesPerBlock)); + text.append (String.format ("File count ....... %d%n", fileCount)); + + return text.toString (); + } +} diff --git a/src/com/bytezone/diskbrowser/prodos/write/FileEntry.java b/src/com/bytezone/diskbrowser/prodos/write/FileEntry.java new file mode 100644 index 0000000..8b14335 --- /dev/null +++ b/src/com/bytezone/diskbrowser/prodos/write/FileEntry.java @@ -0,0 +1,324 @@ +package com.bytezone.diskbrowser.prodos.write; + +import static com.bytezone.diskbrowser.prodos.write.ProdosDisk.BLOCK_SIZE; +import static com.bytezone.diskbrowser.prodos.write.ProdosDisk.UNDERLINE; + +import java.time.LocalDateTime; + +// -----------------------------------------------------------------------------------// +public class FileEntry +// -----------------------------------------------------------------------------------// +{ + private static final int SEEDLING = 0x01; + private static final int SAPLING = 0x02; + private static final int TREE = 0x03; + + ProdosDisk disk; + byte[] buffer; + int ptr; + + String fileName; + byte storageType; + LocalDateTime creationDate; + LocalDateTime modifiedDate; + byte fileType; + int keyPointer; + int blocksUsed; + int eof; + byte version = 0x00; + byte minVersion = 0x00; + byte access = (byte) 0xE3; + int auxType; + int headerPointer; + + private IndexBlock indexBlock = null; + private MasterIndexBlock masterIndexBlock = null; + + // ---------------------------------------------------------------------------------// + public FileEntry (ProdosDisk disk, byte[] buffer, int ptr) + // ---------------------------------------------------------------------------------// + { + this.disk = disk; + this.buffer = buffer; + this.ptr = ptr; + } + + // ---------------------------------------------------------------------------------// + void read () + // ---------------------------------------------------------------------------------// + { + storageType = (byte) ((buffer[ptr] & 0xF0) >>> 4); + int nameLength = buffer[ptr] & 0x0F; + if (nameLength > 0) + fileName = new String (buffer, ptr + 1, nameLength); + else + fileName = ""; + + fileType = buffer[ptr + 0x10]; + keyPointer = ProdosDisk.readShort (buffer, ptr + 0x11); + blocksUsed = ProdosDisk.readShort (buffer, ptr + 0x13); + eof = ProdosDisk.readTriple (buffer, ptr + 0x15); + creationDate = ProdosDisk.getAppleDate (buffer, ptr + 0x18); + + version = buffer[ptr + 0x1C]; + minVersion = buffer[ptr + 0x1D]; + access = buffer[ptr + 0x1E]; + + auxType = ProdosDisk.readShort (buffer, ptr + 0x1F); + modifiedDate = ProdosDisk.getAppleDate (buffer, ptr + 0x21); + headerPointer = ProdosDisk.readShort (buffer, ptr + 0x25); + } + + // ---------------------------------------------------------------------------------// + void write () + // ---------------------------------------------------------------------------------// + { + buffer[ptr] = (byte) ((storageType << 4) | fileName.length ()); + System.arraycopy (fileName.getBytes (), 0, buffer, ptr + 1, fileName.length ()); + + buffer[ptr + 0x10] = fileType; + ProdosDisk.writeShort (buffer, ptr + 0x11, keyPointer); + ProdosDisk.writeShort (buffer, ptr + 0x13, blocksUsed); + ProdosDisk.writeTriple (buffer, ptr + 0x15, eof); + ProdosDisk.putAppleDate (buffer, ptr + 0x18, creationDate); + + buffer[ptr + 0x1C] = version; + buffer[ptr + 0x1D] = minVersion; + buffer[ptr + 0x1E] = access; + + ProdosDisk.writeShort (buffer, ptr + 0x1F, auxType); + ProdosDisk.putAppleDate (buffer, ptr + 0x21, modifiedDate); + ProdosDisk.writeShort (buffer, ptr + 0x25, headerPointer); + } + + // ---------------------------------------------------------------------------------// + void writeFile (byte[] dataBuffer) + // ---------------------------------------------------------------------------------// + { + this.eof = dataBuffer.length; + + int dataPtr = 0; + int remaining = eof; + + while (dataPtr < eof) + { + int actualBlockNo = allocateNextBlock (); + map (dataPtr / BLOCK_SIZE, actualBlockNo); + + int bufferPtr = actualBlockNo * BLOCK_SIZE; + int tfr = Math.min (remaining, BLOCK_SIZE); + + System.arraycopy (dataBuffer, dataPtr, buffer, bufferPtr, tfr); + + dataPtr += BLOCK_SIZE; + remaining -= BLOCK_SIZE; + } + + writeIndices (); + } + + // ---------------------------------------------------------------------------------// + void writeRecord (int recordNo, byte[] dataBuffer) + // ---------------------------------------------------------------------------------// + { + assert auxType > 0; // record length + + int destPtr = auxType * recordNo; + int remaining = Math.min (auxType, dataBuffer.length); + int max = destPtr + remaining; + int dataPtr = 0; + + if (eof < max) + eof = max; + + while (destPtr < max) + { + int logicalBlockNo = destPtr / BLOCK_SIZE; + int blockOffset = destPtr % BLOCK_SIZE; + int tfr = Math.min (BLOCK_SIZE - blockOffset, remaining); + + int actualBlockNo = getActualBlockNo (logicalBlockNo); + int bufferPtr = actualBlockNo * BLOCK_SIZE + blockOffset; + + System.arraycopy (dataBuffer, dataPtr, buffer, bufferPtr, tfr); + + // System.out.printf ("%7d %5d %5d %5d%n", destPtr, tfr, logicalBlockNo, + // blockOffset); + + destPtr += tfr; + dataPtr += tfr; + remaining -= tfr; + } + + writeIndices (); + } + + // ---------------------------------------------------------------------------------// + int allocateNextBlock () + // ---------------------------------------------------------------------------------// + { + blocksUsed++; + return disk.allocateNextBlock (); + } + + // ---------------------------------------------------------------------------------// + int getActualBlockNo (int logicalBlockNo) + // ---------------------------------------------------------------------------------// + { + int actualBlockNo = 0; + + switch (storageType) + { + case TREE: + actualBlockNo = + masterIndexBlock.get (logicalBlockNo / 256).get (logicalBlockNo % 256); + break; + + case SAPLING: + if (logicalBlockNo < 256) + actualBlockNo = indexBlock.get (logicalBlockNo); + break; + + case SEEDLING: + if (logicalBlockNo == 0) + actualBlockNo = keyPointer; + break; + } + + if (actualBlockNo == 0) + { + actualBlockNo = allocateNextBlock (); + map (logicalBlockNo, actualBlockNo); + } + + return actualBlockNo; + } + + // ---------------------------------------------------------------------------------// + private void writeIndices () + // ---------------------------------------------------------------------------------// + { + if (storageType == TREE) + masterIndexBlock.write (buffer); + else if (storageType == SAPLING) + indexBlock.write (buffer); + } + + // ---------------------------------------------------------------------------------// + void map (int logicalBlockNo, int actualBlockNo) + // ---------------------------------------------------------------------------------// + { + if (logicalBlockNo > 255) // potential TREE + { + if (storageType != TREE) + { + masterIndexBlock = new MasterIndexBlock (allocateNextBlock ()); + + if (storageType == SAPLING) // sapling -> tree + { + masterIndexBlock.set (0, indexBlock); + } + else if (storageType == SEEDLING) // seedling -> sapling -> tree + { + indexBlock = new IndexBlock (allocateNextBlock ()); + indexBlock.set (0, keyPointer); + masterIndexBlock.set (0, indexBlock); + } + + keyPointer = masterIndexBlock.blockNo; + storageType = TREE; + indexBlock = null; + } + + getIndexBlock (logicalBlockNo / 256).set (logicalBlockNo % 256, actualBlockNo); + } + else if (logicalBlockNo > 0) // potential SAPLING + { + if (storageType == TREE) // already a tree + { + getIndexBlock (0).set (logicalBlockNo, actualBlockNo); + } + else if (storageType == SAPLING) // already a sapling + { + indexBlock.set (logicalBlockNo, actualBlockNo); + } + else // new file or already a seedling + { + indexBlock = new IndexBlock (allocateNextBlock ()); + if (storageType == SEEDLING) // seedling -> sapling + indexBlock.set (0, keyPointer); + + keyPointer = indexBlock.blockNo; + storageType = SAPLING; + indexBlock.set (logicalBlockNo, actualBlockNo); + } + } + else if (logicalBlockNo == 0) // potential SEEDLING + { + if (storageType == TREE) // already a tree + { + getIndexBlock (0).set (0, actualBlockNo); + } + else if (storageType == SAPLING) // already a sapling + { + indexBlock.set (0, actualBlockNo); + } + else + { + keyPointer = actualBlockNo; + storageType = SEEDLING; + } + } + else + System.out.println ("Error"); + } + + // ---------------------------------------------------------------------------------// + IndexBlock getIndexBlock (int position) + // ---------------------------------------------------------------------------------// + { + IndexBlock indexBlock = masterIndexBlock.get (position); + + if (indexBlock == null) + { + indexBlock = new IndexBlock (allocateNextBlock ()); + masterIndexBlock.set (position, indexBlock); + } + + return indexBlock; + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + + text.append (UNDERLINE); + text.append ("File Entry\n"); + text.append (UNDERLINE); + int blockNo = ptr / BLOCK_SIZE; + text.append (String.format ("Block ............ %04X%n", blockNo)); + text.append (String.format ("Entry ............ %02X%n", + (ptr - blockNo * BLOCK_SIZE - 4) / 39)); + text.append (String.format ("Storage type ..... %02X %s%n", storageType, + ProdosDisk.storageTypes[storageType])); + text.append (String.format ("Name length ...... %02X%n", fileName.length ())); + text.append (String.format ("File name ........ %s%n", fileName)); + text.append (String.format ("File type ........ %02X%n", fileType)); + text.append (String.format ("Key pointer ...... %04X%n", keyPointer)); + text.append (String.format ("Blocks used ...... %d%n", blocksUsed)); + text.append (String.format ("EOF .............. %d%n", eof)); + text.append (String.format ("Created .......... %s%n", creationDate)); + text.append (String.format ("Version .......... %02X%n", version)); + text.append (String.format ("Min version ...... %02X%n", minVersion)); + text.append (String.format ("Access ........... %02X%n", access)); + text.append (String.format ("Aux .............. %d%n", auxType)); + text.append (String.format ("Modified ......... %s%n", modifiedDate)); + text.append (String.format ("Header ptr ....... %04X%n", headerPointer)); + text.append (UNDERLINE); + + return text.toString (); + } +} diff --git a/src/com/bytezone/diskbrowser/prodos/write/IndexBlock.java b/src/com/bytezone/diskbrowser/prodos/write/IndexBlock.java new file mode 100644 index 0000000..76493c8 --- /dev/null +++ b/src/com/bytezone/diskbrowser/prodos/write/IndexBlock.java @@ -0,0 +1,67 @@ +package com.bytezone.diskbrowser.prodos.write; + +import static com.bytezone.diskbrowser.prodos.write.ProdosDisk.BLOCK_SIZE; + +// -----------------------------------------------------------------------------------// +public class IndexBlock +// -----------------------------------------------------------------------------------// +{ + int blockNo; + int[] blocks = new int[BLOCK_SIZE / 2]; + int totalBlocks; + + // ---------------------------------------------------------------------------------// + public IndexBlock (int dataBlockNo) + // ---------------------------------------------------------------------------------// + { + this.blockNo = dataBlockNo; + } + + // ---------------------------------------------------------------------------------// + void set (int position, int actualBlockNo) + // ---------------------------------------------------------------------------------// + { + if (blocks[position] == 0) + totalBlocks++; + + blocks[position] = actualBlockNo; + } + + // ---------------------------------------------------------------------------------// + int get (int position) + // ---------------------------------------------------------------------------------// + { + return blocks[position]; + } + + // ---------------------------------------------------------------------------------// + void write (byte[] buffer) + // ---------------------------------------------------------------------------------// + { + int ptr = blockNo * BLOCK_SIZE; + + for (int i = 0; i < blocks.length; i++) + { + if (blocks[i] == 0) + { + buffer[ptr + i] = 0; + buffer[ptr + i + 0x100] = 0; + } + else + { + int blockNo = blocks[i]; + buffer[ptr + i] = (byte) (blockNo & 0xFF); + buffer[ptr + i + 0x100] = (byte) ((blockNo & 0xFF00) >>> 8); + } + } + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + return String.format ("Index Block: %04X contains %d entries%n", blockNo, + totalBlocks); + } +} diff --git a/src/com/bytezone/diskbrowser/prodos/write/MasterIndexBlock.java b/src/com/bytezone/diskbrowser/prodos/write/MasterIndexBlock.java new file mode 100644 index 0000000..0f3e0d9 --- /dev/null +++ b/src/com/bytezone/diskbrowser/prodos/write/MasterIndexBlock.java @@ -0,0 +1,61 @@ +package com.bytezone.diskbrowser.prodos.write; + +import static com.bytezone.diskbrowser.prodos.write.ProdosDisk.BLOCK_SIZE; + +// -----------------------------------------------------------------------------------// +public class MasterIndexBlock +// -----------------------------------------------------------------------------------// +{ + int blockNo; + IndexBlock[] indexBlocks = new IndexBlock[128]; + int totalBlocks; + + // ---------------------------------------------------------------------------------// + MasterIndexBlock (int blockNo) + // ---------------------------------------------------------------------------------// + { + this.blockNo = blockNo; + } + + // ---------------------------------------------------------------------------------// + void set (int position, IndexBlock indexBlock) + // ---------------------------------------------------------------------------------// + { + if (indexBlocks[position] == null) + totalBlocks++; + + indexBlocks[position] = indexBlock; + } + + // ---------------------------------------------------------------------------------// + IndexBlock get (int position) + // ---------------------------------------------------------------------------------// + { + return indexBlocks[position]; + } + + // ---------------------------------------------------------------------------------// + void write (byte[] buffer) + // ---------------------------------------------------------------------------------// + { + int ptr = blockNo * BLOCK_SIZE; + + for (int i = 0; i < indexBlocks.length; i++) + { + IndexBlock indexBlock = indexBlocks[i]; + if (indexBlock == null) + { + buffer[ptr + i] = 0; + buffer[ptr + i + 0x100] = 0; + } + else + { + indexBlock.write (buffer); + + int blockNo = indexBlock.blockNo; + buffer[ptr + i] = (byte) (blockNo & 0xFF); + buffer[ptr + i + 0x100] = (byte) ((blockNo & 0xFF00) >>> 8); + } + } + } +} diff --git a/src/com/bytezone/diskbrowser/prodos/write/ProdosDisk.java b/src/com/bytezone/diskbrowser/prodos/write/ProdosDisk.java new file mode 100644 index 0000000..4f27a48 --- /dev/null +++ b/src/com/bytezone/diskbrowser/prodos/write/ProdosDisk.java @@ -0,0 +1,551 @@ +package com.bytezone.diskbrowser.prodos.write; + +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.BitSet; +import java.util.HashMap; +import java.util.Map; + +// -----------------------------------------------------------------------------------// +public class ProdosDisk +// -----------------------------------------------------------------------------------// +{ + static final String UNDERLINE = "------------------------------------------------\n"; + + static final int BLOCK_SIZE = 512; + private static final int CATALOG_SIZE = 4; + static final int ENTRY_SIZE = 0x27; + private static final int BITS_PER_BLOCK = 8 * BLOCK_SIZE; + static final String[] storageTypes = + { "Deleted", "Seedling", "Sapling", "Tree", "", "", "", "", "", "", "", "", "", + "Subdirectory", "Subdirectory Header", "Volume Directory Header" }; + + private static byte[] dummySeedling = new byte[50]; + private static byte[] dummySapling = new byte[2530]; + private static byte[] dummyTree = new byte[132000]; + + private BitSet volumeBitMap; + private final int maxBlocks; + private final byte[] buffer; + private final byte[] bootSector = new byte[512]; + + private VolumeDirectoryHeader volumeDirectoryHeader; + private Map subdirectoryHeaders = new HashMap<> (); + + // ---------------------------------------------------------------------------------// + public ProdosDisk (int blocks, String volumeName) throws IOException + // ---------------------------------------------------------------------------------// + { + try (DataInputStream in = new DataInputStream (ProdosDisk.class.getClassLoader () + .getResourceAsStream ("com/bytezone/prodos/block-00.bin"))) + { + int count = in.read (bootSector); + if (count != 512) + System.out.println ("Error with prodos boot sector"); + } + catch (IOException e) + { + e.printStackTrace (); + } + + setBuffer (dummySeedling, "This is a seedling file.~"); + setBuffer (dummySapling, "This is a sapling file.~It could be a bit longer.~"); + setBuffer (dummyTree, "This is a tree file.~It is a bit short though.~"); + + maxBlocks = blocks; + buffer = new byte[blocks * BLOCK_SIZE]; + + volumeBitMap = new BitSet (blocks); + for (int i = 0; i < blocks; i++) + volumeBitMap.set (i, true); + + createCatalog (volumeName); + + for (int i = 1; i < 4; i++) + test (String.format ("FRED/MARY/FILE%02d.TXT", i), i); + for (int i = 4; i <= 4; i++) + test (String.format ("BOB/JANE/TMART%02d.TXT", i), i); + + writeVolumeBitMap (); + + volumeDirectoryHeader.write (); + for (SubdirectoryHeader subdirectoryHeader : subdirectoryHeaders.values ()) + subdirectoryHeader.write (); + } + + // ---------------------------------------------------------------------------------// + void createCatalog (String volumeName) + // ---------------------------------------------------------------------------------// + { + // reserve two boot blocks + allocateNextBlock (); + allocateNextBlock (); + + System.arraycopy (bootSector, 0, buffer, 0, 512); + + // write 4 catalog blocks + for (int i = 0, prevBlockNo = 0; i < CATALOG_SIZE; i++) + { + int blockNo = allocateNextBlock (); + int ptr = blockNo * BLOCK_SIZE; + + writeShort (buffer, ptr, prevBlockNo); + writeShort (buffer, ptr + 2, 0); + + if (prevBlockNo > 0) + writeShort (buffer, prevBlockNo * BLOCK_SIZE + 2, blockNo); + + prevBlockNo = blockNo; + + if (i == 0) + { + volumeDirectoryHeader = new VolumeDirectoryHeader (this, buffer, ptr + 4); + volumeDirectoryHeader.fileName = volumeName; + volumeDirectoryHeader.totalBlocks = maxBlocks; + volumeDirectoryHeader.creationDate = LocalDateTime.now (); + volumeDirectoryHeader.write (); + } + } + + // reserve the bitmap blocks + int indexBlocks = (maxBlocks - 1) / BITS_PER_BLOCK + 1; + for (int i = 0; i < indexBlocks; i++) + allocateNextBlock (); + + writeVolumeBitMap (); + } + + // ---------------------------------------------------------------------------------// + public void saveDisk (Path path) + // ---------------------------------------------------------------------------------// + { + // if (Files.exists (path)) + // { + // System.out.println ("File already exists: " + path); + // return; + // } + + try + { + Files.write (path, buffer); + } + catch (IOException ex) + { + ex.printStackTrace (); + } + } + + // ---------------------------------------------------------------------------------// + public int getFreeBlocks () + // ---------------------------------------------------------------------------------// + { + return volumeBitMap.cardinality (); + } + + // ---------------------------------------------------------------------------------// + void test (String path, int type) + // ---------------------------------------------------------------------------------// + { + String[] paths; + String fileName = ""; + int pos = path.lastIndexOf ('/'); + if (pos > 0) + { + fileName = path.substring (pos + 1); + path = path.substring (0, pos); + paths = path.split ("/"); + } + else + { + fileName = path; + paths = new String[0]; + } + + int catalogBlock = 2; + + for (int i = 0; i < paths.length; i++) + { + String name = paths[i]; + FileEntry fileEntry = searchDirectory (catalogBlock, name); + if (fileEntry == null) + { + FileEntry fileEntry2 = createSubdirectory (catalogBlock, name); + catalogBlock = fileEntry2.keyPointer; + } + else + catalogBlock = fileEntry.keyPointer; + } + + FileEntry fileEntry = searchDirectory (catalogBlock, fileName); + if (fileEntry == null) + fileEntry = createFileEntry (catalogBlock, fileName, type); + } + + // ---------------------------------------------------------------------------------// + FileEntry createFileEntry (int blockNo, String name, int type) + // ---------------------------------------------------------------------------------// + { + FileEntry fileEntry = findFreeSlot (blockNo); + + if (fileEntry != null) + { + fileEntry.fileName = name; + fileEntry.creationDate = LocalDateTime.now (); + fileEntry.modifiedDate = LocalDateTime.now (); + fileEntry.version = 0x00; + fileEntry.minVersion = 0x00; + fileEntry.headerPointer = blockNo; + fileEntry.fileType = 4; // text + + switch (type) + { + case 1: + fileEntry.writeFile (dummySeedling); + break; + + case 2: + fileEntry.writeFile (dummySapling); + break; + + case 3: + fileEntry.writeFile (dummyTree); + break; + + case 4: + fileEntry.auxType = 60; // record length + fileEntry.writeRecord (17, getBuffer (String.format ("Record: %5d~", 17))); + fileEntry.writeRecord (45, getBuffer (String.format ("Record: %5d~", 45))); + fileEntry.writeRecord (50, getBuffer (String.format ("Record: %5d~", 50))); + fileEntry.writeRecord (51, getBuffer (String.format ("Record: %5d~", 51))); + fileEntry.writeRecord (500, getBuffer (String.format ("Record: %5d~", 500))); + fileEntry.writeRecord (2000, getBuffer (String.format ("Record: %5d~", 2000))); + fileEntry.writeRecord (3000, getBuffer (String.format ("Record: %5d~", 3000))); + break; + } + + fileEntry.write (); + updateFileCount (fileEntry.headerPointer); + + return fileEntry; + } + + return null; + } + + // ---------------------------------------------------------------------------------// + private byte[] getBuffer (String text) + // ---------------------------------------------------------------------------------// + { + byte[] dataBuffer = text.getBytes (); + for (int i = 0; i < dataBuffer.length; i++) + if (dataBuffer[i] == '~') + dataBuffer[i] = 0x0D; + else + dataBuffer[i] |= 0x80; + + return dataBuffer; + } + + // ---------------------------------------------------------------------------------// + private void setBuffer (byte[] buffer, String text) + // ---------------------------------------------------------------------------------// + { + byte[] dataBuffer = text.getBytes (); + for (int i = 0; i < dataBuffer.length; i++) + if (dataBuffer[i] == '~') + buffer[i] = 0x0D; + else + buffer[i] = (byte) (dataBuffer[i] | 0x80); + buffer[dataBuffer.length - 1] = 0x0D; + } + + // ---------------------------------------------------------------------------------// + FileEntry searchDirectory (int blockNo, String fileName) + // ---------------------------------------------------------------------------------// + { + int emptySlotPtr = 0; + + do + { + int offset = blockNo * BLOCK_SIZE; + int ptr = offset + 4; + for (int i = 0; i < 13; i++) + { + int storageTypeNameLength = buffer[ptr] & 0xFF; + if (storageTypeNameLength == 0) + { + if (emptySlotPtr == 0) + emptySlotPtr = ptr; + } + else + { + int nameLength = buffer[ptr] & 0x0F; + + String entryName = new String (buffer, ptr + 1, nameLength); + if (entryName.equals (fileName)) + { + FileEntry fileEntry = new FileEntry (this, buffer, ptr); + fileEntry.read (); + return fileEntry; + } + } + + ptr += ENTRY_SIZE; + } + blockNo = ProdosDisk.readShort (buffer, offset + 2); + } while (blockNo > 0); + + return null; + } + + // ---------------------------------------------------------------------------------// + FileEntry createSubdirectory (int blockNo, String name) + // ---------------------------------------------------------------------------------// + { + FileEntry fileEntry = findFreeSlot (blockNo); + + if (fileEntry != null) + { + fileEntry.storageType = 0x0D; // subdirectory + fileEntry.fileName = name; + fileEntry.keyPointer = allocateNextBlock (); + fileEntry.blocksUsed = 1; + fileEntry.eof = 512; + fileEntry.fileType = 0x0F; // DIR + fileEntry.headerPointer = blockNo; + fileEntry.creationDate = LocalDateTime.now (); + fileEntry.modifiedDate = LocalDateTime.now (); + + fileEntry.write (); + + updateFileCount (fileEntry.headerPointer); + + SubdirectoryHeader subdirectoryHeader = + new SubdirectoryHeader (this, buffer, fileEntry.keyPointer * BLOCK_SIZE + 4); + subdirectoryHeader.fileName = name; + subdirectoryHeader.creationDate = LocalDateTime.now (); + subdirectoryHeader.fileCount = 0; + subdirectoryHeader.parentPointer = (byte) blockNo; + subdirectoryHeader.parentEntry = + (byte) (((fileEntry.ptr % BLOCK_SIZE) - 4) / ENTRY_SIZE + 1); + + subdirectoryHeader.write (); + + subdirectoryHeaders.put (fileEntry.keyPointer, subdirectoryHeader); + + return fileEntry; + } + + System.out.println ("failed"); + + return null; // no empty slots found + } + + // ---------------------------------------------------------------------------------// + void updateFileCount (int catalogBlock) + // ---------------------------------------------------------------------------------// + { + if (catalogBlock == 2) + { + volumeDirectoryHeader.fileCount++; + volumeDirectoryHeader.write (); + } + else + { + SubdirectoryHeader subdirectoryHeader = subdirectoryHeaders.get (catalogBlock); + subdirectoryHeader.fileCount++; + subdirectoryHeader.write (); + } + } + + // ---------------------------------------------------------------------------------// + int allocateNextBlock () + // ---------------------------------------------------------------------------------// + { + int nextBlock = getFreeBlock (); + volumeBitMap.set (nextBlock, false); // mark as unavailable + + return nextBlock; + } + + // ---------------------------------------------------------------------------------// + int getFreeBlock () + // ---------------------------------------------------------------------------------// + { + return volumeBitMap.nextSetBit (0); + } + + // ---------------------------------------------------------------------------------// + String getPrefix (String fileName) + // ---------------------------------------------------------------------------------// + { + int pos = fileName.indexOf ('/', 1); + if (pos < 0) + return fileName; + return fileName.substring (0, pos); + } + + // ---------------------------------------------------------------------------------// + FileEntry findFreeSlot (int blockNo) + // ---------------------------------------------------------------------------------// + { + SubdirectoryHeader subdirectoryHeader = subdirectoryHeaders.get (blockNo); + int lastBlockNo = 0; + + do + { + int offset = blockNo * BLOCK_SIZE; + int ptr = offset + 4; + + for (int i = 0; i < 13; i++) + { + if (buffer[ptr] == 0) // free slot + return new FileEntry (this, buffer, ptr); + + ptr += ENTRY_SIZE; + } + + lastBlockNo = blockNo; + blockNo = ProdosDisk.readShort (buffer, offset + 2); // next block + } while (blockNo > 0); + + // no free slots, so add a new catalog block + blockNo = allocateNextBlock (); + + // update file entry size + FileEntry fileEntry = + new FileEntry (this, buffer, subdirectoryHeader.parentPointer * BLOCK_SIZE + + (subdirectoryHeader.parentEntry - 1) * ENTRY_SIZE + 4); + fileEntry.read (); + fileEntry.blocksUsed++; + fileEntry.eof += BLOCK_SIZE; + fileEntry.modifiedDate = LocalDateTime.now (); + fileEntry.write (); + + // update links + int ptr = blockNo * BLOCK_SIZE; + writeShort (buffer, lastBlockNo * BLOCK_SIZE + 2, blockNo); // point to next block + writeShort (buffer, ptr, lastBlockNo); // point to previous block + + return new FileEntry (this, buffer, ptr + 4); // first slot in new block + } + + // ---------------------------------------------------------------------------------// + void writeVolumeBitMap () + // ---------------------------------------------------------------------------------// + { + int ptr = (2 + CATALOG_SIZE) * BLOCK_SIZE; + // int count = 0; + int val = 0; + int blockNo = 0; + + while (blockNo < maxBlocks) + { + val = val << 1; + if (volumeBitMap.get (blockNo++)) + val |= 1; + + if (blockNo % 8 == 0) + { + buffer[ptr++] = (byte) val; + val = 0; + } + } + } + + // ---------------------------------------------------------------------------------// + public static LocalDateTime getAppleDate (byte[] buffer, int offset) + // ---------------------------------------------------------------------------------// + { + int yymmdd = readShort (buffer, offset); + if (yymmdd != 0) + { + int year = (yymmdd & 0xFE00) >> 9; + int month = (yymmdd & 0x01E0) >> 5; + int day = yymmdd & 0x001F; + + int minute = buffer[offset + 2] & 0x3F; + int hour = buffer[offset + 3] & 0x1F; + + if (year < 70) + year += 2000; + else + year += 1900; + return LocalDateTime.of (year, month - 1, day, hour, minute); + } + return null; + } + + // ---------------------------------------------------------------------------------// + public static void putAppleDate (byte[] buffer, int offset, LocalDateTime date) + // ---------------------------------------------------------------------------------// + { + if (date == null) + { + System.out.println ("ignoring null date"); + } + else + { + int year = date.getYear (); + int month = date.getMonthValue (); + int day = date.getDayOfMonth (); + int hour = date.getHour (); + int minute = date.getMinute (); + + if (year < 2000) + year -= 1900; + else + year -= 2000; + + int val1 = year << 9 | month << 5 | day; + writeShort (buffer, offset, val1); + buffer[offset + 2] = (byte) minute; + buffer[offset + 3] = (byte) hour; + } + } + + // ---------------------------------------------------------------------------------// + static void writeShort (byte[] buffer, int ptr, int value) + // ---------------------------------------------------------------------------------// + { + buffer[ptr] = (byte) (value & 0xFF); + buffer[ptr + 1] = (byte) ((value & 0xFF00) >>> 8); + } + + // ---------------------------------------------------------------------------------// + static void writeTriple (byte[] buffer, int ptr, int value) + // ---------------------------------------------------------------------------------// + { + buffer[ptr] = (byte) (value & 0xFF); + buffer[ptr + 1] = (byte) ((value & 0xFF00) >>> 8); + buffer[ptr + 2] = (byte) ((value & 0xFF0000) >>> 16); + } + + // ---------------------------------------------------------------------------------// + static int readShort (byte[] buffer, int ptr) + // ---------------------------------------------------------------------------------// + { + return buffer[ptr] | buffer[ptr + 1] << 8; + } + + // ---------------------------------------------------------------------------------// + static int readTriple (byte[] buffer, int ptr) + // ---------------------------------------------------------------------------------// + { + return buffer[ptr] | buffer[ptr + 1] << 8 | buffer[ptr + 2] << 16; + } + + // ---------------------------------------------------------------------------------// + public static void main (String[] args) throws IOException + // ---------------------------------------------------------------------------------// + { + String base = System.getProperty ("user.home"); + Path path = Paths.get (base + "/Dropbox/Examples/Testing/Test01.po"); + ProdosDisk disk = new ProdosDisk (1600, "DENIS.DISK"); + disk.saveDisk (path); + } +} diff --git a/src/com/bytezone/diskbrowser/prodos/write/SubdirectoryHeader.java b/src/com/bytezone/diskbrowser/prodos/write/SubdirectoryHeader.java new file mode 100644 index 0000000..9885aff --- /dev/null +++ b/src/com/bytezone/diskbrowser/prodos/write/SubdirectoryHeader.java @@ -0,0 +1,75 @@ +package com.bytezone.diskbrowser.prodos.write; + +import static com.bytezone.diskbrowser.prodos.write.ProdosDisk.ENTRY_SIZE; +import static com.bytezone.diskbrowser.prodos.write.ProdosDisk.UNDERLINE; + +// -----------------------------------------------------------------------------------// +public class SubdirectoryHeader extends DirectoryHeader +// -----------------------------------------------------------------------------------// +{ + int parentPointer; + byte parentEntry; + byte parentEntryLength = ENTRY_SIZE; + + // ---------------------------------------------------------------------------------// + public SubdirectoryHeader (ProdosDisk disk, byte[] buffer, int ptr) + // ---------------------------------------------------------------------------------// + { + super (disk, buffer, ptr); + + storageType = (byte) 0x0E; + access = (byte) 0xC3; + } + + // ---------------------------------------------------------------------------------// + @Override + void read () + // ---------------------------------------------------------------------------------// + { + super.read (); + + parentPointer = ProdosDisk.readShort (buffer, ptr + 0x23); + parentEntry = buffer[ptr + 0x25]; + parentEntryLength = buffer[ptr + 0x26]; + } + + // ---------------------------------------------------------------------------------// + @Override + void write () + // ---------------------------------------------------------------------------------// + { + super.write (); + + buffer[ptr + 0x10] = 0x75; // subdirectory header must be 0x75 + + // these are supposed to be unused, but prodos fills them in + // buffer[ptr + 0x11] = version; + // buffer[ptr + 0x13] = access; + // buffer[ptr + 0x14] = parentEntryLength; + // buffer[ptr + 0x15] = entriesPerBlock; + + // fields specific to subdirectory headers + ProdosDisk.writeShort (buffer, ptr + 0x23, parentPointer); + buffer[ptr + 0x25] = parentEntry; + buffer[ptr + 0x26] = parentEntryLength; + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + + text.append (UNDERLINE); + text.append ("Subdirectory Header\n"); + text.append (UNDERLINE); + text.append (super.toString ()); + text.append (String.format ("Parent pointer ... %d%n", parentPointer)); + text.append (String.format ("Parent entry ..... %02X%n", parentEntry)); + text.append (String.format ("PE length ........ %02X%n", parentEntryLength)); + text.append (UNDERLINE); + + return text.toString (); + } +} diff --git a/src/com/bytezone/diskbrowser/prodos/write/VolumeDirectoryHeader.java b/src/com/bytezone/diskbrowser/prodos/write/VolumeDirectoryHeader.java new file mode 100644 index 0000000..44412fe --- /dev/null +++ b/src/com/bytezone/diskbrowser/prodos/write/VolumeDirectoryHeader.java @@ -0,0 +1,60 @@ +package com.bytezone.diskbrowser.prodos.write; + +import static com.bytezone.diskbrowser.prodos.write.ProdosDisk.UNDERLINE; + +// -----------------------------------------------------------------------------------// +public class VolumeDirectoryHeader extends DirectoryHeader +// -----------------------------------------------------------------------------------// +{ + int bitMapPointer = 0x06; + int totalBlocks; + + // ---------------------------------------------------------------------------------// + public VolumeDirectoryHeader (ProdosDisk disk, byte[] buffer, int ptr) + // ---------------------------------------------------------------------------------// + { + super (disk, buffer, ptr); + + storageType = (byte) 0x0F; + } + + // ---------------------------------------------------------------------------------// + @Override + void read () + // ---------------------------------------------------------------------------------// + { + super.read (); + + bitMapPointer = ProdosDisk.readShort (buffer, ptr + 0x23); + totalBlocks = ProdosDisk.readShort (buffer, ptr + 0x25); + } + + // ---------------------------------------------------------------------------------// + @Override + void write () + // ---------------------------------------------------------------------------------// + { + super.write (); + + ProdosDisk.writeShort (buffer, ptr + 0x23, bitMapPointer); + ProdosDisk.writeShort (buffer, ptr + 0x25, totalBlocks); + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + + text.append (UNDERLINE); + text.append ("Volume Directory Header\n"); + text.append (UNDERLINE); + text.append (super.toString ()); + text.append (String.format ("Bitmap pointer ... %d%n", bitMapPointer)); + text.append (String.format ("Total blocks ..... %d%n", totalBlocks)); + text.append (UNDERLINE); + + return text.toString (); + } +} diff --git a/src/com/bytezone/diskbrowser/prodos/write/block-00.bin b/src/com/bytezone/diskbrowser/prodos/write/block-00.bin new file mode 100644 index 0000000..8d5556e Binary files /dev/null and b/src/com/bytezone/diskbrowser/prodos/write/block-00.bin differ diff --git a/src/com/bytezone/diskbrowser/utilities/BlockHeader.java b/src/com/bytezone/diskbrowser/utilities/BlockHeader.java new file mode 100644 index 0000000..2cbdd04 --- /dev/null +++ b/src/com/bytezone/diskbrowser/utilities/BlockHeader.java @@ -0,0 +1,66 @@ +package com.bytezone.diskbrowser.utilities; + +// -----------------------------------------------------------------------------------// +public class BlockHeader +// -----------------------------------------------------------------------------------// +{ + private static final byte[] NuFX = { 0x4E, (byte) 0xF5, 0x46, (byte) 0xD8 }; + private static String[] fileSystems = + { "", "ProDOS/SOS", "DOS 3.3", "DOS 3.2", "Apple II Pascal", "Macintosh HFS", + "Macintosh MFS", "Lisa File System", "Apple CP/M", "", "MS-DOS", "High Sierra", + "ISO 9660", "AppleShare" }; + + int headerCRC; + int attribCount; + int version; + int totalThreads; + int fileSystemID; + int fileSystemInfo; + byte fileSystemSeparator; + int accessFlags; + int fileType; + int extraType; + int storageType; + int fileSystemBlockSize; + DateTime created; + DateTime modified; + DateTime archived; + int optionSize; + int fileNameLength; + String fileName; + + // ---------------------------------------------------------------------------------// + public BlockHeader (byte[] buffer, int ptr) + // ---------------------------------------------------------------------------------// + { + assert Utility.isMagic (buffer, ptr, NuFX); + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + + text.append (String.format ("Header CRC ..... %,d (%<04X)%n", headerCRC)); + text.append (String.format ("Attributes ..... %d%n", attribCount)); + text.append (String.format ("Version ........ %d%n", version)); + text.append (String.format ("Threads ........ %d%n", totalThreads)); + text.append (String.format ("File sys id .... %d (%s)%n", fileSystemID, + fileSystems[fileSystemID])); + text.append (String.format ("Separator ...... %s%n", fileSystemSeparator)); + text.append (String.format ("Access ......... %,d%n", accessFlags)); + text.append (String.format ("File type ...... %,d%n", fileType)); + text.append (String.format ("Aux type ....... %,d%n", extraType)); + text.append (String.format ("Stor type ...... %,d%n", storageType)); + text.append (String.format ("Created ........ %s%n", created.format ())); + text.append (String.format ("Modified ....... %s%n", modified.format ())); + text.append (String.format ("Archived ....... %s%n", archived.format ())); + text.append (String.format ("Option size .... %,d%n", optionSize)); + text.append (String.format ("Filename len ... %,d%n", fileNameLength)); + text.append (String.format ("Filename ....... %s", fileName)); + + return text.toString (); + } +} diff --git a/src/com/bytezone/diskbrowser/utilities/LZW.java b/src/com/bytezone/diskbrowser/utilities/LZW.java index 01a41ec..cd9bc9a 100644 --- a/src/com/bytezone/diskbrowser/utilities/LZW.java +++ b/src/com/bytezone/diskbrowser/utilities/LZW.java @@ -108,6 +108,13 @@ class LZW return outBuffer; } + // ---------------------------------------------------------------------------------// + public int getSize () + // ---------------------------------------------------------------------------------// + { + return chunks.size () * TRACK_LENGTH; + } + // ---------------------------------------------------------------------------------// public byte[] getData () // ---------------------------------------------------------------------------------// diff --git a/src/com/bytezone/diskbrowser/utilities/Header.java b/src/com/bytezone/diskbrowser/utilities/MasterHeader.java similarity index 79% rename from src/com/bytezone/diskbrowser/utilities/Header.java rename to src/com/bytezone/diskbrowser/utilities/MasterHeader.java index 5a1022c..cb73c64 100644 --- a/src/com/bytezone/diskbrowser/utilities/Header.java +++ b/src/com/bytezone/diskbrowser/utilities/MasterHeader.java @@ -1,26 +1,32 @@ package com.bytezone.diskbrowser.utilities; // -----------------------------------------------------------------------------------// -class Header +class MasterHeader // -----------------------------------------------------------------------------------// { - private final int totalRecords; - private final int version; - private final int eof; + private static final byte[] NuFile = + { 0x4E, (byte) 0xF5, 0x46, (byte) 0xE9, 0x6C, (byte) 0xE5 }; + private static final byte[] BIN2 = { 0x0A, 0x47, 0x4C }; + private final int crc; + private final int totalRecords; private final DateTime created; private final DateTime modified; + private final int version; + private final int reserved; + private final int eof; + boolean bin2; // ---------------------------------------------------------------------------------// - public Header (byte[] buffer) throws FileFormatException + public MasterHeader (byte[] buffer) throws FileFormatException // ---------------------------------------------------------------------------------// { int ptr = 0; while (true) { - if (isNuFile (buffer, ptr)) + if (Utility.isMagic (buffer, ptr, NuFile)) break; if (isBin2 (buffer, ptr)) @@ -38,8 +44,11 @@ class Header created = new DateTime (buffer, ptr + 12); modified = new DateTime (buffer, ptr + 20); version = Utility.getWord (buffer, ptr + 28); + reserved = Utility.getWord (buffer, ptr + 30); eof = Utility.getLong (buffer, ptr + 38); + assert reserved == 0; + byte[] crcBuffer = new byte[40]; System.arraycopy (buffer, ptr + 8, crcBuffer, 0, crcBuffer.length); if (crc != Utility.getCRC (crcBuffer, 0)) @@ -56,24 +65,13 @@ class Header return totalRecords; } - // ---------------------------------------------------------------------------------// - private boolean isNuFile (byte[] buffer, int ptr) - // ---------------------------------------------------------------------------------// - { - if (buffer[ptr] == 0x4E && buffer[ptr + 1] == (byte) 0xF5 && buffer[ptr + 2] == 0x46 - && buffer[ptr + 3] == (byte) 0xE9 && buffer[ptr + 4] == 0x6C - && buffer[ptr + 5] == (byte) 0xE5) - return true; - return false; - } - // ---------------------------------------------------------------------------------// private boolean isBin2 (byte[] buffer, int ptr) // ---------------------------------------------------------------------------------// { - if (buffer[ptr] == 0x0A && buffer[ptr + 1] == 0x47 && buffer[ptr + 2] == 0x4C - && buffer[ptr + 18] == (byte) 0x02) + if (Utility.isMagic (buffer, ptr, BIN2) && buffer[ptr + 18] == (byte) 0x02) return true; + return false; } diff --git a/src/com/bytezone/diskbrowser/utilities/NuFX.java b/src/com/bytezone/diskbrowser/utilities/NuFX.java index 9c3a091..cf8da92 100644 --- a/src/com/bytezone/diskbrowser/utilities/NuFX.java +++ b/src/com/bytezone/diskbrowser/utilities/NuFX.java @@ -1,5 +1,6 @@ package com.bytezone.diskbrowser.utilities; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -10,12 +11,13 @@ import java.util.List; public class NuFX // -----------------------------------------------------------------------------------// { - private Header header; + private MasterHeader masterHeader; private final byte[] buffer; private final boolean debug = false; private final List records = new ArrayList<> (); - private final List threads = new ArrayList<> (); + private int totalFiles; + private int totalDisks; // ---------------------------------------------------------------------------------// public NuFX (Path path) throws FileFormatException, IOException @@ -29,16 +31,16 @@ public class NuFX private void readBuffer () // ---------------------------------------------------------------------------------// { - header = new Header (buffer); + masterHeader = new MasterHeader (buffer); int dataPtr = 48; - if (header.bin2) + if (masterHeader.bin2) dataPtr += 128; if (debug) - System.out.printf ("%s%n%n", header); + System.out.printf ("%s%n%n", masterHeader); - for (int rec = 0; rec < header.getTotalRecords (); rec++) + for (int rec = 0; rec < masterHeader.getTotalRecords (); rec++) { Record record = new Record (buffer, dataPtr); records.add (record); @@ -53,33 +55,104 @@ public class NuFX for (int i = 0; i < record.getTotalThreads (); i++) { Thread thread = new Thread (buffer, threadsPtr + i * 16, dataPtr); - threads.add (thread); + record.threads.add (thread); dataPtr += thread.getCompressedEOF (); if (debug) + { System.out.printf ("Thread: %d%n%n%s%n%n", i, thread); + // if (rec == 122 && thread.hasFile ()) + // System.out.println (HexFormatter.format (thread.getData ())); + } } + + if (record.hasFile ()) + ++totalFiles; + if (record.hasDisk ()) + ++totalDisks; } } // ---------------------------------------------------------------------------------// - public byte[] getBuffer () + public byte[] getDiskBuffer () // ---------------------------------------------------------------------------------// { - for (Thread thread : threads) - if (thread.hasDisk ()) - return thread.getData (); + if (totalDisks > 0) + { + for (Record record : records) + for (Thread thread : record.threads) + if (thread.hasDisk ()) + return thread.getData (); + } + else if (totalFiles > 0) + { + listFiles (); + } + return null; } + // ---------------------------------------------------------------------------------// + public byte[] getFileBuffer (String fileName) + // ---------------------------------------------------------------------------------// + { + for (Record record : records) + if (record.hasFile (fileName)) + for (Thread thread : record.threads) + if (thread.hasFile ()) + return thread.getData (); + + return null; + } + + // ---------------------------------------------------------------------------------// + public int getTotalFiles () + // ---------------------------------------------------------------------------------// + { + return totalFiles; + } + + // ---------------------------------------------------------------------------------// + public int getTotalDisks () + // ---------------------------------------------------------------------------------// + { + return totalDisks; + } + + // ---------------------------------------------------------------------------------// + private void listFiles () + // ---------------------------------------------------------------------------------// + { + int count = 0; + for (Record record : records) + { + if (record.hasFile ()) + System.out.printf ("%3d %-35s %,7d %d %,7d%n", count, record.getFileName (), + record.getFileSize (), record.getFileType (), record.getUncompressedSize ()); + count++; + } + } + // ---------------------------------------------------------------------------------// @Override public String toString () // ---------------------------------------------------------------------------------// { - for (Thread thread : threads) - if (thread.hasDisk ()) - return thread.toString (); + for (Record record : records) + for (Thread thread : record.threads) + if (thread.hasDisk ()) + return thread.toString (); + return "no disk"; } + + // ---------------------------------------------------------------------------------// + public static void main (String[] args) throws FileFormatException, IOException + // ---------------------------------------------------------------------------------// + { + File file = new File ("/Users/denismolony/Dropbox/Examples/SHK/Applenet20.shk"); + + NuFX nufx = new NuFX (file.toPath ()); + System.out.println (nufx); + } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/utilities/Record.java b/src/com/bytezone/diskbrowser/utilities/Record.java index 7879e6d..7c5a825 100644 --- a/src/com/bytezone/diskbrowser/utilities/Record.java +++ b/src/com/bytezone/diskbrowser/utilities/Record.java @@ -1,14 +1,26 @@ package com.bytezone.diskbrowser.utilities; +import java.util.ArrayList; +import java.util.List; + +import com.bytezone.diskbrowser.prodos.ProdosConstants; + // -----------------------------------------------------------------------------------// class Record // -----------------------------------------------------------------------------------// { + private static final byte[] NuFX = { 0x4E, (byte) 0xF5, 0x46, (byte) 0xD8 }; private static String[] fileSystems = { "", "ProDOS/SOS", "DOS 3.3", "DOS 3.2", "Apple II Pascal", "Macintosh HFS", "Macintosh MFS", "Lisa File System", "Apple CP/M", "", "MS-DOS", "High Sierra", "ISO 9660", "AppleShare" }; + private static String[] storage = { "", "Seedling", "Sapling", "Tree", "", "Extended", + "", "", "", "", "", "", "", "Subdirectory" }; + + private static String[] accessChars = { "D", "R", "B", "", "", "I", "W", "R" }; + + // private final BlockHeader blockHeader; private final int totThreads; private final int crc; private final char separator; @@ -26,14 +38,19 @@ class Record private final int fileNameLength; private final String fileName; + final List threads = new ArrayList<> (); + // ---------------------------------------------------------------------------------// public Record (byte[] buffer, int dataPtr) throws FileFormatException // ---------------------------------------------------------------------------------// { // check for NuFX - if (!isNuFX (buffer, dataPtr)) + if (!Utility.isMagic (buffer, dataPtr, NuFX)) throw new FileFormatException ("NuFX not found"); + // blockHeader = new BlockHeader (buffer, dataPtr); + // System.out.println (blockHeader); + crc = Utility.getWord (buffer, dataPtr + 4); attributes = Utility.getWord (buffer, dataPtr + 6); version = Utility.getWord (buffer, dataPtr + 8); @@ -73,14 +90,14 @@ class Record } // ---------------------------------------------------------------------------------// - private boolean isNuFX (byte[] buffer, int ptr) - // ---------------------------------------------------------------------------------// - { - if (buffer[ptr] == 0x4E && buffer[ptr + 1] == (byte) 0xF5 && buffer[ptr + 2] == 0x46 - && buffer[ptr + 3] == (byte) 0xD8) - return true; - return false; - } + // private boolean isNuFX (byte[] buffer, int ptr) + // // ---------------------------------------------------------------------------------// + // { + // if (buffer[ptr] == 0x4E && buffer[ptr + 1] == (byte) 0xF5 && buffer[ptr + 2] == 0x46 + // && buffer[ptr + 3] == (byte) 0xD8) + // return true; + // return false; + // } // ---------------------------------------------------------------------------------// int getAttributes () @@ -103,6 +120,79 @@ class Record return totThreads; } + // ---------------------------------------------------------------------------------// + boolean hasDisk () + // ---------------------------------------------------------------------------------// + { + for (Thread thread : threads) + if (thread.hasDisk ()) + return true; + + return false; + } + + // ---------------------------------------------------------------------------------// + boolean hasFile () + // ---------------------------------------------------------------------------------// + { + for (Thread thread : threads) + if (thread.hasFile ()) + return true; + + return false; + } + + // ---------------------------------------------------------------------------------// + boolean hasFile (String fileName) + // ---------------------------------------------------------------------------------// + { + for (Thread thread : threads) + if (thread.hasFile (fileName)) + return true; + + return false; + } + + // ---------------------------------------------------------------------------------// + String getFileName () + // ---------------------------------------------------------------------------------// + { + for (Thread thread : threads) + if (thread.hasFileName ()) + return thread.getFileName (); + + return ""; + } + + // ---------------------------------------------------------------------------------// + int getFileType () + // ---------------------------------------------------------------------------------// + { + return fileType; + } + + // ---------------------------------------------------------------------------------// + int getFileSize () + // ---------------------------------------------------------------------------------// + { + for (Thread thread : threads) + if (thread.hasFile ()) + return thread.getFileSize (); + + return 0; + } + + // ---------------------------------------------------------------------------------// + int getUncompressedSize () + // ---------------------------------------------------------------------------------// + { + for (Thread thread : threads) + if (thread.hasFile ()) + return thread.getUncompressedEOF (); + + return 0; + } + // ---------------------------------------------------------------------------------// @Override public String toString () @@ -110,6 +200,10 @@ class Record { StringBuilder text = new StringBuilder (); + String bits = "00000000" + Integer.toBinaryString (access & 0xFF); + bits = bits.substring (bits.length () - 8); + String decode = Utility.matchFlags (access, accessChars); + text.append (String.format ("Header CRC ..... %,d (%04X)%n", crc, crc)); text.append (String.format ("Attributes ..... %d%n", attributes)); text.append (String.format ("Version ........ %d%n", version)); @@ -117,10 +211,21 @@ class Record text.append (String.format ("File sys id .... %d (%s)%n", fileSystemID, fileSystems[fileSystemID])); text.append (String.format ("Separator ...... %s%n", separator)); - text.append (String.format ("Access ......... %,d%n", access)); - text.append (String.format ("File type ...... %,d%n", fileType)); - text.append (String.format ("Aux type ....... %,d%n", auxType)); - text.append (String.format ("Stor type ...... %,d%n", storType)); + text.append (String.format ("Access ......... %s %s%n", bits, decode)); + if (storType < 16) + { + text.append (String.format ("File type ...... %,d %s%n", fileType, + ProdosConstants.fileTypes[fileType])); + text.append (String.format ("Aux type ....... %,d%n", auxType)); + text.append ( + String.format ("Stor type ...... %,d %s%n", storType, storage[storType])); + } + else + { + text.append (String.format ("Zero ........... %,d%n", fileType)); + text.append (String.format ("Total blocks ... %,d%n", auxType)); + text.append (String.format ("Block size ..... %,d%n", storType)); + } text.append (String.format ("Created ........ %s%n", created.format ())); text.append (String.format ("Modified ....... %s%n", modified.format ())); text.append (String.format ("Archived ....... %s%n", archived.format ())); diff --git a/src/com/bytezone/diskbrowser/utilities/Thread.java b/src/com/bytezone/diskbrowser/utilities/Thread.java index 43977d4..f115bd6 100644 --- a/src/com/bytezone/diskbrowser/utilities/Thread.java +++ b/src/com/bytezone/diskbrowser/utilities/Thread.java @@ -4,21 +4,15 @@ package com.bytezone.diskbrowser.utilities; class Thread // -----------------------------------------------------------------------------------// { - private static String[] threadClassText = { "Message", "Control", "Data", "Filename" }; - private static String[] formatText = - { "Uncompressed", "Huffman squeeze", "LZW/1", "LZW/2", "Unix 12-bit Compress", - "Unix 16-bit Compress" }; - private static String[][] threadKindText = - { { "ASCII text", "predefined EOF", "IIgs icon" }, - { "create directory", "undefined", "undefined" }, - { "data fork", "disk image", "resource fork" }, - { "filename", "undefined", "undefined" } }; - private final ThreadHeader header; private final byte[] data; - private String filename; + private String fileName; private String message; private LZW lzw; + private boolean hasDisk; + private boolean hasFile; + private boolean hasFileName; + private int fileSize; // ---------------------------------------------------------------------------------// public Thread (byte[] buffer, int offset, int dataOffset) @@ -40,23 +34,32 @@ class Thread break; case 2: - if (header.threadKind == 1) + lzw = switch (header.threadFormat) { - if (header.format == 2) - lzw = new LZW1 (data); - else if (header.format == 3) - lzw = new LZW2 (data, header.crc); - else if (header.format == 1) - { - // Huffman Squeeze - System.out.println ("Huffman Squeeze format - not written yet"); - } + case 2 -> new LZW1 (data); + case 3 -> new LZW2 (data, header.threadCrc); + default -> null; // 1 = Huffman Squeeze + }; + + if (header.threadKind == 0) // file + { + hasFile = true; + if (lzw != null) + fileSize = lzw.getSize (); + else + fileSize = header.uncompressedEOF; } + else if (header.threadKind == 1) // disk image + hasDisk = true; + break; case 3: if (header.threadKind == 0) - filename = new String (data, 0, header.uncompressedEOF); + { + hasFileName = true; + fileName = new String (data, 0, header.uncompressedEOF); + } break; default: @@ -64,11 +67,22 @@ class Thread } } + // ---------------------------------------------------------------------------------// + boolean hasFile (String fileName) + // ---------------------------------------------------------------------------------// + { + return header.threadClass == 3 && this.fileName != null + && this.fileName.equals (fileName); + } + // ---------------------------------------------------------------------------------// public byte[] getData () // ---------------------------------------------------------------------------------// { - return hasDisk () ? lzw.getData () : null; + if (header.threadFormat == 0) // uncompressed + return data; + + return lzw.getData (); } // ---------------------------------------------------------------------------------// @@ -78,11 +92,46 @@ class Thread return header.compressedEOF; } + // ---------------------------------------------------------------------------------// + int getUncompressedEOF () + // ---------------------------------------------------------------------------------// + { + return header.uncompressedEOF; + } + // ---------------------------------------------------------------------------------// public boolean hasDisk () // ---------------------------------------------------------------------------------// { - return lzw != null; + return hasDisk; + } + + // ---------------------------------------------------------------------------------// + public boolean hasFile () + // ---------------------------------------------------------------------------------// + { + return hasFile; + } + + // ---------------------------------------------------------------------------------// + public boolean hasFileName () + // ---------------------------------------------------------------------------------// + { + return hasFileName; + } + + // ---------------------------------------------------------------------------------// + String getFileName () + // ---------------------------------------------------------------------------------// + { + return fileName; + } + + // ---------------------------------------------------------------------------------// + int getFileSize () + // ---------------------------------------------------------------------------------// + { + return fileSize; } // ---------------------------------------------------------------------------------// @@ -92,8 +141,8 @@ class Thread { StringBuilder text = new StringBuilder (header.toString ()); - if (filename != null) - text.append ("\n filename .......... " + filename); + if (fileName != null) + text.append ("\n filename .......... " + fileName); else if (message != null) text.append ("\n message ........... " + message); else if (lzw != null) @@ -105,43 +154,4 @@ class Thread return text.toString (); } - // ---------------------------------------------------------------------------------// - class ThreadHeader - // ---------------------------------------------------------------------------------// - { - private final int threadClass; - private final int format; - private final int threadKind; - private final int crc; - private final int uncompressedEOF; - private final int compressedEOF; - - public ThreadHeader (byte[] buffer, int offset) - { - threadClass = Utility.getWord (buffer, offset); - format = Utility.getWord (buffer, offset + 2); - threadKind = Utility.getWord (buffer, offset + 4); - crc = Utility.getWord (buffer, offset + 6); - uncompressedEOF = Utility.getLong (buffer, offset + 8); - compressedEOF = Utility.getLong (buffer, offset + 12); - } - - @Override - public String toString () - { - StringBuilder text = new StringBuilder (); - - text.append (String.format (" threadClass ....... %d %s%n", threadClass, - threadClassText[threadClass])); - text.append ( - String.format (" format ............ %d %s%n", format, formatText[format])); - text.append (String.format (" kind .............. %d %s%n", threadKind, - threadKindText[threadClass][threadKind])); - text.append (String.format (" crc ............... %,d%n", crc)); - text.append (String.format (" uncompressedEOF ... %,d%n", uncompressedEOF)); - text.append (String.format (" compressedEOF ..... %,d (%08X)", compressedEOF, - compressedEOF)); - return text.toString (); - } - } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/utilities/ThreadHeader.java b/src/com/bytezone/diskbrowser/utilities/ThreadHeader.java new file mode 100644 index 0000000..095746d --- /dev/null +++ b/src/com/bytezone/diskbrowser/utilities/ThreadHeader.java @@ -0,0 +1,55 @@ +package com.bytezone.diskbrowser.utilities; + +// -----------------------------------------------------------------------------------// +class ThreadHeader +// -----------------------------------------------------------------------------------// +{ + private static String[] threadClassText = { "Message", "Control", "Data", "Filename" }; + private static String[] formatText = + { "Uncompressed", "Huffman squeeze", "LZW/1", "LZW/2", "Unix 12-bit Compress", + "Unix 16-bit Compress" }; + private static String[][] threadKindText = + { { "ASCII text", "predefined EOF", "IIgs icon" }, + { "create directory", "undefined", "undefined" }, + { "data fork", "disk image", "resource fork" }, + { "filename", "undefined", "undefined" } }; + + final int threadClass; + final int threadFormat; + final int threadKind; + final int threadCrc; + final int uncompressedEOF; + final int compressedEOF; + + // ---------------------------------------------------------------------------------// + public ThreadHeader (byte[] buffer, int offset) + // ---------------------------------------------------------------------------------// + { + threadClass = Utility.getWord (buffer, offset); + threadFormat = Utility.getWord (buffer, offset + 2); + threadKind = Utility.getWord (buffer, offset + 4); + threadCrc = Utility.getWord (buffer, offset + 6); + uncompressedEOF = Utility.getLong (buffer, offset + 8); + compressedEOF = Utility.getLong (buffer, offset + 12); + } + + // ---------------------------------------------------------------------------------// + @Override + public String toString () + // ---------------------------------------------------------------------------------// + { + StringBuilder text = new StringBuilder (); + + text.append (String.format (" threadClass ....... %d %s%n", threadClass, + threadClassText[threadClass])); + text.append ( + String.format (" format ............ %d %s%n", threadFormat, formatText[threadFormat])); + text.append (String.format (" kind .............. %d %s%n", threadKind, + threadKindText[threadClass][threadKind])); + text.append (String.format (" crc ............... %,d%n", threadCrc)); + text.append (String.format (" uncompressedEOF ... %,d%n", uncompressedEOF)); + text.append (String.format (" compressedEOF ..... %,d (%08X)", compressedEOF, + compressedEOF)); + return text.toString (); + } +} diff --git a/src/com/bytezone/diskbrowser/utilities/Utility.java b/src/com/bytezone/diskbrowser/utilities/Utility.java index b8fbe0b..ca60dec 100644 --- a/src/com/bytezone/diskbrowser/utilities/Utility.java +++ b/src/com/bytezone/diskbrowser/utilities/Utility.java @@ -34,7 +34,7 @@ public class Utility public static final byte ASCII_CARET = 0x5E; public static final List suffixes = Arrays.asList ("po", "dsk", "do", "hdv", - "2mg", "v2d", "d13", "sdk", "woz", "img", "dimg"); + "2mg", "v2d", "d13", "sdk", "shk", "woz", "img", "dimg"); // ---------------------------------------------------------------------------------// public static boolean test (Graphics2D g) @@ -181,6 +181,25 @@ public class Utility return val; } + // ---------------------------------------------------------------------------------// + public static String matchFlags (int flag, String[] chars) + // ---------------------------------------------------------------------------------// + { + int weight = (int) Math.pow (2, chars.length - 1); + StringBuilder text = new StringBuilder (); + + for (int i = 0; i < chars.length; i++) + { + if ((flag & weight) != 0) + text.append (chars[i]); + else + text.append ("."); + weight >>>= 1; + } + + return text.toString (); + } + // ---------------------------------------------------------------------------------// public static double getSANEDouble (byte[] buffer, int offset) // ---------------------------------------------------------------------------------// @@ -341,6 +360,17 @@ public class Utility return isDigit (value) || value == ASCII_DOT; } + // ---------------------------------------------------------------------------------// + static boolean isMagic (byte[] buffer, int ptr, byte[] magic) + // ---------------------------------------------------------------------------------// + { + for (int i = 0; i < magic.length; i++) + if (buffer[ptr + i] != magic[i]) + return false; + + return true; + } + // ---------------------------------------------------------------------------------// public static long getChecksumValue (File file) // ---------------------------------------------------------------------------------// @@ -366,10 +396,10 @@ public class Utility } // ---------------------------------------------------------------------------------// - protected static int getCRC (final byte[] buffer, int base) + protected static int getCRC (final byte[] buffer, int initialValue) // ---------------------------------------------------------------------------------// { - int crc = base; + int crc = initialValue; for (int j = 0; j < buffer.length; j++) { crc = ((crc >>> 8) | (crc << 8)) & 0xFFFF; @@ -380,6 +410,7 @@ public class Utility } crc &= 0xFFFF; + return crc; }