Files
libloot/src/backend/helpers.cpp
T

412 lines
14 KiB
C++
Raw Permalink Normal View History

2014-03-06 19:52:46 +00:00
/* LOOT
2013-09-17 07:40:35 +01:00
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
2013-09-17 07:40:35 +01:00
2014-02-02 11:04:00 +00:00
Copyright (C) 2012-2014 WrinklyNinja
2013-09-17 07:40:35 +01:00
2014-03-06 19:52:46 +00:00
This file is part of LOOT.
2013-09-17 07:40:35 +01:00
2014-03-06 19:52:46 +00:00
LOOT is free software: you can redistribute
2013-09-17 07:40:35 +01:00
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.
2014-03-06 19:52:46 +00:00
LOOT is distributed in the hope that it will
2013-09-17 07:40:35 +01:00
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
2014-03-06 19:52:46 +00:00
along with LOOT. If not, see
2013-09-17 07:40:35 +01:00
<http://www.gnu.org/licenses/>.
*/
#include "helpers.h"
#include "error.h"
#include "streams.h"
#include <boost/spirit/include/karma.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/crc.hpp>
#include <boost/regex.hpp>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/locale.hpp>
2013-09-17 07:40:35 +01:00
#include <alphanum.hpp>
#include <cstring>
#include <iostream>
#include <ctype.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sstream>
#if _WIN32 || _WIN64
# ifndef UNICODE
# define UNICODE
# endif
# ifndef _UNICODE
# define _UNICODE
# endif
# include "windows.h"
# include "shlobj.h"
#endif
#define BUFSIZE 4096
2014-03-06 20:18:49 +00:00
namespace loot {
2013-09-17 07:40:35 +01:00
using namespace std;
using boost::algorithm::replace_all;
using boost::algorithm::replace_first;
namespace karma = boost::spirit::karma;
namespace fs = boost::filesystem;
namespace lc = boost::locale;
2013-09-17 07:40:35 +01:00
/// REGEX expression definition
/// Each expression is composed of three parts:
/// 1. The marker string "version", "ver", "rev", "v" or "r"
/// 2. The version string itself.
const char* regex1 =
"^(?:\\bversion\\b[ ]*(?:[:.\\-]?)|\\brevision\\b(?:[:.\\-]?))[ ]*"
"((?:alpha|beta|test|debug)?\\s*[-0-9a-zA-Z._+]+\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*))$"
;
const char* regex2 =
"(?:\\bversion\\b(?:[ :]?)|\\brevision\\b(?:[:.\\-]?))[ ]*"
"([0-9][-0-9a-zA-Z._]+\\+?)"
;
const char* regex3 =
"(?:\\bver(?:[:.]?)|\\brev(?:[:.]?))\\s*"
"([0-9][-0-9a-zA-Z._]*\\+?)"
;
// Matches "Updated: <date>" for the Bashed patch
const char* regex4 =
"(?:Updated:)\\s*"
"([-0-9aAmMpP/ :]+)$"
;
// Matches isolated versions as last resort
const char* regex5 =
"(?:(?:\\bv|\\br)(?:\\s?)(?:[-.:])?(?:\\s*))"
"((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*[0-9][-0-9a-zA-Z._]*\\+?)"
;
// Matches isolated versions as last resort
const char* regex6 =
"((?:(?:\\balpha\\b)?|(?:\\bbeta\\b)?)\\s*\\b[0-9][-0-9a-zA-Z._]*\\+?)$"
;
const char* regex7 =
"(^\\bmark\\b\\s*\\b[IVX0-9][-0-9a-zA-Z._+]*\\s*(?:alpha|beta|test|debug)?\\s*(?:[0-9]*)?)$"
;
/// Array used to try each of the expressions defined above using
/// an iteration for each of them.
boost::regex version_checks[7] = {
boost::regex(regex1, boost::regex::icase),
boost::regex(regex2, boost::regex::icase),
boost::regex(regex3, boost::regex::icase),
boost::regex(regex4, boost::regex::icase),
boost::regex(regex5, boost::regex::icase), //This incorrectly identifies "OBSE v19" where 19 is any integer.
boost::regex(regex6, boost::regex::icase), //This is responsible for metallicow's false positive.
boost::regex(regex7, boost::regex::icase)
};
//////////////////////////////////////////////////////////////////////////
// Helper functions
//////////////////////////////////////////////////////////////////////////
//Calculate the CRC of the given file for comparison purposes.
uint32_t GetCrc32(const fs::path& filename) {
uint32_t chksum = 0;
static const size_t buffer_size = 8192;
char buffer[buffer_size];
2014-03-06 20:18:49 +00:00
loot::ifstream ifile(filename, ios::binary);
BOOST_LOG_TRIVIAL(trace) << "Calculating CRC for: " << filename.string();
2013-09-17 07:40:35 +01:00
boost::crc_32_type result;
if (ifile) {
do {
ifile.read(buffer, buffer_size);
result.process_bytes(buffer, ifile.gcount());
} while (ifile);
chksum = result.checksum();
} else {
BOOST_LOG_TRIVIAL(error) << "Unable to open \"" << filename.string() << "\" for CRC calculation.";
throw error(error::path_read_fail, (boost::format(lc::translate("Unable to open \"%1%\" for CRC calculation.")) % filename.string()).str());
2013-09-17 07:40:35 +01:00
}
BOOST_LOG_TRIVIAL(debug) << "CRC32(\"" << filename.string() << "\"):" << chksum;
2013-09-17 07:40:35 +01:00
return chksum;
}
//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 int n) {
string out;
back_insert_iterator<string> sink(out);
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 int n) {
string out;
back_insert_iterator<string> sink(out);
karma::generate(sink,karma::upper[karma::hex],n);
return out;
}
//Get registry subkey value string.
string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value) {
#if _WIN32 || _WIN64
HKEY hKey, key;
DWORD BufferSize = 4096;
wchar_t val[4096];
if (keyStr == "HKEY_CLASSES_ROOT")
key = HKEY_CLASSES_ROOT;
else if (keyStr == "HKEY_CURRENT_CONFIG")
key = HKEY_CURRENT_CONFIG;
else if (keyStr == "HKEY_CURRENT_USER")
key = HKEY_CURRENT_USER;
else if (keyStr == "HKEY_LOCAL_MACHINE")
key = HKEY_LOCAL_MACHINE;
else if (keyStr == "HKEY_USERS")
key = HKEY_USERS;
BOOST_LOG_TRIVIAL(trace) << "Getting registry object for key and subkey: " << keyStr << " + " << subkey;
2013-09-17 07:40:35 +01:00
LONG ret = RegOpenKeyEx(key, fs::path(subkey).wstring().c_str(), 0, KEY_READ|KEY_WOW64_32KEY, &hKey);
if (ret == ERROR_SUCCESS) {
BOOST_LOG_TRIVIAL(trace) << "Getting value for entry: " << value;
2013-09-17 07:40:35 +01:00
ret = RegQueryValueEx(hKey, fs::path(value).wstring().c_str(), NULL, NULL, (LPBYTE)&val, &BufferSize);
RegCloseKey(hKey);
if (ret == ERROR_SUCCESS)
return fs::path(val).string(); //Easiest way to convert from wide to narrow character strings.
else
return "";
} else
return "";
#else
return "";
#endif
}
boost::filesystem::path GetLocalAppDataPath() {
#if _WIN32 || _WIN64
2014-01-12 11:29:49 +00:00
HWND owner = 0;
2013-09-17 07:40:35 +01:00
TCHAR path[MAX_PATH];
BOOST_LOG_TRIVIAL(trace) << "Getting path to %LOCALAPPDATA%.";
2013-09-17 07:40:35 +01:00
HRESULT res = SHGetFolderPath(owner, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path);
if (res == S_OK)
return fs::path(path);
else
return fs::path("");
#else
return fs::path("");
#endif
}
//Turns an absolute filesystem path into a valid file:// URL.
std::string ToFileURL(const fs::path& file) {
BOOST_LOG_TRIVIAL(trace) << "Converting file path " << file << " to a URL.";
2014-01-29 14:52:02 +00:00
return "file:///" + file.string(); //Seems that we don't need to worry about encoding, tested with Unicode paths.
2013-09-17 07:40:35 +01:00
}
2014-02-04 19:58:30 +00:00
Language::Language(const unsigned int code) {
2014-02-04 20:27:24 +00:00
Construct(code);
2014-02-04 19:58:30 +00:00
}
2014-03-28 10:43:53 +00:00
Language::Language(const std::string& nameOrCode) {
if (nameOrCode == Language(Language::english).Name() || nameOrCode == Language(Language::english).Locale())
2014-03-07 19:24:02 +00:00
Construct(Language::english);
2014-03-28 10:43:53 +00:00
else if (nameOrCode == Language(Language::spanish).Name() || nameOrCode == Language(Language::spanish).Locale())
2014-03-07 19:24:02 +00:00
Construct(Language::spanish);
2014-03-28 10:43:53 +00:00
else if (nameOrCode == Language(Language::russian).Name() || nameOrCode == Language(Language::russian).Locale())
2014-03-07 19:24:02 +00:00
Construct(Language::russian);
2014-03-28 10:43:53 +00:00
else if (nameOrCode == Language(Language::french).Name() || nameOrCode == Language(Language::french).Locale())
2014-03-07 19:24:02 +00:00
Construct(Language::french);
2014-03-28 10:43:53 +00:00
else if (nameOrCode == Language(Language::chinese).Name() || nameOrCode == Language(Language::chinese).Locale())
2014-03-07 19:24:02 +00:00
Construct(Language::chinese);
2014-03-28 10:43:53 +00:00
else if (nameOrCode == Language(Language::polish).Name() || nameOrCode == Language(Language::polish).Locale())
2014-03-07 19:24:02 +00:00
Construct(Language::polish);
2014-03-28 10:43:53 +00:00
else if (nameOrCode == Language(Language::brazilian_portuguese).Name() || nameOrCode == Language(Language::brazilian_portuguese).Locale())
2014-03-07 19:24:02 +00:00
Construct(Language::brazilian_portuguese);
2014-02-04 19:58:30 +00:00
else
2014-03-07 19:24:02 +00:00
Construct(Language::any);
2014-02-04 19:58:30 +00:00
}
void Language::Construct(const unsigned int code) {
_code = code;
2014-03-07 19:24:02 +00:00
if (_code == Language::any) {
2014-02-04 19:58:30 +00:00
_name = boost::locale::translate("None Specified");
2014-02-23 16:56:57 +00:00
_locale = "en";
}
2014-03-07 19:24:02 +00:00
else if (_code == Language::english) {
_name = "English";
2014-02-23 16:56:57 +00:00
_locale = "en";
}
2014-03-07 19:24:02 +00:00
else if (_code == Language::spanish) {
_name = "Español";
2014-02-23 16:56:57 +00:00
_locale = "es";
}
2014-03-07 19:24:02 +00:00
else if (_code == Language::russian) {
_name = "Русский";
2014-02-23 16:56:57 +00:00
_locale = "ru";
}
2014-03-07 19:24:02 +00:00
else if (_code == Language::french) {
_name = "Français";
2014-02-23 16:56:57 +00:00
_locale = "fr";
}
2014-03-07 19:24:02 +00:00
else if (_code == Language::chinese) {
2014-02-04 20:27:24 +00:00
_name = "简体中文";
2014-02-23 16:56:57 +00:00
_locale = "zh_CN";
2014-02-04 20:27:24 +00:00
}
2014-03-07 19:24:02 +00:00
else if (_code == Language::polish) {
2014-02-08 15:27:57 +01:00
_name = "Polski";
2014-02-23 16:56:57 +00:00
_locale = "pl";
2014-02-08 15:27:57 +01:00
}
2014-03-07 19:24:02 +00:00
else if (_code == Language::brazilian_portuguese) {
_name = "Português do Brasil";
_locale = "pt_BR";
}
2013-09-17 07:40:35 +01:00
}
unsigned int Language::Code() const {
return _code;
}
std::string Language::Name() const {
return _name;
}
2014-02-04 19:19:40 +00:00
std::string Language::Locale() const {
return _locale;
}
2013-09-17 07:40:35 +01:00
//////////////////////////////
// Version Class Functions
//////////////////////////////
Version::Version() {}
Version::Version(const std::string& ver)
: verString(ver) {}
Version::Version(const fs::path& file) {
#if _WIN32 || _WIN64
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;
GetFileVersionInfo(file.wstring().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 = IntToString(dwLeftMost) + '.' + IntToString(dwSecondLeft) + '.' + IntToString(dwSecondRight) + '.' + IntToString(dwRightMost);
}
#else
// ensure filename has no quote characters in it to avoid command injection attacks
if (string::npos != file.string().find('"')) {
// command mostly borrowed from the gnome-exe-thumbnailer.sh script
// wrestool is part of the icoutils package
string cmd = "wrestool --extract --raw --type=version \"" + file.string() + "\" | tr '\\0, ' '\\t.\\0' | sed 's/\\t\\t/_/g' | tr -c -d '[:print:]' | sed -r 's/.*Version[^0-9]*([0-9]+(\\.[0-9]+)+).*/\\1/'";
FILE *fp = popen(cmd.c_str(), "r");
// read out the version string
static const uint32_t BUFSIZE = 32;
char buf[BUFSIZE];
if (NULL != fgets(buf, BUFSIZE, fp)) {
verString = string(buf);
}
pclose(fp);
}
#endif
}
Version::Version(const Plugin& plugin) {
verString = plugin.Version();
}
string Version::AsString() const {
return verString;
}
bool Version::operator < (Version ver) {
//Version string could have a wide variety of formats. Use regex to choose specific comparison types.
boost::regex reg1("(\\d+\\.?)+"); //a.b.c.d.e.f.... where the letters are all integers, and 'a' is the shortest possible match.
//boost::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 (boost::regex_match(verString, reg1) && boost::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 > (Version ver) {
return (*this != ver && !(*this < ver));
}
bool Version::operator >= (Version ver) {
return (*this == ver || *this > ver);
}
bool Version::operator <= (Version ver) {
return (*this == ver || *this < ver);
}
bool Version::operator == (Version ver) {
return (verString == ver.AsString());
}
bool Version::operator != (Version ver) {
return !(*this == ver);
}
}