2014-03-06 19:52:46 +00:00
/* LOOT
2013-09-17 07:40:35 +01:00
2014-03-06 19:47:20 +00: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>
2013-09-20 15:09:15 +01:00
#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 ;
2013-09-20 15:09:15 +01:00
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 );
2013-09-20 15:09:15 +01:00
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 {
2013-09-20 15:09:15 +01:00
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
}
2013-09-20 15:09:15 +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 ;
2013-09-20 20:33:44 +01:00
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 ) {
2013-09-20 20:33:44 +01:00
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 ];
2013-09-20 20:33:44 +01:00
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 ) {
2013-09-20 20:33:44 +01:00
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-02-04 15:50:14 +00:00
}
2014-03-07 19:24:02 +00:00
else if ( _code == Language :: english ) {
2014-02-04 15:50:14 +00:00
_name = "English" ;
2014-02-23 16:56:57 +00:00
_locale = "en" ;
2014-02-04 15:50:14 +00:00
}
2014-03-07 19:24:02 +00:00
else if ( _code == Language :: spanish ) {
2014-02-04 15:50:14 +00:00
_name = "Español" ;
2014-02-23 16:56:57 +00:00
_locale = "es" ;
2014-02-04 15:50:14 +00:00
}
2014-03-07 19:24:02 +00:00
else if ( _code == Language :: russian ) {
2014-02-04 15:50:14 +00:00
_name = "Русский" ;
2014-02-23 16:56:57 +00:00
_locale = "ru" ;
2014-02-04 15:50:14 +00:00
}
2014-03-07 19:24:02 +00:00
else if ( _code == Language :: french ) {
2014-02-04 15:50:14 +00:00
_name = "Français" ;
2014-02-23 16:56:57 +00:00
_locale = "fr" ;
2014-02-04 15:50:14 +00:00
}
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 ) {
2014-02-23 17:19:07 +00:00
_name = "Português do Brasil" ;
_locale = "pt_BR" ;
}
2013-09-17 07:40:35 +01:00
}
2014-02-04 15:50:14 +00: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 );
}
}