Add safety for printf

This commit is contained in:
xavo95 2025-04-23 05:27:35 +07:00
parent ac8ba74a3e
commit e07eda21e4
Signed by: xavo95
GPG key ID: CBF8ADED6DEBB783
2 changed files with 53 additions and 37 deletions

View file

@ -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.

View file

@ -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<usize> = OnceLock::new();
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("ContainsNul<u16> error: {0}")]
ContainsNulu16(#[from] widestring::error::ContainsNul<u16>),
#[error("New string is too long and no printf ffi binding has been provided")]
DataTooLongForUnsafe,
#[error("FStringPrintf not set")]
FStringPrintfNotSet,
}
pub trait Printf<T: ?Sized> {
/// Since rust does not support varargs in functions, it is recommended to call format!
fn printf<A: AsRef<T>>(handle: usize, fmt: A);
fn printf_unsafe<A: AsRef<T>>(&mut self, fmt: A);
fn printf<A: AsRef<T>>(handle: usize, fmt: A) -> Result<Self, Error> where Self: Sized;
/// Since rust does not support varargs in functions, it is recommended to call format!
fn printf_safe<A: AsRef<T>>(&mut self, fmt: A) -> Result<Self, Error> where Self: Sized;
/// Since rust does not support varargs in functions, it is recommended to call format!
fn printf_unsafe<A: AsRef<T>>(&mut self, fmt: A) -> Result<Self, Error> where Self: Sized;
}
pub struct FString(pub TArray);
@ -25,24 +38,29 @@ impl FString {
impl Printf<str> for FString {
#[inline(always)]
fn printf<A: AsRef<str>>(handle: usize, fmt: A) {
let tmp = U16CString::from_str(fmt).unwrap();
fn printf<A: AsRef<str>>(handle: usize, fmt: A) -> Result<Self, Error> {
let tmp = U16CString::from_str(fmt)?;
unsafe {
std::mem::transmute::<usize, FStringPrintf>(*fstring_printf())(
handle,
tmp.as_ucstr().as_ptr(),
);
std::mem::transmute::<usize, FStringPrintf>(
*FSTRING_PRINTF.get().ok_or(Error::FStringPrintfNotSet)?,
)(handle, tmp.as_ucstr().as_ptr());
}
Ok(Self(TArray::read(handle)))
}
fn printf_safe<A: AsRef<str>>(&mut self, fmt: A) -> Result<Self, Error> {
match FSTRING_PRINTF.get() {
None => self.printf_unsafe(fmt),
Some(_) => Self::printf(self.0.ptr as usize, fmt),
}
}
#[inline(always)]
fn printf_unsafe<A: AsRef<str>>(&mut self, fmt: A) {
let tmp = U16CString::from_str(fmt).unwrap();
fn printf_unsafe<A: AsRef<str>>(&mut self, fmt: A) -> Result<Self, Error> {
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 {
// TODO: use Result instead
panic!("New string is too long and no printf ffi binding has been provided");
return Err(Error::DataTooLongForUnsafe);
}
tmp_bytes.resize(self.0.cap as usize, 0);
unsafe {
@ -54,6 +72,7 @@ impl Printf<str> for FString {
}
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()
}