From 5896f61a6cb72df79ea3f902365367e64a6b0624 Mon Sep 17 00:00:00 2001 From: Truman Kilen Date: Fri, 7 Jun 2024 13:56:09 -0500 Subject: [PATCH] Implement constructing FName --- hook/src/hooks/server_list.rs | 291 ++++++++++++++++++++++++++++++++++ hook/src/lib.rs | 3 + hook/src/ue/array.rs | 22 ++- hook/src/ue/mod.rs | 1 + hook/src/ue/name.rs | 27 +++- hook_resolvers/src/lib.rs | 3 +- 6 files changed, 337 insertions(+), 10 deletions(-) create mode 100644 hook/src/hooks/server_list.rs diff --git a/hook/src/hooks/server_list.rs b/hook/src/hooks/server_list.rs new file mode 100644 index 00000000..efaa804b --- /dev/null +++ b/hook/src/hooks/server_list.rs @@ -0,0 +1,291 @@ +use std::ffi::c_void; + +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use crate::globals; +use crate::hooks::ExecFn; +use crate::ue::{self, FName, FString, TArray, TMap}; + +retour::static_detour! { + static GetServerName: unsafe extern "system" fn(*const c_void, *const c_void) -> *const ue::FString; + static USessionHandlingFSDFillSessionSetting: unsafe extern "system" fn(*const c_void, *mut c_void, bool, *mut c_void, *mut c_void); +} + +pub fn kismet_hooks() -> &'static [(&'static str, ExecFn)] { + &[( + "/Script/FSD.SessionHandling:FSDGetModsInstalled", + exec_get_mods_installed as ExecFn, + )] +} + +pub unsafe fn init_hooks() -> Result<()> { + if let Ok(server_name) = &globals().resolution.server_name { + GetServerName + .initialize( + std::mem::transmute(server_name.get_server_name.0), + detour_get_server_name, + )? + .enable()?; + } + + if let Ok(server_mods) = &globals().resolution.server_mods { + USessionHandlingFSDFillSessionSetting + .initialize( + std::mem::transmute(server_mods.fill_session_setting.0), + detour_fill_session_setting, + )? + .enable()?; + } + + Ok(()) +} + +fn detour_get_server_name(a: *const c_void, b: *const c_void) -> *const ue::FString { + unsafe { + let name = GetServerName.call(a, b).cast_mut().as_mut().unwrap(); + + let mut new_name = widestring::U16String::new(); + new_name.push_slice([0x5b, 0x4d, 0x4f, 0x44, 0x44, 0x45, 0x44, 0x5d, 0x20, 0x0a]); + new_name.push_slice(name.as_slice()); + + name.clear(); + name.extend_from_slice(new_name.as_slice()); + name.push(0); + + name + } +} + +fn detour_fill_session_setting( + world: *const c_void, + game_settings: *mut c_void, + full_server: bool, + unknown1: *mut c_void, + unknown2: *mut c_void, +) { + unsafe { + USessionHandlingFSDFillSessionSetting.call( + world, + game_settings, + full_server, + unknown1, + unknown2, + ); + + let name = globals().meta.to_server_list_string(); + + let s: FString = serde_json::to_string(&vec![JsonMod { + name, + version: "mint".into(), + category: 0, + }]) + .unwrap() + .as_str() + .into(); + + type Fn = unsafe extern "system" fn(*const c_void, ue::FName, *const ue::FString, u32); + + let f: Fn = std::mem::transmute( + globals() + .resolution + .server_mods + .as_ref() + .unwrap() + .set_fstring + .0, + ); + + f(game_settings, ue::FName::new(&"Mods".into()), &s, 3); + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct JsonMod { + name: String, + version: String, + category: i32, +} + +#[derive(Debug)] +#[repr(C)] +struct FBlueprintSessionResult { + online_result: FOnlineSessionSearchResult, +} +#[derive(Debug)] +#[repr(C)] +struct FOnlineSessionSearchResult { + session: FOnlineSession, + ping_in_ms: i32, +} +#[derive(Debug)] +#[repr(C)] +struct FOnlineSession { + vtable: u64, + owning_user_id: TSharedPtr, // TSharedPtr OwningUserId; + owning_user_name: FString, + session_settings: FOnlineSessionSettings, + session_info: TSharedPtr, //class TSharedPtr SessionInfo; + num_open_private_connections: i32, + num_open_public_connections: i32, +} + +#[derive(Debug)] +#[repr(C)] +struct FOnlineSessionSettings { + vtable: u64, + num_public_connections: i32, + num_private_connections: i32, + b_should_advertise: u8, + b_allow_join_in_progress: u8, + b_is_lan_match: u8, + b_is_dedicated: u8, + b_uses_stats: u8, + b_allow_invites: u8, + b_uses_presence: u8, + b_allow_join_via_presence: u8, + b_allow_join_via_presence_friends_only: u8, + b_anti_cheat_protected: u8, + build_unique_id: i32, + settings: TMap, + member_settings: [u64; 10], +} + +#[derive(Debug)] +#[repr(C)] +struct FOnlineSessionSetting { + data: FVariantData, + padding: [u32; 2], +} + +#[derive(Debug)] +#[repr(u32)] +#[allow(unused)] +enum EOnlineKeyValuePairDataType { + Empty, + Int32, + UInt32, + Int64, + UInt64, + Double, + String, + Float, + Blob, + Bool, + Json, + #[allow(clippy::upper_case_acronyms)] + MAX, +} + +#[repr(C)] +struct FVariantData { + type_: EOnlineKeyValuePairDataType, + value: FVariantDataValue, +} +impl std::fmt::Debug for FVariantData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut dbg = f.debug_struct("FVariantData"); + dbg.field("type", &self.type_); + unsafe { + match self { + Self { + type_: EOnlineKeyValuePairDataType::String, + value: FVariantDataValue { as_tchar }, + } => { + dbg.field("data", &widestring::U16CStr::from_ptr_str(*as_tchar)); + } + Self { + type_: EOnlineKeyValuePairDataType::UInt32, + value: FVariantDataValue { as_uint }, + } => { + dbg.field("data", &as_uint); + } + Self { + type_: EOnlineKeyValuePairDataType::Int32, + value: FVariantDataValue { as_int }, + } => { + dbg.field("data", &as_int); + } + _ => { + dbg.field("data", &""); + } + } + } + dbg.finish() + } +} + +#[repr(C)] +union FVariantDataValue { + as_bool: bool, + as_int: i32, + as_uint: u32, + as_float: f32, + as_int64: i64, + as_uint64: u64, + as_double: f64, + as_tchar: *const u16, + as_blob: std::mem::ManuallyDrop, +} + +#[repr(C)] +struct FVariantDataValueBlob { + blob_data: *const u8, + blob_size: u32, +} + +#[cfg(test)] +mod test { + use super::*; + const _: [u8; 0x20] = [0; std::mem::size_of::()]; + const _: [u8; 0x18] = [0; std::mem::size_of::()]; + const _: [u8; 0x10] = [0; std::mem::size_of::()]; + const _: [u8; 0x10] = [0; std::mem::size_of::()]; +} + +#[derive(Debug)] +#[repr(C)] +struct TSharedPtr { + a: u64, + b: u64, +} + +unsafe extern "system" fn exec_get_mods_installed( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let session: FBlueprintSessionResult = stack.arg(); + let _exclude_verified_mods: bool = stack.arg(); + + let result = &mut *(result as *mut TArray); + result.clear(); + + let settings = &session.online_result.session.session_settings.settings; + + let mods = settings.find(FName::new(&"Mods".into())); + if let Some(mods) = mods { + if let FVariantData { + type_: EOnlineKeyValuePairDataType::String, + value: FVariantDataValue { as_tchar }, + } = mods.data + { + if let Ok(string) = widestring::U16CStr::from_ptr_str(as_tchar).to_string() { + if let Ok(mods) = serde_json::from_str::>(&string) { + for m in mods { + result.push(m.name.as_str().into()); + } + } + } + } + } + + // TODO figure out lifetimes of structs from kismet params + std::mem::forget(session); + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} diff --git a/hook/src/lib.rs b/hook/src/lib.rs index 7431b464..3dc9068a 100644 --- a/hook/src/lib.rs +++ b/hook/src/lib.rs @@ -83,6 +83,9 @@ impl Globals { pub fn fname_to_string(&self) -> ue::FnFNameToString { unsafe { std::mem::transmute(self.resolution.core.as_ref().unwrap().fnametostring.0) } } + pub fn fname_ctor_wchar(&self) -> ue::FnFNameCtorWchar { + unsafe { std::mem::transmute(self.resolution.core.as_ref().unwrap().fname_ctor_wchar.0) } + } pub fn uobject_base_utility_get_path_name(&self) -> ue::FnUObjectBaseUtilityGetPathName { unsafe { std::mem::transmute( diff --git a/hook/src/ue/array.rs b/hook/src/ue/array.rs index 4051af09..6faf5772 100644 --- a/hook/src/ue/array.rs +++ b/hook/src/ue/array.rs @@ -5,34 +5,40 @@ use crate::globals; #[derive(Debug)] #[repr(C)] pub struct TArray { - data: *const T, + data: *mut T, num: i32, max: i32, } impl TArray { pub fn new() -> Self { Self { - data: std::ptr::null(), + data: std::ptr::null_mut(), num: 0, max: 0, } } + pub fn as_ptr(&self) -> *const T { + self.data + } + pub fn as_mut_ptr(&mut self) -> *mut T { + self.data + } } impl Drop for TArray { fn drop(&mut self) { unsafe { std::ptr::drop_in_place(std::ptr::slice_from_raw_parts_mut( - self.data.cast_mut(), + self.data, self.num as usize, )) } - globals().gmalloc().free(self.data as *mut c_void); + globals().gmalloc().free(self.data.cast()); } } impl Default for TArray { fn default() -> Self { Self { - data: std::ptr::null(), + data: std::ptr::null_mut(), num: 0, max: 0, } @@ -44,7 +50,7 @@ impl TArray { data: globals().gmalloc().malloc( capacity * std::mem::size_of::(), std::mem::align_of::() as u32, - ) as *const T, + ) as *mut _, num: 0, max: capacity as i32, } @@ -87,14 +93,14 @@ impl TArray { self.data as *mut c_void, self.max as usize * std::mem::size_of::(), std::mem::align_of::() as u32, - ) as *const T; + ) as *mut _; self.data = new; } } pub fn push(&mut self, new_value: T) { self.reserve(1); unsafe { - std::ptr::write(self.data.add(self.num as usize).cast_mut(), new_value); + std::ptr::write(self.data.add(self.num as usize), new_value); } self.num += 1; } diff --git a/hook/src/ue/mod.rs b/hook/src/ue/mod.rs index 57c8f329..b9af7d50 100644 --- a/hook/src/ue/mod.rs +++ b/hook/src/ue/mod.rs @@ -23,6 +23,7 @@ pub type FnFFrameStepExplicitProperty = unsafe extern "system" fn( property: *const FProperty, ); pub type FnFNameToString = unsafe extern "system" fn(&FName, &mut FString); +pub type FnFNameCtorWchar = unsafe extern "system" fn(&mut FName, *const u16, EFindName); pub type FnUObjectBaseUtilityGetPathName = unsafe extern "system" fn(&UObjectBase, Option<&UObject>, &mut FString); diff --git a/hook/src/ue/name.rs b/hook/src/ue/name.rs index 0bb979f5..dc3dddb3 100644 --- a/hook/src/ue/name.rs +++ b/hook/src/ue/name.rs @@ -1,13 +1,38 @@ use crate::{globals, ue::FString}; #[derive(Debug, Clone, Copy)] +#[repr(u32)] +pub enum EFindName { + Find, + Add, + ReplaceNotSafeForThreading, +} + +#[derive(Default, Clone, Copy)] #[repr(C)] pub struct FName { pub comparison_index: FNameEntryId, pub number: u32, } +impl FName { + pub fn new(string: &FString) -> FName { + let mut ret = FName::default(); + unsafe { globals().fname_ctor_wchar()(&mut ret, string.as_ptr(), EFindName::Add) }; + ret + } + pub fn find(string: &FString) -> FName { + let mut ret = FName::default(); + unsafe { globals().fname_ctor_wchar()(&mut ret, string.as_ptr(), EFindName::Find) }; + ret + } +} +impl std::fmt::Debug for FName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "FName({self})") + } +} -#[derive(Debug, Clone, Copy)] +#[derive(Default, Debug, Clone, Copy)] #[repr(C)] pub struct FNameEntryId { pub value: u32, diff --git a/hook_resolvers/src/lib.rs b/hook_resolvers/src/lib.rs index a4ce41b5..e875bd51 100644 --- a/hook_resolvers/src/lib.rs +++ b/hook_resolvers/src/lib.rs @@ -1,6 +1,6 @@ use patternsleuth::resolvers::futures::future::join_all; use patternsleuth::resolvers::unreal::blueprint_library::UFunctionBind; -use patternsleuth::resolvers::unreal::fname::FNameToString; +use patternsleuth::resolvers::unreal::fname::{FNameCtorWchar, FNameToString}; use patternsleuth::resolvers::unreal::gmalloc::GMalloc; use patternsleuth::resolvers::unreal::kismet::{FFrameStep, FFrameStepExplicitProperty}; use patternsleuth::resolvers::unreal::save_game::{ @@ -217,6 +217,7 @@ impl_try_collector! { pub struct CoreResolution { pub gmalloc: GMalloc, pub fnametostring: FNameToString, + pub fname_ctor_wchar: FNameCtorWchar, pub uobject_base_utility_get_path_name: UObjectBaseUtilityGetPathName, pub ufunction_bind: UFunctionBind, pub fframe_step: FFrameStep,