From 9b582c49bfcf3becf9d4bb4eb0bc0fa0d457dd11 Mon Sep 17 00:00:00 2001 From: xavo95 Date: Mon, 30 Dec 2024 19:29:48 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.lock | 16 ++++++ Cargo.toml | 15 +++++ README.md | 5 ++ src/f_string.rs | 72 ++++++++++++++++++++++++ src/lib.rs | 10 ++++ src/t_array.rs | 146 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 265 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/f_string.rs create mode 100644 src/lib.rs create mode 100644 src/t_array.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6e77824 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "unreal-niggery-rs" +version = "0.1.0" +dependencies = [ + "widestring", +] + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f394d12 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "unreal-niggery-rs" +version = "0.1.0" +edition = "2021" + +[features] + +[dependencies] +widestring = { version = "1.1.0" } + +[profile.release] +strip = true # Automatically strip symbols from the binary. +lto = true # Link-time optimization. +opt-level = 3 # Optimization level 3. +codegen-units = 1 # Maximum size reduction optimizations. diff --git a/README.md b/README.md new file mode 100644 index 0000000..17f0116 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# unreal-niggery-rs + +Subset of UE code ported to rust to be used from Rust code. Some functions might require the use of UE Garbage collector so some FFI calls might be required + + diff --git a/src/f_string.rs b/src/f_string.rs new file mode 100644 index 0000000..19b7e03 --- /dev/null +++ b/src/f_string.rs @@ -0,0 +1,72 @@ +use std::fmt::{Display, Formatter}; +use std::sync::OnceLock; + +use widestring::{WideCStr, WideCString}; + +use crate::{Add, Container}; +use crate::t_array::TArray; + +type FStringPrintf = fn(handle: usize, fmt: *const u16); +static FSTRING_PRINTF: OnceLock = OnceLock::new(); + +pub trait Printf { + /// Since rust does not support varargs in functions, it is recommended to call format! + fn printf>(handle: usize, fmt: A); +} + +pub struct FString(pub(crate) TArray); + +impl FString { + pub fn set_f_string_printf_ptr(f_string_printf: usize) { + FSTRING_PRINTF.set(f_string_printf).unwrap(); + } +} + +impl Printf for FString { + #[inline(always)] + fn printf>(handle: usize, fmt: A) { + let tmp = WideCString::from_str(fmt).unwrap(); + unsafe { + std::mem::transmute::(*fstring_printf())( + handle, + tmp.as_ucstr().as_ptr(), + ); + } + } +} + +impl<'a> Container<&'a WideCStr> for FString { + #[inline(always)] + fn read_container(&self) -> &'a WideCStr { + unsafe { + WideCStr::from_ptr(self.0.data_ptr as *const u16, self.0.len as usize - 1) + }.unwrap() + } +} + +impl Add for FString { + fn add(&mut self, other: &mut Self) -> &mut Self { + let old_len = self.0.resize_grow(Some(other.0.len)); + let src = unsafe { + std::slice::from_raw_parts_mut(other.0.data_ptr as *mut u16, other.0.len as usize) + }; + let dst = unsafe { + std::slice::from_raw_parts_mut(self.0.data_ptr as *mut u16, self.0.len as usize) + }; + dst[old_len as usize..other.0.len as usize].copy_from_slice(src); + // Clear the header of the original container + other.0.release_header_stack(); + self + } +} + +impl Display for FString { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.read_container().to_string_lossy()) + } +} + +#[inline(always)] +fn fstring_printf() -> &'static usize { + FSTRING_PRINTF.get().unwrap() +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..bb9b974 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +pub trait Container { + fn read_container(&self) -> T; +} + +pub trait Add { + fn add(&mut self, other: &mut T) -> &mut T; +} + +pub mod t_array; +pub mod f_string; \ No newline at end of file diff --git a/src/t_array.rs b/src/t_array.rs new file mode 100644 index 0000000..1d5b4c0 --- /dev/null +++ b/src/t_array.rs @@ -0,0 +1,146 @@ +use std::fmt::{Display, Formatter}; +use std::sync::OnceLock; + +use widestring::WideCStr; + +use crate::{Add, Container}; +use crate::f_string::FString; + +const T_ARRAY_HEADER_SIZE: usize = 0x10; +const DATA_PTR_OFFSET: usize = 0x00; +const LEN_OFFSET: usize = 0x08; +const CAP_OFFSET: usize = 0x0C; + +type TArrayResize = fn(ptr: usize, len: u32); +static T_ARRAY_RESIZE_GROW: OnceLock = OnceLock::new(); + +pub struct TArray { + pub(crate) ptr: *const u8, + pub(crate) data_ptr: u64, + pub(crate) len: u32, + pub(crate) cap: u32, +} + +impl TArray { + pub fn set_t_array_resize_grow_ptr(t_array_resize_grow: usize) { + T_ARRAY_RESIZE_GROW.set(t_array_resize_grow).unwrap(); + } + + pub fn read(ptr: usize) -> Self { + let data = unsafe { std::slice::from_raw_parts(ptr as *const u8, T_ARRAY_HEADER_SIZE) }; + Self { + ptr: ptr as *const u8, + data_ptr: u64::from_le_bytes(data[DATA_PTR_OFFSET..LEN_OFFSET].try_into().unwrap()), + len: u32::from_le_bytes(data[LEN_OFFSET..CAP_OFFSET].try_into().unwrap()), + cap: u32::from_le_bytes(data[CAP_OFFSET..T_ARRAY_HEADER_SIZE].try_into().unwrap()), + } + } + + #[inline(always)] + pub(crate) fn resize_grow(&mut self, count: Option) -> u32 { + let count = count.unwrap_or(1); + let old_len = self.len; + self.len = old_len + count; + let data = unsafe { + std::slice::from_raw_parts_mut(self.ptr as *mut u8, T_ARRAY_HEADER_SIZE) + }; + self.write_len_internal(data); + if self.cap < old_len + count { + unsafe { + std::mem::transmute::(*t_array_resize_grow())( + self.ptr as usize, + old_len, + ); + } + // ResizeGrow will change capacity and might change data_ptr, so force refresh + self.data_ptr = u64::from_le_bytes(data[DATA_PTR_OFFSET..LEN_OFFSET].try_into().unwrap()); + self.len = u32::from_le_bytes(data[LEN_OFFSET..CAP_OFFSET].try_into().unwrap()); + self.cap = u32::from_le_bytes(data[CAP_OFFSET..T_ARRAY_HEADER_SIZE].try_into().unwrap()); + } + old_len + } + + #[inline(always)] + pub(crate) fn release_header_stack(&mut self) { + let data = unsafe { + std::slice::from_raw_parts_mut(self.ptr as *mut u8, T_ARRAY_HEADER_SIZE) + }; + data.fill(0); + self.data_ptr = u64::from_le_bytes(data[DATA_PTR_OFFSET..LEN_OFFSET].try_into().unwrap()); + self.len = u32::from_le_bytes(data[LEN_OFFSET..CAP_OFFSET].try_into().unwrap()); + self.cap = u32::from_le_bytes(data[CAP_OFFSET..T_ARRAY_HEADER_SIZE].try_into().unwrap()); + } + + #[inline(always)] + fn write(&self) { + let data = unsafe { + std::slice::from_raw_parts_mut(self.ptr as *mut u8, T_ARRAY_HEADER_SIZE) + }; + self.write_data_ptr_internal(data); + self.write_len_internal(data); + self.write_cap_internal(data); + } + + #[inline(always)] + fn write_data_ptr_internal(&self, data: &mut [u8]) { + let bytes = self.data_ptr.to_le_bytes(); + data[DATA_PTR_OFFSET..LEN_OFFSET].copy_from_slice(&bytes); + } + + #[inline(always)] + fn write_len_internal(&self, data: &mut [u8]) { + let bytes = self.len.to_le_bytes(); + data[LEN_OFFSET..CAP_OFFSET].copy_from_slice(&bytes); + } + + #[inline(always)] + fn write_cap_internal(&self, data: &mut [u8]) { + let bytes = self.cap.to_le_bytes(); + data[CAP_OFFSET..T_ARRAY_HEADER_SIZE].copy_from_slice(&bytes); + } +} + +impl<'a> Container> for TArray { + fn read_container(&self) -> Vec<&'a WideCStr> { + let mut result = Vec::with_capacity(self.len as usize); + for i in 0..self.len { + result.push( + FString( + Self::read(self.data_ptr as usize + (i as usize * T_ARRAY_HEADER_SIZE)) + ).read_container() + ); + } + result + } +} + +impl Add for TArray { + fn add(&mut self, other: &mut Self) -> &mut Self { + let old_len = self.resize_grow(None); + Self { + ptr: (self.data_ptr as usize + (old_len as usize * T_ARRAY_HEADER_SIZE)) as *const u8, + data_ptr: other.data_ptr, + len: other.len, + cap: other.cap, + }.write(); + // Clear the of the original container + other.release_header_stack(); + self + } +} + +impl Display for TArray { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let elements = self.read_container(); + writeln!(f, "[")?; + for element in elements { + writeln!(f, "\t- {}", element.to_string_lossy())?; + } + write!(f, "]") + } +} + +#[inline(always)] +fn t_array_resize_grow() -> &'static usize { + T_ARRAY_RESIZE_GROW.get().unwrap() +} \ No newline at end of file