// 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.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.List; import org.infinity.gui.BrowserMenuBar; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.Writeable; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; public final class BIFFResourceEntry extends ResourceEntry implements Writeable { private final Path keyFile; // Full path to KEY file containing BIFF entry private final String resourceName; // Resource name without extension private final int type; // Resource type private final String extension; // cached resource extension private boolean hasOverride = false; private int locator; public BIFFResourceEntry(BIFFEntry bifEntry, String resourceName, int offset) { this.keyFile = ResourceFactory.getKeyfile().getKeyfile(); int p = resourceName.lastIndexOf('.'); if (p > 0) { this.resourceName = resourceName.substring(0, p); this.type = ResourceFactory.getKeyfile().getExtensionType(resourceName.substring(p+1)); this.extension = ResourceFactory.getKeyfile().getExtension(this.type); } else { throw new UnsupportedOperationException(); } int bifIndex = bifEntry.getIndex(); this.locator = bifIndex << 20; if (this.type == Keyfile.TYPE_TIS) { // TIS this.locator |= offset << 14; } else { this.locator |= offset; } } public BIFFResourceEntry(Path keyFile, ByteBuffer buffer, int offset, int stringLength) { if (keyFile == null || buffer == null) { throw new NullPointerException(); } this.keyFile = keyFile; this.resourceName = StreamUtils.readString(buffer, offset, stringLength); this.type = buffer.getShort() & 0xffff; String ext = ResourceFactory.getKeyfile().getExtension(type); if (ext == null) { ext = "Unknown (" + Integer.toHexString(type) + "h)"; } this.extension = ext; this.locator = buffer.getInt(); } // --------------------- Begin Interface Writeable --------------------- @Override public void write(OutputStream os) throws IOException { StreamUtils.writeString(os, resourceName, 8); StreamUtils.writeShort(os, (short)type); StreamUtils.writeInt(os, locator); } // --------------------- End Interface Writeable --------------------- @Override public boolean equals(Object o) { if (o == this) { return true; } else if (o instanceof BIFFResourceEntry) { BIFFResourceEntry other = (BIFFResourceEntry)o; return (locator == other.locator) && (type == other.type) && resourceName.equalsIgnoreCase(other.resourceName); } return false; } @Override public String toString() { return getResourceName(); } public Path getKeyfile() { return keyFile; } public void deleteOverride() throws IOException { List<Path> overrides = Profile.getOverrideFolders(false); Path file = FileManager.query(overrides, getResourceName()); if (file != null && Files.isRegularFile(file)) { Files.deleteIfExists(file); } file = FileManager.query(overrides, getResourceName()); synchronized (this) { hasOverride = (file != null && Files.isRegularFile(file)); } } @Override public Path getActualPath(boolean ignoreOverride) { if (!ignoreOverride) { List<Path> overrides = Profile.getOverrideFolders(false); Path file = FileManager.query(overrides, getResourceName()); if (file != null && Files.isRegularFile(file)) { return file; } } try { return ResourceFactory.getKeyfile().getBIFFFile(getBIFFEntry()).getFile(); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public long getResourceSize(boolean ignoreOverride) { long retVal = -1L; try { if (!ignoreOverride) { List<Path> overrides = Profile.getOverrideFolders(false); Path file = FileManager.query(overrides, getResourceName()); if (file != null && Files.isRegularFile(file)) { retVal = Files.size(file); return retVal; } } AbstractBIFFReader biff = ResourceFactory.getKeyfile().getBIFFFile(getBIFFEntry()); int[] info = biff.getResourceInfo(locator); if (info != null) { if (info.length == 1) { retVal = info[0]; } else if (info.length == 2) { retVal = info[0]*info[1] + 0x18; } } } catch (Exception e) { e.printStackTrace(); } return retVal; } public BIFFEntry getBIFFEntry() { int sourceIndex = (locator >> 20) & 0xfff; return ResourceFactory.getKeyfile().getBIFFEntry(keyFile, sourceIndex); } @Override public String getExtension() { return extension; } public int getLocator() { return locator; } @Override public ByteBuffer getResourceBuffer(boolean ignoreOverride) throws Exception { if (!ignoreOverride) { List<Path> overrides = Profile.getOverrideFolders(false); Path file = FileManager.query(overrides, getResourceName()); if (file != null && Files.isRegularFile(file)) { try (SeekableByteChannel ch = Files.newByteChannel(file, StandardOpenOption.READ)) { ByteBuffer bb = StreamUtils.getByteBuffer((int)ch.size()); if (ch.read(bb) < ch.size()) { throw new IOException(); } bb.position(0); return bb; } } } AbstractBIFFReader biff = ResourceFactory.getKeyfile().getBIFFFile(getBIFFEntry()); return biff.getResourceBuffer(locator); } @Override public InputStream getResourceDataAsStream(boolean ignoreOverride) throws Exception { if (!ignoreOverride) { List<Path> overrides = Profile.getOverrideFolders(false); Path file = FileManager.query(overrides, getResourceName()); if (file != null && Files.isRegularFile(file)) { return StreamUtils.getInputStream(file); } } AbstractBIFFReader biff = ResourceFactory.getKeyfile().getBIFFFile(getBIFFEntry()); return biff.getResourceAsStream(locator); } @Override public int[] getResourceInfo(boolean ignoreOverride) throws Exception { if (!ignoreOverride) { List<Path> overrides = Profile.getOverrideFolders(false); Path file = FileManager.query(overrides, getResourceName()); if (file != null && Files.isRegularFile(file)) { return getLocalFileInfo(file); } } AbstractBIFFReader biff = ResourceFactory.getKeyfile().getBIFFFile(getBIFFEntry()); return biff.getResourceInfo(locator); } @Override public String getResourceName() { return resourceName + '.' + extension; } @Override public String getTreeFolder() { if ((BrowserMenuBar.getInstance() != null) && (BrowserMenuBar.getInstance().getOverrideMode() == BrowserMenuBar.OVERRIDE_IN_OVERRIDE) && hasOverride()) { return Profile.getOverrideFolderName(); } return getExtension(); } public int getType() { return type; } @Override public boolean hasOverride() { // TODO: update dynamically via WatchService class? if (!BrowserMenuBar.getInstance().cacheOverride()) { List<Path> overrides = Profile.getOverrideFolders(false); Path file = FileManager.query(overrides, getResourceName()); synchronized (this) { hasOverride = (file != null && Files.isRegularFile(file)); } } return hasOverride; } public synchronized void setOverride(boolean hasOverride) { this.hasOverride = hasOverride; } synchronized void adjustSourceIndex(int index) { int sourceindex = (locator >> 20) & 0xfff; if (sourceindex > index) { sourceindex--; locator = (sourceindex << 20) | (locator & 0xfffff); } } }