diff --git a/CMakeLists.txt b/CMakeLists.txt index 29f73397..f456731f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/src/api/api.cpp b/src/api/api.cpp index 00363c86..3854eb5b 100644 --- a/src/api/api.cpp +++ b/src/api/api.cpp @@ -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 #include +#include const unsigned int loot_ok = loot::error::ok; const unsigned int loot_error_liblo_error = loot::error::liblo_error; diff --git a/src/backend/game.cpp b/src/backend/game.cpp index ccbac21a..dfa658bb 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -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 @@ -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()); - if (plugin.IsRegexPlugin()) - regexPlugins.push_back(plugin); - else - plugins.insert(plugin); - } - } - if (metadataList["globals"]) - messages = metadataList["globals"].as< list >(); - - 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 MetadataList::Plugins() const { - list 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 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; + } +} \ No newline at end of file diff --git a/src/backend/game.h b/src/backend/game.h index 74f0fce1..417d7e13 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -25,7 +25,9 @@ #ifndef __LOOT_GAME__ #define __LOOT_GAME__ -#include "metadata.h" +#include "plugin.h" +#include "metadata_list.h" +#include "masterlist.h" #include #include @@ -41,64 +43,6 @@ #include 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 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 messages; - protected: - std::unordered_set plugins; - std::list 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 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() == loot::Game(loot::Game::tes4).FolderName()) + rhs = loot::Game(loot::Game::tes4, node["folder"].as()); + else if (node["type"].as() == loot::Game(loot::Game::tes5).FolderName()) + rhs = loot::Game(loot::Game::tes5, node["folder"].as()); + else if (node["type"].as() == loot::Game(loot::Game::fo3).FolderName()) + rhs = loot::Game(loot::Game::fo3, node["folder"].as()); + else if (node["type"].as() == loot::Game(loot::Game::fonv).FolderName()) + rhs = loot::Game(loot::Game::fonv, node["folder"].as()); + else + return false; + + std::string name, master, repo, branch, path, registry; + if (node["name"]) + name = node["name"].as(); + if (node["master"]) + master = node["master"].as(); + if (node["repo"]) + repo = node["repo"].as(); + if (node["branch"]) + branch = node["branch"].as(); + if (node["path"]) + path = node["path"].as(); + if (node["registry"]) + registry = node["registry"].as(); + + rhs.SetDetails(name, master, repo, branch, path, registry); + + return true; + } + }; + + Emitter& operator << (Emitter& out, const loot::Game& rhs); +} + #endif diff --git a/src/backend/generators.cpp b/src/backend/generators.cpp deleted file mode 100644 index 7731ab37..00000000 --- a/src/backend/generators.cpp +++ /dev/null @@ -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 -. -*/ - -#include "generators.h" -#include "helpers.h" -#include "globals.h" -#include "parsers.h" -#include "streams.h" - -#include -#include - -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; - } -} \ No newline at end of file diff --git a/src/backend/git.cpp b/src/backend/git.cpp index 7fef187c..3b140647 100644 --- a/src/backend/git.cpp +++ b/src/backend/git.cpp @@ -23,7 +23,6 @@ */ #include "error.h" -#include "parsers.h" #include "streams.h" #include "helpers.h" #include "game.h" diff --git a/src/backend/graph.h b/src/backend/graph.h index 5f85b848..aab0e074 100644 --- a/src/backend/graph.h +++ b/src/backend/graph.h @@ -25,7 +25,7 @@ #ifndef __LOOT_GRAPH__ #define __LOOT_GRAPH__ -#include "metadata.h" +#include "plugin.h" #include #include diff --git a/src/backend/helpers.cpp b/src/backend/helpers.cpp index f8680ab8..e00fcdd0 100644 --- a/src/backend/helpers.cpp +++ b/src/backend/helpers.cpp @@ -34,8 +34,6 @@ #include #include -#include - #include #include #include @@ -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 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); - } } diff --git a/src/backend/helpers.h b/src/backend/helpers.h index bd1092ec..89fa8701 100644 --- a/src/backend/helpers.h +++ b/src/backend/helpers.h @@ -25,7 +25,7 @@ #ifndef __LOOT_HELPERS__ #define __LOOT_HELPERS__ -#include "metadata.h" +#include "plugin.h" #include #include @@ -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 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 diff --git a/src/backend/json.h b/src/backend/json.h index fc4eef29..50a02fa3 100644 --- a/src/backend/json.h +++ b/src/backend/json.h @@ -28,6 +28,7 @@ along with LOOT. If not, see #include #include +#include namespace loot { // Handy class for turning YAML objects into JSON and vice-versa. diff --git a/src/backend/language.cpp b/src/backend/language.cpp new file mode 100644 index 00000000..425e5846 --- /dev/null +++ b/src/backend/language.cpp @@ -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 + . + */ + +#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 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() + }); +} diff --git a/src/backend/language.h b/src/backend/language.h new file mode 100644 index 00000000..b37db15c --- /dev/null +++ b/src/backend/language.h @@ -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 + . + */ + +#ifndef __LOOT_LANGUAGE__ +#define __LOOT_LANGUAGE__ + +#include +#include + +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 Names; + private: + unsigned int _code; + std::string _name; + std::string _locale; + + void Construct(const unsigned int code); + }; +} + +#endif diff --git a/src/backend/masterlist.cpp b/src/backend/masterlist.cpp new file mode 100644 index 00000000..aa131f81 --- /dev/null +++ b/src/backend/masterlist.cpp @@ -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 + . + */ + +#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; + } +} diff --git a/src/backend/masterlist.h b/src/backend/masterlist.h new file mode 100644 index 00000000..01d1a5ad --- /dev/null +++ b/src/backend/masterlist.h @@ -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 + . + */ + +#ifndef __LOOT_MASTERLIST__ +#define __LOOT_MASTERLIST__ + +#include "metadata_list.h" + +#include + +#include + +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 diff --git a/src/backend/metadata/condition_grammar.h b/src/backend/metadata/condition_grammar.h new file mode 100644 index 00000000..dad8a648 --- /dev/null +++ b/src/backend/metadata/condition_grammar.h @@ -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 + . + */ + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace loot { + /////////////////////////////// + // Condition parser/evaluator + /////////////////////////////// + + namespace qi = boost::spirit::qi; + namespace unicode = boost::spirit::unicode; + namespace phoenix = boost::phoenix; + + template + 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(expression, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(compound, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(condition, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(function, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(quotedStr, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(filePath, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(comparator, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(invalidPathChars, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + } + + private: + qi::rule expression, compound, condition, function; + qi::rule quotedStr, filePath, comparator; + qi::rule 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 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::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::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(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 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 diff --git a/src/backend/metadata/conditional_metadata.cpp b/src/backend/metadata/conditional_metadata.cpp new file mode 100644 index 00000000..da7fdd71 --- /dev/null +++ b/src/backend/metadata/conditional_metadata.cpp @@ -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 + . + */ + +#include "conditional_metadata.h" +#include "condition_grammar.h" + +#include +#include + +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::const_iterator it = game.conditionCache.find(boost::locale::to_lower(_condition)); + if (it != game.conditionCache.end()) + return it->second; + + ConditionGrammar 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(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 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()); + } + } +} diff --git a/src/backend/metadata/conditional_metadata.h b/src/backend/metadata/conditional_metadata.h new file mode 100644 index 00000000..5bc0d716 --- /dev/null +++ b/src/backend/metadata/conditional_metadata.h @@ -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 + . + */ +#ifndef __LOOT_METADATA_CONDITIONAL_METADATA__ +#define __LOOT_METADATA_CONDITIONAL_METADATA__ + +#include + +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 diff --git a/src/backend/metadata/file.cpp b/src/backend/metadata/file.cpp new file mode 100644 index 00000000..41ab2b73 --- /dev/null +++ b/src/backend/metadata/file.cpp @@ -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 + . + */ + +#include "file.h" + +#include + +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; + } +} \ No newline at end of file diff --git a/src/backend/metadata/file.h b/src/backend/metadata/file.h new file mode 100644 index 00000000..5496603b --- /dev/null +++ b/src/backend/metadata/file.h @@ -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 + . + */ +#ifndef __LOOT_METADATA_FILE__ +#define __LOOT_METADATA_FILE__ + +#include "conditional_metadata.h" + +#include + +#include + +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(); + if (node["name"]) + name = node["name"].as(); + if (node["display"]) + display = node["display"].as(); + rhs = loot::File(name, display, condition); + } + else + rhs = loot::File(node.as()); + return true; + } + }; + + Emitter& operator << (Emitter& out, const loot::File& rhs); +} + +#endif diff --git a/src/backend/metadata/formid.cpp b/src/backend/metadata/formid.cpp new file mode 100644 index 00000000..50f623fc --- /dev/null +++ b/src/backend/metadata/formid.cpp @@ -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 + . + */ + +#include "formid.h" + +#include +#include + +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& 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; + } +} diff --git a/src/backend/metadata/formid.h b/src/backend/metadata/formid.h new file mode 100644 index 00000000..c4621735 --- /dev/null +++ b/src/backend/metadata/formid.h @@ -0,0 +1,54 @@ +/* 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 + . + */ +#ifndef __LOOT_METADATA_FORMID__ +#define __LOOT_METADATA_FORMID__ + +#include +#include +#include + +namespace loot { + // A FormID is a 32 bit unsigned integer of the form xxYYYYYY in hex. + // The xx is the position in the masters list of the plugin that the FormID + // is from, and the YYYYYY is the rest of the FormID. Here the xx bit is + // stored as the corresponding filename to allow comparison between FormIDs + // from different plugins. + class FormID { + public: + FormID(); + FormID(const std::string& pluginName, const uint32_t objectID); + FormID(const std::vector& masters, const uint32_t formID); //The masters here also includes the plugin that they are masters of as the last element. + + bool operator < (const FormID& rhs) const; + bool operator == (const FormID& rhs) const; + + std::string Plugin() const; + uint32_t Id() const; + private: + std::string plugin; + uint32_t id; + }; +} + +#endif diff --git a/src/backend/metadata/location.cpp b/src/backend/metadata/location.cpp new file mode 100644 index 00000000..e9bd6bf0 --- /dev/null +++ b/src/backend/metadata/location.cpp @@ -0,0 +1,63 @@ +/* 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 + . + */ + +#include "location.h" + +#include + +using namespace std; + +namespace loot { + Location::Location() {} + + Location::Location(const std::string& url) : _url(url) {} + + Location::Location(const std::string& url, const std::vector& versions) : _url(url), _versions(versions) {} + + bool Location::operator < (const Location& rhs) const { + return boost::ilexicographical_compare(_url, rhs.URL()); + } + + std::string Location::URL() const { + return _url; + } + + std::vector Location::Versions() const { + return _versions; + } +} + +namespace YAML { + 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; + } +} diff --git a/src/backend/metadata/location.h b/src/backend/metadata/location.h new file mode 100644 index 00000000..9fc408f8 --- /dev/null +++ b/src/backend/metadata/location.h @@ -0,0 +1,86 @@ +/* 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 + . + */ +#ifndef __LOOT_METADATA_LOCATION__ +#define __LOOT_METADATA_LOCATION__ + +#include +#include + +#include + +namespace loot { + class Location { + public: + Location(); + Location(const std::string& url); + Location(const std::string& url, const std::vector& versions); + + bool operator < (const Location& rhs) const; + + std::string URL() const; + std::vector Versions() const; + private: + std::string _url; + std::vector _versions; + }; +} + +namespace YAML { + template<> + struct convert < loot::Location > { + static Node encode(const loot::Location& rhs) { + Node node; + + node["link"] = rhs.URL(); + node["ver"] = rhs.Versions(); + + return node; + } + + static bool decode(const Node& node, loot::Location& rhs) { + std::string url; + std::vector versions; + + if (node.IsMap()) { + if (!node["link"] || !node["ver"]) + return false; + + if (node["link"]) + url = node["link"].as(); + if (node["ver"]) + versions = node["ver"].as>(); + } + else if (node.IsScalar()) + url = node.as(); + + rhs = loot::Location(url, versions); + + return true; + } + }; + + Emitter& operator << (Emitter& out, const loot::Location& rhs); +} + +#endif diff --git a/src/backend/metadata/message.cpp b/src/backend/metadata/message.cpp new file mode 100644 index 00000000..f6f046c5 --- /dev/null +++ b/src/backend/metadata/message.cpp @@ -0,0 +1,135 @@ +/* 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 + . + */ + +#include "message.h" +#include "../language.h" + +#include + +using namespace std; + +namespace loot { + Message::Message() : _type(Message::say) {} + + Message::Message(const unsigned int type, const std::string& content, + const std::string& condition) : _type(type), ConditionalMetadata(condition) { + _content.push_back(MessageContent(content, Language::english)); + } + + Message::Message(const unsigned int type, const std::vector& content, + const std::string& condition) : _type(type), _content(content), ConditionalMetadata(condition) {} + + bool Message::operator < (const Message& rhs) const { + if (!_content.empty() && !rhs.Content().empty()) + return boost::ilexicographical_compare(_content.front().Str(), rhs.Content().front().Str()); + else if (_content.empty()) + return true; + else + return false; + } + + bool Message::operator == (const Message& rhs) const { + return (_type == rhs.Type() && _content == rhs.Content()); + } + + bool Message::EvalCondition(loot::Game& game, const unsigned int language) { + BOOST_LOG_TRIVIAL(trace) << "Choosing message content for language: " << Language(language).Name(); + + if (_content.size() > 1) { + if (language == Language::any) //Can use a message of any language, so use the first string. + _content.resize(1); + else { + MessageContent english, match; + for (const auto &mc : _content) { + if (mc.Language() == language) { + match = mc; + break; + } + else if (mc.Language() == Language::english) + english = mc; + } + _content.resize(1); + if (!match.Str().empty()) + _content[0] = match; + else + _content[0] = english; + } + } + return ConditionalMetadata::EvalCondition(game); + } + + MessageContent Message::ChooseContent(const unsigned int language) const { + BOOST_LOG_TRIVIAL(trace) << "Choosing message content."; + if (_content.size() == 1 || language == Language::any) + return _content[0]; + else { + MessageContent english, match; + for (const auto &mc : _content) { + if (mc.Language() == language) { + match = mc; + break; + } + else if (mc.Language() == Language::english) + english = mc; + } + if (!match.Str().empty()) + return match; + else + return english; + } + } + + unsigned int Message::Type() const { + return _type; + } + + std::vector Message::Content() const { + return _content; + } +} + +namespace YAML { + 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; + } +} \ No newline at end of file diff --git a/src/backend/metadata/message.h b/src/backend/metadata/message.h new file mode 100644 index 00000000..b2319e6b --- /dev/null +++ b/src/backend/metadata/message.h @@ -0,0 +1,147 @@ +/* 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 + . + */ +#ifndef __LOOT_METADATA_MESSAGE__ +#define __LOOT_METADATA_MESSAGE__ + +#include "conditional_metadata.h" +#include "message_content.h" +#include "../language.h" + +#include +#include + +#include +#include + +#include + +namespace loot { + class Game; + + class Message : public ConditionalMetadata { + public: + Message(); + Message(const unsigned int type, const std::string& content, + const std::string& condition = ""); + Message(const unsigned int type, const std::vector& content, + const std::string& condition = ""); + + bool operator < (const Message& rhs) const; + bool operator == (const Message& rhs) const; + + bool EvalCondition(Game& game, const unsigned int language); + + unsigned int Type() const; + std::vector Content() const; + MessageContent ChooseContent(const unsigned int language) const; + + static const unsigned int say = 0; + static const unsigned int warn = 1; + static const unsigned int error = 2; + private: + unsigned int _type; + std::vector _content; + }; +} + +namespace YAML { + template<> + struct convert < loot::Message > { + static Node encode(const loot::Message& rhs) { + Node node; + node["condition"] = rhs.Condition(); + node["content"] = rhs.Content(); + + if (rhs.Type() == loot::Message::say) + node["type"] = "say"; + else if (rhs.Type() == loot::Message::warn) + node["type"] = "warn"; + else + node["type"] = "error"; + + return node; + } + + static bool decode(const Node& node, loot::Message& rhs) { + if (!node.IsMap() || !node["type"] || !node["content"]) + return false; + + unsigned int typeNo = loot::Message::say; + if (node["type"]) { + std::string type; + type = node["type"].as(); + + if (boost::iequals(type, "say")) + typeNo = loot::Message::say; + else if (boost::iequals(type, "warn")) + typeNo = loot::Message::warn; + else + typeNo = loot::Message::error; + } + + std::vector content; + if (node["content"].IsSequence()) + content = node["content"].as< std::vector >(); + else { + content.push_back(loot::MessageContent(node["content"].as(), loot::Language::english)); + } + + //Check now that at least one item in content is English if there are multiple items. + if (content.size() > 1) { + bool found = false; + for (const auto &mc : content) { + if (mc.Language() == loot::Language::english) + found = true; + } + if (!found) + return false; + } + + // Make any substitutions at this point. + if (node["subs"]) { + std::vector subs = node["subs"].as>(); + for (auto& mc : content) { + boost::format f(mc.Str()); + + for (const auto& sub : subs) { + f = f % sub; + } + + mc = loot::MessageContent(f.str(), mc.Language()); + } + } + + std::string condition; + if (node["condition"]) + condition = node["condition"].as(); + + rhs = loot::Message(typeNo, content, condition); + return true; + } + }; + + Emitter& operator << (Emitter& out, const loot::Message& rhs); +} + +#endif diff --git a/src/backend/metadata/message_content.cpp b/src/backend/metadata/message_content.cpp new file mode 100644 index 00000000..024f8136 --- /dev/null +++ b/src/backend/metadata/message_content.cpp @@ -0,0 +1,66 @@ +/* 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 + . + */ + +#include "message_content.h" +#include "../language.h" + +#include + +using namespace std; + +namespace loot { + MessageContent::MessageContent() : _language(Language::english) {} + + MessageContent::MessageContent(const std::string& str, const unsigned int language) : _str(str), _language(language) {} + + std::string MessageContent::Str() const { + return _str; + } + + unsigned int MessageContent::Language() const { + return _language; + } + + bool MessageContent::operator < (const MessageContent& rhs) const { + return boost::ilexicographical_compare(_str, rhs.Str()); + } + + bool MessageContent::operator == (const MessageContent& rhs) const { + return (_language == rhs.Language() && boost::iequals(_str, rhs.Str())); + } +} + +namespace YAML { + 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; + } +} \ No newline at end of file diff --git a/src/backend/metadata/message_content.h b/src/backend/metadata/message_content.h new file mode 100644 index 00000000..eb48af35 --- /dev/null +++ b/src/backend/metadata/message_content.h @@ -0,0 +1,77 @@ +/* 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 + . + */ +#ifndef __LOOT_METADATA_MESSAGE_CONTENT__ +#define __LOOT_METADATA_MESSAGE_CONTENT__ + +#include "../language.h" + +#include + +#include + +namespace loot { + class MessageContent { + public: + MessageContent(); + MessageContent(const std::string& str, const unsigned int language); + + std::string Str() const; + unsigned int Language() const; + + bool operator < (const MessageContent& rhs) const; + bool operator == (const MessageContent& rhs) const; + private: + std::string _str; + unsigned int _language; + }; +} + +namespace YAML { + template<> + struct convert < loot::MessageContent > { + static Node encode(const loot::MessageContent& rhs) { + Node node; + node["str"] = rhs.Str(); + node["lang"] = loot::Language(rhs.Language()).Locale(); + + return node; + } + + static bool decode(const Node& node, loot::MessageContent& rhs) { + if (!node.IsMap() || !node["str"] || !node["lang"]) + return false; + + std::string str = node["str"].as(); + unsigned int lang = loot::Language(node["lang"].as()).Code(); + + rhs = loot::MessageContent(str, lang); + + return true; + } + }; + + Emitter& operator << (Emitter& out, const loot::MessageContent& rhs); +} + +#endif diff --git a/src/backend/metadata/plugin_dirty_info.cpp b/src/backend/metadata/plugin_dirty_info.cpp new file mode 100644 index 00000000..b0ad1056 --- /dev/null +++ b/src/backend/metadata/plugin_dirty_info.cpp @@ -0,0 +1,104 @@ +/* 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 + . + */ + +#include "plugin_dirty_info.h" + +#include +#include + +using namespace std; + +namespace loot { + PluginDirtyInfo::PluginDirtyInfo() : _crc(0), _itm(0), _ref(0), _nav(0) {} + + PluginDirtyInfo::PluginDirtyInfo(uint32_t crc, unsigned int itm, unsigned int ref, unsigned int nav, const std::string& utility) : _crc(crc), _itm(itm), _ref(ref), _nav(nav), _utility(utility) {} + + bool PluginDirtyInfo::operator < (const PluginDirtyInfo& rhs) const { + return _crc < rhs.CRC(); + } + + uint32_t PluginDirtyInfo::CRC() const { + return _crc; + } + + unsigned int PluginDirtyInfo::ITMs() const { + return _itm; + } + + unsigned int PluginDirtyInfo::DeletedRefs() const { + return _ref; + } + + unsigned int PluginDirtyInfo::DeletedNavmeshes() const { + return _nav; + } + + std::string PluginDirtyInfo::CleaningUtility() const { + return _utility; + } + + Message PluginDirtyInfo::AsMessage() const { + boost::format f; + if (this->_itm > 0 && this->_ref > 0 && this->_nav > 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records, %2% deleted references and %3% deleted navmeshes. Clean with %4%.")) % this->_itm % this->_ref % this->_nav % this->_utility; + else if (this->_itm == 0 && this->_ref == 0 && this->_nav == 0) + f = boost::format(boost::locale::translate("Clean with %1%.")) % this->_utility; + + else if (this->_itm == 0 && this->_ref > 0 && this->_nav > 0) + f = boost::format(boost::locale::translate("Contains %1% deleted references and %2% deleted navmeshes. Clean with %3%.")) % this->_ref % this->_nav % this->_utility; + else if (this->_itm == 0 && this->_ref == 0 && this->_nav > 0) + f = boost::format(boost::locale::translate("Contains %1% deleted navmeshes. Clean with %2%.")) % this->_nav % this->_utility; + else if (this->_itm == 0 && this->_ref > 0 && this->_nav == 0) + f = boost::format(boost::locale::translate("Contains %1% deleted references. Clean with %2%.")) % this->_ref % this->_utility; + + else if (this->_itm > 0 && this->_ref == 0 && this->_nav > 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted navmeshes. Clean with %3%.")) % this->_itm % this->_nav % this->_utility; + else if (this->_itm > 0 && this->_ref == 0 && this->_nav == 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records. Clean with %2%.")) % this->_itm % this->_utility; + + else if (this->_itm > 0 && this->_ref > 0 && this->_nav == 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted references. Clean with %3%.")) % this->_itm % this->_ref % this->_utility; + + return Message(Message::warn, f.str()); + } +} + +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; + } +} \ No newline at end of file diff --git a/src/backend/metadata/plugin_dirty_info.h b/src/backend/metadata/plugin_dirty_info.h new file mode 100644 index 00000000..8d7a1659 --- /dev/null +++ b/src/backend/metadata/plugin_dirty_info.h @@ -0,0 +1,101 @@ +/* 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 + . + */ +#ifndef __LOOT_METADATA_PLUGIN_DIRTY_INFO__ +#define __LOOT_METADATA_PLUGIN_DIRTY_INFO__ + +#include "message.h" + +#include +#include + +#include + +namespace loot { + class PluginDirtyInfo { + public: + PluginDirtyInfo(); + PluginDirtyInfo(uint32_t crc, unsigned int itm, unsigned int ref, unsigned int nav, const std::string& utility); + + bool operator < (const PluginDirtyInfo& rhs) const; + + uint32_t CRC() const; + unsigned int ITMs() const; + unsigned int DeletedRefs() const; + unsigned int DeletedNavmeshes() const; + std::string CleaningUtility() const; + + Message AsMessage() const; + private: + uint32_t _crc; + unsigned int _itm; + unsigned int _ref; + unsigned int _nav; + std::string _utility; + }; +} + +namespace YAML { + template<> + struct convert < loot::PluginDirtyInfo > { + static Node encode(const loot::PluginDirtyInfo& rhs) { + Node node; + node["crc"] = rhs.CRC(); + node["util"] = rhs.CleaningUtility(); + + if (rhs.ITMs() > 0) + node["itm"] = rhs.ITMs(); + if (rhs.DeletedRefs() > 0) + node["udr"] = rhs.DeletedRefs(); + if (rhs.DeletedNavmeshes() > 0) + node["nav"] = rhs.DeletedNavmeshes(); + + return node; + } + + static bool decode(const Node& node, loot::PluginDirtyInfo& rhs) { + if (!node.IsMap() || !node["crc"] || !node["util"]) + return false; + + uint32_t crc = node["crc"].as(); + int itm = 0, ref = 0, nav = 0; + + if (node["itm"]) + itm = node["itm"].as(); + if (node["udr"]) + ref = node["udr"].as(); + if (node["nav"]) + nav = node["nav"].as(); + + std::string utility = node["util"].as(); + + rhs = loot::PluginDirtyInfo(crc, itm, ref, nav, utility); + + return true; + } + }; + + Emitter& operator << (Emitter& out, const loot::PluginDirtyInfo& rhs); +} + +#endif diff --git a/src/backend/metadata/tag.cpp b/src/backend/metadata/tag.cpp new file mode 100644 index 00000000..2c3149df --- /dev/null +++ b/src/backend/metadata/tag.cpp @@ -0,0 +1,77 @@ +/* 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 + . + */ + +#include "tag.h" + +#include + +using namespace std; + +namespace loot { + Tag::Tag() : addTag(true) {} + + Tag::Tag(const string& tag, const bool isAddition, const string& condition) : _name(tag), addTag(isAddition), ConditionalMetadata(condition) {} + + bool Tag::operator < (const Tag& rhs) const { + if (addTag != rhs.IsAddition()) + return (addTag && !rhs.IsAddition()); + else + return boost::ilexicographical_compare(Name(), rhs.Name()); + } + + bool Tag::operator == (const Tag& rhs) const { + return (addTag == rhs.IsAddition() && boost::iequals(Name(), rhs.Name())); + } + + bool Tag::IsAddition() const { + return addTag; + } + + std::string Tag::Name() const { + return _name; + } +} + +namespace YAML { + 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; + } +} \ No newline at end of file diff --git a/src/backend/metadata/tag.h b/src/backend/metadata/tag.h new file mode 100644 index 00000000..e9908d9c --- /dev/null +++ b/src/backend/metadata/tag.h @@ -0,0 +1,89 @@ +/* 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 + . + */ +#ifndef __LOOT_METADATA_TAG__ +#define __LOOT_METADATA_TAG__ + +#include "conditional_metadata.h" + +#include + +#include + +namespace loot { + class Tag : public ConditionalMetadata { + public: + Tag(); + Tag(const std::string& tag, const bool isAddition = true, const std::string& condition = ""); + + bool operator < (const Tag& rhs) const; + bool operator == (const Tag& rhs) const; + + bool IsAddition() const; + std::string Name() const; + private: + std::string _name; + bool addTag; + }; +} + +namespace YAML { + template<> + struct convert < loot::Tag > { + static Node encode(const loot::Tag& rhs) { + Node node; + node["condition"] = rhs.Condition(); + if (rhs.IsAddition()) + node["name"] = rhs.Name(); + else + node["name"] = "-" + rhs.Name(); + return node; + } + + static bool decode(const Node& node, loot::Tag& rhs) { + std::string condition, tag; + if (node.IsMap()) { + if (!node["name"]) + return false; + + if (node["condition"]) + condition = node["condition"].as(); + if (node["name"]) + tag = node["name"].as(); + } + else if (node.IsScalar()) + tag = node.as(); + + if (tag[0] == '-') + rhs = loot::Tag(tag.substr(1), false, condition); + else + rhs = loot::Tag(tag, true, condition); + + return true; + } + }; + + Emitter& operator << (Emitter& out, const loot::Tag& rhs); +} + +#endif diff --git a/src/backend/metadata_list.cpp b/src/backend/metadata_list.cpp new file mode 100644 index 00000000..901c305f --- /dev/null +++ b/src/backend/metadata_list.cpp @@ -0,0 +1,186 @@ +/* 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 + . + */ + +#include "metadata_list.h" +#include "globals.h" +#include "error.h" +#include "streams.h" + +#include +#include + +using namespace std; + +namespace loot { + 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()); + if (plugin.IsRegexPlugin()) + regexPlugins.push_back(plugin); + else + plugins.insert(plugin); + } + } + if (metadataList["globals"]) + messages = metadataList["globals"].as< list >(); + + 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 MetadataList::Plugins() const { + list 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 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); + } + } +} diff --git a/src/backend/metadata_list.h b/src/backend/metadata_list.h new file mode 100644 index 00000000..0c392bb0 --- /dev/null +++ b/src/backend/metadata_list.h @@ -0,0 +1,77 @@ +/* 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 + . + */ + +#ifndef __LOOT_METADATA_LIST__ +#define __LOOT_METADATA_LIST__ + +#include "plugin.h" + +#include +#include +#include + +#include + +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 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 messages; + protected: + std::unordered_set plugins; + std::list regexPlugins; + }; +} + +#endif diff --git a/src/backend/parsers.h b/src/backend/parsers.h deleted file mode 100644 index fcc979d3..00000000 --- a/src/backend/parsers.h +++ /dev/null @@ -1,747 +0,0 @@ -/* 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 - . - */ - -#ifndef __LOOT_PARSERS__ -#define __LOOT_PARSERS__ - -#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 "metadata.h" -#include "helpers.h" -#include "error.h" - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace YAML { - /////////////////////// - // Parser - /////////////////////// - - 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() == loot::Game(loot::Game::tes4).FolderName()) - rhs = loot::Game(loot::Game::tes4, node["folder"].as()); - else if (node["type"].as() == loot::Game(loot::Game::tes5).FolderName()) - rhs = loot::Game(loot::Game::tes5, node["folder"].as()); - else if (node["type"].as() == loot::Game(loot::Game::fo3).FolderName()) - rhs = loot::Game(loot::Game::fo3, node["folder"].as()); - else if (node["type"].as() == loot::Game(loot::Game::fonv).FolderName()) - rhs = loot::Game(loot::Game::fonv, node["folder"].as()); - else - return false; - - std::string name, master, repo, branch, path, registry; - if (node["name"]) - name = node["name"].as(); - if (node["master"]) - master = node["master"].as(); - if (node["repo"]) - repo = node["repo"].as(); - if (node["branch"]) - branch = node["branch"].as(); - if (node["path"]) - path = node["path"].as(); - if (node["registry"]) - registry = node["registry"].as(); - - rhs.SetDetails(name, master, repo, branch, path, registry); - - return true; - } - }; - - template<> - struct convert < loot::PluginDirtyInfo > { - static Node encode(const loot::PluginDirtyInfo& rhs) { - Node node; - node["crc"] = rhs.CRC(); - node["util"] = rhs.CleaningUtility(); - - if (rhs.ITMs() > 0) - node["itm"] = rhs.ITMs(); - if (rhs.DeletedRefs() > 0) - node["udr"] = rhs.DeletedRefs(); - if (rhs.DeletedNavmeshes() > 0) - node["nav"] = rhs.DeletedNavmeshes(); - - return node; - } - - static bool decode(const Node& node, loot::PluginDirtyInfo& rhs) { - if (!node.IsMap() || !node["crc"] || !node["util"]) - return false; - - uint32_t crc = node["crc"].as(); - int itm = 0, ref = 0, nav = 0; - - if (node["itm"]) - itm = node["itm"].as(); - if (node["udr"]) - ref = node["udr"].as(); - if (node["nav"]) - nav = node["nav"].as(); - - std::string utility = node["util"].as(); - - rhs = loot::PluginDirtyInfo(crc, itm, ref, nav, utility); - - return true; - } - }; - - template<> - struct convert < loot::MessageContent > { - static Node encode(const loot::MessageContent& rhs) { - Node node; - node["str"] = rhs.Str(); - node["lang"] = loot::Language(rhs.Language()).Locale(); - - return node; - } - - static bool decode(const Node& node, loot::MessageContent& rhs) { - if (!node.IsMap() || !node["str"] || !node["lang"]) - return false; - - std::string str = node["str"].as(); - unsigned int lang = loot::Language(node["lang"].as()).Code(); - - rhs = loot::MessageContent(str, lang); - - return true; - } - }; - - template<> - struct convert < loot::Message > { - static Node encode(const loot::Message& rhs) { - Node node; - node["condition"] = rhs.Condition(); - node["content"] = rhs.Content(); - - if (rhs.Type() == loot::Message::say) - node["type"] = "say"; - else if (rhs.Type() == loot::Message::warn) - node["type"] = "warn"; - else - node["type"] = "error"; - - return node; - } - - static bool decode(const Node& node, loot::Message& rhs) { - if (!node.IsMap() || !node["type"] || !node["content"]) - return false; - - unsigned int typeNo = loot::Message::say; - if (node["type"]) { - std::string type; - type = node["type"].as(); - - if (boost::iequals(type, "say")) - typeNo = loot::Message::say; - else if (boost::iequals(type, "warn")) - typeNo = loot::Message::warn; - else - typeNo = loot::Message::error; - } - - std::vector content; - if (node["content"].IsSequence()) - content = node["content"].as< std::vector >(); - else { - content.push_back(loot::MessageContent(node["content"].as(), loot::Language::english)); - } - - //Check now that at least one item in content is English if there are multiple items. - if (content.size() > 1) { - bool found = false; - for (const auto &mc : content) { - if (mc.Language() == loot::Language::english) - found = true; - } - if (!found) - return false; - } - - // Make any substitutions at this point. - if (node["subs"]) { - std::vector subs = node["subs"].as>(); - for (auto& mc : content) { - boost::format f(mc.Str()); - - for (const auto& sub : subs) { - f = f % sub; - } - - mc = loot::MessageContent(f.str(), mc.Language()); - } - } - - std::string condition; - if (node["condition"]) - condition = node["condition"].as(); - - rhs = loot::Message(typeNo, content, condition); - return true; - } - }; - - 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(); - if (node["name"]) - name = node["name"].as(); - if (node["display"]) - display = node["display"].as(); - rhs = loot::File(name, display, condition); - } - else - rhs = loot::File(node.as()); - return true; - } - }; - - template<> - struct convert < loot::Tag > { - static Node encode(const loot::Tag& rhs) { - Node node; - node["condition"] = rhs.Condition(); - if (rhs.IsAddition()) - node["name"] = rhs.Name(); - else - node["name"] = "-" + rhs.Name(); - return node; - } - - static bool decode(const Node& node, loot::Tag& rhs) { - std::string condition, tag; - if (node.IsMap()) { - if (!node["name"]) - return false; - - if (node["condition"]) - condition = node["condition"].as(); - if (node["name"]) - tag = node["name"].as(); - } - else if (node.IsScalar()) - tag = node.as(); - - if (tag[0] == '-') - rhs = loot::Tag(tag.substr(1), false, condition); - else - rhs = loot::Tag(tag, true, condition); - - return true; - } - }; - - template<> - struct convert < loot::Location > { - static Node encode(const loot::Location& rhs) { - Node node; - - node["link"] = rhs.URL(); - node["ver"] = rhs.Versions(); - - return node; - } - - static bool decode(const Node& node, loot::Location& rhs) { - std::string url; - std::vector versions; - - if (node.IsMap()) { - if (!node["link"] || !node["ver"]) - return false; - - if (node["link"]) - url = node["link"].as(); - if (node["ver"]) - versions = node["ver"].as>(); - } - else if (node.IsScalar()) - url = node.as(); - - rhs = loot::Location(url, versions); - - return true; - } - }; - - template - struct convert < std::set > { - static Node encode(const std::set& rhs) { - Node node; - for (const auto &element : rhs) { - node.push_back(element); - } - return node; - } - - static bool decode(const Node& node, std::set& rhs) { - if (!node.IsSequence()) - return false; - - rhs.clear(); - for (const auto &element : node) { - rhs.insert(element.template as()); - } - return true; - } - }; - - template - struct convert < std::unordered_set > { - static Node encode(const std::unordered_set& rhs) { - Node node; - for (const auto &element : rhs) { - node.push_back(element); - } - return node; - } - - static bool decode(const Node& node, std::unordered_set& rhs) { - if (!node.IsSequence()) - return false; - - rhs.clear(); - for (const auto &element : node) { - rhs.insert(element.template as()); - } - return true; - } - }; - - template<> - struct convert < loot::Plugin > { - static Node encode(const loot::Plugin& rhs) { - Node node; - node["name"] = rhs.Name(); - node["enabled"] = rhs.Enabled(); - node["priority"] = rhs.Priority(); - node["after"] = rhs.LoadAfter(); - node["req"] = rhs.Reqs(); - node["inc"] = rhs.Incs(); - node["msg"] = rhs.Messages(); - node["tag"] = rhs.Tags(); - node["dirty"] = rhs.DirtyInfo(); - node["url"] = rhs.Locations(); - - return node; - } - - static bool decode(const Node& node, loot::Plugin& rhs) { - if (!node.IsMap() || !node["name"]) - return false; - - rhs = loot::Plugin(node["name"].as()); - - if (node["enabled"]) - rhs.Enabled(node["enabled"].as()); - - if (node["priority"]) { - rhs.Priority(node["priority"].as()); - rhs.SetPriorityExplicit(true); - } - - if (node["after"]) - rhs.LoadAfter(node["after"].as< std::set >()); - if (node["req"]) - rhs.Reqs(node["req"].as< std::set >()); - if (node["inc"]) - rhs.Incs(node["inc"].as< std::set >()); - if (node["msg"]) - rhs.Messages(node["msg"].as< std::list >()); - if (node["tag"]) - rhs.Tags(node["tag"].as< std::set >()); - if (node["dirty"]) { - if (rhs.IsRegexPlugin()) - return false; - else - rhs.DirtyInfo(node["dirty"].as< std::set >()); - } - if (node["url"]) - rhs.Locations(node["url"].as< std::set >()); - - return true; - } - }; -} - -namespace loot { - /////////////////////////////// - // Condition parser/evaluator - /////////////////////////////// - - namespace qi = boost::spirit::qi; - namespace unicode = boost::spirit::unicode; - namespace phoenix = boost::phoenix; - - template - class condition_grammar : public qi::grammar < Iterator, bool(), Skipper > { - public: - condition_grammar(Game * game, bool parseOnly) : condition_grammar::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(&condition_grammar::CheckFile, this, qi::labels::_val, qi::labels::_1)] - | ("regex(" > quotedStr > ')')[phoenix::bind(&condition_grammar::CheckRegex, this, qi::labels::_val, qi::labels::_1)] - | ("checksum(" > filePath > ',' > qi::hex > ')')[phoenix::bind(&condition_grammar::CheckSum, this, qi::labels::_val, qi::labels::_1, qi::labels::_2)] - | ("version(" > filePath > ',' > quotedStr > ',' > comparator > ')')[phoenix::bind(&condition_grammar::CheckVersion, this, qi::labels::_val, qi::labels::_1, qi::labels::_2, qi::labels::_3)] - | ("active(" > filePath > ')')[phoenix::bind(&condition_grammar::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(expression, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(compound, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(condition, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(function, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(quotedStr, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(filePath, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(comparator, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(invalidPathChars, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - } - - private: - qi::rule expression, compound, condition, function; - qi::rule quotedStr, filePath, comparator; - qi::rule 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 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::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::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(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 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 diff --git a/src/backend/metadata.cpp b/src/backend/plugin.cpp similarity index 67% rename from src/backend/metadata.cpp rename to src/backend/plugin.cpp index 0aacb537..2a021e3d 100644 --- a/src/backend/metadata.cpp +++ b/src/backend/plugin.cpp @@ -22,16 +22,16 @@ . */ +#include "plugin.h" +#include "game.h" #include "helpers.h" -#include "metadata.h" -#include "parsers.h" -#include "streams.h" #include #include #include #include +#include #include #include @@ -42,336 +42,8 @@ using boost::regex_search; using boost::smatch; namespace loot { - namespace lc = boost::locale; - - FormID::FormID() : id(0) {} - - FormID::FormID(const std::string& sourcePlugin, const uint32_t objectID) : plugin(sourcePlugin), id(objectID) {} - - FormID::FormID(const std::vector& 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; - } - - ConditionStruct::ConditionStruct() {} - - ConditionStruct::ConditionStruct(const string& condition) : _condition(condition) {} - - bool ConditionStruct::IsConditional() const { - return !_condition.empty(); - } - - std::string ConditionStruct::Condition() const { - return _condition; - } - - bool ConditionStruct::EvalCondition(Game& game) const { - if (_condition.empty()) - return true; - - BOOST_LOG_TRIVIAL(trace) << "Evaluating condition: " << _condition; - - unordered_map::const_iterator it = game.conditionCache.find(boost::locale::to_lower(_condition)); - if (it != game.conditionCache.end()) - return it->second; - - condition_grammar 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(boost::locale::to_lower(_condition), eval)); - - return eval; - } - - void ConditionStruct::ParseCondition() const { - if (_condition.empty()) - return; - - BOOST_LOG_TRIVIAL(trace) << "Testing condition syntax: " << _condition; - - condition_grammar 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()); - } - } - - MessageContent::MessageContent() : _language(Language::english) {} - - MessageContent::MessageContent(const std::string& str, const unsigned int language) : _str(str), _language(language) {} - - std::string MessageContent::Str() const { - return _str; - } - - unsigned int MessageContent::Language() const { - return _language; - } - - bool MessageContent::operator < (const MessageContent& rhs) const { - return boost::ilexicographical_compare(_str, rhs.Str()); - } - - bool MessageContent::operator == (const MessageContent& rhs) const { - return (_language == rhs.Language() && boost::iequals(_str, rhs.Str())); - } - - Message::Message() : _type(Message::say) {} - - Message::Message(const unsigned int type, const std::string& content, - const std::string& condition) : _type(type), ConditionStruct(condition) { - _content.push_back(MessageContent(content, Language::english)); - } - - Message::Message(const unsigned int type, const std::vector& content, - const std::string& condition) : _type(type), _content(content), ConditionStruct(condition) {} - - bool Message::operator < (const Message& rhs) const { - if (!_content.empty() && !rhs.Content().empty()) - return boost::ilexicographical_compare(_content.front().Str(), rhs.Content().front().Str()); - else if (_content.empty()) - return true; - else - return false; - } - - bool Message::operator == (const Message& rhs) const { - return (_type == rhs.Type() && _content == rhs.Content()); - } - - bool Message::EvalCondition(loot::Game& game, const unsigned int language) { - BOOST_LOG_TRIVIAL(trace) << "Choosing message content for language: " << Language(language).Name(); - - if (_content.size() > 1) { - if (language == Language::any) //Can use a message of any language, so use the first string. - _content.resize(1); - else { - MessageContent english, match; - for (const auto &mc : _content) { - if (mc.Language() == language) { - match = mc; - break; - } - else if (mc.Language() == Language::english) - english = mc; - } - _content.resize(1); - if (!match.Str().empty()) - _content[0] = match; - else - _content[0] = english; - } - } - return ConditionStruct::EvalCondition(game); - } - - MessageContent Message::ChooseContent(const unsigned int language) const { - BOOST_LOG_TRIVIAL(trace) << "Choosing message content."; - if (_content.size() == 1 || language == Language::any) - return _content[0]; - else { - MessageContent english, match; - for (const auto &mc : _content) { - if (mc.Language() == language) { - match = mc; - break; - } - else if (mc.Language() == Language::english) - english = mc; - } - if (!match.Str().empty()) - return match; - else - return english; - } - } - - unsigned int Message::Type() const { - return _type; - } - - std::vector Message::Content() const { - return _content; - } - - PluginDirtyInfo::PluginDirtyInfo() : _crc(0), _itm(0), _ref(0), _nav(0) {} - - PluginDirtyInfo::PluginDirtyInfo(uint32_t crc, unsigned int itm, unsigned int ref, unsigned int nav, const std::string& utility) : _crc(crc), _itm(itm), _ref(ref), _nav(nav), _utility(utility) {} - - bool PluginDirtyInfo::operator < (const PluginDirtyInfo& rhs) const { - return _crc < rhs.CRC(); - } - - uint32_t PluginDirtyInfo::CRC() const { - return _crc; - } - - unsigned int PluginDirtyInfo::ITMs() const { - return _itm; - } - - unsigned int PluginDirtyInfo::DeletedRefs() const { - return _ref; - } - - unsigned int PluginDirtyInfo::DeletedNavmeshes() const { - return _nav; - } - - std::string PluginDirtyInfo::CleaningUtility() const { - return _utility; - } - - Message PluginDirtyInfo::AsMessage() const { - boost::format f; - if (this->_itm > 0 && this->_ref > 0 && this->_nav > 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records, %2% deleted references and %3% deleted navmeshes. Clean with %4%.")) % this->_itm % this->_ref % this->_nav % this->_utility; - else if (this->_itm == 0 && this->_ref == 0 && this->_nav == 0) - f = boost::format(boost::locale::translate("Clean with %1%.")) % this->_utility; - - else if (this->_itm == 0 && this->_ref > 0 && this->_nav > 0) - f = boost::format(boost::locale::translate("Contains %1% deleted references and %2% deleted navmeshes. Clean with %3%.")) % this->_ref % this->_nav % this->_utility; - else if (this->_itm == 0 && this->_ref == 0 && this->_nav > 0) - f = boost::format(boost::locale::translate("Contains %1% deleted navmeshes. Clean with %2%.")) % this->_nav % this->_utility; - else if (this->_itm == 0 && this->_ref > 0 && this->_nav == 0) - f = boost::format(boost::locale::translate("Contains %1% deleted references. Clean with %2%.")) % this->_ref % this->_utility; - - else if (this->_itm > 0 && this->_ref == 0 && this->_nav > 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted navmeshes. Clean with %3%.")) % this->_itm % this->_nav % this->_utility; - else if (this->_itm > 0 && this->_ref == 0 && this->_nav == 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records. Clean with %2%.")) % this->_itm % this->_utility; - - else if (this->_itm > 0 && this->_ref > 0 && this->_nav == 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted references. Clean with %3%.")) % this->_itm % this->_ref % this->_utility; - - return Message(Message::warn, f.str()); - } - - File::File() {} - File::File(const std::string& name, const std::string& display, const std::string& condition) - : _name(name), _display(display), ConditionStruct(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; - } - - Tag::Tag() : addTag(true) {} - - Tag::Tag(const string& tag, const bool isAddition, const string& condition) : _name(tag), addTag(isAddition), ConditionStruct(condition) {} - - bool Tag::operator < (const Tag& rhs) const { - if (addTag != rhs.IsAddition()) - return (addTag && !rhs.IsAddition()); - else - return boost::ilexicographical_compare(Name(), rhs.Name()); - } - - bool Tag::operator == (const Tag& rhs) const { - return (addTag == rhs.IsAddition() && boost::iequals(Name(), rhs.Name())); - } - - bool Tag::IsAddition() const { - return addTag; - } - - std::string Tag::Name() const { - return _name; - } - - Location::Location() {} - - Location::Location(const std::string& url) : _url(url) {} - - Location::Location(const std::string& url, const std::vector& versions) : _url(url), _versions(versions) {} - - bool Location::operator < (const Location& rhs) const { - return boost::ilexicographical_compare(_url, rhs.URL()); - } - - std::string Location::URL() const { - return _url; - } - - std::vector Location::Versions() const { - return _versions; - } - Plugin::Plugin() : enabled(true), _isPriorityExplicit(false), priority(0), isMaster(false), crc(0), numOverrideRecords(0) {} + Plugin::Plugin(const std::string& n) : name(n), enabled(true), _isPriorityExplicit(false), priority(0), isMaster(false), crc(0), numOverrideRecords(0) { //If the name passed ends in '.ghost', that should be trimmed. if (boost::iends_with(name, ".ghost")) @@ -974,3 +646,43 @@ namespace loot { return rhs == lhs; } } + +namespace YAML { + 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; + } +} \ No newline at end of file diff --git a/src/backend/metadata.h b/src/backend/plugin.h similarity index 55% rename from src/backend/metadata.h rename to src/backend/plugin.h index 4e50d3ba..d3210088 100644 --- a/src/backend/metadata.h +++ b/src/backend/plugin.h @@ -21,10 +21,16 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_METADATA__ -#define __LOOT_METADATA__ +#ifndef __LOOT_METADATA_PLUGIN__ +#define __LOOT_METADATA_PLUGIN__ -#include "globals.h" +#include "metadata/file.h" +#include "metadata/formid.h" +#include "metadata/location.h" +#include "metadata/message.h" +#include "metadata/plugin_dirty_info.h" +#include "metadata/tag.h" +#include "yaml_set_helpers.h" #include #include @@ -34,150 +40,13 @@ #include +#include + namespace loot { const unsigned int max_priority = 1000000; class Game; - //A FormID is a 32 bit unsigned integer of the form xxYYYYYY in hex. The xx is the position in the masters list of the plugin that the FormID is from, and the YYYYYY is the rest of the FormID. Here the xx bit is stored as the corresponding filename to allow comparison between FormIDs from different plugins. - class FormID { - public: - FormID(); - FormID(const std::string& pluginName, const uint32_t objectID); - FormID(const std::vector& masters, const uint32_t formID); //The masters here also includes the plugin that they are masters of as the last element. - - bool operator < (const FormID& rhs) const; - bool operator == (const FormID& rhs) const; - - std::string Plugin() const; - uint32_t Id() const; - private: - std::string plugin; - uint32_t id; - }; - - class ConditionStruct { - public: - ConditionStruct(); - ConditionStruct(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; - }; - - class MessageContent { - public: - MessageContent(); - MessageContent(const std::string& str, const unsigned int language); - - std::string Str() const; - unsigned int Language() const; - - bool operator < (const MessageContent& rhs) const; - bool operator == (const MessageContent& rhs) const; - private: - std::string _str; - unsigned int _language; - }; - - class Message : public ConditionStruct { - public: - Message(); - Message(const unsigned int type, const std::string& content, - const std::string& condition = ""); - Message(const unsigned int type, const std::vector& content, - const std::string& condition = ""); - - bool operator < (const Message& rhs) const; - bool operator == (const Message& rhs) const; - - bool EvalCondition(Game& game, const unsigned int language); - - unsigned int Type() const; - std::vector Content() const; - MessageContent ChooseContent(const unsigned int language) const; - - static const unsigned int say = 0; - static const unsigned int warn = 1; - static const unsigned int error = 2; - private: - unsigned int _type; - std::vector _content; - }; - - class PluginDirtyInfo { - public: - PluginDirtyInfo(); - PluginDirtyInfo(uint32_t crc, unsigned int itm, unsigned int ref, unsigned int nav, const std::string& utility); - - bool operator < (const PluginDirtyInfo& rhs) const; - - uint32_t CRC() const; - unsigned int ITMs() const; - unsigned int DeletedRefs() const; - unsigned int DeletedNavmeshes() const; - std::string CleaningUtility() const; - - Message AsMessage() const; - private: - uint32_t _crc; - unsigned int _itm; - unsigned int _ref; - unsigned int _nav; - std::string _utility; - }; - - class File : public ConditionStruct { - 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; - }; - - class Tag : public ConditionStruct { - public: - Tag(); - Tag(const std::string& tag, const bool isAddition = true, const std::string& condition = ""); - - bool operator < (const Tag& rhs) const; - bool operator == (const Tag& rhs) const; - - bool IsAddition() const; - std::string Name() const; - private: - std::string _name; - bool addTag; - }; - - class Location { - public: - Location(); - Location(const std::string& url); - Location(const std::string& url, const std::vector& versions); - - bool operator < (const Location& rhs) const; - - std::string URL() const; - std::vector Versions() const; - private: - std::string _url; - std::vector _versions; - }; - class Plugin { public: Plugin(); @@ -288,4 +157,63 @@ namespace std { }; } +namespace YAML { + template<> + struct convert < loot::Plugin > { + static Node encode(const loot::Plugin& rhs) { + Node node; + node["name"] = rhs.Name(); + node["enabled"] = rhs.Enabled(); + node["priority"] = rhs.Priority(); + node["after"] = rhs.LoadAfter(); + node["req"] = rhs.Reqs(); + node["inc"] = rhs.Incs(); + node["msg"] = rhs.Messages(); + node["tag"] = rhs.Tags(); + node["dirty"] = rhs.DirtyInfo(); + node["url"] = rhs.Locations(); + + return node; + } + + static bool decode(const Node& node, loot::Plugin& rhs) { + if (!node.IsMap() || !node["name"]) + return false; + + rhs = loot::Plugin(node["name"].as()); + + if (node["enabled"]) + rhs.Enabled(node["enabled"].as()); + + if (node["priority"]) { + rhs.Priority(node["priority"].as()); + rhs.SetPriorityExplicit(true); + } + + if (node["after"]) + rhs.LoadAfter(node["after"].as< std::set >()); + if (node["req"]) + rhs.Reqs(node["req"].as< std::set >()); + if (node["inc"]) + rhs.Incs(node["inc"].as< std::set >()); + if (node["msg"]) + rhs.Messages(node["msg"].as< std::list >()); + if (node["tag"]) + rhs.Tags(node["tag"].as< std::set >()); + if (node["dirty"]) { + if (rhs.IsRegexPlugin()) + return false; + else + rhs.DirtyInfo(node["dirty"].as< std::set >()); + } + if (node["url"]) + rhs.Locations(node["url"].as< std::set >()); + + return true; + } + }; + + Emitter& operator << (Emitter& out, const loot::Plugin& rhs); +} + #endif diff --git a/src/backend/version.cpp b/src/backend/version.cpp new file mode 100644 index 00000000..bb66f04a --- /dev/null +++ b/src/backend/version.cpp @@ -0,0 +1,164 @@ +/* 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 + . + */ + +#include "helpers.h" +#include "version.h" + +#include + +#include + +#include + +#ifdef _WIN32 +# ifndef UNICODE +# define UNICODE +# endif +# ifndef _UNICODE +# define _UNICODE +# endif +# include "windows.h" +# include "shlobj.h" +# include "shlwapi.h" +#endif + +namespace loot { + using namespace std; + using boost::regex; + using boost::regex_match; + + Version::Version() {} + + Version::Version(const std::string& ver) + : verString(ver) {} + + Version::Version(const boost::filesystem::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); + } + } diff --git a/src/backend/version.h b/src/backend/version.h new file mode 100644 index 00000000..5e28d173 --- /dev/null +++ b/src/backend/version.h @@ -0,0 +1,55 @@ +/* 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 + . + */ + +#ifndef __LOOT_VERSION__ +#define __LOOT_VERSION__ + +#include "plugin.h" + +#include +#include + +namespace loot { + //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 diff --git a/src/backend/generators.h b/src/backend/yaml_set_helpers.h similarity index 50% rename from src/backend/generators.h rename to src/backend/yaml_set_helpers.h index b9cae62e..de63209e 100644 --- a/src/backend/generators.h +++ b/src/backend/yaml_set_helpers.h @@ -3,7 +3,7 @@ A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and Fallout: New Vegas. - Copyright (C) 2013-2015 WrinklyNinja + Copyright (C) 2012-2015 WrinklyNinja This file is part of LOOT. @@ -21,19 +21,38 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_GENERATORS__ -#define __LOOT_GENERATORS__ -#include "metadata.h" +#ifndef __LOOT_YAML_SET_HELPERS__ +#define __LOOT_YAML_SET_HELPERS__ + +#include +#include #include -#include -#include -#include -#include - namespace YAML { + template + struct convert < std::set > { + static Node encode(const std::set& rhs) { + Node node; + for (const auto &element : rhs) { + node.push_back(element); + } + return node; + } + + static bool decode(const Node& node, std::set& rhs) { + if (!node.IsSequence()) + return false; + + rhs.clear(); + for (const auto &element : node) { + rhs.insert(element.template as()); + } + return true; + } + }; + template Emitter& operator << (Emitter& out, const std::set& rhs) { out << BeginSeq; @@ -45,6 +64,28 @@ namespace YAML { return out; } + template + struct convert < std::unordered_set > { + static Node encode(const std::unordered_set& rhs) { + Node node; + for (const auto &element : rhs) { + node.push_back(element); + } + return node; + } + + static bool decode(const Node& node, std::unordered_set& rhs) { + if (!node.IsSequence()) + return false; + + rhs.clear(); + for (const auto &element : node) { + rhs.insert(element.template as()); + } + return true; + } + }; + template Emitter& operator << (Emitter& out, const std::unordered_set& rhs) { out << BeginSeq; @@ -55,22 +96,6 @@ namespace YAML { return out; } - - Emitter& operator << (Emitter& out, const loot::PluginDirtyInfo& rhs); - - Emitter& operator << (Emitter& out, const loot::Game& rhs); - - Emitter& operator << (Emitter& out, const loot::MessageContent& rhs); - - Emitter& operator << (Emitter& out, const loot::Message& rhs); - - Emitter& operator << (Emitter& out, const loot::File& rhs); - - Emitter& operator << (Emitter& out, const loot::Tag& rhs); - - Emitter& operator << (Emitter& out, const loot::Location& rhs); - - Emitter& operator << (Emitter& out, const loot::Plugin& rhs); } #endif diff --git a/src/gui/handler.cpp b/src/gui/handler.cpp index 92f0ffd7..908bd56f 100644 --- a/src/gui/handler.cpp +++ b/src/gui/handler.cpp @@ -24,11 +24,13 @@ #include "handler.h" #include "resource.h" -#include "app.h" +#include "loot_app.h" +#include "loot_state.h" +#include "../backend/error.h" +#include "../backend/globals.h" +#include "../backend/helpers.h" #include "../backend/json.h" -#include "../backend/parsers.h" -#include "../backend/generators.h" #include #include @@ -53,13 +55,6 @@ namespace fs = boost::filesystem; namespace loc = boost::locale; namespace loot { - namespace { - LootHandler * g_instance = NULL; - } - - // WinHandler methods - //------------------- - Handler::Handler() {} // Called due to cefQuery execution in binding.html. @@ -1077,235 +1072,4 @@ namespace loot { BOOST_LOG_TRIVIAL(trace) << "Sending progress update: " << message; frame->ExecuteJavaScript("showProgress('" + message + "');", frame->GetURL(), 0); } - - // LootHandler methods - //-------------------- - - LootHandler::LootHandler() : is_closing_(false) { - assert(!g_instance); - g_instance = this; - } - - LootHandler::~LootHandler() { - g_instance = NULL; - } - - LootHandler* LootHandler::GetInstance() { - return g_instance; - } - - // CefClient methods - //------------------ - - CefRefPtr LootHandler::GetDisplayHandler() { - return this; - } - - CefRefPtr LootHandler::GetLifeSpanHandler() { - return this; - } - - CefRefPtr LootHandler::GetLoadHandler() { - return this; - } - - bool LootHandler::OnProcessMessageReceived(CefRefPtr browser, - CefProcessId source_process, - CefRefPtr message) { - return browser_side_router_->OnProcessMessageReceived(browser, source_process, message); - } - - // CefLifeSpanHandler methods - //--------------------------- - - void LootHandler::OnAfterCreated(CefRefPtr browser) { - assert(CefCurrentlyOn(TID_UI)); - -#ifdef _WIN32 - // Set the title bar icon. - HWND hWnd = browser->GetHost()->GetWindowHandle(); - HANDLE hIcon = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(MAINICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); - HANDLE hIconSm = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(MAINICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); - SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); - SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm); - - // Set the window title. - SetWindowText(hWnd, L"LOOT"); -#endif - - // Set window size & position. - YAML::Node settings = g_app_state.GetSettings(); - - if (settings["window"]["left"] && settings["window"]["top"] && settings["window"]["right"] && settings["window"]["bottom"]) { -#ifdef _WIN32 - RECT rc; - rc.left = settings["window"]["left"].as(); - rc.top = settings["window"]["top"].as(); - rc.right = settings["window"]["right"].as(); - rc.bottom = settings["window"]["bottom"].as(); - - // Fit the saved window size/position to the current monitor setup. - - // Get the nearest monitor to the saved size/pos. - HMONITOR hMonitor; - hMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST); - - // Get the rect for the monitor's working area. - MONITORINFO mi; - mi.cbSize = sizeof(mi); - GetMonitorInfo(hMonitor, &mi); - - // Clip the saved rect to fit inside the monitor rect. - int width = rc.right - rc.left; - int height = rc.bottom - rc.top; - rc.left = max(mi.rcWork.left, min(mi.rcWork.right - width, rc.left)); - rc.top = max(mi.rcWork.top, min(mi.rcWork.bottom - height, rc.top)); - rc.right = rc.left + width; - rc.bottom = rc.top + height; - - SetWindowPos(hWnd, HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW); -#endif - } - else { -#ifdef _WIN32 - // High DPI support doesn't seem to scale window content correctly - // unless the window is resized, so if no size info is recorded, - // just set its current size + 1. - RECT rc; - GetWindowRect(browser->GetHost()->GetWindowHandle(), &rc); - SetWindowPos(browser->GetHost()->GetWindowHandle(), HWND_TOP, rc.left, rc.top, rc.right - rc.left + 1, rc.bottom - rc.top + 1, SWP_SHOWWINDOW); -#endif - } - - // Add to the list of existing browsers. - browser_list_.push_back(browser); - - // Create a message router. - CefMessageRouterConfig config; - browser_side_router_ = CefMessageRouterBrowserSide::Create(config); - - browser_side_router_->AddHandler(new Handler(), false); - } - - bool LootHandler::DoClose(CefRefPtr browser) { - assert(CefCurrentlyOn(TID_UI)); - - // Check if unapplied changes exist. - if (g_app_state.numUnappliedChanges > 0) { - browser->GetMainFrame()->ExecuteJavaScript("onQuit();", browser->GetMainFrame()->GetURL(), 0); - return true; - } - - // Closing the main window requires special handling. See the DoClose() - // documentation in the CEF header for a detailed destription of this - // process. - if (browser_list_.size() == 1) { - // Set a flag to indicate that the window close should be allowed. - is_closing_ = true; - } - - // Allow the close. For windowed browsers this will result in the OS close - // event being sent. - return false; - } - - void LootHandler::OnBeforeClose(CefRefPtr browser) { - assert(CefCurrentlyOn(TID_UI)); - - // Save window size & position. - YAML::Node settings = g_app_state.GetSettings(); - -#ifdef _WIN32 - RECT rc; - GetWindowRect(browser->GetHost()->GetWindowHandle(), &rc); - - settings["window"]["left"] = rc.left; - settings["window"]["top"] = rc.top; - settings["window"]["right"] = rc.right; - settings["window"]["bottom"] = rc.bottom; -#endif - - g_app_state.UpdateSettings(settings); - g_app_state.SaveSettings(); - - // Cancel any javascript callbacks. - browser_side_router_->OnBeforeClose(browser); - - // Remove from the list of existing browsers. - for (BrowserList::iterator bit = browser_list_.begin(); bit != browser_list_.end(); ++bit) { - if ((*bit)->IsSame(browser)) { - browser_list_.erase(bit); - break; - } - } - - if (browser_list_.empty()) { - // All browser windows have closed. Quit the application message loop. - CefQuitMessageLoop(); - } - } - - // CefLoadHandler methods - //----------------------- - - void LootHandler::OnLoadError(CefRefPtr browser, - CefRefPtr frame, - ErrorCode errorCode, - const CefString& errorText, - const CefString& failedUrl) { - assert(CefCurrentlyOn(TID_UI)); - - // Don't display an error for downloaded files. - if (errorCode == ERR_ABORTED) - return; - - // Display a load error message. - std::stringstream ss; - ss << "" - << "

