/*
* Copyright (c) 2005 (Mike) Maurice Kienenberger (mkienenb@gmail.com)
*
* 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 org.gamenet.application.mm8leveleditor.mm6;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.Arrays;
import org.gamenet.application.mm8leveleditor.converter.FormatConverter;
import org.gamenet.application.mm8leveleditor.lod.LodEntry;
import org.gamenet.application.mm8leveleditor.lod.LodResource;
import org.gamenet.util.ByteConversions;
import com.mmbreakfast.unlod.lod.LodFile;
public class MM6VidEntry extends LodEntry
{
// entry header
protected static final int ENTRY_NAME__ENTRY_HEADER_OFFSET = 0x00;
protected static final int ENTRY_NAME__ENTRY_HEADER_MAX_LENGTH = 0x28;
protected static final int DATA_SEGMENT_OFFSET__ENTRY_HEADER_OFFSET = 0x28; // 4 bytes
private static final int WIDTH__DATA_HEADER_OFFSET = 0x04; // 4 bytes
private static final int HEIGHT__DATA_HEADER_OFFSET = 0x08; // 4 bytes
private static final int FRAMES__DATA_HEADER_OFFSET = 0x0C; // 4 bytes
private static final int MAYBE_TOTAL_TIME_IN_32BIT_FLOAT__DATA_HEADER_OFFSET = 0x10; // 4 bytes
private static final int SOUND_SAMPLE_RATE__DATA_HEADER_OFFSET = 0x4C; // 2 bytes
private static final int LARGEST_FRAME_SIZE__DATA_HEADER_OFFSET = 0x68; // 4 bytes
protected long dataLength = 0;
public MM6VidEntry(LodFile lodFile, long headerOffset)
throws IOException
{
super(lodFile, headerOffset);
}
protected void computeDataLength(RandomAccessFile raf, long entryIndex, long entryCount) throws IOException
{
long pastLastEntry = (getLodFile().getEntryHeaderLength() * entryCount) + getLodFile().getHeaderOffset();
long dataOffset = ByteConversions.getIntegerInByteArrayAtPosition(entryHeader, getOffsetInEntryHeaderForDataSegmentOffset());
long nextDataOffsetInEntryHeaderForDataSegmentOffset = + getLodFile().getFileHeader().length + (entryIndex + 1) * getLodFile().getEntryHeaderLength() + getOffsetInEntryHeaderForDataSegmentOffset();
long nextDataOffset = 0;
if (nextDataOffsetInEntryHeaderForDataSegmentOffset < pastLastEntry)
{
raf.seek(nextDataOffsetInEntryHeaderForDataSegmentOffset);
for (int longIndex = 0; longIndex < 4; longIndex++)
nextDataOffset += raf.read() << 8 * longIndex;
}
else
{
nextDataOffset = raf.length();
}
dataLength = nextDataOffset - dataOffset;
}
protected int getOffsetInDataHeaderForWidth()
{
return WIDTH__DATA_HEADER_OFFSET;
}
protected int getOffsetInDataHeaderForHeight()
{
return HEIGHT__DATA_HEADER_OFFSET;
}
protected int getOffsetInDataHeaderForFrameCount()
{
return FRAMES__DATA_HEADER_OFFSET;
}
protected int getOffsetInDataHeaderForTotalTime()
{
return MAYBE_TOTAL_TIME_IN_32BIT_FLOAT__DATA_HEADER_OFFSET;
}
protected int getOffsetInDataHeaderForSoundSampleRate()
{
return SOUND_SAMPLE_RATE__DATA_HEADER_OFFSET;
}
protected int getOffsetInDataHeaderForLargestFrameSize()
{
return LARGEST_FRAME_SIZE__DATA_HEADER_OFFSET;
}
protected long getWidth() throws IOException
{
int index = (int)getOffsetInDataHeaderForWidth();
if (-1 == index) return -1;
return ByteConversions.getIntegerInByteArrayAtPosition(getData(), index);
}
protected long getHeight() throws IOException
{
int index = (int)getOffsetInDataHeaderForHeight();
if (-1 == index) return -1;
return ByteConversions.getIntegerInByteArrayAtPosition(getData(), index);
}
protected long getFrameCount() throws IOException
{
int index = (int)getOffsetInDataHeaderForFrameCount();
if (-1 == index) return -1;
return ByteConversions.getIntegerInByteArrayAtPosition(getData(), index);
}
protected float getTotalTime() throws IOException
{
int index = (int)getOffsetInDataHeaderForTotalTime();
if (-1 == index) return -1;
return ByteConversions.getFloatInByteArrayAtPosition(getData(), index);
}
protected short getSoundSampleRate() throws IOException
{
int index = (int)getOffsetInDataHeaderForSoundSampleRate();
if (-1 == index) return -1;
return ByteConversions.getShortInByteArrayAtPosition(getData(), index);
}
protected long getLargestFrameSize() throws IOException
{
int index = (int)getOffsetInDataHeaderForLargestFrameSize();
if (-1 == index) return -1;
return ByteConversions.getIntegerInByteArrayAtPosition(getData(), index);
}
public String getTextDescription()
{
String dataDescription;
try {
dataDescription = "Width: "
+ getWidth()
+ "\n"
+ "Height: "
+ getHeight()
+ "\n"
+ "Frame Count: "
+ getFrameCount()
+ "\n"
+ "Total Time: "
+ getTotalTime()
+ "\n"
+ "Sound Sample Rate: "
+ getSoundSampleRate()
+ "\n"
+ "Largest Frame Size: "
+ getLargestFrameSize();
} catch (IOException e) {
dataDescription = "error reading data: " + e.getMessage();
}
return "Name: "
+ getName()
+ "\n"
+ "EntryName: "
+ getEntryName()
+ "\n"
+ "FileType: "
+ getFileType()
+ "\n"
+ "Data Length: "
+ getDataLength()
+ "\n"
+ "Data Offset: "
+ getDataOffset()
+ "\n"
+ dataDescription
;
// Human Temple0.smk from MM7 Might.vid
//w 460
//h 344
//frames: 86
//color depth: 8
//alpha plane: no
//fps: 12.00
//ms p f: 83.33
//total time: 0 min, 7 secs
//total size: 766624
//av data rate: 106975
//av frame size: 8914
//largest frame size: 111856
//highest one second data rate: 87492
//highest one second data rate start frame: 2
//ring frame: y
//y-interaced: no
//y-doubled: no
//track 2: 22050 KHz, 16 bit, Mono sound
//
//
//4 bytes
//SMK2
//
//4 bytes
//width
//
//4 bytes
//height
//
//4 bytes:
//frames
//
//4 bytes signed 32
//negative ms per frame?
//
//4 bytes boolean? 1
//
//4 bytes boolean? 0
//
//4 byte total time in 32 bit float format?
//
//5x4 zeros (4 32-bit zeros?)
//
//unknown 4 byte value?
//
//...
//
//offset x4c
//
//2 bytes khz sound
//
//offset x68
//largest frame size
}
protected void computeEntryBasedOffsets()
{
}
protected void computeDataBasedOffsets()
{
}
public long getDataOffset()
{
return ByteConversions.getIntegerInByteArrayAtPosition(entryHeader, getOffsetInEntryHeaderForDataSegmentOffset());
}
protected int getDataLength()
{
return (int)dataLength;
}
protected void readDataHeader() throws IOException
{
}
public byte[] getData() throws IOException
{
return readRawData();
}
public String getResourceType()
{
return getFileType();
}
protected int getOffsetInEntryHeaderForDataSegmentOffset()
{
return DATA_SEGMENT_OFFSET__ENTRY_HEADER_OFFSET;
}
public String getFileName()
{
String baseName = getEntryName();
String fileName = null;
if (baseName.toLowerCase().endsWith("." + getFileType().toLowerCase()))
{
fileName = baseName;
}
else
{
fileName = baseName + "." + getFileType();
}
String converterFileType = getFormatConverterFileType();
if (null != converterFileType)
{
if (false == fileName.toLowerCase().endsWith("." + converterFileType.toLowerCase()))
{
fileName = fileName + "." + converterFileType;
}
}
return fileName;
}
protected String getFileType()
{
if (getEntryName().toLowerCase().endsWith(".bik"))
return "bik";
if (getEntryName().toLowerCase().endsWith(".smk"))
return "smk";
return "smk";
}
public String getEntryName()
{
int offset = ENTRY_NAME__ENTRY_HEADER_OFFSET;
int maxLength = ENTRY_NAME__ENTRY_HEADER_MAX_LENGTH;
int length = 0;
while ( (0 != entryHeader[offset + length]) && (length < maxLength) )
length++;
return new String(entryHeader, offset, length);
}
public FormatConverter getFormatConverter()
{
return super.getFormatConverter();
}
public String getFormatConverterFileType()
{
return super.getFormatConverterFileType();
}
protected class NullOutputStream extends OutputStream
{
public void write(int b) throws IOException
{
}
}
protected int getNewDataLength(LodResource lodResourceDataSource) throws IOException
{
try
{
return lodResourceDataSource.getData().length;
}
catch (IOException exception)
{
// Doesn't throw exceptions
exception.printStackTrace();
return -1;
}
}
protected int getUpdatedDataLength(LodResource lodResourceDataSource) throws IOException
{
try
{
return lodResourceDataSource.getData().length;
}
catch (IOException exception)
{
// Doesn't throw exceptions
exception.printStackTrace();
return -1;
}
}
private long writeEntry(String name, long uncompressedDataLength, OutputStream outputStream, long entryListOffset, long entryOffset, long dataOffset) throws IOException
{
byte originalEntryHeader[] = getEntryHeader();
byte newEntryHeader[] = new byte[originalEntryHeader.length];
System.arraycopy(originalEntryHeader, 0, newEntryHeader, 0, originalEntryHeader.length);
if (null != name)
{
// update name
Arrays.fill(newEntryHeader, 0, ENTRY_NAME__ENTRY_HEADER_MAX_LENGTH, (byte)0);
byte[] entryNameBytes = name.getBytes();
System.arraycopy(entryNameBytes, 0, newEntryHeader, 0, Math.min(ENTRY_NAME__ENTRY_HEADER_MAX_LENGTH, entryNameBytes.length));
if (entryNameBytes.length < ENTRY_NAME__ENTRY_HEADER_MAX_LENGTH) newEntryHeader[entryNameBytes.length] = 0;
}
// update data offset
ByteConversions.setIntegerInByteArrayAtPosition(dataOffset, newEntryHeader, getOffsetInEntryHeaderForDataSegmentOffset());
// System.out.println(getName() + "- storing " + String.valueOf(dataOffset) + " in entry.dataoffset");
outputStream.write(newEntryHeader);
return dataOffset + uncompressedDataLength;
}
public long rewriteEntry(OutputStream outputStream, long entryListOffset, long entryOffset, long dataOffset) throws IOException
{
byte[] rawData = readRawData();
return writeEntry(null, rawData.length, outputStream, entryListOffset, entryOffset, dataOffset);
}
public long writeNewEntry(LodResource lodResourceDataSource, OutputStream outputStream, long entryListOffset, long entryOffset, long dataOffset) throws IOException
{
return writeEntry(this.getEntryName(), getNewDataLength(lodResourceDataSource), outputStream, entryListOffset, entryOffset, dataOffset);
}
public long updateEntry(LodResource lodResourceDataSource, OutputStream outputStream, long entryListOffset, long entryOffset, long dataOffset) throws IOException
{
return writeEntry(null, getUpdatedDataLength(lodResourceDataSource), outputStream, entryListOffset, entryOffset, dataOffset);
}
public long writeNewData(LodResource lodResourceDataSource, OutputStream outputStream, long dataOffset)
throws IOException
{
byte uncompressedData[] = lodResourceDataSource.getData();
// System.out.println(getName() + "- DS data size: " + uncompressedData.length);
outputStream.write(uncompressedData);
long newOffset = dataOffset + uncompressedData.length;
// System.out.println(getName() + " - nextoffset: " + newOffset);
return newOffset;
}
public long updateData(LodResource lodResourceDataSource, OutputStream outputStream, long dataOffset)
throws IOException
{
byte uncompressedData[] = lodResourceDataSource.getData();
// System.out.println(getName() + "- DS data size: " + uncompressedData.length);
outputStream.write(uncompressedData);
long newOffset = dataOffset + uncompressedData.length;
// System.out.println(getName() + " - nextoffset: " + newOffset);
return newOffset;
}
public long rewriteData(OutputStream outputStream, long dataOffset)
throws IOException
{
byte[] rawData = readRawData();
outputStream.write(rawData);
long newOffset = dataOffset + rawData.length;
// System.out.println(getName() + " - nextoffset: " + newOffset);
return newOffset;
}
protected long getDataHeaderOffset()
{
return 0;
}
public String getDataName()
{
return null;
}
}