Initial commit

This commit is contained in:
xavo95 2024-12-30 19:29:48 +01:00
commit 9b582c49bf
Signed by: xavo95
GPG key ID: CBF8ADED6DEBB783
7 changed files with 265 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

16
Cargo.lock generated Normal file
View file

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

15
Cargo.toml Normal file
View file

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

5
README.md Normal file
View file

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

72
src/f_string.rs Normal file
View file

@ -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<usize> = OnceLock::new();
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);
}
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<str> for FString {
#[inline(always)]
fn printf<A: AsRef<str>>(handle: usize, fmt: A) {
let tmp = WideCString::from_str(fmt).unwrap();
unsafe {
std::mem::transmute::<usize, FStringPrintf>(*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()
}

10
src/lib.rs Normal file
View file

@ -0,0 +1,10 @@
pub trait Container<T> {
fn read_container(&self) -> T;
}
pub trait Add<T = Self> {
fn add(&mut self, other: &mut T) -> &mut T;
}
pub mod t_array;
pub mod f_string;

146
src/t_array.rs Normal file
View file

@ -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<usize> = 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>) -> 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::<usize, TArrayResize>(*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<Vec<&'a WideCStr>> 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()
}