diff --git a/CMakeLists.txt b/CMakeLists.txt index a456298c..12820127 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/src/api.cpp b/src/api.cpp index ff9a1262..b432d2ec 100644 --- a/src/api.cpp +++ b/src/api.cpp @@ -22,6 +22,93 @@ */ #include "api.h" +#include "globals.h" +#include "game.h" +#include "metadata.h" +#include "parsers.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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 metadata; + std::list userMetadata; + + boost::unordered_map 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 temp; + std::list userTemp; + try { + YAML::Node tempNode = YAML::LoadFile(masterlistPath); + temp = tempNode["plugins"].as< std::list >(); + + tempNode = YAML::LoadFile(userlistPath); + userTemp = tempNode["plugins"].as< std::list >(); + } 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 temp = db->metadata; + try { + for (std::list::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::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 allTags; + + for (std::list::iterator it=db->metadata.begin(), endIt=db->metadata.end(); it != endIt; ++it) { + std::list tags = it->Tags(); + for (std::list::const_iterator jt=tags.begin(), endJt=tags.end(); jt != endJt; ++jt) { + allTags.insert(jt->Name()); + } + } + for (std::list::iterator it=db->userMetadata.begin(), endIt=db->userMetadata.end(); it != endIt; ++it) { + std::list tags = it->Tags(); + for (std::list::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::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 tagsAdded, tagsRemoved; + std::list::iterator it = std::find(db->metadata.begin(), db->metadata.end(), boss::Plugin(plugin)); + if (it != db->metadata.end()) { + std::list tags = it->Tags(); + for (std::list::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 tagsAddedIDs, tagsRemovedIDs; + for (boost::unordered_set::const_iterator it=tagsAdded.begin(), endIt=tagsAdded.end(); it != endIt; ++it) { + boost::unordered_map::const_iterator mapIter = db->bashTagMap.find(*it); + if (mapIter != db->bashTagMap.end()) + tagsAddedIDs.push_back(mapIter->second); + } + for (boost::unordered_set::const_iterator it=tagsRemoved.begin(), endIt=tagsRemoved.end(); it != endIt; ++it) { + boost::unordered_map::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::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 temp = db->metadata; + for (std::list::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; } diff --git a/src/api.h b/src/api.h index 5694b230..d0200b13 100644 --- a/src/api.h +++ b/src/api.h @@ -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 diff --git a/src/game.cpp b/src/game.cpp index 299257ad..2be06c1d 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -25,6 +25,8 @@ #include "globals.h" #include "helpers.h" +#include + #include #include @@ -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(); } diff --git a/src/game.h b/src/game.h index ac9e9419..7d1eb503 100644 --- a/src/game.h +++ b/src/game.h @@ -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 conditionCache; //Holds lowercased strings. boost::unordered_map crcCache; //Holds lowercased strings. private: - uint32_t id; + unsigned id; std::string name; std::string registryKey; diff --git a/src/globals.h b/src/globals.h index e33a5ab0..e04fa717 100644 --- a/src/globals.h +++ b/src/globals.h @@ -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 diff --git a/src/legacy.py b/src/legacy.py index f8f15958..824e15b2 100755 --- a/src/legacy.py +++ b/src/legacy.py @@ -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): diff --git a/src/metadata.cpp b/src/metadata.cpp index 711fe1d7..231db81e 100644 --- a/src/metadata.cpp +++ b/src/metadata.cpp @@ -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(); + } } diff --git a/src/metadata.h b/src/metadata.h index 8a17549e..cff6291d 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -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 diff --git a/src/parsers.h b/src/parsers.h index 45ff40df..328bd49f 100644 --- a/src/parsers.h +++ b/src/parsers.h @@ -34,6 +34,8 @@ #include #include +#include + #include #include #include diff --git a/src/tester.cpp b/src/tester.cpp index ea233f6f..e38b3349 100644 --- a/src/tester.cpp +++ b/src/tester.cpp @@ -27,21 +27,8 @@ int main() { YAML::Node test = YAML::LoadFile("masterlist-example.yaml"); - list 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()); - } - } - - list 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()); - } - } + list globalMessages = test["globals"].as< list >(); + list pluginData = test["plugins"].as< list >(); cout << "Testing masterlist generator." << endl;