/*-
* Copyright (C) 2008 Erik Larsson
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package org.catacombae.hfsexplorer.fs;
import org.catacombae.util.Util;
import org.catacombae.io.ReadableRandomAccessSubstream;
import org.catacombae.io.SynchronizedReadableRandomAccess;
import org.catacombae.io.SynchronizedReadableRandomAccessStream;
import org.catacombae.hfsexplorer.types.resff.ReferenceListEntry;
import org.catacombae.hfsexplorer.types.resff.ResourceHeader;
import org.catacombae.hfsexplorer.types.resff.ResourceMap;
import org.catacombae.io.ReadableConcatenatedStream;
import org.catacombae.io.ReadableRandomAccessStream;
/**
* Accessor class for the data inside a resource fork.
*
* @author <a href="http://www.catacombae.org/" target="_top">Erik Larsson</a>
*/
public class ResourceForkReader {
/*
* Resource fork abstract data model:
*
* ----------------- -------------
* | Resource fork |1----*| Data type | <- fourcc identifer
* ----------------- -------------
* 1 1
* | |
* * +
* -----------------
* | Resource item | <- actual data
* -----------------
*
* Or hierarchically represented as example:
* <resourceFork>
* <dataType name="typ1">
* <resourceItem name="yada"/>
* <resourceItem name="bada"/>
* <resourceItem name="lada"/>
* </dataType>
* <dataType name="typ2">
* <resourceItem name="sada"/>
* <resourceItem name="gada"/>
* </dataType>
* </resourceFork>
*/
private final SynchronizedReadableRandomAccessStream trueForkStream;
private final SynchronizedReadableRandomAccess forkStream;
public ResourceForkReader(ReadableRandomAccessStream forkStream) {
this.trueForkStream = new SynchronizedReadableRandomAccessStream(forkStream);
this.forkStream = trueForkStream;
}
public void close() {
trueForkStream.close();
}
public ResourceHeader getHeader() {
byte[] headerData = new byte[ResourceHeader.length()];
forkStream.readFullyFrom(0, headerData);
return new ResourceHeader(headerData, 0);
}
private void validateHeader(ResourceHeader header)
throws MalformedResourceForkException
{
final long dataOffset = header.getDataOffset();
final long dataLength = header.getDataLength();
final long mapOffset = header.getMapOffset();
final long mapLength = header.getMapLength();
final long forkLength = forkStream.length();
/* If data extends beyond the end of the fork, the header is invalid. */
if(dataLength > forkLength || (forkLength - dataLength) < dataOffset) {
throw new MalformedResourceForkException("Invalid ResourceHeader " +
"data (data region extends beyond end of fork: " +
"fork length=" + forkLength + " " +
"data offset=" + dataOffset + " " +
"data length=" + dataLength + ").");
}
/* If map extends beyond the end of the fork, the header is invalid. */
if(mapLength > forkLength || (forkLength - mapLength) < mapOffset) {
throw new MalformedResourceForkException("Invalid ResourceHeader " +
"data (map region extends beyond end of fork: " +
"fork length=" + forkLength + " " +
"map offset=" + mapOffset + " " +
"map length=" + mapLength + ").");
}
/* If data and map regions overlap, the header is invalid. */
if(dataOffset < (mapOffset + mapLength) &&
(dataOffset + dataLength) > mapOffset)
{
throw new MalformedResourceForkException("Invalid ResourceHeader " +
"data (data and map regions overlap: " +
"data offset=" + dataOffset + " " +
"data length=" + dataLength + " " +
"map offset=" + mapOffset + " " +
"map length=" + mapLength + ").");
}
}
public ResourceMap getResourceMap() throws MalformedResourceForkException {
ResourceHeader header = getHeader();
validateHeader(header);
return new ResourceMap(forkStream, header.getMapOffset());
}
public long getDataLength(ReferenceListEntry entry) {
long dataPos = getDataPos(entry);
return getDataLength(dataPos);
}
private long getDataPos(ReferenceListEntry entry) {
ResourceHeader header = getHeader();
return header.getDataOffset() + entry.getResourceDataOffset();
}
private long getDataLength(long dataPos) {
byte[] dataLengthBytes = new byte[4];
forkStream.readFrom(dataPos, dataLengthBytes);
return Util.unsign(Util.readIntBE(dataLengthBytes));
}
public ReadableRandomAccessStream getResourceStream(ReferenceListEntry entry) {
long dataPos = getDataPos(entry);
long dataLength = getDataLength(dataPos);
//System.err.println("Creating a new stream for ReferenceListEntry:");
//entry.printFields(System.err, " ");
//System.err.println("dataPos=" + dataPos);
//System.err.println("dataLength=" + dataLength);
return new ReadableConcatenatedStream(new ReadableRandomAccessSubstream(forkStream),
dataPos + 4, dataLength);
}
public static class MalformedResourceForkException extends RuntimeException
{
public MalformedResourceForkException() {
super();
}
public MalformedResourceForkException(String message) {
super(message);
}
}
}