2014-03-06 19:52:46 +00:00
/* LOOT
2013-01-06 13:52:31 +00:00
2014-03-06 19:47:20 +00:00
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
Fallout: New Vegas.
2013-01-06 13:52:31 +00:00
2014-02-02 11:04:00 +00:00
Copyright (C) 2013-2014 WrinklyNinja
2013-01-06 13:52:31 +00:00
2014-03-06 19:52:46 +00:00
This file is part of LOOT.
2013-01-06 13:52:31 +00:00
2014-03-06 19:52:46 +00:00
LOOT is free software: you can redistribute
2013-01-06 13:52:31 +00: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-01-06 13:52:31 +00: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-01-06 13:52:31 +00:00
<http://www.gnu.org/licenses/>.
*/
#include "api.h"
2013-06-05 10:09:14 +01:00
#include "../backend/game.h"
#include "../backend/metadata.h"
#include "../backend/parsers.h"
#include "../backend/generators.h"
#include "../backend/error.h"
2013-07-22 10:38:30 +01:00
#include "../backend/streams.h"
2013-01-12 23:03:29 +00:00
#include <yaml-cpp/yaml.h>
#include <algorithm>
#include <clocale>
#include <list>
#include <vector>
2014-06-21 21:06:28 +01:00
#include <regex>
#include <unordered_set>
#include <unordered_map>
2013-01-12 23:03:29 +00:00
2013-01-15 22:31:36 +00:00
#include <boost/algorithm/string.hpp>
2013-01-12 23:03:29 +00:00
#include <boost/filesystem/detail/utf8_codecvt_facet.hpp>
#include <boost/filesystem.hpp>
2014-03-06 20:18:49 +00:00
const unsigned int loot_ok = loot :: error :: ok ;
const unsigned int loot_error_liblo_error = loot :: error :: liblo_error ;
const unsigned int loot_error_file_write_fail = loot :: error :: path_write_fail ;
const unsigned int loot_error_parse_fail = loot :: error :: path_read_fail ;
const unsigned int loot_error_condition_eval_fail = loot :: error :: condition_eval_fail ;
const unsigned int loot_error_regex_eval_fail = loot :: error :: regex_eval_fail ;
const unsigned int loot_error_no_mem = loot :: error :: no_mem ;
const unsigned int loot_error_invalid_args = loot :: error :: invalid_args ;
const unsigned int loot_error_no_tag_map = loot :: error :: no_tag_map ;
const unsigned int loot_error_path_not_found = loot :: error :: path_not_found ;
const unsigned int loot_error_no_game_detected = loot :: error :: no_game_detected ;
const unsigned int loot_error_windows_error = loot :: error :: windows_error ;
const unsigned int loot_error_sorting_error = loot :: error :: sorting_error ;
const unsigned int loot_return_max = loot_error_sorting_error ;
2013-01-12 23:03:29 +00:00
// The following are the games identifiers used by the API.
2014-03-07 19:24:02 +00:00
const unsigned int loot_game_tes4 = loot :: Game :: tes4 ;
const unsigned int loot_game_tes5 = loot :: Game :: tes5 ;
const unsigned int loot_game_fo3 = loot :: Game :: fo3 ;
const unsigned int loot_game_fonv = loot :: Game :: fonv ;
2013-01-12 23:03:29 +00:00
2014-03-06 19:52:46 +00:00
// LOOT message types.
2014-03-07 19:24:02 +00:00
const unsigned int loot_message_say = loot :: Message :: say ;
const unsigned int loot_message_warn = loot :: Message :: warn ;
const unsigned int loot_message_error = loot :: Message :: error ;
const unsigned int loot_message_tag = loot :: Message :: tag ;
2013-06-05 10:09:14 +01:00
2014-03-06 19:52:46 +00:00
// LOOT message languages.
2014-03-07 19:24:02 +00:00
const unsigned int loot_lang_any = loot :: Language :: any ;
const unsigned int loot_lang_english = loot :: Language :: english ;
const unsigned int loot_lang_spanish = loot :: Language :: spanish ;
const unsigned int loot_lang_russian = loot :: Language :: russian ;
const unsigned int loot_lang_french = loot :: Language :: french ;
const unsigned int loot_lang_chinese = loot :: Language :: chinese ;
const unsigned int loot_lang_polish = loot :: Language :: polish ;
const unsigned int loot_lang_brazilian_portuguese = loot :: Language :: brazilian_portuguese ;
2014-06-05 17:22:36 +03:00
const unsigned int loot_lang_finnish = loot :: Language :: finnish ;
2014-07-05 18:24:46 +02:00
const unsigned int loot_lang_german = loot :: Language :: german ;
2014-01-12 11:37:11 +00:00
2014-03-06 19:52:46 +00:00
// LOOT cleanliness codes.
2014-03-06 20:18:49 +00:00
const unsigned int loot_needs_cleaning_no = 0 ;
const unsigned int loot_needs_cleaning_yes = 1 ;
const unsigned int loot_needs_cleaning_unknown = 2 ;
2013-06-05 10:09:14 +01:00
2013-01-12 23:03:29 +00:00
2014-03-06 20:18:49 +00:00
struct _loot_db_int {
_loot_db_int ()
2014-06-21 23:45:40 +01:00
: extTagMap ( nullptr ),
extAddedTagIds ( nullptr ),
extRemovedTagIds ( nullptr ),
extMessageArray ( nullptr ),
2013-01-12 23:03:29 +00:00
extMessageArraySize ( 0 ) {
2013-07-27 09:26:55 +01:00
2014-03-06 20:18:49 +00:00
extMessage . type = loot_message_say ;
2014-06-21 23:45:40 +01:00
extMessage . message = nullptr ;
2013-01-12 23:03:29 +00:00
}
2014-03-06 20:18:49 +00:00
~ _loot_db_int () {
2013-01-12 23:03:29 +00:00
delete [] extAddedTagIds ;
delete [] extRemovedTagIds ;
2013-07-27 09:26:55 +01:00
delete [] extMessage . message ;
2013-01-12 23:03:29 +00:00
2014-06-21 23:45:40 +01:00
if ( extTagMap != nullptr ) {
2013-01-12 23:03:29 +00:00
for ( size_t i = 0 ; i < bashTagMap . size (); i ++ )
delete [] extTagMap [ i ]; //Gotta clear those allocated strings.
delete [] extTagMap ;
}
2014-06-21 23:45:40 +01:00
if ( extMessageArray != nullptr ) {
2013-01-12 23:03:29 +00:00
for ( size_t i = 0 ; i < extMessageArraySize ; i ++ )
delete [] extMessageArray [ i ]. message ; //Gotta clear those allocated strings.
delete [] extMessageArray ;
}
}
2014-03-06 20:18:49 +00:00
loot :: Game game ;
std :: list < loot :: Plugin > metadata , rawMetadata , userMetadata , rawUserMetadata ;
2013-01-12 23:03:29 +00:00
2014-06-21 23:33:44 +01:00
std :: unordered_map < std :: string , unsigned int > bashTagMap ;
2013-01-12 23:03:29 +00:00
char ** extTagMap ;
unsigned int * extAddedTagIds ;
unsigned int * extRemovedTagIds ;
2014-03-06 20:18:49 +00:00
loot_message extMessage ;
loot_message * extMessageArray ;
2013-01-12 23:03:29 +00:00
size_t extMessageArraySize ;
};
2014-06-21 23:45:40 +01:00
char * extMessageStr = nullptr ;
2013-01-12 23:03:29 +00:00
// std::string to null-terminated char string converter.
char * ToNewCString ( std :: string str ) {
char * p = new char [ str . length () + 1 ];
return strcpy ( p , str . c_str ());
}
2013-01-06 13:52:31 +00:00
2014-03-06 20:18:49 +00:00
unsigned int c_error ( const loot :: error & e ) {
2014-02-02 15:37:46 +00:00
delete [] extMessageStr ;
try {
extMessageStr = new char [ strlen ( e . what ()) + 1 ];
strcpy ( extMessageStr , e . what ());
}
2014-05-10 16:41:08 +01:00
catch ( std :: bad_alloc & /*e*/ ) {
2014-06-21 23:45:40 +01:00
extMessageStr = nullptr ;
2014-02-02 15:37:46 +00:00
}
return e . code ();
}
unsigned int c_error ( const unsigned int code , const std :: string & what ) {
2014-03-06 20:18:49 +00:00
return c_error ( loot :: error ( code , what . c_str ()));
2014-02-02 15:37:46 +00:00
}
2013-07-31 21:57:27 +01:00
2013-01-06 13:52:31 +00:00
//////////////////////////////
// Error Handling Functions
//////////////////////////////
// Outputs a string giving the details of the last time an error or
// warning return code was returned by a function. The string exists
// until this function is called again or until CleanUpAPI is called.
2014-03-06 20:18:49 +00:00
LOOT_API unsigned int loot_get_error_message ( const char ** const message ) {
2014-06-21 23:45:40 +01:00
if ( message == nullptr )
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Null message pointer passed." );
2013-01-06 13:52:31 +00:00
2013-01-16 15:56:20 +00:00
* message = extMessageStr ;
2013-01-12 23:03:29 +00:00
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-01-12 23:03:29 +00:00
}
// Frees memory allocated to error string.
2014-03-06 20:18:49 +00:00
LOOT_API void loot_cleanup () {
2013-01-16 15:56:20 +00:00
delete [] extMessageStr ;
2014-06-21 23:45:40 +01:00
extMessageStr = nullptr ;
2013-01-06 13:52:31 +00:00
}
//////////////////////////////
// Version Functions
//////////////////////////////
2014-03-06 19:52:46 +00:00
// Returns whether this version of LOOT supports the API from the given
// LOOT version. Abstracts LOOT API stability policy away from clients.
2014-03-06 20:18:49 +00:00
LOOT_API bool loot_is_compatible ( const unsigned int versionMajor , const unsigned int versionMinor , const unsigned int versionPatch ) {
return versionMajor == loot :: g_version_major && versionMinor == loot :: g_version_minor ;
2013-01-06 13:52:31 +00:00
}
2014-03-06 19:52:46 +00:00
// Returns the version string for this version of LOOT.
2013-01-06 13:52:31 +00:00
// The string exists until this function is called again or until
// CleanUpAPI is called.
2014-03-06 20:18:49 +00:00
LOOT_API unsigned int loot_get_version ( unsigned int * const versionMajor , unsigned int * const versionMinor , unsigned int * const versionPatch ) {
2014-06-21 23:45:40 +01:00
if ( versionMajor == nullptr || versionMinor == nullptr || versionPatch == nullptr )
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Null pointer passed." );
2013-01-06 13:52:31 +00:00
2014-03-06 20:18:49 +00:00
* versionMajor = loot :: g_version_major ;
* versionMinor = loot :: g_version_minor ;
* versionPatch = loot :: g_version_patch ;
2013-01-12 23:03:29 +00:00
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-01-06 13:52:31 +00:00
}
////////////////////////////////////
// Lifecycle Management Functions
////////////////////////////////////
// Explicitly manage database lifetime. Allows clients to free memory when
// they want/need to. clientGame sets the game the DB is for, and dataPath
// is the path to that game's Data folder, and is case-sensitive if the
// underlying filesystem is case-sensitive. This function also checks that
// plugins.txt and loadorder.txt (if they both exist) are in sync. If
2014-06-21 23:45:40 +01:00
// dataPath == nullptr then the API will attempt to detect the data path of
2013-01-06 13:52:31 +00:00
// the specified game.
2014-03-06 20:18:49 +00:00
LOOT_API unsigned int loot_create_db ( loot_db * const db , const unsigned int clientGame , const char * const gamePath ) {
2014-06-21 23:45:40 +01:00
if ( db == nullptr || ( clientGame != loot_game_tes4 && clientGame != loot_game_tes5 && clientGame != loot_game_fo3 && clientGame != loot_game_fonv ))
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Null pointer passed." );
2013-01-06 13:52:31 +00:00
2013-01-12 23:03:29 +00:00
//Set the locale to get encoding conversions working correctly.
std :: setlocale ( LC_CTYPE , "" );
std :: locale global_loc = std :: locale ();
std :: locale loc ( global_loc , new boost :: filesystem :: detail :: utf8_codecvt_facet ());
boost :: filesystem :: path :: imbue ( loc );
2014-01-12 11:37:11 +00:00
//Disable logging or else stdout will get overrun.
boost :: log :: core :: get () -> set_logging_enabled ( false );
2013-01-12 23:03:29 +00:00
std :: string game_path = "" ;
2014-06-21 23:45:40 +01:00
if ( gamePath != nullptr )
2013-01-12 23:03:29 +00:00
game_path = gamePath ;
2014-03-06 20:18:49 +00:00
loot :: Game game ;
2013-01-12 23:03:29 +00:00
try {
2014-03-06 20:18:49 +00:00
game = loot :: Game ( clientGame ). SetPath ( game_path ). Init (); //This also checks to see if the game is installed if game_path is empty and throws an exception if it is not detected. It also creates a folder in %LOCALAPPDATA% and reads the active plugins list, but that shouldn't be an issue.
} catch ( loot :: error & e ) {
2014-02-02 15:37:46 +00:00
return c_error ( e );
2013-01-12 23:03:29 +00:00
}
2014-03-06 20:18:49 +00:00
loot_db retVal ;
2013-01-12 23:03:29 +00:00
try {
2014-03-06 20:18:49 +00:00
retVal = new _loot_db_int ;
2013-01-16 15:56:20 +00:00
} catch ( std :: bad_alloc & e ) {
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_no_mem , e . what ());
2013-01-12 23:03:29 +00:00
}
retVal -> game = game ;
* db = retVal ;
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-01-06 13:52:31 +00:00
}
// Destroys the given DB, freeing any memory allocated as part of its use.
2014-03-06 20:18:49 +00:00
LOOT_API void loot_destroy_db ( loot_db db ) {
2013-01-12 23:03:29 +00:00
delete db ;
2013-01-06 13:52:31 +00:00
}
///////////////////////////////////
// Database Loading Functions
///////////////////////////////////
// Loads the masterlist and userlist from the paths specified.
// Can be called multiple times. On error, the database is unchanged.
// Paths are case-sensitive if the underlying filesystem is case-sensitive.
// masterlistPath and userlistPath are files.
2014-03-06 20:18:49 +00:00
LOOT_API unsigned int loot_load_lists ( loot_db db , const char * const masterlistPath ,
2013-01-16 15:56:20 +00:00
const char * const userlistPath ) {
2014-06-21 23:45:40 +01:00
if ( db == nullptr || masterlistPath == nullptr )
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Null pointer passed." );
2013-01-06 13:52:31 +00:00
2014-03-06 20:18:49 +00:00
std :: list < loot :: Plugin > temp ;
std :: list < loot :: Plugin > userTemp ;
2013-01-12 23:03:29 +00:00
2013-07-27 08:04:34 +01:00
try {
2014-01-19 07:45:13 +00:00
if ( boost :: filesystem :: exists ( masterlistPath )) {
2014-05-08 18:58:55 +01:00
loot :: ifstream in ( masterlistPath );
YAML :: Node tempNode = YAML :: Load ( in );
in . close ();
temp = tempNode [ "plugins" ]. as < std :: list < loot :: Plugin > > ();
2013-07-27 08:04:34 +01:00
}
2014-02-02 15:37:46 +00:00
} catch ( std :: exception & e ) {
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_parse_fail , e . what ());
2013-07-27 08:04:34 +01:00
}
2014-01-19 07:45:13 +00:00
try {
2014-06-21 23:45:40 +01:00
if ( userlistPath != nullptr ) {
2014-01-19 07:45:13 +00:00
if ( boost :: filesystem :: exists ( userlistPath )) {
if ( boost :: algorithm :: iends_with ( userlistPath , ".yaml" )) {
2014-03-06 20:18:49 +00:00
loot :: ifstream in ( userlistPath );
2014-01-29 15:25:15 +00:00
YAML :: Node tempNode = YAML :: Load ( in );
in . close ();
2014-03-06 20:18:49 +00:00
userTemp = tempNode [ "plugins" ]. as < std :: list < loot :: Plugin > > ();
2014-01-19 07:45:13 +00:00
}
2014-01-13 02:34:51 +00:00
}
2014-01-12 11:37:11 +00:00
}
2014-01-19 07:45:13 +00:00
} catch ( YAML :: Exception & e ) {
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_parse_fail , e . what ());
2013-01-12 23:03:29 +00:00
}
2013-01-13 10:50:40 +00:00
//Also free memory.
db -> bashTagMap . clear ();
delete [] db -> extAddedTagIds ;
delete [] db -> extRemovedTagIds ;
2014-06-21 23:45:40 +01:00
if ( db -> extTagMap != nullptr ) {
2013-01-13 10:50:40 +00:00
for ( size_t i = 0 ; i < db -> bashTagMap . size (); i ++ )
delete [] db -> extTagMap [ i ]; //Gotta clear those allocated strings.
delete [] db -> extTagMap ;
}
2014-06-21 23:45:40 +01:00
if ( db -> extMessageArray != nullptr ) {
2013-01-13 10:50:40 +00:00
for ( size_t i = 0 ; i < db -> extMessageArraySize ; i ++ )
delete [] db -> extMessageArray [ i ]. message ; //Gotta clear those allocated strings.
delete [] db -> extMessageArray ;
}
2014-06-21 23:45:40 +01:00
db -> extAddedTagIds = nullptr ;
db -> extRemovedTagIds = nullptr ;
db -> extTagMap = nullptr ;
db -> extMessageArray = nullptr ;
2014-02-02 15:37:46 +00:00
2013-01-13 10:50:40 +00:00
db -> rawMetadata = temp ;
2013-01-12 23:03:29 +00:00
db -> metadata = temp ;
db -> userMetadata = userTemp ;
2013-01-13 10:50:40 +00:00
db -> rawUserMetadata = userTemp ;
2013-01-12 23:03:29 +00:00
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-01-06 13:52:31 +00:00
}
// Evaluates all conditional lines and regex mods the loaded masterlist.
// This exists so that Load() doesn't need to be called whenever the mods
// installed are changed. Evaluation does not take place unless this function
// is called. Repeated calls re-evaluate the masterlist from scratch each time,
// ignoring the results of any previous evaluations. Paths are case-sensitive
// if the underlying filesystem is case-sensitive.
2014-03-06 20:18:49 +00:00
LOOT_API unsigned int loot_eval_lists ( loot_db db , const unsigned int language ) {
2014-06-21 23:45:40 +01:00
if ( db == nullptr )
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Null pointer passed." );
2013-01-06 13:52:31 +00:00
2014-03-06 20:18:49 +00:00
std :: list < loot :: Plugin > temp = db -> rawMetadata ;
2013-01-12 23:03:29 +00:00
try {
2013-01-13 10:50:40 +00:00
db -> game . RefreshActivePluginsList ();
2014-06-21 23:33:44 +01:00
for ( auto it = temp . begin (); it != temp . end ();) {
2013-06-05 10:09:14 +01:00
it -> EvalAllConditions ( db -> game , language );
2013-01-13 10:50:40 +00:00
if ( it -> IsRegexPlugin ()) {
boost :: regex regex ;
try {
2013-09-18 13:08:34 +01:00
regex = boost :: regex ( it -> Name (), boost :: regex :: perl | boost :: regex :: icase );
2013-01-16 15:56:20 +00:00
} catch ( boost :: regex_error & e ) {
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_regex_eval_fail , e . what ());
2013-01-13 10:50:40 +00:00
}
for ( boost :: filesystem :: directory_iterator itr ( db -> game . DataPath ()); itr != boost :: filesystem :: directory_iterator (); ++ itr ) {
const std :: string filename = itr -> path (). filename (). string ();
if ( boost :: regex_match ( filename , regex )) {
2014-03-06 20:18:49 +00:00
loot :: Plugin p = * it ;
2013-01-13 10:50:40 +00:00
p . Name ( filename );
temp . push_back ( p );
}
}
2014-01-27 18:40:36 +00:00
it = temp . erase ( it );
} else {
++ it ;
2013-01-13 10:50:40 +00:00
}
2013-01-12 23:03:29 +00:00
}
2014-03-06 20:18:49 +00:00
} catch ( loot :: error & e ) {
2014-02-02 15:37:46 +00:00
return c_error ( e );
2013-01-12 23:03:29 +00:00
}
db -> metadata = temp ;
2013-01-13 10:50:40 +00:00
temp = db -> rawUserMetadata ;
2013-01-12 23:03:29 +00:00
try {
2014-06-21 23:33:44 +01:00
for ( auto it = temp . begin (); it != temp . end ();) {
2013-06-05 10:09:14 +01:00
it -> EvalAllConditions ( db -> game , language );
2013-01-13 10:50:40 +00:00
if ( it -> IsRegexPlugin ()) {
boost :: regex regex ;
try {
2013-09-18 13:08:34 +01:00
regex = boost :: regex ( it -> Name (), boost :: regex :: perl | boost :: regex :: icase );
2013-01-16 15:56:20 +00:00
} catch ( boost :: regex_error & e ) {
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_regex_eval_fail , e . what ());
2013-01-13 10:50:40 +00:00
}
for ( boost :: filesystem :: directory_iterator itr ( db -> game . DataPath ()); itr != boost :: filesystem :: directory_iterator (); ++ itr ) {
const std :: string filename = itr -> path (). filename (). string ();
if ( boost :: regex_match ( filename , regex )) {
2014-03-06 20:18:49 +00:00
loot :: Plugin p = * it ;
2013-01-13 10:50:40 +00:00
p . Name ( filename );
temp . push_back ( p );
}
}
2014-01-27 18:40:36 +00:00
it = temp . erase ( it );
} else {
++ it ;
2013-01-13 10:50:40 +00:00
}
2013-01-12 23:03:29 +00:00
}
2014-03-06 20:18:49 +00:00
} catch ( loot :: error & e ) {
2014-02-02 15:37:46 +00:00
return c_error ( e );
2013-01-12 23:03:29 +00:00
}
db -> userMetadata = temp ;
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-01-06 13:52:31 +00:00
}
//////////////////////////
// DB Access Functions
//////////////////////////
// Returns an array of the Bash Tags encounterred when loading the masterlist
// and userlist, and the number of tags in the returned array. The array and
// its contents are static and should not be freed by the client.
2014-03-06 20:18:49 +00:00
LOOT_API unsigned int loot_get_tag_map ( loot_db db , char *** const tagMap , size_t * const numTags ) {
2014-06-21 23:45:40 +01:00
if ( db == nullptr || tagMap == nullptr || numTags == nullptr )
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Null pointer passed." );
2013-01-06 13:52:31 +00:00
2013-01-12 23:03:29 +00:00
//Clear existing array allocation.
2014-06-21 23:45:40 +01:00
if ( db -> extTagMap != nullptr ) {
2013-01-12 23:03:29 +00:00
for ( size_t i = 0 , max = db -> bashTagMap . size (); i < max ; ++ i ) {
delete [] db -> extTagMap [ i ];
}
delete [] db -> extTagMap ;
2014-06-21 23:45:40 +01:00
db -> extTagMap = nullptr ;
2013-01-12 23:03:29 +00:00
}
//Initialise output.
2014-06-21 23:45:40 +01:00
* tagMap = nullptr ;
2013-01-12 23:03:29 +00:00
* numTags = 0 ;
2014-06-21 23:33:44 +01:00
std :: unordered_set < std :: string > allTags ;
2013-01-12 23:03:29 +00:00
2014-06-21 23:33:44 +01:00
for ( const auto & plugin : db -> metadata ) {
std :: set < loot :: Tag > tags ( plugin . Tags ());
for ( const auto & tag : tags ) {
allTags . insert ( tag . Name ());
2013-01-12 23:03:29 +00:00
}
}
2014-06-21 23:33:44 +01:00
for ( const auto & plugin : db -> userMetadata ) {
std :: set < loot :: Tag > tags ( plugin . Tags ());
for ( const auto & tag : tags ) {
allTags . insert ( tag . Name ());
2013-01-12 23:03:29 +00:00
}
}
if ( allTags . empty ())
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-01-12 23:03:29 +00:00
try {
db -> extTagMap = new char * [ allTags . size ()];
2013-01-16 15:56:20 +00:00
} catch ( std :: bad_alloc & e ) {
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_no_mem , e . what ());
2013-01-12 23:03:29 +00:00
}
unsigned int UID = 0 ;
try {
2014-06-21 23:33:44 +01:00
for ( const auto & tag : allTags ) {
db -> bashTagMap . emplace ( tag , UID );
2013-01-12 23:03:29 +00:00
//Also allocate memory.
2014-06-21 23:33:44 +01:00
db -> extTagMap [ UID ] = ToNewCString ( tag );
2013-01-12 23:03:29 +00:00
UID ++ ;
}
2013-01-16 15:56:20 +00:00
} catch ( std :: bad_alloc & e ) {
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_no_mem , e . what ());
2013-01-12 23:03:29 +00:00
}
* tagMap = db -> extTagMap ;
* numTags = allTags . size ();
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-01-06 13:52:31 +00:00
}
// Returns arrays of Bash Tag UIDs for Bash Tags suggested for addition and removal
2014-03-06 19:52:46 +00:00
// by LOOT's masterlist and userlist, and the number of tags in each array.
2013-01-06 13:52:31 +00:00
// The returned arrays are valid until the db is destroyed or until the Load
// function is called. The arrays should not be freed by the client. modName is
// case-insensitive. If no Tags are found for an array, the array pointer (*tagIds)
2014-06-21 23:45:40 +01:00
// will be nullptr. The userlistModified bool is true if the userlist contains Bash Tag
2013-01-06 13:52:31 +00:00
// suggestion message additions.
2014-03-06 20:18:49 +00:00
LOOT_API unsigned int loot_get_plugin_tags ( loot_db db , const char * const plugin ,
2013-01-16 15:56:20 +00:00
unsigned int ** const tagIds_added ,
size_t * const numTags_added ,
unsigned int ** const tagIds_removed ,
size_t * const numTags_removed ,
bool * const userlistModified ) {
2014-06-21 23:45:40 +01:00
if ( db == nullptr || plugin == nullptr || tagIds_added == nullptr || numTags_added == nullptr || tagIds_removed == nullptr || numTags_removed == nullptr || userlistModified == nullptr )
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Null pointer passed." );
2013-01-06 13:52:31 +00:00
2013-01-12 23:03:29 +00:00
//Clear existing array allocations.
delete [] db -> extAddedTagIds ;
delete [] db -> extRemovedTagIds ;
2014-06-21 23:45:40 +01:00
db -> extAddedTagIds = nullptr ;
db -> extRemovedTagIds = nullptr ;
2013-01-12 23:03:29 +00:00
//Initialise output.
2014-06-21 23:45:40 +01:00
* tagIds_added = nullptr ;
* tagIds_removed = nullptr ;
2013-01-12 23:03:29 +00:00
* userlistModified = false ;
* numTags_added = 0 ;
* numTags_removed = 0 ;
2014-06-21 23:33:44 +01:00
std :: unordered_set < std :: string > tagsAdded , tagsRemoved ;
2014-03-06 20:18:49 +00:00
std :: list < loot :: Plugin >:: iterator pluginIt = std :: find ( db -> metadata . begin (), db -> metadata . end (), loot :: Plugin ( plugin ));
2013-01-12 23:30:57 +00:00
if ( pluginIt != db -> metadata . end ()) {
2014-06-21 23:33:44 +01:00
std :: set < loot :: Tag > tags ( pluginIt -> Tags ());
for ( const auto & tag : tags ) {
if ( tag . IsAddition ())
tagsAdded . insert ( tag . Name ());
2013-01-12 23:03:29 +00:00
else
2014-06-21 23:33:44 +01:00
tagsRemoved . insert ( tag . Name ());
2013-01-12 23:03:29 +00:00
}
}
2014-03-06 20:18:49 +00:00
pluginIt = std :: find ( db -> userMetadata . begin (), db -> userMetadata . end (), loot :: Plugin ( plugin ));
2013-01-12 23:30:57 +00:00
if ( pluginIt != db -> userMetadata . end ()) {
2013-01-12 23:03:29 +00:00
* userlistModified = true ;
2014-06-21 23:33:44 +01:00
std :: set < loot :: Tag > tags ( pluginIt -> Tags ());
for ( const auto & tag : tags ) {
if ( tag . IsAddition ())
tagsAdded . insert ( tag . Name ());
2014-01-13 19:08:56 +00:00
else
2014-06-21 23:33:44 +01:00
tagsRemoved . insert ( tag . Name ());
2014-01-13 19:08:56 +00:00
}
2013-01-12 23:03:29 +00:00
}
2014-01-12 16:33:28 +00:00
if (( ! tagsAdded . empty () || ! tagsRemoved . empty ()) && db -> bashTagMap . empty ()) {
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_no_tag_map , "No Bash Tag map has been previously generated." );
2014-01-12 16:33:28 +00:00
}
2013-01-12 23:03:29 +00:00
std :: vector < unsigned int > tagsAddedIDs , tagsRemovedIDs ;
2014-06-21 23:33:44 +01:00
for ( const auto & tagNames : tagsAdded ) {
const auto mapIter ( db -> bashTagMap . find ( tagNames ));
2013-01-12 23:03:29 +00:00
if ( mapIter != db -> bashTagMap . end ())
tagsAddedIDs . push_back ( mapIter -> second );
}
2014-06-21 23:33:44 +01:00
for ( const auto & tagNames : tagsRemoved ) {
const auto mapIter ( db -> bashTagMap . find ( tagNames ));
2013-01-12 23:03:29 +00:00
if ( mapIter != db -> bashTagMap . end ())
2014-06-21 23:33:44 +01:00
tagsAddedIDs . push_back ( mapIter -> second );
2013-01-12 23:03:29 +00:00
}
//Allocate memory.
size_t numAdded = tagsAddedIDs . size ();
size_t numRemoved = tagsRemovedIDs . size ();
try {
if ( numAdded != 0 ) {
db -> extAddedTagIds = new uint32_t [ numAdded ];
for ( size_t i = 0 ; i < numAdded ; i ++ )
db -> extAddedTagIds [ i ] = tagsAddedIDs [ i ];
}
if ( numRemoved != 0 ) {
db -> extRemovedTagIds = new uint32_t [ numRemoved ];
for ( size_t i = 0 ; i < numRemoved ; i ++ )
db -> extRemovedTagIds [ i ] = tagsRemovedIDs [ i ];
}
2013-01-16 15:56:20 +00:00
} catch ( std :: bad_alloc & e ) {
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_no_mem , e . what ());
2013-01-12 23:03:29 +00:00
}
//Set outputs.
* tagIds_added = db -> extAddedTagIds ;
* tagIds_removed = db -> extRemovedTagIds ;
* numTags_added = numAdded ;
* numTags_removed = numRemoved ;
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-01-06 13:52:31 +00:00
}
// Returns the messages attached to the given plugin. Messages are valid until Load,
2014-03-06 20:24:05 +00:00
// loot_destroy_db or loot_get_plugin_messages are next called. plugin is case-insensitive.
2014-06-21 23:45:40 +01:00
// If no messages are attached, *messages will be nullptr and numMessages will equal 0.
2014-03-06 20:18:49 +00:00
LOOT_API unsigned int loot_get_plugin_messages ( loot_db db , const char * const plugin ,
loot_message ** const messages ,
2013-01-16 15:56:20 +00:00
size_t * const numMessages ) {
2014-06-21 23:45:40 +01:00
if ( db == nullptr || plugin == nullptr || messages == nullptr || numMessages == nullptr )
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Null pointer passed." );
2013-01-06 13:52:31 +00:00
2013-01-12 23:03:29 +00:00
//Clear existing array allocation.
2014-06-21 23:45:40 +01:00
if ( db -> extMessageArray != nullptr ) {
2013-01-12 23:03:29 +00:00
for ( size_t i = 0 ; i < db -> extMessageArraySize ; ++ i ) {
delete [] db -> extMessageArray [ i ]. message ;
}
delete [] db -> extMessageArray ;
2014-06-21 23:45:40 +01:00
db -> extMessageArray = nullptr ;
2013-01-12 23:03:29 +00:00
}
//Initialise output.
2014-06-21 23:45:40 +01:00
* messages = nullptr ;
2013-01-12 23:03:29 +00:00
* numMessages = 0 ;
2014-03-06 20:18:49 +00:00
std :: list < loot :: Message > pluginMessages ;
std :: list < loot :: Plugin >:: iterator pluginIt = std :: find ( db -> metadata . begin (), db -> metadata . end (), loot :: Plugin ( plugin ));
2013-01-12 23:30:57 +00:00
if ( pluginIt != db -> metadata . end ()) {
pluginMessages = pluginIt -> Messages ();
2013-01-12 23:03:29 +00:00
}
2014-03-06 20:18:49 +00:00
pluginIt = std :: find ( db -> userMetadata . begin (), db -> userMetadata . end (), loot :: Plugin ( plugin ));
2013-01-12 23:30:57 +00:00
if ( pluginIt != db -> userMetadata . end ()) {
2014-03-06 20:18:49 +00:00
std :: list < loot :: Message > temp = pluginIt -> Messages ();
2013-07-27 09:26:55 +01:00
pluginMessages . insert ( pluginMessages . end (), temp . begin (), temp . end ());
2013-01-12 23:30:57 +00:00
}
db -> extMessageArraySize = pluginMessages . size ();
try {
2014-03-06 20:18:49 +00:00
db -> extMessageArray = new loot_message [ db -> extMessageArraySize ];
2013-01-12 23:30:57 +00:00
int i = 0 ;
2014-06-21 23:33:44 +01:00
for ( const auto & message : pluginMessages ) {
db -> extMessageArray [ i ]. type = message . Type ();
db -> extMessageArray [ i ]. message = ToNewCString ( message . ChooseContent ( loot :: Language :: any ). Str ());
2013-01-12 23:30:57 +00:00
}
2013-01-16 15:56:20 +00:00
} catch ( std :: bad_alloc & e ) {
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_no_mem , e . what ());
2013-01-12 23:30:57 +00:00
}
* messages = db -> extMessageArray ;
* numMessages = db -> extMessageArraySize ;
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-01-06 13:52:31 +00:00
}
2014-03-06 20:18:49 +00:00
LOOT_API unsigned int loot_get_dirty_info ( loot_db db , const char * const plugin , unsigned int * const needsCleaning ) {
2014-06-21 23:45:40 +01:00
if ( db == nullptr || plugin == nullptr || needsCleaning == nullptr )
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Null pointer passed." );
2013-07-27 09:26:55 +01:00
2014-03-06 20:18:49 +00:00
* needsCleaning = loot_needs_cleaning_unknown ;
2013-07-27 09:26:55 +01:00
2014-01-12 16:33:28 +00:00
//Get all dirty info.
2014-03-06 20:18:49 +00:00
std :: set < loot :: PluginDirtyInfo > dirtyInfo ;
std :: list < loot :: Plugin >:: iterator pluginIt = std :: find ( db -> metadata . begin (), db -> metadata . end (), loot :: Plugin ( plugin ));
2013-07-27 09:26:55 +01:00
if ( pluginIt != db -> metadata . end ()) {
2014-01-12 16:33:28 +00:00
dirtyInfo = pluginIt -> DirtyInfo ();
2013-07-27 09:26:55 +01:00
}
2014-03-06 20:18:49 +00:00
pluginIt = std :: find ( db -> userMetadata . begin (), db -> userMetadata . end (), loot :: Plugin ( plugin ));
2013-07-27 09:26:55 +01:00
if ( pluginIt != db -> userMetadata . end ()) {
2014-03-06 20:18:49 +00:00
std :: set < loot :: PluginDirtyInfo > temp = pluginIt -> DirtyInfo ();
2014-01-12 16:33:28 +00:00
dirtyInfo . insert ( temp . begin (), temp . end ());
2013-07-27 09:26:55 +01:00
}
2014-01-12 16:33:28 +00:00
if ( ! dirtyInfo . empty ()) {
2014-03-06 20:18:49 +00:00
* needsCleaning = loot_needs_cleaning_yes ;
2013-07-27 09:26:55 +01:00
}
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-07-27 09:26:55 +01:00
}
2013-01-06 13:52:31 +00:00
// Writes a minimal masterlist that only contains mods that have Bash Tag suggestions,
// and/or dirty messages, plus the Tag suggestions and/or messages themselves and their
// conditions, in order to create the Wrye Bash taglist. outputFile is the path to use
// for output. If outputFile already exists, it will only be overwritten if overwrite is true.
2014-03-06 20:18:49 +00:00
LOOT_API unsigned int loot_write_minimal_list ( loot_db db , const char * const outputFile , const bool overwrite ) {
2014-06-21 23:45:40 +01:00
if ( db == nullptr || outputFile == nullptr )
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Null pointer passed." );
2013-01-06 13:52:31 +00:00
2013-01-12 23:03:29 +00:00
if ( boost :: filesystem :: exists ( outputFile ) && ! overwrite )
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Output file exists but overwrite is not set to true." );
2013-01-12 23:03:29 +00:00
2014-03-06 20:18:49 +00:00
std :: list < loot :: Plugin > temp = db -> metadata ;
2014-06-21 23:33:44 +01:00
for ( auto & plugin : temp ) {
loot :: Plugin p ( plugin . Name ());
p . Tags ( plugin . Tags ());
p . DirtyInfo ( plugin . DirtyInfo ());
2013-01-15 22:31:36 +00:00
2014-06-21 23:33:44 +01:00
plugin = p ;
2013-01-12 23:03:29 +00:00
}
YAML :: Emitter yout ;
yout . SetIndent ( 2 );
yout << YAML :: BeginMap
<< YAML :: Key << "plugins" << YAML :: Value << temp
<< YAML :: EndMap ;
2013-07-22 20:44:47 +01:00
boost :: filesystem :: path p ( outputFile );
2014-03-06 20:18:49 +00:00
loot :: ofstream out ( p );
2013-01-12 23:03:29 +00:00
if ( out . fail ())
2014-03-06 20:18:49 +00:00
return c_error ( loot_error_invalid_args , "Couldn't open output file" );
2013-01-12 23:03:29 +00:00
out << yout . c_str ();
out . close ();
2014-03-06 20:18:49 +00:00
return loot_ok ;
2013-01-06 13:52:31 +00:00
}