mirror of
https://github.com/dmolony/DiskBrowser.git
synced 2024-06-13 21:29:31 +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)
|
// if (disk.getDisk ().getInterleave () > 0)
|
||||||
// {
|
// {
|
||||||
// System.out.println (disk);
|
// System.out.println (disk);
|
||||||
|
@ -146,16 +146,19 @@ public class DiskFactory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suffix.equals ("sdk"))
|
if ("sdk".equals (suffix)) // shrinkit disk archive
|
||||||
{
|
{
|
||||||
if (debug)
|
if (debug)
|
||||||
System.out.println (" ** sdk **");
|
System.out.println (" ** sdk **");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NuFX nuFX = new NuFX (file.toPath ());
|
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);
|
FileOutputStream fos = new FileOutputStream (tmp);
|
||||||
fos.write (nuFX.getBuffer ());
|
fos.write (nuFX.getDiskBuffer ());
|
||||||
fos.close ();
|
fos.close ();
|
||||||
tmp.deleteOnExit ();
|
tmp.deleteOnExit ();
|
||||||
file = tmp;
|
file = tmp;
|
||||||
|
@ -172,6 +175,30 @@ public class DiskFactory
|
||||||
return null;
|
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 disk = null;
|
||||||
FormattedDisk disk2 = null;
|
FormattedDisk disk2 = null;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.disk.AppleDiskAddress;
|
||||||
import com.bytezone.diskbrowser.disk.Disk;
|
import com.bytezone.diskbrowser.disk.Disk;
|
||||||
import com.bytezone.diskbrowser.disk.DiskAddress;
|
import com.bytezone.diskbrowser.disk.DiskAddress;
|
||||||
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
||||||
|
@ -167,8 +168,7 @@ class DiskLayoutSelection implements Iterable<DiskAddress>
|
||||||
highlights.clear ();
|
highlights.clear ();
|
||||||
if (list != null)
|
if (list != null)
|
||||||
for (DiskAddress da : list)
|
for (DiskAddress da : list)
|
||||||
if (da != null)
|
if (da != null && (da.getBlockNo () > 0 || ((AppleDiskAddress) da).zeroFlag ()))
|
||||||
// && (da.getBlockNo () > 0 || ((AppleDiskAddress) da).zeroFlag ()))
|
|
||||||
highlights.add (da);
|
highlights.add (da);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ class SaveSectorsAction extends DefaultAction implements SectorSelectionListener
|
||||||
JFileChooser fileChooser = new JFileChooser ();
|
JFileChooser fileChooser = new JFileChooser ();
|
||||||
fileChooser.setDialogTitle ("Save sectors");
|
fileChooser.setDialogTitle ("Save sectors");
|
||||||
fileChooser.setSelectedFile (new File ("saved-" + buffer.length + ".bin"));
|
fileChooser.setSelectedFile (new File ("saved-" + buffer.length + ".bin"));
|
||||||
|
|
||||||
if (fileChooser.showSaveDialog (null) == JFileChooser.APPROVE_OPTION)
|
if (fileChooser.showSaveDialog (null) == JFileChooser.APPROVE_OPTION)
|
||||||
{
|
{
|
||||||
File file = fileChooser.getSelectedFile ();
|
File file = fileChooser.getSelectedFile ();
|
||||||
|
|
|
@ -32,6 +32,7 @@ public interface ProdosConstants
|
||||||
static int FILE_TYPE_FONT = 0xC8;
|
static int FILE_TYPE_FONT = 0xC8;
|
||||||
static int FILE_TYPE_FINDER = 0xC9;
|
static int FILE_TYPE_FINDER = 0xC9;
|
||||||
static int FILE_TYPE_ICN = 0xCA;
|
static int FILE_TYPE_ICN = 0xCA;
|
||||||
|
static int FILE_TYPE_LBR = 0xE0;
|
||||||
static int FILE_TYPE_APPLETALK = 0xE2;
|
static int FILE_TYPE_APPLETALK = 0xE2;
|
||||||
static int FILE_TYPE_PASCAL_VOLUME = 0xEF;
|
static int FILE_TYPE_PASCAL_VOLUME = 0xEF;
|
||||||
static int FILE_TYPE_OVL = 0xF1;
|
static int FILE_TYPE_OVL = 0xF1;
|
||||||
|
@ -87,8 +88,8 @@ public interface ProdosConstants
|
||||||
"CMD", "OVL", "UD2", "UD3", "UD4", "BAT", "UD6", "UD7", //
|
"CMD", "OVL", "UD2", "UD3", "UD4", "BAT", "UD6", "UD7", //
|
||||||
"PRG", "P16", "INT", "IVR", "BAS", "VAR", "REL", "SYS" };
|
"PRG", "P16", "INT", "IVR", "BAS", "VAR", "REL", "SYS" };
|
||||||
|
|
||||||
static int ENTRY_SIZE = 39;
|
static int ENTRY_SIZE = 0x27;
|
||||||
static int ENTRIES_PER_BLOCK = 13;
|
static int ENTRIES_PER_BLOCK = 0x0D;
|
||||||
static int BLOCK_ENTRY_SIZE = ENTRY_SIZE * ENTRIES_PER_BLOCK;
|
static int BLOCK_ENTRY_SIZE = ENTRY_SIZE * ENTRIES_PER_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ class ProdosDirectory extends AbstractFile implements ProdosConstants
|
||||||
{
|
{
|
||||||
StringBuffer text = new StringBuffer ();
|
StringBuffer text = new StringBuffer ();
|
||||||
text.append ("Disk : " + parentFD.getDisplayPath () + newLine2);
|
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;
|
int storageType = (buffer[i] & 0xF0) >> 4;
|
||||||
if (storageType == 0)
|
if (storageType == 0)
|
||||||
|
@ -56,20 +56,21 @@ class ProdosDirectory extends AbstractFile implements ProdosConstants
|
||||||
|
|
||||||
switch (storageType)
|
switch (storageType)
|
||||||
{
|
{
|
||||||
case ProdosConstants.VOLUME_HEADER:
|
case VOLUME_HEADER:
|
||||||
case ProdosConstants.SUBDIRECTORY_HEADER:
|
case SUBDIRECTORY_HEADER:
|
||||||
text.append ("/" + filename + newLine2);
|
String root = storageType == VOLUME_HEADER ? "/" : "";
|
||||||
|
text.append (root + filename + newLine2);
|
||||||
text.append (" NAME TYPE BLOCKS "
|
text.append (" NAME TYPE BLOCKS "
|
||||||
+ "MODIFIED CREATED ENDFILE SUBTYPE" + newLine2);
|
+ "MODIFIED CREATED ENDFILE SUBTYPE" + newLine2);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ProdosConstants.FREE:
|
case FREE:
|
||||||
case ProdosConstants.SEEDLING:
|
case SEEDLING:
|
||||||
case ProdosConstants.SAPLING:
|
case SAPLING:
|
||||||
case ProdosConstants.TREE:
|
case TREE:
|
||||||
case ProdosConstants.PASCAL_ON_PROFILE:
|
case PASCAL_ON_PROFILE:
|
||||||
case ProdosConstants.GSOS_EXTENDED_FILE:
|
case GSOS_EXTENDED_FILE:
|
||||||
case ProdosConstants.SUBDIRECTORY:
|
case SUBDIRECTORY:
|
||||||
int type = buffer[i + 16] & 0xFF;
|
int type = buffer[i + 16] & 0xFF;
|
||||||
int blocks = Utility.intValue (buffer[i + 19], buffer[i + 20]);
|
int blocks = Utility.intValue (buffer[i + 19], buffer[i + 20]);
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ public class ProdosDisk extends AbstractFormattedDisk
|
||||||
sectorTypes[block] = currentSectorType;
|
sectorTypes[block] = currentSectorType;
|
||||||
for (int i = 0; i < volumeDirectoryHeader.totalBitMapBlocks; i++)
|
for (int i = 0; i < volumeDirectoryHeader.totalBitMapBlocks; i++)
|
||||||
sectorTypes[volumeDirectoryHeader.bitMapBlock + i] = volumeMapSector;
|
sectorTypes[volumeDirectoryHeader.bitMapBlock + i] = volumeMapSector;
|
||||||
parentNode.setUserObject (volumeDirectoryHeader); // populate the empty volume node
|
parentNode.setUserObject (volumeDirectoryHeader); // populate the empty volume node
|
||||||
localHeader = volumeDirectoryHeader;
|
localHeader = volumeDirectoryHeader;
|
||||||
break;
|
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;
|
return outBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------//
|
||||||
|
public int getSize ()
|
||||||
|
// ---------------------------------------------------------------------------------//
|
||||||
|
{
|
||||||
|
return chunks.size () * TRACK_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
public byte[] getData ()
|
public byte[] getData ()
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
|
|
|
@ -1,26 +1,32 @@
|
||||||
package com.bytezone.diskbrowser.utilities;
|
package com.bytezone.diskbrowser.utilities;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------//
|
// -----------------------------------------------------------------------------------//
|
||||||
class Header
|
class MasterHeader
|
||||||
// -----------------------------------------------------------------------------------//
|
// -----------------------------------------------------------------------------------//
|
||||||
{
|
{
|
||||||
private final int totalRecords;
|
private static final byte[] NuFile =
|
||||||
private final int version;
|
{ 0x4E, (byte) 0xF5, 0x46, (byte) 0xE9, 0x6C, (byte) 0xE5 };
|
||||||
private final int eof;
|
private static final byte[] BIN2 = { 0x0A, 0x47, 0x4C };
|
||||||
|
|
||||||
private final int crc;
|
private final int crc;
|
||||||
|
private final int totalRecords;
|
||||||
private final DateTime created;
|
private final DateTime created;
|
||||||
private final DateTime modified;
|
private final DateTime modified;
|
||||||
|
private final int version;
|
||||||
|
private final int reserved;
|
||||||
|
private final int eof;
|
||||||
|
|
||||||
boolean bin2;
|
boolean bin2;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
public Header (byte[] buffer) throws FileFormatException
|
public MasterHeader (byte[] buffer) throws FileFormatException
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
{
|
{
|
||||||
int ptr = 0;
|
int ptr = 0;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (isNuFile (buffer, ptr))
|
if (Utility.isMagic (buffer, ptr, NuFile))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (isBin2 (buffer, ptr))
|
if (isBin2 (buffer, ptr))
|
||||||
|
@ -38,8 +44,11 @@ class Header
|
||||||
created = new DateTime (buffer, ptr + 12);
|
created = new DateTime (buffer, ptr + 12);
|
||||||
modified = new DateTime (buffer, ptr + 20);
|
modified = new DateTime (buffer, ptr + 20);
|
||||||
version = Utility.getWord (buffer, ptr + 28);
|
version = Utility.getWord (buffer, ptr + 28);
|
||||||
|
reserved = Utility.getWord (buffer, ptr + 30);
|
||||||
eof = Utility.getLong (buffer, ptr + 38);
|
eof = Utility.getLong (buffer, ptr + 38);
|
||||||
|
|
||||||
|
assert reserved == 0;
|
||||||
|
|
||||||
byte[] crcBuffer = new byte[40];
|
byte[] crcBuffer = new byte[40];
|
||||||
System.arraycopy (buffer, ptr + 8, crcBuffer, 0, crcBuffer.length);
|
System.arraycopy (buffer, ptr + 8, crcBuffer, 0, crcBuffer.length);
|
||||||
if (crc != Utility.getCRC (crcBuffer, 0))
|
if (crc != Utility.getCRC (crcBuffer, 0))
|
||||||
|
@ -56,24 +65,13 @@ class Header
|
||||||
return totalRecords;
|
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)
|
private boolean isBin2 (byte[] buffer, int ptr)
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
{
|
{
|
||||||
if (buffer[ptr] == 0x0A && buffer[ptr + 1] == 0x47 && buffer[ptr + 2] == 0x4C
|
if (Utility.isMagic (buffer, ptr, BIN2) && buffer[ptr + 18] == (byte) 0x02)
|
||||||
&& buffer[ptr + 18] == (byte) 0x02)
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.bytezone.diskbrowser.utilities;
|
package com.bytezone.diskbrowser.utilities;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -10,12 +11,13 @@ import java.util.List;
|
||||||
public class NuFX
|
public class NuFX
|
||||||
// -----------------------------------------------------------------------------------//
|
// -----------------------------------------------------------------------------------//
|
||||||
{
|
{
|
||||||
private Header header;
|
private MasterHeader masterHeader;
|
||||||
private final byte[] buffer;
|
private final byte[] buffer;
|
||||||
private final boolean debug = false;
|
private final boolean debug = false;
|
||||||
|
|
||||||
private final List<Record> records = new ArrayList<> ();
|
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
|
public NuFX (Path path) throws FileFormatException, IOException
|
||||||
|
@ -29,16 +31,16 @@ public class NuFX
|
||||||
private void readBuffer ()
|
private void readBuffer ()
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
{
|
{
|
||||||
header = new Header (buffer);
|
masterHeader = new MasterHeader (buffer);
|
||||||
|
|
||||||
int dataPtr = 48;
|
int dataPtr = 48;
|
||||||
if (header.bin2)
|
if (masterHeader.bin2)
|
||||||
dataPtr += 128;
|
dataPtr += 128;
|
||||||
|
|
||||||
if (debug)
|
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);
|
Record record = new Record (buffer, dataPtr);
|
||||||
records.add (record);
|
records.add (record);
|
||||||
|
@ -53,33 +55,104 @@ public class NuFX
|
||||||
for (int i = 0; i < record.getTotalThreads (); i++)
|
for (int i = 0; i < record.getTotalThreads (); i++)
|
||||||
{
|
{
|
||||||
Thread thread = new Thread (buffer, threadsPtr + i * 16, dataPtr);
|
Thread thread = new Thread (buffer, threadsPtr + i * 16, dataPtr);
|
||||||
threads.add (thread);
|
record.threads.add (thread);
|
||||||
dataPtr += thread.getCompressedEOF ();
|
dataPtr += thread.getCompressedEOF ();
|
||||||
|
|
||||||
if (debug)
|
if (debug)
|
||||||
|
{
|
||||||
System.out.printf ("Thread: %d%n%n%s%n%n", i, thread);
|
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 (totalDisks > 0)
|
||||||
if (thread.hasDisk ())
|
{
|
||||||
return thread.getData ();
|
for (Record record : records)
|
||||||
|
for (Thread thread : record.threads)
|
||||||
|
if (thread.hasDisk ())
|
||||||
|
return thread.getData ();
|
||||||
|
}
|
||||||
|
else if (totalFiles > 0)
|
||||||
|
{
|
||||||
|
listFiles ();
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
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
|
@Override
|
||||||
public String toString ()
|
public String toString ()
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
{
|
{
|
||||||
for (Thread thread : threads)
|
for (Record record : records)
|
||||||
if (thread.hasDisk ())
|
for (Thread thread : record.threads)
|
||||||
return thread.toString ();
|
if (thread.hasDisk ())
|
||||||
|
return thread.toString ();
|
||||||
|
|
||||||
return "no disk";
|
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;
|
package com.bytezone.diskbrowser.utilities;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.prodos.ProdosConstants;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------//
|
// -----------------------------------------------------------------------------------//
|
||||||
class Record
|
class Record
|
||||||
// -----------------------------------------------------------------------------------//
|
// -----------------------------------------------------------------------------------//
|
||||||
{
|
{
|
||||||
|
private static final byte[] NuFX = { 0x4E, (byte) 0xF5, 0x46, (byte) 0xD8 };
|
||||||
private static String[] fileSystems =
|
private static String[] fileSystems =
|
||||||
{ "", "ProDOS/SOS", "DOS 3.3", "DOS 3.2", "Apple II Pascal", "Macintosh HFS",
|
{ "", "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",
|
"Macintosh MFS", "Lisa File System", "Apple CP/M", "", "MS-DOS", "High Sierra",
|
||||||
"ISO 9660", "AppleShare" };
|
"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 totThreads;
|
||||||
private final int crc;
|
private final int crc;
|
||||||
private final char separator;
|
private final char separator;
|
||||||
|
@ -26,14 +38,19 @@ class Record
|
||||||
private final int fileNameLength;
|
private final int fileNameLength;
|
||||||
private final String fileName;
|
private final String fileName;
|
||||||
|
|
||||||
|
final List<Thread> threads = new ArrayList<> ();
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
public Record (byte[] buffer, int dataPtr) throws FileFormatException
|
public Record (byte[] buffer, int dataPtr) throws FileFormatException
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
{
|
{
|
||||||
// check for NuFX
|
// check for NuFX
|
||||||
if (!isNuFX (buffer, dataPtr))
|
if (!Utility.isMagic (buffer, dataPtr, NuFX))
|
||||||
throw new FileFormatException ("NuFX not found");
|
throw new FileFormatException ("NuFX not found");
|
||||||
|
|
||||||
|
// blockHeader = new BlockHeader (buffer, dataPtr);
|
||||||
|
// System.out.println (blockHeader);
|
||||||
|
|
||||||
crc = Utility.getWord (buffer, dataPtr + 4);
|
crc = Utility.getWord (buffer, dataPtr + 4);
|
||||||
attributes = Utility.getWord (buffer, dataPtr + 6);
|
attributes = Utility.getWord (buffer, dataPtr + 6);
|
||||||
version = Utility.getWord (buffer, dataPtr + 8);
|
version = Utility.getWord (buffer, dataPtr + 8);
|
||||||
|
@ -73,14 +90,14 @@ class Record
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
private boolean isNuFX (byte[] buffer, int ptr)
|
// private boolean isNuFX (byte[] buffer, int ptr)
|
||||||
// ---------------------------------------------------------------------------------//
|
// // ---------------------------------------------------------------------------------//
|
||||||
{
|
// {
|
||||||
if (buffer[ptr] == 0x4E && buffer[ptr + 1] == (byte) 0xF5 && buffer[ptr + 2] == 0x46
|
// if (buffer[ptr] == 0x4E && buffer[ptr + 1] == (byte) 0xF5 && buffer[ptr + 2] == 0x46
|
||||||
&& buffer[ptr + 3] == (byte) 0xD8)
|
// && buffer[ptr + 3] == (byte) 0xD8)
|
||||||
return true;
|
// return true;
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
int getAttributes ()
|
int getAttributes ()
|
||||||
|
@ -103,6 +120,79 @@ class Record
|
||||||
return totThreads;
|
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
|
@Override
|
||||||
public String toString ()
|
public String toString ()
|
||||||
|
@ -110,6 +200,10 @@ class Record
|
||||||
{
|
{
|
||||||
StringBuilder text = new StringBuilder ();
|
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 ("Header CRC ..... %,d (%04X)%n", crc, crc));
|
||||||
text.append (String.format ("Attributes ..... %d%n", attributes));
|
text.append (String.format ("Attributes ..... %d%n", attributes));
|
||||||
text.append (String.format ("Version ........ %d%n", version));
|
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,
|
text.append (String.format ("File sys id .... %d (%s)%n", fileSystemID,
|
||||||
fileSystems[fileSystemID]));
|
fileSystems[fileSystemID]));
|
||||||
text.append (String.format ("Separator ...... %s%n", separator));
|
text.append (String.format ("Separator ...... %s%n", separator));
|
||||||
text.append (String.format ("Access ......... %,d%n", access));
|
text.append (String.format ("Access ......... %s %s%n", bits, decode));
|
||||||
text.append (String.format ("File type ...... %,d%n", fileType));
|
if (storType < 16)
|
||||||
text.append (String.format ("Aux type ....... %,d%n", auxType));
|
{
|
||||||
text.append (String.format ("Stor type ...... %,d%n", storType));
|
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 ("Created ........ %s%n", created.format ()));
|
||||||
text.append (String.format ("Modified ....... %s%n", modified.format ()));
|
text.append (String.format ("Modified ....... %s%n", modified.format ()));
|
||||||
text.append (String.format ("Archived ....... %s%n", archived.format ()));
|
text.append (String.format ("Archived ....... %s%n", archived.format ()));
|
||||||
|
|
|
@ -4,21 +4,15 @@ package com.bytezone.diskbrowser.utilities;
|
||||||
class Thread
|
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 ThreadHeader header;
|
||||||
private final byte[] data;
|
private final byte[] data;
|
||||||
private String filename;
|
private String fileName;
|
||||||
private String message;
|
private String message;
|
||||||
private LZW lzw;
|
private LZW lzw;
|
||||||
|
private boolean hasDisk;
|
||||||
|
private boolean hasFile;
|
||||||
|
private boolean hasFileName;
|
||||||
|
private int fileSize;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
public Thread (byte[] buffer, int offset, int dataOffset)
|
public Thread (byte[] buffer, int offset, int dataOffset)
|
||||||
|
@ -40,23 +34,32 @@ class Thread
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
if (header.threadKind == 1)
|
lzw = switch (header.threadFormat)
|
||||||
{
|
{
|
||||||
if (header.format == 2)
|
case 2 -> new LZW1 (data);
|
||||||
lzw = new LZW1 (data);
|
case 3 -> new LZW2 (data, header.threadCrc);
|
||||||
else if (header.format == 3)
|
default -> null; // 1 = Huffman Squeeze
|
||||||
lzw = new LZW2 (data, header.crc);
|
};
|
||||||
else if (header.format == 1)
|
|
||||||
{
|
if (header.threadKind == 0) // file
|
||||||
// Huffman Squeeze
|
{
|
||||||
System.out.println ("Huffman Squeeze format - not written yet");
|
hasFile = true;
|
||||||
}
|
if (lzw != null)
|
||||||
|
fileSize = lzw.getSize ();
|
||||||
|
else
|
||||||
|
fileSize = header.uncompressedEOF;
|
||||||
}
|
}
|
||||||
|
else if (header.threadKind == 1) // disk image
|
||||||
|
hasDisk = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
if (header.threadKind == 0)
|
if (header.threadKind == 0)
|
||||||
filename = new String (data, 0, header.uncompressedEOF);
|
{
|
||||||
|
hasFileName = true;
|
||||||
|
fileName = new String (data, 0, header.uncompressedEOF);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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 ()
|
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;
|
return header.compressedEOF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------//
|
||||||
|
int getUncompressedEOF ()
|
||||||
|
// ---------------------------------------------------------------------------------//
|
||||||
|
{
|
||||||
|
return header.uncompressedEOF;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
public boolean hasDisk ()
|
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 ());
|
StringBuilder text = new StringBuilder (header.toString ());
|
||||||
|
|
||||||
if (filename != null)
|
if (fileName != null)
|
||||||
text.append ("\n filename .......... " + filename);
|
text.append ("\n filename .......... " + fileName);
|
||||||
else if (message != null)
|
else if (message != null)
|
||||||
text.append ("\n message ........... " + message);
|
text.append ("\n message ........... " + message);
|
||||||
else if (lzw != null)
|
else if (lzw != null)
|
||||||
|
@ -105,43 +154,4 @@ class Thread
|
||||||
return text.toString ();
|
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 byte ASCII_CARET = 0x5E;
|
||||||
|
|
||||||
public static final List<String> suffixes = Arrays.asList ("po", "dsk", "do", "hdv",
|
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)
|
public static boolean test (Graphics2D g)
|
||||||
|
@ -181,6 +181,25 @@ public class Utility
|
||||||
return val;
|
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)
|
public static double getSANEDouble (byte[] buffer, int offset)
|
||||||
// ---------------------------------------------------------------------------------//
|
// ---------------------------------------------------------------------------------//
|
||||||
|
@ -341,6 +360,17 @@ public class Utility
|
||||||
return isDigit (value) || value == ASCII_DOT;
|
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)
|
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++)
|
for (int j = 0; j < buffer.length; j++)
|
||||||
{
|
{
|
||||||
crc = ((crc >>> 8) | (crc << 8)) & 0xFFFF;
|
crc = ((crc >>> 8) | (crc << 8)) & 0xFFFF;
|
||||||
|
@ -380,6 +410,7 @@ public class Utility
|
||||||
}
|
}
|
||||||
|
|
||||||
crc &= 0xFFFF;
|
crc &= 0xFFFF;
|
||||||
|
|
||||||
return crc;
|
return crc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user