/* * 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.ByteArrayOutputStream; 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.converter.StrToTextFormatConverter; 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; import com.mmbreakfast.unlod.lod.Extractor; import com.mmbreakfast.unlod.lod.PassThroughLodFileExtractor; public class MM6NewLodEntry extends LodEntry { protected Extractor thePassThroughLodFileExtractor = new PassThroughLodFileExtractor(); protected Extractor theLodFileExtractor = new Extractor(); // entry header protected static final int ENTRY_NAME__ENTRY_HEADER_OFFSET = 0x00; protected static final int ENTRY_NAME__ENTRY_HEADER_MAX_LENGTH = 0x0c; protected static final int DATA_SEGMENT_OFFSET__ENTRY_HEADER_OFFSET = 0x10; protected static final int DATA_SEGMENT_LENGTH__ENTRY_HEADER_OFFSET = 0x14; // really data_header + data_context length, includes palette for tga // data header protected static final int COMPRESSION_HEADER[] = { 120, 156 }; protected static final int MM6_DATA_HEADER_LENGTH_UNCOMPRESSED = 0x00; // if not compressed protected static final int MM6_DATA_HEADER_LENGTH_COMPRESSED = 0x08; // if compressed protected static final int MM6_DATA_CONTENT_COMPRESSED_SIZE__DATA_HEADER_OFFSET = 0x00; protected static final int MM6_DATA_CONTENT_UNCOMPRESSED_SIZE__DATA_HEADER_OFFSET = 0x04; protected Extractor theFileExtractor = new Extractor(); protected Boolean isCompressed; public MM6NewLodEntry(LodFile lodFile, long headerOffset) throws IOException { super(lodFile, headerOffset); } public String getTextDescription() { String fileType = getFileType(); return "Name: " + getName() + "\n" + "EntryName: " + getEntryName() + "\n" + "DataName: " + getDataName() + "\n" + "FileType: " + fileType + "\n" + "Data Length: " + getDataLength() + "\n" + "Decompressed Size: " + getDecompressedSize() + "\n" + "Data Header Offset: " + getDataHeaderOffset() + "\n" + "Data Offset: " + getDataOffset() ; } protected boolean isCompressed() { return isCompressed.booleanValue(); } protected void computeEntryBasedOffsets() { RandomAccessFile raf = lodFile.getRandomAccessFileInputStream().getFile(); long possibleCompressionHeaderOffset = this.getDataHeaderOffset() + MM6_DATA_HEADER_LENGTH_COMPRESSED; try { raf.seek(possibleCompressionHeaderOffset); boolean matchedSignature = true; for (int i = 0; i < COMPRESSION_HEADER.length; i++) { int aByte = raf.read(); if (aByte != COMPRESSION_HEADER[i]) { isCompressed = Boolean.FALSE; } } if (null == isCompressed) isCompressed = Boolean.TRUE; } catch (IOException exception) { isCompressed = Boolean.FALSE; } dataHeader = new byte[getDataHeaderLength()]; } protected void computeDataBasedOffsets() { } public long getDataOffset() { return getDataHeaderOffset() + getDataHeaderLength(); } protected int getDataLength() { int length = ByteConversions.getIntegerInByteArrayAtPosition(entryHeader, DATA_SEGMENT_LENGTH__ENTRY_HEADER_OFFSET) - getDataHeaderLength(); return length; } protected long getDataHeaderOffset() { return getLodFile().getHeaderOffset() + ByteConversions.getIntegerInByteArrayAtPosition(entryHeader, getOffsetInEntryHeaderForDataSegmentOffset()); } public byte[] getData() throws IOException { byte[] rawData = readRawData(); if (0 == getDecompressedSize()) return rawData; else { ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); theLodFileExtractor.decompress(null, rawData, byteStream, null, true); return byteStream.toByteArray(); } } public String getResourceType() { return getFileType(); } protected int getOffsetInEntryHeaderForDataSegmentOffset() { return DATA_SEGMENT_OFFSET__ENTRY_HEADER_OFFSET; } protected int getOffsetInEntryHeaderForDataSegmentLength() { return DATA_SEGMENT_LENGTH__ENTRY_HEADER_OFFSET; } protected int getDataHeaderLength() { if (isCompressed()) return MM6_DATA_HEADER_LENGTH_COMPRESSED; else return MM6_DATA_HEADER_LENGTH_UNCOMPRESSED; } protected long getOffsetInDataHeaderFortDataContentCompressedLength() { if (isCompressed()) return MM6_DATA_CONTENT_COMPRESSED_SIZE__DATA_HEADER_OFFSET; else return -1; } protected long getOffsetInDataHeaderFortDataContentUncompressedLength() { if (isCompressed()) return MM6_DATA_CONTENT_UNCOMPRESSED_SIZE__DATA_HEADER_OFFSET; else return -1; } 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 getFileTypex() { String fileType = getEntryName().toLowerCase(); int dotPos = fileType.lastIndexOf('.'); if (-1 == dotPos) return ""; if ((fileType.length() - 1) == dotPos) return ""; return fileType.substring(dotPos + 1).toLowerCase(); } protected String getFileType() { String fileType = getEntryName().toLowerCase(); int dotPos = fileType.lastIndexOf('.'); if (-1 == dotPos) return ""; if ((fileType.length() - 1) == dotPos) return ""; return fileType.substring(dotPos + 1).toLowerCase(); } 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 String getDataName() { return null; } protected long getDecompressedSize() { int index = (int)getOffsetInDataHeaderFortDataContentUncompressedLength(); if (-1 == index) return 0; return ByteConversions.getIntegerInByteArrayAtPosition(dataHeader, index); } protected Extractor getExtractor() { if (0 == getDecompressedSize()) return thePassThroughLodFileExtractor; else return theLodFileExtractor; } public FormatConverter getFormatConverter() { if (getFileType().equals("str")) return new StrToTextFormatConverter(); else return super.getFormatConverter(); } public String getFormatConverterFileType() { return super.getFormatConverterFileType(); } protected class NullOutputStream extends OutputStream { public void write(int b) throws IOException { } } protected int getNewWrittenDataContentLength(LodResource lodResourceDataSource) { try { return (int)writeNewData(lodResourceDataSource, new NullOutputStream(), 0); } catch (IOException exception) { // Doesn't throw exceptions exception.printStackTrace(); return -1; } } protected int getUpdatedWrittenDataContentLength(LodResource lodResourceDataSource) { try { return (int)updateData(lodResourceDataSource, new NullOutputStream(), 0); } catch (IOException exception) { // Doesn't throw exceptions exception.printStackTrace(); return -1; } } private long writeEntry(String name, long writtenDataLength, 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 - entryListOffset), newEntryHeader, getOffsetInEntryHeaderForDataSegmentOffset()); // System.out.println(getName() + "- storing " + String.valueOf(dataOffset - entryListOffset) + " in entry.dataoffset"); // System.out.println(getName() + "- datacontentlength: " + writtenDataLength); ByteConversions.setIntegerInByteArrayAtPosition(writtenDataLength, newEntryHeader, (int)this.getOffsetInEntryHeaderForDataSegmentLength()); // System.out.println(getName() + "- storing " + String.valueOf(writtenDataLength) + " in entry.datalength"); outputStream.write(newEntryHeader); return dataOffset + writtenDataLength; } public long rewriteEntry(OutputStream outputStream, long entryListOffset, long entryOffset, long dataOffset) throws IOException { byte[] rawData = readRawData(); long dataLength = getDataHeaderLength() + rawData.length; long newDataOffset = writeEntry(null, dataLength, outputStream, entryListOffset, entryOffset, dataOffset); return newDataOffset; } public long writeNewEntry(LodResource lodResourceDataSource, OutputStream outputStream, long entryListOffset, long entryOffset, long dataOffset) throws IOException { return writeEntry(this.getEntryName(), getNewWrittenDataContentLength(lodResourceDataSource), outputStream, entryListOffset, entryOffset, dataOffset); } public long updateEntry(LodResource lodResourceDataSource, OutputStream outputStream, long entryListOffset, long entryOffset, long dataOffset) throws IOException { return writeEntry(null, getUpdatedWrittenDataContentLength(lodResourceDataSource), outputStream, entryListOffset, entryOffset, dataOffset); } /** * @param outputStream * @param dataOffset * @return */ protected long writeNewDataHeader(LodResource lodResourceDataSource, OutputStream outputStream, long dataOffset, long compressedDataLength, long uncompressedDataLength) throws IOException { if (false == isCompressed()) return dataOffset; byte originalDataHeader[] = this.getDataHeader(); byte newDataHeader[] = new byte[originalDataHeader.length]; System.arraycopy( originalDataHeader, 0, newDataHeader, 0, originalDataHeader.length); if (-1 != getOffsetInDataHeaderFortDataContentCompressedLength()) ByteConversions.setIntegerInByteArrayAtPosition( compressedDataLength, newDataHeader, (int)this.getOffsetInDataHeaderFortDataContentCompressedLength()); // For uncompressed data, write 0 for uncompressed data length if (-1 != getOffsetInDataHeaderFortDataContentUncompressedLength()) ByteConversions.setIntegerInByteArrayAtPosition( uncompressedDataLength, newDataHeader, (int)this.getOffsetInDataHeaderFortDataContentUncompressedLength()); long newDataOffset = dataOffset + newDataHeader.length; outputStream.write(newDataHeader); return newDataOffset; } protected long updateDataHeader(LodResource lodResourceDataSource, OutputStream outputStream, long dataOffset, long compressedDataLength, long uncompressedDataLength) throws IOException { if (false == isCompressed()) return dataOffset; byte originalDataHeader[] = this.getDataHeader(); byte newDataHeader[] = new byte[originalDataHeader.length]; System.arraycopy( originalDataHeader, 0, newDataHeader, 0, originalDataHeader.length); if (-1 != getOffsetInDataHeaderFortDataContentCompressedLength()) ByteConversions.setIntegerInByteArrayAtPosition( compressedDataLength, newDataHeader, (int)this.getOffsetInDataHeaderFortDataContentCompressedLength()); // For uncompressed data, write 0 for uncompressed data length if (-1 != getOffsetInDataHeaderFortDataContentUncompressedLength()) ByteConversions.setIntegerInByteArrayAtPosition( uncompressedDataLength, newDataHeader, (int)this.getOffsetInDataHeaderFortDataContentUncompressedLength()); long newDataOffset = dataOffset + newDataHeader.length; outputStream.write(newDataHeader); return newDataOffset; } public long writeNewData(LodResource lodResourceDataSource, OutputStream outputStream, long dataOffset) throws IOException { byte uncompressedData[] = lodResourceDataSource.getData(); // System.out.println(getName() + "- DS data size: " + uncompressedData.length); byte compressedData[] = theLodFileExtractor.compress(uncompressedData); byte smallestData[] = null; int uncompressedDataLengthSpecifier; if (isCompressed()) { smallestData = compressedData; uncompressedDataLengthSpecifier = uncompressedData.length; } else { smallestData = uncompressedData; uncompressedDataLengthSpecifier = 0; } // System.out.println(getName() + "- rawdataLength: " + smallestData.length); long newDataOffset = writeNewDataHeader(lodResourceDataSource, outputStream, dataOffset, smallestData.length, uncompressedDataLengthSpecifier); outputStream.write(smallestData); long newOffset = newDataOffset + smallestData.length; // System.out.println(getName() + " - writeData newOffset: " + 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); byte compressedData[] = theLodFileExtractor.compress(uncompressedData); byte smallestData[] = null; int uncompressedDataLengthSpecifier; if (isCompressed()) { smallestData = compressedData; uncompressedDataLengthSpecifier = uncompressedData.length; } else { smallestData = uncompressedData; uncompressedDataLengthSpecifier = 0; } // System.out.println(getName() + "- rawdataLength: " + smallestData.length); long newDataOffset = updateDataHeader(lodResourceDataSource, outputStream, dataOffset, smallestData.length, uncompressedDataLengthSpecifier); outputStream.write(smallestData); long newOffset = newDataOffset + smallestData.length; // System.out.println(getName() + " - writeData newOffset: " + newOffset); return newOffset; } public long rewriteData(OutputStream outputStream, long dataOffset) throws IOException { outputStream.write(getDataHeader()); byte[] rawData = readRawData(); outputStream.write(rawData); long newOffset = dataOffset + getDataHeaderLength() + rawData.length; // System.out.println(getName() + " - rewriteData newOffset: " + newOffset); return newOffset; } }