Get started on SHK unpacking

ShrinkIt fixes - needed new ways to seek the header magic signatures
This commit is contained in:
2012-07-26 21:00:58 +00:00
parent a79df8f3bc
commit 37e7476bfb
8 changed files with 130 additions and 46 deletions

View File

@ -138,7 +138,7 @@ public class Disk {
new FilenameFilter(textBundle.get("Disk.ApplePcImages"), //$NON-NLS-1$ new FilenameFilter(textBundle.get("Disk.ApplePcImages"), //$NON-NLS-1$
"*.hdv"), //$NON-NLS-1$ "*.hdv"), //$NON-NLS-1$
new FilenameFilter(textBundle.get("Disk.CompressedImages"), //$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$ new FilenameFilter(textBundle.get("Disk.AllFiles"), //$NON-NLS-1$
"*.*") //$NON-NLS-1$ "*.*") //$NON-NLS-1$
}; };
@ -148,6 +148,7 @@ public class Disk {
".po", //$NON-NLS-1$ ".po", //$NON-NLS-1$
".nib", //$NON-NLS-1$ ".nib", //$NON-NLS-1$
".sdk", //$NON-NLS-1$ ".sdk", //$NON-NLS-1$
".shk", //$NON-NLS-1$
".2mg", //$NON-NLS-1$ ".2mg", //$NON-NLS-1$
".2img", //$NON-NLS-1$ ".2img", //$NON-NLS-1$
".hdv", //$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 // If we have an SDK, unpack it and send along the byte array
diskImage = com.webcodepro.shrinkit.Utilities.unpackSDKFile(filename); diskImage = com.webcodepro.shrinkit.Utilities.unpackSDKFile(filename);
diskSize = diskImage.length; 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 { } else {
File file = new File(filename); File file = new File(filename);
diskSize = (int) file.length(); diskSize = (int) file.length();
@ -388,6 +394,14 @@ public class Disk {
return filename.toLowerCase().endsWith(".sdk"); //$NON-NLS-1$ 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). * Indicate if this disk is ProDOS ordered (beginning with block 0).
*/ */

View File

@ -151,7 +151,7 @@ public class ac {
File file = new File(fileName); File file = new File(fileName);
if (!file.canRead()) 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(); ByteArrayOutputStream buf = new ByteArrayOutputStream();
byte[] inb = new byte[1024]; byte[] inb = new byte[1024];
@ -204,7 +204,7 @@ public class ac {
} }
} }
else else
throw new IOException(textBundle.get("CommandLineSDKReadOnly")); throw new IOException(textBundle.get("CommandLineSDKReadOnly")); //$NON-NLS-1$
} }
/** /**

View File

@ -44,7 +44,9 @@ public class HeaderBlock {
* the Header Block size varies significantly. * the Header Block size varies significantly.
*/ */
public HeaderBlock(LittleEndianByteInputStream bs) throws IOException { 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(); headerCrc = bs.readWord();
attribCount = bs.readWord(); attribCount = bs.readWord();
versionNumber = bs.readWord(); versionNumber = bs.readWord();

View File

@ -3,6 +3,7 @@ package com.webcodepro.shrinkit;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
import com.webcodepro.shrinkit.io.ByteConstants;
import com.webcodepro.shrinkit.io.LittleEndianByteInputStream; import com.webcodepro.shrinkit.io.LittleEndianByteInputStream;
/** /**
@ -25,25 +26,24 @@ public class MasterHeaderBlock {
private Date archiveModWhen; private Date archiveModWhen;
private int masterVersion; private int masterVersion;
private long masterEof; private long masterEof;
private byte[] nuFileId = {0,0,0,0,0,0};
/** /**
* Create the Master Header Block, based on the LittleEndianByteInputStream. * Create the Master Header Block, based on the LittleEndianByteInputStream.
*/ */
public MasterHeaderBlock(LittleEndianByteInputStream bs) throws IOException { public MasterHeaderBlock(LittleEndianByteInputStream bs) throws IOException {
int headerOffset = 0; int fileType = 0, headerOffset = 0;
nuFileId = bs.readBytes(6); fileType = bs.seekFileType();
if (checkId(nuFileId,BXY_ID)) { if (fileType == NuFileArchive.BXY_ARCHIVE) {
bs.readBytes(127 - NUFILE_ID.length); bs.readBytes(127 - ByteConstants.NUFILE_ID.length);
headerOffset = 128; headerOffset = 128;
int count = bs.read(); int count = bs.read();
if (count != 0) if (count != 0)
throw new IOException("This is actually a Binary II archive with multiple files in it."); throw new IOException("This is actually a Binary II archive with multiple files in it."); // FIXME - NLS
nuFileId = bs.readBytes(6); fileType = bs.seekFileType();
} }
if (!checkId(nuFileId,NUFILE_ID)) { if (!(fileType == NuFileArchive.NUFILE_ARCHIVE)) {
throw new IOException("Unable to decode this archive."); throw new IOException("Unable to decode this archive."); // FIXME - NLS
} }
masterCrc = bs.readWord(); masterCrc = bs.readWord();
bs.resetCrc(); // CRC is computed from this point to the end of the header bs.resetCrc(); // CRC is computed from this point to the end of the header
@ -105,22 +105,4 @@ public class MasterHeaderBlock {
public boolean isValidCrc() { public boolean isValidCrc() {
return validCrc; 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 };
} }

View File

@ -15,7 +15,14 @@ import com.webcodepro.shrinkit.io.LittleEndianByteInputStream;
public class NuFileArchive { public class NuFileArchive {
private MasterHeaderBlock master; private MasterHeaderBlock master;
private List<HeaderBlock> headers; private List<HeaderBlock> 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. * Read in the NuFile/NuFX/Shrinkit archive.
*/ */

View File

@ -66,12 +66,57 @@ public class Utilities
System.out.println(ex); System.out.println(ex);
} }
} }
dataThread.readThreadData(new LittleEndianByteInputStream(dataThread.getRawInputStream())); if (null != dataThread) {
InputStream fis = dataThread.getInputStream(); dataThread.readThreadData(new LittleEndianByteInputStream(dataThread.getRawInputStream()));
int dmgLen = (int)(dataThread.getThreadEof()); InputStream fis = dataThread.getInputStream();
buffer = new byte[dmgLen]; int dmgLen = (int)(dataThread.getThreadEof());
fis.read(buffer,0,dmgLen); buffer = new byte[dmgLen];
fis.close(); 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; return buffer;
} }

