2021-03-29 01:04:01 +00:00
|
|
|
package com.bytezone.diskbrowser.utilities;
|
|
|
|
|
2021-05-02 04:45:26 +00:00
|
|
|
import static com.bytezone.diskbrowser.prodos.ProdosConstants.fileTypes;
|
|
|
|
|
2021-04-17 02:14:06 +00:00
|
|
|
import java.time.LocalDateTime;
|
2021-04-15 07:27:20 +00:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
|
2021-03-29 01:04:01 +00:00
|
|
|
// -----------------------------------------------------------------------------------//
|
|
|
|
class Record
|
|
|
|
// -----------------------------------------------------------------------------------//
|
|
|
|
{
|
2021-04-15 07:27:20 +00:00
|
|
|
private static final byte[] NuFX = { 0x4E, (byte) 0xF5, 0x46, (byte) 0xD8 };
|
2021-03-29 01:04:01 +00:00
|
|
|
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" };
|
|
|
|
|
2021-04-15 07:27:20 +00:00
|
|
|
private static String[] storage = { "", "Seedling", "Sapling", "Tree", "", "Extended",
|
|
|
|
"", "", "", "", "", "", "", "Subdirectory" };
|
|
|
|
|
|
|
|
private static String[] accessChars = { "D", "R", "B", "", "", "I", "W", "R" };
|
2021-05-02 04:45:26 +00:00
|
|
|
private static String threadFormats[] = { "unc", "sq ", "lz1", "lz2", "", "" };
|
2021-04-15 07:27:20 +00:00
|
|
|
|
2021-03-29 01:04:01 +00:00
|
|
|
private final int totThreads;
|
|
|
|
private final int crc;
|
|
|
|
private final char separator;
|
|
|
|
private final int fileSystemID;
|
|
|
|
private final int attributes;
|
|
|
|
private final int version;
|
|
|
|
private final int access;
|
|
|
|
private final int fileType;
|
|
|
|
private final int auxType;
|
|
|
|
private final int storType;
|
|
|
|
private final DateTime created;
|
|
|
|
private final DateTime modified;
|
|
|
|
private final DateTime archived;
|
|
|
|
private final int optionSize;
|
|
|
|
private final int fileNameLength;
|
|
|
|
private final String fileName;
|
|
|
|
|
2021-04-15 07:27:20 +00:00
|
|
|
final List<Thread> threads = new ArrayList<> ();
|
|
|
|
|
2021-03-29 01:04:01 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
public Record (byte[] buffer, int dataPtr) throws FileFormatException
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
// check for NuFX
|
2021-04-15 07:27:20 +00:00
|
|
|
if (!Utility.isMagic (buffer, dataPtr, NuFX))
|
2021-03-29 01:04:01 +00:00
|
|
|
throw new FileFormatException ("NuFX not found");
|
|
|
|
|
|
|
|
crc = Utility.getWord (buffer, dataPtr + 4);
|
|
|
|
attributes = Utility.getWord (buffer, dataPtr + 6);
|
|
|
|
version = Utility.getWord (buffer, dataPtr + 8);
|
|
|
|
totThreads = Utility.getLong (buffer, dataPtr + 10);
|
|
|
|
fileSystemID = Utility.getWord (buffer, dataPtr + 14);
|
|
|
|
separator = (char) (buffer[dataPtr + 16] & 0x00FF);
|
|
|
|
access = Utility.getLong (buffer, dataPtr + 18);
|
|
|
|
fileType = Utility.getLong (buffer, dataPtr + 22);
|
|
|
|
auxType = Utility.getLong (buffer, dataPtr + 26);
|
|
|
|
storType = Utility.getWord (buffer, dataPtr + 30);
|
|
|
|
created = new DateTime (buffer, dataPtr + 32);
|
|
|
|
modified = new DateTime (buffer, dataPtr + 40);
|
|
|
|
archived = new DateTime (buffer, dataPtr + 48);
|
|
|
|
optionSize = Utility.getWord (buffer, dataPtr + 56);
|
|
|
|
fileNameLength = Utility.getWord (buffer, dataPtr + attributes - 2);
|
|
|
|
|
|
|
|
int len = attributes + fileNameLength - 6;
|
|
|
|
byte[] crcBuffer = new byte[len + totThreads * 16];
|
|
|
|
System.arraycopy (buffer, dataPtr + 6, crcBuffer, 0, crcBuffer.length);
|
|
|
|
|
2021-04-17 04:41:36 +00:00
|
|
|
if (crc != Utility.getCRC (crcBuffer, crcBuffer.length, 0))
|
2021-03-29 01:04:01 +00:00
|
|
|
{
|
2021-04-18 10:11:26 +00:00
|
|
|
System.out.println ("***** Record CRC mismatch *****");
|
|
|
|
throw new FileFormatException ("Record CRC failed");
|
2021-03-29 01:04:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fileNameLength > 0)
|
|
|
|
{
|
|
|
|
int start = dataPtr + attributes;
|
|
|
|
int end = start + fileNameLength;
|
|
|
|
for (int i = start; i < end; i++)
|
|
|
|
buffer[i] &= 0x7F;
|
|
|
|
fileName = new String (buffer, start, fileNameLength);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
fileName = "";
|
|
|
|
}
|
|
|
|
|
2021-04-27 11:26:09 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
boolean isValidFileSystem ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
return fileSystemID <= 4 || fileSystemID == 8;
|
|
|
|
}
|
|
|
|
|
2021-03-29 01:04:01 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
int getAttributes ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
return attributes;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
int getFileNameLength ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
return fileNameLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
int getTotalThreads ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
return totThreads;
|
|
|
|
}
|
|
|
|
|
2021-04-15 07:27:20 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-05-03 20:12:44 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
boolean hasResource ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
for (Thread thread : threads)
|
|
|
|
if (thread.hasResource ())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-04-15 07:27:20 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
String getFileName ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
2021-04-26 00:03:43 +00:00
|
|
|
if (fileNameLength > 0) // probably version 0
|
|
|
|
return fileName;
|
|
|
|
|
2021-04-15 07:27:20 +00:00
|
|
|
for (Thread thread : threads)
|
|
|
|
if (thread.hasFileName ())
|
2021-04-17 02:14:06 +00:00
|
|
|
{
|
|
|
|
String fileName = thread.getFileName ();
|
|
|
|
if (separator != '/')
|
|
|
|
return fileName.replace (separator, '/');
|
2021-04-15 07:27:20 +00:00
|
|
|
return thread.getFileName ();
|
2021-04-17 02:14:06 +00:00
|
|
|
}
|
2021-04-15 07:27:20 +00:00
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
int getFileType ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
return fileType;
|
|
|
|
}
|
|
|
|
|
2021-04-17 02:14:06 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
int getAuxType ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
return auxType;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
LocalDateTime getCreated ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
if (created == null)
|
|
|
|
return null;
|
|
|
|
return created.getLocalDateTime ();
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
LocalDateTime getModified ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
if (modified == null)
|
|
|
|
return null;
|
|
|
|
return modified.getLocalDateTime ();
|
|
|
|
}
|
|
|
|
|
2021-05-03 20:12:44 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
LocalDateTime getArchived ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
if (archived == null)
|
|
|
|
return null;
|
|
|
|
return archived.getLocalDateTime ();
|
|
|
|
}
|
|
|
|
|
2021-04-17 21:32:03 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
int getFileSystemID ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
return fileSystemID;
|
|
|
|
}
|
|
|
|
|
2021-04-27 11:26:09 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
String getFileSystemName ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
return fileSystems[fileSystemID];
|
|
|
|
}
|
|
|
|
|
2021-04-15 07:27:20 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
int getFileSize ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
for (Thread thread : threads)
|
|
|
|
if (thread.hasFile ())
|
|
|
|
return thread.getFileSize ();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-05-02 04:45:26 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
int getThreadFormat ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
for (Thread thread : threads)
|
|
|
|
if (thread.hasFile ())
|
|
|
|
return thread.threadFormat;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-04-15 07:27:20 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
int getUncompressedSize ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
2021-05-03 20:12:44 +00:00
|
|
|
int size = 0;
|
|
|
|
|
2021-04-15 07:27:20 +00:00
|
|
|
for (Thread thread : threads)
|
2021-05-03 20:12:44 +00:00
|
|
|
if (thread.hasFile () || thread.hasResource ())
|
|
|
|
size += thread.getUncompressedEOF ();
|
2021-04-15 07:27:20 +00:00
|
|
|
|
2021-05-03 20:12:44 +00:00
|
|
|
return size;
|
2021-04-15 07:27:20 +00:00
|
|
|
}
|
|
|
|
|
2021-05-02 04:45:26 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
int getCompressedSize ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
2021-05-03 20:12:44 +00:00
|
|
|
int size = 0;
|
|
|
|
|
2021-05-02 04:45:26 +00:00
|
|
|
for (Thread thread : threads)
|
2021-05-03 20:12:44 +00:00
|
|
|
if (thread.hasFile () || thread.hasResource ())
|
|
|
|
size += thread.getCompressedEOF ();
|
2021-05-02 04:45:26 +00:00
|
|
|
|
2021-05-03 20:12:44 +00:00
|
|
|
return size;
|
2021-05-02 04:45:26 +00:00
|
|
|
}
|
|
|
|
|
2021-03-29 01:04:01 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
2021-04-16 11:08:59 +00:00
|
|
|
byte[] getData ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
for (Thread thread : threads)
|
|
|
|
if (thread.hasFile ())
|
|
|
|
return thread.getData ();
|
2021-05-03 20:12:44 +00:00
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
byte[] getResourceData ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
for (Thread thread : threads)
|
|
|
|
if (thread.hasResource ())
|
|
|
|
return thread.getData ();
|
|
|
|
|
2021-04-16 11:08:59 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-05-02 04:45:26 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
String getLine ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
2021-05-03 20:12:44 +00:00
|
|
|
float pct = 0;
|
|
|
|
if (getUncompressedSize () > 0)
|
|
|
|
pct = getCompressedSize () * 100 / getUncompressedSize ();
|
|
|
|
String lockedFlag = (access | 0xC3) == 1 ? "+" : " ";
|
|
|
|
String forkedFlag = hasResource () ? "+" : " ";
|
|
|
|
String name = getFileName ();
|
|
|
|
if (name.length () > 27)
|
|
|
|
name = ".." + name.substring (name.length () - 25);
|
|
|
|
|
|
|
|
return String.format ("%s%-27.27s %s%s $%04X %-15s %s %3.0f%% %7d", lockedFlag,
|
|
|
|
name, fileTypes[fileType], forkedFlag, auxType, archived.format2 (),
|
2021-05-02 04:45:26 +00:00
|
|
|
threadFormats[getThreadFormat ()], pct, getUncompressedSize ());
|
|
|
|
}
|
|
|
|
|
2021-04-16 11:08:59 +00:00
|
|
|
// ---------------------------------------------------------------------------------//
|
2021-03-29 01:04:01 +00:00
|
|
|
@Override
|
|
|
|
public String toString ()
|
|
|
|
// ---------------------------------------------------------------------------------//
|
|
|
|
{
|
|
|
|
StringBuilder text = new StringBuilder ();
|
|
|
|
|
2021-04-15 07:27:20 +00:00
|
|
|
String bits = "00000000" + Integer.toBinaryString (access & 0xFF);
|
|
|
|
bits = bits.substring (bits.length () - 8);
|
|
|
|
String decode = Utility.matchFlags (access, accessChars);
|
|
|
|
|
2021-04-17 02:14:06 +00:00
|
|
|
text.append (String.format ("Header CRC ..... %,d (%<04X)%n", crc));
|
2021-03-29 01:04:01 +00:00
|
|
|
text.append (String.format ("Attributes ..... %d%n", attributes));
|
|
|
|
text.append (String.format ("Version ........ %d%n", version));
|
|
|
|
text.append (String.format ("Threads ........ %d%n", totThreads));
|
|
|
|
text.append (String.format ("File sys id .... %d (%s)%n", fileSystemID,
|
|
|
|
fileSystems[fileSystemID]));
|
|
|
|
text.append (String.format ("Separator ...... %s%n", separator));
|
2021-04-15 07:27:20 +00:00
|
|
|
text.append (String.format ("Access ......... %s %s%n", bits, decode));
|
2021-04-18 10:11:26 +00:00
|
|
|
|
2021-04-15 07:27:20 +00:00
|
|
|
if (storType < 16)
|
|
|
|
{
|
2021-04-17 04:41:36 +00:00
|
|
|
text.append (String.format ("File type ...... %02X %s%n", fileType,
|
2021-05-02 04:45:26 +00:00
|
|
|
fileTypes[fileType]));
|
2021-04-17 04:41:36 +00:00
|
|
|
text.append (String.format ("Aux type ....... %,d $%<04X%n", auxType));
|
2021-04-15 07:27:20 +00:00
|
|
|
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));
|
|
|
|
}
|
2021-04-18 10:11:26 +00:00
|
|
|
|
2021-03-29 01:04:01 +00:00
|
|
|
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 ();
|
|
|
|
}
|
|
|
|
}
|