// 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; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.stream.Stream; import org.infinity.NearInfinity; import org.infinity.resource.key.ResourceTreeFolder; import org.infinity.resource.key.ResourceTreeModel; import org.infinity.util.ObjectString; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; import org.infinity.util.io.DlcManager; import org.infinity.util.io.FileManager; /** * Provides engine- and game-specific properties of the currently opened Infinity Engine game.<br> * <br> * Properties can be accessed by unique identifiers. The returned property can be * of any type defined by the enum {@link Profile.Type} or {@code null}. */ public final class Profile { /** Supported data types for properties. */ public enum Type { /** Property data is of type {@link Boolean}. */ BOOLEAN, /** Property data is of type {@link Integer}. */ INTEGER, /** Property data is of type {@link String}. */ STRING, /** Property data is of type {@link java.util.List}. */ LIST, /** Property data is of type {@link java.nio.file.Path}. */ PATH, /** Property data is of any custom data type. */ OBJECT, } /** Supported Infinity Engine games. */ public enum Game { /** Default for unrecognized or unsupported engine. */ Unknown, /** Baldur's Gate */ BG1, /** Baldur's Gate: Tales of the Sword Coast */ BG1TotSC, /** Baldur's Gate II: Shadows of Amn */ BG2SoA, /** Baldur's Gate II: Throne of Bhaal */ BG2ToB, /** BGTutu and EasyTutu */ Tutu, /** Baldur's Gate Trilogy */ BGT, /** Planescape: Torment */ PST, /** Icewind Dale */ IWD, /** Icewind Dale: Heart of Winter */ IWDHoW, /** Icewind Dale: Trials of the Luremaster */ IWDHowToTLM, /** Icewind Dale II */ IWD2, /** Baldur's Gate: Enhanced Edition */ BG1EE, /** Baldur's Gate: Siege of Dragonspear */ BG1SoD, /** Baldur's Gate II: Enhanced Edition */ BG2EE, /** Icewind Dale: Enhanced Edition */ IWDEE, /** Enhanced Edition Trilogy */ EET, } /** Supported Infinity Engine types by feature levels. */ public enum Engine { /** Default for unrecognized or unsupported games. */ Unknown, /** Includes BG1 and BG1TotSC. */ BG1, /** Includes BG2SoA, BG2ToB, Tutu and BGT. */ BG2, /** Includes PST. */ PST, /** Includes IWD, IWDHoW and IWDTotLM. */ IWD, /** Includes IWD2. */ IWD2, /** Includes BG1EE, BG1SoD, BG2EE, IWDEE and EET. */ EE, } /** * Keys for querying global, game- or engine-specific properties. */ public enum Key { // Static properties /** Property: ({@code String}) Current Near Infinity version. */ GET_GLOBAL_NEARINFINITY_VERSION, /** Property: ({@code List<Game>}) List of supported games. */ GET_GLOBAL_GAMES, /** Property: ({@code String}) The name of the override folder ("{@code Override}"). */ GET_GLOBAL_OVERRIDE_NAME, /** Property: ({@code String}) The name of the parent language folder in Enhanced Edition games ("{@code Lang}"). */ GET_GLOBAL_LANG_NAME, /** Property: ({@code String}) Returns "{@code dialog.tlk}". */ GET_GLOBAL_DIALOG_NAME, /** Property: ({@code String}) Returns "{@code dialogf.tlk}". */ GET_GLOBAL_DIALOG_NAME_FEMALE, // Static properties which require an additional parameter. /** Property: ({@code String}) Returns the game's title. Extra parameter: Desired {@link Game}. */ GET_GLOBAL_GAME_TITLE, /** Property: ({@code List<String>}) Returns a list of extra folders for the specified game. * Extra parameter: Desired {@link Game}. */ GET_GLOBAL_EXTRA_FOLDER_NAMES, /** Property: ({@code String}) Returns the game's home folder name. * Extra parameter: Desired <em>Enhanced Edition</em> {@link Game}. */ GET_GLOBAL_HOME_FOLDER_NAME, // Properties set at runtime /** Property: ({@link Game}) Game identifier. */ GET_GAME_TYPE, /** Property: ({@link Engine}) Engine identifier. */ GET_GAME_ENGINE, /** Property: ({@code String}) Name of the game's root folder. */ GET_GAME_ROOT_FOLDER_NAME, /** Property: ({@code String}) Name of the game's home folder. (Enhanced Editions only) */ GET_GAME_HOME_FOLDER_NAME, /** Property: ({@code String}) Name of the currently selected game language folder. (Enhanced Editions only) */ GET_GAME_LANG_FOLDER_NAME, /** Property: ({@code List<String>}) List of available languages as language code * for the current game. (Enhanced Editions only) */ GET_GAME_LANG_FOLDER_NAMES_AVAILABLE, /** Property: ({@code List<Path>}) List of valid root folder, sorted by priority in descending order. */ GET_GAME_ROOT_FOLDERS_AVAILABLE, /** Property: ({@code Path}) Game's root folder. */ GET_GAME_ROOT_FOLDER, /** Property: ({@code List<Path>}) List of available DLC root folders. (Enhanced Editions only) */ GET_GAME_DLC_FOLDERS_AVAILABLE, /** Property: ({@code Path}) Game's home folder. (Enhanced Editions only) */ GET_GAME_HOME_FOLDER, /** Property: ({@code Path}) Game's language folder. (Enhanced Editions only) */ GET_GAME_LANG_FOLDER, /** Property: ({@code List<Path>}) List of available game language folders. (Enhanced Editions only) */ GET_GAME_LANG_FOLDERS_AVAILABLE, /** Property: ({@code Path}) Game's language root folder (where the actual language subfolder reside). * (Enhanced Editions only) */ GET_GAME_LANG_FOLDER_BASE, /** Property: ({@code List<Path>}) List of override folders to search for game resources, * sorted by priority in descending order. */ GET_GAME_OVERRIDE_FOLDERS, /** Property: ({@code List<String>}) List of extra folder names containing game-related resources, * sorted alphabetically in ascending order. */ GET_GAME_EXTRA_FOLDER_NAMES, /** Property: ({@code List<Path>}) List of extra folders containing game-related resources, * sorted by root folder priority (in descending order) and * alphabetically in ascending order. */ GET_GAME_EXTRA_FOLDERS, /** Property: ({@code Path}) The game's {@code chitin.key}. */ GET_GAME_CHITIN_KEY, /** Property: ({@code List<Path>}) List of {@code mod.key} files of available DLCs * sorted by priority in ascending order. (Enhanced Edition only) */ GET_GAME_DLC_KEYS_AVAILABLE, /** Property: ({@code String}) Title of the game. */ GET_GAME_TITLE, /** Property: ({@code String}) A short user-defined description or name of the game. * Can be used to tell specific game installations apart. */ GET_GAME_DESC, /** Property: ({@code String}) Name of the game's ini file. */ GET_GAME_INI_NAME, /** Property: ({@code Path}) Path of the game's ini file. */ GET_GAME_INI_FILE, /** Property: ({@code Path}) Path to the currently selected {@code dialog.tlk}. */ GET_GAME_DIALOG_FILE, /** Property: ({@code Path}) Path to the currently selected female {@code dialogf.tlk}. * Returns {@code null} if the language does not require a dialogf.tlk. */ GET_GAME_DIALOGF_FILE, /** Property: ({@code List<Path>}) List of folders containing BIFF archives. * (Sorted by priority in descending order for Enhanced Editions, * sorted by entries found in ini file for non-enhanced games) */ GET_GAME_BIFF_FOLDERS, /** Property: ({@code Boolean}) Is game an Enhanced Edition game? */ IS_ENHANCED_EDITION, /** Property: ({@code Boolean}) Has current game been enhanced by TobEx? */ IS_GAME_TOBEX, /** Property: ({@code Boolean}) Are {@code 2DA} resources supported? */ IS_SUPPORTED_2DA, /** Property: ({@code Boolean}) Are {@code ACM} resources supported? */ IS_SUPPORTED_ACM, /** Property: ({@code Boolean}) Are {@code ARE V1.0} resources supported? */ IS_SUPPORTED_ARE_V10, /** Property: ({@code Boolean}) Are {@code ARE V9.1} resources supported? */ IS_SUPPORTED_ARE_V91, /** Property: ({@code Boolean}) Are {@code BAM V1} resources supported? */ IS_SUPPORTED_BAM_V1, /** Property: ({@code Boolean}) Are {@code BAM V1} resources supported? */ IS_SUPPORTED_BAMC_V1, /** Property: ({@code Boolean}) Are {@code BAM V2} resources supported? */ IS_SUPPORTED_BAM_V2, /** Property: ({@code Boolean}) Are {@code BCS} resources supported? */ IS_SUPPORTED_BCS, /** Property: ({@code Boolean}) Are uncompressed {@code BIFF V1} resources supported? */ IS_SUPPORTED_BIFF, /** Property: ({@code Boolean}) Are compressed {@code BIF V1.0} resources supported? */ IS_SUPPORTED_BIF, /** Property: ({@code Boolean}) Are compressed {@code BIFC V1.0} resources supported? */ IS_SUPPORTED_BIFC, /** Property: ({@code Boolean}) Are {@code BIK} resources supported? */ IS_SUPPORTED_BIK, /** Property: ({@code Boolean}) Are {@code BIO} resources supported? */ IS_SUPPORTED_BIO, /** Property: ({@code Boolean}) Are paletted {@code BMP} resources supported? */ IS_SUPPORTED_BMP_PAL, /** Property: ({@code Boolean}) Are alpha-blended {@code BMP} resources supported? */ IS_SUPPORTED_BMP_ALPHA, /** Property: ({@code Boolean}) Are {@code CHR V1.0} resources supported? */ IS_SUPPORTED_CHR_V10, /** Property: ({@code Boolean}) Are {@code CHR V2.0} resources supported? */ IS_SUPPORTED_CHR_V20, /** Property: ({@code Boolean}) Are {@code CHR V2.1} resources supported? */ IS_SUPPORTED_CHR_V21, /** Property: ({@code Boolean}) Are {@code CHR V2.2} resources supported? */ IS_SUPPORTED_CHR_V22, /** Property: ({@code Boolean}) Are {@code CHU} resources supported? */ IS_SUPPORTED_CHU, /** Property: ({@code Boolean}) Are {@code CRE V1.0} resources supported? */ IS_SUPPORTED_CRE_V10, /** Property: ({@code Boolean}) Are {@code CRE V1.2} resources supported? */ IS_SUPPORTED_CRE_V12, /** Property: ({@code Boolean}) Are {@code CRE V2.2} resources supported? */ IS_SUPPORTED_CRE_V22, /** Property: ({@code Boolean}) Are {@code CRE V9.0} resources supported? */ IS_SUPPORTED_CRE_V90, /** Property: ({@code Boolean}) Are {@code DLG} resources supported? */ IS_SUPPORTED_DLG, /** Property: ({@code Boolean}) Are {@code EFF} resources supported? */ IS_SUPPORTED_EFF, /** Property: ({@code Boolean}) Are {@code FNT} resources supported? */ IS_SUPPORTED_FNT, /** Property: ({@code Boolean}) Are {@code GAM V1.1} resources supported? */ IS_SUPPORTED_GAM_V11, /** Property: ({@code Boolean}) Are {@code GAM V2.0} resources supported? */ IS_SUPPORTED_GAM_V20, /** Property: ({@code Boolean}) Are {@code GAM V2.1} resources supported? */ IS_SUPPORTED_GAM_V21, /** Property: ({@code Boolean}) Are {@code GAM V2.2} resources supported? */ IS_SUPPORTED_GAM_V22, /** Property: ({@code Boolean}) Are {@code GLSL} resources supported? */ IS_SUPPORTED_GLSL, /** Property: ({@code Boolean}) Are {@code GUI} resources supported? */ IS_SUPPORTED_GUI, /** Property: ({@code Boolean}) Are {@code IDS} resources supported? */ IS_SUPPORTED_IDS, /** Property: ({@code Boolean}) Are {@code INI} resources supported? */ IS_SUPPORTED_INI, /** Property: ({@code Boolean}) Are {@code ITM V1.0} resources supported? */ IS_SUPPORTED_ITM_V10, /** Property: ({@code Boolean}) Are {@code ITM V1.1} resources supported? */ IS_SUPPORTED_ITM_V11, /** Property: ({@code Boolean}) Are {@code ITM V2.0} resources supported? */ IS_SUPPORTED_ITM_V20, /** Property: ({@code Boolean}) Are {@code KEY} resources supported? */ IS_SUPPORTED_KEY, /** Property: ({@code Boolean}) Are {@code LUA} resources supported? */ IS_SUPPORTED_LUA, /** Property: ({@code Boolean}) Are {@code MENU} resources supported? */ IS_SUPPORTED_MENU, /** Property: ({@code Boolean}) Are {@code MOS V1} resources supported? */ IS_SUPPORTED_MOS_V1, /** Property: ({@code Boolean}) Are {@code MOSC V1} resources supported? */ IS_SUPPORTED_MOSC_V1, /** Property: ({@code Boolean}) Are {@code MOS V2} resources supported? */ IS_SUPPORTED_MOS_V2, /** Property: ({@code Boolean}) Are {@code MUS} resources supported? */ IS_SUPPORTED_MUS, /** Property: ({@code Boolean}) Are {@code MVE} resources supported? */ IS_SUPPORTED_MVE, /** Property: ({@code Boolean}) Are {@code OGG} resources supported? */ IS_SUPPORTED_OGG, /** Property: ({@code Boolean}) Are {@code PLT} resources supported? */ IS_SUPPORTED_PLT, /** Property: ({@code Boolean}) Are {@code PNG} resources supported? */ IS_SUPPORTED_PNG, /** Property: ({@code Boolean}) Are {@code PRO} resources supported? */ IS_SUPPORTED_PRO, /** Property: ({@code Boolean}) Are {@code PVRZ} resources supported? */ IS_SUPPORTED_PVRZ, /** Property: ({@code Boolean}) Are {@code RES} resources supported? */ IS_SUPPORTED_RES, /** Property: ({@code Boolean}) Are {@code SAV} resources supported? */ IS_SUPPORTED_SAV, /** Property: ({@code Boolean}) Are {@code SPL V1} resources supported? */ IS_SUPPORTED_SPL_V1, /** Property: ({@code Boolean}) Are {@code SPL V2} resources supported? */ IS_SUPPORTED_SPL_V2, /** Property: ({@code Boolean}) Are {@code SQL} resources supported? */ IS_SUPPORTED_SQL, /** Property: ({@code Boolean}) Are (PST) {@code SRC} resources supported? */ IS_SUPPORTED_SRC_PST, /** Property: ({@code Boolean}) Are (IWD2) {@code SRC} resources supported? */ IS_SUPPORTED_SRC_IWD2, /** Property: ({@code Boolean}) Are {@code STO V1.0} resources supported? */ IS_SUPPORTED_STO_V10, /** Property: ({@code Boolean}) Are {@code STO V1.1} resources supported? */ IS_SUPPORTED_STO_V11, /** Property: ({@code Boolean}) Are {@code STO V9.0} resources supported? */ IS_SUPPORTED_STO_V90, /** Property: ({@code Boolean}) Are (palette-based) {@code TIS V1} resources supported? */ IS_SUPPORTED_TIS_V1, /** Property: ({@code Boolean}) Are (PVRZ-based) {@code TIS V2} resources supported? */ IS_SUPPORTED_TIS_V2, /** Property: ({@code Boolean}) Are {@code TLK} resources supported? */ IS_SUPPORTED_TLK, /** Property: ({@code Boolean}) Are {@code TO V1} (TOH/TOT) resources supported? */ IS_SUPPORTED_TO_V1, /** Property: ({@code Boolean}) Are {@code TO V2} (TOH only) resources supported? */ IS_SUPPORTED_TO_V2, /** Property: ({@code Boolean}) Are {@code TTF} resources supported? */ IS_SUPPORTED_TTF, /** Property: ({@code Boolean}) Are {@code VAR} resources supported? */ IS_SUPPORTED_VAR, /** Property: ({@code Boolean}) Are {@code VEF} resources supported? */ IS_SUPPORTED_VEF, /** Property: ({@code Boolean}) Are {@code VVC} resources supported? */ IS_SUPPORTED_VVC, /** Property: ({@code Boolean}) Are {@code WAV} resources supported? */ IS_SUPPORTED_WAV, /** Property: ({@code Boolean}) Are {@code WAVC} resources supported? */ IS_SUPPORTED_WAVC, /** Property: ({@code Boolean}) Are {@code WBM} resources supported? */ IS_SUPPORTED_WBM, /** Property: ({@code Boolean}) Are {@code WED} resources supported? */ IS_SUPPORTED_WED, /** Property: ({@code Boolean}) Are {@code WFX} resources supported? */ IS_SUPPORTED_WFX, /** Property: ({@code Boolean}) Are {@code WMP} resources supported? */ IS_SUPPORTED_WMP, /** Property: ({@code Boolean}) Are Kits supported? */ IS_SUPPORTED_KITS, /** Property: ({@code String}) The name of the ALIGNMENT IDS resource. */ GET_IDS_ALIGNMENT, /** Property: ({@code Boolean}) Indices whether overlays in tilesets are stenciled. */ IS_TILESET_STENCILED, } // Container for Property entries private static final EnumMap<Key, Profile.Property> properties = new EnumMap<>(Key.class); // Unique titles for all supported games private static final EnumMap<Game, String> GAME_TITLE = new EnumMap<Game, String>(Game.class); // List of supported extra folders for all supported games private static final EnumMap<Game, List<String>> GAME_EXTRA_FOLDERS = new EnumMap<Game, List<String>>(Game.class); // Home folder name for Enhanced Edition Games private static final EnumMap<Game, String> GAME_HOME_FOLDER = new EnumMap<Game, String>(Game.class); // Using the singleton approach private static Profile instance = null; static { // initializing game titles GAME_TITLE.put(Game.Unknown, "Unknown game"); GAME_TITLE.put(Game.BG1, "Baldur's Gate"); GAME_TITLE.put(Game.BG1TotSC, "Baldur's Gate: Tales of the Sword Coast"); GAME_TITLE.put(Game.BG2SoA, "Baldur's Gate II: Shadows of Amn"); GAME_TITLE.put(Game.BG2ToB, "Baldur's Gate II: Throne of Bhaal"); GAME_TITLE.put(Game.Tutu, "Baldur's Gate - Tutu"); GAME_TITLE.put(Game.BGT, "Baldur's Gate Trilogy"); GAME_TITLE.put(Game.PST, "Planescape: Torment"); GAME_TITLE.put(Game.IWD, "Icewind Dale"); GAME_TITLE.put(Game.IWDHoW, "Icewind Dale: Heart of Winter"); GAME_TITLE.put(Game.IWDHowToTLM, "Icewind Dale: Trials of the Luremaster"); GAME_TITLE.put(Game.IWD2, "Icewind Dale II"); GAME_TITLE.put(Game.BG1EE, "Baldur's Gate: Enhanced Edition"); GAME_TITLE.put(Game.BG1SoD, "Baldur's Gate: Siege of Dragonspear"); GAME_TITLE.put(Game.BG2EE, "Baldur's Gate II: Enhanced Edition"); GAME_TITLE.put(Game.IWDEE, "Icewind Dale: Enhanced Edition"); GAME_TITLE.put(Game.EET, "Baldur's Gate - Enhanced Edition Trilogy"); // initializing extra folders for each supported game final String[] PST_EXTRA_FOLDERS = { "Music", "Save", "Temp" }; final String[] BG_EXTRA_FOLDERS = { "Characters", "MPSave", "Music", "Portraits", "Save", "Scripts", "ScrnShot", "Sounds", "Temp", "TempSave" }; final String[] EE_EXTRA_FOLDERS = { "BPSave", "Characters", "Fonts", "Movies", "MPBPSave", "MPSave", "Music", "Portraits", "Save", "Sounds", "ScrnShot", "Scripts", "Temp", "TempSave" }; GAME_EXTRA_FOLDERS.put(Game.Unknown, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BG1, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BG1TotSC, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BG2SoA, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BG2ToB, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.Tutu, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BGT, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.PST, new ArrayList<>(Arrays.asList(PST_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.IWD, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.IWDHoW, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.IWDHowToTLM, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.IWD2, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BG1EE, new ArrayList<>(Arrays.asList(EE_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BG1SoD, new ArrayList<>(Arrays.asList(EE_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BG2EE, new ArrayList<>(Arrays.asList(EE_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.IWDEE, new ArrayList<>(Arrays.asList(EE_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.EET, new ArrayList<>(Arrays.asList(EE_EXTRA_FOLDERS))); // initializing home folder names for Enhanced Edition games GAME_HOME_FOLDER.put(Game.BG1EE, "Baldur's Gate - Enhanced Edition"); GAME_HOME_FOLDER.put(Game.BG1SoD, GAME_HOME_FOLDER.get(Game.BG1EE)); GAME_HOME_FOLDER.put(Game.BG2EE, "Baldur's Gate II - Enhanced Edition"); GAME_HOME_FOLDER.put(Game.EET, GAME_HOME_FOLDER.get(Game.BG2EE)); GAME_HOME_FOLDER.put(Game.IWDEE, "Icewind Dale - Enhanced Edition"); // static properties are always available initStaticProperties(); } /** * Converts the string representation of a game type into a {@link Game} enum type. * Falls back to {@code Game.Unknown} on error. */ public static Game gameFromString(String gameName) { if (gameName != null && !gameName.isEmpty()) { try { return Enum.valueOf(Game.class, gameName); } catch (IllegalArgumentException e) { System.err.println("Unknown game type \"" + gameName + "\" specified. Falling back to \"Unknown\"."); } } return Game.Unknown; } /** * Initializes properties of a new game. * @param keyFile Full path to the chitin.key of the opened game. * @return {@code true} if the game has been initialized successfully, {@code false} otherwise. */ public static boolean openGame(Path keyFile) { return openGame(keyFile, null); } /** * Initializes properties of a new game. * @param keyFile Full path to the chitin.key of the opened game. * @param desc An optional description associated with the game. * @return {@code true} if the game has been initialized successfully, {@code false} otherwise. */ public static boolean openGame(Path keyFile, String desc) { try { closeGame(); instance = new Profile(keyFile, desc); return true; } catch (Exception e) { e.printStackTrace(); } closeGame(); return false; } /** * Returns whether a game has been initialized. * @return {@code true} if a game has been initialized, {@code false} if not. */ public static boolean isGameOpen() { return (instance != null); } /** * Returns whether a property of the specified key is available. * @param key A key to identify the property. * @return {@code true} if the key is pointing to an existing property, {@code false} otherwise. */ public static boolean hasProperty(Key key) { return properties.containsKey(key); } /** * Returns the data type of the specified property. * @param key The unique key identifying the property. * @return The data type of the property, or {@code null} if not available. */ public static Type getPropertyType(Key key) { Property prop = getEntry(key); if (prop != null) { return prop.getType(); } else { return null; } } /** * Returns the actual data of the specified property. * @param key The unique key identifying the property. * @return The data of the property, or {@code null} if not available. */ public static <T> T getProperty(Key key) { return getProperty(key, null); } /** * Returns the actual data of the specified property. * @param key The unique key identifying the property. * @param param An additional parameter required by a small number of properties. * Specify {@code null} to always return the parent structure of the property. * @return The data of the property, or {@code null} if not available. */ public static <T> T getProperty(Key key, Object param) { Property prop = getEntry(key); if (prop != null) { if (param == null) { return prop.getData(); } else { // handling properties which require an additional parameter EnumMap<?, T> map = null; switch (key) { case GET_GLOBAL_GAME_TITLE: case GET_GLOBAL_EXTRA_FOLDER_NAMES: case GET_GLOBAL_HOME_FOLDER_NAME: if (param instanceof Game) { map = prop.getData(); return map.get(param); } break; default: } } } return null; } /** * Updates the value of the Property entry specified by {@code key} and returns * the previous Property value. * @param key The Property key. * @param data The new value of the Property instance. * @return the previous value of the specified Property, or {@code null} if not available or applicable. */ public static Object updateProperty(Key key, Object data) { // Properties requiring extra parameters cannot be updated if (key != null) { Property prop = properties.get(key); if (prop != null) { return prop.setData(data); } } return null; } /** * Adds or updates a Property entry. * @param key The Property key. * @param type The data type. * @param data The data of the Property instance. * @return {@code true} if Property has been added or updated, {@code false} otherwise. */ public static boolean addProperty(Key key, Type type, Object data) { if (key != null) { if (properties.containsKey(key)) { updateProperty(key, data); return true; } else { addEntry(key, type, data); return true; } } return false; } /** * Returns the name of the default override folder (without path). Does not need an opened game * to return a valid value. * @return The name of the override folder. */ public static String getOverrideFolderName() { return getProperty(Key.GET_GLOBAL_OVERRIDE_NAME); } /** * Returns the game type of the current game. * @return The {@link Game} type of the current game or {@code Game.Unknown} otherwise. */ public static Game getGame() { Object ret = getProperty(Key.GET_GAME_TYPE); return (ret instanceof Game) ? (Game)ret : Game.Unknown; } /** * Returns the engine type of the current game. * @return The {@link Engine} type of the current game or {@code Engine.Unknown} otherwise. */ public static Engine getEngine() { Object ret = getProperty(Key.GET_GAME_ENGINE); return (ret instanceof Engine) ? (Engine)ret : Engine.Unknown; } /** * Returns whether the current game is an Enhanced Edition game. * @return {@code true} if the current game is an Enhanced Edition Game, {@code false} otherwise. */ public static boolean isEnhancedEdition() { Object ret = getProperty(Key.IS_ENHANCED_EDITION); return (ret instanceof Boolean) ? (Boolean)ret : false; } /** * Returns the game's root folder. * @return The game's root folder as {@link Path} object. */ public static Path getGameRoot() { return getProperty(Key.GET_GAME_ROOT_FOLDER); } /** * Returns the game's home folder. A Non-enhanced Edition game will always return the game's * root folder instead. * @return The game's home folder as {@link Path} object. */ public static Path getHomeRoot() { Object ret = getProperty(Key.GET_GAME_HOME_FOLDER); return (ret instanceof Path) ? (Path)ret : getGameRoot(); } /** * Returns the game's language folder (where the effective dialog.tlk is located). * A Non-enhanced Edition game will always return the game's root folder instead. * @return The game's effective language folder as @{link Path} object. */ public static Path getLanguageRoot() { Object ret = getProperty(Key.GET_GAME_LANG_FOLDER); return (ret instanceof Path) ? (Path)ret : getGameRoot(); } /** * Returns all available root folders of the game as a list of {@link Path} objects, sorted by * priority in descending order. * (which includes the home, language and optional DLC roots for Enhanced Edition games). * @return A list of {@code Path} objects specifying the game's root folders. */ public static List<Path> getRootFolders() { return getProperty(Key.GET_GAME_ROOT_FOLDERS_AVAILABLE); } /** * Returns a list of override folders supported by the current game, sorted by priority * in descending order. * @param includeExtraFolders Whether to include extra folders that are treated * as override folders by the current game. * @return List of {@link Path} objects for all available override folders. */ public static List<Path> getOverrideFolders(boolean includeExtraFolders) { List<Path> ret = getProperty(Key.GET_GAME_OVERRIDE_FOLDERS); if (ret != null && !includeExtraFolders) { String overrideName = getProperty(Key.GET_GLOBAL_OVERRIDE_NAME); List<Path> overrides = new ArrayList<>(); ret.forEach((path) -> { if (path.getFileName().toString().equalsIgnoreCase(overrideName)) { overrides.add(path); } }); ret = overrides; } if (ret == null) { ret = new ArrayList<>(); } return ret; } /** * Returns the full path to the chitin.key of the currently open game. * @return The full path of the chitin.key as {@link Path} object. */ public static Path getChitinKey() { Object ret = getProperty(Key.GET_GAME_CHITIN_KEY); return (ret instanceof Path) ? (Path)ret : null; } /** * Updates language-related Properties with the specified game language. (Enhanced Editions only) * @param language The name of the language subfolder. * @return {@code true} if the game language has been updated successfully, {@code false} otherwise. */ public static boolean updateGameLanguage(String language) { if (isEnhancedEdition() && language != null) { List<String> languages = getProperty(Key.GET_GAME_LANG_FOLDER_NAMES_AVAILABLE); for (final String curLang: languages) { if (curLang.equalsIgnoreCase(language)) { instance.initRootDirs(); instance.initOverrides(); // updating language names and folders updateProperty(Key.GET_GAME_LANG_FOLDER_NAME, curLang); Path langPath = FileManager.query((Path)getProperty(Key.GET_GAME_LANG_FOLDER_BASE), curLang); updateProperty(Key.GET_GAME_LANG_FOLDER, langPath); // updating dialog.tlks updateProperty(Key.GET_GAME_DIALOG_FILE, FileManager.query(langPath, getProperty(Key.GET_GLOBAL_DIALOG_NAME))); Path femaleTlkFile = FileManager.query(langPath, getProperty(Key.GET_GLOBAL_DIALOG_NAME_FEMALE)); if (Files.isRegularFile(femaleTlkFile)) { addProperty(Key.GET_GAME_DIALOGF_FILE, Type.PATH, femaleTlkFile); } else { updateProperty(Key.GET_GAME_DIALOGF_FILE, null); } return true; } } } return false; } /** * Returns a list of supported resource types by the current game. * @return String array containing format extensions. */ public static String[] getAvailableResourceTypes() { return getAvailableResourceTypes(false); } /** * Returns a list of known or supported resource types by the current game. * @param ignoreGame If {@code true}, returns all known resource types. * If {@code false}, returns resource types supported by the current game. * @return String array containing format extensions. */ public static String[] getAvailableResourceTypes(boolean ignoreGame) { ArrayList<String> list = new ArrayList<String>(); if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_2DA)) { list.add("2DA"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_ACM)) { list.add("ACM"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_ARE_V10) || (Boolean)getProperty(Key.IS_SUPPORTED_ARE_V91)) { list.add("ARE"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_BAM_V1) || (Boolean)getProperty(Key.IS_SUPPORTED_BAM_V2) || (Boolean)getProperty(Key.IS_SUPPORTED_BAMC_V1)) { list.add("BAM"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_BCS)) { list.add("BCS"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_BIK)) { list.add("BIK"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_BIO)) { list.add("BIO"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_BMP_ALPHA) || (Boolean)getProperty(Key.IS_SUPPORTED_BMP_PAL)) { list.add("BMP"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_CHR_V10) || (Boolean)getProperty(Key.IS_SUPPORTED_CHR_V20) || (Boolean)getProperty(Key.IS_SUPPORTED_CHR_V21) || (Boolean)getProperty(Key.IS_SUPPORTED_CHR_V22)) { list.add("CHR"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_CHU)) { list.add("CHU"); } if ((Boolean)getProperty(Key.IS_SUPPORTED_CRE_V10) || (Boolean)getProperty(Key.IS_SUPPORTED_CRE_V12) || (Boolean)getProperty(Key.IS_SUPPORTED_CRE_V22) || (Boolean)getProperty(Key.IS_SUPPORTED_CRE_V90)) { list.add("CRE"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_DLG)) { list.add("DLG"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_EFF)) { list.add("EFF"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_FNT)) { list.add("FNT"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_GAM_V11) || (Boolean)getProperty(Key.IS_SUPPORTED_GAM_V20) || (Boolean)getProperty(Key.IS_SUPPORTED_GAM_V21) || (Boolean)getProperty(Key.IS_SUPPORTED_GAM_V22)) { list.add("GAM"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_GLSL)) { list.add("GLSL"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_GUI)) { list.add("GUI"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_IDS)) { list.add("IDS"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_INI)) { list.add("INI"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_ITM_V10) || (Boolean)getProperty(Key.IS_SUPPORTED_ITM_V11) || (Boolean)getProperty(Key.IS_SUPPORTED_ITM_V20)) { list.add("ITM"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_LUA)) { list.add("LUA"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_MENU)) { list.add("MENU"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_MOS_V1) || (Boolean)getProperty(Key.IS_SUPPORTED_MOS_V2) || (Boolean)getProperty(Key.IS_SUPPORTED_MOSC_V1)) { list.add("MOS"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_MUS)) { list.add("MUS"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_MVE)) { list.add("MVE"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_PLT)) { list.add("PLT"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_PNG)) { list.add("PNG"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_PRO)) { list.add("PRO"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_PVRZ)) { list.add("PVRZ"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_RES)) { list.add("RES"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_SAV)) { list.add("SAV"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_SPL_V1) || (Boolean)getProperty(Key.IS_SUPPORTED_SPL_V2)) { list.add("SPL"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_SQL)) { list.add("SQL"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_SRC_IWD2) || (Boolean)getProperty(Key.IS_SUPPORTED_SRC_PST)) { list.add("SRC"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_STO_V10) || (Boolean)getProperty(Key.IS_SUPPORTED_STO_V11) || (Boolean)getProperty(Key.IS_SUPPORTED_STO_V90)) { list.add("STO"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_TIS_V1) || (Boolean)getProperty(Key.IS_SUPPORTED_TIS_V2)) { list.add("TIS"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_TO_V1)) { list.add("TOH"); list.add("TOT"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_TO_V2)) { list.add("TOH"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_TTF)) { list.add("TTF"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_VAR)) { list.add("VAR"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_VEF)) { list.add("VEF"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_VVC)) { list.add("VVC"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_WAV) || (Boolean)getProperty(Key.IS_SUPPORTED_WAVC) || (Boolean)getProperty(Key.IS_SUPPORTED_OGG)) { list.add("WAV"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_WBM)) { list.add("WBM"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_WED)) { list.add("WED"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_WFX)) { list.add("WFX"); } if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_WMP)) { list.add("WMP"); } String[] retVal = new String[list.size()]; for (int i = 0; i < retVal.length; i++) { retVal[i] = list.get(i); } return retVal; } // Returns the Property object assigned to the given key. private static Property getEntry(Key key) { return properties.get(key); } // Adds a new Property entry to the list. private static void addEntry(Key key, Type type, Object data) { Property prop = new Property(key, type, data); properties.put(prop.getKey(), prop); } // Cleans up data when closing a game profile private static void closeGame() { properties.clear(); initStaticProperties(); instance = null; } // Initializes properties not related to a specific game private static void initStaticProperties() { // setting current NI version addEntry(Key.GET_GLOBAL_NEARINFINITY_VERSION, Type.STRING, NearInfinity.getVersion()); // setting list of supported games and associated data List<Game> gameList = new ArrayList<Game>(); for (Game game : Game.values()) { gameList.add(game); } addEntry(Key.GET_GLOBAL_GAMES, Type.LIST, gameList); addEntry(Key.GET_GLOBAL_GAME_TITLE, Type.STRING, GAME_TITLE); addEntry(Key.GET_GLOBAL_EXTRA_FOLDER_NAMES, Type.LIST, GAME_EXTRA_FOLDERS); addEntry(Key.GET_GLOBAL_HOME_FOLDER_NAME, Type.STRING, GAME_HOME_FOLDER); // setting default override folder name addEntry(Key.GET_GLOBAL_OVERRIDE_NAME, Type.STRING, "Override"); // Language root folder for Enhanced Edition games addEntry(Key.GET_GLOBAL_LANG_NAME, Type.STRING, "Lang"); // setting dialog.tlk file names addEntry(Key.GET_GLOBAL_DIALOG_NAME, Type.STRING, "dialog.tlk"); addEntry(Key.GET_GLOBAL_DIALOG_NAME_FEMALE, Type.STRING, "dialogf.tlk"); } // Attempts to determine home folder name from the game's "engine.lua" file if available private static String getLuaHomeFolderName(Game game) { Path gameRoot = getGameRoot(); if (gameRoot != null) { Path lua = FileManager.query(gameRoot, "engine.lua"); String name = getLuaValue(lua, "engine_name", "Infinity Engine - Enhanced Edition", true); if (name != null) { return name.replace('"', ' ').trim(); } } if (game != null) { return GAME_HOME_FOLDER.get(game); } else { return null; } } // Returns the value of the Lua script entry specified by key private static String getLuaValue(Path file, String key, String defaultValue, boolean ifLuaExists) { String retVal = ifLuaExists ? null : defaultValue; if (file != null && Files.isRegularFile(file) && key != null && !key.trim().isEmpty()) { retVal = defaultValue; try (Stream<String> lines = Files.lines(file, StandardCharsets.UTF_8)) { for (Iterator<String> iter = lines.iterator(); iter.hasNext();) { String line = iter.next(); int sep = line.indexOf('='); if (sep > 0) { String name = line.substring(0, sep).trim(); if (name.equals(key)) { String value = line.substring(sep+1).trim(); if (!value.isEmpty()) { boolean quote = (value.charAt(0) == '"'); boolean cmt = false; int pos = 0; for (int i = 1, len = value.length(); i < len; i++) { char ch = value.charAt(i); if (ch == '"') { quote = !quote; if (!quote) { pos = i + 1; break; } } else if (ch == '-' && !quote) { cmt = !cmt; if (!cmt) { pos = i - 1; break; } } else { cmt = false; } } if (pos > 0) { value = value.substring(0, pos); } retVal = value; } break; } } } } catch (IOException e) { e.printStackTrace(); } } return retVal; } private Profile(Path keyFile, String desc) throws Exception { init(keyFile, desc); } // Initializes profile private void init(Path keyFile, String desc) throws Exception { if (keyFile == null) { throw new Exception("No chitin.key specified"); } else if (!Files.isRegularFile(keyFile)) { throw new Exception(keyFile.toString() + " does not exist"); } if (desc != null) { addEntry(Key.GET_GAME_DESC, Type.STRING, desc); } // adding chitin.key path addEntry(Key.GET_GAME_CHITIN_KEY, Type.PATH, keyFile); // adding game's root folder and name Path rootDir = keyFile.toAbsolutePath().getParent(); addEntry(Key.GET_GAME_ROOT_FOLDER, Type.PATH, rootDir); addEntry(Key.GET_GAME_ROOT_FOLDER_NAME, Type.STRING, rootDir.getFileName().toString()); // first attempt to determine home directory for current game String home = getLuaHomeFolderName(null); Path homeDir = null; if (home != null) { addEntry(Key.GET_GAME_HOME_FOLDER_NAME, Type.STRING, home); homeDir = ResourceFactory.getHomeRoot(); if (homeDir != null) { addEntry(Key.GET_GAME_HOME_FOLDER, Type.PATH, homeDir); } } // first attempt to initialize DLC content addEntry(Key.GET_GAME_DLC_FOLDERS_AVAILABLE, Type.LIST, initDlc(rootDir, homeDir)); initGame(); } private void initGame() throws Exception { // Main game detection Game game; // Preparing available root paths List<Path> gameRoots = new ArrayList<Path>(); if (Profile.getProperty(Key.GET_GAME_DLC_FOLDERS_AVAILABLE) != null) { gameRoots.addAll(Profile.getProperty(Key.GET_GAME_DLC_FOLDERS_AVAILABLE)); } if (Profile.getGameRoot() != null) { gameRoots.add(Profile.getGameRoot()); } if (Files.isRegularFile(FileManager.query(gameRoots, "movies/howseer.wbm"))) { game = Game.IWDEE; // Note: baldur.ini is initialized later } else if (Files.isRegularFile(FileManager.query(gameRoots, "movies/pocketzz.wbm"))) { if ((Files.isRegularFile(FileManager.query(gameRoots, "override/EET.flag"))) || (Files.isRegularFile(FileManager.query(gameRoots, "data/eetTU00.bif")))) { game = Game.EET; } else { game = Game.BG2EE; } // Note: baldur.ini is initialized later } else if (Files.isRegularFile(FileManager.query(gameRoots, "movies/sodcin01.wbm"))) { game = Game.BG1SoD; // Note: baldur.ini is initialized later } else if (Files.isRegularFile(FileManager.query(gameRoots, "movies/bgenter.wbm"))) { game = Game.BG1EE; // Note: baldur.ini is initialized later } else if ((Files.isRegularFile(FileManager.query(gameRoots, "torment.exe"))) && (!Files.isRegularFile(FileManager.query(gameRoots, "movies/sigil.wbm")))) { game = Game.PST; addEntry(Key.GET_GAME_INI_NAME, Type.STRING, "torment.ini"); Path ini = FileManager.query(gameRoots, getProperty(Key.GET_GAME_INI_NAME)); if (ini != null && Files.isRegularFile(ini)) { addEntry(Key.GET_GAME_INI_FILE, Type.PATH, ini); } } else if ((Files.isRegularFile(FileManager.query(gameRoots, "idmain.exe"))) && (!Files.isRegularFile(FileManager.query(gameRoots, "movies/howseer.wbm")))) { game = Game.IWD; addEntry(Key.GET_GAME_INI_NAME, Type.STRING, "icewind.ini"); Path ini = FileManager.query(gameRoots, getProperty(Key.GET_GAME_INI_NAME)); if (ini != null && Files.isRegularFile(ini)) { addEntry(Key.GET_GAME_INI_FILE, Type.PATH, ini); } } else if ((Files.isRegularFile(FileManager.query(gameRoots, "iwd2.exe"))) && (Files.isRegularFile(FileManager.query(gameRoots, "Data/Credits.mve")))) { game = Game.IWD2; addEntry(Key.GET_GAME_INI_NAME, Type.STRING, "icewind2.ini"); Path ini = FileManager.query(gameRoots, getProperty(Key.GET_GAME_INI_NAME)); if (ini != null && Files.isRegularFile(ini)) { addEntry(Key.GET_GAME_INI_FILE, Type.PATH, ini); } } else if ((Files.isRegularFile(FileManager.query(gameRoots, "baldur.exe"))) && (Files.isRegularFile(FileManager.query(gameRoots, "BGConfig.exe")))) { game = Game.BG2SoA; addEntry(Key.GET_GAME_INI_NAME, Type.STRING, "baldur.ini"); Path ini = FileManager.query(gameRoots, getProperty(Key.GET_GAME_INI_NAME)); if (ini != null && Files.isRegularFile(ini)) { addEntry(Key.GET_GAME_INI_FILE, Type.PATH, ini); } } else if ((Files.isRegularFile(FileManager.query(gameRoots, "movies/graphsim.mov"))) || // Mac BG1 detection hack ((Files.isRegularFile(FileManager.query(gameRoots, "baldur.exe"))) && (Files.isRegularFile(FileManager.query(gameRoots, "Config.exe"))))) { game = Game.BG1; addEntry(Key.GET_GAME_INI_NAME, Type.STRING, "baldur.ini"); Path ini = FileManager.query(gameRoots, getProperty(Key.GET_GAME_INI_NAME)); if (ini != null && Files.isRegularFile(ini)) { addEntry(Key.GET_GAME_INI_FILE, Type.PATH, ini); } } else if (Files.isRegularFile(FileManager.query(gameRoots, "bg1tutu.exe"))) { game = Game.Tutu; addEntry(Key.GET_GAME_INI_NAME, Type.STRING, "baldur.ini"); Path ini = FileManager.query(gameRoots, getProperty(Key.GET_GAME_INI_NAME)); if (ini != null && Files.isRegularFile(ini)) { addEntry(Key.GET_GAME_INI_FILE, Type.PATH, ini); } } else { // game == Game.Unknown game = Game.Unknown; addEntry(Key.GET_GAME_INI_NAME, Type.STRING, "baldur.ini"); Path ini = FileManager.query(gameRoots, getProperty(Key.GET_GAME_INI_NAME)); if (ini != null && Files.isRegularFile(ini)) { addEntry(Key.GET_GAME_INI_FILE, Type.PATH, ini); } } // adding priliminary game type into storage addEntry(Key.GET_GAME_TYPE, Type.OBJECT, game); addEntry(Key.GET_GAME_EXTRA_FOLDER_NAMES, Type.LIST, GAME_EXTRA_FOLDERS.get(game)); // determining game engine initGameEngine(); // initializing method isEnhancedEdition() addEntry(Key.IS_ENHANCED_EDITION, Type.BOOLEAN, Boolean.valueOf(getEngine() == Engine.EE)); if (isEnhancedEdition()) { Path langDir = FileManager.query(gameRoots, "lang"); if (langDir != null && Files.isDirectory(langDir)) { addEntry(Key.GET_GAME_LANG_FOLDER_BASE, Type.PATH, langDir); } } if (!hasProperty(Key.GET_GAME_HOME_FOLDER_NAME) && GAME_HOME_FOLDER.containsKey(game)) { addEntry(Key.GET_GAME_HOME_FOLDER_NAME, Type.STRING, getLuaHomeFolderName(game)); } // delayed initialization of ini files (EE only) if (isEnhancedEdition() && getProperty(Key.GET_GAME_INI_FILE) == null) { initIniFile("baldur.lua", "baldur.ini"); } // initializing available root directories initRootDirs(); // initializing extra folders containing resources initExtraFolders(); // initializing list of available override folders initOverrides(); // initializing dialog.tlk and dialogf.tlk Path tlk = FileManager.query(getRootFolders(), getProperty(Key.GET_GLOBAL_DIALOG_NAME)); if (tlk != null && Files.isRegularFile(tlk)) { addEntry(Key.GET_GAME_DIALOG_FILE, Type.PATH, tlk); } Path tlkf = FileManager.query(getRootFolders(), getProperty(Key.GET_GLOBAL_DIALOG_NAME_FEMALE)); if (tlkf != null && Files.isRegularFile(tlkf)) { addEntry(Key.GET_GAME_DIALOGF_FILE, Type.PATH, tlkf); } // initializing list of folders containing BIFF archives List<Path> biffDirs = ResourceFactory.getBIFFDirs(); if (biffDirs != null && !biffDirs.isEmpty()) { addEntry(Key.GET_GAME_BIFF_FOLDERS, Type.LIST, biffDirs); } // Initializing resource structure ResourceFactory.openGame(getChitinKey()); // Expansion pack detection if (game == Game.IWD && ResourceFactory.resourceExists("HOWDRAG.MVE")) { // detect Trials of the Luremaster if (ResourceFactory.resourceExists("AR9715.ARE")) { game = Game.IWDHowToTLM; } else { game = Game.IWDHoW; } } else if (game == Game.BG2SoA && ResourceFactory.resourceExists("SARADUSH.MVE")) { // detect BG Trilogy if (ResourceFactory.resourceExists("ARU000.ARE")) { game = Game.BGT; } else { game = Game.BG2ToB; } } else if (game == Game.BG1 && ResourceFactory.resourceExists("DURLAG.MVE")) { game = Game.BG1TotSC; } // updating game type addEntry(Key.GET_GAME_TYPE, Type.OBJECT, game); addEntry(Key.GET_GAME_TITLE, Type.STRING, GAME_TITLE.get(game)); // initializing supported resource types initResourceTypes(); // initializing engine-specific traits initFeatures(); } // Initializes the engine type private void initGameEngine() { // determining game engine Game game = getGame(); Engine engine; switch (game) { case BG1: case BG1TotSC: engine = Engine.BG1; break; case BG2SoA: case BG2ToB: case Tutu: case BGT: engine = Engine.BG2; break; case PST: engine = Engine.PST; break; case IWD: case IWDHoW: case IWDHowToTLM: engine = Engine.IWD; break; case IWD2: engine = Engine.IWD2; break; case BG1EE: case BG1SoD: case BG2EE: case IWDEE: case EET: engine = Engine.EE; break; default: engine = Engine.Unknown; } addEntry(Key.GET_GAME_ENGINE, Type.OBJECT, engine); } // Initializes the first available of the specified ini files private void initIniFile(String... iniFiles) { if (iniFiles != null) { Path homeRoot = ResourceFactory.getHomeRoot(); for (int i = 0; i < iniFiles.length; i++) { Path ini = FileManager.query(homeRoot, iniFiles[i]); if (ini != null && Files.isRegularFile(ini)) { addEntry(Key.GET_GAME_INI_NAME, Type.STRING, iniFiles[i]); break; } } } } // Initializes available root folders of the game private void initRootDirs() { // Considering three (or four) different root folders to locate game resources // Note: Order of the root directories is important. FileNI will take the first one available. Path homeRoot = ResourceFactory.getHomeRoot(); String language = ResourceFactory.fetchGameLanguage(FileManager.query(homeRoot, getProperty(Key.GET_GAME_INI_NAME))); String languageDef = ResourceFactory.fetchGameLanguage(null); // adding available roots in order of priority (highest first) List<Path> listRoots = new ArrayList<>(); if (homeRoot != null) { addEntry(Key.GET_GAME_HOME_FOLDER, Type.PATH, homeRoot); Path ini = FileManager.query(homeRoot, getProperty(Key.GET_GAME_INI_NAME)); if (ini != null && Files.isRegularFile(ini)) { addEntry(Key.GET_GAME_INI_FILE, Type.PATH, ini); } listRoots.add(homeRoot); } // initializing available DLC Path gameRoot = getGameRoot(); List<Path> dlcRoots = initDlc(gameRoot, homeRoot); addEntry(Key.GET_GAME_DLC_FOLDERS_AVAILABLE, Type.LIST, dlcRoots); // preparing available game root paths List<Path> roots = new ArrayList<>(); roots.addAll(dlcRoots); roots.add(gameRoot); // process each root separately roots.forEach((root) -> { // adding root of active language Path langRoot = FileManager.query(root, (String)getProperty(Key.GET_GLOBAL_LANG_NAME), language); if (langRoot != null && Files.isDirectory(langRoot)) { addEntry(Key.GET_GAME_LANG_FOLDER_NAME, Type.STRING, language); addEntry(Key.GET_GAME_LANG_FOLDER, Type.PATH, langRoot); List<Path> langPaths = ResourceFactory.getAvailableGameLanguages(); addEntry(Key.GET_GAME_LANG_FOLDERS_AVAILABLE, Type.LIST, langPaths); List<String> languages = new ArrayList<String>(langPaths.size()); langPaths.forEach((path) -> languages.add(path.getFileName().toString())); addEntry(Key.GET_GAME_LANG_FOLDER_NAMES_AVAILABLE, Type.LIST, languages); listRoots.add(langRoot); } // adding fallback language added if selected language is non-english Path langRootDef = FileManager.query((Path)getProperty(Key.GET_GAME_LANG_FOLDER_BASE), languageDef); if (!languageDef.equals(language) && langRootDef != null && Files.isDirectory(langRootDef)) { listRoots.add(langRootDef); } // adding game root listRoots.add(root); }); addEntry(Key.GET_GAME_ROOT_FOLDERS_AVAILABLE, Type.PATH, listRoots); } // Initializes extra folders containing resources private void initExtraFolders() { List<Path> pathList = new ArrayList<>(); List<Path> rootPaths = getRootFolders(); List<String> extraFolders = getProperty(Key.GET_GAME_EXTRA_FOLDER_NAMES); rootPaths.forEach((root) -> { List<Path> list = new ArrayList<>(extraFolders.size()); extraFolders.forEach((folder) -> { Path path = FileManager.query(root, folder); if (path != null && Files.isDirectory(path)) { list.add(path); } }); Collections.sort(list); pathList.addAll(list); }); if (getProperty(Key.GET_GAME_EXTRA_FOLDERS) != null) { updateProperty(Key.GET_GAME_EXTRA_FOLDERS, pathList); } else { addEntry(Key.GET_GAME_EXTRA_FOLDERS, Type.LIST, pathList); } } // Initializes supported override folders used by specific games private void initOverrides() { List<Path> list = new ArrayList<>(); if (isEnhancedEdition()) { // preparations Path gameRoot = getGameRoot(); // relative language paths based on game root String langFolder = gameRoot.relativize(getLanguageRoot()).toString(); String langFolderDef = gameRoot.relativize(getLanguageRoot().getParent().resolve("en_US")).toString(); if (langFolder.equalsIgnoreCase(langFolderDef)) { langFolderDef = null; } Path homeRoot = getHomeRoot(); List<Path> dlcRoots = getProperty(Key.GET_GAME_DLC_FOLDERS_AVAILABLE); // putting all root folders into a list ordered by priority (highest first) List<Path> gameRoots = new ArrayList<>(); gameRoots.add(gameRoot); dlcRoots.forEach((path) -> gameRoots.add(path)); gameRoots.add(homeRoot); // registering override paths for (final Path root: gameRoots) { Path path = FileManager.query(root, langFolder, "Movies"); if (path != null && Files.isDirectory(path)) { list.add(path); } if (langFolderDef != null) { path = FileManager.query(root, langFolderDef, "Movies"); if (path != null && Files.isDirectory(path)) { list.add(path); } } path = FileManager.query(root, "Movies"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, "Characters"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, "Portraits"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, langFolder, "Sounds"); if (path != null && Files.isDirectory(path)) { list.add(path); } if (langFolderDef != null) { path = FileManager.query(root, langFolderDef, "Sounds"); if (path != null && Files.isDirectory(path)) { list.add(path); } } path = FileManager.query(root, langFolder, "Fonts"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, "Sounds"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, "Scripts"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, langFolder, "Override"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, "Override"); if (path != null && Files.isDirectory(path)) { list.add(path); } } } else { Path root = getGameRoot(); Path path = FileManager.query(root, "Movies"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, "Characters"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, "Portraits"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, "Sounds"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, "Scripts"); if (path != null && Files.isDirectory(path)) { list.add(path); } path = FileManager.query(root, "Override"); if (path != null && Files.isDirectory(path)) { list.add(path); } } addEntry(Key.GET_GAME_OVERRIDE_FOLDERS, Type.LIST, list); } // Initializes supported resource types private void initResourceTypes() { Game game = getGame(); Engine engine = getEngine(); addEntry(Key.IS_SUPPORTED_2DA, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_ACM, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_ARE_V10, Type.BOOLEAN, (engine == Engine.BG1 || engine == Engine.BG2 || engine == Engine.IWD || engine == Engine.PST || engine == Engine.EE || engine == Engine.Unknown)); addEntry(Key.IS_SUPPORTED_ARE_V91, Type.BOOLEAN, (engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_BAM_V1, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_BAMC_V1, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.IWD || engine == Engine.IWD2 || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_BAM_V2, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_BCS, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_BIFF, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_BIF, Type.BOOLEAN, (engine == Engine.IWD)); addEntry(Key.IS_SUPPORTED_BIFC, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_BIK, Type.BOOLEAN, (engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_BIO, Type.BOOLEAN, (engine == Engine.BG1 || engine == Engine.BG2 || engine == Engine.EE || engine == Engine.Unknown)); addEntry(Key.IS_SUPPORTED_BMP_PAL, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_BMP_ALPHA, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_CHR_V10, Type.BOOLEAN, (engine == Engine.BG1 || engine == Engine.Unknown)); addEntry(Key.IS_SUPPORTED_CHR_V20, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_CHR_V21, Type.BOOLEAN, (game == Game.BG2ToB || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_CHR_V22, Type.BOOLEAN, (engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_CHU, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_CRE_V10, Type.BOOLEAN, (engine == Engine.BG1 || engine == Engine.BG2 || engine == Engine.EE || engine == Engine.Unknown)); addEntry(Key.IS_SUPPORTED_CRE_V12, Type.BOOLEAN, (engine == Engine.PST)); addEntry(Key.IS_SUPPORTED_CRE_V22, Type.BOOLEAN, (engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_CRE_V90, Type.BOOLEAN, (engine == Engine.IWD)); addEntry(Key.IS_SUPPORTED_DLG, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_EFF, Type.BOOLEAN, (engine == Engine.BG1 || engine == Engine.BG2 || engine == Engine.IWD || engine == Engine.EE || engine == Engine.Unknown)); addEntry(Key.IS_SUPPORTED_FNT, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_GAM_V11, Type.BOOLEAN, (engine == Engine.BG1 || engine == Engine.IWD || engine == Engine.PST || engine == Engine.Unknown)); addEntry(Key.IS_SUPPORTED_GAM_V20, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_GAM_V21, Type.BOOLEAN, (game == Game.BG2ToB || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_GAM_V22, Type.BOOLEAN, (engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_GLSL, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_GUI, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_IDS, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_INI, Type.BOOLEAN, (engine == Engine.IWD || engine == Engine.IWD2 || engine == Engine.PST || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_ITM_V10, Type.BOOLEAN, (engine == Engine.BG1 || engine == Engine.BG2 || engine == Engine.IWD || engine == Engine.EE || engine == Engine.Unknown)); addEntry(Key.IS_SUPPORTED_ITM_V11, Type.BOOLEAN, (engine == Engine.PST)); addEntry(Key.IS_SUPPORTED_ITM_V20, Type.BOOLEAN, (engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_KEY, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_LUA, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_MENU, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_MOS_V1, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_MOSC_V1, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.IWD || engine == Engine.IWD2 || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_MOS_V2, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_MUS, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_MVE, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_OGG, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_PLT, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_PNG, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_PRO, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_PVRZ, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_RES, Type.BOOLEAN, (engine == Engine.IWD || engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_SAV, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_SPL_V1, Type.BOOLEAN, (engine == Engine.BG1 || engine == Engine.BG2 || engine == Engine.IWD || engine == Engine.PST || engine == Engine.EE || engine == Engine.Unknown)); addEntry(Key.IS_SUPPORTED_SPL_V2, Type.BOOLEAN, (engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_SQL, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_SRC_PST, Type.BOOLEAN, (engine == Engine.PST)); addEntry(Key.IS_SUPPORTED_SRC_IWD2, Type.BOOLEAN, (engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_STO_V10, Type.BOOLEAN, (engine == Engine.BG1 || engine == Engine.BG2 || engine == Engine.EE || engine == Engine.Unknown)); addEntry(Key.IS_SUPPORTED_STO_V11, Type.BOOLEAN, (engine == Engine.PST)); addEntry(Key.IS_SUPPORTED_STO_V90, Type.BOOLEAN, (engine == Engine.IWD || engine == Engine.IWD2)); addEntry(Key.IS_SUPPORTED_TIS_V1, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_TIS_V2, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_TLK, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_TO_V1, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.IWD)); addEntry(Key.IS_SUPPORTED_TO_V2, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_TTF, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_VAR, Type.BOOLEAN, (engine == Engine.PST)); addEntry(Key.IS_SUPPORTED_VEF, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_VVC, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_WAV, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_WAVC, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_WBM, Type.BOOLEAN, isEnhancedEdition()); addEntry(Key.IS_SUPPORTED_WED, Type.BOOLEAN, Boolean.valueOf(true)); addEntry(Key.IS_SUPPORTED_WFX, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.EE)); addEntry(Key.IS_SUPPORTED_WMP, Type.BOOLEAN, Boolean.valueOf(true)); } // Initializes game-specific features private void initFeatures() { Game game = getGame(); Engine engine = getEngine(); // Are Kits supported? addEntry(Key.IS_SUPPORTED_KITS, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.IWD2 || engine == Engine.EE)); // the actual name of the "Alignment" IDS resource addEntry(Key.GET_IDS_ALIGNMENT, Type.STRING, (engine == Engine.IWD2) ? "ALIGNMNT.IDS" : "ALIGNMEN.IDS"); // display mode of overlays in tilesets addEntry(Key.IS_TILESET_STENCILED, Type.BOOLEAN, (engine == Engine.BG2 || game == Game.BG2EE)); // Has TobEx been installed? if (engine == Engine.BG2) { Path tobexIni = FileManager.query(getGameRoot(), "TobEx_ini/TobExCore.ini"); addEntry(Key.IS_GAME_TOBEX, Type.BOOLEAN, Files.isRegularFile(tobexIni)); } else { addEntry(Key.IS_GAME_TOBEX, Type.BOOLEAN, Boolean.FALSE); } // Add campaign-specific extra folders initCampaigns(); } // Adds any campaign-specific save folders to the resource tree (EE only) private void initCampaigns() { final String campaign = "CAMPAIGN.2DA"; if (isEnhancedEdition() && ResourceFactory.resourceExists(campaign)) { Table2da table = Table2daCache.get(campaign); if (table == null || table.getRowCount() == 0) { return; } // getting correct column final String saveColName = "SAVE_DIR"; // default column name int col = 12; // default column index for (int i = 0; i < table.getColCount(); i++) { if (saveColName.equalsIgnoreCase(table.getHeader(i))) { col = i; break; } } if (col >= table.getColCount()) { return; } // getting save folder names List<String> extraNames = getProperty(Key.GET_GAME_EXTRA_FOLDER_NAMES); boolean available = false; for (int row = 0; row < table.getRowCount(); row++) { String save = table.get(row, col); if (save != null && !save.isEmpty()) { save = Character.toUpperCase(save.charAt(0)) + save.substring(1).toLowerCase(Locale.ENGLISH); String mpsave = "MP" + save; boolean checkSave = false, checkMPSave = false; for (final String s: extraNames) { checkSave |= save.equalsIgnoreCase(s); checkMPSave |= mpsave.equalsIgnoreCase(s); if (checkSave && checkMPSave) { break; } } if (!checkSave) { available = true; extraNames.add(save); } if (!checkMPSave) { available = true; extraNames.add(mpsave); } } } if (available) { // updating extra folder name and path list Collections.sort(extraNames); updateProperty(Key.GET_GAME_EXTRA_FOLDER_NAMES, extraNames); initExtraFolders(); // adding new paths to resource tree ResourceTreeModel model = ResourceFactory.getResources(); if (model != null) { List<Path> extraDirs = getProperty(Key.GET_GAME_EXTRA_FOLDERS); for (final Path path: extraDirs) { if (Files.isDirectory(path)) { String folderName = path.getFileName().toString(); if (model.getFolder(folderName) == null) { model.addDirectory((ResourceTreeFolder)model.getRoot(), path, false); } } } model.sort(); } } } } // Registers available DLCs and returns them as list of root paths private List<Path> initDlc(Path rootDir, Path homeDir) { List<Path> retVal = new ArrayList<>(); if (homeDir == null) { // assume original IE game or EE game without DLC support return retVal; } List<ObjectString> gameFolders = new ArrayList<>(); // Getting potential DLC folders (search order is important) if (homeDir != null && Files.isDirectory(homeDir)) { gameFolders.add(new ObjectString("zip", homeDir)); } if (rootDir != null && Files.isDirectory(rootDir)) { gameFolders.add(new ObjectString("zip", rootDir)); gameFolders.add(new ObjectString("zip", rootDir.resolve("dlc"))); gameFolders.add(new ObjectString("mod", rootDir.resolve("workshop"))); } for (final ObjectString root: gameFolders) { String ext = root.getString(); Path dir = root.getObject(); if (dir != null && Files.isDirectory(dir)) { List<Path> list = new ArrayList<>(); try (DirectoryStream<Path> dstream = Files.newDirectoryStream(dir)) { for (final Path file: dstream) { try { Path dlcRoot = validateDlc(file, ext); if (dlcRoot != null) { list.add(dlcRoot); } } catch (IOException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } // DLCs of the same root are sorted alphabetically if (!list.isEmpty()) { Collections.sort(list); retVal.addAll(list); } } } // registering paths to mod.key files List<Path> keyList = new ArrayList<>(); retVal.forEach((path) -> { Path keyFile = DlcManager.queryKey(path); if (keyFile != null) { keyList.add(keyFile); } }); if (getProperty(Key.GET_GAME_DLC_KEYS_AVAILABLE) != null) { updateProperty(Key.GET_GAME_DLC_KEYS_AVAILABLE, keyList); } else { addEntry(Key.GET_GAME_DLC_KEYS_AVAILABLE, Type.LIST, keyList); } return retVal; } // Checks whether specified file meets the requirement of a DLC archive, which includes // is regular file, has correct file extension, is of type zip and contains a valid KEY file in // the archive's root folder. // Returns the root path of the Dlc archive if available or null. private Path validateDlc(Path file, String ext) throws IOException { // is regular file? if (file == null && !Files.isRegularFile(file)) { return null; } // has correct file extension? String fileName = file.getFileName().toString().toLowerCase(Locale.ENGLISH); if (!fileName.endsWith('.' + ext.toLowerCase(Locale.ENGLISH))) { return null; } // is already registered? FileSystem fs = DlcManager.getDlc(file); if (fs == null) { // try to register potential DLC fs = DlcManager.register(file); } if (fs != null) { return fs.getPath("/"); } else { return null; } } //-------------------------- INNER CLASSES -------------------------- // Internal definition of a property entry private static class Property { private final Key key; private final Type type; private Object data; /** * Initialize a new property. * @param key A unique identifier for the property. Cannot be modified afterwards. * @param type The data type of the property. Cannot be modified afterwards. * @param data The actual data of the property. Can be modified afterwards. */ public Property(Key key, Type type, Object data) { this.key = key; this.type = type; this.data = data; } /** Returns a unique key which identifies this property. */ public Key getKey() { return key; } /** Returns the data type of this property. */ public Type getType() { return type; } /** Returns the actual data of this property. */ @SuppressWarnings("unchecked") public <T> T getData() { return (T)data; } /** Sets new data value of this property. Returns the previous data. */ public Object setData(Object newValue) { Object retVal = data; data = newValue; return retVal; } @Override public String toString() { return String.format("%1$d:[%2$s] = %3$s", key, type, data); } } }