Refactored C++ classes into separate files.

The YAML converter/emitter code for the metadata and Game
classes has been moved into the class files, and the
set/unordered_set converter/emitter templates have been
moved into yaml_set_helpers.h. For #449.

Also renamed ConditionStruct to ConditionalMetadata, and
condition_grammar to ConditionGrammar, the former to make
its purpose clearer, and the latter to conform to the
class naming style.
This commit is contained in:
Oliver Hamlet
2015-06-10 15:12:21 +01:00
parent 0a7ff31900
commit 5e801abe8a
48 changed files with 3437 additions and 2436 deletions
+34 -7
View File
@@ -77,28 +77,53 @@ find_package(Boost REQUIRED COMPONENTS log log_setup regex locale thread date_ti
find_package(yaml-cpp)
find_package(GTest)
set (LOOT_SRC "${CMAKE_SOURCE_DIR}/src/backend/metadata.cpp"
set (LOOT_SRC "${CMAKE_SOURCE_DIR}/src/backend/metadata/conditional_metadata.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/file.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/formid.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/location.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/message.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/message_content.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/plugin_dirty_info.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/tag.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/game.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/metadata_list.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/masterlist.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/plugin.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/helpers.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/generators.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/language.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/version.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/graph.cpp"
"${CMAKE_SOURCE_DIR}/src/backend/git.cpp"
"${CMAKE_BINARY_DIR}/generated/globals.cpp")
set (LOOT_HEADERS "${CMAKE_SOURCE_DIR}/src/backend/metadata.h"
set (LOOT_HEADERS "${CMAKE_SOURCE_DIR}/src/backend/metadata/condition_grammar.h"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/conditional_metadata.h"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/file.h"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/formid.h"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/location.h"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/message.h"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/message_content.h"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/plugin_dirty_info.h"
"${CMAKE_SOURCE_DIR}/src/backend/metadata/tag.h"
"${CMAKE_SOURCE_DIR}/src/backend/game.h"
"${CMAKE_SOURCE_DIR}/src/backend/metadata_list.h"
"${CMAKE_SOURCE_DIR}/src/backend/masterlist.h"
"${CMAKE_SOURCE_DIR}/src/backend/plugin.h"
"${CMAKE_SOURCE_DIR}/src/backend/helpers.h"
"${CMAKE_SOURCE_DIR}/src/backend/language.h"
"${CMAKE_SOURCE_DIR}/src/backend/version.h"
"${CMAKE_SOURCE_DIR}/src/backend/globals.h"
"${CMAKE_SOURCE_DIR}/src/backend/generators.h"
"${CMAKE_SOURCE_DIR}/src/backend/yaml_set_helpers.h"
"${CMAKE_SOURCE_DIR}/src/backend/graph.h"
"${CMAKE_SOURCE_DIR}/src/backend/error.h"
"${CMAKE_SOURCE_DIR}/src/backend/parsers.h"
"${CMAKE_SOURCE_DIR}/src/backend/streams.h")
set (LOOT_GUI_SRC ${LOOT_SRC}
"${CMAKE_SOURCE_DIR}/src/gui/main_win.cpp"
"${CMAKE_SOURCE_DIR}/src/gui/handler.cpp"
"${CMAKE_SOURCE_DIR}/src/gui/app.cpp"
"${CMAKE_SOURCE_DIR}/src/gui/loot_handler.cpp"
"${CMAKE_SOURCE_DIR}/src/gui/loot_app.cpp"
"${CMAKE_SOURCE_DIR}/src/gui/loot_state.cpp"
"${CMAKE_SOURCE_DIR}/src/gui/scheme.cpp"
"${CMAKE_SOURCE_DIR}/src/resource.rc")
@@ -107,7 +132,9 @@ set (LOOT_GUI_HEADERS ${LOOT_HEADERS}
"${CMAKE_SOURCE_DIR}/src/backend/json.h"
# Actual GUI code.
"${CMAKE_SOURCE_DIR}/src/gui/handler.h"
"${CMAKE_SOURCE_DIR}/src/gui/app.h"
"${CMAKE_SOURCE_DIR}/src/gui/loot_handler.h"
"${CMAKE_SOURCE_DIR}/src/gui/loot_app.h"
"${CMAKE_SOURCE_DIR}/src/gui/loot_state.h"
"${CMAKE_SOURCE_DIR}/src/gui/scheme.h"
"${CMAKE_SOURCE_DIR}/src/gui/resource.h")
+3 -3
View File
@@ -24,9 +24,8 @@
#include "api.h"
#include "../backend/game.h"
#include "../backend/metadata.h"
#include "../backend/parsers.h"
#include "../backend/generators.h"
#include "../backend/globals.h"
#include "../backend/plugin.h"
#include "../backend/error.h"
#include "../backend/streams.h"
@@ -41,6 +40,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/core.hpp>
const unsigned int loot_ok = loot::error::ok;
const unsigned int loot_error_liblo_error = loot::error::liblo_error;
+18 -190
View File
@@ -26,10 +26,8 @@
#include "globals.h"
#include "helpers.h"
#include "error.h"
#include "metadata.h"
#include "parsers.h"
#include "plugin.h"
#include "streams.h"
#include "generators.h"
#include "graph.h"
#include <boost/algorithm/string.hpp>
@@ -67,193 +65,6 @@ namespace loot {
return games;
}
// MetadataList member functions
//------------------------------
void MetadataList::Load(const boost::filesystem::path& filepath) {
plugins.clear();
messages.clear();
BOOST_LOG_TRIVIAL(debug) << "Loading file: " << filepath;
loot::ifstream in(filepath);
YAML::Node metadataList = YAML::Load(in);
in.close();
if (metadataList["plugins"]) {
for (const auto& node : metadataList["plugins"]) {
Plugin plugin(node.as<Plugin>());
if (plugin.IsRegexPlugin())
regexPlugins.push_back(plugin);
else
plugins.insert(plugin);
}
}
if (metadataList["globals"])
messages = metadataList["globals"].as< list<Message> >();
BOOST_LOG_TRIVIAL(debug) << "File loaded successfully.";
}
void MetadataList::Save(const boost::filesystem::path& filepath) {
BOOST_LOG_TRIVIAL(trace) << "Saving metadata list to: " << filepath;
YAML::Emitter yout;
yout.SetIndent(2);
yout << YAML::BeginMap
<< YAML::Key << "plugins" << YAML::Value << Plugins()
<< YAML::Key << "globals" << YAML::Value << messages
<< YAML::EndMap;
loot::ofstream uout(filepath);
uout << yout.c_str();
uout.close();
}
void MetadataList::clear() {
plugins.clear();
messages.clear();
}
bool MetadataList::operator == (const MetadataList& rhs) const {
if (this->plugins.size() != rhs.plugins.size() || this->messages.size() != rhs.messages.size() || this->regexPlugins.size() != rhs.regexPlugins.size()) {
BOOST_LOG_TRIVIAL(info) << "Metadata edited for some plugin, new and old userlists differ in size.";
return false;
}
else {
for (const auto& rhsPlugin : rhs.plugins) {
const auto it = this->plugins.find(rhsPlugin);
if (it == this->plugins.end()) {
BOOST_LOG_TRIVIAL(info) << "Metadata added for plugin: " << it->Name();
return false;
}
if (!it->DiffMetadata(rhsPlugin).HasNameOnly()) {
BOOST_LOG_TRIVIAL(info) << "Metadata edited for plugin: " << it->Name();
return false;
}
}
for (const auto& rhsPlugin : rhs.regexPlugins) {
const auto it = find(regexPlugins.begin(), regexPlugins.end(), rhsPlugin);
if (it == this->regexPlugins.end()) {
BOOST_LOG_TRIVIAL(info) << "Metadata added for plugin: " << it->Name();
return false;
}
if (!it->DiffMetadata(rhsPlugin).HasNameOnly()) {
BOOST_LOG_TRIVIAL(info) << "Metadata edited for plugin: " << it->Name();
return false;
}
}
// Messages are compared exactly by the '==' operator, so there's no need to do a more
// fine-grained check.
for (const auto& rhsMessage : rhs.messages) {
const auto it = std::find(this->messages.begin(), this->messages.end(), rhsMessage);
if (it == this->messages.end()) {
return false;
}
}
}
return true;
}
std::list<Plugin> MetadataList::Plugins() const {
list<Plugin> pluginList(plugins.begin(), plugins.end());
pluginList.insert(pluginList.end(), regexPlugins.begin(), regexPlugins.end());
return pluginList;
}
// Merges multiple matching regex entries if any are found.
Plugin MetadataList::FindPlugin(const Plugin& plugin) const {
Plugin match(plugin.Name());
auto it = plugins.find(plugin);
if (it != plugins.end())
match = *it;
// Now we want to also match possibly multiple regex entries.
auto regIt = find(regexPlugins.begin(), regexPlugins.end(), plugin);
while (regIt != regexPlugins.end()) {
match.MergeMetadata(*regIt);
regIt = find(++regIt, regexPlugins.end(), plugin);
}
return match;
}
void MetadataList::AddPlugin(const Plugin& plugin) {
if (plugin.IsRegexPlugin())
regexPlugins.push_back(plugin);
else
plugins.insert(plugin);
}
// Doesn't erase matching regex entries, because they might also
// be required for other plugins.
void MetadataList::ErasePlugin(const Plugin& plugin) {
auto it = plugins.find(plugin);
if (it != plugins.end()) {
plugins.erase(it);
return;
}
}
void MetadataList::EvalAllConditions(Game& game, const unsigned int language) {
unordered_set<Plugin> replacementSet;
for (auto &plugin : plugins) {
Plugin p(plugin);
p.EvalAllConditions(game, language);
replacementSet.insert(p);
}
plugins = replacementSet;
for (auto &plugin : regexPlugins) {
plugin.EvalAllConditions(game, language);
}
for (auto &message : messages) {
message.EvalCondition(game, language);
}
}
// Masterlist member functions
//----------------------------
bool Masterlist::Load(Game& game, const unsigned int language) {
try {
return Update(game);
}
catch (error& e) {
if (e.code() != error::ok) {
// Error wasn't a parsing error. Need to try parsing masterlist if it exists.
try {
MetadataList::Load(game.MasterlistPath());
}
catch (...) {}
}
throw;
}
}
std::string Masterlist::GetRevision(const boost::filesystem::path& path, bool shortID) {
if (revision.empty() || (shortID && revision.length() == 40) || (!shortID && revision.length() < 40))
GetGitInfo(path, shortID);
return revision;
}
std::string Masterlist::GetDate(const boost::filesystem::path& path) {
if (date.empty())
GetGitInfo(path, true);
return date;
}
// Game member functions
//----------------------
@@ -834,3 +645,20 @@ namespace loot {
return loot::Sort(graph, loadorder);
}
}
namespace YAML {
Emitter& operator << (Emitter& out, const loot::Game& rhs) {
out << BeginMap
<< Key << "type" << Value << YAML::SingleQuoted << loot::Game(rhs.Id()).FolderName()
<< Key << "folder" << Value << YAML::SingleQuoted << rhs.FolderName()
<< Key << "name" << Value << YAML::SingleQuoted << rhs.Name()
<< Key << "master" << Value << YAML::SingleQuoted << rhs.Master()
<< Key << "repo" << Value << YAML::SingleQuoted << rhs.RepoURL()
<< Key << "branch" << Value << YAML::SingleQuoted << rhs.RepoBranch()
<< Key << "path" << Value << YAML::SingleQuoted << rhs.GamePath().string()
<< Key << "registry" << Value << YAML::SingleQuoted << rhs.RegistryKey()
<< EndMap;
return out;
}
}
+59 -59
View File
@@ -25,7 +25,9 @@
#ifndef __LOOT_GAME__
#define __LOOT_GAME__
#include "metadata.h"
#include "plugin.h"
#include "metadata_list.h"
#include "masterlist.h"
#include <string>
#include <vector>
@@ -41,64 +43,6 @@
#include <yaml-cpp/yaml.h>
namespace loot {
class Game;
/* Each Game object should store the config details specific to that game.
It should also store the plugin and masterlist data for that game.
Plugin data should be stored as an unordered hashset, the elements of which are
referenced by ordered lists and other structures.
Masterlist / userlist data should be stored as structures which hold plugin and
global message lists.
Each game should have functions to load this plugin and masterlist / userlist
data. Plugin data should be loaded as header-only and as full data.
*/
class MetadataList {
public:
void Load(const boost::filesystem::path& filepath);
void Save(const boost::filesystem::path& filepath);
void clear();
bool operator == (const MetadataList& rhs) const; //Compares content.
std::list<Plugin> Plugins() const;
// Merges multiple matching regex entries if any are found.
Plugin FindPlugin(const Plugin& plugin) const;
void AddPlugin(const Plugin& plugin);
// Doesn't erase matching regex entries, because they might also
// be required for other plugins.
void ErasePlugin(const Plugin& plugin);
// Eval plugin conditions.
void EvalAllConditions(Game& game, const unsigned int language);
std::list<Message> messages;
protected:
std::unordered_set<Plugin> plugins;
std::list<Plugin> regexPlugins;
};
class Masterlist : public MetadataList {
public:
bool Load(Game& game, const unsigned int language); //Handles update with load fallback.
bool Update(const Game& game);
bool Update(const boost::filesystem::path& path,
const std::string& repoURL,
const std::string& repoBranch);
std::string GetRevision(const boost::filesystem::path& path, bool shortID);
std::string GetDate(const boost::filesystem::path& path);
private:
void GetGitInfo(const boost::filesystem::path& path, bool shortID);
std::string revision;
std::string date;
};
class Game {
public:
//Game functions.
@@ -188,4 +132,60 @@ namespace loot {
std::list<Game> GetGames(YAML::Node& settings);
}
namespace YAML {
template<>
struct convert < loot::Game > {
static Node encode(const loot::Game& rhs) {
Node node;
node["type"] = loot::Game(rhs.Id()).FolderName();
node["name"] = rhs.Name();
node["folder"] = rhs.FolderName();
node["master"] = rhs.Master();
node["repo"] = rhs.RepoURL();
node["branch"] = rhs.RepoBranch();
node["path"] = rhs.GamePath().string();
node["registry"] = rhs.RegistryKey();
return node;
}
static bool decode(const Node& node, loot::Game& rhs) {
if (!node.IsMap() || !node["folder"] || !node["type"])
return false;
if (node["type"].as<std::string>() == loot::Game(loot::Game::tes4).FolderName())
rhs = loot::Game(loot::Game::tes4, node["folder"].as<std::string>());
else if (node["type"].as<std::string>() == loot::Game(loot::Game::tes5).FolderName())
rhs = loot::Game(loot::Game::tes5, node["folder"].as<std::string>());
else if (node["type"].as<std::string>() == loot::Game(loot::Game::fo3).FolderName())
rhs = loot::Game(loot::Game::fo3, node["folder"].as<std::string>());
else if (node["type"].as<std::string>() == loot::Game(loot::Game::fonv).FolderName())
rhs = loot::Game(loot::Game::fonv, node["folder"].as<std::string>());
else
return false;
std::string name, master, repo, branch, path, registry;
if (node["name"])
name = node["name"].as<std::string>();
if (node["master"])
master = node["master"].as<std::string>();
if (node["repo"])
repo = node["repo"].as<std::string>();
if (node["branch"])
branch = node["branch"].as<std::string>();
if (node["path"])
path = node["path"].as<std::string>();
if (node["registry"])
registry = node["registry"].as<std::string>();
rhs.SetDetails(name, master, repo, branch, path, registry);
return true;
}
};
Emitter& operator << (Emitter& out, const loot::Game& rhs);
}
#endif
-193
View File
@@ -1,193 +0,0 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2013-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#include "generators.h"
#include "helpers.h"
#include "globals.h"
#include "parsers.h"
#include "streams.h"
#include <boost/algorithm/string.hpp>
#include <boost/locale.hpp>
using namespace std;
namespace YAML {
Emitter& operator << (Emitter& out, const loot::PluginDirtyInfo& rhs) {
out << BeginMap
<< Key << "crc" << Value << Hex << rhs.CRC() << Dec
<< Key << "util" << Value << YAML::SingleQuoted << rhs.CleaningUtility();
if (rhs.ITMs() > 0)
out << Key << "itm" << Value << rhs.ITMs();
if (rhs.DeletedRefs() > 0)
out << Key << "udr" << Value << rhs.DeletedRefs();
if (rhs.DeletedNavmeshes() > 0)
out << Key << "nav" << Value << rhs.DeletedNavmeshes();
out << EndMap;
return out;
}
Emitter& operator << (Emitter& out, const loot::Game& rhs) {
out << BeginMap
<< Key << "type" << Value << YAML::SingleQuoted << loot::Game(rhs.Id()).FolderName()
<< Key << "folder" << Value << YAML::SingleQuoted << rhs.FolderName()
<< Key << "name" << Value << YAML::SingleQuoted << rhs.Name()
<< Key << "master" << Value << YAML::SingleQuoted << rhs.Master()
<< Key << "repo" << Value << YAML::SingleQuoted << rhs.RepoURL()
<< Key << "branch" << Value << YAML::SingleQuoted << rhs.RepoBranch()
<< Key << "path" << Value << YAML::SingleQuoted << rhs.GamePath().string()
<< Key << "registry" << Value << YAML::SingleQuoted << rhs.RegistryKey()
<< EndMap;
return out;
}
Emitter& operator << (Emitter& out, const loot::MessageContent& rhs) {
out << BeginMap;
out << Key << "lang" << Value << loot::Language(rhs.Language()).Locale();
out << Key << "str" << Value << YAML::SingleQuoted << rhs.Str();
out << EndMap;
return out;
}
Emitter& operator << (Emitter& out, const loot::Message& rhs) {
out << BeginMap;
if (rhs.Type() == loot::Message::say)
out << Key << "type" << Value << "say";
else if (rhs.Type() == loot::Message::warn)
out << Key << "type" << Value << "warn";
else
out << Key << "type" << Value << "error";
if (rhs.Content().size() == 1)
out << Key << "content" << Value << YAML::SingleQuoted << rhs.Content().front().Str();
else
out << Key << "content" << Value << rhs.Content();
if (!rhs.Condition().empty())
out << Key << "condition" << Value << YAML::SingleQuoted << rhs.Condition();
out << EndMap;
return out;
}
Emitter& operator << (Emitter& out, const loot::File& rhs) {
if (!rhs.IsConditional() && rhs.DisplayName().empty())
out << rhs.Name();
else {
out << BeginMap
<< Key << "name" << Value << YAML::SingleQuoted << rhs.Name();
if (rhs.IsConditional())
out << Key << "condition" << Value << YAML::SingleQuoted << rhs.Condition();
if (rhs.DisplayName() != rhs.Name())
out << Key << "display" << Value << YAML::SingleQuoted << rhs.DisplayName();
out << EndMap;
}
return out;
}
Emitter& operator << (Emitter& out, const loot::Tag& rhs) {
if (!rhs.IsConditional()) {
if (rhs.IsAddition())
out << rhs.Name();
else
out << ('-' + rhs.Name());
}
else {
out << BeginMap;
if (rhs.IsAddition())
out << Key << "name" << Value << rhs.Name();
else
out << Key << "name" << Value << ('-' + rhs.Name());
out << Key << "condition" << Value << YAML::SingleQuoted << rhs.Condition()
<< EndMap;
}
return out;
}
Emitter& operator << (Emitter& out, const loot::Location& rhs) {
if (rhs.Versions().empty())
out << rhs.URL();
else {
out << BeginMap
<< Key << "link" << Value << YAML::SingleQuoted << rhs.URL()
<< Key << "ver" << Value << YAML::SingleQuoted << rhs.Versions()
<< EndMap;
}
return out;
}
Emitter& operator << (Emitter& out, const loot::Plugin& rhs) {
if (!rhs.HasNameOnly()) {
out << BeginMap
<< Key << "name" << Value << YAML::SingleQuoted << rhs.Name();
if (rhs.IsPriorityExplicit())
out << Key << "priority" << Value << rhs.Priority();
if (!rhs.Enabled())
out << Key << "enabled" << Value << rhs.Enabled();
if (!rhs.LoadAfter().empty())
out << Key << "after" << Value << rhs.LoadAfter();
if (!rhs.Reqs().empty())
out << Key << "req" << Value << rhs.Reqs();
if (!rhs.Incs().empty())
out << Key << "inc" << Value << rhs.Incs();
if (!rhs.Messages().empty())
out << Key << "msg" << Value << rhs.Messages();
if (!rhs.Tags().empty())
out << Key << "tag" << Value << rhs.Tags();
if (!rhs.DirtyInfo().empty())
out << Key << "dirty" << Value << rhs.DirtyInfo();
if (!rhs.Locations().empty())
out << Key << "url" << Value << rhs.Locations();
out << EndMap;
}
return out;
}
}
-1
View File
@@ -23,7 +23,6 @@
*/
#include "error.h"
#include "parsers.h"
#include "streams.h"
#include "helpers.h"
#include "game.h"
+1 -1
View File
@@ -25,7 +25,7 @@
#ifndef __LOOT_GRAPH__
#define __LOOT_GRAPH__
#include "metadata.h"
#include "plugin.h"
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
-225
View File
@@ -34,8 +34,6 @@
#include <boost/locale.hpp>
#include <boost/regex.hpp>
#include <alphanum.hpp>
#include <cstring>
#include <iostream>
#include <cctype>
@@ -243,227 +241,4 @@ namespace loot {
return str;
}
#endif
Language::Language(const unsigned int code) {
Construct(code);
}
Language::Language(const std::string& nameOrCode) {
if (nameOrCode == Language(Language::english).Name() || nameOrCode == Language(Language::english).Locale())
Construct(Language::english);
else if (nameOrCode == Language(Language::spanish).Name() || nameOrCode == Language(Language::spanish).Locale())
Construct(Language::spanish);
else if (nameOrCode == Language(Language::russian).Name() || nameOrCode == Language(Language::russian).Locale())
Construct(Language::russian);
else if (nameOrCode == Language(Language::french).Name() || nameOrCode == Language(Language::french).Locale())
Construct(Language::french);
else if (nameOrCode == Language(Language::chinese).Name() || nameOrCode == Language(Language::chinese).Locale())
Construct(Language::chinese);
else if (nameOrCode == Language(Language::polish).Name() || nameOrCode == Language(Language::polish).Locale())
Construct(Language::polish);
else if (nameOrCode == Language(Language::brazilian_portuguese).Name() || nameOrCode == Language(Language::brazilian_portuguese).Locale())
Construct(Language::brazilian_portuguese);
else if (nameOrCode == Language(Language::finnish).Name() || nameOrCode == Language(Language::finnish).Locale())
Construct(Language::finnish);
else if (nameOrCode == Language(Language::german).Name() || nameOrCode == Language(Language::german).Locale())
Construct(Language::german);
else if (nameOrCode == Language(Language::danish).Name() || nameOrCode == Language(Language::danish).Locale())
Construct(Language::danish);
else if (nameOrCode == Language(Language::korean).Name() || nameOrCode == Language(Language::korean).Locale())
Construct(Language::korean);
else
Construct(Language::english);
}
void Language::Construct(const unsigned int code) {
_code = code;
if (_code == Language::spanish) {
_name = "Español";
_locale = "es";
}
else if (_code == Language::russian) {
_name = "Русский";
_locale = "ru";
}
else if (_code == Language::french) {
_name = "Français";
_locale = "fr";
}
else if (_code == Language::chinese) {
_name = "简体中文";
_locale = "zh_CN";
}
else if (_code == Language::polish) {
_name = "Polski";
_locale = "pl";
}
else if (_code == Language::brazilian_portuguese) {
_name = "Português do Brasil";
_locale = "pt_BR";
}
else if (_code == Language::finnish) {
_name = "suomi";
_locale = "fi";
}
else if (_code == Language::german) {
_name = "Deutsch";
_locale = "de";
}
else if (_code == Language::danish) {
_name = "Dansk";
_locale = "da";
}
else if (_code == Language::korean) {
_name = "한국어";
_locale = "ko";
}
else {
_name = "English";
_locale = "en";
}
}
unsigned int Language::Code() const {
return _code;
}
std::string Language::Name() const {
return _name;
}
std::string Language::Locale() const {
return _locale;
}
const std::vector<std::string> Language::Names({
Language(Language::english).Name(),
Language(Language::spanish).Name(),
Language(Language::russian).Name(),
Language(Language::french).Name(),
Language(Language::chinese).Name(),
Language(Language::polish).Name(),
Language(Language::brazilian_portuguese).Name(),
Language(Language::finnish).Name(),
Language(Language::german).Name(),
Language(Language::danish).Name(),
Language(Language::korean).Name()
});
//////////////////////////////
// Version Class Functions
//////////////////////////////
Version::Version() {}
Version::Version(const std::string& ver)
: verString(ver) {}
Version::Version(const fs::path& file) {
#ifdef _WIN32
DWORD dummy = 0;
DWORD size = GetFileVersionInfoSize(ToWinWide(file.string()).c_str(), &dummy);
if (size > 0) {
LPBYTE point = new BYTE[size];
UINT uLen;
VS_FIXEDFILEINFO *info;
GetFileVersionInfo(ToWinWide(file.string()).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 = to_string(dwLeftMost) + '.' + to_string(dwSecondLeft) + '.' + to_string(dwSecondRight) + '.' + to_string(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 (nullptr != fgets(buf, BUFSIZE, fp)) {
verString = string(buf);
}
pclose(fp);
}
#endif
}
Version::Version(const Plugin& plugin) : verString(plugin.Version()) {}
string Version::AsString() const {
return verString;
}
bool Version::operator < (const Version& ver) const {
//Version string could have a wide variety of formats. Use regex to choose specific comparison types.
regex reg1("(\\d+\\.?)+"); //a.b.c.d.e.f.... where the letters are all integers, and 'a' is the shortest possible match.
//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 (regex_match(verString, reg1) && 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 > (const Version& ver) const {
return (*this != ver && !(*this < ver));
}
bool Version::operator >= (const Version& ver) const {
return (*this == ver || *this > ver);
}
bool Version::operator <= (const Version& ver) const {
return (*this == ver || *this < ver);
}
bool Version::operator == (const Version& ver) const {
return (verString == ver.AsString());
}
bool Version::operator != (const Version& ver) const {
return !(*this == ver);
}
}
+1 -53
View File
@@ -25,7 +25,7 @@
#ifndef __LOOT_HELPERS__
#define __LOOT_HELPERS__
#include "metadata.h"
#include "plugin.h"
#include <cstdint>
#include <string>
@@ -65,58 +65,6 @@ namespace loot {
std::string FromWinWide(const std::wstring& wstr);
#endif
//Language class for simpler language support.
class Language {
public:
Language(const unsigned int code);
Language(const std::string& nameOrCode);
unsigned int Code() const;
std::string Name() const;
std::string Locale() const;
static const unsigned int any = 0; // This shouldn't be used as a selectable language, just for when picking any string in a message.
static const unsigned int english = 1;
static const unsigned int spanish = 2;
static const unsigned int russian = 3;
static const unsigned int french = 4;
static const unsigned int chinese = 5;
static const unsigned int polish = 6;
static const unsigned int brazilian_portuguese = 7;
static const unsigned int finnish = 8;
static const unsigned int german = 9;
static const unsigned int danish = 10;
static const unsigned int korean = 11;
static const std::vector<std::string> Names;
private:
unsigned int _code;
std::string _name;
std::string _locale;
void Construct(const unsigned int code);
};
//Version class for more robust version comparisons.
class Version {
private:
std::string verString;
public:
Version();
Version(const std::string& ver);
Version(const boost::filesystem::path& file);
Version(const Plugin& plugin);
std::string AsString() const;
bool operator > (const Version&) const;
bool operator < (const Version&) const;
bool operator >= (const Version&) const;
bool operator <= (const Version&) const;
bool operator == (const Version&) const;
bool operator != (const Version&) const;
};
}
#endif
+1
View File
@@ -28,6 +28,7 @@ along with LOOT. If not, see
#include <yaml-cpp/yaml.h>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
namespace loot {
// Handy class for turning YAML objects into JSON and vice-versa.
+132
View File
@@ -0,0 +1,132 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#include "language.h"
namespace loot {
Language::Language(const unsigned int code) {
Construct(code);
}
Language::Language(const std::string& nameOrCode) {
if (nameOrCode == Language(Language::english).Name() || nameOrCode == Language(Language::english).Locale())
Construct(Language::english);
else if (nameOrCode == Language(Language::spanish).Name() || nameOrCode == Language(Language::spanish).Locale())
Construct(Language::spanish);
else if (nameOrCode == Language(Language::russian).Name() || nameOrCode == Language(Language::russian).Locale())
Construct(Language::russian);
else if (nameOrCode == Language(Language::french).Name() || nameOrCode == Language(Language::french).Locale())
Construct(Language::french);
else if (nameOrCode == Language(Language::chinese).Name() || nameOrCode == Language(Language::chinese).Locale())
Construct(Language::chinese);
else if (nameOrCode == Language(Language::polish).Name() || nameOrCode == Language(Language::polish).Locale())
Construct(Language::polish);
else if (nameOrCode == Language(Language::brazilian_portuguese).Name() || nameOrCode == Language(Language::brazilian_portuguese).Locale())
Construct(Language::brazilian_portuguese);
else if (nameOrCode == Language(Language::finnish).Name() || nameOrCode == Language(Language::finnish).Locale())
Construct(Language::finnish);
else if (nameOrCode == Language(Language::german).Name() || nameOrCode == Language(Language::german).Locale())
Construct(Language::german);
else if (nameOrCode == Language(Language::danish).Name() || nameOrCode == Language(Language::danish).Locale())
Construct(Language::danish);
else if (nameOrCode == Language(Language::korean).Name() || nameOrCode == Language(Language::korean).Locale())
Construct(Language::korean);
else
Construct(Language::english);
}
void Language::Construct(const unsigned int code) {
_code = code;
if (_code == Language::spanish) {
_name = "Español";
_locale = "es";
}
else if (_code == Language::russian) {
_name = "Русский";
_locale = "ru";
}
else if (_code == Language::french) {
_name = "Français";
_locale = "fr";
}
else if (_code == Language::chinese) {
_name = "简体中文";
_locale = "zh_CN";
}
else if (_code == Language::polish) {
_name = "Polski";
_locale = "pl";
}
else if (_code == Language::brazilian_portuguese) {
_name = "Português do Brasil";
_locale = "pt_BR";
}
else if (_code == Language::finnish) {
_name = "suomi";
_locale = "fi";
}
else if (_code == Language::german) {
_name = "Deutsch";
_locale = "de";
}
else if (_code == Language::danish) {
_name = "Dansk";
_locale = "da";
}
else if (_code == Language::korean) {
_name = "한국어";
_locale = "ko";
}
else {
_name = "English";
_locale = "en";
}
}
unsigned int Language::Code() const {
return _code;
}
std::string Language::Name() const {
return _name;
}
std::string Language::Locale() const {
return _locale;
}
const std::vector<std::string> Language::Names({
Language(Language::english).Name(),
Language(Language::spanish).Name(),
Language(Language::russian).Name(),
Language(Language::french).Name(),
Language(Language::chinese).Name(),
Language(Language::polish).Name(),
Language(Language::brazilian_portuguese).Name(),
Language(Language::finnish).Name(),
Language(Language::german).Name(),
Language(Language::danish).Name(),
Language(Language::korean).Name()
});
}
+65
View File
@@ -0,0 +1,65 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#ifndef __LOOT_LANGUAGE__
#define __LOOT_LANGUAGE__
#include <string>
#include <vector>
namespace loot {
//Language class for simpler language support.
class Language {
public:
Language(const unsigned int code);
Language(const std::string& nameOrCode);
unsigned int Code() const;
std::string Name() const;
std::string Locale() const;
static const unsigned int any = 0; // This shouldn't be used as a selectable language, just for when picking any string in a message.
static const unsigned int english = 1;
static const unsigned int spanish = 2;
static const unsigned int russian = 3;
static const unsigned int french = 4;
static const unsigned int chinese = 5;
static const unsigned int polish = 6;
static const unsigned int brazilian_portuguese = 7;
static const unsigned int finnish = 8;
static const unsigned int german = 9;
static const unsigned int danish = 10;
static const unsigned int korean = 11;
static const std::vector<std::string> Names;
private:
unsigned int _code;
std::string _name;
std::string _locale;
void Construct(const unsigned int code);
};
}
#endif
+64
View File
@@ -0,0 +1,64 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#include "masterlist.h"
#include "game.h"
#include "error.h"
using namespace std;
namespace fs = boost::filesystem;
namespace lc = boost::locale;
namespace loot {
bool Masterlist::Load(Game& game, const unsigned int language) {
try {
return Update(game);
}
catch (error& e) {
if (e.code() != error::ok) {
// Error wasn't a parsing error. Need to try parsing masterlist if it exists.
try {
MetadataList::Load(game.MasterlistPath());
}
catch (...) {}
}
throw;
}
}
std::string Masterlist::GetRevision(const boost::filesystem::path& path, bool shortID) {
if (revision.empty() || (shortID && revision.length() == 40) || (!shortID && revision.length() < 40))
GetGitInfo(path, shortID);
return revision;
}
std::string Masterlist::GetDate(const boost::filesystem::path& path) {
if (date.empty())
GetGitInfo(path, true);
return date;
}
}
+57
View File
@@ -0,0 +1,57 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#ifndef __LOOT_MASTERLIST__
#define __LOOT_MASTERLIST__
#include "metadata_list.h"
#include <string>
#include <boost/filesystem.hpp>
namespace loot {
class Game;
class Masterlist : public MetadataList {
public:
bool Load(Game& game, const unsigned int language); //Handles update with load fallback.
bool Update(const Game& game);
bool Update(const boost::filesystem::path& path,
const std::string& repoURL,
const std::string& repoBranch);
std::string GetRevision(const boost::filesystem::path& path, bool shortID);
std::string GetDate(const boost::filesystem::path& path);
private:
void GetGitInfo(const boost::filesystem::path& path, bool shortID);
std::string revision;
std::string date;
};
}
#endif
+353
View File
@@ -0,0 +1,353 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#ifndef __LOOT_CONDITION_PARSER__
#define __LOOT_CONDITION_PARSER__
#ifndef BOOST_SPIRIT_UNICODE
#define BOOST_SPIRIT_UNICODE
#endif
#ifndef BOOST_SPIRIT_USE_PHOENIX_V3
#define BOOST_SPIRIT_USE_PHOENIX_V3 1
#endif
#include "../game.h"
#include "../helpers.h"
#include "../plugin.h"
#include "../version.h"
#include "../error.h"
#include <cstdint>
#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/regex.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_bind.hpp>
#include <boost/log/trivial.hpp>
#include <boost/locale.hpp>
#include <boost/format.hpp>
namespace loot {
///////////////////////////////
// Condition parser/evaluator
///////////////////////////////
namespace qi = boost::spirit::qi;
namespace unicode = boost::spirit::unicode;
namespace phoenix = boost::phoenix;
template<typename Iterator, typename Skipper>
class ConditionGrammar : public qi::grammar < Iterator, bool(), Skipper > {
public:
ConditionGrammar(Game * game, bool parseOnly) : ConditionGrammar::base_type(expression, "condition grammar"), _game(game), _parseOnly(parseOnly) {
if (!_parseOnly && _game == nullptr)
throw error(error::invalid_args, "A valid game pointer was not passed during a condition evaluation.");
expression =
compound[qi::labels::_val = qi::labels::_1]
>> *((qi::lit("or") >> compound)[qi::labels::_val = qi::labels::_val || qi::labels::_1])
;
compound =
condition[qi::labels::_val = qi::labels::_1]
>> *((qi::lit("and") >> condition)[qi::labels::_val = qi::labels::_val && qi::labels::_1])
;
condition =
function[qi::labels::_val = qi::labels::_1]
| (qi::lit("not") > condition)[qi::labels::_val = !qi::labels::_1]
| ('(' > expression > ')')[qi::labels::_val = qi::labels::_1]
;
function =
("file(" > filePath > ')')[phoenix::bind(&ConditionGrammar::CheckFile, this, qi::labels::_val, qi::labels::_1)]
| ("regex(" > quotedStr > ')')[phoenix::bind(&ConditionGrammar::CheckRegex, this, qi::labels::_val, qi::labels::_1)]
| ("checksum(" > filePath > ',' > qi::hex > ')')[phoenix::bind(&ConditionGrammar::CheckSum, this, qi::labels::_val, qi::labels::_1, qi::labels::_2)]
| ("version(" > filePath > ',' > quotedStr > ',' > comparator > ')')[phoenix::bind(&ConditionGrammar::CheckVersion, this, qi::labels::_val, qi::labels::_1, qi::labels::_2, qi::labels::_3)]
| ("active(" > filePath > ')')[phoenix::bind(&ConditionGrammar::CheckActive, this, qi::labels::_val, qi::labels::_1)]
;
quotedStr %= '"' > +(unicode::char_ - '"') > '"';
filePath %= '"' > +(unicode::char_ - invalidPathChars) > '"';
invalidPathChars %=
unicode::char_(':')
| unicode::char_('*')
| unicode::char_('?')
| unicode::char_('"')
| unicode::char_('<')
| unicode::char_('>')
| unicode::char_('|')
;
comparator %=
unicode::string("==")
| unicode::string("!=")
| unicode::string("<=")
| unicode::string(">=")
| unicode::string("<")
| unicode::string(">")
;
expression.name("expression");
compound.name("compound condition");
condition.name("condition");
function.name("function");
quotedStr.name("quoted string");
filePath.name("file path");
comparator.name("comparator");
invalidPathChars.name("invalid file path characters");
qi::on_error<qi::fail>(expression, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
qi::on_error<qi::fail>(compound, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
qi::on_error<qi::fail>(condition, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
qi::on_error<qi::fail>(function, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
qi::on_error<qi::fail>(quotedStr, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
qi::on_error<qi::fail>(filePath, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
qi::on_error<qi::fail>(comparator, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
qi::on_error<qi::fail>(invalidPathChars, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
}
private:
qi::rule<Iterator, bool(), Skipper> expression, compound, condition, function;
qi::rule<Iterator, std::string()> quotedStr, filePath, comparator;
qi::rule<Iterator, char()> invalidPathChars;
Game * _game;
bool _parseOnly;
//Eval's exact paths. Check for files and ghosted plugins.
void CheckFile(bool& result, const std::string& file) {
if (_parseOnly)
return;
BOOST_LOG_TRIVIAL(trace) << "Checking to see if the file \"" << file << "\" exists.";
if (file == "LOOT") {
result = true;
return;
}
if (!IsSafePath(file)) {
BOOST_LOG_TRIVIAL(error) << "Invalid file path: " << file;
throw loot::error(loot::error::invalid_args, boost::locale::translate("Invalid file path:").str() + " " + file);
}
if (boost::iends_with(file, ".esp") || boost::iends_with(file, ".esm"))
result = boost::filesystem::exists(_game->DataPath() / file) || boost::filesystem::exists(_game->DataPath() / (file + ".ghost"));
else
result = boost::filesystem::exists(_game->DataPath() / file);
if (result)
BOOST_LOG_TRIVIAL(trace) << "The file does exist.";
else
BOOST_LOG_TRIVIAL(trace) << "The file does not exist.";
}
void CheckRegex(bool& result, const std::string& regexStr) {
if (_parseOnly)
return;
result = false;
//Can't support a regex string where all path components may be regex, since this could
//lead to massive scanning if an unfortunately-named directory is encountered.
//As such, only the filename portion can be a regex. Need to separate that from the rest
//of the string.
/* Look for directory separators: in non-regex strings, they are '/' and '\'. In regex,
the backslash is special so must be escaped using another backslash, so look for '/' and "\\".
In C++ string literals, the backslash must be escaped once more to give "\\\\".
Split the regex with another regex! */
//Need to also check if the regex is for a safe path.
BOOST_LOG_TRIVIAL(trace) << "Checking to see if any files matching the regex \"" << regexStr << "\" exist.";
boost::regex sepReg("/|(\\\\\\\\)", boost::regex::ECMAScript | boost::regex::icase);
std::vector<std::string> components;
boost::sregex_token_iterator it(regexStr.begin(), regexStr.end(), sepReg, -1);
boost::sregex_token_iterator itend;
for (; it != itend; ++it) {
components.push_back(*it);
}
std::string filename = components.back();
components.pop_back();
std::string parent;
for (std::vector<std::string>::const_iterator it = components.begin(), endIt = components.end()--; it != endIt; ++it) {
if (*it == ".")
continue;
parent += *it + '/';
}
if (boost::contains(parent, "../../")) {
BOOST_LOG_TRIVIAL(error) << "Invalid folder path: " << parent;
throw loot::error(loot::error::invalid_args, boost::locale::translate("Invalid folder path:").str() + " " + parent);
}
//Now we have a valid parent path and a regex filename. Check that
//the parent path exists and is a directory.
boost::filesystem::path parent_path = _game->DataPath() / parent;
if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) {
BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory.";
return;
}
boost::regex reg;
try {
reg = boost::regex(filename, boost::regex::ECMAScript | boost::regex::icase);
}
catch (std::exception& /*e*/) {
BOOST_LOG_TRIVIAL(error) << "Invalid regex string:" << filename;
throw loot::error(loot::error::invalid_args, boost::locale::translate("Invalid regex string:").str() + " " + filename);
}
for (boost::filesystem::directory_iterator itr(parent_path); itr != boost::filesystem::directory_iterator(); ++itr) {
if (boost::regex_match(itr->path().filename().string(), reg)) {
result = true;
BOOST_LOG_TRIVIAL(trace) << "Matching file found: " << itr->path();
return;
}
}
}
void CheckSum(bool& result, const std::string& file, const uint32_t checksum) {
if (_parseOnly)
return;
BOOST_LOG_TRIVIAL(trace) << "Checking the CRC of the file \"" << file << "\".";
if (!IsSafePath(file)) {
BOOST_LOG_TRIVIAL(error) << "Invalid file path: " << file;
throw loot::error(loot::error::invalid_args, boost::locale::translate("Invalid file path:").str() + " " + file);
}
uint32_t crc;
std::unordered_map<std::string, uint32_t>::iterator it = _game->crcCache.find(boost::locale::to_lower(file));
if (it != _game->crcCache.end())
crc = it->second;
else {
if (file == "LOOT")
crc = GetCrc32(boost::filesystem::absolute("LOOT.exe"));
if (boost::filesystem::exists(_game->DataPath() / file))
crc = GetCrc32(_game->DataPath() / file);
else if ((boost::iends_with(file, ".esp") || boost::iends_with(file, ".esm")) && boost::filesystem::exists(_game->DataPath() / (file + ".ghost")))
crc = GetCrc32(_game->DataPath() / (file + ".ghost"));
else {
result = false;
return;
}
_game->crcCache.insert(std::pair<std::string, uint32_t>(boost::locale::to_lower(file), crc));
}
result = checksum == crc;
}
void CheckVersion(bool& result, const std::string& file, const std::string& version, const std::string& comparator) {
if (_parseOnly)
return;
BOOST_LOG_TRIVIAL(trace) << "Checking version of file \"" << file << "\".";
CheckFile(result, file);
if (!result) {
if (comparator == "!=" || comparator == "<" || comparator == "<=")
result = true;
BOOST_LOG_TRIVIAL(trace) << "Version check result: " << result;
return;
}
Version givenVersion = Version(version);
Version trueVersion;
if (file == "LOOT")
trueVersion = Version(boost::filesystem::absolute("LOOT.exe"));
else if (_game->IsValidPlugin(file)) {
Plugin plugin(*_game, file, true);
trueVersion = Version(plugin.Version());
}
else
trueVersion = Version(_game->DataPath() / file);
BOOST_LOG_TRIVIAL(trace) << "Version extracted: " << trueVersion.AsString();
if ((comparator == "==" && trueVersion != givenVersion)
|| (comparator == "!=" && trueVersion == givenVersion)
|| (comparator == "<" && trueVersion >= givenVersion)
|| (comparator == ">" && trueVersion <= givenVersion)
|| (comparator == "<=" && trueVersion > givenVersion)
|| (comparator == ">=" && trueVersion < givenVersion))
result = false;
BOOST_LOG_TRIVIAL(trace) << "Version check result: " << result;
}
void CheckActive(bool& result, const std::string& file) {
if (_parseOnly)
return;
if (file == "LOOT")
result = false;
else
result = _game->IsActive(file);
BOOST_LOG_TRIVIAL(trace) << "Active check result: " << result;
}
void SyntaxError(Iterator const& /*first*/, Iterator const& last, Iterator const& errorpos, boost::spirit::info const& what) {
std::string context(errorpos, min(errorpos + 50, last));
boost::trim(context);
BOOST_LOG_TRIVIAL(error) << "Expected \"" << what.tag << "\" at \"" << context << "\".";
throw loot::error(loot::error::condition_eval_fail, (boost::format(boost::locale::translate("Expected \"%1%\" at \"%2%\".")) % what.tag % context).str());
}
//Checks that the path (not regex) doesn't go outside any game folders.
bool IsSafePath(const std::string& path) {
BOOST_LOG_TRIVIAL(trace) << "Checking to see if the path \"" << path << "\" is safe.";
std::vector<std::string> components;
boost::split(components, path, boost::is_any_of("/\\"));
components.pop_back();
std::string parent_path;
for (auto it = components.cbegin(), endIt = components.cend()--; it != endIt; ++it) {
if (*it == ".")
continue;
parent_path += *it + '/';
}
return !boost::contains(parent_path, "../../");
}
};
}
#endif
@@ -0,0 +1,112 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#include "conditional_metadata.h"
#include "condition_grammar.h"
#include <boost/log/trivial.hpp>
#include <boost/locale.hpp>
using namespace std;
namespace loot {
namespace lc = boost::locale;
ConditionalMetadata::ConditionalMetadata() {}
ConditionalMetadata::ConditionalMetadata(const string& condition) : _condition(condition) {}
bool ConditionalMetadata::IsConditional() const {
return !_condition.empty();
}
std::string ConditionalMetadata::Condition() const {
return _condition;
}
bool ConditionalMetadata::EvalCondition(Game& game) const {
if (_condition.empty())
return true;
BOOST_LOG_TRIVIAL(trace) << "Evaluating condition: " << _condition;
unordered_map<std::string, bool>::const_iterator it = game.conditionCache.find(boost::locale::to_lower(_condition));
if (it != game.conditionCache.end())
return it->second;
ConditionGrammar<std::string::const_iterator, boost::spirit::qi::space_type> grammar(&game, false);
boost::spirit::qi::space_type skipper;
std::string::const_iterator begin, end;
bool eval;
begin = _condition.begin();
end = _condition.end();
bool r;
try {
r = boost::spirit::qi::phrase_parse(begin, end, grammar, skipper, eval);
}
catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << _condition << "\": " << e.what();
throw loot::error(loot::error::condition_eval_fail, (boost::format(lc::translate("Failed to parse condition \"%1%\": %2%")) % _condition % e.what()).str());
}
if (!r || begin != end) {
BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << _condition << "\".";
throw loot::error(loot::error::condition_eval_fail, (boost::format(lc::translate("Failed to parse condition \"%1%\".")) % _condition).str());
}
game.conditionCache.insert(pair<string, bool>(boost::locale::to_lower(_condition), eval));
return eval;
}
void ConditionalMetadata::ParseCondition() const {
if (_condition.empty())
return;
BOOST_LOG_TRIVIAL(trace) << "Testing condition syntax: " << _condition;
ConditionGrammar<std::string::const_iterator, boost::spirit::qi::space_type> grammar(nullptr, true);
boost::spirit::qi::space_type skipper;
std::string::const_iterator begin, end;
begin = _condition.begin();
end = _condition.end();
bool r;
try {
r = boost::spirit::qi::phrase_parse(begin, end, grammar, skipper);
}
catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << _condition << "\": " << e.what();
throw loot::error(loot::error::condition_eval_fail, (boost::format(lc::translate("Failed to parse condition \"%1%\": %2%")) % _condition % e.what()).str());
}
if (!r || begin != end) {
BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << _condition << "\".";
throw loot::error(loot::error::condition_eval_fail, (boost::format(lc::translate("Failed to parse condition \"%1%\".")) % _condition).str());
}
}
}
@@ -0,0 +1,46 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#ifndef __LOOT_METADATA_CONDITIONAL_METADATA__
#define __LOOT_METADATA_CONDITIONAL_METADATA__
#include <string>
namespace loot {
class Game;
class ConditionalMetadata {
public:
ConditionalMetadata();
ConditionalMetadata(const std::string& condition);
bool IsConditional() const;
bool EvalCondition(Game& game) const;
void ParseCondition() const; // Throws error on parsing failure.
std::string Condition() const;
private:
std::string _condition;
};
}
#endif
+76
View File
@@ -0,0 +1,76 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#include "file.h"
#include <boost/algorithm/string.hpp>
using namespace std;
namespace loot {
File::File() {}
File::File(const std::string& name, const std::string& display, const std::string& condition)
: _name(name), _display(display), ConditionalMetadata(condition) {}
bool File::operator < (const File& rhs) const {
return boost::ilexicographical_compare(Name(), rhs.Name());
}
bool File::operator == (const File& rhs) const {
return boost::iequals(Name(), rhs.Name());
}
std::string File::Name() const {
return _name;
}
std::string File::DisplayName() const {
if (_display.empty())
return _name;
else
return _display;
}
}
namespace YAML {
Emitter& operator << (Emitter& out, const loot::File& rhs) {
if (!rhs.IsConditional() && rhs.DisplayName().empty())
out << rhs.Name();
else {
out << BeginMap
<< Key << "name" << Value << YAML::SingleQuoted << rhs.Name();
if (rhs.IsConditional())
out << Key << "condition" << Value << YAML::SingleQuoted << rhs.Condition();
if (rhs.DisplayName() != rhs.Name())
out << Key << "display" << Value << YAML::SingleQuoted << rhs.DisplayName();
out << EndMap;
}
return out;
}
}
+85
View File
@@ -0,0 +1,85 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#ifndef __LOOT_METADATA_FILE__
#define __LOOT_METADATA_FILE__
#include "conditional_metadata.h"
#include <string>
#include <yaml-cpp/yaml.h>
namespace loot {
class File : public ConditionalMetadata {
public:
File();
File(const std::string& name, const std::string& display = "",
const std::string& condition = "");
bool operator < (const File& rhs) const;
bool operator == (const File& rhs) const;
std::string Name() const;
std::string DisplayName() const;
private:
std::string _name;
std::string _display;
};
}
namespace YAML {
template<>
struct convert < loot::File > {
static Node encode(const loot::File& rhs) {
Node node;
node["condition"] = rhs.Condition();
node["name"] = rhs.Name();
node["display"] = rhs.DisplayName();
return node;
}
static bool decode(const Node& node, loot::File& rhs) {
if (node.IsMap()) {
if (!node["name"])
return false;
std::string condition, name, display;
if (node["condition"])
condition = node["condition"].as<std::string>();
if (node["name"])
name = node["name"].as<std::string>();
if (node["display"])
display = node["display"].as<std::string>();
rhs = loot::File(name, display, condition);
}
else
rhs = loot::File(node.as<std::string>());
return true;
}
};
Emitter& operator << (Emitter& out, const loot::File& rhs);
}
#endif
+67
View File
@@ -0,0 +1,67 @@
/* LOOT
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
Copyright (C) 2012-2015 WrinklyNinja
This file is part of LOOT.
LOOT 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.
LOOT 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 LOOT. If not, see
<http://www.gnu.org/licenses/>.
*/
#include "formid.h"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
using namespace std;
namespace loot {
FormID::FormID() : id(0) {}
FormID::FormID(const std::string& sourcePlugin, const uint32_t objectID) : plugin(sourcePlugin), id(objectID) {}
FormID::FormID(const std::vector<std::string>& sourcePlugins, const uint32_t formID) {
unsigned int index = formID >> 24;
id = formID & ~((uint32_t)index << 24);
if (index >= sourcePlugins.size()) {
BOOST_LOG_TRIVIAL(trace) << hex << formID << dec << " in " << sourcePlugins.back() << " has a higher modIndex than expected.";
index = sourcePlugins.size() - 1;
}
plugin = sourcePlugins[index];
}
bool FormID::operator == (const FormID& rhs) const {
return (id == rhs.Id() && boost::iequals(plugin, rhs.Plugin()));
}
bool FormID::operator < (const FormID& rhs) const {
if (id != rhs.Id())
return id < rhs.Id();
else
return boost::ilexicographical_compare(plugin, rhs.Plugin());
}
std::string FormID::Plugin() const {
return plugin;
}
uint32_t FormID::Id() const {
return id;
}
}

Some files were not shown because too many files have changed in this diff Show More