mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Add a C++ wrapper crate using CXX
This commit is contained in:
Generated
+194
@@ -29,6 +29,12 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "arraydeque"
|
||||
version = "0.5.1"
|
||||
@@ -77,12 +83,57 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
@@ -162,6 +213,65 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "050906babad73f9b32a91cecc3063ff1e2235226dd2367dd839fd6fbc941c68a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-cmd",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"foldhash",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875d58f2ac56025a775b91a424515b5adf1e68205765f2c90e6dd81e269ae004"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-cmd"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19c3062da294104183e1c34ea9887941d4d8c74f6195ce9fbb430ac4b5290ede"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codespan-reporting",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4b358173a166833ddef75fe468579f71727c789b8082d4cc77c38d08f656c59"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9531217f3b5f7728244d2b7312bc6660f6b3e4cdbc118f4f1fbce48cb401a0f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dataview"
|
||||
version = "1.0.1"
|
||||
@@ -171,6 +281,17 @@ dependencies = [
|
||||
"derive_pod",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "delegate"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "297806318ef30ad066b15792a8372858020ae3ca2e414ee6c2133b1eb9e9e945"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_pod"
|
||||
version = "0.1.2"
|
||||
@@ -283,6 +404,12 @@ version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
@@ -454,6 +581,16 @@ dependencies = [
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloot-cxx"
|
||||
version = "0.25.3"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
"delegate",
|
||||
"libloot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
@@ -464,6 +601,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
@@ -841,6 +987,12 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "saphyr"
|
||||
version = "0.0.3"
|
||||
@@ -863,6 +1015,12 @@ dependencies = [
|
||||
"hashlink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
@@ -880,6 +1038,12 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
@@ -889,6 +1053,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
@@ -914,6 +1084,15 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
@@ -1010,6 +1189,12 @@ version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@@ -1047,6 +1232,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
||||
@@ -24,3 +24,6 @@ windows = { version = "0.60.0", features = ["Win32_Storage_FileSystem"] }
|
||||
rstest = "0.25.0"
|
||||
rstest_reuse = "0.7.0"
|
||||
tempfile = "3.17.1"
|
||||
|
||||
[workspace]
|
||||
members = ["cxx"]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
/include
|
||||
@@ -0,0 +1,41 @@
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
project(ffi_tests CXX)
|
||||
|
||||
include_directories("${CMAKE_SOURCE_DIR}/../target/cxxbridge")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
include(FetchContent)
|
||||
include(GoogleTest)
|
||||
|
||||
set(BUILD_GMOCK OFF)
|
||||
set(gtest_force_shared_crt ON)
|
||||
set(INSTALL_GTEST OFF)
|
||||
# Google Test doesn't have a scoped equivalent to BUILD_SHARED_LIBS, so set the
|
||||
# global value and then set it back to the original value once Google Test has
|
||||
# been configured.
|
||||
set(BUILD_SHARED_LIBS_INITIAL ${BUILD_SHARED_LIBS})
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
FetchContent_Declare(
|
||||
GTest
|
||||
URL "https://github.com/google/googletest/archive/refs/tags/v1.15.2.tar.gz"
|
||||
URL_HASH "SHA256=7b42b4d6ed48810c5362c265a17faebe90dc2373c885e5216439d37927f02926"
|
||||
FIND_PACKAGE_ARGS)
|
||||
|
||||
FetchContent_MakeAvailable(GTest)
|
||||
|
||||
if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set (SYSTEM_LIBS pthread dl)
|
||||
endif ()
|
||||
|
||||
if (MSVC)
|
||||
set (SYSTEM_LIBS ntdll ws2_32 Userenv bcrypt)
|
||||
endif ()
|
||||
|
||||
set (FFI_LIBRARY "${CMAKE_SOURCE_DIR}/../target/debug/libloot_cxx.lib")
|
||||
|
||||
add_executable(ffi_cpp_tests "${CMAKE_SOURCE_DIR}/tests/ffi.cpp")
|
||||
target_link_libraries(ffi_cpp_tests ${FFI_LIBRARY} ${SYSTEM_LIBS} GTest::gtest_main)
|
||||
|
||||
enable_testing()
|
||||
gtest_discover_tests(ffi_cpp_tests DISCOVERY_TIMEOUT 10)
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "libloot-cxx"
|
||||
version = "0.25.3"
|
||||
edition = "2024"
|
||||
license = "GPL-3.0"
|
||||
|
||||
[dependencies]
|
||||
cxx = { version = "1.0", features = ["c++17"] }
|
||||
delegate = "0.13.2"
|
||||
libloot = { path = ".." }
|
||||
|
||||
[build-dependencies]
|
||||
cxx-build = "1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
@@ -0,0 +1,57 @@
|
||||
# libloot-rs C++ wrapper
|
||||
|
||||
This is an **imcomplete** and **experimental** wrapper around the Rust reimplementation of libloot that provides a C++ interface.
|
||||
|
||||
## Current status
|
||||
|
||||
- [x] Expose all API types, aside from `LogLevel` and error types
|
||||
- [x] Expose all getters and setters on metadata types
|
||||
- [x] Expose all other methods on `PluginMetadata`
|
||||
- [x] Expose creator functions (`new_*`) for all metadata types
|
||||
- [x] Expose `Group::DEFAULT_NAME` (using an accessor function)
|
||||
- [x] Expose `MessageContent::DEFAULT_LANGUAGE` (using an accessor function)
|
||||
- [x] Expose `select_message_content()`
|
||||
- [x] Expose `is_compatible()`
|
||||
- [x] Expose `libloot_revision()`
|
||||
- [x] Expose `libloot_version()`
|
||||
- [x] Expose `LIBLOOT_VERSION_*` constants (as unmangled extern C statics)
|
||||
- [ ] Expose `LogLevel`
|
||||
- [ ] Expose `set_logging_callback()`
|
||||
- [ ] Write tests to cover API usage patterns
|
||||
- [ ] Write a C++ layer that provides the same API as the C++ implementation of libloot
|
||||
|
||||
## Building
|
||||
|
||||
The wrapper is currently built as a static library using Cargo and cbindgen.
|
||||
|
||||
```
|
||||
cargo build --release
|
||||
cbindgen -o include/libloot.h
|
||||
```
|
||||
|
||||
cbindgen is needed to create a header that contains the C FFI exports. It needs to be v0.28.0 or higher to support the Rust 2024 edition's unsafe attributes. The header needs to be included into C++ code separately from the CXX-generated header. While CXX does support including headers, it produces the wrong include path in the C++ it generates due to this crate being a member of a workspace, so the C++ side expects the header in a subdirectory that doesn't exist.
|
||||
|
||||
There are also a small number of C++ tests that can be built and run using CMake:
|
||||
|
||||
```
|
||||
cmake -B build .
|
||||
cmake --build build --config RelWithDebInfo
|
||||
ctest --test-dir build --output-on-failure -V
|
||||
```
|
||||
|
||||
To package the build:
|
||||
|
||||
```
|
||||
cpack --config build/CPackConfig.cmake
|
||||
```
|
||||
|
||||
This repository (and so the created package) doesn't currently include any of libloot's documentation, besides the API documentation included in the Rust source code.
|
||||
|
||||
## Usage notes
|
||||
|
||||
- `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.
|
||||
@@ -0,0 +1,7 @@
|
||||
fn main() {
|
||||
cxx_build::bridge("src/lib.rs")
|
||||
.std("c++17")
|
||||
.compile("libloot-cxx");
|
||||
|
||||
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
namespace = "loot"
|
||||
@@ -0,0 +1,281 @@
|
||||
use std::{
|
||||
path::Path,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use delegate::delegate;
|
||||
use libloot::error::DatabaseLockPoisonError;
|
||||
|
||||
use crate::{
|
||||
UnsupportedEnumValueError, VerboseError,
|
||||
ffi::EdgeType,
|
||||
metadata::{Group, Message, OptionalPluginMetadata, PluginMetadata, to_vec_of_unwrapped},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct Database(Arc<RwLock<libloot::Database>>);
|
||||
|
||||
impl Database {
|
||||
pub fn new(db: Arc<RwLock<libloot::Database>>) -> Self {
|
||||
Self(db)
|
||||
}
|
||||
|
||||
pub fn load_masterlist(&self, path: &str) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.write()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.load_masterlist(Path::new(path))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn load_masterlist_with_prelude(
|
||||
&self,
|
||||
masterlist_path: &str,
|
||||
prelude_path: &str,
|
||||
) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.write()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.load_masterlist_with_prelude(Path::new(masterlist_path), Path::new(prelude_path))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn load_userlist(&self, path: &str) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.write()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.load_userlist(Path::new(path))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn write_user_metadata(
|
||||
&self,
|
||||
output_path: &str,
|
||||
overwrite: bool,
|
||||
) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.read()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.write_user_metadata(Path::new(output_path), overwrite)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn write_minimal_list(
|
||||
&self,
|
||||
output_path: &str,
|
||||
overwrite: bool,
|
||||
) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.read()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.write_minimal_list(Path::new(output_path), overwrite)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn known_bash_tags(&self) -> Result<Vec<String>, VerboseError> {
|
||||
Ok(self
|
||||
.0
|
||||
.read()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.known_bash_tags())
|
||||
}
|
||||
|
||||
pub fn general_messages(
|
||||
&self,
|
||||
evaluate_conditions: bool,
|
||||
) -> Result<Vec<Message>, VerboseError> {
|
||||
self.0
|
||||
.write()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.general_messages(evaluate_conditions)
|
||||
.map(|v| v.into_iter().map(Into::into).collect())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn groups(&self, include_user_metadata: bool) -> Result<Vec<Group>, VerboseError> {
|
||||
Ok(self
|
||||
.0
|
||||
.read()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.groups(include_user_metadata)
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect())
|
||||
}
|
||||
|
||||
// I tried returning a GroupRef<'_> here, but it borrows from the DB read guard, which the borrow checker sees as an owned value within this scope, so won't allow me to return a reference to it. This is the only place that uses a group reference, so it's probably not worth figuring out a workaround, and cloning the groups is fine.
|
||||
pub fn user_groups(&self) -> Result<Vec<Group>, VerboseError> {
|
||||
Ok(self
|
||||
.0
|
||||
.read()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.user_groups()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(Into::into)
|
||||
.collect())
|
||||
}
|
||||
|
||||
// This is ugly, but Group can't be held as a value in C++ and CXX doesn't support Vec<Box<Group>> as a parameter, so this can't take ownership of the input groups.
|
||||
pub fn set_user_groups(&self, groups: &[Box<Group>]) -> Result<(), VerboseError> {
|
||||
let groups = to_vec_of_unwrapped(groups);
|
||||
self.0
|
||||
.write()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.set_user_groups(groups);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn groups_path(
|
||||
&self,
|
||||
from_group_name: &str,
|
||||
to_group_name: &str,
|
||||
) -> Result<Vec<Vertex>, VerboseError> {
|
||||
self.0
|
||||
.read()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.groups_path(from_group_name, to_group_name)
|
||||
.map(|v| v.into_iter().map(Into::into).collect())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn plugin_metadata(
|
||||
&self,
|
||||
plugin_name: &str,
|
||||
include_user_metadata: bool,
|
||||
evaluate_conditions: bool,
|
||||
) -> Result<Box<OptionalPluginMetadata>, VerboseError> {
|
||||
self.0
|
||||
.read()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.plugin_metadata(plugin_name, include_user_metadata, evaluate_conditions)
|
||||
.map(|p| Box::new(p.map(Into::into).into()))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn plugin_user_metadata(
|
||||
&self,
|
||||
plugin_name: &str,
|
||||
evaluate_conditions: bool,
|
||||
) -> Result<Box<OptionalPluginMetadata>, VerboseError> {
|
||||
self.0
|
||||
.read()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.plugin_user_metadata(plugin_name, evaluate_conditions)
|
||||
.map(|p| Box::new(p.map(Into::into).into()))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn set_plugin_user_metadata(
|
||||
&mut self,
|
||||
plugin_metadata: Box<PluginMetadata>,
|
||||
) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.write()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.set_plugin_user_metadata(plugin_metadata.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn discard_plugin_user_metadata(&self, plugin: &str) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.write()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.discard_plugin_user_metadata(plugin);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn discard_all_user_metadata(&self) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.write()
|
||||
.map_err(|_| DatabaseLockPoisonError)?
|
||||
.discard_all_user_metadata();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct Vertex(libloot::Vertex);
|
||||
|
||||
pub fn new_vertex(name: String) -> Box<Vertex> {
|
||||
Box::new(Vertex(libloot::Vertex::new(name)))
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
// A value of 255 is used to indicate that there is no out edge.
|
||||
pub fn out_edge_type(&self) -> Result<u8, VerboseError> {
|
||||
match self.0.out_edge_type() {
|
||||
Some(e) => Ok(EdgeType::try_from(e)?.repr),
|
||||
None => Ok(u8::MAX),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_out_edge_type(&mut self, out_edge_type: EdgeType) -> Result<(), VerboseError> {
|
||||
self.0.set_out_edge_type(out_edge_type.try_into()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn boxed_clone(&self) -> Box<Self> {
|
||||
Box::new(Self(self.0.clone()))
|
||||
}
|
||||
|
||||
delegate! {
|
||||
to self.0 {
|
||||
pub fn name(&self) -> &str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<libloot::Vertex> for Vertex {
|
||||
fn from(value: libloot::Vertex) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<libloot::EdgeType> for EdgeType {
|
||||
type Error = UnsupportedEnumValueError;
|
||||
|
||||
fn try_from(value: libloot::EdgeType) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
libloot::EdgeType::Hardcoded => Ok(EdgeType::hardcoded),
|
||||
libloot::EdgeType::MasterFlag => Ok(EdgeType::masterFlag),
|
||||
libloot::EdgeType::Master => Ok(EdgeType::master),
|
||||
libloot::EdgeType::MasterlistRequirement => Ok(EdgeType::masterlistRequirement),
|
||||
libloot::EdgeType::UserRequirement => Ok(EdgeType::userRequirement),
|
||||
libloot::EdgeType::MasterlistLoadAfter => Ok(EdgeType::masterlistLoadAfter),
|
||||
libloot::EdgeType::UserLoadAfter => Ok(EdgeType::userLoadAfter),
|
||||
libloot::EdgeType::MasterlistGroup => Ok(EdgeType::masterlistGroup),
|
||||
libloot::EdgeType::UserGroup => Ok(EdgeType::userGroup),
|
||||
libloot::EdgeType::RecordOverlap => Ok(EdgeType::recordOverlap),
|
||||
libloot::EdgeType::AssetOverlap => Ok(EdgeType::assetOverlap),
|
||||
libloot::EdgeType::TieBreak => Ok(EdgeType::tieBreak),
|
||||
libloot::EdgeType::BlueprintMaster => Ok(EdgeType::blueprintMaster),
|
||||
_ => Err(UnsupportedEnumValueError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<EdgeType> for libloot::EdgeType {
|
||||
type Error = UnsupportedEnumValueError;
|
||||
|
||||
fn try_from(value: EdgeType) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
EdgeType::hardcoded => Ok(libloot::EdgeType::Hardcoded),
|
||||
EdgeType::masterFlag => Ok(libloot::EdgeType::MasterFlag),
|
||||
EdgeType::master => Ok(libloot::EdgeType::Master),
|
||||
EdgeType::masterlistRequirement => Ok(libloot::EdgeType::MasterlistRequirement),
|
||||
EdgeType::userRequirement => Ok(libloot::EdgeType::UserRequirement),
|
||||
EdgeType::masterlistLoadAfter => Ok(libloot::EdgeType::MasterlistLoadAfter),
|
||||
EdgeType::userLoadAfter => Ok(libloot::EdgeType::UserLoadAfter),
|
||||
EdgeType::masterlistGroup => Ok(libloot::EdgeType::MasterlistGroup),
|
||||
EdgeType::userGroup => Ok(libloot::EdgeType::UserGroup),
|
||||
EdgeType::recordOverlap => Ok(libloot::EdgeType::RecordOverlap),
|
||||
EdgeType::assetOverlap => Ok(libloot::EdgeType::AssetOverlap),
|
||||
EdgeType::tieBreak => Ok(libloot::EdgeType::TieBreak),
|
||||
EdgeType::blueprintMaster => Ok(libloot::EdgeType::BlueprintMaster),
|
||||
_ => Err(UnsupportedEnumValueError),
|
||||
}
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
use std::path::Path;
|
||||
|
||||
use delegate::delegate;
|
||||
|
||||
use crate::{
|
||||
PluginRef, UnsupportedEnumValueError, VerboseError, database::Database, ffi::GameType,
|
||||
plugin::OptionalPluginRef,
|
||||
};
|
||||
|
||||
impl TryFrom<libloot::GameType> for GameType {
|
||||
type Error = UnsupportedEnumValueError;
|
||||
|
||||
fn try_from(value: libloot::GameType) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
libloot::GameType::TES4 => Ok(GameType::tes4),
|
||||
libloot::GameType::TES5 => Ok(GameType::tes5),
|
||||
libloot::GameType::FO3 => Ok(GameType::fo3),
|
||||
libloot::GameType::FONV => Ok(GameType::fonv),
|
||||
libloot::GameType::FO4 => Ok(GameType::fo4),
|
||||
libloot::GameType::TES5SE => Ok(GameType::tes5se),
|
||||
libloot::GameType::FO4VR => Ok(GameType::fo4vr),
|
||||
libloot::GameType::TES5VR => Ok(GameType::tes5vr),
|
||||
libloot::GameType::TES3 => Ok(GameType::tes3),
|
||||
libloot::GameType::Starfield => Ok(GameType::starfield),
|
||||
libloot::GameType::OpenMW => Ok(GameType::openmw),
|
||||
_ => Err(UnsupportedEnumValueError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<GameType> for libloot::GameType {
|
||||
type Error = UnsupportedEnumValueError;
|
||||
|
||||
fn try_from(value: GameType) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
GameType::tes4 => Ok(libloot::GameType::TES4),
|
||||
GameType::tes5 => Ok(libloot::GameType::TES5),
|
||||
GameType::fo3 => Ok(libloot::GameType::FO3),
|
||||
GameType::fonv => Ok(libloot::GameType::FONV),
|
||||
GameType::fo4 => Ok(libloot::GameType::FO4),
|
||||
GameType::tes5se => Ok(libloot::GameType::TES5SE),
|
||||
GameType::fo4vr => Ok(libloot::GameType::FO4VR),
|
||||
GameType::tes5vr => Ok(libloot::GameType::TES5VR),
|
||||
GameType::tes3 => Ok(libloot::GameType::TES3),
|
||||
GameType::starfield => Ok(libloot::GameType::Starfield),
|
||||
GameType::openmw => Ok(libloot::GameType::OpenMW),
|
||||
_ => Err(UnsupportedEnumValueError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Game> for libloot::Game {
|
||||
fn from(value: Game) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<libloot::Game> for Game {
|
||||
fn from(value: libloot::Game) -> Self {
|
||||
Game(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NotValidUtf8;
|
||||
|
||||
impl std::fmt::Display for NotValidUtf8 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Value was not valid UTF-8")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for NotValidUtf8 {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct Game(libloot::Game);
|
||||
|
||||
// CXX doesn't support &Path so use &str instead.
|
||||
pub fn new_game(game_type: GameType, game_path: &str) -> Result<Box<Game>, VerboseError> {
|
||||
libloot::Game::new(game_type.try_into()?, Path::new(game_path))
|
||||
.map(|g| Box::new(g.into()))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn new_game_with_local_path(
|
||||
game_type: GameType,
|
||||
game_path: &str,
|
||||
game_local_path: &str,
|
||||
) -> Result<Box<Game>, VerboseError> {
|
||||
libloot::Game::with_local_path(
|
||||
game_type.try_into()?,
|
||||
Path::new(game_path),
|
||||
Path::new(game_local_path),
|
||||
)
|
||||
.map(|g| Box::new(g.into()))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn path_to_string(path: &Path) -> Result<String, VerboseError> {
|
||||
path.to_str()
|
||||
.map(|s| s.to_owned())
|
||||
.ok_or(NotValidUtf8)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn strings_to_paths<'a>(paths: &'a [&'a str]) -> Vec<&'a Path> {
|
||||
paths.iter().map(Path::new).collect()
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn game_type(&self) -> Result<GameType, VerboseError> {
|
||||
self.0.game_type().try_into().map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn additional_data_paths(&self) -> Result<Vec<String>, VerboseError> {
|
||||
self.0
|
||||
.additional_data_paths()
|
||||
.iter()
|
||||
.map(|p| path_to_string(p))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn set_additional_data_paths(
|
||||
&mut self,
|
||||
additional_data_paths: &[&str],
|
||||
) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.set_additional_data_paths(&strings_to_paths(additional_data_paths))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn database(&self) -> Box<Database> {
|
||||
Box::new(Database::new(self.0.database()))
|
||||
}
|
||||
|
||||
pub fn is_valid_plugin(&self, plugin_path: &str) -> bool {
|
||||
self.0.is_valid_plugin(Path::new(plugin_path))
|
||||
}
|
||||
|
||||
pub fn load_plugins(&mut self, plugin_paths: &[&str]) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.load_plugins(&strings_to_paths(plugin_paths))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn load_plugin_headers(&mut self, plugin_paths: &[&str]) -> Result<(), VerboseError> {
|
||||
self.0
|
||||
.load_plugin_headers(&strings_to_paths(plugin_paths))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn plugin(&self, plugin_name: &str) -> Box<OptionalPluginRef> {
|
||||
Box::new(self.0.plugin(plugin_name).into())
|
||||
}
|
||||
|
||||
pub fn loaded_plugins(&self) -> Vec<PluginRef<'_>> {
|
||||
self.0
|
||||
.loaded_plugins()
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn sort_plugins(&self, plugin_names: &[&str]) -> Result<Vec<String>, VerboseError> {
|
||||
self.0.sort_plugins(plugin_names).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn load_current_load_order_state(&mut self) -> Result<(), VerboseError> {
|
||||
self.0.load_current_load_order_state().map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn is_load_order_ambiguous(&self) -> Result<bool, VerboseError> {
|
||||
self.0.is_load_order_ambiguous().map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn active_plugins_file_path(&self) -> Result<String, VerboseError> {
|
||||
path_to_string(self.0.active_plugins_file_path())
|
||||
}
|
||||
|
||||
pub fn load_order(&self) -> Vec<String> {
|
||||
self.0.load_order().iter().map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
pub fn set_load_order(&mut self, load_order: &[&str]) -> Result<(), VerboseError> {
|
||||
self.0.set_load_order(load_order).map_err(Into::into)
|
||||
}
|
||||
|
||||
delegate! {
|
||||
to self.0 {
|
||||
pub fn clear_loaded_plugins(&mut self);
|
||||
|
||||
pub fn is_plugin_active(&self, plugin_name: &str) -> bool;
|
||||
}
|
||||
}
|
||||
}
|
||||
+569
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
use delegate::delegate;
|
||||
|
||||
use crate::{EmptyOptionalError, OptionalRef, VerboseError};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct PluginRef<'a>(&'a libloot::Plugin);
|
||||
|
||||
impl<'a> PluginRef<'a> {
|
||||
pub fn new(plugin: &'a libloot::Plugin) -> Self {
|
||||
Self(plugin)
|
||||
}
|
||||
|
||||
pub fn header_version(&self) -> f32 {
|
||||
self.0.header_version().unwrap_or(f32::NAN)
|
||||
}
|
||||
|
||||
pub fn version(&self) -> &str {
|
||||
self.0.version().unwrap_or("")
|
||||
}
|
||||
|
||||
pub fn masters(&self) -> Result<Vec<String>, VerboseError> {
|
||||
self.0.masters().map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn crc(&self) -> i64 {
|
||||
self.0.crc().map(Into::into).unwrap_or(-1)
|
||||
}
|
||||
|
||||
pub fn is_valid_as_light_plugin(&self) -> Result<bool, VerboseError> {
|
||||
self.0.is_valid_as_light_plugin().map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn is_valid_as_medium_plugin(&self) -> Result<bool, VerboseError> {
|
||||
self.0.is_valid_as_medium_plugin().map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn is_valid_as_update_plugin(&self) -> Result<bool, VerboseError> {
|
||||
self.0.is_valid_as_update_plugin().map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn do_records_overlap(&self, plugin: &Self) -> Result<bool, VerboseError> {
|
||||
self.0.do_records_overlap(plugin.0).map_err(Into::into)
|
||||
}
|
||||
|
||||
delegate! {
|
||||
to self.0 {
|
||||
pub fn name(&self) -> &str;
|
||||
|
||||
pub fn bash_tags(&self) -> &[String];
|
||||
|
||||
pub fn is_master(&self) -> bool;
|
||||
|
||||
pub fn is_light_plugin(&self) -> bool;
|
||||
|
||||
pub fn is_medium_plugin(&self) -> bool;
|
||||
|
||||
pub fn is_update_plugin(&self) -> bool;
|
||||
|
||||
pub fn is_blueprint_plugin(&self) -> bool;
|
||||
|
||||
pub fn is_empty(&self) -> bool;
|
||||
|
||||
pub fn loads_archive(&self) -> bool;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a libloot::Plugin> for PluginRef<'a> {
|
||||
fn from(value: &'a libloot::Plugin) -> Self {
|
||||
PluginRef(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub type OptionalPluginRef = OptionalRef<libloot::Plugin>;
|
||||
|
||||
impl OptionalRef<libloot::Plugin> {
|
||||
/// # Safety
|
||||
///
|
||||
/// This is safe as long as the pointer in the OptionalRef is still valid.
|
||||
pub unsafe fn as_ref(&self) -> Result<Box<PluginRef<'_>>, EmptyOptionalError> {
|
||||
if self.0.is_null() {
|
||||
Err(EmptyOptionalError)
|
||||
} else {
|
||||
unsafe { Ok(Box::new(PluginRef::new(&*self.0))) }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user