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$
"*.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).
*/

View File

@ -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$
}
/**

View File

@ -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();

View File

@ -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 };
}

View File

@ -15,7 +15,14 @@ import com.webcodepro.shrinkit.io.LittleEndianByteInputStream;
public class NuFileArchive {
private MasterHeaderBlock master;
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.
*/

View File

@ -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;
}

View File

@ -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. */

View File

@ -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<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.