Some tidying up. Added game handling, some helper functions from BOSSv2, some globals, the API files, the beginnings of the sorting algorithm stuff and some BOSS data cache file reading/writing.

This commit is contained in:
WrinklyNinja
2013-01-06 13:52:31 +00:00
parent 7797ab961e
commit 895f3d18b8
15 changed files with 1670 additions and 125 deletions
+6 -3
View File
@@ -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 ()
##############################
+148
View File
@@ -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
<http://www.gnu.org/licenses/>.
*/
#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) {
}
+240
View File
@@ -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
<http://www.gnu.org/licenses/>.
*/
#ifndef __BOSS_API_H__
#define __BOSS_API_H__
#include <stdint.h>
#include <stddef.h>
#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 <stdbool.h>
#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
+145
View File
@@ -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
<http://www.gnu.org/licenses/>.
*/
#include "game.h"
#include "globals.h"
#include "helpers.h"
#include <stdexcept>
#if _WIN32 || _WIN64
# include <Windows.h>
# include <Shlobj.h>
#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
}
}
+64
View File
@@ -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
<http://www.gnu.org/licenses/>.
*/
#ifndef __BOSS_GAME__
#define __BOSS_GAME__
#include <boost/filesystem.hpp>
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
+36
View File
@@ -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
<http://www.gnu.org/licenses/>.
*/
#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
+264
View File
@@ -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
<http://www.gnu.org/licenses/>.
$Revision: 3184 $, $Date: 2011-08-26 20:52:13 +0100 (Fri, 26 Aug 2011) $
*/
#include "helpers.h"
#include <boost/spirit/include/karma.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/crc.hpp>
#include <boost/regex.hpp>
#include <alphanum.hpp>
#include "source/utf8.h"
#include <cstring>
#include <iostream>
#include <fstream>
#include <ctype.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sstream>
#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<string> 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<string> 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);
}
}
+79
View File
@@ -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
<http://www.gnu.org/licenses/>.
$Revision: 3163 $, $Date: 2011-08-21 22:03:18 +0100 (Sun, 21 Aug 2011) $
*/
#ifndef __BOSS_HELPERS__
#define __BOSS_HELPERS__
#include <string>
#include <boost/filesystem.hpp>
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
+142 -19
View File
@@ -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<File> Plugin::LoadAfter() const {
return loadAfter;
}
std::list<File> Plugin::Reqs() const {
return requirements;
}
std::set<File, file_comp> Plugin::Incs() const {
return incompatibilities;
}
std::list<Message> Plugin::Messages() const {
return messages;
}
std::list<Tag> 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<File>& l) {
loadAfter = l;
}
void Plugin::Reqs(const std::list<File>& r) {
requirements = r;
}
void Plugin::Incs(const std::set<File, file_comp>& i) {
incompatibilities = i;
}
void Plugin::Messages(const std::list<Message>& m) {
messages = m;
}
void Plugin::Tags(const std::list<Tag>& t) {
tags = t;
}
void Plugin::EvalAllConditions(const boost::filesystem::path& gamePath) {
for (list<File>::iterator it = loadAfter.begin(); it != loadAfter.end();) {
if (!it->EvalCondition())
if (!it->EvalCondition(gamePath))
it = loadAfter.erase(it);
else
++it;
}
for (list<File>::iterator it = requirements.begin(); it != requirements.end();) {
if (!it->EvalCondition())
if (!it->EvalCondition(gamePath))
it = requirements.erase(it);
else
++it;
}
for (set<File, file_comp>::iterator it = incompatibilities.begin(); it != incompatibilities.end();) {
if (!it->EvalCondition())
if (!it->EvalCondition(gamePath))
incompatibilities.erase(it++);
else
++it;
}
for (list<Message>::iterator it = messages.begin(); it != messages.end();) {
if (!it->EvalCondition())
if (!it->EvalCondition(gamePath))
it = messages.erase(it);
else
++it;
}
for (list<Tag>::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";
}
}
+65 -18
View File
@@ -27,73 +27,120 @@
#include <list>
#include <set>
#include <boost/filesystem.hpp>
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<File> LoadAfter() const;
std::list<File> Reqs() const;
std::set<File, file_comp> Incs() const;
std::list<Message> Messages() const;
std::list<Tag> Tags() const;
void Enabled(const bool enabled);
void Priority(const int priority);
void LoadAfter(const std::list<File>& after);
void Reqs(const std::list<File>& reqs);
void Incs(const std::set<File, file_comp>& incs);
void Messages(const std::list<Message>& messages);
void Tags(const std::list<Tag>& 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<File> loadAfter;
std::list<File> requirements;
std::set<File, file_comp> incompatibilities;
std::list<Message> messages;
std::list<Tag> 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();
}
};
}
+148 -78
View File
@@ -24,8 +24,21 @@
#ifndef __BOSS_PARSERS__
#define __BOSS_PARSERS__
#ifndef BOOST_SPIRIT_UNICODE
#define BOOST_SPIRIT_UNICODE
#endif
#include "metadata.h"
#include <stdexcept>
#include <boost/filesystem.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/home/phoenix/object/construct.hpp>
#include <boost/spirit/include/phoenix_bind.hpp>
namespace YAML {
///////////////////////
@@ -36,10 +49,10 @@ namespace YAML {
struct convert<boss::Message> {
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<std::string>();
condition = node["condition"].as<std::string>();
if (node["type"])
rhs.type = node["type"].as<std::string>();
type = node["type"].as<std::string>();
if (node["content"])
rhs.content = node["content"].as<std::string>();
content = node["content"].as<std::string>();
if (node["lang"])
rhs.language = node["lang"].as<std::string>();
language = node["lang"].as<std::string>();
rhs = boss::Message(type, content, condition, language);
return true;
}
};
@@ -63,23 +79,24 @@ namespace YAML {
struct convert<boss::File> {
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<std::string>();
condition = node["condition"].as<std::string>();
if (node["name"])
rhs.name = node["name"].as<std::string>();
name = node["name"].as<std::string>();
if (node["display"])
rhs.display = node["display"].as<std::string>();
} else {
rhs.name = node.as<std::string>();
}
display = node["display"].as<std::string>();
rhs = boss::File(name, display, condition);
} else
rhs = boss::File(node.as<std::string>());
return true;
}
};
@@ -88,25 +105,22 @@ namespace YAML {
struct convert<boss::Tag> {
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<std::string>();
if (node["name"])
tag = node["name"].as<std::string>();
rhs = boss::Tag(tag, condition);
} else if (node.IsScalar()) {
tag = node.as<std::string>();
rhs = boss::Tag(node.as<std::string>());
}
rhs = boss::Tag(condition, tag);
return true;
}
};
@@ -138,14 +152,14 @@ namespace YAML {
struct convert<boss::Plugin> {
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<std::string>();
rhs = boss::Plugin(node["name"].as<std::string>());
if (node["enabled"])
rhs.enabled = node["enabled"].as<bool>();
else
rhs.enabled = true;
rhs.Enabled(node["enabled"].as<bool>());
if (node["priority"])
rhs.priority = node["priority"].as<int>();
else
rhs.priority = 0;
rhs.Priority(node["priority"].as<int>());
if (node["after"])
rhs.loadAfter = node["after"].as< std::list<boss::File> >();
rhs.LoadAfter(node["after"].as< std::list<boss::File> >());
if (node["req"])
rhs.requirements = node["req"].as< std::list<boss::File> >();
rhs.Reqs(node["req"].as< std::list<boss::File> >());
if (node["inc"])
rhs.incompatibilities = node["inc"].as< std::set<boss::File, boss::file_comp> >();
rhs.Incs(node["inc"].as< std::set<boss::File, boss::file_comp> >());
if (node["msg"])
rhs.messages = node["msg"].as< std::list<boss::Message> >();
rhs.Messages(node["msg"].as< std::list<boss::Message> >());
if (node["tag"])
rhs.tags = node["tag"].as< std::list<boss::Tag> >();
rhs.Tags(node["tag"].as< std::list<boss::Tag> >());
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<typename Iterator, typename Skipper>
class condition_grammar : public qi::grammar<Iterator, bool(), Skipper> {
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<Iterator, bool(), Skipper> expression, condition, type;
qi::rule<Iterator, std::string()> 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<std::string::const_iterator, qi::space_type> 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
+212
View File
@@ -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
<http://www.gnu.org/licenses/>.
*/
#include "plugindata.h"
#include <vector>
#include <cctype>
#include <algorithm>
#include <fstream>
#include <new>
#include <boost/filesystem.hpp>
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<std::string> 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<std::string, int> masters;
std::list<uint32_t> fids;
std::string mastersList;
int id = 0;
for (std::list<FormID>::iterator it = formIDs.begin(), endIt = formIDs.end(); it != endIt; ++it) {
std::map<std::string, int>::iterator mit = masters.find(it->plugin);
if (mit != masters.end())
fids.insert(it->objectIndex | (mit->second<<24));
else {
id++;
masters.insert(pair<std::string, int>(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<uint32_t>::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<FormID>::iterator it1 = formIDs.begin(), endIt1 = formIDs.end(); it1 != endIt1; ++it1) {
for (std::list<FormID>::iterator it2 = otherPlugin.formIDs.begin(), endIt2 = otherPlugin.formIDs.end(); it2 != endIt2; ++it2) {
if (*it1 == *it2)
count++;
}
}
}
}
+67
View File
@@ -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
<http://www.gnu.org/licenses/>.
*/
#ifndef __BOSS_PLUGINDATA_H__
#define __BOSS_PLUGINDATA_H__
#include <list>
#include <string>
#include <stdint.h>
//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<FormID> 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
+52
View File
@@ -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
<http://www.gnu.org/licenses/>.
*/
#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
+2 -7
View File
@@ -12,13 +12,8 @@ using namespace std;
void minimisePluginList(set<boss::Plugin, boss::plugin_comp>& plugins) {
set<boss::Plugin, boss::plugin_comp> out;
for (set<boss::Plugin, boss::plugin_comp>::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);