diff --git a/CMakeLists.txt b/CMakeLists.txt
index ea5706fa..696adef3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,10 +11,10 @@
cmake_minimum_required (VERSION 2.8.9)
project (boss)
-set (BOSS_SRC "${CMAKE_SOURCE_DIR}/src/metadata.cpp")
+set (BOSS_SRC "${CMAKE_SOURCE_DIR}/src/metadata.cpp" "${CMAKE_SOURCE_DIR}/src/game.cpp" "${CMAKE_SOURCE_DIR}/src/helpers.cpp")
# Include source and library directories.
-include_directories ("${BOSS_LIBS_DIR}/boost" "${BOSS_LIBS_DIR}/yaml-cpp/include" "${CMAKE_SOURCE_DIR}/src")
+include_directories ("${BOSS_LIBS_DIR}/alphanum" "${BOSS_LIBS_DIR}/utf8" "${BOSS_LIBS_DIR}/boost" "${BOSS_LIBS_DIR}/yaml-cpp/include" "${CMAKE_SOURCE_DIR}/src")
##############################
# Platform-Specific Settings
@@ -37,7 +37,7 @@ ENDIF ()
# Settings when compiling and cross-compiling on Linux.
IF (CMAKE_HOST_SYSTEM_NAME MATCHES "Linux")
- set (BOSS_LIBS yaml-cpp)
+ set (BOSS_LIBS yaml-cpp boost_filesystem boost_system boost_regex)
set (CMAKE_C_FLAGS "-m${BOSS_ARCH}")
set (CMAKE_CXX_FLAGS "-m${BOSS_ARCH}")
set (CMAKE_EXE_LINKER_FLAGS "-static-libstdc++ -static-libgcc")
@@ -45,15 +45,18 @@ IF (CMAKE_HOST_SYSTEM_NAME MATCHES "Linux")
set (CMAKE_MODULE_LINKER_FLAGS "-static-libstdc++ -static-libgcc")
link_directories ("${BOSS_LIBS_DIR}/yaml-cpp/build/")
+ link_directories ("${BOSS_LIBS_DIR}/boost/stage-${BOSS_ARCH}/lib")
IF (CMAKE_SYSTEM_NAME MATCHES "Windows")
link_directories ("${BOSS_LIBS_DIR}/yaml-cpp/build/")
+ link_directories ("${BOSS_LIBS_DIR}/boost/stage-mingw-${BOSS_ARCH}/lib")
ENDIF ()
ENDIF ()
# Settings when not cross-compiling.
IF (CMAKE_SYSTEM_NAME MATCHES CMAKE_HOST_SYSTEM_NAME)
link_directories ("${BOSS_LIBS_DIR}/yaml-cpp/build/")
+ link_directories ("${BOSS_LIBS_DIR}/boost/stage-${BOSS_ARCH}/lib")
ENDIF ()
##############################
diff --git a/src/api.cpp b/src/api.cpp
new file mode 100644
index 00000000..ff9a1262
--- /dev/null
+++ b/src/api.cpp
@@ -0,0 +1,148 @@
+/* BOSS
+
+ A plugin load order optimiser for games that use the esp/esm plugin system.
+
+ Copyright (C) 2012 WrinklyNinja
+
+ This file is part of BOSS.
+
+ BOSS is free software: you can redistribute
+ it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ BOSS is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with BOSS. If not, see
+ .
+*/
+
+#include "api.h"
+
+//////////////////////////////
+// Error Handling Functions
+//////////////////////////////
+
+// Outputs a string giving the details of the last time an error or
+// warning return code was returned by a function. The string exists
+// until this function is called again or until CleanUpAPI is called.
+BOSS_API uint32_t boss_get_error_message (uint8_t ** message) {
+
+}
+
+
+//////////////////////////////
+// Version Functions
+//////////////////////////////
+
+// Returns whether this version of BOSS supports the API from the given
+// BOSS version. Abstracts BOSS API stability policy away from clients.
+BOSS_API bool boss_is_compatible (const uint32_t versionMajor, const uint32_t versionMinor, const uint32_t versionPatch) {
+
+}
+
+// Returns the version string for this version of BOSS.
+// The string exists until this function is called again or until
+// CleanUpAPI is called.
+BOSS_API uint32_t boss_get_version (uint32_t * versionMajor, uint32_t * versionMinor, uint32_t * versionPatch) {
+
+}
+
+
+////////////////////////////////////
+// Lifecycle Management Functions
+////////////////////////////////////
+
+// Explicitly manage database lifetime. Allows clients to free memory when
+// they want/need to. clientGame sets the game the DB is for, and dataPath
+// is the path to that game's Data folder, and is case-sensitive if the
+// underlying filesystem is case-sensitive. This function also checks that
+// plugins.txt and loadorder.txt (if they both exist) are in sync. If
+// dataPath == NULL then the API will attempt to detect the data path of
+// the specified game.
+BOSS_API uint32_t boss_create_db (boss_db * db, const uint32_t clientGame, const uint8_t * gamePath) {
+
+}
+
+// Destroys the given DB, freeing any memory allocated as part of its use.
+BOSS_API void boss_destroy_db (boss_db db) {
+
+}
+
+// Frees memory allocated to version and error strings.
+BOSS_API void boss_cleanup () {
+
+}
+
+
+///////////////////////////////////
+// Database Loading Functions
+///////////////////////////////////
+
+// Loads the masterlist and userlist from the paths specified.
+// Can be called multiple times. On error, the database is unchanged.
+// Paths are case-sensitive if the underlying filesystem is case-sensitive.
+// masterlistPath and userlistPath are files.
+BOSS_API uint32_t boss_load_lists (boss_db db, const uint8_t * masterlistPath,
+ const uint8_t * userlistPath) {
+
+}
+
+// Evaluates all conditional lines and regex mods the loaded masterlist.
+// This exists so that Load() doesn't need to be called whenever the mods
+// installed are changed. Evaluation does not take place unless this function
+// is called. Repeated calls re-evaluate the masterlist from scratch each time,
+// ignoring the results of any previous evaluations. Paths are case-sensitive
+// if the underlying filesystem is case-sensitive.
+BOSS_API uint32_t boss_eval_lists (boss_db db) {
+
+}
+
+
+//////////////////////////
+// DB Access Functions
+//////////////////////////
+
+// Returns an array of the Bash Tags encounterred when loading the masterlist
+// and userlist, and the number of tags in the returned array. The array and
+// its contents are static and should not be freed by the client.
+BOSS_API uint32_t boss_get_tag_map (boss_db db, boss_tag ** tagMap, size_t * numTags) {
+
+}
+
+// Returns arrays of Bash Tag UIDs for Bash Tags suggested for addition and removal
+// by BOSS's masterlist and userlist, and the number of tags in each array.
+// The returned arrays are valid until the db is destroyed or until the Load
+// function is called. The arrays should not be freed by the client. modName is
+// case-insensitive. If no Tags are found for an array, the array pointer (*tagIds)
+// will be NULL. The userlistModified bool is true if the userlist contains Bash Tag
+// suggestion message additions.
+BOSS_API uint32_t boss_get_plugin_tags (boss_db db, const uint8_t * plugin,
+ uint32_t ** tagIds_added,
+ size_t * numTags_added,
+ uint32_t **tagIds_removed,
+ size_t *numTags_removed,
+ bool * userlistModified) {
+
+}
+
+// Returns the messages attached to the given plugin. Messages are valid until Load,
+// DestroyBossDb or GetPluginMessages are next called. plugin is case-insensitive.
+// If no messages are attached, *messages will be NULL and numMessages will equal 0.
+BOSS_API uint32_t boss_get_plugin_messages (boss_db db, const uint8_t * plugin,
+ boss_message ** messages,
+ size_t * numMessages) {
+
+}
+
+// Writes a minimal masterlist that only contains mods that have Bash Tag suggestions,
+// and/or dirty messages, plus the Tag suggestions and/or messages themselves and their
+// conditions, in order to create the Wrye Bash taglist. outputFile is the path to use
+// for output. If outputFile already exists, it will only be overwritten if overwrite is true.
+BOSS_API uint32_t boss_write_minimal_list (boss_db db, const uint8_t * outputFile, const bool overwrite) {
+
+}
diff --git a/src/api.h b/src/api.h
new file mode 100644
index 00000000..5694b230
--- /dev/null
+++ b/src/api.h
@@ -0,0 +1,240 @@
+/* BOSS
+
+ A plugin load order optimiser for games that use the esp/esm plugin system.
+
+ Copyright (C) 2012 WrinklyNinja
+
+ This file is part of BOSS.
+
+ BOSS is free software: you can redistribute
+ it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ BOSS is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with BOSS. If not, see
+ .
+*/
+
+#ifndef __BOSS_API_H__
+#define __BOSS_API_H__
+
+#include
+#include
+
+#if defined(_MSC_VER)
+//MSVC doesn't support C99, so do the stdbool.h definitions ourselves.
+//START OF stdbool.h DEFINITIONS.
+# ifndef __cplusplus
+# define bool _Bool
+# define true 1
+# define false 0
+# endif
+# define __bool_true_false_are_defined 1
+//END OF stdbool.h DEFINITIONS.
+#else
+# include
+#endif
+
+// set up dll import/export decorators
+// when compiling the dll on windows, ensure BOSS_API_EXPORT is defined. clients
+// that use this header do not need to define anything to import the symbols
+// properly.
+#if defined(_WIN32) || defined(_WIN64)
+# ifdef BOSS_API_EXPORT
+# define BOSS_API __declspec(dllexport)
+# else
+# define BOSS_API __declspec(dllimport)
+# endif
+#else
+# define BOSS_API
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+////////////////////////
+// Types
+////////////////////////
+
+// All API strings are uint8_t* strings encoded in UTF-8. Strings returned
+// by the API should not have their memory freed by the client: the API will
+// clean up after itself.
+// All API numbers and error codes are uint32_t integers.
+
+// Abstracts the definition of BOSS's internal state while still providing
+// type safety across the API.
+typedef struct _boss_db_int * boss_db;
+
+// boss_tag structure gives the Unique ID number (UID) for each Bash Tag and
+// the corresponding Tag name string.
+typedef struct {
+ uint32_t id;
+ const uint8_t * name; // don't use char for utf-8 since char can be signed
+} boss_tag;
+
+// boss_message structure gives the type of message and it contents.
+typedef struct {
+ uint32_t type;
+ const uint8_t * message;
+} boss_message;
+
+
+// The following are the possible codes that the API can return.
+BOSS_API extern const uint32_t BOSS_API_OK;
+BOSS_API extern const uint32_t BOSS_API_OK_NO_UPDATE_NECESSARY;
+BOSS_API extern const uint32_t BOSS_API_WARN_BAD_FILENAME;
+BOSS_API extern const uint32_t BOSS_API_WARN_LO_MISMATCH;
+BOSS_API extern const uint32_t BOSS_API_ERROR_FILE_WRITE_FAIL;
+BOSS_API extern const uint32_t BOSS_API_ERROR_FILE_DELETE_FAIL;
+BOSS_API extern const uint32_t BOSS_API_ERROR_FILE_NOT_UTF8;
+BOSS_API extern const uint32_t BOSS_API_ERROR_FILE_NOT_FOUND;
+BOSS_API extern const uint32_t BOSS_API_ERROR_FILE_RENAME_FAIL;
+BOSS_API extern const uint32_t BOSS_API_ERROR_TIMESTAMP_READ_FAIL;
+BOSS_API extern const uint32_t BOSS_API_ERROR_TIMESTAMP_WRITE_FAIL;
+BOSS_API extern const uint32_t BOSS_API_ERROR_PARSE_FAIL;
+BOSS_API extern const uint32_t BOSS_API_ERROR_CONDITION_EVAL_FAIL;
+BOSS_API extern const uint32_t BOSS_API_ERROR_REGEX_EVAL_FAIL;
+BOSS_API extern const uint32_t BOSS_API_ERROR_NO_MEM;
+BOSS_API extern const uint32_t BOSS_API_ERROR_INVALID_ARGS;
+BOSS_API extern const uint32_t BOSS_API_ERROR_NETWORK_FAIL;
+BOSS_API extern const uint32_t BOSS_API_ERROR_NO_INTERNET_CONNECTION;
+BOSS_API extern const uint32_t BOSS_API_ERROR_NO_TAG_MAP;
+BOSS_API extern const uint32_t BOSS_API_ERROR_PLUGINS_FULL;
+BOSS_API extern const uint32_t BOSS_API_ERROR_GAME_NOT_FOUND;
+BOSS_API extern const uint32_t BOSS_API_ERROR_PLUGIN_BEFORE_MASTER;
+BOSS_API extern const uint32_t BOSS_API_RETURN_MAX;
+
+// The following are the games identifiers used by the API.
+BOSS_API extern const uint32_t BOSS_API_GAME_OBLIVION;
+BOSS_API extern const uint32_t BOSS_API_GAME_FALLOUT3;
+BOSS_API extern const uint32_t BOSS_API_GAME_FALLOUTNV;
+BOSS_API extern const uint32_t BOSS_API_GAME_NEHRIM;
+BOSS_API extern const uint32_t BOSS_API_GAME_SKYRIM;
+BOSS_API extern const uint32_t BOSS_API_GAME_MORROWIND;
+
+// BOSS message types.
+BOSS_API extern const uint32_t BOSS_API_MESSAGE_SAY;
+BOSS_API extern const uint32_t BOSS_API_MESSAGE_TAG;
+BOSS_API extern const uint32_t BOSS_API_MESSAGE_REQUIREMENT;
+BOSS_API extern const uint32_t BOSS_API_MESSAGE_INCOMPATIBILITY;
+BOSS_API extern const uint32_t BOSS_API_MESSAGE_DIRTY;
+BOSS_API extern const uint32_t BOSS_API_MESSAGE_WARN;
+BOSS_API extern const uint32_t BOSS_API_MESSAGE_ERROR;
+
+
+
+//////////////////////////////
+// Error Handling Functions
+//////////////////////////////
+
+// Outputs a string giving the details of the last time an error or
+// warning return code was returned by a function. The string exists
+// until this function is called again or until CleanUpAPI is called.
+BOSS_API uint32_t boss_get_error_message (uint8_t ** message);
+
+
+//////////////////////////////
+// Version Functions
+//////////////////////////////
+
+// Returns whether this version of BOSS supports the API from the given
+// BOSS version. Abstracts BOSS API stability policy away from clients.
+BOSS_API bool boss_is_compatible (const uint32_t versionMajor, const uint32_t versionMinor, const uint32_t versionPatch);
+
+// Returns the version string for this version of BOSS.
+// The string exists until this function is called again or until
+// CleanUpAPI is called.
+BOSS_API uint32_t boss_get_version (uint32_t * versionMajor, uint32_t * versionMinor, uint32_t * versionPatch);
+
+
+////////////////////////////////////
+// Lifecycle Management Functions
+////////////////////////////////////
+
+// Explicitly manage database lifetime. Allows clients to free memory when
+// they want/need to. clientGame sets the game the DB is for, and dataPath
+// is the path to that game's Data folder, and is case-sensitive if the
+// underlying filesystem is case-sensitive. This function also checks that
+// plugins.txt and loadorder.txt (if they both exist) are in sync. If
+// dataPath == NULL then the API will attempt to detect the data path of
+// the specified game.
+BOSS_API uint32_t boss_create_db (boss_db * db, const uint32_t clientGame, const uint8_t * gamePath);
+
+// Destroys the given DB, freeing any memory allocated as part of its use.
+BOSS_API void boss_destroy_db (boss_db db);
+
+// Frees memory allocated to version and error strings.
+BOSS_API void boss_cleanup ();
+
+
+///////////////////////////////////
+// Database Loading Functions
+///////////////////////////////////
+
+// Loads the masterlist and userlist from the paths specified.
+// Can be called multiple times. On error, the database is unchanged.
+// Paths are case-sensitive if the underlying filesystem is case-sensitive.
+// masterlistPath and userlistPath are files.
+BOSS_API uint32_t boss_load_lists (boss_db db, const uint8_t * masterlistPath,
+ const uint8_t * userlistPath);
+
+// Evaluates all conditional lines and regex mods the loaded masterlist.
+// This exists so that Load() doesn't need to be called whenever the mods
+// installed are changed. Evaluation does not take place unless this function
+// is called. Repeated calls re-evaluate the masterlist from scratch each time,
+// ignoring the results of any previous evaluations. Paths are case-sensitive
+// if the underlying filesystem is case-sensitive.
+BOSS_API uint32_t boss_eval_lists (boss_db db);
+
+
+//////////////////////////
+// DB Access Functions
+//////////////////////////
+
+// Returns an array of the Bash Tags encounterred when loading the masterlist
+// and userlist, and the number of tags in the returned array. The array and
+// its contents are static and should not be freed by the client.
+BOSS_API uint32_t boss_get_tag_map (boss_db db, boss_tag ** tagMap, size_t * numTags);
+
+// Returns arrays of Bash Tag UIDs for Bash Tags suggested for addition and removal
+// by BOSS's masterlist and userlist, and the number of tags in each array.
+// The returned arrays are valid until the db is destroyed or until the Load
+// function is called. The arrays should not be freed by the client. modName is
+// case-insensitive. If no Tags are found for an array, the array pointer (*tagIds)
+// will be NULL. The userlistModified bool is true if the userlist contains Bash Tag
+// suggestion message additions.
+BOSS_API uint32_t boss_get_plugin_tags (boss_db db, const uint8_t * plugin,
+ uint32_t ** tagIds_added,
+ size_t * numTags_added,
+ uint32_t **tagIds_removed,
+ size_t *numTags_removed,
+ bool * userlistModified);
+
+// Returns the messages attached to the given plugin. Messages are valid until Load,
+// DestroyBossDb or GetPluginMessages are next called. plugin is case-insensitive.
+// If no messages are attached, *messages will be NULL and numMessages will equal 0.
+BOSS_API uint32_t boss_get_plugin_messages (boss_db db, const uint8_t * plugin,
+ boss_message ** messages,
+ size_t * numMessages);
+
+// Writes a minimal masterlist that only contains mods that have Bash Tag suggestions,
+// and/or dirty messages, plus the Tag suggestions and/or messages themselves and their
+// conditions, in order to create the Wrye Bash taglist. outputFile is the path to use
+// for output. If outputFile already exists, it will only be overwritten if overwrite is true.
+BOSS_API uint32_t boss_write_minimal_list (boss_db db, const uint8_t * outputFile, const bool overwrite);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/game.cpp b/src/game.cpp
new file mode 100644
index 00000000..3702f235
--- /dev/null
+++ b/src/game.cpp
@@ -0,0 +1,145 @@
+/* BOSS
+
+ A plugin load order optimiser for games that use the esp/esm plugin system.
+
+ Copyright (C) 2012 WrinklyNinja
+
+ This file is part of BOSS.
+
+ BOSS is free software: you can redistribute
+ it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ BOSS is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with BOSS. If not, see
+ .
+*/
+
+#include "game.h"
+#include "globals.h"
+#include "helpers.h"
+
+#include
+
+#if _WIN32 || _WIN64
+# include
+# include
+#endif
+
+using namespace std;
+
+namespace fs = boost::filesystem;
+
+namespace boss {
+
+ Game::Game()
+ : id(BOSS_GAME_AUTODETECT) {}
+
+ Game::Game(const uint32_t gameCode, const string path, const bool noPathInit)
+ : id(gameCode) {
+ if (Id() == BOSS_GAME_TES4) {
+ name = "TES IV: Oblivion";
+
+ registryKey = "Software\\Bethesda Softworks\\Oblivion";
+ registrySubKey = "Installed Path";
+
+ bossFolderName = "Oblivion";
+ pluginsFolderName = "Data";
+ } else if (Id() == BOSS_GAME_TES5) {
+ name = "TES V: Skyrim";
+
+ registryKey = "Software\\Bethesda Softworks\\Skyrim";
+ registrySubKey = "Installed Path";
+
+ bossFolderName = "Skyrim";
+ pluginsFolderName = "Data";
+ } else if (Id() == BOSS_GAME_FO3) {
+ name = "Fallout 3";
+
+ registryKey = "Software\\Bethesda Softworks\\Fallout3";
+ registrySubKey = "Installed Path";
+
+ bossFolderName = "Fallout 3";
+ pluginsFolderName = "Data";
+ } else if (Id() == BOSS_GAME_FONV) {
+ name = "Fallout: New Vegas";
+
+ registryKey = "Software\\Bethesda Softworks\\FalloutNV";
+ registrySubKey = "Installed Path";
+
+ bossFolderName = "Fallout New Vegas";
+ pluginsFolderName = "Data";
+ } else
+ throw runtime_error("Invalid game ID supplied.");
+
+ if (!noPathInit) {
+ if (path.empty()) {
+ //First look for local install, then look for Registry.
+ if (IsInstalledLocally())
+ gamePath = "..";
+ else if (RegKeyExists("HKEY_LOCAL_MACHINE", registryKey, registrySubKey))
+ gamePath = fs::path(RegKeyStringValue("HKEY_LOCAL_MACHINE", registryKey, registrySubKey));
+ else
+ throw runtime_error("Game path could not be detected.");
+ } else
+ gamePath = fs::path(path);
+ }
+ }
+
+ bool Game::IsInstalled() const {
+ return (IsInstalledLocally() || RegKeyExists("HKEY_LOCAL_MACHINE", registryKey, registrySubKey));
+ }
+
+ bool Game::IsInstalledLocally() const {
+ return fs::exists(fs::path("..") / pluginsFolderName);
+ }
+
+ uint32_t Game::Id() const {
+ return id;
+ }
+
+ string Game::Name() const {
+ return name;
+ }
+
+ fs::path Game::GamePath() const {
+ return gamePath;
+ }
+
+ fs::path Game::DataPath() const {
+ return GamePath() / pluginsFolderName;
+ }
+
+ void Game::CreateBOSSGameFolder() {
+ //Make sure that the BOSS game path exists.
+ try {
+ if (!fs::exists(bossFolderName))
+ fs::create_directory(bossFolderName);
+ } catch (fs::filesystem_error e) {
+ throw runtime_error("Could not create BOSS folder for game.");
+ }
+ }
+
+ //Can be used to get the location of the LOCALAPPDATA folder (and its Windows XP equivalent).
+ fs::path Game::GetLocalAppDataPath() {
+#if _WIN32 || _WIN64
+ HWND owner;
+ TCHAR path[MAX_PATH];
+
+ HRESULT res = SHGetFolderPath(owner, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path);
+
+ if (res == S_OK)
+ return fs::path(path);
+ else
+ return fs::path("");
+#else
+ return fs::path("");
+#endif
+ }
+}
diff --git a/src/game.h b/src/game.h
new file mode 100644
index 00000000..8709561e
--- /dev/null
+++ b/src/game.h
@@ -0,0 +1,64 @@
+/* BOSS
+
+ A plugin load order optimiser for games that use the esp/esm plugin system.
+
+ Copyright (C) 2012 WrinklyNinja
+
+ This file is part of BOSS.
+
+ BOSS is free software: you can redistribute
+ it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ BOSS is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with BOSS. If not, see
+ .
+*/
+
+#ifndef __BOSS_GAME__
+#define __BOSS_GAME__
+
+#include
+
+namespace boss {
+
+ class Game {
+ public:
+ Game(); //Sets game to BOSS_GAME_AUTODETECT, with all other vars being empty.
+ Game(const uint32_t gameCode, const std::string path = "", const bool noPathInit = false); //Empty path means constructor will detect its location. If noPathInit is true, then the game's BOSS subfolder will not be created.
+
+ bool IsInstalled() const;
+ bool IsInstalledLocally() const;
+
+ uint32_t Id() const;
+ std::string Name() const; //Returns the game's name, eg. "TES IV: Oblivion".
+
+ boost::filesystem::path GamePath() const;
+ boost::filesystem::path DataPath() const;
+
+ //Creates directory in BOSS folder for BOSS's game-specific files.
+ void CreateBOSSGameFolder();
+ private:
+ uint32_t id;
+ std::string name;
+
+ std::string registryKey;
+ std::string registrySubKey;
+
+ std::string bossFolderName;
+ std::string pluginsFolderName;
+
+ boost::filesystem::path gamePath; //Path to the game's folder.
+
+ //Can be used to get the location of the LOCALAPPDATA folder (and its Windows XP equivalent).
+ boost::filesystem::path GetLocalAppDataPath();
+ };
+}
+
+#endif
diff --git a/src/globals.h b/src/globals.h
new file mode 100644
index 00000000..e33a5ab0
--- /dev/null
+++ b/src/globals.h
@@ -0,0 +1,36 @@
+/* BOSS
+
+ A plugin load order optimiser for games that use the esp/esm plugin system.
+
+ Copyright (C) 2012 WrinklyNinja
+
+ This file is part of BOSS.
+
+ BOSS is free software: you can redistribute
+ it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ BOSS is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with BOSS. If not, see
+ .
+*/
+#ifndef __BOSS_GLOBALS__
+#define __BOSS_GLOBALS__
+
+const int BOSS_GAME_AUTODETECT = 0;
+const int BOSS_GAME_TES4 = 1;
+const int BOSS_GAME_TES5 = 2;
+const int BOSS_GAME_FO3 = 3;
+const int BOSS_GAME_FONV = 4;
+
+const int BOSS_VERSION_MAJOR = 3;
+const int BOSS_VERSION_MINOR = 0;
+const int BOSS_VERSION_PATCH = 0;
+
+#endif
diff --git a/src/helpers.cpp b/src/helpers.cpp
new file mode 100644
index 00000000..9cd0b78f
--- /dev/null
+++ b/src/helpers.cpp
@@ -0,0 +1,264 @@
+/* BOSS
+
+ A "one-click" program for users that quickly optimises and avoids
+ detrimental conflicts in their TES IV: Oblivion, Nehrim - At Fate's Edge,
+ TES V: Skyrim, Fallout 3 and Fallout: New Vegas mod load orders.
+
+ Copyright (C) 2009-2012 BOSS Development Team.
+
+ This file is part of BOSS.
+
+ BOSS is free software: you can redistribute
+ it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ BOSS is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with BOSS. If not, see
+ .
+
+ $Revision: 3184 $, $Date: 2011-08-26 20:52:13 +0100 (Fri, 26 Aug 2011) $
+*/
+
+#include "helpers.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include "source/utf8.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if _WIN32 || _WIN64
+# include "Windows.h"
+# include "Shlobj.h"
+#endif
+
+namespace boss {
+ using namespace std;
+ using namespace boost;
+ using boost::algorithm::replace_all;
+ using boost::algorithm::replace_first;
+ namespace karma = boost::spirit::karma;
+
+ //Calculate the CRC of the given file for comparison purposes.
+ uint32_t GetCrc32(const fs::path& filename) {
+ uint32_t chksum = 0;
+ static const size_t buffer_size = 8192;
+ char buffer[buffer_size];
+ ifstream ifile(filename.c_str(), ios::binary);
+ // LOG_TRACE("calculating CRC for: '%s'", filename.string().c_str());
+ boost::crc_32_type result;
+ if (ifile) {
+ do {
+ ifile.read(buffer, buffer_size);
+ result.process_bytes(buffer, ifile.gcount());
+ } while (ifile);
+ chksum = result.checksum();
+ } else {
+ throw runtime_error("Unable to open \"" + filename.string() + "\" for CRC calculation.");
+ }
+ // LOG_DEBUG("CRC32('%s'): 0x%x", filename.string().c_str(), chksum);
+ return chksum;
+ }
+
+ //Converts an integer to a string using BOOST's Spirit.Karma, which is apparently a lot faster than a stringstream conversion...
+ std::string IntToString(const uint32_t n) {
+ string out;
+ back_insert_iterator sink(out);
+ karma::generate(sink,karma::upper[karma::uint_],n);
+ return out;
+ }
+
+ //Converts an integer to a hex string using BOOST's Spirit.Karma, which is apparently a lot faster than a stringstream conversion...
+ std::string IntToHexString(const uint32_t n) {
+ string out;
+ back_insert_iterator sink(out);
+ karma::generate(sink,karma::upper[karma::hex],n);
+ return out;
+ }
+
+ //Converts a boolean to a string representation (true/false)
+ std::string BoolToString(const bool b) {
+ if (b)
+ return "true";
+ else
+ return "false";
+ }
+
+ //Check if registry subkey exists.
+ bool RegKeyExists(const std::string& keyStr, const std::string& subkey, const std::string& value) {
+ return !RegKeyStringValue(keyStr, subkey, value).empty();
+ }
+
+ //Get registry subkey value string.
+ string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value) {
+#if _WIN32 || _WIN64
+ HKEY hKey, key;
+ DWORD BufferSize = 4096;
+ wchar_t val[4096];
+
+ if (keyStr == "HKEY_CLASSES_ROOT")
+ key = HKEY_CLASSES_ROOT;
+ else if (keyStr == "HKEY_CURRENT_CONFIG")
+ key = HKEY_CURRENT_CONFIG;
+ else if (keyStr == "HKEY_CURRENT_USER")
+ key = HKEY_CURRENT_USER;
+ else if (keyStr == "HKEY_LOCAL_MACHINE")
+ key = HKEY_LOCAL_MACHINE;
+ else if (keyStr == "HKEY_USERS")
+ key = HKEY_USERS;
+
+ LONG ret = RegOpenKeyEx(key, fs::path(subkey).wstring().c_str(), 0, KEY_READ|KEY_WOW64_32KEY, &hKey);
+
+ if (ret == ERROR_SUCCESS) {
+ ret = RegQueryValueEx(hKey, fs::path(value).wstring().c_str(), NULL, NULL, (LPBYTE)&val, &BufferSize);
+ RegCloseKey(hKey);
+
+ if (ret == ERROR_SUCCESS)
+ return fs::path(val).string(); //Easiest way to convert from wide to narrow character strings.
+ else
+ return "";
+ } else
+ return "";
+#else
+ return "";
+#endif
+ }
+
+
+ //////////////////////////////
+ // Version Class Functions
+ //////////////////////////////
+
+ Version::Version() {}
+
+ Version::Version(const char * ver)
+ : verString(ver) {}
+
+ Version::Version(const std::string ver)
+ : verString(ver) {}
+
+ Version::Version(const fs::path file) {
+ // LOG_TRACE("extracting version from '%s'", file.string().c_str());
+#if _WIN32 || _WIN64
+ DWORD dummy = 0;
+ DWORD size = GetFileVersionInfoSize(file.wstring().c_str(), &dummy);
+
+ if (size > 0) {
+ LPBYTE point = new BYTE[size];
+ UINT uLen;
+ VS_FIXEDFILEINFO *info;
+ string ver;
+
+ GetFileVersionInfo(file.wstring().c_str(),0,size,point);
+
+ VerQueryValue(point,L"\\",(LPVOID *)&info,&uLen);
+
+ DWORD dwLeftMost = HIWORD(info->dwFileVersionMS);
+ DWORD dwSecondLeft = LOWORD(info->dwFileVersionMS);
+ DWORD dwSecondRight = HIWORD(info->dwFileVersionLS);
+ DWORD dwRightMost = LOWORD(info->dwFileVersionLS);
+
+ delete [] point;
+
+ verString = IntToString(dwLeftMost) + '.' + IntToString(dwSecondLeft) + '.' + IntToString(dwSecondRight) + '.' + IntToString(dwRightMost);
+ }
+#else
+ // ensure filename has no quote characters in it to avoid command injection attacks
+ if (string::npos == file.string().find('"')) {
+ // LOG_WARN("filename has embedded quotes; skipping to avoid command injection: '%s'", file.string().c_str());
+ // } else {
+ // command mostly borrowed from the gnome-exe-thumbnailer.sh script
+ // wrestool is part of the icoutils package
+ string cmd = "wrestool --extract --raw --type=version \"" + file.string() + "\" | tr '\\0, ' '\\t.\\0' | sed 's/\\t\\t/_/g' | tr -c -d '[:print:]' | sed -r 's/.*Version[^0-9]*([0-9]+(\\.[0-9]+)+).*/\\1/'";
+
+ FILE *fp = popen(cmd.c_str(), "r");
+
+ // read out the version string
+ static const uint32_t BUFSIZE = 32;
+ char buf[BUFSIZE];
+ if (NULL != fgets(buf, BUFSIZE, fp)) {
+ /* LOG_DEBUG("failed to extract version from '%s'", file.string().c_str());
+ }
+ else {
+ */ verString = string(buf);
+ // LOG_DEBUG("extracted version from '%s': %s", file.string().c_str(), retVal.c_str());
+ }
+ pclose(fp);
+ }
+#endif
+ }
+
+ string Version::AsString() const {
+ return verString;
+ }
+
+ bool Version::operator < (Version ver) {
+ //Version string could have a wide variety of formats. Use regex to choose specific comparison types.
+
+ boost::regex reg1("(\\d+\\.?)+"); //a.b.c.d.e.f.... where the letters are all integers, and 'a' is the shortest possible match.
+
+ //boost::regex reg2("(\\d+\\.?)+([a-zA-Z\\-]+(\\d+\\.?)*)+"); //Matches a mix of letters and numbers - from "0.99.xx", "1.35Alpha2", "0.9.9MB8b1", "10.52EV-D", "1.62EV" to "10.0EV-D1.62EV".
+
+ if (boost::regex_match(verString, reg1) && boost::regex_match(ver.AsString(), reg1)) {
+ //First type: numbers separated by periods. If two versions have a different number of numbers, then the shorter should be padded
+ //with zeros. An arbitrary number of numbers should be supported.
+ istringstream parser1(verString);
+ istringstream parser2(ver.AsString());
+ while (parser1.good() || parser2.good()) {
+ //Check if each stringstream is OK for i/o before doing anything with it. If not, replace its extracted value with a 0.
+ uint32_t n1, n2;
+ if (parser1.good()) {
+ parser1 >> n1;
+ parser1.get();
+ } else
+ n1 = 0;
+ if (parser2.good()) {
+ parser2 >> n2;
+ parser2.get();
+ } else
+ n2 = 0;
+ if (n1 < n2)
+ return true;
+ else if (n1 > n2)
+ return false;
+ }
+ return false;
+ } else {
+ //Wacky format. Use the Alphanum Algorithm. (what a name!)
+ return (doj::alphanum_comp(verString, ver.AsString()) < 0);
+ }
+ }
+
+ bool Version::operator > (Version ver) {
+ return (*this != ver && !(*this < ver));
+ }
+
+ bool Version::operator >= (Version ver) {
+ return (*this == ver || *this > ver);
+ }
+
+ bool Version::operator == (Version ver) {
+ return (verString == ver.AsString());
+ }
+
+ bool Version::operator != (Version ver) {
+ return !(*this == ver);
+ }
+}
diff --git a/src/helpers.h b/src/helpers.h
new file mode 100644
index 00000000..297bec1b
--- /dev/null
+++ b/src/helpers.h
@@ -0,0 +1,79 @@
+/* BOSS
+
+ A "one-click" program for users that quickly optimises and avoids
+ detrimental conflicts in their TES IV: Oblivion, Nehrim - At Fate's Edge,
+ TES V: Skyrim, Fallout 3 and Fallout: New Vegas mod load orders.
+
+ Copyright (C) 2009-2012 BOSS Development Team.
+
+ This file is part of BOSS.
+
+ BOSS is free software: you can redistribute
+ it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ BOSS is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with BOSS. If not, see
+ .
+
+ $Revision: 3163 $, $Date: 2011-08-21 22:03:18 +0100 (Sun, 21 Aug 2011) $
+*/
+
+#ifndef __BOSS_HELPERS__
+#define __BOSS_HELPERS__
+
+#include
+#include
+
+namespace boss {
+ namespace fs = boost::filesystem;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Helper functions
+ //////////////////////////////////////////////////////////////////////////
+
+ //Calculate the CRC of the given file for comparison purposes.
+ uint32_t GetCrc32(const fs::path& filename);
+
+ //Converts an integer to a string using BOOST's Spirit.Karma. Faster than a stringstream conversion.
+ std::string IntToString(const uint32_t n);
+
+ //Converts an integer to a hex string using BOOST's Spirit.Karma. Faster than a stringstream conversion.
+ std::string IntToHexString(const uint32_t n);
+
+ //Converts a boolean to a string representation (true/false)
+ std::string BoolToString(const bool b);
+
+ //Check if registry subkey exists.
+ bool RegKeyExists(const std::string& keyStr, const std::string& subkey, const std::string& value);
+
+ //Get registry subkey value string.
+ std::string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value);
+
+ //Version class for more robust version comparisons.
+ class Version {
+ private:
+ std::string verString;
+ public:
+ Version();
+ Version(const char * ver);
+ Version(const std::string ver);
+ Version(const fs::path file);
+
+ std::string AsString() const;
+
+ bool operator > (Version);
+ bool operator < (Version);
+ bool operator >= (Version);
+ bool operator == (Version);
+ bool operator != (Version);
+ };
+}
+
+#endif
diff --git a/src/metadata.cpp b/src/metadata.cpp
index 2f081326..88391497 100644
--- a/src/metadata.cpp
+++ b/src/metadata.cpp
@@ -29,84 +29,207 @@ namespace boss {
ConditionalData::ConditionalData() {}
- ConditionalData::ConditionalData(const string in) : condition(in) {}
+ ConditionalData::ConditionalData(const string& c) : condition(c) {}
- bool ConditionalData::EvalCondition() const {
+ ConditionalData::ConditionalData(const std::string& c, const std::string& d)
+ : condition(c), data(d) {}
- return true;
+ bool ConditionalData::IsConditional() const {
+ return !condition.empty();
+ }
+
+ std::string ConditionalData::Condition() const {
+ return condition;
+ }
+
+ std::string ConditionalData::Data() const {
+ return data;
+ }
+
+ void ConditionalData::Data(const std::string& d) {
+ data = d;
+ }
+
+ Message::Message() {}
+
+ Message::Message(const std::string& t, const std::string& cont)
+ : type(t), ConditionalData("", cont) {}
+
+ Message::Message(const std::string& t, const std::string& cont,
+ const std::string& cond, const std::string& l)
+ : type(t), language(l), ConditionalData(cond, cont) {}
+
+ std::string Message::Type() const {
+ return type;
+ }
+
+ std::string Message::Language() const {
+ return language;
+ }
+
+ std::string Message::Content() const {
+ return Data();
+ }
+
+ File::File() {}
+ File::File(const std::string& n) : ConditionalData("", n) {}
+ File::File(const std::string& n, const std::string& d, const std::string& c)
+ : display(d), ConditionalData(c, n) {}
+
+ std::string File::Name() const {
+ return Data();
+ }
+
+ std::string File::DisplayName() const {
+ return display;
}
Tag::Tag() : addTag(true) {}
- Tag::Tag(const string tag) {
+ Tag::Tag(const string& tag) {
+ string data;
if (tag[0] == '-') {
addTag = false;
- name = tag.substr(1);
+ data = tag.substr(1);
} else {
addTag = true;
- name = tag;
+ data = tag;
}
+ Data(data);
}
- Tag::Tag(const string condition, const string tag) : ConditionalData(condition) {
+ Tag::Tag(const string& tag, const string& condition) : ConditionalData(condition) {
+ string data;
if (tag[0] == '-') {
addTag = false;
- name = tag.substr(1);
+ data = tag.substr(1);
} else {
addTag = true;
- name = tag;
+ data = tag;
}
+ Data(data);
}
bool Tag::IsAddition() const {
return addTag;
}
- string Tag::Data() const {
+ string Tag::PrefixedName() const {
if (addTag)
- return name;
+ return Name();
else
- return "-" + name;
+ return "-" + Name();
}
- void Plugin::EvalAllConditions() {
+ std::string Tag::Name() const {
+ return Data();
+ }
+
+ Plugin::Plugin() : enabled(true), priority(0) {}
+ Plugin::Plugin(const std::string n) : name(n), enabled(true), priority(0) {}
+
+ std::string Plugin::Name() const {
+ return name;
+ }
+
+ bool Plugin::Enabled() const {
+ return enabled;
+ }
+
+ int Plugin::Priority() const {
+ return priority;
+ }
+
+ std::list Plugin::LoadAfter() const {
+ return loadAfter;
+ }
+
+ std::list Plugin::Reqs() const {
+ return requirements;
+ }
+
+ std::set Plugin::Incs() const {
+ return incompatibilities;
+ }
+
+ std::list Plugin::Messages() const {
+ return messages;
+ }
+
+ std::list Plugin::Tags() const {
+ return tags;
+ }
+
+ void Plugin::Enabled(const bool e) {
+ enabled = e;
+ }
+
+ void Plugin::Priority(const int p) {
+ priority = p;
+ }
+
+ void Plugin::LoadAfter(const std::list& l) {
+ loadAfter = l;
+ }
+
+ void Plugin::Reqs(const std::list& r) {
+ requirements = r;
+ }
+
+ void Plugin::Incs(const std::set& i) {
+ incompatibilities = i;
+ }
+
+ void Plugin::Messages(const std::list& m) {
+ messages = m;
+ }
+
+ void Plugin::Tags(const std::list& t) {
+ tags = t;
+ }
+
+ void Plugin::EvalAllConditions(const boost::filesystem::path& gamePath) {
for (list::iterator it = loadAfter.begin(); it != loadAfter.end();) {
- if (!it->EvalCondition())
+ if (!it->EvalCondition(gamePath))
it = loadAfter.erase(it);
else
++it;
}
for (list::iterator it = requirements.begin(); it != requirements.end();) {
- if (!it->EvalCondition())
+ if (!it->EvalCondition(gamePath))
it = requirements.erase(it);
else
++it;
}
for (set::iterator it = incompatibilities.begin(); it != incompatibilities.end();) {
- if (!it->EvalCondition())
+ if (!it->EvalCondition(gamePath))
incompatibilities.erase(it++);
else
++it;
}
for (list::iterator it = messages.begin(); it != messages.end();) {
- if (!it->EvalCondition())
+ if (!it->EvalCondition(gamePath))
it = messages.erase(it);
else
++it;
}
for (list::iterator it = tags.begin(); it != tags.end();) {
- if (!it->EvalCondition())
+ if (!it->EvalCondition(gamePath))
it = tags.erase(it++);
else
++it;
}
}
- bool Plugin::NameOnly() const {
+ bool Plugin::HasNameOnly() const {
return priority == 0 && enabled == true && loadAfter.empty() && requirements.empty() && incompatibilities.empty() && messages.empty() && tags.empty();
}
+
+ bool Plugin::IsRegexPlugin() const {
+ return name.substr(name.length()-5) == "\\.esp" || name.substr(name.length()-5) == "\\.esm";
+ }
}
diff --git a/src/metadata.h b/src/metadata.h
index e20394bf..06d82cf9 100644
--- a/src/metadata.h
+++ b/src/metadata.h
@@ -27,73 +27,120 @@
#include
#include
+#include
+
namespace boss {
class ConditionalData {
public:
ConditionalData();
- ConditionalData(const std::string s);
- std::string condition;
+ ConditionalData(const std::string& condition);
+ ConditionalData(const std::string& condition, const std::string& data);
- bool EvalCondition() const;
+ bool IsConditional() const;
+ bool EvalCondition(const boost::filesystem::path& gamePath) const;
+
+ std::string Condition() const;
+ std::string Data() const;
+
+ void Data(const std::string& data);
+ private:
+ std::string condition;
+ std::string data;
};
class Message : public ConditionalData {
public:
+ Message();
+ Message(const std::string& type, const std::string& content);
+ Message(const std::string& type, const std::string& content,
+ const std::string& condition, const std::string& language);
+
+ std::string Type() const;
+ std::string Language() const;
+ std::string Content() const;
+ private:
std::string type;
std::string language;
- std::string content;
};
class File : public ConditionalData {
public:
- std::string name;
+ File();
+ File(const std::string& name);
+ File(const std::string& name, const std::string& display,
+ const std::string& condition);
+
+ std::string Name() const;
+ std::string DisplayName() const;
+ private:
std::string display;
};
class Tag : public ConditionalData {
public:
Tag();
- Tag(const std::string tag);
- Tag(const std::string condition, const std::string tag);
-
- bool addTag;
- std::string name;
+ Tag(const std::string& tag);
+ Tag(const std::string& tag, const std::string& condition);
bool IsAddition() const;
- std::string Data() const; //Name with '-' in front if suggested for removal.
+ std::string Name() const;
+ std::string PrefixedName() const; //Name with '-' in front if suggested for removal.
+ private:
+ bool addTag;
};
struct file_comp {
bool operator() (const File& lhs, const File& rhs) const {
- return lhs.name < rhs.name;
+ return lhs.Name() < rhs.Name();
}
};
struct tag_comp {
bool operator() (const Tag& lhs, const Tag& rhs) const {
- return lhs.name < rhs.name;
+ return lhs.Name() < rhs.Name();
}
};
class Plugin {
public:
+ Plugin();
+ Plugin(const std::string name);
+
+ std::string Name() const;
+ bool Enabled() const;
+ int Priority() const;
+ std::list LoadAfter() const;
+ std::list Reqs() const;
+ std::set Incs() const;
+ std::list Messages() const;
+ std::list Tags() const;
+
+ void Enabled(const bool enabled);
+ void Priority(const int priority);
+ void LoadAfter(const std::list& after);
+ void Reqs(const std::list& reqs);
+ void Incs(const std::set& incs);
+ void Messages(const std::list& messages);
+ void Tags(const std::list& tags);
+
+ void EvalAllConditions(const boost::filesystem::path& gamePath);
+ bool HasNameOnly() const;
+ bool IsRegexPlugin() const;
+ private:
std::string name;
bool enabled; //Default to true.
- int priority; //Default to 0 : >0 his higher, <0 is lower priorities.
+ int priority; //Default to 0 : >0 is higher, <0 is lower priorities.
std::list loadAfter;
std::list requirements;
std::set incompatibilities;
std::list messages;
std::list tags;
-
- void EvalAllConditions();
- bool NameOnly() const;
};
struct plugin_comp {
bool operator() (const Plugin& lhs, const Plugin& rhs) const {
- return lhs.name < rhs.name;
+ return lhs.Name() < rhs.Name();
}
};
}
diff --git a/src/parsers.h b/src/parsers.h
index bfa7c6aa..9e88ddb7 100644
--- a/src/parsers.h
+++ b/src/parsers.h
@@ -24,8 +24,21 @@
#ifndef __BOSS_PARSERS__
#define __BOSS_PARSERS__
+#ifndef BOOST_SPIRIT_UNICODE
+#define BOOST_SPIRIT_UNICODE
+#endif
+
#include "metadata.h"
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
namespace YAML {
///////////////////////
@@ -36,10 +49,10 @@ namespace YAML {
struct convert {
static Node encode(const boss::Message& rhs) {
Node node;
- node["condition"] = rhs.condition;
- node["type"] = rhs.type;
- node["content"] = rhs.content;
- node["lang"] = rhs.language;
+ node["condition"] = rhs.Condition();
+ node["type"] = rhs.Type();
+ node["content"] = rhs.Content();
+ node["lang"] = rhs.Language();
return node;
}
@@ -47,14 +60,17 @@ namespace YAML {
if(!node.IsMap())
return false;
+ std::string condition, type, content, language;
if (node["condition"])
- rhs.condition = node["condition"].as();
+ condition = node["condition"].as();
if (node["type"])
- rhs.type = node["type"].as();
+ type = node["type"].as();
if (node["content"])
- rhs.content = node["content"].as();
+ content = node["content"].as();
if (node["lang"])
- rhs.language = node["lang"].as();
+ language = node["lang"].as();
+
+ rhs = boss::Message(type, content, condition, language);
return true;
}
};
@@ -63,23 +79,24 @@ namespace YAML {
struct convert {
static Node encode(const boss::File& rhs) {
Node node;
- node["condition"] = rhs.condition;
- node["name"] = rhs.name;
- node["display"] = rhs.display;
+ node["condition"] = rhs.Condition();
+ node["name"] = rhs.Name();
+ node["display"] = rhs.DisplayName();
return node;
}
static bool decode(const Node& node, boss::File& rhs) {
if(node.IsMap()) {
+ std::string condition, name, display;
if (node["condition"])
- rhs.condition = node["condition"].as();
+ condition = node["condition"].as();
if (node["name"])
- rhs.name = node["name"].as();
+ name = node["name"].as();
if (node["display"])
- rhs.display = node["display"].as();
- } else {
- rhs.name = node.as();
- }
+ display = node["display"].as();
+ rhs = boss::File(name, display, condition);
+ } else
+ rhs = boss::File(node.as());
return true;
}
};
@@ -88,25 +105,22 @@ namespace YAML {
struct convert {
static Node encode(const boss::Tag& rhs) {
Node node;
- node["condition"] = rhs.condition;
- if (!rhs.addTag)
- node["name"] = "-" + rhs.name;
- else
- node["name"] = rhs.name;
+ node["condition"] = rhs.Condition();
+ node["name"] = rhs.PrefixedName();
return node;
}
static bool decode(const Node& node, boss::Tag& rhs) {
- std::string condition, tag;
if(node.IsMap()) {
+ std::string condition, tag;
if (node["condition"])
condition = node["condition"].as();
if (node["name"])
tag = node["name"].as();
+ rhs = boss::Tag(tag, condition);
} else if (node.IsScalar()) {
- tag = node.as();
+ rhs = boss::Tag(node.as());
}
- rhs = boss::Tag(condition, tag);
return true;
}
};
@@ -138,14 +152,14 @@ namespace YAML {
struct convert {
static Node encode(const boss::Plugin& rhs) {
Node node;
- node["name"] = rhs.name;
- node["enabled"] = rhs.enabled;
- node["priority"] = rhs.priority;
- node["after"] = rhs.loadAfter;
- node["req"] = rhs.requirements;
- node["inc"] = rhs.incompatibilities;
- node["msg"] = rhs.messages;
- node["tag"] = rhs.tags;
+ node["name"] = rhs.Name();
+ node["enabled"] = rhs.Enabled();
+ node["priority"] = rhs.Priority();
+ node["after"] = rhs.LoadAfter();
+ node["req"] = rhs.Reqs();
+ node["inc"] = rhs.Incs();
+ node["msg"] = rhs.Messages();
+ node["tag"] = rhs.Tags();
return node;
}
@@ -155,27 +169,23 @@ namespace YAML {
return false;
if (node["name"])
- rhs.name = node["name"].as();
+ rhs = boss::Plugin(node["name"].as());
if (node["enabled"])
- rhs.enabled = node["enabled"].as();
- else
- rhs.enabled = true;
+ rhs.Enabled(node["enabled"].as());
if (node["priority"])
- rhs.priority = node["priority"].as();
- else
- rhs.priority = 0;
+ rhs.Priority(node["priority"].as());
if (node["after"])
- rhs.loadAfter = node["after"].as< std::list >();
+ rhs.LoadAfter(node["after"].as< std::list >());
if (node["req"])
- rhs.requirements = node["req"].as< std::list >();
+ rhs.Reqs(node["req"].as< std::list >());
if (node["inc"])
- rhs.incompatibilities = node["inc"].as< std::set >();
+ rhs.Incs(node["inc"].as< std::set >());
if (node["msg"])
- rhs.messages = node["msg"].as< std::list >();
+ rhs.Messages(node["msg"].as< std::list >());
if (node["tag"])
- rhs.tags = node["tag"].as< std::list >();
+ rhs.Tags(node["tag"].as< std::list >());
return true;
}
};
@@ -195,72 +205,72 @@ namespace YAML {
Emitter& operator << (Emitter& out, const boss::Message& rhs) {
out << BeginMap
- << Key << "type" << rhs.type
- << Key << "content" << rhs.content;
+ << Key << "type" << rhs.Type()
+ << Key << "content" << rhs.Content();
- if (!rhs.language.empty())
- out << Key << "lang" << rhs.language;
+ if (!rhs.Language().empty())
+ out << Key << "lang" << rhs.Language();
- if (!rhs.condition.empty())
- out << Key << "condition" << rhs.condition;
+ if (!rhs.Condition().empty())
+ out << Key << "condition" << rhs.Condition();
out << EndMap;
}
Emitter& operator << (Emitter& out, const boss::File& rhs) {
- if (rhs.condition.empty() && rhs.display.empty())
- out << rhs.name;
+ if (!rhs.IsConditional() && rhs.DisplayName().empty())
+ out << rhs.Name();
else {
out << BeginMap
- << Key << "name" << rhs.name;
+ << Key << "name" << rhs.Name();
- if (!rhs.condition.empty())
- out << Key << "condition" << rhs.condition;
+ if (!rhs.Condition().empty())
+ out << Key << "condition" << rhs.Condition();
- if (!rhs.display.empty())
- out << Key << "display" << rhs.display;
+ if (!rhs.DisplayName().empty())
+ out << Key << "display" << rhs.DisplayName();
out << EndMap;
}
}
Emitter& operator << (Emitter& out, const boss::Tag& rhs) {
- if (rhs.condition.empty())
- out << rhs.Data();
+ if (!rhs.IsConditional())
+ out << rhs.PrefixedName();
else {
out << BeginMap
- << Key << "name" << rhs.Data()
- << Key << "condition" << rhs.condition
+ << Key << "name" << rhs.PrefixedName()
+ << Key << "condition" << rhs.Condition()
<< EndMap;
}
}
Emitter& operator << (Emitter& out, const boss::Plugin& rhs) {
- if (!rhs.NameOnly()) {
+ if (!rhs.HasNameOnly()) {
out << BeginMap
- << Key << "name" << Value << rhs.name;
+ << Key << "name" << Value << rhs.Name();
- if (rhs.priority != 0)
- out << Key << "priority" << Value << rhs.priority;
+ if (rhs.Priority() != 0)
+ out << Key << "priority" << Value << rhs.Priority();
- if (!rhs.enabled)
- out << Key << "enabled" << Value << rhs.enabled;
+ if (!rhs.Enabled())
+ out << Key << "enabled" << Value << rhs.Enabled();
- if (!rhs.loadAfter.empty())
- out << Key << "after" << Value << rhs.loadAfter;
+ if (!rhs.LoadAfter().empty())
+ out << Key << "after" << Value << rhs.LoadAfter();
- if (!rhs.requirements.empty())
- out << Key << "req" << Value << rhs.requirements;
+ if (!rhs.Reqs().empty())
+ out << Key << "req" << Value << rhs.Reqs();
- if (!rhs.incompatibilities.empty())
- out << Key << "inc" << Value << rhs.incompatibilities;
+ if (!rhs.Incs().empty())
+ out << Key << "inc" << Value << rhs.Incs();
- if (!rhs.messages.empty())
- out << Key << "msg" << Value << rhs.messages;
+ if (!rhs.Messages().empty())
+ out << Key << "msg" << Value << rhs.Messages();
- if (!rhs.tags.empty())
- out << Key << "tag" << Value << rhs.tags;
+ if (!rhs.Tags().empty())
+ out << Key << "tag" << Value << rhs.Tags();
out << EndMap;
}
@@ -310,5 +320,65 @@ namespace boss {
}
*/
+
+ namespace qi = boost::spirit::qi;
+ namespace unicode = boost::spirit::unicode;
+
+ template
+ class condition_grammar : public qi::grammar {
+ public:
+ condition_grammar() : condition_grammar::base_type(expression, "condition grammar") {
+
+ expression =
+ condition;
+
+ condition =
+ ( qi::lit("if") >> type ) [qi::labels::_val = qi::labels::_1]
+ | ( qi::lit("ifnot") >> type ) [qi::labels::_val = !qi::labels::_1]
+ ;
+
+ type =
+ ( "file(" > quotedStr > ')' ) [qi::labels::_val = true]
+ | ( "checksum(" > quotedStr > ',' > +unicode::xdigit > ')' ) [qi::labels::_val = true]
+ | ( "version(" > quotedStr > ',' > quotedStr > ',' > unicode::char_ > ')' ) [qi::labels::_val = true]
+ | ( "active(" > quotedStr > ')' ) [qi::labels::_val = true]
+ ;
+
+ quotedStr = '"' > +(unicode::char_ - '"') > '"';
+
+ /* Need to add error handlers and actual type evaluation, then start on
+ compound conditional parsing. Need game path support. */
+ }
+
+ private:
+ qi::rule expression, condition, type;
+ qi::rule quotedStr;
+
+
+
+ //Eval's regex and exact paths.
+ bool FileExists(bool& result, std::string file);
+
+
+ };
+
+ bool ConditionalData::EvalCondition(const boost::filesystem::path& gamePath) const {
+ if (condition.empty())
+ return true;
+ condition_grammar grammar;
+ qi::space_type skipper;
+ std::string::const_iterator begin, end;
+ bool eval;
+
+ begin = condition.begin();
+ end = condition.end();
+
+ bool r = qi::phrase_parse(begin, end, grammar, skipper, eval);
+
+ if (!r || begin != end)
+ throw std::runtime_error("Parsing of condition \"" + condition + "\" failed!");
+
+ return eval;
+ }
}
#endif
diff --git a/src/plugindata.cpp b/src/plugindata.cpp
new file mode 100644
index 00000000..19c87455
--- /dev/null
+++ b/src/plugindata.cpp
@@ -0,0 +1,212 @@
+/* BOSS
+
+ A plugin load order optimiser for games that use the esp/esm plugin system.
+
+ Copyright (C) 2012 WrinklyNinja
+
+ This file is part of BOSS.
+
+ BOSS is free software: you can redistribute
+ it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ BOSS is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with BOSS. If not, see
+ .
+*/
+
+#include "plugindata.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace boss {
+
+ PluginData::PluginData() : crc(0) {}
+
+ PluginData::PluginData(std::string filename) : name(filename) {
+ //Load by scanning plugin file using libespm.
+ }
+
+ PluginData::PluginData(std::string filename, uint32_t fileCRC) : name(filename), crc(fileCRC) {
+ //Load by scanning cache file.
+ std::string file = name[0] + '/' + name[1] + '/' + name + ".dc";
+ //Lowercase the path.
+ std::for_each(file.begin(), file.end(), tolower);
+
+ if (!boost::filesystem::exists(file))
+ return;
+
+ ifstream ifile(file.c_str(), ios_base::binary);
+ ifile.exceptions(ifstream::failbit | ifstream::badbit | ifstream::eofbit);
+
+ //Get the number of entries.
+ uint32_t count;
+ ifile.seekg(4, ios_base::end);
+ ifile.read((char*)&count, 4);
+
+ //Read index.
+ CacheIndexEntry * index;
+ try {
+ index = new CacheIndexEntry[count];
+ } catch (std::bad_alloc& e) {
+ return;
+ }
+ ifile.seekg(4 + count * sizeof(CacheIndexEntry), ios_base::end);
+ ifile.read((char*)index, count * sizeof(CacheIndexEntry));
+
+ //Search index.
+ uint32_t i = 0;
+ for (i; i < count; i++) {
+ if (index[i].crc == crc)
+ break;
+ }
+
+ if (i == count)
+ return;
+
+ //Read plugin data size.
+ uint32_t dataSize;
+ ifile.seekg(index[i].offset, ios_base::beg);
+ ifile.read((char*)&dataSize, 4);
+
+ //Read plugin data.
+ char * data;
+ try {
+ data = new char[dataSize];
+ } catch (std::bad_alloc& e) {
+ return;
+ }
+ ifile.read(data, dataSize);
+
+ //Now we need to read the list of masters.
+ uint16_t mastersLen = *(uint16_t*)data;
+ std::string master;
+ std::vector masters;
+ for (int i=2; i < mastersLen; i++) {
+ if (data[i] != NULL)
+ master += data[i];
+ else {
+ masters.push_back(master);
+ master.clear();
+ }
+ }
+
+ //Now read the FormIDs, building up FormID objects from them and their
+ //associated masters.
+ for (int i=2+mastersLen; i < dataSize; i += 4) {
+ int masterID = data[i+3]; //FormIDs are encoded low to high byte.
+ FormID fid;
+ fid.plugin = masters[masterID];
+ fid.objectIndex = *(uint32_t*)data & 0xFFFFFF; //Zero the masterID.
+ formIDs.insert(fid);
+ }
+
+ ifile.close();
+ }
+
+ PluginData::Save() const {
+ //Save to a cache file.
+ std::string outPath = name[0] + '/' + name[1] + '/' + name + ".dc";
+ //Lowercase the path.
+ std::for_each(outPath.begin(), outPath.end(), tolower);
+
+ uint32_t count = 0;
+ CacheIndexEntry * index = NULL;
+ if (boost::filesystem::exists(file)) {
+ ifstream ifile(outPath.c_str(), ios_base::binary);
+ ifile.exceptions(ifstream::failbit | ifstream::badbit | ifstream::eofbit);
+
+ //Get the number of entries.
+ ifile.seekg(4, ios_base::end);
+ ifile.read((char*)&count, 4);
+
+ //Read index.
+ try {
+ index = new CacheIndexEntry[count];
+ } catch (std::bad_alloc& e) {
+ return;
+ }
+ ifile.seekg(4 + count * sizeof(CacheIndexEntry), ios_base::end);
+ ifile.read((char*)&index, count * sizeof(CacheIndexEntry));
+
+ ifile.close();
+ }
+
+ /*Split the FormID object list into a FormID number list and a master list.
+ This doesn't retain the same ordering of masters as the original data,
+ but that doesn't matter. */
+ std::map masters;
+ std::list fids;
+ std::string mastersList;
+ int id = 0;
+ for (std::list::iterator it = formIDs.begin(), endIt = formIDs.end(); it != endIt; ++it) {
+ std::map::iterator mit = masters.find(it->plugin);
+ if (mit != masters.end())
+ fids.insert(it->objectIndex | (mit->second<<24));
+ else {
+ id++;
+ masters.insert(pair(it->plugin, id);
+ fids.insert(it->objectIndex | (id<<24));
+ mastersList += it->plugin + '\0';
+ }
+ }
+
+ ofstream ofile(file.c_str(), ios_base::binary);
+ ofile.exceptions(ifstream::failbit | ifstream::badbit | ifstream::eofbit);
+
+ CacheIndexEntry entry;
+ entry.crc = crc;
+
+ if (!boost::filesystem::exists(file)) {
+ uint8_t nameLength = name.length();
+ ofile.write((char*)&nameLenth, 1);
+ ofile.write((char*)name.data(), nameLength);
+ entry.offset = tellg();
+ } else {
+ ofile.seekg(4 + count * sizeof(CacheIndexEntry), ios_base::end);
+ entry.offset = ofile.tellg();
+ }
+
+ //Write out data.
+ uint16_t mLen = mastersList.size();
+ ofile.write((char*)&mLen, 2);
+ ofile.write(mastersList.data(), mLen);
+ for (std::list::iterator it = formIDs.begin(), endIt = formIds.end(); it != endIt; ++it) {
+ ofile.write((char*)it, 4);
+ }
+
+ //Write out updated index.
+ if (index != NULL)
+ ofile.write((char*)index, count * sizeof(CacheIndexEntry));
+ ofile.write((char*)&entry, sizeof(CacheIndexEntry));
+ count++;
+ ofile.write((char*)&count, 4);
+
+ ofile.close();
+ }
+
+ PluginData::Empty() const {
+ return formIDs.empty();
+ }
+
+ int PluginData::Overlap(const PluginData& otherPlugin) const {
+ int count = 0;
+ for (std::list::iterator it1 = formIDs.begin(), endIt1 = formIDs.end(); it1 != endIt1; ++it1) {
+ for (std::list::iterator it2 = otherPlugin.formIDs.begin(), endIt2 = otherPlugin.formIDs.end(); it2 != endIt2; ++it2) {
+ if (*it1 == *it2)
+ count++;
+ }
+ }
+ }
+}
diff --git a/src/plugindata.h b/src/plugindata.h
new file mode 100644
index 00000000..8e1096f9
--- /dev/null
+++ b/src/plugindata.h
@@ -0,0 +1,67 @@
+/* BOSS
+
+ A plugin load order optimiser for games that use the esp/esm plugin system.
+
+ Copyright (C) 2012 WrinklyNinja
+
+ This file is part of BOSS.
+
+ BOSS is free software: you can redistribute
+ it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ BOSS is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with BOSS. If not, see
+ .
+*/
+
+#ifndef __BOSS_PLUGINDATA_H__
+#define __BOSS_PLUGINDATA_H__
+
+#include
+#include
+#include
+
+//The functions below make no distinctions between different games. Once a game
+//handler is implemented, they will be updated to use it.
+
+namespace boss {
+
+ struct CacheIndexEntry {
+ uint32_t crc;
+ uint32_t offset;
+ }
+
+ struct FormID {
+ std::string plugin;
+ uint32_t objectIndex;
+ }
+
+ struct PluginData {
+ std::string name;
+ uint32_t crc;
+ std::list formIDs; // Only the edited FormIDs though.
+
+ PluginData();
+ PluginData(std::string pluginPath);
+
+ /* Can throw std::bad_alloc or std::ios::failure */
+ PluginData(std::string name, uint32_t crc);
+
+ /* Can throw std::bad_alloc or std::ios::failure */
+ void Save() const;
+ bool Empty() const;
+
+ // Returns the record overlap of two PluginData objects.
+ int Overlap(const PluginData& otherPlugin) const;
+ }
+
+}
+
+#endif
diff --git a/src/sorting.h b/src/sorting.h
new file mode 100644
index 00000000..2f7d62f1
--- /dev/null
+++ b/src/sorting.h
@@ -0,0 +1,52 @@
+/* BOSS
+
+ A plugin load order optimiser for games that use the esp/esm plugin system.
+
+ Copyright (C) 2012 WrinklyNinja
+
+ This file is part of BOSS.
+
+ BOSS is free software: you can redistribute
+ it and/or modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ BOSS is distributed in the hope that it will
+ be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with BOSS. If not, see
+ .
+*/
+
+#ifndef __BOSS_SORTING_H__
+#define __BOSS_SORTING_H__
+
+#include "plugindata.h"
+
+namespace boss {
+
+ // None of these comparisons care about plugin name or CRC.
+ inline bool operator==(const PluginData& lhs, const PluginData& rhs){
+ return lhs.formIDs == rhs.formIDs;
+ }
+
+
+ inline bool operator!=(const PluginData& lhs, const PluginData& rhs){return !operator==(lhs,rhs);}
+
+
+ inline bool operator< (const PluginData& lhs, const PluginData& rhs){
+ return lhs.formIDs.size() < rhs.formIDs.size();
+ }
+
+
+ inline bool operator> (const PluginData& lhs, const PluginData& rhs){return operator< (rhs,lhs);}
+ inline bool operator<=(const PluginData& lhs, const PluginData& rhs){return !operator> (lhs,rhs);}
+ inline bool operator>=(const PluginData& lhs, const PluginData& rhs){return !operator< (lhs,rhs);}
+
+
+}
+
+#endif
diff --git a/src/tester.cpp b/src/tester.cpp
index 91b1da4e..b2a77f14 100644
--- a/src/tester.cpp
+++ b/src/tester.cpp
@@ -12,13 +12,8 @@ using namespace std;
void minimisePluginList(set& plugins) {
set out;
for (set::iterator it=plugins.begin(), endIt=plugins.end(); it != endIt; ++it) {
- boss::Plugin p = *it;
- p.priority = 0;
- p.enabled = true;
- p.loadAfter.clear();
- p.requirements.clear();
- p.incompatibilities.clear();
- p.messages.clear();
+ boss::Plugin p(it->Name());
+ p.Tags(it->Tags());
out.insert(p);
}
plugins.swap(out);