diff --git a/src/com/webcodepro/applecommander/storage/Disk.java b/src/com/webcodepro/applecommander/storage/Disk.java index c579dff..31f9342 100644 --- a/src/com/webcodepro/applecommander/storage/Disk.java +++ b/src/com/webcodepro/applecommander/storage/Disk.java @@ -138,7 +138,7 @@ public class Disk { new FilenameFilter(textBundle.get("Disk.ApplePcImages"), //$NON-NLS-1$ "*.hdv"), //$NON-NLS-1$ new FilenameFilter(textBundle.get("Disk.CompressedImages"), //$NON-NLS-1$ - ".sdk; *.do.gz; *.dsk.gz; *.po.gz; *.2mg.gz; *.2img.gz"), //$NON-NLS-1$ + "*.sdk; *.shk; *.do.gz; *.dsk.gz; *.po.gz; *.2mg.gz; *.2img.gz"), //$NON-NLS-1$ new FilenameFilter(textBundle.get("Disk.AllFiles"), //$NON-NLS-1$ "*.*") //$NON-NLS-1$ }; @@ -148,6 +148,7 @@ public class Disk { ".po", //$NON-NLS-1$ ".nib", //$NON-NLS-1$ ".sdk", //$NON-NLS-1$ + ".shk", //$NON-NLS-1$ ".2mg", //$NON-NLS-1$ ".2img", //$NON-NLS-1$ ".hdv", //$NON-NLS-1$ @@ -182,6 +183,11 @@ public class Disk { // If we have an SDK, unpack it and send along the byte array diskImage = com.webcodepro.shrinkit.Utilities.unpackSDKFile(filename); diskSize = diskImage.length; + } else if (isSHK()) { + // If we have an SHK, unpack it and send along the byte array + diskImage = com.webcodepro.shrinkit.Utilities.unpackSHKFile(filename); + throw new IOException("SHK unpacking is not implemented yet."); // TODO - remove me + //TODO - diskSize = diskImage.length; } else { File file = new File(filename); diskSize = (int) file.length(); @@ -388,6 +394,14 @@ public class Disk { return filename.toLowerCase().endsWith(".sdk"); //$NON-NLS-1$ } + /** + * Indicate if this disk is a ShrinkIt-compressed package. + */ + public boolean isSHK() + { + return filename.toLowerCase().endsWith(".shk"); //$NON-NLS-1$ + } + /** * Indicate if this disk is ProDOS ordered (beginning with block 0). */ diff --git a/src/com/webcodepro/applecommander/ui/ac.java b/src/com/webcodepro/applecommander/ui/ac.java index 4cd0e6d..99ad02e 100644 --- a/src/com/webcodepro/applecommander/ui/ac.java +++ b/src/com/webcodepro/applecommander/ui/ac.java @@ -151,7 +151,7 @@ public class ac { File file = new File(fileName); if (!file.canRead()) { - throw new IOException("Unable to read input file named "+fileName+"."); + throw new IOException("Unable to read input file named "+fileName+"."); // FIXME - NLS } ByteArrayOutputStream buf = new ByteArrayOutputStream(); byte[] inb = new byte[1024]; @@ -204,7 +204,7 @@ public class ac { } } else - throw new IOException(textBundle.get("CommandLineSDKReadOnly")); + throw new IOException(textBundle.get("CommandLineSDKReadOnly")); //$NON-NLS-1$ } /** diff --git a/src/com/webcodepro/shrinkit/HeaderBlock.java b/src/com/webcodepro/shrinkit/HeaderBlock.java index eb71ba4..618406b 100644 --- a/src/com/webcodepro/shrinkit/HeaderBlock.java +++ b/src/com/webcodepro/shrinkit/HeaderBlock.java @@ -44,7 +44,9 @@ public class HeaderBlock { * the Header Block size varies significantly. */ public HeaderBlock(LittleEndianByteInputStream bs) throws IOException { - bs.checkNuFxId(); + int type = bs.seekFileType(4); + if (type == 0) + throw new IOException("Unable to decode this archive."); // FIXME - NLS headerCrc = bs.readWord(); attribCount = bs.readWord(); versionNumber = bs.readWord(); diff --git a/src/com/webcodepro/shrinkit/MasterHeaderBlock.java b/src/com/webcodepro/shrinkit/MasterHeaderBlock.java index 4758724..efe05db 100644 --- a/src/com/webcodepro/shrinkit/MasterHeaderBlock.java +++ b/src/com/webcodepro/shrinkit/MasterHeaderBlock.java @@ -3,6 +3,7 @@ package com.webcodepro.shrinkit; import java.io.IOException; import java.util.Date; +import com.webcodepro.shrinkit.io.ByteConstants; import com.webcodepro.shrinkit.io.LittleEndianByteInputStream; /** @@ -25,25 +26,24 @@ public class MasterHeaderBlock { private Date archiveModWhen; private int masterVersion; private long masterEof; - private byte[] nuFileId = {0,0,0,0,0,0}; /** * Create the Master Header Block, based on the LittleEndianByteInputStream. */ public MasterHeaderBlock(LittleEndianByteInputStream bs) throws IOException { - int headerOffset = 0; - nuFileId = bs.readBytes(6); + int fileType = 0, headerOffset = 0; + fileType = bs.seekFileType(); - if (checkId(nuFileId,BXY_ID)) { - bs.readBytes(127 - NUFILE_ID.length); + if (fileType == NuFileArchive.BXY_ARCHIVE) { + bs.readBytes(127 - ByteConstants.NUFILE_ID.length); headerOffset = 128; int count = bs.read(); if (count != 0) - throw new IOException("This is actually a Binary II archive with multiple files in it."); - nuFileId = bs.readBytes(6); + throw new IOException("This is actually a Binary II archive with multiple files in it."); // FIXME - NLS + fileType = bs.seekFileType(); } - if (!checkId(nuFileId,NUFILE_ID)) { - throw new IOException("Unable to decode this archive."); + if (!(fileType == NuFileArchive.NUFILE_ARCHIVE)) { + throw new IOException("Unable to decode this archive."); // FIXME - NLS } masterCrc = bs.readWord(); bs.resetCrc(); // CRC is computed from this point to the end of the header @@ -105,22 +105,4 @@ public class MasterHeaderBlock { public boolean isValidCrc() { return validCrc; } - /** - * Test that the requested constant is present. - */ - private boolean checkId(byte[] data, byte[] constant) { - for (int i = 0; i < constant.length; i++){ - if (data[i] != constant[i]) - return false; - } - return true; - } - - /** Master Header Block identifier "magic" bytes. */ - public static final byte[] NUFILE_ID = { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 }; - /** Header Block identifier "magic" bytes. */ - public static final byte[] NUFX_ID = { 0x4e, (byte)0xf5, 0x46, (byte)0xd8 }; - /** Binay II identifier "magic" bytes. */ - public static final byte[] BXY_ID = { 0x0a, 0x47, 0x4c }; - } diff --git a/src/com/webcodepro/shrinkit/NuFileArchive.java b/src/com/webcodepro/shrinkit/NuFileArchive.java index 21ed43e..371156d 100644 --- a/src/com/webcodepro/shrinkit/NuFileArchive.java +++ b/src/com/webcodepro/shrinkit/NuFileArchive.java @@ -15,7 +15,14 @@ import com.webcodepro.shrinkit.io.LittleEndianByteInputStream; public class NuFileArchive { private MasterHeaderBlock master; private List headers; - + + /** + * Need to enumerate some basic sub-types of archives. + */ + public static final int NUFILE_ARCHIVE = 1; + public static final int NUFX_ARCHIVE = 2; + public static final int BXY_ARCHIVE = 3; + /** * Read in the NuFile/NuFX/Shrinkit archive. */ diff --git a/src/com/webcodepro/shrinkit/Utilities.java b/src/com/webcodepro/shrinkit/Utilities.java index 15a05ef..64b9eb1 100644 --- a/src/com/webcodepro/shrinkit/Utilities.java +++ b/src/com/webcodepro/shrinkit/Utilities.java @@ -66,12 +66,57 @@ public class Utilities System.out.println(ex); } } - dataThread.readThreadData(new LittleEndianByteInputStream(dataThread.getRawInputStream())); - InputStream fis = dataThread.getInputStream(); - int dmgLen = (int)(dataThread.getThreadEof()); - buffer = new byte[dmgLen]; - fis.read(buffer,0,dmgLen); - fis.close(); + if (null != dataThread) { + dataThread.readThreadData(new LittleEndianByteInputStream(dataThread.getRawInputStream())); + InputStream fis = dataThread.getInputStream(); + int dmgLen = (int)(dataThread.getThreadEof()); + buffer = new byte[dmgLen]; + fis.read(buffer,0,dmgLen); + fis.close(); + } + } + return buffer; + } + + /** + * Interpret a SHK NuFile/NuFX/Shrinkit archive as a full disk image. + * + * @return byte[] buffer containing full disk of data; null if unable to read + * @throws IllegalArgumentException if the filename is not able to be read + * @throws IOException the file has some malformed-ness about it + */ + public static byte[] unpackSHKFile(String fileName) throws IOException { + TextBundle textBundle = StorageBundle.getInstance(); + byte buffer[] = null; + ThreadRecord dataThread = null; + File file = new File(fileName); + if (file.isDirectory() || !file.canRead()) { + throw new IllegalArgumentException(textBundle.format("NotAFile", fileName, 1)); //$NON-NLS-1$ + } + InputStream is = new FileInputStream(file); + NuFileArchive a = new NuFileArchive(is); + for (HeaderBlock b : a.getHeaderBlocks()) { + for (ThreadRecord r : b.getThreadRecords()) { + try + { + if (r.getThreadKind() == ThreadKind.DISK_IMAGE) + { + dataThread = r; + } + } + catch (Exception ex) + { + System.out.println(ex); + } + } + if (null != dataThread) { + dataThread.readThreadData(new LittleEndianByteInputStream(dataThread.getRawInputStream())); + InputStream fis = dataThread.getInputStream(); + int dmgLen = (int)(dataThread.getThreadEof()); + buffer = new byte[dmgLen]; + fis.read(buffer,0,dmgLen); + fis.close(); + } } return buffer; } diff --git a/src/com/webcodepro/shrinkit/io/ByteConstants.java b/src/com/webcodepro/shrinkit/io/ByteConstants.java index d1a96d6..e1c7e1a 100644 --- a/src/com/webcodepro/shrinkit/io/ByteConstants.java +++ b/src/com/webcodepro/shrinkit/io/ByteConstants.java @@ -13,6 +13,8 @@ public interface ByteConstants { public static final byte[] NUFILE_ID = { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 }; /** Header Block identifier "magic" bytes. */ public static final byte[] NUFX_ID = { 0x4e, (byte)0xf5, 0x46, (byte)0xd8 }; + /** Binary II identifier "magic" bytes. */ + public static final byte[] BXY_ID = { 0x0a, 0x47, 0x4c }; /** Apple IIgs Toolbox TimeRec seconds byte position. */ public static final int TIMEREC_SECOND = 0; /** Apple IIgs Toolbox TimeRec seconds byte position. */ diff --git a/src/com/webcodepro/shrinkit/io/LittleEndianByteInputStream.java b/src/com/webcodepro/shrinkit/io/LittleEndianByteInputStream.java index 5280c0d..05dc684 100644 --- a/src/com/webcodepro/shrinkit/io/LittleEndianByteInputStream.java +++ b/src/com/webcodepro/shrinkit/io/LittleEndianByteInputStream.java @@ -8,6 +8,7 @@ import java.util.Date; import java.util.GregorianCalendar; import com.webcodepro.shrinkit.CRC16; +import com.webcodepro.shrinkit.NuFileArchive; /** * A simple class to hide the source of byte data. @@ -69,18 +70,49 @@ public class LittleEndianByteInputStream extends InputStream implements ByteCons } /** - * Test that the NuFile id is embedded in the LittleEndianByteInputStream. + * Test the beginning of the data stream for a magic signature, for up to a total + * of 2k bytes of leading garbage */ - public boolean checkNuFileId() throws IOException { - byte[] data = readBytes(6); - return Arrays.equals(data, NUFILE_ID); + public int seekFileType() throws IOException { + return seekFileType(6); } /** - * Test that the NuFx id is embedded in the LittleEndianByteInputStream. + * Test the beginning of the data stream for a magic signature, specifying the + * maximum size of a signature to test for */ - public boolean checkNuFxId() throws IOException { - byte[] data = readBytes(4); - return Arrays.equals(data, NUFX_ID); + public int seekFileType(int max) throws IOException { + byte[] data = new byte[2048]; + byte[] testNUFILE = new byte[6]; + byte[] testNUFX = new byte[4]; + byte[] testBXY = new byte[3]; + int type = 0, i, pos = 0; + for (i = 0;i