mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Map C++ exception types
This commit is contained in:
Generated
+3
@@ -588,7 +588,10 @@ dependencies = [
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
"delegate",
|
||||
"esplugin",
|
||||
"libloadorder",
|
||||
"libloot",
|
||||
"loot-condition-interpreter",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ Currently complete:
|
||||
- [x] Metadata-related functionality (excluding writing YAML)
|
||||
- [x] Sorting functionality
|
||||
- [x] Unit tests
|
||||
- [ ] Integration tests
|
||||
- [ ] C++ FFI
|
||||
- [x] Integration tests
|
||||
- [x] C++ FFI
|
||||
- [ ] Python FFI
|
||||
|
||||
The complete bits should match libloot commit [55b341fc6cbdccee52e42923c13a91eddb5ca97d](https://github.com/loot/libloot/commit/55b341fc6cbdccee52e42923c13a91eddb5ca97d), which is libloot v0.25.3 plus a few changes prompted by this translation.
|
||||
|
||||
@@ -41,6 +41,7 @@ set(LIBLOOT_SRC_API_CPP_FILES
|
||||
"${CMAKE_SOURCE_DIR}/src/api/convert.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/database.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/error_categories.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/exception.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/metadata/conditional_metadata.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/metadata/file.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/metadata/filename.cpp"
|
||||
@@ -89,6 +90,7 @@ set(LIBLOOT_INCLUDE_H_FILES
|
||||
set(LIBLOOT_SRC_API_H_FILES
|
||||
"${CMAKE_SOURCE_DIR}/src/api/convert.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/database.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/exception.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/game.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/plugin.h"
|
||||
)
|
||||
|
||||
@@ -10,6 +10,10 @@ delegate = "0.13.2"
|
||||
libloot = { path = ".." }
|
||||
unicase = "2.8.1"
|
||||
|
||||
esplugin = "6.1.1"
|
||||
libloadorder = { git = "https://github.com/Ortham/libloadorder.git", rev = "6245dbc6824c4751daef5ab01dfe7b9497f5446f" }
|
||||
loot-condition-interpreter = { git = "https://github.com/loot/loot-condition-interpreter.git", rev = "f7d9947fa434037743ce81e2d409184ec0bfb693" }
|
||||
|
||||
[build-dependencies]
|
||||
cxx-build = "1.0"
|
||||
|
||||
|
||||
+10
-44
@@ -1,49 +1,10 @@
|
||||
# libloot-rs C++ wrapper
|
||||
|
||||
This is an **imcomplete** and **experimental** wrapper around the Rust reimplementation of libloot that provides a C++ interface that's ABI-compatible with libloot v0.25.3.
|
||||
|
||||
## Current status
|
||||
|
||||
- [x] `EdgeType`
|
||||
- [x] `GameType`
|
||||
- [x] `LogLevel`
|
||||
- [x] `MessageType`
|
||||
- [ ] `ConditionSyntaxError`
|
||||
- [ ] `CyclicInteractionError`
|
||||
- [ ] `FileAccessError`
|
||||
- [ ] `UndefinedGroupError`
|
||||
- [ ] `std::system_error` with esplugin `std::error_category`
|
||||
- [ ] `std::system_error` with libloadorder `std::error_category`
|
||||
- [ ] `std::system_error` with loot-condition-interpreter `std::error_category`
|
||||
- [x] `ConditionalMetadata`
|
||||
- [x] `File`
|
||||
- [x] `Filename`
|
||||
- [x] `Group`
|
||||
- [x] `Location`
|
||||
- [x] `MessageContent`
|
||||
- [x] `SelectMessageContent`
|
||||
- [x] `Message`
|
||||
- [x] `PluginCleaningData`
|
||||
- [x] `PluginMetadata`
|
||||
- [x] `Tag`
|
||||
- [x] `SetLoggingCallback`
|
||||
- [x] `IsCompatible`
|
||||
- [x] `CreateGameHandle`
|
||||
- [x] `DatabaseInterface`
|
||||
- [x] `GameInterface`
|
||||
- [x] `LIBLOOT_VERSION_MAJOR`
|
||||
- [x] `LIBLOOT_VERSION_MINOR`
|
||||
- [x] `LIBLOOT_VERSION_PATCH`
|
||||
- [x] `GetLiblootVersion`
|
||||
- [x] `GetLiblootRevision`
|
||||
- [x] `PluginInterface`
|
||||
- [x] `Vertex`
|
||||
|
||||
The error types are defined but currently unused, causing behavioural differences when errors occur - see the implementation notes below for details.
|
||||
This is an **experimental** wrapper around the Rust reimplementation of libloot that provides a C++ interface that's ABI-compatible with libloot v0.25.3.
|
||||
|
||||
## Building
|
||||
|
||||
The wrapper is currently built in two layers:
|
||||
The wrapper has two layers:
|
||||
|
||||
- a static library built using Cargo, which provides a C++ interface
|
||||
- a shared library built using CMake, which wraps that C++ interface to provide another that is ABI-compatible with C++ libloot.
|
||||
@@ -62,8 +23,6 @@ This also builds a copy of the public API tests from C++ libloot v0.25.3, which
|
||||
ctest --test-dir build --output-on-failure -V
|
||||
```
|
||||
|
||||
Some of the tests currently fail: they are all expecting the exception classes that aren't yet used.
|
||||
|
||||
To package the build:
|
||||
|
||||
```
|
||||
@@ -74,9 +33,16 @@ This repository (and so the created package) doesn't currently include any of li
|
||||
|
||||
## Usage notes
|
||||
|
||||
For the first layer of the wrapper, built using Cargo:
|
||||
|
||||
- `LIBLOOT_VERSION_MAJOR`, `LIBLOOT_VERSION_MINOR` and `LIBLOOT_VERSION_PATCH` are exposed as `extern "C"` `static unsigned int` globals.
|
||||
- CXX doesn't provide integration between Rust's `Option<_>` and C++'s `std::optional<T>`, so sentinel values are used to communicate the absence of a value in the wrapper's API:
|
||||
- For `Option<&str>`, `None` is represented using an empty string, which is how it's already done for many metadata object fields in the C++ implementation of libloot.
|
||||
- For `Option<f32>`, `None` is represented using `NaN`, since the C++ implementation of libloot's public API already says that `NaN` values get converted to `None`.
|
||||
- Plugin CRCs (which are `Option<u32>` in Rust) are represented as `int64_t`, with `-1` representing `None`, since all `uint32_t` values are possibly valid CRCs.
|
||||
- All Rust errors are converted to `::rust::Error` exceptions that have a `what()` string that is the concatenation of the error's display string and all its recursive source error display strings.
|
||||
- All Rust errors are converted to `::rust::Error` exceptions that have a `what()` string that is the concatenation of the error's display string and all its recursive source error display strings. For some errors the `what()` string also includes some data that is parsed by the wrapper's second layer to differentiate certain error types.
|
||||
|
||||
For the ABI-compatible second layer of the wrapper, built using CMake:
|
||||
|
||||
- Some exceptions have changed type: they all still derive from `std::exception`, but for example some `std::logic_error` and `std::invalid_argument` exceptions have become `std::runtime_error` and `FileAccessError` exceptions, some `YAML::RepresentationException` exceptions have become `FileAccessError` exceptions, etc.
|
||||
- Exception messages are generally not expected to be the same between the two implementations.
|
||||
|
||||
+72
-40
@@ -1,5 +1,7 @@
|
||||
#include "api/convert.h"
|
||||
|
||||
#include "api/exception.h"
|
||||
|
||||
namespace loot {
|
||||
// To public types
|
||||
/////////////////////
|
||||
@@ -118,11 +120,15 @@ std::optional<loot::EdgeType> convert(uint8_t edgeType) {
|
||||
}
|
||||
|
||||
loot::Vertex convert(const loot::rust::Vertex& vertex) {
|
||||
const auto outEdgeType = convert(vertex.out_edge_type());
|
||||
if (outEdgeType.has_value()) {
|
||||
return loot::Vertex(std::string(vertex.name()), outEdgeType.value());
|
||||
} else {
|
||||
return loot::Vertex(std::string(vertex.name()));
|
||||
try {
|
||||
const auto outEdgeType = convert(vertex.out_edge_type());
|
||||
if (outEdgeType.has_value()) {
|
||||
return loot::Vertex(std::string(vertex.name()), outEdgeType.value());
|
||||
} else {
|
||||
return loot::Vertex(std::string(vertex.name()));
|
||||
}
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +146,14 @@ loot::Vertex convert(const loot::rust::Vertex& vertex) {
|
||||
::rust::Box<loot::rust::File> convert(const loot::File& file) {
|
||||
auto output = loot::rust::new_file(std::string(file.GetName()));
|
||||
output->set_display_name(file.GetDisplayName());
|
||||
output->set_detail(
|
||||
::rust::Slice(convert<loot::rust::MessageContent>(file.GetDetail())));
|
||||
|
||||
try {
|
||||
output->set_detail(
|
||||
::rust::Slice(convert<loot::rust::MessageContent>(file.GetDetail())));
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
|
||||
output->set_condition(file.GetCondition());
|
||||
|
||||
return output;
|
||||
@@ -169,29 +181,44 @@ loot::rust::MessageType convert(loot::MessageType messageType) {
|
||||
}
|
||||
|
||||
::rust::Box<loot::rust::Message> convert(const loot::Message& message) {
|
||||
auto output = loot::rust::multilingual_message(
|
||||
convert(message.GetType()),
|
||||
::rust::Slice(convert<loot::rust::MessageContent>(message.GetContent())));
|
||||
output->set_condition(message.GetCondition());
|
||||
try {
|
||||
auto output = loot::rust::multilingual_message(
|
||||
convert(message.GetType()),
|
||||
::rust::Slice(
|
||||
convert<loot::rust::MessageContent>(message.GetContent())));
|
||||
output->set_condition(message.GetCondition());
|
||||
|
||||
return output;
|
||||
return output;
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
::rust::Box<loot::rust::Tag> convert(const loot::Tag& tag) {
|
||||
const auto suggestion = tag.IsAddition() ? loot::rust::TagSuggestion::Addition
|
||||
: loot::rust::TagSuggestion::Removal;
|
||||
auto output = loot::rust::new_tag(tag.GetName(), suggestion);
|
||||
output->set_condition(tag.GetCondition());
|
||||
try {
|
||||
const auto suggestion = tag.IsAddition()
|
||||
? loot::rust::TagSuggestion::Addition
|
||||
: loot::rust::TagSuggestion::Removal;
|
||||
auto output = loot::rust::new_tag(tag.GetName(), suggestion);
|
||||
output->set_condition(tag.GetCondition());
|
||||
|
||||
return output;
|
||||
return output;
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
::rust::Box<loot::rust::PluginCleaningData> convert(
|
||||
const loot::PluginCleaningData& data) {
|
||||
auto output = loot::rust::new_plugin_cleaning_data(data.GetCRC(),
|
||||
data.GetCleaningUtility());
|
||||
output->set_detail(
|
||||
::rust::Slice(convert<loot::rust::MessageContent>(data.GetDetail())));
|
||||
try {
|
||||
output->set_detail(
|
||||
::rust::Slice(convert<loot::rust::MessageContent>(data.GetDetail())));
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
|
||||
output->set_itm_count(data.GetITMCount());
|
||||
output->set_deleted_reference_count(data.GetDeletedReferenceCount());
|
||||
output->set_deleted_navmesh_count(data.GetDeletedNavmeshCount());
|
||||
@@ -208,29 +235,34 @@ loot::rust::MessageType convert(loot::MessageType messageType) {
|
||||
|
||||
::rust::Box<loot::rust::PluginMetadata> convert(
|
||||
const loot::PluginMetadata& metadata) {
|
||||
auto output = loot::rust::new_plugin_metadata(metadata.GetName());
|
||||
try {
|
||||
auto output = loot::rust::new_plugin_metadata(metadata.GetName());
|
||||
|
||||
if (metadata.GetGroup().has_value()) {
|
||||
output->set_group(metadata.GetGroup().value());
|
||||
if (metadata.GetGroup().has_value()) {
|
||||
output->set_group(metadata.GetGroup().value());
|
||||
}
|
||||
|
||||
output->set_load_after_files(
|
||||
::rust::Slice(convert<loot::rust::File>(metadata.GetLoadAfterFiles())));
|
||||
output->set_requirements(
|
||||
::rust::Slice(convert<loot::rust::File>(metadata.GetRequirements())));
|
||||
output->set_incompatibilities(::rust::Slice(
|
||||
convert<loot::rust::File>(metadata.GetIncompatibilities())));
|
||||
output->set_messages(
|
||||
::rust::Slice(convert<loot::rust::Message>(metadata.GetMessages())));
|
||||
output->set_tags(
|
||||
::rust::Slice(convert<loot::rust::Tag>(metadata.GetTags())));
|
||||
output->set_dirty_info(::rust::Slice(
|
||||
convert<loot::rust::PluginCleaningData>(metadata.GetDirtyInfo())));
|
||||
output->set_clean_info(::rust::Slice(
|
||||
convert<loot::rust::PluginCleaningData>(metadata.GetCleanInfo())));
|
||||
output->set_locations(
|
||||
::rust::Slice(convert<loot::rust::Location>(metadata.GetLocations())));
|
||||
|
||||
return output;
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
|
||||
output->set_load_after_files(
|
||||
::rust::Slice(convert<loot::rust::File>(metadata.GetLoadAfterFiles())));
|
||||
output->set_requirements(
|
||||
::rust::Slice(convert<loot::rust::File>(metadata.GetRequirements())));
|
||||
output->set_incompatibilities(::rust::Slice(
|
||||
convert<loot::rust::File>(metadata.GetIncompatibilities())));
|
||||
output->set_messages(
|
||||
::rust::Slice(convert<loot::rust::Message>(metadata.GetMessages())));
|
||||
output->set_tags(
|
||||
::rust::Slice(convert<loot::rust::Tag>(metadata.GetTags())));
|
||||
output->set_dirty_info(
|
||||
::rust::Slice(convert<loot::rust::PluginCleaningData>(metadata.GetDirtyInfo())));
|
||||
output->set_clean_info(
|
||||
::rust::Slice(convert<loot::rust::PluginCleaningData>(metadata.GetCleanInfo())));
|
||||
output->set_locations(::rust::Slice(convert<loot::rust::Location>(metadata.GetLocations())));
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Between containers
|
||||
|
||||
+90
-32
@@ -2,6 +2,7 @@
|
||||
#include "api/database.h"
|
||||
|
||||
#include "api/convert.h"
|
||||
#include "api/exception.h"
|
||||
|
||||
namespace loot {
|
||||
Database::Database(::rust::Box<loot::rust::Database>&& database) :
|
||||
@@ -10,91 +11,148 @@ Database::Database(::rust::Box<loot::rust::Database>&& database) :
|
||||
void Database::LoadLists(const std::filesystem::path& masterlistPath,
|
||||
const std::filesystem::path& userlistPath,
|
||||
const std::filesystem::path& masterlistPreludePath) {
|
||||
if (!masterlistPath.empty()) {
|
||||
if (!masterlistPreludePath.empty()) {
|
||||
database_->load_masterlist_with_prelude(masterlistPath.u8string(),
|
||||
masterlistPreludePath.u8string());
|
||||
} else {
|
||||
database_->load_masterlist(masterlistPath.u8string());
|
||||
try {
|
||||
if (!masterlistPath.empty()) {
|
||||
if (!masterlistPreludePath.empty()) {
|
||||
database_->load_masterlist_with_prelude(
|
||||
masterlistPath.u8string(), masterlistPreludePath.u8string());
|
||||
} else {
|
||||
database_->load_masterlist(masterlistPath.u8string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!userlistPath.empty()) {
|
||||
database_->load_userlist(userlistPath.u8string());
|
||||
if (!userlistPath.empty()) {
|
||||
database_->load_userlist(userlistPath.u8string());
|
||||
}
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::WriteUserMetadata(const std::filesystem::path& outputFile,
|
||||
const bool overwrite) const {
|
||||
database_->write_user_metadata(outputFile.u8string(), overwrite);
|
||||
try {
|
||||
database_->write_user_metadata(outputFile.u8string(), overwrite);
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> Database::GetKnownBashTags() const {
|
||||
return convert<std::string>(database_->known_bash_tags());
|
||||
try {
|
||||
return convert<std::string>(database_->known_bash_tags());
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Message> Database::GetGeneralMessages(
|
||||
bool evaluateConditions) const {
|
||||
return convert<Message>(database_->general_messages(evaluateConditions));
|
||||
try {
|
||||
return convert<Message>(database_->general_messages(evaluateConditions));
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Group> Database::GetGroups(bool includeUserMetadata) const {
|
||||
return convert<Group>(database_->groups(includeUserMetadata));
|
||||
try {
|
||||
return convert<Group>(database_->groups(includeUserMetadata));
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Group> Database::GetUserGroups() const {
|
||||
return convert<Group>(database_->user_groups());
|
||||
try {
|
||||
return convert<Group>(database_->user_groups());
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::SetUserGroups(const std::vector<Group>& groups) {
|
||||
database_->set_user_groups(::rust::Slice(convert<loot::rust::Group>(groups)));
|
||||
try {
|
||||
database_->set_user_groups(
|
||||
::rust::Slice(convert<loot::rust::Group>(groups)));
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vertex> Database::GetGroupsPath(
|
||||
const std::string& fromGroupName,
|
||||
const std::string& toGroupName) const {
|
||||
return convert<Vertex>(database_->groups_path(fromGroupName, toGroupName));
|
||||
try {
|
||||
return convert<Vertex>(database_->groups_path(fromGroupName, toGroupName));
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<PluginMetadata> Database::GetPluginMetadata(
|
||||
const std::string& plugin,
|
||||
bool includeUserMetadata,
|
||||
bool evaluateConditions) const {
|
||||
const auto metadata = database_->plugin_metadata(
|
||||
plugin, includeUserMetadata, evaluateConditions);
|
||||
if (metadata->is_some()) {
|
||||
return convert(metadata->as_ref());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
try {
|
||||
const auto metadata = database_->plugin_metadata(
|
||||
plugin, includeUserMetadata, evaluateConditions);
|
||||
if (metadata->is_some()) {
|
||||
return convert(metadata->as_ref());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<PluginMetadata> Database::GetPluginUserMetadata(
|
||||
const std::string& plugin,
|
||||
bool evaluateConditions) const {
|
||||
const auto metadata =
|
||||
database_->plugin_user_metadata(plugin, evaluateConditions);
|
||||
if (metadata->is_some()) {
|
||||
return convert(metadata->as_ref());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
try {
|
||||
const auto metadata =
|
||||
database_->plugin_user_metadata(plugin, evaluateConditions);
|
||||
if (metadata->is_some()) {
|
||||
return convert(metadata->as_ref());
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::SetPluginUserMetadata(const PluginMetadata& pluginMetadata) {
|
||||
database_->set_plugin_user_metadata(convert(pluginMetadata));
|
||||
try {
|
||||
database_->set_plugin_user_metadata(convert(pluginMetadata));
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::DiscardPluginUserMetadata(const std::string& plugin) {
|
||||
database_->discard_plugin_user_metadata(plugin);
|
||||
try {
|
||||
database_->discard_plugin_user_metadata(plugin);
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::DiscardAllUserMetadata() {
|
||||
database_->discard_all_user_metadata();
|
||||
try {
|
||||
database_->discard_all_user_metadata();
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
void Database::WriteMinimalList(const std::filesystem::path& outputFile,
|
||||
const bool overwrite) const {
|
||||
database_->write_minimal_list(outputFile.u8string(), overwrite);
|
||||
try {
|
||||
database_->write_minimal_list(outputFile.u8string(), overwrite);
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
#include "api/exception.h"
|
||||
|
||||
#include <charconv>
|
||||
|
||||
#include "loot/exception/condition_syntax_error.h"
|
||||
#include "loot/exception/cyclic_interaction_error.h"
|
||||
#include "loot/exception/error_categories.h"
|
||||
#include "loot/exception/file_access_error.h"
|
||||
#include "loot/exception/undefined_group_error.h"
|
||||
#include "loot/vertex.h"
|
||||
|
||||
namespace {
|
||||
using loot::EdgeType;
|
||||
using loot::Vertex;
|
||||
|
||||
constexpr std::string_view CYCLIC_ERROR_PREFIX = "CyclicInteractionError: ";
|
||||
constexpr std::string_view UNDEFINED_GROUP_ERROR_PREFIX =
|
||||
"UndefinedGroupError: ";
|
||||
constexpr std::string_view ESPLUGIN_ERROR_PREFIX = "EspluginError: ";
|
||||
constexpr std::string_view LIBLOADORDER_ERROR_PREFIX = "LibloadorderError: ";
|
||||
constexpr std::string_view LCI_ERROR_PREFIX = "LciError: ";
|
||||
constexpr std::string_view FILE_ACCESS_ERROR_PREFIX = "FileAccessError: ";
|
||||
constexpr std::string_view INVALID_ARGUMENT_PREFIX = "InvalidArgument: ";
|
||||
|
||||
bool startsWith(const std::string_view& str, const std::string_view& prefix) {
|
||||
if (str.size() < prefix.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return str.substr(0, prefix.size()) == prefix;
|
||||
}
|
||||
|
||||
std::string replace(const std::string_view& str,
|
||||
const std::string_view& from,
|
||||
const std::string_view& to) {
|
||||
std::string out;
|
||||
out.reserve(str.size());
|
||||
|
||||
size_t i = 0;
|
||||
while (i < str.size()) {
|
||||
if (i + from.size() <= str.size() && str.substr(i, from.size()) == from) {
|
||||
out.append(to);
|
||||
i += from.size();
|
||||
} else {
|
||||
out.push_back(str[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
EdgeType toEdgeType(const std::string_view& edgeTypeDisplay) {
|
||||
if (edgeTypeDisplay == "Hardcoded") {
|
||||
return EdgeType::hardcoded;
|
||||
} else if (edgeTypeDisplay == "Master Flag") {
|
||||
return EdgeType::masterFlag;
|
||||
} else if (edgeTypeDisplay == "Master") {
|
||||
return EdgeType::master;
|
||||
} else if (edgeTypeDisplay == "Masterlist Requirement") {
|
||||
return EdgeType::masterlistRequirement;
|
||||
} else if (edgeTypeDisplay == "User Requirement") {
|
||||
return EdgeType::userRequirement;
|
||||
} else if (edgeTypeDisplay == "Masterlist Load After") {
|
||||
return EdgeType::masterlistLoadAfter;
|
||||
} else if (edgeTypeDisplay == "User Load After") {
|
||||
return EdgeType::userLoadAfter;
|
||||
} else if (edgeTypeDisplay == "Masterlist Group") {
|
||||
return EdgeType::masterlistGroup;
|
||||
} else if (edgeTypeDisplay == "User Group") {
|
||||
return EdgeType::userGroup;
|
||||
} else if (edgeTypeDisplay == "Record Overlap") {
|
||||
return EdgeType::recordOverlap;
|
||||
} else if (edgeTypeDisplay == "Asset Overlap") {
|
||||
return EdgeType::assetOverlap;
|
||||
} else if (edgeTypeDisplay == "Tie Break") {
|
||||
return EdgeType::tieBreak;
|
||||
} else if (edgeTypeDisplay == "Blueprint Master") {
|
||||
return EdgeType::blueprintMaster;
|
||||
} else {
|
||||
std::string what("Unrecognised edge type: ");
|
||||
what += edgeTypeDisplay;
|
||||
throw std::logic_error(what);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vertex> parseCyclicError(const std::string_view& what) {
|
||||
const auto suffix = what.substr(0, CYCLIC_ERROR_PREFIX.size());
|
||||
|
||||
std::vector<Vertex> vertices;
|
||||
size_t pos = 0;
|
||||
while (pos < suffix.size()) {
|
||||
const auto sepPos = suffix.find("--", pos);
|
||||
const auto escapedName = suffix.substr(pos, sepPos);
|
||||
const auto name = replace(replace(escapedName, "\\-", "-"), "\\\\", "\\");
|
||||
|
||||
if (sepPos != std::string::npos) {
|
||||
const auto secondSepPos = suffix.find("--", sepPos + 2);
|
||||
const auto escapedEdgeName =
|
||||
suffix.substr(sepPos + 2, secondSepPos - (sepPos + 2));
|
||||
|
||||
vertices.push_back(Vertex(name, toEdgeType(escapedEdgeName)));
|
||||
|
||||
pos = secondSepPos + 2;
|
||||
} else {
|
||||
vertices.push_back(Vertex(name));
|
||||
pos = suffix.size();
|
||||
}
|
||||
}
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
std::string getErrorSuffix(const std::string_view& what) {
|
||||
const auto sepPos = what.find(": ");
|
||||
|
||||
return std::string(what.substr(sepPos + 2));
|
||||
}
|
||||
|
||||
std::pair<int, std::string> parseSystemError(
|
||||
const std::string_view& whatSuffix) {
|
||||
const auto sepPos = whatSuffix.find(": ");
|
||||
int code;
|
||||
const auto result =
|
||||
std::from_chars(whatSuffix.data(), whatSuffix.data() + sepPos, code);
|
||||
if (result.ec != std::errc{}) {
|
||||
std::string err = "Could not parse error code from string: ";
|
||||
err += whatSuffix;
|
||||
throw std::runtime_error(err);
|
||||
}
|
||||
|
||||
return std::make_pair(code, std::string(whatSuffix.substr(sepPos + 2)));
|
||||
}
|
||||
}
|
||||
|
||||
namespace loot {
|
||||
std::exception_ptr mapError(const ::rust::Error& error) {
|
||||
if (startsWith(error.what(), CYCLIC_ERROR_PREFIX)) {
|
||||
return std::make_exception_ptr(
|
||||
CyclicInteractionError(parseCyclicError(error.what())));
|
||||
} else if (startsWith(error.what(), UNDEFINED_GROUP_ERROR_PREFIX)) {
|
||||
return std::make_exception_ptr(
|
||||
UndefinedGroupError(getErrorSuffix(error.what())));
|
||||
} else if (startsWith(error.what(), ESPLUGIN_ERROR_PREFIX)) {
|
||||
const auto [code, details] = parseSystemError(
|
||||
std::string_view(error.what()).substr(0, ESPLUGIN_ERROR_PREFIX.size()));
|
||||
|
||||
return std::make_exception_ptr(
|
||||
std::system_error(code, esplugin_category(), details));
|
||||
|
||||
} else if (startsWith(error.what(), LIBLOADORDER_ERROR_PREFIX)) {
|
||||
const auto [code, details] =
|
||||
parseSystemError(std::string_view(error.what())
|
||||
.substr(0, LIBLOADORDER_ERROR_PREFIX.size()));
|
||||
|
||||
return std::make_exception_ptr(
|
||||
std::system_error(code, libloadorder_category(), details));
|
||||
} else if (startsWith(error.what(), LCI_ERROR_PREFIX)) {
|
||||
const auto [code, details] = parseSystemError(
|
||||
std::string_view(error.what()).substr(0, LCI_ERROR_PREFIX.size()));
|
||||
|
||||
return std::make_exception_ptr(ConditionSyntaxError(
|
||||
code, loot_condition_interpreter_category(), details));
|
||||
} else if (startsWith(error.what(), FILE_ACCESS_ERROR_PREFIX)) {
|
||||
return std::make_exception_ptr(
|
||||
FileAccessError(getErrorSuffix(error.what())));
|
||||
} else if (startsWith(error.what(), FILE_ACCESS_ERROR_PREFIX)) {
|
||||
return std::make_exception_ptr(
|
||||
FileAccessError(getErrorSuffix(error.what())));
|
||||
} else if (startsWith(error.what(), INVALID_ARGUMENT_PREFIX)) {
|
||||
return std::make_exception_ptr(
|
||||
std::invalid_argument(getErrorSuffix(error.what())));
|
||||
} else {
|
||||
return std::make_exception_ptr(std::runtime_error(error.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef LOOT_API_EXCEPTION
|
||||
#define LOOT_API_EXCEPTION
|
||||
|
||||
#include "rust/cxx.h"
|
||||
|
||||
namespace loot {
|
||||
std::exception_ptr mapError(const ::rust::Error& error);
|
||||
}
|
||||
|
||||
#endif
|
||||
+76
-29
@@ -2,6 +2,7 @@
|
||||
#include "api/game.h"
|
||||
|
||||
#include "api/convert.h"
|
||||
#include "api/exception.h"
|
||||
|
||||
namespace {
|
||||
loot::GameType convert(loot::rust::GameType gameType) {
|
||||
@@ -66,12 +67,16 @@ rust::Box<loot::rust::Game> constructGame(
|
||||
const loot::GameType gameType,
|
||||
const std::filesystem::path& gamePath,
|
||||
const std::filesystem::path& localDataPath) {
|
||||
if (localDataPath.empty()) {
|
||||
return std::move(
|
||||
loot::rust::new_game(convert(gameType), gamePath.u8string()));
|
||||
} else {
|
||||
return std::move(loot::rust::new_game_with_local_path(
|
||||
convert(gameType), gamePath.u8string(), localDataPath.u8string()));
|
||||
try {
|
||||
if (localDataPath.empty()) {
|
||||
return std::move(
|
||||
loot::rust::new_game(convert(gameType), gamePath.u8string()));
|
||||
} else {
|
||||
return std::move(loot::rust::new_game_with_local_path(
|
||||
convert(gameType), gamePath.u8string(), localDataPath.u8string()));
|
||||
}
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(loot::mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,19 +97,30 @@ Game::Game(const GameType gameType,
|
||||
game_(constructGame(gameType, gamePath, localDataPath)),
|
||||
database_(game_->database()) {}
|
||||
|
||||
GameType Game::GetType() const { return ::convert(game_->game_type()); }
|
||||
GameType Game::GetType() const {
|
||||
try {
|
||||
return ::convert(game_->game_type());
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
const DatabaseInterface& Game::GetDatabase() const { return database_; }
|
||||
|
||||
DatabaseInterface& Game::GetDatabase() { return database_; }
|
||||
|
||||
std::vector<std::filesystem::path> Game::GetAdditionalDataPaths() const {
|
||||
std::vector<std::filesystem::path> paths;
|
||||
for (const auto& path_str : game_->additional_data_paths()) {
|
||||
paths.push_back(std::filesystem::u8path(path_str.begin(), path_str.end()));
|
||||
}
|
||||
try {
|
||||
std::vector<std::filesystem::path> paths;
|
||||
for (const auto& path_str : game_->additional_data_paths()) {
|
||||
paths.push_back(
|
||||
std::filesystem::u8path(path_str.begin(), path_str.end()));
|
||||
}
|
||||
|
||||
return paths;
|
||||
return paths;
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
void Game::SetAdditionalDataPaths(
|
||||
@@ -115,8 +131,12 @@ void Game::SetAdditionalDataPaths(
|
||||
path_strings.push_back(path.u8string());
|
||||
path_strs.push_back(path_strings.back());
|
||||
}
|
||||
|
||||
game_->set_additional_data_paths(::rust::Slice<const ::rust::Str>(path_strs));
|
||||
try {
|
||||
game_->set_additional_data_paths(
|
||||
::rust::Slice<const ::rust::Str>(path_strs));
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
bool Game::IsValidPlugin(const std::filesystem::path& pluginPath) const {
|
||||
@@ -132,10 +152,14 @@ void Game::LoadPlugins(const std::vector<std::filesystem::path>& pluginPaths,
|
||||
path_strs.push_back(path_strings.back());
|
||||
}
|
||||
|
||||
if (loadHeadersOnly) {
|
||||
game_->load_plugin_headers(::rust::Slice<const ::rust::Str>(path_strs));
|
||||
} else {
|
||||
game_->load_plugins(::rust::Slice<const ::rust::Str>(path_strs));
|
||||
try {
|
||||
if (loadHeadersOnly) {
|
||||
game_->load_plugin_headers(::rust::Slice<const ::rust::Str>(path_strs));
|
||||
} else {
|
||||
game_->load_plugins(::rust::Slice<const ::rust::Str>(path_strs));
|
||||
}
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
|
||||
for (const auto& path : pluginPaths) {
|
||||
@@ -164,10 +188,14 @@ const PluginInterface* Game::GetPlugin(const std::string& pluginName) const {
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
auto plugin = std::make_shared<Plugin>(std::move(pluginOpt->as_ref()));
|
||||
const auto result = plugins_.emplace(key, plugin);
|
||||
try {
|
||||
auto plugin = std::make_shared<Plugin>(std::move(pluginOpt->as_ref()));
|
||||
const auto result = plugins_.emplace(key, plugin);
|
||||
|
||||
return result.first->second.get();
|
||||
return result.first->second.get();
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const PluginInterface*> Game::GetLoadedPlugins() const {
|
||||
@@ -188,23 +216,38 @@ std::vector<std::string> Game::SortPlugins(
|
||||
const std::vector<std::string>& pluginFilenames) {
|
||||
const auto strs = as_str_refs(pluginFilenames);
|
||||
|
||||
const auto results =
|
||||
game_->sort_plugins(::rust::Slice(strs));
|
||||
try {
|
||||
const auto results = game_->sort_plugins(::rust::Slice(strs));
|
||||
|
||||
return convert<std::string>(results);
|
||||
return convert<std::string>(results);
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
void Game::LoadCurrentLoadOrderState() {
|
||||
game_->load_current_load_order_state();
|
||||
try {
|
||||
game_->load_current_load_order_state();
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
bool Game::IsLoadOrderAmbiguous() const {
|
||||
return game_->is_load_order_ambiguous();
|
||||
try {
|
||||
return game_->is_load_order_ambiguous();
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path Game::GetActivePluginsFilePath() const {
|
||||
const auto path_string = game_->active_plugins_file_path();
|
||||
return std::filesystem::u8path(path_string.begin(), path_string.end());
|
||||
try {
|
||||
const auto path_string = game_->active_plugins_file_path();
|
||||
return std::filesystem::u8path(path_string.begin(), path_string.end());
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
bool Game::IsPluginActive(const std::string& pluginName) const {
|
||||
@@ -218,6 +261,10 @@ std::vector<std::string> Game::GetLoadOrder() const {
|
||||
void Game::SetLoadOrder(const std::vector<std::string>& loadOrder) {
|
||||
const auto strs = as_str_refs(loadOrder);
|
||||
|
||||
game_->set_load_order(::rust::Slice(strs));
|
||||
try {
|
||||
game_->set_load_order(::rust::Slice(strs));
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-4
@@ -4,6 +4,7 @@
|
||||
#include <typeinfo>
|
||||
|
||||
#include "api/convert.h"
|
||||
#include "api/exception.h"
|
||||
|
||||
namespace loot {
|
||||
Plugin::Plugin(::rust::Box<loot::rust::PluginRef> plugin) :
|
||||
@@ -30,7 +31,11 @@ std::optional<std::string> Plugin::GetVersion() const {
|
||||
}
|
||||
|
||||
std::vector<std::string> Plugin::GetMasters() const {
|
||||
return convert<std::string>(plugin_->masters());
|
||||
try {
|
||||
return convert<std::string>(plugin_->masters());
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Tag> Plugin::GetBashTags() const {
|
||||
@@ -64,15 +69,27 @@ bool Plugin::IsBlueprintPlugin() const {
|
||||
}
|
||||
|
||||
bool Plugin::IsValidAsLightPlugin() const {
|
||||
return plugin_->is_valid_as_light_plugin();
|
||||
try {
|
||||
return plugin_->is_valid_as_light_plugin();
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
bool Plugin::IsValidAsMediumPlugin() const {
|
||||
return plugin_->is_valid_as_medium_plugin();
|
||||
try {
|
||||
return plugin_->is_valid_as_medium_plugin();
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
bool Plugin::IsValidAsUpdatePlugin() const {
|
||||
return plugin_->is_valid_as_update_plugin();
|
||||
try {
|
||||
return plugin_->is_valid_as_update_plugin();
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
|
||||
bool Plugin::IsEmpty() const { return plugin_->is_empty(); }
|
||||
@@ -86,6 +103,8 @@ bool Plugin::DoRecordsOverlap(const PluginInterface& plugin) const {
|
||||
return plugin_->do_records_overlap(*otherPlugin.plugin_);
|
||||
} catch (std::bad_cast&) {
|
||||
throw std::invalid_argument("Tried to check if records overlapped with a different concrete type implementing PluginInterface");
|
||||
} catch (const ::rust::Error& e) {
|
||||
std::rethrow_exception(mapError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
pub(crate) mod lci {
|
||||
use loot_condition_interpreter::Error;
|
||||
use std::ffi::c_int;
|
||||
|
||||
/// Invalid arguments were given for the function.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LCI_ERROR_INVALID_ARGS: c_int = -1;
|
||||
|
||||
/// Something went wrong while parsing the condition expression.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LCI_ERROR_PARSING_ERROR: c_int = -2;
|
||||
|
||||
/// Something went wrong while getting the version of an executable.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LCI_ERROR_PE_PARSING_ERROR: c_int = -3;
|
||||
|
||||
/// Some sort of I/O error occurred.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LCI_ERROR_IO_ERROR: c_int = -4;
|
||||
|
||||
/// Something panicked.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LCI_ERROR_PANICKED: c_int = -5;
|
||||
|
||||
/// A thread lock was poisoned.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LCI_ERROR_POISONED_THREAD_LOCK: c_int = -6;
|
||||
|
||||
/// Failed to encode string as a C string, e.g. because there was a nul present.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LCI_ERROR_TEXT_ENCODE_FAIL: c_int = -7;
|
||||
|
||||
/// The library encountered an error that should not have been possible to encounter.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LCI_ERROR_INTERNAL_LOGIC_ERROR: c_int = -8;
|
||||
|
||||
pub(crate) fn map_error(err: &Error) -> c_int {
|
||||
match err {
|
||||
Error::ParsingIncomplete(_) => LCI_ERROR_PARSING_ERROR,
|
||||
Error::UnconsumedInput(_) => LCI_ERROR_PARSING_ERROR,
|
||||
Error::ParsingError(_, _) => LCI_ERROR_PARSING_ERROR,
|
||||
Error::PeParsingError(_, _) => LCI_ERROR_PE_PARSING_ERROR,
|
||||
Error::IoError(_, _) => LCI_ERROR_IO_ERROR,
|
||||
_ => LCI_ERROR_INTERNAL_LOGIC_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod libloadorder {
|
||||
use loadorder::Error;
|
||||
use std::ffi::c_uint;
|
||||
|
||||
/// There is a mismatch between the files used to keep track of load order.
|
||||
///
|
||||
/// This warning can only occur when using libloadorder with a game that uses the textfile-based
|
||||
/// load order system. The load order in the active plugins list file (`plugins.txt`) does not
|
||||
/// match the load order in the full load order file (`loadorder.txt`). Synchronisation between
|
||||
/// the two is automatic when load order is managed through libloadorder. It is left to the client
|
||||
/// to decide how best to restore synchronisation.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_WARN_LO_MISMATCH: c_uint = 2;
|
||||
|
||||
/// The specified file could not be found.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_FILE_NOT_FOUND: c_uint = 6;
|
||||
|
||||
/// A file could not be renamed.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_FILE_RENAME_FAIL: c_uint = 7;
|
||||
|
||||
/// There was an error parsing a plugin file.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_FILE_PARSE_FAIL: c_uint = 10;
|
||||
|
||||
/// Invalid arguments were given for the function.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_INVALID_ARGS: c_uint = 12;
|
||||
|
||||
/// A thread lock was poisoned.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_POISONED_THREAD_LOCK: c_uint = 14;
|
||||
|
||||
/// An unknown I/O error occurred. This is used when the I/O error kind doesn't fit another error
|
||||
/// code.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_IO_ERROR: c_uint = 15;
|
||||
|
||||
/// Permission denied while trying to access a filesystem path.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_IO_PERMISSION_DENIED: c_uint = 16;
|
||||
|
||||
/// A plugin filename contains characters that do not have Windows-1252 code points, or a character
|
||||
/// string contains a null character.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_TEXT_ENCODE_FAIL: c_uint = 17;
|
||||
|
||||
/// Text expected to be encoded in Windows-1252 could not be decoded to UTF-8.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_TEXT_DECODE_FAIL: c_uint = 18;
|
||||
|
||||
/// The library encountered an error that should not have been possible to encounter.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_INTERNAL_LOGIC_ERROR: c_uint = 19;
|
||||
|
||||
/// Something panicked.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_PANICKED: c_uint = 20;
|
||||
|
||||
/// A path cannot be encoded in UTF-8.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_PATH_ENCODE_FAIL: c_uint = 21;
|
||||
|
||||
/// An unknown operating system error occurred.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_SYSTEM_ERROR: c_uint = 22;
|
||||
|
||||
/// A system path definition (e.g. for local app data on Windows, or $HOME on Linux) could not be
|
||||
/// found.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_ERROR_NO_PATH: c_uint = 23;
|
||||
|
||||
/// Matches the value of the highest-numbered return code.
|
||||
///
|
||||
/// Provided in case clients wish to incorporate additional return codes in their implementation
|
||||
/// and desire some method of avoiding value conflicts.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static LIBLO_RETURN_MAX: c_uint = 23;
|
||||
|
||||
fn map_io_error(err: &std::io::Error) -> c_uint {
|
||||
use std::io::ErrorKind::*;
|
||||
match err.kind() {
|
||||
NotFound => LIBLO_ERROR_FILE_NOT_FOUND,
|
||||
AlreadyExists => LIBLO_ERROR_FILE_RENAME_FAIL,
|
||||
PermissionDenied => LIBLO_ERROR_IO_PERMISSION_DENIED,
|
||||
_ => LIBLO_ERROR_IO_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_error(err: &Error) -> c_uint {
|
||||
use Error::*;
|
||||
match *err {
|
||||
InvalidPath(_) => LIBLO_ERROR_FILE_NOT_FOUND,
|
||||
IoError(_, ref x) => map_io_error(x),
|
||||
NoFilename(_) => LIBLO_ERROR_FILE_PARSE_FAIL,
|
||||
DecodeError(_) => LIBLO_ERROR_TEXT_DECODE_FAIL,
|
||||
EncodeError(_) => LIBLO_ERROR_TEXT_ENCODE_FAIL,
|
||||
PluginParsingError(_, _) => LIBLO_ERROR_FILE_PARSE_FAIL,
|
||||
PluginNotFound(_) => LIBLO_ERROR_INVALID_ARGS,
|
||||
TooManyActivePlugins { .. } => LIBLO_ERROR_INVALID_ARGS,
|
||||
DuplicatePlugin(_) => LIBLO_ERROR_INVALID_ARGS,
|
||||
NonMasterBeforeMaster { .. } => LIBLO_ERROR_INVALID_ARGS,
|
||||
InvalidEarlyLoadingPluginPosition { .. } => LIBLO_ERROR_INVALID_ARGS,
|
||||
ImplicitlyActivePlugin(_) => LIBLO_ERROR_INVALID_ARGS,
|
||||
NoLocalAppData => LIBLO_ERROR_INVALID_ARGS,
|
||||
NoDocumentsPath => LIBLO_ERROR_INVALID_ARGS,
|
||||
NoUserConfigPath => LIBLO_ERROR_NO_PATH,
|
||||
NoUserDataPath => LIBLO_ERROR_NO_PATH,
|
||||
NoProgramFilesPath => LIBLO_ERROR_NO_PATH,
|
||||
UnrepresentedHoist { .. } => LIBLO_ERROR_INVALID_ARGS,
|
||||
InstalledPlugin(_) => LIBLO_ERROR_INVALID_ARGS,
|
||||
IniParsingError { .. } => LIBLO_ERROR_FILE_PARSE_FAIL,
|
||||
VdfParsingError(_, _) => LIBLO_ERROR_FILE_PARSE_FAIL,
|
||||
SystemError(_, _) => LIBLO_ERROR_SYSTEM_ERROR,
|
||||
InvalidBlueprintPluginPosition { .. } => LIBLO_ERROR_INVALID_ARGS,
|
||||
_ => LIBLO_ERROR_INTERNAL_LOGIC_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod esplugin {
|
||||
use std::ffi::c_uint;
|
||||
|
||||
use esplugin::Error;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_NULL_POINTER: u32 = 1;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_NOT_UTF8: u32 = 2;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_STRING_CONTAINS_NUL: u32 = 3;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_INVALID_GAME_ID: u32 = 4;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_PARSE_ERROR: u32 = 5;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_PANICKED: u32 = 6;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_NO_FILENAME: u32 = 7;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_TEXT_DECODE_ERROR: u32 = 8;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_TEXT_ENCODE_ERROR: u32 = 9;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_IO_ERROR: u32 = 10;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_FILE_NOT_FOUND: u32 = 11;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_IO_PERMISSION_DENIED: u32 = 12;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_UNRESOLVED_RECORD_IDS: u32 = 13;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static ESP_ERROR_PLUGIN_METADATA_NOT_FOUND: u32 = 14;
|
||||
|
||||
fn map_io_error(err: &std::io::Error) -> c_uint {
|
||||
match err.kind() {
|
||||
std::io::ErrorKind::NotFound => ESP_ERROR_FILE_NOT_FOUND,
|
||||
std::io::ErrorKind::PermissionDenied => ESP_ERROR_IO_PERMISSION_DENIED,
|
||||
_ => ESP_ERROR_IO_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_error(err: &Error) -> c_uint {
|
||||
match *err {
|
||||
Error::IoError(ref x) => map_io_error(x),
|
||||
Error::NoFilename(_) => ESP_ERROR_NO_FILENAME,
|
||||
Error::ParsingIncomplete(_) => ESP_ERROR_PARSE_ERROR,
|
||||
Error::ParsingError(_, _) => ESP_ERROR_PARSE_ERROR,
|
||||
Error::DecodeError(_) => ESP_ERROR_TEXT_DECODE_ERROR,
|
||||
Error::UnresolvedRecordIds(_) => ESP_ERROR_UNRESOLVED_RECORD_IDS,
|
||||
Error::PluginMetadataNotFound(_) => ESP_ERROR_PLUGIN_METADATA_NOT_FOUND,
|
||||
}
|
||||
}
|
||||
}
|
||||
+200
-14
@@ -1,10 +1,11 @@
|
||||
mod database;
|
||||
mod error_codes;
|
||||
mod game;
|
||||
mod metadata;
|
||||
mod plugin;
|
||||
|
||||
use database::{Database, Vertex, new_vertex};
|
||||
use game::{Game, new_game, new_game_with_local_path};
|
||||
use game::{Game, NotValidUtf8, new_game, new_game_with_local_path};
|
||||
use metadata::{
|
||||
File, Filename, Group, Location, Message, MessageContent, OptionalMessageContentRef,
|
||||
OptionalPluginMetadata, PluginCleaningData, PluginMetadata, Tag, group_default_name,
|
||||
@@ -14,32 +15,217 @@ use metadata::{
|
||||
};
|
||||
use plugin::{OptionalPluginRef, PluginRef};
|
||||
use std::{
|
||||
ffi::{c_char, c_uchar, c_uint, c_void, CString},
|
||||
sync::{atomic::AtomicPtr, Mutex},
|
||||
error::Error,
|
||||
ffi::{CString, c_char, c_uchar, c_uint, c_void},
|
||||
sync::{Mutex, atomic::AtomicPtr},
|
||||
};
|
||||
use unicase::UniCase;
|
||||
|
||||
use libloot::set_logging_callback;
|
||||
use libloot::{
|
||||
error::{
|
||||
ConditionEvaluationError, DatabaseLockPoisonError, GameHandleCreationError,
|
||||
GroupsPathError, LoadOrderError, LoadOrderStateError, LoadPluginsError,
|
||||
MetadataRetrievalError, PluginDataError, SortPluginsError,
|
||||
},
|
||||
metadata::error::{
|
||||
LoadMetadataError, MultilingualMessageContentsError, RegexError, WriteMetadataError,
|
||||
},
|
||||
set_logging_callback,
|
||||
};
|
||||
pub use libloot::{is_compatible, libloot_revision, libloot_version};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VerboseError(Box<dyn std::error::Error>);
|
||||
pub enum VerboseError {
|
||||
CyclicInteractionError(Vec<libloot::Vertex>),
|
||||
UndefinedGroupError(String),
|
||||
EspluginError(u32, String),
|
||||
LibloadorderError(u32, String),
|
||||
LciError(i32, String),
|
||||
FileAccessError(String),
|
||||
InvalidArgument(String),
|
||||
Other(Box<dyn std::error::Error>),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VerboseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)?;
|
||||
let mut error = self.0.as_ref();
|
||||
while let Some(source) = error.source() {
|
||||
write!(f, ": {}", source)?;
|
||||
error = source;
|
||||
match self {
|
||||
Self::CyclicInteractionError(cycle) => {
|
||||
write!(f, "CyclicInteractionError: ")?;
|
||||
for vertex in cycle {
|
||||
let name = vertex.name().replace("\\", "\\\\").replace("-", "\\-");
|
||||
match vertex.out_edge_type() {
|
||||
Some(e) => write!(f, "{}--{}--", name, e)?,
|
||||
None => write!(f, "{}", name)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Self::UndefinedGroupError(group) => {
|
||||
write!(f, "UndefinedGroupError: {}", group)
|
||||
}
|
||||
Self::EspluginError(c, s) => {
|
||||
write!(f, "EspluginError: {}: {}", c, s)
|
||||
}
|
||||
Self::LibloadorderError(c, s) => {
|
||||
write!(f, "LibloadorderError: {}: {}", c, s)
|
||||
}
|
||||
Self::LciError(c, s) => {
|
||||
write!(f, "LciError: {}: {}", c, s)
|
||||
}
|
||||
Self::FileAccessError(s) => write!(f, "FileAccessError: {}", s),
|
||||
Self::InvalidArgument(s) => write!(f, "InvalidArgument: {}", s),
|
||||
Self::Other(e) => {
|
||||
write!(f, "{}", e)?;
|
||||
let mut error = e.as_ref();
|
||||
while let Some(source) = error.source() {
|
||||
write!(f, ": {}", source)?;
|
||||
error = source;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: std::error::Error + 'static> From<E> for VerboseError {
|
||||
fn from(value: E) -> Self {
|
||||
VerboseError(Box::new(value))
|
||||
impl From<GameHandleCreationError> for VerboseError {
|
||||
fn from(value: GameHandleCreationError) -> Self {
|
||||
match value {
|
||||
GameHandleCreationError::LoadOrderError(e) => e.into(),
|
||||
GameHandleCreationError::NotADirectory(_) => {
|
||||
Self::InvalidArgument(value.to_string())
|
||||
}
|
||||
_ => Self::Other(Box::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnsupportedEnumValueError> for VerboseError {
|
||||
fn from(value: UnsupportedEnumValueError) -> Self {
|
||||
Self::Other(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NotValidUtf8> for VerboseError {
|
||||
fn from(value: NotValidUtf8) -> Self {
|
||||
Self::Other(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DatabaseLockPoisonError> for VerboseError {
|
||||
fn from(value: DatabaseLockPoisonError) -> Self {
|
||||
Self::Other(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadPluginsError> for VerboseError {
|
||||
fn from(value: LoadPluginsError) -> Self {
|
||||
match value {
|
||||
LoadPluginsError::PluginDataError(e) => e.into(),
|
||||
LoadPluginsError::PluginValidationError(_) => {
|
||||
Self::InvalidArgument(value.to_string())
|
||||
}
|
||||
_ => Self::Other(Box::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SortPluginsError> for VerboseError {
|
||||
fn from(value: SortPluginsError) -> Self {
|
||||
match value {
|
||||
SortPluginsError::MetadataRetrievalError(e) => e.into(),
|
||||
SortPluginsError::UndefinedGroup(g) => Self::UndefinedGroupError(g),
|
||||
SortPluginsError::CycleFound(cycle) => Self::CyclicInteractionError(cycle),
|
||||
SortPluginsError::PluginDataError(e) => e.into(),
|
||||
_ => Self::Other(Box::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadOrderStateError> for VerboseError {
|
||||
fn from(value: LoadOrderStateError) -> Self {
|
||||
match value {
|
||||
LoadOrderStateError::LoadOrderError(e) => e.into(),
|
||||
_ => Self::Other(Box::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadOrderError> for VerboseError {
|
||||
fn from(value: LoadOrderError) -> Self {
|
||||
let error = value
|
||||
.source()
|
||||
.expect("LoadOrderError has source")
|
||||
.downcast_ref::<loadorder::Error>()
|
||||
.expect("LoadOrderError source is a loadorder::Error");
|
||||
let error_code = error_codes::libloadorder::map_error(error);
|
||||
Self::LibloadorderError(error_code, error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoadMetadataError> for VerboseError {
|
||||
fn from(value: LoadMetadataError) -> Self {
|
||||
Self::FileAccessError(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WriteMetadataError> for VerboseError {
|
||||
fn from(value: WriteMetadataError) -> Self {
|
||||
Self::FileAccessError(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConditionEvaluationError> for VerboseError {
|
||||
fn from(value: ConditionEvaluationError) -> Self {
|
||||
let error = value
|
||||
.source()
|
||||
.expect("LoadOrderError has source")
|
||||
.downcast_ref::<loot_condition_interpreter::Error>()
|
||||
.expect("LoadOrderError source is a loot_condition_interpreter::Error");
|
||||
let error_code = error_codes::lci::map_error(error);
|
||||
Self::LciError(error_code, error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GroupsPathError> for VerboseError {
|
||||
fn from(value: GroupsPathError) -> Self {
|
||||
match value {
|
||||
GroupsPathError::UndefinedGroup(g) => Self::UndefinedGroupError(g),
|
||||
GroupsPathError::CycleFound(cycle) => Self::CyclicInteractionError(cycle),
|
||||
_ => Self::Other(Box::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MetadataRetrievalError> for VerboseError {
|
||||
fn from(value: MetadataRetrievalError) -> Self {
|
||||
match value {
|
||||
MetadataRetrievalError::ConditionEvaluationError(e) => e.into(),
|
||||
_ => Self::Other(Box::new(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PluginDataError> for VerboseError {
|
||||
fn from(value: PluginDataError) -> Self {
|
||||
let error = value
|
||||
.source()
|
||||
.expect("LoadOrderError has source")
|
||||
.downcast_ref::<esplugin::Error>()
|
||||
.expect("LoadOrderError source is an esplugin::Error");
|
||||
let error_code = error_codes::esplugin::map_error(error);
|
||||
Self::EspluginError(error_code, error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MultilingualMessageContentsError> for VerboseError {
|
||||
fn from(value: MultilingualMessageContentsError) -> Self {
|
||||
Self::Other(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RegexError> for VerboseError {
|
||||
fn from(value: RegexError) -> Self {
|
||||
Self::Other(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user