// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.util.io; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.spi.FileSystemProvider; import java.util.HashMap; import org.infinity.util.io.zip.DlcFileSystem; import org.infinity.util.io.zip.DlcFileSystemProvider; /** * Manages available DLCs used by the current game. */ public class DlcManager { // list of supported KEY files private static final String[] KEY_FILES = {"mod.key", "chitin.key"}; private static DlcManager instance; private final HashMap<Path, FileSystem> fileSystems; /** * Checks whether the specified file is a valid DLC and registers it internally if not yet done. * Returns the {@code FileSystem} object created from the file. * @param dlcFile The {@link Path} object pointing to a DLC archive. * @return The {@link FileSystem} object created from the file or fetched from the cache * if it had been already registered. Returns {@code null} if the file does not point to * a valid DLC archive. */ public static FileSystem register(Path dlcFile) throws IOException { return getInstance()._register(dlcFile); } /** * Returns the FileSystem object created from the specified file if available. * @param dlcFile The file used to create a FileSystem object from. * @return A {@link FileSystem} object when available, {@code null} otherwise. */ public static FileSystem getDlc(Path dlcFile) { return getInstance()._getDlc(dlcFile); } /** * Attempts to find the {@code mod.key} in the specified {@code path}. * @param path Either a {@code DlcPath} object pointing to the root of the DLC filesystem or * the path to the DLC archive on the default filesystem. * @return {@code DlcPath} object pointing to the {@code mod.key} or {@code null} if not available. */ public static Path queryKey(Path path) { return getInstance()._queryKey(path); } /** Closes all available {@link FileSystem} objects and removes them from the cache. */ public static void close() { getInstance()._close(); } private DlcManager() { this.fileSystems = new HashMap<>(); } private FileSystem _register(Path dlcFile) throws IOException { FileSystem fs = _getDlc(dlcFile); if (fs == null) { fs = _validateDlc(dlcFile); if (fs != null) { fileSystems.put(dlcFile, fs); } } return fs; } private FileSystem _getDlc(Path dlcFile) { if (dlcFile != null && Files.isRegularFile(dlcFile)) { return fileSystems.get(dlcFile); } return null; } private Path _queryKey(Path path) { if (path != null) { FileSystem fs = path.getFileSystem(); if (!(fs instanceof DlcFileSystem)) { fs = _getDlc(path); } if (fs != null) { for (final String keyFile: KEY_FILES) { Path key = fs.getPath(keyFile); if (key != null && Files.isRegularFile(key)) { try (InputStream is = StreamUtils.getInputStream(key)) { String sig = StreamUtils.readString(is, 8); if ("KEY V1 ".equals(sig)) { return key; } } catch (IOException e) { e.printStackTrace(); } } } } } return null; } private FileSystem _validateDlc(Path dlcFile) throws IOException { if (dlcFile == null || !Files.isRegularFile(dlcFile)) { return null; } // checking first 3 LOC entries for compatibility try (SeekableByteChannel ch = Files.newByteChannel(dlcFile)) { ByteBuffer buffer = StreamUtils.getByteBuffer(30); for (int i = 0; i < 3; i++) { buffer.compact().position(0); if (ch.read(buffer) != buffer.limit()) { return null; } buffer.flip(); if (buffer.getInt(0) != 0x04034b50) { // signature return null; } if ((buffer.getShort(4) & 0xffff) > 20) { // version return null; } if ((buffer.getShort(8) & 0xffff) != 0) { // compression return null; } if (buffer.getInt(18) == -1 || buffer.getInt(22) == -1) { // contains zip64 header? return null; } long skip = (long)buffer.getInt(18) & 0xffffffffL; skip += (buffer.getShort(26) & 0xffff); skip += (buffer.getShort(28) & 0xffff); ch.position(ch.position() + skip); } } catch (Throwable t) { return null; } FileSystemProvider provider = new DlcFileSystemProvider(); FileSystem fs = null; try { fs = provider.newFileSystem(dlcFile, null); Path key = _queryKey(fs.getPath("/")); if (key != null) { return fs; } } catch (Throwable t) { if (fs != null) { try { fs.close(); } catch (Throwable t2) { } fs = null; } } return null; } private void _close() { for (final FileSystem fs: fileSystems.values()) { try { fs.close(); } catch (IOException e) { } } fileSystems.clear(); } private static DlcManager getInstance() { if (instance == null) { instance = new DlcManager(); } return instance; } }