Implemented condition parser for single conditions. The ModFormat and Types source files should be replaced by libespm usage at some point. Committing example masterlist and settings files for reference.

This commit is contained in:
WrinklyNinja
2013-01-06 23:13:05 +00:00
parent 895f3d18b8
commit 94016422f0
16 changed files with 1051 additions and 104 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
cmake_minimum_required (VERSION 2.8.9)
project (boss)
set (BOSS_SRC "${CMAKE_SOURCE_DIR}/src/metadata.cpp" "${CMAKE_SOURCE_DIR}/src/game.cpp" "${CMAKE_SOURCE_DIR}/src/helpers.cpp")
set (BOSS_SRC "${CMAKE_SOURCE_DIR}/src/metadata.cpp" "${CMAKE_SOURCE_DIR}/src/game.cpp" "${CMAKE_SOURCE_DIR}/src/helpers.cpp" "${CMAKE_SOURCE_DIR}/src/plugin/ModFormat.cpp" "${CMAKE_SOURCE_DIR}/src/plugin/VersionRegex.cpp")
# Include source and library directories.
include_directories ("${BOSS_LIBS_DIR}/alphanum" "${BOSS_LIBS_DIR}/utf8" "${BOSS_LIBS_DIR}/boost" "${BOSS_LIBS_DIR}/yaml-cpp/include" "${CMAKE_SOURCE_DIR}/src")
+2 -4
View File
@@ -175,9 +175,8 @@ possible, for example:
* Boost: for filesystem interaction, various helpful functions.
As a reflection of this, LOOT's API will provide functions only for
accessing LOOT-specific functionality and data, eg. auto-sorting,
masterlist / userlist reading, masterlist download and user
edits submission.
accessing LOOT-specific functionality and data, eg. masterlist / userlist reading,
masterlist download and user edits submission.
The support for arbitrary data sources will be implemented by having the source
LOOT uses specified in a settings file, which will be editable via the GUI.
@@ -529,7 +528,6 @@ bUseOnlineCache Boolean Cache = Database
bGetMetadataUpdates Boolean Metadata = Masterlist
bPromptMetadataSubmit Boolean
bAlwaysSubmitMetadata Boolean Only has effect if the above is false.
bDoStartupUpdateCheck Boolean Check for program updates.
sOnlineCacheURL String URL to the base of the database repository.
sMetadataURL String
+313
View File
@@ -0,0 +1,313 @@
# This is the Oblivion masterlist up to the end of the ESMs group, converted by
# hand to MF3 (ie. YAML-based format for BOSSv3). Mods without messages attached
# have been removed (since they wouldn't be included unless extra data was
# required for BOSS to sort them.
globals:
- condition: IF VERSION("../BOSS/BOSS.exe", "2.1.1.0", <)
type: warn
content: An update for BOSS (v2.1.1) has been released. Download it at "http://code.google.com/p/better-oblivion-sorting-software/downloads/list"
- condition: IFNOT VERSION("../BOSS/BOSS.exe", "2.1.1.0", <)
type: say
content: You are using the latest version of BOSS.
plugins:
- name: Oblivion_1.1.esm
msg:
- &modESM
type: say
content: A modding esm - do not use in game, deactivate.
- name: Oblivion_SI.esm
msg:
- *modESM
- name: Oblivion_GOTY non-SI.esm
msg:
- *modESM
- name: Hammerfell.esm
msg:
- condition: if checksum("Hammerfell.esm", 7D22F9DF)
type: warn
content: 'Contains dirty edits; 0 ITM, 4 UDR records. Needs TES4Edit cleaning: "http://cs.elderscrolls.com/constwiki/index.php/TES4Edit_Cleaning_Guide"'
- name: GTAesgaard_2.esm
msg:
- &warnWorldspaces
type: warn
content: This needs to load directly after Oblivion.esm to avoid landscape issues caused by a failure by the mod author to use TES4Gecko's "Move Worldspaces" function.
- name: DibellasWatch.esm
msg:
- *warnWorldspaces
- name: Belas.esm
msg:
- type: warn
content: Part of Chronicles of Reeb. Do not use. Has serious landscape issues due to editing the Tamriel worldspace. Uninstall and delete.
- name: Morrowind_ob.esm
msg:
- *warnWorldspaces
- name: Silgrad_Tower.esm
msg:
- condition: if file("Silgrad_Tower_T.esm")
type: warn
content: Use either Silgrad_Tower.esm or Silgrad_Tower_T.esm but not both together.
- name: Silgrad_Tower_T.esm
msg:
- condition: if file("Silgrad_Tower.esm")
type: warn
content: Use either Silgrad_Tower.esm or Silgrad_Tower_T.esm but not both together.
- name: Beautiful People 2ch-Ed.esm
tag:
- Eyes
- Graphics
- Hair
- R.Relations
- name: xeoex.esm
tag:
- Body-F
- Body-Size-F
- Body-M
- Body-Size-M
- Eyes
- name: EnhancedWeatherSIOnly.esm
msg:
- type: warn
content: Obsolete. Remove this and install Enhanced Weather.
- name: Francesco's Leveled Creatures-Items Mod.esm
tag:
- Actors.ACBS
- Actors.AIData
- Actors.AIPackages
- Actors.CombatStype
- Actors.DeathItem
- Actors.Stats
- Delev
- Invent
- NpcFaces
- Relev
- Scripts
- Stats
msg:
- condition: IF CHECKSUM("Francesco's Leveled Creatures-Items Mod.esm", 17F6FD1A)
type: warn
content: 'Contains dirty edits; 6 ITM, 0 UDR records. Needs TES4Edit cleaning: "http://cs.elderscrolls.com/constwiki/index.php/TES4Edit_Cleaning_Guide"'
- name: Francesco's Optional New Items Add-On.esm
tag:
- Delev
- Relev
msg:
- condition: IF CHECKSUM("Francesco's Optional New Items Add-On.esm", DFD4FB97)
type: warn
content: 'Contains dirty edits; 2 ITM, 0 UDR records. Needs TES4Edit cleaning: "http://cs.elderscrolls.com/constwiki/index.php/TES4Edit_Cleaning_Guide"'
- name: Francesco's Optional New Creatures Add-On.esm
tag:
- Actors.AIPackages
- Delev
- Factions
- Invent
- Relations
- Relev
- Scripts
msg:
- condition: &fcomCond IF FILE("FCOM_Convergence.esm")
type: error
content: Delete. Not compatible with FCOM. Already integrated into FCOM.
- name: Crafting Skill Engine.esm
msg:
- condition: IF FILE("Retros Fletching and Imbueing 1.7.1_rus.esp")
type: error
content: Delete. Included in Retros Fletching and Imbueing 1.7.1_rus.esp
- name: Tamrielic Ingredients.esm
msg:
- type: say
content: Do not use Tamrielic Ingredients and COBL at the same time.
- name: Cobl Main.esm
msg:
- type: say
content: Do not use Tamrielic Ingredients and COBL at the same time.
- name: Item interchange.esm
msg:
- type: say
content: Outdated. All functions are now handled by Cobl.esm. Update to the latest versions of 'Item Interchange' and Cobl.
- name: Qarls_Harvest.esm
tag:
- Scripts
- name: DEJ Harvest - Flora.esm
msg:
- type: say
content: Obsolete. Update to 'Harvest [Flora]' v3.0.1+.
- name: Tamriel's Glittering Geology.esm
msg:
- condition: &oooCond IF FILE("Oscuro's_Oblivion_Overhaul.esm")
type: error
content: Not compatible with OOO. Already included in OOO.
- name: Oscuro's_Oblivion_Overhaul.esm
tag:
- Actors.Spells
- Graphics
- Invent
- Relations
- Scripts
- Stats
- condition: IF FILE("Mart's Monster Mod for OOO.esm") || IF VAR(FCOM)
name: -Relations
msg:
- type: say
content: Do not clean. "Dirty" edits are intentional and required for the mod to function.
- name: Mart's Monster Mod.esm
tag:
- Actors.AIData
- Actors.AIPackages
- Actors.Spells
- Actors.Stats
- Delev
- Factions
- Graphics
- Invent
- Names
- Relations
- Relev
- Scripts
- Stats
- name: Mart's Monster Mod for OOO.esm
msg:
- condition: *fcomCond
type: error
content: Delete. Not compatible with FCOM.
tag:
- Delev
- Factions
- Relations
- Relev
# The message attached to the Waalx plugin below is too vague for the specialised
# incompatibility list in MF3, and should probably be removed.
- name: Waalx Animals & Creatures.esm
msg:
- type: say
content: Incompatible with other overhaul mods.
- name: FCOM_Convergence.esm
tag:
- Delev
- Factions
- Relations
- Relev
- name: Armamentarium.esm
tag:
- Relev
- name: ArmamentariumFran.esm
tag:
- Relev
- name: ArmamentariumEnchanted.esm
msg:
- type: say
content: Outdated, included in Armamentarium Complete.
- name: bookplacing.esm
msg:
- type: warn
content: Check you are using v2+. If not, Update. v1 has a severe bug with the Mystic Emporium disappearing.
- name: No Lights Flicker.esm
msg:
- condition: &bpTweakMsg IF FILE("Bashed Patch.*\.esp")
type: say
content: Use Wrye Bash Bashed Patch tweak instead.
# If a plugin name was given for Reznod's Mannequins, the incompatibility list
# syntax could be used.
- name: TLS Modded Mannequins.esm
msg:
- type: say
content: Incompatible with Reznod's Mannequins.
- name: DKImperials.esm
tag:
- Hair
- Eyes
- name: Minotaur Race.esm
req:
- name: &OBSE ../obse_loader.exe
condition: if version("../obse_loader.exe", "0.0.18.0", >=)
display: OBSE v18+
msg:
- type: say
content: Requires 'Race Repository'
- condition: IF CHECKSUM("Minotaur Race.esm", 477340C8)
type: warn
content: 'Contains dirty edits; 7 ITM, 0 UDR records. Needs TES4Edit cleaning: "http://cs.elderscrolls.com/constwiki/index.php/TES4Edit_Cleaning_Guide"'
- name: x117race.esm
tag:
- Hair
- name: Chocolate Elves.esm
tag:
- NpcFaces
- Hair
- Eyes
- R.Relations
- name: Independent High Elf Body (Replace)(v1.1).esm
tag:
- NpcFaces
- Hair
- Eyes
- R.Relations
- name: p2p'sAshDraconian.esm
tag:
- NpcFaces
- Hair
- Eyes
- R.Relations
- name: SPTDiverseGuards-Resources.esm
msg:
- condition: *oooCond
type: say
content: Probably not compatible with OOO. Untested.
- name: John's Leveled List Overhaul.esm
tag:
- Graphics
# I've just made up the OBME DLL name and version here for an example.
- name: Diverse Magic Effects Resource.esm
req:
- name: *OBSE
condition: if version("../obse_loader.exe", "0.0.19.0", >=)
display: OBSE v19+
- name: OBSE/Plugins/obme.dll
condition: if version("../obse_loader.exe", "0.1.0.0", >=)
display: OBME v1+
+20
View File
@@ -0,0 +1,20 @@
# BOSS Settings File
# This file is written in YAML <http://www.yaml.org/>.
# If you don't know YAML, just think of a normal ini file, but replace the
# equals signs with colons followed by a space. No tabs allowed.
bEnableNetworking: true
bUseOnlineCache: true
bGetMetadataUpdates: true
bPromptMetadataSubmit: true
bAlwaysSubmitMetadata: true
sOnlineCacheURL: "http://better-oblivion-sorting-software.googlecode.com/svn/data/boss-nehrim/masterlist.txt"
sMetadataURL: "http://better-oblivion-sorting-software.googlecode.com/svn/data/boss-nehrim/masterlist.txt"
sVersionCheckerURL: "http://better-oblivion-sorting-software.googlecode.com/svn/data/boss-nehrim/masterlist.txt"
sGame: auto # auto, oblivion, skyrim, fallout3, falloutnv
sLastGame: auto # auto, oblivion, skyrim, fallout3, falloutnv
iDebugVerbosity: 0 # 0, 1, 2, 3. Logging takes place if > 0.
bDoTrialRun: false
sLanguage: eng # An ISO 639-3 language string.
+11
View File
@@ -27,6 +27,8 @@
#include <stdexcept>
#include <boost/algorithm/string.hpp>
#if _WIN32 || _WIN64
# include <Windows.h>
# include <Shlobj.h>
@@ -116,6 +118,15 @@ namespace boss {
return GamePath() / pluginsFolderName;
}
bool Game::IsActive(const std::string& plugin) const {
if (activePlugins.empty()) {
//Use libloadorder to fetch the active plugins list. Fill the set
//with the lowercased filenames.
}
return activePlugins.find(boost::to_lower_copy(plugin)) != activePlugins.end();
}
void Game::CreateBOSSGameFolder() {
//Make sure that the BOSS game path exists.
try {
+12
View File
@@ -24,7 +24,12 @@
#ifndef __BOSS_GAME__
#define __BOSS_GAME__
#include <string>
#include <stdint.h>
#include <boost/filesystem.hpp>
#include <boost/unordered_map.hpp>
#include <boost/unordered_set.hpp>
namespace boss {
@@ -44,6 +49,12 @@ namespace boss {
//Creates directory in BOSS folder for BOSS's game-specific files.
void CreateBOSSGameFolder();
bool IsActive(const std::string& plugin) const;
//Caches for condition results, active plugins and CRCs.
boost::unordered_map<std::string, bool> conditionCache; //Holds lowercased strings.
boost::unordered_map<std::string, uint32_t> crcCache; //Holds lowercased strings.
private:
uint32_t id;
std::string name;
@@ -55,6 +66,7 @@ namespace boss {
std::string pluginsFolderName;
boost::filesystem::path gamePath; //Path to the game's folder.
boost::unordered_set<std::string> activePlugins; //Holds lowercased strings.
//Can be used to get the location of the LOCALAPPDATA folder (and its Windows XP equivalent).
boost::filesystem::path GetLocalAppDataPath();
+48 -38
View File
@@ -26,6 +26,7 @@
*/
#include "helpers.h"
#include "plugin/ModFormat.h"
#include <boost/spirit/include/karma.hpp>
#include <boost/algorithm/string.hpp>
@@ -78,15 +79,15 @@ namespace boss {
}
//Converts an integer to a string using BOOST's Spirit.Karma, which is apparently a lot faster than a stringstream conversion...
std::string IntToString(const uint32_t n) {
std::string IntToString(const int n) {
string out;
back_insert_iterator<string> sink(out);
karma::generate(sink,karma::upper[karma::uint_],n);
karma::generate(sink,karma::upper[karma::int_],n);
return out;
}
//Converts an integer to a hex string using BOOST's Spirit.Karma, which is apparently a lot faster than a stringstream conversion...
std::string IntToHexString(const uint32_t n) {
std::string IntToHexString(const int n) {
string out;
back_insert_iterator<string> sink(out);
karma::generate(sink,karma::upper[karma::hex],n);
@@ -156,53 +157,58 @@ namespace boss {
Version::Version(const fs::path file) {
// LOG_TRACE("extracting version from '%s'", file.string().c_str());
if (file.extension().string() == ".esm" || file.extension().string() == ".esp") {
//Use libespm interface.
verString = ReadHeader(file).Version;
} else {
#if _WIN32 || _WIN64
DWORD dummy = 0;
DWORD size = GetFileVersionInfoSize(file.wstring().c_str(), &dummy);
DWORD dummy = 0;
DWORD size = GetFileVersionInfoSize(file.wstring().c_str(), &dummy);
if (size > 0) {
LPBYTE point = new BYTE[size];
UINT uLen;
VS_FIXEDFILEINFO *info;
string ver;
if (size > 0) {
LPBYTE point = new BYTE[size];
UINT uLen;
VS_FIXEDFILEINFO *info;
string ver;
GetFileVersionInfo(file.wstring().c_str(),0,size,point);
GetFileVersionInfo(file.wstring().c_str(),0,size,point);
VerQueryValue(point,L"\\",(LPVOID *)&info,&uLen);
VerQueryValue(point,L"\\",(LPVOID *)&info,&uLen);
DWORD dwLeftMost = HIWORD(info->dwFileVersionMS);
DWORD dwSecondLeft = LOWORD(info->dwFileVersionMS);
DWORD dwSecondRight = HIWORD(info->dwFileVersionLS);
DWORD dwRightMost = LOWORD(info->dwFileVersionLS);
DWORD dwLeftMost = HIWORD(info->dwFileVersionMS);
DWORD dwSecondLeft = LOWORD(info->dwFileVersionMS);
DWORD dwSecondRight = HIWORD(info->dwFileVersionLS);
DWORD dwRightMost = LOWORD(info->dwFileVersionLS);
delete [] point;
delete [] point;
verString = IntToString(dwLeftMost) + '.' + IntToString(dwSecondLeft) + '.' + IntToString(dwSecondRight) + '.' + IntToString(dwRightMost);
}
verString = IntToString(dwLeftMost) + '.' + IntToString(dwSecondLeft) + '.' + IntToString(dwSecondRight) + '.' + IntToString(dwRightMost);
}
#else
// ensure filename has no quote characters in it to avoid command injection attacks
if (string::npos == file.string().find('"')) {
// LOG_WARN("filename has embedded quotes; skipping to avoid command injection: '%s'", file.string().c_str());
// } else {
// command mostly borrowed from the gnome-exe-thumbnailer.sh script
// wrestool is part of the icoutils package
string cmd = "wrestool --extract --raw --type=version \"" + file.string() + "\" | tr '\\0, ' '\\t.\\0' | sed 's/\\t\\t/_/g' | tr -c -d '[:print:]' | sed -r 's/.*Version[^0-9]*([0-9]+(\\.[0-9]+)+).*/\\1/'";
// ensure filename has no quote characters in it to avoid command injection attacks
if (string::npos == file.string().find('"')) {
// LOG_WARN("filename has embedded quotes; skipping to avoid command injection: '%s'", file.string().c_str());
// } else {
// command mostly borrowed from the gnome-exe-thumbnailer.sh script
// wrestool is part of the icoutils package
string cmd = "wrestool --extract --raw --type=version \"" + file.string() + "\" | tr '\\0, ' '\\t.\\0' | sed 's/\\t\\t/_/g' | tr -c -d '[:print:]' | sed -r 's/.*Version[^0-9]*([0-9]+(\\.[0-9]+)+).*/\\1/'";
FILE *fp = popen(cmd.c_str(), "r");
FILE *fp = popen(cmd.c_str(), "r");
// read out the version string
static const uint32_t BUFSIZE = 32;
char buf[BUFSIZE];
if (NULL != fgets(buf, BUFSIZE, fp)) {
/* LOG_DEBUG("failed to extract version from '%s'", file.string().c_str());
// read out the version string
static const uint32_t BUFSIZE = 32;
char buf[BUFSIZE];
if (NULL != fgets(buf, BUFSIZE, fp)) {
/* LOG_DEBUG("failed to extract version from '%s'", file.string().c_str());
}
else {
*/ verString = string(buf);
// LOG_DEBUG("extracted version from '%s': %s", file.string().c_str(), retVal.c_str());
}
pclose(fp);
}
else {
*/ verString = string(buf);
// LOG_DEBUG("extracted version from '%s': %s", file.string().c_str(), retVal.c_str());
}
pclose(fp);
}
#endif
}
}
string Version::AsString() const {
@@ -254,6 +260,10 @@ namespace boss {
return (*this == ver || *this > ver);
}
bool Version::operator <= (Version ver) {
return (*this == ver || *this < ver);
}
bool Version::operator == (Version ver) {
return (verString == ver.AsString());
}
+4 -2
View File
@@ -28,6 +28,7 @@
#ifndef __BOSS_HELPERS__
#define __BOSS_HELPERS__
#include <stdint.h>
#include <string>
#include <boost/filesystem.hpp>
@@ -42,10 +43,10 @@ namespace boss {
uint32_t GetCrc32(const fs::path& filename);
//Converts an integer to a string using BOOST's Spirit.Karma. Faster than a stringstream conversion.
std::string IntToString(const uint32_t n);
std::string IntToString(const int n);
//Converts an integer to a hex string using BOOST's Spirit.Karma. Faster than a stringstream conversion.
std::string IntToHexString(const uint32_t n);
std::string IntToHexString(const int n);
//Converts a boolean to a string representation (true/false)
std::string BoolToString(const bool b);
@@ -71,6 +72,7 @@ namespace boss {
bool operator > (Version);
bool operator < (Version);
bool operator >= (Version);
bool operator <= (Version);
bool operator == (Version);
bool operator != (Version);
};
+8 -7
View File
@@ -22,6 +22,7 @@
*/
#include "metadata.h"
#include <boost/algorithm/string.hpp>
using namespace std;
@@ -188,37 +189,37 @@ namespace boss {
tags = t;
}
void Plugin::EvalAllConditions(const boost::filesystem::path& gamePath) {
void Plugin::EvalAllConditions(boss::Game& game) {
for (list<File>::iterator it = loadAfter.begin(); it != loadAfter.end();) {
if (!it->EvalCondition(gamePath))
if (!it->EvalCondition(game))
it = loadAfter.erase(it);
else
++it;
}
for (list<File>::iterator it = requirements.begin(); it != requirements.end();) {
if (!it->EvalCondition(gamePath))
if (!it->EvalCondition(game))
it = requirements.erase(it);
else
++it;
}
for (set<File, file_comp>::iterator it = incompatibilities.begin(); it != incompatibilities.end();) {
if (!it->EvalCondition(gamePath))
if (!it->EvalCondition(game))
incompatibilities.erase(it++);
else
++it;
}
for (list<Message>::iterator it = messages.begin(); it != messages.end();) {
if (!it->EvalCondition(gamePath))
if (!it->EvalCondition(game))
it = messages.erase(it);
else
++it;
}
for (list<Tag>::iterator it = tags.begin(); it != tags.end();) {
if (!it->EvalCondition(gamePath))
if (!it->EvalCondition(game))
it = tags.erase(it++);
else
++it;
@@ -230,6 +231,6 @@ namespace boss {
}
bool Plugin::IsRegexPlugin() const {
return name.substr(name.length()-5) == "\\.esp" || name.substr(name.length()-5) == "\\.esm";
return boost::iends_with(name, "\\.esm") || boost::iends_with(name, "\\.esp");
}
}
+4 -2
View File
@@ -23,6 +23,8 @@
#ifndef __BOSS_METADATA__
#define __BOSS_METADATA__
#include "game.h"
#include <string>
#include <list>
#include <set>
@@ -38,7 +40,7 @@ namespace boss {
ConditionalData(const std::string& condition, const std::string& data);
bool IsConditional() const;
bool EvalCondition(const boost::filesystem::path& gamePath) const;
bool EvalCondition(boss::Game& game) const;
std::string Condition() const;
std::string Data() const;
@@ -124,7 +126,7 @@ namespace boss {
void Messages(const std::list<Message>& messages);
void Tags(const std::list<Tag>& tags);
void EvalAllConditions(const boost::filesystem::path& gamePath);
void EvalAllConditions(boss::Game& game);
bool HasNameOnly() const;
bool IsRegexPlugin() const;
private:
+135 -50
View File
@@ -29,10 +29,14 @@
#endif
#include "metadata.h"
#include "helpers.h"
#include <stdexcept>
#include <stdint.h>
#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
@@ -279,50 +283,16 @@ namespace YAML {
namespace boss {
/* Conditional parser
///////////////////////////////
// Condition parser/evaluator
///////////////////////////////
Expression is of the form:
condition = keyword type
expression = -'(' condition (operator condition)* -')' operator
comp_exp = expression (operator expression)*
-'(' keyword type (operator -'(' keyword type -')' )*
keyword = if | ifnot
operator = and | or
type = file(...) | checksum(...) | version(...) | active(...)
'and' takes precedence over 'or', and brackets can also be used to indicate
overriding precedences.
Split string up into sub-strings according to the bracketing if present, then
according to operators, until each sub-string only contains one condition.
{
expression =
term
>> *(string("or") >> term)
;
term =
factor
>> *(string("and") >> factor)
factor =
condition
| '(' >> expression >> ')'
|
condition = keyword >> type
}
*/
/* Still need to add support for bracketing and evaluation of the compound
conditions. */
namespace qi = boost::spirit::qi;
namespace unicode = boost::spirit::unicode;
namespace phoenix = boost::phoenix;
template<typename Iterator, typename Skipper>
class condition_grammar : public qi::grammar<Iterator, bool(), Skipper> {
@@ -330,7 +300,14 @@ namespace boss {
condition_grammar() : condition_grammar::base_type(expression, "condition grammar") {
expression =
condition;
andStatement
>> *(qi::lit("or") >> andStatement)
;
andStatement =
condition
>> *(qi::lit("and") >> condition)
;
condition =
( qi::lit("if") >> type ) [qi::labels::_val = qi::labels::_1]
@@ -338,38 +315,144 @@ namespace boss {
;
type =
( "file(" > quotedStr > ')' ) [qi::labels::_val = true]
| ( "checksum(" > quotedStr > ',' > +unicode::xdigit > ')' ) [qi::labels::_val = true]
| ( "version(" > quotedStr > ',' > quotedStr > ',' > unicode::char_ > ')' ) [qi::labels::_val = true]
| ( "active(" > quotedStr > ')' ) [qi::labels::_val = true]
( "file(" > quotedStr > ')' ) [phoenix::bind(&condition_grammar::CheckFile, this, qi::labels::_val, qi::labels::_1)]
| ( "checksum(" > quotedStr > ',' > qi::hex > ')' ) [phoenix::bind(&condition_grammar::CheckSum, this, qi::labels::_val, qi::labels::_1, qi::labels::_2)]
| ( "version(" > quotedStr > ',' > quotedStr > ',' > unicode::char_ > ')' ) [phoenix::bind(&condition_grammar::CheckVersion, this, qi::labels::_val, qi::labels::_1, qi::labels::_2, qi::labels::_3)]
| ( "active(" > quotedStr > ')' ) [phoenix::bind(&condition_grammar::CheckActive, this, qi::labels::_val, qi::labels::_1)]
;
quotedStr = '"' > +(unicode::char_ - '"') > '"';
/* Need to add error handlers and actual type evaluation, then start on
compound conditional parsing. Need game path support. */
expression.name("expression");
condition.name("condition");
type.name("condition type");
quotedStr.name("quoted string");
qi::on_error<qi::fail>(expression, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
qi::on_error<qi::fail>(condition, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
qi::on_error<qi::fail>(type, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
qi::on_error<qi::fail>(quotedStr, phoenix::bind(&condition_grammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4));
}
void SetGame(boss::Game& g) {
game = &g;
}
private:
qi::rule<Iterator, bool(), Skipper> expression, condition, type;
qi::rule<Iterator, std::string()> quotedStr;
boss::Game * game;
//Eval's regex and exact paths. Check for files and ghosted plugins.
void CheckFile(bool& result, const std::string& file) {
if (boost::contains(file, "\\.")) { //Regex. Only supports filenames right now.
result = false;
boost::regex regex;
try {
regex = boost::regex(file, boost::regex::extended|boost::regex::icase);
} catch (boost::regex_error e) {
throw std::runtime_error("The regex string \"" + file + "\" is invalid.");
// LOG_ERROR("\"%s\" is not a valid regular expression. Item skipped.", reg.c_str());
}
for (fs::directory_iterator itr(game->DataPath()); itr != fs::directory_iterator(); ++itr) {
if (fs::is_regular_file(itr->status())) {
if (boost::regex_match(itr->path().filename().string(), regex)) {
result = true;
break;
}
}
}
//Eval's regex and exact paths.
bool FileExists(bool& result, std::string file);
} else if (IsPlugin(file))
result = boost::filesystem::exists(game->DataPath() / file) || boost::filesystem::exists(game->DataPath() / (file + ".ghost"));
else
result = boost::filesystem::exists(game->DataPath() / file);
}
void CheckSum(bool& result, const std::string& file, const uint32_t checksum) {
uint32_t crc;
boost::unordered_map<std::string,uint32_t>::iterator it = game->crcCache.find(boost::to_lower_copy(file));
if (it != game->crcCache.end())
crc = it->second;
else {
if (boost::filesystem::exists(game->DataPath() / file))
crc = GetCrc32(game->DataPath() / file);
else if (IsPlugin(file) && boost::filesystem::exists(game->DataPath() / (file + ".ghost")))
crc = GetCrc32(game->DataPath() / (file + ".ghost"));
else {
result = false;
return;
}
game->crcCache.emplace(boost::to_lower_copy(file), crc);
}
result = checksum == crc;
}
void CheckVersion(bool& result, const std::string& file, const std::string& version, const char comparator) {
CheckFile(result, file);
if (!result) {
if (comparator == '<')
result = true;
return;
}
Version givenVersion = Version(version);
Version trueVersion = Version(game->DataPath() / file);
switch (comparator) {
case '>':
if (trueVersion <= givenVersion)
result = false;
break;
case '<':
if (trueVersion >= givenVersion)
result = false;
break;
case '=':
if (trueVersion != givenVersion)
result = false;
break;
}
}
void CheckActive(bool& result, const std::string& file) {
result = game->IsActive(file);
}
bool IsPlugin(const std::string& file) {
return boost::iends_with(file, ".esm") || boost::iends_with(file, ".esp");
}
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);
throw std::runtime_error("Error parsing condition at \"" + context + "\", expected \"" + what.tag + "\"");
}
};
bool ConditionalData::EvalCondition(const boost::filesystem::path& gamePath) const {
bool ConditionalData::EvalCondition(boss::Game& game) const {
if (condition.empty())
return true;
boost::unordered_map<std::string, bool>::const_iterator it = game.conditionCache.find(boost::to_lower_copy(condition));
if (it != game.conditionCache.end())
return it->second;
condition_grammar<std::string::const_iterator, qi::space_type> grammar;
qi::space_type skipper;
std::string::const_iterator begin, end;
bool eval;
grammar.SetGame(game);
begin = condition.begin();
end = condition.end();
@@ -378,6 +461,8 @@ namespace boss {
if (!r || begin != end)
throw std::runtime_error("Parsing of condition \"" + condition + "\" failed!");
game.conditionCache.emplace(boost::to_lower_copy(condition), eval);
return eval;
}
}
+241
View File
@@ -0,0 +1,241 @@
/* BOSS
A "one-click" program for users that quickly optimises and avoids
detrimental conflicts in their TES IV: Oblivion, Nehrim - At Fate's Edge,
TES V: Skyrim, Fallout 3 and Fallout: New Vegas mod load orders.
Copyright (C) 2009-2012 BOSS Development Team.
This file is part of BOSS.
BOSS is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
BOSS is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with BOSS. If not, see
<http://www.gnu.org/licenses/>.
$Revision: 2488 $, $Date: 2011-03-27 14:31:33 +0100 (Sun, 27 Mar 2011) $
*/
#include "Types.h"
#include "ModFormat.h"
#include "VersionRegex.h"
#include <cstring>
#include <fstream>
#include <boost/regex.hpp>
#include <boost/algorithm/string/trim.hpp>
namespace boss {
using namespace std;
using boost::algorithm::trim_copy;
using boost::regex;
//
// string ParseVersion(string&):
// - Tries to extract the version string value from the given text,
// using the above defined regexes to do the dirty work.
//
string ParseVersion(const string& text){
string::const_iterator begin, end;
begin = text.begin();
end = text.end();
for(int i = 0; regex* re = version_checks[i]; i++) {
smatch what;
while (regex_search(begin, end, what, *re)) {
if (what.empty()){
continue;
}
ssub_match match = what[1];
if (!match.matched) {
continue;
}
return trim_copy(string(match.first, match.second));
}
}
return string();
}
//
// string ReadString(pointer&, maxsize):
// - Reads a consecutive array of charactes up to maxsize length and
// returns them as a new string.
//
string ReadString(char*& bufptr, ushort size){
string data;
data.reserve(size + 1);
while (char c = *bufptr++) {
data.append(1, c);
}
return data;
}
//
// T Peek<T>(pointer&):
// - Peeks into the received buffer and returns the value pointed
// converting it to the type T.
//
template <typename T>
T Peek(char* buffer) {
return *reinterpret_cast<T*>(buffer);
}
//
// T Read<T>(pointer&):
// - Tries to extract a value of the specified type T from the
// received buffer, incrementing the pointer to point past the readen
// value.
//
template <typename T>
inline T Read(char*& buffer) {
T value = Peek<T>(buffer);
buffer += sizeof(T);
return value;
}
//-
// ModHeader ReadHeader(string):
// - Parses the mod file contents and extract the header information
// returning the most important data using a ModHeader struct.
// --> see:
// http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format,
//
// and in particular this link:
// http://www.uesp.net/wiki/Tes4Mod:Mod_File_Format/TES4
//
bool IsPluginMaster(boost::filesystem::path filename) {
char buffer[MAXLENGTH];
char* bufptr = buffer;
ModHeader modHeader;
if (filename.empty())
return false;
ifstream file(filename.native().c_str(), ios_base::binary | ios_base::in);
if (file.bad())
//throw boss_error(BOSS_ERROR_FILE_READ_FAIL, filename.string());
return false;
// Reads the first MAXLENGTH bytes into the buffer
file.read(&buffer[0], sizeof(buffer));
// Check for the 'magic' marker at start
if (Read<uint>(bufptr) != Record::TES4){
return false;
}
// Next field is the total header size
/*uint headerSize =*/ Read<uint>(bufptr);
// Next comes the header record Flags
uint flags = Read<uint>(bufptr);
// LSb of this record's flags is used to indicate if the
// mod is a master or a plugin
return ((flags & 0x1) != 0);
}
ModHeader ReadHeader(boost::filesystem::path filename) {
char buffer[MAXLENGTH];
char* bufptr = buffer;
ModHeader modHeader;
ifstream file(filename.native().c_str(), ios_base::binary | ios_base::in);
modHeader.Name = filename.string();
// Reads the first MAXLENGTH bytes into the buffer
file.read(&buffer[0], sizeof(buffer));
// Check for the 'magic' marker at start
if (Read<uint>(bufptr) != Record::TES4){
return modHeader;
}
// Next field is the total header size
/*uint headerSize =*/ Read<uint>(bufptr);
// Next comes the header record Flags
uint flags = Read<uint>(bufptr);
// LSb of this record's flags is used to indicate if the
// mod is a master or a plugin
modHeader.IsMaster = (flags & 0x1) != 0;
// Next comes the FormID...
/*uint formId =*/ Read<uint>(bufptr); // skip formID
// ...and extra flags
/*uint flags2 =*/ Read<uint>(bufptr); // skip flags2
// Here the Header record starts, check for its signature 'HEDR'
if (Read<uint>(bufptr) != Record::HEDR){
return modHeader;
}
// HEDR record has fields: DataSize, Version (0.8 o 1.0), Record Count
// and Next Object Id
/*ushort dataSize =*/ Read<ushort>(bufptr);
/*float version =*/ Read<float>(bufptr);
/*int numRecords =*/ Read<int>(bufptr);
/*uint nextObjId =*/ Read<uint>(bufptr);
// Then comes the sub-records
uint signature = Read<uint>(bufptr);
// skip optional records
bool loop = true;
while (loop){
switch (signature)
{
case Record::OFST:
case Record::DELE:
bufptr += Read<ushort>(bufptr); // skip
signature = Read<uint>(bufptr);
break;
// extract author name, if present
case Record::CNAM:
modHeader.Author = ReadString(bufptr, Read<ushort>(bufptr));
signature = Read<uint>(bufptr);
break;
// extract description and version, if present
case Record::SNAM:
modHeader.Description = ReadString(bufptr, Read<ushort>(bufptr));
modHeader.Version = ParseVersion(modHeader.Description);
signature = Read<uint>(bufptr);
break;
default:
loop = false;
}
}
// We should have all the required information.
return modHeader;
}
}
+64
View File
@@ -0,0 +1,64 @@
/* BOSS
A "one-click" program for users that quickly optimises and avoids
detrimental conflicts in their TES IV: Oblivion, Nehrim - At Fate's Edge,
TES V: Skyrim, Fallout 3 and Fallout: New Vegas mod load orders.
Copyright (C) 2009-2012 BOSS Development Team.
This file is part of BOSS.
BOSS is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
BOSS is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with BOSS. If not, see
<http://www.gnu.org/licenses/>.
$Revision: 2488 $, $Date: 2011-03-27 14:31:33 +0100 (Sun, 27 Mar 2011) $
*/
#ifndef __SUPPORT_MODFORMAT__HPP__
#define __SUPPORT_MODFORMAT__HPP__
#include "Types.h"
#include <cstring>
#include <boost/filesystem.hpp>
namespace boss {
using namespace std;
// Structure for grouping the information gathered from each mod's header.
struct ModHeader {
string Name;
string Description;
string Author;
string Version;
bool IsMaster;
};
struct Record {
static const ulong TES4 = '4SET';
static const ulong HEDR = 'RDEH';
static const ulong OFST = 'TSFO';
static const ulong DELE = 'ELED';
static const ulong CNAM = 'MANC';
static const ulong SNAM = 'MANS';
};
ModHeader ReadHeader(boost::filesystem::path filename);
bool IsPluginMaster(boost::filesystem::path filename); //Shorter version of the above, to only get master flag.
}
#endif
+51
View File
@@ -0,0 +1,51 @@
/* BOSS
A "one-click" program for users that quickly optimises and avoids
detrimental conflicts in their TES IV: Oblivion, Nehrim - At Fate's Edge,
TES V: Skyrim, Fallout 3 and Fallout: New Vegas mod load orders.
Copyright (C) 2009-2012 BOSS Development Team.
This file is part of BOSS.
BOSS is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
BOSS is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with BOSS. If not, see
<http://www.gnu.org/licenses/>.
$Revision: 1679 $, $Date: 2010-10-17 20:41:49 +0100 (Sun, 17 Oct 2010) $
*/
#ifndef __SUPPORT_TYPES__HPP__
#define __SUPPORT_TYPES__HPP__
namespace boss {
//////////////////////////////////////////////////////////////////////////
// Common type definitions
//////////////////////////////////////////////////////////////////////////
typedef unsigned long ulong;
typedef unsigned int uint;
typedef unsigned short ushort;
//////////////////////////////////////////////////////////////////////////
// Constants
//////////////////////////////////////////////////////////////////////////
// An arbitrary large number. Controls the size of some buffers used to read data from files.
const ulong MAXLENGTH = 4096UL;
}
#endif
+92
View File
@@ -0,0 +1,92 @@
/* BOSS
A "one-click" program for users that quickly optimises and avoids
detrimental conflicts in their TES IV: Oblivion, Nehrim - At Fate's Edge,
TES V: Skyrim, Fallout 3 and Fallout: New Vegas mod load orders.
Copyright (C) 2009-2012 BOSS Development Team.
This file is part of BOSS.
BOSS is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
BOSS is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with BOSS. If not, see
<http://www.gnu.org/licenses/>.
$Revision: 1681 $, $Date: 2010-10-17 21:01:25 +0100 (Sun, 17 Oct 2010) $
*/
#include <string>
#include <boost/regex.hpp>
namespace boss {
using namespace std;
using namespace boost;
/// REGEX expression definition
/// Each expression is composed of three parts:
/// 1. The marker string "version", "ver", "rev", "v" or "r"
/// 2. The version string itself.
const char* regex1 =
"^(?:\\bversion\\b[ ]*(?:[:.\\-]?)|\\brevision\\b(?:[:.\\-]?))[ ]*"
"((?:alpha|beta|test|debug)?\\s*[-0-9a-zA-Z._+]+\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*))$"
;
const char* regex2 =
"(?:\\bversion\\b(?:[ :]?)|\\brevision\\b(?:[:.\\-]?))[ ]*"
"([0-9][-0-9a-zA-Z._]+\\+?)"
;
const char* regex3 =
"(?:\\bver(?:[:.]?)|\\brev(?:[:.]?))\\s*"
"([0-9][-0-9a-zA-Z._]*\\+?)"
;
// Matches "Updated: <date>" for the Bashed patch
const char* regex4 =
"(?:Updated:)\\s*"
"([-0-9aAmMpP/ :]+)$"
;
// Matches isolated versions as last resort
const char* regex5 =
"(?:(?:\\bv|\\br)(?:\\s?)(?:[-.:])?(?:\\s*))"
"((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*[0-9][-0-9a-zA-Z._]*\\+?)"
;
// Matches isolated versions as last resort
const char* regex6 =
"((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*\\b[0-9][-0-9a-zA-Z._]*\\+?)$"
;
const char* regex7 =
"(^\\bmark\\b\\s*\\b[IVX0-9][-0-9a-zA-Z._+]*\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*)?)$"
;
/// Array used to try each of the expressions defined above using
/// an iteration for each of them.
regex* version_checks[] = {
new regex(regex1, regex::icase),
new regex(regex2, regex::icase),
new regex(regex3, regex::icase),
new regex(regex4, regex::icase),
new regex(regex5, regex::icase), //This incorrectly identifies "OBSE v19" where 19 is any integer.
new regex(regex6, regex::icase), //This is responsible for metallicow's false positive.
new regex(regex7, regex::icase),
0
};
}
+45
View File
@@ -0,0 +1,45 @@
/* BOSS
A "one-click" program for users that quickly optimises and avoids
detrimental conflicts in their TES IV: Oblivion, Nehrim - At Fate's Edge,
TES V: Skyrim, Fallout 3 and Fallout: New Vegas mod load orders.
Copyright (C) 2009-2012 BOSS Development Team.
This file is part of BOSS.
BOSS is free software: you can redistribute
it and/or modify it under the terms of the GNU General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
BOSS is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with BOSS. If not, see
<http://www.gnu.org/licenses/>.
$Revision: 1681 $, $Date: 2010-10-17 21:01:25 +0100 (Sun, 17 Oct 2010) $
*/
#ifndef __SUPPORT_VERSIONREGEX__HPP__
#define __SUPPORT_VERSIONREGEX__HPP__
#include <boost/regex.hpp>
namespace boss {
using namespace boost;
/// Array used to try each of the expressions defined above using
/// an iteration for each of them.
extern regex* version_checks[];
}
#endif