// 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.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; 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 uncompressed BIFF V1 archives. */ public class BIFFReader extends AbstractBIFFReader { private final WindowBlocker blocker; private int numFiles, numTilesets; protected BIFFReader(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 (!"BIFFV1 ".equals(sigver)) { throw new Exception("Invalid BIFF header"); } this.numFiles = StreamUtils.readInt(channel); this.numTilesets = StreamUtils.readInt(channel); int ofsFiles = StreamUtils.readInt(channel); ByteBuffer bb = StreamUtils.getByteBuffer(this.numFiles*0x10 + this.numTilesets*0x14); channel.position(ofsFiles); channel.read(bb); bb.position(0); init(bb, numFiles, numTilesets); } } @Override public Type getType() { return Type.BIFF; } @Override public int getFileCount() { return numFiles; } @Override public int getTilesetCount() { return numTilesets; } @Override public int getBIFFSize() { try { return (int)Files.size(getFile()); } catch (IOException e) { } return -1; } @Override public ByteBuffer getResourceBuffer(int locator) throws IOException { Entry entry = getEntry(locator); if (entry == null) { throw new IOException("Resource not found"); } ByteBuffer buffer; try (FileChannel channel = FileChannel.open(getFile(), StandardOpenOption.READ)) { channel.position(entry.offset); if (entry.isTile) { ByteBuffer header = getTisHeader(entry.count, entry.size); int remaining = entry.count*entry.size + header.limit(); if (remaining > 1000000) { blocker.setBlocked(true); } try { buffer = StreamUtils.getByteBuffer(remaining); StreamUtils.copyBytes(header, buffer, header.limit()); remaining -= header.limit(); while (channel.read(buffer) > 0); } finally { blocker.setBlocked(false); } } else { buffer = StreamUtils.getByteBuffer(entry.size); while (channel.read(buffer) > 0); } buffer.position(0); return buffer; } } @Override public InputStream getResourceAsStream(int locator) throws IOException { Entry entry = getEntry(locator); if (entry == null) { throw new IOException("Resource not found"); } try (FileChannel channel = FileChannel.open(getFile(), StandardOpenOption.READ)) { int size = entry.isTile ? entry.count*entry.size : entry.size; ByteBuffer buffer = channel.map(MapMode.READ_ONLY, entry.offset, size).order(ByteOrder.LITTLE_ENDIAN); InputStream is; if (entry.isTile) { ByteBuffer header = getTisHeader(entry.count, entry.size); is = new ByteBufferInputStream(header, buffer); } else { is = new ByteBufferInputStream(buffer); } return is; } } private void init(ByteBuffer buffer, int numFiles, int numTilesets) throws IOException { // reading file entries for (int i = 0; i < numFiles; i++) { int locator = buffer.getInt() & 0xfffff; int offset = buffer.getInt(); int size = buffer.getInt(); short type = buffer.getShort(); buffer.getShort(); // unknown data addEntry(new Entry(locator, offset, size, type)); } // reading tileset entries for (int i = 0; i < numTilesets; i++) { int locator = buffer.getInt() & 0xfffff; int offset = buffer.getInt(); int count = buffer.getInt(); int size = buffer.getInt(); short type = buffer.getShort(); buffer.getShort(); // unknown data addEntry(new Entry(locator, offset, count, size, type)); } } }