// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.resource.key; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.zip.InflaterInputStream; import org.infinity.NearInfinity; import org.infinity.gui.WindowBlocker; import org.infinity.util.io.ByteBufferInputStream; import org.infinity.util.io.StreamUtils; /** * Provides read operations for file-compressed BIF V1.0 archives. */ public class BIFReader extends AbstractBIFFReader { private final WindowBlocker blocker; private MappedByteBuffer mappedBuffer; private int uncSize, compSize, compOffset; private int numFiles, numTilesets; protected BIFReader(Path file) throws Exception { super(file); this.blocker = new WindowBlocker(NearInfinity.getInstance()); open(); } @Override public synchronized void open() throws Exception { try (FileChannel channel = FileChannel.open(getFile(), StandardOpenOption.READ)) { String sigver = StreamUtils.readString(channel, 8); if (!"BIF V1.0".equals(sigver)) { throw new Exception("Invalid BIFF header"); } int nameLength = StreamUtils.readInt(channel); channel.position(channel.position() + nameLength); this.uncSize = StreamUtils.readInt(channel); this.compSize = StreamUtils.readInt(channel); if (this.uncSize < 0 || this.compSize < 0) { throw new Exception("Invalid BIFF archive"); } this.compOffset = (int)channel.position(); mappedBuffer = channel.map(MapMode.READ_ONLY, compOffset, compSize); mappedBuffer.order(ByteOrder.LITTLE_ENDIAN); } init(); } @Override public Type getType() { return Type.BIF; } @Override public int getFileCount() { return numFiles; } @Override public int getTilesetCount() { return numTilesets; } @Override public int getBIFFSize() { return uncSize; } @Override public ByteBuffer getResourceBuffer(int locator) throws IOException { Entry entry = getEntry(locator); if (entry == null) { throw new IOException("Resource not found"); } ByteBuffer buffer; if (entry.isTile) { ByteBuffer header = getTisHeader(entry.count, entry.size); buffer = StreamUtils.getByteBuffer(entry.count*entry.size + header.limit()); StreamUtils.copyBytes(header, buffer, header.limit()); } else { buffer = StreamUtils.getByteBuffer(entry.size); } if (buffer.limit() > 1000000) { blocker.setBlocked(true); } try (InflaterInputStream iis = getInflaterInputStream()) { int remaining = entry.offset; while (remaining > 0) { long n = iis.skip(entry.offset); remaining -= n; } StreamUtils.readBytes(iis, buffer); } finally { blocker.setBlocked(false); } buffer.position(0); return buffer; } @Override public InputStream getResourceAsStream(int locator) throws IOException { return new ByteBufferInputStream(getResourceBuffer(locator)); } private void init() throws Exception { try (InflaterInputStream iis = new InflaterInputStream( new ByteBufferInputStream(mappedBuffer.duplicate()))) { int curOfs = 0; String sigver = StreamUtils.readString(iis, 8); if (!"BIFFV1 ".equals(sigver)) { throw new Exception("Invalid decompressed BIFF signature"); } this.numFiles = StreamUtils.readInt(iis); this.numTilesets = StreamUtils.readInt(iis); int entryOfs = StreamUtils.readInt(iis); curOfs += 20; if (entryOfs < curOfs) { throw new Exception("Invalid decompressed BIFF header"); } int remaining = entryOfs - curOfs; while (remaining > 0) { long n = iis.skip(entryOfs - curOfs); remaining -= n; } // reading file entries for (int i = 0; i < numFiles; i++) { int locator = StreamUtils.readInt(iis) & 0xfffff; int offset = StreamUtils.readInt(iis); int size = StreamUtils.readInt(iis); short type = StreamUtils.readShort(iis); iis.skip(2); // unknown data addEntry(new Entry(locator, offset, size, type)); } // reading tileset entries for (int i = 0; i < numTilesets; i++) { int locator = StreamUtils.readInt(iis) & 0xfffff; int offset = StreamUtils.readInt(iis); int count = StreamUtils.readInt(iis); int size = StreamUtils.readInt(iis); short type = StreamUtils.readShort(iis); iis.skip(2); // unknown data addEntry(new Entry(locator, offset, count, size, type)); } } } // Returns an inflater input stream private InflaterInputStream getInflaterInputStream() throws IOException { return new InflaterInputStream(new ByteBufferInputStream(mappedBuffer.duplicate())); } }