diff --git a/Cargo.toml b/Cargo.toml index f394d12..390b23f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "unreal-niggery-rs" version = "0.1.0" -edition = "2021" +edition = "2024" [features] [dependencies] -widestring = { version = "1.1.0" } +thiserror = "2.0.12" +widestring = "1.2.0" [profile.release] strip = true # Automatically strip symbols from the binary. diff --git a/src/f_string.rs b/src/f_string.rs index 75165b5..e1131d6 100644 --- a/src/f_string.rs +++ b/src/f_string.rs @@ -3,16 +3,29 @@ use std::sync::OnceLock; use widestring::{U16CStr, U16CString}; -use crate::{Add, Container}; use crate::t_array::TArray; +use crate::{Add, Container}; type FStringPrintf = fn(handle: usize, fmt: *const u16); static FSTRING_PRINTF: OnceLock = OnceLock::new(); +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("ContainsNul error: {0}")] + ContainsNulu16(#[from] widestring::error::ContainsNul), + #[error("New string is too long and no printf ffi binding has been provided")] + DataTooLongForUnsafe, + #[error("FStringPrintf not set")] + FStringPrintfNotSet, +} + pub trait Printf { /// Since rust does not support varargs in functions, it is recommended to call format! - fn printf>(handle: usize, fmt: A); - fn printf_unsafe>(&mut self, fmt: A); + fn printf>(handle: usize, fmt: A) -> Result where Self: Sized; + /// Since rust does not support varargs in functions, it is recommended to call format! + fn printf_safe>(&mut self, fmt: A) -> Result where Self: Sized; + /// Since rust does not support varargs in functions, it is recommended to call format! + fn printf_unsafe>(&mut self, fmt: A) -> Result where Self: Sized; } pub struct FString(pub TArray); @@ -25,35 +38,41 @@ impl FString { impl Printf for FString { #[inline(always)] - fn printf>(handle: usize, fmt: A) { - let tmp = U16CString::from_str(fmt).unwrap(); + fn printf>(handle: usize, fmt: A) -> Result { + let tmp = U16CString::from_str(fmt)?; unsafe { - std::mem::transmute::(*fstring_printf())( - handle, - tmp.as_ucstr().as_ptr(), - ); + std::mem::transmute::( + *FSTRING_PRINTF.get().ok_or(Error::FStringPrintfNotSet)?, + )(handle, tmp.as_ucstr().as_ptr()); + } + Ok(Self(TArray::read(handle))) + } + + fn printf_safe>(&mut self, fmt: A) -> Result { + match FSTRING_PRINTF.get() { + None => self.printf_unsafe(fmt), + Some(_) => Self::printf(self.0.ptr as usize, fmt), } } - - #[inline(always)] - fn printf_unsafe>(&mut self, fmt: A) { - let tmp = U16CString::from_str(fmt).unwrap(); - let mut tmp_bytes = tmp.into_vec_with_nul(); - let new_len = tmp_bytes.len(); - if new_len > self.0.cap as usize { - // TODO: use Result instead - panic!("New string is too long and no printf ffi binding has been provided"); - } - tmp_bytes.resize(self.0.cap as usize, 0); - unsafe { - std::ptr::copy_nonoverlapping( - tmp_bytes.as_ptr(), - self.0.data_ptr as *mut u16, - self.0.cap as usize, - ); - } - self.0.len = new_len as u32; - self.0.write(); + + fn printf_unsafe>(&mut self, fmt: A) -> Result { + let tmp = U16CString::from_str(fmt)?; + let mut tmp_bytes = tmp.into_vec_with_nul(); + let new_len = tmp_bytes.len(); + if new_len > self.0.cap as usize { + return Err(Error::DataTooLongForUnsafe); + } + tmp_bytes.resize(self.0.cap as usize, 0); + unsafe { + std::ptr::copy_nonoverlapping( + tmp_bytes.as_ptr(), + self.0.data_ptr as *mut u16, + self.0.cap as usize, + ); + } + self.0.len = new_len as u32; + self.0.write(); + Ok(FString(TArray::read(self.0.ptr as usize))) } } @@ -63,7 +82,8 @@ impl<'a> Container<&'a U16CStr> for FString { unsafe { // TODO: Verify this in linux when symphonic is ready U16CStr::from_ptr(self.0.data_ptr as *const u16, self.0.len as usize - 1) - }.unwrap() + } + .unwrap() } } @@ -88,8 +108,3 @@ impl Display for FString { write!(f, "{}", self.read_container().to_string_lossy()) } } - -#[inline(always)] -fn fstring_printf() -> &'static usize { - FSTRING_PRINTF.get().unwrap() -} \ No newline at end of file