diff --git a/src/com/bytezone/diskbrowser/disk/DiskFactory.java b/src/com/bytezone/diskbrowser/disk/DiskFactory.java index 04aec3a..0ec9593 100755 --- a/src/com/bytezone/diskbrowser/disk/DiskFactory.java +++ b/src/com/bytezone/diskbrowser/disk/DiskFactory.java @@ -148,7 +148,7 @@ public class DiskFactory if ("sdk".equals (suffix) // NuFX disk || "shk".equals (suffix) // NuFX files or disk - || "bxy".equals (suffix)) // NuFX in Binary2 + || "bxy".equals (suffix)) // NuFX in Bin2 { if (debug) System.out.println (" ** sdk/shk/bxy **"); @@ -156,7 +156,11 @@ public class DiskFactory { nuFX = new NuFX (file.toPath ()); if (nuFX.getTotalDisks () == 0 && nuFX.getTotalFiles () == 0) + { + if (debug) + System.out.println ("Empty NuFX file"); return null; + } byte[] diskBuffer = nuFX.getDiskBuffer (); if (diskBuffer == null) @@ -183,10 +187,10 @@ public class DiskFactory return null; } } - else if ("bny".equals (suffix)) // Binary2 uncompressed files + else if ("bny".equals (suffix) || "bqy".equals (suffix)) // Binary2 uncompressed files { if (debug) - System.out.println (" ** bny **"); + System.out.println (" ** bny/bqy **"); try { binary2 = new Binary2 (file.toPath ()); diff --git a/src/com/bytezone/diskbrowser/gui/FontAction.java b/src/com/bytezone/diskbrowser/gui/FontAction.java index 35c1bf2..0394300 100644 --- a/src/com/bytezone/diskbrowser/gui/FontAction.java +++ b/src/com/bytezone/diskbrowser/gui/FontAction.java @@ -36,6 +36,7 @@ public class FontAction extends DefaultAction implements QuitListener { super ("Set Font...", "Set display to a different font or font size", "/com/bytezone/loadlister/"); + int mask = Toolkit.getDefaultToolkit ().getMenuShortcutKeyMaskEx (); putValue (Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke (KeyEvent.VK_F, mask)); } @@ -146,8 +147,7 @@ public class FontAction extends DefaultAction implements QuitListener // ---------------------------------------------------------------------------------// { FontChangeEvent fontChangeEvent = new FontChangeEvent (font); - FontChangeListener[] listeners = - (listenerList.getListeners (FontChangeListener.class)); + FontChangeListener[] listeners = (listenerList.getListeners (FontChangeListener.class)); for (FontChangeListener listener : listeners) listener.changeFont (fontChangeEvent); } diff --git a/src/com/bytezone/diskbrowser/gui/SaveFileAction.java b/src/com/bytezone/diskbrowser/gui/SaveFileAction.java index 7608e07..8d4b82e 100644 --- a/src/com/bytezone/diskbrowser/gui/SaveFileAction.java +++ b/src/com/bytezone/diskbrowser/gui/SaveFileAction.java @@ -1,11 +1,15 @@ package com.bytezone.diskbrowser.gui; +import java.awt.Toolkit; import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; import java.io.File; +import javax.swing.Action; import javax.swing.JCheckBox; import javax.swing.JFileChooser; import javax.swing.JOptionPane; +import javax.swing.KeyStroke; import com.bytezone.diskbrowser.applefile.AppleFileSource; @@ -23,6 +27,8 @@ class SaveFileAction extends AbstractSaveAction implements FileSelectionListener super ("Save file...", "Save currently selected file", "Save File"); fileChooser.setAccessory (formatted); + int mask = Toolkit.getDefaultToolkit ().getMenuShortcutKeyMaskEx (); + putValue (Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke (KeyEvent.VK_S, mask)); } // ---------------------------------------------------------------------------------// @@ -36,6 +42,11 @@ class SaveFileAction extends AbstractSaveAction implements FileSelectionListener return; } + if (formatted.isSelected ()) + setSelectedFile (new File (appleFileSource.getUniqueName () + ".txt")); + else + setSelectedFile (new File (appleFileSource.getUniqueName () + ".bin")); + if (fileChooser.showSaveDialog (null) != JFileChooser.APPROVE_OPTION) return; @@ -51,7 +62,6 @@ class SaveFileAction extends AbstractSaveAction implements FileSelectionListener // ---------------------------------------------------------------------------------// { this.appleFileSource = event.appleFileSource; - setSelectedFile (new File (appleFileSource.getUniqueName () + ".bin")); setEnabled (appleFileSource != null && appleFileSource.getDataSource () != null && appleFileSource.getDataSource ().getBuffer () != null); diff --git a/src/com/bytezone/diskbrowser/nufx/Binary2.java b/src/com/bytezone/diskbrowser/nufx/Binary2.java index 29d82ca..d332749 100644 --- a/src/com/bytezone/diskbrowser/nufx/Binary2.java +++ b/src/com/bytezone/diskbrowser/nufx/Binary2.java @@ -10,14 +10,14 @@ import com.bytezone.diskbrowser.prodos.write.DiskFullException; import com.bytezone.diskbrowser.prodos.write.FileAlreadyExistsException; import com.bytezone.diskbrowser.prodos.write.ProdosDisk; import com.bytezone.diskbrowser.prodos.write.VolumeCatalogFullException; +import com.bytezone.diskbrowser.utilities.Utility; // -----------------------------------------------------------------------------------// public class Binary2 // -----------------------------------------------------------------------------------// { private static final String UNDERLINE = - "------------------------------------------------------" - + "-----------------------"; + "------------------------------------------------------" + "-----------------------"; Binary2Header binary2Header; byte[] buffer; @@ -43,7 +43,6 @@ public class Binary2 do { binary2Header = new Binary2Header (buffer, ptr); - System.out.println (binary2Header); headers.add (binary2Header); totalBlocks += binary2Header.totalBlocks; @@ -53,19 +52,33 @@ public class Binary2 } // ---------------------------------------------------------------------------------// - public byte[] getDiskBuffer () throws DiskFullException, VolumeCatalogFullException, - FileAlreadyExistsException, IOException + public byte[] getDiskBuffer () + throws DiskFullException, VolumeCatalogFullException, FileAlreadyExistsException, IOException // ---------------------------------------------------------------------------------// { ProdosDisk disk = new ProdosDisk (800, "DiskBrowser"); for (Binary2Header header : headers) { - byte[] dataBuffer = new byte[header.eof]; // this sux - System.arraycopy (buffer, header.ptr + 128, dataBuffer, 0, dataBuffer.length); + if (header.compressed && buffer[header.ptr + 128] == 0x76 + && buffer[header.ptr + 129] == (byte) 0xFF) + { + byte[] tmp = new byte[header.eof]; // this sux + System.arraycopy (buffer, header.ptr + 128, tmp, 0, tmp.length); + String name = Utility.getCString (tmp, 4); - disk.addFile (header.fileName, header.fileType, header.auxType, header.created, - header.modified, dataBuffer, header.eof); + Squeeze squeeze = new Squeeze (); + byte[] dataBuffer = squeeze.unSqueeze (tmp); + disk.addFile (name, header.fileType, header.auxType, header.created, header.modified, + dataBuffer, header.eof); + } + else + { + byte[] dataBuffer = new byte[header.eof]; // this sux + System.arraycopy (buffer, header.ptr + 128, dataBuffer, 0, dataBuffer.length); + disk.addFile (header.fileName, header.fileType, header.auxType, header.created, + header.modified, dataBuffer, header.eof); + } } disk.close (); @@ -80,12 +93,12 @@ public class Binary2 { StringBuilder text = new StringBuilder (); - text.append (String.format ( - " %-15.15s Files:%5d%n%n", - fileName, headers.size ())); + text.append ( + String.format (" %-15.15s Files:%5d%n%n", + fileName, headers.size ())); - text.append (" Name Type Auxtyp Modified" - + " Fmat Length\n"); + text.append ( + " Name Type Auxtyp Modified" + " Fmat Length\n"); text.append (String.format ("%s%n", UNDERLINE)); diff --git a/src/com/bytezone/diskbrowser/nufx/Binary2Header.java b/src/com/bytezone/diskbrowser/nufx/Binary2Header.java index ec41a5e..902b60d 100644 --- a/src/com/bytezone/diskbrowser/nufx/Binary2Header.java +++ b/src/com/bytezone/diskbrowser/nufx/Binary2Header.java @@ -14,10 +14,9 @@ public class Binary2Header // -----------------------------------------------------------------------------------// { static DateTimeFormatter formatter = DateTimeFormatter.ofPattern ("dd-LLL-yy HH:mm"); - static String[] osTypes = - { "Prodos", "DOS 3.3", "Reserved", "DOS 3.2 or 3.1", "Pascal", "Macintosh MFS", - "Macintosh HFS", "Lisa", "CPM", "Reserved", "MS-DOS", "High Sierra (CD-ROM)", - "ISO 9660 (CD-ROM)", "AppleShare" }; + static String[] osTypes = { "Prodos", "DOS 3.3", "Reserved", "DOS 3.2 or 3.1", "Pascal", + "Macintosh MFS", "Macintosh HFS", "Lisa", "CPM", "Reserved", "MS-DOS", "High Sierra (CD-ROM)", + "ISO 9660 (CD-ROM)", "AppleShare" }; int ptr; byte[] buffer; @@ -89,8 +88,8 @@ public class Binary2Header public String getLine () // ---------------------------------------------------------------------------------// { - return String.format (" %-33s %3s $%04X %s unc %7d", fileName, - fileTypes[fileType], auxType, modified.format (formatter), eof); + return String.format (" %-33s %3s $%04X %s unc %7d", fileName, fileTypes[fileType], + auxType, modified.format (formatter), eof); } // ---------------------------------------------------------------------------------// @@ -117,12 +116,13 @@ public class Binary2Header text.append (String.format ("Prodos storage type ... %02X%n", prodos16storageType)); text.append (String.format ("Prodos total blocks ... %02X%n", prodos16totalBlocks)); text.append (String.format ("Prodos eof ............ %06X %<,d%n", prodos16eof)); - text.append ( - String.format ("Disk space needed ..... %08X %<,d%n", diskSpaceRequired)); - text.append ( - String.format ("OS type ............... %02X %s%n", osType, osTypes[osType])); + text.append (String.format ("Disk space needed ..... %08X %<,d%n", diskSpaceRequired)); + text.append (String.format ("OS type ............... %02X %s%n", osType, osTypes[osType])); text.append (String.format ("Native file type ...... %02X%n", nativeFileType)); text.append (String.format ("Data flags ............ %02X%n", dataFlags)); + text.append (String.format (" compressed .......... %s%n", compressed)); + text.append (String.format (" encrypted ........... %s%n", encrypted)); + text.append (String.format (" sparse .............. %s%n", sparsePacked)); text.append (String.format ("Version ............... %02X%n", version)); text.append (String.format ("Following files ....... %02X%n", filesToFollow)); diff --git a/src/com/bytezone/diskbrowser/nufx/Squeeze.java b/src/com/bytezone/diskbrowser/nufx/Squeeze.java new file mode 100644 index 0000000..571f687 --- /dev/null +++ b/src/com/bytezone/diskbrowser/nufx/Squeeze.java @@ -0,0 +1,125 @@ +package com.bytezone.diskbrowser.nufx; + +import com.bytezone.diskbrowser.utilities.FileFormatException; +import com.bytezone.diskbrowser.utilities.Utility; + +// see http://fileformats.archiveteam.org/wiki/Squeeze +// see http://fileformats.archiveteam.org/wiki/RLE90 +// -----------------------------------------------------------------------------------// +public class Squeeze +// -----------------------------------------------------------------------------------// +{ + private static final byte[] Squeeze = { 0x76, (byte) 0xFF }; + private static int RLE_DELIMITER = 0x90; + private static int EOF_TOKEN = 0x100; + + private int bits; + private int bitPos = 7; // trigger the first read + private int ptr; + private byte[] buffer; + private Node[] nodes; + + // ---------------------------------------------------------------------------------// + public byte[] unSqueeze (byte[] buffer) + // ---------------------------------------------------------------------------------// + { + if (!Utility.isMagic (buffer, 0, Squeeze)) + throw new FileFormatException ("Not Squeeze format"); + + byte[] uncompressed = new byte[buffer.length * 3]; + int uncPtr = 0; + + int fileChecksum = Utility.getShort (buffer, 2); + String fileName = Utility.getCString (buffer, 4); + + ptr = fileName.length () + 5; + int nodeCount = Utility.getShort (buffer, ptr); + ptr += 2; + + nodes = new Node[nodeCount]; + this.buffer = buffer; + + for (int i = 0; i < nodes.length; i++) + { + int left = Utility.getSignedShort (buffer, ptr); + int right = Utility.getSignedShort (buffer, ptr + 2); + nodes[i] = new Node (left, right); + ptr += 4; + } + + boolean repeating = false; + int lastVal = 0; + int sum = 0; + + while (true) + { + int val = decodeSymbol (); + if (val == EOF_TOKEN) + break; + + if (repeating) + { + repeating = false; + + if (val == 0) // flag indicating a single RLE_DELIMITER + { + lastVal = RLE_DELIMITER; + val = 2; + } + + while (--val != 0) + { + sum += lastVal; + uncompressed[uncPtr++] = (byte) lastVal; + } + } + else + { + if (val == RLE_DELIMITER) + repeating = true; + else + { + lastVal = val; + sum += lastVal; + uncompressed[uncPtr++] = (byte) lastVal; + } + } + } + + if ((sum & 0xFFFF) != fileChecksum) + System.out.printf ("Checksum mismatch : %04X %04X%n", fileChecksum, sum & 0xFFFF); + + byte[] uncompressedBuffer = new byte[uncPtr]; + System.arraycopy (uncompressed, 0, uncompressedBuffer, 0, uncompressedBuffer.length); + + return uncompressedBuffer; + } + + // ---------------------------------------------------------------------------------// + private int decodeSymbol () + // ---------------------------------------------------------------------------------// + { + int val = 0; + + while (true) + { + if (++bitPos > 7) + { + bits = buffer[ptr++]; + bitPos = 0; + } + + val = (bits & 1) == 0 ? nodes[val].left : nodes[val].right; + bits >>>= 1; + + if (val < 0) + return -++val; // increment and make positive + } + } + + // ---------------------------------------------------------------------------------// + record Node (int left, int right) + // ---------------------------------------------------------------------------------// + { + }; +} diff --git a/src/com/bytezone/diskbrowser/utilities/DefaultAction.java b/src/com/bytezone/diskbrowser/utilities/DefaultAction.java index 375cbdf..74cd96a 100644 --- a/src/com/bytezone/diskbrowser/utilities/DefaultAction.java +++ b/src/com/bytezone/diskbrowser/utilities/DefaultAction.java @@ -17,6 +17,7 @@ public abstract class DefaultAction extends AbstractAction // ---------------------------------------------------------------------------------// { super (text); + this.baseURL = null; putValue (Action.SHORT_DESCRIPTION, tip); } @@ -26,6 +27,7 @@ public abstract class DefaultAction extends AbstractAction // ---------------------------------------------------------------------------------// { super (text); + this.baseURL = baseURL; putValue (Action.SHORT_DESCRIPTION, tip); } @@ -39,6 +41,7 @@ public abstract class DefaultAction extends AbstractAction System.out.println ("Base URL not set"); return; } + URL url = this.getClass ().getResource (baseURL + iconName); if (url != null) putValue (iconType, new ImageIcon (url)); diff --git a/src/com/bytezone/diskbrowser/utilities/Utility.java b/src/com/bytezone/diskbrowser/utilities/Utility.java index bedc0b2..4e85bf6 100644 --- a/src/com/bytezone/diskbrowser/utilities/Utility.java +++ b/src/com/bytezone/diskbrowser/utilities/Utility.java @@ -41,7 +41,7 @@ public final class Utility private static MathContext mathContext8 = new MathContext (15); private static final List suffixes = Arrays.asList ("po", "dsk", "do", "hdv", "2mg", - "d13", "sdk", "shk", "bxy", "bny", "woz", "img", "dimg"); + "d13", "sdk", "shk", "bxy", "bny", "bqy", "woz", "img", "dimg"); // ---------------------------------------------------------------------------------// private Utility () @@ -454,6 +454,19 @@ public final class Utility } } + // ---------------------------------------------------------------------------------// + public static String getCString (byte[] buffer, int offset) + // ---------------------------------------------------------------------------------// + { + int length = 0; + int ptr = offset; + + while (buffer[ptr++] != 0) + ++length; + + return new String (buffer, offset, length); + } + // ---------------------------------------------------------------------------------// public static int getWizLong (byte[] buffer, int offset) // ---------------------------------------------------------------------------------//