mirror of
https://github.com/loot/loot-condition-interpreter.git
synced 2026-07-27 14:16:09 -07:00
Add FFI wrapper crate
This commit is contained in:
@@ -4,3 +4,6 @@
|
|||||||
Cargo.lock
|
Cargo.lock
|
||||||
/testing-plugins
|
/testing-plugins
|
||||||
/loot_api*
|
/loot_api*
|
||||||
|
/ffi/include/
|
||||||
|
/ffi/build/
|
||||||
|
.vscode
|
||||||
|
|||||||
+11
-2
@@ -23,5 +23,14 @@ before_script:
|
|||||||
- 7z x loot_api-0.13.8-0-g47797cc_dev-win64.7z
|
- 7z x loot_api-0.13.8-0-g47797cc_dev-win64.7z
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- cargo fmt -- --check
|
- cargo fmt --all -- --check
|
||||||
- cargo test
|
- cargo test --all --all-features
|
||||||
|
|
||||||
|
# Need to rebuild the FFI wrapper so that its binary is given a filename
|
||||||
|
# without a hash.
|
||||||
|
- cargo build --manifest-path ffi/Cargo.toml --features ffi-headers
|
||||||
|
- mkdir ffi/build
|
||||||
|
- cd ffi/build
|
||||||
|
- cmake ..
|
||||||
|
- make
|
||||||
|
- make test
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
name = "loot-condition-interpreter"
|
name = "loot-condition-interpreter"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Oliver Hamlet <oliver.hamlet@gmail.com>"]
|
authors = ["Oliver Hamlet <oliver.hamlet@gmail.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crc = "1.0.0"
|
crc = "1.0.0"
|
||||||
@@ -17,3 +18,6 @@ tempfile = "3.0.0"
|
|||||||
[[bench]]
|
[[bench]]
|
||||||
name = "eval"
|
name = "eval"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["ffi"]
|
||||||
|
|||||||
+11
-2
@@ -29,5 +29,14 @@ install:
|
|||||||
build: false
|
build: false
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cargo fmt -- --check
|
- cargo fmt --all -- --check
|
||||||
- cargo test
|
- cargo test --all --all-features
|
||||||
|
|
||||||
|
# Need to rebuild the FFI wrapper so that its binary is given a filename
|
||||||
|
# without a hash.
|
||||||
|
- cargo build --manifest-path ffi/Cargo.toml --features ffi-headers
|
||||||
|
- ps: mkdir ffi/build
|
||||||
|
- cd ffi/build
|
||||||
|
- cmake .. -G "Visual Studio 15 2017 Win64"
|
||||||
|
- cmake --build .
|
||||||
|
- ctest
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.8)
|
||||||
|
project(ffi_tests CXX)
|
||||||
|
|
||||||
|
include_directories("${CMAKE_SOURCE_DIR}/include")
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
|
||||||
|
if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
set (SYSTEM_LIBS pthread dl)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
set (SYSTEM_LIBS ws2_32 Userenv)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set (FFI_LIBRARY "${CMAKE_SOURCE_DIR}/../target/debug/${CMAKE_STATIC_LIBRARY_PREFIX}loot_condition_interpreter_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||||
|
|
||||||
|
add_executable(ffi_cpp_tests "${CMAKE_SOURCE_DIR}/tests/ffi.cpp")
|
||||||
|
target_link_libraries(ffi_cpp_tests ${FFI_LIBRARY} ${SYSTEM_LIBS})
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
add_test(ffi_cpp_tests ffi_cpp_tests)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "loot-condition-interpreter-ffi"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Oliver Hamlet <oliver.hamlet@gmail.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
loot-condition-interpreter = { path = ".." }
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "loot_condition_interpreter_ffi"
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
ffi-headers = ["cbindgen"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cbindgen = { version = "0.6", optional = true }
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#[cfg(feature = "ffi-headers")]
|
||||||
|
mod ffi_headers {
|
||||||
|
extern crate cbindgen;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use self::cbindgen::Builder;
|
||||||
|
use self::cbindgen::Language;
|
||||||
|
|
||||||
|
pub fn generate_headers() {
|
||||||
|
let crate_dir = env::var("CARGO_MANIFEST_DIR")
|
||||||
|
.expect("could not get value of CARGO_MANIFEST_DIR env var");
|
||||||
|
|
||||||
|
fs::create_dir_all("include").expect("could not create include directory");
|
||||||
|
|
||||||
|
Builder::new()
|
||||||
|
.with_crate(&crate_dir)
|
||||||
|
.with_language(Language::Cxx)
|
||||||
|
.with_std_types(false)
|
||||||
|
.generate()
|
||||||
|
.expect("could not generate C++ header file")
|
||||||
|
.write_to_file("include/loot_condition_interpreter.hpp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
#[cfg(feature = "ffi-headers")]
|
||||||
|
ffi_headers::generate_headers();
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
use libc::c_int;
|
||||||
|
use loot_condition_interpreter::GameType;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_OK: c_int = 0;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_RESULT_FALSE: c_int = 0;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_RESULT_TRUE: c_int = 1;
|
||||||
|
|
||||||
|
/// Invalid arguments were given for the function.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_ERROR_INVALID_ARGS: c_int = -1;
|
||||||
|
|
||||||
|
/// Something went wrong while parsing the condition expression.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_ERROR_PARSING_ERROR: c_int = -2;
|
||||||
|
|
||||||
|
/// Something went wrong while getting the version of an executable.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_ERROR_PE_PARSING_ERROR: c_int = -3;
|
||||||
|
|
||||||
|
/// Some sort of I/O error occurred.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_ERROR_IO_ERROR: c_int = -4;
|
||||||
|
|
||||||
|
/// Something panicked.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_ERROR_PANICKED: c_int = -5;
|
||||||
|
|
||||||
|
/// A thread lock was poisoned.
|
||||||
|
#[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.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_ERROR_TEXT_ENCODE_FAIL: c_int = -7;
|
||||||
|
|
||||||
|
/// Game code for The Elder Scrolls IV: Oblivion.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_GAME_TES4: c_int = GameType::Tes4 as c_int;
|
||||||
|
|
||||||
|
/// Game code for The Elder Scrolls V: Skyrim.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_GAME_TES5: c_int = GameType::Tes5 as c_int;
|
||||||
|
|
||||||
|
/// Game code for Fallout 3.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_GAME_FO3: c_int = GameType::Fo3 as c_int;
|
||||||
|
|
||||||
|
/// Game code for Fallout: New Vegas.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_GAME_FNV: c_int = GameType::Fonv as c_int;
|
||||||
|
|
||||||
|
/// Game code for Fallout 4.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_GAME_FO4: c_int = GameType::Fo4 as c_int;
|
||||||
|
|
||||||
|
/// Game code for The Elder Scrolls V: Skyrim Special Edition.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_GAME_TES5SE: c_int = GameType::Tes5se as c_int;
|
||||||
|
|
||||||
|
/// Game code for The Elder Scrolls V: Skyrim VR.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_GAME_TES5VR: c_int = GameType::Tes5vr as c_int;
|
||||||
|
|
||||||
|
/// Game code for Fallout 4 VR.
|
||||||
|
#[no_mangle]
|
||||||
|
pub static LCI_GAME_FO4VR: c_int = GameType::Fo4vr as c_int;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn game_constants_should_have_expected_integer_values() {
|
||||||
|
assert_eq!(0, LCI_GAME_TES4);
|
||||||
|
assert_eq!(1, LCI_GAME_TES5);
|
||||||
|
assert_eq!(2, LCI_GAME_TES5SE);
|
||||||
|
assert_eq!(3, LCI_GAME_TES5VR);
|
||||||
|
assert_eq!(4, LCI_GAME_FO3);
|
||||||
|
assert_eq!(5, LCI_GAME_FNV);
|
||||||
|
assert_eq!(6, LCI_GAME_FO4);
|
||||||
|
assert_eq!(7, LCI_GAME_FO4VR);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
use libc::{c_char, c_int, size_t};
|
||||||
|
use loot_condition_interpreter::{Error, GameType};
|
||||||
|
|
||||||
|
use super::ERROR_MESSAGE;
|
||||||
|
use constants::*;
|
||||||
|
use state::{plugin_crc, plugin_version};
|
||||||
|
|
||||||
|
pub fn error(code: c_int, message: &str) -> c_int {
|
||||||
|
ERROR_MESSAGE.with(|f| {
|
||||||
|
*f.borrow_mut() = unsafe { CString::from_vec_unchecked(message.as_bytes().to_vec()) }
|
||||||
|
});
|
||||||
|
code
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_error(err: Error) -> c_int {
|
||||||
|
let code = map_error(&err);
|
||||||
|
error(code, &format!("{}", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_error(err: &Error) -> c_int {
|
||||||
|
match err {
|
||||||
|
Error::ParsingIncomplete => LCI_ERROR_PARSING_ERROR,
|
||||||
|
Error::GenericParsingError(_, _) => LCI_ERROR_PARSING_ERROR,
|
||||||
|
Error::CustomParsingError(_, _) => LCI_ERROR_PARSING_ERROR,
|
||||||
|
Error::PeParsingError(_, _) => LCI_ERROR_PE_PARSING_ERROR,
|
||||||
|
Error::IoError(_, _) => LCI_ERROR_IO_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_game_type(game_type: c_int) -> Result<GameType, c_int> {
|
||||||
|
match game_type {
|
||||||
|
x if x == LCI_GAME_TES4 => Ok(GameType::Tes4),
|
||||||
|
x if x == LCI_GAME_TES5 => Ok(GameType::Tes5),
|
||||||
|
x if x == LCI_GAME_TES5SE => Ok(GameType::Tes5se),
|
||||||
|
x if x == LCI_GAME_TES5VR => Ok(GameType::Tes5vr),
|
||||||
|
x if x == LCI_GAME_FO3 => Ok(GameType::Fo3),
|
||||||
|
x if x == LCI_GAME_FNV => Ok(GameType::Fonv),
|
||||||
|
x if x == LCI_GAME_FO4 => Ok(GameType::Fo4),
|
||||||
|
x if x == LCI_GAME_FO4VR => Ok(GameType::Fo4vr),
|
||||||
|
_ => Err(LCI_ERROR_INVALID_ARGS),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn to_str<'a>(c_string: *const c_char) -> Result<&'a str, c_int> {
|
||||||
|
if c_string.is_null() {
|
||||||
|
Err(error(LCI_ERROR_INVALID_ARGS, "Null pointer passed"))
|
||||||
|
} else {
|
||||||
|
CStr::from_ptr(c_string)
|
||||||
|
.to_str()
|
||||||
|
.map_err(|_| error(LCI_ERROR_INVALID_ARGS, "Non-UTF-8 string passed"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn to_vec<U, V, F>(array: *const U, array_size: size_t, mapper: F) -> Result<Vec<V>, i32>
|
||||||
|
where
|
||||||
|
F: Fn(&U) -> Result<V, i32>,
|
||||||
|
{
|
||||||
|
if array.is_null() || array_size == 0 {
|
||||||
|
Ok(Vec::new())
|
||||||
|
} else {
|
||||||
|
slice::from_raw_parts(array, array_size)
|
||||||
|
.iter()
|
||||||
|
.map(|c| mapper(c))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn to_str_vec<'a>(
|
||||||
|
array: *const *const c_char,
|
||||||
|
array_size: size_t,
|
||||||
|
) -> Result<Vec<&'a str>, i32> {
|
||||||
|
to_vec(array, array_size, |c| to_str(*c))
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn map_plugin_version(c_object: &plugin_version) -> Result<(String, String), i32> {
|
||||||
|
to_str(c_object.plugin_name)
|
||||||
|
.and_then(|n| to_str(c_object.version).map(|v| (n.into(), v.into())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn map_plugin_versions(
|
||||||
|
plugin_versions: *const plugin_version,
|
||||||
|
num_plugins: size_t,
|
||||||
|
) -> Result<Vec<(String, String)>, i32> {
|
||||||
|
to_vec(plugin_versions, num_plugins, |v| map_plugin_version(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn map_plugin_crcs(
|
||||||
|
plugin_crcs: *const plugin_crc,
|
||||||
|
num_entries: size_t,
|
||||||
|
) -> Result<Vec<(String, u32)>, i32> {
|
||||||
|
to_vec(plugin_crcs, num_entries, |v| {
|
||||||
|
to_str(v.plugin_name).map(|s| (s.into(), v.crc))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
extern crate libc;
|
||||||
|
extern crate loot_condition_interpreter;
|
||||||
|
|
||||||
|
mod constants;
|
||||||
|
mod helpers;
|
||||||
|
mod state;
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::panic::catch_unwind;
|
||||||
|
use std::ptr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use libc::{c_char, c_int};
|
||||||
|
use loot_condition_interpreter::*;
|
||||||
|
|
||||||
|
pub use constants::*;
|
||||||
|
use helpers::{error, handle_error, to_str};
|
||||||
|
pub use state::*;
|
||||||
|
|
||||||
|
thread_local!(static ERROR_MESSAGE: RefCell<CString> = RefCell::default());
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lci_condition_parse(condition: *const c_char) -> c_int {
|
||||||
|
catch_unwind(|| {
|
||||||
|
if condition.is_null() {
|
||||||
|
error(LCI_ERROR_INVALID_ARGS, "Null pointer passed")
|
||||||
|
} else {
|
||||||
|
let expression = match to_str(condition) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = Expression::from_str(expression) {
|
||||||
|
handle_error(e)
|
||||||
|
} else {
|
||||||
|
LCI_OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).unwrap_or(LCI_ERROR_PANICKED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lci_condition_eval(
|
||||||
|
condition: *const c_char,
|
||||||
|
state: *mut lci_state,
|
||||||
|
) -> c_int {
|
||||||
|
catch_unwind(|| {
|
||||||
|
if condition.is_null() || state.is_null() {
|
||||||
|
error(LCI_ERROR_INVALID_ARGS, "Null pointer passed")
|
||||||
|
} else {
|
||||||
|
let expression = match to_str(condition) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
let expression = match Expression::from_str(expression) {
|
||||||
|
Err(e) => return handle_error(e),
|
||||||
|
Ok(x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = match (*state).0.read() {
|
||||||
|
Err(e) => return error(LCI_ERROR_POISONED_THREAD_LOCK, e.description()),
|
||||||
|
Ok(s) => s,
|
||||||
|
};
|
||||||
|
|
||||||
|
match expression.eval(&state) {
|
||||||
|
Ok(true) => LCI_RESULT_TRUE,
|
||||||
|
Ok(false) => LCI_RESULT_FALSE,
|
||||||
|
Err(e) => handle_error(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).unwrap_or(LCI_ERROR_PANICKED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lci_get_error_message(message: *mut *const c_char) -> c_int {
|
||||||
|
catch_unwind(|| {
|
||||||
|
if message.is_null() {
|
||||||
|
error(LCI_ERROR_INVALID_ARGS, "Null pointer passed")
|
||||||
|
} else {
|
||||||
|
ERROR_MESSAGE.with(|f| {
|
||||||
|
if f.borrow().as_bytes().is_empty() {
|
||||||
|
*message = ptr::null();
|
||||||
|
} else {
|
||||||
|
*message = f.borrow().as_ptr() as *const i8;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
LCI_OK
|
||||||
|
}
|
||||||
|
}).unwrap_or(LCI_ERROR_PANICKED)
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::panic::catch_unwind;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
use libc::{c_char, c_int, size_t, uint32_t};
|
||||||
|
use loot_condition_interpreter::State;
|
||||||
|
|
||||||
|
use constants::*;
|
||||||
|
use helpers::{error, map_game_type, map_plugin_crcs, map_plugin_versions, to_str, to_str_vec};
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[no_mangle]
|
||||||
|
pub struct lci_state(pub RwLock<State>);
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[no_mangle]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct plugin_version {
|
||||||
|
pub plugin_name: *const c_char,
|
||||||
|
pub version: *const c_char,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
#[no_mangle]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct plugin_crc {
|
||||||
|
pub plugin_name: *const c_char,
|
||||||
|
pub crc: uint32_t,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lci_state_create(
|
||||||
|
state: *mut *mut lci_state,
|
||||||
|
game_type: c_int,
|
||||||
|
data_path: *const c_char,
|
||||||
|
loot_path: *const c_char,
|
||||||
|
) -> c_int {
|
||||||
|
catch_unwind(|| {
|
||||||
|
if state.is_null() || data_path.is_null() || loot_path.is_null() {
|
||||||
|
error(LCI_ERROR_INVALID_ARGS, "Null pointer passed")
|
||||||
|
} else {
|
||||||
|
let game_type = match map_game_type(game_type) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(x) => return error(x, "Invalid game specified"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let data_path = match to_str(data_path) {
|
||||||
|
Ok(x) => PathBuf::from(x),
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
let loot_path = match to_str(loot_path) {
|
||||||
|
Ok(x) => PathBuf::from(x),
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
*state = Box::into_raw(Box::new(lci_state(RwLock::new(State::new(
|
||||||
|
game_type, data_path, loot_path,
|
||||||
|
)))));
|
||||||
|
|
||||||
|
LCI_OK
|
||||||
|
}
|
||||||
|
}).unwrap_or(LCI_ERROR_PANICKED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lci_state_destroy(state: *mut lci_state) {
|
||||||
|
if !state.is_null() {
|
||||||
|
Box::from_raw(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lci_state_set_active_plugins(
|
||||||
|
state: *mut lci_state,
|
||||||
|
plugin_names: *const *const c_char,
|
||||||
|
num_plugins: size_t,
|
||||||
|
) -> c_int {
|
||||||
|
catch_unwind(|| {
|
||||||
|
if state.is_null() {
|
||||||
|
error(LCI_ERROR_INVALID_ARGS, "Null state pointer passed")
|
||||||
|
} else if plugin_names.is_null() && num_plugins != 0 {
|
||||||
|
error(
|
||||||
|
LCI_ERROR_INVALID_ARGS,
|
||||||
|
"Null plugin_names pointer passed but num_plugins is non-zero",
|
||||||
|
)
|
||||||
|
} else if !plugin_names.is_null() && num_plugins == 0 {
|
||||||
|
error(
|
||||||
|
LCI_ERROR_INVALID_ARGS,
|
||||||
|
"Non-null plugin_names pointer passed but num_plugins is zero",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let plugins: Vec<&str> = match to_str_vec(plugin_names, num_plugins) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut state = match (*state).0.write() {
|
||||||
|
Err(e) => return error(LCI_ERROR_POISONED_THREAD_LOCK, e.description()),
|
||||||
|
Ok(h) => h,
|
||||||
|
};
|
||||||
|
|
||||||
|
state.set_active_plugins(&plugins);
|
||||||
|
|
||||||
|
LCI_OK
|
||||||
|
}
|
||||||
|
}).unwrap_or(LCI_ERROR_PANICKED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lci_state_set_plugin_versions(
|
||||||
|
state: *mut lci_state,
|
||||||
|
plugin_versions: *const plugin_version,
|
||||||
|
num_plugins: size_t,
|
||||||
|
) -> c_int {
|
||||||
|
catch_unwind(|| {
|
||||||
|
if state.is_null() {
|
||||||
|
error(LCI_ERROR_INVALID_ARGS, "Null state pointer passed")
|
||||||
|
} else if plugin_versions.is_null() && num_plugins != 0 {
|
||||||
|
error(
|
||||||
|
LCI_ERROR_INVALID_ARGS,
|
||||||
|
"Null plugin_versions pointer passed but num_plugins is non-zero",
|
||||||
|
)
|
||||||
|
} else if !plugin_versions.is_null() && num_plugins == 0 {
|
||||||
|
error(
|
||||||
|
LCI_ERROR_INVALID_ARGS,
|
||||||
|
"Non-null plugin_versions pointer passed but num_plugins is zero",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let plugin_versions = match map_plugin_versions(plugin_versions, num_plugins) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut state = match (*state).0.write() {
|
||||||
|
Err(e) => return error(LCI_ERROR_POISONED_THREAD_LOCK, e.description()),
|
||||||
|
Ok(h) => h,
|
||||||
|
};
|
||||||
|
|
||||||
|
state.set_plugin_versions(&plugin_versions);
|
||||||
|
|
||||||
|
LCI_OK
|
||||||
|
}
|
||||||
|
}).unwrap_or(LCI_ERROR_PANICKED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lci_state_set_crc_cache(
|
||||||
|
state: *mut lci_state,
|
||||||
|
entries: *const plugin_crc,
|
||||||
|
num_entries: size_t,
|
||||||
|
) -> c_int {
|
||||||
|
catch_unwind(|| {
|
||||||
|
if state.is_null() {
|
||||||
|
error(LCI_ERROR_INVALID_ARGS, "Null state pointer passed")
|
||||||
|
} else if entries.is_null() && num_entries != 0 {
|
||||||
|
error(
|
||||||
|
LCI_ERROR_INVALID_ARGS,
|
||||||
|
"Null entries pointer passed but num_entries is non-zero",
|
||||||
|
)
|
||||||
|
} else if !entries.is_null() && num_entries == 0 {
|
||||||
|
error(
|
||||||
|
LCI_ERROR_INVALID_ARGS,
|
||||||
|
"Non-null entries pointer passed but num_entries is zero",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let plugin_crcs = match map_plugin_crcs(entries, num_entries) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
match (*state).0.write() {
|
||||||
|
Err(e) => error(LCI_ERROR_POISONED_THREAD_LOCK, e.description()),
|
||||||
|
Ok(mut s) => match s.set_cached_crcs(&plugin_crcs) {
|
||||||
|
Err(e) => error(LCI_ERROR_POISONED_THREAD_LOCK, e.description()),
|
||||||
|
Ok(_) => LCI_OK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).unwrap_or(LCI_ERROR_PANICKED)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn lci_state_clear_condition_cache(state: *mut lci_state) -> c_int {
|
||||||
|
catch_unwind(|| {
|
||||||
|
if state.is_null() {
|
||||||
|
error(LCI_ERROR_INVALID_ARGS, "Null state pointer passed")
|
||||||
|
} else {
|
||||||
|
match (*state).0.write() {
|
||||||
|
Err(e) => error(LCI_ERROR_POISONED_THREAD_LOCK, e.description()),
|
||||||
|
Ok(mut s) => match s.clear_condition_cache() {
|
||||||
|
Err(e) => error(LCI_ERROR_POISONED_THREAD_LOCK, e.description()),
|
||||||
|
Ok(_) => LCI_OK,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).unwrap_or(LCI_ERROR_PANICKED)
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdbool>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "loot_condition_interpreter.hpp"
|
||||||
|
|
||||||
|
void test_game_id_values() {
|
||||||
|
printf("testing LCI_GAME_* values...\n");
|
||||||
|
assert(LCI_GAME_TES4 == 0);
|
||||||
|
assert(LCI_GAME_TES5 == 1);
|
||||||
|
assert(LCI_GAME_TES5SE == 2);
|
||||||
|
assert(LCI_GAME_TES5VR == 3);
|
||||||
|
assert(LCI_GAME_FO3 == 4);
|
||||||
|
assert(LCI_GAME_FNV == 5);
|
||||||
|
assert(LCI_GAME_FO4 == 6);
|
||||||
|
assert(LCI_GAME_FO4VR == 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_lci_condition_parse() {
|
||||||
|
printf("testing lci_condition_parse()...\n");
|
||||||
|
|
||||||
|
int return_code = lci_condition_parse("file(\"Blank.esm\")");
|
||||||
|
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_lci_get_error_message() {
|
||||||
|
printf("testing lci_get_error_message()...\n");
|
||||||
|
|
||||||
|
const char * message = nullptr;
|
||||||
|
int return_code = lci_get_error_message(&message);
|
||||||
|
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
assert(message == nullptr);
|
||||||
|
|
||||||
|
return_code = lci_condition_parse("file(\"Blank.");
|
||||||
|
|
||||||
|
assert(return_code == LCI_ERROR_PARSING_ERROR);
|
||||||
|
|
||||||
|
return_code = lci_get_error_message(&message);
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
assert(message != nullptr);
|
||||||
|
assert(strcmp(message, "An error was encountered in the parser \"SeparatedList\" while parsing the expression \"file(\\\"Blank.\"") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_lci_state_create() {
|
||||||
|
printf("testing lci_state_create()...\n");
|
||||||
|
|
||||||
|
lci_state * state = nullptr;
|
||||||
|
int return_code = lci_state_create(&state, LCI_GAME_TES4, ".", ".");
|
||||||
|
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
assert(state != nullptr);
|
||||||
|
|
||||||
|
lci_state_destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_lci_condition_eval() {
|
||||||
|
printf("testing lci_condition_eval()...\n");
|
||||||
|
|
||||||
|
lci_state * state = nullptr;
|
||||||
|
int return_code = lci_state_create(&state, LCI_GAME_TES4, "../../testing-plugins/Oblivion/Data", ".");
|
||||||
|
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
assert(state != nullptr);
|
||||||
|
|
||||||
|
return_code = lci_condition_eval("file(\"Blank.esm\")", state);
|
||||||
|
|
||||||
|
assert(return_code == LCI_RESULT_TRUE);
|
||||||
|
|
||||||
|
return_code = lci_condition_eval("file(\"missing.esm\")", state);
|
||||||
|
|
||||||
|
assert(return_code == LCI_RESULT_FALSE);
|
||||||
|
|
||||||
|
lci_state_destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_lci_state_set_active_plugins() {
|
||||||
|
printf("testing lci_state_set_active_plugins()...\n");
|
||||||
|
|
||||||
|
lci_state * state = nullptr;
|
||||||
|
int return_code = lci_state_create(&state, LCI_GAME_TES4, "../../testing-plugins/Oblivion/Data", ".");
|
||||||
|
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
assert(state != nullptr);
|
||||||
|
|
||||||
|
char const * plugins[] = { "Blank.esm" };
|
||||||
|
|
||||||
|
return_code = lci_state_set_active_plugins(state, plugins, 0);
|
||||||
|
assert(return_code != LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_state_set_active_plugins(state, nullptr, 1);
|
||||||
|
assert(return_code != LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_state_set_active_plugins(state, plugins, 1);
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_condition_eval("active(\"Blank.esm\")", state);
|
||||||
|
assert(return_code == LCI_RESULT_TRUE);
|
||||||
|
|
||||||
|
return_code = lci_state_set_active_plugins(state, nullptr, 0);
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_condition_eval("active(\"Blank.esm\")", state);
|
||||||
|
assert(return_code == LCI_RESULT_FALSE);
|
||||||
|
|
||||||
|
lci_state_destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_lci_state_set_plugin_versions() {
|
||||||
|
printf("testing lci_state_set_plugin_versions()...\n");
|
||||||
|
|
||||||
|
lci_state * state = nullptr;
|
||||||
|
int return_code = lci_state_create(&state, LCI_GAME_TES4, "../../testing-plugins/Oblivion/Data", ".");
|
||||||
|
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
assert(state != nullptr);
|
||||||
|
|
||||||
|
plugin_version plugins[] = { {"Blank.esm", "5"} };
|
||||||
|
|
||||||
|
return_code = lci_state_set_plugin_versions(state, plugins, 0);
|
||||||
|
assert(return_code != LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_state_set_plugin_versions(state, nullptr, 1);
|
||||||
|
assert(return_code != LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_state_set_plugin_versions(state, plugins, 1);
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_condition_eval("version(\"Blank.esm\", \"5\", ==)", state);
|
||||||
|
assert(return_code == LCI_RESULT_TRUE);
|
||||||
|
|
||||||
|
return_code = lci_state_set_plugin_versions(state, nullptr, 0);
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_state_clear_condition_cache(state);
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_condition_eval("version(\"Blank.esm\", \"5\", ==)", state);
|
||||||
|
assert(return_code == LCI_RESULT_FALSE);
|
||||||
|
|
||||||
|
lci_state_destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_lci_state_set_crc_cache() {
|
||||||
|
printf("testing lci_state_set_crc_cache()...\n");
|
||||||
|
|
||||||
|
lci_state * state = nullptr;
|
||||||
|
int return_code = lci_state_create(&state, LCI_GAME_TES4, "../../testing-plugins/Oblivion/Data", ".");
|
||||||
|
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
assert(state != nullptr);
|
||||||
|
|
||||||
|
plugin_crc plugin_crcs[] = { {"Blank.esm", 0xDEADBEEF} };
|
||||||
|
|
||||||
|
return_code = lci_state_set_crc_cache(state, plugin_crcs, 0);
|
||||||
|
assert(return_code != LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_state_set_crc_cache(state, nullptr, 1);
|
||||||
|
assert(return_code != LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_state_set_crc_cache(state, plugin_crcs, 1);
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_condition_eval("checksum(\"Blank.esm\", DEADBEEF)", state);
|
||||||
|
assert(return_code == LCI_RESULT_TRUE);
|
||||||
|
|
||||||
|
return_code = lci_state_set_crc_cache(state, nullptr, 0);
|
||||||
|
assert(return_code == LCI_OK);
|
||||||
|
|
||||||
|
return_code = lci_condition_eval("checksum(\"Blank.esm\", DEADBEEF)", state);
|
||||||
|
assert(return_code == LCI_RESULT_FALSE);
|
||||||
|
|
||||||
|
lci_state_destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
test_game_id_values();
|
||||||
|
|
||||||
|
test_lci_condition_parse();
|
||||||
|
test_lci_get_error_message();
|
||||||
|
|
||||||
|
test_lci_state_create();
|
||||||
|
test_lci_condition_eval();
|
||||||
|
test_lci_state_set_active_plugins();
|
||||||
|
test_lci_state_set_plugin_versions();
|
||||||
|
test_lci_state_set_crc_cache();
|
||||||
|
|
||||||
|
printf("SUCCESS\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+31
-6
@@ -15,9 +15,10 @@ mod version;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::ops::DerefMut;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::RwLock;
|
use std::sync::{PoisonError, RwLock, RwLockWriteGuard};
|
||||||
|
|
||||||
use nom::types::CompleteStr;
|
use nom::types::CompleteStr;
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
@@ -94,19 +95,43 @@ impl State {
|
|||||||
mut self,
|
mut self,
|
||||||
plugin_versions: &[(T, V)],
|
plugin_versions: &[(T, V)],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.plugin_versions = plugin_versions
|
self.set_plugin_versions(plugin_versions);
|
||||||
.iter()
|
|
||||||
.map(|(p, v)| (p.as_ref().to_lowercase(), v.to_string()))
|
|
||||||
.collect();
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_active_plugins<T: AsRef<str>>(mut self, active_plugins: &[T]) -> Self {
|
pub fn with_active_plugins<T: AsRef<str>>(mut self, active_plugins: &[T]) -> Self {
|
||||||
|
self.set_active_plugins(active_plugins);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_active_plugins<T: AsRef<str>>(&mut self, active_plugins: &[T]) {
|
||||||
self.active_plugins = active_plugins
|
self.active_plugins = active_plugins
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| s.as_ref().to_lowercase())
|
.map(|s| s.as_ref().to_lowercase())
|
||||||
.collect();
|
.collect();
|
||||||
self
|
}
|
||||||
|
|
||||||
|
pub fn set_plugin_versions<T: AsRef<str>, V: ToString>(&mut self, plugin_versions: &[(T, V)]) {
|
||||||
|
self.plugin_versions = plugin_versions
|
||||||
|
.iter()
|
||||||
|
.map(|(p, v)| (p.as_ref().to_lowercase(), v.to_string()))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cached_crcs<T: AsRef<str>>(
|
||||||
|
&mut self,
|
||||||
|
plugin_crcs: &[(T, u32)],
|
||||||
|
) -> Result<(), PoisonError<RwLockWriteGuard<HashMap<String, u32>>>> {
|
||||||
|
let mut writer = self.crc_cache.write()?;
|
||||||
|
|
||||||
|
writer.deref_mut().clear();
|
||||||
|
writer.deref_mut().extend(
|
||||||
|
plugin_crcs
|
||||||
|
.iter()
|
||||||
|
.map(|(p, v)| (p.as_ref().to_lowercase(), *v)),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_condition_cache(
|
pub fn clear_condition_cache(
|
||||||
|
|||||||
Reference in New Issue
Block a user