Mostly implemented API (userlist side of things is missing). As yet untested, though it builds fine.

This commit is contained in:
WrinklyNinja
2013-01-12 23:03:29 +00:00
parent 2cecda2ec7
commit 1cd14809aa
11 changed files with 510 additions and 147 deletions
+11 -13
View File
@@ -14,7 +14,7 @@ project (boss)
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")
include_directories ("${BOSS_LIBS_DIR}/alphanum" "${BOSS_LIBS_DIR}/utf8" "${BOSS_LIBS_DIR}/boost" "${BOSS_LIBS_DIR}/yaml-cpp/include" "${CMAKE_SOURCE_DIR}/src" "${BOSS_LIBS_DIR}/libloadorder/src")
##############################
# Platform-Specific Settings
@@ -37,32 +37,30 @@ ENDIF ()
# Settings when compiling and cross-compiling on Linux.
IF (CMAKE_HOST_SYSTEM_NAME MATCHES "Linux")
set (BOSS_LIBS yaml-cpp boost_filesystem boost_system boost_regex)
set (BOSS_LIBS loadorder yaml-cpp boost_filesystem boost_system boost_regex)
set (CMAKE_C_FLAGS "-m${BOSS_ARCH}")
set (CMAKE_CXX_FLAGS "-m${BOSS_ARCH}")
set (CMAKE_EXE_LINKER_FLAGS "-static-libstdc++ -static-libgcc")
set (CMAKE_SHARED_LINKER_FLAGS "-static-libstdc++ -static-libgcc")
set (CMAKE_MODULE_LINKER_FLAGS "-static-libstdc++ -static-libgcc")
link_directories ("${BOSS_LIBS_DIR}/yaml-cpp/build/")
link_directories ("${BOSS_LIBS_DIR}/yaml-cpp/build")
link_directories ("${BOSS_LIBS_DIR}/libloadorder/build")
link_directories ("${BOSS_LIBS_DIR}/boost/stage-${BOSS_ARCH}/lib")
IF (CMAKE_SYSTEM_NAME MATCHES "Windows")
link_directories ("${BOSS_LIBS_DIR}/yaml-cpp/build/")
link_directories ("${BOSS_LIBS_DIR}/boost/stage-mingw-${BOSS_ARCH}/lib")
link_directories ("${BOSS_LIBS_DIR}/boost/stage-mingw-${BOSS_ARCH}/lib")
ENDIF ()
ENDIF ()
# Settings when not cross-compiling.
IF (CMAKE_SYSTEM_NAME MATCHES CMAKE_HOST_SYSTEM_NAME)
link_directories ("${BOSS_LIBS_DIR}/yaml-cpp/build/")
link_directories ("${BOSS_LIBS_DIR}/boost/stage-${BOSS_ARCH}/lib")
ENDIF ()
##############################
# Actual Building
##############################
# Build API.
add_library (boss "${CMAKE_SOURCE_DIR}/src/api.cpp" ${BOSS_SRC})
target_link_libraries (boss ${BOSS_LIBS})
# Build tester.
add_executable (parser-tester "${CMAKE_SOURCE_DIR}/src/tester.cpp" ${BOSS_SRC})
target_link_libraries (parser-tester ${BOSS_LIBS})
add_executable (tester "${CMAKE_SOURCE_DIR}/src/tester.cpp" ${BOSS_SRC})
target_link_libraries (tester ${BOSS_LIBS})
+361 -25
View File
@@ -22,6 +22,93 @@
*/
#include "api.h"
#include "globals.h"
#include "game.h"
#include "metadata.h"
#include "parsers.h"
#include <yaml-cpp/yaml.h>
#include <algorithm>
#include <fstream>
#include <clocale>
#include <list>
#include <vector>
#include <boost/filesystem/detail/utf8_codecvt_facet.hpp>
#include <boost/filesystem.hpp>
#include <boost/unordered_set.hpp>
#include <boost/unordered_map.hpp>
const unsigned int BOSS_API_OK = 0;
const unsigned int BOSS_API_ERROR_LIBLO_ERROR = 1;
const unsigned int BOSS_API_ERROR_PARSE_FAIL = 2;
const unsigned int BOSS_API_ERROR_CONDITION_EVAL_FAIL = 3;
const unsigned int BOSS_API_ERROR_REGEX_EVAL_FAIL = 4;
const unsigned int BOSS_API_ERROR_NO_MEM = 5;
const unsigned int BOSS_API_ERROR_INVALID_ARGS = 6;
const unsigned int BOSS_API_ERROR_NO_TAG_MAP = 7;
const unsigned int BOSS_API_RETURN_MAX = BOSS_API_ERROR_NO_TAG_MAP;
// The following are the games identifiers used by the API.
const unsigned int BOSS_API_GAME_TES4 = BOSS_GAME_TES4;
const unsigned int BOSS_API_GAME_TES5 = BOSS_GAME_TES5;
const unsigned int BOSS_API_GAME_FO3 = BOSS_GAME_FO3;
const unsigned int BOSS_API_GAME_FONV = BOSS_GAME_FONV;
// BOSS message types.
const unsigned int BOSS_API_MESSAGE_SAY = 1;
const unsigned int BOSS_API_MESSAGE_WARN = 2;
const unsigned int BOSS_API_MESSAGE_ERROR = 3;
struct _boss_db_int {
_boss_db_int()
: extTagMap(NULL),
extAddedTagIds(NULL),
extRemovedTagIds(NULL),
extMessageArray(NULL),
extMessageArraySize(0) {
}
~_boss_db_int() {
delete [] extAddedTagIds;
delete [] extRemovedTagIds;
if (extTagMap != NULL) {
for (size_t i=0; i < bashTagMap.size(); i++)
delete [] extTagMap[i]; //Gotta clear those allocated strings.
delete [] extTagMap;
}
if (extMessageArray != NULL) {
for (size_t i=0; i < extMessageArraySize; i++)
delete [] extMessageArray[i].message; //Gotta clear those allocated strings.
delete [] extMessageArray;
}
}
boss::Game game;
std::list<boss::Plugin> metadata;
std::list<boss::Plugin> userMetadata;
boost::unordered_map<std::string, unsigned int> bashTagMap;
char ** extTagMap;
unsigned int * extAddedTagIds;
unsigned int * extRemovedTagIds;
boss_message * extMessageArray;
size_t extMessageArraySize;
};
char * extMessageStr;
// 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());
}
//////////////////////////////
// Error Handling Functions
@@ -30,8 +117,18 @@
// 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.
BOSS_API uint32_t boss_get_error_message (uint8_t ** message) {
BOSS_API unsigned int boss_get_error_message (char ** message) {
if (message == NULL)
return BOSS_API_ERROR_INVALID_ARGS;
*message = "Something went wrong.";
return BOSS_API_OK;
}
// Frees memory allocated to error string.
BOSS_API void boss_cleanup () {
delete[] extMessageStr;
}
@@ -41,15 +138,22 @@ BOSS_API uint32_t boss_get_error_message (uint8_t ** message) {
// Returns whether this version of BOSS supports the API from the given
// BOSS version. Abstracts BOSS API stability policy away from clients.
BOSS_API bool boss_is_compatible (const uint32_t versionMajor, const uint32_t versionMinor, const uint32_t versionPatch) {
BOSS_API bool boss_is_compatible (const unsigned int versionMajor, const unsigned int versionMinor, const unsigned int versionPatch) {
return versionMajor == BOSS_VERSION_MAJOR && versionMinor == BOSS_VERSION_MINOR;
}
// Returns the version string for this version of BOSS.
// The string exists until this function is called again or until
// CleanUpAPI is called.
BOSS_API uint32_t boss_get_version (uint32_t * versionMajor, uint32_t * versionMinor, uint32_t * versionPatch) {
BOSS_API unsigned int boss_get_version (unsigned int * versionMajor, unsigned int * versionMinor, unsigned int * versionPatch) {
if (versionMajor == NULL || versionMinor == NULL || versionPatch == NULL)
return BOSS_API_ERROR_INVALID_ARGS;
*versionMajor = BOSS_VERSION_MAJOR;
*versionMinor = BOSS_VERSION_MINOR;
*versionPatch = BOSS_VERSION_PATCH;
return BOSS_API_OK;
}
@@ -64,18 +168,42 @@ BOSS_API uint32_t boss_get_version (uint32_t * versionMajor, uint32_t * versionM
// plugins.txt and loadorder.txt (if they both exist) are in sync. If
// dataPath == NULL then the API will attempt to detect the data path of
// the specified game.
BOSS_API uint32_t boss_create_db (boss_db * db, const uint32_t clientGame, const uint8_t * gamePath) {
BOSS_API unsigned int boss_create_db (boss_db * db, const unsigned int clientGame, const char * gamePath) {
if (db == NULL || (clientGame != BOSS_API_GAME_TES4 && clientGame != BOSS_API_GAME_TES5 && clientGame != BOSS_API_GAME_FO3 && clientGame != BOSS_API_GAME_FONV))
return BOSS_API_ERROR_INVALID_ARGS;
//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);
std::string game_path = "";
if (gamePath != NULL)
game_path = gamePath;
boss::Game game;
try {
game = boss::Game(clientGame, game_path); //This also checks to see if the game is installed if game_path is empty and throws an exception if it is not detected.
} catch (std::runtime_error& e) {
return BOSS_API_ERROR_INVALID_ARGS;
}
boss_db retVal;
try {
retVal = new _boss_db_int;
} catch (std::bad_alloc /*&e*/) {
return BOSS_API_ERROR_INVALID_ARGS;
}
retVal->game = game;
*db = retVal;
return BOSS_API_OK;
}
// Destroys the given DB, freeing any memory allocated as part of its use.
BOSS_API void boss_destroy_db (boss_db db) {
}
// Frees memory allocated to version and error strings.
BOSS_API void boss_cleanup () {
delete db;
}
@@ -87,9 +215,27 @@ BOSS_API void boss_cleanup () {
// 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.
BOSS_API uint32_t boss_load_lists (boss_db db, const uint8_t * masterlistPath,
const uint8_t * userlistPath) {
BOSS_API unsigned int boss_load_lists (boss_db db, const char * masterlistPath,
const char * userlistPath) {
if (db == NULL || masterlistPath == NULL)
return BOSS_API_ERROR_INVALID_ARGS;
std::list<boss::Plugin> temp;
std::list<boss::Plugin> userTemp;
try {
YAML::Node tempNode = YAML::LoadFile(masterlistPath);
temp = tempNode["plugins"].as< std::list<boss::Plugin> >();
tempNode = YAML::LoadFile(userlistPath);
userTemp = tempNode["plugins"].as< std::list<boss::Plugin> >();
} catch (YAML::Exception &e) {
return BOSS_API_ERROR_INVALID_ARGS;
}
db->metadata = temp;
db->userMetadata = userTemp;
return BOSS_API_OK;
}
// Evaluates all conditional lines and regex mods the loaded masterlist.
@@ -98,8 +244,29 @@ BOSS_API uint32_t boss_load_lists (boss_db db, const uint8_t * masterlistPath,
// 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.
BOSS_API uint32_t boss_eval_lists (boss_db db) {
BOSS_API unsigned int boss_eval_lists (boss_db db) {
std::list<boss::Plugin> temp = db->metadata;
try {
for (std::list<boss::Plugin>::iterator it=temp.begin(), endIt=temp.end(); it != endIt; ++it) {
it->EvalAllConditions(db->game);
}
} catch (std::runtime_error& e) {
return BOSS_API_ERROR_INVALID_ARGS;
}
db->metadata = temp;
temp = db->userMetadata;
try {
for (std::list<boss::Plugin>::iterator it=temp.begin(), endIt=temp.end(); it != endIt; ++it) {
it->EvalAllConditions(db->game);
}
} catch (std::runtime_error& e) {
return BOSS_API_ERROR_INVALID_ARGS;
}
db->userMetadata = temp;
return BOSS_API_OK;
}
@@ -110,8 +277,62 @@ BOSS_API uint32_t boss_eval_lists (boss_db db) {
// 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.
BOSS_API uint32_t boss_get_tag_map (boss_db db, boss_tag ** tagMap, size_t * numTags) {
BOSS_API unsigned int boss_get_tag_map (boss_db db, char *** tagMap, size_t * numTags) {
if (db == NULL || tagMap == NULL || numTags == NULL)
return BOSS_API_ERROR_INVALID_ARGS;
//Clear existing array allocation.
if (db->extTagMap != NULL) {
for (size_t i=0, max=db->bashTagMap.size(); i < max; ++i) {
delete [] db->extTagMap[i];
}
delete [] db->extTagMap;
}
//Initialise output.
*tagMap = NULL;
*numTags = 0;
boost::unordered_set<std::string> allTags;
for (std::list<boss::Plugin>::iterator it=db->metadata.begin(), endIt=db->metadata.end(); it != endIt; ++it) {
std::list<boss::Tag> tags = it->Tags();
for (std::list<boss::Tag>::const_iterator jt=tags.begin(), endJt=tags.end(); jt != endJt; ++jt) {
allTags.insert(jt->Name());
}
}
for (std::list<boss::Plugin>::iterator it=db->userMetadata.begin(), endIt=db->userMetadata.end(); it != endIt; ++it) {
std::list<boss::Tag> tags = it->Tags();
for (std::list<boss::Tag>::const_iterator jt=tags.begin(), endJt=tags.end(); jt != endJt; ++jt) {
allTags.insert(jt->Name());
}
}
if (allTags.empty())
return BOSS_API_OK;
try {
db->extTagMap = new char*[allTags.size()];
} catch (std::bad_alloc /*&e*/) {
return BOSS_API_ERROR_INVALID_ARGS;
}
unsigned int UID = 0;
try {
for (boost::unordered_set<std::string>::const_iterator it=allTags.begin(), endIt=allTags.end(); it != endIt; ++it) {
db->bashTagMap.emplace(*it, UID);
//Also allocate memory.
db->extTagMap[UID] = ToNewCString(*it);
UID++;
}
} catch (std::bad_alloc /*&e*/) {
return BOSS_API_ERROR_INVALID_ARGS;
}
*tagMap = db->extTagMap;
*numTags = allTags.size();
return BOSS_API_OK;
}
// Returns arrays of Bash Tag UIDs for Bash Tags suggested for addition and removal
@@ -121,28 +342,143 @@ BOSS_API uint32_t boss_get_tag_map (boss_db db, boss_tag ** tagMap, size_t * num
// case-insensitive. If no Tags are found for an array, the array pointer (*tagIds)
// will be NULL. The userlistModified bool is true if the userlist contains Bash Tag
// suggestion message additions.
BOSS_API uint32_t boss_get_plugin_tags (boss_db db, const uint8_t * plugin,
uint32_t ** tagIds_added,
size_t * numTags_added,
uint32_t **tagIds_removed,
size_t *numTags_removed,
bool * userlistModified) {
BOSS_API unsigned int boss_get_plugin_tags (boss_db db, const char * plugin,
unsigned int ** tagIds_added,
size_t * numTags_added,
unsigned int **tagIds_removed,
size_t * numTags_removed,
bool * userlistModified) {
if (db == NULL || plugin == NULL || tagIds_added == NULL || numTags_added == NULL || tagIds_removed == NULL || numTags_removed == NULL || userlistModified == NULL)
return BOSS_API_ERROR_INVALID_ARGS;
//Clear existing array allocations.
delete [] db->extAddedTagIds;
delete [] db->extRemovedTagIds;
//Initialise output.
*tagIds_added = NULL;
*tagIds_removed = NULL;
*userlistModified = false;
*numTags_added = 0;
*numTags_removed = 0;
boost::unordered_set<std::string> tagsAdded, tagsRemoved;
std::list<boss::Plugin>::iterator it = std::find(db->metadata.begin(), db->metadata.end(), boss::Plugin(plugin));
if (it != db->metadata.end()) {
std::list<boss::Tag> tags = it->Tags();
for (std::list<boss::Tag>::const_iterator it=tags.begin(), endIt=tags.end(); it != endIt; ++it) {
if (it->IsAddition())
tagsAdded.insert(it->Name());
else
tagsRemoved.insert(it->Name());
}
}
it = std::find(db->userMetadata.begin(), db->userMetadata.end(), boss::Plugin(plugin));
if (it != db->userMetadata.end()) {
*userlistModified = true;
}
std::vector<unsigned int> tagsAddedIDs, tagsRemovedIDs;
for (boost::unordered_set<std::string>::const_iterator it=tagsAdded.begin(), endIt=tagsAdded.end(); it != endIt; ++it) {
boost::unordered_map<std::string, unsigned int>::const_iterator mapIter = db->bashTagMap.find(*it);
if (mapIter != db->bashTagMap.end())
tagsAddedIDs.push_back(mapIter->second);
}
for (boost::unordered_set<std::string>::const_iterator it=tagsRemoved.begin(), endIt=tagsRemoved.end(); it != endIt; ++it) {
boost::unordered_map<std::string, unsigned int>::const_iterator mapIter = db->bashTagMap.find(*it);
if (mapIter != db->bashTagMap.end())
tagsRemovedIDs.push_back(mapIter->second);
}
//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];
}
} catch (std::bad_alloc /*&e*/) {
return BOSS_API_ERROR_INVALID_ARGS;
}
//Set outputs.
*tagIds_added = db->extAddedTagIds;
*tagIds_removed = db->extRemovedTagIds;
*numTags_added = numAdded;
*numTags_removed = numRemoved;
return BOSS_API_OK;
}
// Returns the messages attached to the given plugin. Messages are valid until Load,
// DestroyBossDb or GetPluginMessages are next called. plugin is case-insensitive.
// If no messages are attached, *messages will be NULL and numMessages will equal 0.
BOSS_API uint32_t boss_get_plugin_messages (boss_db db, const uint8_t * plugin,
boss_message ** messages,
size_t * numMessages) {
BOSS_API unsigned int boss_get_plugin_messages (boss_db db, const char * plugin,
boss_message ** messages,
size_t * numMessages) {
if (db == NULL || plugin == NULL || messages == NULL || numMessages == NULL)
return BOSS_API_ERROR_INVALID_ARGS;
//Clear existing array allocation.
if (db->extMessageArray != NULL) {
for (size_t i=0; i < db->extMessageArraySize; ++i) {
delete [] db->extMessageArray[i].message;
}
delete [] db->extMessageArray;
}
//Initialise output.
*messages = NULL;
*numMessages = 0;
std::list<boss::Plugin>::iterator it = std::find(db->metadata.begin(), db->metadata.end(), boss::Plugin(plugin));
if (it == db->metadata.end()) {
it = std::find(db->userMetadata.begin(), db->userMetadata.end(), boss::Plugin(plugin));
if (it == db->userMetadata.end())
return BOSS_API_OK;
}
return BOSS_API_OK;
}
// 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.
BOSS_API uint32_t boss_write_minimal_list (boss_db db, const uint8_t * outputFile, const bool overwrite) {
BOSS_API unsigned int boss_write_minimal_list (boss_db db, const char * outputFile, const bool overwrite) {
if (db == NULL || outputFile == NULL)
return BOSS_API_ERROR_INVALID_ARGS;
if (boost::filesystem::exists(outputFile) && !overwrite)
return BOSS_API_ERROR_INVALID_ARGS;
std::list<boss::Plugin> temp = db->metadata;
for (std::list<boss::Plugin>::iterator it=temp.begin(), endIt=temp.end(); it != endIt; ++it) {
boss::Plugin p(it->Name());
p.Tags(it->Tags());
*it = p;
}
YAML::Emitter yout;
yout.SetIndent(2);
yout << YAML::BeginMap
<< YAML::Key << "plugins" << YAML::Value << temp
<< YAML::EndMap;
std::ofstream out(outputFile);
if (out.fail())
return BOSS_API_ERROR_INVALID_ARGS;
out << yout.c_str();
out.close();
return BOSS_API_OK;
}
+37 -63
View File
@@ -68,67 +68,40 @@ extern "C"
// All API strings are uint8_t* strings encoded in UTF-8. Strings returned
// by the API should not have their memory freed by the client: the API will
// clean up after itself.
// All API numbers and error codes are uint32_t integers.
// All API numbers and error codes are unsigned int integers.
// Abstracts the definition of BOSS's internal state while still providing
// type safety across the API.
typedef struct _boss_db_int * boss_db;
// boss_tag structure gives the Unique ID number (UID) for each Bash Tag and
// the corresponding Tag name string.
typedef struct {
uint32_t id;
const uint8_t * name; // don't use char for utf-8 since char can be signed
} boss_tag;
// boss_message structure gives the type of message and it contents.
typedef struct {
uint32_t type;
const uint8_t * message;
unsigned int type;
const char * message;
} boss_message;
// The following are the possible codes that the API can return.
BOSS_API extern const uint32_t BOSS_API_OK;
BOSS_API extern const uint32_t BOSS_API_OK_NO_UPDATE_NECESSARY;
BOSS_API extern const uint32_t BOSS_API_WARN_BAD_FILENAME;
BOSS_API extern const uint32_t BOSS_API_WARN_LO_MISMATCH;
BOSS_API extern const uint32_t BOSS_API_ERROR_FILE_WRITE_FAIL;
BOSS_API extern const uint32_t BOSS_API_ERROR_FILE_DELETE_FAIL;
BOSS_API extern const uint32_t BOSS_API_ERROR_FILE_NOT_UTF8;
BOSS_API extern const uint32_t BOSS_API_ERROR_FILE_NOT_FOUND;
BOSS_API extern const uint32_t BOSS_API_ERROR_FILE_RENAME_FAIL;
BOSS_API extern const uint32_t BOSS_API_ERROR_TIMESTAMP_READ_FAIL;
BOSS_API extern const uint32_t BOSS_API_ERROR_TIMESTAMP_WRITE_FAIL;
BOSS_API extern const uint32_t BOSS_API_ERROR_PARSE_FAIL;
BOSS_API extern const uint32_t BOSS_API_ERROR_CONDITION_EVAL_FAIL;
BOSS_API extern const uint32_t BOSS_API_ERROR_REGEX_EVAL_FAIL;
BOSS_API extern const uint32_t BOSS_API_ERROR_NO_MEM;
BOSS_API extern const uint32_t BOSS_API_ERROR_INVALID_ARGS;
BOSS_API extern const uint32_t BOSS_API_ERROR_NETWORK_FAIL;
BOSS_API extern const uint32_t BOSS_API_ERROR_NO_INTERNET_CONNECTION;
BOSS_API extern const uint32_t BOSS_API_ERROR_NO_TAG_MAP;
BOSS_API extern const uint32_t BOSS_API_ERROR_PLUGINS_FULL;
BOSS_API extern const uint32_t BOSS_API_ERROR_GAME_NOT_FOUND;
BOSS_API extern const uint32_t BOSS_API_ERROR_PLUGIN_BEFORE_MASTER;
BOSS_API extern const uint32_t BOSS_API_RETURN_MAX;
BOSS_API extern const unsigned int BOSS_API_OK;
BOSS_API extern const unsigned int BOSS_API_ERROR_LIBLO_ERROR;
BOSS_API extern const unsigned int BOSS_API_ERROR_PARSE_FAIL;
BOSS_API extern const unsigned int BOSS_API_ERROR_CONDITION_EVAL_FAIL;
BOSS_API extern const unsigned int BOSS_API_ERROR_REGEX_EVAL_FAIL;
BOSS_API extern const unsigned int BOSS_API_ERROR_NO_MEM;
BOSS_API extern const unsigned int BOSS_API_ERROR_INVALID_ARGS;
BOSS_API extern const unsigned int BOSS_API_ERROR_NO_TAG_MAP;
BOSS_API extern const unsigned int BOSS_API_RETURN_MAX;
// The following are the games identifiers used by the API.
BOSS_API extern const uint32_t BOSS_API_GAME_OBLIVION;
BOSS_API extern const uint32_t BOSS_API_GAME_FALLOUT3;
BOSS_API extern const uint32_t BOSS_API_GAME_FALLOUTNV;
BOSS_API extern const uint32_t BOSS_API_GAME_NEHRIM;
BOSS_API extern const uint32_t BOSS_API_GAME_SKYRIM;
BOSS_API extern const uint32_t BOSS_API_GAME_MORROWIND;
BOSS_API extern const unsigned int BOSS_API_GAME_TES4;
BOSS_API extern const unsigned int BOSS_API_GAME_TES5;
BOSS_API extern const unsigned int BOSS_API_GAME_FO3;
BOSS_API extern const unsigned int BOSS_API_GAME_FONV;
// BOSS message types.
BOSS_API extern const uint32_t BOSS_API_MESSAGE_SAY;
BOSS_API extern const uint32_t BOSS_API_MESSAGE_TAG;
BOSS_API extern const uint32_t BOSS_API_MESSAGE_REQUIREMENT;
BOSS_API extern const uint32_t BOSS_API_MESSAGE_INCOMPATIBILITY;
BOSS_API extern const uint32_t BOSS_API_MESSAGE_DIRTY;
BOSS_API extern const uint32_t BOSS_API_MESSAGE_WARN;
BOSS_API extern const uint32_t BOSS_API_MESSAGE_ERROR;
BOSS_API extern const unsigned int BOSS_API_MESSAGE_SAY;
BOSS_API extern const unsigned int BOSS_API_MESSAGE_WARN;
BOSS_API extern const unsigned int BOSS_API_MESSAGE_ERROR;
@@ -138,8 +111,11 @@ BOSS_API extern const uint32_t BOSS_API_MESSAGE_ERROR;
// 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.
BOSS_API uint32_t boss_get_error_message (uint8_t ** message);
// until this function is called again or until boss_cleanup is called.
BOSS_API unsigned int boss_get_error_message (char ** message);
// Frees memory allocated to error string.
BOSS_API void boss_cleanup ();
//////////////////////////////
@@ -148,12 +124,12 @@ BOSS_API uint32_t boss_get_error_message (uint8_t ** message);
// Returns whether this version of BOSS supports the API from the given
// BOSS version. Abstracts BOSS API stability policy away from clients.
BOSS_API bool boss_is_compatible (const uint32_t versionMajor, const uint32_t versionMinor, const uint32_t versionPatch);
BOSS_API bool boss_is_compatible (const unsigned int versionMajor, const unsigned int versionMinor, const unsigned int versionPatch);
// Returns the version string for this version of BOSS.
// The string exists until this function is called again or until
// CleanUpAPI is called.
BOSS_API uint32_t boss_get_version (uint32_t * versionMajor, uint32_t * versionMinor, uint32_t * versionPatch);
BOSS_API unsigned int boss_get_version (unsigned int * versionMajor, unsigned int * versionMinor, unsigned int * versionPatch);
////////////////////////////////////
@@ -167,14 +143,11 @@ BOSS_API uint32_t boss_get_version (uint32_t * versionMajor, uint32_t * versionM
// plugins.txt and loadorder.txt (if they both exist) are in sync. If
// dataPath == NULL then the API will attempt to detect the data path of
// the specified game.
BOSS_API uint32_t boss_create_db (boss_db * db, const uint32_t clientGame, const uint8_t * gamePath);
BOSS_API unsigned int boss_create_db (boss_db * db, const unsigned int clientGame, const char * gamePath);
// Destroys the given DB, freeing any memory allocated as part of its use.
BOSS_API void boss_destroy_db (boss_db db);
// Frees memory allocated to version and error strings.
BOSS_API void boss_cleanup ();
///////////////////////////////////
// Database Loading Functions
@@ -184,8 +157,8 @@ BOSS_API void boss_cleanup ();
// 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.
BOSS_API uint32_t boss_load_lists (boss_db db, const uint8_t * masterlistPath,
const uint8_t * userlistPath);
BOSS_API unsigned int boss_load_lists (boss_db db, const char * masterlistPath,
const char * userlistPath);
// Evaluates all conditional lines and regex mods the loaded masterlist.
// This exists so that Load() doesn't need to be called whenever the mods
@@ -193,7 +166,7 @@ BOSS_API uint32_t boss_load_lists (boss_db db, const uint8_t * masterlistPath,
// 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.
BOSS_API uint32_t boss_eval_lists (boss_db db);
BOSS_API unsigned int boss_eval_lists (boss_db db);
//////////////////////////
@@ -203,7 +176,8 @@ BOSS_API uint32_t boss_eval_lists (boss_db db);
// 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.
BOSS_API uint32_t boss_get_tag_map (boss_db db, boss_tag ** tagMap, size_t * numTags);
// The indices of the tagMap are each tag's UID.
BOSS_API unsigned int boss_get_tag_map (boss_db db, char *** tagMap, size_t * numTags);
// Returns arrays of Bash Tag UIDs for Bash Tags suggested for addition and removal
// by BOSS's masterlist and userlist, and the number of tags in each array.
@@ -212,17 +186,17 @@ BOSS_API uint32_t boss_get_tag_map (boss_db db, boss_tag ** tagMap, size_t * num
// case-insensitive. If no Tags are found for an array, the array pointer (*tagIds)
// will be NULL. The userlistModified bool is true if the userlist contains Bash Tag
// suggestion message additions.
BOSS_API uint32_t boss_get_plugin_tags (boss_db db, const uint8_t * plugin,
uint32_t ** tagIds_added,
BOSS_API unsigned int boss_get_plugin_tags (boss_db db, const char * plugin,
unsigned int ** tagIds_added,
size_t * numTags_added,
uint32_t **tagIds_removed,
unsigned int **tagIds_removed,
size_t *numTags_removed,
bool * userlistModified);
// Returns the messages attached to the given plugin. Messages are valid until Load,
// DestroyBossDb or GetPluginMessages are next called. plugin is case-insensitive.
// If no messages are attached, *messages will be NULL and numMessages will equal 0.
BOSS_API uint32_t boss_get_plugin_messages (boss_db db, const uint8_t * plugin,
BOSS_API unsigned int boss_get_plugin_messages (boss_db db, const char * plugin,
boss_message ** messages,
size_t * numMessages);
@@ -230,7 +204,7 @@ BOSS_API uint32_t boss_get_plugin_messages (boss_db db, const uint8_t * plugin,
// 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.
BOSS_API uint32_t boss_write_minimal_list (boss_db db, const uint8_t * outputFile, const bool overwrite);
BOSS_API unsigned int boss_write_minimal_list (boss_db db, const char * outputFile, const bool overwrite);
#ifdef __cplusplus
+29 -7
View File
@@ -25,6 +25,8 @@
#include "globals.h"
#include "helpers.h"
#include <libloadorder.h>
#include <stdexcept>
#include <boost/algorithm/string.hpp>
@@ -43,7 +45,7 @@ namespace boss {
Game::Game()
: id(BOSS_GAME_AUTODETECT) {}
Game::Game(const uint32_t gameCode, const string path, const bool noPathInit)
Game::Game(const unsigned int gameCode, const string path, const bool noPathInit)
: id(gameCode) {
if (Id() == BOSS_GAME_TES4) {
name = "TES IV: Oblivion";
@@ -91,6 +93,31 @@ namespace boss {
throw runtime_error("Game path could not be detected.");
} else
gamePath = fs::path(path);
lo_game_handle gh;
int ret;
char ** pluginArr;
size_t pluginArrSize;
if (id == BOSS_GAME_TES4)
ret = lo_create_handle(&gh, LIBLO_GAME_TES4, gamePath.string().c_str());
else if (id == BOSS_GAME_TES5)
ret = lo_create_handle(&gh, LIBLO_GAME_TES5, gamePath.string().c_str());
else if (id == BOSS_GAME_FO3)
ret = lo_create_handle(&gh, LIBLO_GAME_FO3, gamePath.string().c_str());
else if (id == BOSS_GAME_FONV)
ret = lo_create_handle(&gh, LIBLO_GAME_FNV, gamePath.string().c_str());
if (ret != LIBLO_OK)
throw runtime_error("Active plugin list lookup failed.");
else {
if (lo_get_active_plugins(gh, &pluginArr, &pluginArrSize) != LIBLO_OK)
throw runtime_error("Active plugin list lookup failed.");
else {
for (size_t i=0; i < pluginArrSize; ++i) {
activePlugins.insert(string(pluginArr[i]));
}
}
}
}
}
@@ -102,7 +129,7 @@ namespace boss {
return fs::exists(fs::path("..") / pluginsFolderName);
}
uint32_t Game::Id() const {
unsigned int Game::Id() const {
return id;
}
@@ -119,11 +146,6 @@ namespace boss {
}
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();
}
+3 -3
View File
@@ -36,12 +36,12 @@ namespace boss {
class Game {
public:
Game(); //Sets game to BOSS_GAME_AUTODETECT, with all other vars being empty.
Game(const uint32_t gameCode, const std::string path = "", const bool noPathInit = false); //Empty path means constructor will detect its location. If noPathInit is true, then the game's BOSS subfolder will not be created.
Game(const unsigned int gameCode, const std::string path = "", const bool noPathInit = false); //Empty path means constructor will detect its location. If noPathInit is true, then the game's BOSS subfolder will not be created.
bool IsInstalled() const;
bool IsInstalledLocally() const;
uint32_t Id() const;
unsigned int Id() const;
std::string Name() const; //Returns the game's name, eg. "TES IV: Oblivion".
boost::filesystem::path GamePath() const;
@@ -56,7 +56,7 @@ namespace boss {
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;
unsigned id;
std::string name;
std::string registryKey;
+8 -8
View File
@@ -23,14 +23,14 @@
#ifndef __BOSS_GLOBALS__
#define __BOSS_GLOBALS__
const int BOSS_GAME_AUTODETECT = 0;
const int BOSS_GAME_TES4 = 1;
const int BOSS_GAME_TES5 = 2;
const int BOSS_GAME_FO3 = 3;
const int BOSS_GAME_FONV = 4;
const unsigned int BOSS_GAME_AUTODETECT = 0;
const unsigned int BOSS_GAME_TES4 = 1;
const unsigned int BOSS_GAME_TES5 = 2;
const unsigned int BOSS_GAME_FO3 = 3;
const unsigned int BOSS_GAME_FONV = 4;
const int BOSS_VERSION_MAJOR = 3;
const int BOSS_VERSION_MINOR = 0;
const int BOSS_VERSION_PATCH = 0;
const unsigned int BOSS_VERSION_MAJOR = 3;
const unsigned int BOSS_VERSION_MINOR = 0;
const unsigned int BOSS_VERSION_PATCH = 0;
#endif
+49 -13
View File
@@ -23,13 +23,10 @@
# This is a script that converts a MF 2.3 masterlist to a MF 3 masterlist. It's
# a bit hacky, but the YAML parser accepts it. Limitations are:
# - Doesn't lowercase condition statements.
# - Doesn't strip plugins that no longer belong in the masterlist, ie. without
# messages or positioning comments.
# - Requirements and incompatibilities do not use their new data structures,
# and are just converted to 'say' messages.
# - All strings except tags are enclosed in double quotes and escaped, even
# those that don't need to be, and those that are already escaped.
# - No extraneous comments/messages are removed.
# - VAR conditions are not replaced with their conditional statements. SET lines
# are stripped, so the old masterlist must be used to check what they are.
@@ -38,15 +35,46 @@
# - Conditional plugin positions are all made unconditional, so there will be
# duplicate entries.
import re
def escapeYAMLStr(s):
return '"' + s.replace('"', '\\"').replace('\\', '\\\\').strip(' ') + '"'
special = ['-', '?', ':', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`']
if ('\'' in s):
return '"' + s.replace('\\', '\\\\').replace('"', '\\"').strip() + '"'
elif (1 in [c in s for c in special]):
return '\'' + s.strip() + '\''
else:
return s;
def mContent(line):
content = line[line.find(':')+1:]
return escapeYAMLStr(content)
inFile = open('masterlist.txt', 'r')
outFile = open('masterlist.yaml', 'w')
def convertCondition(condition):
#IF no longer exists.
#IFNOT is now 'not'.
#The VAR condition no longer exists.
#The '=' comparator is now '=='.
#The LANG condition no longer exists.
#'&&' is now 'and'.
#'||' is now 'or'.
#Really, these will probably have to be done manually.
parts = condition.split('"') # Every odd index is quoted and shouldn't be lowercased.
for i in range(len(parts)):
if (i % 2 == 0):
parts[i] = parts[i].lower()
condition = '"'.join(parts)
condition.replace(' && ', ' and ').replace(' || ', ' or ').replace(' =)', ' ==)')
return escapeYAMLStr(condition)
inFile = open('../../BOSS/data/boss-oblivion/masterlist.txt', 'r')
outFile = open('../build/masterlist.yaml', 'w')
outFile.write('---\n');
@@ -57,7 +85,7 @@ for line in inFile:
line = line.replace('\n', '')
if (len(line) == 0):
if (len(line.strip()) == 0):
continue
# Skip group lines.
@@ -102,14 +130,15 @@ for line in inFile:
if ('IF' in line and ':' in line):
key = line[:line.find(':')]
key = key.replace('GLOBAL', '')
condition = key[:key.rfind(' ')].strip(' ')
condition = condition.replace('&&', 'and').replace('||', 'or')
condition = key[:key.rfind(' ')].strip()
elif ('ELSE' in line and ':' in line):
condition = condition.replace('IF ', 'IFNOT2 ')
condition = condition.replace('IFNOT ', 'IF ')
condition = condition.replace('IFNOT2 ', 'IFNOT ')
if (condition[0] != 'n'):
condition = 'not ' + condition
condition = condition.replace('not ', '`~||~`')
condition = condition.replace('d f', 'd not f').replace('d c', 'd not c', ', 'and
else:
condition = ''
condition = convertCondition(condition);
# Write tag lines.
if ('TAG:' in line):
@@ -141,10 +170,17 @@ for line in inFile:
indent = ' '
else:
indent = ' '
prefix = ''
if ('INC:' in line):
line = 'Incompatible with ' + line.split(':')[1]
elif ('REQ:' in line):
line = 'Requires ' + line.split(':')[1]
else:
line = line.split(':')[1]
outFile.write(indent + '- type: say\n')
if (condition):
outFile.write(indent + ' condition: ' + condition + '\n')
outFile.write(indent + ' content: ' + mContent(line) + '\n')
outFile.write(indent + ' content: ' + escapeYAMLStr(line) + '\n')
# Write 'warn' messages.
if ('DIRTY:' in line or 'WARN:' in line):
+4
View File
@@ -233,4 +233,8 @@ namespace boss {
bool Plugin::IsRegexPlugin() const {
return boost::iends_with(name, "\\.esm") || boost::iends_with(name, "\\.esp");
}
bool Plugin::operator == (Plugin rhs) {
return name == rhs.Name();
}
}
+4
View File
@@ -129,6 +129,8 @@ namespace boss {
void EvalAllConditions(boss::Game& game);
bool HasNameOnly() const;
bool IsRegexPlugin() const;
bool operator == (Plugin rhs);
private:
std::string name;
bool enabled; //Default to true.
@@ -145,6 +147,8 @@ namespace boss {
return lhs.Name() < rhs.Name();
}
};
}
#endif
+2
View File
@@ -34,6 +34,8 @@
#include <stdexcept>
#include <stdint.h>
#include <yaml-cpp/yaml.h>
#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
+2 -15
View File
@@ -27,21 +27,8 @@ int main() {
YAML::Node test = YAML::LoadFile("masterlist-example.yaml");
list<boss::Message> globalMessages;
if (test["globals"]) {
YAML::Node globals = test["globals"];
for (YAML::const_iterator it=globals.begin(); it != globals.end(); ++it) {
globalMessages.push_back(it->as<boss::Message>());
}
}
list<boss::Plugin> pluginData;
if (test["plugins"]) {
YAML::Node plugins = test["plugins"];
for (YAML::const_iterator it=plugins.begin(); it != plugins.end(); ++it) {
pluginData.push_back(it->as<boss::Plugin>());
}
}
list<boss::Message> globalMessages = test["globals"].as< list<boss::Message> >();
list<boss::Plugin> pluginData = test["plugins"].as< list<boss::Plugin> >();
cout << "Testing masterlist generator." << endl;