dmolony-DiskBrowser/src/com/bytezone/diskbrowser/nufx/NuFX.java

414 lines
13 KiB
Java
Raw Permalink Normal View History

2021-05-21 02:57:03 +00:00
package com.bytezone.diskbrowser.nufx;
2015-06-01 09:35:51 +00:00
2021-05-08 09:56:57 +00:00
import static com.bytezone.diskbrowser.prodos.ProdosConstants.BLOCK_SIZE;
2015-06-01 09:35:51 +00:00
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
2021-04-17 02:14:06 +00:00
import java.time.LocalDateTime;
2015-06-01 09:35:51 +00:00
import java.util.ArrayList;
import java.util.List;
2021-04-17 02:14:06 +00:00
import com.bytezone.diskbrowser.prodos.write.DiskFullException;
2021-05-04 00:02:20 +00:00
import com.bytezone.diskbrowser.prodos.write.FileAlreadyExistsException;
2021-04-19 04:10:01 +00:00
import com.bytezone.diskbrowser.prodos.write.FileEntry;
2021-04-16 11:08:59 +00:00
import com.bytezone.diskbrowser.prodos.write.ProdosDisk;
2021-04-25 02:08:09 +00:00
import com.bytezone.diskbrowser.prodos.write.VolumeCatalogFullException;
2021-05-21 02:57:03 +00:00
import com.bytezone.diskbrowser.utilities.FileFormatException;
2021-04-16 11:08:59 +00:00
2020-02-07 23:26:38 +00:00
// -----------------------------------------------------------------------------------//
2015-06-01 09:35:51 +00:00
public class NuFX
2020-02-07 23:26:38 +00:00
// -----------------------------------------------------------------------------------//
2015-06-01 09:35:51 +00:00
{
2021-05-02 04:45:26 +00:00
private static final String UNDERLINE =
2023-03-15 23:00:31 +00:00
"------------------------------------------------------"
+ "-----------------------";
2021-04-15 07:27:20 +00:00
private MasterHeader masterHeader;
2015-06-01 09:35:51 +00:00
private final byte[] buffer;
2021-05-12 07:08:24 +00:00
private final boolean debug = false;
2015-06-01 09:35:51 +00:00
2020-02-02 10:17:49 +00:00
private final List<Record> records = new ArrayList<> ();
2021-04-15 07:27:20 +00:00
private int totalFiles;
private int totalDisks;
2021-04-17 21:32:03 +00:00
private int totalBlocks;
2015-06-01 09:35:51 +00:00
2021-04-19 02:46:45 +00:00
private VolumeName volumeName;
2021-04-17 22:54:21 +00:00
2020-02-07 23:26:38 +00:00
// ---------------------------------------------------------------------------------//
2015-06-01 09:35:51 +00:00
public NuFX (Path path) throws FileFormatException, IOException
2020-02-07 23:26:38 +00:00
// ---------------------------------------------------------------------------------//
2015-06-01 09:35:51 +00:00
{
buffer = Files.readAllBytes (path);
2021-08-20 10:04:53 +00:00
volumeName = new VolumeName (path.getFileName ().toString ());
2023-03-15 23:00:31 +00:00
2021-08-20 10:04:53 +00:00
read (buffer);
}
// ---------------------------------------------------------------------------------//
public NuFX (byte[] buffer, String fileName)
// ---------------------------------------------------------------------------------//
{
this.buffer = buffer;
this.volumeName = new VolumeName (fileName);
read (buffer);
}
2016-12-15 00:01:42 +00:00
2021-08-20 10:04:53 +00:00
// ---------------------------------------------------------------------------------//
void read (byte[] buffer)
// ---------------------------------------------------------------------------------//
{
2021-04-15 07:27:20 +00:00
masterHeader = new MasterHeader (buffer);
2015-06-01 09:35:51 +00:00
2021-08-30 06:56:43 +00:00
int dataPtr = masterHeader.bin2 ? 176 : 48;
2015-06-01 09:35:51 +00:00
if (debug)
2021-04-15 07:27:20 +00:00
System.out.printf ("%s%n%n", masterHeader);
2015-06-01 09:35:51 +00:00
2021-04-15 07:27:20 +00:00
for (int rec = 0; rec < masterHeader.getTotalRecords (); rec++)
2015-06-01 09:35:51 +00:00
{
2021-03-29 01:04:01 +00:00
Record record = new Record (buffer, dataPtr);
2015-06-01 09:35:51 +00:00
records.add (record);
if (debug)
System.out.printf ("Record: %d%n%n%s%n%n", rec, record);
2021-03-29 01:04:01 +00:00
dataPtr += record.getAttributes () + record.getFileNameLength ();
2015-06-01 09:35:51 +00:00
int threadsPtr = dataPtr;
2021-03-29 01:04:01 +00:00
dataPtr += record.getTotalThreads () * 16;
2015-06-01 09:35:51 +00:00
2021-03-29 01:04:01 +00:00
for (int i = 0; i < record.getTotalThreads (); i++)
2015-06-01 09:35:51 +00:00
{
Thread thread = new Thread (buffer, threadsPtr + i * 16, dataPtr);
2021-04-15 07:27:20 +00:00
record.threads.add (thread);
2015-06-01 09:35:51 +00:00
dataPtr += thread.getCompressedEOF ();
if (debug)
System.out.printf ("Thread: %d%n%n%s%n%n", i, thread);
}
2021-04-15 07:27:20 +00:00
if (record.hasFile ())
2021-04-17 21:32:03 +00:00
{
2021-04-15 07:27:20 +00:00
++totalFiles;
2021-05-02 04:45:26 +00:00
volumeName.storePath (record.getFileName ());
}
if (record.hasDisk ())
++totalDisks;
}
2024-05-06 22:34:02 +00:00
// System.out.println (toString ());
2021-05-02 04:45:26 +00:00
}
// ---------------------------------------------------------------------------------//
2021-05-08 09:56:57 +00:00
private void calculateTotalBlocks () // not exact
2021-05-02 04:45:26 +00:00
// ---------------------------------------------------------------------------------//
{
totalBlocks = 0;
for (Record record : records)
2021-05-08 09:56:57 +00:00
if (record.hasFile () || record.hasResource ())
2021-05-02 04:45:26 +00:00
{
2021-04-18 23:53:37 +00:00
// note: total blocks does not include subdirectory blocks
2021-05-08 09:56:57 +00:00
int blocks = (record.getUncompressedSize () - 1) / BLOCK_SIZE + 1;
2021-04-17 21:32:03 +00:00
if (blocks == 1) // seedling
totalBlocks += blocks;
2021-05-08 09:56:57 +00:00
else if (blocks <= 0x100) // sapling
2021-04-17 21:32:03 +00:00
totalBlocks += blocks + 1;
else // tree
2021-05-08 09:56:57 +00:00
totalBlocks += blocks + (blocks / 0x100) + 2;
2021-04-17 21:32:03 +00:00
}
2015-06-01 09:35:51 +00:00
}
2020-02-07 23:26:38 +00:00
// ---------------------------------------------------------------------------------//
2021-04-15 07:27:20 +00:00
public byte[] getDiskBuffer ()
2020-02-07 23:26:38 +00:00
// ---------------------------------------------------------------------------------//
2015-06-01 09:35:51 +00:00
{
2021-04-15 07:27:20 +00:00
if (totalDisks > 0)
{
2021-05-03 20:12:44 +00:00
if (debug)
System.out.println ("Reading disk");
2021-05-03 22:50:30 +00:00
2021-04-15 07:27:20 +00:00
for (Record record : records)
for (Thread thread : record.threads)
if (thread.hasDisk ())
return thread.getData ();
}
2021-04-18 01:18:59 +00:00
2021-05-14 02:19:32 +00:00
if (totalFiles == 0)
return null;
if (debug)
System.out.println ("Reading files");
calculateTotalBlocks ();
int[] diskSizes = { 280, 800, 1600, 3200, 6400, 65536 };
for (int diskSize : diskSizes) // in case we choose a size that is too small
2021-04-19 02:33:14 +00:00
{
2021-05-14 02:19:32 +00:00
if (diskSize < (totalBlocks + 10))
continue;
2021-05-03 22:50:30 +00:00
2021-05-14 02:19:32 +00:00
try
2021-04-16 11:08:59 +00:00
{
2021-05-14 02:19:32 +00:00
ProdosDisk disk = new ProdosDisk (diskSize, volumeName.getVolumeName ());
int count = 0;
2021-04-17 21:32:03 +00:00
2021-05-14 02:19:32 +00:00
for (Record record : records)
2021-04-16 11:08:59 +00:00
{
2021-05-14 02:19:32 +00:00
if (record.hasFile ())
2021-04-16 11:08:59 +00:00
{
2021-05-14 02:19:32 +00:00
String fileName = volumeName.convert (record.getFileName ());
if (!record.isValidFileSystem ())
2021-04-17 21:32:03 +00:00
{
2021-05-14 02:19:32 +00:00
System.out.printf ("File %s is file system %s%n", fileName,
record.getFileSystemName ());
continue;
}
2021-05-04 00:02:20 +00:00
2021-05-14 02:19:32 +00:00
// int fileSize = record.getFileSize ();
byte fileType = (byte) record.getFileType ();
int eof = record.getUncompressedSize ();
int auxType = record.getAuxType ();
LocalDateTime created = record.getCreated ();
LocalDateTime modified = record.getModified ();
byte[] buffer;
try
{
buffer = record.getData ();
}
catch (Exception e)
{
System.out.println (e.getMessage ());
System.out.printf ("Failed to unpack: %s%n", fileName);
continue;
}
2021-05-04 00:02:20 +00:00
2021-05-14 02:19:32 +00:00
if (debug)
2023-03-15 23:00:31 +00:00
System.out.printf ("%3d %-35s %02X %,7d %,7d %,7d %s %s%n", ++count,
fileName, fileType, auxType, eof, buffer.length, created, modified);
2021-04-17 21:32:03 +00:00
2021-05-14 02:19:32 +00:00
FileEntry fileEntry;
try
{
2023-03-15 23:00:31 +00:00
fileEntry = disk.addFile (fileName, fileType, auxType, created, modified,
buffer, eof);
2021-05-14 02:19:32 +00:00
}
catch (FileAlreadyExistsException e)
{
System.out.printf ("File %s not added%n", fileName);
break;
}
2021-04-17 21:32:03 +00:00
2021-05-14 02:19:32 +00:00
if (record.hasResource ())
{
2021-05-04 00:02:20 +00:00
try
{
2021-05-14 02:19:32 +00:00
buffer = record.getResourceData ();
disk.addResourceFork (fileEntry, buffer, buffer.length);
2021-04-19 04:10:01 +00:00
}
2021-05-14 02:19:32 +00:00
catch (Exception e)
2021-05-03 20:12:44 +00:00
{
2021-05-14 02:19:32 +00:00
System.out.println (e.getMessage ());
System.out.printf ("Failed to unpack resource fork: %s%n", fileName);
2021-05-03 20:12:44 +00:00
}
2021-04-17 21:32:03 +00:00
}
2021-04-16 11:08:59 +00:00
}
2021-05-14 02:19:32 +00:00
}
2021-04-16 11:08:59 +00:00
2021-05-14 02:19:32 +00:00
disk.close ();
2021-04-16 11:08:59 +00:00
2021-05-14 02:19:32 +00:00
return disk.getBuffer ();
}
catch (DiskFullException e)
{
System.out.println ("disk full: " + diskSize); // go round again
}
catch (VolumeCatalogFullException e)
{
e.printStackTrace ();
return null;
}
catch (IOException e)
{
e.printStackTrace ();
return null;
2021-04-17 02:14:06 +00:00
}
2021-04-15 07:27:20 +00:00
}
2015-06-01 09:35:51 +00:00
return null;
}
2021-04-15 07:27:20 +00:00
// ---------------------------------------------------------------------------------//
public int getTotalFiles ()
// ---------------------------------------------------------------------------------//
{
return totalFiles;
}
// ---------------------------------------------------------------------------------//
public int getTotalDisks ()
// ---------------------------------------------------------------------------------//
{
return totalDisks;
}
2021-04-17 21:32:03 +00:00
// ---------------------------------------------------------------------------------//
public int getTotalBlocks ()
// ---------------------------------------------------------------------------------//
{
return totalBlocks;
}
2020-02-07 23:26:38 +00:00
// ---------------------------------------------------------------------------------//
2015-06-01 09:35:51 +00:00
@Override
public String toString ()
2020-02-07 23:26:38 +00:00
// ---------------------------------------------------------------------------------//
2015-06-01 09:35:51 +00:00
{
StringBuilder text = new StringBuilder ();
2021-05-08 09:56:57 +00:00
text.append (String.format (" %-15.15s Created:%-17s Mod:%-17s Recs:%5d%n%n",
2021-08-20 10:04:53 +00:00
volumeName.volumeName, masterHeader.getCreated2 (), masterHeader.getModified2 (),
masterHeader.getTotalRecords ()));
2023-03-15 23:00:31 +00:00
text.append (" Name Type Auxtyp Archived"
+ " Fmat Size Un-Length\n");
text.append (String.format ("%s%n", UNDERLINE));
int totalUncompressedSize = 0;
int totalCompressedSize = 0;
2021-04-15 07:27:20 +00:00
for (Record record : records)
{
2021-05-12 07:08:24 +00:00
text.append (String.format ("%s%n", record.getLine ()));
totalUncompressedSize += record.getUncompressedSize ();
totalCompressedSize += record.getCompressedSize ();
}
2021-05-12 07:08:24 +00:00
text.append (String.format ("%s%n", UNDERLINE));
2021-04-15 07:27:20 +00:00
float pct = 0;
if (totalUncompressedSize > 0)
pct = totalCompressedSize * 100 / totalUncompressedSize;
text.append (String.format (" Uncomp:%7d Comp:%7d %%of orig:%3.0f%%%n%n",
totalUncompressedSize, totalCompressedSize, pct));
return text.toString ();
2015-06-01 09:35:51 +00:00
}
2021-04-15 07:27:20 +00:00
2021-04-19 02:33:14 +00:00
// ---------------------------------------------------------------------------------//
class VolumeName
// ---------------------------------------------------------------------------------//
{
private List<String> paths = new ArrayList<> ();
private boolean rootContainsFiles;
private String volumeName = "DiskBrowser";
private int nameOffset = 0;
2021-04-19 02:46:45 +00:00
// -------------------------------------------------------------------------------//
2021-08-20 10:04:53 +00:00
VolumeName (String name)
2021-04-19 02:46:45 +00:00
// -------------------------------------------------------------------------------//
{
2021-08-20 10:04:53 +00:00
int pos = name.lastIndexOf ('.');
2021-04-19 02:46:45 +00:00
if (pos > 0)
2021-08-20 10:04:53 +00:00
name = name.substring (0, pos);
if (name.length () > 15)
name = name.substring (0, 15);
name = name.replace (' ', '.');
2021-04-19 02:46:45 +00:00
2021-08-20 10:04:53 +00:00
this.volumeName = name;
2021-05-02 04:45:26 +00:00
}
// -------------------------------------------------------------------------------//
2021-04-19 02:33:14 +00:00
private void storePath (String fileName)
// -------------------------------------------------------------------------------//
{
int pos = fileName.lastIndexOf ('/');
if (pos < 0)
rootContainsFiles = true;
else
{
String path = fileName.substring (0, pos);
for (int i = 0; i < paths.size (); i++)
{
String cmp = paths.get (i);
if (cmp.startsWith (path)) // longer path already there
return;
if (path.startsWith (cmp))
{
paths.set (i, path); // replace shorter path with longer path
return;
}
}
paths.add (path);
}
}
// -------------------------------------------------------------------------------//
private String getVolumeName ()
// -------------------------------------------------------------------------------//
{
if (rootContainsFiles)
return volumeName;
if (paths.size () > 0)
{
int pos = paths.get (0).indexOf ('/');
if (pos > 0)
{
String firstPath = paths.get (0).substring (0, pos + 1);
boolean allSame = true;
for (String pathName : paths)
if (!pathName.startsWith (firstPath))
{
allSame = false;
break;
}
if (allSame)
{
volumeName = firstPath.substring (0, pos);
nameOffset = volumeName.length () + 1; // skip volume name in all paths
}
}
}
if (paths.size () == 1) // exactly one directory path
{
String onlyPath = paths.get (0);
int pos = onlyPath.indexOf ('/');
if (pos == -1) // no separators
volumeName = onlyPath;
else // use first component
volumeName = onlyPath.substring (0, pos);
nameOffset = volumeName.length () + 1; // skip volume name in all paths
}
return volumeName;
}
// -------------------------------------------------------------------------------//
String convert (String fileName)
// -------------------------------------------------------------------------------//
{
if (nameOffset > 0) // remove volume name from path
return fileName.substring (nameOffset);
return fileName;
}
// -------------------------------------------------------------------------------//
void info ()
// -------------------------------------------------------------------------------//
{
if (rootContainsFiles)
System.out.println ("Root contains files");
System.out.println ("Unique paths:");
if (paths.size () == 0)
System.out.println ("<none>");
for (String pathName : paths)
System.out.println (pathName);
}
}
2015-06-01 09:35:51 +00:00
}