Merging changes that existed in AppleCommander's clone. Closes #2.

This commit is contained in:
Rob Greene 2018-03-06 21:34:58 -06:00
parent 1c54b5ac16
commit 1bd7b5f431
9 changed files with 186 additions and 189 deletions

6
.gitignore vendored
View File

@ -1,2 +1,8 @@
# Gradle
.gradle/
build/
# Eclipse
.settings/
.classpath
.project

View File

@ -37,6 +37,7 @@ public class HeaderBlock {
private byte[] attribBytes;
private String filename;
private String rawFilename;
private long headerSize = 0;
private List<ThreadRecord> threads = new ArrayList<ThreadRecord>();
/**
@ -44,7 +45,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();
@ -86,7 +89,10 @@ public class HeaderBlock {
*/
public void readThreads(LittleEndianByteInputStream bs) throws IOException {
for (long l=0; l<totalThreads; l++) threads.add(new ThreadRecord(this, bs));
for (ThreadRecord r : threads) r.readThreadData(bs);
for (ThreadRecord r : threads) {
r.readThreadData(bs);
headerSize += r.getThreadEof();
}
}
/**
@ -102,10 +108,24 @@ public class HeaderBlock {
ThreadRecord r = findThreadRecord(ThreadKind.FILENAME);
if (r != null) filename = r.getText();
if (filename == null) filename = rawFilename;
if (filename.contains(":")) {
filename = filename.replace(":","/");
}
}
return filename;
}
/**
* Final element in the path, in those cases where a filename actually holds a path name
*/
public String getFinalFilename() {
String filename = getFilename();
String[] path;
path = filename.split("/");
filename = path[path.length - 1];
return filename;
}
/**
* Get the data fork.
*/
@ -240,4 +260,7 @@ public class HeaderBlock {
public void setThreadRecords(List<ThreadRecord> threads) {
this.threads = threads;
}
public long getHeaderSize() {
return headerSize;
}
}

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,26 +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; // Binary II wrappers will need to bump out the normal header size
nuFileId = bs.readBytes(6);
int fileType = 0, headerOffset = 0;
fileType = bs.seekFileType();
if (checkId(nuFileId,BXY_ID)) {
// Binary II wrapper present...
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
@ -106,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,15 @@ import com.webcodepro.shrinkit.io.LittleEndianByteInputStream;
public class NuFileArchive {
private MasterHeaderBlock master;
private List<HeaderBlock> headers;
private long totalSize = 0;
/**
* 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.
*/
@ -27,13 +35,20 @@ public class NuFileArchive {
HeaderBlock header = new HeaderBlock(bs);
header.readThreads(bs);
headers.add(header);
totalSize += header.getHeaderSize();
}
}
/**
* @return long size in bytes of the archive
*/
public long getArchiveSize() {
return totalSize;
}
public MasterHeaderBlock getMasterHeaderBlock() {
return master;
}
public List<HeaderBlock> getHeaderBlocks() {
return headers;
}
}
}}

View File