Failed to load URL " << std::string(failedUrl) - << " with error " << std::string(errorText) << " (" << errorCode - << ").

"; - - frame->LoadString(ss.str(), failedUrl); - } - - // CefRequestHandler methods - //-------------------------- - - bool LootHandler::OnBeforeBrowse(CefRefPtr< CefBrowser > browser, - CefRefPtr< CefFrame > frame, - CefRefPtr< CefRequest > request, - bool is_redirect) { - BOOST_LOG_TRIVIAL(trace) << "Attemping to open link: " << request->GetURL().ToString(); - BOOST_LOG_TRIVIAL(trace) << "Comparing with URL: " << ToFileURL(g_path_report); - - if (boost::iequals(request->GetURL().ToString(), ToFileURL(g_path_report))) { - BOOST_LOG_TRIVIAL(trace) << "Link is to LOOT page, allowing CEF's default handling."; - return false; - } - - BOOST_LOG_TRIVIAL(info) << "Opening link in Windows' default handler."; - // Open readme in default application. - HINSTANCE ret = ShellExecute(0, NULL, request->GetURL().ToWString().c_str(), NULL, NULL, SW_SHOWNORMAL); - if ((int)ret <= 32) - throw error(error::windows_error, "Shell execute failed."); - - return true; - } - - void LootHandler::CloseAllBrowsers(bool force_close) { - if (!CefCurrentlyOn(TID_UI)) { - // Execute on the UI thread. - CefPostTask(TID_UI, - NewCefRunnableMethod(this, &LootHandler::CloseAllBrowsers, force_close)); - return; - } - - if (browser_list_.empty()) - return; - - for (BrowserList::const_iterator it = browser_list_.begin(); it != browser_list_.end(); ++it) { - (*it)->GetHost()->CloseBrowser(force_close); - } - } } diff --git a/src/gui/handler.h b/src/gui/handler.h index af0631f2..34c488d4 100644 --- a/src/gui/handler.h +++ b/src/gui/handler.h @@ -25,16 +25,12 @@ #ifndef __LOOT_GUI_HANDLER__ #define __LOOT_GUI_HANDLER__ -#include "../backend/globals.h" -#include "../backend/helpers.h" +#include "../backend/plugin.h" -#include #include #include -#include - namespace loot { class Handler : public CefMessageRouterBrowserSide::Handler { public: @@ -79,71 +75,6 @@ namespace loot { private: IMPLEMENT_REFCOUNTING(Handler); }; - - class LootHandler : public CefClient, - public CefDisplayHandler, - public CefLifeSpanHandler, - public CefLoadHandler, - public CefRequestHandler { - public: - LootHandler(); - ~LootHandler(); - - // Provide access to the single global instance of this object. - static LootHandler * GetInstance(); - - // CefClient methods - //------------------ - virtual CefRefPtr GetDisplayHandler() OVERRIDE; - virtual CefRefPtr GetLifeSpanHandler() OVERRIDE; - virtual CefRefPtr GetLoadHandler() OVERRIDE; - - virtual bool OnProcessMessageReceived(CefRefPtr browser, - CefProcessId source_process, - CefRefPtr message) OVERRIDE; - - // CefLifeSpanHandler methods - //--------------------------- - virtual void OnAfterCreated(CefRefPtr browser) OVERRIDE; - virtual bool DoClose(CefRefPtr browser) OVERRIDE; - virtual void OnBeforeClose(CefRefPtr browser) OVERRIDE; - - // CefLoadHandler methods - //----------------------- - virtual void OnLoadError(CefRefPtr browser, - CefRefPtr frame, - ErrorCode errorCode, - const CefString& errorText, - const CefString& failedUrl) OVERRIDE; - - // CefRequestHandler methods - //-------------------------- - - virtual CefRefPtr GetRequestHandler() OVERRIDE{ - return this; - } - - virtual bool OnBeforeBrowse(CefRefPtr< CefBrowser > browser, - CefRefPtr< CefFrame > frame, - CefRefPtr< CefRequest > request, - bool is_redirect) OVERRIDE; - - // Request that all existing browser windows close. - void CloseAllBrowsers(bool force_close); - - bool IsClosing() const { return is_closing_; } - - private: - // List of existing browser windows. Only accessed on the CEF UI thread. - typedef std::list > BrowserList; - BrowserList browser_list_; - CefRefPtr browser_side_router_; - - bool is_closing_; - - // Include the default reference counting implementation. - IMPLEMENT_REFCOUNTING(LootHandler); - }; } #endif diff --git a/src/gui/loot_app.cpp b/src/gui/loot_app.cpp new file mode 100644 index 00000000..03909705 --- /dev/null +++ b/src/gui/loot_app.cpp @@ -0,0 +1,139 @@ +/* LOOT + + A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and + Fallout: New Vegas. + + Copyright (C) 2014-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 + . + */ + +#include "loot_app.h" +#include "loot_state.h" +#include "loot_handler.h" +#include "scheme.h" + +#include "../backend/globals.h" +#include "../backend/helpers.h" +#include "../backend/language.h" + +#include +#include +#include + +#include +#include +#include + +#include + +using namespace std; +using boost::locale::translate; +using boost::format; + +namespace fs = boost::filesystem; + +namespace loot { + LootApp::LootApp() {} + + void LootApp::OnBeforeCommandLineProcessing(const CefString& process_type, + CefRefPtr command_line) { + if (process_type.empty()) { + // Browser process, OK to modify the command line. + + // Disable spell checking. + command_line->AppendSwitch("--disable-spell-checking"); + } + } + + CefRefPtr LootApp::GetBrowserProcessHandler() { + return this; + } + + CefRefPtr LootApp::GetRenderProcessHandler() { + return this; + } + + void LootApp::OnRegisterCustomSchemes(CefRefPtr registrar) { + // Register "loot" as a standard scheme. + registrar->AddCustomScheme("loot", true, false, false); + } + + void LootApp::OnContextInitialized() { + //Make sure this is running in the UI thread. + assert(CefCurrentlyOn(TID_UI)); + + // Information used when creating the native window. + CefWindowInfo window_info; + +#ifdef _WIN32 + // On Windows we need to specify certain flags that will be passed to CreateWindowEx(). + window_info.SetAsPopup(NULL, "LOOT"); +#endif + + // Set the handler for browser-level callbacks. + CefRefPtr handler(new LootHandler()); + + // Register the custom "loot" scheme handlers. + CefRegisterSchemeHandlerFactory("loot", "l10n", new LootSchemeHandlerFactory()); + + // Specify CEF browser settings here. + CefBrowserSettings browser_settings; + + // Need to set the global locale for this process so that messages will + // be translated. + BOOST_LOG_TRIVIAL(debug) << "Initialising language settings in UI thread."; + const YAML::Node& settings = g_app_state.GetSettings(); + if (settings["language"] && settings["language"].as() != Language(Language::english).Locale()) { + boost::locale::generator gen; + gen.add_messages_path(g_path_l10n.string()); + gen.add_messages_domain("loot"); + + loot::Language lang(settings["language"].as()); + BOOST_LOG_TRIVIAL(debug) << "Selected language: " << lang.Name(); + locale::global(gen(lang.Locale() + ".UTF-8")); + boost::filesystem::path::imbue(locale()); + } + + // Set URL to load. Ignore any command line values. + std::string url = ToFileURL(g_path_report); + + // Create the first browser window. + CefBrowserHost::CreateBrowser(window_info, handler.get(), url, browser_settings, NULL); + } + + void LootApp::OnWebKitInitialized() { + // Create the renderer-side router for query handling. + CefMessageRouterConfig config; + message_router_ = CefMessageRouterRendererSide::Create(config); + } + + bool LootApp::OnProcessMessageReceived( + CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) { + // Handle IPC messages from the browser process... + return message_router_->OnProcessMessageReceived(browser, source_process, message); + } + + void LootApp::OnContextCreated(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context) { + // Register javascript functions. + message_router_->OnContextCreated(browser, frame, context); + } +} diff --git a/src/gui/app.h b/src/gui/loot_app.h similarity index 63% rename from src/gui/app.h rename to src/gui/loot_app.h index 3655d499..c2e0c695 100644 --- a/src/gui/app.h +++ b/src/gui/loot_app.h @@ -22,17 +22,13 @@ . */ -#ifndef __LOOT_GUI_APP__ -#define __LOOT_GUI_APP__ - -#include "../backend/game.h" +#ifndef __LOOT_GUI_LOOT_APP__ +#define __LOOT_GUI_LOOT_APP__ #include #include #include -#include - namespace loot { class LootApp : public CefApp, public CefBrowserProcessHandler, @@ -64,45 +60,6 @@ namespace loot { IMPLEMENT_REFCOUNTING(LootApp); }; - - class LootState : public CefBase { - public: - LootState(); - - void Init(const std::string& cmdLineGame); - const std::vector& InitErrors() const; - - Game& CurrentGame(); - void ChangeGame(const std::string& newGameFolder); - void UpdateGames(std::list& games); - // Get the folder names of the installed games. - std::vector InstalledGames() const; - - const YAML::Node& GetSettings() const; - void UpdateSettings(const YAML::Node& settings); - void SaveSettings(); - - // Used to check if LOOT has unaccepted sorting or metadata changes on quit. - int numUnappliedChanges; - private: - YAML::Node _settings; - std::list _games; - std::list::iterator _currentGame; - std::vector _initErrors; - - // Select initial game. - void SelectGame(std::string cmdLineGame); - - // Check if the settings file has the right root keys (doesn't check their values). - bool AreSettingsValid(); - YAML::Node GetDefaultSettings() const; - - // Lock used to protect access to member variables. - base::Lock _lock; - IMPLEMENT_REFCOUNTING(LootState); - }; - - extern LootState g_app_state; } #endif diff --git a/src/gui/loot_handler.cpp b/src/gui/loot_handler.cpp new file mode 100644 index 00000000..5b26e6f9 --- /dev/null +++ b/src/gui/loot_handler.cpp @@ -0,0 +1,290 @@ +/* LOOT + + A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and + Fallout: New Vegas. + + Copyright (C) 2014-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 + . + */ + +#include "loot_handler.h" +#include "handler.h" +#include "resource.h" +#include "loot_app.h" +#include "loot_state.h" + +#include "../backend/error.h" +#include "../backend/globals.h" +#include "../backend/helpers.h" +#include "../backend/json.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +using namespace std; + +using boost::format; + +namespace fs = boost::filesystem; +namespace loc = boost::locale; + +namespace loot { + namespace { + LootHandler * g_instance = NULL; + } + + LootHandler::LootHandler() : is_closing_(false) { + assert(!g_instance); + g_instance = this; + } + + LootHandler::~LootHandler() { + g_instance = NULL; + } + + LootHandler* LootHandler::GetInstance() { + return g_instance; + } + + // CefClient methods + //------------------ + + CefRefPtr LootHandler::GetDisplayHandler() { + return this; + } + + CefRefPtr LootHandler::GetLifeSpanHandler() { + return this; + } + + CefRefPtr LootHandler::GetLoadHandler() { + return this; + } + + bool LootHandler::OnProcessMessageReceived(CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) { + return browser_side_router_->OnProcessMessageReceived(browser, source_process, message); + } + + // CefLifeSpanHandler methods + //--------------------------- + + void LootHandler::OnAfterCreated(CefRefPtr browser) { + assert(CefCurrentlyOn(TID_UI)); + +#ifdef _WIN32 + // Set the title bar icon. + HWND hWnd = browser->GetHost()->GetWindowHandle(); + HANDLE hIcon = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(MAINICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); + HANDLE hIconSm = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(MAINICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); + SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm); + + // Set the window title. + SetWindowText(hWnd, L"LOOT"); +#endif + + // Set window size & position. + YAML::Node settings = g_app_state.GetSettings(); + + if (settings["window"]["left"] && settings["window"]["top"] && settings["window"]["right"] && settings["window"]["bottom"]) { +#ifdef _WIN32 + RECT rc; + rc.left = settings["window"]["left"].as(); + rc.top = settings["window"]["top"].as(); + rc.right = settings["window"]["right"].as(); + rc.bottom = settings["window"]["bottom"].as(); + + // Fit the saved window size/position to the current monitor setup. + + // Get the nearest monitor to the saved size/pos. + HMONITOR hMonitor; + hMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST); + + // Get the rect for the monitor's working area. + MONITORINFO mi; + mi.cbSize = sizeof(mi); + GetMonitorInfo(hMonitor, &mi); + + // Clip the saved rect to fit inside the monitor rect. + int width = rc.right - rc.left; + int height = rc.bottom - rc.top; + rc.left = max(mi.rcWork.left, min(mi.rcWork.right - width, rc.left)); + rc.top = max(mi.rcWork.top, min(mi.rcWork.bottom - height, rc.top)); + rc.right = rc.left + width; + rc.bottom = rc.top + height; + + SetWindowPos(hWnd, HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW); +#endif + } + else { +#ifdef _WIN32 + // High DPI support doesn't seem to scale window content correctly + // unless the window is resized, so if no size info is recorded, + // just set its current size + 1. + RECT rc; + GetWindowRect(browser->GetHost()->GetWindowHandle(), &rc); + SetWindowPos(browser->GetHost()->GetWindowHandle(), HWND_TOP, rc.left, rc.top, rc.right - rc.left + 1, rc.bottom - rc.top + 1, SWP_SHOWWINDOW); +#endif + } + + // Add to the list of existing browsers. + browser_list_.push_back(browser); + + // Create a message router. + CefMessageRouterConfig config; + browser_side_router_ = CefMessageRouterBrowserSide::Create(config); + + browser_side_router_->AddHandler(new Handler(), false); + } + + bool LootHandler::DoClose(CefRefPtr browser) { + assert(CefCurrentlyOn(TID_UI)); + + // Check if unapplied changes exist. + if (g_app_state.numUnappliedChanges > 0) { + browser->GetMainFrame()->ExecuteJavaScript("onQuit();", browser->GetMainFrame()->GetURL(), 0); + return true; + } + + // Closing the main window requires special handling. See the DoClose() + // documentation in the CEF header for a detailed destription of this + // process. + if (browser_list_.size() == 1) { + // Set a flag to indicate that the window close should be allowed. + is_closing_ = true; + } + + // Allow the close. For windowed browsers this will result in the OS close + // event being sent. + return false; + } + + void LootHandler::OnBeforeClose(CefRefPtr browser) { + assert(CefCurrentlyOn(TID_UI)); + + // Save window size & position. + YAML::Node settings = g_app_state.GetSettings(); + +#ifdef _WIN32 + RECT rc; + GetWindowRect(browser->GetHost()->GetWindowHandle(), &rc); + + settings["window"]["left"] = rc.left; + settings["window"]["top"] = rc.top; + settings["window"]["right"] = rc.right; + settings["window"]["bottom"] = rc.bottom; +#endif + + g_app_state.UpdateSettings(settings); + g_app_state.SaveSettings(); + + // Cancel any javascript callbacks. + browser_side_router_->OnBeforeClose(browser); + + // Remove from the list of existing browsers. + for (BrowserList::iterator bit = browser_list_.begin(); bit != browser_list_.end(); ++bit) { + if ((*bit)->IsSame(browser)) { + browser_list_.erase(bit); + break; + } + } + + if (browser_list_.empty()) { + // All browser windows have closed. Quit the application message loop. + CefQuitMessageLoop(); + } + } + + // CefLoadHandler methods + //----------------------- + + void LootHandler::OnLoadError(CefRefPtr browser, + CefRefPtr frame, + ErrorCode errorCode, + const CefString& errorText, + const CefString& failedUrl) { + assert(CefCurrentlyOn(TID_UI)); + + // Don't display an error for downloaded files. + if (errorCode == ERR_ABORTED) + return; + + // Display a load error message. + std::stringstream ss; + ss << "" + << "

Failed to load URL " << std::string(failedUrl) + << " with error " << std::string(errorText) << " (" << errorCode + << ").

"; + + frame->LoadString(ss.str(), failedUrl); + } + + // CefRequestHandler methods + //-------------------------- + + bool LootHandler::OnBeforeBrowse(CefRefPtr< CefBrowser > browser, + CefRefPtr< CefFrame > frame, + CefRefPtr< CefRequest > request, + bool is_redirect) { + BOOST_LOG_TRIVIAL(trace) << "Attemping to open link: " << request->GetURL().ToString(); + BOOST_LOG_TRIVIAL(trace) << "Comparing with URL: " << ToFileURL(g_path_report); + + if (boost::iequals(request->GetURL().ToString(), ToFileURL(g_path_report))) { + BOOST_LOG_TRIVIAL(trace) << "Link is to LOOT page, allowing CEF's default handling."; + return false; + } + + BOOST_LOG_TRIVIAL(info) << "Opening link in Windows' default handler."; + // Open readme in default application. + HINSTANCE ret = ShellExecute(0, NULL, request->GetURL().ToWString().c_str(), NULL, NULL, SW_SHOWNORMAL); + if ((int)ret <= 32) + throw error(error::windows_error, "Shell execute failed."); + + return true; + } + + void LootHandler::CloseAllBrowsers(bool force_close) { + if (!CefCurrentlyOn(TID_UI)) { + // Execute on the UI thread. + CefPostTask(TID_UI, + NewCefRunnableMethod(this, &LootHandler::CloseAllBrowsers, force_close)); + return; + } + + if (browser_list_.empty()) + return; + + for (BrowserList::const_iterator it = browser_list_.begin(); it != browser_list_.end(); ++it) { + (*it)->GetHost()->CloseBrowser(force_close); + } + } +} diff --git a/src/gui/loot_handler.h b/src/gui/loot_handler.h new file mode 100644 index 00000000..7a278395 --- /dev/null +++ b/src/gui/loot_handler.h @@ -0,0 +1,100 @@ +/* LOOT + + A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and + Fallout: New Vegas. + + Copyright (C) 2014-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 + . + */ + +#ifndef __LOOT_GUI_LOOT_HANDLER__ +#define __LOOT_GUI_LOOT_HANDLER__ + +#include +#include + +#include + +namespace loot { + class LootHandler : public CefClient, + public CefDisplayHandler, + public CefLifeSpanHandler, + public CefLoadHandler, + public CefRequestHandler { + public: + LootHandler(); + ~LootHandler(); + + // Provide access to the single global instance of this object. + static LootHandler * GetInstance(); + + // CefClient methods + //------------------ + virtual CefRefPtr GetDisplayHandler() OVERRIDE; + virtual CefRefPtr GetLifeSpanHandler() OVERRIDE; + virtual CefRefPtr GetLoadHandler() OVERRIDE; + + virtual bool OnProcessMessageReceived(CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) OVERRIDE; + + // CefLifeSpanHandler methods + //--------------------------- + virtual void OnAfterCreated(CefRefPtr browser) OVERRIDE; + virtual bool DoClose(CefRefPtr browser) OVERRIDE; + virtual void OnBeforeClose(CefRefPtr browser) OVERRIDE; + + // CefLoadHandler methods + //----------------------- + virtual void OnLoadError(CefRefPtr browser, + CefRefPtr frame, + ErrorCode errorCode, + const CefString& errorText, + const CefString& failedUrl) OVERRIDE; + + // CefRequestHandler methods + //-------------------------- + + virtual CefRefPtr GetRequestHandler() OVERRIDE{ + return this; + } + + virtual bool OnBeforeBrowse(CefRefPtr< CefBrowser > browser, + CefRefPtr< CefFrame > frame, + CefRefPtr< CefRequest > request, + bool is_redirect) OVERRIDE; + + // Request that all existing browser windows close. + void CloseAllBrowsers(bool force_close); + + bool IsClosing() const { return is_closing_; } + + private: + // List of existing browser windows. Only accessed on the CEF UI thread. + typedef std::list > BrowserList; + BrowserList browser_list_; + CefRefPtr browser_side_router_; + + bool is_closing_; + + // Include the default reference counting implementation. + IMPLEMENT_REFCOUNTING(LootHandler); + }; +} + +#endif diff --git a/src/gui/app.cpp b/src/gui/loot_state.cpp similarity index 80% rename from src/gui/app.cpp rename to src/gui/loot_state.cpp index 42bce830..535d1e54 100644 --- a/src/gui/app.cpp +++ b/src/gui/loot_state.cpp @@ -22,21 +22,14 @@ . */ -#include "app.h" -#include "handler.h" -#include "scheme.h" +#include "loot_state.h" #include "../backend/error.h" #include "../backend/globals.h" #include "../backend/helpers.h" -#include "../backend/parsers.h" -#include "../backend/generators.h" +#include "../backend/language.h" #include "../backend/streams.h" -#include -#include -#include - #include #include #include @@ -55,98 +48,6 @@ namespace fs = boost::filesystem; namespace loot { LootState g_app_state = LootState(); - LootApp::LootApp() {} - - void LootApp::OnBeforeCommandLineProcessing(const CefString& process_type, - CefRefPtr command_line) { - if (process_type.empty()) { - // Browser process, OK to modify the command line. - - // Disable spell checking. - command_line->AppendSwitch("--disable-spell-checking"); - } - } - - CefRefPtr LootApp::GetBrowserProcessHandler() { - return this; - } - - CefRefPtr LootApp::GetRenderProcessHandler() { - return this; - } - - void LootApp::OnRegisterCustomSchemes(CefRefPtr registrar) { - // Register "loot" as a standard scheme. - registrar->AddCustomScheme("loot", true, false, false); - } - - void LootApp::OnContextInitialized() { - //Make sure this is running in the UI thread. - assert(CefCurrentlyOn(TID_UI)); - - // Information used when creating the native window. - CefWindowInfo window_info; - -#ifdef _WIN32 - // On Windows we need to specify certain flags that will be passed to CreateWindowEx(). - window_info.SetAsPopup(NULL, "LOOT"); -#endif - - // Set the handler for browser-level callbacks. - CefRefPtr handler(new LootHandler()); - - // Register the custom "loot" scheme handlers. - CefRegisterSchemeHandlerFactory("loot", "l10n", new LootSchemeHandlerFactory()); - - // Specify CEF browser settings here. - CefBrowserSettings browser_settings; - - // Need to set the global locale for this process so that messages will - // be translated. - BOOST_LOG_TRIVIAL(debug) << "Initialising language settings in UI thread."; - const YAML::Node& settings = g_app_state.GetSettings(); - if (settings["language"] && settings["language"].as() != Language(Language::english).Locale()) { - boost::locale::generator gen; - gen.add_messages_path(g_path_l10n.string()); - gen.add_messages_domain("loot"); - - loot::Language lang(settings["language"].as()); - BOOST_LOG_TRIVIAL(debug) << "Selected language: " << lang.Name(); - locale::global(gen(lang.Locale() + ".UTF-8")); - boost::filesystem::path::imbue(locale()); - } - - // Set URL to load. Ignore any command line values. - std::string url = ToFileURL(g_path_report); - - // Create the first browser window. - CefBrowserHost::CreateBrowser(window_info, handler.get(), url, browser_settings, NULL); - } - - void LootApp::OnWebKitInitialized() { - // Create the renderer-side router for query handling. - CefMessageRouterConfig config; - message_router_ = CefMessageRouterRendererSide::Create(config); - } - - bool LootApp::OnProcessMessageReceived( - CefRefPtr browser, - CefProcessId source_process, - CefRefPtr message) { - // Handle IPC messages from the browser process... - return message_router_->OnProcessMessageReceived(browser, source_process, message); - } - - void LootApp::OnContextCreated(CefRefPtr browser, - CefRefPtr frame, - CefRefPtr context) { - // Register javascript functions. - message_router_->OnContextCreated(browser, frame, context); - } - - // LootState member functions - //--------------------------- - LootState::LootState() : numUnappliedChanges(0), _currentGame(_games.end()) {} void LootState::Init(const std::string& cmdLineGame) { diff --git a/src/gui/loot_state.h b/src/gui/loot_state.h new file mode 100644 index 00000000..3ba50c20 --- /dev/null +++ b/src/gui/loot_state.h @@ -0,0 +1,76 @@ +/* LOOT + + A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and + Fallout: New Vegas. + + Copyright (C) 2014-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 + . + */ + +#ifndef __LOOT_GUI_LOOT_STATE__ +#define __LOOT_GUI_LOOT_STATE__ + +#include "../backend/game.h" + +#include +#include + +#include + +namespace loot { + class LootState : public CefBase { + public: + LootState(); + + void Init(const std::string& cmdLineGame); + const std::vector& InitErrors() const; + + Game& CurrentGame(); + void ChangeGame(const std::string& newGameFolder); + void UpdateGames(std::list& games); + // Get the folder names of the installed games. + std::vector InstalledGames() const; + + const YAML::Node& GetSettings() const; + void UpdateSettings(const YAML::Node& settings); + void SaveSettings(); + + // Used to check if LOOT has unaccepted sorting or metadata changes on quit. + int numUnappliedChanges; + private: + YAML::Node _settings; + std::list _games; + std::list::iterator _currentGame; + std::vector _initErrors; + + // Select initial game. + void SelectGame(std::string cmdLineGame); + + // Check if the settings file has the right root keys (doesn't check their values). + bool AreSettingsValid(); + YAML::Node GetDefaultSettings() const; + + // Lock used to protect access to member variables. + base::Lock _lock; + IMPLEMENT_REFCOUNTING(LootState); + }; + + extern LootState g_app_state; +} + +#endif diff --git a/src/gui/main_win.cpp b/src/gui/main_win.cpp index d4a8d39d..84520e66 100644 --- a/src/gui/main_win.cpp +++ b/src/gui/main_win.cpp @@ -22,7 +22,9 @@ . */ -#include "app.h" +#include "loot_app.h" +#include "loot_state.h" +#include "../backend/globals.h" #include #include