View File

@ -13,6 +13,8 @@ public interface ByteConstants {
public static final byte[] NUFILE_ID = { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 }; public static final byte[] NUFILE_ID = { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 };
/** Header Block identifier "magic" bytes. */ /** Header Block identifier "magic" bytes. */
public static final byte[] NUFX_ID = { 0x4e, (byte)0xf5, 0x46, (byte)0xd8 }; 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. */ /** Apple IIgs Toolbox TimeRec seconds byte position. */
public static final int TIMEREC_SECOND = 0; public static final int TIMEREC_SECOND = 0;
/** Apple IIgs Toolbox TimeRec seconds byte position. */ /** Apple IIgs Toolbox TimeRec seconds byte position. */

View File

@ -8,6 +8,7 @@ import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import com.webcodepro.shrinkit.CRC16; import com.webcodepro.shrinkit.CRC16;
import com.webcodepro.shrinkit.NuFileArchive;
/** /**
* A simple class to hide the source of byte data. * 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 { public int seekFileType() throws IOException {
byte[] data = readBytes(6); return seekFileType(6);
return Arrays.equals(data, NUFILE_ID);
} }
/** /**
* 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 { public int seekFileType(int max) throws IOException {
byte[] data = readBytes(4); byte[] data = new byte[2048];
return Arrays.equals(data, NUFX_ID); 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<data.length;i++) {
data[i] = 0;
}
for (i = 0; i < max; i++) {
data[i] = (byte)readByte();
}
while (pos < data.length-max) {
if (max == 6) {
System.arraycopy(data, pos, testNUFILE, 0, NUFILE_ID.length);
if (Arrays.equals(testNUFILE,NUFILE_ID)) {
type = NuFileArchive.NUFILE_ARCHIVE;
break;
}
}
System.arraycopy(data, pos, testNUFX, 0, NUFX_ID.length);
System.arraycopy(data, pos, testBXY, 0, BXY_ID.length);
if (Arrays.equals(testNUFX, NUFX_ID)) {
type = NuFileArchive.NUFX_ARCHIVE;
break;
} else if (Arrays.equals(testBXY,BXY_ID)) {
type = NuFileArchive.BXY_ARCHIVE;
break;
}
data[pos+max] = (byte)readByte();
pos++;
}
return type;
} }
/** /**
* Read the two bytes in as a "Word" which needs to be stored as a Java int. * Read the two bytes in as a "Word" which needs to be stored as a Java int.