From a400162811a860a943d8169698ec1083bc203f06 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sat, 13 Jun 2015 21:09:31 +0100 Subject: [PATCH] Refactored plugin loading code into new class. PluginLoader now handles the details of how plugins are read, cleaning up the Game and Plugin classes a little. --- CMakeLists.txt | 2 + src/backend/game.cpp | 15 +--- src/backend/game.h | 6 +- src/backend/plugin.cpp | 89 +++++---------------- src/backend/plugin_loader.cpp | 145 ++++++++++++++++++++++++++++++++++ src/backend/plugin_loader.h | 59 ++++++++++++++ 6 files changed, 226 insertions(+), 90 deletions(-) create mode 100644 src/backend/plugin_loader.cpp create mode 100644 src/backend/plugin_loader.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e191f635..5d55fabf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,7 @@ set (LOOT_SRC "${CMAKE_SOURCE_DIR}/src/backend/metadata/conditional_metadata.c "${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/plugin_loader.cpp" "${CMAKE_SOURCE_DIR}/src/backend/helpers.cpp" "${CMAKE_SOURCE_DIR}/src/backend/language.cpp" "${CMAKE_SOURCE_DIR}/src/backend/version.cpp" @@ -113,6 +114,7 @@ set (LOOT_HEADERS "${CMAKE_SOURCE_DIR}/src/backend/metadata/condition_grammar.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/plugin_loader.h" "${CMAKE_SOURCE_DIR}/src/backend/helpers.h" "${CMAKE_SOURCE_DIR}/src/backend/language.h" "${CMAKE_SOURCE_DIR}/src/backend/version.h" diff --git a/src/backend/game.cpp b/src/backend/game.cpp index b73ba959..88f4f309 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -51,20 +51,7 @@ namespace loot { gameSettings.RegistryKey()); } - Game::Game(const unsigned int gameCode, const std::string& folder) : GameSettings(gameCode, folder), gh(nullptr) { - if (Id() == Game::tes4) { - espm_settings = espm::Settings("tes4"); - } - else if (Id() == Game::tes5) { - espm_settings = espm::Settings("tes5"); - } - else if (Id() == Game::fo3) { - espm_settings = espm::Settings("fo3"); - } - else if (Id() == Game::fonv) { - espm_settings = espm::Settings("fonv"); - } - } + Game::Game(const unsigned int gameCode, const std::string& folder) : GameSettings(gameCode, folder), gh(nullptr) {} Game::~Game() { lo_destroy_handle(gh); diff --git a/src/backend/game.h b/src/backend/game.h index 37e13537..81f99ee7 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -40,7 +40,6 @@ #include #include -#include #include namespace loot { @@ -62,12 +61,11 @@ namespace loot { void GetLoadOrder(std::list& loadOrder) const; void SetLoadOrder(const std::list& loadOrder) const; //Modifies game load order, even though const. void SetLoadOrder(const char * const * const loadOrder, const size_t numPlugins) const; // For API. - void RefreshActivePluginsList(); void RedatePlugins(); //Change timestamps to match load order (Skyrim only). + void LoadPlugins(bool headersOnly); //Loads all installed plugins. bool HasBeenLoaded(); // Checks if the game's plugins have already been loaded. - std::list Sort(const unsigned int language, std::function progressCallback); //Caches for condition results, active plugins and CRCs. @@ -79,8 +77,6 @@ namespace loot { Masterlist masterlist; MetadataList userlist; std::unordered_map plugins; //Map so that plugin data can be edited. - - espm::Settings espm_settings; private: boost::filesystem::path _gameLocalDataPath; // Path to the game's folder in %LOCALAPPDATA%. diff --git a/src/backend/plugin.cpp b/src/backend/plugin.cpp index c59c9a4e..d94b0a81 100644 --- a/src/backend/plugin.cpp +++ b/src/backend/plugin.cpp @@ -23,11 +23,10 @@ */ #include "plugin.h" +#include "plugin_loader.h" #include "game.h" #include "helpers.h" -#include - #include #include #include @@ -42,71 +41,34 @@ using boost::regex_search; using boost::smatch; namespace loot { - Plugin::Plugin() : PluginMetadata(), isMaster(false), crc(0), numOverrideRecords(0) {} + Plugin::Plugin() : PluginMetadata(), _isEmpty(true), isMaster(false), crc(0), numOverrideRecords(0) {} - Plugin::Plugin(const std::string& n) : PluginMetadata(n), isMaster(false), crc(0), numOverrideRecords(0) {} + Plugin::Plugin(const std::string& n) : PluginMetadata(n), _isEmpty(true), isMaster(false), crc(0), numOverrideRecords(0) {} Plugin::Plugin(loot::Game& game, const std::string& n, const bool headerOnly) - : PluginMetadata(n), isMaster(false), crc(0), numOverrideRecords(0) { - // Get data from file contents using libespm. Assumes libespm has already been initialised. - BOOST_LOG_TRIVIAL(trace) << name << ": " << "Opening with libespm..."; - boost::filesystem::path filepath = game.DataPath() / name; - - //In case the plugin is ghosted. - if (!boost::filesystem::exists(filepath) && boost::filesystem::exists(filepath.string() + ".ghost")) - filepath += ".ghost"; - - espm::File * file = nullptr; + : PluginMetadata(n), _isEmpty(true), isMaster(false), crc(0), numOverrideRecords(0) { try { - if (game.Id() == Game::tes4) - file = new espm::tes4::File(filepath, game.espm_settings, false, headerOnly); - else if (game.Id() == Game::tes5) - file = new espm::tes5::File(filepath, game.espm_settings, false, headerOnly); - else if (game.Id() == Game::fo3) - file = new espm::fo3::File(filepath, game.espm_settings, false, headerOnly); - else - file = new espm::fonv::File(filepath, game.espm_settings, false, headerOnly); - - BOOST_LOG_TRIVIAL(trace) << name << ": " << "Checking master flag."; - isMaster = file->isMaster(game.espm_settings); - - BOOST_LOG_TRIVIAL(trace) << name << ": " << "Getting masters."; - for (const auto& master : file->getMasters()) { - masters.push_back(boost::locale::conv::to_utf(master, "Windows-1252", boost::locale::conv::stop)); - } - - BOOST_LOG_TRIVIAL(trace) << name << ": " << "Number of masters: " << masters.size(); + PluginLoader loader; + loader.Load(game, name, headerOnly, false); + isMaster = loader.IsMaster(); + masters = loader.Masters(); + _isEmpty = loader.IsEmpty(); + formIDs.insert(loader.FormIDs().begin(), loader.FormIDs().end()); if (!headerOnly) { - BOOST_LOG_TRIVIAL(trace) << name << ": " << "Getting CRC."; - crc = file->crc; + BOOST_LOG_TRIVIAL(trace) << name << ": Caching CRC value."; + crc = loader.Crc(); game.crcCache.insert(pair(boost::locale::to_lower(name), crc)); } - BOOST_LOG_TRIVIAL(trace) << name << ": " << "Getting the FormIDs."; - vector records = file->getFormIDs(); - vector plugins = masters; - plugins.push_back(name); - for (const auto &record : records) { - FormID fid = FormID(plugins, record); - formIDs.insert(fid); - if (!boost::iequals(fid.Plugin(), name)) + BOOST_LOG_TRIVIAL(trace) << name << ": Counting override FormIDs."; + for (const auto& formID : formIDs) { + if (!boost::iequals(formID.Plugin(), name)) ++numOverrideRecords; } - BOOST_LOG_TRIVIAL(trace) << name << ": " << "Checking if plugin is empty."; - if (headerOnly) { - // Check the header records count. - _isEmpty = file->getNumRecords() == 0; - } - else { - _isEmpty = formIDs.empty(); - } - //Also read Bash Tags applied and version string in description. - BOOST_LOG_TRIVIAL(trace) << name << ": " << "Reading the description."; - string text = boost::locale::conv::to_utf(file->getDescription(), "Windows-1252", boost::locale::conv::stop); - + string text = loader.Description(); BOOST_LOG_TRIVIAL(trace) << name << ": " << "Attempting to read the version from the description."; for (size_t i = 0; i < version_checks.size(); ++i) { smatch what; @@ -139,7 +101,6 @@ namespace loot { } } catch (std::exception& e) { - delete file; BOOST_LOG_TRIVIAL(error) << "Cannot read plugin file \"" << name << "\". Details: " << e.what(); messages.push_back(loot::Message(loot::Message::error, (boost::format(boost::locale::translate("Cannot read \"%1%\". Details: %2%")) % name % e.what()).str())); } @@ -222,22 +183,8 @@ namespace loot { } try { - boost::filesystem::path filepath = game.DataPath() / name; - //In case the plugin is ghosted. - if (!boost::filesystem::exists(filepath) && boost::filesystem::exists(filepath.string() + ".ghost")) - filepath += ".ghost"; - - espm::File * file = nullptr; - if (game.Id() == Game::tes4) - file = new espm::tes4::File(filepath, game.espm_settings, false, true); - else if (game.Id() == Game::tes5) - file = new espm::tes5::File(filepath, game.espm_settings, false, true); - else if (game.Id() == Game::fo3) - file = new espm::fo3::File(filepath, game.espm_settings, false, true); - else - file = new espm::fonv::File(filepath, game.espm_settings, false, true); - - delete file; + PluginLoader loader; + return loader.Load(game, name, true, true); } catch (std::exception& /*e*/) { BOOST_LOG_TRIVIAL(warning) << "The .es(p|m) file \"" << name << "\" is not a valid plugin."; diff --git a/src/backend/plugin_loader.cpp b/src/backend/plugin_loader.cpp new file mode 100644 index 00000000..1b96bf68 --- /dev/null +++ b/src/backend/plugin_loader.cpp @@ -0,0 +1,145 @@ +/* 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_loader.h" +#include "game.h" +#include "helpers.h" + +#include + +#include +#include +#include +#include + +using namespace std; + +namespace loot { + PluginLoader::PluginLoader() : _isMaster(false), _crc(0) {} + + bool PluginLoader::Load(const Game& game, const std::string& name, const bool headerOnly, const bool checkValidityOnly) { + espm::File * file = nullptr; + espm::Settings espmSettings; + + // Get data from file contents using libespm. + BOOST_LOG_TRIVIAL(trace) << name << ": " << "Opening with libespm..."; + boost::filesystem::path filepath = game.DataPath() / name; + + //In case the plugin is ghosted. + if (!boost::filesystem::exists(filepath) && boost::filesystem::exists(filepath.string() + ".ghost")) + filepath += ".ghost"; + + try { + if (game.Id() == Game::tes4) { + espmSettings = espm::Settings("tes4"); + file = new espm::tes4::File(filepath, espmSettings, false, headerOnly); + } + else if (game.Id() == Game::tes5) { + espmSettings = espm::Settings("tes5"); + file = new espm::tes5::File(filepath, espmSettings, false, headerOnly); + } + else if (game.Id() == Game::fo3) { + espmSettings = espm::Settings("fo3"); + file = new espm::fo3::File(filepath, espmSettings, false, headerOnly); + } + else { + espmSettings = espm::Settings("fonv"); + file = new espm::fonv::File(filepath, espmSettings, false, headerOnly); + } + + // If only wanting to test for valid parsing, quit now. + if (checkValidityOnly) { + delete file; + return true; + } + + BOOST_LOG_TRIVIAL(trace) << filepath.filename() << ": " << "Checking master flag."; + _isMaster = file->isMaster(espmSettings); + + BOOST_LOG_TRIVIAL(trace) << filepath.filename() << ": " << "Getting masters."; + for (const auto& master : file->getMasters()) { + _masters.push_back(boost::locale::conv::to_utf(master, "Windows-1252", boost::locale::conv::stop)); + } + + BOOST_LOG_TRIVIAL(trace) << filepath.filename() << ": " << "Number of masters: " << _masters.size(); + + if (!headerOnly) { + BOOST_LOG_TRIVIAL(trace) << filepath.filename() << ": " << "Getting CRC."; + _crc = file->crc; + } + + BOOST_LOG_TRIVIAL(trace) << name << ": " << "Getting the FormIDs."; + vector records = file->getFormIDs(); + vector plugins = _masters; + plugins.push_back(name); + for (const auto &record : records) { + _formIDs.insert(FormID(plugins, record)); + } + + BOOST_LOG_TRIVIAL(trace) << name << ": " << "Checking if plugin is empty."; + if (headerOnly) { + // Check the header records count. + _isEmpty = file->getNumRecords() == 0; + } + else { + _isEmpty = _formIDs.empty(); + } + + //Also read Bash Tags applied and version string in description. + BOOST_LOG_TRIVIAL(trace) << name << ": " << "Reading the description."; + _description = boost::locale::conv::to_utf(file->getDescription(), "Windows-1252", boost::locale::conv::stop); + } + catch (std::exception& e) { + delete file; + throw; + } + + BOOST_LOG_TRIVIAL(trace) << name << ": " << "Plugin loading complete."; + return true; + } + + bool PluginLoader::IsEmpty() const { + return _isEmpty; + } + + bool PluginLoader::IsMaster() const { + return _isMaster; + } + + const std::set& PluginLoader::FormIDs() const { + return _formIDs; + } + + std::vector PluginLoader::Masters() const { + return _masters; + } + + std::string PluginLoader::Description() const { + return _description; + } + + uint32_t PluginLoader::Crc() const { + return _crc; + } +} diff --git a/src/backend/plugin_loader.h b/src/backend/plugin_loader.h new file mode 100644 index 00000000..f52eac35 --- /dev/null +++ b/src/backend/plugin_loader.h @@ -0,0 +1,59 @@ +/* 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_PLUGIN_LOADER__ +#define __LOOT_PLUGIN_LOADER__ + +#include "metadata/formid.h" + +#include +#include +#include +#include + +namespace loot { + class Game; + + class PluginLoader { + public: + PluginLoader(); + + bool Load(const Game& game, const std::string& name, const bool headerOnly, const bool checkValidityOnly); + + bool IsEmpty() const; + bool IsMaster() const; //Checks master bit flag. + const std::set& FormIDs() const; + std::vector Masters() const; + std::string Description() const; + uint32_t Crc() const; + private: + bool _isEmpty; + bool _isMaster; + std::set _formIDs; + std::vector _masters; + std::string _description; + uint32_t _crc; + }; +} + +#endif