AppleCommander/src/main/java/com/webcodepro/applecommander/util/AppleSingle.java

113 lines
3.2 KiB
Java

package com.webcodepro.applecommander.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* Support reading of data from and AppleSingle source.
* Does not implement all components at this time, extend as required.
*
* @see <a href="https://github.com/AppleCommander/AppleCommander/issues/20">AppleCommander issue #20</a>
*/
public class AppleSingle {
public static final int MAGIC_NUMBER = 0x0051600;
public static final int VERSION_NUMBER = 0x00020000;
private byte[] dataFork;
private byte[] resourceFork;
private String realName;
private ProdosFileInfo prodosFileInfo;
public AppleSingle(String filename) throws IOException {
byte[] fileData = Files.readAllBytes(Paths.get(filename));
load(fileData);
}
public AppleSingle(InputStream stream) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
StreamUtil.copy(stream, os);
os.flush();
load(os.toByteArray());
}
private void load(byte[] fileData) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(fileData)
.order(ByteOrder.BIG_ENDIAN)
.asReadOnlyBuffer();
required(buffer, MAGIC_NUMBER, "Not an AppleSingle file - magic number does not match.");
required(buffer, VERSION_NUMBER, "Only AppleSingle version 2 supported.");
buffer.position(buffer.position() + 16); // Skip filler
int entries = buffer.getShort();
for (int i = 0; i < entries; i++) {
int entryId = buffer.getInt();
int offset = buffer.getInt();
int length = buffer.getInt();
buffer.mark();
buffer.position(offset);
byte[] entryData = new byte[length];
buffer.get(entryData);
if (entryId == 1) {
dataFork = entryData;
} else if (entryId == 2) {
resourceFork = entryData;
} else if (entryId == 11) {
ByteBuffer infoData = ByteBuffer.wrap(entryData)
.order(ByteOrder.BIG_ENDIAN)
.asReadOnlyBuffer();
int access = infoData.getShort();
int fileType = infoData.getShort();
int auxType = infoData.getInt();
prodosFileInfo = new ProdosFileInfo(access, fileType, auxType);
} else {
throw new IOException(String.format("Unknown entry type of %04x", entryId));
}
buffer.reset();
}
}
private void required(ByteBuffer buffer, int expected, String message) throws IOException {
int actual = buffer.getInt();
if (actual != expected) {
throw new IOException(String.format("%s Expected 0x%08x but read 0x%08x.", message, expected, actual));
}
}
public byte[] getDataFork() {
return dataFork;
}
public byte[] getResourceFork() {
return resourceFork;
}
public String getRealName() {
return realName;
}
public ProdosFileInfo getProdosFileInfo() {
return prodosFileInfo;
}
public class ProdosFileInfo {
private int access;
private int fileType;
private int auxType;
public ProdosFileInfo(int access, int fileType, int auxType) {
this.access = access;
this.fileType = fileType;
this.auxType = auxType;
}
public int getAccess() {
return access;
}
public int getFileType() {
return fileType;
}
public int getAuxType() {
return auxType;
}
}
}