mirror of
https://github.com/AppleCommander/ShrinkItArchive.git
synced 2024-12-22 02:31:00 +00:00
Initial commit. Reads basic NuFile/NuFX archive format. Does not handle CRC-16, does not write, does not handle compressed threads.
This commit is contained in:
commit
68a5b3a5f5
8
.classpath
Normal file
8
.classpath
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="test_src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
17
.project
Normal file
17
.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ShrinkItArchive</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
BIN
samples/Joystick.SHK
Normal file
BIN
samples/Joystick.SHK
Normal file
Binary file not shown.
BIN
samples/Scc.shk
Normal file
BIN
samples/Scc.shk
Normal file
Binary file not shown.
33
src/com/webcodepro/shrinkit/ByteConstants.java
Normal file
33
src/com/webcodepro/shrinkit/ByteConstants.java
Normal file
@ -0,0 +1,33 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
/**
|
||||
* Provides constants for the ByteSource and ByteTarget classes.
|
||||
*
|
||||
* @author robgreene@users.sourceforge.net
|
||||
* @see ByteSource
|
||||
* @see ByteTarget
|
||||
*/
|
||||
public interface ByteConstants {
|
||||
/** 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 };
|
||||
/** Apple IIgs Toolbox TimeRec seconds byte position. */
|
||||
public static final int TIMEREC_SECOND = 0;
|
||||
/** Apple IIgs Toolbox TimeRec seconds byte position. */
|
||||
public static final int TIMEREC_MINUTE = 1;
|
||||
/** Apple IIgs Toolbox TimeRec minutes byte position. */
|
||||
public static final int TIMEREC_HOUR = 2;
|
||||
/** Apple IIgs Toolbox TimeRec hours byte position. */
|
||||
public static final int TIMEREC_YEAR = 3;
|
||||
/** Apple IIgs Toolbox TimeRec year byte position. */
|
||||
public static final int TIMEREC_DAY = 4;
|
||||
/** Apple IIgs Toolbox TimeRec day byte position. */
|
||||
public static final int TIMEREC_MONTH = 5;
|
||||
/** Apple IIgs Toolbox TimeRec weekday (Mon, Tue, etc) byte position. */
|
||||
public static final int TIMEREC_WEEKDAY = 7;
|
||||
/** Apple IIgs Toolbox TimeRec length. */
|
||||
public static final int TIMEREC_LENGTH = 8;
|
||||
/** A null TimeRec */
|
||||
public static final byte[] TIMEREC_NULL = new byte[TIMEREC_LENGTH];
|
||||
}
|
105
src/com/webcodepro/shrinkit/ByteSource.java
Normal file
105
src/com/webcodepro/shrinkit/ByteSource.java
Normal file
@ -0,0 +1,105 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
/**
|
||||
* A simple class to hide the source of byte data.
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public class ByteSource implements ByteConstants {
|
||||
private InputStream inputStream;
|
||||
|
||||
/**
|
||||
* Construct a ByteSource from an InputStream.
|
||||
*/
|
||||
public ByteSource(InputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
/**
|
||||
* Construct a ByteSource from a byte array.
|
||||
*/
|
||||
public ByteSource(byte[] data) {
|
||||
this.inputStream = new ByteArrayInputStream(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next byte.
|
||||
* Returns -1 if at end of input.
|
||||
* Note that an unsigned byte needs to be returned in a larger container (ie, a short or int or long).
|
||||
*/
|
||||
public int read() throws IOException {
|
||||
return inputStream.read();
|
||||
}
|
||||
/**
|
||||
* Get the next byte and fail if we are at EOF.
|
||||
* Note that an unsigned byte needs to be returned in a larger container (ie, a short or int or long).
|
||||
*/
|
||||
public int readByte() throws IOException {
|
||||
int i = read();
|
||||
if (i == -1) throw new IOException("Expecting a byte but at EOF");
|
||||
return i;
|
||||
}
|
||||
/**
|
||||
* Get the next set of bytes as an array.
|
||||
* If EOF encountered, an IOException is thrown.
|
||||
*/
|
||||
public byte[] readBytes(int bytes) throws IOException {
|
||||
byte[] data = new byte[bytes];
|
||||
int read = inputStream.read(data);
|
||||
if (read < bytes) {
|
||||
throw new IOException("Requested " + bytes + " bytes, but " + read + " read");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the NuFile id is embedded in the ByteSource.
|
||||
*/
|
||||
public boolean checkNuFileId() throws IOException {
|
||||
byte[] data = readBytes(6);
|
||||
return Arrays.equals(data, NUFILE_ID);
|
||||
}
|
||||
/**
|
||||
* Test that the NuFx id is embedded in the ByteSource.
|
||||
*/
|
||||
public boolean checkNuFxId() throws IOException {
|
||||
byte[] data = readBytes(4);
|
||||
return Arrays.equals(data, NUFX_ID);
|
||||
}
|
||||
/**
|
||||
* Read the two bytes in as a "Word" which needs to be stored as a Java int.
|
||||
*/
|
||||
public int readWord() throws IOException {
|
||||
return (readByte() | readByte() << 8) & 0xffff;
|
||||
}
|
||||
/**
|
||||
* Read the two bytes in as a "Long" which needs to be stored as a Java long.
|
||||
*/
|
||||
public long readLong() throws IOException {
|
||||
long a = readByte();
|
||||
long b = readByte();
|
||||
long c = readByte();
|
||||
long d = readByte();
|
||||
return (long)(a | b<<8 | c<<16 | d<<24);
|
||||
}
|
||||
/**
|
||||
* Read the TimeRec into a Java Date object.
|
||||
* Note that years 1900-1939 are assumed to be 2000-2039 per the NuFX addendum
|
||||
* at http://www.nulib.com/library/nufx-addendum.htm.
|
||||
* @see http://www.nulib.com/library/nufx-addendum.htm
|
||||
*/
|
||||
public Date readDate() throws IOException {
|
||||
byte[] data = readBytes(TIMEREC_LENGTH);
|
||||
if (Arrays.equals(TIMEREC_NULL, data)) return null;
|
||||
int year = data[TIMEREC_YEAR]+1900;
|
||||
if (year < 1940) year+= 100;
|
||||
GregorianCalendar gc = new GregorianCalendar(year, data[TIMEREC_MONTH]-1, data[TIMEREC_DAY],
|
||||
data[TIMEREC_HOUR], data[TIMEREC_MINUTE], data[TIMEREC_SECOND]);
|
||||
return gc.getTime();
|
||||
}
|
||||
}
|
200
src/com/webcodepro/shrinkit/HeaderBlock.java
Normal file
200
src/com/webcodepro/shrinkit/HeaderBlock.java
Normal file
@ -0,0 +1,200 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Header Block contains information and content
|
||||
* about a single entry (be it a file or disk image).
|
||||
* <p>
|
||||
* Note that we need to support multiple versions of the NuFX
|
||||
* archive format. Some details may be invalid, depending on
|
||||
* version, and those are documented in the getter methods.
|
||||
*
|
||||
* @author robgreene@users.sourceforge.net
|
||||
* @see http://www.nulib.com/library/FTN.e08002.htm
|
||||
*/
|
||||
public class HeaderBlock {
|
||||
private int headerCrc;
|
||||
private int attribCount;
|
||||
private int versionNumber;
|
||||
private long totalThreads;
|
||||
private int fileSysId;
|
||||
private int fileSysInfo;
|
||||
private long access;
|
||||
private long fileType;
|
||||
private long extraType;
|
||||
private int storageType;
|
||||
private Date createWhen;
|
||||
private Date modWhen;
|
||||
private Date archiveWhen;
|
||||
private int optionSize;
|
||||
private byte[] optionListBytes;
|
||||
private byte[] attribBytes;
|
||||
private String filename;
|
||||
private List<ThreadRecord> threads;
|
||||
|
||||
/**
|
||||
* Create the Header Block. This is done dynamically since
|
||||
* the Header Block size varies significantly.
|
||||
*/
|
||||
public HeaderBlock(ByteSource bs) throws IOException {
|
||||
bs.checkNuFxId();
|
||||
headerCrc = bs.readWord();
|
||||
attribCount = bs.readWord();
|
||||
versionNumber = bs.readWord();
|
||||
totalThreads = bs.readLong();
|
||||
fileSysId = bs.readWord();
|
||||
fileSysInfo = bs.readWord();
|
||||
access = bs.readLong();
|
||||
fileType = bs.readLong();
|
||||
extraType = bs.readLong();
|
||||
storageType = bs.readWord();
|
||||
createWhen = bs.readDate();
|
||||
modWhen = bs.readDate();
|
||||
archiveWhen = bs.readDate();
|
||||
// Read the mysterious option_list
|
||||
if (versionNumber >= 1) {
|
||||
optionSize = bs.readWord();
|
||||
if (optionSize > 0) {
|
||||
optionListBytes = bs.readBytes(optionSize-2);
|
||||
}
|
||||
}
|
||||
// Compute attribute bytes that exist and read (if needed)
|
||||
int sizeofAttrib = attribCount - 58;
|
||||
if (versionNumber >= 1) {
|
||||
if (optionSize == 0) sizeofAttrib -= 2;
|
||||
else sizeofAttrib -= optionSize;
|
||||
}
|
||||
if (sizeofAttrib > 0) {
|
||||
attribBytes = bs.readBytes(sizeofAttrib);
|
||||
}
|
||||
// Read the (defunct) filename
|
||||
int length = bs.readWord();
|
||||
if (length > 0) {
|
||||
filename = new String(bs.readBytes(length));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Read in all data threads. All ThreadRecords are read and then
|
||||
* each thread's data is read (per NuFX spec).
|
||||
*/
|
||||
public void readThreads(ByteSource bs) throws IOException {
|
||||
threads = new ArrayList<ThreadRecord>();
|
||||
for (long l=0; l<totalThreads; l++) threads.add(new ThreadRecord(bs));
|
||||
for (ThreadRecord r : threads) r.readThreadData(bs);
|
||||
}
|
||||
|
||||
// GENERATED CODE
|
||||
|
||||
public int getHeaderCrc() {
|
||||
return headerCrc;
|
||||
}
|
||||
public void setHeaderCrc(int headerCrc) {
|
||||
this.headerCrc = headerCrc;
|
||||
}
|
||||
public int getAttribCount() {
|
||||
return attribCount;
|
||||
}
|
||||
public void setAttribCount(int attribCount) {
|
||||
this.attribCount = attribCount;
|
||||
}
|
||||
public int getVersionNumber() {
|
||||
return versionNumber;
|
||||
}
|
||||
public void setVersionNumber(int versionNumber) {
|
||||
this.versionNumber = versionNumber;
|
||||
}
|
||||
public long getTotalThreads() {
|
||||
return totalThreads;
|
||||
}
|
||||
public void setTotalThreads(long totalThreads) {
|
||||
this.totalThreads = totalThreads;
|
||||
}
|
||||
public int getFileSysId() {
|
||||
return fileSysId;
|
||||
}
|
||||
public void setFileSysId(int fileSysId) {
|
||||
this.fileSysId = fileSysId;
|
||||
}
|
||||
public int getFileSysInfo() {
|
||||
return fileSysInfo;
|
||||
}
|
||||
public void setFileSysInfo(int fileSysInfo) {
|
||||
this.fileSysInfo = fileSysInfo;
|
||||
}
|
||||
public long getAccess() {
|
||||
return access;
|
||||
}
|
||||
public void setAccess(long access) {
|
||||
this.access = access;
|
||||
}
|
||||
public long getFileType() {
|
||||
return fileType;
|
||||
}
|
||||
public void setFileType(long fileType) {
|
||||
this.fileType = fileType;
|
||||
}
|
||||
public long getExtraType() {
|
||||
return extraType;
|
||||
}
|
||||
public void setExtraType(long extraType) {
|
||||
this.extraType = extraType;
|
||||
}
|
||||
public int getStorageType() {
|
||||
return storageType;
|
||||
}
|
||||
public void setStorageType(int storageType) {
|
||||
this.storageType = storageType;
|
||||
}
|
||||
public Date getCreateWhen() {
|
||||
return createWhen;
|
||||
}
|
||||
public void setCreateWhen(Date createWhen) {
|
||||
this.createWhen = createWhen;
|
||||
}
|
||||
public Date getModWhen() {
|
||||
return modWhen;
|
||||
}
|
||||
public void setModWhen(Date modWhen) {
|
||||
this.modWhen = modWhen;
|
||||
}
|
||||
public Date getArchiveWhen() {
|
||||
return archiveWhen;
|
||||
}
|
||||
public void setArchiveWhen(Date archiveWhen) {
|
||||
this.archiveWhen = archiveWhen;
|
||||
}
|
||||
public int getOptionSize() {
|
||||
return optionSize;
|
||||
}
|
||||
public void setOptionSize(int optionSize) {
|
||||
this.optionSize = optionSize;
|
||||
}
|
||||
public byte[] getOptionListBytes() {
|
||||
return optionListBytes;
|
||||
}
|
||||
public void setOptionListBytes(byte[] optionListBytes) {
|
||||
this.optionListBytes = optionListBytes;
|
||||
}
|
||||
public byte[] getAttribBytes() {
|
||||
return attribBytes;
|
||||
}
|
||||
public void setAttribBytes(byte[] attribBytes) {
|
||||
this.attribBytes = attribBytes;
|
||||
}
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
public void setFilename(String filename) {
|
||||
this.filename = filename;
|
||||
}
|
||||
public List<ThreadRecord> getThreadRecords() {
|
||||
return threads;
|
||||
}
|
||||
public void setThreadRecords(List<ThreadRecord> threads) {
|
||||
this.threads = threads;
|
||||
}
|
||||
}
|
87
src/com/webcodepro/shrinkit/MasterHeaderBlock.java
Normal file
87
src/com/webcodepro/shrinkit/MasterHeaderBlock.java
Normal file
@ -0,0 +1,87 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* The Master Header Block contains information about the entire
|
||||
* ShrinkIt archive.
|
||||
* <p>
|
||||
* Note that we need to support multiple versions of the NuFX
|
||||
* archive format. Some details may be invalid, depending on
|
||||
* version, and those are documented in the getter methods.
|
||||
*
|
||||
* @author robgreene@users.sourceforge.net
|
||||
* @see http://www.nulib.com/library/FTN.e08002.htm
|
||||
*/
|
||||
public class MasterHeaderBlock {
|
||||
private static final int MASTER_HEADER_LENGTH = 48;
|
||||
private int masterCrc;
|
||||
private long totalRecords;
|
||||
private Date archiveCreateWhen;
|
||||
private Date archiveModWhen;
|
||||
private int masterVersion;
|
||||
private long masterEof;
|
||||
|
||||
/**
|
||||
* Create the Master Header Block, based on the ByteSource.
|
||||
* To avoid byte counting, we read in the fixed size header
|
||||
* and then work our way through the data. When we are done,
|
||||
* that data is thrown away, and we don't need to ensure
|
||||
* that we've read a consistent number of bytes.
|
||||
*/
|
||||
public MasterHeaderBlock(ByteSource bs) throws IOException {
|
||||
bs = new ByteSource(bs.readBytes(MASTER_HEADER_LENGTH));
|
||||
bs.checkNuFileId();
|
||||
masterCrc = bs.readWord();
|
||||
totalRecords = bs.readLong();
|
||||
archiveCreateWhen = bs.readDate();
|
||||
archiveModWhen = bs.readDate();
|
||||
masterVersion = bs.readWord();
|
||||
if (masterVersion > 0) {
|
||||
bs.readBytes(8); // documented to be null, but we don't care
|
||||
masterEof = bs.readLong();
|
||||
} else {
|
||||
masterEof = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// GENERATED CODE
|
||||
|
||||
public int getMasterCrc() {
|
||||
return masterCrc;
|
||||
}
|
||||
public void setMasterCrc(int masterCrc) {
|
||||
this.masterCrc = masterCrc;
|
||||
}
|
||||
public long getTotalRecords() {
|
||||
return totalRecords;
|
||||
}
|
||||
public void setTotalRecords(long totalRecords) {
|
||||
this.totalRecords = totalRecords;
|
||||
}
|
||||
public Date getArchiveCreateWhen() {
|
||||
return archiveCreateWhen;
|
||||
}
|
||||
public void setArchiveCreateWhen(Date archiveCreateWhen) {
|
||||
this.archiveCreateWhen = archiveCreateWhen;
|
||||
}
|
||||
public Date getArchiveModWhen() {
|
||||
return archiveModWhen;
|
||||
}
|
||||
public void setArchiveModWhen(Date archiveModWhen) {
|
||||
this.archiveModWhen = archiveModWhen;
|
||||
}
|
||||
public int getMasterVersion() {
|
||||
return masterVersion;
|
||||
}
|
||||
public void setMasterVersion(int masterVersion) {
|
||||
this.masterVersion = masterVersion;
|
||||
}
|
||||
public long getMasterEof() {
|
||||
return masterEof;
|
||||
}
|
||||
public void setMasterEof(long masterEof) {
|
||||
this.masterEof = masterEof;
|
||||
}
|
||||
}
|
37
src/com/webcodepro/shrinkit/NuFileArchive.java
Normal file
37
src/com/webcodepro/shrinkit/NuFileArchive.java
Normal file
@ -0,0 +1,37 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Basic reading of a NuFX archive.
|
||||
*
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public class NuFileArchive {
|
||||
private MasterHeaderBlock master;
|
||||
private List<HeaderBlock> headers;
|
||||
|
||||
/**
|
||||
* Read in the NuFile/NuFX/Shrinkit archive.
|
||||
*/
|
||||
public NuFileArchive(InputStream inputStream) throws IOException {
|
||||
ByteSource bs = new ByteSource(inputStream);
|
||||
master = new MasterHeaderBlock(bs);
|
||||
headers = new ArrayList<HeaderBlock>();
|
||||
for (int i=0; i<master.getTotalRecords(); i++) {
|
||||
HeaderBlock header = new HeaderBlock(bs);
|
||||
header.readThreads(bs);
|
||||
headers.add(header);
|
||||
}
|
||||
}
|
||||
|
||||
public MasterHeaderBlock getMasterHeaderBlock() {
|
||||
return master;
|
||||
}
|
||||
public List<HeaderBlock> getHeaderBlocks() {
|
||||
return headers;
|
||||
}
|
||||
}
|
24
src/com/webcodepro/shrinkit/ThreadClass.java
Normal file
24
src/com/webcodepro/shrinkit/ThreadClass.java
Normal file
@ -0,0 +1,24 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
/**
|
||||
* Define and decode the thread_class field.
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public enum ThreadClass {
|
||||
MESSAGE, CONTROL, DATA, FILENAME;
|
||||
|
||||
/**
|
||||
* Find the given ThreadClass.
|
||||
* @throws IllegalArgumentException if the thread_class is unknown
|
||||
*/
|
||||
public static ThreadClass find(int threadClass) {
|
||||
switch (threadClass) {
|
||||
case 0x0000: return MESSAGE;
|
||||
case 0x0001: return CONTROL;
|
||||
case 0x0002: return DATA;
|
||||
case 0x0003: return FILENAME;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown thread_class of " + threadClass);
|
||||
}
|
||||
}
|
||||
}
|
27
src/com/webcodepro/shrinkit/ThreadFormat.java
Normal file
27
src/com/webcodepro/shrinkit/ThreadFormat.java
Normal file
@ -0,0 +1,27 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
/**
|
||||
* Define and decode the thread_format field.
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public enum ThreadFormat {
|
||||
UNCOMPRESSED, HUFFMAN_SQUEEZE, DYNAMIC_LZW1, DYNAMIC_LZW2,
|
||||
UNIX_12BIT_COMPRESS, UNIX_16BIT_COMPRESS;
|
||||
|
||||
/**
|
||||
* Find the ThreadFormat.
|
||||
* @throws IllegalArgumentException if the thread_format is unknown
|
||||
*/
|
||||
public static ThreadFormat find(int threadFormat) {
|
||||
switch (threadFormat) {
|
||||
case 0x0000: return UNCOMPRESSED;
|
||||
case 0x0001: return HUFFMAN_SQUEEZE;
|
||||
case 0x0002: return DYNAMIC_LZW1;
|
||||
case 0x0003: return DYNAMIC_LZW2;
|
||||
case 0x0004: return UNIX_12BIT_COMPRESS;
|
||||
case 0x0005: return UNIX_16BIT_COMPRESS;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown thread_format of " + threadFormat);
|
||||
}
|
||||
}
|
||||
}
|
41
src/com/webcodepro/shrinkit/ThreadKind.java
Normal file
41
src/com/webcodepro/shrinkit/ThreadKind.java
Normal file
@ -0,0 +1,41 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
/**
|
||||
* Define and decode the thread_kind field.
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public enum ThreadKind {
|
||||
ASCII_TEXT, ALLOCATED_SPACE, APPLE_IIGS_ICON, CREATE_DIRECTORY, DATA_FORK, DISK_IMAGE, RESOURCE_FORK,
|
||||
FILENAME;
|
||||
|
||||
/**
|
||||
* Find the specific ThreadKind.
|
||||
* @throws IllegalArgumentException when the thread_kind cannot be determined
|
||||
*/
|
||||
public static ThreadKind find(int threadKind, ThreadClass threadClass) {
|
||||
switch (threadClass) {
|
||||
case MESSAGE:
|
||||
switch (threadKind) {
|
||||
case 0x0000: return ASCII_TEXT;
|
||||
case 0x0001: return ALLOCATED_SPACE;
|
||||
case 0x0002: return APPLE_IIGS_ICON;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown thread_kind for message thread_class of " + threadKind);
|
||||
case CONTROL:
|
||||
if (threadKind == 0x0000) return CREATE_DIRECTORY;
|
||||
throw new IllegalArgumentException("Unknown thread_kind for control thread_class of " + threadKind);
|
||||
case DATA:
|
||||
switch (threadKind) {
|
||||
case 0x0000: return DATA_FORK;
|
||||
case 0x0001: return DISK_IMAGE;
|
||||
case 0x0002: return RESOURCE_FORK;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown thread_kind for data thread_class of " + threadKind);
|
||||
case FILENAME:
|
||||
if (threadKind == 0x0000) return FILENAME;
|
||||
throw new IllegalArgumentException("Unknown thread_kind for filename thread_class of " + threadKind);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown thread_class of " + threadClass);
|
||||
}
|
||||
}
|
||||
}
|
116
src/com/webcodepro/shrinkit/ThreadRecord.java
Normal file
116
src/com/webcodepro/shrinkit/ThreadRecord.java
Normal file
@ -0,0 +1,116 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* This represents a single thread from the Shrinkit archive.
|
||||
* As it is constructed, the thread "header" is read. Once all
|
||||
* threads have been constructed, use <code>readThreadData</code>
|
||||
* to load up the data.
|
||||
* <p>
|
||||
* Depending on the type of thread, the data may be text. If so,
|
||||
* <code>isText</code> will return true and <code>getText</code>
|
||||
* will return the string. Otherwise the data should be read through
|
||||
* one of the <code>InputStream</code> options.
|
||||
*
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public class ThreadRecord {
|
||||
private ThreadClass threadClass;
|
||||
private ThreadFormat threadFormat;
|
||||
private ThreadKind threadKind;
|
||||
private int threadCrc;
|
||||
private long threadEof;
|
||||
private long compThreadEof;
|
||||
private byte[] threadData;
|
||||
|
||||
/**
|
||||
* Construct the ThreadRecord and read the header details.
|
||||
*/
|
||||
public ThreadRecord(ByteSource bs) throws IOException {
|
||||
threadClass = ThreadClass.find(bs.readWord());
|
||||
threadFormat = ThreadFormat.find(bs.readWord());
|
||||
threadKind = ThreadKind.find(bs.readWord(), threadClass);
|
||||
threadCrc = bs.readWord();
|
||||
threadEof = bs.readLong();
|
||||
compThreadEof = bs.readLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the raw thread data. This must be called.
|
||||
*/
|
||||
public void readThreadData(ByteSource bs) throws IOException {
|
||||
threadData = bs.readBytes((int)compThreadEof);
|
||||
}
|
||||
/**
|
||||
* Determine if this is a text-type field.
|
||||
*/
|
||||
public boolean isText() {
|
||||
return threadKind == ThreadKind.ASCII_TEXT || threadKind == ThreadKind.FILENAME;
|
||||
}
|
||||
/**
|
||||
* Return the text data.
|
||||
*/
|
||||
public String getText() {
|
||||
return isText() ? new String(threadData, 0, (int)threadEof) : null;
|
||||
}
|
||||
/**
|
||||
* Get raw data bytes (compressed).
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
return threadData;
|
||||
}
|
||||
/**
|
||||
* Get the raw data input stream.
|
||||
*/
|
||||
public InputStream getRawInputStream() {
|
||||
return new ByteArrayInputStream(threadData);
|
||||
}
|
||||
|
||||
// GENERATED CODE
|
||||
|
||||
public ThreadClass getThreadClass() {
|
||||
return threadClass;
|
||||
}
|
||||
public void setThreadClass(ThreadClass threadClass) {
|
||||
this.threadClass = threadClass;
|
||||
}
|
||||
public ThreadFormat getThreadFormat() {
|
||||
return threadFormat;
|
||||
}
|
||||
public void setThreadFormat(ThreadFormat threadFormat) {
|
||||
this.threadFormat = threadFormat;
|
||||
}
|
||||
public ThreadKind getThreadKind() {
|
||||
return threadKind;
|
||||
}
|
||||
public void setThreadKind(ThreadKind threadKind) {
|
||||
this.threadKind = threadKind;
|
||||
}
|
||||
public int getThreadCrc() {
|
||||
return threadCrc;
|
||||
}
|
||||
public void setThreadCrc(int threadCrc) {
|
||||
this.threadCrc = threadCrc;
|
||||
}
|
||||
public long getThreadEof() {
|
||||
return threadEof;
|
||||
}
|
||||
public void setThreadEof(long threadEof) {
|
||||
this.threadEof = threadEof;
|
||||
}
|
||||
public long getCompThreadEof() {
|
||||
return compThreadEof;
|
||||
}
|
||||
public void setCompThreadEof(long compThreadEof) {
|
||||
this.compThreadEof = compThreadEof;
|
||||
}
|
||||
public byte[] getThreadData() {
|
||||
return threadData;
|
||||
}
|
||||
public void setThreadData(byte[] threadData) {
|
||||
this.threadData = threadData;
|
||||
}
|
||||
}
|
90
src/com/webcodepro/shrinkit/TimeRec.java
Normal file
90
src/com/webcodepro/shrinkit/TimeRec.java
Normal file
@ -0,0 +1,90 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
/**
|
||||
* Apple IIgs Toolbox TimeRec object.
|
||||
*
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public class TimeRec {
|
||||
private static final int SECOND = 0;
|
||||
private static final int MINUTE = 1;
|
||||
private static final int HOUR = 2;
|
||||
private static final int YEAR = 3;
|
||||
private static final int DAY = 4;
|
||||
private static final int MONTH = 5;
|
||||
private static final int WEEKDAY = 7;
|
||||
private static final int LENGTH = 8;
|
||||
private byte[] data = null;
|
||||
|
||||
/**
|
||||
* Construct a TimeRec with the current date.
|
||||
*/
|
||||
public TimeRec() {
|
||||
this(new Date());
|
||||
}
|
||||
/**
|
||||
* Construct a TimeRec with the specified date. You may pass in a null for a null date (all 0x00's).
|
||||
*/
|
||||
public TimeRec(Date date) {
|
||||
setDate(date);
|
||||
}
|
||||
/**
|
||||
* Construct a TimeRec from the given LENGTH byte array.
|
||||
*/
|
||||
public TimeRec(byte[] bytes, int offset) {
|
||||
if (bytes == null || bytes.length - offset < LENGTH) {
|
||||
throw new IllegalArgumentException("TimeRec requires a " + LENGTH + " byte array.");
|
||||
}
|
||||
data = Arrays.copyOfRange(bytes, offset, LENGTH);
|
||||
}
|
||||
/**
|
||||
* Construct a TimeRec from the InputStream.
|
||||
*/
|
||||
public TimeRec(InputStream inputStream) throws IOException {
|
||||
data = new byte[LENGTH];
|
||||
for (int i=0; i<LENGTH; i++) {
|
||||
data[i] = (byte)inputStream.read();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date.
|
||||
*/
|
||||
public void setDate(Date date) {
|
||||
data = new byte[LENGTH];
|
||||
if (date != null) {
|
||||
GregorianCalendar gc = new GregorianCalendar();
|
||||
gc.setTime(date);
|
||||
data[SECOND] = (byte)gc.get(Calendar.SECOND);
|
||||
data[MINUTE] = (byte)gc.get(Calendar.MINUTE);
|
||||
data[HOUR] = (byte)gc.get(Calendar.HOUR_OF_DAY);
|
||||
data[YEAR] = (byte)(gc.get(Calendar.YEAR) - 1900);
|
||||
data[DAY] = (byte)(gc.get(Calendar.DAY_OF_MONTH) - 1);
|
||||
data[MONTH] = (byte)gc.get(Calendar.MONTH);
|
||||
data[WEEKDAY] = (byte)gc.get(Calendar.DAY_OF_WEEK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the TimeRec into a Java Date object.
|
||||
* Note that years 1900-1939 are assumed to be 2000-2039 per the NuFX addendum
|
||||
* at http://www.nulib.com/library/nufx-addendum.htm.
|
||||
* @see http://www.nulib.com/library/nufx-addendum.htm
|
||||
*/
|
||||
public Date getDate() {
|
||||
int year = data[YEAR]+1900;
|
||||
if (year < 1940) year+= 100;
|
||||
GregorianCalendar gc = new GregorianCalendar(year, data[MONTH]+1, data[DAY], data[HOUR], data[MINUTE], data[SECOND]);
|
||||
return gc.getTime();
|
||||
}
|
||||
public byte[] getBytes() {
|
||||
return data;
|
||||
}
|
||||
}
|
94
test_src/com/webcodepro/shrinkit/ByteSourceTest.java
Normal file
94
test_src/com/webcodepro/shrinkit/ByteSourceTest.java
Normal file
@ -0,0 +1,94 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import com.webcodepro.shrinkit.ByteSource;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Exercise the ByteSource class.
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public class ByteSourceTest extends TestCase {
|
||||
public void testReadA() throws IOException {
|
||||
ByteSource bs = new ByteSource("a".getBytes());
|
||||
assertEquals('a', bs.read());
|
||||
assertEquals(-1, bs.read());
|
||||
}
|
||||
public void testReadB() throws IOException {
|
||||
// Just to ensure we can get more than one byte...
|
||||
ByteSource bs = new ByteSource("hello".getBytes());
|
||||
assertEquals('h', bs.read());
|
||||
assertEquals('e', bs.read());
|
||||
assertEquals('l', bs.read());
|
||||
assertEquals('l', bs.read());
|
||||
assertEquals('o', bs.read());
|
||||
assertEquals(-1, bs.read());
|
||||
}
|
||||
public void testReadBytesInt() throws IOException {
|
||||
// Ensure we read the requested data.
|
||||
ByteSource bs = new ByteSource("HelloWorld".getBytes());
|
||||
assertEquals("Hello", new String(bs.readBytes(5)));
|
||||
assertEquals("World", new String(bs.readBytes(5)));
|
||||
assertEquals(-1, bs.read());
|
||||
}
|
||||
public void textReadBytesIntError() {
|
||||
// Ensure that we fail appropriately
|
||||
ByteSource bs = new ByteSource("Hi".getBytes());
|
||||
try {
|
||||
bs.readBytes(3);
|
||||
fail();
|
||||
} catch (IOException ex) {
|
||||
assertTrue(true); // Expected
|
||||
}
|
||||
}
|
||||
public void testCheckNuFileId() throws IOException {
|
||||
ByteSource bs = new ByteSource(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 });
|
||||
assertTrue(bs.checkNuFileId());
|
||||
bs = new ByteSource("NotNuFile".getBytes());
|
||||
assertFalse(bs.checkNuFileId());
|
||||
}
|
||||
public void testCheckNuFxId() throws IOException {
|
||||
ByteSource bs = new ByteSource(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xd8 });
|
||||
assertTrue(bs.checkNuFxId());
|
||||
bs = new ByteSource("NotNuFx".getBytes());
|
||||
assertFalse(bs.checkNuFxId());
|
||||
}
|
||||
public void testReadWord() throws IOException {
|
||||
ByteSource bs = new ByteSource(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
|
||||
assertEquals(0x0201, bs.readWord());
|
||||
assertEquals(0x0403, bs.readWord());
|
||||
}
|
||||
public void testReadWordHighBitSet() throws IOException {
|
||||
ByteSource bs = new ByteSource(new byte[] { (byte)0xff, (byte)0xff });
|
||||
assertEquals(0xffff, bs.readWord());
|
||||
}
|
||||
public void testReadLong() throws IOException {
|
||||
ByteSource bs = new ByteSource(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
|
||||
assertEquals(0x04030201, bs.readLong());
|
||||
}
|
||||
public void testReadLongHighBitSet() throws IOException {
|
||||
ByteSource bs = new ByteSource(new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff });
|
||||
assertEquals(0xffffffffL, bs.readLong());
|
||||
}
|
||||
public void testReadDate() throws IOException {
|
||||
ByteSource bs = new ByteSource(new byte[] {
|
||||
// From NuFX documentation, final revision 3
|
||||
0x00, 0x0a, 0x01, 0x58, 0x16, 0x0a, 0x00, 0x07, // 01:10:00am 10/22/1988 saturday
|
||||
0x00, 0x10, 0x0b, 0x58, 0x11, 0x0b, 0x00, 0x05, // 11:16:00am 11/17/1988 thursday
|
||||
0x00, 0x0c, 0x0d, 0x58, 0x16, 0x0a, 0x00, 0x07, // 01:12:00pm 10/22/1988 saturday
|
||||
});
|
||||
assertEquals(new GregorianCalendar(1988, Calendar.OCTOBER, 22, 1, 10, 0).getTime(), bs.readDate());
|
||||
assertEquals(new GregorianCalendar(1988, Calendar.NOVEMBER, 17, 11, 16, 0).getTime(), bs.readDate());
|
||||
assertEquals(new GregorianCalendar(1988, Calendar.OCTOBER, 22, 13, 12, 0).getTime(), bs.readDate());
|
||||
}
|
||||
public void testReadNullDate() throws IOException {
|
||||
ByteSource bs = new ByteSource(new byte[] {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // null date
|
||||
});
|
||||
assertNull(bs.readDate());
|
||||
}
|
||||
}
|
144
test_src/com/webcodepro/shrinkit/HeaderBlockTest.java
Normal file
144
test_src/com/webcodepro/shrinkit/HeaderBlockTest.java
Normal file
@ -0,0 +1,144 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Exercise the Header Block.
|
||||
* The source of some these test cases come from the "NuFX
|
||||
* Documentation Final Revision Three" document.
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public class HeaderBlockTest extends TestCase {
|
||||
/**
|
||||
* From "NuFX Documentation Final Revision Three", version 0, "Normal Files" sample.
|
||||
*/
|
||||
public void testNormalFiles() throws IOException {
|
||||
byte[] data = {
|
||||
0x4e, (byte)0xf5, 0x46, (byte)0xd8, 0x55, 0x34, 0x3a, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x2f, 0x00, (byte)0xc3, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x0a, 0x01, 0x58, 0x16, 0x0a, 0x00, 0x07,
|
||||
0x00, 0x10, 0x0b, 0x58, 0x11, 0x0b, 0x00, 0x05,
|
||||
0x00, 0x0c, 0x01, 0x58, 0x16, 0x0a, 0x00, 0x07,
|
||||
0x05, 0x00, 0x53, 0x54, 0x55, 0x46, 0x46
|
||||
};
|
||||
ByteSource bs = new ByteSource(data);
|
||||
HeaderBlock b = new HeaderBlock(bs);
|
||||
assertEquals(0x3455, b.getHeaderCrc());
|
||||
assertEquals(0x003a, b.getAttribCount());
|
||||
assertEquals(0x0000, b.getVersionNumber());
|
||||
assertEquals(0x00000001, b.getTotalThreads());
|
||||
assertEquals(0x0001, b.getFileSysId());
|
||||
assertEquals(0x002f, b.getFileSysInfo());
|
||||
assertEquals(0x000000c3, b.getAccess());
|
||||
assertEquals(0x00000004, b.getFileType());
|
||||
assertEquals(0x00000000, b.getExtraType());
|
||||
assertEquals(0x0001, b.getStorageType());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x0a,0x01,0x58,0x16,0x0a,0x00,0x07}).readDate(), b.getCreateWhen());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x10,0x0b,0x58,0x11,0x0b,0x00,0x05}).readDate(), b.getModWhen());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x0c,0x01,0x58,0x16,0x0a,0x00,0x07}).readDate(), b.getArchiveWhen());
|
||||
assertEquals("STUFF", b.getFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* From "NuFX Documentation Final Revision Three", version 0, "Extended Files" sample.
|
||||
*/
|
||||
public void testExtendedFiles() throws IOException {
|
||||
byte[] data = {
|
||||
0x4e, (byte)0xf5, 0x46, (byte)0xd8, 0x65, 0x78, 0x3a, 0x00,
|
||||
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x2f, 0x00, (byte)0xc3, 0x00, 0x00, 0x00, (byte)0xb3, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
|
||||
0x00, 0x0a, 0x01, 0x58, 0x16, 0x0a, 0x00, 0x07,
|
||||
0x00, 0x10, 0x0b, 0x58, 0x11, 0x0b, 0x00, 0x05,
|
||||
0x00, 0x0c, 0x01, 0x58, 0x16, 0x0a, 0x00, 0x07,
|
||||
0x09, 0x00, 0x45, 0x58, 0x54, 0x2e, 0x53, 0x54,
|
||||
0x55, 0x46, 0x46
|
||||
};
|
||||
ByteSource bs = new ByteSource(data);
|
||||
HeaderBlock b = new HeaderBlock(bs);
|
||||
assertEquals(0x7865, b.getHeaderCrc());
|
||||
assertEquals(0x003a, b.getAttribCount());
|
||||
assertEquals(0x0000, b.getVersionNumber());
|
||||
assertEquals(0x00000002, b.getTotalThreads());
|
||||
assertEquals(0x0001, b.getFileSysId());
|
||||
assertEquals(0x002f, b.getFileSysInfo());
|
||||
assertEquals(0x000000c3, b.getAccess());
|
||||
assertEquals(0x000000b3, b.getFileType());
|
||||
assertEquals(0x00000000, b.getExtraType());
|
||||
assertEquals(0x0005, b.getStorageType());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x0a,0x01,0x58,0x16,0x0a,0x00,0x07}).readDate(), b.getCreateWhen());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x10,0x0b,0x58,0x11,0x0b,0x00,0x05}).readDate(), b.getModWhen());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x0c,0x01,0x58,0x16,0x0a,0x00,0x07}).readDate(), b.getArchiveWhen());
|
||||
assertEquals("EXT.STUFF", b.getFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* From "NuFX Documentation Final Revision Three", version 0, "Disk" sample.
|
||||
*/
|
||||
public void testDiskImage() throws IOException {
|
||||
byte[] data = {
|
||||
0x4e, (byte)0xf5, 0x46, (byte)0xd8, 0x67, 0x05, 0x3a, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x40, 0x06, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x0a, 0x01, 0x58, 0x16, 0x0a, 0x00, 0x07,
|
||||
0x00, 0x10, 0x0b, 0x58, 0x11, 0x0b, 0x00, 0x05,
|
||||
0x00, 0x0c, 0x01, 0x58, 0x16, 0x0a, 0x00, 0x07,
|
||||
0x04, 0x00, 0x44, 0x49, 0x53, 0x4b
|
||||
};
|
||||
ByteSource bs = new ByteSource(data);
|
||||
HeaderBlock b = new HeaderBlock(bs);
|
||||
assertEquals(0x0567, b.getHeaderCrc());
|
||||
assertEquals(0x003a, b.getAttribCount());
|
||||
assertEquals(0x0000, b.getVersionNumber());
|
||||
assertEquals(0x00000001, b.getTotalThreads());
|
||||
assertEquals(0x0001, b.getFileSysId());
|
||||
assertEquals(0x002f, b.getFileSysInfo());
|
||||
assertEquals(0x00000000, b.getAccess());
|
||||
assertEquals(0x00000000, b.getFileType());
|
||||
assertEquals(0x00000640, b.getExtraType());
|
||||
assertEquals(0x0200, b.getStorageType());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x0a,0x01,0x58,0x16,0x0a,0x00,0x07}).readDate(), b.getCreateWhen());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x10,0x0b,0x58,0x11,0x0b,0x00,0x05}).readDate(), b.getModWhen());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x0c,0x01,0x58,0x16,0x0a,0x00,0x07}).readDate(), b.getArchiveWhen());
|
||||
assertEquals("DISK", b.getFilename());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample taking from the SCC.SHK file, first header entry.
|
||||
*/
|
||||
public void testSccShkHeader1() throws IOException {
|
||||
byte[] data = {
|
||||
0x4e, (byte)0xf5, 0x46, (byte)0xd8, 0x5e, 0x40, 0x3c, 0x00,
|
||||
0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x3a, 0x00, (byte)0xe3, 0x00, 0x00, 0x00, (byte)0xb0, 0x00,
|
||||
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00,
|
||||
0x00, 0x1c, 0x10, 0x5d, 0x1c, 0x06, 0x00, 0x05,
|
||||
0x00, 0x11, 0x11, 0x5e, 0x13, 0x01, 0x00, 0x01,
|
||||
0x38, 0x0c, 0x14, 0x5f, 0x08, 0x07, 0x00, 0x04,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
ByteSource bs = new ByteSource(data);
|
||||
HeaderBlock b = new HeaderBlock(bs);
|
||||
assertEquals(0x405e, b.getHeaderCrc());
|
||||
assertEquals(0x003c, b.getAttribCount());
|
||||
assertEquals(0x0003, b.getVersionNumber());
|
||||
assertEquals(0x00000003, b.getTotalThreads());
|
||||
assertEquals(0x0001, b.getFileSysId());
|
||||
assertEquals(0x003a, b.getFileSysInfo());
|
||||
assertEquals(0x000000e3, b.getAccess());
|
||||
assertEquals(0x000000b0, b.getFileType());
|
||||
assertEquals(0x00000003, b.getExtraType());
|
||||
assertEquals(0x0002, b.getStorageType());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x1c,0x10,0x5d,0x1c,0x06,0x00,0x05}).readDate(), b.getCreateWhen());
|
||||
assertEquals(new ByteSource(new byte[] {0x00,0x11,0x11,0x5e,0x13,0x01,0x00,0x01}).readDate(), b.getModWhen());
|
||||
assertEquals(new ByteSource(new byte[] {0x38,0x0c,0x14,0x5f,0x08,0x07,0x00,0x04}).readDate(), b.getArchiveWhen());
|
||||
assertEquals(0x0000, b.getOptionSize());
|
||||
assertNull(b.getFilename());
|
||||
}
|
||||
}
|
32
test_src/com/webcodepro/shrinkit/MasterHeaderBlockTest.java
Normal file
32
test_src/com/webcodepro/shrinkit/MasterHeaderBlockTest.java
Normal file
@ -0,0 +1,32 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Exercise the Master Header Block.
|
||||
* For right now, we just grab a "real" header
|
||||
* and check it against our computed values.
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public class MasterHeaderBlockTest extends TestCase {
|
||||
public void test1() throws IOException {
|
||||
ByteSource bs = new ByteSource(new byte[] {
|
||||
0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5, (byte)0xdc, 0x1b,
|
||||
0x2d, 0x00, 0x00, 0x00, 0x38, 0x0c, 0x14, 0x5f,
|
||||
0x08, 0x07, 0x30, 0x04, 0x29, 0x0d, 0x14, 0x5f,
|
||||
0x08, 0x07, 0x01, 0x04, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0xae, (byte)0xac,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
});
|
||||
MasterHeaderBlock b = new MasterHeaderBlock(bs);
|
||||
// Using byte values since it should be a bit more clear where they came from
|
||||
assertEquals(0x1bdc, b.getMasterCrc());
|
||||
assertEquals(0x2d, b.getTotalRecords());
|
||||
assertEquals(new ByteSource(new byte[] {0x38, 0x0c, 0x14, 0x5f, 0x08, 0x07, 0x30, 0x04}).readDate(), b.getArchiveCreateWhen());
|
||||
assertEquals(new ByteSource(new byte[] {0x29, 0x0d, 0x14, 0x5f, 0x08, 0x07, 0x01, 0x04}).readDate(), b.getArchiveModWhen());
|
||||
assertEquals(0x01, b.getMasterVersion());
|
||||
assertEquals(0x1acae, b.getMasterEof());
|
||||
}
|
||||
}
|
61
test_src/com/webcodepro/shrinkit/NuFileArchiveTest.java
Normal file
61
test_src/com/webcodepro/shrinkit/NuFileArchiveTest.java
Normal file
@ -0,0 +1,61 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Not really a JUnit test, but this does verify that NuFX archives can be read.
|
||||
* We read a couple of samples and dump out the details.
|
||||
* <p>
|
||||
* Note that to successfully run this, the classpath must have the samples folder
|
||||
* added to it.
|
||||
*
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public class NuFileArchiveTest extends TestCase {
|
||||
public void testReadJoystickShk() throws IOException {
|
||||
display("/Joystick.SHK");
|
||||
}
|
||||
public void testReadSccShk() throws IOException {
|
||||
display("/Scc.shk");
|
||||
}
|
||||
|
||||
private void display(String archiveName) throws IOException {
|
||||
System.out.printf("Details for %s\n\n", archiveName);
|
||||
InputStream is = getClass().getResourceAsStream(archiveName);
|
||||
if (is == null) {
|
||||
System.out.printf("*** ERROR: Unable to locate '%s'", archiveName);
|
||||
fail("Unable to locate archive file");
|
||||
}
|
||||
NuFileArchive a = new NuFileArchive(is);
|
||||
MasterHeaderBlock m = a.getMasterHeaderBlock();
|
||||
System.out.printf("Master Header Block\n==================\n"
|
||||
+ "master_crc=$%x\ntotal_records=%d\narchive_create_when=%tc\narchive_mod_when=%tc\n"
|
||||
+ "master_version=%d\nmaster_eof=$%x\n\n",
|
||||
m.getMasterCrc(), m.getTotalRecords(), m.getArchiveCreateWhen(), m.getArchiveModWhen(),
|
||||
m.getMasterVersion(), m.getMasterEof());
|
||||
for (HeaderBlock b : a.getHeaderBlocks()) {
|
||||
System.out.printf("\tHeader Block\n\t============\n");
|
||||
System.out.printf("\theader_crc=$%x\n\tattrib_count=%d\n\tversion_number=%d\n\ttotal_threads=%d\n\t"
|
||||
+ "file_sys_id=$%x\n\tfile_sys_info=$%x\n\taccess=$%x\n\tfile_type=$%x\n\textra_type=$%x\n\t"
|
||||
+ "storage_type=$%x\n\tcreate_when=%tc\n\tmod_when=%tc\n\tarchive_when=%tc\n\toption_size=%d\n\t"
|
||||
+ "filename=%s\n\n",
|
||||
b.getHeaderCrc(), b.getAttribCount(), b.getVersionNumber(), b.getTotalThreads(), b.getFileSysId(),
|
||||
b.getFileSysInfo(), b.getAccess(), b.getFileType(), b.getExtraType(), b.getStorageType(),
|
||||
b.getCreateWhen(), b.getModWhen(), b.getArchiveWhen(), b.getOptionSize(), b.getFilename());
|
||||
System.out.printf("\t\tThreads\n\t\t=======\n");
|
||||
for (ThreadRecord r : b.getThreadRecords()) {
|
||||
System.out.printf("\t\tthread_class=%s\n\t\tthread_format=%s\n\t\tthread_kind=%s\n\t\t"
|
||||
+ "thread_crc=$%x\n\t\tthread_eof=$%x\n\t\tcompThreadEof=$%x\n",
|
||||
r.getThreadClass(), r.getThreadFormat(), r.getThreadKind(), r.getThreadCrc(),
|
||||
r.getThreadEof(), r.getCompThreadEof());
|
||||
if (r.getThreadKind() == ThreadKind.FILENAME) {
|
||||
System.out.printf("\t\tFILENAME=%s\n", r.getText());
|
||||
}
|
||||
System.out.printf("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
131
test_src/com/webcodepro/shrinkit/ThreadRecordTest.java
Normal file
131
test_src/com/webcodepro/shrinkit/ThreadRecordTest.java
Normal file
@ -0,0 +1,131 @@
|
||||
package com.webcodepro.shrinkit;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Exercise the Thread Record.
|
||||
* The source of some these test cases come from the "NuFX
|
||||
* Documentation Final Revision Three" document.
|
||||
* @author robgreene@users.sourceforge.net
|
||||
*/
|
||||
public class ThreadRecordTest extends TestCase {
|
||||
/**
|
||||
* From "NuFX Documentation Final Revision Three", version 0, "Normal Files" sample.
|
||||
*/
|
||||
public void testNormalFiles() throws IOException {
|
||||
byte[] data = {
|
||||
0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00
|
||||
};
|
||||
ByteSource bs = new ByteSource(data);
|
||||
ThreadRecord r = new ThreadRecord(bs);
|
||||
assertEquals(ThreadClass.DATA, r.getThreadClass());
|
||||
assertEquals(ThreadFormat.DYNAMIC_LZW1, r.getThreadFormat());
|
||||
assertEquals(ThreadKind.DATA_FORK, r.getThreadKind());
|
||||
assertEquals(0x0000, r.getThreadCrc());
|
||||
assertEquals(0x00002000, r.getThreadEof());
|
||||
assertEquals(0x00001000, r.getCompThreadEof());
|
||||
}
|
||||
|
||||
/**
|
||||
* From "NuFX Documentation Final Revision Three", version 0, "Extended Files" sample.
|
||||
*/
|
||||
public void testExtendedFiles() throws IOException {
|
||||
byte[] data = {
|
||||
0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,
|
||||
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00
|
||||
};
|
||||
ByteSource bs = new ByteSource(data);
|
||||
ThreadRecord r1 = new ThreadRecord(bs);
|
||||
assertEquals(ThreadClass.DATA, r1.getThreadClass());
|
||||
assertEquals(ThreadFormat.DYNAMIC_LZW1, r1.getThreadFormat());
|
||||
assertEquals(ThreadKind.DATA_FORK, r1.getThreadKind());
|
||||
assertEquals(0x0000, r1.getThreadCrc());
|
||||
assertEquals(0x00002000, r1.getThreadEof());
|
||||
assertEquals(0x00000800, r1.getCompThreadEof());
|
||||
ThreadRecord r2 = new ThreadRecord(bs);
|
||||
assertEquals(ThreadClass.DATA, r2.getThreadClass());
|
||||
assertEquals(ThreadFormat.DYNAMIC_LZW1, r2.getThreadFormat());
|
||||
assertEquals(ThreadKind.RESOURCE_FORK, r2.getThreadKind());
|
||||
assertEquals(0x0000, r2.getThreadCrc());
|
||||
assertEquals(0x00001000, r2.getThreadEof());
|
||||
assertEquals(0x00000800, r2.getCompThreadEof());
|
||||
}
|
||||
|
||||
/**
|
||||
* From "NuFX Documentation Final Revision Three", version 0, "Disk" sample.
|
||||
*/
|
||||
public void testDiskImage() throws IOException {
|
||||
byte[] data = {
|
||||
0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x51, 0x45, 0x07, 0x00
|
||||
};
|
||||
ByteSource bs = new ByteSource(data);
|
||||
ThreadRecord r = new ThreadRecord(bs);
|
||||
assertEquals(ThreadClass.DATA, r.getThreadClass());
|
||||
assertEquals(ThreadFormat.DYNAMIC_LZW1, r.getThreadFormat());
|
||||
assertEquals(ThreadKind.DISK_IMAGE, r.getThreadKind());
|
||||
assertEquals(0x0000, r.getThreadCrc());
|
||||
assertEquals(0x00000000, r.getThreadEof());
|
||||
assertEquals(0x00074551, r.getCompThreadEof());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample taken from the SCC.SHK file, first header entry.
|
||||
*/
|
||||
public void testSccShkHeader1() throws IOException {
|
||||
byte[] data = {
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, (byte)0xc8, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x58, 0x4a,
|
||||
(byte)0xd6, 0x06, 0x00, 0x00, (byte)0xd4, 0x03, 0x00, 0x00
|
||||
};
|
||||
ByteSource bs = new ByteSource(data);
|
||||
ThreadRecord r1 = new ThreadRecord(bs);
|
||||
assertEquals(ThreadClass.FILENAME, r1.getThreadClass());
|
||||
assertEquals(ThreadFormat.UNCOMPRESSED, r1.getThreadFormat());
|
||||
assertEquals(ThreadKind.FILENAME, r1.getThreadKind());
|
||||
assertEquals(0x0000, r1.getThreadCrc());
|
||||
assertEquals(0x0000000b, r1.getThreadEof());
|
||||
assertEquals(0x00000020, r1.getCompThreadEof());
|
||||
ThreadRecord r2 = new ThreadRecord(bs);
|
||||
assertEquals(ThreadClass.MESSAGE, r2.getThreadClass());
|
||||
assertEquals(ThreadFormat.UNCOMPRESSED, r2.getThreadFormat());
|
||||
assertEquals(ThreadKind.ALLOCATED_SPACE, r2.getThreadKind());
|
||||
assertEquals(0x0000, r2.getThreadCrc());
|
||||
assertEquals(0x00000000, r2.getThreadEof());
|
||||
assertEquals(0x000000c8, r2.getCompThreadEof());
|
||||
ThreadRecord r3 = new ThreadRecord(bs);
|
||||
assertEquals(ThreadClass.DATA, r3.getThreadClass());
|
||||
assertEquals(ThreadFormat.DYNAMIC_LZW2, r3.getThreadFormat());
|
||||
assertEquals(ThreadKind.DATA_FORK, r3.getThreadKind());
|
||||
assertEquals(0x4a58, r3.getThreadCrc());
|
||||
assertEquals(0x000006d6, r3.getThreadEof());
|
||||
assertEquals(0x000003d4, r3.getCompThreadEof());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample taken from the SCC.SHK file, first header entry.
|
||||
*/
|
||||
public void testSccShkHeader1FilenameThread() throws IOException {
|
||||
byte[] data = {
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0b, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x73, 0x63, 0x63, 0x3a, 0x65, 0x71, 0x75, 0x61,
|
||||
0x74, 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
ByteSource bs = new ByteSource(data);
|
||||
ThreadRecord r = new ThreadRecord(bs);
|
||||
r.readThreadData(bs);
|
||||
assertTrue(r.isText());
|
||||
assertEquals("scc:equates", r.getText());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user