new package for writing prodos disks

This commit is contained in:
Denis Molony 2021-04-15 17:27:20 +10:00
parent 2168025e80
commit 645c6a4a36
22 changed files with 1746 additions and 135 deletions

View File

@ -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;

View File

@ -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<DiskAddress>
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);
}

View File

@ -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 ();

View File

@ -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;
}

View File

@ -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]);

View File

@ -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;

View File

@ -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 ();
}
}

View File

@ -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 ();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}

View File

@ -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<Integer, SubdirectoryHeader> 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);
}
}

View File

@ -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 ();
}
}

View File

@ -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 ();
}
}

Binary file not shown.

View File

@ -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 ();
}
}

View File

@ -108,6 +108,13 @@ class LZW
return outBuffer;
}
// ---------------------------------------------------------------------------------//
public int getSize ()
// ---------------------------------------------------------------------------------//
{
return chunks.size () * TRACK_LENGTH;
}
// ---------------------------------------------------------------------------------//
public byte[] getData ()
// ---------------------------------------------------------------------------------//

View File

@ -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;
}

View File

@ -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<Record> records = new ArrayList<> ();
private final List<Thread> 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);
}
}

View File

@ -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<Thread> 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 ()));

View File

@ -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 ();
}
}
}

View File

@ -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 ();
}
}

View File

@ -34,7 +34,7 @@ public class Utility
public static final byte ASCII_CARET = 0x5E;
public static final List<String> 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;
}