Implemented new very-lightweight plugin parser (in my libespm fork).

This commit is contained in:
WrinklyNinja
2013-05-06 13:32:33 +01:00
parent 3f8702be93
commit 24c289b989
17 changed files with 477 additions and 392 deletions
+2 -4
View File
@@ -11,12 +11,10 @@
cmake_minimum_required (VERSION 2.8.9)
project (boss)
set (BOSS_SRC "${CMAKE_SOURCE_DIR}/src/metadata.cpp" "${CMAKE_SOURCE_DIR}/src/game.cpp" "${CMAKE_SOURCE_DIR}/src/helpers.cpp" "${CMAKE_SOURCE_DIR}/src/VersionRegex.cpp")
set (BOSS_SRC ${BOSS_SRC} "${PROJECT_LIBS_DIR}/libespm/src/commonSupport.cpp" "${PROJECT_LIBS_DIR}/libespm/src/fileFormat.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 ("${PROJECT_LIBS_DIR}/alphanum" "${PROJECT_LIBS_DIR}/utf8" "${PROJECT_LIBS_DIR}/boost" "${PROJECT_LIBS_DIR}/yaml-cpp/include" "${CMAKE_SOURCE_DIR}/src" "${PROJECT_LIBS_DIR}/libloadorder/src" "${PROJECT_LIBS_DIR}/BOSS/trunk" "${PROJECT_LIBS_DIR}/libespm" "${PROJECT_LIBS_DIR}/zlib")
include_directories ("${PROJECT_LIBS_DIR}/alphanum" "${PROJECT_LIBS_DIR}/boost" "${PROJECT_LIBS_DIR}/yaml-cpp/include" "${CMAKE_SOURCE_DIR}/src" "${PROJECT_LIBS_DIR}/libloadorder/src" "${PROJECT_LIBS_DIR}/libespm" "${PROJECT_LIBS_DIR}/zlib")
##############################
+11 -40
View File
@@ -39,9 +39,9 @@ When run, BOSS shall output its results to a "log.yaml" file, which shall then b
BOSSv3 won't have a command line interface. Command line options may be added to the GUI if requested. Users need to be made aware that what BOSS sets isn't the absolutely perfect load order, and is open to tweaking, which is being lost on some people currently, in part due to the 'one click' nature of BOSS.
Roadmap
=======
Roadmap/To Do
=============
- Write new masterlist, userlist, settings file parsers. DONE
- Implement per-game handling. DONE
- Write API. DONE
@@ -50,7 +50,14 @@ Roadmap
- Write masterlist updating code.
- Develop graphical user interface.
- Error handling.
- Initialisation and finishing routines (read settings file, write log file, etc.).
- Initialisation and finishing routines (read settings file, write log file, etc.). DONE
- Remove game detection override.
- Checks for cyclic dependencies and incompatibilities.
- Setting load order.
- Include libespm initialisation into Game constructor.
- Optimisations to load ordering.
- Implement logging.
- Implement reading of plugin versions from their description field.
Sorting Algorithm
@@ -105,39 +112,3 @@ ZiggyX200, RiddlingLynx, AliTheLord, Tokc.D.K., Valda, Space Oden69, Televator,
Leandro Conde, Psymon, Loucifer, Torrello, Malonn, Skyline, Sharlikran, Red Eye,
iyumichan, Peste, Calen Ellefson, SilentSpike, Arkangel, zyp, v111, Chevenga,
rowynyew
Backwards Compatibility
=======================
BOSSv3 will not be backwards compatible with previous versions of BOSS: that is, it will not be able to use masterlists, userlists, translation files and ini files from previous versions of BOSS.
It may be possible to perform limited conversion from v2 masterlists and userlists to their v3 equivalents, but as their capabilities have substantially changed, much of them may be untransferrable or at least not automatically transferable.
A userlist converter would be most useful, since that would make most people's switch between v2 and v3 easier. The masterlists will probably have to be manually scanned for useful information.
Userlist Conversion
-------------------
Differences between BOSSv2 and v3:
- In v2, plugins can be sorted before or after other plugins, but v3 only supports sorting after other plugins.
- In v2, it is possible to replace or append to masterlist messages. In v3, the user can only append messages.
- In v2, it is possible to sort groups or relative to groups, but groups do not exist in v3.
- In v2, the order of rules affects their execution. In v3 the order of rules doesn't matter.
- In v2, requirements and incompatibilities are added in messages, but in v3 they are added in a separate data structure.
Conversion for syntax is just a matter of reading in using the old parser and writing out using the new generator. If any of the following are detected in a rule, conversion should be aborted and error messages detailing why displayed, asking the user to make the relevant changes before running the converter again.
- If the rule object is a group.
- If the sort keyword is 'BEFORE', 'TOP' or 'BOTTOM'.
- If the sort object is a group.
- If the message keyword is 'REPLACE'.
- If the message object's keyword is 'REQ' or 'INC'.
If any of the following are detected, then BOSS should display a warning that the result may differ from the original:
- If a message object's keyword is 'TAG'.
- If more than one rule references the same sort object or the same plugin appears as a sort object in one rule and a rule object in another.
Additionally, a warning should be displayed that all comments will be ignored.
+2 -2
View File
@@ -3,7 +3,7 @@
<title>BOSS Readme</title>
<style>
body {
font:12pt/1.5 Helvetica,sans-serif;
font:10pt/1.5 Helvetica,sans-serif;
text-rendering:optimizeLegibility;}
p, ul, li {margin:1.5em 0;}
h1,h3,h2 {font-weight:normal;}
@@ -175,7 +175,7 @@ Although BOSS can correctly position the vast majority of plugins, it is not a c
<li>Zanderat
</ul>
</ul>
<p>BOSS is written in C/C++ and makes use of the <a href="https://github.com/WrinklyNinja/libespm">Libespm</a>, <a href="https://github.com/WrinklyNinja/libloadorder">Libloadorder</a>, <a href="http://www.boost.org/">Boost</a>, <a href="http://curl.haxx.se/libcurl/">libcurl</a>, <a href="http://www.wxwidgets.org/">wxWidgets</a> and <a href="http://utfcpp.sourceforge.net/">UTF8-CPP</a> libraries. Without them, the majority of BOSS's features would probably never have been implemented. BOSS also uses an adaptation of Dirk Jagdmann's implementation of the Alphanum Algorithm developed by Dave Koelle for comparing version strings.
<p>BOSS is written in C/C++ and makes use of the <a href="http://www.davekoelle.com/alphanum.html">Alphanum</a>, <a href="http://www.boost.org/">Boost</a>, <a href="https://github.com/WrinklyNinja/libespm">libespm</a>, <a href="https://github.com/WrinklyNinja/libloadorder">libloadorder</a>, <a href="http://www.wxwidgets.org/">wxWidgets</a> and <a href="https://code.google.com/p/yaml-cpp/">yaml-cpp</a> libraries.
<h2 id="project">Project Members</h2>
-92
View File
@@ -1,92 +0,0 @@
/* 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: 1681 $, $Date: 2010-10-17 21:01:25 +0100 (Sun, 17 Oct 2010) $
*/
#include <string>
#include <boost/regex.hpp>
namespace boss {
using namespace std;
using namespace boost;
/// REGEX expression definition
/// Each expression is composed of three parts:
/// 1. The marker string "version", "ver", "rev", "v" or "r"
/// 2. The version string itself.
const char* regex1 =
"^(?:\\bversion\\b[ ]*(?:[:.\\-]?)|\\brevision\\b(?:[:.\\-]?))[ ]*"
"((?:alpha|beta|test|debug)?\\s*[-0-9a-zA-Z._+]+\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*))$"
;
const char* regex2 =
"(?:\\bversion\\b(?:[ :]?)|\\brevision\\b(?:[:.\\-]?))[ ]*"
"([0-9][-0-9a-zA-Z._]+\\+?)"
;
const char* regex3 =
"(?:\\bver(?:[:.]?)|\\brev(?:[:.]?))\\s*"
"([0-9][-0-9a-zA-Z._]*\\+?)"
;
// Matches "Updated: <date>" for the Bashed patch
const char* regex4 =
"(?:Updated:)\\s*"
"([-0-9aAmMpP/ :]+)$"
;
// Matches isolated versions as last resort
const char* regex5 =
"(?:(?:\\bv|\\br)(?:\\s?)(?:[-.:])?(?:\\s*))"
"((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*[0-9][-0-9a-zA-Z._]*\\+?)"
;
// Matches isolated versions as last resort
const char* regex6 =
"((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*\\b[0-9][-0-9a-zA-Z._]*\\+?)$"
;
const char* regex7 =
"(^\\bmark\\b\\s*\\b[IVX0-9][-0-9a-zA-Z._+]*\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*)?)$"
;
/// Array used to try each of the expressions defined above using
/// an iteration for each of them.
regex* version_checks[] = {
new regex(regex1, regex::icase),
new regex(regex2, regex::icase),
new regex(regex3, regex::icase),
new regex(regex4, regex::icase),
new regex(regex5, regex::icase), //This incorrectly identifies "OBSE v19" where 19 is any integer.
new regex(regex6, regex::icase), //This is responsible for metallicow's false positive.
new regex(regex7, regex::icase),
0
};
}
-45
View File
@@ -1,45 +0,0 @@
/* 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: 1681 $, $Date: 2010-10-17 21:01:25 +0100 (Sun, 17 Oct 2010) $
*/
#ifndef __SUPPORT_VERSIONREGEX__HPP__
#define __SUPPORT_VERSIONREGEX__HPP__
#include <boost/regex.hpp>
namespace boss {
using namespace boost;
/// Array used to try each of the expressions defined above using
/// an iteration for each of them.
extern regex* version_checks[];
}
#endif
+1
View File
@@ -39,6 +39,7 @@ namespace boss {
const unsigned int ERROR_INVALID_ARGS = 7;
const unsigned int ERROR_NO_TAG_MAP = 8;
const unsigned int ERROR_PATH_NOT_FOUND = 9;
const unsigned int ERROR_NO_GAME_DETECTED = 10;
class error : public std::exception {
public:
+134 -40
View File
@@ -25,8 +25,8 @@
#include "globals.h"
#include "helpers.h"
#include "error.h"
#include "metadata.h"
#include <libloadorder.h>
#include <stdexcept>
@@ -43,67 +43,95 @@ namespace fs = boost::filesystem;
namespace boss {
void DetectGames(std::vector<unsigned int>& detected, std::vector<unsigned int>& undetected) {
if (Game(GAME_TES4, false).IsInstalled()) //Look for Oblivion.
detected.push_back(GAME_TES4);
else
undetected.push_back(GAME_TES4);
if (Game(GAME_NEHRIM, false).IsInstalled()) //Look for Nehrim.
detected.push_back(GAME_NEHRIM);
else
undetected.push_back(GAME_NEHRIM);
if (Game(GAME_TES5, false).IsInstalled()) //Look for Skyrim.
detected.push_back(GAME_TES5);
else
undetected.push_back(GAME_TES5);
if (Game(GAME_FO3, false).IsInstalled()) //Look for Fallout 3.
detected.push_back(GAME_FO3);
else
undetected.push_back(GAME_FO3);
if (Game(GAME_FONV, false).IsInstalled()) //Look for Fallout New Vegas.
detected.push_back(GAME_FONV);
else
undetected.push_back(GAME_FONV);
}
Game::Game() {}
Game::Game(const unsigned int gameCode, const string path, const bool noPathInit)
Game::Game(const unsigned int gameCode, const bool pathInit)
: id(gameCode) {
string libespmGame;
if (Id() == GAME_TES4) {
name = "TES IV: Oblivion";
registryKey = "Software\\Bethesda Softworks\\Oblivion";
registrySubKey = "Installed Path";
bossFolderName = "Oblivion";
pluginsFolderName = "Data";
masterFile = "Oblivion.esm";
libespmGame = "Oblivion";
} else if (Id() == GAME_NEHRIM) {
name = "Nehrim - At Fate's Edge";
registryKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Nehrim - At Fate's Edge_is1";
registrySubKey = "InstallLocation";
bossFolderName = "Nehrim";
masterFile = "Nehrim.esm";
libespmGame = "Oblivion";
} else if (Id() == GAME_TES5) {
name = "TES V: Skyrim";
registryKey = "Software\\Bethesda Softworks\\Skyrim";
registrySubKey = "Installed Path";
bossFolderName = "Skyrim";
pluginsFolderName = "Data";
masterFile = "Skyrim.esm";
libespmGame = "Skyrim";
} else if (Id() == GAME_FO3) {
name = "Fallout 3";
registryKey = "Software\\Bethesda Softworks\\Fallout3";
registrySubKey = "Installed Path";
bossFolderName = "Fallout 3";
pluginsFolderName = "Data";
masterFile = "Fallout3.esm";
libespmGame = "";
} else if (Id() == GAME_FONV) {
name = "Fallout: New Vegas";
registryKey = "Software\\Bethesda Softworks\\FalloutNV";
registrySubKey = "Installed Path";
bossFolderName = "Fallout New Vegas";
pluginsFolderName = "Data";
masterFile = "FalloutNV.esm";
libespmGame = "";
} else
throw error(ERROR_INVALID_ARGS, "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 error(ERROR_PATH_NOT_FOUND, "Game path could not be detected.");
} else
gamePath = fs::path(path);
if (pathInit) {
//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 error(ERROR_PATH_NOT_FOUND, "Game path could not be detected.");
RefreshActivePluginsList();
CreateBOSSGameFolder();
}
espm_settings = espm::Settings(libespm_options_path, libespmGame);
}
bool Game::IsInstalled() const {
return (IsInstalledLocally() || RegKeyExists("HKEY_LOCAL_MACHINE", registryKey, registrySubKey));
}
bool Game::IsInstalledLocally() const {
return fs::exists(fs::path("..") / pluginsFolderName);
return fs::exists(fs::path("..") / "Data" / masterFile);
}
unsigned int Game::Id() const {
@@ -119,40 +147,106 @@ namespace boss {
}
fs::path Game::DataPath() const {
return GamePath() / pluginsFolderName;
return GamePath() / "Data";
}
fs::path Game::MasterlistPath() const {
return fs::path(bossFolderName) / "masterlist.yaml";
}
fs::path Game::UserlistPath() const {
return fs::path(bossFolderName) / "userlist.yaml";
}
fs::path Game::ResultsPath() const {
return fs::path(bossFolderName) / "results.yaml";
}
void Game::RefreshActivePluginsList() {
lo_game_handle gh;
int ret;
char ** pluginArr;
size_t pluginArrSize;
if (id == GAME_TES4)
int ret;
if (Id() == GAME_TES4)
ret = lo_create_handle(&gh, LIBLO_GAME_TES4, gamePath.string().c_str());
else if (id == GAME_TES5)
else if (Id() == GAME_NEHRIM) {
ret = lo_create_handle(&gh, LIBLO_GAME_TES4, gamePath.string().c_str());
if (ret == LIBLO_OK)
ret = lo_set_game_master(gh, masterFile.c_str());
} else if (Id() == GAME_TES5)
ret = lo_create_handle(&gh, LIBLO_GAME_TES5, gamePath.string().c_str());
else if (id == GAME_FO3)
else if (Id() == GAME_FO3)
ret = lo_create_handle(&gh, LIBLO_GAME_FO3, gamePath.string().c_str());
else if (id == GAME_FONV)
else if (Id() == GAME_FONV)
ret = lo_create_handle(&gh, LIBLO_GAME_FNV, gamePath.string().c_str());
if (ret != LIBLO_OK)
throw error(ERROR_LIBLO_ERROR, "libloadorder game handle creation failed.");
if (lo_get_active_plugins(gh, &pluginArr, &pluginArrSize) != LIBLO_OK)
throw error(ERROR_LIBLO_ERROR, "Active plugin list lookup failed.");
else {
if (lo_get_active_plugins(gh, &pluginArr, &pluginArrSize) != LIBLO_OK)
throw error(ERROR_LIBLO_ERROR, "Active plugin list lookup failed.");
else {
for (size_t i=0; i < pluginArrSize; ++i) {
activePlugins.insert(string(pluginArr[i]));
}
for (size_t i=0; i < pluginArrSize; ++i) {
activePlugins.insert(string(pluginArr[i]));
}
}
lo_destroy_handle(gh);
}
bool Game::IsActive(const std::string& plugin) const {
return activePlugins.find(boost::to_lower_copy(plugin)) != activePlugins.end();
}
void Game::SetLoadOrder(const std::list<Plugin>& loadOrder) const {
lo_game_handle gh;
char ** pluginArr;
size_t pluginArrSize;
pluginArrSize = loadOrder.size();
pluginArr = new char*[pluginArrSize];
int i = 0;
for (list<Plugin>::const_iterator it=loadOrder.begin(),endIt=loadOrder.end(); it != endIt; ++it) {
pluginArr[i] = new char[it->Name().length() + 1];
strcpy(pluginArr[i], it->Name().c_str());
++i;
}
int ret;
if (Id() == GAME_TES4)
ret = lo_create_handle(&gh, LIBLO_GAME_TES4, gamePath.string().c_str());
else if (Id() == GAME_NEHRIM) {
ret = lo_create_handle(&gh, LIBLO_GAME_TES4, gamePath.string().c_str());
if (ret == LIBLO_OK)
ret = lo_set_game_master(gh, masterFile.c_str());
} else if (Id() == GAME_TES5)
ret = lo_create_handle(&gh, LIBLO_GAME_TES5, gamePath.string().c_str());
else if (Id() == GAME_FO3)
ret = lo_create_handle(&gh, LIBLO_GAME_FO3, gamePath.string().c_str());
else if (Id() == GAME_FONV)
ret = lo_create_handle(&gh, LIBLO_GAME_FNV, gamePath.string().c_str());
if (ret != LIBLO_OK) {
for (size_t i=0; i < pluginArrSize; i++)
delete [] pluginArr[i];
delete [] pluginArr;
throw error(ERROR_LIBLO_ERROR, "libloadorder game handle creation failed.");
}
if (lo_set_load_order(gh, pluginArr, pluginArrSize) != LIBLO_OK) {
for (size_t i=0; i < pluginArrSize; i++)
delete [] pluginArr[i];
delete [] pluginArr;
throw error(ERROR_LIBLO_ERROR, "Setting load order failed.");
}
for (size_t i=0; i < pluginArrSize; i++)
delete [] pluginArr[i];
delete [] pluginArr;
lo_destroy_handle(gh);
}
void Game::CreateBOSSGameFolder() {
//Make sure that the BOSS game path exists.
try {
+18 -2
View File
@@ -24,19 +24,28 @@
#ifndef __BOSS_GAME__
#define __BOSS_GAME__
#include <string>
#include <vector>
#include <stdint.h>
#include <boost/filesystem.hpp>
#include <boost/unordered_map.hpp>
#include <boost/unordered_set.hpp>
#include <libloadorder.h>
#include <src/playground.h>
namespace boss {
class Plugin;
void DetectGames(std::vector<unsigned int>& detected, std::vector<unsigned int>& undetected);
class Game {
public:
Game(); //Sets game to BOSS_GAME_AUTODETECT, with all other vars being empty.
Game(const unsigned int 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.
Game(const unsigned int gameCode, const bool pathInit = true); //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;
@@ -46,6 +55,9 @@ namespace boss {
boost::filesystem::path GamePath() const;
boost::filesystem::path DataPath() const;
boost::filesystem::path MasterlistPath() const;
boost::filesystem::path UserlistPath() const;
boost::filesystem::path ResultsPath() const;
//Creates directory in BOSS folder for BOSS's game-specific files.
void CreateBOSSGameFolder();
@@ -53,9 +65,13 @@ namespace boss {
void RefreshActivePluginsList();
bool IsActive(const std::string& plugin) const;
void SetLoadOrder(const std::list<Plugin>& loadOrder) const;
//Caches for condition results, active plugins and CRCs.
boost::unordered_map<std::string, bool> conditionCache; //Holds lowercased strings.
boost::unordered_map<std::string, uint32_t> crcCache; //Holds lowercased strings.
espm::Settings espm_settings;
private:
unsigned id;
std::string name;
@@ -64,7 +80,7 @@ namespace boss {
std::string registrySubKey;
std::string bossFolderName;
std::string pluginsFolderName;
std::string masterFile;
boost::filesystem::path gamePath; //Path to the game's folder.
boost::unordered_set<std::string> activePlugins; //Holds lowercased strings.
+15
View File
@@ -23,8 +23,11 @@
#ifndef __BOSS_GLOBALS__
#define __BOSS_GLOBALS__
#include <boost/filesystem.hpp>
namespace boss {
//Game values.
const unsigned int GAME_AUTODETECT = 0;
const unsigned int GAME_TES4 = 1;
const unsigned int GAME_TES5 = 2;
@@ -32,10 +35,22 @@ namespace boss {
const unsigned int GAME_FONV = 4;
const unsigned int GAME_NEHRIM = 5;
//Version numbers.
const unsigned int VERSION_MAJOR = 3;
const unsigned int VERSION_MINOR = 0;
const unsigned int VERSION_PATCH = 0;
//Common paths.
const boost::filesystem::path settings_path = "settings.yaml";
const boost::filesystem::path log_path = "BOSSDebugLog.txt";
const boost::filesystem::path readme_path = boost::filesystem::path("Docs") / "BOSS Readme.html";
const boost::filesystem::path syntax_doc_path = boost::filesystem::path("Docs") / "BOSS Metadata File Syntax.html";
const boost::filesystem::path api_doc_path = boost::filesystem::path("Docs") / "BOSS API Readme.html";
const boost::filesystem::path version_history_path = boost::filesystem::path("Docs") / "BOSS Version History.html";
const boost::filesystem::path licenses_path = boost::filesystem::path("Docs") / "Licenses.txt";
// Temporary hack?
const char * const libespm_options_path = "libespm.yaml";
}
#endif
-9
View File
@@ -62,13 +62,4 @@ wxString FromUTF8(const std::string& str);
wxString FromUTF8(const boost::format& f);
// Temporary hacks.
const char * const libespm_options_path = "libespm.cfg";
const char * const libespm_game = "Skyrim";
const char * const masterlist_path = "masterlist.yaml";
const char * const userlist_path = "userlist.yaml";
const char * const results_path = "results.yaml";
const char * const settings_path = "settings.yaml";
#endif
+140 -56
View File
@@ -31,11 +31,13 @@
#include <algorithm>
#include <iterator>
#include <ctime>
#include <clocale>
#include <src/commonSupport.h>
#include <src/fileFormat.h>
#include <libloadorder.h>
#include <src/playground.h>
#include <boost/filesystem.hpp>
#include <boost/filesystem/detail/utf8_codecvt_facet.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/locale.hpp>
@@ -70,18 +72,13 @@ IMPLEMENT_APP(BossGUI);
using namespace boss;
using namespace std;
using boost::format;
namespace fs = boost::filesystem;
namespace loc = boost::locale;
// Temporary hacks.
const fs::path readme_path = fs::path("Docs") / "BOSS Readme.html";
const fs::path syntax_doc_path = fs::path("Docs") / "BOSS Metadata File Syntax.html";
const fs::path api_doc_path = fs::path("Docs") / "BOSS API Readme.html";
const fs::path version_history_path = fs::path("Docs") / "BOSS Version History.html";
const fs::path licenses_path = fs::path("Docs") / "Licenses.txt";
bool BossGUI::OnInit() {
//Check if GUI is already running.
wxSingleInstanceChecker *checker = new wxSingleInstanceChecker;
@@ -96,15 +93,109 @@ bool BossGUI::OnInit() {
//Load settings.
YAML::Node settings;
if (fs::exists(settings_path)) {
settings = YAML::LoadFile(settings_path);
try {
settings = YAML::LoadFile(settings_path.string());
} catch (YAML::ParserException& e) {
//LOG_ERROR("Error: %s", e.getString().c_str());
wxMessageBox(
FromUTF8(format(loc::translate("Error: Settings parsing failed. %1%")) % e.what()),
translate("BOSS: Error"),
wxOK | wxICON_ERROR,
NULL);
}
}
//Apply logging and language settings (skipping for tester though).
//Skip logging initialisation for tester.
/* if (gl_log_debug_output)
g_logger.setStream(debug_log_path.string().c_str());
g_logger.setOriginTracking(gl_debug_with_source);
// it's ok if this number is too high. setVerbosity will handle it
g_logger.setVerbosity(static_cast<LogVerbosity>(LV_WARN + gl_debug_verbosity));
*/
//Specify location of language dictionaries
boost::locale::generator gen;
gen.add_messages_path(fs::path("l10n").string());
gen.add_messages_domain("messages");
//Also skipping game detection.
Game game(GAME_TES5);
vector<unsigned int> detected;
detected.push_back(GAME_TES5);
//Set the locale to get encoding and language conversions working correctly.
if (settings["Language"]) {
string localeId = "";
wxLanguage lang;
if (settings["Language"].as<string>() == "eng") {
localeId = "en.UTF-8";
lang = wxLANGUAGE_ENGLISH;
}
try {
locale::global(gen(localeId));
cout.imbue(locale());
//Need to also set up wxWidgets locale so that its default interface text comes out in the right language.
wxLoc = new wxLocale();
if (!wxLoc->Init(lang, wxLOCALE_LOAD_DEFAULT))
throw runtime_error("System GUI text could not be set.");
wxLocale::AddCatalogLookupPathPrefix(".\\l10n");
wxLoc->AddCatalog("wxstd");
} catch(runtime_error &e) {
// LOG_ERROR("could not implement translation: %s", e.what());
wxMessageBox(
FromUTF8(format(loc::translate("Error: could not apply translation: %1%")) % e.what()),
translate("BOSS: Error"),
wxOK | wxICON_ERROR,
NULL);
}
locale global_loc = locale();
locale loc(global_loc, new boost::filesystem::detail::utf8_codecvt_facet());
boost::filesystem::path::imbue(loc);
}
//Detect installed games.
vector<unsigned int> detected, undetected;
DetectGames(detected, undetected);
if (detected.empty())
throw error(ERROR_NO_GAME_DETECTED, "None of the supported games were detected.");
string target;
unsigned int targetGame;
if (settings["Game"] && settings["Game"].as<string>() != "auto")
target = settings["Game"].as<string>();
else if (settings["Last Game"] && settings["Last Game"].as<string>() != "auto")
target = settings["Last Game"].as<string>();
if (!target.empty()) {
for (size_t i=0, max=detected.size(); i < max; ++i) {
if (target == "oblivion" && detected[i] == GAME_TES4) {
targetGame = GAME_TES4;
break;
} else if (target == "nehrim" && detected[i] == GAME_NEHRIM) {
targetGame = GAME_NEHRIM;
break;
} else if (target == "skyrim" && detected[i] == GAME_TES5) {
targetGame = GAME_TES5;
break;
} else if (target == "fallout3" && detected[i] == GAME_FO3) {
targetGame = GAME_FO3;
break;
} else if (target == "falloutnv" && detected[i] == GAME_FONV) {
targetGame = GAME_FONV;
break;
}
}
} else {
if (Game(GAME_NEHRIM, false).IsInstalledLocally()) //Before Oblivion because Nehrim installs can have Oblivion.esm for porting mods.
targetGame = GAME_NEHRIM;
else if (Game(GAME_TES4, false).IsInstalledLocally())
targetGame = GAME_TES4;
else if (Game(GAME_TES5, false).IsInstalledLocally())
targetGame = GAME_TES5;
else if (Game(GAME_FONV, false).IsInstalledLocally()) //Before Fallout 3 because some mods for New Vegas require Fallout3.esm.
targetGame = GAME_FONV;
else if (Game(GAME_FO3, false).IsInstalledLocally())
targetGame = GAME_FO3;
else
targetGame = detected[0];
}
targetGame = GAME_TES5;
Game game(targetGame);
//Create launcher window.
Launcher * launcher = new Launcher(wxT("BOSS"), settings, game, detected);
@@ -192,30 +283,10 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
time_t t0 = time(NULL);
/* Stuff still missing from a normal execution of BOSS:
- Reading of settings file.
- Detecting game to run for.
- Error handling.
- Masterlist updating.
- Checks for metadata self-consistency.
- Checks for cyclic dependencies and incompatibilities.
- Setting of load order.
Need to include libespm setup into game setup, once the former is made to be not global.
*/
ofstream out("out.txt");
out << "Setting up libespm and BOSS..." << endl;
// Set up libesm.
common::options::setGame(libespm_game);
ifstream input(libespm_options_path);
common::readOptions(input);
input.close();
//Set up BOSS vars.
boss::Game game(boss::GAME_TES5);
out << "Reading plugins in Data folder..." << endl;
@@ -228,11 +299,6 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
if (fs::is_regular_file(it->status()) && (it->path().extension().string() == ".esp" || it->path().extension().string() == ".esm")) {
string filename = it->path().filename().string();
if (filename == "Skyrim.esm"
|| filename == "Tamriel Compendium.esp"
|| filename == "Tamriel Compendium - Skill Books.esp")
continue; // Libespm crashes with these plugins.
out << "Reading plugin: " << filename << endl;
boss::Plugin plugin(game, filename);
plugins.push_back(plugin);
@@ -249,10 +315,19 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
list<boss::Message> messages, mlist_messages, ulist_messages;
list<boss::Plugin> mlist_plugins, ulist_plugins;
if (fs::exists(masterlist_path)) {
if (fs::exists(game.MasterlistPath())) {
out << "Parsing masterlist..." << endl;
mlist = YAML::LoadFile(masterlist_path);
try {
mlist = YAML::LoadFile(game.MasterlistPath().string());
} catch (YAML::ParserException& e) {
//LOG_ERROR("Error: %s", e.getString().c_str());
wxMessageBox(
FromUTF8(format(loc::translate("Error: Masterlist parsing failed. %1%")) % e.what()),
translate("BOSS: Error"),
wxOK | wxICON_ERROR,
NULL);
}
if (mlist["globals"])
mlist_messages = mlist["globals"].as< list<boss::Message> >();
if (mlist["plugins"])
@@ -263,10 +338,19 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
start = time(NULL);
}
if (fs::exists(userlist_path)) {
if (fs::exists(game.UserlistPath())) {
out << "Parsing userlist..." << endl;
ulist = YAML::LoadFile(userlist_path);
try {
ulist = YAML::LoadFile(game.UserlistPath().string());
} catch (YAML::ParserException& e) {
//LOG_ERROR("Error: %s", e.getString().c_str());
wxMessageBox(
FromUTF8(format(loc::translate("Error: Userlist parsing failed. %1%")) % e.what()),
translate("BOSS: Error"),
wxOK | wxICON_ERROR,
NULL);
}
if (ulist["globals"])
ulist_messages = ulist["globals"].as< list<boss::Message> >();
if (ulist["plugins"])
@@ -279,7 +363,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
progDia->Pulse();
if (fs::exists(masterlist_path) || fs::exists(userlist_path)) {
if (fs::exists(game.MasterlistPath()) || fs::exists(game.UserlistPath())) {
out << "Merging plugin lists..." << endl;
//Merge all global message lists.
@@ -291,17 +375,13 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
//Check if there is already a plugin in the 'plugins' list or not.
list<boss::Plugin>::iterator pos = std::find(mlist_plugins.begin(), mlist_plugins.end(), *it);
if (pos != mlist_plugins.end()) {
//Need to merge plugins.
if (pos != mlist_plugins.end())
it->Merge(*pos);
}
pos = std::find(ulist_plugins.begin(), ulist_plugins.end(), *it);
if (pos != ulist_plugins.end()) {
//Need to merge plugins.
if (pos != ulist_plugins.end())
it->Merge(*pos);
}
}
end = time(NULL);
@@ -317,7 +397,12 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
try {
it->EvalAllConditions(game);
} catch (boss::error& e) {
out << e.what() << endl;
//LOG_ERROR("Error: %s", e.what());
wxMessageBox(
FromUTF8(format(loc::translate("Error: Condition evaluation failed. %1%")) % e.what()),
translate("BOSS: Error"),
wxOK | wxICON_ERROR,
NULL);
}
}
@@ -343,7 +428,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
progDia->Pulse();
out << "Printing plugin details..." << endl;
/* out << "Printing plugin details..." << endl;
for (list<boss::Plugin>::iterator it=plugins.begin(), endIt = plugins.end(); it != endIt; ++it) {
out << it->Name() << endl
@@ -370,7 +455,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
end = time(NULL);
out << "Time taken to print plugins' details: " << (end - start) << " seconds." << endl;
start = time(NULL);
*/
out << "Sorting plugins..." << endl;
plugins.sort();
@@ -392,7 +477,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
<< YAML::Key << "plugins" << YAML::Value << plugins
<< YAML::EndMap;
ofstream results_out(results_path);
ofstream results_out(game.ResultsPath().string().c_str());
results_out << yout.c_str();
results_out.close();
@@ -446,7 +531,6 @@ void Launcher::OnGameChange(wxCommandEvent& event) {
} catch (error& e) {
wxMessageBox(e.what(), translate("BOSS: Error"), wxOK | wxICON_ERROR, this);
}
_game.CreateBOSSGameFolder();
SetTitle(wxT("BOSS - " + _game.Name())); //Don't need to convert name, known to be only ASCII chars.
}
+2
View File
@@ -34,6 +34,8 @@
class BossGUI : public wxApp {
public:
bool OnInit(); //Load settings, apply logging and language settings, check if BOSS is already running, detect games, set game to last game or to first detected game if auto, create launcher window.
private:
wxLocale * wxLoc;
};
class Launcher : public wxFrame {
+2 -1
View File
@@ -22,6 +22,7 @@
*/
#include "settings.h"
#include "../globals.h"
#include <fstream>
BEGIN_EVENT_TABLE ( SettingsFrame, wxFrame )
@@ -230,7 +231,7 @@ void SettingsFrame::OnQuit(wxCommandEvent& event) {
yout.SetIndent(2);
yout << _settings;
ofstream out(settings_path);
ofstream out(boss::settings_path.string().c_str());
out << yout.c_str();
out.close();
}
+98 -78
View File
@@ -26,7 +26,6 @@
*/
#include "helpers.h"
#include "VersionRegex.h"
#include "error.h"
#include <boost/spirit/include/karma.hpp>
@@ -35,7 +34,6 @@
#include <boost/regex.hpp>
#include <alphanum.hpp>
#include "source/utf8.h"
#include <cstring>
#include <iostream>
@@ -46,10 +44,6 @@
#include <sys/types.h>
#include <sstream>
// Libespm files.
#include <src/commonSupport.h>
#include <src/fileFormat.h>
#if _WIN32 || _WIN64
# ifndef UNICODE
# define UNICODE
@@ -67,6 +61,65 @@ namespace boss {
using boost::algorithm::replace_first;
namespace karma = boost::spirit::karma;
namespace fs = boost::filesystem;
/// REGEX expression definition
/// Each expression is composed of three parts:
/// 1. The marker string "version", "ver", "rev", "v" or "r"
/// 2. The version string itself.
const char* regex1 =
"^(?:\\bversion\\b[ ]*(?:[:.\\-]?)|\\brevision\\b(?:[:.\\-]?))[ ]*"
"((?:alpha|beta|test|debug)?\\s*[-0-9a-zA-Z._+]+\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*))$"
;
const char* regex2 =
"(?:\\bversion\\b(?:[ :]?)|\\brevision\\b(?:[:.\\-]?))[ ]*"
"([0-9][-0-9a-zA-Z._]+\\+?)"
;
const char* regex3 =
"(?:\\bver(?:[:.]?)|\\brev(?:[:.]?))\\s*"
"([0-9][-0-9a-zA-Z._]*\\+?)"
;
// Matches "Updated: <date>" for the Bashed patch
const char* regex4 =
"(?:Updated:)\\s*"
"([-0-9aAmMpP/ :]+)$"
;
// Matches isolated versions as last resort
const char* regex5 =
"(?:(?:\\bv|\\br)(?:\\s?)(?:[-.:])?(?:\\s*))"
"((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*[0-9][-0-9a-zA-Z._]*\\+?)"
;
// Matches isolated versions as last resort
const char* regex6 =
"((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*\\b[0-9][-0-9a-zA-Z._]*\\+?)$"
;
const char* regex7 =
"(^\\bmark\\b\\s*\\b[IVX0-9][-0-9a-zA-Z._+]*\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*)?)$"
;
/// Array used to try each of the expressions defined above using
/// an iteration for each of them.
boost::regex* version_checks[] = {
new boost::regex(regex1, boost::regex::icase),
new boost::regex(regex2, boost::regex::icase),
new boost::regex(regex3, boost::regex::icase),
new boost::regex(regex4, boost::regex::icase),
new boost::regex(regex5, boost::regex::icase), //This incorrectly identifies "OBSE v19" where 19 is any integer.
new boost::regex(regex6, boost::regex::icase), //This is responsible for metallicow's false positive.
new boost::regex(regex7, boost::regex::icase),
0
};
//////////////////////////////////////////////////////////////////////////
// Helper functions
//////////////////////////////////////////////////////////////////////////
//Calculate the CRC of the given file for comparison purposes.
uint32_t GetCrc32(const fs::path& filename) {
@@ -160,88 +213,55 @@ namespace boss {
Version::Version() {}
Version::Version(const char * ver)
Version::Version(const std::string& ver)
: verString(ver) {}
Version::Version(const std::string ver)
: verString(ver) {}
Version::Version(const fs::path file) {
if (file.extension().string() == ".esm" || file.extension().string() == ".esp") {
//Use libespm interface. Assumes that it has previously been initialised.
ifstream input(file.string().c_str(), ios::binary);
espm::file FileStruct;
espm::readHeaderThing(input, FileStruct);
input.close();
for(size_t i=0,max=FileStruct.fields.size(); i < max; ++i){
if (FileStruct.fields[i].name == "SNAM") {
string text = FileStruct.fields[i].data;
string::const_iterator begin, end;
begin = text.begin();
end = text.end();
for(int i = 0; regex* re = version_checks[i]; i++) {
smatch what;
while (regex_search(begin, end, what, *re)) {
if (what.empty())
continue;
ssub_match match = what[1];
if (!match.matched)
continue;
verString = trim_copy(string(match.first, match.second));
}
}
break;
}
}
} else {
Version::Version(const fs::path& file) {
#if _WIN32 || _WIN64
DWORD dummy = 0;
DWORD size = GetFileVersionInfoSize(file.wstring().c_str(), &dummy);
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;
if (size > 0) {
LPBYTE point = new BYTE[size];
UINT uLen;
VS_FIXEDFILEINFO *info;
string ver;
GetFileVersionInfo(file.wstring().c_str(),0,size,point);
GetFileVersionInfo(file.wstring().c_str(),0,size,point);
VerQueryValue(point,L"\\",(LPVOID *)&info,&uLen);
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);
DWORD dwLeftMost = HIWORD(info->dwFileVersionMS);
DWORD dwSecondLeft = LOWORD(info->dwFileVersionMS);
DWORD dwSecondRight = HIWORD(info->dwFileVersionLS);
DWORD dwRightMost = LOWORD(info->dwFileVersionLS);
delete [] point;
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('"')) {
// 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)) {
verString = string(buf);
}
pclose(fp);
}
#endif
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('"')) {
// 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)) {
verString = string(buf);
}
pclose(fp);
}
#endif
}
Version::Version(const Plugin& plugin) {
verString = plugin.Version();
}
string Version::AsString() const {
+10 -3
View File
@@ -28,12 +28,19 @@
#ifndef __BOSS_HELPERS__
#define __BOSS_HELPERS__
#include "metadata.h"
#include <stdint.h>
#include <string>
#include <boost/filesystem.hpp>
#include <boost/regex.hpp>
namespace boss {
/// Array used to try each of the expressions defined using
/// an iteration for each of them.
extern boost::regex* version_checks[];
//////////////////////////////////////////////////////////////////////////
// Helper functions
//////////////////////////////////////////////////////////////////////////
@@ -62,9 +69,9 @@ namespace boss {
std::string verString;
public:
Version();
Version(const char * ver);
Version(const std::string ver);
Version(const boost::filesystem::path file);
Version(const std::string& ver);
Version(const boost::filesystem::path& file);
Version(const Plugin& plugin);
std::string AsString() const;
+40 -20
View File
@@ -21,11 +21,13 @@
<http://www.gnu.org/licenses/>.
*/
#include "metadata.h"
#include "helpers.h"
#include "metadata.h"
#include <src/playground.h>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <src/fileFormat.h>
#include <iostream>
@@ -195,34 +197,48 @@ namespace boss {
// Get data from file contents using libespm. Assumes libespm has already been initialised.
boost::filesystem::path filepath = game.DataPath() / n;
ifstream input(filepath.string().c_str(), ios::binary);
espm::file File;
espm::readFile(input, File);
input.exceptions(std::ifstream::failbit | std::ifstream::badbit);
espm::File file;
file.Read(input, game.espm_settings);
input.close();
isMaster = espm::isMaster(File);
vector<char*> rawMasters = espm::getMasters(File);
for (size_t i=0,max=rawMasters.size(); i < max; ++i) {
masters.push_back(rawMasters[i]);
}
isMaster = file.IsMaster(game.espm_settings);
masters = file.GetMasters();
vector<espm::item> records = espm::getRecords(File);
vector<uint32_t> records = file.GetFormIDs();
vector<string> plugins = masters;
plugins.push_back(name);
for (vector<espm::item>::const_iterator it = records.begin(),endIt = records.end(); it != endIt; ++it){
uint32_t id = *reinterpret_cast<uint32_t*>(it->record.recID);
formIDs.insert(FormID(plugins, id));
}
for (vector<uint32_t>::const_iterator it = records.begin(),endIt = records.end(); it != endIt; ++it)
formIDs.insert(FormID(plugins, *it));
//Also read Bash Tags applied in description.
for(size_t i=0,max=File.fields.size(); i < max; ++i){
if (File.fields[i].name == "SNAM") {
string text = File.fields[i].data;
//Also read Bash Tags applied and version string in description.
for(size_t i=0,max=file.fields.size(); i < max; ++i){
if (strncmp(file.fields[i].type,"SNAM", 4) == 0) {
string text = file.fields[i].data;
string::const_iterator begin, end;
begin = text.begin();
end = text.end();
/* for(int j = 0; boost::regex* re = version_checks[j]; j++) {
boost::smatch what;
while (boost::regex_search(begin, end, what, *re)) {
if (what.empty())
continue;
boost::ssub_match match = what[1];
if (!match.matched)
continue;
version = boost::trim_copy(string(match.first, match.second));
}
cout << "Checking regex: " << j << endl;
}*/
size_t pos1 = text.find("{{BASH:");
if (pos1 == string::npos)
break;
continue;
size_t pos2 = text.find("}}", pos1);
if (pos2 == string::npos)
@@ -459,6 +475,10 @@ namespace boss {
return isMaster;
}
std::string Plugin::Version() const {
return version;
}
bool Plugin::MustLoadAfter(const Plugin& plugin) const {
for (vector<string>::const_iterator it=masters.begin(), endIt=masters.end(); it != endIt; ++it) {
if (boost::iequals(*it, plugin.Name()))
+2
View File
@@ -139,6 +139,7 @@ namespace boss {
std::set<FormID> FormIDs() const;
std::vector<std::string> Masters() const;
bool IsMaster() const; //Checks master bit flag.
std::string Version() const;
void Name(const std::string& name);
void Enabled(const bool enabled);
@@ -181,6 +182,7 @@ namespace boss {
std::vector<std::string> masters;
std::set<FormID> formIDs;
std::string version; //Obtained from description field.
bool isMaster;
};
}