mirror of
https://github.com/dmolony/DiskBrowser.git
synced 2024-11-28 20:50:13 +00:00
new package for writing prodos disks
This commit is contained in:
parent
2168025e80
commit
645c6a4a36
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 ();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 ();
|
||||
}
|
||||
}
|
324
src/com/bytezone/diskbrowser/prodos/write/FileEntry.java
Normal file
324
src/com/bytezone/diskbrowser/prodos/write/FileEntry.java
Normal 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 ();
|
||||
}
|
||||
}
|
67
src/com/bytezone/diskbrowser/prodos/write/IndexBlock.java
Normal file
67
src/com/bytezone/diskbrowser/prodos/write/IndexBlock.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
551
src/com/bytezone/diskbrowser/prodos/write/ProdosDisk.java
Normal file
551
src/com/bytezone/diskbrowser/prodos/write/ProdosDisk.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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 ();
|
||||
}
|
||||
}
|
@ -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 ();
|
||||
}
|
||||
}
|
BIN
src/com/bytezone/diskbrowser/prodos/write/block-00.bin
Normal file
BIN
src/com/bytezone/diskbrowser/prodos/write/block-00.bin
Normal file
Binary file not shown.
66
src/com/bytezone/diskbrowser/utilities/BlockHeader.java
Normal file
66
src/com/bytezone/diskbrowser/utilities/BlockHeader.java
Normal 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 ();
|
||||
}
|
||||
}
|
@ -108,6 +108,13 @@ class LZW
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------//
|
||||
public int getSize ()
|
||||
// ---------------------------------------------------------------------------------//
|
||||
{
|
||||
return chunks.size () * TRACK_LENGTH;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------//
|
||||
public byte[] getData ()
|
||||
// ---------------------------------------------------------------------------------//
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 ()));
|
||||
|
@ -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 ();
|
||||
}
|
||||
}
|
||||
}
|
55
src/com/bytezone/diskbrowser/utilities/ThreadHeader.java
Normal file
55
src/com/bytezone/diskbrowser/utilities/ThreadHeader.java
Normal 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 ();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user