package com.bytezone.diskbrowser; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import com.bytezone.common.Utility; public class NuFX { private static String[] fileSystems = {// "", "ProDOS/SOS", "DOS 3.3", "DOS 3.2", "Apple II Pascal", "Macintosh HFS", "Macintosh MFS", "Lisa File System", "Apple CP/M", "", "MS-DOS", "High Sierra", "ISO 9660", "AppleShare" }; private final Header header; private final byte[] buffer; private final boolean debug = false; private final List records = new ArrayList (); private final List threads = new ArrayList (); public NuFX (Path path) throws FileFormatException, IOException { buffer = Files.readAllBytes (path); header = new Header (buffer); int dataPtr = 48; if (header.bin2) dataPtr += 128; if (debug) System.out.printf ("%s%n%n", header); for (int rec = 0; rec < header.totalRecords; rec++) { Record record = new Record (dataPtr); records.add (record); if (debug) System.out.printf ("Record: %d%n%n%s%n%n", rec, record); dataPtr += record.attributes + record.fileNameLength; int threadsPtr = dataPtr; dataPtr += record.totThreads * 16; for (int i = 0; i < record.totThreads; i++) { Thread thread = new Thread (buffer, threadsPtr + i * 16, dataPtr); threads.add (thread); dataPtr += thread.getCompressedEOF (); if (debug) System.out.printf ("Thread: %d%n%n%s%n%n", i, thread); } } } public byte[] getBuffer () { for (Thread thread : threads) if (thread.hasDisk ()) return thread.getData (); return null; } @Override public String toString () { for (Thread thread : threads) if (thread.hasDisk ()) return thread.toString (); return "no disk"; } protected static int getCRC (final byte[] buffer, int base) { int crc = base; for (int j = 0; j < buffer.length; j++) { crc = ((crc >>> 8) | (crc << 8)) & 0xFFFF; crc ^= (buffer[j] & 0xFF); crc ^= ((crc & 0xFF) >> 4); crc ^= (crc << 12) & 0xFFFF; crc ^= ((crc & 0xFF) << 5) & 0xFFFF; } crc &= 0xFFFF; return crc; } class Header { private final int totalRecords; private final int version; private final int eof; private final int crc; private final DateTime created; private final DateTime modified; boolean bin2; public Header (byte[] buffer) throws FileFormatException { int ptr = 0; while (true) { if (isNuFile (buffer, ptr)) break; if (isBin2 (buffer, ptr)) { ptr += 128; bin2 = true; continue; } throw new FileFormatException ("NuFile not found"); } crc = Utility.getWord (buffer, ptr + 6); totalRecords = Utility.getLong (buffer, ptr + 8); created = new DateTime (buffer, ptr + 12); modified = new DateTime (buffer, ptr + 20); version = Utility.getWord (buffer, ptr + 28); eof = Utility.getLong (buffer, ptr + 38); byte[] crcBuffer = new byte[40]; System.arraycopy (buffer, ptr + 8, crcBuffer, 0, crcBuffer.length); if (crc != getCRC (crcBuffer, 0)) { System.out.println ("***** Master CRC mismatch *****"); throw new FileFormatException ("Master CRC failed"); } } private boolean isNuFile (byte[] buffer, int ptr) { if (buffer[ptr] == 0x4E && buffer[ptr + 1] == (byte) 0xF5 && buffer[ptr + 2] == 0x46 && buffer[ptr + 3] == (byte) 0xE9 && buffer[ptr + 4] == 0x6C && buffer[ptr + 5] == (byte) 0xE5) return true; return false; } private boolean isBin2 (byte[] buffer, int ptr) { if (buffer[ptr] == 0x0A && buffer[ptr + 1] == 0x47 && buffer[ptr + 2] == 0x4C && buffer[ptr + 18] == (byte) 0x02) return true; return false; } @Override public String toString () { StringBuilder text = new StringBuilder (); text.append (String.format ("Master CRC ..... %,d (%04X)%n", crc, crc)); text.append (String.format ("Records ........ %,d%n", totalRecords)); text.append (String.format ("Created ........ %s%n", created.format ())); text.append (String.format ("Modified ....... %s%n", modified.format ())); text.append (String.format ("Version ........ %,d%n", version)); text.append (String.format ("Master EOF ..... %,d", eof)); return text.toString (); } } class Record { 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; public Record (int dataPtr) throws FileFormatException { // check for NuFX if (!isNuFX (buffer, dataPtr)) 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); if (crc != getCRC (crcBuffer, 0)) { System.out.println ("***** Header CRC mismatch *****"); throw new FileFormatException ("Header CRC failed"); } 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 = ""; } private boolean isNuFX (byte[] buffer, int ptr) { if (buffer[ptr] == 0x4E && buffer[ptr + 1] == (byte) 0xF5 && buffer[ptr + 2] == 0x46 && buffer[ptr + 3] == (byte) 0xD8) return true; return false; } @Override public String toString () { StringBuilder text = new StringBuilder (); text.append (String.format ("Header CRC ..... %,d (%04X)%n", crc, crc)); text.append (String.format ("Attributes ..... %d%n", attributes)); text.append (String.format ("Version ........ %d%n", version)); 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)); text.append (String.format ("Access ......... %,d%n", access)); text.append (String.format ("File type ...... %,d%n", fileType)); text.append (String.format ("Aux type ....... %,d%n", auxType)); text.append (String.format ("Stor type ...... %,d%n", storType)); text.append (String.format ("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 (); } } }