From a0ad18ad61a03fea9ccff8cee64c44fe23ac0547 Mon Sep 17 00:00:00 2001 From: Truman Kilen Date: Thu, 25 Jul 2024 23:39:18 -0500 Subject: [PATCH] hot reload mint modules --- Cargo.lock | 206 +++- Cargo.toml | 9 +- hook/Cargo.toml | 4 +- hook/src/hooks/debug_drawing.rs | 4 +- hook/src/hooks/mod.rs | 66 +- hook/src/hooks/nav.rs | 23 +- hook/src/hooks/pak/mod.rs | 4 +- hook/src/hooks/server_list.rs | 4 +- hook/src/lib.rs | 85 +- hook_modules/hook_lib/Cargo.toml | 15 + hook_modules/hook_lib/src/lib.rs | 95 ++ .../hook_lib}/src/ue/array.rs | 32 +- hook_modules/hook_lib/src/ue/debug_drawing.rs | 925 ++++++++++++++++++ .../hook_lib}/src/ue/kismet.rs | 0 .../hook_lib}/src/ue/malloc.rs | 11 +- {hook => hook_modules/hook_lib}/src/ue/map.rs | 0 {hook => hook_modules/hook_lib}/src/ue/mod.rs | 2 + .../hook_lib}/src/ue/name.rs | 0 .../hook_lib}/src/ue/object.rs | 0 .../hook_lib}/src/ue/string.rs | 0 .../hook_lib}/src/ue/world.rs | 0 {hook => hook_modules/hook_lib}/src/util.rs | 0 hook_modules/hook_test/Cargo.toml | 14 + hook_modules/hook_test/src/lib.rs | 67 ++ hook_resolvers/src/lib.rs | 3 +- 25 files changed, 1435 insertions(+), 134 deletions(-) create mode 100644 hook_modules/hook_lib/Cargo.toml create mode 100644 hook_modules/hook_lib/src/lib.rs rename {hook => hook_modules/hook_lib}/src/ue/array.rs (80%) create mode 100644 hook_modules/hook_lib/src/ue/debug_drawing.rs rename {hook => hook_modules/hook_lib}/src/ue/kismet.rs (100%) rename {hook => hook_modules/hook_lib}/src/ue/malloc.rs (85%) rename {hook => hook_modules/hook_lib}/src/ue/map.rs (100%) rename {hook => hook_modules/hook_lib}/src/ue/mod.rs (97%) rename {hook => hook_modules/hook_lib}/src/ue/name.rs (100%) rename {hook => hook_modules/hook_lib}/src/ue/object.rs (100%) rename {hook => hook_modules/hook_lib}/src/ue/string.rs (100%) rename {hook => hook_modules/hook_lib}/src/ue/world.rs (100%) rename {hook => hook_modules/hook_lib}/src/util.rs (100%) create mode 100644 hook_modules/hook_test/Cargo.toml create mode 100644 hook_modules/hook_test/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index e15156d..75041bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1951,6 +1951,27 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" +[[package]] +name = "file-id" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc904b9bbefcadbd8e3a9fb0d464a9b979de6324c03b3c663e8994f46a5be36" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox 0.1.3", + "windows-sys 0.59.0", +] + [[package]] name = "flate2" version = "1.0.34" @@ -2018,6 +2039,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "funty" version = "2.0.0" @@ -2489,7 +2519,9 @@ dependencies = [ "element-ptr", "fs-err", "glob", + "hook_lib", "hook_resolvers", + "hot-lib-reloader", "mint_lib", "nalgebra", "patternsleuth", @@ -2507,6 +2539,18 @@ dependencies = [ "windows 0.58.0", ] +[[package]] +name = "hook_lib" +version = "0.2.10" +dependencies = [ + "bitflags 2.6.0", + "element-ptr", + "hook_resolvers", + "mint_lib", + "nalgebra", + "widestring", +] + [[package]] name = "hook_resolvers" version = "0.2.10" @@ -2515,6 +2559,40 @@ dependencies = [ "serde", ] +[[package]] +name = "hook_test" +version = "0.2.10" +dependencies = [ + "hook_lib", + "hot-lib-reloader", +] + +[[package]] +name = "hot-lib-reloader" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda706fdb8bcf5279ce95754dd86a23eab0a81ad2823d5402c2e10a4dd3283c2" +dependencies = [ + "crc32fast", + "hot-lib-reloader-macro", + "libloading 0.8.1", + "log", + "notify", + "notify-debouncer-full", + "thiserror", +] + +[[package]] +name = "hot-lib-reloader-macro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f01c6c25eabdd8e2de50ccdb7e13186ba2ac0f69ebd8bd5f96ae235414180800" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "http" version = "0.2.11" @@ -2800,6 +2878,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.1.3" @@ -2959,6 +3057,26 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -3012,6 +3130,17 @@ dependencies = [ "redox_syscall 0.4.1", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.5.7", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -3267,6 +3396,18 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.2" @@ -3374,9 +3515,9 @@ checksum = "fce7b49e1e6d8aa67232ef1c4c936c0af58756eb2db6f65c40bacb39035e7f42" [[package]] name = "nalgebra" -version = "0.32.6" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" dependencies = [ "approx", "matrixmultiply", @@ -3477,6 +3618,39 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.6.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb7fd166739789c9ff169e654dc1501373db9d80a4c3f972817c8a4d7cf8f34e" +dependencies = [ + "crossbeam-channel", + "file-id", + "log", + "notify", + "parking_lot", + "walkdir", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3487,6 +3661,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -3517,6 +3701,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ + "num-bigint", "num-integer", "num-traits", ] @@ -3889,7 +4074,7 @@ version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" dependencies = [ - "libredox", + "libredox 0.0.2", ] [[package]] @@ -4475,6 +4660,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -5122,9 +5316,9 @@ dependencies = [ [[package]] name = "simba" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" dependencies = [ "approx", "num-complex", @@ -5622,7 +5816,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.2", "parking_lot", "pin-project-lite", "signal-hook-registry", diff --git a/Cargo.toml b/Cargo.toml index 1d471c4..0fbd970 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [workspace] +nalgebra = "0.33.0" members = [ "mint_lib", "hook", "hook_resolvers", - "workspace_hack", + "workspace_hack", "hook_modules/hook_test", "hook_modules/hook_lib", ] [workspace.package] @@ -15,7 +16,7 @@ edition = "2021" [workspace.dependencies] anyhow = { version = "1.0.92", features = ["backtrace"] } -patternsleuth = { git = "https://github.com/trumank/patternsleuth" } +patternsleuth = { git = "https://github.com/trumank/patternsleuth", features = ["image-pe"] } steamlocate = "2.0.0-beta.4" repak = { git = "https://github.com/trumank/repak" } serde = { version = "1.0.214", features = ["derive"] } @@ -29,6 +30,7 @@ tracing-subscriber = { version = "0.3.18", features = ["fmt", "env-filter", "std tokio = "1.41.0" reqwest = { version = "0.11.27", default-features = false, features = ["blocking", "rustls", "json"] } snafu = "0.8.5" +nalgebra = "0.33.0" [package] name = "mint" @@ -113,6 +115,9 @@ strip = true opt-level = 3 lto = "off" +[profile.release2] +inherits = "release" + # Config for 'cargo dist' [workspace.metadata.dist] # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) diff --git a/hook/Cargo.toml b/hook/Cargo.toml index 9167b6f..be6e1e4 100644 --- a/hook/Cargo.toml +++ b/hook/Cargo.toml @@ -29,13 +29,15 @@ windows = { version = "0.58.0", features = [ "Win32_System_Threading", ] } mint_lib = { path = "../mint_lib" } +hook_lib = { path = "../hook_modules/hook_lib" } bitflags = "2.6.0" widestring = "1.1.0" tokio = { workspace = true, features = ["full"] } tracing-appender = "0.2.3" proxy_dll = { git = "https://github.com/trumank/proxy_dll.git", version = "0.1.0" } element-ptr = "0.0.2" -nalgebra = "0.32.5" +nalgebra.workspace = true seq-macro = "0.3.5" byteorder = "1.5.0" glob = "0.3.1" +hot-lib-reloader = "0.7.0" diff --git a/hook/src/hooks/debug_drawing.rs b/hook/src/hooks/debug_drawing.rs index 578226d..9d4b9fd 100644 --- a/hook/src/hooks/debug_drawing.rs +++ b/hook/src/hooks/debug_drawing.rs @@ -6,11 +6,11 @@ use na::{Matrix, Matrix4, Point3, Vector3}; use nalgebra as na; use crate::hooks::ExecFn; -use crate::ue::{ +use hook_lib::ue::{ self, get_world, FBatchedLine, FBatchedPoint, FLinearColor, FRotator, FVector, TArray, ULineBatchComponent, UObject, UWorld, }; -use crate::util::NN; +use hook_lib::util::NN; pub fn kismet_hooks() -> &'static [(&'static str, ExecFn)] { &[ diff --git a/hook/src/hooks/mod.rs b/hook/src/hooks/mod.rs index bbc5883..33328d8 100644 --- a/hook/src/hooks/mod.rs +++ b/hook/src/hooks/mod.rs @@ -1,5 +1,15 @@ #![allow(clippy::missing_transmute_annotations)] +#[hot_lib_reloader::hot_module( + dylib = "hook_test", + //lib_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/target/x86_64-pc-windows-gnu/release") + lib_dir = "/home/truman/projects/drg-modding/tools/mint/target/x86_64-pc-windows-gnu/release2" +)] +mod hot_lib { + //pub use hook_test::State; + hot_functions_from_file!("hook_modules/hook_test/src/lib.rs"); +} + mod debug_drawing; mod nav; mod pak; @@ -18,7 +28,8 @@ use hook_resolvers::GasFixResolution; use mint_lib::DRGInstallationType; use windows::Win32::System::Memory::{VirtualProtect, PAGE_EXECUTE_READWRITE}; -use crate::{globals, ue, LOG_GUARD}; +use crate::LOG_GUARD; +use hook_lib::{globals, ue, USaveGame}; retour::static_detour! { static HookUFunctionBind: unsafe extern "system" fn(*mut ue::UFunction); @@ -27,15 +38,9 @@ retour::static_detour! { static DoesSaveGameExist: unsafe extern "system" fn(*const ue::FString, i32) -> bool; static UObjectTemperatureComponentTimerCallback: unsafe extern "system" fn(*mut c_void); static WinMain: unsafe extern "system" fn(*mut (), *mut (), *mut (), i32, *const ()) -> i32; + static FEngineLoopTick: unsafe extern "system" fn(*mut ()); } -#[repr(C)] -pub struct USaveGame; - -pub type FnSaveGameToMemory = - unsafe extern "system" fn(*const USaveGame, *mut ue::TArray) -> bool; -pub type FnLoadGameFromMemory = - unsafe extern "system" fn(*const ue::TArray) -> *const USaveGame; type ExecFn = unsafe extern "system" fn(*mut ue::UObject, *mut ue::kismet::FFrame, *mut c_void); pub unsafe fn initialize() -> Result<()> { @@ -48,6 +53,10 @@ pub unsafe fn initialize() -> Result<()> { "/Script/Engine.KismetSystemLibrary:PrintString", exec_print_string as ExecFn, ), + ( + "/Game/_AssemblyStorm/TestMod/MintDebugStuff/InitCave.InitCave_C:ReceiveTick", + exec_tick as ExecFn, + ), ] .iter() .chain(server_list::kismet_hooks().iter()) @@ -62,6 +71,27 @@ pub unsafe fn initialize() -> Result<()> { )?; WinMain.enable()?; + //let value = hot_lib::get_number(); + + //FEngineLoopTick.initialize( + // std::mem::transmute( + // globals() + // .resolution + // .core + // .as_ref() + // .unwrap() + // .fengine_loop_tick + // .0, + // ), + // |this| { + // FEngineLoopTick.call(this); + // tracing::info!("tick"); + // let value = hot_lib::get_number(globals()); + // tracing::info!("value = {value}"); + // }, + //)?; + //FEngineLoopTick.enable()?; + pak::init()?; HookUFunctionBind.initialize( @@ -348,3 +378,23 @@ unsafe extern "system" fn exec_print_string( stack.code = stack.code.add(1); } + +unsafe extern "system" fn exec_tick( + context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let _delta_seconds: f32 = stack.arg(); + + hot_lib::tick(globals(), context); + + //if let Some(world) = get_world(context.nn()) { + // render(world); + //} + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} diff --git a/hook/src/hooks/nav.rs b/hook/src/hooks/nav.rs index e79d89f..d95e4e8 100644 --- a/hook/src/hooks/nav.rs +++ b/hook/src/hooks/nav.rs @@ -1,16 +1,17 @@ use element_ptr::element_ptr; +use hook_lib::globals; use std::ffi::c_void; use std::ptr::NonNull; -use crate::ue::get_world; -use crate::ue::FBatchedLine; -use crate::ue::FBatchedPoint; -use crate::ue::FLinearColor; -use crate::ue::FVector; -use crate::ue::TArray; -use crate::ue::UObject; -use crate::ue::UWorld; -use crate::util::NN as _; +use hook_lib::ue::get_world; +use hook_lib::ue::FBatchedLine; +use hook_lib::ue::FBatchedPoint; +use hook_lib::ue::FLinearColor; +use hook_lib::ue::FVector; +use hook_lib::ue::TArray; +use hook_lib::ue::UObject; +use hook_lib::ue::UWorld; +use hook_lib::util::NN as _; use super::debug_drawing::draw_box; use super::debug_drawing::draw_lines; @@ -249,7 +250,7 @@ unsafe fn get_path( type_: DeepPathFinderType, pref: DeepPathFinderPreference, ) -> Option> { - let Ok(get_path) = crate::globals() + let Ok(get_path) = globals() .resolution .get_path .as_ref() @@ -645,7 +646,7 @@ pub unsafe fn spawn_points( size: DeepPathFinderSize, type_: DeepPathFinderType, ) { - let Ok(get_all_spawn_points) = crate::globals() + let Ok(get_all_spawn_points) = globals() .resolution .get_all_spawn_points_in_sphere .as_ref() diff --git a/hook/src/hooks/pak/mod.rs b/hook/src/hooks/pak/mod.rs index a7ffc42..edd120c 100644 --- a/hook/src/hooks/pak/mod.rs +++ b/hook/src/hooks/pak/mod.rs @@ -7,8 +7,8 @@ use serde::Deserialize; use std::io::{Read, Seek, Write}; use std::sync::{Mutex, OnceLock}; -use crate::globals; -use crate::ue::{FString, TArray}; +use hook_lib::globals; +use hook_lib::ue::{FString, TArray}; use self::file::PlainFileProviderConfig; use self::network::EditorNetworkConfig; diff --git a/hook/src/hooks/server_list.rs b/hook/src/hooks/server_list.rs index 3c5ab8a..65ca14a 100644 --- a/hook/src/hooks/server_list.rs +++ b/hook/src/hooks/server_list.rs @@ -3,9 +3,9 @@ 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}; +use hook_lib::globals; +use hook_lib::ue::{self, FName, FString, TArray, TMap}; retour::static_detour! { static GetServerName: unsafe extern "system" fn(*const c_void, *const c_void) -> *const ue::FString; diff --git a/hook/src/lib.rs b/hook/src/lib.rs index 23a24be..a047ba1 100644 --- a/hook/src/lib.rs +++ b/hook/src/lib.rs @@ -1,6 +1,4 @@ mod hooks; -mod ue; -mod util; use std::{ io::BufReader, @@ -9,7 +7,7 @@ use std::{ use anyhow::{Context, Result}; use fs_err as fs; -use hooks::{FnLoadGameFromMemory, FnSaveGameToMemory}; +use hook_lib::{init_globals, Globals}; use mint_lib::mod_info::Meta; use tracing::{info, warn}; @@ -21,89 +19,10 @@ fn init() { } } -static mut GLOBALS: Option = None; thread_local! { static LOG_GUARD: std::cell::RefCell> = None.into(); } -pub struct Globals { - resolution: hook_resolvers::HookResolution, - meta: Meta, - bin_dir: Option, -} - -impl Globals { - pub fn gmalloc(&self) -> &ue::FMalloc { - unsafe { - &**(self.resolution.core.as_ref().unwrap().gmalloc.0 as *const *const ue::FMalloc) - } - } - pub fn fframe_step(&self) -> ue::FnFFrameStep { - unsafe { std::mem::transmute(self.resolution.core.as_ref().unwrap().fframe_step.0) } - } - pub fn fframe_step_explicit_property(&self) -> ue::FnFFrameStepExplicitProperty { - unsafe { - std::mem::transmute( - self.resolution - .core - .as_ref() - .unwrap() - .fframe_step_explicit_property - .0, - ) - } - } - 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( - self.resolution - .core - .as_ref() - .unwrap() - .uobject_base_utility_get_path_name - .0, - ) - } - } - pub fn save_game_to_memory(&self) -> FnSaveGameToMemory { - unsafe { - std::mem::transmute( - self.resolution - .save_game - .as_ref() - .unwrap() - .save_game_to_memory - .0, - ) - } - } - pub fn load_game_from_memory(&self) -> FnLoadGameFromMemory { - unsafe { - std::mem::transmute( - self.resolution - .save_game - .as_ref() - .unwrap() - .load_game_from_memory - .0, - ) - } - } -} - -pub fn globals() -> &'static Globals { - #[allow(static_mut_refs)] - unsafe { - GLOBALS.as_ref().unwrap() - } -} - unsafe fn patch() -> Result<()> { let exe_path = std::env::current_exe().ok(); let bin_dir = exe_path.as_deref().and_then(Path::parent); @@ -130,11 +49,13 @@ unsafe fn patch() -> Result<()> { let resolution = image.resolve(hook_resolvers::HookResolution::resolver())?; info!("PS scan: {:#x?}", resolution); + static mut GLOBALS: Option = None; GLOBALS = Some(Globals { resolution, meta, bin_dir: bin_dir.map(|d| d.to_path_buf()), }); + init_globals(GLOBALS.as_ref().unwrap()); LOG_GUARD.with_borrow_mut(|g| *g = guard); hooks::initialize()?; diff --git a/hook_modules/hook_lib/Cargo.toml b/hook_modules/hook_lib/Cargo.toml new file mode 100644 index 0000000..8d88ac4 --- /dev/null +++ b/hook_modules/hook_lib/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "hook_lib" +repository.workspace = true +authors.workspace = true +license.workspace = true +version.workspace = true +edition.workspace = true + +[dependencies] +bitflags = "2.6.0" +element-ptr = "0.0.2" +hook_resolvers = { version = "0.2.10", path = "../../hook_resolvers" } +mint_lib = { version = "0.2.10", path = "../../mint_lib" } +nalgebra.workspace = true +widestring = "1.1.0" diff --git a/hook_modules/hook_lib/src/lib.rs b/hook_modules/hook_lib/src/lib.rs new file mode 100644 index 0000000..21b6985 --- /dev/null +++ b/hook_modules/hook_lib/src/lib.rs @@ -0,0 +1,95 @@ +use std::path::PathBuf; + +use mint_lib::mod_info::Meta; + +pub mod ue; +pub mod util; + +static mut GLOBALS: Option<&'static Globals> = None; + +pub fn init_globals(globals: &'static Globals) { + unsafe { GLOBALS = Some(globals) } +} + +pub fn globals() -> &'static Globals { + unsafe { GLOBALS.unwrap() } +} + +// TODO move these type definitions to a more logical place +#[repr(C)] +pub struct USaveGame; + +pub type FnSaveGameToMemory = + unsafe extern "system" fn(*const USaveGame, *mut ue::TArray) -> bool; +pub type FnLoadGameFromMemory = + unsafe extern "system" fn(*const ue::TArray) -> *const USaveGame; + +pub struct Globals { + pub resolution: hook_resolvers::HookResolution, + pub meta: Meta, + pub bin_dir: Option, +} +impl Globals { + pub fn gmalloc(&self) -> &ue::FMalloc { + unsafe { + &**(self.resolution.core.as_ref().unwrap().gmalloc.0 as *const *const ue::FMalloc) + } + } + pub fn fframe_step(&self) -> ue::FnFFrameStep { + unsafe { std::mem::transmute(self.resolution.core.as_ref().unwrap().fframe_step.0) } + } + pub fn fframe_step_explicit_property(&self) -> ue::FnFFrameStepExplicitProperty { + unsafe { + std::mem::transmute( + self.resolution + .core + .as_ref() + .unwrap() + .fframe_step_explicit_property + .0, + ) + } + } + 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( + self.resolution + .core + .as_ref() + .unwrap() + .uobject_base_utility_get_path_name + .0, + ) + } + } + pub fn save_game_to_memory(&self) -> FnSaveGameToMemory { + unsafe { + std::mem::transmute( + self.resolution + .save_game + .as_ref() + .unwrap() + .save_game_to_memory + .0, + ) + } + } + pub fn load_game_from_memory(&self) -> FnLoadGameFromMemory { + unsafe { + std::mem::transmute( + self.resolution + .save_game + .as_ref() + .unwrap() + .load_game_from_memory + .0, + ) + } + } +} diff --git a/hook/src/ue/array.rs b/hook_modules/hook_lib/src/ue/array.rs similarity index 80% rename from hook/src/ue/array.rs rename to hook_modules/hook_lib/src/ue/array.rs index cca04df..db5b00a 100644 --- a/hook/src/ue/array.rs +++ b/hook_modules/hook_lib/src/ue/array.rs @@ -34,9 +34,9 @@ impl Drop for TArray { std::ptr::drop_in_place(std::ptr::slice_from_raw_parts_mut( self.data, self.num as usize, - )) + )); + globals().gmalloc().free(self.data.cast()); } - globals().gmalloc().free(self.data.cast()); } } impl Default for TArray { @@ -50,13 +50,15 @@ impl Default for TArray { } impl TArray { pub fn with_capacity(capacity: usize) -> Self { - Self { - data: globals().gmalloc().malloc( - capacity * std::mem::size_of::(), - std::mem::align_of::() as u32, - ) as *mut _, - num: 0, - max: capacity as i32, + unsafe { + Self { + data: globals().gmalloc().malloc( + capacity * std::mem::size_of::(), + std::mem::align_of::() as u32, + ) as *mut _, + num: 0, + max: capacity as i32, + } } } pub fn len(&self) -> usize { @@ -93,11 +95,13 @@ impl TArray { pub fn reserve(&mut self, additional: usize) { if self.num + additional as i32 >= self.max { self.max = u32::next_power_of_two((self.max + additional as i32) as u32) as i32; - let new = globals().gmalloc().realloc( - self.data as *mut c_void, - self.max as usize * std::mem::size_of::(), - std::mem::align_of::() as u32, - ) as *mut _; + let new = unsafe { + globals().gmalloc().realloc( + self.data as *mut c_void, + self.max as usize * std::mem::size_of::(), + std::mem::align_of::() as u32, + ) as *mut _ + }; self.data = new; } } diff --git a/hook_modules/hook_lib/src/ue/debug_drawing.rs b/hook_modules/hook_lib/src/ue/debug_drawing.rs new file mode 100644 index 0000000..5b322cd --- /dev/null +++ b/hook_modules/hook_lib/src/ue/debug_drawing.rs @@ -0,0 +1,925 @@ +use crate::util::NN as _; + +use crate::ue::{self, *}; +use element_ptr::element_ptr; +use na::Point3; +use nalgebra::{Matrix, Matrix4, Vector3}; +use std::ptr::NonNull; + +use nalgebra as na; + +pub unsafe fn get_batcher(world: NonNull, duration: f32) -> NonNull { + if duration > 0. { + element_ptr!(world => .persistent_line_batcher.*) + } else { + element_ptr!(world => .line_batcher.*) + } + .nn() + .unwrap() +} + +pub fn draw_box( + lines: &mut Vec, + center: FVector, + extent: FVector, + color: FLinearColor, +) { + DebugBox { + center, + extent, + color, + rotation: FRotator { + pitch: 0., + yaw: 0., + roll: 0., + }, + duration: 0., + thickness: 2., + } + .draw(lines); +} + +pub fn draw_lines(batcher: &mut ULineBatchComponent, lines: &[FBatchedLine]) { + if let Some((last, lines)) = lines.split_last() { + batcher.batched_lines.extend_from_slice(lines); + + // call draw_line directly on last element so it gets properly marked as dirty + let draw_line = batcher.vftable.draw_line; + draw_line( + batcher, + &last.start, + &last.end, + &last.color, + last.depth_priority, + last.thickness, + last.remaining_life_time, + ); + } +} + +pub fn draw_points(batcher: &mut ULineBatchComponent, points: &[FBatchedPoint]) { + if let Some((last, points)) = points.split_last() { + batcher.batched_points.extend_from_slice(points); + + // call draw_point directly on last element so it gets properly marked as dirty + let draw_point = batcher.vftable.draw_point; + draw_point( + batcher, + &last.position, + &last.color, + last.point_size, + last.depth_priority, + last.remaining_life_time, + ); + } +} + +unsafe extern "system" fn exec_draw_debug_line( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let world_context: Option> = stack.arg(); + let start: FVector = stack.arg(); + let end: FVector = stack.arg(); + let color: FLinearColor = stack.arg(); + let duration: f32 = stack.arg(); + let thickness: f32 = stack.arg(); + + if let Some(world) = get_world(world_context) { + let batcher = get_batcher(world, duration).as_mut(); + let f = batcher.vftable.draw_line; + f(batcher, &start, &end, &color, 0, thickness, duration); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +unsafe extern "system" fn exec_draw_debug_point( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let world_context: Option> = stack.arg(); + let position: FVector = stack.arg(); + let size: f32 = stack.arg(); + let color: FLinearColor = stack.arg(); + let duration: f32 = stack.arg(); + + if let Some(world) = get_world(world_context) { + let batcher = get_batcher(world, duration).as_mut(); + let f = batcher.vftable.draw_point; + f(batcher, &position, &color, size, 0, duration); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +unsafe extern "system" fn exec_draw_debug_circle( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let world_context: Option> = stack.arg(); + let center: FVector = stack.arg(); + let radius: f32 = stack.arg(); + let num_segments: u32 = stack.arg(); + let color: FLinearColor = stack.arg(); + let duration: f32 = stack.arg(); + let thickness: f32 = stack.arg(); + let y_axis: FVector = stack.arg(); + let z_axis: FVector = stack.arg(); + let draw_axis: bool = stack.arg(); + + if let Some(world) = get_world(world_context) { + let batcher = get_batcher(world, duration).as_mut(); + + let line_config = FBatchedLine { + color, + remaining_life_time: duration, + thickness, + ..Default::default() + }; + + let mut tm = Matrix4::identity(); + tm.fixed_view_mut::<3, 1>(0, 3).copy_from(¢er.into()); + + let x_axis = Vector3::new(1.0, 0.0, 0.0); + tm.fixed_view_mut::<3, 1>(0, 0).copy_from(&x_axis); + tm.fixed_view_mut::<3, 1>(0, 1).copy_from(&y_axis.into()); + tm.fixed_view_mut::<3, 1>(0, 2).copy_from(&z_axis.into()); + + let mut segments = num_segments.max(4); + let angle_step = 2.0 * std::f32::consts::PI / segments as f32; + + let center = get_origin(&tm); + let axis_y = Vector3::new(tm[(0, 1)], tm[(1, 1)], tm[(2, 1)]); + let axis_z = Vector3::new(tm[(0, 2)], tm[(1, 2)], tm[(2, 2)]); + + let mut lines = Vec::with_capacity(segments as usize); + + let mut angle: f32 = 0.0; + while segments > 0 { + let vertex1 = center + radius * (axis_y * angle.cos() + axis_z * angle.sin()); + angle += angle_step; + let vertex2 = center + radius * (axis_y * angle.cos() + axis_z * angle.sin()); + lines.push(FBatchedLine { + start: vertex1.into(), + end: vertex2.into(), + ..line_config + }); + segments -= 1; + } + + if draw_axis { + lines.push(FBatchedLine { + start: (center - radius * axis_y).into(), + end: (center + radius * axis_y).into(), + ..line_config + }); + lines.push(FBatchedLine { + start: (center - radius * axis_z).into(), + end: (center + radius * axis_z).into(), + ..line_config + }); + } + draw_lines(batcher, &lines); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +unsafe extern "system" fn exec_draw_debug_sphere( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let world_context: Option> = stack.arg(); + let shape = DebugSphere { + center: stack.arg(), + radius: stack.arg(), + num_segments: stack.arg(), + color: stack.arg(), + duration: stack.arg(), + thickness: stack.arg(), + }; + + if let Some(world) = get_world(world_context) { + let batcher = get_batcher(world, shape.duration).as_mut(); + let mut lines = vec![]; + shape.draw(&mut lines); + draw_lines(batcher, &lines); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +fn find_best_axis_vectors(direction: &Vector3) -> (Vector3, Vector3) { + let nx = direction.x.abs(); + let ny = direction.y.abs(); + let nz = direction.z.abs(); + + let axis1 = if nz > nx && nz > ny { + Vector3::new(1., 0., 0.) + } else { + Vector3::new(0., 0., 1.) + }; + + let tmp = axis1 - direction * direction.dot(&axis1); + let axis1_normalized = tmp.normalize(); + + (axis1_normalized, axis1_normalized.cross(direction)) +} + +fn get_origin( + matrix: &Matrix, nalgebra::Const<4>, na::ArrayStorage>, +) -> Vector3 { + Vector3::new(matrix[(0, 3)], matrix[(1, 3)], matrix[(2, 3)]) +} + +fn add_half_circle( + lines: &mut Vec, + base: &Vector3, + x: &Vector3, + y: &Vector3, + color: &FLinearColor, + radius: f32, + num_sides: i32, + life_time: f32, + depth_priority: u8, + thickness: f32, +) { + let num_sides = num_sides.max(2); + let angle_delta = 2.0 * std::f32::consts::PI / num_sides as f32; + let mut last_vertex = base + x * radius; + + for side_index in 0..(num_sides / 2) { + let i = (side_index + 1) as f32; + let vertex = base + (x * (angle_delta * i).cos() + y * (angle_delta * i).sin()) * radius; + lines.push(FBatchedLine { + start: last_vertex.into(), + end: vertex.into(), + color: *color, + remaining_life_time: life_time, + thickness, + depth_priority, + }); + last_vertex = vertex; + } +} + +fn add_circle( + lines: &mut Vec, + base: &Vector3, + x: &Vector3, + y: &Vector3, + color: &FLinearColor, + radius: f32, + num_sides: i32, + life_time: f32, + depth_priority: u8, + thickness: f32, +) { + let num_sides = num_sides.max(2); + let angle_delta = 2.0 * std::f32::consts::PI / num_sides as f32; + let mut last_vertex = base + x * radius; + + for side_index in 0..num_sides { + let i = (side_index + 1) as f32; + let vertex = base + (x * (angle_delta * i).cos() + y * (angle_delta * i).sin()) * radius; + lines.push(FBatchedLine { + start: last_vertex.into(), + end: vertex.into(), + color: *color, + remaining_life_time: life_time, + thickness, + depth_priority, + }); + last_vertex = vertex; + } +} + +fn draw_cone( + batcher: &mut ULineBatchComponent, + origin: FVector, + direction: FVector, + length: f32, + angle_width: f32, + angle_height: f32, + num_sides: u32, + color: FLinearColor, + duration: f32, + thickness: f32, +) { + let line_config = FBatchedLine { + color, + remaining_life_time: duration, + thickness, + ..Default::default() + }; + + let origin: Vector3 = origin.into(); + let direction: Vector3 = direction.into(); + + let num_sides = num_sides.max(4) as usize; + + let angle1 = angle_height.clamp(f32::EPSILON, std::f32::consts::PI - f32::EPSILON); + let angle2 = angle_width.clamp(f32::EPSILON, std::f32::consts::PI - f32::EPSILON); + + let sin_x_2 = (0.5 * angle1).sin(); + let sin_y_2 = (0.5 * angle2).sin(); + + let sin_sq_x_2 = sin_x_2 * sin_x_2; + let sin_sq_y_2 = sin_y_2 * sin_y_2; + + let mut cone_verts = Vec::with_capacity(num_sides); + + for i in 0..num_sides { + let fraction = i as f32 / num_sides as f32; + let thi = 2.0 * std::f32::consts::PI * fraction; + let phi = (thi.sin() * sin_y_2).atan2(thi.cos() * sin_x_2); + let sin_phi = phi.sin(); + let cos_phi = phi.cos(); + let sin_sq_phi = sin_phi * sin_phi; + let cos_sq_phi = cos_phi * cos_phi; + + let r_sq = sin_sq_x_2 * sin_sq_y_2 / (sin_sq_x_2 * sin_sq_phi + sin_sq_y_2 * cos_sq_phi); + let r = r_sq.sqrt(); + let sqr = (1.0 - r_sq).sqrt(); + let alpha = r * cos_phi; + let beta = r * sin_phi; + + cone_verts.push(Vector3::new( + 1.0 - 2.0 * r_sq, + 2.0 * sqr * alpha, + 2.0 * sqr * beta, + )); + } + + let direction_norm = direction.normalize(); + let (y_axis, z_axis) = find_best_axis_vectors(&direction_norm); + let cone_to_world = Matrix4::from_columns(&[ + direction_norm.push(0.), + y_axis.push(0.), + z_axis.push(0.), + origin.push(1.), + ]) * Matrix4::new_scaling(length); + + let mut lines = vec![]; + + let mut current_point = Vector3::zeros(); + let mut prev_point = Vector3::zeros(); + let mut first_point = Vector3::zeros(); + for (i, vert) in cone_verts.iter().enumerate().take(num_sides) { + current_point = cone_to_world.transform_point(&(*vert).into()).coords; + lines.push(FBatchedLine { + start: get_origin(&cone_to_world).into(), + end: current_point.into(), + ..line_config + }); + + if i > 0 { + lines.push(FBatchedLine { + start: prev_point.into(), + end: current_point.into(), + ..line_config + }); + } else { + first_point = current_point; + } + + prev_point = current_point; + } + lines.push(FBatchedLine { + start: current_point.into(), + end: first_point.into(), + ..line_config + }); + + draw_lines(batcher, &lines); +} + +unsafe extern "system" fn exec_draw_debug_cone( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let world_context: Option> = stack.arg(); + let origin: FVector = stack.arg(); + let direction: FVector = stack.arg(); + let length: f32 = stack.arg(); + let angle_width: f32 = stack.arg(); + let angle_height: f32 = stack.arg(); + let num_sides: u32 = stack.arg(); + let color: FLinearColor = stack.arg(); + let duration: f32 = stack.arg(); + let thickness: f32 = stack.arg(); + + if let Some(world) = get_world(world_context) { + let batcher = get_batcher(world, duration).as_mut(); + draw_cone( + batcher, + origin, + direction, + length, + angle_width, + angle_height, + num_sides, + color, + duration, + thickness, + ); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} +unsafe extern "system" fn exec_draw_debug_cone_in_degrees( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let world_context: Option> = stack.arg(); + let origin: FVector = stack.arg(); + let direction: FVector = stack.arg(); + let length: f32 = stack.arg(); + let angle_width: f32 = stack.arg(); + let angle_height: f32 = stack.arg(); + let num_sides: u32 = stack.arg(); + let color: FLinearColor = stack.arg(); + let duration: f32 = stack.arg(); + let thickness: f32 = stack.arg(); + + if let Some(world) = get_world(world_context) { + let batcher = get_batcher(world, duration).as_mut(); + draw_cone( + batcher, + origin, + direction, + length, + angle_width.to_radians(), + angle_height.to_radians(), + num_sides, + color, + duration, + thickness, + ); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +unsafe extern "system" fn exec_draw_debug_cylinder( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let world_context: Option> = stack.arg(); + let start: FVector = stack.arg(); + let end: FVector = stack.arg(); + let radius: f32 = stack.arg(); + let segments: u32 = stack.arg(); + let color: FLinearColor = stack.arg(); + let duration: f32 = stack.arg(); + let thickness: f32 = stack.arg(); + + if let Some(world) = get_world(world_context) { + let batcher = get_batcher(world, duration).as_mut(); + + let line_config = FBatchedLine { + color, + remaining_life_time: duration, + thickness, + ..Default::default() + }; + + let mut segments = segments.max(4); + + let end: Vector3 = end.into(); + let start: Vector3 = start.into(); + + let angle_inc = 360.0 / segments as f32; + let mut angle = angle_inc; + + let mut axis = (end - start).normalize(); + if axis == Vector3::zeros() { + axis = Vector3::new(0.0, 0.0, 1.0); + } + + let (perpendicular, _) = find_best_axis_vectors(&axis); + + let offset = perpendicular * radius; + + let mut p1 = start + offset; + let mut p3 = end + offset; + + let mut lines = vec![]; + while segments > 0 { + let rotation = + na::Rotation3::from_axis_angle(&na::Unit::new_normalize(axis), angle.to_radians()); + let p2 = start + rotation.transform_vector(&offset); + let p4 = end + rotation.transform_vector(&offset); + + lines.push(FBatchedLine { + start: p2.into(), + end: p4.into(), + ..line_config + }); + lines.push(FBatchedLine { + start: p1.into(), + end: p2.into(), + ..line_config + }); + lines.push(FBatchedLine { + start: p3.into(), + end: p4.into(), + ..line_config + }); + + p1 = p2; + p3 = p4; + angle += angle_inc; + segments -= 1; + } + + draw_lines(batcher, &lines); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +unsafe extern "system" fn exec_draw_debug_capsule( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let world_context: Option> = stack.arg(); + let center: FVector = stack.arg(); + let half_height: f32 = stack.arg(); + let radius: f32 = stack.arg(); + let rotation: FRotator = stack.arg(); + let color: FLinearColor = stack.arg(); + let duration: f32 = stack.arg(); + let thickness: f32 = stack.arg(); + + if let Some(world) = get_world(world_context) { + let batcher = get_batcher(world, duration).as_mut(); + + let line_config = FBatchedLine { + color, + remaining_life_time: duration, + thickness, + ..Default::default() + }; + + let mut lines = vec![]; + + const DRAW_COLLISION_SIDES: i32 = 16; + let origin: Vector3 = center.into(); + let rot = na::Rotation3::from_euler_angles( + rotation.roll.to_radians(), + rotation.pitch.to_radians(), + rotation.yaw.to_radians(), + ); + let axes = rot.matrix(); + + let x_axis = axes.fixed_view::<3, 1>(0, 0).xyz(); + let y_axis = axes.fixed_view::<3, 1>(0, 1).xyz(); + let z_axis = axes.fixed_view::<3, 1>(0, 2).xyz(); + + // Draw top and bottom circles + let half_axis = (half_height - radius).max(1.0); + let top_end = origin + (half_axis * z_axis); + let bottom_end = origin - half_axis * z_axis; + + add_circle( + &mut lines, + &top_end, + &x_axis, + &y_axis, + &color, + radius, + DRAW_COLLISION_SIDES, + duration, + 0, + thickness, + ); + add_circle( + &mut lines, + &bottom_end, + &x_axis, + &y_axis, + &color, + radius, + DRAW_COLLISION_SIDES, + duration, + 0, + thickness, + ); + + // Draw domed caps + add_half_circle( + &mut lines, + &top_end, + &y_axis, + &z_axis, + &color, + radius, + DRAW_COLLISION_SIDES, + duration, + 0, + thickness, + ); + add_half_circle( + &mut lines, + &top_end, + &x_axis, + &z_axis, + &color, + radius, + DRAW_COLLISION_SIDES, + duration, + 0, + thickness, + ); + + let neg_z_axis = -z_axis; + + add_half_circle( + &mut lines, + &bottom_end, + &y_axis, + &neg_z_axis, + &color, + radius, + DRAW_COLLISION_SIDES, + duration, + 0, + thickness, + ); + add_half_circle( + &mut lines, + &bottom_end, + &x_axis, + &neg_z_axis, + &color, + radius, + DRAW_COLLISION_SIDES, + duration, + 0, + thickness, + ); + + // Draw connected lines + lines.push(FBatchedLine { + start: (top_end + radius * x_axis).into(), + end: (bottom_end + radius * x_axis).into(), + ..line_config + }); + lines.push(FBatchedLine { + start: (top_end - radius * x_axis).into(), + end: (bottom_end - radius * x_axis).into(), + ..line_config + }); + lines.push(FBatchedLine { + start: (top_end + radius * y_axis).into(), + end: (bottom_end + radius * y_axis).into(), + ..line_config + }); + lines.push(FBatchedLine { + start: (top_end - radius * y_axis).into(), + end: (bottom_end - radius * y_axis).into(), + ..line_config + }); + + draw_lines(batcher, &lines); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct DebugBox { + pub center: FVector, + pub extent: FVector, + pub color: FLinearColor, + pub rotation: FRotator, + pub duration: f32, + pub thickness: f32, +} +#[derive(Debug, Default, Clone, Copy)] +pub struct DebugSphere { + pub center: FVector, + pub radius: f32, + pub num_segments: u32, + pub color: FLinearColor, + pub duration: f32, + pub thickness: f32, +} +impl DebugBox { + fn draw(&self, lines: &mut Vec) { + let Self { + center, + extent, + color, + rotation, + duration, + thickness, + } = *self; + + let line_config = FBatchedLine { + color, + remaining_life_time: duration, + thickness, + ..Default::default() + }; + + let center: Vector3 = center.into(); + let extent: Vector3 = extent.into(); + + let transform = na::Isometry3::from_parts( + na::Translation3::from(center), + na::Rotation3::from_euler_angles( + rotation.roll.to_radians(), + rotation.pitch.to_radians(), + rotation.yaw.to_radians(), + ) + .into(), + ); + + let half_dimensions: Vector3 = extent * 0.5; + + let vertices = [ + Point3::new(half_dimensions.x, half_dimensions.y, half_dimensions.z), + Point3::new(half_dimensions.x, -half_dimensions.y, half_dimensions.z), + Point3::new(-half_dimensions.x, -half_dimensions.y, half_dimensions.z), + Point3::new(-half_dimensions.x, half_dimensions.y, half_dimensions.z), + Point3::new(half_dimensions.x, half_dimensions.y, -half_dimensions.z), + Point3::new(half_dimensions.x, -half_dimensions.y, -half_dimensions.z), + Point3::new(-half_dimensions.x, -half_dimensions.y, -half_dimensions.z), + Point3::new(-half_dimensions.x, half_dimensions.y, -half_dimensions.z), + Point3::new(half_dimensions.x, half_dimensions.y, half_dimensions.z), + Point3::new(half_dimensions.x, half_dimensions.y, -half_dimensions.z), + Point3::new(half_dimensions.x, -half_dimensions.y, half_dimensions.z), + Point3::new(half_dimensions.x, -half_dimensions.y, -half_dimensions.z), + Point3::new(-half_dimensions.x, -half_dimensions.y, half_dimensions.z), + Point3::new(-half_dimensions.x, -half_dimensions.y, -half_dimensions.z), + Point3::new(-half_dimensions.x, half_dimensions.y, half_dimensions.z), + Point3::new(-half_dimensions.x, half_dimensions.y, -half_dimensions.z), + ]; + + let indices = [ + (0, 1), + (1, 2), + (2, 3), + (3, 0), + (4, 5), + (5, 6), + (6, 7), + (7, 4), + (0, 4), + (1, 5), + (2, 6), + (3, 7), + ]; + + for &(i, j) in &indices { + lines.push(FBatchedLine { + start: (transform * vertices[i]).into(), + end: (transform * vertices[j]).into(), + ..line_config + }); + } + } +} +impl DebugSphere { + fn draw(&self, lines: &mut Vec) { + let line_config = FBatchedLine { + color: self.color, + remaining_life_time: self.duration, + thickness: self.thickness, + ..Default::default() + }; + + let segments = self.num_segments.max(4); + + let angle_inc = 2.0 * std::f32::consts::PI / segments as f32; + let mut num_segments_y = segments; + let mut latitude = angle_inc; + let mut sin_y1 = 0.0; + let mut cos_y1 = 1.0; + let center: Vector3 = self.center.into(); + + while num_segments_y > 0 { + let sin_y2 = latitude.sin(); + let cos_y2 = latitude.cos(); + + let mut vertex1 = Vector3::new(sin_y1, 0.0, cos_y1) * self.radius + center; + let mut vertex3 = Vector3::new(sin_y2, 0.0, cos_y2) * self.radius + center; + let mut longitude = angle_inc; + + let mut num_segments_x = segments; + while num_segments_x > 0 { + let sin_x = longitude.sin(); + let cos_x = longitude.cos(); + + let vertex2 = + Vector3::new(cos_x * sin_y1, sin_x * sin_y1, cos_y1) * self.radius + center; + let vertex4 = + Vector3::new(cos_x * sin_y2, sin_x * sin_y2, cos_y2) * self.radius + center; + + lines.push(FBatchedLine { + start: vertex1.into(), + end: vertex2.into(), + ..line_config + }); + lines.push(FBatchedLine { + start: vertex1.into(), + end: vertex3.into(), + ..line_config + }); + + vertex1 = vertex2; + vertex3 = vertex4; + longitude += angle_inc; + num_segments_x -= 1; + } + + sin_y1 = sin_y2; + cos_y1 = cos_y2; + latitude += angle_inc; + num_segments_y -= 1; + } + } +} + +unsafe extern "system" fn exec_draw_debug_box( + _context: *mut ue::UObject, + stack: *mut ue::kismet::FFrame, + _result: *mut c_void, +) { + let stack = stack.as_mut().unwrap(); + + let world_context: Option> = stack.arg(); + let shape = DebugBox { + center: stack.arg(), + extent: stack.arg(), + color: stack.arg(), + rotation: stack.arg(), + duration: stack.arg(), + thickness: stack.arg(), + }; + + if let Some(world) = get_world(world_context) { + let batcher = get_batcher(world, shape.duration).as_mut(); + let mut lines = vec![]; + shape.draw(&mut lines); + draw_lines(batcher, &lines); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} + +pub fn draw_debug_box(batcher: &mut ULineBatchComponent, shape: &DebugBox) { + let mut lines = vec![]; + shape.draw(&mut lines); + draw_lines(batcher, &lines); +} +pub fn draw_debug_sphere(batcher: &mut ULineBatchComponent, shape: &DebugSphere) { + let mut lines = vec![]; + shape.draw(&mut lines); + draw_lines(batcher, &lines); +} diff --git a/hook/src/ue/kismet.rs b/hook_modules/hook_lib/src/ue/kismet.rs similarity index 100% rename from hook/src/ue/kismet.rs rename to hook_modules/hook_lib/src/ue/kismet.rs diff --git a/hook/src/ue/malloc.rs b/hook_modules/hook_lib/src/ue/malloc.rs similarity index 85% rename from hook/src/ue/malloc.rs rename to hook_modules/hook_lib/src/ue/malloc.rs index 0f9acf3..42d7865 100644 --- a/hook/src/ue/malloc.rs +++ b/hook_modules/hook_lib/src/ue/malloc.rs @@ -8,13 +8,18 @@ pub struct FMalloc { unsafe impl Sync for FMalloc {} unsafe impl Send for FMalloc {} impl FMalloc { - pub fn malloc(&self, count: usize, alignment: u32) -> *mut c_void { + pub unsafe fn malloc(&self, count: usize, alignment: u32) -> *mut c_void { unsafe { ((*self.vtable).malloc)(self, count, alignment) } } - pub fn realloc(&self, original: *mut c_void, count: usize, alignment: u32) -> *mut c_void { + pub unsafe fn realloc( + &self, + original: *mut c_void, + count: usize, + alignment: u32, + ) -> *mut c_void { unsafe { ((*self.vtable).realloc)(self, original, count, alignment) } } - pub fn free(&self, original: *mut c_void) { + pub unsafe fn free(&self, original: *mut c_void) { unsafe { ((*self.vtable).free)(self, original) } } } diff --git a/hook/src/ue/map.rs b/hook_modules/hook_lib/src/ue/map.rs similarity index 100% rename from hook/src/ue/map.rs rename to hook_modules/hook_lib/src/ue/map.rs diff --git a/hook/src/ue/mod.rs b/hook_modules/hook_lib/src/ue/mod.rs similarity index 97% rename from hook/src/ue/mod.rs rename to hook_modules/hook_lib/src/ue/mod.rs index 07c4f2f..5ce964c 100644 --- a/hook/src/ue/mod.rs +++ b/hook_modules/hook_lib/src/ue/mod.rs @@ -1,4 +1,5 @@ mod array; +mod debug_drawing; pub mod kismet; mod malloc; mod map; @@ -8,6 +9,7 @@ mod string; mod world; pub use array::*; +pub use debug_drawing::*; pub use malloc::*; pub use map::*; pub use name::*; diff --git a/hook/src/ue/name.rs b/hook_modules/hook_lib/src/ue/name.rs similarity index 100% rename from hook/src/ue/name.rs rename to hook_modules/hook_lib/src/ue/name.rs diff --git a/hook/src/ue/object.rs b/hook_modules/hook_lib/src/ue/object.rs similarity index 100% rename from hook/src/ue/object.rs rename to hook_modules/hook_lib/src/ue/object.rs diff --git a/hook/src/ue/string.rs b/hook_modules/hook_lib/src/ue/string.rs similarity index 100% rename from hook/src/ue/string.rs rename to hook_modules/hook_lib/src/ue/string.rs diff --git a/hook/src/ue/world.rs b/hook_modules/hook_lib/src/ue/world.rs similarity index 100% rename from hook/src/ue/world.rs rename to hook_modules/hook_lib/src/ue/world.rs diff --git a/hook/src/util.rs b/hook_modules/hook_lib/src/util.rs similarity index 100% rename from hook/src/util.rs rename to hook_modules/hook_lib/src/util.rs diff --git a/hook_modules/hook_test/Cargo.toml b/hook_modules/hook_test/Cargo.toml new file mode 100644 index 0000000..eb99ca0 --- /dev/null +++ b/hook_modules/hook_test/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "hook_test" +repository.workspace = true +authors.workspace = true +license.workspace = true +version.workspace = true +edition.workspace = true + +[dependencies] +hook_lib = { version = "0.2.10", path = "../hook_lib" } +hot-lib-reloader = "0.7.0" + +[lib] +crate-type = ["rlib", "cdylib"] diff --git a/hook_modules/hook_test/src/lib.rs b/hook_modules/hook_test/src/lib.rs new file mode 100644 index 0000000..77fd00d --- /dev/null +++ b/hook_modules/hook_test/src/lib.rs @@ -0,0 +1,67 @@ +use std::f32::consts::PI; + +use hook_lib::{ue::*, util::NN as _}; + +#[no_mangle] +pub fn tick(globals: &'static hook_lib::Globals, context: *mut hook_lib::ue::UObject) { + hook_lib::init_globals(globals); + + unsafe { + let Some(world) = get_world(context.nn()) else { + return; + }; + + let batcher = get_batcher(world, 0.).as_mut(); + + const RED: FLinearColor = FLinearColor { + r: 1., + g: 0., + b: 0., + a: 0., + }; + const GREEN: FLinearColor = FLinearColor { + r: 0., + g: 1., + b: 0., + a: 0., + }; + const BLUE: FLinearColor = FLinearColor { + r: 0., + g: 0., + b: 1., + a: 0., + }; + + let n = 20; + let r = 1000.; + for i in 0..n { + let a = (i as f32 / n as f32) * PI * 2.; + #[rustfmt::skip] + let shape = DebugBox { + center: FVector { x: r * a.sin(), y: r * a.cos(), z: 0., }, + extent: FVector { x: 100., y: 100., z: 800., }, + color: FLinearColor { r: 1., g: 1., b: 0., a: 0. }, + rotation: FRotator { pitch: 0., yaw: 0., roll: 0. }, + duration: 0., + thickness: 10., + }; + //draw_debug_box(batcher, &shape); + } + #[rustfmt::skip] + let shape = DebugSphere { + center: FVector { x: 100., y: 100., z: 100., }, + radius: 100., + num_segments: 10, + color: GREEN, + duration: 0., + thickness: 1., + }; + //draw_debug_sphere(batcher, &shape); + + //let obj = &*context; + //println!( + // "ayy?? {:X?}", + // context, + //); + } +} diff --git a/hook_resolvers/src/lib.rs b/hook_resolvers/src/lib.rs index f691fd8..21e1c65 100644 --- a/hook_resolvers/src/lib.rs +++ b/hook_resolvers/src/lib.rs @@ -1,7 +1,7 @@ use patternsleuth::resolvers::futures::future::join_all; use patternsleuth::resolvers::unreal::blueprint_library::UFunctionBind; use patternsleuth::resolvers::unreal::fname::{FNameCtorWchar, FNameToString}; -use patternsleuth::resolvers::unreal::game_loop::Main; +use patternsleuth::resolvers::unreal::game_loop::{FEngineLoopTick, Main}; use patternsleuth::resolvers::unreal::gmalloc::GMalloc; use patternsleuth::resolvers::unreal::kismet::{FFrameStep, FFrameStepExplicitProperty}; use patternsleuth::resolvers::unreal::pak::FPakPlatformFileInitialize; @@ -205,6 +205,7 @@ impl_try_collector! { pub struct CoreResolution { pub gmalloc: GMalloc, pub main: Main, + pub fengine_loop_tick: FEngineLoopTick, pub fnametostring: FNameToString, pub fname_ctor_wchar: FNameCtorWchar, pub uobject_base_utility_get_path_name: UObjectBaseUtilityGetPathName,