diff --git a/Cargo.lock b/Cargo.lock index 516839f..9388490 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,6 +276,15 @@ dependencies = [ "backtrace", ] +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arbitrary" version = "1.3.2" @@ -1677,6 +1686,27 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "element-ptr" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91596eb9a72e0cb16e4993a79fcc190511c20bc34cf2c06aad78577611846c31" +dependencies = [ + "element-ptr-macro", +] + +[[package]] +name = "element-ptr-macro" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e5a8d0b3bcad37af3dde0bf059f49f50fd0c4b8930aa4d5bde4cb4010c7e9b" +dependencies = [ + "proc-macro-crate 3.2.0", + "proc-macro2", + "quote", + "syn 2.0.86", +] + [[package]] name = "emath" version = "0.29.1" @@ -2449,9 +2479,11 @@ version = "0.2.10" dependencies = [ "anyhow", "bitflags 2.6.0", + "element-ptr", "fs-err", "hook_resolvers", "mint_lib", + "nalgebra", "patternsleuth", "postcard", "proxy_dll", @@ -3048,6 +3080,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.4" @@ -3321,6 +3363,33 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fce7b49e1e6d8aa67232ef1c4c936c0af58756eb2db6f65c40bacb39035e7f42" +[[package]] +name = "nalgebra" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.86", +] + [[package]] name = "ndk" version = "0.9.0" @@ -3409,17 +3478,45 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -3448,7 +3545,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.86", @@ -3460,7 +3557,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.86", @@ -4147,7 +4244,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit 0.22.22", ] [[package]] @@ -4262,7 +4368,7 @@ dependencies = [ "once_cell", "socket2 0.5.5", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4316,6 +4422,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.10.0" @@ -4754,6 +4866,15 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "safe_arch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -4990,6 +5111,19 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simba" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -5536,9 +5670,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" @@ -5548,7 +5682,18 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.6.0", "toml_datetime", - "winnow", + "winnow 0.5.28", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.6.0", + "toml_datetime", + "winnow 0.6.20", ] [[package]] @@ -6354,6 +6499,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wide" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "widestring" version = "1.1.0" @@ -6831,6 +6986,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -7048,7 +7212,7 @@ version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2207eb71efebda17221a579ca78b45c4c5f116f074eb745c3a172e688ccf89f5" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "regex", @@ -7062,7 +7226,7 @@ version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7a3e850ff1e7217a3b7a07eba90d37fe9bb9e89a310f718afcde5885ca9b6d7" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "regex", @@ -7244,7 +7408,7 @@ version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0277758a8a0afc0e573e80ed5bfd9d9c2b48bd3108ffe09384f9f738c83f4a55" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -7257,7 +7421,7 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72a5857e2856435331636a9fbb415b09243df4521a267c5bedcd5289b4d5799e" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", diff --git a/hook/Cargo.toml b/hook/Cargo.toml index 99e29af..3ca7cea 100644 --- a/hook/Cargo.toml +++ b/hook/Cargo.toml @@ -34,3 +34,5 @@ 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" diff --git a/hook/src/hooks/debug_drawing.rs b/hook/src/hooks/debug_drawing.rs new file mode 100644 index 0000000..ae86d5d --- /dev/null +++ b/hook/src/hooks/debug_drawing.rs @@ -0,0 +1,933 @@ +use std::ffi::c_void; +use std::ptr::NonNull; + +use element_ptr::element_ptr; +use na::{Matrix, Matrix4, Point3, Vector3}; +use nalgebra as na; + +use crate::hooks::ExecFn; +use crate::ue::{self, FLinearColor, FRotator, FVector}; + +pub fn kismet_hooks() -> &'static [(&'static str, ExecFn)] { + &[ + ( + "/Script/Engine.KismetSystemLibrary:DrawDebugLine", + exec_draw_debug_line as ExecFn, + ), + ( + "/Script/Engine.KismetSystemLibrary:DrawDebugCircle", + exec_draw_debug_circle as ExecFn, + ), + ( + "/Script/Engine.KismetSystemLibrary:DrawDebugSphere", + exec_draw_debug_sphere as ExecFn, + ), + ( + "/Script/Engine.KismetSystemLibrary:DrawDebugCone", + exec_draw_debug_cone as ExecFn, + ), + ( + "/Script/Engine.KismetSystemLibrary:DrawDebugConeInDegrees", + exec_draw_debug_cone_in_degrees as ExecFn, + ), + ( + "/Script/Engine.KismetSystemLibrary:DrawDebugCylinder", + exec_draw_debug_cylinder as ExecFn, + ), + ( + "/Script/Engine.KismetSystemLibrary:DrawDebugCapsule", + exec_draw_debug_capsule as ExecFn, + ), + ( + "/Script/Engine.KismetSystemLibrary:DrawDebugBox", + exec_draw_debug_box as ExecFn, + ), + ] +} +#[repr(C)] +struct UWorld { + object: ue::UObject, + network_notify: *const (), + persistent_level: *const (), // ULevel + net_driver: *const (), // UNetDriver + line_batcher: *const ULineBatchComponent, + persistent_line_batcher: *const ULineBatchComponent, + foreground_line_batcher: *const ULineBatchComponent, +} + +#[repr(C)] +struct ULineBatchComponent { + vftable: *const ULineBatchComponentVTable, + // lots more +} + +#[repr(C)] +#[rustfmt::skip] +struct ULineBatchComponentVTable { + padding: [*const (); 0x110], + draw_line: unsafe extern "system" fn(this: NonNull, start: &FVector, end: &FVector, color: &FLinearColor, depth_priority: u8, life_time: f32, thickness: f32), + draw_point: unsafe extern "system" fn(this: NonNull, position: &FVector, color: &FLinearColor, point_size: f32, depth_priority: u8, life_time: f32), +} + +#[derive(Debug, Default, Copy, Clone)] +#[repr(C)] +struct FBatchedLine { + start: FVector, + end: FVector, + color: FLinearColor, + life_time: f32, + thickness: f32, + depth_priority: u8, + batch_id: u32, +} + +unsafe fn draw_lines(batcher: NonNull, lines: &[FBatchedLine]) { + let draw_line = element_ptr!(batcher => .vftable.*.draw_line.*); + for line in lines { + draw_line( + batcher, + &line.start, + &line.end, + &line.color, + line.depth_priority, + line.thickness, + line.life_time, + ); + } +} + +trait NN { + fn nn(self) -> Option>; +} +impl NN for *const T { + fn nn(self) -> Option> { + NonNull::new(self.cast_mut()) + } +} +impl NN for *mut T { + fn nn(self) -> Option> { + NonNull::new(self) + } +} +trait CastOptionNN { + fn cast(self) -> Option>; +} +impl CastOptionNN for Option> { + fn cast(self) -> Option> { + self.map(|s| s.cast()) + } +} + +unsafe fn get_world(mut ctx: Option>) -> Option> { + loop { + let Some(outer) = ctx else { + break; + }; + let class = element_ptr!(outer => .uobject_base_utility.uobject_base.class_private.*).nn(); + if let Some(class) = class { + if "/Script/Engine.World" + == element_ptr!(class => + .ustruct + .ufield + .uobject + .uobject_base_utility + .uobject_base.*) + .get_path_name(None) + { + break; + } + } + ctx = element_ptr!(outer => .uobject_base_utility.uobject_base.outer_private.*).nn(); + } + ctx.cast() +} + +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 = element_ptr!(world => .line_batcher.*).nn().unwrap(); + let f = element_ptr!(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_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 = element_ptr!(world => .line_batcher.*).nn().unwrap(); + + let line_config = FBatchedLine { + color, + 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 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(); + + if let Some(world) = get_world(world_context) { + let batcher = element_ptr!(world => .line_batcher.*).nn().unwrap(); + + let line_config = FBatchedLine { + color, + life_time: duration, + thickness, + ..Default::default() + }; + + let segments = 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 = center.into(); + + let mut lines = Vec::new(); + lines.reserve(num_segments_y as usize * segments as usize * 2); + + 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) * radius + center; + let mut vertex3 = Vector3::new(sin_y2, 0.0, cos_y2) * 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) * radius + center; + let vertex4 = + Vector3::new(cos_x * sin_y2, sin_x * sin_y2, cos_y2) * 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; + } + + 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, + batch_id: u32, +) { + 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, + life_time, + thickness, + depth_priority, + batch_id, + }); + 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, + batch_id: u32, +) { + 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, + life_time, + thickness, + depth_priority, + batch_id, + }); + last_vertex = vertex; + } +} + +unsafe fn draw_cone( + batcher: NonNull, + 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, + 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(std::f32::EPSILON, std::f32::consts::PI - std::f32::EPSILON); + let angle2 = angle_width.clamp(std::f32::EPSILON, std::f32::consts::PI - std::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 as usize); + + 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 in 0..num_sides { + current_point = cone_to_world.transform_point(&cone_verts[i].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 = element_ptr!(world => .line_batcher.*).nn().unwrap(); + 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 = element_ptr!(world => .line_batcher.*).nn().unwrap(); + 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 = element_ptr!(world => .line_batcher.*).nn().unwrap(); + + let line_config = FBatchedLine { + color, + 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 = element_ptr!(world => .line_batcher.*).nn().unwrap(); + + let line_config = FBatchedLine { + color, + 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, + 0, + ); + add_circle( + &mut lines, + &bottom_end, + &x_axis, + &y_axis, + &color, + radius, + DRAW_COLLISION_SIDES, + duration, + 0, + thickness, + 0, + ); + + // Draw domed caps + add_half_circle( + &mut lines, + &top_end, + &y_axis, + &z_axis, + &color, + radius, + DRAW_COLLISION_SIDES, + duration, + 0, + thickness, + 0, + ); + add_half_circle( + &mut lines, + &top_end, + &x_axis, + &z_axis, + &color, + radius, + DRAW_COLLISION_SIDES, + duration, + 0, + thickness, + 0, + ); + + 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, + 0, + ); + add_half_circle( + &mut lines, + &bottom_end, + &x_axis, + &neg_z_axis, + &color, + radius, + DRAW_COLLISION_SIDES, + duration, + 0, + thickness, + 0, + ); + + // 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); + } +} + +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 center: FVector = stack.arg(); + let extent: FVector = stack.arg(); + let color: FLinearColor = stack.arg(); + let rotation: FRotator = stack.arg(); + let duration: f32 = stack.arg(); + let thickness: f32 = stack.arg(); + + if let Some(world) = get_world(world_context) { + let batcher = element_ptr!(world => .line_batcher.*).nn().unwrap(); + + let line_config = FBatchedLine { + color, + life_time: duration, + thickness, + ..Default::default() + }; + + let mut lines = vec![]; + + 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 + }); + } + + draw_lines(batcher, &lines); + } + + if !stack.code.is_null() { + stack.code = stack.code.add(1); + } +} diff --git a/hook/src/hooks/mod.rs b/hook/src/hooks/mod.rs index 2f99136..a09318e 100644 --- a/hook/src/hooks/mod.rs +++ b/hook/src/hooks/mod.rs @@ -1,5 +1,6 @@ #![allow(clippy::missing_transmute_annotations)] +mod debug_drawing; mod server_list; use std::{ @@ -15,11 +16,7 @@ use hook_resolvers::GasFixResolution; use mint_lib::DRGInstallationType; use windows::Win32::System::Memory::{VirtualProtect, PAGE_EXECUTE_READWRITE}; -use crate::{ - globals, - ue::{self, FLinearColor, UObject}, - LOG_GUARD, -}; +use crate::{globals, ue, LOG_GUARD}; retour::static_detour! { static HookUFunctionBind: unsafe extern "system" fn(*mut ue::UFunction); @@ -38,7 +35,6 @@ 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<()> { @@ -54,6 +50,7 @@ pub unsafe fn initialize() -> Result<()> { ] .iter() .chain(server_list::kismet_hooks().iter()) + .chain(debug_drawing::kismet_hooks().iter()) .cloned() .collect::>(); @@ -336,11 +333,11 @@ unsafe extern "system" fn exec_print_string( ) { let stack = stack.as_mut().unwrap(); - let _ctx: Option> = stack.arg(); + let _ctx: Option> = stack.arg(); let string: ue::FString = stack.arg(); let _print_to_screen: bool = stack.arg(); let _print_to_log: bool = stack.arg(); - let _color: FLinearColor = stack.arg(); + let _color: ue::FLinearColor = stack.arg(); let _duration: f32 = stack.arg(); println!("PrintString({string})"); diff --git a/hook/src/ue/mod.rs b/hook/src/ue/mod.rs index 121bed8..1408a33 100644 --- a/hook/src/ue/mod.rs +++ b/hook/src/ue/mod.rs @@ -30,15 +30,51 @@ pub type FnFNameCtorWchar = unsafe extern "system" fn(&mut FName, *const u16, EF pub type FnUObjectBaseUtilityGetPathName = unsafe extern "system" fn(&UObjectBase, Option<&UObject>, &mut FString); -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone, Copy)] #[repr(C)] pub struct FVector { pub x: f32, pub y: f32, pub z: f32, } +impl From for nalgebra::Vector3 { + fn from(val: FVector) -> Self { + nalgebra::Vector3::new(val.x, val.y, val.z) + } +} +impl From> for FVector { + fn from(value: nalgebra::Vector3) -> Self { + Self { + x: value.x, + y: value.y, + z: value.z, + } + } +} +impl From for nalgebra::Point3 { + fn from(val: FVector) -> Self { + nalgebra::Point3::new(val.x, val.y, val.z) + } +} +impl From> for FVector { + fn from(value: nalgebra::Point3) -> Self { + Self { + x: value.x, + y: value.y, + z: value.z, + } + } +} + +#[derive(Debug, Default, Copy, Clone)] +#[repr(C)] +pub struct FRotator { + pub pitch: f32, + pub yaw: f32, + pub roll: f32, +} -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone, Copy)] #[repr(C)] pub struct FLinearColor { pub r: f32,