@ -1,75 +0,0 @@
package com.webcodepro.shrinkit;
/*
* Copyright (C) 2012 by David Schmidt
* david__schmidt at users.sourceforge.net
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import com.webcodepro.shrinkit.io.LittleEndianByteInputStream;
/**
* Some higher-level utilities for dealing with a NuFX archive.
*
* @author david__schmidt@users.sourceforge.net
*/
public class Utilities
{
/**
* Interpret a SDK 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[] unpackSDKFile(String fileName) throws IOException {
byte buffer[] = null;
ThreadRecord dataThread = null;
File file = new File(fileName);
if (file.isDirectory()) {
throw new IllegalArgumentException("'" + fileName + "' is not a file.");
}
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);
}
}
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.
@ -61,6 +62,9 @@ public class LittleEndianByteInputStream extends InputStream implements ByteCons
byte[] data = new byte[bytes];
int read = inputStream.read(data);
bytesRead+= read;
// In the case where we have a zero-byte file, 'read' stays at -1, which is not correct. Fix it.
if ((bytes == 0) && (read == -1))
read = 0;
if (read < bytes) {
throw new IOException("Requested " + bytes + " bytes, but " + read + " read");
}
@ -69,18 +73,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.

View File

@ -4,8 +4,6 @@ import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import com.webcodepro.shrinkit.io.LittleEndianByteInputStream;
import junit.framework.TestCase;
/**
@ -14,93 +12,105 @@ import junit.framework.TestCase;
*/
public class LittleEndianByteInputStreamTest extends TestCase {
public void testReadA() throws IOException {
LittleEndianByteInputStream bs = new LittleEndianByteInputStream("a".getBytes());
assertEquals('a', bs.read());
assertEquals(-1, bs.read());
assertEquals(1, bs.getTotalBytesRead());
try (LittleEndianByteInputStream bs = new LittleEndianByteInputStream("a".getBytes())) {
assertEquals('a', bs.read());
assertEquals(-1, bs.read());
assertEquals(1, bs.getTotalBytesRead());
}
}
public void testReadB() throws IOException {
// Just to ensure we can get more than one byte...
LittleEndianByteInputStream bs = new LittleEndianByteInputStream("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());
assertEquals(5, bs.getTotalBytesRead());
try (LittleEndianByteInputStream bs = new LittleEndianByteInputStream("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());
assertEquals(5, bs.getTotalBytesRead());
}
}
public void testReadBytesInt() throws IOException {
// Ensure we read the requested data.
LittleEndianByteInputStream bs = new LittleEndianByteInputStream("HelloWorld".getBytes());
assertEquals("Hello", new String(bs.readBytes(5)));
assertEquals("World", new String(bs.readBytes(5)));
assertEquals(-1, bs.read());
assertEquals(10, bs.getTotalBytesRead());
try (LittleEndianByteInputStream bs = new LittleEndianByteInputStream("HelloWorld".getBytes())) {
assertEquals("Hello", new String(bs.readBytes(5)));
assertEquals("World", new String(bs.readBytes(5)));
assertEquals(-1, bs.read());
assertEquals(10, bs.getTotalBytesRead());
}
}
public void textReadBytesIntError() {
public void textReadBytesIntError() throws IOException {
// Ensure that we fail appropriately
LittleEndianByteInputStream bs = new LittleEndianByteInputStream("Hi".getBytes());
try {
bs.readBytes(3);
fail();
} catch (IOException ex) {
assertTrue(true); // Expected
try (LittleEndianByteInputStream bs = new LittleEndianByteInputStream("Hi".getBytes())) {
try {
bs.readBytes(3);
fail();
} catch (IOException ex) {
assertTrue(true); // Expected
assertEquals(2, bs.getTotalBytesRead());
}
}
}
// This methods got removed at some point but nobody updated the unit tests. Bad developer!
// public void testCheckNuFileId() throws IOException {
// LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 });
// assertTrue(NuFileArchive.bs.checkNuFileId());
// assertEquals(6, bs.getTotalBytesRead());
// bs = new LittleEndianByteInputStream("NotNuFile".getBytes());
// assertFalse(bs.checkNuFileId());
// }
// This methods got removed at some point but nobody updated the unit tests. Bad developer!
// public void testCheckNuFxId() throws IOException {
// LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xd8 });
// assertTrue(bs.checkNuFxId());
// assertEquals(4, bs.getTotalBytesRead());
// bs = new LittleEndianByteInputStream("NotNuFx".getBytes());
// assertFalse(bs.checkNuFxId());
// }
public void testReadWord() throws IOException {
try (LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 })) {
assertEquals(0x0201, bs.readWord());
assertEquals(0x0403, bs.readWord());
assertEquals(4, bs.getTotalBytesRead());
}
}
public void testReadWordHighBitSet() throws IOException {
try (LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { (byte)0xff, (byte)0xff })) {
assertEquals(0xffff, bs.readWord());
assertEquals(2, bs.getTotalBytesRead());
}
}
public void testCheckNuFileId() throws IOException {
LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 });
assertTrue(bs.checkNuFileId());
assertEquals(6, bs.getTotalBytesRead());
bs = new LittleEndianByteInputStream("NotNuFile".getBytes());
assertFalse(bs.checkNuFileId());
}
public void testCheckNuFxId() throws IOException {
LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xd8 });
assertTrue(bs.checkNuFxId());
assertEquals(4, bs.getTotalBytesRead());
bs = new LittleEndianByteInputStream("NotNuFx".getBytes());
assertFalse(bs.checkNuFxId());
}
public void testReadWord() throws IOException {
LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
assertEquals(0x0201, bs.readWord());
assertEquals(0x0403, bs.readWord());
assertEquals(4, bs.getTotalBytesRead());
}
public void testReadWordHighBitSet() throws IOException {
LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { (byte)0xff, (byte)0xff });
assertEquals(0xffff, bs.readWord());
assertEquals(2, bs.getTotalBytesRead());
}
public void testReadLong() throws IOException {
LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
assertEquals(0x04030201, bs.readLong());
assertEquals(4, bs.getTotalBytesRead());
try (LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 })) {
assertEquals(0x04030201, bs.readLong());
assertEquals(4, bs.getTotalBytesRead());
}
}
public void testReadLongHighBitSet() throws IOException {
LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff });
assertEquals(0xffffffffL, bs.readLong());
assertEquals(4, bs.getTotalBytesRead());
try (LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff })) {
assertEquals(0xffffffffL, bs.readLong());
assertEquals(4, bs.getTotalBytesRead());
}
}
public void testReadDate() throws IOException {
LittleEndianByteInputStream bs = new LittleEndianByteInputStream(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());
assertEquals(24, bs.getTotalBytesRead());
try (LittleEndianByteInputStream bs = new LittleEndianByteInputStream(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());
assertEquals(24, bs.getTotalBytesRead());
}
}
public void testReadNullDate() throws IOException {
LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // null date
});
assertNull(bs.readDate());
assertEquals(8, bs.getTotalBytesRead());
try (LittleEndianByteInputStream bs = new LittleEndianByteInputStream(new byte[] {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // null date
})) {
assertNull(bs.readDate());
assertEquals(8, bs.getTotalBytesRead());
}
}
}

View File

@ -76,7 +76,7 @@ public class LzwTest extends TestCaseHelper {
(byte)0x81, 0x00, (byte)0xde, 0x6c, 0x3b, 0x48, 0x10, (byte)0xa1,
(byte)0xc2, 0x3f, 0x0f, 0x02, (byte)0xfe, (byte)0x93, 0x48, 0x11,
(byte)0xc0, 0x44, (byte)0x8b, 0x15, 0x2f, 0x6a, (byte)0xcc, (byte)0xc8,
0x11, 0x23, (byte)0x80, 0x73, 0x00
0x11, 0x23, (byte)0x80, 0x73
};
}
protected int[] getHgrColorsUncompressed() {