#![allow(clippy::not_unsafe_ptr_arg_deref)]
use crate::*;
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_uint};
#[repr(C)]
pub struct CItemAttribute {
pub name: *mut c_char,
pub initial_value: f64,
pub min: f64,
pub max: f64,
pub required: u8,
pub scaling_factor: f64,
pub chance: f64,
}
impl CItemAttribute {
fn from_rust(attr: &ItemAttribute) -> Self {
CItemAttribute {
name: CString::new(attr.name.clone())
.ok()
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
initial_value: attr.initial_value,
min: attr.min,
max: attr.max,
required: if attr.required { 1 } else { 0 },
scaling_factor: attr.scaling_factor,
chance: attr.chance,
}
}
fn free(&mut self) {
if !self.name.is_null() {
unsafe {
let _ = CString::from_raw(self.name);
}
self.name = std::ptr::null_mut();
}
}
}
#[repr(C)]
pub struct CAffix {
pub name: *mut c_char,
pub attributes: *mut CItemAttribute,
pub attributes_count: c_uint,
}
impl CAffix {
fn from_rust(affix: &Affix) -> Self {
let attributes: Vec<CItemAttribute> = affix.attributes.iter().map(CItemAttribute::from_rust).collect();
let count = attributes.len() as c_uint;
let attrs_ptr = if count > 0 {
Box::into_raw(attributes.into_boxed_slice()) as *mut CItemAttribute
} else {
std::ptr::null_mut()
};
CAffix {
name: CString::new(affix.name.clone())
.ok()
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
attributes: attrs_ptr,
attributes_count: count,
}
}
fn free(&mut self) {
if !self.name.is_null() {
unsafe {
let _ = CString::from_raw(self.name);
}
self.name = std::ptr::null_mut();
}
if !self.attributes.is_null() && self.attributes_count > 0 {
unsafe {
for i in 0..self.attributes_count {
(*self.attributes.add(i as usize)).free();
}
let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(
self.attributes,
self.attributes_count as usize,
));
}
self.attributes = std::ptr::null_mut();
self.attributes_count = 0;
}
}
}
#[repr(C)]
pub struct CItem {
pub name: *mut c_char,
pub quality: *mut c_char,
pub item_type: *mut c_char,
pub subtype: *mut c_char,
pub prefix: CAffix,
pub suffix: CAffix,
pub attributes: *mut CItemAttribute,
pub attributes_count: c_uint,
}
impl CItem {
fn from_rust(item: &Item) -> Self {
let attributes: Vec<CItemAttribute> = item
.attributes
.values()
.map(CItemAttribute::from_rust)
.collect();
let attr_count = attributes.len() as c_uint;
let attrs_ptr = if attr_count > 0 {
Box::into_raw(attributes.into_boxed_slice()) as *mut CItemAttribute
} else {
std::ptr::null_mut()
};
CItem {
name: CString::new(item.name.clone())
.ok()
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
quality: CString::new(item.quality.clone())
.ok()
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
item_type: CString::new(item.item_type.clone())
.ok()
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
subtype: CString::new(item.subtype.clone())
.ok()
.map(|s| s.into_raw())
.unwrap_or(std::ptr::null_mut()),
prefix: CAffix::from_rust(&item.prefix),
suffix: CAffix::from_rust(&item.suffix),
attributes: attrs_ptr,
attributes_count: attr_count,
}
}
fn free(&mut self) {
if !self.name.is_null() {
unsafe {
let _ = CString::from_raw(self.name);
}
self.name = std::ptr::null_mut();
}
if !self.quality.is_null() {
unsafe {
let _ = CString::from_raw(self.quality);
}
self.quality = std::ptr::null_mut();
}
if !self.item_type.is_null() {
unsafe {
let _ = CString::from_raw(self.item_type);
}
self.item_type = std::ptr::null_mut();
}
if !self.subtype.is_null() {
unsafe {
let _ = CString::from_raw(self.subtype);
}
self.subtype = std::ptr::null_mut();
}
self.prefix.free();
self.suffix.free();
if !self.attributes.is_null() && self.attributes_count > 0 {
unsafe {
for i in 0..self.attributes_count {
(*self.attributes.add(i as usize)).free();
}
let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(
self.attributes,
self.attributes_count as usize,
));
}
self.attributes = std::ptr::null_mut();
self.attributes_count = 0;
}
}
}
#[repr(C)]
pub struct CItemArray {
pub items: *mut CItem,
pub count: c_uint,
}
impl CItemArray {
fn from_rust(items: &[Item]) -> Self {
let c_items: Vec<CItem> = items.iter().map(CItem::from_rust).collect();
let count = c_items.len() as c_uint;
let items_ptr = if count > 0 {
Box::into_raw(c_items.into_boxed_slice()) as *mut CItem
} else {
std::ptr::null_mut()
};
CItemArray {
items: items_ptr,
count,
}
}
}
pub struct PraedaGeneratorHandle {
generator: Box<PraedaGenerator>,
}
pub struct CItemArrayHandle {
array: CItemArray,
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_new() -> *mut PraedaGeneratorHandle {
Box::into_raw(Box::new(PraedaGeneratorHandle {
generator: Box::new(PraedaGenerator::new()),
}))
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_free(handle: *mut PraedaGeneratorHandle) {
if !handle.is_null() {
unsafe {
let _ = Box::from_raw(handle);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_string_free(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
let _ = CString::from_raw(ptr);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_error_free(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
let _ = CString::from_raw(ptr);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_item_array_free(handle: *mut CItemArrayHandle) {
if !handle.is_null() {
unsafe {
let array_handle = Box::from_raw(handle);
if !array_handle.array.items.is_null() && array_handle.array.count > 0 {
for i in 0..array_handle.array.count {
(*array_handle.array.items.add(i as usize)).free();
}
let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(
array_handle.array.items,
array_handle.array.count as usize,
));
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_load_toml(
handle: *mut PraedaGeneratorHandle,
toml_str: *const c_char,
error_out: *mut *mut c_char,
) -> i32 {
if handle.is_null() || toml_str.is_null() {
if !error_out.is_null()
&& let Ok(err) = CString::new("Invalid handle or TOML string") {
unsafe {
*error_out = err.into_raw();
}
}
return -1;
}
let toml_cstr = unsafe { CStr::from_ptr(toml_str) };
let toml_string = match toml_cstr.to_str() {
Ok(s) => s,
Err(_) => {
if !error_out.is_null()
&& let Ok(err) = CString::new("Invalid UTF-8 in TOML string") {
unsafe {
*error_out = err.into_raw();
}
}
return -1;
}
};
let generator = unsafe { &mut (*handle).generator };
match generator.load_data(toml_string) {
Ok(_) => 0,
Err(e) => {
if !error_out.is_null()
&& let Ok(err) = CString::new(format!("Failed to load TOML: {}", e)) {
unsafe {
*error_out = err.into_raw();
}
}
-1
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_set_quality_data(
handle: *mut PraedaGeneratorHandle,
quality_name: *const c_char,
weight: i32,
) -> i32 {
if handle.is_null() || quality_name.is_null() {
return -1;
}
let quality_cstr = unsafe { CStr::from_ptr(quality_name) };
let quality_str = match quality_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let generator = unsafe { &mut (*handle).generator };
generator.set_quality_data(quality_str, weight);
0
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_set_item_type(
handle: *mut PraedaGeneratorHandle,
type_name: *const c_char,
weight: i32,
) -> i32 {
if handle.is_null() || type_name.is_null() {
return -1;
}
let type_cstr = unsafe { CStr::from_ptr(type_name) };
let type_str = match type_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let generator = unsafe { &mut (*handle).generator };
generator.set_item_type(type_str, weight);
0
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_set_item_subtype(
handle: *mut PraedaGeneratorHandle,
type_name: *const c_char,
subtype_name: *const c_char,
weight: i32,
) -> i32 {
if handle.is_null() || type_name.is_null() || subtype_name.is_null() {
return -1;
}
let type_cstr = unsafe { CStr::from_ptr(type_name) };
let type_str = match type_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let subtype_cstr = unsafe { CStr::from_ptr(subtype_name) };
let subtype_str = match subtype_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let generator = unsafe { &mut (*handle).generator };
generator.set_item_subtype(type_str, subtype_str, weight);
0
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_set_attribute(
handle: *mut PraedaGeneratorHandle,
type_name: *const c_char,
subtype_name: *const c_char,
attr_name: *const c_char,
initial_value: f64,
min_value: f64,
max_value: f64,
required: i32,
) -> i32 {
if handle.is_null() || type_name.is_null() || subtype_name.is_null() || attr_name.is_null() {
return -1;
}
let type_cstr = unsafe { CStr::from_ptr(type_name) };
let type_str = match type_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let subtype_cstr = unsafe { CStr::from_ptr(subtype_name) };
let subtype_str = match subtype_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let attr_cstr = unsafe { CStr::from_ptr(attr_name) };
let attr_str = match attr_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let attribute = ItemAttribute::new(
attr_str,
initial_value,
min_value,
max_value,
required != 0,
);
let generator = unsafe { &mut (*handle).generator };
generator.set_attribute(
type_str,
subtype_str,
attribute,
);
0
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_set_item_names(
handle: *mut PraedaGeneratorHandle,
type_name: *const c_char,
subtype_name: *const c_char,
names: *const *const c_char,
names_count: c_uint,
) -> i32 {
if handle.is_null() || type_name.is_null() || subtype_name.is_null() || names.is_null() {
return -1;
}
let type_cstr = unsafe { CStr::from_ptr(type_name) };
let type_str = match type_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let subtype_cstr = unsafe { CStr::from_ptr(subtype_name) };
let subtype_str = match subtype_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let mut names_vec = Vec::new();
for i in 0..names_count as usize {
let name_ptr = unsafe { *names.add(i) };
if name_ptr.is_null() {
return -1;
}
let name_cstr = unsafe { CStr::from_ptr(name_ptr) };
match name_cstr.to_str() {
Ok(s) => names_vec.push(s.to_string()),
Err(_) => return -1,
}
}
let generator = unsafe { &mut (*handle).generator };
let names_refs: Vec<&str> = names_vec.iter().map(|s| s.as_str()).collect();
generator.set_item(type_str, subtype_str, names_refs);
0
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_set_prefix_attribute(
handle: *mut PraedaGeneratorHandle,
type_name: *const c_char,
subtype_name: *const c_char,
affix_name: *const c_char,
attr_name: *const c_char,
initial_value: f64,
min_value: f64,
max_value: f64,
required: i32,
) -> i32 {
if handle.is_null() || type_name.is_null() || subtype_name.is_null() || affix_name.is_null() || attr_name.is_null() {
return -1;
}
let type_cstr = unsafe { CStr::from_ptr(type_name) };
let type_str = match type_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let subtype_cstr = unsafe { CStr::from_ptr(subtype_name) };
let subtype_str = match subtype_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let affix_cstr = unsafe { CStr::from_ptr(affix_name) };
let affix_str = match affix_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let attr_cstr = unsafe { CStr::from_ptr(attr_name) };
let attr_str = match attr_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let attribute = ItemAttribute::new(
attr_str,
initial_value,
min_value,
max_value,
required != 0,
);
let generator = unsafe { &mut (*handle).generator };
generator.set_prefix_attribute(
type_str,
subtype_str,
affix_str,
attribute,
);
0
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_set_suffix_attribute(
handle: *mut PraedaGeneratorHandle,
type_name: *const c_char,
subtype_name: *const c_char,
affix_name: *const c_char,
attr_name: *const c_char,
initial_value: f64,
min_value: f64,
max_value: f64,
required: i32,
) -> i32 {
if handle.is_null() || type_name.is_null() || subtype_name.is_null() || affix_name.is_null() || attr_name.is_null() {
return -1;
}
let type_cstr = unsafe { CStr::from_ptr(type_name) };
let type_str = match type_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let subtype_cstr = unsafe { CStr::from_ptr(subtype_name) };
let subtype_str = match subtype_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let affix_cstr = unsafe { CStr::from_ptr(affix_name) };
let affix_str = match affix_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let attr_cstr = unsafe { CStr::from_ptr(attr_name) };
let attr_str = match attr_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let attribute = ItemAttribute::new(
attr_str,
initial_value,
min_value,
max_value,
required != 0,
);
let generator = unsafe { &mut (*handle).generator };
generator.set_suffix_attribute(
type_str,
subtype_str,
affix_str,
attribute,
);
0
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_generate_loot(
handle: *mut PraedaGeneratorHandle,
number_of_items: c_uint,
base_level: f64,
level_variance: f64,
affix_chance: f64,
linear: u8,
scaling_factor: f64,
error_out: *mut *mut c_char,
) -> *mut CItemArrayHandle {
if handle.is_null() {
if !error_out.is_null()
&& let Ok(err) = CString::new("Invalid handle") {
unsafe {
*error_out = err.into_raw();
}
}
return std::ptr::null_mut();
}
let options = GeneratorOptions {
number_of_items,
base_level,
level_variance,
affix_chance,
linear: linear != 0,
scaling_factor,
};
let generator = unsafe { &mut (*handle).generator };
match generator.generate_loot(&options, &GeneratorOverrides::empty(), "ffi") {
Ok(items) => {
let c_array = CItemArray::from_rust(&items);
Box::into_raw(Box::new(CItemArrayHandle { array: c_array }))
}
Err(e) => {
if !error_out.is_null()
&& let Ok(err) = CString::new(format!("Failed to generate loot: {}", e)) {
unsafe {
*error_out = err.into_raw();
}
}
std::ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_item_array_get(
handle: *const CItemArrayHandle,
index: c_uint,
) -> *const CItem {
if handle.is_null() {
return std::ptr::null();
}
let array_handle = unsafe { &*handle };
if index >= array_handle.array.count || array_handle.array.items.is_null() {
return std::ptr::null();
}
unsafe { &*array_handle.array.items.add(index as usize) }
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_item_array_count(handle: *const CItemArrayHandle) -> c_uint {
if handle.is_null() {
return 0;
}
unsafe { (*handle).array.count }
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_generator_has_quality(
handle: *const PraedaGeneratorHandle,
quality: *const c_char,
) -> i32 {
if handle.is_null() || quality.is_null() {
return -1;
}
let quality_cstr = unsafe { CStr::from_ptr(quality) };
let quality_str = match quality_cstr.to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let generator = unsafe { &(*handle).generator };
if generator.has_quality(quality_str) {
1
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn praeda_version() -> *mut c_char {
const VERSION: &str = env!("CARGO_PKG_VERSION");
if let Ok(version) = CString::new(VERSION) {
version.into_raw()
} else {
std::ptr::null_mut()
}
}