/*******************************************************************************
* Copyright (c) 2015
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*******************************************************************************/
package jsettlers.graphics.reader;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import jsettlers.graphics.image.GuiImage;
import jsettlers.graphics.image.Image;
import jsettlers.graphics.image.LandscapeImage;
import jsettlers.graphics.image.MultiImageMap;
import jsettlers.graphics.image.NullImage;
import jsettlers.graphics.image.SettlerImage;
import jsettlers.graphics.image.ShadowImage;
import jsettlers.graphics.image.SingleImage;
import jsettlers.graphics.image.TorsoImage;
import jsettlers.graphics.reader.bytereader.ByteReader;
import jsettlers.graphics.reader.translator.DatBitmapTranslator;
import jsettlers.graphics.reader.translator.GuiTranslator;
import jsettlers.graphics.reader.translator.LandscapeTranslator;
import jsettlers.graphics.reader.translator.SettlerTranslator;
import jsettlers.graphics.reader.translator.ShadowTranslator;
import jsettlers.graphics.reader.translator.TorsoTranslator;
import jsettlers.graphics.sequence.ArraySequence;
import jsettlers.graphics.sequence.Sequence;
/**
* This is an advanced dat file reader. It can read the file, but it only reads needed sequences.
* <p>
* The format of a dat file is (all numbers in little endian):
* <table>
* <tr>
* <td>Bytes 0..47:</td>
* <td>Always the same</td>
* </tr>
* <tr>
* <td>Bytes 48 .. 51:</td>
* <td>file size</td>
* </tr>
* <tr>
* <td>Bytes 52 .. 55:</td>
* <td>Unknown Pointer, seems not to be a sequence.</td>
* </tr>
* <tr>
* <td>Bytes 56 .. 59:</td>
* <td>Start position of landscape sequence pointers.</td>
* </tr>
* <tr>
* <td>Bytes 60 .. 63:</td>
* <td>Unneeded Pointer</td>
* </tr>
* <tr>
* <td>Bytes 64 .. 67:</td>
* <td>Settler/Building/.. pointers</td>
* </tr>
* <tr>
* <td>Bytes 68 .. 71:</td>
* <td>Torso pointers</td>
* </tr>
* <tr>
* <td>Bytes 72 .. 75:</td>
* <td>Position after above</td>
* </tr>
* <tr>
* <td>Bytes 76 .. 79:</td>
* <td>Position after above</td>
* </tr>
* <tr>
* <td>Bytes 80 .. 83:</td>
* <td>Something, seems to be like 52..55</td>
* </tr>
* <tr>
* <td>Bytes 84 .. 87:</td>
* <td>{04 19 00 00}</td>
* </tr>
* <tr>
* <td>Bytes 88 .. 91:</td>
* <td>{0c 00 00 00}</td>
* </tr>
* <tr>
* <td>Bytes 92 .. 95:</td>
* <td>{00 00 00 00}</td>
* </tr>
* <tr>
* <td>e.g. Bytes 102 .. 103:</td>
* <td>Image count of image sequences for one type</td>
* </tr>
* <tr>
* <td>e.g. Bytes 104 .. 107:</td>
* <td>Start position of fist image sequence list.</td>
* </tr>
* </table>
*
* @author michael
*/
public class AdvancedDatFileReader implements DatFileSet {
/**
* Every dat file seems to have to start with this sequence.
*/
private static final byte[] FILE_START1 = {
0x04,
0x13,
0x04,
0x00,
0x0c,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x54,
0x00,
0x00,
0x00,
0x20,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x10,
0x00,
0x00,
0x00,
0x00,
};
private static final byte[] FILE_START2 = {
0x00,
0x00,
0x1f,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00
};
private static final byte[] FILE_HEADER_END = {
0x04,
0x19,
0x00,
0x00,
0x0c,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00
};
static final int SEQUENCE_TYPE_COUNT = 6;
static final int ID_SETTLERS = 0x106;
static final int ID_TORSOS = 0x3112;
static final int ID_LANDSCAPE = 0x2412;
static final int ID_SHADOWS = 0x5982;
// fullscreen images
static final int ID_GUIS = 0x11306;
private final DatBitmapTranslator<SettlerImage> settlerTranslator;
private final DatBitmapTranslator<TorsoImage> torsoTranslator;
private final DatBitmapTranslator<LandscapeImage> landscapeTranslator;
private final DatBitmapTranslator<ShadowImage> shadowTranslator;
private final DatBitmapTranslator<GuiImage> guiTranslator;
private ByteReader reader = null;
private final File file;
/**
* This is a list of file positions where the settler sequences start.
*/
private int[] settlerstarts;
/**
* A list of loaded settler sequences.
*/
private Sequence<Image>[] settlersequences = null;
/**
* An array with the same length as settlers.
*/
private int[] torsostarts;
/**
* An array with the same length as settlers.
*/
private int[] shadowstarts;
/**
* A list of loaded landscae images.
*/
private LandscapeImage[] landscapeimages = null;
private final Sequence<LandscapeImage> landscapesequence =
new LandscapeImageSequence();
private int[] landscapestarts;
private GuiImage[] guiimages = null;
private int[] guistarts;
private final Sequence<GuiImage> guisequence = new GuiImageSequence();
private final SequenceList<Image> directSettlerList;
private static final byte[] START = new byte[] {
0x02, 0x14, 0x00, 0x00, 0x08, 0x00, 0x00
};
private final DatFileType type;
public AdvancedDatFileReader(File file, DatFileType type) {
this.file = file;
this.type = type;
directSettlerList = new DirectSettlerSequenceList();
settlerTranslator =
new SettlerTranslator(type);
torsoTranslator =
new TorsoTranslator();
landscapeTranslator =
new LandscapeTranslator(type);
shadowTranslator =
new ShadowTranslator();
guiTranslator =
new GuiTranslator(type);
}
/**
* Initializes the reader, reads the index.
*/
@SuppressWarnings("unchecked")
public void initialize() {
try {
try {
reader = new ByteReader(new RandomAccessFile(file, "r"));
initFromReader(file, reader);
} catch (IOException e) {
if (reader != null) {
reader.close();
reader = null;
}
throw e;
}
} catch (Exception e) {
e.printStackTrace();
}
initializeNullFile();
landscapeimages = new LandscapeImage[landscapestarts.length];
guiimages = new GuiImage[guistarts.length];
settlersequences = new Sequence[settlerstarts.length];
int torsodifference = settlerstarts.length - torsostarts.length;
if (torsodifference != 0) {
int[] oldtorsos = torsostarts;
torsostarts = new int[settlerstarts.length];
for (int i = 0; i < oldtorsos.length; i++) {
torsostarts[i + torsodifference] = oldtorsos[i];
}
for (int i = 0; i < torsodifference; i++) {
torsostarts[i] = -1;
}
}
int shadowdifference = settlerstarts.length - shadowstarts.length;
if (shadowstarts.length < settlerstarts.length) {
int[] oldshadows = shadowstarts;
shadowstarts = new int[settlerstarts.length];
for (int i = 0; i < oldshadows.length; i++) {
shadowstarts[i + shadowdifference] = oldshadows[i];
}
for (int i = 0; i < shadowdifference; i++) {
torsostarts[i] = -1;
}
}
}
private void initFromReader(File file, ByteReader reader)
throws IOException {
int[] sequenceIndexStarts =
readSequenceIndexStarts(file.length(), reader);
for (int i = 0; i < SEQUENCE_TYPE_COUNT; i++) {
try {
readSequencesAt(reader, sequenceIndexStarts[i]);
} catch (IOException e) {
System.err.println("Error while loading sequence" + ": "
+ e.getMessage());
e.printStackTrace();
}
}
}
private int[] readSequenceIndexStarts(long filelength,
ByteReader reader) throws IOException {
reader.assumeToRead(FILE_START1);
reader.assumeToRead(type.getFileStartMagic());
reader.assumeToRead(FILE_START2);
int fileSize = reader.read32();
if (fileSize != filelength) {
throw new IOException(
"The length stored in the dat file is not the file length.");
}
// ignore unknown bytes.
reader.read32();
// read settler image pointer
int[] sequenceIndexStarts = new int[SEQUENCE_TYPE_COUNT];
for (int i = 0; i < SEQUENCE_TYPE_COUNT; i++) {
sequenceIndexStarts[i] = reader.read32();
}
// ignore unknown bytes.
reader.read32();
reader.assumeToRead(FILE_HEADER_END);
return sequenceIndexStarts;
}
/**
* reads all sequence starts at a given position.
* <p>
* Does not align torsos and shadows.
*
* @param reader
* The reader to read from.
* @param sequenceIndexStart
* The position to start at.
* @param type
* The type of the sequence
* @throws IOException
* if an read error occurred.
*/
private void readSequencesAt(ByteReader reader, int sequenceIndexStart)
throws IOException {
// read data index 0
reader.skipTo(sequenceIndexStart);
int sequenceType = reader.read32();
int byteCount = reader.read16();
int pointerCount = reader.read16();
if (byteCount != pointerCount * 4 + 8) {
throw new IOException("Sequence index block length ("
+ pointerCount + ") and " + "bytecount (" + byteCount
+ ") are not consistent.");
}
int[] sequenceIndexPointers = new int[pointerCount];
for (int i = 0; i < pointerCount; i++) {
sequenceIndexPointers[i] = reader.read32();
}
if (sequenceType == ID_SETTLERS) {
settlerstarts = sequenceIndexPointers;
} else if (sequenceType == ID_TORSOS) {
torsostarts = sequenceIndexPointers;
} else if (sequenceType == ID_LANDSCAPE) {
landscapestarts = sequenceIndexPointers;
} else if (sequenceType == ID_SHADOWS) {
shadowstarts = sequenceIndexPointers;
} else if (sequenceType == ID_GUIS) {
guistarts = sequenceIndexPointers;
}
}
private void initializeNullFile() {
if (settlerstarts == null) {
settlerstarts = new int[0];
}
if (torsostarts == null) {
torsostarts = new int[0];
}
if (shadowstarts == null) {
shadowstarts = new int[0];
}
if (landscapestarts == null) {
landscapestarts = new int[0];
}
if (guistarts == null) {
guistarts = new int[0];
}
}
private void initializeIfNeeded() {
if (settlersequences == null) {
initialize();
}
}
@Override
public SequenceList<Image> getSettlers() {
return directSettlerList;
}
private static final Sequence<Image> NULL_SETTLER_SEQUENCE =
new ArraySequence<Image>(new SettlerImage[0]);
private class DirectSettlerSequenceList implements SequenceList<Image> {
@Override
public Sequence<Image> get(int index) {
initializeIfNeeded();
if (settlersequences[index] == null) {
settlersequences[index] = NULL_SETTLER_SEQUENCE;
try {
System.out.println("Loading Sequence number " + index);
loadSettlers(index);
} catch (Exception e) {
}
}
return settlersequences[index];
}
@Override
public int size() {
initializeIfNeeded();
return settlersequences.length;
}
}
private synchronized void loadSettlers(int index) throws IOException {
int position = settlerstarts[index];
long[] framePositions = readSequenceHeader(position);
SettlerImage[] images = new SettlerImage[framePositions.length];
for (int i = 0; i < framePositions.length; i++) {
reader.skipTo(framePositions[i]);
images[i] = DatBitmapReader.getImage(settlerTranslator, reader);
}
int torsoposition = torsostarts[index];
if (torsoposition >= 0) {
long[] torsoPositions = readSequenceHeader(torsoposition);
for (int i = 0; i < torsoPositions.length
&& i < framePositions.length; i++) {
reader.skipTo(torsoPositions[i]);
TorsoImage torso =
DatBitmapReader.getImage(torsoTranslator, reader);
images[i].setTorso(torso);
}
}
settlersequences[index] = new ArraySequence<Image>(images);
}
private long[] readSequenceHeader(int position) throws IOException {
reader.skipTo(position);
reader.assumeToRead(START);
int frameCount = reader.read8();
long[] framePositions = new long[frameCount];
for (int i = 0; i < frameCount; i++) {
framePositions[i] = reader.read32() + position;
}
return framePositions;
}
@Override
public Sequence<LandscapeImage> getLandscapes() {
return landscapesequence;
}
@Override
public Sequence<GuiImage> getGuis() {
return guisequence;
}
/**
* This landscape image list loads the landscape images.
*
* @author michael
*/
private class LandscapeImageSequence implements Sequence<LandscapeImage> {
/**
* Forces a get of the image.
*/
@Override
public LandscapeImage getImage(int index) {
initializeIfNeeded();
if (landscapeimages[index] == null) {
loadLandscapeImage(index);
}
return landscapeimages[index];
}
@Override
public int length() {
initializeIfNeeded();
return landscapeimages.length;
}
@Override
public SingleImage getImageSafe(int index) {
initializeIfNeeded();
if (index < 0 || index >= length()) {
return NullImage.getInstance();
} else {
if (landscapeimages[index] == null) {
loadLandscapeImage(index);
}
return landscapeimages[index];
}
}
}
public ByteReader getReaderForLandscape(int index) throws IOException {
initializeIfNeeded();
reader.skipTo(landscapestarts[index]);
return reader;
}
private void loadLandscapeImage(int index) {
try {
reader.skipTo(landscapestarts[index]);
LandscapeImage image =
DatBitmapReader.getImage(landscapeTranslator, reader);
landscapeimages[index] = image;
} catch (IOException e) {
landscapeimages[index] = NullImage.getForLandscape();
}
}
/**
* This landscape image list loads the landscape images.
*
* @author michael
*/
private class GuiImageSequence implements Sequence<GuiImage> {
/**
* Forces a get of the image.
*/
@Override
public GuiImage getImage(int index) {
initializeIfNeeded();
if (guiimages[index] == null) {
loadGuiImage(index);
}
return guiimages[index];
}
@Override
public int length() {
initializeIfNeeded();
return guiimages.length;
}
@Override
public SingleImage getImageSafe(int index) {
initializeIfNeeded();
if (index < 0 || index >= length()) {
return NullImage.getInstance();
} else {
if (guiimages[index] == null) {
loadGuiImage(index);
}
return guiimages[index];
}
}
}
private void loadGuiImage(int index) {
try {
reader.skipTo(guistarts[index]);
GuiImage image = DatBitmapReader.getImage(guiTranslator, reader);
guiimages[index] = image;
} catch (IOException e) {
guiimages[index] = NullImage.getForGui();
}
}
public long[] getSettlerPointers(int seqindex) throws IOException {
initializeIfNeeded();
return readSequenceHeader(settlerstarts[seqindex]);
}
public long[] getTorsoPointers(int seqindex) throws IOException {
initializeIfNeeded();
int position = torsostarts[seqindex];
if (position >= 0) {
return readSequenceHeader(position);
} else {
return null;
}
}
/**
* Gets a reader positioned at the given settler
*
* @param pointer
* @return
* @throws IOException
*/
public ByteReader getReaderForPointer(long pointer) throws IOException {
initializeIfNeeded();
reader.skipTo(pointer);
return reader;
}
public void generateImageMap(int width, int height, int[] sequences,
String id) throws IOException {
initializeIfNeeded();
MultiImageMap map = new MultiImageMap(width, height, id);
if (!map.hasCache()) {
map.addSequences(this, sequences, settlersequences);
map.writeCache();
}
}
public DatBitmapTranslator<SettlerImage> getSettlerTranslator() {
return settlerTranslator;
}
public DatBitmapTranslator<TorsoImage> getTorsoTranslator() {
return torsoTranslator;
}
public DatBitmapTranslator<LandscapeImage> getLandscapeTranslator() {
return landscapeTranslator;
}
}