Bif.java
// <editor-fold defaultstate="collapsed" desc="license">
/*
* Copyright (c) 2009, Karl H. Beckers
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the <ORGANIZATION> nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
**/
// </editor-fold>
package net.jarre_de_the.griffin.file;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import net.jarre_de_the.griffin.Util;
import net.jarre_de_the.griffin.types.data.DWordData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author charly4711
*/
public class Bif extends PersistableNwnFile {
public static final String DEFAULT_FILE_TYPE = "BIFF";
public static final String DEFAULT_FILE_VERSION = "V1 ";
private static final Logger LOGGER = LoggerFactory.getLogger(Bif.class);
private List<VariableBifResource> resources = new ArrayList<VariableBifResource>();
public Bif() {
setFileType(DEFAULT_FILE_TYPE);
setFileVersion(DEFAULT_FILE_VERSION);
}
public Bif(File in) throws IOException {
parseFile(in);
}
private void parseFile(File inFile)
throws IOException {
try (RandomAccessFile in = new RandomAccessFile(inFile, "r")) {
// set the header fields
for (BifHeaderField f : BifHeaderField.values()) {
DWordData hf = DWordData.read(in);
getHeaderFields().put(f.field(), hf);
}
// go to the var resource table
in.seek(getVariableTableOffset());
for (int i = 0; i < getVariableResourceCount(); i++) {
VariableBifResource vbr = new VariableBifResource();
vbr.read(in, 0);
resources.add(vbr);
}
}
}
@Override
public byte[] persist() throws IOException {
ByteArrayOutputStream varResourceTable = new ByteArrayOutputStream();
ByteArrayOutputStream varResourceData = new ByteArrayOutputStream();
/* not used, yet
ByteArrayOutputStream fixedResourceTable = new ByteArrayOutputStream();
ByteArrayOutputStream fixedResourceData = new ByteArrayOutputStream();
*/
DWordData[] header = new DWordData[BifHeaderField.values().length];
// file type
DWordData hf = getHeaderFields().get(BifHeaderField.FILE_TYPE.field());
if (hf == null) {
hf = new DWordData(Util.stringToBitField(DEFAULT_FILE_TYPE));
}
header[BifHeaderField.FILE_TYPE.ordinal()] = hf;
LOGGER.debug("ft: ");
for (byte b : hf.getValueAsByteArray()) {
char c = (char) b;
LOGGER.debug("" + c);
}
LOGGER.debug("");
// file version
hf = getHeaderFields().get(BifHeaderField.FILE_VERSION.field());
if (hf == null) {
hf = new DWordData(Util.stringToBitField(DEFAULT_FILE_VERSION));
}
header[BifHeaderField.FILE_VERSION.ordinal()] = hf;
LOGGER.debug("fv: ");
for (byte b : hf.getValueAsByteArray()) {
char c = (char) b;
LOGGER.debug("" + c);
}
LOGGER.debug("");
header[BifHeaderField.VARIABLE_RES_COUNT.ordinal()]
= new DWordData(getResources().size());
header[BifHeaderField.FIXED_RES_COUNT.ordinal()]
= new DWordData(getFixedResourceCount());
header[BifHeaderField.VARIABLE_TABLE_OFFSET.ordinal()]
= new DWordData(BifHeaderField.values().length * DWordData.LENGTH);
// variable resource table
// where will the variable resource data begin?
// headers + variable table data ( 4 * 4 bytes * number of var resources ) +
// fixed table data (always 0 at this point)
long offsetToVarData = (header.length * DWordData.LENGTH)
+ (4 * DWordData.LENGTH * getVariableResourceCount())
+ (5 * DWordData.LENGTH * getFixedResourceCount());
for (int i = 0; i < this.resources.size(); i++) {
VariableBifResource resource = getResources().get(i);
// as per specifications there is a bitwise shifting of a value
// x which is 0 in the patches and irrelevant, anyway, so the remaining
// part is just y which is the index into the resource table,
// which here is: i
varResourceTable.write(new DWordData(i).getValueAsByteArray());
varResourceTable.write(new DWordData(offsetToVarData).getValueAsByteArray());
Object resourceData = resource.getData();
byte[] resourceBytes = null;
Class clazz = resource.getResourceType().contentType().typeClass();
if (null != clazz) {
if (resourceData instanceof PersistableNwnFile
&& (Gff.class.isAssignableFrom(clazz) || TwoDa.class.isAssignableFrom(clazz))) {
PersistableNwnFile file = (PersistableNwnFile) resourceData;
resourceBytes = file.persist();
} else if (String.class.isAssignableFrom(clazz)) {
resourceBytes = ((String) resourceData).getBytes(Util.CHARSET_US_ASCII);
} else {
// this case does not exist, atm
resourceBytes = (byte[]) resourceData;
}
} else {
// treat as binary
resourceBytes = (byte[]) resourceData;
}
if (null != resourceBytes) {
varResourceData.write(resourceBytes);
offsetToVarData += resourceBytes.length;
}
varResourceTable.write(
new DWordData((resourceBytes != null
? resourceBytes.length : 0)).getValueAsByteArray());
varResourceTable.write(
new DWordData(resource.getResourceType().id()).getValueAsByteArray());
}
// put it all together
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (DWordData d : header) {
out.write(d.getValueAsByteArray());
}
out.write(varResourceTable.toByteArray());
// out.write(fixedResourceTable.toByteArray());
out.write(varResourceData.toByteArray());
return out.toByteArray();
}
@Override
public Bif clone() throws CloneNotSupportedException {
Bif clone = (Bif) super.clone();
clone.setResources(new ArrayList<VariableBifResource>());
// header fields not in PersistableNwnFile
clone.setFixedResourceCount(getFixedResourceCount());
clone.setVariableResourceCount(getVariableResourceCount());
clone.setVariableTableOffset(getVariableTableOffset());
for (Bif.VariableBifResource r : this.getResources()) {
clone.getResources().add(r.clone());
}
return clone;
}
@Override
public boolean equals(Object compare) {
if (compare == this) {
return true;
}
if (!super.equals(compare)) {
return false;
}
if (!(compare instanceof Bif)) {
return false;
}
Bif file = (Bif) compare;
return areNullablePropertiesEqual(this.getResources(), file.getResources())
&& this.getFixedResourceCount() == file.getFixedResourceCount()
&& this.getVariableResourceCount() == file.getVariableResourceCount()
&& this.getVariableTableOffset() == file.getVariableTableOffset();
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 41 * hash + Objects.hashCode(this.resources);
return hash;
}
protected enum BifHeaderField implements HeaderField {
FILE_TYPE(PersistableNwnFile.CommonHeaderField.FILE_TYPE.field()),
FILE_VERSION(PersistableNwnFile.CommonHeaderField.FILE_VERSION.field()),
VARIABLE_RES_COUNT("varCount"),
FIXED_RES_COUNT("fixCount"),
VARIABLE_TABLE_OFFSET("varTableOff");
private final String field;
private BifHeaderField(String field) {
this.field = field;
}
public String field() {
return field;
}
}
public long getVariableResourceCount() {
return getHeaderNumber(BifHeaderField.VARIABLE_RES_COUNT.field(), getResources());
}
private void setVariableResourceCount(long bifCount) {
getHeaderFields().put(BifHeaderField.VARIABLE_RES_COUNT.field(),
new DWordData(bifCount));
}
public long getFixedResourceCount() {
return getHeaderNumber(BifHeaderField.FIXED_RES_COUNT.field(), null);
}
private void setFixedResourceCount(long bifCount) {
getHeaderFields().put(BifHeaderField.FIXED_RES_COUNT.field(),
new DWordData(bifCount));
}
public long getVariableTableOffset() {
return getHeaderNumber(BifHeaderField.VARIABLE_TABLE_OFFSET.field(), null);
}
private void setVariableTableOffset(long bifCount) {
getHeaderFields().put(BifHeaderField.VARIABLE_TABLE_OFFSET.field(),
new DWordData(bifCount));
}
public List<VariableBifResource> getResources() {
return resources;
}
public void setResources(List<VariableBifResource> resources) {
this.resources = resources;
}
public static class VariableBifResource extends EmbeddedResource {
private int id = 0;
public static final int LENGTH = DWordData.LENGTH * 4;
public VariableBifResource() {
}
@Override
public void read(RandomAccessFile in, long offset) throws IOException {
DWordData idData = DWordData.read(in);
// as per docs, the game and toolset ignore the parts that are shifted
// out by 20 bits ... sooooo
int mask = Integer.parseInt("11111111111111111111", 2);
id = idData.getValue() & mask;
setOffset(DWordData.read(in).getValueAsNumber());
setResourceLength(DWordData.read(in).getValueAsNumber());
setResourceType(ResourceType.getResourceTypeById(
DWordData.read(in).getValueAsNumber().intValue()));
long sPos = in.getFilePointer();
// readResource jumps to the right place, need to jump back
setData(readResource(in));
in.seek(sPos);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public VariableBifResource clone() throws CloneNotSupportedException {
VariableBifResource clone = (VariableBifResource) super.clone();
clone.setId(getId());
return clone;
}
@Override
public boolean equals(Object compare) {
if (compare == this) {
return true;
}
if (!super.equals(compare)) {
return false;
}
if (!(compare instanceof VariableBifResource)) {
return false;
}
VariableBifResource resource = (VariableBifResource) compare;
return this.getId() == resource.getId();
}
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 71 * hash + this.id;
return hash;
}
}
}